diff --git a/benchmark/src/main/resources/benchmark.xsd b/benchmark/src/main/resources/benchmark.xsd
index 86ede21c93..3041c71ccb 100644
--- a/benchmark/src/main/resources/benchmark.xsd
+++ b/benchmark/src/main/resources/benchmark.xsd
@@ -458,6 +458,9 @@
+
+
+
@@ -2819,6 +2822,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/core/src/build/revapi-differences.json b/core/src/build/revapi-differences.json
index 537f4e3860..37cbc3628e 100644
--- a/core/src/build/revapi-differences.json
+++ b/core/src/build/revapi-differences.json
@@ -450,6 +450,17 @@
"new": "class ai.timefold.solver.core.config.heuristic.selector.value.ValueSelectorConfig",
"annotation": "@org.jspecify.annotations.NullMarked",
"justification": "Update config"
+ },
+ {
+ "ignore": true,
+ "code": "java.annotation.attributeValueChanged",
+ "old": "class ai.timefold.solver.core.config.score.director.ScoreDirectorFactoryConfig",
+ "new": "class ai.timefold.solver.core.config.score.director.ScoreDirectorFactoryConfig",
+ "annotationType": "jakarta.xml.bind.annotation.XmlType",
+ "attribute": "propOrder",
+ "oldValue": "{\"easyScoreCalculatorClass\", \"easyScoreCalculatorCustomProperties\", \"constraintProviderClass\", \"constraintProviderCustomProperties\", \"constraintStreamImplType\", \"incrementalScoreCalculatorClass\", \"incrementalScoreCalculatorCustomProperties\", \"scoreDrlList\", \"initializingScoreTrend\", \"assertionScoreDirectorFactory\"}",
+ "newValue": "{\"easyScoreCalculatorClass\", \"easyScoreCalculatorCustomProperties\", \"constraintProviderClass\", \"constraintProviderCustomProperties\", \"constraintStreamImplType\", \"constraintStreamAutomaticNodeSharing\", \"constraintStreamProfilingMode\", \"incrementalScoreCalculatorClass\", \"incrementalScoreCalculatorCustomProperties\", \"scoreDrlList\", \"initializingScoreTrend\", \"assertionScoreDirectorFactory\"}",
+ "justification": "Add constraint profiling to config"
}
]
}
diff --git a/core/src/main/java/ai/timefold/solver/core/config/score/director/ConstraintProfilingMode.java b/core/src/main/java/ai/timefold/solver/core/config/score/director/ConstraintProfilingMode.java
new file mode 100644
index 0000000000..cbf2aa814a
--- /dev/null
+++ b/core/src/main/java/ai/timefold/solver/core/config/score/director/ConstraintProfilingMode.java
@@ -0,0 +1,22 @@
+package ai.timefold.solver.core.config.score.director;
+
+/**
+ * Controls how constraints are profiled.
+ * Enabling profiling have a minor performance impact.
+ */
+public enum ConstraintProfilingMode {
+ /**
+ * Disables profiling.
+ */
+ NONE,
+
+ /**
+ * Profile by the method an operation was defined in.
+ */
+ BY_METHOD,
+
+ /**
+ * Profile by the line an operation was defined on.
+ */
+ BY_LINE
+}
diff --git a/core/src/main/java/ai/timefold/solver/core/config/score/director/ScoreDirectorFactoryConfig.java b/core/src/main/java/ai/timefold/solver/core/config/score/director/ScoreDirectorFactoryConfig.java
index c402d3e6fc..6b88e40885 100644
--- a/core/src/main/java/ai/timefold/solver/core/config/score/director/ScoreDirectorFactoryConfig.java
+++ b/core/src/main/java/ai/timefold/solver/core/config/score/director/ScoreDirectorFactoryConfig.java
@@ -27,6 +27,7 @@
"constraintProviderCustomProperties",
"constraintStreamImplType",
"constraintStreamAutomaticNodeSharing",
+ "constraintStreamProfilingMode",
"incrementalScoreCalculatorClass",
"incrementalScoreCalculatorCustomProperties",
"scoreDrlList",
@@ -46,6 +47,7 @@ public class ScoreDirectorFactoryConfig extends AbstractConfig constraintProviderCustomProperties = null;
protected ConstraintStreamImplType constraintStreamImplType;
protected Boolean constraintStreamAutomaticNodeSharing;
+ protected ConstraintProfilingMode constraintStreamProfilingMode;
protected Class extends IncrementalScoreCalculator> incrementalScoreCalculatorClass = null;
@@ -126,6 +128,14 @@ public void setConstraintStreamAutomaticNodeSharing(@Nullable Boolean constraint
this.constraintStreamAutomaticNodeSharing = constraintStreamAutomaticNodeSharing;
}
+ public ConstraintProfilingMode getConstraintStreamProfilingMode() {
+ return constraintStreamProfilingMode;
+ }
+
+ public void setConstraintStreamProfilingMode(ConstraintProfilingMode constraintStreamProfilingMode) {
+ this.constraintStreamProfilingMode = constraintStreamProfilingMode;
+ }
+
public @Nullable Class extends IncrementalScoreCalculator> getIncrementalScoreCalculatorClass() {
return incrementalScoreCalculatorClass;
}
@@ -227,6 +237,12 @@ public void setAssertionScoreDirectorFactory(@Nullable ScoreDirectorFactoryConfi
return this;
}
+ public @NonNull ScoreDirectorFactoryConfig
+ withConstraintStreamProfiling(@NonNull ConstraintProfilingMode constraintStreamProfiling) {
+ this.constraintStreamProfilingMode = constraintStreamProfiling;
+ return this;
+ }
+
public @NonNull ScoreDirectorFactoryConfig
withIncrementalScoreCalculatorClass(
@NonNull Class extends IncrementalScoreCalculator> incrementalScoreCalculatorClass) {
@@ -288,6 +304,8 @@ public ScoreDirectorFactoryConfig withScoreDrls(String... scoreDrls) {
constraintStreamImplType, inheritedConfig.getConstraintStreamImplType());
constraintStreamAutomaticNodeSharing = ConfigUtils.inheritOverwritableProperty(constraintStreamAutomaticNodeSharing,
inheritedConfig.getConstraintStreamAutomaticNodeSharing());
+ constraintStreamProfilingMode = ConfigUtils.inheritOverwritableProperty(constraintStreamProfilingMode,
+ inheritedConfig.getConstraintStreamProfilingMode());
incrementalScoreCalculatorClass = ConfigUtils.inheritOverwritableProperty(
incrementalScoreCalculatorClass, inheritedConfig.getIncrementalScoreCalculatorClass());
incrementalScoreCalculatorCustomProperties = ConfigUtils.inheritMergeableMapProperty(
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/bavet/AbstractSession.java b/core/src/main/java/ai/timefold/solver/core/impl/bavet/AbstractSession.java
index f4f1320ff8..262b78eaf2 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/bavet/AbstractSession.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/bavet/AbstractSession.java
@@ -63,4 +63,8 @@ public void settle() {
nodeNetwork.settle();
}
+ public final void summarizeProfileIfPresent() {
+ nodeNetwork.summarizeProfileIfPresent();
+ }
+
}
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/bavet/NodeNetwork.java b/core/src/main/java/ai/timefold/solver/core/impl/bavet/NodeNetwork.java
index 07d21536ab..e31b99fa87 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/bavet/NodeNetwork.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/bavet/NodeNetwork.java
@@ -8,8 +8,12 @@
import ai.timefold.solver.core.api.domain.solution.PlanningSolution;
import ai.timefold.solver.core.impl.bavet.common.BavetRootNode;
+import ai.timefold.solver.core.impl.bavet.common.ConstraintProfiler;
import ai.timefold.solver.core.impl.bavet.common.Propagator;
+import org.jspecify.annotations.NullMarked;
+import org.jspecify.annotations.Nullable;
+
/**
* Represents Bavet's network of nodes, specific to a particular session.
* Nodes only used by disabled constraints have already been removed.
@@ -19,10 +23,11 @@
* @param layeredNodes nodes grouped first by their layer, then by their index within the layer;
* propagation needs to happen in this order.
*/
+@NullMarked
public record NodeNetwork(Map, List>> declaredClassToNodeMap,
- Propagator[][] layeredNodes) {
+ Propagator[][] layeredNodes, @Nullable ConstraintProfiler constraintProfiler) {
- public static final NodeNetwork EMPTY = new NodeNetwork(Map.of(), new Propagator[0][0]);
+ public static final NodeNetwork EMPTY = new NodeNetwork(Map.of(), new Propagator[0][0], null);
public int forEachNodeCount() {
return declaredClassToNodeMap.size();
@@ -70,6 +75,12 @@ private static void settleLayer(Propagator[] nodesInLayer) {
}
}
+ public void summarizeProfileIfPresent() {
+ if (constraintProfiler != null) {
+ constraintProfiler.summarize();
+ }
+ }
+
@Override
public boolean equals(Object o) {
if (this == o)
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/bavet/common/AbstractNode.java b/core/src/main/java/ai/timefold/solver/core/impl/bavet/common/AbstractNode.java
index f66816b2eb..b0b4342b33 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/bavet/common/AbstractNode.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/bavet/common/AbstractNode.java
@@ -1,14 +1,23 @@
package ai.timefold.solver.core.impl.bavet.common;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraintSession;
+import org.jspecify.annotations.NullMarked;
+import org.jspecify.annotations.Nullable;
+
/**
* @see PropagationQueue Description of the propagation mechanism.
*/
+@NullMarked
public abstract class AbstractNode {
private long id;
private long layerIndex = -1;
+ private @Nullable Set locationSet;
/**
* Instead of calling the propagation directly from here,
@@ -29,6 +38,20 @@ public final void setId(long id) {
this.id = id;
}
+ public Set getLocationSet() {
+ if (locationSet == null) {
+ return Collections.emptySet();
+ }
+ return locationSet;
+ }
+
+ public void addLocationSet(Set locationSet) {
+ if (this.locationSet == null) {
+ this.locationSet = new LinkedHashSet<>();
+ }
+ this.locationSet.addAll(locationSet);
+ }
+
public final void setLayerIndex(long layerIndex) {
if (layerIndex < 0) {
throw new IllegalArgumentException("Impossible state: layer index (" + layerIndex + ") must be at least 0.");
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/bavet/common/AbstractNodeBuildHelper.java b/core/src/main/java/ai/timefold/solver/core/impl/bavet/common/AbstractNodeBuildHelper.java
index 7b3f605910..15b47b9b6f 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/bavet/common/AbstractNodeBuildHelper.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/bavet/common/AbstractNodeBuildHelper.java
@@ -17,6 +17,10 @@
import ai.timefold.solver.core.impl.bavet.common.tuple.RightTupleLifecycle;
import ai.timefold.solver.core.impl.bavet.common.tuple.TupleLifecycle;
+import org.jspecify.annotations.NullMarked;
+import org.jspecify.annotations.Nullable;
+
+@NullMarked
public abstract class AbstractNodeBuildHelper {
private final Set activeStreamSet;
@@ -24,15 +28,22 @@ public abstract class AbstractNodeBuildHelper {
private final Map> tupleLifecycleMap;
private final Map storeIndexMap;
+ @Nullable
+ private final ConstraintProfiler constraintProfiler;
+
+ @Nullable
private List reversedNodeList;
+ private long nextLifecycleProfilingId = 0;
- protected AbstractNodeBuildHelper(Set activeStreamSet) {
+ protected AbstractNodeBuildHelper(Set activeStreamSet,
+ @Nullable ConstraintProfiler constraintProfiler) {
this.activeStreamSet = activeStreamSet;
int activeStreamSetSize = activeStreamSet.size();
this.nodeCreatorMap = new HashMap<>(Math.max(16, activeStreamSetSize));
this.tupleLifecycleMap = new HashMap<>(Math.max(16, activeStreamSetSize));
this.storeIndexMap = new HashMap<>(Math.max(16, activeStreamSetSize / 2));
this.reversedNodeList = new ArrayList<>(activeStreamSetSize);
+ this.constraintProfiler = constraintProfiler;
}
public boolean isStreamActive(Stream_ stream) {
@@ -45,6 +56,7 @@ public void addNode(AbstractNode node, Stream_ creator) {
public void addNode(AbstractNode node, Stream_ creator, Stream_ parent) {
reversedNodeList.add(node);
+ node.addLocationSet(creator.getLocationSet());
nodeCreatorMap.put(node, creator);
if (!(node instanceof BavetRootNode>)) {
if (parent == null) {
@@ -57,6 +69,7 @@ public void addNode(AbstractNode node, Stream_ creator, Stream_ parent) {
public void addNode(AbstractNode node, Stream_ creator, Stream_ leftParent, Stream_ rightParent) {
reversedNodeList.add(node);
+ node.addLocationSet(creator.getLocationSet());
nodeCreatorMap.put(node, creator);
putInsertUpdateRetract(leftParent, TupleLifecycle.ofLeft((LeftTupleLifecycle extends AbstractTuple>) node));
putInsertUpdateRetract(rightParent, TupleLifecycle.ofRight((RightTupleLifecycle extends AbstractTuple>) node));
@@ -64,6 +77,11 @@ public void addNode(AbstractNode node, Stream_ creator, Stream_ leftParent, Stre
public void putInsertUpdateRetract(Stream_ stream,
TupleLifecycle tupleLifecycle) {
+ if (constraintProfiler != null) {
+ tupleLifecycle = TupleLifecycle.profiling(constraintProfiler, nextLifecycleProfilingId,
+ stream, tupleLifecycle);
+ nextLifecycleProfilingId++;
+ }
tupleLifecycleMap.put(stream, tupleLifecycle);
}
@@ -147,11 +165,14 @@ public AbstractNode findParentNode(Stream_ childNodeCreator) {
}
public static NodeNetwork buildNodeNetwork(List nodeList,
- Map, List>> declaredClassToNodeMap) {
+ Map, List>> declaredClassToNodeMap,
+ AbstractNodeBuildHelper> nodeBuildHelper) {
var layerMap = new TreeMap>();
for (var node : nodeList) {
- layerMap.computeIfAbsent(node.getLayerIndex(), k -> new ArrayList<>())
- .add(node.getPropagator());
+ var layer = node.getLayerIndex();
+ var propagator = node.getPropagator();
+ layerMap.computeIfAbsent(layer, k -> new ArrayList<>())
+ .add(propagator);
}
var layerCount = layerMap.size();
var layeredNodes = new Propagator[layerCount][];
@@ -159,7 +180,7 @@ public static NodeNetwork buildNodeNetwork(List nodeList,
var layer = layerMap.get((long) i);
layeredNodes[i] = layer.toArray(new Propagator[0]);
}
- return new NodeNetwork(declaredClassToNodeMap, layeredNodes);
+ return new NodeNetwork(declaredClassToNodeMap, layeredNodes, nodeBuildHelper.constraintProfiler);
}
public > List buildNodeList(Set streamSet,
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/bavet/common/BavetAbstractConstraintStream.java b/core/src/main/java/ai/timefold/solver/core/impl/bavet/common/BavetAbstractConstraintStream.java
index be636ae902..ad3fb48be3 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/bavet/common/BavetAbstractConstraintStream.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/bavet/common/BavetAbstractConstraintStream.java
@@ -1,6 +1,7 @@
package ai.timefold.solver.core.impl.bavet.common;
import java.util.ArrayList;
+import java.util.LinkedHashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
@@ -22,15 +23,22 @@ public abstract class BavetAbstractConstraintStream
extends AbstractConstraintStream
implements BavetStream {
+ private static final String BAVET_IMPL_PACKAGE = "ai.timefold.solver.core.impl.bavet";
+ private static final String BAVET_SCORE_IMPL_PACKAGE = "ai.timefold.solver.core.impl.score.stream";
+ private static final String BAVET_SCORE_API_PACKAGE = "ai.timefold.solver.core.api.score.stream";
+
protected final BavetConstraintFactory constraintFactory;
protected final BavetAbstractConstraintStream parent;
protected final List> childStreamList = new ArrayList<>(2);
+ protected final Set streamLocationSet;
protected BavetAbstractConstraintStream(BavetConstraintFactory constraintFactory,
BavetAbstractConstraintStream parent) {
super(parent.getRetrievalSemantics());
this.constraintFactory = constraintFactory;
this.parent = parent;
+ this.streamLocationSet = new LinkedHashSet<>();
+ streamLocationSet.add(determineStreamLocation());
}
protected BavetAbstractConstraintStream(BavetConstraintFactory constraintFactory,
@@ -38,6 +46,28 @@ protected BavetAbstractConstraintStream(BavetConstraintFactory constr
super(retrievalSemantics);
this.constraintFactory = constraintFactory;
this.parent = null;
+ this.streamLocationSet = new LinkedHashSet<>();
+ streamLocationSet.add(determineStreamLocation());
+ }
+
+ private static ConstraintNodeLocation determineStreamLocation() {
+ return StackWalker.getInstance().walk(stack -> stack
+ .dropWhile(stackFrame -> stackFrame.getClassName().startsWith(BAVET_IMPL_PACKAGE) ||
+ stackFrame.getClassName().startsWith(BAVET_SCORE_IMPL_PACKAGE) ||
+ stackFrame.getClassName().startsWith(BAVET_SCORE_API_PACKAGE))
+ .map(stackFrame -> new ConstraintNodeLocation(stackFrame.getClassName(),
+ stackFrame.getMethodName(),
+ stackFrame.getLineNumber()))
+ .findFirst()
+ .orElseGet(ConstraintNodeLocation::unknown));
+ }
+
+ public Set getLocationSet() {
+ return streamLocationSet;
+ }
+
+ public void addLocationSet(Set locationSet) {
+ streamLocationSet.addAll(locationSet);
}
/**
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/bavet/common/BavetStream.java b/core/src/main/java/ai/timefold/solver/core/impl/bavet/common/BavetStream.java
index 5866eefdb8..55009e5220 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/bavet/common/BavetStream.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/bavet/common/BavetStream.java
@@ -1,7 +1,13 @@
package ai.timefold.solver.core.impl.bavet.common;
+import java.util.Set;
+
public interface BavetStream {
Stream_ getParent();
+ default Set getLocationSet() {
+ return Set.of(ConstraintNodeLocation.unknown());
+ }
+
}
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/bavet/common/ConstraintNodeLocation.java b/core/src/main/java/ai/timefold/solver/core/impl/bavet/common/ConstraintNodeLocation.java
new file mode 100644
index 0000000000..ef671775b9
--- /dev/null
+++ b/core/src/main/java/ai/timefold/solver/core/impl/bavet/common/ConstraintNodeLocation.java
@@ -0,0 +1,52 @@
+package ai.timefold.solver.core.impl.bavet.common;
+
+import java.util.Objects;
+
+import ai.timefold.solver.core.impl.util.Pair;
+import ai.timefold.solver.core.impl.util.Triple;
+
+import org.jspecify.annotations.NullMarked;
+
+@NullMarked
+public record ConstraintNodeLocation(String className,
+ String methodName,
+ int lineNumber) {
+
+ public static ConstraintNodeLocation unknown() {
+ return new ConstraintNodeLocation("", "", -1);
+ }
+
+ public record LocationKeyAndDisplay(Object key, String display) implements Comparable {
+ @Override
+ public boolean equals(Object object) {
+ if (!(object instanceof LocationKeyAndDisplay that))
+ return false;
+ return Objects.equals(key, that.key);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(key);
+ }
+
+ @Override
+ public String toString() {
+ return display;
+ }
+
+ @Override
+ public int compareTo(LocationKeyAndDisplay o) {
+ return display.compareTo(o.display);
+ }
+ }
+
+ public LocationKeyAndDisplay getMethodId() {
+ return new LocationKeyAndDisplay(new Pair<>(className, methodName),
+ "%s#%s".formatted(className, methodName));
+ }
+
+ public LocationKeyAndDisplay getLineId() {
+ return new LocationKeyAndDisplay(new Triple<>(className, methodName, lineNumber),
+ "%s#%s:%d".formatted(className, methodName, lineNumber));
+ }
+}
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/bavet/common/ConstraintNodeProfileId.java b/core/src/main/java/ai/timefold/solver/core/impl/bavet/common/ConstraintNodeProfileId.java
new file mode 100644
index 0000000000..2b0df8bb7e
--- /dev/null
+++ b/core/src/main/java/ai/timefold/solver/core/impl/bavet/common/ConstraintNodeProfileId.java
@@ -0,0 +1,17 @@
+package ai.timefold.solver.core.impl.bavet.common;
+
+import java.util.Set;
+
+public record ConstraintNodeProfileId(long key, Set locationSet) {
+ @Override
+ public boolean equals(Object object) {
+ if (!(object instanceof ConstraintNodeProfileId that))
+ return false;
+ return key == that.key;
+ }
+
+ @Override
+ public int hashCode() {
+ return Long.hashCode(key);
+ }
+}
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/bavet/common/ConstraintProfiler.java b/core/src/main/java/ai/timefold/solver/core/impl/bavet/common/ConstraintProfiler.java
new file mode 100644
index 0000000000..ab0973ccf8
--- /dev/null
+++ b/core/src/main/java/ai/timefold/solver/core/impl/bavet/common/ConstraintProfiler.java
@@ -0,0 +1,112 @@
+package ai.timefold.solver.core.impl.bavet.common;
+
+import java.util.Comparator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.function.Function;
+
+import ai.timefold.solver.core.config.score.director.ConstraintProfilingMode;
+import ai.timefold.solver.core.impl.util.MutableLong;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import io.micrometer.core.instrument.Clock;
+
+public final class ConstraintProfiler {
+ private final Logger logger = LoggerFactory.getLogger(getClass());
+ private final Clock clock;
+ private final ConstraintProfilingMode profilingMode;
+ private final Map profileIdToRetractRuntime;
+ private final Map profileIdToUpdateRuntime;
+ private final Map profileIdToInsertRuntime;
+
+ public ConstraintProfiler(ConstraintProfilingMode profilingMode) {
+ this(Clock.SYSTEM, profilingMode);
+ }
+
+ public ConstraintProfiler(Clock clock, ConstraintProfilingMode profilingMode) {
+ this.clock = clock;
+ this.profilingMode = profilingMode;
+ this.profileIdToRetractRuntime = new LinkedHashMap<>();
+ this.profileIdToUpdateRuntime = new LinkedHashMap<>();
+ this.profileIdToInsertRuntime = new LinkedHashMap<>();
+ }
+
+ public void register(ConstraintNodeProfileId profileId) {
+ // When phase changes, the node network is recalculated,
+ // but the profiler is reused
+ profileIdToRetractRuntime.putIfAbsent(profileId, new MutableLong());
+ profileIdToUpdateRuntime.putIfAbsent(profileId, new MutableLong());
+ profileIdToInsertRuntime.putIfAbsent(profileId, new MutableLong());
+ }
+
+ public void measure(ConstraintNodeProfileId profileId, Operation operation, Runnable measurable) {
+ var start = clock.monotonicTime();
+ measurable.run();
+ var end = clock.monotonicTime();
+ var duration = end - start;
+ switch (operation) {
+ case RETRACT -> profileIdToRetractRuntime.get(profileId).add(duration);
+ case UPDATE -> profileIdToUpdateRuntime.get(profileId).add(duration);
+ case INSERT -> profileIdToInsertRuntime.get(profileId).add(duration);
+ }
+ }
+
+ String getSummary() {
+ var summary = new StringBuilder("Constraint Profiling Summary");
+ Map methodIdToTotalRuntime = new LinkedHashMap<>();
+ var totalDuration = 0L;
+ totalDuration += getTotalDuration(methodIdToTotalRuntime, profileIdToRetractRuntime);
+ totalDuration += getTotalDuration(methodIdToTotalRuntime, profileIdToUpdateRuntime);
+ totalDuration += getTotalDuration(methodIdToTotalRuntime, profileIdToInsertRuntime);
+
+ long finalTotalDuration = totalDuration;
+ methodIdToTotalRuntime.entrySet()
+ .stream()
+ .sorted((Comparator>) Comparator
+ .comparing(entry -> (Comparable) ((Map.Entry) entry).getValue())
+ .thenComparing(entry -> ((Map.Entry) entry).getKey())
+ .reversed())
+ .forEach(entry -> {
+ var percentage = entry.getValue().doubleValue() / finalTotalDuration;
+ summary.append('\n')
+ .append(entry.getKey().display())
+ .append(' ')
+ .append(String.format("%.2f", percentage * 100))
+ .append('%');
+ });
+
+ return summary.toString();
+ }
+
+ public void summarize() {
+ logger.info(getSummary());
+
+ }
+
+ private long getTotalDuration(Map methodIdToTotalRuntime,
+ Map profileIdToRuntime) {
+ var totalDuration = 0L;
+ Function profileIdToKey = switch (profilingMode) {
+ case BY_METHOD -> ConstraintNodeLocation::getMethodId;
+ case BY_LINE -> ConstraintNodeLocation::getLineId;
+ case NONE -> throw new IllegalStateException("Impossible state: profiling is disabled");
+ };
+ for (var entry : profileIdToRuntime.entrySet()) {
+ var duration = entry.getValue().longValue();
+ for (var location : entry.getKey().locationSet()) {
+ methodIdToTotalRuntime.computeIfAbsent(profileIdToKey.apply(location), k -> new MutableLong())
+ .add(duration);
+ }
+ totalDuration += duration;
+ }
+ return totalDuration;
+ }
+
+ public enum Operation {
+ RETRACT,
+ UPDATE,
+ INSERT
+ }
+}
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/bavet/common/tuple/ProfilingTupleLifecycle.java b/core/src/main/java/ai/timefold/solver/core/impl/bavet/common/tuple/ProfilingTupleLifecycle.java
new file mode 100644
index 0000000000..e9928bab24
--- /dev/null
+++ b/core/src/main/java/ai/timefold/solver/core/impl/bavet/common/tuple/ProfilingTupleLifecycle.java
@@ -0,0 +1,31 @@
+package ai.timefold.solver.core.impl.bavet.common.tuple;
+
+import ai.timefold.solver.core.impl.bavet.common.ConstraintNodeProfileId;
+import ai.timefold.solver.core.impl.bavet.common.ConstraintProfiler;
+
+public record ProfilingTupleLifecycle(
+ ConstraintProfiler constraintProfiler,
+ ConstraintNodeProfileId profileId,
+ TupleLifecycle delegate) implements TupleLifecycle {
+ public ProfilingTupleLifecycle {
+ constraintProfiler.register(profileId);
+ }
+
+ @Override
+ public void insert(Tuple_ tuple) {
+ constraintProfiler.measure(profileId, ConstraintProfiler.Operation.INSERT,
+ () -> delegate.insert(tuple));
+ }
+
+ @Override
+ public void update(Tuple_ tuple) {
+ constraintProfiler.measure(profileId, ConstraintProfiler.Operation.UPDATE,
+ () -> delegate.update(tuple));
+ }
+
+ @Override
+ public void retract(Tuple_ tuple) {
+ constraintProfiler.measure(profileId, ConstraintProfiler.Operation.RETRACT,
+ () -> delegate.retract(tuple));
+ }
+}
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/bavet/common/tuple/TupleLifecycle.java b/core/src/main/java/ai/timefold/solver/core/impl/bavet/common/tuple/TupleLifecycle.java
index 675e6f7164..4a987e9c09 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/bavet/common/tuple/TupleLifecycle.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/bavet/common/tuple/TupleLifecycle.java
@@ -7,6 +7,9 @@
import ai.timefold.solver.core.api.function.QuadPredicate;
import ai.timefold.solver.core.api.function.TriPredicate;
+import ai.timefold.solver.core.impl.bavet.common.BavetStream;
+import ai.timefold.solver.core.impl.bavet.common.ConstraintNodeProfileId;
+import ai.timefold.solver.core.impl.bavet.common.ConstraintProfiler;
public interface TupleLifecycle {
@@ -55,6 +58,14 @@ static TupleLifecycle recording() {
return new RecordingTupleLifecycle<>();
}
+ static TupleLifecycle profiling(
+ ConstraintProfiler constraintProfiler, long lifecycleId, Stream_ stream,
+ TupleLifecycle delegate) {
+ return new ProfilingTupleLifecycle<>(constraintProfiler,
+ new ConstraintNodeProfileId(lifecycleId, stream.getLocationSet()),
+ delegate);
+ }
+
void insert(Tuple_ tuple);
void update(Tuple_ tuple);
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/DatasetSessionFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/DatasetSessionFactory.java
index 879f50cffc..4a6ac9720c 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/DatasetSessionFactory.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/DatasetSessionFactory.java
@@ -60,7 +60,7 @@ private NodeNetwork buildNodeNetwork(Set> e
// TODO implement node network visualization
throw new UnsupportedOperationException("Not implemented yet");
}
- return AbstractNodeBuildHelper.buildNodeNetwork(nodeList, declaredClassToNodeMap);
+ return AbstractNodeBuildHelper.buildNodeNetwork(nodeList, declaredClassToNodeMap, buildHelper);
}
}
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/common/DataNodeBuildHelper.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/common/DataNodeBuildHelper.java
index 9e2c02f9fb..c09107197d 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/common/DataNodeBuildHelper.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/common/DataNodeBuildHelper.java
@@ -21,7 +21,7 @@ public final class DataNodeBuildHelper extends AbstractNodeBuildHelpe
public DataNodeBuildHelper(SessionContext sessionContext,
Set> activeStreamSet) {
- super(activeStreamSet);
+ super(activeStreamSet, null);
this.sessionContext = Objects.requireNonNull(sessionContext);
}
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/director/stream/BavetConstraintStreamScoreDirector.java b/core/src/main/java/ai/timefold/solver/core/impl/score/director/stream/BavetConstraintStreamScoreDirector.java
index 1072445bdf..9a969b0fc0 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/score/director/stream/BavetConstraintStreamScoreDirector.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/score/director/stream/BavetConstraintStreamScoreDirector.java
@@ -125,6 +125,9 @@ public boolean requiresFlushing() {
public void close() {
super.close();
if (session != null) {
+ if (!derived) {
+ session.summarizeProfileIfPresent();
+ }
session = null;
}
}
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/director/stream/BavetConstraintStreamScoreDirectorFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/score/director/stream/BavetConstraintStreamScoreDirectorFactory.java
index 3815286b2c..41475428ec 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/score/director/stream/BavetConstraintStreamScoreDirectorFactory.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/score/director/stream/BavetConstraintStreamScoreDirectorFactory.java
@@ -1,11 +1,13 @@
package ai.timefold.solver.core.impl.score.director.stream;
import java.util.Arrays;
+import java.util.Objects;
import java.util.function.Consumer;
import ai.timefold.solver.core.api.score.Score;
import ai.timefold.solver.core.api.score.stream.ConstraintMetaModel;
import ai.timefold.solver.core.api.score.stream.ConstraintProvider;
+import ai.timefold.solver.core.config.score.director.ConstraintProfilingMode;
import ai.timefold.solver.core.config.score.director.ScoreDirectorFactoryConfig;
import ai.timefold.solver.core.config.solver.EnvironmentMode;
import ai.timefold.solver.core.config.util.ConfigUtils;
@@ -36,9 +38,11 @@ public final class BavetConstraintStreamScoreDirectorFactory(solutionDescriptor, constraintProvider, environmentMode);
+ return new BavetConstraintStreamScoreDirectorFactory<>(solutionDescriptor, constraintProvider, environmentMode,
+ profilingMode);
}
private static Class extends ConstraintProvider> getConstraintProviderClass(ScoreDirectorFactoryConfig config,
@@ -56,11 +60,12 @@ private static Class extends ConstraintProvider> getConstraintProviderClass(Sc
private final ConstraintMetaModel constraintMetaModel;
public BavetConstraintStreamScoreDirectorFactory(SolutionDescriptor solutionDescriptor,
- ConstraintProvider constraintProvider, EnvironmentMode environmentMode) {
+ ConstraintProvider constraintProvider, EnvironmentMode environmentMode, ConstraintProfilingMode profilingMode) {
super(solutionDescriptor);
var constraintFactory = new BavetConstraintFactory<>(solutionDescriptor, environmentMode);
constraintMetaModel = DefaultConstraintMetaModel.of(constraintFactory.buildConstraints(constraintProvider));
- constraintSessionFactory = new BavetConstraintSessionFactory<>(solutionDescriptor, constraintMetaModel);
+ constraintSessionFactory =
+ new BavetConstraintSessionFactory<>(solutionDescriptor, constraintMetaModel, profilingMode);
}
public BavetConstraintSession newSession(Solution_ workingSolution,
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/BavetConstraintFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/BavetConstraintFactory.java
index 6e4fd2652e..a1aa29cda4 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/BavetConstraintFactory.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/BavetConstraintFactory.java
@@ -105,10 +105,12 @@ public > Stream_ share(
*/
public > Stream_ share(Stream_ stream,
Consumer consumer) {
- return (Stream_) sharingStreamMap.computeIfAbsent(stream, k -> {
+ var out = (Stream_) sharingStreamMap.computeIfAbsent(stream, k -> {
consumer.accept(stream);
return stream;
});
+ out.addLocationSet(stream.getLocationSet());
+ return out;
}
// ************************************************************************
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/BavetConstraintSessionFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/BavetConstraintSessionFactory.java
index c38a493cde..436518da04 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/BavetConstraintSessionFactory.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/BavetConstraintSessionFactory.java
@@ -12,10 +12,12 @@
import ai.timefold.solver.core.api.score.Score;
import ai.timefold.solver.core.api.score.stream.Constraint;
import ai.timefold.solver.core.api.score.stream.ConstraintMetaModel;
+import ai.timefold.solver.core.config.score.director.ConstraintProfilingMode;
import ai.timefold.solver.core.impl.bavet.NodeNetwork;
import ai.timefold.solver.core.impl.bavet.common.AbstractNodeBuildHelper;
import ai.timefold.solver.core.impl.bavet.common.BavetAbstractConstraintStream;
import ai.timefold.solver.core.impl.bavet.common.BavetRootNode;
+import ai.timefold.solver.core.impl.bavet.common.ConstraintProfiler;
import ai.timefold.solver.core.impl.bavet.uni.AbstractForEachUniNode;
import ai.timefold.solver.core.impl.bavet.visual.NodeGraph;
import ai.timefold.solver.core.impl.domain.solution.descriptor.SolutionDescriptor;
@@ -25,6 +27,7 @@
import ai.timefold.solver.core.impl.score.stream.common.inliner.AbstractScoreInliner;
import ai.timefold.solver.core.impl.util.CollectionUtils;
+import org.jspecify.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.event.Level;
@@ -36,11 +39,14 @@ public final class BavetConstraintSessionFactory solutionDescriptor;
private final ConstraintMetaModel constraintMetaModel;
+ private final @Nullable ConstraintProfiler constraintProfiler;
public BavetConstraintSessionFactory(SolutionDescriptor solutionDescriptor,
- ConstraintMetaModel constraintMetaModel) {
+ ConstraintMetaModel constraintMetaModel, ConstraintProfilingMode profilingMode) {
this.solutionDescriptor = Objects.requireNonNull(solutionDescriptor);
this.constraintMetaModel = Objects.requireNonNull(constraintMetaModel);
+ this.constraintProfiler =
+ (profilingMode != ConstraintProfilingMode.NONE) ? new ConstraintProfiler(profilingMode) : null;
}
// ************************************************************************
@@ -117,14 +123,17 @@ public BavetConstraintSession buildSession(Solution_ workingSolution,
}
return new BavetConstraintSession<>(scoreInliner,
buildNodeNetwork(workingSolution, consistencyTracker, constraintStreamSet, scoreInliner,
+ constraintProfiler,
nodeNetworkVisualizationConsumer));
}
private static > NodeNetwork buildNodeNetwork(Solution_ workingSolution,
ConsistencyTracker consistencyTracker, Set> constraintStreamSet,
AbstractScoreInliner scoreInliner,
+ ConstraintProfiler profiler,
Consumer nodeNetworkVisualizationConsumer) {
- var buildHelper = new ConstraintNodeBuildHelper<>(consistencyTracker, constraintStreamSet, scoreInliner);
+ var buildHelper =
+ new ConstraintNodeBuildHelper<>(consistencyTracker, constraintStreamSet, scoreInliner, profiler);
var declaredClassToNodeMap = new LinkedHashMap, List>>();
var nodeList = buildHelper.buildNodeList(constraintStreamSet, buildHelper,
BavetAbstractConstraintStream::buildNode,
@@ -161,7 +170,7 @@ private static > NodeNetwork buildNodeNe
.buildGraphvizDOT();
nodeNetworkVisualizationConsumer.accept(visualisation);
}
- return AbstractNodeBuildHelper.buildNodeNetwork(nodeList, declaredClassToNodeMap);
+ return AbstractNodeBuildHelper.buildNodeNetwork(nodeList, declaredClassToNodeMap, buildHelper);
}
}
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/BavetPrecomputeBuildHelper.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/BavetPrecomputeBuildHelper.java
index c61a018cf7..034645da51 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/BavetPrecomputeBuildHelper.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/BavetPrecomputeBuildHelper.java
@@ -63,7 +63,8 @@ public BavetPrecomputeBuildHelper(
var buildHelper = new ConstraintNodeBuildHelper<>(new ConsistencyTracker<>(), streamSet,
AbstractScoreInliner.buildScoreInliner(new SimpleScoreDefinition(), Collections.emptyMap(),
- ConstraintMatchPolicy.DISABLED));
+ ConstraintMatchPolicy.DISABLED),
+ null);
var declaredClassToNodeMap = new LinkedHashMap, List>>();
var nodeList = buildHelper.buildNodeList(streamSet, buildHelper,
@@ -79,7 +80,7 @@ public BavetPrecomputeBuildHelper(
}
});
- this.nodeNetwork = AbstractNodeBuildHelper.buildNodeNetwork(nodeList, declaredClassToNodeMap);
+ this.nodeNetwork = AbstractNodeBuildHelper.buildNodeNetwork(nodeList, declaredClassToNodeMap, buildHelper);
this.recordingTupleLifecycle =
(RecordingTupleLifecycle) buildHelper
.getAggregatedTupleLifecycle(List.of(recordingPrecomputeConstraintStream));
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/ConstraintNodeBuildHelper.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/ConstraintNodeBuildHelper.java
index 383236c26e..a3b2512dc0 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/ConstraintNodeBuildHelper.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/ConstraintNodeBuildHelper.java
@@ -8,11 +8,14 @@
import ai.timefold.solver.core.api.score.Score;
import ai.timefold.solver.core.impl.bavet.common.AbstractNodeBuildHelper;
import ai.timefold.solver.core.impl.bavet.common.BavetAbstractConstraintStream;
+import ai.timefold.solver.core.impl.bavet.common.ConstraintProfiler;
import ai.timefold.solver.core.impl.domain.entity.descriptor.EntityDescriptor;
import ai.timefold.solver.core.impl.domain.variable.declarative.ConsistencyTracker;
import ai.timefold.solver.core.impl.score.stream.common.ForEachFilteringCriteria;
import ai.timefold.solver.core.impl.score.stream.common.inliner.AbstractScoreInliner;
+import org.jspecify.annotations.Nullable;
+
public final class ConstraintNodeBuildHelper>
extends AbstractNodeBuildHelper> {
@@ -22,8 +25,9 @@ public final class ConstraintNodeBuildHelper consistencyTracker,
Set> activeStreamSet,
- AbstractScoreInliner scoreInliner) {
- super(activeStreamSet);
+ AbstractScoreInliner scoreInliner,
+ @Nullable ConstraintProfiler profiler) {
+ super(activeStreamSet, profiler);
this.consistencyTracker = consistencyTracker;
this.scoreInliner = scoreInliner;
this.entityDescriptorToForEachCriteriaToPredicateMap = new HashMap<>();
diff --git a/core/src/main/resources/solver.xsd b/core/src/main/resources/solver.xsd
index 8c6e02fc1c..97a2f4127f 100644
--- a/core/src/main/resources/solver.xsd
+++ b/core/src/main/resources/solver.xsd
@@ -111,6 +111,8 @@
+
+
@@ -1705,6 +1707,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/core/src/test/java/ai/timefold/solver/core/impl/bavet/common/ConstraintProfilerTest.java b/core/src/test/java/ai/timefold/solver/core/impl/bavet/common/ConstraintProfilerTest.java
new file mode 100644
index 0000000000..66f21eba3b
--- /dev/null
+++ b/core/src/test/java/ai/timefold/solver/core/impl/bavet/common/ConstraintProfilerTest.java
@@ -0,0 +1,106 @@
+package ai.timefold.solver.core.impl.bavet.common;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.List;
+import java.util.Set;
+
+import ai.timefold.solver.core.config.score.director.ConstraintProfilingMode;
+
+import org.junit.jupiter.api.Test;
+
+import io.micrometer.core.instrument.MockClock;
+
+class ConstraintProfilerTest {
+ final String constraintProviderClassName = "MyConstraintProvider";
+ final ConstraintNodeProfileId A_1 = new ConstraintNodeProfileId(0, Set.of(
+ new ConstraintNodeLocation(constraintProviderClassName, "a", 1)));
+ final ConstraintNodeProfileId A_2 = new ConstraintNodeProfileId(1, Set.of(
+ new ConstraintNodeLocation(constraintProviderClassName, "a", 2)));
+ final ConstraintNodeProfileId B_1 = new ConstraintNodeProfileId(2, Set.of(
+ new ConstraintNodeLocation(constraintProviderClassName, "b", 1)));
+ final ConstraintNodeProfileId B_2 = new ConstraintNodeProfileId(3, Set.of(
+ new ConstraintNodeLocation(constraintProviderClassName, "b", 2)));
+ final ConstraintNodeProfileId AB_3 = new ConstraintNodeProfileId(4, Set.of(
+ new ConstraintNodeLocation(constraintProviderClassName, "a", 3),
+ new ConstraintNodeLocation(constraintProviderClassName, "b", 3)));
+
+ ConstraintProfiler constraintProfiler;
+ MockClock clock;
+
+ void setUp(ConstraintProfilingMode constraintProfilingMode) {
+ clock = new MockClock();
+ constraintProfiler = new ConstraintProfiler(clock, constraintProfilingMode);
+ List.of(A_1, A_2, B_1, B_2, AB_3).forEach(constraintProfiler::register);
+ }
+
+ Runnable advance(long seconds) {
+ return () -> clock.addSeconds(seconds);
+ }
+
+ @Test
+ void getSummaryByMethod() {
+ setUp(ConstraintProfilingMode.BY_METHOD);
+
+ constraintProfiler.measure(A_1, ConstraintProfiler.Operation.INSERT,
+ advance(1));
+ constraintProfiler.measure(A_2, ConstraintProfiler.Operation.RETRACT,
+ advance(2));
+ constraintProfiler.measure(B_1, ConstraintProfiler.Operation.UPDATE,
+ advance(3));
+ constraintProfiler.measure(B_2, ConstraintProfiler.Operation.INSERT,
+ advance(4));
+ constraintProfiler.measure(AB_3, ConstraintProfiler.Operation.INSERT,
+ advance(5));
+ constraintProfiler.measure(A_1, ConstraintProfiler.Operation.INSERT,
+ advance(3));
+
+ // Total A = 1 + 2 + 5 + 3 = 11
+ // Total B = 3 + 4 + 5 = 12
+ // Subtract 5 because A3 and B3 share the same node
+ // Total = 11 + 12 - 5 = 18
+
+ assertThat(constraintProfiler.getSummary())
+ .isEqualTo("""
+ Constraint Profiling Summary
+ MyConstraintProvider#b 66.67%
+ MyConstraintProvider#a 61.11%""");
+ }
+
+ @Test
+ void getSummaryByLine() {
+ setUp(ConstraintProfilingMode.BY_LINE);
+
+ constraintProfiler.measure(A_1, ConstraintProfiler.Operation.INSERT,
+ advance(1));
+ constraintProfiler.measure(A_2, ConstraintProfiler.Operation.RETRACT,
+ advance(2));
+ constraintProfiler.measure(B_1, ConstraintProfiler.Operation.UPDATE,
+ advance(3));
+ constraintProfiler.measure(B_2, ConstraintProfiler.Operation.INSERT,
+ advance(4));
+ constraintProfiler.measure(AB_3, ConstraintProfiler.Operation.INSERT,
+ advance(5));
+ constraintProfiler.measure(A_1, ConstraintProfiler.Operation.INSERT,
+ advance(3));
+
+ // Total A1 = 1 + 3 = 4
+ // Total A2 = 2
+ // Total A3 = 5
+ // Total B1 = 3
+ // Total B2 = 4
+ // Total B3 = 5
+ // Subtract 5 because A3 and B3 share the same node
+ // Total = 4 + 2 + 3 + 4 + 5 + 5 - 5 = 18
+
+ assertThat(constraintProfiler.getSummary())
+ .isEqualTo("""
+ Constraint Profiling Summary
+ MyConstraintProvider#b:3 27.78%
+ MyConstraintProvider#a:3 27.78%
+ MyConstraintProvider#b:2 22.22%
+ MyConstraintProvider#a:1 22.22%
+ MyConstraintProvider#b:1 16.67%
+ MyConstraintProvider#a:2 11.11%""");
+ }
+}
\ No newline at end of file
diff --git a/core/src/test/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/move/ChangeMoveDefinitionTest.java b/core/src/test/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/move/ChangeMoveDefinitionTest.java
index cc47071583..cd6143b30c 100644
--- a/core/src/test/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/move/ChangeMoveDefinitionTest.java
+++ b/core/src/test/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/move/ChangeMoveDefinitionTest.java
@@ -10,6 +10,7 @@
import ai.timefold.solver.core.api.score.stream.Constraint;
import ai.timefold.solver.core.api.score.stream.ConstraintFactory;
import ai.timefold.solver.core.api.score.stream.ConstraintProvider;
+import ai.timefold.solver.core.config.score.director.ConstraintProfilingMode;
import ai.timefold.solver.core.config.solver.EnvironmentMode;
import ai.timefold.solver.core.impl.domain.solution.descriptor.SolutionDescriptor;
import ai.timefold.solver.core.impl.neighborhood.maybeapi.MoveDefinition;
@@ -339,7 +340,7 @@ private Iterable> createMoveIterable(MoveDefinition<
var firstEntityClass = solutionDescriptor.getMetaModel().genuineEntities().get(0).type();
var constraintProvider = new TestingConstraintProvider(firstEntityClass);
var scoreDirectorFactory = new BavetConstraintStreamScoreDirectorFactory<>(solutionDescriptor, constraintProvider,
- EnvironmentMode.TRACKED_FULL_ASSERT);
+ EnvironmentMode.TRACKED_FULL_ASSERT, ConstraintProfilingMode.NONE);
var scoreDirector = scoreDirectorFactory.buildScoreDirector();
scoreDirector.setWorkingSolution(solution);
return scoreDirector;
diff --git a/core/src/test/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/move/ListChangeMoveDefinitionTest.java b/core/src/test/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/move/ListChangeMoveDefinitionTest.java
index 2fb657a726..179008664a 100644
--- a/core/src/test/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/move/ListChangeMoveDefinitionTest.java
+++ b/core/src/test/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/move/ListChangeMoveDefinitionTest.java
@@ -10,6 +10,7 @@
import ai.timefold.solver.core.api.score.stream.Constraint;
import ai.timefold.solver.core.api.score.stream.ConstraintFactory;
import ai.timefold.solver.core.api.score.stream.ConstraintProvider;
+import ai.timefold.solver.core.config.score.director.ConstraintProfilingMode;
import ai.timefold.solver.core.config.solver.EnvironmentMode;
import ai.timefold.solver.core.impl.domain.solution.descriptor.SolutionDescriptor;
import ai.timefold.solver.core.impl.neighborhood.maybeapi.MoveDefinition;
@@ -366,7 +367,7 @@ private Iterable> createMoveIterable(MoveDefinition<
var firstEntityClass = solutionDescriptor.getMetaModel().genuineEntities().get(0).type();
var constraintProvider = new TestingConstraintProvider(firstEntityClass);
var scoreDirectorFactory = new BavetConstraintStreamScoreDirectorFactory<>(solutionDescriptor, constraintProvider,
- EnvironmentMode.TRACKED_FULL_ASSERT);
+ EnvironmentMode.TRACKED_FULL_ASSERT, ConstraintProfilingMode.NONE);
var scoreDirector = scoreDirectorFactory.buildScoreDirector();
scoreDirector.setWorkingSolution(solution);
return scoreDirector;
diff --git a/core/src/test/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/move/ListSwapMoveDefinitionTest.java b/core/src/test/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/move/ListSwapMoveDefinitionTest.java
index 6d4b1fb617..af3d12ddef 100644
--- a/core/src/test/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/move/ListSwapMoveDefinitionTest.java
+++ b/core/src/test/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/move/ListSwapMoveDefinitionTest.java
@@ -9,6 +9,7 @@
import ai.timefold.solver.core.api.score.stream.Constraint;
import ai.timefold.solver.core.api.score.stream.ConstraintFactory;
import ai.timefold.solver.core.api.score.stream.ConstraintProvider;
+import ai.timefold.solver.core.config.score.director.ConstraintProfilingMode;
import ai.timefold.solver.core.config.solver.EnvironmentMode;
import ai.timefold.solver.core.impl.domain.solution.descriptor.SolutionDescriptor;
import ai.timefold.solver.core.impl.neighborhood.maybeapi.MoveDefinition;
@@ -125,7 +126,7 @@ private Iterable> createMoveIterable(MoveDefinition<
var firstEntityClass = solutionDescriptor.getMetaModel().genuineEntities().get(0).type();
var constraintProvider = new TestingConstraintProvider(firstEntityClass);
var scoreDirectorFactory = new BavetConstraintStreamScoreDirectorFactory<>(solutionDescriptor, constraintProvider,
- EnvironmentMode.TRACKED_FULL_ASSERT);
+ EnvironmentMode.TRACKED_FULL_ASSERT, ConstraintProfilingMode.NONE);
var scoreDirector = scoreDirectorFactory.buildScoreDirector();
scoreDirector.setWorkingSolution(solution);
return scoreDirector;
diff --git a/core/src/test/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/move/SwapMoveDefinitionTest.java b/core/src/test/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/move/SwapMoveDefinitionTest.java
index e4478d8a7b..daf9e326db 100644
--- a/core/src/test/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/move/SwapMoveDefinitionTest.java
+++ b/core/src/test/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/move/SwapMoveDefinitionTest.java
@@ -9,6 +9,7 @@
import ai.timefold.solver.core.api.score.stream.Constraint;
import ai.timefold.solver.core.api.score.stream.ConstraintFactory;
import ai.timefold.solver.core.api.score.stream.ConstraintProvider;
+import ai.timefold.solver.core.config.score.director.ConstraintProfilingMode;
import ai.timefold.solver.core.config.solver.EnvironmentMode;
import ai.timefold.solver.core.impl.domain.solution.descriptor.SolutionDescriptor;
import ai.timefold.solver.core.impl.neighborhood.maybeapi.MoveDefinition;
@@ -147,7 +148,7 @@ private Iterable> createMoveIterable(MoveDefinition<
var firstEntityClass = solutionDescriptor.getMetaModel().genuineEntities().get(0).type();
var constraintProvider = new TestingConstraintProvider(firstEntityClass);
var scoreDirectorFactory = new BavetConstraintStreamScoreDirectorFactory<>(solutionDescriptor, constraintProvider,
- EnvironmentMode.TRACKED_FULL_ASSERT);
+ EnvironmentMode.TRACKED_FULL_ASSERT, ConstraintProfilingMode.NONE);
var scoreDirector = scoreDirectorFactory.buildScoreDirector();
scoreDirector.setWorkingSolution(solution);
return scoreDirector;
diff --git a/core/src/test/java/ai/timefold/solver/core/impl/score/stream/bavet/BavetConstraintStreamImplSupport.java b/core/src/test/java/ai/timefold/solver/core/impl/score/stream/bavet/BavetConstraintStreamImplSupport.java
index 1ac2b50706..4b8c948712 100644
--- a/core/src/test/java/ai/timefold/solver/core/impl/score/stream/bavet/BavetConstraintStreamImplSupport.java
+++ b/core/src/test/java/ai/timefold/solver/core/impl/score/stream/bavet/BavetConstraintStreamImplSupport.java
@@ -3,6 +3,7 @@
import ai.timefold.solver.core.api.score.Score;
import ai.timefold.solver.core.api.score.stream.ConstraintFactory;
import ai.timefold.solver.core.api.score.stream.ConstraintProvider;
+import ai.timefold.solver.core.config.score.director.ConstraintProfilingMode;
import ai.timefold.solver.core.config.solver.EnvironmentMode;
import ai.timefold.solver.core.impl.domain.solution.descriptor.SolutionDescriptor;
import ai.timefold.solver.core.impl.score.constraint.ConstraintMatchPolicy;
@@ -18,7 +19,7 @@ public record BavetConstraintStreamImplSupport(ConstraintMatchPolicy constraintM
public , Solution_> InnerScoreDirector buildScoreDirector(
SolutionDescriptor solutionDescriptorSupplier, ConstraintProvider constraintProvider) {
var scoreDirectorFactory = new BavetConstraintStreamScoreDirectorFactory(solutionDescriptorSupplier,
- constraintProvider, EnvironmentMode.PHASE_ASSERT);
+ constraintProvider, EnvironmentMode.PHASE_ASSERT, ConstraintProfilingMode.NONE);
return scoreDirectorFactory.createScoreDirectorBuilder()
.withConstraintMatchPolicy(constraintMatchPolicy)
.build();
diff --git a/quarkus-integration/quarkus/deployment/src/main/java/ai/timefold/solver/quarkus/deployment/TimefoldProcessor.java b/quarkus-integration/quarkus/deployment/src/main/java/ai/timefold/solver/quarkus/deployment/TimefoldProcessor.java
index 4391944538..c2b05ad7c3 100644
--- a/quarkus-integration/quarkus/deployment/src/main/java/ai/timefold/solver/quarkus/deployment/TimefoldProcessor.java
+++ b/quarkus-integration/quarkus/deployment/src/main/java/ai/timefold/solver/quarkus/deployment/TimefoldProcessor.java
@@ -789,6 +789,12 @@ private void applySolverProperties(IndexView indexView, String solverName, Solve
.flatMap(SolverBuildTimeConfig::enabledPreviewFeatures)
.ifPresent(solverConfig::setEnablePreviewFeatureSet);
+ timefoldBuildTimeConfig.getSolverConfig(solverName)
+ .flatMap(SolverBuildTimeConfig::constraintStreamProfilingMode)
+ .ifPresent(profilingMode -> {
+ solverConfig.getScoreDirectorFactoryConfig().withConstraintStreamProfiling(profilingMode);
+ });
+
timefoldBuildTimeConfig.getSolverConfig(solverName)
.flatMap(SolverBuildTimeConfig::nearbyDistanceMeterClass)
.ifPresent(clazz -> {
@@ -800,7 +806,6 @@ private void applySolverProperties(IndexView indexView, String solverName, Solve
}
solverConfig.withNearbyDistanceMeterClass((Class extends NearbyDistanceMeter, ?>>) clazz);
});
-
// Termination properties are set at runtime
}
diff --git a/quarkus-integration/quarkus/deployment/src/main/java/ai/timefold/solver/quarkus/deployment/config/SolverBuildTimeConfig.java b/quarkus-integration/quarkus/deployment/src/main/java/ai/timefold/solver/quarkus/deployment/config/SolverBuildTimeConfig.java
index 8120de3ffa..9572859c5b 100644
--- a/quarkus-integration/quarkus/deployment/src/main/java/ai/timefold/solver/quarkus/deployment/config/SolverBuildTimeConfig.java
+++ b/quarkus-integration/quarkus/deployment/src/main/java/ai/timefold/solver/quarkus/deployment/config/SolverBuildTimeConfig.java
@@ -5,6 +5,7 @@
import ai.timefold.solver.core.api.domain.common.DomainAccessType;
import ai.timefold.solver.core.api.score.stream.ConstraintStreamImplType;
+import ai.timefold.solver.core.config.score.director.ConstraintProfilingMode;
import ai.timefold.solver.core.config.solver.PreviewFeature;
import ai.timefold.solver.core.config.solver.SolverConfig;
import ai.timefold.solver.quarkus.config.SolverRuntimeConfig;
@@ -58,6 +59,11 @@ public interface SolverBuildTimeConfig {
@Deprecated(forRemoval = true, since = "1.4.0")
Optional constraintStreamImplType();
+ /**
+ * What profiling mode to use. Defaults to {@link ConstraintProfilingMode#NONE}.
+ */
+ Optional constraintStreamProfilingMode();
+
/**
* Note: this setting is only available
* for Timefold Solver
diff --git a/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorSolverPropertiesTest.java b/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorSolverPropertiesTest.java
index 99b2c9b5ee..4b5792447b 100644
--- a/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorSolverPropertiesTest.java
+++ b/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorSolverPropertiesTest.java
@@ -11,6 +11,7 @@
import ai.timefold.solver.core.api.domain.common.DomainAccessType;
import ai.timefold.solver.core.api.score.buildin.simple.SimpleScore;
import ai.timefold.solver.core.api.solver.SolverFactory;
+import ai.timefold.solver.core.config.score.director.ConstraintProfilingMode;
import ai.timefold.solver.core.config.solver.EnvironmentMode;
import ai.timefold.solver.core.config.solver.SolverConfig;
import ai.timefold.solver.quarkus.testdomain.dummy.DummyDistanceMeter;
@@ -35,6 +36,7 @@ class TimefoldProcessorSolverPropertiesTest {
"ai.timefold.solver.quarkus.testdomain.dummy.DummyDistanceMeter")
.overrideConfigKey("quarkus.timefold.solver.move-thread-count", "2")
.overrideConfigKey("quarkus.timefold.solver.domain-access-type", "REFLECTION")
+ .overrideConfigKey("quarkus.timefold.solver.constraint-stream-profiling-mode", "BY_METHOD")
.overrideConfigKey("quarkus.timefold.solver.termination.spent-limit", "4h")
.overrideConfigKey("quarkus.timefold.solver.termination.unimproved-spent-limit", "5h")
.overrideConfigKey("quarkus.timefold.solver.termination.best-score-limit", "0")
@@ -58,6 +60,8 @@ void solverProperties() {
assertEquals(DomainAccessType.REFLECTION, solverConfig.getDomainAccessType());
assertEquals(null,
solverConfig.getScoreDirectorFactoryConfig().getConstraintStreamImplType());
+ assertEquals(ConstraintProfilingMode.BY_METHOD,
+ solverConfig.getScoreDirectorFactoryConfig().getConstraintStreamProfilingMode());
assertNotNull(solverConfig.getNearbyDistanceMeterClass());
assertNotNull(solverFactory);
}
diff --git a/spring-integration/spring-boot-autoconfigure/src/main/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldSolverAutoConfiguration.java b/spring-integration/spring-boot-autoconfigure/src/main/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldSolverAutoConfiguration.java
index 1eb52152d6..f7f7adb3bd 100644
--- a/spring-integration/spring-boot-autoconfigure/src/main/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldSolverAutoConfiguration.java
+++ b/spring-integration/spring-boot-autoconfigure/src/main/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldSolverAutoConfiguration.java
@@ -266,6 +266,10 @@ private void applyScoreDirectorFactoryProperties(IncludeAbstractClassesEntitySca
Objects.requireNonNull(solverConfig.getScoreDirectorFactoryConfig())
.setConstraintStreamAutomaticNodeSharing(true);
}
+ if (solverProperties.getConstraintStreamProfilingMode() != null) {
+ Objects.requireNonNull(solverConfig.getScoreDirectorFactoryConfig())
+ .setConstraintStreamProfilingMode(solverProperties.getConstraintStreamProfilingMode());
+ }
if (solverProperties.getEnvironmentMode() != null) {
solverConfig.setEnvironmentMode(solverProperties.getEnvironmentMode());
}
diff --git a/spring-integration/spring-boot-autoconfigure/src/main/java/ai/timefold/solver/spring/boot/autoconfigure/config/SolverProperties.java b/spring-integration/spring-boot-autoconfigure/src/main/java/ai/timefold/solver/spring/boot/autoconfigure/config/SolverProperties.java
index 619e77b063..041dab5bca 100644
--- a/spring-integration/spring-boot-autoconfigure/src/main/java/ai/timefold/solver/spring/boot/autoconfigure/config/SolverProperties.java
+++ b/spring-integration/spring-boot-autoconfigure/src/main/java/ai/timefold/solver/spring/boot/autoconfigure/config/SolverProperties.java
@@ -6,6 +6,7 @@
import ai.timefold.solver.core.api.domain.common.DomainAccessType;
import ai.timefold.solver.core.api.score.stream.ConstraintStreamImplType;
+import ai.timefold.solver.core.config.score.director.ConstraintProfilingMode;
import ai.timefold.solver.core.config.solver.EnvironmentMode;
import ai.timefold.solver.core.config.solver.PreviewFeature;
import ai.timefold.solver.core.impl.heuristic.selector.common.nearby.NearbyDistanceMeter;
@@ -66,6 +67,8 @@ public class SolverProperties {
@Deprecated(forRemoval = true, since = "1.4.0")
private ConstraintStreamImplType constraintStreamImplType;
+ private ConstraintProfilingMode constraintStreamProfilingMode;
+
/**
* Note: this setting is only available
* for Timefold Solver
@@ -162,6 +165,14 @@ public void setConstraintStreamImplType(ConstraintStreamImplType constraintStrea
this.constraintStreamImplType = constraintStreamImplType;
}
+ public ConstraintProfilingMode getConstraintStreamProfilingMode() {
+ return constraintStreamProfilingMode;
+ }
+
+ public void setConstraintStreamProfilingMode(ConstraintProfilingMode constraintStreamProfilingMode) {
+ this.constraintStreamProfilingMode = constraintStreamProfilingMode;
+ }
+
public Boolean getConstraintStreamAutomaticNodeSharing() {
return constraintStreamAutomaticNodeSharing;
}
diff --git a/spring-integration/spring-boot-autoconfigure/src/main/java/ai/timefold/solver/spring/boot/autoconfigure/config/SolverProperty.java b/spring-integration/spring-boot-autoconfigure/src/main/java/ai/timefold/solver/spring/boot/autoconfigure/config/SolverProperty.java
index 2651c7f3e1..eb2205b303 100644
--- a/spring-integration/spring-boot-autoconfigure/src/main/java/ai/timefold/solver/spring/boot/autoconfigure/config/SolverProperty.java
+++ b/spring-integration/spring-boot-autoconfigure/src/main/java/ai/timefold/solver/spring/boot/autoconfigure/config/SolverProperty.java
@@ -12,6 +12,7 @@
import ai.timefold.solver.core.api.domain.common.DomainAccessType;
import ai.timefold.solver.core.api.score.stream.ConstraintStreamImplType;
+import ai.timefold.solver.core.config.score.director.ConstraintProfilingMode;
import ai.timefold.solver.core.config.solver.EnvironmentMode;
import ai.timefold.solver.core.config.solver.PreviewFeature;
import ai.timefold.solver.core.impl.heuristic.selector.common.nearby.NearbyDistanceMeter;
@@ -50,6 +51,8 @@ public enum SolverProperty {
@Deprecated(forRemoval = true, since = "1.4.0")
CONSTRAINT_STREAM_IMPL_TYPE("constraint-stream-impl-type", SolverProperties::setConstraintStreamImplType,
value -> ConstraintStreamImplType.valueOf(value.toString())),
+ CONSTRAINT_STREAM_PROFILING_MODE("constraint-stream-profiling-mode", SolverProperties::setConstraintStreamProfilingMode,
+ value -> ConstraintProfilingMode.valueOf(value.toString())),
CONSTRAINT_STREAM_AUTOMATIC_NODE_SHARING("constraint-stream-automatic-node-sharing",
SolverProperties::setConstraintStreamAutomaticNodeSharing, value -> Boolean.valueOf(value.toString())),
RANDOM_SEED("random-seed", SolverProperties::setRandomSeed, value -> Long.parseLong(value.toString())),
diff --git a/spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldSolverSingleSolverAutoConfigurationTest.java b/spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldSolverSingleSolverAutoConfigurationTest.java
index c9d45f7e94..df5bd8f646 100644
--- a/spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldSolverSingleSolverAutoConfigurationTest.java
+++ b/spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldSolverSingleSolverAutoConfigurationTest.java
@@ -6,6 +6,7 @@
import java.time.Duration;
import java.util.Collections;
import java.util.List;
+import java.util.Objects;
import java.util.stream.IntStream;
import ai.timefold.solver.benchmark.api.PlannerBenchmarkFactory;
@@ -13,6 +14,7 @@
import ai.timefold.solver.core.api.solver.SolverConfigOverride;
import ai.timefold.solver.core.api.solver.SolverFactory;
import ai.timefold.solver.core.api.solver.SolverManager;
+import ai.timefold.solver.core.config.score.director.ConstraintProfilingMode;
import ai.timefold.solver.core.config.solver.SolverConfig;
import ai.timefold.solver.core.config.solver.termination.TerminationConfig;
import ai.timefold.solver.core.impl.solver.DefaultSolverJob;
@@ -128,6 +130,19 @@ void solve() {
});
}
+ @Test
+ void solveWithProfilingMode() {
+ contextRunner
+ .withClassLoader(allDefaultsFilteredClassLoader)
+ .withPropertyValues("timefold.solver.constraint-stream-profiling-mode=BY_METHOD")
+ .run(context -> {
+ var solverConfig = context.getBean(SolverConfig.class);
+ assertThat(Objects.requireNonNull(solverConfig.getScoreDirectorFactoryConfig())
+ .getConstraintStreamProfilingMode())
+ .isEqualTo(ConstraintProfilingMode.BY_METHOD);
+ });
+ }
+
@Test
void solveWithParallelSolverCount() {
contextRunner
diff --git a/test/src/main/java/ai/timefold/solver/test/impl/score/stream/ScoreDirectorFactoryCache.java b/test/src/main/java/ai/timefold/solver/test/impl/score/stream/ScoreDirectorFactoryCache.java
index 6732f2d494..2f242f3327 100644
--- a/test/src/main/java/ai/timefold/solver/test/impl/score/stream/ScoreDirectorFactoryCache.java
+++ b/test/src/main/java/ai/timefold/solver/test/impl/score/stream/ScoreDirectorFactoryCache.java
@@ -11,6 +11,7 @@
import ai.timefold.solver.core.api.score.stream.Constraint;
import ai.timefold.solver.core.api.score.stream.ConstraintFactory;
import ai.timefold.solver.core.api.score.stream.ConstraintProvider;
+import ai.timefold.solver.core.config.score.director.ConstraintProfilingMode;
import ai.timefold.solver.core.config.solver.EnvironmentMode;
import ai.timefold.solver.core.impl.domain.solution.descriptor.SolutionDescriptor;
import ai.timefold.solver.core.impl.score.director.stream.BavetConstraintStreamScoreDirectorFactory;
@@ -85,7 +86,8 @@ public ScoreDirectorFactoryCache(SolutionDescriptor solutionDescripto
ConstraintRef constraintRef,
ConstraintProvider constraintProvider, EnvironmentMode environmentMode) {
return scoreDirectorFactoryMap.computeIfAbsent(constraintRef,
- k -> new BavetConstraintStreamScoreDirectorFactory<>(solutionDescriptor, constraintProvider, environmentMode));
+ k -> new BavetConstraintStreamScoreDirectorFactory<>(solutionDescriptor, constraintProvider, environmentMode,
+ ConstraintProfilingMode.NONE));
}
}