From 519a2676fcedcc0f4998e64c6d00c74f4d831706 Mon Sep 17 00:00:00 2001 From: David Rijsman Date: Wed, 24 Apr 2024 16:47:46 +0200 Subject: [PATCH 01/11] Reuse solution stops --- ....go => model_constraint_successors_test.go | 2 +- solution_move_stops_generator.go | 40 ++++++++++--------- solution_vehicle.go | 4 +- 3 files changed, 25 insertions(+), 21 deletions(-) rename model_constraint_successor_test.go => model_constraint_successors_test.go (99%) diff --git a/model_constraint_successor_test.go b/model_constraint_successors_test.go similarity index 99% rename from model_constraint_successor_test.go rename to model_constraint_successors_test.go index cb1254d..0d59ab8 100644 --- a/model_constraint_successor_test.go +++ b/model_constraint_successors_test.go @@ -9,7 +9,7 @@ import ( "github.com/nextmv-io/nextroute" ) -func TestNewSuccessorConstraintConstraint_EstimateIsViolated(t *testing.T) { +func TestNewSuccessorConstraint_EstimateIsViolated(t *testing.T) { model, err := createModel( input( vehicleTypes("truck"), diff --git a/solution_move_stops_generator.go b/solution_move_stops_generator.go index 8b70c0c..8ba4f2b 100644 --- a/solution_move_stops_generator.go +++ b/solution_move_stops_generator.go @@ -30,7 +30,7 @@ func SolutionMoveStopsGeneratorChannel( ch := make(chan SolutionMoveStops) go func() { defer close(ch) - SolutionMoveStopsGenerator( + solutionMoveStopsGenerator( vehicle, planUnit, func(move SolutionMoveStops) { @@ -81,7 +81,7 @@ func SolutionMoveStopsGeneratorTest( preAllocatedMoveContainer *PreAllocatedMoveContainer, shouldStop func() bool, ) { - SolutionMoveStopsGenerator( + solutionMoveStopsGenerator( vehicle.(solutionVehicleImpl), planUnit.(*solutionPlanStopsUnitImpl), yield, @@ -91,9 +91,9 @@ func SolutionMoveStopsGeneratorTest( ) } -// SolutionMoveStopsGenerator generates all possible moves for a given vehicle and +// solutionMoveStopsGenerator generates all possible moves for a given vehicle and // plan unit. The function yield is called for each solutionMoveStopsImpl. -func SolutionMoveStopsGenerator( +func solutionMoveStopsGenerator( vehicle solutionVehicleImpl, planUnit *solutionPlanStopsUnitImpl, yield func(move SolutionMoveStops), @@ -116,34 +116,34 @@ func SolutionMoveStopsGenerator( return } - // TODO: we can reuse the stopPositions slice from m - positions := make([]stopPositionImpl, len(source)) + if cap(m.(*solutionMoveStopsImpl).stopPositions) < len(source) { + m.(*solutionMoveStopsImpl).stopPositions = make([]stopPositionImpl, len(source)) + } + m.(*solutionMoveStopsImpl).stopPositions = m.(*solutionMoveStopsImpl).stopPositions[:len(source)] + for idx := range source { - positions[idx].stopIndex = source[idx].index - positions[idx].solution = source[idx].solution + m.(*solutionMoveStopsImpl).stopPositions[idx].stopIndex = source[idx].index + m.(*solutionMoveStopsImpl).stopPositions[idx].solution = source[idx].solution } locations := make([]int, 0, len(source)) - generate(positions, locations, source, target, func(locations []int) { - m.(*solutionMoveStopsImpl).reset() + generate(m.(*solutionMoveStopsImpl).stopPositions, locations, source, target, func(locations []int) { m.(*solutionMoveStopsImpl).planUnit = planUnit - m.(*solutionMoveStopsImpl).stopPositions = positions m.(*solutionMoveStopsImpl).allowed = false m.(*solutionMoveStopsImpl).valueSeen = 1 yield(m) }, shouldStop) } -func isNotAllowed(from, to SolutionStop) bool { +func disallowedSuccessors(from, to SolutionStop) bool { fromModelStop := from.ModelStop() toModelStop := to.ModelStop() model := fromModelStop.Model() - return model.(*modelImpl).disallowedSuccessors[fromModelStop.Index()][toModelStop.Index()] } -func mustBeNeighbours(from, to SolutionStop) bool { +func mustBeDirectSuccessor(from, to SolutionStop) bool { if !from.ModelStop().HasPlanStopsUnit() { return false } @@ -177,7 +177,7 @@ func generate( } for i := start; i < len(target)-1; i++ { - if i > 0 && mustBeNeighbours(target[i], target[i+1]) { + if i > 0 && mustBeDirectSuccessor(target[i], target[i+1]) { continue } combination = append(combination, i+1) @@ -193,22 +193,26 @@ func generate( stopPositions[positionIdx-1].nextStopIndex = stopPositions[positionIdx].stopIndex } else { stopPositions[positionIdx-1].nextStopIndex = target[combination[positionIdx-1]].index - if mustBeNeighbours(stopPositions[positionIdx-1].Stop(), stopPositions[positionIdx].Stop()) { + if mustBeDirectSuccessor(stopPositions[positionIdx-1].Stop(), stopPositions[positionIdx].Stop()) { combination = combination[:positionIdx] break } } - if isNotAllowed(stopPositions[positionIdx-1].Stop(), stopPositions[positionIdx-1].Next()) { + if disallowedSuccessors(stopPositions[positionIdx-1].Stop(), stopPositions[positionIdx-1].Next()) { combination = combination[:positionIdx] if stopPositions[positionIdx-1].nextStopIndex != stopPositions[positionIdx].previousStopIndex { + // changing the previous stop index of positionIdx is not going to change + // stopPositions[positionIdx-1].nextStopIndex break } + // changing position of positionIdx is going to change stopPositions[positionIdx-1].nextStopIndex continue } } - if isNotAllowed(stopPositions[positionIdx].Previous(), stopPositions[positionIdx].Stop()) { + if disallowedSuccessors(stopPositions[positionIdx].Previous(), stopPositions[positionIdx].Stop()) { + // try next position for positionIdx combination = combination[:positionIdx] continue } diff --git a/solution_vehicle.go b/solution_vehicle.go index 7b2f92d..f33b964 100644 --- a/solution_vehicle.go +++ b/solution_vehicle.go @@ -107,7 +107,7 @@ func (v solutionVehicleImpl) firstMovePlanStopsUnit( ) (SolutionMove, error) { stop := false var bestMove SolutionMove = newNotExecutableSolutionMoveStops(planUnit) - SolutionMoveStopsGenerator( + solutionMoveStopsGenerator( v, planUnit, func(nextMove SolutionMoveStops) { @@ -353,7 +353,7 @@ func (v solutionVehicleImpl) bestMoveSequence( ) SolutionMove { var bestMove SolutionMove = newNotExecutableSolutionMoveStops(planUnit) stop := false - SolutionMoveStopsGenerator( + solutionMoveStopsGenerator( v, planUnit, func(nextMove SolutionMoveStops) { From 184517ef9ee8fe694edac452fbf944eeb9fda1d6 Mon Sep 17 00:00:00 2001 From: David Rijsman Date: Fri, 26 Apr 2024 10:31:32 +0200 Subject: [PATCH 02/11] Solution constraint interleaved --- model.go | 14 +++ model_constraint_interleave.go | 172 ++++++++++++++++++++++++++++ model_constraint_interleave_test.go | 122 ++++++++++++++++++++ solution.go | 13 ++- solution_constraint_interleaved.go | 98 ++++++++++++++++ solution_move_stops_generator.go | 5 +- 6 files changed, 421 insertions(+), 3 deletions(-) create mode 100644 model_constraint_interleave.go create mode 100644 model_constraint_interleave_test.go create mode 100644 solution_constraint_interleaved.go diff --git a/model.go b/model.go index ef421e8..7439b3d 100644 --- a/model.go +++ b/model.go @@ -403,6 +403,20 @@ func (m *modelImpl) AddConstraint(constraint ModelConstraint) error { reflect.TypeOf(constraint).String(), ) } + if _, ok := constraint.(SuccessorConstraint); ok { + if _, alreadyOneAdded := existingConstraint.(SuccessorConstraint); alreadyOneAdded { + return fmt.Errorf( + "only one SuccessorConstraint can be added to the model", + ) + } + } + if _, ok := constraint.(InterleaveConstraint); ok { + if _, alreadyOneAdded := existingConstraint.(InterleaveConstraint); alreadyOneAdded { + return fmt.Errorf( + "only one InterleaveConstraint can be added to the model", + ) + } + } } if _, ok := constraint.(ConstraintDataUpdater); ok { return fmt.Errorf( diff --git a/model_constraint_interleave.go b/model_constraint_interleave.go new file mode 100644 index 0000000..02c27bd --- /dev/null +++ b/model_constraint_interleave.go @@ -0,0 +1,172 @@ +// © 2019-present nextmv.io inc + +package nextroute + +import ( + "fmt" + "github.com/nextmv-io/nextroute/common" +) + +// InterleaveConstraint is a constraint that disallows certain target to be +// interleaved. +type InterleaveConstraint interface { + ModelConstraint + // DisallowInterleaving disallows the given planUnits to be interleaved. + DisallowInterleaving(source ModelPlanUnit, targets []ModelPlanUnit) error + + DisallowedInterleaves() []DisallowedInterleave +} + +// NewInterleaveConstraint returns a new InterleaveConstraint. +func NewInterleaveConstraint() (InterleaveConstraint, error) { + return &interleaveConstraintImpl{ + modelConstraintImpl: newModelConstraintImpl( + "interleave", + ModelExpressions{}, + ), + }, nil +} + +type DisallowedInterleave interface { + Target() ModelPlanUnit + + Sources() []ModelPlanUnit +} + +type disallowedInterleaveImpl struct { + target ModelPlanUnit + sources []ModelPlanUnit +} + +func (d *disallowedInterleaveImpl) Target() ModelPlanUnit { + return d.target +} + +func (d *disallowedInterleaveImpl) Sources() []ModelPlanUnit { + return d.sources +} + +func newDisallowedInterleave( + target ModelPlanUnit, + sources []ModelPlanUnit, +) DisallowedInterleave { + return &disallowedInterleaveImpl{ + target: target, + sources: common.DefensiveCopy(sources), + } +} + +type interleaveConstraintImpl struct { + modelConstraintImpl + disallowedInterleaves []DisallowedInterleave +} + +func (l *interleaveConstraintImpl) DisallowedInterleaves() []DisallowedInterleave { + return l.disallowedInterleaves +} + +func (l *interleaveConstraintImpl) Lock(model Model) error { + + return nil +} + +func verifyPlanUnitAllOnSameVehicle(planUnit ModelPlanUnit, preFix string) error { + if modelPlanUnitsUnit, isModelPlanUnitsUnit := planUnit.(ModelPlanUnitsUnit); isModelPlanUnitsUnit { + if modelPlanUnitsUnit.PlanAll() && !modelPlanUnitsUnit.SameVehicle() { + return fmt.Errorf( + "%s, all plan units in a conjunction must be on the same vehicle", + preFix, + ) + } + for _, planUnit := range modelPlanUnitsUnit.PlanUnits() { + if err := verifyPlanUnitAllOnSameVehicle(planUnit, preFix); err != nil { + return err + } + } + } + return nil +} + +func (l *interleaveConstraintImpl) DisallowInterleaving(target ModelPlanUnit, sources []ModelPlanUnit) error { + if target == nil { + return fmt.Errorf("source cannot be nil") + } + if sources == nil { + return fmt.Errorf("sources cannot be nil") + } + if len(sources) == 0 { + return nil + } + for idx, source := range sources { + if source == nil { + return fmt.Errorf("source[%v] cannot be nil", idx) + } + if source == target { + return fmt.Errorf("target is also in a source") + } + } + uniqueSources := common.UniqueDefined(sources, func(t ModelPlanUnit) int { + return t.Index() + }) + if len(uniqueSources) != len(sources) { + return fmt.Errorf("sources cannot have duplicate plan units") + } + // check the type of planUnit and + err := verifyPlanUnitAllOnSameVehicle(target, "target") + if err != nil { + return err + } + for idx, unit := range sources { + err = verifyPlanUnitAllOnSameVehicle(unit, fmt.Sprintf("sources[%v]", idx)) + if err != nil { + return err + } + } + + index := common.FindIndex(l.disallowedInterleaves, func(disallowedInterleave DisallowedInterleave) bool { + if disallowedInterleave.Target() == target { + return true + } + return false + }) + if index < 0 { + l.disallowedInterleaves = append(l.disallowedInterleaves, newDisallowedInterleave(target, sources)) + } else { + l.disallowedInterleaves[index].(*disallowedInterleaveImpl).sources = append( + l.disallowedInterleaves[index].(*disallowedInterleaveImpl).sources, + sources..., + ) + l.disallowedInterleaves[index].(*disallowedInterleaveImpl).sources = common.UniqueDefined( + l.disallowedInterleaves[index].(*disallowedInterleaveImpl).sources, + func(t ModelPlanUnit) int { + return t.Index() + }, + ) + } + + return nil +} + +func (l *interleaveConstraintImpl) String() string { + return l.name +} + +func (l *interleaveConstraintImpl) EstimationCost() Cost { + return LinearStop +} + +func (l *interleaveConstraintImpl) EstimateIsViolated( + move SolutionMoveStops, +) (isViolated bool, stopPositionsHint StopPositionsHint) { + for _, stopPosition := range move.StopPositions() { + disallowed, _ := move.Solution().(*solutionImpl).interleaveConstraint.(*solutionConstraintInterleavedImpl).disallowedSuccessors( + stopPosition.Stop(), + stopPosition.Next(), + ) + if disallowed { + return true, noPositionsHint() + } + } + + return false, noPositionsHint() +} diff --git a/model_constraint_interleave_test.go b/model_constraint_interleave_test.go new file mode 100644 index 0000000..72e9d08 --- /dev/null +++ b/model_constraint_interleave_test.go @@ -0,0 +1,122 @@ +// © 2019-present nextmv.io inc + +package nextroute_test + +import ( + "context" + "testing" + + "github.com/nextmv-io/nextroute" +) + +func TestNewInterleaveConstraint(t *testing.T) { + model, err := createModel( + input( + vehicleTypes("truck"), + []Vehicle{ + vehicles( + "truck", + depot(), + 1, + )[0], + }, + nil, + planPairSequences(), + ), + ) + if err != nil { + t.Error(err) + } + + interleaveConstraint, err := nextroute.NewInterleaveConstraint() + if err != nil { + t.Error(err) + } + + if interleaveConstraint == nil { + t.Error("interleave constraint should not be nil") + } + + err = interleaveConstraint.DisallowInterleaving( + model.PlanUnits()[0], + []nextroute.ModelPlanUnit{ + model.PlanUnits()[1], + }, + ) + if err != nil { + t.Error(err) + } + + err = interleaveConstraint.DisallowInterleaving( + model.PlanUnits()[0], + []nextroute.ModelPlanUnit{}, + ) + if err != nil { + t.Error(err) + } + err = interleaveConstraint.DisallowInterleaving( + nil, + []nextroute.ModelPlanUnit{ + model.PlanUnits()[1], + }, + ) + if err == nil { + t.Error("expected error, target cannot be nil") + } + err = interleaveConstraint.DisallowInterleaving( + model.PlanUnits()[0], + nil, + ) + if err == nil { + t.Error("expected error, sources cannot be nil") + } + err = interleaveConstraint.DisallowInterleaving( + model.PlanUnits()[0], + []nextroute.ModelPlanUnit{ + model.PlanUnits()[0], + nil, + }, + ) + if err == nil { + t.Error("expected error, target cannot be nil") + } + err = interleaveConstraint.DisallowInterleaving( + model.PlanUnits()[0], + []nextroute.ModelPlanUnit{ + model.PlanUnits()[0], + }, + ) + if err == nil { + t.Error("expected error, target is also in a target") + } + + err = interleaveConstraint.DisallowInterleaving( + model.PlanUnits()[0], + []nextroute.ModelPlanUnit{ + model.PlanUnits()[1], + model.PlanUnits()[1], + }, + ) + if err == nil { + t.Error("expected error, sources has duplicates") + } + + err = model.AddConstraint(interleaveConstraint) + if err != nil { + t.Error(err) + } + + solution, err := nextroute.NewSolution(model) + if err != nil { + t.Error(err) + } + + if solution == nil { + t.Error("solution should not be nil") + } + + solution.Vehicles()[0].BestMove( + context.Background(), + solution.SolutionPlanUnit(model.PlanUnits()[0]), + ) +} diff --git a/solution.go b/solution.go index 75d5710..81cd5f4 100644 --- a/solution.go +++ b/solution.go @@ -124,9 +124,17 @@ func NewSolution( } } - solution := &solutionImpl{ - model: m, + var solutionInterleaveConstraint SolutionConstraintInterleaved + for _, constraint := range model.constraints { + if interleaveConstraint, ok := constraint.(InterleaveConstraint); ok { + solutionInterleaveConstraint = newSolutionConstraintInterleaved(interleaveConstraint) + break + } + } + solution := &solutionImpl{ + model: m, + interleaveConstraint: solutionInterleaveConstraint, vehicleIndices: make([]int, 0, len(model.vehicles)), vehicles: make([]solutionVehicleImpl, 0, len(model.vehicles)), solutionVehicles: make([]SolutionVehicle, 0, len(model.vehicles)), @@ -585,6 +593,7 @@ type solutionImpl struct { objectiveSolutionData map[ModelObjective]Copier constraintSolutionData map[ModelConstraint]Copier cumulativeValues [][]float64 + interleaveConstraint SolutionConstraintInterleaved // TODO: explore if stopToPlanUnit should rather contain interfaces stopToPlanUnit []*solutionPlanStopsUnitImpl diff --git a/solution_constraint_interleaved.go b/solution_constraint_interleaved.go new file mode 100644 index 0000000..f3bdc1a --- /dev/null +++ b/solution_constraint_interleaved.go @@ -0,0 +1,98 @@ +package nextroute + +type SolutionConstraintInterleaved interface { + InterleaveConstraint() InterleaveConstraint +} + +type plannedInformation struct { + first SolutionStop + last SolutionStop +} + +// SolutionConstraintInterleaved is a data structure attached to a solution that +// contains the information about the interleaved constraint. +type solutionConstraintInterleavedImpl struct { + constraint InterleaveConstraint + sourceDisallowedInterleaves map[ModelPlanUnit]DisallowedInterleave + targetDisallowedInterleaves map[ModelPlanUnit]DisallowedInterleave + plannedInformation map[ModelPlanUnit]plannedInformation +} + +func (s *solutionConstraintInterleavedImpl) InterleaveConstraint() InterleaveConstraint { + return s.constraint +} + +func newSolutionConstraintInterleaved(constraint InterleaveConstraint) SolutionConstraintInterleaved { + impl := &solutionConstraintInterleavedImpl{ + constraint: constraint, + sourceDisallowedInterleaves: make(map[ModelPlanUnit]DisallowedInterleave), + targetDisallowedInterleaves: make(map[ModelPlanUnit]DisallowedInterleave), + plannedInformation: make(map[ModelPlanUnit]plannedInformation), + } + for _, disallowedInterleave := range constraint.DisallowedInterleaves() { + impl.sourceDisallowedInterleaves[disallowedInterleave.Target()] = disallowedInterleave + impl.plannedInformation[disallowedInterleave.Target()] = plannedInformation{} + for _, source := range disallowedInterleave.Sources() { + impl.targetDisallowedInterleaves[source] = disallowedInterleave + impl.plannedInformation[source] = plannedInformation{ + first: nil, + last: nil, + } + } + } + return impl +} + +// I want to add a stop before stop, is this allowed +// by the interleaved constraint? +func (s *solutionConstraintInterleavedImpl) disallowedSuccessors( + stop SolutionStop, + beforeStop SolutionStop, +) (bool, error) { + // DisallowInterleaving({S1, S2}, {{G1, G2, G3},{K1,K2}}) + // F - G1 - G2 - A - B - G3 - L + // F - G1 - G2 - S1? - B - A - G3 - L + // ask can you tell me something about S1, it tells me something where the other sources are + + // F - S1 - G1 - G2 - B - A - G3 - S2? - L + + // F - S1 - G1 - G2 - S1 - B - A - G3 - L + // F - K1? - S1 - G1 - G2 - B - A - G3 - L + // F - K1 - S1 - K2? - G1 - G2 - B - A - G3 - L + + if !beforeStop.IsPlanned() { + return false, nil + } + + // we need to know if stop is a target + modelStop := stop.ModelStop() + if modelStop.HasPlanStopsUnit() { + var stopPlanUnit ModelPlanUnit = modelStop.PlanStopsUnit() + + if planUnitsUnit, hasPlanUnitsUnit := stopPlanUnit.PlanUnitsUnit(); hasPlanUnitsUnit { + stopPlanUnit = planUnitsUnit + } + + // source side check + //if disallowedInterleaves, ok := s.sourceDisallowedInterleaves[stopPlanUnit]; ok { + // stopPlanUnitInformation := s.plannedInformation[stopPlanUnit] + // if stopPlanUnitInformation.first != nil { + // if stopPlanUnitInformation.first.Position() <= beforeStop.Position() && + // stopPlanUnitInformation.last.Position() >= beforeStop.Position() { + // return false, nil + // } + // } + // for _, source := range disallowedInterleaves.Sources() { + // solutionPlanUnit := solution.SolutionPlanUnit(source) + // + // } + //} + } + // it is possible that this stopPlanUnit is part of a planUnitsUnit + // we need to know if any other planUnit of the planUnitsUnit is planned + // if so, we need to check if the other planUnit is a target of the disallowedInterleave + + //beforeStopPlanUnit := beforeStop.PlanStopsUnit() + + return false, nil +} diff --git a/solution_move_stops_generator.go b/solution_move_stops_generator.go index 8ba4f2b..9c6ee67 100644 --- a/solution_move_stops_generator.go +++ b/solution_move_stops_generator.go @@ -140,7 +140,10 @@ func disallowedSuccessors(from, to SolutionStop) bool { fromModelStop := from.ModelStop() toModelStop := to.ModelStop() model := fromModelStop.Model() - return model.(*modelImpl).disallowedSuccessors[fromModelStop.Index()][toModelStop.Index()] + solution := from.Solution() + disallowed, _ := solution.(*solutionImpl).interleaveConstraint.(*solutionConstraintInterleavedImpl).disallowedSuccessors(from, to) + + return disallowed || model.(*modelImpl).disallowedSuccessors[fromModelStop.Index()][toModelStop.Index()] } func mustBeDirectSuccessor(from, to SolutionStop) bool { From 4e4e5240122bba4631cb50afce02f51b87c2b760 Mon Sep 17 00:00:00 2001 From: David Rijsman Date: Wed, 1 May 2024 12:47:00 +0200 Subject: [PATCH 03/11] Introduced tests and implementation of constraint. --- model_constraint_interleave.go | 224 +++++++- model_constraint_interleave_test.go | 774 +++++++++++++++++++++++++++- solution.go | 10 - solution_constraint_interleaved.go | 98 ---- solution_move_stops_generator.go | 5 +- 5 files changed, 988 insertions(+), 123 deletions(-) delete mode 100644 solution_constraint_interleaved.go diff --git a/model_constraint_interleave.go b/model_constraint_interleave.go index 02c27bd..eeeb467 100644 --- a/model_constraint_interleave.go +++ b/model_constraint_interleave.go @@ -15,6 +15,14 @@ type InterleaveConstraint interface { DisallowInterleaving(source ModelPlanUnit, targets []ModelPlanUnit) error DisallowedInterleaves() []DisallowedInterleave + + // SourceDisallowedInterleaves returns the disallowed interleaves for the + // given source. + SourceDisallowedInterleaves(source ModelPlanUnit) []DisallowedInterleave + + // TargetDisallowedInterleaves returns the disallowed interleaves for the + // given target. + TargetDisallowedInterleaves(target ModelPlanUnit) []DisallowedInterleave } // NewInterleaveConstraint returns a new InterleaveConstraint. @@ -24,12 +32,20 @@ func NewInterleaveConstraint() (InterleaveConstraint, error) { "interleave", ModelExpressions{}, ), + disallowedInterleaves: make([]DisallowedInterleave, 0), + sourceDisallowedInterleaves: nil, + targetDisallowedInterleaves: nil, }, nil } +// DisallowedInterleave is a disallowed interleave between a target and a set of +// sources. type DisallowedInterleave interface { + // Target returns the target plan unit. This plan unit cannot be interleaved + // with the sources. Target() ModelPlanUnit + // Sources returns the sources that cannot be interleaved with the target. Sources() []ModelPlanUnit } @@ -58,15 +74,79 @@ func newDisallowedInterleave( type interleaveConstraintImpl struct { modelConstraintImpl - disallowedInterleaves []DisallowedInterleave + disallowedInterleaves []DisallowedInterleave + sourceDisallowedInterleaves map[ModelPlanUnit][]DisallowedInterleave + targetDisallowedInterleaves map[ModelPlanUnit][]DisallowedInterleave +} + +func (l *interleaveConstraintImpl) SourceDisallowedInterleaves(source ModelPlanUnit) []DisallowedInterleave { + if l.sourceDisallowedInterleaves != nil { + if disallowedInterleaves, ok := l.sourceDisallowedInterleaves[source]; ok { + return disallowedInterleaves + } + return []DisallowedInterleave{} + } + + found := make([]DisallowedInterleave, 0) + for _, disallowedInterleave := range l.disallowedInterleaves { + for _, source := range disallowedInterleave.Sources() { + if source == source { + found = append(found, disallowedInterleave) + } + } + } + return found +} + +func (l *interleaveConstraintImpl) TargetDisallowedInterleaves(target ModelPlanUnit) []DisallowedInterleave { + if l.targetDisallowedInterleaves != nil { + if disallowedInterleaves, ok := l.targetDisallowedInterleaves[target]; ok { + return disallowedInterleaves + } + return []DisallowedInterleave{} + } + + found := make([]DisallowedInterleave, 0) + for _, disallowedInterleave := range l.disallowedInterleaves { + if disallowedInterleave.Target() == target { + found = append(found, disallowedInterleave) + } + } + return found } func (l *interleaveConstraintImpl) DisallowedInterleaves() []DisallowedInterleave { return l.disallowedInterleaves } +func addToMap(planUnit ModelPlanUnit, mapUnit map[ModelPlanUnit][]DisallowedInterleave, disallowedInterleave DisallowedInterleave) { + if modelPlanUnitsUnit, ok := planUnit.(ModelPlanUnitsUnit); ok { + for _, pu := range modelPlanUnitsUnit.PlanUnits() { + addToMap(pu, mapUnit, disallowedInterleave) + } + return + } + + if _, ok := mapUnit[planUnit]; !ok { + mapUnit[planUnit] = []DisallowedInterleave{} + } + + disallowedInterleaves := mapUnit[planUnit] + disallowedInterleaves = append(disallowedInterleaves, disallowedInterleave) + mapUnit[planUnit] = disallowedInterleaves +} + func (l *interleaveConstraintImpl) Lock(model Model) error { + l.sourceDisallowedInterleaves = make(map[ModelPlanUnit][]DisallowedInterleave) + l.targetDisallowedInterleaves = make(map[ModelPlanUnit][]DisallowedInterleave) + + for _, disallowedInterleave := range l.disallowedInterleaves { + addToMap(disallowedInterleave.Target(), l.targetDisallowedInterleaves, disallowedInterleave) + for _, source := range disallowedInterleave.Sources() { + addToMap(source, l.sourceDisallowedInterleaves, disallowedInterleave) + } + } return nil } @@ -91,6 +171,14 @@ func (l *interleaveConstraintImpl) DisallowInterleaving(target ModelPlanUnit, so if target == nil { return fmt.Errorf("source cannot be nil") } + + // TODO: cover all cases + if modelPlanStopsUnit, ok := target.(ModelPlanStopsUnit); ok { + if modelPlanStopsUnit.Stops()[0].Model().IsLocked() { + return fmt.Errorf(lockErrorMessage, "DisallowInterleaving") + } + } + if sources == nil { return fmt.Errorf("sources cannot be nil") } @@ -155,18 +243,138 @@ func (l *interleaveConstraintImpl) EstimationCost() Cost { return LinearStop } +//func nameMove(move Move) string { +// result := "" +// for idx, stopPosition := range move.StopPositions() { +// if idx > 0 { +// result += " .. " +// } +// result += stopPosition.Previous().ModelStop().ID() + " - [" +// result += stopPosition.Stop().ModelStop().ID() + "] - " +// result += stopPosition.Next().ModelStop().ID() +// } +// return result +//} +// +//func namePlanStopsUnit(planStopsUnit ModelPlanStopsUnit) string { +// result := "[" +// for idx, stop := range planStopsUnit.Stops() { +// if idx > 0 { +// result += ", " +// } +// result += stop.ID() +// } +// result += "]" +// return result +//} + func (l *interleaveConstraintImpl) EstimateIsViolated( move SolutionMoveStops, ) (isViolated bool, stopPositionsHint StopPositionsHint) { - for _, stopPosition := range move.StopPositions() { - disallowed, _ := move.Solution().(*solutionImpl).interleaveConstraint.(*solutionConstraintInterleavedImpl).disallowedSuccessors( - stopPosition.Stop(), - stopPosition.Next(), - ) - if disallowed { - return true, noPositionsHint() + solution := move.Solution() + //fmt.Println("\U0001FAE3 Move: ", + // nameMove(move), + // "for unit", + // namePlanStopsUnit(move.PlanStopsUnit().ModelPlanUnit().(ModelPlanStopsUnit)), + //) + + // FirstPlanUnit and LastPlanUnit are the first and last stops of the plan unit of + // the move. In case the move planUnit is a part of a PlanUnitsUnit, the first and last + // stops should include the already planned stops of the other plan units of the PlanUnitsUnit. + firstPlanUnit := move.Previous() + lastPlanUnit := move.Next() + + if modelPlanUnitsUnit, hasModelPlanUnitsUnit := move.PlanStopsUnit().ModelPlanUnit().PlanUnitsUnit(); hasModelPlanUnitsUnit { + // fmt.Println(" ☐ Move is for a plan unit that is part of a PlanUnitsUnit") + // first and last stop of the already planned plan-units of PlanUnitsUnit + var first, last SolutionStop + for _, planUnit := range modelPlanUnitsUnit.PlanUnits() { + if planUnit.Index() == move.PlanStopsUnit().ModelPlanUnit().Index() { + continue + } + solutionPlanUnit := solution.SolutionPlanUnit(planUnit) + if solutionPlanUnit.IsPlanned() { + for _, solutionPlanStopsUnit := range solutionPlanUnit.PlannedPlanStopsUnits() { + if solutionPlanStopsUnit.SolutionStops()[0].Vehicle() != move.Vehicle() { + continue + } + // fmt.Println(" ☐ Getting first and last of planned unit", + // namePlanStopsUnit(solutionPlanStopsUnit.ModelPlanStopsUnit()), + // ) + first, last = determineFirstLastSolutionStops(first, last, solutionPlanStopsUnit) + if first != nil && + move.Previous().Position() >= first.Position() && + move.Next().Position() <= last.Position() { + // fmt.Println(" ✅ Move is in the middle of already planned plan units of PlanUnitsUnit") + return false, noPositionsHint() + } + } + } + } + if first != nil && first.Position() <= firstPlanUnit.Position() { + firstPlanUnit = first + } + if last != nil && last.Position() >= lastPlanUnit.Position() { + lastPlanUnit = last } } + // check if the target is disallowed to be interleaved with the source + if targetDisallowedInterleaves, isTargetPlanUnit := l.targetDisallowedInterleaves[move.PlanStopsUnit().ModelPlanUnit()]; isTargetPlanUnit { + // fmt.Println(" ☐ Move is for a plan unit that is a target for a source") + var first, last SolutionStop + + for _, disallowedInterleave := range targetDisallowedInterleaves { + for _, sourcePlanUnit := range disallowedInterleave.Sources() { + sourceSolutionPlanUnit := move.Solution().SolutionPlanUnit(sourcePlanUnit) + if sourceSolutionPlanUnit.IsPlanned() { + solutionPlanStopsUnits := sourceSolutionPlanUnit.PlannedPlanStopsUnits() + for _, solutionPlanStopsUnit := range solutionPlanStopsUnits { + if solutionPlanStopsUnit.SolutionStops()[0].Vehicle() != move.Vehicle() { + continue + } + // fmt.Println(" ☐ Planned source", + // namePlanStopsUnit(solutionPlanStopsUnit.ModelPlanStopsUnit()), + // "can not interleave with target", + // namePlanStopsUnit(move.PlanStopsUnit().ModelPlanUnit().(ModelPlanStopsUnit)), + // ) + first, last = determineFirstLastSolutionStops(first, last, solutionPlanStopsUnit) + } + } + } + } + + if first != nil { + if lastPlanUnit.Position() <= first.Position() || firstPlanUnit.Position() >= last.Position() { + // fmt.Println(" ✅ Sources do not overlap with to be planned target") + return false, noPositionsHint() + } + } + + // fmt.Println(" ❌ A source would be interleaved with to be planned target") + return true, noPositionsHint() + } + // fmt.Println("✅ No violations for move") return false, noPositionsHint() } + +// determineFirstLastSolutionStops determines the first and last solution stops +// of the given solution plan stops unit. +func determineFirstLastSolutionStops( + first, last SolutionStop, + solutionPlanStopUnit SolutionPlanStopsUnit, +) (SolutionStop, SolutionStop) { + if !solutionPlanStopUnit.IsPlanned() { + return nil, nil + } + solutionStops := solutionPlanStopUnit.SolutionStops() + f := solutionStops[0] + if f != nil && (first == nil || f.Position() < first.Position()) { + first = f + } + l := solutionStops[len(solutionStops)-1] + if l != nil && (last == nil || l.Position() > last.Position()) { + last = l + } + return first, last +} diff --git a/model_constraint_interleave_test.go b/model_constraint_interleave_test.go index 72e9d08..79e14bc 100644 --- a/model_constraint_interleave_test.go +++ b/model_constraint_interleave_test.go @@ -4,6 +4,7 @@ package nextroute_test import ( "context" + "github.com/nextmv-io/nextroute/common" "testing" "github.com/nextmv-io/nextroute" @@ -114,9 +115,776 @@ func TestNewInterleaveConstraint(t *testing.T) { if solution == nil { t.Error("solution should not be nil") } +} + +func TestInterleaveConstraint1(t *testing.T) { + model, planUnits, modelStops := createInterleaveModel(t) + xPlanUnit := planUnits[0] + yPlanUnit := planUnits[1] + iPlanUnit := planUnits[2] + //jPlanUnit := planUnits[3] + a := modelStops[0] + b := modelStops[1] + c := modelStops[2] + d := modelStops[3] + e := modelStops[4] + f := modelStops[5] + g := modelStops[6] + h := modelStops[7] + i := modelStops[8] + //j := modelStops[9] + + solution, err := nextroute.NewSolution(model) + if err != nil { + t.Error(err) + } + + solutionVehicle := solution.Vehicles()[0] + + iSolutionStop := solution.SolutionStop(i) + iSolutionPlanUnit := solution.SolutionPlanUnit(iPlanUnit) + + stopPosition, err := nextroute.NewStopPosition( + solutionVehicle.First(), + iSolutionStop, + solutionVehicle.Last(), + ) + if err != nil { + t.Fatal(err) + } + move, err := nextroute.NewMoveStops( + iSolutionPlanUnit.(nextroute.SolutionPlanStopsUnit), + nextroute.StopPositions{stopPosition}, + ) + if err != nil { + t.Fatal(err) + } + + if !move.IsExecutable() { + t.Fatal("expected move to be executable") + } + + planned, err := move.Execute(context.Background()) + if err != nil { + t.Fatal(err) + } + if !planned { + t.Fatal("expected move to be planned") + } + + // we now have: F - i - L + + eSolutionStop := solution.SolutionStop(e) + + ePlanUnit := xPlanUnit.(nextroute.ModelPlanUnitsUnit).PlanUnits()[2] + if len(ePlanUnit.(nextroute.ModelPlanStopsUnit).Stops()) != 1 { + t.Fatal("expected plan unit to have 1 stop (e)") + } + eSolutionPlanUnit := solution.SolutionPlanUnit(ePlanUnit) + + stopPosition, err = nextroute.NewStopPosition( + solutionVehicle.First(), + eSolutionStop, + solutionVehicle.First().Next(), + ) + if err != nil { + t.Fatal(err) + } + move, err = nextroute.NewMoveStops( + eSolutionPlanUnit.(nextroute.SolutionPlanStopsUnit), + nextroute.StopPositions{stopPosition}, + ) + if err != nil { + t.Fatal(err) + } + + if !move.IsExecutable() { + t.Fatal("expected move to be executable") + } + + planned, err = move.Execute(context.Background()) + if err != nil { + t.Fatal(err) + } + if !planned { + t.Fatal("expected move to be planned") + } + + // we now have: F - e - i - L + aSolutionStop := solution.SolutionStop(a) + bSolutionStop := solution.SolutionStop(b) + + abPlanUnit := xPlanUnit.(nextroute.ModelPlanUnitsUnit).PlanUnits()[0] + if len(abPlanUnit.(nextroute.ModelPlanStopsUnit).Stops()) != 2 { + t.Fatal("expected plan unit to have 2 stops (a, b)") + } + abSolutionPlanUnit := solution.SolutionPlanUnit(abPlanUnit) + + // Check F - a - b - e - i - L is accepted + stopPositiona, err := nextroute.NewStopPosition( + solutionVehicle.First(), + aSolutionStop, + bSolutionStop, + ) + if err != nil { + t.Fatal(err) + } + stopPositionb, err := nextroute.NewStopPosition( + aSolutionStop, + bSolutionStop, + solutionVehicle.First().Next(), + ) + if err != nil { + t.Fatal(err) + } + move, err = nextroute.NewMoveStops( + abSolutionPlanUnit.(nextroute.SolutionPlanStopsUnit), + nextroute.StopPositions{stopPositiona, stopPositionb}, + ) + if err != nil { + t.Fatal(err) + } + + if !move.IsExecutable() { + t.Fatal("expected move to be executable") + } + + planned, err = move.Execute(context.Background()) + if err != nil { + t.Fatal(err) + } + if !planned { + t.Fatal("expected move to be planned") + } + unplanned, err := abSolutionPlanUnit.UnPlan() + if err != nil { + t.Fatal(err) + } + if !unplanned { + t.Fatal("expected plan unit to be unplanned") + } + // Check F - a - e - b - i - L is accepted + stopPositiona, err = nextroute.NewStopPosition( + solutionVehicle.First(), + aSolutionStop, + eSolutionStop, + ) + if err != nil { + t.Fatal(err) + } + stopPositionb, err = nextroute.NewStopPosition( + eSolutionStop, + bSolutionStop, + iSolutionStop, + ) + if err != nil { + t.Fatal(err) + } + move, err = nextroute.NewMoveStops( + abSolutionPlanUnit.(nextroute.SolutionPlanStopsUnit), + nextroute.StopPositions{stopPositiona, stopPositionb}, + ) + if err != nil { + t.Fatal(err) + } + if !move.IsExecutable() { + t.Fatal("expected move to be executable") + } + + planned, err = move.Execute(context.Background()) + if err != nil { + t.Fatal(err) + } + if !planned { + t.Fatal("expected move to be planned") + } + unplanned, err = abSolutionPlanUnit.UnPlan() + if err != nil { + t.Fatal(err) + } + if !unplanned { + t.Fatal("expected plan unit to be unplanned") + } + // Check F - a - e - i - b - L is rejected + stopPositiona, err = nextroute.NewStopPosition( + solutionVehicle.First(), + aSolutionStop, + eSolutionStop, + ) + if err != nil { + t.Fatal(err) + } + stopPositionb, err = nextroute.NewStopPosition( + iSolutionStop, + bSolutionStop, + solutionVehicle.Last(), + ) + if err != nil { + t.Fatal(err) + } + move, err = nextroute.NewMoveStops( + abSolutionPlanUnit.(nextroute.SolutionPlanStopsUnit), + nextroute.StopPositions{stopPositiona, stopPositionb}, + ) + if err != nil { + t.Fatal(err) + } + if move.IsExecutable() { + t.Fatal( + "expected move to be not executable, i can not be interleaved with a-b" + + ", a before i and b after i is not allowed", + ) + } + // Check F - e - i - a - b - L is rejected + stopPositiona, err = nextroute.NewStopPosition( + solutionVehicle.Last().Previous(), + aSolutionStop, + bSolutionStop, + ) + if err != nil { + t.Fatal(err) + } + stopPositionb, err = nextroute.NewStopPosition( + aSolutionStop, + bSolutionStop, + solutionVehicle.Last(), + ) + if err != nil { + t.Fatal(err) + } + move, err = nextroute.NewMoveStops( + abSolutionPlanUnit.(nextroute.SolutionPlanStopsUnit), + nextroute.StopPositions{stopPositiona, stopPositionb}, + ) + if err != nil { + t.Fatal(err) + } + if move.IsExecutable() { + t.Fatal( + "expected move to be not executable, i can not be interleaved with a-b" + + ", a-b not allowed after i as e is already before i", + ) + } + // Check F - e - a - b - i - L is accepted + stopPositiona, err = nextroute.NewStopPosition( + eSolutionStop, + aSolutionStop, + bSolutionStop, + ) + if err != nil { + t.Fatal(err) + } + stopPositionb, err = nextroute.NewStopPosition( + aSolutionStop, + bSolutionStop, + iSolutionStop, + ) + if err != nil { + t.Fatal(err) + } + move, err = nextroute.NewMoveStops( + abSolutionPlanUnit.(nextroute.SolutionPlanStopsUnit), + nextroute.StopPositions{stopPositiona, stopPositionb}, + ) + if err != nil { + t.Fatal(err) + } + if !move.IsExecutable() { + t.Fatal("expected move to be executable") + } + + planned, err = move.Execute(context.Background()) + if err != nil { + t.Fatal(err) + } + if !planned { + t.Fatal("expected move to be planned") + } + // We now have F - e - a - b - i - L + cSolutionStop := solution.SolutionStop(c) + dSolutionStop := solution.SolutionStop(d) + + cdPlanUnit := xPlanUnit.(nextroute.ModelPlanUnitsUnit).PlanUnits()[1] + if len(cdPlanUnit.(nextroute.ModelPlanStopsUnit).Stops()) != 2 { + t.Fatal("expected plan unit to have 2 stops (c, d)") + } + cdSolutionPlanUnit := solution.SolutionPlanUnit(cdPlanUnit) + + // Check F - c - e - a - b - i - d - L is rejected + stopPositionc, err := nextroute.NewStopPosition( + solutionVehicle.First(), + cSolutionStop, + eSolutionStop, + ) + if err != nil { + t.Fatal(err) + } + stopPositiond, err := nextroute.NewStopPosition( + iSolutionStop, + dSolutionStop, + solutionVehicle.Last(), + ) + if err != nil { + t.Fatal(err) + } + move, err = nextroute.NewMoveStops( + cdSolutionPlanUnit.(nextroute.SolutionPlanStopsUnit), + nextroute.StopPositions{stopPositionc, stopPositiond}, + ) + if err != nil { + t.Fatal(err) + } + if move.IsExecutable() { + t.Fatal("expected move not to be executable, d not allowed after i as b is already before i") + } + // Check F - e - a - b - i - c - d - L is rejected + stopPositionc, err = nextroute.NewStopPosition( + iSolutionStop, + cSolutionStop, + dSolutionStop, + ) + if err != nil { + t.Fatal(err) + } + stopPositiond, err = nextroute.NewStopPosition( + cSolutionStop, + dSolutionStop, + solutionVehicle.Last(), + ) + if err != nil { + t.Fatal(err) + } + move, err = nextroute.NewMoveStops( + cdSolutionPlanUnit.(nextroute.SolutionPlanStopsUnit), + nextroute.StopPositions{stopPositionc, stopPositiond}, + ) + if err != nil { + t.Fatal(err) + } + if move.IsExecutable() { + t.Fatal("expected move not to be executable, c - d not allowed after i as b is already before i") + } + // Check F - e - a - b - c - d - i - L is accepted + stopPositionc, err = nextroute.NewStopPosition( + bSolutionStop, + cSolutionStop, + dSolutionStop, + ) + if err != nil { + t.Fatal(err) + } + stopPositiond, err = nextroute.NewStopPosition( + cSolutionStop, + dSolutionStop, + iSolutionStop, + ) + if err != nil { + t.Fatal(err) + } + move, err = nextroute.NewMoveStops( + cdSolutionPlanUnit.(nextroute.SolutionPlanStopsUnit), + nextroute.StopPositions{stopPositionc, stopPositiond}, + ) + if err != nil { + t.Fatal(err) + } + if !move.IsExecutable() { + t.Fatal("expected move to be executable, c - d is before i") + } + planned, err = move.Execute(context.Background()) + if err != nil { + t.Fatal(err) + } + if !planned { + t.Fatal("expected move to be planned") + } + + // We now have F - e - a - b - c - d - i - L - solution.Vehicles()[0].BestMove( - context.Background(), - solution.SolutionPlanUnit(model.PlanUnits()[0]), + fSolutionStop := solution.SolutionStop(f) + gSolutionStop := solution.SolutionStop(g) + + fgPlanUnit := yPlanUnit.(nextroute.ModelPlanUnitsUnit).PlanUnits()[0] + if len(fgPlanUnit.(nextroute.ModelPlanStopsUnit).Stops()) != 2 { + t.Fatal("expected plan unit to have 2 stops (f, g)") + } + fgSolutionPlanUnit := solution.SolutionPlanUnit(fgPlanUnit) + + // Check F - f - e - a - b - c - d - i - g - L is rejected + stopPositionf, err := nextroute.NewStopPosition( + solutionVehicle.First(), + fSolutionStop, + eSolutionStop, + ) + if err != nil { + t.Fatal(err) + } + stopPositiong, err := nextroute.NewStopPosition( + iSolutionStop, + gSolutionStop, + solutionVehicle.Last(), + ) + if err != nil { + t.Fatal(err) + } + move, err = nextroute.NewMoveStops( + fgSolutionPlanUnit.(nextroute.SolutionPlanStopsUnit), + nextroute.StopPositions{stopPositionf, stopPositiong}, + ) + if err != nil { + t.Fatal(err) + } + if move.IsExecutable() { + t.Fatal("expected move not to be executable, x not allowed to interleave f - g") + } + // Check F - e - a - b - c - d - f - i - g - L is accepted + stopPositionf, err = nextroute.NewStopPosition( + dSolutionStop, + fSolutionStop, + iSolutionStop, + ) + if err != nil { + t.Fatal(err) + } + stopPositiong, err = nextroute.NewStopPosition( + iSolutionStop, + gSolutionStop, + solutionVehicle.Last(), + ) + if err != nil { + t.Fatal(err) + } + move, err = nextroute.NewMoveStops( + fgSolutionPlanUnit.(nextroute.SolutionPlanStopsUnit), + nextroute.StopPositions{stopPositionf, stopPositiong}, ) + if err != nil { + t.Fatal(err) + } + if !move.IsExecutable() { + t.Fatal("expected move to be executable, i can interleave f - g") + } + // Check F - f - g - e - a - b - c - d - i - L is accepted + stopPositionf, err = nextroute.NewStopPosition( + solutionVehicle.First(), + fSolutionStop, + gSolutionStop, + ) + if err != nil { + t.Fatal(err) + } + stopPositiong, err = nextroute.NewStopPosition( + fSolutionStop, + gSolutionStop, + solutionVehicle.First().Next(), + ) + if err != nil { + t.Fatal(err) + } + move, err = nextroute.NewMoveStops( + fgSolutionPlanUnit.(nextroute.SolutionPlanStopsUnit), + nextroute.StopPositions{stopPositionf, stopPositiong}, + ) + if err != nil { + t.Fatal(err) + } + if !move.IsExecutable() { + t.Fatal("expected move to be executable, i can interleave f - g") + } + + hSolutionStop := solution.SolutionStop(h) + hPlanUnit := yPlanUnit.(nextroute.ModelPlanUnitsUnit).PlanUnits()[1] + if len(hPlanUnit.(nextroute.ModelPlanStopsUnit).Stops()) != 1 { + t.Fatal("expected plan unit to have 1 stop (h)") + } + hSolutionPlanUnit := solution.SolutionPlanUnit(hPlanUnit) + + // Check F - h - e - a - b - c - d - i - L is accepted + stopPositionh, err := nextroute.NewStopPosition( + solutionVehicle.First(), + hSolutionStop, + eSolutionStop, + ) + if err != nil { + t.Fatal(err) + } + move, err = nextroute.NewMoveStops( + hSolutionPlanUnit.(nextroute.SolutionPlanStopsUnit), + nextroute.StopPositions{stopPositionh}, + ) + if err != nil { + t.Fatal(err) + } + if !move.IsExecutable() { + t.Fatal("expected move to be executable, h is allowed first") + } + // Check F - e - h - a - b - c - d - i - L is rejected + stopPositionh, err = nextroute.NewStopPosition( + eSolutionStop, + hSolutionStop, + aSolutionStop, + ) + if err != nil { + t.Fatal(err) + } + move, err = nextroute.NewMoveStops( + hSolutionPlanUnit.(nextroute.SolutionPlanStopsUnit), + nextroute.StopPositions{stopPositionh}, + ) + if err != nil { + t.Fatal(err) + } + if move.IsExecutable() { + t.Fatal("expected move not to be executable, h cannot be interleaved with e and a-b") + } + // Check F - e - a - h - b - c - d - i - L is rejected + stopPositionh, err = nextroute.NewStopPosition( + aSolutionStop, + hSolutionStop, + bSolutionStop, + ) + if err != nil { + t.Fatal(err) + } + move, err = nextroute.NewMoveStops( + hSolutionPlanUnit.(nextroute.SolutionPlanStopsUnit), + nextroute.StopPositions{stopPositionh}, + ) + if err != nil { + t.Fatal(err) + } + if move.IsExecutable() { + t.Fatal("expected move not to be executable, h cannot be interleaved with a-b") + } + + // Check F - e - a - b - c - d - h - i - L is accepted + stopPositionh, err = nextroute.NewStopPosition( + dSolutionStop, + hSolutionStop, + iSolutionStop, + ) + if err != nil { + t.Fatal(err) + } + move, err = nextroute.NewMoveStops( + hSolutionPlanUnit.(nextroute.SolutionPlanStopsUnit), + nextroute.StopPositions{stopPositionh}, + ) + if err != nil { + t.Fatal(err) + } + if !move.IsExecutable() { + t.Fatal("expected move to be executable") + } + // Check F - e - a - b - c - d - i - h - L is accepted + stopPositionh, err = nextroute.NewStopPosition( + iSolutionStop, + hSolutionStop, + solutionVehicle.Last(), + ) + if err != nil { + t.Fatal(err) + } + move, err = nextroute.NewMoveStops( + hSolutionPlanUnit.(nextroute.SolutionPlanStopsUnit), + nextroute.StopPositions{stopPositionh}, + ) + if err != nil { + t.Fatal(err) + } + if !move.IsExecutable() { + t.Fatal("expected move to be executable") + } +} + +// createInterleaveModel creates a model with 10 stops, 4 plan units, 2 vehicles. +// Creates 10 stops A, B, C, D, E, F, G, H, I, J +// Creates 4 plan units: +// 1. Plan units unit x consisting out of 3 plan stops units: +// - Plan stops unit x1 consisting out of stops A, B, A has to go before B +// - Plan stops unit x2 consisting out of stops C, D, C has to go before D +// - Plan stops unit x3 consisting out of stop E +// +// 2. Plan units unit y consisting out of 2 plan stops units: +// - Plan stops unit y1 consisting out of stops F, G, F has to go before G +// - Plan stops unit y2 consisting out of stop H +// +// 3. Plan stops unit i consisting out of stop I +// 4. Plan stops unit j consisting out of stop J +// +// Creates an interleave constraint that: +// - Disallows interleaving of plan unit y and i with plan unit x +// - Disallows interleaving of plan unit x and j with plan unit y +// Adds the interleave constraint to the model +// Returns the model, plan units [x,y, i, j], and stops [A, B, C, D, E, F, G, H, I, J] +func createInterleaveModel(t *testing.T) (nextroute.Model, nextroute.ModelPlanUnits, nextroute.ModelStops) { + model, err := nextroute.NewModel() + if err != nil { + t.Fatal(err) + } + center, err := common.NewLocation(0, 0) + if err != nil { + t.Fatal(err) + } + a, err := model.NewStop(center) + a.SetID("a") + if err != nil { + t.Fatal(err) + } + b, err := model.NewStop(center) + b.SetID("b") + if err != nil { + t.Fatal(err) + } + c, err := model.NewStop(center) + c.SetID("c") + if err != nil { + t.Fatal(err) + } + d, err := model.NewStop(center) + d.SetID("d") + if err != nil { + t.Fatal(err) + } + e, err := model.NewStop(center) + e.SetID("e") + if err != nil { + t.Fatal(err) + } + x1DAG := nextroute.NewDirectedAcyclicGraph() + err = x1DAG.AddArc(a, b) + if err != nil { + t.Fatal(err) + } + x1, err := model.NewPlanMultipleStops([]nextroute.ModelStop{a, b}, x1DAG) + if err != nil { + t.Fatal(err) + } + x2DAG := nextroute.NewDirectedAcyclicGraph() + err = x2DAG.AddArc(c, d) + if err != nil { + t.Fatal(err) + } + x2, err := model.NewPlanMultipleStops([]nextroute.ModelStop{c, d}, x2DAG) + if err != nil { + t.Fatal(err) + } + x3, err := model.NewPlanSingleStop(e) + if err != nil { + t.Fatal(err) + } + xPlanUnit, err := model.NewPlanAllPlanUnits(true, x1, x2, x3) + if err != nil { + t.Fatal(err) + } + + f, err := model.NewStop(center) + f.SetID("f") + if err != nil { + t.Fatal(err) + } + g, err := model.NewStop(center) + g.SetID("g") + if err != nil { + t.Fatal(err) + } + h, err := model.NewStop(center) + h.SetID("h") + if err != nil { + t.Fatal(err) + } + y1DAG := nextroute.NewDirectedAcyclicGraph() + err = y1DAG.AddArc(f, g) + if err != nil { + t.Fatal(err) + } + y1, err := model.NewPlanMultipleStops([]nextroute.ModelStop{f, g}, y1DAG) + if err != nil { + t.Fatal(err) + } + y2, err := model.NewPlanSingleStop(h) + if err != nil { + t.Fatal(err) + } + yPlanUnit, err := model.NewPlanAllPlanUnits(true, y1, y2) + if err != nil { + t.Fatal(err) + } + + i, err := model.NewStop(center) + i.SetID("i") + if err != nil { + t.Fatal(err) + } + j, err := model.NewStop(center) + j.SetID("j") + if err != nil { + t.Fatal(err) + } + iPlanUnit, err := model.NewPlanSingleStop(i) + if err != nil { + t.Fatal(err) + } + jPlanUnit, err := model.NewPlanSingleStop(j) + if err != nil { + t.Fatal(err) + } + + interleaveConstraint, err := nextroute.NewInterleaveConstraint() + if err != nil { + t.Fatal(err) + } + err = interleaveConstraint.DisallowInterleaving(xPlanUnit, []nextroute.ModelPlanUnit{yPlanUnit, iPlanUnit}) + if err != nil { + t.Fatal(err) + } + err = interleaveConstraint.DisallowInterleaving(yPlanUnit, []nextroute.ModelPlanUnit{xPlanUnit, jPlanUnit}) + if err != nil { + t.Fatal(err) + } + + err = model.AddConstraint(interleaveConstraint) + if err != nil { + t.Fatal(err) + } + + vt, err := model.NewVehicleType( + nextroute.NewTimeIndependentDurationExpression( + nextroute.NewTravelDurationExpression( + nextroute.NewHaversineExpression(), + common.NewSpeed( + 10.0, + common.MetersPerSecond, + ), + ), + ), + nextroute.NewDurationExpression( + "travelDuration", + nextroute.NewStopDurationExpression("serviceDuration", 0.0), + common.Second, + ), + ) + if err != nil { + t.Fatal(err) + } + warehouse, err := model.NewStop(center) + if err != nil { + t.Fatal(err) + } + warehouse.SetID("warehouse") + + v, err := model.NewVehicle(vt, model.Epoch(), warehouse, warehouse) + v.SetID("v1") + if err != nil { + t.Fatal(err) + } + v, err = model.NewVehicle(vt, model.Epoch(), warehouse, warehouse) + v.SetID("v2") + if err != nil { + t.Fatal(err) + } + + return model, + nextroute.ModelPlanUnits{xPlanUnit, yPlanUnit, iPlanUnit, jPlanUnit}, + nextroute.ModelStops{a, b, c, d, e, f, g, h, i, j} } diff --git a/solution.go b/solution.go index 4db77e3..bbdd9f2 100644 --- a/solution.go +++ b/solution.go @@ -124,17 +124,8 @@ func NewSolution( } } - var solutionInterleaveConstraint SolutionConstraintInterleaved - for _, constraint := range model.constraints { - if interleaveConstraint, ok := constraint.(InterleaveConstraint); ok { - solutionInterleaveConstraint = newSolutionConstraintInterleaved(interleaveConstraint) - break - } - } - solution := &solutionImpl{ model: m, - interleaveConstraint: solutionInterleaveConstraint, vehicleIndices: make([]int, 0, len(model.vehicles)), vehicles: make([]solutionVehicleImpl, 0, len(model.vehicles)), solutionVehicles: make([]SolutionVehicle, 0, len(model.vehicles)), @@ -593,7 +584,6 @@ type solutionImpl struct { objectiveSolutionData map[ModelObjective]Copier constraintSolutionData map[ModelConstraint]Copier cumulativeValues [][]float64 - interleaveConstraint SolutionConstraintInterleaved // TODO: explore if stopToPlanUnit should rather contain interfaces stopToPlanUnit []*solutionPlanStopsUnitImpl diff --git a/solution_constraint_interleaved.go b/solution_constraint_interleaved.go deleted file mode 100644 index f3bdc1a..0000000 --- a/solution_constraint_interleaved.go +++ /dev/null @@ -1,98 +0,0 @@ -package nextroute - -type SolutionConstraintInterleaved interface { - InterleaveConstraint() InterleaveConstraint -} - -type plannedInformation struct { - first SolutionStop - last SolutionStop -} - -// SolutionConstraintInterleaved is a data structure attached to a solution that -// contains the information about the interleaved constraint. -type solutionConstraintInterleavedImpl struct { - constraint InterleaveConstraint - sourceDisallowedInterleaves map[ModelPlanUnit]DisallowedInterleave - targetDisallowedInterleaves map[ModelPlanUnit]DisallowedInterleave - plannedInformation map[ModelPlanUnit]plannedInformation -} - -func (s *solutionConstraintInterleavedImpl) InterleaveConstraint() InterleaveConstraint { - return s.constraint -} - -func newSolutionConstraintInterleaved(constraint InterleaveConstraint) SolutionConstraintInterleaved { - impl := &solutionConstraintInterleavedImpl{ - constraint: constraint, - sourceDisallowedInterleaves: make(map[ModelPlanUnit]DisallowedInterleave), - targetDisallowedInterleaves: make(map[ModelPlanUnit]DisallowedInterleave), - plannedInformation: make(map[ModelPlanUnit]plannedInformation), - } - for _, disallowedInterleave := range constraint.DisallowedInterleaves() { - impl.sourceDisallowedInterleaves[disallowedInterleave.Target()] = disallowedInterleave - impl.plannedInformation[disallowedInterleave.Target()] = plannedInformation{} - for _, source := range disallowedInterleave.Sources() { - impl.targetDisallowedInterleaves[source] = disallowedInterleave - impl.plannedInformation[source] = plannedInformation{ - first: nil, - last: nil, - } - } - } - return impl -} - -// I want to add a stop before stop, is this allowed -// by the interleaved constraint? -func (s *solutionConstraintInterleavedImpl) disallowedSuccessors( - stop SolutionStop, - beforeStop SolutionStop, -) (bool, error) { - // DisallowInterleaving({S1, S2}, {{G1, G2, G3},{K1,K2}}) - // F - G1 - G2 - A - B - G3 - L - // F - G1 - G2 - S1? - B - A - G3 - L - // ask can you tell me something about S1, it tells me something where the other sources are - - // F - S1 - G1 - G2 - B - A - G3 - S2? - L - - // F - S1 - G1 - G2 - S1 - B - A - G3 - L - // F - K1? - S1 - G1 - G2 - B - A - G3 - L - // F - K1 - S1 - K2? - G1 - G2 - B - A - G3 - L - - if !beforeStop.IsPlanned() { - return false, nil - } - - // we need to know if stop is a target - modelStop := stop.ModelStop() - if modelStop.HasPlanStopsUnit() { - var stopPlanUnit ModelPlanUnit = modelStop.PlanStopsUnit() - - if planUnitsUnit, hasPlanUnitsUnit := stopPlanUnit.PlanUnitsUnit(); hasPlanUnitsUnit { - stopPlanUnit = planUnitsUnit - } - - // source side check - //if disallowedInterleaves, ok := s.sourceDisallowedInterleaves[stopPlanUnit]; ok { - // stopPlanUnitInformation := s.plannedInformation[stopPlanUnit] - // if stopPlanUnitInformation.first != nil { - // if stopPlanUnitInformation.first.Position() <= beforeStop.Position() && - // stopPlanUnitInformation.last.Position() >= beforeStop.Position() { - // return false, nil - // } - // } - // for _, source := range disallowedInterleaves.Sources() { - // solutionPlanUnit := solution.SolutionPlanUnit(source) - // - // } - //} - } - // it is possible that this stopPlanUnit is part of a planUnitsUnit - // we need to know if any other planUnit of the planUnitsUnit is planned - // if so, we need to check if the other planUnit is a target of the disallowedInterleave - - //beforeStopPlanUnit := beforeStop.PlanStopsUnit() - - return false, nil -} diff --git a/solution_move_stops_generator.go b/solution_move_stops_generator.go index b426d09..709f724 100644 --- a/solution_move_stops_generator.go +++ b/solution_move_stops_generator.go @@ -140,10 +140,7 @@ func disallowedSuccessors(from, to SolutionStop) bool { fromModelStop := from.ModelStop() toModelStop := to.ModelStop() model := fromModelStop.Model() - solution := from.Solution() - disallowed, _ := solution.(*solutionImpl).interleaveConstraint.(*solutionConstraintInterleavedImpl).disallowedSuccessors(from, to) - - return disallowed || model.(*modelImpl).disallowedSuccessors[fromModelStop.Index()][toModelStop.Index()] + return model.(*modelImpl).disallowedSuccessors[fromModelStop.Index()][toModelStop.Index()] } func mustBeDirectSuccessor(from, to SolutionStop) bool { From bd7ea71360814928c795a2330188d3c08b095304 Mon Sep 17 00:00:00 2001 From: David Rijsman Date: Thu, 2 May 2024 14:19:32 +0200 Subject: [PATCH 04/11] Interleave constraint, factory constraint enable interleave option --- factory/constraint_interleave.go | 111 ++++++ factory/factory.go | 4 + factory/model.go | 3 +- model_constraint_interleave.go | 205 ++++++---- model_constraint_interleave_test.go | 570 +++++++++++++++++++++++++++- 5 files changed, 808 insertions(+), 85 deletions(-) create mode 100644 factory/constraint_interleave.go diff --git a/factory/constraint_interleave.go b/factory/constraint_interleave.go new file mode 100644 index 0000000..763ace9 --- /dev/null +++ b/factory/constraint_interleave.go @@ -0,0 +1,111 @@ +package factory + +import ( + "fmt" + "github.com/nextmv-io/nextroute" + "github.com/nextmv-io/nextroute/schema" +) + +// addInterleaveConstraint adds a constraint which limits stop groups to be +// interleaved. +func addInterleaveConstraint( + input schema.Input, + model nextroute.Model, + options Options, +) (nextroute.Model, error) { + if input.StopGroups == nil || options.Constraints.Disable.Groups { + return model, nil + } + + data, err := getModelData(model) + if err != nil { + return nil, err + } + + planUnits := make(nextroute.ModelPlanUnits, 0, len(*input.StopGroups)) + for idx, stops := range *input.StopGroups { + if len(stops) == 0 { + return nil, fmt.Errorf("stop group %v is empty", idx) + } + stop := stops[0] + modelStop, err := model.Stop(data.stopIDToIndex[stop]) + if err != nil { + return nil, err + } + if !modelStop.HasPlanStopsUnit() { + return nil, fmt.Errorf("stop %s does not have a plan-stops unit", stop) + } + planStopsUnit := modelStop.PlanStopsUnit() + if planUnitsUnit, hasPlanUnitsUnit := planStopsUnit.PlanUnitsUnit(); hasPlanUnitsUnit { + if numberOfStops(planUnitsUnit) != len(stops) { + return nil, + fmt.Errorf( + "stop group %v starting with stop %v is not a plan unit with %v stops,"+ + " but has %v stops instead, probably because there are inter stop group precedence"+ + " relationships which are not allowed with interleave constraint", + idx, + stop, + len(stops), + numberOfStops(planUnitsUnit), + ) + } + planUnits = append(planUnits, planUnitsUnit) + } else { + if numberOfStops(planStopsUnit) != len(stops) { + return nil, + fmt.Errorf( + "stop group %v starting with stop %v is not a plan unit with %v stops,"+ + " but has %v stops instead, probably because there are inter stop group precedence"+ + " relationships which are not allowed with interleave constraint", + idx, + stop, + len(stops), + numberOfStops(planStopsUnit), + ) + } + planUnits = append(planUnits, planStopsUnit) + } + } + + if len(planUnits) <= 1 { + return model, nil + } + + interleaveConstraint, err := nextroute.NewInterleaveConstraint() + if err != nil { + return model, err + } + + for i, p1 := range planUnits { + inputPlanUnits := make(nextroute.ModelPlanUnits, 0, len(planUnits)-1) + for j, p2 := range planUnits { + if i != j { + inputPlanUnits = append(inputPlanUnits, p2) + } + } + err = interleaveConstraint.DisallowInterleaving(p1, inputPlanUnits) + if err != nil { + return model, err + } + } + + err = model.AddConstraint(interleaveConstraint) + if err != nil { + return model, err + } + + return model, nil +} + +func numberOfStops(planUnit nextroute.ModelPlanUnit) int { + if planStopsUnit, isPlanStopsUnit := planUnit.(nextroute.ModelPlanStopsUnit); isPlanStopsUnit { + return len(planStopsUnit.Stops()) + } + count := 0 + if planUnitsUnit, isPlanUnitsUnit := planUnit.PlanUnitsUnit(); isPlanUnitsUnit { + for _, unit := range planUnitsUnit.PlanUnits() { + count += numberOfStops(unit) + } + } + return count +} diff --git a/factory/factory.go b/factory/factory.go index 20f5667..9dab27b 100644 --- a/factory/factory.go +++ b/factory/factory.go @@ -48,6 +48,10 @@ func getModifiersFromOptions(options Options) []modelModifier { modifiers = append(modifiers, addInitialSolution) } + if options.Constraints.Enable.Interleave { + modifiers = append(modifiers, addInterleaveConstraint) + } + return modifiers } diff --git a/factory/model.go b/factory/model.go index 9935061..c1cacca 100644 --- a/factory/model.go +++ b/factory/model.go @@ -24,7 +24,8 @@ type Options struct { StartTimeWindows bool `json:"start_time_windows" usage:"ignore the start time windows constraint"` } `json:"disable"` Enable struct { - Cluster bool `json:"cluster" usage:"enable the cluster constraint"` + Cluster bool `json:"cluster" usage:"enable the cluster constraint"` + Interleave bool `json:"interleave" usage:"enable no overlapping of stop groups"` } `json:"enable"` } `json:"constraints"` Objectives struct { diff --git a/model_constraint_interleave.go b/model_constraint_interleave.go index eeeb467..a4e590e 100644 --- a/model_constraint_interleave.go +++ b/model_constraint_interleave.go @@ -14,6 +14,7 @@ type InterleaveConstraint interface { // DisallowInterleaving disallows the given planUnits to be interleaved. DisallowInterleaving(source ModelPlanUnit, targets []ModelPlanUnit) error + // DisallowedInterleaves returns the disallowed interleaves. DisallowedInterleaves() []DisallowedInterleave // SourceDisallowedInterleaves returns the disallowed interleaves for the @@ -79,7 +80,9 @@ type interleaveConstraintImpl struct { targetDisallowedInterleaves map[ModelPlanUnit][]DisallowedInterleave } -func (l *interleaveConstraintImpl) SourceDisallowedInterleaves(source ModelPlanUnit) []DisallowedInterleave { +func (l *interleaveConstraintImpl) SourceDisallowedInterleaves( + source ModelPlanUnit, +) []DisallowedInterleave { if l.sourceDisallowedInterleaves != nil { if disallowedInterleaves, ok := l.sourceDisallowedInterleaves[source]; ok { return disallowedInterleaves @@ -98,7 +101,9 @@ func (l *interleaveConstraintImpl) SourceDisallowedInterleaves(source ModelPlanU return found } -func (l *interleaveConstraintImpl) TargetDisallowedInterleaves(target ModelPlanUnit) []DisallowedInterleave { +func (l *interleaveConstraintImpl) TargetDisallowedInterleaves( + target ModelPlanUnit, +) []DisallowedInterleave { if l.targetDisallowedInterleaves != nil { if disallowedInterleaves, ok := l.targetDisallowedInterleaves[target]; ok { return disallowedInterleaves @@ -119,7 +124,11 @@ func (l *interleaveConstraintImpl) DisallowedInterleaves() []DisallowedInterleav return l.disallowedInterleaves } -func addToMap(planUnit ModelPlanUnit, mapUnit map[ModelPlanUnit][]DisallowedInterleave, disallowedInterleave DisallowedInterleave) { +func addToMap( + planUnit ModelPlanUnit, + mapUnit map[ModelPlanUnit][]DisallowedInterleave, + disallowedInterleave DisallowedInterleave, +) { if modelPlanUnitsUnit, ok := planUnit.(ModelPlanUnitsUnit); ok { for _, pu := range modelPlanUnitsUnit.PlanUnits() { addToMap(pu, mapUnit, disallowedInterleave) @@ -169,26 +178,34 @@ func verifyPlanUnitAllOnSameVehicle(planUnit ModelPlanUnit, preFix string) error func (l *interleaveConstraintImpl) DisallowInterleaving(target ModelPlanUnit, sources []ModelPlanUnit) error { if target == nil { - return fmt.Errorf("source cannot be nil") - } - - // TODO: cover all cases - if modelPlanStopsUnit, ok := target.(ModelPlanStopsUnit); ok { - if modelPlanStopsUnit.Stops()[0].Model().IsLocked() { - return fmt.Errorf(lockErrorMessage, "DisallowInterleaving") - } + return fmt.Errorf("target cannot be nil") } if sources == nil { return fmt.Errorf("sources cannot be nil") } + if len(sources) == 0 { return nil } + + if _, hasPlanUnitsUnit := target.PlanUnitsUnit(); hasPlanUnitsUnit { + return fmt.Errorf("target cannot be a plan unit part of a PlanUnitsUnit") + } + + if modelPlanStopsUnit, ok := target.(ModelPlanStopsUnit); ok { + if modelPlanStopsUnit.Stops()[0].Model().IsLocked() { + return fmt.Errorf(lockErrorMessage, "DisallowInterleaving") + } + } + for idx, source := range sources { if source == nil { return fmt.Errorf("source[%v] cannot be nil", idx) } + if _, hasPlanUnitsUnit := source.PlanUnitsUnit(); hasPlanUnitsUnit { + return fmt.Errorf("source at index %v cannot be a plan unit part of a PlanUnitsUnit", idx) + } if source == target { return fmt.Errorf("target is also in a source") } @@ -243,50 +260,54 @@ func (l *interleaveConstraintImpl) EstimationCost() Cost { return LinearStop } -//func nameMove(move Move) string { -// result := "" -// for idx, stopPosition := range move.StopPositions() { -// if idx > 0 { -// result += " .. " -// } -// result += stopPosition.Previous().ModelStop().ID() + " - [" -// result += stopPosition.Stop().ModelStop().ID() + "] - " -// result += stopPosition.Next().ModelStop().ID() -// } -// return result -//} -// -//func namePlanStopsUnit(planStopsUnit ModelPlanStopsUnit) string { -// result := "[" -// for idx, stop := range planStopsUnit.Stops() { -// if idx > 0 { -// result += ", " -// } -// result += stop.ID() -// } -// result += "]" -// return result -//} +func isViolatedPositions(sourceFirstPosition, sourceLastPosition, targetFirstPosition, targetLastPosition int) bool { + // S===S + // T=========T + if sourceFirstPosition > targetFirstPosition && + sourceLastPosition < targetLastPosition { + return true + } + // S=====S + // T=========T + if sourceFirstPosition < targetFirstPosition && + sourceLastPosition > targetFirstPosition && + sourceLastPosition < targetLastPosition { + return true + } + // S=====S + // T=========T + if sourceFirstPosition > targetFirstPosition && + sourceFirstPosition < targetLastPosition && + sourceLastPosition > targetLastPosition { + return true + } + + return false +} func (l *interleaveConstraintImpl) EstimateIsViolated( move SolutionMoveStops, ) (isViolated bool, stopPositionsHint StopPositionsHint) { solution := move.Solution() - //fmt.Println("\U0001FAE3 Move: ", - // nameMove(move), - // "for unit", - // namePlanStopsUnit(move.PlanStopsUnit().ModelPlanUnit().(ModelPlanStopsUnit)), - //) - - // FirstPlanUnit and LastPlanUnit are the first and last stops of the plan unit of - // the move. In case the move planUnit is a part of a PlanUnitsUnit, the first and last - // stops should include the already planned stops of the other plan units of the PlanUnitsUnit. - firstPlanUnit := move.Previous() - lastPlanUnit := move.Next() - - if modelPlanUnitsUnit, hasModelPlanUnitsUnit := move.PlanStopsUnit().ModelPlanUnit().PlanUnitsUnit(); hasModelPlanUnitsUnit { - // fmt.Println(" ☐ Move is for a plan unit that is part of a PlanUnitsUnit") - // first and last stop of the already planned plan-units of PlanUnitsUnit + + solutionMoveStops := move.(*solutionMoveStopsImpl) + + generator := newSolutionStopGenerator(*solutionMoveStops, true, true) + defer generator.release() + + newPositions := make(map[SolutionStop]int) + + position := 0 + for solutionStop, ok := generator.next(); ok; solutionStop, ok = generator.next() { + newPositions[solutionStop] = position + position += 1 + } + + newPlanUnitSpanFirstPosition := move.Previous().Position() + 1 + newPlanUnitSpanLastPosition := move.Next().Position() + len(move.PlanStopsUnit().SolutionStops()) - 1 + + if modelPlanUnitsUnit, hasModelPlanUnitsUnit := + move.PlanStopsUnit().ModelPlanUnit().PlanUnitsUnit(); hasModelPlanUnitsUnit { var first, last SolutionStop for _, planUnit := range modelPlanUnitsUnit.PlanUnits() { if planUnit.Index() == move.PlanStopsUnit().ModelPlanUnit().Index() { @@ -298,63 +319,85 @@ func (l *interleaveConstraintImpl) EstimateIsViolated( if solutionPlanStopsUnit.SolutionStops()[0].Vehicle() != move.Vehicle() { continue } - // fmt.Println(" ☐ Getting first and last of planned unit", - // namePlanStopsUnit(solutionPlanStopsUnit.ModelPlanStopsUnit()), - // ) + first, last = determineFirstLastSolutionStops(first, last, solutionPlanStopsUnit) - if first != nil && - move.Previous().Position() >= first.Position() && - move.Next().Position() <= last.Position() { - // fmt.Println(" ✅ Move is in the middle of already planned plan units of PlanUnitsUnit") - return false, noPositionsHint() + if first == nil { + continue } } } } - if first != nil && first.Position() <= firstPlanUnit.Position() { - firstPlanUnit = first + if first != nil && newPositions[first] <= newPlanUnitSpanFirstPosition { + newPlanUnitSpanFirstPosition = newPositions[first] } - if last != nil && last.Position() >= lastPlanUnit.Position() { - lastPlanUnit = last + if last != nil && newPositions[last] >= newPlanUnitSpanLastPosition { + newPlanUnitSpanLastPosition = newPositions[last] } } - // check if the target is disallowed to be interleaved with the source - if targetDisallowedInterleaves, isTargetPlanUnit := l.targetDisallowedInterleaves[move.PlanStopsUnit().ModelPlanUnit()]; isTargetPlanUnit { - // fmt.Println(" ☐ Move is for a plan unit that is a target for a source") - var first, last SolutionStop - + // Check if the plan unit we are moving is a target + if targetDisallowedInterleaves, isTargetPlanUnit := + l.targetDisallowedInterleaves[move.PlanStopsUnit().ModelPlanUnit()]; isTargetPlanUnit { for _, disallowedInterleave := range targetDisallowedInterleaves { for _, sourcePlanUnit := range disallowedInterleave.Sources() { sourceSolutionPlanUnit := move.Solution().SolutionPlanUnit(sourcePlanUnit) if sourceSolutionPlanUnit.IsPlanned() { + var sourceSpanFirst, sourceSpanLast SolutionStop solutionPlanStopsUnits := sourceSolutionPlanUnit.PlannedPlanStopsUnits() for _, solutionPlanStopsUnit := range solutionPlanStopsUnits { if solutionPlanStopsUnit.SolutionStops()[0].Vehicle() != move.Vehicle() { continue } - // fmt.Println(" ☐ Planned source", - // namePlanStopsUnit(solutionPlanStopsUnit.ModelPlanStopsUnit()), - // "can not interleave with target", - // namePlanStopsUnit(move.PlanStopsUnit().ModelPlanUnit().(ModelPlanStopsUnit)), - // ) - first, last = determineFirstLastSolutionStops(first, last, solutionPlanStopsUnit) + sourceSpanFirst, sourceSpanLast = determineFirstLastSolutionStops( + sourceSpanFirst, + sourceSpanLast, + solutionPlanStopsUnit, + ) + newSourceSpanFirstPosition := newPositions[sourceSpanFirst] + newSourceSpanLastPosition := newPositions[sourceSpanLast] + + if isViolatedPositions( + newSourceSpanFirstPosition, + newSourceSpanLastPosition, + newPlanUnitSpanFirstPosition, + newPlanUnitSpanLastPosition, + ) { + return true, noPositionsHint() + } } } } } + } - if first != nil { - if lastPlanUnit.Position() <= first.Position() || firstPlanUnit.Position() >= last.Position() { - // fmt.Println(" ✅ Sources do not overlap with to be planned target") - return false, noPositionsHint() + // check if plan unit is a source + if sourceDisallowedInterleaves, isSourcePlanUnit := + l.sourceDisallowedInterleaves[move.PlanStopsUnit().ModelPlanUnit()]; isSourcePlanUnit { + for _, disallowedInterleave := range sourceDisallowedInterleaves { + targetSolutionPlanUnit := solution.SolutionPlanUnit(disallowedInterleave.Target()) + if targetSolutionPlanUnit.IsPlanned() { + var targetSpanFirst, targetSpanLast SolutionStop + for _, plannedSolutionStops := range targetSolutionPlanUnit.PlannedPlanStopsUnits() { + if plannedSolutionStops.SolutionStops()[0].Vehicle() != move.Vehicle() { + continue + } + targetSpanFirst, targetSpanLast = determineFirstLastSolutionStops( + targetSpanFirst, + targetSpanLast, + plannedSolutionStops, + ) + if isViolatedPositions( + newPlanUnitSpanFirstPosition, + newPlanUnitSpanLastPosition, + newPositions[targetSpanFirst], + newPositions[targetSpanLast], + ) { + return true, noPositionsHint() + } + } } } - - // fmt.Println(" ❌ A source would be interleaved with to be planned target") - return true, noPositionsHint() } - // fmt.Println("✅ No violations for move") return false, noPositionsHint() } diff --git a/model_constraint_interleave_test.go b/model_constraint_interleave_test.go index 79e14bc..d6e8d62 100644 --- a/model_constraint_interleave_test.go +++ b/model_constraint_interleave_test.go @@ -117,8 +117,428 @@ func TestNewInterleaveConstraint(t *testing.T) { } } +func TestInterleaveConstraint0(t *testing.T) { + model, planUnits, modelStops := createModel1(t, false) + xPlanUnit := planUnits[0] + aPlanUnit := xPlanUnit.(nextroute.ModelPlanUnitsUnit).PlanUnits()[0] + //bPlanUnit := xPlanUnit.(nextroute.ModelPlanUnitsUnit).PlanUnits()[1] + + yPlanUnit := planUnits[1] + cPlanUnit := yPlanUnit.(nextroute.ModelPlanUnitsUnit).PlanUnits()[0] + dPlanUnit := yPlanUnit.(nextroute.ModelPlanUnitsUnit).PlanUnits()[1] + + //zPlanUnit := planUnits[2] + //ePlanUnit := zPlanUnit.(nextroute.ModelPlanUnitsUnit).PlanUnits()[0] + //fPlanUnit := zPlanUnit.(nextroute.ModelPlanUnitsUnit).PlanUnits()[1] + + a := modelStops[0] + //b := modelStops[1] + c := modelStops[2] + d := modelStops[3] + //e := modelStops[4] + //f := modelStops[5] + + solution, err := nextroute.NewSolution(model) + if err != nil { + t.Error(err) + } + + solutionVehicle := solution.Vehicles()[0] + + aSolutionStop := solution.SolutionStop(a) + //bSolutionStop := solution.SolutionStop(b) + cSolutionStop := solution.SolutionStop(c) + dSolutionStop := solution.SolutionStop(d) + //eSolutionStop := solution.SolutionStop(e) + //fSolutionStop := solution.SolutionStop(f) + + // xSolutionPlanUnit := solution.SolutionPlanUnit(xPlanUnit) + aSolutionPlanUnit := solution.SolutionPlanUnit(aPlanUnit).(nextroute.SolutionPlanStopsUnit) + //bSolutionPlanUnit := solution.SolutionPlanUnit(bPlanUnit).(nextroute.SolutionPlanStopsUnit) + + //ySolutionPlanUnit := solution.SolutionPlanUnit(yPlanUnit) + cSolutionPlanUnit := solution.SolutionPlanUnit(cPlanUnit).(nextroute.SolutionPlanStopsUnit) + dSolutionPlanUnit := solution.SolutionPlanUnit(dPlanUnit).(nextroute.SolutionPlanStopsUnit) + + //zSolutionPlanUnit := solution.SolutionPlanUnit(yPlanUnit) + //eSolutionPlanUnit := solution.SolutionPlanUnit(ePlanUnit).(nextroute.SolutionPlanStopsUnit) + //fSolutionPlanUnit := solution.SolutionPlanUnit(fPlanUnit).(nextroute.SolutionPlanStopsUnit) + + // F - c - L + stopPositionc, err := nextroute.NewStopPosition( + solutionVehicle.First(), + cSolutionStop, + solutionVehicle.Last(), + ) + if err != nil { + t.Fatal(err) + } + + move, err := nextroute.NewMoveStops( + cSolutionPlanUnit, + nextroute.StopPositions{stopPositionc}, + ) + if err != nil { + t.Fatal(err) + } + + if !move.IsExecutable() { + t.Fatal("expected move to be executable") + } + + planned, err := move.Execute(context.Background()) + if err != nil { + t.Fatal(err) + } + if !planned { + t.Fatal("expected move to be planned") + } + + // F - c - d - L + stopPositiond, err := nextroute.NewStopPosition( + cSolutionStop, + dSolutionStop, + solutionVehicle.Last(), + ) + if err != nil { + t.Fatal(err) + } + + move, err = nextroute.NewMoveStops( + dSolutionPlanUnit, + nextroute.StopPositions{stopPositiond}, + ) + if err != nil { + t.Fatal(err) + } + + if !move.IsExecutable() { + t.Fatal("expected move to be executable") + } + + planned, err = move.Execute(context.Background()) + if err != nil { + t.Fatal(err) + } + if !planned { + t.Fatal("expected move to be planned") + } + + // F - c - a - d - L + stopPositiona, err := nextroute.NewStopPosition( + cSolutionStop, + aSolutionStop, + dSolutionStop, + ) + if err != nil { + t.Fatal(err) + } + + move, err = nextroute.NewMoveStops( + aSolutionPlanUnit, + nextroute.StopPositions{stopPositiona}, + ) + if err != nil { + t.Fatal(err) + } + + if !move.IsExecutable() { + t.Fatal("expected move to be executable") + } + + planned, err = move.Execute(context.Background()) + if err != nil { + t.Fatal(err) + } + if !planned { + t.Fatal("expected move to be planned") + } +} + func TestInterleaveConstraint1(t *testing.T) { - model, planUnits, modelStops := createInterleaveModel(t) + model, planUnits, modelStops := createModel1(t, true) + + xPlanUnit := planUnits[0] + aPlanUnit := xPlanUnit.(nextroute.ModelPlanUnitsUnit).PlanUnits()[0] + bPlanUnit := xPlanUnit.(nextroute.ModelPlanUnitsUnit).PlanUnits()[1] + + yPlanUnit := planUnits[1] + cPlanUnit := yPlanUnit.(nextroute.ModelPlanUnitsUnit).PlanUnits()[0] + dPlanUnit := yPlanUnit.(nextroute.ModelPlanUnitsUnit).PlanUnits()[1] + + zPlanUnit := planUnits[2] + ePlanUnit := zPlanUnit.(nextroute.ModelPlanUnitsUnit).PlanUnits()[0] + fPlanUnit := zPlanUnit.(nextroute.ModelPlanUnitsUnit).PlanUnits()[1] + + a := modelStops[0] + b := modelStops[1] + c := modelStops[2] + d := modelStops[3] + e := modelStops[4] + f := modelStops[5] + + solution, err := nextroute.NewSolution(model) + if err != nil { + t.Error(err) + } + + solutionVehicle := solution.Vehicles()[0] + + aSolutionStop := solution.SolutionStop(a) + bSolutionStop := solution.SolutionStop(b) + cSolutionStop := solution.SolutionStop(c) + dSolutionStop := solution.SolutionStop(d) + eSolutionStop := solution.SolutionStop(e) + fSolutionStop := solution.SolutionStop(f) + + // xSolutionPlanUnit := solution.SolutionPlanUnit(xPlanUnit) + aSolutionPlanUnit := solution.SolutionPlanUnit(aPlanUnit).(nextroute.SolutionPlanStopsUnit) + bSolutionPlanUnit := solution.SolutionPlanUnit(bPlanUnit).(nextroute.SolutionPlanStopsUnit) + + //ySolutionPlanUnit := solution.SolutionPlanUnit(yPlanUnit) + cSolutionPlanUnit := solution.SolutionPlanUnit(cPlanUnit).(nextroute.SolutionPlanStopsUnit) + dSolutionPlanUnit := solution.SolutionPlanUnit(dPlanUnit).(nextroute.SolutionPlanStopsUnit) + + //zSolutionPlanUnit := solution.SolutionPlanUnit(yPlanUnit) + eSolutionPlanUnit := solution.SolutionPlanUnit(ePlanUnit).(nextroute.SolutionPlanStopsUnit) + fSolutionPlanUnit := solution.SolutionPlanUnit(fPlanUnit).(nextroute.SolutionPlanStopsUnit) + + // F - a - L + stopPositiona, err := nextroute.NewStopPosition( + solutionVehicle.First(), + aSolutionStop, + solutionVehicle.Last(), + ) + if err != nil { + t.Fatal(err) + } + + move, err := nextroute.NewMoveStops( + aSolutionPlanUnit, + nextroute.StopPositions{stopPositiona}, + ) + if err != nil { + t.Fatal(err) + } + + if !move.IsExecutable() { + t.Fatal("expected move to be executable") + } + + planned, err := move.Execute(context.Background()) + if err != nil { + t.Fatal(err) + } + if !planned { + t.Fatal("expected move to be planned") + } + + // F - a - b - L + stopPositionb, err := nextroute.NewStopPosition( + aSolutionStop, + bSolutionStop, + solutionVehicle.Last(), + ) + if err != nil { + t.Fatal(err) + } + + move, err = nextroute.NewMoveStops( + bSolutionPlanUnit, + nextroute.StopPositions{stopPositionb}, + ) + if err != nil { + t.Fatal(err) + } + + if !move.IsExecutable() { + t.Fatal("expected move to be executable") + } + + planned, err = move.Execute(context.Background()) + if err != nil { + t.Fatal(err) + } + if !planned { + t.Fatal("expected move to be planned") + } + + // F - a - b - c - L + stopPositionc, err := nextroute.NewStopPosition( + bSolutionStop, + cSolutionStop, + solutionVehicle.Last(), + ) + if err != nil { + t.Fatal(err) + } + + move, err = nextroute.NewMoveStops( + cSolutionPlanUnit, + nextroute.StopPositions{stopPositionc}, + ) + if err != nil { + t.Fatal(err) + } + + if !move.IsExecutable() { + t.Fatal("expected move to be executable") + } + + planned, err = move.Execute(context.Background()) + if err != nil { + t.Fatal(err) + } + if !planned { + t.Fatal("expected move to be planned") + } + + // F - a - b - c - d - L + stopPositiond, err := nextroute.NewStopPosition( + cSolutionStop, + dSolutionStop, + solutionVehicle.Last(), + ) + if err != nil { + t.Fatal(err) + } + + move, err = nextroute.NewMoveStops( + dSolutionPlanUnit, + nextroute.StopPositions{stopPositiond}, + ) + if err != nil { + t.Fatal(err) + } + + if !move.IsExecutable() { + t.Fatal("expected move to be executable") + } + + planned, err = move.Execute(context.Background()) + if err != nil { + t.Fatal(err) + } + if !planned { + t.Fatal("expected move to be planned") + } + + // F - e - a - b - c - d - L allowed + stopPositione, err := nextroute.NewStopPosition( + solutionVehicle.First(), + eSolutionStop, + aSolutionStop, + ) + if err != nil { + t.Fatal(err) + } + + move, err = nextroute.NewMoveStops( + eSolutionPlanUnit, + nextroute.StopPositions{stopPositione}, + ) + if err != nil { + t.Fatal(err) + } + + if !move.IsExecutable() { + t.Fatal("expected move to be executable") + } + + // F - a - e - b - c - d - L not allowed + stopPositione, err = nextroute.NewStopPosition( + aSolutionStop, + eSolutionStop, + bSolutionStop, + ) + if err != nil { + t.Fatal(err) + } + + move, err = nextroute.NewMoveStops( + eSolutionPlanUnit, + nextroute.StopPositions{stopPositione}, + ) + if err != nil { + t.Fatal(err) + } + + if move.IsExecutable() { + t.Fatal("expected move not to be executable") + } + + // F - a - b - e - c - d - L allowed + stopPositione, err = nextroute.NewStopPosition( + bSolutionStop, + eSolutionStop, + cSolutionStop, + ) + if err != nil { + t.Fatal(err) + } + + move, err = nextroute.NewMoveStops( + eSolutionPlanUnit, + nextroute.StopPositions{stopPositione}, + ) + if err != nil { + t.Fatal(err) + } + + if !move.IsExecutable() { + t.Fatal("expected move to be executable") + } + + // F - a - b - c - e - d - L not allowed + stopPositione, err = nextroute.NewStopPosition( + cSolutionStop, + eSolutionStop, + dSolutionStop, + ) + if err != nil { + t.Fatal(err) + } + + move, err = nextroute.NewMoveStops( + eSolutionPlanUnit, + nextroute.StopPositions{stopPositione}, + ) + if err != nil { + t.Fatal(err) + } + + if move.IsExecutable() { + t.Fatal("expected move to be not executable") + } + + // F - a - b - c - d - e - L allowed + stopPositione, err = nextroute.NewStopPosition( + dSolutionStop, + eSolutionStop, + solutionVehicle.Last(), + ) + if err != nil { + t.Fatal(err) + } + + move, err = nextroute.NewMoveStops( + eSolutionPlanUnit, + nextroute.StopPositions{stopPositione}, + ) + if err != nil { + t.Fatal(err) + } + + if !move.IsExecutable() { + t.Fatal("expected move to be executable") + } + + _ = fSolutionStop + _ = fSolutionPlanUnit +} + +func TestInterleaveConstraint2(t *testing.T) { + model, planUnits, modelStops := createModel2(t) xPlanUnit := planUnits[0] yPlanUnit := planUnits[1] iPlanUnit := planUnits[2] @@ -697,7 +1117,7 @@ func TestInterleaveConstraint1(t *testing.T) { } } -// createInterleaveModel creates a model with 10 stops, 4 plan units, 2 vehicles. +// createModel2 creates a model with 10 stops, 4 plan units, 2 vehicles. // Creates 10 stops A, B, C, D, E, F, G, H, I, J // Creates 4 plan units: // 1. Plan units unit x consisting out of 3 plan stops units: @@ -717,7 +1137,11 @@ func TestInterleaveConstraint1(t *testing.T) { // - Disallows interleaving of plan unit x and j with plan unit y // Adds the interleave constraint to the model // Returns the model, plan units [x,y, i, j], and stops [A, B, C, D, E, F, G, H, I, J] -func createInterleaveModel(t *testing.T) (nextroute.Model, nextroute.ModelPlanUnits, nextroute.ModelStops) { +func createModel2(t *testing.T) ( + nextroute.Model, + nextroute.ModelPlanUnits, + nextroute.ModelStops, +) { model, err := nextroute.NewModel() if err != nil { t.Fatal(err) @@ -888,3 +1312,143 @@ func createInterleaveModel(t *testing.T) (nextroute.Model, nextroute.ModelPlanUn nextroute.ModelPlanUnits{xPlanUnit, yPlanUnit, iPlanUnit, jPlanUnit}, nextroute.ModelStops{a, b, c, d, e, f, g, h, i, j} } + +func createModel1(t *testing.T, postAll bool) ( + nextroute.Model, + nextroute.ModelPlanUnits, + nextroute.ModelStops, +) { + model, err := nextroute.NewModel() + if err != nil { + t.Fatal(err) + } + center, err := common.NewLocation(0, 0) + if err != nil { + t.Fatal(err) + } + a, err := model.NewStop(center) + a.SetID("a") + if err != nil { + t.Fatal(err) + } + b, err := model.NewStop(center) + b.SetID("b") + if err != nil { + t.Fatal(err) + } + c, err := model.NewStop(center) + c.SetID("c") + if err != nil { + t.Fatal(err) + } + d, err := model.NewStop(center) + d.SetID("d") + if err != nil { + t.Fatal(err) + } + e, err := model.NewStop(center) + e.SetID("e") + if err != nil { + t.Fatal(err) + } + f, err := model.NewStop(center) + f.SetID("f") + if err != nil { + t.Fatal(err) + } + + a_pu, err := model.NewPlanSingleStop(a) + if err != nil { + t.Fatal(err) + } + b_pu, err := model.NewPlanSingleStop(b) + if err != nil { + t.Fatal(err) + } + xPlanUnit, err := model.NewPlanAllPlanUnits(true, a_pu, b_pu) + if err != nil { + t.Fatal(err) + } + c_pu, err := model.NewPlanSingleStop(c) + if err != nil { + t.Fatal(err) + } + d_pu, err := model.NewPlanSingleStop(d) + if err != nil { + t.Fatal(err) + } + yPlanUnit, err := model.NewPlanAllPlanUnits(true, c_pu, d_pu) + if err != nil { + t.Fatal(err) + } + e_pu, err := model.NewPlanSingleStop(e) + if err != nil { + t.Fatal(err) + } + f_pu, err := model.NewPlanSingleStop(f) + if err != nil { + t.Fatal(err) + } + zPlanUnit, err := model.NewPlanAllPlanUnits(true, e_pu, f_pu) + if err != nil { + t.Fatal(err) + } + interleaveConstraint, err := nextroute.NewInterleaveConstraint() + if err != nil { + t.Fatal(err) + } + err = interleaveConstraint.DisallowInterleaving(xPlanUnit, []nextroute.ModelPlanUnit{yPlanUnit, zPlanUnit}) + if err != nil { + t.Fatal(err) + } + + if postAll { + err = interleaveConstraint.DisallowInterleaving(yPlanUnit, []nextroute.ModelPlanUnit{xPlanUnit, zPlanUnit}) + if err != nil { + t.Fatal(err) + } + err = interleaveConstraint.DisallowInterleaving(zPlanUnit, []nextroute.ModelPlanUnit{xPlanUnit, yPlanUnit}) + if err != nil { + t.Fatal(err) + } + } + err = model.AddConstraint(interleaveConstraint) + if err != nil { + t.Fatal(err) + } + + vt, err := model.NewVehicleType( + nextroute.NewTimeIndependentDurationExpression( + nextroute.NewTravelDurationExpression( + nextroute.NewHaversineExpression(), + common.NewSpeed( + 10.0, + common.MetersPerSecond, + ), + ), + ), + nextroute.NewDurationExpression( + "travelDuration", + nextroute.NewStopDurationExpression("serviceDuration", 0.0), + common.Second, + ), + ) + if err != nil { + t.Fatal(err) + } + warehouse, err := model.NewStop(center) + if err != nil { + t.Fatal(err) + } + warehouse.SetID("warehouse") + + v, err := model.NewVehicle(vt, model.Epoch(), warehouse, warehouse) + v.SetID("v1") + if err != nil { + t.Fatal(err) + } + + return model, + nextroute.ModelPlanUnits{xPlanUnit, yPlanUnit, zPlanUnit}, + nextroute.ModelStops{a, b, c, d, e, f} +} From 3dfd45fd0fdd6188ea95fb6703926957a4027401 Mon Sep 17 00:00:00 2001 From: David Rijsman Date: Tue, 7 May 2024 08:25:41 +0200 Subject: [PATCH 05/11] Interleave constraint, buffer planned units --- model_constraint_interleave.go | 134 +++++++++++++++++++++++++++------ 1 file changed, 111 insertions(+), 23 deletions(-) diff --git a/model_constraint_interleave.go b/model_constraint_interleave.go index a4e590e..1b115fa 100644 --- a/model_constraint_interleave.go +++ b/model_constraint_interleave.go @@ -5,6 +5,7 @@ package nextroute import ( "fmt" "github.com/nextmv-io/nextroute/common" + "math" ) // InterleaveConstraint is a constraint that disallows certain target to be @@ -24,6 +25,10 @@ type InterleaveConstraint interface { // TargetDisallowedInterleaves returns the disallowed interleaves for the // given target. TargetDisallowedInterleaves(target ModelPlanUnit) []DisallowedInterleave + + // UpdateConstraintStopData is called when a stop has been added to a + // solution for each stop in a vehicle after the added stop. + UpdateConstraintStopData(s SolutionStop) (Copier, error) } // NewInterleaveConstraint returns a new InterleaveConstraint. @@ -80,6 +85,87 @@ type interleaveConstraintImpl struct { targetDisallowedInterleaves map[ModelPlanUnit][]DisallowedInterleave } +type interleaveSolutionStopSpan struct { + first int + last int +} + +type interleaveConstraintData struct { + solutionPlanStopUnits map[int]interleaveSolutionStopSpan +} + +func (i *interleaveConstraintData) Copy() Copier { + solutionPlanStopUnits := make(map[int]interleaveSolutionStopSpan, len(i.solutionPlanStopUnits)) + + for k := range i.solutionPlanStopUnits { + solutionPlanStopUnits[k] = interleaveSolutionStopSpan{ + first: i.solutionPlanStopUnits[k].first, + last: i.solutionPlanStopUnits[k].last, + } + } + + return &interleaveConstraintData{ + solutionPlanStopUnits: solutionPlanStopUnits, + } +} + +func (l *interleaveConstraintImpl) UpdateConstraintStopData(s SolutionStop) (Copier, error) { + if s.IsLast() { + data := interleaveConstraintData{ + solutionPlanStopUnits: make(map[int]interleaveSolutionStopSpan), + } + + modelPlanStopsUnits := map[SolutionPlanStopsUnit]struct{}{} + stop := s.Vehicle().First().Next() + for !stop.IsLast() { + + planStopsUnit := stop.PlanStopsUnit() + + stop = stop.Next() + + if _, ok := modelPlanStopsUnits[planStopsUnit]; ok { + continue + } + + if modelPlanUnitsUnit, ok := planStopsUnit.ModelPlanStopsUnit().PlanUnitsUnit(); ok { + modelPlanStopsUnits[planStopsUnit] = struct{}{} + + planUnitsUnitFirst := math.MaxInt64 + planUnitsUnitLast := math.MinInt64 + + for _, planUnit := range modelPlanUnitsUnit.PlanUnits() { + if modelPlanStopsUnit, ok := planUnit.(ModelPlanStopsUnit); ok { + solutionPlanStopsUnit := s.Solution().SolutionPlanStopsUnit(modelPlanStopsUnit).(SolutionPlanStopsUnit) + if solutionPlanStopsUnit.IsPlanned() { + first := solutionPlanStopsUnit.SolutionStops()[0].Position() + last := solutionPlanStopsUnit.SolutionStops()[len(solutionPlanStopsUnit.SolutionStops())-1].Position() + + data.solutionPlanStopUnits[modelPlanStopsUnit.Index()] = interleaveSolutionStopSpan{ + first: first, + last: last, + } + + if first < planUnitsUnitFirst { + planUnitsUnitFirst = first + } + if last > planUnitsUnitLast { + planUnitsUnitLast = last + } + } + } + } + data.solutionPlanStopUnits[modelPlanUnitsUnit.Index()] = interleaveSolutionStopSpan{ + first: planUnitsUnitFirst, + last: planUnitsUnitLast, + } + } + + } + return &data, nil + } + return nil, nil +} + func (l *interleaveConstraintImpl) SourceDisallowedInterleaves( source ModelPlanUnit, ) []DisallowedInterleave { @@ -290,49 +376,47 @@ func (l *interleaveConstraintImpl) EstimateIsViolated( ) (isViolated bool, stopPositionsHint StopPositionsHint) { solution := move.Solution() + moveImpl := move.(*solutionMoveStopsImpl) + vehicle := moveImpl.vehicle() + data := vehicle.last().ConstraintData(l).(*interleaveConstraintData) + solutionMoveStops := move.(*solutionMoveStopsImpl) generator := newSolutionStopGenerator(*solutionMoveStops, true, true) defer generator.release() newPositions := make(map[SolutionStop]int) + oldPositions := make([]SolutionStop, 0, vehicle.NumberOfStops()+2) position := 0 for solutionStop, ok := generator.next(); ok; solutionStop, ok = generator.next() { newPositions[solutionStop] = position + if solutionStop.IsPlanned() { + oldPositions = append(oldPositions, solutionStop) + } position += 1 } newPlanUnitSpanFirstPosition := move.Previous().Position() + 1 newPlanUnitSpanLastPosition := move.Next().Position() + len(move.PlanStopsUnit().SolutionStops()) - 1 + // Check if the plan unit we are moving is a plan units unit, if it is we + // need to check if the plan units that are already are planned of this unit + // spans the new positions of the plan unit we are moving, if so we are good if modelPlanUnitsUnit, hasModelPlanUnitsUnit := move.PlanStopsUnit().ModelPlanUnit().PlanUnitsUnit(); hasModelPlanUnitsUnit { - var first, last SolutionStop - for _, planUnit := range modelPlanUnitsUnit.PlanUnits() { - if planUnit.Index() == move.PlanStopsUnit().ModelPlanUnit().Index() { - continue + + if _, ok := data.solutionPlanStopUnits[modelPlanUnitsUnit.Index()]; ok { + firstSolutionStop := oldPositions[data.solutionPlanStopUnits[modelPlanUnitsUnit.Index()].first] + if newPositions[firstSolutionStop] < newPlanUnitSpanFirstPosition { + newPlanUnitSpanFirstPosition = newPositions[firstSolutionStop] } - solutionPlanUnit := solution.SolutionPlanUnit(planUnit) - if solutionPlanUnit.IsPlanned() { - for _, solutionPlanStopsUnit := range solutionPlanUnit.PlannedPlanStopsUnits() { - if solutionPlanStopsUnit.SolutionStops()[0].Vehicle() != move.Vehicle() { - continue - } - first, last = determineFirstLastSolutionStops(first, last, solutionPlanStopsUnit) - if first == nil { - continue - } - } + lastSolutionStop := oldPositions[data.solutionPlanStopUnits[modelPlanUnitsUnit.Index()].last] + if newPositions[lastSolutionStop] > newPlanUnitSpanLastPosition { + newPlanUnitSpanLastPosition = newPositions[lastSolutionStop] } } - if first != nil && newPositions[first] <= newPlanUnitSpanFirstPosition { - newPlanUnitSpanFirstPosition = newPositions[first] - } - if last != nil && newPositions[last] >= newPlanUnitSpanLastPosition { - newPlanUnitSpanLastPosition = newPositions[last] - } } // Check if the plan unit we are moving is a target @@ -348,6 +432,7 @@ func (l *interleaveConstraintImpl) EstimateIsViolated( if solutionPlanStopsUnit.SolutionStops()[0].Vehicle() != move.Vehicle() { continue } + sourceSpanFirst, sourceSpanLast = determineFirstLastSolutionStops( sourceSpanFirst, sourceSpanLast, @@ -386,11 +471,14 @@ func (l *interleaveConstraintImpl) EstimateIsViolated( targetSpanLast, plannedSolutionStops, ) + newTargetSpanFirstPosition := newPositions[targetSpanFirst] + newTargetSpanLastPosition := newPositions[targetSpanLast] + if isViolatedPositions( newPlanUnitSpanFirstPosition, newPlanUnitSpanLastPosition, - newPositions[targetSpanFirst], - newPositions[targetSpanLast], + newTargetSpanFirstPosition, + newTargetSpanLastPosition, ) { return true, noPositionsHint() } From 11a7b0ce5cdf7511586a1180903022c781378786 Mon Sep 17 00:00:00 2001 From: David Rijsman Date: Wed, 8 May 2024 09:25:21 +0200 Subject: [PATCH 06/11] Interleave move generator --- factory/constraint_interleave.go | 5 + model.go | 17 +- model_constraint_interleave_test.go | 4 +- solution.go | 6 +- solution_move_stops_generator.go | 232 ++++++++++++++++++++----- solution_move_stops_generator_test.go | 235 ++++++++++++++++++++++++++ solution_vehicle.go | 6 +- 7 files changed, 451 insertions(+), 54 deletions(-) diff --git a/factory/constraint_interleave.go b/factory/constraint_interleave.go index 763ace9..85c54aa 100644 --- a/factory/constraint_interleave.go +++ b/factory/constraint_interleave.go @@ -102,6 +102,11 @@ func numberOfStops(planUnit nextroute.ModelPlanUnit) int { return len(planStopsUnit.Stops()) } count := 0 + if planUnitsUnit, isPlanUnitsUnit := planUnit.(nextroute.ModelPlanUnitsUnit); isPlanUnitsUnit { + for _, unit := range planUnitsUnit.PlanUnits() { + count += numberOfStops(unit) + } + } if planUnitsUnit, isPlanUnitsUnit := planUnit.PlanUnitsUnit(); isPlanUnitsUnit { for _, unit := range planUnitsUnit.PlanUnits() { count += numberOfStops(unit) diff --git a/model.go b/model.go index 7439b3d..3867523 100644 --- a/model.go +++ b/model.go @@ -60,6 +60,10 @@ type Model interface { // locked after a solution has been created using the model. IsLocked() bool + // InterleaveConstraint returns the interleave constraint of the model if + // it exists, otherwise returns nil. + InterleaveConstraint() InterleaveConstraint + // NewPlanSequence creates a new plan sequence. A plan sequence is a plan // unit. A plan unit is a collection of stops which are always planned and // unplanned as a single unit. In this case they have to be planned as a @@ -213,7 +217,8 @@ func NewModel() (Model, error) { // planunit without any relationship, we will still fully explore all // permutations. To find a better number we would have to run // experiments on pathologic cases. - sequenceSampleSize: 24, + sequenceSampleSize: 24, + interleaveConstraint: nil, } if m.epoch.Second() != 0 || m.epoch.Nanosecond() != 0 { @@ -260,6 +265,7 @@ type modelImpl struct { mutex sync.RWMutex isLocked bool disallowedSuccessors [][]bool + interleaveConstraint InterleaveConstraint } func (m *modelImpl) Vehicles() ModelVehicles { @@ -391,6 +397,10 @@ func (m *modelImpl) addToCheckAt(checkAt CheckedAt, constraint ModelConstraint) m.constraintMap[checkAt] = append(m.constraintMap[checkAt], constraint) } +func (m *modelImpl) InterleaveConstraint() InterleaveConstraint { + return m.interleaveConstraint +} + func (m *modelImpl) AddConstraint(constraint ModelConstraint) error { if m.IsLocked() { return fmt.Errorf(lockErrorMessage, "constraint") @@ -411,13 +421,16 @@ func (m *modelImpl) AddConstraint(constraint ModelConstraint) error { } } if _, ok := constraint.(InterleaveConstraint); ok { - if _, alreadyOneAdded := existingConstraint.(InterleaveConstraint); alreadyOneAdded { + if m.interleaveConstraint != nil { return fmt.Errorf( "only one InterleaveConstraint can be added to the model", ) } } } + if _, ok := constraint.(InterleaveConstraint); ok { + m.interleaveConstraint = constraint.(InterleaveConstraint) + } if _, ok := constraint.(ConstraintDataUpdater); ok { return fmt.Errorf( "ConstraintDataUpdater has been deprecated, "+ diff --git a/model_constraint_interleave_test.go b/model_constraint_interleave_test.go index d6e8d62..e02a6b7 100644 --- a/model_constraint_interleave_test.go +++ b/model_constraint_interleave_test.go @@ -4,10 +4,10 @@ package nextroute_test import ( "context" - "github.com/nextmv-io/nextroute/common" "testing" "github.com/nextmv-io/nextroute" + "github.com/nextmv-io/nextroute/common" ) func TestNewInterleaveConstraint(t *testing.T) { @@ -556,7 +556,7 @@ func TestInterleaveConstraint2(t *testing.T) { solution, err := nextroute.NewSolution(model) if err != nil { - t.Error(err) + t.Fatal(err) } solutionVehicle := solution.Vehicles()[0] diff --git a/solution.go b/solution.go index bbdd9f2..9904b3f 100644 --- a/solution.go +++ b/solution.go @@ -1054,9 +1054,9 @@ func (s *solutionImpl) UnPlannedPlanUnits() ImmutableSolutionPlanUnitCollection // PreAllocatedMoveContainer is used to reduce allocations. // It contains objects that can be used instead of allocating new ones. type PreAllocatedMoveContainer struct { - // singleStopPosSolutionMoveStop has the underlying type *solutionMoveStopsImpl. + // solutionMoveStops has the underlying type *solutionMoveStopsImpl. // and has a length 1 stopPositions slice. - singleStopPosSolutionMoveStop SolutionMoveStops + solutionMoveStops SolutionMoveStops } // NewPreAllocatedMoveContainer creates a new PreAllocatedMoveContainer. @@ -1067,7 +1067,7 @@ func NewPreAllocatedMoveContainer(planUnit SolutionPlanUnit) *PreAllocatedMoveCo case SolutionPlanStopsUnit: m := newNotExecutableSolutionMoveStops(planUnit.(*solutionPlanStopsUnitImpl)) m.stopPositions = make([]stopPositionImpl, 1, 2) - allocations.singleStopPosSolutionMoveStop = m + allocations.solutionMoveStops = m case SolutionPlanUnitsUnit: } return &allocations diff --git a/solution_move_stops_generator.go b/solution_move_stops_generator.go index eff108e..b4b79d0 100644 --- a/solution_move_stops_generator.go +++ b/solution_move_stops_generator.go @@ -3,6 +3,8 @@ package nextroute import ( + "math" + "github.com/nextmv-io/nextroute/common" ) @@ -101,38 +103,44 @@ func SolutionMoveStopsGenerator( preAllocatedMoveContainer *PreAllocatedMoveContainer, shouldStop func() bool, ) { - source := common.Map(stops, func(stop SolutionStop) solutionStopImpl { - return stop.(solutionStopImpl) - }) - target := common.Map(vehicle.SolutionStops(), func(stop SolutionStop) solutionStopImpl { - return stop.(solutionStopImpl) - }) - m := preAllocatedMoveContainer.singleStopPosSolutionMoveStop - m.(*solutionMoveStopsImpl).reset() + solution := vehicle.solution + nrStops := len(stops) + + m := preAllocatedMoveContainer.solutionMoveStops m.(*solutionMoveStopsImpl).planUnit = planUnit - m.(*solutionMoveStopsImpl).allowed = false - if len(source) == 0 { + m.(*solutionMoveStopsImpl).reset() + + if nrStops == 0 { yield(m) return } - // TODO: we can reuse the stopPositions slice from m - positions := make([]stopPositionImpl, len(source)) - for idx := range source { - positions[idx].stopIndex = source[idx].index - positions[idx].solution = source[idx].solution + if cap(m.(*solutionMoveStopsImpl).stopPositions) < nrStops { + m.(*solutionMoveStopsImpl).stopPositions = make([]stopPositionImpl, nrStops) } + m.(*solutionMoveStopsImpl).stopPositions = m.(*solutionMoveStopsImpl).stopPositions[:nrStops] - locations := make([]int, 0, len(source)) + for idx, stop := range stops { + m.(*solutionMoveStopsImpl).stopPositions[idx].stopIndex = stop.(solutionStopImpl).index + m.(*solutionMoveStopsImpl).stopPositions[idx].solution = solution + } - generate(positions, locations, source, target, func() { - m.(*solutionMoveStopsImpl).reset() - m.(*solutionMoveStopsImpl).planUnit = planUnit - m.(*solutionMoveStopsImpl).stopPositions = positions - m.(*solutionMoveStopsImpl).allowed = false - m.(*solutionMoveStopsImpl).valueSeen = 1 - yield(m) - }, shouldStop) + target := common.Map(vehicle.SolutionStops(), func(stop SolutionStop) solutionStopImpl { + return stop.(solutionStopImpl) + }) + + combination := make([]int, 0, nrStops) + + generate( + vehicle, + planUnit, + m.(*solutionMoveStopsImpl).stopPositions, combination, target, func() { + m.(*solutionMoveStopsImpl).allowed = false + m.(*solutionMoveStopsImpl).valueSeen = 1 + yield(m) + }, + shouldStop, + ) } func isNotAllowed(from, to solutionStopImpl) bool { @@ -154,10 +162,18 @@ func mustBeNeighbours(from, to solutionStopImpl) bool { HasDirectArc(from.ModelStop(), to.ModelStop()) } +// - combination[i] will define before which stop in target the stop at +// stopPositions[i].stop should be inserted in target. +// - if len(combination) == len(stopPositions) then we have a complete +// definition of where all the stop positions should be inserted. +// - combination[i] >= combination[i-1] should be true for all i > 0, this is +// because we want to preserve the order of the stops associated with the +// stop positions. func generate( + vehicle SolutionVehicle, + solutionPlanStopsUnit SolutionPlanStopsUnit, stopPositions []stopPositionImpl, combination []int, - source []solutionStopImpl, target []solutionStopImpl, yield func(), shouldStop func() bool, @@ -166,53 +182,181 @@ func generate( return } - if len(combination) == len(source) { + if len(combination) == len(stopPositions) { yield() return } start := 0 - if len(combination) > 0 { + + if len(combination) != 0 { start = combination[len(combination)-1] - 1 } - for i := start; i < len(target)-1; i++ { + // can we be more precise on where we can end? + end := len(target) - 1 + + excludePositions := map[int]struct{}{} + + interleaveConstraint := vehicle.ModelVehicle().Model().InterleaveConstraint() + + if interleaveConstraint != nil { + solution := vehicle.SolutionStops()[0].Solution() + + modelPlanUnit := solutionPlanStopsUnit.ModelPlanUnit() + + // what is the first planned target position for this plan unit if + // it is a composition of plan units (plan units unit)? + firstPlannedTargetPosition := math.MaxInt64 + + if planUnitsUnit, hasPlanUnitsUnit := modelPlanUnit.PlanUnitsUnit(); hasPlanUnitsUnit { + data := vehicle.Last().ConstraintData(interleaveConstraint).(*interleaveConstraintData) + if solutionStopSpan, ok := data.solutionPlanStopUnits[planUnitsUnit.Index()]; ok { + firstPlannedTargetPosition = solutionStopSpan.first + } + } + + sourceDisallowedInterleaves := interleaveConstraint.SourceDisallowedInterleaves(modelPlanUnit) + + for _, sourceDisallowedInterleave := range sourceDisallowedInterleaves { + targetSolutionPlanUnit := solution.SolutionPlanUnit(sourceDisallowedInterleave.Target()) + if targetSolutionPlanUnit.IsPlanned() { + var first SolutionStop + var last SolutionStop + for _, plannedPlanStopsUnit := range targetSolutionPlanUnit.PlannedPlanStopsUnits() { + if plannedPlanStopsUnit.SolutionStops()[0].Vehicle() == vehicle { + // the source is not allowed to be interleaved into these positions + for _, solutionStop := range plannedPlanStopsUnit.SolutionStops() { + if first == nil || solutionStop.Position() < first.Position() { + first = solutionStop + } + if last == nil || solutionStop.Position() > last.Position() { + last = solutionStop + } + } + } + } + for s := first.Next(); s != last; s = s.Next() { + excludePositions[s.Position()-1] = struct{}{} + } + excludePositions[last.Position()-1] = struct{}{} + } + } + + targetDisallowedInterleaves := interleaveConstraint.TargetDisallowedInterleaves(modelPlanUnit) + + TargetDisallowedInterleavesLoop: + for _, targetDisallowedInterleave := range targetDisallowedInterleaves { + for _, source := range targetDisallowedInterleave.Sources() { + sourceSolutionPlanUnit := solution.SolutionPlanUnit(source) + if sourceSolutionPlanUnit.IsPlanned() { + // Each source can not be interleaved with the to be planned + // plan unit (target). + for _, plannedPlanStopsUnit := range sourceSolutionPlanUnit.PlannedPlanStopsUnits() { + if plannedPlanStopsUnit.SolutionStops()[0].Vehicle() == vehicle { + firstStopOfPlanUnit := plannedPlanStopsUnit.SolutionStops()[0] + + // If we already decided to be before or after this planned source all position must be + // before or after. We force before by setting end, we do not have to force after as the + // sequence of stops already does that. + if len(combination) > 0 && + combination[len(combination)-1] <= firstStopOfPlanUnit.Position() { + end = firstStopOfPlanUnit.Position() + 1 + break TargetDisallowedInterleavesLoop + } + + // If we already planned part of the plan units unit target we must plan this target also + // before or after the planned source. + if firstPlannedTargetPosition != math.MaxInt64 { + lastStopOfPlanUnit := + plannedPlanStopsUnit.SolutionStops()[len(plannedPlanStopsUnit.SolutionStops())-1] + + if firstPlannedTargetPosition > lastStopOfPlanUnit.Position() { + start = lastStopOfPlanUnit.Position() + 1 + } + + if firstPlannedTargetPosition < firstStopOfPlanUnit.Position() { + end = firstStopOfPlanUnit.Position() + 1 + } + } + } + } + } + } + } + } + + positions := make([]int, 0, end-start) + for i := start; i < end; i++ { + if _, excludePosition := excludePositions[i]; !excludePosition { + positions = append(positions, i) + } + } + + for _, i := range positions { + // if the stops on the existing vehicle must be neighbours, we can only + // explore combinations where the stops are neighbours. In other words + // we can not add something between these two stops. if i > 0 && mustBeNeighbours(target[i], target[i+1]) { continue } - combination = append(combination, i+1) - positionIdx := len(combination) - 1 + stopPositionIdx := len(combination) + + combination = append(combination, i+1) - stopPositions[positionIdx].previousStopIndex = target[combination[positionIdx]-1].index - stopPositions[positionIdx].nextStopIndex = target[combination[positionIdx]].index + stopPositions[stopPositionIdx].previousStopIndex = target[combination[stopPositionIdx]-1].index + stopPositions[stopPositionIdx].nextStopIndex = target[combination[stopPositionIdx]].index - if positionIdx > 0 { - if combination[positionIdx] == combination[positionIdx-1] { - stopPositions[positionIdx].previousStopIndex = stopPositions[positionIdx-1].stopIndex - stopPositions[positionIdx-1].nextStopIndex = stopPositions[positionIdx].stopIndex + if stopPositionIdx > 0 { + previousStopPositionIdx := stopPositionIdx - 1 + if combination[stopPositionIdx] == combination[stopPositionIdx-1] { + stopPositions[stopPositionIdx].previousStopIndex = stopPositions[previousStopPositionIdx].stopIndex + stopPositions[previousStopPositionIdx].nextStopIndex = stopPositions[stopPositionIdx].stopIndex } else { - stopPositions[positionIdx-1].nextStopIndex = target[combination[positionIdx-1]].index - if mustBeNeighbours(stopPositions[positionIdx-1].stop(), stopPositions[positionIdx].stop()) { + stopPositions[previousStopPositionIdx].nextStopIndex = + target[combination[previousStopPositionIdx]].index + if mustBeNeighbours( + stopPositions[previousStopPositionIdx].stop(), + stopPositions[stopPositionIdx].stop(), + ) { + // if the previous stop and this stop must be neighbours, we can break immediately and only explore + // them as neighbours (no need to try to position positionIdx at any other position). break } } - if isNotAllowed(stopPositions[positionIdx-1].stop(), stopPositions[positionIdx-1].next()) { - combination = combination[:positionIdx] - if stopPositions[positionIdx-1].nextStopIndex != stopPositions[positionIdx].previousStopIndex { + if isNotAllowed( + stopPositions[previousStopPositionIdx].stop(), + stopPositions[previousStopPositionIdx].next(), + ) { + // undo the last combination (undo where we position stop at stopPositionIdx) + combination = combination[:stopPositionIdx] + // if selecting a new position for the stop at stopPositionIdx does not influence the previous stop next + // stop it does not matter where we position the stop at stopPositionIdx, therefor we trigger a back + // track immediately and go for the next position for the previous stop position. + if stopPositions[previousStopPositionIdx].nextStopIndex != + stopPositions[stopPositionIdx].previousStopIndex { break } continue } } - if isNotAllowed(stopPositions[positionIdx].previous(), stopPositions[positionIdx].stop()) { - combination = combination[:positionIdx] + if isNotAllowed(stopPositions[stopPositionIdx].previous(), stopPositions[stopPositionIdx].stop()) { + combination = combination[:stopPositionIdx] continue } - generate(stopPositions, combination, source, target, yield, shouldStop) + generate( + vehicle, + solutionPlanStopsUnit, + stopPositions, + combination, + target, + yield, + shouldStop, + ) combination = combination[:len(combination)-1] } diff --git a/solution_move_stops_generator_test.go b/solution_move_stops_generator_test.go index 6bdaf68..db9f40c 100644 --- a/solution_move_stops_generator_test.go +++ b/solution_move_stops_generator_test.go @@ -877,3 +877,238 @@ func testMoves( ) } } + +func TestSolutionMoveStopsGeneratorInterleaved( + t *testing.T, +) { + model, planUnits, _ := createModel2(t) + xPlanUnit := planUnits[0] + yPlanUnit := planUnits[1] + iPlanUnit := planUnits[2] + jPlanUnit := planUnits[3] + + solution, err := nextroute.NewSolution(model) + if err != nil { + t.Fatal(err) + } + + v := solution.Vehicles()[0] + + xSolutionPlanUnit := solution.SolutionPlanUnit(xPlanUnit) + + move := v.BestMove(context.Background(), xSolutionPlanUnit) + if move == nil { + t.Fatal("move should not be nil") + } + if !move.IsExecutable() { + t.Fatal("move should be executable") + } + planned, err := move.Execute(context.Background()) + if err != nil { + t.Fatal(err) + } + if !planned { + t.Fatal("move should be planned") + } + + // i can not be interleaved anywhere in 'warehouse - a - b - c - d - e - warehouse' + // but can go first or last + iSolutionPlanUnit := solution.SolutionPlanUnit(iPlanUnit).(nextroute.SolutionPlanStopsUnit) + + moveCount := 0 + + alloc := nextroute.NewPreAllocatedMoveContainer(iSolutionPlanUnit) + nextroute.SolutionMoveStopsGeneratorTest( + v, + iSolutionPlanUnit, + func(move nextroute.SolutionMoveStops) { + moveCount += 1 + if moveCount > 2 { + t.Fatal("move count should not exceed 2") + } + if moveCount == 1 { + if move.StopPositions()[0].Previous() != v.First() { + t.Fatal("previous stop should be the first stop") + } + } + if moveCount == 2 { + if move.StopPositions()[0].Next() != v.Last() { + t.Fatal("next stop should be the last stop") + } + } + }, + iSolutionPlanUnit.SolutionStops(), + alloc, + func() bool { + return false + }, + ) + + // j can go anywhere in 'warehouse - a - b - c - d - e - warehouse' + jSolutionPlanUnit := solution.SolutionPlanUnit(jPlanUnit).(nextroute.SolutionPlanStopsUnit) + + moveCount = 0 + + alloc = nextroute.NewPreAllocatedMoveContainer(jSolutionPlanUnit) + nextroute.SolutionMoveStopsGeneratorTest( + v, + jSolutionPlanUnit, + func(move nextroute.SolutionMoveStops) { + moveCount += 1 + if moveCount > 6 { + t.Fatal("move count should not exceed 6") + } + }, + jSolutionPlanUnit.SolutionStops(), + alloc, + func() bool { + return false + }, + ) + if moveCount != 6 { + t.Fatal("move count should be 6") + } + + hPlanUnit := yPlanUnit.(nextroute.ModelPlanUnitsUnit).PlanUnits()[1] + + hSolutionPlanUnit := solution.SolutionPlanUnit(hPlanUnit).(nextroute.SolutionPlanStopsUnit) + + // h can go only at start and end of 'warehouse - a - b - c - d - e - warehouse' + // h is part of Y and Y can not be interleaved with X and a,b,c,d and e are + // part of X so h can only go at start and end + moveCount = 0 + + alloc = nextroute.NewPreAllocatedMoveContainer(hSolutionPlanUnit) + nextroute.SolutionMoveStopsGeneratorTest( + v, + hSolutionPlanUnit, + func(move nextroute.SolutionMoveStops) { + moveCount += 1 + }, + hSolutionPlanUnit.SolutionStops(), + alloc, + func() bool { + return false + }, + ) + if moveCount != 2 { + t.Fatal("move for h count should be 2, it is", moveCount) + } + + fgPlanUnit := yPlanUnit.(nextroute.ModelPlanUnitsUnit).PlanUnits()[0] + + fgSolutionPlanUnit := solution.SolutionPlanUnit(fgPlanUnit).(nextroute.SolutionPlanStopsUnit) + + // fg can can not interleave a-..- e in in 'warehouse - a - b - c - d - e - warehouse' + // and fg can not be interleaved by any a-..-e, so 2 moves + + moveCount = 0 + + alloc = nextroute.NewPreAllocatedMoveContainer(fgSolutionPlanUnit) + nextroute.SolutionMoveStopsGeneratorTest( + v, + fgSolutionPlanUnit, + func(move nextroute.SolutionMoveStops) { + moveCount += 1 + }, + fgSolutionPlanUnit.SolutionStops(), + alloc, + func() bool { + return false + }, + ) + if moveCount != 2 { + t.Fatal("move for fg count should be 2, it is", moveCount) + } + + hFirstPosition, err := nextroute.NewStopPosition(v.First(), hSolutionPlanUnit.SolutionStops()[0], v.First().Next()) + if err != nil { + t.Fatal(err) + } + + moveH, err := nextroute.NewMoveStops( + hSolutionPlanUnit, + nextroute.StopPositions{ + hFirstPosition, + }, + ) + if err != nil { + t.Fatal(err) + } + planned, err = moveH.Execute(context.Background()) + if err != nil { + t.Fatal(err) + } + if !planned { + t.Fatal("move should be planned") + } + //fmt.Println(common.Map(v.SolutionStops(), func(stop nextroute.SolutionStop) string { + // return stop.ModelStop().ID() + //})) + + moveCount = 0 + + nextroute.SolutionMoveStopsGeneratorTest( + v, + fgSolutionPlanUnit, + func(move nextroute.SolutionMoveStops) { + moveCount += 1 + }, + fgSolutionPlanUnit.SolutionStops(), + alloc, + func() bool { + return false + }, + ) + if moveCount != 3 { + t.Fatal("move for fg count should be 3, it is", moveCount) + } + + unplanned, err := hSolutionPlanUnit.UnPlan() + if err != nil { + t.Fatal(err) + } + if !unplanned { + t.Fatal("h should be unplanned") + } + + hLastPosition, err := nextroute.NewStopPosition(v.Last().Previous(), hSolutionPlanUnit.SolutionStops()[0], v.Last()) + if err != nil { + t.Fatal(err) + } + + moveH, err = nextroute.NewMoveStops( + hSolutionPlanUnit, + nextroute.StopPositions{ + hLastPosition, + }, + ) + if err != nil { + t.Fatal(err) + } + planned, err = moveH.Execute(context.Background()) + if err != nil { + t.Fatal(err) + } + if !planned { + t.Fatal("move should be planned") + } + + moveCount = 0 + + nextroute.SolutionMoveStopsGeneratorTest( + v, + fgSolutionPlanUnit, + func(move nextroute.SolutionMoveStops) { + moveCount += 1 + }, + fgSolutionPlanUnit.SolutionStops(), + alloc, + func() bool { + return false + }, + ) + if moveCount != 4 { + t.Fatal("move for fg count should be 4, it is", moveCount) + } +} diff --git a/solution_vehicle.go b/solution_vehicle.go index f33b964..6ea641f 100644 --- a/solution_vehicle.go +++ b/solution_vehicle.go @@ -107,7 +107,7 @@ func (v solutionVehicleImpl) firstMovePlanStopsUnit( ) (SolutionMove, error) { stop := false var bestMove SolutionMove = newNotExecutableSolutionMoveStops(planUnit) - solutionMoveStopsGenerator( + SolutionMoveStopsGenerator( v, planUnit, func(nextMove SolutionMoveStops) { @@ -241,7 +241,7 @@ func (v solutionVehicleImpl) bestMovePlanSingleStop( preAllocatedMoveContainer *PreAllocatedMoveContainer, ) SolutionMoveStops { candidateStop := planUnit.solutionStops[0] - move := preAllocatedMoveContainer.singleStopPosSolutionMoveStop + move := preAllocatedMoveContainer.solutionMoveStops move.(*solutionMoveStopsImpl).reset() // ensure that stopPositions is a length 1 slice move.(*solutionMoveStopsImpl).stopPositions = append( @@ -353,7 +353,7 @@ func (v solutionVehicleImpl) bestMoveSequence( ) SolutionMove { var bestMove SolutionMove = newNotExecutableSolutionMoveStops(planUnit) stop := false - solutionMoveStopsGenerator( + SolutionMoveStopsGenerator( v, planUnit, func(nextMove SolutionMoveStops) { From 2eb824bbfe43c5e60c3800fe4cc55035e72a65c5 Mon Sep 17 00:00:00 2001 From: David Rijsman Date: Wed, 8 May 2024 09:25:21 +0200 Subject: [PATCH 07/11] Interleave move generator --- .golangci.yml | 7 + factory/constraint_interleave.go | 5 + model.go | 17 +- model_constraint_interleave.go | 103 ++++++------ model_constraint_interleave_test.go | 49 ++---- solution.go | 6 +- solution_move_stops_generator.go | 232 +++++++++++++++++++++----- solution_move_stops_generator_test.go | 229 +++++++++++++++++++++++++ solution_vehicle.go | 6 +- 9 files changed, 514 insertions(+), 140 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 25344d3..e0c0d33 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -148,6 +148,13 @@ issues: linters: - gocyclo # Next route tests and methods contain complex code hard to simplify. + - path: model_constraint_interleave\.go + linters: + - nestif + - path: solution_move_stops_generator\.go + linters: + - nestif + - gocyclo - path: model_expression_time_dependent_test\.go linters: - gocyclo diff --git a/factory/constraint_interleave.go b/factory/constraint_interleave.go index 763ace9..85c54aa 100644 --- a/factory/constraint_interleave.go +++ b/factory/constraint_interleave.go @@ -102,6 +102,11 @@ func numberOfStops(planUnit nextroute.ModelPlanUnit) int { return len(planStopsUnit.Stops()) } count := 0 + if planUnitsUnit, isPlanUnitsUnit := planUnit.(nextroute.ModelPlanUnitsUnit); isPlanUnitsUnit { + for _, unit := range planUnitsUnit.PlanUnits() { + count += numberOfStops(unit) + } + } if planUnitsUnit, isPlanUnitsUnit := planUnit.PlanUnitsUnit(); isPlanUnitsUnit { for _, unit := range planUnitsUnit.PlanUnits() { count += numberOfStops(unit) diff --git a/model.go b/model.go index 7439b3d..3867523 100644 --- a/model.go +++ b/model.go @@ -60,6 +60,10 @@ type Model interface { // locked after a solution has been created using the model. IsLocked() bool + // InterleaveConstraint returns the interleave constraint of the model if + // it exists, otherwise returns nil. + InterleaveConstraint() InterleaveConstraint + // NewPlanSequence creates a new plan sequence. A plan sequence is a plan // unit. A plan unit is a collection of stops which are always planned and // unplanned as a single unit. In this case they have to be planned as a @@ -213,7 +217,8 @@ func NewModel() (Model, error) { // planunit without any relationship, we will still fully explore all // permutations. To find a better number we would have to run // experiments on pathologic cases. - sequenceSampleSize: 24, + sequenceSampleSize: 24, + interleaveConstraint: nil, } if m.epoch.Second() != 0 || m.epoch.Nanosecond() != 0 { @@ -260,6 +265,7 @@ type modelImpl struct { mutex sync.RWMutex isLocked bool disallowedSuccessors [][]bool + interleaveConstraint InterleaveConstraint } func (m *modelImpl) Vehicles() ModelVehicles { @@ -391,6 +397,10 @@ func (m *modelImpl) addToCheckAt(checkAt CheckedAt, constraint ModelConstraint) m.constraintMap[checkAt] = append(m.constraintMap[checkAt], constraint) } +func (m *modelImpl) InterleaveConstraint() InterleaveConstraint { + return m.interleaveConstraint +} + func (m *modelImpl) AddConstraint(constraint ModelConstraint) error { if m.IsLocked() { return fmt.Errorf(lockErrorMessage, "constraint") @@ -411,13 +421,16 @@ func (m *modelImpl) AddConstraint(constraint ModelConstraint) error { } } if _, ok := constraint.(InterleaveConstraint); ok { - if _, alreadyOneAdded := existingConstraint.(InterleaveConstraint); alreadyOneAdded { + if m.interleaveConstraint != nil { return fmt.Errorf( "only one InterleaveConstraint can be added to the model", ) } } } + if _, ok := constraint.(InterleaveConstraint); ok { + m.interleaveConstraint = constraint.(InterleaveConstraint) + } if _, ok := constraint.(ConstraintDataUpdater); ok { return fmt.Errorf( "ConstraintDataUpdater has been deprecated, "+ diff --git a/model_constraint_interleave.go b/model_constraint_interleave.go index 1b115fa..d1719d5 100644 --- a/model_constraint_interleave.go +++ b/model_constraint_interleave.go @@ -4,8 +4,9 @@ package nextroute import ( "fmt" - "github.com/nextmv-io/nextroute/common" "math" + + "github.com/nextmv-io/nextroute/common" ) // InterleaveConstraint is a constraint that disallows certain target to be @@ -110,60 +111,62 @@ func (i *interleaveConstraintData) Copy() Copier { } func (l *interleaveConstraintImpl) UpdateConstraintStopData(s SolutionStop) (Copier, error) { - if s.IsLast() { - data := interleaveConstraintData{ - solutionPlanStopUnits: make(map[int]interleaveSolutionStopSpan), + if !s.IsLast() { + return nil, nil + } + + data := interleaveConstraintData{ + solutionPlanStopUnits: make(map[int]interleaveSolutionStopSpan), + } + + modelPlanStopsUnits := map[SolutionPlanStopsUnit]struct{}{} + + stop := s.Vehicle().First().Next() + for !stop.IsLast() { + planStopsUnit := stop.PlanStopsUnit() + + stop = stop.Next() + + if _, ok := modelPlanStopsUnits[planStopsUnit]; ok { + continue } - modelPlanStopsUnits := map[SolutionPlanStopsUnit]struct{}{} - stop := s.Vehicle().First().Next() - for !stop.IsLast() { + if modelPlanUnitsUnit, ok := planStopsUnit.ModelPlanStopsUnit().PlanUnitsUnit(); ok { + modelPlanStopsUnits[planStopsUnit] = struct{}{} - planStopsUnit := stop.PlanStopsUnit() + planUnitsUnitFirst := math.MaxInt64 + planUnitsUnitLast := math.MinInt64 - stop = stop.Next() + for _, planUnit := range modelPlanUnitsUnit.PlanUnits() { + if modelPlanStopsUnit, ok := planUnit.(ModelPlanStopsUnit); ok { + solutionPlanStopsUnit := s.Solution().SolutionPlanStopsUnit(modelPlanStopsUnit) + if solutionPlanStopsUnit.IsPlanned() { + solutionStops := solutionPlanStopsUnit.SolutionStops() + first := solutionStops[0].Position() + last := solutionStops[len(solutionStops)-1].Position() - if _, ok := modelPlanStopsUnits[planStopsUnit]; ok { - continue - } + data.solutionPlanStopUnits[modelPlanStopsUnit.Index()] = interleaveSolutionStopSpan{ + first: first, + last: last, + } - if modelPlanUnitsUnit, ok := planStopsUnit.ModelPlanStopsUnit().PlanUnitsUnit(); ok { - modelPlanStopsUnits[planStopsUnit] = struct{}{} - - planUnitsUnitFirst := math.MaxInt64 - planUnitsUnitLast := math.MinInt64 - - for _, planUnit := range modelPlanUnitsUnit.PlanUnits() { - if modelPlanStopsUnit, ok := planUnit.(ModelPlanStopsUnit); ok { - solutionPlanStopsUnit := s.Solution().SolutionPlanStopsUnit(modelPlanStopsUnit).(SolutionPlanStopsUnit) - if solutionPlanStopsUnit.IsPlanned() { - first := solutionPlanStopsUnit.SolutionStops()[0].Position() - last := solutionPlanStopsUnit.SolutionStops()[len(solutionPlanStopsUnit.SolutionStops())-1].Position() - - data.solutionPlanStopUnits[modelPlanStopsUnit.Index()] = interleaveSolutionStopSpan{ - first: first, - last: last, - } - - if first < planUnitsUnitFirst { - planUnitsUnitFirst = first - } - if last > planUnitsUnitLast { - planUnitsUnitLast = last - } + if first < planUnitsUnitFirst { + planUnitsUnitFirst = first + } + if last > planUnitsUnitLast { + planUnitsUnitLast = last } } } - data.solutionPlanStopUnits[modelPlanUnitsUnit.Index()] = interleaveSolutionStopSpan{ - first: planUnitsUnitFirst, - last: planUnitsUnitLast, - } } - + data.solutionPlanStopUnits[modelPlanUnitsUnit.Index()] = interleaveSolutionStopSpan{ + first: planUnitsUnitFirst, + last: planUnitsUnitLast, + } } - return &data, nil } - return nil, nil + + return &data, nil } func (l *interleaveConstraintImpl) SourceDisallowedInterleaves( @@ -178,8 +181,8 @@ func (l *interleaveConstraintImpl) SourceDisallowedInterleaves( found := make([]DisallowedInterleave, 0) for _, disallowedInterleave := range l.disallowedInterleaves { - for _, source := range disallowedInterleave.Sources() { - if source == source { + for _, s := range disallowedInterleave.Sources() { + if source == s { found = append(found, disallowedInterleave) } } @@ -231,7 +234,7 @@ func addToMap( mapUnit[planUnit] = disallowedInterleaves } -func (l *interleaveConstraintImpl) Lock(model Model) error { +func (l *interleaveConstraintImpl) Lock(_ Model) error { l.sourceDisallowedInterleaves = make(map[ModelPlanUnit][]DisallowedInterleave) l.targetDisallowedInterleaves = make(map[ModelPlanUnit][]DisallowedInterleave) @@ -315,10 +318,7 @@ func (l *interleaveConstraintImpl) DisallowInterleaving(target ModelPlanUnit, so } index := common.FindIndex(l.disallowedInterleaves, func(disallowedInterleave DisallowedInterleave) bool { - if disallowedInterleave.Target() == target { - return true - } - return false + return disallowedInterleave.Target() == target }) if index < 0 { l.disallowedInterleaves = append(l.disallowedInterleaves, newDisallowedInterleave(target, sources)) @@ -394,7 +394,7 @@ func (l *interleaveConstraintImpl) EstimateIsViolated( if solutionStop.IsPlanned() { oldPositions = append(oldPositions, solutionStop) } - position += 1 + position++ } newPlanUnitSpanFirstPosition := move.Previous().Position() + 1 @@ -405,7 +405,6 @@ func (l *interleaveConstraintImpl) EstimateIsViolated( // spans the new positions of the plan unit we are moving, if so we are good if modelPlanUnitsUnit, hasModelPlanUnitsUnit := move.PlanStopsUnit().ModelPlanUnit().PlanUnitsUnit(); hasModelPlanUnitsUnit { - if _, ok := data.solutionPlanStopUnits[modelPlanUnitsUnit.Index()]; ok { firstSolutionStop := oldPositions[data.solutionPlanStopUnits[modelPlanUnitsUnit.Index()].first] if newPositions[firstSolutionStop] < newPlanUnitSpanFirstPosition { diff --git a/model_constraint_interleave_test.go b/model_constraint_interleave_test.go index d6e8d62..f7fe05b 100644 --- a/model_constraint_interleave_test.go +++ b/model_constraint_interleave_test.go @@ -4,10 +4,10 @@ package nextroute_test import ( "context" - "github.com/nextmv-io/nextroute/common" "testing" "github.com/nextmv-io/nextroute" + "github.com/nextmv-io/nextroute/common" ) func TestNewInterleaveConstraint(t *testing.T) { @@ -121,22 +121,14 @@ func TestInterleaveConstraint0(t *testing.T) { model, planUnits, modelStops := createModel1(t, false) xPlanUnit := planUnits[0] aPlanUnit := xPlanUnit.(nextroute.ModelPlanUnitsUnit).PlanUnits()[0] - //bPlanUnit := xPlanUnit.(nextroute.ModelPlanUnitsUnit).PlanUnits()[1] yPlanUnit := planUnits[1] cPlanUnit := yPlanUnit.(nextroute.ModelPlanUnitsUnit).PlanUnits()[0] dPlanUnit := yPlanUnit.(nextroute.ModelPlanUnitsUnit).PlanUnits()[1] - //zPlanUnit := planUnits[2] - //ePlanUnit := zPlanUnit.(nextroute.ModelPlanUnitsUnit).PlanUnits()[0] - //fPlanUnit := zPlanUnit.(nextroute.ModelPlanUnitsUnit).PlanUnits()[1] - a := modelStops[0] - //b := modelStops[1] c := modelStops[2] d := modelStops[3] - //e := modelStops[4] - //f := modelStops[5] solution, err := nextroute.NewSolution(model) if err != nil { @@ -146,24 +138,13 @@ func TestInterleaveConstraint0(t *testing.T) { solutionVehicle := solution.Vehicles()[0] aSolutionStop := solution.SolutionStop(a) - //bSolutionStop := solution.SolutionStop(b) cSolutionStop := solution.SolutionStop(c) dSolutionStop := solution.SolutionStop(d) - //eSolutionStop := solution.SolutionStop(e) - //fSolutionStop := solution.SolutionStop(f) - // xSolutionPlanUnit := solution.SolutionPlanUnit(xPlanUnit) aSolutionPlanUnit := solution.SolutionPlanUnit(aPlanUnit).(nextroute.SolutionPlanStopsUnit) - //bSolutionPlanUnit := solution.SolutionPlanUnit(bPlanUnit).(nextroute.SolutionPlanStopsUnit) - - //ySolutionPlanUnit := solution.SolutionPlanUnit(yPlanUnit) cSolutionPlanUnit := solution.SolutionPlanUnit(cPlanUnit).(nextroute.SolutionPlanStopsUnit) dSolutionPlanUnit := solution.SolutionPlanUnit(dPlanUnit).(nextroute.SolutionPlanStopsUnit) - //zSolutionPlanUnit := solution.SolutionPlanUnit(yPlanUnit) - //eSolutionPlanUnit := solution.SolutionPlanUnit(ePlanUnit).(nextroute.SolutionPlanStopsUnit) - //fSolutionPlanUnit := solution.SolutionPlanUnit(fPlanUnit).(nextroute.SolutionPlanStopsUnit) - // F - c - L stopPositionc, err := nextroute.NewStopPosition( solutionVehicle.First(), @@ -291,15 +272,12 @@ func TestInterleaveConstraint1(t *testing.T) { eSolutionStop := solution.SolutionStop(e) fSolutionStop := solution.SolutionStop(f) - // xSolutionPlanUnit := solution.SolutionPlanUnit(xPlanUnit) aSolutionPlanUnit := solution.SolutionPlanUnit(aPlanUnit).(nextroute.SolutionPlanStopsUnit) bSolutionPlanUnit := solution.SolutionPlanUnit(bPlanUnit).(nextroute.SolutionPlanStopsUnit) - //ySolutionPlanUnit := solution.SolutionPlanUnit(yPlanUnit) cSolutionPlanUnit := solution.SolutionPlanUnit(cPlanUnit).(nextroute.SolutionPlanStopsUnit) dSolutionPlanUnit := solution.SolutionPlanUnit(dPlanUnit).(nextroute.SolutionPlanStopsUnit) - //zSolutionPlanUnit := solution.SolutionPlanUnit(yPlanUnit) eSolutionPlanUnit := solution.SolutionPlanUnit(ePlanUnit).(nextroute.SolutionPlanStopsUnit) fSolutionPlanUnit := solution.SolutionPlanUnit(fPlanUnit).(nextroute.SolutionPlanStopsUnit) @@ -542,7 +520,7 @@ func TestInterleaveConstraint2(t *testing.T) { xPlanUnit := planUnits[0] yPlanUnit := planUnits[1] iPlanUnit := planUnits[2] - //jPlanUnit := planUnits[3] + a := modelStops[0] b := modelStops[1] c := modelStops[2] @@ -552,11 +530,10 @@ func TestInterleaveConstraint2(t *testing.T) { g := modelStops[6] h := modelStops[7] i := modelStops[8] - //j := modelStops[9] solution, err := nextroute.NewSolution(model) if err != nil { - t.Error(err) + t.Fatal(err) } solutionVehicle := solution.Vehicles()[0] @@ -1136,7 +1113,7 @@ func TestInterleaveConstraint2(t *testing.T) { // - Disallows interleaving of plan unit y and i with plan unit x // - Disallows interleaving of plan unit x and j with plan unit y // Adds the interleave constraint to the model -// Returns the model, plan units [x,y, i, j], and stops [A, B, C, D, E, F, G, H, I, J] +// Returns the model, plan units [x,y, i, j], and stops [A, B, C, D, E, F, G, H, I, J]. func createModel2(t *testing.T) ( nextroute.Model, nextroute.ModelPlanUnits, @@ -1357,39 +1334,39 @@ func createModel1(t *testing.T, postAll bool) ( t.Fatal(err) } - a_pu, err := model.NewPlanSingleStop(a) + aPlanUnit, err := model.NewPlanSingleStop(a) if err != nil { t.Fatal(err) } - b_pu, err := model.NewPlanSingleStop(b) + bPlanUnit, err := model.NewPlanSingleStop(b) if err != nil { t.Fatal(err) } - xPlanUnit, err := model.NewPlanAllPlanUnits(true, a_pu, b_pu) + xPlanUnit, err := model.NewPlanAllPlanUnits(true, aPlanUnit, bPlanUnit) if err != nil { t.Fatal(err) } - c_pu, err := model.NewPlanSingleStop(c) + cPlanUnit, err := model.NewPlanSingleStop(c) if err != nil { t.Fatal(err) } - d_pu, err := model.NewPlanSingleStop(d) + dPlanUnit, err := model.NewPlanSingleStop(d) if err != nil { t.Fatal(err) } - yPlanUnit, err := model.NewPlanAllPlanUnits(true, c_pu, d_pu) + yPlanUnit, err := model.NewPlanAllPlanUnits(true, cPlanUnit, dPlanUnit) if err != nil { t.Fatal(err) } - e_pu, err := model.NewPlanSingleStop(e) + ePlanUnit, err := model.NewPlanSingleStop(e) if err != nil { t.Fatal(err) } - f_pu, err := model.NewPlanSingleStop(f) + fPlanUnit, err := model.NewPlanSingleStop(f) if err != nil { t.Fatal(err) } - zPlanUnit, err := model.NewPlanAllPlanUnits(true, e_pu, f_pu) + zPlanUnit, err := model.NewPlanAllPlanUnits(true, ePlanUnit, fPlanUnit) if err != nil { t.Fatal(err) } diff --git a/solution.go b/solution.go index bbdd9f2..9904b3f 100644 --- a/solution.go +++ b/solution.go @@ -1054,9 +1054,9 @@ func (s *solutionImpl) UnPlannedPlanUnits() ImmutableSolutionPlanUnitCollection // PreAllocatedMoveContainer is used to reduce allocations. // It contains objects that can be used instead of allocating new ones. type PreAllocatedMoveContainer struct { - // singleStopPosSolutionMoveStop has the underlying type *solutionMoveStopsImpl. + // solutionMoveStops has the underlying type *solutionMoveStopsImpl. // and has a length 1 stopPositions slice. - singleStopPosSolutionMoveStop SolutionMoveStops + solutionMoveStops SolutionMoveStops } // NewPreAllocatedMoveContainer creates a new PreAllocatedMoveContainer. @@ -1067,7 +1067,7 @@ func NewPreAllocatedMoveContainer(planUnit SolutionPlanUnit) *PreAllocatedMoveCo case SolutionPlanStopsUnit: m := newNotExecutableSolutionMoveStops(planUnit.(*solutionPlanStopsUnitImpl)) m.stopPositions = make([]stopPositionImpl, 1, 2) - allocations.singleStopPosSolutionMoveStop = m + allocations.solutionMoveStops = m case SolutionPlanUnitsUnit: } return &allocations diff --git a/solution_move_stops_generator.go b/solution_move_stops_generator.go index eff108e..b4b79d0 100644 --- a/solution_move_stops_generator.go +++ b/solution_move_stops_generator.go @@ -3,6 +3,8 @@ package nextroute import ( + "math" + "github.com/nextmv-io/nextroute/common" ) @@ -101,38 +103,44 @@ func SolutionMoveStopsGenerator( preAllocatedMoveContainer *PreAllocatedMoveContainer, shouldStop func() bool, ) { - source := common.Map(stops, func(stop SolutionStop) solutionStopImpl { - return stop.(solutionStopImpl) - }) - target := common.Map(vehicle.SolutionStops(), func(stop SolutionStop) solutionStopImpl { - return stop.(solutionStopImpl) - }) - m := preAllocatedMoveContainer.singleStopPosSolutionMoveStop - m.(*solutionMoveStopsImpl).reset() + solution := vehicle.solution + nrStops := len(stops) + + m := preAllocatedMoveContainer.solutionMoveStops m.(*solutionMoveStopsImpl).planUnit = planUnit - m.(*solutionMoveStopsImpl).allowed = false - if len(source) == 0 { + m.(*solutionMoveStopsImpl).reset() + + if nrStops == 0 { yield(m) return } - // TODO: we can reuse the stopPositions slice from m - positions := make([]stopPositionImpl, len(source)) - for idx := range source { - positions[idx].stopIndex = source[idx].index - positions[idx].solution = source[idx].solution + if cap(m.(*solutionMoveStopsImpl).stopPositions) < nrStops { + m.(*solutionMoveStopsImpl).stopPositions = make([]stopPositionImpl, nrStops) } + m.(*solutionMoveStopsImpl).stopPositions = m.(*solutionMoveStopsImpl).stopPositions[:nrStops] - locations := make([]int, 0, len(source)) + for idx, stop := range stops { + m.(*solutionMoveStopsImpl).stopPositions[idx].stopIndex = stop.(solutionStopImpl).index + m.(*solutionMoveStopsImpl).stopPositions[idx].solution = solution + } - generate(positions, locations, source, target, func() { - m.(*solutionMoveStopsImpl).reset() - m.(*solutionMoveStopsImpl).planUnit = planUnit - m.(*solutionMoveStopsImpl).stopPositions = positions - m.(*solutionMoveStopsImpl).allowed = false - m.(*solutionMoveStopsImpl).valueSeen = 1 - yield(m) - }, shouldStop) + target := common.Map(vehicle.SolutionStops(), func(stop SolutionStop) solutionStopImpl { + return stop.(solutionStopImpl) + }) + + combination := make([]int, 0, nrStops) + + generate( + vehicle, + planUnit, + m.(*solutionMoveStopsImpl).stopPositions, combination, target, func() { + m.(*solutionMoveStopsImpl).allowed = false + m.(*solutionMoveStopsImpl).valueSeen = 1 + yield(m) + }, + shouldStop, + ) } func isNotAllowed(from, to solutionStopImpl) bool { @@ -154,10 +162,18 @@ func mustBeNeighbours(from, to solutionStopImpl) bool { HasDirectArc(from.ModelStop(), to.ModelStop()) } +// - combination[i] will define before which stop in target the stop at +// stopPositions[i].stop should be inserted in target. +// - if len(combination) == len(stopPositions) then we have a complete +// definition of where all the stop positions should be inserted. +// - combination[i] >= combination[i-1] should be true for all i > 0, this is +// because we want to preserve the order of the stops associated with the +// stop positions. func generate( + vehicle SolutionVehicle, + solutionPlanStopsUnit SolutionPlanStopsUnit, stopPositions []stopPositionImpl, combination []int, - source []solutionStopImpl, target []solutionStopImpl, yield func(), shouldStop func() bool, @@ -166,53 +182,181 @@ func generate( return } - if len(combination) == len(source) { + if len(combination) == len(stopPositions) { yield() return } start := 0 - if len(combination) > 0 { + + if len(combination) != 0 { start = combination[len(combination)-1] - 1 } - for i := start; i < len(target)-1; i++ { + // can we be more precise on where we can end? + end := len(target) - 1 + + excludePositions := map[int]struct{}{} + + interleaveConstraint := vehicle.ModelVehicle().Model().InterleaveConstraint() + + if interleaveConstraint != nil { + solution := vehicle.SolutionStops()[0].Solution() + + modelPlanUnit := solutionPlanStopsUnit.ModelPlanUnit() + + // what is the first planned target position for this plan unit if + // it is a composition of plan units (plan units unit)? + firstPlannedTargetPosition := math.MaxInt64 + + if planUnitsUnit, hasPlanUnitsUnit := modelPlanUnit.PlanUnitsUnit(); hasPlanUnitsUnit { + data := vehicle.Last().ConstraintData(interleaveConstraint).(*interleaveConstraintData) + if solutionStopSpan, ok := data.solutionPlanStopUnits[planUnitsUnit.Index()]; ok { + firstPlannedTargetPosition = solutionStopSpan.first + } + } + + sourceDisallowedInterleaves := interleaveConstraint.SourceDisallowedInterleaves(modelPlanUnit) + + for _, sourceDisallowedInterleave := range sourceDisallowedInterleaves { + targetSolutionPlanUnit := solution.SolutionPlanUnit(sourceDisallowedInterleave.Target()) + if targetSolutionPlanUnit.IsPlanned() { + var first SolutionStop + var last SolutionStop + for _, plannedPlanStopsUnit := range targetSolutionPlanUnit.PlannedPlanStopsUnits() { + if plannedPlanStopsUnit.SolutionStops()[0].Vehicle() == vehicle { + // the source is not allowed to be interleaved into these positions + for _, solutionStop := range plannedPlanStopsUnit.SolutionStops() { + if first == nil || solutionStop.Position() < first.Position() { + first = solutionStop + } + if last == nil || solutionStop.Position() > last.Position() { + last = solutionStop + } + } + } + } + for s := first.Next(); s != last; s = s.Next() { + excludePositions[s.Position()-1] = struct{}{} + } + excludePositions[last.Position()-1] = struct{}{} + } + } + + targetDisallowedInterleaves := interleaveConstraint.TargetDisallowedInterleaves(modelPlanUnit) + + TargetDisallowedInterleavesLoop: + for _, targetDisallowedInterleave := range targetDisallowedInterleaves { + for _, source := range targetDisallowedInterleave.Sources() { + sourceSolutionPlanUnit := solution.SolutionPlanUnit(source) + if sourceSolutionPlanUnit.IsPlanned() { + // Each source can not be interleaved with the to be planned + // plan unit (target). + for _, plannedPlanStopsUnit := range sourceSolutionPlanUnit.PlannedPlanStopsUnits() { + if plannedPlanStopsUnit.SolutionStops()[0].Vehicle() == vehicle { + firstStopOfPlanUnit := plannedPlanStopsUnit.SolutionStops()[0] + + // If we already decided to be before or after this planned source all position must be + // before or after. We force before by setting end, we do not have to force after as the + // sequence of stops already does that. + if len(combination) > 0 && + combination[len(combination)-1] <= firstStopOfPlanUnit.Position() { + end = firstStopOfPlanUnit.Position() + 1 + break TargetDisallowedInterleavesLoop + } + + // If we already planned part of the plan units unit target we must plan this target also + // before or after the planned source. + if firstPlannedTargetPosition != math.MaxInt64 { + lastStopOfPlanUnit := + plannedPlanStopsUnit.SolutionStops()[len(plannedPlanStopsUnit.SolutionStops())-1] + + if firstPlannedTargetPosition > lastStopOfPlanUnit.Position() { + start = lastStopOfPlanUnit.Position() + 1 + } + + if firstPlannedTargetPosition < firstStopOfPlanUnit.Position() { + end = firstStopOfPlanUnit.Position() + 1 + } + } + } + } + } + } + } + } + + positions := make([]int, 0, end-start) + for i := start; i < end; i++ { + if _, excludePosition := excludePositions[i]; !excludePosition { + positions = append(positions, i) + } + } + + for _, i := range positions { + // if the stops on the existing vehicle must be neighbours, we can only + // explore combinations where the stops are neighbours. In other words + // we can not add something between these two stops. if i > 0 && mustBeNeighbours(target[i], target[i+1]) { continue } - combination = append(combination, i+1) - positionIdx := len(combination) - 1 + stopPositionIdx := len(combination) + + combination = append(combination, i+1) - stopPositions[positionIdx].previousStopIndex = target[combination[positionIdx]-1].index - stopPositions[positionIdx].nextStopIndex = target[combination[positionIdx]].index + stopPositions[stopPositionIdx].previousStopIndex = target[combination[stopPositionIdx]-1].index + stopPositions[stopPositionIdx].nextStopIndex = target[combination[stopPositionIdx]].index - if positionIdx > 0 { - if combination[positionIdx] == combination[positionIdx-1] { - stopPositions[positionIdx].previousStopIndex = stopPositions[positionIdx-1].stopIndex - stopPositions[positionIdx-1].nextStopIndex = stopPositions[positionIdx].stopIndex + if stopPositionIdx > 0 { + previousStopPositionIdx := stopPositionIdx - 1 + if combination[stopPositionIdx] == combination[stopPositionIdx-1] { + stopPositions[stopPositionIdx].previousStopIndex = stopPositions[previousStopPositionIdx].stopIndex + stopPositions[previousStopPositionIdx].nextStopIndex = stopPositions[stopPositionIdx].stopIndex } else { - stopPositions[positionIdx-1].nextStopIndex = target[combination[positionIdx-1]].index - if mustBeNeighbours(stopPositions[positionIdx-1].stop(), stopPositions[positionIdx].stop()) { + stopPositions[previousStopPositionIdx].nextStopIndex = + target[combination[previousStopPositionIdx]].index + if mustBeNeighbours( + stopPositions[previousStopPositionIdx].stop(), + stopPositions[stopPositionIdx].stop(), + ) { + // if the previous stop and this stop must be neighbours, we can break immediately and only explore + // them as neighbours (no need to try to position positionIdx at any other position). break } } - if isNotAllowed(stopPositions[positionIdx-1].stop(), stopPositions[positionIdx-1].next()) { - combination = combination[:positionIdx] - if stopPositions[positionIdx-1].nextStopIndex != stopPositions[positionIdx].previousStopIndex { + if isNotAllowed( + stopPositions[previousStopPositionIdx].stop(), + stopPositions[previousStopPositionIdx].next(), + ) { + // undo the last combination (undo where we position stop at stopPositionIdx) + combination = combination[:stopPositionIdx] + // if selecting a new position for the stop at stopPositionIdx does not influence the previous stop next + // stop it does not matter where we position the stop at stopPositionIdx, therefor we trigger a back + // track immediately and go for the next position for the previous stop position. + if stopPositions[previousStopPositionIdx].nextStopIndex != + stopPositions[stopPositionIdx].previousStopIndex { break } continue } } - if isNotAllowed(stopPositions[positionIdx].previous(), stopPositions[positionIdx].stop()) { - combination = combination[:positionIdx] + if isNotAllowed(stopPositions[stopPositionIdx].previous(), stopPositions[stopPositionIdx].stop()) { + combination = combination[:stopPositionIdx] continue } - generate(stopPositions, combination, source, target, yield, shouldStop) + generate( + vehicle, + solutionPlanStopsUnit, + stopPositions, + combination, + target, + yield, + shouldStop, + ) combination = combination[:len(combination)-1] } diff --git a/solution_move_stops_generator_test.go b/solution_move_stops_generator_test.go index 6bdaf68..ecdf348 100644 --- a/solution_move_stops_generator_test.go +++ b/solution_move_stops_generator_test.go @@ -877,3 +877,232 @@ func testMoves( ) } } + +func TestSolutionMoveStopsGeneratorInterleaved( + t *testing.T, +) { + model, planUnits, _ := createModel2(t) + xPlanUnit := planUnits[0] + yPlanUnit := planUnits[1] + iPlanUnit := planUnits[2] + jPlanUnit := planUnits[3] + + solution, err := nextroute.NewSolution(model) + if err != nil { + t.Fatal(err) + } + + v := solution.Vehicles()[0] + + xSolutionPlanUnit := solution.SolutionPlanUnit(xPlanUnit) + + move := v.BestMove(context.Background(), xSolutionPlanUnit) + if move == nil { + t.Fatal("move should not be nil") + } + if !move.IsExecutable() { + t.Fatal("move should be executable") + } + planned, err := move.Execute(context.Background()) + if err != nil { + t.Fatal(err) + } + if !planned { + t.Fatal("move should be planned") + } + + // i can not be interleaved anywhere in 'warehouse - a - b - c - d - e - warehouse' + // but can go first or last + iSolutionPlanUnit := solution.SolutionPlanUnit(iPlanUnit).(nextroute.SolutionPlanStopsUnit) + + moveCount := 0 + + alloc := nextroute.NewPreAllocatedMoveContainer(iSolutionPlanUnit) + nextroute.SolutionMoveStopsGeneratorTest( + v, + iSolutionPlanUnit, + func(move nextroute.SolutionMoveStops) { + moveCount++ + if moveCount > 2 { + t.Fatal("move count should not exceed 2") + } + if moveCount == 1 { + if move.StopPositions()[0].Previous() != v.First() { + t.Fatal("previous stop should be the first stop") + } + } + if moveCount == 2 { + if move.StopPositions()[0].Next() != v.Last() { + t.Fatal("next stop should be the last stop") + } + } + }, + iSolutionPlanUnit.SolutionStops(), + alloc, + func() bool { + return false + }, + ) + + // j can go anywhere in 'warehouse - a - b - c - d - e - warehouse' + jSolutionPlanUnit := solution.SolutionPlanUnit(jPlanUnit).(nextroute.SolutionPlanStopsUnit) + + moveCount = 0 + + alloc = nextroute.NewPreAllocatedMoveContainer(jSolutionPlanUnit) + nextroute.SolutionMoveStopsGeneratorTest( + v, + jSolutionPlanUnit, + func(_ nextroute.SolutionMoveStops) { + moveCount++ + }, + jSolutionPlanUnit.SolutionStops(), + alloc, + func() bool { + return false + }, + ) + if moveCount != 6 { + t.Fatal("move count should be 6") + } + + hPlanUnit := yPlanUnit.(nextroute.ModelPlanUnitsUnit).PlanUnits()[1] + + hSolutionPlanUnit := solution.SolutionPlanUnit(hPlanUnit).(nextroute.SolutionPlanStopsUnit) + + // h can go only at start and end of 'warehouse - a - b - c - d - e - warehouse' + // h is part of Y and Y can not be interleaved with X and a,b,c,d and e are + // part of X so h can only go at start and end + moveCount = 0 + + alloc = nextroute.NewPreAllocatedMoveContainer(hSolutionPlanUnit) + nextroute.SolutionMoveStopsGeneratorTest( + v, + hSolutionPlanUnit, + func(_ nextroute.SolutionMoveStops) { + moveCount++ + }, + hSolutionPlanUnit.SolutionStops(), + alloc, + func() bool { + return false + }, + ) + if moveCount != 2 { + t.Fatal("move for h count should be 2, it is", moveCount) + } + + fgPlanUnit := yPlanUnit.(nextroute.ModelPlanUnitsUnit).PlanUnits()[0] + + fgSolutionPlanUnit := solution.SolutionPlanUnit(fgPlanUnit).(nextroute.SolutionPlanStopsUnit) + + // fg can can not interleave a-..- e in in 'warehouse - a - b - c - d - e - warehouse' + // and fg can not be interleaved by any a-..-e, so 2 moves + + moveCount = 0 + + alloc = nextroute.NewPreAllocatedMoveContainer(fgSolutionPlanUnit) + nextroute.SolutionMoveStopsGeneratorTest( + v, + fgSolutionPlanUnit, + func(_ nextroute.SolutionMoveStops) { + moveCount++ + }, + fgSolutionPlanUnit.SolutionStops(), + alloc, + func() bool { + return false + }, + ) + if moveCount != 2 { + t.Fatal("move for fg count should be 2, it is", moveCount) + } + + hFirstPosition, err := nextroute.NewStopPosition(v.First(), hSolutionPlanUnit.SolutionStops()[0], v.First().Next()) + if err != nil { + t.Fatal(err) + } + + moveH, err := nextroute.NewMoveStops( + hSolutionPlanUnit, + nextroute.StopPositions{ + hFirstPosition, + }, + ) + if err != nil { + t.Fatal(err) + } + planned, err = moveH.Execute(context.Background()) + if err != nil { + t.Fatal(err) + } + if !planned { + t.Fatal("move should be planned") + } + + moveCount = 0 + + nextroute.SolutionMoveStopsGeneratorTest( + v, + fgSolutionPlanUnit, + func(_ nextroute.SolutionMoveStops) { + moveCount++ + }, + fgSolutionPlanUnit.SolutionStops(), + alloc, + func() bool { + return false + }, + ) + if moveCount != 3 { + t.Fatal("move for fg count should be 3, it is", moveCount) + } + + unplanned, err := hSolutionPlanUnit.UnPlan() + if err != nil { + t.Fatal(err) + } + if !unplanned { + t.Fatal("h should be unplanned") + } + + hLastPosition, err := nextroute.NewStopPosition(v.Last().Previous(), hSolutionPlanUnit.SolutionStops()[0], v.Last()) + if err != nil { + t.Fatal(err) + } + + moveH, err = nextroute.NewMoveStops( + hSolutionPlanUnit, + nextroute.StopPositions{ + hLastPosition, + }, + ) + if err != nil { + t.Fatal(err) + } + planned, err = moveH.Execute(context.Background()) + if err != nil { + t.Fatal(err) + } + if !planned { + t.Fatal("move should be planned") + } + + moveCount = 0 + + nextroute.SolutionMoveStopsGeneratorTest( + v, + fgSolutionPlanUnit, + func(_ nextroute.SolutionMoveStops) { + moveCount++ + }, + fgSolutionPlanUnit.SolutionStops(), + alloc, + func() bool { + return false + }, + ) + if moveCount != 4 { + t.Fatal("move for fg count should be 4, it is", moveCount) + } +} diff --git a/solution_vehicle.go b/solution_vehicle.go index f33b964..6ea641f 100644 --- a/solution_vehicle.go +++ b/solution_vehicle.go @@ -107,7 +107,7 @@ func (v solutionVehicleImpl) firstMovePlanStopsUnit( ) (SolutionMove, error) { stop := false var bestMove SolutionMove = newNotExecutableSolutionMoveStops(planUnit) - solutionMoveStopsGenerator( + SolutionMoveStopsGenerator( v, planUnit, func(nextMove SolutionMoveStops) { @@ -241,7 +241,7 @@ func (v solutionVehicleImpl) bestMovePlanSingleStop( preAllocatedMoveContainer *PreAllocatedMoveContainer, ) SolutionMoveStops { candidateStop := planUnit.solutionStops[0] - move := preAllocatedMoveContainer.singleStopPosSolutionMoveStop + move := preAllocatedMoveContainer.solutionMoveStops move.(*solutionMoveStopsImpl).reset() // ensure that stopPositions is a length 1 slice move.(*solutionMoveStopsImpl).stopPositions = append( @@ -353,7 +353,7 @@ func (v solutionVehicleImpl) bestMoveSequence( ) SolutionMove { var bestMove SolutionMove = newNotExecutableSolutionMoveStops(planUnit) stop := false - solutionMoveStopsGenerator( + SolutionMoveStopsGenerator( v, planUnit, func(nextMove SolutionMoveStops) { From 705c3b308bd8f092d0adac135921561852bac7df Mon Sep 17 00:00:00 2001 From: David Rijsman Date: Mon, 13 May 2024 19:08:45 +0200 Subject: [PATCH 08/11] Header info --- factory/constraint_interleave.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/factory/constraint_interleave.go b/factory/constraint_interleave.go index 85c54aa..db08294 100644 --- a/factory/constraint_interleave.go +++ b/factory/constraint_interleave.go @@ -1,3 +1,5 @@ +// © 2019-present nextmv.io inc + package factory import ( From 34e256ebc0e102300e604b8831b967990cb178ab Mon Sep 17 00:00:00 2001 From: David Rijsman Date: Mon, 13 May 2024 19:18:28 +0200 Subject: [PATCH 09/11] Linter --- factory/constraint_interleave.go | 1 + 1 file changed, 1 insertion(+) diff --git a/factory/constraint_interleave.go b/factory/constraint_interleave.go index db08294..85995c8 100644 --- a/factory/constraint_interleave.go +++ b/factory/constraint_interleave.go @@ -4,6 +4,7 @@ package factory import ( "fmt" + "github.com/nextmv-io/nextroute" "github.com/nextmv-io/nextroute/schema" ) From a5e1e138d39f8cb726347a22e0b8b09d2a4f60b5 Mon Sep 17 00:00:00 2001 From: David Rijsman Date: Mon, 13 May 2024 19:25:23 +0200 Subject: [PATCH 10/11] Update golden files --- tests/check/input.json.golden | 3 ++- tests/custom_constraint/input.json.golden | 3 ++- tests/custom_matrices/input.json.golden | 3 ++- tests/custom_objective/input.json.golden | 3 ++- tests/custom_operators/input.json.golden | 3 ++- tests/golden/testdata/activation_penalty.json.golden | 3 ++- tests/golden/testdata/alternates.json.golden | 3 ++- tests/golden/testdata/basic.json.golden | 3 ++- tests/golden/testdata/capacity.json.golden | 3 ++- .../testdata/compatibility_attributes.json.golden | 3 ++- tests/golden/testdata/complex_precedence.json.golden | 3 ++- tests/golden/testdata/custom_data.json.golden | 3 ++- tests/golden/testdata/defaults.json.golden | 3 ++- tests/golden/testdata/direct_precedence.json.golden | 3 ++- .../testdata/direct_precedence_linked.json.golden | 3 ++- tests/golden/testdata/distance_matrix.json.golden | 3 ++- tests/golden/testdata/duration_groups.json.golden | 3 ++- .../duration_groups_with_stop_multiplier.json.golden | 3 ++- tests/golden/testdata/duration_matrix.json.golden | 3 ++- .../golden/testdata/early_arrival_penalty.json.golden | 3 ++- tests/golden/testdata/initial_stops.json.golden | 3 ++- ...initial_stops_infeasible_compatibility.json.golden | 3 ++- .../initial_stops_infeasible_max_duration.json.golden | 3 ++- .../initial_stops_infeasible_remove_all.json.golden | 3 ++- .../initial_stops_infeasible_temporal.json.golden | 3 ++- .../initial_stops_infeasible_tuple.json.golden | 3 ++- .../golden/testdata/late_arrival_penalty.json.golden | 3 ++- tests/golden/testdata/max_distance.json.golden | 7 ++++--- tests/golden/testdata/max_duration.json.golden | 3 ++- tests/golden/testdata/max_stops.json.golden | 3 ++- tests/golden/testdata/max_wait_stop.json.golden | 3 ++- tests/golden/testdata/max_wait_vehicle.json.golden | 3 ++- tests/golden/testdata/min_stops.json.golden | 11 ++++++----- tests/golden/testdata/multi_window.json.golden | 3 ++- tests/golden/testdata/no_mix.json.golden | 3 ++- .../golden/testdata/precedence pathologic.json.golden | 3 ++- tests/golden/testdata/precedence.json.golden | 3 ++- tests/golden/testdata/start_level.json.golden | 3 ++- tests/golden/testdata/start_time_window.json.golden | 3 ++- tests/golden/testdata/stop_duration.json.golden | 3 ++- .../testdata/stop_duration_multiplier.json.golden | 3 ++- tests/golden/testdata/stop_groups.json.golden | 3 ++- tests/golden/testdata/template_input.json.golden | 3 ++- tests/golden/testdata/unplanned_penalty.json.golden | 3 ++- .../testdata/vehicle_start_end_location.json.golden | 11 ++++++----- .../testdata/vehicle_start_end_time.json.golden | 3 ++- .../testdata/vehicles_duration_objective.json.golden | 3 ++- tests/inline_options/input.json.golden | 3 ++- tests/output_options/main.sh.golden | 3 ++- 49 files changed, 108 insertions(+), 59 deletions(-) diff --git a/tests/check/input.json.golden b/tests/check/input.json.golden index 97dcd4b..dde1a04 100644 --- a/tests/check/input.json.golden +++ b/tests/check/input.json.golden @@ -28,7 +28,8 @@ "vehicle_start_time": false }, "enable": { - "cluster": false + "cluster": false, + "interleave": false } }, "objectives": { diff --git a/tests/custom_constraint/input.json.golden b/tests/custom_constraint/input.json.golden index 277bec4..b2b4406 100644 --- a/tests/custom_constraint/input.json.golden +++ b/tests/custom_constraint/input.json.golden @@ -28,7 +28,8 @@ "vehicle_start_time": false }, "enable": { - "cluster": false + "cluster": false, + "interleave": false } }, "objectives": { diff --git a/tests/custom_matrices/input.json.golden b/tests/custom_matrices/input.json.golden index 4f3abc2..7e590ec 100644 --- a/tests/custom_matrices/input.json.golden +++ b/tests/custom_matrices/input.json.golden @@ -28,7 +28,8 @@ "vehicle_start_time": false }, "enable": { - "cluster": false + "cluster": false, + "interleave": false } }, "objectives": { diff --git a/tests/custom_objective/input.json.golden b/tests/custom_objective/input.json.golden index 9967889..e150984 100644 --- a/tests/custom_objective/input.json.golden +++ b/tests/custom_objective/input.json.golden @@ -28,7 +28,8 @@ "vehicle_start_time": false }, "enable": { - "cluster": false + "cluster": false, + "interleave": false } }, "objectives": { diff --git a/tests/custom_operators/input.json.golden b/tests/custom_operators/input.json.golden index a539e88..6517c99 100644 --- a/tests/custom_operators/input.json.golden +++ b/tests/custom_operators/input.json.golden @@ -28,7 +28,8 @@ "vehicle_start_time": false }, "enable": { - "cluster": false + "cluster": false, + "interleave": false } }, "objectives": { diff --git a/tests/golden/testdata/activation_penalty.json.golden b/tests/golden/testdata/activation_penalty.json.golden index c571835..bec876b 100644 --- a/tests/golden/testdata/activation_penalty.json.golden +++ b/tests/golden/testdata/activation_penalty.json.golden @@ -28,7 +28,8 @@ "vehicle_start_time": false }, "enable": { - "cluster": false + "cluster": false, + "interleave": false } }, "objectives": { diff --git a/tests/golden/testdata/alternates.json.golden b/tests/golden/testdata/alternates.json.golden index eac78ce..a414c78 100644 --- a/tests/golden/testdata/alternates.json.golden +++ b/tests/golden/testdata/alternates.json.golden @@ -28,7 +28,8 @@ "vehicle_start_time": false }, "enable": { - "cluster": false + "cluster": false, + "interleave": false } }, "objectives": { diff --git a/tests/golden/testdata/basic.json.golden b/tests/golden/testdata/basic.json.golden index a73489a..5696f7a 100644 --- a/tests/golden/testdata/basic.json.golden +++ b/tests/golden/testdata/basic.json.golden @@ -28,7 +28,8 @@ "vehicle_start_time": false }, "enable": { - "cluster": false + "cluster": false, + "interleave": false } }, "objectives": { diff --git a/tests/golden/testdata/capacity.json.golden b/tests/golden/testdata/capacity.json.golden index 10d8b2c..ba2ad39 100644 --- a/tests/golden/testdata/capacity.json.golden +++ b/tests/golden/testdata/capacity.json.golden @@ -28,7 +28,8 @@ "vehicle_start_time": false }, "enable": { - "cluster": false + "cluster": false, + "interleave": false } }, "objectives": { diff --git a/tests/golden/testdata/compatibility_attributes.json.golden b/tests/golden/testdata/compatibility_attributes.json.golden index c7cf69f..96533bf 100644 --- a/tests/golden/testdata/compatibility_attributes.json.golden +++ b/tests/golden/testdata/compatibility_attributes.json.golden @@ -28,7 +28,8 @@ "vehicle_start_time": false }, "enable": { - "cluster": false + "cluster": false, + "interleave": false } }, "objectives": { diff --git a/tests/golden/testdata/complex_precedence.json.golden b/tests/golden/testdata/complex_precedence.json.golden index 12b81ba..ca87f1b 100644 --- a/tests/golden/testdata/complex_precedence.json.golden +++ b/tests/golden/testdata/complex_precedence.json.golden @@ -28,7 +28,8 @@ "vehicle_start_time": false }, "enable": { - "cluster": false + "cluster": false, + "interleave": false } }, "objectives": { diff --git a/tests/golden/testdata/custom_data.json.golden b/tests/golden/testdata/custom_data.json.golden index 11a2723..2853e93 100644 --- a/tests/golden/testdata/custom_data.json.golden +++ b/tests/golden/testdata/custom_data.json.golden @@ -28,7 +28,8 @@ "vehicle_start_time": false }, "enable": { - "cluster": false + "cluster": false, + "interleave": false } }, "objectives": { diff --git a/tests/golden/testdata/defaults.json.golden b/tests/golden/testdata/defaults.json.golden index 7329cf3..66b3368 100644 --- a/tests/golden/testdata/defaults.json.golden +++ b/tests/golden/testdata/defaults.json.golden @@ -28,7 +28,8 @@ "vehicle_start_time": false }, "enable": { - "cluster": false + "cluster": false, + "interleave": false } }, "objectives": { diff --git a/tests/golden/testdata/direct_precedence.json.golden b/tests/golden/testdata/direct_precedence.json.golden index ab29c0d..6b908b8 100644 --- a/tests/golden/testdata/direct_precedence.json.golden +++ b/tests/golden/testdata/direct_precedence.json.golden @@ -28,7 +28,8 @@ "vehicle_start_time": false }, "enable": { - "cluster": false + "cluster": false, + "interleave": false } }, "objectives": { diff --git a/tests/golden/testdata/direct_precedence_linked.json.golden b/tests/golden/testdata/direct_precedence_linked.json.golden index d319218..11f302e 100644 --- a/tests/golden/testdata/direct_precedence_linked.json.golden +++ b/tests/golden/testdata/direct_precedence_linked.json.golden @@ -28,7 +28,8 @@ "vehicle_start_time": false }, "enable": { - "cluster": false + "cluster": false, + "interleave": false } }, "objectives": { diff --git a/tests/golden/testdata/distance_matrix.json.golden b/tests/golden/testdata/distance_matrix.json.golden index 52b5f63..b426bf2 100644 --- a/tests/golden/testdata/distance_matrix.json.golden +++ b/tests/golden/testdata/distance_matrix.json.golden @@ -28,7 +28,8 @@ "vehicle_start_time": false }, "enable": { - "cluster": false + "cluster": false, + "interleave": false } }, "objectives": { diff --git a/tests/golden/testdata/duration_groups.json.golden b/tests/golden/testdata/duration_groups.json.golden index 58b73c2..cfd9e84 100644 --- a/tests/golden/testdata/duration_groups.json.golden +++ b/tests/golden/testdata/duration_groups.json.golden @@ -28,7 +28,8 @@ "vehicle_start_time": false }, "enable": { - "cluster": false + "cluster": false, + "interleave": false } }, "objectives": { diff --git a/tests/golden/testdata/duration_groups_with_stop_multiplier.json.golden b/tests/golden/testdata/duration_groups_with_stop_multiplier.json.golden index d8422d7..4213c05 100644 --- a/tests/golden/testdata/duration_groups_with_stop_multiplier.json.golden +++ b/tests/golden/testdata/duration_groups_with_stop_multiplier.json.golden @@ -28,7 +28,8 @@ "vehicle_start_time": false }, "enable": { - "cluster": false + "cluster": false, + "interleave": false } }, "objectives": { diff --git a/tests/golden/testdata/duration_matrix.json.golden b/tests/golden/testdata/duration_matrix.json.golden index b465359..4d31687 100644 --- a/tests/golden/testdata/duration_matrix.json.golden +++ b/tests/golden/testdata/duration_matrix.json.golden @@ -28,7 +28,8 @@ "vehicle_start_time": false }, "enable": { - "cluster": false + "cluster": false, + "interleave": false } }, "objectives": { diff --git a/tests/golden/testdata/early_arrival_penalty.json.golden b/tests/golden/testdata/early_arrival_penalty.json.golden index 61518bc..af5826d 100644 --- a/tests/golden/testdata/early_arrival_penalty.json.golden +++ b/tests/golden/testdata/early_arrival_penalty.json.golden @@ -28,7 +28,8 @@ "vehicle_start_time": false }, "enable": { - "cluster": false + "cluster": false, + "interleave": false } }, "objectives": { diff --git a/tests/golden/testdata/initial_stops.json.golden b/tests/golden/testdata/initial_stops.json.golden index 08c9c18..0e0137c 100644 --- a/tests/golden/testdata/initial_stops.json.golden +++ b/tests/golden/testdata/initial_stops.json.golden @@ -28,7 +28,8 @@ "vehicle_start_time": false }, "enable": { - "cluster": false + "cluster": false, + "interleave": false } }, "objectives": { diff --git a/tests/golden/testdata/initial_stops_infeasible_compatibility.json.golden b/tests/golden/testdata/initial_stops_infeasible_compatibility.json.golden index ebcd026..6471897 100644 --- a/tests/golden/testdata/initial_stops_infeasible_compatibility.json.golden +++ b/tests/golden/testdata/initial_stops_infeasible_compatibility.json.golden @@ -28,7 +28,8 @@ "vehicle_start_time": false }, "enable": { - "cluster": false + "cluster": false, + "interleave": false } }, "objectives": { diff --git a/tests/golden/testdata/initial_stops_infeasible_max_duration.json.golden b/tests/golden/testdata/initial_stops_infeasible_max_duration.json.golden index 8bddde4..01bb23f 100644 --- a/tests/golden/testdata/initial_stops_infeasible_max_duration.json.golden +++ b/tests/golden/testdata/initial_stops_infeasible_max_duration.json.golden @@ -28,7 +28,8 @@ "vehicle_start_time": false }, "enable": { - "cluster": false + "cluster": false, + "interleave": false } }, "objectives": { diff --git a/tests/golden/testdata/initial_stops_infeasible_remove_all.json.golden b/tests/golden/testdata/initial_stops_infeasible_remove_all.json.golden index 1d6dfe0..a4a3c72 100644 --- a/tests/golden/testdata/initial_stops_infeasible_remove_all.json.golden +++ b/tests/golden/testdata/initial_stops_infeasible_remove_all.json.golden @@ -28,7 +28,8 @@ "vehicle_start_time": false }, "enable": { - "cluster": false + "cluster": false, + "interleave": false } }, "objectives": { diff --git a/tests/golden/testdata/initial_stops_infeasible_temporal.json.golden b/tests/golden/testdata/initial_stops_infeasible_temporal.json.golden index 5dbbc60..6317533 100644 --- a/tests/golden/testdata/initial_stops_infeasible_temporal.json.golden +++ b/tests/golden/testdata/initial_stops_infeasible_temporal.json.golden @@ -28,7 +28,8 @@ "vehicle_start_time": false }, "enable": { - "cluster": false + "cluster": false, + "interleave": false } }, "objectives": { diff --git a/tests/golden/testdata/initial_stops_infeasible_tuple.json.golden b/tests/golden/testdata/initial_stops_infeasible_tuple.json.golden index ecc6b78..fcd0b12 100644 --- a/tests/golden/testdata/initial_stops_infeasible_tuple.json.golden +++ b/tests/golden/testdata/initial_stops_infeasible_tuple.json.golden @@ -28,7 +28,8 @@ "vehicle_start_time": false }, "enable": { - "cluster": false + "cluster": false, + "interleave": false } }, "objectives": { diff --git a/tests/golden/testdata/late_arrival_penalty.json.golden b/tests/golden/testdata/late_arrival_penalty.json.golden index 4e93c70..e230ccf 100644 --- a/tests/golden/testdata/late_arrival_penalty.json.golden +++ b/tests/golden/testdata/late_arrival_penalty.json.golden @@ -28,7 +28,8 @@ "vehicle_start_time": false }, "enable": { - "cluster": false + "cluster": false, + "interleave": false } }, "objectives": { diff --git a/tests/golden/testdata/max_distance.json.golden b/tests/golden/testdata/max_distance.json.golden index 4bd92cb..9c85fca 100644 --- a/tests/golden/testdata/max_distance.json.golden +++ b/tests/golden/testdata/max_distance.json.golden @@ -28,7 +28,8 @@ "vehicle_start_time": false }, "enable": { - "cluster": false + "cluster": false, + "interleave": false } }, "objectives": { @@ -75,10 +76,10 @@ "name": "1 * vehicles_duration + 1 * unplanned_penalty", "objectives": [ { - "base": 148.90959499292012, + "base": 148.9095949929201, "factor": 1, "name": "vehicles_duration", - "value": 148.90959499292012 + "value": 148.9095949929201 }, { "base": 6000000, diff --git a/tests/golden/testdata/max_duration.json.golden b/tests/golden/testdata/max_duration.json.golden index f5feb00..f71e17f 100644 --- a/tests/golden/testdata/max_duration.json.golden +++ b/tests/golden/testdata/max_duration.json.golden @@ -28,7 +28,8 @@ "vehicle_start_time": false }, "enable": { - "cluster": false + "cluster": false, + "interleave": false } }, "objectives": { diff --git a/tests/golden/testdata/max_stops.json.golden b/tests/golden/testdata/max_stops.json.golden index a61a3b6..e39a3f2 100644 --- a/tests/golden/testdata/max_stops.json.golden +++ b/tests/golden/testdata/max_stops.json.golden @@ -28,7 +28,8 @@ "vehicle_start_time": false }, "enable": { - "cluster": false + "cluster": false, + "interleave": false } }, "objectives": { diff --git a/tests/golden/testdata/max_wait_stop.json.golden b/tests/golden/testdata/max_wait_stop.json.golden index b3ff703..ee78e76 100644 --- a/tests/golden/testdata/max_wait_stop.json.golden +++ b/tests/golden/testdata/max_wait_stop.json.golden @@ -28,7 +28,8 @@ "vehicle_start_time": false }, "enable": { - "cluster": false + "cluster": false, + "interleave": false } }, "objectives": { diff --git a/tests/golden/testdata/max_wait_vehicle.json.golden b/tests/golden/testdata/max_wait_vehicle.json.golden index 576d260..76615fd 100644 --- a/tests/golden/testdata/max_wait_vehicle.json.golden +++ b/tests/golden/testdata/max_wait_vehicle.json.golden @@ -28,7 +28,8 @@ "vehicle_start_time": false }, "enable": { - "cluster": false + "cluster": false, + "interleave": false } }, "objectives": { diff --git a/tests/golden/testdata/min_stops.json.golden b/tests/golden/testdata/min_stops.json.golden index ea7b1ba..80e0405 100644 --- a/tests/golden/testdata/min_stops.json.golden +++ b/tests/golden/testdata/min_stops.json.golden @@ -28,7 +28,8 @@ "vehicle_start_time": false }, "enable": { - "cluster": false + "cluster": false, + "interleave": false } }, "objectives": { @@ -75,10 +76,10 @@ "name": "1 * vehicles_duration + 1 * unplanned_penalty + 1 * min_stops", "objectives": [ { - "base": 909.0466359667603, + "base": 909.04663596676, "factor": 1, "name": "vehicles_duration", - "value": 909.0466359667603 + "value": 909.04663596676 }, { "factor": 1, @@ -91,7 +92,7 @@ "value": 0 } ], - "value": 909.0466359667603 + "value": 909.04663596676 }, "unplanned": [], "vehicles": [ @@ -214,7 +215,7 @@ "unplanned_stops": 0 }, "duration": 0.123, - "value": 909.0466359667603 + "value": 909.04663596676 }, "run": { "duration": 0.123, diff --git a/tests/golden/testdata/multi_window.json.golden b/tests/golden/testdata/multi_window.json.golden index 87b4b6d..b28c93c 100644 --- a/tests/golden/testdata/multi_window.json.golden +++ b/tests/golden/testdata/multi_window.json.golden @@ -28,7 +28,8 @@ "vehicle_start_time": false }, "enable": { - "cluster": false + "cluster": false, + "interleave": false } }, "objectives": { diff --git a/tests/golden/testdata/no_mix.json.golden b/tests/golden/testdata/no_mix.json.golden index 2de194c..da10501 100644 --- a/tests/golden/testdata/no_mix.json.golden +++ b/tests/golden/testdata/no_mix.json.golden @@ -28,7 +28,8 @@ "vehicle_start_time": false }, "enable": { - "cluster": false + "cluster": false, + "interleave": false } }, "objectives": { diff --git a/tests/golden/testdata/precedence pathologic.json.golden b/tests/golden/testdata/precedence pathologic.json.golden index 9b6a7b1..7e43092 100644 --- a/tests/golden/testdata/precedence pathologic.json.golden +++ b/tests/golden/testdata/precedence pathologic.json.golden @@ -28,7 +28,8 @@ "vehicle_start_time": false }, "enable": { - "cluster": false + "cluster": false, + "interleave": false } }, "objectives": { diff --git a/tests/golden/testdata/precedence.json.golden b/tests/golden/testdata/precedence.json.golden index b9d1f66..bea2730 100644 --- a/tests/golden/testdata/precedence.json.golden +++ b/tests/golden/testdata/precedence.json.golden @@ -28,7 +28,8 @@ "vehicle_start_time": false }, "enable": { - "cluster": false + "cluster": false, + "interleave": false } }, "objectives": { diff --git a/tests/golden/testdata/start_level.json.golden b/tests/golden/testdata/start_level.json.golden index 5864c38..6ee53b0 100644 --- a/tests/golden/testdata/start_level.json.golden +++ b/tests/golden/testdata/start_level.json.golden @@ -28,7 +28,8 @@ "vehicle_start_time": false }, "enable": { - "cluster": false + "cluster": false, + "interleave": false } }, "objectives": { diff --git a/tests/golden/testdata/start_time_window.json.golden b/tests/golden/testdata/start_time_window.json.golden index 16c6643..a0267dc 100644 --- a/tests/golden/testdata/start_time_window.json.golden +++ b/tests/golden/testdata/start_time_window.json.golden @@ -28,7 +28,8 @@ "vehicle_start_time": false }, "enable": { - "cluster": false + "cluster": false, + "interleave": false } }, "objectives": { diff --git a/tests/golden/testdata/stop_duration.json.golden b/tests/golden/testdata/stop_duration.json.golden index 3314ad3..240aeee 100644 --- a/tests/golden/testdata/stop_duration.json.golden +++ b/tests/golden/testdata/stop_duration.json.golden @@ -28,7 +28,8 @@ "vehicle_start_time": false }, "enable": { - "cluster": false + "cluster": false, + "interleave": false } }, "objectives": { diff --git a/tests/golden/testdata/stop_duration_multiplier.json.golden b/tests/golden/testdata/stop_duration_multiplier.json.golden index b91363a..c75db77 100644 --- a/tests/golden/testdata/stop_duration_multiplier.json.golden +++ b/tests/golden/testdata/stop_duration_multiplier.json.golden @@ -28,7 +28,8 @@ "vehicle_start_time": false }, "enable": { - "cluster": false + "cluster": false, + "interleave": false } }, "objectives": { diff --git a/tests/golden/testdata/stop_groups.json.golden b/tests/golden/testdata/stop_groups.json.golden index f1d092a..f8ffcc7 100644 --- a/tests/golden/testdata/stop_groups.json.golden +++ b/tests/golden/testdata/stop_groups.json.golden @@ -28,7 +28,8 @@ "vehicle_start_time": false }, "enable": { - "cluster": false + "cluster": false, + "interleave": false } }, "objectives": { diff --git a/tests/golden/testdata/template_input.json.golden b/tests/golden/testdata/template_input.json.golden index 0d9dd72..7c06c4b 100644 --- a/tests/golden/testdata/template_input.json.golden +++ b/tests/golden/testdata/template_input.json.golden @@ -28,7 +28,8 @@ "vehicle_start_time": false }, "enable": { - "cluster": false + "cluster": false, + "interleave": false } }, "objectives": { diff --git a/tests/golden/testdata/unplanned_penalty.json.golden b/tests/golden/testdata/unplanned_penalty.json.golden index afb418e..7f67943 100644 --- a/tests/golden/testdata/unplanned_penalty.json.golden +++ b/tests/golden/testdata/unplanned_penalty.json.golden @@ -28,7 +28,8 @@ "vehicle_start_time": false }, "enable": { - "cluster": false + "cluster": false, + "interleave": false } }, "objectives": { diff --git a/tests/golden/testdata/vehicle_start_end_location.json.golden b/tests/golden/testdata/vehicle_start_end_location.json.golden index 4924d73..a7574c3 100644 --- a/tests/golden/testdata/vehicle_start_end_location.json.golden +++ b/tests/golden/testdata/vehicle_start_end_location.json.golden @@ -28,7 +28,8 @@ "vehicle_start_time": false }, "enable": { - "cluster": false + "cluster": false, + "interleave": false } }, "objectives": { @@ -75,10 +76,10 @@ "name": "1 * vehicles_duration + 1 * unplanned_penalty", "objectives": [ { - "base": 375.4720230604402, + "base": 375.47202306044016, "factor": 1, "name": "vehicles_duration", - "value": 375.4720230604402 + "value": 375.47202306044016 }, { "factor": 1, @@ -86,7 +87,7 @@ "value": 0 } ], - "value": 375.4720230604402 + "value": 375.47202306044016 }, "unplanned": [], "vehicles": [ @@ -255,7 +256,7 @@ "unplanned_stops": 0 }, "duration": 0.123, - "value": 375.4720230604402 + "value": 375.47202306044016 }, "run": { "duration": 0.123, diff --git a/tests/golden/testdata/vehicle_start_end_time.json.golden b/tests/golden/testdata/vehicle_start_end_time.json.golden index 896a660..409af4c 100644 --- a/tests/golden/testdata/vehicle_start_end_time.json.golden +++ b/tests/golden/testdata/vehicle_start_end_time.json.golden @@ -28,7 +28,8 @@ "vehicle_start_time": false }, "enable": { - "cluster": false + "cluster": false, + "interleave": false } }, "objectives": { diff --git a/tests/golden/testdata/vehicles_duration_objective.json.golden b/tests/golden/testdata/vehicles_duration_objective.json.golden index c4f3078..1f09f6a 100644 --- a/tests/golden/testdata/vehicles_duration_objective.json.golden +++ b/tests/golden/testdata/vehicles_duration_objective.json.golden @@ -28,7 +28,8 @@ "vehicle_start_time": false }, "enable": { - "cluster": false + "cluster": false, + "interleave": false } }, "objectives": { diff --git a/tests/inline_options/input.json.golden b/tests/inline_options/input.json.golden index 8abf09d..ac79ff4 100644 --- a/tests/inline_options/input.json.golden +++ b/tests/inline_options/input.json.golden @@ -28,7 +28,8 @@ "vehicle_start_time": false }, "enable": { - "cluster": false + "cluster": false, + "interleave": false } }, "objectives": { diff --git a/tests/output_options/main.sh.golden b/tests/output_options/main.sh.golden index c34ef90..21734ed 100644 --- a/tests/output_options/main.sh.golden +++ b/tests/output_options/main.sh.golden @@ -18,7 +18,8 @@ "start_time_windows": false }, "enable": { - "cluster": false + "cluster": false, + "interleave": false } }, "objectives": { From a42aa87d1a8d09450926aea02742ccb7371dbd96 Mon Sep 17 00:00:00 2001 From: David Rijsman Date: Wed, 15 May 2024 12:23:32 +0200 Subject: [PATCH 11/11] No sources planned so no first and last planned. --- solution_move_stops_generator.go | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/solution_move_stops_generator.go b/solution_move_stops_generator.go index b4b79d0..1392810 100644 --- a/solution_move_stops_generator.go +++ b/solution_move_stops_generator.go @@ -3,6 +3,7 @@ package nextroute import ( + "fmt" "math" "github.com/nextmv-io/nextroute/common" @@ -137,6 +138,17 @@ func SolutionMoveStopsGenerator( m.(*solutionMoveStopsImpl).stopPositions, combination, target, func() { m.(*solutionMoveStopsImpl).allowed = false m.(*solutionMoveStopsImpl).valueSeen = 1 + + ids := common.Map(m.StopPositions(), func(stopPosition StopPosition) string { + return stopPosition.Stop().ModelStop().ID() + }, + ) + + fmt.Println("yielding move", m.Vehicle().ModelVehicle().ID(), + m.Previous().ModelStop().ID(), + ids, + m.Next().ModelStop().ID(), + ) yield(m) }, shouldStop, @@ -236,10 +248,12 @@ func generate( } } } - for s := first.Next(); s != last; s = s.Next() { - excludePositions[s.Position()-1] = struct{}{} + if first != nil { + for s := first.Next(); s != last; s = s.Next() { + excludePositions[s.Position()-1] = struct{}{} + } + excludePositions[last.Position()-1] = struct{}{} } - excludePositions[last.Position()-1] = struct{}{} } }