Skip to content

Commit 84e4bc7

Browse files
authored
Merge branch 'main' into deep-planning-clone-class
2 parents 264591c + 0f2da1e commit 84e4bc7

File tree

67 files changed

+1147
-754
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

67 files changed

+1147
-754
lines changed

build/build-parent/pom.xml

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,15 @@
2020
<!-- ************************************************************************ -->
2121
<!-- Dependencies -->
2222
<!-- ************************************************************************ -->
23-
<version.ch.qos.logback>1.5.12</version.ch.qos.logback>
24-
<version.io.quarkus>3.17.3</version.io.quarkus>
23+
<version.ch.qos.logback>1.5.16</version.ch.qos.logback>
24+
<version.io.quarkus>3.17.5</version.io.quarkus>
2525
<version.org.apache.commons.math3>3.6.1</version.org.apache.commons.math3>
26-
<version.org.apache.logging.log4j>2.24.2</version.org.apache.logging.log4j>
27-
<version.org.assertj>3.26.3</version.org.assertj>
28-
<version.org.freemarker>2.3.33</version.org.freemarker>
26+
<version.org.apache.logging.log4j>2.24.3</version.org.apache.logging.log4j>
27+
<version.org.assertj>3.27.2</version.org.assertj>
28+
<version.org.freemarker>2.3.34</version.org.freemarker>
2929
<version.org.jspecify>1.0.0</version.org.jspecify>
30-
<version.org.openrewrite.recipe>2.23.1</version.org.openrewrite.recipe>
31-
<version.org.springframework.boot>3.4.0</version.org.springframework.boot>
30+
<version.org.openrewrite.recipe>2.23.2</version.org.openrewrite.recipe>
31+
<version.org.springframework.boot>3.4.1</version.org.springframework.boot>
3232
<version.ow2.asm>9.7.1</version.ow2.asm>
3333

3434
<!-- ************************************************************************ -->
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package ai.timefold.solver.core.api.function;
2+
3+
import java.util.Objects;
4+
import java.util.function.Consumer;
5+
import java.util.function.Function;
6+
7+
import org.jspecify.annotations.NonNull;
8+
9+
/**
10+
* Represents a function that accepts four arguments and returns no result.
11+
* This is the three-arity specialization of {@link Consumer}.
12+
*
13+
* <p>
14+
* This is a <a href="package-summary.html">functional interface</a>
15+
* whose functional method is {@link #accept(Object, Object, Object, Object)}.
16+
*
17+
* @param <A> the type of the first argument to the function
18+
* @param <B> the type of the second argument to the function
19+
* @param <C> the type of the third argument to the function
20+
* @param <D> the type of the fourth argument to the function
21+
*
22+
* @see Function
23+
*/
24+
@FunctionalInterface
25+
public interface QuadConsumer<A, B, C, D> {
26+
27+
/**
28+
* Performs this operation on the given arguments.
29+
*
30+
* @param a the first function argument
31+
* @param b the second function argument
32+
* @param c the third function argument
33+
* @param d the fourth function argument
34+
*/
35+
void accept(A a, B b, C c, D d);
36+
37+
default @NonNull QuadConsumer<A, B, C, D> andThen(@NonNull QuadConsumer<? super A, ? super B, ? super C, ? super D> after) {
38+
Objects.requireNonNull(after);
39+
return (a, b, c, d) -> {
40+
accept(a, b, c, d);
41+
after.accept(a, b, c, d);
42+
};
43+
}
44+
45+
}

core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ListVariableState.java

Lines changed: 93 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import java.util.List;
44
import java.util.Map;
5-
import java.util.Objects;
65

76
import ai.timefold.solver.core.impl.domain.variable.descriptor.ListVariableDescriptor;
87
import ai.timefold.solver.core.impl.domain.variable.index.IndexShadowVariableDescriptor;
@@ -26,7 +25,7 @@ final class ListVariableState<Solution_> {
2625
private boolean requiresLocationMap = true;
2726
private InnerScoreDirector<Solution_, ?> scoreDirector;
2827
private int unassignedCount = 0;
29-
private Map<Object, LocationInList> elementLocationMap;
28+
private Map<Object, MutableLocationInList> elementLocationMap;
3029

3130
public ListVariableState(ListVariableDescriptor<Solution_> sourceVariableDescriptor) {
3231
this.sourceVariableDescriptor = sourceVariableDescriptor;
@@ -69,8 +68,7 @@ public void initialize(InnerScoreDirector<Solution_, ?> scoreDirector, int initi
6968

7069
public void addElement(Object entity, List<Object> elements, Object element, int index) {
7170
if (requiresLocationMap) {
72-
var location = ElementLocation.of(entity, index);
73-
var oldLocation = elementLocationMap.put(element, location);
71+
var oldLocation = elementLocationMap.put(element, new MutableLocationInList(entity, index));
7472
if (oldLocation != null) {
7573
throw new IllegalStateException(
7674
"The supply for list variable (%s) is corrupted, because the element (%s) at index (%d) already exists (%s)."
@@ -100,7 +98,7 @@ public void removeElement(Object entity, Object element, int index) {
10098
"The supply for list variable (%s) is corrupted, because the element (%s) at index (%d) was already unassigned (%s)."
10199
.formatted(sourceVariableDescriptor, element, index, oldElementLocation));
102100
}
103-
var oldIndex = oldElementLocation.index();
101+
var oldIndex = oldElementLocation.getIndex();
104102
if (oldIndex != index) {
105103
throw new IllegalStateException(
106104
"The supply for list variable (%s) is corrupted, because the element (%s) at index (%d) had an old index (%d) which is not the current index (%d)."
@@ -168,13 +166,22 @@ public boolean changeElement(Object entity, List<Object> elements, int index) {
168166

169167
private ChangeType processElementLocation(Object entity, Object element, int index) {
170168
if (requiresLocationMap) { // Update the location and figure out if it is different from previous.
171-
var newLocation = ElementLocation.of(entity, index);
172-
var oldLocation = elementLocationMap.put(element, newLocation);
169+
var oldLocation = elementLocationMap.get(element);
173170
if (oldLocation == null) {
171+
elementLocationMap.put(element, new MutableLocationInList(entity, index));
174172
unassignedCount--;
175173
return ChangeType.BOTH;
176174
}
177-
return compareLocations(entity, oldLocation.entity(), index, oldLocation.index());
175+
var changeType = compareLocations(entity, oldLocation.getEntity(), index, oldLocation.getIndex());
176+
if (changeType.anythingChanged) { // Replace the map value in-place, to avoid a put() on the hot path.
177+
if (changeType.entityChanged) {
178+
oldLocation.setEntity(entity);
179+
}
180+
if (changeType.indexChanged) {
181+
oldLocation.setIndex(index);
182+
}
183+
}
184+
return changeType;
178185
} else { // Read the location and figure out if it is different from previous.
179186
var oldEntity = getInverseSingleton(element);
180187
if (oldEntity == null) {
@@ -199,27 +206,13 @@ private static ChangeType compareLocations(Object entity, Object otherEntity, in
199206
}
200207
}
201208

202-
private enum ChangeType {
203-
204-
BOTH(true, true),
205-
INDEX(false, true),
206-
NEITHER(false, false);
207-
208-
final boolean anythingChanged;
209-
final boolean entityChanged;
210-
final boolean indexChanged;
211-
212-
ChangeType(boolean entityChanged, boolean indexChanged) {
213-
this.anythingChanged = entityChanged || indexChanged;
214-
this.entityChanged = entityChanged;
215-
this.indexChanged = indexChanged;
216-
}
217-
218-
}
219-
220209
public ElementLocation getLocationInList(Object planningValue) {
221210
if (requiresLocationMap) {
222-
return Objects.requireNonNullElse(elementLocationMap.get(planningValue), ElementLocation.unassigned());
211+
var mutableLocationInList = elementLocationMap.get(planningValue);
212+
if (mutableLocationInList == null) {
213+
return ElementLocation.unassigned();
214+
}
215+
return mutableLocationInList.getLocationInList();
223216
} else { // At this point, both inverse and index are externalized.
224217
var inverse = externalizedInverseProcessor.getInverseSingleton(planningValue);
225218
if (inverse == null) {
@@ -235,7 +228,7 @@ public Integer getIndex(Object planningValue) {
235228
if (elementLocation == null) {
236229
return null;
237230
}
238-
return elementLocation.index();
231+
return elementLocation.getIndex();
239232
}
240233
return externalizedIndexProcessor.getIndex(planningValue);
241234
}
@@ -246,34 +239,35 @@ public Object getInverseSingleton(Object planningValue) {
246239
if (elementLocation == null) {
247240
return null;
248241
}
249-
return elementLocation.entity();
242+
return elementLocation.getEntity();
250243
}
251244
return externalizedInverseProcessor.getInverseSingleton(planningValue);
252245
}
253246

254247
public Object getPreviousElement(Object element) {
255248
if (externalizedPreviousElementProcessor == null) {
256-
var elementLocation = getLocationInList(element);
257-
if (!(elementLocation instanceof LocationInList locationInList)) {
249+
var mutableLocationInList = elementLocationMap.get(element);
250+
if (mutableLocationInList == null) {
258251
return null;
259252
}
260-
var index = locationInList.index();
253+
var index = mutableLocationInList.getIndex();
261254
if (index == 0) {
262255
return null;
263256
}
264-
return sourceVariableDescriptor.getValue(locationInList.entity()).get(index - 1);
257+
return sourceVariableDescriptor.getValue(mutableLocationInList.getEntity())
258+
.get(index - 1);
265259
}
266260
return externalizedPreviousElementProcessor.getElement(element);
267261
}
268262

269263
public Object getNextElement(Object element) {
270264
if (externalizedNextElementProcessor == null) {
271-
var elementLocation = getLocationInList(element);
272-
if (!(elementLocation instanceof LocationInList locationInList)) {
265+
var mutableLocationInList = elementLocationMap.get(element);
266+
if (mutableLocationInList == null) {
273267
return null;
274268
}
275-
var list = sourceVariableDescriptor.getValue(locationInList.entity());
276-
var index = locationInList.index();
269+
var list = sourceVariableDescriptor.getValue(mutableLocationInList.getEntity());
270+
var index = mutableLocationInList.getIndex();
277271
if (index == list.size() - 1) {
278272
return null;
279273
}
@@ -286,4 +280,66 @@ public int getUnassignedCount() {
286280
return unassignedCount;
287281
}
288282

283+
private enum ChangeType {
284+
285+
BOTH(true, true),
286+
INDEX(false, true),
287+
NEITHER(false, false);
288+
289+
final boolean anythingChanged;
290+
final boolean entityChanged;
291+
final boolean indexChanged;
292+
293+
ChangeType(boolean entityChanged, boolean indexChanged) {
294+
this.anythingChanged = entityChanged || indexChanged;
295+
this.entityChanged = entityChanged;
296+
this.indexChanged = indexChanged;
297+
}
298+
299+
}
300+
301+
/**
302+
* This class is used to avoid creating a new {@link LocationInList} object every time we need to return a location.
303+
* The actual value is held in a map and can be updated without doing a put() operation, which is more efficient.
304+
* The {@link LocationInList} object is only created when it is actually requested,
305+
* and stored until the next time the mutable state is updated and therefore the cache invalidated.
306+
*/
307+
private static final class MutableLocationInList {
308+
309+
private Object entity;
310+
private int index;
311+
private LocationInList locationInList;
312+
313+
public MutableLocationInList(Object entity, int index) {
314+
this.entity = entity;
315+
this.index = index;
316+
}
317+
318+
public Object getEntity() {
319+
return entity;
320+
}
321+
322+
public void setEntity(Object entity) {
323+
this.entity = entity;
324+
this.locationInList = null;
325+
}
326+
327+
public int getIndex() {
328+
return index;
329+
}
330+
331+
public void setIndex(int index) {
332+
this.index = index;
333+
this.locationInList = null;
334+
}
335+
336+
public LocationInList getLocationInList() {
337+
if (locationInList == null) {
338+
locationInList = ElementLocation.of(entity, index);
339+
}
340+
return locationInList;
341+
}
342+
343+
}
344+
289345
}

core/src/main/java/ai/timefold/solver/core/impl/score/director/stream/BavetConstraintStreamScoreDirectorFactory.java

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package ai.timefold.solver.core.impl.score.director.stream;
22

33
import java.util.Arrays;
4+
import java.util.function.Consumer;
45

56
import ai.timefold.solver.core.api.score.Score;
67
import ai.timefold.solver.core.api.score.stream.ConstraintMetaModel;
@@ -56,8 +57,7 @@ public BavetConstraintStreamScoreDirectorFactory(SolutionDescriptor<Solution_> s
5657
ConstraintProvider constraintProvider, EnvironmentMode environmentMode) {
5758
super(solutionDescriptor);
5859
var constraintFactory = new BavetConstraintFactory<>(solutionDescriptor, environmentMode);
59-
constraintMetaModel =
60-
DefaultConstraintMetaModel.of(constraintFactory.buildConstraints(constraintProvider));
60+
constraintMetaModel = DefaultConstraintMetaModel.of(constraintFactory.buildConstraints(constraintProvider));
6161
constraintSessionFactory = new BavetConstraintSessionFactory<>(solutionDescriptor, constraintMetaModel);
6262
}
6363

@@ -76,7 +76,13 @@ public InnerScoreDirector<Solution_, Score_> buildDerivedScoreDirector(boolean l
7676

7777
public BavetConstraintSession<Score_> newSession(Solution_ workingSolution, ConstraintMatchPolicy constraintMatchPolicy,
7878
boolean scoreDirectorDerived) {
79-
return constraintSessionFactory.buildSession(workingSolution, constraintMatchPolicy, scoreDirectorDerived);
79+
return newSession(workingSolution, constraintMatchPolicy, scoreDirectorDerived, null);
80+
}
81+
82+
public BavetConstraintSession<Score_> newSession(Solution_ workingSolution, ConstraintMatchPolicy constraintMatchPolicy,
83+
boolean scoreDirectorDerived, Consumer<String> nodeNetworkVisualizationConsumer) {
84+
return constraintSessionFactory.buildSession(workingSolution, constraintMatchPolicy, scoreDirectorDerived,
85+
nodeNetworkVisualizationConsumer);
8086
}
8187

8288
@Override

core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/BavetConstraint.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@ public BavetConstraint(BavetConstraintFactory<Solution_> constraintFactory, Cons
2323
this.scoringConstraintStream = scoringConstraintStream;
2424
}
2525

26-
// ************************************************************************
27-
// Node creation
28-
// ************************************************************************
26+
public BavetScoringConstraintStream<Solution_> getScoringConstraintStream() {
27+
return scoringConstraintStream;
28+
}
2929

3030
@Override
3131
public String toString() {

0 commit comments

Comments
 (0)