Extract, analyze, and summarize simulation output data — pull spatial fields at specific timesteps, compute time-series trends and detect steady state, extract line profiles through the domain, generate statistical summaries and distributions, calculate derived quantities (gradients, fluxes, volume fractions, interface area), compare results against analytical solutions or experimental data, and produce automated analysis reports. Use when interpreting finished simulation results, checking mass or energy conservation, comparing two runs or meshes, extracting interface profiles from phase-field output, or preparing publication-quality analysis, even if the user only says "what do my results look like" or "did my simulation reach steady state."
Analyze and extract meaningful information from simulation output data.
Goal
Transform raw simulation output into actionable insights through field extraction, statistical analysis, derived quantities, visualizations, and comparison with reference data.
Inputs to Gather
Before running post-processing scripts, collect:
Output Data Location
Path to simulation output files (JSON, CSV, HDF5, VTK)
Time step/snapshot indices of interest
Field names to extract
Read field names from the file, never assume them. Before extracting,
open the output file (or run field_extractor.py --input <file> --list --json) and use only the field names that actually appear under fields.
Do not invent fields such as temperature if they are not present, and do
not assume a grid size — read the real shape/count from the data.
Analysis Type
Field extraction (spatial data at specific times)
Time series (temporal evolution of quantities)
Line profiles (1D cuts through domain)
Statistical summary (mean, std, distributions)
Derived quantities (gradients, integrals, fluxes)
Comparison to reference data
Output Requirements
Output format (JSON, CSV, tabular)
Visualization needs
Report format
Scripts
Script
Purpose
Key Inputs
field_extractor.py
Extract field data from output files
--input, --field, --timestep
time_series_analyzer.py
Analyze temporal evolution
--input, --quantity, --window
profile_extractor.py
Extract line profiles
--input, --field, --start, --end
statistical_analyzer.py
Compute field statistics
--input, --field, --region
derived_quantities.py
Calculate derived quantities
--input, --quantity, --params
comparison_tool.py
Compare to reference data
--simulation, --reference, --metric
report_generator.py
Generate summary reports
--input, --template, --output
Workflow
1. Data Inventory
First, understand what data is available:
# List available fields and timesteps
python scripts/field_extractor.py --input results/ --list --json
# Global statistics
python scripts/statistical_analyzer.py \
--input results/field_0100.json \
--field concentration \
--json
# Statistics in a specific spatial region (1D/2D fields only).
# Coordinates are derived from the field shape and grid spacing
# (explicit dx/dy, or Lx/Ly via dx = Lx/(nx-1)); only the variables
# x, y, z compared against numbers, joined by and/or, are allowed.
python scripts/statistical_analyzer.py \
--input results/field_0100.json \
--field phi \
--region "x>0.3 and x<0.7" \
--json
# Distribution analysis
python scripts/statistical_analyzer.py \
--input results/field_0100.json \
--field phi \
--histogram \
--bins 50 \
--json
Interpret convergence differently depending on the quantity type, because the
two signals the analyzer reports (convergence.{rate,type} and
steady_state.reached) answer different questions and can legitimately
disagree.
Judge convergence by absolute magnitude vs a tolerance: a residual at or
below the target tolerance (e.g. 1e-6) is converged. Use
--absolute-threshold <tol> to get the convergence_threshold block, which
is the physically correct test for residuals.
The relative --detect-steady-state test answers "has the residual stopped
changing?", not "has it converged?". For a still-decreasing residual,
steady_state.reached = false is expected and not a failure.
A plateau of a residual means a stalled solver
(convergence.type = "stalled"), not steady state.
Reconcile the signals: if convergence.type is fast/linear and the
final residual is small (or convergence_threshold.reached = true), report
the run as converged even when steady_state.reached = false.
Monotonic decrease in energy: system approaching equilibrium.
Plateau (steady_state.reached = true): steady state reached.
Oscillations: may indicate the time step is too large.
Sudden jumps: possible numerical instability.
Statistical Analysis
Bimodal distribution of order parameter: Two-phase mixture
High variance: Heterogeneous microstructure
Skewed distribution: Asymmetric phase fractions
Comparison Metrics
Metric
Interpretation
L2 error < 1%
Excellent agreement
L2 error 1-5%
Good agreement
L2 error 5-10%
Moderate agreement
L2 error > 10%
Poor agreement, investigate
Output Format
All scripts support the --json flag for machine-readable output. Most
scripts emit a flat top-level object whose keys depend on the script. For
example, field_extractor.py --include-data on a single field emits:
Notes on envelope shapes (they are not uniform across scripts):
field_extractor.py, statistical_analyzer.py, time_series_analyzer.py,
profile_extractor.py, and comparison_tool.py emit a flat object with a
source_file key plus script-specific result keys.
derived_quantities.py wraps its payload in an { "inputs": {...}, "results": {...} } envelope.
report_generator.py emits top-level report_version and generator
keys plus the requested report sections.
No script emits top-level script, version, or input_file keys, and
field statistics (min/max/mean/count) appear at the top level, not
nested under a data object.
Verification checklist
Before trusting or reporting a post-processing result, produce and record the
concrete evidence below (tied to this skill's scripts and output keys):
Listed the real fields first. Ran field_extractor.py --list --json
and recorded the actual fields names and shape; every later --field
argument is one that appears in that list (no assumed temperature, no
guessed grid size).
Used the right convergence test for the quantity type. For a residual/error,
recorded convergence_threshold.reached and final_value from
--absolute-threshold <tol> (the |x_final| <= tol test) rather than
relying on steady_state.reached; for a physical quantity, recorded
steady_state.{reached,relative_variation,value} from --detect-steady-state.
Reconciled the two convergence signals. Logged convergence.type and
convergence.rate alongside steady_state.reached, and confirmed a
convergence.type = "stalled" is read as a stalled solver (not steady
state), and a still-decreasing residual with steady_state.reached = false
is not reported as a failure.
Checked conservation against a tolerance. Recorded the conserved
integral (derived_quantities.py --quantity mass / integral, or
volume_fraction) at the first and last timestep and confirmed the drift
is within the documented tolerance for the dynamics (≈0 for Cahn-Hilliard
conserved order parameter; expected to change for Allen-Cahn).
Confirmed grid spacing is physical. Recorded the spacing block
(dx/dy/dz) echoed by derived_quantities.py and verified it came
from explicit dx/dy or the correct Lx/(nx-1) derivation — and that no
WARNING: explicit dx ... inconsistent with Lx line was emitted to stderr.
Verified no non-finite values corrupted the result. Confirmed
derived-quantity scripts did not raise Field contains non-finite value
(NaN/Inf) and that reported min/max are physically plausible (e.g. an
order parameter stays within its expected bounds).
Qualified comparison error against the documented bands. Recorded the
comparison_tool.py metric value (e.g. l2_error) and mapped it to the
agreement band in this skill (<1% excellent ... >10% poor, investigate),
confirming the simulation and reference were aligned/interpolated onto the
same axis first.
Common pitfalls & rationalizations
Tempting shortcut
Why it's wrong / what to do
"steady_state.reached = false, so the solver didn't converge."
The relative steady-state test asks "has it stopped changing?", which is false for a still-decreasing residual. For residuals use --absolute-threshold <tol> and read convergence_threshold.reached; reconcile with convergence.type/rate.
"The residual plateaued, so it reached steady state."
A flat residual is a stalled solver (convergence.type = "stalled", rate > 0.99), not convergence. Confirm the plateau value is actually at/below tolerance before calling it converged.
"I'll just extract temperature / assume a 256×256 grid."
Field names and shape are not guaranteed. Run field_extractor.py --list --json first and use only the fields and shape that the file actually reports.
"Two grids/runs agree, so the result is mesh-independent."
comparison_tool.py on two fields only bounds their difference; it does not establish the asymptotic range. Use >=3 resolutions to estimate an observed order before claiming mesh independence.
"Volume fraction looks stable, so mass is conserved."
A stable volume_fraction (a thresholded count) is not the conserved integral. Check --quantity integral/mass drift between first and last timestep against tolerance — and only expect ≈0 drift for conserved (Cahn-Hilliard) dynamics.
"Default dx=1.0 is fine for the derived quantity."
When no spacing is in the file the scripts fall back to dx=dy=dz=1.0, so any length/area/flux/integral is in grid units, not physical units. Supply --dx/--dy (or Lx/Ly in the file) and verify the echoed spacing block.
"It ran and emitted JSON, so the numbers are valid."
Completion is not correctness. Verify conservation drift, the convergence verdict, finite values, and the comparison error band before reporting.
Security
Input Validation
User-provided field names are validated against [a-zA-Z_][a-zA-Z0-9_.-]* to prevent injection via crafted field names
statistical_analyzer.py validates --region conditions against a strict allowlist before use: only the coordinate variables x, y, z compared (< <= > >= == !=) against numeric literals and joined by and/or are accepted; anything else exits with code 2. The parsed condition is applied as a real coordinate mask (no eval/exec), so the reported statistics describe the requested region
profile_extractor.py validates the field name against the same pattern and point coordinates as finite numbers with max 3 dimensions
--metric values in comparison_tool.py are validated against a fixed allowlist (l1_error, l2_error, linf_error, rmse, mae, max_difference, correlation, r_squared); unknown metrics return an error
--sections in report_generator.py are validated against the known section names (summary, statistics, convergence, validation, files, parameters, all); unknown sections exit with code 2
--bins (statistical_analyzer), --points (profile_extractor), and --window (time_series_analyzer) are validated as positive integers with upper bounds; out-of-range values exit with code 2
File Access
All JSON and CSV loading functions reject files exceeding 500 MB before parsing
Loaded JSON files must have an object (dict) as root element
report_generator.py caps directory listing at 10,000 entries to prevent resource exhaustion
Scripts read user-specified simulation output files (JSON, CSV) but do not traverse directories beyond what is explicitly provided
Output goes to stdout (JSON) unless the agent uses Write to save reports
Tool Restrictions
Read: Used to inspect script source, references, and simulation output files
Write: Used to save analysis results, comparison reports, or generated summaries; writes are scoped to the user's working directory
Grep/Glob: Used to locate simulation output files and search references
The skill's allowed-tools excludes Bash to prevent the agent from executing arbitrary commands when processing untrusted simulation output files
Safety Measures
No eval(), exec(), or dynamic code generation — region parsing uses regex matching, never code evaluation
All subprocess calls use explicit argument lists (no shell=True)
Reduced tool surface (no Bash) limits the agent to read/write operations only
Field names and region expressions are sanitized before use to prevent injection
references/comparison_metrics.md - Error metrics and interpretation
Requirements
Python 3.10+
NumPy (for numerical operations)
No other external dependencies for core functionality
Version History
See CHANGELOG.md for the authoritative record.
v1.1.3 (2026-06-24): Added a "Verification checklist" (evidence-based, tied to
the scripts' real output keys: field listing, residual-vs-physical convergence,
signal reconciliation, conservation drift, grid-spacing sanity, non-finite
guards, comparison error bands) and a "Common pitfalls & rationalizations"
table before the Security section. Documentation only; no script behavior change.
v1.1.2 (2026-06-23): Made the eval suite self-contained and discriminating —
copied the real fixtures into evals/files/ (only phi/concentration fields
on a 10x10 grid; residual series ending at 5e-6), rewrote every eval prompt to
reference those exact files, and added deterministic script_checks pinning the
verified script outputs (including the correct verdict that the 1e-6 absolute
residual threshold is NOT reached). Added guidance to read field names from the
output file rather than assuming them.
v1.1.1 (2026-06-23): Implemented real coordinate-based --region filtering in
statistical_analyzer.py; fixed report_generator.py to read nested
fields.*.values output; gave explicit dx/dy/dz precedence in
derived_quantities.py grid spacing; added an --absolute-threshold
convergence mode and residual-vs-physical interpretation guidance; corrected
the Output Format example and version metadata; added --bins/--window
bounds validation.
1---2name: post-processing3description: Extract, analyze, and summarize simulation output data — pull spatial fields at specific timesteps, compute time-series trends and detect steady state, extract line profiles through the domain, generate statistical summaries and distributions, calculate derived quantities (gradients, fluxes, volume fractions, interface area), compare results against analytical solutions or experimental data, and produce automated analysis reports. Use when interpreting finished simulation results, checking mass or energy conservation, comparing two runs or meshes, extracting interface profiles from phase-field output, or preparing publication-quality analysis, even if the user only says "what do my results look like" or "did my simulation reach steady state."4---56# Post-Processing Skill78Analyze and extract meaningful information from simulation output data.910## Goal1112Transform raw simulation output into actionable insights through field extraction, statistical analysis, derived quantities, visualizations, and comparison with reference data.1314## Inputs to Gather1516Before running post-processing scripts, collect:17181. **Output Data Location**19 - Path to simulation output files (JSON, CSV, HDF5, VTK)20 - Time step/snapshot indices of interest21 - Field names to extract2223 > **Read field names from the file, never assume them.** Before extracting,24 > open the output file (or run `field_extractor.py --input <file> --list25 > --json`) and use only the field names that actually appear under `fields`.26 > Do not invent fields such as `temperature` if they are not present, and do27 > not assume a grid size — read the real `shape`/`count` from the data.28292. **Analysis Type**30 - Field extraction (spatial data at specific times)31 - Time series (temporal evolution of quantities)32 - Line profiles (1D cuts through domain)33 - Statistical summary (mean, std, distributions)34 - Derived quantities (gradients, integrals, fluxes)35 - Comparison to reference data36373. **Output Requirements**38 - Output format (JSON, CSV, tabular)39 - Visualization needs40 - Report format4142## Scripts4344| Script | Purpose | Key Inputs |45|--------|---------|------------|46| `field_extractor.py` | Extract field data from output files | --input, --field, --timestep |47| `time_series_analyzer.py` | Analyze temporal evolution | --input, --quantity, --window |48| `profile_extractor.py` | Extract line profiles | --input, --field, --start, --end |49| `statistical_analyzer.py` | Compute field statistics | --input, --field, --region |50| `derived_quantities.py` | Calculate derived quantities | --input, --quantity, --params |51| `comparison_tool.py` | Compare to reference data | --simulation, --reference, --metric |52| `report_generator.py` | Generate summary reports | --input, --template, --output |5354## Workflow5556### 1. Data Inventory5758First, understand what data is available:5960```bash61# List available fields and timesteps62python scripts/field_extractor.py --input results/ --list --json63```6465### 2. Field Extraction6667Extract spatial field data at specific timesteps:6869```bash70# Extract concentration field at timestep 10071python scripts/field_extractor.py \72 --input results/field_0100.json \73 --field concentration \74 --json7576# Extract multiple fields77python scripts/field_extractor.py \78 --input results/field_0100.json \79 --field "phi,concentration,temperature" \80 --json81```8283### 3. Time Series Analysis8485Analyze temporal evolution of quantities:8687```bash88# Extract total energy vs time89python scripts/time_series_analyzer.py \90 --input results/history.json \91 --quantity total_energy \92 --json9394# Compute moving average with window95python scripts/time_series_analyzer.py \96 --input results/history.json \97 --quantity mass \98 --window 10 \99 --json100101# Detect steady state (relative-variation test; best for physical quantities)102python scripts/time_series_analyzer.py \103 --input results/history.json \104 --quantity residual \105 --detect-steady-state \106 --tolerance 1e-6 \107 --json108109# Convergence by absolute threshold (physically correct test for residuals)110python scripts/time_series_analyzer.py \111 --input results/history.json \112 --quantity residual \113 --absolute-threshold 1e-6 \114 --json115```116117### 4. Line Profile Extraction118119Extract 1D profiles through the domain:120121```bash122# Extract profile along x-axis at y=0.5123python scripts/profile_extractor.py \124 --input results/field_0100.json \125 --field concentration \126 --start "0,0.5,0" \127 --end "1,0.5,0" \128 --points 100 \129 --json130131# Interface profile (through center)132python scripts/profile_extractor.py \133 --input results/field_0100.json \134 --field phi \135 --axis x \136 --slice-position 0.5 \137 --json138```139140### 5. Statistical Analysis141142Compute statistics over field data:143144```bash145# Global statistics146python scripts/statistical_analyzer.py \147 --input results/field_0100.json \148 --field concentration \149 --json150151# Statistics in a specific spatial region (1D/2D fields only).152# Coordinates are derived from the field shape and grid spacing153# (explicit dx/dy, or Lx/Ly via dx = Lx/(nx-1)); only the variables154# x, y, z compared against numbers, joined by and/or, are allowed.155python scripts/statistical_analyzer.py \156 --input results/field_0100.json \157 --field phi \158 --region "x>0.3 and x<0.7" \159 --json160161# Distribution analysis162python scripts/statistical_analyzer.py \163 --input results/field_0100.json \164 --field phi \165 --histogram \166 --bins 50 \167 --json168```169170### 6. Derived Quantities171172Calculate physical quantities from raw data:173174```bash175# Compute interface area176python scripts/derived_quantities.py \177 --input results/field_0100.json \178 --quantity interface_area \179 --threshold 0.5 \180 --json181182# Compute gradient magnitude183python scripts/derived_quantities.py \184 --input results/field_0100.json \185 --quantity gradient_magnitude \186 --field phi \187 --json188189# Compute volume fractions190python scripts/derived_quantities.py \191 --input results/field_0100.json \192 --quantity volume_fraction \193 --field phi \194 --threshold 0.5 \195 --json196197# Compute flux through boundary198python scripts/derived_quantities.py \199 --input results/field_0100.json \200 --quantity boundary_flux \201 --field concentration \202 --boundary "x=0" \203 --json204```205206### 7. Comparison with Reference207208Compare simulation results to reference data:209210```bash211# Compare to analytical solution212python scripts/comparison_tool.py \213 --simulation results/profile.json \214 --reference reference/analytical.json \215 --metric l2_error \216 --json217218# Compare to experimental data219python scripts/comparison_tool.py \220 --simulation results/history.json \221 --reference experimental_data.csv \222 --metric rmse \223 --interpolate \224 --json225226# Compare two simulations227python scripts/comparison_tool.py \228 --simulation results_fine/field.json \229 --reference results_coarse/field.json \230 --metric max_difference \231 --json232```233234### 8. Report Generation235236Generate automated reports:237238```bash239# Generate summary report240python scripts/report_generator.py \241 --input results/ \242 --output report.json \243 --json244245# Generate with specific sections246python scripts/report_generator.py \247 --input results/ \248 --sections "summary,statistics,convergence" \249 --output report.json \250 --json251```252253## Typical Post-Processing Pipeline254255For a complete simulation analysis:256257```bash258python scripts/field_extractor.py --input results/ --list --json # 1. inventory259python scripts/statistical_analyzer.py --input results/field_final.json --field phi --json # 2. final-state stats260python scripts/time_series_analyzer.py --input results/history.json --quantity residual --detect-steady-state --json # 3. convergence261python scripts/derived_quantities.py --input results/field_final.json --quantity volume_fraction --field phi --json # 4. derived quantities262python scripts/comparison_tool.py --simulation results/profile.json --reference benchmark/expected.json --metric l2_error --json # 5. compare to reference263python scripts/report_generator.py --input results/ --output analysis_report.json --json # 6. summary report264```265266## Interpretation Guidelines267268### Time Series Analysis269270Interpret convergence differently depending on the quantity type, because the271two signals the analyzer reports (`convergence.{rate,type}` and272`steady_state.reached`) answer different questions and can legitimately273disagree.274275**Residual / error quantities** (e.g. `residual`, `error`):276- Judge convergence by **absolute magnitude vs a tolerance**: a residual at or277 below the target tolerance (e.g. `1e-6`) is converged. Use278 `--absolute-threshold <tol>` to get the `convergence_threshold` block, which279 is the physically correct test for residuals.280- The relative `--detect-steady-state` test answers "has the residual stopped281 changing?", not "has it converged?". For a still-decreasing residual,282 `steady_state.reached = false` is **expected and not a failure**.283- A **plateau** of a residual means a **stalled** solver284 (`convergence.type = "stalled"`), not steady state.285- **Reconcile the signals**: if `convergence.type` is `fast`/`linear` and the286 final residual is small (or `convergence_threshold.reached = true`), report287 the run as **converged** even when `steady_state.reached = false`.288289**Physical quantities** (e.g. `energy`, `volume_fraction`, `mass`,290`interface_area`):291- **Monotonic decrease** in energy: system approaching equilibrium.292- **Plateau** (`steady_state.reached = true`): steady state reached.293- **Oscillations**: may indicate the time step is too large.294- **Sudden jumps**: possible numerical instability.295296### Statistical Analysis297- **Bimodal distribution** of order parameter: Two-phase mixture298- **High variance**: Heterogeneous microstructure299- **Skewed distribution**: Asymmetric phase fractions300301### Comparison Metrics302| Metric | Interpretation |303|--------|----------------|304| L2 error < 1% | Excellent agreement |305| L2 error 1-5% | Good agreement |306| L2 error 5-10% | Moderate agreement |307| L2 error > 10% | Poor agreement, investigate |308309## Output Format310311All scripts support the `--json` flag for machine-readable output. Most312scripts emit a **flat** top-level object whose keys depend on the script. For313example, `field_extractor.py --include-data` on a single field emits:314315```json316{317 "field": "concentration",318 "found": true,319 "data": [[0.1, 0.9], [0.3, 0.6]],320 "shape": [2, 2],321 "min": 0.1,322 "max": 0.9,323 "mean": 0.475,324 "count": 4,325 "source_file": "results/field_0100.json",326 "timestep_info": {"timestep": 100, "time": 1.5}327}328```329330Notes on envelope shapes (they are not uniform across scripts):331332- `field_extractor.py`, `statistical_analyzer.py`, `time_series_analyzer.py`,333 `profile_extractor.py`, and `comparison_tool.py` emit a flat object with a334 `source_file` key plus script-specific result keys.335- `derived_quantities.py` wraps its payload in an `{ "inputs": {...},336 "results": {...} }` envelope.337- `report_generator.py` emits top-level `report_version` and `generator`338 keys plus the requested report sections.339340No script emits top-level `script`, `version`, or `input_file` keys, and341field statistics (`min`/`max`/`mean`/`count`) appear at the top level, not342nested under a `data` object.343344## Verification checklist345346Before trusting or reporting a post-processing result, produce and record the347concrete evidence below (tied to this skill's scripts and output keys):348349- [ ] **Listed the real fields first.** Ran `field_extractor.py --list --json`350 and recorded the actual `fields` names and `shape`; every later `--field`351 argument is one that appears in that list (no assumed `temperature`, no352 guessed grid size).353- [ ] **Used the right convergence test for the quantity type.** For a residual/error,354 recorded `convergence_threshold.reached` and `final_value` from355 `--absolute-threshold <tol>` (the `|x_final| <= tol` test) rather than356 relying on `steady_state.reached`; for a physical quantity, recorded357 `steady_state.{reached,relative_variation,value}` from `--detect-steady-state`.358- [ ] **Reconciled the two convergence signals.** Logged `convergence.type` and359 `convergence.rate` alongside `steady_state.reached`, and confirmed a360 `convergence.type = "stalled"` is read as a stalled solver (not steady361 state), and a still-decreasing residual with `steady_state.reached = false`362 is not reported as a failure.363- [ ] **Checked conservation against a tolerance.** Recorded the conserved364 integral (`derived_quantities.py --quantity mass` / `integral`, or365 `volume_fraction`) at the first and last timestep and confirmed the drift366 is within the documented tolerance for the dynamics (≈0 for Cahn-Hilliard367 conserved order parameter; expected to change for Allen-Cahn).368- [ ] **Confirmed grid spacing is physical.** Recorded the `spacing` block369 (`dx`/`dy`/`dz`) echoed by `derived_quantities.py` and verified it came370 from explicit `dx`/`dy` or the correct `Lx/(nx-1)` derivation — and that no371 `WARNING: explicit dx ... inconsistent with Lx` line was emitted to stderr.372- [ ] **Verified no non-finite values corrupted the result.** Confirmed373 derived-quantity scripts did not raise `Field contains non-finite value`374 (NaN/Inf) and that reported `min`/`max` are physically plausible (e.g. an375 order parameter stays within its expected bounds).376- [ ] **Qualified comparison error against the documented bands.** Recorded the377 `comparison_tool.py` metric value (e.g. `l2_error`) and mapped it to the378 agreement band in this skill (<1% excellent ... >10% poor, investigate),379 confirming the simulation and reference were aligned/interpolated onto the380 same axis first.381382## Common pitfalls & rationalizations383384| Tempting shortcut | Why it's wrong / what to do |385|-------------------|-----------------------------|386| "`steady_state.reached = false`, so the solver didn't converge." | The relative steady-state test asks "has it stopped changing?", which is false for a still-decreasing residual. For residuals use `--absolute-threshold <tol>` and read `convergence_threshold.reached`; reconcile with `convergence.type`/`rate`. |387| "The residual plateaued, so it reached steady state." | A flat residual is a *stalled* solver (`convergence.type = "stalled"`, rate > 0.99), not convergence. Confirm the plateau value is actually at/below tolerance before calling it converged. |388| "I'll just extract `temperature` / assume a 256×256 grid." | Field names and shape are not guaranteed. Run `field_extractor.py --list --json` first and use only the `fields` and `shape` that the file actually reports. |389| "Two grids/runs agree, so the result is mesh-independent." | `comparison_tool.py` on two fields only bounds their difference; it does not establish the asymptotic range. Use >=3 resolutions to estimate an observed order before claiming mesh independence. |390| "Volume fraction looks stable, so mass is conserved." | A stable `volume_fraction` (a thresholded count) is not the conserved integral. Check `--quantity integral`/`mass` drift between first and last timestep against tolerance — and only expect ≈0 drift for conserved (Cahn-Hilliard) dynamics. |391| "Default `dx=1.0` is fine for the derived quantity." | When no spacing is in the file the scripts fall back to `dx=dy=dz=1.0`, so any length/area/flux/integral is in grid units, not physical units. Supply `--dx`/`--dy` (or `Lx`/`Ly` in the file) and verify the echoed `spacing` block. |392| "It ran and emitted JSON, so the numbers are valid." | Completion is not correctness. Verify conservation drift, the convergence verdict, finite values, and the comparison error band before reporting. |393394## Security395396### Input Validation397- User-provided field names are validated against `[a-zA-Z_][a-zA-Z0-9_.-]*` to prevent injection via crafted field names398- `statistical_analyzer.py` validates `--region` conditions against a strict allowlist before use: only the coordinate variables `x`, `y`, `z` compared (`< <= > >= == !=`) against numeric literals and joined by `and`/`or` are accepted; anything else exits with code 2. The parsed condition is applied as a real coordinate mask (no `eval`/`exec`), so the reported statistics describe the requested region399- `profile_extractor.py` validates the field name against the same pattern and point coordinates as finite numbers with max 3 dimensions400- `--metric` values in `comparison_tool.py` are validated against a fixed allowlist (`l1_error`, `l2_error`, `linf_error`, `rmse`, `mae`, `max_difference`, `correlation`, `r_squared`); unknown metrics return an error401- `--sections` in `report_generator.py` are validated against the known section names (`summary`, `statistics`, `convergence`, `validation`, `files`, `parameters`, `all`); unknown sections exit with code 2402- `--bins` (statistical_analyzer), `--points` (profile_extractor), and `--window` (time_series_analyzer) are validated as positive integers with upper bounds; out-of-range values exit with code 2403404### File Access405- All JSON and CSV loading functions reject files exceeding 500 MB before parsing406- Loaded JSON files must have an object (dict) as root element407- `report_generator.py` caps directory listing at 10,000 entries to prevent resource exhaustion408- Scripts read user-specified simulation output files (JSON, CSV) but do not traverse directories beyond what is explicitly provided409- Output goes to stdout (JSON) unless the agent uses Write to save reports410411### Tool Restrictions412- **Read**: Used to inspect script source, references, and simulation output files413- **Write**: Used to save analysis results, comparison reports, or generated summaries; writes are scoped to the user's working directory414- **Grep/Glob**: Used to locate simulation output files and search references415- The skill's `allowed-tools` excludes `Bash` to prevent the agent from executing arbitrary commands when processing untrusted simulation output files416417### Safety Measures418- No `eval()`, `exec()`, or dynamic code generation — region parsing uses regex matching, never code evaluation419- All subprocess calls use explicit argument lists (no `shell=True`)420- Reduced tool surface (no Bash) limits the agent to read/write operations only421- Field names and region expressions are sanitized before use to prevent injection422423## References424425For detailed information, see:426427- `references/data_formats.md` - Supported input/output formats428- `references/statistical_methods.md` - Statistical analysis methods429- `references/derived_quantities_guide.md` - Physical quantity calculations430- `references/comparison_metrics.md` - Error metrics and interpretation431432## Requirements433434- Python 3.10+435- NumPy (for numerical operations)436- No other external dependencies for core functionality437438## Version History439440See `CHANGELOG.md` for the authoritative record.441442- v1.1.3 (2026-06-24): Added a "Verification checklist" (evidence-based, tied to443 the scripts' real output keys: field listing, residual-vs-physical convergence,444 signal reconciliation, conservation drift, grid-spacing sanity, non-finite445 guards, comparison error bands) and a "Common pitfalls & rationalizations"446 table before the Security section. Documentation only; no script behavior change.447- v1.1.2 (2026-06-23): Made the eval suite self-contained and discriminating —448 copied the real fixtures into `evals/files/` (only `phi`/`concentration` fields449 on a 10x10 grid; residual series ending at 5e-6), rewrote every eval prompt to450 reference those exact files, and added deterministic `script_checks` pinning the451 verified script outputs (including the correct verdict that the 1e-6 absolute452 residual threshold is NOT reached). Added guidance to read field names from the453 output file rather than assuming them.454- v1.1.1 (2026-06-23): Implemented real coordinate-based `--region` filtering in455 `statistical_analyzer.py`; fixed `report_generator.py` to read nested456 `fields.*.values` output; gave explicit `dx`/`dy`/`dz` precedence in457 `derived_quantities.py` grid spacing; added an `--absolute-threshold`458 convergence mode and residual-vs-physical interpretation guidance; corrected459 the Output Format example and version metadata; added `--bins`/`--window`460 bounds validation.461- v1.1.0 (2026-03-26): Optimized description, evaluation suite, security review,462 standardized metadata, CHANGELOG.463- v1.0.0 (2026-02-25): Initial release.
Run npx skillmds@latest add heshamfs/post-processing in your terminal (requires Node.js), paste this page's agent-chat prompt into Claude, Cursor, or any MCP-connected agent, or download the SKILL.md file and copy it into your agent's skills directory.
Extract, analyze, and summarize simulation output data — pull spatial fields at specific timesteps, compute time-series trends and detect steady state, extract line profiles through the domain, generate statistical summaries and distributions, calculate derived quantities (gradients, fluxes, volume fractions, interface area), compare results against analytical solutions or experimental data, and produce automated analysis reports. Use when interpreting finished simulation results, checking mass or energy conservation, comparing two runs or meshes, extracting interface profiles from phase-field output, or preparing publication-quality analysis, even if the user only says "what do my results look like" or "did my simulation reach steady state." It is listed under Research & Search on SkillMD.
This skill has not completed SkillMD's automated safety review yet. Capability flags: executes scripts. SkillMD never runs a skill's scripts for you; review the SKILL.md before installing.
This skill is tagged as working with Claude Code, Claude.ai, OpenAI Codex. SKILL.md is an open format, so most agents that read a skills directory can load it too.
Yes. Installing skills from SkillMD is free, and the skill stays under its author's original license.
HeshamFS (@heshamfs) published this skill. Their other Agent Skills are listed on their SkillMD profile.