diff --git a/solution.go b/solution.go index 2e67eb0..c597bb0 100644 --- a/solution.go +++ b/solution.go @@ -118,12 +118,7 @@ func NewSolution( random := rand.New(rand.NewSource(m.Random().Int63())) - maxExpressionIndex := -1 - for _, expression := range model.expressions { - if expression.Index() > maxExpressionIndex { - maxExpressionIndex = expression.Index() - } - } + nExpressions := len(model.expressions) solution := &solutionImpl{ model: m, @@ -143,8 +138,8 @@ func NewSolution( slack: make([]float64, 0, nrStops), start: make([]float64, 0, nrStops), end: make([]float64, 0, nrStops), - values: make([][]float64, maxExpressionIndex+1), - cumulativeValues: make([][]float64, maxExpressionIndex+1), + values: make(map[int][]float64, nExpressions), + cumulativeValues: make(map[int][]float64, nExpressions), stopToPlanUnit: make([]*solutionPlanStopsUnitImpl, nrStops), constraintStopData: make(map[ModelConstraint][]Copier), objectiveStopData: make(map[ModelObjective][]Copier), @@ -577,12 +572,12 @@ func (s *solutionImpl) addInitialSolution(m Model) error { type solutionImpl struct { model Model scores map[ModelObjective]float64 - values [][]float64 + values map[int][]float64 objectiveStopData map[ModelObjective][]Copier constraintStopData map[ModelConstraint][]Copier objectiveSolutionData map[ModelObjective]Copier constraintSolutionData map[ModelConstraint]Copier - cumulativeValues [][]float64 + cumulativeValues map[int][]float64 // TODO: explore if stopToPlanUnit should rather contain interfaces stopToPlanUnit []*solutionPlanStopsUnitImpl @@ -690,6 +685,7 @@ func (s *solutionImpl) Copy() Solution { // to the correct size nrStops := len(s.stop) nrVehicles := len(s.vehicles) + nrExpressions := len(model.expressions) ints := make([]int, 5*nrStops+3*nrVehicles) first, ints := common.CopySliceFrom(ints, s.first) vehicleIndices, ints := common.CopySliceFrom(ints, s.vehicleIndices) @@ -699,12 +695,12 @@ func (s *solutionImpl) Copy() Solution { next, ints := common.CopySliceFrom(ints, s.next) stopPosition, ints := common.CopySliceFrom(ints, s.stopPosition) stop, _ := common.CopySliceFrom(ints, s.stop) - floats := make([]float64, 5*nrStops) + floats := make([]float64, (5+2*nrExpressions)*nrStops) start, floats := common.CopySliceFrom(floats, s.start) end, floats := common.CopySliceFrom(floats, s.end) arrival, floats := common.CopySliceFrom(floats, s.arrival) slack, floats := common.CopySliceFrom(floats, s.slack) - cumulativeTravelDuration, _ := common.CopySliceFrom(floats, s.cumulativeTravelDuration) + cumulativeTravelDuration, floats := common.CopySliceFrom(floats, s.cumulativeTravelDuration) solution := &solutionImpl{ arrival: arrival, slack: slack, @@ -713,7 +709,7 @@ func (s *solutionImpl) Copy() Solution { constraintSolutionData: make(map[ModelConstraint]Copier, len(s.constraintSolutionData)), objectiveSolutionData: make(map[ModelObjective]Copier, len(s.objectiveSolutionData)), cumulativeTravelDuration: cumulativeTravelDuration, - cumulativeValues: make([][]float64, len(s.cumulativeValues)), + cumulativeValues: make(map[int][]float64, len(s.cumulativeValues)), stopToPlanUnit: make([]*solutionPlanStopsUnitImpl, len(s.stopToPlanUnit)), end: end, first: first, @@ -725,7 +721,7 @@ func (s *solutionImpl) Copy() Solution { start: start, stop: stop, stopPosition: stopPosition, - values: make([][]float64, len(s.values)), + values: make(map[int][]float64, len(s.values)), vehicleIndices: vehicleIndices, random: random, fixedPlanUnits: newSolutionPlanUnitCollectionBaseImpl( @@ -752,8 +748,9 @@ func (s *solutionImpl) Copy() Solution { } for _, expression := range model.expressions { - solution.cumulativeValues[expression.Index()] = slices.Clone(s.cumulativeValues[expression.Index()]) - solution.values[expression.Index()] = slices.Clone(s.values[expression.Index()]) + eIndex := expression.Index() + solution.cumulativeValues[eIndex], floats = common.CopySliceFrom(floats, s.cumulativeValues[eIndex]) + solution.values[eIndex], floats = common.CopySliceFrom(floats, s.values[eIndex]) } for _, constraint := range model.constraintsWithStopUpdater { diff --git a/solution_move_stops_generator.go b/solution_move_stops_generator.go index 76e5bbb..f65f10f 100644 --- a/solution_move_stops_generator.go +++ b/solution_move_stops_generator.go @@ -2,10 +2,6 @@ package nextroute -import ( - "github.com/nextmv-io/nextroute/common" -) - // SolutionMoveStopsGeneratorChannel generates all possible moves for a given // vehicle and plan unit. // @@ -101,12 +97,8 @@ func SolutionMoveStopsGenerator( preAllocatedMoveContainer *PreAllocatedMoveContainer, shouldStop func() bool, ) { - source := common.Map(stops, func(stop SolutionStop) SolutionStop { - return stop - }) - target := common.Map(vehicle.SolutionStops(), func(stop SolutionStop) SolutionStop { - return stop - }) + source := stops + target := vehicle.SolutionStops() m := preAllocatedMoveContainer.singleStopPosSolutionMoveStop m.(*solutionMoveStopsImpl).reset() m.(*solutionMoveStopsImpl).planUnit = planUnit diff --git a/solution_plan_stops_unit.go b/solution_plan_stops_unit.go index 4d7e6b1..7552ad5 100644 --- a/solution_plan_stops_unit.go +++ b/solution_plan_stops_unit.go @@ -101,9 +101,7 @@ func (p *solutionPlanStopsUnitImpl) SolutionStops() SolutionStops { } func (p *solutionPlanStopsUnitImpl) solutionStopsImpl() []SolutionStop { - solutionStops := make([]SolutionStop, len(p.solutionStops)) - copy(solutionStops, p.solutionStops) - return solutionStops + return p.solutionStops } func (p *solutionPlanStopsUnitImpl) IsPlanned() bool { diff --git a/solution_test.go b/solution_test.go new file mode 100644 index 0000000..3a18842 --- /dev/null +++ b/solution_test.go @@ -0,0 +1,76 @@ +// © 2019-present nextmv.io inc + +package nextroute_test + +import ( + "testing" + "time" + + "github.com/nextmv-io/nextroute" +) + +func BenchmarkAllocationsSolution(b *testing.B) { + for i := 0; i < b.N; i++ { + b.StopTimer() + model, err := createModel(singleVehiclePlanSequenceModel()) + if err != nil { + b.Error(err) + } + + maximum := nextroute.NewVehicleTypeDurationExpression( + "maximum duration", + 3*time.Minute, + ) + expression := nextroute.NewStopExpression("test", 2.0) + + cnstr, err := nextroute.NewMaximum(expression, maximum) + if err != nil { + b.Error(err) + } + + err = model.AddConstraint(cnstr) + if err != nil { + b.Error(err) + } + b.StartTimer() + _, err = nextroute.NewSolution(model) + if err != nil { + b.Fatal(err) + } + } +} + +// TestLimitAllocations tests the number of allocations in the solution creation. +// We want to ensure that the number of allocations is limited and does not grow +// accidentally. +func TestLimitAllocations(t *testing.T) { + model, err := createModel(singleVehiclePlanSequenceModel()) + if err != nil { + t.Error(err) + } + + maximum := nextroute.NewVehicleTypeDurationExpression( + "maximum duration", + 3*time.Minute, + ) + expression := nextroute.NewStopExpression("test", 2.0) + + cnstr, err := nextroute.NewMaximum(expression, maximum) + if err != nil { + t.Error(err) + } + + err = model.AddConstraint(cnstr) + if err != nil { + t.Error(err) + } + allocs := testing.AllocsPerRun(2, func() { + _, err = nextroute.NewSolution(model) + if err != nil { + t.Fatal(err) + } + }) + if allocs > 66 { + t.Errorf("expected 66 allocations, got %v", allocs) + } +}