Skip to content
Jip Claassens edited this page Sep 15, 2026 · 1 revision

FAQ

In one sentence: the questions a colleague asks after months away from the pharmacy model, each answered in a few lines with a link to the page that owns the full story — read this when you need one answer now, follow the link when you need the why.

Where this sits: across the whole pipeline (GeoDMS → Arrow → Julia LP → λ-sweep → rounding → scenarios → deck). Every answer is a compressed pointer into The pipeline in plain words, the method pages and the how-to pages, as of September 2026 (commit e25416b; branch state on Branches data and environment; permalinks pinned to https://github.com/ObjectVision/NetworkModel_EU/blob/e25416b/). Terms are defined in the Glossary.

Concepts

What is w, what is λ, and why does the €100 000 not matter?

λ (lambda) is the price the model charges per open pharmacy cell; the LP (linear programme, an optimisation that allows fractional answers) minimises travel + λ × open cells. w is the knob the sweep turns: λ = w * FACILITY_MIN_COSTS (100 000) in lp_run.jl → solve_at_w!, so w = 0.2 prints as €20 000. The € is a label inherited from the school-era €99 699 and never calibrated (b3c634b); λ's real unit is person-minutes per facility under LINEAR, and changing the constant re-labels λ without moving the frontier or S1/S2. Owner: The lambda sweep and the Pareto frontier.

Who is a client, what is a candidate, and why is everything a cell?

Both live on the 1 km² population grid: a client is a populated cell weighted by its residents; a candidate is a cell the LP may open — every cell with ≥ 50 residents plus every cell holding a pharmacy today (MinPopSizeForPharmacyLocation := 50, agreed 24 June 2026: ~98 % of pharmacy locations kept in ~2.9 % of EU cells; 7c6768f). Why the 1 km cell is the facility unit is not recorded (b4bc44a). Owner: GeoDMS OD matrix and choice set.

What are the frontier, S1, S2 and S3?

Plot every possible network with x = open cells and y = total population travel cost; the lower-left outline of the achievable points is the Pareto frontier — no network has both fewer pharmacies and less travel than a frontier point. Today's network ★ sits above it. S1 is the frontier point straight below ★ (same count, least travel — what relocation alone saves); S2 the point straight left of it (same travel, fewest locations — how many are surplus); S3 is the curve itself.

The frontier, ★, S1, S2, the improvement rectangle and a concave dent no λ can reach

Owner: Scenarios S1 S2 S3; formal statement in Lambda sweep method §4.

Why can the LP beat the integer optimum?

Because it may open half a pharmacy: on a ring of three clients and three candidates it opens y = (½, ½, ½), splits each client between its two nearest sites and beats every whole-number answer (6 < 7). The relaxation minimises over a larger set that still contains every integer answer, so its value is a certified lower bound — and sum_y (Σ y*, the fractional count of open cells) can read 1.5. Worked triangle on The facility location model explained; theory in Background theory §3.

What is frac_y, what does a fractional y mean physically, and why do old logs say frac_x?

frac_y (code n_frac) counts candidates with 1e-6 < y* < 1 − 1e-6: cells the LP left part-open. A few dozen out of tens of thousands (29 793 candidates in the May 2026 Netherlands log) means the relaxation is almost a real network; hundreds or thousands (high-λ tails, LOGISTIC generally) mean clients with two equally good options. Before 4cee238 (24 July 2026) the code used x for openness and y for assignment, so old logs, issue #47, Service allocation procedure and the commit message "Sum y<=1" say sum_x/frac_x; doc/build_deck_data.py accepts both. Owner: The facility location model explained (notation table); Log lines and output files ("If you open a pre-July log").

Why soft coverage, and what is BIG?

Under hard coverage several areas' full-coverage floor lay above today's count (the Netherlands: 1 899 versus 1 615 existing cells), so S1 could not be found. Soft coverage (Σ x ≤ 1; agreed 10 July, committed 16 July 2026, 4ec47a9) lets a client go unserved at a flat price BIG per resident: big_cost() in settings.jl = c(120 min) = 120 for LINEAR, 1.0 for LOGISTIC, the same everywhere because the earlier per-area worst trip (42 min in Paris, 120 in Norway) broke the cross-area aggregate. Owner: The facility location model explained (soft coverage); Travel cost functions (BIG per function and its history).

Why is the baseline priced at BIG too, and what are "the scope rules"?

★ must be a point of the same problem as the frontier, so baseline_metrics charges BIG for every client with candidate-side OD rows but no existing-side row; before 206923c (6 July 2026) such clients were dropped, which put Denmark's and ITG's baselines below their own frontier. A data gap therefore shows up as a huge BIG bill, hence three scope rules: the country coverage filter (ModelClient in Analyses.dms), the per-landbody network rule (next question) and the NUTS region exclusion — drop a region when more than 50 % of its inhabited cells reach no existing pharmacy (872b388, issue #49; the 50 % is "as agreed", nothing more). Today only the Azores and Madeira are excluded; Portugal's baseline travel fell 62 %. Owner: Scope rules and data gaps.

What is a landbody, and what did the landbody fix change?

A landbody is one polygon of a country's multipolygon — mainland, Sicilia, each island. The old rule kept the single largest strongly-connected road component of the whole extract, so an island without a modelled ferry link lost its network and its residents were priced at BIG — 983 169 people in ITG, 84.6 % of its baseline cost. Since 79cb58b (Chris, 31 August 2026) Roads_isConnected keeps the largest component per landbody; no data changed, ITG's baseline mean fell from 22.30 to 4.88 min. Owner: Scope rules and data gaps; configuration in GeoDMS OD matrix and choice set.

What is the choice set, and why is it a radius?

GeoDMS grows a shortest-path tree from each client and stops at five cells holding a pharmacy today (limit(...) in impedance_matrix_od64) or 120 minutes (cut(...)). The radius is fixed by today's five nearest and does not grow when the optimiser closes some, so at S2 a client whose five close is priced at BIG — the frontier is under-estimated (SE2: 10.2 % of residents one closure from stranding). Five was a school-era compute choice (issue #22); why 120 is not recorded; widening to 10 is the first roadmap item. Owner: GeoDMS OD matrix and choice set; where it binds on Scope rules and data gaps.

Why LINEAR and LOGISTIC — why run two travel-cost functions?

c(t) prices one resident's trip of t minutes. LINEAR (c = t) says a minute is a minute: communicable, but a few remote residents can pull a location towards them. LOGISTIC (c = 1/(1+e^{−(t−25)/10})) is the equity weighting the JRC side asked for — indifferent to short trips, concentrated on moving people from ~45 to ~15 minutes; retuned from 30/15 on 2 July 2026 to match Lewis's ~5- and ~45-minute kinks. The direction of the results survives both (aggregate S1 −26 % travel LINEAR, −13 % LOGISTIC) while λ differs a hundredfold, so never compare costs or λ across the two. Owner: Travel cost functions.

Why is the LOGISTIC λ-range compressed?

A LOGISTIC trip costs at most 1 per resident against up to 120 under LINEAR, so a location pays for itself at a λ about a hundred times lower: the aggregate's S1 sits at w = 0.130 under LINEAR and 0.00130 under LOGISTIC. Hence SWEEP_WMAX defaults to 0.5 for LOGISTIC and 5.0 for LINEAR, and the Netherlands' whole S1–S2 stretch falls inside one grid step — the "degeneracy" reviewer Martijn Brons noted. Bernhard Nöbauer's proposal to rescale the logistic so c(0) and c(60) match the linear curve is an open group decision (deck p63–64). Owner: Travel cost functions.

What is the certified gap, and why is the deck's "multi vs relax" not it?

At one λ the LP total is a floor no real network can beat and the multistart set is a real network, so travel_relax + λ·sum_y ≤ integer optimum ≤ travel_multi + λ·n_open: that sandwich on total cost is the certified gap. The deck's "multi vs relax" compares travel alone; since n_open = round(sum_y) the facility terms differ by up to λ/2, and the travel-only ratio can even be negative (the Netherlands LINEAR w = 0.02 row).

LP lower bound, multistart upper bound and the certified gap between them along λ

Owner: From LP fractions to real pharmacies; Background theory §8.

Why warm start, and why is the sweep sequential?

solve_at_w! changes only the y coefficients of an LP built once per area (the cost of serving a client never depends on λ), so the previous optimal basis stays feasible and HiGHS repairs it by dual simplex in a few pivots: 45 876 iterations cold, 1 369 for the next grid point. Each solve must follow the previous one, hence one sequential Julia process per area; parallelism is workers, several run_resweep_batch.ps1 consoles each on its own area. A time-out poisons every later warm start, which is why the tail stops at the first failure (issue #52). Owner: The lambda sweep and the Pareto frontier; Background theory §6.

Why can no λ reach some counts (the "concave dent")?

Sweeping λ never asks for exactly p pharmacies; it prices them, and a sloping line dropped onto the frontier rests only on its lower convex envelope: a count whose point sits above the chord between its neighbours is never touched — the count jumps past it at one λ (the grey point in the figure above). Hence S1/S2 are bracketed between two grid points and bisected. Dents are shallow in practice: S1 landed within 1 % or one facility of today's count in 87 of 88 sweeps, median error 0.13 % (issue #52). Toy with the arithmetic on The lambda sweep and the Pareto frontier; proof sketch in Background theory §5.

Why are counts in cells rather than pharmacies?

The Existing side is one row per cell holding at least one pharmacy (uq_cells). The Netherlands has 1 992 pharmacies in 1 617 cells, of which 1 615 are road-nearest for somebody — the facilities used (cells) line and the S1 target; a cell nobody is nearest to ("empty") is already closed as far as the scoring goes. The raw 1 992 appears only as n-raw, hard-coded for the Netherlands alone; "locations" in the deck means cells. Owner: Scenarios S1 S2 S3; "empty" pharmacies on Metrics aggregation and ranking.

Reading results

What does one log line mean?

The Netherlands LINEAR row at w = 0.2 (print_sweep_header / print_sweep_row, lambda_sweep_simplex.jl around lines 210 and 221):

w      λ (€)     sum_y     travel_relax  travel_topp   travel_greedy  travel_multi  n_open  fac_€      mean_t  frac_y  n-cells  n-raw
0.2    20000.0   1899.83   4.1587045e7   4.2340372e7   4.1596596e7    4.1590157e7   1900    1.89983e8  2.3822  57      285      -92

At €20 000 per location the LP wants 1 899.83 cells' worth of openness and says travel cannot go below 41.59 M person-minutes; the multistart rounding opens 1 900 real cells at +0.007 % above that bound — essentially integral, as the 57 fractional candidates confirm. n-cells = +285 over today's 1 615, so S1 lies at a higher price. Owner: Log lines and output files (every column with units).

What do "coverage-consistent" and "coverage-honest" mean, and is mean_t comparable between the baseline block and the sweep rows?

Both say that a client with no reachable pharmacy is charged BIG per resident rather than dropped: coverage-consistent is the baseline's word (baseline_metrics, 206923c), coverage-honest the rounding's (comment above travel_of in lp_run.jl, e1c426b; before June 2026 the rounding metric dropped stranded clients, hiding that top-p abandoned hundreds of rural cells). mean_t is neither: the baseline's mean charges every stranded resident 120 minutes, a sweep row's counts assigned clients only over the whole population (lp_run.jl line 551), so the row is flattered wherever stranding matters — compare the costs, which carry BIG on both sides. Owner: The facility location model explained; the asymmetry on Log lines and output files (units table).

Which S1 is which — the table, the chart marker, the file — and why was S1 = S2 in five areas?

Three constructions: (a) the snapped row — printed scenario summary, scen in deck_data.json, region tables, exported location files — a real solve; (b) chord interpolation between the two bracketing rows — the deck's λ tables and the chart markers ◆ ■; (c) doc/frontier_metrics.py's chord on the Pareto envelope. Snapping is why, before 10 September, one grid row was nearest on both count and travel in FRC, FRH, FRJ, FRK and SE2; since 29fcf7f (10 September, issue #52) each scenario is bisected inside the sweep, so the snapped row is a pinned one (the deck_data.json in this checkout is still pre-#52). The aggregate table snaps to the nearest union row, hence S1 at 43 357 for a baseline of 43 320. Owner: Scenarios S1 S2 S3 ("Which artefact uses which definition"); Metrics aggregation and ranking.

What does "pinned" mean, and can I trust it?

bisect! re-solves at the midpoint in log w (√(lo·hi); eight halvings of a ×2.5 bracket land within ×1.004) until S1 is within 0.2 % of today's count (or one location if that is more) or S2 within 0.2 % of today's travel (REFINE_TOL = 0.002, not recorded as a reasoned choice), or REFINE_ITERS = 8 solves are spent. It prints S1 pinned at w=…: sum_y=… (target …) either way, so always read the residual: Denmark LOGISTIC once reported S2 "pinned" 9.5 % off after a fallback bisection across a failed point (issue #52). Owner: Scenarios S1 S2 S3; the code on Code walkthrough lambda_sweep_simplex.

How do I read the frontier chart and the λ-axis chart?

Frontier chart (e.g. images/Netherlands_LINEAR.png): x = sum_y, left y = total travel cost. Solid blue = travel_multi, the rounded network — the upper bound and "the frontier"; dashed grey = travel_relax, the LP lower bound (where blue rises above grey the rounding is losing something); amber dashed, right axis = log₁₀ λ in €. ★ today; ◆ S1 straight below; ■ S2 where blue crosses today's travel — both interpolated, not snapped. The λ-axis chart (deck p65–66) reads the same sweep along λ: today's travel is the S2 level, today's count the S1 level. Owner: The lambda sweep and the Pareto frontier ("How to read the two charts"); Deck and charts pipeline.

What is the improvement rectangle, and why rect_rel?

The box spanned by ★, S1 and S2: its width is the locations you could close at equal travel, its height the travel you could save at equal count. rect_rel = (B_x − S2_x)/B_x · (B_y − S1_y)/B_y divides both by today's values because the raw area "mostly ranks by region size" (2a6ea08); the division also cancels the € placeholder. The diagonal from ★ to the far corner hits the frontier at the "balanced improvement" point, and lambda_cross is the price at which the sweep would have picked it; read on chords between solved points, all three slightly under-state the saving. Netherlands LINEAR: 0.274 × 0.199 = 0.055. Owner: Metrics aggregation and ranking.

Why is "southern Italy has the most to gain" gone?

It was missing data. The 10 September results page ranks ITF 0.268 and ITG 0.261 on rect_rel with Italy on the ESPON 2021 shapefile of 12 991 pharmacies; the OECD-geolocated Ministry of Health list delivered on 7 September has 20 635. On the regenerated results (issue #53, commit 216ac77, not on GitHub) ITG and ITF fall to 0.073 and 0.071 while SE1, Norway, Lithuania and SE2 lead. Never quote a ranking without saying which pharmacy list and which scope rules it was made on. Owner: Branches data and environment; Scope rules and data gaps.

How many study areas are in the aggregate — 41, 42, 43 or 44 — and why does its LOGISTIC range stop at w = 0.02?

Each is right for a different artefact: 42 swept at e25416b (14 countries + 28 NUTS-1 regions) and in doc/deck_data.json; 41 in the aggregate, because country-level Poland overlaps its seven NUTS-1 parts and is dropped; 44 names in build_deck_data.py's ORDER, Hungary and Finland added ahead of their sweeps; 43 disjoint areas in issue #53's regenerated aggregate. The aggregate is summed only over the w-range every area solved (aggregate_fd in doc/build_charts.py): [1e-4, 0.5] under LINEAR but [1e-4, 0.02] under LOGISTIC, because Denmark's LOGISTIC sweep stops at the 9–16 July 2026 w_max of 0.02 (f93aea1; raised to 0.5 in 4ec47a9), so the points 0.05–0.5 solved everywhere else are discarded until Denmark is re-swept. Owner: Metrics aggregation and ranking ("Counting the areas").

Running

Why does TRAVEL_FUNC default to QUADRATIC, and what happens if I forget to set it?

settings.jl line 44: travel_func = parse_func("TRAVEL_FUNC", "QUADRATIC") — the school-era placeholder (issue #27) nobody changed when LINEAR and LOGISTIC became the production pair; no commit records why (inertia — inference). A bare julia lambda_sweep_simplex.jl therefore sweeps COUNTRIES = France Italy Netherlands Sweden with a cost function nothing downstream parses. Every runner sets TRAVEL_FUNC and COUNTRIES; do the same by hand. Owner: Travel cost functions; Running a sweep end to end.

How long does a sweep take, and does --threads help?

The Julia sweep is the whole bill (GeoDMS network + OD takes 9 s to 174 s per area). Full sweeps of both functions in July 2026 took ~81.5 h sequentially for 42 areas — country-level Poland 32.3 h, ITF 7.5 h. With the September tail stop the Italian areas took 1.5–3 h instead of 9–14 h; a refine-only pass 5 min (Luxembourg) to 4 h (Austria) per area and function under six-way contention (#52). --threads=N is inert — the driver spawns no tasks and sets no HiGHS threads option (inference, unmeasured); parallelism is workers, and RAM per big area, not cores, sets how many. Owner: Running a sweep end to end ("How long it takes").

How do I add a country, and why is Iceland not one?

Follow the Hungary/Finland precedent (issues #53, #44): a <iso3>_pharmacies.parquet under <NetworkModelDataDir>/Locations/pharmacies/, an entry in the Country enum of cfg/main/SourceData/Locations.dms, the name in every per-area list (Recipe A names them; COUNTRY_SET in doc/build_deck.mjs was missed for Hungary and Finland at e25416b); then rebuild, descriptives, sweep, deck, export. Iceland (isl) is in the enum but has no study area: it lies outside the Ardeco 2021 population grid, so it has no clients and no pharmacy OD, and a GeoDMS run on it hangs rather than errors (doc/todo.md D10; dropped from every batch on 5 July 2026, d0ae5ef; issue #44). Owner: Running a sweep end to end (Recipe A; the fix table); Branches data and environment §3.

I changed the Julia code — full re-sweep or refine-only?

Anything that changes the frontier — settings.jl, lp_run.jl, the grid — needs a full sweep (run_resweep_batch.ps1 -Areas a,b,c -Tag q1). If only where S1/S2 sit on an unchanged frontier matters (the #52 case), refine-only (-RefineOnly) walks the grid LP-only while the count is more than 1.5× today's, bisects each bracket as it closes and stops before the tail. The trap is issue #54: refine points join the old frontier, honest only if the OD and candidate set are unchanged. Owner: Running a sweep end to end (Recipes B, C); the light walk on Code walkthrough lambda_sweep_simplex.

How do I know a run succeeded?

Not from the exit code: the driver catches per-area exceptions, prints <area> — failed: … and exits 0. Grep the log for S1 pinned at w=… and S2 pinned at w=… with a small (target …) residual, then <Area> — scenario summary (what -SkipComplete tests for). Bad signs: FAILED … termination_status=TIME_LIMIT inside a bracket, Neither S1 … nor S2 … bracketed, post-hoc bisection. If the deck parser finds no rows, the log was most likely written by PowerShell's own > as UTF-16 (inference; the runners redirect through cmd /c for this reason). Owner: Running a sweep end to end.

Can I reproduce anything from a fresh checkout?

Only the deck: doc/deck_data.json, agg_data.json, the metrics CSVs and the descriptives are committed, so steps 2 onward of doc/README_deck.md re-render the 10 September deck (built on the 3 September data). Nothing pharmacy-related can be solved: logs/, the Arrow exports and the S1/S2 folders live under C:\LocalData\networkmodel_eu\ on Maarten's machine; rebuilding an OD needs the TomTom/population/NUTS/pharmacy share and GeoDMS 20.19.1.m; there is no Project.toml, so Julia package versions are unpinned. Owner: Branches data and environment §5.

How do I refresh the wiki images after a re-sweep?

No script does it: rerun the deck pipeline (doc/README_deck.md, steps 2 onward), copy the ten PNGs from doc/charts/ (git-ignored) to doc/img/ (committed; last touched 1ed57f2, 10 September 2026), commit, copy them to the wiki's images/, commit and push the wiki. Because the copy is manual the two drift: at wiki HEAD 1d16cc7 the two λ-axis images are older renders than doc/img/. The four images/explainer_*.png figures come from scripts/make_figures.py in the wiki repository, not from the deck. Owner: Branches data and environment §7; Deck and charts pipeline (step 7).

How do I look at S1/S2 on a map in GeoDMS?

Set STUDY_AREA in the shell before launching the GUI (unset, it falls back to StudyArea_default := 'ITG') and make sure the area's lambda_sweep/<FUNC>/S1, /S2 and …/baseline folders exist on that machine. Then open /Analyses/AllocateClientsToNewPharmacies/SweepResults/lin_S1/OpenFacilities (or log_S1, lin_S2, log_S2), …/ClientTravelTime_Classified for binned minutes, …/dT_lin_S1 for the change against today. SweepResults/baseline errors on any area without a June QUADRATIC folder: Analyses.dms line 381 still reads lambda_sweep/QUADRATIC/baseline. Owner: GeoDMS OD matrix and choice set ("Looking at S1/S2 on a map").

Code

Where is the LP built, where does λ enter, and why does HiGHS print a large negative objective?

lp_run.jl → build_lp_warmstart (around line 397): one x[k] per OD row, one y[j] per candidate, Σ x ≤ 1 per client, x[k] ≤ y[j] per row. Leaving everybody unserved costs BIG × total population whatever the answer, so that constant is omitted and each x gets the coefficient (c(t) − BIG)·pop ≤ 0; HiGHS's Objective value is therefore travel_relax + λ·sum_y − BIG·Σpop, negative under LINEAR — read the sweep row, never the solver line. λ enters only in solve_at_w! (around line 464); open it first. Owner: Code walkthrough lp_run; the trick on The facility location model explained ("The constant drops out"); Toyland on The pipeline in plain words.

What is PROTECT_BASELINE, why did FRI's S2 once exceed today's count, and which of the four loaders applies it?

LOCATION_SELECTION_FACTOR = K keeps every K-th candidate so a huge area fits a solve (afef439, 27 May 2026; the Existing side is never subsampled). The blind stride also dropped most of today's pharmacy cells — FRI at K = 3 kept 555 of 1 590 — so FRI's S2 landed at 1 772 locations, more than the 1 584 cells in use today. PROTECT_BASELINE=1 (default since f167da2, 21 July 2026) keeps every candidate coinciding with an existing pharmacy cell and strides only the rest: FRI 17 141 → 6 765 candidates with 1 577 of 1 590 baseline cells kept, S2 1 772 → 1 288. Only lambda_sweep_simplex.jl → load_from is on the production path and applies both the guard and the NUTS region exclusion; the other three loaders each apply less, the source of three bugs (f6edc57, d834a7c, the cap ladder without exclusion). Owner: Scope rules and data gaps ("Candidate subsampling and PROTECT_BASELINE"); Code walkthrough settings ("The four loaders").

Which environment variables do the runners set, and which do I inherit?

The .ps1 workers set COUNTRIES=<area>, TRAVEL_FUNC=<fn>, ROUNDING=multistart, MAX_PARALLEL=<Threads> (inert for this driver) and, with -RefineOnly, SWEEP_STOP_AFTER_REFINE=1. They neither set nor clear SOLVER=simplex, LP_TIME_LIMIT=3600, SWEEP_WMAX=5.0 (LINEAR) / 0.5 (LOGISTIC), SWEEP_MULTS=1,2,5, LOCATION_SELECTION_FACTOR=1, REFINE_TOL=0.002, REFINE_ITERS=8 (defaults shown), so a stray value in your shell silently changes every worker you start. The log records only w_max (the Common λ sweep banner), a subsample (kept K of M (factor=F)), the bisection tolerance and cap, and the selected rounding; SOLVER, LP_TIME_LIMIT and SWEEP_MULTS leave no trace. Owner: Running a sweep end to end (Knobs); full tables on Code walkthrough settings and Code walkthrough lambda_sweep_simplex.

Why does the log show three roundings when only one is used?

Top-p (the p largest y*) and lazy greedy (CELF) are the first two of the ten starting sets that multistart polishes by swaps, so logging them costs nothing; ROUNDING (default multistart) only decides which set is selected for the arrows, cost_c and the S2 bisection. The side-by-side exposed top-p's rural failure in June 2026 (e1c426b): at Austria's LINEAR S2 point top-p strands 806 client cells, greedy 0, multistart 2. Since swaps only ever lower travel, travel_multi ≤ min(travel_topp, travel_greedy) holds by construction. Owner: From LP fractions to real pharmacies.

History

Why was the cap ladder dropped?

The May–June 2026 recipe from Lewis and Bernhard was a four-rung ladder — closures decided by a minimum and maximum catchment (residents per pharmacy) in rungs A–C, a price per location only at D — attractive because it needs no euro. Built in a day (cap_scenario.jl, 22 June), run on the Netherlands and de-emphasised the next morning (6dc70f5, "per feedback"; whose is not recorded): Dutch per-cell catchments run from 4 394 (p10) to 34 905 (max), so a cap at the maximum binds nowhere and one at p90 declares a tenth of today's network illegal — "realistic min/max bounds are hard to set". Formally dropped 4 September (ef05320). Owner: Alternatives not pursued §1.

Why did pharmacies replace schools, and what changed in the model?

Why is not in the repository — doc/topics.md names only the mail thread of 13 May 2026. What changed: the school LP charged λ per pupil by which an open school fell short of a minimum enrolment, so the penalty could close small schools but never trade count against travel. For pharmacies min_clients = Inf switches that term off and every open location pays the same λ (README §Algorithms); soft coverage came later, and Client became the whole population (033dd32, 2 July). Owner: Alternatives not pursued §2; dates on Decision log.

What is issue #54 about?

A reviewer noticed FRI's LINEAR LP lower bound is not convex — and one LP's curve must be, since a sliding line only touches the lower convex envelope. build_deck_data.py had merged FRI's July full sweep — 6 765 of 17 141 candidates (LOCATION_SELECTION_FACTOR=3), solved by IPM, on the pre-landbody OD — with September refine-only points on the full candidate set: two problems on one curve. FRI is being fully re-swept (unpushed); 16 more areas whose July OD differs marginally from the 2 September rebuild await Chris's call (~20 h). Owner: The lambda sweep and the Pareto frontier; per-area provenance on Branches data and environment §6.

Which branch and commit describe the results, and what is not on GitHub?

ServiceAccess at e25416b is what this wiki documents and the only branch results are reproducible from; origin/main (d9f1f17, 6 August 2026) has ten later commits — nine of them Chris's GeoDMS work — but nothing pharmacy-related, and nothing has been merged either way. Every hash the September issues cite fails git cat-file -t: 216ac77 (the 11–12 September regeneration: OECD Italy, Hungary, Finland, S1/S2 pinned in 87 of 88 sweeps, new deck_data.json, doc/check_s1s2.py) and the four refine-walk refinements of #52 exist only in Maarten's working copy, so the results page is one data generation ahead of the code here. Owner: Branches data and environment §1–2.

What is still open?

In the roadmap's order (ef05320, deck p63): communicate λ in person-minutes per location; widen the choice-set radius 5 → 10 (Norway: 18.6 % of residents one closure from stranding at S2); RSSV candidate reduction (Alternatives not pursued §6); an exact p-median MIP to measure the integrality gap and pin S1/S2 exactly; territorial coverage constraints; decide the logistic rescaling; calibrate the real fixed cost; capacity caps; the #54 re-sweeps. Known but unfiled: the mean_t asymmetry; the stale QUADRATIC wiring in Analyses.dms; frontier_metrics.py interpolating lambda_cross linearly in w while the deck's λ columns use log w (a few percent apart; undecided which is right), and its pareto_frontier envelope, which can lose a genuine solve on a #54-style kinked curve (Metrics aggregation and ranking, Pitfalls). Owner: Decision log (Era 5).

In the code

Each answer names the file and function it rests on (settings.jl, lp_run.jl, lambda_sweep_simplex.jl, cfg/main/Analyses.dms, doc/build_deck_data.py, the .ps1 runners); the pinned permalinks and the line-by-line reading live on Code walkthrough settings, Code walkthrough lp_run, Code walkthrough lambda_sweep_simplex, GeoDMS OD matrix and choice set and Deck and charts pipeline.

Pitfalls and open questions

  • Every number here is the e25416b generation (3 September data, ESPON Italy, snapped S1/S2 in deck_data.json); the 12 September numbers quoted from issues #52–#54 are unpushed and cannot be verified from a checkout.
  • Two S1s, four area counts, two x/y conventions, mean_t versus cost — the four traps behind most misreadings; each has its question above.
  • Rationale never recorded: 120 min; five nearest; 1-2-5 per decade; REFINE_TOL/REFINE_ITERS; MS_RESTARTS/MS_ROUNDS/MS_SEED; 30/15 as the first logistic; NUTS_EXCLUDE_SHARE = 0.5; the 1 km facility unit; why pharmacies replaced schools; whose feedback shelved the ladder.
  • Questions this page does not answer belong on Glossary (terms) or Home (reading order).

See also

Clone this wiki locally