Skip to content

Commit

Permalink
Refactoring of the control package (#122)
Browse files Browse the repository at this point in the history
* Refactoring for better modularity, mostly in the control modules. The documentation is not up to date. Many planned aspects not yet done, but this commit aims to show one possible approach.

* Some naming changes

* Grouping the configuration parameters, cleaning the naming conventions.

* Fix some naming

* Simplifications in naming conventions

* Observer-based V/Hz control added after refactoring

* Starting to revise docstrings

* Improved docstrings. Global imports are used in order to enable Sphinx to find the base classes properly.

* For testing the documentation and examples

* Move RateLimiter in V/Hz control from the configuration dataclass to the actual controller class

* Testing if the newest Sphinx works

* Testing Sphinx 7.1.2

* Testing Sphinx

* Change the doc page address back to the original

* Major refactoring of system models

---------

Co-authored-by: Lauri Tiitinen <85596019+lauritapio@users.noreply.github.com>
  • Loading branch information
mhinkkan and lauritapio authored Jun 5, 2024
1 parent a3a9421 commit 6b4bb46
Show file tree
Hide file tree
Showing 62 changed files with 3,562 additions and 4,517 deletions.
2 changes: 1 addition & 1 deletion docs/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ numpy
scipy
matplotlib
numpydoc
sphinx==6.2.1
sphinx==7.1.2
sphinx-autoapi
sphinx-copybutton
sphinx-gallery
Expand Down
3 changes: 2 additions & 1 deletion docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
author = "Aalto Electric Drives"

# The full version, including alpha/beta/rc tags
release = "0.2.0"
release = "0.3.0"

# -- General configuration ---------------------------------------------------

Expand Down Expand Up @@ -90,6 +90,7 @@
html_theme_options = {
"repository_url": "https://github.com/Aalto-Electric-Drives/motulator",
"use_repository_button": True,
"navigation_with_keys": False,
}

# Add any paths that contain custom static files (such as style sheets) here,
Expand Down
2 changes: 1 addition & 1 deletion docs/source/control/current_ctrl.rst
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ The other parts of the above controller are not affected by the saturation. The
Synchronous Machines
--------------------

The flux-based control algorithms :eq:`cc_flux` and :eq:`cc_disturbance` can be directly used for both non-salient and salient synchronous machines by mapping the stator current to the flux linkage, [#Awa2019]_
The flux-based control algorithms :eq:`cc_flux` and :eq:`cc_disturbance` can be directly used for both non-salient and salient synchronous machines by mapping the stator current to the flux linkage, [#Awa2019]_

.. math::
\boldsymbol{\psi}_\mathrm{ref} &= \hat{L}_\mathrm{d}\mathrm{Re}\{\boldsymbol{i}_\mathrm{s,ref}\} + \mathrm{j} \hat{L}_\mathrm{q}\mathrm{Im}\{\boldsymbol{i}_\mathrm{s,ref}\} \\
Expand Down
2 changes: 1 addition & 1 deletion docs/source/installation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ Several powerful open-source IDEs are available for Python. The following instru

3) Launch VS Code from the cloned project's root directory on the command line (or choose the proper directory after launching VS Code).
4) Create a virtual environment in the workspace using the instructions provided here: https://code.visualstudio.com/docs/python/environments.
5) Enable installation of suggested requirements in the virtual environment (at least ``requirements.txt``). Alternatively, you may run the command ``pip install -r requirements.txt`` in the VS Code terminal after the virtual environment is created and activated.
5) Enable installation of optional dependencies from ``pyproject.toml`` (selecting at least ``dev`` is recommended). Alternatively, for installing the project with its core dependencies, you may run the command ``pip install .`` in the VS Code terminal after the virtual environment is created and activated (or to include optional dependencies, run ``pip install .[dev,doc]``).

After completing the above steps, the virtual environment can be found in the ``.venv`` directory at the root of the repository. Now you should be able to run all the examples as well as to modify the existing code. When you start VS Code next time, it will automatically detect the virtual environment and use it.

Expand Down
8 changes: 4 additions & 4 deletions docs/source/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,15 @@ After :doc:`installation`, *motulator* can be used by creating a continuous-time
# Discrete-time controller
par = control.im.ModelPars(
R_s=3.7, R_R=2.1, L_sgm=.021, L_M=.224, n_p=2, J=.015)
ref = control.im.CurrentReferencePars(par, i_s_max=1.5*np.sqrt(2)*5)
ctrl = control.im.VectorCtrl(par, ref)
cfg = control.im.CurrentReferenceCfg(par, max_i_s=1.5*np.sqrt(2)*5)
ctrl = control.im.VectorCtrl(par, cfg)
# Acceleration at t = 0.2 s and load torque step of 14 Nm at t = 0.75 s
ctrl.w_m_ref = lambda t: (t > .2)*(2*np.pi*50)
ctrl.ref.w_m = lambda t: (t > .2)*(2*np.pi*50)
mdl.mechanics.tau_L_t = lambda t: (t > .75)*14
# Create a simulation object, simulate, and plot example figures
sim = model.Simulation(mdl, ctrl, pwm=False)
sim = model.Simulation(mdl, ctrl)
sim.simulate(t_stop=1.5)
plot(sim)
Expand Down
27 changes: 13 additions & 14 deletions examples/flux_vector/plot_flux_vector_pmsm_2kw.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,46 +2,45 @@
2.2-kW PMSM
===========
This example simulates sensorless stator-flux-vector control of a 2.2-kW PMSM
drive.
This example simulates sensorless flux-vector control of a 2.2-kW PMSM drive.
"""

# %%
# Imports.

from motulator import model, control
from motulator import BaseValues, plot
from motulator import BaseValues, NominalValues, plot

# %%
# Compute base values based on the nominal values (just for figures).

base = BaseValues(
U_nom=370, I_nom=4.3, f_nom=75, tau_nom=14, P_nom=2.2e3, n_p=3)
nom = NominalValues(U=370, I=4.3, f=75, P=2.2e3, tau=14)
base = BaseValues.from_nominal(nom, n_p=2)

# %%
# Configure the system model.

machine = model.sm.SynchronousMachine(
machine = model.SynchronousMachine(
n_p=3, R_s=3.6, L_d=.036, L_q=.051, psi_f=.545)
mechanics = model.Mechanics(J=.015)
converter = model.Inverter(u_dc=540)
mdl = model.sm.Drive(machine, mechanics, converter)
mdl = model.Drive(converter, machine, mechanics)

# %%
# Configure the control system.

par = control.sm.ModelPars(
n_p=3, R_s=3.6, L_d=.036, L_q=.051, psi_f=.545, J=.015)
ref = control.sm.FluxTorqueReferencePars(par, i_s_max=1.5*base.i, k_u=.9)
ctrl = control.sm.FluxVectorCtrl(par, ref, sensorless=True)
cfg = control.sm.FluxTorqueReferenceCfg(par, max_i_s=1.5*base.i, k_u=.9)
ctrl = control.sm.FluxVectorCtrl(par, cfg, T_s=250e-6, sensorless=True)

# %%
# Set the speed reference and the external load torque.

# Simple acceleration and load torque step
ctrl.w_m_ref = lambda t: (t > .2)*(2*base.w)
mdl.mechanics.tau_L_t = lambda t: (t > .8)*base.tau_nom*.7
# Speed reference (electrical rad/s)
ctrl.ref.w_m = lambda t: (t > .2)*2*base.w

# Load torque step
mdl.mechanics.tau_L_t = lambda t: (t > .8)*nom.tau*.7

# %%
# Create the simulation object and simulate it.
Expand Down
28 changes: 13 additions & 15 deletions examples/flux_vector/plot_flux_vector_pmsyrm_5kw.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,7 @@
does.
"""

# %%
# Imports.

from os import path
import inspect
Expand All @@ -22,13 +20,13 @@
from scipy.optimize import minimize_scalar
import matplotlib.pyplot as plt
from motulator import model, control
from motulator import BaseValues, Sequence, plot
from motulator import BaseValues, NominalValues, Sequence, plot

# %%
# Compute base values based on the nominal values (just for figures).

base = BaseValues(
U_nom=370, I_nom=8.8, f_nom=60, tau_nom=29.2, P_nom=5.5e3, n_p=2)
nom = NominalValues(U=370, I=8.8, f=60, P=5.5e3, tau=29.2)
base = BaseValues.from_nominal(nom, n_p=2)

# %%
# Create a saturation model, which will be used in the machine model in the
Expand Down Expand Up @@ -143,14 +141,13 @@ def i_s(psi_s):
# %%
# Configure the system model.

machine = model.sm.SynchronousMachineSaturated(
n_p=2, R_s=.63, current=i_s, psi_s0=psi_s0)
machine = model.SynchronousMachine(n_p=2, R_s=.63, i_s=i_s, psi_s0=psi_s0)
# Magnetically linear PM-SyRM model for comparison
# machine = model.sm.SynchronousMachine(
# n_p=2, R_s=.63, L_d=18e-3, L_q=110e-3, psi_f=.47)
mechanics = model.Mechanics(J=.015)
converter = model.Inverter(u_dc=540)
mdl = model.sm.Drive(machine, mechanics, converter)
mdl = model.Drive(converter, machine, mechanics)
# mdl.pwm = model.CarrierComparison() # Enable the PWM model

# %%
Expand All @@ -160,22 +157,23 @@ def i_s(psi_s):
par = control.sm.ModelPars(
n_p=2, R_s=.63, L_d=18e-3, L_q=110e-3, psi_f=.47, J=.015)
# Limit the maximum reference flux to the base value
ref = control.sm.FluxTorqueReferencePars(
par, i_s_max=2*base.i, k_u=1, psi_s_max=base.psi)
ctrl = control.sm.FluxVectorCtrl(par, ref, sensorless=True)
cfg = control.sm.FluxTorqueReferenceCfg(
par, max_i_s=2*base.i, k_u=1, max_psi_s=base.psi)
ctrl = control.sm.FluxVectorCtrl(par, cfg, sensorless=True)
# Select a lower speed-estimation bandwidth to mitigate the saturation effects
ctrl.observer = control.sm.Observer(par, alpha_o=2*np.pi*40, sensorless=True)
ctrl.observer = control.sm.Observer(
control.sm.ObserverCfg(par, alpha_o=2*np.pi*40, sensorless=True))

# %%
# Set the speed reference and the external load torque.

# Speed reference
# Speed reference (electrical rad/s)
times = np.array([0, .125, .25, .375, .5, .625, .75, .875, 1])*4
values = np.array([0, 0, 1, 1, 0, -1, -1, 0, 0])*base.w
ctrl.w_m_ref = Sequence(times, values)
ctrl.ref.w_m = Sequence(times, values)
# External load torque
times = np.array([0, .125, .125, .875, .875, 1])*4
values = np.array([0, 0, 1, 1, 0, 0])*base.tau_nom
values = np.array([0, 0, 1, 1, 0, 0])*nom.tau
mdl.mechanics.tau_L_t = Sequence(times, values)

# %%
Expand Down
48 changes: 26 additions & 22 deletions examples/flux_vector/plot_flux_vector_syrm_7kw.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,18 @@
Naturally, the PM-flux estimation can be used in PM machine drives as well.
"""

# %%
# Imports.

import numpy as np
import matplotlib.pyplot as plt
from motulator import model, control
from motulator import BaseValues, Sequence, plot
from motulator import BaseValues, NominalValues, Sequence, plot

# %%
# Compute base values based on the nominal values (just for figures).

base = BaseValues(
U_nom=370, I_nom=15.5, f_nom=105.8, tau_nom=20.1, P_nom=6.7e3, n_p=2)
nom = NominalValues(U=370, I=15.5, f=105.8, P=6.7e3, tau=20.1)
base = BaseValues.from_nominal(nom, n_p=2)

# %%
# Create a saturation model, see the example
Expand All @@ -48,13 +46,13 @@ def i_s(psi_s):
# %%
# Configure the system model.

machine = model.sm.SynchronousMachineSaturated(n_p=2, R_s=.54, current=i_s)
machine = model.SynchronousMachine(n_p=2, R_s=.54, i_s=i_s, psi_s0=0)
# Magnetically linear SyRM model for comparison
# machine = model.sm.SynchronousMachine(
# n_p=2, R_s=.54, L_d=37e-3, L_q=6.2e-3, psi_f=0)
mechanics = model.Mechanics(J=.015)
converter = model.Inverter(u_dc=540)
mdl = model.sm.Drive(machine, mechanics, converter)
mdl = model.Drive(converter, machine, mechanics)

# %%
# Configure the control system. The saturation is not taken into account.
Expand All @@ -64,28 +62,29 @@ def i_s(psi_s):
par = control.sm.ModelPars(
n_p=2, R_s=.54, L_d=.7*37e-3, L_q=.8*6.2e-3, psi_f=0, J=.015)
# Disable MTPA since the control system does not consider the saturation
ref = control.sm.FluxTorqueReferencePars(
par, i_s_max=2*base.i, k_u=.9, psi_s_min=base.psi, psi_s_max=base.psi)
ctrl = control.sm.FluxVectorCtrl(par, ref, sensorless=True)
cfg = control.sm.FluxTorqueReferenceCfg(
par, max_i_s=2*base.i, k_u=.9, min_psi_s=base.psi, max_psi_s=base.psi)
ctrl = control.sm.FluxVectorCtrl(par, cfg, sensorless=True)
# Since the saturation is not considered in the control system, the speed
# estimation bandwidth is set to a lower value. Furthermore, the PM-flux
# disturbance estimation is enabled at speeds above 2*pi*20 rad/s (electrical).
ctrl.observer = control.sm.Observer(
par,
alpha_o=2*np.pi*40,
k_f=lambda w_m: max(.05*(np.abs(w_m) - 2*np.pi*20), 0),
sensorless=True)
control.sm.ObserverCfg(
par,
alpha_o=2*np.pi*40,
k_f=lambda w_m: max(.05*(np.abs(w_m) - 2*np.pi*20), 0),
sensorless=True))

# %%
# Set the speed reference and the external load torque.

# Speed reference
# Speed reference (electrical rad/s)
times = np.array([0, .125, .25, .375, .5, .625, .75, .875, 1])*4
values = np.array([0, 0, 1, 1, 0, -1, -1, 0, 0])*base.w
ctrl.w_m_ref = Sequence(times, values)
ctrl.ref.w_m = Sequence(times, values)
# External load torque
times = np.array([0, .125, .125, .875, .875, 1])*4
values = np.array([0, 0, 1, 1, 0, 0])*base.tau_nom
values = np.array([0, 0, 1, 1, 0, 0])*nom.tau
mdl.mechanics.tau_L_t = Sequence(times, values)

# %%
Expand All @@ -105,13 +104,18 @@ def i_s(psi_s):
# inductance errors and the magnetic saturation, it is nonzero even if the
# machine has no magnets.

mdl = sim.mdl.data # Continuous-time data
mdl = sim.mdl # Continuous-time data
ctrl = sim.ctrl.data # Discrete-time data
ctrl.t = ctrl.ref.t # Discrete time
plt.figure()
plt.plot(mdl.t, np.abs(mdl.psi_s)/base.psi, label=r"$\psi_\mathrm{s}$")
plt.plot(ctrl.t, np.abs(ctrl.psi_s)/base.psi, label=r"$\hat{\psi}_\mathrm{s}$")
plt.plot(ctrl.t, ctrl.psi_f/base.psi, label=r"$\hat{\psi}_\mathrm{f}$")
plt.plot(ctrl.t, ctrl.psi_s_ref/base.psi, "--", label=r"$\psi_\mathrm{s,ref}$")
plt.plot(
mdl.data.t,
np.abs(mdl.machine.data.psi_s)/base.psi,
label=r"$\psi_\mathrm{s}$")
plt.plot(
ctrl.t, np.abs(ctrl.fbk.psi_s)/base.psi, label=r"$\hat{\psi}_\mathrm{s}$")
plt.plot(ctrl.t, ctrl.fbk.psi_f/base.psi, label=r"$\hat{\psi}_\mathrm{f}$")
plt.plot(ctrl.t, ctrl.ref.psi_s/base.psi, "--", label=r"$\psi_\mathrm{s,ref}$")
plt.xlim(0, 4)
plt.xlabel("Time (s)")
plt.ylabel("Flux linkage (p.u.)")
Expand Down
22 changes: 10 additions & 12 deletions examples/obs_vhz/plot_obs_vhz_ctrl_im_2kw.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,53 +6,51 @@
drive.
"""

# %%
# Imports.

import numpy as np
from motulator import model, control
from motulator import BaseValues, Sequence, plot
from motulator import BaseValues, NominalValues, Sequence, plot

# %%
# Compute base values based on the nominal values (just for figures).

base = BaseValues(
U_nom=400, I_nom=5, f_nom=50, tau_nom=14.6, P_nom=2.2e3, n_p=2)
nom = NominalValues(U=400, I=5, f=50, P=2.2e3, tau=14.6)
base = BaseValues.from_nominal(nom, n_p=2)

# %%
# Configure the system model.

# Configure the induction machine using its inverse-Γ parameters
machine = model.im.InductionMachineInvGamma(
machine = model.InductionMachineInvGamma(
R_s=3.7, R_R=2.1, L_sgm=.021, L_M=.224, n_p=2)
mechanics = model.Mechanics(J=.015)
converter = model.Inverter(u_dc=540)
mdl = model.im.Drive(machine, mechanics, converter)
mdl = model.Drive(converter, machine, mechanics)

# %%
# Configure the control system.

# Inverse-Γ model parameter estimates
par = control.im.ModelPars(R_s=3.7, R_R=2.1, L_sgm=.021, L_M=.224, n_p=2)
ctrl_par = control.im.ObserverBasedVHzCtrlPars(
psi_s_nom=base.psi, i_s_max=1.5*base.i)
ctrl = control.im.ObserverBasedVHzCtrl(par, ctrl_par, T_s=250e-6)
cfg = control.im.ObserverBasedVHzCtrlCfg(
nom_psi_s=base.psi, max_i_s=1.5*base.i, slip_compensation=False)
ctrl = control.im.ObserverBasedVHzCtrl(par, cfg, T_s=250e-6)

# %%
# Set the speed reference.

# Speed reference
times = np.array([0, .125, .25, .375, .5, .625, .75, .875, 1])*4
values = np.array([0, 0, 1, 1, 0, -1, -1, 0, 0])*base.w
ctrl.w_m_ref = Sequence(times, values)
ctrl.ref.w_m = Sequence(times, values)

# %%
# Set the load torque reference.

# External load torque
times = np.array([0, .125, .125, .875, .875, 1])*4
values = np.array([0, 0, 1, 1, 0, 0])*base.tau_nom
values = np.array([0, 0, 1, 1, 0, 0])*nom.tau
mdl.mechanics.tau_L_t = Sequence(times, values)

# Quadratic load torque profile, e.g. pumps and fans (uncomment to enable)
Expand Down
Loading

0 comments on commit 6b4bb46

Please sign in to comment.