Buckets:
| """Persist a :class:`~fpgm.physics.types.PosteriorResult` and render it for a log line. | |
| Two JSON files, both produced by the same code path | |
| (:func:`_write_report`) because they are the same shape of thing -- a | |
| :meth:`PosteriorResult.as_dict` dump -- at two different points in the | |
| pipeline: | |
| * ``physics.json`` (:func:`write_object_report`) -- one object, in one | |
| episode's directory, from a single :func:`~fpgm.physics.inference.accumulate` | |
| call. Mirrors ``poses.npz``/``events.json`` living next to the episode they | |
| describe. | |
| * ``physics_posterior.json`` (:func:`write_pooled_report`) -- the | |
| :func:`~fpgm.physics.inference.pool`-ed result across every episode of one | |
| object *class*, written once per class rather than once per episode. | |
| Both go through :func:`fpgm.utils.io.atomic_write` for the reason that module's | |
| own docstring gives: a later stage's "is this cached?" check has no way to tell | |
| a truncated write from a real one apart from the file simply existing, so a | |
| process killed mid-write must never leave a partial file at the final path. | |
| **Interface assumption, stated because it cannot be verified from this | |
| module alone:** ``out_path`` is documented as the *directory* the episode/class | |
| output lives in, not the file path itself -- the fixed filenames above are | |
| appended here. This mirrors how ``poses.npz``/``events.json``/``meta.json`` are | |
| found by name inside a known episode directory elsewhere in the datagen stages, | |
| rather than every writer inventing its own filename. If S9's orchestrator (built | |
| by another agent, outside this module's scope) instead wants to hand this | |
| module a full file path, that is a one-line change at the two call sites below | |
| -- flagged here rather than silently guessed at. | |
| """ | |
| from __future__ import annotations | |
| import json | |
| from pathlib import Path | |
| from typing import Any | |
| from fpgm.physics.types import PhysicsError, PosteriorResult | |
| from fpgm.utils.io import atomic_write | |
| __all__ = ["write_object_report", "write_pooled_report", "format_table"] | |
| _OBJECT_FILENAME = "physics.json" | |
| _POOLED_FILENAME = "physics_posterior.json" | |
| def _write_report( | |
| result: PosteriorResult, out_dir: Path, filename: str, extra: dict[str, Any] | None | |
| ) -> Path: | |
| payload = result.as_dict() | |
| if extra: | |
| collision = sorted(set(extra) & set(payload)) | |
| if collision: | |
| raise PhysicsError( | |
| f"extra keys collide with PosteriorResult.as_dict()'s own fields: {collision}" | |
| ) | |
| payload = {**payload, **extra} | |
| data = json.dumps(payload, indent=2).encode("utf-8") | |
| path = Path(out_dir) / filename | |
| atomic_write(path, data) | |
| return path | |
| def write_object_report( | |
| result: PosteriorResult, out_path: Path, *, extra: dict[str, Any] | None = None | |
| ) -> Path: | |
| """Write ``result`` as ``<out_path>/physics.json``. Returns the file path written. | |
| Args: | |
| result: A single episode-object's :func:`~fpgm.physics.inference.accumulate` | |
| output. | |
| out_path: Directory to write into (created if missing, via | |
| :func:`~fpgm.utils.io.atomic_write`'s own ``ensure_dir``). | |
| extra: Extra fields to merge into the JSON payload alongside | |
| :meth:`PosteriorResult.as_dict`'s own (e.g. episode uuid, camera | |
| serial, object label -- whatever identifies *this* report to a | |
| caller that only has the resulting file, not the Python object). | |
| Raises :class:`~fpgm.physics.types.PhysicsError` on any key | |
| collision rather than silently letting one side win. | |
| """ | |
| return _write_report(result, Path(out_path), _OBJECT_FILENAME, extra) | |
| def write_pooled_report( | |
| result: PosteriorResult, out_path: Path, *, extra: dict[str, Any] | None = None | |
| ) -> Path: | |
| """Write ``result`` as ``<out_path>/physics_posterior.json``. Returns the file path written. | |
| Same payload shape as :func:`write_object_report`; different filename and | |
| call site (once per object class, after :func:`~fpgm.physics.inference.pool`, | |
| rather than once per episode). | |
| """ | |
| return _write_report(result, Path(out_path), _POOLED_FILENAME, extra) | |
| def format_table(result: PosteriorResult) -> str: | |
| """Human-readable per-parameter table for a log line: prior/posterior/contraction/learned. | |
| Rows follow ``result.space.names`` order, not a "most-informative first" | |
| sort: this table exists to compare one parameter across many logged runs at | |
| a glance, which only works if that parameter is always on the same row. | |
| ``result.params`` is expected to already be built in that order (both | |
| :func:`~fpgm.physics.inference.accumulate` and | |
| :func:`~fpgm.physics.inference.pool` construct it by iterating | |
| ``prior.space.names``), but this function re-indexes by name explicitly | |
| rather than trusting positional order -- printing one parameter's numbers | |
| under another's name would be a silent, easy-to-miss bug in exactly the | |
| kind of log line nobody double-checks against the source of truth. | |
| """ | |
| by_name = {p.name: p for p in result.params} | |
| missing = [n for n in result.space.names if n not in by_name] | |
| if missing: | |
| raise PhysicsError(f"format_table: PosteriorResult has no params for {missing}") | |
| name_w = max(len(n) for n in result.space.names) | |
| header = ( | |
| f"{'param':<{name_w}} {'prior (mean +- std)':>22} {'posterior (mean +- std)':>24} " | |
| f"{'contract':>8} learned" | |
| ) | |
| lines = [header, "-" * len(header)] | |
| for name in result.space.names: | |
| p = by_name[name] | |
| prior_s = f"{p.prior_mean:+.4f} +- {p.prior_std:.4f}" | |
| post_s = f"{p.post_mean:+.4f} +- {p.post_std:.4f}" | |
| lines.append( | |
| f"{name:<{name_w}} {prior_s:>22} {post_s:>24} " | |
| f"{p.contraction:>8.3f} {'yes' if p.learned else 'no'}" | |
| ) | |
| return "\n".join(lines) | |
Xet Storage Details
- Size:
- 5.85 kB
- Xet hash:
- d5bd52590f3cd06176a9f7e9c585a2a8b74d17035e18cfd2aa9aba277d677502
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.