Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change method of metric keep #1786

Merged
merged 7 commits into from
Dec 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions apps/predbat/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,18 @@
"icon": "mdi:battery-50",
"default": 0.5,
},
{
"name": "best_soc_keep_weight",
"friendly_name": "Best SOC Keep Weighting",
"type": "input_number",
"min": 0.1,
"max": 5,
"step": 0.10,
"unit": "*",
"icon": "mdi:multiplication",
"default": 0.5,
"enable": "expert_mode",
},
{
"name": "metric_min_improvement",
"friendly_name": "Metric Min Improvement",
Expand Down
1 change: 1 addition & 0 deletions apps/predbat/fetch.py
Original file line number Diff line number Diff line change
Expand Up @@ -1759,6 +1759,7 @@ def fetch_config_options(self):
self.best_soc_min = self.get_arg("best_soc_min")
self.best_soc_max = self.get_arg("best_soc_max")
self.best_soc_keep = self.get_arg("best_soc_keep")
self.best_soc_keep_weight = self.get_arg("best_soc_keep_weight")
self.set_soc_minutes = 30
self.set_window_minutes = 30
self.inverter_set_charge_before = self.get_arg("inverter_set_charge_before")
Expand Down
1 change: 1 addition & 0 deletions apps/predbat/predbat.py
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,7 @@ def reset(self):
self.best_soc_max = 0
self.best_soc_margin = 0
self.best_soc_keep = 0
self.best_soc_keep_weight = 0.5
self.rate_min = 0
self.rate_min_minute = 0
self.rate_min_forward = {}
Expand Down
13 changes: 5 additions & 8 deletions apps/predbat/prediction.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ def __init__(self, base=None, pv_forecast_minute_step=None, pv_forecast_minute10
self.battery_loss = base.battery_loss
self.battery_loss_discharge = base.battery_loss_discharge
self.best_soc_keep = base.best_soc_keep
self.best_soc_keep_weight = base.best_soc_keep_weight
self.best_soc_min = base.best_soc_min
self.car_charging_battery_size = base.car_charging_battery_size
self.rate_import = base.rate_import
Expand Down Expand Up @@ -411,9 +412,9 @@ def run_prediction(self, charge_limit, charge_window, export_window, export_limi
if minute < 4 * 60:
keep_minute_scaling = 0
else:
keep_minute_scaling = min(((minute - 4 * 60) / (2 * 60)), 1.0) * 0.5
keep_minute_scaling = min(((minute - 4 * 60) / (2 * 60)), 1.0) * self.best_soc_keep_weight
else:
keep_minute_scaling = 0.5
keep_minute_scaling = self.best_soc_keep_weight

# Find charge & discharge windows
charge_window_n = charge_window_optimised.get(minute_absolute, -1)
Expand Down Expand Up @@ -808,12 +809,8 @@ def run_prediction(self, charge_limit, charge_window, export_window, export_limi
diff = get_diff(battery_draw, pv_dc, pv_ac, load_yesterday, inverter_loss)

# Metric keep - pretend the battery is empty and you have to import instead of using the battery
if soc < self.best_soc_keep:
# Apply keep as a percentage of the time in the future so it gets stronger over an 4 hour period
# Weight to 50% chance of the scenario
keep_diff = max(get_diff(0, 0, pv_now, load_yesterday, inverter_loss), battery_draw)
if keep_diff > 0:
metric_keep += rate_import[minute_absolute] * keep_diff * keep_minute_scaling
if self.best_soc_keep > 0 and soc <= self.best_soc_keep:
metric_keep += (self.best_soc_keep - soc) * rate_import[minute_absolute] * keep_minute_scaling * step / 60.0
if diff > 0:
# Import
# All imports must go to home (no inverter loss) or to the battery (inverter loss accounted before above)
Expand Down
44 changes: 36 additions & 8 deletions apps/predbat/unit_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -1196,6 +1196,7 @@ def simple_scenario(
carbon=0,
assert_final_carbon=0.0,
keep=0.0,
keep_weight=0.5,
assert_keep=0.0,
save="best",
quiet=False,
Expand Down Expand Up @@ -1250,6 +1251,7 @@ def simple_scenario(
my_predbat.iboost_gas_scale = gas_scale
my_predbat.iboost_charging = iboost_charging
my_predbat.best_soc_keep = keep
my_predbat.best_soc_keep_weight = keep_weight
my_predbat.car_charging_soc[0] = 0
my_predbat.car_charging_limit[0] = 100.0

Expand Down Expand Up @@ -1345,7 +1347,7 @@ def simple_scenario(
if abs(final_carbon_g - assert_final_carbon) >= 0.1:
print("ERROR: Final Carbon {} should be {}".format(final_carbon_g, assert_final_carbon))
failed = True
if abs(metric_keep - assert_keep) >= 0.1:
if abs(metric_keep - assert_keep) >= 0.5:
print("ERROR: Metric keep {} should be {}".format(metric_keep, assert_keep))
failed = True
if assert_iboost_running != prediction.iboost_running:
Expand Down Expand Up @@ -3168,6 +3170,7 @@ def run_optimise_all_windows(
hybrid=False,
inverter_loss=1.0,
best_soc_keep=0.0,
best_soc_keep_weight=0.5,
):
print("Starting optimise all windows test {}".format(name))
end_record = my_predbat.forecast_minutes
Expand All @@ -3179,6 +3182,7 @@ def run_optimise_all_windows(
my_predbat.inverter_hybrid = hybrid
my_predbat.inverter_loss = inverter_loss
my_predbat.best_soc_keep = best_soc_keep
my_predbat.best_soc_keep_weight = best_soc_keep_weight

reset_rates(my_predbat, rate_import, rate_export)
update_rates_import(my_predbat, charge_window_best)
Expand Down Expand Up @@ -3276,7 +3280,7 @@ def run_optimise_all_windows_tests(my_predbat):
for n in range(0, 48):
price = 16 - n % 16
charge_window_best.append({"start": my_predbat.minutes_now + 30 * n, "end": my_predbat.minutes_now + 30 * (n + 1), "average": price})
expect_charge_limit.append(100 if price <= 5.0 else 0)
expect_charge_limit.append(10 if price <= 5.0 else 0)
failed |= run_optimise_all_windows(
"created2",
my_predbat,
Expand All @@ -3286,7 +3290,26 @@ def run_optimise_all_windows_tests(my_predbat):
pv_amount=0,
expect_best_price=5 / 0.9,
inverter_loss=0.9,
best_soc_keep=0.5,
best_soc_keep=0.0,
battery_size=10,
)
if failed:
return failed

# One extra charge as we will fall below keep otherwise
expect_charge_limit[10] = 10.0
failed |= run_optimise_all_windows(
"created3",
my_predbat,
charge_window_best=charge_window_best,
expect_charge_limit=expect_charge_limit,
load_amount=0.2,
pv_amount=0,
expect_best_price=5 / 0.9,
inverter_loss=0.9,
best_soc_keep=1,
battery_soc=2,
battery_size=10,
)
if failed:
return failed
Expand Down Expand Up @@ -4061,8 +4084,9 @@ def run_model_tests(my_predbat):
with_battery=True,
discharge=0,
battery_soc=10,
assert_keep=1 * import_rate * KEEP_SCALE,
assert_keep=14 * import_rate * 0.5 + ((1 + (1 / 12)) * import_rate * 0.5 * 0.5),
keep=1,
keep_weight=0.5,
)
failed |= simple_scenario(
"battery_discharge_loss",
Expand Down Expand Up @@ -4110,8 +4134,9 @@ def run_model_tests(my_predbat):
with_battery=True,
discharge=0,
battery_soc=10,
assert_keep=14 * import_rate * 0.5 * KEEP_SCALE + 1 * import_rate * KEEP_SCALE,
assert_keep=14 * import_rate + 1 * import_rate * 0.5,
keep=1.0,
keep_weight=1.0,
)
failed |= simple_scenario(
"battery_discharge_load_keep_mode_test1",
Expand All @@ -4123,8 +4148,9 @@ def run_model_tests(my_predbat):
with_battery=True,
discharge=0,
battery_soc=10,
assert_keep=14 * import_rate * 0.5 * KEEP_SCALE + 1 * import_rate * KEEP_SCALE,
assert_keep=14 * import_rate * 0.8 + 1 * import_rate * 0.8 * 0.5,
keep=1.0,
keep_weight=0.8,
save="test",
)
failed |= simple_scenario(
Expand All @@ -4137,8 +4163,9 @@ def run_model_tests(my_predbat):
with_battery=True,
discharge=0,
battery_soc=10,
assert_keep=14 * import_rate * 0.5 * KEEP_SCALE + 1 * import_rate * KEEP_SCALE,
assert_keep=14 * import_rate * 0.8 + 1 * import_rate * 0.8 * 0.5,
keep=1.0,
keep_weight=0.8,
save="none",
)
failed |= simple_scenario(
Expand Down Expand Up @@ -4781,8 +4808,9 @@ def run_model_tests(my_predbat):
discharge=0,
battery_size=10,
keep=1.0,
keep_weight=1.0,
assert_final_iboost=0,
assert_keep=import_rate * 14 * 0.5 * KEEP_SCALE + import_rate * 1 * KEEP_SCALE,
assert_keep=import_rate * 14 + import_rate * 1 * 0.5,
)

# Alternating high/low rates
Expand Down
8 changes: 7 additions & 1 deletion docs/customisation.md
Original file line number Diff line number Diff line change
Expand Up @@ -254,9 +254,15 @@ you want to export as late in the day as you can.
**input_number.predbat_best_soc_keep** is the minimum battery level in kWh that Predbat will to try to keep the battery above for the Predbat plan.
This is a soft constraint only that's used for longer term planning and is ignored for the forthcoming first 4 hours of the plan.
As this is not used for short-term planning it's possible for your SoC to drop below this - use **input_number.predbat_best_soc_min**
if you need a hard SoC constraint that will always be maintained.
if you want to force all charges to be above a set level.
It's usually good to have best_soc_keep set to a value above 0 to allow some margin in case you use more energy than planned between charge slots.

**input_number.predbat_best_soc_keep_weight** (_expert_mode_) Is used to tune how strongly you want the keep metric to apply.
A value of 0 would essentially ignore keep while higher values will make it more important to always stay above your keep threshold even if it costs
more money to do so.

The default is 0.5 - this is the recommended setting.

**input_number.predbat_best_soc_min** (_expert mode_) sets the minimum charge level (in kWh) for charging during each slot and the
minimum force export level also (set to 0 if you want to skip some slots).
If you set this to a non-zero value you will need to use the low rate threshold to control which slots you charge from or you may charge all the time.
Expand Down
Loading