Repository navigation
UPDATE 2.1 - #222
Merged
Merged
UPDATE 2.1#222
Conversation
* fix(estimators): make the positional step() and update() overloads fail loudly With seven controls and a seven-row measurement stack the legacy step(control, measurements) call was dispatched as correct(measurements= control, orbital_state=measurements) and ran silently, leaving the control values in the posterior rate. The two-argument form now requires an orbital state (duck-typed on J2000). update(sensors=, os=) without a control vector used to be accepted on the first call and fail on the second; reject it up front. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix(estimators): warn when dt disagrees with the orbital-state time gap A filter built with one dt and driven by a simulation running at another integrates the wrong interval and nothing notices: a 10x mismatch took a 16 degree mean attitude error to 47. predict() now compares its step with the J2000 gap between the two orbital states and warns beyond 10%; identical orbital states (the static-orbit test pattern) carry no time and are ignored. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix(sensors): size scalar error models to the sensor's output length A scalar Noise or Bias on a vector sensor (EarthHorizonSensor(), StarTracker(), StarTrackerQuaternion(noise=Noise(std_noise=1e-3)), any default-constructed vector sensor) produced a 1x1 measurement-noise or bias-PSD block that the measurement stack assembled unchecked; the filters then failed at correct() with a covariance-shape message naming no sensor. Broadcast scalars per axis in Sensor.__init__ and reject any other size mismatch there. Subclassed error models with their own parameterisation (AnisotropicNoise) are left untouched. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix(satellite): reject quaternion star-tracker bias estimation at assembly The rejection lived behind the measurement stack's cached property, so an EstimatedSatellite happily registered a 4-wide sensor-bias block first and the augmented filters raised a misleading process-noise size error at predict(). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix(controller): store est_sat on the base class The remote controller service dereferences controller.est_sat; only two of fifteen controllers set it, so the other thirteen failed remotely with AttributeError inside an XML-RPC fault. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * chore(estimators): DeprecationWarning from the UAKF/SRUAKF alias stubs The stubs silently alias the new UKF/SRUKF, whose constructor differs entirely from the legacy est_sat/J2000/x_hat/P_hat/Q_hat/cross_term/ quat_as_vec interface. Every legacy keyword call raises a clear TypeError, but a two-positional call constructs the new class silently and nothing said the names had moved. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * test(estimators): pin the guard rails (calls, dt gap, error-model sizing, tracker bias, controller, stubs) Also imports warnings in attitude_estimator.py for the dt-gap check. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * estimator speedups, 1.4x for EKF/MEKF, 6x for UKF/SRUKF * added development flag to pyproject * updated pyproject to 0.2.1.dev0 --------- Co-authored-by: t <p@x> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
nscheuer
marked this pull request as draft
October 1, 2026 07:47
* first attempt at test coverage ci * ultra fast orbit tests * removed coverage test which tooks ages to run * tightened orbit sensitivities * reorganized test folder structure, tests not run yet! (#214) * test: place new tests in reorganized test directories
* v1 IGRF choice [needs review] * added theta=0 check and corresponding test * added loading message for magnetic field model * added IGRF coefficients citation in the documentation * fix test case --------- Co-authored-by: Niclas Scheuer <niclas_scheuer@web.de>
…tion (#217) * feat(estimators): TRIAD, QUEST and the q-method for single-frame attitude determination Pure functions on unit-vector pairs (body direction, inertial reference, angular standard deviation per observation), in the package's quaternion convention (scalar first, rot_mat maps body to inertial): - triad(): the two-direction construction, with a linearised covariance when the two standard deviations are given. - quest(): Shuster's QUEST (Newton on the characteristic polynomial, Gibbs vector), solved in four sequential-rotation frames with the best conditioned one kept, so 180 degree attitudes are handled. - q_method(): Davenport's exact 4x4 eigen-solution, used as the oracle. - wahba_covariance(): the small-angle body-frame covariance of the optimal solution, in rad^2; raises when the directions leave a rotation unobservable. - quaternion_from_rotation_matrix(): Shepperd's method (the package had no matrix-to-quaternion helper). Tests: noise-free exactness, QUEST equals the q-method on noisy data, QUEST never worse than TRIAD, 180 degree cases, Monte-Carlo consistency of both covariances, degenerate inputs, and weighting. * fix(estimators): polish QUEST and the q-method with Gauss-Newton steps The closed-form solutions lose accuracy when one direction is weighted far more heavily than the others: with weights 1e6 apart QUEST was off by up to 1e-4 rad on exact data, with 1e10 apart by whole radians, and the q-method's eigenvector degrades the same way. A few Gauss-Newton steps on the Wahba cost (solved by orthogonal factorisation, where only the square root of the weight ratio enters) recover the optimum to machine precision. Both solvers take refine_steps (default 8, 0 for the textbook algorithm) and report the loss of the attitude they return. The covariance's observability check now fires only below the numerical resolution of the information matrix. ---------
…ff a raw measurement vector (#218) * feat(estimators): TRIAD, QUEST and the q-method for single-frame attitude determination Pure functions on unit-vector pairs (body direction, inertial reference, angular standard deviation per observation), in the package's quaternion convention (scalar first, rot_mat maps body to inertial): - triad(): the two-direction construction, with a linearised covariance when the two standard deviations are given. - quest(): Shuster's QUEST (Newton on the characteristic polynomial, Gibbs vector), solved in four sequential-rotation frames with the best conditioned one kept, so 180 degree attitudes are handled. - q_method(): Davenport's exact 4x4 eigen-solution, used as the oracle. - wahba_covariance(): the small-angle body-frame covariance of the optimal solution, in rad^2; raises when the directions leave a rotation unobservable. - quaternion_from_rotation_matrix(): Shepperd's method (the package had no matrix-to-quaternion helper). Tests: noise-free exactness, QUEST equals the q-method on noisy data, QUEST never worse than TRIAD, 180 degree cases, Monte-Carlo consistency of both covariances, degenerate inputs, and weighting. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix(estimators): polish QUEST and the q-method with Gauss-Newton steps The closed-form solutions lose accuracy when one direction is weighted far more heavily than the others: with weights 1e6 apart QUEST was off by up to 1e-4 rad on exact data, with 1e10 apart by whole radians, and the q-method's eigenvector degrades the same way. A few Gauss-Newton steps on the Wahba cost (solved by orthogonal factorisation, where only the square root of the weight ratio enters) recover the optimum to machine precision. Both solvers take refine_steps (default 8, 0 for the textbook algorithm) and report the loss of the attitude they return. The covariance's observability check now fires only below the numerical resolution of the information matrix. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * feat(sensors, estimators): direction observations from the sensor readings Every attitude sensor now says what it observes (observation_kind: the magnetic field, the sun, nadir, a star, the attitude, or the angular rate) and can give the inertial direction of that quantity for an orbital state (reference_direction). ADCS.estimators.initialization turns one raw measurement vector into body/inertial direction pairs with angular standard deviations (vector_observations), combining single-axis sensors of one kind by weighted least squares and taking vector sensors as they are; it also reads the angular rate off the gyros (angular_rate_from_readings) and the wheel momenta off the wheels (wheel_momentum_from_readings). These are the inputs the QUEST/TRIAD warm start needs. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * added additional checks for the initialization --------- Co-authored-by: Niclas Scheuer <niclas_scheuer@web.de>
…start) (#219) * feat(estimators): TRIAD, QUEST and the q-method for single-frame attitude determination Pure functions on unit-vector pairs (body direction, inertial reference, angular standard deviation per observation), in the package's quaternion convention (scalar first, rot_mat maps body to inertial): - triad(): the two-direction construction, with a linearised covariance when the two standard deviations are given. - quest(): Shuster's QUEST (Newton on the characteristic polynomial, Gibbs vector), solved in four sequential-rotation frames with the best conditioned one kept, so 180 degree attitudes are handled. - q_method(): Davenport's exact 4x4 eigen-solution, used as the oracle. - wahba_covariance(): the small-angle body-frame covariance of the optimal solution, in rad^2; raises when the directions leave a rotation unobservable. - quaternion_from_rotation_matrix(): Shepperd's method (the package had no matrix-to-quaternion helper). Tests: noise-free exactness, QUEST equals the q-method on noisy data, QUEST never worse than TRIAD, 180 degree cases, Monte-Carlo consistency of both covariances, degenerate inputs, and weighting. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix(estimators): polish QUEST and the q-method with Gauss-Newton steps The closed-form solutions lose accuracy when one direction is weighted far more heavily than the others: with weights 1e6 apart QUEST was off by up to 1e-4 rad on exact data, with 1e10 apart by whole radians, and the q-method's eigenvector degrades the same way. A few Gauss-Newton steps on the Wahba cost (solved by orthogonal factorisation, where only the square root of the weight ratio enters) recover the optimum to machine precision. Both solvers take refine_steps (default 8, 0 for the textbook algorithm) and report the loss of the attitude they return. The covariance's observability check now fires only below the numerical resolution of the information matrix. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * feat(sensors, estimators): direction observations from the sensor readings Every attitude sensor now says what it observes (observation_kind: the magnetic field, the sun, nadir, a star, the attitude, or the angular rate) and can give the inertial direction of that quantity for an orbital state (reference_direction). ADCS.estimators.initialization turns one raw measurement vector into body/inertial direction pairs with angular standard deviations (vector_observations), combining single-axis sensors of one kind by weighted least squares and taking vector sensors as they are; it also reads the angular rate off the gyros (angular_rate_from_readings) and the wheel momenta off the wheels (wheel_momentum_from_readings). These are the inputs the QUEST/TRIAD warm start needs. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * feat(estimators): start a filter from its first readings (QUEST/TRIAD warm start) Any attitude estimator accepts a WarmStart recipe in place of an initial state and builds its state from the first measurement vector it receives: the attitude from a quaternion star tracker or QUEST/TRIAD over the direction observations, the angular rate from the gyros with zero bias, the wheel momenta from the wheel readings, biases and disturbance parameters at zero or the given values, and a block-diagonal covariance from the sensors' noise and the given standard deviations, mapped into the filter's attitude chart (or projected onto the full quaternion for the EKF). That first cycle's correction is skipped because the readings are consumed; predict(), state and update() refuse to run before it. The same state can be built directly with initial_state_from_readings, and Filter.from_readings constructs and initialises in one call. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * test(estimators): compare the warm-started run against an analytic truth The simulate() comparison now uses a spherical, torque-free satellite whose attitude at every recorded time is known in closed form, so the test no longer depends on how simulate() aligns its recorded histories. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> --------- Co-authored-by: t <p@x> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> Co-authored-by: Nic <102034908+nscheuer@users.noreply.github.com>
…of the record (#220) simulate() and simulate_mc() recorded the real state after the step's propagation while the time, orbital state, readings, estimate and control of the same record came from the start of the step, so state_hist[k] was the state at time_s[k] + dt, one step ahead of the estimate it is plotted against, and state_hist[0] was never the initial state. Both drivers now record the state the step's readings, estimate and control refer to. The last recorded state is therefore the one at tf - dt, like every other recorded quantity.
Nothing called a disturbance's update(), and update() only re-applied a noise sample nothing ever redrew, so a dipole or propulsion disturbance with a noise model stayed at its nominal value, and parameter_std_rate only told the estimator to expect a random walk the true parameter never made. Disturbance.update_errors(j2000) now walks the estimated parameters at that rate and draws the step's noise sample; Satellite.update_errors runs it with the actuator update, once per step before the integrator, in both simulate() and simulate_mc(). The solver still sees one frozen realisation per step.
* docs(estimators): explain the single-frame attitude methods and cite their sources The attitude_determination module docstring now says what a direction observation is, what Wahba's problem asks, how TRIAD, the q-method and QUEST each solve it and when to use which, why a single-frame solution is worth having next to the recursive filters, and lists the original papers. The citations page gets the same sources. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * docs(tutorials): say why a warm start helps before showing how
* disturbance speed improvements * update benchmark timings
* disturbance speed improvements * update benchmark timings * first pass on centralizing math
nscheuer
marked this pull request as ready for review
October 19, 2026 07:58
patrickmckeen
approved these changes
Oct 19, 2026
patrickmckeen
left a comment
Collaborator
There was a problem hiding this comment.
I'm happy with this. Claude had some points on the release notes:
Release note gaps worth fixing, in order
- Everything from main since 0.2.0 is in this release and unmentioned: #207 (UKF drops a sensor some sigma points cannot see instead of aborting), #208 (sigma-point spread kept inside the chart), #209 (GPS noise rotated into ECI; the right sensor's noise used), #211 (RK4 midpoint averaged in update() and simulate()), #212 (guard rails, dt-versus-orbit check, and the removal of ADCS.estimators.old_attitude_estimators, which is a breaking change for anyone importing it).
- The simulate() history alignment (#220): state_hist[k] is now the real state at time_s[k], one step earlier than before. Anyone post-processing results will see numbers move; that deserves a sentence.
- ppigrf is now an optional extra, not a core dependency, and igrf_numba is the default field model. Users who import ppigrf directly need pip install generalized-adcs[ppigrf].
- Smaller: it says "a faster IGRF implementation is available" where it is the default; "orbit tests have been sped up" undersells a 100x on that directory; and the sensor-side observation_kind / reference_direction protocol from #218 is public API worth one line.
iliketocode2
requested changes
Oct 20, 2026
iliketocode2
left a comment
Collaborator
There was a problem hiding this comment.
Just two small 2-3 line changes! This looks so good, can't wait for 0.2.1 to drop!
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Will hopefully add the following:
Current merge order: