From 594291f30134ebeed7e1e64b6c408c291daef7bb Mon Sep 17 00:00:00 2001 From: RanilThomas Date: Wed, 19 Feb 2025 15:41:20 +0530 Subject: [PATCH 1/4] six phase motor - inprogress --- .../electric_motors/SixPhase_PMSM.py | 81 +++++++++++++++++++ .../electric_motors/six_phase_motor.py | 77 ++++++++++++++++++ 2 files changed, 158 insertions(+) create mode 100644 src/gym_electric_motor/physical_systems/electric_motors/SixPhase_PMSM.py create mode 100644 src/gym_electric_motor/physical_systems/electric_motors/six_phase_motor.py diff --git a/src/gym_electric_motor/physical_systems/electric_motors/SixPhase_PMSM.py b/src/gym_electric_motor/physical_systems/electric_motors/SixPhase_PMSM.py new file mode 100644 index 00000000..e835157b --- /dev/null +++ b/src/gym_electric_motor/physical_systems/electric_motors/SixPhase_PMSM.py @@ -0,0 +1,81 @@ +import math + +import numpy as np + +from .six_phase_motor import SixPhaseMotor + + +class SixPhasePMSM(SixPhaseMotor): + """ + ===================== ========== ============= =========================================== + Motor Parameter Unit Default Value Description + ===================== ========== ============= =========================================== + r_s Ohm 64.3e-3 Stator resistance + l_d H 125e-6 Direct axis inductance + l_q H 126e-6 Quadrature axis inductance + l_x H 39e-6 x-axis inductance + l_y H 35e-6 y-axis inductance + p 1 5 Pole pair number + psi_PM Vs 4.7e-3 flux linkage of the permanent magnets + ===================== ========== ============= =========================================== + + =============== ====== ============================================= + Motor Currents Unit Description + =============== ====== ============================================= + i_sd A Direct axis current + i_sq A Quadrature axis current + i_sx A + i_sy A + i_salpha A Stator current in alpha direction + i_sbeta A Stator current in beta direction + i_sX A + i_sY A + i_sa1 A + i_sa2 A + i_sb1 A + i_sb2 A + i_sc1 A + i_sc2 A + + =============== ====== ============================================= + =============== ====== ============================================= + Motor Voltages Unit Description + =============== ====== ============================================= + u_sd V Direct axis voltage + =============== ====== ============================================= + + ======== =========================================================== + Limits / Nominal Value Dictionary Entries: + -------- ----------------------------------------------------------- + Entry Description + ======== =========================================================== + i General current limit / nominal value + ======== =========================================================== + + """ +#### Parameters taken from https://ieeexplore.ieee.org/document/10372153 + _default_motor_parameter = { + "p": 5, + "l_d": 125e-6, + "l_q": 126e-6, + "l_x": 39e-6, + "l_y": 35e-6, + "r_s": 64.3e-3, + "psi_PM": 4.7e-3, + } + #_default_limits = ? + #_default_nominal_values = ? + #_model_constants = None + #_default_initializer = {"states": {?},"interval": None,"random_init": None,"random_params": (None, None),} + + + + @property + def motor_parameter(self): + # Docstring of superclass + return self._motor_parameter + + @property + def initializer(self): + # Docstring of superclass + return self._initializer \ No newline at end of file diff --git a/src/gym_electric_motor/physical_systems/electric_motors/six_phase_motor.py b/src/gym_electric_motor/physical_systems/electric_motors/six_phase_motor.py new file mode 100644 index 00000000..e49cda50 --- /dev/null +++ b/src/gym_electric_motor/physical_systems/electric_motors/six_phase_motor.py @@ -0,0 +1,77 @@ +import math + +import numpy as np + +from .electric_motor import ElectricMotor + + +class SixPhaseMotor(ElectricMotor): + """ + The SixPhaseMotor and its subclasses implement the technical system of Six Phase Motors. + + """ + + # transformation matrix from abc to alpha-beta representation + # VSD is the modeling approach used for multi-phase machines, which represents a generalized form of the clarke transformation + # for the six phase machine with Winding configuration in the stator- and rotor-fixed coordinate systems with δ =(2/3)*π and γ =π/6 + _t46= 1/ 3 * np.array([ + [1, -0.5, -0.5, 0.5 * np.sqrt(3), -0.5 * np.sqrt(3), 0], + [0, 0.5 * np.sqrt(3), -0.5 * np.sqrt(3), 0.5, 0.5, -1], + [1, -0.5, -0.5, -0.5 * np.sqrt(3), 0.5 * np.sqrt(3), 0], + [0, -0.5 * np.sqrt(3), 0.5 * np.sqrt(3), 0.5, 0.5, -1] + ]) + + # i_s - stator current + @staticmethod + def t_46(quantities): + """ + Transformation from abc representation to alpha-beta representation + + Args: + quantities: The properties in the abc representation like ''[i_sa1, i_sb1, i_sc1, i_sa2, i_sb2, i_sc2]'' + + Returns: + The converted quantities in the alpha-beta representation like ''[i_salpha, i_sbeta, i_sX, i_sY]'' + """ + return np.matmul(SixPhaseMotor._t46, quantities) + + @staticmethod + def q(quantities, epsilon): + """ + Transformation of the abc representation into dq using the electrical angle + + Args: + quantities: the properties in the abc representation like ''[i_sa1, i_sb1, i_sc1, i_sa2, i_sb2, i_sc2]'' + epsilon: electrical rotor position + + Returns: + The converted quantities in the dq representation like ''[i_sd, i_sq, i_sx, i_sy, i_s0+, i_s0-]''. + since 2N topology is considered (case where the neutral points are not connected) i_s0+, i_s0- will not be taken into account + """ + t_vsd = 1/ 3 * np.array([ + [1, -0.5, -0.5, 0.5 * np.sqrt(3), -0.5 * np.sqrt(3), 0], + [0, 0.5 * np.sqrt(3), -0.5 * np.sqrt(3), 0.5, 0.5, -1], + [1, -0.5, -0.5, -0.5 * np.sqrt(3), 0.5 * np.sqrt(3), 0], + [0, -0.5 * np.sqrt(3), 0.5 * np.sqrt(3), 0.5, 0.5, -1], + [1, 1, 1, 0, 0, 0], + [0, 0, 0, 1, 1, 1] + ]) + cos = math.cos(epsilon) + sin = math.sin(epsilon) + tp_alphaBetaXY = np.array([ + [cos, sin, 0, 0, 0, 0], + [-sin, cos, 0, 0, 0, 0], + [0, 0, cos, sin, 0, 0], + [0, 0, -sin, cos, 0, 0], + [0, 0, 0, 0, 1, 0] + [0, 0, 0, 0, 0, 1] + ]) + tp_vsd = np.matmul(tp_alphaBetaXY, t_vsd) + return np.matmul(tp_vsd, quantities) + + def _torque_limit(self): + """ + Returns: + Maximal possible torque for the given limits in self._limits + """ + raise NotImplementedError() \ No newline at end of file From 3a55e3393a0d1fe30df1cc03263919f0958f3db2 Mon Sep 17 00:00:00 2001 From: RanilThomas Date: Wed, 26 Feb 2025 20:45:13 +0530 Subject: [PATCH 2/4] ode for the sixphase PMSM --- .../electric_motors/SixPhase_PMSM.py | 122 +++++++++++++++--- .../physical_systems/physical_systems.py | 38 ++++++ 2 files changed, 143 insertions(+), 17 deletions(-) diff --git a/src/gym_electric_motor/physical_systems/electric_motors/SixPhase_PMSM.py b/src/gym_electric_motor/physical_systems/electric_motors/SixPhase_PMSM.py index e835157b..581e2c65 100644 --- a/src/gym_electric_motor/physical_systems/electric_motors/SixPhase_PMSM.py +++ b/src/gym_electric_motor/physical_systems/electric_motors/SixPhase_PMSM.py @@ -42,6 +42,9 @@ class SixPhasePMSM(SixPhaseMotor): Motor Voltages Unit Description =============== ====== ============================================= u_sd V Direct axis voltage + u_sq V Quadrature axis voltage + u_sx V + u_sx V =============== ====== ============================================= ======== =========================================================== @@ -53,22 +56,13 @@ class SixPhasePMSM(SixPhaseMotor): ======== =========================================================== """ -#### Parameters taken from https://ieeexplore.ieee.org/document/10372153 - _default_motor_parameter = { - "p": 5, - "l_d": 125e-6, - "l_q": 126e-6, - "l_x": 39e-6, - "l_y": 35e-6, - "r_s": 64.3e-3, - "psi_PM": 4.7e-3, - } - #_default_limits = ? - #_default_nominal_values = ? - #_model_constants = None - #_default_initializer = {"states": {?},"interval": None,"random_init": None,"random_params": (None, None),} - - + I_SD_IDX = 0 + I_SQ_IDX = 1 + I_SX_IDX = 2 + I_SY_IDX = 3 + CURRENTS_IDX = [0, 1, 2, 3] + CURRENTS = ["i_sd", "i_sq", "i_sx", "i_sy"] + VOLTAGES = ["u_sd", "u_sq", "u_sx", "u_sx"] @property def motor_parameter(self): @@ -78,4 +72,98 @@ def motor_parameter(self): @property def initializer(self): # Docstring of superclass - return self._initializer \ No newline at end of file + return self._initializer + + #### Parameters taken from https://ieeexplore.ieee.org/document/10372153 + _default_motor_parameter = {"p": 5, "l_d": 125e-6, "l_q": 126e-6, "l_x": 39e-6, "l_y": 35e-6, "r_s": 64.3e-3, "psi_PM": 4.7e-3,} + #_default_limits = ? + #_default_nominal_values = ? + _default_initializer = { + "states": {"i_sd": 0.0, "i_sq": 0.0, "i_sx": 0.0, "i_sy": 0.0}, + "interval": None, + "random_init": None, + "random_params": (None, None), + } + + _model_constants = None + + _initializer = None + + def __init__( + self, + motor_parameter=None, + nominal_values=None, + limit_values=None, + motor_initializer=None, + ): + # Docstring of superclass + nominal_values = nominal_values or {} + limit_values = limit_values or {} + super().__init__(motor_parameter, nominal_values, limit_values, motor_initializer) + self._update_model() + self._update_limits() + + def _update_model(self): + """Updates the motor's model parameters with the motor parameters. + + Called internally when the motor parameters are changed or the motor is initialized. + """ + mp = self._motor_parameter + self._model_constants = np.array([ + [-mp['r_s'], mp['l_q'], 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [ 0, 0, 0, -mp['r_s'], -mp['l_d'], -mp['psi_PM'], 1, 0, 0, 0, 0, 0, 0], + [ 0, 0, 0, 0, 0, 0, 0, -mp['r_s'], -mp['l_y'], 1, 0, 0, 0], + [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -mp['r_s'], mp['l_x'], 1] + + ]) + self._model_constants[self.I_SD_IDX] = self._model_constants[self.I_SD_IDX] / mp["l_d"] + self._model_constants[self.I_SQ_IDX] = self._model_constants[self.I_SQ_IDX] / mp["l_q"] + self._model_constants[self.I_SX_IDX] = self._model_constants[self.I_SX_IDX] / mp["l_x"] + self._model_constants[self.I_SY_IDX] = self._model_constants[self.I_SY_IDX] / mp["l_y"] + + + def electrical_ode(self, state, u_dqxy, omega, *_): + """ + The differential equation of the Six phase PMSM. + + Args: + state: The current state of the motor. [i_sd, i_sq, i_sx, i_sy] + omega: electrical rotational speed + u_qdxy: The input voltages [u_sd, u_sq, u_sx, u_sy] + + Returns: + The derivatives of the state vector d/dt([i_sd, i_sq, i_sx, i_sy]) + """ + return np.matmul( + self._model_constants, + np.array( + [ + state[self.I_SD_IDX], + omega * state[self.I_SQ_IDX], + u_dqxy[0], + state[self.I_SQ_IDX], + omega * state[self.I_SD_IDX], + omega, + u_dqxy[1], + state[self.I_SX_IDX], + omega * state[self.I_SY_IDX], + u_dqxy[2], + state[self.I_SY_IDX], + omega * state[self.I_SX_IDX], + u_dqxy[3] + ] + ), + ) + + + def i_in(self, state): + # Docstring of superclass + return state[self.CURRENTS_IDX] + + def reset(self, state_space, state_positions, **__): + # Docstring of superclass + if self._initializer and self._initializer["states"]: + self.initialize(state_space, state_positions) + return np.asarray(list(self._initial_states.values())) + else: + return np.zeros(len(self.CURRENTS) + 1) diff --git a/src/gym_electric_motor/physical_systems/physical_systems.py b/src/gym_electric_motor/physical_systems/physical_systems.py index 5e8640e9..d5d7ca4a 100644 --- a/src/gym_electric_motor/physical_systems/physical_systems.py +++ b/src/gym_electric_motor/physical_systems/physical_systems.py @@ -1111,3 +1111,41 @@ def reset(self, *_): ] ) return system_state / self._limits + +class SixPhaseMotorSystem(SCMLSystem): + """ + SCML-System that implements the basic transformations needed for six phase drives. + """ + + def abc_to_alphabeta_space(self, abc_quantities): + """ + Transformation from abc to alphabeta space + + Args: + abc_quantities: The properties in the abc representation like ''[i_sa1, i_sb1, i_sc1, i_sa2, i_sb2, i_sc2]'' + + Returns: + The converted quantities in the alpha-beta representation like ''[i_salpha, i_sbeta, i_sX, i_sY]'' + """ + alphabeta_quantity = self._electrical_motor.t_46(abc_quantities) + return alphabeta_quantity + + def abc_to_dq_space(self, abc_quantities,epsilon): + """ + Transformation of the abc representation into dq using the electrical angle + + Args: + abc_quantities: the properties in the abc representation like ''[i_sa1, i_sb1, i_sc1, i_sa2, i_sb2, i_sc2]'' + epsilon: electrical rotor position + + Returns: + The converted quantities in the dq representation like ''[i_sd, i_sq, i_sx, i_sy,]''. + """ + dqxy_quantity = self._electrical_motor.q(abc_quantities,epsilon) + return dqxy_quantity + +class SixPhasePMSM(SixPhaseMotorSystem): + def _build_state_space(self, state_names): + raise NotImplementedError + + #def _build_state_names(self): ? \ No newline at end of file From e85433735658a96c354422f5e43bf3bf3e41bb4f Mon Sep 17 00:00:00 2001 From: RanilThomas Date: Thu, 6 Mar 2025 01:38:14 +0530 Subject: [PATCH 3/4] six phase - corrections as per the review --- .../electric_motors/SixPhase_PMSM.py | 48 +++++++++---- .../electric_motors/six_phase_motor.py | 26 ++++++- .../physical_systems/physical_systems.py | 71 +++++++++++++++++-- 3 files changed, 124 insertions(+), 21 deletions(-) diff --git a/src/gym_electric_motor/physical_systems/electric_motors/SixPhase_PMSM.py b/src/gym_electric_motor/physical_systems/electric_motors/SixPhase_PMSM.py index 581e2c65..f424e842 100644 --- a/src/gym_electric_motor/physical_systems/electric_motors/SixPhase_PMSM.py +++ b/src/gym_electric_motor/physical_systems/electric_motors/SixPhase_PMSM.py @@ -45,6 +45,12 @@ class SixPhasePMSM(SixPhaseMotor): u_sq V Quadrature axis voltage u_sx V u_sx V + u_a1 V + u_a2 V + u_b1 V + u_b2 V + u_c1 V + u_c2 V =============== ====== ============================================= ======== =========================================================== @@ -62,7 +68,7 @@ class SixPhasePMSM(SixPhaseMotor): I_SY_IDX = 3 CURRENTS_IDX = [0, 1, 2, 3] CURRENTS = ["i_sd", "i_sq", "i_sx", "i_sy"] - VOLTAGES = ["u_sd", "u_sq", "u_sx", "u_sx"] + VOLTAGES = ["u_sd", "u_sq", "u_sx", "u_sy"] @property def motor_parameter(self): @@ -76,8 +82,8 @@ def initializer(self): #### Parameters taken from https://ieeexplore.ieee.org/document/10372153 _default_motor_parameter = {"p": 5, "l_d": 125e-6, "l_q": 126e-6, "l_x": 39e-6, "l_y": 35e-6, "r_s": 64.3e-3, "psi_PM": 4.7e-3,} - #_default_limits = ? - #_default_nominal_values = ? + #_default_limits = ?maximum + #_default_nominal_values = ?rated _default_initializer = { "states": {"i_sd": 0.0, "i_sq": 0.0, "i_sx": 0.0, "i_sy": 0.0}, "interval": None, @@ -110,10 +116,11 @@ def _update_model(self): """ mp = self._motor_parameter self._model_constants = np.array([ - [-mp['r_s'], mp['l_q'], 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [ 0, 0, 0, -mp['r_s'], -mp['l_d'], -mp['psi_PM'], 1, 0, 0, 0, 0, 0, 0], - [ 0, 0, 0, 0, 0, 0, 0, -mp['r_s'], -mp['l_y'], 1, 0, 0, 0], - [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -mp['r_s'], mp['l_x'], 1] + # omega, i_d, i_q, i_x, i_y, u_d, u_q, u_x, u_y, omega * i_d, omega * i_q, omega * i_x, omega * i_y + [ 0, -mp['r_s'], 0, 0, 0, 1, 0, 0, 0, 0, mp['l_q'], 0, 0], + [-mp['psi_PM'], 0, -mp['r_s'], 0, 0, 0, 1, 0, 0, -mp['l_d'], 0, 0, 0], + [ 0, 0, 0, -mp['r_s'], 0, 0, 0, 1, 0, 0, 0, 0, -mp['l_y']], + [ 0, 0, 0, 0, -mp['r_s'], 0, 0, 0, 1, 0, 0, mp['l_x'], 0] ]) self._model_constants[self.I_SD_IDX] = self._model_constants[self.I_SD_IDX] / mp["l_d"] @@ -138,19 +145,19 @@ def electrical_ode(self, state, u_dqxy, omega, *_): self._model_constants, np.array( [ + omega, state[self.I_SD_IDX], - omega * state[self.I_SQ_IDX], - u_dqxy[0], state[self.I_SQ_IDX], - omega * state[self.I_SD_IDX], - omega, - u_dqxy[1], state[self.I_SX_IDX], - omega * state[self.I_SY_IDX], - u_dqxy[2], state[self.I_SY_IDX], + u_dqxy[0], + u_dqxy[1], + u_dqxy[2], + u_dqxy[3], + omega * state[self.I_SD_IDX], + omega * state[self.I_SQ_IDX], omega * state[self.I_SX_IDX], - u_dqxy[3] + omega * state[self.I_SY_IDX], ] ), ) @@ -167,3 +174,14 @@ def reset(self, state_space, state_positions, **__): return np.asarray(list(self._initial_states.values())) else: return np.zeros(len(self.CURRENTS) + 1) + + #from pmsm + def torque(self, currents): + # Docstring of superclass + mp = self._motor_parameter + return ( + 1.5 * mp["p"] * (mp["psi_PM "] + (mp["l_d"] - mp["l_q"]) * currents[self.I_SD_IDX]) * currents[self.I_SQ_IDX] + ) + + #torque limit ? + \ No newline at end of file diff --git a/src/gym_electric_motor/physical_systems/electric_motors/six_phase_motor.py b/src/gym_electric_motor/physical_systems/electric_motors/six_phase_motor.py index e49cda50..bb2c99b0 100644 --- a/src/gym_electric_motor/physical_systems/electric_motors/six_phase_motor.py +++ b/src/gym_electric_motor/physical_systems/electric_motors/six_phase_motor.py @@ -48,6 +48,7 @@ def q(quantities, epsilon): The converted quantities in the dq representation like ''[i_sd, i_sq, i_sx, i_sy, i_s0+, i_s0-]''. since 2N topology is considered (case where the neutral points are not connected) i_s0+, i_s0- will not be taken into account """ + """ t_vsd = 1/ 3 * np.array([ [1, -0.5, -0.5, 0.5 * np.sqrt(3), -0.5 * np.sqrt(3), 0], [0, 0.5 * np.sqrt(3), -0.5 * np.sqrt(3), 0.5, 0.5, -1], @@ -56,8 +57,6 @@ def q(quantities, epsilon): [1, 1, 1, 0, 0, 0], [0, 0, 0, 1, 1, 1] ]) - cos = math.cos(epsilon) - sin = math.sin(epsilon) tp_alphaBetaXY = np.array([ [cos, sin, 0, 0, 0, 0], [-sin, cos, 0, 0, 0, 0], @@ -66,8 +65,31 @@ def q(quantities, epsilon): [0, 0, 0, 0, 1, 0] [0, 0, 0, 0, 0, 1] ]) + """ + t_vsd = 1/ 3 * np.array([ + [1, -0.5, -0.5, 0.5 * np.sqrt(3), -0.5 * np.sqrt(3), 0], + [0, 0.5 * np.sqrt(3), -0.5 * np.sqrt(3), 0.5, 0.5, -1], + [1, -0.5, -0.5, -0.5 * np.sqrt(3), 0.5 * np.sqrt(3), 0], + [0, -0.5 * np.sqrt(3), 0.5 * np.sqrt(3), 0.5, 0.5, -1], + ]) + + def rotation_matrix(theta): + return np.array([ + [math.cos(theta), math.sin(theta)], + [-math.sin(theta), math.cos(theta)] + ]) + + t1 = rotation_matrix(epsilon) + t2 = rotation_matrix(-epsilon) + tp_alphaBetaXY = np.block([ + [t1, np.zeros(t1.shape[0],t1.shape[1])], + [np.zeros(t2.shape[0],t2.shape[1]), t2], + ]) tp_vsd = np.matmul(tp_alphaBetaXY, t_vsd) return np.matmul(tp_vsd, quantities) + + + def _torque_limit(self): """ diff --git a/src/gym_electric_motor/physical_systems/physical_systems.py b/src/gym_electric_motor/physical_systems/physical_systems.py index d5d7ca4a..98df4e4f 100644 --- a/src/gym_electric_motor/physical_systems/physical_systems.py +++ b/src/gym_electric_motor/physical_systems/physical_systems.py @@ -1145,7 +1145,70 @@ def abc_to_dq_space(self, abc_quantities,epsilon): return dqxy_quantity class SixPhasePMSM(SixPhaseMotorSystem): - def _build_state_space(self, state_names): - raise NotImplementedError - - #def _build_state_names(self): ? \ No newline at end of file + def __init__(self, control_space="dqxy", **kwargs): + """ + Args: + control_space(str):('abc' or 'dq') Choose, if actions the actions space is in dq or abc space + kwargs: Further arguments to pass tp SCMLSystem + """ + super().__init__(**kwargs) + self.control_space = control_space + if control_space == "dqxy": + assert ( + type(self._converter.action_space) == Box + ), "" + #self._action_space = ? how does it differ from a converter action space, what is the significance + + def _build_state_space(self, state_names): + # Docstring of superclass + low = -1 * np.ones_like(state_names, dtype=float) + low[self.U_SUP_IDX] = 0.0 + high = np.ones_like(state_names, dtype=float) + return Box(low, high, dtype=np.float64) + + def _build_state_names(self): + # Docstring of superclass + return self._mechanical_load.state_names + [ + "torque", + "i_a1", + "i_b1", + "i_c1", + "i_a2", + "i_b2", + "i_c2", + "i_sd", + "i_sq", + "i_sx", + "i_sy", + "u_a1", + "u_b1", + "u_c1", + "u_a2", + "u_b2", + "u_c2" + "u_sd", + "u_sq", + "u_sx", + "u_sy", + "epsilon", + "u_sup", + ] + #how to do - logic/know how ? + def _set_indices(self): + # Docstring of superclass + super()._set_indices() + self._omega_ode_idx = self._mechanical_load.OMEGA_IDX + self._load_ode_idx = list(range(len(self._mechanical_load.state_names))) + self._ode_currents_idx = list( + range( + self._load_ode_idx[-1] + 1, + self._load_ode_idx[-1] + 1 + len(self._electrical_motor.CURRENTS), + ) + ) + self._motor_ode_idx = self._ode_currents_idx + self._motor_ode_idx += [self._motor_ode_idx[-1] + 1] + self._ode_currents_idx = self._motor_ode_idx[:-1] + self.OMEGA_IDX = self.mechanical_load.OMEGA_IDX + #know why ? + #currents_lower = self.TORQUE_IDX + 1 + #currents_upper = currents_lower + 5 \ No newline at end of file From 010bb2aff61e3579546be4b81773d8badc2a105c Mon Sep 17 00:00:00 2001 From: RanilThomas Date: Thu, 13 Mar 2025 02:26:18 +0530 Subject: [PATCH 4/4] sixphase environment - in progress --- src/gym_electric_motor/__init__.py | 5 + src/gym_electric_motor/demoSixPhasePMSM.py | 11 ++ src/gym_electric_motor/envs/__init__.py | 3 + .../envs/gym_sixphase_pmsm/__init__.py | 1 + .../gym_sixphase_pmsm/cont_cc_sixpmsm_env.py | 85 ++++++++++++++ .../electric_motors/SixPhase_PMSM.py | 5 +- .../electric_motors/__init__.py | 3 + .../electric_motors/six_phase_motor.py | 37 +++++- .../physical_systems/physical_systems.py | 111 ++++++++++++++++-- 9 files changed, 250 insertions(+), 11 deletions(-) create mode 100644 src/gym_electric_motor/demoSixPhasePMSM.py create mode 100644 src/gym_electric_motor/envs/gym_sixphase_pmsm/__init__.py create mode 100644 src/gym_electric_motor/envs/gym_sixphase_pmsm/cont_cc_sixpmsm_env.py diff --git a/src/gym_electric_motor/__init__.py b/src/gym_electric_motor/__init__.py index 828f1163..cab89988 100644 --- a/src/gym_electric_motor/__init__.py +++ b/src/gym_electric_motor/__init__.py @@ -281,3 +281,8 @@ register( id="Cont-SC-DFIM-v0", entry_point=envs_path + "ContSpeedControlDoublyFedInductionMotorEnv", **registration_kwargs ) + +#sixphase machines +register( + id="Cont-CC-SIXPMSM-v0", entry_point=envs_path + "ContCurrentControlSixPhasePermanentMagnetSynchronousMotorEnv", **registration_kwargs +) \ No newline at end of file diff --git a/src/gym_electric_motor/demoSixPhasePMSM.py b/src/gym_electric_motor/demoSixPhasePMSM.py new file mode 100644 index 00000000..88e5fdc7 --- /dev/null +++ b/src/gym_electric_motor/demoSixPhasePMSM.py @@ -0,0 +1,11 @@ +import gym_electric_motor as gem +from gym_electric_motor.reference_generators import LaplaceProcessReferenceGenerator + + # Select a different ode_solver with default parameters by passing a keystring +env = gem.make( + "Cont-CC-SIXPMSM-v0",) +terminated = True +for _ in range(1000): + if terminated: + state, reference = env.reset() + (state, reference), reward, terminated, truncated, _ = env.step(env.action_space.sample()) \ No newline at end of file diff --git a/src/gym_electric_motor/envs/__init__.py b/src/gym_electric_motor/envs/__init__.py index 475000ff..43a2f6c1 100644 --- a/src/gym_electric_motor/envs/__init__.py +++ b/src/gym_electric_motor/envs/__init__.py @@ -50,6 +50,9 @@ from .gym_eesm.finite_tc_eesm_env import ( FiniteTorqueControlExternallyExcitedSynchronousMotorEnv, ) +from .gym_sixphase_pmsm.cont_cc_sixpmsm_env import ( + ContCurrentControlSixPhasePermanentMagnetSynchronousMotorEnv, +) from .gym_im import ( ContCurrentControlDoublyFedInductionMotorEnv, ContCurrentControlSquirrelCageInductionMotorEnv, diff --git a/src/gym_electric_motor/envs/gym_sixphase_pmsm/__init__.py b/src/gym_electric_motor/envs/gym_sixphase_pmsm/__init__.py new file mode 100644 index 00000000..8b1f8646 --- /dev/null +++ b/src/gym_electric_motor/envs/gym_sixphase_pmsm/__init__.py @@ -0,0 +1 @@ +from .cont_cc_sixpmsm_env import ContCurrentControlSixPhasePermanentMagnetSynchronousMotorEnv \ No newline at end of file diff --git a/src/gym_electric_motor/envs/gym_sixphase_pmsm/cont_cc_sixpmsm_env.py b/src/gym_electric_motor/envs/gym_sixphase_pmsm/cont_cc_sixpmsm_env.py new file mode 100644 index 00000000..bc7d05a7 --- /dev/null +++ b/src/gym_electric_motor/envs/gym_sixphase_pmsm/cont_cc_sixpmsm_env.py @@ -0,0 +1,85 @@ + +from gym_electric_motor import physical_systems as ps +from gym_electric_motor.constraints import SquaredConstraint +from gym_electric_motor.core import ( + ElectricMotorEnvironment, + ElectricMotorVisualization, + ReferenceGenerator, + RewardFunction, +) +from gym_electric_motor.physical_systems.physical_systems import SixPhasePMSM +from gym_electric_motor.reference_generators import ( + MultipleReferenceGenerator, + WienerProcessReferenceGenerator, +) +from gym_electric_motor.reward_functions import WeightedSumOfErrors +from gym_electric_motor.utils import initialize +from gym_electric_motor.visualization import MotorDashboard + + +class ContCurrentControlSixPhasePermanentMagnetSynchronousMotorEnv(ElectricMotorEnvironment): + + def __init__( + self, + supply=None, + converter=None, + motor=None, + load=None, + ode_solver=None, + reward_function=None, + reference_generator=None, + visualization=None, + state_filter=None, + callbacks=(), + constraints=(SquaredConstraint(("i_sq", "i_sd", "i_sx", "i_sy")),), + calc_jacobian=True, + tau=1e-4, + physical_system_wrappers=(), + **kwargs, + ): + + default_subgenerators = ( + WienerProcessReferenceGenerator(reference_state="i_sd"), + WienerProcessReferenceGenerator(reference_state="i_sq"), + WienerProcessReferenceGenerator(reference_state="i_sx"), + WienerProcessReferenceGenerator(reference_state="i_sy") + ) + + physical_system = SixPhasePMSM( + supply=initialize(ps.VoltageSupply, supply, ps.IdealVoltageSupply, dict(u_nominal=300.0)), + converter=initialize(ps.PowerElectronicConverter, converter, ps.ContB6BridgeConverter, dict()), + motor=initialize(ps.ElectricMotor, motor, ps.electric_motors.SixPhasePMSM, dict()), + load=initialize(ps.MechanicalLoad, load, ps.PolynomialStaticLoad, dict(load_parameter=dict(a=0.01, b=0.01, c=0.0))), + ode_solver=initialize(ps.OdeSolver, ode_solver, ps.ScipyOdeSolver, dict()), + calc_jacobian=calc_jacobian, + tau=tau, + ) + reference_generator = initialize( + ReferenceGenerator, + reference_generator, + MultipleReferenceGenerator, + dict(sub_generators=default_subgenerators), + ) + reward_function = initialize( + RewardFunction, + reward_function, + WeightedSumOfErrors, + dict(reward_weights=dict(i_sd=0.5, i_sq=0.5, i_sx=0.5, i_sy=0.5,)), + ) + visualization = initialize( + ElectricMotorVisualization, + visualization, + MotorDashboard, + dict(state_plots=("i_sd", "i_sq"), action_plots="all"), + ) + super().__init__( + physical_system=physical_system, + reference_generator=reference_generator, + reward_function=reward_function, + constraints=constraints, + visualization=visualization, + state_filter=state_filter, + callbacks=callbacks, + physical_system_wrappers=physical_system_wrappers, + **kwargs, + ) \ No newline at end of file diff --git a/src/gym_electric_motor/physical_systems/electric_motors/SixPhase_PMSM.py b/src/gym_electric_motor/physical_systems/electric_motors/SixPhase_PMSM.py index f424e842..8ba4b8d1 100644 --- a/src/gym_electric_motor/physical_systems/electric_motors/SixPhase_PMSM.py +++ b/src/gym_electric_motor/physical_systems/electric_motors/SixPhase_PMSM.py @@ -81,8 +81,9 @@ def initializer(self): return self._initializer #### Parameters taken from https://ieeexplore.ieee.org/document/10372153 - _default_motor_parameter = {"p": 5, "l_d": 125e-6, "l_q": 126e-6, "l_x": 39e-6, "l_y": 35e-6, "r_s": 64.3e-3, "psi_PM": 4.7e-3,} - #_default_limits = ?maximum + _default_motor_parameter = {"p": 5, "l_d": 125e-6, "l_q": 126e-6, "l_x": 39e-6, "l_y": 35e-6, "r_s": 64.3e-3, "psi_PM": 4.7e-3, "j_rotor": 0.0110,} + # + _default_limits =dict(omega=4e3 * np.pi / 30, torque=0.0, i=400, epsilon=math.pi, u=300) #_default_nominal_values = ?rated _default_initializer = { "states": {"i_sd": 0.0, "i_sq": 0.0, "i_sx": 0.0, "i_sy": 0.0}, diff --git a/src/gym_electric_motor/physical_systems/electric_motors/__init__.py b/src/gym_electric_motor/physical_systems/electric_motors/__init__.py index e6a096dd..9d11c27d 100644 --- a/src/gym_electric_motor/physical_systems/electric_motors/__init__.py +++ b/src/gym_electric_motor/physical_systems/electric_motors/__init__.py @@ -21,3 +21,6 @@ # Three Phase Motors from .three_phase_motor import ThreePhaseMotor + +#six phase motors +from .SixPhase_PMSM import SixPhasePMSM \ No newline at end of file diff --git a/src/gym_electric_motor/physical_systems/electric_motors/six_phase_motor.py b/src/gym_electric_motor/physical_systems/electric_motors/six_phase_motor.py index bb2c99b0..6afb821a 100644 --- a/src/gym_electric_motor/physical_systems/electric_motors/six_phase_motor.py +++ b/src/gym_electric_motor/physical_systems/electric_motors/six_phase_motor.py @@ -88,8 +88,41 @@ def rotation_matrix(theta): tp_vsd = np.matmul(tp_alphaBetaXY, t_vsd) return np.matmul(tp_vsd, quantities) - - + + @staticmethod + def q_inv(quantities, epsilon): + """ + Transformation of the dq representation into abc + + Args: + quantities: the properties in the dq representation like ''[i_sd, i_sq, i_sx, i_sy, i_s0+, i_s0-]''. + epsilon: electrical rotor position + + Returns: + The converted quantities in the abc representation like ''[i_sa1, i_sb1, i_sc1, i_sa2, i_sb2, i_sc2]''. + + """ + t_vsd = 1/ 3 * np.array([ + [1, -0.5, -0.5, 0.5 * np.sqrt(3), -0.5 * np.sqrt(3), 0], + [0, 0.5 * np.sqrt(3), -0.5 * np.sqrt(3), 0.5, 0.5, -1], + [1, -0.5, -0.5, -0.5 * np.sqrt(3), 0.5 * np.sqrt(3), 0], + [0, -0.5 * np.sqrt(3), 0.5 * np.sqrt(3), 0.5, 0.5, -1], + [1, 1, 1, 0, 0, 0], + [0, 0, 0, 1, 1, 1] + ]) + cos = math.cos(epsilon) + sin = math.sin(epsilon) + tp_alphaBetaXY = np.array([ + [cos, sin, 0, 0, 0, 0], + [-sin, cos, 0, 0, 0, 0], + [0, 0, cos, -sin, 0, 0], + [0, 0, sin, cos, 0, 0], + [0, 0, 0, 0, 1, 0], + [0, 0, 0, 0, 0, 1], + ]) + tp_vsd = np.matmul(tp_alphaBetaXY, t_vsd) + inv_tpVsd = np.linalg.inv(tp_vsd) + return np.matmul(inv_tpVsd, quantities) def _torque_limit(self): """ diff --git a/src/gym_electric_motor/physical_systems/physical_systems.py b/src/gym_electric_motor/physical_systems/physical_systems.py index 98df4e4f..c0edb1fd 100644 --- a/src/gym_electric_motor/physical_systems/physical_systems.py +++ b/src/gym_electric_motor/physical_systems/physical_systems.py @@ -1144,8 +1144,22 @@ def abc_to_dq_space(self, abc_quantities,epsilon): dqxy_quantity = self._electrical_motor.q(abc_quantities,epsilon) return dqxy_quantity + def dq_to_abc_space(self, dq_quantities,epsilon): + """ + Transformation of the dq representation into abc using the electrical angle + + Args: + dq_quantities: the properties in the dq representation like ''[i_sd, i_sq, i_sx, i_sy, i_s0+, i_s0-]''. + epsilon: electrical rotor position + + Returns: + The converted quantities in the abc representation like ''[i_sa1, i_sb1, i_sc1, i_sa2, i_sb2, i_sc2]''. + """ + abc_quantity = self._electrical_motor.q_inv(dq_quantities,epsilon) + return abc_quantity + class SixPhasePMSM(SixPhaseMotorSystem): - def __init__(self, control_space="dqxy", **kwargs): + def __init__(self, control_space="abc", **kwargs): """ Args: control_space(str):('abc' or 'dq') Choose, if actions the actions space is in dq or abc space @@ -1153,16 +1167,16 @@ def __init__(self, control_space="dqxy", **kwargs): """ super().__init__(**kwargs) self.control_space = control_space - if control_space == "dqxy": + if control_space == "abc": assert ( type(self._converter.action_space) == Box ), "" - #self._action_space = ? how does it differ from a converter action space, what is the significance + self._action_space = Box(-1, 1, shape=(4,), dtype=np.float64) def _build_state_space(self, state_names): # Docstring of superclass low = -1 * np.ones_like(state_names, dtype=float) - low[self.U_SUP_IDX] = 0.0 + low[self.U_SUP_IDX -1] = 0.0 high = np.ones_like(state_names, dtype=float) return Box(low, high, dtype=np.float64) @@ -1209,6 +1223,89 @@ def _set_indices(self): self._motor_ode_idx += [self._motor_ode_idx[-1] + 1] self._ode_currents_idx = self._motor_ode_idx[:-1] self.OMEGA_IDX = self.mechanical_load.OMEGA_IDX - #know why ? - #currents_lower = self.TORQUE_IDX + 1 - #currents_upper = currents_lower + 5 \ No newline at end of file + self.TORQUE_IDX = len(self.mechanical_load.state_names) + currents_lower = self.TORQUE_IDX + 1 + currents_upper = currents_lower + 10 # considering six stator currents in abc and four currents in dqxy + self.CURRENTS_IDX = list(range(currents_lower, currents_upper)) + voltages_lower = currents_upper + voltages_upper = voltages_lower + 10 + self.VOLTAGES_IDX = list(range(voltages_lower, voltages_upper)) + self.EPSILON_IDX = voltages_upper + self.U_SUP_IDX = self.EPSILON_IDX + 1 + self._ode_epsilon_idx = self._motor_ode_idx[-1] + + def simulate(self, action, *_, **__): + # Docstring of superclass + ode_state = self._ode_solver.y + eps = ode_state[self._ode_epsilon_idx] + if self.control_space == "dq": + action = self.dq_to_abc_space(action, eps) + i_in = self.dq_to_abc_space(self._electrical_motor.i_in(ode_state[self._ode_currents_idx]), eps) + switching_times = self._converter.set_action(action, self._t) + + for t in switching_times[:-1]: + i_sup = self._converter.i_sup(i_in) + u_sup = self._supply.get_voltage(self._t, i_sup) + u_in = self._converter.convert(i_in, self._ode_solver.t) + u_in = [u * u_s for u in u_in for u_s in u_sup] + u_dq = self.abc_to_dq_space(u_in, eps) + self._ode_solver.set_f_params(u_dq) + ode_state = self._ode_solver.integrate(t) + eps = ode_state[self._ode_epsilon_idx] + i_in = self.dq_to_abc_space(self._electrical_motor.i_in(ode_state[self._ode_currents_idx]), eps) + + i_sup = self._converter.i_sup(i_in) + u_sup = self._supply.get_voltage(self._t, i_sup) + u_in = self._converter.convert(i_in, self._ode_solver.t) + u_in = [u * u_s for u in u_in for u_s in u_sup] + u_dq = self.abc_to_dq_space(u_in, eps) + self._ode_solver.set_f_params(u_dq) + ode_state = self._ode_solver.integrate(self._t + self._tau) + self._t = self._ode_solver.t + self._k += 1 + torque = self._electrical_motor.torque(ode_state[self._motor_ode_idx]) + mechanical_state = ode_state[self._load_ode_idx] + i_dq = ode_state[self._ode_currents_idx] + i_abc = list(self.dq_to_abc_space(i_dq, eps)) + eps = ode_state[self._ode_epsilon_idx] % (2 * np.pi) + if eps > np.pi: + eps -= 2 * np.pi + + system_state = np.concatenate((mechanical_state, [torque], i_abc, i_dq, u_in, u_dq, [eps], u_sup)) + return system_state / self._limits + + def reset(self, *_): + # Docstring of superclass + motor_state = self._electrical_motor.reset(state_space=self.state_space, state_positions=self.state_positions) + mechanical_state = self._mechanical_load.reset( + state_positions=self.state_positions, + state_space=self.state_space, + nominal_state=self.nominal_state, + ) + ode_state = np.concatenate((mechanical_state, motor_state)) + u_sup = self.supply.reset() + eps = ode_state[self._ode_epsilon_idx] + if eps > np.pi: + eps -= 2 * np.pi + u_abc = self.converter.reset() + u_abc = [u * u_s for u in u_abc for u_s in u_sup] + u_dq = self.abc_to_dq_space(u_abc, eps) + i_dq = ode_state[self._ode_currents_idx] + i_abc = self.dq_to_abc_space(i_dq, eps) + torque = self.electrical_motor.torque(motor_state) + self._t = 0 + self._k = 0 + self._ode_solver.set_initial_value(ode_state, self._t) + system_state = np.concatenate( + ( + mechanical_state, + [torque], + i_abc, + i_dq, + u_abc, + u_dq, + [eps], + u_sup, + ) + ) + return system_state / self._limits \ No newline at end of file