PyOpenMagnetics 1.3.12__tar.gz → 1.3.13__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. {pyopenmagnetics-1.3.12 → pyopenmagnetics-1.3.13}/.github/workflows/publish.yml +16 -11
  2. {pyopenmagnetics-1.3.12 → pyopenmagnetics-1.3.13}/CMakeLists.txt +14 -10
  3. {pyopenmagnetics-1.3.12 → pyopenmagnetics-1.3.13}/PKG-INFO +1 -1
  4. {pyopenmagnetics-1.3.12 → pyopenmagnetics-1.3.13}/pyproject.toml +1 -1
  5. {pyopenmagnetics-1.3.12 → pyopenmagnetics-1.3.13}/src/converter.cpp +109 -32
  6. {pyopenmagnetics-1.3.12 → pyopenmagnetics-1.3.13}/src/settings.cpp +6 -0
  7. {pyopenmagnetics-1.3.12 → pyopenmagnetics-1.3.13}/.github/workflows/ci.yml +0 -0
  8. {pyopenmagnetics-1.3.12 → pyopenmagnetics-1.3.13}/.gitignore +0 -0
  9. {pyopenmagnetics-1.3.12 → pyopenmagnetics-1.3.13}/AGENTS.md +0 -0
  10. {pyopenmagnetics-1.3.12 → pyopenmagnetics-1.3.13}/LICENSE +0 -0
  11. {pyopenmagnetics-1.3.12 → pyopenmagnetics-1.3.13}/PyOpenMagnetics.pyi +0 -0
  12. {pyopenmagnetics-1.3.12 → pyopenmagnetics-1.3.13}/README.md +0 -0
  13. {pyopenmagnetics-1.3.12 → pyopenmagnetics-1.3.13}/api/MAS.py +0 -0
  14. {pyopenmagnetics-1.3.12 → pyopenmagnetics-1.3.13}/api/mas_db_reader.py +0 -0
  15. {pyopenmagnetics-1.3.12 → pyopenmagnetics-1.3.13}/api/validation.py +0 -0
  16. {pyopenmagnetics-1.3.12 → pyopenmagnetics-1.3.13}/clear_cibuildwheel_cache.sh +0 -0
  17. {pyopenmagnetics-1.3.12 → pyopenmagnetics-1.3.13}/docs/compatibility.md +0 -0
  18. {pyopenmagnetics-1.3.12 → pyopenmagnetics-1.3.13}/docs/errors.md +0 -0
  19. {pyopenmagnetics-1.3.12 → pyopenmagnetics-1.3.13}/docs/performance.md +0 -0
  20. {pyopenmagnetics-1.3.12 → pyopenmagnetics-1.3.13}/examples/README.md +0 -0
  21. {pyopenmagnetics-1.3.12 → pyopenmagnetics-1.3.13}/examples/buck_inductor.py +0 -0
  22. {pyopenmagnetics-1.3.12 → pyopenmagnetics-1.3.13}/examples/complete_simulation_example.py +0 -0
  23. {pyopenmagnetics-1.3.12 → pyopenmagnetics-1.3.13}/examples/converter_design_example.py +0 -0
  24. {pyopenmagnetics-1.3.12 → pyopenmagnetics-1.3.13}/examples/debug_bobbin.py +0 -0
  25. {pyopenmagnetics-1.3.12 → pyopenmagnetics-1.3.13}/examples/debug_coil.py +0 -0
  26. {pyopenmagnetics-1.3.12 → pyopenmagnetics-1.3.13}/examples/debug_core.py +0 -0
  27. {pyopenmagnetics-1.3.12 → pyopenmagnetics-1.3.13}/examples/debug_plotting.py +0 -0
  28. {pyopenmagnetics-1.3.12 → pyopenmagnetics-1.3.13}/examples/flyback_220v_12v_1a.py +0 -0
  29. {pyopenmagnetics-1.3.12 → pyopenmagnetics-1.3.13}/examples/flyback_220v_12v_2a_complete.py +0 -0
  30. {pyopenmagnetics-1.3.12 → pyopenmagnetics-1.3.13}/examples/flyback_bh_curve.png +0 -0
  31. {pyopenmagnetics-1.3.12 → pyopenmagnetics-1.3.13}/examples/flyback_core.png +0 -0
  32. {pyopenmagnetics-1.3.12 → pyopenmagnetics-1.3.13}/examples/flyback_design.py +0 -0
  33. {pyopenmagnetics-1.3.12 → pyopenmagnetics-1.3.13}/examples/flyback_summary.png +0 -0
  34. {pyopenmagnetics-1.3.12 → pyopenmagnetics-1.3.13}/examples/flyback_waveforms.png +0 -0
  35. {pyopenmagnetics-1.3.12 → pyopenmagnetics-1.3.13}/examples/list_plot_funcs.py +0 -0
  36. {pyopenmagnetics-1.3.12 → pyopenmagnetics-1.3.13}/examples/plot_flyback_design.py +0 -0
  37. {pyopenmagnetics-1.3.12 → pyopenmagnetics-1.3.13}/examples/plot_flyback_pyom.py +0 -0
  38. {pyopenmagnetics-1.3.12 → pyopenmagnetics-1.3.13}/examples/test_field_calc.py +0 -0
  39. {pyopenmagnetics-1.3.12 → pyopenmagnetics-1.3.13}/examples/test_field_plot.py +0 -0
  40. {pyopenmagnetics-1.3.12 → pyopenmagnetics-1.3.13}/force_fresh_build.sh +0 -0
  41. {pyopenmagnetics-1.3.12 → pyopenmagnetics-1.3.13}/llms.txt +0 -0
  42. {pyopenmagnetics-1.3.12 → pyopenmagnetics-1.3.13}/notebooks/01_getting_started.ipynb +0 -0
  43. {pyopenmagnetics-1.3.12 → pyopenmagnetics-1.3.13}/notebooks/02_buck_inductor.ipynb +0 -0
  44. {pyopenmagnetics-1.3.12 → pyopenmagnetics-1.3.13}/notebooks/03_core_losses.ipynb +0 -0
  45. {pyopenmagnetics-1.3.12 → pyopenmagnetics-1.3.13}/notebooks/README.md +0 -0
  46. {pyopenmagnetics-1.3.12 → pyopenmagnetics-1.3.13}/requirements.txt +0 -0
  47. {pyopenmagnetics-1.3.12 → pyopenmagnetics-1.3.13}/src/advisers.cpp +0 -0
  48. {pyopenmagnetics-1.3.12 → pyopenmagnetics-1.3.13}/src/advisers.h +0 -0
  49. {pyopenmagnetics-1.3.12 → pyopenmagnetics-1.3.13}/src/bobbin.cpp +0 -0
  50. {pyopenmagnetics-1.3.12 → pyopenmagnetics-1.3.13}/src/bobbin.h +0 -0
  51. {pyopenmagnetics-1.3.12 → pyopenmagnetics-1.3.13}/src/common.h +0 -0
  52. {pyopenmagnetics-1.3.12 → pyopenmagnetics-1.3.13}/src/converter.h +0 -0
  53. {pyopenmagnetics-1.3.12 → pyopenmagnetics-1.3.13}/src/core.cpp +0 -0
  54. {pyopenmagnetics-1.3.12 → pyopenmagnetics-1.3.13}/src/core.h +0 -0
  55. {pyopenmagnetics-1.3.12 → pyopenmagnetics-1.3.13}/src/database.cpp +0 -0
  56. {pyopenmagnetics-1.3.12 → pyopenmagnetics-1.3.13}/src/database.h +0 -0
  57. {pyopenmagnetics-1.3.12 → pyopenmagnetics-1.3.13}/src/logging.cpp +0 -0
  58. {pyopenmagnetics-1.3.12 → pyopenmagnetics-1.3.13}/src/logging.h +0 -0
  59. {pyopenmagnetics-1.3.12 → pyopenmagnetics-1.3.13}/src/losses.cpp +0 -0
  60. {pyopenmagnetics-1.3.12 → pyopenmagnetics-1.3.13}/src/losses.h +0 -0
  61. {pyopenmagnetics-1.3.12 → pyopenmagnetics-1.3.13}/src/module.cpp +0 -0
  62. {pyopenmagnetics-1.3.12 → pyopenmagnetics-1.3.13}/src/plotting.cpp +0 -0
  63. {pyopenmagnetics-1.3.12 → pyopenmagnetics-1.3.13}/src/plotting.h +0 -0
  64. {pyopenmagnetics-1.3.12 → pyopenmagnetics-1.3.13}/src/settings.h +0 -0
  65. {pyopenmagnetics-1.3.12 → pyopenmagnetics-1.3.13}/src/simulation.cpp +0 -0
  66. {pyopenmagnetics-1.3.12 → pyopenmagnetics-1.3.13}/src/simulation.h +0 -0
  67. {pyopenmagnetics-1.3.12 → pyopenmagnetics-1.3.13}/src/utils.cpp +0 -0
  68. {pyopenmagnetics-1.3.12 → pyopenmagnetics-1.3.13}/src/utils.h +0 -0
  69. {pyopenmagnetics-1.3.12 → pyopenmagnetics-1.3.13}/src/winding.cpp +0 -0
  70. {pyopenmagnetics-1.3.12 → pyopenmagnetics-1.3.13}/src/winding.h +0 -0
  71. {pyopenmagnetics-1.3.12 → pyopenmagnetics-1.3.13}/src/wire.cpp +0 -0
  72. {pyopenmagnetics-1.3.12 → pyopenmagnetics-1.3.13}/src/wire.h +0 -0
  73. {pyopenmagnetics-1.3.12 → pyopenmagnetics-1.3.13}/test.py +0 -0
  74. {pyopenmagnetics-1.3.12 → pyopenmagnetics-1.3.13}/tests/__init__.py +0 -0
  75. {pyopenmagnetics-1.3.12 → pyopenmagnetics-1.3.13}/tests/conftest.py +0 -0
  76. {pyopenmagnetics-1.3.12 → pyopenmagnetics-1.3.13}/tests/test_converter_endpoints.py +0 -0
  77. {pyopenmagnetics-1.3.12 → pyopenmagnetics-1.3.13}/tests/test_core.py +0 -0
  78. {pyopenmagnetics-1.3.12 → pyopenmagnetics-1.3.13}/tests/test_core_adviser.py +0 -0
  79. {pyopenmagnetics-1.3.12 → pyopenmagnetics-1.3.13}/tests/test_examples_integration.py +0 -0
  80. {pyopenmagnetics-1.3.12 → pyopenmagnetics-1.3.13}/tests/test_inputs.py +0 -0
  81. {pyopenmagnetics-1.3.12 → pyopenmagnetics-1.3.13}/tests/test_logging.py +0 -0
  82. {pyopenmagnetics-1.3.12 → pyopenmagnetics-1.3.13}/tests/test_magnetic_adviser.py +0 -0
  83. {pyopenmagnetics-1.3.12 → pyopenmagnetics-1.3.13}/tests/test_plotting.py +0 -0
  84. {pyopenmagnetics-1.3.12 → pyopenmagnetics-1.3.13}/tests/test_winding.py +0 -0
@@ -10,6 +10,11 @@ on:
10
10
  required: false
11
11
  default: false
12
12
  type: boolean
13
+ publish_to_pypi:
14
+ description: 'Publish to real PyPI instead of TestPyPI (use to rebuild a failed release without recreating the GitHub release/tag)'
15
+ required: false
16
+ default: false
17
+ type: boolean
13
18
 
14
19
  env:
15
20
  GIT_LFS_SKIP_SMUDGE: 1 # Skip LFS to avoid bandwidth quota issues
@@ -109,18 +114,18 @@ jobs:
109
114
  if [ $i -lt 3 ]; then sleep 60; fi
110
115
  done
111
116
  CIBW_ENVIRONMENT_LINUX: PATH=/opt/rh/gcc-toolset-13/root/usr/bin:$PATH LD_LIBRARY_PATH=/opt/rh/gcc-toolset-13/root/usr/lib64:$LD_LIBRARY_PATH
112
- CIBW_BEFORE_BUILD: pip install --default-timeout=60 --retries 5 scikit-build-core cmake ninja pybind11
117
+ CIBW_BEFORE_BUILD: pip install --default-timeout=60 --retries 5 scikit-build-core "cmake<4" ninja pybind11
113
118
  CIBW_TEST_COMMAND: python -c "import PyOpenMagnetics; print(f'PyOpenMagnetics loaded successfully with {len(dir(PyOpenMagnetics))} functions')"
114
119
 
115
120
  - name: Publish Linux wheels to PyPI
116
- if: github.event_name == 'release'
121
+ if: github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && inputs.publish_to_pypi)
117
122
  uses: pypa/gh-action-pypi-publish@release/v1
118
123
  with:
119
124
  password: ${{ secrets.PYPI_API_TOKEN }}
120
125
  packages-dir: wheelhouse/
121
126
 
122
127
  - name: Publish Linux wheels to TestPyPI
123
- if: github.event_name == 'workflow_dispatch'
128
+ if: github.event_name == 'workflow_dispatch' && !inputs.publish_to_pypi
124
129
  uses: pypa/gh-action-pypi-publish@release/v1
125
130
  with:
126
131
  repository-url: https://test.pypi.org/legacy/
@@ -181,21 +186,21 @@ jobs:
181
186
  CIBW_BUILD: cp310-* cp311-* cp312-* cp313-*
182
187
  CIBW_SKIP: "*-win32 *-manylinux_i686 *musllinux* pp*"
183
188
  CIBW_BEFORE_ALL_WINDOWS: npm install -g quicktype
184
- CIBW_BEFORE_BUILD: pip install --default-timeout=60 --retries 5 scikit-build-core cmake ninja pybind11
189
+ CIBW_BEFORE_BUILD: pip install --default-timeout=60 --retries 5 scikit-build-core "cmake<4" ninja pybind11
185
190
  CIBW_TEST_COMMAND: python -c "import PyOpenMagnetics; print(f'PyOpenMagnetics loaded successfully with {len(dir(PyOpenMagnetics))} functions')"
186
191
 
187
192
  - name: Install twine
188
193
  run: python -m pip install twine
189
194
 
190
195
  - name: Publish Windows wheels to PyPI
191
- if: github.event_name == 'release'
196
+ if: github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && inputs.publish_to_pypi)
192
197
  run: python -m twine upload wheelhouse/*.whl
193
198
  env:
194
199
  TWINE_USERNAME: __token__
195
200
  TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
196
201
 
197
202
  - name: Publish Windows wheels to TestPyPI
198
- if: github.event_name == 'workflow_dispatch'
203
+ if: github.event_name == 'workflow_dispatch' && !inputs.publish_to_pypi
199
204
  run: python -m twine upload --repository-url https://test.pypi.org/legacy/ wheelhouse/*.whl
200
205
  env:
201
206
  TWINE_USERNAME: __token__
@@ -266,21 +271,21 @@ jobs:
266
271
  if [ $i -lt 5 ]; then sleep 60; fi
267
272
  done
268
273
  CIBW_ENVIRONMENT_MACOS: MACOSX_DEPLOYMENT_TARGET=15.0
269
- CIBW_BEFORE_BUILD: pip install --default-timeout=60 --retries 5 scikit-build-core cmake ninja pybind11
274
+ CIBW_BEFORE_BUILD: pip install --default-timeout=60 --retries 5 scikit-build-core "cmake<4" ninja pybind11
270
275
  CIBW_TEST_COMMAND: python -c "import PyOpenMagnetics; print(f'PyOpenMagnetics loaded successfully with {len(dir(PyOpenMagnetics))} functions')"
271
276
 
272
277
  - name: Install twine
273
278
  run: python -m pip install twine
274
279
 
275
280
  - name: Publish macOS wheels to PyPI
276
- if: github.event_name == 'release'
281
+ if: github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && inputs.publish_to_pypi)
277
282
  run: python -m twine upload wheelhouse/*.whl
278
283
  env:
279
284
  TWINE_USERNAME: __token__
280
285
  TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
281
286
 
282
287
  - name: Publish macOS wheels to TestPyPI
283
- if: github.event_name == 'workflow_dispatch'
288
+ if: github.event_name == 'workflow_dispatch' && !inputs.publish_to_pypi
284
289
  run: python -m twine upload --repository-url https://test.pypi.org/legacy/ wheelhouse/*.whl
285
290
  env:
286
291
  TWINE_USERNAME: __token__
@@ -338,14 +343,14 @@ jobs:
338
343
  run: python -m build --sdist
339
344
 
340
345
  - name: Publish sdist to PyPI
341
- if: github.event_name == 'release'
346
+ if: github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && inputs.publish_to_pypi)
342
347
  uses: pypa/gh-action-pypi-publish@release/v1
343
348
  with:
344
349
  password: ${{ secrets.PYPI_API_TOKEN }}
345
350
  packages-dir: dist/
346
351
 
347
352
  - name: Publish sdist to TestPyPI
348
- if: github.event_name == 'workflow_dispatch'
353
+ if: github.event_name == 'workflow_dispatch' && !inputs.publish_to_pypi
349
354
  uses: pypa/gh-action-pypi-publish@release/v1
350
355
  with:
351
356
  repository-url: https://test.pypi.org/legacy/
@@ -123,12 +123,12 @@ if(NOT LOCAL_MKF_MAS)
123
123
  set(MKF_FORCE_REFRESH "2026-04-29-extra-components")
124
124
  # Tell MKF to disable matplotplusplus and use SVG-based Painter instead
125
125
  set(INCLUDE_PYMKF ON CACHE BOOL "Build Python interface" FORCE)
126
- # GIT_SUBMODULES_RECURSE pulls in CAS/EAS (added 2026-04 alongside the
126
+ # GIT_SUBMODULES_RECURSE pulls in CAS/PEAS (added 2026-04 alongside the
127
127
  # ExtraComponentsMode API). MAS is also a submodule of MKF but PyMKF
128
- # fetches it independently below for fast-update reasons; CAS/EAS only
128
+ # fetches it independently below for fast-update reasons; CAS/PEAS only
129
129
  # exist as MKF submodules, so we need them populated under MKF/.
130
130
  # NOTE: we cannot use GIT_SHALLOW with GIT_SUBMODULES — shallow clone
131
- # skips submodule init. Drop the shallow flag for MKF so CAS/EAS/
131
+ # skips submodule init. Drop the shallow flag for MKF so CAS/PEAS/
132
132
  # cci_coords are actually checked out. cci_coords MUST stay in this
133
133
  # list — the CCI generator reads from MKF/cci_coords/coordinates/.
134
134
  # MAS is fetched separately below (not a sub-fetch of MKF here) so
@@ -137,7 +137,7 @@ if(NOT LOCAL_MKF_MAS)
137
137
  GIT_REPOSITORY https://github.com/OpenMagnetics/MKF.git
138
138
  GIT_TAG main
139
139
  GIT_PROGRESS TRUE
140
- GIT_SUBMODULES "CAS" "EAS" "cci_coords"
140
+ GIT_SUBMODULES "CAS" "PEAS" "cci_coords"
141
141
  GIT_SUBMODULES_RECURSE TRUE)
142
142
 
143
143
  message(STATUS "Fetching mas")
@@ -262,14 +262,14 @@ add_custom_target(PyMASGeneration
262
262
 
263
263
  # ──────────────────────────────────────────────────────────────────
264
264
  # CAS.hpp generation (mirrors MKF/CMakeLists.txt:419-451)
265
- # CAS = Capacitor Adviser Schema. EAS = Element Adviser Schema.
265
+ # CAS = Capacitor Adviser Schema. PEAS = Power Element Adviser Schema.
266
266
  # Both are submodules of MKF (added 2026-04). MKF/Topology.h #includes
267
267
  # <CAS.hpp> for the get_extra_components_inputs API; we must generate
268
268
  # the header here so PyOpenMagnetics's TU sees it.
269
269
  # ──────────────────────────────────────────────────────────────────
270
270
  set(CAS_DIRECTORY "${CMAKE_BINARY_DIR}/CAS/")
271
271
  set(CAS_DIR "${MKF_DIR}/CAS")
272
- set(EAS_DIR "${MKF_DIR}/EAS")
272
+ set(PEAS_DIR "${MKF_DIR}/PEAS")
273
273
  file(MAKE_DIRECTORY "${CAS_DIRECTORY}")
274
274
 
275
275
  add_custom_command(
@@ -277,8 +277,10 @@ add_custom_command(
277
277
  COMMAND ${CMAKE_COMMAND} -E remove -f "${CAS_DIRECTORY}/CAS.hpp"
278
278
  COMMAND quicktype -l c++ -s schema ${CAS_DIR}/schemas/inputs.json
279
279
  -S ${CAS_DIR}/schemas/inputs/designRequirements.json
280
- -S ${EAS_DIR}/schemas/utils.json
281
- -S ${EAS_DIR}/schemas/inputs/twoTerminalOperatingPoint.json
280
+ -S ${PEAS_DIR}/schemas/utils.json
281
+ -S ${PEAS_DIR}/schemas/inputs/twoTerminalOperatingPoint.json
282
+ -S ${PEAS_DIR}/schemas/inputs/operatingConditions.json
283
+ -S ${PEAS_DIR}/schemas/inputs/operatingPointExcitation.json
282
284
  -o ${CAS_DIRECTORY}/CAS.hpp --namespace CAS --source-style single-source
283
285
  --type-style pascal-case --member-style underscore-case
284
286
  --enumerator-style upper-underscore-case --no-boost
@@ -286,8 +288,10 @@ add_custom_command(
286
288
  DEPENDS
287
289
  "${CAS_DIR}/schemas/inputs.json"
288
290
  "${CAS_DIR}/schemas/inputs/designRequirements.json"
289
- "${EAS_DIR}/schemas/utils.json"
290
- "${EAS_DIR}/schemas/inputs/twoTerminalOperatingPoint.json"
291
+ "${PEAS_DIR}/schemas/utils.json"
292
+ "${PEAS_DIR}/schemas/inputs/twoTerminalOperatingPoint.json"
293
+ "${PEAS_DIR}/schemas/inputs/operatingConditions.json"
294
+ "${PEAS_DIR}/schemas/inputs/operatingPointExcitation.json"
291
295
  USES_TERMINAL)
292
296
 
293
297
  add_custom_target(PyCASGeneration
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: PyOpenMagnetics
3
- Version: 1.3.12
3
+ Version: 1.3.13
4
4
  Summary: Python wrapper for OpenMagnetics
5
5
  Author-Email: Alfonso Martinez <Alfonso_VII@hotmail.com>
6
6
  Classifier: Development Status :: 4 - Beta
@@ -4,7 +4,7 @@ build-backend = "scikit_build_core.build"
4
4
 
5
5
  [project]
6
6
  name = "PyOpenMagnetics"
7
- version = "1.3.12"
7
+ version = "1.3.13"
8
8
  requires-python = ">=3.8"
9
9
  authors = [
10
10
  { name="Alfonso Martinez", email="Alfonso_VII@hotmail.com" },
@@ -1180,14 +1180,48 @@ dispatch_extra_components(const std::string& topologyName,
1180
1180
  // (Lr / Cr for LLC, etc.) is populated before deck generation.
1181
1181
  // ─────────────────────────────────────────────────────────────────────
1182
1182
  namespace {
1183
+ // Apply optional bridge simulation mode override on a topology instance.
1184
+ // Must be called BEFORE topology.process() because process() consults the
1185
+ // mode when computing dead time and other timing parameters.
1186
+ //
1187
+ // modeStr accepts:
1188
+ // "" | "default" | "pulse" | "behavioral_pulse" → no-op (BEHAVIORAL_PULSE
1189
+ // stays as default; safe for non-bridge topologies because
1190
+ // set_bridge_simulation_mode throws if you try to force PULSE on a
1191
+ // non-bridge topology).
1192
+ // "switch" | "voltage_controlled_switch" → set VOLTAGE_CONTROLLED_SWITCH,
1193
+ // producing a real SW1/body-diode/snubber bridge instead of a single
1194
+ // dependent V-source. Required when downstream tooling needs to size
1195
+ // the bridge MOSFETs (e.g. Heaviside's TAS decomposer).
1196
+ // anything else → returns false so the caller can raise a clear error.
1197
+ template <typename TopologyT>
1198
+ bool apply_bridge_simulation_mode(TopologyT& topology, const std::string& modeStr) {
1199
+ if (modeStr.empty() || modeStr == "default" ||
1200
+ modeStr == "pulse" || modeStr == "behavioral_pulse") {
1201
+ return true;
1202
+ }
1203
+ if (modeStr == "switch" || modeStr == "voltage_controlled_switch") {
1204
+ topology.set_bridge_simulation_mode(
1205
+ OpenMagnetics::BridgeSimulationMode::VOLTAGE_CONTROLLED_SWITCH);
1206
+ return true;
1207
+ }
1208
+ return false;
1209
+ }
1210
+
1183
1211
  // Isolated topologies (transformer): pass turns ratios + magnetizing inductance.
1184
1212
  template <typename TopologyT>
1185
1213
  std::string generate_spice_isolated(const json& converterJson,
1186
1214
  const std::vector<double>& turnsRatios,
1187
1215
  double magnetizingInductance,
1188
- size_t vinIdx, size_t opIdx) {
1216
+ size_t vinIdx, size_t opIdx,
1217
+ const std::string& bridgeMode) {
1189
1218
  TopologyT topology(converterJson);
1190
1219
  topology._assertErrors = true;
1220
+ if (!apply_bridge_simulation_mode(topology, bridgeMode)) {
1221
+ throw std::runtime_error(
1222
+ "generate_ngspice_circuit: unknown bridge_simulation_mode '" +
1223
+ bridgeMode + "' — expected '', 'pulse', or 'switch'");
1224
+ }
1191
1225
  topology.process();
1192
1226
  return topology.generate_ngspice_circuit(turnsRatios, magnetizingInductance, vinIdx, opIdx);
1193
1227
  }
@@ -1196,9 +1230,15 @@ std::string generate_spice_isolated(const json& converterJson,
1196
1230
  template <typename TopologyT>
1197
1231
  std::string generate_spice_inductor(const json& converterJson,
1198
1232
  double inductance,
1199
- size_t vinIdx, size_t opIdx) {
1233
+ size_t vinIdx, size_t opIdx,
1234
+ const std::string& bridgeMode) {
1200
1235
  TopologyT topology(converterJson);
1201
1236
  topology._assertErrors = true;
1237
+ if (!apply_bridge_simulation_mode(topology, bridgeMode)) {
1238
+ throw std::runtime_error(
1239
+ "generate_ngspice_circuit: unknown bridge_simulation_mode '" +
1240
+ bridgeMode + "' — expected '', 'pulse', or 'switch'");
1241
+ }
1202
1242
  topology.process();
1203
1243
  return topology.generate_ngspice_circuit(inductance, vinIdx, opIdx);
1204
1244
  }
@@ -1210,9 +1250,15 @@ template <typename TopologyT>
1210
1250
  std::string generate_spice_isolated_scalar(const json& converterJson,
1211
1251
  const std::vector<double>& turnsRatios,
1212
1252
  double magnetizingInductance,
1213
- size_t vinIdx, size_t opIdx) {
1253
+ size_t vinIdx, size_t opIdx,
1254
+ const std::string& bridgeMode) {
1214
1255
  TopologyT topology(converterJson);
1215
1256
  topology._assertErrors = true;
1257
+ if (!apply_bridge_simulation_mode(topology, bridgeMode)) {
1258
+ throw std::runtime_error(
1259
+ "generate_ngspice_circuit: unknown bridge_simulation_mode '" +
1260
+ bridgeMode + "' — expected '', 'pulse', or 'switch'");
1261
+ }
1216
1262
  topology.process();
1217
1263
  double turnsRatio = turnsRatios.empty() ? 1.0 : turnsRatios[0];
1218
1264
  return topology.generate_ngspice_circuit(turnsRatio, magnetizingInductance, vinIdx, opIdx);
@@ -1225,53 +1271,83 @@ json generate_ngspice_circuit(const std::string& topologyName,
1225
1271
  std::vector<double> turnsRatios,
1226
1272
  double magnetizingInductance,
1227
1273
  size_t vinIdx,
1228
- size_t opIdx) {
1274
+ size_t opIdx,
1275
+ std::string bridgeMode) {
1229
1276
  try {
1230
1277
  std::string spice;
1231
1278
  // Non-isolated single-inductor topologies — magnetizingInductance
1232
1279
  // arg is interpreted as the main inductor value.
1233
1280
  if (topologyName == "buck")
1234
- spice = generate_spice_inductor<OpenMagnetics::Buck>(converterJson, magnetizingInductance, vinIdx, opIdx);
1281
+ spice = generate_spice_inductor<OpenMagnetics::Buck>(converterJson, magnetizingInductance, vinIdx, opIdx, bridgeMode);
1235
1282
  else if (topologyName == "boost")
1236
- spice = generate_spice_inductor<OpenMagnetics::Boost>(converterJson, magnetizingInductance, vinIdx, opIdx);
1283
+ spice = generate_spice_inductor<OpenMagnetics::Boost>(converterJson, magnetizingInductance, vinIdx, opIdx, bridgeMode);
1237
1284
  // Isolated topologies — turnsRatios + magnetizing inductance.
1238
- else if (topologyName == "flyback") spice = generate_spice_isolated<OpenMagnetics::Flyback>(converterJson, turnsRatios, magnetizingInductance, vinIdx, opIdx);
1239
- else if (topologyName == "single_switch_forward") spice = generate_spice_isolated<OpenMagnetics::SingleSwitchForward>(converterJson, turnsRatios, magnetizingInductance, vinIdx, opIdx);
1240
- else if (topologyName == "two_switch_forward") spice = generate_spice_isolated<OpenMagnetics::TwoSwitchForward>(converterJson, turnsRatios, magnetizingInductance, vinIdx, opIdx);
1241
- else if (topologyName == "active_clamp_forward") spice = generate_spice_isolated<OpenMagnetics::ActiveClampForward>(converterJson, turnsRatios, magnetizingInductance, vinIdx, opIdx);
1242
- else if (topologyName == "push_pull") spice = generate_spice_isolated<OpenMagnetics::PushPull>(converterJson, turnsRatios, magnetizingInductance, vinIdx, opIdx);
1243
- else if (topologyName == "llc") spice = generate_spice_isolated<OpenMagnetics::Llc>(converterJson, turnsRatios, magnetizingInductance, vinIdx, opIdx);
1244
- // CLLC is intentionally not wired here yet — its
1245
- // generate_ngspice_circuit signature takes (double turnsRatio,
1246
- // CllcResonantParameters&, ...), which doesn't fit the uniform
1247
- // (vector<double> turnsRatios, double Lm, ...) shape used by every
1248
- // other topology. Add a separate dispatch path when we need it.
1249
- else if (topologyName == "dab") spice = generate_spice_isolated<OpenMagnetics::Dab>(converterJson, turnsRatios, magnetizingInductance, vinIdx, opIdx);
1250
- else if (topologyName == "phase_shifted_full_bridge" || topologyName == "psfb") spice = generate_spice_isolated<OpenMagnetics::Psfb>(converterJson, turnsRatios, magnetizingInductance, vinIdx, opIdx);
1251
- else if (topologyName == "phase_shifted_half_bridge" || topologyName == "pshb") spice = generate_spice_isolated<OpenMagnetics::Pshb>(converterJson, turnsRatios, magnetizingInductance, vinIdx, opIdx);
1252
- else if (topologyName == "isolated_buck") spice = generate_spice_isolated<OpenMagnetics::IsolatedBuck>(converterJson, turnsRatios, magnetizingInductance, vinIdx, opIdx);
1253
- else if (topologyName == "isolated_buck_boost") spice = generate_spice_isolated<OpenMagnetics::IsolatedBuckBoost>(converterJson, turnsRatios, magnetizingInductance, vinIdx, opIdx);
1285
+ else if (topologyName == "flyback") spice = generate_spice_isolated<OpenMagnetics::Flyback>(converterJson, turnsRatios, magnetizingInductance, vinIdx, opIdx, bridgeMode);
1286
+ else if (topologyName == "single_switch_forward") spice = generate_spice_isolated<OpenMagnetics::SingleSwitchForward>(converterJson, turnsRatios, magnetizingInductance, vinIdx, opIdx, bridgeMode);
1287
+ else if (topologyName == "two_switch_forward") spice = generate_spice_isolated<OpenMagnetics::TwoSwitchForward>(converterJson, turnsRatios, magnetizingInductance, vinIdx, opIdx, bridgeMode);
1288
+ else if (topologyName == "active_clamp_forward") spice = generate_spice_isolated<OpenMagnetics::ActiveClampForward>(converterJson, turnsRatios, magnetizingInductance, vinIdx, opIdx, bridgeMode);
1289
+ else if (topologyName == "push_pull") spice = generate_spice_isolated<OpenMagnetics::PushPull>(converterJson, turnsRatios, magnetizingInductance, vinIdx, opIdx, bridgeMode);
1290
+ else if (topologyName == "llc") spice = generate_spice_isolated<OpenMagnetics::Llc>(converterJson, turnsRatios, magnetizingInductance, vinIdx, opIdx, bridgeMode);
1291
+ // CLLC has a distinct signature: generate_ngspice_circuit takes
1292
+ // (double turnsRatio, const CllcResonantParameters&, ...) instead
1293
+ // of the uniform (vector<double> turnsRatios, double Lm, ...)
1294
+ // shape, because CLLC needs the full resonant-parameter set
1295
+ // (Lr1, Cr1, Lr2, Cr2, Lm) computed by
1296
+ // CllcConverter::calculate_resonant_parameters() a single
1297
+ // magnetizing inductance scalar would not determine the tank.
1298
+ else if (topologyName == "cllc" || topologyName == "advanced_cllc") {
1299
+ OpenMagnetics::AdvancedCllcConverter topology(converterJson);
1300
+ topology._assertErrors = true;
1301
+ if (!apply_bridge_simulation_mode(topology, bridgeMode)) {
1302
+ throw std::runtime_error(
1303
+ "generate_ngspice_circuit: unknown bridge_simulation_mode '" +
1304
+ bridgeMode + "' — expected '', 'pulse', or 'switch'");
1305
+ }
1306
+ topology.process();
1307
+ auto params = topology.calculate_resonant_parameters();
1308
+ double turnsRatio = turnsRatios.empty() ? 1.0 : turnsRatios[0];
1309
+ spice = topology.generate_ngspice_circuit(turnsRatio, params, vinIdx, opIdx);
1310
+ }
1311
+ else if (topologyName == "dab") spice = generate_spice_isolated<OpenMagnetics::Dab>(converterJson, turnsRatios, magnetizingInductance, vinIdx, opIdx, bridgeMode);
1312
+ else if (topologyName == "phase_shifted_full_bridge" || topologyName == "psfb") spice = generate_spice_isolated<OpenMagnetics::Psfb>(converterJson, turnsRatios, magnetizingInductance, vinIdx, opIdx, bridgeMode);
1313
+ else if (topologyName == "phase_shifted_half_bridge" || topologyName == "pshb") spice = generate_spice_isolated<OpenMagnetics::Pshb>(converterJson, turnsRatios, magnetizingInductance, vinIdx, opIdx, bridgeMode);
1314
+ else if (topologyName == "isolated_buck") spice = generate_spice_isolated<OpenMagnetics::IsolatedBuck>(converterJson, turnsRatios, magnetizingInductance, vinIdx, opIdx, bridgeMode);
1315
+ else if (topologyName == "isolated_buck_boost") spice = generate_spice_isolated<OpenMagnetics::IsolatedBuckBoost>(converterJson, turnsRatios, magnetizingInductance, vinIdx, opIdx, bridgeMode);
1254
1316
  // Single-inductor non-isolated (2026-05): Cuk, Sepic, Zeta, FSBB.
1255
1317
  else if (topologyName == "cuk" || topologyName == "advanced_cuk")
1256
- spice = generate_spice_inductor<OpenMagnetics::Cuk>(converterJson, magnetizingInductance, vinIdx, opIdx);
1318
+ spice = generate_spice_inductor<OpenMagnetics::Cuk>(converterJson, magnetizingInductance, vinIdx, opIdx, bridgeMode);
1257
1319
  else if (topologyName == "sepic" || topologyName == "advanced_sepic")
1258
- spice = generate_spice_inductor<OpenMagnetics::Sepic>(converterJson, magnetizingInductance, vinIdx, opIdx);
1320
+ spice = generate_spice_inductor<OpenMagnetics::Sepic>(converterJson, magnetizingInductance, vinIdx, opIdx, bridgeMode);
1259
1321
  else if (topologyName == "zeta" || topologyName == "advanced_zeta")
1260
- spice = generate_spice_inductor<OpenMagnetics::Zeta>(converterJson, magnetizingInductance, vinIdx, opIdx);
1322
+ spice = generate_spice_inductor<OpenMagnetics::Zeta>(converterJson, magnetizingInductance, vinIdx, opIdx, bridgeMode);
1261
1323
  else if (topologyName == "four_switch_buck_boost" || topologyName == "advanced_four_switch_buck_boost")
1262
- spice = generate_spice_inductor<OpenMagnetics::FourSwitchBuckBoost>(converterJson, magnetizingInductance, vinIdx, opIdx);
1324
+ spice = generate_spice_inductor<OpenMagnetics::FourSwitchBuckBoost>(converterJson, magnetizingInductance, vinIdx, opIdx, bridgeMode);
1263
1325
  // Isolated with vector turns ratios (2026-05): AsymHB, Clllc, Src, Vienna.
1264
1326
  else if (topologyName == "asymmetric_half_bridge" || topologyName == "advanced_asymmetric_half_bridge")
1265
- spice = generate_spice_isolated<OpenMagnetics::AsymmetricHalfBridge>(converterJson, turnsRatios, magnetizingInductance, vinIdx, opIdx);
1327
+ spice = generate_spice_isolated<OpenMagnetics::AsymmetricHalfBridge>(converterJson, turnsRatios, magnetizingInductance, vinIdx, opIdx, bridgeMode);
1266
1328
  else if (topologyName == "clllc" || topologyName == "advanced_clllc")
1267
- spice = generate_spice_isolated<OpenMagnetics::Clllc>(converterJson, turnsRatios, magnetizingInductance, vinIdx, opIdx);
1329
+ spice = generate_spice_isolated<OpenMagnetics::Clllc>(converterJson, turnsRatios, magnetizingInductance, vinIdx, opIdx, bridgeMode);
1268
1330
  else if (topologyName == "src" || topologyName == "advanced_src")
1269
- spice = generate_spice_isolated<OpenMagnetics::Src>(converterJson, turnsRatios, magnetizingInductance, vinIdx, opIdx);
1331
+ spice = generate_spice_isolated<OpenMagnetics::Src>(converterJson, turnsRatios, magnetizingInductance, vinIdx, opIdx, bridgeMode);
1270
1332
  else if (topologyName == "vienna" || topologyName == "advanced_vienna")
1271
- spice = generate_spice_isolated<OpenMagnetics::Vienna>(converterJson, turnsRatios, magnetizingInductance, vinIdx, opIdx);
1333
+ spice = generate_spice_isolated<OpenMagnetics::Vienna>(converterJson, turnsRatios, magnetizingInductance, vinIdx, opIdx, bridgeMode);
1272
1334
  // Isolated with scalar turns ratio (single secondary): Weinberg.
1273
1335
  else if (topologyName == "weinberg" || topologyName == "advanced_weinberg")
1274
- spice = generate_spice_isolated_scalar<OpenMagnetics::Weinberg>(converterJson, turnsRatios, magnetizingInductance, vinIdx, opIdx);
1336
+ spice = generate_spice_isolated_scalar<OpenMagnetics::Weinberg>(converterJson, turnsRatios, magnetizingInductance, vinIdx, opIdx, bridgeMode);
1337
+ // PFC has a distinct method signature — (inductance, dcResistance,
1338
+ // simulationTime, timeStep) — because its deck simulates over a
1339
+ // line cycle rather than a switching cycle. Use the method's
1340
+ // defaults for dcResistance/simulationTime/timeStep; vinIdx/opIdx
1341
+ // are not meaningful for PFC (line voltage drives the operating
1342
+ // point).
1343
+ else if (topologyName == "power_factor_correction"
1344
+ || topologyName == "advanced_power_factor_correction"
1345
+ || topologyName == "pfc") {
1346
+ OpenMagnetics::PowerFactorCorrection topology(converterJson);
1347
+ topology._assertErrors = true;
1348
+ topology.process();
1349
+ spice = topology.generate_ngspice_circuit(magnetizingInductance);
1350
+ }
1275
1351
  else return json{{"error", "generate_ngspice_circuit: unknown topology '" + topologyName + "'"}};
1276
1352
  return json{{"netlist", spice}};
1277
1353
  } catch (const std::exception& exc) {
@@ -1367,7 +1443,8 @@ void register_converter_bindings(py::module& m) {
1367
1443
  "populated. Returns {'netlist': '<spice>'} or {'error': '...'}.",
1368
1444
  py::arg("topology_name"), py::arg("converter_json"),
1369
1445
  py::arg("turns_ratios"), py::arg("magnetizing_inductance"),
1370
- py::arg("vin_index") = 0, py::arg("op_index") = 0);
1446
+ py::arg("vin_index") = 0, py::arg("op_index") = 0,
1447
+ py::arg("bridge_simulation_mode") = std::string(""));
1371
1448
 
1372
1449
  m.def("get_extra_components_inputs", &get_extra_components_inputs,
1373
1450
  "Return the design requirements for extra components a topology brings "
@@ -161,6 +161,9 @@ json get_settings() {
161
161
  settingsJson["coreAdviserIncludeMargin"] = OpenMagnetics::settings.get_core_adviser_include_margin();
162
162
  settingsJson["coreAdviserEnableIntermediatePruning"] = OpenMagnetics::settings.get_core_adviser_enable_intermediate_pruning();
163
163
  settingsJson["coreAdviserMaximumMagneticsAfterFiltering"] = OpenMagnetics::settings.get_core_adviser_maximum_magnetics_after_filtering();
164
+ settingsJson["coreAdviserEnableTemperatureFilter"] = OpenMagnetics::settings.get_core_adviser_enable_temperature_filter();
165
+ settingsJson["coreAdviserMaximumTemperature"] = OpenMagnetics::settings.get_core_adviser_maximum_temperature();
166
+ settingsJson["coreAdviserSaturationMargin"] = OpenMagnetics::settings.get_core_adviser_saturation_margin();
164
167
  {
165
168
  json aux;
166
169
  to_json(aux, OpenMagnetics::settings.get_gapping_strategy());
@@ -347,6 +350,9 @@ void set_settings(json settingsJson) {
347
350
  if (settingsJson.contains("coreAdviserIncludeMargin")) OpenMagnetics::settings.set_core_adviser_include_margin(settingsJson["coreAdviserIncludeMargin"]);
348
351
  if (settingsJson.contains("coreAdviserEnableIntermediatePruning")) OpenMagnetics::settings.set_core_adviser_enable_intermediate_pruning(settingsJson["coreAdviserEnableIntermediatePruning"]);
349
352
  if (settingsJson.contains("coreAdviserMaximumMagneticsAfterFiltering")) OpenMagnetics::settings.set_core_adviser_maximum_magnetics_after_filtering(settingsJson["coreAdviserMaximumMagneticsAfterFiltering"]);
353
+ if (settingsJson.contains("coreAdviserEnableTemperatureFilter")) OpenMagnetics::settings.set_core_adviser_enable_temperature_filter(settingsJson["coreAdviserEnableTemperatureFilter"]);
354
+ if (settingsJson.contains("coreAdviserMaximumTemperature")) OpenMagnetics::settings.set_core_adviser_maximum_temperature(settingsJson["coreAdviserMaximumTemperature"]);
355
+ if (settingsJson.contains("coreAdviserSaturationMargin")) OpenMagnetics::settings.set_core_adviser_saturation_margin(settingsJson["coreAdviserSaturationMargin"]);
350
356
  if (settingsJson.contains("gappingStrategy")) {
351
357
  GappingOptimizationStrategy strategy;
352
358
  from_json(settingsJson["gappingStrategy"], strategy);