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:
Load database - Load the thermochemical database appropriate for your thermochemical system.
Set units (recommended) - Define consistent units for amounts, temperature, pressure, volume, and energy.
Create streams - Define input materials with compositions, temperatures, and pressures.
Set incoming amounts - Specify how much of each material enters the process.
Set equilibrium conditions - Define reaction temperature, pressure, or other constraints.
Calculate equilibrium - Perform the thermodynamic calculation.
Extract results - Obtain compositions, properties, and thermodynamic data.
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:
Primary steelmaking - LD converter with hot metal, oxygen, scrap, and lime
Alloying - Addition of ferro-alloys for composition adjustment
Degassing - Vacuum treatment for gas removal
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:
chemapp.friendly
module reference documentationWorked Examples Streams and Density Functionalities example for detailed practical implementation