PyOpenMagnetics 1.7.5__tar.gz → 1.7.7__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 (92) hide show
  1. {pyopenmagnetics-1.7.5 → pyopenmagnetics-1.7.7}/.github/workflows/ci.yml +5 -1
  2. {pyopenmagnetics-1.7.5 → pyopenmagnetics-1.7.7}/.github/workflows/publish.yml +19 -8
  3. {pyopenmagnetics-1.7.5 → pyopenmagnetics-1.7.7}/CMakeLists.txt +1 -1
  4. {pyopenmagnetics-1.7.5 → pyopenmagnetics-1.7.7}/PKG-INFO +1 -1
  5. {pyopenmagnetics-1.7.5 → pyopenmagnetics-1.7.7}/pyproject.toml +1 -1
  6. pyopenmagnetics-1.7.7/src/common.h +114 -0
  7. {pyopenmagnetics-1.7.5 → pyopenmagnetics-1.7.7}/src/core.cpp +12 -18
  8. {pyopenmagnetics-1.7.5 → pyopenmagnetics-1.7.7}/src/losses.cpp +2 -4
  9. {pyopenmagnetics-1.7.5 → pyopenmagnetics-1.7.7}/src/plotting.cpp +29 -3
  10. {pyopenmagnetics-1.7.5 → pyopenmagnetics-1.7.7}/src/simulation.cpp +4 -12
  11. {pyopenmagnetics-1.7.5 → pyopenmagnetics-1.7.7}/src/winding.cpp +14 -2
  12. pyopenmagnetics-1.7.5/src/common.h +0 -58
  13. {pyopenmagnetics-1.7.5 → pyopenmagnetics-1.7.7}/.gitignore +0 -0
  14. {pyopenmagnetics-1.7.5 → pyopenmagnetics-1.7.7}/AGENTS.md +0 -0
  15. {pyopenmagnetics-1.7.5 → pyopenmagnetics-1.7.7}/LICENSE +0 -0
  16. {pyopenmagnetics-1.7.5 → pyopenmagnetics-1.7.7}/PyOpenMagnetics.pyi +0 -0
  17. {pyopenmagnetics-1.7.5 → pyopenmagnetics-1.7.7}/README.md +0 -0
  18. {pyopenmagnetics-1.7.5 → pyopenmagnetics-1.7.7}/_packaging/__init__.py +0 -0
  19. {pyopenmagnetics-1.7.5 → pyopenmagnetics-1.7.7}/_packaging/_build_info.py.in +0 -0
  20. {pyopenmagnetics-1.7.5 → pyopenmagnetics-1.7.7}/api/MAS.py +0 -0
  21. {pyopenmagnetics-1.7.5 → pyopenmagnetics-1.7.7}/api/mas_db_reader.py +0 -0
  22. {pyopenmagnetics-1.7.5 → pyopenmagnetics-1.7.7}/api/validation.py +0 -0
  23. {pyopenmagnetics-1.7.5 → pyopenmagnetics-1.7.7}/clear_cibuildwheel_cache.sh +0 -0
  24. {pyopenmagnetics-1.7.5 → pyopenmagnetics-1.7.7}/docs/compatibility.md +0 -0
  25. {pyopenmagnetics-1.7.5 → pyopenmagnetics-1.7.7}/docs/errors.md +0 -0
  26. {pyopenmagnetics-1.7.5 → pyopenmagnetics-1.7.7}/docs/performance.md +0 -0
  27. {pyopenmagnetics-1.7.5 → pyopenmagnetics-1.7.7}/examples/README.md +0 -0
  28. {pyopenmagnetics-1.7.5 → pyopenmagnetics-1.7.7}/examples/buck_inductor.py +0 -0
  29. {pyopenmagnetics-1.7.5 → pyopenmagnetics-1.7.7}/examples/complete_simulation_example.py +0 -0
  30. {pyopenmagnetics-1.7.5 → pyopenmagnetics-1.7.7}/examples/converter_design_example.py +0 -0
  31. {pyopenmagnetics-1.7.5 → pyopenmagnetics-1.7.7}/examples/debug_bobbin.py +0 -0
  32. {pyopenmagnetics-1.7.5 → pyopenmagnetics-1.7.7}/examples/debug_coil.py +0 -0
  33. {pyopenmagnetics-1.7.5 → pyopenmagnetics-1.7.7}/examples/debug_core.py +0 -0
  34. {pyopenmagnetics-1.7.5 → pyopenmagnetics-1.7.7}/examples/debug_plotting.py +0 -0
  35. {pyopenmagnetics-1.7.5 → pyopenmagnetics-1.7.7}/examples/flyback_220v_12v_1a.py +0 -0
  36. {pyopenmagnetics-1.7.5 → pyopenmagnetics-1.7.7}/examples/flyback_220v_12v_2a_complete.py +0 -0
  37. {pyopenmagnetics-1.7.5 → pyopenmagnetics-1.7.7}/examples/flyback_bh_curve.png +0 -0
  38. {pyopenmagnetics-1.7.5 → pyopenmagnetics-1.7.7}/examples/flyback_core.png +0 -0
  39. {pyopenmagnetics-1.7.5 → pyopenmagnetics-1.7.7}/examples/flyback_design.py +0 -0
  40. {pyopenmagnetics-1.7.5 → pyopenmagnetics-1.7.7}/examples/flyback_summary.png +0 -0
  41. {pyopenmagnetics-1.7.5 → pyopenmagnetics-1.7.7}/examples/flyback_waveforms.png +0 -0
  42. {pyopenmagnetics-1.7.5 → pyopenmagnetics-1.7.7}/examples/list_plot_funcs.py +0 -0
  43. {pyopenmagnetics-1.7.5 → pyopenmagnetics-1.7.7}/examples/plot_flyback_design.py +0 -0
  44. {pyopenmagnetics-1.7.5 → pyopenmagnetics-1.7.7}/examples/plot_flyback_pyom.py +0 -0
  45. {pyopenmagnetics-1.7.5 → pyopenmagnetics-1.7.7}/examples/test_field_calc.py +0 -0
  46. {pyopenmagnetics-1.7.5 → pyopenmagnetics-1.7.7}/examples/test_field_plot.py +0 -0
  47. {pyopenmagnetics-1.7.5 → pyopenmagnetics-1.7.7}/force_fresh_build.sh +0 -0
  48. {pyopenmagnetics-1.7.5 → pyopenmagnetics-1.7.7}/llms.txt +0 -0
  49. {pyopenmagnetics-1.7.5 → pyopenmagnetics-1.7.7}/notebooks/01_getting_started.ipynb +0 -0
  50. {pyopenmagnetics-1.7.5 → pyopenmagnetics-1.7.7}/notebooks/02_buck_inductor.ipynb +0 -0
  51. {pyopenmagnetics-1.7.5 → pyopenmagnetics-1.7.7}/notebooks/03_core_losses.ipynb +0 -0
  52. {pyopenmagnetics-1.7.5 → pyopenmagnetics-1.7.7}/notebooks/README.md +0 -0
  53. {pyopenmagnetics-1.7.5 → pyopenmagnetics-1.7.7}/requirements.txt +0 -0
  54. {pyopenmagnetics-1.7.5 → pyopenmagnetics-1.7.7}/src/advisers.cpp +0 -0
  55. {pyopenmagnetics-1.7.5 → pyopenmagnetics-1.7.7}/src/advisers.h +0 -0
  56. {pyopenmagnetics-1.7.5 → pyopenmagnetics-1.7.7}/src/bobbin.cpp +0 -0
  57. {pyopenmagnetics-1.7.5 → pyopenmagnetics-1.7.7}/src/bobbin.h +0 -0
  58. {pyopenmagnetics-1.7.5 → pyopenmagnetics-1.7.7}/src/cmc.cpp +0 -0
  59. {pyopenmagnetics-1.7.5 → pyopenmagnetics-1.7.7}/src/cmc.h +0 -0
  60. {pyopenmagnetics-1.7.5 → pyopenmagnetics-1.7.7}/src/converter.cpp +0 -0
  61. {pyopenmagnetics-1.7.5 → pyopenmagnetics-1.7.7}/src/converter.h +0 -0
  62. {pyopenmagnetics-1.7.5 → pyopenmagnetics-1.7.7}/src/core.h +0 -0
  63. {pyopenmagnetics-1.7.5 → pyopenmagnetics-1.7.7}/src/crossref.cpp +0 -0
  64. {pyopenmagnetics-1.7.5 → pyopenmagnetics-1.7.7}/src/crossref.h +0 -0
  65. {pyopenmagnetics-1.7.5 → pyopenmagnetics-1.7.7}/src/database.cpp +0 -0
  66. {pyopenmagnetics-1.7.5 → pyopenmagnetics-1.7.7}/src/database.h +0 -0
  67. {pyopenmagnetics-1.7.5 → pyopenmagnetics-1.7.7}/src/logging.cpp +0 -0
  68. {pyopenmagnetics-1.7.5 → pyopenmagnetics-1.7.7}/src/logging.h +0 -0
  69. {pyopenmagnetics-1.7.5 → pyopenmagnetics-1.7.7}/src/losses.h +0 -0
  70. {pyopenmagnetics-1.7.5 → pyopenmagnetics-1.7.7}/src/module.cpp +0 -0
  71. {pyopenmagnetics-1.7.5 → pyopenmagnetics-1.7.7}/src/plotting.h +0 -0
  72. {pyopenmagnetics-1.7.5 → pyopenmagnetics-1.7.7}/src/settings.cpp +0 -0
  73. {pyopenmagnetics-1.7.5 → pyopenmagnetics-1.7.7}/src/settings.h +0 -0
  74. {pyopenmagnetics-1.7.5 → pyopenmagnetics-1.7.7}/src/simulation.h +0 -0
  75. {pyopenmagnetics-1.7.5 → pyopenmagnetics-1.7.7}/src/utils.cpp +0 -0
  76. {pyopenmagnetics-1.7.5 → pyopenmagnetics-1.7.7}/src/utils.h +0 -0
  77. {pyopenmagnetics-1.7.5 → pyopenmagnetics-1.7.7}/src/winding.h +0 -0
  78. {pyopenmagnetics-1.7.5 → pyopenmagnetics-1.7.7}/src/wire.cpp +0 -0
  79. {pyopenmagnetics-1.7.5 → pyopenmagnetics-1.7.7}/src/wire.h +0 -0
  80. {pyopenmagnetics-1.7.5 → pyopenmagnetics-1.7.7}/test.py +0 -0
  81. {pyopenmagnetics-1.7.5 → pyopenmagnetics-1.7.7}/tests/__init__.py +0 -0
  82. {pyopenmagnetics-1.7.5 → pyopenmagnetics-1.7.7}/tests/conftest.py +0 -0
  83. {pyopenmagnetics-1.7.5 → pyopenmagnetics-1.7.7}/tests/test_converter_endpoints.py +0 -0
  84. {pyopenmagnetics-1.7.5 → pyopenmagnetics-1.7.7}/tests/test_core.py +0 -0
  85. {pyopenmagnetics-1.7.5 → pyopenmagnetics-1.7.7}/tests/test_core_adviser.py +0 -0
  86. {pyopenmagnetics-1.7.5 → pyopenmagnetics-1.7.7}/tests/test_examples_integration.py +0 -0
  87. {pyopenmagnetics-1.7.5 → pyopenmagnetics-1.7.7}/tests/test_inputs.py +0 -0
  88. {pyopenmagnetics-1.7.5 → pyopenmagnetics-1.7.7}/tests/test_logging.py +0 -0
  89. {pyopenmagnetics-1.7.5 → pyopenmagnetics-1.7.7}/tests/test_magnetic_adviser.py +0 -0
  90. {pyopenmagnetics-1.7.5 → pyopenmagnetics-1.7.7}/tests/test_magnetics_catalogue_load.py +0 -0
  91. {pyopenmagnetics-1.7.5 → pyopenmagnetics-1.7.7}/tests/test_plotting.py +0 -0
  92. {pyopenmagnetics-1.7.5 → pyopenmagnetics-1.7.7}/tests/test_winding.py +0 -0
@@ -17,7 +17,11 @@ jobs:
17
17
  fail-fast: false
18
18
  matrix:
19
19
  os: [ubuntu-22.04, windows-2022, macos-14]
20
- python-version: ["3.10", "3.11", "3.12"]
20
+ # Track every CPython that publish.yml ships a wheel for (cp310-cp314),
21
+ # so a version we distribute is never untested. 3.14 was added after a
22
+ # Windows user on 3.14 hit the sdist fallback: no cp314 wheel existed,
23
+ # pip tried to compile the engine with nmake and failed.
24
+ python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
21
25
 
22
26
  steps:
23
27
  - uses: actions/checkout@v4
@@ -26,15 +26,15 @@ jobs:
26
26
  build_and_publish_linux:
27
27
  name: Build and Publish Linux wheels (${{ matrix.python }})
28
28
  runs-on: ubuntu-22.04
29
- # Each matrix job builds a single CPython version. Building all four
30
- # (cp310-cp313) sequentially in one job exceeded the runner's window and
29
+ # Each matrix job builds a single CPython version. Building them all
30
+ # (cp310-cp314) sequentially in one job exceeded the runner's window and
31
31
  # was killed mid-compile (exit 143). One version per parallel job keeps
32
32
  # each job short and well under any timeout.
33
33
  timeout-minutes: 120
34
34
  strategy:
35
35
  fail-fast: false
36
36
  matrix:
37
- python: [cp310, cp311, cp312, cp313]
37
+ python: [cp310, cp311, cp312, cp313, cp314]
38
38
  environment:
39
39
  name: pypi
40
40
  url: https://pypi.org/p/pyopenmagnetics
@@ -85,7 +85,7 @@ jobs:
85
85
  done
86
86
 
87
87
  - name: Install cibuildwheel
88
- run: python -m pip install cibuildwheel==2.21.3
88
+ run: python -m pip install cibuildwheel==4.2.0
89
89
 
90
90
  - name: Pre-download virtualenv
91
91
  shell: bash
@@ -154,7 +154,7 @@ jobs:
154
154
  strategy:
155
155
  fail-fast: false
156
156
  matrix:
157
- python: [cp310, cp311, cp312, cp313]
157
+ python: [cp310, cp311, cp312, cp313, cp314]
158
158
  environment:
159
159
  name: pypi
160
160
  url: https://pypi.org/p/pyopenmagnetics
@@ -197,7 +197,7 @@ jobs:
197
197
  run: npm install -g quicktype@23.2.6
198
198
 
199
199
  - name: Install cibuildwheel
200
- run: python -m pip install cibuildwheel==2.21.3
200
+ run: python -m pip install cibuildwheel==4.2.0
201
201
 
202
202
  - name: Build wheels
203
203
  run: python -m cibuildwheel --output-dir wheelhouse
@@ -206,6 +206,17 @@ jobs:
206
206
  CIBW_SKIP: "*-win32 *-manylinux_i686 *musllinux* pp*"
207
207
  CIBW_BEFORE_ALL_WINDOWS: git config --global url."https://github.com/".insteadOf "git@github.com:" && npm install -g quicktype@23.2.6
208
208
  CIBW_BEFORE_BUILD: pip install --default-timeout=60 --retries 5 scikit-build-core "cmake<4" ninja pybind11
209
+ # cibuildwheel 3.0 started running `delvewheel repair` on Windows by
210
+ # default; 2.21.3 did no Windows repair at all. delvewheel resolves an
211
+ # extension's dependent DLLs via PATH, and kirchhoffapi.dll is built by
212
+ # the Kirchhoff ExternalProject and shipped INSIDE the package rather
213
+ # than installed system-wide -- so the repair aborts with "Unable to
214
+ # find library: kirchhoffapi.dll" and every Windows wheel fails,
215
+ # cp310 included. Empty = keep the behaviour that shipped every
216
+ # Windows wheel up to 1.7.5; scikit-build-core already places the DLL
217
+ # next to the extension. Bundling it via delvewheel --add-path is the
218
+ # follow-up, not a prerequisite for 3.14 support.
219
+ CIBW_REPAIR_WHEEL_COMMAND_WINDOWS: ""
209
220
  CIBW_TEST_COMMAND: python -c "import PyOpenMagnetics; print(f'PyOpenMagnetics loaded successfully with {len(dir(PyOpenMagnetics))} functions')"
210
221
 
211
222
  - name: Install twine
@@ -234,7 +245,7 @@ jobs:
234
245
  strategy:
235
246
  fail-fast: false
236
247
  matrix:
237
- python: [cp310, cp311, cp312, cp313]
248
+ python: [cp310, cp311, cp312, cp313, cp314]
238
249
  environment:
239
250
  name: pypi
240
251
  url: https://pypi.org/p/pyopenmagnetics
@@ -282,7 +293,7 @@ jobs:
282
293
  done
283
294
 
284
295
  - name: Install cibuildwheel
285
- run: python -m pip install cibuildwheel==2.21.3
296
+ run: python -m pip install cibuildwheel==4.2.0
286
297
 
287
298
  - name: Build wheels
288
299
  run: python -m cibuildwheel --output-dir wheelhouse
@@ -142,7 +142,7 @@ if(NOT LOCAL_MKF_MAS)
142
142
  set(MKF_GIT_TAG "main"
143
143
  CACHE STRING "MKF git ref for the vendored PyOM build")
144
144
  # Force fresh clone by using a unique timestamp - update this when the pin changes
145
- set(MKF_FORCE_REFRESH "2026-08-24-v1.7.5-mkf-25069e00")
145
+ set(MKF_FORCE_REFRESH "2026-08-26-mkf-10abe8d7")
146
146
  # Tell MKF to disable matplotplusplus and use SVG-based Painter instead
147
147
  set(INCLUDE_PYMKF ON CACHE BOOL "Build Python interface" FORCE)
148
148
  # GIT_SUBMODULES_RECURSE pulls in CAS/PEAS (added 2026-04 alongside the
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: PyOpenMagnetics
3
- Version: 1.7.5
3
+ Version: 1.7.7
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.7.5"
7
+ version = "1.7.7"
8
8
  requires-python = ">=3.8"
9
9
  authors = [
10
10
  { name="Alfonso Martinez", email="Alfonso_VII@hotmail.com" },
@@ -0,0 +1,114 @@
1
+ #pragma once
2
+
3
+ #include <pybind11/pybind11.h>
4
+ #include <pybind11/stl.h>
5
+ #include "pybind11_json/pybind11_json.hpp"
6
+ #include <magic_enum.hpp>
7
+ #include <algorithm>
8
+ #include <stdexcept>
9
+ #include "json.hpp"
10
+
11
+ #include <MAS.hpp>
12
+ #include "advisers/MagneticAdviser.h"
13
+ #include "constructive_models/Bobbin.h"
14
+ #include "constructive_models/Coil.h"
15
+ #include "constructive_models/Core.h"
16
+ #include "constructive_models/Insulation.h"
17
+ #include "constructive_models/Magnetic.h"
18
+ #include "constructive_models/Mas.h"
19
+ #include "constructive_models/NumberTurns.h"
20
+ #include "constructive_models/Wire.h"
21
+ #include "physical_models/InitialPermeability.h"
22
+ #include "physical_models/MagneticEnergy.h"
23
+ #include "physical_models/Reluctance.h"
24
+ #include "physical_models/Temperature.h"
25
+ #include "physical_models/ThermalResistance.h"
26
+ #include "physical_models/MagnetizingInductance.h"
27
+ #include "physical_models/CoreLosses.h"
28
+ #include "physical_models/Resistivity.h"
29
+ #include "physical_models/CoreTemperature.h"
30
+ #include "physical_models/LeakageInductance.h"
31
+ #include "physical_models/Inductance.h"
32
+ #include "physical_models/StrayCapacitance.h"
33
+ #include "physical_models/WindingOhmicLosses.h"
34
+ #include "physical_models/WindingSkinEffectLosses.h"
35
+ #include "physical_models/WindingLosses.h"
36
+ #include "processors/Inputs.h"
37
+ #include "processors/MagneticSimulator.h"
38
+ #include "processors/CircuitSimulatorInterface.h"
39
+ #include "processors/Sweeper.h"
40
+ #include "support/Painter.h"
41
+ #include "support/Utils.h"
42
+
43
+ using namespace MAS;
44
+ using json = nlohmann::json;
45
+ using ordered_json = nlohmann::ordered_json;
46
+
47
+ namespace py = pybind11;
48
+
49
+ #define STRINGIFY(x) #x
50
+ #define MACRO_STRINGIFY(x) STRINGIFY(x)
51
+
52
+ namespace PyMKF {
53
+
54
+ // THE MODELS DICT NAMES THE GAP-RELUCTANCE MODEL UNDER TWO SPELLINGS, AND BOTH ARE REAL.
55
+ // Every Python entry point here has always read "reluctance", settings.cpp publishes that key as
56
+ // the default, and it stays the documented name. MKF's own C++ spells the same choice
57
+ // "gapReluctance" -- CoreCrossReferencer, MagneticAdviser, MagneticFilter, MagneticFilterLosses
58
+ // and CoreAdviserPipeline all use that -- so anyone moving between the two layers reaches for it
59
+ // naturally. It used to be accepted and silently dropped: the key was never looked at, the call
60
+ // fell back to defaults.reluctanceModelDefault, and a sweep over all nine models returned nine
61
+ // identical numbers, which reads as "the model does not matter for this core" rather than "the
62
+ // argument was ignored" (heimdall ABT #952 -- it cost a full pass of a real investigation).
63
+ //
64
+ // Accept both, "reluctance" first so the documented key wins if a caller somehow sets both.
65
+ // Unknown KEYS are still ignored on purpose: one dict legitimately carries several subsystems'
66
+ // choices ("coreLosses" alongside this one), so a strict reader would break valid callers.
67
+ // An unknown model NAME does throw, and now says so -- magic_enum's .value() on an empty
68
+ // optional threw a bare "bad optional access" that named neither the key nor the offending value.
69
+ inline bool find_reluctance_model(const std::map<std::string, std::string>& models,
70
+ OpenMagnetics::ReluctanceModels& reluctanceModelName) {
71
+ for (const char* key : {"reluctance", "gapReluctance"}) {
72
+ auto entry = models.find(key);
73
+ if (entry == models.end()) {
74
+ continue;
75
+ }
76
+ std::string modelNameJsonUpper = entry->second;
77
+ std::transform(modelNameJsonUpper.begin(), modelNameJsonUpper.end(),
78
+ modelNameJsonUpper.begin(), ::toupper);
79
+ auto parsed = magic_enum::enum_cast<OpenMagnetics::ReluctanceModels>(modelNameJsonUpper);
80
+ if (!parsed.has_value()) {
81
+ std::string known;
82
+ for (auto name : magic_enum::enum_names<OpenMagnetics::ReluctanceModels>()) {
83
+ known += (known.empty() ? "" : ", ") + std::string(name);
84
+ }
85
+ throw std::runtime_error("Unknown reluctance model '" + entry->second + "' given as '"
86
+ + std::string(key) + "'. Known models: " + known);
87
+ }
88
+ reluctanceModelName = parsed.value();
89
+ return true;
90
+ }
91
+ return false;
92
+ }
93
+
94
+ inline bool find_reluctance_model(const json& modelsData,
95
+ OpenMagnetics::ReluctanceModels& reluctanceModelName) {
96
+ if (modelsData.is_null() || !modelsData.is_object()) {
97
+ return false;
98
+ }
99
+ std::map<std::string, std::string> models;
100
+ for (auto& [key, value] : modelsData.items()) {
101
+ if (value.is_string()) {
102
+ models[key] = value.get<std::string>();
103
+ }
104
+ }
105
+ return find_reluctance_model(models, reluctanceModelName);
106
+ }
107
+
108
+ // Global database declaration - defined in module.cpp
109
+ extern std::map<std::string, OpenMagnetics::Mas> masDatabase;
110
+
111
+ // Get the directory containing the PyOpenMagnetics module
112
+ std::string get_module_path();
113
+
114
+ } // namespace PyMKF
@@ -290,11 +290,7 @@ double calculate_inductance_from_number_turns_and_gapping(json coreData, json co
290
290
  std::map<std::string, std::string> models = modelsData.get<std::map<std::string, std::string>>();
291
291
 
292
292
  auto reluctanceModelName = OpenMagnetics::defaults.reluctanceModelDefault;
293
- if (models.find("reluctance") != models.end()) {
294
- std::string modelNameJsonUpper = models["reluctance"];
295
- std::transform(modelNameJsonUpper.begin(), modelNameJsonUpper.end(), modelNameJsonUpper.begin(), ::toupper);
296
- reluctanceModelName = magic_enum::enum_cast<OpenMagnetics::ReluctanceModels>(modelNameJsonUpper).value();
297
- }
293
+ find_reluctance_model(models, reluctanceModelName);
298
294
 
299
295
  OpenMagnetics::MagnetizingInductance magnetizingInductanceObj(reluctanceModelName);
300
296
  double magnetizingInductance = magnetizingInductanceObj.calculate_inductance_from_number_turns_and_gapping(core, coil, &operatingPoint).get_magnetizing_inductance().get_nominal().value();
@@ -310,9 +306,7 @@ double calculate_number_turns_from_gapping_and_inductance(json coreData, json co
310
306
  std::map<std::string, std::string> models = modelsData.get<std::map<std::string, std::string>>();
311
307
 
312
308
  auto reluctanceModelName = OpenMagnetics::defaults.reluctanceModelDefault;
313
- if (models.find("reluctance") != models.end()) {
314
- OpenMagnetics::from_json(models["reluctance"], reluctanceModelName);
315
- }
309
+ find_reluctance_model(models, reluctanceModelName);
316
310
 
317
311
  OpenMagnetics::MagnetizingInductance magnetizingInductanceObj(reluctanceModelName);
318
312
  double numberTurns = magnetizingInductanceObj.calculate_number_turns_from_gapping_and_inductance(core, coil, &inputs);
@@ -331,9 +325,7 @@ double calculate_number_turns_from_gapping_and_inductance_legacy(json coreData,
331
325
  std::map<std::string, std::string> models = modelsData.get<std::map<std::string, std::string>>();
332
326
 
333
327
  auto reluctanceModelName = OpenMagnetics::defaults.reluctanceModelDefault;
334
- if (models.find("reluctance") != models.end()) {
335
- OpenMagnetics::from_json(models["reluctance"], reluctanceModelName);
336
- }
328
+ find_reluctance_model(models, reluctanceModelName);
337
329
 
338
330
  OpenMagnetics::MagnetizingInductance magnetizingInductanceObj(reluctanceModelName);
339
331
  double numberTurns = magnetizingInductanceObj.calculate_number_turns_from_gapping_and_inductance(core, &inputs);
@@ -351,9 +343,7 @@ json calculate_gapping_from_number_turns_and_inductance(json coreData, json coil
351
343
  OpenMagnetics::GappingType gappingType = magic_enum::enum_cast<OpenMagnetics::GappingType>(gappingTypeJson).value();
352
344
 
353
345
  auto reluctanceModelName = OpenMagnetics::defaults.reluctanceModelDefault;
354
- if (models.find("reluctance") != models.end()) {
355
- OpenMagnetics::from_json(models["reluctance"], reluctanceModelName);
356
- }
346
+ find_reluctance_model(models, reluctanceModelName);
357
347
 
358
348
  OpenMagnetics::MagnetizingInductance magnetizingInductanceObj(reluctanceModelName);
359
349
  std::vector<CoreGap> gapping = magnetizingInductanceObj.calculate_gapping_from_number_turns_and_inductance(core, coil, &inputs, gappingType, decimals);
@@ -1003,7 +993,8 @@ void register_core_bindings(py::module& m) {
1003
993
  core_data: JSON object with core specification.
1004
994
  coil_data: JSON object with coil specification (for turns).
1005
995
  operating_point_data: JSON operating point for DC bias consideration.
1006
- models_data: JSON dict with "reluctance" model selection.
996
+ models_data: JSON dict with "reluctance" (or MKF's own spelling,
997
+ "gapReluctance" -- both accepted) model selection.
1007
998
 
1008
999
  Returns:
1009
1000
  Magnetizing inductance in Henries.
@@ -1021,7 +1012,8 @@ void register_core_bindings(py::module& m) {
1021
1012
  core_data: JSON object with core specification.
1022
1013
  coil_data: JSON object with coil/winding specification.
1023
1014
  inputs_data: JSON Inputs with magnetizingInductance requirement.
1024
- models_data: JSON dict with "reluctance" model selection.
1015
+ models_data: JSON dict with "reluctance" (or MKF's own spelling,
1016
+ "gapReluctance" -- both accepted) model selection.
1025
1017
 
1026
1018
  Returns:
1027
1019
  Required number of turns (may be non-integer).
@@ -1043,7 +1035,8 @@ void register_core_bindings(py::module& m) {
1043
1035
  Args:
1044
1036
  core_data: JSON object with core specification.
1045
1037
  inputs_data: JSON Inputs with magnetizingInductance requirement.
1046
- models_data: JSON dict with "reluctance" model selection.
1038
+ models_data: JSON dict with "reluctance" (or MKF's own spelling,
1039
+ "gapReluctance" -- both accepted) model selection.
1047
1040
 
1048
1041
  Returns:
1049
1042
  Required number of turns (may be non-integer).
@@ -1063,7 +1056,8 @@ void register_core_bindings(py::module& m) {
1063
1056
  inputs_data: JSON Inputs with magnetizingInductance requirement.
1064
1057
  gapping_type_json: Gap type ("SUBTRACTIVE", "ADDITIVE", "DISTRIBUTED").
1065
1058
  decimals: Precision in decimal places for gap length.
1066
- models_data: JSON dict with "reluctance" model selection.
1059
+ models_data: JSON dict with "reluctance" (or MKF's own spelling,
1060
+ "gapReluctance" -- both accepted) model selection.
1067
1061
 
1068
1062
  Returns:
1069
1063
  JSON Core object with updated gapping configuration.
@@ -18,9 +18,7 @@ json calculate_core_losses(json coreData, json coilData, json inputsData, json m
18
18
  std::map<std::string, std::string> models = modelsData.get<std::map<std::string, std::string>>();
19
19
 
20
20
  auto reluctanceModelName = OpenMagnetics::defaults.reluctanceModelDefault;
21
- if (models.find("reluctance") != models.end()) {
22
- OpenMagnetics::from_json(models["reluctance"], reluctanceModelName);
23
- }
21
+ find_reluctance_model(models, reluctanceModelName);
24
22
  auto coreLossesModelName = OpenMagnetics::defaults.coreLossesModelDefault;
25
23
  if (models.find("coreLosses") != models.end()) {
26
24
  OpenMagnetics::from_json(models["coreLosses"], coreLossesModelName);
@@ -315,7 +313,7 @@ void register_losses_bindings(py::module& m) {
315
313
  inputs_data: JSON object with operating points (frequency, flux density).
316
314
  models_data: JSON dict specifying models to use:
317
315
  - "coreLosses": "STEINMETZ", "IGSE", "MSE", "BARG", "ROSHEN", "PROPRIETARY"
318
- - "reluctance": "ZHANG", "MUEHLETHALER", "PARTRIDGE", "STENGLEIN"
316
+ - "reluctance" (alias "gapReluctance"): "ZHANG", "MUEHLETHALER", "PARTRIDGE", "STENGLEIN"
319
317
 
320
318
  Returns:
321
319
  JSON object containing:
@@ -320,14 +320,40 @@ json plot_temperature_field(json magneticJson, json operatingPointJson, std::str
320
320
  OpenMagnetics::Magnetic magnetic(magneticJson);
321
321
  OperatingPoint operatingPoint(operatingPointJson);
322
322
 
323
- OpenMagnetics::Temperature temperatureModel(magnetic);
323
+ // For toroidal cores, ensure the coil is wound (turn nodes need positions)
324
+ if (magnetic.get_core().get_shape_family() == CoreShapeFamily::T) {
325
+ auto coil = magnetic.get_mutable_coil();
326
+ if (!coil.get_turns_description() || coil.get_turns_description()->empty()) {
327
+ coil.wind();
328
+ magnetic.set_coil(coil);
329
+ }
330
+ }
331
+
332
+ double ambientTemp = operatingPoint.get_conditions().get_ambient_temperature();
333
+
334
+ // Simulate the operating point to get the losses that power the thermal
335
+ // network — a default-constructed Temperature(magnetic) solves an UNPOWERED
336
+ // network at the default 25 C ambient and paints everything stone cold
337
+ // (ABT #906). This mirrors the WASM wrapper in WebLibMKF.
338
+ OpenMagnetics::MagneticSimulator magneticSimulator;
339
+ OpenMagnetics::Mas mas;
340
+ mas.set_magnetic(magnetic);
341
+ mas.get_mutable_inputs().set_operating_points({operatingPoint});
342
+ auto simulatedMas = magneticSimulator.simulate(mas);
343
+ if (simulatedMas.get_outputs().empty()) {
344
+ throw std::runtime_error("plot_temperature_field: simulation produced no outputs");
345
+ }
346
+
347
+ // Shared config builder (ABT #906): the same configuration MagneticSimulator uses
348
+ // for outputs[].temperature, so this plot and the exported MAS can never disagree.
349
+ auto config = OpenMagnetics::TemperatureConfig::fromSimulatedOutput(operatingPoint, simulatedMas.get_outputs()[0]);
350
+
351
+ OpenMagnetics::Temperature temperatureModel(magnetic, config);
324
352
  auto thermalResult = temperatureModel.calculateTemperatures();
325
353
 
326
354
  std::filesystem::path filePath = outputPath.empty()
327
355
  ? std::filesystem::temp_directory_path() / "pyom_plot_temperature_field.svg"
328
356
  : std::filesystem::path(outputPath);
329
-
330
- double ambientTemp = operatingPoint.get_conditions().get_ambient_temperature();
331
357
  OpenMagnetics::Painter painter(filePath);
332
358
  painter.paint_temperature_field(magnetic, thermalResult.nodeTemperatures, true,
333
359
  OpenMagnetics::ColorPalette::BLUE_TO_RED, ambientTemp, textColor, bgColor);
@@ -7,9 +7,7 @@ json simulate(json inputsJson, json magneticJson, json modelsData) {
7
7
  OpenMagnetics::Magnetic magnetic(magneticJson);
8
8
 
9
9
  auto reluctanceModelName = OpenMagnetics::defaults.reluctanceModelDefault;
10
- if (!modelsData.is_null() && modelsData.find("reluctance") != modelsData.end()) {
11
- OpenMagnetics::from_json(modelsData["reluctance"], reluctanceModelName);
12
- }
10
+ find_reluctance_model(modelsData, reluctanceModelName);
13
11
  auto coreLossesModelName = OpenMagnetics::defaults.coreLossesModelDefault;
14
12
  if (!modelsData.is_null() && modelsData.find("coreLosses") != modelsData.end()) {
15
13
  OpenMagnetics::from_json(modelsData["coreLosses"], coreLossesModelName);
@@ -145,9 +143,7 @@ json calculate_inductance_matrix(json magneticJson, double frequency, json model
145
143
  OpenMagnetics::Magnetic magnetic(magneticJson);
146
144
 
147
145
  auto reluctanceModelName = OpenMagnetics::defaults.reluctanceModelDefault;
148
- if (!modelsData.is_null() && modelsData.find("reluctance") != modelsData.end()) {
149
- OpenMagnetics::from_json(modelsData["reluctance"], reluctanceModelName);
150
- }
146
+ find_reluctance_model(modelsData, reluctanceModelName);
151
147
 
152
148
  OpenMagnetics::Inductance inductance(reluctanceModelName);
153
149
  auto inductanceMatrix = inductance.calculate_inductance_matrix(magnetic, frequency);
@@ -313,9 +309,7 @@ json calculate_coupling_coefficient_matrix(json magneticJson, double frequency,
313
309
  OpenMagnetics::Magnetic magnetic(magneticJson);
314
310
 
315
311
  auto reluctanceModelName = OpenMagnetics::defaults.reluctanceModelDefault;
316
- if (!modelsData.is_null() && modelsData.find("reluctance") != modelsData.end()) {
317
- OpenMagnetics::from_json(modelsData["reluctance"], reluctanceModelName);
318
- }
312
+ find_reluctance_model(modelsData, reluctanceModelName);
319
313
 
320
314
  OpenMagnetics::Inductance inductance(reluctanceModelName);
321
315
 
@@ -352,9 +346,7 @@ json calculate_leakage_inductance_matrix(json magneticJson, double frequency, js
352
346
  OpenMagnetics::Magnetic magnetic(magneticJson);
353
347
 
354
348
  auto reluctanceModelName = OpenMagnetics::defaults.reluctanceModelDefault;
355
- if (!modelsData.is_null() && modelsData.find("reluctance") != modelsData.end()) {
356
- OpenMagnetics::from_json(modelsData["reluctance"], reluctanceModelName);
357
- }
349
+ find_reluctance_model(modelsData, reluctanceModelName);
358
350
 
359
351
  OpenMagnetics::Inductance inductance(reluctanceModelName);
360
352
  auto leakageInductanceMatrix = inductance.calculate_leakage_inductance_matrix(magnetic, frequency);
@@ -117,7 +117,14 @@ json wind(json coilJson, size_t repetitions, json proportionPerWindingJson, json
117
117
  }
118
118
 
119
119
  if (!coil.get_turns_description()) {
120
- throw std::runtime_error("Turns not created");
120
+ // ABT #930: "Turns not created" on its own cannot be acted on — it reads as a broken
121
+ // winder even when MKF is correctly refusing an impossible geometry (a 0.36 mm conductor
122
+ // asked into a 0.10 mm annulus). MKF now records why; pass it on.
123
+ const auto& reason = coil.get_last_fit_failure();
124
+ throw std::runtime_error(reason.empty()
125
+ ? std::string("Turns not created: the winding does not fit its window, and the reason "
126
+ "could not be narrowed further.")
127
+ : "Turns not created. " + reason);
121
128
  }
122
129
 
123
130
  json result;
@@ -136,7 +143,12 @@ json wind_planar(json coilJson, json stackUpJson, double borderToWireDistance, j
136
143
  coil.wind_planar(stackUp, borderToWireDistance, wireToWireDistance, insulationThickness, coreToLayerDistance);
137
144
 
138
145
  if (!coil.get_turns_description()) {
139
- throw std::runtime_error("Turns not created");
146
+ // ABT #930: same as wind() above — say why, not just that.
147
+ const auto& reason = coil.get_last_fit_failure();
148
+ throw std::runtime_error(reason.empty()
149
+ ? std::string("Turns not created: the planar winding does not fit its window, and the "
150
+ "reason could not be narrowed further.")
151
+ : "Turns not created. " + reason);
140
152
  }
141
153
 
142
154
  return coil;
@@ -1,58 +0,0 @@
1
- #pragma once
2
-
3
- #include <pybind11/pybind11.h>
4
- #include <pybind11/stl.h>
5
- #include "pybind11_json/pybind11_json.hpp"
6
- #include <magic_enum.hpp>
7
- #include "json.hpp"
8
-
9
- #include <MAS.hpp>
10
- #include "advisers/MagneticAdviser.h"
11
- #include "constructive_models/Bobbin.h"
12
- #include "constructive_models/Coil.h"
13
- #include "constructive_models/Core.h"
14
- #include "constructive_models/Insulation.h"
15
- #include "constructive_models/Magnetic.h"
16
- #include "constructive_models/Mas.h"
17
- #include "constructive_models/NumberTurns.h"
18
- #include "constructive_models/Wire.h"
19
- #include "physical_models/InitialPermeability.h"
20
- #include "physical_models/MagneticEnergy.h"
21
- #include "physical_models/Reluctance.h"
22
- #include "physical_models/Temperature.h"
23
- #include "physical_models/ThermalResistance.h"
24
- #include "physical_models/MagnetizingInductance.h"
25
- #include "physical_models/CoreLosses.h"
26
- #include "physical_models/Resistivity.h"
27
- #include "physical_models/CoreTemperature.h"
28
- #include "physical_models/LeakageInductance.h"
29
- #include "physical_models/Inductance.h"
30
- #include "physical_models/StrayCapacitance.h"
31
- #include "physical_models/WindingOhmicLosses.h"
32
- #include "physical_models/WindingSkinEffectLosses.h"
33
- #include "physical_models/WindingLosses.h"
34
- #include "processors/Inputs.h"
35
- #include "processors/MagneticSimulator.h"
36
- #include "processors/CircuitSimulatorInterface.h"
37
- #include "processors/Sweeper.h"
38
- #include "support/Painter.h"
39
- #include "support/Utils.h"
40
-
41
- using namespace MAS;
42
- using json = nlohmann::json;
43
- using ordered_json = nlohmann::ordered_json;
44
-
45
- namespace py = pybind11;
46
-
47
- #define STRINGIFY(x) #x
48
- #define MACRO_STRINGIFY(x) STRINGIFY(x)
49
-
50
- namespace PyMKF {
51
-
52
- // Global database declaration - defined in module.cpp
53
- extern std::map<std::string, OpenMagnetics::Mas> masDatabase;
54
-
55
- // Get the directory containing the PyOpenMagnetics module
56
- std::string get_module_path();
57
-
58
- } // namespace PyMKF
File without changes
File without changes