PyOpenMagnetics 1.7.35__tar.gz → 1.7.37__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.
- {pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/CMakeLists.txt +15 -1
- {pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/PKG-INFO +1 -1
- {pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/pyproject.toml +1 -1
- {pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/src/core.cpp +12 -7
- {pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/src/losses.cpp +15 -5
- {pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/src/simulation.cpp +52 -3
- {pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/src/simulation.h +4 -1
- {pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/tests/test_core.py +49 -3
- {pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/.github/workflows/ci.yml +0 -0
- {pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/.github/workflows/publish.yml +0 -0
- {pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/.gitignore +0 -0
- {pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/AGENTS.md +0 -0
- {pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/LICENSE +0 -0
- {pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/PyOpenMagnetics.pyi +0 -0
- {pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/README.md +0 -0
- {pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/_packaging/__init__.py +0 -0
- {pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/_packaging/_build_info.py.in +0 -0
- {pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/api/MAS.py +0 -0
- {pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/api/mas_db_reader.py +0 -0
- {pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/api/validation.py +0 -0
- {pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/clear_cibuildwheel_cache.sh +0 -0
- {pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/docs/compatibility.md +0 -0
- {pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/docs/errors.md +0 -0
- {pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/docs/performance.md +0 -0
- {pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/examples/README.md +0 -0
- {pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/examples/buck_inductor.py +0 -0
- {pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/examples/complete_simulation_example.py +0 -0
- {pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/examples/converter_design_example.py +0 -0
- {pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/examples/debug_bobbin.py +0 -0
- {pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/examples/debug_coil.py +0 -0
- {pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/examples/debug_core.py +0 -0
- {pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/examples/debug_plotting.py +0 -0
- {pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/examples/flyback_220v_12v_1a.py +0 -0
- {pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/examples/flyback_220v_12v_2a_complete.py +0 -0
- {pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/examples/flyback_bh_curve.png +0 -0
- {pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/examples/flyback_core.png +0 -0
- {pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/examples/flyback_design.py +0 -0
- {pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/examples/flyback_summary.png +0 -0
- {pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/examples/flyback_waveforms.png +0 -0
- {pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/examples/list_plot_funcs.py +0 -0
- {pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/examples/plot_flyback_design.py +0 -0
- {pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/examples/plot_flyback_pyom.py +0 -0
- {pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/examples/test_field_calc.py +0 -0
- {pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/examples/test_field_plot.py +0 -0
- {pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/force_fresh_build.sh +0 -0
- {pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/llms.txt +0 -0
- {pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/notebooks/01_getting_started.ipynb +0 -0
- {pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/notebooks/02_buck_inductor.ipynb +0 -0
- {pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/notebooks/03_core_losses.ipynb +0 -0
- {pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/notebooks/README.md +0 -0
- {pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/requirements.txt +0 -0
- {pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/src/advisers.cpp +0 -0
- {pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/src/advisers.h +0 -0
- {pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/src/bobbin.cpp +0 -0
- {pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/src/bobbin.h +0 -0
- {pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/src/cmc.cpp +0 -0
- {pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/src/cmc.h +0 -0
- {pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/src/common.h +0 -0
- {pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/src/converter.cpp +0 -0
- {pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/src/converter.h +0 -0
- {pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/src/core.h +0 -0
- {pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/src/crossref.cpp +0 -0
- {pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/src/crossref.h +0 -0
- {pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/src/database.cpp +0 -0
- {pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/src/database.h +0 -0
- {pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/src/logging.cpp +0 -0
- {pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/src/logging.h +0 -0
- {pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/src/losses.h +0 -0
- {pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/src/module.cpp +0 -0
- {pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/src/plotting.cpp +0 -0
- {pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/src/plotting.h +0 -0
- {pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/src/settings.cpp +0 -0
- {pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/src/settings.h +0 -0
- {pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/src/utils.cpp +0 -0
- {pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/src/utils.h +0 -0
- {pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/src/winding.cpp +0 -0
- {pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/src/winding.h +0 -0
- {pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/src/wire.cpp +0 -0
- {pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/src/wire.h +0 -0
- {pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/test.py +0 -0
- {pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/tests/__init__.py +0 -0
- {pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/tests/conftest.py +0 -0
- {pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/tests/test_converter_endpoints.py +0 -0
- {pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/tests/test_core_adviser.py +0 -0
- {pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/tests/test_datasheet_only_catalogue.py +0 -0
- {pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/tests/test_examples_integration.py +0 -0
- {pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/tests/test_inputs.py +0 -0
- {pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/tests/test_logging.py +0 -0
- {pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/tests/test_losses_not_evaluable_catalogue.py +0 -0
- {pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/tests/test_magnetic_adviser.py +0 -0
- {pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/tests/test_magnetics_catalogue_load.py +0 -0
- {pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/tests/test_mas_schema_walk.py +0 -0
- {pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/tests/test_material_validator.py +0 -0
- {pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/tests/test_plotting.py +0 -0
- {pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/tests/test_winding.py +0 -0
|
@@ -142,7 +142,7 @@ if(NOT LOCAL_MKF_MAS)
|
|
|
142
142
|
set(MKF_GIT_TAG "main"
|
|
143
143
|
CACHE STRING "MKF git ref for the vendored PyOM build")
|
|
144
144
|
# Force fresh clone by using a unique timestamp - update this when the pin changes
|
|
145
|
-
set(MKF_FORCE_REFRESH "2026-10-
|
|
145
|
+
set(MKF_FORCE_REFRESH "2026-10-28-mkf-292dd4c2")
|
|
146
146
|
# Tell MKF to disable matplotplusplus and use SVG-based Painter instead
|
|
147
147
|
set(INCLUDE_PYMKF ON CACHE BOOL "Build Python interface" FORCE)
|
|
148
148
|
# GIT_SUBMODULES_RECURSE pulls in CAS/PEAS (added 2026-04 alongside the
|
|
@@ -430,6 +430,20 @@ file(GLOB SOURCES src/*.cpp
|
|
|
430
430
|
${MKF_DIR}/src/support/*.cpp
|
|
431
431
|
)
|
|
432
432
|
|
|
433
|
+
# MKF parses its MAS databases with simdjson (src/support/FastJsonParse.cpp), compiled from the single-header
|
|
434
|
+
# amalgamation as in MKF's own CMakeLists.txt; only those two files see its header.
|
|
435
|
+
if(EXISTS "${MKF_DIR}/src/support/FastJsonParse.cpp")
|
|
436
|
+
FetchContent_Declare(simdjson
|
|
437
|
+
GIT_REPOSITORY https://github.com/simdjson/simdjson.git
|
|
438
|
+
GIT_TAG tags/v5.0.5
|
|
439
|
+
GIT_SHALLOW TRUE
|
|
440
|
+
SOURCE_SUBDIR singleheader-not-a-cmake-project)
|
|
441
|
+
FetchContent_MakeAvailable(simdjson)
|
|
442
|
+
list(APPEND SOURCES "${simdjson_SOURCE_DIR}/singleheader/simdjson.cpp")
|
|
443
|
+
set_source_files_properties("${simdjson_SOURCE_DIR}/singleheader/simdjson.cpp" "${MKF_DIR}/src/support/FastJsonParse.cpp"
|
|
444
|
+
PROPERTIES INCLUDE_DIRECTORIES "${simdjson_SOURCE_DIR}/singleheader")
|
|
445
|
+
endif()
|
|
446
|
+
|
|
433
447
|
# converter.cpp is now a thin shim over the Kirchhoff string API (libKirchhoffApi) — re-enabled.
|
|
434
448
|
|
|
435
449
|
set(CCI_GENERATED_CPP "${CMAKE_BINARY_DIR}/generated/CciCoordinatesData.cpp")
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#include "core.h"
|
|
2
2
|
#include "constructive_models/CorePiece.h"
|
|
3
3
|
#include "physical_models/ComplexPermeability.h"
|
|
4
|
+
#include "Defaults.h"
|
|
4
5
|
#include <filesystem>
|
|
5
6
|
|
|
6
7
|
namespace PyMKF {
|
|
@@ -284,7 +285,8 @@ json calculate_gap_reluctance(json coreGapData, std::string modelNameString) {
|
|
|
284
285
|
auto reluctanceModel = OpenMagnetics::ReluctanceModel::factory(modelName);
|
|
285
286
|
CoreGap coreGap(coreGapData);
|
|
286
287
|
|
|
287
|
-
|
|
288
|
+
// A standalone gap: no core material, so its storable energy is at the default reference flux density.
|
|
289
|
+
auto coreGapResult = reluctanceModel->get_gap_reluctance(coreGap, OpenMagnetics::Defaults().gapReluctanceEnergyFluxDensity);
|
|
288
290
|
json result;
|
|
289
291
|
to_json(result, coreGapResult);
|
|
290
292
|
return result;
|
|
@@ -445,13 +447,16 @@ double calculate_peak_winding_current(json magneticJson, json operatingPointJson
|
|
|
445
447
|
}
|
|
446
448
|
|
|
447
449
|
// The magnetizing (flux-driving) current is referred to the primary
|
|
448
|
-
// (winding 0): I_mag = (1/L) ∫
|
|
449
|
-
//
|
|
450
|
-
// the
|
|
451
|
-
//
|
|
452
|
-
|
|
450
|
+
// (winding 0): Faraday on the primary voltage, I_mag = (1/L) ∫ v dt,
|
|
451
|
+
// plus the DC the winding currents give (a single winding's own current,
|
|
452
|
+
// or the anchor of windings that do not always conduct together: a
|
|
453
|
+
// flyback, a forward's reset). This is MKF's one shared decision
|
|
454
|
+
// (Inputs::calculate_magnetizing_current on the operating point), so the
|
|
455
|
+
// returned peak is the saturation-driving peak (DC + ripple). The former
|
|
456
|
+
// call passed `true` into the dcCurrent slot, which C++ converted to a
|
|
457
|
+
// flat 1 A DC whatever the currents were.
|
|
453
458
|
auto magnetizingCurrent = OpenMagnetics::Inputs::calculate_magnetizing_current(
|
|
454
|
-
|
|
459
|
+
op, 0, magnetizingInductance, magnetic.get_mutable_coil().get_turns_ratios(), true);
|
|
455
460
|
auto processed = magnetizingCurrent.get_processed();
|
|
456
461
|
if (!processed.has_value() || !processed->get_peak().has_value()) {
|
|
457
462
|
throw std::runtime_error(
|
|
@@ -32,7 +32,11 @@ json calculate_core_losses(json coreData, json coilData, json inputsData, json m
|
|
|
32
32
|
OpenMagnetics::MagneticSimulator magneticSimulator;
|
|
33
33
|
magneticSimulator.set_core_losses_model_name(coreLossesModelName);
|
|
34
34
|
magneticSimulator.set_reluctance_model_name(reluctanceModelName);
|
|
35
|
-
|
|
35
|
+
// ABT #1876 (Alf, 2026-10-14): a point with no thermal operating point gets its core losses at ambient and
|
|
36
|
+
// NO temperature (temperatureOutput stays empty); its coreLosses.temperature is the ambient they were
|
|
37
|
+
// evaluated at, not an operating temperature.
|
|
38
|
+
std::optional<TemperatureOutput> temperatureOutput;
|
|
39
|
+
auto coreLossesOutput = magneticSimulator.calculate_core_losses(operatingPoint, magnetic, nullptr, &temperatureOutput);
|
|
36
40
|
json result;
|
|
37
41
|
to_json(result, coreLossesOutput);
|
|
38
42
|
|
|
@@ -44,8 +48,12 @@ json calculate_core_losses(json coreData, json coilData, json inputsData, json m
|
|
|
44
48
|
result["voltageRms"] = operatingPoint.get_mutable_excitations_per_winding()[0].get_voltage().value().get_processed().value().get_rms().value();
|
|
45
49
|
result["currentRms"] = operatingPoint.get_mutable_excitations_per_winding()[0].get_current().value().get_processed().value().get_rms().value();
|
|
46
50
|
result["apparentPower"] = operatingPoint.get_mutable_excitations_per_winding()[0].get_voltage().value().get_processed().value().get_rms().value() * operatingPoint.get_mutable_excitations_per_winding()[0].get_current().value().get_processed().value().get_rms().value();
|
|
47
|
-
|
|
48
|
-
|
|
51
|
+
// The core temperature the losses were solved at, only when the point has a thermal operating point:
|
|
52
|
+
// otherwise both keys are absent.
|
|
53
|
+
if (temperatureOutput) {
|
|
54
|
+
result["maximumCoreTemperature"] = coreLossesOutput.get_temperature();
|
|
55
|
+
result["maximumCoreTemperatureRise"] = coreLossesOutput.get_temperature() - operatingPoint.get_conditions().get_ambient_temperature();
|
|
56
|
+
}
|
|
49
57
|
|
|
50
58
|
return result;
|
|
51
59
|
}
|
|
@@ -353,8 +361,10 @@ void register_losses_bindings(py::module& m) {
|
|
|
353
361
|
- voltageRms: RMS voltage in Volts
|
|
354
362
|
- currentRms: RMS current in Amperes
|
|
355
363
|
- apparentPower: Apparent power in VA
|
|
356
|
-
- maximumCoreTemperature:
|
|
357
|
-
|
|
364
|
+
- maximumCoreTemperature: Core temperature the losses were solved at, in Celsius.
|
|
365
|
+
Absent when the point has no thermal operating point (the losses are then
|
|
366
|
+
those at ambient; analyze_results reports NO_THERMAL_OPERATING_POINT).
|
|
367
|
+
- maximumCoreTemperatureRise: Temperature rise in Kelvin; absent likewise.
|
|
358
368
|
|
|
359
369
|
Example:
|
|
360
370
|
>>> models = {"coreLosses": "IGSE", "reluctance": "ZHANG"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#include "simulation.h"
|
|
2
|
+
#include "processors/ResultsAnalyzer.h"
|
|
2
3
|
|
|
3
4
|
namespace PyMKF {
|
|
4
5
|
|
|
@@ -23,6 +24,14 @@ json simulate(json inputsJson, json magneticJson, json modelsData) {
|
|
|
23
24
|
return result;
|
|
24
25
|
}
|
|
25
26
|
|
|
27
|
+
json analyze_results(json masJson) {
|
|
28
|
+
OpenMagnetics::Mas mas(masJson);
|
|
29
|
+
auto findings = OpenMagnetics::ResultsAnalyzer::analyze_results(mas);
|
|
30
|
+
json result;
|
|
31
|
+
OpenMagnetics::to_json(result, findings);
|
|
32
|
+
return result;
|
|
33
|
+
}
|
|
34
|
+
|
|
26
35
|
json build_datasheet(json inputsJson, json magneticJson, json modelsData) {
|
|
27
36
|
// Simulate the magnetic at the given operating points (the same models as simulate), then let
|
|
28
37
|
// MagneticSimulator::build_datasheet read the outputs into a manufacturer-style datasheet. Returns
|
|
@@ -312,10 +321,30 @@ json sweep_magnetizing_inductance_over_dc_bias(json magneticJson, double start,
|
|
|
312
321
|
return resultJson;
|
|
313
322
|
}
|
|
314
323
|
|
|
315
|
-
|
|
324
|
+
// The generated from_json leaves a Topology untouched for a name it does not know, so an unknown
|
|
325
|
+
// name would reach the model as an uninitialised value: match it against each topology's own
|
|
326
|
+
// spelling and throw when none matches.
|
|
327
|
+
static std::optional<MAS::Topology> parse_optional_topology(const json& topologyJson) {
|
|
328
|
+
if (topologyJson.is_null()) {
|
|
329
|
+
return std::nullopt;
|
|
330
|
+
}
|
|
331
|
+
for (auto candidate : magic_enum::enum_values<MAS::Topology>()) {
|
|
332
|
+
json spelling;
|
|
333
|
+
to_json(spelling, candidate);
|
|
334
|
+
if (spelling == topologyJson) {
|
|
335
|
+
return candidate;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
throw std::invalid_argument("Unknown topology " + topologyJson.dump());
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// topologyJson: the design's designRequirements.topology, None when the design has none. The
|
|
342
|
+
// magnetizing current that drives the core depends on it (MKF ABT #1754).
|
|
343
|
+
json sweep_core_losses_over_frequency(json magneticJson, json operatingPointJson, json topologyJson, double start, double stop, size_t numberElements, double temperature, std::string mode, std::string title) {
|
|
316
344
|
OpenMagnetics::Magnetic magnetic(magneticJson);
|
|
317
345
|
OperatingPoint operatingPoint(operatingPointJson);
|
|
318
|
-
auto
|
|
346
|
+
auto topology = parse_optional_topology(topologyJson);
|
|
347
|
+
auto result = OpenMagnetics::Sweeper::sweep_core_losses_over_frequency(magnetic, operatingPoint, topology, start, stop, numberElements, temperature, mode, title);
|
|
319
348
|
json resultJson;
|
|
320
349
|
to_json(resultJson, result);
|
|
321
350
|
return resultJson;
|
|
@@ -443,6 +472,25 @@ void register_simulation_bindings(py::module& m) {
|
|
|
443
472
|
)pbdoc",
|
|
444
473
|
py::call_guard<py::gil_scoped_release>());
|
|
445
474
|
|
|
475
|
+
m.def("analyze_results", &analyze_results,
|
|
476
|
+
R"pbdoc(
|
|
477
|
+
MKF's findings on a simulated MAS (ABT #1855).
|
|
478
|
+
|
|
479
|
+
simulate gives its numbers for every operating point and does not judge them; this says
|
|
480
|
+
whether they describe a part that works: a saturating core, a voltage that is not
|
|
481
|
+
volt-second balanced (simulated on its balanced part), a core or winding above a
|
|
482
|
+
temperature limit its part states (or a part that states none), a core driven past the
|
|
483
|
+
temperatures its material data covers.
|
|
484
|
+
|
|
485
|
+
Args:
|
|
486
|
+
mas_json: a simulated MAS, with one output per operating point.
|
|
487
|
+
|
|
488
|
+
Returns:
|
|
489
|
+
JSON array of {operatingPointIndex, operatingPointName (when the point has one),
|
|
490
|
+
code, message}. Raises when the MAS lacks an output for an operating point.
|
|
491
|
+
)pbdoc",
|
|
492
|
+
py::call_guard<py::gil_scoped_release>());
|
|
493
|
+
|
|
446
494
|
m.def("build_datasheet", &build_datasheet,
|
|
447
495
|
R"pbdoc(
|
|
448
496
|
Build a manufacturer-style datasheet for a magnetic from MKF's own simulation.
|
|
@@ -776,13 +824,14 @@ void register_simulation_bindings(py::module& m) {
|
|
|
776
824
|
m.def("sweep_core_losses_over_frequency", &sweep_core_losses_over_frequency,
|
|
777
825
|
py::arg("magnetic_json"),
|
|
778
826
|
py::arg("operating_point_json"),
|
|
827
|
+
py::arg("topology"),
|
|
779
828
|
py::arg("start"),
|
|
780
829
|
py::arg("stop"),
|
|
781
830
|
py::arg("number_elements"),
|
|
782
831
|
py::arg("temperature"),
|
|
783
832
|
py::arg("mode"),
|
|
784
833
|
py::arg("title"),
|
|
785
|
-
"Sweep core losses over a frequency range.",
|
|
834
|
+
"Sweep core losses over a frequency range. topology: the design's designRequirements.topology, None when it has none.",
|
|
786
835
|
py::call_guard<py::gil_scoped_release>());
|
|
787
836
|
|
|
788
837
|
m.def("sweep_winding_losses_over_frequency", &sweep_winding_losses_over_frequency,
|
|
@@ -6,6 +6,9 @@ namespace PyMKF {
|
|
|
6
6
|
|
|
7
7
|
// Simulation
|
|
8
8
|
json simulate(json inputsJson, json magneticJson, json modelsData);
|
|
9
|
+
// ABT #1855: MKF's findings on a simulated MAS (one output per operating point), each naming its
|
|
10
|
+
// operating point: a JSON array of {operatingPointIndex, operatingPointName?, code, message}.
|
|
11
|
+
json analyze_results(json masJson);
|
|
9
12
|
json build_datasheet(json inputsJson, json magneticJson, json modelsData);
|
|
10
13
|
|
|
11
14
|
// Export
|
|
@@ -44,7 +47,7 @@ json sweep_resistance_over_frequency(json magneticJson, double start, double sto
|
|
|
44
47
|
json sweep_magnetizing_inductance_over_frequency(json magneticJson, double start, double stop, size_t numberElements, double temperature, std::string mode, std::string title);
|
|
45
48
|
json sweep_magnetizing_inductance_over_temperature(json magneticJson, double start, double stop, size_t numberElements, double frequency, std::string mode, std::string title);
|
|
46
49
|
json sweep_magnetizing_inductance_over_dc_bias(json magneticJson, double start, double stop, size_t numberElements, double temperature, std::string mode, std::string title);
|
|
47
|
-
json sweep_core_losses_over_frequency(json magneticJson, json operatingPointJson, double start, double stop, size_t numberElements, double temperature, std::string mode, std::string title);
|
|
50
|
+
json sweep_core_losses_over_frequency(json magneticJson, json operatingPointJson, json topologyJson, double start, double stop, size_t numberElements, double temperature, std::string mode, std::string title);
|
|
48
51
|
json sweep_winding_losses_over_frequency(json magneticJson, json operatingPointJson, double start, double stop, size_t numberElements, double temperature, std::string mode, std::string title);
|
|
49
52
|
|
|
50
53
|
// Matrix calculations
|
|
@@ -4,6 +4,7 @@ Tests for PyOpenMagnetics Core data retrieval and calculations.
|
|
|
4
4
|
These tests mirror TestCore.cpp and TestCoreAdviser.cpp from MKF,
|
|
5
5
|
verifying core shape, material, and gapping calculations.
|
|
6
6
|
"""
|
|
7
|
+
import math
|
|
7
8
|
import pytest
|
|
8
9
|
import PyOpenMagnetics
|
|
9
10
|
|
|
@@ -245,6 +246,42 @@ class TestPeakWindingCurrent:
|
|
|
245
246
|
magnetic, op_used, 25.0)
|
|
246
247
|
assert isat > ipeak # an advised design should not be saturating
|
|
247
248
|
|
|
249
|
+
def test_flyback_peak_is_the_primary_current_peak(self):
|
|
250
|
+
"""
|
|
251
|
+
A CCM flyback, 1:1, 100 kHz, D = 0.5: the primary carries 2 -> 6 A
|
|
252
|
+
during the on-time, the secondary 6 -> 2 A during the off-time. Each
|
|
253
|
+
winding alone carries the magnetizing MMF while it conducts, so the
|
|
254
|
+
magnetizing current peaks at the primary current's peak, 6 A (MKF's
|
|
255
|
+
shared magnetizing-current decision, DC anchored where the primary
|
|
256
|
+
conducts alone). The binding used to pass `true` into MKF's dcCurrent
|
|
257
|
+
argument: a flat 1 A DC under a zero-mean Faraday ripple, ~3 A.
|
|
258
|
+
"""
|
|
259
|
+
def winding(name, current, voltage):
|
|
260
|
+
return {
|
|
261
|
+
"name": name,
|
|
262
|
+
"frequency": 100000,
|
|
263
|
+
"current": {"waveform": {"data": current,
|
|
264
|
+
"time": [0, 0.000005, 0.000005, 0.00001]}},
|
|
265
|
+
"voltage": {"waveform": {"data": voltage,
|
|
266
|
+
"time": [0, 0.000005, 0.000005, 0.00001]}},
|
|
267
|
+
}
|
|
268
|
+
op = {
|
|
269
|
+
"name": "Nominal",
|
|
270
|
+
"conditions": {"ambientTemperature": 25},
|
|
271
|
+
"excitationsPerWinding": [
|
|
272
|
+
winding("Primary", [2, 6, 0, 0], [80, 80, -80, -80]),
|
|
273
|
+
winding("Secondary", [0, 0, 6, 2], [-80, -80, 80, 80]),
|
|
274
|
+
],
|
|
275
|
+
}
|
|
276
|
+
inputs = {
|
|
277
|
+
"designRequirements": {"magnetizingInductance": {"nominal": 100e-6},
|
|
278
|
+
"turnsRatios": [{"nominal": 1.0}]},
|
|
279
|
+
"operatingPoints": [op],
|
|
280
|
+
}
|
|
281
|
+
magnetic, op_used = self._advise_magnetic(inputs)
|
|
282
|
+
ipeak = PyOpenMagnetics.calculate_peak_winding_current(magnetic, op_used, 0)
|
|
283
|
+
assert ipeak == pytest.approx(6.0, rel=0.05)
|
|
284
|
+
|
|
248
285
|
def test_transformer_winding_index_scales_by_turns_ratio(self):
|
|
249
286
|
"""
|
|
250
287
|
For a transformer the per-winding magnetizing peak is the primary
|
|
@@ -324,11 +361,20 @@ class TestComplexPermeabilityFrequencyRange:
|
|
|
324
361
|
# A07's tabulated mu''(f) ends at ~17 MHz (mu' extended to it by Kramers-Kronig in MAS).
|
|
325
362
|
assert 1e7 < span["maximum"] < 1e8
|
|
326
363
|
|
|
327
|
-
def
|
|
364
|
+
def test_inside_the_span_answers_and_above_it_estimates(self):
|
|
365
|
+
# MKF ABT #1861: outside the tabulated span the complex permeability of a material the
|
|
366
|
+
# estimator covers is answered with the approved estimate, no longer thrown
|
|
367
|
+
# (MATERIAL_FREQUENCY_OUT_OF_SPAN). Inside the span the table answers, as before.
|
|
328
368
|
material = PyOpenMagnetics.find_core_material_by_name("A07")
|
|
329
369
|
span = PyOpenMagnetics.calculate_complex_permeability_frequency_range(material)
|
|
330
370
|
assert span == PyOpenMagnetics.calculate_complex_permeability_frequency_range("A07")
|
|
331
371
|
inside = PyOpenMagnetics.calculate_complex_permeability(material, span["maximum"] * 0.99)
|
|
332
372
|
assert inside["imaginary"] > 0
|
|
333
|
-
|
|
334
|
-
|
|
373
|
+
at_edge = PyOpenMagnetics.calculate_complex_permeability(material, span["maximum"])
|
|
374
|
+
above = PyOpenMagnetics.calculate_complex_permeability(material, span["maximum"] * 1.01)
|
|
375
|
+
# A finite estimate that continues the table: same sign of both parts as at the span's
|
|
376
|
+
# end, and mu'' still falling with frequency there.
|
|
377
|
+
for part in ("real", "imaginary"):
|
|
378
|
+
assert math.isfinite(above[part])
|
|
379
|
+
assert above[part] * at_edge[part] > 0
|
|
380
|
+
assert 0 < above["imaginary"] <= at_edge["imaginary"]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pyopenmagnetics-1.7.35 → pyopenmagnetics-1.7.37}/tests/test_losses_not_evaluable_catalogue.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|