Simulators
The hybrid simulators step the hand-written plant derivative alongside the discrete blocks at a fixed sample time. They are plain Julia over concrete Float64 and never touch symbolic types — that is what keeps ModelingToolkit out of src/.
Current-loop simulator
InductionMachineDrives.simulate_foc_current_hybrid — Method
simulate_foc_current_hybrid(; kwargs...)Run the hybrid FOC current-loop simulation and return the logged signals as a NamedTuple of equal-length vectors, one entry per control sample.
This is the simplest of the hybrid simulators and the right place to start when validating the inner loop: there is no outer loop at all, dq current references come straight from a step profile, and the speed is whatever the resulting torque and load produce. Each iteration runs the rotor-flux observer, then current_controller_step!, converts the dq voltage command to alpha-beta, holds it, and integrates the plant one sample period with fixed-step RK4.
Keyword arguments
Simulation
t_end = 12.0: simulated duration in s.Ts = 100e-6: control sample time in s. Also the plant integration step unlessplant_substeps > 1.plant_substeps = 1: RK4 substeps per control period. Raise it to confirm the result is not integration-limited while keeping the controller rate fixed.
Current reference steps — isd_ref steps to isd_ref_mag at t_id_step, then isq_ref steps to isq_ref_pos at t_iq_pos_step, to isq_ref_neg at t_iq_neg_step, and back to zero at t_iq_zero_step.
t_id_step = 1.0,t_iq_pos_step = 3.0,t_iq_neg_step = 6.0,t_iq_zero_step = 9.0: step times in s.isd_ref_mag = 5.0,isq_ref_pos = 5.0,isq_ref_neg = -5.0: levels in A.
Load
load_profile = :constant::constantholdsTload;:stepsapplies0, thenTload_step1fromt_load_step1, thenTload_step2fromt_load_step2. Any other value raises an error.Tload = 0.0,Tload_step1 = 40.0,Tload_step2 = 80.0: torques in N·m, positive when accelerating toward positive speed (J*dω/dt = Te + TL - B*ω).t_load_step1 = 6.0,t_load_step2 = 8.0: step times in s.
Machine — Rs, Rr, Lls, Llr, Lm, p_pairs, J, B. Used for both the plant and, in derived form (sigma_Lss, k = Lm/Lrr, tau_r = Lrr/Rr), the observer and controller. Unlike the torque and speed simulators, this one has no *_scale mismatch knobs — plant and controller always agree.
Controller — Vs_max = 310.0 (V), Is_max = 40.0 (A), and the four feature flags use_filter, use_feedforward, use_saturation, use_antiwindup, all true. See CurrentControllerDiscreteParams.
Returned signals
t (s), the references isd_ref/isq_ref and their limited versions isd_ref_lim/isq_ref_lim (A), the achieved isd/isq (A, from the observer), the voltage command as vsd/vsq and vsα/vsβ (V), speed_rpm and omega_m (rpm, rad/s), torque (N·m, from the plant) alongside torque_obs (the observer's estimate — comparing the two is the point of this run), flux_r (Wb), theta_e (rad), omega_e (rad/s), Tload (N·m), the stationary currents isα/isβ (A), plus saturation (1.0 while the voltage limit is active) and vs_mod_unsat (V).
See also simulate_foc_torque_f1_hybrid to add a torque outer loop, and simulate_foc_speed_f1_hybrid for closed-loop speed control.
Torque-loop simulator, F1 constant flux
InductionMachineDrives.simulate_foc_torque_f1_hybrid — Method
simulate_foc_torque_f1_hybrid(; kwargs...)Run the hybrid FOC torque-control simulation — F1 constant-flux outer torque loop plus the inner current loop — and return the logged signals as a NamedTuple of equal-length vectors, one entry per control sample.
Each iteration runs rotor_flux_observer_step!, then outer_torque_flux_f1_step! to turn the commanded torque into dq current references, then current_controller_step!, and finally integrates the plant one sample period with fixed-step RK4 while the voltage command is held.
Speed is not controlled here. With the default Tload = 0 the machine simply accelerates under the commanded torque, which is what makes the torque steps easy to read; add a load to hold it near a working point.
Keyword arguments
Simulation
t_end = 12.0: simulated duration in s.Ts = 100e-6: control sample time in s.plant_substeps = 1: RK4 substeps per control period.
Torque reference profile — zero, then Te_ref_pos from t_Te_pos_step, Te_ref_neg from t_Te_neg_step, back to zero at t_Te_zero_step.
t_Te_pos_step = 2.0,t_Te_neg_step = 6.0,t_Te_zero_step = 10.0: step times in s.Te_ref_pos = 20.0,Te_ref_neg = -20.0: levels in N·m. The outer loop still slew-limits them atTe_dot_max, so the plant sees ramps, not steps.
Load — load_profile (:constant or :steps), Tload, Tload_step1, Tload_step2, t_load_step1, t_load_step2, exactly as in simulate_foc_current_hybrid. Positive torque accelerates toward positive speed.
Machine nominal parameters — Rs, Rr, Lls, Llr, Lm, p_pairs, J, B. These define the design point: the observer, outer loop and current controller are all built from them.
Outer loop — isd_nom = 23.04579328 (A), outer_Is_max = 40.0 (A), isd_min = 5.0 (A), Te_max = 124.0419647 (N·m), Te_dot_max = 500.0 (N·m/s), id_dot_max = 600.0 (A/s). See OuterTorqueFluxF1Params. Note outer_Is_max is separate from the inner Is_max, so the two limiters can be studied independently.
Inner current controller — Vs_max = 310.0 (V), Is_max = 40.0 (A), and the flags use_filter, use_feedforward, use_saturation, use_antiwindup.
Parameter mismatch multipliers — all default to 1.0, meaning plant, observer and controller agree. Perturb them to study detuning: plant_Rs_scale, plant_Rr_scale, plant_Lls_scale, plant_Llr_scale, plant_Lm_scale, plant_J_scale, plant_B_scale change the simulated machine; obs_Lm_scale, obs_Lss_scale, obs_Lrr_scale, obs_tau_r_scale change what the observer assumes; ctrl_Rs_scale, ctrl_sigma_Lss_scale, ctrl_k_scale change what the current controller assumes. For example plant_Rr_scale = 1.30 with obs_tau_r_scale = 1.0 simulates a rotor 30 % hotter than the observer believes.
Returned signals
t (s), Te_ref_ext and Te_ref_out (N·m, commanded versus slew- and limit-shaped), the current references isd_ref/isq_ref and their limited versions isd_ref_lim/isq_ref_lim (A), achieved isd/isq (A), voltages vsd/vsq and vsα/vsβ (V), speed_rpm and omega_m, torque (plant) and torque_obs (observer) in N·m, flux_r (Wb), theta_e (rad), omega_e (rad/s), Tload (N·m), isα/isβ (A), the saturation flags saturation_current, sat_isd, sat_isq, sat_Te, and vs_mod_unsat (V).
See also simulate_foc_speed_f1_hybrid, which wraps the same F1 flux policy in a speed loop.
Speed-loop simulator, F1 constant flux
This file also defines interp_profile_linear and the other CSV profile-playback helpers, which the MTPA simulator reuses — hence the include order fixed in src/InductionMachineDrives.jl.
InductionMachineDrives.simulate_foc_speed_f1_hybrid — Method
simulate_foc_speed_f1_hybrid(; kwargs...)Run the hybrid FOC speed-control simulation — F1 constant-flux outer speed loop plus the inner current loop — and return the logged signals as a NamedTuple of equal-length vectors, one entry per control sample.
This is the workhorse of the package. Each iteration runs rotor_flux_observer_step!, optionally load_torque_kalman_step!, then outer_speed_flux_f1_step! and current_controller_step!, and integrates the plant one sample period with fixed-step RK4 while the voltage command is held.
Beyond the internal ramp/step profiles it can play back recorded AWES speed and tether-torque profiles (see profiles/), linearly interpolated at each timestep the way a Simulink timeseries would be.
Keyword arguments
Simulation
t_end = 12.0: simulated duration in s.Ts = 100e-6: control sample time in s.plant_substeps = 1: RK4 substeps per control period.
Internal speed reference profile — used when speed_reference_source = :ramp: flat until t_ramp_up_start, linear up to wm_ref_high_rpm by t_ramp_up_end, held until t_hold_end, back to zero by t_ramp_down_end.
t_ramp_up_start = 1.0,t_ramp_up_end = 4.0,t_hold_end = 7.0,t_ramp_down_end = 10.0: times in s.wm_ref_high_rpm = 500.0: plateau speed in rpm.
External profile playback
speed_reference_source = :ramp::rampfor the internal profile above,:profileto interpolateprofile_speed_ref_rpmoverprofile_time.load_source = :internal::internalfor the internal load profile,:profileto interpolateprofile_load_torque_Nm.profile_time,profile_speed_ref_rpm,profile_load_torque_Nm: the CSV columns, asVector{Float64}.profile_timemust be strictly increasing and the value vectors must match its length; values outside the range are held at the endpoints. Validated up front, with an error naming the offending vector.profile_torque_sign = 1.0: use1.0when the CSV torque already follows this package's convention (positiveTLpulls toward positive speed),-1.0to flip it.
Set wm_dot_max very high (e.g. 1e6) when playing back a recorded speed profile, otherwise the outer loop's own slew limiter reshapes it.
Internal load profile — load_profile = :steps (or :constant), Tload, Tload_step1 = 20.0, Tload_step2 = 0.0, t_load_step1 = 5.0, t_load_step2 = 8.0. Torques in N·m under J*dω/dt = Te + TL - B*ω.
Load estimation and feedforward
load_estimator = :none::nonesupplies no estimate,:actualcheats by feeding back the true load (useful as an upper bound on what feedforward can buy),:kalmanrunsload_torque_kalman_step!on the measured speed and the observed torque.use_load_feedforward = false: whether the speed loop actually uses that estimate. Estimating and using are separate switches, so the estimate can be logged without being acted on.load_ff_sign = -1.0: required by the mechanical convention — a positive load torque already accelerates the machine, so compensating it meansTe_ff = J*dωref/dt + B*ωref - TL_est. Do not flip this without re-reading section 3 ofdocs/src/architecture.md.TL_kalman_R = 0.01,TL_kalman_q_omega = 0.1,TL_kalman_q_TL = 6.0: filter tuning. SeeLoadTorqueKalmanParams.TL_kalman_limit_positive = false: keptfalsehere because AWES tether torque changes sign between reel-out and reel-in; clamping it at zero would discard half the profile.
Machine nominal parameters — Rs, Rr, Lls, Llr, Lm, p_pairs, J, B, defining the design point for observer, outer loop and current controller.
Outer loop — isd_nom = 23.04579328 (A), outer_Is_max = 40.0 (A), isd_min = 5.0 (A), Te_max = 124.0419647 (N·m), wm_dot_max = 100.0 (rad/s²), id_dot_max = 600.0 (A/s). See OuterSpeedFluxF1Params. Field weakening and the speed PI tuning are not exposed here and stay at their defaults — field weakening is exercised by simulate_foc_speed_f1_im_160kw.
Inner current controller — Vs_max = 310.0 (V), Is_max = 40.0 (A), and use_filter, use_feedforward, use_saturation, use_antiwindup. Vs_max is shared with the outer loop.
Parameter mismatch multipliers — all 1.0 by default: plant_Rs_scale, plant_Rr_scale, plant_Lls_scale, plant_Llr_scale, plant_Lm_scale, plant_J_scale, plant_B_scale perturb the simulated machine; obs_Lm_scale, obs_Lss_scale, obs_Lrr_scale, obs_tau_r_scale perturb the observer's assumptions; ctrl_Rs_scale, ctrl_sigma_Lss_scale, ctrl_k_scale perturb the current controller's.
Returned signals
t (s); the speed loop's wm_ref, wm_ref_ramp, wm_filt, e_wm (rad/s) and its torque split Te_ref_out, Te_PI, Te_ff (N·m); current references isd_ref/isq_ref with limited isd_ref_lim/isq_ref_lim and achieved isd/isq (A); voltages vsd/vsq, vsα/vsβ (V); speed_rpm, omega_m; torque (plant) and torque_obs (observer) in N·m; flux_r (Wb), theta_e (rad), omega_e (rad/s); Tload, TL_est (N·m) and omega_hat_load (rad/s) from the estimator; stationary isα/isβ (A); three-phase ia/ib/ic (A) and va/vb/vc (V); the power breakdown Pelec, Pmech, Pload, Pfric (W), which is what the efficiency scripts consume; the flags saturation_current, sat_isd, sat_isq, sat_Te; and vs_mod_unsat (V).
See also simulate_foc_speed_mtpa_hybrid, which is deliberately the same simulation with a different flux policy, so the two can be compared run for run.
Speed-loop simulator, constrained MTPA
InductionMachineDrives.simulate_foc_speed_mtpa_hybrid — Method
simulate_foc_speed_mtpa_hybrid(; kwargs...)Run the hybrid FOC speed-control simulation with a constrained-MTPA outer loop and return the logged signals as a NamedTuple of equal-length vectors, one entry per control sample.
Deliberately the twin of simulate_foc_speed_f1_hybrid: same plant, same rotor-flux observer, same inner current controller, same load estimator, same speed and load profiles. The single difference is the flux policy — F1 holds isd at isd_nom regardless of load, while outer_speed_flux_mtpa_step! picks isd per sample to minimise stator current for the torque being demanded, subject to a flux floor and a torque reserve. Running both with identical keywords is how the efficiency benefit is measured.
Keyword arguments
Every keyword of simulate_foc_speed_f1_hybrid is accepted with the same meaning and the same defaults, except that the F1 field-weakening settings have no counterpart here. In brief: t_end, Ts, plant_substeps; the internal ramp profile t_ramp_up_start, t_ramp_up_end, t_hold_end, t_ramp_down_end, wm_ref_high_rpm; profile playback via speed_reference_source, load_source, profile_time, profile_speed_ref_rpm, profile_load_torque_Nm, profile_torque_sign; the internal load profile load_profile, Tload, Tload_step1, Tload_step2, t_load_step1, t_load_step2; load estimation load_estimator, use_load_feedforward, load_ff_sign = -1.0, TL_kalman_R, TL_kalman_q_omega, TL_kalman_q_TL, TL_kalman_limit_positive = false; the machine parameters Rs, Rr, Lls, Llr, Lm, p_pairs, J, B; the outer-loop limits isd_nom, outer_Is_max, isd_min, Te_max, wm_dot_max, id_dot_max; the inner controller Vs_max, Is_max, use_filter, use_feedforward, use_saturation, use_antiwindup; and the plant_*_scale, obs_*_scale, ctrl_*_scale mismatch multipliers.
The keywords specific to this simulator are:
lambda_rd_floor = 0.35: minimum rotor flux in Wb held regardless of torque demand. Without it, pure MTPA would let the flux collapse near zero torque and the machine would be slow to respond. Set to0to disable.Te_reserve = 45.0: torque in N·m that must stay achievable withinIs_maxat all times, converted internally into a second flux floor. This is the knob that trades efficiency for transient headroom.tau_f_wm = 10e-3,ts_wm = 500e-3,ts_dist_wm = 3.0: speed filter time constant and PI design times in s. Unlike the F1 simulator, these are exposed here; the defaults match the MATLAB F3 controller.
Mechanical sign convention is unchanged: J*dω/dt = Te + TL - B*ω, so a positive TL pulls toward positive speed and load_ff_sign stays -1.0.
Returned signals
All the fields returned by simulate_foc_speed_f1_hybrid except the F1-specific field-weakening diagnostics, plus the MTPA diagnostics that show which constraint set the flux at each instant: lambda_ref_out (Wb), Kt_isd (N·m/A²), isd_mtpa, isd_floor, isd_reserve, isd_desired, isq_max_disp (all A), Te_current_limited (N·m) and torque_current_limited (flag). Comparing isd_desired against isd_mtpa, isd_floor and isd_reserve tells you directly whether the run is genuinely operating at the MTPA optimum or is being held off it by one of the constraints.
160 kW speed-loop simulator
The bipolar speed-tracking case with field weakening and voltage/current limits; see 160 kW FOC F1 case for the full description.
InductionMachineDrives.im_160kw_load_torque_profile — Method
im_160kw_load_torque_profile(t; kwargs...)Opposing load-torque steps for the bipolar speed test.
At positive speed:
-Tload_step1-Tload_step2
At negative speed:
+Tload_step1+Tload_step2
This sign choice follows:
J*dωm/dt = Te + Tload - B*ωmand therefore makes the load oppose the commanded direction in both halves of the test.
InductionMachineDrives.im_160kw_speed_reference — Method
im_160kw_speed_reference(t; kwargs...)Bipolar mechanical-speed reference in rad/s:
- zero-speed flux build-up;
- ramp from 0 to +
wm_abs; - positive-speed hold;
- ramp back to zero;
- zero-speed dwell;
- ramp from 0 to -
wm_abs; - negative-speed hold;
- ramp back to zero.
The default profile reaches +50 rad/s and -50 rad/s.
InductionMachineDrives.simulate_foc_speed_f1_im_160kw — Method
simulate_foc_speed_f1_im_160kw(; kwargs...)Run the 160 kW im induction machine with:
- stationary alpha-beta induction-machine plant;
- discrete rotor-flux and torque observer;
- discrete F1 outer speed/flux controller;
- discrete dq current controller;
- optional load-torque estimator.
The default machine and control values come from the supplied pre_kitepower_IM.m and IM__FOC_kitepower.slx model.
Parameter independence
Nominal values define the controller-design point. The actual plant, observer, outer controller, current controller, and load estimator can then be perturbed independently through their corresponding scale factors.
For example, a rotor-resistance robustness test can use:
plant_Rr_scale = 1.30
obs_tau_r_scale = 1.00so the simulated rotor resistance is 30% above the observer assumption.