phaseprobe 0.2.0__tar.gz → 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {phaseprobe-0.2.0 → phaseprobe-0.3.0}/.github/workflows/ci.yml +24 -10
- phaseprobe-0.3.0/.github/workflows/publish-pypi.yml +166 -0
- {phaseprobe-0.2.0 → phaseprobe-0.3.0}/.github/workflows/release.yml +4 -7
- phaseprobe-0.3.0/CHANGELOG.md +63 -0
- {phaseprobe-0.2.0 → phaseprobe-0.3.0}/CITATION.cff +1 -2
- {phaseprobe-0.2.0 → phaseprobe-0.3.0}/PKG-INFO +62 -13
- {phaseprobe-0.2.0 → phaseprobe-0.3.0}/README.md +60 -11
- {phaseprobe-0.2.0 → phaseprobe-0.3.0}/SCIENTIFIC_METHODS.md +3 -0
- {phaseprobe-0.2.0 → phaseprobe-0.3.0}/assets/scipy-demo-session.txt +10 -10
- phaseprobe-0.3.0/assets/scipy-demo-static.png +0 -0
- phaseprobe-0.3.0/assets/scipy-demo.gif +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.3.0}/docs/upstream/SCIPY_INTEGRATION_PROPOSAL.md +47 -11
- {phaseprobe-0.2.0 → phaseprobe-0.3.0}/examples/README.md +10 -5
- {phaseprobe-0.2.0 → phaseprobe-0.3.0}/pyproject.toml +1 -1
- {phaseprobe-0.2.0 → phaseprobe-0.3.0}/scripts/audit_package.py +36 -4
- {phaseprobe-0.2.0 → phaseprobe-0.3.0}/scripts/generate_scipy_demo.py +8 -8
- phaseprobe-0.3.0/scripts/installed_smoke.py +199 -0
- phaseprobe-0.3.0/scripts/verify_artifacts.py +221 -0
- {phaseprobe-0.2.0 → phaseprobe-0.3.0}/src/phaseprobe/__init__.py +1 -1
- {phaseprobe-0.2.0 → phaseprobe-0.3.0}/src/phaseprobe/cli.py +30 -3
- {phaseprobe-0.2.0 → phaseprobe-0.3.0}/src/phaseprobe/config.py +69 -18
- phaseprobe-0.3.0/src/phaseprobe/data/examples/scipy-lorenz-negative.json +54 -0
- phaseprobe-0.3.0/src/phaseprobe/data/examples/scipy-lorenz.json +54 -0
- phaseprobe-0.3.0/src/phaseprobe/data/examples/scipy-predator-prey-coarse.json +45 -0
- phaseprobe-0.3.0/src/phaseprobe/data/examples/scipy-predator-prey.json +45 -0
- phaseprobe-0.3.0/src/phaseprobe/generate.py +377 -0
- phaseprobe-0.3.0/src/phaseprobe/py.typed +1 -0
- phaseprobe-0.3.0/tests/test_artifacts_replay.py +229 -0
- {phaseprobe-0.2.0 → phaseprobe-0.3.0}/tests/test_cli.py +27 -0
- phaseprobe-0.3.0/tests/test_config.py +148 -0
- {phaseprobe-0.2.0 → phaseprobe-0.3.0}/tests/test_optional_dependency.py +7 -1
- {phaseprobe-0.2.0 → phaseprobe-0.3.0}/tests/test_scipy_adapter.py +52 -6
- {phaseprobe-0.2.0 → phaseprobe-0.3.0}/uv.lock +1 -1
- phaseprobe-0.2.0/CHANGELOG.md +0 -30
- phaseprobe-0.2.0/assets/scipy-demo-static.png +0 -0
- phaseprobe-0.2.0/assets/scipy-demo.gif +0 -0
- phaseprobe-0.2.0/src/phaseprobe/generate.py +0 -72
- phaseprobe-0.2.0/tests/test_artifacts_replay.py +0 -86
- phaseprobe-0.2.0/tests/test_config.py +0 -69
- {phaseprobe-0.2.0 → phaseprobe-0.3.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.3.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.3.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.3.0}/.github/ISSUE_TEMPLATE/good_first_accessibility.yml +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.3.0}/.github/ISSUE_TEMPLATE/good_first_pendulum.yml +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.3.0}/.github/ISSUE_TEMPLATE/good_first_schema.yml +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.3.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.3.0}/.gitignore +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.3.0}/AGENTS.md +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.3.0}/ARCHITECTURE.md +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.3.0}/CODE_OF_CONDUCT.md +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.3.0}/CONTRIBUTING.md +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.3.0}/LICENSE +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.3.0}/LIMITATIONS.md +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.3.0}/PRIOR_ART.md +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.3.0}/ROADMAP.md +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.3.0}/SECURITY.md +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.3.0}/assets/demo-session.txt +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.3.0}/assets/demo-static.png +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.3.0}/assets/demo.gif +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.3.0}/assets/social-preview.jpg +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.3.0}/docs/SCIPY_SOLVE_IVP_AUDIT.md +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.3.0}/examples/configs/logistic-negative.json +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.3.0}/examples/configs/logistic-scan.json +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.3.0}/examples/configs/lorenz-negative.json +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.3.0}/examples/configs/lorenz-perturb.json +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.3.0}/examples/configs/predator-prey-check.json +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.3.0}/examples/configs/predator-prey-negative.json +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.3.0}/examples/configs/toggle-negative.json +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.3.0}/examples/configs/toggle-perturb.json +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.3.0}/examples/scipy/lorenz-negative.json +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.3.0}/examples/scipy/lorenz.json +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.3.0}/examples/scipy/predator-prey-coarse.json +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.3.0}/examples/scipy/predator-prey.json +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.3.0}/examples/scipy/report.html +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.3.0}/scripts/check_links.py +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.3.0}/scripts/generate_demo.py +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.3.0}/scripts/hygiene.py +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.3.0}/src/phaseprobe/__main__.py +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.3.0}/src/phaseprobe/adapters/__init__.py +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.3.0}/src/phaseprobe/adapters/loader.py +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.3.0}/src/phaseprobe/adapters/scipy.py +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.3.0}/src/phaseprobe/api.py +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.3.0}/src/phaseprobe/artifacts.py +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.3.0}/src/phaseprobe/data/__init__.py +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.3.0}/src/phaseprobe/data/examples/__init__.py +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.3.0}/src/phaseprobe/data/examples/logistic-negative.json +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.3.0}/src/phaseprobe/data/examples/logistic-scan.json +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.3.0}/src/phaseprobe/data/examples/lorenz-negative.json +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.3.0}/src/phaseprobe/data/examples/lorenz-perturb.json +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.3.0}/src/phaseprobe/data/examples/predator-prey-check.json +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.3.0}/src/phaseprobe/data/examples/predator-prey-negative.json +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.3.0}/src/phaseprobe/data/examples/toggle-negative.json +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.3.0}/src/phaseprobe/data/examples/toggle-perturb.json +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.3.0}/src/phaseprobe/engine.py +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.3.0}/src/phaseprobe/errors.py +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.3.0}/src/phaseprobe/examples/__init__.py +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.3.0}/src/phaseprobe/examples/scipy_models.py +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.3.0}/src/phaseprobe/models/__init__.py +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.3.0}/src/phaseprobe/models/_common.py +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.3.0}/src/phaseprobe/models/logistic.py +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.3.0}/src/phaseprobe/models/lorenz.py +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.3.0}/src/phaseprobe/models/predator_prey.py +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.3.0}/src/phaseprobe/models/toggle.py +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.3.0}/src/phaseprobe/replay.py +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.3.0}/src/phaseprobe/reporting.py +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.3.0}/src/phaseprobe/types.py +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.3.0}/tests/conftest.py +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.3.0}/tests/generated/fixtures/logistic_map-replay.json +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.3.0}/tests/generated/fixtures/lorenz_scipy-replay.json +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.3.0}/tests/generated/fixtures/predator_prey_scipy-replay.json +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.3.0}/tests/generated/test_logistic_map_transition.py +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.3.0}/tests/generated/test_lorenz_scipy_transition.py +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.3.0}/tests/generated/test_predator_prey_scipy_transition.py +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.3.0}/tests/test_engine.py +0 -0
|
@@ -50,8 +50,8 @@ jobs:
|
|
|
50
50
|
- run: python -m ruff check .
|
|
51
51
|
- run: python -m mypy src tests
|
|
52
52
|
- run: python -m pytest --cov=phaseprobe --cov-report=term
|
|
53
|
-
- run: python -m phaseprobe perturb --
|
|
54
|
-
- run: python -m phaseprobe check --
|
|
53
|
+
- run: python -m phaseprobe perturb --example scipy-lorenz-negative
|
|
54
|
+
- run: python -m phaseprobe check --example scipy-predator-prey
|
|
55
55
|
|
|
56
56
|
package-and-hygiene:
|
|
57
57
|
runs-on: ubuntu-latest
|
|
@@ -65,14 +65,10 @@ jobs:
|
|
|
65
65
|
- run: python -m build
|
|
66
66
|
- run: python -m twine check dist/*
|
|
67
67
|
- run: python scripts/audit_package.py
|
|
68
|
-
- run:
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
- 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"
|
|
73
|
-
- run: python -m venv .cache/scipy-smoke
|
|
74
|
-
- run: .cache/scipy-smoke/bin/python -m pip install "$(find dist -name '*.whl')[scipy]"
|
|
75
|
-
- run: .cache/scipy-smoke/bin/python -m phaseprobe check --config examples/scipy/predator-prey.json
|
|
68
|
+
- run: >-
|
|
69
|
+
python scripts/verify_artifacts.py
|
|
70
|
+
--dist-dir dist
|
|
71
|
+
--work-root "${RUNNER_TEMP}/phaseprobe artifact gate"
|
|
76
72
|
- run: python scripts/check_links.py
|
|
77
73
|
- run: python scripts/hygiene.py
|
|
78
74
|
- uses: actions/upload-artifact@v4
|
|
@@ -80,3 +76,21 @@ jobs:
|
|
|
80
76
|
name: phaseprobe-packages
|
|
81
77
|
path: dist/*
|
|
82
78
|
if-no-files-found: error
|
|
79
|
+
|
|
80
|
+
windows-artifact-install:
|
|
81
|
+
name: artifact install / windows / Python 3.12
|
|
82
|
+
runs-on: windows-latest
|
|
83
|
+
steps:
|
|
84
|
+
- uses: actions/checkout@v4
|
|
85
|
+
- uses: actions/setup-python@v5
|
|
86
|
+
with:
|
|
87
|
+
python-version: "3.12"
|
|
88
|
+
cache: pip
|
|
89
|
+
- run: python -m pip install --upgrade pip build twine
|
|
90
|
+
- run: python -m build
|
|
91
|
+
- run: python -m twine check dist/*
|
|
92
|
+
- run: python scripts/audit_package.py
|
|
93
|
+
- run: >-
|
|
94
|
+
python scripts/verify_artifacts.py
|
|
95
|
+
--dist-dir dist
|
|
96
|
+
--work-root "$env:RUNNER_TEMP\phaseprobe artifact gate"
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
name: Publish an existing GitHub release to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
inputs:
|
|
6
|
+
tag:
|
|
7
|
+
description: Immutable release tag (for example, v0.2.1)
|
|
8
|
+
required: true
|
|
9
|
+
type: string
|
|
10
|
+
version:
|
|
11
|
+
description: Expected package version (for example, 0.2.1)
|
|
12
|
+
required: true
|
|
13
|
+
type: string
|
|
14
|
+
commit:
|
|
15
|
+
description: Expected 40-character commit for the tag
|
|
16
|
+
required: true
|
|
17
|
+
type: string
|
|
18
|
+
wheel_sha256:
|
|
19
|
+
description: Expected SHA-256 for the wheel release asset
|
|
20
|
+
required: true
|
|
21
|
+
type: string
|
|
22
|
+
sdist_sha256:
|
|
23
|
+
description: Expected SHA-256 for the source release asset
|
|
24
|
+
required: true
|
|
25
|
+
type: string
|
|
26
|
+
|
|
27
|
+
permissions: {}
|
|
28
|
+
|
|
29
|
+
concurrency:
|
|
30
|
+
group: pypi-${{ inputs.tag }}
|
|
31
|
+
cancel-in-progress: false
|
|
32
|
+
|
|
33
|
+
jobs:
|
|
34
|
+
verify:
|
|
35
|
+
name: Verify immutable release assets
|
|
36
|
+
if: >-
|
|
37
|
+
github.repository == 'aliengineering-byte/phaseprobe' &&
|
|
38
|
+
github.ref == 'refs/heads/main'
|
|
39
|
+
runs-on: ubuntu-latest
|
|
40
|
+
permissions: {}
|
|
41
|
+
steps:
|
|
42
|
+
- name: Download and verify the selected release
|
|
43
|
+
env:
|
|
44
|
+
TAG: ${{ inputs.tag }}
|
|
45
|
+
VERSION: ${{ inputs.version }}
|
|
46
|
+
EXPECTED_COMMIT: ${{ inputs.commit }}
|
|
47
|
+
WHEEL_SHA256: ${{ inputs.wheel_sha256 }}
|
|
48
|
+
SDIST_SHA256: ${{ inputs.sdist_sha256 }}
|
|
49
|
+
shell: bash
|
|
50
|
+
run: |
|
|
51
|
+
set -euo pipefail
|
|
52
|
+
|
|
53
|
+
[[ "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]
|
|
54
|
+
[[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]
|
|
55
|
+
[[ "$TAG" == "v$VERSION" ]]
|
|
56
|
+
[[ "$EXPECTED_COMMIT" =~ ^[0-9a-f]{40}$ ]]
|
|
57
|
+
[[ "$WHEEL_SHA256" =~ ^[0-9a-f]{64}$ ]]
|
|
58
|
+
[[ "$SDIST_SHA256" =~ ^[0-9a-f]{64}$ ]]
|
|
59
|
+
|
|
60
|
+
repository_url="https://github.com/${GITHUB_REPOSITORY}.git"
|
|
61
|
+
tag_refs="$(git ls-remote "$repository_url" "refs/tags/$TAG" "refs/tags/$TAG^{}")"
|
|
62
|
+
direct_commit="$(awk -v ref="refs/tags/$TAG" '$2 == ref { print $1 }' <<<"$tag_refs")"
|
|
63
|
+
peeled_commit="$(awk -v ref="refs/tags/$TAG^{}" '$2 == ref { print $1 }' <<<"$tag_refs")"
|
|
64
|
+
resolved_commit="${peeled_commit:-$direct_commit}"
|
|
65
|
+
[[ -n "$resolved_commit" ]]
|
|
66
|
+
[[ "$resolved_commit" == "$EXPECTED_COMMIT" ]]
|
|
67
|
+
|
|
68
|
+
wheel="phaseprobe-$VERSION-py3-none-any.whl"
|
|
69
|
+
sdist="phaseprobe-$VERSION.tar.gz"
|
|
70
|
+
release_url="https://github.com/${GITHUB_REPOSITORY}/releases/download/$TAG"
|
|
71
|
+
mkdir dist
|
|
72
|
+
curl --fail --location --proto '=https' --tlsv1.2 \
|
|
73
|
+
--output "dist/$wheel" "$release_url/$wheel"
|
|
74
|
+
curl --fail --location --proto '=https' --tlsv1.2 \
|
|
75
|
+
--output "dist/$sdist" "$release_url/$sdist"
|
|
76
|
+
printf '%s %s\n%s %s\n' \
|
|
77
|
+
"$WHEEL_SHA256" "dist/$wheel" \
|
|
78
|
+
"$SDIST_SHA256" "dist/$sdist" | sha256sum --check --strict
|
|
79
|
+
|
|
80
|
+
python3 -m venv .twine-check
|
|
81
|
+
.twine-check/bin/python -m pip install --disable-pip-version-check 'twine==7.0.0'
|
|
82
|
+
.twine-check/bin/python -m twine check "dist/$wheel" "dist/$sdist"
|
|
83
|
+
|
|
84
|
+
python3 - "$VERSION" "dist/$wheel" "dist/$sdist" <<'PY'
|
|
85
|
+
import email
|
|
86
|
+
import sys
|
|
87
|
+
import tarfile
|
|
88
|
+
import zipfile
|
|
89
|
+
|
|
90
|
+
expected_version, wheel_path, sdist_path = sys.argv[1:]
|
|
91
|
+
|
|
92
|
+
with zipfile.ZipFile(wheel_path) as archive:
|
|
93
|
+
wheel_names = set(archive.namelist())
|
|
94
|
+
metadata_names = [
|
|
95
|
+
name for name in wheel_names if name.endswith(".dist-info/METADATA")
|
|
96
|
+
]
|
|
97
|
+
assert len(metadata_names) == 1, metadata_names
|
|
98
|
+
wheel_metadata = email.message_from_bytes(archive.read(metadata_names[0]))
|
|
99
|
+
|
|
100
|
+
with tarfile.open(sdist_path, "r:gz") as archive:
|
|
101
|
+
sdist_names = {member.name for member in archive.getmembers()}
|
|
102
|
+
metadata_members = [
|
|
103
|
+
member
|
|
104
|
+
for member in archive.getmembers()
|
|
105
|
+
if member.isfile() and member.name.count("/") == 1 and member.name.endswith("/PKG-INFO")
|
|
106
|
+
]
|
|
107
|
+
assert len(metadata_members) == 1, [member.name for member in metadata_members]
|
|
108
|
+
extracted = archive.extractfile(metadata_members[0])
|
|
109
|
+
assert extracted is not None
|
|
110
|
+
sdist_metadata = email.message_from_bytes(extracted.read())
|
|
111
|
+
|
|
112
|
+
sdist_roots = {name.split("/", 1)[0] for name in sdist_names if "/" in name}
|
|
113
|
+
assert len(sdist_roots) == 1, sorted(sdist_roots)
|
|
114
|
+
sdist_root = next(iter(sdist_roots))
|
|
115
|
+
source_prefix = f"{sdist_root}/src/phaseprobe/"
|
|
116
|
+
runtime_files = {
|
|
117
|
+
name.removeprefix(f"{sdist_root}/src/")
|
|
118
|
+
for name in sdist_names
|
|
119
|
+
if name.startswith(source_prefix)
|
|
120
|
+
and not name.endswith(".py")
|
|
121
|
+
and "__pycache__" not in name.split("/")
|
|
122
|
+
}
|
|
123
|
+
assert "phaseprobe/py.typed" in runtime_files
|
|
124
|
+
assert not runtime_files - wheel_names, sorted(runtime_files - wheel_names)
|
|
125
|
+
|
|
126
|
+
for metadata in (wheel_metadata, sdist_metadata):
|
|
127
|
+
assert metadata["Name"] == "phaseprobe"
|
|
128
|
+
assert metadata["Version"] == expected_version
|
|
129
|
+
assert metadata["Requires-Python"] == ">=3.10"
|
|
130
|
+
assert metadata["License-Expression"] == "Apache-2.0"
|
|
131
|
+
assert "scipy" in metadata.get_all("Provides-Extra", [])
|
|
132
|
+
requirements = metadata.get_all("Requires-Dist", [])
|
|
133
|
+
assert any(req.startswith("numpy") and "extra == 'scipy'" in req for req in requirements)
|
|
134
|
+
assert any(req.startswith("scipy") and "extra == 'scipy'" in req for req in requirements)
|
|
135
|
+
project_urls = metadata.get_all("Project-URL", [])
|
|
136
|
+
assert any(url.startswith("Source, https://github.com/aliengineering-byte/phaseprobe") for url in project_urls)
|
|
137
|
+
PY
|
|
138
|
+
|
|
139
|
+
- name: Store the verified distributions
|
|
140
|
+
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
|
141
|
+
with:
|
|
142
|
+
name: verified-distributions-${{ inputs.tag }}
|
|
143
|
+
path: dist/*
|
|
144
|
+
if-no-files-found: error
|
|
145
|
+
retention-days: 1
|
|
146
|
+
|
|
147
|
+
publish:
|
|
148
|
+
name: Publish verified distributions
|
|
149
|
+
needs: verify
|
|
150
|
+
runs-on: ubuntu-latest
|
|
151
|
+
environment:
|
|
152
|
+
name: pypi
|
|
153
|
+
url: https://pypi.org/project/phaseprobe/${{ inputs.version }}/
|
|
154
|
+
permissions:
|
|
155
|
+
id-token: write
|
|
156
|
+
steps:
|
|
157
|
+
- name: Retrieve the verified distributions
|
|
158
|
+
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
|
|
159
|
+
with:
|
|
160
|
+
name: verified-distributions-${{ inputs.tag }}
|
|
161
|
+
path: dist
|
|
162
|
+
|
|
163
|
+
- name: Publish distributions to PyPI
|
|
164
|
+
uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # release/v1
|
|
165
|
+
with:
|
|
166
|
+
packages-dir: dist/
|
|
@@ -20,13 +20,10 @@ jobs:
|
|
|
20
20
|
- run: python -m build
|
|
21
21
|
- run: python -m twine check dist/*
|
|
22
22
|
- run: python scripts/audit_package.py
|
|
23
|
-
- run:
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
- run: python -m venv .cache/tag-scipy-smoke
|
|
28
|
-
- run: .cache/tag-scipy-smoke/bin/python -m pip install "$(find dist -name '*.whl')[scipy]"
|
|
29
|
-
- run: .cache/tag-scipy-smoke/bin/python -m phaseprobe check --config examples/scipy/predator-prey.json
|
|
23
|
+
- run: >-
|
|
24
|
+
python scripts/verify_artifacts.py
|
|
25
|
+
--dist-dir dist
|
|
26
|
+
--work-root "${RUNNER_TEMP}/phaseprobe tagged artifact gate"
|
|
30
27
|
- run: python scripts/hygiene.py
|
|
31
28
|
- uses: actions/upload-artifact@v4
|
|
32
29
|
with:
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes are documented here. PhaseProbe follows semantic versioning.
|
|
4
|
+
|
|
5
|
+
## Unreleased
|
|
6
|
+
|
|
7
|
+
- Added bounded offline `verify-evidence` validation for generated pytest evidence, replay
|
|
8
|
+
decisions, artifact hashes, safe relative paths, and the fixed regression template.
|
|
9
|
+
- The unreleased package identity is now `0.3.0` for the new generated-evidence contract.
|
|
10
|
+
- `generate-test` now writes a path-portable claim/decision evidence record that binds the
|
|
11
|
+
validated replay verdict to SHA-256 hashes of the copied fixture and generated pytest, the
|
|
12
|
+
exact reproduction command, PhaseProbe attribution, and explicit scientific limitations.
|
|
13
|
+
- Identical generated evidence is idempotent; conflicting evidence is rejected without overwrite.
|
|
14
|
+
- Invariant-only fixtures now use the narrower `simulation-replay-regression` claim kind, and the
|
|
15
|
+
unsigned evidence-integrity boundary is explicit.
|
|
16
|
+
|
|
17
|
+
## 0.2.1 — 2026-08-31
|
|
18
|
+
|
|
19
|
+
- Fixed Issue #4: all four SciPy quick-start configurations now ship inside the importable
|
|
20
|
+
package in both wheel and source distribution and load through `importlib.resources` as the
|
|
21
|
+
`scipy-lorenz`, `scipy-lorenz-negative`, `scipy-predator-prey`, and
|
|
22
|
+
`scipy-predator-prey-coarse` built-in examples.
|
|
23
|
+
- Preserved the four exact former `examples/scipy/` config paths as narrow compatibility aliases
|
|
24
|
+
when no file exists at the requested path. Existing user files take precedence; matching is
|
|
25
|
+
case-sensitive and separator-portable, with no fuzzy or basename fallback.
|
|
26
|
+
- Added actionable, versioned diagnostics for missing or malformed built-in resources without
|
|
27
|
+
changing arbitrary config-path or built-in-example behavior.
|
|
28
|
+
- Added Linux and Windows Python 3.12 release gates that build, inspect, install, and exercise the
|
|
29
|
+
wheel and sdist outside the checkout with `PYTHONPATH` removed. The gate runs scan, replay,
|
|
30
|
+
generated pytest, SciPy Lorenz, SciPy predator–prey, `pip check`, and a dependency-free base
|
|
31
|
+
wheel check.
|
|
32
|
+
- Added a source-derived runtime-resource audit and the PEP 561 `py.typed` marker.
|
|
33
|
+
- Generated regression creation is now idempotent for identical evidence and rejects conflicting
|
|
34
|
+
files instead of silently overwriting them.
|
|
35
|
+
- Added an analytic backward-time directional-event check while preserving all solver defaults
|
|
36
|
+
and the existing tolerance-based adaptive replay contract.
|
|
37
|
+
|
|
38
|
+
## 0.2.0 — 2026-08-02
|
|
39
|
+
|
|
40
|
+
- Added the backward-compatible `TrajectoryAdapter` protocol and shared engine dispatch; all v0.1
|
|
41
|
+
step adapters and examples continue unchanged.
|
|
42
|
+
- Added optional `phaseprobe[scipy]` support with a typed public `SolveIVPAdapter`, controlled
|
|
43
|
+
evaluation grids, scalar/vector tolerances, methods, maximum step, named events, observables,
|
|
44
|
+
classifiers, invariants, invalid-value checks, bounded retention, and solver/environment evidence.
|
|
45
|
+
- Added schema-v2 `exact` and `tolerance` replay while keeping schema-v1 exact fixtures readable.
|
|
46
|
+
Tolerance fixtures preserve state, observable, classifier, invariant threshold, grid, endpoint,
|
|
47
|
+
event, solver-success, version, and platform evidence without claiming byte-identical replay.
|
|
48
|
+
- Added genuine SciPy Lorenz finite-time divergence and predator–prey first-integral examples,
|
|
49
|
+
negative controls, refinement evidence, HTML evidence, and generated pytest fixtures.
|
|
50
|
+
- Added explicit safe-shape Python module/factory loading, core-without-SciPy tests, optional import
|
|
51
|
+
diagnostics, a Windows/Linux core/SciPy CI matrix, current SciPy audit, security/limitations docs,
|
|
52
|
+
demo assets, and an unposted upstream visibility proposal.
|
|
53
|
+
- Base installation remains dependency-free. NumPy and SciPy are optional; no PyPI publication was
|
|
54
|
+
performed.
|
|
55
|
+
|
|
56
|
+
## 0.1.0 — 2026-08-01
|
|
57
|
+
|
|
58
|
+
- Added `scan`, `perturb`, `check`, `replay`, `generate-test`, and `report` commands.
|
|
59
|
+
- Added typed adapters for logistic map, Lorenz, Lotka–Volterra predator–prey, and a mutually repressing genetic toggle.
|
|
60
|
+
- Added deterministic seeds, canonical configuration, fixed-step execution, invalid-integration diagnostics, capped trace retention, and exact trace hashes.
|
|
61
|
+
- Added transition brackets, bounded initial-state search, repeatability confirmation, replay fixtures with integrity hashes, and fixed-template pytest generation.
|
|
62
|
+
- Added terminal, versioned JSON, and self-contained offline HTML evidence.
|
|
63
|
+
- Added Windows/Linux CI, packaging and packed-install checks, documentation/hygiene gates, and repository-native demo assets.
|
|
@@ -4,8 +4,7 @@ title: "PhaseProbe"
|
|
|
4
4
|
type: software
|
|
5
5
|
authors:
|
|
6
6
|
- name: "Ali"
|
|
7
|
-
version: 0.
|
|
8
|
-
date-released: 2026-08-02
|
|
7
|
+
version: 0.3.0
|
|
9
8
|
url: "https://github.com/aliengineering-byte/phaseprobe"
|
|
10
9
|
repository-code: "https://github.com/aliengineering-byte/phaseprobe"
|
|
11
10
|
license: Apache-2.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: phaseprobe
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Find reproducible qualitative simulation transitions and turn them into regression tests.
|
|
5
5
|
Project-URL: Documentation, https://github.com/aliengineering-byte/phaseprobe#readme
|
|
6
6
|
Project-URL: Issues, https://github.com/aliengineering-byte/phaseprobe/issues
|
|
@@ -43,7 +43,7 @@ Description-Content-Type: text/markdown
|
|
|
43
43
|
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.
|
|
44
44
|
|
|
45
45
|
```console
|
|
46
|
-
$
|
|
46
|
+
$ pip install phaseprobe pytest
|
|
47
47
|
$ phaseprobe scan --example logistic
|
|
48
48
|
QUALITATIVE TRANSITION FOUND
|
|
49
49
|
|
|
@@ -62,15 +62,24 @@ $ python -m pytest -q tests/generated
|
|
|
62
62
|
|
|
63
63
|
No API key, LLM, GPU, Docker, account, telemetry, network connection, or hosted service is required at runtime. PhaseProbe's base installation has no third-party runtime dependencies; NumPy and SciPy are isolated in the optional `scipy` extra.
|
|
64
64
|
|
|
65
|
+
## Installation
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
pip install phaseprobe
|
|
69
|
+
pip install "phaseprobe[scipy]"
|
|
70
|
+
pip install pytest # needed only to execute a generated regression test
|
|
71
|
+
```
|
|
72
|
+
|
|
65
73
|
## Five-minute quick start
|
|
66
74
|
|
|
67
75
|
Requires Python 3.10 or newer on Windows or Linux.
|
|
68
76
|
|
|
69
77
|
```bash
|
|
70
|
-
|
|
78
|
+
pip install phaseprobe pytest
|
|
71
79
|
phaseprobe scan --example logistic
|
|
72
80
|
phaseprobe replay .phaseprobe/runs/<run-id>/replay.json
|
|
73
81
|
phaseprobe generate-test .phaseprobe/runs/<run-id>/replay.json
|
|
82
|
+
phaseprobe verify-evidence tests/generated/logistic_map-pytest-evidence.json
|
|
74
83
|
python -m pytest -q tests/generated
|
|
75
84
|
phaseprobe report .phaseprobe/runs/<run-id>
|
|
76
85
|
```
|
|
@@ -90,22 +99,41 @@ Five-minute path:
|
|
|
90
99
|
[](assets/scipy-demo.gif)
|
|
91
100
|
|
|
92
101
|
The verified command transcript is [assets/scipy-demo-session.txt](assets/scipy-demo-session.txt),
|
|
93
|
-
with a self-contained [HTML example report](examples/scipy/report.html).
|
|
102
|
+
with a self-contained [HTML example report](examples/scipy/report.html). The following path starts
|
|
103
|
+
from a clean environment and does not require a source checkout:
|
|
94
104
|
|
|
95
105
|
```bash
|
|
96
|
-
python -m
|
|
97
|
-
|
|
98
|
-
|
|
106
|
+
python -m venv .venv
|
|
107
|
+
# Linux: source .venv/bin/activate
|
|
108
|
+
# Windows PowerShell: .venv\Scripts\Activate.ps1
|
|
109
|
+
python -m pip install "phaseprobe[scipy]" "pytest==8.4.1"
|
|
110
|
+
phaseprobe perturb --example scipy-lorenz
|
|
111
|
+
phaseprobe check --example scipy-predator-prey
|
|
99
112
|
phaseprobe replay .phaseprobe/runs/<run-id>/replay.json
|
|
100
113
|
phaseprobe generate-test .phaseprobe/runs/<run-id>/replay.json
|
|
101
114
|
python -m pytest -q tests/generated
|
|
102
115
|
```
|
|
103
116
|
|
|
117
|
+
PhaseProbe 0.2.1 also recognizes the former Issue #4 command
|
|
118
|
+
`phaseprobe perturb --config examples/scipy/lorenz.json` when that relative file is absent and
|
|
119
|
+
loads the corresponding packaged example. This compatibility is limited to the four former
|
|
120
|
+
`examples/scipy/` paths: an existing file always takes precedence, matching is case-sensitive,
|
|
121
|
+
both slash styles are accepted, and unrelated missing paths remain errors. New documentation and
|
|
122
|
+
automation should use the installed-safe `--example scipy-*` names.
|
|
123
|
+
|
|
104
124
|
The Lorenz command searches a declared initial-`x` perturbation and reports only finite-time
|
|
105
|
-
divergence evidence
|
|
106
|
-
The predator–prey command checks the
|
|
107
|
-
|
|
108
|
-
settings.
|
|
125
|
+
divergence evidence and prints `FINITE-TIME TRAJECTORY DIVERGENCE FOUND`. Its installed
|
|
126
|
+
short-window control is `--example scipy-lorenz-negative`. The predator–prey command checks the
|
|
127
|
+
declared first integral with tightly resolved DOP853 settings and prints `CHECK POLICY PASSED`;
|
|
128
|
+
`--example scipy-predator-prey-coarse` deliberately fails the same policy with loose RK23 settings.
|
|
129
|
+
Each successful command writes a replay fixture and offline report below
|
|
130
|
+
`.phaseprobe/runs/<run-id>/`.
|
|
131
|
+
|
|
132
|
+
Run `phaseprobe perturb --help` or `phaseprobe check --help` to list installed example names. A
|
|
133
|
+
missing packaged resource reports the PhaseProbe version and resource name. Adaptive SciPy replay
|
|
134
|
+
compares the declared state, observable, invariant, endpoint, event, and retained-grid
|
|
135
|
+
tolerances—it does not promise bitwise trajectory equality across platforms or dependency
|
|
136
|
+
versions.
|
|
109
137
|
|
|
110
138
|
For a direct Python API:
|
|
111
139
|
|
|
@@ -144,7 +172,8 @@ does not import it. See [the audited contract](docs/SCIPY_SOLVE_IVP_AUDIT.md),
|
|
|
144
172
|
| `perturb` | Baseline/perturbed twin runs over bounded initial-state changes | Finding or no finding, exit `0` |
|
|
145
173
|
| `check` | Execute a declared configuration policy for CI | Exit `1` only when policy fails |
|
|
146
174
|
| `replay` | Validate fixture integrity and re-execute model, parameters, seed, initial state, tolerances, and retention | Declared `exact` or `tolerance` comparison passes |
|
|
147
|
-
| `generate-test` | Validate and copy a fixture into a non-extensible pytest template | Executable test under `tests/generated/` |
|
|
175
|
+
| `generate-test` | Validate and copy a fixture into a non-extensible pytest template without conflicting overwrites | Executable test under `tests/generated/` |
|
|
176
|
+
| `verify-evidence` | Re-derive claims/replay decisions and validate bounded strict JSON, artifact hashes, paths, and the fixed template offline | Stable verified or invalid-input exit |
|
|
148
177
|
| `report` | Regenerate terminal, versioned JSON, and self-contained offline HTML evidence | Local report files |
|
|
149
178
|
|
|
150
179
|
Common options:
|
|
@@ -152,6 +181,7 @@ Common options:
|
|
|
152
181
|
```console
|
|
153
182
|
phaseprobe scan --config examples/configs/logistic-scan.json
|
|
154
183
|
phaseprobe perturb --example lorenz --json
|
|
184
|
+
phaseprobe perturb --example scipy-lorenz --json
|
|
155
185
|
phaseprobe check --example predator-prey
|
|
156
186
|
phaseprobe scan --example logistic --fail-on-finding
|
|
157
187
|
```
|
|
@@ -166,6 +196,8 @@ Exit codes are stable: `0` completed, `1` declared policy or explicit `--fail-on
|
|
|
166
196
|
| Lorenz system | `phaseprobe perturb --example lorenz` | Small initial separation exceeds the declared finite-time trajectory-distance threshold | Short window plus unreachable threshold reports no finding | [Lorenz, 1963](https://journals.ametsoc.org/view/journals/atsc/20/2/1520-0469_1963_020_0130_dnf_2_0_co_2.xml) |
|
|
167
197
|
| Predator–prey | `phaseprobe check --example predator-prey` | Refined RK4 step preserves the analytic first integral within tolerance | Coarse step fails the invariant-drift policy | [Lotka, 1920](https://doi.org/10.1073/pnas.6.7.410) |
|
|
168
198
|
| Genetic toggle | `phaseprobe perturb --example toggle` | Bounded initial-state perturbation reaches the opposite dominant state | Smaller declared range stays in the baseline basin | [Gardner, Cantor & Collins, 2000](https://www.nature.com/articles/35002131) |
|
|
199
|
+
| SciPy Lorenz | `phaseprobe perturb --example scipy-lorenz` | DOP853 twin trajectories cross the declared finite-time distance threshold | `--example scipy-lorenz-negative` shortens the window | [Lorenz, 1963](https://journals.ametsoc.org/view/journals/atsc/20/2/1520-0469_1963_020_0130_dnf_2_0_co_2.xml) |
|
|
200
|
+
| SciPy predator–prey | `phaseprobe check --example scipy-predator-prey` | Tight DOP853 settings preserve the declared first integral tolerance | `--example scipy-predator-prey-coarse` deliberately fails | [Lotka, 1920](https://doi.org/10.1073/pnas.6.7.410) |
|
|
169
201
|
|
|
170
202
|
Each 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](examples/README.md) for equations and interpretation.
|
|
171
203
|
|
|
@@ -188,6 +220,22 @@ manifest.json sizes and SHA-256 hashes
|
|
|
188
220
|
|
|
189
221
|
Model names used for generated test paths are sanitized. The generated source comes from a fixed template; configuration strings never become executable Python.
|
|
190
222
|
|
|
223
|
+
`generate-test` validates the replay before writing anything. Alongside the fixed pytest template
|
|
224
|
+
and copied fixture, it writes `<model>-pytest-evidence.json`: a path-portable claim/decision record
|
|
225
|
+
containing repository/version attribution, the declared replay comparisons, SHA-256 hashes for
|
|
226
|
+
both executable artifacts, the exact pytest command, and explicit scientific limitations.
|
|
227
|
+
Run `phaseprobe verify-evidence <evidence.json>` before pytest to validate the strict schema,
|
|
228
|
+
recompute the hashes, re-execute the replay policy, derive the claim/decision fields, and confirm
|
|
229
|
+
that the regression still matches PhaseProbe's fixed template. Validation is offline, bounded to a
|
|
230
|
+
1 MiB evidence document and 32 MiB per generated artifact, rejects duplicate keys and unsafe paths,
|
|
231
|
+
and exits `2` for invalid input.
|
|
232
|
+
The record is unsigned: consumers must recompute the two artifact hashes to detect a recomputed or
|
|
233
|
+
substituted record. It is portable integrity metadata, not an authentication signature.
|
|
234
|
+
|
|
235
|
+
If `generate-test` reports a conflict, it has left the existing output untouched. An identical
|
|
236
|
+
artifact can be generated again safely; for different content, inspect the existing files and
|
|
237
|
+
choose a fresh output directory (or remove them only after deciding they are no longer needed).
|
|
238
|
+
|
|
191
239
|
## What PhaseProbe adds—and what it does not
|
|
192
240
|
|
|
193
241
|
| existing category | established strength | PhaseProbe’s narrower job |
|
|
@@ -269,6 +317,7 @@ python scripts/hygiene.py
|
|
|
269
317
|
```
|
|
270
318
|
|
|
271
319
|
See [CONTRIBUTING.md](CONTRIBUTING.md) and the concrete good-first-issue templates in `.github/ISSUE_TEMPLATE/`.
|
|
320
|
+
Report a reproducible defect with the [bug form](https://github.com/aliengineering-byte/phaseprobe/issues/new?template=bug_report.yml), or propose a bounded adapter or workflow with the [feature form](https://github.com/aliengineering-byte/phaseprobe/issues/new?template=feature_request.yml). Please do not include secrets or private model data.
|
|
272
321
|
|
|
273
322
|
## License and citation
|
|
274
323
|
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
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.
|
|
6
6
|
|
|
7
7
|
```console
|
|
8
|
-
$
|
|
8
|
+
$ pip install phaseprobe pytest
|
|
9
9
|
$ phaseprobe scan --example logistic
|
|
10
10
|
QUALITATIVE TRANSITION FOUND
|
|
11
11
|
|
|
@@ -24,15 +24,24 @@ $ python -m pytest -q tests/generated
|
|
|
24
24
|
|
|
25
25
|
No API key, LLM, GPU, Docker, account, telemetry, network connection, or hosted service is required at runtime. PhaseProbe's base installation has no third-party runtime dependencies; NumPy and SciPy are isolated in the optional `scipy` extra.
|
|
26
26
|
|
|
27
|
+
## Installation
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
pip install phaseprobe
|
|
31
|
+
pip install "phaseprobe[scipy]"
|
|
32
|
+
pip install pytest # needed only to execute a generated regression test
|
|
33
|
+
```
|
|
34
|
+
|
|
27
35
|
## Five-minute quick start
|
|
28
36
|
|
|
29
37
|
Requires Python 3.10 or newer on Windows or Linux.
|
|
30
38
|
|
|
31
39
|
```bash
|
|
32
|
-
|
|
40
|
+
pip install phaseprobe pytest
|
|
33
41
|
phaseprobe scan --example logistic
|
|
34
42
|
phaseprobe replay .phaseprobe/runs/<run-id>/replay.json
|
|
35
43
|
phaseprobe generate-test .phaseprobe/runs/<run-id>/replay.json
|
|
44
|
+
phaseprobe verify-evidence tests/generated/logistic_map-pytest-evidence.json
|
|
36
45
|
python -m pytest -q tests/generated
|
|
37
46
|
phaseprobe report .phaseprobe/runs/<run-id>
|
|
38
47
|
```
|
|
@@ -52,22 +61,41 @@ Five-minute path:
|
|
|
52
61
|
[](assets/scipy-demo.gif)
|
|
53
62
|
|
|
54
63
|
The verified command transcript is [assets/scipy-demo-session.txt](assets/scipy-demo-session.txt),
|
|
55
|
-
with a self-contained [HTML example report](examples/scipy/report.html).
|
|
64
|
+
with a self-contained [HTML example report](examples/scipy/report.html). The following path starts
|
|
65
|
+
from a clean environment and does not require a source checkout:
|
|
56
66
|
|
|
57
67
|
```bash
|
|
58
|
-
python -m
|
|
59
|
-
|
|
60
|
-
|
|
68
|
+
python -m venv .venv
|
|
69
|
+
# Linux: source .venv/bin/activate
|
|
70
|
+
# Windows PowerShell: .venv\Scripts\Activate.ps1
|
|
71
|
+
python -m pip install "phaseprobe[scipy]" "pytest==8.4.1"
|
|
72
|
+
phaseprobe perturb --example scipy-lorenz
|
|
73
|
+
phaseprobe check --example scipy-predator-prey
|
|
61
74
|
phaseprobe replay .phaseprobe/runs/<run-id>/replay.json
|
|
62
75
|
phaseprobe generate-test .phaseprobe/runs/<run-id>/replay.json
|
|
63
76
|
python -m pytest -q tests/generated
|
|
64
77
|
```
|
|
65
78
|
|
|
79
|
+
PhaseProbe 0.2.1 also recognizes the former Issue #4 command
|
|
80
|
+
`phaseprobe perturb --config examples/scipy/lorenz.json` when that relative file is absent and
|
|
81
|
+
loads the corresponding packaged example. This compatibility is limited to the four former
|
|
82
|
+
`examples/scipy/` paths: an existing file always takes precedence, matching is case-sensitive,
|
|
83
|
+
both slash styles are accepted, and unrelated missing paths remain errors. New documentation and
|
|
84
|
+
automation should use the installed-safe `--example scipy-*` names.
|
|
85
|
+
|
|
66
86
|
The Lorenz command searches a declared initial-`x` perturbation and reports only finite-time
|
|
67
|
-
divergence evidence
|
|
68
|
-
The predator–prey command checks the
|
|
69
|
-
|
|
70
|
-
settings.
|
|
87
|
+
divergence evidence and prints `FINITE-TIME TRAJECTORY DIVERGENCE FOUND`. Its installed
|
|
88
|
+
short-window control is `--example scipy-lorenz-negative`. The predator–prey command checks the
|
|
89
|
+
declared first integral with tightly resolved DOP853 settings and prints `CHECK POLICY PASSED`;
|
|
90
|
+
`--example scipy-predator-prey-coarse` deliberately fails the same policy with loose RK23 settings.
|
|
91
|
+
Each successful command writes a replay fixture and offline report below
|
|
92
|
+
`.phaseprobe/runs/<run-id>/`.
|
|
93
|
+
|
|
94
|
+
Run `phaseprobe perturb --help` or `phaseprobe check --help` to list installed example names. A
|
|
95
|
+
missing packaged resource reports the PhaseProbe version and resource name. Adaptive SciPy replay
|
|
96
|
+
compares the declared state, observable, invariant, endpoint, event, and retained-grid
|
|
97
|
+
tolerances—it does not promise bitwise trajectory equality across platforms or dependency
|
|
98
|
+
versions.
|
|
71
99
|
|
|
72
100
|
For a direct Python API:
|
|
73
101
|
|
|
@@ -106,7 +134,8 @@ does not import it. See [the audited contract](docs/SCIPY_SOLVE_IVP_AUDIT.md),
|
|
|
106
134
|
| `perturb` | Baseline/perturbed twin runs over bounded initial-state changes | Finding or no finding, exit `0` |
|
|
107
135
|
| `check` | Execute a declared configuration policy for CI | Exit `1` only when policy fails |
|
|
108
136
|
| `replay` | Validate fixture integrity and re-execute model, parameters, seed, initial state, tolerances, and retention | Declared `exact` or `tolerance` comparison passes |
|
|
109
|
-
| `generate-test` | Validate and copy a fixture into a non-extensible pytest template | Executable test under `tests/generated/` |
|
|
137
|
+
| `generate-test` | Validate and copy a fixture into a non-extensible pytest template without conflicting overwrites | Executable test under `tests/generated/` |
|
|
138
|
+
| `verify-evidence` | Re-derive claims/replay decisions and validate bounded strict JSON, artifact hashes, paths, and the fixed template offline | Stable verified or invalid-input exit |
|
|
110
139
|
| `report` | Regenerate terminal, versioned JSON, and self-contained offline HTML evidence | Local report files |
|
|
111
140
|
|
|
112
141
|
Common options:
|
|
@@ -114,6 +143,7 @@ Common options:
|
|
|
114
143
|
```console
|
|
115
144
|
phaseprobe scan --config examples/configs/logistic-scan.json
|
|
116
145
|
phaseprobe perturb --example lorenz --json
|
|
146
|
+
phaseprobe perturb --example scipy-lorenz --json
|
|
117
147
|
phaseprobe check --example predator-prey
|
|
118
148
|
phaseprobe scan --example logistic --fail-on-finding
|
|
119
149
|
```
|
|
@@ -128,6 +158,8 @@ Exit codes are stable: `0` completed, `1` declared policy or explicit `--fail-on
|
|
|
128
158
|
| Lorenz system | `phaseprobe perturb --example lorenz` | Small initial separation exceeds the declared finite-time trajectory-distance threshold | Short window plus unreachable threshold reports no finding | [Lorenz, 1963](https://journals.ametsoc.org/view/journals/atsc/20/2/1520-0469_1963_020_0130_dnf_2_0_co_2.xml) |
|
|
129
159
|
| Predator–prey | `phaseprobe check --example predator-prey` | Refined RK4 step preserves the analytic first integral within tolerance | Coarse step fails the invariant-drift policy | [Lotka, 1920](https://doi.org/10.1073/pnas.6.7.410) |
|
|
130
160
|
| Genetic toggle | `phaseprobe perturb --example toggle` | Bounded initial-state perturbation reaches the opposite dominant state | Smaller declared range stays in the baseline basin | [Gardner, Cantor & Collins, 2000](https://www.nature.com/articles/35002131) |
|
|
161
|
+
| SciPy Lorenz | `phaseprobe perturb --example scipy-lorenz` | DOP853 twin trajectories cross the declared finite-time distance threshold | `--example scipy-lorenz-negative` shortens the window | [Lorenz, 1963](https://journals.ametsoc.org/view/journals/atsc/20/2/1520-0469_1963_020_0130_dnf_2_0_co_2.xml) |
|
|
162
|
+
| SciPy predator–prey | `phaseprobe check --example scipy-predator-prey` | Tight DOP853 settings preserve the declared first integral tolerance | `--example scipy-predator-prey-coarse` deliberately fails | [Lotka, 1920](https://doi.org/10.1073/pnas.6.7.410) |
|
|
131
163
|
|
|
132
164
|
Each 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](examples/README.md) for equations and interpretation.
|
|
133
165
|
|
|
@@ -150,6 +182,22 @@ manifest.json sizes and SHA-256 hashes
|
|
|
150
182
|
|
|
151
183
|
Model names used for generated test paths are sanitized. The generated source comes from a fixed template; configuration strings never become executable Python.
|
|
152
184
|
|
|
185
|
+
`generate-test` validates the replay before writing anything. Alongside the fixed pytest template
|
|
186
|
+
and copied fixture, it writes `<model>-pytest-evidence.json`: a path-portable claim/decision record
|
|
187
|
+
containing repository/version attribution, the declared replay comparisons, SHA-256 hashes for
|
|
188
|
+
both executable artifacts, the exact pytest command, and explicit scientific limitations.
|
|
189
|
+
Run `phaseprobe verify-evidence <evidence.json>` before pytest to validate the strict schema,
|
|
190
|
+
recompute the hashes, re-execute the replay policy, derive the claim/decision fields, and confirm
|
|
191
|
+
that the regression still matches PhaseProbe's fixed template. Validation is offline, bounded to a
|
|
192
|
+
1 MiB evidence document and 32 MiB per generated artifact, rejects duplicate keys and unsafe paths,
|
|
193
|
+
and exits `2` for invalid input.
|
|
194
|
+
The record is unsigned: consumers must recompute the two artifact hashes to detect a recomputed or
|
|
195
|
+
substituted record. It is portable integrity metadata, not an authentication signature.
|
|
196
|
+
|
|
197
|
+
If `generate-test` reports a conflict, it has left the existing output untouched. An identical
|
|
198
|
+
artifact can be generated again safely; for different content, inspect the existing files and
|
|
199
|
+
choose a fresh output directory (or remove them only after deciding they are no longer needed).
|
|
200
|
+
|
|
153
201
|
## What PhaseProbe adds—and what it does not
|
|
154
202
|
|
|
155
203
|
| existing category | established strength | PhaseProbe’s narrower job |
|
|
@@ -231,6 +279,7 @@ python scripts/hygiene.py
|
|
|
231
279
|
```
|
|
232
280
|
|
|
233
281
|
See [CONTRIBUTING.md](CONTRIBUTING.md) and the concrete good-first-issue templates in `.github/ISSUE_TEMPLATE/`.
|
|
282
|
+
Report a reproducible defect with the [bug form](https://github.com/aliengineering-byte/phaseprobe/issues/new?template=bug_report.yml), or propose a bounded adapter or workflow with the [feature form](https://github.com/aliengineering-byte/phaseprobe/issues/new?template=feature_request.yml). Please do not include secrets or private model data.
|
|
234
283
|
|
|
235
284
|
## License and citation
|
|
236
285
|
|
|
@@ -19,6 +19,9 @@ Tolerance replay compares retained times/states, named numeric observables, clas
|
|
|
19
19
|
invariant results and stored thresholds, endpoint/event evidence, and expected solver success.
|
|
20
20
|
Environment and solver evidence are retained for interpretation. Artifact hashes still detect
|
|
21
21
|
fixture tampering, but an adaptive trace hash is not required to match across supported platforms.
|
|
22
|
+
The adapter validation suite checks an exponential system with an analytic terminal crossing in
|
|
23
|
+
both forward and backward integration directions; this validates event plumbing, not exhaustive
|
|
24
|
+
root detection between adaptive internal steps.
|
|
22
25
|
See [the complete SciPy audit](docs/SCIPY_SOLVE_IVP_AUDIT.md).
|
|
23
26
|
|
|
24
27
|
PhaseProbe 0.1.0 produces bounded computational evidence. It does not perform formal verification, model validation against observations, or computer-assisted proof.
|