Find a simulation behavior boundary. Preserve it as a replay. Turn it into a test.
Maintaining a simulation is risky when a tiny parameter or initial-condition change can cross a qualitative boundary while ordinary numeric assertions still look plausible. PhaseProbe runs a bounded, deterministic search, records exactly what it tested, and emits an offline report plus an executable pytest regression.
$ python -m pip install .
$ phaseprobe scan --example logistic
QUALITATIVE TRANSITION FOUND
Model: logistic-map
Search dimension: r
Baseline regime: period-2
Changed regime: period-4
Replay: .phaseprobe/runs/<run-id>/replay.json
$ phaseprobe generate-test .phaseprobe/runs/<run-id>/replay.json
$ python -m pytest -q tests/generated
1 passed
No API key, LLM, GPU, Docker, account, telemetry, network connection, or hosted service is required at runtime. PhaseProbe 0.1.0 has no third-party runtime dependencies.
Requires Python 3.10 or newer on Windows or Linux.
python -m pip install .
phaseprobe scan --example logistic
phaseprobe replay .phaseprobe/runs/<run-id>/replay.json
phaseprobe generate-test .phaseprobe/runs/<run-id>/replay.json
python -m pytest -q tests/generated
phaseprobe report .phaseprobe/runs/<run-id>
Replace <run-id>
with the directory printed by scan
. The scan returns 0
when it successfully finds a transition. Add --fail-on-finding
only when a finding should fail CI.
The quick-start evidence is empirical: the built-in classifier finds a finite-time period-2/period-4 bracket for the logistic map, performs bounded binary refinement, repeats both endpoints, saves trace hashes and a versioned fixture, and generates a fixed pytest template. It does not claim an exact bifurcation point.
| command | job | normal success |
|---|---|---|
scan |
||
| Bounded one-dimensional parameter scan, adjacent class-change detection, and stable bracket refinement | Finding or no finding, exit 0 |
|
perturb |
||
| Baseline/perturbed twin runs over bounded initial-state changes | Finding or no finding, exit 0 |
|
check |
||
| Execute a declared configuration policy for CI | Exit 1 only when policy fails |
|
replay |
||
| Validate fixture integrity and re-execute model, parameters, seed, initial state, tolerances, and retention | Matching class and exact retained trace hashes | |
generate-test |
||
| Validate and copy a fixture into a non-extensible pytest template | Executable test under tests/generated/ |
|
report |
||
| Regenerate terminal, versioned JSON, and self-contained offline HTML evidence | Local report files |
Common options:
phaseprobe scan --config examples/configs/logistic-scan.json
phaseprobe perturb --example lorenz --json
phaseprobe check --example predator-prey
phaseprobe scan --example logistic --fail-on-finding
Exit codes are stable: 0
completed, 1
declared policy or explicit --fail-on-finding
, 2
invalid input/configuration, 3
numerical failure, and 4
internal PhaseProbe defect.
| example | command | positive evidence | negative control | direct technical source |
|---|---|---|---|---|
| Logistic map | phaseprobe scan --example logistic |
|||
| Finite-time period-2 to period-4 classification bracket | --example logistic-negative stays period-2 over its declared range |
|||
phaseprobe perturb --example lorenz
Lorenz, 1963phaseprobe check --example predator-prey
Lotka, 1920phaseprobe perturb --example toggle
Gardner, Cantor & Collins, 2000Each configuration records the seed, fixed integration/iteration settings, tolerances, burn-in, observation window, classification rule, refinement rule, invalid-state policy, and trace cap. See examples/README.md for equations and interpretation.
On 2026-08-01 with CPython 3.12.13 on Windows, the instrumented logistic quick-start scan completed in 10.114 seconds with a 34.81 MiB peak process-tree working set. All eight positive/negative example commands completed in 12.238 seconds. The final 38-test suite with branch coverage completed in 64.86 seconds at 87.92% coverage. These are observations from one local run, not cross-machine performance claims; the generated transcript is in assets/demo-session.txt.
Every execution is bounded under .phaseprobe/runs/<run-id>/
:
run.json complete versioned evidence
findings.json compact finding or negative result
replay.json schema-versioned integrity-protected fixture
trace.jsonl capped baseline/changed retained points
report.html self-contained offline report
manifest.json sizes and SHA-256 hashes
Model names used for generated test paths are sanitized. The generated source comes from a fixed template; configuration strings never become executable Python.
| existing category | established strength | PhaseProbe’s narrower job |
|---|---|---|
| Solvers such as SciPy | Integrate differential equations with mature numerical methods | Consume an adapter’s trajectories and preserve a discovered qualitative boundary as test evidence |
| Property-based testing such as Hypothesis | Generate and shrink broad input domains | Search declared simulation dimensions with model-specific observables and classes |
| Bifurcation/attractor tools such as AUTO, PyDSTool, and Attractors.jl | Deep continuation, bifurcation, attractor, and basin analysis | Lightweight black-box numerical brackets plus replay and pytest materialization |
| Sensitivity tools such as SALib | Quantify input contributions to output variation | Find a reproducible qualitative predicate change |
| Simulation environments such as Mesa, NetLogo, cadCAD, and Golly | Build, run, and explore models | Test adapters without becoming another simulation environment |
The evidence-backed audit is in PRIOR_ART.md.
Adapters implement a small typed protocol:
class ModelAdapter(Protocol):
name: str
identity: str
dimensions: tuple[str, ...]
def initial_state(self, config, seed): ...
def step(self, state, parameters, dt): ...
def observe(self, state): ...
def classify(self, trace, tolerances): ...
def invariants(self, trace, parameters, tolerances): ...
The explicit serializable state tuple makes bounded perturbation, invalid-value detection, trace hashing, and replay straightforward. See ARCHITECTURE.md for extension guidance.
PhaseProbe uses these terms deliberately:
- finite-time trajectory divergence: measured separation over the recorded window;
- sensitive-dependence evidence: repeatable finite-time divergence under a declared small perturbation, not by itself a proof of chaos;
- numerical instability: behavior attributable to the numerical method or step size;
- invariant violation: a declared conservation or boundedness rule failed;
- qualitative regime change: the adapter’s declared classifier changed;
- bifurcation evidence: a numerical class-change bracket, not an exact bifurcation point;
- invalid integration: NaN, infinity, overflow, state-shape mismatch, or hard bound breach;
- solver failure: the step method could not advance a valid state.
The Lorenz example reports finite-time divergence rate
; it does not compute or claim a Lyapunov exponent. “Smallest” always means smallest found within the declared bounded search. Read SCIENTIFIC_METHODS.md and LIMITATIONS.md.
python -m pip install -e ".[dev]"
python -m ruff format --check .
python -m ruff check .
python -m mypy src tests
python -m pytest --cov=phaseprobe
python -m build
python scripts/check_links.py
python scripts/hygiene.py
See CONTRIBUTING.md and the concrete good-first-issue templates in .github/ISSUE_TEMPLATE/
.
Apache-2.0. Copyright 2026 Ali. Citation metadata is in CITATION.cff.