from typing import Tuple, Optional, Dict, List from dataclasses import dataclass from model import TransformerModel import itertools @dataclass class OptimizationResult: """Result of transformer optimization.""" # Optimal configuration primary_tap: int secondary_tap: int Vp_rms: float efficiency: float # Operating point details Np_eff: int Ns_eff: int turns_ratio: float B_peak_T: float Vs_rms: float Ip_rms: float Is_rms: float P_out_W: float P_in_W: float P_cu_W: float P_cu_primary_W: float P_cu_secondary_W: float P_core_W: float # Constraint satisfaction power_error_percent: float voltage_used: float flux_density_T: float class TransformerOptimizer: """ Optimizer for finding best transformer configuration. Searches across primary taps, secondary taps, and input voltages to: - Deliver desired power to the load - Maximize efficiency - Stay within flux density and voltage constraints """ def __init__(self, model: TransformerModel): self.model = model def optimize( self, load_ohms: float, target_power_W: float, freq_hz: float, Vp_min: float = 5.0, Vp_max: float = 50.0, Vp_step: float = 0.5, B_max_T: float = 0.3, Vs_max: float = float('inf'), core_loss_W: float = 0.0, power_tolerance_percent: float = 2.0, fallback_max_power: bool = True, ) -> Optional[OptimizationResult]: """ Find optimal transformer configuration for given constraints. Arguments: - load_ohms: Resistive load (ohms) - target_power_W: Desired output power (W) - freq_hz: Operating frequency (Hz) - Vp_min, Vp_max, Vp_step: Input voltage search range - B_max_T: Maximum allowed flux density (Tesla) - Vs_max: Maximum allowed secondary voltage (V) - core_loss_W: Core loss at this operating point - power_tolerance_percent: Acceptable power delivery error (%) - fallback_max_power: If True and target cannot be met, find max power with best efficiency Returns: - OptimizationResult with best configuration, or None if no valid solution """ if load_ohms <= 0: raise ValueError("Load must be > 0") if target_power_W <= 0: raise ValueError("Target power must be > 0") if Vp_min >= Vp_max: raise ValueError("Vp_min must be < Vp_max") # Generate all possible tap numbers primary_taps = list(range(1, len(self.model.primary_taps))) secondary_taps = list(range(1, len(self.model.secondary_taps))) if not primary_taps or not secondary_taps: raise ValueError("Need at least 2 taps on each winding") best_result = None best_efficiency = -1.0 # Fallback tracking: find max achievable power under target fallback_result = None fallback_max_power_achieved = -1.0 fallback_best_efficiency = -1.0 # Search over all tap combinations for p_tap, s_tap in itertools.product(primary_taps, secondary_taps): # For each tap combination, find the voltage that delivers target power result = self._optimize_voltage_for_tap( primary_tap=p_tap, secondary_tap=s_tap, load_ohms=load_ohms, target_power_W=target_power_W, freq_hz=freq_hz, Vp_min=Vp_min, Vp_max=Vp_max, Vp_step=Vp_step, B_max_T=B_max_T, Vs_max=Vs_max, core_loss_W=core_loss_W, power_tolerance_percent=power_tolerance_percent, fallback_max_power=fallback_max_power, ) if result is not None: power_error = abs(result.P_out_W - target_power_W) / target_power_W * 100 # Check if this meets the target power tolerance if power_error <= power_tolerance_percent: if result.efficiency > best_efficiency: best_efficiency = result.efficiency best_result = result # Track fallback: maximize power below target, then maximize efficiency elif fallback_max_power and result.P_out_W < target_power_W: if (result.P_out_W > fallback_max_power_achieved or (result.P_out_W == fallback_max_power_achieved and result.efficiency > fallback_best_efficiency)): fallback_max_power_achieved = result.P_out_W fallback_best_efficiency = result.efficiency fallback_result = result # Return best result if found, otherwise fallback return best_result if best_result is not None else fallback_result def _optimize_voltage_for_tap( self, primary_tap: int, secondary_tap: int, load_ohms: float, target_power_W: float, freq_hz: float, Vp_min: float, Vp_max: float, Vp_step: float, B_max_T: float, Vs_max: float, core_loss_W: float, power_tolerance_percent: float, fallback_max_power: bool, ) -> Optional[OptimizationResult]: """ For a given tap configuration, find the best voltage. In normal mode: returns highest efficiency that meets target power ± tolerance In fallback mode: returns highest efficiency at max achievable power below target Returns OptimizationResult if valid solution found, else None. """ best_within_tolerance = None best_efficiency_within_tolerance = -1.0 # For fallback: track max power achievable and best efficiency at that power max_power_below_target = -1.0 best_at_max_power = None best_efficiency_at_max_power = -1.0 # Generate voltage sweep import numpy as np voltages = np.arange(Vp_min, Vp_max + Vp_step/2, Vp_step) for Vp_rms in voltages: try: sim = self.model.simulate( primary_tap=primary_tap, secondary_tap=secondary_tap, Vp_rms=float(Vp_rms), freq_hz=freq_hz, load_ohms=load_ohms, core_loss_W=core_loss_W, ) except (ValueError, ZeroDivisionError): continue # Check constraints if sim["B_peak_T"] > B_max_T: continue if sim["Vs_rms"] > Vs_max: continue power_error = abs(sim["P_out_W"] - target_power_W) / target_power_W * 100 result = OptimizationResult( primary_tap=primary_tap, secondary_tap=secondary_tap, Vp_rms=float(Vp_rms), efficiency=sim["efficiency"], Np_eff=sim["Np_eff"], Ns_eff=sim["Ns_eff"], turns_ratio=sim["turns_ratio"], B_peak_T=sim["B_peak_T"], Vs_rms=sim["Vs_rms"], Ip_rms=sim["Ip_rms"], Is_rms=sim["Is_rms"], P_out_W=sim["P_out_W"], P_in_W=sim["P_in_W"], P_cu_W=sim["P_cu_W"], P_cu_primary_W=sim["P_cu_primary_W"], P_cu_secondary_W=sim["P_cu_secondary_W"], P_core_W=sim["P_core_W"], power_error_percent=power_error, voltage_used=float(Vp_rms), flux_density_T=sim["B_peak_T"], ) # Track solutions within tolerance if power_error <= power_tolerance_percent: if sim["efficiency"] > best_efficiency_within_tolerance: best_efficiency_within_tolerance = sim["efficiency"] best_within_tolerance = result # Track fallback: max power below target with best efficiency if fallback_max_power and sim["P_out_W"] < target_power_W: if sim["P_out_W"] > max_power_below_target: max_power_below_target = sim["P_out_W"] best_efficiency_at_max_power = sim["efficiency"] best_at_max_power = result elif sim["P_out_W"] == max_power_below_target and sim["efficiency"] > best_efficiency_at_max_power: best_efficiency_at_max_power = sim["efficiency"] best_at_max_power = result # Return best within tolerance if found, otherwise fallback return best_within_tolerance if best_within_tolerance is not None else best_at_max_power