Skip to content

Commit

Permalink
Merge pull request #28 from nextmv-io/feature/eng-5017-nextroute-cust…
Browse files Browse the repository at this point in the history
…om-move-can-result-in-infeasible-solution

Test and fix infeasible custom moves.
  • Loading branch information
davidrijsman authored Apr 22, 2024
2 parents 865ccaf + e2ae211 commit 8f6caa0
Show file tree
Hide file tree
Showing 3 changed files with 141 additions and 26 deletions.
12 changes: 6 additions & 6 deletions model_constraint_cluster_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -192,8 +192,8 @@ func TestClusterConstraint_EstimateIsViolated(t *testing.T) {
if err != nil {
t.Error(err)
}
if !b {
t.Error("move could not be executed")
if b {
t.Error("move resulted in planned planunit although it results in infeasible solution")
}

solutionSequencePlanUnit = solution.SolutionPlanStopsUnit(sequencePlanUnits[1])
Expand Down Expand Up @@ -221,16 +221,16 @@ func TestClusterConstraint_EstimateIsViolated(t *testing.T) {
t.Fatal(err)
}

if violated, _ := cnstr.EstimateIsViolated(moveSequenceOnVehicle0); violated {
t.Error("constraint is violated")
if violated, _ := cnstr.EstimateIsViolated(moveSequenceOnVehicle0); !violated {
t.Error("constraint is not violated")
}

b, err = moveSequenceOnVehicle0.Execute(context.Background())
if err != nil {
t.Error(err)
}
if !b {
t.Error("move could not be executed")
if b {
t.Error("move is executed and planned")
}
}

Expand Down
30 changes: 10 additions & 20 deletions solution_move_stops.go
Original file line number Diff line number Diff line change
Expand Up @@ -624,26 +624,16 @@ func NewMoveStops(
for i, stopPosition := range stopPositions {
stopPositionsImpl[i] = stopPosition.(stopPositionImpl)
}

return newMove(
planUnit.(*solutionPlanStopsUnitImpl),
stopPositionsImpl,
0.0,
0,
), nil
}

func newMove(
planUnit *solutionPlanStopsUnitImpl,
stopPositions []stopPositionImpl,
value float64,
valueSeen int,
) *solutionMoveStopsImpl {
return &solutionMoveStopsImpl{
planUnit: planUnit,
stopPositions: stopPositions,
value: value,
valueSeen: valueSeen,
move := &solutionMoveStopsImpl{
planUnit: planUnit.(*solutionPlanStopsUnitImpl),
stopPositions: stopPositionsImpl,
value: 0.0,
valueSeen: 0,
allowed: true,
}
value, allowed, _ := planUnit.(*solutionPlanStopsUnitImpl).solution().checkConstraintsAndEstimateDeltaScore(move)
move.value = value
move.allowed = allowed
move.valueSeen = 1
return move, nil
}
125 changes: 125 additions & 0 deletions solution_move_stops_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
// © 2019-present nextmv.io inc

package nextroute_test

import (
"context"
"testing"

"github.com/nextmv-io/nextroute"
)

func TestSolutionMoveStops(t *testing.T) {
model, err := createModel(singleVehiclePlanSequenceModel())
if err != nil {
t.Fatal(err)
}

s1 := model.Stops()[0]
s2 := model.Stops()[1]
s3 := model.Stops()[2]
s4 := model.Stops()[3]

mixItems := map[nextroute.ModelStop]nextroute.MixItem{
s1: {
Name: "avocados",
Quantity: 1,
},
s2: {
Name: "avocados",
Quantity: -1,
},
s3: {
Name: "grapes",
Quantity: 1,
},
s4: {
Name: "grapes",
Quantity: -1,
},
}

noMixConstraint, err := nextroute.NewNoMixConstraint(mixItems)
if err != nil {
t.Fatal(err)
}

err = model.AddConstraint(noMixConstraint)
if err != nil {
t.Fatal(err)
}

solution, err := nextroute.NewSolution(model)
if err != nil {
t.Fatal(err)
}

// Let's add a plan unit to the vehicle
v := solution.Vehicles()[0]
planUnit0 := solution.UnPlannedPlanUnits().RandomElement().(nextroute.SolutionPlanStopsUnit)
position1, err := nextroute.NewStopPosition(
v.First(),
planUnit0.SolutionStops()[0],
planUnit0.SolutionStops()[1],
)
if err != nil {
t.Fatal(err)
}
position2, err := nextroute.NewStopPosition(
planUnit0.SolutionStops()[0],
planUnit0.SolutionStops()[1],
v.Last(),
)
if err != nil {
t.Fatal(err)
}
move0, err := nextroute.NewMoveStops(
planUnit0,
nextroute.StopPositions{position1, position2},
)
if err != nil {
t.Fatal(err)
}

planned, err := move0.Execute(context.Background())
if err != nil {
t.Fatal(err)
}
if !planned {
t.Fatal("expected move to be planned")
}

// Let's add the other fruit in the vehicle while there is still the other
// fruit in the vehicle (which is not allowed)
planUnit1 := solution.UnPlannedPlanUnits().RandomElement().(nextroute.SolutionPlanStopsUnit)
position3, err := nextroute.NewStopPosition(
v.First(),
planUnit1.SolutionStops()[0],
v.First().Next(),
)
if err != nil {
t.Fatal(err)
}
position4, err := nextroute.NewStopPosition(
v.First().Next(),
planUnit1.SolutionStops()[1],
v.First().Next().Next(),
)
if err != nil {
t.Fatal(err)
}
move1, err := nextroute.NewMoveStops(
planUnit1,
nextroute.StopPositions{position3, position4},
)
if err != nil {
t.Fatal(err)
}
planned, err = move1.Execute(context.Background())
if err != nil {
t.Fatal(err)
}
if planned {
t.Fatal("expected move to not be planned")
}
}

0 comments on commit 8f6caa0

Please sign in to comment.