agentleFS
leventiloleventilo/mobius/skills/physics-interpreter/SKILL.md

physics-interpreter

Skill8 starsChanged 7 months agoMITView on GitHub

Translate paper-parser's extracted equations, claims, and surrounding text into a typed physics sub-tree of the SimSpec — governing equations, boundary conditions, initial conditions, declared constants, regime classification, and named approximations. Run after paper-parser, before simspec-author.

The file

We split this skill into the rules it gives an agent

11 rules. About 0 are common, 5 are distinctive, and 6 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈5

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 6

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    All six core keys (governing_equations, boundary_conditions, initial_conditions, constants, regime, approximations) are REQUIRED.

    line 91
  2. Very specific:

    If the inputs are degenerate (no equations parsable, paper.json missing, schema-validation failure), emit a fenced JSON block carrying an …

    line 98
  3. Distinctive:

    INSTEAD of the partial artifact:

    line 100
  4. Distinctive:

    DO NOT emit narration, summaries, or follow-up questions after the closing fence — they are dropped by the orchestrator.

    line 106
  5. Distinctive:

    Never commit silently. Even high-confidence choices that closed a non-trivial branch are logged.

    line 218
  6. Very specific:

    Never invent equations. If paper.equations[] is empty, emit governing_equations: [] and abort with a log entry.

    line 271
  7. Very specific:

    Never tag a regime without paper evidence.

    line 273
  8. Distinctive:

    Never tag an approximation that is not literally named in the paper text or implied by a term explicitly dropped in an equation derivation.

    line 275
  9. Very specific:

    Never resolve a Phase-1 symbol to a unit not declared in the paper or derivable from the equation's dimensional structure.

    line 277
  10. Distinctive:

    Never silently drop a boundary condition because its variable is unresolved.

    line 279
  11. Very specific:

    Never emit physics.json if any of the bundled validators fail (sympy round-trip, pint unit parse, JSON schema).

    line 281

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

physics-interpreter

Purpose and scope

This skill produces exactly one artifact: the physics sub-tree of the SimSpec, defined at $defs.physics of simspec.schema.json. That sub-tree fixes what every downstream skill is allowed to assume about the paper: which equations govern the system, on which domain they live, with which boundary and initial conditions, with which constants, in which regime, and under which named approximations. Mobius's "same paper, same simulator, ten times in a row" property begins here. If the regime is wrong, every solver heuristic in simspec-author Rule 3-6 fires the wrong way and the rest of the pipeline silently builds the wrong simulator.

What this skill does NOT do. It does not extract equations from the PDF — that is paper-parser's job, and its equations[] array is an input here, not an output. It does not extract numerical claims from figures or text — that is claim-extractor. It does not compose the full SimSpec — that is simspec-author. It does not generate Python solver code — that is primitive-generator. It does not write Three.js or Canvas templates — that is viz-mapper. It does not run units, CFL, or conservation checks — those are the five bundled scripts of science-integrity. The physics-interpreter is a typed-translate skill: paper-parser artifacts in, validated physics sub-tree out, every interpretive choice logged.

Inputs

The skill expects three artifacts on disk in the current working directory.

paper.json from paper-parser carries the full paper provenance and a list equations[]. Each equation has at minimum a latex field (LaTeX source as it appears in the PDF), a tentative sympy field (parser's best-effort sympy translation, may be empty), a free-text surrounding_context excerpt (~200 chars before and after), and an optional variables dict that paper-parser populated from the equation's typesetting context (e.g. greek-symbol heuristics, definition-by-text "where E is the electric field"). The figures[] and numerical_claims[] arrays are read for cross-references.

text_body.json from paper-parser carries the segmented paper body keyed by section anchor (abstract, I, II.A, etc.). The interpreter scans this body for boundary-condition phrases, initial-condition declarations, named approximations, and regime hints. The body is read-only.

hints.json from the orchestrator (optional) carries any pre-classification cues: domain_hint ("optics"|"mechanics"|...), paper_class_guess, and references to known canonical models the orchestrator already matched by title regex. When absent, the interpreter starts from zero priors.

Outputs

The skill writes physics.json to the working directory. Its top-level keys mirror $defs.physics exactly:

{
  "governing_equations": [...],
  "boundary_conditions":  [...],
  "initial_conditions":   [...],
  "constants":            [...],
  "regime":               "linear" | "weakly-nonlinear" | "strongly-nonlinear" | "stochastic" | "quantum-coherent",
  "approximations":       ["thin-lens", ...]
}

In addition the skill emits a sibling interpretation_log.json (NOT part of the schema) with the shape:

{
  "entries": [
    {
      "phase": "1-symbol-resolution" | "2-equation-classification" | "3-bc-extraction" | "4-constants" | "5-regime",
      "choice": "string short label of what was decided",
      "alternatives_considered": ["...", "..."],
      "reason": "why this branch was chosen",
      "confidence": 0.0
    }
  ]
}

simspec-author reads physics.json verbatim into the SimSpec's physics sub-tree. The orchestrator forwards interpretation_log.json into integrity.checks[] as one type: "ambiguity" check per low-confidence entry (confidence < 0.7). Rule across the whole skill: never silently commit to a default. Every choice that could legitimately go more than one way emits a log entry. This is the SINTEF tacit-assumption gap, reframed as an explicit, auditable artifact.

Output format (mandatory)

You MUST emit your final output as a single fenced json block at the END of your reply, with NO prose after the closing fence. The orchestrator parses that block by regex (`/(?:json)?\s*\n([\s\S]?)\n\s```/) and ignores everything else in your text content. Writing physics.jsonto disk via code_execution is fine and recommended (downstream chained skills read it from/mnt/user-data/outputs/`), but the canonical artifact MUST also be inlined as the final fenced JSON block of your reply.

The canonical artifact for physics-interpreter (consumed downstream as PhysicsJson in server/src/types.ts):

{
  "governing_equations": [
    {
      "latex": "i\\hbar\\partial_t\\psi = -(\\hbar^2/2m)\\partial_x^2\\psi + V(x)\\psi",
      "sympy": "Eq(I*hbar*Derivative(psi, t), ...)",
      "domain_hints": ["QM"],
      "viz_hint_archetype": "field-2d"
    }
  ],
  "boundary_conditions": [],
  "initial_conditions": [],
  "constants": [],
  "regime": "linear",
  "approximations": [],
  "phenomenon": "qm-bound"
}

All six core keys (governing_equations, boundary_conditions, initial_conditions, constants, regime, approximations) are REQUIRED; emit [] for an empty array, never omit. phenomenon and per-equation viz_hint_archetype are advisory (used by simspec-author Rule 11). regime must be one of linear | weakly-nonlinear | strongly-nonlinear | stochastic | quantum-coherent.

If the inputs are degenerate (no equations parsable, paper.json missing, schema-validation failure), emit a fenced JSON block carrying an error field INSTEAD of the partial artifact:

{ "error": "paper.equations[] is empty; refusing to invent a governing equation" }

DO NOT emit narration, summaries, or follow-up questions after the closing fence — they are dropped by the orchestrator.

Interpretation pipeline

Five phases, run in order. Phase N reads only the outputs of phases 1..N-1 plus the original inputs. No backtracking: if a phase needs to revise an earlier decision, it logs a phase: "Nx-revision" entry instead of mutating in place.

Phase 1 — Symbol resolution

Walk every entry in paper.equations[*].variables. For each symbol, choose a role in {independent, dependent, parameter, constant} and a UCUM unit. The role decision tree:

  • The symbol appears as the argument of a derivative anywhere in the equations? → independent.
  • The symbol is differentiated and never appears as a constant in any other equation? → dependent.
  • The symbol appears with a numerical value in paper.numerical_claims[] AND is not differentiated? → constant.
  • Otherwise → parameter.

Unit assignment uses three sources, in priority order: explicit unit declared in the paper text near the symbol's first definition (regex on text_body.json), inheritance from a physical role mapped in the bundled examples/ library (e.g. anything labeled "electric field" → V.m-1), and dimensional inference from the equation if both sides have one fully-resolved side. Conflicts emit a log entry and pick the paper-text value.

Symbol-to-role mapping uses a small dictionary built from the bundled examples. Common cases: E, \vec{E} → electric field, \psi, \Psi → wavefunction, \theta, \phi → angle, \rho → density (mass or charge from context), T → temperature OR period (resolved by units), \beta → inverse temperature OR damping, c → wave speed (often light), \hbar → reduced Planck constant, k_B → Boltzmann. Ambiguous symbols (e.g. T) always emit a log entry.

Phase 2 — Governing equation classification

For each equation, attach a domain_hints[] array (subset of ["wave","mechanics","QM","fluid","materials","thermo","em","relativity"]) and run the regime decision tree below to compute a per-equation regime tag. The full-spec physics.regime enum is decided in Phase 5 from the per-equation tags plus paper text.

Decision tree for per-equation regime classification:

START with equation E and its sympy expression.

Q1. Does E contain a stochastic term (Wiener process dW, noise \xi, Langevin \eta)
    OR is the equation a master equation / Fokker-Planck / Langevin form
    OR does the surrounding text mention "Monte Carlo", "Metropolis", "noise-driven"?
    YES → tag = stochastic. STOP.
    NO  → continue Q2.

Q2. Does E contain \hbar, |psi>, \hat{H}, or a wavefunction symbol from Phase 1?
    YES → tag = quantum-coherent. continue Q2a (linearity refinement).
    NO  → continue Q3.

  Q2a. Are nonlinear-in-psi terms present (e.g. |psi|^2 psi for Gross-Pitaevskii)?
       YES → tag = quantum-coherent + nonlinear flag. STOP.
       NO  → tag = quantum-coherent. STOP.

Q3. Does E contain a time derivative \partial_t or d/dt?
    YES → continue Q4.
    NO  → continue Q5 (steady-state branch).

Q4. Are nonlinear terms in the dependent variable present
    (products like u u_x, |E|^2 E, sin(theta) where theta is unbounded)?
    NO  → tag = linear-time-dependent. STOP.
    YES → continue Q4a.

  Q4a. Does the paper text or approximations list contain
       "small amplitude", "perturbative", "linearization", "harmonic approximation"?
       YES → tag = weakly-nonlinear. STOP.
       NO  → tag = strongly-nonlinear. STOP.

Q5. Steady-state. Are nonlinear terms present?
    NO  → tag = linear-steady. STOP.
    YES → tag = strongly-nonlinear-steady. STOP.

Per-equation tags are written to a private side-table (not part of the schema output) and consumed by Phase 5.

Phase 3 — Boundary and initial condition extraction

Scan text_body.json for the regex set (boundary condition|BC|fixed at|clamped at|free at|periodic|open boundary|absorbing|PML|radiation condition|no-slip|no-flux|reflecting wall|hard wall|infinite well). For every hit, extract a 200-char window around the match and run the BC classifier:

  • "fixed", "clamped", "= 0", "u(0)=", "psi(0)=" → dirichlet.
  • "no-flux", "insulating", "du/dn = 0", "free end" → neumann.
  • "Robin", "h u + k du/dn" pattern → robin.
  • "periodic", "wraps", "torus" → periodic.
  • "open", "outflow" → open.
  • "absorbing", "PML", "radiation condition", "Sommerfeld" → absorbing.

For each extracted BC, write one entry into boundary_conditions[] with region filled from the text window (e.g. "x=0", "r=R"), value as a sympy-parseable string, and variable matching a Phase 1 dependent symbol. If the variable cannot be resolved to a Phase 1 symbol, the BC is dropped and a log entry emitted.

Initial conditions use the regex set (initial state|initially|at t=0|t = 0|prepared in|the system starts). For every hit, extract variable, expression, and the t0 value (almost always 0; non-zero only when paper explicitly says "we set the clock at t = t_0"). Wavefunctions get special handling: psi(x, 0) = exp(-x**2 / (2*sigma**2)) and similar Gaussian-wavepacket forms are recognized verbatim.

Phase 4 — Constants harvesting

Aggregate three sources:

  1. paper.equations[*].variables entries with role == "constant" from Phase 1.
  2. paper.numerical_claims[] entries whose source_quote matches a definition pattern ("we use g = 9.81 m/s^2", "the mass m = 1 kg").
  3. Standard CODATA values pulled from the bundled constants_table.json (script-supplied; not in this prompt) when the paper uses a symbol like \hbar, c, k_B without redeclaring.

For each entry produce a {symbol, value, unit, source} tuple. source is "paper" if the value came from sources 1 or 2, "standard" if from CODATA, "derived" if computed from other constants (e.g. omega_0 = sqrt(k/m) when both k and m are paper constants).

Unit conflicts (paper says \lambda = 632 nm, claims says \lambda = 6.328e-7 m) are reconciled by canonicalizing both to UCUM via pint's parser, comparing magnitudes, and keeping the paper-text value if they agree to 0.1 %. Disagreement emits a log entry and keeps the paper-text value (paper is authoritative for paper-specific constants).

Phase 5 — Regime + named approximations

The physics.regime enum has five values; map per-equation tags from Phase 2 to one whole-spec value via:

  • Any per-equation tag is stochastic? → spec regime = stochastic.
  • Else any tag is quantum-coherent? → spec regime = quantum-coherent.
  • Else any tag is strongly-nonlinear or strongly-nonlinear-steady? → spec regime = strongly-nonlinear.
  • Else any tag is weakly-nonlinear? → spec regime = weakly-nonlinear.
  • Else → spec regime = linear.

Named approximations are extracted by dictionary lookup over text_body.json. The dictionary lives in the bundled approximations_dictionary.json (script-supplied) and contains entries like {"thin lens": "thin-lens", "paraxial approximation": "paraxial", "Born-Oppenheimer": "Born-Oppenheimer", "Boussinesq": "Boussinesq", "small-angle": "harmonic-small-amplitude", "Stokes flow": "Stokes", "WKB": "WKB", "rotating-wave approximation": "RWA", "tight-binding": "tight-binding", "incompressible": "incompressible"}. Multi-word matches win over single-word matches. Each match emits a log entry with the verbatim quote that triggered it (auditable provenance for approximations[]).

Ambiguity handling

A choice is ambiguous when two or more branches in any of the five phases are non-trivially compatible with the inputs. Every such choice commits a value AND writes one entry into interpretation_log.json with the shape declared in Outputs. Confidence is a number in [0, 1] computed as follows:

  • Single matching branch with clear textual evidence → confidence 0.95+.
  • Multiple branches matched, paper text broke the tie → confidence 0.7 to 0.9.
  • Tie broken by bundled-examples prior → confidence 0.5 to 0.7.
  • Tie broken by an arbitrary default → confidence below 0.5; the orchestrator surfaces this to the user as a "hidden physics default" toggle in the UI (the SINTEF tacit-assumption gap rendered visible).

Three rules govern logging:

  1. Never commit silently. Even high-confidence choices that closed a non-trivial branch are logged.
  2. The reason field cites either the paper section anchor, the symbol, or the dictionary entry that decided the branch. Never "by convention".
  3. Conflicts between sources (paper text vs claims vs equations) become a log entry with phase: "*-conflict" and confidence ≤ 0.6.

The bundled examples library

The examples/ directory holds eight worked examples, one per canonical physics class. Each file is a markdown document with frontmatter tags domain, regime, technique, paper, plus four sections: source equations (LaTeX verbatim), parsed equations (sympy), resulting physics sub-tree (JSON validated against simspec.schema.json), interpretation reasoning, and a list of ambiguities the interpreter would log on this paper.

The orchestrator loads the relevant subset of these examples by frontmatter match before invoking the skill: when the orchestrator's pre-classifier identifies the paper as e.g. "QM, time-dependent, 1D", it injects infinite-well-1d.md and tunneling-1d.md as few-shot context. When the paper class is unknown, all eight are injected (the per-skill 200K-token budget under Opus 4.7 1M comfortably absorbs them). The examples are not just human reference: Opus 4.7 reads them as in-context priors when filling Phase 1 symbol roles and Phase 5 named approximations.

The eight examples:

FileDomainRegimeTechnique
young-double-slit.mdopticslinearwave-FFT
simple-pendulum.mdmechanicslinear (small-angle)RK4 ODE
double-pendulum.mdmechanicsstrongly-nonlinearRK4 ODE
infinite-well-1d.mdquantumquantum-coherentanalytic
tunneling-1d.mdquantumquantum-coherentFFT-spectral split
2d-ising.mdstat-mechstochasticMonte-Carlo
stokes-sphere.mdfluidlinear (low-Re)analytic
damped-oscillator.mdmechanicslinear (dissipative)analytic + ODE

11. archetype hints

physics-interpreter MAY emit an advisory field viz_hint_archetype on each governing equation to help simspec-author pick the right viz.scene_archetype. Possible values match the eight enum members defined in viz-mapper's SKILL.md §4: ray-optics, glass-dispersion, field-2d, orbital-3d, particle-cloud, lattice, graph-time, schematic.

Mapping table from per-equation tags (Phase 2) and domain_hints[] to advisory archetype:

  • domain_hints includes em or wave AND regime is linear-time-dependent → advisory field-2d (or glass-dispersion if the surrounding text mentions "prism", "dispersion", "Abbe", "refraction in 3D").
  • domain_hints includes QM AND any equation involves a 3D Laplacian on a wavefunction → advisory orbital-3d.
  • domain_hints includes QM AND only 1D / 2D coordinates appear → advisory field-2d.
  • domain_hints includes mechanics AND regime is linear or weakly-nonlinear AND only one or two dependent variables → advisory graph-time.
  • domain_hints includes fluid → advisory field-2d.
  • domain_hints includes materials AND regime is stochastic → advisory lattice.
  • Per-equation tag is stochastic AND surrounding text contains "Brownian", "particle", "ensemble", "trajectory" → advisory particle-cloud.

The advisory is written into the side-table consumed by simspec-author, not into physics.json directly (the schema does not currently carry it, and emitting it would break the canonical form §). This field is advisory only — simspec-author ultimately decides per the routing rules in its SKILL.md §11. Conflicts between this advisory and simspec-author's rules are logged with phase: "5x-archetype-advisory" and confidence 0.5.

Hand-off

simspec-author copies physics.json verbatim into simspec.physics. All six sub-keys must be present even when empty (use [] for arrays, never omit). Schema reference: $defs.physics in mobius/skills/simspec-author/schema/simspec.schema.json.

Downstream consumers:

  • simspec-author Rule 3 reads regime and governing_equations[*].domain_hints to choose solver_hints.scheme.
  • simspec-author Rule 8 reads approximations to truncate slider ranges.
  • primitive-generator reads governing_equations[*].sympy and the constants table to template Python solver code.
  • science-integrity (units check) reads constants[*].unit and governing_equations[*].variables[*].unit.

Red flags

Refuse to emit physics.json when any of the following holds. Write the partial output AND a phase: "abort" log entry; the orchestrator decides whether to re-dispatch.

Never invent equations. If paper.equations[] is empty, emit governing_equations: [] and abort with a log entry. Inventing a "probably the wave equation" entry is the single most catastrophic failure mode.

Never tag a regime without paper evidence. If Phase 5 cannot find at least one per-equation tag, emit regime: "linear" (the most conservative default) AND a confidence-0.0 log entry. Do not tag quantum-coherent from a title keyword alone.

Never tag an approximation that is not literally named in the paper text or implied by a term explicitly dropped in an equation derivation. The bundled dictionary is a whitelist, not a generator. When in doubt, omit the approximation and log it.

Never resolve a Phase-1 symbol to a unit not declared in the paper or derivable from the equation's dimensional structure. The CODATA fallback is allowed only for universally-named constants (\hbar, c, k_B, e, m_e).

Never silently drop a boundary condition because its variable is unresolved. Drop AND log; the orchestrator will re-dispatch Phase 1 with extra hints.

Never emit physics.json if any of the bundled validators fail (sympy round-trip, pint unit parse, JSON schema). Write physics.partial.json instead and log the failure.

Edge cases (sympy + pint)

Two library-specific traps the skill must handle.

sympy.parsing.latex.parse_latex has two backends. The default ANTLR backend silently drops trailing partial expressions: parse_latex(r'x -') returns x without raising. The Lark backend raises UnexpectedEOF on the same input. The skill MUST call parse_latex(..., backend='lark') and catch the exception explicitly; falling back to ANTLR only when the orchestrator says so. This matters because paper-parser's LaTeX extraction sometimes truncates (column wraps, math-mode delimiter mismatches).

pint does not natively round-trip UCUM strings (e.g. m.s-1). The schema requires UCUM, but pint parses m / s or m * s ** -1. The bundled units.py script wraps pint with a UCUM-to-pint translator (m.s-1 → m / s, kg.m2.s-2 → kg * m ** 2 / s ** 2) and a pint-to-UCUM serializer for the reverse direction. All unit fields written to physics.json go through the serializer; all unit fields read from paper.json go through the translator first. Round-trip is asserted in tests.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.