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:
- Concepts and Terminology
- Creating a Hybrid Energy System Optimization Problem
- Creating an Electric Energy System Optimization Problem
- 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.
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):
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:
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 showsFreeFlow
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:
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
andcapex_ref
in this case) and the parameterscalingFactor
. WhenscalingFactor_free_
is checked (i.e., set totrue
), thescalingFactor
will be optimized, otherwise the value entered in theOptimization
tab will be used. The default value is 1, in which caseQ_flow_ref
andQ_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
inelectricityPurchase
, 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:
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:
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: