CustomMeasurement#

class CustomMeasurement(name: str, instrument: Instrument, observable, sigma: ArrayWUnits = None, units=None, dimension: int = 1, parameters: list | None = None, partial_by_position=None, partial_by_velocity=None, depends_on_position: bool | None = None, depends_on_velocity: bool | None = None, position_step: float | None = None, velocity_step: float | None = None, state_definition=None, sequence_definition=None)#

Bases: Measurement

User-supplied measurement model, defined entirely from a script.

CustomMeasurement is the observation-side counterpart of CustomDynamicModel: it turns a plain Python function into a first-class Scarabaeus measurement, so a new observable — and any biases or calibration terms it carries — can be added and estimated without changing the library source.

The model computes

\[y = h(\mathbf{r}_\text{rel},\, \dot{\mathbf{r}}_\text{rel},\, \mathbf{p},\, t)\]

where \(\mathbf{r}_\text{rel}\) is the target position relative to the observing instrument and \(\mathbf{p}\) are the declared CustomParameter objects. It supplies the corresponding row(s) of the measurement Jacobian

\[\tilde{H} = \left[ \frac{\partial h}{\partial \mathbf r},\; \frac{\partial h}{\partial \dot{\mathbf r}},\; \frac{\partial h}{\partial \mathbf p} \right]\]

with a zero block for every state component the model does not depend on. Any Jacobian not given analytically is evaluated by central finite differences of the user function, so a working model needs only observable.

Sign convention. The relative state is r_target - r_instrument. The Jacobian is therefore +dh/dr for the target’s position column and -dh/dr for the observer’s, matching the built-in models.

Calling convention. Every user callable is invoked with keyword arguments; declare only the ones you need, in any order:

position

(3,) target position relative to the instrument, km

velocity

(3,) relative velocity, km/s

relative_state

(6,) the two stacked

epoch

measurement epoch, TDB seconds past J2000

params

dict of this model’s parameters at their current estimated values (or their defaults)

target

the observed body

instrument

the observing instrument

frame

reference Frame

model

this CustomMeasurement instance

A callable declaring **kwargs receives all of them.

Parameters:
namestr

Name of the measurement model.

instrumentInstrument

Observing instrument (ground station, camera, …).

observablecallable

Returns the computed measurement: a scalar, or a (dimension,) array for a multi-component observable. Required.

sigmaArrayWUnits

Measurement noise standard deviation, also used to generate noise.

unitsUnits, optional

Units of the observable. Defaults to the units of sigma.

dimensionint, optional

Number of components the observable returns. Defaults to 1.

parameterslist, optional

Solve-for parameters this measurement depends on, as CustomParameter objects or plain names. Their partial callable, when supplied, is \(\partial h / \partial p\) with shape (dimension, p_dim).

partial_by_positioncallable, optional

\(\partial h / \partial \mathbf r\) as (dimension, 3). Defaults to a central finite difference of observable.

partial_by_velocitycallable, optional

\(\partial h / \partial \dot{\mathbf r}\) as (dimension, 3). Defaults to a central finite difference of observable.

depends_on_position, depends_on_velocitybool, optional

Whether the observable depends on relative position / velocity. Inferred from the signature of observable when None.

position_stepfloat, optional

Absolute finite-difference step on position, km. Defaults to 1e-6 * max(|r_i|, 1) per component.

velocity_stepfloat, optional

Absolute finite-difference step on velocity, km/s. Defaults to 1e-9 * max(|v_i|, 1) per component.

state_definitionlist, optional

State layout used to build the Jacobian columns. Normally supplied by update_reference_state().

sequence_definitionMissionSequence, optional

Sequence whose per-leg state definitions are used instead.

Attributes:
AUX_KEYS
component_labels

Names of the two components of a vector observable.

computed_measurements_dict

Auxiliary TRK-2-34 tracking-data dictionary needed for the generation of the computed measurements of a real radiometric model.

default_dataset_name

Dataset name used when the caller does not supply one.

depends_on_position

True when the observable is a function of relative position.

depends_on_velocity

True when the observable is a function of relative velocity.

dimension

Number of components the observable returns.

dl_station_cal

Downlink station calibration [RU] written into simulated auxiliary data.

dsn_bands

DSN frequency-band parameters of this model, a read-only mapping from band letter to DSNBand.

epoch_sec

Observation epochs expressed as seconds of day.

instrument

The instrument.

media_corrections

Tropospheric and ionospheric path-delay model, or None if not set.

name

The name of the model.

parameters

Declared solve-for parameters, keyed by name.

ramp_entries

Number of entries of a simulated ramp table, at least 2.

ramp_margin

Span [s] a simulated ramp table extends before the first and after the last observation.

ramp_rate

Uplink frequency ramp rate [Hz/s] of the ramp table the default_*_aux builders simulate.

ramp_type

SFDU 9 ramp_type code of the segments of a simulated ramp table.

rcv_time_tag_delay

Receive time-tag delay [s] written into simulated auxiliary data.

sigma

The standard deviation of the measurement model.

solar_corona_coefficient

Coefficient of the solar-corona group delay [us GHz^2 m^2] of a real model built with solar_corona=True.

transponder_delay

Spacecraft transponder delay [RU] written into simulated auxiliary data by the default_*_aux builders.

ul_station_cal

Uplink station calibration [RU] written into simulated auxiliary data.

units

Units of the computed measurement.

Methods

align_aux_to_epochs(source, key, target[, ...])

Sample a TRK-2-34 record at the epochs of another record by time tag.

compute_partials(target, epoch_array, frame)

Stacks together measurement _partials for an epoch array at different epochs.

computed_measurements(target, epoch_array, ...)

Evaluate the user observable over an epoch array.

generate_measurement_dataset(dataset_name, ...)

Generates a MeasurementDataSet object that can be used by filters downstream.

observed_measurements(file_name, meas_name, ...)

Reads measurements from a .json file.

residuals(observed_meas, computed_meas)

Generates the measurement model's residuals given observed and computed ArrayWFrames.

resolve_elevation_mask(elevation_mask)

Normalise an elevation mask to degrees.

rtlt_grad_pos_exact(n_hat_dn, n_hat_up, ...)

Exact round-trip light-time gradient with respect to the spacecraft position.

rtlt_grad_station_exact(n_hat_dn, n_hat_up, ...)

Exact round-trip light-time gradients with respect to the station position.

rtlt_leg_weights(n_hat_dn, n_hat_up, v_sc, ...)

Sensitivity of the round-trip light time to a delay inside each leg's solution.

set_pass_epochs(pass_epochs)

Enable per-pass biases.

set_trajectory(trajectory)

Attach the reference trajectory supplying the STMs.

transponder_delay_seconds(delay_RU, ...)

Convert a spacecraft transponder delay from range units to seconds.

update_reference_state(state_vector)

Attach the estimated state the model should read its parameters from.

validate_aux(aux[, label])

Check that an auxiliary-data dictionary is internally consistent.

write_observed_measurements(target, ...)

Generates synthetic measurements and write them as a .json file.

See also

scarabaeus.CustomParameter

Declaration of an estimable custom parameter.

scarabaeus.CustomDynamicModel

The dynamics-side counterpart.

scarabaeus.RangeIdeal

Built-in model this one mirrors structurally.

Notes

Positions and velocities are handed over in km and km/s. The observable is returned in units (or the units of sigma).

Examples

Range with an estimated station bias, written from a script:

import numpy as np
import scarabaeus as scb

km = scb.Units.get_units("km")
bias = scb.CustomParameter("station_bias", units=km, default=0.0)

def biased_range(position, params):
    return np.linalg.norm(position) + params["station_bias"][0]

model = scb.CustomMeasurement(
    "GS1 Custom Range",
    instrument = gs,
    observable = biased_range,
    sigma      = scb.ArrayWUnits(1e-3, km),
    parameters = [bias],
)

The bias is estimated by adding it to the state definition:

state_def = (scb.StateDefinition()
    .position(sc, pos_0)
    .velocity(sc, vel_0)
    .custom_param("station_bias", gs, scb.ArrayWFrame(0.0, km, None))
)
static align_aux_to_epochs(source: dict, key: str, target: dict, invalid=None) → ndarray#

Sample a TRK-2-34 record at the epochs of another record by time tag.

Each SFDU record of a tracking file has its own cadence (e.g. the uplink z-height correction every second, the ranging aux at the ranging cadence), so its rows do not correspond to the measurement rows. The value in force at each target epoch is the latest source entry at or before it (zero-order hold); target epochs earlier than the whole source take its first entry.

Parameters:
sourcedict

Record holding year, doy, sec and key.

keystr

Field of source to sample.

targetdict

Record holding the year, doy, sec time tags to sample at, typically the measurement record (e.g. trk["2W_doppler"]).

invalidfloat, optional

Source entries equal to this value are skipped (e.g. 0 for the uplink frequency of rows with no uplink).

Returns:
np.ndarray

source[key] at each target epoch, of length len(target["sec"]).

Raises:
Measurement.AuxDataError

If source holds no valid entry of key.

static resolve_elevation_mask(elevation_mask: ArrayWUnits | float) → float#

Normalise an elevation mask to degrees.

Parameters:
elevation_maskArrayWUnits or float

Minimum elevation angle. An ArrayWUnits is converted from its own units, so radians work as well as degrees. A bare number is taken to be in degrees, which is what the parameter has always meant and what every default (10.0) is.

Returns:
float

The mask in degrees.

Raises:
ElevationMaskError

If the mask is not a number or an angle, or is outside 0 to 90 degrees. Also if it is a bare number that is not a whole number of degrees and is no more than pi/2: 0.17 could be 0.17 deg or 10 deg meant in radians, and reading it the wrong way silently keeps or drops the observations near the horizon. Such a mask has to carry its units. A larger bare number is read as degrees, since as radians it would be above 90 deg and be refused anyway.

Examples

Measurement.resolve_elevation_mask(10.0)                      # 10.0
Measurement.resolve_elevation_mask(ArrayWUnits(0.2, rad))     # 11.459...
Measurement.resolve_elevation_mask(ArrayWUnits(0.5, deg))     # 0.5
Measurement.resolve_elevation_mask(0.5)       # ElevationMaskError
static rtlt_grad_pos_exact(n_hat_dn: ndarray, n_hat_up: ndarray, v_sc: ndarray, v_gs: ndarray, c: float) → ndarray#

Exact round-trip light-time gradient with respect to the spacecraft position.

Chains Moyer (2000) Eqs. 12-12 (downleg) and 12-17 (upleg) through the reflection time \(t_2\), holding the reception time \(t_3\) fixed, so that \(\rho = t_3 - t_1\) and \(\partial \rho / \partial \mathbf{r}_{SC} = -\partial t_1 / \partial \mathbf{r}_{SC}\) [s/km]. With the Eq. 8-59/8-60 definitions, the spacecraft transmits on the downleg and the station on the upleg:

\[ \begin{align}\begin{aligned}\dot{p}_{23} = \hat{\mathbf{r}}_{23} \cdot \mathbf{v}_{SC} / c = -\hat{\mathbf{n}}_{dn} \cdot \mathbf{v}_{SC} / c, \qquad \dot{p}_{12} = \hat{\mathbf{r}}_{12} \cdot \mathbf{v}_{GS} / c = \hat{\mathbf{n}}_{up} \cdot \mathbf{v}_{GS} / c\\\dot{r}_{12} = \hat{\mathbf{n}}_{up} \cdot (\mathbf{v}_{SC} - \mathbf{v}_{GS}) / c, \qquad \dot{r}_{12} + \dot{p}_{12} = \hat{\mathbf{n}}_{up} \cdot \mathbf{v}_{SC} / c\end{aligned}\end{align} \]

where the station terms cancel in the last sum. The gradient is then

\[\frac{\partial t_2}{\partial \mathbf{r}_{SC}} = -\frac{\hat{\mathbf{n}}_{dn}}{c\,(1 - \dot{p}_{23})}, \qquad \frac{\partial \rho}{\partial \mathbf{r}_{SC}} = -\frac{(1 - \dot{r}_{12} - \dot{p}_{12})\, \partial t_2 / \partial \mathbf{r}_{SC} - \hat{\mathbf{n}}_{up} / c} {1 - \dot{p}_{12}}\]
Parameters:
n_hat_dnnumpy.ndarray, shape (3,)

Unit vector GS to SC on the downleg, \(\hat{\mathbf{n}}_{dn} = (\mathbf{r}_{SC}(t_2) - \mathbf{r}_{GS}(t_3)) / \rho_{dn}\).

n_hat_upnumpy.ndarray, shape (3,)

Unit vector GS to SC on the upleg, \(\hat{\mathbf{n}}_{up} = (\mathbf{r}_{SC}(t_2) - \mathbf{r}_{GS}(t_1)) / \rho_{up}\).

v_scnumpy.ndarray, shape (3,)

Spacecraft velocity at \(t_2\) [km/s], in the space-time frame the light time is solved in (see _light_time_velocity()).

v_gsnumpy.ndarray, shape (3,)

Ground station velocity at \(t_1\) [km/s], in the same frame.

cfloat

Speed of light [km/s].

Returns:
numpy.ndarray, shape (3,)

\(\partial \rho / \partial \mathbf{r}_{SC}\) [s/km].

References

[1]

Moyer, T. D. (2000). Formulation for Observed and Computed Values of Deep Space Network Data Types for Navigation. JPL Publication 00-7.

static rtlt_grad_station_exact(n_hat_dn: ndarray, n_hat_up: ndarray, v_sc: ndarray, v_gs: ndarray, c: float) → tuple#

Exact round-trip light-time gradients with respect to the station position.

The station enters at reception \(t_3\) and at transmission \(t_1\), through the same Moyer (2000) 12-12/12-17 chain as rtlt_grad_pos_exact(). Displacing the receiver at \(t_3\) moves the reflection time \(t_2\) (Eq. 12-12, with the station as the receiver), and the upleg carries that change to \(t_1\) through its own \(\dot{p}_{12}\) denominator (Eq. 12-17); displacing the transmitter at \(t_1\) enters the upleg only:

\[\frac{\partial \rho}{\partial \mathbf{r}_{GS}(t_3)} = -\frac{(1 - \dot{r}_{12} - \dot{p}_{12})\,\hat{\mathbf{n}}_{dn}} {c\,(1 - \dot{p}_{23})(1 - \dot{p}_{12})}, \qquad \frac{\partial \rho}{\partial \mathbf{r}_{GS}(t_1)} = -\frac{\hat{\mathbf{n}}_{up}}{c\,(1 - \dot{p}_{12})}\]

with \(\dot{p}\) and \(\dot{r}\) as defined in rtlt_grad_pos_exact(): each leg’s range over c weighted by rtlt_leg_weights(). Their sum is -rtlt_grad_pos_exact(...): a common translation of station and spacecraft leaves the light time unchanged. Dropping the denominators, i.e. using \(-\hat{\mathbf{n}}/c\) on each leg, is off by the transmitters’ velocities over c, about 1e-4 relative with heliocentric velocities.

Parameters:
n_hat_dnnumpy.ndarray, shape (3,)

Unit vector GS to SC on the downleg.

n_hat_upnumpy.ndarray, shape (3,)

Unit vector GS to SC on the upleg.

v_scnumpy.ndarray, shape (3,)

Spacecraft velocity at \(t_2\) [km/s].

v_gsnumpy.ndarray, shape (3,)

Ground station velocity at \(t_1\) [km/s].

cfloat

Speed of light [km/s].

Returns:
grad_t3numpy.ndarray, shape (3,)

\(\partial \rho / \partial \mathbf{r}_{GS}(t_3)\) [s/km], in the frame of the input vectors.

grad_t1numpy.ndarray, shape (3,)

\(\partial \rho / \partial \mathbf{r}_{GS}(t_1)\) [s/km], in the same frame.

See also

rtlt_grad_pos_exact

definitions of the inputs.

static rtlt_leg_weights(n_hat_dn: ndarray, n_hat_up: ndarray, v_sc: ndarray, v_gs: ndarray, c: float) → tuple#

Sensitivity of the round-trip light time to a delay inside each leg’s solution.

Uses the Moyer (2000) 12-12/12-17 chain of rtlt_grad_pos_exact(). A delay \(\delta\tau_{23}\) in the downleg equation moves the reflection time \(t_2\), and the upleg carries that change to \(t_1\); a delay \(\delta\tau_{12}\) in the upleg equation moves \(t_1\) only:

\[\frac{\partial \rho}{\partial \tau_{23}} = \frac{1 - \dot{r}_{12} - \dot{p}_{12}}{(1 - \dot{p}_{23})(1 - \dot{p}_{12})}, \qquad \frac{\partial \rho}{\partial \tau_{12}} = \frac{1}{1 - \dot{p}_{12}}\]

with \(\dot{p}\) and \(\dot{r}\) as defined in rtlt_grad_pos_exact(). Both are 1 to within the transmitters’ velocities over c, about 1e-4 with heliocentric velocities. They apply to what the iteration solves for, the geometric range and the Shapiro delay, and not to the media, corona and electronic delays, which are added after it converges.

Parameters:
n_hat_dnnumpy.ndarray, shape (3,)

Unit vector GS to SC on the downleg.

n_hat_upnumpy.ndarray, shape (3,)

Unit vector GS to SC on the upleg.

v_scnumpy.ndarray, shape (3,)

Spacecraft velocity at \(t_2\) [km/s].

v_gsnumpy.ndarray, shape (3,)

Ground station velocity at \(t_1\) [km/s].

cfloat

Speed of light [km/s].

Returns:
w_dnfloat

\(\partial \rho / \partial \tau_{23}\), dimensionless.

w_upfloat

\(\partial \rho / \partial \tau_{12}\), dimensionless.

See also

rtlt_grad_pos_exact

definitions of the inputs.

static transponder_delay_seconds(delay_RU: float, reference_frequency: float, range_unit: float) → float#

Convert a spacecraft transponder delay from range units to seconds.

The delay is divided by range_unit * reference_frequency, the rate of the clock the delay is counted against.

Parameters:
delay_RUfloat

Transponder delay as the mission specifies it [RU].

reference_frequencyfloat

The band’s range-unit reference frequency [1/s], i.e. Measurement.DSNBand.ru_reference_frequency.

range_unitfloat

The range unit, from the pass’s exciter scalars.

Returns:
float

The delay [s].

Notes

Pass the band’s reference frequency, not its uplink frequency. The two differ by 18% in X band, and the delay enters the round-trip light time directly rather than acting as a scale factor, so substituting the uplink makes the delay 2.4 us longer – 1.4 km of two-way range. Tried on real tracking data, that drove the sequential-ranging residuals from 55-206 RU to 2330-2694 RU, so the reference frequency is what the data supports. Why a downlink-scale reference is the right one is an open question; see the attribute’s own note.

static validate_aux(aux: dict, label: str = '') → None#

Check that an auxiliary-data dictionary is internally consistent.

An aux dict describes two things sampled on two different grids: one value per observation, and one entry per ramp-table segment. Everything in each group has to be the same length, and a value meant to be per-observation has to be a sequence rather than a bare number.

Nothing checks that on the way in otherwise, and the failures it prevents surface far from their cause:

  • Mismatched lengths reach RampTableManager.integrate(), which indexes ramp_freq with an index derived from ramp_sec and raised IndexError: index 9 is out of bounds for axis 0 with size 5 from inside the forward model.

  • A ragged per-observation group reaches write_observed_measurements(), whose own check then refuses to write the file – correct, but one step later than here.

Parameters:
auxdict

The dictionary to check, as a default_*_aux helper or _read_aux() produces it.

labelstr, optional

Named in the error, to say which dictionary was wrong.

Raises:
TypeError

If aux is not a dict. None is reported specifically, since that means the model was never given its auxiliary data.

ValueError

If the per-observation group or the ramp group is ragged. The message groups the keys by the length they have, since the odd one out is usually obvious once they are laid side by side.

Examples

aux = doppler_model.default_doppler_aux(t0, tf, N=20, band="S")
scb.Measurement.validate_aux(aux, label="DSS-25 Range Rate")
compute_partials(target: Spacecraft, epoch_array: EpochArray, frame: Frame = J2000 (0 - SOLAR SYSTEM BARYCENTER)) → list#

Stacks together measurement _partials for an epoch array at different epochs.

Parameters:
targetSpacecraft

The target spacecraft.

epoch_arrayEpochArray

The epochs.

frameFrame, optional

The reference frame. Defaults to a J2000 Frame object.

Returns:
_partialslist

A list with all the _partials evaluated at different epochs in the epoch_array.

computed_measurements(target, epoch_array: EpochArray = None, epoch_start: EpochArray = None, epoch_end: EpochArray = None, tstep: float = 1, frame: Frame = J2000 (0 - SOLAR SYSTEM BARYCENTER), noisy: bool = False, pass_epochs: list[list] | None = None)#

Evaluate the user observable over an epoch array.

Parameters:
targetBody

The observed body.

epoch_arrayEpochArray, optional

Epochs at which to evaluate. Overrides the start/end/step form.

epoch_start, epoch_endEpochArray, optional

Bounds used when epoch_array is not given.

tstepfloat, optional

Step in seconds for the start/end form. Defaults to 1.

frameFrame, optional

Reference frame of the relative state. Defaults to J2000.

noisybool, optional

Add Gaussian noise of standard deviation sigma.

pass_epochslist of list, optional

Pass grouping, forwarded to set_pass_epochs().

Returns:
tuple

(t1, t2, t3, measurements) — the three time tags (identical for an ideal model) and the computed measurements as an ArrayWFrame.

generate_measurement_dataset(dataset_name: str | None = None, target: Body = None, observed_meas: tuple | list | None = None, epochs: EpochArray | None = None, frame: Frame = J2000 (0 - SOLAR SYSTEM BARYCENTER), noisy: bool = False) → list[MeasurementDataSet]#

Generates a MeasurementDataSet object that can be used by filters downstream.

Parameters:
dataset_namestr, optional

The name of the MeasurementDataSet, which becomes the residual key in solution.json. Defaults to default_dataset_name — "<instrument>_<measurement_type>".

targetSpacecraft

The target spacecraft.

observed_meastuple or list, optional

Observed measurements as (epoch_array, meas_sec, meas_obs, outlier_flag). The computed values are evaluated at epoch_array. Give this or epochs, not both. Defaults to None.

epochsEpochArray, optional

Epochs to simulate the measurements at, with no outliers flagged. Give this or observed_meas, not both. Defaults to None.

frameFrame, optional

The reference frame. Defaults to a J2000 Frame object.

noisybool, optional

Indicates if noise is added to the measurements or not. Defaults to False.

Returns:
mds_listlist[MeasurementDataSet]

A list of MeasurementDataSet objects representing the measurements with their key properties to be used by a filter.

Raises:
ValueError

If neither or both of observed_meas and epochs are given, if observed_meas carries fewer than four entries (the fourth is the outlier-flag array, without which the observations cannot be kept on one index set with their flags), or if the model has more than two components.

Notes

This is the single bridge from a measurement model to the estimator: fit() and compute() both come through here, and MeasurementDataSet is populated nowhere else. It pairs each observation with its computed value, residual, partial and sigma on one consistent set of indices, which is what lets several models with different epochs and different observables be combined into one filter update.

That is also why it, rather than the caller, unpacks t1, t2, t3 from computed_measurements(). The estimator needs the epochs alongside the observable – t2 to match measurements across models, t1 to detrend residuals during outlier rejection – so they have to survive the trip into the dataset. See computed_measurements() for what each epoch means and why a single-epoch observable collapses all three.

The MeasurementDataSet output is generated in 6 steps:

  1. Computed measurements

  2. Partials

  3. Residuals

  4. Sigmas

  5. Outlier flag

  6. Pack everything in a list

  7. Pack the list in a MeasurementDataSet object

observed_measurements(file_name, meas_name: str = 'meas_sim', units: Units = unitless, frame: Frame = J2000 (0 - SOLAR SYSTEM BARYCENTER), **read_kwargs) → ObsData#

Reads measurements from a .json file.

Parameters:
file_namestr

The filename of the .json file containig the measurement information.

meas_namestr, optional

The name of the measurement data to access from the dictionary. Defaults to 'meas_sim'. Files written before the rename use 'meas_ideal'; either name resolves to whichever block the file actually contains, with a DeprecationWarning on the fallback.

unitsUnits, optional

Units to be used to write the output AWU. Defaults to unitless.

frameFrame, optional

Frame to be used to write the output AWF. Defaults to a J2000 Frame object.

**read_kwargs

Options for the model’s own _read_aux(), forwarded unchanged. Models with no auxiliary data to read ignore them.

This is where sc_transponder_delay_RU goes for the DSN radiometric models, which is the only such option today. It is not a named parameter here because this signature is shared by every measurement model, optical ones included.

Returns:
ObsData

A ObsData named tuple with five fields, addressable by name or index:

  • [0] meas_time_etEpochArray

    Observation epochs. Built from the file’s UTC station tag via YDS2et(); see the comment at the conversion below for why this carries SPICE’s time model deliberately.

  • [1] meas_secnumpy.ndarray

    The observation times as seconds of day, as given in the file.

  • [2] meas_obsArrayWFrame

    The observed quantities, with units and frame attached.

  • [3] meas_outliersnumpy.ndarray

    Per-observation outlier flags, 1 = reject. Seeded from the file’s own outlier_flag and additionally set here for records carrying the -1 no-data sentinel and for the rows the model knows to be invalid (_flag_invalid_rows()). Downstream this is the authoritative flag array: it is what the filter, outlier_flags() and the editors gate on, and generate_measurement_dataset() writes into it when the forward model cannot compute an observation.

  • [4] aux_dictdict

    Ancillary per-observation data read by the model’s _read_aux (ramp tables, transponder ratios, electronic delays, …). None for models that need none.

Notes

The writing of the json assumes or requires units and frames.

Every row in the file is returned, including rows flagged bad. Nothing is dropped here, so the observations, the aux_dict and any later outlier rejection all stay on one consistent set of indices.

residuals(observed_meas: ArrayWFrame, computed_meas: ArrayWFrame) → ArrayWFrame#

Generates the measurement model’s residuals given observed and computed ArrayWFrames.

Parameters:
observed_measArrayWFrame

The observed measurements values (O).

computed_measArrayWFrame

The computed measurements values (C).

Returns:
residualsArrayWFrame

AWF with the residual O-C.

set_pass_epochs(pass_epochs: list[list] | None)#

Enable per-pass biases.

Parameters:
pass_epochslist[list[float]] | None

Observation epochs grouped by pass, e.g. [[t0, t1], [t10, t11]]. Pass None to revert to global-bias mode.

set_trajectory(trajectory)#

Attach the reference trajectory supplying the STMs.

Read only by the analytical light-time partials, which map a deviation between the endpoints of an observable (see _short_arc_stm()).

Parameters:
trajectoryTrajectory

Trajectory carrying the STMs. Must span every epoch the model evaluates.

update_reference_state(state_vector: StateArray)#

Attach the estimated state the model should read its parameters from.

Call this once per iteration, before generate_measurement_dataset(). The model then pulls the entries belonging to this instrument, matched on its SPICE ID:

  • meas_bias_ideal_*, as an ArrayWUnits.

  • gs_delta_location_ECEF_*, as an ArrayWFrame.

Parameters:
state_vectorStateArray

The current estimated state. Its layout is also recorded, so that the partials can build a column for every estimated parameter beyond position and velocity.

write_observed_measurements(target: Spacecraft, epoch_array: EpochArray = None, epoch_start: EpochArray = None, epoch_end: EpochArray = None, tstep: float = 1, frame: Frame = J2000 (0 - SOLAR SYSTEM BARYCENTER), noisy: bool = False, file_name: str = 'ideal_measurement', check_visibility: bool = False, elevation_mask: ArrayWUnits | float = 10.0, folder_path_override: str = None, fmt: str = 'json') → None#

Generates synthetic measurements and write them as a .json file. The input of this method encapsulate the ones needed for the “computed_meas” method in each measurement model class.

Parameters:
targetSpacecraft

The target spacecraft for which the range measurement is to be computed.

epoch_arrayEpochArray, optional

An array of epochs (times) at which the range measurements should be computed. If provided, overrides epoch_start, epoch_end, and tstep.

epoch_startEpochArray, optional

The starting epoch for the range measurement computations. Required if epoch_array is not provided.

epoch_endEpochArray, optional

The ending epoch for the range measurement computations. Required if epoch_array is not provided.

tstepfloat, optional

The time step, in seconds, between consecutive range measurements. If epoch_array is not provided. Defaults to 1 second.

frameFrame , optional

The reference frame in which the range computation is performed. Defaults to None.

noisybool , optional

Whether to add noise to the computed range measurement. Defaults to False.

file_namestr, optional

The filename of the JSON in which the measurement is saved, Defaults to 'ideal_measurement'.

check_visibilitybool, optional

When True, epochs where the instrument cannot observe the target are removed before generating measurements. The specific check is instrument-dependent: GroundStation uses an elevation-angle test; Camera uses a FOV projection test. Defaults to False.

elevation_maskArrayWUnits or float, optional

Minimum elevation angle used by the GroundStation visibility check. An ArrayWUnits is converted from its own units; a bare number is read as degrees (see resolve_elevation_mask(), which refuses a bare one that could be radians). Ignored for Camera instruments. Only relevant when check_visibility=True. Defaults to 10.0 degrees.

folder_path_overridestr, optional

If provided, the JSON file is saved directly to this path instead of the default data/measurements/radiometric or data/measurements/optical folder. The directory is created automatically if it does not exist. Defaults to None.

fmt{“json”, “pickle”}, optional

On-disk format; selects the .json or .pkl extension appended to file_name. Read back transparently by observed_measurements(). Defaults to "json".

Returns:
None
AUX_KEYS = None#
DL_STATION_CAL = 850000.0#

Default downlink station calibration [RU] of simulated auxiliary data, the starting dl_station_cal of a model.

Representative, not physical: real passes carry the station value.

DSN_BANDS = {'S': Measurement.DSNBand(name='S', uplink_multiplier=221, downlink_multiplier=240, turnaround_num=240, turnaround_den=221, exciter_scalar_num=221.0, exciter_scalar_den=7072.0, uplink_frequency=2110362223.0 Hz, ru_reference_frequency=2303391185.181818 Hz), 'X': Measurement.DSNBand(name='X', uplink_multiplier=749, downlink_multiplier=880, turnaround_num=880, turnaround_den=749, exciter_scalar_num=221.0, exciter_scalar_den=23968.0, uplink_frequency=7152313596.9977 Hz, ru_reference_frequency=8445767679.0 Hz)}#

Default DSN frequency-band parameters, keyed by band letter, each a DSNBand. A model starts with these as dsn_bands.

Supported bands are 'X' and 'S'. 'X' holds the values the measurement models used as literals before they were collected here, so it is the default everywhere and reproduces the previous numbers exactly.

[1]

Moyer, T. D., Formulation for Observed and Computed Values of Deep Space Network Data Types for Navigation, JPL Publication 00-7, Monograph 2, Deep Space Communications and Navigation Series, Chapter 13.

[2]

DSN Telecommunications Link Design Handbook, 810-005, Module 201, Frequency and Channel Assignments.

LIGHT_TIME_FRAMES = ('barycentric', 'geocentric')#
LIGHT_TIME_SETTINGS = {'et_tai_iterations': 2, 'max_iterations': 10, 'tolerance': 1e-12}#

Default settings of the round-trip light-time solution of the real measurement models, which take a dictionary with any of these keys as light_time_settings. They apply to both solvers, _compute_precise_RTLT() and _compute_geocentric_RTLT().

  • "tolerance" [s]: each leg (t3 to t2, then t2 to t1) is corrected with Moyer 2000 eq. 8-72 until the correction is no larger than this. 1e-12 s is 0.3 mm of one-way path. The correction cannot fall much below the float spacing of the one-way light time: 9.1e-13 s from 4096 s (8.2 AU), 1.8e-12 s from 8192 s (16 AU). A tolerance under that floor makes every leg run to "max_iterations", so raise it for distant spacecraft.

  • "max_iterations": iteration cap of each leg. A leg that reaches it keeps its last estimate and does not raise.

  • "et_tai_iterations": evaluations of the ET - TAI series (Moyer 2000 eq. 2-26) when the reception time is converted from TAI to ET. Each one is made at the ET the previous one gave. The first starts from the approximate series, eq. 2-27.

[1]

Moyer, T.D. (2000). Formulation for Observed and Computed Values of Deep Space Network Data Types for Navigation. JPL Publication 00-7, Monograph 2, Deep Space Communications and Navigation Series, Sec. 8.3.6 and Eq. 2-26.

PARTIALS_METHODS = ('fd', 'analytical')#
RAMP_AUX_KEYS = ('ramp_sec', 'ramp_day', 'ramp_freq', 'ramp_rate', 'ramp_type')#

Auxiliary-data keys of the ramp table every real radiometric model carries: start second of day, day of year, frequency, rate and type of each ramp segment.

RAMP_ENTRIES = 12#

Default number of entries of a simulated ramp table, the starting ramp_entries of a model.

RAMP_MARGIN = 3600.0 sec#

Default span a simulated ramp table extends before the first and after the last observation, the starting ramp_margin of a model.

The uplink transmit time precedes the first tag by the round-trip light time, so a table that starts at the first tag raises RampCoverageError. One hour clears a spacecraft out to the asteroid belt.

RAMP_RATE = 0.2826 sec^-2#

Default uplink frequency ramp rate of a simulated ramp table, the starting ramp_rate of a model.

A representative DSN ramp slope, not a physical constant: real passes carry their measured rate in the ramp table.

RAMP_TYPE = 1#

Default ramp type of a simulated ramp table, the SFDU 9 ramp_type code of a ramped segment; the starting ramp_type of a model.

RAMP_YEAR_KEY = 'ramp_year'#

Auxiliary-data key of the year of each ramp segment. Optional: a table read without it is assumed to lie in the year of the observations.

RCV_TIME_TAG_DELAY = 0.0008 sec#

Default receive time-tag delay of simulated auxiliary data, the starting rcv_time_tag_delay of a model.

Representative, not physical: real passes carry the station value.

TRANSPONDER_DELAY = 1e-05#

Default spacecraft transponder delay [RU] of simulated auxiliary data, the starting transponder_delay of a model.

Representative, not physical: real passes carry the calibrated delay.

UL_STATION_CAL = 5000.0#

Default uplink station calibration [RU] of simulated auxiliary data, the starting ul_station_cal of a model.

Representative, not physical: real passes carry the station value.

property component_labels: tuple[str, str]#

Names of the two components of a vector observable.

A two-component measurement is split into one scalar dataset per component, and these are the suffixes those datasets carry — so they end up as the residual keys in solution.json. Each model sets them in its __init__ beside _measurement_type: AngularIdeal measures ("ra", "dec"), CentroidingIdeal ("sample", "line").

property computed_measurements_dict: dict#

Auxiliary TRK-2-34 tracking-data dictionary needed for the generation of the computed measurements of a real radiometric model. See the computed_measurements_dict parameter of DopplerReal and SequentialRangingReal for the keys each requires.

property default_dataset_name: str#

Dataset name used when the caller does not supply one.

"<instrument>_<measurement_type>", which is the convention the radiometric datasets already read as — DSS-14_RangeIdeal — and now the optical ones too. For a camera-based model the instrument is the camera, so a centroiding dataset comes out as NAC_CentroidingIdeal.

Note that an AngularIdeal observer is the spacecraft, not the camera (its name is resolved through SPICE), so a run that images with several cameras should pass dataset_name explicitly to tell them apart.

property depends_on_position: bool#

True when the observable is a function of relative position.

property depends_on_velocity: bool#

True when the observable is a function of relative velocity.

property dimension: int#

Number of components the observable returns.

property dl_station_cal: float#

Downlink station calibration [RU] written into simulated auxiliary data. Starts as DL_STATION_CAL.

property dsn_bands: MappingProxyType#

DSN frequency-band parameters of this model, a read-only mapping from band letter to DSNBand. Starts as DSN_BANDS.

The band of a real model is looked up here, for the reference frequency that converts the transponder delay to seconds and the range unit of a tracking file without exciter scalars, and so is the band of the default_*_aux builders, which take its turnaround ratio, exciter scalars and uplink frequency.

Set a whole new mapping to change it; its keys are upper-cased.

Examples

X = model.dsn_bands["X"]
model.dsn_bands = {
    **model.dsn_bands,
    "X": dataclasses.replace(X, uplink_frequency=scb.ArrayWUnits(7.18e9, 1 / sec)),
}
property epoch_sec: EpochArray#

Observation epochs expressed as seconds of day.

property instrument: Instrument#

The instrument.

property media_corrections#

Tropospheric and ionospheric path-delay model, or None if not set.

property name: str#

The name of the model.

property parameters: dict#

Declared solve-for parameters, keyed by name.

property ramp_entries: int#

Number of entries of a simulated ramp table, at least 2. Starts as RAMP_ENTRIES.

property ramp_margin: ArrayWUnits#

Span [s] a simulated ramp table extends before the first and after the last observation. Starts as RAMP_MARGIN; see _default_aux_grids() for why it is needed.

Set an ArrayWUnits time, or a bare, non-negative number of seconds.

property ramp_rate: ArrayWUnits#

Uplink frequency ramp rate [Hz/s] of the ramp table the default_*_aux builders simulate. Starts as RAMP_RATE.

Set an ArrayWUnits, converted to 1/s^2, or a bare number in Hz/s.

property ramp_type: int#

SFDU 9 ramp_type code of the segments of a simulated ramp table. Starts as RAMP_TYPE.

property rcv_time_tag_delay: ArrayWUnits#

Receive time-tag delay [s] written into simulated auxiliary data. Starts as RCV_TIME_TAG_DELAY.

Set an ArrayWUnits time, or a bare number of seconds.

property sigma: ArrayWUnits#

The standard deviation of the measurement model.

property solar_corona_coefficient: float#

Coefficient of the solar-corona group delay [us GHz^2 m^2] of a real model built with solar_corona=True. Starts as MediaCorrections.SOLAR_CORONA_DELAY_COEFF; see MediaCorrections._compute_SC_lt.

A fixed change of the delay model. To estimate a correction from the data, estimate solar_corona_scale instead.

station_eps_defaults = {'body_orientation_delta': 0.0001 rad, 'eop_delta_celestial_pole_x': 0.0001 rad, 'eop_delta_celestial_pole_y': 0.0001 rad, 'eop_delta_pole_x': 0.0001 rad, 'eop_delta_pole_y': 0.0001 rad, 'eop_delta_ut1': 1.0 sec, 'gs_delta_location_CYL': 1.0 km, 'gs_delta_location_ECEF': 1.0 km, 'iono_zenith_delay': 10.0 m, 'love_h2': 100.0, 'pole_dec': 0.0001 rad, 'pole_pm': 1e-06, 'pole_ra': 0.0001 rad, 'pole_w0': 0.0001 rad, 'shida_l2': 100.0, 'tropo_zenith_delay': 10.0 m}#

Finite-difference perturbations for station parameters, used only by partials_method="fd" (_compute_partials_with_fd()); every analytical path differentiates the station in closed form. They live in the same eps_dict as the spacecraft dx/dvx entries, keyed by StateDefinition parameter kind, so a model has one perturbation dictionary rather than two. The values are large because an RTLT solution and ramp integration lose significance in a small perturbation; they are numerical choices, not prior sigmas. The light time carries about 1e-13 s of noise per leg (SSB positions near 1.5e8 km), and a unit Love number moves a station by centimetres, so a Doppler fed by a step near one is mostly that noise. The tide and the zenith delays are exactly linear in their parameters, so their steps are as large as needed.

property transponder_delay: float#

Spacecraft transponder delay [RU] written into simulated auxiliary data by the default_*_aux builders. Starts as TRANSPONDER_DELAY.

Only simulated data: a real pass takes the sc_transponder_delay_RU given to observed_measurements, or else the delay in its tracking file.

property ul_station_cal: float#

Uplink station calibration [RU] written into simulated auxiliary data. Starts as UL_STATION_CAL.

property units#

Units of the computed measurement.

exception DuplicateParameterError#

Bases: ValueError

Raised when the same parameter name is declared twice by one model.

exception ShapeError#

Bases: ValueError

Raised when a user callable returns an array of unexpected shape.