Asking the fleet what it is doing…
monad-knowledge Wi-Fi sensing lab · FIIT STU

Pedestrian dynamics on the platform: JuPedSim → PedPy

Week 2 — Pedestrian Dynamics on the Platform: JuPedSim → PedPy

Where We Are

Last week we met the idea of a contract: a simulation produces output files, and each file comes with a published description saying exactly what it should contain. That published description is the promise that lets someone else re-run the work and trust the result. This week we stop talking about the idea in the abstract and make it concrete on the first real pair of programs the platform ships.

A clarification up front, because a sister course covers neighbouring ground. CRD503 teaches the crowd physics — why crowding and walking speed are linked, and what the underlying movement model computes. SIM507 teaches how you operate the tools — how you drive these two programs and what data flows between them. We will treat both programs as sealed boxes. The lesson is not the physics; it is the data contract that lets one program's output become another program's input without anyone hand-editing a file. If you finish this week able to draw that contract and check a real file against it, you have the skill the rest of the course builds on.

The throughline of the whole course applies here in miniature: a simulation is an instrument, and an instrument whose outputs you cannot check, trace, and re-run is not evidence. The file that passes between the two programs is where that discipline lives or dies.


First, the Words

A handful of plain definitions before anything else. Each of these comes back in context later, so you do not have to memorise them now.

  • Simulator — a program that imitates a real physical process on a computer.
  • Docker image — a sealed software package that carries everything the program needs to run: its code, its libraries, its settings. Think of it as a recipe box that ships with all its own ingredients, so it runs the same way on any computer.
  • Trajectory — the path each simulated person walked, recorded as a list of positions over time.
  • Parquet file — a storage file like a spreadsheet, but strict about the type of each column. A plain CSV will happily let you type the word "twelve" into a number column; a Parquet file refuses, because every column has a declared type and the file enforces it.
  • Output file — any file a simulator produces when it finishes.
  • Published description — the program's own list of the files it expects to receive and the files it produces, with the declared type of every column. (On the platform this is returned by a listing tool that reports each program's inputs and outputs.)
  • Pipeline diagram — a flow chart in which work only moves forward, step to step, never looping back. One program's output becomes the next program's input.

Two everyday anchors for the words most likely to feel abstract:

A Docker image is a sealed lunchbox. It carries its own program, its own libraries, its own settings. Hand it to any computer and it runs the same way — there is no "but it works on my machine".

A Parquet file is a spreadsheet with the column types locked. That strictness is the whole point: it is what lets a second program refuse a file whose columns are wrong, instead of silently mis-reading them.


Two Programs, One File Between Them

On the platform, this simulation is not one program. It is two sealed programs run one after the other.

Before the diagram, an analogy. Picture a lab with two technicians who never meet. One technician runs the experiment and writes every result onto a standard form. A second technician picks up that form and analyses it. They never speak and never share a desk. They agree on exactly one thing: the layout of the form. Get the form right and either technician can be replaced without telling the other. That standard form is the file the two programs pass between them — the trajectory file.

Now the two programs:

  1. The walker wraps a crowd-dynamics library (JuPedSim). It is the agent-based engine: given a room, a number of people, a duration and a starting setup, it advances each pedestrian one small time-step at a time and writes out where everyone was at every frame. The movement rule it uses internally — a published collision-avoidance model that keeps walkers from overlapping — is a sealed detail for us; this course is about operating the program, not deriving the rule.
  2. The measurer wraps an analysis library (PedPy). It is the measurement library: it does not move anyone. It measures a trajectory that already exists, computing crowding and speed.

The walker writes exactly one file the measurer is contractually required to read: the trajectory file. Everything else this week hangs off that single connection.

The walker (JuPedSim) writes one trajectory file; the measurer (PedPy) reads that same file and produces the summaries. Work only moves forward, one program's output becoming the next program's input.

The arrow between them is the entire point of the week. The two programs are written by different people, depend on different libraries, and run in different sealed boxes. They never share memory and never call each other's functions. They agree on one thing: a file with five named, typed columns. As long as the walker writes those columns and the measurer reads those columns, neither needs to know anything else about the other. That independence is exactly what lets the platform mix and match programs — and it is exactly what makes the contract worth defending.


The Contract: the Trajectory File

The contract is not folklore; it is written down. The platform's listing tool returns a machine-readable published description for every program, and the walker's description declares its trajectory file as five columns.

The schema — the locked list of columns, each column's name, its type, and its plain meaning — is:

Column Type Meaning
frame whole number Which frame this is: 0, 1, 2, …
t_s decimal number The clock time of that frame, in seconds (s)
agent_id whole number Which simulated person this row is about
x decimal number Position along the room's long side, in metres (m)
y decimal number Position along the room's short side, in metres (m)

One row per pedestrian per frame. How big does that get? A little arithmetic, one step at a time. The walker records a frame every 0.05 seconds — that is 20 frames every second. A run lasts 180 seconds, so it produces 180 ÷ 0.05 = 3 600 frames. With 16 people, every person gets one row per frame, so the file holds 3 600 × 16 = 57 600 rows. The row count is fully predictable from three knobs: how often, for how long, and how many people.

The measurer's published description declares the same file as an input — but with a different job. The walker lists the trajectory file as its main output (its job is "trajectory"). The measurer lists the very same file as something it needs but did not make (its job is "incoming input"), and records which program produced it.

That "job" label is the platform's role vocabulary: it describes what a file is for, independent of what is in it. The five jobs you meet this week — trajectory, incoming input, per-window summary, single-number summary, and chart — are the same words that describe the next program's files in Week 3. Learn them once here.

The same trajectory file carries two jobs: the walker lists it as its main output, the measurer lists it as an incoming input it needs but did not make. The role vocabulary labels what each file is for, independent of what is in it.

A plug-and-socket comparison makes the dual job concrete: the appliance maker and the wall-socket maker never meet, but both build to the same plug shape. The file's "job" label is that agreed shape — the walker builds the plug, the measurer builds the socket, and the standard layout is what lets them connect.

Why the Schema Check Matters

A producer-consumer contract is only useful if it is checked. Think of a customs officer who reads the label on a parcel: a wrong label means the parcel is refused at the border, not waved through on a guess about what is inside.

If the walker were to rename t_s to time, or store x as a less precise number type, the measurer would face a file that looks plausible but is wrong. The correct behaviour is to refuse the file, not to emit a confident-but-meaningless crowding number. In the workbook (Cell 5) we build a small fake trajectory and run it through exactly this check against the published description — a column-presence and type check that mirrors what a disciplined program does before it trusts an incoming file. A pipeline that skips this step fails silently, which is the worst way for a scientific instrument to fail.


What the Measurer Produces: the Per-Window Summary

Once the measurer accepts a trajectory, it produces a summary file whose job is per-window summary — one row for each short slice of time:

Column Meaning
t_s When this time window starts, in seconds (s)
mean_density_ped_m2 Average crowding in that window, in people per square metre (ped/m²)
mean_speed_m_s Average walking speed in that window, in metres per second (m/s)
n_active How many people were inside the measured area in that window

The unit ped/m² (people per square metre) is the natural crowding unit: about 0.5 ped/m² is a sparsely-populated room, while 4 ped/m² and above is a dangerous jam.

How does the measurer turn positions into crowding? Picture the floor split into territories: every spot on the floor belongs to whoever is nearest to it. Each person "owns" the patch of floor closest to them. A person hemmed in by close neighbours owns a small patch and so has a high local crowding; an isolated person owns a large patch and so has a low local crowding. The formal name for that nearest-owner split is the Voronoi method, and each person's local crowding is simply one divided by the area of their patch.

In the workbook (Cell 9) we cannot install the real measurement library offline, so we reproduce the shape and layout of its output with a lighter stand-in. The stand-in estimates each person's local crowding from the distance to their nearest neighbour: draw a circle out to that nearest neighbour, and treat the little disc halfway between you as the space you "own". A closer neighbour means a smaller disc and a higher crowding. Written as a formula,

\hat{\rho}_i = \frac{4}{\pi\, d_i^{\,2}},

where \hat{\rho}_i is the estimated crowding for person i and d_i is the distance to their nearest neighbour; the rest of the expression just turns that distance into the area of the little disc. This is a coarse stand-in for a real Voronoi patch — it reads local spacing, which runs higher than the room-average crowding — so the value it recovers is the right ballpark, not an exact match to the measured number. The teaching point is the shape: a crowding time series that starts in a warm-up and settles to a steady plateau, with the warm-up discarded before any average is taken.

Crowding over time: the series starts in a warm-up while people settle from their artificial initial layout, then settles to a steady plateau. The first 17% of each run — about the first 30 seconds of a 180-second run — is discarded before any average is taken.

The warm-up discard deserves a sentence of its own, because it is small but load-bearing. The people start from an artificial initial layout and need time to settle into steady, natural motion; averaging over the start-up would bias the crowding number. So the run's settings declare a discard rule: throw away the first 17% of every run — about the first 30 seconds of a 180-second run — before computing the mean. The reason this lives in the run's written, versioned settings rather than buried in code is precisely so that it is reproducible. Visible settings are what make a result repeatable.


The Output the Whole Pipeline Exists For: the Speed-Versus-Crowding Chart

The point of running both programs is one figure: average walking speed plotted against average crowding. Each run contributes one point. The lab ran the walker at three different people-counts in an 8 m × 12 m seminar room — with an effective walkable area of about 96 m² — and the measurer reported these three points:

Run People Crowding (ped/m²) Speed (m/s)
Run A 4 0.042 1.169
Run B 16 0.167 1.139
Run C 48 0.500 0.708

Speed versus crowding: each of the three runs contributes one measured point, and a smooth curve is drawn through them for the eye only. Speed falls as crowding rises, from 1.169 m/s at 0.042 ped/m² down to 0.708 m/s at 0.500 ped/m².

The same three measured points, with no illustrative curve, so you can read each one directly:

Speed falls strictly as crowding rises. To put a name to "every single time", we use a rank correlation — a score from −1 to +1 that asks only whether, as one thing goes up, the other goes reliably down (or up). A score of +1 means they always rise together; −1 means they always move in opposite directions; 0 means no pattern. Our three points score −1.0: speed fell every time crowding rose, with no exception. The sparsest condition (1.17 m/s) is a relaxed adult walking pace; the densest (0.71 m/s) shows the onset of a congestion-driven shuffle. Note what −1.0 does and does not say: it says the ordering was perfect, not by how much — which is honest with only three points.

Two cautions, both about reading data honestly:

  1. The curve is illustrative, the points are sovereign. It is tempting to draw a smooth curve through the three points so the eye has something to follow. We do, but only for the eye. A reasonable shape says walking speed depends on the space each person has: with lots of space people walk near their free-flow speed v_0 (how fast you go with the floor to yourself); as the crowd approaches a total jam, speed collapses toward zero. A common form writes v(\rho) = v_0\left[1 - \exp\!\left(-\gamma\left(\tfrac{1}{\rho} - \tfrac{1}{\rho_{\max}}\right)\right)\right], where \rho is crowding, \rho_{\max} is the crowding of a complete jam, and \gamma controls how sharply speed drops as the crowd thickens. But three points cannot prove a two-parameter curve. The lab reports the points; the curve is a teaching aid, not evidence.
  2. Three points span two regimes, not four. A regime is a band of crowding in which the crowd behaves in one characteristic way. The measurer sorts crowding into four bands — free-flow (below 0.5 ped/m²), light congestion (0.5–2.0), dense flow (2.0–4.0), and jam (4.0 and above). In a 96 m² room the people-counts tested reach only the first two. Reaching the jam band needs roughly 400 people or a narrow choke-point; the lab deliberately deferred that to a follow-on study. Do not extrapolate the curve into bands the data never visited.

Operating the Pair End-to-End

This is a toolkit-operation course, so the operating path is examinable. Here is how you would run, inspect, and locate one of these runs on the platform — every step through the platform's own tools, with no hand-poking at cloud storage.

Launch a run. A run is one single execution of the pair with one set of settings. The speed-versus-crowding study already exists, so you attach new runs to it rather than improvising. A single run changes only the number of people; the room, the duration, the time-step and the discard rule are filled in automatically from the study's shared settings. The launch hands back a ticket number, and you check on that ticket until it reports that the run has finished, at which point the run's record carries its results. The whole sweep across people-counts is the study's job — you can dispatch it to run automatically, or watch it step by step.

Inspect the outputs. Once a run finishes, asking the platform to read the run back returns its full record — the exact program versions used, the settings, and the summary numbers the measurer reported. For the raw data files themselves, you ask for a signed link: a temporary download link to a file in cloud storage that expires after a short time, so it cannot be shared forever.

Find the run in the vault. Every synced run leaves a permanent record page in the research notebook: a generated page for the run under the experiments section, and a page for the whole study tracking its latest batch. This is the fifth objective in practice: any number you cite traces backward through run → session → campaign → recorded page — where a session is a batch of runs done together and a campaign is the whole study those sessions serve. In the workbook, Cell 13 walks that chain for the densest point and shows the guard that does nothing harmful when you are working offline.


Why This Is the Reusable Lesson

The reason we spend a week on five columns in one file is that the same contract pattern is how every program on the platform composes. Week 3 introduces a ray-traced radio-channel simulator; its published description lists the very same trajectory file as an incoming input produced by the walker — the very file you studied this week becomes the crowd that the channel simulator's radio waves have to pass through and around. The walker does not know the channel simulator exists. They compose only because they agree on the contract.

That is the deeper claim of the course, restated at the file level: reproducibility and composability are not properties of any one program. They are properties of the typed connections between programs. Master the connection and the platform stops being a pile of sealed boxes and becomes a pipeline you can reason about.


Key Results and Where to Reproduce Them

Result Where it comes from Workbook
The simulation is two programs joined by one trajectory file The programs' published descriptions fig_pipeline_dag.png (Cell 3)
Trajectory schema frame, t_s, agent_id, x, y, checked against the published description The walker's published description Cell 5 + fig_artefact_schema.png (Cell 7)
Per-window summary shape; warm-up discarded (first 17% of each run) The measurer's published description fig_density_vs_time.png (Cell 9)
4 people: crowding 0.042 ped/m², speed 1.169 m/s The lab's measured runs fig_fundamental_diagram.png (Cell 11)
16 people: crowding 0.167 ped/m², speed 1.139 m/s As above As above
48 people: crowding 0.500 ped/m², speed 0.708 m/s As above As above
More people → slower walking: rank correlation = −1.0 As above Cell 11
Every number traces run → session → campaign → recorded page The platform's own tools Cell 13

Looking Ahead

Week 3 swaps the measurement program for a ray-traced radio-channel simulator and reuses this week's trajectory file as the moving crowd that blocks synthetic radio links. The contract you checked this week is the input that week consumes. The skill carries forward unchanged: read the published description, check the schema at the connection, and trace every number to its run.


Further Reading

  • the walker (JuPedSim runner) — the curated note on the pedestrian-walking program: its contract, inputs, outputs, the movement-model taxonomy, the parameter reference, and the regime bands. Read the Contract / Inputs / Outputs sections for operation; the rest is upstream reference.
  • the measurer (PedPy analyser) — the curated note on the measurement program: the crowding-method taxonomy (classic / Voronoi / line / passing), the speed and flow methods, and the same regime bands keyed to the empirical literature.
  • Liddle et al. 2022 — the empirical study the measurer's regime bands derive from; the source of the free-flow and light-congestion speed ranges.
  • EXP-S1 simulation sandbox — the vault note that chains these two programs, including the Geometry A room definition and the platform readiness gate.