opensci-engine 0.1.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.
- opensci_engine-0.1.0/.gitignore +11 -0
- opensci_engine-0.1.0/PKG-INFO +121 -0
- opensci_engine-0.1.0/README.md +102 -0
- opensci_engine-0.1.0/docs/ENGINE_ARCHITECTURE.md +149 -0
- opensci_engine-0.1.0/docs/NUMERICAL_CONVENTIONS.md +181 -0
- opensci_engine-0.1.0/docs/PHYSICS_SCOPE.md +477 -0
- opensci_engine-0.1.0/docs/PYODIDE_COMPATIBILITY.md +68 -0
- opensci_engine-0.1.0/docs/generate_docs.py +41 -0
- opensci_engine-0.1.0/pyproject.toml +56 -0
- opensci_engine-0.1.0/src/opensci_engine/__init__.py +32 -0
- opensci_engine-0.1.0/src/opensci_engine/__main__.py +3 -0
- opensci_engine-0.1.0/src/opensci_engine/_version.py +24 -0
- opensci_engine-0.1.0/src/opensci_engine/builders.py +328 -0
- opensci_engine-0.1.0/src/opensci_engine/cli.py +69 -0
- opensci_engine-0.1.0/src/opensci_engine/compare.py +79 -0
- opensci_engine-0.1.0/src/opensci_engine/config.py +158 -0
- opensci_engine-0.1.0/src/opensci_engine/contracts/__init__.py +76 -0
- opensci_engine-0.1.0/src/opensci_engine/contracts/base.py +28 -0
- opensci_engine-0.1.0/src/opensci_engine/contracts/components.py +143 -0
- opensci_engine-0.1.0/src/opensci_engine/contracts/geometry.py +84 -0
- opensci_engine-0.1.0/src/opensci_engine/contracts/manifest.py +27 -0
- opensci_engine-0.1.0/src/opensci_engine/contracts/materials.py +127 -0
- opensci_engine-0.1.0/src/opensci_engine/contracts/optics.py +281 -0
- opensci_engine-0.1.0/src/opensci_engine/contracts/polarization.py +77 -0
- opensci_engine-0.1.0/src/opensci_engine/contracts/project.py +143 -0
- opensci_engine-0.1.0/src/opensci_engine/contracts/request.py +25 -0
- opensci_engine-0.1.0/src/opensci_engine/contracts/results.py +218 -0
- opensci_engine-0.1.0/src/opensci_engine/control/__init__.py +19 -0
- opensci_engine-0.1.0/src/opensci_engine/control/actuation.py +73 -0
- opensci_engine-0.1.0/src/opensci_engine/control/analysis.py +135 -0
- opensci_engine-0.1.0/src/opensci_engine/control/lm.py +165 -0
- opensci_engine-0.1.0/src/opensci_engine/control/readings.py +61 -0
- opensci_engine-0.1.0/src/opensci_engine/control/sensitivity.py +108 -0
- opensci_engine-0.1.0/src/opensci_engine/errors/__init__.py +6 -0
- opensci_engine-0.1.0/src/opensci_engine/errors/codes.py +83 -0
- opensci_engine-0.1.0/src/opensci_engine/errors/exceptions.py +19 -0
- opensci_engine-0.1.0/src/opensci_engine/errors/issues.py +69 -0
- opensci_engine-0.1.0/src/opensci_engine/errors/status.py +42 -0
- opensci_engine-0.1.0/src/opensci_engine/layout/__init__.py +6 -0
- opensci_engine-0.1.0/src/opensci_engine/layout/checks.py +282 -0
- opensci_engine-0.1.0/src/opensci_engine/layout/geometry.py +168 -0
- opensci_engine-0.1.0/src/opensci_engine/manifest/__init__.py +13 -0
- opensci_engine-0.1.0/src/opensci_engine/manifest/build.py +51 -0
- opensci_engine-0.1.0/src/opensci_engine/manifest/canonical.py +62 -0
- opensci_engine-0.1.0/src/opensci_engine/math/__init__.py +37 -0
- opensci_engine-0.1.0/src/opensci_engine/math/pose.py +61 -0
- opensci_engine-0.1.0/src/opensci_engine/math/rng.py +49 -0
- opensci_engine-0.1.0/src/opensci_engine/math/rotation.py +74 -0
- opensci_engine-0.1.0/src/opensci_engine/math/vectors.py +96 -0
- opensci_engine-0.1.0/src/opensci_engine/optics/__init__.py +0 -0
- opensci_engine-0.1.0/src/opensci_engine/optics/gaussian/__init__.py +24 -0
- opensci_engine-0.1.0/src/opensci_engine/optics/gaussian/ops.py +185 -0
- opensci_engine-0.1.0/src/opensci_engine/optics/gaussian/state.py +143 -0
- opensci_engine-0.1.0/src/opensci_engine/optics/gaussian/truncation.py +136 -0
- opensci_engine-0.1.0/src/opensci_engine/optics/interference/__init__.py +3 -0
- opensci_engine-0.1.0/src/opensci_engine/optics/interference/two_beam.py +184 -0
- opensci_engine-0.1.0/src/opensci_engine/optics/materials/__init__.py +5 -0
- opensci_engine-0.1.0/src/opensci_engine/optics/materials/coatings.py +78 -0
- opensci_engine-0.1.0/src/opensci_engine/optics/materials/index.py +107 -0
- opensci_engine-0.1.0/src/opensci_engine/optics/materials/library.py +51 -0
- opensci_engine-0.1.0/src/opensci_engine/optics/polarization/__init__.py +26 -0
- opensci_engine-0.1.0/src/opensci_engine/optics/polarization/fresnel.py +58 -0
- opensci_engine-0.1.0/src/opensci_engine/optics/polarization/jones.py +104 -0
- opensci_engine-0.1.0/src/opensci_engine/optics/rays/__init__.py +4 -0
- opensci_engine-0.1.0/src/opensci_engine/optics/rays/pupil.py +29 -0
- opensci_engine-0.1.0/src/opensci_engine/optics/rays/tracer.py +709 -0
- opensci_engine-0.1.0/src/opensci_engine/optics/surfaces/__init__.py +14 -0
- opensci_engine-0.1.0/src/opensci_engine/optics/surfaces/geometry.py +161 -0
- opensci_engine-0.1.0/src/opensci_engine/optics/surfaces/interaction.py +74 -0
- opensci_engine-0.1.0/src/opensci_engine/py.typed +0 -0
- opensci_engine-0.1.0/src/opensci_engine/scene/__init__.py +0 -0
- opensci_engine-0.1.0/src/opensci_engine/scene/lower.py +203 -0
- opensci_engine-0.1.0/src/opensci_engine/scene/model.py +100 -0
- opensci_engine-0.1.0/src/opensci_engine/units.py +6 -0
- opensci_engine-0.1.0/src/opensci_engine/validation/__init__.py +4 -0
- opensci_engine-0.1.0/src/opensci_engine/validation/metrics.py +287 -0
- opensci_engine-0.1.0/src/opensci_engine/validation/observations.py +196 -0
- opensci_engine-0.1.0/src/opensci_engine/validation/pipeline.py +224 -0
- opensci_engine-0.1.0/src/opensci_engine/validation/scope.py +38 -0
- opensci_engine-0.1.0/tests/fixtures/contract_freeze.json +27 -0
- opensci_engine-0.1.0/tests/fixtures/requests/aperture_clipping.json +268 -0
- opensci_engine-0.1.0/tests/fixtures/requests/benchmark_standard.json +8712 -0
- opensci_engine-0.1.0/tests/fixtures/requests/control_recoverability.json +344 -0
- opensci_engine-0.1.0/tests/fixtures/requests/hwp_pbs.json +389 -0
- opensci_engine-0.1.0/tests/fixtures/requests/interference_405_500.json +482 -0
- opensci_engine-0.1.0/tests/fixtures/requests/invalid_zero_normal.json +223 -0
- opensci_engine-0.1.0/tests/fixtures/requests/keplerian_expander.json +341 -0
- opensci_engine-0.1.0/tests/fixtures/requests/layout_example.json +527 -0
- opensci_engine-0.1.0/tests/fixtures/requests/multi_laser.json +403 -0
- opensci_engine-0.1.0/tests/fixtures/requests/mzi_window.json +560 -0
- opensci_engine-0.1.0/tests/fixtures/requests/out_of_scope_targets.json +265 -0
- opensci_engine-0.1.0/tests/fixtures/requests/tilted_mirror_astigmatism.json +245 -0
- opensci_engine-0.1.0/tests/fixtures/snapshots/aperture_clipping.json +377 -0
- opensci_engine-0.1.0/tests/fixtures/snapshots/benchmark_standard.json +13625 -0
- opensci_engine-0.1.0/tests/fixtures/snapshots/control_recoverability.json +458 -0
- opensci_engine-0.1.0/tests/fixtures/snapshots/hwp_pbs.json +578 -0
- opensci_engine-0.1.0/tests/fixtures/snapshots/interference_405_500.json +702 -0
- opensci_engine-0.1.0/tests/fixtures/snapshots/invalid_zero_normal.json +151 -0
- opensci_engine-0.1.0/tests/fixtures/snapshots/keplerian_expander.json +401 -0
- opensci_engine-0.1.0/tests/fixtures/snapshots/layout_example.json +427 -0
- opensci_engine-0.1.0/tests/fixtures/snapshots/multi_laser.json +875 -0
- opensci_engine-0.1.0/tests/fixtures/snapshots/mzi_window.json +1106 -0
- opensci_engine-0.1.0/tests/fixtures/snapshots/out_of_scope_targets.json +393 -0
- opensci_engine-0.1.0/tests/fixtures/snapshots/tilted_mirror_astigmatism.json +340 -0
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: opensci-engine
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Deterministic, stateless, offline 3D optical / layout / control engineering engine for OpenSci (CPython + Pyodide).
|
|
5
|
+
Author: OpenSci
|
|
6
|
+
License: Proprietary
|
|
7
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
8
|
+
Classifier: Topic :: Scientific/Engineering :: Physics
|
|
9
|
+
Classifier: Typing :: Typed
|
|
10
|
+
Requires-Python: >=3.14
|
|
11
|
+
Requires-Dist: numpy<3,>=2.3
|
|
12
|
+
Requires-Dist: pydantic<3,>=2.11
|
|
13
|
+
Requires-Dist: scipy<2,>=1.16
|
|
14
|
+
Requires-Dist: shapely<3,>=2.1
|
|
15
|
+
Provides-Extra: test
|
|
16
|
+
Requires-Dist: pytest-cov>=5; extra == 'test'
|
|
17
|
+
Requires-Dist: pytest>=8; extra == 'test'
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
|
|
20
|
+
# opensci-engine
|
|
21
|
+
|
|
22
|
+
Deterministic, stateless, **offline** engineering computation core for OpenSci: 3D geometric optics, Gaussian beams
|
|
23
|
+
(x/y-independent q-parameter), Jones polarization with a transported 3D transverse basis, analytic two-beam interference,
|
|
24
|
+
2.5D layout, and control / recoverability analysis. One pure-Python package, one algorithm core for native CPython,
|
|
25
|
+
Pyodide/WASM (Web Worker), CI and controlled server verification.
|
|
26
|
+
|
|
27
|
+
* Python **3.14**, `numpy`, `scipy`, `pydantic>=2`, `shapely` (layout only). No compiler, CMake, CUDA or system library.
|
|
28
|
+
* Same input + same engine version + same numerical settings = same result within the declared tolerance.
|
|
29
|
+
* Everything out of scope returns `OUT_OF_MODEL_SCOPE` / `UNKNOWN` — never `PASS`.
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install opensci-engine
|
|
33
|
+
opensci-engine validate request.json --pretty
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Quick example (405 nm / 500 nm two-beam interference)
|
|
37
|
+
|
|
38
|
+
Every example in this repository is an executable fixture (`tests/fixtures/requests/*.json`, built by
|
|
39
|
+
`tests/fixture_scenes.py`). The 405 nm / 500 nm case:
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
import json
|
|
43
|
+
from opensci_engine import ValidationRequest, validate
|
|
44
|
+
|
|
45
|
+
request = ValidationRequest.model_validate_json(open("tests/fixtures/requests/interference_405_500.json").read())
|
|
46
|
+
result = validate(request)
|
|
47
|
+
|
|
48
|
+
print(result.status) # PASS
|
|
49
|
+
pair = result.interference[0]
|
|
50
|
+
print(f"{pair.fringe_period_nm:.6f} nm, {pair.half_angle_deg:.4f} deg") # 500.000000 nm, 23.8911 deg (traced 3D wave vectors)
|
|
51
|
+
print(f"{pair.fringe_visibility:.6f} {pair.coherence_margin:.6f}") # 1.000000 1.000000
|
|
52
|
+
print(result.manifest.input_hash, result.manifest.result_hash)
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Build a scene programmatically with the factories in `opensci_engine.builders`:
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
import math
|
|
59
|
+
from opensci_engine import builders as B
|
|
60
|
+
from opensci_engine.contracts import ProjectSnapshot, Target
|
|
61
|
+
from opensci_engine import ValidationRequest, validate
|
|
62
|
+
|
|
63
|
+
project = ProjectSnapshot(
|
|
64
|
+
components=(
|
|
65
|
+
B.laser("L", wavelength_nm=532.0, waist_radius_mm=1.0, position=(0, 0, 20), direction=(1, 0, 0)),
|
|
66
|
+
B.thin_lens("Lens", focal_length_mm=100.0, clear_radius_mm=12.7, position=(10, 0, 20), axis=(1, 0, 0)),
|
|
67
|
+
),
|
|
68
|
+
observations=(B.observation_plane("focus", position=(110, 0, 20), normal=(1, 0, 0), radius_mm=5.0),),
|
|
69
|
+
targets=(Target(target_id="w", metric="beam_radius_mm", observation_id="focus", maximum=0.05),),
|
|
70
|
+
)
|
|
71
|
+
result = validate(ValidationRequest(project=project))
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Public API
|
|
75
|
+
|
|
76
|
+
| Object | Purpose |
|
|
77
|
+
|---|---|
|
|
78
|
+
| `validate(request)` / `validate_json(text)` | pure function: request -> `ValidationResult` (never raises for physical / contract validation outcomes represented by the result model) |
|
|
79
|
+
| `ValidationRequest`, `ProjectSnapshot`, `EngineConfiguration`, `NumericalConfig` | inputs (Pydantic v2, JSON friendly) |
|
|
80
|
+
| `ValidationResult`, `ValidationIssue`, `ValidationManifest` | outputs (finite numbers only, structured issues, hashes) |
|
|
81
|
+
| `opensci_engine.builders` | element factories producing contract objects |
|
|
82
|
+
| `opensci_engine.compare.compare_results` | cross-runtime comparison within `cross_runtime_rel_tol/abs_tol` |
|
|
83
|
+
| `opensci-engine validate|schema|compare` | command line |
|
|
84
|
+
|
|
85
|
+
Status vocabulary: `PASS`, `WARNING`, `FAIL`, `UNKNOWN`, `NOT_APPLICABLE`, `OUT_OF_MODEL_SCOPE`.
|
|
86
|
+
CLI exit codes: 0 PASS/WARNING/NOT_APPLICABLE, 1 FAIL, 2 UNKNOWN, 3 OUT_OF_MODEL_SCOPE, 64 usage error.
|
|
87
|
+
|
|
88
|
+
The "never raises" guarantee above applies to the `validate()` / `validate_json()` entry points: physical and
|
|
89
|
+
contract-validation outcomes are always represented by the `ValidationResult` model, never raised as exceptions.
|
|
90
|
+
Direct construction of the frozen Pydantic contract models (e.g. `builders.laser(wavelength_nm=float("nan"))` or
|
|
91
|
+
`SourceSpec(...)`) can still raise a Pydantic `ValidationError` *before* those entry points are called, because the
|
|
92
|
+
contract models deliberately set `allow_inf_nan=False`. Pass such input through `validate_json` (the CLI / Pyodide
|
|
93
|
+
bridge) to receive it as a structured `FAIL` / `INVALID_INPUT` result instead.
|
|
94
|
+
|
|
95
|
+
## Conventions (short; details in `docs/NUMERICAL_CONVENTIONS.md`)
|
|
96
|
+
|
|
97
|
+
Right-handed 3D, lengths mm, wavelengths nm (vacuum), angles degrees at the API, power W, `w` = 1/e² intensity **radius**.
|
|
98
|
+
Element local `+z` = optical axis; surface normals are explicit; Jones vectors live in a transported transverse basis
|
|
99
|
+
`(e1, e2)`, `e1 × e2 = d`. `E ∝ exp[i(k·r − ωt)]`.
|
|
100
|
+
|
|
101
|
+
## Documentation
|
|
102
|
+
|
|
103
|
+
* `docs/ENGINE_ARCHITECTURE.md` — layers, contracts (frozen vs provisional), dependency decisions
|
|
104
|
+
* `docs/PHYSICS_SCOPE.md` — equations, assumptions, applicability, failure modes, independent references per module
|
|
105
|
+
* `docs/NUMERICAL_CONVENTIONS.md` — units, frames, every tolerance and its reason, determinism, hashing
|
|
106
|
+
* `docs/PYODIDE_COMPATIBILITY.md` — Pyodide policy and the automated tests
|
|
107
|
+
* `KNOWN_LIMITATIONS.md`, `GOLDEN_CASE_REPORT.md`, `NUMERICAL_ACCURACY_REPORT.md`, `PYODIDE_COMPATIBILITY_REPORT.md`,
|
|
108
|
+
`DEPENDENCY_REPORT.md`, `ENGINE_IMPLEMENTATION_REPORT.md`
|
|
109
|
+
|
|
110
|
+
## Development
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
uv venv --python 3.14 .venv
|
|
114
|
+
uv pip install -e ".[test]"
|
|
115
|
+
pytest # native tests (golden physics, robustness, layout, control, determinism, hygiene)
|
|
116
|
+
python tests/generate_fixtures.py # regenerate fixtures / snapshots
|
|
117
|
+
python benchmarks/bench_validation.py # standard local-validation benchmark
|
|
118
|
+
cd tests/pyodide && npm ci && cd ../..
|
|
119
|
+
pytest -m pyodide # real Pyodide install + native/Pyodide parity (needs node + network on a cold cache)
|
|
120
|
+
pytest -m install # build the wheel, install into a clean venv, run a Golden Case
|
|
121
|
+
```
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# opensci-engine
|
|
2
|
+
|
|
3
|
+
Deterministic, stateless, **offline** engineering computation core for OpenSci: 3D geometric optics, Gaussian beams
|
|
4
|
+
(x/y-independent q-parameter), Jones polarization with a transported 3D transverse basis, analytic two-beam interference,
|
|
5
|
+
2.5D layout, and control / recoverability analysis. One pure-Python package, one algorithm core for native CPython,
|
|
6
|
+
Pyodide/WASM (Web Worker), CI and controlled server verification.
|
|
7
|
+
|
|
8
|
+
* Python **3.14**, `numpy`, `scipy`, `pydantic>=2`, `shapely` (layout only). No compiler, CMake, CUDA or system library.
|
|
9
|
+
* Same input + same engine version + same numerical settings = same result within the declared tolerance.
|
|
10
|
+
* Everything out of scope returns `OUT_OF_MODEL_SCOPE` / `UNKNOWN` — never `PASS`.
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
pip install opensci-engine
|
|
14
|
+
opensci-engine validate request.json --pretty
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Quick example (405 nm / 500 nm two-beam interference)
|
|
18
|
+
|
|
19
|
+
Every example in this repository is an executable fixture (`tests/fixtures/requests/*.json`, built by
|
|
20
|
+
`tests/fixture_scenes.py`). The 405 nm / 500 nm case:
|
|
21
|
+
|
|
22
|
+
```python
|
|
23
|
+
import json
|
|
24
|
+
from opensci_engine import ValidationRequest, validate
|
|
25
|
+
|
|
26
|
+
request = ValidationRequest.model_validate_json(open("tests/fixtures/requests/interference_405_500.json").read())
|
|
27
|
+
result = validate(request)
|
|
28
|
+
|
|
29
|
+
print(result.status) # PASS
|
|
30
|
+
pair = result.interference[0]
|
|
31
|
+
print(f"{pair.fringe_period_nm:.6f} nm, {pair.half_angle_deg:.4f} deg") # 500.000000 nm, 23.8911 deg (traced 3D wave vectors)
|
|
32
|
+
print(f"{pair.fringe_visibility:.6f} {pair.coherence_margin:.6f}") # 1.000000 1.000000
|
|
33
|
+
print(result.manifest.input_hash, result.manifest.result_hash)
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Build a scene programmatically with the factories in `opensci_engine.builders`:
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
import math
|
|
40
|
+
from opensci_engine import builders as B
|
|
41
|
+
from opensci_engine.contracts import ProjectSnapshot, Target
|
|
42
|
+
from opensci_engine import ValidationRequest, validate
|
|
43
|
+
|
|
44
|
+
project = ProjectSnapshot(
|
|
45
|
+
components=(
|
|
46
|
+
B.laser("L", wavelength_nm=532.0, waist_radius_mm=1.0, position=(0, 0, 20), direction=(1, 0, 0)),
|
|
47
|
+
B.thin_lens("Lens", focal_length_mm=100.0, clear_radius_mm=12.7, position=(10, 0, 20), axis=(1, 0, 0)),
|
|
48
|
+
),
|
|
49
|
+
observations=(B.observation_plane("focus", position=(110, 0, 20), normal=(1, 0, 0), radius_mm=5.0),),
|
|
50
|
+
targets=(Target(target_id="w", metric="beam_radius_mm", observation_id="focus", maximum=0.05),),
|
|
51
|
+
)
|
|
52
|
+
result = validate(ValidationRequest(project=project))
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Public API
|
|
56
|
+
|
|
57
|
+
| Object | Purpose |
|
|
58
|
+
|---|---|
|
|
59
|
+
| `validate(request)` / `validate_json(text)` | pure function: request -> `ValidationResult` (never raises for physical / contract validation outcomes represented by the result model) |
|
|
60
|
+
| `ValidationRequest`, `ProjectSnapshot`, `EngineConfiguration`, `NumericalConfig` | inputs (Pydantic v2, JSON friendly) |
|
|
61
|
+
| `ValidationResult`, `ValidationIssue`, `ValidationManifest` | outputs (finite numbers only, structured issues, hashes) |
|
|
62
|
+
| `opensci_engine.builders` | element factories producing contract objects |
|
|
63
|
+
| `opensci_engine.compare.compare_results` | cross-runtime comparison within `cross_runtime_rel_tol/abs_tol` |
|
|
64
|
+
| `opensci-engine validate|schema|compare` | command line |
|
|
65
|
+
|
|
66
|
+
Status vocabulary: `PASS`, `WARNING`, `FAIL`, `UNKNOWN`, `NOT_APPLICABLE`, `OUT_OF_MODEL_SCOPE`.
|
|
67
|
+
CLI exit codes: 0 PASS/WARNING/NOT_APPLICABLE, 1 FAIL, 2 UNKNOWN, 3 OUT_OF_MODEL_SCOPE, 64 usage error.
|
|
68
|
+
|
|
69
|
+
The "never raises" guarantee above applies to the `validate()` / `validate_json()` entry points: physical and
|
|
70
|
+
contract-validation outcomes are always represented by the `ValidationResult` model, never raised as exceptions.
|
|
71
|
+
Direct construction of the frozen Pydantic contract models (e.g. `builders.laser(wavelength_nm=float("nan"))` or
|
|
72
|
+
`SourceSpec(...)`) can still raise a Pydantic `ValidationError` *before* those entry points are called, because the
|
|
73
|
+
contract models deliberately set `allow_inf_nan=False`. Pass such input through `validate_json` (the CLI / Pyodide
|
|
74
|
+
bridge) to receive it as a structured `FAIL` / `INVALID_INPUT` result instead.
|
|
75
|
+
|
|
76
|
+
## Conventions (short; details in `docs/NUMERICAL_CONVENTIONS.md`)
|
|
77
|
+
|
|
78
|
+
Right-handed 3D, lengths mm, wavelengths nm (vacuum), angles degrees at the API, power W, `w` = 1/e² intensity **radius**.
|
|
79
|
+
Element local `+z` = optical axis; surface normals are explicit; Jones vectors live in a transported transverse basis
|
|
80
|
+
`(e1, e2)`, `e1 × e2 = d`. `E ∝ exp[i(k·r − ωt)]`.
|
|
81
|
+
|
|
82
|
+
## Documentation
|
|
83
|
+
|
|
84
|
+
* `docs/ENGINE_ARCHITECTURE.md` — layers, contracts (frozen vs provisional), dependency decisions
|
|
85
|
+
* `docs/PHYSICS_SCOPE.md` — equations, assumptions, applicability, failure modes, independent references per module
|
|
86
|
+
* `docs/NUMERICAL_CONVENTIONS.md` — units, frames, every tolerance and its reason, determinism, hashing
|
|
87
|
+
* `docs/PYODIDE_COMPATIBILITY.md` — Pyodide policy and the automated tests
|
|
88
|
+
* `KNOWN_LIMITATIONS.md`, `GOLDEN_CASE_REPORT.md`, `NUMERICAL_ACCURACY_REPORT.md`, `PYODIDE_COMPATIBILITY_REPORT.md`,
|
|
89
|
+
`DEPENDENCY_REPORT.md`, `ENGINE_IMPLEMENTATION_REPORT.md`
|
|
90
|
+
|
|
91
|
+
## Development
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
uv venv --python 3.14 .venv
|
|
95
|
+
uv pip install -e ".[test]"
|
|
96
|
+
pytest # native tests (golden physics, robustness, layout, control, determinism, hygiene)
|
|
97
|
+
python tests/generate_fixtures.py # regenerate fixtures / snapshots
|
|
98
|
+
python benchmarks/bench_validation.py # standard local-validation benchmark
|
|
99
|
+
cd tests/pyodide && npm ci && cd ../..
|
|
100
|
+
pytest -m pyodide # real Pyodide install + native/Pyodide parity (needs node + network on a cold cache)
|
|
101
|
+
pytest -m install # build the wheel, install into a clean venv, run a Golden Case
|
|
102
|
+
```
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# opensci-engine — Architecture
|
|
2
|
+
|
|
3
|
+
Status: Phase 1 baseline (engine v0.1.0). This document is normative for module
|
|
4
|
+
boundaries, public contracts and dependency choices. Physics scope is in
|
|
5
|
+
`PHYSICS_SCOPE.md`; numeric conventions in `NUMERICAL_CONVENTIONS.md`; Pyodide
|
|
6
|
+
constraints in `PYODIDE_COMPATIBILITY.md`.
|
|
7
|
+
|
|
8
|
+
## 1. What the engine is
|
|
9
|
+
|
|
10
|
+
`opensci-engine` is a **deterministic, stateless, offline engineering
|
|
11
|
+
computation library**. One import, one call:
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
ValidationRequest (Pydantic, JSON) -> validate() -> ValidationResult (Pydantic, JSON)
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
It is not a database, web API, cloud service, supplier resolver, catalog or
|
|
18
|
+
file store. It performs no I/O of any kind while computing (no network, no
|
|
19
|
+
filesystem reads of user data, no environment probing other than the
|
|
20
|
+
Python/NumPy versions recorded in the manifest, no clocks, no global
|
|
21
|
+
mutable caches, no multiprocessing).
|
|
22
|
+
|
|
23
|
+
Invariant: same canonical input + same engine version + same
|
|
24
|
+
`NumericalConfig` = same result within the declared numerical tolerance.
|
|
25
|
+
|
|
26
|
+
## 2. Layering
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
JSON / Python objects
|
|
30
|
+
|
|
|
31
|
+
v
|
|
32
|
+
contracts/ Pydantic v2, frozen, extra="forbid", JSON friendly <-- public boundary
|
|
33
|
+
| (validated, then lowered once)
|
|
34
|
+
v
|
|
35
|
+
scene/ lowering: contracts -> numpy-backed internal structures (dataclasses)
|
|
36
|
+
|
|
|
37
|
+
v
|
|
38
|
+
math/ optics/ layout/ control/ pure numerical code, NumPy arrays / dataclasses, no Pydantic in loops
|
|
39
|
+
|
|
|
40
|
+
v
|
|
41
|
+
validation/ targets, aggregation, issue synthesis -> Pydantic results
|
|
42
|
+
|
|
|
43
|
+
v
|
|
44
|
+
manifest/ canonical serialization, hashing
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Rules:
|
|
48
|
+
|
|
49
|
+
* Pydantic objects are created only at the boundary (input validation and
|
|
50
|
+
result assembly), never inside ray/beam/optimizer inner loops.
|
|
51
|
+
* `math/` depends on NumPy only. `optics/`, `layout/`, `control/` depend on
|
|
52
|
+
`math/`, `config` and `errors/`, never on `contracts/` request/result types
|
|
53
|
+
except through `scene/` lowering.
|
|
54
|
+
* `contracts/` never import `optics/` etc. (one-way dependency), so contracts
|
|
55
|
+
can be frozen independently of algorithms.
|
|
56
|
+
* No module reads a module-level mutable. All tolerances travel in an explicit
|
|
57
|
+
`NumericalConfig` object (frozen, hashed into the manifest).
|
|
58
|
+
|
|
59
|
+
## 3. Package layout
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
src/opensci_engine/
|
|
63
|
+
__init__.py public API: validate, validate_json, __version__, contracts re-exports
|
|
64
|
+
_version.py ENGINE_VERSION, SCHEMA_VERSION, NUMERICAL_CONFIG_VERSION, MODEL_VERSIONS
|
|
65
|
+
config.py NumericalConfig (all epsilons/tolerances, each documented)
|
|
66
|
+
errors/ Status, ErrorCode, ValidationIssue, EngineInputError
|
|
67
|
+
math/ vectors, rotation (quaternion), pose, rng (PCG64 -> uniform/normal)
|
|
68
|
+
contracts/ frozen public models (see 4)
|
|
69
|
+
scene/ contracts -> internal lowering
|
|
70
|
+
optics/
|
|
71
|
+
materials/ constant / Sellmeier / tabulated n(lambda), coatings, range checks
|
|
72
|
+
surfaces/ plane & sphere intersection, aperture, Snell/reflect/TIR, thin lens
|
|
73
|
+
polarization/ Jones calculus, transverse-basis transport, Fresnel, elements
|
|
74
|
+
gaussian/ q-parameter / ABCD (x,y independent), truncation, applicability
|
|
75
|
+
rays/ vectorized ray bundles, sequential-agnostic (non-sequential) tracer, pupil sampling
|
|
76
|
+
interference/ two-beam analytic model
|
|
77
|
+
layout/ footprint, keepout, beam envelope, moving envelope (Shapely, 2D/2.5D only)
|
|
78
|
+
control/ actuators, sensors, Jacobian, Levenberg-Marquardt, recoverability, Monte Carlo
|
|
79
|
+
validation/ target metric registry, out-of-scope registry, pipeline
|
|
80
|
+
manifest/ canonical JSON, hashing, ValidationManifest assembly
|
|
81
|
+
cli.py `opensci-engine validate request.json`
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## 4. Public contracts
|
|
85
|
+
|
|
86
|
+
### 4.1 Frozen (physics/math layer; independent of the database team)
|
|
87
|
+
|
|
88
|
+
`Vector3`, `Quaternion`, `Pose3D`, `Ray`, `RaySegment`, `OpticalSurface`
|
|
89
|
+
(plane, sphere; apertures; interactions), `OpticalPort`, `OpticalPath`,
|
|
90
|
+
`BeamState`, `GaussianBeamState`, `JonesVector`, `PolarizationBasis`,
|
|
91
|
+
`MaterialOpticalModel` (+ `CoatingModel`), `Target`, `Observation`,
|
|
92
|
+
`ValidationResult`, `ValidationIssue`, `ValidationManifest`, `NumericalConfig`.
|
|
93
|
+
|
|
94
|
+
"Frozen" means: field names and semantics may only change via a
|
|
95
|
+
`SCHEMA_VERSION` bump plus migration. This is enforced: `tests/fixtures/contract_freeze.json` stores the JSON-Schema hash of
|
|
96
|
+
each frozen model and `tests/test_fixtures_cli_freeze.py::test_frozen_contracts_are_unchanged_since_the_recorded_baseline`
|
|
97
|
+
fails on any change without a version bump (`python tests/generate_fixtures.py --freeze` rewrites the baseline).
|
|
98
|
+
|
|
99
|
+
### 4.2 Provisional (awaiting the database / Product & Supply team)
|
|
100
|
+
|
|
101
|
+
`EngineeringComponentSnapshot`, `EngineeringEnvelopeSnapshot`,
|
|
102
|
+
`ActuatorSpec`, `ControlSpec`, `SourceSpec` (wrapper fields other than the
|
|
103
|
+
physical beam parameters), `ProjectSnapshot` (container fields such as ids,
|
|
104
|
+
names, revision metadata), `ValidationRequest.project` envelope.
|
|
105
|
+
|
|
106
|
+
The envelope's plan-parallel frame (`EngineeringEnvelopeSnapshot.frame_pose`) and the element `mount_margin_mm` semantics
|
|
107
|
+
were introduced during implementation because the first draft (footprints in the optical local frame; an infinite blocking plane)
|
|
108
|
+
was physically wrong (documented in `ENGINE_IMPLEMENTATION_REPORT.md`).
|
|
109
|
+
|
|
110
|
+
These are *engine-side input snapshots*. The engine never imports an ORM or
|
|
111
|
+
`DesignPart`/`SupplierPart`/`AssemblyImplementation`. The future adapter
|
|
112
|
+
direction is `Database model -> adapter -> Engineering*Snapshot -> engine`.
|
|
113
|
+
|
|
114
|
+
## 5. Dependency decisions
|
|
115
|
+
|
|
116
|
+
| Package | Decision | Reason |
|
|
117
|
+
|---|---|---|
|
|
118
|
+
| numpy | required, `>=2.3,<3` | vector math; shipped by Pyodide 314 (2.4.6) |
|
|
119
|
+
| scipy | required, `>=1.16,<2` | interpolation (`PchipInterpolator`, `RegularGridInterpolator`); shipped by Pyodide (1.18.0) |
|
|
120
|
+
| pydantic | required, `>=2.11,<3` | public contracts; Pyodide ships 2.12.5 + pydantic-core wasm |
|
|
121
|
+
| shapely | required, `>=2.1,<3` | *only* `layout/` footprint / keepout polygon operations; Pyodide ships 2.1.2 |
|
|
122
|
+
| networkx | not adopted | Pyodide's build depends on matplotlib; graph needs here are trees |
|
|
123
|
+
| trimesh | not adopted | not in Pyodide lock; M1 needs only analytic plane/sphere geometry, so no Rtree/Embree question arises |
|
|
124
|
+
|
|
125
|
+
Shapely does 2D/2.5D footprint booleans, distances and buffering. It does
|
|
126
|
+
not replace 3D physics: beam/ray propagation, apertures and surface
|
|
127
|
+
intersection are NumPy.
|
|
128
|
+
|
|
129
|
+
## 6. Runtime targets
|
|
130
|
+
|
|
131
|
+
Native CPython 3.14, Pyodide 314.0.x in a Web Worker, CI, controlled server
|
|
132
|
+
verification. The package is pure Python (`py3-none-any` wheel); all binary
|
|
133
|
+
dependencies come from the target's own distribution (PyPI wheels / Pyodide
|
|
134
|
+
lock). No compiler, CMake, CUDA or system shared library.
|
|
135
|
+
|
|
136
|
+
## 7. Statefulness
|
|
137
|
+
|
|
138
|
+
None. `validate()` is a pure function of `(request)`. Caches (e.g., material
|
|
139
|
+
index lookups inside one trace) live for one call. There are no module-level
|
|
140
|
+
caches or mutable globals. `engine_build_id` is recomputed per call from the
|
|
141
|
+
installed package's `.py` source bytes (read-only, ~ms) and is used only for
|
|
142
|
+
the manifest; it cannot influence physics.
|
|
143
|
+
|
|
144
|
+
## 8. Extension points
|
|
145
|
+
|
|
146
|
+
* New element type: add a discriminated-union member in `contracts/optics.py`
|
|
147
|
+
and an interaction handler in `optics/surfaces/interaction.py`.
|
|
148
|
+
* New metric: register in `validation/metrics.py`; unsupported physics goes
|
|
149
|
+
into the explicit out-of-scope registry (returns `OUT_OF_MODEL_SCOPE`).
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
# opensci-engine — Numerical Conventions
|
|
2
|
+
|
|
3
|
+
## 1. Units (fixed by schema, not by field-level choice)
|
|
4
|
+
|
|
5
|
+
| Quantity | Unit | Notes |
|
|
6
|
+
|---|---|---|
|
|
7
|
+
| length | mm | every `*_mm` field; radius vs diameter is explicit in the field name (`*_radius_mm`) — never both |
|
|
8
|
+
| wavelength | nm (vacuum) | `wavelength_nm`; fringe periods in nm |
|
|
9
|
+
| angle (API) | degree | `*_deg`; internal computation in radian |
|
|
10
|
+
| power | W | `power_W` |
|
|
11
|
+
| irradiance | W/mm² | conversion to mW/cm² only in exposure metrics |
|
|
12
|
+
| time | s | |
|
|
13
|
+
| dose | mJ/cm² | |
|
|
14
|
+
| phase | rad | |
|
|
15
|
+
| Gaussian radius | mm | `w` is the **1/e² intensity radius** (not FWHM, not diameter) |
|
|
16
|
+
| divergence | rad | half-angle, `θ = M²λ/(π n w₀)` |
|
|
17
|
+
|
|
18
|
+
## 2. Coordinates
|
|
19
|
+
|
|
20
|
+
* Global frame: right-handed, `x` right, `y` forward, `z` up (board normal).
|
|
21
|
+
The baseplate top plane is `z = 0` unless the project says otherwise; the
|
|
22
|
+
optical axis height is an absolute `z` of the ray.
|
|
23
|
+
* Element local frame: right-handed, origin at the element reference point.
|
|
24
|
+
For transmissive elements local `+z` is the nominal optical axis (light
|
|
25
|
+
travels along `+z`); surface normals are given explicitly per surface, so no
|
|
26
|
+
hidden convention is required.
|
|
27
|
+
* `Pose3D = (position_mm, quaternion (w,x,y,z))`; *active* transform
|
|
28
|
+
`x_g = R(q) x_l + p`. Composition `T_parent ∘ T_child`: `R = R_p R_c`,
|
|
29
|
+
`p = R_p p_c + p_p`. Quaternions are Hamilton, unit norm within
|
|
30
|
+
`quaternion_norm_tol`, renormalized on lowering (the deviation is not
|
|
31
|
+
hidden: outside tolerance is a validation error).
|
|
32
|
+
* `Pose3D.from_axis_direction(position, axis, up)` (the `compute_pose` helper):
|
|
33
|
+
local `z := axis`, local `y := up` orthogonalized against `axis`, local
|
|
34
|
+
`x := y × z`.
|
|
35
|
+
* Angle sign: positive rotation is right-handed about the stated axis.
|
|
36
|
+
* Surface normal `n̂`: "positive side" is the side `n̂` points toward.
|
|
37
|
+
`medium_positive` / `medium_negative` name the media on the two sides. A ray
|
|
38
|
+
with `d̂·n̂ < 0` arrives from the positive side.
|
|
39
|
+
* Mirror: `reflective_side ∈ {positive, negative, both}`. Sphere normal is the
|
|
40
|
+
outward radial vector.
|
|
41
|
+
* Signed radius for ABCD: `R>0` if the centre of curvature lies ahead of the
|
|
42
|
+
surface along the propagation direction (refraction) / on the incident
|
|
43
|
+
side (mirror, concave).
|
|
44
|
+
* Transverse basis of a ray: `(e₁, e₂)`, `e₁ × e₂ = d̂`.
|
|
45
|
+
* Polarization phase convention: `E ∝ exp[i(k·r − ωt)]`; a retarder's slow axis
|
|
46
|
+
gets `+δ/2`. Ideal metal mirror: `(r_s, r_p) = (−1, +1)` in the `(ŝ, d̂×ŝ)` triad, equivalently
|
|
47
|
+
`E_out = −(I − 2 n̂ n̂ᵀ) E_in` (tested against exact vector reflection through non-coplanar folds).
|
|
48
|
+
* Mount margin: a surface with a clear aperture may declare `mount_margin_mm` — the width of the
|
|
49
|
+
opaque ring around the aperture. A ray hitting the ring is blocked (`CLIPPED`); a ray beyond it
|
|
50
|
+
(or beyond the aperture when the margin is 0, e.g. a mirror edge) *misses* the element and passes by.
|
|
51
|
+
An infinite plane never blocks anything by itself.
|
|
52
|
+
* Observation planes are virtual and unbounded. Without an active `aperture` every crossing is an
|
|
53
|
+
arrival. With one, a beam counts as an arrival only if its chief ray is inside the area or at least
|
|
54
|
+
`capture_min_power_fraction` of its Gaussian power falls inside it; otherwise the crossing is
|
|
55
|
+
reported as `OBSERVATION_NOT_REACHED` with `nearest_miss_mm`. Two observation planes must not be
|
|
56
|
+
coincident (the second would be hidden by the ray-advance guard).
|
|
57
|
+
* Envelope frame: `EngineeringEnvelopeSnapshot.frame_pose` places the envelope frame relative to the
|
|
58
|
+
component frame. The footprint / z range / keepout / tool / cable regions live in that frame and
|
|
59
|
+
must be plan-parallel. For elements built with local `z` = beam axis use
|
|
60
|
+
`Pose3D.plan_frame_for_optical_axis()` (envelope x = beam axis, y = transverse horizontal, z = up).
|
|
61
|
+
* Two-beam phase difference: `phase_difference_rad = wrap[k₀ (OPL_a − OPL_b) − arg⟨E_a, E_b⟩]`
|
|
62
|
+
with `⟨E_a, E_b⟩ = conj(E_a)·E_b` (3D fields, interface phases included, spatial fringe term
|
|
63
|
+
excluded).
|
|
64
|
+
|
|
65
|
+
## 3. Tolerances (all in `NumericalConfig`, each with a stated reason)
|
|
66
|
+
|
|
67
|
+
No numeric literal used as a tolerance appears outside `config.py`, named module constants
|
|
68
|
+
or `units.py` (enforced by `tests/test_hygiene.py`, which AST-scans the physics packages for
|
|
69
|
+
unnamed floats and large integers). The table below is generated from `NumericalConfig`
|
|
70
|
+
(`python docs/generate_docs.py`); `tests/test_docs_in_sync.py` fails if it drifts.
|
|
71
|
+
|
|
72
|
+
<!-- BEGIN TOLERANCES (generated by docs/generate_docs.py; do not edit) -->
|
|
73
|
+
|
|
74
|
+
| Field | Default | Meaning / reason |
|
|
75
|
+
|---|---|---|
|
|
76
|
+
| `ray_min_advance_mm` | `1e-07` | Minimum ray parameter t for a valid hit; prevents re-intersecting the surface just left. |
|
|
77
|
+
| `parallel_dot_tol` | `1e-12` | |n.d| below this is 'parallel': no intersection is reported. |
|
|
78
|
+
| `grazing_warn_aoi_deg` | `85.0` | AOI above which GRAZING_INCIDENCE warns. |
|
|
79
|
+
| `grazing_fail_aoi_deg` | `89.9` | AOI above which the interaction is rejected. |
|
|
80
|
+
| `antiparallel_tol` | `1e-12` | 1+cos(angle) below which two directions are treated as antiparallel (basis transport). |
|
|
81
|
+
| `unit_vector_tol` | `1e-09` | Allowed |1-|v|| for user-supplied unit vectors. |
|
|
82
|
+
| `zero_vector_tol` | `1e-12` | Vectors with norm below this are zero-length. |
|
|
83
|
+
| `quaternion_norm_tol` | `1e-06` | Allowed |1-|q|| for user-supplied quaternions. |
|
|
84
|
+
| `aperture_edge_tol_mm` | `1e-09` | Inclusive aperture boundary tolerance (floating-point edge). |
|
|
85
|
+
| `tir_warn_margin` | `0.001` | Relative distance to the critical angle that triggers TIR_BOUNDARY. |
|
|
86
|
+
| `power_epsilon_W` | `1e-15` | Power at or below this terminates a path (ZERO_POWER). |
|
|
87
|
+
| `energy_conservation_tol` | `1e-09` | Tolerance for R+T<=1 and unit-norm Jones checks. |
|
|
88
|
+
| `basis_orthonormality_tol` | `1e-09` | Allowed deviation of a transported transverse basis before a warning. |
|
|
89
|
+
| `max_interactions_per_path` | `64` | Guard against infinite cavity loops. |
|
|
90
|
+
| `max_paths` | `4096` | Guard against unbounded branching. |
|
|
91
|
+
| `retarder_warn_aoi_deg` | `20.0` | Retarder/polarizer AOI above which RETARDER_OBLIQUE_INCIDENCE warns. |
|
|
92
|
+
| `sellmeier_pole_rel_tol` | `1e-09` | Relative |l^2 - C_i| / l^2 below which a Sellmeier pole is considered hit. |
|
|
93
|
+
| `tabulated_range_tol_nm` | `1e-09` | Slack (nm) on the inclusive wavelength validity bounds of materials and coatings. |
|
|
94
|
+
| `tabulated_aoi_tol_deg` | `1e-09` | Slack (deg) on the AOI bounds of tabulated coatings. |
|
|
95
|
+
| `gaussian_warn_divergence_rad` | `0.1` | Half-angle divergence (NA~0.1) warning threshold. |
|
|
96
|
+
| `gaussian_scope_divergence_rad` | `0.3` | Half-angle divergence above which the paraxial Gaussian model is out of scope. |
|
|
97
|
+
| `gaussian_max_w_over_radius` | `0.2` | Beam radius / |surface radius of curvature| limit for ABCD validity. |
|
|
98
|
+
| `gaussian_max_aoi_deg` | `60.0` | Tilted-surface ABCD validity limit on AOI. |
|
|
99
|
+
| `gaussian_clip_warn_fraction` | `0.001` | Truncation power loss that warns. |
|
|
100
|
+
| `gaussian_clip_scope_fraction` | `0.1` | Truncation power loss above which the Gaussian state is invalidated. |
|
|
101
|
+
| `gaussian_astig_align_tol` | `1e-09` | |sin(phi)cos(phi)| below which beam axes are aligned with (t,s). |
|
|
102
|
+
| `gaussian_round_tol` | `1e-09` | Relative difference below which the two transverse axes are 'round'. |
|
|
103
|
+
| `gaussian_plane_axial_spread_fraction` | `0.1` | Axial spread across a tilted plane footprint vs z_R that triggers a warning. |
|
|
104
|
+
| `aperture_quad_angular` | `256` | Angular trapezoid nodes for circular-aperture Gaussian truncation (periodic, spectral). |
|
|
105
|
+
| `aperture_quad_sector_nodes` | `48` | Gauss-Legendre nodes per panel/sector for off-axis circular and rectangular truncation. |
|
|
106
|
+
| `aperture_quad_extent_sigmas` | `6.0` | Gaussian integrals are truncated at this many beam radii from the beam centre (exp(-72) ~ 0). |
|
|
107
|
+
| `pupil_ring_count` | `8` | Equal-power rings in the default pupil sampling. |
|
|
108
|
+
| `pupil_points_per_ring` | `12` | Points on each ring of the default pupil sampling. |
|
|
109
|
+
| `interference_min_crossing_deg` | `1e-06` | Crossing angle below which the fringe period is undefined. |
|
|
110
|
+
| `interference_wavelength_tol_nm` | `1e-09` | Two arrivals must agree in wavelength within this to interfere. |
|
|
111
|
+
| `interference_min_overlap` | `0.001` | Spatial overlap ratio below which beams are considered not overlapping (visibility < 0.1 %). |
|
|
112
|
+
| `interference_collinear_period_ratio` | `10.0` | Fringe period / beam diameter above which two beams are treated as collinear (single-fringe detector). |
|
|
113
|
+
| `max_interference_pairs` | `256` | Cap on automatically analysed beam pairs per observation. |
|
|
114
|
+
| `interference_det_tol` | `1e-30` | Determinant floor for the summed envelope matrix. |
|
|
115
|
+
| `fd_step_translation_mm` | `0.001` | Central-difference step for translation DOFs. |
|
|
116
|
+
| `fd_step_rotation_rad` | `1e-05` | Central-difference step for rotation DOFs. |
|
|
117
|
+
| `lm_initial_damping` | `0.001` | Initial Levenberg-Marquardt damping. |
|
|
118
|
+
| `lm_damping_up` | `10.0` | Damping multiplier after a rejected step. |
|
|
119
|
+
| `lm_damping_down` | `0.1` | Damping multiplier after an accepted step. |
|
|
120
|
+
| `lm_max_damping` | `1000000000000.0` | Damping above which LM gives up. |
|
|
121
|
+
| `lm_max_iterations` | `100` | Iteration cap for LM. |
|
|
122
|
+
| `lm_cost_tol` | `1e-18` | Converged when the cost falls below this. |
|
|
123
|
+
| `lm_step_tol` | `1e-12` | Converged when the scaled step norm falls below this. |
|
|
124
|
+
| `lm_residual_tol` | `1e-06` | Default weighted-residual norm regarded as converged when the spec gives none (sensor units, e.g. mm). |
|
|
125
|
+
| `lm_scaling_floor` | `1e-12` | Floor on diag(J^T J) in Marquardt scaling so unobserved DOFs stay bounded. |
|
|
126
|
+
| `lm_gain_accept` | `0.0` | Minimum actual/predicted reduction ratio to accept a step. |
|
|
127
|
+
| `jacobian_rcond` | `1e-10` | Singular values below rcond*sigma_max are rank-deficient. |
|
|
128
|
+
| `observability_participation_min` | `0.25` | An actuator is reported unobservable when at least this fraction of its unit motion lies in the null space of the sensor Jacobian (0.5 would sit exactly on the symmetric two-DOF case). |
|
|
129
|
+
| `jacobian_condition_warn` | `1000000.0` | Jacobian condition number that warns. |
|
|
130
|
+
| `capture_min_power_fraction` | `0.001` | Detected/available power below this means the beam is lost. |
|
|
131
|
+
| `capture_scan_max_points` | `4096` | Maximum points of the coarse-capture raster scan. |
|
|
132
|
+
| `layout_geometry_tol_mm` | `1e-06` | Overlaps/clearance deficits below this are ignored. |
|
|
133
|
+
| `layout_arc_resolution` | `32` | Shapely buffer segments per quarter circle. |
|
|
134
|
+
| `beam_envelope_k` | `2.0` | Beam envelope radius = k*w (k=2 -> 1-exp(-8)=99.97% power). |
|
|
135
|
+
| `moving_envelope_rotation_samples` | `9` | Angles sampled for rotational sweeps. |
|
|
136
|
+
| `layout_height_tol_mm` | `1e-06` | Height intervals overlapping by less than this are disjoint. |
|
|
137
|
+
| `hash_float_sig_digits` | `9` | Significant digits kept when hashing results. |
|
|
138
|
+
| `hash_float_abs_floor` | `1e-12` | Result values below this magnitude hash as 0. |
|
|
139
|
+
| `cross_runtime_rel_tol` | `1e-09` | Relative tolerance for cross-runtime comparison. |
|
|
140
|
+
| `cross_runtime_abs_tol` | `1e-12` | Absolute tolerance for cross-runtime comparison. |
|
|
141
|
+
|
|
142
|
+
<!-- END TOLERANCES -->
|
|
143
|
+
|
|
144
|
+
Changing a default increments `NUMERICAL_CONFIG_VERSION`.
|
|
145
|
+
|
|
146
|
+
## 4. Determinism
|
|
147
|
+
|
|
148
|
+
* No wall-clock, no `id()`, no dict-order dependence in results (all
|
|
149
|
+
collections are sorted by stable ids before output).
|
|
150
|
+
* Random numbers only in Monte Carlo, from `numpy.random.PCG64(seed)` raw
|
|
151
|
+
64-bit words; uniform `u = (w >> 11) · 2⁻⁵³`; normal via Box–Muller using
|
|
152
|
+
`math.log`/`math.cos`. The PRNG identifier `PCG64+BoxMuller-v1` and the seed
|
|
153
|
+
go into the manifest.
|
|
154
|
+
* NumPy `BitGenerator` raw streams are stable across versions and platforms;
|
|
155
|
+
`Generator` distribution methods are deliberately not used.
|
|
156
|
+
* Floating-point differences between runtimes come from libm/BLAS; results are
|
|
157
|
+
compared with `cross_runtime_rel_tol` / `_abs_tol`, never bitwise.
|
|
158
|
+
|
|
159
|
+
## 5. Error reporting, never NaN
|
|
160
|
+
|
|
161
|
+
All public numeric outputs are finite. If a computation cannot produce a
|
|
162
|
+
finite meaningful number it returns a structured `ValidationIssue`
|
|
163
|
+
(`code`, `message`, `location`, `affected_targets`, `numerical_context`,
|
|
164
|
+
`suggested_action`) and the dependent metric has status `UNKNOWN`,
|
|
165
|
+
`FAIL` or `OUT_OF_MODEL_SCOPE` with `value = null`. Result models forbid
|
|
166
|
+
`NaN`/`Inf` (`allow_inf_nan=False`).
|
|
167
|
+
|
|
168
|
+
## 6. Canonical serialization and hashing
|
|
169
|
+
|
|
170
|
+
* JSON, UTF-8, keys sorted, no whitespace (`separators=(",",":")`).
|
|
171
|
+
* Inputs hashed from the validated model dump (`mode="json"`, so defaults are
|
|
172
|
+
materialized and aliases normalized).
|
|
173
|
+
* Floats in **results** are quantized before hashing: value `v` with
|
|
174
|
+
`|v| < hash_float_abs_floor` → `0.0`; else round to `hash_float_sig_digits`
|
|
175
|
+
significant digits and print with `repr`. This makes `result_hash` stable
|
|
176
|
+
against last-bit libm differences; cross-runtime comparison still uses
|
|
177
|
+
explicit tolerances (`compare_results`), because rounding can straddle a
|
|
178
|
+
boundary.
|
|
179
|
+
* Hash = SHA-256 hex. No secret keys: hashes are integrity/reproducibility
|
|
180
|
+
aids, not authenticity proofs.
|
|
181
|
+
* `trace_hash` covers the ordered ray-event list of the chief rays.
|