Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ Note any unreleased items inside the comment here. Not visible until release.
### Fixed

- Kappa equivalent-Eulerian chi axis now matches fourcv/fourch/psic. (#284)
- Populate alpha_i/beta_out/psi/omega output extras after `forward()`. (#292)

### Maintenance

Expand Down
7 changes: 7 additions & 0 deletions docs/source/geometries/psic.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,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̂ (surface normal) |
| **Extras (output)** | alpha_i (incidence angle), beta_out (exit angle) |

### `fixed_beta_out_vertical`

Expand All @@ -128,6 +129,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̂ (surface normal) |
| **Extras (output)** | alpha_i, beta_out |

### `alpha_eq_beta_vertical`

Expand All @@ -139,6 +141,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̂ (surface normal) |
| **Extras (output)** | alpha_i, beta_out |

### `fixed_psi_vertical`

Expand Down Expand Up @@ -175,6 +178,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̂ (surface normal) |
| **Extras (output)** | alpha_i, beta_out |

### `fixed_omega_vertical`

Expand Down Expand Up @@ -294,6 +298,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̂ (surface normal) |
| **Extras (output)** | alpha_i, beta_out |

### `fixed_beta_out_horizontal`

Expand All @@ -306,6 +311,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̂ (surface normal) |
| **Extras (output)** | alpha_i, beta_out |

### `alpha_eq_beta_horizontal`

Expand All @@ -317,6 +323,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̂ (surface normal) |
| **Extras (output)** | alpha_i, beta_out |

### `fixed_psi_horizontal`

Expand Down
91 changes: 91 additions & 0 deletions src/ad_hoc_diffractometer/forward.py
Original file line number Diff line number Diff line change
Expand Up @@ -459,9 +459,100 @@ def compute_forward(

solutions = _solve_constraint_set(geometry, Q_phi, ttheta_deg, mode)
_validate_solutions(solutions, mode, geometry)
_populate_output_extras(geometry, mode, solutions)
return solutions


# ---------------------------------------------------------------------------
# Output-extras population (issue #292)
# ---------------------------------------------------------------------------


# Map of output-slot key → (callable(geometry, angles) → float, friendly label).
# 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")


def _populate_output_extras(
geometry: AdHocDiffractometer,
mode,
solutions: list[dict[str, float]],
) -> None:
"""Populate output-slot extras (alpha_i, beta_out, 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
in ``mode.extras`` for derived angles that the forward solver does not
constrain directly. Before this hook those slots remained at their YAML
default of ``None`` even after a successful ``forward()`` call. This
function fills each declared slot with a list of values aligned with
``solutions`` (one float per solution), computed via the corresponding
helper in :mod:`ad_hoc_diffractometer.reference`.

Behavior
--------
* 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
``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.
"""
if mode is None or not getattr(mode, "extras", None):
return

relevant = [k for k in _OUTPUT_EXTRA_KEYS if k in mode.extras]
if not relevant:
return

# Lazy imports — ``reference`` imports from ``forward``.
from .reference import exit_angle as _exit_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,
"psi": _psi_angle,
"omega": _omega_pseudo,
}

for key in relevant:
compute = computers[key]
values: list[float] = []
failure: Exception | None = None
for angles in solutions:
try:
values.append(float(compute(geometry, angles=angles)))
except Exception as exc: # noqa: BLE001
# Underlying call raised (e.g. missing surface_normal /
# azimuthal_reference, or psi undefined when Q ∥ n_ref).
# Leave the slot unpopulated and record the cause for a
# single debug log below. We break immediately so a
# later good solution does not mask the failure.
failure = exc
values = []
break
if values:
mode.extras[key] = values
else:
# Reset to None so a stale value from a previous forward() call
# is not retained, and report the cause once.
mode.extras[key] = None if solutions else []
if failure is not None:
logger.debug(
"_populate_output_extras: leaving mode.extras[%r] as None "
"for mode %r on geometry %r: %s",
key,
geometry.mode_name,
geometry.name,
failure,
)


# ---------------------------------------------------------------------------
# Constraint-set dispatcher
# ---------------------------------------------------------------------------
Expand Down
Loading