Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
aa2f910
adds DUSE_LIBGLPK flag
juanluisrto Jul 10, 2023
bae5493
adding missing dependencies
jonathf Jul 10, 2023
76628bd
add glpk
juanluisrto Jul 12, 2023
730f541
adds linking flag
juanluisrto Jul 13, 2023
be16fb2
adding glpk to cibuildwheel
jonathf Jul 13, 2023
6829a8e
adding more glpk to cibuildwheel
jonathf Jul 13, 2023
62d0dad
giving up on win, updating on linux
jonathf Jul 13, 2023
a67b809
resetting conan
jonathf Jul 13, 2023
59fb238
try without the recipe version
nilsnolde Aug 7, 2023
b71d2af
trigger win
nilsnolde Aug 7, 2023
16f4a82
conan: only build missing binary packages
nilsnolde Aug 7, 2023
3e022e0
revert workflow stuff
nilsnolde Aug 7, 2023
4f9128c
don't build CI on irrelevant files
nilsnolde Aug 7, 2023
645cdcd
update readme
nilsnolde Aug 8, 2023
23570da
Fix Conan GLPK linking on macOS
blegat Feb 11, 2026
4ee9dde
Add plan mode test
blegat Feb 11, 2026
7403465
Fix tests
blegat Feb 15, 2026
72fa346
bugfixes
jonathf Feb 16, 2026
f9bd6cd
Attempt to fix ci
blegat Feb 16, 2026
b6a16fc
Try fix
blegat Feb 17, 2026
ab88bed
Try fix
blegat Feb 17, 2026
d911933
Fix coverage
blegat Feb 17, 2026
25ee57f
Fix doctest
blegat Feb 17, 2026
8e78878
Attempt to fix tests
blegat Feb 17, 2026
6e23849
Move pandas back
blegat Mar 3, 2026
166d11e
Use openssl back in --build
blegat Mar 3, 2026
1019b8a
Apply suggestion from @blegat
blegat Mar 3, 2026
5486dcb
Merge branch 'main' into bugfix/plan-mode
blegat Mar 3, 2026
f93cf97
Build glpk if needed
blegat Mar 9, 2026
985440b
Use --build=missing
blegat Mar 9, 2026
64813c6
Add comments
blegat Mar 9, 2026
530319f
Make GLPK mandatory
blegat Mar 11, 2026
147b5d8
Apply suggestion from @jonathf
blegat Mar 11, 2026
37c8fc1
Apply suggestion from @blegat
blegat Mar 11, 2026
ec8f40d
Apply suggestion from @blegat
blegat Mar 11, 2026
bf55850
Move import pytest to the top
blegat Mar 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion .github/workflows/main_push.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ on:
push:
branches:
- main
paths-ignore:
- '.gitignore'
- 'LICENSE'
- '*.rst'

jobs:
check-platform-builds:
Expand Down Expand Up @@ -52,7 +56,9 @@ jobs:
conan profile update "settings.compiler=Visual Studio" default
conan profile update "settings.compiler.version=17" default
conan config set "storage.path=$env:GITHUB_WORKSPACE/conan_data"
conan install --build=openssl --install-folder conan_build .
# Conan Center has no prebuilt binaries for Windows (VS17) for glpk, openssl, or
# their build deps (e.g. libtool). --build=missing builds any missing package from source.
conan install --build=missing --install-folder conan_build .

- name: Set up QEMU
if: matrix.platform == 'linux'
Expand Down
10 changes: 9 additions & 1 deletion .github/workflows/pull_request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,17 @@ on:
pull_request:
branches:
- main
paths-ignore:
- '.gitignore'
- 'LICENSE'
- '*.rst'
push:
branches:
- main
paths-ignore:
- '.gitignore'
- 'LICENSE'
- '*.rst'

jobs:
test:
Expand All @@ -23,7 +31,7 @@ jobs:
python-version: "3.10"

- name: "Install system dependencies"
run: sudo apt update -y && sudo apt install -y libssl-dev libasio-dev
run: sudo apt update -y && sudo apt install -y libssl-dev libasio-dev libglpk-dev glpk-utils

- name: "Install python environment"
run: |
Expand Down
4 changes: 3 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,9 @@ jobs:
conan profile update "settings.compiler=Visual Studio" default
conan profile update "settings.compiler.version=17" default
conan config set "storage.path=$env:GITHUB_WORKSPACE/conan_data"
conan install --build=openssl --install-folder conan_build .
# Conan Center has no prebuilt binaries for Windows (VS17) for glpk, openssl, or
# their build deps (e.g. libtool). --build=missing builds any missing package from source.
conan install --build=missing --install-folder conan_build .

- name: Set up QEMU
if: matrix.platform == 'linux' && runner.arch == 'X64'
Expand Down
13 changes: 3 additions & 10 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -63,16 +63,9 @@ Basic usage
'waiting_time', 'location_index', 'id', 'description'],
dtype='object')

>>> solution.routes[["vehicle_id", "type", "arrival", "location_index", "id"]]
vehicle_id type arrival location_index id
0 47 start 0 0 <NA>
1 47 job 2104 1 1515
2 47 job 4207 0 1414
3 47 end 4207 0 <NA>
4 48 start 0 2 <NA>
5 48 job 1102 3 1717
6 48 job 2204 2 1616
7 48 end 2204 2 <NA>
>>> groups = solution.routes[solution.routes.type == "job"].groupby("vehicle_id")["id"].apply(lambda x: set(x.dropna().astype(int)))

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are two possible solutions:

  • Vehicle 47: jobs in order 1414, 1515 (was 1515, 1414), with arrivals 0, 2104, 4207 for the two jobs.
  • Vehicle 48: jobs in order 1616, 1717 (was 1717, 1616), with arrivals 0, 1102, 2204 for the two jobs.
    Cost stays 6411; the solver is just returning a different (equivalent) route order.

This makes the doctest fail on ci if I write one solution here and the solver returns another one.
The change here make it work in both cases

@jonathf jonathf Mar 2, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the README exists primarily for domonstration purpouses. If the example is ambiguous, I think the right correction is to change the example so the ambiguity doesn't arrises, not to do check for multiple solutions.

>>> groups[47] == {1414, 1515} and groups[48] == {1616, 1717}
True

Usage with a routing engine
---------------------------
Expand Down
1 change: 1 addition & 0 deletions conanfile.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
[requires]
openssl/1.1.1m
asio/1.21.0
glpk/5.0

[generators]
json
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ before-all = """
dnf update -y
dnf module enable -y mariadb-devel
dnf install -y openssl-devel asio-devel
dnf install -y glpk-devel glpk-utils
"""
archs = ["x86_64", "aarch64"]

Expand All @@ -70,11 +71,13 @@ select = "*musllinux*"
before-all = """
apk add asio-dev
apk add openssl-dev
apk add glpk
"""
[tool.cibuildwheel.macos]

before-all = """
brew install --ignore-dependencies asio
brew install glpk
"""


Expand Down
19 changes: 19 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"-DASIO_STANDALONE",
"-DUSE_PYTHON_BINDINGS",
"-DUSE_ROUTING=true",
"-DUSE_LIBGLPK=true",
]
extra_link_args = []

Expand All @@ -39,13 +40,23 @@
"-DNDEBUG",
"-DUSE_PYTHON_BINDINGS",
"-DUSE_ROUTING=true",
"-DUSE_LIBGLPK=true",
]
extra_link_args = [
"-lpthread",
"-lssl",
"-lcrypto",
"-lglpk",
]

# Add gcov coverage flags when CFLAGS/CXXFLAGS request coverage (e.g. CI).
# setuptools does not pass CXXFLAGS to C++ extensions by default.
_cflags = os.environ.get("CFLAGS", "") + " " + os.environ.get("CXXFLAGS", "")
if "coverage" in _cflags or "-fprofile-arcs" in _cflags:
extra_compile_args = [a for a in extra_compile_args if a != "-O3"]
extra_compile_args.extend(["-O0", "-g", "-fprofile-arcs", "-ftest-coverage"])
extra_link_args.append("--coverage")

if platform.system() == "Darwin":
# Homebrew puts include folders in weird places.
prefix = run(["brew", "--prefix"], capture_output=True).stdout.decode("utf-8")[
Expand All @@ -68,6 +79,14 @@
libraries.extend(dep["libs"])
libraries.extend(dep["system_libs"])
library_dirs.extend(dep["lib_paths"])
# So the linker finds Conan-built libs (fixes macOS/Windows when using Conan)
for lib_path in dep["lib_paths"]:
extra_link_args.insert(0, f"-L{lib_path}")
if platform.system() == "Darwin":
# Embed rpath so the dynamic loader finds Conan libs at runtime (e.g. libglpk.dylib)
for dep in conan_deps:
for lib_path in dep["lib_paths"]:
extra_link_args.append(f"-Wl,-rpath,{lib_path}")
else:
logging.warning(
"Conan not installed and/or no conan build detected. Assuming dependencies are installed."
Expand Down
3 changes: 2 additions & 1 deletion src/_vroom.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@
#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 "algorithms/validation/check.cpp"
#include "algorithms/validation/choose_ETA.cpp"

// #include "routing/libosrm_wrapper.cpp"
#include "routing/http_wrapper.cpp"
Expand Down
2 changes: 1 addition & 1 deletion src/bind/input/input.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -79,5 +79,5 @@ void init_input(py::module_ &m) {
"Solve routing problem",
py::arg("nb_searches"), py::arg("depth"), py::arg("nb_threads"), py::arg("timeout")
)
.def("check", &vroom::Input::check);
.def("check", &vroom::Input::check, "Check solution feasibility", py::arg("nb_thread") = 1);
}
65 changes: 58 additions & 7 deletions test/test_libvroom_examples.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Reproduce the libvroom_example as tests."""
import pytest
import numpy
import pandas

Expand Down Expand Up @@ -37,12 +38,62 @@ def test_example_with_custom_matrix():

routes = solution.routes
assert numpy.all(routes.vehicle_id.drop_duplicates() == [7, 8])
assert numpy.all(routes.id == [None, 1515, 1414, None,
None, 1717, 1616, None])
assert numpy.all(routes.type == ["start", "job", "job", "end",
"start", "job", "job", "end"])
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])
# Solver may return either job order per vehicle (both optimal)
job_rows = routes[routes.type == "job"]
by_vehicle = {
7: set(job_rows[job_rows.vehicle_id == 7]["id"].dropna().astype(int)),
8: set(job_rows[job_rows.vehicle_id == 8]["id"].dropna().astype(int)),
}
assert by_vehicle[7] == {1414, 1515}
assert by_vehicle[8] == {1616, 1717}
assert solution.summary.cost == 6411


def test_plan_mode_check():
"""Test plan mode (Input.check()) when built with USE_LIBGLPK.

Plan mode validates predefined vehicle routes (steps) and sets ETAs.
Uses the same problem as test_example_with_custom_matrix with vehicles
that have predefined steps matching the optimal solution. Jobs must be
added before vehicles so that vehicle steps can reference job ids.
"""
problem_instance = vroom.Input()
problem_instance.set_durations_matrix(
profile="car",
matrix_input=[[0, 2104, 197, 1299],
[2103, 0, 2255, 3152],
[197, 2256, 0, 1102],
[1299, 3153, 1102, 0]],
)
# Jobs first: vehicle steps reference these job ids
problem_instance.add_job([vroom.Job(id=1414, location=0),
vroom.Job(id=1515, location=1),
vroom.Job(id=1616, location=2),
vroom.Job(id=1717, location=3)])
problem_instance.add_vehicle([
vroom.Vehicle(
7,
start=0,
end=0,
steps=[
vroom.VehicleStep("start"),
vroom.VehicleStep("single", 1515),
vroom.VehicleStep("single", 1414),
vroom.VehicleStep("end"),
],
),
vroom.Vehicle(
8,
start=2,
end=2,
steps=[
vroom.VehicleStep("start"),
vroom.VehicleStep("single", 1717),
vroom.VehicleStep("single", 1616),
vroom.VehicleStep("end"),
],
),
])
assert problem_instance.check(), "no feasible route possible."
Loading