Skip to content

Commit 71a7380

Browse files
committed
docs: Add docstrings to MPC controller
1 parent 7e7c4e2 commit 71a7380

2 files changed

Lines changed: 90 additions & 28 deletions

File tree

docs/parts_gc/api_documentation/mpc_current_controller.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,8 @@ within the ``gym-electric-motor`` simulation environment.
9393
ActionType.Finite
9494
)
9595
96+
#physical_system_wrapper = [DeadTimeProcessor(steps=1)] # Dead time processor with 1 step delay
97+
#uncomment the above line to activate the DeadTimeProcessor
9698
env = gem.make(
9799
motor.env_id(),
98100
visualization=visu,
@@ -108,6 +110,8 @@ within the ``gym-electric-motor`` simulation environment.
108110
limit_values=limit_values,
109111
nominal_values=nominal_values
110112
),
113+
#physical_system_wrappers=physical_system_wrapper,
114+
#uncomment the above line to activate the DeadTimeProcessor
111115
)
112116
113117
visu.initialize()

src/gem_controllers/mpc_current_controller.py

Lines changed: 86 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,33 @@
22
from .gem_controller import GemController
33

44
class MPCCurrentController(GemController):
5+
"""
6+
Finite-Control Set Model Predictive Current Controller for PMSM and SynRM motors.
7+
8+
This controller implements FCS-MPC to directly evaluate switching states
9+
and optimize current tracking in field-oriented coordinates.
10+
11+
Args:
12+
env: Gym environment instance
13+
env_id: Environment ID string
14+
prediction_horizon: Prediction horizon length (default: 1)
15+
w_d: Weight for d-axis current error (default: 1.0)
16+
w_q: Weight for q-axis current error (default: 1.0)
17+
"""
18+
519
def __init__(self, env, env_id, prediction_horizon=1, w_d=1.0, w_q=1.0):
6-
"""
7-
Args:
8-
env: Gym environment instance
9-
env_id: Environment ID string
10-
prediction_horizon: Prediction horizon (N)
11-
w_d: Weight for d-axis current error
12-
w_q: Weight for q-axis current error
13-
"""
1420
super().__init__()
1521
self.env_id = env_id
1622
self.prediction_horizon = prediction_horizon
1723
self.w_d = w_d
1824
self.w_q = w_q
1925

20-
"""Assign self.step from the environment wrapper, default to 0 if no DeadTimeProcessor"""
26+
# Assign self.step from the environment wrapper, default to 0 if no DeadTimeProcessor
2127
ps_wrapper = env.unwrapped.physical_system
2228
self.step = getattr(ps_wrapper, 'dead_time', 0)
2329
print(f"DeadTimeProcessor steps: {self.step}")
2430

25-
"""Retrieve environment info and motor parameters"""
31+
# Retrieve environment info and motor parameters
2632
self.state_names = env.get_wrapper_attr('state_names')
2733
self.physical_system = env.get_wrapper_attr('physical_system')
2834
self.tau = self.physical_system.tau
@@ -31,35 +37,51 @@ def __init__(self, env, env_id, prediction_horizon=1, w_d=1.0, w_q=1.0):
3137
for key, value in self.motor_params.items():
3238
setattr(self, key, value)
3339

34-
"""Identify indices of key states and inputs"""
40+
# Identify indices of key states and inputs
3541
self.i_sd_idx = self.state_names.index('i_sd')
3642
self.i_sq_idx = self.state_names.index('i_sq')
3743
self.omega_idx = self.state_names.index('omega')
3844
self.u_sd_idx = self.state_names.index('u_sd')
3945
self.u_lim = self.limits[self.u_sd_idx]
4046

41-
"""Setup coordinate transforms and precompute voltage combinations"""
47+
# Setup coordinate transforms and precompute voltage combinations
4248
self.abc_to_dq = self.physical_system.abc_to_dq_space
4349
self.subactions = -np.power(-1, self.physical_system._converter._subactions)
4450
self.u_abc_k1 = self.u_lim * self.subactions
4551

46-
"""Load motor model constants and motor-specific state names"""
52+
# Load motor model constants and motor-specific state names
4753
self._model_constants = self.physical_system.electrical_motor._model_constants
4854
motor_type = type(self.physical_system.electrical_motor).__name__
4955
if motor_type in ["PermanentMagnetSynchronousMotor", "SynchronousReluctanceMotor"]:
5056
self.motor_state_names = ["i_sd", "i_sq", "epsilon"]
5157
else:
5258
raise NotImplementedError(f"MPC controller not implemented for motor type: {motor_type}")
5359

54-
"""Initialize delay compensation variables"""
60+
# Initialize delay compensation variables
5561
self.previous_voltage_idx = 0
5662
self.past_references = []
5763
self.extrapolation_order = 2
5864

59-
# -------- Delay Compensation Helpers ----------
60-
"""
61-
# Extrapolate reference for delay compensation using past references. (works for sinusodia only for e.g. alfa-beta frames)
6265
def _extrapolate_reference(self, current_ref, n=2):
66+
"""
67+
[REFERENCE IMPLEMENTATION - Not currently active]
68+
69+
Extrapolate reference for delay compensation using past references.
70+
Works for sinusoidal signals in alpha-beta frames.
71+
72+
This method uses polynomial extrapolation to predict future reference
73+
values based on past samples. Useful for systems with computational delays.
74+
75+
Args:
76+
current_ref: Current reference value
77+
n: Extrapolation order (default: 2 for quadratic extrapolation)
78+
79+
Returns:
80+
extrapolated_ref: Extrapolated reference value
81+
"""
82+
# Implementation kept as reference for future use
83+
# Uncomment and modify as needed for specific applications
84+
"""
6385
self.past_references.append(current_ref.copy())
6486
if len(self.past_references) > n + 1:
6587
self.past_references.pop(0)
@@ -69,10 +91,23 @@ def _extrapolate_reference(self, current_ref, n=2):
6991
ref_km1 = self.past_references[-2]
7092
ref_km2 = self.past_references[-3]
7193
return 6 * ref_k - 8 * ref_km1 + 3 * ref_km2
72-
"""
94+
"""
95+
# Currently using state estimation instead of reference extrapolation
96+
return current_ref
7397

74-
"""Estimate currents at next timestep using previous voltage for delay compensation"""
7598
def _estimate_currents(self, model_constants, x, omega, voltage_idx):
99+
"""
100+
Estimate currents at next timestep using previous voltage for delay compensation.
101+
102+
Args:
103+
model_constants: Motor model constants matrix
104+
x: Current state vector [i_sd, i_sq, epsilon]
105+
omega: Electrical rotor speed
106+
voltage_idx: Index of previously applied voltage vector
107+
108+
Returns:
109+
Estimated state vector at next timestep
110+
"""
76111
v_abc = self.u_abc_k1[voltage_idx]
77112
v_dq = np.transpose(
78113
np.array([self.abc_to_dq(v_abc, x[-1] + 0.5 * omega * self.tau)])
@@ -84,9 +119,22 @@ def _estimate_currents(self, model_constants, x, omega, voltage_idx):
84119
dx = model_constants @ ext_vec
85120
return x + self.tau * dx
86121

87-
# -------- Prediction / Cost Evaluation ----------
88-
"""Simulate all possible voltage sequences to find the one minimizing the cost"""
89122
def _simulate_sequence(self, model_constants, x, omega, ref_i_d, ref_i_q, depth):
123+
"""
124+
Simulate all possible voltage sequences to find the one minimizing the cost.
125+
126+
Args:
127+
model_constants: Motor model constants matrix
128+
x: Current state vector
129+
omega: Electrical rotor speed
130+
ref_i_d: D-axis current reference
131+
ref_i_q: Q-axis current reference
132+
depth: Current depth in prediction horizon
133+
134+
Returns:
135+
min_cost: Minimum cost found
136+
best_sequence: Best voltage sequence
137+
"""
90138
min_cost = float('inf')
91139
best_sequence = []
92140

@@ -101,7 +149,7 @@ def _simulate_sequence(self, model_constants, x, omega, ref_i_d, ref_i_q, depth)
101149
dx = model_constants @ ext_vec
102150
x_next = x + self.tau * dx
103151

104-
"""Compute cost based on tracking error"""
152+
# Compute cost based on tracking error
105153
cost = self.w_d * (x_next[0] - ref_i_d) ** 2 + self.w_q * (x_next[1] - ref_i_q) ** 2
106154

107155
if depth == self.prediction_horizon - 1:
@@ -119,22 +167,30 @@ def _simulate_sequence(self, model_constants, x, omega, ref_i_d, ref_i_q, depth)
119167

120168
return min_cost, best_sequence
121169

122-
# -------- Control Interface ----------
123-
"""Compute the best voltage index based on current state and reference"""
124170
def control(self, state, reference):
171+
"""
172+
Compute the optimal voltage vector based on current state and reference.
173+
174+
Args:
175+
state: Current motor state vector
176+
reference: Current reference vector [i_sd_ref, i_sq_ref]
177+
178+
Returns:
179+
best_idx: Index of optimal voltage vector
180+
"""
125181
x_measured = np.array([state[self.state_names.index(n)] * self.limits[self.state_names.index(n)]
126182
for n in self.motor_state_names])
127183
omega = state[self.omega_idx] * self.limits[self.omega_idx]
128184
ref_i_d = reference[0] * self.limits[self.i_sd_idx]
129185
ref_i_q = reference[1] * self.limits[self.i_sq_idx]
130186

131187
if self.step == 0:
132-
"""Without delay compensation"""
188+
# Without delay compensation
133189
_, best_sequence = self._simulate_sequence(
134190
self._model_constants, x_measured, omega, ref_i_d, ref_i_q, depth=0
135191
)
136192
else:
137-
"""With delay compensation"""
193+
# With delay compensation
138194
x_est = self._estimate_currents(self._model_constants, x_measured, omega, self.previous_voltage_idx)
139195
_, best_sequence = self._simulate_sequence(
140196
self._model_constants, x_est, omega, ref_i_d, ref_i_q, depth=0
@@ -144,7 +200,9 @@ def control(self, state, reference):
144200
self.previous_voltage_idx = best_idx
145201
return best_idx
146202

147-
"""Reset delay compensation state"""
148203
def reset(self):
204+
"""
205+
Reset the controller state including delay compensation variables.
206+
"""
149207
self.previous_voltage_idx = 0
150-
self.past_references = []
208+
self.past_references = []

0 commit comments

Comments
 (0)