Skip to content

Creation of the magnetometer sensor - #1190

Open
aitorperezgrau-sys wants to merge 95 commits into
RocketPy-Team:developfrom
aitorperezgrau-sys:ENH/creation-magnetometer
Open

Creation of the magnetometer sensor#1190
aitorperezgrau-sys wants to merge 95 commits into
RocketPy-Team:developfrom
aitorperezgrau-sys:ENH/creation-magnetometer

Conversation

@aitorperezgrau-sys

@aitorperezgrau-sys aitorperezgrau-sys commented Sep 4, 2026

Copy link
Copy Markdown

Pull request type

  • Code changes (bugfix, features)
  • Code maintenance (refactoring, formatting, tests)
  • ReadMe, Docs and GitHub updates

Checklist

  • Tests for the changes have been added (if needed)
  • Docs have been reviewed and added / updated
  • Lint (black rocketpy/ tests/) has passed locally
  • All tests (pytest tests -m slow --runslow) have passed locally

New behavior

The backbone of this PR is the creation of the magnetometer sensor.

Unlike other sensors, the measured value (magnetic field) is not a result of the flight simulation, as a result I used the WMM (World Magnetic Model) to obtain the magnetic field, and use frame changes to adjust the magnetic field reading to the sensor position.
When it comes to the noise and distortions included we can divide them into:

  1. Magnetic distortion:
    To represent the magnetic distortions (hard iron distortion, soft iron distortion and power interference) I created auxiliary classes Plate and Wire. Their distortions are applied in the Body Axis Coordinate System frame. Moreover, print and plot methods to validate their geometric position and its characteristics have been included together with new methods to satisfy their physical requirements.  

  2. Sensor Noise
    The noise related to the apparatus limitations has been covered using the existing InertialSensor and Sensor noise model and included in the measured value when the magnetic field is in the sensor frame.

Furthermore, the documentation has been improved to include the new features and testing of every new element has been performed successfully. 

Breaking change

  • Yes
  • No

Additional information

  • This PR requires pywmm Python library to evaluate the World Magnetic Model (WMM). It has been added to requirements.txt.

  • A change in the accelerometer sensor has been made, due to existing coordinate transformation error.

  • I opted for a software architecture that raises error when input parameters are incorrect because this approach allows users to know the cause of the error rather than seeing the error in an inner layer of the code. The obvious disadvantage is that for personal/private usage of the library there is no room for other parameters if desired; nonetheless, I have accounted for these having more flexible arguments. This is illustrated with the ignition_wire_type, which allows any type of strings, so that It can be used for HIL testing.

  • One of the functions implemented that were required to ensure a physically accurate model was the general_radius method. This method belongs to the rocket and returns the radius of the rocket, without the fins, for any value along the longitudinal axis. Also methods for adding plates and wires to the rocket have been implemented add_wire and add_plate.

  • The distortion created by wires is calculated using the Biot-Savart expression for finite wire, while the soft iron distortion modeled by the Plates is the sum of the distortions of the points that form the plate.

  • The current model supports plates that turns around the rocket, are bigger than the size of the rocket, have radius changes. All of these plate geometry characteristics are illustrated in the following image:

nose_cone_with_plate

Other images that illustrate the creation of the plate are the following
image
image

  • Focusing on code efficiency, the calculation of the magnetic distortion (by the wire, and the plate) is calculated once, when it is needed, and then stored in a dictionary with keys the position for which it has been calculated and value the magnetic field or soft iron distortion matrix computed.

  • Aiming to have a coherent naming style and better readability, almost all the variables/attributes that start with _ are Vector instances.

  • Plots of all plates and wires attached to the rocket have also been implemented in _RocketPlot (draw_wires, draw_plates). Due to code repetition in this class, a function _rocket_shape_plot that creates the outline of the rocket has been added.
    Example of draw_wires

image Example of `draw_plates` image
  • Supporting features were included in sensor.py, nose_cone.py, tail.py, components.py....

  • The user defined coordinate system orientation and the center of dry mass are required for the changes in coordinate systems in Wire and Plate and the rocket instance to which they are attached is necessary for the corresponding plotting associated classes. As a result, the _rocket_belonging method is implemented in Wire and Plate and called when added to the rocket with add_wire add_plate. I am aware that cross referencing is not the best software practice, and it reduces the conceptual understanding of the inner library; however, I find it the best solution for this necessity.

  • All plotting methods have been implemented using Matplotlib

  • The test "tests/unit/environment/test_environment_analysis.py::test_values" failed during local test, yet it is an atmospheric test unrelated to the new features. Outputs of Monte Carlo testing .../monte_carlo_analysis/monte_carlo_analysis_outputs/mrs.inputs.txt and .../monte_carlo_analysis/monte_carlo_analysis_outputs/mrs.outputs.txt have been automatically included in the current version.

…ire to have effect on different magnetometers, while ensuring efficiency and coherence with library
…nction of the nose cone, intoduction of plate in inits modules,
@aitorperezgrau-sys
aitorperezgrau-sys requested a review from a team as a code owner September 4, 2026 10:01
Comment thread docs/reference/index.rst

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

e and plates? what do those mean?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In rockets, magnetometers face three magnetic distortions beyond the sensor's intrinsic limitations. These are

  • Soft iron distortion: caused by materials with higher magnetic permeability than vacuum. As a result, the magnetic field lines are forced to pass through these materials, altering the magnetic reading. To account for this magnetic distortion, I created the Plate class, which simulates a material sheet that is responsible for this bending of the magnetic field lines.

  • Power interference: caused by wires in the avionics bay, which, due to current flow, generate a magnetic field, affecting the magnetic field reading. The power interference is, in turn, divided into communication interference, which comprises the magnetic field generated by wires with constant electron flow; and activation signal interference, which includes only the magnetic field generated by wires which fulfill some activation/pyrotechnic function (such as the one that activates the engine, or the one used to deploy the parachute). To account for this power interference, I created the Wire class.

  • Hard iron distortion: caused by fixed components in the avionics bay, and it is simulated by a vector addition.

There is additional information about the formulation and usage of the Wire and Plate classes in the PR description and in their docstrings, let me know if you have more questions or if you would like me to clarify something.

Gui-FernandesBR and others added 4 commits September 13, 2026 23:08
# Conflicts:
#	rocketpy/rocket/rocket.py
#	tests/unit/sensors/test_sensor.py
Reviewing RocketPy-Team#1190 turned up problems that the test suite could not see,
because the only coverage of the plate and wire distortion called
Magnetometer.measure by hand with a real Rocket, which is not the object
Flight hands it.

Flight.__measure_sensors passed `rocket=self`, a Flight. The distortion
branches reach for rocket.plates, rocket._communication_wires and
rocket._ignition_wires, none of which a Flight has, so any flight of a
magnetometer configured with plates or wires died with
`AttributeError: 'Flight' object has no attribute 'plates'`. It now passes
self.rocket, and a new integration test flies the wires-and-plates fixture
so the hand-off itself is covered.

The WMM epoch came from datetime.now() at construction, which made the
same script produce a different geomagnetic field on different days, threw
away the date the flight is actually simulated for, and would have silently
extrapolated past the 2030 end of the WMM-2025 coefficients. It is resolved
per measurement from Environment.datetime_date instead, warning when the
Environment carries no date. _obtain_magnetic_field takes the epoch as an
argument so it is a function of its inputs.

Magnetometer, Wire and Plate each defined from_dict with no to_dict, so a
save/load round trip silently rebuilt them with every distortion back at
its default, and the encoder fell back to __dict__ for Wire and Plate,
which holds caches keyed by tuples of floats that JSON cannot encode.
Writing to_dict surfaced that _validate_soft_iron fell through both of its
branches for a serialized matrix, leaving the sensor with no distortion
matrix at all and raising no error; it now accepts nested sequences and
rejects anything else.

The distortion derived from the rocket was never reset between flights, so
a second simulation with a different rocket, a moved sensor or an added
plate silently reused the first one's matrix. Magnetometer._reset restores
the configured baseline. In apply_soft_iron the accumulation sat inside the
"not already cached" branch, so a plate whose matrix had been computed
before contributed nothing to the total.

Also: the plate dipole sum divided by zero without a word when the sensor
sat on a plate point; the wire collinearity warning fired once per
integration step; and the magnetometer applied temperature drift before
noise while the other two inertial sensors do the reverse.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ters

The branch carried changes that have nothing to do with the magnetometer,
which are reverted here so the feature can be reviewed on its own: the
regenerated Monte Carlo notebook outputs (mrs.inputs.txt, mrs.outputs.txt),
the project-wide napoleon_include_private_with_doc switch in docs/conf.py,
the numericalunits import rename, and stray blank-line edits in motor.py,
test_tools.py, test_defiance_rocket.py and barometer.py. The barometer's
relative_position docstring type is kept, since that one is a correction.

docs/requirements.txt asked for wcwidth>=0.8.0. No such release exists, the
project has never gone past 0.2.x, so the docs install could not resolve;
it goes back to the pinned wcwidth==0.2.13 the rest of that lock file uses.
requirements.txt gets pywmm pinned to >=1.1.1 and its trailing newline back.

rocket.py had grown past the 3050-line ceiling in .pylintrc. Rather than
raise the ceiling, _define_3d_endpoints moves to wire.py as the module
level define_wire_endpoints: it never touched self, and it is wire geometry.

Also adds the CHANGELOG entries for the feature and for the accelerometer
fix, and a test that states what a rotated accelerometer does instead of
recomputing the implementation, so the direction of the frame change is
actually pinned. ruff, ruff format and pylint are clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@codecov

codecov Bot commented Sep 14, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 88.09055% with 121 lines in your changes missing coverage. Please review.
✅ Project coverage is 91.22%. Comparing base (88aed5a) to head (27ce7e0).
⚠️ Report is 2 commits behind head on develop.

Files with missing lines Patch % Lines
rocketpy/rocket/wire.py 82.01% 25 Missing ⚠️
rocketpy/rocket/plate.py 90.94% 22 Missing ⚠️
rocketpy/plots/rocket_plots.py 73.41% 21 Missing ⚠️
rocketpy/sensors/magnetometer.py 91.37% 20 Missing ⚠️
rocketpy/rocket/rocket.py 86.17% 17 Missing ⚠️
rocketpy/plots/wire_plots.py 89.36% 5 Missing ⚠️
rocketpy/plots/plate_plots.py 93.22% 4 Missing ⚠️
rocketpy/prints/wire_prints.py 81.81% 4 Missing ⚠️
rocketpy/sensors/sensor.py 75.00% 2 Missing ⚠️
rocketpy/prints/plate_prints.py 95.00% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##           develop    #1190      +/-   ##
===========================================
- Coverage    91.41%   91.22%   -0.20%     
===========================================
  Files          132      139       +7     
  Lines        18133    19117     +984     
===========================================
+ Hits         16577    17440     +863     
- Misses        1556     1677     +121     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants