Source code for empyrean.od.disposition
"""What a fit does with each parameter axis: solved, considered, or fixed."""
from __future__ import annotations
import enum
__all__ = ["ParamDisposition"]
[docs]
class ParamDisposition(str, enum.Enum):
"""A parameter axis's treatment in a fit.
Subclasses ``str`` so values serialize directly into the string
columns that carry them, matching the convention
:class:`~empyrean.propagation.tagged_covariance.CovarianceKind` and
``non_grav.model`` already use.
The three are different operations with different mathematics, and
using one where another is meant is silent — each produces a
well-formed covariance:
- :attr:`FIXED` — marginalized out of the prior. Contributes
nothing and changes no number.
- :attr:`SOLVED` — estimated from the data. Occupies a solved slot
and comes back with a posterior variance.
- :attr:`CONSIDERED` — not estimated, but uncertain: its prior
uncertainty reaches the posterior through its measurement
partials (Schmidt-Kalman consider analysis; Tapley, Byron D.,
Schutz, Bob E., and Born, George H., *Statistical Orbit
Determination*, Elsevier Academic Press, 2004, ch. 6).
A considered axis is **not** a safety margin. Under an uncorrelated
prior the correction strictly widens the posterior, but when the
orbit supplies cross terms between the considered axis and the
solved ones the correction is sign-indefinite and the posterior can
come back **tighter**.
No boolean coercion
-------------------
There is deliberately no ``from_bool`` and no truthiness contract
beyond ``str``'s. A disposition is a modelling statement, and a
conversion that turned ``True`` into ``SOLVED`` would let a call
site inherit one rather than state it. Passing ``True`` where a
disposition is expected raises rather than resolving:
>>> ParamDisposition.parse(True)
Traceback (most recent call last):
...
TypeError: parameter disposition must be a string ...
"""
FIXED = "fixed"
SOLVED = "solved"
CONSIDERED = "considered"
[docs]
@classmethod
def parse(cls, value: object) -> ParamDisposition:
"""Parse a wire tag, refusing anything that is not one.
A ``bool`` is refused by name rather than coerced — the whole
point of the tri-state is that "not solved" is two different
answers, and ``False`` cannot say which.
"""
if isinstance(value, cls):
return value
if isinstance(value, bool):
raise TypeError(
"parameter disposition must be a string, not a bool: True/False "
"cannot say whether an unsolved axis is 'considered' (its "
"uncertainty inflates the posterior) or 'fixed' (it contributes "
"nothing). Pass 'solved', 'considered' or 'fixed'."
)
if not isinstance(value, str):
raise TypeError(f"parameter disposition must be a string, got {type(value).__name__}")
try:
return cls(value)
except ValueError:
raise ValueError(
f"unknown parameter disposition {value!r}; expected "
f"{', '.join(repr(m.value) for m in cls)}"
) from None
@property
def is_solved(self) -> bool:
"""Whether this axis is estimated (and so occupies a solved slot)."""
return self is ParamDisposition.SOLVED
@property
def is_considered(self) -> bool:
"""Whether this axis inflates the posterior through consider analysis."""
return self is ParamDisposition.CONSIDERED