Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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 @@ -17,8 +17,8 @@
*/
// TODO In Solver 2.0, maybe convert this to an interface.
public class BestSolutionChangedEvent<Solution_> extends EventObject {

private final Solver<Solution_> solver;
private final EventProducerId producerId;
private final long timeMillisSpent;
private final Solution_ newBestSolution;
private final Score newBestScore;
Expand All @@ -31,7 +31,7 @@ public class BestSolutionChangedEvent<Solution_> extends EventObject {
@Deprecated(forRemoval = true, since = "1.22.0")
public BestSolutionChangedEvent(@NonNull Solver<Solution_> solver, long timeMillisSpent,
@NonNull Solution_ newBestSolution, @NonNull Score newBestScore) {
this(solver, timeMillisSpent, newBestSolution, newBestScore, true);
this(solver, EventProducerId.unknown(), timeMillisSpent, newBestSolution, newBestScore, true);
}

/**
Expand All @@ -42,8 +42,20 @@ public BestSolutionChangedEvent(@NonNull Solver<Solution_> solver, long timeMill
public BestSolutionChangedEvent(@NonNull Solver<Solution_> solver, long timeMillisSpent,
@NonNull Solution_ newBestSolution, @NonNull Score newBestScore,
boolean isNewBestSolutionInitialized) {
this(solver, EventProducerId.unknown(), timeMillisSpent, newBestSolution, newBestScore, isNewBestSolutionInitialized);
}

/**
* @param timeMillisSpent {@code >= 0L}
* @deprecated Users should not manually construct instances of this event.
*/
@Deprecated(forRemoval = true, since = "1.28.0")
public BestSolutionChangedEvent(@NonNull Solver<Solution_> solver, EventProducerId producerId, long timeMillisSpent,
@NonNull Solution_ newBestSolution, @NonNull Score newBestScore,
boolean isNewBestSolutionInitialized) {
super(solver);
this.solver = solver;
this.producerId = producerId;
this.timeMillisSpent = timeMillisSpent;
this.newBestSolution = newBestSolution;
this.newBestScore = newBestScore;
Expand All @@ -58,6 +70,15 @@ public long getTimeMillisSpent() {
return timeMillisSpent;
}

/**
* @return A {@link EventProducerId} identifying what generated the event, either a
* {@link SolveEventProducerId} if the cause is not associated with a Phase,
* or {@link PhaseEventProducerId} if it is.
*/
public EventProducerId getProducerId() {
return producerId;
}

/**
* Note that:
* <ul>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package ai.timefold.solver.core.api.solver.event;

import java.io.Serializable;
import java.util.OptionalInt;

import ai.timefold.solver.core.config.solver.SolverConfig;
import ai.timefold.solver.core.impl.phase.NoChangePhase;

import org.jspecify.annotations.NullMarked;

/**
* Identifies the producer of a {@link BestSolutionChangedEvent}.
* Will be an instance of {@link PhaseEventProducerId} if the event is associated
* with a phase, or a {@link SolveEventProducerId} otherwise.
*/
@NullMarked
public sealed interface EventProducerId extends Serializable permits PhaseEventProducerId, SolveEventProducerId {
/**
* An unique string identifying what produced the event, either of the form
* "Event" where "Event" is a string describing the event that cause the update (like "Solving started")
* or "Phase (index)", where "Phase" is a string identifying the type of phase (like "Construction Heuristics")
* and index is the index of the phase in the {@link SolverConfig#getPhaseConfigList()}.
*
* @return An unique string identifying what produced the event.
*/
String producerId();

/**
* A (non-unique) string describing what produced the event.
* Events from different phases of the same type (for example,
* when multiple Construction Heuristics are configured)
* will return the same value.
*
* @return A (non-unique) string describing what produced the event.
*/
String simpleProducerName();

/**
* An optional integer, that if present, identify what index in {@link SolverConfig#getPhaseConfigList()}
* corresponds to the Phase that produced the event.
*
* @return The index of the Phase that produced the event, or {@link OptionalInt#empty()} if the event producer
* is not associated with a phase.
*/
OptionalInt eventPhaseIndex();

static EventProducerId unknown() {
return SolveEventProducerId.UNKNOWN;
}

static EventProducerId solvingStarted() {
return SolveEventProducerId.SOLVING_STARTED;
}

static EventProducerId problemChange() {
return SolveEventProducerId.PROBLEM_CHANGE;
}

/**
* @deprecated Deprecated on account of {@link NoChangePhase} having no use.
*/
@Deprecated(forRemoval = true, since = "1.28.0")
static EventProducerId noChange(int phaseIndex) {
return new PhaseEventProducerId(PhaseType.NO_CHANGE, phaseIndex);
}

static EventProducerId constructionHeuristic(int phaseIndex) {
return new PhaseEventProducerId(PhaseType.CONSTRUCTION_HEURISTIC, phaseIndex);
}

static EventProducerId localSearch(int phaseIndex) {
return new PhaseEventProducerId(PhaseType.LOCAL_SEARCH, phaseIndex);
}

static EventProducerId exhaustiveSearch(int phaseIndex) {
return new PhaseEventProducerId(PhaseType.EXHAUSTIVE_SEARCH, phaseIndex);
}

static EventProducerId partitionedSearch(int phaseIndex) {
return new PhaseEventProducerId(PhaseType.PARTITIONED_SEARCH, phaseIndex);
}

static EventProducerId customPhase(int phaseIndex) {
return new PhaseEventProducerId(PhaseType.CUSTOM_PHASE, phaseIndex);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package ai.timefold.solver.core.api.solver.event;

import java.util.OptionalInt;

import org.jspecify.annotations.NullMarked;

/**
* {@link EventProducerId} for when a {@link BestSolutionChangedEvent} is
* caused by a phase.
*/
@NullMarked
public record PhaseEventProducerId(PhaseType phaseType, int phaseIndex) implements EventProducerId {
@Override
public String producerId() {
return "%s (%d)".formatted(phaseType.getPhaseName(), phaseIndex);
}

@Override
public String simpleProducerName() {
return phaseType.getPhaseName();
}

@Override
public OptionalInt eventPhaseIndex() {
return OptionalInt.of(phaseIndex);
}

@Override
public String toString() {
return producerId();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package ai.timefold.solver.core.api.solver.event;

import ai.timefold.solver.core.config.constructionheuristic.ConstructionHeuristicPhaseConfig;
import ai.timefold.solver.core.config.exhaustivesearch.ExhaustiveSearchPhaseConfig;
import ai.timefold.solver.core.config.localsearch.LocalSearchPhaseConfig;
import ai.timefold.solver.core.config.partitionedsearch.PartitionedSearchPhaseConfig;
import ai.timefold.solver.core.config.phase.NoChangePhaseConfig;
import ai.timefold.solver.core.config.phase.custom.CustomPhaseConfig;
import ai.timefold.solver.core.impl.phase.NoChangePhase;

/**
* The type of phase (for example, a Construction Heuristic).
*/
public enum PhaseType {
/**
* The type of phase associated with {@link NoChangePhaseConfig}.
*
* @deprecated Deprecated on account of {@link NoChangePhase} having no use.
*/
@Deprecated(forRemoval = true, since = "1.28.0")
NO_CHANGE("No Change"),
/**
* The type of phase associated with {@link ConstructionHeuristicPhaseConfig}.
*/
CONSTRUCTION_HEURISTIC("Construction Heuristics"),
/**
* The type of phase associated with {@link LocalSearchPhaseConfig}.
*/
LOCAL_SEARCH("Local Search"),
/**
* The type of phase associated with {@link ExhaustiveSearchPhaseConfig}.
*/
EXHAUSTIVE_SEARCH("Exhaustive Search"),
/**
* The type of phase associated with {@link PartitionedSearchPhaseConfig}.
*/
PARTITIONED_SEARCH("Partitioned Search"),
/**
* The type of phase associated with {@link CustomPhaseConfig}.
*/
CUSTOM_PHASE("Custom Phase");

private final String phaseName;

PhaseType(String phaseName) {
this.phaseName = phaseName;
}

public String getPhaseName() {
return phaseName;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package ai.timefold.solver.core.api.solver.event;

import java.util.OptionalInt;

import org.jspecify.annotations.NullMarked;

/**
* {@link EventProducerId} for when a {@link BestSolutionChangedEvent} is not
* caused by a phase.
*/
@NullMarked
public enum SolveEventProducerId implements EventProducerId {
/**
* The cause is unknown. This is the {@link EventProducerId}
* used when one of the deprecated {@link BestSolutionChangedEvent}
* constructors are used.
*
* @deprecated Only used when Users manually construct instances of {@link BestSolutionChangedEvent}.
*/
@Deprecated(forRemoval = true, since = "1.28.0")
UNKNOWN("Unknown"),

/**
* The solver was started with an initialized solution.
*/
SOLVING_STARTED("Solving started"),

/**
* One or more problem changes occured that change the best solution.
*/
PROBLEM_CHANGE("Problem change");

private final String producerId;

SolveEventProducerId(String producerId) {
this.producerId = producerId;
}

@Override
public String producerId() {
return producerId;
}

@Override
public String simpleProducerName() {
return producerId;
}

@Override
public OptionalInt eventPhaseIndex() {
return OptionalInt.empty();
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package ai.timefold.solver.core.impl.constructionheuristic;

import java.util.function.IntFunction;

import ai.timefold.solver.core.api.domain.solution.PlanningSolution;
import ai.timefold.solver.core.api.solver.event.EventProducerId;
import ai.timefold.solver.core.api.solver.event.PhaseType;
import ai.timefold.solver.core.config.solver.EnvironmentMode;
import ai.timefold.solver.core.impl.constructionheuristic.decider.ConstructionHeuristicDecider;
import ai.timefold.solver.core.impl.constructionheuristic.placer.EntityPlacer;
Expand All @@ -23,7 +27,6 @@
public class DefaultConstructionHeuristicPhase<Solution_>
extends AbstractPossiblyInitializingPhase<Solution_>
implements ConstructionHeuristicPhase<Solution_> {

protected final ConstructionHeuristicDecider<Solution_> decider;
protected final PlacerBasedMoveRepository<Solution_> moveRepository;
private TerminationStatus terminationStatus = TerminationStatus.NOT_TERMINATED;
Expand All @@ -45,7 +48,12 @@ public TerminationStatus getTerminationStatus() {

@Override
public String getPhaseTypeString() {
return "Construction Heuristics";
return PhaseType.CONSTRUCTION_HEURISTIC.getPhaseName();
}

@Override
public IntFunction<EventProducerId> getEventProducerIdSupplier() {
return EventProducerId::constructionHeuristic;
}

// ************************************************************************
Expand Down Expand Up @@ -196,7 +204,7 @@ private void updateBestSolutionAndFire(ConstructionHeuristicPhaseScope<Solution_
if (!isNested() && !phaseScope.getStartingScore().equals(phaseScope.getBestScore())) {
// Only update the best solution if the CH made any change; nested phases don't update the best solution.
var solver = phaseScope.getSolverScope().getSolver();
solver.getBestSolutionRecaller().updateBestSolutionAndFire(phaseScope.getSolverScope());
solver.getBestSolutionRecaller().updateBestSolutionAndFire(phaseScope.getSolverScope(), phaseScope);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@
import java.util.Collections;
import java.util.Comparator;
import java.util.TreeSet;
import java.util.function.IntFunction;

import ai.timefold.solver.core.api.domain.solution.PlanningSolution;
import ai.timefold.solver.core.api.score.Score;
import ai.timefold.solver.core.api.solver.event.EventProducerId;
import ai.timefold.solver.core.api.solver.event.PhaseType;
import ai.timefold.solver.core.config.solver.EnvironmentMode;
import ai.timefold.solver.core.impl.exhaustivesearch.decider.ExhaustiveSearchDecider;
import ai.timefold.solver.core.impl.exhaustivesearch.node.ExhaustiveSearchLayer;
Expand Down Expand Up @@ -47,7 +50,12 @@ private DefaultExhaustiveSearchPhase(Builder<Solution_> builder) {

@Override
public String getPhaseTypeString() {
return "Exhaustive Search";
return PhaseType.EXHAUSTIVE_SEARCH.getPhaseName();
}

@Override
public IntFunction<EventProducerId> getEventProducerIdSupplier() {
return EventProducerId::exhaustiveSearch;
}

// ************************************************************************
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.IntFunction;

import ai.timefold.solver.core.api.domain.solution.PlanningSolution;
import ai.timefold.solver.core.api.score.Score;
import ai.timefold.solver.core.api.score.constraint.ConstraintMatchTotal;
import ai.timefold.solver.core.api.solver.event.EventProducerId;
import ai.timefold.solver.core.api.solver.event.PhaseType;
import ai.timefold.solver.core.config.solver.EnvironmentMode;
import ai.timefold.solver.core.config.solver.monitoring.SolverMetric;
import ai.timefold.solver.core.impl.localsearch.decider.LocalSearchDecider;
Expand All @@ -31,7 +34,6 @@
*/
public class DefaultLocalSearchPhase<Solution_> extends AbstractPhase<Solution_> implements LocalSearchPhase<Solution_>,
LocalSearchPhaseLifecycleListener<Solution_> {

protected final LocalSearchDecider<Solution_> decider;
protected final AtomicLong acceptedMoveCountPerStep = new AtomicLong(0);
protected final AtomicLong selectedMoveCountPerStep = new AtomicLong(0);
Expand All @@ -47,7 +49,12 @@ private DefaultLocalSearchPhase(Builder<Solution_> builder) {

@Override
public String getPhaseTypeString() {
return "Local Search";
return PhaseType.LOCAL_SEARCH.getPhaseName();
}

@Override
public IntFunction<EventProducerId> getEventProducerIdSupplier() {
return EventProducerId::localSearch;
}

// ************************************************************************
Expand Down
Loading
Loading