A Python package, with C++17 kernels, for reconstructing one-dimensional point sets from unordered pairwise distances — the Turnpike problem — including uncertain, structured, and incomplete observations.
Given a set of n points on a line, you can read off all m = n(n-1)/2
pairwise distances between them. The Turnpike problem is the inverse: you are
handed that multiset of distances with no indication of which pair produced
which distance, and asked to recover the original points. The related Beltway
problem places the points on a circle.
The same loss of correspondence arises in partial digestion for genomic mapping, molecular structure determination, tandem mass spectrometry, and DNA-based error-correcting codes.
Exact Turnpike has practical reconstruction algorithms, although its worst-case complexity remains open. Bounded-error Turnpike and related noisy variants are strongly NP-hard, and real observations are also commonly rounded, incomplete, or duplicated. The package therefore combines large-scale fixed-point fitting, moderate-size global fitting, and exact or diagnostic certification:
| Approach | Regime | Guarantee |
|---|---|---|
| Streaming coordinate ascent / MM | ~10⁵ points | fixed-point method; no local or global optimality claim |
| Sorting-network MILP | moderate n |
solver-reported global best fit within the supplied span bound |
| Triangle-equality LP/ILP | moderate n |
exact realizability certificate only when integral and post-verified |
Beltway is handled through the missing-distance machinery rather than a
separate code path: in the real-arithmetic model, Beltway on n points reduces
to Turnpike with missing distances on 2n + 1 points, and the partitioned
routines below are exactly what that reduction requires. The reduction itself is
laid out in the MM work.
This is the consolidated implementation behind three papers from the Kingsford group at Carnegie Mellon University's Ray and Stephanie Lane Computational Biology Department, and behind the PhD dissertation of C. S. Elder:
- C. S. Elder, M. Hoang, M. Ferdosi, C. Kingsford. A Scalable Optimization Algorithm for Solving the Beltway and Turnpike Problems with Uncertain Measurements. RECOMB. — the MM solver.
- The extended journal version of the above, in the Journal of Computational Biology — partitioned MM and the sorting-network MILP models.
- C. S. Elder, G. Marçais, C. Kingsford. Turnpike with Uncertain Measurements: Triangle-Equality Integer Programming with a Deterministic Recovery Guarantee. WABI. Full version: arXiv:2603.18283 — the triangle certificates.
The papers give the original formulations; this package is their reference implementation. One distinction is easy to miss in the journal presentation and is made explicit below: the observed-only and missing-row-imputation updates optimize different objectives.
python3 -m pip install .
python3 -m pytest -q # optional: run the test suiteRequires a C++17 compiler, Python ≥ 3.9, NumPy ≥ 1.22, SciPy ≥ 1.9, and PyBind11 ≥ 2.10 (pulled in automatically at build time). The tests exercise the compiled extension against deliberately naive pure-Python oracles.
The mathematical kernels are C++17 bound with PyBind11; validation, solver
calls, and result semantics stay in Python. The private _core module assumes
valid contiguous buffers — the public functions are the checked API, and are
what you should call.
For n points there are m = n(n-1)/2 intervals. Every module uses the same
j-major ordering:
>>> import turnpike as tp
>>> tp.pairs(4)
array([[0, 1], [0, 2], [1, 2], [0, 3], [1, 3], [2, 3]])q(x) computes all x[j] - x[i] in this order; qt(d, n) computes Q.T @ d.
Rank ties are resolved by predicted value, then shorter interval, then smaller
left endpoint.
The scalable full-data solver uses the j-major difference matrix
For a current
This is exact two-block coordinate ascent on
It is also MM on the profiled objective
After sorting d once, the streaming kernel takes O(m log n) time per
iteration and O(n) = O(√m) auxiliary words beyond the stored distances and
outputs. This is the structure behind the ~100,000-point scale.
import numpy as np
import turnpike as tp
d = np.array([1, 2, 3, 4, 5, 6]) # unordered pairwise distances
z0 = np.array([0, 1, 2, 4]) # initial guess
z = tp.mm(d, z0) # centered, sorted, unit MM representative
x = tp.solve(d, z0) # anchored physical least-squares coordinatesSmaller primitives keep the mathematically distinct outputs explicit:
raw(d, z)→u = Q.T @ P(z) @ dstep(d, z)→u / ||u||fit(d, z)→ the centered regressionu / nmm(d, z)→ iterates the normalized assignment–coordinate updatesolve(d, z)→ iterates MM, regresses the final assignment, anchors at zero
In exact arithmetic with deterministic ties, the uncapped map has a finite
assignment image and reaches a fixed point after finitely many iterations. The
floating-point APIs retain tol and max_iter. A fixed point need not be a
local or global optimum, so no reconstruction-optimality guarantee is claimed.
Internally the iteration ranks an unscaled raw representative and keeps a
separate unit vector for stopping and output; this prevents normalization from
splitting exact interval ties.
For distances observed in groups rather than as one undifferentiated multiset,
each observed cell is a multiset D[c] assigned to intervals I[c], specified
by j-major row indices or (i, j) pairs. Cells are disjoint; omitted intervals
are treated as missing.
D = [np.array([1, 1, 7]), np.array([6, 8])]
I = [np.array([0, 1, 4]), np.array([2, 3])] # interval row 5 is missing
z_corr = tp.bmm(D, I, [0, 1, 2, 8])
z_loss = tp.impute(D, I, [0, 1, 2, 8])The implementation supports products of independent exchangeable cells. A fixed assignment is a singleton cell; a general permutation family needs its own exact assignment oracle and is not silently treated as a cell. The two public API pairs share the same cellwise assignment and unweighted coordinate-space PAV machinery, but they optimize different objectives when rows are missing:
bstep(D, I, z)/bmm(D, I, z, tol=..., max_iter=...)perform observed-correlation projected ascent; omitted rows contribute zero.impute_step(D, I, z)/impute(D, I, z, tol=..., max_iter=...)perform imputation MM for profiled observed half squared loss; omitted rows contribute their current predictions to the surrogate only.
For cell
The observed-only step is
Thus bstep is one exact projected coordinate-ascent step for
bmm iterates it. PAV is necessary because
independently co-sorted cells can make bmm map reaches a fixed point finitely; the
floating-point routine uses tol and max_iter. If rows are missing, this
correlation objective is not observed least squares because
For the imputation mode, let
The implementation fills each missing row with its current prediction
This update exactly minimizes the tight global majorizer
Indeed,
with strict decrease when the normalized iterate changes. Missing predictions
are optimization-transfer terms, not observations. Because the surrogate
depends continuously on impute; the guarantee is monotone, bounded profiled-loss
values, not global optimality or convergence of the iterates.
Clarification relative to the journal presentation. Its displayed observed-only objective omits the null block, while its pseudocode fills that block with current predictions. The API exposes both mathematically distinct updates.
bstep/bmmare correlation ascent;impute_step/imputeimplement the pseudocode and are genuine MM for$\rho$ . They therefore guarantee observed-loss descent, not observed-correlation ascent.
If no intervals are missing, impute_step equals bstep, and impute equals bmm, up to floating-point
roundoff. If one exchangeable cell contains every row, PAV is inactive and these
calls reduce to step and mm.
A three-point example shows why the two objectives cannot be conflated. Using
the API's zero-based endpoints, observe singleton distance 1 on (0, 1) and
distance 2 on (0, 2), omit (1, 2), and take
impute_step lowers observed correlation from
approximately 3.703280 to 3.690960 while lowering profiled observed half
squared loss from approximately 0.189577 to 0.188267 — exactly the behavior
promised by the imputation-MM contract, and impossible for a correlation-ascent
step.
Exact global best fit for moderate n.
fit = tp.best_fit(D, I, n=4, u=10, loss="l1")best_fit builds one exact bitonic sorting network per cell and supports l1
and linf residuals. The finite span bound u is required: it is
simultaneously the coordinate-domain bound, the comparator big-M, and the
padding sentinel, and cannot be inferred safely from noisy or partial
observations.
Fit reports status, x, fun, gap, bound, and the solver message. The
default relative MIP gap is zero. "Optimal" remains a floating-point solver
certificate, and any user-supplied nonzero gap stays visible in the result.
These are moderate-size global best-fit models, not the scalable solver.
Squared l2 would be an MIQP and Euclidean l2 a conic model, so neither is
offered here mislabeled as an MILP.
Exact-arithmetic feasibility certificates.
cert = tp.triangle([1, 2, 3])
cert.status # "feasible"
cert.P # interval-to-distinct-value assignment
cert.x # x[0] = 0 spine reconstruction
cert.guaranteed # TrueThe backend constructs a zero-objective feasibility model with P interval
labels and T ordered two-partitions. Exact relations use scaled signed 64-bit
integers with overflow-safe unsigned 64-bit sums. Decimal or rational data
must be scaled to integers first; no hidden tolerance is introduced into an
exact certificate.
model, parts, rank_mask, and verify expose the modular components
without requiring a solver-specific C++ layer.
This distinction is deliberate and load-bearing:
| Mode or returned result | Meaning |
|---|---|
triangle(..., lp=False) |
ILP with spine basis and rank pruning by default |
triangle(..., lp=True) |
LP with the full triangle system by default |
integral, exactly post-verified P |
realizability certificate (guaranteed=True) |
fractional LP P |
relaxation diagnostic — not a nonrealizability certificate |
| infeasible LP/ILP | solver-reported nonrealizability for the supplied relations; no independently checkable infeasibility proof artifact is returned |
guaranteed=True is reserved for an integral assignment that verify re-checks
in exact integer arithmetic. Solver-reported infeasibility and fractional
relaxations are deliberately not given that label.
rel = tp.rounded_relations(d_obs, r=0.02, R=0.01)
crit = tp.critical_radii(d_obs, R=0.01)
diag = tp.robust(d_obs, r=0.02, R=0.01)
cal = tp.calibrate(d_obs, R=0.01)R is the rounding-grid spacing, so its rounding error is at most R/2;
half-grid ties round upward for positive distances. Relations enter when
max(0, |y[r]+y[s]-y[t]|/3 - R/2) <= r, including the critical endpoint.
calibrate binary-searches these nested events for the first feasible
diagnostic model. Robust coordinates are a regression, not an exact spine claim.
robust(...).guaranteed is deliberately False. A theorem-level guarantee also
requires externally known strict separation conditions, and independently
perturbed duplicate copies can split across rounded bins. Treat it as a
relation-recovery and feasibility diagnostic unless those additional assumptions
are established outside this API.
If you use this software, please cite the paper corresponding to the method you used — the MM solver, the partitioned/MILP extensions, or the triangle certificates — from the Provenance section above.
Issues and questions: the Kingsford group, Carnegie Mellon University.