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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package ai.timefold.solver.service.definition.api;

/**
* Marker interface for an optional output produced by a {@link ModelPostProcessor} for a completed model run.
*/
public interface ModelPostProcessingResult {

}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,18 @@ public interface ModelPostProcessor {
*/
void process(ModelOutput modelOutput, SolverModel<?> solverModel, String id);

/**
* Process a successfully solved model run and optionally return a result to attach to the final best solution event.
*
* @param solverModel the model with all data taken from the solving
* @param id unique identifier of the model
* @return the result to attach to the final best solution event, or {@code null} if this post-processor contributes none
*/
default ModelPostProcessingResult processWithResult(ModelOutput modelOutput, SolverModel<?> solverModel, String id) {
process(modelOutput, solverModel, id);
return null;
}

/**
* Process successfully computed model (not yet solved).
* <p>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package ai.timefold.solver.service.definition.internal.events;

import java.util.List;

import ai.timefold.solver.core.api.solver.SolverJob;
import ai.timefold.solver.service.definition.api.ModelPostProcessingResult;
import ai.timefold.solver.service.definition.api.SolverModel;
import ai.timefold.solver.service.definition.api.domain.Metadata;

Expand All @@ -9,8 +12,23 @@
*/
public final class FinalBestSolutionEvent extends SolverWorkerEvent {

private final List<ModelPostProcessingResult> postProcessingResults;

public FinalBestSolutionEvent(Metadata metadata, SolverModel model, SolverJob job, String planName, String tenantName) {
this(metadata, model, job, planName, tenantName, null);
}

public FinalBestSolutionEvent(Metadata metadata, SolverModel model, SolverJob job, String planName, String tenantName,

Check warning on line 21 in service/definition/src/main/java/ai/timefold/solver/service/definition/internal/events/FinalBestSolutionEvent.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Provide the parametrized type for this generic.

See more on https://sonarcloud.io/project/issues?id=ai.timefold%3Atimefold-solver&issues=AZ6ytUut7-YR18ojaNtZ&open=AZ6ytUut7-YR18ojaNtZ&pullRequest=2355

Check warning on line 21 in service/definition/src/main/java/ai/timefold/solver/service/definition/internal/events/FinalBestSolutionEvent.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Provide the parametrized type for this generic.

See more on https://sonarcloud.io/project/issues?id=ai.timefold%3Atimefold-solver&issues=AZ6ytUut7-YR18ojaNtb&open=AZ6ytUut7-YR18ojaNtb&pullRequest=2355

Check warning on line 21 in service/definition/src/main/java/ai/timefold/solver/service/definition/internal/events/FinalBestSolutionEvent.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Provide the parametrized type for this generic.

See more on https://sonarcloud.io/project/issues?id=ai.timefold%3Atimefold-solver&issues=AZ6ytUut7-YR18ojaNta&open=AZ6ytUut7-YR18ojaNta&pullRequest=2355
List<ModelPostProcessingResult> postProcessingResults) {
super(metadata, model, job, planName, tenantName, null);
this.postProcessingResults = postProcessingResults == null ? List.of() : List.copyOf(postProcessingResults);
}

/**
* The results contributed by the post-processors for this run, in the order the post-processors ran.
*/
public List<ModelPostProcessingResult> getPostProcessingResults() {
return postProcessingResults;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import static ai.timefold.solver.service.definition.internal.platform.EnvironmentVars.ENV_TIMEFOLD_TENANT_NAME;

import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
Expand Down Expand Up @@ -32,6 +33,7 @@
import ai.timefold.solver.service.definition.api.ModelConvertorBase;
import ai.timefold.solver.service.definition.api.ModelInput;
import ai.timefold.solver.service.definition.api.ModelOutput;
import ai.timefold.solver.service.definition.api.ModelPostProcessingResult;
import ai.timefold.solver.service.definition.api.ModelPostProcessor;
import ai.timefold.solver.service.definition.api.SolverModel;
import ai.timefold.solver.service.definition.api.SolvingStatus;
Expand Down Expand Up @@ -699,10 +701,12 @@ protected void notifyOnComplete(String id, SolverModel solverModel) {
extractOutputMetrics(solverModel));

processor.onNext(metadata);
sendEvent(finalSolutionEmitter, new FinalBestSolutionEvent(metadata, solverModel,
new SolverWorkerJobState(SolverStatus.NOT_SOLVING, solverJob), planName, tenantName));

postProcessCompleteOutput(id, modelOutput, solverModel);
List<ModelPostProcessingResult> postProcessingResults = postProcessCompleteOutput(id, modelOutput, solverModel);

sendEvent(finalSolutionEmitter, new FinalBestSolutionEvent(metadata, solverModel,
new SolverWorkerJobState(SolverStatus.NOT_SOLVING, solverJob), planName, tenantName,
postProcessingResults));

sendEvent(scheduleCompletedEmitter, new ItemCompleted(id));

Expand All @@ -728,15 +732,21 @@ private void postProcessOutput(String id, ModelOutput modelOutput, SolverModel s
}
}

private void postProcessCompleteOutput(String id, ModelOutput modelOutput, SolverModel solverModel) {
private List<ModelPostProcessingResult> postProcessCompleteOutput(String id, ModelOutput modelOutput,
SolverModel solverModel) {
List<ModelPostProcessingResult> results = new ArrayList<>();
for (var processor : modelPostProcessors) {
try {
processor.process(modelOutput, solverModel, id);
ModelPostProcessingResult result = processor.processWithResult(modelOutput, solverModel, id);
if (result != null) {
results.add(result);
}
} catch (Throwable e) {
LOGGER.warn("Unexpected error while invoking post processor {} that returned {}",
processor.getClass().getSimpleName(), e.getMessage());
}
}
return results;
}

private void storeSolvedInput(String datasetId, ModelOutput modelOutput) {
Expand Down
Loading