Skip to content

Dynamic Optimization - Creating a Hybrid Energy System Optimization Problem🔗

Focus on conceptual considerations of energy systems optimization

1. Introduction🔗

This article is the second in a series of articles related to dynamic optimization:

  1. Concepts and Terminology
  2. Creating a Hybrid Energy System Optimization Problem
  3. Creating an Electric Energy System Optimization Problem
  4. Best Practice and Troubleshooting

The following hands on tutorial guides the user step-by-step through the optimization workflow. Important modeling and usability concepts of the EnergySystems library are explained in each step. These concepts are relevant for all sectors and models, so this tutorial is helpful for every beginner, even if the system at hand should be of low interest. The explanations given here aim at a relatively broad user group. If they are not sufficient for you and you get stuck, please don't hesitate to contact us at support@modelon.com. For a basic introduction to optimization problems in general, please refer to the first article. For more experienced users, sections labeled Advanced are included that offer a deeper understanding. Feel free to skip them if they are more confusing than clarifying to you.

2. Hands on Tutorial🔗

As an educational example, we will analyze a simple heating system that covers a given heat demand. A heat pump and a furnace will be compared in combination with a thermal storage. A screenshot of the final system model is shown below, followed by instructions on how to build the model step by step.


Final model


Step 0: Prepare workspace🔗

You need a workspace with the library EnergySystems 1.0 in the dependencies to build this model. For instructions on how to set up a workspace in Impact, please refer here.

Step 1: Extend template🔗

To start this example, extend the model EnergySystems.ScenarioSetup.EconomyOptimizationTemplate and chose a name for your system model. As explained in the previous article, the models economy and optimizer are included in the template. It should look like this (press alt + g to turn grid on/off):


Step 1


We want to analyze one week of operation. To do so, click on the optimizer and set:

  • optimizer: t_opt_end: 7*24*3600 (s)

All other values can be kept at their defaults.

Advanced

  • The optimization objective in the optimizer is set to minimize the system's TCO, i.e., the total cost of ownership (available in the economy).
  • This leads to the optimum balance of capex vs. opex. If sizing is deactivated, the capex of the system is fixed and optimizing the TCO is essentially the same as optimizing the total cash flow.

Lessons learned

  • Always use the template as basis for your system models.
  • The optimization time horizon (like many other settings) is defined in the model optimizer.

Step 2: Furnace heating🔗

Heat demand To model a heating demand covered by a furnace, you start by defining the heating demand. Add the model EnergySystems.SupplyAndDemand.Heat.Boundary to the system by dragging it from the library subfolders into the canvas. Change the name (upper right corner) from boundary to heatDemand and set:

  • heatDemand T_ref: 383.15 (K) / 110 (°C)

Advanced

  • T_ref is the reference temperature of the boundary. Since heat flows from hot to cold, it is a maximum for outflow and a minimum required temperature for inflow. This limit will be respected by the optimizer.

The input connector of the model is used to set the demand. Add the model Modelica.Blocks.Sources.Sine to the system, rename it to dataHeatDemand, connect the output to the model heatDemand and set:

  • dataHeatDemand amplitude: 50e3 (-)
  • dataHeatDemand f: 1/(24*3600) (Hz)
  • dataHeatDemand offset: 150e3 (-)

Furnace and fuel Add the model EnergySystems.HeatingUnits.Furnace to the system, rename it to furnace, connect the red (heat) port to the model heatDemand and select/set:

  • furnace flowType: FreeFlow
  • furnace Q_flow_ref: 250e3 (W) / 250 (kW)
  • furnace T_ref: 383.15 (K) / 110 (°C)
  • furnace Fuel: NaturalGas
  • furnace (Economy tab) lifetime: 20 (yr)
  • furnace (Economy tab) capex_ref: 1e5 (-)

Advanced

  • The heat flow rate from the furnace is set to FreeFlow, because the flow from the furnace can only go to the heat demand and the flow is defined there.
  • Setting a flow in the furnace as well would lead to an overdetermined system.

Next, add the model EnergySystems.SupplyAndDemand.Fluid.Purchase to the system, rename it to fuelPurchase, connect it to the model furnace and select:

  • fuelPurchase Medium: NaturalGas
  • fuelPurchase flowType: FreeFlow

The input connector of the model is used to set the purchase price for the natural gas. Add the model Modelica.Blocks.Sources.Ramp to the system, rename it to dataGasPrice, connect the output to the model fuelPurchase and set:

  • dataGasPrice height: 0.01 (-)
  • dataGasPrice duration: 5*24*3600 (s)
  • dataGasPrice offset: 0.01 (-)
  • dataGasPrice startTime: 24*3600 (s)

Your system model should now look something like this:


Step 2


You can now start an optimization. Hover over the play button, choose Energysystems.dynamic Optimization, and click play. Go to the Results tab, click on the economy's dog ear, expand the summary, and plot the variable opex_total by dragging it into the canvas (see here for details on result visualization). The final (rounded) value for opex_total should be 29.4.

Advanced

  • You technically performed an optimization, but since you did not define any degrees of freedom yet, it was practically just a simulation.
  • There is no unit for money in ESL. Make sure to enter all monetary values in the same unit (e.g., $ or €).
  • Setting T_ref in the furnace lower than in the heatDemand leads to an infeasible problem (no solution possible) and a corresponding error message.

Lessons learned

  • The package SupplyAndDemand offers models for in- and outflows - with and without pricing.
  • The definition of flows is important - pay attention to where you set them and where you need FreeFlow. A black square behind a connector shows FreeFlow and there must be exactly one black square connector in each connection set.
  • The model economy gathers all cash flows in the system.

Step 3: Carbon price and sizing🔗

Carbon price To enable pricing of the furnace's carbon emissions, add the model EnergySystems.SupplyAndDemand.Fluid.CO2Disposal to the system, rename it to CO2Disposal and set:

  • CO2Disposal price_kg: 5e-3 (1/kg)

In the model furnace, check the box use_CO2Port and connect the enabled port to the model CO2Disposal. Your system model should now look something like this:


Step 3


Rerun the optimization and compare the variable opex_total to the previous result in a plot. The new final value (rounded) should be 56.5.

Sizing We will now analyze the size of the furnace. Plot the values for Q_flow_ref and Q_flow_out in one diagram (convenience tip: to filter the results, click on the model furnace and/or write Q_flow in the filter field). You previously set the reference heating power Q_flow_ref to 250 kW, so this should be shown in the plot (convenience tip: displayed units can be changed in the settings). Q_flow_out should vary between 100 and 200 kW as defined in the model dataHeatDemand. This shows that the furnace is oversized for this case. To enable optimal scaling of the furnace, check the box scalingFactor_free_ in the Optimization tab of the model furnace. Note the yellow dot that appears in the corner of the model, which indicates a degree of freedom. Rerun the optimization. Q_flow_ref and Q_flow_out should have the same values as before. Add the furnace's Q_flow_rat to the plot. This is the rated heating power of the optimally scaled furnace and should be at the maximum required heat demand, i.e., 200 kW (corresponding to a scalingFactor of 0.8 which can also be plotted). Plot the economy's capex for the two previous results and see how a smaller furnace leads to reduced investment costs (but is still able to deliver the required heat demand). Deactivate the scaling for the remainder of the tutorial by unchecking the box scalingFactor_free_ in the Optimization tab of the model furnace.

Advanced

  • Linear scaling of components is applied based on the defined reference device (Q_flow_ref and capex_ref in this case) and the parameter scalingFactor. When scalingFactor_free_ is checked (i.e., set to true), the scalingFactor will be optimized, otherwise the value entered in the Optimization tab will be used. The default value is 1, in which case Q_flow_ref and Q_flow_rat are identical.

  • The sizing only works, if the capex is included in the objective function. The default is the total cost of ownership, which finds the optimal trade-off of capex and opex. In this example with a given heat demand, simply minimizing capex would lead to the same result. Minimizing opex would not optimize the size of the furnace because it's performance is independent of the size.

Lessons learned

  • Sizing of a component can be activated in its Optimization tab and a yellow dot will indicate the degree of freedom. The reference power/capacity (parameter with name-ending _ref) will be used as basis.
  • The model economy gathers all investment costs in the system.

Step 4: Heat pump heating🔗

To decarbonize our heat supply system, we now implement heat pump heating. Add the model EnergySystems.HeatingUnits.HeatPump heatPump to the system, connect the heatPort_hot to the model heatDemand and set/select:

  • heatPump controlledVariable: Electric power
  • heatPump T_hot_max: 388.15 (K) / 115 (°C)
  • heatPump T_cold_min: 313.15 (K) / 40 (°C)
  • heatPump P_el_ref: 50e3 (W) / 50 (kW)

The heat pump needs a heat source, so add the model EnergySystems.SupplyAndDemand.Heat.Boundary to the system, rename it to heatSupply, connect it to the heatPort_cold of the model heatPump and select:

  • heatSupply flowDirection: Outflow
  • heatSupply flowType: FreeFlow,
  • heatSupply T_ref: 343.15 (K) / 70 (°C)

The heat pump also needs electricity, so add the model EnergySystems.SupplyAndDemand.Electricity.Purchase_AC to the system, rename it to electricityPurchase, connect it to the model heatPump and select:

  • electricityPurchase: flowType: FreeFlow

Note that both heatSupply and electricityPurchase have FreeFlow, because the required heat and electricity is calculated by the heat pump model.

Advanced

  • We can ignore the wrong value of 1 for the reference voltage V_ref in electricityPurchase, because we are neglecting voltage levels in this example and the heat pump model does not require a minimum voltage.

To set the electricity price, add the model Modelica.Blocks.Sources.Sine to the system, rename it to dataElectricityPrice, connect the output to the model electricityPurchase and set:

  • dataElectricityPrice amplitude: 5e-3 (-)
  • dataElectricityPrice f: 1/(24*3600) (Hz)
  • dataElectricityPrice offset: 1e-2 (-)

Note that the price for the fluidPurchase is per kg, whereas the price for the electricityPurchase is per kWh.

The only thing missing now is the control input for the model heatPump. Note that the name of the connector is u_ctrl, which stands for control input. Many components in the library have this input and it is important to understand that it is a scaled input from 0 to 100%. The value supplied (between 0 and 1) will be scaled up based on the rated power of the component. This approach is necessary, because the rated power can be a degree of freedom (as demonstrated during furnace sizing) and supplying absolute values could lead to unintended/infeasible behavior. In contrast, boundary models from the package SupplyAndDemand do not have a rated power and therefore not a scaled input either. These models need absolute values for the desired energy/fluid flows.

A key functionality of the EnergySystems library is the optimal operation of components. Since the heat pump and the furnace are connected in parallel, a control system needs to decide how much heat should be supplied by which component. In a simulation, such a control system would typically follow a price-based if-then-else logic. However, implementing such a logic will never guarantee optimal operation and can become very complex for larger and integrated systems. In this library, we simply use an optimalControl model and let the optimizer find the optimal best operation of each component. To do so, add the model EnergySystems.ScenarioSetup.OptimalControl to the system, rename it to optHP and connect the output to the model heatPump. The model needs an initial guess as starting point, so add the model Modelica.Blocks.Sources.RealExpression to the system, rename it to initHP, connect the output to the model optHP and set:

  • initHP y: 0.5 (-)

Advanced

  • The furnace does not have an OptimalControl input. However, the optimizer finds the optimal solution for the entire system and since the furnace operation is a result of the heat pump operation and the heat demand, it's operation is optimized as well.
  • The initialization is not too important in this case. For more complex system models, it can be beneficial to implement a simple control system for the initial guess.

Your system model should now look something like this:


Step 4


Rerun the optimization and plot opex_total again. It should have decreased to 48.1. Plot the prices for electricity and natural gas in one diagram and the heat pump's u_ctrl in another. You should see that the heat pump is used when the electricity price is low and that the heat pump operation increases as the gas price increases.

Advanced

  • To purely focus on emission reduction, we could set the objective function to minimize the opex of the model CO2Disposal. Alternatively, a constraint could be implemented to set a maximum limit for the allowed emissions during the defined time horizon.
  • You can test how changing the prices for electricity, natural gas and carbon emissions leads to a change in operation and opex_total.

Lessons learned

  • Optimal operation of components is a key feature of the EnergySystems library and can be implemented conveniently.
  • Components in the EnergySystems library have a scaled control input between 0 and 1, boundary models have absolute values for energy/fluid flow as input.

Step 5: Thermal storage🔗

As last part of the tutorial, we will implement a thermal energy storage to reduce opex further by making use of the varying prices. To do so, add the model EnergySystems.Storage.Heat.IdealHeatStorage to the system, rename it to heatStorage, connect it to the model heatDemand and set:

  • heatStorage capacity_ref: 3.6e9 (J) / 1e3 (kWh)
  • heatStorage T_min: 383.15 (K) / 110 (°C)
  • heatStorage T_max: 388.15 (K) / 115 (°C)
  • heatStorage T_start: 383.15 (K) / 110 (°C)

Advanced

  • Temperature levels play a crucial role when analyzing a thermal storage and need to be set according to the type of storage. The model used here is an idealized thermal capacity, so effects like temperature distribution or phase fraction are neglected. The SOC corresponds to the temperature of the storage between minimum and maximum.

Note that the connector of the storage has a black square. This means, that all connected components need to define their flow which the furnace does not. To change this and implement optimal control of the furnace, you first need to select/set:

  • furnace flowType: DefinedFlow
  • furnace T_ref: 388.15 (K) / 115 (°C)

Note how the black square disappears and an input connector u_ctrl appears. Add the model EnergySystems.ScenarioSetup.OptimalControl to the system, rename it to optFurnace and connect the output to the connector of the model furnace. As for the heat pump, we need an initial guess as starting point, so add the model Modelica.Blocks.Sources.RealExpression to the system, rename it to initFurnace, connect the output to the model optFurnace and set:

  • initFurnace y: 0.5 (-)

Your system model should now look something like this:


Step 5


Rerun the optimization, click on the model heatStorage and plot the variable SOC (state of charge) to see how the storage is operated. The control strategy that the solver found in the previous step could also have been implemented with a feedback controller, since it simply used the cheapest energy source at every point in time. In contrast, the optimal operation that was achieved in this step takes into account the storage capacity and optimized the operation over the entire time horizon.

To see the economic effect of this operation, plot opex_total again. It should have decreased to 42.9.

Lessons learned

  • Including a storage may require changing the control of a connected device.

3. Summary🔗

This tutorial gave a basic introduction to key concepts of the EnergySystems library, including the definition of flows with and without pricing as well as optimal sizing and optimal operation of components. After finishing it, you should now be able to build simple system models yourself and understand the examples in the library better. However, some parts were not covered in this tutorial, for example:

  • Reading input data from file (see here)
  • Checking and analyzing the log (see here)
  • Advanced initialization (see here)
  • Implementing constraints, e.g. for total budget (see here)
  • Avoiding fast changes of control signals
  • Different objective functions (see here)
  • Troubleshooting (see here)