@@ -16,7 +16,7 @@ namespace OpenMagnetics {
1616 // Static analytical helpers (CUK_PLAN.md §2.13, §4.1–§4.4)
1717 // ============================================================
1818
19- double Cuk::calculate_duty_cycle (double inputVoltage, double outputVoltageMagnitude, double diodeVoltageDrop, double efficiency, double turnsRatio) {
19+ double Cuk::calculate_duty_cycle (double inputVoltage, double outputVoltageMagnitude, double diodeVoltageDrop, double efficiency, double turnsRatio, double maximumDutyCycle ) {
2020 // Cuk CCM, ideal:
2121 // V1/V2 (n=1): M(D) = -D/(1-D)
2222 // V3 isolated: M(D) = -D / ((1-D) · n), n = Np/Ns (Flyback convention)
@@ -37,10 +37,14 @@ namespace OpenMagnetics {
3737 double effVin = inputVoltage * efficiency;
3838 double reflectedVo = (outputVoltageMagnitude + diodeVoltageDrop) * turnsRatio;
3939 double dutyCycle = reflectedVo / (reflectedVo + effVin);
40- if (dutyCycle >= 0.95 ) {
40+ // Explicit configurable maxD gate (no silent clamp). 1 % tolerance
41+ // absorbs design-requirements rounding.
42+ constexpr double dutyTolerance = 0.01 ;
43+ if (dutyCycle > maximumDutyCycle * (1.0 + dutyTolerance)) {
4144 throw InvalidInputException (ErrorCode::INVALID_INPUT ,
4245 " Cuk::calculate_duty_cycle: duty cycle " + std::to_string (dutyCycle) +
43- " >= 0.95 — converter would lose regulation; reduce |Vo| or raise Vin" );
46+ " exceeds maximumDutyCycle " + std::to_string (maximumDutyCycle) +
47+ " — converter would lose regulation; reduce |Vo|, raise Vin, or relax maximumDutyCycle." );
4448 }
4549 return dutyCycle;
4650 }
@@ -114,7 +118,7 @@ namespace OpenMagnetics {
114118 for (const auto & op : get_operating_points ()) {
115119 double Iout = op.get_output_currents ()[0 ];
116120 double Vo = std::abs (op.get_output_voltages ()[0 ]);
117- double D = calculate_duty_cycle (maximumInputVoltage, Vo, get_diode_voltage_drop (), efficiency, turnsRatio);
121+ double D = calculate_duty_cycle (maximumInputVoltage, Vo, get_diode_voltage_drop (), efficiency, turnsRatio, maximumDutyCycle. value_or ( 0.95 ) );
118122 double IL1avg = Iout * D / ((1.0 - D) * turnsRatio * efficiency);
119123 maximumDeltaIL1 = std::max (maximumDeltaIL1, rippleRatio * IL1avg);
120124 }
@@ -124,7 +128,7 @@ namespace OpenMagnetics {
124128 for (const auto & op : get_operating_points ()) {
125129 double Iout = op.get_output_currents ()[0 ];
126130 double Vo = std::abs (op.get_output_voltages ()[0 ]);
127- double D = calculate_duty_cycle (minimumInputVoltage, Vo, get_diode_voltage_drop (), efficiency, turnsRatio);
131+ double D = calculate_duty_cycle (minimumInputVoltage, Vo, get_diode_voltage_drop (), efficiency, turnsRatio, maximumDutyCycle. value_or ( 0.95 ) );
128132 double IL1avg = Iout * D / ((1.0 - D) * turnsRatio * efficiency);
129133 double IL2avg = Iout;
130134 // Switch-current peak: IS_pk = IL1avg + IL2avg/n + ripples/2.
@@ -141,7 +145,7 @@ namespace OpenMagnetics {
141145 for (const auto & op : get_operating_points ()) {
142146 double switchingFrequency = op.get_switching_frequency ();
143147 double Vo = std::abs (op.get_output_voltages ()[0 ]);
144- double D = calculate_duty_cycle (maximumInputVoltage, Vo, get_diode_voltage_drop (), efficiency, turnsRatio);
148+ double D = calculate_duty_cycle (maximumInputVoltage, Vo, get_diode_voltage_drop (), efficiency, turnsRatio, maximumDutyCycle. value_or ( 0.95 ) );
145149 double L1 = calculate_l1_min (maximumInputVoltage, D, maximumDeltaIL1, switchingFrequency);
146150 maximumNeededInductance = std::max (maximumNeededInductance, L1 );
147151 }
@@ -186,7 +190,7 @@ namespace OpenMagnetics {
186190 }
187191 lastTurnsRatio = turnsRatio;
188192
189- double dutyCycle = calculate_duty_cycle (inputVoltage, outputVoltageMag, diodeVoltageDrop, efficiency, turnsRatio);
193+ double dutyCycle = calculate_duty_cycle (inputVoltage, outputVoltageMag, diodeVoltageDrop, efficiency, turnsRatio, maximumDutyCycle. value_or ( 0.95 ) );
190194
191195 // Common derived quantities (V1/V2/V3).
192196 // Power balance: Vin·η·IL1avg = |Vo|·Iout ⇒ IL1avg = |Vo|·Iout/(Vin·η).
@@ -467,7 +471,7 @@ namespace OpenMagnetics {
467471 for (const auto & op : get_operating_points ()) {
468472 double Iout = op.get_output_currents ()[0 ];
469473 double Vo = std::abs (op.get_output_voltages ()[0 ]);
470- double D = calculate_duty_cycle (maximumInputVoltage, Vo, get_diode_voltage_drop (), efficiency);
474+ double D = calculate_duty_cycle (maximumInputVoltage, Vo, get_diode_voltage_drop (), efficiency, 1.0 , maximumDutyCycle. value_or ( 0.95 ) );
471475 double IL1avg = Iout * D / (1.0 - D);
472476 maximumDeltaIL1 = std::max (maximumDeltaIL1, rippleRatio * IL1avg);
473477 }
@@ -477,7 +481,7 @@ namespace OpenMagnetics {
477481 for (const auto & op : get_operating_points ()) {
478482 double Iout = op.get_output_currents ()[0 ];
479483 double Vo = std::abs (op.get_output_voltages ()[0 ]);
480- double D = calculate_duty_cycle (minimumInputVoltage, Vo, get_diode_voltage_drop (), efficiency);
484+ double D = calculate_duty_cycle (minimumInputVoltage, Vo, get_diode_voltage_drop (), efficiency, 1.0 , maximumDutyCycle. value_or ( 0.95 ) );
481485 double IL1avg = Iout * D / (1.0 - D);
482486 // IS_peak ≈ IL1avg + IL2avg + (ΔIL1 + ΔIL2)/2; treat ΔIL2 as
483487 // ~30 % of IL2avg (the L2 sizing default), so the L1 ripple
@@ -496,7 +500,7 @@ namespace OpenMagnetics {
496500 for (const auto & op : get_operating_points ()) {
497501 double switchingFrequency = op.get_switching_frequency ();
498502 double Vo = std::abs (op.get_output_voltages ()[0 ]);
499- double D = calculate_duty_cycle (maximumInputVoltage, Vo, get_diode_voltage_drop (), efficiency);
503+ double D = calculate_duty_cycle (maximumInputVoltage, Vo, get_diode_voltage_drop (), efficiency, 1.0 , maximumDutyCycle. value_or ( 0.95 ) );
500504 double L1 = calculate_l1_min (maximumInputVoltage, D, maximumDeltaIL1, switchingFrequency);
501505 maximumNeededInductance = std::max (maximumNeededInductance, L1 );
502506 }
@@ -538,7 +542,7 @@ namespace OpenMagnetics {
538542 double switchingFrequency = op.get_switching_frequency ();
539543 double Vo = std::abs (op.get_output_voltages ()[0 ]);
540544 double Iout = op.get_output_currents ()[0 ];
541- double D = calculate_duty_cycle (minimumInputVoltage, Vo, get_diode_voltage_drop (), efficiency, turnsRatio);
545+ double D = calculate_duty_cycle (minimumInputVoltage, Vo, get_diode_voltage_drop (), efficiency, turnsRatio, maximumDutyCycle. value_or ( 0.95 ) );
542546 double IL1avg = Iout * D / ((1.0 - D) * turnsRatio * efficiency);
543547 double VCa = minimumInputVoltage / (1.0 - D);
544548 double deltaIm_target = std::max (0.20 * IL1avg, 1e-6 );
@@ -719,7 +723,7 @@ namespace OpenMagnetics {
719723 double efficiency = 1.0 ;
720724 if (get_efficiency ()) efficiency = get_efficiency ().value ();
721725
722- double dutyCycle = calculate_duty_cycle (inputVoltage, outputVoltageMag, diodeVoltageDrop, efficiency);
726+ double dutyCycle = calculate_duty_cycle (inputVoltage, outputVoltageMag, diodeVoltageDrop, efficiency, 1.0 , maximumDutyCycle. value_or ( 0.95 ) );
723727
724728 // Internally-sized L2, C1, Co (mirror process_operating_points_for_input_voltage)
725729 double IL1avg = outputCurrent * dutyCycle / (1.0 - dutyCycle);
0 commit comments