Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public enum PreviewFeature {

DIVERSIFIED_LATE_ACCEPTANCE,
PLANNING_SOLUTION_DIFF,
IGNORE_INCONSISTENT_SOLUTIONS,
/**
* Unlike other preview features, Neighborhoods are an active research project.
* It is intended to simplify the creation of custom moves, eventually replacing move selectors.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ static <T> T loadOrDefault(Function<TimefoldSolverEnterpriseService, T> builder,
}
}

TopologicalOrderGraph buildTopologyGraph(int size);
TopologicalOrderGraph buildTopologyGraph(int size, boolean ignoreInconsistentSolutions);

/**
* Will create new classes that apply node-sharing to the given {@link ConstraintProvider}.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ public void updateShadowVariables(Class<Solution_> solutionClass, Object... enti
.formatted(missingShadowVariableTypeList));
}
// No solution, we trigger all supported events manually
var session = InternalShadowVariableSession.build(solutionDescriptor, entities);
var session = InternalShadowVariableSession.build(solutionDescriptor, false, entities);
// Update all built-in shadow variables
var listVariableDescriptor = solutionDescriptor.getListVariableDescriptor();
if (listVariableDescriptor == null) {
Expand All @@ -123,12 +123,14 @@ private record InternalShadowVariableSession<Solution_>(SolutionDescriptor<Solut

public static <Solution_> InternalShadowVariableSession<Solution_> build(
SolutionDescriptor<Solution_> solutionDescriptor,
boolean ignoreInconsistentSolutions,
Object... entities) {
return new InternalShadowVariableSession<>(solutionDescriptor,
DefaultShadowVariableSessionFactory.buildGraph(
new DefaultShadowVariableSessionFactory.GraphDescriptor<>(solutionDescriptor,
ChangedVariableNotifier.empty(), entities)
.assertingNoReferencedMissingEntities()));
.assertingNoReferencedMissingEntities(),
ignoreInconsistentSolutions));
}

/**
Expand Down Expand Up @@ -331,7 +333,7 @@ public void setWorkingSolutionWithoutUpdatingShadows(Solution_ workingSolution)
}

@Override
public InnerScore<Score_> calculateScore() {
public InnerScore<Score_> innerCalculateScore() {
throw new UnsupportedOperationException();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ public abstract sealed class AbstractVariableReferenceGraph<Solution_, ChangeTra
* and {@link #afterVariableChanged(VariableMetaModel, Object)}
* can short circuit.
*/
abstract void innerUpdateChanged();
abstract boolean innerUpdateChanged();

/**
* Called when any non-declarative source variable for the
Expand All @@ -90,10 +90,11 @@ public abstract sealed class AbstractVariableReferenceGraph<Solution_, ChangeTra
abstract void markChanged(GraphNode<Solution_> changed);

@Override
public final void updateChanged() {
public final boolean updateChanged() {
isUpdating = true;
innerUpdateChanged();
var success = innerUpdateChanged();
isUpdating = false;
return success;
}

private BaseTopologicalOrderGraph.NodeTopologicalOrder[] buildNodeTopologicalOrderArray(BaseTopologicalOrderGraph graph,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,11 @@ private ConsistencyTracker(boolean isFrozen) {
}

public static <Solution_> ConsistencyTracker<Solution_> frozen(SolutionDescriptor<Solution_> solutionDescriptor,
boolean ignoreInconsistentSolutions,
Object[] entityOrFacts) {
var out = new ConsistencyTracker<Solution_>(true);
out.setUnknownConsistencyFromEntityShadowVariablesInconsistent(solutionDescriptor, entityOrFacts);
out.setUnknownConsistencyFromEntityShadowVariablesInconsistent(solutionDescriptor, ignoreInconsistentSolutions,
entityOrFacts);
return out;
}

Expand All @@ -46,6 +48,7 @@ public static <Solution_> ConsistencyTracker<Solution_> frozen(SolutionDescripto
* (regardless of its actual consistency in the graph).
*/
void setUnknownConsistencyFromEntityShadowVariablesInconsistent(SolutionDescriptor<Solution_> solutionDescriptor,
boolean ignoreInconsistentSolutions,
Object[] entityOrFacts) { // Not private so DefaultVariableReferenceGraph javadoc can reference it.
var entities = Arrays.stream(entityOrFacts)
.filter(maybeEntity -> solutionDescriptor.hasEntityDescriptor(maybeEntity.getClass()))
Expand All @@ -60,7 +63,8 @@ void setUnknownConsistencyFromEntityShadowVariablesInconsistent(SolutionDescript
new DefaultShadowVariableSessionFactory.GraphDescriptor<>(solutionDescriptor,
ChangedVariableNotifier.empty(), entities)
.withConsistencyTracker(this)
.assertingNoReferencedMissingEntities());
.assertingNoReferencedMissingEntities(),
ignoreInconsistentSolutions);

// Graph will either be DefaultVariableReferenceGraph or EmptyVariableReferenceGraph
// If it is empty, we don't need to do anything.
Expand All @@ -71,7 +75,7 @@ void setUnknownConsistencyFromEntityShadowVariablesInconsistent(SolutionDescript

/**
* If true, consistency and shadow variables are frozen and should not be updated.
* ConstraintVerifier creates a frozen instance via {@link #frozen(SolutionDescriptor, Object[])}.
* ConstraintVerifier creates a frozen instance via {@link #frozen(SolutionDescriptor, boolean, Object[])}.
*
* @return true if consistency and shadow variables are frozen and should not be updated
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public void afterVariableChanged(VariableMetaModel<Solution_, ?, ?> variableMeta
entity);
}

public void updateVariables() {
graph.updateChanged();
public boolean updateVariables() {
return graph.updateChanged();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -162,29 +162,31 @@ public ChangedVariableNotifier<Solution_> changedVariableNotifier() {
}
}

public static <Solution_> VariableReferenceGraph buildGraph(GraphDescriptor<Solution_> graphDescriptor) {
public static <Solution_> VariableReferenceGraph buildGraph(GraphDescriptor<Solution_> graphDescriptor,
boolean ignoreInconsistentSolutions) {
var graphStructureAndDirection = GraphStructure.determineGraphStructure(graphDescriptor.solutionDescriptor(),
graphDescriptor.entities());
LOGGER.trace("Shadow variable graph structure: {}", graphStructureAndDirection);
return buildGraphForStructureAndDirection(graphStructureAndDirection, graphDescriptor);
return buildGraphForStructureAndDirection(graphStructureAndDirection, graphDescriptor, ignoreInconsistentSolutions);
}

static <Solution_> VariableReferenceGraph buildGraphForStructureAndDirection(
GraphStructure.GraphStructureAndDirection graphStructureAndDirection, GraphDescriptor<Solution_> graphDescriptor) {
GraphStructure.GraphStructureAndDirection graphStructureAndDirection, GraphDescriptor<Solution_> graphDescriptor,
boolean ignoreInconsistentSolutions) {
return switch (graphStructureAndDirection.structure()) {
case EMPTY -> EmptyVariableReferenceGraph.INSTANCE;
case SINGLE_DIRECTIONAL_PARENT -> {
var scoreDirector =
graphDescriptor.variableReferenceGraphBuilder().changedVariableNotifier.innerScoreDirector();
if (scoreDirector == null) {
yield buildArbitraryGraph(graphDescriptor);
yield buildArbitraryGraph(graphDescriptor, ignoreInconsistentSolutions);
}
yield buildSingleDirectionalParentGraph(graphDescriptor, graphStructureAndDirection);
}
case ARBITRARY_SINGLE_ENTITY_AT_MOST_ONE_DIRECTIONAL_PARENT_TYPE ->
buildArbitrarySingleEntityGraph(graphDescriptor);
buildArbitrarySingleEntityGraph(graphDescriptor, ignoreInconsistentSolutions);
case NO_DYNAMIC_EDGES, ARBITRARY ->
buildArbitraryGraph(graphDescriptor);
buildArbitraryGraph(graphDescriptor, ignoreInconsistentSolutions);
};
}

Expand Down Expand Up @@ -280,7 +282,8 @@ yield new TopologicalSorter(listStateSupply::getPreviousElement,
};
}

private static <Solution_> VariableReferenceGraph buildArbitraryGraph(GraphDescriptor<Solution_> graphDescriptor) {
private static <Solution_> VariableReferenceGraph buildArbitraryGraph(GraphDescriptor<Solution_> graphDescriptor,
boolean ignoreInconsistentSolutions) {
var declarativeShadowVariableDescriptors =
graphDescriptor.solutionDescriptor().getDeclarativeShadowVariableDescriptors();
var variableIdToUpdater = EntityVariableUpdaterLookup.<Solution_> entityIndependentLookup();
Expand All @@ -294,13 +297,14 @@ private static <Solution_> VariableReferenceGraph buildArbitraryGraph(GraphDescr
graphDescriptor,
declarativeShadowVariableDescriptors, variableIdToUpdater);
return buildVariableReferenceGraph(graphDescriptor, declarativeShadowVariableDescriptors,
declarativeShadowVariableToAliasMap);
declarativeShadowVariableToAliasMap, ignoreInconsistentSolutions);
}

private static <Solution_> VariableReferenceGraph buildVariableReferenceGraph(
GraphDescriptor<Solution_> graphDescriptor,
List<DeclarativeShadowVariableDescriptor<Solution_>> declarativeShadowVariableDescriptors,
Map<VariableMetaModel<?, ?, ?>, Set<VariableSourceReference>> declarativeShadowVariableToAliasMap) {
Map<VariableMetaModel<?, ?, ?>, Set<VariableSourceReference>> declarativeShadowVariableToAliasMap,
boolean ignoreInconsistentSolutions) {
// Create variable processors for each declarative shadow variable descriptor
for (var declarativeShadowVariable : declarativeShadowVariableDescriptors) {
var fromVariableId = declarativeShadowVariable.getVariableMetaModel();
Expand All @@ -315,7 +319,8 @@ private static <Solution_> VariableReferenceGraph buildVariableReferenceGraph(
// Create the fixed edges in the graph
createFixedVariableRelationEdges(graphDescriptor.variableReferenceGraphBuilder(), graphDescriptor.entities(),
declarativeShadowVariableDescriptors);
return graphDescriptor.variableReferenceGraphBuilder().build(graphDescriptor.graphCreator());
return graphDescriptor.variableReferenceGraphBuilder().build(graphDescriptor.graphCreator(),
ignoreInconsistentSolutions);
}

private record GroupVariableUpdaterInfo<Solution_>(
Expand Down Expand Up @@ -443,7 +448,7 @@ public List<VariableUpdaterInfo<Solution_>> getUpdatersForEntityVariable(Object
}

private static <Solution_> VariableReferenceGraph buildArbitrarySingleEntityGraph(
GraphDescriptor<Solution_> graphDescriptor) {
GraphDescriptor<Solution_> graphDescriptor, boolean ignoreInconsistentSolutions) {
var declarativeShadowVariableDescriptors =
graphDescriptor.solutionDescriptor().getDeclarativeShadowVariableDescriptors();
// Use a dependent lookup; if an entity does not use groups, then all variables can share the same node.
Expand Down Expand Up @@ -475,7 +480,7 @@ private static <Solution_> VariableReferenceGraph buildArbitrarySingleEntityGrap
(entity, declarativeShadowVariable, variableId) -> variableIdToGroupedUpdater.get(variableId)
.getUpdatersForEntityVariable(entity, declarativeShadowVariable));
return buildVariableReferenceGraph(graphDescriptor, declarativeShadowVariableDescriptors,
declarativeShadowVariableToAliasMap);
declarativeShadowVariableToAliasMap, ignoreInconsistentSolutions);
}

private static <Solution_> Map<VariableMetaModel<?, ?, ?>, Set<VariableSourceReference>> createGraphNodes(
Expand Down Expand Up @@ -691,18 +696,21 @@ private static <Solution_> void createFixedVariableRelationEdges(
}

public DefaultShadowVariableSession<Solution_> forSolution(ConsistencyTracker<Solution_> consistencyTracker,
boolean ignoreInconsistentSolutions,
Solution_ solution) {
var entities = new ArrayList<>();
solutionDescriptor.visitAllEntities(solution, entities::add);
return forEntities(consistencyTracker, entities.toArray());
return forEntities(consistencyTracker, ignoreInconsistentSolutions, entities.toArray());
}

public DefaultShadowVariableSession<Solution_> forEntities(ConsistencyTracker<Solution_> consistencyTracker,
boolean ignoreInconsistentSolutions,
Object... entities) {
var graph = buildGraph(
new GraphDescriptor<>(solutionDescriptor, ChangedVariableNotifier.of(scoreDirector), entities)
.withConsistencyTracker(consistencyTracker)
.withGraphCreator(graphCreator));
.withGraphCreator(graphCreator),
ignoreInconsistentSolutions);
return new DefaultShadowVariableSession<>(graph);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ public int getTopologicalOrder(int node) {
}

@Override
public void commitChanges(BitSet changed) {
public boolean commitChanges(BitSet changed) {
var index = new MutableInt(1);
var stackIndex = new MutableInt(0);
var size = forwardEdges.length;
Expand All @@ -126,6 +126,7 @@ public void commitChanges(BitSet changed) {
var lowMap = new int[size];
var onStackSet = new boolean[size];
var components = new ArrayList<BitSet>();
var anyLooped = false;
componentMap.clear();

for (var node = 0; node < size; node++) {
Expand All @@ -139,6 +140,7 @@ public void commitChanges(BitSet changed) {
var component = components.get(i);
var componentSize = component.cardinality();
var isComponentLooped = componentSize != 1;
anyLooped |= isComponentLooped;
var componentNodes = new ArrayList<Integer>(componentSize);
for (var node = component.nextSetBit(0); node >= 0; node = component.nextSetBit(node + 1)) {
nodeIdToTopologicalOrderMap[node] = ordIndex;
Expand All @@ -159,6 +161,7 @@ public void commitChanges(BitSet changed) {
}
}
}
return anyLooped;
}

private void strongConnect(int node, MutableInt index, MutableInt stackIndex, int[] stack,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@
final class DefaultVariableReferenceGraph<Solution_> extends AbstractVariableReferenceGraph<Solution_, BitSet> {
// These structures are mutable.
private final AffectedEntitiesUpdater<Solution_> affectedEntitiesUpdater;
private final boolean ignoreInconsistentSolutions;

public DefaultVariableReferenceGraph(VariableReferenceGraphBuilder<Solution_> outerGraph,
IntFunction<TopologicalOrderGraph> graphCreator) {
IntFunction<TopologicalOrderGraph> graphCreator,
boolean ignoreInconsistentSolutions) {
super(outerGraph, graphCreator);

this.ignoreInconsistentSolutions = ignoreInconsistentSolutions;
var entityToVariableReferenceMap = new IdentityHashMap<Object, List<GraphNode<Solution_>>>();
for (var instance : nodeList) {
if (instance.groupEntityIds() == null) {
Expand Down Expand Up @@ -49,12 +51,16 @@ void markChanged(@NonNull GraphNode<Solution_> node) {
}

@Override
void innerUpdateChanged() {
boolean innerUpdateChanged() {
if (changeTracker.isEmpty()) {
return;
return true;
}
if (graph.commitChanges(changeTracker) && ignoreInconsistentSolutions) {
return false;
} else {
affectedEntitiesUpdater.accept(changeTracker);
return true;
}
graph.commitChanges(changeTracker);
affectedEntitiesUpdater.accept(changeTracker);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ final class EmptyVariableReferenceGraph implements VariableReferenceGraph {
public static final EmptyVariableReferenceGraph INSTANCE = new EmptyVariableReferenceGraph();

@Override
public void updateChanged() {
public boolean updateChanged() {
// No need to do anything.
return true;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,13 +74,13 @@
}

@Override
void innerUpdateChanged() {
boolean innerUpdateChanged() {

Check failure on line 77 in core/src/main/java/ai/timefold/solver/core/impl/domain/variable/declarative/FixedVariableReferenceGraph.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this method to not always return the same value.

See more on https://sonarcloud.io/project/issues?id=ai.timefold%3Atimefold-solver&issues=AZ7gcmNVpLzj5ptQb--h&open=AZ7gcmNVpLzj5ptQb--h&pullRequest=2396
BitSet visited;
if (!changeTracker.isEmpty()) {
visited = new BitSet(nodeList.size());
visited.set(changeTracker.peek().nodeId());
} else {
return;
return true;
}

// NOTE: This assumes the user did not add any fixed loops to
Expand All @@ -104,5 +104,6 @@
}
}
isChanged.clear();
return true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ public SingleDirectionalParentVariableReferenceGraph(
}

@Override
public void updateChanged() {
public boolean updateChanged() {
isUpdating = true;
changedEntities.sort(topologicalOrderComparator);
for (var changedEntity : changedEntities) {
Expand All @@ -94,6 +94,7 @@ public void updateChanged() {
isUpdating = false;
changedEntities.clear();
keyToLastProcessedObject.clear();
return true;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ public interface TopologicalOrderGraph extends BaseTopologicalOrderGraph {
* Called when all edge modifications are queued.
* After this method returns, {@link #getTopologicalOrder(int)}
* must be accurate for every node in the graph.
*
* @return true if the graph has loops, false otherwise
*/
void commitChanges(BitSet changed);
boolean commitChanges(BitSet changed);

/**
* Called on graph creation to supply metadata about the graph nodes.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@ public sealed interface VariableReferenceGraph
* Called after all {@link ai.timefold.solver.core.impl.domain.variable.VariableListener} are
* triggered. Declarative {@link ai.timefold.solver.core.api.domain.variable.ShadowVariable}
* are guaranteed to be the last variables to update.
*
* @return true if the update successful; false otherwise
*/
void updateChanged();
boolean updateChanged();

/**
* Called before the variable corresponding to the {@link VariableMetaModel} on the given entity changes.
Expand Down
Loading
Loading