Skip to content

Commit b474daf

Browse files
authored
Merge pull request #291 from upb-lea/nightly
Merge for Release 3.0.3
2 parents 5555196 + 458ba18 commit b474daf

302 files changed

Lines changed: 6806 additions & 3465 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/build_and_test.yml

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,44 @@ jobs:
3636
build-doc:
3737
runs-on: ubuntu-latest
3838
steps:
39-
- uses: actions/checkout@v3
40-
- name: Build Sphinx documentation
41-
uses: nicholasphair/sphinx-action@7.0.0
39+
- uses: actions/checkout@v4
40+
- uses: actions/setup-python@v5
4241
with:
43-
pre-build-command: "python -m pip install sphinx m2r2 sphinx_rtd_theme==1.3.0 && python -m pip install -r requirements.txt && python -m pip install ."
44-
docs-folder: "docs/"
45-
# Publish built docs to gh-pages branch.
46-
# ===============================
42+
python-version: "3.11"
43+
- name: Install docs toolchain
44+
#pre-build-command: "apt-get update && apt-get install -y git && python -m pip install 'sphinx==3.5.4' 'docutils==0.16' sphinx_mdinclude 'sphinx-rtd-theme==1.0.0' 'sphinxcontrib-applehelp==1.0.4' 'sphinxcontrib-devhelp==1.0.2' 'sphinxcontrib-qthelp==1.0.3' 'sphinxcontrib-serializinghtml==1.1.5' 'sphinxcontrib-htmlhelp==2.0.0' && python -m pip install -r requirements.txt && python -m pip install git+https://github.com/upb-lea/control-block-diagram && python -m pip install ."
45+
#docs-folder: "docs/"
46+
run: |
47+
python -m pip install -U pip setuptools wheel
48+
if [ -f requirements.txt ]; then pip install -r requirements.txt || true; fi
49+
pip install "sphinx==7.3.7" "docutils==0.20.1" "myst-parser==2.0.0" "sphinx-rtd-theme==3.0.2"
50+
# REQUIRED so autodoc can import gem_controllers.*
51+
pip install git+https://github.com/upb-lea/gem-control@main
52+
# Optional: if gem-control imports this at import time in your pages
53+
# pip install git+https://github.com/upb-lea/control-block-diagram
54+
# Install your package so autodoc can import your modules too
55+
pip install .
56+
- name: Ensure docs/_static exists
57+
run: mkdir -p docs/_static
58+
- name: Build docs
59+
working-directory: docs
60+
run: |
61+
sphinx-build -b html -n -T -v -E . _build/html --keep-going -w /tmp/sphinx-log
62+
echo "---- Sphinx log ----"
63+
cat /tmp/sphinx-log || true
64+
- name: Upload docs as Pages artifact
65+
if: success()
66+
uses: actions/upload-pages-artifact@v3
67+
with:
68+
path: docs/_build/html
69+
- name: Print Sphinx log on failure
70+
if: failure()
71+
run: |
72+
echo "::group::/tmp/sphinx-log"
73+
tail -n +1 /tmp/sphinx-log || true
74+
echo "::endgroup::"
75+
# Publish built docs to gh-pages branch.
76+
# ===============================
4777
- name: Commit documentation changes
4878
run: |
4979
git clone https://github.com/upb-lea/gym-electric-motor.git --branch gh-pages --single-branch gh-pages

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,7 @@ examples/logs/
3131
.vscode/settings.json
3232
.vscode/launch.json
3333
plots/
34-
saved_plots/
34+
saved_plots/
35+
36+
# debug files
37+
*/debug.ipynb

CHANGELOG.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99
## Changed
1010
## Fixed
1111

12+
## [3.0.3] - 2025-12-19
13+
## Added
14+
- Automated testing of the existing examples
15+
- Automated testing of the electric motors
16+
- 2x3 phase PMSM environment with tests and documentation
17+
- finite-control-set model predictive current control example
18+
- finite-control-set model predicitve current control is now available in gem-control for PMSM and SynRM (currently working not with superimposed speed and / or torque control)
19+
- merged gem-control documentation into the gem docs
20+
## Changed
21+
- Changed minimum required gymnasium version to 0.29.1.
22+
- Updated the code of gem-control to be compatible with gymnasium v1.0.0
23+
## Fixed
24+
- Updated syntax in the classic_controllers to run with gymnasium v1.0.0
25+
- #263 updated the stable-baselines3, model predictive control and gem-control examples to run with gymnasium v1.0.0
26+
- exchanged widget to ipympl within the examples to run the visualization in visual studio code
27+
1228
## [3.0.2] - 2024-11-19
1329
## Added
1430
## Changed

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919

2020
## Overview
2121
The gym-electric-motor (GEM) package is a Python toolbox for the simulation and control of various electric motors.
22-
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.
22+
It is built upon [Farama 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.
2323
In addition, an automated framework for classical control structures based on PI controllers is provided.
2424

2525
## Getting Started
@@ -82,14 +82,14 @@ A GEM environment consists of following building blocks:
8282
Among various DC-motor models, the following AC motors - together with their power electronic counterparts - are available:
8383
- Permanent magnet synchronous motor (PMSM)
8484
- Synchronous reluctance motor (SynRM)
85-
- Externally exited synchronous motor (EESM)
85+
- Externally excited synchronous motor (EESM)
8686
- Squirrel cage induction motor (SCIM)
8787
- Doubly-fed induction motor (DFIM)
8888

8989
The converters can be driven by means of a duty cycle (continuous control set) or switching commands (finite control set).
9090

9191
### Citation
92-
A white paper for the general toolbox in the context of drive simulation and control prototyping can be found in the [Journal of Open Sorce Software (JOSS)](https://joss.theoj.org/papers/10.21105/joss.02498). Please use the following BibTeX entry for citing it:
92+
A white paper for the general toolbox in the context of drive simulation and control prototyping can be found in the [Journal of Open Source Software (JOSS)](https://joss.theoj.org/papers/10.21105/joss.02498). Please use the following BibTeX entry for citing it:
9393
```
9494
@article{Balakrishna2021,
9595
doi = {10.21105/joss.02498},

docs/conf.py

Lines changed: 86 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,23 @@
1414
#
1515
import os
1616
import sys
17+
18+
19+
os.environ.setdefault("MPLBACKEND", "Agg")
20+
os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
21+
try:
22+
import matplotlib; matplotlib.use("Agg")
23+
except Exception:
24+
pass
25+
26+
autodoc_mock_imports = [
27+
"tkinter","tkinter.ttk","tkinter.filedialog",
28+
"IPython","IPython.display",
29+
# add more if needed in your project:
30+
"PyQt5","PyQt6","PySide2","PySide6","OpenGL","OpenGL.GL","cv2",
31+
"PIL.ImageTk","wx","gi","kivy","vispy","glfw",
32+
]
1733
sys.path.insert(0, os.path.abspath('..'))
18-
sys.setrecursionlimit(1500)
1934
# -- Project information -----------------------------------------------------
2035

2136
project = 'gym-electric-motor'
@@ -39,23 +54,41 @@
3954
# ones.
4055
extensions = [
4156
'sphinx.ext.autodoc',
42-
'sphinx.ext.coverage',
57+
'sphinx.ext.autosummary',
4358
'sphinx.ext.mathjax',
4459
'sphinx.ext.viewcode',
4560
'sphinx.ext.napoleon',
4661
'sphinx_rtd_theme',
47-
'm2r2'
62+
'myst_parser',
63+
'sphinx.ext.intersphinx'
4864
]
49-
65+
html_theme = "sphinx_rtd_theme"
5066
# Add any paths that contain templates here, relative to this directory.
5167
templates_path = ['_templates']
68+
suppress_warnings = ['autodoc.import_object']
5269

5370
# The suffix(es) of source filenames.
71+
#source_suffix = ['.rst', '.md']
5472
# You can specify multiple suffix as a list of string:
55-
#
56-
source_suffix = ['.rst', '.md']
5773
# source_suffix = '.rst'
74+
source_suffix = { ".rst": "restructuredtext", ".md": "myst" }
75+
76+
# MyST features
77+
myst_enable_extensions = ["colon_fence", "deflist", "dollarmath", "amsmath"]
78+
autosummary_generate = True
79+
autodoc_default_options = {
80+
"members": True,
81+
"undoc-members": True,
82+
"inherited-members": True,
83+
"show-inheritance": True,
84+
"member-order": "groupwise",
85+
}
5886

87+
# Cross-link to external docs
88+
intersphinx_mapping = {
89+
"python": ("https://docs.python.org/3", {}),
90+
"gem_control": ("https://upb-lea.github.io/gem-control/", None),
91+
}
5992
# The master toctree document.
6093
master_doc = 'index'
6194

@@ -67,8 +100,11 @@
67100
#
68101
# This is also used if you do content translation via gettext catalogs.
69102
# Usually you set "language" from the command line for these cases.
70-
language = None
71-
103+
language = "en"
104+
# Don't look for translation catalogs (avoids "locale_dir ... does not exist")
105+
locale_dirs = []
106+
# (optional) keeps .po files ungrouped if you ever add i18n later
107+
gettext_compact = False
72108
# List of patterns, relative to source directory, that match files and
73109
# directories to ignore when looking for source files.
74110
# This pattern also affects html_static_path and html_extra_path .
@@ -83,13 +119,17 @@
83119
# The theme to use for HTML and HTML Help pages. See the documentation for
84120
# a list of builtin themes.
85121
#
86-
html_theme = 'sphinx_rtd_theme'
122+
87123

88124
# Theme options are theme-specific and customize the look and feel of a theme
89125
# further. For a list of options available for each theme, see the
90126
# documentation.
91127
#
92-
# html_theme_options = {}
128+
html_theme_options = {
129+
"collapse_navigation": False, # keep the tree expanded (optional)
130+
"navigation_depth": 4, # how deep the *page* tree goes
131+
"titles_only": True,
132+
}
93133

94134
# Add any paths that contain custom static files (such as style sheets) here,
95135
# relative to this directory. They are copied after the builtin static files,
@@ -110,7 +150,7 @@
110150
# -- Options for HTMLHelp output ---------------------------------------------
111151

112152
# Output file base name for HTML help builder.
113-
htmlhelp_basename = 'LEA-RLdoc'
153+
htmlhelp_basename = 'GEM-doc'
114154

115155

116156
# -- Options for LaTeX output ------------------------------------------------
@@ -129,20 +169,22 @@
129169

130170
# Additional stuff for the LaTeX preamble.
131171
#
132-
# 'preamble': '',
172+
'preamble': r'''
173+
\usepackage{amsmath}
174+
''',
133175

134176
# Latex figure (float) alignment
135177
#
136178
# 'figure_align': 'htbp',
137-
'packages': latex_packages
179+
#'packages': latex_packages
138180
}
139181

140182
# Grouping the document tree into LaTeX files. List of tuples
141183
# (source start file, target name, title,
142184
# author, documentclass [howto, manual, or own class]).
143185
latex_documents = [
144-
(master_doc, 'LEA-RL.tex', 'LEA-RL Documentation',
145-
'Arne Traue, Gerrit Book', 'manual'),
186+
(master_doc, 'gem-doc.tex', 'GEM Documentation',
187+
'Praneeth Balakrishna, Gerrit Book, Felix Book, Darius Jakobeit, Wilhelm Kirchgässner, Maximilian Schenke, Arne Traue, Oliver Wallscheid', 'manual'),
146188
]
147189

148190

@@ -151,9 +193,35 @@
151193
# One entry per manual page. List of tuples
152194
# (source start file, name, description, authors, manual section).
153195
man_pages = [
154-
(master_doc, 'lea-rl', 'LEA-RL Documentation',
196+
(master_doc, 'gem', 'GEM Documentation',
155197
[author], 1)
156198
]
199+
# --- Patch broken upstream docstrings so the build doesn't fail ---
200+
BAD_DOCSTRINGS = {
201+
"gym_electric_motor.core.ElectricMotorEnvironment",
202+
"gym_electric_motor.core.ConstraintMonitor",
203+
}
204+
BAD_DOCSTRING_PREFIXES = (
205+
"gym_electric_motor.physical_systems.electric_motors.",
206+
"gym_electric_motor.physical_systems.mechanical_loads.",
207+
# add more if needed:
208+
# "gym_electric_motor.envs.",
209+
)
210+
211+
def _suppress_broken_docstrings(app, what, name, obj, options, lines):
212+
"""Replace known-bad docstrings with a short stub so Sphinx won't error."""
213+
if name in BAD_DOCSTRINGS or any(name.startswith(pfx) for pfx in BAD_DOCSTRING_PREFIXES):
214+
lines[:] = [
215+
f"API for ``{name}``.",
216+
"",
217+
".. note::",
218+
" The original docstring is temporarily suppressed due to formatting issues",
219+
" upstream. Once it’s cleaned, we’ll restore the full text here.",
220+
]
221+
222+
def setup(app):
223+
app.connect("autodoc-process-docstring", _suppress_broken_docstrings)
224+
return {"version": "1.0", "parallel_read_safe": True}
157225

158226

159227
# -- Options for Texinfo output ----------------------------------------------
@@ -162,10 +230,8 @@
162230
# (source start file, target name, title, author,
163231
# dir menu entry, description, category)
164232
texinfo_documents = [
165-
(master_doc, 'LEA-RL', 'LEA-RL Documentation',
166-
author, 'LEA-RL', 'One line description of project.',
233+
(master_doc, 'GEM', 'GEM Documentation',
234+
author, 'GEM', 'A package to simulate and control electrical drives.',
167235
'Miscellaneous'),
168236
]
169-
170-
171237
# -- Extension configuration -------------------------------------------------

docs/constraints.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
sphinx==7.3.7
2+
docutils==0.20.1
3+
myst-parser==2.0.0
4+
sphinx-rtd-theme==3.0.2
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<mxfile host="Electron" modified="2021-11-10T19:25:48.558Z" agent="5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/14.9.6 Chrome/89.0.4389.128 Electron/12.0.16 Safari/537.36" etag="1cYLRA7Q3UVx1JnmBNAv" version="14.9.6" type="device"><diagram id="6Mgs3Ru2CiZi36AC6PC4" name="Page-1">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</diagram></mxfile>

0 commit comments

Comments
 (0)