Changelog¶
All notable changes to this project are documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
Unreleased¶
Added¶
- Algorithm study of the technician case,
examples/technician_madrid_study/: 59 solver configurations on the 183-node Madrid instance under equal wall-clock budgets, a rigorous bound (MILPproves the plain tour optimal at 1 078.3 driving minutes in 559 s, hence at least 14 days) and an exact polish that re-solves every day withHeldKarp; the recorded results are committed underresults/and the user guide page Which solver wins: an algorithm study narrates them (best plan: 15 days, 1 532.8 minutes of driving).
[2.1.0] - 2026-09-05¶
Added¶
- Service times:
fit(..., service_time=)adds the time spent at each stop to the per-trip budget;skroute.metrics.timetable(withStop,timetable_summaryandunits=) turns a solution into a per-day timetable (D32). skroute.preprocessing.maps: real road travel times (travel_time_matrix, OSRM or Google),geocode(Nominatim or Google) andfetch_pois(OpenStreetMap through Overpass) (D33).GoogleDistanceMatrix(departure_time=)asks for traffic-aware durations; anetworkpytest marker (deselected by default, run nightly) exercises the live services.skroute.viz.google_maps: the plan on Google Maps as Directions URLs, a KML for Google My Maps and a standalone Maps JavaScript page;plot_route_map(names=, trip_names=)(D34).- Worked case
examples/technician_madrid.py: a maintenance technician covering every Burger King of the Madrid region from an office in Leganés, with real driving times, 30-minute visits and 8-hour days; the data are committed underexamples/data/(OpenStreetMap + OSRM, 2026-09-05) and the user guide page A real case: the technician's plan narrates it (D35).
Changed¶
service_timegiven as a pandas Series must carry the labels ofXin row order (liketime_matrix); a bool, string or other non-numeric scalar is refused with a type-oriented message; a 0-d array is the scalar it wraps.timetablewarns when a route read as driven runs over the budget;Stopcompares without its clock;timetable_summaryrefuses the DataFrame form with a pointer togroupby("day").fetch_pois(...).labelsis an object ndarray (was a list);travel_time_matrixnever requests a one-point table block; the Overpass socket timeout is the query timeout plus ten seconds;GoogleDistanceMatrix(timeout=).- The Google Maps exports refuse a route that visits a node twice and a
names=/trip_names=given as one string; the JavaScript page paces its Directions requests (two at a time, one retry).
2.0.0 - 2026-09-04¶
A complete rewrite. Version 1.0.0a2 (2021) is the last release of the old code base; see the migration guide for a symbol-by-symbol map.
Added¶
- One problem model for every solver: a closed tour from a depot over a dense cost
matrix, optionally decoded into trips under a per-trip working-time budget with a
fixed charge per extra trip (
RoutingProblem,split="greedy"or"optimal"). - Eighteen solvers over the same core: exact
BruteForce,HeldKarpandMILP(Dantzig–Fulkerson–Johnson with lazy subtour cuts on HiGHS); constructionNearestNeighbour,Insertion,ClarkeWrightandNRBS; local searchTwoOpt,OrOpt,LocalSearchandIteratedLocalSearch; metaheuristicsSimulatedAnnealing,TabuSearch,Genetic(OX/PMX, optional memetic descent),AntColony(MAX-MIN) andSOM; theMultiStartwrapper and the legacyEnsemble*names. skroute.metrics(route_cost,split_trips),skroute.check_router(the estimator battery),skroute.all_solvers,skroute.set_log_level.- Datasets return
Bunchobjects;load_tsp(name)and a TSPLIB reader (read_tsplib,read_tsplib_tour);distance_matrixwith the TSPLIB metrics (EUC_2D,CEIL_2D,MAN_2D,ATT,GEO), Euclidean, Manhattan and haversine. - Wheels for CPython 3.11–3.14 on Linux, macOS and Windows; typed (
py.typed). - Progress callbacks:
fit(..., callback=)andskroute.RouteEventreportstart/iteration/endevents with label-space tours from every solver; returningTruestops an iterative solver (stop_reason_ == "callback");MultiStartforwards sequential restarts;check_routergains check 14. Theendevent reportsn_iter_as its iteration; afitthat raises (in the kernel or in the callback) never leaves the estimator looking fitted;_emitrefuses a secondstartor anendfrom a solver (the base class emits both) while a callback is set. skroute.viz(extraviz, maps withviz-map):plot_route,plot_history,LivePlot(watch the current and best tour whilefitruns, in scripts and notebooks),Recorder(record a run, animate it, save a GIF or a Plotly figure with a slider) andplot_route_mapon OpenStreetMap tiles.- Watch the search being built: construction solvers report the partial structure after every step
(
extra["edges"]),AntColonyits strongest pheromone trails (extra["edge_weights"]) andSOMits ring (extra["ring"]);LivePlotdraws them (show=,trail=),Recorderkeeps wall-clock timestamps and replays a run at time-lapse speed (replay(speed=),animate(speed=, fps=),save()to GIF or MP4, a Plotly speed menu);examples/live_demo.py --record/--speed/--set.LivePlot(map=True)picks the Plotly backend by itself (backend=None);Recorder.replaypaces on a target clock (short gaps accumulate, drawing time is absorbed, gaps over two seconds are cut) andRecorder.animate(speed=)really paces the frames underplt.show(). A callback answeringTrueatstartof a construction solver silences every step event (the result never depends on the callback).
Changed¶
fit()takes the cost matrix (numpy, DataFrame or dict-of-dicts) and returnsself;time_matrixis keyword-only; results live inroute_,tour_,trips_,cost_,history_,n_iter_and friends.- Objective: trips never exceed
max_time_workincluding the return leg;peoplemultiplies onlyextra_cost. - Default hyper-parameters changed for every solver; results with defaults differ from 1.0 (table in the migration guide).
NRBS: the five exponents must be >= 0 (1.0 validated only the type and accepted negative values).Insertion(strategy="cheapest")honours a first-edge tie rule.ClarkeWrightchecks a merge on the trip as it will be driven and may flip a trip when only the reverse orientation fits the budget.Genetic: real OX/PMX crossover.SimulatedAnnealing: the route/cost aliasing bug is fixed, three move types, automatic temperature.TabuSearch: rewritten with edge tabu attributes (a tenure lasts exactlytenureiterations; on asymmetric matrices a reversal marks every reversed arc).NRBS: union-find cycle check, the hard-coded route length is gone,distance_weigth→distance_weight.SOM: numpy only.- Datasets: pickled DataFrames replaced by CSV;
frame/as_frame=Trueinstead of"DataFrame";feature_namesdropped. - Dependencies: numpy, scipy and joblib only; pandas and googlemaps are optional extras; tensorflow, scikit-learn and tqdm are gone. Python >= 3.11, numpy 2 ready.
EnsembleGeneticandEnsembleSimulatedAnnealingremain as explicit-parameter wrappers overMultiStart(threads by default; results independent ofn_jobs).- Every solver is checked by the public
check_routerbattery and by tolerance tests against the published optima of the bundled instances (tests/tolerances.py,docs/benchmarks.md).
Removed¶
skroute.cluster(use scikit-learn directly),preprocessing.df_to_tuple,preprocessing.matrix_parse,preprocessing.DataLossWarning,CostScraper.to_pickle, the tuple return value offit(),route_example.
Fixed¶
load_costs_qatarloaded Valencia;matrix_to_dictreferenced an undefined variable; the datasets__all__was missing a comma; the TSPLIB reader used the deprecatedsep="\s"; the 1.0 Cython code did not compile with Cython 3.
Deprecated¶
skroute.heuristics.*and the legacyskroute.metaheuristics.<subpackage>paths (import fromskroute.exact,skroute.construction,skroute.metaheuristics,skroute.ensemble),CostScraper,load_costs_qatar,mode=in the TSP loaders. Removed in 3.0.
1.0.0a2 - 2021-06-21¶
Last release of the original code base (Cython 0.29, TensorFlow SOM, googlemaps).