diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index dd5c776..09534fe 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -80,16 +80,19 @@ jobs: - name: Build wheels if: matrix.platform != 'macos-arm' - uses: pypa/cibuildwheel@v2.19.1 + uses: pypa/cibuildwheel@v2.19.2 env: MACOSX_DEPLOYMENT_TARGET: 13.0 - CIBW_ARCHS_LINUX: x86_64 aarch64 - + CC: gcc-13 + CXX: g++-13 + - name: Build wheels if: matrix.platform == 'macos-arm' - uses: pypa/cibuildwheel@v2.19.1 + uses: pypa/cibuildwheel@v2.19.2 env: MACOSX_DEPLOYMENT_TARGET: 14.0 + CC: gcc-13 + CXX: g++-13 - name: Verify clean directory run: git diff --exit-code diff --git a/Makefile b/Makefile index be418aa..04e9e9e 100644 --- a/Makefile +++ b/Makefile @@ -26,4 +26,4 @@ format: @echo format python code with black @python -m black src/vroom @echo format c++ code with clang-format - @find src -type f -name '*.cpp' | xargs -I{} clang-format-10 -i -style=file {} + @find src -type f -name '*.cpp' | xargs -I{} clang-format-14 -i -style=file {} diff --git a/README.rst b/README.rst index 3ece7d1..89fd73c 100644 --- a/README.rst +++ b/README.rst @@ -113,7 +113,7 @@ Installation of the pre-compiled releases should be as simple as: The current minimal requirements are as follows: * Python at least version 3.9. -* Intel MacOS (or Rosetta2) at least version 13.0. +* Intel MacOS (or Rosetta2) at least version 14.0. * Apple Silicon MacOS at least version 14.0. * Windows on AMD64. * Linux on x86_64 and Aarch64 given glibc at least version 2.28. diff --git a/pyproject.toml b/pyproject.toml index 76f20ed..dddddfa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,6 +46,5 @@ apk add openssl-dev [tool.cibuildwheel.macos] before-all = """ -brew install openssl@1.1 brew install --ignore-dependencies asio """ diff --git a/setup.py b/setup.py index 4b120b4..eb6f328 100644 --- a/setup.py +++ b/setup.py @@ -53,6 +53,7 @@ include_dirs.append(f"{prefix}/include") extra_link_args.insert(0, f"-L{prefix}/lib") extra_link_args.insert(0, f"-L{prefix}/opt/openssl@1.1/lib") + extra_link_args.append(f"-Wl,-ld_classic") # try conan dependency resolution conanfile = tuple(Path(__file__).parent.resolve().rglob("conanbuildinfo.json")) @@ -76,6 +77,7 @@ libraries=libraries, extra_compile_args=extra_compile_args, extra_link_args=extra_link_args, + cxx_std=20 ), ] diff --git a/src/_vroom.cpp b/src/_vroom.cpp index 3f62b2e..1988d6a 100644 --- a/src/_vroom.cpp +++ b/src/_vroom.cpp @@ -30,6 +30,7 @@ #include "algorithms/heuristics/heuristics.cpp" #include "algorithms/local_search/local_search.cpp" #include "algorithms/local_search/operator.cpp" +#include "algorithms/local_search/top_insertions.cpp" #include "algorithms/validation/check.h" // #include "routing/libosrm_wrapper.cpp" @@ -41,6 +42,7 @@ #include "structures/typedefs.h" #include "structures/generic/edge.cpp" +#include "structures/generic/matrix.cpp" #include "structures/generic/undirected_graph.cpp" #include "structures/vroom/cost_wrapper.cpp" @@ -48,6 +50,7 @@ #include "structures/vroom/solution_state.cpp" #include "structures/vroom/tw_route.cpp" +#include "structures/vroom/bbox.cpp" #include "structures/vroom/solution/computing_times.cpp" #include "structures/vroom/solution/violations.cpp" @@ -57,16 +60,18 @@ #include "problems/cvrp/operators/intra_exchange.cpp" #include "problems/cvrp/operators/intra_mixed_exchange.cpp" #include "problems/cvrp/operators/intra_or_opt.cpp" -#include "problems/cvrp/operators/intra_two_opt.cpp" #include "problems/cvrp/operators/intra_relocate.cpp" +#include "problems/cvrp/operators/intra_two_opt.cpp" #include "problems/cvrp/operators/mixed_exchange.cpp" #include "problems/cvrp/operators/or_opt.cpp" #include "problems/cvrp/operators/pd_shift.cpp" +#include "problems/cvrp/operators/priority_replace.cpp" #include "problems/cvrp/operators/relocate.cpp" #include "problems/cvrp/operators/reverse_two_opt.cpp" #include "problems/cvrp/operators/route_exchange.cpp" #include "problems/cvrp/operators/route_split.cpp" #include "problems/cvrp/operators/swap_star.cpp" +#include "problems/cvrp/operators/tsp_fix.cpp" #include "problems/cvrp/operators/two_opt.cpp" #include "problems/cvrp/operators/unassigned_exchange.cpp" #include "problems/vrp.cpp" @@ -76,16 +81,18 @@ #include "problems/vrptw/operators/intra_exchange.cpp" #include "problems/vrptw/operators/intra_mixed_exchange.cpp" #include "problems/vrptw/operators/intra_or_opt.cpp" -#include "problems/vrptw/operators/intra_two_opt.cpp" #include "problems/vrptw/operators/intra_relocate.cpp" +#include "problems/vrptw/operators/intra_two_opt.cpp" #include "problems/vrptw/operators/mixed_exchange.cpp" #include "problems/vrptw/operators/or_opt.cpp" #include "problems/vrptw/operators/pd_shift.cpp" +#include "problems/vrptw/operators/priority_replace.cpp" #include "problems/vrptw/operators/relocate.cpp" #include "problems/vrptw/operators/reverse_two_opt.cpp" #include "problems/vrptw/operators/route_exchange.cpp" #include "problems/vrptw/operators/route_split.cpp" #include "problems/vrptw/operators/swap_star.cpp" +#include "problems/vrptw/operators/tsp_fix.cpp" #include "problems/vrptw/operators/two_opt.cpp" #include "problems/vrptw/operators/unassigned_exchange.cpp" #include "problems/vrptw/vrptw.cpp" @@ -94,7 +101,7 @@ #include "problems/tsp/heuristics/local_search.cpp" #include "problems/tsp/tsp.cpp" -#include "utils/helpers.h" +#include "utils/helpers.cpp" #include "utils/version.cpp" namespace py = pybind11; @@ -137,7 +144,7 @@ PYBIND11_MODULE(_vroom, m) { .def(py::init<>()) .def(py::init([](const vroom::Duration lead_time, const vroom::Duration delay, - const std::unordered_set types) { + std::unordered_set types) { return new vroom::Violations(lead_time, delay, std::move(types)); })) .def(py::self += py::self) diff --git a/src/bind/amount.cpp b/src/bind/amount.cpp index 9492547..e6470cb 100644 --- a/src/bind/amount.cpp +++ b/src/bind/amount.cpp @@ -28,8 +28,8 @@ void init_amount(py::module_ &m) { py::format_descriptor::format(), 1, {a.size()}, {sizeof(int64_t)}); }) - .def("_lshift", [](const vroom::Amount &a, - const vroom::Amount &b) { return a << b; }) + .def("_lshift", + [](const vroom::Amount &a, const vroom::Amount &b) { return a < b; }) .def("_le", [](const vroom::Amount &a, const vroom::Amount &b) { return a <= b; }) .def("_push_back", &vroom::Amount::push_back) diff --git a/src/bind/break.cpp b/src/bind/break.cpp index 2c0543a..ef3397a 100644 --- a/src/bind/break.cpp +++ b/src/bind/break.cpp @@ -8,10 +8,11 @@ void init_break(py::module_ &m) { py::class_(m, "Break") .def(py::init([](vroom::Break &b) { return b; }), py::arg("break")) - .def(py::init &, - vroom::Duration, std::string &, std::optional &>(), - py::arg("id"), py::arg("time_windows"), py::arg("service"), - py::arg("description"), py::arg("max_load")) + .def( + py::init &, vroom::Duration, + std::string &, std::optional &>(), + py::arg("id"), py::arg("time_windows"), py::arg("service"), + py::arg("description"), py::arg("max_load")) .def("_is_valid_start", &vroom::Break::is_valid_start, py::arg("time")) .def_readwrite("_id", &vroom::Break::id) .def_readwrite("_time_windows", &vroom::Break::tws) diff --git a/src/bind/generic/matrix.cpp b/src/bind/generic/matrix.cpp index bb65194..f7c7260 100644 --- a/src/bind/generic/matrix.cpp +++ b/src/bind/generic/matrix.cpp @@ -1,6 +1,6 @@ #include -#include "structures/generic/matrix.cpp" +#include "structures/generic/matrix.h" namespace py = pybind11; diff --git a/src/bind/input/input.cpp b/src/bind/input/input.cpp index 35316e8..b95b4a7 100644 --- a/src/bind/input/input.cpp +++ b/src/bind/input/input.cpp @@ -1,4 +1,5 @@ #include +#include #include @@ -32,6 +33,11 @@ void init_input(py::module_ &m) { vroom::Matrix &m) { self.set_durations_matrix(profile, std::move(m)); }) + .def("_set_distances_matrix", + [](vroom::Input &self, const std::string &profile, + vroom::Matrix &m) { + self.set_distances_matrix(profile, std::move(m)); + }) .def("_set_costs_matrix", [](vroom::Input &self, const std::string &profile, vroom::Matrix &m) { diff --git a/src/bind/location.cpp b/src/bind/location.cpp index 85dced8..0002bd5 100644 --- a/src/bind/location.cpp +++ b/src/bind/location.cpp @@ -5,7 +5,8 @@ void init_location(py::module_ &m) { py::class_(m, "Coordinates") - .def(py::init(), py::arg("lon"), py::arg("lat")); + .def(py::init(), py::arg("lon"), + py::arg("lat")); py::class_(m, "Location") .def(py::init(), py::arg("index")) diff --git a/src/bind/solution/route.cpp b/src/bind/solution/route.cpp index 5c507bf..b8b1f1a 100644 --- a/src/bind/solution/route.cpp +++ b/src/bind/solution/route.cpp @@ -8,25 +8,27 @@ void init_route(py::module_ &m) { py::class_(m, "Route") .def(py::init<>()) - .def(py::init([](vroom::Id vehicle, std::vector &steps, - vroom::Cost cost, vroom::Duration setup, - vroom::Duration service, vroom::Duration duration, - vroom::Duration waiting_time, vroom::Priority priority, - const vroom::Amount &delivery, - const vroom::Amount &pickup, const std::string &profile, - const std::string &description, - const vroom::Violations &violations) { - return new vroom::Route(vehicle, std::move(steps), cost, setup, service, - duration, waiting_time, priority, delivery, - pickup, profile, description, - std::move(violations)); - })) + .def(py::init( + [](vroom::Id vehicle, std::vector &steps, + vroom::UserCost cost, vroom::UserDuration duration, + vroom::UserDistance distance, vroom::UserDuration setup, + vroom::UserDuration service, vroom::UserDuration waiting_time, + vroom::Priority priority, const vroom::Amount &delivery, + const vroom::Amount &pickup, const std::string &profile, + const std::string &description, vroom::Violations &violations) { + return new vroom::Route(vehicle, std::move(steps), cost, duration, + distance, setup, service, waiting_time, + priority, delivery, pickup, profile, + description, std::move(violations)); + })) + .def_readwrite("vehicle", &vroom::Route::vehicle) .def_readonly("steps", &vroom::Route::steps) .def_readwrite("cost", &vroom::Route::cost) .def_readwrite("setup", &vroom::Route::setup) .def_readwrite("service", &vroom::Route::service) .def_readwrite("duration", &vroom::Route::duration) + .def_readwrite("distance", &vroom::Route::distance) .def_readwrite("waiting_time", &vroom::Route::waiting_time) .def_readwrite("priority", &vroom::Route::priority) .def_readwrite("delivery", &vroom::Route::delivery) diff --git a/src/bind/solution/solution.cpp b/src/bind/solution/solution.cpp index d392f1f..a3f6c7d 100644 --- a/src/bind/solution/solution.cpp +++ b/src/bind/solution/solution.cpp @@ -33,11 +33,10 @@ void init_solution(py::module_ &m) { py::class_(m, "Solution") .def(py::init([](vroom::Solution s) { return s; })) - .def(py::init()) - .def(py::init([](unsigned code, unsigned amount_size, + .def(py::init([](const vroom::Amount &zero_amount, std::vector &routes, std::vector &unassigned) { - return new vroom::Solution(code, amount_size, std::move(routes), + return new vroom::Solution(zero_amount, std::move(routes), std::move(unassigned)); })) .def("_routes_numpy", @@ -72,14 +71,18 @@ void init_solution(py::module_ &m) { strncpy(ptr[idx].type, type.c_str(), 9); strncpy(ptr[idx].description, step.description.c_str(), 40); - ptr[idx].longitude = step.location.has_coordinates() - ? step.location.lon() - : NA_SUBSTITUTE; - ptr[idx].latitude = step.location.has_coordinates() - ? step.location.lat() - : NA_SUBSTITUTE; - ptr[idx].location_index = step.location.user_index() - ? step.location.index() + ptr[idx].longitude = + step.location.has_value() && + step.location.value().has_coordinates() + ? step.location.value().coordinates().lon + : NA_SUBSTITUTE; + ptr[idx].latitude = + step.location.has_value() && + step.location.value().has_coordinates() + ? step.location.value().coordinates().lat + : NA_SUBSTITUTE; + ptr[idx].location_index = step.location.has_value() + ? step.location.value().index() : NA_SUBSTITUTE; ptr[idx].id = (step.step_type == vroom::STEP_TYPE::JOB or @@ -103,16 +106,14 @@ void init_solution(py::module_ &m) { [](vroom::Solution solution) { py::scoped_ostream_redirect stream( std::cout, py::module_::import("sys").attr("stdout")); - vroom::io::write_to_json(solution, false, ""); + vroom::io::write_to_json(solution, "", false); }) .def("_geometry_solution_json", [](vroom::Solution solution) { py::scoped_ostream_redirect stream( std::cout, py::module_::import("sys").attr("stdout")); - vroom::io::write_to_json(solution, true, ""); + vroom::io::write_to_json(solution, "", true); }) - .def_readwrite("code", &vroom::Solution::code) - .def_readwrite("error", &vroom::Solution::error) .def_readonly("summary", &vroom::Solution::summary) .def_readonly("_routes", &vroom::Solution::routes) .def_readonly("unassigned", &vroom::Solution::unassigned); diff --git a/src/bind/solution/summary.cpp b/src/bind/solution/summary.cpp index 51acd77..fda1e72 100644 --- a/src/bind/solution/summary.cpp +++ b/src/bind/solution/summary.cpp @@ -8,7 +8,7 @@ void init_summary(py::module_ &m) { py::class_(m, "Summary") .def(py::init<>()) - .def(py::init()) + .def(py::init()) .def_readwrite("cost", &vroom::Summary::cost) .def_readonly("routes", &vroom::Summary::routes) .def_readonly("unassigned", &vroom::Summary::unassigned) diff --git a/src/bind/time_window.cpp b/src/bind/time_window.cpp index 618d322..3fb0920 100644 --- a/src/bind/time_window.cpp +++ b/src/bind/time_window.cpp @@ -10,10 +10,12 @@ void init_time_window(py::module_ &m) { .def(py::init([]() { return new vroom::TimeWindow(); })) .def(py::init([](vroom::Duration start, vroom::Duration end) { return new vroom::TimeWindow(start, end); - }), py::arg("start"), py::arg("end")) + }), + py::arg("start"), py::arg("end")) .def("_is_default", &vroom::TimeWindow::is_default) .def(py::self < py::self) .def_readwrite("_start", &vroom::TimeWindow::start) .def_readwrite("_end", &vroom::TimeWindow::end) - .def_readonly_static("_DEFAULT_LENGTH", &vroom::TimeWindow::default_length); + .def_readonly_static("_DEFAULT_LENGTH", + &vroom::TimeWindow::default_length); } diff --git a/src/bind/utils.cpp b/src/bind/utils.cpp index 56a3682..23c2742 100644 --- a/src/bind/utils.cpp +++ b/src/bind/utils.cpp @@ -4,20 +4,10 @@ void init_utils(py::module_ &m) { - m.def( - "scale_from_user_duration", - &vroom::utils::scale_from_user_duration, - py::arg("duration") - ); - m.def( - "scale_to_user_duration", - &vroom::utils::scale_to_user_duration, - py::arg("duration") - ); - m.def( - "scale_to_user_cost", - &vroom::utils::scale_to_user_cost, - py::arg("cost") - ); - + m.def("scale_from_user_duration", &vroom::utils::scale_from_user_duration, + py::arg("duration")); + m.def("scale_to_user_duration", &vroom::utils::scale_to_user_duration, + py::arg("duration")); + m.def("scale_to_user_cost", &vroom::utils::scale_to_user_cost, + py::arg("cost")); } diff --git a/src/bind/vehicle.cpp b/src/bind/vehicle.cpp index 5dde498..1658893 100644 --- a/src/bind/vehicle.cpp +++ b/src/bind/vehicle.cpp @@ -7,40 +7,35 @@ namespace py = pybind11; void init_vehicle(py::module_ &m) { py::class_(m, "VehicleCosts") - .def(py::init(), - "VehicleCost constructor.", - py::arg("fixed") = 0, py::arg("per_hour") = 3600) + .def(py::init(), + "VehicleCost constructor.", py::arg("fixed") = 0, + py::arg("per_hour") = 3600) .def_readonly("_fixed", &vroom::VehicleCosts::fixed) .def_readonly("_per_hour", &vroom::VehicleCosts::per_hour); py::class_(m, "CostWrapper") - .def(py::init(), - "CostWrapper constructor", - py::arg("speed_factor"), py::arg("per_hour")) - .def("set_durations_matrix", &vroom::CostWrapper::set_durations_matrix) - .def("set_costs_matrix", &vroom::CostWrapper::set_costs_matrix) - .def("_get_speed_factor", &vroom::CostWrapper::get_speed_factor) - .def("_get_per_hour", &vroom::CostWrapper::get_per_hour); + .def(py::init(), + "CostWrapper constructor", py::arg("speed_factor"), + py::arg("per_hour"), py::arg("per_km")) + .def("set_durations_matrix", &vroom::CostWrapper::set_durations_matrix) + .def("set_distances_matrix", &vroom::CostWrapper::set_distances_matrix) + .def("set_costs_matrix", &vroom::CostWrapper::set_costs_matrix); py::class_(m, "Vehicle") .def(py::init &, std::optional &, std::string &, vroom::Amount &, vroom::Skills &, vroom::TimeWindow &, - std::vector &, std::string &, vroom::VehicleCosts, double, size_t, - std::optional, + std::vector &, std::string &, + vroom::VehicleCosts, double, std::optional &, + std::optional &, + std::optional &, std::vector &>(), "Vehicle constructor.", py::arg("id"), py::arg("start"), - py::arg("end"), py::arg("profile"), - py::arg("capacity"), - py::arg("skills"), - py::arg("time_window"), - py::arg("breaks"), - py::arg("description"), - py::arg("costs"), - py::arg("speed_factor"), - py::arg("max_tasks"), - py::arg("max_travel_time"), - py::arg("steps")) + py::arg("end"), py::arg("profile"), py::arg("capacity"), + py::arg("skills"), py::arg("time_window"), py::arg("breaks"), + py::arg("description"), py::arg("costs"), py::arg("speed_factor"), + py::arg("max_tasks"), py::arg("max_travel_time"), + py::arg("max_distance"), py::arg("steps")) // .def("has_start", &vroom::Vehicle::has_start) // .def("has_end", &vroom::Vehicle::has_end) .def("_has_same_locations", &vroom::Vehicle::has_same_locations) @@ -59,5 +54,6 @@ void init_vehicle(py::module_ &m) { // .def_readwrite("_speed_factor", &vroom::Vehicle::speed_factor) .def_readonly("_max_tasks", &vroom::Vehicle::max_tasks) .def_readonly("_max_travel_time", &vroom::Vehicle::max_travel_time) + .def_readonly("_max_distance", &vroom::Vehicle::max_distance) .def_readonly("_steps", &vroom::Vehicle::steps); } diff --git a/src/vroom/__init__.py b/src/vroom/__init__.py index 3e8209e..7293df9 100644 --- a/src/vroom/__init__.py +++ b/src/vroom/__init__.py @@ -1,4 +1,5 @@ """Vehicle routing open-source optimization machine (VROOM).""" + import sys from typing import Optional, Sequence from ._vroom import _main, JOB_TYPE, STEP_TYPE # type: ignore diff --git a/src/vroom/amount.py b/src/vroom/amount.py index 675f906..1724206 100644 --- a/src/vroom/amount.py +++ b/src/vroom/amount.py @@ -1,4 +1,5 @@ """An array of integers describing multidimensional quantities.""" + from __future__ import annotations from typing import Sequence, Union @@ -41,7 +42,7 @@ class Amount(_vroom.Amount): def __init__( self, - amount: Union[Amount, Sequence[int]] = (), + amount: Union[Amount, Sequence[int], numpy.ndarray] = (), ) -> None: """ Initialize. diff --git a/src/vroom/break_.py b/src/vroom/break_.py index 4362b40..83678a3 100644 --- a/src/vroom/break_.py +++ b/src/vroom/break_.py @@ -109,8 +109,7 @@ def max_load(self, value: Union[None, Amount, Sequence[int]]) -> None: def is_valid_start(self, time: int): """Check if break has a valid start time.""" - return self._is_valid_start( - time=_vroom.scale_from_user_duration(time)) + return self._is_valid_start(time=_vroom.scale_from_user_duration(time)) def __repr__(self) -> str: args = [f"{self.id}"] diff --git a/src/vroom/input/input.py b/src/vroom/input/input.py index 26bc509..75eff3b 100644 --- a/src/vroom/input/input.py +++ b/src/vroom/input/input.py @@ -1,4 +1,5 @@ """VROOM input definition.""" + from __future__ import annotations from typing import Dict, Optional, Sequence, Set, Union from pathlib import Path @@ -30,6 +31,7 @@ class Input(_vroom.Input): """ _geometry: bool = False + _distances: bool = False def __init__( self, @@ -75,8 +77,9 @@ def __repr__(self) -> str: args.append(f"router={self._router}") return f"{self.__class__.__name__}({', '.join(args)})" - @staticmethod + @classmethod def from_json( + cls, filepath: Path, servers: Optional[Dict[str, Union[str, _vroom.Server]]] = None, router: _vroom.ROUTER = _vroom.ROUTER.OSRM, @@ -107,7 +110,7 @@ def from_json( if geometry is None: geometry = servers is not None if geometry: - self._set_geometry(True) + cls._set_geometry(True) instance = Input(servers=servers, router=router) with open(filepath) as handle: instance._from_json(handle.read(), geometry) @@ -287,6 +290,27 @@ def set_durations_matrix( matrix_input = _vroom.Matrix(numpy.asarray(matrix_input, dtype="uint32")) self._set_durations_matrix(profile, matrix_input) + def set_distances_matrix( + self, + profile: str, + matrix_input: ArrayLike, + ) -> None: + """Set distances matrix. + + Args: + profile: + Name of the transportation category profile in question. + Typically "car", "truck", etc. + matrix_input: + A square matrix consisting of distances between each location of + interest. Diagonal is canonically set to 0. + """ + assert isinstance(profile, str) + if not isinstance(matrix_input, _vroom.Matrix): + matrix_input = _vroom.Matrix(numpy.asarray(matrix_input, dtype="uint32")) + self._set_distances_matrix(profile, matrix_input) + self._distances = True + def set_costs_matrix( self, profile: str, @@ -317,7 +341,7 @@ def solve( exploration_level=exploration_level, nb_threads=nb_threads, ) - ) solution._geometry = self._geometry + solution._distances = self._distances return solution diff --git a/src/vroom/input/vehicle_step.py b/src/vroom/input/vehicle_step.py index 003ddc3..803deb1 100644 --- a/src/vroom/input/vehicle_step.py +++ b/src/vroom/input/vehicle_step.py @@ -466,7 +466,7 @@ def __new__( if step_type._step_type in (_vroom.STEP_TYPE.START, _vroom.STEP_TYPE.END): assert id == 0 id = None - + service_at = step_type._forced_service._service_at service_after = step_type._forced_service._service_after service_before = step_type._forced_service._service_before diff --git a/src/vroom/solution/solution.py b/src/vroom/solution/solution.py index 7a2c0ba..c87c873 100644 --- a/src/vroom/solution/solution.py +++ b/src/vroom/solution/solution.py @@ -1,4 +1,5 @@ """The computed solutions.""" + from typing import Any, Dict, Union from pathlib import Path import io @@ -23,6 +24,7 @@ class Solution(_vroom.Solution): """ _geometry: bool = False + _distances: bool = False @property def routes(self) -> pandas.DataFrame: @@ -57,6 +59,8 @@ def routes(self) -> pandas.DataFrame: The identifier for the task that was performed. description: Text description provided to this step. + distance: + Total route distance. """ array = numpy.asarray(self._routes_numpy()) frame = pandas.DataFrame( @@ -72,9 +76,9 @@ def routes(self) -> pandas.DataFrame: "service": array["service"], "waiting_time": array["waiting_time"], "location_index": array["location_index"], - "longitude": pandas.array(array["longitude"]), - "latitude": pandas.array(array["latitude"]), - "id": pandas.array(array["id"], dtype="Int64"), + "longitude": pandas.array(array["longitude"].tolist()), + "latitude": pandas.array(array["latitude"].tolist()), + "id": pandas.array(array["id"].tolist(), dtype="Int64"), "description": array["description"].astype("U40"), } ) @@ -83,7 +87,7 @@ def routes(self) -> pandas.DataFrame: del frame[column] else: frame.loc[frame[column] == NA_SUBSTITUTE, column] = pandas.NA - if self._geometry: + if self._geometry or self._distances: frame["distance"] = array["distance"] return frame @@ -91,7 +95,7 @@ def to_dict(self) -> Dict[str, Any]: """Convert solution into VROOM compatible dictionary.""" stream = io.StringIO() with redirect_stdout(stream): - if self._geometry: + if self._geometry or self._distances: self._geometry_solution_json() else: self._solution_json() @@ -101,7 +105,7 @@ def to_json(self, filepath: Union[str, Path]) -> None: """Store solution into VROOM compatible JSON file.""" with open(filepath, "w") as handler: with redirect_stdout(handler): - if self._geometry: + if self._geometry or self._distances: self._geometry_solution_json() else: self._solution_json() diff --git a/src/vroom/time_window.py b/src/vroom/time_window.py index bdfa845..1125d50 100644 --- a/src/vroom/time_window.py +++ b/src/vroom/time_window.py @@ -1,9 +1,8 @@ """Time window for when a delivery/pickup/task is possible.""" + from __future__ import annotations from typing import Any, Optional, Sequence, Union -import numpy - from . import _vroom diff --git a/src/vroom/vehicle.py b/src/vroom/vehicle.py index 2fa2705..2c0d6a8 100644 --- a/src/vroom/vehicle.py +++ b/src/vroom/vehicle.py @@ -11,8 +11,9 @@ from . import _vroom -MAX_UINT = int(numpy.iinfo(numpy.uintp).max) +MAX_UINT = int(numpy.iinfo(numpy.uint).max) MAX_INT = int(numpy.iinfo(numpy.intp).max) +MAX_UINT32 = int(numpy.iinfo(numpy.uint32).max) class VehicleCosts(_vroom.VehicleCosts): @@ -91,6 +92,10 @@ class Vehicle(_vroom.Vehicle): the default. max_tasks: The maximum number of tasks this vehicle can perform. + max_travel_time: + An integer defining the maximum travel time for this vehicle. + max_distance: + An integer defining the maximum distance for this vehicle. steps: Set of custom steps this vehicle should take. @@ -113,8 +118,9 @@ def __init__( description: str = "", costs: VehicleCosts = VehicleCosts(), speed_factor: float = 1.0, - max_tasks: int = MAX_UINT, + max_tasks: Optional[int] = MAX_UINT, max_travel_time: Optional[int] = None, + max_distance: Optional[int] = MAX_UINT32, steps: Sequence[VehicleStep] = (), ) -> None: self._speed_factor = float(speed_factor) @@ -133,6 +139,7 @@ def __init__( speed_factor=self._speed_factor, max_tasks=max_tasks, max_travel_time=max_travel_time, + max_distance=max_distance, steps=steps, ) assert isinstance(self.capacity, Amount) @@ -170,6 +177,7 @@ def __repr__(self) -> str: ("speed_factor", 1.0), ("max_tasks", MAX_UINT), ("max_travel_time", _vroom.scale_to_user_duration(MAX_INT)), + ("max_distance", MAX_UINT32), ("steps", []), ]: attribute = getattr(self, name) @@ -237,6 +245,10 @@ def max_tasks(self) -> str: def max_travel_time(self) -> str: return _vroom.scale_to_user_duration(self._max_travel_time) + @property + def max_distance(self) -> str: + return self._max_distance + @property def steps(self) -> List[VehicleStep]: return [VehicleStep(step) for step in self._steps] diff --git a/test/test_libvroom_examples.py b/test/test_libvroom_examples.py index d7f35b4..dc7baa5 100644 --- a/test/test_libvroom_examples.py +++ b/test/test_libvroom_examples.py @@ -15,6 +15,13 @@ def test_example_with_custom_matrix(): [197, 2256, 0, 1102], [1299, 3153, 1102, 0]], ) + problem_instance.set_distances_matrix( + profile="car", + matrix_input=[[0, 21040, 1970, 12990], + [21030, 0, 22550, 31520], + [1970, 22560, 0, 11020], + [12990, 31530, 11020, 0]], + ) problem_instance.add_vehicle([vroom.Vehicle(7, start=0, end=0), vroom.Vehicle(8, start=2, end=2)]) problem_instance.add_job([vroom.Job(id=1414, location=0), @@ -37,3 +44,5 @@ def test_example_with_custom_matrix(): assert numpy.all(routes.arrival == [0, 2104, 4207, 4207, 0, 1102, 2204, 2204]) assert numpy.all(routes.location_index == [0, 1, 0, 0, 2, 3, 2, 2]) + assert numpy.all(routes.distance == [0, 21040, 42070, 42070, + 0, 11020, 22040, 22040]) \ No newline at end of file diff --git a/test/test_vehicle.py b/test/test_vehicle.py index 6a4b188..c26b41c 100644 --- a/test/test_vehicle.py +++ b/test/test_vehicle.py @@ -19,6 +19,10 @@ def test_repr(): == "vroom.Vehicle(3, end=7, speed_factor=2.0)") assert (repr(vroom.Vehicle(3, start=7, max_tasks=17)) == "vroom.Vehicle(3, start=7, max_tasks=17)") + assert (repr(vroom.Vehicle(3, start=7, max_distance=17)) + == "vroom.Vehicle(3, start=7, max_distance=17)") + assert (repr(vroom.Vehicle(3, start=7, max_travel_time=17)) + == "vroom.Vehicle(3, start=7, max_travel_time=17)") assert (repr(vroom.Vehicle(3, end=7, steps=[vroom.VehicleStep("single", 3)])) == """vroom.Vehicle(3, end=7, \ steps=[vroom.VehicleStepStart(), vroom.VehicleStepSingle(3), vroom.VehicleStepEnd()])""") diff --git a/vroom b/vroom index e2653a4..1fd711b 160000 --- a/vroom +++ b/vroom @@ -1 +1 @@ -Subproject commit e2653a46f5e84d52032f2f47788e3898d9ef98fc +Subproject commit 1fd711bc8c20326dd8e9538e2c7e4cb1ebd67bdb