22from .gem_controller import GemController
33
44class 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