How to lower execution time of a template that optimizes energy consumption (error: exceed maximum execution time of 3.0s)

Hi all,

i am trying to develop the logic to optimize my energy consumption to increase the self consumption of my solar panels. I have the logic running for one device. I like to expand it to more devices, and therefore expanded the logic in developer tools / template section. Here i get the message “exceed maximum execution time of 3.0s”.

This makes sense as my approach uses large, nested loops. In other words, i need OR to find a more efficient code, OR use another approach for the logic.

I checked he community for similar problems but coulnd’t find any similar posts.

Does anyone have any suggestions for improving the efficiency of the code, or does anyone have an alternative approach to optimize energy consumption?

The objective
The code now only optimizes for self consumption of solar energy, controlling only a dishwasher and a washing machine. But in the future I would like to optimize my energy consumption in order to use the energy as much as possible when the hourly (market) energy prices are low. The code should be able to include the control of the heat pump (for hot water), room thermostates (increase room temperature when prices are low, stop heating during expensive hours), and a charger for an electric vehicle.

My approach

The approach is to take a forecast for the solar panels (solcast), measure the self consumption, and deduct that. Next, i assume a time the device is switched on and deduct the power consumption of this device during the time the device is running.
What remains is the net energy consumption profile.
Then i recalculate the same profile, assuming another time the device is switched on. I then check with energy consumption profile has the highest level of self consumption and store that in a namespace variable. Then on to another round of calculations, for again another time for switching on the device. And so forth.
This way i have split the day in 48 blocks of half an hour, and can check for each block what the consumption profile becomes when the device is switched on in that block.

The code

The code is:

{%- set ns = namespace(netafname =()) %}
{%- set ns2 = namespace(minste_netafname =()) %}
{%- set ns3 = namespace(tijd_minste_netafname =()) %}
{%- set ns4 = namespace(vermogensprofiel = [])%}
{%- set ns5 = namespace(optimaalprofiel = [])%}
{%- set ns2.minste_netafname = -100000000 %}
{%- set ns3.tijd_minste_netafname = 99 %}
{%- set ns6 = namespace(start_vw = ()) %}
{%- set ns7 = namespace(meeste_marge = (0)) %}
{%- set ns8 = namespace(marge =()) %}
{%- set ns8.marge = 100000 %}
{%- set ns9 = namespace(start_wm = ()) %}
{% set ns10 = namespace(sluipverbruik =()) %}
{% set ns10.sluipverbruik = states('input_number.sluipverbruik')|float(0)/1000 %}

{%- set forecast = state_attr('sensor.solcast_pv_forecast_forecast_today', 'detailedForecast')%}


{#-voor iedere starttijd van de vaatwasser bepalen we een hoeveel eigenverbruik er is -#}
{#- daarom hebben we een for loop die alle mogelijke starttijden langs gaat -#}
{%- set min_start_vw = 20 %}
{%- set max_start_vw = 45 %}        
{%- set doorloop_vw = 4 %}
{%- set vermogen_vw = 2.0 %}
{%- set min_start_wm = 10 %}
{%- set max_start_wm = 40 %}
{%- set doorloop_wm = 6 %}
{%- set vermogen_wm = 10.0 %}
{%- set loop_start_vw = max_start_vw - min_start_vw + 1%}        
{%- for j in range (0, loop_start_vw) %}
  {%- set ns6.start_vw = min_start_vw + j %}
  {%- set loop_start_wm = max_start_wm - min_start_wm + 1%} 
  {%- for k in range (0, loop_start_wm) %}
    {#- hieronder wordt bepaald of er een nieuwe starttijd berekend moet worden voor de wasmachine#}
    {#- als op de dag van de uitgestelde start de starttijd al geweest is blijft de oorspronkelijke starttijd staan #}
    {%- set ns9.start_wm = min_start_wm + k %}
    {%- set extravermogen = ns10.sluipverbruik %}
    {%- set ns.netafname = 0 %}
    {%- set ns4.vermogensprofiel = [] %}
    {%- for i in range (0,48) %}
      {#- als het tijdstip gelijk is aan een moment dat de vaatwasser aanstaat, wordt het vermogen van de vaatwasser opgeteld #}
      {%- if i <= (ns6.start_vw + doorloop_vw) - 1 %}
        {%- if i >= ns6.start_vw -1 %}
          {%- set extravermogen = extravermogen + vermogen_vw %}
        {%- endif %}
      {%- endif %}           
      {#- als het tijdstip gelijk is aan een moment dat de wasmachine aanstaat, wordt het vermogen van de wasmachine opgeteld #}
      {%- if i <= (ns9.start_wm + doorloop_wm) - 1 %}
        {%- if i >= ns9.start_wm -1 %}
          {%- set extravermogen = extravermogen + vermogen_wm %}
        {%- endif %}
      {%- endif %}               
      {%- set opwek_nu = (forecast[i].pv_estimate) %}
      {%- set vermogensprofielnu = (opwek_nu - extravermogen)|round(2) %}         
      {#- hieronder berekenen we de netafname in het betreffende uur. De combinatie van starttijden #}
      {#- die resulteren in de laagste netafname is de beste. Dan hebben we het meeste eigenverbruik #}
      {%- if vermogensprofielnu < 0 %}
        {%- set netafname_nu = (opwek_nu - extravermogen)|round(2) %}
        {%- set ns.netafname = (ns.netafname +  netafname_nu)|round(2) %}
      {%- endif %}
      {#- We berekenen ook hoeveel netto opwek er nog over is als een apparaat aan staat. #}
      {#- Dit doen we zodat we, bij 2 opties met hetzelfde % eigenverbruik, de beste kunnen kiezen #}
      {#- We kiezen dan degene met de meeste marge op het moment met 'de kleinste marge tijdens eigenverbruik' #}
      {#- We bepalen daarom wat de kleinde marge is tijdens het gebruik van een apparaat #}
      {#- een marge kan ook negatief zijn. Dan wil je de meest negatieve marge vergelijken #}
      {%- if i <= ((ns6.start_vw + doorloop_vw) - 1) and i >= (ns6.start_vw -1)  %}
        {%- if vermogensprofielnu < ns8.marge %} 
          {%- set ns8.marge = vermogensprofielnu %} 
        {%- endif %}
      {%- endif %}
      {%- if i <= ((ns9.start_wm + doorloop_wm) - 1) and i >= ns9.start_wm -1 %}
        {%- if vermogensprofielnu < ns8.marge %} 
          {%- set ns8.marge = vermogensprofielnu %} 
        {%- endif %}
      {%- endif %}
      {#- tenslotte voegen we het netto vermogen van het huidig uur toe aan de lijst "vermogensprofiel" #}
      {%- set ns4.vermogensprofiel = ns4.vermogensprofiel + [vermogensprofielnu] %}        
    {%- endfor %}
  {%- endfor %}
  {#- We hebben nu een vermogensprofiel van 48 tijdsstippen in een lijst #}
  {#- Dit is dus een profiel voor 1 optie van de starttijd van de vaatwasser #} 
  {#- hieronder kijken we of dit profiel gunstiger is dan het tot nu toe bekende #}
  {#- gunstigste profiel. Zo ja, dan slaan we dit profiel en de bijbehorende starttijd op #}
  {#- we slaan ook de bijbehorende marge op, zodat we dit kunnen vergelijken met een ander #}
  {#- profiel dat hetzelfde percentage eigenverbruik heeft, als dat er blijkt te zijn. #}      
  {%- if ns.netafname /2 > ns2.minste_netafname %}
    {%- set ns7.meeste_marge = ns8.marge %}
    {%- set ns2.minste_netafname = ns.netafname/2 %}
    {%- set ns3.tijd_minste_netafname = ns6.start_vw %}
    {%- set ns5.optimaalprofiel = ns4.vermogensprofiel %}
  {%- elif ns.netafname /2 == ns2.minste_netafname %}
    {%- if ns8.marge > ns7.meeste_marge %}
      {%- set ns7.meeste_marge = ns8.marge %}
      {%- set ns2.minste_netafname = ns.netafname/2 %}
      {%- set ns3.tijd_minste_netafname = ns6.start_vw %}
      {%- set ns5.optimaalprofiel = ns4.vermogensprofiel %}
      {%- endif %}
   {%- endif %}
{%- endfor %}
{#- we hebben nu het beste profiel gevonden. Deze publiceren we in de attribuut: #}
{{ns5.optimaalprofiel}}