248 lines
8.9 KiB
Python
248 lines
8.9 KiB
Python
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'),
|
|
Is_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)
|
|
- Is_max: Maximum allowed secondary current (A)
|
|
- 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,
|
|
Is_max=Is_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,
|
|
Is_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
|
|
|
|
if sim["Is_rms"] > Is_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
|