helixordevelopers
Get a licenseSign up

Solving routes and rosters, embedded or hosted

The solver runs in one of two deployment models, and you choose which. Embedded runs the solver inside your process. Hosted delegates to the Helixor solver service. The two return the same answer and raise the same errors. There is no default and no fallback from one to the other.

Preview20 minutesIntermediatePython 3.12

Runtime 0.3.0 or later

HelixorSolver(mode=...) ships in runtime 0.3.0. The embedded model also needs helixor-solvers 0.7, which is not part of the runtime wheel. The hosted model needs access to the Helixor solver service. See How this page was verified.

Choose the model#

EmbeddedHosted
Where it runsIn your process. No network call.The Helixor solver service, over HTTPS.
ConfigureHelixorSolver(mode="embedded", license=lic)HelixorSolver(mode="hosted", base_url=..., auth=token)
Installhelixor-runtime[solvers] for routing (pure Python). Add [solvers-full] for rostering and the hybrid genetic search.helixor-runtime[solvers-hosted] (Python 3.11+)
Authorises the solveA licence entitled to vrp.fleet.v1 or rostering.staff.v1The service token

Missing or contradictory configuration is refused with SolverConfigurationError before anything runs. For example, base_url given to the embedded model is refused, and so is a hosted model with no auth.

The answer#

solve(problem_type, problem, options=...) returns the solve contract. It has the same fields in both models:

FieldMeaning
verdictfeasible or infeasible, decided by the certificate
solutionThe routes or roster, only when the verdict is feasible. It is null otherwise; a plan that breaks a hard rule is never returned as the answer.
shortfallsWhat is short, and by how much: failed pre-solve checks, unserved or late stops, hard rules broken
alternatesWhen infeasible, ranked options, each saying what it relaxes
certificateHow the verdict was reached, including the rejected plan when there is one
metadatamodel, the engine that ran and why, the budget, and the seed and final cost

The same typed errors come from both models:

  • SolverInputError: the problem is missing data. It is HTTP 422 on the service.
  • SolverOptionError: an option or problem type is not accepted. Also 422.
  • SolverEngineUnavailableError: the requested engine is not installed where the solve runs. It is 503 on the service.
  • SolverServiceError: the hosted service could not be used. This one is hosted only.

Routing engines#

options.engineWhat runs
auto (default)With time windows, the local search. Otherwise the hybrid genetic search when it is installed, and the local search when it is not. The choice is named in metadata.engine_reason.
hgsSplit seeds plus hybrid genetic search, for capacity only. A request with time windows is refused rather than solved as if it had none.
local_searchA sweep seed, then simulated annealing with ruin-and-recreate. Pure Python. Sequences stops against their time windows.

Also available: time_limit_seconds, up to 600, and seed. Unknown options are refused.

Steps#

  1. Get the scripts

    This page follows use_cases/fleet_routing.py and use_cases/shift_rostering.py in the public demos repository. Each script takes one switch, --mode embedded or --mode hosted. Without it, the solve step says it did not run.

    python use_cases/fleet_routing.py --mode embedded
    HELIXOR_SOLVER_URL=https://... HELIXOR_SOLVER_TOKEN=... python use_cases/fleet_routing.py --mode hosted
    
  2. Configure the model
    def configured_solver(mode, license):
        if mode == "embedded":
            return HelixorSolver(mode="embedded", license=license)
        if mode == "hosted":
            return HelixorSolver(mode="hosted",
                                 base_url=os.environ["HELIXOR_SOLVER_URL"],
                                 auth=os.environ["HELIXOR_SOLVER_TOKEN"])
        return None
    
  3. Solve today's deliveries as stated

    One van with 40 crates of capacity, 74 crates of demand, and a kiosk that cannot be reached inside its window. The answer is infeasible, with no plan. It names both shortfalls and ranks three relaxed alternates.

      Today as stated: verdict=infeasible, plan returned: False
        shortfall: Total demand is 74 units but the fleet carries 40 (1 vehicle(s) x 40); shortfall 34 units. ...
        shortfall: ferry-kiosk: the earliest possible arrival is 8.79h (depot opens 7h, direct drive 107 min) but its window closes at 7.5h; ...
        alternate #1: relaxes capacity 40.0 -> 74.0
        alternate #2: relaxes n_vehicles 1 -> 3
        alternate #3: relaxes visit_all_customers all stops -> 4 of 7 stops
    
  4. Solve after the suggested changes

    With a second van, and the kiosk moved to tomorrow, the local search sequences the stops against their windows:

      With a second van and the kiosk moved: verdict=feasible
        engine: local search (auto: time windows declared: the local search sequences against them)
        seed 28.8 km -> optimised 23.1 km in a 2 s budget
        vehicle 1: school -> cafe -> market  load 39, 12.8 km
        vehicle 2: clinic -> hotel -> bakery  load 29, 10.3 km
        late stops: 0; not enforced by the search: ['return_to_depot_deadline', 'max_driver_hours']
    

    The hosted run prints the same lines, except Served by: hosted. The engine line names the engine that ran; it is abbreviated on this page.

  5. Solve the clinic's week

    The week needs 84 hours of registered-nurse cover, and the nurses can give 72. shift_rostering.py answers infeasible in both models. No roster is returned as the solution; the ranked alternates name the rule each one breaks.

    [6] Solve the week
      Served by: embedded; engine: rostering local search
      verdict=infeasible, roster returned as the solution: False
        shortfall: Skill 'rn': demanded coverage is 84h but skill-capable employee capacity is 72h (deficit 12h); ...
        hard rule broken: undercovered x1
        alternate #1: breaks employer_coverage x1 (hard)
        alternate #2: breaks employer_coverage x1 (hard)
        alternate #3: breaks employee_max_weekly_hours x1 (hard); employer_coverage x1 (hard)
    

What the solver reaches#

These are measured on published benchmark instances. An independent scorer checks every answer; it shares no code with the solver. Both models were run at the same engine and budget.

InstanceEngine, budgetCostBest known
A-n32-k5 (capacitated)hgs, 3 s784784
X-n115-k10 (capacitated)hgs, 8 s12,74712,747
X-n101-k25 (capacitated)hgs, 30 s30,584 (+10.9%)27,591
Solomon C101, 25 customers (time windows)local_search, 5 s191.8191.3
Solomon R101, 25 customers (time windows)local_search, 5 s618.3617.1
Solomon R101, 100 customers (time windows)local_search, 30 s1,799–1,854 (+10–13%)1,637.7
Solomon C101, 100 customers (time windows)local_search, 30 s1,179–1,194 (+43%)827.3

At 100 customers with time windows, the local search is well short of best-known. It is gated in the weekly quality run so the gap cannot grow unnoticed.

How this page was verified#

The embedded output blocks above were re-run on 2026-09-29 with the released runtime 0.3.0 wheel and helixor-solvers 0.7.0 on Python 3.12, and match line for line apart from the abbreviated engine lines. The hosted output was captured the same day from the same code, pre-release, against a local solver service process, and differed only in Served by.

The benchmark table comes from Helixor's solver quality suite. That suite runs the same requests through both models and fails if hard rules are broken, if a cost drifts past its declared tolerance, or if the two models disagree.