phaseprobe 0.2.0__tar.gz → 0.2.1__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.2.1}/.github/workflows/ci.yml +24 -10
- phaseprobe-0.2.1/.github/workflows/publish-pypi.yml +166 -0
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/.github/workflows/release.yml +4 -7
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/CHANGELOG.md +21 -0
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/CITATION.cff +2 -2
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/PKG-INFO +43 -13
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/README.md +41 -11
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/SCIENTIFIC_METHODS.md +3 -0
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/assets/scipy-demo-session.txt +10 -10
- phaseprobe-0.2.1/assets/scipy-demo-static.png +0 -0
- phaseprobe-0.2.1/assets/scipy-demo.gif +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/docs/upstream/SCIPY_INTEGRATION_PROPOSAL.md +47 -11
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/examples/README.md +10 -5
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/pyproject.toml +1 -1
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/scripts/audit_package.py +36 -4
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/scripts/generate_scipy_demo.py +8 -8
- phaseprobe-0.2.1/scripts/installed_smoke.py +199 -0
- phaseprobe-0.2.1/scripts/verify_artifacts.py +221 -0
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/src/phaseprobe/__init__.py +1 -1
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/src/phaseprobe/cli.py +2 -2
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/src/phaseprobe/config.py +69 -18
- phaseprobe-0.2.1/src/phaseprobe/data/examples/scipy-lorenz-negative.json +54 -0
- phaseprobe-0.2.1/src/phaseprobe/data/examples/scipy-lorenz.json +54 -0
- phaseprobe-0.2.1/src/phaseprobe/data/examples/scipy-predator-prey-coarse.json +45 -0
- phaseprobe-0.2.1/src/phaseprobe/data/examples/scipy-predator-prey.json +45 -0
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/src/phaseprobe/generate.py +21 -5
- phaseprobe-0.2.1/src/phaseprobe/py.typed +1 -0
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/tests/test_artifacts_replay.py +13 -0
- phaseprobe-0.2.1/tests/test_config.py +148 -0
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/tests/test_optional_dependency.py +7 -1
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/tests/test_scipy_adapter.py +52 -6
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/uv.lock +1 -1
- 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/tests/test_config.py +0 -69
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/.github/ISSUE_TEMPLATE/good_first_accessibility.yml +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/.github/ISSUE_TEMPLATE/good_first_pendulum.yml +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/.github/ISSUE_TEMPLATE/good_first_schema.yml +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/.gitignore +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/AGENTS.md +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/ARCHITECTURE.md +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/CODE_OF_CONDUCT.md +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/CONTRIBUTING.md +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/LICENSE +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/LIMITATIONS.md +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/PRIOR_ART.md +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/ROADMAP.md +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/SECURITY.md +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/assets/demo-session.txt +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/assets/demo-static.png +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/assets/demo.gif +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/assets/social-preview.jpg +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/docs/SCIPY_SOLVE_IVP_AUDIT.md +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/examples/configs/logistic-negative.json +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/examples/configs/logistic-scan.json +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/examples/configs/lorenz-negative.json +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/examples/configs/lorenz-perturb.json +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/examples/configs/predator-prey-check.json +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/examples/configs/predator-prey-negative.json +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/examples/configs/toggle-negative.json +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/examples/configs/toggle-perturb.json +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/examples/scipy/lorenz-negative.json +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/examples/scipy/lorenz.json +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/examples/scipy/predator-prey-coarse.json +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/examples/scipy/predator-prey.json +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/examples/scipy/report.html +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/scripts/check_links.py +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/scripts/generate_demo.py +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/scripts/hygiene.py +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/src/phaseprobe/__main__.py +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/src/phaseprobe/adapters/__init__.py +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/src/phaseprobe/adapters/loader.py +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/src/phaseprobe/adapters/scipy.py +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/src/phaseprobe/api.py +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/src/phaseprobe/artifacts.py +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/src/phaseprobe/data/__init__.py +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/src/phaseprobe/data/examples/__init__.py +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/src/phaseprobe/data/examples/logistic-negative.json +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/src/phaseprobe/data/examples/logistic-scan.json +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/src/phaseprobe/data/examples/lorenz-negative.json +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/src/phaseprobe/data/examples/lorenz-perturb.json +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/src/phaseprobe/data/examples/predator-prey-check.json +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/src/phaseprobe/data/examples/predator-prey-negative.json +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/src/phaseprobe/data/examples/toggle-negative.json +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/src/phaseprobe/data/examples/toggle-perturb.json +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/src/phaseprobe/engine.py +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/src/phaseprobe/errors.py +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/src/phaseprobe/examples/__init__.py +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/src/phaseprobe/examples/scipy_models.py +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/src/phaseprobe/models/__init__.py +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/src/phaseprobe/models/_common.py +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/src/phaseprobe/models/logistic.py +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/src/phaseprobe/models/lorenz.py +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/src/phaseprobe/models/predator_prey.py +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/src/phaseprobe/models/toggle.py +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/src/phaseprobe/replay.py +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/src/phaseprobe/reporting.py +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/src/phaseprobe/types.py +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/tests/conftest.py +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/tests/generated/fixtures/logistic_map-replay.json +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/tests/generated/fixtures/lorenz_scipy-replay.json +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/tests/generated/fixtures/predator_prey_scipy-replay.json +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/tests/generated/test_logistic_map_transition.py +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/tests/generated/test_lorenz_scipy_transition.py +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/tests/generated/test_predator_prey_scipy_transition.py +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/tests/test_cli.py +0 -0
- {phaseprobe-0.2.0 → phaseprobe-0.2.1}/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:
|
|
@@ -2,6 +2,27 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes are documented here. PhaseProbe follows semantic versioning.
|
|
4
4
|
|
|
5
|
+
## 0.2.1 — 2026-08-31
|
|
6
|
+
|
|
7
|
+
- Fixed Issue #4: all four SciPy quick-start configurations now ship inside the importable
|
|
8
|
+
package in both wheel and source distribution and load through `importlib.resources` as the
|
|
9
|
+
`scipy-lorenz`, `scipy-lorenz-negative`, `scipy-predator-prey`, and
|
|
10
|
+
`scipy-predator-prey-coarse` built-in examples.
|
|
11
|
+
- Preserved the four exact former `examples/scipy/` config paths as narrow compatibility aliases
|
|
12
|
+
when no file exists at the requested path. Existing user files take precedence; matching is
|
|
13
|
+
case-sensitive and separator-portable, with no fuzzy or basename fallback.
|
|
14
|
+
- Added actionable, versioned diagnostics for missing or malformed built-in resources without
|
|
15
|
+
changing arbitrary config-path or built-in-example behavior.
|
|
16
|
+
- Added Linux and Windows Python 3.12 release gates that build, inspect, install, and exercise the
|
|
17
|
+
wheel and sdist outside the checkout with `PYTHONPATH` removed. The gate runs scan, replay,
|
|
18
|
+
generated pytest, SciPy Lorenz, SciPy predator–prey, `pip check`, and a dependency-free base
|
|
19
|
+
wheel check.
|
|
20
|
+
- Added a source-derived runtime-resource audit and the PEP 561 `py.typed` marker.
|
|
21
|
+
- Generated regression creation is now idempotent for identical evidence and rejects conflicting
|
|
22
|
+
files instead of silently overwriting them.
|
|
23
|
+
- Added an analytic backward-time directional-event check while preserving all solver defaults
|
|
24
|
+
and the existing tolerance-based adaptive replay contract.
|
|
25
|
+
|
|
5
26
|
## 0.2.0 — 2026-08-02
|
|
6
27
|
|
|
7
28
|
- Added the backward-compatible `TrajectoryAdapter` protocol and shared engine dispatch; all v0.1
|
|
@@ -4,8 +4,8 @@ title: "PhaseProbe"
|
|
|
4
4
|
type: software
|
|
5
5
|
authors:
|
|
6
6
|
- name: "Ali"
|
|
7
|
-
version: 0.2.
|
|
8
|
-
date-released: 2026-08-
|
|
7
|
+
version: 0.2.1
|
|
8
|
+
date-released: 2026-08-31
|
|
9
9
|
url: "https://github.com/aliengineering-byte/phaseprobe"
|
|
10
10
|
repository-code: "https://github.com/aliengineering-byte/phaseprobe"
|
|
11
11
|
license: Apache-2.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: phaseprobe
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.1
|
|
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,12 +62,20 @@ $ 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
|
|
@@ -90,22 +98,41 @@ Five-minute path:
|
|
|
90
98
|
[](assets/scipy-demo.gif)
|
|
91
99
|
|
|
92
100
|
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).
|
|
101
|
+
with a self-contained [HTML example report](examples/scipy/report.html). The following path starts
|
|
102
|
+
from a clean environment and does not require a source checkout:
|
|
94
103
|
|
|
95
104
|
```bash
|
|
96
|
-
python -m
|
|
97
|
-
|
|
98
|
-
|
|
105
|
+
python -m venv .venv
|
|
106
|
+
# Linux: source .venv/bin/activate
|
|
107
|
+
# Windows PowerShell: .venv\Scripts\Activate.ps1
|
|
108
|
+
python -m pip install "phaseprobe[scipy]" "pytest==8.4.1"
|
|
109
|
+
phaseprobe perturb --example scipy-lorenz
|
|
110
|
+
phaseprobe check --example scipy-predator-prey
|
|
99
111
|
phaseprobe replay .phaseprobe/runs/<run-id>/replay.json
|
|
100
112
|
phaseprobe generate-test .phaseprobe/runs/<run-id>/replay.json
|
|
101
113
|
python -m pytest -q tests/generated
|
|
102
114
|
```
|
|
103
115
|
|
|
116
|
+
PhaseProbe 0.2.1 also recognizes the former Issue #4 command
|
|
117
|
+
`phaseprobe perturb --config examples/scipy/lorenz.json` when that relative file is absent and
|
|
118
|
+
loads the corresponding packaged example. This compatibility is limited to the four former
|
|
119
|
+
`examples/scipy/` paths: an existing file always takes precedence, matching is case-sensitive,
|
|
120
|
+
both slash styles are accepted, and unrelated missing paths remain errors. New documentation and
|
|
121
|
+
automation should use the installed-safe `--example scipy-*` names.
|
|
122
|
+
|
|
104
123
|
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.
|
|
124
|
+
divergence evidence and prints `FINITE-TIME TRAJECTORY DIVERGENCE FOUND`. Its installed
|
|
125
|
+
short-window control is `--example scipy-lorenz-negative`. The predator–prey command checks the
|
|
126
|
+
declared first integral with tightly resolved DOP853 settings and prints `CHECK POLICY PASSED`;
|
|
127
|
+
`--example scipy-predator-prey-coarse` deliberately fails the same policy with loose RK23 settings.
|
|
128
|
+
Each successful command writes a replay fixture and offline report below
|
|
129
|
+
`.phaseprobe/runs/<run-id>/`.
|
|
130
|
+
|
|
131
|
+
Run `phaseprobe perturb --help` or `phaseprobe check --help` to list installed example names. A
|
|
132
|
+
missing packaged resource reports the PhaseProbe version and resource name. Adaptive SciPy replay
|
|
133
|
+
compares the declared state, observable, invariant, endpoint, event, and retained-grid
|
|
134
|
+
tolerances—it does not promise bitwise trajectory equality across platforms or dependency
|
|
135
|
+
versions.
|
|
109
136
|
|
|
110
137
|
For a direct Python API:
|
|
111
138
|
|
|
@@ -144,7 +171,7 @@ does not import it. See [the audited contract](docs/SCIPY_SOLVE_IVP_AUDIT.md),
|
|
|
144
171
|
| `perturb` | Baseline/perturbed twin runs over bounded initial-state changes | Finding or no finding, exit `0` |
|
|
145
172
|
| `check` | Execute a declared configuration policy for CI | Exit `1` only when policy fails |
|
|
146
173
|
| `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/` |
|
|
174
|
+
| `generate-test` | Validate and copy a fixture into a non-extensible pytest template without conflicting overwrites | Executable test under `tests/generated/` |
|
|
148
175
|
| `report` | Regenerate terminal, versioned JSON, and self-contained offline HTML evidence | Local report files |
|
|
149
176
|
|
|
150
177
|
Common options:
|
|
@@ -152,6 +179,7 @@ Common options:
|
|
|
152
179
|
```console
|
|
153
180
|
phaseprobe scan --config examples/configs/logistic-scan.json
|
|
154
181
|
phaseprobe perturb --example lorenz --json
|
|
182
|
+
phaseprobe perturb --example scipy-lorenz --json
|
|
155
183
|
phaseprobe check --example predator-prey
|
|
156
184
|
phaseprobe scan --example logistic --fail-on-finding
|
|
157
185
|
```
|
|
@@ -166,6 +194,8 @@ Exit codes are stable: `0` completed, `1` declared policy or explicit `--fail-on
|
|
|
166
194
|
| 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
195
|
| 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
196
|
| 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) |
|
|
197
|
+
| 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) |
|
|
198
|
+
| 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
199
|
|
|
170
200
|
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
201
|
|
|
@@ -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,12 +24,20 @@ $ 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
|
|
@@ -52,22 +60,41 @@ Five-minute path:
|
|
|
52
60
|
[](assets/scipy-demo.gif)
|
|
53
61
|
|
|
54
62
|
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).
|
|
63
|
+
with a self-contained [HTML example report](examples/scipy/report.html). The following path starts
|
|
64
|
+
from a clean environment and does not require a source checkout:
|
|
56
65
|
|
|
57
66
|
```bash
|
|
58
|
-
python -m
|
|
59
|
-
|
|
60
|
-
|
|
67
|
+
python -m venv .venv
|
|
68
|
+
# Linux: source .venv/bin/activate
|
|
69
|
+
# Windows PowerShell: .venv\Scripts\Activate.ps1
|
|
70
|
+
python -m pip install "phaseprobe[scipy]" "pytest==8.4.1"
|
|
71
|
+
phaseprobe perturb --example scipy-lorenz
|
|
72
|
+
phaseprobe check --example scipy-predator-prey
|
|
61
73
|
phaseprobe replay .phaseprobe/runs/<run-id>/replay.json
|
|
62
74
|
phaseprobe generate-test .phaseprobe/runs/<run-id>/replay.json
|
|
63
75
|
python -m pytest -q tests/generated
|
|
64
76
|
```
|
|
65
77
|
|
|
78
|
+
PhaseProbe 0.2.1 also recognizes the former Issue #4 command
|
|
79
|
+
`phaseprobe perturb --config examples/scipy/lorenz.json` when that relative file is absent and
|
|
80
|
+
loads the corresponding packaged example. This compatibility is limited to the four former
|
|
81
|
+
`examples/scipy/` paths: an existing file always takes precedence, matching is case-sensitive,
|
|
82
|
+
both slash styles are accepted, and unrelated missing paths remain errors. New documentation and
|
|
83
|
+
automation should use the installed-safe `--example scipy-*` names.
|
|
84
|
+
|
|
66
85
|
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.
|
|
86
|
+
divergence evidence and prints `FINITE-TIME TRAJECTORY DIVERGENCE FOUND`. Its installed
|
|
87
|
+
short-window control is `--example scipy-lorenz-negative`. The predator–prey command checks the
|
|
88
|
+
declared first integral with tightly resolved DOP853 settings and prints `CHECK POLICY PASSED`;
|
|
89
|
+
`--example scipy-predator-prey-coarse` deliberately fails the same policy with loose RK23 settings.
|
|
90
|
+
Each successful command writes a replay fixture and offline report below
|
|
91
|
+
`.phaseprobe/runs/<run-id>/`.
|
|
92
|
+
|
|
93
|
+
Run `phaseprobe perturb --help` or `phaseprobe check --help` to list installed example names. A
|
|
94
|
+
missing packaged resource reports the PhaseProbe version and resource name. Adaptive SciPy replay
|
|
95
|
+
compares the declared state, observable, invariant, endpoint, event, and retained-grid
|
|
96
|
+
tolerances—it does not promise bitwise trajectory equality across platforms or dependency
|
|
97
|
+
versions.
|
|
71
98
|
|
|
72
99
|
For a direct Python API:
|
|
73
100
|
|
|
@@ -106,7 +133,7 @@ does not import it. See [the audited contract](docs/SCIPY_SOLVE_IVP_AUDIT.md),
|
|
|
106
133
|
| `perturb` | Baseline/perturbed twin runs over bounded initial-state changes | Finding or no finding, exit `0` |
|
|
107
134
|
| `check` | Execute a declared configuration policy for CI | Exit `1` only when policy fails |
|
|
108
135
|
| `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/` |
|
|
136
|
+
| `generate-test` | Validate and copy a fixture into a non-extensible pytest template without conflicting overwrites | Executable test under `tests/generated/` |
|
|
110
137
|
| `report` | Regenerate terminal, versioned JSON, and self-contained offline HTML evidence | Local report files |
|
|
111
138
|
|
|
112
139
|
Common options:
|
|
@@ -114,6 +141,7 @@ Common options:
|
|
|
114
141
|
```console
|
|
115
142
|
phaseprobe scan --config examples/configs/logistic-scan.json
|
|
116
143
|
phaseprobe perturb --example lorenz --json
|
|
144
|
+
phaseprobe perturb --example scipy-lorenz --json
|
|
117
145
|
phaseprobe check --example predator-prey
|
|
118
146
|
phaseprobe scan --example logistic --fail-on-finding
|
|
119
147
|
```
|
|
@@ -128,6 +156,8 @@ Exit codes are stable: `0` completed, `1` declared policy or explicit `--fail-on
|
|
|
128
156
|
| 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
157
|
| 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
158
|
| 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) |
|
|
159
|
+
| 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) |
|
|
160
|
+
| 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
161
|
|
|
132
162
|
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
163
|
|
|
@@ -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.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
$ phaseprobe perturb --
|
|
1
|
+
$ phaseprobe perturb --example scipy-lorenz
|
|
2
2
|
FINITE-TIME TRAJECTORY DIVERGENCE FOUND
|
|
3
3
|
|
|
4
4
|
Model: lorenz-scipy
|
|
@@ -10,12 +10,12 @@ Baseline regime: bounded-finite-window
|
|
|
10
10
|
Changed regime: bounded-finite-window
|
|
11
11
|
Invariant violations: 0
|
|
12
12
|
Repeatable: true
|
|
13
|
-
Replay: <repository>/.cache/phaseprobe-scipy-demo-
|
|
13
|
+
Replay: <repository>/.cache/phaseprobe-scipy-demo-4sai_vv1/runs/lorenz/20260831T174342Z-7fcc5f8d/replay.json
|
|
14
14
|
|
|
15
15
|
Scope: Smallest reproducible perturbation found within the declared finite search; not a proof of global minimality.
|
|
16
|
-
HTML report: <repository>/.cache/phaseprobe-scipy-demo-
|
|
16
|
+
HTML report: <repository>/.cache/phaseprobe-scipy-demo-4sai_vv1/runs/lorenz/20260831T174342Z-7fcc5f8d/report.html
|
|
17
17
|
|
|
18
|
-
$ phaseprobe check --
|
|
18
|
+
$ phaseprobe check --example scipy-predator-prey
|
|
19
19
|
CHECK POLICY PASSED
|
|
20
20
|
|
|
21
21
|
Model: predator-prey-scipy
|
|
@@ -23,8 +23,8 @@ Baseline regime: positive-oscillation
|
|
|
23
23
|
Changed regime: n/a
|
|
24
24
|
Invariant violations: 0
|
|
25
25
|
Repeatable: true
|
|
26
|
-
Replay: <repository>/.cache/phaseprobe-scipy-demo-
|
|
27
|
-
HTML report: <repository>/.cache/phaseprobe-scipy-demo-
|
|
26
|
+
Replay: <repository>/.cache/phaseprobe-scipy-demo-4sai_vv1/runs/predator-prey/20260831T174343Z-38b5e8de/replay.json
|
|
27
|
+
HTML report: <repository>/.cache/phaseprobe-scipy-demo-4sai_vv1/runs/predator-prey/20260831T174343Z-38b5e8de/report.html
|
|
28
28
|
|
|
29
29
|
$ phaseprobe replay .phaseprobe/runs/<run-id>/replay.json
|
|
30
30
|
REPLAY VERIFIED
|
|
@@ -36,11 +36,11 @@ baseline: declared tolerances=True
|
|
|
36
36
|
$ phaseprobe generate-test .phaseprobe/runs/<run-id>/replay.json
|
|
37
37
|
PYTEST REGRESSION GENERATED
|
|
38
38
|
|
|
39
|
-
Test: <repository>/.cache/phaseprobe-scipy-demo-
|
|
40
|
-
Replay fixture: <repository>/.cache/phaseprobe-scipy-demo-
|
|
39
|
+
Test: <repository>/.cache/phaseprobe-scipy-demo-4sai_vv1/generated/test_predator_prey_scipy_transition.py
|
|
40
|
+
Replay fixture: <repository>/.cache/phaseprobe-scipy-demo-4sai_vv1/generated/fixtures/predator_prey_scipy-replay.json
|
|
41
41
|
|
|
42
42
|
$ python -m pytest -q tests/generated
|
|
43
43
|
. [100%]
|
|
44
|
-
1 passed in 1.
|
|
44
|
+
1 passed in 1.08s
|
|
45
45
|
|
|
46
|
-
Measured demo run: lorenz=
|
|
46
|
+
Measured demo run: lorenz=6.285s, predator-prey=1.409s, replay=1.336s, generate=1.340s, pytest=1.814s.
|
|
Binary file
|
|
Binary file
|
|
@@ -44,8 +44,8 @@ plus solver configuration metadata; it does not hash or serialize callable sourc
|
|
|
44
44
|
|
|
45
45
|
```bash
|
|
46
46
|
python -m pip install "phaseprobe[scipy]"
|
|
47
|
-
phaseprobe perturb --
|
|
48
|
-
phaseprobe check --
|
|
47
|
+
phaseprobe perturb --example scipy-lorenz
|
|
48
|
+
phaseprobe check --example scipy-predator-prey
|
|
49
49
|
phaseprobe replay .phaseprobe/runs/<run-id>/replay.json
|
|
50
50
|
phaseprobe generate-test .phaseprobe/runs/<run-id>/replay.json
|
|
51
51
|
```
|
|
@@ -95,15 +95,51 @@ propose this external qualitative regression/replay layer.
|
|
|
95
95
|
Project quick start and terminal assets:
|
|
96
96
|
https://github.com/aliengineering-byte/phaseprobe#use-with-scipy
|
|
97
97
|
|
|
98
|
-
##
|
|
98
|
+
## Discussion title and body
|
|
99
99
|
|
|
100
|
-
`PhaseProbe:
|
|
100
|
+
`PhaseProbe: reproducible transition detection and regression generation for solve_ivp models`
|
|
101
101
|
|
|
102
|
-
|
|
102
|
+
Hi SciPy community,
|
|
103
103
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
104
|
+
I built PhaseProbe, an open-source downstream testing tool for dynamical-system simulations. It
|
|
105
|
+
uses `scipy.integrate.solve_ivp` through a trajectory-level adapter to search bounded parameter or
|
|
106
|
+
initial-condition regions for reproducible qualitative transitions, preserve replay evidence, and
|
|
107
|
+
generate pytest regression tests from the findings.
|
|
108
|
+
|
|
109
|
+
The motivation is a practical testing problem: a numerical model can continue integrating
|
|
110
|
+
successfully while a small parameter or initial-condition change moves it into a different
|
|
111
|
+
qualitative regime. PhaseProbe records the search configuration, solver method and tolerances,
|
|
112
|
+
environment evidence, trajectory hashes, observables, classifications, and transition bracket so
|
|
113
|
+
that the result can be independently replayed.
|
|
114
|
+
|
|
115
|
+
The current release includes examples for Lorenz finite-time divergence and predator–prey
|
|
116
|
+
invariant drift. For adaptive SciPy integrations, replay is tolerance-based rather than claimed to
|
|
117
|
+
be bit-exact. The results are explicitly bounded, finite-time numerical evidence—not proofs of
|
|
118
|
+
chaos, exact bifurcation locations, Lyapunov estimates, or globally minimal perturbations.
|
|
119
|
+
|
|
120
|
+
Repository:
|
|
121
|
+
https://github.com/aliengineering-byte/phaseprobe
|
|
122
|
+
|
|
123
|
+
Release:
|
|
124
|
+
https://github.com/aliengineering-byte/phaseprobe/releases/tag/v0.2.0
|
|
125
|
+
|
|
126
|
+
PyPI:
|
|
127
|
+
https://pypi.org/project/phaseprobe/
|
|
128
|
+
|
|
129
|
+
Technical proposal and limitations:
|
|
130
|
+
https://github.com/aliengineering-byte/phaseprobe/blob/v0.2.0/docs/upstream/SCIPY_INTEGRATION_PROPOSAL.md
|
|
131
|
+
|
|
132
|
+
The release links above preserve the original 0.2.0 integration record. The installed-safe
|
|
133
|
+
`--example scipy-*` commands shown here are included in the planned 0.2.1 packaging fix.
|
|
134
|
+
|
|
135
|
+
I am not proposing to add PhaseProbe to SciPy core. I would value feedback on three points:
|
|
136
|
+
|
|
137
|
+
1. Whether the tolerance-based replay evidence for adaptive `solve_ivp` trajectories is
|
|
138
|
+
scientifically and practically appropriate.
|
|
139
|
+
2. Whether the trajectory-level adapter and recorded solver/environment metadata omit evidence
|
|
140
|
+
that SciPy users would expect.
|
|
141
|
+
3. Whether a downstream example or ecosystem/documentation reference could be appropriate if the
|
|
142
|
+
tool proves useful to users.
|
|
143
|
+
|
|
144
|
+
Feedback on the design, terminology, numerical claims, and useful real-world test cases would be
|
|
145
|
+
very welcome.
|