|
42 | 42 | // unconditionally, so loading one against this Python segfaults on the first prediction rather than |
43 | 43 | // falling back. Bumping makes the loader reject it and use the Python engine, which is the whole |
44 | 44 | // point of the check. |
45 | | -#define PK_ABI_VERSION 4 |
46 | | -#define PK_PARITY_REVISION 7 |
| 45 | +#define PK_ABI_VERSION 5 |
| 46 | +#define PK_PARITY_REVISION 9 |
47 | 47 | #define PK_MAX_CARS 8 |
48 | 48 | #define PK_RUN_EVERY 5 // const.py RUN_EVERY |
49 | 49 |
|
@@ -169,6 +169,7 @@ struct PkContext { |
169 | 169 | double battery_loss; |
170 | 170 | double battery_loss_discharge; |
171 | 171 | double inverter_loss; |
| 172 | + double inverter_freeze_export_discharge_rate; // per-minute rate (multiplied by step in the kernel), residual battery-side discharge entering the AC balance during Freeze Export |
172 | 173 | double inverter_limit; // per-minute rate (multiplied by step in the kernel) |
173 | 174 | double export_limit; // per-minute rate |
174 | 175 | double pv_ac_limit; // per-minute rate |
@@ -714,6 +715,7 @@ static int32_t pk_run_one(const ContextStore *store, const PkScenario *s, PkResu |
714 | 715 | const double battery_rate_max_discharge = c->battery_rate_max_discharge; |
715 | 716 | const double battery_rate_max_export = c->battery_rate_max_export; |
716 | 717 | const double battery_rate_min = c->battery_rate_min; |
| 718 | + const double inverter_freeze_export_discharge_rate = c->inverter_freeze_export_discharge_rate; |
717 | 719 | // PV10 de-rating of the charge rate - prediction.py:587-592. PV90 is the upside case, no de-rate. |
718 | 720 | const double battery_rate_max_scaling = is_pv10 ? c->battery_rate_max_scaling10 : c->battery_rate_max_scaling; |
719 | 721 | const double battery_rate_max_scaling_discharge = c->battery_rate_max_scaling_discharge; |
@@ -1077,6 +1079,19 @@ static int32_t pk_run_one(const ContextStore *store, const PkScenario *s, PkResu |
1077 | 1079 | pv_ac = (pv_now - pv_dc) * inverter_loss_ac; |
1078 | 1080 | } |
1079 | 1081 | } |
| 1082 | + |
| 1083 | + // Some inverters (observed on AlphaESS) continue a small residual battery |
| 1084 | + // discharge during Freeze Export. Feed the battery-side rate into the normal AC |
| 1085 | + // balance so load consumes it first and any surplus may export, while respecting |
| 1086 | + // the reserve and the physical grid export limit. |
| 1087 | + if (inverter_freeze_export_discharge_rate > 0 && battery_draw >= 0) { |
| 1088 | + double freeze_draw = std::min(inverter_freeze_export_discharge_rate * step * battery_loss_discharge, battery_to_min); |
| 1089 | + const double freeze_diff = get_diff(freeze_draw, pv_dc, pv_ac, load_yesterday, inverter_loss, inverter_loss_recp); |
| 1090 | + if (freeze_diff < 0 && std::abs(freeze_diff) > export_limit) { |
| 1091 | + freeze_draw = std::max(freeze_draw - (std::abs(freeze_diff) - export_limit) * inverter_loss_recp, 0.0); |
| 1092 | + } |
| 1093 | + battery_draw = freeze_draw; |
| 1094 | + } |
1080 | 1095 | } else { |
1081 | 1096 | // ECO Mode - prediction.py:951-997 |
1082 | 1097 | pv_ac = pv_now * inverter_loss_ac; |
|
0 commit comments