Skip to content

Commit

Permalink
Merge branch 'develop' into feature/eng-4867-add-sequence-constraint-…
Browse files Browse the repository at this point in the history
…feature-request
  • Loading branch information
larsbeck committed Apr 29, 2024
2 parents 6dc5eba + 8f5ec34 commit f133570
Show file tree
Hide file tree
Showing 14 changed files with 165 additions and 77 deletions.
7 changes: 6 additions & 1 deletion factory/format.go
Original file line number Diff line number Diff line change
Expand Up @@ -286,9 +286,14 @@ func DefaultCustomResultStatistics(solution nextroute.Solution) schema.CustomRes
}
}

unplannedStops := common.MapSlice(
solution.UnPlannedPlanUnits().SolutionPlanUnits(),
toSolutionOutputStops,
)

return schema.CustomResultStatistics{
ActivatedVehicles: vehicleCount,
UnplannedStops: solution.UnPlannedPlanUnits().Size(),
UnplannedStops: len(unplannedStops),
MaxTravelDuration: maxTravelDuration,
MaxDuration: maxDuration,
MinTravelDuration: minTravelDuration,
Expand Down
3 changes: 3 additions & 0 deletions model_expression_haversine.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ func (h *haversineExpression) Value(
from ModelStop,
to ModelStop,
) float64 {
if from == nil || to == nil || !from.Location().IsValid() || !to.Location().IsValid() {
return 0.0
}
return haversineDistance(from.Location(), to.Location()).
Value(vehicle.Model().DistanceUnit())
}
3 changes: 3 additions & 0 deletions model_expression_measure_byindex.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,5 +52,8 @@ func (m *measureByIndexExpression) SetName(n string) {
}

func (m *measureByIndexExpression) Value(_ ModelVehicleType, from, to ModelStop) float64 {
if from == nil || to == nil || !from.Location().IsValid() || !to.Location().IsValid() {
return 0.0
}
return m.measure.Cost(from.(*stopImpl).measureIndex, to.(*stopImpl).measureIndex)
}
6 changes: 6 additions & 0 deletions model_expression_measure_bypoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,14 @@ func (m *measureByPointExpression) SetName(n string) {
}

func (m *measureByPointExpression) Value(_ ModelVehicleType, from, to ModelStop) float64 {
if from == nil || to == nil {
return 0.0
}
locFrom := from.Location()
locTo := to.Location()
if !locFrom.IsValid() || !locTo.IsValid() {
return 0.0
}
value := m.measure.Cost(
measure.Point{locFrom.Longitude(), locFrom.Latitude()},
measure.Point{locTo.Longitude(), locTo.Latitude()},
Expand Down
7 changes: 7 additions & 0 deletions schema/input.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ package schema

import (
"time"

"github.com/nextmv-io/sdk/measure"
)

// Input is the default input schema for nextroute.
Expand Down Expand Up @@ -226,6 +228,11 @@ type Location struct {
Lat float64 `json:"lat" minimum:"-90" maximum:"90"`
}

// ToPoint converts a schema.Location to a measure.Point.
func (l Location) ToPoint() measure.Point {
return measure.Point{l.Lon, l.Lat}
}

// DurationGroup represents a group of stops that get additional duration
// whenever a stop of the group is approached for the first time.
type DurationGroup struct {
Expand Down
42 changes: 42 additions & 0 deletions solve_events.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,3 +87,45 @@ func (e *BaseEvent2[S, T]) Trigger(payload1 S, payload2 T) {

// Handler2 is a function that handles an event with two payloads.
type Handler2[S any, T any] func(payload1 S, payload2 T)

// BaseEvent3 is a base event type that can be used to implement events
// with three payloads.
type BaseEvent3[S any, T any, U any] struct {
handlers []Handler3[S, T, U]
}

// Register adds an event handler for this event.
func (e *BaseEvent3[S, T, U]) Register(handler Handler3[S, T, U]) {
e.handlers = append(e.handlers, handler)
}

// Trigger sends out an event with the payload.
func (e *BaseEvent3[S, T, U]) Trigger(payload1 S, payload2 T, payload3 U) {
for _, handler := range e.handlers {
handler(payload1, payload2, payload3)
}
}

// Handler3 is a function that handles an event with three payloads.
type Handler3[S any, T any, U any] func(payload1 S, payload2 T, payload3 U)

// BaseEvent4 is a base event type that can be used to implement events
// with four payloads.
type BaseEvent4[S any, T any, U any, V any] struct {
handlers []Handler4[S, T, U, V]
}

// Register adds an event handler for this event.
func (e *BaseEvent4[S, T, U, V]) Register(handler Handler4[S, T, U, V]) {
e.handlers = append(e.handlers, handler)
}

// Trigger sends out an event with the payload.
func (e *BaseEvent4[S, T, U, V]) Trigger(payload1 S, payload2 T, payload3 U, payload4 V) {
for _, handler := range e.handlers {
handler(payload1, payload2, payload3, payload4)
}
}

// Handler4 is a function that handles an event with four payloads.
type Handler4[S any, T any, U any, V any] func(payload1 S, payload2 T, payload3 U, payload4 V)
35 changes: 35 additions & 0 deletions solve_parallel_events.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// © 2019-present nextmv.io inc

package nextroute

// ParallelSolveEvents is a struct that contains events that are fired during a
// solve invocation of the parallel solver.
type ParallelSolveEvents struct {
// End is fired when the parallel solver is done. The first payload is the
// solver, the second payload is the number of iterations, the third payload
// is the best solution found.
End *BaseEvent3[ParallelSolver, int, Solution]

// NewSolution is fired when a new solution is found.
NewSolution *BaseEvent2[ParallelSolveInformation, Solution]

// Start is fired when the parallel solver is started. The first payload is
// the parallel solver, the second payload is the options, the third payload
// is the number of parallel runs will be invoked.
Start *BaseEvent3[ParallelSolver, ParallelSolveOptions, int]
// StartSolver is fired when one of the solver that will run in parallel is
// started. The first payload is the parallel solve information, the second
// payload is the solver, the third payload is the solve options, the fourth
// payload is the start solution.
StartSolver *BaseEvent4[ParallelSolveInformation, Solver, SolveOptions, Solution]
}

// NewParallelSolveEvents creates a new instance of ParallelSolveEvents.
func NewParallelSolveEvents() ParallelSolveEvents {
return ParallelSolveEvents{
End: &BaseEvent3[ParallelSolver, int, Solution]{},
NewSolution: &BaseEvent2[ParallelSolveInformation, Solution]{},
Start: &BaseEvent3[ParallelSolver, ParallelSolveOptions, int]{},
StartSolver: &BaseEvent4[ParallelSolveInformation, Solver, SolveOptions, Solution]{},
}
}
13 changes: 10 additions & 3 deletions solve_solver.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import (
"time"

"github.com/nextmv-io/nextroute/common"
"github.com/nextmv-io/sdk/run"
)

// IntParameterOptions are the options for an integer parameter.
Expand Down Expand Up @@ -247,7 +246,12 @@ func (s *solveImpl) Solve(
s.workSolution = newWorkSolution
s.random = rand.New(rand.NewSource(newWorkSolution.Random().Int63()))

start := ctx.Value(run.Start).(time.Time)
start := time.Now()

ctx, cancel := context.WithDeadline(
ctx,
start.Add(solveOptions.Duration),
)

solveInformation := &solveInformationImpl{
iteration: 0,
Expand All @@ -268,7 +272,10 @@ func (s *solveImpl) Solve(
Error: nil,
}
go func() {
defer close(solutions)
defer func() {
close(solutions)
cancel()
}()

Loop:
for iteration := 0; iteration < solveOptions.Iterations; iteration++ {
Expand Down
114 changes: 44 additions & 70 deletions solve_solver_parallel.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,10 @@ type ParallelSolver interface {
// Solve starts the solving process using the given options. It returns the
// solutions as a channel.
Solve(context.Context, ParallelSolveOptions, ...Solution) (SolutionChannel, error)
// SolveEvents returns the solve-events used by the solver.
// SolveEvents returns the solve-events used by the individual solver instances.
SolveEvents() SolveEvents
// ParallelSolveEvents returns the solve-events used by the parallel solver.
ParallelSolveEvents() ParallelSolveEvents
}

// SolveOptionsFactory is a factory type for creating new solve options.
Expand Down Expand Up @@ -92,11 +94,9 @@ func NewSkeletonParallelSolver(model Model) (ParallelSolver, error) {
return nil, fmt.Errorf("model cannot be nil")
}
parallelSolver := &parallelSolverImpl{
parallelSolverObservedImpl: parallelSolverObservedImpl{
observers: make([]ParallelSolverObserver, 0),
},
solveEvents: NewSolveEvents(),
model: model,
solveEvents: NewSolveEvents(),
parallelSolveEvents: NewParallelSolveEvents(),
model: model,
}

return parallelSolver, nil
Expand Down Expand Up @@ -129,71 +129,23 @@ func (s metaSolveInformationImpl) Random() *rand.Rand {
return s.random
}

// ParallelSolverObserver is the interface for observing the parallel solver.
type ParallelSolverObserver interface {
// OnStart is called when the parallel solver is started.
OnStart(
solver ParallelSolver,
options ParallelSolveOptions,
parallelRuns int,
)
// OnNewRun is called when a new run is started.
OnNewRun(
solver ParallelSolver,
)
// OnNewSolution is called when a new solution is found.
OnNewSolution(
solver ParallelSolver,
solution Solution,
)
}

type parallelSolverObservedImpl struct {
observers []ParallelSolverObserver
}

func (o *parallelSolverObservedImpl) AddMetaSearchObserver(
observer ParallelSolverObserver,
) {
o.observers = append(o.observers, observer)
}

func (o *parallelSolverObservedImpl) OnStart(
solver ParallelSolver,
options ParallelSolveOptions,
parallelRuns int,
) {
for _, observer := range o.observers {
observer.OnStart(solver, options, parallelRuns)
}
}

func (o *parallelSolverObservedImpl) OnNewRun(
solver ParallelSolver,
) {
for _, observer := range o.observers {
observer.OnNewRun(solver)
}
}

func (o *parallelSolverObservedImpl) OnNewSolution(
solver ParallelSolver,
solution Solution,
) {
for _, observer := range o.observers {
observer.OnNewSolution(solver, solution)
}
}

type parallelSolverImpl struct {
parallelSolverObservedImpl
model Model
progression []ProgressionEntry
solveEvents SolveEvents
parallelSolveEvents ParallelSolveEvents
solveOptionsFactory SolveOptionsFactory
solverFactory SolverFactory
}

func (s *parallelSolverImpl) ParallelSolveEvents() ParallelSolveEvents {
return s.parallelSolveEvents
}

func (s *parallelSolverImpl) SolveEvents() SolveEvents {
return s.solveEvents
}

func (s *parallelSolverImpl) Model() Model {
return s.model
}
Expand Down Expand Up @@ -278,7 +230,11 @@ func (s *parallelSolverImpl) Solve(
}
}

start := ctx.Value(run.Start).(time.Time)
start := time.Now()

if ctx.Value(run.Start) != nil {
start = ctx.Value(run.Start).(time.Time)
}

ctx, cancel := context.WithDeadline(
ctx,
Expand All @@ -294,7 +250,11 @@ func (s *parallelSolverImpl) Solve(
parallelRuns = runtime.NumCPU()
}

s.OnStart(s, options, parallelRuns)
s.ParallelSolveEvents().Start.Trigger(
s,
options,
parallelRuns,
)

bestSolution := solutions[0]

Expand Down Expand Up @@ -408,6 +368,14 @@ func (s *parallelSolverImpl) Solve(
if updatedIterations < 0 {
opt.Iterations = int(updatedIterations + int64(opt.Iterations))
}

s.ParallelSolveEvents().StartSolver.Trigger(
metaSolveInformation,
solver,
opt,
solution,
)

solutionChannel, err := solver.Solve(
ctx,
opt,
Expand All @@ -417,6 +385,13 @@ func (s *parallelSolverImpl) Solve(
panic(err)
}
for sol := range solutionChannel {
if sol.Solution != nil {
s.ParallelSolveEvents().NewSolution.Trigger(
metaSolveInformation,
sol.Solution,
)
}

syncResultChannel <- solutionContainer{
Solution: sol,
Error: sol.Error,
Expand All @@ -434,11 +409,14 @@ func (s *parallelSolverImpl) Solve(

go func() {
defer func() {
iterations := int(totalIterations.Load())
if dataMap, ok := ctx.Value(run.Data).(*sync.Map); ok {
converted := int(totalIterations.Load())
converted := iterations
dataMap.Store(Iterations, converted)
}
close(resultChannel)

s.ParallelSolveEvents().End.Trigger(s, iterations, bestSolution)
}()
for solverResult := range syncResultChannel {
if solverResult.Error != nil {
Expand Down Expand Up @@ -468,10 +446,6 @@ func (s *parallelSolverImpl) Solve(
return resultChannel, nil
}

func (s *parallelSolverImpl) SolveEvents() SolveEvents {
return s.solveEvents
}

func (s *parallelSolverImpl) RegisterEvents(
events SolveEvents,
) {
Expand Down
4 changes: 4 additions & 0 deletions solver_parallel.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ type parallelSolverWrapperImpl struct {
solver ParallelSolver
}

func (p *parallelSolverWrapperImpl) ParallelSolveEvents() ParallelSolveEvents {
return p.solver.ParallelSolveEvents()
}

func (p *parallelSolverWrapperImpl) Model() Model {
return p.solver.Model()
}
Expand Down
Loading

0 comments on commit f133570

Please sign in to comment.