import numpy as np
from gymnasium.spaces import Box, Discrete, MultiDiscrete
[docs]class PowerElectronicConverter:
"""
Base class for all converters in a SCMLSystem.
Properties:
| *voltages(tuple(float, float))*: Determines which output voltage polarities the converter can generate.
| E.g. (0, 1) - Only positive voltages / (-1, 1) Positive and negative voltages
| *currents(tuple(float, float))*: Determines which output current polarities the converter can generate.
| E.g. (0, 1) - Only positive currents / (-1, 1) Positive and negative currents
"""
#: gymnasium.Space that defines Minimum, Maximum and Dimension of possible output voltage of the converter
voltages = None
#: gymnasium.Space that defines Minimum, Maximum and Dimension of possible output current of the converter
currents = None
#: gymnasium.Space that defines the set of all possible actions for the converter
action_space = None
#: Default action that is taken after a reset.
_reset_action = None
@property
def tau(self):
"""Float: Time of one simulation step in seconds."""
return self._tau
@tau.setter
def tau(self, value):
self._tau = float(value)
def __init__(self, tau, interlocking_time=0.0):
"""
:param tau: Discrete time step of the system in seconds
:param interlocking_time: Interlocking time of the transistors in seconds
"""
self._tau = tau
self._interlocking_time = interlocking_time
self._action_start_time = 0.0
self._current_action = self._reset_action
[docs] def reset(self):
"""
Reset all converter states to a default.
Returns:
list(float): A default output voltage after reset(=0V).
"""
self._current_action = self._reset_action
self._action_start_time = 0.0
return [0.0]
[docs] def set_action(self, action, t):
"""
Set the next action of the converter at the beginning of a simulation step in the system.
Args:
action(element of action_space): The control action on the converter.
t(float): Time at the beginning of the simulation step in seconds.
Returns:
list(float): Times when a switching action occurs and the conversion function must be called by the system.
"""
self._action_start_time = t
self._current_action = action
return self._set_switching_pattern(action)
[docs] def i_sup(self, i_out):
"""
Calculate the current, the converter takes from the supply for the given output currents and the current
switching state.
Args:
i_out(list(float)): All currents flowing out of the converter and into the motor.
Returns:
float: The current drawn from the supply.
"""
raise NotImplementedError
[docs] def convert(self, i_out, t):
"""
The conversion function that converts the previously set action to an input voltage for the motor.
This function has to be called at least at every previously defined switching time, because the input voltage
for the motor might change at these times.
Args:
i_out(list(float)): All currents that flow out of the converter into the motor.
t(float): Current time of the system.
Returns:
list(float): List of all input voltages at the motor.
"""
raise NotImplementedError
def _set_switching_pattern(self, action):
"""
Method to calculate the switching pattern and corresponding switching times for the next time step.
At least, the next time step [t + tau] is returned.
Args:
action(instance of action_space): The action for the next time step.
Returns:
list(float): Switching times.
"""
self._switching_pattern = [action]
return [self._action_start_time + self._tau]
[docs]class NoConverter(PowerElectronicConverter):
"""Dummy Converter class used to directly transfer the supply voltage to the motor"""
# Dummy default values for voltages and currents.
# No real use other than to fit the current physical system architecture
voltages = Box(0, 1, shape=(3,), dtype=np.float64)
currents = Box(0, 1, shape=(3,), dtype=np.float64)
action_space = Box(low=np.array([]), high=np.array([]), dtype=np.float64)
[docs] def i_sup(self, i_out):
return i_out[0]
[docs] def convert(self, i_out, t):
return [1]
[docs]class ContDynamicallyAveragedConverter(PowerElectronicConverter):
"""
Base class for all continuously controlled converters that calculate the input voltages to the motor with a
dynamically averaged model over one time step.
This class also implements the interlocking time of the transistors as a discount on the output voltage.
"""
_reset_action = [0]
def __init__(self, tau=1e-4, **kwargs):
# Docstring in base class
super().__init__(tau=tau, **kwargs)
[docs] def set_action(self, action, t):
# Docstring in base class
return super().set_action(min(max(action, self.action_space.low), self.action_space.high), t)
[docs] def convert(self, i_out, t):
# Docstring in base class
return [
min(
max(
self._convert(i_out, t) - self._interlock(i_out, t),
self.voltages.low[0],
),
self.voltages.high[0],
)
]
def _convert(self, i_in, t):
"""
Calculate an idealized output voltage for the current active action neglecting interlocking times.
Args:
i_in(list(float)): Input currents of the motor
t(float): Time of the system
Returns:
float: Idealized output voltage neglecting interlocking times
"""
raise NotImplementedError
[docs] def i_sup(self, i_out):
# Docstring in base class
raise NotImplementedError
def _interlock(self, i_in, *_):
"""
Calculate the output voltage discount due to the interlocking time of the transistors
Args:
i_in(list(float)): list of all currents flowing into the motor.
"""
return np.sign(i_in[0]) / self._tau * self._interlocking_time
[docs]class FiniteConverter(PowerElectronicConverter):
"""
Base class for all finite converters.
"""
#: The switching states of the converter for the current action
_switching_pattern = []
#: The current switching state of the converter
_switching_state = 0
#: The action that is the default after reset
_reset_action = 0
def __init__(self, tau=1e-5, **kwargs):
# Docstring in base class
super().__init__(tau=tau, **kwargs)
[docs] def set_action(self, action, t):
assert self.action_space.contains(
action
), f"The selected action {action} is not a valid element of the action space {self.action_space}."
return super().set_action(action, t)
[docs] def convert(self, i_out, t):
# Docstring in base class
raise NotImplementedError
[docs] def i_sup(self, i_out):
# Docstring in base class
raise NotImplementedError
[docs]class FiniteOneQuadrantConverter(FiniteConverter):
"""
Key:
'Finite-1QC'
Switching States / Actions:
| 0: Transistor off.
| 1: Transistor on.
Action Space:
Discrete(2)
Output Voltages and Currents:
| voltages: Box(0, 1, shape=(1,))
| currents: Box(0, 1, shape=(1,))
"""
voltages = Box(0, 1, shape=(1,), dtype=np.float64)
currents = Box(0, 1, shape=(1,), dtype=np.float64)
action_space = Discrete(2)
[docs] def convert(self, i_out, t):
# Docstring in base class
return [self._current_action if i_out[0] >= 0 else 1]
[docs] def i_sup(self, i_out):
# Docstring in base class
return i_out[0] if self._current_action == 1 else 0
[docs]class FiniteTwoQuadrantConverter(FiniteConverter):
"""
Key:
'Finite-2QC'
Switching States / Actions:
| 0: Both Transistors off.
| 1: Upper Transistor on.
| 2: Lower Transistor on.
Action Space:
Discrete(3)
Output Voltages and Currents:
| voltages: Box(0, 1, shape=(1,))
| currents: Box(-1, 1, shape=(1,))
"""
voltages = Box(0, 1, shape=(1,), dtype=np.float64)
currents = Box(-1, 1, shape=(1,), dtype=np.float64)
action_space = Discrete(3)
[docs] def convert(self, i_out, t):
# Docstring in base class
# Converter switches slightly (tau / 1000 seconds) before interlocking time due to inaccuracy of the solvers.
if t - self._tau / 1000 > self._action_start_time + self._interlocking_time:
self._switching_state = self._switching_pattern[-1]
else:
self._switching_state = self._switching_pattern[0]
if self._switching_state == 0:
if i_out[0] < 0:
return [1]
elif i_out[0] >= 0:
return [0.0]
elif self._switching_state == 1:
return [1]
elif self._switching_state == 2:
return [0.0]
else:
raise Exception("Invalid switching state of the converter")
[docs] def i_sup(self, i_out):
# Docstring in base class
if self._switching_state == 0:
return i_out[0] if i_out[0] < 0 else 0
elif self._switching_state == 1:
return i_out[0]
elif self._switching_state == 2:
return 0
else:
raise Exception("Invalid switching state of the converter")
def _set_switching_pattern(self, action):
# Docstring in base class
if action == 0 or self._switching_state == 0 or action == self._switching_state or self._interlocking_time == 0:
self._switching_pattern = [action]
return [self._action_start_time + self._tau]
else:
self._switching_pattern = [0, action]
return [
self._action_start_time + self._interlocking_time,
self._action_start_time + self._tau,
]
[docs]class FiniteFourQuadrantConverter(FiniteConverter):
"""
Key:
'Finite-4QC'
Switching States / Actions:
| 0: T2, T4 on.
| 1: T1, T4 on.
| 2: T2, T3 on.
| 3: T1, T3 on.
Action Space:
Discrete(4)
Output Voltages and Currents:
| Box(-1, 1, shape=(1,))
| Box(-1, 1, shape=(1,))
"""
voltages = Box(-1, 1, shape=(1,), dtype=np.float64)
currents = Box(-1, 1, shape=(1,), dtype=np.float64)
action_space = Discrete(4)
def __init__(self, **kwargs):
# Docstring in base class
super().__init__(**kwargs)
self._subconverters = [
FiniteTwoQuadrantConverter(**kwargs),
FiniteTwoQuadrantConverter(**kwargs),
]
[docs] def reset(self):
# Docstring in base class
self._subconverters[0].reset()
self._subconverters[1].reset()
return super().reset()
[docs] def convert(self, i_out, t):
# Docstring in base class
return [self._subconverters[0].convert(i_out, t)[0] - self._subconverters[1].convert([-i_out[0]], t)[0]]
[docs] def set_action(self, action, t):
# Docstring in base class
assert self.action_space.contains(
action
), f"The selected action {action} is not a valid element of the action space {self.action_space}."
times = []
action0 = [1, 1, 2, 2][action]
action1 = [1, 2, 1, 2][action]
times += self._subconverters[0].set_action(action0, t)
times += self._subconverters[1].set_action(action1, t)
return sorted(list(set(times)))
[docs] def i_sup(self, i_out):
# Docstring in base class
return self._subconverters[0].i_sup(i_out) + self._subconverters[1].i_sup([-i_out[0]])
[docs]class ContOneQuadrantConverter(ContDynamicallyAveragedConverter):
"""
Key:
'Cont1QC'
Action:
Duty Cycle of the Transistor in [0,1].
Action Space:
Box([0,1])
Output Voltages and Currents:
| voltages: Box(0, 1, shape=(1,))
| currents: Box(0, 1, shape=(1,))
"""
voltages = Box(0, 1, shape=(1,), dtype=np.float64)
currents = Box(0, 1, shape=(1,), dtype=np.float64)
action_space = Box(0, 1, shape=(1,), dtype=np.float64)
def _convert(self, i_in, *_):
# Docstring in base class
return self._current_action[0] if i_in[0] >= 0 else 1
def _interlock(self, *_):
# Docstring in base class
return 0
[docs] def i_sup(self, i_out):
# Docstring in base class
return self._current_action[0] * i_out[0]
[docs]class ContTwoQuadrantConverter(ContDynamicallyAveragedConverter):
"""
Key:
'Cont-2QC'
Actions:
| Duty Cycle upper Transistor: Action
| Duty Cycle upper Transistor: 1 - Action
Action Space:
Box([0,1])
Output Voltages and Currents:
| voltages: Box(0, 1, shape=(1,))
| currents: Box(-1, 1, shape=(1,))
"""
voltages = Box(0, 1, shape=(1,), dtype=np.float64)
currents = Box(-1, 1, shape=(1,), dtype=np.float64)
action_space = Box(0, 1, shape=(1,), dtype=np.float64)
def _convert(self, *_):
# Docstring in base class
return self._current_action[0]
[docs] def i_sup(self, i_out):
# Docstring in base class
interlocking_current = 1 if i_out[0] < 0 else 0
return (
self._current_action[0]
+ self._interlocking_time / self._tau * (interlocking_current - self._current_action[0])
) * i_out[0]
[docs]class ContFourQuadrantConverter(ContDynamicallyAveragedConverter):
"""
The continuous four quadrant converter (4QC) is simulated with two continuous 2QC.
Key:
'Cont-4QC'
Actions:
| Duty Cycle Transistor T1: 0.5 * (Action + 1)
| Duty Cycle Transistor T2: 1 - 0.5 * (Action + 1)
| Duty Cycle Transistor T3: 1 - 0.5 * (Action + 1)
| Duty Cycle Transistor T4: 0.5 * (Action + 1)
Action Space:
Box(-1, 1, shape=(1,))
Output Voltages and Currents:
| voltages: Box(-1, 1, shape=(1,))
| currents: Box(-1, 1, shape=(1,))
"""
voltages = Box(-1, 1, shape=(1,), dtype=np.float64)
currents = Box(-1, 1, shape=(1,), dtype=np.float64)
action_space = Box(-1, 1, shape=(1,), dtype=np.float64)
def __init__(self, **kwargs):
# Docstring in base class
super().__init__(**kwargs)
self._subconverters = [
ContTwoQuadrantConverter(**kwargs),
ContTwoQuadrantConverter(**kwargs),
]
def _convert(self, *_):
# Not used here
pass
[docs] def reset(self):
# Docstring in base class
self._subconverters[0].reset()
self._subconverters[1].reset()
return super().reset()
[docs] def convert(self, i_out, t):
# Docstring in base class
return [self._subconverters[0].convert(i_out, t)[0] - self._subconverters[1].convert(i_out, t)[0]]
[docs] def set_action(self, action, t):
# Docstring in base class
super().set_action(action, t)
times = []
times += self._subconverters[0].set_action([0.5 * (action[0] + 1)], t)
times += self._subconverters[1].set_action([-0.5 * (action[0] - 1)], t)
return sorted(list(set(times)))
[docs] def i_sup(self, i_out):
# Docstring in base class
return self._subconverters[0].i_sup(i_out) + self._subconverters[1].i_sup([-i_out[0]])
[docs]class FiniteMultiConverter(FiniteConverter):
"""
Converter that allows to include an arbitrary number of independent finite subconverters.
Subconverters must be 'elementary' and can not be MultiConverters.
Key:
'Finite-Multi'
Actions:
Concatenation of the subconverters' action spaces
Action Space:
MultiDiscrete([subconverter[0].action_space.n , subconverter[1].action_space.n, ...])
Output Voltage Space:
Box([subconverter[0].voltages.low, subconverter[1].voltages.low, ...],
[subconverter[0].voltages.high, subconverter[1].voltages.high, ...])
"""
@property
def tau(self):
return self._tau
@tau.setter
def tau(self, value):
self._tau = float(value)
for sub_converter in self._sub_converters:
sub_converter.tau = value
def sub_converters(self):
return self._sub_converters
def __init__(self, subconverters, **kwargs):
"""
Args:
subconverters(list(str/class/object): Subconverters to instantiate .
kwargs(dict): Parameters to pass to the Subconverters and the superclass
"""
super().__init__(**kwargs)
self._sub_converters = []
for subconverter in subconverters:
assert not isinstance(subconverter, str)
if isinstance(subconverter, type):
subconverter = subconverter(**kwargs)
self._sub_converters.append(subconverter)
self.subsignal_current_space_dims = []
self.subsignal_voltage_space_dims = []
self.action_space = []
currents_low = []
currents_high = []
voltages_low = []
voltages_high = []
# get the limits and space dims from each subconverter
for subconverter in self._sub_converters:
self.subsignal_current_space_dims.append(np.squeeze(subconverter.currents.shape) or 1)
self.subsignal_voltage_space_dims.append(np.squeeze(subconverter.voltages.shape) or 1)
self.action_space.append(subconverter.action_space.n)
currents_low.append(subconverter.currents.low)
currents_high.append(subconverter.currents.high)
voltages_low.append(subconverter.voltages.low)
voltages_high.append(subconverter.voltages.high)
# convert to 1D list
self.subsignal_current_space_dims = np.array(self.subsignal_current_space_dims)
self.subsignal_voltage_space_dims = np.array(self.subsignal_voltage_space_dims)
currents_low = np.concatenate(currents_low)
currents_high = np.concatenate(currents_high)
voltages_low = np.concatenate(voltages_low)
voltages_high = np.concatenate(voltages_high)
# put limits into gym_space format
self.action_space = MultiDiscrete(self.action_space)
self.currents = Box(currents_low, currents_high, dtype=np.float64)
self.voltages = Box(voltages_low, voltages_high, dtype=np.float64)
[docs] def convert(self, i_out, t):
# Docstring in base class
u_in = []
subsignal_idx_low = 0
for subconverter, subsignal_space_size in zip(self._sub_converters, self.subsignal_voltage_space_dims):
subsignal_idx_high = subsignal_idx_low + subsignal_space_size
u_in += subconverter.convert(i_out[subsignal_idx_low:subsignal_idx_high], t)
subsignal_idx_low = subsignal_idx_high
return u_in
[docs] def reset(self):
# Docstring in base class
u_in = []
for subconverter in self._sub_converters:
u_in += subconverter.reset()
return u_in
[docs] def set_action(self, action, t):
# Docstring in base class
times = []
for subconverter, sub_action in zip(self._sub_converters, action):
times += subconverter.set_action(sub_action, t)
return sorted(list(set(times)))
[docs] def i_sup(self, i_out):
# Docstring in base class
i_sup = 0
subsignal_idx_low = 0
for subconverter, subsignal_space_size in zip(self._sub_converters, self.subsignal_current_space_dims):
subsignal_idx_high = subsignal_idx_low + subsignal_space_size
i_sup += subconverter.i_sup(i_out[subsignal_idx_low:subsignal_idx_high])
subsignal_idx_low = subsignal_idx_high
return i_sup
[docs]class ContMultiConverter(ContDynamicallyAveragedConverter):
"""
Converter that allows to include an arbitrary number of independent continuous sub-converters.
Sub-converters must be 'elementary' and can not be MultiConverters.
Key:
'Cont-Multi'
Actions:
Concatenation of the subconverters' action spaces
Action Space:
Box([subconverter[0].action_space.low, subconverter[1].action_space.low, ...],
[subconverter[0].action_space.high, subconverter[1].action_space.high, ...])
Output Voltage Space:
Box([subconverter[0].voltages.low, subconverter[1].voltages.low, ...],
[subconverter[0].voltages.high, subconverter[1].voltages.high, ...])
"""
@property
def tau(self):
return self._tau
@tau.setter
def tau(self, value):
self._tau = float(value)
for sub_converter in self._sub_converters:
sub_converter.tau = value
def __init__(self, subconverters, **kwargs):
"""
Args:
subconverters(list(str/class/object): Subconverters to instantiate .
kwargs(dict): Parameters to pass to the Subconverters
"""
super().__init__(**kwargs)
self._sub_converters = []
for subconverter in subconverters:
assert not isinstance(subconverter, str)
if isinstance(subconverter, type):
subconverter = subconverter(**kwargs)
self._sub_converters.append(subconverter)
self.subsignal_current_space_dims = []
self.subsignal_voltage_space_dims = []
action_space_low = []
action_space_high = []
currents_low = []
currents_high = []
voltages_low = []
voltages_high = []
# get the limits and space dims from each subconverter
for subconverter in self._sub_converters:
self.subsignal_current_space_dims.append(np.squeeze(subconverter.currents.shape) or 1)
self.subsignal_voltage_space_dims.append(np.squeeze(subconverter.voltages.shape) or 1)
action_space_low.append(subconverter.action_space.low)
action_space_high.append(subconverter.action_space.high)
currents_low.append(subconverter.currents.low)
currents_high.append(subconverter.currents.high)
voltages_low.append(subconverter.voltages.low)
voltages_high.append(subconverter.voltages.high)
# convert to 1D list
self.subsignal_current_space_dims = np.array(self.subsignal_current_space_dims)
self.subsignal_voltage_space_dims = np.array(self.subsignal_voltage_space_dims)
action_space_low = np.concatenate(action_space_low)
action_space_high = np.concatenate(action_space_high)
currents_low = np.concatenate(currents_low)
currents_high = np.concatenate(currents_high)
voltages_low = np.concatenate(voltages_low)
voltages_high = np.concatenate(voltages_high)
# put limits into gym_space format
self.action_space = Box(action_space_low, action_space_high, dtype=np.float64)
self.currents = Box(currents_low, currents_high, dtype=np.float64)
self.voltages = Box(voltages_low, voltages_high, dtype=np.float64)
[docs] def set_action(self, action, t):
# Docstring in base class
times = []
ind = 0
for subconverter in self._sub_converters:
sub_action = action[ind : ind + subconverter.action_space.shape[0]]
ind += subconverter.action_space.shape[0]
times += subconverter.set_action(sub_action, t)
return sorted(list(set(times)))
[docs] def reset(self):
# Docstring in base class
u_in = []
for subconverter in self._sub_converters:
u_in += subconverter.reset()
return u_in
[docs] def convert(self, i_out, t):
# Docstring in base class
u_in = []
subsignal_idx_low = 0
for subconverter, subsignal_space_size in zip(self._sub_converters, self.subsignal_voltage_space_dims):
subsignal_idx_high = subsignal_idx_low + subsignal_space_size
u_in += subconverter.convert(i_out[subsignal_idx_low:subsignal_idx_high], t)
subsignal_idx_low = subsignal_idx_high
return u_in
def _convert(self, i_in, t):
# Not used
pass
[docs] def i_sup(self, i_out):
# Docstring in base class
i_sup = 0
subsignal_idx_low = 0
for subconverter, subsignal_space_size in zip(self._sub_converters, self.subsignal_current_space_dims):
subsignal_idx_high = subsignal_idx_low + subsignal_space_size
i_sup += subconverter.i_sup(i_out[subsignal_idx_low:subsignal_idx_high])
subsignal_idx_low = subsignal_idx_high
return i_sup
[docs]class FiniteB6BridgeConverter(FiniteConverter):
"""
The finite B6 bridge converters (B6C) is simulated with three finite 2QC.
Key:
'Finite-B6C'
Actions:
+-+-----+-----+-----+
| |H_1 |H_2 |H_3 |
+=+=====+=====+=====+
|0|lower|lower|lower|
+-+-----+-----+-----+
|1|lower|lower|upper|
+-+-----+-----+-----+
|2|lower|upper|lower|
+-+-----+-----+-----+
|3|lower|upper|upper|
+-+-----+-----+-----+
|4|upper|lower|lower|
+-+-----+-----+-----+
|5|upper|lower|upper|
+-+-----+-----+-----+
|6|upper|upper|lower|
+-+-----+-----+-----+
|7|upper|upper|upper|
+-+-----+-----+-----+
Action Space:
Discrete(8)
Output Voltages and Currents:
| voltages: Box(-1,1, shape=(3,))
| currents: Box(-1,1, shape=(3,))
Output Voltage Space:
Box(-0.5, 0.5, shape=(3,))
"""
action_space = Discrete(8)
# Only positive voltages can be applied
voltages = Box(-1, 1, shape=(3,), dtype=np.float64)
# positive and negative currents are possible
currents = Box(-1, 1, shape=(3,), dtype=np.float64)
_reset_action = 0
_subactions = [
[2, 2, 2],
[2, 2, 1],
[2, 1, 2],
[2, 1, 1],
[1, 2, 2],
[1, 2, 1],
[1, 1, 2],
[1, 1, 1],
]
def __init__(self, tau=1e-5, **kwargs):
# Docstring in base class
super().__init__(tau=tau, **kwargs)
self._sub_converters = [
FiniteTwoQuadrantConverter(tau=tau, **kwargs),
FiniteTwoQuadrantConverter(tau=tau, **kwargs),
FiniteTwoQuadrantConverter(tau=tau, **kwargs),
]
[docs] def reset(self):
# Docstring in base class
return [
self._sub_converters[0].reset()[0] - 0.5,
self._sub_converters[1].reset()[0] - 0.5,
self._sub_converters[2].reset()[0] - 0.5,
]
[docs] def convert(self, i_out, t):
# Docstring in base class
u_out = [
self._sub_converters[0].convert([i_out[0]], t)[0] - 0.5,
self._sub_converters[1].convert([i_out[1]], t)[0] - 0.5,
self._sub_converters[2].convert([i_out[2]], t)[0] - 0.5,
]
return u_out
[docs] def set_action(self, action, t):
# Docstring in base class
assert self.action_space.contains(
action
), f"The selected action {action} is not a valid element of the action space {self.action_space}."
subactions = self._subactions[action]
times = []
times += self._sub_converters[0].set_action(subactions[0], t)
times += self._sub_converters[1].set_action(subactions[1], t)
times += self._sub_converters[2].set_action(subactions[2], t)
return sorted(list(set(times)))
[docs] def i_sup(self, i_out):
# Docstring in base class
return sum([subconverter.i_sup([i_out_]) for subconverter, i_out_ in zip(self._sub_converters, i_out)])
[docs]class ContB6BridgeConverter(ContDynamicallyAveragedConverter):
"""
The continuous B6 bridge converter (B6C) is simulated with three continuous 2QC.
Key:
'Cont-B6C'
Actions:
The Duty Cycle for each half bridge in the range of (-1,1)
Action Space:
Box(-1, 1, shape=(3,))
Output Voltages and Currents:
| voltages: Box(-1,1, shape=(3,))
| currents: Box(-1,1, shape=(3,))
Output Voltage Space:
Box(-0.5, 0.5, shape=(3,))
"""
action_space = Box(-1, 1, shape=(3,), dtype=np.float64)
# Only positive voltages can be applied
voltages = Box(-1, 1, shape=(3,), dtype=np.float64)
# Positive and negative currents are possible
currents = Box(-1, 1, shape=(3,), dtype=np.float64)
_reset_action = [0, 0, 0]
def __init__(self, tau=1e-4, **kwargs):
# Docstring in base class
super().__init__(tau=tau, **kwargs)
self._subconverters = [
ContTwoQuadrantConverter(tau=tau, **kwargs),
ContTwoQuadrantConverter(tau=tau, **kwargs),
ContTwoQuadrantConverter(tau=tau, **kwargs),
]
[docs] def reset(self):
# Docstring in base class
return [
self._subconverters[0].reset()[0] - 0.5,
self._subconverters[1].reset()[0] - 0.5,
self._subconverters[2].reset()[0] - 0.5,
]
[docs] def convert(self, i_out, t):
# Docstring in base class
u_out = [
self._subconverters[0].convert([i_out[0]], t)[0] - 0.5,
self._subconverters[1].convert([i_out[1]], t)[0] - 0.5,
self._subconverters[2].convert([i_out[2]], t)[0] - 0.5,
]
return u_out
[docs] def set_action(self, action, t):
# Docstring in base class
times = []
times += self._subconverters[0].set_action([0.5 * (action[0] + 1)], t)
times += self._subconverters[1].set_action([0.5 * (action[1] + 1)], t)
times += self._subconverters[2].set_action([0.5 * (action[2] + 1)], t)
return sorted(list(set(times)))
def _convert(self, i_in, t):
# Not used
pass
[docs] def i_sup(self, i_out):
# Docstring in base class
return sum([subconverter.i_sup([i_out_]) for subconverter, i_out_ in zip(self._subconverters, i_out)])