From 30fb5096de2817adf1ce71296f77a1f199975ebf Mon Sep 17 00:00:00 2001 From: Marius Merschformann Date: Thu, 19 Dec 2024 10:16:57 +0100 Subject: [PATCH 1/7] Adding option for early termination when plateauing --- go.mod | 2 +- go.sum | 4 +- solve_solver.go | 54 +++++- solve_solver_parallel.go | 28 ++- solve_terminate.go | 119 +++++++++++++ solver_parallel.go | 1 + tests/plateau_stopping_criterion/input.json | 21 +++ .../input.json.duration.golden | 166 ++++++++++++++++++ .../input.json.iterations.golden | 166 ++++++++++++++++++ tests/plateau_stopping_criterion/main.go | 64 +++++++ tests/plateau_stopping_criterion/main_test.go | 84 +++++++++ 11 files changed, 699 insertions(+), 10 deletions(-) create mode 100644 solve_terminate.go create mode 100644 tests/plateau_stopping_criterion/input.json create mode 100644 tests/plateau_stopping_criterion/input.json.duration.golden create mode 100644 tests/plateau_stopping_criterion/input.json.iterations.golden create mode 100644 tests/plateau_stopping_criterion/main.go create mode 100644 tests/plateau_stopping_criterion/main_test.go diff --git a/go.mod b/go.mod index 11ccbfb..cf0d89f 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/nextmv-io/nextroute go 1.21 require ( - github.com/nextmv-io/sdk v1.8.2 + github.com/nextmv-io/sdk v1.8.3-0.20241219091227-002f36a342d6 gonum.org/v1/gonum v0.14.0 ) diff --git a/go.sum b/go.sum index ea77de9..23bbdf4 100644 --- a/go.sum +++ b/go.sum @@ -301,8 +301,8 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/nextmv-io/sdk v1.8.2 h1:9jqtchlgrt7aDzRCRRBr9AM192tRBaN82hUIxpOV/Xg= -github.com/nextmv-io/sdk v1.8.2/go.mod h1:Y48XLPcIOOxRgO86ICNpqGrH2N5+dd1TDNvef/FD2Kc= +github.com/nextmv-io/sdk v1.8.3-0.20241219091227-002f36a342d6 h1:icnvtf2R9Zg9Qs+SyVbQ6HEZ5fPj6A+7ywxOT2lrWus= +github.com/nextmv-io/sdk v1.8.3-0.20241219091227-002f36a342d6/go.mod h1:Y48XLPcIOOxRgO86ICNpqGrH2N5+dd1TDNvef/FD2Kc= github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= diff --git a/solve_solver.go b/solve_solver.go index a5e4ad2..93c64e0 100644 --- a/solve_solver.go +++ b/solve_solver.go @@ -30,10 +30,47 @@ type SolverOptions struct { Restart IntParameterOptions `json:"restart" usage:"restart parameter"` } +// PlateauOptions define how the solver should react to plateaus, i.e., periods +// without significant improvement in the best solution. The solver stops when +// either the Duration or Iterations condition is met, depending on which occurs +// first. +type PlateauOptions struct { + // Duration is the maximum duration without (significant) improvement. I.e., + // if the solver does not improve the best solution (significantly) within + // this duration, the solver will stop. A duration of 0 means that the + // solver will not check for (significant) improvements based on duration, + // only based on iterations. If both duration and iterations are set, the + // solver will stop if either one of the conditions is met. Default: 0s + // (time-based plateau detection disabled). + Duration time.Duration `json:"duration" usage:"maximum duration without (significant) improvement" default:"0s"` + // Iterations is the maximum number of iterations without (significant) + // improvement. I.e., if the solver does not improve the best solution + // within this number of iterations, the solver will stop. A negative value + // means that the solver will not check for (significant) improvements based + // on iterations, only based on duration. Default: 0 (iteration-based + // plateau detection disabled). + Iterations int `json:"iterations" usage:"maximum number of iterations without (significant) improvement" default:"0"` + // RelativeThreshold defines the minimum relative improvement of the best + // solution withing the plateau duration or iterations to be considered + // significant. E.g., a value of 0.1 means that the best solution must + // improve by at least 10% to be considered significant, thus resetting the + // plateau timer/counter. A negative value means that the relative threshold + // is disabled. + RelativeThreshold float64 `json:"relative_threshold" usage:"relative threshold for significant improvement" default:"0.0"` + // AbsoluteThreshold defines the minimum absolute improvement of the best + // solution withing the plateau duration or iterations to be considered + // significant. E.g., a value of 10 means that the best solution must + // improve by at least 10 to be considered significant, thus resetting the + // plateau timer/counter. A negative value means that the absolute threshold + // is disabled. + AbsoluteThreshold float64 `json:"absolute_threshold" usage:"absolute threshold for significant improvement" default:"-1.0"` +} + // SolveOptions holds the options for the solve process. type SolveOptions struct { - Iterations int `json:"iterations" usage:"maximum number of iterations, -1 assumes no limit" default:"-1"` - Duration time.Duration `json:"duration" usage:"maximum duration of solver in seconds" default:"30s"` + Iterations int `json:"iterations" usage:"maximum number of iterations, -1 assumes no limit" default:"-1"` + Duration time.Duration `json:"duration" usage:"maximum duration of solver in seconds" default:"30s"` + Plateau PlateauOptions `json:"plateau" usage:"plateau options"` } // Solver is the interface for the Adaptive Local Neighborhood Search algorithm @@ -89,6 +126,7 @@ type solveImpl struct { random *rand.Rand solveOperators SolveOperators parameters SolveParameters + plateauTracker *plateauTracker progression []ProgressionEntry } @@ -211,6 +249,11 @@ func (s *solveImpl) invoke( func (s *solveImpl) newBestSolution(solution Solution, solveInformation *solveInformationImpl) { s.bestSolution = solution.Copy() s.solveEvents.NewBestSolution.Trigger(solveInformation) + s.plateauTracker.onImprovement( + time.Since(solveInformation.Start()).Seconds(), + solveInformation.Iteration(), + solveInformation.Solver().BestSolution().Score(), + ) } func (s *solveImpl) Solve( @@ -253,6 +296,10 @@ func (s *solveImpl) Solve( start.Add(solveOptions.Duration), ) + if plateauTrackingActivated(solveOptions.Plateau) { + s.plateauTracker = newPlateauTracker(solveOptions.Plateau) + } + solveInformation := &solveInformationImpl{ iteration: 0, solver: s, @@ -305,6 +352,9 @@ func (s *solveImpl) Solve( Error: nil, } } + if s.plateauTracker.IsStop(iteration, time.Since(start)) { + break Loop + } } } diff --git a/solve_solver_parallel.go b/solve_solver_parallel.go index a18c5b5..affca01 100644 --- a/solve_solver_parallel.go +++ b/solve_solver_parallel.go @@ -21,11 +21,12 @@ const Iterations string = "iterations" // ParallelSolveOptions holds the options for the parallel solver. type ParallelSolveOptions struct { - Iterations int `json:"iterations" usage:"maximum number of iterations, -1 assumes no limit; iterations are counted after start solutions are generated" default:"-1"` - Duration time.Duration `json:"duration" usage:"maximum duration of the solver" default:"5s"` - ParallelRuns int `json:"parallel_runs" usage:"maximum number of parallel runs, -1 results in using all available resources" default:"-1"` - StartSolutions int `json:"start_solutions" usage:"number of solutions to generate on top of those passed in; one solution generated with sweep algorithm, the rest generated randomly" default:"-1"` - RunDeterministically bool `json:"run_deterministically" usage:"run the parallel solver deterministically"` + Iterations int `json:"iterations" usage:"maximum number of iterations, -1 assumes no limit; iterations are counted after start solutions are generated" default:"-1"` + Duration time.Duration `json:"duration" usage:"maximum duration of the solver" default:"5s"` + Plateau PlateauOptions `json:"plateau" usage:"plateau options"` + ParallelRuns int `json:"parallel_runs" usage:"maximum number of parallel runs, -1 results in using all available resources" default:"-1"` + StartSolutions int `json:"start_solutions" usage:"number of solutions to generate on top of those passed in; one solution generated with sweep algorithm, the rest generated randomly" default:"-1"` + RunDeterministically bool `json:"run_deterministically" usage:"run the parallel solver deterministically"` } // ParallelSolver is the interface for parallel solver. The parallel solver will @@ -136,6 +137,7 @@ type parallelSolverImpl struct { parallelSolveEvents ParallelSolveEvents solveOptionsFactory SolveOptionsFactory solverFactory SolverFactory + plateauTracker *plateauTracker } func (s *parallelSolverImpl) ParallelSolveEvents() ParallelSolveEvents { @@ -199,6 +201,7 @@ func (s *parallelSolverImpl) Solve( ParallelRuns: options.ParallelRuns, StartSolutions: options.StartSolutions, RunDeterministically: options.RunDeterministically, + Plateau: options.Plateau, } if interpretedParallelSolveOptions.ParallelRuns == -1 { @@ -230,6 +233,10 @@ func (s *parallelSolverImpl) Solve( } } + if plateauTrackingActivated(interpretedParallelSolveOptions.Plateau) { + s.plateauTracker = newPlateauTracker(interpretedParallelSolveOptions.Plateau) + } + start := time.Now() if ctx.Value(run.Start) != nil { @@ -284,6 +291,11 @@ func (s *parallelSolverImpl) Solve( Value: solutionContainer.Solution.Score(), Iterations: solutionContainer.Iterations, }) + s.plateauTracker.onImprovement( + time.Since(start).Seconds(), + solutionContainer.Iterations, + solutionContainer.Solution.Score(), + ) } } @@ -351,6 +363,12 @@ func (s *parallelSolverImpl) Solve( if totalIterations.Add(1) >= int64(interpretedParallelSolveOptions.Iterations) { cancel() } + if s.plateauTracker.IsStop( + int(totalIterations.Load()), + time.Since(start), + ) { + cancel() + } }) opt, err := s.solveOptionsFactory( diff --git a/solve_terminate.go b/solve_terminate.go new file mode 100644 index 0000000..1c7c645 --- /dev/null +++ b/solve_terminate.go @@ -0,0 +1,119 @@ +// © 2019-present nextmv.io inc + +package nextroute + +import ( + "time" +) + +type plateauTracker struct { + // progression is the value progression of the solver. This is tracked + // separately of any other progression tracking to avoid conflicts. + progression []ProgressionEntry + // durationIndex is the current index of the first progression entry within + // the duration cutoff. + durationIndex int + // iterationsIndex is the current index of the first progression entry + // within the iterations cutoff. + iterationsIndex int + // options are the options for the plateau tracker. + options PlateauOptions +} + +func newPlateauTracker(options PlateauOptions) *plateauTracker { + return &plateauTracker{ + progression: make([]ProgressionEntry, 0), + durationIndex: 0, + iterationsIndex: 0, + options: options, + } +} + +// plateauTrackingActivated returns true if the plateau tracking should be +// activated based on the provided options. +func plateauTrackingActivated(options PlateauOptions) bool { + // We need to be testing within some duration or iteration cutoff. + return (options.Duration > 0 || options.Iterations > 0) && + // We need to have some threshold configured (negative threshold is deactivating the corresponding check). + (options.AbsoluteThreshold >= 0 || options.RelativeThreshold >= 0) +} + +// onImprovement is called to update the plateau tracker whenever a new +// improvement is found. +func (t *plateauTracker) onImprovement(elapsed float64, iterations int, value float64) { + if t == nil { + return + } + // Add the new progression entry. + t.progression = append(t.progression, ProgressionEntry{ + ElapsedSeconds: elapsed, + Value: value, + Iterations: iterations, + }) +} + +// IsStop returns true if the solver should stop due to a detected plateau. +func (t *plateauTracker) IsStop(iterations int, elapsed time.Duration) bool { + if t == nil { + return false + } + + currentValue := t.progression[len(t.progression)-1].Value + + // Check if no significantly improving solutions were found during the + // configured duration. + if t.options.Duration > 0 { + cutoffSeconds := t.options.Duration.Seconds() + elapsedSeconds := elapsed.Seconds() + // Move the duration index to the first entry within the cutoff. + for t.durationIndex < len(t.progression) && + (elapsedSeconds-t.progression[t.durationIndex].ElapsedSeconds) > cutoffSeconds { + t.durationIndex++ + } + // If the duration index is at the end of the progression, no + // improvement was found within the cutoff. + if t.durationIndex == len(t.progression) { + return true + } + // Compare the current value to the value at the duration index. + cutoffValue := t.progression[t.durationIndex].Value + if t.options.AbsoluteThreshold >= 0 && + currentValue-cutoffValue < t.options.AbsoluteThreshold { + return true + } + if t.options.RelativeThreshold >= 0 && + currentValue > 0 && // Relative threshold is only supported for positive values. + (currentValue-cutoffValue)/currentValue < t.options.RelativeThreshold { + return true + } + } + + // Check if no significantly improving solutions were found during the + // configured iterations. + if t.options.Iterations > 0 { + // Move the iterations index to the first entry within the cutoff. + for t.iterationsIndex < len(t.progression) && + iterations-t.progression[t.iterationsIndex].Iterations > t.options.Iterations { + t.iterationsIndex++ + } + // If the iterations index is at the end of the progression, no + // improvement was found within the cutoff. + if t.iterationsIndex == len(t.progression) { + return true + } + // Compare the current value to the value at the iterations index. + cutoffValue := t.progression[t.iterationsIndex].Value + if t.options.AbsoluteThreshold >= 0 && + currentValue-cutoffValue < t.options.AbsoluteThreshold { + return true + } + if t.options.RelativeThreshold >= 0 && + currentValue > 0 && // Relative threshold is only supported for positive values. + (currentValue-cutoffValue)/currentValue < t.options.RelativeThreshold { + return true + } + } + + // No plateau detected. + return false +} diff --git a/solver_parallel.go b/solver_parallel.go index d1f39f1..1a486ef 100644 --- a/solver_parallel.go +++ b/solver_parallel.go @@ -86,6 +86,7 @@ func (p *parallelSolverWrapperImpl) Solve( ParallelRuns: solveOptions.ParallelRuns, StartSolutions: solveOptions.StartSolutions, RunDeterministically: solveOptions.RunDeterministically, + Plateau: solveOptions.Plateau, } if interpretedParallelSolveOptions.ParallelRuns == -1 { diff --git a/tests/plateau_stopping_criterion/input.json b/tests/plateau_stopping_criterion/input.json new file mode 100644 index 0000000..039c6f4 --- /dev/null +++ b/tests/plateau_stopping_criterion/input.json @@ -0,0 +1,21 @@ +{ + "defaults": { + "vehicles": { + "speed": 20, + "start_time": "2023-01-01T06:00:00-06:00", + "end_time": "2023-01-01T10:00:00-06:00" + } + }, + "stops": [ + { + "id": "Fushimi Inari Taisha", + "location": { "lon": 135.772695, "lat": 34.967146 } + } + ], + "vehicles": [ + { + "id": "v1", + "start_location": { "lon": 135.672009, "lat": 35.017209 } + } + ] +} diff --git a/tests/plateau_stopping_criterion/input.json.duration.golden b/tests/plateau_stopping_criterion/input.json.duration.golden new file mode 100644 index 0000000..150e7db --- /dev/null +++ b/tests/plateau_stopping_criterion/input.json.duration.golden @@ -0,0 +1,166 @@ +{ + "options": { + "check": { + "duration": 30000000000, + "verbosity": "off" + }, + "format": { + "disable": { + "progression": true + } + }, + "model": { + "constraints": { + "disable": { + "attributes": false, + "capacities": null, + "capacity": false, + "distance_limit": false, + "groups": false, + "maximum_duration": false, + "maximum_stops": false, + "maximum_wait_stop": false, + "maximum_wait_vehicle": false, + "mixing_items": false, + "precedence": false, + "start_time_windows": false, + "vehicle_end_time": false, + "vehicle_start_time": false + }, + "enable": { + "cluster": false + } + }, + "objectives": { + "capacities": "", + "cluster": 0, + "early_arrival_penalty": 1, + "late_arrival_penalty": 1, + "min_stops": 1, + "stop_balance": 0, + "travel_duration": 0, + "unplanned_penalty": 1, + "vehicle_activation_penalty": 1, + "vehicles_duration": 1 + }, + "properties": { + "disable": { + "duration_groups": false, + "durations": false, + "initial_solution": false, + "stop_duration_multipliers": false + }, + "maximum_time_horizon": 15552000 + }, + "validate": { + "disable": { + "resources": false, + "start_time": false + }, + "enable": { + "matrix": false, + "matrix_asymmetry_tolerance": 20 + } + } + }, + "solve": { + "duration": 10000000000, + "iterations": -1, + "parallel_runs": 1, + "plateau": { + "absolute_threshold": -1, + "duration": 500000000, + "iterations": 0, + "relative_threshold": 0 + }, + "run_deterministically": true, + "start_solutions": 1 + } + }, + "solutions": [ + { + "objective": { + "name": "1 * vehicles_duration + 1 * unplanned_penalty", + "objectives": [ + { + "base": 536.4530622959137, + "factor": 1, + "name": "vehicles_duration", + "value": 536.4530622959137 + }, + { + "factor": 1, + "name": "unplanned_penalty", + "value": 0 + } + ], + "value": 536.4530622959137 + }, + "unplanned": [], + "vehicles": [ + { + "id": "v1", + "route": [ + { + "arrival_time": "2023-01-01T06:00:00-06:00", + "cumulative_travel_duration": 0, + "end_time": "2023-01-01T06:00:00-06:00", + "start_time": "2023-01-01T06:00:00-06:00", + "stop": { + "id": "v1-start", + "location": { + "lat": 35.017209, + "lon": 135.672009 + } + }, + "travel_duration": 0 + }, + { + "arrival_time": "2023-01-01T06:08:56-06:00", + "cumulative_travel_distance": 10729, + "cumulative_travel_duration": 536, + "end_time": "2023-01-01T06:08:56-06:00", + "start_time": "2023-01-01T06:08:56-06:00", + "stop": { + "id": "Fushimi Inari Taisha", + "location": { + "lat": 34.967146, + "lon": 135.772695 + } + }, + "travel_distance": 10729, + "travel_duration": 536 + } + ], + "route_duration": 536, + "route_travel_distance": 10729, + "route_travel_duration": 536 + } + ] + } + ], + "statistics": { + "result": { + "custom": { + "activated_vehicles": 1, + "max_duration": 536, + "max_stops_in_vehicle": 1, + "max_travel_duration": 536, + "min_duration": 536, + "min_stops_in_vehicle": 1, + "min_travel_duration": 536, + "unplanned_stops": 0 + }, + "duration": 0.123, + "value": 536.4530622959137 + }, + "run": { + "duration": 0.5, + "iterations": 123 + }, + "schema": "v1" + }, + "version": { + "sdk": "VERSION" + } +} diff --git a/tests/plateau_stopping_criterion/input.json.iterations.golden b/tests/plateau_stopping_criterion/input.json.iterations.golden new file mode 100644 index 0000000..1ed9f71 --- /dev/null +++ b/tests/plateau_stopping_criterion/input.json.iterations.golden @@ -0,0 +1,166 @@ +{ + "options": { + "check": { + "duration": 30000000000, + "verbosity": "off" + }, + "format": { + "disable": { + "progression": true + } + }, + "model": { + "constraints": { + "disable": { + "attributes": false, + "capacities": null, + "capacity": false, + "distance_limit": false, + "groups": false, + "maximum_duration": false, + "maximum_stops": false, + "maximum_wait_stop": false, + "maximum_wait_vehicle": false, + "mixing_items": false, + "precedence": false, + "start_time_windows": false, + "vehicle_end_time": false, + "vehicle_start_time": false + }, + "enable": { + "cluster": false + } + }, + "objectives": { + "capacities": "", + "cluster": 0, + "early_arrival_penalty": 1, + "late_arrival_penalty": 1, + "min_stops": 1, + "stop_balance": 0, + "travel_duration": 0, + "unplanned_penalty": 1, + "vehicle_activation_penalty": 1, + "vehicles_duration": 1 + }, + "properties": { + "disable": { + "duration_groups": false, + "durations": false, + "initial_solution": false, + "stop_duration_multipliers": false + }, + "maximum_time_horizon": 15552000 + }, + "validate": { + "disable": { + "resources": false, + "start_time": false + }, + "enable": { + "matrix": false, + "matrix_asymmetry_tolerance": 20 + } + } + }, + "solve": { + "duration": 10000000000, + "iterations": -1, + "parallel_runs": 1, + "plateau": { + "absolute_threshold": -1, + "duration": 0, + "iterations": 20, + "relative_threshold": 0 + }, + "run_deterministically": true, + "start_solutions": 1 + } + }, + "solutions": [ + { + "objective": { + "name": "1 * vehicles_duration + 1 * unplanned_penalty", + "objectives": [ + { + "base": 536.4530622959137, + "factor": 1, + "name": "vehicles_duration", + "value": 536.4530622959137 + }, + { + "factor": 1, + "name": "unplanned_penalty", + "value": 0 + } + ], + "value": 536.4530622959137 + }, + "unplanned": [], + "vehicles": [ + { + "id": "v1", + "route": [ + { + "arrival_time": "2023-01-01T06:00:00-06:00", + "cumulative_travel_duration": 0, + "end_time": "2023-01-01T06:00:00-06:00", + "start_time": "2023-01-01T06:00:00-06:00", + "stop": { + "id": "v1-start", + "location": { + "lat": 35.017209, + "lon": 135.672009 + } + }, + "travel_duration": 0 + }, + { + "arrival_time": "2023-01-01T06:08:56-06:00", + "cumulative_travel_distance": 10729, + "cumulative_travel_duration": 536, + "end_time": "2023-01-01T06:08:56-06:00", + "start_time": "2023-01-01T06:08:56-06:00", + "stop": { + "id": "Fushimi Inari Taisha", + "location": { + "lat": 34.967146, + "lon": 135.772695 + } + }, + "travel_distance": 10729, + "travel_duration": 536 + } + ], + "route_duration": 536, + "route_travel_distance": 10729, + "route_travel_duration": 536 + } + ] + } + ], + "statistics": { + "result": { + "custom": { + "activated_vehicles": 1, + "max_duration": 536, + "max_stops_in_vehicle": 1, + "max_travel_duration": 536, + "min_duration": 536, + "min_stops_in_vehicle": 1, + "min_travel_duration": 536, + "unplanned_stops": 0 + }, + "duration": 0.123, + "value": 536.4530622959137 + }, + "run": { + "duration": 0.123, + "iterations": 21 + }, + "schema": "v1" + }, + "version": { + "sdk": "VERSION" + } +} diff --git a/tests/plateau_stopping_criterion/main.go b/tests/plateau_stopping_criterion/main.go new file mode 100644 index 0000000..0428ae8 --- /dev/null +++ b/tests/plateau_stopping_criterion/main.go @@ -0,0 +1,64 @@ +// © 2019-present nextmv.io inc + +// package main holds the implementation of the nextroute template. +package main + +import ( + "context" + "log" + + "github.com/nextmv-io/nextroute" + "github.com/nextmv-io/nextroute/check" + "github.com/nextmv-io/nextroute/factory" + "github.com/nextmv-io/nextroute/schema" + "github.com/nextmv-io/sdk/run" + runSchema "github.com/nextmv-io/sdk/run/schema" +) + +func main() { + runner := run.CLI(solver) + err := runner.Run(context.Background()) + if err != nil { + log.Fatal(err) + } +} + +type options struct { + Model factory.Options `json:"model,omitempty"` + Solve nextroute.ParallelSolveOptions `json:"solve,omitempty"` + Format nextroute.FormatOptions `json:"format,omitempty"` + Check check.Options `json:"check,omitempty"` +} + +func solver( + ctx context.Context, + input schema.Input, + options options, +) (runSchema.Output, error) { + model, err := factory.NewModel(input, options.Model) + if err != nil { + return runSchema.Output{}, err + } + + solver, err := nextroute.NewParallelSolver(model) + if err != nil { + return runSchema.Output{}, err + } + + solutions, err := solver.Solve(ctx, options.Solve) + if err != nil { + return runSchema.Output{}, err + } + last, err := solutions.Last() + if err != nil { + return runSchema.Output{}, err + } + + output, err := check.Format(ctx, options, options.Check, solver, last) + if err != nil { + return runSchema.Output{}, err + } + output.Statistics.Result.Custom = factory.DefaultCustomResultStatistics(last) + + return output, nil +} diff --git a/tests/plateau_stopping_criterion/main_test.go b/tests/plateau_stopping_criterion/main_test.go new file mode 100644 index 0000000..062da7f --- /dev/null +++ b/tests/plateau_stopping_criterion/main_test.go @@ -0,0 +1,84 @@ +// © 2019-present nextmv.io inc + +package main + +import ( + "os" + "testing" + + "github.com/nextmv-io/sdk/golden" +) + +func TestMain(m *testing.M) { + golden.Setup() + code := m.Run() + golden.Teardown() + os.Exit(code) +} + +// TestPlateauIterations executes a golden file test applying the plateau +// stopping criterion using a number of iterations. +func TestPlateauIterations(t *testing.T) { + config := golden.Config{ + GoldenExtension: ".iterations.golden", + Args: []string{ + "-solve.duration", "10s", + "-format.disable.progression", + "-solve.parallelruns", "1", + "-solve.rundeterministically", + "-solve.startsolutions", "1", + "-solve.plateau.iterations", "20", + }, + TransientFields: []golden.TransientField{ + {Key: "$.version.sdk", Replacement: golden.StableVersion}, + {Key: "$.statistics.result.duration", Replacement: golden.StableFloat}, + {Key: "$.statistics.run.duration", Replacement: golden.StableFloat}, + {Key: ".solutions[0].check.duration_used", Replacement: golden.StableFloat}, + }, + Thresholds: golden.Tresholds{ + Float: 0.01, + CustomThresholds: golden.CustomThresholds{ + Int: map[string]int{ + "$.statistics.run.iterations": 0, + }, + }, + }, + } + golden.FileTest(t, "input.json", config) +} + +// TestPlateauDuration executes a golden file test applying the plateau +// stopping criterion using a duration. +func TestPlateauDuration(t *testing.T) { + config := golden.Config{ + GoldenExtension: ".duration.golden", + Args: []string{ + "-solve.duration", "10s", + "-format.disable.progression", + "-solve.parallelruns", "1", + "-solve.rundeterministically", + "-solve.startsolutions", "1", + "-solve.plateau.duration", "0.5s", + }, + TransientFields: []golden.TransientField{ + {Key: "$.version.sdk", Replacement: golden.StableVersion}, + {Key: "$.statistics.result.duration", Replacement: golden.StableFloat}, + {Key: "$.statistics.run.iterations", Replacement: golden.StableInt}, + {Key: ".solutions[0].check.duration_used", Replacement: golden.StableFloat}, + }, + OutputProcessConfig: golden.OutputProcessConfig{ + RoundingConfig: []golden.RoundingConfig{ + {Key: "$.statistics.run.duration", Precision: 1}, + }, + }, + Thresholds: golden.Tresholds{ + Float: 0.01, + CustomThresholds: golden.CustomThresholds{ + Float: map[string]float64{ + "$.statistics.run.duration": 0.2, + }, + }, + }, + } + golden.FileTest(t, "input.json", config) +} From 597abb860db0c41bceb530f085ed18b4bbc59668 Mon Sep 17 00:00:00 2001 From: Marius Merschformann Date: Thu, 19 Dec 2024 10:28:51 +0100 Subject: [PATCH 2/7] Fixing typo --- solve_solver.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/solve_solver.go b/solve_solver.go index 93c64e0..2c37984 100644 --- a/solve_solver.go +++ b/solve_solver.go @@ -51,14 +51,14 @@ type PlateauOptions struct { // plateau detection disabled). Iterations int `json:"iterations" usage:"maximum number of iterations without (significant) improvement" default:"0"` // RelativeThreshold defines the minimum relative improvement of the best - // solution withing the plateau duration or iterations to be considered + // solution within the plateau duration or iterations to be considered // significant. E.g., a value of 0.1 means that the best solution must // improve by at least 10% to be considered significant, thus resetting the // plateau timer/counter. A negative value means that the relative threshold // is disabled. RelativeThreshold float64 `json:"relative_threshold" usage:"relative threshold for significant improvement" default:"0.0"` // AbsoluteThreshold defines the minimum absolute improvement of the best - // solution withing the plateau duration or iterations to be considered + // solution within the plateau duration or iterations to be considered // significant. E.g., a value of 10 means that the best solution must // improve by at least 10 to be considered significant, thus resetting the // plateau timer/counter. A negative value means that the absolute threshold From 30ba6bb1b67fd3195064239dd5d205674c986aa7 Mon Sep 17 00:00:00 2001 From: Marius Merschformann Date: Thu, 19 Dec 2024 10:29:58 +0100 Subject: [PATCH 3/7] Updating expectations --- tests/check/input.json.golden | 6 ++++++ tests/custom_constraint/input.json.golden | 6 ++++++ tests/custom_matrices/input.json.golden | 6 ++++++ tests/custom_objective/input.json.golden | 6 ++++++ tests/custom_operators/input.json.golden | 6 ++++++ tests/golden/testdata/activation_penalty.json.go.golden | 6 ++++++ tests/golden/testdata/alternates.json.go.golden | 6 ++++++ tests/golden/testdata/basic.json.go.golden | 6 ++++++ tests/golden/testdata/capacity.json.go.golden | 6 ++++++ .../golden/testdata/compatibility_attributes.json.go.golden | 6 ++++++ tests/golden/testdata/complex_precedence.json.go.golden | 6 ++++++ tests/golden/testdata/custom_data.json.go.golden | 6 ++++++ tests/golden/testdata/defaults.json.go.golden | 6 ++++++ tests/golden/testdata/direct_precedence.json.go.golden | 6 ++++++ .../golden/testdata/direct_precedence_linked.json.go.golden | 6 ++++++ tests/golden/testdata/distance_matrix.json.go.golden | 6 ++++++ tests/golden/testdata/duration_groups.json.go.golden | 6 ++++++ .../duration_groups_with_stop_multiplier.json.go.golden | 6 ++++++ tests/golden/testdata/duration_matrix.json.go.golden | 6 ++++++ .../testdata/duration_matrix_time_dependent0.json.go.golden | 6 ++++++ .../testdata/duration_matrix_time_dependent1.json.go.golden | 6 ++++++ .../testdata/duration_matrix_time_dependent2.json.go.golden | 6 ++++++ .../testdata/duration_matrix_time_dependent3.json.go.golden | 6 ++++++ tests/golden/testdata/early_arrival_penalty.json.go.golden | 6 ++++++ tests/golden/testdata/initial_stops.json.go.golden | 6 ++++++ .../initial_stops_infeasible_compatibility.json.go.golden | 6 ++++++ .../initial_stops_infeasible_max_duration.json.go.golden | 6 ++++++ .../initial_stops_infeasible_remove_all.json.go.golden | 6 ++++++ .../initial_stops_infeasible_temporal.json.go.golden | 6 ++++++ .../testdata/initial_stops_infeasible_tuple.json.go.golden | 6 ++++++ tests/golden/testdata/late_arrival_penalty.json.go.golden | 6 ++++++ tests/golden/testdata/max_distance.json.go.golden | 6 ++++++ tests/golden/testdata/max_duration.json.go.golden | 6 ++++++ tests/golden/testdata/max_stops.json.go.golden | 6 ++++++ tests/golden/testdata/max_wait_stop.json.go.golden | 6 ++++++ tests/golden/testdata/max_wait_vehicle.json.go.golden | 6 ++++++ tests/golden/testdata/min_stops.json.go.golden | 6 ++++++ tests/golden/testdata/multi_window.json.go.golden | 6 ++++++ tests/golden/testdata/no_mix.json.go.golden | 6 ++++++ tests/golden/testdata/no_mix_null.json.go.golden | 6 ++++++ tests/golden/testdata/precedence.json.go.golden | 6 ++++++ tests/golden/testdata/precedence_pathologic.json.go.golden | 6 ++++++ tests/golden/testdata/start_level.json.go.golden | 6 ++++++ tests/golden/testdata/start_time_window.json.go.golden | 6 ++++++ tests/golden/testdata/stop_duration.json.go.golden | 6 ++++++ .../golden/testdata/stop_duration_multiplier.json.go.golden | 6 ++++++ tests/golden/testdata/stop_groups.json.go.golden | 6 ++++++ tests/golden/testdata/template_input.json.go.golden | 6 ++++++ tests/golden/testdata/unplanned_penalty.json.go.golden | 6 ++++++ .../testdata/vehicle_start_end_location.json.go.golden | 6 ++++++ tests/golden/testdata/vehicle_start_end_time.json.go.golden | 6 ++++++ .../testdata/vehicles_duration_objective.json.go.golden | 6 ++++++ tests/inline_options/input.json.golden | 6 ++++++ tests/output_options/main.sh.golden | 6 ++++++ tests/stop_balancing_objective/input.json.golden | 6 ++++++ 55 files changed, 330 insertions(+) diff --git a/tests/check/input.json.golden b/tests/check/input.json.golden index b6ad083..f0cf9f8 100644 --- a/tests/check/input.json.golden +++ b/tests/check/input.json.golden @@ -67,6 +67,12 @@ "duration": 10000000000, "iterations": 0, "parallel_runs": 1, + "plateau": { + "absolute_threshold": -1, + "duration": 0, + "iterations": 0, + "relative_threshold": 0 + }, "run_deterministically": true, "start_solutions": 0 } diff --git a/tests/custom_constraint/input.json.golden b/tests/custom_constraint/input.json.golden index 5e07ae4..56b036b 100644 --- a/tests/custom_constraint/input.json.golden +++ b/tests/custom_constraint/input.json.golden @@ -67,6 +67,12 @@ "duration": 10000000000, "iterations": 50, "parallel_runs": 1, + "plateau": { + "absolute_threshold": -1, + "duration": 0, + "iterations": 0, + "relative_threshold": 0 + }, "run_deterministically": true, "start_solutions": 1 } diff --git a/tests/custom_matrices/input.json.golden b/tests/custom_matrices/input.json.golden index a61b66f..f4c35bc 100644 --- a/tests/custom_matrices/input.json.golden +++ b/tests/custom_matrices/input.json.golden @@ -67,6 +67,12 @@ "duration": 10000000000, "iterations": 50, "parallel_runs": 1, + "plateau": { + "absolute_threshold": -1, + "duration": 0, + "iterations": 0, + "relative_threshold": 0 + }, "run_deterministically": true, "start_solutions": 1 } diff --git a/tests/custom_objective/input.json.golden b/tests/custom_objective/input.json.golden index 82733d0..a7a33ca 100644 --- a/tests/custom_objective/input.json.golden +++ b/tests/custom_objective/input.json.golden @@ -67,6 +67,12 @@ "duration": 10000000000, "iterations": 50, "parallel_runs": 1, + "plateau": { + "absolute_threshold": -1, + "duration": 0, + "iterations": 0, + "relative_threshold": 0 + }, "run_deterministically": true, "start_solutions": 1 } diff --git a/tests/custom_operators/input.json.golden b/tests/custom_operators/input.json.golden index 6235077..897cd8d 100644 --- a/tests/custom_operators/input.json.golden +++ b/tests/custom_operators/input.json.golden @@ -67,6 +67,12 @@ "duration": 10000000000, "iterations": 50, "parallel_runs": 1, + "plateau": { + "absolute_threshold": -1, + "duration": 0, + "iterations": 0, + "relative_threshold": 0 + }, "run_deterministically": true, "start_solutions": 1 } diff --git a/tests/golden/testdata/activation_penalty.json.go.golden b/tests/golden/testdata/activation_penalty.json.go.golden index 5a5e183..962a90e 100644 --- a/tests/golden/testdata/activation_penalty.json.go.golden +++ b/tests/golden/testdata/activation_penalty.json.go.golden @@ -67,6 +67,12 @@ "duration": 10000000000, "iterations": 50, "parallel_runs": 1, + "plateau": { + "absolute_threshold": -1, + "duration": 0, + "iterations": 0, + "relative_threshold": 0 + }, "run_deterministically": true, "start_solutions": 1 } diff --git a/tests/golden/testdata/alternates.json.go.golden b/tests/golden/testdata/alternates.json.go.golden index 6a7b1b6..a47d75d 100644 --- a/tests/golden/testdata/alternates.json.go.golden +++ b/tests/golden/testdata/alternates.json.go.golden @@ -67,6 +67,12 @@ "duration": 10000000000, "iterations": 50, "parallel_runs": 1, + "plateau": { + "absolute_threshold": -1, + "duration": 0, + "iterations": 0, + "relative_threshold": 0 + }, "run_deterministically": true, "start_solutions": 1 } diff --git a/tests/golden/testdata/basic.json.go.golden b/tests/golden/testdata/basic.json.go.golden index e38ddd1..c7b3f4d 100644 --- a/tests/golden/testdata/basic.json.go.golden +++ b/tests/golden/testdata/basic.json.go.golden @@ -67,6 +67,12 @@ "duration": 10000000000, "iterations": 50, "parallel_runs": 1, + "plateau": { + "absolute_threshold": -1, + "duration": 0, + "iterations": 0, + "relative_threshold": 0 + }, "run_deterministically": true, "start_solutions": 1 } diff --git a/tests/golden/testdata/capacity.json.go.golden b/tests/golden/testdata/capacity.json.go.golden index 62e9682..41a6daf 100644 --- a/tests/golden/testdata/capacity.json.go.golden +++ b/tests/golden/testdata/capacity.json.go.golden @@ -67,6 +67,12 @@ "duration": 10000000000, "iterations": 50, "parallel_runs": 1, + "plateau": { + "absolute_threshold": -1, + "duration": 0, + "iterations": 0, + "relative_threshold": 0 + }, "run_deterministically": true, "start_solutions": 1 } diff --git a/tests/golden/testdata/compatibility_attributes.json.go.golden b/tests/golden/testdata/compatibility_attributes.json.go.golden index 990dc8a..1754d5f 100644 --- a/tests/golden/testdata/compatibility_attributes.json.go.golden +++ b/tests/golden/testdata/compatibility_attributes.json.go.golden @@ -67,6 +67,12 @@ "duration": 10000000000, "iterations": 50, "parallel_runs": 1, + "plateau": { + "absolute_threshold": -1, + "duration": 0, + "iterations": 0, + "relative_threshold": 0 + }, "run_deterministically": true, "start_solutions": 1 } diff --git a/tests/golden/testdata/complex_precedence.json.go.golden b/tests/golden/testdata/complex_precedence.json.go.golden index a48d477..f487d33 100644 --- a/tests/golden/testdata/complex_precedence.json.go.golden +++ b/tests/golden/testdata/complex_precedence.json.go.golden @@ -67,6 +67,12 @@ "duration": 10000000000, "iterations": 50, "parallel_runs": 1, + "plateau": { + "absolute_threshold": -1, + "duration": 0, + "iterations": 0, + "relative_threshold": 0 + }, "run_deterministically": true, "start_solutions": 1 } diff --git a/tests/golden/testdata/custom_data.json.go.golden b/tests/golden/testdata/custom_data.json.go.golden index 2efe46d..a88dcd5 100644 --- a/tests/golden/testdata/custom_data.json.go.golden +++ b/tests/golden/testdata/custom_data.json.go.golden @@ -67,6 +67,12 @@ "duration": 10000000000, "iterations": 50, "parallel_runs": 1, + "plateau": { + "absolute_threshold": -1, + "duration": 0, + "iterations": 0, + "relative_threshold": 0 + }, "run_deterministically": true, "start_solutions": 1 } diff --git a/tests/golden/testdata/defaults.json.go.golden b/tests/golden/testdata/defaults.json.go.golden index 50968d8..d8fb792 100644 --- a/tests/golden/testdata/defaults.json.go.golden +++ b/tests/golden/testdata/defaults.json.go.golden @@ -67,6 +67,12 @@ "duration": 10000000000, "iterations": 50, "parallel_runs": 1, + "plateau": { + "absolute_threshold": -1, + "duration": 0, + "iterations": 0, + "relative_threshold": 0 + }, "run_deterministically": true, "start_solutions": 1 } diff --git a/tests/golden/testdata/direct_precedence.json.go.golden b/tests/golden/testdata/direct_precedence.json.go.golden index e4710d4..035f705 100644 --- a/tests/golden/testdata/direct_precedence.json.go.golden +++ b/tests/golden/testdata/direct_precedence.json.go.golden @@ -67,6 +67,12 @@ "duration": 10000000000, "iterations": 50, "parallel_runs": 1, + "plateau": { + "absolute_threshold": -1, + "duration": 0, + "iterations": 0, + "relative_threshold": 0 + }, "run_deterministically": true, "start_solutions": 1 } diff --git a/tests/golden/testdata/direct_precedence_linked.json.go.golden b/tests/golden/testdata/direct_precedence_linked.json.go.golden index 3747766..01c5ec9 100644 --- a/tests/golden/testdata/direct_precedence_linked.json.go.golden +++ b/tests/golden/testdata/direct_precedence_linked.json.go.golden @@ -67,6 +67,12 @@ "duration": 10000000000, "iterations": 50, "parallel_runs": 1, + "plateau": { + "absolute_threshold": -1, + "duration": 0, + "iterations": 0, + "relative_threshold": 0 + }, "run_deterministically": true, "start_solutions": 1 } diff --git a/tests/golden/testdata/distance_matrix.json.go.golden b/tests/golden/testdata/distance_matrix.json.go.golden index 02f975b..a23438a 100644 --- a/tests/golden/testdata/distance_matrix.json.go.golden +++ b/tests/golden/testdata/distance_matrix.json.go.golden @@ -67,6 +67,12 @@ "duration": 10000000000, "iterations": 50, "parallel_runs": 1, + "plateau": { + "absolute_threshold": -1, + "duration": 0, + "iterations": 0, + "relative_threshold": 0 + }, "run_deterministically": true, "start_solutions": 1 } diff --git a/tests/golden/testdata/duration_groups.json.go.golden b/tests/golden/testdata/duration_groups.json.go.golden index 269a52d..62cff58 100644 --- a/tests/golden/testdata/duration_groups.json.go.golden +++ b/tests/golden/testdata/duration_groups.json.go.golden @@ -67,6 +67,12 @@ "duration": 10000000000, "iterations": 50, "parallel_runs": 1, + "plateau": { + "absolute_threshold": -1, + "duration": 0, + "iterations": 0, + "relative_threshold": 0 + }, "run_deterministically": true, "start_solutions": 1 } diff --git a/tests/golden/testdata/duration_groups_with_stop_multiplier.json.go.golden b/tests/golden/testdata/duration_groups_with_stop_multiplier.json.go.golden index f952ea9..7329f9a 100644 --- a/tests/golden/testdata/duration_groups_with_stop_multiplier.json.go.golden +++ b/tests/golden/testdata/duration_groups_with_stop_multiplier.json.go.golden @@ -67,6 +67,12 @@ "duration": 10000000000, "iterations": 50, "parallel_runs": 1, + "plateau": { + "absolute_threshold": -1, + "duration": 0, + "iterations": 0, + "relative_threshold": 0 + }, "run_deterministically": true, "start_solutions": 1 } diff --git a/tests/golden/testdata/duration_matrix.json.go.golden b/tests/golden/testdata/duration_matrix.json.go.golden index 2f66fac..758ebf7 100644 --- a/tests/golden/testdata/duration_matrix.json.go.golden +++ b/tests/golden/testdata/duration_matrix.json.go.golden @@ -67,6 +67,12 @@ "duration": 10000000000, "iterations": 50, "parallel_runs": 1, + "plateau": { + "absolute_threshold": -1, + "duration": 0, + "iterations": 0, + "relative_threshold": 0 + }, "run_deterministically": true, "start_solutions": 1 } diff --git a/tests/golden/testdata/duration_matrix_time_dependent0.json.go.golden b/tests/golden/testdata/duration_matrix_time_dependent0.json.go.golden index c3a5c60..00345f4 100644 --- a/tests/golden/testdata/duration_matrix_time_dependent0.json.go.golden +++ b/tests/golden/testdata/duration_matrix_time_dependent0.json.go.golden @@ -67,6 +67,12 @@ "duration": 10000000000, "iterations": 50, "parallel_runs": 1, + "plateau": { + "absolute_threshold": -1, + "duration": 0, + "iterations": 0, + "relative_threshold": 0 + }, "run_deterministically": true, "start_solutions": 1 } diff --git a/tests/golden/testdata/duration_matrix_time_dependent1.json.go.golden b/tests/golden/testdata/duration_matrix_time_dependent1.json.go.golden index 4539daf..3cc5cfb 100644 --- a/tests/golden/testdata/duration_matrix_time_dependent1.json.go.golden +++ b/tests/golden/testdata/duration_matrix_time_dependent1.json.go.golden @@ -67,6 +67,12 @@ "duration": 10000000000, "iterations": 50, "parallel_runs": 1, + "plateau": { + "absolute_threshold": -1, + "duration": 0, + "iterations": 0, + "relative_threshold": 0 + }, "run_deterministically": true, "start_solutions": 1 } diff --git a/tests/golden/testdata/duration_matrix_time_dependent2.json.go.golden b/tests/golden/testdata/duration_matrix_time_dependent2.json.go.golden index d6ecc4c..b42b730 100644 --- a/tests/golden/testdata/duration_matrix_time_dependent2.json.go.golden +++ b/tests/golden/testdata/duration_matrix_time_dependent2.json.go.golden @@ -67,6 +67,12 @@ "duration": 10000000000, "iterations": 50, "parallel_runs": 1, + "plateau": { + "absolute_threshold": -1, + "duration": 0, + "iterations": 0, + "relative_threshold": 0 + }, "run_deterministically": true, "start_solutions": 1 } diff --git a/tests/golden/testdata/duration_matrix_time_dependent3.json.go.golden b/tests/golden/testdata/duration_matrix_time_dependent3.json.go.golden index 18ef55e..7bd7c9d 100644 --- a/tests/golden/testdata/duration_matrix_time_dependent3.json.go.golden +++ b/tests/golden/testdata/duration_matrix_time_dependent3.json.go.golden @@ -67,6 +67,12 @@ "duration": 10000000000, "iterations": 50, "parallel_runs": 1, + "plateau": { + "absolute_threshold": -1, + "duration": 0, + "iterations": 0, + "relative_threshold": 0 + }, "run_deterministically": true, "start_solutions": 1 } diff --git a/tests/golden/testdata/early_arrival_penalty.json.go.golden b/tests/golden/testdata/early_arrival_penalty.json.go.golden index 3a3ab05..bcbdff3 100644 --- a/tests/golden/testdata/early_arrival_penalty.json.go.golden +++ b/tests/golden/testdata/early_arrival_penalty.json.go.golden @@ -67,6 +67,12 @@ "duration": 10000000000, "iterations": 50, "parallel_runs": 1, + "plateau": { + "absolute_threshold": -1, + "duration": 0, + "iterations": 0, + "relative_threshold": 0 + }, "run_deterministically": true, "start_solutions": 1 } diff --git a/tests/golden/testdata/initial_stops.json.go.golden b/tests/golden/testdata/initial_stops.json.go.golden index b044b08..cae7539 100644 --- a/tests/golden/testdata/initial_stops.json.go.golden +++ b/tests/golden/testdata/initial_stops.json.go.golden @@ -67,6 +67,12 @@ "duration": 10000000000, "iterations": 50, "parallel_runs": 1, + "plateau": { + "absolute_threshold": -1, + "duration": 0, + "iterations": 0, + "relative_threshold": 0 + }, "run_deterministically": true, "start_solutions": 1 } diff --git a/tests/golden/testdata/initial_stops_infeasible_compatibility.json.go.golden b/tests/golden/testdata/initial_stops_infeasible_compatibility.json.go.golden index c460be4..30112cd 100644 --- a/tests/golden/testdata/initial_stops_infeasible_compatibility.json.go.golden +++ b/tests/golden/testdata/initial_stops_infeasible_compatibility.json.go.golden @@ -67,6 +67,12 @@ "duration": 10000000000, "iterations": 50, "parallel_runs": 1, + "plateau": { + "absolute_threshold": -1, + "duration": 0, + "iterations": 0, + "relative_threshold": 0 + }, "run_deterministically": true, "start_solutions": 1 } diff --git a/tests/golden/testdata/initial_stops_infeasible_max_duration.json.go.golden b/tests/golden/testdata/initial_stops_infeasible_max_duration.json.go.golden index d3f0d83..f1ae1fb 100644 --- a/tests/golden/testdata/initial_stops_infeasible_max_duration.json.go.golden +++ b/tests/golden/testdata/initial_stops_infeasible_max_duration.json.go.golden @@ -67,6 +67,12 @@ "duration": 10000000000, "iterations": 50, "parallel_runs": 1, + "plateau": { + "absolute_threshold": -1, + "duration": 0, + "iterations": 0, + "relative_threshold": 0 + }, "run_deterministically": true, "start_solutions": 1 } diff --git a/tests/golden/testdata/initial_stops_infeasible_remove_all.json.go.golden b/tests/golden/testdata/initial_stops_infeasible_remove_all.json.go.golden index aa6747b..8539f00 100644 --- a/tests/golden/testdata/initial_stops_infeasible_remove_all.json.go.golden +++ b/tests/golden/testdata/initial_stops_infeasible_remove_all.json.go.golden @@ -67,6 +67,12 @@ "duration": 10000000000, "iterations": 50, "parallel_runs": 1, + "plateau": { + "absolute_threshold": -1, + "duration": 0, + "iterations": 0, + "relative_threshold": 0 + }, "run_deterministically": true, "start_solutions": 1 } diff --git a/tests/golden/testdata/initial_stops_infeasible_temporal.json.go.golden b/tests/golden/testdata/initial_stops_infeasible_temporal.json.go.golden index 071d85c..bae70ba 100644 --- a/tests/golden/testdata/initial_stops_infeasible_temporal.json.go.golden +++ b/tests/golden/testdata/initial_stops_infeasible_temporal.json.go.golden @@ -67,6 +67,12 @@ "duration": 10000000000, "iterations": 50, "parallel_runs": 1, + "plateau": { + "absolute_threshold": -1, + "duration": 0, + "iterations": 0, + "relative_threshold": 0 + }, "run_deterministically": true, "start_solutions": 1 } diff --git a/tests/golden/testdata/initial_stops_infeasible_tuple.json.go.golden b/tests/golden/testdata/initial_stops_infeasible_tuple.json.go.golden index fe2bf7b..56ce5a8 100644 --- a/tests/golden/testdata/initial_stops_infeasible_tuple.json.go.golden +++ b/tests/golden/testdata/initial_stops_infeasible_tuple.json.go.golden @@ -67,6 +67,12 @@ "duration": 10000000000, "iterations": 50, "parallel_runs": 1, + "plateau": { + "absolute_threshold": -1, + "duration": 0, + "iterations": 0, + "relative_threshold": 0 + }, "run_deterministically": true, "start_solutions": 1 } diff --git a/tests/golden/testdata/late_arrival_penalty.json.go.golden b/tests/golden/testdata/late_arrival_penalty.json.go.golden index af4e58b..0368ce6 100644 --- a/tests/golden/testdata/late_arrival_penalty.json.go.golden +++ b/tests/golden/testdata/late_arrival_penalty.json.go.golden @@ -67,6 +67,12 @@ "duration": 10000000000, "iterations": 50, "parallel_runs": 1, + "plateau": { + "absolute_threshold": -1, + "duration": 0, + "iterations": 0, + "relative_threshold": 0 + }, "run_deterministically": true, "start_solutions": 1 } diff --git a/tests/golden/testdata/max_distance.json.go.golden b/tests/golden/testdata/max_distance.json.go.golden index beba902..83552ae 100644 --- a/tests/golden/testdata/max_distance.json.go.golden +++ b/tests/golden/testdata/max_distance.json.go.golden @@ -67,6 +67,12 @@ "duration": 10000000000, "iterations": 50, "parallel_runs": 1, + "plateau": { + "absolute_threshold": -1, + "duration": 0, + "iterations": 0, + "relative_threshold": 0 + }, "run_deterministically": true, "start_solutions": 1 } diff --git a/tests/golden/testdata/max_duration.json.go.golden b/tests/golden/testdata/max_duration.json.go.golden index 558c54b..70596ae 100644 --- a/tests/golden/testdata/max_duration.json.go.golden +++ b/tests/golden/testdata/max_duration.json.go.golden @@ -67,6 +67,12 @@ "duration": 10000000000, "iterations": 50, "parallel_runs": 1, + "plateau": { + "absolute_threshold": -1, + "duration": 0, + "iterations": 0, + "relative_threshold": 0 + }, "run_deterministically": true, "start_solutions": 1 } diff --git a/tests/golden/testdata/max_stops.json.go.golden b/tests/golden/testdata/max_stops.json.go.golden index 33f4a3e..aa9c9a6 100644 --- a/tests/golden/testdata/max_stops.json.go.golden +++ b/tests/golden/testdata/max_stops.json.go.golden @@ -67,6 +67,12 @@ "duration": 10000000000, "iterations": 50, "parallel_runs": 1, + "plateau": { + "absolute_threshold": -1, + "duration": 0, + "iterations": 0, + "relative_threshold": 0 + }, "run_deterministically": true, "start_solutions": 1 } diff --git a/tests/golden/testdata/max_wait_stop.json.go.golden b/tests/golden/testdata/max_wait_stop.json.go.golden index 42159b3..0d5253f 100644 --- a/tests/golden/testdata/max_wait_stop.json.go.golden +++ b/tests/golden/testdata/max_wait_stop.json.go.golden @@ -67,6 +67,12 @@ "duration": 10000000000, "iterations": 50, "parallel_runs": 1, + "plateau": { + "absolute_threshold": -1, + "duration": 0, + "iterations": 0, + "relative_threshold": 0 + }, "run_deterministically": true, "start_solutions": 1 } diff --git a/tests/golden/testdata/max_wait_vehicle.json.go.golden b/tests/golden/testdata/max_wait_vehicle.json.go.golden index ce8a928..3c63a35 100644 --- a/tests/golden/testdata/max_wait_vehicle.json.go.golden +++ b/tests/golden/testdata/max_wait_vehicle.json.go.golden @@ -67,6 +67,12 @@ "duration": 10000000000, "iterations": 50, "parallel_runs": 1, + "plateau": { + "absolute_threshold": -1, + "duration": 0, + "iterations": 0, + "relative_threshold": 0 + }, "run_deterministically": true, "start_solutions": 1 } diff --git a/tests/golden/testdata/min_stops.json.go.golden b/tests/golden/testdata/min_stops.json.go.golden index 8af7f37..531ed61 100644 --- a/tests/golden/testdata/min_stops.json.go.golden +++ b/tests/golden/testdata/min_stops.json.go.golden @@ -67,6 +67,12 @@ "duration": 10000000000, "iterations": 50, "parallel_runs": 1, + "plateau": { + "absolute_threshold": -1, + "duration": 0, + "iterations": 0, + "relative_threshold": 0 + }, "run_deterministically": true, "start_solutions": 1 } diff --git a/tests/golden/testdata/multi_window.json.go.golden b/tests/golden/testdata/multi_window.json.go.golden index 0076b29..e10990f 100644 --- a/tests/golden/testdata/multi_window.json.go.golden +++ b/tests/golden/testdata/multi_window.json.go.golden @@ -67,6 +67,12 @@ "duration": 10000000000, "iterations": 50, "parallel_runs": 1, + "plateau": { + "absolute_threshold": -1, + "duration": 0, + "iterations": 0, + "relative_threshold": 0 + }, "run_deterministically": true, "start_solutions": 1 } diff --git a/tests/golden/testdata/no_mix.json.go.golden b/tests/golden/testdata/no_mix.json.go.golden index 8d8d344..aa2dba6 100644 --- a/tests/golden/testdata/no_mix.json.go.golden +++ b/tests/golden/testdata/no_mix.json.go.golden @@ -67,6 +67,12 @@ "duration": 10000000000, "iterations": 50, "parallel_runs": 1, + "plateau": { + "absolute_threshold": -1, + "duration": 0, + "iterations": 0, + "relative_threshold": 0 + }, "run_deterministically": true, "start_solutions": 1 } diff --git a/tests/golden/testdata/no_mix_null.json.go.golden b/tests/golden/testdata/no_mix_null.json.go.golden index d185935..65672df 100644 --- a/tests/golden/testdata/no_mix_null.json.go.golden +++ b/tests/golden/testdata/no_mix_null.json.go.golden @@ -67,6 +67,12 @@ "duration": 10000000000, "iterations": 50, "parallel_runs": 1, + "plateau": { + "absolute_threshold": -1, + "duration": 0, + "iterations": 0, + "relative_threshold": 0 + }, "run_deterministically": true, "start_solutions": 1 } diff --git a/tests/golden/testdata/precedence.json.go.golden b/tests/golden/testdata/precedence.json.go.golden index 19a69a5..f8862b9 100644 --- a/tests/golden/testdata/precedence.json.go.golden +++ b/tests/golden/testdata/precedence.json.go.golden @@ -67,6 +67,12 @@ "duration": 10000000000, "iterations": 50, "parallel_runs": 1, + "plateau": { + "absolute_threshold": -1, + "duration": 0, + "iterations": 0, + "relative_threshold": 0 + }, "run_deterministically": true, "start_solutions": 1 } diff --git a/tests/golden/testdata/precedence_pathologic.json.go.golden b/tests/golden/testdata/precedence_pathologic.json.go.golden index c0c8ccb..667ac35 100644 --- a/tests/golden/testdata/precedence_pathologic.json.go.golden +++ b/tests/golden/testdata/precedence_pathologic.json.go.golden @@ -67,6 +67,12 @@ "duration": 10000000000, "iterations": 50, "parallel_runs": 1, + "plateau": { + "absolute_threshold": -1, + "duration": 0, + "iterations": 0, + "relative_threshold": 0 + }, "run_deterministically": true, "start_solutions": 1 } diff --git a/tests/golden/testdata/start_level.json.go.golden b/tests/golden/testdata/start_level.json.go.golden index 69f25a1..c41c9aa 100644 --- a/tests/golden/testdata/start_level.json.go.golden +++ b/tests/golden/testdata/start_level.json.go.golden @@ -67,6 +67,12 @@ "duration": 10000000000, "iterations": 50, "parallel_runs": 1, + "plateau": { + "absolute_threshold": -1, + "duration": 0, + "iterations": 0, + "relative_threshold": 0 + }, "run_deterministically": true, "start_solutions": 1 } diff --git a/tests/golden/testdata/start_time_window.json.go.golden b/tests/golden/testdata/start_time_window.json.go.golden index 7a7eeeb..5e7d8d9 100644 --- a/tests/golden/testdata/start_time_window.json.go.golden +++ b/tests/golden/testdata/start_time_window.json.go.golden @@ -67,6 +67,12 @@ "duration": 10000000000, "iterations": 50, "parallel_runs": 1, + "plateau": { + "absolute_threshold": -1, + "duration": 0, + "iterations": 0, + "relative_threshold": 0 + }, "run_deterministically": true, "start_solutions": 1 } diff --git a/tests/golden/testdata/stop_duration.json.go.golden b/tests/golden/testdata/stop_duration.json.go.golden index 7325b8d..7ec14f1 100644 --- a/tests/golden/testdata/stop_duration.json.go.golden +++ b/tests/golden/testdata/stop_duration.json.go.golden @@ -67,6 +67,12 @@ "duration": 10000000000, "iterations": 50, "parallel_runs": 1, + "plateau": { + "absolute_threshold": -1, + "duration": 0, + "iterations": 0, + "relative_threshold": 0 + }, "run_deterministically": true, "start_solutions": 1 } diff --git a/tests/golden/testdata/stop_duration_multiplier.json.go.golden b/tests/golden/testdata/stop_duration_multiplier.json.go.golden index 69cc789..6da233e 100644 --- a/tests/golden/testdata/stop_duration_multiplier.json.go.golden +++ b/tests/golden/testdata/stop_duration_multiplier.json.go.golden @@ -67,6 +67,12 @@ "duration": 10000000000, "iterations": 50, "parallel_runs": 1, + "plateau": { + "absolute_threshold": -1, + "duration": 0, + "iterations": 0, + "relative_threshold": 0 + }, "run_deterministically": true, "start_solutions": 1 } diff --git a/tests/golden/testdata/stop_groups.json.go.golden b/tests/golden/testdata/stop_groups.json.go.golden index d46f51f..8c9f87e 100644 --- a/tests/golden/testdata/stop_groups.json.go.golden +++ b/tests/golden/testdata/stop_groups.json.go.golden @@ -67,6 +67,12 @@ "duration": 10000000000, "iterations": 50, "parallel_runs": 1, + "plateau": { + "absolute_threshold": -1, + "duration": 0, + "iterations": 0, + "relative_threshold": 0 + }, "run_deterministically": true, "start_solutions": 1 } diff --git a/tests/golden/testdata/template_input.json.go.golden b/tests/golden/testdata/template_input.json.go.golden index 64558a9..d39d9d2 100644 --- a/tests/golden/testdata/template_input.json.go.golden +++ b/tests/golden/testdata/template_input.json.go.golden @@ -67,6 +67,12 @@ "duration": 10000000000, "iterations": 50, "parallel_runs": 1, + "plateau": { + "absolute_threshold": -1, + "duration": 0, + "iterations": 0, + "relative_threshold": 0 + }, "run_deterministically": true, "start_solutions": 1 } diff --git a/tests/golden/testdata/unplanned_penalty.json.go.golden b/tests/golden/testdata/unplanned_penalty.json.go.golden index 3e92d01..fce620d 100644 --- a/tests/golden/testdata/unplanned_penalty.json.go.golden +++ b/tests/golden/testdata/unplanned_penalty.json.go.golden @@ -67,6 +67,12 @@ "duration": 10000000000, "iterations": 50, "parallel_runs": 1, + "plateau": { + "absolute_threshold": -1, + "duration": 0, + "iterations": 0, + "relative_threshold": 0 + }, "run_deterministically": true, "start_solutions": 1 } diff --git a/tests/golden/testdata/vehicle_start_end_location.json.go.golden b/tests/golden/testdata/vehicle_start_end_location.json.go.golden index 2c3b323..de34df1 100644 --- a/tests/golden/testdata/vehicle_start_end_location.json.go.golden +++ b/tests/golden/testdata/vehicle_start_end_location.json.go.golden @@ -67,6 +67,12 @@ "duration": 10000000000, "iterations": 50, "parallel_runs": 1, + "plateau": { + "absolute_threshold": -1, + "duration": 0, + "iterations": 0, + "relative_threshold": 0 + }, "run_deterministically": true, "start_solutions": 1 } diff --git a/tests/golden/testdata/vehicle_start_end_time.json.go.golden b/tests/golden/testdata/vehicle_start_end_time.json.go.golden index 7c57aa6..c528025 100644 --- a/tests/golden/testdata/vehicle_start_end_time.json.go.golden +++ b/tests/golden/testdata/vehicle_start_end_time.json.go.golden @@ -67,6 +67,12 @@ "duration": 10000000000, "iterations": 50, "parallel_runs": 1, + "plateau": { + "absolute_threshold": -1, + "duration": 0, + "iterations": 0, + "relative_threshold": 0 + }, "run_deterministically": true, "start_solutions": 1 } diff --git a/tests/golden/testdata/vehicles_duration_objective.json.go.golden b/tests/golden/testdata/vehicles_duration_objective.json.go.golden index 330a2cc..9892b7a 100644 --- a/tests/golden/testdata/vehicles_duration_objective.json.go.golden +++ b/tests/golden/testdata/vehicles_duration_objective.json.go.golden @@ -67,6 +67,12 @@ "duration": 10000000000, "iterations": 50, "parallel_runs": 1, + "plateau": { + "absolute_threshold": -1, + "duration": 0, + "iterations": 0, + "relative_threshold": 0 + }, "run_deterministically": true, "start_solutions": 1 } diff --git a/tests/inline_options/input.json.golden b/tests/inline_options/input.json.golden index bf14cf0..3f2d3ab 100644 --- a/tests/inline_options/input.json.golden +++ b/tests/inline_options/input.json.golden @@ -67,6 +67,12 @@ "duration": 11000000000, "iterations": 51, "parallel_runs": 1, + "plateau": { + "absolute_threshold": -1, + "duration": 0, + "iterations": 0, + "relative_threshold": 0 + }, "run_deterministically": true, "start_solutions": 1 } diff --git a/tests/output_options/main.sh.golden b/tests/output_options/main.sh.golden index 851f8ed..0953c32 100644 --- a/tests/output_options/main.sh.golden +++ b/tests/output_options/main.sh.golden @@ -56,6 +56,12 @@ "solve": { "iterations": 50, "duration": 10000000000, + "plateau": { + "duration": 0, + "iterations": 0, + "relative_threshold": 0, + "absolute_threshold": -1 + }, "parallel_runs": 1, "start_solutions": 1, "run_deterministically": true diff --git a/tests/stop_balancing_objective/input.json.golden b/tests/stop_balancing_objective/input.json.golden index a0e76b5..6d8219d 100644 --- a/tests/stop_balancing_objective/input.json.golden +++ b/tests/stop_balancing_objective/input.json.golden @@ -67,6 +67,12 @@ "duration": 10000000000, "iterations": 10000, "parallel_runs": 1, + "plateau": { + "absolute_threshold": -1, + "duration": 0, + "iterations": 0, + "relative_threshold": 0 + }, "run_deterministically": true, "start_solutions": 1 } From ba8702d9484153f9ae777141c56cbef04511aa4b Mon Sep 17 00:00:00 2001 From: Marius Merschformann Date: Fri, 20 Dec 2024 03:09:01 +0100 Subject: [PATCH 4/7] Added missing python nextroute options, improved test to automatically sync options --- src/nextroute/options.py | 12 ++++ src/tests/test_options.py | 136 +++++++++++++++++++++++++------------- 2 files changed, 103 insertions(+), 45 deletions(-) diff --git a/src/nextroute/options.py b/src/nextroute/options.py index b565416..ca3a5c7 100644 --- a/src/nextroute/options.py +++ b/src/nextroute/options.py @@ -89,6 +89,8 @@ class Options(BaseModel): """Factor to weigh the late arrival objective.""" MODEL_OBJECTIVES_MINSTOPS: float = 1.0 """Factor to weigh the min stops objective.""" + MODEL_OBJECTIVES_STOPBALANCE: float = 0.0 + """Factor to weigh the stop balance objective.""" MODEL_OBJECTIVES_TRAVELDURATION: float = 0.0 """Factor to weigh the travel duration objective.""" MODEL_OBJECTIVES_UNPLANNEDPENALTY: float = 1.0 @@ -105,6 +107,8 @@ class Options(BaseModel): """Ignore the initial solution.""" MODEL_PROPERTIES_DISABLE_STOPDURATIONMULTIPLIERS: bool = False """Ignore the stop duration multipliers defined on vehicles.""" + MODEL_PROPERTIES_MAXIMUMTIMEHORIZON: int = 15552000 + """Maximum time horizon for the model in seconds.""" MODEL_VALIDATE_DISABLE_RESOURCES: bool = False """Disable the resources validation.""" MODEL_VALIDATE_DISABLE_STARTTIME: bool = False @@ -125,6 +129,14 @@ class Options(BaseModel): Maximum number of parallel runs, -1 results in using all available resources. """ + SOLVE_PLATEAU_ABSOLUTETHRESHOLD: float = -1 + """Absolute threshold for significant improvement.""" + SOLVE_PLATEAU_DURATION: float = 0 + """Maximum duration without (significant) improvement.""" + SOLVE_PLATEAU_ITERATIONS: int = 0 + """Maximum number of iterations without (significant) improvement.""" + SOLVE_PLATEAU_RELATIVETHRESHOLD: float = 0 + """Relative threshold for significant improvement.""" SOLVE_RUNDETERMINISTICALLY: bool = False """Run the parallel solver deterministically.""" SOLVE_STARTSOLUTIONS: int = -1 diff --git a/src/tests/test_options.py b/src/tests/test_options.py index 2d95cd2..19044c7 100644 --- a/src/tests/test_options.py +++ b/src/tests/test_options.py @@ -1,57 +1,103 @@ +import os +import re +import subprocess import unittest +from typing import Any, List, Tuple import nextroute +NEXTROUTE_CMD = os.path.join(os.path.dirname(__file__), "..", "..", "cmd") + + +def _compile_nextroute(): + """Compiles the nextroute binary.""" + subprocess.run(["go", "build", "-o", "nextroute.exe"], cwd=NEXTROUTE_CMD, check=True) + + +def _extract_option(line_1: str, line_2: str) -> Tuple[str, type, Any, str]: + """Extracts an option from the help lines of the nextroute binary.""" + components = line_1.split(" ") + name = components[0].replace(".", "_").upper()[1:] + t = bool + default = False + default_match = re.compile(r"\(default (.+?)\)").search(line_2) + default_extracted = None + if default_match: + default_extracted = default_match.group(1) + if len(components) > 1: + if components[1] == "duration": + t = float + default = float(default_extracted[:-1]) if default_extracted else 0.0 + elif components[1] == "string": + t = str + default = default_extracted[1:-1] if default_extracted else "" + elif components[1] == "int": + t = int + default = int(default_extracted) if default_extracted else 0 + elif components[1] == "float": + t = float + default = float(default_extracted) if default_extracted else 0.0 + elif components[1] == "value": + t = Any + default = None # No default extraction for value as this can be anything. + else: + raise ValueError(f"Unsupported type: {components[1]}") + + descr_end = line_2.find("(env") - 1 + if descr_end < 0: + raise ValueError(f"Could not find the end of the description in line: {line_2}") + descr = line_2[:descr_end].strip() + + return name, t, default, descr + + +def _extract_options() -> List[Tuple[str, type, Any, str]]: + """Extracts the options from the nextroute binary.""" + _compile_nextroute() + executable = os.path.join(NEXTROUTE_CMD, "nextroute.exe") + output = subprocess.check_output([executable, "--help"], stderr=subprocess.STDOUT, text=True) + + options = [] + lines = output.splitlines() + for line_idx, line in enumerate(lines): + if not line.startswith(" -"): + continue + + line = line.strip() + + if len(lines) <= line_idx + 1: + raise ValueError(f'Could not find the description for option: {line.split(" ")[0]}') + next_line = lines[line_idx + 1] + + options.append(_extract_option(line, next_line)) + return options + class TestOptions(unittest.TestCase): def test_options_default_values(self): + IGNORED_OPTIONS = { + "RUNNER_INPUT_PATH", + "RUNNER_OUTPUT_PATH", + "RUNNER_OUTPUT_SOLUTIONS", + "RUNNER_PROFILE_CPU", + "RUNNER_PROFILE_MEMORY", + } opt = nextroute.Options() options_dict = opt.to_dict() - self.assertDictEqual( - options_dict, - { - "CHECK_DURATION": 30.0, - "CHECK_VERBOSITY": "off", - "FORMAT_DISABLE_PROGRESSION": False, - "MODEL_CONSTRAINTS_DISABLE_ATTRIBUTES": False, - "MODEL_CONSTRAINTS_DISABLE_CAPACITIES": [], - "MODEL_CONSTRAINTS_DISABLE_CAPACITY": False, - "MODEL_CONSTRAINTS_DISABLE_DISTANCELIMIT": False, - "MODEL_CONSTRAINTS_DISABLE_GROUPS": False, - "MODEL_CONSTRAINTS_DISABLE_MAXIMUMDURATION": False, - "MODEL_CONSTRAINTS_DISABLE_MAXIMUMSTOPS": False, - "MODEL_CONSTRAINTS_DISABLE_MAXIMUMWAITSTOP": False, - "MODEL_CONSTRAINTS_DISABLE_MAXIMUMWAITVEHICLE": False, - "MODEL_CONSTRAINTS_DISABLE_MIXINGITEMS": False, - "MODEL_CONSTRAINTS_DISABLE_PRECEDENCE": False, - "MODEL_CONSTRAINTS_DISABLE_STARTTIMEWINDOWS": False, - "MODEL_CONSTRAINTS_DISABLE_VEHICLEENDTIME": False, - "MODEL_CONSTRAINTS_DISABLE_VEHICLESTARTTIME": False, - "MODEL_CONSTRAINTS_ENABLE_CLUSTER": False, - "MODEL_OBJECTIVES_CAPACITIES": "", - "MODEL_OBJECTIVES_CLUSTER": 0.0, - "MODEL_OBJECTIVES_EARLYARRIVALPENALTY": 1.0, - "MODEL_OBJECTIVES_LATEARRIVALPENALTY": 1.0, - "MODEL_OBJECTIVES_MINSTOPS": 1.0, - "MODEL_OBJECTIVES_TRAVELDURATION": 0.0, - "MODEL_OBJECTIVES_UNPLANNEDPENALTY": 1.0, - "MODEL_OBJECTIVES_VEHICLEACTIVATIONPENALTY": 1.0, - "MODEL_OBJECTIVES_VEHICLESDURATION": 1.0, - "MODEL_PROPERTIES_DISABLE_DURATIONGROUPS": False, - "MODEL_PROPERTIES_DISABLE_DURATIONS": False, - "MODEL_PROPERTIES_DISABLE_INITIALSOLUTION": False, - "MODEL_PROPERTIES_DISABLE_STOPDURATIONMULTIPLIERS": False, - "MODEL_VALIDATE_DISABLE_RESOURCES": False, - "MODEL_VALIDATE_DISABLE_STARTTIME": False, - "MODEL_VALIDATE_ENABLE_MATRIX": False, - "MODEL_VALIDATE_ENABLE_MATRIXASYMMETRYTOLERANCE": 20, - "SOLVE_DURATION": 5.0, - "SOLVE_ITERATIONS": -1, - "SOLVE_PARALLELRUNS": -1, - "SOLVE_RUNDETERMINISTICALLY": False, - "SOLVE_STARTSOLUTIONS": -1, - }, - ) + # Check that all (relevant) options are present and have the correct default + # values. We get the options from the nextroute binary help output to ensure + # that we are in sync with the Go implementation. + bin_options = _extract_options() + for name, t, default, _ in bin_options: + if name in IGNORED_OPTIONS: + continue + self.assertIn(name, options_dict, f"Option {name} is missing.") + if t is not Any: + self.assertEqual( + options_dict[name], + default, + f"Option {name} has the wrong default value. {options_dict[name]} != {default} (got != expected)", + ) def test_options_to_args(self): # Default options should not produce any arguments. From 2fdc7ba91c6cb0ecf0f0dac8a2d6a6b11061c983 Mon Sep 17 00:00:00 2001 From: Marius Merschformann Date: Fri, 20 Dec 2024 03:11:31 +0100 Subject: [PATCH 5/7] Change python test workflow order to meet new go dependency --- .github/workflows/python-test.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/python-test.yml b/.github/workflows/python-test.yml index ca7b3ae..7038309 100644 --- a/.github/workflows/python-test.yml +++ b/.github/workflows/python-test.yml @@ -26,10 +26,6 @@ jobs: python -m pip install --upgrade pip pip install -r requirements.txt - - name: Python unit tests - run: python -m unittest - working-directory: src - # There appears to be a bug around # `nextmv-io/sdk@v1.8.0/golden/file.go:75` specifically in Windows. When # attempting to remove a temp file, the following error is encountered: @@ -44,6 +40,10 @@ jobs: with: go-version: ${{ env.GO_VERSION }} + - name: Python unit tests + run: python -m unittest + working-directory: src + - name: golden file tests from Python package if: ${{ matrix.platform != 'windows-latest' }} run: go test $(go list ./... | grep github.com/nextmv-io/nextroute/src) From 6264b9bae01a4b57deb892319f74f358ff1818a4 Mon Sep 17 00:00:00 2001 From: Marius Merschformann Date: Fri, 20 Dec 2024 03:16:21 +0100 Subject: [PATCH 6/7] Updating test expectation --- tests/golden/testdata/activation_penalty.json.python.golden | 6 ++++++ tests/golden/testdata/alternates.json.python.golden | 6 ++++++ tests/golden/testdata/basic.json.python.golden | 6 ++++++ tests/golden/testdata/capacity.json.python.golden | 6 ++++++ .../testdata/compatibility_attributes.json.python.golden | 6 ++++++ tests/golden/testdata/complex_precedence.json.python.golden | 6 ++++++ tests/golden/testdata/custom_data.json.python.golden | 6 ++++++ tests/golden/testdata/defaults.json.python.golden | 6 ++++++ tests/golden/testdata/direct_precedence.json.python.golden | 6 ++++++ .../testdata/direct_precedence_linked.json.python.golden | 6 ++++++ tests/golden/testdata/distance_matrix.json.python.golden | 6 ++++++ tests/golden/testdata/duration_groups.json.python.golden | 6 ++++++ .../duration_groups_with_stop_multiplier.json.python.golden | 6 ++++++ tests/golden/testdata/duration_matrix.json.python.golden | 6 ++++++ .../duration_matrix_time_dependent0.json.python.golden | 6 ++++++ .../duration_matrix_time_dependent1.json.python.golden | 6 ++++++ .../duration_matrix_time_dependent2.json.python.golden | 6 ++++++ .../duration_matrix_time_dependent3.json.python.golden | 6 ++++++ .../testdata/early_arrival_penalty.json.python.golden | 6 ++++++ tests/golden/testdata/initial_stops.json.python.golden | 6 ++++++ ...nitial_stops_infeasible_compatibility.json.python.golden | 6 ++++++ ...initial_stops_infeasible_max_duration.json.python.golden | 6 ++++++ .../initial_stops_infeasible_remove_all.json.python.golden | 6 ++++++ .../initial_stops_infeasible_temporal.json.python.golden | 6 ++++++ .../initial_stops_infeasible_tuple.json.python.golden | 6 ++++++ .../golden/testdata/late_arrival_penalty.json.python.golden | 6 ++++++ tests/golden/testdata/max_distance.json.python.golden | 6 ++++++ tests/golden/testdata/max_duration.json.python.golden | 6 ++++++ tests/golden/testdata/max_stops.json.python.golden | 6 ++++++ tests/golden/testdata/max_wait_stop.json.python.golden | 6 ++++++ tests/golden/testdata/max_wait_vehicle.json.python.golden | 6 ++++++ tests/golden/testdata/min_stops.json.python.golden | 6 ++++++ tests/golden/testdata/multi_window.json.python.golden | 6 ++++++ tests/golden/testdata/no_mix.json.python.golden | 6 ++++++ tests/golden/testdata/no_mix_null.json.python.golden | 6 ++++++ tests/golden/testdata/precedence.json.python.golden | 6 ++++++ .../testdata/precedence_pathologic.json.python.golden | 6 ++++++ tests/golden/testdata/start_level.json.python.golden | 6 ++++++ tests/golden/testdata/start_time_window.json.python.golden | 6 ++++++ tests/golden/testdata/stop_duration.json.python.golden | 6 ++++++ .../testdata/stop_duration_multiplier.json.python.golden | 6 ++++++ tests/golden/testdata/stop_groups.json.python.golden | 6 ++++++ tests/golden/testdata/template_input.json.python.golden | 6 ++++++ tests/golden/testdata/unplanned_penalty.json.python.golden | 6 ++++++ .../testdata/vehicle_start_end_location.json.python.golden | 6 ++++++ .../testdata/vehicle_start_end_time.json.python.golden | 6 ++++++ .../testdata/vehicles_duration_objective.json.python.golden | 6 ++++++ 47 files changed, 282 insertions(+) diff --git a/tests/golden/testdata/activation_penalty.json.python.golden b/tests/golden/testdata/activation_penalty.json.python.golden index fbe8fab..8a9953f 100644 --- a/tests/golden/testdata/activation_penalty.json.python.golden +++ b/tests/golden/testdata/activation_penalty.json.python.golden @@ -24,6 +24,7 @@ "model_objectives_earlyarrivalpenalty": 1, "model_objectives_latearrivalpenalty": 1, "model_objectives_minstops": 1, + "model_objectives_stopbalance": 0, "model_objectives_travelduration": 0, "model_objectives_unplannedpenalty": 1, "model_objectives_vehicleactivationpenalty": 1, @@ -32,6 +33,7 @@ "model_properties_disable_durations": false, "model_properties_disable_initialsolution": false, "model_properties_disable_stopdurationmultipliers": false, + "model_properties_maximumtimehorizon": 15552000, "model_validate_disable_resources": false, "model_validate_disable_starttime": false, "model_validate_enable_matrix": false, @@ -40,6 +42,10 @@ "solve_duration": 10, "solve_iterations": 50, "solve_parallelruns": 1, + "solve_plateau_absolutethreshold": -1, + "solve_plateau_duration": 0, + "solve_plateau_iterations": 0, + "solve_plateau_relativethreshold": 0, "solve_rundeterministically": true, "solve_startsolutions": 1 }, diff --git a/tests/golden/testdata/alternates.json.python.golden b/tests/golden/testdata/alternates.json.python.golden index 48a6658..28fc177 100644 --- a/tests/golden/testdata/alternates.json.python.golden +++ b/tests/golden/testdata/alternates.json.python.golden @@ -24,6 +24,7 @@ "model_objectives_earlyarrivalpenalty": 1, "model_objectives_latearrivalpenalty": 1, "model_objectives_minstops": 1, + "model_objectives_stopbalance": 0, "model_objectives_travelduration": 0, "model_objectives_unplannedpenalty": 1, "model_objectives_vehicleactivationpenalty": 1, @@ -32,6 +33,7 @@ "model_properties_disable_durations": false, "model_properties_disable_initialsolution": false, "model_properties_disable_stopdurationmultipliers": false, + "model_properties_maximumtimehorizon": 15552000, "model_validate_disable_resources": false, "model_validate_disable_starttime": false, "model_validate_enable_matrix": false, @@ -40,6 +42,10 @@ "solve_duration": 10, "solve_iterations": 50, "solve_parallelruns": 1, + "solve_plateau_absolutethreshold": -1, + "solve_plateau_duration": 0, + "solve_plateau_iterations": 0, + "solve_plateau_relativethreshold": 0, "solve_rundeterministically": true, "solve_startsolutions": 1 }, diff --git a/tests/golden/testdata/basic.json.python.golden b/tests/golden/testdata/basic.json.python.golden index 96e8252..8175259 100644 --- a/tests/golden/testdata/basic.json.python.golden +++ b/tests/golden/testdata/basic.json.python.golden @@ -24,6 +24,7 @@ "model_objectives_earlyarrivalpenalty": 1, "model_objectives_latearrivalpenalty": 1, "model_objectives_minstops": 1, + "model_objectives_stopbalance": 0, "model_objectives_travelduration": 0, "model_objectives_unplannedpenalty": 1, "model_objectives_vehicleactivationpenalty": 1, @@ -32,6 +33,7 @@ "model_properties_disable_durations": false, "model_properties_disable_initialsolution": false, "model_properties_disable_stopdurationmultipliers": false, + "model_properties_maximumtimehorizon": 15552000, "model_validate_disable_resources": false, "model_validate_disable_starttime": false, "model_validate_enable_matrix": false, @@ -40,6 +42,10 @@ "solve_duration": 10, "solve_iterations": 50, "solve_parallelruns": 1, + "solve_plateau_absolutethreshold": -1, + "solve_plateau_duration": 0, + "solve_plateau_iterations": 0, + "solve_plateau_relativethreshold": 0, "solve_rundeterministically": true, "solve_startsolutions": 1 }, diff --git a/tests/golden/testdata/capacity.json.python.golden b/tests/golden/testdata/capacity.json.python.golden index f6a9639..f368d31 100644 --- a/tests/golden/testdata/capacity.json.python.golden +++ b/tests/golden/testdata/capacity.json.python.golden @@ -24,6 +24,7 @@ "model_objectives_earlyarrivalpenalty": 1, "model_objectives_latearrivalpenalty": 1, "model_objectives_minstops": 1, + "model_objectives_stopbalance": 0, "model_objectives_travelduration": 0, "model_objectives_unplannedpenalty": 1, "model_objectives_vehicleactivationpenalty": 1, @@ -32,6 +33,7 @@ "model_properties_disable_durations": false, "model_properties_disable_initialsolution": false, "model_properties_disable_stopdurationmultipliers": false, + "model_properties_maximumtimehorizon": 15552000, "model_validate_disable_resources": false, "model_validate_disable_starttime": false, "model_validate_enable_matrix": false, @@ -40,6 +42,10 @@ "solve_duration": 10, "solve_iterations": 50, "solve_parallelruns": 1, + "solve_plateau_absolutethreshold": -1, + "solve_plateau_duration": 0, + "solve_plateau_iterations": 0, + "solve_plateau_relativethreshold": 0, "solve_rundeterministically": true, "solve_startsolutions": 1 }, diff --git a/tests/golden/testdata/compatibility_attributes.json.python.golden b/tests/golden/testdata/compatibility_attributes.json.python.golden index f3bcedc..c34def4 100644 --- a/tests/golden/testdata/compatibility_attributes.json.python.golden +++ b/tests/golden/testdata/compatibility_attributes.json.python.golden @@ -24,6 +24,7 @@ "model_objectives_earlyarrivalpenalty": 1, "model_objectives_latearrivalpenalty": 1, "model_objectives_minstops": 1, + "model_objectives_stopbalance": 0, "model_objectives_travelduration": 0, "model_objectives_unplannedpenalty": 1, "model_objectives_vehicleactivationpenalty": 1, @@ -32,6 +33,7 @@ "model_properties_disable_durations": false, "model_properties_disable_initialsolution": false, "model_properties_disable_stopdurationmultipliers": false, + "model_properties_maximumtimehorizon": 15552000, "model_validate_disable_resources": false, "model_validate_disable_starttime": false, "model_validate_enable_matrix": false, @@ -40,6 +42,10 @@ "solve_duration": 10, "solve_iterations": 50, "solve_parallelruns": 1, + "solve_plateau_absolutethreshold": -1, + "solve_plateau_duration": 0, + "solve_plateau_iterations": 0, + "solve_plateau_relativethreshold": 0, "solve_rundeterministically": true, "solve_startsolutions": 1 }, diff --git a/tests/golden/testdata/complex_precedence.json.python.golden b/tests/golden/testdata/complex_precedence.json.python.golden index eaeb883..f836487 100644 --- a/tests/golden/testdata/complex_precedence.json.python.golden +++ b/tests/golden/testdata/complex_precedence.json.python.golden @@ -24,6 +24,7 @@ "model_objectives_earlyarrivalpenalty": 1, "model_objectives_latearrivalpenalty": 1, "model_objectives_minstops": 1, + "model_objectives_stopbalance": 0, "model_objectives_travelduration": 0, "model_objectives_unplannedpenalty": 1, "model_objectives_vehicleactivationpenalty": 1, @@ -32,6 +33,7 @@ "model_properties_disable_durations": false, "model_properties_disable_initialsolution": false, "model_properties_disable_stopdurationmultipliers": false, + "model_properties_maximumtimehorizon": 15552000, "model_validate_disable_resources": false, "model_validate_disable_starttime": false, "model_validate_enable_matrix": false, @@ -40,6 +42,10 @@ "solve_duration": 10, "solve_iterations": 50, "solve_parallelruns": 1, + "solve_plateau_absolutethreshold": -1, + "solve_plateau_duration": 0, + "solve_plateau_iterations": 0, + "solve_plateau_relativethreshold": 0, "solve_rundeterministically": true, "solve_startsolutions": 1 }, diff --git a/tests/golden/testdata/custom_data.json.python.golden b/tests/golden/testdata/custom_data.json.python.golden index 23c69a1..44589f7 100644 --- a/tests/golden/testdata/custom_data.json.python.golden +++ b/tests/golden/testdata/custom_data.json.python.golden @@ -24,6 +24,7 @@ "model_objectives_earlyarrivalpenalty": 1, "model_objectives_latearrivalpenalty": 1, "model_objectives_minstops": 1, + "model_objectives_stopbalance": 0, "model_objectives_travelduration": 0, "model_objectives_unplannedpenalty": 1, "model_objectives_vehicleactivationpenalty": 1, @@ -32,6 +33,7 @@ "model_properties_disable_durations": false, "model_properties_disable_initialsolution": false, "model_properties_disable_stopdurationmultipliers": false, + "model_properties_maximumtimehorizon": 15552000, "model_validate_disable_resources": false, "model_validate_disable_starttime": false, "model_validate_enable_matrix": false, @@ -40,6 +42,10 @@ "solve_duration": 10, "solve_iterations": 50, "solve_parallelruns": 1, + "solve_plateau_absolutethreshold": -1, + "solve_plateau_duration": 0, + "solve_plateau_iterations": 0, + "solve_plateau_relativethreshold": 0, "solve_rundeterministically": true, "solve_startsolutions": 1 }, diff --git a/tests/golden/testdata/defaults.json.python.golden b/tests/golden/testdata/defaults.json.python.golden index 39c2397..504f721 100644 --- a/tests/golden/testdata/defaults.json.python.golden +++ b/tests/golden/testdata/defaults.json.python.golden @@ -24,6 +24,7 @@ "model_objectives_earlyarrivalpenalty": 1, "model_objectives_latearrivalpenalty": 1, "model_objectives_minstops": 1, + "model_objectives_stopbalance": 0, "model_objectives_travelduration": 0, "model_objectives_unplannedpenalty": 1, "model_objectives_vehicleactivationpenalty": 1, @@ -32,6 +33,7 @@ "model_properties_disable_durations": false, "model_properties_disable_initialsolution": false, "model_properties_disable_stopdurationmultipliers": false, + "model_properties_maximumtimehorizon": 15552000, "model_validate_disable_resources": false, "model_validate_disable_starttime": false, "model_validate_enable_matrix": false, @@ -40,6 +42,10 @@ "solve_duration": 10, "solve_iterations": 50, "solve_parallelruns": 1, + "solve_plateau_absolutethreshold": -1, + "solve_plateau_duration": 0, + "solve_plateau_iterations": 0, + "solve_plateau_relativethreshold": 0, "solve_rundeterministically": true, "solve_startsolutions": 1 }, diff --git a/tests/golden/testdata/direct_precedence.json.python.golden b/tests/golden/testdata/direct_precedence.json.python.golden index b6c5ce2..914ebab 100644 --- a/tests/golden/testdata/direct_precedence.json.python.golden +++ b/tests/golden/testdata/direct_precedence.json.python.golden @@ -24,6 +24,7 @@ "model_objectives_earlyarrivalpenalty": 1, "model_objectives_latearrivalpenalty": 1, "model_objectives_minstops": 1, + "model_objectives_stopbalance": 0, "model_objectives_travelduration": 0, "model_objectives_unplannedpenalty": 1, "model_objectives_vehicleactivationpenalty": 1, @@ -32,6 +33,7 @@ "model_properties_disable_durations": false, "model_properties_disable_initialsolution": false, "model_properties_disable_stopdurationmultipliers": false, + "model_properties_maximumtimehorizon": 15552000, "model_validate_disable_resources": false, "model_validate_disable_starttime": false, "model_validate_enable_matrix": false, @@ -40,6 +42,10 @@ "solve_duration": 10, "solve_iterations": 50, "solve_parallelruns": 1, + "solve_plateau_absolutethreshold": -1, + "solve_plateau_duration": 0, + "solve_plateau_iterations": 0, + "solve_plateau_relativethreshold": 0, "solve_rundeterministically": true, "solve_startsolutions": 1 }, diff --git a/tests/golden/testdata/direct_precedence_linked.json.python.golden b/tests/golden/testdata/direct_precedence_linked.json.python.golden index 0cd8e1e..1f17e02 100644 --- a/tests/golden/testdata/direct_precedence_linked.json.python.golden +++ b/tests/golden/testdata/direct_precedence_linked.json.python.golden @@ -24,6 +24,7 @@ "model_objectives_earlyarrivalpenalty": 1, "model_objectives_latearrivalpenalty": 1, "model_objectives_minstops": 1, + "model_objectives_stopbalance": 0, "model_objectives_travelduration": 0, "model_objectives_unplannedpenalty": 1, "model_objectives_vehicleactivationpenalty": 1, @@ -32,6 +33,7 @@ "model_properties_disable_durations": false, "model_properties_disable_initialsolution": false, "model_properties_disable_stopdurationmultipliers": false, + "model_properties_maximumtimehorizon": 15552000, "model_validate_disable_resources": false, "model_validate_disable_starttime": false, "model_validate_enable_matrix": false, @@ -40,6 +42,10 @@ "solve_duration": 10, "solve_iterations": 50, "solve_parallelruns": 1, + "solve_plateau_absolutethreshold": -1, + "solve_plateau_duration": 0, + "solve_plateau_iterations": 0, + "solve_plateau_relativethreshold": 0, "solve_rundeterministically": true, "solve_startsolutions": 1 }, diff --git a/tests/golden/testdata/distance_matrix.json.python.golden b/tests/golden/testdata/distance_matrix.json.python.golden index d2fd687..22e0065 100644 --- a/tests/golden/testdata/distance_matrix.json.python.golden +++ b/tests/golden/testdata/distance_matrix.json.python.golden @@ -24,6 +24,7 @@ "model_objectives_earlyarrivalpenalty": 1, "model_objectives_latearrivalpenalty": 1, "model_objectives_minstops": 1, + "model_objectives_stopbalance": 0, "model_objectives_travelduration": 0, "model_objectives_unplannedpenalty": 1, "model_objectives_vehicleactivationpenalty": 1, @@ -32,6 +33,7 @@ "model_properties_disable_durations": false, "model_properties_disable_initialsolution": false, "model_properties_disable_stopdurationmultipliers": false, + "model_properties_maximumtimehorizon": 15552000, "model_validate_disable_resources": false, "model_validate_disable_starttime": false, "model_validate_enable_matrix": false, @@ -40,6 +42,10 @@ "solve_duration": 10, "solve_iterations": 50, "solve_parallelruns": 1, + "solve_plateau_absolutethreshold": -1, + "solve_plateau_duration": 0, + "solve_plateau_iterations": 0, + "solve_plateau_relativethreshold": 0, "solve_rundeterministically": true, "solve_startsolutions": 1 }, diff --git a/tests/golden/testdata/duration_groups.json.python.golden b/tests/golden/testdata/duration_groups.json.python.golden index ada7619..b66745b 100644 --- a/tests/golden/testdata/duration_groups.json.python.golden +++ b/tests/golden/testdata/duration_groups.json.python.golden @@ -24,6 +24,7 @@ "model_objectives_earlyarrivalpenalty": 1, "model_objectives_latearrivalpenalty": 1, "model_objectives_minstops": 1, + "model_objectives_stopbalance": 0, "model_objectives_travelduration": 0, "model_objectives_unplannedpenalty": 1, "model_objectives_vehicleactivationpenalty": 1, @@ -32,6 +33,7 @@ "model_properties_disable_durations": false, "model_properties_disable_initialsolution": false, "model_properties_disable_stopdurationmultipliers": false, + "model_properties_maximumtimehorizon": 15552000, "model_validate_disable_resources": false, "model_validate_disable_starttime": false, "model_validate_enable_matrix": false, @@ -40,6 +42,10 @@ "solve_duration": 10, "solve_iterations": 50, "solve_parallelruns": 1, + "solve_plateau_absolutethreshold": -1, + "solve_plateau_duration": 0, + "solve_plateau_iterations": 0, + "solve_plateau_relativethreshold": 0, "solve_rundeterministically": true, "solve_startsolutions": 1 }, diff --git a/tests/golden/testdata/duration_groups_with_stop_multiplier.json.python.golden b/tests/golden/testdata/duration_groups_with_stop_multiplier.json.python.golden index fa54b2c..1b8702a 100644 --- a/tests/golden/testdata/duration_groups_with_stop_multiplier.json.python.golden +++ b/tests/golden/testdata/duration_groups_with_stop_multiplier.json.python.golden @@ -24,6 +24,7 @@ "model_objectives_earlyarrivalpenalty": 1, "model_objectives_latearrivalpenalty": 1, "model_objectives_minstops": 1, + "model_objectives_stopbalance": 0, "model_objectives_travelduration": 0, "model_objectives_unplannedpenalty": 1, "model_objectives_vehicleactivationpenalty": 1, @@ -32,6 +33,7 @@ "model_properties_disable_durations": false, "model_properties_disable_initialsolution": false, "model_properties_disable_stopdurationmultipliers": false, + "model_properties_maximumtimehorizon": 15552000, "model_validate_disable_resources": false, "model_validate_disable_starttime": false, "model_validate_enable_matrix": false, @@ -40,6 +42,10 @@ "solve_duration": 10, "solve_iterations": 50, "solve_parallelruns": 1, + "solve_plateau_absolutethreshold": -1, + "solve_plateau_duration": 0, + "solve_plateau_iterations": 0, + "solve_plateau_relativethreshold": 0, "solve_rundeterministically": true, "solve_startsolutions": 1 }, diff --git a/tests/golden/testdata/duration_matrix.json.python.golden b/tests/golden/testdata/duration_matrix.json.python.golden index 46f221e..26ddd8f 100644 --- a/tests/golden/testdata/duration_matrix.json.python.golden +++ b/tests/golden/testdata/duration_matrix.json.python.golden @@ -24,6 +24,7 @@ "model_objectives_earlyarrivalpenalty": 1, "model_objectives_latearrivalpenalty": 1, "model_objectives_minstops": 1, + "model_objectives_stopbalance": 0, "model_objectives_travelduration": 0, "model_objectives_unplannedpenalty": 1, "model_objectives_vehicleactivationpenalty": 1, @@ -32,6 +33,7 @@ "model_properties_disable_durations": false, "model_properties_disable_initialsolution": false, "model_properties_disable_stopdurationmultipliers": false, + "model_properties_maximumtimehorizon": 15552000, "model_validate_disable_resources": false, "model_validate_disable_starttime": false, "model_validate_enable_matrix": false, @@ -40,6 +42,10 @@ "solve_duration": 10, "solve_iterations": 50, "solve_parallelruns": 1, + "solve_plateau_absolutethreshold": -1, + "solve_plateau_duration": 0, + "solve_plateau_iterations": 0, + "solve_plateau_relativethreshold": 0, "solve_rundeterministically": true, "solve_startsolutions": 1 }, diff --git a/tests/golden/testdata/duration_matrix_time_dependent0.json.python.golden b/tests/golden/testdata/duration_matrix_time_dependent0.json.python.golden index 1a49122..0e3ddf4 100644 --- a/tests/golden/testdata/duration_matrix_time_dependent0.json.python.golden +++ b/tests/golden/testdata/duration_matrix_time_dependent0.json.python.golden @@ -24,6 +24,7 @@ "model_objectives_earlyarrivalpenalty": 1, "model_objectives_latearrivalpenalty": 1, "model_objectives_minstops": 1, + "model_objectives_stopbalance": 0, "model_objectives_travelduration": 0, "model_objectives_unplannedpenalty": 1, "model_objectives_vehicleactivationpenalty": 1, @@ -32,6 +33,7 @@ "model_properties_disable_durations": false, "model_properties_disable_initialsolution": false, "model_properties_disable_stopdurationmultipliers": false, + "model_properties_maximumtimehorizon": 15552000, "model_validate_disable_resources": false, "model_validate_disable_starttime": false, "model_validate_enable_matrix": false, @@ -40,6 +42,10 @@ "solve_duration": 10, "solve_iterations": 50, "solve_parallelruns": 1, + "solve_plateau_absolutethreshold": -1, + "solve_plateau_duration": 0, + "solve_plateau_iterations": 0, + "solve_plateau_relativethreshold": 0, "solve_rundeterministically": true, "solve_startsolutions": 1 }, diff --git a/tests/golden/testdata/duration_matrix_time_dependent1.json.python.golden b/tests/golden/testdata/duration_matrix_time_dependent1.json.python.golden index a9e2628..a9d3b68 100644 --- a/tests/golden/testdata/duration_matrix_time_dependent1.json.python.golden +++ b/tests/golden/testdata/duration_matrix_time_dependent1.json.python.golden @@ -24,6 +24,7 @@ "model_objectives_earlyarrivalpenalty": 1, "model_objectives_latearrivalpenalty": 1, "model_objectives_minstops": 1, + "model_objectives_stopbalance": 0, "model_objectives_travelduration": 0, "model_objectives_unplannedpenalty": 1, "model_objectives_vehicleactivationpenalty": 1, @@ -32,6 +33,7 @@ "model_properties_disable_durations": false, "model_properties_disable_initialsolution": false, "model_properties_disable_stopdurationmultipliers": false, + "model_properties_maximumtimehorizon": 15552000, "model_validate_disable_resources": false, "model_validate_disable_starttime": false, "model_validate_enable_matrix": false, @@ -40,6 +42,10 @@ "solve_duration": 10, "solve_iterations": 50, "solve_parallelruns": 1, + "solve_plateau_absolutethreshold": -1, + "solve_plateau_duration": 0, + "solve_plateau_iterations": 0, + "solve_plateau_relativethreshold": 0, "solve_rundeterministically": true, "solve_startsolutions": 1 }, diff --git a/tests/golden/testdata/duration_matrix_time_dependent2.json.python.golden b/tests/golden/testdata/duration_matrix_time_dependent2.json.python.golden index f936e14..9f6eef7 100644 --- a/tests/golden/testdata/duration_matrix_time_dependent2.json.python.golden +++ b/tests/golden/testdata/duration_matrix_time_dependent2.json.python.golden @@ -24,6 +24,7 @@ "model_objectives_earlyarrivalpenalty": 1, "model_objectives_latearrivalpenalty": 1, "model_objectives_minstops": 1, + "model_objectives_stopbalance": 0, "model_objectives_travelduration": 0, "model_objectives_unplannedpenalty": 1, "model_objectives_vehicleactivationpenalty": 1, @@ -32,6 +33,7 @@ "model_properties_disable_durations": false, "model_properties_disable_initialsolution": false, "model_properties_disable_stopdurationmultipliers": false, + "model_properties_maximumtimehorizon": 15552000, "model_validate_disable_resources": false, "model_validate_disable_starttime": false, "model_validate_enable_matrix": false, @@ -40,6 +42,10 @@ "solve_duration": 10, "solve_iterations": 50, "solve_parallelruns": 1, + "solve_plateau_absolutethreshold": -1, + "solve_plateau_duration": 0, + "solve_plateau_iterations": 0, + "solve_plateau_relativethreshold": 0, "solve_rundeterministically": true, "solve_startsolutions": 1 }, diff --git a/tests/golden/testdata/duration_matrix_time_dependent3.json.python.golden b/tests/golden/testdata/duration_matrix_time_dependent3.json.python.golden index 29f42b5..ecf6902 100644 --- a/tests/golden/testdata/duration_matrix_time_dependent3.json.python.golden +++ b/tests/golden/testdata/duration_matrix_time_dependent3.json.python.golden @@ -24,6 +24,7 @@ "model_objectives_earlyarrivalpenalty": 1, "model_objectives_latearrivalpenalty": 1, "model_objectives_minstops": 1, + "model_objectives_stopbalance": 0, "model_objectives_travelduration": 0, "model_objectives_unplannedpenalty": 1, "model_objectives_vehicleactivationpenalty": 1, @@ -32,6 +33,7 @@ "model_properties_disable_durations": false, "model_properties_disable_initialsolution": false, "model_properties_disable_stopdurationmultipliers": false, + "model_properties_maximumtimehorizon": 15552000, "model_validate_disable_resources": false, "model_validate_disable_starttime": false, "model_validate_enable_matrix": false, @@ -40,6 +42,10 @@ "solve_duration": 10, "solve_iterations": 50, "solve_parallelruns": 1, + "solve_plateau_absolutethreshold": -1, + "solve_plateau_duration": 0, + "solve_plateau_iterations": 0, + "solve_plateau_relativethreshold": 0, "solve_rundeterministically": true, "solve_startsolutions": 1 }, diff --git a/tests/golden/testdata/early_arrival_penalty.json.python.golden b/tests/golden/testdata/early_arrival_penalty.json.python.golden index 1642ebb..cacf824 100644 --- a/tests/golden/testdata/early_arrival_penalty.json.python.golden +++ b/tests/golden/testdata/early_arrival_penalty.json.python.golden @@ -24,6 +24,7 @@ "model_objectives_earlyarrivalpenalty": 1, "model_objectives_latearrivalpenalty": 1, "model_objectives_minstops": 1, + "model_objectives_stopbalance": 0, "model_objectives_travelduration": 0, "model_objectives_unplannedpenalty": 1, "model_objectives_vehicleactivationpenalty": 1, @@ -32,6 +33,7 @@ "model_properties_disable_durations": false, "model_properties_disable_initialsolution": false, "model_properties_disable_stopdurationmultipliers": false, + "model_properties_maximumtimehorizon": 15552000, "model_validate_disable_resources": false, "model_validate_disable_starttime": false, "model_validate_enable_matrix": false, @@ -40,6 +42,10 @@ "solve_duration": 10, "solve_iterations": 50, "solve_parallelruns": 1, + "solve_plateau_absolutethreshold": -1, + "solve_plateau_duration": 0, + "solve_plateau_iterations": 0, + "solve_plateau_relativethreshold": 0, "solve_rundeterministically": true, "solve_startsolutions": 1 }, diff --git a/tests/golden/testdata/initial_stops.json.python.golden b/tests/golden/testdata/initial_stops.json.python.golden index f6f584e..f635867 100644 --- a/tests/golden/testdata/initial_stops.json.python.golden +++ b/tests/golden/testdata/initial_stops.json.python.golden @@ -24,6 +24,7 @@ "model_objectives_earlyarrivalpenalty": 1, "model_objectives_latearrivalpenalty": 1, "model_objectives_minstops": 1, + "model_objectives_stopbalance": 0, "model_objectives_travelduration": 0, "model_objectives_unplannedpenalty": 1, "model_objectives_vehicleactivationpenalty": 1, @@ -32,6 +33,7 @@ "model_properties_disable_durations": false, "model_properties_disable_initialsolution": false, "model_properties_disable_stopdurationmultipliers": false, + "model_properties_maximumtimehorizon": 15552000, "model_validate_disable_resources": false, "model_validate_disable_starttime": false, "model_validate_enable_matrix": false, @@ -40,6 +42,10 @@ "solve_duration": 10, "solve_iterations": 50, "solve_parallelruns": 1, + "solve_plateau_absolutethreshold": -1, + "solve_plateau_duration": 0, + "solve_plateau_iterations": 0, + "solve_plateau_relativethreshold": 0, "solve_rundeterministically": true, "solve_startsolutions": 1 }, diff --git a/tests/golden/testdata/initial_stops_infeasible_compatibility.json.python.golden b/tests/golden/testdata/initial_stops_infeasible_compatibility.json.python.golden index 776ea40..7af5bbd 100644 --- a/tests/golden/testdata/initial_stops_infeasible_compatibility.json.python.golden +++ b/tests/golden/testdata/initial_stops_infeasible_compatibility.json.python.golden @@ -24,6 +24,7 @@ "model_objectives_earlyarrivalpenalty": 1, "model_objectives_latearrivalpenalty": 1, "model_objectives_minstops": 1, + "model_objectives_stopbalance": 0, "model_objectives_travelduration": 0, "model_objectives_unplannedpenalty": 1, "model_objectives_vehicleactivationpenalty": 1, @@ -32,6 +33,7 @@ "model_properties_disable_durations": false, "model_properties_disable_initialsolution": false, "model_properties_disable_stopdurationmultipliers": false, + "model_properties_maximumtimehorizon": 15552000, "model_validate_disable_resources": false, "model_validate_disable_starttime": false, "model_validate_enable_matrix": false, @@ -40,6 +42,10 @@ "solve_duration": 10, "solve_iterations": 50, "solve_parallelruns": 1, + "solve_plateau_absolutethreshold": -1, + "solve_plateau_duration": 0, + "solve_plateau_iterations": 0, + "solve_plateau_relativethreshold": 0, "solve_rundeterministically": true, "solve_startsolutions": 1 }, diff --git a/tests/golden/testdata/initial_stops_infeasible_max_duration.json.python.golden b/tests/golden/testdata/initial_stops_infeasible_max_duration.json.python.golden index 279b8fb..d00005f 100644 --- a/tests/golden/testdata/initial_stops_infeasible_max_duration.json.python.golden +++ b/tests/golden/testdata/initial_stops_infeasible_max_duration.json.python.golden @@ -24,6 +24,7 @@ "model_objectives_earlyarrivalpenalty": 1, "model_objectives_latearrivalpenalty": 1, "model_objectives_minstops": 1, + "model_objectives_stopbalance": 0, "model_objectives_travelduration": 0, "model_objectives_unplannedpenalty": 1, "model_objectives_vehicleactivationpenalty": 1, @@ -32,6 +33,7 @@ "model_properties_disable_durations": false, "model_properties_disable_initialsolution": false, "model_properties_disable_stopdurationmultipliers": false, + "model_properties_maximumtimehorizon": 15552000, "model_validate_disable_resources": false, "model_validate_disable_starttime": false, "model_validate_enable_matrix": false, @@ -40,6 +42,10 @@ "solve_duration": 10, "solve_iterations": 50, "solve_parallelruns": 1, + "solve_plateau_absolutethreshold": -1, + "solve_plateau_duration": 0, + "solve_plateau_iterations": 0, + "solve_plateau_relativethreshold": 0, "solve_rundeterministically": true, "solve_startsolutions": 1 }, diff --git a/tests/golden/testdata/initial_stops_infeasible_remove_all.json.python.golden b/tests/golden/testdata/initial_stops_infeasible_remove_all.json.python.golden index 3e5e4f5..135d5f9 100644 --- a/tests/golden/testdata/initial_stops_infeasible_remove_all.json.python.golden +++ b/tests/golden/testdata/initial_stops_infeasible_remove_all.json.python.golden @@ -24,6 +24,7 @@ "model_objectives_earlyarrivalpenalty": 1, "model_objectives_latearrivalpenalty": 1, "model_objectives_minstops": 1, + "model_objectives_stopbalance": 0, "model_objectives_travelduration": 0, "model_objectives_unplannedpenalty": 1, "model_objectives_vehicleactivationpenalty": 1, @@ -32,6 +33,7 @@ "model_properties_disable_durations": false, "model_properties_disable_initialsolution": false, "model_properties_disable_stopdurationmultipliers": false, + "model_properties_maximumtimehorizon": 15552000, "model_validate_disable_resources": false, "model_validate_disable_starttime": false, "model_validate_enable_matrix": false, @@ -40,6 +42,10 @@ "solve_duration": 10, "solve_iterations": 50, "solve_parallelruns": 1, + "solve_plateau_absolutethreshold": -1, + "solve_plateau_duration": 0, + "solve_plateau_iterations": 0, + "solve_plateau_relativethreshold": 0, "solve_rundeterministically": true, "solve_startsolutions": 1 }, diff --git a/tests/golden/testdata/initial_stops_infeasible_temporal.json.python.golden b/tests/golden/testdata/initial_stops_infeasible_temporal.json.python.golden index 816d59b..0a7e38c 100644 --- a/tests/golden/testdata/initial_stops_infeasible_temporal.json.python.golden +++ b/tests/golden/testdata/initial_stops_infeasible_temporal.json.python.golden @@ -24,6 +24,7 @@ "model_objectives_earlyarrivalpenalty": 1, "model_objectives_latearrivalpenalty": 1, "model_objectives_minstops": 1, + "model_objectives_stopbalance": 0, "model_objectives_travelduration": 0, "model_objectives_unplannedpenalty": 1, "model_objectives_vehicleactivationpenalty": 1, @@ -32,6 +33,7 @@ "model_properties_disable_durations": false, "model_properties_disable_initialsolution": false, "model_properties_disable_stopdurationmultipliers": false, + "model_properties_maximumtimehorizon": 15552000, "model_validate_disable_resources": false, "model_validate_disable_starttime": false, "model_validate_enable_matrix": false, @@ -40,6 +42,10 @@ "solve_duration": 10, "solve_iterations": 50, "solve_parallelruns": 1, + "solve_plateau_absolutethreshold": -1, + "solve_plateau_duration": 0, + "solve_plateau_iterations": 0, + "solve_plateau_relativethreshold": 0, "solve_rundeterministically": true, "solve_startsolutions": 1 }, diff --git a/tests/golden/testdata/initial_stops_infeasible_tuple.json.python.golden b/tests/golden/testdata/initial_stops_infeasible_tuple.json.python.golden index a3fa46e..3eda2f3 100644 --- a/tests/golden/testdata/initial_stops_infeasible_tuple.json.python.golden +++ b/tests/golden/testdata/initial_stops_infeasible_tuple.json.python.golden @@ -24,6 +24,7 @@ "model_objectives_earlyarrivalpenalty": 1, "model_objectives_latearrivalpenalty": 1, "model_objectives_minstops": 1, + "model_objectives_stopbalance": 0, "model_objectives_travelduration": 0, "model_objectives_unplannedpenalty": 1, "model_objectives_vehicleactivationpenalty": 1, @@ -32,6 +33,7 @@ "model_properties_disable_durations": false, "model_properties_disable_initialsolution": false, "model_properties_disable_stopdurationmultipliers": false, + "model_properties_maximumtimehorizon": 15552000, "model_validate_disable_resources": false, "model_validate_disable_starttime": false, "model_validate_enable_matrix": false, @@ -40,6 +42,10 @@ "solve_duration": 10, "solve_iterations": 50, "solve_parallelruns": 1, + "solve_plateau_absolutethreshold": -1, + "solve_plateau_duration": 0, + "solve_plateau_iterations": 0, + "solve_plateau_relativethreshold": 0, "solve_rundeterministically": true, "solve_startsolutions": 1 }, diff --git a/tests/golden/testdata/late_arrival_penalty.json.python.golden b/tests/golden/testdata/late_arrival_penalty.json.python.golden index 4be5abf..60278b7 100644 --- a/tests/golden/testdata/late_arrival_penalty.json.python.golden +++ b/tests/golden/testdata/late_arrival_penalty.json.python.golden @@ -24,6 +24,7 @@ "model_objectives_earlyarrivalpenalty": 1, "model_objectives_latearrivalpenalty": 1, "model_objectives_minstops": 1, + "model_objectives_stopbalance": 0, "model_objectives_travelduration": 0, "model_objectives_unplannedpenalty": 1, "model_objectives_vehicleactivationpenalty": 1, @@ -32,6 +33,7 @@ "model_properties_disable_durations": false, "model_properties_disable_initialsolution": false, "model_properties_disable_stopdurationmultipliers": false, + "model_properties_maximumtimehorizon": 15552000, "model_validate_disable_resources": false, "model_validate_disable_starttime": false, "model_validate_enable_matrix": false, @@ -40,6 +42,10 @@ "solve_duration": 10, "solve_iterations": 50, "solve_parallelruns": 1, + "solve_plateau_absolutethreshold": -1, + "solve_plateau_duration": 0, + "solve_plateau_iterations": 0, + "solve_plateau_relativethreshold": 0, "solve_rundeterministically": true, "solve_startsolutions": 1 }, diff --git a/tests/golden/testdata/max_distance.json.python.golden b/tests/golden/testdata/max_distance.json.python.golden index 1c417bf..c535d4e 100644 --- a/tests/golden/testdata/max_distance.json.python.golden +++ b/tests/golden/testdata/max_distance.json.python.golden @@ -24,6 +24,7 @@ "model_objectives_earlyarrivalpenalty": 1, "model_objectives_latearrivalpenalty": 1, "model_objectives_minstops": 1, + "model_objectives_stopbalance": 0, "model_objectives_travelduration": 0, "model_objectives_unplannedpenalty": 1, "model_objectives_vehicleactivationpenalty": 1, @@ -32,6 +33,7 @@ "model_properties_disable_durations": false, "model_properties_disable_initialsolution": false, "model_properties_disable_stopdurationmultipliers": false, + "model_properties_maximumtimehorizon": 15552000, "model_validate_disable_resources": false, "model_validate_disable_starttime": false, "model_validate_enable_matrix": false, @@ -40,6 +42,10 @@ "solve_duration": 10, "solve_iterations": 50, "solve_parallelruns": 1, + "solve_plateau_absolutethreshold": -1, + "solve_plateau_duration": 0, + "solve_plateau_iterations": 0, + "solve_plateau_relativethreshold": 0, "solve_rundeterministically": true, "solve_startsolutions": 1 }, diff --git a/tests/golden/testdata/max_duration.json.python.golden b/tests/golden/testdata/max_duration.json.python.golden index 1d18a93..e84c103 100644 --- a/tests/golden/testdata/max_duration.json.python.golden +++ b/tests/golden/testdata/max_duration.json.python.golden @@ -24,6 +24,7 @@ "model_objectives_earlyarrivalpenalty": 1, "model_objectives_latearrivalpenalty": 1, "model_objectives_minstops": 1, + "model_objectives_stopbalance": 0, "model_objectives_travelduration": 0, "model_objectives_unplannedpenalty": 1, "model_objectives_vehicleactivationpenalty": 1, @@ -32,6 +33,7 @@ "model_properties_disable_durations": false, "model_properties_disable_initialsolution": false, "model_properties_disable_stopdurationmultipliers": false, + "model_properties_maximumtimehorizon": 15552000, "model_validate_disable_resources": false, "model_validate_disable_starttime": false, "model_validate_enable_matrix": false, @@ -40,6 +42,10 @@ "solve_duration": 10, "solve_iterations": 50, "solve_parallelruns": 1, + "solve_plateau_absolutethreshold": -1, + "solve_plateau_duration": 0, + "solve_plateau_iterations": 0, + "solve_plateau_relativethreshold": 0, "solve_rundeterministically": true, "solve_startsolutions": 1 }, diff --git a/tests/golden/testdata/max_stops.json.python.golden b/tests/golden/testdata/max_stops.json.python.golden index 4a6bb7f..a781d79 100644 --- a/tests/golden/testdata/max_stops.json.python.golden +++ b/tests/golden/testdata/max_stops.json.python.golden @@ -24,6 +24,7 @@ "model_objectives_earlyarrivalpenalty": 1, "model_objectives_latearrivalpenalty": 1, "model_objectives_minstops": 1, + "model_objectives_stopbalance": 0, "model_objectives_travelduration": 0, "model_objectives_unplannedpenalty": 1, "model_objectives_vehicleactivationpenalty": 1, @@ -32,6 +33,7 @@ "model_properties_disable_durations": false, "model_properties_disable_initialsolution": false, "model_properties_disable_stopdurationmultipliers": false, + "model_properties_maximumtimehorizon": 15552000, "model_validate_disable_resources": false, "model_validate_disable_starttime": false, "model_validate_enable_matrix": false, @@ -40,6 +42,10 @@ "solve_duration": 10, "solve_iterations": 50, "solve_parallelruns": 1, + "solve_plateau_absolutethreshold": -1, + "solve_plateau_duration": 0, + "solve_plateau_iterations": 0, + "solve_plateau_relativethreshold": 0, "solve_rundeterministically": true, "solve_startsolutions": 1 }, diff --git a/tests/golden/testdata/max_wait_stop.json.python.golden b/tests/golden/testdata/max_wait_stop.json.python.golden index dd87de2..80dae3e 100644 --- a/tests/golden/testdata/max_wait_stop.json.python.golden +++ b/tests/golden/testdata/max_wait_stop.json.python.golden @@ -24,6 +24,7 @@ "model_objectives_earlyarrivalpenalty": 1, "model_objectives_latearrivalpenalty": 1, "model_objectives_minstops": 1, + "model_objectives_stopbalance": 0, "model_objectives_travelduration": 0, "model_objectives_unplannedpenalty": 1, "model_objectives_vehicleactivationpenalty": 1, @@ -32,6 +33,7 @@ "model_properties_disable_durations": false, "model_properties_disable_initialsolution": false, "model_properties_disable_stopdurationmultipliers": false, + "model_properties_maximumtimehorizon": 15552000, "model_validate_disable_resources": false, "model_validate_disable_starttime": false, "model_validate_enable_matrix": false, @@ -40,6 +42,10 @@ "solve_duration": 10, "solve_iterations": 50, "solve_parallelruns": 1, + "solve_plateau_absolutethreshold": -1, + "solve_plateau_duration": 0, + "solve_plateau_iterations": 0, + "solve_plateau_relativethreshold": 0, "solve_rundeterministically": true, "solve_startsolutions": 1 }, diff --git a/tests/golden/testdata/max_wait_vehicle.json.python.golden b/tests/golden/testdata/max_wait_vehicle.json.python.golden index 32da637..bdbe52e 100644 --- a/tests/golden/testdata/max_wait_vehicle.json.python.golden +++ b/tests/golden/testdata/max_wait_vehicle.json.python.golden @@ -24,6 +24,7 @@ "model_objectives_earlyarrivalpenalty": 1, "model_objectives_latearrivalpenalty": 1, "model_objectives_minstops": 1, + "model_objectives_stopbalance": 0, "model_objectives_travelduration": 0, "model_objectives_unplannedpenalty": 1, "model_objectives_vehicleactivationpenalty": 1, @@ -32,6 +33,7 @@ "model_properties_disable_durations": false, "model_properties_disable_initialsolution": false, "model_properties_disable_stopdurationmultipliers": false, + "model_properties_maximumtimehorizon": 15552000, "model_validate_disable_resources": false, "model_validate_disable_starttime": false, "model_validate_enable_matrix": false, @@ -40,6 +42,10 @@ "solve_duration": 10, "solve_iterations": 50, "solve_parallelruns": 1, + "solve_plateau_absolutethreshold": -1, + "solve_plateau_duration": 0, + "solve_plateau_iterations": 0, + "solve_plateau_relativethreshold": 0, "solve_rundeterministically": true, "solve_startsolutions": 1 }, diff --git a/tests/golden/testdata/min_stops.json.python.golden b/tests/golden/testdata/min_stops.json.python.golden index 8dc177c..d240351 100644 --- a/tests/golden/testdata/min_stops.json.python.golden +++ b/tests/golden/testdata/min_stops.json.python.golden @@ -24,6 +24,7 @@ "model_objectives_earlyarrivalpenalty": 1, "model_objectives_latearrivalpenalty": 1, "model_objectives_minstops": 1, + "model_objectives_stopbalance": 0, "model_objectives_travelduration": 0, "model_objectives_unplannedpenalty": 1, "model_objectives_vehicleactivationpenalty": 1, @@ -32,6 +33,7 @@ "model_properties_disable_durations": false, "model_properties_disable_initialsolution": false, "model_properties_disable_stopdurationmultipliers": false, + "model_properties_maximumtimehorizon": 15552000, "model_validate_disable_resources": false, "model_validate_disable_starttime": false, "model_validate_enable_matrix": false, @@ -40,6 +42,10 @@ "solve_duration": 10, "solve_iterations": 50, "solve_parallelruns": 1, + "solve_plateau_absolutethreshold": -1, + "solve_plateau_duration": 0, + "solve_plateau_iterations": 0, + "solve_plateau_relativethreshold": 0, "solve_rundeterministically": true, "solve_startsolutions": 1 }, diff --git a/tests/golden/testdata/multi_window.json.python.golden b/tests/golden/testdata/multi_window.json.python.golden index 7c55df2..4b45bf2 100644 --- a/tests/golden/testdata/multi_window.json.python.golden +++ b/tests/golden/testdata/multi_window.json.python.golden @@ -24,6 +24,7 @@ "model_objectives_earlyarrivalpenalty": 1, "model_objectives_latearrivalpenalty": 1, "model_objectives_minstops": 1, + "model_objectives_stopbalance": 0, "model_objectives_travelduration": 0, "model_objectives_unplannedpenalty": 1, "model_objectives_vehicleactivationpenalty": 1, @@ -32,6 +33,7 @@ "model_properties_disable_durations": false, "model_properties_disable_initialsolution": false, "model_properties_disable_stopdurationmultipliers": false, + "model_properties_maximumtimehorizon": 15552000, "model_validate_disable_resources": false, "model_validate_disable_starttime": false, "model_validate_enable_matrix": false, @@ -40,6 +42,10 @@ "solve_duration": 10, "solve_iterations": 50, "solve_parallelruns": 1, + "solve_plateau_absolutethreshold": -1, + "solve_plateau_duration": 0, + "solve_plateau_iterations": 0, + "solve_plateau_relativethreshold": 0, "solve_rundeterministically": true, "solve_startsolutions": 1 }, diff --git a/tests/golden/testdata/no_mix.json.python.golden b/tests/golden/testdata/no_mix.json.python.golden index 74c1544..0f20f1e 100644 --- a/tests/golden/testdata/no_mix.json.python.golden +++ b/tests/golden/testdata/no_mix.json.python.golden @@ -24,6 +24,7 @@ "model_objectives_earlyarrivalpenalty": 1, "model_objectives_latearrivalpenalty": 1, "model_objectives_minstops": 1, + "model_objectives_stopbalance": 0, "model_objectives_travelduration": 0, "model_objectives_unplannedpenalty": 1, "model_objectives_vehicleactivationpenalty": 1, @@ -32,6 +33,7 @@ "model_properties_disable_durations": false, "model_properties_disable_initialsolution": false, "model_properties_disable_stopdurationmultipliers": false, + "model_properties_maximumtimehorizon": 15552000, "model_validate_disable_resources": false, "model_validate_disable_starttime": false, "model_validate_enable_matrix": false, @@ -40,6 +42,10 @@ "solve_duration": 10, "solve_iterations": 50, "solve_parallelruns": 1, + "solve_plateau_absolutethreshold": -1, + "solve_plateau_duration": 0, + "solve_plateau_iterations": 0, + "solve_plateau_relativethreshold": 0, "solve_rundeterministically": true, "solve_startsolutions": 1 }, diff --git a/tests/golden/testdata/no_mix_null.json.python.golden b/tests/golden/testdata/no_mix_null.json.python.golden index 9bad82b..9745248 100644 --- a/tests/golden/testdata/no_mix_null.json.python.golden +++ b/tests/golden/testdata/no_mix_null.json.python.golden @@ -24,6 +24,7 @@ "model_objectives_earlyarrivalpenalty": 1, "model_objectives_latearrivalpenalty": 1, "model_objectives_minstops": 1, + "model_objectives_stopbalance": 0, "model_objectives_travelduration": 0, "model_objectives_unplannedpenalty": 1, "model_objectives_vehicleactivationpenalty": 1, @@ -32,6 +33,7 @@ "model_properties_disable_durations": false, "model_properties_disable_initialsolution": false, "model_properties_disable_stopdurationmultipliers": false, + "model_properties_maximumtimehorizon": 15552000, "model_validate_disable_resources": false, "model_validate_disable_starttime": false, "model_validate_enable_matrix": false, @@ -40,6 +42,10 @@ "solve_duration": 10, "solve_iterations": 50, "solve_parallelruns": 1, + "solve_plateau_absolutethreshold": -1, + "solve_plateau_duration": 0, + "solve_plateau_iterations": 0, + "solve_plateau_relativethreshold": 0, "solve_rundeterministically": true, "solve_startsolutions": 1 }, diff --git a/tests/golden/testdata/precedence.json.python.golden b/tests/golden/testdata/precedence.json.python.golden index 2c11379..ac70981 100644 --- a/tests/golden/testdata/precedence.json.python.golden +++ b/tests/golden/testdata/precedence.json.python.golden @@ -24,6 +24,7 @@ "model_objectives_earlyarrivalpenalty": 1, "model_objectives_latearrivalpenalty": 1, "model_objectives_minstops": 1, + "model_objectives_stopbalance": 0, "model_objectives_travelduration": 0, "model_objectives_unplannedpenalty": 1, "model_objectives_vehicleactivationpenalty": 1, @@ -32,6 +33,7 @@ "model_properties_disable_durations": false, "model_properties_disable_initialsolution": false, "model_properties_disable_stopdurationmultipliers": false, + "model_properties_maximumtimehorizon": 15552000, "model_validate_disable_resources": false, "model_validate_disable_starttime": false, "model_validate_enable_matrix": false, @@ -40,6 +42,10 @@ "solve_duration": 10, "solve_iterations": 50, "solve_parallelruns": 1, + "solve_plateau_absolutethreshold": -1, + "solve_plateau_duration": 0, + "solve_plateau_iterations": 0, + "solve_plateau_relativethreshold": 0, "solve_rundeterministically": true, "solve_startsolutions": 1 }, diff --git a/tests/golden/testdata/precedence_pathologic.json.python.golden b/tests/golden/testdata/precedence_pathologic.json.python.golden index 86aa54b..e375cc4 100644 --- a/tests/golden/testdata/precedence_pathologic.json.python.golden +++ b/tests/golden/testdata/precedence_pathologic.json.python.golden @@ -24,6 +24,7 @@ "model_objectives_earlyarrivalpenalty": 1, "model_objectives_latearrivalpenalty": 1, "model_objectives_minstops": 1, + "model_objectives_stopbalance": 0, "model_objectives_travelduration": 0, "model_objectives_unplannedpenalty": 1, "model_objectives_vehicleactivationpenalty": 1, @@ -32,6 +33,7 @@ "model_properties_disable_durations": false, "model_properties_disable_initialsolution": false, "model_properties_disable_stopdurationmultipliers": false, + "model_properties_maximumtimehorizon": 15552000, "model_validate_disable_resources": false, "model_validate_disable_starttime": false, "model_validate_enable_matrix": false, @@ -40,6 +42,10 @@ "solve_duration": 10, "solve_iterations": 50, "solve_parallelruns": 1, + "solve_plateau_absolutethreshold": -1, + "solve_plateau_duration": 0, + "solve_plateau_iterations": 0, + "solve_plateau_relativethreshold": 0, "solve_rundeterministically": true, "solve_startsolutions": 1 }, diff --git a/tests/golden/testdata/start_level.json.python.golden b/tests/golden/testdata/start_level.json.python.golden index d1e8478..86c7eb1 100644 --- a/tests/golden/testdata/start_level.json.python.golden +++ b/tests/golden/testdata/start_level.json.python.golden @@ -24,6 +24,7 @@ "model_objectives_earlyarrivalpenalty": 1, "model_objectives_latearrivalpenalty": 1, "model_objectives_minstops": 1, + "model_objectives_stopbalance": 0, "model_objectives_travelduration": 0, "model_objectives_unplannedpenalty": 1, "model_objectives_vehicleactivationpenalty": 1, @@ -32,6 +33,7 @@ "model_properties_disable_durations": false, "model_properties_disable_initialsolution": false, "model_properties_disable_stopdurationmultipliers": false, + "model_properties_maximumtimehorizon": 15552000, "model_validate_disable_resources": false, "model_validate_disable_starttime": false, "model_validate_enable_matrix": false, @@ -40,6 +42,10 @@ "solve_duration": 10, "solve_iterations": 50, "solve_parallelruns": 1, + "solve_plateau_absolutethreshold": -1, + "solve_plateau_duration": 0, + "solve_plateau_iterations": 0, + "solve_plateau_relativethreshold": 0, "solve_rundeterministically": true, "solve_startsolutions": 1 }, diff --git a/tests/golden/testdata/start_time_window.json.python.golden b/tests/golden/testdata/start_time_window.json.python.golden index 6c49b93..37c6d9e 100644 --- a/tests/golden/testdata/start_time_window.json.python.golden +++ b/tests/golden/testdata/start_time_window.json.python.golden @@ -24,6 +24,7 @@ "model_objectives_earlyarrivalpenalty": 1, "model_objectives_latearrivalpenalty": 1, "model_objectives_minstops": 1, + "model_objectives_stopbalance": 0, "model_objectives_travelduration": 0, "model_objectives_unplannedpenalty": 1, "model_objectives_vehicleactivationpenalty": 1, @@ -32,6 +33,7 @@ "model_properties_disable_durations": false, "model_properties_disable_initialsolution": false, "model_properties_disable_stopdurationmultipliers": false, + "model_properties_maximumtimehorizon": 15552000, "model_validate_disable_resources": false, "model_validate_disable_starttime": false, "model_validate_enable_matrix": false, @@ -40,6 +42,10 @@ "solve_duration": 10, "solve_iterations": 50, "solve_parallelruns": 1, + "solve_plateau_absolutethreshold": -1, + "solve_plateau_duration": 0, + "solve_plateau_iterations": 0, + "solve_plateau_relativethreshold": 0, "solve_rundeterministically": true, "solve_startsolutions": 1 }, diff --git a/tests/golden/testdata/stop_duration.json.python.golden b/tests/golden/testdata/stop_duration.json.python.golden index 0cccff0..07f9c11 100644 --- a/tests/golden/testdata/stop_duration.json.python.golden +++ b/tests/golden/testdata/stop_duration.json.python.golden @@ -24,6 +24,7 @@ "model_objectives_earlyarrivalpenalty": 1, "model_objectives_latearrivalpenalty": 1, "model_objectives_minstops": 1, + "model_objectives_stopbalance": 0, "model_objectives_travelduration": 0, "model_objectives_unplannedpenalty": 1, "model_objectives_vehicleactivationpenalty": 1, @@ -32,6 +33,7 @@ "model_properties_disable_durations": false, "model_properties_disable_initialsolution": false, "model_properties_disable_stopdurationmultipliers": false, + "model_properties_maximumtimehorizon": 15552000, "model_validate_disable_resources": false, "model_validate_disable_starttime": false, "model_validate_enable_matrix": false, @@ -40,6 +42,10 @@ "solve_duration": 10, "solve_iterations": 50, "solve_parallelruns": 1, + "solve_plateau_absolutethreshold": -1, + "solve_plateau_duration": 0, + "solve_plateau_iterations": 0, + "solve_plateau_relativethreshold": 0, "solve_rundeterministically": true, "solve_startsolutions": 1 }, diff --git a/tests/golden/testdata/stop_duration_multiplier.json.python.golden b/tests/golden/testdata/stop_duration_multiplier.json.python.golden index 852beff..709cf37 100644 --- a/tests/golden/testdata/stop_duration_multiplier.json.python.golden +++ b/tests/golden/testdata/stop_duration_multiplier.json.python.golden @@ -24,6 +24,7 @@ "model_objectives_earlyarrivalpenalty": 1, "model_objectives_latearrivalpenalty": 1, "model_objectives_minstops": 1, + "model_objectives_stopbalance": 0, "model_objectives_travelduration": 0, "model_objectives_unplannedpenalty": 1, "model_objectives_vehicleactivationpenalty": 1, @@ -32,6 +33,7 @@ "model_properties_disable_durations": false, "model_properties_disable_initialsolution": false, "model_properties_disable_stopdurationmultipliers": false, + "model_properties_maximumtimehorizon": 15552000, "model_validate_disable_resources": false, "model_validate_disable_starttime": false, "model_validate_enable_matrix": false, @@ -40,6 +42,10 @@ "solve_duration": 10, "solve_iterations": 50, "solve_parallelruns": 1, + "solve_plateau_absolutethreshold": -1, + "solve_plateau_duration": 0, + "solve_plateau_iterations": 0, + "solve_plateau_relativethreshold": 0, "solve_rundeterministically": true, "solve_startsolutions": 1 }, diff --git a/tests/golden/testdata/stop_groups.json.python.golden b/tests/golden/testdata/stop_groups.json.python.golden index 383d50d..f5ed3e0 100644 --- a/tests/golden/testdata/stop_groups.json.python.golden +++ b/tests/golden/testdata/stop_groups.json.python.golden @@ -24,6 +24,7 @@ "model_objectives_earlyarrivalpenalty": 1, "model_objectives_latearrivalpenalty": 1, "model_objectives_minstops": 1, + "model_objectives_stopbalance": 0, "model_objectives_travelduration": 0, "model_objectives_unplannedpenalty": 1, "model_objectives_vehicleactivationpenalty": 1, @@ -32,6 +33,7 @@ "model_properties_disable_durations": false, "model_properties_disable_initialsolution": false, "model_properties_disable_stopdurationmultipliers": false, + "model_properties_maximumtimehorizon": 15552000, "model_validate_disable_resources": false, "model_validate_disable_starttime": false, "model_validate_enable_matrix": false, @@ -40,6 +42,10 @@ "solve_duration": 10, "solve_iterations": 50, "solve_parallelruns": 1, + "solve_plateau_absolutethreshold": -1, + "solve_plateau_duration": 0, + "solve_plateau_iterations": 0, + "solve_plateau_relativethreshold": 0, "solve_rundeterministically": true, "solve_startsolutions": 1 }, diff --git a/tests/golden/testdata/template_input.json.python.golden b/tests/golden/testdata/template_input.json.python.golden index fb77914..d9b2231 100644 --- a/tests/golden/testdata/template_input.json.python.golden +++ b/tests/golden/testdata/template_input.json.python.golden @@ -24,6 +24,7 @@ "model_objectives_earlyarrivalpenalty": 1, "model_objectives_latearrivalpenalty": 1, "model_objectives_minstops": 1, + "model_objectives_stopbalance": 0, "model_objectives_travelduration": 0, "model_objectives_unplannedpenalty": 1, "model_objectives_vehicleactivationpenalty": 1, @@ -32,6 +33,7 @@ "model_properties_disable_durations": false, "model_properties_disable_initialsolution": false, "model_properties_disable_stopdurationmultipliers": false, + "model_properties_maximumtimehorizon": 15552000, "model_validate_disable_resources": false, "model_validate_disable_starttime": false, "model_validate_enable_matrix": false, @@ -40,6 +42,10 @@ "solve_duration": 10, "solve_iterations": 50, "solve_parallelruns": 1, + "solve_plateau_absolutethreshold": -1, + "solve_plateau_duration": 0, + "solve_plateau_iterations": 0, + "solve_plateau_relativethreshold": 0, "solve_rundeterministically": true, "solve_startsolutions": 1 }, diff --git a/tests/golden/testdata/unplanned_penalty.json.python.golden b/tests/golden/testdata/unplanned_penalty.json.python.golden index 004229c..7321dcc 100644 --- a/tests/golden/testdata/unplanned_penalty.json.python.golden +++ b/tests/golden/testdata/unplanned_penalty.json.python.golden @@ -24,6 +24,7 @@ "model_objectives_earlyarrivalpenalty": 1, "model_objectives_latearrivalpenalty": 1, "model_objectives_minstops": 1, + "model_objectives_stopbalance": 0, "model_objectives_travelduration": 0, "model_objectives_unplannedpenalty": 1, "model_objectives_vehicleactivationpenalty": 1, @@ -32,6 +33,7 @@ "model_properties_disable_durations": false, "model_properties_disable_initialsolution": false, "model_properties_disable_stopdurationmultipliers": false, + "model_properties_maximumtimehorizon": 15552000, "model_validate_disable_resources": false, "model_validate_disable_starttime": false, "model_validate_enable_matrix": false, @@ -40,6 +42,10 @@ "solve_duration": 10, "solve_iterations": 50, "solve_parallelruns": 1, + "solve_plateau_absolutethreshold": -1, + "solve_plateau_duration": 0, + "solve_plateau_iterations": 0, + "solve_plateau_relativethreshold": 0, "solve_rundeterministically": true, "solve_startsolutions": 1 }, diff --git a/tests/golden/testdata/vehicle_start_end_location.json.python.golden b/tests/golden/testdata/vehicle_start_end_location.json.python.golden index b5f80f0..4133b19 100644 --- a/tests/golden/testdata/vehicle_start_end_location.json.python.golden +++ b/tests/golden/testdata/vehicle_start_end_location.json.python.golden @@ -24,6 +24,7 @@ "model_objectives_earlyarrivalpenalty": 1, "model_objectives_latearrivalpenalty": 1, "model_objectives_minstops": 1, + "model_objectives_stopbalance": 0, "model_objectives_travelduration": 0, "model_objectives_unplannedpenalty": 1, "model_objectives_vehicleactivationpenalty": 1, @@ -32,6 +33,7 @@ "model_properties_disable_durations": false, "model_properties_disable_initialsolution": false, "model_properties_disable_stopdurationmultipliers": false, + "model_properties_maximumtimehorizon": 15552000, "model_validate_disable_resources": false, "model_validate_disable_starttime": false, "model_validate_enable_matrix": false, @@ -40,6 +42,10 @@ "solve_duration": 10, "solve_iterations": 50, "solve_parallelruns": 1, + "solve_plateau_absolutethreshold": -1, + "solve_plateau_duration": 0, + "solve_plateau_iterations": 0, + "solve_plateau_relativethreshold": 0, "solve_rundeterministically": true, "solve_startsolutions": 1 }, diff --git a/tests/golden/testdata/vehicle_start_end_time.json.python.golden b/tests/golden/testdata/vehicle_start_end_time.json.python.golden index 3f8e72e..7ba6d91 100644 --- a/tests/golden/testdata/vehicle_start_end_time.json.python.golden +++ b/tests/golden/testdata/vehicle_start_end_time.json.python.golden @@ -24,6 +24,7 @@ "model_objectives_earlyarrivalpenalty": 1, "model_objectives_latearrivalpenalty": 1, "model_objectives_minstops": 1, + "model_objectives_stopbalance": 0, "model_objectives_travelduration": 0, "model_objectives_unplannedpenalty": 1, "model_objectives_vehicleactivationpenalty": 1, @@ -32,6 +33,7 @@ "model_properties_disable_durations": false, "model_properties_disable_initialsolution": false, "model_properties_disable_stopdurationmultipliers": false, + "model_properties_maximumtimehorizon": 15552000, "model_validate_disable_resources": false, "model_validate_disable_starttime": false, "model_validate_enable_matrix": false, @@ -40,6 +42,10 @@ "solve_duration": 10, "solve_iterations": 50, "solve_parallelruns": 1, + "solve_plateau_absolutethreshold": -1, + "solve_plateau_duration": 0, + "solve_plateau_iterations": 0, + "solve_plateau_relativethreshold": 0, "solve_rundeterministically": true, "solve_startsolutions": 1 }, diff --git a/tests/golden/testdata/vehicles_duration_objective.json.python.golden b/tests/golden/testdata/vehicles_duration_objective.json.python.golden index 987091f..f8627b6 100644 --- a/tests/golden/testdata/vehicles_duration_objective.json.python.golden +++ b/tests/golden/testdata/vehicles_duration_objective.json.python.golden @@ -24,6 +24,7 @@ "model_objectives_earlyarrivalpenalty": 1, "model_objectives_latearrivalpenalty": 1, "model_objectives_minstops": 1, + "model_objectives_stopbalance": 0, "model_objectives_travelduration": 0, "model_objectives_unplannedpenalty": 1, "model_objectives_vehicleactivationpenalty": 1, @@ -32,6 +33,7 @@ "model_properties_disable_durations": false, "model_properties_disable_initialsolution": false, "model_properties_disable_stopdurationmultipliers": false, + "model_properties_maximumtimehorizon": 15552000, "model_validate_disable_resources": false, "model_validate_disable_starttime": false, "model_validate_enable_matrix": false, @@ -40,6 +42,10 @@ "solve_duration": 10, "solve_iterations": 50, "solve_parallelruns": 1, + "solve_plateau_absolutethreshold": -1, + "solve_plateau_duration": 0, + "solve_plateau_iterations": 0, + "solve_plateau_relativethreshold": 0, "solve_rundeterministically": true, "solve_startsolutions": 1 }, From 6389f94f848c6d8b6323402d3f1b9403d4a34c4b Mon Sep 17 00:00:00 2001 From: Marius Merschformann Date: Fri, 20 Dec 2024 21:54:09 +0100 Subject: [PATCH 7/7] Addressing comments --- solve_solver.go | 2 +- solve_solver_parallel.go | 7 ++++--- solve_terminate.go | 9 +++++---- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/solve_solver.go b/solve_solver.go index 2c37984..7b91231 100644 --- a/solve_solver.go +++ b/solve_solver.go @@ -352,7 +352,7 @@ func (s *solveImpl) Solve( Error: nil, } } - if s.plateauTracker.IsStop(iteration, time.Since(start)) { + if s.plateauTracker.ShouldTerminate(iteration, time.Since(start)) { break Loop } } diff --git a/solve_solver_parallel.go b/solve_solver_parallel.go index affca01..4cc8f85 100644 --- a/solve_solver_parallel.go +++ b/solve_solver_parallel.go @@ -360,11 +360,12 @@ func (s *parallelSolverImpl) Solve( s.RegisterEvents(solver.SolveEvents()) solver.SolveEvents().Iterated.Register(func(_ SolveInformation) { - if totalIterations.Add(1) >= int64(interpretedParallelSolveOptions.Iterations) { + iterations := totalIterations.Add(1) + if iterations >= int64(interpretedParallelSolveOptions.Iterations) { cancel() } - if s.plateauTracker.IsStop( - int(totalIterations.Load()), + if s.plateauTracker.ShouldTerminate( + int(iterations), time.Since(start), ) { cancel() diff --git a/solve_terminate.go b/solve_terminate.go index 1c7c645..3e9e4c4 100644 --- a/solve_terminate.go +++ b/solve_terminate.go @@ -52,8 +52,9 @@ func (t *plateauTracker) onImprovement(elapsed float64, iterations int, value fl }) } -// IsStop returns true if the solver should stop due to a detected plateau. -func (t *plateauTracker) IsStop(iterations int, elapsed time.Duration) bool { +// ShouldTerminate returns true if the solver should terminate due to a detected +// plateau. +func (t *plateauTracker) ShouldTerminate(iterations int, elapsed time.Duration) bool { if t == nil { return false } @@ -72,7 +73,7 @@ func (t *plateauTracker) IsStop(iterations int, elapsed time.Duration) bool { } // If the duration index is at the end of the progression, no // improvement was found within the cutoff. - if t.durationIndex == len(t.progression) { + if t.durationIndex >= len(t.progression) { return true } // Compare the current value to the value at the duration index. @@ -98,7 +99,7 @@ func (t *plateauTracker) IsStop(iterations int, elapsed time.Duration) bool { } // If the iterations index is at the end of the progression, no // improvement was found within the cutoff. - if t.iterationsIndex == len(t.progression) { + if t.iterationsIndex >= len(t.progression) { return true } // Compare the current value to the value at the iterations index.