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))