nextroute 1.12.0__cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.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 +230 -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.12.0.dist-info/METADATA +282 -0
- nextroute-1.12.0.dist-info/RECORD +26 -0
- nextroute-1.12.0.dist-info/WHEEL +7 -0
- nextroute-1.12.0.dist-info/licenses/LICENSE +87 -0
- nextroute-1.12.0.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
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# © 2019-present nextmv.io inc
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Defines the output class.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from datetime import datetime
|
|
8
|
+
from typing import Any, Dict, List, Optional
|
|
9
|
+
|
|
10
|
+
from nextroute.base_model import BaseModel
|
|
11
|
+
from nextroute.check.schema import Output as CheckOutput
|
|
12
|
+
from nextroute.schema.location import Location
|
|
13
|
+
from nextroute.schema.statistics import Statistics
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Version(BaseModel):
|
|
17
|
+
"""A version used for solving."""
|
|
18
|
+
|
|
19
|
+
sdk: str
|
|
20
|
+
"""Nextmv SDK."""
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class StopOutput(BaseModel):
|
|
24
|
+
"""Basic structure for the output of a stop."""
|
|
25
|
+
|
|
26
|
+
id: str
|
|
27
|
+
"""ID of the stop."""
|
|
28
|
+
location: Location
|
|
29
|
+
"""Location of the stop."""
|
|
30
|
+
|
|
31
|
+
custom_data: Optional[Any] = None
|
|
32
|
+
"""Custom data of the stop."""
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class PlannedStopOutput(BaseModel):
|
|
36
|
+
"""Output of a stop planned in the solution."""
|
|
37
|
+
|
|
38
|
+
stop: StopOutput
|
|
39
|
+
"""Basic information on the stop."""
|
|
40
|
+
|
|
41
|
+
arrival_time: Optional[datetime] = None
|
|
42
|
+
"""Actual arrival time at this stop."""
|
|
43
|
+
cumulative_travel_distance: Optional[float] = None
|
|
44
|
+
"""Cumulative distance to travel from the first stop to this one, in meters."""
|
|
45
|
+
cumulative_travel_duration: Optional[float] = None
|
|
46
|
+
"""Cumulative duration to travel from the first stop to this one, in seconds."""
|
|
47
|
+
custom_data: Optional[Any] = None
|
|
48
|
+
"""Custom data of the stop."""
|
|
49
|
+
duration: Optional[float] = None
|
|
50
|
+
"""Duration of the service at the stop, in seconds."""
|
|
51
|
+
early_arrival_duration: Optional[float] = None
|
|
52
|
+
"""Duration of early arrival at the stop, in seconds."""
|
|
53
|
+
end_time: Optional[datetime] = None
|
|
54
|
+
"""End time of the service at the stop."""
|
|
55
|
+
late_arrival_duration: Optional[float] = None
|
|
56
|
+
"""Duration of late arrival at the stop, in seconds."""
|
|
57
|
+
mix_items: Optional[Any] = None
|
|
58
|
+
"""Mix items at the stop."""
|
|
59
|
+
start_time: Optional[datetime] = None
|
|
60
|
+
"""Start time of the service at the stop."""
|
|
61
|
+
target_arrival_time: Optional[datetime] = None
|
|
62
|
+
"""Target arrival time at this stop."""
|
|
63
|
+
travel_distance: Optional[float] = None
|
|
64
|
+
"""Distance to travel from the previous stop to this one, in meters."""
|
|
65
|
+
travel_duration: Optional[float] = None
|
|
66
|
+
"""Duration to travel from the previous stop to this one, in seconds."""
|
|
67
|
+
waiting_duration: Optional[float] = None
|
|
68
|
+
"""Waiting duratino at the stop, in seconds."""
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class VehicleOutput(BaseModel):
|
|
72
|
+
"""Output of a vehicle in the solution."""
|
|
73
|
+
|
|
74
|
+
id: str
|
|
75
|
+
"""ID of the vehicle."""
|
|
76
|
+
|
|
77
|
+
alternate_stops: Optional[List[str]] = None
|
|
78
|
+
"""List of alternate stops that were planned on the vehicle."""
|
|
79
|
+
custom_data: Optional[Any] = None
|
|
80
|
+
"""Custom data of the vehicle."""
|
|
81
|
+
route: Optional[List[PlannedStopOutput]] = None
|
|
82
|
+
"""Route of the vehicle, which is a list of stops that were planned on
|
|
83
|
+
it."""
|
|
84
|
+
route_duration: Optional[float] = None
|
|
85
|
+
"""Total duration of the vehicle's route, in seconds."""
|
|
86
|
+
route_stops_duration: Optional[float] = None
|
|
87
|
+
"""Total duration of the stops of the vehicle, in seconds."""
|
|
88
|
+
route_travel_distance: Optional[float] = None
|
|
89
|
+
"""Total travel distance of the vehicle, in meters."""
|
|
90
|
+
route_travel_duration: Optional[float] = None
|
|
91
|
+
"""Total travel duration of the vehicle, in seconds."""
|
|
92
|
+
route_waiting_duration: Optional[float] = None
|
|
93
|
+
"""Total waiting duration of the vehicle, in seconds."""
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class ObjectiveOutput(BaseModel):
|
|
97
|
+
"""Information of the objective (value function)."""
|
|
98
|
+
|
|
99
|
+
name: str
|
|
100
|
+
"""Name of the objective."""
|
|
101
|
+
|
|
102
|
+
base: Optional[float] = None
|
|
103
|
+
"""Base of the objective."""
|
|
104
|
+
custom_data: Optional[Any] = None
|
|
105
|
+
"""Custom data of the objective."""
|
|
106
|
+
factor: Optional[float] = None
|
|
107
|
+
"""Factor of the objective."""
|
|
108
|
+
objectives: Optional[List[Dict[str, Any]]] = None
|
|
109
|
+
"""List of objectives. Each list is actually of the same class
|
|
110
|
+
`ObjectiveOutput`, but we avoid a recursive definition here."""
|
|
111
|
+
value: Optional[float] = None
|
|
112
|
+
"""Value of the objective, which is equivalent to `self.base *
|
|
113
|
+
self.factor`."""
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class Solution(BaseModel):
|
|
117
|
+
"""Solution to a Vehicle Routing Problem (VRP)."""
|
|
118
|
+
|
|
119
|
+
unplanned: Optional[List[StopOutput]] = None
|
|
120
|
+
"""List of stops that were not planned in the solution."""
|
|
121
|
+
vehicles: Optional[List[VehicleOutput]] = None
|
|
122
|
+
"""List of vehicles in the solution."""
|
|
123
|
+
objective: Optional[ObjectiveOutput] = None
|
|
124
|
+
"""Information of the objective (value function)."""
|
|
125
|
+
check: Optional[CheckOutput] = None
|
|
126
|
+
"""Check of the solution, if enabled."""
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class Output(BaseModel):
|
|
130
|
+
"""Output schema for Nextroute."""
|
|
131
|
+
|
|
132
|
+
options: Dict[str, Any]
|
|
133
|
+
"""Options used to obtain this output."""
|
|
134
|
+
version: Version
|
|
135
|
+
"""Versions used for the solution."""
|
|
136
|
+
|
|
137
|
+
solutions: Optional[List[Solution]] = None
|
|
138
|
+
"""Solutions to the problem."""
|
|
139
|
+
statistics: Optional[Statistics] = None
|
|
140
|
+
"""Statistics of the solution."""
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# © 2019-present nextmv.io inc
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Schema for statistics.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Any, Dict, List, Optional, Union
|
|
8
|
+
|
|
9
|
+
from pydantic import Field
|
|
10
|
+
|
|
11
|
+
from nextroute.base_model import BaseModel
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class RunStatistics(BaseModel):
|
|
15
|
+
"""
|
|
16
|
+
Statistics about a general run.
|
|
17
|
+
|
|
18
|
+
Parameters
|
|
19
|
+
----------
|
|
20
|
+
duration : float, optional
|
|
21
|
+
Duration of the run in seconds.
|
|
22
|
+
iterations : int, optional
|
|
23
|
+
Number of iterations.
|
|
24
|
+
custom : Union[Any, Dict[str, Any]], optional
|
|
25
|
+
Custom statistics created by the user. Can normally expect a `Dict[str,
|
|
26
|
+
Any]`.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
duration: Optional[float] = None
|
|
30
|
+
"""Duration of the run in seconds."""
|
|
31
|
+
iterations: Optional[int] = None
|
|
32
|
+
"""Number of iterations."""
|
|
33
|
+
custom: Optional[
|
|
34
|
+
Union[
|
|
35
|
+
Any,
|
|
36
|
+
Dict[str, Any],
|
|
37
|
+
]
|
|
38
|
+
] = None
|
|
39
|
+
"""Custom statistics created by the user. Can normally expect a `Dict[str,
|
|
40
|
+
Any]`."""
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class ResultStatistics(BaseModel):
|
|
44
|
+
"""
|
|
45
|
+
Statistics about a specific result.
|
|
46
|
+
|
|
47
|
+
Parameters
|
|
48
|
+
----------
|
|
49
|
+
duration : float, optional
|
|
50
|
+
Duration of the run in seconds.
|
|
51
|
+
value : float, optional
|
|
52
|
+
Value of the result.
|
|
53
|
+
custom : Union[Any, Dict[str, Any]], optional
|
|
54
|
+
Custom statistics created by the user. Can normally expect a `Dict[str,
|
|
55
|
+
Any]`.
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
duration: Optional[float] = None
|
|
59
|
+
"""Duration of the run in seconds."""
|
|
60
|
+
value: Optional[float] = None
|
|
61
|
+
"""Value of the result."""
|
|
62
|
+
custom: Optional[
|
|
63
|
+
Union[
|
|
64
|
+
Any,
|
|
65
|
+
Dict[str, Any],
|
|
66
|
+
]
|
|
67
|
+
] = None
|
|
68
|
+
"""Custom statistics created by the user. Can normally expect a `Dict[str,
|
|
69
|
+
Any]`."""
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class DataPoint(BaseModel):
|
|
73
|
+
"""
|
|
74
|
+
A data point.
|
|
75
|
+
|
|
76
|
+
Parameters
|
|
77
|
+
----------
|
|
78
|
+
x : float
|
|
79
|
+
X coordinate of the data point.
|
|
80
|
+
y : float
|
|
81
|
+
Y coordinate of the data point.
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
x: float
|
|
85
|
+
"""X coordinate of the data point."""
|
|
86
|
+
y: float
|
|
87
|
+
"""Y coordinate of the data point."""
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class Series(BaseModel):
|
|
91
|
+
"""
|
|
92
|
+
A series of data points.
|
|
93
|
+
|
|
94
|
+
Parameters
|
|
95
|
+
----------
|
|
96
|
+
name : str, optional
|
|
97
|
+
Name of the series.
|
|
98
|
+
data_points : List[DataPoint], optional
|
|
99
|
+
Data of the series.
|
|
100
|
+
"""
|
|
101
|
+
|
|
102
|
+
name: Optional[str] = None
|
|
103
|
+
"""Name of the series."""
|
|
104
|
+
data_points: Optional[List[DataPoint]] = None
|
|
105
|
+
"""Data of the series."""
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class SeriesData(BaseModel):
|
|
109
|
+
"""
|
|
110
|
+
Data of a series.
|
|
111
|
+
|
|
112
|
+
Parameters
|
|
113
|
+
----------
|
|
114
|
+
value : Series, optional
|
|
115
|
+
A series for the value of the solution.
|
|
116
|
+
custom : List[Series], optional
|
|
117
|
+
A list of series for custom statistics.
|
|
118
|
+
"""
|
|
119
|
+
|
|
120
|
+
value: Optional[Series] = None
|
|
121
|
+
"""A series for the value of the solution."""
|
|
122
|
+
custom: Optional[List[Series]] = None
|
|
123
|
+
"""A list of series for custom statistics."""
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class Statistics(BaseModel):
|
|
127
|
+
"""
|
|
128
|
+
Statistics of a solution.
|
|
129
|
+
|
|
130
|
+
Parameters
|
|
131
|
+
----------
|
|
132
|
+
run : RunStatistics, optional
|
|
133
|
+
Statistics about the run.
|
|
134
|
+
result : ResultStatistics, optional
|
|
135
|
+
Statistics about the last result.
|
|
136
|
+
series_data : SeriesData, optional
|
|
137
|
+
Series data about some metric.
|
|
138
|
+
statistics_schema : str, optional
|
|
139
|
+
Schema (version). This class only supports `v1`.
|
|
140
|
+
"""
|
|
141
|
+
|
|
142
|
+
run: Optional[RunStatistics] = None
|
|
143
|
+
"""Statistics about the run."""
|
|
144
|
+
result: Optional[ResultStatistics] = None
|
|
145
|
+
"""Statistics about the last result."""
|
|
146
|
+
series_data: Optional[SeriesData] = None
|
|
147
|
+
"""Data of the series."""
|
|
148
|
+
statistics_schema: Optional[str] = Field(alias="schema", default="v1")
|
|
149
|
+
"""Schema (version). This class only supports `v1`."""
|
nextroute/schema/stop.py
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# © 2019-present nextmv.io inc
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Defines the stop class.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from datetime import datetime
|
|
8
|
+
from typing import Any, List, Optional
|
|
9
|
+
|
|
10
|
+
from nextroute.base_model import BaseModel
|
|
11
|
+
from nextroute.schema.location import Location
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class StopDefaults(BaseModel):
|
|
15
|
+
"""Default values for a stop."""
|
|
16
|
+
|
|
17
|
+
compatibility_attributes: Optional[List[str]] = None
|
|
18
|
+
"""Attributes that the stop is compatible with."""
|
|
19
|
+
duration: Optional[int] = None
|
|
20
|
+
"""Duration of the stop in seconds."""
|
|
21
|
+
early_arrival_time_penalty: Optional[float] = None
|
|
22
|
+
"""Penalty per second for arriving at the stop before the target arrival time."""
|
|
23
|
+
late_arrival_time_penalty: Optional[float] = None
|
|
24
|
+
"""Penalty per second for arriving at the stop after the target arrival time."""
|
|
25
|
+
max_wait: Optional[int] = None
|
|
26
|
+
"""Maximum waiting duration in seconds at the stop."""
|
|
27
|
+
quantity: Optional[Any] = None
|
|
28
|
+
"""Quantity of the stop."""
|
|
29
|
+
start_time_window: Optional[Any] = None
|
|
30
|
+
"""Time window in which the stop can start service."""
|
|
31
|
+
target_arrival_time: Optional[datetime] = None
|
|
32
|
+
"""Target arrival time at the stop."""
|
|
33
|
+
unplanned_penalty: Optional[int] = None
|
|
34
|
+
"""Penalty for not planning a stop."""
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class Stop(StopDefaults):
|
|
38
|
+
"""Stop is a location that must be visited by a vehicle in a Vehicle
|
|
39
|
+
Routing Problem (VRP.)"""
|
|
40
|
+
|
|
41
|
+
id: str
|
|
42
|
+
"""Unique identifier for the stop."""
|
|
43
|
+
location: Location
|
|
44
|
+
"""Location of the stop."""
|
|
45
|
+
|
|
46
|
+
custom_data: Optional[Any] = None
|
|
47
|
+
"""Arbitrary data associated with the stop."""
|
|
48
|
+
mixing_items: Optional[Any] = None
|
|
49
|
+
"""Defines the items that are inserted or removed from the vehicle when visiting the stop."""
|
|
50
|
+
precedes: Optional[Any] = None
|
|
51
|
+
"""Stops that must be visited after this one on the same route."""
|
|
52
|
+
succeeds: Optional[Any] = None
|
|
53
|
+
"""Stops that must be visited before this one on the same route."""
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class AlternateStop(StopDefaults):
|
|
57
|
+
"""An alternate stop can be serviced instead of another stop."""
|
|
58
|
+
|
|
59
|
+
id: str
|
|
60
|
+
"""Unique identifier for the stop."""
|
|
61
|
+
location: Location
|
|
62
|
+
"""Location of the stop."""
|
|
63
|
+
|
|
64
|
+
custom_data: Optional[Any] = None
|
|
65
|
+
"""Arbitrary data associated with the stop."""
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# © 2019-present nextmv.io inc
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Defines the vehicle class.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from datetime import datetime
|
|
8
|
+
from typing import Any, List, Optional
|
|
9
|
+
|
|
10
|
+
from nextroute.base_model import BaseModel
|
|
11
|
+
from nextroute.schema.location import Location
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class InitialStop(BaseModel):
|
|
15
|
+
"""Represents a stop that is already planned on a vehicle."""
|
|
16
|
+
|
|
17
|
+
id: str
|
|
18
|
+
"""Unique identifier of the stop."""
|
|
19
|
+
|
|
20
|
+
fixed: Optional[bool] = None
|
|
21
|
+
"""Whether the stop is fixed on the route."""
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class VehicleDefaults(BaseModel):
|
|
25
|
+
"""Default values for vehicles."""
|
|
26
|
+
|
|
27
|
+
activation_penalty: Optional[int] = None
|
|
28
|
+
"""Penalty of using the vehicle."""
|
|
29
|
+
alternate_stops: Optional[List[str]] = None
|
|
30
|
+
"""A set of alternate stops for which only one should be serviced."""
|
|
31
|
+
capacity: Optional[Any] = None
|
|
32
|
+
"""Capacity of the vehicle."""
|
|
33
|
+
compatibility_attributes: Optional[List[str]] = None
|
|
34
|
+
"""Attributes that the vehicle is compatible with."""
|
|
35
|
+
end_location: Optional[Location] = None
|
|
36
|
+
"""Location where the vehicle ends."""
|
|
37
|
+
end_time: Optional[datetime] = None
|
|
38
|
+
"""Latest time at which the vehicle ends its route."""
|
|
39
|
+
max_distance: Optional[int] = None
|
|
40
|
+
"""Maximum distance in meters that the vehicle can travel."""
|
|
41
|
+
max_duration: Optional[int] = None
|
|
42
|
+
"""Maximum duration in seconds that the vehicle can travel."""
|
|
43
|
+
max_stops: Optional[int] = None
|
|
44
|
+
"""Maximum number of stops that the vehicle can visit."""
|
|
45
|
+
max_wait: Optional[int] = None
|
|
46
|
+
"""Maximum aggregated waiting time that the vehicle can wait across route stops."""
|
|
47
|
+
min_stops: Optional[int] = None
|
|
48
|
+
"""Minimum stops that a vehicle should visit."""
|
|
49
|
+
min_stops_penalty: Optional[float] = None
|
|
50
|
+
"""Penalty for not visiting the minimum number of stops."""
|
|
51
|
+
speed: Optional[float] = None
|
|
52
|
+
"""Speed of the vehicle in meters per second."""
|
|
53
|
+
start_level: Optional[Any] = None
|
|
54
|
+
"""Initial level of the vehicle."""
|
|
55
|
+
start_location: Optional[Location] = None
|
|
56
|
+
"""Location where the vehicle starts."""
|
|
57
|
+
start_time: Optional[datetime] = None
|
|
58
|
+
"""Time when the vehicle starts its route."""
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class Vehicle(VehicleDefaults):
|
|
62
|
+
"""A vehicle services stops in a Vehicle Routing Problem (VRP)."""
|
|
63
|
+
|
|
64
|
+
id: str
|
|
65
|
+
"""Unique identifier of the vehicle."""
|
|
66
|
+
|
|
67
|
+
custom_data: Optional[Any] = None
|
|
68
|
+
"""Arbitrary custom data."""
|
|
69
|
+
initial_stops: Optional[List[InitialStop]] = None
|
|
70
|
+
"""Initial stops planned on the vehicle."""
|
|
71
|
+
stop_duration_multiplier: Optional[float] = None
|
|
72
|
+
"""Multiplier for the duration of stops."""
|
nextroute/solve.py
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# © 2019-present nextmv.io inc
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Methods for solving a Vehicle Routing Problem with Nextroute.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
import os
|
|
9
|
+
import platform
|
|
10
|
+
import subprocess
|
|
11
|
+
from typing import Any, Dict, Union
|
|
12
|
+
|
|
13
|
+
from nextroute.options import Options
|
|
14
|
+
from nextroute.schema.input import Input
|
|
15
|
+
from nextroute.schema.output import Output
|
|
16
|
+
|
|
17
|
+
SUPPORTED_OS = ["linux", "windows", "darwin"]
|
|
18
|
+
"""The operating systems supported by the Nextroute engine."""
|
|
19
|
+
SUPPORTED_ARCHITECTURES = ["amd64", "x86_64", "arm64", "aarch64"]
|
|
20
|
+
"""The architectures supported by the Nextroute engine."""
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def solve(
|
|
24
|
+
input: Union[Input, Dict[str, Any]],
|
|
25
|
+
options: Union[Options, Dict[str, Any]],
|
|
26
|
+
) -> Output:
|
|
27
|
+
"""
|
|
28
|
+
Solve a Vehicle Routing Problem (VRP) using the Nextroute engine. The input
|
|
29
|
+
and options are passed to the engine, and the output is returned. The input
|
|
30
|
+
and options can be provided as dictionaries or as objects, although the
|
|
31
|
+
recommended way is to use the classes, as they provide validation.
|
|
32
|
+
|
|
33
|
+
Examples
|
|
34
|
+
--------
|
|
35
|
+
|
|
36
|
+
* Using default options to load an input from a file.
|
|
37
|
+
```python
|
|
38
|
+
import json
|
|
39
|
+
|
|
40
|
+
import nextroute
|
|
41
|
+
|
|
42
|
+
with open("input.json") as f:
|
|
43
|
+
data = json.load(f)
|
|
44
|
+
|
|
45
|
+
input = nextroute.schema.Input.from_dict(data)
|
|
46
|
+
options = nextroute.Options()
|
|
47
|
+
output = nextroute.solve(input, options)
|
|
48
|
+
print(output)
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
* Using custom options to load an input from a file.
|
|
52
|
+
```python
|
|
53
|
+
import json
|
|
54
|
+
|
|
55
|
+
import nextroute
|
|
56
|
+
|
|
57
|
+
with open("input.json") as f:
|
|
58
|
+
data = json.load(f)
|
|
59
|
+
|
|
60
|
+
input = nextroute.schema.Input.from_dict(data)
|
|
61
|
+
options = nextroute.Options(
|
|
62
|
+
solve=nextroute.ParallelSolveOptions(duration=2),
|
|
63
|
+
)
|
|
64
|
+
output = nextroute.solve(input, options)
|
|
65
|
+
print(output)
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
* Using custom dict options to load an input from a file.
|
|
69
|
+
```python
|
|
70
|
+
import json
|
|
71
|
+
|
|
72
|
+
import nextroute
|
|
73
|
+
|
|
74
|
+
with open("input.json") as f:
|
|
75
|
+
data = json.load(f)
|
|
76
|
+
|
|
77
|
+
input = nextroute.schema.Input.from_dict(data)
|
|
78
|
+
options = {
|
|
79
|
+
"solve": {
|
|
80
|
+
"duration": 2,
|
|
81
|
+
},
|
|
82
|
+
}
|
|
83
|
+
output = nextroute.solve(input, options)
|
|
84
|
+
print(output)
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
Parameters
|
|
89
|
+
----------
|
|
90
|
+
input : Union[schema.Input, Dict[str, Any]]
|
|
91
|
+
The input to the Nextroute engine. If a dictionary is provided, it will
|
|
92
|
+
be converted to an Input object to validate it.
|
|
93
|
+
options : Union[Options, Dict[str, Any]]
|
|
94
|
+
The options for the Nextroute engine. If a dictionary is provided, it
|
|
95
|
+
will be converted to an Options object.
|
|
96
|
+
|
|
97
|
+
Returns
|
|
98
|
+
-------
|
|
99
|
+
schema.Output
|
|
100
|
+
The output of the Nextroute engine. You can call the `to_dict` method
|
|
101
|
+
on this object to get a dictionary representation of the output.
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
if isinstance(input, dict):
|
|
105
|
+
input = Input.from_dict(input)
|
|
106
|
+
|
|
107
|
+
input_stream = json.dumps(input.to_dict())
|
|
108
|
+
|
|
109
|
+
if isinstance(options, dict):
|
|
110
|
+
options = Options.from_dict(options)
|
|
111
|
+
|
|
112
|
+
os_name = platform.system().lower()
|
|
113
|
+
if os_name not in SUPPORTED_OS:
|
|
114
|
+
raise Exception(f'unsupported operating system: "{os_name}", supported os are: {", ".join(SUPPORTED_OS)}')
|
|
115
|
+
|
|
116
|
+
architecture = platform.machine().lower()
|
|
117
|
+
if architecture not in SUPPORTED_ARCHITECTURES:
|
|
118
|
+
raise Exception(
|
|
119
|
+
f'unsupported architecture: "{architecture}", supported arch are: {", ".join(SUPPORTED_ARCHITECTURES)}'
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
executable = os.path.join(os.path.dirname(__file__), "bin", "nextroute.exe")
|
|
123
|
+
if not os.path.exists(executable):
|
|
124
|
+
raise Exception(f"missing Nextroute binary: {executable}")
|
|
125
|
+
|
|
126
|
+
option_args = options.to_args()
|
|
127
|
+
args = [executable] + option_args
|
|
128
|
+
|
|
129
|
+
try:
|
|
130
|
+
result = subprocess.run(
|
|
131
|
+
args,
|
|
132
|
+
env=os.environ,
|
|
133
|
+
check=True,
|
|
134
|
+
text=True,
|
|
135
|
+
capture_output=True,
|
|
136
|
+
input=input_stream,
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
except subprocess.CalledProcessError as e:
|
|
140
|
+
raise Exception(f"error running Nextroute binary: {e.stderr}") from e
|
|
141
|
+
|
|
142
|
+
raw_output = result.stdout
|
|
143
|
+
output = Output.from_dict(json.loads(raw_output))
|
|
144
|
+
|
|
145
|
+
return output
|
nextroute/version.py
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# © 2019-present nextmv.io inc
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import subprocess
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def nextroute_version() -> str:
|
|
8
|
+
"""
|
|
9
|
+
Get the version of the embedded Nextroute binary.
|
|
10
|
+
"""
|
|
11
|
+
executable = os.path.join(os.path.dirname(__file__), "bin", "nextroute.exe")
|
|
12
|
+
return subprocess.check_output([executable, "--version"]).decode().strip()
|