How to Generate Stable Starting Values¶
Applying numerical algorithms and methods, the starting value of a variable is the value used for the first iteration. TESPy generates starting values for all variables automatically: known values propagate through the components, temperature levels are estimated and phase consistent enthalpies and saturation pressures are derived from them. The full process is described in the starting values section of the solver documentation. With this machinery most models converge without any help from the user.
With more complex models it can still happen, that the simulation does not converge from the automatically generated values. In that case, the strategy is to solve the model with a simpler but robust set of specifications first, for example directly imposing the saturation temperature levels of a heat pump cycle instead of terminal temperature differences. Since the solver starts from the previous solution, the actual specifications can then be imposed in a second solve: primary variables that hold a solved value do not need to be guessed anymore.
Here we provide a short tutorial for you to better understand, how this process could look like at the example of a subcritical heat pump with different working fluids.
Note
If the heat pump operates in trans- or supercritical range, some modifications have to be made on this setup. We plan to include respective examples here in the future.
You can download the full code of this example here:
starting_values.py
Topology of the heat pump¶
Following the first tutorial a slightly different topology for a heat pump with internal heat exchangers is considered instead of dumping the heat to the ambient. You can see the plant topology in the figure below.
Figure: Topology of heat pump with internal heat exchanger¶
Figure: Topology of heat pump with internal heat exchanger¶
The system consists of a consumer system, a valve, an evaporator system, a compressor and additionally an internal heat exchanger. In order to simulate this heat pump, the TESPy model has to be built up. First, the network has to be initialized, and the refrigerants used have to be specified. This example shows how to make the heat pump model work with a variety of working fluids with water on both the heat source and heat sink side of the system.
Running into errors¶
As always, we start by importing the necessary TESPy classes.
from tespy.networks import Network
from tespy.components import (
Condenser, Compressor, CycleCloser, HeatExchanger,
SimpleHeatExchanger, Pump, Sink, Source, Valve, PowerBus, PowerSource,
HeatSink
)
from tespy.connections import Connection, PowerConnection, HeatConnection
Then, we can build the network by defining components and connections. The working fluid will be set with the variable wf, “R290” is used in the first setup. This way, we will be able to change the working fluid in a flexible way.
Click to expand to code section
wf = "R290"
# network
nw = Network()
nw.units.set_defaults(
temperature="degC", pressure="bar", enthalpy="kJ/kg", power="MW", heat="MW",
pressure_difference="bar"
)
# components
cycle_closer = CycleCloser("Refrigerant Cycle Closer")
# heat source
heatsource_feedflow = Source("Heat Source Feed Flow")
heatsource_pump = Pump("Heat Source Recirculation Pump")
heatsource_evaporator = HeatExchanger("Heat Source Evaporator")
heatsource_backflow = Sink("Heat Source Back Flow")
# compression
compressor = Compressor("Compressor")
# heat sink
cons_pump = Pump("Heat Sink Recirculation Pump")
condenser = Condenser("Heat Sink Condenser")
cons_heatsink = SimpleHeatExchanger("Heat Consumer")
cons_cycle_closer = CycleCloser("Consumer Feed Flow")
# internal heat exchange
int_heatex = HeatExchanger("Internal Heat Exchanger")
# expansion
valve = Valve("Expansion Valve")
# connections
# main cycle
c0 = Connection(cycle_closer, "out1", heatsource_evaporator, "in2", label="0")
c1 = Connection(heatsource_evaporator, "out2", int_heatex, "in2", label="1")
c2 = Connection(int_heatex, "out2", compressor, "in1", label="2")
c3 = Connection(compressor, "out1", condenser, "in1", label="3")
c4 = Connection(condenser, "out1", int_heatex, "in1", label="4")
c5 = Connection(int_heatex, "out1", valve, "in1", label="5")
c6 = Connection(valve, "out1", cycle_closer, "in1", label="6")
nw.add_conns(c0, c1, c2, c3, c4, c5, c6)
# heat source
c11 = Connection(heatsource_feedflow, "out1", heatsource_pump, "in1", label="11")
c12 = Connection(heatsource_pump, "out1", heatsource_evaporator, "in1", label="12")
c13 = Connection(heatsource_evaporator, "out1", heatsource_backflow, "in1", label="13")
nw.add_conns(c11, c12, c13)
# heat sink
c21 = Connection(cons_cycle_closer, "out1", cons_pump, "in1", label="21")
c22 = Connection(cons_pump, "out1", condenser, "in2", label="22")
c23 = Connection(condenser, "out2", cons_heatsink, "in1", label="23")
c24 = Connection(cons_heatsink, "out1", cons_cycle_closer, "in1", label="24")
nw.add_conns(c21, c22, c23, c24)
After setting up the topology, the system’s parameters should be set in the following way:
Heat sink temperature levels (T at 23 and 24)
Heat source temperature levels (T at 11 and 13)
Degree of overheating after the internal heat exchanger (td_dew at 2)
Pinch point temperature difference at the evaporator (ttd_l) to derive evaporation pressure
Temperature difference at the condenser (ttd_u) to derive condensation pressure
Saturated gaseous state of the working fluid (x=1) after leaving the evaporator
Efficiencies of pumps and the compressor (eta_s)
Pressure losses in all heat exchangers (pr1, pr2, pr)
Consumer heat demand (Q)
Click to expand to code section
# parametrization connections
# set feedflow and backflow temperature of heat source and consumer
T_hs_bf = 10
T_hs_ff = 15
T_cons_bf = 50
T_cons_ff = 70
# consumer cycle
c23.set_attr(T=T_cons_ff, p=10, fluid={"water": 1})
c24.set_attr(T=T_cons_bf)
# heat source cycle
c11.set_attr(T=T_hs_ff, p=1, fluid={"water": 1})
c13.set_attr(T=T_hs_bf, p=1)
# evaporation to fully saturated gas
c1.set_attr(x=1, fluid={wf: 1})
# degree of overheating after internal heat exchanger (evaporation side)
c2.set_attr(td_dew=10)
# parametrization components
# isentropic efficiency
cons_pump.set_attr(eta_s=0.8)
heatsource_pump.set_attr(eta_s=0.8)
compressor.set_attr(eta_s=0.85)
# pressure ratios
condenser.set_attr(pr1=0.98, pr2=0.98)
heatsource_evaporator.set_attr(pr1=0.98, pr2=0.98)
cons_heatsink.set_attr(pr=0.99)
int_heatex.set_attr(pr1=0.98, pr2=0.98)
# temperature differences
heatsource_evaporator.set_attr(ttd_l=5)
condenser.set_attr(ttd_u=5)
# consumer heat demand
cons_heatsink.set_attr(Q=-1)
try:
nw.solve("design")
except ValueError as e:
print(e)
The system should be well defined with the parameter settings, however the solver does not find a solution from the automatically generated starting values. It isolates the part of the problem it fails on and reports it:
Error
Block 5 did not converge, solving the remaining 2 blocks simultaneously.
Cause: no acceptance within the iteration budget of 50 iterations,
the last scaled residual is 1.86e-02
Equations: Compressor.eta_s,
Heat Sink Condenser.energy_balance_constraints,
Heat Source Evaporator.ttd_l,
Internal Heat Exchanger.energy_balance_constraints, 1.x, 2.td_dew
Variables: h0, h1, h4, m6, h7, p11
The remaining system did not converge either, restarting with the
simultaneous solution of the full system from its initial state.
The solver does not seem to make any progress, aborting calculation.
Scaled residual value is 1.86e-02 (1: x)
Possible reasons include:
- fluid properties moving outside the valid range of the property
database (consider adjusting p_range or h_range),
- an impossible constraint that can never be satisfied
- bad starting values causing the Newton solver to diverge.
Use nw.print_residuals() to identify which equations have the largest
residuals.
The messages already narrow the failure down: the equation system was
decomposed into blocks and every block
solved fine except one. The group of six coupled equations determining the
evaporation side of the cycle failed. Neither solving the remaining blocks in
one coupled system nor restarting with the full system finds the solution, so
the calculation ends with nw.status == 2.
Tip
To investigate such a failure interactively, the solver can pause at the failing block instead of escalating:
nw.solve("design", pause_on_block_failure=True)
nw.print_block_states(block=5, at="failure")
nw.print_block_jacobian(block=5)
The state tables show the fluid states the newton algorithm diverged to,
the variable values of the failed block can be modified and the solution
process continued with nw.solve_continue(). The section on
interacting with the solver describes the
workflow in detail.
Fixing the errors¶
All equations of the failed block are tied to the pressure and enthalpy levels of the evaporation side of the cycle, so this is where better starting values are required. To provide them, it is recommended to fix the saturation levels of the cycle directly instead of the temperature differences in a first calculation. In this example, the fixed points can be identified with the help of the logph diagram which you can see in the figure below.
Figure: Logph diagram of propane¶
Figure: Logph diagram of propane¶
A rough estimation of the evaporation and condensation temperature can be
obtained from the temperature levels of the heat source and the heat sink.
Evaporation takes place a few Kelvin below the heat source backflow
temperature, condensation a few Kelvin above the consumer feed flow
temperature. These estimates can be imposed directly through the dew line
temperature T_dew at the evaporator outlet and the bubble line
temperature T_bubble at the condenser outlet. Each of them fixes the
pressure of its saturation state.
The terminal temperature differences are unset and the saturation temperatures are set instead.
# evaporation point
c1.set_attr(T_dew=T_hs_bf - 5)
heatsource_evaporator.set_attr(ttd_l=None)
# condensation point
c4.set_attr(T_bubble=T_cons_ff + 5)
condenser.set_attr(ttd_u=None)
# solve the network again
nw.solve("design")
The model was solved successfully and has stored the starting values for any follow-up. Therefore, we can undo our recent changes and restart the simulation. For example, the COP is then calculated.
# evaporation point
c1.set_attr(T_dew=None)
heatsource_evaporator.set_attr(ttd_l=5)
# condensation point
c4.set_attr(T_bubble=None)
condenser.set_attr(ttd_u=5)
# internal heat exchanger superheating
c2.set_attr(td_dew=5)
# solve the network again
nw.solve("design")
# calculate the COP
cop = abs(
cons_heatsink.Q.val
/ (cons_pump.P.val + heatsource_pump.P.val + compressor.P.val)
)
print(cop)
Expand fix to any working fluids¶
Finally, using this strategy, it is possible to build a generic function, building a network, that works with a variety of working fluids.
Click to expand to code section
def generate_network_with_starting_values(wf):
# network
nw = Network(iterinfo=False)
nw.units.set_defaults(
temperature="degC", pressure="bar", enthalpy="kJ/kg", power="MW",
heat="MW", pressure_difference="bar"
)
# components
cycle_closer = CycleCloser("Refrigerant Cycle Closer")
# heat source
heatsource_feedflow = Source("Heat Source Feed Flow")
heatsource_pump = Pump("Heat Source Recirculation Pump")
heatsource_evaporator = HeatExchanger("Heat Source Evaporator")
heatsource_backflow = Sink("Heat Source Back Flow")
# compression
compressor = Compressor("Compressor")
# heat sink
cons_pump = Pump("Heat Sink Recirculation Pump")
condenser = Condenser("Heat Sink Condenser")
cons_heatsink = SimpleHeatExchanger("Heat Consumer")
cons_cycle_closer = CycleCloser("Consumer Feed Flow")
# internal heat exchange
int_heatex = HeatExchanger("Internal Heat Exchanger")
# expansion
valve = Valve("Expansion Valve")
# connections
# main cycle
c0 = Connection(cycle_closer, "out1", heatsource_evaporator, "in2", label="0")
c1 = Connection(heatsource_evaporator, "out2", int_heatex, "in2", label="1")
c2 = Connection(int_heatex, "out2", compressor, "in1", label="2")
c3 = Connection(compressor, "out1", condenser, "in1", label="3")
c4 = Connection(condenser, "out1", int_heatex, "in1", label="4")
c5 = Connection(int_heatex, "out1", valve, "in1", label="5")
c6 = Connection(valve, "out1", cycle_closer, "in1", label="6")
nw.add_conns(c0, c1, c2, c3, c4, c5, c6)
# heat source
c11 = Connection(heatsource_feedflow, "out1", heatsource_pump, "in1", label="11")
c12 = Connection(heatsource_pump, "out1", heatsource_evaporator, "in1", label="12")
c13 = Connection(heatsource_evaporator, "out1", heatsource_backflow, "in1", label="13")
nw.add_conns(c11, c12, c13)
# heat sink
c21 = Connection(cons_cycle_closer, "out1", cons_pump, "in1", label="20")
c22 = Connection(cons_pump, "out1", condenser, "in2", label="21")
c23 = Connection(condenser, "out2", cons_heatsink, "in1", label="22")
c24 = Connection(cons_heatsink, "out1", cons_cycle_closer, "in1", label="23")
nw.add_conns(c21, c22, c23, c24)
# set feedflow and backflow temperature of heat source and consumer
T_hs_bf = 10
T_hs_ff = 15
T_cons_bf = 50
T_cons_ff = 70
# consumer cycle
c23.set_attr(T=T_cons_ff, p=10, fluid={"water": 1})
c24.set_attr(T=T_cons_bf)
# heat source cycle
c11.set_attr(T=T_hs_ff, p=1, fluid={"water": 1})
c13.set_attr(T=T_hs_bf, p=1)
# evaporation to fully saturated gas
c1.set_attr(x=1, fluid={wf: 1})
# parametrization components
# isentropic efficiency
cons_pump.set_attr(eta_s=0.8)
heatsource_pump.set_attr(eta_s=0.8)
compressor.set_attr(eta_s=0.85)
# pressure ratios
condenser.set_attr(pr1=0.98, pr2=0.98)
heatsource_evaporator.set_attr(pr1=0.98, pr2=0.98)
cons_heatsink.set_attr(pr=0.99)
int_heatex.set_attr(pr1=0.98, pr2=0.98)
# evaporation point
c1.set_attr(T_dew=T_hs_bf - 5)
# condensation point
c4.set_attr(T_bubble=T_cons_ff + 5)
# internal heat exchanger superheating
c2.set_attr(td_dew=5)
# consumer heat demand
cons_heatsink.set_attr(Q=-1.0)
grid = PowerSource("grid")
electricity = PowerBus("electricity distribution", num_in=1, num_out=3)
heat = HeatSink("heat production")
e1 = PowerConnection(grid, "power", electricity, "power_in1", label="e1")
e2 = PowerConnection(electricity, "power_out1", compressor, "power", label="e2")
e3 = PowerConnection(electricity, "power_out2", cons_pump, "power", label="e3")
e4 = PowerConnection(electricity, "power_out3", heatsource_pump, "power", label="e4")
h1 = HeatConnection(cons_heatsink, "heat", heat, "heat", label="h1")
nw.add_conns(e1, e2, e3, e4, h1)
nw.solve("design")
# evaporation point
c1.set_attr(T_dew=None)
heatsource_evaporator.set_attr(ttd_l=5)
# condensation point
c4.set_attr(T_bubble=None)
condenser.set_attr(ttd_u=5)
# solve the network again
nw.solve("design")
nw.assert_convergence()
return nw
We can run that function for different working fluids and plot the results:
import matplotlib.pyplot as plt
import pandas as pd
# make text reasonably sized
plt.rc("font", **{"size": 18})
cop = pd.DataFrame(columns=["COP"])
for wf in ["NH3", "R22", "R134a", "R152a", "R290", "R718"]:
nw = generate_network_with_starting_values(wf)
power = nw.get_conn("e1").E.val
heat = nw.get_conn("h1").E.val
cop.loc[wf] = heat / power
fig, ax = plt.subplots(1, figsize=(16, 8))
cop.plot.bar(ax=ax, legend=False)
ax.set_axisbelow(True)
ax.yaxis.grid(linestyle="dashed")
ax.set_xlabel("Name of working fluid")
ax.set_ylabel("Coefficicent of performance")
plt.tight_layout()
fig.savefig("COP_by_wf.svg")
Figure: Analysis of the COP using different working fluids¶
Figure: Analysis of the COP using different working fluids¶