davidusb-geek / emhass

emhass: Energy Management for Home Assistant, is a Python module designed to optimize your home energy interfacing with Home Assistant.
MIT License
260 stars 51 forks source link

Infeasible result with 0.10.1, optimal with 0.9.1 #305

Open Micr0mega opened 3 weeks ago

Micr0mega commented 3 weeks ago

Describe the bug I have just updated from 0.9.1 to 0.10.1, and my day ahead optimization produces an infeasible result.

I have dynamic energy prices and solar panales, so I am using load_cost_forecast and prod_price_forecast parameters. It did produce a P_deferrable0 value of 1 instead of the specified 2000.

When I reverted to 0.9.1 a few minutes later, the result with the exact same parameters was optimal.

To Reproduce See above.

Expected behavior Optimal result.

Home Assistant installation type

Your hardware

EMHASS installation type

Additional context

Logs for both versions, INFO level, not DEBUG.

0.10.1:

2024-06-07 14:29:28,918 - web_server - INFO - Passed runtime parameters: {'load_cost_forecast': [17.2714, 16.87089, 18.31079, 22.43568, 24.98151, 27.50678, 29.64486, 31.36306, 28.80028, 26.05479, 26.58113, 24.98394, 24.12121, 23.94696, 23.28025, 23.54404, 23.48354, 21.64192, 19.83539, 16.67245, 15.16479, 14.89254, 13.96689, 12.38179], 'prod_price_forecast': [17.2714, 16.87089, 18.31079, 22.43568, 24.98151, 27.50678, 29.64486, 31.36306, 28.80028, 26.05479, 26.58113, 24.98394, 24.12121, 23.94696, 23.28025, 23.54404, 23.48354, 21.64192, 19.83539, 16.67245, 15.16479, 14.89254, 13.96689, 12.38179]}
2024-06-07 14:29:28,919 - web_server - INFO -  >> Setting input data dict
2024-06-07 14:29:28,919 - web_server - INFO - Setting up needed data
2024-06-07 14:29:28,924 - web_server - INFO - Retrieving weather forecast data using method = scrapper
2024-06-07 14:29:31,522 - web_server - INFO - Retrieving data from hass for load forecast using method = naive
2024-06-07 14:29:31,524 - web_server - INFO - Retrieve hass get data method initiated...
2024-06-07 14:29:34,177 - web_server - INFO -  >> Performing dayahead optimization...
2024-06-07 14:29:34,177 - web_server - INFO - Performing day-ahead forecast optimization
2024-06-07 14:29:34,215 - web_server - INFO - Perform optimization for the day-ahead
2024-06-07 14:29:34,337 - web_server - WARNING - Solver default unknown, using default
Welcome to the CBC MILP Solver 
Version: 2.10.10 
Build Date: Sep 26 2023 

command line - /usr/local/lib/python3.11/dist-packages/pulp/solverdir/cbc/linux/arm64/cbc /tmp/d57227bc725646bf97cb963156367305-pulp.mps -max -timeMode elapsed -branch -printingOptions all -solution /tmp/d57227bc725646bf97cb963156367305-pulp.sol (default strategy 1)
At line 2 NAME          MODEL
At line 3 ROWS
At line 249 COLUMNS
At line 1112 RHS
At line 1357 BOUNDS
At line 1550 ENDATA
Problem MODEL has 244 rows, 192 columns and 622 elements
Coin0008I MODEL read with 0 errors
Option for timeMode changed from cpu to elapsed
Problem is infeasible - 0.00 seconds
Option for printingOptions changed from normal to all
Total time (CPU seconds):       0.00   (Wallclock seconds):       0.01

0.9.1

2024-06-07 14:36:41,553 - web_server - INFO - Passed runtime parameters: {'load_cost_forecast': [17.2714, 16.87089, 18.31079, 22.43568, 24.98151, 27.50678, 29.64486, 31.36306, 28.80028, 26.05479, 26.58113, 24.98394, 24.12121, 23.94696, 23.28025, 23.54404, 23.48354, 21.64192, 19.83539, 16.67245, 15.16479, 14.89254, 13.96689, 12.38179], 'prod_price_forecast': [17.2714, 16.87089, 18.31079, 22.43568, 24.98151, 27.50678, 29.64486, 31.36306, 28.80028, 26.05479, 26.58113, 24.98394, 24.12121, 23.94696, 23.28025, 23.54404, 23.48354, 21.64192, 19.83539, 16.67245, 15.16479, 14.89254, 13.96689, 12.38179]}
2024-06-07 14:36:41,554 - web_server - INFO -  >> Setting input data dict
2024-06-07 14:36:41,554 - web_server - INFO - Setting up needed data
2024-06-07 14:36:41,626 - web_server - INFO - Retrieving weather forecast data using method = scrapper
2024-06-07 14:36:44,091 - web_server - INFO - Retrieving data from hass for load forecast using method = naive
2024-06-07 14:36:44,093 - web_server - INFO - Retrieve hass get data method initiated...
2024-06-07 14:36:46,972 - web_server - INFO -  >> Performing dayahead optimization...
2024-06-07 14:36:46,973 - web_server - INFO - Performing day-ahead forecast optimization
2024-06-07 14:36:47,012 - web_server - INFO - Perform optimization for the day-ahead
2024-06-07 14:36:47,033 - web_server - WARNING - Solver default unknown, using default
Welcome to the CBC MILP Solver 
Version: 2.10.10 
Build Date: Sep 26 2023 

command line - /usr/local/lib/python3.11/dist-packages/pulp/solverdir/cbc/linux/arm64/cbc /tmp/710876c6ef3d409797932e1c0aedf59d-pulp.mps -max -timeMode elapsed -branch -printingOptions all -solution /tmp/710876c6ef3d409797932e1c0aedf59d-pulp.sol (default strategy 1)
At line 2 NAME          MODEL
At line 3 ROWS
At line 176 COLUMNS
At line 872 RHS
At line 1044 BOUNDS
At line 1237 ENDATA
Problem MODEL has 171 rows, 168 columns and 455 elements
Coin0008I MODEL read with 0 errors
Option for timeMode changed from cpu to elapsed
Continuous objective value is -39.3902 - 0.00 seconds
Cgl0003I 0 fixed, 0 tightened bounds, 33 strengthened rows, 66 substitutions
Cgl0003I 0 fixed, 0 tightened bounds, 0 strengthened rows, 48 substitutions
Cgl0004I processed model has 33 rows, 31 columns (11 integer (11 of which binary)) and 103 elements
Cbc0038I Initial state - 0 integers unsatisfied sum - 2.22045e-16
Cbc0038I Solution found of -39.3902
Cbc0038I Relaxing continuous gives -39.3902
Cbc0038I Before mini branch and bound, 11 integers at bound fixed and 10 continuous
Cbc0038I Mini branch and bound did not improve solution (0.02 seconds)
Cbc0038I After 0.02 seconds - Feasibility pump exiting with objective of -39.3902 - took 0.00 seconds
Cbc0012I Integer solution of -39.390228 found by feasibility pump after 0 iterations and 0 nodes (0.02 seconds)
Cbc0001I Search completed - best objective -39.39022833378532, took 0 iterations and 0 nodes (0.02 seconds)
Cbc0035I Maximum depth 0, 0 variables fixed on reduced cost
Cuts at root node changed objective from -39.3902 to -39.3902
Probing was tried 0 times and created 0 cuts of which 0 were active after adding rounds of cuts (0.000 seconds)
Gomory was tried 0 times and created 0 cuts of which 0 were active after adding rounds of cuts (0.000 seconds)
Knapsack was tried 0 times and created 0 cuts of which 0 were active after adding rounds of cuts (0.000 seconds)
Clique was tried 0 times and created 0 cuts of which 0 were active after adding rounds of cuts (0.000 seconds)
MixedIntegerRounding2 was tried 0 times and created 0 cuts of which 0 were active after adding rounds of cuts (0.000 seconds)
FlowCover was tried 0 times and created 0 cuts of which 0 were active after adding rounds of cuts (0.000 seconds)
TwoMirCuts was tried 0 times and created 0 cuts of which 0 were active after adding rounds of cuts (0.000 seconds)
ZeroHalf was tried 0 times and created 0 cuts of which 0 were active after adding rounds of cuts (0.000 seconds)

Result - Optimal solution found

Objective value:                -39.39022833
Enumerated nodes:               0
Total iterations:               0
Time (CPU seconds):             0.01
Time (Wallclock seconds):       0.03

Option for printingOptions changed from normal to all
Total time (CPU seconds):       0.02   (Wallclock seconds):       0.04

2024-06-07 14:36:47,106 - web_server - INFO - Status: Optimal
2024-06-07 14:36:47,106 - web_server - INFO - Total value of the Cost function = -39.39

If you need any more info, I can provide screenshots of the plots if needed, but I thought it might be a bit too much clutter for now.

Edit, found the full YAML config. Note I use 60 minute steps instead of the default 30.

data_path: default
costfun: profit
sensor_power_photovoltaics: sensor.power_photovoltaics
sensor_power_load_no_var_loads: sensor.power_load_no_var_loads
set_total_pv_sell: false
set_nocharge_from_grid: false
set_nodischarge_to_grid: false
maximum_power_from_grid: 17250
maximum_power_to_grid: 9000
number_of_deferrable_loads: 1
list_nominal_power_of_deferrable_loads:
  - nominal_power_of_deferrable_loads: 2000
list_operating_hours_of_each_deferrable_load:
  - operating_hours_of_each_deferrable_load: 1
list_start_timesteps_of_each_deferrable_load:
  - start_timesteps_of_each_deferrable_load: 0
list_end_timesteps_of_each_deferrable_load:
  - end_timesteps_of_each_deferrable_load: 0
list_peak_hours_periods_start_hours:
  - peak_hours_periods_start_hours: "05:54"
list_peak_hours_periods_end_hours:
  - peak_hours_periods_end_hours: "09:24"
list_treat_deferrable_load_as_semi_cont:
  - treat_deferrable_load_as_semi_cont: true
list_set_deferrable_load_single_constant:
  - set_deferrable_load_single_constant: true
load_peak_hours_cost: 0.27
load_offpeak_hours_cost: 0.18
photovoltaic_production_sell_price: 0.065
list_pv_module_model:
  - pv_module_model: Solar_Frontier_SF170_S
list_pv_inverter_model:
  - pv_inverter_model: GoodWe_Technologies_Co___Ltd___GW7700_MS_US30__240V_
list_surface_tilt:
  - surface_tilt: 30
list_surface_azimuth:
  - surface_azimuth: 176
list_modules_per_string:
  - modules_per_string: 18
list_strings_per_inverter:
  - strings_per_inverter: 1
set_use_battery: false
battery_nominal_energy_capacity: 5000
optimization_time_step: 60
stevenwhately commented 3 weeks ago

Also have Infeasible result with 0.10.1. Optimal with 0.10.0. Optimal with 0.10.1 when passing pv_power_forcast.

stevenwhately commented 2 weeks ago

The following change produced infeasible results. https://github.com/davidusb-geek/emhass/commit/dd00d438df95d78210fef52638ce472aafaf1805#diff-73fdad90feda707a91a77eb0a3628bf4346268f4ae70160850a06b29ce32024bL317-R317

This was caused by scrapper returning negative values. Something like the following will stop any negative PV values from producing infeasible results

            constraints.update({"constraint_curtailment_{}".format(i) :
                plp.LpConstraint(
-                   e = P_PV_curtailment[i] - P_PV[i],
+                  e = P_PV_curtailment[i] - max(P_PV[i],0),
                    sense = plp.LpConstraintLE,
                    rhs = 0)
                for i in set_I})