{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Tutorial: StateJunction with Modular Property Package\n",
"\n",
"![](statejunction.svg)\n",
"\n",
"## Learning Outcomes\n",
"\n",
"- Demonstrate use of the StateJunction in IDAES\n",
"- Demonstrate different options available\n",
"\n",
"\n",
"## Problem Statement\n",
"\n",
"In this example, we will be passing a mixed benzene-toluene stream and a mixed H2O-NaOH stream through a StateJunction. The IDAES StateJunction is a pass-through block or simple pipe that is primarily used to link streams to/from different process alternatives in conceptual design applications. The inlet conditions are as follows:\n",
"\n",
"**Case 1:**\n",
"\n",
"Molar Flow Rate = 100 mol/s\n",
"\n",
"Temperature = 298.15 K\n",
"\n",
"Pressure = 101325 Pa\n",
"\n",
"Mole Fraction (benzene) = 0.6\n",
"\n",
"Mole Fraction (toluene) = 0.4\n",
"\n",
"**Case 2**\n",
"\n",
"Volumetric Flow Rate = 10 m^3/s\n",
"\n",
"H2O Concentration = 5000 mol/m^3\n",
"\n",
"NaOH Concentration = 25 mol/m^3\n",
"\n",
"Temperature = 298.15 K\n",
"\n",
"Pressure = 101325 Pa \n",
"\n",
"We will look at two cases in this tutorial:\n",
"\n",
"* Case 1: Use the StateJunction to solve a problem with constraints (requires a flowsheet-level solver).\n",
"\n",
"* Case 2: Use the StateJunction to solve a problem without constraints (does not require a flowsheet-level solver).\n",
"\n",
"\n",
"For more details, please refer to the IDAES documentation: https://idaes-pse.readthedocs.io/en/stable"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Importing necessary tools\n",
"\n",
"In the following cell, we will be importing the necessary components from Pyomo and IDAES."
]
},
{
"cell_type": "code",
"execution_count": 55,
"metadata": {},
"outputs": [],
"source": [
"# Import objects from pyomo package \n",
"from pyomo.environ import ConcreteModel, Constraint, value, SolverFactory\n",
"\n",
"# Import the solver\n",
"from idaes.core.solvers import get_solver\n",
"\n",
"# Import the main FlowsheetBlock from IDAES. The flowsheet block will contain the unit model\n",
"from idaes.core import FlowsheetBlock\n",
"\n",
"# Import the StateJunction block\n",
"from idaes.models.unit_models import StateJunction\n",
"\n",
"# Import idaes logger to set output levels\n",
"import idaes.logger as idaeslog\n",
"\n",
"# Import the modular property package to create a property block for the case 1 flowsheet\n",
"from idaes.models.properties.modular_properties.base.generic_property import GenericParameterBlock\n",
"\n",
"# Import the BT_ideal property package to create a configuration file for the GenericParameterBlock based on the ideal eos\n",
"from idaes.models.properties.modular_properties.examples.BT_ideal import configuration as configuration\n",
"\n",
"# Import the saponification property package to create a property block for the case 2 flowsheet\n",
"from idaes.models.properties.examples.saponification_thermo import SaponificationParameterBlock\n",
"\n",
"# Import the degrees_of_freedom function from the idaes.core.util.model_statistics package\n",
"# DOF = Number of Model Variables - Number of Model Constraints\n",
"from idaes.core.util.model_statistics import degrees_of_freedom"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Setting up the flowsheet\n",
"In the following cell, we will create the `ConcreteModel` foundation, attach the steady state flowsheet, and declare the property parameter block that will used for each of the cases.\n",
"\n",
"More information on this general workflow can be found here: https://idaes-pse.readthedocs.io/en/stable/how_to_guides/workflow/general.html"
]
},
{
"cell_type": "code",
"execution_count": 56,
"metadata": {},
"outputs": [],
"source": [
"m = ConcreteModel()\n",
"\n",
"m.fs = FlowsheetBlock(dynamic=False) # dynamic or ss flowsheet needs to be specified here\n",
"\n",
"m.fs.properties_1 = GenericParameterBlock(**configuration) # Case 1\n",
"\n",
"m.fs.properties_2 = SaponificationParameterBlock() # Case 2"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Case 1:\n",
"\n",
"In the following cell, we will be creating the StateJunction block, assigning the appropriate property package to it, and determining the initial degrees of freedom associated with the StateJunction."
]
},
{
"cell_type": "code",
"execution_count": 57,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"The initial degrees of freedom is: 5\n"
]
}
],
"source": [
"m.fs.statejunction_1 = StateJunction(property_package=m.fs.properties_1)\n",
"\n",
"DOF_initial = degrees_of_freedom(m)\n",
"print('The initial degrees of freedom is: {0}'.format(DOF_initial))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Fixing input specifications\n",
"In the following cell, we will be specifying the inlet conditions for the StateJunction block and re-evaluating the degrees of freedom to ensure the problem is square (i.e. DOF=0)."
]
},
{
"cell_type": "code",
"execution_count": 59,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"The final degrees of freedom is: 0\n"
]
}
],
"source": [
"m.fs.statejunction_1.inlet.flow_mol.fix(100) # converting to mol/s as unit basis is mol/s\n",
"m.fs.statejunction_1.inlet.mole_frac_comp[0, \"benzene\"].fix(0.6)\n",
"m.fs.statejunction_1.inlet.mole_frac_comp[0, \"toluene\"].fix(0.4)\n",
"m.fs.statejunction_1.inlet.pressure.fix(101325) # Pa\n",
"m.fs.statejunction_1.inlet.temperature.fix(298.15) # K\n",
"\n",
"DOF_final = degrees_of_freedom(m)\n",
"print('The final degrees of freedom is: {0}'.format(DOF_final))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Flowsheet Initialization\n",
"IDAES includes pre-written initialization routines for all unit models. Failing to initialize or having a poor intialization of a flowsheet may result in the problem being unsolvable. The output from initialization can be set to 7 different levels depending on the details required by the user. In general, when a particular output level is set, any information at that level and above gets picked up by logger. The default level taken by the logger is INFO. \n",
"\n",
"More information on these levels can be found in the IDAES documentation: \n",
"https://idaes-pse.readthedocs.io/en/stable/reference_guides/logging.html"
]
},
{
"cell_type": "code",
"execution_count": 61,
"metadata": {},
"outputs": [],
"source": [
"m.fs.statejunction_1.initialize(outlvl=idaeslog.WARNING)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Obtaining Simulation Results\n",
"In the following cell, the flowsheet will be solved using the IDAES `get_solver` tool. Setting `tee=True` will display the solver output."
]
},
{
"cell_type": "code",
"execution_count": 62,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Ipopt 3.13.2: nlp_scaling_method=gradient-based\n",
"tol=1e-06\n",
"\n",
"\n",
"******************************************************************************\n",
"This program contains Ipopt, a library for large-scale nonlinear optimization.\n",
" Ipopt is released as open source code under the Eclipse Public License (EPL).\n",
" For more information visit http://projects.coin-or.org/Ipopt\n",
"\n",
"This version of Ipopt was compiled from source code available at\n",
" https://github.com/IDAES/Ipopt as part of the Institute for the Design of\n",
" Advanced Energy Systems Process Systems Engineering Framework (IDAES PSE\n",
" Framework) Copyright (c) 2018-2019. See https://github.com/IDAES/idaes-pse.\n",
"\n",
"This version of Ipopt was compiled using HSL, a collection of Fortran codes\n",
" for large-scale scientific computation. All technical papers, sales and\n",
" publicity material resulting from use of the HSL codes within IPOPT must\n",
" contain the following acknowledgement:\n",
" HSL, a collection of Fortran codes for large-scale scientific\n",
" computation. See http://www.hsl.rl.ac.uk.\n",
"******************************************************************************\n",
"\n",
"This is Ipopt version 3.13.2, running with linear solver ma27.\n",
"\n",
"Number of nonzeros in equality constraint Jacobian...: 39\n",
"Number of nonzeros in inequality constraint Jacobian.: 0\n",
"Number of nonzeros in Lagrangian Hessian.............: 13\n",
"\n",
"Total number of variables............................: 16\n",
" variables with only lower bounds: 6\n",
" variables with lower and upper bounds: 8\n",
" variables with only upper bounds: 0\n",
"Total number of equality constraints.................: 16\n",
"Total number of inequality constraints...............: 0\n",
" inequality constraints with only lower bounds: 0\n",
" inequality constraints with lower and upper bounds: 0\n",
" inequality constraints with only upper bounds: 0\n",
"\n",
"iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n",
" 0 0.0000000e+00 9.90e-01 1.00e+00 -1.0 0.00e+00 - 0.00e+00 0.00e+00 0\n",
" 1 0.0000000e+00 9.89e-03 3.42e-01 -1.0 1.00e-02 - 9.90e-01 9.90e-01h 1\n",
" 2 0.0000000e+00 9.07e-05 9.17e+00 -1.0 9.99e-05 - 1.00e+00 9.91e-01h 1\n",
" 3 0.0000000e+00 7.28e-11 1.34e-03 -2.5 9.16e-07 - 1.00e+00 1.00e+00h 1\n",
"\n",
"Number of Iterations....: 3\n",
"\n",
" (scaled) (unscaled)\n",
"Objective...............: 0.0000000000000000e+00 0.0000000000000000e+00\n",
"Dual infeasibility......: 0.0000000000000000e+00 0.0000000000000000e+00\n",
"Constraint violation....: 5.4495236394249097e-14 7.2759576141834259e-11\n",
"Complementarity.........: 0.0000000000000000e+00 0.0000000000000000e+00\n",
"Overall NLP error.......: 5.4495236394249097e-14 7.2759576141834259e-11\n",
"\n",
"\n",
"Number of objective function evaluations = 4\n",
"Number of objective gradient evaluations = 4\n",
"Number of equality constraint evaluations = 4\n",
"Number of inequality constraint evaluations = 0\n",
"Number of equality constraint Jacobian evaluations = 4\n",
"Number of inequality constraint Jacobian evaluations = 0\n",
"Number of Lagrangian Hessian evaluations = 3\n",
"Total CPU secs in IPOPT (w/o function evaluations) = 0.002\n",
"Total CPU secs in NLP function evaluations = 0.000\n",
"\n",
"EXIT: Optimal Solution Found.\n"
]
}
],
"source": [
"solver = get_solver()\n",
"result = solver.solve(m, tee=True)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### View Results\n",
"\n",
"As expected of a pass-through block, the report will show that stream composition of the outlet is identical to that of the inlet."
]
},
{
"cell_type": "code",
"execution_count": 64,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"====================================================================================\n",
"Unit : fs.statejunction_1 Time: 0.0\n",
"------------------------------------------------------------------------------------\n",
" Stream Table\n",
" Units Inlet Outlet \n",
" Total Molar Flowrate mole / second 100.00 100.00\n",
" Total Mole Fraction benzene dimensionless 0.60000 0.60000\n",
" Total Mole Fraction toluene dimensionless 0.40000 0.40000\n",
" Temperature kelvin 298.15 298.15\n",
" Pressure pascal 1.0132e+05 1.0132e+05\n",
"====================================================================================\n"
]
}
],
"source": [
"m.fs.statejunction_1.report()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Case 2:\n",
"\n",
"In the following cell, we will be creating the StateJunction block, assigning the appropriate property package to it, and determining the initial degrees of freedom associated with the StateJunction."
]
},
{
"cell_type": "code",
"execution_count": 66,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"The initial degrees of freedom is: 0\n"
]
}
],
"source": [
"m.fs.statejunction_2 = StateJunction(property_package=m.fs.properties_2)\n",
"\n",
"DOF_initial = degrees_of_freedom(m)\n",
"print('The initial degrees of freedom is: {0}'.format(DOF_initial))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Fixing input specifications\n",
"In the following cell, we will be specifying the inlet conditions for the StateJunction block and re-evaluating the degrees of freedom to ensure the problem is square (i.e. DOF=0)."
]
},
{
"cell_type": "code",
"execution_count": 68,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"The final degrees of freedom is: 0\n"
]
}
],
"source": [
"m.fs.statejunction_2.inlet.flow_vol.fix(10) # m^3/s\n",
"\n",
"m.fs.statejunction_2.inlet.conc_mol_comp[0, \"H2O\"].fix(5000) # mol/m^3\n",
"m.fs.statejunction_2.inlet.conc_mol_comp[0, \"NaOH\"].fix(25) # mol/m^3\n",
"m.fs.statejunction_2.inlet.conc_mol_comp[0, \"EthylAcetate\"].fix(50) # mol/m^3\n",
"m.fs.statejunction_2.inlet.conc_mol_comp[0, \"SodiumAcetate\"].fix(100) # mol/m^3\n",
"m.fs.statejunction_2.inlet.conc_mol_comp[0, \"Ethanol\"].fix(200) # mol/m^3\n",
"\n",
"m.fs.statejunction_2.inlet.pressure.fix(101325) # Pa\n",
"m.fs.statejunction_2.inlet.temperature.fix(298.15) # K\n",
"\n",
"DOF_final = degrees_of_freedom(m)\n",
"print('The final degrees of freedom is: {0}'.format(DOF_final))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Flowsheet Initialization\n",
"IDAES includes pre-written initialization routines for all unit models. Failing to initialize or having a poor intialization of a flowsheet may result in the problem being unsolvable. The output from initialization can be set to 7 different levels depending on the details required by the user. In general, when a particular output level is set, any information at that level and above gets picked up by logger. The default level taken by the logger is INFO. \n",
"\n",
"More information on these levels can be found in the IDAES documentation: \n",
"https://idaes-pse.readthedocs.io/en/stable/reference_guides/logging.html"
]
},
{
"cell_type": "code",
"execution_count": 70,
"metadata": {},
"outputs": [],
"source": [
"m.fs.statejunction_2.initialize(outlvl=idaeslog.WARNING)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### View Results\n",
"\n",
"Since this problem has no constraints, an additional solver is not required to compute the properties of the outlet stream. As the following report will show, the stream composition of the inlet is identical to that of the outlet."
]
},
{
"cell_type": "code",
"execution_count": 71,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"====================================================================================\n",
"Unit : fs.statejunction_2 Time: 0.0\n",
"------------------------------------------------------------------------------------\n",
" Stream Table\n",
" Units Inlet Outlet \n",
" Volumetric Flowrate meter ** 3 / second 10.000 10.000\n",
" Molar Concentration H2O mole / meter ** 3 5000.0 5000.0\n",
" Molar Concentration NaOH mole / meter ** 3 25.000 25.000\n",
" Molar Concentration EthylAcetate mole / meter ** 3 50.000 50.000\n",
" Molar Concentration SodiumAcetate mole / meter ** 3 100.00 100.00\n",
" Molar Concentration Ethanol mole / meter ** 3 200.00 200.00\n",
" Temperature kelvin 298.15 298.15\n",
" Pressure pascal 1.0132e+05 1.0132e+05\n",
"====================================================================================\n"
]
}
],
"source": [
"m.fs.statejunction_2.report()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"celltoolbar": "Tags",
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.13"
}
},
"nbformat": 4,
"nbformat_minor": 2
}