Skip to content

Commit

Permalink
Merge pull request #35 from nextmv-io/feature/eng-4867-add-sequence-c…
Browse files Browse the repository at this point in the history
…onstraint-feature-request

Allowing direct successors and disallowing successors
  • Loading branch information
larsbeck authored May 1, 2024
2 parents 0003d5d + d030c88 commit 06eb7e8
Show file tree
Hide file tree
Showing 24 changed files with 2,005 additions and 102 deletions.
13 changes: 13 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -303,3 +303,16 @@ issues:
linters:
- govet
text: structtag
# We want to keep the error message as is.
- path: precedence\.go
linters:
- goconst
- path: model_directed_acyclic_graph\.go
linters:
- nestif
- path: solution_move_stops_generator\.go
linters:
- nestif
- path: solution_sequence_generator\.go
linters:
- nestif
4 changes: 3 additions & 1 deletion factory/data.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,12 @@ type group struct {
}

// sequence represents two stops that must be part of the same planUnit. The
// predecessor must be visited before the successor.
// predecessor must be visited before the successor; the direct field indicates
// if the successor must be the direct successor of the predecessor.
type sequence struct {
predecessor string
successor string
direct bool
}

// latestStartExpression returns the StopTimeExpression that represents the
Expand Down
17 changes: 13 additions & 4 deletions factory/plan_units.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,11 @@ func addPlanUnits(
if err != nil {
return nil, err
}
if len(units) > 1 {
_, err := model.NewPlanAllPlanUnits(true, units...)
uniquePlanUnits := common.UniqueDefined(units, func(t nextroute.ModelPlanUnit) int {
return t.Index()
})
if len(uniquePlanUnits) > 1 {
_, err := model.NewPlanAllPlanUnits(true, uniquePlanUnits...)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -145,7 +148,7 @@ func allSequences(data modelData) [][]sequence {
continue
// both predecessor and successor are already in a unit and not the same unit.
} else if unitIndex1 != -1 && unitIndex2 != -1 && unitIndex1 != unitIndex2 {
units, inUnit = mergeUnits(unitIndex1, unitIndex2, units, inUnit)
units, inUnit = mergeUnits(unitIndex1, unitIndex2, units, inUnit, dataSequence)
continue
// both predecessor and successor are already in a unit and the same unit.
} else if unitIndex1 != -1 && unitIndex2 != -1 && unitIndex1 == unitIndex2 {
Expand Down Expand Up @@ -181,6 +184,7 @@ func mergeUnits(
unitIndex2 int,
units []unitInformation,
inUnit map[string]int,
datasequence sequence,
) ([]unitInformation, map[string]int) {
if unitIndex1 > unitIndex2 {
unitIndex1, unitIndex2 = unitIndex2, unitIndex1
Expand All @@ -197,6 +201,7 @@ func mergeUnits(
units[unitIndex1].stops[stop] = struct{}{}
}
units[unitIndex1].sequences = append(units[unitIndex1].sequences, oldUnit.sequences...)
units[unitIndex1].sequences = append(units[unitIndex1].sequences, datasequence)
return units, inUnit
}

Expand Down Expand Up @@ -238,7 +243,11 @@ func buildDirectedAcyclicGraph(
return nil, err
}

if err = dag.AddArc(origin, destination); err != nil {
if sequence.direct {
if err = dag.AddDirectArc(origin, destination); err != nil {
return nil, err
}
} else if err = dag.AddArc(origin, destination); err != nil {
return nil, err
}
}
Expand Down
74 changes: 47 additions & 27 deletions factory/precedence.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,48 +55,66 @@ func addPrecedenceInformation(
// precedence processes the "Precedes" or "Succeeds" field of a stop. It return
// the precedence (succeeds or precedes) as a slice of strings, even for a
// single string.
func precedence(stop schema.Stop, name string) ([]string, error) {
func precedence(stop schema.Stop, name string) ([]precedenceData, error) {
field := reflect.ValueOf(stop).FieldByName(name).Interface()
var precedence []string
var precedence []precedenceData
if field == nil {
return precedence, nil
}

parsed, ok := field.([]any)
if ok {
for i, v := range parsed {
value, ok := v.(string)
if !ok {
// field can be a string, a slice of strings or a slice of structs of type
// precedenceData.
switch field := field.(type) {
case string:
precedence = append(precedence, precedenceData{id: field})
return precedence, nil
case []any:
for i, element := range field {
switch element := element.(type) {
case string:
precedence = append(precedence, precedenceData{id: element})
case map[string]any:
if id, ok := element["id"].(string); ok {
direct, _ := element["direct"].(bool)
precedence = append(precedence, precedenceData{id: id, direct: direct})
} else {
return nil,
nmerror.NewInputDataError(fmt.Errorf(
"could not obtain %s from stop %s, "+
"element %v in slice is missing field id",
name,
stop.ID,
i,
))
}
default:
return nil,
nmerror.NewInputDataError(fmt.Errorf(
"could not obtain %s from stop %s, "+
"element %v in slice is not string, got %v",
"element %v in slice is neither string nor struct, got %v",
name,
stop.ID,
i,
v,
element,
))
}
precedence = append(precedence, value)
}

return precedence, nil
}

value, ok := field.(string)
if ok {
precedence = append(precedence, value)
return precedence, nil
default:
return nil,
fmt.Errorf(
"could not obtain %s from stop %s, "+
"it is not of type string or slice of string or slice of structs with fields id and direct, got %v",
name,
stop.ID,
field,
)
}
}

return nil,
fmt.Errorf(
"could not obtain %s from stop %s, "+
"it is neither slice of string or string, got %v",
name,
stop.ID,
field,
)
type precedenceData struct {
id string
direct bool
}

// getSequences returns all the sequences for a stop, based on the "precedes"
Expand All @@ -113,7 +131,8 @@ func getSequences(stop schema.Stop) ([]sequence, error) {
for i, p := range precedes {
predecessorSequences[i] = sequence{
predecessor: stop.ID,
successor: p,
successor: p.id,
direct: p.direct,
}
}
sequences = append(sequences, predecessorSequences...)
Expand All @@ -128,8 +147,9 @@ func getSequences(stop schema.Stop) ([]sequence, error) {
successorSequences := make([]sequence, len(succeeds))
for i, s := range succeeds {
successorSequences[i] = sequence{
predecessor: s,
predecessor: s.id,
successor: stop.ID,
direct: s.direct,
}
}

Expand Down
8 changes: 4 additions & 4 deletions factory/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -387,11 +387,11 @@ func validateStop(idx int, stop schema.Stop, stopIDs map[string]bool) error {
}

for _, p := range precedes {
if !stopIDs[p] {
if !stopIDs[p.id] {
return nmerror.NewInputDataError(fmt.Errorf(
"stop `%s` precedes references unknown stop %s",
stop.ID,
p,
p.id,
))
}

Expand All @@ -403,11 +403,11 @@ func validateStop(idx int, stop schema.Stop, stopIDs map[string]bool) error {
}
}
for _, s := range succeeds {
if !stopIDs[s] {
if !stopIDs[s.id] {
return nmerror.NewInputDataError(fmt.Errorf(
"stop `%s` succeeds references unknown stop %s",
stop.ID,
s,
s.id,
))
}

Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module github.com/nextmv-io/nextroute
go 1.21

require (
github.com/nextmv-io/sdk v1.5.0
github.com/nextmv-io/sdk v1.6.0
gonum.org/v1/gonum v0.14.0
)

Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -301,8 +301,8 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nextmv-io/sdk v1.5.0 h1:sbaO/99qq7Yf1FeGZSmbbjWCum9gXE/vnT/fbsCzuf8=
github.com/nextmv-io/sdk v1.5.0/go.mod h1:4kKTivuXdlx2ky+ZkBeUkTPIc8BTJ0PqKFYF3B+wCy4=
github.com/nextmv-io/sdk v1.6.0 h1:WU9/znVN9XzXNUXWoQu9t6xZCHZ4AOaKBi4yqFVLLG0=
github.com/nextmv-io/sdk v1.6.0/go.mod h1:4kKTivuXdlx2ky+ZkBeUkTPIc8BTJ0PqKFYF3B+wCy4=
github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
Expand Down
8 changes: 8 additions & 0 deletions model.go
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,7 @@ type modelImpl struct {
sequenceSampleSize int
mutex sync.RWMutex
isLocked bool
disallowedSuccessors [][]bool
}

func (m *modelImpl) Vehicles() ModelVehicles {
Expand Down Expand Up @@ -615,6 +616,13 @@ func (m *modelImpl) lock() error {
if m.isLocked {
return nil
}

// initialize disallowedSuccessors
m.disallowedSuccessors = make([][]bool, m.NumberOfStops())
for i := range m.disallowedSuccessors {
m.disallowedSuccessors[i] = make([]bool, m.NumberOfStops())
}

m.setConstraintEstimationOrder()
for _, constraint := range m.constraints {
if locker, ok := constraint.(Locker); ok {
Expand Down
4 changes: 2 additions & 2 deletions model_constraint_attributes.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ func (l *attributesConstraintImpl) SetStopAttributes(
stopAttributes []string,
) error {
if stop.Model().IsLocked() {
return fmt.Errorf("cannot set stop attributes after model is locked")
return fmt.Errorf(lockErrorMessage, "set stop attributes")
}
l.stopAttributes[stop.Index()] = common.Unique(stopAttributes)
return nil
Expand All @@ -147,7 +147,7 @@ func (l *attributesConstraintImpl) SetVehicleTypeAttributes(
vehicleAttributes []string,
) error {
if vehicleType.Model().IsLocked() {
return fmt.Errorf("cannot set vehicle type attributes after model is locked")
return fmt.Errorf(lockErrorMessage, "set vehicle type attributes")
}
l.vehicleTypeAttributes[vehicleType.Index()] = common.Unique(vehicleAttributes)
return nil
Expand Down
2 changes: 1 addition & 1 deletion model_constraint_maximum_duration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ func TestMaximumDurationConstraint_EstimateIsViolated(t *testing.T) {
t.Fatal(err)
}
moveSingleOnVehicle, err = nextroute.NewMoveStops(
solutionSingleStopPlanUnit0,
solutionSingleStopPlanUnit1,
[]nextroute.StopPosition{position},
)
if err != nil {
Expand Down
Loading

0 comments on commit 06eb7e8

Please sign in to comment.