Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 21 additions & 21 deletions motulator/common/control/_pwm.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class PWM:
----------
k_comp : float, optional
Compensation factor for the angular delay effect, defaults to 1.5.
u_c_ab0 : float, optional
u_c0_ab : float, optional
Initial voltage (V) in stationary coordinates. This is used to compute the
realized voltage, defaults to 0.
overmodulation : Literal["MPE", "MME", "six_step"], optional
Expand All @@ -51,16 +51,16 @@ class PWM:
def __init__(
self,
k_comp: float = 1.5,
u_c_ab0: complex = 0j,
u_c0_ab: complex = 0j,
overmodulation: Literal["MPE", "MME", "six_step"] = "MPE",
) -> None:
self.k_comp = k_comp
self.overmodulation = overmodulation
self.realized_voltage = u_c_ab0
self._old_u_c_ab = u_c_ab0
self.realized_voltage = u_c0_ab
self._old_u_c_ab = u_c0_ab

@staticmethod
def six_step_overmodulation(u_c_ab_ref: complex, u_dc: float) -> complex:
def six_step_overmodulation(u_c_ref_ab: complex, u_dc: float) -> complex:
"""
Overmodulation up to six-step operation.

Expand All @@ -69,14 +69,14 @@ def six_step_overmodulation(u_c_ab_ref: complex, u_dc: float) -> complex:

Parameters
----------
u_c_ab_ref : complex
u_c_ref_ab : complex
Converter voltage reference (V) in stationary coordinates.
u_dc : float
DC-bus voltage (V).

Returns
-------
u_c_ab_ref : complex
u_c_ref_ab : complex
Modified converter voltage reference (V) in stationary coordinates.

References
Expand All @@ -87,11 +87,11 @@ def six_step_overmodulation(u_c_ab_ref: complex, u_dc: float) -> complex:

"""
# Limited magnitude
r = min([abs(u_c_ab_ref), 2 / 3 * u_dc])
r = min([abs(u_c_ref_ab), 2 / 3 * u_dc])

if sqrt(3) * r > u_dc:
# Angle and sector of the reference vector
theta = phase(u_c_ab_ref)
theta = phase(u_c_ref_ab)
sector = floor(3 * theta / pi)

# Angle reduced to the first sector (at which sector == 0)
Expand All @@ -107,17 +107,17 @@ def six_step_overmodulation(u_c_ab_ref: complex, u_dc: float) -> complex:
theta0 = pi / 3 - alpha_g

# Modified reference voltage
u_c_ab_ref = r * exp(1j * (theta0 + sector * pi / 3))
u_c_ref_ab = r * exp(1j * (theta0 + sector * pi / 3))

return u_c_ab_ref
return u_c_ref_ab

def duty_ratios(self, u_c_ab_ref: complex, u_dc: float) -> list[float]:
def duty_ratios(self, u_c_ref_ab: complex, u_dc: float) -> list[float]:
"""
Compute the duty ratios for three-phase space-vector PWM.

Parameters
----------
u_c_ab_ref : complex
u_c_ref_ab : complex
Converter voltage reference (V) in stationary coordinates.
u_dc : float
DC-bus voltage (V).
Expand All @@ -129,7 +129,7 @@ def duty_ratios(self, u_c_ab_ref: complex, u_dc: float) -> list[float]:

"""
# Phase voltages without the zero-sequence voltage
u_abc = complex2abc(u_c_ab_ref)
u_abc = complex2abc(u_c_ref_ab)

# Zero-sequence voltage resulting in space-vector PWM
u_0 = 0.5 * (max(u_abc) + min(u_abc))
Expand All @@ -148,7 +148,7 @@ def duty_ratios(self, u_c_ab_ref: complex, u_dc: float) -> list[float]:
return d_abc

def compute_output(
self, T_s: float, u_c_ab_ref: complex, u_dc: float, w: float
self, T_s: float, u_c_ref_ab: complex, u_dc: float, w: float
) -> tuple[list[float], complex]:
"""
Compute the duty ratios and the limited voltage reference.
Expand All @@ -157,7 +157,7 @@ def compute_output(
----------
T_s : float
Sampling period (s).
u_c_ab_ref : complex
u_c_ref_ab : complex
Converter voltage reference (V) in stationary coordinates.
u_dc : float
DC-bus voltage (V).
Expand All @@ -175,14 +175,14 @@ def compute_output(
# Advance the angle due to the computational delay (N*T_s) and the ZOH (PWM)
# delay (0.5*T_s), typically 1.5*T_s*w
theta_comp = self.k_comp * T_s * w
u_c_ab_ref = exp(1j * theta_comp) * u_c_ab_ref
u_c_ref_ab = exp(1j * theta_comp) * u_c_ref_ab

# Modify angle in the overmodulation region
if self.overmodulation == "six_step":
u_c_ab_ref = self.six_step_overmodulation(u_c_ab_ref, u_dc)
u_c_ref_ab = self.six_step_overmodulation(u_c_ref_ab, u_dc)

# Duty ratios
d_abc = self.duty_ratios(u_c_ab_ref, u_dc)
d_abc = self.duty_ratios(u_c_ref_ab, u_dc)

# Limited voltage reference
u_c_ab = abc2complex(d_abc) * u_dc
Expand All @@ -208,8 +208,8 @@ def update(self, u_c_ab: complex) -> None:
self._old_u_c_ab = u_c_ab

def __call__(
self, T_s: float, u_c_ab_ref: complex, u_dc: float, w: float
self, T_s: float, u_c_ref_ab: complex, u_dc: float, w: float
) -> list[float]:
d_abc, u_c_ab = self.compute_output(T_s, u_c_ab_ref, u_dc, w)
d_abc, u_c_ab = self.compute_output(T_s, u_c_ref_ab, u_dc, w)
self.update(u_c_ab)
return d_abc
9 changes: 7 additions & 2 deletions motulator/common/model/_simulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,12 +109,17 @@ def simulate(self, t_stop: float = 1.0) -> SimulationResults:
"""
try:
# Initialize outputs based on initial states
self.mdl.set_outputs(0) # Set t = 0 for initialization
self.mdl.set_outputs(0.0)

# Main simulation loop
progress_bar = None
if self.show_progress:
progress_bar = tqdm(total=t_stop, desc="Simulation", unit="s")
progress_bar = tqdm(
total=t_stop,
desc="Simulation",
unit="s",
bar_format="{l_bar}{bar}| {n:.2f}/{total:.2f} {unit}",
)

def update_progress() -> None:
if progress_bar is not None:
Expand Down
14 changes: 9 additions & 5 deletions motulator/drive/control/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ class ExternalReferences:
class Measurements:
"""Measured signals."""

i_s_ab: complex
i_c_ab: complex # Converter current (A) in stationary coordinates
u_dc: float
w_M: float | None = None
theta_M: float | None = None
Expand Down Expand Up @@ -76,8 +76,8 @@ class VectorControlSystem(ControlSystem):
----------
vector_ctrl : VectorController
Vector controller whose input is the torque reference.
speed_ctrl : SpeedController, optional
Speed controller. If not given, torque-control mode is used.
speed_ctrl : SpeedController | PIController | None
Speed controller. If not given or None, torque-control mode is used.

"""

Expand Down Expand Up @@ -118,14 +118,18 @@ def set_speed_ref(self, ref_fcn: Callable[[float], float]) -> None:
def get_measurement(self, mdl: Drive) -> Measurements:
"""Get measurements from sensors."""
u_dc = mdl.converter.meas_dc_voltage()
i_s_ab = abc2complex(mdl.machine.meas_currents())
if mdl.lc_filter is not None:
i_c_ab = abc2complex(mdl.lc_filter.meas_currents())
else:
i_c_ab = abc2complex(mdl.machine.meas_currents())

if self.vector_ctrl.sensorless:
w_M = None
theta_M = None
else:
w_M = mdl.mechanics.meas_speed()
theta_M = wrap(mdl.mechanics.meas_position())
return Measurements(i_s_ab, u_dc, w_M, theta_M)
return Measurements(i_c_ab, u_dc, w_M, theta_M)

def get_feedback(self, meas: Measurements) -> Feedbacks:
"""Get feedback signals."""
Expand Down
6 changes: 3 additions & 3 deletions motulator/drive/control/_im_current_vector.py
Original file line number Diff line number Diff line change
Expand Up @@ -266,11 +266,11 @@ def __init__(

def get_feedback(self, meas: Measurements) -> ObserverOutputs:
"""Get the feedback signals."""
u_s_ab = self.pwm.get_realized_voltage()
u_c_ab = self.pwm.get_realized_voltage()
if self.sensorless:
fbk = self.observer.compute_output(meas, u_s_ab)
fbk = self.observer.compute_output(u_c_ab, meas.i_c_ab)
else:
fbk = self.observer.compute_output(meas, u_s_ab, meas.w_M)
fbk = self.observer.compute_output(u_c_ab, meas.i_c_ab, meas.w_M)
fbk.u_dc = meas.u_dc
return fbk

Expand Down
10 changes: 5 additions & 5 deletions motulator/drive/control/_im_flux_vector.py
Original file line number Diff line number Diff line change
Expand Up @@ -311,11 +311,11 @@ def __init__(

def get_feedback(self, meas: Measurements) -> ObserverOutputs:
"""Get feedback signals."""
u_s_ab = self.pwm.get_realized_voltage()
u_c_ab = self.pwm.get_realized_voltage()
if self.sensorless:
fbk = self.observer.compute_output(meas, u_s_ab)
fbk = self.observer.compute_output(u_c_ab, meas.i_c_ab)
else:
fbk = self.observer.compute_output(meas, u_s_ab, meas.w_M)
fbk = self.observer.compute_output(u_c_ab, meas.i_c_ab, meas.w_M)
fbk.u_dc = meas.u_dc
return fbk

Expand Down Expand Up @@ -418,8 +418,8 @@ def __init__(

def get_feedback(self, w_M_ref: float, meas: Measurements) -> ObserverOutputs:
"""Get feedback signals."""
u_s_ab = self.pwm.get_realized_voltage()
fbk = self.observer.compute_output(meas, u_s_ab, w_M_ref)
u_c_ab = self.pwm.get_realized_voltage()
fbk = self.observer.compute_output(u_c_ab, meas.i_c_ab, w_M_ref)
fbk.u_dc = meas.u_dc
return fbk

Expand Down
32 changes: 23 additions & 9 deletions motulator/drive/control/_im_observers.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
from typing import Callable

from motulator.common.utils import wrap
from motulator.drive.control._base import Measurements
from motulator.drive.utils._parameters import InductionMachineInvGammaPars


Expand Down Expand Up @@ -91,17 +90,17 @@ def __init__(
self._work = ObserverWorkspace()

def compute_output(
self, meas: Measurements, u_s_ab: complex, w_M: float | None
self, u_s_ab: complex, i_s_ab: complex, w_M: float | None
) -> ObserverOutputs:
"""
Compute the feedback signals for the control system.

Parameters
----------
meas : Measurements
Measured signals.
u_s_ab : complex
Stator voltage (V) in stator coordinates.
i_s_ab : complex
Stator current (A) in stator coordinates.
w_M : float, optional
Rotor speed (mechanical rad/s), either measured or estimated.

Expand All @@ -118,7 +117,7 @@ def compute_output(
out = ObserverOutputs(psi_R=self.state.psi_R, theta_s=self.state.theta_s)

# Current and voltage vectors in estimated rotor flux coordinates
out.i_s = exp(-1j * out.theta_s) * meas.i_s_ab
out.i_s = exp(-1j * out.theta_s) * i_s_ab
out.u_s = exp(-1j * out.theta_s) * u_s_ab

# Stator flux estimate
Expand Down Expand Up @@ -204,11 +203,26 @@ def __init__(
self.alpha_o = alpha_o

def compute_output(
self, meas: Measurements, u_s_ab: complex, w_M=None
self, u_s_ab: complex, i_s_ab: complex, w_M=None
) -> ObserverOutputs:
"""Compute feedback signals with speed estimation."""
w_M = self.state.w_m / self.par.n_p
out = super().compute_output(meas, u_s_ab, w_M)
"""
Compute feedback signals with speed estimation.

Parameters
----------
u_s_ab : complex
Stator voltage (V) in stator coordinates.
i_s_ab : complex
Stator current (A) in stator coordinates.

Returns
-------
out : ObserverOutputs
Estimated feedback signals for the control system, including speed estimate.

"""
w_M_est = self.state.w_m / self.par.n_p
out = super().compute_output(u_s_ab, i_s_ab, w_M_est)
self._work.w_r = out.w_r
return out

Expand Down
8 changes: 5 additions & 3 deletions motulator/drive/control/_sm_current_vector.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,11 +166,13 @@ def __init__(

def get_feedback(self, meas: Measurements) -> ObserverOutputs:
"""Get feedback signals."""
u_s_ab = self.pwm.get_realized_voltage()
u_c_ab = self.pwm.get_realized_voltage()
if self.observer.sensorless:
fbk = self.observer.compute_output(meas, u_s_ab)
fbk = self.observer.compute_output(u_c_ab, meas.i_c_ab)
else:
fbk = self.observer.compute_output(meas, u_s_ab, meas.w_M)
fbk = self.observer.compute_output(
u_c_ab, meas.i_c_ab, meas.w_M, meas.theta_M
)
fbk.u_dc = meas.u_dc
return fbk

Expand Down
12 changes: 7 additions & 5 deletions motulator/drive/control/_sm_flux_vector.py
Original file line number Diff line number Diff line change
Expand Up @@ -237,11 +237,13 @@ def __init__(

def get_feedback(self, meas: Measurements) -> ObserverOutputs:
"""Get feedback signals."""
u_s_ab = self.pwm.get_realized_voltage()
u_c_ab = self.pwm.get_realized_voltage()
if self.observer.sensorless:
fbk = self.observer.compute_output(meas, u_s_ab)
fbk = self.observer.compute_output(u_c_ab, meas.i_c_ab)
else:
fbk = self.observer.compute_output(meas, u_s_ab, meas.w_M)
fbk = self.observer.compute_output(
u_c_ab, meas.i_c_ab, meas.w_M, meas.theta_M
)
fbk.u_dc = meas.u_dc
return fbk

Expand Down Expand Up @@ -344,8 +346,8 @@ def __init__(

def get_feedback(self, w_M_ref: float, meas: Measurements) -> ObserverOutputs:
"""Get feedback signals."""
u_s_ab = self.pwm.get_realized_voltage()
fbk = self.observer.compute_output(meas, u_s_ab, w_M_ref)
u_c_ab = self.pwm.get_realized_voltage()
fbk = self.observer.compute_output(u_c_ab, meas.i_c_ab, w_M_ref)
fbk.u_dc = meas.u_dc
return fbk

Expand Down
Loading