Python
Using pip
This is the easiest way to start using the solver's latest version:
pip install vrp-cli
python examples/python-interop/example.py # test example
See python code example in repo or in next section.
Using maturin
You can use maturin tool to build solver locally for you. Here are the steps:
-
Create a virtual environment and install maturin (and pydantic):
cd vrp-cli # directory of the crate where with python bindings are located python3 -m venv .venv source .venv/bin/activate pip install -U pip maturin[patchelf] pydantic pip freeze
-
Use maturin to build and install the solver library in your current environment:
maturin develop --release --features "py_bindings"
-
Import and use the library in your python code:
import vrp_cli
import pragmatic_types as prg
import config_types as cfg
import json
from pydantic.json import pydantic_encoder
# if you want to use approximation, you can skip this definition and pass empty list later
# also there is a get_locations method to get list of locations in expected order.
# you can use this list to fetch routing matrix externally
matrix = prg.RoutingMatrix(
profile='normal_car',
durations=[0, 609, 981, 906, 813, 0, 371, 590, 1055, 514, 0, 439, 948, 511, 463, 0],
distances=[0, 3840, 5994, 5333, 4696, 0, 2154, 3226, 5763, 2674, 0, 2145, 5112, 2470, 2152, 0]
)
# specify termination criteria: max running time in seconds or max amount of refinement generations
config = cfg.Config(
termination=cfg.Termination(
maxTime=5,
maxGenerations=1000
)
)
# specify test problem
problem = prg.Problem(
plan=prg.Plan(
jobs=[
prg.Job(
id='delivery_job1',
deliveries=[
prg.JobTask(
places=[
prg.JobPlace(
location=prg.Location(lat=52.52599, lng=13.45413),
duration=300,
times=[['2019-07-04T09:00:00Z', '2019-07-04T18:00:00Z']]
),
],
demand=[1]
)
]
),
prg.Job(
id='pickup_job2',
pickups=[
prg.JobTask(
places=[
prg.JobPlace(
location=prg.Location(lat=52.5225, lng=13.4095),
duration=240,
times=[['2019-07-04T10:00:00Z', '2019-07-04T16:00:00Z']]
)
],
demand=[1]
)
]
),
prg.Job(
id="pickup_delivery_job3",
pickups=[
prg.JobTask(
places=[
prg.JobPlace(
location=prg.Location(lat=52.5225, lng=13.4095),
duration=300,
tag="p1"
)
],
demand=[1]
)
],
deliveries=[
prg.JobTask(
places=[
prg.JobPlace(
location=prg.Location(lat=52.5165, lng=13.3808),
duration=300,
tag="d1"
),
],
demand=[1]
)
]
)
]
),
fleet=prg.Fleet(
vehicles=[
prg.VehicleType(
typeId='vehicle',
vehicleIds=['vehicle_1'],
profile=prg.VehicleProfile(matrix='normal_car'),
costs=prg.VehicleCosts(fixed=22, distance=0.0002, time=0.005),
shifts=[
prg.VehicleShift(
start=prg.VehicleShiftStart(
earliest="2019-07-04T09:00:00Z",
location=prg.Location(lat=52.5316, lng=13.3884),
),
end=prg.VehicleShiftEnd(
latest="2019-07-04T18:00:00Z",
location=prg.Location(lat=52.5316, lng=13.3884),
)
)
],
capacity=[10]
)
],
profiles=[prg.RoutingProfile(name='normal_car')]
)
)
# run solver and deserialize result into solution model
solution = prg.Solution(**json.loads(vrp_cli.solve_pragmatic(
problem=json.dumps(problem, default=pydantic_encoder),
matrices=[json.dumps(matrix, default=pydantic_encoder)],
config=json.dumps(config, default=pydantic_encoder),
)))
print(solution)
You can check the project repository for complete example.
Please note, that type wrappers, defined in examples with pydantic
, are incomplete. However, it should be enough to
get started, and you can tweak them according to the documentation or rust source code.
Using local build
Another way to run the solver, built locally, from python is to use subprocess
to run vrp-cli
directly:
import subprocess
import json
# NOTE: ensure that paths are correct on your environment
cli_path = "./target/release/vrp-cli"
problem_path = "./examples/data/pragmatic/simple.basic.problem.json"
solution_path = "./examples/data/pragmatic/simple.basic.solution.json"
geojson_solution_path = "./examples/data/pragmatic/simple.basic.solution.geojson"
class Deserializer:
@classmethod
def from_dict(cls, dict):
obj = cls()
obj.__dict__.update(dict)
return obj
class SolverClient:
def __init__(self, cli_path):
self.cli_path = cli_path
def solve_pragmatic(self, problem_path, solution_path, geojson_solution_path):
# NOTE: modify example to pass matrix, config, initial solution, etc.
p = subprocess.run([self.cli_path, 'solve', 'pragmatic', problem_path,
'-o', solution_path, '-g', geojson_solution_path, '--log'])
if p.returncode == 0:
with open(solution_path, 'r') as f:
solution_str = f.read()
return json.loads(solution_str, object_hook=Deserializer.from_dict)
else:
pass
solver = SolverClient(cli_path)
solution = solver.solve_pragmatic(problem_path, solution_path, geojson_solution_path)
print(f"Total cost is {solution.statistic.cost}, tours: {len(solution.tours)}")
Please note, that the solver expects file paths instead of json strings as input arguments.