Adds inverter AC/DC efficiency and break-even penalty (#888)
Some checks failed
Bump Version / Bump Version Workflow (push) Has been cancelled
docker-build / platform-excludes (push) Has been cancelled
docker-build / build (push) Has been cancelled
docker-build / merge (push) Has been cancelled
pre-commit / pre-commit (push) Has been cancelled
Run Pytest on Pull Request / test (push) Has been cancelled

* feat: add inverter AC/DC efficiency and break-even penalty

* test: update tests/test_geneticoptimize.py with new ac_charge_break_even parameter

* docs: update documentation

* chore: update version numbers in configuration files to v0.2.0.dev2602272006923535
This commit is contained in:
Christopher Nadler
2026-02-27 23:12:08 +01:00
committed by GitHub
parent 04420e66ab
commit 3ccc25d731
30 changed files with 3043 additions and 152 deletions

View File

@@ -193,6 +193,44 @@ class InverterCommonSettings(DevicesBaseSettings):
},
)
ac_to_dc_efficiency: float = Field(
default=1.0,
ge=0,
le=1,
json_schema_extra={
"description": (
"Efficiency of AC to DC conversion for grid-to-battery AC charging (0-1). "
"Set to 0 to disable AC charging. Default 1.0 (no additional inverter loss)."
),
"examples": [0.95, 1.0, 0.0],
},
)
dc_to_ac_efficiency: float = Field(
default=1.0,
gt=0,
le=1,
json_schema_extra={
"description": (
"Efficiency of DC to AC conversion for battery discharging to AC load/grid (0-1). "
"Default 1.0 (no additional inverter loss)."
),
"examples": [0.95, 1.0],
},
)
max_ac_charge_power_w: Optional[float] = Field(
default=None,
ge=0,
json_schema_extra={
"description": (
"Maximum AC charging power in watts. "
"null means no additional limit. Set to 0 to disable AC charging."
),
"examples": [None, 0, 5000],
},
)
@computed_field # type: ignore[prop-decorator]
@property
def measurement_keys(self) -> Optional[list[str]]:

View File

@@ -26,6 +26,9 @@ class Inverter:
self.max_power_wh = (
self.parameters.max_power_wh
) # Maximum power that the inverter can handle
self.dc_to_ac_efficiency = self.parameters.dc_to_ac_efficiency
self.ac_to_dc_efficiency = self.parameters.ac_to_dc_efficiency
self.max_ac_charge_power_w = self.parameters.max_ac_charge_power_w
def process_energy(
self, generation: float, consumption: float, hour: int
@@ -35,6 +38,9 @@ class Inverter:
grid_import = 0.0
self_consumption = 0.0
# Cache inverter DC→AC efficiency for discharge path
dc_to_ac_eff = self.dc_to_ac_efficiency
if generation >= consumption:
if consumption > self.max_power_wh:
# If consumption exceeds maximum inverter power
@@ -56,21 +62,29 @@ class Inverter:
if remaining_load_evq > 0:
# Akku muss den Restverbrauch decken
if self.battery:
from_battery, discharge_losses = self.battery.discharge_energy(
remaining_load_evq, hour
# Request more DC from battery to account for DC→AC conversion loss
dc_request = remaining_load_evq / dc_to_ac_eff
from_battery_dc, discharge_losses = self.battery.discharge_energy(
dc_request, hour
)
remaining_load_evq -= from_battery # Restverbrauch nach Akkuentladung
losses += discharge_losses
# Convert DC output to AC
from_battery_ac = from_battery_dc * dc_to_ac_eff
inverter_discharge_losses = from_battery_dc - from_battery_ac
remaining_load_evq -= from_battery_ac
losses += discharge_losses + inverter_discharge_losses
else:
from_battery_ac = 0.0
# Wenn der Akku den Restverbrauch nicht vollständig decken kann, wird der Rest ins Netz gezogen
if remaining_load_evq > 0:
grid_import += remaining_load_evq
remaining_load_evq = 0
else:
from_battery = 0.0
from_battery_ac = 0.0
if remaining_power > 0:
# Load battery with excess energy
# Load battery with excess energy (DC path, no inverter conversion needed)
charge_losses = 0.0
if self.battery:
charged_energie, charge_losses = self.battery.charge_energy(
remaining_power, hour
@@ -88,7 +102,7 @@ class Inverter:
losses += charge_losses
self_consumption = (
consumption + from_battery
consumption + from_battery_ac
) # Self-consumption is equal to the load
else:
@@ -98,15 +112,21 @@ class Inverter:
# Discharge battery to cover shortfall, if possible
if self.battery:
battery_discharge, discharge_losses = self.battery.discharge_energy(
min(shortfall, available_ac_power), hour
# Need shortfall in AC, request more DC from battery for DC→AC conversion
ac_needed = min(shortfall, available_ac_power)
dc_request = ac_needed / dc_to_ac_eff
battery_discharge_dc, discharge_losses = self.battery.discharge_energy(
dc_request, hour
)
losses += discharge_losses
# Convert DC output to AC
battery_discharge_ac = battery_discharge_dc * dc_to_ac_eff
inverter_discharge_losses = battery_discharge_dc - battery_discharge_ac
losses += discharge_losses + inverter_discharge_losses
else:
battery_discharge = 0
battery_discharge_ac = 0
# Draw remaining required power from the grid (discharge_losses are already substraved in the battery)
grid_import = shortfall - battery_discharge
self_consumption = generation + battery_discharge
# Draw remaining required power from the grid (discharge_losses are already subtracted in the battery)
grid_import = shortfall - battery_discharge_ac
self_consumption = generation + battery_discharge_ac
return grid_export, grid_import, losses, self_consumption