Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

six phase motor - inprogress #267

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
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
u_sq V Quadrature axis voltage
u_sx V
u_sx V
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

u_sy

u_a1 V
u_a2 V
u_b1 V
u_b2 V
u_c1 V
u_c2 V
=============== ====== =============================================

======== ===========================================================
Limits / Nominal Value Dictionary Entries:
-------- -----------------------------------------------------------
Entry Description
======== ===========================================================
i General current limit / nominal value
======== ===========================================================

"""
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_sy"]

@property
def motor_parameter(self):
# Docstring of superclass
return self._motor_parameter

@property
def initializer(self):
# Docstring of superclass
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_nominal_values = ?rated
_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([
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be helpful to specify via comment which matrix column belongs to which regressor (please see other motor types for reference).

# 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]
Comment on lines +120 to +123
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks very good to me. Good job!


])
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(
[
omega,
state[self.I_SD_IDX],
state[self.I_SQ_IDX],
state[self.I_SX_IDX],
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],
omega * state[self.I_SY_IDX],
]
),
)


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)

#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 ?

Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
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]
])
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]
])
Comment on lines +60 to +67
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The first diagonal block entry has a positive sense of rotation, but the second one should have a negative. (As of now, both are coded the same).
For easier comprehension, I would suggest to define the 2D rotation matrix first as a function T(epsilon), and then compound this 6D rotation matrix from blkdiag[T(epsilon), T(-epsilon), eye(2)].

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The sense of rotation is still documented in the wrong way here

"""
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],
])
Comment on lines +82 to +87
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

scipy provides a command that should make this snippet more clear

https://docs.scipy.org/doc/scipy/reference/generated/scipy.linalg.block_diag.html

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()
101 changes: 101 additions & 0 deletions src/gym_electric_motor/physical_systems/physical_systems.py
Original file line number Diff line number Diff line change
Expand Up @@ -1111,3 +1111,104 @@ 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 __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
Loading