nextroute 1.11.1.dev0__cp38-cp38-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of nextroute might be problematic. Click here for more details.
- nextroute/__about__.py +3 -0
- nextroute/__init__.py +18 -0
- nextroute/base_model.py +24 -0
- nextroute/bin/nextroute.exe +0 -0
- nextroute/check/__init__.py +28 -0
- nextroute/check/schema.py +145 -0
- nextroute/options.py +225 -0
- nextroute/schema/__init__.py +29 -0
- nextroute/schema/input.py +87 -0
- nextroute/schema/location.py +16 -0
- nextroute/schema/output.py +140 -0
- nextroute/schema/statistics.py +149 -0
- nextroute/schema/stop.py +65 -0
- nextroute/schema/vehicle.py +72 -0
- nextroute/solve.py +145 -0
- nextroute/version.py +12 -0
- nextroute-1.11.1.dev0.dist-info/LICENSE +87 -0
- nextroute-1.11.1.dev0.dist-info/METADATA +281 -0
- nextroute-1.11.1.dev0.dist-info/RECORD +26 -0
- nextroute-1.11.1.dev0.dist-info/WHEEL +5 -0
- nextroute-1.11.1.dev0.dist-info/top_level.txt +2 -0
- tests/schema/__init__.py +1 -0
- tests/schema/test_input.py +59 -0
- tests/schema/test_output.py +318 -0
- tests/solve_golden/__init__.py +1 -0
- tests/solve_golden/main.py +50 -0
nextroute/__about__.py
ADDED
nextroute/__init__.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# © 2019-present nextmv.io inc
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
The Nextroute Python interface.
|
|
5
|
+
|
|
6
|
+
Nextroute is a flexible engine for solving Vehicle Routing Problems (VRPs). The
|
|
7
|
+
core of Nextroute is written in Go and this package provides a Python interface
|
|
8
|
+
to it.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from .__about__ import __version__
|
|
12
|
+
from .options import Options as Options
|
|
13
|
+
from .options import Verbosity as Verbosity
|
|
14
|
+
from .solve import solve as solve
|
|
15
|
+
from .version import nextroute_version as nextroute_version
|
|
16
|
+
|
|
17
|
+
VERSION = __version__
|
|
18
|
+
"""The version of the Nextroute Python package."""
|
nextroute/base_model.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# © 2019-present nextmv.io inc
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
JSON class for data wrangling JSON objects.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Any, Dict
|
|
8
|
+
|
|
9
|
+
from pydantic import BaseModel
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class BaseModel(BaseModel):
|
|
13
|
+
"""Base class for data wrangling tasks with JSON."""
|
|
14
|
+
|
|
15
|
+
@classmethod
|
|
16
|
+
def from_dict(cls, data: Dict[str, Any]):
|
|
17
|
+
"""Instantiates the class from a dict."""
|
|
18
|
+
|
|
19
|
+
return cls(**data)
|
|
20
|
+
|
|
21
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
22
|
+
"""Converts the class to a dict."""
|
|
23
|
+
|
|
24
|
+
return self.model_dump(mode="json", exclude_none=True, by_alias=True)
|
|
Binary file
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# © 2019-present nextmv.io inc
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Check provides a plugin that allows you to check models and solutions.
|
|
5
|
+
|
|
6
|
+
Checking a model or a solution checks the unplanned plan units. It checks each
|
|
7
|
+
individual plan unit if it can be added to the solution. If the plan unit can
|
|
8
|
+
be added to the solution, the report will include on how many vehicles and
|
|
9
|
+
what the impact would be on the objective value. If the plan unit cannot be
|
|
10
|
+
added to the solution, the report will include the reason why it cannot be
|
|
11
|
+
added to the solution.
|
|
12
|
+
|
|
13
|
+
The check can be invoked on a nextroute.Model or a nextroute.Solution. If the
|
|
14
|
+
check is invoked on a model, an empty solution is created and the check is
|
|
15
|
+
executed on this empty solution. An empty solution is a solution with all the
|
|
16
|
+
initial stops that are fixed, initial stops that are not fixed are not added
|
|
17
|
+
to the solution. The check is executed on the unplanned plan units of the
|
|
18
|
+
solution. If the check is invoked on a solution, it is executed on the
|
|
19
|
+
unplanned plan units of the solution.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from .schema import Objective as Objective
|
|
23
|
+
from .schema import ObjectiveTerm as ObjectiveTerm
|
|
24
|
+
from .schema import Output as Output
|
|
25
|
+
from .schema import PlanUnit as PlanUnit
|
|
26
|
+
from .schema import Solution as Solution
|
|
27
|
+
from .schema import Summary as Summary
|
|
28
|
+
from .schema import Vehicle as Vehicle
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# © 2019-present nextmv.io inc
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
This module contains definitions for the schema in the Nextroute check.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Dict, List, Optional
|
|
8
|
+
|
|
9
|
+
from nextroute.base_model import BaseModel
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ObjectiveTerm(BaseModel):
|
|
13
|
+
"""Check of the individual terms of the objective for a move."""
|
|
14
|
+
|
|
15
|
+
base: Optional[float] = None
|
|
16
|
+
"""Base of the objective term."""
|
|
17
|
+
factor: Optional[float] = None
|
|
18
|
+
"""Factor of the objective term."""
|
|
19
|
+
name: Optional[str] = None
|
|
20
|
+
"""Name of the objective term."""
|
|
21
|
+
value: Optional[float] = None
|
|
22
|
+
"""Value of the objective term, which is equivalent to `self.base *
|
|
23
|
+
self.factor`."""
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class Objective(BaseModel):
|
|
27
|
+
"""Estimate of an objective of a move."""
|
|
28
|
+
|
|
29
|
+
terms: Optional[List[ObjectiveTerm]] = None
|
|
30
|
+
"""Check of the individual terms of the objective."""
|
|
31
|
+
value: Optional[float] = None
|
|
32
|
+
"""Value of the objective."""
|
|
33
|
+
vehicle: Optional[str] = None
|
|
34
|
+
"""ID of the vehicle for which it reports the objective."""
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class Solution(BaseModel):
|
|
38
|
+
"""Solution that the check has been executed on."""
|
|
39
|
+
|
|
40
|
+
objective: Optional[Objective] = None
|
|
41
|
+
"""Objective of the start solution."""
|
|
42
|
+
plan_units_planned: Optional[int] = None
|
|
43
|
+
"""Number of plan units planned in the start solution."""
|
|
44
|
+
plan_units_unplanned: Optional[int] = None
|
|
45
|
+
"""Number of plan units unplanned in the start solution."""
|
|
46
|
+
stops_planned: Optional[int] = None
|
|
47
|
+
"""Number of stops planned in the start solution."""
|
|
48
|
+
vehicles_not_used: Optional[int] = None
|
|
49
|
+
"""Number of vehicles not used in the start solution."""
|
|
50
|
+
vehicles_used: Optional[int] = None
|
|
51
|
+
"""Number of vehicles used in the start solution."""
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class Summary(BaseModel):
|
|
55
|
+
"""Summary of the check."""
|
|
56
|
+
|
|
57
|
+
moves_failed: Optional[int] = None
|
|
58
|
+
"""number of moves that failed. A move can fail if the estimate of a
|
|
59
|
+
constraint is incorrect. A constraint is incorrect if `ModelConstraint.
|
|
60
|
+
EstimateIsViolated` returns true and one of the violation checks returns
|
|
61
|
+
false. Violation checks are implementations of one or more of the
|
|
62
|
+
interfaces [SolutionStopViolationCheck], [SolutionVehicleViolationCheck] or
|
|
63
|
+
[SolutionViolationCheck] on the same constraint. Most constraints do not
|
|
64
|
+
need and do not have violation checks as the estimate is perfect. The
|
|
65
|
+
number of moves failed can be more than one per plan unit as we continue to
|
|
66
|
+
try moves on different vehicles until we find a move that is executable or
|
|
67
|
+
all vehicles have been visited."""
|
|
68
|
+
plan_units_best_move_failed: Optional[int] = None
|
|
69
|
+
"""Number of plan units for which the best move can not be planned. This
|
|
70
|
+
should not happen if all the constraints are implemented correct."""
|
|
71
|
+
plan_units_best_move_found: Optional[int] = None
|
|
72
|
+
"""Number of plan units for which at least one move has been found and the
|
|
73
|
+
move is executable."""
|
|
74
|
+
plan_units_best_move_increases_objective: Optional[int] = None
|
|
75
|
+
"""Number of plan units for which the best move is executable but would
|
|
76
|
+
increase the objective value instead of decreasing it."""
|
|
77
|
+
plan_units_checked: Optional[int] = None
|
|
78
|
+
"""Number of plan units that have been checked. If this is less than
|
|
79
|
+
`self.plan_units_to_be_checked` the check timed out."""
|
|
80
|
+
plan_units_have_no_move: Optional[int] = None
|
|
81
|
+
"""Number of plan units for which no feasible move has been found. This
|
|
82
|
+
implies there is no move that can be executed without violating a
|
|
83
|
+
constraint."""
|
|
84
|
+
plan_units_to_be_checked: Optional[int] = None
|
|
85
|
+
"""Number of plan units to be checked."""
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class PlanUnit(BaseModel):
|
|
89
|
+
"""Check of a plan unit."""
|
|
90
|
+
|
|
91
|
+
best_move_failed: Optional[bool] = None
|
|
92
|
+
"""True if the plan unit's best move failed to execute."""
|
|
93
|
+
best_move_increases_objective: Optional[bool] = None
|
|
94
|
+
"""True if the best move for the plan unit increases the objective."""
|
|
95
|
+
best_move_objective: Optional[Objective] = None
|
|
96
|
+
"""Estimate of the objective of the best move if the plan unit has a best
|
|
97
|
+
move."""
|
|
98
|
+
constraints: Optional[Dict[str, int]] = None
|
|
99
|
+
"""Constraints that are violated for the plan unit."""
|
|
100
|
+
has_best_move: Optional[bool] = None
|
|
101
|
+
"""True if a move is found for the plan unit. A plan unit has no move found
|
|
102
|
+
if the plan unit is over-constrained or the move found is too expensive."""
|
|
103
|
+
stops: Optional[List[str]] = None
|
|
104
|
+
"""IDs of the sops in the plan unit."""
|
|
105
|
+
vehicles_have_moves: Optional[int] = None
|
|
106
|
+
"""Number of vehicles that have moves for the plan unit. Only calculated if
|
|
107
|
+
the verbosity is very high."""
|
|
108
|
+
vehicles_with_moves: Optional[List[str]] = None
|
|
109
|
+
"""IDs of the vehicles that have moves for the plan unit. Only calculated
|
|
110
|
+
if the verbosity is very high."""
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class Vehicle(BaseModel):
|
|
114
|
+
"""Check of a vehicle."""
|
|
115
|
+
|
|
116
|
+
id: str
|
|
117
|
+
"""ID of the vehicle."""
|
|
118
|
+
|
|
119
|
+
plan_units_have_moves: Optional[int] = None
|
|
120
|
+
"""Number of plan units that have moves for the vehicle. Only calculated if
|
|
121
|
+
the depth is medium."""
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class Output(BaseModel):
|
|
125
|
+
"""Output of a feasibility check."""
|
|
126
|
+
|
|
127
|
+
duration_maximum: Optional[float] = None
|
|
128
|
+
"""Maximum duration of the check, in seconds."""
|
|
129
|
+
duration_used: Optional[float] = None
|
|
130
|
+
"""Duration used by the check, in seconds."""
|
|
131
|
+
error: Optional[str] = None
|
|
132
|
+
"""Error raised during the check."""
|
|
133
|
+
plan_units: Optional[List[PlanUnit]] = None
|
|
134
|
+
"""Check of the individual plan units."""
|
|
135
|
+
remark: Optional[str] = None
|
|
136
|
+
"""Remark of the check. It can be "ok", "timeout" or anything else that
|
|
137
|
+
should explain itself."""
|
|
138
|
+
solution: Optional[Solution] = None
|
|
139
|
+
"""Start soltuion of the check."""
|
|
140
|
+
summary: Optional[Summary] = None
|
|
141
|
+
"""Summary of the check."""
|
|
142
|
+
vehicles: Optional[List[Vehicle]] = None
|
|
143
|
+
"""Check of the vehicles."""
|
|
144
|
+
verbosity: Optional[str] = None
|
|
145
|
+
"""Verbosity level of the check."""
|
nextroute/options.py
ADDED
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
# © 2019-present nextmv.io inc
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Options for working with the Nextroute engine.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
from enum import Enum
|
|
9
|
+
from typing import Any, Dict, List
|
|
10
|
+
|
|
11
|
+
from pydantic import Field
|
|
12
|
+
|
|
13
|
+
from nextroute.base_model import BaseModel
|
|
14
|
+
|
|
15
|
+
# Arguments that require a duration suffix.
|
|
16
|
+
_DURATIONS_ARGS = [
|
|
17
|
+
"-check.duration",
|
|
18
|
+
"-solve.duration",
|
|
19
|
+
"-solve.plateau.duration",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
# Arguments that require a string enum.
|
|
23
|
+
_STR_ENUM_ARGS = [
|
|
24
|
+
"CHECK_VERBOSITY",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class Verbosity(str, Enum):
|
|
29
|
+
"""Format of an `Input`."""
|
|
30
|
+
|
|
31
|
+
OFF = "off"
|
|
32
|
+
"""The check engine is not run."""
|
|
33
|
+
LOW = "low"
|
|
34
|
+
"""Low verbosity for the check engine."""
|
|
35
|
+
MEDIUM = "medium"
|
|
36
|
+
"""Medium verbosity for the check engine."""
|
|
37
|
+
HIGH = "high"
|
|
38
|
+
"""High verbosity for the check engine."""
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class Options(BaseModel):
|
|
42
|
+
"""Options for using Nextroute."""
|
|
43
|
+
|
|
44
|
+
CHECK_DURATION: float = 30
|
|
45
|
+
"""Maximum duration of the check, in seconds."""
|
|
46
|
+
CHECK_VERBOSITY: Verbosity = Verbosity.OFF
|
|
47
|
+
"""Verbosity of the check engine."""
|
|
48
|
+
FORMAT_DISABLE_PROGRESSION: bool = False
|
|
49
|
+
"""Whether to disable the progression series."""
|
|
50
|
+
MODEL_CONSTRAINTS_DISABLE_ATTRIBUTES: bool = False
|
|
51
|
+
"""Ignore the compatibility attributes constraint."""
|
|
52
|
+
MODEL_CONSTRAINTS_DISABLE_CAPACITIES: List[str] = Field(default_factory=list)
|
|
53
|
+
"""Ignore the capacity constraint for the given resource names."""
|
|
54
|
+
MODEL_CONSTRAINTS_DISABLE_CAPACITY: bool = False
|
|
55
|
+
"""Ignore the capacity constraint for all resources."""
|
|
56
|
+
MODEL_CONSTRAINTS_DISABLE_DISTANCELIMIT: bool = False
|
|
57
|
+
"""Ignore the distance limit constraint."""
|
|
58
|
+
MODEL_CONSTRAINTS_DISABLE_GROUPS: bool = False
|
|
59
|
+
"""Ignore the groups constraint."""
|
|
60
|
+
MODEL_CONSTRAINTS_DISABLE_MAXIMUMDURATION: bool = False
|
|
61
|
+
"""Ignore the maximum duration constraint."""
|
|
62
|
+
MODEL_CONSTRAINTS_DISABLE_MAXIMUMSTOPS: bool = False
|
|
63
|
+
"""Ignore the maximum stops constraint."""
|
|
64
|
+
MODEL_CONSTRAINTS_DISABLE_MAXIMUMWAITSTOP: bool = False
|
|
65
|
+
"""Ignore the maximum stop wait constraint."""
|
|
66
|
+
MODEL_CONSTRAINTS_DISABLE_MAXIMUMWAITVEHICLE: bool = False
|
|
67
|
+
"""Ignore the maximum vehicle wait constraint."""
|
|
68
|
+
MODEL_CONSTRAINTS_DISABLE_MIXINGITEMS: bool = False
|
|
69
|
+
"""Ignore the do not mix items constraint."""
|
|
70
|
+
MODEL_CONSTRAINTS_DISABLE_PRECEDENCE: bool = False
|
|
71
|
+
"""Ignore the precedence (pickups & deliveries) constraint."""
|
|
72
|
+
MODEL_CONSTRAINTS_DISABLE_STARTTIMEWINDOWS: bool = False
|
|
73
|
+
"""Ignore the start time windows constraint."""
|
|
74
|
+
MODEL_CONSTRAINTS_DISABLE_VEHICLEENDTIME: bool = False
|
|
75
|
+
"""Ignore the vehicle end time constraint."""
|
|
76
|
+
MODEL_CONSTRAINTS_DISABLE_VEHICLESTARTTIME: bool = False
|
|
77
|
+
"""Ignore the vehicle start time constraint."""
|
|
78
|
+
MODEL_CONSTRAINTS_ENABLE_CLUSTER: bool = False
|
|
79
|
+
"""Enable the cluster constraint."""
|
|
80
|
+
MODEL_OBJECTIVES_CAPACITIES: str = ""
|
|
81
|
+
"""
|
|
82
|
+
Capacity objective, provide triple for each resource
|
|
83
|
+
`name:default;factor:1.0;offset;0.0`.
|
|
84
|
+
"""
|
|
85
|
+
MODEL_OBJECTIVES_CLUSTER: float = 0.0
|
|
86
|
+
"""Factor to weigh the cluster objective."""
|
|
87
|
+
MODEL_OBJECTIVES_EARLYARRIVALPENALTY: float = 1.0
|
|
88
|
+
"""Factor to weigh the early arrival objective."""
|
|
89
|
+
MODEL_OBJECTIVES_LATEARRIVALPENALTY: float = 1.0
|
|
90
|
+
"""Factor to weigh the late arrival objective."""
|
|
91
|
+
MODEL_OBJECTIVES_MINSTOPS: float = 1.0
|
|
92
|
+
"""Factor to weigh the min stops objective."""
|
|
93
|
+
MODEL_OBJECTIVES_STOPBALANCE: float = 0.0
|
|
94
|
+
"""Factor to weigh the stop balance objective."""
|
|
95
|
+
MODEL_OBJECTIVES_TRAVELDURATION: float = 0.0
|
|
96
|
+
"""Factor to weigh the travel duration objective."""
|
|
97
|
+
MODEL_OBJECTIVES_UNPLANNEDPENALTY: float = 1.0
|
|
98
|
+
"""Factor to weigh the unplanned objective."""
|
|
99
|
+
MODEL_OBJECTIVES_VEHICLEACTIVATIONPENALTY: float = 1.0
|
|
100
|
+
"""Factor to weigh the vehicle activation objective."""
|
|
101
|
+
MODEL_OBJECTIVES_VEHICLESDURATION: float = 1.0
|
|
102
|
+
"""Factor to weigh the vehicles duration objective."""
|
|
103
|
+
MODEL_PROPERTIES_DISABLE_DURATIONGROUPS: bool = False
|
|
104
|
+
"""Ignore the durations groups of stops."""
|
|
105
|
+
MODEL_PROPERTIES_DISABLE_DURATIONS: bool = False
|
|
106
|
+
"""Ignore the durations of stops."""
|
|
107
|
+
MODEL_PROPERTIES_DISABLE_INITIALSOLUTION: bool = False
|
|
108
|
+
"""Ignore the initial solution."""
|
|
109
|
+
MODEL_PROPERTIES_DISABLE_STOPDURATIONMULTIPLIERS: bool = False
|
|
110
|
+
"""Ignore the stop duration multipliers defined on vehicles."""
|
|
111
|
+
MODEL_PROPERTIES_MAXIMUMTIMEHORIZON: int = 15552000
|
|
112
|
+
"""Maximum time horizon for the model in seconds."""
|
|
113
|
+
MODEL_VALIDATE_DISABLE_RESOURCES: bool = False
|
|
114
|
+
"""Disable the resources validation."""
|
|
115
|
+
MODEL_VALIDATE_DISABLE_STARTTIME: bool = False
|
|
116
|
+
"""Disable the start time validation."""
|
|
117
|
+
MODEL_VALIDATE_ENABLE_MATRIX: bool = False
|
|
118
|
+
"""Enable matrix validation."""
|
|
119
|
+
MODEL_VALIDATE_ENABLE_MATRIXASYMMETRYTOLERANCE: int = 20
|
|
120
|
+
"""Percentage of acceptable matrix asymmetry, requires matrix validation enabled."""
|
|
121
|
+
SOLVE_DURATION: float = 5
|
|
122
|
+
"""Maximum duration, in seconds, of the solver."""
|
|
123
|
+
SOLVE_ITERATIONS: int = -1
|
|
124
|
+
"""
|
|
125
|
+
Maximum number of iterations, -1 assumes no limit; iterations are counted
|
|
126
|
+
after start solutions are generated.
|
|
127
|
+
"""
|
|
128
|
+
SOLVE_PARALLELRUNS: int = -1
|
|
129
|
+
"""
|
|
130
|
+
Maximum number of parallel runs, -1 results in using all available
|
|
131
|
+
resources.
|
|
132
|
+
"""
|
|
133
|
+
SOLVE_PLATEAU_ABSOLUTETHRESHOLD: float = -1
|
|
134
|
+
"""Absolute threshold for significant improvement."""
|
|
135
|
+
SOLVE_PLATEAU_DURATION: float = 0.0
|
|
136
|
+
"""Maximum duration, in seconds, without (significant) improvement."""
|
|
137
|
+
SOLVE_PLATEAU_ITERATIONS: int = 0
|
|
138
|
+
"""Maximum number of iterations without (significant) improvement."""
|
|
139
|
+
SOLVE_PLATEAU_RELATIVETHRESHOLD: float = 0.0
|
|
140
|
+
"""Relative threshold for significant improvement."""
|
|
141
|
+
SOLVE_RUNDETERMINISTICALLY: bool = False
|
|
142
|
+
"""Run the parallel solver deterministically."""
|
|
143
|
+
SOLVE_STARTSOLUTIONS: int = -1
|
|
144
|
+
"""
|
|
145
|
+
Number of solutions to generate on top of those passed in; one solution
|
|
146
|
+
generated with sweep algorithm, the rest generated randomly.
|
|
147
|
+
"""
|
|
148
|
+
|
|
149
|
+
def to_args(self) -> List[str]:
|
|
150
|
+
"""
|
|
151
|
+
Convert the options to command-line arguments.
|
|
152
|
+
|
|
153
|
+
Returns
|
|
154
|
+
----------
|
|
155
|
+
List[str]
|
|
156
|
+
The flattened options as a list of strings.
|
|
157
|
+
"""
|
|
158
|
+
|
|
159
|
+
opt_dict = self.to_dict()
|
|
160
|
+
|
|
161
|
+
default_options = Options()
|
|
162
|
+
default_options_dict = default_options.to_dict()
|
|
163
|
+
|
|
164
|
+
args = []
|
|
165
|
+
for key, value in opt_dict.items():
|
|
166
|
+
# We only care about custom options, so we skip the default ones.
|
|
167
|
+
default_value = default_options_dict.get(key)
|
|
168
|
+
if value == default_value:
|
|
169
|
+
continue
|
|
170
|
+
|
|
171
|
+
key = f"-{key.replace('_', '.').lower()}"
|
|
172
|
+
|
|
173
|
+
str_value = json.dumps(value)
|
|
174
|
+
if key in _DURATIONS_ARGS:
|
|
175
|
+
str_value = str_value + "s" # Transforms into seconds.
|
|
176
|
+
|
|
177
|
+
if str_value.startswith('"') and str_value.endswith('"'):
|
|
178
|
+
str_value = str_value[1:-1]
|
|
179
|
+
|
|
180
|
+
# Nextroute’s Go implementation does not support boolean flags with
|
|
181
|
+
# values. If the value is a boolean, then we only append the key if
|
|
182
|
+
# the value is True.
|
|
183
|
+
should_append_value = True
|
|
184
|
+
if isinstance(value, bool):
|
|
185
|
+
if not value:
|
|
186
|
+
continue
|
|
187
|
+
|
|
188
|
+
should_append_value = False
|
|
189
|
+
|
|
190
|
+
args.append(key)
|
|
191
|
+
if should_append_value:
|
|
192
|
+
args.append(str_value)
|
|
193
|
+
|
|
194
|
+
return args
|
|
195
|
+
|
|
196
|
+
@classmethod
|
|
197
|
+
def extract_from_dict(cls, data: Dict[str, Any]) -> "Options":
|
|
198
|
+
"""
|
|
199
|
+
Extracts options from a dictionary. This dictionary may contain more
|
|
200
|
+
keys that are not part of the Nextroute options.
|
|
201
|
+
|
|
202
|
+
Parameters
|
|
203
|
+
----------
|
|
204
|
+
data : Dict[str, Any]
|
|
205
|
+
The dictionary to extract options from.
|
|
206
|
+
|
|
207
|
+
Returns
|
|
208
|
+
----------
|
|
209
|
+
Options
|
|
210
|
+
The Nextroute options.
|
|
211
|
+
"""
|
|
212
|
+
|
|
213
|
+
options = cls()
|
|
214
|
+
for key, value in data.items():
|
|
215
|
+
key = key.upper()
|
|
216
|
+
if not hasattr(options, key):
|
|
217
|
+
continue
|
|
218
|
+
|
|
219
|
+
# Enums need to be handled manually.
|
|
220
|
+
if key == "CHECK_VERBOSITY":
|
|
221
|
+
value = Verbosity(value)
|
|
222
|
+
|
|
223
|
+
setattr(options, key, value)
|
|
224
|
+
|
|
225
|
+
return options
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# © 2019-present nextmv.io inc
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Schema (class) definitions for the entities in Nextroute.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .input import Defaults as Defaults
|
|
8
|
+
from .input import DurationGroup as DurationGroup
|
|
9
|
+
from .input import Input as Input
|
|
10
|
+
from .location import Location as Location
|
|
11
|
+
from .output import ObjectiveOutput as ObjectiveOutput
|
|
12
|
+
from .output import Output as Output
|
|
13
|
+
from .output import PlannedStopOutput as PlannedStopOutput
|
|
14
|
+
from .output import Solution as Solution
|
|
15
|
+
from .output import StopOutput as StopOutput
|
|
16
|
+
from .output import VehicleOutput as VehicleOutput
|
|
17
|
+
from .output import Version as Version
|
|
18
|
+
from .statistics import DataPoint as DataPoint
|
|
19
|
+
from .statistics import ResultStatistics as ResultStatistics
|
|
20
|
+
from .statistics import RunStatistics as RunStatistics
|
|
21
|
+
from .statistics import Series as Series
|
|
22
|
+
from .statistics import SeriesData as SeriesData
|
|
23
|
+
from .statistics import Statistics as Statistics
|
|
24
|
+
from .stop import AlternateStop as AlternateStop
|
|
25
|
+
from .stop import Stop as Stop
|
|
26
|
+
from .stop import StopDefaults as StopDefaults
|
|
27
|
+
from .vehicle import InitialStop as InitialStop
|
|
28
|
+
from .vehicle import Vehicle as Vehicle
|
|
29
|
+
from .vehicle import VehicleDefaults as VehicleDefaults
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# © 2019-present nextmv.io inc
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Defines the input class.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from datetime import datetime
|
|
8
|
+
from typing import Any, List, Optional, Union
|
|
9
|
+
|
|
10
|
+
from nextroute.base_model import BaseModel
|
|
11
|
+
from nextroute.schema.stop import AlternateStop, Stop, StopDefaults
|
|
12
|
+
from nextroute.schema.vehicle import Vehicle, VehicleDefaults
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Defaults(BaseModel):
|
|
16
|
+
"""Default values for vehicles and stops."""
|
|
17
|
+
|
|
18
|
+
stops: Optional[StopDefaults] = None
|
|
19
|
+
"""Default values for stops."""
|
|
20
|
+
vehicles: Optional[VehicleDefaults] = None
|
|
21
|
+
"""Default values for vehicles."""
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class DurationGroup(BaseModel):
|
|
25
|
+
"""Represents a group of stops that get additional duration whenever a stop
|
|
26
|
+
of the group is approached for the first time."""
|
|
27
|
+
|
|
28
|
+
duration: int
|
|
29
|
+
"""Duration to add when visiting the group."""
|
|
30
|
+
group: List[str]
|
|
31
|
+
"""Stop IDs contained in the group."""
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class MatrixTimeFrame(BaseModel):
|
|
35
|
+
"""Represents a time-dependent duration matrix or scaling factor."""
|
|
36
|
+
|
|
37
|
+
start_time: datetime
|
|
38
|
+
"""Start time of the time frame."""
|
|
39
|
+
end_time: datetime
|
|
40
|
+
"""End time of the time frame."""
|
|
41
|
+
matrix: Optional[List[List[float]]] = None
|
|
42
|
+
"""Duration matrix for the time frame."""
|
|
43
|
+
scaling_factor: Optional[float] = None
|
|
44
|
+
"""Scaling factor for the time frame."""
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class TimeDependentMatrix(BaseModel):
|
|
48
|
+
"""Represents time-dependent duration matrices."""
|
|
49
|
+
|
|
50
|
+
vehicle_ids: Optional[List[str]] = None
|
|
51
|
+
"""Vehicle IDs for which the duration matrix is defined."""
|
|
52
|
+
default_matrix: List[List[float]]
|
|
53
|
+
"""Default duration matrix."""
|
|
54
|
+
matrix_time_frames: Optional[List[MatrixTimeFrame]] = None
|
|
55
|
+
"""Time-dependent duration matrices."""
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class Input(BaseModel):
|
|
59
|
+
"""Input schema for Nextroute."""
|
|
60
|
+
|
|
61
|
+
stops: List[Stop]
|
|
62
|
+
"""Stops that must be visited by the vehicles."""
|
|
63
|
+
vehicles: List[Vehicle]
|
|
64
|
+
"""Vehicles that service the stops."""
|
|
65
|
+
|
|
66
|
+
alternate_stops: Optional[List[AlternateStop]] = None
|
|
67
|
+
"""A set of alternate stops for the vehicles."""
|
|
68
|
+
custom_data: Optional[Any] = None
|
|
69
|
+
"""Arbitrary data associated with the input."""
|
|
70
|
+
defaults: Optional[Defaults] = None
|
|
71
|
+
"""Default values for vehicles and stops."""
|
|
72
|
+
distance_matrix: Optional[List[List[float]]] = None
|
|
73
|
+
"""Matrix of travel distances in meters between stops."""
|
|
74
|
+
duration_groups: Optional[List[DurationGroup]] = None
|
|
75
|
+
"""Duration in seconds added when approaching the group."""
|
|
76
|
+
duration_matrix: Optional[
|
|
77
|
+
Union[
|
|
78
|
+
List[List[float]],
|
|
79
|
+
TimeDependentMatrix,
|
|
80
|
+
List[TimeDependentMatrix],
|
|
81
|
+
]
|
|
82
|
+
] = None
|
|
83
|
+
"""Matrix of travel durations in seconds between stops as a single matrix or duration matrices."""
|
|
84
|
+
options: Optional[Any] = None
|
|
85
|
+
"""Arbitrary options."""
|
|
86
|
+
stop_groups: Optional[List[List[str]]] = None
|
|
87
|
+
"""Groups of stops that must be part of the same route."""
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# © 2019-present nextmv.io inc
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Defines the location class.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from nextroute.base_model import BaseModel
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Location(BaseModel):
|
|
11
|
+
"""Location represents a geographical location."""
|
|
12
|
+
|
|
13
|
+
lat: float
|
|
14
|
+
"""Latitude of the location."""
|
|
15
|
+
lon: float
|
|
16
|
+
"""Longitude of the location."""
|