Hands-on: Using Qiskit to Solve a Real-World TMS Routing Subproblem
Practical Qiskit tutorial solving a TMS tendering/dispatch subproblem inspired by Aurora–McLeod—dataset prep, QAOA notebook, and classical comparison.
Hook: Why quantum-assisted TMS routing matters for your team (and why now)
You’re a developer or IT lead trying to integrate autonomous trucking capacity from partners like Aurora into your Transportation Management System (TMS). You face constrained compute budgets, fragmented toolchains, and the need to tender and dispatch loads quickly while respecting capacity and timing constraints. What if a quantum-hybrid approach could give you another heuristic in the toolbox—one that’s easy to prototype in a notebook and compare directly with classical solvers as part of an A/B test?
The landscape in 2026 — short context for practical readers
By early 2026, industry integrations like the Aurora–McLeod connection (which allows tendering of autonomous truck capacity through McLeod’s TMS) have accelerated demand for rapid, reproducible routing and dispatch experiments. Cloud quantum resources and local simulators are more accessible than ever. Hybrid algorithms (QAOA-based heuristics, warm-started variational methods) are production-ready for small-to-medium subproblems and can be trialed as part of an A/B test against classical optimizers.
What you'll get from this hands‑on guide
- A simplified, realistic TMS tendering/dispatch subproblem inspired by the Aurora–McLeod use case
- End-to-end dataset prep and CSVs you can drop into a notebook
- Qiskit code to formulate the problem as a QuadraticProgram and solve it with QAOA (simulator / runtime)
- Clear comparison with classical solvers (PuLP brute force / OR-Tools-style approaches) and reproducible benchmarks
- Practical notes on scaling, hybrid strategies, and next steps for integration
1) Problem definition — a focused tendering/dispatch subproblem
We simplify the real-world problem to an assignment / tendering subproblem that is common in TMS workflows: assign loads to available trucks (including Aurora autonomous trucks) to maximize profit or minimize cost while respecting capacity and scheduling constraints.
Variables: x_{i,j} = 1 if load i is assigned to truck j; 0 otherwise. Objective: minimize total cost (or maximize profit) across assignments. Constraints: each load is assigned at most once; truck capacity not exceeded; optional time-window feasibility.
Why this subproblem?
It maps directly to the tendering step exposed by McLeod–Aurora: the TMS needs to decide which loads to offer to autonomous capacity (or other carriers) and how to dispatch chosen loads. The model is small enough to run on quantum simulators and instructive for hybrid experiments.
2) Dataset: Creating a reproducible sample (CSV-ready)
We’ll generate a small synthetic dataset that mirrors TMS fields: loads, weights, revenue, pickup/delivery windows; trucks, capacities, base costs. Drop this into a notebook as loads.csv and trucks.csv.
# dataset_prep.py
import pandas as pd
import numpy as np
np.random.seed(42)
# Create 6 loads
loads = pd.DataFrame({
'load_id': [f'L{i}' for i in range(6)],
'weight': np.random.randint(5, 30, size=6), # tons
'pickup_time': np.random.randint(8, 14, size=6), # hours
'delivery_time': np.random.randint(12, 22, size=6),
'revenue': np.random.randint(500, 2500, size=6)
})
# Create 3 trucks (including one Aurora autonomous truck)
trucks = pd.DataFrame({
'truck_id': ['T1', 'Aurora_T2', 'T3'],
'capacity': [40, 30, 50],
'available_from': [7, 8, 6],
'cost_per_mile': [1.2, 1.0, 1.5]
})
loads.to_csv('loads.csv', index=False)
trucks.to_csv('trucks.csv', index=False)
print('Saved loads.csv and trucks.csv')
These files are intentionally small so you can run full enumerations and QAOA experiments in a notebook quickly. You can scale to larger datasets by converting the same construction to batches (tender batches are typical in real TMSs).
3) From CSV to QuadraticProgram (QUBO) in Qiskit
The natural linear formulation is easily turned into a QUBO by adding squared penalty terms for constraints. We use Qiskit's QuadraticProgram to define binary variables x_i_j and build an objective that includes penalties for constraints. Then we solve with QAOA via the MinimumEigenOptimizer.
Key ideas
- Objective: minimize total operational cost = sum_j sum_i x_ij * cost_{i,j} - sum_i assigned_revenue (equivalently maximize profit)
- Constraints enforced via penalty terms: each load assigned at most once; truck capacity limits.
- Use small penalty weights (tuned) to keep problem well-conditioned for QAOA.
# qiskit_dispatch.py
import pandas as pd
import numpy as np
from qiskit_optimization import QuadraticProgram
from qiskit_optimization.algorithms import MinimumEigenOptimizer
from qiskit.algorithms import QAOA
from qiskit.utils import algorithm_globals, QuantumInstance
from qiskit import Aer
# Load data
loads = pd.read_csv('loads.csv')
trucks = pd.read_csv('trucks.csv')
# Build variables list
vars = []
for i, load in loads.iterrows():
for j, truck in trucks.iterrows():
vars.append((load['load_id'], truck['truck_id']))
qp = QuadraticProgram()
for (l, t) in vars:
qp.binary_var(name=f'x_{l}_{t}')
# Build linear cost coefficients (negative revenue + operational cost estimate)
# Simple cost: cost_per_mile * (weight/10) as proxy for miles
lin_cost = {}
for (l, t) in vars:
load = loads[loads['load_id'] == l].iloc[0]
truck = trucks[trucks['truck_id'] == t].iloc[0]
op_cost = truck['cost_per_mile'] * (load['weight'] / 10.0)
profit = load['revenue']
# Minimize cost -> cost - profit (so assigning a load reduces objective if profitable)
lin_cost[f'x_{l}_{t}'] = op_cost - profit
for var_name, coef in lin_cost.items():
qp.minimize(linear={var_name: coef}) if qp.objective is None else qp.objective.set_linear(var_name, coef)
# Add penalty for "each load assigned at most once"
penalty = 1000.0
for l in loads['load_id']:
expr = {f'x_{l}_{t}': 1 for t in trucks['truck_id']}
qp.linear_constraint(linear=expr, sense='LE', rhs=1, name=f'assign_once_{l}')
# Add capacity constraints per truck
for t in trucks['truck_id']:
expr = {}
for l in loads['load_id']:
w = int(loads[loads['load_id'] == l]['weight'])
expr[f'x_{l}_{t}'] = w
cap = int(trucks[trucks['truck_id'] == t]['capacity'])
qp.linear_constraint(linear=expr, sense='LE', rhs=cap, name=f'cap_{t}')
# Convert to QUBO via penalty method
# Qiskit can directly handle linear constraints with some optimizers but we want a QUBO-style experiment.
# For clarity, we convert constraints manually here by adding quadratic penalties.
# Simple conversion: add sum((Ax - b)^2) for each equality/inequality converted to soft penalty
# (I keep this explanatory — in practice use Qiskit's converters.)
# NOTE: For brevity we run QAOA on the QuadraticProgram using MinimumEigenOptimizer
backend = Aer.get_backend('aer_simulator')
algorithm_globals.random_seed = 123
quantum_instance = QuantumInstance(backend=backend, shots=1024, seed_simulator=123, seed_transpiler=123)
qaoa = QAOA(reps=1, quantum_instance=quantum_instance)
meo = MinimumEigenOptimizer(qaoa)
result = meo.solve(qp)
print(result)
The snippet above intentionally uses a compact form and should be expanded in your notebook to display assignments and compute objective values. In production you'd use Qiskit's ConstraintConverter utilities to convert linear constraints to penalized quadratic forms—this keeps the problem well-formed for MinimumEigenOptimizer.
4) Classical baselines: pulp brute force and local search
To assess whether QAOA gives useful solutions for tendering, you need classical baselines. For the small problem sizes we're using, brute-force enumeration is viable. For larger batches, use PuLP or OR-Tools.
# classical_baselines.py
import pandas as pd
import itertools
from math import inf
loads = pd.read_csv('loads.csv')
trucks = pd.read_csv('trucks.csv')
load_ids = list(loads['load_id'])
truck_ids = list(trucks['truck_id'])
best_obj = inf
best_assign = None
# Enumerate assignments: for each load choose one truck or 'unassigned' (None)
choices = truck_ids + [None]
for assignment in itertools.product(choices, repeat=len(load_ids)):
# Build assignment mapping
assign_map = dict(zip(load_ids, assignment))
# Capacity check
capacities = {t: 0 for t in truck_ids}
feas = True
cost = 0
for l, t in assign_map.items():
if t is None:
continue
w = int(loads[loads['load_id'] == l]['weight'])
capacities[t] += w
cap = int(trucks[trucks['truck_id'] == t]['capacity'])
if capacities[t] > cap:
feas = False
break
truck = trucks[trucks['truck_id'] == t].iloc[0]
op_cost = truck['cost_per_mile'] * (w / 10.0)
revenue = int(loads[loads['load_id'] == l]['revenue'])
cost += op_cost - revenue
if not feas:
continue
if cost < best_obj:
best_obj = cost
best_assign = assign_map
print('Brute force best objective:', best_obj)
print('Assignment:', best_assign)
5) Running the notebook: reproducible benchmarking steps
- Run dataset_prep.py to generate CSVs.
- Open a Jupyter or Colab notebook and run the Qiskit and classical scripts shown above.
- Record objective value, wall-clock time, and sample counts for QAOA (shots, reps).
- Compare the QAOA solution to the brute-force / pulp solution (use matching objective and feasibility checks).
- Repeat with different random seeds and penalty weights; report mean and variance.
6) Expected outcomes and interpretation (realistic in 2026)
For the small instance above you should expect:
- Classical brute force will find an exact optimum quickly (seconds for our small sample).
- QAOA on a simulator may find the optimal or near-optimal assignment depending on reps, shots, and parameter initialization. With low-depth reps (1–2) it's an approximation; reps>=2 often improves quality but increases runtime.
- Noise and sampling will introduce variance — always run multiple seeds and report top-k solutions.
Practical benchmarking metrics
- Objective value gap (%) relative to classical optimum
- Time-to-first-feasible-solution
- Wall-clock runtime and quantum runtime separately (if using hardware)
- Reproducibility: variance across seeds
7) Advanced strategies and real-world scaling
A few tactics used in industry experiments in late 2025–early 2026 have matured and are actionable now:
- Decompose by tender batch: split large carriers' tender lists into batches of 8–12 loads, run hybrid optimizers on each batch, and stitch results via a classical meta-optimizer.
- Warm-start QAOA: initialize QAOA parameters from a classical heuristic (greedy or LP relaxation). This reduces search time and improves consistent solution quality.
- Use quantum-inspired solvers: Digital annealers and tensor-network heuristics often provide better-than-random starting points for quantum runs.
- Leverage Qiskit Runtime: For production experimentation, run QAOA on IBM's Runtime primitives (reduced latency); integrate as a microservice for A/B testing dispatch decisions.
8) Case study: Aurora–McLeod motivated experiment
Imagine a McLeod user with an Aurora subscription wanting to tender a batch of loads for autonomous capacity. The TMS API can return candidate autonomous trucks with availability and cost. Use our notebook to test: for each tender batch, run a 12-load QAOA experiment and compare to a classical greedy tendering policy. In practice teams observed in 2025 proofs-of-concept that hybrid runs produced assignments that were as good or marginally better than greedy heuristics on profit metrics while providing additional solution diversity for operations to choose from.
"The ability to tender autonomous loads through our existing McLeod dashboard has been a meaningful operational improvement." — Rami Abdeljaber, Russell Transport (paraphrased)
9) Pitfalls, tuning tips, and reproducibility checklist
- Penalty tuning: too large penalties can overshadow objective structure; too small penalties allow infeasible solutions. Start with penalties an order of magnitude larger than absolute objective coefficients and adjust.
- Scalability: QAOA depth and problem size both increase circuit depth and noise. Keep batches small for hardware runs or use simulators for larger depths.
- Seed everything: call algorithm_globals.random_seed and fix simulator seeds. Log shots, transpiler seeds, and hardware backend versions for audits.
- Compare apples-to-apples: ensure classical solvers use identical cost models and constraints before comparing objective values.
10) Next steps and integration advice
If you’re evaluating quantum/hybrid tooling for your TMS workflows, follow this pragmatic path:
- Build a suite of small tendering benchmarks (8–16 loads) that reflect real mixes from your McLeod/Aurora integration logs.
- Automate dataset generation from real events (obfuscate sensitive fields) for reproducible A/B experiments.
- Run the QAOA notebook on simulators and Qiskit Runtime; collect objective values, runtimes, and variance.
- Use warm-starts and hybrid heuristics; if quantum solutions consistently improve the dispatch KPIs (fuel, empty miles, profit), pilot a dispatch advisory flow to operations staff.
Actionable takeaways
- Start with small, repeatable problem batches and CSVs — your aim is a reproducible experimental pipeline, not immediate quantum advantage.
- Use Qiskit’s QuadraticProgram + MinimumEigenOptimizer (QAOA) to prototype rapidly; compare against PuLP brute force for ground truth.
- Tune penalties, warm-start QAOA with classical heuristics, and log everything — seed values, backend, and versions — for reproducibility.
- Integrate quantum runs as an advisory microservice behind your TMS UI; email or UI recommendations let operations retain control while you evaluate value.
Final thoughts — why this matters in 2026
The Aurora–McLeod integration is an example of how TMS platforms are being extended to include novel capacity sources. That creates opportunities for new dispatching heuristics to improve cost and utilization. Quantum-hybrid methods are not a silver bullet yet, but in 2026 they are a practical part of the optimizer toolbox: useful for research, A/B testing, and generating diverse candidate schedules that classical heuristics may miss.
Call to action
Ready to try this in your environment? Download the complete Jupyter notebook and example CSVs from our repo, run the Qiskit notebook on an Aer simulator or Qiskit Runtime, and compare the results with your current TMS tendering policy. If you want an enterprise pilot—where we help map your real McLeod/Aurora logs to batch experiments and build a hybrid optimizer pipeline—contact the qbitshared team for a technical evaluation.
Get the notebook: clone our example repo, run dataset_prep.py, and use the Qiskit scripts above to get started. Experiment, iterate, and report back — we’ll publish interesting benchmarks and recipes for scaling to 20+ loads per batch.
Related Reading
- The Evolution of Cloud VPS in 2026: Micro‑Edge Instances for Latency‑Sensitive Apps
- How Startups Cut Costs and Grew Engagement with Bitbox.Cloud in 2026 — A Case Study
- Edge‑First Layouts in 2026: Shipping Pixel‑Accurate Experiences with Less Bandwidth
- Observability‑First Risk Lakehouse: Cost‑Aware Query Governance & Real‑Time Visualizations for Insurers (2026)
- Rechargeable vs Microwavable Pet Warmers: Which is Better for Your Household?
- How to Build a Low-Cost Smart Home Starter Kit (Router, Lamp, Charger)
- Platform Policy Shifts and Airline Customer Service: Preparing for Moderation Backlash and Unexpected Content Issues
- Amiibo Compatibility Cheatsheet: Which Figures Unlock What in New Horizons
- Score a Smart Lamp for Less: Why Govee’s RGBIC Discount Is a Better Bargain Than a Standard Lamp
Related Topics
qbitshared
Contributor
Senior editor and content strategist. Writing about technology, design, and the future of digital media. Follow along for deep dives into the industry's moving parts.
Up Next
More stories handpicked for you