"""
Inverter module for PV system sizing and efficiency.
This module handles:
- Inverter sizing based on PV array power
- DC/AC coupling configurations
- Efficiency calculations
"""
from dataclasses import dataclass
from typing import Optional
[docs]
@dataclass
class InverterConfig:
"""
Inverter configuration parameters.
Attributes:
nominal_power_w: Inverter nominal AC power (W). If None, sized from PV.
dc_ac_ratio: DC/AC sizing ratio (typical: 1.1-1.25)
inverter_efficiency: Peak inverter efficiency (typical: 0.96-0.98)
is_hybrid: Whether this is a hybrid inverter with battery support
mppt_channels: Number of MPPT channels
cost_per_kw_simple: Cost per kW for simple (grid-tie) inverter
cost_per_kw_hybrid: Cost per kW for hybrid inverter (with battery)
"""
nominal_power_w: Optional[float] = None
dc_ac_ratio: float = 1.25 # Default 1.25
inverter_efficiency: float = 0.96
is_hybrid: bool = True
mppt_channels: int = 2
cost_per_kw_simple: float = 48.37 # €/kW for simple grid-tie inverter
cost_per_kw_hybrid: float = 102.58 # €/kW for hybrid inverter
[docs]
def size_from_pv(self, pv_peak_power_w: float) -> float:
"""
Size inverter based on PV peak power.
Args:
pv_peak_power_w: Total PV array peak power (Wp)
Returns:
Inverter nominal AC power (W)
"""
return pv_peak_power_w / self.dc_ac_ratio
[docs]
def get_cost(self, pv_peak_power_w: Optional[float] = None) -> float:
"""
Calculate inverter cost.
Args:
pv_peak_power_w: PV peak power for sizing (uses nominal_power if provided)
Returns:
Inverter cost in €
"""
if self.nominal_power_w is not None:
power = self.nominal_power_w
elif pv_peak_power_w is not None:
power = self.size_from_pv(pv_peak_power_w)
else:
raise ValueError("Either nominal_power_w or pv_peak_power_w must be provided")
cost_per_kw = self.cost_per_kw_hybrid if self.is_hybrid else self.cost_per_kw_simple
power_kw = power / 1000 # Convert W to kW
return power_kw * cost_per_kw
@dataclass(frozen=True)
class InverterConversionResult:
"""AC conversion result with explicit DC-side clipping bookkeeping."""
ac_power_w: float
conversion_loss_w: float
clipping_loss_dc_w: float
clipping_loss_ac_equivalent_w: float
@property
def total_dc_input_w(self) -> float:
"""DC input reconstructed from AC output, conversion loss, and clipping."""
return self.ac_power_w + self.conversion_loss_w + self.clipping_loss_dc_w
# Common inverter presets
INVERTER_PRESETS = {
"residential_hybrid": InverterConfig(
dc_ac_ratio=1.25,
inverter_efficiency=0.96,
is_hybrid=True,
),
"residential_simple": InverterConfig(
dc_ac_ratio=1.25,
inverter_efficiency=0.96,
is_hybrid=False,
),
"commercial_hybrid": InverterConfig(
dc_ac_ratio=1.25,
inverter_efficiency=0.98,
is_hybrid=True,
),
"oversized_1.5": InverterConfig(
dc_ac_ratio=1.5,
inverter_efficiency=0.96,
is_hybrid=True,
),
}
[docs]
def get_inverter_preset(name: str) -> InverterConfig:
"""
Get a pre-defined inverter configuration.
Available presets:
- residential_hybrid: 1.25 ratio, 0.96 efficiency, hybrid
- residential_simple: 1.25 ratio, 0.96 efficiency, grid-tie only
- commercial_hybrid: 1.25 ratio, 0.98 efficiency, hybrid
- oversized_1.5: 1.5 ratio for high DC/AC
Args:
name: Preset name
Returns:
InverterConfig object
"""
if name not in INVERTER_PRESETS:
available = ", ".join(INVERTER_PRESETS.keys())
raise KeyError(f"Preset '{name}' not found. Available: {available}")
return INVERTER_PRESETS[name]
def calculate_dc_ac_power(
pv_dc_power: float, inverter_ac_power: float, inverter_efficiency: float = 0.96
) -> InverterConversionResult:
"""
Calculate AC output and loss buckets for a DC-to-AC inverter.
Clipping is reported on the DC side: power above the DC input required
to saturate the AC rating is ``clipping_loss_dc_w``. The AC-equivalent
clipping value is also exposed for reports that compare against
``pv_dc_power * inverter_efficiency``.
Args:
pv_dc_power: DC power from PV array (W)
inverter_ac_power: Inverter AC rating (W)
inverter_efficiency: Inverter efficiency at MPP
Returns:
InverterConversionResult with AC output and loss buckets.
"""
pv_dc_power = max(0.0, float(pv_dc_power))
inverter_ac_power = max(0.0, float(inverter_ac_power))
inverter_efficiency = min(1.0, max(0.0, float(inverter_efficiency)))
if inverter_efficiency <= 0.0 or inverter_ac_power <= 0.0:
return InverterConversionResult(
ac_power_w=0.0,
conversion_loss_w=0.0,
clipping_loss_dc_w=pv_dc_power,
clipping_loss_ac_equivalent_w=0.0,
)
theoretical_ac = pv_dc_power * inverter_efficiency
ac_power = min(theoretical_ac, inverter_ac_power)
dc_used = min(pv_dc_power, inverter_ac_power / inverter_efficiency)
clipping_loss_dc = max(0.0, pv_dc_power - dc_used)
conversion_loss = max(0.0, dc_used - ac_power)
clipping_loss_ac_equiv = max(0.0, theoretical_ac - ac_power)
return InverterConversionResult(
ac_power_w=ac_power,
conversion_loss_w=conversion_loss,
clipping_loss_dc_w=clipping_loss_dc,
clipping_loss_ac_equivalent_w=clipping_loss_ac_equiv,
)