diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7fed3b0e..ab07dda1 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -45,6 +45,15 @@ repos: pass_filenames: false always_run: true + - id: no-pre-v1.0-deprecations + name: Forbid deprecation-cycle infrastructure (pre-v1.0 policy) + language: pygrep + entry: '(REMOVE-AT-V1\.0|DeprecationWarning|_DEPRECATED_[A-Z_]+_ALIASES)' + files: ^(src/ad_hoc_diffractometer|tests)/.*\.py$ + # See the "Operating directives (pre-v1.0)" section of AGENTS.md. + # Rename freely. No aliases, no DeprecationWarning emitters, + # no REMOVE-AT-V1.0 markers, no alias-map constants. + - repo: https://github.com/PyCQA/isort rev: 5.13.2 hooks: diff --git a/AGENTS.md b/AGENTS.md index faefdba9..4dd3f4c5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4,6 +4,50 @@ This file provides context for AI coding agents working on this project. --- +## Operating directives (pre-v1.0) + +These rules apply for every issue and every PR until the project tags +v1.0. They override anything in the rest of this file or in the +agent's general training that conflicts. + +### No deprecation cycles + +Rename freely. Do not add forwarding aliases, `DeprecationWarning` +emitters, alias maps, `REMOVE-AT-V1.0` markers, or any other +deprecation-cycle infrastructure for any rename, signature change, or +removal. The whole package is still pre-1.0; users have no API +stability contract yet. Make the change cleanly, update every +in-tree call site, document the change in `CHANGES.md`, move on. + +### Scope is the issue + +The issue text defines the scope. If something falls under the +issue's title or stated motivation, it is in scope by default. Do +not unilaterally narrow the scope or split work into "follow-up" +issues. If the scope is genuinely ambiguous, ask exactly one +clarifying question, then proceed. + +### No volunteered follow-up issues + +Do not file new issues, propose new issues, or build "future v1.0 +cleanup" tracking unless the user asks. Pre-1.0 cleanup is just +"edit the code now." + +### Response length matches the request + +When the user asks a brief question, answer briefly: a sentence or a +short list, no tables, no preamble, no recap. When the user asks +for analysis or a plan, longer is fine. Match the form of the +request. + +### When in doubt, ask one short question + +A single clarifying question is cheaper than building the wrong +thing and unwinding it. Do not stack multiple questions; ask one, +wait, then proceed. + +--- + ## Attribution Always sign your work. Every commit message, pull request description, and diff --git a/CHANGES.md b/CHANGES.md index 68522094..5c4216a0 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,24 +5,21 @@ Issues](https://github.com/BCDA-APS/ad_hoc_diffractometer/issues) for the full issue tracker. The initial project development roadmap is documented here: [roadmap](https://github.com/BCDA-APS/ad_hoc_diffractometer/blob/main/roadmap.md). - ## Release v0.11.1 diff --git a/docs/source/concepts.md b/docs/source/concepts.md index e5b57b3d..cb96a43a 100644 --- a/docs/source/concepts.md +++ b/docs/source/concepts.md @@ -356,8 +356,8 @@ reference vector n̂ (surface normal, polarization axis, etc.): ```python from ad_hoc_diffractometer import ReferenceConstraint -ReferenceConstraint("alpha_i", 5.0) # incidence angle fixed -ReferenceConstraint("a_eq_b", True) # alpha_i = beta_out (symmetric) +ReferenceConstraint("incidence", 5.0) # incidence angle fixed +ReferenceConstraint("specular", True) # incidence = emergence (symmetric) ``` Taxonomy rules: at most one {class}`~ad_hoc_diffractometer.mode.DetectorConstraint`, diff --git a/docs/source/geometries/psic.md b/docs/source/geometries/psic.md index d7843d95..1f6e7f41 100644 --- a/docs/source/geometries/psic.md +++ b/docs/source/geometries/psic.md @@ -106,11 +106,11 @@ The scattering plane is locked vertical by `mu = 0` and `nu = 0`; | **Computed** | eta, phi, delta | | **Constant during** `forward()` | chi, mu = 0, nu = 0 | -### `fixed_alpha_i_vertical` +### `fixed_incidence_vertical` Incidence angle α_i fixed at declared value (default 0°) in the vertical scattering plane. -Override at run time with `g.modes["fixed_alpha_i_vertical"].with_constraint_values(alpha_i=...)` — see {doc}`../howto/constraints`. +Override at run time with `g.modes["fixed_incidence_vertical"].with_constraint_values(incidence=...)` — see {doc}`../howto/constraints`. Set ``g.surface_normal = (h, k, l)`` before calling ``forward()``. | | | @@ -118,13 +118,13 @@ Set ``g.surface_normal = (h, k, l)`` before calling ``forward()``. | **Computed** | eta, chi, phi, delta | | **Constant during** `forward()` | mu = 0, nu = 0 | | **Extras (input)** | n̂ → set `g.surface_normal = (h, k, l)` | -| **Extras (output)** | alpha_i (incidence angle), beta_out (exit angle) | +| **Extras (output)** | incidence (incidence angle), emergence (exit angle) | -### `fixed_beta_out_vertical` +### `fixed_emergence_vertical` Exit angle β_out fixed at declared value (default 0°) in the vertical scattering plane. -Override at run time with `g.modes["fixed_beta_out_vertical"].with_constraint_values(beta_out=...)` — see {doc}`../howto/constraints`. +Override at run time with `g.modes["fixed_emergence_vertical"].with_constraint_values(emergence=...)` — see {doc}`../howto/constraints`. Set ``g.surface_normal = (h, k, l)`` before calling ``forward()``. | | | @@ -132,9 +132,9 @@ Set ``g.surface_normal = (h, k, l)`` before calling ``forward()``. | **Computed** | eta, chi, phi, delta | | **Constant during** `forward()` | mu = 0, nu = 0 | | **Extras (input)** | n̂ → set `g.surface_normal = (h, k, l)` | -| **Extras (output)** | alpha_i, beta_out | +| **Extras (output)** | incidence, emergence | -### `alpha_eq_beta_vertical` +### `specular_vertical` Symmetric reflection: α_i = β_out in the vertical scattering plane. Set ``g.surface_normal = (h, k, l)`` before calling ``forward()``. @@ -144,7 +144,7 @@ Set ``g.surface_normal = (h, k, l)`` before calling ``forward()``. | **Computed** | eta, chi, phi, delta | | **Constant during** `forward()` | mu = 0, nu = 0 | | **Extras (input)** | n̂ → set `g.surface_normal = (h, k, l)` | -| **Extras (output)** | alpha_i, beta_out | +| **Extras (output)** | incidence, emergence | ### `fixed_psi_vertical` @@ -167,7 +167,7 @@ validated ψ. See {doc}`../howto/surface`. | **Extras (input)** | n̂ → set `g.azimuth = (h, k, l)`; ψ target via `with_constraint_values(psi=...)` | | **Extras (output)** | psi (computed azimuth) | -### `fixed_alpha_i_fixed_chi_fixed_phi` +### `fixed_incidence_fixed_chi_fixed_phi` Issue #264. Two sample stages (`chi`, `phi`) and the incidence angle α_i are all fixed; the four remaining angles `mu`, `eta`, `nu`, `delta` @@ -175,7 +175,7 @@ are solved jointly from the Bragg condition plus the α_i target. This is a 4-D Newton solve that routes through the ``_solve_free_detectors`` solver (both detector stages float to lift the detector arm out of plane as needed). -Override any of the three pinned values at run time with `g.modes["fixed_alpha_i_fixed_chi_fixed_phi"].with_constraint_values(chi=..., phi=..., alpha_i=...)` — see {doc}`../howto/constraints`. +Override any of the three pinned values at run time with `g.modes["fixed_incidence_fixed_chi_fixed_phi"].with_constraint_values(chi=..., phi=..., incidence=...)` — see {doc}`../howto/constraints`. Set ``g.surface_normal = (h, k, l)`` before calling ``forward()``. @@ -184,7 +184,7 @@ Set ``g.surface_normal = (h, k, l)`` before calling ``forward()``. | **Computed** | mu, eta, nu, delta | | **Constant during** `forward()` | chi, phi, α_i = target | | **Extras (input)** | n̂ → set `g.surface_normal = (h, k, l)` | -| **Extras (output)** | alpha_i, beta_out | +| **Extras (output)** | incidence, emergence | ### `fixed_omega_vertical` @@ -296,11 +296,11 @@ is kinematically infeasible in this mode. | **Computed** | mu, phi, nu | | **Constant during** `forward()` | chi, eta = 0, delta = 0 | -### `fixed_alpha_i_horizontal` +### `fixed_incidence_horizontal` Incidence angle α_i fixed at declared value (default 0°) in the horizontal scattering plane. -Override at run time with `g.modes["fixed_alpha_i_horizontal"].with_constraint_values(alpha_i=...)` — see {doc}`../howto/constraints`. +Override at run time with `g.modes["fixed_incidence_horizontal"].with_constraint_values(incidence=...)` — see {doc}`../howto/constraints`. Set ``g.surface_normal = (h, k, l)`` before calling ``forward()``. | | | @@ -308,13 +308,13 @@ Set ``g.surface_normal = (h, k, l)`` before calling ``forward()``. | **Computed** | mu, chi, phi, nu | | **Constant during** `forward()` | eta = 0, delta = 0 | | **Extras (input)** | n̂ → set `g.surface_normal = (h, k, l)` | -| **Extras (output)** | alpha_i, beta_out | +| **Extras (output)** | incidence, emergence | -### `fixed_beta_out_horizontal` +### `fixed_emergence_horizontal` Exit angle β_out fixed at declared value (default 0°) in the horizontal scattering plane. -Override at run time with `g.modes["fixed_beta_out_horizontal"].with_constraint_values(beta_out=...)` — see {doc}`../howto/constraints`. +Override at run time with `g.modes["fixed_emergence_horizontal"].with_constraint_values(emergence=...)` — see {doc}`../howto/constraints`. Set ``g.surface_normal = (h, k, l)`` before calling ``forward()``. | | | @@ -322,9 +322,9 @@ Set ``g.surface_normal = (h, k, l)`` before calling ``forward()``. | **Computed** | mu, chi, phi, nu | | **Constant during** `forward()` | eta = 0, delta = 0 | | **Extras (input)** | n̂ → set `g.surface_normal = (h, k, l)` | -| **Extras (output)** | alpha_i, beta_out | +| **Extras (output)** | incidence, emergence | -### `alpha_eq_beta_horizontal` +### `specular_horizontal` Symmetric reflection: α_i = β_out in the horizontal scattering plane. Set ``g.surface_normal = (h, k, l)`` before calling ``forward()``. @@ -334,7 +334,7 @@ Set ``g.surface_normal = (h, k, l)`` before calling ``forward()``. | **Computed** | mu, chi, phi, nu | | **Constant during** `forward()` | eta = 0, delta = 0 | | **Extras (input)** | n̂ → set `g.surface_normal = (h, k, l)` | -| **Extras (output)** | alpha_i, beta_out | +| **Extras (output)** | incidence, emergence | ### `fixed_psi_horizontal` @@ -449,23 +449,23 @@ the Hkl/Soleil `E6C` `hkl` engine, and You (1999). | `bisecting_vertical` | `(2,0,5,0,0)` | `bissector_vertical` | §5.1 | | `fixed_phi_vertical` | `(2,0,4,2,0)` | `constant_phi_vertical` | §5.2 | | `fixed_chi_vertical` | `(2,0,3,2,0)` | `constant_chi_vertical` | §5.2 | -| `fixed_alpha_i_vertical` | `(2,2,2,0,0)` | — | §6.1 | -| `fixed_beta_out_vertical` | `(2,3,2,0,0)` | — | §6.2 | -| `alpha_eq_beta_vertical` | `(2,1,2,0,0)` | — | §6.3 | +| `fixed_incidence_vertical` | `(2,2,2,0,0)` | — | §6.1 | +| `fixed_emergence_vertical` | `(2,3,2,0,0)` | — | §6.2 | | `fixed_psi_vertical` | `(2,4,2,0,0)` | `psi_constant_vertical` | §6.4 | -| `fixed_alpha_i_fixed_chi_fixed_phi` | `(2,2,3,4,0)` ‡ | — | §6.1 | +| `fixed_incidence_fixed_chi_fixed_phi` | `(2,2,3,4,0)` ‡ | — | §6.1 | | `fixed_omega_vertical` | `setmode d1 0 0 0` | — | §5 (Q[6]) | | `double_diffraction_vertical` | — | `double_diffraction_vertical` | §6.5 | +| `specular_vertical` | `(2,1,2,0,0)` | — | §6.3 | | `zone_vertical` | `setmode 5` | (TODO `HklEngine "zone"`) | §6 | | `bisecting_horizontal` | `(1,0,6,0,0)` | `bissector_horizontal` | §5.1 | | `fixed_phi_horizontal` | `(1,0,4,1,0)` † | — | §5.2 | | `fixed_chi_horizontal` | `(1,0,3,1,0)` † | — | §5.2 | -| `fixed_alpha_i_horizontal` | `(1,2,1,0,0)` | — | §6.1 | -| `fixed_beta_out_horizontal` | `(1,3,1,0,0)` | — | §6.2 | -| `alpha_eq_beta_horizontal` | `(1,1,1,0,0)` | — | §6.3 | +| `fixed_incidence_horizontal` | `(1,2,1,0,0)` | — | §6.1 | +| `fixed_emergence_horizontal` | `(1,3,1,0,0)` | — | §6.2 | | `fixed_psi_horizontal` | `(1,4,1,0,0)` | `psi_constant_horizontal` | §6.4 | | `fixed_omega_horizontal` | `setmode d1 0 0 0` | — | §5 (Q[6]) | | `double_diffraction_horizontal` | — | `double_diffraction_horizontal` | §6.5 | +| `specular_horizontal` | `(1,1,1,0,0)` | — | §6.3 | | `zone_horizontal` | `setmode 5` | (TODO `HklEngine "zone"`) | §6 | | `lifting_detector_phi` | `setmode 0 0 2 3 5` ‡ | `lifting_detector_phi` | §5.4 | | `lifting_detector_mu` | `setmode 0 0 1 3 4` ‡ | `lifting_detector_mu` | §5.4 | diff --git a/docs/source/geometries/s2d2.md b/docs/source/geometries/s2d2.md index 62718877..245ddb83 100644 --- a/docs/source/geometries/s2d2.md +++ b/docs/source/geometries/s2d2.md @@ -79,7 +79,7 @@ Override at run time with `g.modes["fixed_mu"].with_constraint_values(mu=...)` ### `reflectivity` {class}`~ad_hoc_diffractometer.mode.ReferenceConstraint`: -symmetric reflection — incidence angle equals exit angle (alpha_i = beta_out). +symmetric reflection — incidence = emergence. Requires ``g.surface_normal = (h, k, l)`` — see {doc}`../howto/surface`. | | | @@ -87,7 +87,7 @@ Requires ``g.surface_normal = (h, k, l)`` — see {doc}`../howto/surface`. | **Computed** | mu, Z, nu, delta | | **Constant during** `forward()` | — | | **Extras (input)** | n̂ → set `g.surface_normal = (h, k, l)` | -| **Extras (output)** | alpha_i, beta_out | +| **Extras (output)** | incidence, emergence | ## API reference diff --git a/docs/source/geometries/sixc.md b/docs/source/geometries/sixc.md index 9a8d7aff..7dfc1720 100644 --- a/docs/source/geometries/sixc.md +++ b/docs/source/geometries/sixc.md @@ -105,44 +105,44 @@ Override at run time with `g.modes["fixed_alpha_5c"].with_constraint_values(alph | **Computed** | omega, chi, phi, delta, gamma | | **Constant during** `forward()` | alpha, gamma = 0 | -### `fixed_alpha_zaxis` +### `fixed_incidence_zaxis` {class}`~ad_hoc_diffractometer.mode.SampleConstraint` × 2 + {class}`~ad_hoc_diffractometer.mode.ReferenceConstraint`: Z-axis mode with fixed incidence angle. Requires ``g.surface_normal = (h, k, l)`` — see {doc}`../howto/surface`. -Override any of the three pinned values at run time with `g.modes["fixed_alpha_zaxis"].with_constraint_values(alpha=..., chi=..., phi=...)` — see {doc}`../howto/constraints`. +Override any of the three pinned values at run time with `g.modes["fixed_incidence_zaxis"].with_constraint_values(alpha=..., chi=..., phi=...)` — see {doc}`../howto/constraints`. | | | |---|---| | **Computed** | omega, delta, gamma | | **Constant during** `forward()` | alpha (= β_in), chi, phi | | **Extras (input)** | n̂ → set `g.surface_normal = (h, k, l)` | -| **Extras (output)** | alpha_i (incidence angle), beta_out (exit angle) | +| **Extras (output)** | incidence (incidence angle), emergence (exit angle) | -### `fixed_beta_zaxis` +### `fixed_emergence_zaxis` {class}`~ad_hoc_diffractometer.mode.DetectorConstraint` + {class}`~ad_hoc_diffractometer.mode.SampleConstraint` + {class}`~ad_hoc_diffractometer.mode.ReferenceConstraint`: Z-axis mode with fixed exit angle. Requires ``g.surface_normal = (h, k, l)`` — see {doc}`../howto/surface`. -Override at run time with `g.modes["fixed_beta_zaxis"].with_constraint_values(gamma=..., chi=...)` — see {doc}`../howto/constraints`. +Override at run time with `g.modes["fixed_emergence_zaxis"].with_constraint_values(gamma=..., chi=...)` — see {doc}`../howto/constraints`. | | | |---|---| | **Computed** | omega, delta, alpha | | **Constant during** `forward()` | gamma (= β_out), chi | | **Extras (input)** | n̂ → set `g.surface_normal = (h, k, l)` | -| **Extras (output)** | alpha_i, beta_out | +| **Extras (output)** | incidence, emergence | -### `alpha_eq_beta_zaxis` +### `specular_zaxis` {class}`~ad_hoc_diffractometer.mode.SampleConstraint` × 2 + {class}`~ad_hoc_diffractometer.mode.ReferenceConstraint`: Z-axis mode, symmetric reflection (α = γ, β_in = β_out). Requires ``g.surface_normal = (h, k, l)`` — see {doc}`../howto/surface`. -Override the chi or phi pin at run time with `g.modes["alpha_eq_beta_zaxis"].with_constraint_values(chi=..., phi=...)` — see {doc}`../howto/constraints`. +Override the chi or phi pin at run time with `g.modes["specular_zaxis"].with_constraint_values(chi=..., phi=...)` — see {doc}`../howto/constraints`. | | | |---|---| | **Computed** | omega, delta, alpha, gamma | | **Constant during** `forward()` | chi, phi | | **Extras (input)** | n̂ → set `g.surface_normal = (h, k, l)` | -| **Extras (output)** | alpha_i, beta_out | +| **Extras (output)** | incidence, emergence | ## API reference diff --git a/docs/source/geometries/zaxis.md b/docs/source/geometries/zaxis.md index fd761eee..263c5679 100644 --- a/docs/source/geometries/zaxis.md +++ b/docs/source/geometries/zaxis.md @@ -72,26 +72,26 @@ See {doc}`../howto/constraints` for the extras dict pattern. {class}`~ad_hoc_diffractometer.mode.ReferenceConstraint`: surface normal aligned with the Z-axis; alpha directly equals the incidence angle β_in, gamma directly equals the exit angle β_out. -Override the α_i target at run time with `g.modes["zaxis"].with_constraint_values(alpha_i=...)` — see {doc}`../howto/constraints`. +Override the α_i target at run time with `g.modes["zaxis"].with_constraint_values(incidence=...)` — see {doc}`../howto/constraints`. | | | |---|---| | **Computed** | Z, delta, gamma | | **Constant during** `forward()` | — | | **Extras (input)** | n̂ → set `g.surface_normal = (h, k, l)` | -| **Extras (output)** | alpha_i (= alpha), beta_out (= gamma) | +| **Extras (output)** | incidence (= alpha), emergence (= gamma) | ### `reflectivity` {class}`~ad_hoc_diffractometer.mode.ReferenceConstraint`: -symmetric reflection — alpha_i = beta_out (alpha = gamma). +symmetric reflection — incidence = emergence (alpha = gamma). | | | |---|---| | **Computed** | Z, delta, alpha, gamma | | **Constant during** `forward()` | — | | **Extras (input)** | n̂ → set `g.surface_normal = (h, k, l)` | -| **Extras (output)** | alpha_i, beta_out | +| **Extras (output)** | incidence, emergence | ## API reference diff --git a/docs/source/glossary.md b/docs/source/glossary.md index 58e5686b..7ff1ed7d 100644 --- a/docs/source/glossary.md +++ b/docs/source/glossary.md @@ -27,10 +27,7 @@ Azimuthal reference vector per-mode ``Extras (input)`` tables this same vector is referred to by its mathematical symbol **n̂** (rendered as the ``n_hat`` key in ``mode.extras``). See {term}`n̂ (reference vector)` for the full - surface-form table, and {doc}`howto/surface`. The attribute was - named ``azimuthal_reference`` before v0.12.0 (issue #298); the old - name remains as a deprecated forwarding alias and will be removed - in a future release. + surface-form table, and {doc}`howto/surface`. B matrix The matrix that encodes the reciprocal lattice and maps Miller indices @@ -214,8 +211,8 @@ n̂ (reference vector) Assigning a value here has no effect on ``forward()`` (and emits a ``UserWarning`` directing the caller at the geometry attribute below — issue #294). - * - On the geometry, when used by ``"alpha_i"``, ``"beta_out"``, - ``"a_eq_b"`` reference constraints + * - On the geometry, when used by ``"incidence"``, ``"emergence"``, + ``"specular"`` reference constraints - {attr}`~ad_hoc_diffractometer.diffractometer.AdHocDiffractometer.surface_normal` - The actual stored vector. Set via ``g.surface_normal = (h, k, l)``. @@ -313,10 +310,10 @@ Surface normal the vector perpendicular to the sample surface. Stored on the geometry as {attr}`~ad_hoc_diffractometer.diffractometer.AdHocDiffractometer.surface_normal` - and consumed by the ``"alpha_i"``, ``"beta_out"``, and ``"a_eq_b"`` + and consumed by the ``"incidence"``, ``"emergence"``, and ``"specular"`` {class}`~ad_hoc_diffractometer.mode.ReferenceConstraint` modes (plus {func}`~ad_hoc_diffractometer.reference.incidence_angle` / - {func}`~ad_hoc_diffractometer.reference.exit_angle` and + {func}`~ad_hoc_diffractometer.reference.emergence_angle` and {mod}`~ad_hoc_diffractometer.surface` helpers). Set with ``g.surface_normal = (h, k, l)`` (a length-3 sequence of numbers; ``(0, 0, 0)`` is rejected). Default is ``None``. In per-mode diff --git a/docs/source/howto/constraints.md b/docs/source/howto/constraints.md index c0819516..e894f18e 100644 --- a/docs/source/howto/constraints.md +++ b/docs/source/howto/constraints.md @@ -61,17 +61,17 @@ This is implemented for all geometries with two or more detector stages **Reference constraint** — expresses a condition between Q and a reference vector n̂ (surface normal, polarization axis, etc.). -The incidence/exit-angle constraints (``alpha_i``, ``beta_out``, -``a_eq_b``) are implemented when ``surface_normal`` is set; +The incidence/emergence-angle constraints (``incidence``, ``emergence``, +``specular``) are implemented when ``surface_normal`` is set; ``psi`` and ``naz`` are not yet implemented as forward constraints. See {doc}`surface`: ```python from ad_hoc_diffractometer import ReferenceConstraint -ReferenceConstraint("psi", 90.0) # azimuthal angle of n̂ about Q -ReferenceConstraint("alpha_i", 5.0) # incidence angle -ReferenceConstraint("a_eq_b", True) # alpha_i = beta_out (symmetric) +ReferenceConstraint("psi", 90.0) # azimuthal angle of n̂ about Q +ReferenceConstraint("incidence", 5.0) # incidence angle +ReferenceConstraint("specular", True) # incidence = emergence (symmetric) ``` **Rules:** at most one {class}`~ad_hoc_diffractometer.mode.DetectorConstraint`, @@ -146,8 +146,8 @@ readable for your case. `ConstraintSet.with_constraint_values(**updates)` returns a fresh `ConstraintSet` with the named constraint values replaced. Each keyword argument names a constraint by its `.name` attribute (a stage name for -sample / detector constraints, a reference name like `alpha_i` / -`beta_out` / `psi` / `a_eq_b` for reference constraints). Constraint +sample / detector constraints, a reference name like `incidence` / +`emergence` / `psi` / `specular` for reference constraints). Constraint order, `computed`, `extras`, and `cut_points` are preserved. ```python @@ -155,10 +155,10 @@ import ad_hoc_diffractometer as ahd g = ahd.make_geometry("psic") -# Multiple values at once (psic B3 mode: chi, phi, and the alpha_i target): -g.modes["fixed_alpha_i_fixed_chi_fixed_phi"] = ( - g.modes["fixed_alpha_i_fixed_chi_fixed_phi"] - .with_constraint_values(chi=15.0, phi=30.0, alpha_i=5.0) +# Multiple values at once (psic B3 mode: chi, phi, and the incidence target): +g.modes["fixed_incidence_fixed_chi_fixed_phi"] = ( + g.modes["fixed_incidence_fixed_chi_fixed_phi"] + .with_constraint_values(chi=15.0, phi=30.0, incidence=5.0) ) # Single value (psic fixed_chi_vertical: default chi=90° → 45°): @@ -219,16 +219,16 @@ For a reference-target value (surface modes), pass a new ```python from ad_hoc_diffractometer import ReferenceConstraint -# psic B3 mode: pin the incidence angle alpha_i at 5° instead of the +# psic B3 mode: pin the incidence angle at 5° instead of the # YAML default 0°. -g.modes["fixed_alpha_i_fixed_chi_fixed_phi"] = ConstraintSet( +g.modes["fixed_incidence_fixed_chi_fixed_phi"] = ConstraintSet( [ SampleConstraint("chi", 0.0), SampleConstraint("phi", 0.0), - ReferenceConstraint("alpha_i", 5.0), # was 0.0; now 5.0 + ReferenceConstraint("incidence", 5.0), # was 0.0; now 5.0 ], - computed=g.modes["fixed_alpha_i_fixed_chi_fixed_phi"].computed, - extras=dict(g.modes["fixed_alpha_i_fixed_chi_fixed_phi"].extras), + computed=g.modes["fixed_incidence_fixed_chi_fixed_phi"].computed, + extras=dict(g.modes["fixed_incidence_fixed_chi_fixed_phi"].extras), ) ``` diff --git a/docs/source/howto/modes.md b/docs/source/howto/modes.md index d47139bb..7bdf6524 100644 --- a/docs/source/howto/modes.md +++ b/docs/source/howto/modes.md @@ -71,10 +71,10 @@ g.mode_name = "fixed_chi" sols_45 = g.forward(1, 0, 0) # chi = 45° # Several values at once (e.g. psic B3 mode: two sample stages plus -# the alpha_i target — three pinned values in one call): -g.modes["fixed_alpha_i_fixed_chi_fixed_phi"] = ( - g.modes["fixed_alpha_i_fixed_chi_fixed_phi"] - .with_constraint_values(chi=15.0, phi=30.0, alpha_i=5.0) +# the incidence target — three pinned values in one call): +g.modes["fixed_incidence_fixed_chi_fixed_phi"] = ( + g.modes["fixed_incidence_fixed_chi_fixed_phi"] + .with_constraint_values(chi=15.0, phi=30.0, incidence=5.0) ) ``` diff --git a/docs/source/howto/surface.md b/docs/source/howto/surface.md index a16f1565..cac5119b 100644 --- a/docs/source/howto/surface.md +++ b/docs/source/howto/surface.md @@ -14,14 +14,14 @@ chosen by the active mode's | ReferenceConstraint name | Set on the geometry | Recipe | |---|---|---| -| `alpha_i`, `beta_out`, `a_eq_b` | `surface_normal` | `g.surface_normal = (h, k, l)` | +| `incidence`, `emergence`, `specular` | `surface_normal` | `g.surface_normal = (h, k, l)` | | `psi`, `naz` | `azimuth` | `g.azimuth = (h, k, l)` | | `omega` (SPEC pseudo-angle) | (none required) | — | Don't want to memorise the table? Ask the geometry directly: ```python -g.mode_name = "fixed_alpha_i_fixed_chi_fixed_phi" +g.mode_name = "fixed_incidence_fixed_chi_fixed_phi" attr = g.required_reference_vector # → 'surface_normal' setattr(g, attr, (0, 0, 1)) # equivalent to g.surface_normal = ... ``` @@ -29,15 +29,6 @@ setattr(g, attr, (0, 0, 1)) # equivalent to g.surface_normal = ... `required_reference_vector` returns `'surface_normal'`, `'azimuth'`, or `None`. -```{note} -The `azimuth` attribute was named `azimuthal_reference` before -v0.12.0 (issue #298). The old name remains as a deprecated forwarding -alias — both the property access and the constructor keyword emit -`DeprecationWarning` — and will be removed in a future release. -`required_reference_vector` now returns `'azimuth'` where it -previously returned `'azimuthal_reference'`. -``` - ## What the `n̂` placeholder in `mode.extras` means Every per-geometry mode table shows a row like @@ -85,8 +76,8 @@ internally using the UB matrix. Two separate reference vectors may be set: - **`surface_normal`** — the direction perpendicular to the sample surface, - used by `alpha_i`, `alpha_f`, `incidence_angle`, `exit_angle`, and - surface modes (`zaxis`, `reflectivity`, `alpha_eq_beta_zaxis`). + used by `incidence`, `emergence`, `incidence_angle`, `emergence_angle`, and + surface modes (`zaxis`, `reflectivity`, `specular_zaxis`). - **`azimuth`** — the direction used to define ψ = 0, used by `psi_angle` and `fixed_psi_*` modes. @@ -134,10 +125,10 @@ print(g.azimuth) # (1.0, 0.0, 0.0) g.azimuth = (0, 0, 1) ``` -## Compute incidence and exit angles +## Compute incidence and emergence angles ```python -from ad_hoc_diffractometer import incidence_angle, exit_angle +from ad_hoc_diffractometer import emergence_angle, incidence_angle g.surface_normal = (0, 0, 1) g.mode_name = "bisecting_vertical" @@ -145,8 +136,8 @@ solutions = g.forward(1, 0, 0) for sol in solutions: ai = incidence_angle(g, angles=sol) - af = exit_angle(g, angles=sol) - print(f"alpha_i = {ai:.4f}° beta_out = {af:.4f}°") + af = emergence_angle(g, angles=sol) + print(f"incidence = {ai:.4f}° emergence = {af:.4f}°") ``` Both functions use the current stage angles when `angles=None`: @@ -191,10 +182,10 @@ print(f"naz = {naz:.4f}°") naz is the azimuthal angle of the surface normal n̂ projected onto the horizontal plane of the lab frame. -## Symmetric reflection condition (α_i = β_out) +## Specular reflection condition (incidence = emergence) ```python -from ad_hoc_diffractometer import incidence_angle, exit_angle +from ad_hoc_diffractometer import emergence_angle, incidence_angle g.surface_normal = (0, 0, 1) g.mode_name = "bisecting_vertical" @@ -202,9 +193,9 @@ solutions = g.forward(1, 0, 0) for sol in solutions: ai = incidence_angle(g, angles=sol) - af = exit_angle(g, angles=sol) + af = emergence_angle(g, angles=sol) is_sym = abs(ai - af) < 0.01 # within 0.01° - print(f"alpha_i={ai:.3f}° beta_out={af:.3f}° symmetric={is_sym}") + print(f"incidence={ai:.3f}° emergence={af:.3f}° specular={is_sym}") ``` Alternatively, use the built-in `is_specular()` method on the geometry: @@ -236,10 +227,6 @@ g2 = AdHocDiffractometer.from_dict(d) print(g2.surface_normal) # (0.0, 0.0, 1.0) ``` -`from_dict()` also accepts the legacy key `"azimuthal_reference"` -for backward compatibility with sessions saved by -ad_hoc_diffractometer ≤ v0.11.x (issue #298). - ## Reference constraint modes Modes that use a ``ReferenceConstraint`` require the appropriate reference @@ -270,7 +257,7 @@ only if it matches the stored target — otherwise it returns an empty list. - {doc}`constraints` — constraint framework and run-time mode customisation - {func}`~ad_hoc_diffractometer.reference.incidence_angle` -- {func}`~ad_hoc_diffractometer.reference.exit_angle` +- {func}`~ad_hoc_diffractometer.reference.emergence_angle` - {func}`~ad_hoc_diffractometer.reference.psi_angle` - {func}`~ad_hoc_diffractometer.reference.naz_angle` - {class}`~ad_hoc_diffractometer.mode.ReferenceConstraint` diff --git a/docs/source/reference/declarative_geometry_schema.md b/docs/source/reference/declarative_geometry_schema.md index 658ff012..756a772d 100644 --- a/docs/source/reference/declarative_geometry_schema.md +++ b/docs/source/reference/declarative_geometry_schema.md @@ -284,9 +284,9 @@ any others. | `detector` | `stage`, `value` | {class}`~ad_hoc_diffractometer.mode.DetectorConstraint` | | `reference` | `name`, `value` | {class}`~ad_hoc_diffractometer.mode.ReferenceConstraint` | -For `reference`, `name` is one of `"psi"`, `"alpha_i"`, `"beta_out"`, -`"a_eq_b"`, `"naz"`, `"omega"`; `value` is a float for the angular -constraints and the literal `true` for `a_eq_b`. The `"omega"` name +For `reference`, `name` is one of `"psi"`, `"incidence"`, `"emergence"`, +`"specular"`, `"naz"`, `"omega"`; `value` is a float for the angular +constraints and the literal `true` for `specular`. The `"omega"` name selects the SPEC `OMEGA` pseudo-angle (the angle between Q and the plane of the chi circle, SPEC `psic` `def OMEGA 'Q[6]'`); it applies to psic-family geometries (those with a sample stage named `chi`) and diff --git a/src/ad_hoc_diffractometer/benchmark.py b/src/ad_hoc_diffractometer/benchmark.py index cc2824af..06ec9375 100644 --- a/src/ad_hoc_diffractometer/benchmark.py +++ b/src/ad_hoc_diffractometer/benchmark.py @@ -100,7 +100,7 @@ def _prepare_mode(geometry, mode_name: str) -> None: - fixed_psi modes: sets ``azimuth`` - double_diffraction modes: sets h2/k2/l2 extras - zone modes: sets z0/z1 extras to a generic (h,k,0) plane - - surface/reference modes (alpha_i, beta_out, a_eq_b): + - surface/reference modes (incidence, emergence, specular): sets ``surface_normal`` """ geometry.mode_name = mode_name @@ -123,10 +123,10 @@ def _prepare_mode(geometry, mode_name: str) -> None: if cname == "psi" and geometry.azimuth is None: geometry.azimuth = (0, 0, 1) - # Surface modes: set surface_normal if needed + # Surface modes: set surface_normal if needed. for c in cs._constraints: cname = getattr(c, "_name", getattr(c, "name", "")) - if cname in ("alpha_i", "beta_out", "a_eq_b"): + if cname in ("incidence", "emergence", "specular"): if geometry.surface_normal is None: geometry.surface_normal = (0, 0, 1) diff --git a/src/ad_hoc_diffractometer/diffractometer.py b/src/ad_hoc_diffractometer/diffractometer.py index 68a63076..42e23ad8 100644 --- a/src/ad_hoc_diffractometer/diffractometer.py +++ b/src/ad_hoc_diffractometer/diffractometer.py @@ -9,7 +9,6 @@ import builtins import logging -import warnings import numpy as np @@ -74,10 +73,6 @@ class AdHocDiffractometer: Azimuthal reference direction as Miller indices (h, k, l). Used by :meth:`psi` to compute the azimuthal angle ψ. ``None`` (default) means no reference is set. Must be a non-zero vector. - azimuthal_reference : tuple of float or None, optional - Deprecated alias for ``azimuth``. Accepted for backward - compatibility; emits :class:`DeprecationWarning` if used. Will - be removed in a future release. modes : dict[str, ConstraintSet] or ModeDict or None, optional Named diffraction modes available for this geometry. Keys are mode names (str); values are :class:`~mode.DiffractionMode` @@ -110,7 +105,6 @@ def __init__( kappa_alpha_deg: float | None = None, kappa_pseudo_angle_convention=None, azimuth: tuple[float, float, float] | None = None, - azimuthal_reference: tuple[float, float, float] | None = None, modes: dict | ModeDict | None = None, default_mode: str | None = None, cut_points: dict[str, float] | None = None, @@ -122,25 +116,6 @@ def __init__( self.wavelength = wavelength # validated via property setter self.kappa_alpha_deg = kappa_alpha_deg self.kappa_pseudo_angle_convention = kappa_pseudo_angle_convention - # Resolve azimuth / azimuthal_reference (deprecated alias). If both - # are supplied and disagree, raise; if only the deprecated form is - # supplied, warn and accept it. - if azimuthal_reference is not None: - if azimuth is not None and tuple(azimuth) != tuple(azimuthal_reference): - raise ValueError( - "Cannot specify both 'azimuth' and 'azimuthal_reference' " - "with different values; 'azimuthal_reference' is the " - "deprecated alias for 'azimuth'." - ) - warnings.warn( - "The 'azimuthal_reference' constructor keyword is deprecated " - "since v0.12.0; use 'azimuth' instead. The old name will be " - "removed in a future release.", - DeprecationWarning, - stacklevel=2, - ) - if azimuth is None: - azimuth = azimuthal_reference self.azimuth = azimuth # validated via property setter self._surface_normal: tuple[float, float, float] | None = None self._detector_distance: float | None = None @@ -615,38 +590,6 @@ def azimuth(self, value: tuple[float, float, float] | None) -> None: ) self._azimuth = (h, k, l) - @property - def azimuthal_reference(self) -> tuple[float, float, float] | None: - """ - Deprecated alias for :attr:`azimuth`. - - .. deprecated:: 0.12.0 - Use :attr:`azimuth` instead. This alias will be removed in a - future release. - - Reads and writes are forwarded to :attr:`azimuth`; every access - emits a :class:`DeprecationWarning`. - """ - warnings.warn( - "AdHocDiffractometer.azimuthal_reference is deprecated since " - "v0.12.0; use 'azimuth' instead. The old name will be removed " - "in a future release.", - DeprecationWarning, - stacklevel=2, - ) - return self._azimuth - - @azimuthal_reference.setter - def azimuthal_reference(self, value: tuple[float, float, float] | None) -> None: - warnings.warn( - "AdHocDiffractometer.azimuthal_reference is deprecated since " - "v0.12.0; use 'azimuth' instead. The old name will be removed " - "in a future release.", - DeprecationWarning, - stacklevel=2, - ) - self.azimuth = value - # ------------------------------------------------------------------ # Diffraction modes # ------------------------------------------------------------------ @@ -1206,8 +1149,8 @@ def surface_normal(self) -> tuple[float, float, float] | None: The surface normal defines the direction perpendicular to the sample surface in reciprocal space. It is used by the surface geometry - calculations (:meth:`alpha_i`, :meth:`alpha_f`, :meth:`q_components`, - :meth:`is_specular`, :meth:`is_evanescent`). + calculations (:meth:`incidence`, :meth:`emergence`, + :meth:`q_components`, :meth:`is_specular`, :meth:`is_evanescent`). When ``None``, the surface calculations fall back to :attr:`azimuth` if that is set. @@ -1267,9 +1210,9 @@ def required_reference_vector(self) -> str | None: ===================================== ================================= ReferenceConstraint name Required attribute on the geometry ===================================== ================================= - ``"alpha_i"`` :attr:`surface_normal` - ``"beta_out"`` :attr:`surface_normal` - ``"a_eq_b"`` :attr:`surface_normal` + ``"incidence"`` :attr:`surface_normal` + ``"emergence"`` :attr:`surface_normal` + ``"specular"`` :attr:`surface_normal` ``"psi"`` :attr:`azimuth` ``"naz"`` :attr:`azimuth` ``"omega"`` (none) @@ -1322,7 +1265,7 @@ def required_reference_vector(self) -> str | None: rc: ReferenceConstraint | None = mode.reference_constraint if rc is None: return None - if rc.name in {"alpha_i", "beta_out", "a_eq_b"}: + if rc.name in {"incidence", "emergence", "specular"}: return "surface_normal" if rc.name in {"psi", "naz"}: return "azimuth" @@ -1445,7 +1388,7 @@ def detector_offset(self, value: tuple[float, float] | None) -> None: # Surface geometry: incidence / emergence / Q-decomposition methods # ------------------------------------------------------------------ - def alpha_i(self, angles: dict[str, float] | None = None) -> float: + def incidence(self, angles: dict[str, float] | None = None) -> float: """ Angle of incidence αᵢ (degrees). @@ -1465,7 +1408,7 @@ def alpha_i(self, angles: dict[str, float] | None = None) -> float: See Also -------- - alpha_f : Angle of emergence. + emergence : Angle of emergence. Examples -------- @@ -1473,14 +1416,14 @@ def alpha_i(self, angles: dict[str, float] | None = None) -> float: >>> g.wavelength = 1.5406 >>> g.surface_normal = (0, 0, 1) >>> ub_identity(g.sample) - >>> g.alpha_i({"alpha": 5.0, "Z": 0.0, "delta": 20.0, "gamma": 0.0}) + >>> g.incidence({"alpha": 5.0, "Z": 0.0, "delta": 20.0, "gamma": 0.0}) 5.0 """ - from .surface import alpha_i as _alpha_i + from .surface import incidence as _incidence - return _alpha_i(self, angles) + return _incidence(self, angles) - def alpha_f(self, angles: dict[str, float] | None = None) -> float: + def emergence(self, angles: dict[str, float] | None = None) -> float: """ Angle of emergence αf (degrees). @@ -1500,11 +1443,11 @@ def alpha_f(self, angles: dict[str, float] | None = None) -> float: See Also -------- - alpha_i : Angle of incidence. + incidence : Angle of incidence. """ - from .surface import alpha_f as _alpha_f + from .surface import emergence as _emergence - return _alpha_f(self, angles) + return _emergence(self, angles) def q_components(self, angles: dict[str, float] | None = None) -> dict[str, float]: """ @@ -1522,7 +1465,7 @@ def q_components(self, angles: dict[str, float] | None = None) -> dict[str, floa See Also -------- - alpha_i, alpha_f : Incidence and emergence angles. + incidence, emergence : Incidence and emergence angles. """ from .surface import q_components as _q_components @@ -2216,7 +2159,7 @@ def from_dict(cls, d: dict) -> "AdHocDiffractometer": description=d.get("description", ""), wavelength=d.get("wavelength"), kappa_alpha_deg=d.get("kappa_alpha_deg"), - azimuth=d.get("azimuth", d.get("azimuthal_reference")), + azimuth=d.get("azimuth"), modes=restored_modes if restored_modes else None, default_mode=d.get("mode_name"), cut_points=d.get("cut_points"), diff --git a/src/ad_hoc_diffractometer/forward.py b/src/ad_hoc_diffractometer/forward.py index c23837a6..d40157f1 100644 --- a/src/ad_hoc_diffractometer/forward.py +++ b/src/ad_hoc_diffractometer/forward.py @@ -472,7 +472,12 @@ def compute_forward( # The callables are imported lazily inside :func:`_populate_output_extras` to # avoid a circular import at module load time (``reference`` imports from # ``forward``). -_OUTPUT_EXTRA_KEYS = ("alpha_i", "beta_out", "psi", "omega") +_OUTPUT_EXTRA_KEYS = ( + "incidence", + "emergence", + "psi", + "omega", +) def _populate_output_extras( @@ -480,7 +485,7 @@ def _populate_output_extras( mode, solutions: list[dict[str, float]], ) -> None: - """Populate output-slot extras (alpha_i, beta_out, psi, omega) per solution. + """Populate output-slot extras (incidence, emergence, psi, omega) per solution. Issue #292. A subset of declarative modes (psic, sixc, zaxis, s2d2 surface modes; the fixed_psi_* family; fixed_omega_*) declare placeholder slots @@ -495,7 +500,7 @@ def _populate_output_extras( -------- * Only keys actually declared in ``mode.extras`` are touched. * A key declared but whose required reference vector is unset on the - geometry (e.g. ``alpha_i`` without ``surface_normal``) is left as + geometry (e.g. ``incidence`` without ``surface_normal``) is left as ``None``; a debug-level log message records why. * Empty ``solutions`` leaves every slot as an empty list. * Each successful call **replaces** the prior contents of the slot. @@ -508,14 +513,14 @@ def _populate_output_extras( return # Lazy imports — ``reference`` imports from ``forward``. - from .reference import exit_angle as _exit_angle + from .reference import emergence_angle as _emergence_angle from .reference import incidence_angle as _incidence_angle from .reference import omega_pseudo as _omega_pseudo from .reference import psi_angle as _psi_angle computers: dict[str, callable] = { - "alpha_i": _incidence_angle, - "beta_out": _exit_angle, + "incidence": _incidence_angle, + "emergence": _emergence_angle, "psi": _psi_angle, "omega": _omega_pseudo, } @@ -622,7 +627,7 @@ def _solve_constraint_set( # Free-detectors mode (issue #264 — both detector stages float to # satisfy Bragg jointly with the remaining sample stages, optionally - # with one ReferenceConstraint such as alpha_i). + # with one ReferenceConstraint such as incidence). if _is_free_detectors_mode(geometry, mode): return _solve_free_detectors(geometry, Q_phi, ttheta_deg, mode) @@ -3025,7 +3030,7 @@ def _is_surface_mode(geometry: AdHocDiffractometer, mode) -> bool: return False # Defer to :func:`_is_free_detectors_mode` for psic-family modes # with 2 free sample stages and 2 free detector stages (issue - # #264 — the B3 mode ``fixed_alpha_i_fixed_chi_fixed_phi``). The + # #264 — the B3 mode ``fixed_incidence_fixed_chi_fixed_phi``). The # ``chi`` stage check restricts this exclusion to psic; existing # zaxis/s2d2/sixc surface modes (which also have free detectors) # stay on :func:`_solve_surface` where the legacy 1-D Newton works @@ -3056,9 +3061,9 @@ def _solve_surface( Supports ReferenceConstraint modes where the constraint is: - - ``"alpha_i"`` — incidence angle fixed at target value - - ``"beta_out"`` — exit angle fixed at target value - - ``"a_eq_b"`` — symmetric reflection: alpha_i = beta_out + - ``"incidence"`` — incidence angle fixed at target value + - ``"emergence"`` — emergence angle fixed at target value + - ``"specular"`` — specular reflection: incidence = emergence The solver builds a baseline angles dict (applying all fixed sample/detector constraints and setting the detector stage to ttheta_deg), then performs a @@ -3079,9 +3084,8 @@ def _solve_surface( from .mode import ReferenceConstraint - # Extract the reference constraint rc = next(c for c in mode.constraints if isinstance(c, ReferenceConstraint)) - target_name = rc.name # "alpha_i", "beta_out", or "a_eq_b" + target_name = rc.name # "incidence", "emergence", or "specular" target_value = rc.value # float or True # Build baseline angles dict with all fixed constraints applied @@ -3197,18 +3201,18 @@ def _surface_residual( Returns a float residual in degrees (zero = constraint satisfied). """ - from .reference import exit_angle as _beta_out - from .reference import incidence_angle as _alpha_i + from .reference import emergence_angle as _emergence_angle + from .reference import incidence_angle as _incidence_angle - if target_name == "alpha_i": - ai = _alpha_i(geometry, angles=angles) + if target_name == "incidence": + ai = _incidence_angle(geometry, angles=angles) return ai - float(target_value) - if target_name == "beta_out": - bo = _beta_out(geometry, angles=angles) + if target_name == "emergence": + bo = _emergence_angle(geometry, angles=angles) return bo - float(target_value) - # a_eq_b: alpha_i = beta_out - ai = _alpha_i(geometry, angles=angles) - bo = _beta_out(geometry, angles=angles) + # specular: incidence = emergence (symmetric reflection) + ai = _incidence_angle(geometry, angles=angles) + bo = _emergence_angle(geometry, angles=angles) return ai - bo @@ -3789,12 +3793,12 @@ def _is_free_detectors_mode(geometry: AdHocDiffractometer, mode) -> bool: Used by :func:`_solve_free_detectors` to dispatch psic-family modes that fix multiple sample stages and let the detector arm orient itself entirely from the Bragg condition (and any reference - constraint such as ``alpha_i``). Examples (issue #264): + constraint such as ``incidence``). Examples (issue #264): - ``lifting_detector_eta`` (3 sample fixed, 1 sample + 2 detector free) - revised ``lifting_detector_phi`` / ``lifting_detector_mu`` (after step C of #264 — same shape, different fixed sample stage) - - ``fixed_alpha_i_fixed_chi_fixed_phi`` (2 sample fixed + alpha_i, + - ``fixed_incidence_fixed_chi_fixed_phi`` (2 sample fixed + incidence, 2 sample + 2 detector free) The predicate is intentionally conservative: it requires the @@ -3863,7 +3867,7 @@ def _solve_free_detectors( - ``lifting_detector_eta`` (B4) - revised ``lifting_detector_phi`` / ``lifting_detector_mu`` (C3, C4) - - ``fixed_alpha_i_fixed_chi_fixed_phi`` (B3, with one alpha_i row) + - ``fixed_incidence_fixed_chi_fixed_phi`` (B3, with one incidence row) Variables: every free sample stage plus both detector stages. Equations: 3 from Bragg (``Q_phi(angles) == Q_phi_target``) plus 1 per diff --git a/src/ad_hoc_diffractometer/geometries/psic.yml b/src/ad_hoc_diffractometer/geometries/psic.yml index 60576396..865a54c7 100644 --- a/src/ad_hoc_diffractometer/geometries/psic.yml +++ b/src/ad_hoc_diffractometer/geometries/psic.yml @@ -46,14 +46,14 @@ documentation: | Mode families (24 modes total): - 9 vertical-scattering-plane modes (mu = nu = 0): bisecting, - fixed_phi, fixed_chi, fixed_alpha_i, fixed_beta_out, - alpha_eq_beta, fixed_psi, fixed_omega (#264), + fixed_phi, fixed_chi, fixed_incidence, fixed_emergence, + specular, fixed_psi, fixed_omega (#264), double_diffraction. - 9 horizontal-scattering-plane mirror modes (eta = delta = 0). - - 1 hybrid mode fixed_alpha_i_fixed_chi_fixed_phi (#264) that - fixes chi, phi, and alpha_i and lets every other angle float. + - 1 hybrid mode fixed_incidence_fixed_chi_fixed_phi (#264) that + fixes chi, phi, and incidence and lets every other angle float. - 2 zone modes (You 1999 §6, SPEC ``setmode 5``): scattering vector Q is confined to the plane defined by two reciprocal-lattice @@ -115,38 +115,38 @@ modes: - {type: detector, stage: nu, value: 0.0} computed: [eta, phi, delta] - fixed_alpha_i_vertical: + fixed_incidence_vertical: constraints: - {type: sample, stage: mu, value: 0.0} - {type: detector, stage: nu, value: 0.0} - - {type: reference, name: alpha_i, value: 0.0} + - {type: reference, name: incidence, value: 0.0} computed: [eta, chi, phi, delta] extras: n_hat: REQUIRED - alpha_i: null - beta_out: null + incidence: null + emergence: null - fixed_beta_out_vertical: + fixed_emergence_vertical: constraints: - {type: sample, stage: mu, value: 0.0} - {type: detector, stage: nu, value: 0.0} - - {type: reference, name: beta_out, value: 0.0} + - {type: reference, name: emergence, value: 0.0} computed: [eta, chi, phi, delta] extras: n_hat: REQUIRED - alpha_i: null - beta_out: null + incidence: null + emergence: null - alpha_eq_beta_vertical: + specular_vertical: constraints: - {type: sample, stage: mu, value: 0.0} - {type: detector, stage: nu, value: 0.0} - - {type: reference, name: a_eq_b, value: true} + - {type: reference, name: specular, value: true} computed: [eta, chi, phi, delta] extras: n_hat: REQUIRED - alpha_i: null - beta_out: null + incidence: null + emergence: null # Per @jwkim-anl, issue #264: drop the bisect(eta, delta) constraint # entirely. "vertical" means nu = 0 (detector pin); mu is pinned @@ -171,20 +171,20 @@ modes: psi: null # Per @jwkim-anl, issue #264: a useful psic mode that fixes chi and - # phi and the incidence angle alpha_i, leaving mu, eta, nu, delta - # to be determined from the Bragg condition + the alpha_i target. + # phi and the incidence angle, leaving mu, eta, nu, delta + # to be determined from the Bragg condition + the incidence target. # Two free sample stages and two free detector stages — solved by a # dedicated 4-D Newton. - fixed_alpha_i_fixed_chi_fixed_phi: + fixed_incidence_fixed_chi_fixed_phi: constraints: - {type: sample, stage: chi, value: 0.0} - {type: sample, stage: phi, value: 0.0} - - {type: reference, name: alpha_i, value: 0.0} + - {type: reference, name: incidence, value: 0.0} computed: [mu, eta, nu, delta] extras: n_hat: REQUIRED - alpha_i: null - beta_out: null + incidence: null + emergence: null # SPEC ``setmode d1 0 0 0`` family — issue #264. # OMEGA = angle between Q and the plane of the chi circle @@ -239,38 +239,38 @@ modes: - {type: detector, stage: delta, value: 0.0} computed: [mu, phi, nu] - fixed_alpha_i_horizontal: + fixed_incidence_horizontal: constraints: - {type: sample, stage: eta, value: 0.0} - {type: detector, stage: delta, value: 0.0} - - {type: reference, name: alpha_i, value: 0.0} + - {type: reference, name: incidence, value: 0.0} computed: [mu, chi, phi, nu] extras: n_hat: REQUIRED - alpha_i: null - beta_out: null + incidence: null + emergence: null - fixed_beta_out_horizontal: + fixed_emergence_horizontal: constraints: - {type: sample, stage: eta, value: 0.0} - {type: detector, stage: delta, value: 0.0} - - {type: reference, name: beta_out, value: 0.0} + - {type: reference, name: emergence, value: 0.0} computed: [mu, chi, phi, nu] extras: n_hat: REQUIRED - alpha_i: null - beta_out: null + incidence: null + emergence: null - alpha_eq_beta_horizontal: + specular_horizontal: constraints: - {type: sample, stage: eta, value: 0.0} - {type: detector, stage: delta, value: 0.0} - - {type: reference, name: a_eq_b, value: true} + - {type: reference, name: specular, value: true} computed: [mu, chi, phi, nu] extras: n_hat: REQUIRED - alpha_i: null - beta_out: null + incidence: null + emergence: null # Per @jwkim-anl, issue #264: drop the bisect(mu, nu) constraint # entirely. "horizontal" means delta = 0 (detector pin); eta is diff --git a/src/ad_hoc_diffractometer/geometries/s2d2.yml b/src/ad_hoc_diffractometer/geometries/s2d2.yml index c9e8377f..df5088b0 100644 --- a/src/ad_hoc_diffractometer/geometries/s2d2.yml +++ b/src/ad_hoc_diffractometer/geometries/s2d2.yml @@ -69,9 +69,9 @@ modes: reflectivity: constraints: - - {type: reference, name: a_eq_b, value: true} + - {type: reference, name: specular, value: true} computed: [mu, Z, nu, delta] extras: n_hat: REQUIRED - alpha_i: null - beta_out: null + incidence: null + emergence: null diff --git a/src/ad_hoc_diffractometer/geometries/schema.json b/src/ad_hoc_diffractometer/geometries/schema.json index d605a188..375ed256 100644 --- a/src/ad_hoc_diffractometer/geometries/schema.json +++ b/src/ad_hoc_diffractometer/geometries/schema.json @@ -238,7 +238,7 @@ "type": { "const": "reference" }, "name": { "type": "string", - "enum": ["psi", "alpha_i", "beta_out", "a_eq_b", "naz", "omega"] + "enum": ["psi", "incidence", "emergence", "specular", "naz", "omega"] }, "value": { "oneOf": [ diff --git a/src/ad_hoc_diffractometer/geometries/sixc.yml b/src/ad_hoc_diffractometer/geometries/sixc.yml index 241cd28d..c696cd91 100644 --- a/src/ad_hoc_diffractometer/geometries/sixc.yml +++ b/src/ad_hoc_diffractometer/geometries/sixc.yml @@ -83,35 +83,35 @@ modes: - {type: detector, stage: gamma, value: 0.0} computed: [omega, chi, phi, delta, gamma] - fixed_alpha_zaxis: + fixed_incidence_zaxis: constraints: - {type: sample, stage: alpha, value: 0.0} - {type: sample, stage: chi, value: 0.0} - - {type: reference, name: alpha_i, value: 0.0} + - {type: reference, name: incidence, value: 0.0} computed: [omega, delta, gamma] extras: n_hat: REQUIRED - alpha_i: null - beta_out: null + incidence: null + emergence: null - fixed_beta_zaxis: + fixed_emergence_zaxis: constraints: - {type: detector, stage: gamma, value: 0.0} - {type: sample, stage: chi, value: 0.0} - - {type: reference, name: beta_out, value: 0.0} + - {type: reference, name: emergence, value: 0.0} computed: [omega, delta, alpha] extras: n_hat: REQUIRED - alpha_i: null - beta_out: null + incidence: null + emergence: null - alpha_eq_beta_zaxis: + specular_zaxis: constraints: - {type: sample, stage: chi, value: 0.0} - {type: sample, stage: phi, value: 0.0} - - {type: reference, name: a_eq_b, value: true} + - {type: reference, name: specular, value: true} computed: [omega, delta, alpha, gamma] extras: n_hat: REQUIRED - alpha_i: null - beta_out: null + incidence: null + emergence: null diff --git a/src/ad_hoc_diffractometer/geometries/zaxis.yml b/src/ad_hoc_diffractometer/geometries/zaxis.yml index 771b2772..28b384ff 100644 --- a/src/ad_hoc_diffractometer/geometries/zaxis.yml +++ b/src/ad_hoc_diffractometer/geometries/zaxis.yml @@ -64,18 +64,18 @@ stages: modes: zaxis: constraints: - - {type: reference, name: alpha_i, value: 0.0} + - {type: reference, name: incidence, value: 0.0} computed: [Z, delta, gamma] extras: n_hat: REQUIRED - alpha_i: null - beta_out: null + incidence: null + emergence: null reflectivity: constraints: - - {type: reference, name: a_eq_b, value: true} + - {type: reference, name: specular, value: true} computed: [Z, delta, alpha, gamma] extras: n_hat: REQUIRED - alpha_i: null - beta_out: null + incidence: null + emergence: null diff --git a/src/ad_hoc_diffractometer/mode.py b/src/ad_hoc_diffractometer/mode.py index 12b45955..37de8597 100644 --- a/src/ad_hoc_diffractometer/mode.py +++ b/src/ad_hoc_diffractometer/mode.py @@ -39,8 +39,8 @@ condition between Q and an external reference vector n̂ (surface normal, polarization axis, etc.) stored on the geometry. The named options are physical pseudo-angles from You (1999) and Lohmeier & Vlieg (1993): -``"psi"``, ``"alpha_i"``, ``"beta_out"``, ``"a_eq_b"``, ``"naz"``. At -most one reference constraint is allowed. +``"psi"``, ``"incidence"``, ``"emergence"``, ``"specular"``, ``"naz"``. +At most one reference constraint is allowed. Classes ------- @@ -195,7 +195,14 @@ def __init__( # --------------------------------------------------------------------------- REFERENCE_NAMES: frozenset[str] = frozenset( - {"psi", "alpha_i", "beta_out", "a_eq_b", "naz", "omega"} + { + "psi", + "incidence", + "emergence", + "specular", + "naz", + "omega", + } ) """Valid reference constraint names (physical pseudo-angles). @@ -206,15 +213,16 @@ def __init__( geometry (no reference vector required): - ``"psi"`` — azimuthal angle of n̂ about Q (You 1999, eq. 23) -- ``"alpha_i"`` — angle of incidence (incident beam vs. surface plane) -- ``"beta_out"`` — angle of exit (diffracted beam vs. surface plane) -- ``"a_eq_b"`` — relational: alpha_i = beta_out (symmetric reflection) +- ``"incidence"`` — angle of incidence (incident beam vs. surface plane) +- ``"emergence"`` — angle of emergence (diffracted beam vs. surface plane) +- ``"specular"`` — specular reflection (relational: incidence = emergence) - ``"naz"`` — azimuthal angle of n̂ in the lab frame - ``"omega"`` — SPEC ``OMEGA`` pseudo-angle (``Q[6]``): angle between Q and the plane of the chi circle (psic family); see :func:`~ad_hoc_diffractometer.reference.omega_pseudo` """ + QAZ: str = "qaz" """Special name for the qaz detector pseudo-angle (You 1999, eq. 18). @@ -315,7 +323,7 @@ def __setitem__(self, key: str, value: Any) -> None: f"effect on forward(). Set the reference vector on the " f"geometry instead: use 'g.surface_normal = (h, k, l)' " f"for surface-mode constraints " - f"(alpha_i / beta_out / a_eq_b), or " + f"(incidence / emergence / specular), or " f"'g.azimuth = (h, k, l)' for " f"psi / naz constraints. See " f"AdHocDiffractometer.required_reference_vector to " @@ -847,14 +855,14 @@ class ReferenceConstraint: ``"psi"`` Azimuthal angle of n̂ about Q (You 1999, eq. 23). - ``"alpha_i"`` + ``"incidence"`` Angle of incidence: angle between the incident beam and the surface plane (perpendicular to n̂). - ``"beta_out"`` - Angle of exit: angle between the diffracted beam and the surface - plane. - ``"a_eq_b"`` - Relational: alpha_i = beta_out (symmetric reflection). + ``"emergence"`` + Angle of emergence: angle between the diffracted beam and the + surface plane. + ``"specular"`` + Specular reflection: relational condition ``incidence = emergence``. ``value`` must be ``True``. ``"naz"`` Azimuthal angle of n̂ in the lab frame (You 1999). @@ -862,17 +870,19 @@ class ReferenceConstraint: Parameters ---------- name : str - One of ``"psi"``, ``"alpha_i"``, ``"beta_out"``, ``"a_eq_b"``, - ``"naz"``. + One of ``"psi"``, ``"incidence"``, ``"emergence"``, ``"specular"``, + ``"naz"``, ``"omega"``. value : float or bool - Target value in degrees (or ``True`` for ``"a_eq_b"``). + Target value in degrees (or ``True`` for ``"specular"``). Examples -------- >>> ReferenceConstraint("psi", 90.0) ReferenceConstraint('psi', 90.0) - >>> ReferenceConstraint("a_eq_b", True) - ReferenceConstraint('a_eq_b', True) + >>> ReferenceConstraint("specular", True) + ReferenceConstraint('specular', True) + >>> ReferenceConstraint("incidence", 5.0) + ReferenceConstraint('incidence', 5.0) """ category: str = "reference" @@ -881,16 +891,16 @@ class ReferenceConstraint: def __init__(self, name: str, value: float | bool) -> None: if name not in REFERENCE_NAMES: raise ValueError( - f"ReferenceConstraint name must be one of {sorted(REFERENCE_NAMES)}; " - f"got {name!r}." + f"ReferenceConstraint name must be one of " + f"{sorted(REFERENCE_NAMES)}; got {name!r}." ) - if name == "a_eq_b" and value is not True: + if name == "specular" and value is not True: raise ValueError( - "ReferenceConstraint('a_eq_b', value): value must be True; " + "ReferenceConstraint('specular', value): value must be True; " f"got {value!r}." ) self._name = name - self._value: float | bool = True if name == "a_eq_b" else float(value) # type: ignore[arg-type] + self._value: float | bool = True if name == "specular" else float(value) # type: ignore[arg-type] @property def name(self) -> str: @@ -899,7 +909,7 @@ def name(self) -> str: @property def value(self) -> float | bool: - """Target value in degrees, or ``True`` for ``"a_eq_b"``.""" + """Target value in degrees, or ``True`` for ``"specular"``.""" return self._value def evaluate( @@ -938,7 +948,7 @@ def has_reference_vector(self, geometry: AdHocDiffractometer) -> bool: For ``"psi"`` and ``"naz"``: requires :attr:`~geometry.AdHocDiffractometer.azimuth` to be set. - For ``"alpha_i"``, ``"beta_out"``, and ``"a_eq_b"``: requires + For ``"incidence"``, ``"emergence"``, and ``"specular"``: requires :attr:`~geometry.AdHocDiffractometer.surface_normal` to be set. For ``"omega"``: no reference vector is required (always returns @@ -963,9 +973,9 @@ def is_implemented(self, geometry: AdHocDiffractometer) -> bool: Implemented constraints: - - ``"alpha_i"`` — requires :attr:`~geometry.AdHocDiffractometer.surface_normal` - - ``"beta_out"`` — requires :attr:`~geometry.AdHocDiffractometer.surface_normal` - - ``"a_eq_b"`` — requires :attr:`~geometry.AdHocDiffractometer.surface_normal` + - ``"incidence"`` — requires :attr:`~geometry.AdHocDiffractometer.surface_normal` + - ``"emergence"`` — requires :attr:`~geometry.AdHocDiffractometer.surface_normal` + - ``"specular"`` — requires :attr:`~geometry.AdHocDiffractometer.surface_normal` - ``"psi"`` — requires :attr:`~geometry.AdHocDiffractometer.azimuth`. The forward solver treats ψ as a **validation filter**: for a given (h,k,l) and UB, ψ is a pure phi-frame quantity that is the same for @@ -989,7 +999,7 @@ def is_implemented(self, geometry: AdHocDiffractometer) -> bool: if self._name == "omega": # Implemented for any geometry with a chi sample stage. return any(s.name == "chi" for s in geometry.sample_stages) - # alpha_i, beta_out, a_eq_b — implemented when surface_normal is set + # incidence, emergence, specular — implemented when surface_normal is set return geometry.surface_normal is not None def to_dict(self) -> dict: @@ -1357,7 +1367,7 @@ def with_constraint_values(self, **updates: float | bool) -> ConstraintSet: exactly match the ``.name`` attribute of an existing :class:`SampleConstraint`, :class:`DetectorConstraint`, or :class:`ReferenceConstraint` in the set. Values are floats - (or ``bool`` for the ``"a_eq_b"`` :class:`ReferenceConstraint`). + (or ``bool`` for the ``"specular"`` :class:`ReferenceConstraint`). Returns ------- @@ -1387,11 +1397,11 @@ def with_constraint_values(self, **updates: float | bool) -> ConstraintSet: ... g.modes["fixed_chi_vertical"].with_constraint_values(chi=45.0) ... ) - Multiple values at once (psic ``fixed_alpha_i_fixed_chi_fixed_phi``): + Multiple values at once (psic ``fixed_incidence_fixed_chi_fixed_phi``): - >>> g.modes["fixed_alpha_i_fixed_chi_fixed_phi"] = ( - ... g.modes["fixed_alpha_i_fixed_chi_fixed_phi"] - ... .with_constraint_values(chi=15.0, phi=30.0, alpha_i=5.0) + >>> g.modes["fixed_incidence_fixed_chi_fixed_phi"] = ( + ... g.modes["fixed_incidence_fixed_chi_fixed_phi"] + ... .with_constraint_values(chi=15.0, phi=30.0, incidence=5.0) ... ) Notes diff --git a/src/ad_hoc_diffractometer/reference.py b/src/ad_hoc_diffractometer/reference.py index c9c91c9d..77c611c9 100644 --- a/src/ad_hoc_diffractometer/reference.py +++ b/src/ad_hoc_diffractometer/reference.py @@ -5,8 +5,8 @@ Provides standalone functions for computing the physical pseudo-angles that appear in :class:`~mode.ReferenceConstraint` conditions: incidence angle, -exit angle, azimuthal angle ψ, lab-frame azimuthal angle naz, and the -SPEC ``OMEGA`` pseudo-angle (angle between Q and the chi-circle plane). +emergence angle, azimuthal angle ψ, lab-frame azimuthal angle naz, and +the SPEC ``OMEGA`` pseudo-angle (angle between Q and the chi-circle plane). These functions require the geometry's :attr:`surface_normal` or :attr:`azimuth` to be set before calling, **except** for @@ -18,8 +18,8 @@ :func:`incidence_angle` Angle of incidence α_i between the incident beam and the sample surface. -:func:`exit_angle` - Angle of exit β_out between the diffracted beam and the sample surface. +:func:`emergence_angle` + Angle of emergence α_f between the diffracted beam and the sample surface. :func:`psi_angle` Azimuthal angle ψ of the reference vector n̂ about Q (You 1999, eq. 23). @@ -126,18 +126,18 @@ def incidence_angle( * Lohmeier & Vlieg (1993), §4.2. """ _require_surface_normal(geometry) - return geometry.alpha_i(angles=angles) + return geometry.incidence(angles=angles) -def exit_angle( +def emergence_angle( geometry: AdHocDiffractometer, angles: dict[str, float] | None = None, ) -> float: """ - Compute the angle of exit β_out in degrees. + Compute the angle of emergence α_f in degrees. The angle between the diffracted beam and the sample surface. - Positive when the diffracted beam exits through the front face. + Positive when the diffracted beam emerges through the front face. Requires :attr:`~geometry.AdHocDiffractometer.surface_normal` to be set. @@ -152,7 +152,7 @@ def exit_angle( Returns ------- float - Exit angle β_out in degrees. + Emergence angle α_f in degrees. Raises ------ @@ -161,11 +161,11 @@ def exit_angle( References ---------- + * Lohmeier & Vlieg (1993), §4.2, eq. 16. * You (1999), eq. 11. - * Lohmeier & Vlieg (1993), §4.2. """ _require_surface_normal(geometry) - return geometry.alpha_f(angles=angles) + return geometry.emergence(angles=angles) def psi_angle( @@ -484,8 +484,8 @@ def omega_pseudo( Notes ----- - Unlike :func:`incidence_angle`, :func:`exit_angle`, :func:`psi_angle`, - and :func:`naz_angle`, this function does **not** require any + Unlike :func:`incidence_angle`, :func:`emergence_angle`, + :func:`psi_angle`, and :func:`naz_angle`, this function does **not** require any reference vector (``surface_normal`` / ``azimuth``) to be set on the geometry. OMEGA is a pure motor-frame quantity defined by the diffractometer's internal geometry. diff --git a/src/ad_hoc_diffractometer/surface.py b/src/ad_hoc_diffractometer/surface.py index fceb1d40..1822740f 100644 --- a/src/ad_hoc_diffractometer/surface.py +++ b/src/ad_hoc_diffractometer/surface.py @@ -12,11 +12,11 @@ Public API ---------- -alpha_i(geometry, angles=None) +incidence(geometry, angles=None) Angle of incidence αᵢ in degrees — angle between the incoming beam and the sample surface. -alpha_f(geometry, angles=None) +emergence(geometry, angles=None) Angle of emergence αf in degrees — angle between the diffracted beam and the sample surface. @@ -203,7 +203,7 @@ def _surface_vectors( # --------------------------------------------------------------------------- -def alpha_i( +def incidence( geometry: AdHocDiffractometer, angles: dict[str, float] | None = None, ) -> float: @@ -240,7 +240,7 @@ def alpha_i( >>> g.wavelength = 1.5406 >>> g.surface_normal = (0, 0, 1) >>> ahd.ub_identity(g.sample) - >>> ai = g.alpha_i({"alpha": 5.0, "Z": 0.0, "delta": 20.0, "gamma": 0.0}) + >>> ai = g.incidence({"alpha": 5.0, "Z": 0.0, "delta": 20.0, "gamma": 0.0}) >>> round(ai, 4) 5.0 @@ -255,7 +255,7 @@ def alpha_i( return math.degrees(math.asin(abs(sin_ai))) -def alpha_f( +def emergence( geometry: AdHocDiffractometer, angles: dict[str, float] | None = None, ) -> float: @@ -387,8 +387,8 @@ def is_specular( ValueError If any required attribute is not set. """ - ai = alpha_i(geometry, angles) - af = alpha_f(geometry, angles) + ai = incidence(geometry, angles) + af = emergence(geometry, angles) return abs(ai - af) <= atol @@ -436,5 +436,5 @@ def is_evanescent( "and is not stored on the geometry. " "Typical values: 0.1°–0.5° for hard X-rays." ) - ai = alpha_i(geometry, angles) + ai = incidence(geometry, angles) return ai < critical_angle_deg diff --git a/tests/test_benchmark.py b/tests/test_benchmark.py index fd4a2279..2020a53d 100644 --- a/tests/test_benchmark.py +++ b/tests/test_benchmark.py @@ -138,9 +138,9 @@ def test_zone_extras_replaced(self, geometry_name, mode_name): @pytest.mark.parametrize( "geometry_name, mode_name", [ - pytest.param("sixc", "fixed_alpha_zaxis", id="sixc-alpha-zaxis"), - pytest.param("sixc", "fixed_beta_zaxis", id="sixc-beta-zaxis"), - pytest.param("sixc", "alpha_eq_beta_zaxis", id="sixc-a-eq-b"), + pytest.param("sixc", "fixed_incidence_zaxis", id="sixc-alpha-zaxis"), + pytest.param("sixc", "fixed_emergence_zaxis", id="sixc-beta-zaxis"), + pytest.param("sixc", "specular_zaxis", id="sixc-a-eq-b"), ], ) def test_surface_modes_set_normal(self, geometry_name, mode_name): @@ -154,7 +154,7 @@ def test_surface_mode_preserves_existing_normal(self): """If surface_normal is already set, _prepare_mode leaves it.""" g = _setup_geometry("sixc") g.surface_normal = (1, 0, 0) - _prepare_mode(g, "fixed_alpha_zaxis") + _prepare_mode(g, "fixed_incidence_zaxis") assert g.surface_normal == (1.0, 0.0, 0.0) diff --git a/tests/test_diffractometer.py b/tests/test_diffractometer.py index 365473b8..4ba7759b 100644 --- a/tests/test_diffractometer.py +++ b/tests/test_diffractometer.py @@ -829,129 +829,6 @@ def test_azimuth_wrong_length_raises(): g.azimuth = (0, 1) -# --------------------------------------------------------------------------- -# azimuthal_reference deprecated alias (#298) -# --------------------------------------------------------------------------- -# -# Issue #298 renamed `azimuthal_reference` to `azimuth`. The old name is -# kept as a forwarding alias that emits DeprecationWarning on both read -# and write. The constructor keyword is kept under the same policy. -# `to_dict()` writes the new key `"azimuth"`; `from_dict()` accepts -# either the new or the legacy key for backward compatibility. - - -def test_azimuthal_reference_alias_set_warns(): - """Writing the deprecated name emits DeprecationWarning.""" - from helpers import fourcv - - g = fourcv() - with pytest.warns(DeprecationWarning, match=re.escape("azimuthal_reference")): - g.azimuthal_reference = (0, 0, 1) - - -def test_azimuthal_reference_alias_get_warns(): - """Reading the deprecated name emits DeprecationWarning.""" - from helpers import fourcv - - g = fourcv() - g.azimuth = (0, 0, 1) - with pytest.warns(DeprecationWarning, match=re.escape("azimuthal_reference")): - _ = g.azimuthal_reference - - -def test_azimuthal_reference_alias_shares_storage(): - """The deprecated alias and the canonical name share underlying storage.""" - import warnings - - from helpers import fourcv - - g = fourcv() - g.azimuth = (0, 0, 1) - with warnings.catch_warnings(): - warnings.simplefilter("ignore", DeprecationWarning) - assert g.azimuthal_reference == (0.0, 0.0, 1.0) - g.azimuthal_reference = (1, 0, 0) - assert g.azimuth == (1.0, 0.0, 0.0) - - -def test_azimuthal_reference_constructor_kwarg_warns(): - """The deprecated constructor kwarg emits DeprecationWarning.""" - from helpers import fourcv - - from ad_hoc_diffractometer.diffractometer import AdHocDiffractometer - - template = fourcv() - with pytest.warns(DeprecationWarning, match=re.escape("azimuthal_reference")): - g = AdHocDiffractometer( - name="fourcv", - stages=list(template._stages.values()), - basis=template.basis, - azimuthal_reference=(0, 0, 1), - ) - assert g.azimuth == (0.0, 0.0, 1.0) - - -def test_azimuthal_reference_and_azimuth_conflict_raises(): - """Supplying both kwargs with disagreeing values raises ValueError.""" - from helpers import fourcv - - from ad_hoc_diffractometer.diffractometer import AdHocDiffractometer - - template = fourcv() - with pytest.raises(ValueError, match=re.escape("Cannot specify both")): - AdHocDiffractometer( - name="fourcv", - stages=list(template._stages.values()), - basis=template.basis, - azimuth=(0, 0, 1), - azimuthal_reference=(1, 0, 0), - ) - - -def test_azimuth_and_azimuthal_reference_matching_values_accepted(): - """Supplying both kwargs with matching values warns but does not raise.""" - from helpers import fourcv - - from ad_hoc_diffractometer.diffractometer import AdHocDiffractometer - - template = fourcv() - with pytest.warns(DeprecationWarning, match=re.escape("azimuthal_reference")): - g = AdHocDiffractometer( - name="fourcv", - stages=list(template._stages.values()), - basis=template.basis, - azimuth=(0, 0, 1), - azimuthal_reference=(0, 0, 1), - ) - assert g.azimuth == (0.0, 0.0, 1.0) - - -def test_to_dict_writes_azimuth_key_not_legacy(): - """to_dict() emits the new key "azimuth" and not the legacy key.""" - from helpers import fourcv - - g = fourcv() - g.azimuth = (0, 0, 1) - d = g.to_dict() - assert d["azimuth"] == [0.0, 0.0, 1.0] - assert "azimuthal_reference" not in d - - -def test_from_dict_accepts_legacy_azimuthal_reference_key(): - """from_dict() reads the legacy "azimuthal_reference" key when present.""" - from helpers import fourcv - - from ad_hoc_diffractometer.diffractometer import AdHocDiffractometer - - g = fourcv() - g.azimuth = (0, 0, 1) - d = g.to_dict() - # Simulate a session saved by ad_hoc_diffractometer < v0.12.0 - d["azimuthal_reference"] = d.pop("azimuth") - g2 = AdHocDiffractometer.from_dict(d) - assert g2.azimuth == (0.0, 0.0, 1.0) - - # --------------------------------------------------------------------------- # psi() method (#11) # --------------------------------------------------------------------------- @@ -2326,7 +2203,7 @@ def test_geometry_to_dict_version_unknown_on_metadata_error(): [ pytest.param( "psic", - "fixed_alpha_i_fixed_chi_fixed_phi", + "fixed_incidence_fixed_chi_fixed_phi", "surface_normal", id="psic-B3-surface_normal", ), @@ -2397,7 +2274,7 @@ def test_required_reference_vector_usable_with_setattr(): import ad_hoc_diffractometer as ahd g = ahd.make_geometry("psic") - g.mode_name = "fixed_alpha_i_fixed_chi_fixed_phi" + g.mode_name = "fixed_incidence_fixed_chi_fixed_phi" attr = g.required_reference_vector assert attr == "surface_normal" setattr(g, attr, (0, 0, 1)) diff --git a/tests/test_forward.py b/tests/test_forward.py index 04dd2ad0..4e2d9613 100644 --- a/tests/test_forward.py +++ b/tests/test_forward.py @@ -1386,9 +1386,9 @@ def test_sixc_round_trip(mode_name, h, k, l): # noqa: E741 @pytest.mark.parametrize( "mode_name", [ - pytest.param("fixed_alpha_zaxis", id="fixed_alpha_zaxis"), - pytest.param("fixed_beta_zaxis", id="fixed_beta_zaxis"), - pytest.param("alpha_eq_beta_zaxis", id="alpha_eq_beta_zaxis"), + pytest.param("fixed_incidence_zaxis", id="fixed_incidence_zaxis"), + pytest.param("fixed_emergence_zaxis", id="fixed_emergence_zaxis"), + pytest.param("specular_zaxis", id="specular_zaxis"), ], ) def test_sixc_zaxis_stub_not_implemented(mode_name): @@ -1450,13 +1450,13 @@ def test_sixc_four_circle_omega_equals_delta_half(): "lifting_detector_eta", "fixed_psi_vertical", "fixed_psi_horizontal", - "fixed_alpha_i_vertical", - "fixed_beta_out_vertical", - "alpha_eq_beta_vertical", - "fixed_alpha_i_fixed_chi_fixed_phi", - "fixed_alpha_i_horizontal", - "fixed_beta_out_horizontal", - "alpha_eq_beta_horizontal", + "fixed_incidence_vertical", + "fixed_emergence_vertical", + "specular_vertical", + "fixed_incidence_fixed_chi_fixed_phi", + "fixed_incidence_horizontal", + "fixed_emergence_horizontal", + "specular_horizontal", "fixed_omega_vertical", "fixed_omega_horizontal", "zone_vertical", @@ -3172,7 +3172,7 @@ def test_psic_fixed_alpha_i_fixed_chi_fixed_phi_round_trip( alpha_target, context, ): - """Issue #264 B3: 4-D Newton with chi=phi=0 + alpha_i target.""" + """Issue #264 B3: 4-D Newton with chi=phi=0 + incidence target.""" from ad_hoc_diffractometer import REQUIRED from ad_hoc_diffractometer import ConstraintSet from ad_hoc_diffractometer import ReferenceConstraint @@ -3186,20 +3186,20 @@ def test_psic_fixed_alpha_i_fixed_chi_fixed_phi_round_trip( [ SampleConstraint("chi", 0.0), SampleConstraint("phi", 0.0), - ReferenceConstraint("alpha_i", alpha_target), + ReferenceConstraint("incidence", alpha_target), ], computed=["mu", "eta", "nu", "delta"], - extras={"n_hat": REQUIRED, "alpha_i": None, "beta_out": None}, + extras={"n_hat": REQUIRED, "incidence": None, "emergence": None}, ) g.mode_name = "__test_b3" sols = g.forward(h, k, l) - assert len(sols) > 0, f"B3 ({h},{k},{l}) alpha_i={alpha_target}: no solutions" + assert len(sols) > 0, f"B3 ({h},{k},{l}) incidence={alpha_target}: no solutions" for sol in sols: assert sol["chi"] == pytest.approx(0.0, abs=1e-6) assert sol["phi"] == pytest.approx(0.0, abs=1e-6) ai = incidence_angle(g, angles=sol) assert ai == pytest.approx(alpha_target, abs=1e-3), ( - f"B3 ({h},{k},{l}) alpha_i target {alpha_target}: got {ai}" + f"B3 ({h},{k},{l}) incidence target {alpha_target}: got {ai}" ) hkl_back = g.inverse(sol) assert np.allclose(hkl_back, [h, k, l], atol=1e-5) @@ -3208,7 +3208,7 @@ def test_psic_fixed_alpha_i_fixed_chi_fixed_phi_round_trip( def test_psic_fixed_alpha_i_fixed_chi_fixed_phi_requires_surface_normal(): """B3 mode is_implemented=False until surface_normal is set.""" g = _setup_cubic(psic, a=4.0) - cs = g.modes["fixed_alpha_i_fixed_chi_fixed_phi"] + cs = g.modes["fixed_incidence_fixed_chi_fixed_phi"] assert cs.is_implemented(g) is False g.surface_normal = (0, 0, 1) assert cs.is_implemented(g) is True diff --git a/tests/test_mode.py b/tests/test_mode.py index 4734bd8a..88c549a8 100644 --- a/tests/test_mode.py +++ b/tests/test_mode.py @@ -737,15 +737,15 @@ def test_detector_constraint_evaluate_qaz(): "name, value, context", [ pytest.param("psi", 90.0, does_not_raise(), id="psi"), - pytest.param("alpha_i", 5.0, does_not_raise(), id="alpha_i"), - pytest.param("beta_out", 5.0, does_not_raise(), id="beta_out"), - pytest.param("a_eq_b", True, does_not_raise(), id="a_eq_b-true"), + pytest.param("incidence", 5.0, does_not_raise(), id="incidence"), + pytest.param("emergence", 5.0, does_not_raise(), id="emergence"), + pytest.param("specular", True, does_not_raise(), id="specular-true"), pytest.param("naz", 0.0, does_not_raise(), id="naz"), pytest.param( - "a_eq_b", + "specular", False, pytest.raises(ValueError, match=re.escape("must be True")), - id="a_eq_b-false-raises", + id="specular-false-raises", ), pytest.param( "unknown", @@ -769,7 +769,7 @@ def test_reference_constraint_value_coerced_to_float(): def test_reference_constraint_a_eq_b_value_is_true(): - rc = ReferenceConstraint("a_eq_b", True) + rc = ReferenceConstraint("specular", True) assert rc.value is True @@ -789,7 +789,7 @@ def test_reference_constraint_to_dict_from_dict(): def test_reference_constraint_to_dict_from_dict_a_eq_b(): - rc = ReferenceConstraint("a_eq_b", True) + rc = ReferenceConstraint("specular", True) d = rc.to_dict() assert d["value"] is True rc2 = ReferenceConstraint.from_dict(d) @@ -799,12 +799,12 @@ def test_reference_constraint_to_dict_from_dict_a_eq_b(): @pytest.mark.parametrize( "name, value, surface_normal, expected", [ - # alpha_i/beta_out/a_eq_b: implemented when surface_normal is set - pytest.param("alpha_i", 0.0, (0, 0, 1), True, id="alpha_i-with-sn"), - pytest.param("alpha_i", 0.0, None, False, id="alpha_i-no-sn"), - pytest.param("beta_out", 0.0, (0, 0, 1), True, id="beta_out-with-sn"), - pytest.param("a_eq_b", True, (0, 0, 1), True, id="a_eq_b-with-sn"), - pytest.param("a_eq_b", True, None, False, id="a_eq_b-no-sn"), + # incidence/emergence/specular: implemented when surface_normal is set + pytest.param("incidence", 0.0, (0, 0, 1), True, id="incidence-with-sn"), + pytest.param("incidence", 0.0, None, False, id="incidence-no-sn"), + pytest.param("emergence", 0.0, (0, 0, 1), True, id="emergence-with-sn"), + pytest.param("specular", True, (0, 0, 1), True, id="specular-with-sn"), + pytest.param("specular", True, None, False, id="specular-no-sn"), # psi/naz: never implemented (no forward solver yet) pytest.param("psi", 0.0, (0, 0, 1), False, id="psi-not-implemented"), pytest.param("naz", 0.0, (0, 0, 1), False, id="naz-not-implemented"), @@ -869,7 +869,7 @@ def test_constraint_set_two_reference_raises(): ConstraintSet( [ ReferenceConstraint("psi", 0.0), - ReferenceConstraint("alpha_i", 5.0), + ReferenceConstraint("incidence", 5.0), ] ) @@ -932,8 +932,8 @@ def test_reference_constraint_hash(): rc1 = ReferenceConstraint("psi", 90.0) rc2 = ReferenceConstraint("psi", 90.0) assert hash(rc1) == hash(rc2) - # a_eq_b with bool value also hashable - rc3 = ReferenceConstraint("a_eq_b", True) + # specular with bool value also hashable + rc3 = ReferenceConstraint("specular", True) assert isinstance(hash(rc3), int) @@ -1775,13 +1775,13 @@ def test_fivec_modes_round_trip_serialisation(): "bisecting_4c", "fixed_gamma_5c", "fixed_alpha_5c", - "fixed_alpha_zaxis", - "fixed_beta_zaxis", - "alpha_eq_beta_zaxis", + "fixed_incidence_zaxis", + "fixed_emergence_zaxis", + "specular_zaxis", } _SIXC_IMPLEMENTED = {"bisecting_4c", "fixed_gamma_5c", "fixed_alpha_5c"} -_SIXC_STUBS = {"fixed_alpha_zaxis", "fixed_beta_zaxis", "alpha_eq_beta_zaxis"} +_SIXC_STUBS = {"fixed_incidence_zaxis", "fixed_emergence_zaxis", "specular_zaxis"} def test_sixc_factory_mode_names(): @@ -1839,9 +1839,13 @@ def test_sixc_surface_mode_implemented_with_surface_normal(mode_name): pytest.param("bisecting_4c", True, id="bisecting_4c-has-bisect"), pytest.param("fixed_gamma_5c", True, id="fixed_gamma_5c-has-bisect"), pytest.param("fixed_alpha_5c", True, id="fixed_alpha_5c-has-bisect"), - pytest.param("fixed_alpha_zaxis", False, id="fixed_alpha_zaxis-no-bisect"), - pytest.param("fixed_beta_zaxis", False, id="fixed_beta_zaxis-no-bisect"), - pytest.param("alpha_eq_beta_zaxis", False, id="alpha_eq_beta_zaxis-no-bisect"), + pytest.param( + "fixed_incidence_zaxis", False, id="fixed_incidence_zaxis-no-bisect" + ), + pytest.param( + "fixed_emergence_zaxis", False, id="fixed_emergence_zaxis-no-bisect" + ), + pytest.param("specular_zaxis", False, id="specular_zaxis-no-bisect"), ], ) def test_sixc_mode_has_bisect(mode_name, expected_has_bisect): @@ -1853,20 +1857,30 @@ def test_sixc_mode_has_bisect(mode_name, expected_has_bisect): "mode_name, extras_key, expected_value", [ pytest.param( - "fixed_alpha_zaxis", "n_hat", "REQUIRED", id="fixed_alpha_zaxis-n_hat" + "fixed_incidence_zaxis", + "n_hat", + "REQUIRED", + id="fixed_incidence_zaxis-n_hat", ), pytest.param( - "fixed_alpha_zaxis", "alpha_i", None, id="fixed_alpha_zaxis-alpha_i-output" + "fixed_incidence_zaxis", + "incidence", + None, + id="fixed_incidence_zaxis-incidence-output", ), pytest.param( - "fixed_beta_zaxis", "n_hat", "REQUIRED", id="fixed_beta_zaxis-n_hat" + "fixed_emergence_zaxis", + "n_hat", + "REQUIRED", + id="fixed_emergence_zaxis-n_hat", ), pytest.param( - "fixed_beta_zaxis", "beta_out", None, id="fixed_beta_zaxis-beta_out-output" - ), - pytest.param( - "alpha_eq_beta_zaxis", "n_hat", "REQUIRED", id="alpha_eq_beta_zaxis-n_hat" + "fixed_emergence_zaxis", + "emergence", + None, + id="fixed_emergence_zaxis-emergence-output", ), + pytest.param("specular_zaxis", "n_hat", "REQUIRED", id="specular_zaxis-n_hat"), ], ) def test_sixc_zaxis_extras_declared(mode_name, extras_key, expected_value): @@ -1979,11 +1993,13 @@ def test_s2d2_fixed_mu_is_implemented(): "factory, mode_name, extras_key, expected_value", [ pytest.param(zaxis, "zaxis", "n_hat", "REQUIRED", id="zaxis-n_hat"), - pytest.param(zaxis, "zaxis", "alpha_i", None, id="zaxis-alpha_i-output"), - pytest.param(zaxis, "zaxis", "beta_out", None, id="zaxis-beta_out-output"), + pytest.param(zaxis, "zaxis", "incidence", None, id="zaxis-incidence-output"), + pytest.param(zaxis, "zaxis", "emergence", None, id="zaxis-emergence-output"), pytest.param(zaxis, "reflectivity", "n_hat", "REQUIRED", id="zaxis-refl-n_hat"), pytest.param(s2d2, "reflectivity", "n_hat", "REQUIRED", id="s2d2-refl-n_hat"), - pytest.param(s2d2, "reflectivity", "alpha_i", None, id="s2d2-alpha_i-output"), + pytest.param( + s2d2, "reflectivity", "incidence", None, id="s2d2-incidence-output" + ), ], ) def test_zaxis_s2d2_extras_declared(factory, mode_name, extras_key, expected_value): @@ -2443,20 +2459,26 @@ def test_qaz_residual_nonzero(): def _b3_constraint_set(): - """Build a ConstraintSet matching psic 'fixed_alpha_i_fixed_chi_fixed_phi'. + """Build a ConstraintSet matching psic 'fixed_incidence_fixed_chi_fixed_phi'. Three settable-value constraints: two SampleConstraint plus one ReferenceConstraint. Used by several with_constraint_values tests that exercise the multi-value path. + + The reference constraint is built with the canonical name + ``"incidence"`` (issue #299). The YAML mode name retains its + historical ``fixed_alpha_i_*`` spelling because mode names are a + separate identifier from the constraint-name vocabulary; that + rename, if any, is out of scope for this issue. """ return ConstraintSet( [ SampleConstraint("chi", 0.0), SampleConstraint("phi", 0.0), - ReferenceConstraint("alpha_i", 0.0), + ReferenceConstraint("incidence", 0.0), ], computed=["mu", "eta", "nu", "delta"], - extras={"n_hat": REQUIRED, "alpha_i": None, "beta_out": None}, + extras={"n_hat": REQUIRED, "incidence": None, "emergence": None}, cut_points={"eta": -180.0}, ) @@ -2494,37 +2516,37 @@ def _b3_constraint_set(): ), pytest.param( _b3_constraint_set, - {"alpha_i": 5.0}, - {"chi": 0.0, "phi": 0.0, "alpha_i": 5.0}, + {"incidence": 5.0}, + {"chi": 0.0, "phi": 0.0, "incidence": 5.0}, does_not_raise(), - id="reference-alpha_i-5", + id="reference-incidence-5", ), pytest.param( - # ReferenceConstraint('a_eq_b', ...) only accepts True + # ReferenceConstraint('specular', ...) only accepts True # (the constraint expresses the *boolean* condition - # alpha_i = beta_out; there is no False variant). This + # incidence = emergence; there is no False variant). This # case verifies the bool branch of with_constraint_values # by re-applying the only legal value. lambda: ConstraintSet( - [ReferenceConstraint("a_eq_b", True)], + [ReferenceConstraint("specular", True)], extras={"n_hat": REQUIRED}, ), - {"a_eq_b": True}, - {"a_eq_b": True}, + {"specular": True}, + {"specular": True}, does_not_raise(), - id="reference-a_eq_b-True", + id="reference-specular-True", ), pytest.param( _b3_constraint_set, - {"chi": 15.0, "phi": 30.0, "alpha_i": 5.0}, - {"chi": 15.0, "phi": 30.0, "alpha_i": 5.0}, + {"chi": 15.0, "phi": 30.0, "incidence": 5.0}, + {"chi": 15.0, "phi": 30.0, "incidence": 5.0}, does_not_raise(), id="b3-three-values", ), pytest.param( _b3_constraint_set, {}, - {"chi": 0.0, "phi": 0.0, "alpha_i": 0.0}, + {"chi": 0.0, "phi": 0.0, "incidence": 0.0}, does_not_raise(), id="empty-updates-no-change", ), @@ -2568,8 +2590,8 @@ def test_with_constraint_values_preserves_computed_extras_cut_points(): assert new.extras is not cs.extras assert new.extras == cs.extras assert new.extras["n_hat"] is REQUIRED - assert new.extras["alpha_i"] is None - assert new.extras["beta_out"] is None + assert new.extras["incidence"] is None + assert new.extras["emergence"] is None # OPTIONAL sentinel identity survives too. cs2 = ConstraintSet( [SampleConstraint("chi", 0.0)], @@ -2598,7 +2620,7 @@ def test_with_constraint_values_unknown_key_raises_KeyError(): match=re.escape( "with_constraint_values: no constraint(s) named " "['cho'] in this ConstraintSet; available names " - "are ['alpha_i', 'chi', 'phi']." + "are ['chi', 'incidence', 'phi']." ), ): cs.with_constraint_values(cho=45.0) @@ -2688,8 +2710,8 @@ def test_with_constraint_values_round_trips_via_to_dict(): """Replace then restore via to_dict comparison: identical dicts.""" cs = _b3_constraint_set() original_dict = cs.to_dict() - intermediate = cs.with_constraint_values(chi=15.0, phi=30.0, alpha_i=5.0) - restored = intermediate.with_constraint_values(chi=0.0, phi=0.0, alpha_i=0.0) + intermediate = cs.with_constraint_values(chi=15.0, phi=30.0, incidence=5.0) + restored = intermediate.with_constraint_values(chi=0.0, phi=0.0, incidence=0.0) assert restored.to_dict() == original_dict @@ -2762,13 +2784,13 @@ def test_extras_non_placeholder_keys_never_warn(): cs = ConstraintSet( [SampleConstraint("chi", 0.0)], - extras={"psi": None, "alpha_i": None, "beta_out": None, "omega": None}, + extras={"psi": None, "incidence": None, "emergence": None, "omega": None}, ) with warnings.catch_warnings(record=True) as captured: warnings.simplefilter("always") cs.extras["psi"] = 12.5 - cs.extras["alpha_i"] = 5.0 - cs.extras["beta_out"] = 5.0 + cs.extras["incidence"] = 5.0 + cs.extras["emergence"] = 5.0 cs.extras["omega"] = 0.0 cs.extras["new_key"] = "anything" placeholder_warnings = [ diff --git a/tests/test_reference.py b/tests/test_reference.py index 0606175e..10342f41 100644 --- a/tests/test_reference.py +++ b/tests/test_reference.py @@ -5,7 +5,7 @@ Covers: - incidence_angle: requires surface_normal; raises when None - - exit_angle: requires surface_normal; raises when None + - emergence_angle: requires surface_normal; raises when None - psi_angle: requires azimuth; raises when None - naz_angle: requires surface_normal; raises when None; vertical n̂ gives 0 - ReferenceConstraint.is_implemented(): True when reference set, False when None @@ -26,7 +26,7 @@ from ad_hoc_diffractometer import AdHocDiffractometer from ad_hoc_diffractometer import ReferenceConstraint from ad_hoc_diffractometer import ub_identity -from ad_hoc_diffractometer.reference import exit_angle +from ad_hoc_diffractometer.reference import emergence_angle from ad_hoc_diffractometer.reference import incidence_angle from ad_hoc_diffractometer.reference import natural_psi from ad_hoc_diffractometer.reference import naz_angle @@ -79,31 +79,31 @@ def test_incidence_angle_uses_current_angles_when_none(): # --------------------------------------------------------------------------- -# exit_angle +# emergence_angle # --------------------------------------------------------------------------- -def test_exit_angle_raises_without_surface_normal(): - """exit_angle raises ValueError when surface_normal is None.""" +def test_emergence_angle_raises_without_surface_normal(): + """emergence_angle raises ValueError when surface_normal is None.""" g = _setup_psic() with pytest.raises(ValueError, match=re.escape("surface_normal must be set")): - exit_angle(g) + emergence_angle(g) -def test_exit_angle_with_surface_normal(): - """exit_angle returns a float in [-90, 90] when surface_normal is set.""" +def test_emergence_angle_with_surface_normal(): + """emergence_angle returns a float in [-90, 90] when surface_normal is set.""" g = _setup_psic() g.surface_normal = (0, 0, 1) g.mode_name = "bisecting_vertical" sols = g.forward(1, 0, 0) for s in sols: - af = exit_angle(g, angles=s) + af = emergence_angle(g, angles=s) assert isinstance(af, float) assert -90.0 <= af <= 90.0 def test_specular_condition_alpha_i_equals_alpha_f(): - """At bisecting with surface normal ⊥ to scattering plane, alpha_i ≈ alpha_f.""" + """At bisecting with surface normal ⊥ to scattering plane, incidence ≈ alpha_f.""" g = _setup_psic() # Surface normal along transverse axis — perpendicular to the scattering plane g.surface_normal = (0, 0, 1) @@ -111,7 +111,7 @@ def test_specular_condition_alpha_i_equals_alpha_f(): sols = g.forward(1, 0, 0) for s in sols: ai = incidence_angle(g, angles=s) - af = exit_angle(g, angles=s) + af = emergence_angle(g, angles=s) # At bisecting in vertical plane with transverse surface normal, ai ≈ af assert ai == pytest.approx(af, abs=1e-6) @@ -228,19 +228,23 @@ def test_naz_angle_vertical_normal_returns_zero(): [ # surface-normal constraints: implemented when surface_normal is set pytest.param( - "alpha_i", 0.0, "surface_normal", (0, 0, 1), True, id="alpha_i-with-sn" + "incidence", 0.0, "surface_normal", (0, 0, 1), True, id="incidence-with-sn" ), - pytest.param("alpha_i", 0.0, "surface_normal", None, False, id="alpha_i-no-sn"), pytest.param( - "beta_out", 0.0, "surface_normal", (0, 0, 1), True, id="beta_out-with-sn" + "incidence", 0.0, "surface_normal", None, False, id="incidence-no-sn" ), pytest.param( - "beta_out", 0.0, "surface_normal", None, False, id="beta_out-no-sn" + "emergence", 0.0, "surface_normal", (0, 0, 1), True, id="emergence-with-sn" ), pytest.param( - "a_eq_b", True, "surface_normal", (0, 0, 1), True, id="a_eq_b-with-sn" + "emergence", 0.0, "surface_normal", None, False, id="emergence-no-sn" + ), + pytest.param( + "specular", True, "surface_normal", (0, 0, 1), True, id="specular-with-sn" + ), + pytest.param( + "specular", True, "surface_normal", None, False, id="specular-no-sn" ), - pytest.param("a_eq_b", True, "surface_normal", None, False, id="a_eq_b-no-sn"), # psi: implemented when azimuth is set pytest.param( "psi", @@ -282,19 +286,23 @@ def test_reference_constraint_is_implemented( @pytest.mark.parametrize( "name, value, ref_attr, ref_value, expected", [ - pytest.param("alpha_i", 0.0, "surface_normal", None, False, id="alpha_i-no-sn"), pytest.param( - "alpha_i", 0.0, "surface_normal", (0, 0, 1), True, id="alpha_i-with-sn" + "incidence", 0.0, "surface_normal", None, False, id="incidence-no-sn" + ), + pytest.param( + "incidence", 0.0, "surface_normal", (0, 0, 1), True, id="incidence-with-sn" ), pytest.param( - "beta_out", 0.0, "surface_normal", None, False, id="beta_out-no-sn" + "emergence", 0.0, "surface_normal", None, False, id="emergence-no-sn" ), pytest.param( - "beta_out", 0.0, "surface_normal", (0, 0, 1), True, id="beta_out-with-sn" + "emergence", 0.0, "surface_normal", (0, 0, 1), True, id="emergence-with-sn" ), - pytest.param("a_eq_b", True, "surface_normal", None, False, id="a_eq_b-no-sn"), pytest.param( - "a_eq_b", True, "surface_normal", (0, 0, 1), True, id="a_eq_b-with-sn" + "specular", True, "surface_normal", None, False, id="specular-no-sn" + ), + pytest.param( + "specular", True, "surface_normal", (0, 0, 1), True, id="specular-with-sn" ), pytest.param("psi", 0.0, "azimuth", None, False, id="psi-no-ar"), pytest.param("psi", 0.0, "azimuth", (0, 0, 1), True, id="psi-with-ar"), @@ -416,9 +424,9 @@ def _setup_surface(factory, surface_normal=(0, 0, 1)): pytest.param(zaxis, "zaxis", id="zaxis-zaxis"), pytest.param(zaxis, "reflectivity", id="zaxis-reflectivity"), pytest.param(s2d2, "reflectivity", id="s2d2-reflectivity"), - pytest.param(sixc, "fixed_alpha_zaxis", id="sixc-fixed_alpha_zaxis"), - pytest.param(sixc, "fixed_beta_zaxis", id="sixc-fixed_beta_zaxis"), - pytest.param(sixc, "alpha_eq_beta_zaxis", id="sixc-alpha_eq_beta_zaxis"), + pytest.param(sixc, "fixed_incidence_zaxis", id="sixc-fixed_incidence_zaxis"), + pytest.param(sixc, "fixed_emergence_zaxis", id="sixc-fixed_emergence_zaxis"), + pytest.param(sixc, "specular_zaxis", id="sixc-specular_zaxis"), ], ) def test_surface_mode_is_implemented_with_surface_normal(factory, mode_name): @@ -433,9 +441,9 @@ def test_surface_mode_is_implemented_with_surface_normal(factory, mode_name): pytest.param(zaxis, "zaxis", id="zaxis-zaxis"), pytest.param(zaxis, "reflectivity", id="zaxis-reflectivity"), pytest.param(s2d2, "reflectivity", id="s2d2-reflectivity"), - pytest.param(sixc, "fixed_alpha_zaxis", id="sixc-fixed_alpha_zaxis"), - pytest.param(sixc, "fixed_beta_zaxis", id="sixc-fixed_beta_zaxis"), - pytest.param(sixc, "alpha_eq_beta_zaxis", id="sixc-alpha_eq_beta_zaxis"), + pytest.param(sixc, "fixed_incidence_zaxis", id="sixc-fixed_incidence_zaxis"), + pytest.param(sixc, "fixed_emergence_zaxis", id="sixc-fixed_emergence_zaxis"), + pytest.param(sixc, "specular_zaxis", id="sixc-specular_zaxis"), ], ) def test_surface_mode_not_implemented_without_surface_normal(factory, mode_name): @@ -450,15 +458,19 @@ def test_surface_mode_not_implemented_without_surface_normal(factory, mode_name) pytest.param(zaxis, "zaxis", 0, 1, 0, id="zaxis-zaxis"), pytest.param(zaxis, "reflectivity", 0, 0, 1, id="zaxis-reflectivity"), pytest.param(s2d2, "reflectivity", 0, 1, 0, id="s2d2-reflectivity"), - pytest.param(sixc, "fixed_alpha_zaxis", 0, 1, 0, id="sixc-fixed_alpha_zaxis"), - pytest.param(sixc, "fixed_beta_zaxis", 0, 1, 0, id="sixc-fixed_beta_zaxis"), + pytest.param( + sixc, "fixed_incidence_zaxis", 0, 1, 0, id="sixc-fixed_incidence_zaxis" + ), + pytest.param( + sixc, "fixed_emergence_zaxis", 0, 1, 0, id="sixc-fixed_emergence_zaxis" + ), pytest.param( sixc, - "alpha_eq_beta_zaxis", + "specular_zaxis", 0, 1, 0, - id="sixc-alpha_eq_beta_zaxis", + id="sixc-specular_zaxis", ), ], ) @@ -473,19 +485,19 @@ def test_surface_mode_returns_solutions(factory, mode_name, h, k, l): # noqa: E @pytest.mark.parametrize( "factory, mode_name, h, k, l", [ - pytest.param(zaxis, "zaxis", 0, 1, 0, id="zaxis-zaxis-alpha_i=0"), + pytest.param(zaxis, "zaxis", 0, 1, 0, id="zaxis-zaxis-incidence=0"), pytest.param( sixc, - "fixed_alpha_zaxis", + "fixed_incidence_zaxis", 0, 1, 0, - id="sixc-fixed_alpha-alpha_i=0", + id="sixc-fixed_alpha-incidence=0", ), ], ) def test_surface_alpha_i_fixed_constraint_satisfied(factory, mode_name, h, k, l): # noqa: E741 - """alpha_i modes: incidence angle equals declared target (0°) in all solutions.""" + """incidence modes: incidence angle equals declared target (0°) in all solutions.""" g = _setup_surface(factory) g.mode_name = mode_name solutions = g.forward(h, k, l) @@ -500,22 +512,22 @@ def test_surface_alpha_i_fixed_constraint_satisfied(factory, mode_name, h, k, l) [ pytest.param( sixc, - "fixed_beta_zaxis", + "fixed_emergence_zaxis", 0, 1, 0, - id="sixc-fixed_beta-beta_out=0", + id="sixc-fixed_beta-emergence=0", ), ], ) def test_surface_beta_out_fixed_constraint_satisfied(factory, mode_name, h, k, l): # noqa: E741 - """beta_out modes: exit angle equals declared target (0°) in all solutions.""" + """emergence modes: exit angle equals declared target (0°) in all solutions.""" g = _setup_surface(factory) g.mode_name = mode_name solutions = g.forward(h, k, l) assert len(solutions) > 0 for sol in solutions: - bo = exit_angle(g, angles=sol) + bo = emergence_angle(g, angles=sol) assert bo == pytest.approx(0.0, abs=1e-4) @@ -524,18 +536,18 @@ def test_surface_beta_out_fixed_constraint_satisfied(factory, mode_name, h, k, l [ pytest.param(zaxis, "reflectivity", 0, 0, 1, id="zaxis-reflectivity"), pytest.param(s2d2, "reflectivity", 0, 1, 0, id="s2d2-reflectivity"), - pytest.param(sixc, "alpha_eq_beta_zaxis", 0, 1, 0, id="sixc-alpha_eq_beta"), + pytest.param(sixc, "specular_zaxis", 0, 1, 0, id="sixc-specular"), ], ) def test_surface_a_eq_b_constraint_satisfied(factory, mode_name, h, k, l): # noqa: E741 - """a_eq_b modes: alpha_i ≈ beta_out in all solutions.""" + """specular modes: incidence ≈ emergence in all solutions.""" g = _setup_surface(factory) g.mode_name = mode_name solutions = g.forward(h, k, l) assert len(solutions) > 0 for sol in solutions: ai = incidence_angle(g, angles=sol) - bo = exit_angle(g, angles=sol) + bo = emergence_angle(g, angles=sol) assert ai == pytest.approx(bo, abs=1e-4) @@ -665,9 +677,9 @@ def test_omega_pseudo_independent_of_chi(): "name, expected", [ pytest.param("psi", True, id="psi"), - pytest.param("alpha_i", True, id="alpha_i"), - pytest.param("beta_out", True, id="beta_out"), - pytest.param("a_eq_b", True, id="a_eq_b"), + pytest.param("incidence", True, id="incidence"), + pytest.param("emergence", True, id="emergence"), + pytest.param("specular", True, id="specular"), pytest.param("naz", True, id="naz"), pytest.param("omega", True, id="omega"), pytest.param("not_a_pseudo_angle", False, id="invalid"), @@ -681,7 +693,7 @@ def test_reference_constraint_accepts_omega(name, expected): else pytest.raises(ValueError, match=re.escape("ReferenceConstraint name")) ) with context: - if name == "a_eq_b": + if name == "specular": ReferenceConstraint(name, True) else: ReferenceConstraint(name, 0.0) diff --git a/tests/test_regression_issue_264.py b/tests/test_regression_issue_264.py index 178ac5ff..74d3e798 100644 --- a/tests/test_regression_issue_264.py +++ b/tests/test_regression_issue_264.py @@ -13,7 +13,7 @@ solver dispatch that handles them. 2. Three additional psic modes were introduced or revised per the - @jwkim-anl review: ``fixed_alpha_i_fixed_chi_fixed_phi`` (B3), + @jwkim-anl review: ``fixed_incidence_fixed_chi_fixed_phi`` (B3), ``lifting_detector_eta`` (B4), plus the ``lifting_detector_phi`` / ``lifting_detector_mu`` revisions (C3/C4) that drop the ``qaz = 90`` constraint and fix every sample @@ -31,7 +31,7 @@ satisfy the Bragg condition end-to-end. - The dispatcher routes each new/revised mode to its intended solver branch (``_solve_omega_mode`` for ``fixed_omega_*``, - ``_solve_free_detectors`` for ``fixed_alpha_i_fixed_chi_fixed_phi``, + ``_solve_free_detectors`` for ``fixed_incidence_fixed_chi_fixed_phi``, ``lifting_detector_eta``, and the revised ``lifting_detector_phi`` / ``lifting_detector_mu``). @@ -76,7 +76,7 @@ _ISSUE_264_NEW_MODES = { "fixed_omega_vertical", "fixed_omega_horizontal", - "fixed_alpha_i_fixed_chi_fixed_phi", + "fixed_incidence_fixed_chi_fixed_phi", "lifting_detector_eta", } _ISSUE_264_REVISED_MODES = { @@ -305,8 +305,8 @@ def test_fixed_alpha_i_fixed_chi_fixed_phi_routes_to_free_detectors(): """B3 routes to ``_solve_free_detectors`` only after ``surface_normal`` is set (otherwise the mode is a stub).""" g = _setup_psic_cubic() - cs = g.modes["fixed_alpha_i_fixed_chi_fixed_phi"] - # Without surface_normal, the alpha_i ReferenceConstraint reports + cs = g.modes["fixed_incidence_fixed_chi_fixed_phi"] + # Without surface_normal, the incidence ReferenceConstraint reports # is_implemented=False, so the mode is a stub regardless of dispatch. assert cs.is_implemented(g) is False g.surface_normal = (0, 0, 1) @@ -325,7 +325,7 @@ def test_fixed_alpha_i_fixed_chi_fixed_phi_routes_to_free_detectors(): [ pytest.param("fixed_omega_vertical", 1, 0, 0, False, id="fov-100"), pytest.param("fixed_omega_horizontal", 0, 0, 1, False, id="foh-001"), - pytest.param("fixed_alpha_i_fixed_chi_fixed_phi", 0, 1, 1, True, id="b3-011"), + pytest.param("fixed_incidence_fixed_chi_fixed_phi", 0, 1, 1, True, id="b3-011"), pytest.param("lifting_detector_eta", 1, 1, 0, False, id="le-110"), pytest.param("lifting_detector_phi", 1, 0, 0, False, id="lp-100"), # Under issue #280 ub_identity, (0,1,0) is unreachable on psic @@ -555,7 +555,7 @@ def test_omega_constraint_unimplemented_without_chi_stage(): # --------------------------------------------------------------------------- -# Issue #264 design invariant: B3 produces solutions whose alpha_i matches +# Issue #264 design invariant: B3 produces solutions whose incidence matches # the requested target. # --------------------------------------------------------------------------- @@ -565,17 +565,17 @@ def test_omega_constraint_unimplemented_without_chi_stage(): [0.0, 3.0, 7.5], ) def test_b3_alpha_i_target_satisfied(alpha_target): - """B3 ``fixed_alpha_i_fixed_chi_fixed_phi`` solutions satisfy - alpha_i == target within tolerance.""" + """B3 ``fixed_incidence_fixed_chi_fixed_phi`` solutions satisfy + incidence == target within tolerance.""" g = _setup_psic_cubic() g.surface_normal = (0, 0, 1) - cs = g.modes["fixed_alpha_i_fixed_chi_fixed_phi"] - # Override alpha_i target with the parametrized value. + cs = g.modes["fixed_incidence_fixed_chi_fixed_phi"] + # Override incidence target with the parametrized value. g.modes["__b3_test"] = ConstraintSet( [ SampleConstraint("chi", 0.0), SampleConstraint("phi", 0.0), - ReferenceConstraint("alpha_i", alpha_target), + ReferenceConstraint("incidence", alpha_target), ], computed=cs.computed, extras=dict(cs.extras), @@ -586,5 +586,5 @@ def test_b3_alpha_i_target_satisfied(alpha_target): for sol in sols: ai = incidence_angle(g, angles=sol) assert ai == pytest.approx(alpha_target, abs=1e-3), ( - f"B3 alpha_i target {alpha_target}: got {ai}" + f"B3 incidence target {alpha_target}: got {ai}" ) diff --git a/tests/test_regression_issue_267.py b/tests/test_regression_issue_267.py index 0edea15c..c85f31a3 100644 --- a/tests/test_regression_issue_267.py +++ b/tests/test_regression_issue_267.py @@ -184,32 +184,32 @@ def _reference_psic() -> AdHocDiffractometer: ], computed=["eta", "phi", "delta"], ), - "fixed_alpha_i_vertical": ConstraintSet( + "fixed_incidence_vertical": ConstraintSet( [ SampleConstraint("mu", 0.0), DetectorConstraint("nu", 0.0), - ReferenceConstraint("alpha_i", 0.0), + ReferenceConstraint("incidence", 0.0), ], computed=["eta", "chi", "phi", "delta"], - extras={"n_hat": REQUIRED, "alpha_i": None, "beta_out": None}, + extras={"n_hat": REQUIRED, "incidence": None, "emergence": None}, ), - "fixed_beta_out_vertical": ConstraintSet( + "fixed_emergence_vertical": ConstraintSet( [ SampleConstraint("mu", 0.0), DetectorConstraint("nu", 0.0), - ReferenceConstraint("beta_out", 0.0), + ReferenceConstraint("emergence", 0.0), ], computed=["eta", "chi", "phi", "delta"], - extras={"n_hat": REQUIRED, "alpha_i": None, "beta_out": None}, + extras={"n_hat": REQUIRED, "incidence": None, "emergence": None}, ), - "alpha_eq_beta_vertical": ConstraintSet( + "specular_vertical": ConstraintSet( [ SampleConstraint("mu", 0.0), DetectorConstraint("nu", 0.0), - ReferenceConstraint("a_eq_b", True), + ReferenceConstraint("specular", True), ], computed=["eta", "chi", "phi", "delta"], - extras={"n_hat": REQUIRED, "alpha_i": None, "beta_out": None}, + extras={"n_hat": REQUIRED, "incidence": None, "emergence": None}, ), "fixed_psi_vertical": ConstraintSet( [ @@ -220,14 +220,14 @@ def _reference_psic() -> AdHocDiffractometer: computed=["eta", "chi", "phi", "delta"], extras={"n_hat": REQUIRED, "psi": None}, ), - "fixed_alpha_i_fixed_chi_fixed_phi": ConstraintSet( + "fixed_incidence_fixed_chi_fixed_phi": ConstraintSet( [ SampleConstraint("chi", 0.0), SampleConstraint("phi", 0.0), - ReferenceConstraint("alpha_i", 0.0), + ReferenceConstraint("incidence", 0.0), ], computed=["mu", "eta", "nu", "delta"], - extras={"n_hat": REQUIRED, "alpha_i": None, "beta_out": None}, + extras={"n_hat": REQUIRED, "incidence": None, "emergence": None}, ), "fixed_omega_vertical": ConstraintSet( [ @@ -271,32 +271,32 @@ def _reference_psic() -> AdHocDiffractometer: ], computed=["mu", "phi", "nu"], ), - "fixed_alpha_i_horizontal": ConstraintSet( + "fixed_incidence_horizontal": ConstraintSet( [ SampleConstraint("eta", 0.0), DetectorConstraint("delta", 0.0), - ReferenceConstraint("alpha_i", 0.0), + ReferenceConstraint("incidence", 0.0), ], computed=["mu", "chi", "phi", "nu"], - extras={"n_hat": REQUIRED, "alpha_i": None, "beta_out": None}, + extras={"n_hat": REQUIRED, "incidence": None, "emergence": None}, ), - "fixed_beta_out_horizontal": ConstraintSet( + "fixed_emergence_horizontal": ConstraintSet( [ SampleConstraint("eta", 0.0), DetectorConstraint("delta", 0.0), - ReferenceConstraint("beta_out", 0.0), + ReferenceConstraint("emergence", 0.0), ], computed=["mu", "chi", "phi", "nu"], - extras={"n_hat": REQUIRED, "alpha_i": None, "beta_out": None}, + extras={"n_hat": REQUIRED, "incidence": None, "emergence": None}, ), - "alpha_eq_beta_horizontal": ConstraintSet( + "specular_horizontal": ConstraintSet( [ SampleConstraint("eta", 0.0), DetectorConstraint("delta", 0.0), - ReferenceConstraint("a_eq_b", True), + ReferenceConstraint("specular", True), ], computed=["mu", "chi", "phi", "nu"], - extras={"n_hat": REQUIRED, "alpha_i": None, "beta_out": None}, + extras={"n_hat": REQUIRED, "incidence": None, "emergence": None}, ), "fixed_psi_horizontal": ConstraintSet( [ @@ -721,32 +721,32 @@ def _reference_sixc() -> AdHocDiffractometer: ], computed=["omega", "chi", "phi", "delta", "gamma"], ), - "fixed_alpha_zaxis": ConstraintSet( + "fixed_incidence_zaxis": ConstraintSet( [ SampleConstraint("alpha", 0.0), SampleConstraint("chi", 0.0), - ReferenceConstraint("alpha_i", 0.0), + ReferenceConstraint("incidence", 0.0), ], computed=["omega", "delta", "gamma"], - extras={"n_hat": REQUIRED, "alpha_i": None, "beta_out": None}, + extras={"n_hat": REQUIRED, "incidence": None, "emergence": None}, ), - "fixed_beta_zaxis": ConstraintSet( + "fixed_emergence_zaxis": ConstraintSet( [ DetectorConstraint("gamma", 0.0), SampleConstraint("chi", 0.0), - ReferenceConstraint("beta_out", 0.0), + ReferenceConstraint("emergence", 0.0), ], computed=["omega", "delta", "alpha"], - extras={"n_hat": REQUIRED, "alpha_i": None, "beta_out": None}, + extras={"n_hat": REQUIRED, "incidence": None, "emergence": None}, ), - "alpha_eq_beta_zaxis": ConstraintSet( + "specular_zaxis": ConstraintSet( [ SampleConstraint("chi", 0.0), SampleConstraint("phi", 0.0), - ReferenceConstraint("a_eq_b", True), + ReferenceConstraint("specular", True), ], computed=["omega", "delta", "alpha", "gamma"], - extras={"n_hat": REQUIRED, "alpha_i": None, "beta_out": None}, + extras={"n_hat": REQUIRED, "incidence": None, "emergence": None}, ), } return AdHocDiffractometer( @@ -777,14 +777,14 @@ def _reference_zaxis() -> AdHocDiffractometer: ] modes = { "zaxis": ConstraintSet( - [ReferenceConstraint("alpha_i", 0.0)], + [ReferenceConstraint("incidence", 0.0)], computed=["Z", "delta", "gamma"], - extras={"n_hat": REQUIRED, "alpha_i": None, "beta_out": None}, + extras={"n_hat": REQUIRED, "incidence": None, "emergence": None}, ), "reflectivity": ConstraintSet( - [ReferenceConstraint("a_eq_b", True)], + [ReferenceConstraint("specular", True)], computed=["Z", "delta", "alpha", "gamma"], - extras={"n_hat": REQUIRED, "alpha_i": None, "beta_out": None}, + extras={"n_hat": REQUIRED, "incidence": None, "emergence": None}, ), } return AdHocDiffractometer( @@ -818,9 +818,9 @@ def _reference_s2d2() -> AdHocDiffractometer: computed=["Z", "nu", "delta"], ), "reflectivity": ConstraintSet( - [ReferenceConstraint("a_eq_b", True)], + [ReferenceConstraint("specular", True)], computed=["mu", "Z", "nu", "delta"], - extras={"n_hat": REQUIRED, "alpha_i": None, "beta_out": None}, + extras={"n_hat": REQUIRED, "incidence": None, "emergence": None}, ), } return AdHocDiffractometer( diff --git a/tests/test_regression_issue_279.py b/tests/test_regression_issue_279.py index ba512d87..fe02825d 100644 --- a/tests/test_regression_issue_279.py +++ b/tests/test_regression_issue_279.py @@ -8,8 +8,8 @@ ``2θ`` angle to the **last** stage in ``geometry.detector_stages`` unconditionally. For psic that last stage is ``delta``, and the horizontal surface modes -(``fixed_alpha_i_horizontal``, ``fixed_beta_out_horizontal``, -``alpha_eq_beta_horizontal``) declare a +(``fixed_incidence_horizontal``, ``fixed_emergence_horizontal``, +``specular_horizontal``) declare a :class:`~ad_hoc_diffractometer.mode.DetectorConstraint` that pins ``delta = 0``. The solver therefore overwrote the pinned value moments after applying it and left the truly active detector stage @@ -17,8 +17,8 @@ ``nu = 0`` that violated the mode's own constraint. The mirror failure occurred in the vertical surface modes -(``fixed_alpha_i_vertical``, ``fixed_beta_out_vertical``, -``alpha_eq_beta_vertical``), where the mode pins ``nu = 0`` and +(``fixed_incidence_vertical``, ``fixed_emergence_vertical``, +``specular_vertical``), where the mode pins ``nu = 0`` and the active detector stage is ``delta`` — but the dispatch picked ``delta`` for both roles regardless, so the constraint happened to agree with the active stage by accident (the resulting ``nu = 0`` @@ -64,17 +64,17 @@ # Surface-reference modes on psic whose DetectorConstraint pins the # inner (delta) stage so the active detector for 2θ must be ``nu``. _PSIC_HORIZONTAL_SURFACE_MODES = ( - "fixed_alpha_i_horizontal", - "fixed_beta_out_horizontal", - "alpha_eq_beta_horizontal", + "fixed_incidence_horizontal", + "fixed_emergence_horizontal", + "specular_horizontal", ) # Surface-reference modes on psic whose DetectorConstraint pins the # outer (nu) stage so the active detector for 2θ must be ``delta``. _PSIC_VERTICAL_SURFACE_MODES = ( - "fixed_alpha_i_vertical", - "fixed_beta_out_vertical", - "alpha_eq_beta_vertical", + "fixed_incidence_vertical", + "fixed_emergence_vertical", + "specular_vertical", ) @@ -170,21 +170,21 @@ def test_psic_vertical_surface_honors_nu_pin(mode_name, context): # must not alter the existing healthy round-trip count. pytest.param( sixc, - "fixed_alpha_zaxis", + "fixed_incidence_zaxis", 1, 0, 0, does_not_raise(), - id="sixc-fixed_alpha_zaxis-100", + id="sixc-fixed_incidence_zaxis-100", ), pytest.param( sixc, - "alpha_eq_beta_zaxis", + "specular_zaxis", 1, 0, 0, does_not_raise(), - id="sixc-alpha_eq_beta_zaxis-100", + id="sixc-specular_zaxis-100", ), # zaxis reflectivity mode: confirm at least one returned # solution still round-trips, i.e. the legacy path is intact. @@ -278,7 +278,7 @@ def test_solve_surface_returns_empty_when_only_detector_stage_pinned(): constraints=[ SampleConstraint("omega", 0.0), DetectorConstraint(det_name, 0.0), - ReferenceConstraint("alpha_i", 0.0), + ReferenceConstraint("incidence", 0.0), ], ) diff --git a/tests/test_regression_issue_292.py b/tests/test_regression_issue_292.py index 155bcfa8..17011d1d 100644 --- a/tests/test_regression_issue_292.py +++ b/tests/test_regression_issue_292.py @@ -3,9 +3,9 @@ """ Regression tests for issue #292. -Issue #292 noted that the ``fixed_alpha_i_*``, ``fixed_beta_out_*``, -``alpha_eq_beta_*`` modes (psic, sixc, zaxis, s2d2) — and by extension -every mode declaring an ``alpha_i``, ``beta_out``, ``psi``, or ``omega`` +Issue #292 noted that the ``fixed_incidence_*``, ``fixed_emergence_*``, +``specular_*`` modes (psic, sixc, zaxis, s2d2) — and by extension every +mode declaring an ``incidence``, ``emergence``, ``psi``, or ``omega`` output slot in ``mode.extras`` — left those slots at their YAML default of ``None`` even after a successful ``forward()`` call. This was a latent contract bug: the slots advertised an output the solver never @@ -16,10 +16,10 @@ slot is replaced by a Python list of per-solution float values computed via the corresponding helper in :mod:`ad_hoc_diffractometer.reference`: -* ``alpha_i`` → :func:`~ad_hoc_diffractometer.reference.incidence_angle` -* ``beta_out`` → :func:`~ad_hoc_diffractometer.reference.exit_angle` -* ``psi`` → :func:`~ad_hoc_diffractometer.reference.psi_angle` -* ``omega`` → :func:`~ad_hoc_diffractometer.reference.omega_pseudo` +* ``incidence`` → :func:`~ad_hoc_diffractometer.reference.incidence_angle` +* ``emergence`` → :func:`~ad_hoc_diffractometer.reference.emergence_angle` +* ``psi`` → :func:`~ad_hoc_diffractometer.reference.psi_angle` +* ``omega`` → :func:`~ad_hoc_diffractometer.reference.omega_pseudo` Empty solution lists reset the slot to ``[]``; a missing reference vector leaves the slot at ``None`` (with a debug-level log message). @@ -37,7 +37,7 @@ import pytest import ad_hoc_diffractometer as ahd -from ad_hoc_diffractometer.reference import exit_angle +from ad_hoc_diffractometer.reference import emergence_angle from ad_hoc_diffractometer.reference import incidence_angle from ad_hoc_diffractometer.reference import omega_pseudo from ad_hoc_diffractometer.reference import psi_angle @@ -55,67 +55,66 @@ def _setup_cubic(name, a=4.0): # --------------------------------------------------------------------------- -# B3 surface mode: psic / fixed_alpha_i_fixed_chi_fixed_phi -# (the only fixed_alpha_i_* mode that is currently implemented for psic). +# B3 surface mode: psic / fixed_incidence_fixed_chi_fixed_phi. # --------------------------------------------------------------------------- @pytest.mark.parametrize( - "h, k, l, alpha_target, context", + "h, k, l, incidence_target, context", [ - pytest.param(0, 1, 1, 0.0, does_not_raise(), id="011-ai0"), - pytest.param(0, 1, 1, 5.0, does_not_raise(), id="011-ai5"), - pytest.param(1, 1, 1, 3.0, does_not_raise(), id="111-ai3"), + pytest.param(0, 1, 1, 0.0, does_not_raise(), id="011-i0"), + pytest.param(0, 1, 1, 5.0, does_not_raise(), id="011-i5"), + pytest.param(1, 1, 1, 3.0, does_not_raise(), id="111-i3"), ], ) -def test_psic_b3_populates_alpha_i_and_beta_out_extras( +def test_psic_b3_populates_incidence_and_emergence_extras( h, k, l, # noqa: E741 - alpha_target, + incidence_target, context, ): - """B3 mode populates ``extras['alpha_i']`` and ``extras['beta_out']``.""" + """B3 mode populates ``extras['incidence']`` and ``extras['emergence']``.""" with context: g = _setup_cubic("psic") g.surface_normal = (0, 0, 1) - cs = g.modes["fixed_alpha_i_fixed_chi_fixed_phi"] + cs = g.modes["fixed_incidence_fixed_chi_fixed_phi"] # Update the reference-constraint target via the public API: - # rebuild the constraint set with the requested alpha_i value + # rebuild the constraint set with the requested incidence value # (the YAML defaults to 0.0; we want non-zero targets too). from ad_hoc_diffractometer import REQUIRED from ad_hoc_diffractometer import ConstraintSet from ad_hoc_diffractometer import ReferenceConstraint from ad_hoc_diffractometer import SampleConstraint - g.modes["fixed_alpha_i_fixed_chi_fixed_phi"] = ConstraintSet( + g.modes["fixed_incidence_fixed_chi_fixed_phi"] = ConstraintSet( [ SampleConstraint("chi", 0.0), SampleConstraint("phi", 0.0), - ReferenceConstraint("alpha_i", alpha_target), + ReferenceConstraint("incidence", incidence_target), ], computed=cs.computed, - extras={"n_hat": REQUIRED, "alpha_i": None, "beta_out": None}, + extras={"n_hat": REQUIRED, "incidence": None, "emergence": None}, ) - g.mode_name = "fixed_alpha_i_fixed_chi_fixed_phi" + g.mode_name = "fixed_incidence_fixed_chi_fixed_phi" sols = g.forward(h, k, l) assert len(sols) > 0 - mode = g.modes["fixed_alpha_i_fixed_chi_fixed_phi"] - assert isinstance(mode.extras["alpha_i"], list) - assert isinstance(mode.extras["beta_out"], list) - assert len(mode.extras["alpha_i"]) == len(sols) - assert len(mode.extras["beta_out"]) == len(sols) + mode = g.modes["fixed_incidence_fixed_chi_fixed_phi"] + assert isinstance(mode.extras["incidence"], list) + assert isinstance(mode.extras["emergence"], list) + assert len(mode.extras["incidence"]) == len(sols) + assert len(mode.extras["emergence"]) == len(sols) - # Each populated alpha_i value matches the per-solution recomputation + # Each populated incidence value matches the per-solution recomputation # and (per the mode's constraint) equals the target. for ai_stored, bo_stored, sol in zip( - mode.extras["alpha_i"], mode.extras["beta_out"], sols, strict=True + mode.extras["incidence"], mode.extras["emergence"], sols, strict=True ): assert ai_stored == pytest.approx(incidence_angle(g, angles=sol), abs=1e-8) - assert bo_stored == pytest.approx(exit_angle(g, angles=sol), abs=1e-8) - assert ai_stored == pytest.approx(alpha_target, abs=1e-3) + assert bo_stored == pytest.approx(emergence_angle(g, angles=sol), abs=1e-8) + assert ai_stored == pytest.approx(incidence_target, abs=1e-3) # --------------------------------------------------------------------------- @@ -287,7 +286,7 @@ def test_modes_without_output_slots_are_untouched(): sols = g.forward(0, 1, 1) assert len(sols) > 0 extras = g.modes["bisecting_vertical"].extras - for key in ("alpha_i", "beta_out", "psi", "omega"): + for key in ("incidence", "emergence", "psi", "omega"): assert key not in extras, ( f"populate hook must not add {key!r} to a mode whose YAML did not " f"declare it; bisecting_vertical extras: {sorted(extras)}" diff --git a/tests/test_surface.py b/tests/test_surface.py index fc0c5cb3..bb7e4bb7 100644 --- a/tests/test_surface.py +++ b/tests/test_surface.py @@ -8,9 +8,9 @@ - surface_normal fallback to azimuth - _surface_vectors precondition errors (no wavelength, no UB, no normal, unknown stage name) - - alpha_i: matches motor angle for canonical geometries (s2d2, zaxis) - - alpha_f: matches out-of-plane detector angle (s2d2, zaxis) - - alpha_i and alpha_f are symmetric: same at ai=af angles + - incidence: matches motor angle for canonical geometries (s2d2, zaxis) + - emergence: matches out-of-plane detector angle (s2d2, zaxis) + - incidence and emergence are symmetric: same at ai=af angles - q_components: Q_perp, Q_par, Q_perp_signed, Q_total - Q_perp ≥ 0, Q_par ≥ 0 always - Q_total = sqrt(Q_perp^2 + Q_par^2) @@ -18,9 +18,9 @@ - is_specular: True when ai ≈ af, False otherwise - is_evanescent: True when ai < crit, False when ai ≥ crit, error when crit=None - Serialisation round-trip: surface_normal in to_dict/from_dict - - Standalone functions (alpha_i, alpha_f, q_components, is_specular, + - Standalone functions (incidence, emergence, q_components, is_specular, is_evanescent) imported from top-level - - geometry.alpha_i/alpha_f/q_components/is_specular/is_evanescent methods + - geometry.incidence/emergence/q_components/is_specular/is_evanescent methods - Default angles (None) uses current stage angles - psic surface: surface_normal set, UB=identity, verify ai/af/psi combination """ @@ -37,8 +37,8 @@ import ad_hoc_diffractometer as ahd from ad_hoc_diffractometer import ub_identity from ad_hoc_diffractometer.surface import _surface_vectors -from ad_hoc_diffractometer.surface import alpha_f -from ad_hoc_diffractometer.surface import alpha_i +from ad_hoc_diffractometer.surface import emergence +from ad_hoc_diffractometer.surface import incidence from ad_hoc_diffractometer.surface import is_evanescent from ad_hoc_diffractometer.surface import is_specular from ad_hoc_diffractometer.surface import q_components @@ -226,12 +226,12 @@ def test_surface_normal_fallback_to_azimuth(): g.surface_normal = None g.azimuth = (0, 0, 1) # Should not raise and should produce a result - ai = g.alpha_i({"mu": 5.0, "Z": 0.0, "nu": 0.0, "delta": 0.0}) + ai = g.incidence({"mu": 5.0, "Z": 0.0, "nu": 0.0, "delta": 0.0}) assert pytest.approx(ai, abs=1e-6) == 5.0 # --------------------------------------------------------------------------- -# alpha_i — s2d2 canonical: alpha_i = mu (surface_normal along +z, a=1) +# incidence — s2d2 canonical: incidence = mu (surface_normal along +z, a=1) # --------------------------------------------------------------------------- @@ -246,16 +246,16 @@ def test_surface_normal_fallback_to_azimuth(): pytest.param(90.0, 90.0, does_not_raise(), id="mu=90"), ], ) -def test_alpha_i_s2d2_equals_mu(mu, expected_ai, context): - """In s2d2 with surface_normal=(0,0,1), alpha_i = mu exactly.""" +def test_incidence_s2d2_equals_mu(mu, expected_ai, context): + """In s2d2 with surface_normal=(0,0,1), incidence = mu exactly.""" g = _make_s2d2() with context: - ai = g.alpha_i({"mu": mu, "Z": 0.0, "nu": 0.0, "delta": 0.0}) + ai = g.incidence({"mu": mu, "Z": 0.0, "nu": 0.0, "delta": 0.0}) assert ai == pytest.approx(expected_ai, abs=1e-6) # --------------------------------------------------------------------------- -# alpha_i — zaxis canonical: alpha_i = alpha (surface_normal along +z) +# incidence — zaxis canonical: incidence = alpha (surface_normal along +z) # --------------------------------------------------------------------------- @@ -268,16 +268,16 @@ def test_alpha_i_s2d2_equals_mu(mu, expected_ai, context): pytest.param(20.0, 20.0, does_not_raise(), id="alpha=20"), ], ) -def test_alpha_i_zaxis_equals_alpha(alpha_val, expected_ai, context): - """In zaxis with surface_normal=(0,0,1), alpha_i = alpha exactly.""" +def test_incidence_zaxis_equals_alpha(alpha_val, expected_ai, context): + """In zaxis with surface_normal=(0,0,1), incidence = alpha exactly.""" g = _make_zaxis() with context: - ai = g.alpha_i({"alpha": alpha_val, "Z": 0.0, "delta": 10.0, "gamma": 0.0}) + ai = g.incidence({"alpha": alpha_val, "Z": 0.0, "delta": 10.0, "gamma": 0.0}) assert ai == pytest.approx(expected_ai, abs=1e-6) # --------------------------------------------------------------------------- -# alpha_f — s2d2: alpha_f = nu when delta = 0 +# emergence — s2d2: emergence = nu when delta = 0 # --------------------------------------------------------------------------- @@ -290,24 +290,24 @@ def test_alpha_i_zaxis_equals_alpha(alpha_val, expected_ai, context): pytest.param(20.0, 20.0, does_not_raise(), id="nu=20"), ], ) -def test_alpha_f_s2d2_equals_nu_at_delta_zero(nu, expected_af, context): - """In s2d2 with delta=0 and surface_normal=(0,0,1), alpha_f = nu exactly.""" +def test_emergence_s2d2_equals_nu_at_delta_zero(nu, expected_af, context): + """In s2d2 with delta=0 and surface_normal=(0,0,1), emergence = nu exactly.""" g = _make_s2d2() with context: - af = g.alpha_f({"mu": 0.0, "Z": 0.0, "nu": nu, "delta": 0.0}) + af = g.emergence({"mu": 0.0, "Z": 0.0, "nu": nu, "delta": 0.0}) assert af == pytest.approx(expected_af, abs=1e-6) -def test_alpha_f_s2d2_zero_at_any_in_plane_delta(): - """In s2d2 with nu=0, alpha_f = 0 for any delta (in-plane only).""" +def test_emergence_s2d2_zero_at_any_in_plane_delta(): + """In s2d2 with nu=0, emergence = 0 for any delta (in-plane only).""" g = _make_s2d2() for delta in [0.0, 10.0, 20.0, 45.0]: - af = g.alpha_f({"mu": 0.0, "Z": 0.0, "nu": 0.0, "delta": delta}) + af = g.emergence({"mu": 0.0, "Z": 0.0, "nu": 0.0, "delta": delta}) assert af == pytest.approx(0.0, abs=1e-6) # --------------------------------------------------------------------------- -# alpha_f — zaxis: matches geometric formula +# emergence — zaxis: matches geometric formula # --------------------------------------------------------------------------- @@ -329,7 +329,7 @@ def test_alpha_f_s2d2_zero_at_any_in_plane_delta(): # With the surface normal along +transverse (= +z in YOU # basis, set by ``surface_normal = (0, 0, 1)``), the # out-of-plane projection is simply ``sin γ`` — i.e. - # ``alpha_f = γ`` independent of delta. The earlier formula + # ``emergence = γ`` independent of delta. The earlier formula # ``arcsin(cos δ · sin γ)`` was a baked-in expectation under # the pre-#280 (inner-leftmost) composition where # ``D = R(gamma) @ R(delta)``. @@ -349,32 +349,32 @@ def test_alpha_f_s2d2_zero_at_any_in_plane_delta(): ), ], ) -def test_alpha_f_zaxis_formula(delta, gamma, expected_af, context): - """alpha_f for zaxis under the standard BL1967 detector composition.""" +def test_emergence_zaxis_formula(delta, gamma, expected_af, context): + """emergence for zaxis under the standard BL1967 detector composition.""" g = _make_zaxis() with context: - af = g.alpha_f({"alpha": 0.0, "Z": 0.0, "delta": delta, "gamma": gamma}) + af = g.emergence({"alpha": 0.0, "Z": 0.0, "delta": delta, "gamma": gamma}) assert af == pytest.approx(expected_af, abs=1e-6) # --------------------------------------------------------------------------- -# alpha_i always in [0°, 90°] +# incidence always in [0°, 90°] # --------------------------------------------------------------------------- -def test_alpha_i_always_nonnegative(): - """alpha_i is always in [0°, 90°] regardless of sign of mu.""" +def test_incidence_always_nonnegative(): + """incidence is always in [0°, 90°] regardless of sign of mu.""" g = _make_s2d2() for mu in [-20.0, -10.0, -5.0, 0.0, 5.0, 10.0, 20.0]: - ai = g.alpha_i({"mu": mu, "Z": 0.0, "nu": 0.0, "delta": 0.0}) + ai = g.incidence({"mu": mu, "Z": 0.0, "nu": 0.0, "delta": 0.0}) assert 0.0 <= ai <= 90.0 -def test_alpha_f_always_nonnegative(): - """alpha_f is always in [0°, 90°].""" +def test_emergence_always_nonnegative(): + """emergence is always in [0°, 90°].""" g = _make_s2d2() for nu in [-20.0, -10.0, 0.0, 10.0, 20.0]: - af = g.alpha_f({"mu": 0.0, "Z": 0.0, "nu": nu, "delta": 0.0}) + af = g.emergence({"mu": 0.0, "Z": 0.0, "nu": nu, "delta": 0.0}) assert 0.0 <= af <= 90.0 @@ -453,8 +453,8 @@ def test_is_specular_true_when_ai_equals_af(): is_specular returns True when ai = af. In s2d2 with surface_normal=(0,0,1) and a=1, the specular condition is: - - alpha_i = mu (verified separately) - - alpha_f = |nu - mu| when delta=0 (rotation of surface normal and detector + - incidence = mu (verified separately) + - emergence = |nu - mu| when delta=0 (rotation of surface normal and detector both about the same +x axis means their relative angle is nu - mu) Therefore specular (ai = af) requires nu = 2*mu. """ @@ -527,21 +527,21 @@ def test_is_evanescent_raises_without_critical_angle(): # --------------------------------------------------------------------------- -def test_psic_alpha_i_from_mu(): - """In psic with surface_normal=(0,0,1), alpha_i = mu (rotation about +x).""" +def test_psic_incidence_from_mu(): + """In psic with surface_normal=(0,0,1), incidence = mu (rotation about +x).""" g = _make_psic() for mu in [0.0, 2.0, 5.0, 10.0]: - ai = g.alpha_i( + ai = g.incidence( {"mu": mu, "eta": 0.0, "chi": 0.0, "phi": 0.0, "nu": 0.0, "delta": 0.0} ) assert ai == pytest.approx(mu, abs=1e-6) -def test_psic_alpha_f_from_nu(): - """In psic with surface_normal=(0,0,1), alpha_f = nu when delta=0.""" +def test_psic_emergence_from_nu(): + """In psic with surface_normal=(0,0,1), emergence = nu when delta=0.""" g = _make_psic() for nu in [0.0, 2.0, 5.0, 10.0]: - af = g.alpha_f( + af = g.emergence( {"mu": 0.0, "eta": 0.0, "chi": 0.0, "phi": 0.0, "nu": nu, "delta": 0.0} ) assert af == pytest.approx(nu, abs=1e-6) @@ -556,8 +556,8 @@ def test_default_angles_uses_current_stage_angles(): """When angles=None, current stage angles are used.""" g = _make_s2d2() g.set_angle("mu", 5.0) - ai_explicit = g.alpha_i({"mu": 5.0, "Z": 0.0, "nu": 0.0, "delta": 0.0}) - ai_default = g.alpha_i() # uses current angles + ai_explicit = g.incidence({"mu": 5.0, "Z": 0.0, "nu": 0.0, "delta": 0.0}) + ai_default = g.incidence() # uses current angles assert ai_explicit == pytest.approx(ai_default, abs=1e-10) @@ -566,16 +566,16 @@ def test_default_angles_uses_current_stage_angles(): # --------------------------------------------------------------------------- -def test_standalone_alpha_i(): +def test_standalone_incidence(): g = _make_s2d2() angles = {"mu": 5.0, "Z": 0.0, "nu": 0.0, "delta": 0.0} - assert alpha_i(g, angles) == pytest.approx(5.0, abs=1e-6) + assert incidence(g, angles) == pytest.approx(5.0, abs=1e-6) -def test_standalone_alpha_f(): +def test_standalone_emergence(): g = _make_s2d2() angles = {"mu": 0.0, "Z": 0.0, "nu": 5.0, "delta": 0.0} - assert alpha_f(g, angles) == pytest.approx(5.0, abs=1e-6) + assert emergence(g, angles) == pytest.approx(5.0, abs=1e-6) def test_standalone_q_components(): @@ -642,4 +642,4 @@ def test_surface_normal_zero_in_phi_frame_raises(): # Set UB to all-zeros (degenerate) g.sample.UB = np.zeros((3, 3)) with pytest.raises(ValueError, match=re.escape("zero vector")): - g.alpha_i({"mu": 5.0, "Z": 0.0, "nu": 0.0, "delta": 0.0}) + g.incidence({"mu": 5.0, "Z": 0.0, "nu": 0.0, "delta": 0.0})