Basic Example
Introduction
This notebook runs a single WattAdvisor optimization.
The target is to find a cost minimal technology set for the supply of the given energy demands.
Copyright (c) 2007, Eclipse Foundation, Inc. and its licensors. All rights reserved. Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
Import of necessary packages
All necessary modules from WattAdvisor and additional packages are imported.
from pathlib import Path
from wattadvisor.utils.weather_data import (
get_weather_data_from_era5_netcdf,
)
from wattadvisor.opt_model import OptModel
from wattadvisor.data_models.enums import EnergyType, EnergyUnit, EnergyPriceUnit
from wattadvisor.components import (
energy_demand,
heat_pump,
photovoltaic,
energy_purchase,
gas_boiler,
electrical_energy_storage,
thermal_energy_storage,
)
Specification of the considered location
At first, it should be specified at which location the plants should be dimensioned for. Therefore, the location is specified using decimal longitude and latitude geocoordinates.
latitude = 50.693
longitude = 10.937
Load weather data file
Note: To successfully execute this code block, weather data from ECMWF Copernicus Climate Change Service must to be downloaded and placed into this directory under the name “weather.nc” previously. Check the documentation here for more information.
path_netcdf = Path().joinpath("weather.nc")
weather_data = get_weather_data_from_era5_netcdf(
path_netcdf, longitude=longitude, latitude=latitude
)
Add energy demands
The following energy demands that should be supplied are added to the model:
electrical energy demand with an annual demand of 3.500 kWh of electrical energy
thermal energy demand with an annual demand of 10.000 kWh of thermal energy
The annual demand values given are used in combination with standard load profiles to create synthetic, hourly load profiles of the demands.
demands = [
energy_demand.EnergyDemand(
demand_sum=3500,
demand_unit=EnergyUnit.KWH,
energy_type=EnergyType.ELECTRICAL,
profile_type="h0",
profile_year=2022,
),
energy_demand.EnergyDemand(
demand_sum=10000,
demand_unit=EnergyUnit.KWH,
energy_type=EnergyType.THERMAL,
profile_type="EFH",
profile_year=2022,
temperature_air=weather_data.air_temperature,
)
]
Add energy production, transformation and storage component options
To supply the energy demands, several component options are added to the model, from which the solver choses the optimal dimensioning:
gas boiler
air source heat pump
ground source heat pump
roof integrated photovoltaic plant
wind power plant
electrical energy storage
thermal energy storage
components = [
gas_boiler.GasBoiler(
lifespan=20, eff=0.75, capex=300, opex=2, installed_power=10
),
heat_pump.HeatPumpAir(
lifespan=20,
capex=1000,
opex=2,
source_temperature_series=weather_data.air_temperature,
),
heat_pump.HeatPumpGround(
lifespan=20,
capex=1300,
opex=2,
source_temperature_series=weather_data.soil_temperature,
),
photovoltaic.PhotovoltaikRoof(
lifespan=20,
capex=750,
opex=2.87,
potential_power=20,
latitude=latitude,
longitude=longitude,
ghi=weather_data.ghi,
dhi=weather_data.dhi,
air_temperature=weather_data.air_temperature,
),
electrical_energy_storage.ElectricalEnergyStorage(
lifespan=15,
capex_capacity=500,
capex_power=500,
opex=0.1,
eff=0.9,
relative_losses=0.00007
),
thermal_energy_storage.ThermalEnergyStorage(
lifespan=20,
capex_capacity=5,
capex_power=100,
opex=2,
eff=0.75,
relative_losses=0.00138
)
]
Add energy purchases
Furthermore, options to purchase electrical energy and natural gas from external sources or public grid are added.
purchases = [
energy_purchase.EnergyPurchase(
name="Electrical energy purchase",
energy_type=EnergyType.ELECTRICAL,
energy_price_scalar=0.35,
energy_price_unit=EnergyPriceUnit.EUR_PER_KWH,
co2_intensity=445,
),
energy_purchase.EnergyPurchase(
name="Natural gas purchase",
energy_type=EnergyType.NATURAL_GAS,
energy_price_scalar=0.10,
energy_price_unit=EnergyPriceUnit.EUR_PER_KWH,
co2_intensity=202,
),
]
Start optimization
The solver is to find the best solution for the model. This can take up to several minutes.
options = demands + components + purchases
optimization = OptModel(options)
# Start the optimization
results = optimization.run_calculation()
2025-05-28 15:38:09,060 - INFO - Create model instance
2025-05-28 15:38:10,966 - INFO - Built bilance constraint for energy type EnergyType.ELECTRICAL.
2025-05-28 15:38:11,046 - INFO - Built bilance constraint for energy type EnergyType.THERMAL.
2025-05-28 15:38:11,114 - INFO - Built bilance constraint for energy type EnergyType.NATURAL_GAS.
2025-05-28 15:38:11,115 - INFO - Optimizing model
2025-05-28 15:38:11,247 - INFO - Running HiGHS 1.10.0 (git hash: fd86653): Copyright (c) 2025 HiGHS under MIT licence terms
2025-05-28 15:38:20,272 - INFO - RUN!
2025-05-28 15:38:20,273 - INFO - LP has 227782 rows; 183992 cols; 582161 nonzeros
2025-05-28 15:38:20,278 - INFO - Coefficient ranges:
2025-05-28 15:38:20,278 - INFO - Matrix [7e-05, 1e+03]
2025-05-28 15:38:20,278 - INFO - Cost [1e+00, 1e+00]
2025-05-28 15:38:20,295 - INFO - Bound [1e+01, 2e+01]
2025-05-28 15:38:20,296 - INFO - RHS [1e-02, 4e+00]
2025-05-28 15:38:20,366 - INFO - Presolving model
2025-05-28 15:38:20,519 - INFO - 140160 rows, 113888 cols, 371873 nonzeros 0s
2025-05-28 15:38:20,520 - INFO - 113878 rows, 87606 cols, 319309 nonzeros 0s
2025-05-28 15:38:20,722 - INFO - Dependent equations search running on 26277 equations with time limit of 1000.00s
2025-05-28 15:38:20,722 - INFO - Dependent equations search removed 0 rows and 0 nonzeros in 0.00s (limit = 1000.00s)
2025-05-28 15:38:20,722 - INFO - 113875 rows, 87603 cols, 319297 nonzeros 0s
2025-05-28 15:38:20,722 - INFO - Presolve : Reductions: rows 113875(-113907); columns 87603(-96389); elements 319297(-262864)
2025-05-28 15:38:20,722 - INFO - Solving the presolved LP
2025-05-28 15:38:20,826 - INFO - Using EKK dual simplex solver - serial
2025-05-28 15:38:20,826 - INFO - Iteration Objective Infeasibilities num(sum)
2025-05-28 15:38:20,828 - INFO - 0 2.6175098323e+02 Pr: 17519(25480.2) 0s
2025-05-28 15:38:26,355 - INFO - 13468 7.3250339995e+02 Pr: 19694(1.25846e+06); Du: 0(1.34566e-08) 6s
2025-05-28 15:38:31,481 - INFO - 19145 1.1396460666e+03 Pr: 26003(339474); Du: 0(9.87494e-08) 11s
2025-05-28 15:38:36,509 - INFO - 28310 1.3963532362e+03 Pr: 17574(8.47287e+06); Du: 0(2.11774e-07) 16s
2025-05-28 15:38:41,735 - INFO - 51501 1.6747503373e+03 Pr: 24857(780622); Du: 0(4.8243e-07) 21s
2025-05-28 15:38:46,759 - INFO - 69128 1.8284909383e+03 Pr: 23005(533278); Du: 0(4.9752e-07) 26s
2025-05-28 15:38:51,789 - INFO - 77271 1.9094620839e+03 Pr: 28712(407701); Du: 0(8.66145e-07) 31s
2025-05-28 15:38:56,913 - INFO - 86167 1.9650254902e+03 Pr: 43321(1.11499e+06); Du: 0(8.35974e-07) 37s
2025-05-28 15:39:02,042 - INFO - 97057 2.0109353498e+03 Pr: 8059(143258); Du: 0(3.23489e-07) 42s
2025-05-28 15:39:07,264 - INFO - 105922 2.0315899438e+03 Pr: 34990(2.90249e+06); Du: 0(3.87967e-07) 47s
2025-05-28 15:39:12,591 - INFO - 114849 2.0400619013e+03 Pr: 857(347.222); Du: 0(1.84667e-07) 52s
2025-05-28 15:39:13,196 - INFO - 115460 2.0382437722e+03 Pr: 0(0); Du: 0(5.63453e-11) 53s
2025-05-28 15:39:13,196 - INFO - 115460 2.0382437722e+03 Pr: 0(0); Du: 0(5.63453e-11) 53s
2025-05-28 15:39:13,196 - INFO - Solving the original LP from the solution after postsolve
2025-05-28 15:39:13,398 - INFO - Model status : Optimal
2025-05-28 15:39:13,400 - INFO - Simplex iterations: 115460
2025-05-28 15:39:13,401 - INFO - Objective value : 2.0382437722e+03
2025-05-28 15:39:13,401 - INFO - Relative P-D gap : 7.2509918125e-15
2025-05-28 15:39:13,401 - INFO - HiGHS run time : 53.03
2025-05-28 15:39:13,873 - INFO - Optimization successfully completed
2025-05-28 15:39:13,874 - INFO - Write optimization results
2025-05-28 15:39:13,887 - INFO - Optimizing model
2025-05-28 15:39:13,958 - INFO - Running HiGHS 1.10.0 (git hash: fd86653): Copyright (c) 2025 HiGHS under MIT licence terms
2025-05-28 15:39:24,056 - INFO - RUN!
2025-05-28 15:39:24,057 - INFO - LP has 227782 rows; 183992 cols; 499358 nonzeros
2025-05-28 15:39:24,059 - INFO - Coefficient ranges:
2025-05-28 15:39:24,059 - INFO - Matrix [7e-05, 4e+02]
2025-05-28 15:39:24,060 - INFO - Cost [1e+00, 1e+00]
2025-05-28 15:39:24,061 - INFO - Bound [1e+01, 1e+01]
2025-05-28 15:39:24,061 - INFO - RHS [1e-02, 3e+03]
2025-05-28 15:39:24,132 - INFO - Presolving model
2025-05-28 15:39:24,286 - INFO - 8760 rows, 8760 cols, 8760 nonzeros 0s
2025-05-28 15:39:24,286 - INFO - 0 rows, 0 cols, 0 nonzeros 0s
2025-05-28 15:39:24,286 - INFO - Presolve : Reductions: rows 0(-227782); columns 0(-183992); elements 0(-499358) - Reduced to empty
2025-05-28 15:39:24,286 - INFO - Solving the original LP from the solution after postsolve
2025-05-28 15:39:24,388 - INFO - Model status : Optimal
2025-05-28 15:39:24,388 - INFO - Objective value : 2.8200690351e+03
2025-05-28 15:39:24,389 - INFO - Relative P-D gap : 3.8700954783e-15
2025-05-28 15:39:24,389 - INFO - HiGHS run time : 0.31
2025-05-28 15:39:24,595 - INFO - Optimization successfully completed
2025-05-28 15:39:24,601 - INFO - Optimization results written
Inspect results
After optimization has finished, an object containing the relevant results is returned. For example, the advised power of each component, the total investment cost and the cost from energy purchase from external sources can be obtained from the object.
for component in results.target_scenario.components:
if "advised_power" in component:
print(f"{component['name']}: {component['advised_power']:.2f} kW")
if "purchase_cost" in component:
print(f"{component['name']}: {component['purchase_cost']:.2f} €/a")
print(f"Total investment cost: {results.target_scenario.kpis.total_investment_cost:.2f} €")
GasBoiler_1: 10.00 kW
HeatPumpAir_1: 0.42 kW
HeatPumpGround_1: 2.34 kW
PhotovoltaikRoof_1: 2.84 kW
ElectricalEnergyStorage_1: 1.03 kW
ThermalEnergyStorage_1: 0.89 kW
Electrical energy purchase: 969.70 €/a
Natural gas purchase: 18.09 €/a
Total investment cost: 11921.48 €
Export results
Results are exported as a JSON file.
with open("results.json", "w") as file:
file.write(results.model_dump_json(exclude_none=True, indent=4))