Skip to content

chore: move quickstarts to declarative shadow variables #902

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: development
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,12 @@
import ai.timefold.solver.core.api.domain.variable.NextElementShadowVariable;
import ai.timefold.solver.core.api.domain.variable.PreviousElementShadowVariable;
import ai.timefold.solver.core.api.domain.variable.ShadowVariable;
import ai.timefold.solver.core.preview.api.domain.variable.declarative.ShadowSources;

import com.fasterxml.jackson.annotation.JsonIgnore;

import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;

@PlanningEntity
Expand All @@ -34,13 +37,8 @@ public class Job {

@InverseRelationShadowVariable(sourceVariableName = "jobs")
private Line line;
@ShadowVariable(
variableListenerClass = LineOperatorUpdatingVariableListener.class,
sourceEntityClass = Line.class,
sourceVariableName = "operator")
@ShadowVariable(
variableListenerClass = JobOperatorUpdatingVariableListener.class,
sourceVariableName = "line")

@ShadowVariable(supplierName = "lineOperatorSupplier")
private Operator lineOperator;
@JsonIgnore
@PreviousElementShadowVariable(sourceVariableName = "jobs")
Expand All @@ -52,11 +50,11 @@ public class Job {
/**
* Start is after cleanup.
*/
@CascadingUpdateShadowVariable(targetMethodName = "updateStartCleaningDateTime")
@ShadowVariable(supplierName = "startCleaningDateTimeSupplier")
private LocalDateTime startCleaningDateTime;
@CascadingUpdateShadowVariable(targetMethodName = "updateStartCleaningDateTime")
@ShadowVariable(supplierName = "startProductionDateTimeSupplier")
private LocalDateTime startProductionDateTime;
@CascadingUpdateShadowVariable(targetMethodName = "updateStartCleaningDateTime")
@ShadowVariable(supplierName = "endDateTimeSupplier")
private LocalDateTime endDateTime;

// No-arg constructor required for Timefold
Expand Down Expand Up @@ -188,30 +186,44 @@ public void setEndDateTime(LocalDateTime endDateTime) {
// ************************************************************************
// Complex methods
// ************************************************************************
@SuppressWarnings("unused")
@ShadowSources({"line", "line.operator"})
private Operator lineOperatorSupplier() {
if (line == null) {
return null;
}
return line.getOperator();
}

@SuppressWarnings("unused")
@ShadowSources({"line", "previousJob.endDateTime"})
private LocalDateTime startCleaningDateTimeSupplier() {
if (line == null) {
return null;
}
if (previousJob == null) {
return line.getStartDateTime();
} else {
return previousJob.getEndDateTime();
}
}

@SuppressWarnings("unused")
private void updateStartCleaningDateTime() {
if (getLine() == null) {
if (getStartCleaningDateTime() != null) {
setStartCleaningDateTime(null);
setStartProductionDateTime(null);
setEndDateTime(null);
}
return;
@ShadowSources({"line", "startCleaningDateTime"})
private LocalDateTime startProductionDateTimeSupplier() {
if (line == null) {
return null;
}
Job previous = getPreviousJob();
LocalDateTime startCleaning;
LocalDateTime startProduction;
if (previous == null) {
startCleaning = line.getStartDateTime();
startProduction = line.getStartDateTime();
if (previousJob == null) {
return line.getStartDateTime();
} else {
startCleaning = previous.getEndDateTime();
startProduction = startCleaning == null ? null : startCleaning.plus(getProduct().getCleanupDuration(previous.getProduct()));
return startCleaningDateTime == null ? null : startCleaningDateTime.plus(getProduct().getCleanupDuration(previousJob.getProduct()));
}
setStartCleaningDateTime(startCleaning);
setStartProductionDateTime(startProduction);
var endTime = startProduction == null ? null : startProduction.plus(getDuration());
setEndDateTime(endTime);
}

@SuppressWarnings("unused")
@ShadowSources({"startProductionDateTime"})
private LocalDateTime endDateTimeSupplier() {
return startProductionDateTime == null ? null : startProductionDateTime.plus(getDuration());
}
}

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

# The solver runs for 30 seconds. To run for 5 minutes use "5m" and for 2 hours use "2h".
quarkus.timefold.solver.termination.spent-limit=30s
quarkus.timefold.solver.enabled-preview-features=DECLARATIVE_SHADOW_VARIABLES

# To change how many solvers to run in parallel
# timefold.solver-manager.parallel-solver-count=4
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@
import ai.timefold.solver.core.api.domain.lookup.PlanningId;
import ai.timefold.solver.core.api.domain.variable.PlanningVariable;
import ai.timefold.solver.core.api.domain.variable.ShadowVariable;

import org.acme.maintenancescheduling.solver.EndDateUpdatingVariableListener;
import ai.timefold.solver.core.preview.api.domain.variable.declarative.ShadowSources;

@PlanningEntity
public class Job {
Expand All @@ -29,7 +28,7 @@ public class Job {
// Follows the TimeGrain Design Pattern
@PlanningVariable
private LocalDate startDate; // Inclusive
@ShadowVariable(variableListenerClass = EndDateUpdatingVariableListener.class, sourceVariableName = "startDate")
@ShadowVariable(supplierName = "endDateSupplier")
private LocalDate endDate; // Exclusive

public Job() {
Expand All @@ -56,7 +55,7 @@ public Job(String id, String name, int durationInDays, LocalDate minStartDate, L
this.tags = tags;
this.crew = crew;
this.startDate = startDate;
this.endDate = EndDateUpdatingVariableListener.calculateEndDate(startDate, durationInDays);
this.endDate = calculateEndDate(startDate, durationInDays);
}

@Override
Expand Down Expand Up @@ -119,4 +118,25 @@ public LocalDate getEndDate() {
public void setEndDate(LocalDate endDate) {
this.endDate = endDate;
}

// ************************************************************************
// Complex methods
// ************************************************************************
@SuppressWarnings("unused")
@ShadowSources("startDate")
public LocalDate endDateSupplier() {
return calculateEndDate(startDate, durationInDays);
}

public static LocalDate calculateEndDate(LocalDate startDate, int durationInDays) {
if (startDate == null) {
return null;
} else {
// Skip weekends. Does not work for holidays.
// To skip holidays too, cache all working days in WorkCalendar.
// Keep in sync with MaintenanceSchedule.createStartDateList().
int weekendPadding = 2 * ((durationInDays + (startDate.getDayOfWeek().getValue() - 1)) / 5);
return startDate.plusDays(durationInDays + weekendPadding);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public MaintenanceSchedule(HardSoftLongScore score, SolverStatus solverStatus) {
public List<LocalDate> createStartDateList() {
return workCalendar.getFromDate().datesUntil(workCalendar.getToDate())
// Skip weekends. Does not work for holidays.
// Keep in sync with EndDateUpdatingVariableListener.updateEndDate().
// Keep in sync with Job.calculateEndDate().
// To skip holidays too, cache all working days in WorkCalendar.
.filter(date -> date.getDayOfWeek() != DayOfWeek.SATURDAY
&& date.getDayOfWeek() != DayOfWeek.SUNDAY)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
import org.acme.maintenancescheduling.domain.Job;
import org.acme.maintenancescheduling.domain.MaintenanceSchedule;
import org.acme.maintenancescheduling.domain.WorkCalendar;
import org.acme.maintenancescheduling.solver.EndDateUpdatingVariableListener;

@ApplicationScoped
public class DemoDataGenerator {
Expand Down Expand Up @@ -63,9 +62,9 @@ public MaintenanceSchedule generateDemoData(DemoData demoData) {
+ random.nextInt(workdayTotal - (durationInDays + 5));
int minWorkdayOffset = random.nextInt(workdayTotal - minMaxBetweenWorkdays + 1);
int minIdealEndBetweenWorkdays = minMaxBetweenWorkdays - 1 - random.nextInt(4);
LocalDate minStartDate = EndDateUpdatingVariableListener.calculateEndDate(fromDate, minWorkdayOffset);
LocalDate maxEndDate = EndDateUpdatingVariableListener.calculateEndDate(minStartDate, minMaxBetweenWorkdays);
LocalDate idealEndDate = EndDateUpdatingVariableListener.calculateEndDate(minStartDate, minIdealEndBetweenWorkdays);
LocalDate minStartDate = Job.calculateEndDate(fromDate, minWorkdayOffset);
LocalDate maxEndDate = Job.calculateEndDate(minStartDate, minMaxBetweenWorkdays);
LocalDate idealEndDate = Job.calculateEndDate(minStartDate, minIdealEndBetweenWorkdays);
Set<String> tags = random.nextDouble() < 0.1 ? Set.of(jobArea, "Subway") : Set.of(jobArea);
jobs.add(new Job(Integer.toString(i), jobArea + " " + jobTarget, durationInDays, minStartDate, maxEndDate, idealEndDate,
tags));
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

# The solver runs for 30 seconds. To run for 5 minutes use "5m" and for 2 hours use "2h".
quarkus.timefold.solver.termination.spent-limit=30s
quarkus.timefold.solver.enabled-preview-features=DECLARATIVE_SHADOW_VARIABLES

# To change how many solvers to run in parallel
# timefold.solver-manager.parallel-solver-count=4
Expand Down
Loading
Loading