empyrean.evaluate_plan¶
- evaluate_plan(orbit, planned, config=None, orbit_id=None)[source]¶
Rank candidate follow-up observations by how much each would tighten an orbit.
The orbit’s covariance is the information prior; each candidate is folded into it in turn, and the result reports the marginal gain per candidate plus the prior / posterior covariance summary for the campaign as a whole.
Optical candidates contribute sky-plane (RA/Dec) information; radar candidates contribute the line-of-sight range and range-rate that angles-only astrometry cannot supply. A radar candidate’s measurement σ is the Cramér-Rao bound set by the waveform bandwidth and the effective SNR — supply the SNR, or leave
PlannedObservation.radar_snratNoneto have it derived from a link budget over the target’s physical properties.- Parameters:
orbit (
CartesianOrbits|KeplerianOrbits|CometaryOrbits|SphericalOrbits) –Single orbit (exactly one row) carrying a 6×6 Cartesian covariance, with its origin at the Solar System barycenter. The frame is free.
Both requirements are hard: the covariance is consumed as a Cartesian state prior and is never converted, so elements in another representation would be silently reinterpreted. A fit in another basis — a
determine()result is heliocentric, and may be cometary or Keplerian — converts in one call withtransform_coordinates(fit.orbit.coordinates, CartesianCoordinates, origin="SSB"). The origin half of that is a pure translation, so the covariance and every metric below it are unchanged by it.planned (
Sequence[PlannedObservation]) – Candidate observations. Build them withPlannedObservation.optical()/PlannedObservation.radar(). Must not be empty.config (
PlanningConfig|None) – Configuration. Defaults toPlanningConfigdefaults (Standard force model).orbit_id (
str|None) – Label carried through ontoPlanResult.orbit_id. Defaults to the input orbit’s ownorbit_id.
- Return type:
PlanResult- Returns:
PlanResult – The two-row
PlanMetricstable bracketing the campaign, thePlanCandidatestable of per-candidate gains, and thePlanEphemeristable of predicted sky positions.- Raises:
ValueError – The orbit is not exactly one row, carries no covariance, is not Cartesian, is not barycentric, or the candidate list is empty.
RuntimeError – The engine rejected the plan — a singular prior covariance, an unregistered observatory code, a defunct radar dish, a non-positive bandwidth for a delay measurement, or a link budget missing a property it needs. The message names the cause.
Notes
Candidates are evaluated in ascending epoch order regardless of the order they were supplied in, and each is folded into the covariance that already contains every earlier one. The marginal gains are therefore conditional on that sequence: two identical observations do not score identically, and
PlanCandidates.best_by_information_gain()ranks contributions within one campaign rather than standalone candidate value. Evaluate a one-candidate plan per candidate to compare them head to head.What this entry point does not expose, recorded so the omissions are not mistaken for oversights: the engine also offers a non-gravitational planning variant that solves over state ⊕ (A1, A2, A3) and reports the σ(A2) tightening a radar campaign buys, a visibility survey over a time window, batch evaluation across many orbits, and an encounter-B-plane characterization. None of them is reachable from this package.
An orbit carrying non-gravitational parameters — a Yarkovsky fit, say — is accepted here and evaluated state-only. The non-gravitational acceleration still acts in the dynamics, so the predicted trajectory and sky positions account for it, but the solve-for set stays 6×6 (
PlanResult.active_widthreports6), the A1/A2/A3 columns are not folded, and no σ(A2) is reported. The plan prices what the observations buy for the state under that force model, not what they buy for the non-gravitational parameters themselves.This function is the single-object form, while
evaluate_planis the batch name one layer down in the engine. That is deliberate: the C symbolempyrean_evaluate_planhas carried single-object semantics since v0.7.0 and is frozen, and this package mirrors the C ABI it wraps. If a batch form is exposed later it follows the migration precedentempyrean.transform_coordinates()set — the batch takes the plain name and the single-object form gains a_singlesuffix — rather than renaming this function out from under callers now.Examples
Two optical nights and one Goldstone radar run against a fitted orbit, shifted to the barycentre first:
>>> from empyrean import ( ... CartesianCoordinates, ... CartesianOrbits, ... PlannedObservation, ... evaluate_plan, ... transform_coordinates, ... ) >>> coords = transform_coordinates( ... fit.orbit.coordinates, CartesianCoordinates, origin="SSB" ... ) >>> orbit = fit.orbit.set_column("coordinates", coords) >>> t0 = float(coords.epoch.to_numpy()[0]) >>> plan = evaluate_plan( ... orbit, ... [ ... PlannedObservation.optical(t0 + 30.0, "F51", (0.2, 0.2)), ... PlannedObservation.optical(t0 + 31.0, "F51", (0.2, 0.2)), ... PlannedObservation.radar( ... t0 + 45.0, ... radar_bandwidth_hz=1.0e5, ... radar_freq_resolution_hz=0.1, ... radar_snr=50.0, ... ), ... ], ... ) >>> plan.metrics.stage.to_pylist() ['prior', 'posterior'] >>> before = plan.metrics.prior().position_sigma_km[0].as_py() >>> after = plan.metrics.posterior().position_sigma_km[0].as_py() >>> after <= before True