zymera.metrics
metrics
Canonical metrics + :class:StepCtx — compute once, read everywhere.
This module replaces the metric blocks zymera v0 triplicated across the env
reward, the trainer cores, and graded eval: pure-JAX geometry / graph helpers
(:func:pairwise_dist, :func:adjacency, :func:reach, ...) plus the
per-step derived-quantity cache :class:StepCtx produced by :func:derive.
JAX contract (see docs/specs/2026-06-11-zymera-design.md §3.1):
- Every function is jit/vmap/
lax.scan-safe with fixed shapes. - :class:
StepCtxstructure is a function of therequiresset ONLY — un-requested fields stay PythonNoneat trace time, so un-requested machinery never compiles and the pytree structure never depends on data. - Doctrine #4:
ctx.adj/ctx.reachread POTENTIAL topology; realized (post-dropout) edges arrive separately asctx.delivered. Carrying both prevents a silent re-baseline when comms become stochastic.
StepCtx
Per-step derived quantities, computed ONCE by :func:derive and read
by reward terms, obs builders, and mission metrics.
Every field is either a fixed-shape array or Python None — which one
is decided at trace time by the requires set, never by data, so the
pytree structure is stable under vmap/scan for a given env configuration.
adj/reachare POTENTIAL-topology quantities (doctrine #4);deliveredis the realized (post-dropout) edge set from the channel.newly_covered/overlapreproduce v0's coverage reward inputs: per-agent counts of fresh footprint cells and same-step footprint overlap with other agents.
cheby_footprint
cheby_footprint(pos, h, w, r)
(N, H, W) bool — cells within Chebyshev radius r of each agent.
Source code in zymera/metrics.py
31 32 33 34 35 36 37 | |
pairwise_dist
pairwise_dist(pos)
(N, N) int32 — Chebyshev distances between agents.
Source code in zymera/metrics.py
40 41 42 43 44 | |
adjacency
adjacency(pos, radius)
(N, N) bool — Chebyshev disk graph (symmetric, diagonal True).
Source code in zymera/metrics.py
52 53 54 | |
reach
reach(adj)
(N, N) bool transitive closure — reach[i, j] = j reachable from i.
Log2 matrix squaring: the loop count depends only on the STATIC agent count, so this unrolls at trace time (no traced-value branching).
Source code in zymera/metrics.py
57 58 59 60 61 62 63 64 65 66 67 | |
connected
connected(adj)
Scalar bool — is the graph connected?
Source code in zymera/metrics.py
70 71 72 | |
giant_component
giant_component(adj)
Scalar int32 — size of the largest connected component.
Source code in zymera/metrics.py
75 76 77 | |
collisions
collisions(pos)
(N,) float32 — number of OTHER agents co-located with each agent.
Source code in zymera/metrics.py
85 86 87 88 89 | |
coverage_fraction
coverage_fraction(covered)
Scalar float32 — fraction of ALL cells covered (v0 used sum / H·W, walls included in the denominator; kept for metric continuity).
Source code in zymera/metrics.py
92 93 94 95 | |
redundancy
redundancy(seen_by, covered)
Scalar float32 — Σ per-agent covered cells / team covered cells.
1.0 = perfectly disjoint coverage; N = everyone covered the same cells.
Source code in zymera/metrics.py
98 99 100 101 102 103 | |
dist_to_frontier
dist_to_frontier(pos, uncovered, h, w)
(N,) float32 — Chebyshev distance from each agent to its nearest UNCOVERED cell (0 if nothing uncovered). Φ1 = −this; absolute cells → size-invariant.
Source code in zymera/metrics.py
106 107 108 109 110 111 112 113 114 115 116 117 118 119 | |
field_mean_dist
field_mean_dist(pos, uncovered, h, w)
Scalar float32 — MEAN over uncovered cells of the Chebyshev distance to the nearest agent (0 if nothing uncovered). Φ3 = −this; mean (not sum) → size-invariant.
Source code in zymera/metrics.py
122 123 124 125 126 127 128 129 130 131 132 133 134 135 | |
derive
derive(prev, world, requires, *, topology=None, cover_r=0, delivered=None, blocked=None)
Compute the requested :class:StepCtx fields for the prev → world
transition. Gating is Python-time on the static requires set.
dist/collisionscome fromworld.body.position.adj(andreach, its closure) come fromtopology.adjacency(world)— requesting either without a topology is a configuration error.newly_covered[i]= #cells incheby_footprint(pos, cover_r) & ~wallnot yet inprev.covered(v0'snewlyreward input).overlap[i]= #cells of i's footprint this step that another agent's footprint also covers this step (v0 anti-redundancy).delivered/blockedpass through the keyword arguments (produced by the channel and the dynamics respectively).
Source code in zymera/metrics.py
175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 | |