Networks

The network class handles preprocessing, solving and post-processing. We will walk you through all the important steps.

Setup

Network container

The TESPy network contains all data of your plant, which in terms of the calculation is represented by a nonlinear system of equations. The system variables of your TESPy network are a subset of the following:

  • mass flow,

  • pressure,

  • enthalpy and

  • the mass fractions of the fluids of every Connection instance as well as

  • energy flow (power or heat) for PowerConnection instances and

  • component variables (e.g. compressor rpm) if declared a variable.

The solver will simplify the variable space in a presolving step and then solve for the remaining variables. In the following, you will find information on the Network setup, solving and debugging.

Unit specifications

There are a couple of ways to impose boundary conditions with the respective units to your models. The unit can be specified through two different ways:

  • as default units per quantity (e.g. temperature, temperature difference, power, …) for all properties of the components and connections, or

  • as individual units of a specific parameter.

Tip

TESPy implements pint in the back-end to handle the units. If you want to learn more on how to work with pint units, check out the respective documentation.

The example below shows, how you can setup units for your Network.

>>> from tespy.networks import Network
>>> nw = Network()

Default units are SI units. We can check, what unit is the default unit like this:

>>> nw.units.get_default("temperature")
'kelvin'
>>> nw.units.get_default("power")
'W'
>>> nw.units.get_default("specific_volume")
'm3/kg'

We can change the default units as follows:

>>> nw.units.set_defaults(temperature="degC")  # celsius
>>> nw.units.get_default("temperature")
'degC'
>>> nw.units.set_defaults(power="hp")  # horse power
>>> nw.units.get_default("power")
'hp'
>>> nw.units.set_defaults(efficiency="%")  # percent
>>> nw.units.set_defaults(pressure="bar", pressure_difference="bar")

The unit specification then applies to all parameters with the same quantity. For example, let’s set up a model of a compressor.

>>> from tespy.components import Compressor, Source, Sink
>>> from tespy.connections import Connection
>>> source = Source("gas inflow")
>>> compressor = Compressor("compressor")
>>> sink = Sink("gas discharge")
>>> c1 = Connection(source, "out1", compressor, "in1", label="c1")
>>> c2 = Connection(compressor, "out1", sink, "in1", label="c2")
>>> nw.add_conns(c1, c2)

Now we can parametrize our problem. It will utilize the unit specifications we created above.

>>> c1.set_attr(fluid={"air": 1}, m=1, p=1, T=25)  # p in bar, T in celsius
>>> c2.set_attr(p=3)
>>> compressor.set_attr(eta_s=80)  # efficiency in %

We can disable the iteration printouts by setting iterinfo=False

>>> nw.iterinfo = False
>>> nw.solve("design")

Now we can check results, e.g. the power of the compressor, which is expected to be in horse power:

>>> round(compressor.P.val, 0)
185.0

We can also retrieve the value with the respective unit, and then use pint to transform it into what ever unit we need:

>>> round(compressor.P.val_with_unit, 0)
<Quantity(185.0, 'horsepower')>
>>> round(compressor.P.val_with_unit.to("kW"), 0)
<Quantity(138.0, 'kilowatt')>

Alternatively, we can specify an individual unit using the Quantity class of pint. For that you have to utilize the UnitRegistry of your Network.units: ureg.

>>> ureg = nw.units.ureg
>>> c1.set_attr(m=ureg.Quantity(1, "t/h"))
>>> c1.m.val_with_unit
<Quantity(1, 'metric_ton / hour')>

Caution

If you now modify this number, it will remove the individual unit! On top, by specifying a bare number, you will always remove the unit information. This information is only reconnected once, the Network is initialized in context of a simulation!

>>> c1.set_attr(m=5)
>>> c1.m.val_with_unit
5
>>> nw.solve("design")
>>> c1.m.val_with_unit
<Quantity(5, 'kilogram / second')>

To understand, what quantity is associated with a specific parameter, you can do the following:

>>> compressor.dp.quantity
'pressure_difference'
>>> c1.td_dew.quantity
'temperature_difference'

It is also possible to use your own UnitRegistry:

>>> from pint import UnitRegistry
>>> ureg = UnitRegistry()
>>> nw.units.set_ureg(ureg)

Changing the ureg will only have effect on future specifications. Existing quantities are not changed.

Attention

The quantities pressure and pressure_difference as well as temperature and temperature_difference need to be set individually!

Printouts and logging

TESPy comes with an inbuilt logger. If you want to keep track of debugging-messages, general information, warnings or errors you should enable the logger. At the beginning of your python script e.g. add the following lines:

>>> from tespy.tools import logger
>>> import logging
>>> ();logger.define_logging(
...     logpath="myloggings", log_the_path=True, log_the_version=True,
...     screen_level=logging.ERROR, file_level=logging.DEBUG
... );()  # +doctest: ELIPSIS
(...)

The log-file will be saved to ~/.tespy/log_files/ by default. All available options are documented in the API.

Prior to solving the network there are options regarding the console printouts for the calculation progress. Specify, if you want to enable or disable convergence progress printouts:

>>> nw.iterinfo
False

# enable iteration information printout
>>> nw.iterinfo = True
>>> nw.iterinfo
True

# disable iteration information printout
>>> nw.iterinfo = False

Adding connections

As seen in the introduction, you will have to create your networks from the components and the connections between them. You can add connections directly or via subsystems using the corresponding methods:

>>> nw.add_conns()
>>> nw.add_subsystems()

Note

You do not need to add the components to the network, as they are inherited via the added connections. After having set up your network and added all required elements, you can start the calculation.

There are two types of connections, you can learn about them more in these sections.

Start calculation

You can start the solution process with the following line:

nw.solve(mode='design')

This starts the initialisation of your network and proceeds to its calculation. The specification of the calculation mode is mandatory, This is the list of available keywords:

  • mode is the calculation mode ('design'-calculation or 'offdesign'-calculation).

  • init_path is the path to a saved network state (json file path or a dict returned by nw.save(as_dict=True)) to use for initialisation.

  • design_path is the path to a saved network state (json file path or a dict returned by nw.save(as_dict=True)) which holds the information of your plant’s design point.

  • max_iter is the maximum amount of iterations performed by the solver.

  • min_iter is the minimum amount of iterations before a solution can be accepted (given the convergence criterion is satisfied).

  • init_only stop after initialisation (True/False).

  • init_previous use starting values from previous simulation (True/False).

  • use_cuda use cuda instead of numpy for matrix inversion, speeds up simulation in some cases by outsourcing calculation to graphics card. For more information please visit the cupy documentation.

There are two calculation modes available ('design' and 'offdesign'), which are explained in the subsections below. If you choose offdesign as calculation mode the specification of a design_path is mandatory.

The usage of an initialisation path is always optional but highly recommended, as the convergence of the solution process will be improved, if you provide good starting values. If you do not specify an init_path, the initialisation from saved results will be skipped. init_only=True usually is used for debugging. Or, you could use this feature to export a not solved network, if you want to do the parametrisation in .csv-files rather than your python script.

The init_previous parameter can be used in design and offdesign calculations and works very similar to specifying an init_path. In contrast, starting values are taken from the previous calculation. Specifying the init_path overwrites init_previous.

Design mode

The design mode is used to design your system and is always the first calculation of your plant. The offdesign calculation is always based on a design calculation! Obviously as you are designing the plant the way you want, you are flexible to choose the parameters to specify. However, you can not specify parameters that are based on a design case, as for example the isentropic efficiency characteristic function of a turbine or a pump. Specifying a value for the efficiency is of course possible.

Offdesign mode

The offdesign mode is used to calculate the performance of your plant, if parameters deviate from the plant’s design point. This can be partload operation, operation at different temperature or pressure levels etc.. Thus, before starting an offdesign calculation you have to design your plant first. By stating 'offdesign' as calculation mode, components and connections will switch to the offdesign mode. This means that all parameters provided as design parameters will be unset and all parameters provided as offdesign parameters will be set instead. You can specify a connection’s or component’s (off-)design parameters using the set_attr method.

For example, for a condenser you would usually design it to a maximum terminal temperature difference, in offdesign the heat transfer coefficient is selected. The heat transfer coefficient is calculated in the preprocessing of the offdesign case based on the results from the design-case. Of course, this applies to all other parameters in the same way. Also, the pressure drop is a result of the geometry for the offdesign case, thus we swap the pressure ratios with geometry independent zeta \(\frac{\zeta}{D^4}\) values.

mycomponent.set_attr(
    design=['ttd_u', 'pr1', 'pr2'], offdesign=['UA', 'zeta1_d4', 'zeta2_d4']
)

Note

Some parameters come with characteristic functions based on the design case properties. This means, that e.g. the isentropic efficiency of a turbine is calculated as function of the actual mass flow to design mass flow ratio. You can provide your own (measured) data or use the already existing data from TESPy. All standard characteristic functions are available at tespy.data module.

For connections it works in the same way, e.g. write

myconnection.set_attr(design=['h'], offdesign=['T'])

if you want to replace the enthalpy with the temperature for your offdesign. The temperature is a result of the design calculation and that value is then used for the offdesign calculation in this example.

To solve your offdesign calculation, use:

nw.solve(mode='offdesign', design_path='path/to/designpoint.json')
Component-level design references

In some situations a single component - or a small group of components - has been redesigned or replaced, so its individual design point differs from the one captured in the network-wide design_path. TESPy supports two complementary mechanisms to handle this.

Individual design path in an offdesign simulation

You can assign a separate design_path directly to any component. During the offdesign preprocessing TESPy will then load that component’s design values (e.g. the heat-transfer coefficient UA of a heat exchanger or the isentropic efficiency of a compressor) from the component’s own path, while every other component keeps using the global design_path. The design values of all connections adjacent to that component are also read from the component’s individual path and stored internally; they serve as the reference for characteristic functions that scale with mass-flow or similar.

# Heat exchanger was replaced; new design case is in a separate file.
heatex.set_attr(design_path='path/to/new_heatex_design.json')
nw.solve(mode='offdesign', design_path='path/to/network_design.json')

Setting design_path=None on a component reverts it to the global reference on the next solve.

Note

It is possible to use an individual design from an isolated sub-network that contains only the component of interest. Connection labels or even component labels in that file do not need to match the labels in the main network: if no label match is found TESPy tries to identify the correct entry via the port topology (inlet/outlet IDs) stored in the file. This topology information is only available in design files exported with TESPy 0.9.15 or higher. Older exports require matching labels.

Local offdesign component in a design simulation

It is also possible to keep most of the network in design mode, while one component that has already been built should be treated in offdesign mode. Setting local_offdesign=True together with a design_path on that component achieves this. The component’s offdesign equations become active for that solve, and its design values and adjacent-connection references are loaded from its own design_path.

# Existing heat exchanger is frozen in offdesign; rest of network is sized.
heatex.set_attr(
    local_offdesign=True,
    design_path='path/to/existing_heatex_design.json'
)
nw.solve(mode='design')

Solving and debugging

Everything about the solution process - how the variable space is reduced, how starting values are generated, how the equation system is decomposed into blocks and solved, how to interact with a running solution process and how to debug non-converging models - is described in the solver section.