PyOpenMagnetics 1.7.39__tar.gz → 1.7.41__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.39 → pyopenmagnetics-1.7.41}/PKG-INFO +1 -1
  2. {pyopenmagnetics-1.7.39 → pyopenmagnetics-1.7.41}/pyproject.toml +1 -1
  3. {pyopenmagnetics-1.7.39 → pyopenmagnetics-1.7.41}/src/losses.cpp +24 -0
  4. {pyopenmagnetics-1.7.39 → pyopenmagnetics-1.7.41}/src/settings.cpp +140 -101
  5. {pyopenmagnetics-1.7.39 → pyopenmagnetics-1.7.41}/.github/workflows/ci.yml +0 -0
  6. {pyopenmagnetics-1.7.39 → pyopenmagnetics-1.7.41}/.github/workflows/publish.yml +0 -0
  7. {pyopenmagnetics-1.7.39 → pyopenmagnetics-1.7.41}/.gitignore +0 -0
  8. {pyopenmagnetics-1.7.39 → pyopenmagnetics-1.7.41}/AGENTS.md +0 -0
  9. {pyopenmagnetics-1.7.39 → pyopenmagnetics-1.7.41}/CMakeLists.txt +0 -0
  10. {pyopenmagnetics-1.7.39 → pyopenmagnetics-1.7.41}/LICENSE +0 -0
  11. {pyopenmagnetics-1.7.39 → pyopenmagnetics-1.7.41}/PyOpenMagnetics.pyi +0 -0
  12. {pyopenmagnetics-1.7.39 → pyopenmagnetics-1.7.41}/README.md +0 -0
  13. {pyopenmagnetics-1.7.39 → pyopenmagnetics-1.7.41}/_packaging/__init__.py +0 -0
  14. {pyopenmagnetics-1.7.39 → pyopenmagnetics-1.7.41}/_packaging/_build_info.py.in +0 -0
  15. {pyopenmagnetics-1.7.39 → pyopenmagnetics-1.7.41}/api/MAS.py +0 -0
  16. {pyopenmagnetics-1.7.39 → pyopenmagnetics-1.7.41}/api/mas_db_reader.py +0 -0
  17. {pyopenmagnetics-1.7.39 → pyopenmagnetics-1.7.41}/api/validation.py +0 -0
  18. {pyopenmagnetics-1.7.39 → pyopenmagnetics-1.7.41}/clear_cibuildwheel_cache.sh +0 -0
  19. {pyopenmagnetics-1.7.39 → pyopenmagnetics-1.7.41}/docs/compatibility.md +0 -0
  20. {pyopenmagnetics-1.7.39 → pyopenmagnetics-1.7.41}/docs/errors.md +0 -0
  21. {pyopenmagnetics-1.7.39 → pyopenmagnetics-1.7.41}/docs/performance.md +0 -0
  22. {pyopenmagnetics-1.7.39 → pyopenmagnetics-1.7.41}/examples/README.md +0 -0
  23. {pyopenmagnetics-1.7.39 → pyopenmagnetics-1.7.41}/examples/buck_inductor.py +0 -0
  24. {pyopenmagnetics-1.7.39 → pyopenmagnetics-1.7.41}/examples/complete_simulation_example.py +0 -0
  25. {pyopenmagnetics-1.7.39 → pyopenmagnetics-1.7.41}/examples/converter_design_example.py +0 -0
  26. {pyopenmagnetics-1.7.39 → pyopenmagnetics-1.7.41}/examples/debug_bobbin.py +0 -0
  27. {pyopenmagnetics-1.7.39 → pyopenmagnetics-1.7.41}/examples/debug_coil.py +0 -0
  28. {pyopenmagnetics-1.7.39 → pyopenmagnetics-1.7.41}/examples/debug_core.py +0 -0
  29. {pyopenmagnetics-1.7.39 → pyopenmagnetics-1.7.41}/examples/debug_plotting.py +0 -0
  30. {pyopenmagnetics-1.7.39 → pyopenmagnetics-1.7.41}/examples/flyback_220v_12v_1a.py +0 -0
  31. {pyopenmagnetics-1.7.39 → pyopenmagnetics-1.7.41}/examples/flyback_220v_12v_2a_complete.py +0 -0
  32. {pyopenmagnetics-1.7.39 → pyopenmagnetics-1.7.41}/examples/flyback_bh_curve.png +0 -0
  33. {pyopenmagnetics-1.7.39 → pyopenmagnetics-1.7.41}/examples/flyback_core.png +0 -0
  34. {pyopenmagnetics-1.7.39 → pyopenmagnetics-1.7.41}/examples/flyback_design.py +0 -0
  35. {pyopenmagnetics-1.7.39 → pyopenmagnetics-1.7.41}/examples/flyback_summary.png +0 -0
  36. {pyopenmagnetics-1.7.39 → pyopenmagnetics-1.7.41}/examples/flyback_waveforms.png +0 -0
  37. {pyopenmagnetics-1.7.39 → pyopenmagnetics-1.7.41}/examples/list_plot_funcs.py +0 -0
  38. {pyopenmagnetics-1.7.39 → pyopenmagnetics-1.7.41}/examples/plot_flyback_design.py +0 -0
  39. {pyopenmagnetics-1.7.39 → pyopenmagnetics-1.7.41}/examples/plot_flyback_pyom.py +0 -0
  40. {pyopenmagnetics-1.7.39 → pyopenmagnetics-1.7.41}/examples/test_field_calc.py +0 -0
  41. {pyopenmagnetics-1.7.39 → pyopenmagnetics-1.7.41}/examples/test_field_plot.py +0 -0
  42. {pyopenmagnetics-1.7.39 → pyopenmagnetics-1.7.41}/force_fresh_build.sh +0 -0
  43. {pyopenmagnetics-1.7.39 → pyopenmagnetics-1.7.41}/llms.txt +0 -0
  44. {pyopenmagnetics-1.7.39 → pyopenmagnetics-1.7.41}/notebooks/01_getting_started.ipynb +0 -0
  45. {pyopenmagnetics-1.7.39 → pyopenmagnetics-1.7.41}/notebooks/02_buck_inductor.ipynb +0 -0
  46. {pyopenmagnetics-1.7.39 → pyopenmagnetics-1.7.41}/notebooks/03_core_losses.ipynb +0 -0
  47. {pyopenmagnetics-1.7.39 → pyopenmagnetics-1.7.41}/notebooks/README.md +0 -0
  48. {pyopenmagnetics-1.7.39 → pyopenmagnetics-1.7.41}/requirements.txt +0 -0
  49. {pyopenmagnetics-1.7.39 → pyopenmagnetics-1.7.41}/src/advisers.cpp +0 -0
  50. {pyopenmagnetics-1.7.39 → pyopenmagnetics-1.7.41}/src/advisers.h +0 -0
  51. {pyopenmagnetics-1.7.39 → pyopenmagnetics-1.7.41}/src/bobbin.cpp +0 -0
  52. {pyopenmagnetics-1.7.39 → pyopenmagnetics-1.7.41}/src/bobbin.h +0 -0
  53. {pyopenmagnetics-1.7.39 → pyopenmagnetics-1.7.41}/src/cmc.cpp +0 -0
  54. {pyopenmagnetics-1.7.39 → pyopenmagnetics-1.7.41}/src/cmc.h +0 -0
  55. {pyopenmagnetics-1.7.39 → pyopenmagnetics-1.7.41}/src/common.h +0 -0
  56. {pyopenmagnetics-1.7.39 → pyopenmagnetics-1.7.41}/src/converter.cpp +0 -0
  57. {pyopenmagnetics-1.7.39 → pyopenmagnetics-1.7.41}/src/converter.h +0 -0
  58. {pyopenmagnetics-1.7.39 → pyopenmagnetics-1.7.41}/src/core.cpp +0 -0
  59. {pyopenmagnetics-1.7.39 → pyopenmagnetics-1.7.41}/src/core.h +0 -0
  60. {pyopenmagnetics-1.7.39 → pyopenmagnetics-1.7.41}/src/crossref.cpp +0 -0
  61. {pyopenmagnetics-1.7.39 → pyopenmagnetics-1.7.41}/src/crossref.h +0 -0
  62. {pyopenmagnetics-1.7.39 → pyopenmagnetics-1.7.41}/src/database.cpp +0 -0
  63. {pyopenmagnetics-1.7.39 → pyopenmagnetics-1.7.41}/src/database.h +0 -0
  64. {pyopenmagnetics-1.7.39 → pyopenmagnetics-1.7.41}/src/logging.cpp +0 -0
  65. {pyopenmagnetics-1.7.39 → pyopenmagnetics-1.7.41}/src/logging.h +0 -0
  66. {pyopenmagnetics-1.7.39 → pyopenmagnetics-1.7.41}/src/losses.h +0 -0
  67. {pyopenmagnetics-1.7.39 → pyopenmagnetics-1.7.41}/src/module.cpp +0 -0
  68. {pyopenmagnetics-1.7.39 → pyopenmagnetics-1.7.41}/src/plotting.cpp +0 -0
  69. {pyopenmagnetics-1.7.39 → pyopenmagnetics-1.7.41}/src/plotting.h +0 -0
  70. {pyopenmagnetics-1.7.39 → pyopenmagnetics-1.7.41}/src/settings.h +0 -0
  71. {pyopenmagnetics-1.7.39 → pyopenmagnetics-1.7.41}/src/simulation.cpp +0 -0
  72. {pyopenmagnetics-1.7.39 → pyopenmagnetics-1.7.41}/src/simulation.h +0 -0
  73. {pyopenmagnetics-1.7.39 → pyopenmagnetics-1.7.41}/src/utils.cpp +0 -0
  74. {pyopenmagnetics-1.7.39 → pyopenmagnetics-1.7.41}/src/utils.h +0 -0
  75. {pyopenmagnetics-1.7.39 → pyopenmagnetics-1.7.41}/src/winding.cpp +0 -0
  76. {pyopenmagnetics-1.7.39 → pyopenmagnetics-1.7.41}/src/winding.h +0 -0
  77. {pyopenmagnetics-1.7.39 → pyopenmagnetics-1.7.41}/src/wire.cpp +0 -0
  78. {pyopenmagnetics-1.7.39 → pyopenmagnetics-1.7.41}/src/wire.h +0 -0
  79. {pyopenmagnetics-1.7.39 → pyopenmagnetics-1.7.41}/test.py +0 -0
  80. {pyopenmagnetics-1.7.39 → pyopenmagnetics-1.7.41}/tests/__init__.py +0 -0
  81. {pyopenmagnetics-1.7.39 → pyopenmagnetics-1.7.41}/tests/conftest.py +0 -0
  82. {pyopenmagnetics-1.7.39 → pyopenmagnetics-1.7.41}/tests/test_converter_endpoints.py +0 -0
  83. {pyopenmagnetics-1.7.39 → pyopenmagnetics-1.7.41}/tests/test_core.py +0 -0
  84. {pyopenmagnetics-1.7.39 → pyopenmagnetics-1.7.41}/tests/test_core_adviser.py +0 -0
  85. {pyopenmagnetics-1.7.39 → pyopenmagnetics-1.7.41}/tests/test_datasheet_only_catalogue.py +0 -0
  86. {pyopenmagnetics-1.7.39 → pyopenmagnetics-1.7.41}/tests/test_examples_integration.py +0 -0
  87. {pyopenmagnetics-1.7.39 → pyopenmagnetics-1.7.41}/tests/test_inputs.py +0 -0
  88. {pyopenmagnetics-1.7.39 → pyopenmagnetics-1.7.41}/tests/test_logging.py +0 -0
  89. {pyopenmagnetics-1.7.39 → pyopenmagnetics-1.7.41}/tests/test_losses_not_evaluable_catalogue.py +0 -0
  90. {pyopenmagnetics-1.7.39 → pyopenmagnetics-1.7.41}/tests/test_magnetic_adviser.py +0 -0
  91. {pyopenmagnetics-1.7.39 → pyopenmagnetics-1.7.41}/tests/test_magnetics_catalogue_load.py +0 -0
  92. {pyopenmagnetics-1.7.39 → pyopenmagnetics-1.7.41}/tests/test_mas_schema_walk.py +0 -0
  93. {pyopenmagnetics-1.7.39 → pyopenmagnetics-1.7.41}/tests/test_material_validator.py +0 -0
  94. {pyopenmagnetics-1.7.39 → pyopenmagnetics-1.7.41}/tests/test_plotting.py +0 -0
  95. {pyopenmagnetics-1.7.39 → pyopenmagnetics-1.7.41}/tests/test_winding.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: PyOpenMagnetics
3
- Version: 1.7.39
3
+ Version: 1.7.41
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.39"
7
+ version = "1.7.41"
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() {