Skip to content

Misc improvements#2520

Open
triceo wants to merge 2 commits into
TimefoldAI:mainfrom
triceo:rating
Open

Misc improvements#2520
triceo wants to merge 2 commits into
TimefoldAI:mainfrom
triceo:rating

Conversation

@triceo

@triceo triceo commented Jul 19, 2026

Copy link
Copy Markdown
Collaborator

Two unrelated things I discovered while working something else.
Will be merged as individual commits.

triceo and others added 2 commits July 20, 2026 09:03
…ch node

ForEachFilteredUniNode.update() re-entered insert()/retract() on filter
transitions, corrupting tupleCountWithoutFiltering: a fact flipping
fail->pass was counted twice, and a fact flipping pass->fail was
decremented while still inserted.

The activation protocol (ActivitySupport) reads that counter exactly
once, at the first settle() — and shadow variable initialization
delivers updates between fact insertion and that first settle. A
pass->fail transition in that window could therefore zero the counter
and permanently remove the node and its downstream constraint from
propagation, silently producing wrong scores for the rest of the
session.

Counter mutations are now exclusive to the external insert()/retract()
entry points; update()'s filter transitions route through non-counting
helpers.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ntaining-index removal

put() already holds the downstream indexer reference when it builds the
per-child bookkeeping, but remove() looked it up again from the indexer
map, hashing a user key per child per removal. The child entry now
carries the indexer alongside the key (Pair becomes Triple inside
CompositeListEntry), and remove() uses it directly. In
conference_scheduling this lookup was among the top HashMap.getNode
callers.
@triceo
triceo marked this pull request as ready for review July 20, 2026 07:07
Copilot AI review requested due to automatic review settings July 20, 2026 07:07
@triceo triceo self-assigned this Jul 20, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR delivers two small internal correctness/robustness improvements in the Bavet implementation: it tightens ForEachFilteredUniNode activity tracking to be based on “facts inserted” rather than “tuples propagated after filtering”, and it makes CONTAINING/CONTAINING_ANY_OF indexers more resilient during removal by carrying the resolved downstream indexer along with each child entry.

Changes:

  • Adjust ForEachFilteredUniNode to track activity based on the number of inserted facts regardless of filter outcome, and avoid corrupting that count during filter transitions on update().
  • Make ContainingIndexer and ContainingAnyOfIndexer store the downstream Indexer in each CompositeListEntry child to avoid re-lookup on remove() and enable safe key reuse after last tuple removal.
  • Add regression tests covering filtered-node activity behavior and key reuse after last removal for both indexer types.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
core/src/main/java/ai/timefold/solver/core/impl/bavet/uni/ForEachFilteredUniNode.java Fixes activity bookkeeping so filtered-out facts still keep the node active until retracted.
core/src/test/java/ai/timefold/solver/core/impl/bavet/uni/ForEachFilteredUniNodeActivityTest.java Adds regression coverage for activity behavior across filter transitions and activation timing.
core/src/main/java/ai/timefold/solver/core/impl/bavet/common/index/ContainingIndexer.java Stores downstream indexer per child entry to make removal robust and allow downstream map cleanup without breaking later operations.
core/src/main/java/ai/timefold/solver/core/impl/bavet/common/index/ContainingAnyOfIndexer.java Same as above for CONTAINING_ANY_OF behavior.
core/src/main/java/ai/timefold/solver/core/impl/util/CompositeListEntry.java Updates child payload from Pair to Triple to carry downstream indexer alongside key and list entry.
core/src/test/java/ai/timefold/solver/core/impl/bavet/common/index/ContainingIndexerTest.java Adds regression test for key reuse after last tuple removal.
core/src/test/java/ai/timefold/solver/core/impl/bavet/common/index/ContainingAnyOfIndexerTest.java Adds regression test for key reuse after last tuple removal.

Comment on lines +5 to +21
import ai.timefold.solver.core.impl.bavet.common.index.Indexer;
import ai.timefold.solver.core.impl.bavet.common.joiner.JoinerType;

import org.jspecify.annotations.NullMarked;

/**
* Allows to store the same tuple in multiple downstream {@link ElementAwareLinkedList}s.
* Needed by {@link JoinerType#CONTAINING}, {@link JoinerType#CONTAINING_ANY_OF}, ...
* Each child carries the downstream {@link Indexer} resolved at put() time,
* so that remove() does not need to look it up again.
*
* @param <T> the tuple type
*/
@NullMarked
public record CompositeListEntry<Key_, T>(T element, List<Pair<Key_, ListEntry<T>>> children) implements ListEntry<T> {
public record CompositeListEntry<Key_, T>(T element, List<Triple<Key_, Indexer<T>, ListEntry<T>>> children)
implements
ListEntry<T> {
@sonarqubecloud

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants