Skip to content

Commit bbd5850

Browse files
AlfVIIclaude
andcommitted
test(characterisation): re-baseline drifted snapshots after 2026-06 adviser reworks (ABT #10)
The [characterisation] suite had 11 stale snapshots (all captured 2026-05-19) after the saturation reclassification + isat gate, the proximity-aware single-winding re-rank, and the loss/saturation recompute landed on main. Each drift was verified as an intended consequence before re-pinning: - MagneticFilter SATURATION valid true->false: the 40+20-turn fixture is now classified as an inductor (no isolation sides) and gated by gap-aware isat; an ungapped ferrite at ~1.73 A correctly fails. ENERGY_STORED/TEMPERATURE drift from the same reclassified flux recompute. - CoreAdviser AVAILABLE/STANDARD POWER: proximity-aware re-rank promotes EP 20 over PQ 20/20 / PQ 27/15 on real winding-proximity loss (intended, ~1.6%). - MagneticAdviser 3W 507 kHz: the under-turned E 8.3/4 (10 turns) now fails the saturation gate; the 20-turn E 10/3 wins. Valid pool thinned to 2 so slot 2 is an INVALID-penalised candidate (documented thin-pool fallback). - CoreCrossReferencer DEFAULT/SAME_MATERIAL/ONLY_TDK: powder re-shuffle below a stable top-1 ferrite + sub-0.3% score nudges. - CoilAdviser flyback top[1]: benign within-class reinforced-wire re-rank (ABT #3). - CoreAdviser AVAILABLE x INTERFERENCE_SUPPRESSION: re-baselined to the SAME three XFlux 26 toroids as the 2026-05-19 baseline now that the suppression returns-0 fix restores candidate flow (see preceding 3 commits); scores nudged < 0.6%. Full [characterisation] suite green (314 assertions, 55 cases); [smoke-test] gate unaffected. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent cb4612b commit bbd5850

5 files changed

Lines changed: 98 additions & 53 deletions

tests/TestCoilAdviserCharacterisation.cpp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,10 +164,16 @@ void check_structural_topN(const std::string& label,
164164
// Note: requested top-3 but pipeline only produces 2 candidates for this
165165
// fixture — locked as the current behaviour. If a future refactor unlocks
166166
// more candidates this snapshot will fail loudly and need regeneration.
167+
// Refreshed 2026-06-16 (ABT #10 / ABT #3): top[1] primary re-ranked from
168+
// Round T20A01TXXX-1 to Round T25A01TXXX-1.5 (12 layers, was 10). Both are
169+
// insulated 3-layer reinforced wires (bdv 4500 → 6000) — the expected wire
170+
// class for this BASIC isolated flyback — so this is a benign within-class
171+
// re-rank from the wire-scoring shift, verified pre-existing in ABT #3, not a
172+
// regression. top[0] unchanged.
167173
const std::vector<CoilStructural> kTopFlyback = {
168174
// wires sections layers totalTurns
169175
{"Round T25A01PXXX-1.5 || Litz SXXL825/44FX-3(MWXX)", 8, 12, 87},
170-
{"Round T20A01TXXX-1 || Litz SXXL825/44FX-3(MWXX)", 8, 10, 87},
176+
{"Round T25A01TXXX-1.5 || Litz SXXL825/44FX-3(MWXX)", 8, 12, 87},
171177
};
172178

173179
} // namespace

tests/TestCoreAdviserCharacterisation.cpp

Lines changed: 36 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -167,19 +167,22 @@ void check_top_n(const std::string& label,
167167
// Baselines captured 2026-05-19. Top entries are ordering-sensitive; scores
168168
// are within 1e-6 relative tolerance.
169169
// ----------------------------------------------------------------------------
170-
// Refreshed after the saturation-margin default flipped 1.0 → 1.2
171-
// (Maniktala Ch.5 production safety). Slot 2 now hosts an EP 20 3C91
172-
// gapped 0.605 mm that previously scored below the EFD 25/13/9 entry —
173-
// at margin 1.2 the EFD candidate hits the saturation-rejection gate
174-
// for its higher Bpeak/Bsat ratio and drops out of the top-5. PQ 20/20
175-
// 3C97 stays top-1 with a small score shift (4.030 → 3.929) reflecting
176-
// the tightened acceptance window.
170+
// Refreshed 2026-06-16 (ABT #10) after the proximity-aware re-rank landed
171+
// (5bd45414 "feat(core-adviser): proximity-aware re-rank for single-winding
172+
// inductors"). This POWER query is a single-winding inductor, so the adviser
173+
// now winds+simulates the top-K and re-orders them by proximity-inclusive
174+
// loss. That promotes EP 20 3C91 0.605 mm from slot 2 → slot 1 (its score
175+
// jumps 3.830 → 3.992 once its lower real winding-proximity loss is counted)
176+
// while PQ 20/20 3C97 is essentially unchanged (3.929 → 3.930) and slips to
177+
// slot 2. The Kool Mµ Hƒ 40 toroid also enters the top-5. EP 20 winning by
178+
// ~1.6 % over PQ 20/20 is the more-accurate ranking, not a regression.
179+
// (Previous refresh was the saturation-margin 1.0 → 1.2 flip.)
177180
const std::vector<TopEntry> kTopAvailablePower = {
178-
{"PQ 20/20 - 3C97 - Gapped 0.477 mm", 3.9291496909131993},
179-
{"EP 20 - 3C91 - Gapped 0.382 mm", 3.8653763355963369},
180-
{"EP 20 - 3C91 - Gapped 0.605 mm", 3.8298752301770511},
181-
{"T 21/12/7.1 - Kool Mµ Hƒ 60 - Ungapped", 3.8224660990687345},
182-
{"PQ 20/20 - 3C95 - Gapped 0.477 mm", 3.8009258949850238},
181+
{"EP 20 - 3C91 - Gapped 0.605 mm", 3.9918167530621829},
182+
{"PQ 20/20 - 3C97 - Gapped 0.477 mm", 3.9301728472909492},
183+
{"T 21/12/7.1 - Kool Mµ Hƒ 40 - Ungapped", 3.8811377987041928},
184+
{"T 21/12/7.1 - Kool Mµ Hƒ 60 - Ungapped", 3.8604853384926003},
185+
{"PQ 20/20 - 3C95 - Gapped 0.477 mm", 3.8580496485195876},
183186
};
184187

185188
// STANDARD_CORES x POWER: top-5 unique standard-shape ferrite candidates.
@@ -189,18 +192,31 @@ const std::vector<TopEntry> kTopAvailablePower = {
189192
// (previously slots 0-2 were three identical copies of
190193
// "95 PQ 27/15 gapped 0.36 mm"). See CoreAdviser.cpp get_advised_core
191194
// dispatch path for the canonical-name dedupe.
195+
// Refreshed 2026-06-16 (ABT #10): same proximity-aware re-rank as the
196+
// AVAILABLE_CORES_POWER table above promotes 95 EP 20 (gapped 0.32 mm) to
197+
// slot 1 over 95 PQ 27/15, and the post-rerank gap selection on PQ 27/15
198+
// settles at 0.18 mm. Single-winding-inductor re-rank, intended.
192199
const std::vector<TopEntry> kTopStandardPower = {
193-
{"95 PQ 27/15 gapped 0.36 mm", 3.8839874324577632},
194-
{"95 EQ 26/19/7 gapped 0.39 mm", 3.6405809245945537},
195-
{"95 PQ 26/20 gapped 0.39 mm", 3.4445103127181436},
196-
{"95 RM 12/17 gapped 0.36 mm", 3.4194800188185512},
197-
{"95 PQ 28/20 gapped 0.38 mm", 3.3998371194660173},
200+
{"95 EP 20 gapped 0.32 mm", 3.9366311157847029},
201+
{"95 PQ 27/15 gapped 0.18 mm", 3.8943328027343398},
202+
{"95 RM 10/ILP gapped 0.24 mm", 3.8317384650663673},
203+
{"95 E 21/9/5 2 stacks gapped 0.21 mm", 3.8316279736954892},
204+
{"98 RM 10/ILP gapped 0.24 mm", 3.7865863909666828},
198205
};
199206

207+
// Refreshed 2026-06-16 (ABT #10) after landing the suppression returns-0 fix
208+
// (3-layer: a905fd2 pipeline pass-through + 957c516 unscorable-material +
209+
// 3be26c6 cross-ref loss-skip, cherry-picked from
210+
// feat/wideband-impedance-resonances). Before the fix this scenario returned
211+
// ZERO candidates on main — the losses filter rejected/threw on every complex-
212+
// permeability suppression ferrite and the adviser produced an empty set. With
213+
// the fix the SAME three cores as the 2026-05-19 baseline flow again (XFlux 26
214+
// toroids, identical identities + order); only the scores nudged < 0.6 % from
215+
// the 2026-06 loss/saturation recompute.
200216
const std::vector<TopEntry> kTopAvailableInterference = {
201-
{"T 134/77/155 - XFlux 26 - Ungapped", 1.9491685553595064},
202-
{"T 134/77/78 - XFlux 26 - Ungapped", 1.8605239944178327},
203-
{"T 167/87/27 - XFlux 26 - Ungapped", 1.5147561009882304},
217+
{"T 134/77/155 - XFlux 26 - Ungapped", 1.9451312382492831},
218+
{"T 134/77/78 - XFlux 26 - Ungapped", 1.8563034258168774},
219+
{"T 167/87/27 - XFlux 26 - Ungapped", 1.5057090764821521},
204220
};
205221

206222
} // namespace

tests/TestCoreCrossReferencerCharacterisation.cpp

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -112,31 +112,41 @@ void check_top_n(const std::string& label,
112112
// contribution downward by ~3 % for several powder toroids. The top-1 ferrite
113113
// is unaffected; powder slots 1-4 keep the same identities and order but
114114
// with lower scores.
115+
// Refreshed 2026-06-16 (ABT #10): the 2026-06 saturation/loss recompute
116+
// reshuffled the powder cross-references below the (unchanged, score-stable)
117+
// EC 35/17/10 3C94 top-1 ferrite — Kool Mµ MAX 75 rises to slot 1, Edge 75
118+
// drops to slot 2, and two T 27 parylene powders (OE 75 / Mix 66) displace
119+
// the former Kool Mµ 75 / XFlux 75 in slots 3-4. The actual cross-reference
120+
// winner (the ferrite) is unchanged; only the powder ordering moved.
115121
const std::vector<TopEntry> kTopDefault = {
116122
{"EC 35/17/10 - 3C94 - Gapped 1.000 mm", 3.0915469239085107},
117-
{"T 28/14/12 - epoxy coated - Edge 75 - Ungapped", 2.5332385934426584},
118-
{"T 28/14/12 - epoxy coated - Kool Mµ 75 - Ungapped", 2.4637902771095823},
119-
{"T 28/14/12 - epoxy coated - Kool Mµ MAX 75 - Ungapped", 2.3482690546043852},
120-
{"T 28/14/12 - epoxy coated - XFlux 75 - Ungapped", 2.02063120011971},
123+
{"T 28/14/12 - epoxy coated - Kool Mµ MAX 75 - Ungapped", 2.5524218280737472},
124+
{"T 28/14/12 - epoxy coated - Edge 75 - Ungapped", 2.4306644789891148},
125+
{"T 27/14.7/14.0 - parylene coated - OE 75 - Ungapped", 2.3440327755042603},
126+
{"T 27/14.5/14.6 - parylene coated - Mix 66 - Ungapped", 2.052278908151667},
121127
};
122128

129+
// Refreshed 2026-06-16 (ABT #10): identical ordering, scores nudged < 0.3 %
130+
// by the 2026-06 loss/saturation recompute.
123131
const std::vector<TopEntry> kTopSameMaterial = {
124-
{"EP 20 - 3C91 - Gapped 0.605 mm", 2.3260884808139326},
125-
{"EC 41/19/12 - 3C91 - Gapped 1.000 mm", 2.2891873964853477},
126-
{"EP 17 - 3C91 - Gapped 0.414 mm", 1.6374042631675698},
127-
{"RM 8/I - 3C91 - Gapped 0.480 mm", 1.5642380256468966},
128-
{"EP 17 - 3C91 - Gapped 0.255 mm", 1.414304619475435},
132+
{"EP 20 - 3C91 - Gapped 0.605 mm", 2.326855381298027},
133+
{"EC 41/19/12 - 3C91 - Gapped 1.000 mm", 2.2856989762412012},
134+
{"EP 17 - 3C91 - Gapped 0.414 mm", 1.6353493768514515},
135+
{"RM 8/I - 3C91 - Gapped 0.480 mm", 1.5594438851744803},
136+
{"EP 17 - 3C91 - Gapped 0.255 mm", 1.4153519258586909},
129137
};
130138

131139
// Refreshed 2026-05-24: 3c851744 reshuffled slots 1 and 4 — the
132140
// EC 35/17/10 distributed-gap N27 moved from slot 4 up past
133141
// E 32/16/9 N87, which dropped to slot 4. ETD 29/16/10 stays on top.
142+
// Refreshed 2026-06-16 (ABT #10): identical ordering, scores nudged < 0.1 %
143+
// by the 2026-06 loss/saturation recompute.
134144
const std::vector<TopEntry> kTopOnlyTdk = {
135-
{"ETD 29/16/10 - N87 - Gapped 1.000 mm", 2.8472835377128982},
136-
{"EC 35/17/10 - N27 - Distributed gapped 0.500 mm", 2.6366007138718883},
137-
{"ETD 29/16/10 - N27 - Gapped 1.000 mm", 2.6094871648192499},
138-
{"ETD 29/16/10 - N27 - Distributed gapped 0.500 mm", 2.5227157465982319},
139-
{"E 32/16/9 - N27 - Gapped 1.000 mm", 1.9199033881211673},
145+
{"ETD 29/16/10 - N87 - Gapped 1.000 mm", 2.8471783659710788},
146+
{"EC 35/17/10 - N27 - Distributed gapped 0.500 mm", 2.6358944598653742},
147+
{"ETD 29/16/10 - N27 - Gapped 1.000 mm", 2.609290926957466},
148+
{"ETD 29/16/10 - N27 - Distributed gapped 0.500 mm", 2.5223183281041655},
149+
{"E 32/16/9 - N27 - Gapped 1.000 mm", 1.9187849608198064},
140150
};
141151

142152
// Phase 1 fix landed: with SATURATION now active, the powder ordering

tests/TestMagneticAdviserCharacterisation.cpp

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -154,22 +154,27 @@ void check_top_n(const std::string& label,
154154
// every winding here now resolves to Single Build. Top-2 also swapped
155155
// rankings because the new wire selection changed the magnetic-level
156156
// scoring.
157-
// Refreshed after the saturation-margin default flipped 1.0 → 1.2.
158-
// At the tighter Bpeak·1.2 ≤ Bsat gate, the 8/2 3-stack candidate is
159-
// rejected (saturation-edge under no-derate, over-margin under 1.2);
160-
// the next-larger 8.3/4 2-stack candidate wins both rectifier rounds
161-
// and the slightly larger 10/3 fills the third slot at a lower score
162-
// (margin pushes its loss budget tighter too).
157+
// Refreshed 2026-06-16 (ABT #10) after the 2026-06 saturation rework
158+
// (5000909f inductor reclassification + 60fe7c79 gap-aware isat gate). The
159+
// former winner — 96 E 8.3/4 2 stacks at only 10 turns — now FAILS the
160+
// saturation gate (under-turned → B_peak too high), so the adviser promotes
161+
// the 79 E 10/3 at 20 turns, which has the headroom to stay valid (top-1
162+
// score exactly 2.0). NOTE: the stricter gate thinned the valid 3-winding
163+
// pool for this 507 kHz spec to two designs, so slot 2 is now an
164+
// INVALID-penalised candidate (score 0.85) — that is the magnetic adviser's
165+
// documented thin-pool fallback, not a silenced failure; top-1/top-2 are
166+
// both valid. If the valid pool should be deeper here, that is a separate
167+
// investigation (the saturation gate is correct; the query is demanding).
163168
const std::vector<MagneticEntry> kTopThreeWinding = {
164-
{"96 E 8.3/4 2 stacks, Turns: 10, Order: 102, Non-Interleaved, Margin Taped 00",
165-
"Round 33.0 - Single Build || Round 38.0 - Single Build || Round 38.0 - Single Build",
166-
1.7391079152509736},
167-
{"96 E 8.3/4 2 stacks, Turns: 10, Order: 102, Non-Interleaved, Margin Taped 01",
168-
"Round 33.0 - Single Build || Round 38.0 - Heavy Build || Round 38.0 - Single Build",
169-
1.5356432403340934},
170-
{"96 E 10/3, Turns: 23, Order: 012, Non-Interleaved, Margin Taped 00",
171-
"Round 33.0 - Single Build || Round 41.0 - Single Build || Round 41.0 - Heavy Build",
172-
1.1470545003241872},
169+
{"79 E 10/3, Turns: 20, Order: 012, Non-Interleaved, Margin Taped 00",
170+
"Round 33.0 - Single Build || Round 41.0 - Single Build || Round 41.0 - Single Build",
171+
2.0},
172+
{"79 E 10/3, Turns: 20, Order: 012, Non-Interleaved, Margin Taped 01",
173+
"Round 33.0 - Single Build || Round 41.0 - Heavy Build || Round 41.0 - Single Build",
174+
1.8855006474886866},
175+
{"INVALID (failed validity filters): 95 E 8.3/4 2 stacks, Turns: 34, Order: 102, Interleaved, Margin Taped 01",
176+
"Round 39.0 - Single Build || Round 44.0 - Single Build || Round 43.5 - Single Build",
177+
0.84999999999999998},
173178
};
174179

175180
} // namespace

tests/TestMagneticFilter.cpp

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,15 @@ const std::map<std::string, Snapshot> kSnapshots = {
140140
// Round 2.00 - Grade 1 wire, 100 kHz triangular, ±√3 A, 25 °C.
141141
// ---------------------------------------------------------------
142142
{"AREA_PRODUCT", {true, 1.8374733304713626e-08}},
143-
{"ENERGY_STORED", {true, 0.00018855126491439841}},
143+
// ENERGY_STORED / TEMPERATURE / SATURATION refreshed 2026-06-16 (ABT #10).
144+
// The reference 40+20-turn fixture has no isolation sides set, so the
145+
// 2026-06 saturation rework now classifies it as an INDUCTOR
146+
// (5000909f "classify all-one-isolation-side magnetics as inductors")
147+
// and gates it by gap-aware saturation current (60fe7c79). An ungapped
148+
// E35 ferrite at ~1.73 A peak has isat << margin·ipeak, so SATURATION
149+
// flips valid true→false (was {true, 0.1185}). ENERGY_STORED (-0.4 %) and
150+
// TEMPERATURE (-1.4 %) drift from the same reclassified flux/B recompute.
151+
{"ENERGY_STORED", {true, 0.00018781148528340766}},
144152
{"ESTIMATED_COST", {true, 1.7774692926005085}},
145153
{"COST", {true, 7.0}},
146154
// CORE_AND_DC_LOSSES returns (false, 0) here because the reference
@@ -160,7 +168,7 @@ const std::map<std::string, Snapshot> kSnapshots = {
160168
{"PROXIMITY_FACTOR", {false, 0.0}},
161169
{"TURNS_RATIOS", {true, 0.0}},
162170
{"MAXIMUM_DIMENSIONS", {true, 0.0}},
163-
{"SATURATION", {true, 0.11848164108564301}},
171+
{"SATURATION", {false, 0.0}}, // 2026-06-16 ABT #10: now isat-gated inductor, rejected (see ENERGY_STORED note)
164172
{"DC_CURRENT_DENSITY", {false, 0.0}},
165173
{"EFFECTIVE_CURRENT_DENSITY", {false, 0.0}},
166174
{"IMPEDANCE", {true, 0.0}},
@@ -174,7 +182,7 @@ const std::map<std::string, Snapshot> kSnapshots = {
174182
// LEAKAGE_INDUCTANCE no longer returns DBL_MAX sentinel — the error path
175183
// is now a throws-contract test below (see TEST_CASE "LEAKAGE_INDUCTANCE
176184
// throws on missing turns description"). No snapshot entry needed.
177-
{"TEMPERATURE", {true, 27.548577138623852}},
185+
{"TEMPERATURE", {true, 27.158311798960909}}, // 2026-06-16 ABT #10: -1.4 % from reclassified B recompute
178186
{"TURN_COUNT", {true, 2.1000000000000001}},
179187
// FRINGING_FACTOR returns score=1.0 on every non-crashing path
180188
// (MagneticFilter.cpp:2079, 2082, 2089, 2092). After fix A the factory

0 commit comments

Comments
 (0)