physverify 0.5.0__tar.gz → 0.6.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.
- {physverify-0.5.0/src/physverify.egg-info → physverify-0.6.0}/PKG-INFO +7 -4
- {physverify-0.5.0 → physverify-0.6.0}/README.md +5 -3
- {physverify-0.5.0 → physverify-0.6.0}/pyproject.toml +2 -2
- {physverify-0.5.0 → physverify-0.6.0}/src/physverify/__init__.py +1 -1
- {physverify-0.5.0 → physverify-0.6.0}/src/physverify/agent.py +12 -3
- {physverify-0.5.0 → physverify-0.6.0}/src/physverify/cli.py +20 -6
- {physverify-0.5.0 → physverify-0.6.0}/src/physverify/constants.py +3 -1
- {physverify-0.5.0 → physverify-0.6.0}/src/physverify/lint.py +18 -3
- physverify-0.6.0/src/physverify/probes.py +1057 -0
- {physverify-0.5.0 → physverify-0.6.0}/src/physverify/scan.py +52 -4
- {physverify-0.5.0 → physverify-0.6.0/src/physverify.egg-info}/PKG-INFO +7 -4
- {physverify-0.5.0 → physverify-0.6.0}/src/physverify.egg-info/SOURCES.txt +2 -0
- {physverify-0.5.0 → physverify-0.6.0}/src/physverify.egg-info/requires.txt +3 -0
- {physverify-0.5.0 → physverify-0.6.0}/tests/test_agent.py +16 -0
- physverify-0.6.0/tests/test_probes.py +314 -0
- {physverify-0.5.0 → physverify-0.6.0}/tests/test_scan.py +71 -0
- {physverify-0.5.0 → physverify-0.6.0}/tests/test_versions.py +5 -0
- {physverify-0.5.0 → physverify-0.6.0}/LICENSE +0 -0
- {physverify-0.5.0 → physverify-0.6.0}/setup.cfg +0 -0
- {physverify-0.5.0 → physverify-0.6.0}/src/physverify/__main__.py +0 -0
- {physverify-0.5.0 → physverify-0.6.0}/src/physverify/butcher.py +0 -0
- {physverify-0.5.0 → physverify-0.6.0}/src/physverify/checks.py +0 -0
- {physverify-0.5.0 → physverify-0.6.0}/src/physverify/demo.py +0 -0
- {physverify-0.5.0 → physverify-0.6.0}/src/physverify/identities.py +0 -0
- {physverify-0.5.0 → physverify-0.6.0}/src/physverify/loaders.py +0 -0
- {physverify-0.5.0 → physverify-0.6.0}/src/physverify/mms.py +0 -0
- {physverify-0.5.0 → physverify-0.6.0}/src/physverify/order.py +0 -0
- {physverify-0.5.0 → physverify-0.6.0}/src/physverify/pytest_plugin.py +0 -0
- {physverify-0.5.0 → physverify-0.6.0}/src/physverify/references.py +0 -0
- {physverify-0.5.0 → physverify-0.6.0}/src/physverify/report.py +0 -0
- {physverify-0.5.0 → physverify-0.6.0}/src/physverify/scaffold.py +0 -0
- {physverify-0.5.0 → physverify-0.6.0}/src/physverify/smoke.py +0 -0
- {physverify-0.5.0 → physverify-0.6.0}/src/physverify/steppers.py +0 -0
- {physverify-0.5.0 → physverify-0.6.0}/src/physverify/stochastic/__init__.py +0 -0
- {physverify-0.5.0 → physverify-0.6.0}/src/physverify/stochastic/calibrate.py +0 -0
- {physverify-0.5.0 → physverify-0.6.0}/src/physverify/stochastic/checks.py +0 -0
- {physverify-0.5.0 → physverify-0.6.0}/src/physverify/stochastic/exact.py +0 -0
- {physverify-0.5.0 → physverify-0.6.0}/src/physverify/stochastic/ising.py +0 -0
- {physverify-0.5.0 → physverify-0.6.0}/src/physverify/stochastic/reference.py +0 -0
- {physverify-0.5.0 → physverify-0.6.0}/src/physverify/stochastic/sampler.py +0 -0
- {physverify-0.5.0 → physverify-0.6.0}/src/physverify/stochastic/stats.py +0 -0
- {physverify-0.5.0 → physverify-0.6.0}/src/physverify/studies.py +0 -0
- {physverify-0.5.0 → physverify-0.6.0}/src/physverify/symbolic.py +0 -0
- {physverify-0.5.0 → physverify-0.6.0}/src/physverify.egg-info/dependency_links.txt +0 -0
- {physverify-0.5.0 → physverify-0.6.0}/src/physverify.egg-info/entry_points.txt +0 -0
- {physverify-0.5.0 → physverify-0.6.0}/src/physverify.egg-info/top_level.txt +0 -0
- {physverify-0.5.0 → physverify-0.6.0}/tests/test_awkward_input.py +0 -0
- {physverify-0.5.0 → physverify-0.6.0}/tests/test_batch7_lessons.py +0 -0
- {physverify-0.5.0 → physverify-0.6.0}/tests/test_batch8_lessons.py +0 -0
- {physverify-0.5.0 → physverify-0.6.0}/tests/test_butcher_properties.py +0 -0
- {physverify-0.5.0 → physverify-0.6.0}/tests/test_cli.py +0 -0
- {physverify-0.5.0 → physverify-0.6.0}/tests/test_corpus_lessons.py +0 -0
- {physverify-0.5.0 → physverify-0.6.0}/tests/test_docs_examples.py +0 -0
- {physverify-0.5.0 → physverify-0.6.0}/tests/test_figures.py +0 -0
- {physverify-0.5.0 → physverify-0.6.0}/tests/test_ising.py +0 -0
- {physverify-0.5.0 → physverify-0.6.0}/tests/test_method_bugs.py +0 -0
- {physverify-0.5.0 → physverify-0.6.0}/tests/test_mms.py +0 -0
- {physverify-0.5.0 → physverify-0.6.0}/tests/test_new_problems.py +0 -0
- {physverify-0.5.0 → physverify-0.6.0}/tests/test_order.py +0 -0
- {physverify-0.5.0 → physverify-0.6.0}/tests/test_properties.py +0 -0
- {physverify-0.5.0 → physverify-0.6.0}/tests/test_real_projects.py +0 -0
- {physverify-0.5.0 → physverify-0.6.0}/tests/test_reference_problems.py +0 -0
- {physverify-0.5.0 → physverify-0.6.0}/tests/test_references.py +0 -0
- {physverify-0.5.0 → physverify-0.6.0}/tests/test_scaffold.py +0 -0
- {physverify-0.5.0 → physverify-0.6.0}/tests/test_seeded_bugs.py +0 -0
- {physverify-0.5.0 → physverify-0.6.0}/tests/test_studies.py +0 -0
- {physverify-0.5.0 → physverify-0.6.0}/tests/test_v040_helpers.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: physverify
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.0
|
|
4
4
|
Summary: Verification checks for physics simulation code: manufactured solutions, order-of-accuracy studies and a seeded-bug benchmark.
|
|
5
5
|
Author: Ram Chandra Giri
|
|
6
6
|
License-Expression: MIT
|
|
@@ -27,6 +27,7 @@ Requires-Python: >=3.10
|
|
|
27
27
|
Description-Content-Type: text/markdown
|
|
28
28
|
License-File: LICENSE
|
|
29
29
|
Requires-Dist: numpy>=1.24
|
|
30
|
+
Requires-Dist: tomli>=1.1; python_version < "3.11"
|
|
30
31
|
Provides-Extra: symbolic
|
|
31
32
|
Requires-Dist: sympy>=1.12; extra == "symbolic"
|
|
32
33
|
Provides-Extra: dev
|
|
@@ -45,7 +46,7 @@ Dynamic: license-file
|
|
|
45
46
|
|
|
46
47
|
**Checks that a simulation solves the equations it claims, at the order of accuracy it claims, not just that it runs.**
|
|
47
48
|
|
|
48
|
-
physverify is actively evolving (v0.
|
|
49
|
+
physverify is actively evolving (v0.6.0), welcoming contributions and test cases from the computational physics community. Scope: numerical solutions of ODEs and PDEs (time integration, convergence, conservation), plus a few Monte Carlo checks. It needs only NumPy.
|
|
49
50
|
|
|
50
51
|

|
|
51
52
|
|
|
@@ -72,7 +73,7 @@ New to this? [Start here](https://rcgiri-physics.github.io/physverify/start-here
|
|
|
72
73
|
A failed check fails the job, like a broken unit test ([CI guide](https://rcgiri-physics.github.io/physverify/ci/): options, pre-commit, pytest):
|
|
73
74
|
|
|
74
75
|
```yaml
|
|
75
|
-
- uses: rcgiri-physics/physverify@v0.
|
|
76
|
+
- uses: rcgiri-physics/physverify@v0.6.0
|
|
76
77
|
with:
|
|
77
78
|
specs: tests/verify_*.py
|
|
78
79
|
```
|
|
@@ -89,4 +90,6 @@ Works best for ODEs; PDEs only in 1D so far. Your code needs a way to add a sour
|
|
|
89
90
|
|
|
90
91
|
## Contributing, citing, licence
|
|
91
92
|
|
|
92
|
-
Help is welcome: a trial report on your own solver, seeded bugs written by someone other than the author, adapters, documentation. See [CONTRIBUTING.md](https://github.com/rcgiri-physics/physverify/blob/main/CONTRIBUTING.md), the [code of conduct](https://github.com/rcgiri-physics/physverify/blob/main/CODE_OF_CONDUCT.md), [SUPPORT.md](https://github.com/rcgiri-physics/physverify/blob/main/SUPPORT.md) for help, [SECURITY.md](https://github.com/rcgiri-physics/physverify/blob/main/SECURITY.md) on running untrusted code, and [AGENTS.md](https://github.com/rcgiri-physics/physverify/blob/main/AGENTS.md) for AI assistants. To cite, use [doi:10.5281/zenodo.23534512](https://doi.org/10.5281/zenodo.23534512) or [CITATION.cff](https://github.com/rcgiri-physics/physverify/blob/main/CITATION.cff). Changes: [CHANGELOG.md](https://github.com/rcgiri-physics/physverify/blob/main/CHANGELOG.md); plans: [ROADMAP.md](https://github.com/rcgiri-physics/physverify/blob/main/ROADMAP.md). MIT licence, copyright (c) 2026 Ram Chandra Giri.
|
|
93
|
+
Help is welcome: a trial report on your own solver, seeded bugs written by someone other than the author, adapters, documentation. See [CONTRIBUTING.md](https://github.com/rcgiri-physics/physverify/blob/main/CONTRIBUTING.md), the [code of conduct](https://github.com/rcgiri-physics/physverify/blob/main/CODE_OF_CONDUCT.md), [SUPPORT.md](https://github.com/rcgiri-physics/physverify/blob/main/SUPPORT.md) for help, [SECURITY.md](https://github.com/rcgiri-physics/physverify/blob/main/SECURITY.md) on running untrusted code, and [AGENTS.md](https://github.com/rcgiri-physics/physverify/blob/main/AGENTS.md) for AI assistants. To cite, use [doi:10.5281/zenodo.23534512](https://doi.org/10.5281/zenodo.23534512) or [CITATION.cff](https://github.com/rcgiri-physics/physverify/blob/main/CITATION.cff). Changes: [CHANGELOG.md](https://github.com/rcgiri-physics/physverify/blob/main/CHANGELOG.md); plans: [ROADMAP.md](https://github.com/rcgiri-physics/physverify/blob/main/ROADMAP.md). The methods are standard and other tools do parts of this, some better (NodePy for Runge-Kutta analysis); see [related work and credits](https://rcgiri-physics.github.io/physverify/related-work/). MIT licence, copyright (c) 2026 Ram Chandra Giri.
|
|
94
|
+
|
|
95
|
+
<!-- mcp-name: io.github.rcgiri-physics/physverify -->
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
**Checks that a simulation solves the equations it claims, at the order of accuracy it claims, not just that it runs.**
|
|
6
6
|
|
|
7
|
-
physverify is actively evolving (v0.
|
|
7
|
+
physverify is actively evolving (v0.6.0), welcoming contributions and test cases from the computational physics community. Scope: numerical solutions of ODEs and PDEs (time integration, convergence, conservation), plus a few Monte Carlo checks. It needs only NumPy.
|
|
8
8
|
|
|
9
9
|

|
|
10
10
|
|
|
@@ -31,7 +31,7 @@ New to this? [Start here](https://rcgiri-physics.github.io/physverify/start-here
|
|
|
31
31
|
A failed check fails the job, like a broken unit test ([CI guide](https://rcgiri-physics.github.io/physverify/ci/): options, pre-commit, pytest):
|
|
32
32
|
|
|
33
33
|
```yaml
|
|
34
|
-
- uses: rcgiri-physics/physverify@v0.
|
|
34
|
+
- uses: rcgiri-physics/physverify@v0.6.0
|
|
35
35
|
with:
|
|
36
36
|
specs: tests/verify_*.py
|
|
37
37
|
```
|
|
@@ -48,4 +48,6 @@ Works best for ODEs; PDEs only in 1D so far. Your code needs a way to add a sour
|
|
|
48
48
|
|
|
49
49
|
## Contributing, citing, licence
|
|
50
50
|
|
|
51
|
-
Help is welcome: a trial report on your own solver, seeded bugs written by someone other than the author, adapters, documentation. See [CONTRIBUTING.md](https://github.com/rcgiri-physics/physverify/blob/main/CONTRIBUTING.md), the [code of conduct](https://github.com/rcgiri-physics/physverify/blob/main/CODE_OF_CONDUCT.md), [SUPPORT.md](https://github.com/rcgiri-physics/physverify/blob/main/SUPPORT.md) for help, [SECURITY.md](https://github.com/rcgiri-physics/physverify/blob/main/SECURITY.md) on running untrusted code, and [AGENTS.md](https://github.com/rcgiri-physics/physverify/blob/main/AGENTS.md) for AI assistants. To cite, use [doi:10.5281/zenodo.23534512](https://doi.org/10.5281/zenodo.23534512) or [CITATION.cff](https://github.com/rcgiri-physics/physverify/blob/main/CITATION.cff). Changes: [CHANGELOG.md](https://github.com/rcgiri-physics/physverify/blob/main/CHANGELOG.md); plans: [ROADMAP.md](https://github.com/rcgiri-physics/physverify/blob/main/ROADMAP.md). MIT licence, copyright (c) 2026 Ram Chandra Giri.
|
|
51
|
+
Help is welcome: a trial report on your own solver, seeded bugs written by someone other than the author, adapters, documentation. See [CONTRIBUTING.md](https://github.com/rcgiri-physics/physverify/blob/main/CONTRIBUTING.md), the [code of conduct](https://github.com/rcgiri-physics/physverify/blob/main/CODE_OF_CONDUCT.md), [SUPPORT.md](https://github.com/rcgiri-physics/physverify/blob/main/SUPPORT.md) for help, [SECURITY.md](https://github.com/rcgiri-physics/physverify/blob/main/SECURITY.md) on running untrusted code, and [AGENTS.md](https://github.com/rcgiri-physics/physverify/blob/main/AGENTS.md) for AI assistants. To cite, use [doi:10.5281/zenodo.23534512](https://doi.org/10.5281/zenodo.23534512) or [CITATION.cff](https://github.com/rcgiri-physics/physverify/blob/main/CITATION.cff). Changes: [CHANGELOG.md](https://github.com/rcgiri-physics/physverify/blob/main/CHANGELOG.md); plans: [ROADMAP.md](https://github.com/rcgiri-physics/physverify/blob/main/ROADMAP.md). The methods are standard and other tools do parts of this, some better (NodePy for Runge-Kutta analysis); see [related work and credits](https://rcgiri-physics.github.io/physverify/related-work/). MIT licence, copyright (c) 2026 Ram Chandra Giri.
|
|
52
|
+
|
|
53
|
+
<!-- mcp-name: io.github.rcgiri-physics/physverify -->
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "physverify"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.6.0"
|
|
8
8
|
description = "Verification checks for physics simulation code: manufactured solutions, order-of-accuracy studies and a seeded-bug benchmark."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -28,7 +28,7 @@ classifiers = [
|
|
|
28
28
|
"Topic :: Software Development :: Testing",
|
|
29
29
|
"Development Status :: 3 - Alpha",
|
|
30
30
|
]
|
|
31
|
-
dependencies = ["numpy>=1.24"]
|
|
31
|
+
dependencies = ["numpy>=1.24", "tomli>=1.1; python_version < '3.11'"]
|
|
32
32
|
|
|
33
33
|
[project.urls]
|
|
34
34
|
Homepage = "https://github.com/rcgiri-physics/physverify"
|
|
@@ -49,13 +49,18 @@ def find_specs(root: Path) -> list[Path]:
|
|
|
49
49
|
pyproject = root / "pyproject.toml"
|
|
50
50
|
if pyproject.is_file():
|
|
51
51
|
try:
|
|
52
|
-
|
|
52
|
+
try:
|
|
53
|
+
import tomllib
|
|
54
|
+
except ImportError: # Python 3.10
|
|
55
|
+
import tomli as tomllib
|
|
53
56
|
|
|
54
57
|
config = tomllib.loads(pyproject.read_text(encoding="utf-8")).get("tool", {}).get("physverify", {})
|
|
55
58
|
except Exception:
|
|
56
59
|
config = {}
|
|
57
60
|
if config.get("specs"):
|
|
58
|
-
|
|
61
|
+
# Entries that leave the root are dropped (GCVE-110-VULN-PYPI-2026-000391).
|
|
62
|
+
inside = root.resolve()
|
|
63
|
+
return [p for p in ((root / s).resolve() for s in config["specs"]) if p.is_relative_to(inside)]
|
|
59
64
|
specs = []
|
|
60
65
|
for path in sorted(root.rglob("verify_*.py")):
|
|
61
66
|
parts = path.relative_to(root).parts[:-1]
|
|
@@ -111,8 +116,12 @@ def hook_main(stdin=None, stderr=None) -> int:
|
|
|
111
116
|
edited = tool_input.get("file_path") or tool_input.get("notebook_path") or ""
|
|
112
117
|
if edited and not edited.endswith(".py"):
|
|
113
118
|
return 0
|
|
114
|
-
cwd = Path(event.get("cwd") or ".")
|
|
119
|
+
cwd = Path(event.get("cwd") or ".").resolve()
|
|
120
|
+
if edited and not Path(edited).resolve().is_relative_to(cwd):
|
|
121
|
+
return 0 # an edit outside the session folder must not run that folder's code (GCVE-110-VULN-PYPI-2026-000391)
|
|
115
122
|
root = find_project_root(Path(edited).parent if edited else cwd)
|
|
123
|
+
if not root.is_relative_to(cwd):
|
|
124
|
+
root = cwd # never climb above the session folder
|
|
116
125
|
specs = find_specs(root)
|
|
117
126
|
if not specs:
|
|
118
127
|
return 0
|
|
@@ -125,17 +125,24 @@ def _cmd_smoke(args) -> int:
|
|
|
125
125
|
def _cmd_scan(args) -> int:
|
|
126
126
|
import json
|
|
127
127
|
|
|
128
|
+
from .probes import DEFAULT_PROBES, PROBES, probe
|
|
128
129
|
from .scan import STAGES, WARN, scan, to_dict
|
|
129
130
|
|
|
130
|
-
|
|
131
|
-
|
|
131
|
+
known = STAGES + PROBES if args.run else STAGES
|
|
132
|
+
stages = args.only.split(",") if args.only else (STAGES + DEFAULT_PROBES if args.run else STAGES)
|
|
133
|
+
unknown = set(stages) - set(known)
|
|
132
134
|
if unknown:
|
|
133
|
-
|
|
135
|
+
extra = "; the probes need --run" if unknown <= set(PROBES) else ""
|
|
136
|
+
print(f"physverify: unknown stage {', '.join(sorted(unknown))}; choose from {', '.join(known)}{extra}", file=sys.stderr)
|
|
134
137
|
return 2
|
|
135
138
|
if not Path(args.path).exists():
|
|
136
139
|
print(f"physverify: {args.path} does not exist", file=sys.stderr)
|
|
137
140
|
return 2
|
|
138
|
-
findings, unparsed = scan(args.path, stages=stages)
|
|
141
|
+
findings, unparsed = scan(args.path, stages=[s for s in stages if s in STAGES])
|
|
142
|
+
probes = [s for s in stages if s in PROBES]
|
|
143
|
+
if probes:
|
|
144
|
+
print("physverify: --run calls the code; use it only on code you trust or inside the sandbox", file=sys.stderr)
|
|
145
|
+
findings += probe(args.path, probes=probes, timeout=args.timeout)
|
|
139
146
|
if args.json:
|
|
140
147
|
text = json.dumps(to_dict(findings, unparsed), indent=1)
|
|
141
148
|
if args.json == "-":
|
|
@@ -148,8 +155,11 @@ def _cmd_scan(args) -> int:
|
|
|
148
155
|
for u in unparsed:
|
|
149
156
|
print(f"NOTE parse {u}")
|
|
150
157
|
warn = sum(f.level == WARN for f in findings)
|
|
151
|
-
|
|
152
|
-
|
|
158
|
+
skip = sum(f.level == "SKIP" for f in findings)
|
|
159
|
+
ran = (f", {skip} skipped (not passed)" if probes else "")
|
|
160
|
+
how = "the probes called the code with small inputs" if probes else "the scan reads the code and runs nothing"
|
|
161
|
+
print(f"{warn} warnings, {len(findings) - warn - skip} notes{ran}, {len(unparsed)} files not parsed"
|
|
162
|
+
f" ({how}; a clean scan means only that these patterns were not found)")
|
|
153
163
|
return 1 if any(f.level == WARN for f in findings) else 0
|
|
154
164
|
|
|
155
165
|
|
|
@@ -204,6 +214,10 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
204
214
|
sc.add_argument("path", help="a Python file or a folder")
|
|
205
215
|
sc.add_argument("--only", metavar="STAGES", help="comma-separated stages: constants,tables,grid,pairs,names,api,literals,units (default all)")
|
|
206
216
|
sc.add_argument("--json", metavar="FILE", help="write findings as JSON to FILE, or to stdout with -")
|
|
217
|
+
sc.add_argument("--run", action="store_true",
|
|
218
|
+
help="also run the probes smoke and branches, which CALL the code (trusted code or the sandbox only); "
|
|
219
|
+
"the experimental probes edges,deadargs,roundtrip,siblings,known,monotone with --only")
|
|
220
|
+
sc.add_argument("--timeout", type=float, default=60.0, help="with --run: seconds per script or file of functions (default 60)")
|
|
207
221
|
sc.set_defaults(func=_cmd_scan)
|
|
208
222
|
|
|
209
223
|
r = sub.add_parser("references", help="list the exact reference solutions in physverify.references")
|
|
@@ -32,11 +32,13 @@ KNOWN = {
|
|
|
32
32
|
"e (charge)": ((1.602176634e-19,), "C", "exact: 1.602176634e-19"),
|
|
33
33
|
"m_e": ((9.1093837015e-31, 9.1093837139e-31), "kg", "CODATA 2018: 9.1093837015e-31, 2022: 9.1093837139e-31"),
|
|
34
34
|
"m_p": ((1.67262192369e-27, 1.67262192595e-27), "kg", "CODATA 2018: 1.67262192369e-27, 2022: 1.67262192595e-27"),
|
|
35
|
+
"m_n": ((1.67492749804e-27, 1.67492750056e-27), "kg", "CODATA 2018: 1.67492749804e-27, 2022: 1.67492750056e-27"),
|
|
36
|
+
"atomic mass unit": ((1.66053906660e-27, 1.66053906892e-27), "kg", "CODATA 2018: 1.66053906660e-27, 2022: 1.66053906892e-27"),
|
|
35
37
|
"epsilon_0": ((8.8541878128e-12,), "F/m", "8.8541878128e-12"),
|
|
36
38
|
"T_c (2D Ising)": ((2 / math.log(1 + math.sqrt(2)),), "J/k_B", "2 / ln(1 + sqrt 2) = 2.269185314"),
|
|
37
39
|
# exact by definition (SI 2019, IAU 2012, international units)
|
|
38
40
|
"N_A": ((6.02214076e23,), "1/mol", "exact: 6.02214076e23"),
|
|
39
|
-
"R (gas constant)": ((8.
|
|
41
|
+
"R (gas constant)": ((8.31446261815324,), "J/(mol K)", "exact: N_A k_B = 8.31446261815324"),
|
|
40
42
|
"sigma (Stefan-Boltzmann)": ((5.670374419e-8,), "W m^-2 K^-4", "5.670374419e-8"),
|
|
41
43
|
"astronomical unit": ((149597870700.0, 149597870.7, 1.495978707e13), "m, km or cm", "exact: 149597870700 m (IAU 2012)"),
|
|
42
44
|
"light year": ((9460730472580800.0,), "m", "exact: 9460730472580800 m (Julian year)"),
|
|
@@ -129,6 +129,10 @@ def _annotation_nodes(tree) -> set[int]:
|
|
|
129
129
|
return {id(m) for r in roots for m in ast.walk(r)}
|
|
130
130
|
|
|
131
131
|
|
|
132
|
+
# a file with this many undefined names, none of them a usual module alias, is treated as getting them at run time
|
|
133
|
+
_INJECTED_MIN = 6
|
|
134
|
+
_COMMON_ALIASES = {"np", "plt", "pd", "sp", "scipy", "numpy", "math", "os", "sys", "re", "json", "time", "random",
|
|
135
|
+
"torch", "jnp", "jax", "tf", "mpl", "sns", "nx", "integrate", "interpolate", "optimize", "linalg"}
|
|
132
136
|
_NOQA = re.compile(r"#\s*noqa(?::[^#]*\bF821\b|\s*$|\s*#)", re.I)
|
|
133
137
|
|
|
134
138
|
|
|
@@ -155,6 +159,14 @@ def scan_names(path: Path, tree, rel: str, source: str = "") -> list[Finding]:
|
|
|
155
159
|
continue # annotations are not evaluated with `from __future__ import annotations` (or from Python 3.14)
|
|
156
160
|
unknown.setdefault(n.id, []).append(n.lineno)
|
|
157
161
|
out = []
|
|
162
|
+
if len(unknown) >= _INJECTED_MIN and not unknown.keys() & _COMMON_ALIASES:
|
|
163
|
+
# many names and none of them a forgotten import: the file most likely gets them at run time
|
|
164
|
+
# (a host program such as SpaceClaim, or a library that defines names in the caller's namespace)
|
|
165
|
+
first = min(lines[0] for lines in unknown.values())
|
|
166
|
+
shown = ", ".join(sorted(unknown)[:8]) + (", ..." if len(unknown) > 8 else "")
|
|
167
|
+
return [Finding("names", NOTE, rel, first,
|
|
168
|
+
f"{len(unknown)} names are used but never defined or imported ({shown}); so many usually means they"
|
|
169
|
+
" are provided at run time by a host program or a library, so they are not reported one by one")]
|
|
158
170
|
for name, lines in sorted(unknown.items(), key=lambda kv: kv[1][0]):
|
|
159
171
|
close = [k for k in known if k != name and len(k) > 2 and _one_edit(name, k)]
|
|
160
172
|
hint = f" (did you mean {close[0]}?)" if close else ""
|
|
@@ -225,7 +237,8 @@ _SCIPY = {
|
|
|
225
237
|
"cumtrapz": "scipy.integrate.cumulative_trapezoid", "romb": None},
|
|
226
238
|
"scipy.misc": {"*": "scipy.datasets, scipy.special or numpy (scipy.misc was removed)"},
|
|
227
239
|
}
|
|
228
|
-
|
|
240
|
+
_SHIMMED = {"distutils"} # still importable wherever setuptools is installed, so only a NOTE
|
|
241
|
+
_PY_REMOVED = {"imp": "importlib (removed in Python 3.12)", "distutils": "setuptools or sysconfig (removed from the standard library in Python 3.12; setuptools still provides it)",
|
|
229
242
|
"asynchat": "asyncio (removed in Python 3.12)", "asyncore": "asyncio (removed in Python 3.12)",
|
|
230
243
|
"smtpd": "aiosmtpd (removed in Python 3.12)"}
|
|
231
244
|
|
|
@@ -281,12 +294,14 @@ def scan_api(path: Path, tree, rel: str) -> list[Finding]:
|
|
|
281
294
|
out.append(Finding("api", WARN, rel, n.lineno, f"{n.module}.{a.name} has been removed from SciPy"
|
|
282
295
|
+ (f"; use {new}" if new else "")))
|
|
283
296
|
if n.module.split(".")[0] in _PY_REMOVED:
|
|
284
|
-
|
|
297
|
+
top = n.module.split(".")[0]
|
|
298
|
+
out.append(Finding("api", NOTE if top in _SHIMMED else WARN, rel, n.lineno, f"{n.module}: use {_PY_REMOVED[top]}"))
|
|
285
299
|
if isinstance(n, ast.Import):
|
|
286
300
|
for a in n.names:
|
|
287
301
|
top = a.name.split(".")[0]
|
|
288
302
|
if top in _PY_REMOVED:
|
|
289
|
-
out.append(Finding("api", WARN, rel, n.lineno,
|
|
303
|
+
out.append(Finding("api", NOTE if top in _SHIMMED else WARN, rel, n.lineno,
|
|
304
|
+
f"import {a.name}: use {_PY_REMOVED[top]}"))
|
|
290
305
|
if a.name == "scipy.misc":
|
|
291
306
|
out.append(Finding("api", WARN, rel, n.lineno, "scipy.misc has been removed from SciPy"))
|
|
292
307
|
if isinstance(n, ast.Attribute) and n.attr in ("simps", "cumtrapz") and isinstance(n.value, ast.Attribute) \
|