Stream Functionalities

Streams: the Building Blocks of Process Flowsheets

Beyond equilibrium calculations in isolated systems, ChemApp adopts a concept called ‘Streams’ to enable connections between different systems. Stream is always associated with a composition, temperature, and pressure. Such concept is the building block of the ‘Flowsheeting’ approach used in the modeling of complex industrial processes. In essence, a ‘Stream’ consists of an assemblage of chemical species with defined amounts which can represent a material either as a mechanical mixture (not in thermodynamic equilibrium) or as a chemically stable mixture (in thermodynamic equilibrium). This chapter presents the functions available for streams creation, reaction, splitting and combination.

The StreamCalculation Class

The StreamCalculation class provides the functionality required to setup and perform equilibrium calculations that take streams as inputs. For example, ore, reductant, and flux streams can be combined. With these calculations, both absolute and relative values of thermodynamic properties (Cp, H, S, G, V; and ΔCp, ΔH, ΔS, ΔG, ΔV) can be calculated, as well as phases and phase constituents distributions and individual thermodynamic properties.

Stream-based equilibrium calculations follow a typical workflow:

  1. Load database - Load the thermochemical database appropriate for your thermochemical system.

  2. Set units (recommended) - Define consistent units for amounts, temperature, pressure, volume, and energy.

  3. Create streams - Define input materials with compositions, temperatures, and pressures.

  4. Set incoming amounts - Specify how much of each material enters the process.

  5. Set equilibrium conditions - Define reaction temperature, pressure, or other constraints.

  6. Calculate equilibrium - Perform the thermodynamic calculation.

  7. Extract results - Obtain compositions, properties, and thermodynamic data.

  8. Product separation - Split products by physical properties (e.g. density thresholds), phase selection, element selection, fractions for further iterations.

The functions described below can simplify this workflow by allowing steps 3-4 and 5-6 to be performed as single operations.

The following sections describe the stream functionality available in the friendly module. For detailed examples, see the StreamCalculation examples in the examples/30-friendly/StreamCalculation/ directory.

Creation of Streams

ChemApp provides several conceptual approaches for creating streams, each can be applied for different modeling scenarios:

Create streams by instantiating a class object: Build streams by specifying exactly what system conditions, materials and amounts you want through Manual Stream Creation,Complex Formula-Based Stream Creation, or StreamState-Based Creation methods.

Create streams from results of equilibrium calculations: Extract streams from the results of previous equilibrium calculations through Result-Based Stream Creation, allowing you to obtain specific phases, elements, or physical states as separate process streams.

Create streams by filtering/specifying additional conditions: Combine extraction with filtering capabilities through Product Separation and Stream Splitting to create streams that meet specific criteria such as excluding unwanted phases, focusing on particular elements, or applying thresholds to ignore trace amounts.

These approaches reflect different stages of process modeling: starting with known feed compositions, extracting products from reaction results, and refining stream definitions for downstream processing steps.

Manual Stream Creation

create_st

This is the basic stream generation function used to initialize a stream state with a specified name, temperature, and pressure. ChemApp for Python provides functions for manually defining phase constituent amounts, such as set_IA_pc(), which can be used to add or modify the stream content after its creation.

Example:

# Create basic stream with T and P
hot_metal = StreamCalculation.create_st("HotMetal", T=1300.0, P=1.0)
StreamCalculation.set_IA_pc("HotMetal", "LIQUID#1", "Fe", 94.6225)
StreamCalculation.set_IA_pc("HotMetal", "LIQUID#1", "C", 4.35)
StreamCalculation.set_IA_pc("HotMetal", "LIQUID#1", "Si", 0.505)

Complex Formula-Based Stream Creation

create_stream_from_complex_formulas / create_st_cfs

Create a new stream from complex formulas by performing an independent isothermal equilibrium calculation at the given temperature and pressure. The create_st_cfs() function is a shorthand version.

The complex formulas are entered as incoming amounts and the equilibrium result is used to create the new stream. This is useful for defining streams from chemical compositions defined by their base components, like “CaO”, “SiO2” and “Al2O3”, instead of their exact phase constituents. This function is not limited to complex formulas, elements or any chemcial formula can also be used in a similar way.

Note: both create_stream_from_complex_formulas() and create_st_cfs() will yield the same results, and can be used interchangeably.

Example:

# Create stream from complex formulas
slag = StreamCalculation.create_stream_from_complex_formulas(
    name="EAF_Slag",
    cfs=["CaO", "SiO2", "FeO", "MgO", "Al2O3", "MnO", "Cr2O3", "TiO2"],
    amounts=[25.6, 13.6, 34.2, 11.3, 6.0, 6.4, 2.5, 0.4],
    T=1600.0,
    P=1.0,
    units={"T": TemperatureUnit.C, "P": PressureUnit.atm,
           "A": AmountUnit.kg},
)

# Create stream from complex formulas (shorthand version)
hot_metal = StreamCalculation.create_st_cfs(
    "HotMetal",
    ["Fe", "C", "Si", "S", "P", "Mn"],
    [94.6225, 4.35, 0.505, 0.0225, 0.1, 0.4],
    T=1300.0,
)
oxygen = StreamCalculation.create_st_cfs("Oxygen", ["O2"], [6.0], T=25.0)

StreamState-Based Creation

A stream object can be created from a calculation result (e.g. chemapp.friendly.StreamCalculation.create_st()) or by instantiating a chemapp.core.StreamState object. The former will allow to make use of the results of a previous equilibrium calculation to access extensive properties or to perform further equilibrium calculations involving the specified stream. The latter will not provide extensive stream properties as it is not connected to a previous calculation.

Example:

from chemapp.core import StreamState, PhaseConstituentIncomingAmountState

# Create stream using StreamState
air_methane = StreamState("Air + Methane", 25.0, 1.0, Units.get())
air_methane.A_pcs = [
    PhaseConstituentIncomingAmountState("gas_ideal", "N2", 0.78),
    PhaseConstituentIncomingAmountState("gas_ideal", "O2", 0.22),
    PhaseConstituentIncomingAmountState("gas_ideal", "CH4", 3.0),
]

Result-Based Stream Creation

create_stream_by_state

Convenience routine for creating streams limited to specific aggregate phases. These are particularly useful when extracting a specific state of matter from calculation results.

Example:

from chemapp.core import PhaseMatterState

# Extract the solids from a calculation result
solid_stream = result.create_stream_by_state(
   state=PhaseMatterState.SOLID,
   name="solid_residue"
)

# Extract the liquids from a calculation result
liquid_stream = result.create_stream_by_state(
    state=PhaseMatterState.LIQUID,
    name="liquid_phases",
    include_zeros=True,
)

State-Specific Stream Creation: create_gas_stream / create_liquid_stream / create_solid_stream

Convenience routines for creating streams limited to specific aggregate phases. These are particularly useful when extracting specific phases from calculation results.

Example:

# Create phase-specific streams from results
gas_stream = result.create_gas_stream(name="gas_products")

# Create liquid stream including phases with zero amounts
liquid_stream = result.create_liquid_stream(name="liquid_products", include_zeros=True)

Filtered Stream Creation: create_stream

The most versatile stream creation routine that allows various types of filters and selections, including or excluding certain phases and elements. It also allows setting a threshold to ignore residual amounts of phase constituents.

Example:

# Create stream with phase filtering
slag = result.create_stream(
    "Slag",
    phs_exclude=["gas_ideal", "BCC#1"],
)
steel = result.create_stream(
    "Steel",
    phs_include=["BCC#1"],
)

# Create stream with element filtering and element threshold
oxides = result.create_stream(
 "Oxides",
 els_include=["O"],
 el_threshold=1e-6,
 phsconfigs=ThermochemicalSystem.get_config_phs(),
 )

Stream Operations and Management

These functions manage named streams within ChemApp for use in calculations:

set_IA_pc

Set the incoming amount of a phase constituent in a named stream. This function is used to build up stream compositions by specifying how much of each phase constituent enters the process. Use set_IA_pc() to define stream compositions constituent by constituent.

Example:

# Set incoming amounts for phase constituents
StreamCalculation.set_IA_pc("stream", "LIQUID", "Fe", 100.0)
StreamCalculation.set_IA_pc("stream", "LIQUID", "C", 5.0)

Stream Property Access: get_st_Cp / get_st_G / get_st_H / get_st_S / get_st_V

Get the heat capacity, Gibbs energy, enthalpy, entropy, or volume of a specified stream. These functions return the thermodynamic properties based on the stream’s current composition, temperature, and pressure. Use get_st_H() and related property functions to retrieve thermodynamic properties.

Example:

# Get stream properties
enthalpy = StreamCalculation.get_st_H("inlet_stream")
volume = StreamCalculation.get_st_V("inlet_stream")
heat_capacity = StreamCalculation.get_st_Cp("inlet_stream")

Stream Cleanup: remove_st / remove_sts

Remove a specified stream or all streams from ChemApp. Used for cleanup or when resetting calculations. Use remove_st() to remove specific streams or remove_sts() to remove all streams.

Example:

# Remove specific stream
StreamCalculation.remove_st("old_stream")

# Remove all streams
StreamCalculation.remove_sts()

Process Flowsheeting with Stream Reactions

The StreamCalculation class provides four main types of reaction calculations that form the backbone of process flowsheet modeling:

Isothermal reaction: reaction_isothermal

Perform an isothermal reaction at a specified temperature. The input streams undergo equilibrium calculation at constant temperature and pressure. This is useful for modeling reactions where temperature is controlled. Use reaction_isothermal() for reactions at constant temperature.

Example:

# Isothermal reaction at 1600 C
result = StreamCalculation.reaction_isothermal(
    streams=[ore, reductant], T=1600.0, P=1.0
)

# Split streams by density to create a steel stream
off_gas, slag, steel_stream = result.create_streams_by_density([1.0, 5.0])

# Perform a new isothermal reaction with alloying content
# Assuming Al_stream, and FeSi_stream are defined
alloying_result = StreamCalculation.reaction_isothermal(
    [steel_stream, Al_stream, FeSi_stream], T=steel_stream.T
)

Adiabatic reaction: reaction_adiabatic

Perform an adiabatic reaction with zero heat exchange. The reaction proceeds under adiabatic conditions where the enthalpy change is zero. Use reaction_adiabatic() for adiabatic processes. without external heating or cooling, allowing the temperature to change based on the heat of reaction. This models reactions in well-insulated systems.

Example:

# Adiabatic reaction in LD converter
converter_result = StreamCalculation.reaction_adiabatic(
    [hot_metal, oxygen, scrap, lime]
)

Diathermal reaction: reaction_diathermal

Perform a diathermal reaction with specified heat input. The amount of heat added or removed from the system can be specified. This allows modeling of reactions with controlled heating or cooling. Use reaction_diathermal() for reactions with controlled heat exchange.

Example:

# Diathermal reaction with 1 MJ heat input
result = StreamCalculation.reaction_diathermal(
    streams=[stream1, stream2], dH=1e6, P=1.0  # Heat input in J
)

General reaction: reaction

Customize the conditions of a reaction. The reaction() function allows specifying conditions freely, including temperature, pressure, final activities, target variables and more. It serves as a general-purpose alternative to the more specific reaction_isothermal, reaction_adiabatic, and reaction_diathermal functions. By passing different combinations of conditions, you can model a wide range of processes, from simple isothermal reactions to complex target-based calculations where an input is adjusted to meet a specific outcome.

Example:

# Liquidus reaction with temperature as a target
result = StreamCalculation.reaction(
    streams=[stream1, stream2],
    setters=[
        (StreamCalculation.set_eq_P, {"value": 1.0}),
        (StreamCalculation.set_target_formation_of_ph, {"ph": "LIQUID"}),
    ],
    calculate=StreamCalculation.calculate_eq_T,
    T_guess=1600.0,
    print_results=False,
)

Process Integration Examples - Flowsheeting at Work

Processes can be modeled as a sequence of stream reactions:

def secondary_steelmaking(ld_streams, alloying_streams, Ar, deS_agent):
    # LD converter
    converter_result = StreamCalculation.reaction_adiabatic(ld_streams)
    off_gas, slag, steel = converter_result.create_streams_by_density(
        [1.0, 5.0]
    )

    # Tapping and alloying
    alloying_result = StreamCalculation.reaction_isothermal(
        [steel] + alloying_streams, T=steel.T
    )
    off_gas_tap, slag_tap, alloyed_steel = (
        alloying_result.create_streams_by_density([1.0, 5.0])
    )

    # Degassing
    degasser_result = StreamCalculation.reaction_isothermal(
        [Ar, alloyed_steel], T=alloyed_steel.T, P=0.01
    )

    return refined_steel, combined_slag, combined_off_gas

A more detailed example can be found in Worked Examples.

Product Separation and Stream Splitting

Advanced stream processing functions for separating phases by physical properties are essential for modeling downstream separation processes:

Density-Based Separation

create_streams_by_density

Create multiple streams separated by density thresholds from an equilibrium result. This function takes density cutoff values and creates separate streams for light, medium, and heavy phases, for example. Useful for modeling physical separation processes like flotation or gravity separation. Use create_streams_by_density() to split results into multiple streams by density.

Note 1: This function assumes a constant density unit of g/cm³ for all calculations. This is independent of the units set in ChemApp for Python. Note 2: If the density calculation fails for any phase, that phase is assigned a density of “0” g/cm³.

Example:

# Separate products by density (gas, slag, metal)
off_gas, slag, steel = converter_result.create_streams_by_density(
    [1.0, 5.0]
)

# Three-phase separation with custom names
light_stream, medium_stream, heavy_stream = (
    result.create_streams_by_density(
        split_at=[2.0, 6.0], names=["light", "medium", "heavy"]  # g/cm³
    )
)

split_by_density

Split existing streams by density thresholds. This is typically applied to StreamState objects to separate them into density ranges. Use split_by_density() to split stream states by density.

Example:

# Split stream by density
light, heavy = stream.split_by_density(split_at=[4.0])

Phase-Based Separation

Extract specific phases from calculation results:

Example:

# Extract different phases from converter results
slag = converter_result.create_stream(
    "Slag", phs_exclude=["gas_ideal", "LIQUID#1"]
)
steel = converter_result.create_stream(
    "Steel", phs_include=["LIQUID#1"]
)

Element-Based Separation

Create streams that track specific elements throughout your process. This is particularly useful for monitoring element distribution by filtering the equilibrium results to show only phases containing elements of interest.

Example:

# Assuming we have a result object from a previous calculation

# Track calcium distribution across all phases
ca_containing_phases = result.create_stream(
    "Calcium_tracker",
    els_include=["Ca"]
)

# Monitor sulfur removal efficiency
sulfur_stream = result.create_stream(
    "Sulfur_distribution",
    els_include=["S"],
    el_threshold=1e-6  # Ignore trace amounts below 1 ppm
)

# Track multiple alloying elements
alloying_elements = result.create_stream(
    "Alloying_monitor",
    els_include=["Mn", "Si", "Al", "Ti"],
    el_threshold=1e-5
)

This approach enables effective monitoring of element fate and distribution throughout complex process calculations.

Stream Combination

Multiple streams can be combined for further processing. When combining streams, the resulting stream inherits the temperature and pressure from the calling (first) stream object, while extensive properties (such as enthalpy, volume, and entropy) from individual streams are dropped and must be recalculated if needed for the combined stream.

Example:

# Combine multiple off-gas streams
# The combined stream will have the temperature and pressure of off_gas
total_off_gas = (
    off_gas.combine_with(off_gas_tap)
    .combine_with(off_gas_dg)
    .combine_with(off_gas_deS)
)

Applications and Parametric Studies

Stream calculations enable sophisticated process modeling and optimization studies. The notebooks in this section demonstrate real-world applications:

Secondary Steelmaking Example

The secondary steelmaking process involves multiple steps that can be modeled using stream reactions:

  1. Primary steelmaking - LD converter with hot metal, oxygen, scrap, and lime

  2. Alloying - Addition of ferro-alloys for composition adjustment

  3. Degassing - Vacuum treatment for gas removal

  4. Desulfurization - Chemical treatment for sulfur removal

Each step can be modeled as a separate reaction with appropriate stream inputs and outputs, demonstrating the power of the flowsheeting approach.

Parametric Studies

Stream calculations facilitate parametric studies where process variables are systematically varied:

Example:

# Parametric study of titanium content in scrap
Ti_scrap = []
Ti_steel = []

for Ti_A in np.arange(0, 1, 0.1):
    scrap = StreamCalculation.create_st_cfs(
        "Scrap", ["Fe", "Ti"], [18.0 - Ti_A, Ti_A], T=25.0
    )

    # Perform steelmaking calculation
    # Note that this function in imaginary as a placeholder.
    # This example can not run without definition of secondary_steelmaking
    refined_steel, slag, off_gas = secondary_steelmaking(
        ld_streams, alloying_streams, Ar, deS_agent
    )

    # Analyze results
    Ti_scrap.append(Ti_A)
    Ti_steel.append(refined_steel.phs["LIQUID#1"].scs["Ti"].A)

This approach enables optimization of process conditions and understanding of how input variables affect final product quality.

Complete Workflow Example

A comprehensive steelmaking process workflow demonstrating the integration of multiple stream operations:

# 1. Set up units and load database
Units.set(A=AmountUnit.tonne, T=TemperatureUnit.C, P=PressureUnit.bar)
ThermochemicalSystem.load("steelmaking.cst")

# 2. Create input streams
hot_metal = StreamCalculation.create_st_cfs(
    "HotMetal",
    ["Fe", "C", "Si", "S", "P", "Mn"],
    [94.6225, 4.35, 0.505, 0.0225, 0.1, 0.4],
    T=1300.0,
)
oxygen = StreamCalculation.create_st_cfs("Oxygen", ["O2"], [6.0], T=25.0)
scrap = StreamCalculation.create_st_cfs("Scrap", ["Fe"], [18.0], T=25.0)
lime = StreamCalculation.create_st_cfs("Lime", ["CaO"], [2.5], T=25.0)

# 3. Primary steelmaking (LD converter)
converter_result = StreamCalculation.reaction_adiabatic(
    [hot_metal, oxygen, scrap, lime]
)

# 4. Separate products by density
off_gas, slag, steel = converter_result.create_streams_by_density(
    [1.0, 5.0]
)

# 5. Secondary treatment with alloying
Al = StreamCalculation.create_st_cfs("Al", ["Al"], [0.15], T=25.0)
Fe_Si = StreamCalculation.create_st_cfs(
    "Fe_Si", ["Si", "Fe"], [0.0238, 0.0102], T=25.0
)

alloying_result = StreamCalculation.reaction_isothermal(
    [steel, Al, Fe_Si], T=steel.T
)

# 6. Extract final products
final_steel = alloying_result.create_stream(
    "FinalSteel", phs_include=["LIQUID#1"]
)

print(f"Final steel composition: {final_steel.phs['LIQUID#1'].scs['Ti'].A}")
print(f"Steel temperature: {final_steel.T} °C")

This example demonstrates the complete workflow from raw materials to finished steel, showcasing the power of stream-based process modeling.

See also

For additional examples and practical applications of stream functionalities:

Public Examples Repository: The GTT Technologies ChemApp Examples repository provides comprehensive real-world examples and tutorials. Visit https://github.com/GTT-Technologies/ChemApp-Examples.git to explore industrial process modeling scenarios, parametric studies, and advanced flowsheeting applications.

Reference Documentation: