Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 35 additions & 9 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ permissions:
contents: read

jobs:
quality-and-tests:
name: ${{ matrix.os }} / Python ${{ matrix.python }}
core-only:
name: core / ${{ matrix.os }} / Python ${{ matrix.python }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
Expand All @@ -26,12 +26,32 @@ jobs:
cache: pip
- run: python -m pip install --upgrade pip
- run: python -m pip install -e ".[dev]"
- run: python -m pytest -m "not scipy"
- run: python -m phaseprobe --version
- run: python -m phaseprobe scan --example logistic-negative

scipy-extra:
name: scipy / ${{ matrix.os }} / Python ${{ matrix.python }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest]
python: ["3.10", "3.12", "3.14"]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python }}
cache: pip
- run: python -m pip install --upgrade pip
- run: python -m pip install -e ".[dev,scipy]"
- run: python -m ruff format --check .
- run: python -m ruff check .
- run: python -m mypy src tests
- run: python -m pytest --cov=phaseprobe --cov-report=term
- run: python -m phaseprobe --version
- run: python -m phaseprobe scan --example logistic-negative
- run: python -m phaseprobe perturb --config examples/scipy/lorenz-negative.json
- run: python -m phaseprobe check --config examples/scipy/predator-prey.json

package-and-hygiene:
runs-on: ubuntu-latest
Expand All @@ -41,12 +61,18 @@ jobs:
with:
python-version: "3.12"
cache: pip
- run: python -m pip install --upgrade pip build
- run: python -m pip install --upgrade pip build twine
- run: python -m build
- run: python -m venv .packed-smoke
- run: .packed-smoke/bin/python -m pip install dist/*.whl
- run: .packed-smoke/bin/python -m phaseprobe --version
- run: .packed-smoke/bin/python -m phaseprobe scan --example logistic-negative
- run: python -m twine check dist/*
- run: python scripts/audit_package.py
- run: python -m venv .cache/packed-smoke
- run: .cache/packed-smoke/bin/python -m pip install dist/*.whl
- run: .cache/packed-smoke/bin/python -m phaseprobe --version
- run: .cache/packed-smoke/bin/python -m phaseprobe scan --example logistic-negative
- run: .cache/packed-smoke/bin/python -c "import importlib.util; assert importlib.util.find_spec('numpy') is None; assert importlib.util.find_spec('scipy') is None"
- run: python -m venv .cache/scipy-smoke
- run: .cache/scipy-smoke/bin/python -m pip install "$(find dist -name '*.whl')[scipy]"
- run: .cache/scipy-smoke/bin/python -m phaseprobe check --config examples/scipy/predator-prey.json
- run: python scripts/check_links.py
- run: python scripts/hygiene.py
- uses: actions/upload-artifact@v4
Expand Down
15 changes: 10 additions & 5 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,17 @@ jobs:
with:
python-version: "3.12"
cache: pip
- run: python -m pip install --upgrade pip build
- run: python -m pip install --upgrade pip build twine
- run: python -m build
- run: python -m venv .tag-smoke
- run: .tag-smoke/bin/python -m pip install dist/*.whl
- run: .tag-smoke/bin/python -m phaseprobe --version
- run: .tag-smoke/bin/python -m phaseprobe scan --example logistic
- run: python -m twine check dist/*
- run: python scripts/audit_package.py
- run: python -m venv .cache/tag-core-smoke
- run: .cache/tag-core-smoke/bin/python -m pip install dist/*.whl
- run: .cache/tag-core-smoke/bin/python -m phaseprobe --version
- run: .cache/tag-core-smoke/bin/python -m phaseprobe scan --example logistic
- run: python -m venv .cache/tag-scipy-smoke
- run: .cache/tag-scipy-smoke/bin/python -m pip install "$(find dist -name '*.whl')[scipy]"
- run: .cache/tag-scipy-smoke/bin/python -m phaseprobe check --config examples/scipy/predator-prey.json
- run: python scripts/hygiene.py
- uses: actions/upload-artifact@v4
with:
Expand Down
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@
- Required validation: `python -m ruff format --check .`, `python -m ruff check .`, `python -m mypy src tests`, `python -m pytest`, `python -m build`, packed-install smoke test, quick start, examples, replay, generated-test execution, privacy/secret/large-file scans, and clean Git status.
- Do not fabricate tests, performance, scientific evidence, benchmark results, users, or adoption claims.
- Do not publish to PyPI without separate authorization.
- Release only after all local gates pass; then use the authenticated `aliengineering-byte` GitHub account, pass CI, tag an annotated `v0.1.0`, create the release, and verify a fresh unauthenticated clone.
- Release only after all local gates pass; then use the authenticated `aliengineering-byte` GitHub account, pass CI, create the intended new annotated semantic-version tag and release, and verify a fresh unauthenticated clone. Never rewrite a published tag.
54 changes: 41 additions & 13 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ PhaseProbe separates simulation semantics from search, evidence, and test materi
JSON config / built-in example
|
v
typed adapter ----> deterministic step + observe
typed adapter ----> fixed step OR whole trajectory
| |
v v
bounded engine ----> finite validation ----> capped trace + SHA-256
bounded engine ----> finite validation ----> capped trace + SHA-256 integrity
| |
| +---- scan / perturb / check policy
v
Expand All @@ -20,36 +20,64 @@ JSON config / built-in example

## Modules

- `config.py` validates schema `1.0`, loads packaged examples, and emits canonical JSON.
- `types.py` defines the public adapter protocol, state shape, trace point, and invariant result.
- `config.py` emits schema `2.0`, keeps schema `1.0` readable, validates explicit Python adapter
references without importing them, loads packaged examples, and emits canonical JSON.
- `types.py` defines the step-level `ModelAdapter`, trajectory-level `TrajectoryAdapter`, state
shape, trace point/trace, replay mode, and invariant result.
- `models/` contains four independent reference adapters. They are examples, not engine special cases.
- `engine.py` owns bounded execution, NaN/Inf/hard-limit checks, trace retention, scanning, perturbation, bracket refinement, repeatability confirmation, and CI policy evaluation.
- `adapters/scipy.py` optionally imports NumPy/SciPy and wraps only public `solve_ivp` behavior.
- `adapters/loader.py` imports a user-selected dotted module and calls its named factory only at
execution time.
- `engine.py` dispatches step versus trajectory execution and owns shared NaN/Inf/hard-limit
checks, bounded retention, scanning, perturbation, bracket refinement, repeatability
confirmation, and CI policy evaluation.
- `artifacts.py` creates one finite run directory and hashes each evidence file.
- `replay.py` verifies fixture integrity before re-executing exact model/config/seed/state/parameter inputs and comparing classifications plus retained trace hashes.
- `replay.py` reads v1 exact fixtures and emits v2 fixtures with explicit `exact` or `tolerance`
comparison, always after SHA-256 integrity validation.
- `generate.py` uses a fixed code template and sanitized names. It never evaluates configuration text.
- `reporting.py` renders terminal, JSON, and offline HTML with explicit limitations.
- `cli.py` maps the six public commands to stable exit codes.

## Adapter design
## Adapter design and dispatch

The engine deliberately does not require NumPy. A state is a tuple of finite floats; this keeps serialization and perturbation explicit. Downstream adapters may wrap larger simulators, but their `step` boundary must return a bounded tuple suitable for deterministic replay. Observations are scalar mappings and cannot carry arbitrary executable objects.
The engine deliberately does not require NumPy. A state is a tuple of finite floats; this keeps
serialization and perturbation explicit. Existing adapters implement `ModelAdapter.step`, and
their behavior is unchanged. Whole-trajectory solvers implement `TrajectoryAdapter.simulate` and
return `SimulationTrace`; the engine never manufactures a fake fixed-step loop around them.
Observations are scalar mappings and cannot carry arbitrary executable objects.

Resolution order is explicit: a Python API caller may supply an adapter instance; otherwise a
built-in model name resolves from the immutable registry; otherwise a schema-v2 `adapter` section
may name an absolute dotted module and factory. Loading that factory executes trusted user code.
Configuration validation alone checks syntax and does not import the module.

An adapter supplies scientific judgment: initial conditions, state advance, observables, qualitative classification, and invariants. The engine supplies operational judgment: search bounds, retention, failure containment, hashes, artifacts, and policy exits.

## Determinism boundary
## Determinism and replay boundary

PhaseProbe controls seeds, canonical configuration serialization, fixed command order, search
grids, state perturbations, trace retention, and artifact hashing. An external adapter remains
responsible for solver settings, thread behavior, native library versions, and hardware-sensitive
arithmetic.

PhaseProbe controls seeds, canonical configuration serialization, fixed command order, search grids, state perturbations, trace retention, and fixture hashing. An external adapter remains responsible for deterministic solver settings, thread behavior, native library versions, and hardware-sensitive arithmetic. Exact replay hashes intentionally expose drift; users may choose a classification-only policy in a future version, but v0.1.0 replay is strict.
Step adapters default to exact classification/model-identity/trace-hash replay. Adaptive SciPy
adapters require tolerance replay: the fixture carries declared state/observable/invariant/grid/
endpoint/event tolerances and expected solver success. The original trace hash remains as artifact
integrity evidence but is not required to match numerically across environments.

## Artifact safety

Trace points are capped per series. Run IDs combine UTC time and an evidence digest. Replay fixtures carry a schema version and SHA-256 over every unsigned field. Generated tests copy a validated fixture into `tests/generated/fixtures/` and contain only a sanitized identifier plus a fixed relative path.

## Extension checklist

1. Give the adapter a stable `identity` version.
1. Give the adapter a stable explicit `identity` version and serialize configuration separately
from callable code.
2. Make `initial_state` deterministic for the declared seed.
3. Return the same state dimension after every step.
4. Define classifier thresholds in configuration tolerances.
5. Distinguish mathematical invariants from diagnostic bounds in invariant details.
6. Add positive, negative-control, invalid-state, repeatability, and replay tests.
7. Cite a primary technical source for the model and document solver limitations.
6. Select `exact` only where byte-identical retained values are justified; otherwise declare a
complete tolerance policy.
7. Add positive, negative-control, invalid-state, repeatability, and replay tests.
8. Cite a primary technical source for the model and document solver limitations.
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,24 @@

All notable changes are documented here. PhaseProbe follows semantic versioning.

## 0.2.0 — 2026-08-02

- Added the backward-compatible `TrajectoryAdapter` protocol and shared engine dispatch; all v0.1
step adapters and examples continue unchanged.
- Added optional `phaseprobe[scipy]` support with a typed public `SolveIVPAdapter`, controlled
evaluation grids, scalar/vector tolerances, methods, maximum step, named events, observables,
classifiers, invariants, invalid-value checks, bounded retention, and solver/environment evidence.
- Added schema-v2 `exact` and `tolerance` replay while keeping schema-v1 exact fixtures readable.
Tolerance fixtures preserve state, observable, classifier, invariant threshold, grid, endpoint,
event, solver-success, version, and platform evidence without claiming byte-identical replay.
- Added genuine SciPy Lorenz finite-time divergence and predator–prey first-integral examples,
negative controls, refinement evidence, HTML evidence, and generated pytest fixtures.
- Added explicit safe-shape Python module/factory loading, core-without-SciPy tests, optional import
diagnostics, a Windows/Linux core/SciPy CI matrix, current SciPy audit, security/limitations docs,
demo assets, and an unposted upstream visibility proposal.
- Base installation remains dependency-free. NumPy and SciPy are optional; no PyPI publication was
performed.

## 0.1.0 — 2026-08-01

- Added `scan`, `perturb`, `check`, `replay`, `generate-test`, and `report` commands.
Expand Down
4 changes: 2 additions & 2 deletions CITATION.cff
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ title: "PhaseProbe"
type: software
authors:
- name: "Ali"
version: 0.1.0
date-released: 2026-08-01
version: 0.2.0
date-released: 2026-08-02
url: "https://github.com/aliengineering-byte/phaseprobe"
repository-code: "https://github.com/aliengineering-byte/phaseprobe"
license: Apache-2.0
Expand Down
13 changes: 12 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,14 @@ python -m mypy src tests
python -m pytest --cov=phaseprobe
```

Before a pull request, also run `python -m build`, install the wheel into a clean environment, execute the logistic quick start, run `python scripts/check_links.py`, and run `python scripts/hygiene.py`.
Install `.[dev,scipy]` to run the optional trajectory-adapter suite. Core-only jobs intentionally
install `.[dev]` and run tests marked `not scipy`; SciPy jobs run the full suite. The committed
`uv.lock` resolves the supported Python-dependent SciPy lines reproducibly.

Before a pull request, also run `python -m build`, `python -m twine check dist/*`,
`python scripts/audit_package.py`, install the base wheel and SciPy extra into separate clean
environments, execute both quick starts, run `python scripts/check_links.py`, and run
`python scripts/hygiene.py`.

## Scientific changes

Expand All @@ -27,6 +34,10 @@ New adapters or classifiers must include:
- replay and generated-test coverage;
- solver-refinement or convergence evidence where numerical integration matters.

Trajectory adapters must serialize explicit configuration without callable source, select an
honest exact/tolerance replay mode, and test solver failure, invalid values, retention, and any
supported event behavior.

Do not call finite-time divergence a Lyapunov exponent, a numerical bracket an exact bifurcation point, or a bounded search result globally minimal.

## Pull requests
Expand Down
21 changes: 21 additions & 0 deletions LIMITATIONS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,26 @@
# Limitations

## Optional SciPy adapter

- Adaptive `solve_ivp` trajectories may vary within declared tolerances across SciPy/NumPy
versions, compilers, operating systems, and architectures. Tolerance replay is not exact
deterministic replay.
- A controlled `t_eval` grid standardizes retained comparison points but does not control or expose
SciPy's internal adaptive step sequence.
- Event detection looks for sign changes across internal solver steps, so several zero crossings
inside one step can be missed. PhaseProbe records event configuration and returned event states;
it does not claim exhaustive root detection.
- PhaseProbe's portable state schema is real-valued. SciPy's method-specific complex-domain
support is intentionally outside `SolveIVPAdapter`; split real and imaginary components.
- `vectorized=True` is a user assertion about the RHS contract and can help or hurt performance
depending on method and system size.
- Solver `success=True` does not establish scientific accuracy. Users must declare invariant,
convergence, endpoint, or qualitative policies appropriate to their model.
- A configured Python adapter module/factory executes trusted code during run and replay. PhaseProbe
validates dotted names and never loads code during JSON parsing, but it is not a sandbox.
- Callable source is neither serialized nor treated as securely hashable. Model identity combines
an explicit user identifier with serialized numerical configuration metadata.

- A reported bracket is finite-time, classifier-specific numerical evidence, not an exact bifurcation point.
- A smallest result is the smallest reproducible candidate found by the declared finite grid/refinement, not a proof of global minimality.
- The Lorenz metric is a finite-time divergence rate, not a Lyapunov exponent and not by itself proof of chaos.
Expand Down
Loading
Loading