Skip to content

Commit

Permalink
Make threads configurable (#767)
Browse files Browse the repository at this point in the history
* Make threads configurable

---------

Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
  • Loading branch information
springfall2008 and pre-commit-ci-lite[bot] authored Feb 17, 2024
1 parent 23767f6 commit 8610cbe
Show file tree
Hide file tree
Showing 4 changed files with 88 additions and 37 deletions.
4 changes: 4 additions & 0 deletions apps/predbat/config/apps.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ pred_bat:
# Timezone to work in
timezone: Europe/London

# Number of threads to use in plan calculation
# Can be auto for automatic, 0 for off or values 1-N for a fixed number
threads: auto

# XXX: This is a configuration template, delete this line once you edit your configuration
template: True

Expand Down
111 changes: 74 additions & 37 deletions apps/predbat/predbat.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
import pytz
import requests
import yaml
from multiprocessing import Pool
from multiprocessing import Pool, cpu_count

# Only assign globals once to avoid re-creating them with processes are forked
if not "PRED_GLOBAL" in globals():
Expand Down Expand Up @@ -1129,6 +1129,25 @@ def find_charge_rate(model, minutes_now, soc, window, target_soc, max_rate, quie
return max_rate


"""
Used to mimic threads when they are disabled
"""


class DummyThread:
def __init__(self, result):
"""
Store the data into the class
"""
self.result = result

def get(self):
"""
Return the result
"""
return self.result


class Prediction:
"""
Class to hold prediction input and output data and the run function
Expand Down Expand Up @@ -8159,6 +8178,37 @@ def optimise_charge_limit_price(
)
return best_limits, best_discharge, best_price_charge, best_price_discharge, best_metric, best_cost

def launch_run_prediction_charge(self, loop_soc, window_n, charge_limit, charge_window, discharge_window, discharge_limits, pv10, all_n, end_record):
"""
Launch a thread to run a prediction
"""
if self.pool:
han = self.pool.apply_async(
self.prediction.thread_run_prediction_charge, (loop_soc, window_n, charge_limit, charge_window, discharge_window, discharge_limits, pv10, all_n, end_record)
)
else:
han = DummyThread(
self.prediction.thread_run_prediction_charge(loop_soc, window_n, charge_limit, charge_window, discharge_window, discharge_limits, pv10, all_n, end_record)
)
return han

def launch_run_prediction_discharge(self, this_discharge_limit, start, window_n, try_charge_limit, charge_window, try_discharge_window, try_discharge, pv10, all_n, end_record):
"""
Launch a thread to run a prediction
"""
if self.pool:
han = self.pool.apply_async(
self.prediction.thread_run_prediction_discharge,
(this_discharge_limit, start, window_n, try_charge_limit, charge_window, try_discharge_window, try_discharge, pv10, all_n, end_record),
)
else:
han = DummyThread(
self.prediction.thread_run_prediction_discharge(
this_discharge_limit, start, window_n, try_charge_limit, charge_window, try_discharge_window, try_discharge, pv10, all_n, end_record
)
)
return han

def optimise_charge_limit(
self,
window_n,
Expand Down Expand Up @@ -8204,27 +8254,10 @@ def optimise_charge_limit(
hans = []
all_max_soc = 0
all_min_soc = self.soc_max
hans.append(
self.pool.apply_async(
self.prediction.thread_run_prediction_charge, (loop_soc, window_n, charge_limit, charge_window, discharge_window, discharge_limits, False, all_n, end_record)
)
)
hans.append(
self.pool.apply_async(
self.prediction.thread_run_prediction_charge, (loop_soc, window_n, charge_limit, charge_window, discharge_window, discharge_limits, True, all_n, end_record)
)
)
hans.append(
self.pool.apply_async(
self.prediction.thread_run_prediction_charge,
(best_soc_min, window_n, charge_limit, charge_window, discharge_window, discharge_limits, False, all_n, end_record),
)
)
hans.append(
self.pool.apply_async(
self.prediction.thread_run_prediction_charge, (best_soc_min, window_n, charge_limit, charge_window, discharge_window, discharge_limits, True, all_n, end_record)
)
)
hans.append(self.launch_run_prediction_charge(loop_soc, window_n, charge_limit, charge_window, discharge_window, discharge_limits, False, all_n, end_record))
hans.append(self.launch_run_prediction_charge(loop_soc, window_n, charge_limit, charge_window, discharge_window, discharge_limits, True, all_n, end_record))
hans.append(self.launch_run_prediction_charge(best_soc_min, window_n, charge_limit, charge_window, discharge_window, discharge_limits, False, all_n, end_record))
hans.append(self.launch_run_prediction_charge(best_soc_min, window_n, charge_limit, charge_window, discharge_window, discharge_limits, True, all_n, end_record))
id = 0
for han in hans:
metricmid, import_kwh_battery, import_kwh_house, export_kwh, soc_min, soc, soc_min_minute, battery_cycle, metric_keep, final_iboost, min_soc, max_soc = han.get()
Expand Down Expand Up @@ -8323,13 +8356,9 @@ def optimise_charge_limit(
results10 = []
for try_soc in try_socs:
if try_soc not in resultmid:
hanres = self.pool.apply_async(
self.prediction.thread_run_prediction_charge, (try_soc, window_n, charge_limit, charge_window, discharge_window, discharge_limits, False, all_n, end_record)
)
hanres = self.launch_run_prediction_charge(try_soc, window_n, charge_limit, charge_window, discharge_window, discharge_limits, False, all_n, end_record)
results.append(hanres)
hanres10 = self.pool.apply_async(
self.prediction.thread_run_prediction_charge, (try_soc, window_n, charge_limit, charge_window, discharge_window, discharge_limits, True, all_n, end_record)
)
hanres10 = self.launch_run_prediction_charge(try_soc, window_n, charge_limit, charge_window, discharge_window, discharge_limits, True, all_n, end_record)
results10.append(hanres10)

# Get results from sims if we simulated them
Expand Down Expand Up @@ -8517,16 +8546,16 @@ def optimise_discharge(
this_discharge_limit = max(calc_percent_limit(max(self.best_soc_min, self.reserve), self.soc_max), this_discharge_limit)
try_options.append([start, this_discharge_limit])

hanres = self.pool.apply_async(
self.prediction.thread_run_prediction_discharge,
(this_discharge_limit, start, window_n, try_charge_limit, charge_window, try_discharge_window, try_discharge, False, all_n, end_record),
results.append(
self.launch_run_prediction_discharge(
this_discharge_limit, start, window_n, try_charge_limit, charge_window, try_discharge_window, try_discharge, False, all_n, end_record
)
)
hanres10 = self.pool.apply_async(
self.prediction.thread_run_prediction_discharge,
(this_discharge_limit, start, window_n, try_charge_limit, charge_window, try_discharge_window, try_discharge, True, all_n, end_record),
results10.append(
self.launch_run_prediction_discharge(
this_discharge_limit, start, window_n, try_charge_limit, charge_window, try_discharge_window, try_discharge, True, all_n, end_record
)
)
results.append(hanres)
results10.append(hanres10)

# Get results from sims
try_results = []
Expand Down Expand Up @@ -9918,7 +9947,15 @@ def calculate_plan(self, recompute=True):

# Create pool
if not self.pool:
self.pool = Pool(processes=8)
threads = self.get_arg("threads", "auto")
if threads == "auto":
self.log("Creating pool of {} processes to match your CPU count".format(cpu_count()))
self.pool = Pool(processes=cpu_count())
elif threads:
self.log("Creating pool of {} processes as per apps.yaml".format(int(threads)))
self.pool = Pool(processes=int(threads))
else:
self.log("Not using threading as threads is set to 0 in apps.yaml")

# Simulate current settings to get initial data
metric, import_kwh_battery, import_kwh_house, export_kwh, soc_min, soc, soc_min_minute, battery_cycle, metric_keep, final_iboost = self.run_prediction(
Expand Down
7 changes: 7 additions & 0 deletions docs/apps-yaml.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,13 @@ Basic configuration items
- **template** - Initially set to True, this is used to stop Predbat from operating until you have finished configuring your apps.yaml.
Once you have made all other required changes to apps.yaml this line should be deleted or commented out.

- **threads** - If defined sets the number of threads to use during plan calculation, the default is 'auto' which will use the same number of threads as
you have CPUs in your system.
Valid values are:
- 'auto' - Use the same number of threads as your CPU count
- '0' - Don't use threads - disabled
- 'N' - Use N threads, recommended values are between 2 and 8

- **notify_devices** - A list of device names to notify when Predbat sends a notification. The default is just 'notify' which contacts all mobile devices

- **days_previous** - Predbat needs to know what your likely future house load will be to set and manage the battery level to support it.
Expand Down
3 changes: 3 additions & 0 deletions docs/customisation.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,9 @@ quite CPU intensive and provides very little improvement for most systems.
If you have performance problems turn **switch.predbat_calculate_fast_plan** (_expert mode_) On to help
reduce your CPU load.

The number of threads you use can change your performance, you can set **threads** in apps.yaml to 0 to disable threading
if you don't have multiple CPUs available or set it to 'auto' (the default) to use one thread per CPU.

## Battery loss options

**input_number.predbat_battery_loss** is an assumed percentage figure for energy lost when charging the battery, the default 0.05 is 5%.
Expand Down

0 comments on commit 8610cbe

Please sign in to comment.