Skip to content

Commit 968d5cf

Browse files
committed
Improved advisers
1 parent 9d03a44 commit 968d5cf

File tree

13 files changed

+1753
-48
lines changed

13 files changed

+1753
-48
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,3 +125,7 @@ website/package-lock.json
125125

126126
*.csv
127127
*.Identifier
128+
129+
docs/
130+
tests/testData
131+
src/tools

src/advisers/CoilAdviser.h

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,70 @@ using namespace MAS;
1010

1111
namespace OpenMagnetics {
1212

13+
/**
14+
* @class CoilAdviser
15+
* @brief Recommends complete coil configurations including winding patterns, wire selection, and insulation.
16+
*
17+
* ## Overview
18+
* CoilAdviser extends WireAdviser to recommend complete coil designs. It handles:
19+
* - Winding pattern selection (interleaved vs. non-interleaved)
20+
* - Wire selection per winding (using WireAdviser)
21+
* - Section proportion calculation based on power handling
22+
* - Insulation coordination per safety standards
23+
*
24+
* ## Design Process
25+
* 1. Calculate winding window proportions based on average power per winding
26+
* 2. Generate candidate patterns (winding order permutations)
27+
* 3. For each pattern, determine insulation requirements
28+
* 4. Select optimal wires for each winding using WireAdviser
29+
* 5. Score complete coil configurations and return ranked results
30+
*
31+
* ## Scoring System
32+
* The default scoring filters (configurable via `load_filter_flow()`):
33+
* - **EFFECTIVE_RESISTANCE**: AC resistance of windings (lower = better)
34+
* - **EFFECTIVE_CURRENT_DENSITY**: Current density in conductors (lower = better)
35+
* - **MAGNETOMOTIVE_FORCE**: MMF distribution quality (lower = better)
36+
*
37+
* Each filter uses:
38+
* - `invert=true`: Lower raw values get higher scores
39+
* - `log=true`: Logarithmic normalization (compresses large differences)
40+
* - `weight=1.0`: Equal weight for all criteria
41+
*
42+
* ## Winding Patterns
43+
* For a 2-winding transformer, patterns include:
44+
* - `{0, 1}`: Primary then Secondary (non-interleaved)
45+
* - `{1, 0}`: Secondary then Primary
46+
* - With `repetitions > 1`: Interleaved sections (P-S-P-S, etc.)
47+
*
48+
* ## Insulation Coordination
49+
* Based on IEC 60664-1 and IEC 61558, determines:
50+
* - Required creepage/clearance distances
51+
* - Wire insulation grade requirements
52+
* - Whether margin tape or insulated wire is needed
53+
*
54+
* ## Planar vs. Wound Coils
55+
* - **Wound**: Traditional bobbin-wound coils with round/litz/foil wire
56+
* - **Planar**: PCB-based windings with copper traces
57+
*
58+
* ## Key Configuration
59+
* - `set_allow_margin_tape(bool)`: Allow/disallow margin tape insulation
60+
* - `set_allow_insulated_wire(bool)`: Allow/disallow triple-insulated wire
61+
* - `set_common_wire_standard(WireStandard)`: Restrict to specific wire standards
62+
* - `set_maximum_effective_current_density(double)`: Max current density
63+
* - `set_maximum_number_parallels(int)`: Max parallel conductors
64+
*
65+
* ## Usage Example
66+
* ```cpp
67+
* CoilAdviser coilAdviser;
68+
* coilAdviser.set_maximum_effective_current_density(5e6);
69+
* coilAdviser.set_allow_margin_tape(true);
70+
* auto results = coilAdviser.get_advised_coil(mas, 5); // Top 5 configurations
71+
* ```
72+
*
73+
* ## References
74+
* - IEC 60664-1: Insulation coordination for low-voltage equipment
75+
* - IEC 61558: Safety of transformers
76+
*/
1377
class CoilAdviser : public WireAdviser {
1478
private:
1579
bool _allowMarginTape = true;

src/advisers/CoreAdviser.cpp

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -622,10 +622,10 @@ std::vector<std::pair<Mas, double>> CoreAdviser::get_advised_core(Inputs inputs,
622622

623623
size_t maximumMagneticsAfterFiltering = defaults.coreAdviserMaximumMagneticsAfterFiltering;
624624
if (get_application() == Application::POWER) {
625-
return filter_standard_cores_power_application(&magnetics, inputs, maximumMagneticsAfterFiltering, maximumNumberResults);
625+
return filter_standard_cores_power_application(&magnetics, inputs, _weights, maximumMagneticsAfterFiltering, maximumNumberResults);
626626
}
627627
else {
628-
return filter_standard_cores_interference_suppression_application(&magnetics, inputs, maximumMagneticsAfterFiltering, maximumNumberResults);
628+
return filter_standard_cores_interference_suppression_application(&magnetics, inputs, _weights, maximumMagneticsAfterFiltering, maximumNumberResults);
629629
}
630630
}
631631

@@ -1359,15 +1359,15 @@ std::vector<std::pair<Mas, double>> CoreAdviser::filter_available_cores_power_ap
13591359
filterDimensions.set_filter_configuration(&_filterConfiguration);
13601360

13611361
std::vector<std::pair<Magnetic, double>> magneticsWithScoring = *magnetics;
1362-
magneticsWithScoring = filterAreaProduct.filter_magnetics(&magneticsWithScoring, inputs, 1.0, true);
1362+
magneticsWithScoring = filterAreaProduct.filter_magnetics(&magneticsWithScoring, inputs, 1.0, true); // Fixed weight: pre-filtering criterion, not efficiency scoring
13631363
logEntry("There are " + std::to_string(magneticsWithScoring.size()) + " magnetics after the Area Product filter.", "CoreAdviser");
13641364

13651365
if (magneticsWithScoring.size() > maximumMagneticsAfterFiltering) {
13661366
magneticsWithScoring = std::vector<std::pair<Magnetic, double>>(magneticsWithScoring.begin(), magneticsWithScoring.end() - (magneticsWithScoring.size() - maximumMagneticsAfterFiltering));
13671367
logEntry("There are " + std::to_string(magneticsWithScoring.size()) + " after culling by the score on the first filter.", "CoreAdviser");
13681368
}
13691369

1370-
magneticsWithScoring = filterEnergyStored.filter_magnetics(&magneticsWithScoring, inputs, 1.0, true);
1370+
magneticsWithScoring = filterEnergyStored.filter_magnetics(&magneticsWithScoring, inputs, 1.0, true); // Fixed weight: pre-filtering criterion, not efficiency scoring
13711371
logEntry("There are " + std::to_string(magneticsWithScoring.size()) + " magnetics after the Energy Stored filter.", "CoreAdviser");
13721372

13731373
add_initial_turns_by_inductance(&magneticsWithScoring, inputs);
@@ -1446,8 +1446,8 @@ std::vector<std::pair<Mas, double>> CoreAdviser::filter_available_cores_suppress
14461446
magneticsWithScoring = filterMagneticInductance.filter_magnetics(&magneticsWithScoring, inputs, weights[CoreAdviserFilters::EFFICIENCY], true);
14471447
logEntry("There are " + std::to_string(magneticsWithScoring.size()) + " magnetics after the Magnetizing Inductance.", "CoreAdviser");
14481448

1449-
// magneticsWithScoring = filterLosses.filter_magnetics(&magneticsWithScoring, inputs, weights[CoreAdviserFilters::EFFICIENCY], true);
1450-
// logEntry("There are " + std::to_string(magneticsWithScoring.size()) + " magnetics after the Core Losses filter.", "CoreAdviser");
1449+
magneticsWithScoring = filterLosses.filter_magnetics(&magneticsWithScoring, inputs, weights[CoreAdviserFilters::EFFICIENCY], true);
1450+
logEntry("There are " + std::to_string(magneticsWithScoring.size()) + " magnetics after the Core Losses filter.", "CoreAdviser");
14511451

14521452
if (magneticsWithScoring.size() == 0) {
14531453
return {};
@@ -1474,7 +1474,7 @@ std::vector<std::pair<Mas, double>> CoreAdviser::filter_available_cores_suppress
14741474
return masWithScoring;
14751475
}
14761476

1477-
std::vector<std::pair<Mas, double>> CoreAdviser::filter_standard_cores_power_application(std::vector<std::pair<Magnetic, double>>* magnetics, Inputs inputs, size_t maximumMagneticsAfterFiltering, size_t maximumNumberResults){
1477+
std::vector<std::pair<Mas, double>> CoreAdviser::filter_standard_cores_power_application(std::vector<std::pair<Magnetic, double>>* magnetics, Inputs inputs, std::map<CoreAdviserFilters, double> weights, size_t maximumMagneticsAfterFiltering, size_t maximumNumberResults){
14781478
inputs = pre_process_inputs(inputs);
14791479

14801480
MagneticCoreFilterAreaProduct filterAreaProduct(inputs);
@@ -1580,7 +1580,7 @@ std::vector<std::pair<Mas, double>> CoreAdviser::filter_standard_cores_power_app
15801580
return masWithScoring;
15811581
}
15821582

1583-
std::vector<std::pair<Mas, double>> CoreAdviser::filter_standard_cores_interference_suppression_application(std::vector<std::pair<Magnetic, double>>* magnetics, Inputs inputs, size_t maximumMagneticsAfterFiltering, size_t maximumNumberResults){
1583+
std::vector<std::pair<Mas, double>> CoreAdviser::filter_standard_cores_interference_suppression_application(std::vector<std::pair<Magnetic, double>>* magnetics, Inputs inputs, std::map<CoreAdviserFilters, double> weights, size_t maximumMagneticsAfterFiltering, size_t maximumNumberResults){
15841584
inputs = pre_process_inputs(inputs);
15851585

15861586
MagneticCoreFilterLosses filterLosses(inputs, _models);

src/advisers/CoreAdviser.h

Lines changed: 149 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,80 @@ using namespace MAS;
2020

2121
namespace OpenMagnetics {
2222

23+
/**
24+
* @class CoreAdviser
25+
* @brief Multi-criteria magnetic core recommendation system.
26+
*
27+
* ## Overview
28+
* CoreAdviser selects optimal magnetic cores for power electronics applications based on
29+
* user-defined priorities. It evaluates cores from a database using multiple criteria and
30+
* returns ranked recommendations with normalized scores.
31+
*
32+
* ## Scoring System
33+
* The adviser uses three main filter categories, each with configurable weights (0.0-1.0):
34+
* - **COST**: Estimated manufacturing/purchasing cost (lower is better)
35+
* - **EFFICIENCY**: Power losses (core + DC winding losses, lower is better)
36+
* - **DIMENSIONS**: Physical size/volume (smaller is better)
37+
*
38+
* ### Score Calculation
39+
* 1. Each filter computes a raw score for each core candidate
40+
* 2. Scores are normalized using `normalize_scoring()`:
41+
* - `invert=true`: Lower raw values get higher scores (used for cost, losses, size)
42+
* - `log=true`: Logarithmic normalization (compresses large differences)
43+
* - `log=false`: Linear normalization (preserves proportional differences)
44+
* 3. Final score = Σ(normalized_score × weight) for all filters
45+
*
46+
* ## Filter Pipeline
47+
*
48+
* ### Power Application (filter_available_cores_power_application)
49+
* The filter chain for power inductors/transformers:
50+
* 1. **filterAreaProduct**: Pre-filter using Area Product (AP = Aw × Ac)
51+
* - Fixed weight 1.0 (binary pass/fail, not scored)
52+
* - Eliminates cores too small for required energy storage
53+
* 2. **filterEnergyStored**: Validates energy storage capacity (L×I²/2)
54+
* - Fixed weight 1.0 (binary pass/fail, not scored)
55+
* - Checks if core can store required energy without saturation
56+
* 3. **filterCost**: Scores by estimated cost
57+
* - Weight: COST user weight
58+
* 4. **filterDimensions**: Scores by physical volume
59+
* - Weight: DIMENSIONS user weight
60+
* - Uses linear normalization to preserve size differences
61+
* 5. **filterLosses**: Scores by total losses (core + winding DC)
62+
* - Weight: EFFICIENCY user weight
63+
*
64+
* ### Suppression Application (filter_available_cores_suppression_application)
65+
* The filter chain for EMI/RFI suppression:
66+
* 1. **filterMinimumImpedance**: Pre-filter by impedance at operating frequency
67+
* 2. **filterCost**: Scores by estimated cost
68+
* 3. **filterDimensions**: Scores by physical volume
69+
* 4. **filterMagneticInductance**: Scores by magnetizing inductance
70+
* 5. **filterLosses**: Scores by total losses
71+
*
72+
* ## Operating Modes
73+
* - **AVAILABLE_CORES**: Uses manufacturer stock database (fastest)
74+
* - **STANDARD_CORES**: Uses standard core shapes with material optimization
75+
* - **CUSTOM_CORES**: Uses user-provided core list
76+
*
77+
* ## Toroid Handling
78+
* Toroidal cores use geometry-based bobbin filling factor calculation:
79+
* fillingFactor = 0.55 + 0.15 × (innerRadius / outerRadius)
80+
* This accounts for the reduced winding area in toroid centers (range: 0.55-0.70).
81+
*
82+
* ## Usage Example
83+
* ```cpp
84+
* std::map<CoreAdviserFilters, double> weights = {
85+
* {CoreAdviserFilters::COST, 0.3},
86+
* {CoreAdviserFilters::EFFICIENCY, 0.5},
87+
* {CoreAdviserFilters::DIMENSIONS, 0.2}
88+
* };
89+
* CoreAdviser adviser;
90+
* auto results = adviser.get_advised_core(inputs, weights, 5); // Top 5 cores
91+
* ```
92+
*
93+
* ## References
94+
* - Industry practice: LI² (energy storage), Area Product method
95+
* - Colonel Wm. T. McLyman, "Transformer and Inductor Design Handbook"
96+
*/
2397
class CoreAdviser {
2498
public:
2599
enum class CoreAdviserFilters : int {
@@ -45,6 +119,21 @@ class CoreAdviser {
45119

46120
public:
47121

122+
/**
123+
* @brief Filter normalization configuration for each scoring category.
124+
*
125+
* Each filter category has two configuration options:
126+
* - **invert**: If true, lower raw values result in higher normalized scores.
127+
* Used when "less is better" (cost, losses, size).
128+
* - **log**: If true, uses logarithmic normalization; if false, linear.
129+
* Linear normalization preserves proportional differences between cores.
130+
*
131+
* Configuration rationale:
132+
* - COST: inverted (cheaper=better), logarithmic (diminishing returns on savings)
133+
* - EFFICIENCY: inverted (lower losses=better), logarithmic (diminishing returns)
134+
* - DIMENSIONS: inverted (smaller=better), LINEAR (size differences should be
135+
* proportionally reflected; a core 2× larger should score proportionally worse)
136+
*/
48137
std::map<CoreAdviserFilters, std::map<std::string, bool>> _filterConfiguration{
49138
{ CoreAdviserFilters::COST, { {"invert", true}, {"log", true} } },
50139
{ CoreAdviserFilters::EFFICIENCY, { {"invert", true}, {"log", true} } },
@@ -63,6 +152,11 @@ class CoreAdviser {
63152
_models["coreLosses"] = to_string(defaults.coreLossesModelDefault);
64153
_models["coreTemperature"] = to_string(defaults.coreTemperatureModelDefault);
65154
}
155+
/**
156+
* @brief Get per-core scoring breakdown for debugging/analysis.
157+
* @param weighted If true, returns weighted scores; otherwise raw normalized scores.
158+
* @return Map of core names to their per-filter scores.
159+
*/
66160
std::map<std::string, std::map<CoreAdviserFilters, double>> get_scorings(bool weighted = false);
67161

68162
void set_unique_core_shapes(bool value);
@@ -72,7 +166,21 @@ class CoreAdviser {
72166
void set_mode(CoreAdviserModes value);
73167
CoreAdviserModes get_mode();
74168

169+
/**
170+
* @brief Main entry point for core recommendation.
171+
* @param inputs Operating conditions (voltage, current, frequency, etc.)
172+
* @param maximumNumberResults Maximum number of cores to return.
173+
* @return Vector of (Mas, score) pairs, sorted by descending score.
174+
*/
75175
std::vector<std::pair<Mas, double>> get_advised_core(Inputs inputs, size_t maximumNumberResults=1);
176+
177+
/**
178+
* @brief Core recommendation with custom weights.
179+
* @param inputs Operating conditions.
180+
* @param weights Map of filter weights (COST, EFFICIENCY, DIMENSIONS). Values 0.0-1.0.
181+
* @param maximumNumberResults Maximum number of cores to return.
182+
* @return Vector of (Mas, score) pairs, sorted by descending score.
183+
*/
76184
std::vector<std::pair<Mas, double>> get_advised_core(Inputs inputs, std::map<CoreAdviserFilters, double> weights, size_t maximumNumberResults=1);
77185
std::vector<std::pair<Mas, double>> get_advised_core(Inputs inputs, std::vector<Core>* cores, size_t maximumNumberResults=1);
78186
std::vector<std::pair<Mas, double>> get_advised_core(Inputs inputs, std::map<CoreAdviserFilters, double> weights, std::vector<Core>* cores, size_t maximumNumberResults=1);
@@ -81,10 +189,49 @@ class CoreAdviser {
81189
std::vector<std::pair<Mas, double>> get_advised_core(Inputs inputs, std::vector<CoreShape>* shapes, size_t maximumNumberResults=1);
82190

83191
Mas post_process_core(Magnetic magnetic, Inputs inputs);
192+
193+
/**
194+
* @brief Filter pipeline for power inductor/transformer applications.
195+
*
196+
* Applies filters in order: AreaProduct → EnergyStored → Cost → Dimensions → Losses.
197+
* - AreaProduct and EnergyStored use fixed weight 1.0 (pre-filtering, not scoring)
198+
* - Cost, Dimensions, Losses use user-provided weights for scoring
199+
*
200+
* @param magnetics Input list of candidate magnetics with initial scores.
201+
* @param inputs Operating conditions.
202+
* @param weights User-defined weights for COST, EFFICIENCY, DIMENSIONS.
203+
* @param maximumMagneticsAfterFiltering Max candidates to keep after filtering.
204+
* @param maximumNumberResults Max results to return.
205+
* @return Filtered and scored list of (Mas, score) pairs.
206+
*/
84207
std::vector<std::pair<Mas, double>> filter_available_cores_power_application(std::vector<std::pair<Magnetic, double>>* magnetics, Inputs inputs, std::map<CoreAdviserFilters, double> weights, size_t maximumMagneticsAfterFiltering, size_t maximumNumberResults);
208+
209+
/**
210+
* @brief Filter pipeline for EMI/RFI suppression applications.
211+
*
212+
* Applies filters: MinimumImpedance → Cost → Dimensions → MagneticInductance → Losses.
213+
*
214+
* @param magnetics Input list of candidate magnetics with initial scores.
215+
* @param inputs Operating conditions (includes required impedance at frequency).
216+
* @param weights User-defined weights for COST, EFFICIENCY, DIMENSIONS.
217+
* @param maximumMagneticsAfterFiltering Max candidates to keep after filtering.
218+
* @param maximumNumberResults Max results to return.
219+
* @return Filtered and scored list of (Mas, score) pairs.
220+
*/
85221
std::vector<std::pair<Mas, double>> filter_available_cores_suppression_application(std::vector<std::pair<Magnetic, double>>* magnetics, Inputs inputs, std::map<CoreAdviserFilters, double> weights, size_t maximumMagneticsAfterFiltering, size_t maximumNumberResults);
86-
std::vector<std::pair<Mas, double>> filter_standard_cores_power_application(std::vector<std::pair<Magnetic, double>>* magnetics, Inputs inputs, size_t maximumMagneticsAfterFiltering, size_t maximumNumberResults);
87-
std::vector<std::pair<Mas, double>> filter_standard_cores_interference_suppression_application(std::vector<std::pair<Magnetic, double>>* magnetics, Inputs inputs, size_t maximumMagneticsAfterFiltering, size_t maximumNumberResults);
222+
223+
/**
224+
* @brief Filter pipeline for standard core shapes (with material selection).
225+
*
226+
* Similar to filter_available_cores_power_application but works with
227+
* parametric core shapes rather than specific stock cores.
228+
*/
229+
std::vector<std::pair<Mas, double>> filter_standard_cores_power_application(std::vector<std::pair<Magnetic, double>>* magnetics, Inputs inputs, std::map<CoreAdviserFilters, double> weights, size_t maximumMagneticsAfterFiltering, size_t maximumNumberResults);
230+
231+
/**
232+
* @brief Filter pipeline for standard core shapes in suppression applications.
233+
*/
234+
std::vector<std::pair<Mas, double>> filter_standard_cores_interference_suppression_application(std::vector<std::pair<Magnetic, double>>* magnetics, Inputs inputs, std::map<CoreAdviserFilters, double> weights, size_t maximumMagneticsAfterFiltering, size_t maximumNumberResults);
88235
std::vector<std::pair<Magnetic, double>> create_magnetic_dataset(Inputs inputs, std::vector<Core>* cores, bool includeStacks);
89236
std::vector<std::pair<Magnetic, double>> create_magnetic_dataset(Inputs inputs, std::vector<CoreShape>* shapes, bool includeStacks);
90237
void expand_magnetic_dataset_with_stacks(Inputs inputs, std::vector<Core>* cores, std::vector<std::pair<Magnetic, double>>* magnetics);

0 commit comments

Comments
 (0)