Skip to content

Commit

Permalink
Merge pull request #253 from upb-lea/update_examples
Browse files Browse the repository at this point in the history
Update examples and completion of the Motor class
  • Loading branch information
XyDrKRulof authored Nov 7, 2024
2 parents 7242e82 + cfccc20 commit 83e8624
Show file tree
Hide file tree
Showing 22 changed files with 879 additions and 3,751 deletions.
17 changes: 16 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

[**Overview paper**](https://joss.theoj.org/papers/10.21105/joss.02498)
| [**Reinforcement learning paper**](https://arxiv.org/abs/1910.09434)
| [**GEM control paper**](https://ieeexplore.ieee.org/document/10239044)
| [**Quickstart**](#getting-started)
| [**Install guide**](#installation)
| [**Reference docs**](https://upb-lea.github.io/gym-electric-motor/)
Expand All @@ -18,7 +19,8 @@

## Overview
The gym-electric-motor (GEM) package is a Python toolbox for the simulation and control of various electric motors.
It is built upon [Faram Gymnasium Environments](https://gym.openai.com/), and, therefore, can be used for both, classical control simulation and [reinforcement learning](https://github.com/upb-lea/reinforcement_learning_course_materials) experiments. It allows you to construct a typical drive train with the usual building blocks, i.e., supply voltages, converters, electric motors and load models, and obtain not only a closed-loop simulation of this physical structure, but also a rich interface for plugging in any decision making algorithm, from linear feedback control to [Deep Deterministic Policy Gradient](https://spinningup.openai.com/en/latest/algorithms/ddpg.html) agents.
It is built upon [Faram Gymnasium Environments](https://gymnasium.farama.org/), and, therefore, can be used for both, classical control simulation and [reinforcement learning](https://github.com/upb-lea/reinforcement_learning_course_materials) experiments. It allows you to construct a typical drive train with the usual building blocks, i.e., supply voltages, converters, electric motors and load models, and obtain not only a closed-loop simulation of this physical structure, but also a rich interface for plugging in any decision making algorithm, from linear feedback control to [Deep Deterministic Policy Gradient](https://spinningup.openai.com/en/latest/algorithms/ddpg.html) agents.
Since v2.0.1, gym-electric-motor control is integrated in gym-electric-motor and provide automated classical control structures for the environment of the Toolbox.

## Getting Started
An easy way to get started with GEM is by playing around with the following interactive notebooks in Google Colaboratory. Most important features of GEM as well as application demonstrations are showcased, and give a kickstart for engineers in industry and academia.
Expand Down Expand Up @@ -116,6 +118,19 @@ A white paper for the utilization of this framework within reinforcement learnin
doi={10.1109/TNNLS.2020.3029573}}
```

A white paper for the classical control approaches of gym-electric-motor control is available at [IEEE-Xplore](https://ieeexplore.ieee.org/document/10239044). Please use the following BibTeX entry for citing it:
```
@INPROCEEDINGS{10239044,
author={Book, Felix and Traue, Arne and Schenke, Maximilian and Haucke-Korber, Barnabas and Wallscheid, Oliver},
booktitle={2023 IEEE International Electric Machines & Drives Conference (IEMDC)},
title={Gym-Electric-Motor (GEM) Control: An Automated Open-Source Controller Design Suite for Drives},
year={2023},
volume={},
number={},
pages={1-7},
doi={10.1109/IEMDC55163.2023.10239044}}
```

### Running Unit Tests with Pytest
To run the unit tests ''pytest'' is required.
All tests can be found in the ''tests'' folder.
Expand Down
14 changes: 7 additions & 7 deletions examples/classic_controllers/classic_controllers.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ def reference_states(environment, **controller_kwargs):
def find_controller_type(environment, stages, **controller_kwargs):
_stages = stages

if isinstance(environment.physical_system.unwrapped, DcMotorSystem):
if isinstance(environment.unwrapped.physical_system, DcMotorSystem):
if type(stages) is list:
if len(stages) > 1:
if type(stages[0]) is list:
Expand Down Expand Up @@ -413,24 +413,24 @@ def automated_gain(environment, stages, controller_type, _controllers, **control
_controllers[stages_a[i][0]["controller_type"]][1] == ContinuousController
): # had to add [0] to make dict in list acessable
if i == 0:
p_gain = mp["l"] / (environment.physical_system.tau * a) / u_a_lim * i_a_lim
i_gain = p_gain / (environment.physical_system.tau * a**2)
p_gain = mp["l"] / (environment.unwrapped.physical_system.tau * a) / u_a_lim * i_a_lim
i_gain = p_gain / (environment.unwrapped.physical_system.tau * a**2)

if _controllers[stages_a[i][0]["controller_type"]][2] == PIDController:
d_gain = p_gain * environment.physical_system.tau
d_gain = p_gain * environment.unwrapped.physical_system.tau
stages_a[i][0]["d_gain"] = stages_a[i][0].get("d_gain", d_gain)

elif i == 1:
t_n = environment.physical_system.tau * a**2
t_n = environment.unwrapped.physical_system.tau * a**2
p_gain = (
environment.physical_system.mechanical_load.j_total
environment.unwrapped.physical_system.mechanical_load.j_total
/ (a * t_n)
/ i_a_lim
* omega_lim
)
i_gain = p_gain / (a * t_n)
if _controllers[stages_a[i][0]["controller_type"]][2] == PIDController:
d_gain = p_gain * environment.physical_system.tau
d_gain = p_gain * environment.unwrapped.physical_system.tau
stages_a[i][0]["d_gain"] = stages_a[i][0].get("d_gain", d_gain)

stages_a[i][0]["p_gain"] = stages_a[i][0].get("p_gain", p_gain) # ?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from externally_referenced_state_plot import ExternallyReferencedStatePlot

import gym_electric_motor as gem
from gym_electric_motor.envs.motors import ActionType, ControlType, Motor, MotorType
from gym_electric_motor.visualization import MotorDashboard
from gym_electric_motor.visualization.render_modes import RenderMode

Expand All @@ -20,28 +21,27 @@
'Finite' Discrete Action Space
"""

"""
motor_type = "PermExDc"
control_type = "TC"
action_type = "Cont"
"""

motor = action_type + "-" + control_type + "-" + motor_type + "-v0"

if motor_type in ["PermExDc", "SeriesDc"]:
states = ["omega", "torque", "i", "u"]
elif motor_type == "ShuntDc":
states = ["omega", "torque", "i_a", "i_e", "u"]
elif motor_type == "ExtExDc":
states = ["omega", "torque", "i_a", "i_e", "u_a", "u_e"]
else:
raise KeyError(motor_type + " is not available")
motor = Motor(
MotorType.PermanentlyExcitedDcMotor,
ControlType.TorqueControl,
ActionType.Continuous,
)


# definition of the plotted variables
external_ref_plots = [ExternallyReferencedStatePlot(state) for state in states]
external_ref_plots = [ExternallyReferencedStatePlot(state) for state in motor.states()]

motor_dashboard = MotorDashboard(additional_plots=external_ref_plots, render_mode=RenderMode.Figure)
# initialize the gym-electric-motor environment
env = gem.make(
motor,
motor.env_id(),
visualization=motor_dashboard,
)
"""
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
from gym_electric_motor.envs.motors import ActionType, ControlType, Motor, MotorType
from classic_controllers import Controller
from externally_referenced_state_plot import ExternallyReferencedStatePlot
from external_plot import ExternalPlot
import gym_electric_motor as gem

from gym_electric_motor.visualization import MotorDashboard
from gym_electric_motor.physical_system_wrappers import FluxObserver
import numpy as np
Expand All @@ -16,27 +18,33 @@
action_type: 'AbcCont' Continuous Action Space
"""

"""
motor_type = "SCIM"
control_type = "TC"
action_type = "Cont"
"""

env_id = action_type + "-" + control_type + "-" + motor_type + "-v0"
motor = Motor(
MotorType.SquirrelCageInductionMotor,
ControlType.TorqueControl,
ActionType.Continuous,
)

# definition of the plotted variables
states = ["omega", "torque", "i_sd", "i_sq", "u_sd", "u_sq"]
external_ref_plots = [ExternallyReferencedStatePlot(state) for state in states]
external_ref_plots = [ExternallyReferencedStatePlot(state) for state in motor.states()]
external_plot = [
ExternalPlot(referenced=control_type != "CC"),
ExternalPlot(referenced= ControlType.TorqueControl != "CC"),
ExternalPlot(min=-np.pi, max=np.pi),
]
external_ref_plots += external_plot

motor_dashboard = MotorDashboard(state_plots=("omega", "psi_abs", "psi_angle"))
# initialize the gym-electric-motor environment
env = gem.make(
env_id,
motor.env_id(),
physical_system_wrappers=(FluxObserver(),),
visualization=MotorDashboard(state_plots=("omega", "psi_abs", "psi_angle")),
visualization=MotorDashboard(),
)

"""
Expand All @@ -60,4 +68,5 @@
if terminated:
env.reset()
controller.reset()
motor_dashboard.show_and_hold()
env.close()
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from gym_electric_motor.envs.motors import ActionType, ControlType, Motor, MotorType
from classic_controllers import Controller
from externally_referenced_state_plot import ExternallyReferencedStatePlot
import gym_electric_motor as gem
Expand All @@ -16,22 +17,28 @@
action_type: 'Cont' Continuous Action Space in ABC-Coordinates
'Finite' Discrete Action Space
"""


"""
motor_type = "PMSM"
control_type = "TC"
action_type = "Cont"
"""

env_id = action_type + "-" + control_type + "-" + motor_type + "-v0"
motor = Motor(
MotorType.PermanentMagnetSynchronousMotor,
ControlType.TorqueControl,
ActionType.Continuous
)

# definition of the plotted variables
external_ref_plots = [
ExternallyReferencedStatePlot(state)
for state in ["omega", "torque", "i_sd", "i_sq", "u_sd", "u_sq"]
ExternallyReferencedStatePlot(state) for state in ["omega", "torque", "i_sd", "i_sq", "u_sd", "u_sq"]
]

motor_dashboard = MotorDashboard(additional_plots=external_ref_plots)
# initialize the gym-electric-motor environment
env = gem.make(
env_id, visualization=MotorDashboard(additional_plots=external_ref_plots)
motor.env_id(), visualization=motor_dashboard
)

"""
Expand Down Expand Up @@ -64,5 +71,6 @@
if terminated:
env.reset()
controller.reset()


motor_dashboard.show_and_hold()
env.close()
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
from classic_controllers import Controller
from externally_referenced_state_plot import ExternallyReferencedStatePlot
import gym_electric_motor as gem
from gym_electric_motor.envs.motors import ActionType, ControlType, Motor, MotorType
from gym_electric_motor.visualization import MotorDashboard
from gym_electric_motor.visualization.render_modes import RenderMode

if __name__ == "__main__":
"""
Expand All @@ -19,29 +21,27 @@
"""

# following manual controller design addresses an ExtExDc. Other motor types require different controller stages
"""
motor_type = "ExtExDc"
control_type = "CC"
action_type = "Cont"
"""

motor = action_type + "-" + control_type + "-" + motor_type + "-v0"
motor = Motor(MotorType.ExternallyExcitedDcMotor,
ControlType.CurrentControl,
ActionType.Continuous,)

if motor_type in ["PermExDc", "SeriesDc"]:
states = ["omega", "torque", "i", "u"]
elif motor_type == "ShuntDc":
states = ["omega", "torque", "i_a", "i_e", "u"]
elif motor_type == "ExtExDc":
states = ["omega", "torque", "i_a", "i_e", "u_a", "u_e"]
else:
raise KeyError(motor_type + " is not available")

# definition of the plotted variables
external_ref_plots = [ExternallyReferencedStatePlot(state) for state in states]
external_ref_plots = [ExternallyReferencedStatePlot(state) for state in motor.states()]

motor_dashboard = MotorDashboard(additional_plots=external_ref_plots, render_mode=RenderMode.Figure)

# initialize the gym-electric-motor environment
env = gem.make(
motor,
visualization=MotorDashboard(additional_plots=external_ref_plots),
render_mode="figure",
motor.env_id(),
visualization=motor_dashboard

)

"""
Expand Down Expand Up @@ -92,4 +92,5 @@
env.reset()
controller.reset()

motor_dashboard.show_and_hold()
env.close()
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from gym_electric_motor.envs.motors import ActionType, ControlType, Motor, MotorType
from classic_controllers import Controller
from externally_referenced_state_plot import ExternallyReferencedStatePlot
from external_plot import ExternalPlot
Expand All @@ -15,25 +16,34 @@
action_type: 'Cont' Continuous Action Space
"""


"""
motor_type = "SCIM"
control_type = "SC"
action_type = "Cont"
"""

env_id = action_type + "-" + control_type + "-" + motor_type + "-v0"
motor = Motor(
MotorType.SquirrelCageInductionMotor,
ControlType.SpeedControl,
ActionType.Continuous,
)

# definition of the plotted variables
states = ["omega", "torque", "i_sd", "i_sq", "u_sd", "u_sq"]
external_ref_plots = [ExternallyReferencedStatePlot(state) for state in states]
external_plot = [
ExternalPlot(referenced=control_type != "CC"),
ExternalPlot(referenced=ControlType.SpeedControl != "CC"),
ExternalPlot(min=-np.pi, max=np.pi),
]
external_ref_plots += external_plot

motor_dashboard = MotorDashboard(additional_plots=external_ref_plots)

# initialize the gym-electric-motor environment
env = gem.make(
env_id, visualization=MotorDashboard(additional_plots=external_ref_plots)
motor.env_id(),
visualization=motor_dashboard
)

"""
Expand Down Expand Up @@ -67,4 +77,6 @@
if terminated:
env.reset()
controller.reset()

motor_dashboard.show_and_hold()
env.close()
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from gym_electric_motor.envs.motors import ActionType, ControlType, Motor, MotorType
from classic_controllers import Controller
from externally_referenced_state_plot import ExternallyReferencedStatePlot
import gym_electric_motor as gem
Expand All @@ -16,15 +17,18 @@
action_type: 'Cont' Continuous Action Space in ABC-Coordinates
'Finite' Discrete Action Space
"""

"""
motor_type = "PMSM"
control_type = "SC"
action_type = "Cont"
"""

env_id = action_type + "-" + control_type + "-" + motor_type + "-v0"
motor = Motor(MotorType.PermanentMagnetSynchronousMotor,
ControlType.SpeedControl,
ActionType.Continuous)

# definition of the motor parameters
psi_p = 0 if motor_type == "SynRM" else 45e-3
psi_p = 0 if "SynRM" in motor.env_id() else 45e-3
limit_values = dict(omega=12e3 * np.pi / 30, torque=100, i=280, u=320)
nominal_values = dict(
omega=10e3 * np.pi / 30, torque=95.0, i=240, epsilon=np.pi, u=300
Expand All @@ -39,9 +43,10 @@
for state in ["omega", "torque", "i_sd", "i_sq", "u_sd", "u_sq"]
]

motor_dashboard = MotorDashboard(additional_plots=external_ref_plots)
# initialize the gym-electric-motor environment
env = gem.make(
env_id,
motor.env_id(),
visualization=MotorDashboard(additional_plots=external_ref_plots),
motor=dict(
limit_values=limit_values,
Expand Down Expand Up @@ -140,5 +145,5 @@
if terminated:
env.reset()
controller.reset()

env.close()
Loading

0 comments on commit 83e8624

Please sign in to comment.