PyOpenMagnetics 1.7.38__tar.gz → 1.7.40__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 (95) hide show
  1. {pyopenmagnetics-1.7.38 → pyopenmagnetics-1.7.40}/CMakeLists.txt +5 -3
  2. {pyopenmagnetics-1.7.38 → pyopenmagnetics-1.7.40}/PKG-INFO +1 -1
  3. {pyopenmagnetics-1.7.38 → pyopenmagnetics-1.7.40}/pyproject.toml +1 -1
  4. {pyopenmagnetics-1.7.38 → pyopenmagnetics-1.7.40}/src/losses.cpp +24 -0
  5. {pyopenmagnetics-1.7.38 → pyopenmagnetics-1.7.40}/src/settings.cpp +140 -101
  6. {pyopenmagnetics-1.7.38 → pyopenmagnetics-1.7.40}/.github/workflows/ci.yml +0 -0
  7. {pyopenmagnetics-1.7.38 → pyopenmagnetics-1.7.40}/.github/workflows/publish.yml +0 -0
  8. {pyopenmagnetics-1.7.38 → pyopenmagnetics-1.7.40}/.gitignore +0 -0
  9. {pyopenmagnetics-1.7.38 → pyopenmagnetics-1.7.40}/AGENTS.md +0 -0
  10. {pyopenmagnetics-1.7.38 → pyopenmagnetics-1.7.40}/LICENSE +0 -0
  11. {pyopenmagnetics-1.7.38 → pyopenmagnetics-1.7.40}/PyOpenMagnetics.pyi +0 -0
  12. {pyopenmagnetics-1.7.38 → pyopenmagnetics-1.7.40}/README.md +0 -0
  13. {pyopenmagnetics-1.7.38 → pyopenmagnetics-1.7.40}/_packaging/__init__.py +0 -0
  14. {pyopenmagnetics-1.7.38 → pyopenmagnetics-1.7.40}/_packaging/_build_info.py.in +0 -0
  15. {pyopenmagnetics-1.7.38 → pyopenmagnetics-1.7.40}/api/MAS.py +0 -0
  16. {pyopenmagnetics-1.7.38 → pyopenmagnetics-1.7.40}/api/mas_db_reader.py +0 -0
  17. {pyopenmagnetics-1.7.38 → pyopenmagnetics-1.7.40}/api/validation.py +0 -0
  18. {pyopenmagnetics-1.7.38 → pyopenmagnetics-1.7.40}/clear_cibuildwheel_cache.sh +0 -0
  19. {pyopenmagnetics-1.7.38 → pyopenmagnetics-1.7.40}/docs/compatibility.md +0 -0
  20. {pyopenmagnetics-1.7.38 → pyopenmagnetics-1.7.40}/docs/errors.md +0 -0
  21. {pyopenmagnetics-1.7.38 → pyopenmagnetics-1.7.40}/docs/performance.md +0 -0
  22. {pyopenmagnetics-1.7.38 → pyopenmagnetics-1.7.40}/examples/README.md +0 -0
  23. {pyopenmagnetics-1.7.38 → pyopenmagnetics-1.7.40}/examples/buck_inductor.py +0 -0
  24. {pyopenmagnetics-1.7.38 → pyopenmagnetics-1.7.40}/examples/complete_simulation_example.py +0 -0
  25. {pyopenmagnetics-1.7.38 → pyopenmagnetics-1.7.40}/examples/converter_design_example.py +0 -0
  26. {pyopenmagnetics-1.7.38 → pyopenmagnetics-1.7.40}/examples/debug_bobbin.py +0 -0
  27. {pyopenmagnetics-1.7.38 → pyopenmagnetics-1.7.40}/examples/debug_coil.py +0 -0
  28. {pyopenmagnetics-1.7.38 → pyopenmagnetics-1.7.40}/examples/debug_core.py +0 -0
  29. {pyopenmagnetics-1.7.38 → pyopenmagnetics-1.7.40}/examples/debug_plotting.py +0 -0
  30. {pyopenmagnetics-1.7.38 → pyopenmagnetics-1.7.40}/examples/flyback_220v_12v_1a.py +0 -0
  31. {pyopenmagnetics-1.7.38 → pyopenmagnetics-1.7.40}/examples/flyback_220v_12v_2a_complete.py +0 -0
  32. {pyopenmagnetics-1.7.38 → pyopenmagnetics-1.7.40}/examples/flyback_bh_curve.png +0 -0
  33. {pyopenmagnetics-1.7.38 → pyopenmagnetics-1.7.40}/examples/flyback_core.png +0 -0
  34. {pyopenmagnetics-1.7.38 → pyopenmagnetics-1.7.40}/examples/flyback_design.py +0 -0
  35. {pyopenmagnetics-1.7.38 → pyopenmagnetics-1.7.40}/examples/flyback_summary.png +0 -0
  36. {pyopenmagnetics-1.7.38 → pyopenmagnetics-1.7.40}/examples/flyback_waveforms.png +0 -0
  37. {pyopenmagnetics-1.7.38 → pyopenmagnetics-1.7.40}/examples/list_plot_funcs.py +0 -0
  38. {pyopenmagnetics-1.7.38 → pyopenmagnetics-1.7.40}/examples/plot_flyback_design.py +0 -0
  39. {pyopenmagnetics-1.7.38 → pyopenmagnetics-1.7.40}/examples/plot_flyback_pyom.py +0 -0
  40. {pyopenmagnetics-1.7.38 → pyopenmagnetics-1.7.40}/examples/test_field_calc.py +0 -0
  41. {pyopenmagnetics-1.7.38 → pyopenmagnetics-1.7.40}/examples/test_field_plot.py +0 -0
  42. {pyopenmagnetics-1.7.38 → pyopenmagnetics-1.7.40}/force_fresh_build.sh +0 -0
  43. {pyopenmagnetics-1.7.38 → pyopenmagnetics-1.7.40}/llms.txt +0 -0
  44. {pyopenmagnetics-1.7.38 → pyopenmagnetics-1.7.40}/notebooks/01_getting_started.ipynb +0 -0
  45. {pyopenmagnetics-1.7.38 → pyopenmagnetics-1.7.40}/notebooks/02_buck_inductor.ipynb +0 -0
  46. {pyopenmagnetics-1.7.38 → pyopenmagnetics-1.7.40}/notebooks/03_core_losses.ipynb +0 -0
  47. {pyopenmagnetics-1.7.38 → pyopenmagnetics-1.7.40}/notebooks/README.md +0 -0
  48. {pyopenmagnetics-1.7.38 → pyopenmagnetics-1.7.40}/requirements.txt +0 -0
  49. {pyopenmagnetics-1.7.38 → pyopenmagnetics-1.7.40}/src/advisers.cpp +0 -0
  50. {pyopenmagnetics-1.7.38 → pyopenmagnetics-1.7.40}/src/advisers.h +0 -0
  51. {pyopenmagnetics-1.7.38 → pyopenmagnetics-1.7.40}/src/bobbin.cpp +0 -0
  52. {pyopenmagnetics-1.7.38 → pyopenmagnetics-1.7.40}/src/bobbin.h +0 -0
  53. {pyopenmagnetics-1.7.38 → pyopenmagnetics-1.7.40}/src/cmc.cpp +0 -0
  54. {pyopenmagnetics-1.7.38 → pyopenmagnetics-1.7.40}/src/cmc.h +0 -0
  55. {pyopenmagnetics-1.7.38 → pyopenmagnetics-1.7.40}/src/common.h +0 -0
  56. {pyopenmagnetics-1.7.38 → pyopenmagnetics-1.7.40}/src/converter.cpp +0 -0
  57. {pyopenmagnetics-1.7.38 → pyopenmagnetics-1.7.40}/src/converter.h +0 -0
  58. {pyopenmagnetics-1.7.38 → pyopenmagnetics-1.7.40}/src/core.cpp +0 -0
  59. {pyopenmagnetics-1.7.38 → pyopenmagnetics-1.7.40}/src/core.h +0 -0
  60. {pyopenmagnetics-1.7.38 → pyopenmagnetics-1.7.40}/src/crossref.cpp +0 -0
  61. {pyopenmagnetics-1.7.38 → pyopenmagnetics-1.7.40}/src/crossref.h +0 -0
  62. {pyopenmagnetics-1.7.38 → pyopenmagnetics-1.7.40}/src/database.cpp +0 -0
  63. {pyopenmagnetics-1.7.38 → pyopenmagnetics-1.7.40}/src/database.h +0 -0
  64. {pyopenmagnetics-1.7.38 → pyopenmagnetics-1.7.40}/src/logging.cpp +0 -0
  65. {pyopenmagnetics-1.7.38 → pyopenmagnetics-1.7.40}/src/logging.h +0 -0
  66. {pyopenmagnetics-1.7.38 → pyopenmagnetics-1.7.40}/src/losses.h +0 -0
  67. {pyopenmagnetics-1.7.38 → pyopenmagnetics-1.7.40}/src/module.cpp +0 -0
  68. {pyopenmagnetics-1.7.38 → pyopenmagnetics-1.7.40}/src/plotting.cpp +0 -0
  69. {pyopenmagnetics-1.7.38 → pyopenmagnetics-1.7.40}/src/plotting.h +0 -0
  70. {pyopenmagnetics-1.7.38 → pyopenmagnetics-1.7.40}/src/settings.h +0 -0
  71. {pyopenmagnetics-1.7.38 → pyopenmagnetics-1.7.40}/src/simulation.cpp +0 -0
  72. {pyopenmagnetics-1.7.38 → pyopenmagnetics-1.7.40}/src/simulation.h +0 -0
  73. {pyopenmagnetics-1.7.38 → pyopenmagnetics-1.7.40}/src/utils.cpp +0 -0
  74. {pyopenmagnetics-1.7.38 → pyopenmagnetics-1.7.40}/src/utils.h +0 -0
  75. {pyopenmagnetics-1.7.38 → pyopenmagnetics-1.7.40}/src/winding.cpp +0 -0
  76. {pyopenmagnetics-1.7.38 → pyopenmagnetics-1.7.40}/src/winding.h +0 -0
  77. {pyopenmagnetics-1.7.38 → pyopenmagnetics-1.7.40}/src/wire.cpp +0 -0
  78. {pyopenmagnetics-1.7.38 → pyopenmagnetics-1.7.40}/src/wire.h +0 -0
  79. {pyopenmagnetics-1.7.38 → pyopenmagnetics-1.7.40}/test.py +0 -0
  80. {pyopenmagnetics-1.7.38 → pyopenmagnetics-1.7.40}/tests/__init__.py +0 -0
  81. {pyopenmagnetics-1.7.38 → pyopenmagnetics-1.7.40}/tests/conftest.py +0 -0
  82. {pyopenmagnetics-1.7.38 → pyopenmagnetics-1.7.40}/tests/test_converter_endpoints.py +0 -0
  83. {pyopenmagnetics-1.7.38 → pyopenmagnetics-1.7.40}/tests/test_core.py +0 -0
  84. {pyopenmagnetics-1.7.38 → pyopenmagnetics-1.7.40}/tests/test_core_adviser.py +0 -0
  85. {pyopenmagnetics-1.7.38 → pyopenmagnetics-1.7.40}/tests/test_datasheet_only_catalogue.py +0 -0
  86. {pyopenmagnetics-1.7.38 → pyopenmagnetics-1.7.40}/tests/test_examples_integration.py +0 -0
  87. {pyopenmagnetics-1.7.38 → pyopenmagnetics-1.7.40}/tests/test_inputs.py +0 -0
  88. {pyopenmagnetics-1.7.38 → pyopenmagnetics-1.7.40}/tests/test_logging.py +0 -0
  89. {pyopenmagnetics-1.7.38 → pyopenmagnetics-1.7.40}/tests/test_losses_not_evaluable_catalogue.py +0 -0
  90. {pyopenmagnetics-1.7.38 → pyopenmagnetics-1.7.40}/tests/test_magnetic_adviser.py +0 -0
  91. {pyopenmagnetics-1.7.38 → pyopenmagnetics-1.7.40}/tests/test_magnetics_catalogue_load.py +0 -0
  92. {pyopenmagnetics-1.7.38 → pyopenmagnetics-1.7.40}/tests/test_mas_schema_walk.py +0 -0
  93. {pyopenmagnetics-1.7.38 → pyopenmagnetics-1.7.40}/tests/test_material_validator.py +0 -0
  94. {pyopenmagnetics-1.7.38 → pyopenmagnetics-1.7.40}/tests/test_plotting.py +0 -0
  95. {pyopenmagnetics-1.7.38 → pyopenmagnetics-1.7.40}/tests/test_winding.py +0 -0
@@ -31,14 +31,16 @@ else ()
31
31
  # Enable Link Time Optimization to reduce binary size
32
32
  set(CMAKE_INTERPROCEDURAL_OPTIMIZATION FALSE) # TEMP: disable LTO (OOM)
33
33
 
34
- # Wheel/CI build: -O1 keeps compilation fast and low-memory. -g is
34
+ # Wheel/CI build: -O2 (Alf, 2026-10-28, ABT #2124: at -O1 the thermal network's elimination ran as a
35
+ # scalar loop and was 43% of a catalogue search). Still the baseline x86-64 target with no -march, so no
36
+ # FMA contraction: results are not expected to change from -O1. -g is
35
37
  # intentionally dropped — full debug info on the large pybind11 + json
36
38
  # translation units (converter.cpp is ~6k lines) balloons RAM during
37
39
  # compile and OOM-kills CI runners (exit 143). Frame pointers are kept
38
40
  # for usable backtraces. We REPLACE (not append) the flags so the -g in
39
41
  # the default RelWithDebInfo flags is stripped too. For local debugging
40
42
  # with symbols, re-add -g here.
41
- set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-O1 -DNDEBUG -fno-omit-frame-pointer")
43
+ set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-O2 -DNDEBUG -fno-omit-frame-pointer")
42
44
  endif()
43
45
 
44
46
  SET(MAS_DIRECTORY "${CMAKE_BINARY_DIR}/MAS/")
@@ -142,7 +144,7 @@ if(NOT LOCAL_MKF_MAS)
142
144
  set(MKF_GIT_TAG "main"
143
145
  CACHE STRING "MKF git ref for the vendored PyOM build")
144
146
  # Force fresh clone by using a unique timestamp - update this when the pin changes
145
- set(MKF_FORCE_REFRESH "2026-10-28-mkf-619fa558")
147
+ set(MKF_FORCE_REFRESH "2026-10-28-mkf-ff429ce0")
146
148
  # Tell MKF to disable matplotplusplus and use SVG-based Painter instead
147
149
  set(INCLUDE_PYMKF ON CACHE BOOL "Build Python interface" FORCE)
148
150
  # 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.38
3
+ Version: 1.7.40
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.38"
7
+ version = "1.7.40"
8
8
  requires-python = ">=3.8"
9
9
  authors = [
10
10
  { name="Alfonso Martinez", email="Alfonso_VII@hotmail.com" },
@@ -55,6 +55,30 @@ json calculate_core_losses(json coreData, json coilData, json inputsData, json m
55
55
  result["maximumCoreTemperatureRise"] = coreLossesOutput.get_temperature() - operatingPoint.get_conditions().get_ambient_temperature();
56
56
  }
57
57
 
58
+ // ABT #2264: when its own losses drive the core to its material's data limit (its Curie point, or where its
59
+ // permeability or loss data ends), MKF holds it there (ABT #1855, #2078). Held at Curie it is magnetized as the
60
+ // paramagnetic core it is, so the published losses are tiny while the temperature is the limit. Without these keys a
61
+ // held point read like a cool, low-loss one. Always present, so the absence of a hold is stated, not implied.
62
+ auto lossesBeforeHold = magneticSimulator.get_last_losses_before_hold();
63
+ result["coreHeldAtDataLimit"] = lossesBeforeHold.has_value();
64
+ if (lossesBeforeHold) {
65
+ const auto& held = lossesBeforeHold.value();
66
+ std::string limit;
67
+ switch (held.heldAt.source) {
68
+ case OpenMagnetics::CoreDataCeilingSource::CURIE_TEMPERATURE: limit = "curieTemperature"; break;
69
+ case OpenMagnetics::CoreDataCeilingSource::PERMEABILITY_TABLE_END: limit = "permeabilityDataEnd"; break;
70
+ case OpenMagnetics::CoreDataCeilingSource::STEINMETZ_TEMPERATURE_FACTOR_END: limit = "lossDataEnd"; break;
71
+ }
72
+ result["coreHeldAt"] = {{"temperature", held.heldAt.temperature}, {"limit", limit}};
73
+ // The last iterate with the core still the material its data describes: the losses that drove it to the limit.
74
+ json beforeHold;
75
+ to_json(beforeHold, held.coreLosses);
76
+ result["coreLossesBeforeHold"] = beforeHold;
77
+ }
78
+ if (temperatureOutput) {
79
+ result["temperatureMethod"] = temperatureOutput->get_method_used();
80
+ }
81
+
58
82
  return result;
59
83
  }
60
84
 
@@ -1,4 +1,8 @@
1
1
  #include "settings.h"
2
+ #include <set>
3
+ #include <stdexcept>
4
+ #include <string>
5
+ #include <vector>
2
6
 
3
7
  namespace PyMKF {
4
8
 
@@ -99,7 +103,6 @@ json get_settings() {
99
103
  to_json(aux, OpenMagnetics::settings.get_effective_parameter_standard());
100
104
  settingsJson["effectiveParameterStandard"] = aux;
101
105
  }
102
- settingsJson["nanocrystallineStackingFactor"] = OpenMagnetics::settings.get_nanocrystalline_stacking_factor();
103
106
 
104
107
  // Painter
105
108
  settingsJson["painterNumberPointsX"] = OpenMagnetics::settings.get_painter_number_points_x();
@@ -262,199 +265,235 @@ json get_settings() {
262
265
  settingsJson["preferredCoreMaterialPowderManufacturer"] = OpenMagnetics::settings.get_preferred_core_material_powder_manufacturer();
263
266
  settingsJson["coreCrossReferencerAllowDifferentCoreMaterialType"] = OpenMagnetics::settings.get_core_cross_referencer_allow_different_core_material_type();
264
267
 
268
+ // Material data (ABT #2265)
269
+ settingsJson["allowMaterialDataExtrapolation"] = OpenMagnetics::settings.get_allow_material_data_extrapolation();
270
+
265
271
  return settingsJson;
266
272
  }
267
273
 
268
- void set_settings(json settingsJson) {
274
+ // Applies every key of settingsJson that PyOM knows. With checkedKeys set, it also records each key name it looks
275
+ // for, so calling it on an empty object lists the known keys without applying anything (set_settings uses this).
276
+ static void apply_settings(json settingsJson, std::set<std::string>* checkedKeys) {
277
+ auto has = [&](const char* key) {
278
+ if (checkedKeys) checkedKeys->insert(key);
279
+ return settingsJson.contains(key);
280
+ };
281
+ // Material data (ABT #2265): whether material data (Steinmetz, permeability, ...) is evaluated outside the range
282
+ // it was measured on, holding the nearest data with a warning, or throws there.
283
+ if (has("allowMaterialDataExtrapolation")) OpenMagnetics::settings.set_allow_material_data_extrapolation(settingsJson["allowMaterialDataExtrapolation"]);
284
+
269
285
  // General
270
- if (settingsJson.contains("verbose")) OpenMagnetics::settings.set_verbose(settingsJson["verbose"]);
271
- if (settingsJson.contains("useToroidalCores")) OpenMagnetics::settings.set_use_toroidal_cores(settingsJson["useToroidalCores"]);
272
- if (settingsJson.contains("useConcentricCores")) OpenMagnetics::settings.set_use_concentric_cores(settingsJson["useConcentricCores"]);
273
- if (settingsJson.contains("useOnlyCoresInStock")) OpenMagnetics::settings.set_use_only_cores_in_stock(settingsJson["useOnlyCoresInStock"]);
274
- if (settingsJson.contains("usePowderCores")) OpenMagnetics::settings.set_use_powder_cores(settingsJson["usePowderCores"]);
286
+ if (has("verbose")) OpenMagnetics::settings.set_verbose(settingsJson["verbose"]);
287
+ if (has("useToroidalCores")) OpenMagnetics::settings.set_use_toroidal_cores(settingsJson["useToroidalCores"]);
288
+ if (has("useConcentricCores")) OpenMagnetics::settings.set_use_concentric_cores(settingsJson["useConcentricCores"]);
289
+ if (has("useOnlyCoresInStock")) OpenMagnetics::settings.set_use_only_cores_in_stock(settingsJson["useOnlyCoresInStock"]);
290
+ if (has("usePowderCores")) OpenMagnetics::settings.set_use_powder_cores(settingsJson["usePowderCores"]);
275
291
 
276
292
  // Inputs
277
- if (settingsJson.contains("inputsTrimHarmonics")) OpenMagnetics::settings.set_inputs_trim_harmonics(settingsJson["inputsTrimHarmonics"]);
278
- if (settingsJson.contains("inputsNumberPointsSampledWaveforms")) OpenMagnetics::settings.set_inputs_number_points_sampled_waveforms(settingsJson["inputsNumberPointsSampledWaveforms"]);
293
+ if (has("inputsTrimHarmonics")) OpenMagnetics::settings.set_inputs_trim_harmonics(settingsJson["inputsTrimHarmonics"]);
294
+ if (has("inputsNumberPointsSampledWaveforms")) OpenMagnetics::settings.set_inputs_number_points_sampled_waveforms(settingsJson["inputsNumberPointsSampledWaveforms"]);
279
295
 
280
296
  // Magnetizing inductance
281
- if (settingsJson.contains("magnetizingInductanceIncludeAirInductance")) OpenMagnetics::settings.set_magnetizing_inductance_include_air_inductance(settingsJson["magnetizingInductanceIncludeAirInductance"]);
297
+ if (has("magnetizingInductanceIncludeAirInductance")) OpenMagnetics::settings.set_magnetizing_inductance_include_air_inductance(settingsJson["magnetizingInductanceIncludeAirInductance"]);
282
298
 
283
299
  // Coil
284
- if (settingsJson.contains("coilAllowMarginTape")) OpenMagnetics::settings.set_coil_allow_margin_tape(settingsJson["coilAllowMarginTape"]);
285
- if (settingsJson.contains("coilAllowInsulatedWire")) OpenMagnetics::settings.set_coil_allow_insulated_wire(settingsJson["coilAllowInsulatedWire"]);
286
- if (settingsJson.contains("coilFillSectionsWithMarginTape")) OpenMagnetics::settings.set_coil_fill_sections_with_margin_tape(settingsJson["coilFillSectionsWithMarginTape"]);
287
- if (settingsJson.contains("coilWindEvenIfNotFit")) OpenMagnetics::settings.set_coil_wind_even_if_not_fit(settingsJson["coilWindEvenIfNotFit"]);
288
- if (settingsJson.contains("coilDelimitAndCompact")) OpenMagnetics::settings.set_coil_delimit_and_compact(settingsJson["coilDelimitAndCompact"]);
289
- if (settingsJson.contains("coilTryRewind")) OpenMagnetics::settings.set_coil_try_rewind(settingsJson["coilTryRewind"]);
290
- if (settingsJson.contains("coilIncludeAdditionalCoordinates")) OpenMagnetics::settings.set_coil_include_additional_coordinates(settingsJson["coilIncludeAdditionalCoordinates"]);
291
- if (settingsJson.contains("coilEqualizeMargins")) OpenMagnetics::settings.set_coil_equalize_margins(settingsJson["coilEqualizeMargins"]);
292
- if (settingsJson.contains("coilOnlyOneTurnPerLayerInContiguousRectangular")) OpenMagnetics::settings.set_coil_only_one_turn_per_layer_in_contiguous_rectangular(settingsJson["coilOnlyOneTurnPerLayerInContiguousRectangular"]);
293
- if (settingsJson.contains("coilMaximumLayersPlanar")) OpenMagnetics::settings.set_coil_maximum_layers_planar(settingsJson["coilMaximumLayersPlanar"]);
294
- if (settingsJson.contains("coilMesherInsideTurnsFactor")) OpenMagnetics::settings.set_coil_mesher_inside_turns_factor(settingsJson["coilMesherInsideTurnsFactor"]);
295
- if (settingsJson.contains("coilEnableUserWindingLossesModels")) OpenMagnetics::settings.set_coil_enable_user_winding_losses_models(settingsJson["coilEnableUserWindingLossesModels"]);
300
+ if (has("coilAllowMarginTape")) OpenMagnetics::settings.set_coil_allow_margin_tape(settingsJson["coilAllowMarginTape"]);
301
+ if (has("coilAllowInsulatedWire")) OpenMagnetics::settings.set_coil_allow_insulated_wire(settingsJson["coilAllowInsulatedWire"]);
302
+ if (has("coilFillSectionsWithMarginTape")) OpenMagnetics::settings.set_coil_fill_sections_with_margin_tape(settingsJson["coilFillSectionsWithMarginTape"]);
303
+ if (has("coilWindEvenIfNotFit")) OpenMagnetics::settings.set_coil_wind_even_if_not_fit(settingsJson["coilWindEvenIfNotFit"]);
304
+ if (has("coilDelimitAndCompact")) OpenMagnetics::settings.set_coil_delimit_and_compact(settingsJson["coilDelimitAndCompact"]);
305
+ if (has("coilTryRewind")) OpenMagnetics::settings.set_coil_try_rewind(settingsJson["coilTryRewind"]);
306
+ if (has("coilIncludeAdditionalCoordinates")) OpenMagnetics::settings.set_coil_include_additional_coordinates(settingsJson["coilIncludeAdditionalCoordinates"]);
307
+ if (has("coilEqualizeMargins")) OpenMagnetics::settings.set_coil_equalize_margins(settingsJson["coilEqualizeMargins"]);
308
+ if (has("coilOnlyOneTurnPerLayerInContiguousRectangular")) OpenMagnetics::settings.set_coil_only_one_turn_per_layer_in_contiguous_rectangular(settingsJson["coilOnlyOneTurnPerLayerInContiguousRectangular"]);
309
+ if (has("coilMaximumLayersPlanar")) OpenMagnetics::settings.set_coil_maximum_layers_planar(settingsJson["coilMaximumLayersPlanar"]);
310
+ if (has("coilMesherInsideTurnsFactor")) OpenMagnetics::settings.set_coil_mesher_inside_turns_factor(settingsJson["coilMesherInsideTurnsFactor"]);
311
+ if (has("coilEnableUserWindingLossesModels")) OpenMagnetics::settings.set_coil_enable_user_winding_losses_models(settingsJson["coilEnableUserWindingLossesModels"]);
296
312
 
297
313
  // Effective parameter standard
298
- if (settingsJson.contains("effectiveParameterStandard")) {
314
+ if (has("effectiveParameterStandard")) {
299
315
  EffectiveParameterStandard standard;
300
316
  from_json(settingsJson["effectiveParameterStandard"], standard);
301
317
  OpenMagnetics::settings.set_effective_parameter_standard(standard);
302
318
  }
303
- if (settingsJson.contains("nanocrystallineStackingFactor")) OpenMagnetics::settings.set_nanocrystalline_stacking_factor(settingsJson["nanocrystallineStackingFactor"]);
304
319
 
305
320
  // Painter
306
- if (settingsJson.contains("painterNumberPointsX")) OpenMagnetics::settings.set_painter_number_points_x(settingsJson["painterNumberPointsX"]);
307
- if (settingsJson.contains("painterNumberPointsY")) OpenMagnetics::settings.set_painter_number_points_y(settingsJson["painterNumberPointsY"]);
308
- if (settingsJson.contains("painterMode")) OpenMagnetics::settings.set_painter_mode(settingsJson["painterMode"]);
309
- if (settingsJson.contains("painterLogarithmicScale")) OpenMagnetics::settings.set_painter_logarithmic_scale(settingsJson["painterLogarithmicScale"]);
310
- if (settingsJson.contains("painterIncludeFringing")) OpenMagnetics::settings.set_painter_include_fringing(settingsJson["painterIncludeFringing"]);
311
- if (settingsJson.contains("painterDrawSpacer")) OpenMagnetics::settings.set_painter_draw_spacer(settingsJson["painterDrawSpacer"]);
312
- if (settingsJson.contains("painterSimpleLitz")) OpenMagnetics::settings.set_painter_simple_litz(settingsJson["painterSimpleLitz"]);
313
- if (settingsJson.contains("painterAdvancedLitz")) OpenMagnetics::settings.set_painter_advanced_litz(settingsJson["painterAdvancedLitz"]);
314
- if (settingsJson.contains("painterMaximumValueColorbar")) OpenMagnetics::settings.set_painter_maximum_value_colorbar(settingsJson["painterMaximumValueColorbar"]);
315
- if (settingsJson.contains("painterMinimumValueColorbar")) OpenMagnetics::settings.set_painter_minimum_value_colorbar(settingsJson["painterMinimumValueColorbar"]);
316
- if (settingsJson.contains("painterColorFerrite")) OpenMagnetics::settings.set_painter_color_ferrite(settingsJson["painterColorFerrite"]);
317
- if (settingsJson.contains("painterColorBobbin")) OpenMagnetics::settings.set_painter_color_bobbin(settingsJson["painterColorBobbin"]);
318
- if (settingsJson.contains("painterColorCopper")) OpenMagnetics::settings.set_painter_color_copper(settingsJson["painterColorCopper"]);
319
- if (settingsJson.contains("painterColorInsulation")) OpenMagnetics::settings.set_painter_color_insulation(settingsJson["painterColorInsulation"]);
320
- if (settingsJson.contains("painterColorFr4")) OpenMagnetics::settings.set_painter_color_fr4(settingsJson["painterColorFr4"]);
321
- if (settingsJson.contains("painterColorEnamel")) OpenMagnetics::settings.set_painter_color_enamel(settingsJson["painterColorEnamel"]);
322
- if (settingsJson.contains("painterColorFEP")) OpenMagnetics::settings.set_painter_color_fep(settingsJson["painterColorFEP"]);
323
- if (settingsJson.contains("painterColorETFE")) OpenMagnetics::settings.set_painter_color_etfe(settingsJson["painterColorETFE"]);
324
- if (settingsJson.contains("painterColorTCA")) OpenMagnetics::settings.set_painter_color_tca(settingsJson["painterColorTCA"]);
325
- if (settingsJson.contains("painterColorPFA")) OpenMagnetics::settings.set_painter_color_pfa(settingsJson["painterColorPFA"]);
326
- if (settingsJson.contains("painterColorSilk")) OpenMagnetics::settings.set_painter_color_silk(settingsJson["painterColorSilk"]);
327
- if (settingsJson.contains("painterColorMargin")) OpenMagnetics::settings.set_painter_color_margin(settingsJson["painterColorMargin"]);
328
- if (settingsJson.contains("painterColorSpacer")) OpenMagnetics::settings.set_painter_color_spacer(settingsJson["painterColorSpacer"]);
329
- if (settingsJson.contains("painterColorLines")) OpenMagnetics::settings.set_painter_color_lines(settingsJson["painterColorLines"]);
330
- if (settingsJson.contains("painterColorText")) OpenMagnetics::settings.set_painter_color_text(settingsJson["painterColorText"]);
331
- if (settingsJson.contains("painterColorCurrentDensity")) OpenMagnetics::settings.set_painter_color_current_density(settingsJson["painterColorCurrentDensity"]);
332
- if (settingsJson.contains("painterColorMagneticFieldMinimum")) OpenMagnetics::settings.set_painter_color_magnetic_field_minimum(settingsJson["painterColorMagneticFieldMinimum"]);
333
- if (settingsJson.contains("painterColorMagneticFieldMaximum")) OpenMagnetics::settings.set_painter_color_magnetic_field_maximum(settingsJson["painterColorMagneticFieldMaximum"]);
321
+ if (has("painterNumberPointsX")) OpenMagnetics::settings.set_painter_number_points_x(settingsJson["painterNumberPointsX"]);
322
+ if (has("painterNumberPointsY")) OpenMagnetics::settings.set_painter_number_points_y(settingsJson["painterNumberPointsY"]);
323
+ if (has("painterMode")) OpenMagnetics::settings.set_painter_mode(settingsJson["painterMode"]);
324
+ if (has("painterLogarithmicScale")) OpenMagnetics::settings.set_painter_logarithmic_scale(settingsJson["painterLogarithmicScale"]);
325
+ if (has("painterIncludeFringing")) OpenMagnetics::settings.set_painter_include_fringing(settingsJson["painterIncludeFringing"]);
326
+ if (has("painterDrawSpacer")) OpenMagnetics::settings.set_painter_draw_spacer(settingsJson["painterDrawSpacer"]);
327
+ if (has("painterSimpleLitz")) OpenMagnetics::settings.set_painter_simple_litz(settingsJson["painterSimpleLitz"]);
328
+ if (has("painterAdvancedLitz")) OpenMagnetics::settings.set_painter_advanced_litz(settingsJson["painterAdvancedLitz"]);
329
+ if (has("painterMaximumValueColorbar")) OpenMagnetics::settings.set_painter_maximum_value_colorbar(settingsJson["painterMaximumValueColorbar"]);
330
+ if (has("painterMinimumValueColorbar")) OpenMagnetics::settings.set_painter_minimum_value_colorbar(settingsJson["painterMinimumValueColorbar"]);
331
+ if (has("painterColorFerrite")) OpenMagnetics::settings.set_painter_color_ferrite(settingsJson["painterColorFerrite"]);
332
+ if (has("painterColorBobbin")) OpenMagnetics::settings.set_painter_color_bobbin(settingsJson["painterColorBobbin"]);
333
+ if (has("painterColorCopper")) OpenMagnetics::settings.set_painter_color_copper(settingsJson["painterColorCopper"]);
334
+ if (has("painterColorInsulation")) OpenMagnetics::settings.set_painter_color_insulation(settingsJson["painterColorInsulation"]);
335
+ if (has("painterColorFr4")) OpenMagnetics::settings.set_painter_color_fr4(settingsJson["painterColorFr4"]);
336
+ if (has("painterColorEnamel")) OpenMagnetics::settings.set_painter_color_enamel(settingsJson["painterColorEnamel"]);
337
+ if (has("painterColorFEP")) OpenMagnetics::settings.set_painter_color_fep(settingsJson["painterColorFEP"]);
338
+ if (has("painterColorETFE")) OpenMagnetics::settings.set_painter_color_etfe(settingsJson["painterColorETFE"]);
339
+ if (has("painterColorTCA")) OpenMagnetics::settings.set_painter_color_tca(settingsJson["painterColorTCA"]);
340
+ if (has("painterColorPFA")) OpenMagnetics::settings.set_painter_color_pfa(settingsJson["painterColorPFA"]);
341
+ if (has("painterColorSilk")) OpenMagnetics::settings.set_painter_color_silk(settingsJson["painterColorSilk"]);
342
+ if (has("painterColorMargin")) OpenMagnetics::settings.set_painter_color_margin(settingsJson["painterColorMargin"]);
343
+ if (has("painterColorSpacer")) OpenMagnetics::settings.set_painter_color_spacer(settingsJson["painterColorSpacer"]);
344
+ if (has("painterColorLines")) OpenMagnetics::settings.set_painter_color_lines(settingsJson["painterColorLines"]);
345
+ if (has("painterColorText")) OpenMagnetics::settings.set_painter_color_text(settingsJson["painterColorText"]);
346
+ if (has("painterColorCurrentDensity")) OpenMagnetics::settings.set_painter_color_current_density(settingsJson["painterColorCurrentDensity"]);
347
+ if (has("painterColorMagneticFieldMinimum")) OpenMagnetics::settings.set_painter_color_magnetic_field_minimum(settingsJson["painterColorMagneticFieldMinimum"]);
348
+ if (has("painterColorMagneticFieldMaximum")) OpenMagnetics::settings.set_painter_color_magnetic_field_maximum(settingsJson["painterColorMagneticFieldMaximum"]);
334
349
  // null means "not set" (see get_settings above), so set_settings(get_settings()) round-trips
335
350
  // instead of trying to assign a null to a std::string.
336
- if (settingsJson.contains("painterCciCoordinatesPath") && !settingsJson["painterCciCoordinatesPath"].is_null())
351
+ if (has("painterCciCoordinatesPath") && !settingsJson["painterCciCoordinatesPath"].is_null())
337
352
  OpenMagnetics::settings.set_painter_cci_coordinates_path(settingsJson["painterCciCoordinatesPath"]);
338
- if (settingsJson.contains("painterMirroringDimension")) OpenMagnetics::settings.set_painter_mirroring_dimension(settingsJson["painterMirroringDimension"]);
339
- if (settingsJson.contains("painterMagneticFieldStrengthModel")) {
353
+ if (has("painterMirroringDimension")) OpenMagnetics::settings.set_painter_mirroring_dimension(settingsJson["painterMirroringDimension"]);
354
+ if (has("painterMagneticFieldStrengthModel")) {
340
355
  OpenMagnetics::MagneticFieldStrengthModels model;
341
356
  from_json(settingsJson["painterMagneticFieldStrengthModel"], model);
342
357
  OpenMagnetics::settings.set_painter_magnetic_field_strength_model(model);
343
358
  }
344
359
 
345
360
  // Magnetic field
346
- if (settingsJson.contains("magneticFieldNumberPointsX")) OpenMagnetics::settings.set_magnetic_field_number_points_x(settingsJson["magneticFieldNumberPointsX"]);
347
- if (settingsJson.contains("magneticFieldNumberPointsY")) OpenMagnetics::settings.set_magnetic_field_number_points_y(settingsJson["magneticFieldNumberPointsY"]);
348
- if (settingsJson.contains("magneticFieldIncludeFringing")) OpenMagnetics::settings.set_magnetic_field_include_fringing(settingsJson["magneticFieldIncludeFringing"]);
349
- if (settingsJson.contains("magneticFieldMirroringDimension")) OpenMagnetics::settings.set_magnetic_field_mirroring_dimension(settingsJson["magneticFieldMirroringDimension"]);
361
+ if (has("magneticFieldNumberPointsX")) OpenMagnetics::settings.set_magnetic_field_number_points_x(settingsJson["magneticFieldNumberPointsX"]);
362
+ if (has("magneticFieldNumberPointsY")) OpenMagnetics::settings.set_magnetic_field_number_points_y(settingsJson["magneticFieldNumberPointsY"]);
363
+ if (has("magneticFieldIncludeFringing")) OpenMagnetics::settings.set_magnetic_field_include_fringing(settingsJson["magneticFieldIncludeFringing"]);
364
+ if (has("magneticFieldMirroringDimension")) OpenMagnetics::settings.set_magnetic_field_mirroring_dimension(settingsJson["magneticFieldMirroringDimension"]);
350
365
 
351
366
  // Leakage inductance
352
- if (settingsJson.contains("leakageInductanceGridAutoScaling")) OpenMagnetics::settings.set_leakage_inductance_grid_auto_scaling(settingsJson["leakageInductanceGridAutoScaling"]);
353
- if (settingsJson.contains("leakageInductanceGridPrecisionLevelPlanar")) OpenMagnetics::settings.set_leakage_inductance_grid_precision_level_planar(settingsJson["leakageInductanceGridPrecisionLevelPlanar"]);
354
- if (settingsJson.contains("leakageInductanceGridPrecisionLevelWound")) OpenMagnetics::settings.set_leakage_inductance_grid_precision_level_wound(settingsJson["leakageInductanceGridPrecisionLevelWound"]);
367
+ if (has("leakageInductanceGridAutoScaling")) OpenMagnetics::settings.set_leakage_inductance_grid_auto_scaling(settingsJson["leakageInductanceGridAutoScaling"]);
368
+ if (has("leakageInductanceGridPrecisionLevelPlanar")) OpenMagnetics::settings.set_leakage_inductance_grid_precision_level_planar(settingsJson["leakageInductanceGridPrecisionLevelPlanar"]);
369
+ if (has("leakageInductanceGridPrecisionLevelWound")) OpenMagnetics::settings.set_leakage_inductance_grid_precision_level_wound(settingsJson["leakageInductanceGridPrecisionLevelWound"]);
355
370
 
356
371
  // Adviser
357
- if (settingsJson.contains("coilAdviserMaximumNumberWires")) OpenMagnetics::settings.set_coil_adviser_maximum_number_wires(settingsJson["coilAdviserMaximumNumberWires"]);
358
- if (settingsJson.contains("coreAdviserIncludeStacks")) OpenMagnetics::settings.set_core_adviser_include_stacks(settingsJson["coreAdviserIncludeStacks"]);
359
- if (settingsJson.contains("coreAdviserIncludeDistributedGaps")) OpenMagnetics::settings.set_core_adviser_include_distributed_gaps(settingsJson["coreAdviserIncludeDistributedGaps"]);
360
- if (settingsJson.contains("coreAdviserIncludeMargin")) OpenMagnetics::settings.set_core_adviser_include_margin(settingsJson["coreAdviserIncludeMargin"]);
361
- if (settingsJson.contains("coreAdviserEnableIntermediatePruning")) OpenMagnetics::settings.set_core_adviser_enable_intermediate_pruning(settingsJson["coreAdviserEnableIntermediatePruning"]);
362
- if (settingsJson.contains("coreAdviserMaximumMagneticsAfterFiltering")) OpenMagnetics::settings.set_core_adviser_maximum_magnetics_after_filtering(settingsJson["coreAdviserMaximumMagneticsAfterFiltering"]);
363
- if (settingsJson.contains("coreAdviserEnableTemperatureFilter")) OpenMagnetics::settings.set_core_adviser_enable_temperature_filter(settingsJson["coreAdviserEnableTemperatureFilter"]);
364
- if (settingsJson.contains("coreAdviserMaximumTemperature")) OpenMagnetics::settings.set_core_adviser_maximum_temperature(settingsJson["coreAdviserMaximumTemperature"]);
365
- if (settingsJson.contains("coreAdviserSaturationMargin")) OpenMagnetics::settings.set_core_adviser_saturation_margin(settingsJson["coreAdviserSaturationMargin"]);
366
- if (settingsJson.contains("gappingStrategy")) {
372
+ if (has("coilAdviserMaximumNumberWires")) OpenMagnetics::settings.set_coil_adviser_maximum_number_wires(settingsJson["coilAdviserMaximumNumberWires"]);
373
+ if (has("coreAdviserIncludeStacks")) OpenMagnetics::settings.set_core_adviser_include_stacks(settingsJson["coreAdviserIncludeStacks"]);
374
+ if (has("coreAdviserIncludeDistributedGaps")) OpenMagnetics::settings.set_core_adviser_include_distributed_gaps(settingsJson["coreAdviserIncludeDistributedGaps"]);
375
+ if (has("coreAdviserIncludeMargin")) OpenMagnetics::settings.set_core_adviser_include_margin(settingsJson["coreAdviserIncludeMargin"]);
376
+ if (has("coreAdviserEnableIntermediatePruning")) OpenMagnetics::settings.set_core_adviser_enable_intermediate_pruning(settingsJson["coreAdviserEnableIntermediatePruning"]);
377
+ if (has("coreAdviserMaximumMagneticsAfterFiltering")) OpenMagnetics::settings.set_core_adviser_maximum_magnetics_after_filtering(settingsJson["coreAdviserMaximumMagneticsAfterFiltering"]);
378
+ if (has("coreAdviserEnableTemperatureFilter")) OpenMagnetics::settings.set_core_adviser_enable_temperature_filter(settingsJson["coreAdviserEnableTemperatureFilter"]);
379
+ if (has("coreAdviserMaximumTemperature")) OpenMagnetics::settings.set_core_adviser_maximum_temperature(settingsJson["coreAdviserMaximumTemperature"]);
380
+ if (has("coreAdviserSaturationMargin")) OpenMagnetics::settings.set_core_adviser_saturation_margin(settingsJson["coreAdviserSaturationMargin"]);
381
+ if (has("gappingStrategy")) {
367
382
  GappingOptimizationStrategy strategy;
368
383
  from_json(settingsJson["gappingStrategy"], strategy);
369
384
  OpenMagnetics::settings.set_gapping_strategy(strategy);
370
385
  }
371
386
 
372
387
  // Wire adviser
373
- if (settingsJson.contains("wireAdviserIncludePlanar")) OpenMagnetics::settings.set_wire_adviser_include_planar(settingsJson["wireAdviserIncludePlanar"]);
374
- if (settingsJson.contains("wireAdviserIncludeFoil")) OpenMagnetics::settings.set_wire_adviser_include_foil(settingsJson["wireAdviserIncludeFoil"]);
375
- if (settingsJson.contains("wireAdviserIncludeRectangular")) OpenMagnetics::settings.set_wire_adviser_include_rectangular(settingsJson["wireAdviserIncludeRectangular"]);
376
- if (settingsJson.contains("wireAdviserIncludeLitz")) OpenMagnetics::settings.set_wire_adviser_include_litz(settingsJson["wireAdviserIncludeLitz"]);
377
- if (settingsJson.contains("wireAdviserIncludeRound")) OpenMagnetics::settings.set_wire_adviser_include_round(settingsJson["wireAdviserIncludeRound"]);
378
- if (settingsJson.contains("wireAdviserAllowRectangularInToroidalCores")) OpenMagnetics::settings.set_wire_adviser_allow_rectangular_in_toroidal_cores(settingsJson["wireAdviserAllowRectangularInToroidalCores"]);
388
+ if (has("wireAdviserIncludePlanar")) OpenMagnetics::settings.set_wire_adviser_include_planar(settingsJson["wireAdviserIncludePlanar"]);
389
+ if (has("wireAdviserIncludeFoil")) OpenMagnetics::settings.set_wire_adviser_include_foil(settingsJson["wireAdviserIncludeFoil"]);
390
+ if (has("wireAdviserIncludeRectangular")) OpenMagnetics::settings.set_wire_adviser_include_rectangular(settingsJson["wireAdviserIncludeRectangular"]);
391
+ if (has("wireAdviserIncludeLitz")) OpenMagnetics::settings.set_wire_adviser_include_litz(settingsJson["wireAdviserIncludeLitz"]);
392
+ if (has("wireAdviserIncludeRound")) OpenMagnetics::settings.set_wire_adviser_include_round(settingsJson["wireAdviserIncludeRound"]);
393
+ if (has("wireAdviserAllowRectangularInToroidalCores")) OpenMagnetics::settings.set_wire_adviser_allow_rectangular_in_toroidal_cores(settingsJson["wireAdviserAllowRectangularInToroidalCores"]);
379
394
 
380
395
  // Harmonics
381
- if (settingsJson.contains("harmonicAmplitudeThresholdQuickMode")) OpenMagnetics::settings.set_harmonic_amplitude_threshold_quick_mode(settingsJson["harmonicAmplitudeThresholdQuickMode"]);
382
- if (settingsJson.contains("harmonicAmplitudeThreshold")) OpenMagnetics::settings.set_harmonic_amplitude_threshold(settingsJson["harmonicAmplitudeThreshold"]);
383
- if (settingsJson.contains("magneticFieldTurnSumsCacheBytes")) OpenMagnetics::settings.set_magnetic_field_turn_sums_cache_bytes(settingsJson["magneticFieldTurnSumsCacheBytes"].get<size_t>());
396
+ if (has("harmonicAmplitudeThresholdQuickMode")) OpenMagnetics::settings.set_harmonic_amplitude_threshold_quick_mode(settingsJson["harmonicAmplitudeThresholdQuickMode"]);
397
+ if (has("harmonicAmplitudeThreshold")) OpenMagnetics::settings.set_harmonic_amplitude_threshold(settingsJson["harmonicAmplitudeThreshold"]);
398
+ if (has("magneticFieldTurnSumsCacheBytes")) OpenMagnetics::settings.set_magnetic_field_turn_sums_cache_bytes(settingsJson["magneticFieldTurnSumsCacheBytes"].get<size_t>());
384
399
 
385
400
  // Thermal network
386
- if (settingsJson.contains("thermalNetworkStrictGeometry")) OpenMagnetics::settings.set_thermal_network_strict_geometry(settingsJson["thermalNetworkStrictGeometry"]);
401
+ if (has("thermalNetworkStrictGeometry")) OpenMagnetics::settings.set_thermal_network_strict_geometry(settingsJson["thermalNetworkStrictGeometry"]);
387
402
 
388
403
  // Models
389
- if (settingsJson.contains("magneticFieldStrengthModel")) {
404
+ if (has("magneticFieldStrengthModel")) {
390
405
  OpenMagnetics::MagneticFieldStrengthModels model;
391
406
  from_json(settingsJson["magneticFieldStrengthModel"], model);
392
407
  OpenMagnetics::settings.set_magnetic_field_strength_model(model);
393
408
  }
394
- if (settingsJson.contains("magneticFieldStrengthFringingEffectModel")) {
409
+ if (has("magneticFieldStrengthFringingEffectModel")) {
395
410
  OpenMagnetics::MagneticFieldStrengthFringingEffectModels model;
396
411
  from_json(settingsJson["magneticFieldStrengthFringingEffectModel"], model);
397
412
  OpenMagnetics::settings.set_magnetic_field_strength_fringing_effect_model(model);
398
413
  }
399
- if (settingsJson.contains("leakageInductanceMagneticFieldStrengthModel")) {
414
+ if (has("leakageInductanceMagneticFieldStrengthModel")) {
400
415
  OpenMagnetics::MagneticFieldStrengthModels model;
401
416
  from_json(settingsJson["leakageInductanceMagneticFieldStrengthModel"], model);
402
417
  OpenMagnetics::settings.set_leakage_inductance_magnetic_field_strength_model(model);
403
418
  }
404
- if (settingsJson.contains("reluctanceModel")) {
419
+ if (has("reluctanceModel")) {
405
420
  OpenMagnetics::ReluctanceModels model;
406
421
  from_json(settingsJson["reluctanceModel"], model);
407
422
  OpenMagnetics::settings.set_reluctance_model(model);
408
423
  }
409
- if (settingsJson.contains("coreTemperatureModel")) {
424
+ if (has("coreTemperatureModel")) {
410
425
  OpenMagnetics::CoreTemperatureModels model;
411
426
  from_json(settingsJson["coreTemperatureModel"], model);
412
427
  OpenMagnetics::settings.set_core_temperature_model(model);
413
428
  }
414
- if (settingsJson.contains("coreThermalResistanceModel")) {
429
+ if (has("coreThermalResistanceModel")) {
415
430
  OpenMagnetics::CoreThermalResistanceModels model;
416
431
  from_json(settingsJson["coreThermalResistanceModel"], model);
417
432
  OpenMagnetics::settings.set_core_thermal_resistance_model(model);
418
433
  }
419
- if (settingsJson.contains("windingSkinEffectLossesModel")) {
434
+ if (has("windingSkinEffectLossesModel")) {
420
435
  OpenMagnetics::WindingSkinEffectLossesModels model;
421
436
  from_json(settingsJson["windingSkinEffectLossesModel"], model);
422
437
  OpenMagnetics::settings.set_winding_skin_effect_losses_model(model);
423
438
  }
424
- if (settingsJson.contains("windingProximityEffectLossesModel")) {
439
+ if (has("windingProximityEffectLossesModel")) {
425
440
  OpenMagnetics::WindingProximityEffectLossesModels model;
426
441
  from_json(settingsJson["windingProximityEffectLossesModel"], model);
427
442
  OpenMagnetics::settings.set_winding_proximity_effect_losses_model(model);
428
443
  }
429
- if (settingsJson.contains("strayCapacitanceModel")) {
444
+ if (has("strayCapacitanceModel")) {
430
445
  OpenMagnetics::StrayCapacitanceModels model;
431
446
  from_json(settingsJson["strayCapacitanceModel"], model);
432
447
  OpenMagnetics::settings.set_stray_capacitance_model(model);
433
448
  }
434
- if (settingsJson.contains("electricFieldOutputUnit")) {
449
+ if (has("electricFieldOutputUnit")) {
435
450
  OpenMagnetics::ElectricFieldOutputUnit unit;
436
451
  from_json(settingsJson["electricFieldOutputUnit"], unit);
437
452
  OpenMagnetics::settings.set_electric_field_output_unit(unit);
438
453
  }
439
454
 
440
455
  // Core losses model
441
- if (settingsJson.contains("coreLossesPreferredModelName")) {
456
+ if (has("coreLossesPreferredModelName")) {
442
457
  OpenMagnetics::CoreLossesModels model;
443
458
  from_json(settingsJson["coreLossesPreferredModelName"], model);
444
459
  OpenMagnetics::settings.set_core_losses_preferred_model_name(model);
445
460
  }
446
461
 
447
462
  // Circuit simulator
448
- if (settingsJson.contains("circuitSimulatorCurveFittingMode")) OpenMagnetics::settings.set_circuit_simulator_curve_fitting_mode(settingsJson["circuitSimulatorCurveFittingMode"]);
449
- if (settingsJson.contains("circuitSimulatorFracpoleOptions")) OpenMagnetics::settings.set_circuit_simulator_fracpole_options(settingsJson["circuitSimulatorFracpoleOptions"].get<std::map<std::string, double>>());
450
- if (settingsJson.contains("circuitSimulatorIncludeSaturation")) OpenMagnetics::settings.set_circuit_simulator_include_saturation(settingsJson["circuitSimulatorIncludeSaturation"]);
451
- if (settingsJson.contains("circuitSimulatorIncludeMutualResistance")) OpenMagnetics::settings.set_circuit_simulator_include_mutual_resistance(settingsJson["circuitSimulatorIncludeMutualResistance"]);
452
- if (settingsJson.contains("circuitSimulatorCoreLossTopology")) OpenMagnetics::settings.set_circuit_simulator_core_loss_topology(settingsJson["circuitSimulatorCoreLossTopology"]);
463
+ if (has("circuitSimulatorCurveFittingMode")) OpenMagnetics::settings.set_circuit_simulator_curve_fitting_mode(settingsJson["circuitSimulatorCurveFittingMode"]);
464
+ if (has("circuitSimulatorFracpoleOptions")) OpenMagnetics::settings.set_circuit_simulator_fracpole_options(settingsJson["circuitSimulatorFracpoleOptions"].get<std::map<std::string, double>>());
465
+ if (has("circuitSimulatorIncludeSaturation")) OpenMagnetics::settings.set_circuit_simulator_include_saturation(settingsJson["circuitSimulatorIncludeSaturation"]);
466
+ if (has("circuitSimulatorIncludeMutualResistance")) OpenMagnetics::settings.set_circuit_simulator_include_mutual_resistance(settingsJson["circuitSimulatorIncludeMutualResistance"]);
467
+ if (has("circuitSimulatorCoreLossTopology")) OpenMagnetics::settings.set_circuit_simulator_core_loss_topology(settingsJson["circuitSimulatorCoreLossTopology"]);
453
468
 
454
469
  // Preferred manufacturers
455
- if (settingsJson.contains("preferredCoreMaterialFerriteManufacturer")) OpenMagnetics::settings.set_preferred_core_material_ferrite_manufacturer(settingsJson["preferredCoreMaterialFerriteManufacturer"]);
456
- if (settingsJson.contains("preferredCoreMaterialPowderManufacturer")) OpenMagnetics::settings.set_preferred_core_material_powder_manufacturer(settingsJson["preferredCoreMaterialPowderManufacturer"]);
457
- if (settingsJson.contains("coreCrossReferencerAllowDifferentCoreMaterialType")) OpenMagnetics::settings.set_core_cross_referencer_allow_different_core_material_type(settingsJson["coreCrossReferencerAllowDifferentCoreMaterialType"]);
470
+ if (has("preferredCoreMaterialFerriteManufacturer")) OpenMagnetics::settings.set_preferred_core_material_ferrite_manufacturer(settingsJson["preferredCoreMaterialFerriteManufacturer"]);
471
+ if (has("preferredCoreMaterialPowderManufacturer")) OpenMagnetics::settings.set_preferred_core_material_powder_manufacturer(settingsJson["preferredCoreMaterialPowderManufacturer"]);
472
+ if (has("coreCrossReferencerAllowDifferentCoreMaterialType")) OpenMagnetics::settings.set_core_cross_referencer_allow_different_core_material_type(settingsJson["coreCrossReferencerAllowDifferentCoreMaterialType"]);
473
+ }
474
+
475
+ void set_settings(json settingsJson) {
476
+ // ABT #2265: an unknown key used to be dropped silently, so a misspelt or not-yet-exposed setting
477
+ // (e.g. allowMaterialDataExtrapolation before it was added here) looked set and did nothing. The known keys come
478
+ // from apply_settings itself, so this list cannot drift from what is applied.
479
+ static const std::set<std::string> knownKeys = [] {
480
+ std::set<std::string> keys;
481
+ apply_settings(json::object(), &keys);
482
+ return keys;
483
+ }();
484
+ if (!settingsJson.is_object()) {
485
+ throw std::invalid_argument("set_settings expects a JSON object of settings, got: " + settingsJson.dump());
486
+ }
487
+ std::vector<std::string> unknown;
488
+ for (const auto& item : settingsJson.items()) {
489
+ if (!knownKeys.count(item.key())) unknown.push_back(item.key());
490
+ }
491
+ if (!unknown.empty()) {
492
+ std::string list;
493
+ for (const auto& key : unknown) list += (list.empty() ? "" : ", ") + key;
494
+ throw std::invalid_argument("set_settings: unknown setting(s): " + list + ". Nothing was applied; get_settings() lists the known keys.");
495
+ }
496
+ apply_settings(settingsJson, nullptr);
458
497
  }
459
498
 
460
499
  void reset_settings() {