Skip to content

Add MotorElement Class - #1296

Open
jguarato wants to merge 103 commits into
petrobras:mainfrom
jguarato:feature-motor
Open

jguarato wants to merge 103 commits into
petrobras:mainfrom
jguarato:feature-motor

Conversation

@jguarato

@jguarato jguarato commented May 7, 2026 •

Copy link
Copy Markdown
Collaborator

This PR introduces a new MotorElement class that enables the simulation of three-phase induction motors in ROSS analyses. The motor model is implemented using a fourth-order Runge–Kutta (RK4) numerical integration method, capturing the coupled dynamics of magnetic fluxes and electrical currents.

Besides that it adds a Rotor.run_with_motor(). That method simulates the motor, then applies the net electromagnetic torque and the actual shaft speed to the rotor's time response. It supports three drive modes:

  • DOL: direct-on-line start. Optional grid harmonics and three-phase voltage unbalance.
  • VFD_VF: variable-frequency drive with open-loop V/f adjustment.
  • VFD_FOC: variable-frequency drive with closed-loop field-oriented control.

What's new

ross/motors/ (new subpackage)

  • motor_element.py: MotorElement models the TPIM in a synchronous reference frame. It has run_direct_on_line(), run_with_inverter_vf() and run_with_inverter_foc(). The time loops are compiled with numba. It also provides motor_example().
  • motor_drive.py: power supplies and drives: SourceAC (with harmonics and unbalance), the Inverter base class, InverterVF and InverterFOC.
  • results.py: MotorResponseResults has plot_torque(), plot_speed(), plot_phase_currents(), plot_phase_voltages(), plot_line_voltages() and sample_at(). PhaseResults has plot() and plot_dfft().
  • utils.py: Clarke/Park transforms, phase/line/DC-bus conversions, a windowed DFFT and an RK4 step.
  • motor_icon.svg: the icon drawn on the rotor plot.

Rotor integration (rotor_assembly.py)

  • Rotor, CoAxialRotor and MultiRotor accept one motor_element. It is carried through add_elements, remove_elements, from_section, summary and the rotor plot.
  • run_with_motor() runs the motor model and samples electric torque, load torque and speed on the rotor time grid. It adds Te − Tl to the torsional DOF at the motor node and the speed-dependent unbalance force. It then integrates with Newmark. By default (steady_state=True) it integrates only the last third of the interval after the load is applied. The motor results are stored in results.motor_results.
  • The Newmark integration now includes a torque-dependent stiffness term, K1 + K2·accel + Ktq·torque.

Supporting changes

  • ShaftElement.Ktq(): the torque stiffness matrix, pulled out of K() so the time integration can reuse it. K() still returns the same matrix.
  • ross.units.format_unit(): compact unit strings for plot axis labels, such as N⋅m and rad/s.
  • ross.utils.downsample_figure(): MinMax-LTTB downsampling (via tsdownsample) for long time-series plots.
  • ross.utils.limit_data_range(): now also used by the DFFT plot in TimeResponseResults.
  • The model-reduction progress messages now print on one line, so the doctest outputs changed to Running direct method....
  • cv (metric horsepower) was added to new_units.txt.

Docs

  • New tutorial docs/user_guide/tutorial_motor.ipynb.
  • New references in references.bib: Wu (2016) and Novotny & Lipo (1996).

Tests

New ross/tests/test_motor_element.py covers:

  • Motor example parameters and equality.
  • DOL at no load, rated load and locked rotor: RMS stator current, speed and starting torque.
  • V/f at rated and half frequency: speed near synchronous, and speed droop under load.
  • FOC: speed follows the rated and reduced references, with and without load.
  • The DFFT frequency range narrows the torque and current spectra.
  • run_with_motor: normal run, invalid drive_mode, and a rotor with no motor.

New dependency

  • tsdownsample (in requirements.txt).

Example

import numpy as np
import ross as rs

motor = rs.motor_example()
rotor = rs.rotor_example().add_elements([motor])
n1, n2 = (d.n for d in rotor.disk_elements)

results = rotor.run_with_motor(
    t=np.linspace(0, 1, 1000),
    node=[n1, n2],
    unbalance_magnitude=[5e-4, 0],
    unbalance_phase=[-np.pi / 2, 0],
    drive_mode="DOL",
    load_torque_entrance_time=0.5,
)
results.plot_1d(probe=[rs.Probe(n1, 0)])
results.motor_results.plot_speed()

Notes for reviewers

  • A rotor supports only one motor, and the motor adds nothing to the global structural matrices. It only supplies torque and speed.
  • The VFD switching frequency is fixed at 5 kHz.

@codecov-commenter

codecov-commenter commented May 19, 2026 •

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 85.08108% with 138 lines in your changes missing coverage. Please review.
✅ Project coverage is 82.88%. Comparing base (4aa371d) to head (fa7f0be).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
ross/motors/motor_element.py 75.98% 73 Missing ⚠️
ross/motors/utils.py 64.44% 16 Missing ⚠️
ross/rotor_assembly.py 84.00% 16 Missing ⚠️
ross/motors/motor_drive.py 94.81% 14 Missing ⚠️
ross/motors/results.py 94.66% 8 Missing ⚠️
ross/multi_rotor/results.py 14.28% 6 Missing ⚠️
ross/multi_rotor/multi_rotor.py 50.00% 4 Missing ⚠️
ross/utils.py 95.83% 1 Missing ⚠️
❗ Your organization needs to install the Codecov GitHub app to enable full functionality.
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #1296      +/-   ##
==========================================
+ Coverage   82.59%   82.88%   +0.29%     
==========================================
  Files         173      177       +4     
  Lines       27549    28449     +900     
==========================================
+ Hits        22754    23580     +826     
- Misses       4795     4869      +74     
Files with missing lines Coverage Δ
ross/__init__.py 95.00% <100.00%> (+0.12%) ⬆️
ross/faults/crack.py 45.30% <ø> (ø)
ross/faults/misalignment.py 99.24% <ø> (ø)
ross/faults/rubbing.py 95.08% <ø> (ø)
ross/results.py 89.42% <100.00%> (+2.37%) ⬆️
ross/shaft_element.py 95.68% <100.00%> (+0.04%) ⬆️
ross/units.py 91.13% <100.00%> (+0.72%) ⬆️
ross/utils.py 90.81% <95.83%> (+0.17%) ⬆️
ross/multi_rotor/multi_rotor.py 87.29% <50.00%> (-0.85%) ⬇️
ross/multi_rotor/results.py 17.74% <14.28%> (ø)
... and 5 more

Continue to review full report in Codecov by Harness.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 3a258dc...fa7f0be. Read the comment docs.

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

bgglzv and others added 17 commits September 14, 2026 09:12
… sync

The "Rearrange inverters structure" and "Add plotly resampler to motor
plots" commits (merged in from jguarato/ross:feature-motor) broke most
of the motors doctest/test suite:

- SourceAC.get_operating_state() was left behind when InverterVF/
  InverterFOC were renamed to get_current_state(); every
  run_direct_on_line() call and downstream results.py doctest failed
  with AttributeError.
- InverterVF.get_phase_voltages()/get_current_state() gained a required
  theta_0 parameter that their own doctests never passed.
- line_to_dc_bus()'s docstring documented 310.9 for line_to_dc_bus(219.9)
  when the function (v_line * 1.35) actually returns 296.865.
- run_with_inverter_vf() now requires frequency_s positionally; our
  InverterVF test fixtures (added when the method still hardcoded
  5000 Hz internally) didn't pass it.
- plotly-resampler (new hard import in motors/results.py) was never
  added to requirements.txt, so a clean install can't import
  ross.motors at all.

Also update the three renamed tutorial notebooks
(tutorial_motor_part_1/2/3.ipynb): they still called
run_open_loop_vf_adjustment(), the name jguarato's branch used before
this same rearrange renamed it back to run_with_inverter_vf().

Full suite (pytest ross) is back to 758 passed / 9 skipped / 2 failed,
matching the two failures that pre-date this branch
(Rotor.run_with_motor and steady_state_index doctests).
Fix breakage introduced by the inverter rearrange + missing plotly-resampler dependency
@jguarato
jguarato marked this pull request as ready for review September 24, 2026 12:32

This branch has not been deployed

No deployments
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.

3 participants