PyOpenMagnetics 1.4.5__tar.gz → 1.5.0__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 (85) hide show
  1. {pyopenmagnetics-1.4.5 → pyopenmagnetics-1.5.0}/PKG-INFO +1 -1
  2. {pyopenmagnetics-1.4.5 → pyopenmagnetics-1.5.0}/PyOpenMagnetics.pyi +16 -5
  3. {pyopenmagnetics-1.4.5 → pyopenmagnetics-1.5.0}/llms.txt +4 -2
  4. {pyopenmagnetics-1.4.5 → pyopenmagnetics-1.5.0}/pyproject.toml +1 -1
  5. {pyopenmagnetics-1.4.5 → pyopenmagnetics-1.5.0}/src/converter.cpp +248 -88
  6. {pyopenmagnetics-1.4.5 → pyopenmagnetics-1.5.0}/src/converter.h +7 -6
  7. {pyopenmagnetics-1.4.5 → pyopenmagnetics-1.5.0}/src/core.cpp +48 -4
  8. {pyopenmagnetics-1.4.5 → pyopenmagnetics-1.5.0}/src/core.h +2 -1
  9. {pyopenmagnetics-1.4.5 → pyopenmagnetics-1.5.0}/.github/workflows/ci.yml +0 -0
  10. {pyopenmagnetics-1.4.5 → pyopenmagnetics-1.5.0}/.github/workflows/publish.yml +0 -0
  11. {pyopenmagnetics-1.4.5 → pyopenmagnetics-1.5.0}/.gitignore +0 -0
  12. {pyopenmagnetics-1.4.5 → pyopenmagnetics-1.5.0}/AGENTS.md +0 -0
  13. {pyopenmagnetics-1.4.5 → pyopenmagnetics-1.5.0}/CMakeLists.txt +0 -0
  14. {pyopenmagnetics-1.4.5 → pyopenmagnetics-1.5.0}/LICENSE +0 -0
  15. {pyopenmagnetics-1.4.5 → pyopenmagnetics-1.5.0}/README.md +0 -0
  16. {pyopenmagnetics-1.4.5 → pyopenmagnetics-1.5.0}/__init__.py +0 -0
  17. {pyopenmagnetics-1.4.5 → pyopenmagnetics-1.5.0}/api/MAS.py +0 -0
  18. {pyopenmagnetics-1.4.5 → pyopenmagnetics-1.5.0}/api/mas_db_reader.py +0 -0
  19. {pyopenmagnetics-1.4.5 → pyopenmagnetics-1.5.0}/api/validation.py +0 -0
  20. {pyopenmagnetics-1.4.5 → pyopenmagnetics-1.5.0}/clear_cibuildwheel_cache.sh +0 -0
  21. {pyopenmagnetics-1.4.5 → pyopenmagnetics-1.5.0}/docs/compatibility.md +0 -0
  22. {pyopenmagnetics-1.4.5 → pyopenmagnetics-1.5.0}/docs/errors.md +0 -0
  23. {pyopenmagnetics-1.4.5 → pyopenmagnetics-1.5.0}/docs/performance.md +0 -0
  24. {pyopenmagnetics-1.4.5 → pyopenmagnetics-1.5.0}/examples/README.md +0 -0
  25. {pyopenmagnetics-1.4.5 → pyopenmagnetics-1.5.0}/examples/buck_inductor.py +0 -0
  26. {pyopenmagnetics-1.4.5 → pyopenmagnetics-1.5.0}/examples/complete_simulation_example.py +0 -0
  27. {pyopenmagnetics-1.4.5 → pyopenmagnetics-1.5.0}/examples/converter_design_example.py +0 -0
  28. {pyopenmagnetics-1.4.5 → pyopenmagnetics-1.5.0}/examples/debug_bobbin.py +0 -0
  29. {pyopenmagnetics-1.4.5 → pyopenmagnetics-1.5.0}/examples/debug_coil.py +0 -0
  30. {pyopenmagnetics-1.4.5 → pyopenmagnetics-1.5.0}/examples/debug_core.py +0 -0
  31. {pyopenmagnetics-1.4.5 → pyopenmagnetics-1.5.0}/examples/debug_plotting.py +0 -0
  32. {pyopenmagnetics-1.4.5 → pyopenmagnetics-1.5.0}/examples/flyback_220v_12v_1a.py +0 -0
  33. {pyopenmagnetics-1.4.5 → pyopenmagnetics-1.5.0}/examples/flyback_220v_12v_2a_complete.py +0 -0
  34. {pyopenmagnetics-1.4.5 → pyopenmagnetics-1.5.0}/examples/flyback_bh_curve.png +0 -0
  35. {pyopenmagnetics-1.4.5 → pyopenmagnetics-1.5.0}/examples/flyback_core.png +0 -0
  36. {pyopenmagnetics-1.4.5 → pyopenmagnetics-1.5.0}/examples/flyback_design.py +0 -0
  37. {pyopenmagnetics-1.4.5 → pyopenmagnetics-1.5.0}/examples/flyback_summary.png +0 -0
  38. {pyopenmagnetics-1.4.5 → pyopenmagnetics-1.5.0}/examples/flyback_waveforms.png +0 -0
  39. {pyopenmagnetics-1.4.5 → pyopenmagnetics-1.5.0}/examples/list_plot_funcs.py +0 -0
  40. {pyopenmagnetics-1.4.5 → pyopenmagnetics-1.5.0}/examples/plot_flyback_design.py +0 -0
  41. {pyopenmagnetics-1.4.5 → pyopenmagnetics-1.5.0}/examples/plot_flyback_pyom.py +0 -0
  42. {pyopenmagnetics-1.4.5 → pyopenmagnetics-1.5.0}/examples/test_field_calc.py +0 -0
  43. {pyopenmagnetics-1.4.5 → pyopenmagnetics-1.5.0}/examples/test_field_plot.py +0 -0
  44. {pyopenmagnetics-1.4.5 → pyopenmagnetics-1.5.0}/force_fresh_build.sh +0 -0
  45. {pyopenmagnetics-1.4.5 → pyopenmagnetics-1.5.0}/notebooks/01_getting_started.ipynb +0 -0
  46. {pyopenmagnetics-1.4.5 → pyopenmagnetics-1.5.0}/notebooks/02_buck_inductor.ipynb +0 -0
  47. {pyopenmagnetics-1.4.5 → pyopenmagnetics-1.5.0}/notebooks/03_core_losses.ipynb +0 -0
  48. {pyopenmagnetics-1.4.5 → pyopenmagnetics-1.5.0}/notebooks/README.md +0 -0
  49. {pyopenmagnetics-1.4.5 → pyopenmagnetics-1.5.0}/requirements.txt +0 -0
  50. {pyopenmagnetics-1.4.5 → pyopenmagnetics-1.5.0}/src/advisers.cpp +0 -0
  51. {pyopenmagnetics-1.4.5 → pyopenmagnetics-1.5.0}/src/advisers.h +0 -0
  52. {pyopenmagnetics-1.4.5 → pyopenmagnetics-1.5.0}/src/bobbin.cpp +0 -0
  53. {pyopenmagnetics-1.4.5 → pyopenmagnetics-1.5.0}/src/bobbin.h +0 -0
  54. {pyopenmagnetics-1.4.5 → pyopenmagnetics-1.5.0}/src/common.h +0 -0
  55. {pyopenmagnetics-1.4.5 → pyopenmagnetics-1.5.0}/src/database.cpp +0 -0
  56. {pyopenmagnetics-1.4.5 → pyopenmagnetics-1.5.0}/src/database.h +0 -0
  57. {pyopenmagnetics-1.4.5 → pyopenmagnetics-1.5.0}/src/logging.cpp +0 -0
  58. {pyopenmagnetics-1.4.5 → pyopenmagnetics-1.5.0}/src/logging.h +0 -0
  59. {pyopenmagnetics-1.4.5 → pyopenmagnetics-1.5.0}/src/losses.cpp +0 -0
  60. {pyopenmagnetics-1.4.5 → pyopenmagnetics-1.5.0}/src/losses.h +0 -0
  61. {pyopenmagnetics-1.4.5 → pyopenmagnetics-1.5.0}/src/module.cpp +0 -0
  62. {pyopenmagnetics-1.4.5 → pyopenmagnetics-1.5.0}/src/plotting.cpp +0 -0
  63. {pyopenmagnetics-1.4.5 → pyopenmagnetics-1.5.0}/src/plotting.h +0 -0
  64. {pyopenmagnetics-1.4.5 → pyopenmagnetics-1.5.0}/src/settings.cpp +0 -0
  65. {pyopenmagnetics-1.4.5 → pyopenmagnetics-1.5.0}/src/settings.h +0 -0
  66. {pyopenmagnetics-1.4.5 → pyopenmagnetics-1.5.0}/src/simulation.cpp +0 -0
  67. {pyopenmagnetics-1.4.5 → pyopenmagnetics-1.5.0}/src/simulation.h +0 -0
  68. {pyopenmagnetics-1.4.5 → pyopenmagnetics-1.5.0}/src/utils.cpp +0 -0
  69. {pyopenmagnetics-1.4.5 → pyopenmagnetics-1.5.0}/src/utils.h +0 -0
  70. {pyopenmagnetics-1.4.5 → pyopenmagnetics-1.5.0}/src/winding.cpp +0 -0
  71. {pyopenmagnetics-1.4.5 → pyopenmagnetics-1.5.0}/src/winding.h +0 -0
  72. {pyopenmagnetics-1.4.5 → pyopenmagnetics-1.5.0}/src/wire.cpp +0 -0
  73. {pyopenmagnetics-1.4.5 → pyopenmagnetics-1.5.0}/src/wire.h +0 -0
  74. {pyopenmagnetics-1.4.5 → pyopenmagnetics-1.5.0}/test.py +0 -0
  75. {pyopenmagnetics-1.4.5 → pyopenmagnetics-1.5.0}/tests/__init__.py +0 -0
  76. {pyopenmagnetics-1.4.5 → pyopenmagnetics-1.5.0}/tests/conftest.py +0 -0
  77. {pyopenmagnetics-1.4.5 → pyopenmagnetics-1.5.0}/tests/test_converter_endpoints.py +0 -0
  78. {pyopenmagnetics-1.4.5 → pyopenmagnetics-1.5.0}/tests/test_core.py +0 -0
  79. {pyopenmagnetics-1.4.5 → pyopenmagnetics-1.5.0}/tests/test_core_adviser.py +0 -0
  80. {pyopenmagnetics-1.4.5 → pyopenmagnetics-1.5.0}/tests/test_examples_integration.py +0 -0
  81. {pyopenmagnetics-1.4.5 → pyopenmagnetics-1.5.0}/tests/test_inputs.py +0 -0
  82. {pyopenmagnetics-1.4.5 → pyopenmagnetics-1.5.0}/tests/test_logging.py +0 -0
  83. {pyopenmagnetics-1.4.5 → pyopenmagnetics-1.5.0}/tests/test_magnetic_adviser.py +0 -0
  84. {pyopenmagnetics-1.4.5 → pyopenmagnetics-1.5.0}/tests/test_plotting.py +0 -0
  85. {pyopenmagnetics-1.4.5 → pyopenmagnetics-1.5.0}/tests/test_winding.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: PyOpenMagnetics
3
- Version: 1.4.5
3
+ Version: 1.5.0
4
4
  Summary: Python wrapper for OpenMagnetics
5
5
  Author-Email: Alfonso Martinez <Alfonso_VII@hotmail.com>
6
6
  Classifier: Development Status :: 4 - Beta
@@ -84,7 +84,7 @@ by reading AGENTS.md before starting.
84
84
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
85
85
  """
86
86
 
87
- from typing import Dict, List, Any, Optional, Union, Literal
87
+ from typing import Dict, List, Any, Optional, Union, Literal, overload
88
88
 
89
89
  # Type aliases for JSON-like structures
90
90
  JsonDict = Dict[str, Any]
@@ -373,12 +373,23 @@ def calculate_inductance_from_number_turns_and_gapping(
373
373
  """
374
374
  ...
375
375
 
376
+ @overload
376
377
  def calculate_number_turns_from_gapping_and_inductance(
377
- core: Core,
378
- inputs: Inputs,
379
- models: ModelsDict
378
+ core_data: Core,
379
+ coil_data: Coil,
380
+ inputs_data: Inputs,
381
+ models_data: ModelsDict
382
+ ) -> int:
383
+ """Calculate required turns for target inductance with given gap (coil-aware, preferred)."""
384
+ ...
385
+
386
+ @overload
387
+ def calculate_number_turns_from_gapping_and_inductance(
388
+ core_data: Core,
389
+ inputs_data: Inputs,
390
+ models_data: ModelsDict
380
391
  ) -> int:
381
- """Calculate required turns for target inductance with given gap."""
392
+ """Legacy 3-argument form (no coil_data); synthesizes a single-primary-winding coil. Prefer the coil-aware overload."""
382
393
  ...
383
394
 
384
395
  def calculate_gapping_from_number_turns_and_inductance(
@@ -230,10 +230,12 @@ L = PyOpenMagnetics.calculate_inductance_from_number_turns_and_gapping(
230
230
  core, coil, operating_point, models
231
231
  )
232
232
 
233
- # Calculate turns from inductance and gap
233
+ # Calculate turns from inductance and gap (coil-aware, preferred)
234
234
  N = PyOpenMagnetics.calculate_number_turns_from_gapping_and_inductance(
235
- core, inputs, models
235
+ core, coil, inputs, models
236
236
  )
237
+ # Legacy 3-argument form (no coil) is still accepted for backward compatibility:
238
+ # N = PyOpenMagnetics.calculate_number_turns_from_gapping_and_inductance(core, inputs, models)
237
239
 
238
240
  # Calculate gap from turns and inductance
239
241
  core_with_gap = PyOpenMagnetics.calculate_gapping_from_number_turns_and_inductance(
@@ -4,7 +4,7 @@ build-backend = "scikit_build_core.build"
4
4
 
5
5
  [project]
6
6
  name = "PyOpenMagnetics"
7
- version = "1.4.5"
7
+ version = "1.5.0"
8
8
  requires-python = ">=3.8"
9
9
  authors = [
10
10
  { name="Alfonso Martinez", email="Alfonso_VII@hotmail.com" },
@@ -149,7 +149,75 @@ static std::string normalize_topology_name(const std::string& s) {
149
149
  return s;
150
150
  }
151
151
 
152
+ // ─────────────────────────────────────────────────────────────────────
153
+ // Base-spec fast path (ABT #11)
154
+ //
155
+ // The fast path normally constructs an Advanced* converter whose from_json
156
+ // hard-requires `desiredInductance` (e.g. AdvancedBuck, MKF Buck.h:219). A
157
+ // caller passing a BASE spec (operating point + currentRippleRatio, no
158
+ // desiredInductance) would otherwise get
159
+ // `[json.exception.out_of_range.403] key 'desiredInductance' not found`.
160
+ //
161
+ // When `desiredInductance` is absent we instead construct the BASE converter
162
+ // class, whose virtual process_design_requirements() DERIVES the inductance
163
+ // from the operating points + currentRippleRatio (or maximumSwitchCurrent) —
164
+ // the same MKF math the slow path uses. No L-derivation is duplicated here;
165
+ // it all stays in MKF (house rule). If neither desiredInductance nor a ripple
166
+ // driver is present, the base class throws a loud, specific exception rather
167
+ // than substituting a default.
168
+ //
169
+ // Two helpers cover the two simulate signatures: single-inductor topologies
170
+ // (Buck/Boost/Cuk/…) take a scalar inductance; transformer topologies
171
+ // (Flyback/Forward family/IsolatedBuck/…) take (turnsRatios, inductance).
172
+ // ─────────────────────────────────────────────────────────────────────
173
+ template <typename BaseConverter>
174
+ OpenMagnetics::Inputs process_base_spec_scalar_L(const json& converterJson, bool useNgspice, MAS::Topologies topology) {
175
+ BaseConverter converter(converterJson);
176
+ converter._assertErrors = true;
177
+ if (useNgspice) {
178
+ auto designReqs = converter.process_design_requirements();
179
+ double inductance = OpenMagnetics::resolve_dimensional_values(designReqs.get_magnetizing_inductance());
180
+ auto operatingPoints = converter.simulate_and_extract_operating_points(inductance);
181
+ OpenMagnetics::Inputs result;
182
+ result.set_design_requirements(designReqs);
183
+ result.set_operating_points(operatingPoints);
184
+ result.get_mutable_design_requirements().set_topology(topology);
185
+ return result;
186
+ } else {
187
+ OpenMagnetics::Inputs result = converter.process();
188
+ result.get_mutable_design_requirements().set_topology(topology);
189
+ return result;
190
+ }
191
+ }
192
+
193
+ template <typename BaseConverter>
194
+ OpenMagnetics::Inputs process_base_spec_vector_L(const json& converterJson, bool useNgspice, MAS::Topologies topology) {
195
+ BaseConverter converter(converterJson);
196
+ converter._assertErrors = true;
197
+ if (useNgspice) {
198
+ auto designReqs = converter.process_design_requirements();
199
+ std::vector<double> turnsRatios;
200
+ for (const auto& tr : designReqs.get_turns_ratios()) {
201
+ turnsRatios.push_back(OpenMagnetics::resolve_dimensional_values(tr));
202
+ }
203
+ double inductance = OpenMagnetics::resolve_dimensional_values(designReqs.get_magnetizing_inductance());
204
+ auto operatingPoints = converter.simulate_and_extract_operating_points(turnsRatios, inductance);
205
+ OpenMagnetics::Inputs result;
206
+ result.set_design_requirements(designReqs);
207
+ result.set_operating_points(operatingPoints);
208
+ result.get_mutable_design_requirements().set_topology(topology);
209
+ return result;
210
+ } else {
211
+ OpenMagnetics::Inputs result = converter.process();
212
+ result.get_mutable_design_requirements().set_topology(topology);
213
+ return result;
214
+ }
215
+ }
216
+
152
217
  OpenMagnetics::Inputs process_flyback_internal(const json& converterJson, bool useNgspice) {
218
+ if (!converterJson.contains("desiredInductance")) {
219
+ return process_base_spec_vector_L<OpenMagnetics::Flyback>(converterJson, useNgspice, MAS::Topologies::FLYBACK_CONVERTER);
220
+ }
153
221
  OpenMagnetics::AdvancedFlyback converter(converterJson);
154
222
  converter._assertErrors = true;
155
223
 
@@ -184,6 +252,9 @@ OpenMagnetics::Inputs process_flyback_internal(const json& converterJson, bool u
184
252
  }
185
253
 
186
254
  OpenMagnetics::Inputs process_buck_internal(const json& converterJson, bool useNgspice) {
255
+ if (!converterJson.contains("desiredInductance")) {
256
+ return process_base_spec_scalar_L<OpenMagnetics::Buck>(converterJson, useNgspice, MAS::Topologies::BUCK_CONVERTER);
257
+ }
187
258
  OpenMagnetics::AdvancedBuck converter(converterJson);
188
259
  converter._assertErrors = true;
189
260
 
@@ -204,6 +275,9 @@ OpenMagnetics::Inputs process_buck_internal(const json& converterJson, bool useN
204
275
  }
205
276
 
206
277
  OpenMagnetics::Inputs process_boost_internal(const json& converterJson, bool useNgspice) {
278
+ if (!converterJson.contains("desiredInductance")) {
279
+ return process_base_spec_scalar_L<OpenMagnetics::Boost>(converterJson, useNgspice, MAS::Topologies::BOOST_CONVERTER);
280
+ }
207
281
  OpenMagnetics::AdvancedBoost converter(converterJson);
208
282
  converter._assertErrors = true;
209
283
 
@@ -224,6 +298,9 @@ OpenMagnetics::Inputs process_boost_internal(const json& converterJson, bool use
224
298
  }
225
299
 
226
300
  OpenMagnetics::Inputs process_single_switch_forward_internal(const json& converterJson, bool useNgspice) {
301
+ if (!converterJson.contains("desiredInductance")) {
302
+ return process_base_spec_vector_L<OpenMagnetics::SingleSwitchForward>(converterJson, useNgspice, MAS::Topologies::SINGLE_SWITCH_FORWARD_CONVERTER);
303
+ }
227
304
  OpenMagnetics::AdvancedSingleSwitchForward converter(converterJson);
228
305
  converter._assertErrors = true;
229
306
 
@@ -248,6 +325,9 @@ OpenMagnetics::Inputs process_single_switch_forward_internal(const json& convert
248
325
  }
249
326
 
250
327
  OpenMagnetics::Inputs process_two_switch_forward_internal(const json& converterJson, bool useNgspice) {
328
+ if (!converterJson.contains("desiredInductance")) {
329
+ return process_base_spec_vector_L<OpenMagnetics::TwoSwitchForward>(converterJson, useNgspice, MAS::Topologies::TWO_SWITCH_FORWARD_CONVERTER);
330
+ }
251
331
  OpenMagnetics::AdvancedTwoSwitchForward converter(converterJson);
252
332
  converter._assertErrors = true;
253
333
 
@@ -272,6 +352,9 @@ OpenMagnetics::Inputs process_two_switch_forward_internal(const json& converterJ
272
352
  }
273
353
 
274
354
  OpenMagnetics::Inputs process_active_clamp_forward_internal(const json& converterJson, bool useNgspice) {
355
+ if (!converterJson.contains("desiredInductance")) {
356
+ return process_base_spec_vector_L<OpenMagnetics::ActiveClampForward>(converterJson, useNgspice, MAS::Topologies::ACTIVE_CLAMP_FORWARD_CONVERTER);
357
+ }
275
358
  OpenMagnetics::AdvancedActiveClampForward converter(converterJson);
276
359
  converter._assertErrors = true;
277
360
 
@@ -296,6 +379,9 @@ OpenMagnetics::Inputs process_active_clamp_forward_internal(const json& converte
296
379
  }
297
380
 
298
381
  OpenMagnetics::Inputs process_push_pull_internal(const json& converterJson, bool useNgspice) {
382
+ if (!converterJson.contains("desiredInductance")) {
383
+ return process_base_spec_vector_L<OpenMagnetics::PushPull>(converterJson, useNgspice, MAS::Topologies::PUSH_PULL_CONVERTER);
384
+ }
299
385
  OpenMagnetics::AdvancedPushPull converter(converterJson);
300
386
  converter._assertErrors = true;
301
387
 
@@ -428,6 +514,9 @@ OpenMagnetics::Inputs process_pshb_internal(const json& converterJson, bool useN
428
514
  }
429
515
 
430
516
  OpenMagnetics::Inputs process_isolated_buck_internal(const json& converterJson, bool useNgspice) {
517
+ if (!converterJson.contains("desiredInductance")) {
518
+ return process_base_spec_vector_L<OpenMagnetics::IsolatedBuck>(converterJson, useNgspice, MAS::Topologies::ISOLATED_BUCK_CONVERTER);
519
+ }
431
520
  OpenMagnetics::AdvancedIsolatedBuck converter(converterJson);
432
521
  converter._assertErrors = true;
433
522
 
@@ -452,6 +541,9 @@ OpenMagnetics::Inputs process_isolated_buck_internal(const json& converterJson,
452
541
  }
453
542
 
454
543
  OpenMagnetics::Inputs process_isolated_buck_boost_internal(const json& converterJson, bool useNgspice) {
544
+ if (!converterJson.contains("desiredInductance")) {
545
+ return process_base_spec_vector_L<OpenMagnetics::IsolatedBuckBoost>(converterJson, useNgspice, MAS::Topologies::ISOLATED_BUCK_BOOST_CONVERTER);
546
+ }
455
547
  OpenMagnetics::AdvancedIsolatedBuckBoost converter(converterJson);
456
548
  converter._assertErrors = true;
457
549
 
@@ -511,6 +603,9 @@ OpenMagnetics::Inputs process_pfc_internal(const json& converterJson) {
511
603
  // ─────────────────────────────────────────────────────────────────────
512
604
 
513
605
  OpenMagnetics::Inputs process_cuk_internal(const json& converterJson, bool useNgspice) {
606
+ if (!converterJson.contains("desiredInductance")) {
607
+ return process_base_spec_scalar_L<OpenMagnetics::Cuk>(converterJson, useNgspice, MAS::Topologies::CUK_CONVERTER);
608
+ }
514
609
  OpenMagnetics::AdvancedCuk converter(converterJson);
515
610
  converter._assertErrors = true;
516
611
  if (useNgspice) {
@@ -530,6 +625,9 @@ OpenMagnetics::Inputs process_cuk_internal(const json& converterJson, bool useNg
530
625
  }
531
626
 
532
627
  OpenMagnetics::Inputs process_sepic_internal(const json& converterJson, bool useNgspice) {
628
+ if (!converterJson.contains("desiredInductance")) {
629
+ return process_base_spec_scalar_L<OpenMagnetics::Sepic>(converterJson, useNgspice, MAS::Topologies::SEPIC_CONVERTER);
630
+ }
533
631
  OpenMagnetics::AdvancedSepic converter(converterJson);
534
632
  converter._assertErrors = true;
535
633
  if (useNgspice) {
@@ -549,6 +647,9 @@ OpenMagnetics::Inputs process_sepic_internal(const json& converterJson, bool use
549
647
  }
550
648
 
551
649
  OpenMagnetics::Inputs process_zeta_internal(const json& converterJson, bool useNgspice) {
650
+ if (!converterJson.contains("desiredInductance")) {
651
+ return process_base_spec_scalar_L<OpenMagnetics::Zeta>(converterJson, useNgspice, MAS::Topologies::ZETA_CONVERTER);
652
+ }
552
653
  OpenMagnetics::AdvancedZeta converter(converterJson);
553
654
  converter._assertErrors = true;
554
655
  if (useNgspice) {
@@ -568,6 +669,9 @@ OpenMagnetics::Inputs process_zeta_internal(const json& converterJson, bool useN
568
669
  }
569
670
 
570
671
  OpenMagnetics::Inputs process_four_switch_buck_boost_internal(const json& converterJson, bool useNgspice) {
672
+ if (!converterJson.contains("desiredInductance")) {
673
+ return process_base_spec_scalar_L<OpenMagnetics::FourSwitchBuckBoost>(converterJson, useNgspice, MAS::Topologies::FOUR_SWITCH_BUCK_BOOST_CONVERTER);
674
+ }
571
675
  OpenMagnetics::AdvancedFourSwitchBuckBoost converter(converterJson);
572
676
  converter._assertErrors = true;
573
677
  if (useNgspice) {
@@ -610,6 +714,35 @@ OpenMagnetics::Inputs process_asymmetric_half_bridge_internal(const json& conver
610
714
  }
611
715
 
612
716
  OpenMagnetics::Inputs process_weinberg_internal(const json& converterJson, bool useNgspice) {
717
+ // Base-spec fast path (ABT #11): derive L from the base Weinberg when no
718
+ // desiredInductance is supplied. Inlined (not via the shared helper)
719
+ // because Weinberg's simulate signature takes a SCALAR turns ratio
720
+ // (single-secondary topology), not the vector the other transformers use.
721
+ if (!converterJson.contains("desiredInductance")) {
722
+ OpenMagnetics::Weinberg converter(converterJson);
723
+ converter._assertErrors = true;
724
+ if (useNgspice) {
725
+ auto designReqs = converter.process_design_requirements();
726
+ std::vector<double> turnsRatios;
727
+ for (const auto& tr : designReqs.get_turns_ratios()) {
728
+ turnsRatios.push_back(OpenMagnetics::resolve_dimensional_values(tr));
729
+ }
730
+ if (turnsRatios.empty()) {
731
+ throw std::runtime_error("Weinberg converter requires at least one turns ratio");
732
+ }
733
+ double inductance = OpenMagnetics::resolve_dimensional_values(designReqs.get_magnetizing_inductance());
734
+ auto operatingPoints = converter.simulate_and_extract_operating_points(turnsRatios[0], inductance);
735
+ OpenMagnetics::Inputs result;
736
+ result.set_design_requirements(designReqs);
737
+ result.set_operating_points(operatingPoints);
738
+ result.get_mutable_design_requirements().set_topology(MAS::Topologies::WEINBERG_CONVERTER);
739
+ return result;
740
+ } else {
741
+ OpenMagnetics::Inputs result = converter.process();
742
+ result.get_mutable_design_requirements().set_topology(MAS::Topologies::WEINBERG_CONVERTER);
743
+ return result;
744
+ }
745
+ }
613
746
  OpenMagnetics::AdvancedWeinberg converter(converterJson);
614
747
  converter._assertErrors = true;
615
748
  if (useNgspice) {
@@ -788,16 +921,50 @@ json process_converter(const std::string& topologyName, json converterJson, bool
788
921
  return process_converter_internal(normalized, converterJson, useNgspice);
789
922
  }
790
923
 
924
+ // Build candidate magnetics from a converter object via the MKF template
925
+ // method, threading the `fast` flag (fast core-only adviser vs full
926
+ // winding+simulation adviser) and optional per-filter weights.
927
+ template <typename ConverterType>
928
+ static std::vector<std::pair<OpenMagnetics::Mas, double>> advise_from_converter_spec(
929
+ const json& converterJson, bool fast,
930
+ const std::map<OpenMagnetics::MagneticFilters, double>& weights,
931
+ int maxResults, OpenMagnetics::MagneticAdviser& adviser) {
932
+ ConverterType converter(converterJson);
933
+ converter._assertErrors = true;
934
+ return weights.empty()
935
+ ? adviser.get_advised_magnetic_from_converter(converter, maxResults, fast)
936
+ : adviser.get_advised_magnetic_from_converter(converter, weights, maxResults, fast);
937
+ }
938
+
939
+ // Single-inductor, non-isolated family (Buck/Boost/Cuk/Sepic/Zeta/
940
+ // FourSwitchBuckBoost). Their Advanced* class requires ONLY desiredInductance
941
+ // on top of a plain converter spec, so we pick the model purely on the
942
+ // presence of that key: Advanced* when the user pins L (honoured as the
943
+ // nominal target), Base when absent (L derived from the ripple budget in
944
+ // MKF). Transformer/resonant families do NOT get this duality — their
945
+ // Advanced* from_json also requires desiredTurnsRatios (Flyback also
946
+ // desiredDutyCycle), i.e. design *outputs* a converter spec does not carry —
947
+ // so they always use the Base model, which derives turns + L itself.
948
+ template <typename Base, typename Advanced>
949
+ static std::vector<std::pair<OpenMagnetics::Mas, double>> advise_inductor_family(
950
+ const json& converterJson, bool fast,
951
+ const std::map<OpenMagnetics::MagneticFilters, double>& weights,
952
+ int maxResults, OpenMagnetics::MagneticAdviser& adviser) {
953
+ return converterJson.contains("desiredInductance")
954
+ ? advise_from_converter_spec<Advanced>(converterJson, fast, weights, maxResults, adviser)
955
+ : advise_from_converter_spec<Base>(converterJson, fast, weights, maxResults, adviser);
956
+ }
957
+
791
958
  // Simplified design_magnetics_from_converter using MKF template method
792
959
  json design_magnetics_from_converter(
793
960
  const std::string& topologyNameRaw,
794
- json converterJson,
795
- int maxResults,
796
- json coreModeJson,
797
- bool useNgspice,
798
- json weightsJson) {
799
-
800
- (void)useNgspice; // Template method always uses ngspice
961
+ json converterJson,
962
+ int maxResults,
963
+ json coreModeJson,
964
+ bool useNgspice,
965
+ json weightsJson,
966
+ bool fast) {
967
+
801
968
 
802
969
  // Accept MAS 1.0 camelCase, pre-1.0 Title Case, or internal short form.
803
970
  const std::string topologyName = normalize_topology_name(topologyNameRaw);
@@ -821,125 +988,109 @@ json design_magnetics_from_converter(
821
988
 
822
989
  std::vector<std::pair<OpenMagnetics::Mas, double>> masMagnetics;
823
990
 
824
- // Use MKF template method - handles all converters automatically.
825
- // For Buck / Boost the basic process_design_requirements() sets
826
- // only `magnetizingInductance.minimum` (a ripple-floor), and
827
- // the CoreAdviser picks the smallest core satisfying it
828
- // regardless of the L the user asked for. Dispatch to the
829
- // Advanced* subclass so spec.desiredInductance becomes the
830
- // nominal target (AdvancedBuck/Boost::process_design_requirements
831
- // override does this, MKF commit 8acd72c7).
991
+ // Build candidate magnetics from the converter spec via the MKF
992
+ // template method. Two independent axes (4 behaviours total):
832
993
  //
833
- // Flyback stays on the basic ctor: AdvancedFlyback's from_json
834
- // requires extra fields (desiredDutyCycle, desiredTurnsRatios)
835
- // that aren't in a normal converter spec. The basic
836
- // Flyback::process_design_requirements already sets nominal
837
- // from its V·s + duty-cycle derivation, so the magnetic gets
838
- // sized correctly without the Advanced* path.
994
+ // * Base vs Advanced* model chosen PER TOPOLOGY:
995
+ // - Single-inductor family (Buck/Boost/Cuk/Sepic/Zeta/
996
+ // FourSwitchBuckBoost): advise_inductor_family() picks
997
+ // Advanced* when desiredInductance is present (honoured as the
998
+ // nominal L target), Base otherwise (L derived from the ripple
999
+ // budget). This is ABT #11 — the slow path used to force
1000
+ // Advanced for Buck/Boost and throw on a base spec.
1001
+ // - Transformer/resonant families: always Base. Their Advanced*
1002
+ // from_json requires desiredTurnsRatios (Flyback also
1003
+ // desiredDutyCycle) — design outputs a converter spec lacks —
1004
+ // so only Base is constructible; it derives turns + L itself.
1005
+ // * fast flag — fast core-only adviser vs full winding+sim adviser,
1006
+ // threaded into get_advised_magnetic_from_converter for every
1007
+ // topology.
839
1008
  if (topologyName == "flyback" || topologyName == "advanced_flyback") {
840
1009
  OpenMagnetics::Flyback converter(converterJson);
841
1010
  converter._assertErrors = true;
842
1011
  masMagnetics = weights.empty()
843
- ? magneticAdviser.get_advised_magnetic_from_converter(converter, maxResults)
844
- : magneticAdviser.get_advised_magnetic_from_converter(converter, weights, maxResults);
1012
+ ? magneticAdviser.get_advised_magnetic_from_converter(converter, maxResults, fast)
1013
+ : magneticAdviser.get_advised_magnetic_from_converter(converter, weights, maxResults, fast);
845
1014
  }
846
1015
  else if (topologyName == "buck" || topologyName == "advanced_buck") {
847
- OpenMagnetics::AdvancedBuck converter(converterJson);
848
- converter._assertErrors = true;
849
- masMagnetics = weights.empty()
850
- ? magneticAdviser.get_advised_magnetic_from_converter(converter, maxResults)
851
- : magneticAdviser.get_advised_magnetic_from_converter(converter, weights, maxResults);
1016
+ masMagnetics = advise_inductor_family<OpenMagnetics::Buck, OpenMagnetics::AdvancedBuck>(
1017
+ converterJson, fast, weights, maxResults, magneticAdviser);
852
1018
  }
853
1019
  else if (topologyName == "boost" || topologyName == "advanced_boost") {
854
- OpenMagnetics::AdvancedBoost converter(converterJson);
855
- converter._assertErrors = true;
856
- masMagnetics = weights.empty()
857
- ? magneticAdviser.get_advised_magnetic_from_converter(converter, maxResults)
858
- : magneticAdviser.get_advised_magnetic_from_converter(converter, weights, maxResults);
1020
+ masMagnetics = advise_inductor_family<OpenMagnetics::Boost, OpenMagnetics::AdvancedBoost>(
1021
+ converterJson, fast, weights, maxResults, magneticAdviser);
859
1022
  }
860
1023
  else if (topologyName == "single_switch_forward") {
861
1024
  OpenMagnetics::SingleSwitchForward converter(converterJson);
862
1025
  converter._assertErrors = true;
863
1026
  masMagnetics = weights.empty()
864
- ? magneticAdviser.get_advised_magnetic_from_converter(converter, maxResults)
865
- : magneticAdviser.get_advised_magnetic_from_converter(converter, weights, maxResults);
1027
+ ? magneticAdviser.get_advised_magnetic_from_converter(converter, maxResults, fast)
1028
+ : magneticAdviser.get_advised_magnetic_from_converter(converter, weights, maxResults, fast);
866
1029
  }
867
1030
  else if (topologyName == "two_switch_forward") {
868
1031
  OpenMagnetics::TwoSwitchForward converter(converterJson);
869
1032
  converter._assertErrors = true;
870
1033
  masMagnetics = weights.empty()
871
- ? magneticAdviser.get_advised_magnetic_from_converter(converter, maxResults)
872
- : magneticAdviser.get_advised_magnetic_from_converter(converter, weights, maxResults);
1034
+ ? magneticAdviser.get_advised_magnetic_from_converter(converter, maxResults, fast)
1035
+ : magneticAdviser.get_advised_magnetic_from_converter(converter, weights, maxResults, fast);
873
1036
  }
874
1037
  else if (topologyName == "active_clamp_forward") {
875
1038
  OpenMagnetics::ActiveClampForward converter(converterJson);
876
1039
  converter._assertErrors = true;
877
1040
  masMagnetics = weights.empty()
878
- ? magneticAdviser.get_advised_magnetic_from_converter(converter, maxResults)
879
- : magneticAdviser.get_advised_magnetic_from_converter(converter, weights, maxResults);
1041
+ ? magneticAdviser.get_advised_magnetic_from_converter(converter, maxResults, fast)
1042
+ : magneticAdviser.get_advised_magnetic_from_converter(converter, weights, maxResults, fast);
880
1043
  }
881
1044
  else if (topologyName == "push_pull") {
882
1045
  OpenMagnetics::PushPull converter(converterJson);
883
1046
  converter._assertErrors = true;
884
1047
  masMagnetics = weights.empty()
885
- ? magneticAdviser.get_advised_magnetic_from_converter(converter, maxResults)
886
- : magneticAdviser.get_advised_magnetic_from_converter(converter, weights, maxResults);
1048
+ ? magneticAdviser.get_advised_magnetic_from_converter(converter, maxResults, fast)
1049
+ : magneticAdviser.get_advised_magnetic_from_converter(converter, weights, maxResults, fast);
887
1050
  }
888
1051
  else if (topologyName == "isolated_buck") {
889
1052
  OpenMagnetics::IsolatedBuck converter(converterJson);
890
1053
  converter._assertErrors = true;
891
1054
  masMagnetics = weights.empty()
892
- ? magneticAdviser.get_advised_magnetic_from_converter(converter, maxResults)
893
- : magneticAdviser.get_advised_magnetic_from_converter(converter, weights, maxResults);
1055
+ ? magneticAdviser.get_advised_magnetic_from_converter(converter, maxResults, fast)
1056
+ : magneticAdviser.get_advised_magnetic_from_converter(converter, weights, maxResults, fast);
894
1057
  }
895
1058
  else if (topologyName == "isolated_buck_boost") {
896
1059
  OpenMagnetics::IsolatedBuckBoost converter(converterJson);
897
1060
  converter._assertErrors = true;
898
1061
  masMagnetics = weights.empty()
899
- ? magneticAdviser.get_advised_magnetic_from_converter(converter, maxResults)
900
- : magneticAdviser.get_advised_magnetic_from_converter(converter, weights, maxResults);
1062
+ ? magneticAdviser.get_advised_magnetic_from_converter(converter, maxResults, fast)
1063
+ : magneticAdviser.get_advised_magnetic_from_converter(converter, weights, maxResults, fast);
901
1064
  }
902
1065
  else if (topologyName == "llc" || topologyName == "advanced_llc") {
903
1066
  OpenMagnetics::Llc converter(converterJson);
904
1067
  converter._assertErrors = true;
905
1068
  masMagnetics = weights.empty()
906
- ? magneticAdviser.get_advised_magnetic_from_converter(converter, maxResults)
907
- : magneticAdviser.get_advised_magnetic_from_converter(converter, weights, maxResults);
1069
+ ? magneticAdviser.get_advised_magnetic_from_converter(converter, maxResults, fast)
1070
+ : magneticAdviser.get_advised_magnetic_from_converter(converter, weights, maxResults, fast);
908
1071
  }
909
1072
  else if (topologyName == "cuk" || topologyName == "advanced_cuk") {
910
- OpenMagnetics::Cuk converter(converterJson);
911
- converter._assertErrors = true;
912
- masMagnetics = weights.empty()
913
- ? magneticAdviser.get_advised_magnetic_from_converter(converter, maxResults)
914
- : magneticAdviser.get_advised_magnetic_from_converter(converter, weights, maxResults);
1073
+ masMagnetics = advise_inductor_family<OpenMagnetics::Cuk, OpenMagnetics::AdvancedCuk>(
1074
+ converterJson, fast, weights, maxResults, magneticAdviser);
915
1075
  }
916
1076
  else if (topologyName == "sepic" || topologyName == "advanced_sepic") {
917
- OpenMagnetics::Sepic converter(converterJson);
918
- converter._assertErrors = true;
919
- masMagnetics = weights.empty()
920
- ? magneticAdviser.get_advised_magnetic_from_converter(converter, maxResults)
921
- : magneticAdviser.get_advised_magnetic_from_converter(converter, weights, maxResults);
1077
+ masMagnetics = advise_inductor_family<OpenMagnetics::Sepic, OpenMagnetics::AdvancedSepic>(
1078
+ converterJson, fast, weights, maxResults, magneticAdviser);
922
1079
  }
923
1080
  else if (topologyName == "zeta" || topologyName == "advanced_zeta") {
924
- OpenMagnetics::Zeta converter(converterJson);
925
- converter._assertErrors = true;
926
- masMagnetics = weights.empty()
927
- ? magneticAdviser.get_advised_magnetic_from_converter(converter, maxResults)
928
- : magneticAdviser.get_advised_magnetic_from_converter(converter, weights, maxResults);
1081
+ masMagnetics = advise_inductor_family<OpenMagnetics::Zeta, OpenMagnetics::AdvancedZeta>(
1082
+ converterJson, fast, weights, maxResults, magneticAdviser);
929
1083
  }
930
1084
  else if (topologyName == "four_switch_buck_boost" || topologyName == "advanced_four_switch_buck_boost") {
931
- OpenMagnetics::FourSwitchBuckBoost converter(converterJson);
932
- converter._assertErrors = true;
933
- masMagnetics = weights.empty()
934
- ? magneticAdviser.get_advised_magnetic_from_converter(converter, maxResults)
935
- : magneticAdviser.get_advised_magnetic_from_converter(converter, weights, maxResults);
1085
+ masMagnetics = advise_inductor_family<OpenMagnetics::FourSwitchBuckBoost, OpenMagnetics::AdvancedFourSwitchBuckBoost>(
1086
+ converterJson, fast, weights, maxResults, magneticAdviser);
936
1087
  }
937
1088
  else if (topologyName == "asymmetric_half_bridge" || topologyName == "advanced_asymmetric_half_bridge") {
938
1089
  OpenMagnetics::AsymmetricHalfBridge converter(converterJson);
939
1090
  converter._assertErrors = true;
940
1091
  masMagnetics = weights.empty()
941
- ? magneticAdviser.get_advised_magnetic_from_converter(converter, maxResults)
942
- : magneticAdviser.get_advised_magnetic_from_converter(converter, weights, maxResults);
1092
+ ? magneticAdviser.get_advised_magnetic_from_converter(converter, maxResults, fast)
1093
+ : magneticAdviser.get_advised_magnetic_from_converter(converter, weights, maxResults, fast);
943
1094
  }
944
1095
  else if (topologyName == "phase_shifted_full_bridge" || topologyName == "psfb" ||
945
1096
  topologyName == "advanced_phase_shifted_full_bridge" || topologyName == "advanced_psfb") {
@@ -952,36 +1103,36 @@ json design_magnetics_from_converter(
952
1103
  OpenMagnetics::Psfb converter(converterJson);
953
1104
  converter._assertErrors = true;
954
1105
  masMagnetics = weights.empty()
955
- ? magneticAdviser.get_advised_magnetic_from_converter(converter, maxResults)
956
- : magneticAdviser.get_advised_magnetic_from_converter(converter, weights, maxResults);
1106
+ ? magneticAdviser.get_advised_magnetic_from_converter(converter, maxResults, fast)
1107
+ : magneticAdviser.get_advised_magnetic_from_converter(converter, weights, maxResults, fast);
957
1108
  }
958
1109
  else if (topologyName == "weinberg" || topologyName == "advanced_weinberg") {
959
1110
  OpenMagnetics::Weinberg converter(converterJson);
960
1111
  converter._assertErrors = true;
961
1112
  masMagnetics = weights.empty()
962
- ? magneticAdviser.get_advised_magnetic_from_converter(converter, maxResults)
963
- : magneticAdviser.get_advised_magnetic_from_converter(converter, weights, maxResults);
1113
+ ? magneticAdviser.get_advised_magnetic_from_converter(converter, maxResults, fast)
1114
+ : magneticAdviser.get_advised_magnetic_from_converter(converter, weights, maxResults, fast);
964
1115
  }
965
1116
  else if (topologyName == "vienna" || topologyName == "advanced_vienna") {
966
1117
  OpenMagnetics::Vienna converter(converterJson);
967
1118
  converter._assertErrors = true;
968
1119
  masMagnetics = weights.empty()
969
- ? magneticAdviser.get_advised_magnetic_from_converter(converter, maxResults)
970
- : magneticAdviser.get_advised_magnetic_from_converter(converter, weights, maxResults);
1120
+ ? magneticAdviser.get_advised_magnetic_from_converter(converter, maxResults, fast)
1121
+ : magneticAdviser.get_advised_magnetic_from_converter(converter, weights, maxResults, fast);
971
1122
  }
972
1123
  else if (topologyName == "clllc" || topologyName == "advanced_clllc") {
973
1124
  OpenMagnetics::Clllc converter(converterJson);
974
1125
  converter._assertErrors = true;
975
1126
  masMagnetics = weights.empty()
976
- ? magneticAdviser.get_advised_magnetic_from_converter(converter, maxResults)
977
- : magneticAdviser.get_advised_magnetic_from_converter(converter, weights, maxResults);
1127
+ ? magneticAdviser.get_advised_magnetic_from_converter(converter, maxResults, fast)
1128
+ : magneticAdviser.get_advised_magnetic_from_converter(converter, weights, maxResults, fast);
978
1129
  }
979
1130
  else if (topologyName == "src" || topologyName == "advanced_src") {
980
1131
  OpenMagnetics::Src converter(converterJson);
981
1132
  converter._assertErrors = true;
982
1133
  masMagnetics = weights.empty()
983
- ? magneticAdviser.get_advised_magnetic_from_converter(converter, maxResults)
984
- : magneticAdviser.get_advised_magnetic_from_converter(converter, weights, maxResults);
1134
+ ? magneticAdviser.get_advised_magnetic_from_converter(converter, maxResults, fast)
1135
+ : magneticAdviser.get_advised_magnetic_from_converter(converter, weights, maxResults, fast);
985
1136
  }
986
1137
  else if (topologyName == "dab" || topologyName == "advanced_dab" ||
987
1138
  topologyName == "dual_active_bridge") {
@@ -992,18 +1143,26 @@ json design_magnetics_from_converter(
992
1143
  OpenMagnetics::Dab converter(converterJson);
993
1144
  converter._assertErrors = true;
994
1145
  masMagnetics = weights.empty()
995
- ? magneticAdviser.get_advised_magnetic_from_converter(converter, maxResults)
996
- : magneticAdviser.get_advised_magnetic_from_converter(converter, weights, maxResults);
1146
+ ? magneticAdviser.get_advised_magnetic_from_converter(converter, maxResults, fast)
1147
+ : magneticAdviser.get_advised_magnetic_from_converter(converter, weights, maxResults, fast);
997
1148
  }
998
1149
  else {
999
- // Fall back to old approach for other topologies
1150
+ // Fall back for topologies without a converter-model adviser path
1151
+ // (e.g. PFC, current transformer). dispatch_converter / the
1152
+ // process_*_internal it calls already pick Base vs Advanced on the
1153
+ // presence of desiredInductance (ABT #11), so the Base/Advanced
1154
+ // behaviour is preserved here too; only the adviser stage differs.
1000
1155
  json inputsJson = process_converter_internal(topologyName, converterJson, useNgspice);
1001
1156
  if (inputsJson.contains("error")) return inputsJson;
1002
-
1157
+
1003
1158
  OpenMagnetics::Inputs inputs(inputsJson);
1004
- masMagnetics = weights.empty()
1005
- ? magneticAdviser.get_advised_magnetic(inputs, maxResults)
1006
- : magneticAdviser.get_advised_magnetic(inputs, weights, maxResults);
1159
+ if (fast) {
1160
+ masMagnetics = magneticAdviser.get_advised_magnetic_fast(inputs, maxResults);
1161
+ } else if (weights.empty()) {
1162
+ masMagnetics = magneticAdviser.get_advised_magnetic(inputs, maxResults);
1163
+ } else {
1164
+ masMagnetics = magneticAdviser.get_advised_magnetic(inputs, weights, maxResults);
1165
+ }
1007
1166
  }
1008
1167
 
1009
1168
  // Build results (same as before)
@@ -3944,9 +4103,10 @@ void register_converter_bindings(py::module& m) {
3944
4103
 
3945
4104
  m.def("design_magnetics_from_converter", &design_magnetics_from_converter,
3946
4105
  "Design magnetic components from a converter specification.",
3947
- py::arg("topology_name"), py::arg("converter_json"),
4106
+ py::arg("topology_name"), py::arg("converter_json"),
3948
4107
  py::arg("max_results") = 1, py::arg("core_mode_json") = "available cores",
3949
- py::arg("use_ngspice") = true, py::arg("weights_json") = nullptr);
4108
+ py::arg("use_ngspice") = true, py::arg("weights_json") = nullptr,
4109
+ py::arg("fast") = false);
3950
4110
 
3951
4111
  m.def("process_flyback", &process_flyback, "Process Flyback converter.", py::arg("flyback"));
3952
4112
  m.def("process_buck", &process_buck, "Process Buck converter.", py::arg("buck"));
@@ -9,12 +9,13 @@ json process_converter(const std::string& topologyName, json converterJson, bool
9
9
 
10
10
  // Combined endpoint: converter -> magnetic designs
11
11
  json design_magnetics_from_converter(
12
- const std::string& topologyName,
13
- json converterJson,
14
- int maxResults,
15
- json coreModeJson,
16
- bool useNgspice = true,
17
- json weightsJson = nullptr);
12
+ const std::string& topologyName,
13
+ json converterJson,
14
+ int maxResults,
15
+ json coreModeJson,
16
+ bool useNgspice = true,
17
+ json weightsJson = nullptr,
18
+ bool fast = false);
18
19
 
19
20
  // Per-topology thin wrappers (from .pyi stubs)
20
21
  json process_flyback(json flybackJson);
@@ -322,12 +322,34 @@ double calculate_inductance_from_number_turns_and_gapping(json coreData, json co
322
322
  return magnetizingInductance;
323
323
  }
324
324
 
325
- double calculate_number_turns_from_gapping_and_inductance(json coreData, json inputsData, json modelsData) {
325
+ double calculate_number_turns_from_gapping_and_inductance(json coreData, json coilData, json inputsData, json modelsData) {
326
326
  OpenMagnetics::Core core(coreData);
327
+ OpenMagnetics::Coil coil(coilData);
327
328
  OpenMagnetics::Inputs inputs(inputsData);
328
329
 
329
330
  std::map<std::string, std::string> models = modelsData.get<std::map<std::string, std::string>>();
330
-
331
+
332
+ auto reluctanceModelName = OpenMagnetics::defaults.reluctanceModelDefault;
333
+ if (models.find("reluctance") != models.end()) {
334
+ OpenMagnetics::from_json(models["reluctance"], reluctanceModelName);
335
+ }
336
+
337
+ OpenMagnetics::MagnetizingInductance magnetizingInductanceObj(reluctanceModelName);
338
+ double numberTurns = magnetizingInductanceObj.calculate_number_turns_from_gapping_and_inductance(core, coil, &inputs);
339
+
340
+ return numberTurns;
341
+ }
342
+
343
+ // Legacy 3-argument variant (no coil_data). Backward-compat shim for callers
344
+ // written before the coil parameter was added; forwards to MKF's 3-argument
345
+ // overload, which synthesizes a single-primary-winding coil internally. New
346
+ // code should pass coil_data and use the 4-argument form above.
347
+ double calculate_number_turns_from_gapping_and_inductance_legacy(json coreData, json inputsData, json modelsData) {
348
+ OpenMagnetics::Core core(coreData);
349
+ OpenMagnetics::Inputs inputs(inputsData);
350
+
351
+ std::map<std::string, std::string> models = modelsData.get<std::map<std::string, std::string>>();
352
+
331
353
  auto reluctanceModelName = OpenMagnetics::defaults.reluctanceModelDefault;
332
354
  if (models.find("reluctance") != models.end()) {
333
355
  OpenMagnetics::from_json(models["reluctance"], reluctanceModelName);
@@ -960,14 +982,36 @@ void register_core_bindings(py::module& m) {
960
982
 
961
983
  Args:
962
984
  core_data: JSON object with core specification.
985
+ coil_data: JSON object with coil/winding specification.
963
986
  inputs_data: JSON Inputs with magnetizingInductance requirement.
964
987
  models_data: JSON dict with "reluctance" model selection.
965
-
988
+
989
+ Returns:
990
+ Required number of turns (may be non-integer).
991
+ )pbdoc",
992
+ py::arg("core_data"), py::arg("coil_data"), py::arg("inputs_data"), py::arg("models_data"));
993
+
994
+ // Legacy 3-argument overload (no coil_data) registered under the SAME name:
995
+ // pybind dispatches by arity, so existing 3-arg callers keep working while
996
+ // new code uses the coil-aware 4-arg form above (the canonical default).
997
+ m.def("calculate_number_turns_from_gapping_and_inductance", &calculate_number_turns_from_gapping_and_inductance_legacy,
998
+ R"pbdoc(
999
+ Legacy variant of calculate_number_turns_from_gapping_and_inductance
1000
+ without coil_data (kept for backward compatibility).
1001
+
1002
+ Synthesizes a single-primary-winding coil internally. Prefer the
1003
+ coil-aware 4-argument form for multi-winding designs.
1004
+
1005
+ Args:
1006
+ core_data: JSON object with core specification.
1007
+ inputs_data: JSON Inputs with magnetizingInductance requirement.
1008
+ models_data: JSON dict with "reluctance" model selection.
1009
+
966
1010
  Returns:
967
1011
  Required number of turns (may be non-integer).
968
1012
  )pbdoc",
969
1013
  py::arg("core_data"), py::arg("inputs_data"), py::arg("models_data"));
970
-
1014
+
971
1015
  m.def("calculate_gapping_from_number_turns_and_inductance", &calculate_gapping_from_number_turns_and_inductance,
972
1016
  R"pbdoc(
973
1017
  Calculate required gap from turns count and target inductance.
@@ -43,7 +43,8 @@ double calculate_temperature_from_core_thermal_resistance(json coreJson, double
43
43
  json calculate_gap_reluctance(json coreGapData, std::string modelNameString);
44
44
  json get_gap_reluctance_model_information();
45
45
  double calculate_inductance_from_number_turns_and_gapping(json coreData, json coilData, json operatingPointData, json modelsData);
46
- double calculate_number_turns_from_gapping_and_inductance(json coreData, json inputsData, json modelsData);
46
+ double calculate_number_turns_from_gapping_and_inductance(json coreData, json coilData, json inputsData, json modelsData);
47
+ double calculate_number_turns_from_gapping_and_inductance_legacy(json coreData, json inputsData, json modelsData);
47
48
  json calculate_gapping_from_number_turns_and_inductance(json coreData, json coilData, json inputsData, std::string gappingTypeJson, int decimals, json modelsData);
48
49
 
49
50
  // Shape queries by family/manufacturer
File without changes
File without changes