.. _ch-stream-calculations: :new:`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 :py:class:`~chemapp.friendly.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 :py:func:`~chemapp.friendly.StreamCalculation.set_IA_pc`, which can be used to add or modify the stream content after its creation. Example: .. code:: python # 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 :py:func:`~chemapp.friendly.StreamCalculation.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 :py:func:`~chemapp.friendly.StreamCalculation.create_stream_from_complex_formulas` and :py:func:`~chemapp.friendly.StreamCalculation.create_st_cfs` will yield the same results, and can be used interchangeably. Example: .. code:: python # 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. :py:func:`chemapp.friendly.StreamCalculation.create_st`) or by instantiating a :py:class:`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: .. code:: python 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: .. code:: python 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: .. code:: python # 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: .. code:: python # 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 :py:func:`~chemapp.friendly.StreamCalculation.set_IA_pc` to define stream compositions constituent by constituent. Example: .. code:: python # 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 :py:func:`~chemapp.friendly.StreamCalculation.get_st_H` and related property functions to retrieve thermodynamic properties. Example: .. code:: python # 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 :py:func:`~chemapp.friendly.StreamCalculation.remove_st` to remove specific streams or :py:func:`~chemapp.friendly.StreamCalculation.remove_sts` to remove all streams. Example: .. code:: python # 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 :py:func:`~chemapp.friendly.StreamCalculation.reaction_isothermal` for reactions at constant temperature. Example: .. code:: python # 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 :py:func:`~chemapp.friendly.StreamCalculation.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: .. code:: python # 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 :py:func:`~chemapp.friendly.StreamCalculation.reaction_diathermal` for reactions with controlled heat exchange. Example: .. code:: python # 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 :py:func:`~chemapp.friendly.StreamCalculation.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: .. code:: python # 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: .. code:: python 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 :doc:`/using-friendly/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 :py:func:`~chemapp.core.Result.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: .. code:: python # 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 :py:func:`~chemapp.friendly.StreamState.split_by_density` to split stream states by density. Example: .. code:: python # Split stream by density light, heavy = stream.split_by_density(split_at=[4.0]) Phase-Based Separation ====================== Extract specific phases from calculation results: Example: .. code:: python # 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: .. code:: python # 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: .. code:: python # 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: .. code:: python # 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: .. code:: python # 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**: - :mod:`chemapp.friendly` module reference documentation - :doc:`/using-friendly/worked-examples` Streams and Density Functionalities example for detailed practical implementation