From f32ebbb2974ba7d8318c8126f64f4a73a64d1fb0 Mon Sep 17 00:00:00 2001 From: tannerpolley Date: Thu, 12 Jun 2025 16:23:37 -0600 Subject: [PATCH 1/9] Flash Unite and HDA Flowsheet Tutorial Revisions to reflect changes to initialization and scaling --- .../notebooks/docs/tut/core/flash_unit.ipynb | 1584 +++++- .../docs/tut/core/flash_unit_doc.ipynb | 3412 ++++++------- .../docs/tut/core/flash_unit_exercise.ipynb | 731 ++- .../docs/tut/core/flash_unit_solution.ipynb | 1536 +++++- .../docs/tut/core/flash_unit_test.ipynb | 1324 ++++- .../docs/tut/core/flash_unit_usr.ipynb | 1536 +++++- .../docs/tut/core/hda_flowsheet.ipynb | 2407 +++++++-- .../docs/tut/core/hda_flowsheet_doc.ipynb | 4057 ++++++++++----- .../tut/core/hda_flowsheet_exercise.ipynb | 4038 ++++++++++----- .../tut/core/hda_flowsheet_solution.ipynb | 4522 +++++++++++------ .../docs/tut/core/hda_flowsheet_test.ipynb | 4362 ++++++++++------ .../docs/tut/core/hda_flowsheet_usr.ipynb | 4522 +++++++++++------ 12 files changed, 23652 insertions(+), 10379 deletions(-) diff --git a/idaes_examples/notebooks/docs/tut/core/flash_unit.ipynb b/idaes_examples/notebooks/docs/tut/core/flash_unit.ipynb index 6dc88109..2fe01927 100644 --- a/idaes_examples/notebooks/docs/tut/core/flash_unit.ipynb +++ b/idaes_examples/notebooks/docs/tut/core/flash_unit.ipynb @@ -2,14 +2,16 @@ "cells": [ { "cell_type": "code", - "execution_count": null, "metadata": { "tags": [ "header", "hide-cell" - ] + ], + "ExecuteTime": { + "end_time": "2025-06-06T16:45:45.923673Z", + "start_time": "2025-06-06T16:45:45.919855Z" + } }, - "outputs": [], "source": [ "###############################################################################\n", "# The Institute for the Design of Advanced Energy Systems Integrated Platform\n", @@ -23,37 +25,46 @@ "# All rights reserved. Please see the files COPYRIGHT.md and LICENSE.md\n", "# for full copyright and license information.\n", "###############################################################################" - ] + ], + "outputs": [], + "execution_count": 1 }, { - "cell_type": "markdown", "metadata": {}, + "cell_type": "markdown", "source": [ - "# Flash Unit Model\n", + "# Flash Unit Model Tutorial\n", "\n", - "Author: Jaffer Ghouse \n", - "Maintainer: Andrew Lee \n", - "Updated: 2023-06-01 \n", + "Author: Jaffer Ghouse
\n", + "Maintainer: Tanner Polley
\n", + "Updated: 2025-06-03\n", "\n", - "In this module, we will familiarize ourselves with the IDAES framework by creating and working with a flowsheet that contains a single flash tank. The flash tank will be used to perform separation of Benzene and Toluene. The inlet specifications for this flash tank are:\n", + "In this module, we will familiarize ourselves with the IDAES framework by creating and working with a flowsheet that contains a single flash tank. The flash tank will be used to perform separation of Benzene and Toluene.\n", "\n", - "Inlet Specifications:\n", - "* Mole fraction (Benzene) = 0.5\n", - "* Mole fraction (Toluene) = 0.5\n", - "* Pressure = 101325 Pa\n", - "* Temperature = 368 K\n", + "The general workflow of setting up an IDAES flowsheet is the following:\n", + "\n", + "- 1 Importing Modules\n", + "- 2 Building a Model\n", + "- 3 Scaling the Model\n", + "- 4 Specifying the Model\n", + "- 5 Initializing the Model\n", + "- 6 Solving the Model\n", + "- 7 Analyzing and Visualizing the Results\n", "\n", - "We will complete the following tasks:\n", - "* Create the model and the IDAES Flowsheet object\n", - "* Import the appropriate property packages\n", - "* Create the flash unit and set the operating conditions\n", - "* Initialize the model and simulate the system\n", - "* Demonstrate analyses on this model through some examples and exercises\n", + "We will complete each of these steps as well as demonstrate analyses on this model through some examples and exercises\n", "\n", "## Key links to documentation\n", "* Main IDAES online documentation page: https://idaes-pse.readthedocs.io/en/stable/\n", - "\n", - "## Create the Model and the IDAES Flowsheet\n", + "* General Workflow: https://idaes-pse.readthedocs.io/en/stable/how_to_guides/workflow/general.html\n", + "* Flash Unit Model Documentation: https://idaes-pse.readthedocs.io/en/stable/reference_guides/model_libraries/generic/unit_models/flash.html\n", + "\n" + ] + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": [ + "## 1 Import Modules\n", "\n", "In the next cell, we will perform the necessary imports to get us started. From `pyomo.environ` (a standard import for the Pyomo package), we are importing `ConcreteModel` (to create the Pyomo model that will contain the IDAES flowsheet) and `SolverFactory` (to create the object we will use to solve the equations). We will also import `Constraint` as we will be adding a constraint to the model later in the module. Lastly, we also import `value` from Pyomo. This is a function that can be used to return the current numerical value for variables and parameters in the model. These are all part of Pyomo.\n", "\n", @@ -66,23 +77,31 @@ ] }, { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:49.094293Z", + "start_time": "2025-06-06T16:45:45.938076Z" + } + }, "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], "source": [ "from pyomo.environ import ConcreteModel, SolverFactory, Constraint, value\n", "from idaes.core import FlowsheetBlock\n", "\n", "# Import idaes logger to set output levels\n", - "import idaes.logger as idaeslog" - ] + "import idaes.logger as idaeslog\n", + "%matplotlib inline" + ], + "outputs": [], + "execution_count": 2 }, { - "cell_type": "markdown", "metadata": {}, + "cell_type": "markdown", "source": [ - "In the next cell, we will create the `ConcreteModel` and the `FlowsheetBlock`, and attach the flowsheet block to the Pyomo model.\n", + "## 2 Create the Model and IDAES Flowsheet\n", + "\n", + "In the next cell, we will create the `ConcreteModel` object often named `m` (which comes from Pyomo) and then connect the `FlowsheetBlock` (which comes from IDAES) to `m`. We ensure `dynamic=False` since this is a steady-state problem. This creates our overall model and adds the flowsheet capabilities that IDAES provides to the Pyomo model.\n", "\n", "
\n", "Inline Exercise:\n", @@ -91,20 +110,29 @@ ] }, { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:49.521103Z", + "start_time": "2025-06-06T16:45:49.517229Z" + } + }, "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], "source": [ "m = ConcreteModel()\n", "m.fs = FlowsheetBlock(dynamic=False)" - ] + ], + "outputs": [], + "execution_count": 3 }, { + "metadata": { + "tags": [ + "exercise" + ] + }, "cell_type": "markdown", - "metadata": {}, "source": [ - "At this point, we have a single Pyomo model that contains an (almost) empty flowsheet block.\n", + "At this point, we have a single Pyomo model that contains an (almost) empty flowsheet block. Lets use the `m.pprint()` to investigate the current contents of the model.\n", "\n", "
\n", "Inline Exercise:\n", @@ -113,41 +141,64 @@ ] }, { - "cell_type": "code", - "execution_count": 3, "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:49.531374Z", + "start_time": "2025-06-06T16:45:49.527521Z" + }, "tags": [ "exercise" ] }, + "cell_type": "code", + "source": "# Todo: call pprint on the model", "outputs": [], - "source": [ - "# Todo: call pprint on the model" - ] + "execution_count": 4 }, { - "cell_type": "code", - "execution_count": 4, "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:49.577500Z", + "start_time": "2025-06-06T16:45:49.572256Z" + }, "tags": [ "solution" ] }, - "outputs": [], + "cell_type": "code", "source": [ "# Todo: call pprint on the model\n", "m.pprint()" - ] + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1 Block Declarations\n", + " fs : Size=1, Index=None, Active=True\n", + " 1 Set Declarations\n", + " _time : Size=1, Index=None, Ordered=Insertion\n", + " Key : Dimen : Domain : Size : Members\n", + " None : 1 : Any : 1 : {0.0,}\n", + "\n", + " 1 Declarations: _time\n", + "\n", + "1 Declarations: fs\n" + ] + } + ], + "execution_count": 5 }, { - "cell_type": "markdown", "metadata": {}, + "cell_type": "markdown", "source": [ - "## Define Properties\n", + "### 2.1 Define Properties\n", "\n", "We need to define the property package for our flowsheet. In this example, we will be using the ideal property package that is available as part of the IDAES framework. This property package supports ideal gas - ideal liquid, ideal gas - NRTL, and ideal gas - Wilson models for VLE. More details on this property package can be found at: https://idaes-pse.readthedocs.io/en/stable/reference_guides/model_libraries/generic/property_models/activity_coefficient.html\n", "\n", - "IDAES also supports creation of your own property packages that allow for specification of the fluid using any set of valid state variables (e.g., component molar flows vs overall flow and mole fractions). This flexibility is designed to support advanced modeling needs that may rely on specific formulations. To learn about creating your own property package, please consult the online documentation at: https://idaes-pse.readthedocs.io/en/stable/explanations/components/property_package/index.html and look at examples within IDAES\n", + "IDAES also supports creation of your own property packages that will be shown in a later module.\n", "\n", "For this workshop, we will import the BTX_activity_coeff_VLE property parameter block to be used in the flowsheet. This properties block will be passed to our unit model to define the appropriate state variables and equations for performing thermodynamic calculations.\n", "\n", @@ -158,34 +209,44 @@ ] }, { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:49.649106Z", + "start_time": "2025-06-06T16:45:49.626357Z" + } + }, "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [], "source": [ "from idaes.models.properties.activity_coeff_models.BTX_activity_coeff_VLE import (\n", " BTXParameterBlock,\n", ")" - ] + ], + "outputs": [], + "execution_count": 6 }, { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:49.669359Z", + "start_time": "2025-06-06T16:45:49.657658Z" + } + }, "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [], "source": [ "m.fs.properties = BTXParameterBlock(\n", " valid_phase=(\"Liq\", \"Vap\"), activity_coeff_model=\"Ideal\", state_vars=\"FTPz\"\n", ")" - ] + ], + "outputs": [], + "execution_count": 7 }, { - "cell_type": "markdown", "metadata": {}, + "cell_type": "markdown", "source": [ - "## Adding Flash Unit\n", + "### 2.2 Adding Flash Unit\n", "\n", - "Now that we have the flowsheet and the properties defined, we can create the flash unit and add it to the flowsheet. \n", + "Now that we have the flowsheet and the properties defined, we can create the flash unit and add it to the flowsheet.\n", "\n", "**The Unit Model Library within IDAES includes a large set of common unit operations (see the online documentation for details: https://idaes-pse.readthedocs.io/en/stable/reference_guides/model_libraries/generic/unit_models/index.html**\n", "\n", @@ -200,7 +261,7 @@ "* Pressure changing equipment (compressors, expanders, pumps)\n", "* Feed and Product (source / sink) components\n", "\n", - "In this module, we will import the `Flash` unit model from `idaes.models.unit_models` and create an instance of the flash unit, attaching it to the flowsheet. Each IDAES unit model has several configurable options to customize the model behavior, but also includes defaults for these options. In this example, we will specify that the property package to be used with the Flash is the one we created earlier.\n", + "In this module, we will import the `Flash` unit model from `idaes.models.unit_models` and create an instance of the flash unit, attaching it to the flowsheet. Each IDAES unit model has several configurable options to customize the model behavior, but also includes defaults for these options. In this example, we will specify that the property package to be used with the Flash unit model is the one we created earlier by setting `property_package=m.fs.properties` within the `Flash` method.\n", "\n", "
\n", "Inline Exercise:\n", @@ -209,40 +270,183 @@ ] }, { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:49.749283Z", + "start_time": "2025-06-06T16:45:49.678730Z" + } + }, "cell_type": "code", - "execution_count": 7, - "metadata": {}, + "source": "from idaes.models.unit_models import Flash", "outputs": [], - "source": [ - "from idaes.models.unit_models import Flash" - ] + "execution_count": 8 }, { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:49.772441Z", + "start_time": "2025-06-06T16:45:49.758167Z" + } + }, "cell_type": "code", - "execution_count": 8, + "source": "m.fs.flash = Flash(property_package=m.fs.properties)", + "outputs": [], + "execution_count": 9 + }, + { "metadata": {}, + "cell_type": "markdown", + "source": "At this point, we have created a flowsheet and a properties block. We have also created a flash unit and added it to the flowsheet. Under the hood, IDAES has created the required state variables and model equations. Everything is open. You can see these variables and equations by calling the Pyomo method `pprint` on the model, flowsheet, or flash tank objects. Note that this output is very exhaustive, and is not intended to provide any summary information about the model, but rather a complete picture of all of the variables and equations in the model." + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:49.783706Z", + "start_time": "2025-06-06T16:45:49.781688Z" + }, + "tags": [ + "noauto" + ] + }, + "cell_type": "code", + "source": "m.pprint()", "outputs": [], + "execution_count": 10 + }, + { + "metadata": {}, + "cell_type": "markdown", "source": [ - "m.fs.flash = Flash(property_package=m.fs.properties)" + "## 3 Scaling the Model\n", + "\n", + "Now that the model is built, with properties set and the unit model created and added to the flowsheet, the next step is to scale the model. Ensuring that a model is well scaled is important for increasing the efficiency and reliability of solvers, and users should consider model scaling as an integral part of the modeling process. IDAES provides a number of tool for assisting users with scaling their models, and details on these can be found at https://idaes-pse.readthedocs.io/en/stable/reference_guides/scaling/scaling.html#scaling-toolbox\n", + "\n", + "There are currently two primary methods in scaling the model: manual scaling of each relevant component, or utilizing the AutoScaler Class. The more careful and risk-free method of manually scaling each component is the recommended method for maximum control and assurance that the model will be well-scaled. This comes with the drawback of being more meticulous while the AutoScaler is much simpler to use since it scaled the whole model all at once, it is less precise by lacking direct control over the scaling factor for each component and relying on scaling factors to be estimated. Both methods will be shown below" ] }, { + "metadata": {}, "cell_type": "markdown", + "source": [ + "### 3.1 Manual Scaling\n", + "The `set_scaling_factor` function is imported from `idaes.core.scaling.util` and is called with and used on each relevant component that needs to be well scaled. The component is the first argument and its scaling factor is the second argument.\n", + "\n", + "
\n", + "Inline Exercise:\n", + "Execute the following two cells to import the `set_scaling_factor` and set the scaling factor for both temperature and pressure\n", + "
\n", + "\n", + "Both `temperature` and `pressure` can be found at `m.fs.flash.inlet`." + ] + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:49.795252Z", + "start_time": "2025-06-06T16:45:49.792075Z" + } + }, + "cell_type": "code", + "source": "from idaes.core.scaling.util import set_scaling_factor", + "outputs": [], + "execution_count": 11 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:49.809330Z", + "start_time": "2025-06-06T16:45:49.806174Z" + } + }, + "cell_type": "code", + "source": [ + "set_scaling_factor(m.fs.flash.inlet.temperature, 300)\n", + "set_scaling_factor(m.fs.flash.inlet.pressure, 1e6)" + ], + "outputs": [], + "execution_count": 12 + }, + { "metadata": {}, + "cell_type": "markdown", "source": [ - "At this point, we have created a flowsheet and a properties block. We have also created a flash unit and added it to the flowsheet. Under the hood, IDAES has created the required state variables and model equations. Everything is open. You can see these variables and equations by calling the Pyomo method `pprint` on the model, flowsheet, or flash tank objects. Note that this output is very exhaustive, and is not intended to provide any summary information about the model, but rather a complete picture of all of the variables and equations in the model." + "### 3.2 Scaling with AutoScaler\n", + "The `AutoScaler` class is imported from `idaes.core.scaling.autoscaling` and an instance of the class is created. This instance contains the method `scale_model` which is used to scale the whole model at once. This can be a useful option but is generally more risky than manually scaling the model components since it has less direct control and specification.\n", + "\n", + "
\n", + "Inline Exercise:\n", + "Execute the following two cells to import the `AutoScaler` class and create an autoscaler instance that scaled the whole model at once\n", + "
" ] }, { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:49.826276Z", + "start_time": "2025-06-06T16:45:49.823030Z" + } + }, + "cell_type": "code", + "source": "from idaes.core.scaling.autoscaling import AutoScaler", + "outputs": [], + "execution_count": 13 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:50.217776Z", + "start_time": "2025-06-06T16:45:49.836920Z" + } + }, + "cell_type": "code", + "source": [ + "autoscaler = AutoScaler()\n", + "autoscaler.scale_model(m)" + ], + "outputs": [], + "execution_count": 14 + }, + { + "metadata": {}, "cell_type": "markdown", + "source": "" + }, + { "metadata": {}, + "cell_type": "markdown", "source": [ - "## Set Operating Conditions\n", + "## 4 Set Operating Conditions\n", "\n", - "Now that we have created our unit model, we can specify the necessary operating conditions. It is often very useful to determine the degrees of freedom before we specify any conditions.\n", + "Now that we have created our unit model and scaled it, we can specify the necessary operating conditions. The inlet specifications for this flash tank are:\n", "\n", - "The `idaes.core.util.model_statistics` package has a function `degrees_of_freedom`. To see how to use this function, we can make use of the Python function `help(func)`. This function prints the appropriate documentation string for the function.\n", + "Inlet Specifications:\n", + "* Mole fraction (Benzene) = 0.5\n", + "* Mole fraction (Toluene) = 0.5\n", + "* Pressure = 101325 Pa\n", + "* Temperature = 368 K\n", "\n", + "\n" + ] + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": [ + "### 4.1 Degrees of Freedom\n", + "\n", + "It is often very useful to first determine the degrees of freedom before we specify any conditions.\n", + "\n", + "The `idaes.core.util.model_statistics` package has a function `degrees_of_freedom`. To see how to use this function, we can make use of the Python function `help(func)`. This function prints the appropriate documentation string for the function.\n" + ] + }, + { + "metadata": { + "tags": [ + "exercise" + ] + }, + "cell_type": "markdown", + "source": [ "
\n", "Inline Exercise:\n", "Import the degrees_of_freedom function and print the help for the function by calling the Python help function.\n", @@ -250,41 +454,71 @@ ] }, { - "cell_type": "code", - "execution_count": 9, "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:50.224457Z", + "start_time": "2025-06-06T16:45:50.221905Z" + }, "tags": [ "exercise" ] }, - "outputs": [], + "cell_type": "code", "source": [ "# Todo: import the degrees_of_freedom function from the idaes.core.util.model_statistics package\n", "\n", "\n", "# Todo: Call the python help on the degrees_of_freedom function" - ] + ], + "outputs": [], + "execution_count": 15 }, { - "cell_type": "code", - "execution_count": 10, "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:50.240605Z", + "start_time": "2025-06-06T16:45:50.237594Z" + }, "tags": [ "solution" ] }, - "outputs": [], + "cell_type": "code", "source": [ "# Todo: import the degrees_of_freedom function from the idaes.core.util.model_statistics package\n", "from idaes.core.util.model_statistics import degrees_of_freedom\n", "\n", "# Todo: Call the python help on the degrees_of_freedom function\n", "help(degrees_of_freedom)" - ] + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Help on function degrees_of_freedom in module idaes.core.util.model_statistics:\n", + "\n", + "degrees_of_freedom(block)\n", + " Method to return the degrees of freedom of a model.\n", + "\n", + " Args:\n", + " block : model to be studied\n", + "\n", + " Returns:\n", + " Number of degrees of freedom in block.\n", + "\n" + ] + } + ], + "execution_count": 16 }, { + "metadata": { + "tags": [ + "exercise" + ] + }, "cell_type": "markdown", - "metadata": {}, "source": [ "
\n", "Inline Exercise:\n", @@ -293,50 +527,70 @@ ] }, { - "cell_type": "code", - "execution_count": 11, "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:50.271361Z", + "start_time": "2025-06-06T16:45:50.268763Z" + }, "tags": [ "exercise" ] }, + "cell_type": "code", + "source": "# Todo: print the degrees of freedom for your model", "outputs": [], - "source": [ - "# Todo: print the degrees of freedom for your model" - ] + "execution_count": 17 }, { - "cell_type": "code", - "execution_count": 12, "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:50.297399Z", + "start_time": "2025-06-06T16:45:50.291352Z" + }, "tags": [ "solution" ] }, - "outputs": [], + "cell_type": "code", "source": [ "# Todo: print the degrees of freedom for your model\n", "print(\"Degrees of Freedom =\", degrees_of_freedom(m))" - ] + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Degrees of Freedom = 7\n" + ] + } + ], + "execution_count": 18 }, { - "cell_type": "code", - "execution_count": 13, "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:50.334804Z", + "start_time": "2025-06-06T16:45:50.327991Z" + }, "tags": [ "testing" ] }, - "outputs": [], + "cell_type": "code", "source": [ "# Check the degrees of freedom\n", "assert degrees_of_freedom(m) == 7" - ] + ], + "outputs": [], + "execution_count": 19 }, { - "cell_type": "markdown", "metadata": {}, + "cell_type": "markdown", "source": [ + "### 4.2 Specify Inlet Conditions\n", + "\n", "To satisfy our degrees of freedom, we will first specify the inlet conditions. We can specify these values through the `inlet` port of the flash unit.\n", "\n", "**To see the list of naming conventions for variables within the IDAES framework, consult the online documentation at: https://idaes-pse.readthedocs.io/en/stable/explanations/conventions.html#standard-naming-format**\n", @@ -373,7 +627,17 @@ "* inlet mole fraction (toluene) = 0.5 (`mole_frac_comp[0, \"toluene\"]`)\n", "* The heat duty on the flash set to 0 (`heat_duty`)\n", "* The pressure drop across the flash tank set to 0 (`deltaP`)\n", - "\n", + "\n" + ] + }, + { + "metadata": { + "tags": [ + "exercise" + ] + }, + "cell_type": "markdown", + "source": [ "
\n", "Inline Exercise:\n", "Write the code below to specify the inlet conditions and unit specifications described above\n", @@ -381,30 +645,36 @@ ] }, { - "cell_type": "code", - "execution_count": 14, "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:50.360852Z", + "start_time": "2025-06-06T16:45:50.357341Z" + }, "tags": [ "exercise" ] }, - "outputs": [], + "cell_type": "code", "source": [ "# Todo: Add inlet specifications given above\n", "\n", "\n", "# Todo: Add 2 flash unit specifications given above" - ] + ], + "outputs": [], + "execution_count": 20 }, { - "cell_type": "code", - "execution_count": 15, "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:50.396800Z", + "start_time": "2025-06-06T16:45:50.392314Z" + }, "tags": [ "solution" ] }, - "outputs": [], + "cell_type": "code", "source": [ "# Todo: Add inlet specifications given above\n", "m.fs.flash.inlet.flow_mol.fix(1)\n", @@ -416,11 +686,25 @@ "# Todo: Add 2 flash unit specifications given above\n", "m.fs.flash.heat_duty.fix(0)\n", "m.fs.flash.deltaP.fix(0)" + ], + "outputs": [], + "execution_count": 21 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": [ + "Now that all the inlets have been specified, we can check the degrees of freedom again to ensure the system is square and has a degree of freedom of 0\n", + "\n" ] }, { + "metadata": { + "tags": [ + "exercise" + ] + }, "cell_type": "markdown", - "metadata": {}, "source": [ "
\n", "Inline Exercise:\n", @@ -429,93 +713,161 @@ ] }, { - "cell_type": "code", - "execution_count": 16, "metadata": { "tags": [ "exercise" ] }, + "cell_type": "code", "outputs": [], - "source": [ - "# Todo: print the degrees of freedom for your model" - ] + "execution_count": 22, + "source": "# Todo: print the degrees of freedom for your model" }, { - "cell_type": "code", - "execution_count": 17, "metadata": { "tags": [ "solution" ] }, - "outputs": [], + "cell_type": "code", + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Degrees of Freedom = 0\n" + ] + } + ], + "execution_count": 23, "source": [ "# Todo: print the degrees of freedom for your model\n", "print(\"Degrees of Freedom =\", degrees_of_freedom(m))" ] }, { - "cell_type": "code", - "execution_count": 18, "metadata": { "tags": [ "testing" ] }, + "cell_type": "code", "outputs": [], + "execution_count": 24, "source": [ "# Check the degrees of freedom\n", "assert degrees_of_freedom(m) == 0" ] }, { - "cell_type": "markdown", "metadata": {}, + "cell_type": "markdown", "source": [ - "## Initializing the Model\n", + "## 5 Initializing the Model\n", "\n", - "IDAES includes pre-written initialization routines for all unit models. You can call this initialize method on the units. In the next module, we will demonstrate the use of a sequential modular solve cycle to initialize flowsheets.\n", + "Now that all building steps are complete, the last step before solving the model is to initialize the model, or prepping the solve by giving it a good starting point. This is essentially giving the solver an initial guess for the iterative solver to reach convergence and is essential for both a fast and accurate solution. In IDAES, the current standard for initializing the model is by utilizing initializer instances. These initializer instances contain the initialize method that can be applied to any model type. For more information on initializing in IDAES, visit https://idaes-pse.readthedocs.io/en/stable/reference_guides/initialization/index.html.
\n", "\n", + "For this tutorial, we will import the initializer class `BlockTriangularizationInitializer` class from the `default_initializer` method from the flash unit model. This is often the simplest way to obtain a compatible initializer for each unit model, but you can also directly important any initializer needed from this source `idaes.core.initialization`. Each initializer instance contains the `initialize()` method that requires an argument to be initialized and in this case its the flash unit model.\n" + ] + }, + { + "metadata": { + "tags": [ + "exercise" + ] + }, + "cell_type": "markdown", + "source": [ "
\n", "Inline Exercise:\n", - "Call the initialize method on the flash unit to initialize the model.\n", + "Define the initializer instance, and initialize the flash unit model\n", "
" ] }, { - "cell_type": "code", - "execution_count": 19, "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:50.499945Z", + "start_time": "2025-06-06T16:45:50.497439Z" + }, "tags": [ "exercise" ] }, + "cell_type": "code", + "source": "# Todo: initialize the flash unit", "outputs": [], - "source": [ - "# Todo: initialize the flash unit" - ] + "execution_count": 25 }, { - "cell_type": "code", - "execution_count": 20, "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:51:40.247234Z", + "start_time": "2025-06-06T16:51:39.730044Z" + }, "tags": [ "solution" ] }, - "outputs": [], + "cell_type": "code", "source": [ "# Todo: initialize the flash unit\n", - "m.fs.flash.initialize(outlvl=idaeslog.INFO)" - ] + "FlashInitializer = m.fs.flash.default_initializer()\n", + "FlashInitializer.initialize(m.fs.flash)" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2025-06-06 10:51:39 [INFO] idaes.init.fs.flash.control_volume.properties_in: Initialization Step 1 optimal - .\n", + "2025-06-06 10:51:39 [INFO] idaes.init.fs.flash.control_volume.properties_in: Initialization Step 2 optimal - .\n", + "2025-06-06 10:51:39 [INFO] idaes.init.fs.flash.control_volume.properties_in: Initialization Step 3 optimal - .\n", + "2025-06-06 10:51:39 [INFO] idaes.init.fs.flash.control_volume.properties_in: Initialization Step 4 optimal - .\n", + "2025-06-06 10:51:39 [INFO] idaes.init.fs.flash.control_volume.properties_in: Initialization Step 5 optimal - .\n", + "2025-06-06 10:51:40 [INFO] idaes.init.fs.flash.control_volume.properties_out: Initialization Step 1 optimal - .\n", + "2025-06-06 10:51:40 [INFO] idaes.init.fs.flash.control_volume.properties_out: Initialization Step 2 optimal - .\n", + "2025-06-06 10:51:40 [INFO] idaes.init.fs.flash.control_volume.properties_out: Initialization Step 3 optimal - .\n", + "2025-06-06 10:51:40 [INFO] idaes.init.fs.flash.control_volume.properties_out: Initialization Step 4 optimal - .\n", + "2025-06-06 10:51:40 [INFO] idaes.init.fs.flash.control_volume.properties_out: Initialization Step 5 optimal - .\n" + ] + }, + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 33, + "metadata": {}, + "output_type": "execute_result" + } + ], + "execution_count": 33 }, { + "metadata": {}, "cell_type": "markdown", + "source": "Another option for initializing is utilizing the default initializer that is attached to the unit model. Each unit model as a default initializer that is hypothetically the most compatible. It can be called with `m.fs.flash.initialize()`. While this is an option, it is generally preferred to import an initializer object and initialize the model with that to ensure more control over the initialization." + }, + { "metadata": {}, + "cell_type": "markdown", "source": [ - "Now that the model has been defined and initialized, we can solve the model.\n", + "## 6 Solving the Model\n", "\n", + "Now that the model has been defined and initialized, we can solve the model.\n", + "\n" + ] + }, + { + "metadata": { + "tags": [ + "exercise" + ] + }, + "cell_type": "markdown", + "source": [ "
\n", "Inline Exercise:\n", "Using the notation described in the previous model, create an instance of the \"ipopt\" solver and use it to solve the model. Set the tee option to True to see the log output.\n", @@ -523,58 +875,140 @@ ] }, { - "cell_type": "code", - "execution_count": 21, "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T17:04:32.219172Z", + "start_time": "2025-06-06T17:04:32.216161Z" + }, "tags": [ "exercise" ] }, - "outputs": [], + "cell_type": "code", "source": [ "# Todo: create the ipopt solver\n", "\n", "# Todo: solve the model" - ] + ], + "outputs": [], + "execution_count": 36 }, { - "cell_type": "code", - "execution_count": 22, "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T17:04:32.439966Z", + "start_time": "2025-06-06T17:04:32.396984Z" + }, "tags": [ "solution" ] }, - "outputs": [], + "cell_type": "code", "source": [ "# Todo: create the ipopt solver\n", "solver = SolverFactory(\"ipopt\")\n", "\n", "# Todo: solve the model\n", "status = solver.solve(m, tee=True)" - ] + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "Ipopt 3.13.2: \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...: 135\n", + "Number of nonzeros in inequality constraint Jacobian.: 0\n", + "Number of nonzeros in Lagrangian Hessian.............: 72\n", + "\n", + "Total number of variables............................: 41\n", + " variables with only lower bounds: 3\n", + " variables with lower and upper bounds: 10\n", + " variables with only upper bounds: 0\n", + "Total number of equality constraints.................: 41\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 6.22e-05 1.00e+00 -1.0 0.00e+00 - 0.00e+00 0.00e+00 0\n", + " 1 0.0000000e+00 1.46e-11 1.00e-02 -1.0 6.22e-05 - 9.90e-01 1.00e+00h 1\n", + "\n", + "Number of Iterations....: 1\n", + "\n", + " (scaled) (unscaled)\n", + "Objective...............: 0.0000000000000000e+00 0.0000000000000000e+00\n", + "Dual infeasibility......: 0.0000000000000000e+00 0.0000000000000000e+00\n", + "Constraint violation....: 2.0417014662014577e-12 1.4551915228366852e-11\n", + "Complementarity.........: 0.0000000000000000e+00 0.0000000000000000e+00\n", + "Overall NLP error.......: 2.0417014662014577e-12 1.4551915228366852e-11\n", + "\n", + "\n", + "Number of objective function evaluations = 2\n", + "Number of objective gradient evaluations = 2\n", + "Number of equality constraint evaluations = 2\n", + "Number of inequality constraint evaluations = 0\n", + "Number of equality constraint Jacobian evaluations = 2\n", + "Number of inequality constraint Jacobian evaluations = 0\n", + "Number of Lagrangian Hessian evaluations = 1\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" + ] + } + ], + "execution_count": 37 }, { - "cell_type": "code", - "execution_count": 23, "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T17:04:32.975773Z", + "start_time": "2025-06-06T17:04:32.972754Z" + }, "tags": [ "testing" ] }, - "outputs": [], + "cell_type": "code", "source": [ - "# Check for optimal solution\n", + "# Check for an optimal solution\n", "from pyomo.environ import TerminationCondition\n", "\n", "assert status.solver.termination_condition == TerminationCondition.optimal" - ] + ], + "outputs": [], + "execution_count": 38 }, { - "cell_type": "markdown", "metadata": {}, + "cell_type": "markdown", "source": [ - "## Viewing the Results\n", + "## 7 Viewing the Results\n", "\n", "Once a model is solved, the values returned by the solver are loaded into the model object itself. We can access the value of any variable in the model with the `value` function. For example:\n", "```python\n", @@ -594,10 +1028,13 @@ ] }, { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T17:04:35.556815Z", + "start_time": "2025-06-06T17:04:35.551070Z" + } + }, "cell_type": "code", - "execution_count": 24, - "metadata": {}, - "outputs": [], "source": [ "# Print the pressure of the flash vapor outlet\n", "print(\"Pressure =\", value(m.fs.flash.vap_outlet.pressure[0]))\n", @@ -607,38 +1044,95 @@ "# Call display on vap_outlet and liq_outlet of the flash\n", "m.fs.flash.vap_outlet.display()\n", "m.fs.flash.liq_outlet.display()" - ] + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Pressure = 101325.0\n", + "\n", + "Output from display:\n", + "vap_outlet : Size=1\n", + " Key : Name : Value\n", + " None : flow_mol : {0.0: 0.3961181748774193}\n", + " : mole_frac_comp : {(0.0, 'benzene'): 0.633976648508129, (0.0, 'toluene'): 0.366023351491871}\n", + " : pressure : {0.0: 101325.0}\n", + " : temperature : {0.0: 368.0}\n", + "liq_outlet : Size=1\n", + " Key : Name : Value\n", + " None : flow_mol : {0.0: 0.6038818251225807}\n", + " : mole_frac_comp : {(0.0, 'benzene'): 0.41211759772293044, (0.0, 'toluene'): 0.5878824022770694}\n", + " : pressure : {0.0: 101325.0}\n", + " : temperature : {0.0: 368.0}\n" + ] + } + ], + "execution_count": 39 }, { - "cell_type": "markdown", "metadata": {}, + "cell_type": "markdown", "source": [ "The output from `display` is quite exhaustive and not really intended to provide quick summary information. Because Pyomo is built on Python, there are opportunities to format the output any way we like. Most IDAES models have a `report` method which provides a summary of the results for the model.\n", "\n", "
\n", "Inline Exercise:\n", - "Execute the cell below which uses the function above to print a summary of the key variables in the flash model, including the inlet, the vapor, and the liquid ports. \n", + "Execute the cell below which uses the function above to print a summary of the key variables in the flash model, including the inlet, the vapor, and the liquid ports.\n", "
" ] }, { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T17:04:38.727731Z", + "start_time": "2025-06-06T17:04:38.712131Z" + } + }, "cell_type": "code", - "execution_count": 25, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.flash.report()" - ] + "source": "m.fs.flash.report()", + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "====================================================================================\n", + "Unit : fs.flash Time: 0.0\n", + "------------------------------------------------------------------------------------\n", + " Unit Performance\n", + "\n", + " Variables: \n", + "\n", + " Key : Value : Units : Fixed : Bounds\n", + " Heat Duty : 0.0000 : watt : True : (None, None)\n", + " Pressure Change : 0.0000 : pascal : True : (None, None)\n", + "\n", + "------------------------------------------------------------------------------------\n", + " Stream Table\n", + " Units Inlet Vapor Outlet Liquid Outlet\n", + " flow_mol mole / second 1.0000 0.39612 0.60388 \n", + " mole_frac_comp benzene dimensionless 0.50000 0.63398 0.41212 \n", + " mole_frac_comp toluene dimensionless 0.50000 0.36602 0.58788 \n", + " temperature kelvin 368.00 368.00 368.00 \n", + " pressure pascal 1.0132e+05 1.0132e+05 1.0132e+05 \n", + "====================================================================================\n" + ] + } + ], + "execution_count": 40 }, { - "cell_type": "code", - "execution_count": 26, "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T17:04:39.229802Z", + "start_time": "2025-06-06T17:04:39.132903Z" + }, "tags": [ "testing" ] }, - "outputs": [], + "cell_type": "code", "source": [ "# Check optimal solution values\n", "import pytest\n", @@ -662,13 +1156,15 @@ ")\n", "assert value(m.fs.flash.vap_outlet.temperature[0]) == pytest.approx(368, abs=1e-3)\n", "assert value(m.fs.flash.vap_outlet.pressure[0]) == pytest.approx(101325, abs=1e-3)" - ] + ], + "outputs": [], + "execution_count": 41 }, { - "cell_type": "markdown", "metadata": {}, + "cell_type": "markdown", "source": [ - "## Studying Purity as a Function of Heat Duty\n", + "## Exercise: Studying Purity as a Function of Heat Duty\n", "\n", "Since the entire modeling framework is built upon Python, it includes a complete programming environment for whatever analysis we may want to perform. In this next exercise, we will make use of what we learned in this and the previous module to generate a figure showing some output variables as a function of the heat duty in the flash tank.\n", "\n", @@ -680,22 +1176,35 @@ ] }, { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T17:04:43.426680Z", + "start_time": "2025-06-06T17:04:43.423025Z" + } + }, "cell_type": "code", - "execution_count": 27, - "metadata": {}, + "source": "import matplotlib.pyplot as plt", "outputs": [], - "source": [ - "import matplotlib.pyplot as plt" - ] + "execution_count": 42 }, { - "cell_type": "markdown", "metadata": {}, + "cell_type": "markdown", "source": [ "Exercise specifications:\n", "* Generate a figure showing the flash tank heat duty (`m.fs.flash.heat_duty[0]`) vs. the vapor flowrate (`m.fs.flash.vap_outlet.flow_mol[0]`)\n", "* Specify the heat duty from -17000 to 25000 over 50 steps\n", - "\n", + "\n" + ] + }, + { + "metadata": { + "tags": [ + "exercise" + ] + }, + "cell_type": "markdown", + "source": [ "
\n", "Inline Exercise:\n", "Using what you have learned so far, fill in the missing code below to generate the figure specified above. (Hint: import numpy and use the linspace function from the previous module)\n", @@ -703,14 +1212,12 @@ ] }, { - "cell_type": "code", - "execution_count": 29, "metadata": { "tags": [ "exercise" ] }, - "outputs": [], + "cell_type": "code", "source": [ "# import the solve_successful checking function from workshop tools\n", "from idaes_examples.mod.tut.workshoptools import solve_successful\n", @@ -723,22 +1230,22 @@ "V = []\n", "\n", "# re-initialize model\n", - "m.fs.flash.initialize(outlvl=idaeslog.WARNING)\n", + "UnitModelInitializer.initialize(m.fs.flash)\n", "\n", "# Todo: Write the for loop specification using numpy's linspace\n", "\n", " # fix the heat duty\n", " m.fs.flash.heat_duty.fix(duty)\n", - " \n", + "\n", " # append the value of the duty to the Q list\n", " Q.append(duty)\n", - " \n", + "\n", " # print the current simulation\n", " print(\"Simulating with Q = \", value(m.fs.flash.heat_duty[0]))\n", "\n", " # Solve the model\n", " status = solver.solve(m)\n", - " \n", + "\n", " # append the value for vapor fraction if the solve was successful\n", " if solve_successful(status):\n", " V.append(value(m.fs.flash.vap_outlet.flow_mol[0]))\n", @@ -746,7 +1253,7 @@ " else:\n", " V.append(0.0)\n", " print('... solve failed.')\n", - " \n", + "\n", "# Create and show the figure\n", "plt.figure(\"Vapor Fraction\")\n", "plt.plot(Q, V)\n", @@ -754,18 +1261,21 @@ "plt.xlabel(\"Heat Duty [J]\")\n", "plt.ylabel(\"Vapor Fraction [-]\")\n", "plt.show()" - ] + ], + "outputs": [], + "execution_count": null }, { - "cell_type": "code", - "execution_count": 30, "metadata": { - "scrolled": true, + "ExecuteTime": { + "end_time": "2025-06-06T17:04:48.800601Z", + "start_time": "2025-06-06T17:04:46.438264Z" + }, "tags": [ "solution" ] }, - "outputs": [], + "cell_type": "code", "source": [ "# import the solve_successful checking function from workshop tools\n", "from idaes_examples.mod.tut.workshoptools import solve_successful\n", @@ -778,7 +1288,7 @@ "V = []\n", "\n", "# re-initialize model\n", - "m.fs.flash.initialize(outlvl=idaeslog.WARNING)\n", + "FlashInitializer.initialize(m.fs.flash)\n", "\n", "# Todo: Write the for loop specification using numpy's linspace\n", "for duty in np.linspace(-17000, 25000, 50):\n", @@ -809,11 +1319,244 @@ "plt.xlabel(\"Heat Duty [J]\")\n", "plt.ylabel(\"Vapor Fraction [-]\")\n", "plt.show()" - ] + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2025-06-06 11:04:46 [INFO] idaes.init.fs.flash.control_volume.properties_in: Initialization Step 1 optimal - .\n", + "2025-06-06 11:04:46 [INFO] idaes.init.fs.flash.control_volume.properties_in: Initialization Step 2 optimal - .\n", + "2025-06-06 11:04:46 [INFO] idaes.init.fs.flash.control_volume.properties_in: Initialization Step 3 optimal - .\n", + "2025-06-06 11:04:46 [INFO] idaes.init.fs.flash.control_volume.properties_in: Initialization Step 4 optimal - .\n", + "2025-06-06 11:04:46 [INFO] idaes.init.fs.flash.control_volume.properties_in: Initialization Step 5 optimal - .\n", + "2025-06-06 11:04:46 [INFO] idaes.init.fs.flash.control_volume.properties_out: Initialization Step 1 optimal - .\n", + "2025-06-06 11:04:46 [INFO] idaes.init.fs.flash.control_volume.properties_out: Initialization Step 2 optimal - .\n", + "2025-06-06 11:04:46 [INFO] idaes.init.fs.flash.control_volume.properties_out: Initialization Step 3 optimal - .\n", + "2025-06-06 11:04:46 [INFO] idaes.init.fs.flash.control_volume.properties_out: Initialization Step 4 optimal - .\n", + "2025-06-06 11:04:46 [INFO] idaes.init.fs.flash.control_volume.properties_out: Initialization Step 5 optimal - .\n", + "Simulating with Q = -17000.0\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -16142.857142857143\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -15285.714285714286\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -14428.571428571428\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -13571.428571428572\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -12714.285714285714\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -11857.142857142857\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -11000.0\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -10142.857142857143\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -9285.714285714286\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -8428.57142857143\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -7571.4285714285725\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -6714.285714285714\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -5857.142857142857\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -5000.0\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -4142.857142857143\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -3285.7142857142862\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -2428.5714285714294\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -1571.4285714285725\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -714.2857142857156\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 142.8571428571413\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 1000.0\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 1857.142857142855\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 2714.2857142857138\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 3571.4285714285725\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 4428.5714285714275\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 5285.714285714286\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 6142.857142857141\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 7000.0\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 7857.142857142855\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 8714.285714285714\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 9571.428571428569\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 10428.571428571428\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 11285.714285714286\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 12142.857142857141\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 13000.0\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 13857.142857142855\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 14714.285714285714\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 15571.428571428569\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 16428.571428571428\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 17285.714285714283\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 18142.857142857145\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 19000.0\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 19857.142857142855\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 20714.28571428571\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 21571.428571428572\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 22428.571428571428\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 23285.714285714283\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 24142.857142857145\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 25000.0\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n" + ] + }, + { + "data": { + "text/plain": [ + "
" + ], + "image/png": "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" + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "execution_count": 43 }, { + "metadata": { + "tags": [ + "exercise" + ] + }, "cell_type": "markdown", - "metadata": {}, "source": [ "
\n", "Inline Exercise:\n", @@ -822,27 +1565,27 @@ ] }, { - "cell_type": "code", - "execution_count": null, "metadata": { "tags": [ "exercise" ] }, + "cell_type": "code", + "source": "# Todo: generate a figure of heat duty vs. mole fraction of Benzene in the vapor", "outputs": [], - "source": [ - "# Todo: generate a figure of heat duty vs. mole fraction of Benzene in the vapor" - ] + "execution_count": null }, { - "cell_type": "code", - "execution_count": null, "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T17:05:02.101331Z", + "start_time": "2025-06-06T17:05:00.157447Z" + }, "tags": [ "solution" ] }, - "outputs": [], + "cell_type": "code", "source": [ "# Todo: generate a figure of heat duty vs. mole fraction of Benzene in the vapor\n", "Q = []\n", @@ -875,18 +1618,247 @@ "plt.xlabel(\"Heat Duty [J]\")\n", "plt.ylabel(\"Vapor Benzene Mole Fraction [-]\")\n", "plt.show()" - ] + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Simulating with Q = -17000.0\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -16142.857142857143\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -15285.714285714286\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -14428.571428571428\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -13571.428571428572\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -12714.285714285714\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -11857.142857142857\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -11000.0\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -10142.857142857143\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -9285.714285714286\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -8428.57142857143\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -7571.4285714285725\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -6714.285714285714\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -5857.142857142857\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -5000.0\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -4142.857142857143\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -3285.7142857142862\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -2428.5714285714294\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -1571.4285714285725\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -714.2857142857156\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 142.8571428571413\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 1000.0\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 1857.142857142855\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 2714.2857142857138\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 3571.4285714285725\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 4428.5714285714275\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 5285.714285714286\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 6142.857142857141\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 7000.0\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 7857.142857142855\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 8714.285714285714\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 9571.428571428569\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 10428.571428571428\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 11285.714285714286\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 12142.857142857141\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 13000.0\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 13857.142857142855\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 14714.285714285714\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 15571.428571428569\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 16428.571428571428\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 17285.714285714283\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 18142.857142857145\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 19000.0\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 19857.142857142855\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 20714.28571428571\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 21571.428571428572\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 22428.571428571428\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 23285.714285714283\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 24142.857142857145\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 25000.0\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n" + ] + }, + { + "data": { + "text/plain": [ + "
" + ], + "image/png": "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" + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "execution_count": 44 }, { - "cell_type": "markdown", "metadata": {}, + "cell_type": "markdown", "source": [ "Recall that the IDAES framework is an equation-oriented modeling environment. This means that we can specify \"design\" problems natively. That is, there is no need to have our specifications on the inlet alone. We can put specifications on the outlet as long as we retain a well-posed, square system of equations.\n", "\n", "For example, we can remove the specification on heat duty and instead specify that we want the mole fraction of Benzene in the vapor outlet to be equal to 0.6. The mole fraction is not a native variable in the property block, so we cannot use \"fix\". We can, however, add a constraint to the model.\n", "\n", "Note that we have been executing a number of solves on the problem, and may not be sure of the current state. To help convergence, therefore, we will first call initialize, then add the new constraint and solve the problem. Note that the reference for the mole fraction of Benzene in the vapor outlet is `m.fs.flash.vap_outlet.mole_frac_comp[0, \"benzene\"]`.\n", - "\n", + "\n" + ] + }, + { + "metadata": { + "tags": [ + "exercise" + ] + }, + "cell_type": "markdown", + "source": [ "
\n", "Inline Exercise:\n", "Fill in the missing code below and add a constraint on the mole fraction of Benzene (to a value of 0.6) to find the required heat duty.\n", @@ -894,14 +1866,16 @@ ] }, { - "cell_type": "code", - "execution_count": null, "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T17:05:05.363302Z", + "start_time": "2025-06-06T17:05:04.960601Z" + }, "tags": [ "exercise" ] }, - "outputs": [], + "cell_type": "code", "source": [ "# re-initialize the model - this may or may not be required depending on current state but safe to initialize\n", "m.fs.flash.heat_duty.fix(0)\n", @@ -917,17 +1891,118 @@ "\n", "# Check stream condition\n", "m.fs.flash.report()" - ] + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "WARNING: model contains export suffix 'scaling_factor' that contains 6\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "Ipopt 3.13.2: \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...: 136\n", + "Number of nonzeros in inequality constraint Jacobian.: 0\n", + "Number of nonzeros in Lagrangian Hessian.............: 72\n", + "\n", + "Total number of variables............................: 42\n", + " variables with only lower bounds: 3\n", + " variables with lower and upper bounds: 10\n", + " variables with only upper bounds: 0\n", + "Total number of equality constraints.................: 41\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 3.07e-08 1.01e-04 -1.0 0.00e+00 - 0.00e+00 0.00e+00 0\n", + " 1 0.0000000e+00 1.46e+00 2.42e-01 -1.0 4.86e+02 - 9.90e-01 1.00e+00f 1\n", + " 2 0.0000000e+00 5.55e+01 5.60e-03 -1.0 3.00e+03 - 9.90e-01 1.00e+00h 1\n", + " 3 0.0000000e+00 1.91e+00 4.24e-05 -1.0 5.68e+02 - 1.00e+00 1.00e+00h 1\n", + " 4 0.0000000e+00 1.53e-05 1.90e-06 -2.5 1.35e+00 - 1.00e+00 1.00e+00h 1\n", + " 5 0.0000000e+00 4.66e-10 1.50e-09 -3.8 8.72e-03 - 1.00e+00 1.00e+00h 1\n", + " 6 0.0000000e+00 2.18e-11 1.84e-11 -5.7 1.92e-03 - 1.00e+00 1.00e+00h 1\n", + " 7 0.0000000e+00 1.46e-11 2.51e-14 -8.6 2.10e-04 - 1.00e+00 1.00e+00H 1\n", + "\n", + "Number of Iterations....: 7\n", + "\n", + " (scaled) (unscaled)\n", + "Objective...............: 0.0000000000000000e+00 0.0000000000000000e+00\n", + "Dual infeasibility......: 2.5059035640133008e-14 2.5059035640133008e-14\n", + "Constraint violation....: 4.8801876740451558e-13 1.4551915228366852e-11\n", + "Complementarity.........: 2.5059101787473095e-09 2.5059101787473095e-09\n", + "Overall NLP error.......: 2.5059101787473095e-09 2.5059101787473095e-09\n", + "\n", + "\n", + "Number of objective function evaluations = 9\n", + "Number of objective gradient evaluations = 8\n", + "Number of equality constraint evaluations = 9\n", + "Number of inequality constraint evaluations = 0\n", + "Number of equality constraint Jacobian evaluations = 8\n", + "Number of inequality constraint Jacobian evaluations = 0\n", + "Number of Lagrangian Hessian evaluations = 7\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", + "\n", + "====================================================================================\n", + "Unit : fs.flash Time: 0.0\n", + "------------------------------------------------------------------------------------\n", + " Unit Performance\n", + "\n", + " Variables: \n", + "\n", + " Key : Value : Units : Fixed : Bounds\n", + " Heat Duty : 4059.3 : watt : False : (None, None)\n", + " Pressure Change : 0.0000 : pascal : True : (None, None)\n", + "\n", + "------------------------------------------------------------------------------------\n", + " Stream Table\n", + " Units Inlet Vapor Outlet Liquid Outlet\n", + " flow_mol mole / second 1.0000 0.51773 0.48227 \n", + " mole_frac_comp benzene dimensionless 0.50000 0.60690 0.38524 \n", + " mole_frac_comp toluene dimensionless 0.50000 0.39310 0.61476 \n", + " temperature kelvin 368.00 368.85 368.85 \n", + " pressure pascal 1.0132e+05 1.0132e+05 1.0132e+05 \n", + "====================================================================================\n" + ] + } + ], + "execution_count": 45 }, { - "cell_type": "code", - "execution_count": null, "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T17:05:05.768445Z", + "start_time": "2025-06-06T17:05:05.378930Z" + }, "tags": [ "solution" ] }, - "outputs": [], + "cell_type": "code", "source": [ "# re-initialize the model - this may or may not be required depending on current state but safe to initialize\n", "m.fs.flash.heat_duty.fix(0)\n", @@ -946,17 +2021,110 @@ "\n", "# Check stream condition\n", "m.fs.flash.report()" - ] + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "WARNING: model contains export suffix 'scaling_factor' that contains 6\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "Ipopt 3.13.2: \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...: 137\n", + "Number of nonzeros in inequality constraint Jacobian.: 0\n", + "Number of nonzeros in Lagrangian Hessian.............: 72\n", + "\n", + "Total number of variables............................: 42\n", + " variables with only lower bounds: 3\n", + " variables with lower and upper bounds: 10\n", + " variables with only upper bounds: 0\n", + "Total number of equality constraints.................: 42\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 3.40e-02 1.00e+00 -1.0 0.00e+00 - 0.00e+00 0.00e+00 0\n", + " 1 0.0000000e+00 1.64e+02 7.01e-02 -1.0 5.15e+03 - 9.87e-01 1.00e+00h 1\n", + " 2 0.0000000e+00 9.59e-02 2.03e-03 -1.0 7.07e+01 - 9.90e-01 1.00e+00h 1\n", + " 3 0.0000000e+00 6.96e-08 2.50e-06 -1.0 4.13e-01 - 9.98e-01 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....: 8.9151738215745240e-11 6.9550878833979368e-08\n", + "Complementarity.........: 0.0000000000000000e+00 0.0000000000000000e+00\n", + "Overall NLP error.......: 8.9151738215745240e-11 6.9550878833979368e-08\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.001\n", + "Total CPU secs in NLP function evaluations = 0.000\n", + "\n", + "EXIT: Optimal Solution Found.\n", + "\n", + "====================================================================================\n", + "Unit : fs.flash Time: 0.0\n", + "------------------------------------------------------------------------------------\n", + " Unit Performance\n", + "\n", + " Variables: \n", + "\n", + " Key : Value : Units : Fixed : Bounds\n", + " Heat Duty : 5083.6 : watt : False : (None, None)\n", + " Pressure Change : 0.0000 : pascal : True : (None, None)\n", + "\n", + "------------------------------------------------------------------------------------\n", + " Stream Table\n", + " Units Inlet Vapor Outlet Liquid Outlet\n", + " flow_mol mole / second 1.0000 0.54833 0.45167 \n", + " mole_frac_comp benzene dimensionless 0.50000 0.60000 0.37860 \n", + " mole_frac_comp toluene dimensionless 0.50000 0.40000 0.62140 \n", + " temperature kelvin 368.00 369.07 369.07 \n", + " pressure pascal 1.0132e+05 1.0132e+05 1.0132e+05 \n", + "====================================================================================\n" + ] + } + ], + "execution_count": 46 }, { - "cell_type": "code", - "execution_count": null, "metadata": { "tags": [ "testing" ] }, - "outputs": [], + "cell_type": "code", "source": [ "# Check for solver status\n", "assert status.solver.termination_condition == TerminationCondition.optimal\n", @@ -981,7 +2149,9 @@ ")\n", "assert value(m.fs.flash.vap_outlet.temperature[0]) == pytest.approx(369.07, abs=1e-2)\n", "assert value(m.fs.flash.vap_outlet.pressure[0]) == pytest.approx(101325, abs=1e-3)" - ] + ], + "outputs": [], + "execution_count": null } ], "metadata": { diff --git a/idaes_examples/notebooks/docs/tut/core/flash_unit_doc.ipynb b/idaes_examples/notebooks/docs/tut/core/flash_unit_doc.ipynb index 9432ccbe..5f3b9777 100644 --- a/idaes_examples/notebooks/docs/tut/core/flash_unit_doc.ipynb +++ b/idaes_examples/notebooks/docs/tut/core/flash_unit_doc.ipynb @@ -1,1912 +1,1590 @@ { - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "metadata": { - "tags": [ - "header", - "hide-cell" - ] - }, - "outputs": [], - "source": [ - "###############################################################################\n", - "# The Institute for the Design of Advanced Energy Systems Integrated Platform\n", - "# Framework (IDAES IP) was produced under the DOE Institute for the\n", - "# Design of Advanced Energy Systems (IDAES).\n", - "#\n", - "# Copyright (c) 2018-2023 by the software owners: The Regents of the\n", - "# University of California, through Lawrence Berkeley National Laboratory,\n", - "# National Technology & Engineering Solutions of Sandia, LLC, Carnegie Mellon\n", - "# University, West Virginia University Research Corporation, et al.\n", - "# All rights reserved. Please see the files COPYRIGHT.md and LICENSE.md\n", - "# for full copyright and license information.\n", - "###############################################################################" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Flash Unit Model\n", - "\n", - "Author: Jaffer Ghouse \n", - "Maintainer: Andrew Lee \n", - "Updated: 2023-06-01 \n", - "\n", - "In this module, we will familiarize ourselves with the IDAES framework by creating and working with a flowsheet that contains a single flash tank. The flash tank will be used to perform separation of Benzene and Toluene. The inlet specifications for this flash tank are:\n", - "\n", - "Inlet Specifications:\n", - "* Mole fraction (Benzene) = 0.5\n", - "* Mole fraction (Toluene) = 0.5\n", - "* Pressure = 101325 Pa\n", - "* Temperature = 368 K\n", - "\n", - "We will complete the following tasks:\n", - "* Create the model and the IDAES Flowsheet object\n", - "* Import the appropriate property packages\n", - "* Create the flash unit and set the operating conditions\n", - "* Initialize the model and simulate the system\n", - "* Demonstrate analyses on this model through some examples and exercises\n", - "\n", - "## Key links to documentation\n", - "* Main IDAES online documentation page: https://idaes-pse.readthedocs.io/en/stable/\n", - "\n", - "## Create the Model and the IDAES Flowsheet\n", - "\n", - "In the next cell, we will perform the necessary imports to get us started. From `pyomo.environ` (a standard import for the Pyomo package), we are importing `ConcreteModel` (to create the Pyomo model that will contain the IDAES flowsheet) and `SolverFactory` (to create the object we will use to solve the equations). We will also import `Constraint` as we will be adding a constraint to the model later in the module. Lastly, we also import `value` from Pyomo. This is a function that can be used to return the current numerical value for variables and parameters in the model. These are all part of Pyomo.\n", - "\n", - "We will also import the main `FlowsheetBlock` from IDAES. The flowsheet block will contain our unit model.\n", - "\n", - "
\n", - "Inline Exercise:\n", - "Execute the cell below to perform the imports. Let a workshop organizer know if you see any errors.\n", - "
" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "from pyomo.environ import ConcreteModel, SolverFactory, Constraint, value\n", - "from idaes.core import FlowsheetBlock\n", - "\n", - "# Import idaes logger to set output levels\n", - "import idaes.logger as idaeslog" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "In the next cell, we will create the `ConcreteModel` and the `FlowsheetBlock`, and attach the flowsheet block to the Pyomo model.\n", - "\n", - "
\n", - "Inline Exercise:\n", - "Execute the cell below to create the objects\n", - "
" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [], - "source": [ - "m = ConcreteModel()\n", - "m.fs = FlowsheetBlock(dynamic=False)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "At this point, we have a single Pyomo model that contains an (almost) empty flowsheet block.\n", - "\n", - "
\n", - "Inline Exercise:\n", - "Use the pprint method on the model, i.e. m.pprint(), to see what is currently contained in the model.\n", - "
" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": { - "tags": [ - "solution" - ] - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "1 Block Declarations\n", - " fs : Size=1, Index=None, Active=True\n", - " 1 Set Declarations\n", - " _time : Size=1, Index=None, Ordered=Insertion\n", - " Key : Dimen : Domain : Size : Members\n", - " None : 1 : Any : 1 : {0.0,}\n", - "\n", - " 1 Declarations: _time\n", - "\n", - "1 Declarations: fs\n" - ] - } - ], - "source": [ - "# Todo: call pprint on the model\n", - "m.pprint()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Define Properties\n", - "\n", - "We need to define the property package for our flowsheet. In this example, we will be using the ideal property package that is available as part of the IDAES framework. This property package supports ideal gas - ideal liquid, ideal gas - NRTL, and ideal gas - Wilson models for VLE. More details on this property package can be found at: https://idaes-pse.readthedocs.io/en/stable/reference_guides/model_libraries/generic/property_models/activity_coefficient.html\n", - "\n", - "IDAES also supports creation of your own property packages that allow for specification of the fluid using any set of valid state variables (e.g., component molar flows vs overall flow and mole fractions). This flexibility is designed to support advanced modeling needs that may rely on specific formulations. To learn about creating your own property package, please consult the online documentation at: https://idaes-pse.readthedocs.io/en/stable/explanations/components/property_package/index.html and look at examples within IDAES\n", - "\n", - "For this workshop, we will import the BTX_activity_coeff_VLE property parameter block to be used in the flowsheet. This properties block will be passed to our unit model to define the appropriate state variables and equations for performing thermodynamic calculations.\n", - "\n", - "
\n", - "Inline Exercise:\n", - "Execute the following two cells to import and create the properties block.\n", - "
" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [], - "source": [ - "from idaes.models.properties.activity_coeff_models.BTX_activity_coeff_VLE import (\n", - " BTXParameterBlock,\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.properties = BTXParameterBlock(\n", - " valid_phase=(\"Liq\", \"Vap\"), activity_coeff_model=\"Ideal\", state_vars=\"FTPz\"\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Adding Flash Unit\n", - "\n", - "Now that we have the flowsheet and the properties defined, we can create the flash unit and add it to the flowsheet. \n", - "\n", - "**The Unit Model Library within IDAES includes a large set of common unit operations (see the online documentation for details: https://idaes-pse.readthedocs.io/en/stable/reference_guides/model_libraries/generic/unit_models/index.html**\n", - "\n", - "IDAES also fully supports the development of customized unit models (which we will see in a later module).\n", - "\n", - "Some of the IDAES pre-written unit models:\n", - "* Mixer / Splitter\n", - "* Heater / Cooler\n", - "* Heat Exchangers (simple and 1D discretized)\n", - "* Flash\n", - "* Reactors (kinetic, equilibrium, gibbs, stoichiometric conversion)\n", - "* Pressure changing equipment (compressors, expanders, pumps)\n", - "* Feed and Product (source / sink) components\n", - "\n", - "In this module, we will import the `Flash` unit model from `idaes.models.unit_models` and create an instance of the flash unit, attaching it to the flowsheet. Each IDAES unit model has several configurable options to customize the model behavior, but also includes defaults for these options. In this example, we will specify that the property package to be used with the Flash is the one we created earlier.\n", - "\n", - "
\n", - "Inline Exercise:\n", - "Execute the following two cells to import the Flash and create an instance of the unit model, attaching it to the flowsheet object.\n", - "
" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [], - "source": [ - "from idaes.models.unit_models import Flash" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.flash = Flash(property_package=m.fs.properties)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "At this point, we have created a flowsheet and a properties block. We have also created a flash unit and added it to the flowsheet. Under the hood, IDAES has created the required state variables and model equations. Everything is open. You can see these variables and equations by calling the Pyomo method `pprint` on the model, flowsheet, or flash tank objects. Note that this output is very exhaustive, and is not intended to provide any summary information about the model, but rather a complete picture of all of the variables and equations in the model." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Set Operating Conditions\n", - "\n", - "Now that we have created our unit model, we can specify the necessary operating conditions. It is often very useful to determine the degrees of freedom before we specify any conditions.\n", - "\n", - "The `idaes.core.util.model_statistics` package has a function `degrees_of_freedom`. To see how to use this function, we can make use of the Python function `help(func)`. This function prints the appropriate documentation string for the function.\n", - "\n", - "
\n", - "Inline Exercise:\n", - "Import the degrees_of_freedom function and print the help for the function by calling the Python help function.\n", - "
" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": { - "tags": [ - "solution" - ] - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Help on function degrees_of_freedom in module idaes.core.util.model_statistics:\n", - "\n", - "degrees_of_freedom(block)\n", - " Method to return the degrees of freedom of a model.\n", - " \n", - " Args:\n", - " block : model to be studied\n", - " \n", - " Returns:\n", - " Number of degrees of freedom in block.\n", - "\n" - ] - } - ], - "source": [ - "# Todo: import the degrees_of_freedom function from the idaes.core.util.model_statistics package\n", - "from idaes.core.util.model_statistics import degrees_of_freedom\n", - "\n", - "# Todo: Call the python help on the degrees_of_freedom function\n", - "help(degrees_of_freedom)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "
\n", - "Inline Exercise:\n", - "Now print the degrees of freedom for your model. The result should be 7.\n", - "
" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": { - "tags": [ - "solution" - ] - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Degrees of Freedom = 7\n" - ] - } - ], - "source": [ - "# Todo: print the degrees of freedom for your model\n", - "print(\"Degrees of Freedom =\", degrees_of_freedom(m))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "To satisfy our degrees of freedom, we will first specify the inlet conditions. We can specify these values through the `inlet` port of the flash unit.\n", - "\n", - "**To see the list of naming conventions for variables within the IDAES framework, consult the online documentation at: https://idaes-pse.readthedocs.io/en/stable/explanations/conventions.html#standard-naming-format**\n", - "\n", - "As an example, to fix the molar flow of the inlet to be 1.0, you can use the following notation:\n", - "```python\n", - "m.fs.flash.inlet.flow_mol.fix(1.0)\n", - "```\n", - "\n", - "To specify variables that are indexed by components, you can use the following notation:\n", - "```python\n", - "m.fs.flash.inlet.mole_frac_comp[0, \"benzene\"].fix(0.5)\n", - "```\n", - "\n", - "
\n", - "Note:\n", - "The \"0\" in the indexing of the component mole fraction is present because IDAES models support both dynamic and steady state simulation, and the \"0\" refers to a timestep. Dynamic modeling is beyond the scope of this workshop. Since we are performing steady state modeling, there is only a single timestep in the model.\n", - "
\n", - "\n", - "In the next cell, we will specify the inlet conditions. To satisfy the remaining degrees of freedom, we will make two additional specifications on the flash tank itself. The names of the key variables within the Flash unit model can also be found in the online documentation: https://idaes-pse.readthedocs.io/en/stable/reference_guides/model_libraries/generic/unit_models/flash.html#variables.\n", - "\n", - "\n", - "To specify the value of a variable on the unit itself, use the following notation.\n", - "\n", - "```python\n", - "m.fs.flash.heat_duty.fix(0)\n", - "```\n", - "\n", - "For this module, we will use the following specifications:\n", - "* inlet overall molar flow = 1.0 (`flow_mol`)\n", - "* inlet temperature = 368 K (`temperature`)\n", - "* inlet pressure = 101325 Pa (`pressure`)\n", - "* inlet mole fraction (benzene) = 0.5 (`mole_frac_comp[0, \"benzene\"]`)\n", - "* inlet mole fraction (toluene) = 0.5 (`mole_frac_comp[0, \"toluene\"]`)\n", - "* The heat duty on the flash set to 0 (`heat_duty`)\n", - "* The pressure drop across the flash tank set to 0 (`deltaP`)\n", - "\n", - "
\n", - "Inline Exercise:\n", - "Write the code below to specify the inlet conditions and unit specifications described above\n", - "
" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": { - "tags": [ - "solution" - ] - }, - "outputs": [], - "source": [ - "# Todo: Add inlet specifications given above\n", - "m.fs.flash.inlet.flow_mol.fix(1)\n", - "m.fs.flash.inlet.temperature.fix(368)\n", - "m.fs.flash.inlet.pressure.fix(101325)\n", - "m.fs.flash.inlet.mole_frac_comp[0, \"benzene\"].fix(0.5)\n", - "m.fs.flash.inlet.mole_frac_comp[0, \"toluene\"].fix(0.5)\n", - "\n", - "# Todo: Add 2 flash unit specifications given above\n", - "m.fs.flash.heat_duty.fix(0)\n", - "m.fs.flash.deltaP.fix(0)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "
\n", - "Inline Exercise:\n", - "Check the degrees of freedom again to ensure that the system is now square. You should see that the degrees of freedom is now 0.\n", - "
" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": { - "tags": [ - "solution" - ] - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Degrees of Freedom = 0\n" - ] - } - ], - "source": [ - "# Todo: print the degrees of freedom for your model\n", - "print(\"Degrees of Freedom =\", degrees_of_freedom(m))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Initializing the Model\n", - "\n", - "IDAES includes pre-written initialization routines for all unit models. You can call this initialize method on the units. In the next module, we will demonstrate the use of a sequential modular solve cycle to initialize flowsheets.\n", - "\n", - "
\n", - "Inline Exercise:\n", - "Call the initialize method on the flash unit to initialize the model.\n", - "
" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": { - "tags": [ - "solution" - ] - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "2023-11-02 10:30:25 [INFO] idaes.init.fs.flash.control_volume.properties_in: Initialization Step 1 optimal - Optimal Solution Found.\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "2023-11-02 10:30:25 [INFO] idaes.init.fs.flash.control_volume.properties_in: Initialization Step 2 optimal - Optimal Solution Found.\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "2023-11-02 10:30:25 [INFO] idaes.init.fs.flash.control_volume.properties_in: Initialization Step 3 optimal - Optimal Solution Found.\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "2023-11-02 10:30:25 [INFO] idaes.init.fs.flash.control_volume.properties_in: Initialization Step 4 optimal - Optimal Solution Found.\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "2023-11-02 10:30:25 [INFO] idaes.init.fs.flash.control_volume.properties_in: Initialization Step 5 optimal - Optimal Solution Found.\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "2023-11-02 10:30:25 [INFO] idaes.init.fs.flash.control_volume.properties_out: Initialization Step 1 optimal - Optimal Solution Found.\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "2023-11-02 10:30:25 [INFO] idaes.init.fs.flash.control_volume.properties_out: Initialization Step 2 optimal - Optimal Solution Found.\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "2023-11-02 10:30:25 [INFO] idaes.init.fs.flash.control_volume.properties_out: Initialization Step 3 optimal - Optimal Solution Found.\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "2023-11-02 10:30:25 [INFO] idaes.init.fs.flash.control_volume.properties_out: Initialization Step 4 optimal - Optimal Solution Found.\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "2023-11-02 10:30:25 [INFO] idaes.init.fs.flash.control_volume.properties_out: Initialization Step 5 optimal - Optimal Solution Found.\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "2023-11-02 10:30:25 [INFO] idaes.init.fs.flash.control_volume.properties_out: State Released.\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "2023-11-02 10:30:25 [INFO] idaes.init.fs.flash.control_volume: Initialization Complete\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "2023-11-02 10:30:25 [INFO] idaes.init.fs.flash.control_volume.properties_in: State Released.\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "2023-11-02 10:30:25 [INFO] idaes.init.fs.flash: Initialization Complete: optimal - Optimal Solution Found\n" - ] - } - ], - "source": [ - "# Todo: initialize the flash unit\n", - "m.fs.flash.initialize(outlvl=idaeslog.INFO)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now that the model has been defined and initialized, we can solve the model.\n", - "\n", - "
\n", - "Inline Exercise:\n", - "Using the notation described in the previous model, create an instance of the \"ipopt\" solver and use it to solve the model. Set the tee option to True to see the log output.\n", - "
" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "metadata": { - "tags": [ - "solution" - ] - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Ipopt 3.13.2: \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...: 135\n", - "Number of nonzeros in inequality constraint Jacobian.: 0\n", - "Number of nonzeros in Lagrangian Hessian.............: 72\n", - "\n", - "Total number of variables............................: 41\n", - " variables with only lower bounds: 3\n", - " variables with lower and upper bounds: 10\n", - " variables with only upper bounds: 0\n", - "Total number of equality constraints.................: 41\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 1.46e-11 1.00e+00 -1.0 0.00e+00 - 0.00e+00 0.00e+00 0\n", - "\n", - "Number of Iterations....: 0\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.6292494973376915e-12 1.4551915228366852e-11\n", - "Complementarity.........: 0.0000000000000000e+00 0.0000000000000000e+00\n", - "Overall NLP error.......: 5.6292494973376915e-12 1.4551915228366852e-11\n", - "\n", - "\n", - "Number of objective function evaluations = 1\n", - "Number of objective gradient evaluations = 1\n", - "Number of equality constraint evaluations = 1\n", - "Number of inequality constraint evaluations = 0\n", - "Number of equality constraint Jacobian evaluations = 1\n", - "Number of inequality constraint Jacobian evaluations = 0\n", - "Number of Lagrangian Hessian evaluations = 0\n", - "Total CPU secs in IPOPT (w/o function evaluations) = 0.000\n", - "Total CPU secs in NLP function evaluations = 0.000\n", - "\n", - "EXIT: Optimal Solution Found.\n", - "\b\b\b\b\b\b\b\b\b\b\b\b\b\b" - ] - } - ], - "source": [ - "# Todo: create the ipopt solver\n", - "solver = SolverFactory(\"ipopt\")\n", - "\n", - "# Todo: solve the model\n", - "status = solver.solve(m, tee=True)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Viewing the Results\n", - "\n", - "Once a model is solved, the values returned by the solver are loaded into the model object itself. We can access the value of any variable in the model with the `value` function. For example:\n", - "```python\n", - "print('Vap. Outlet Temperature = ', value(m.fs.flash.vap_outlet.temperature[0]))\n", - "```\n", - "\n", - "You can also find more information about a variable or an entire port using the `display` method from Pyomo:\n", - "```python\n", - "m.fs.flash.vap_outlet.temperature.display()\n", - "m.fs.flash.vap_outlet.display()\n", - "```\n", - "\n", - "
\n", - "Inline Exercise:\n", - "Execute the cells below to show the current value of the flash vapor outlet pressure. This cell also shows use of the display function to see the values of the variables in the vap_outlet and the liq_outlet.\n", - "
" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Pressure = 101325.0\n", - "\n", - "Output from display:\n", - "vap_outlet : Size=1\n", - " Key : Name : Value\n", - " None : flow_mol : {0.0: 0.39611817487741735}\n", - " : mole_frac_comp : {(0.0, 'benzene'): 0.6339766485081294, (0.0, 'toluene'): 0.36602335149187054}\n", - " : pressure : {0.0: 101325.0}\n", - " : temperature : {0.0: 368.0}\n", - "liq_outlet : Size=1\n", - " Key : Name : Value\n", - " None : flow_mol : {0.0: 0.6038818251225827}\n", - " : mole_frac_comp : {(0.0, 'benzene'): 0.4121175977229309, (0.0, 'toluene'): 0.587882402277069}\n", - " : pressure : {0.0: 101325.0}\n", - " : temperature : {0.0: 368.0}\n" - ] - } - ], - "source": [ - "# Print the pressure of the flash vapor outlet\n", - "print(\"Pressure =\", value(m.fs.flash.vap_outlet.pressure[0]))\n", - "\n", - "print()\n", - "print(\"Output from display:\")\n", - "# Call display on vap_outlet and liq_outlet of the flash\n", - "m.fs.flash.vap_outlet.display()\n", - "m.fs.flash.liq_outlet.display()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The output from `display` is quite exhaustive and not really intended to provide quick summary information. Because Pyomo is built on Python, there are opportunities to format the output any way we like. Most IDAES models have a `report` method which provides a summary of the results for the model.\n", - "\n", - "
\n", - "Inline Exercise:\n", - "Execute the cell below which uses the function above to print a summary of the key variables in the flash model, including the inlet, the vapor, and the liquid ports. \n", - "
" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "====================================================================================\n", - "Unit : fs.flash Time: 0.0\n", - "------------------------------------------------------------------------------------\n", - " Unit Performance\n", - "\n", - " Variables: \n", - "\n", - " Key : Value : Units : Fixed : Bounds\n", - " Heat Duty : 0.0000 : watt : True : (None, None)\n", - " Pressure Change : 0.0000 : pascal : True : (None, None)\n", - "\n", - "------------------------------------------------------------------------------------\n", - " Stream Table\n", - " Units Inlet Vapor Outlet Liquid Outlet\n", - " flow_mol mole / second 1.0000 0.39612 0.60388 \n", - " mole_frac_comp benzene dimensionless 0.50000 0.63398 0.41212 \n", - " mole_frac_comp toluene dimensionless 0.50000 0.36602 0.58788 \n", - " temperature kelvin 368.00 368.00 368.00 \n", - " pressure pascal 1.0132e+05 1.0132e+05 1.0132e+05 \n", - "====================================================================================\n" - ] - } - ], - "source": [ - "m.fs.flash.report()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Studying Purity as a Function of Heat Duty\n", - "\n", - "Since the entire modeling framework is built upon Python, it includes a complete programming environment for whatever analysis we may want to perform. In this next exercise, we will make use of what we learned in this and the previous module to generate a figure showing some output variables as a function of the heat duty in the flash tank.\n", - "\n", - "First, let's import the matplotlib package for plotting as we did in the previous module.\n", - "
\n", - "Inline Exercise:\n", - "Execute the cell below to import matplotlib appropriately.\n", - "
" - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "metadata": {}, - "outputs": [], - "source": [ - "import matplotlib.pyplot as plt" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Exercise specifications:\n", - "* Generate a figure showing the flash tank heat duty (`m.fs.flash.heat_duty[0]`) vs. the vapor flowrate (`m.fs.flash.vap_outlet.flow_mol[0]`)\n", - "* Specify the heat duty from -17000 to 25000 over 50 steps\n", - "\n", - "
\n", - "Inline Exercise:\n", - "Using what you have learned so far, fill in the missing code below to generate the figure specified above. (Hint: import numpy and use the linspace function from the previous module)\n", - "
" - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "metadata": { - "scrolled": true, - "tags": [ - "solution" - ] - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Simulating with Q = -17000.0\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = -16142.857142857143\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = -15285.714285714286\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = -14428.571428571428\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = -13571.428571428572\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = -12714.285714285714\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = -11857.142857142857\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = -11000.0\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = -10142.857142857143\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = -9285.714285714286\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = -8428.57142857143\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = -7571.4285714285725\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = -6714.285714285714\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = -5857.142857142857\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = -5000.0\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = -4142.857142857143\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = -3285.7142857142862\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = -2428.5714285714294\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = -1571.4285714285725\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = -714.2857142857156\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = 142.8571428571413\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = 1000.0\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = 1857.142857142855\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = 2714.2857142857138\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = 3571.4285714285725\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = 4428.5714285714275\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = 5285.714285714286\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = 6142.857142857141\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = 7000.0\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = 7857.142857142855\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = 8714.285714285714\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = 9571.428571428569\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = 10428.571428571428\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = 11285.714285714286\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = 12142.857142857141\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = 13000.0\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = 13857.142857142855\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = 14714.285714285714\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = 15571.428571428569\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = 16428.571428571428\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = 17285.714285714283\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = 18142.857142857145\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = 19000.0\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = 19857.142857142855\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = 20714.28571428571\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = 21571.428571428572\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = 22428.571428571428\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = 23285.714285714283\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = 24142.857142857145\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = 25000.0\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n" - ] - }, - { - "data": { - "image/png": "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", - "text/plain": [ - "
" + "cells": [ + { + "cell_type": "code", + "metadata": { + "tags": [ + "header", + "hide-cell" + ], + "ExecuteTime": { + "end_time": "2025-06-06T16:45:45.923673Z", + "start_time": "2025-06-06T16:45:45.919855Z" + } + }, + "source": [ + "###############################################################################\n", + "# The Institute for the Design of Advanced Energy Systems Integrated Platform\n", + "# Framework (IDAES IP) was produced under the DOE Institute for the\n", + "# Design of Advanced Energy Systems (IDAES).\n", + "#\n", + "# Copyright (c) 2018-2023 by the software owners: The Regents of the\n", + "# University of California, through Lawrence Berkeley National Laboratory,\n", + "# National Technology & Engineering Solutions of Sandia, LLC, Carnegie Mellon\n", + "# University, West Virginia University Research Corporation, et al.\n", + "# All rights reserved. Please see the files COPYRIGHT.md and LICENSE.md\n", + "# for full copyright and license information.\n", + "###############################################################################" + ], + "outputs": [], + "execution_count": 1 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": [ + "# Flash Unit Model Tutorial\n", + "\n", + "Author: Jaffer Ghouse
\n", + "Maintainer: Tanner Polley
\n", + "Updated: 2025-06-03\n", + "\n", + "In this module, we will familiarize ourselves with the IDAES framework by creating and working with a flowsheet that contains a single flash tank. The flash tank will be used to perform separation of Benzene and Toluene.\n", + "\n", + "The general workflow of setting up an IDAES flowsheet is the following:\n", + "\n", + "- 1 Importing Modules\n", + "- 2 Building a Model\n", + "- 3 Scaling the Model\n", + "- 4 Specifying the Model\n", + "- 5 Initializing the Model\n", + "- 6 Solving the Model\n", + "- 7 Analyzing and Visualizing the Results\n", + "\n", + "We will complete each of these steps as well as demonstrate analyses on this model through some examples and exercises\n", + "\n", + "## Key links to documentation\n", + "* Main IDAES online documentation page: https://idaes-pse.readthedocs.io/en/stable/\n", + "* General Workflow: https://idaes-pse.readthedocs.io/en/stable/how_to_guides/workflow/general.html\n", + "* Flash Unit Model Documentation: https://idaes-pse.readthedocs.io/en/stable/reference_guides/model_libraries/generic/unit_models/flash.html\n", + "\n" ] - }, - "metadata": { - "filenames": { - "image/png": "C:\\Users\\dkgun\\src\\dangunter\\examples\\idaes_examples\\notebooks\\_build\\jupyter_execute\\docs\\tut\\core\\flash_unit_doc_33_51.png" - } - }, - "output_type": "display_data" - } - ], - "source": [ - "# import the solve_successful checking function from workshop tools\n", - "from idaes_examples.mod.tut.workshoptools import solve_successful\n", - "\n", - "# Todo: import numpy\n", - "import numpy as np\n", - "\n", - "# create the empty lists to store the results that will be plotted\n", - "Q = []\n", - "V = []\n", - "\n", - "# re-initialize model\n", - "m.fs.flash.initialize(outlvl=idaeslog.WARNING)\n", - "\n", - "# Todo: Write the for loop specification using numpy's linspace\n", - "for duty in np.linspace(-17000, 25000, 50):\n", - " # fix the heat duty\n", - " m.fs.flash.heat_duty.fix(duty)\n", - "\n", - " # append the value of the duty to the Q list\n", - " Q.append(duty)\n", - "\n", - " # print the current simulation\n", - " print(\"Simulating with Q = \", value(m.fs.flash.heat_duty[0]))\n", - "\n", - " # Solve the model\n", - " status = solver.solve(m)\n", - "\n", - " # append the value for vapor fraction if the solve was successful\n", - " if solve_successful(status):\n", - " V.append(value(m.fs.flash.vap_outlet.flow_mol[0]))\n", - " print(\"... solve successful.\")\n", - " else:\n", - " V.append(0.0)\n", - " print(\"... solve failed.\")\n", - "\n", - "# Create and show the figure\n", - "plt.figure(\"Vapor Fraction\")\n", - "plt.plot(Q, V)\n", - "plt.grid()\n", - "plt.xlabel(\"Heat Duty [J]\")\n", - "plt.ylabel(\"Vapor Fraction [-]\")\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "
\n", - "Inline Exercise:\n", - "Repeat the exercise above, but create a figure showing the heat duty vs. the mole fraction of Benzene in the vapor outlet. Remove any unnecessary printing to create cleaner results.\n", - "
" - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "metadata": { - "tags": [ - "solution" - ] - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Simulating with Q = -17000.0\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = -16142.857142857143\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = -15285.714285714286\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = -14428.571428571428\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = -13571.428571428572\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = -12714.285714285714\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = -11857.142857142857\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = -11000.0\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = -10142.857142857143\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = -9285.714285714286\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = -8428.57142857143\n" - ] }, { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = -7571.4285714285725\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = -6714.285714285714\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = -5857.142857142857\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = -5000.0\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = -4142.857142857143\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = -3285.7142857142862\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = -2428.5714285714294\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = -1571.4285714285725\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = -714.2857142857156\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = 142.8571428571413\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = 1000.0\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = 1857.142857142855\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = 2714.2857142857138\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = 3571.4285714285725\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = 4428.5714285714275\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = 5285.714285714286\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = 6142.857142857141\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = 7000.0\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = 7857.142857142855\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = 8714.285714285714\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = 9571.428571428569\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = 10428.571428571428\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = 11285.714285714286\n" - ] + "metadata": {}, + "cell_type": "markdown", + "source": [ + "## 1 Import Modules\n", + "\n", + "In the next cell, we will perform the necessary imports to get us started. From `pyomo.environ` (a standard import for the Pyomo package), we are importing `ConcreteModel` (to create the Pyomo model that will contain the IDAES flowsheet) and `SolverFactory` (to create the object we will use to solve the equations). We will also import `Constraint` as we will be adding a constraint to the model later in the module. Lastly, we also import `value` from Pyomo. This is a function that can be used to return the current numerical value for variables and parameters in the model. These are all part of Pyomo.\n", + "\n", + "We will also import the main `FlowsheetBlock` from IDAES. The flowsheet block will contain our unit model.\n", + "\n", + "
\n", + "Inline Exercise:\n", + "Execute the cell below to perform the imports. Let a workshop organizer know if you see any errors.\n", + "
" + ] }, { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = 12142.857142857141\n" - ] + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:49.094293Z", + "start_time": "2025-06-06T16:45:45.938076Z" + } + }, + "cell_type": "code", + "source": [ + "from pyomo.environ import ConcreteModel, SolverFactory, Constraint, value\n", + "from idaes.core import FlowsheetBlock\n", + "\n", + "# Import idaes logger to set output levels\n", + "import idaes.logger as idaeslog\n", + "%matplotlib inline" + ], + "outputs": [], + "execution_count": 2 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": [ + "## 2 Create the Model and IDAES Flowsheet\n", + "\n", + "In the next cell, we will create the `ConcreteModel` object often named `m` (which comes from Pyomo) and then connect the `FlowsheetBlock` (which comes from IDAES) to `m`. We ensure `dynamic=False` since this is a steady-state problem. This creates our overall model and adds the flowsheet capabilities that IDAES provides to the Pyomo model.\n", + "\n", + "
\n", + "Inline Exercise:\n", + "Execute the cell below to create the objects\n", + "
" + ] }, { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = 13000.0\n" - ] + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:49.521103Z", + "start_time": "2025-06-06T16:45:49.517229Z" + } + }, + "cell_type": "code", + "source": [ + "m = ConcreteModel()\n", + "m.fs = FlowsheetBlock(dynamic=False)" + ], + "outputs": [], + "execution_count": 3 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:49.577500Z", + "start_time": "2025-06-06T16:45:49.572256Z" + }, + "tags": [ + "solution" + ] + }, + "cell_type": "code", + "source": [ + "# Todo: call pprint on the model\n", + "m.pprint()" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1 Block Declarations\n", + " fs : Size=1, Index=None, Active=True\n", + " 1 Set Declarations\n", + " _time : Size=1, Index=None, Ordered=Insertion\n", + " Key : Dimen : Domain : Size : Members\n", + " None : 1 : Any : 1 : {0.0,}\n", + "\n", + " 1 Declarations: _time\n", + "\n", + "1 Declarations: fs\n" + ] + } + ], + "execution_count": 5 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": [ + "### 2.1 Define Properties\n", + "\n", + "We need to define the property package for our flowsheet. In this example, we will be using the ideal property package that is available as part of the IDAES framework. This property package supports ideal gas - ideal liquid, ideal gas - NRTL, and ideal gas - Wilson models for VLE. More details on this property package can be found at: https://idaes-pse.readthedocs.io/en/stable/reference_guides/model_libraries/generic/property_models/activity_coefficient.html\n", + "\n", + "IDAES also supports creation of your own property packages that will be shown in a later module.\n", + "\n", + "For this workshop, we will import the BTX_activity_coeff_VLE property parameter block to be used in the flowsheet. This properties block will be passed to our unit model to define the appropriate state variables and equations for performing thermodynamic calculations.\n", + "\n", + "
\n", + "Inline Exercise:\n", + "Execute the following two cells to import and create the properties block.\n", + "
" + ] }, { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = 13857.142857142855\n" - ] + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:49.649106Z", + "start_time": "2025-06-06T16:45:49.626357Z" + } + }, + "cell_type": "code", + "source": [ + "from idaes.models.properties.activity_coeff_models.BTX_activity_coeff_VLE import (\n", + " BTXParameterBlock,\n", + ")" + ], + "outputs": [], + "execution_count": 6 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:49.669359Z", + "start_time": "2025-06-06T16:45:49.657658Z" + } + }, + "cell_type": "code", + "source": [ + "m.fs.properties = BTXParameterBlock(\n", + " valid_phase=(\"Liq\", \"Vap\"), activity_coeff_model=\"Ideal\", state_vars=\"FTPz\"\n", + ")" + ], + "outputs": [], + "execution_count": 7 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": [ + "### 2.2 Adding Flash Unit\n", + "\n", + "Now that we have the flowsheet and the properties defined, we can create the flash unit and add it to the flowsheet.\n", + "\n", + "**The Unit Model Library within IDAES includes a large set of common unit operations (see the online documentation for details: https://idaes-pse.readthedocs.io/en/stable/reference_guides/model_libraries/generic/unit_models/index.html**\n", + "\n", + "IDAES also fully supports the development of customized unit models (which we will see in a later module).\n", + "\n", + "Some of the IDAES pre-written unit models:\n", + "* Mixer / Splitter\n", + "* Heater / Cooler\n", + "* Heat Exchangers (simple and 1D discretized)\n", + "* Flash\n", + "* Reactors (kinetic, equilibrium, gibbs, stoichiometric conversion)\n", + "* Pressure changing equipment (compressors, expanders, pumps)\n", + "* Feed and Product (source / sink) components\n", + "\n", + "In this module, we will import the `Flash` unit model from `idaes.models.unit_models` and create an instance of the flash unit, attaching it to the flowsheet. Each IDAES unit model has several configurable options to customize the model behavior, but also includes defaults for these options. In this example, we will specify that the property package to be used with the Flash unit model is the one we created earlier by setting `property_package=m.fs.properties` within the `Flash` method.\n", + "\n", + "
\n", + "Inline Exercise:\n", + "Execute the following two cells to import the Flash and create an instance of the unit model, attaching it to the flowsheet object.\n", + "
" + ] }, { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = 14714.285714285714\n" - ] + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:49.749283Z", + "start_time": "2025-06-06T16:45:49.678730Z" + } + }, + "cell_type": "code", + "source": "from idaes.models.unit_models import Flash", + "outputs": [], + "execution_count": 8 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:49.772441Z", + "start_time": "2025-06-06T16:45:49.758167Z" + } + }, + "cell_type": "code", + "source": "m.fs.flash = Flash(property_package=m.fs.properties)", + "outputs": [], + "execution_count": 9 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "At this point, we have created a flowsheet and a properties block. We have also created a flash unit and added it to the flowsheet. Under the hood, IDAES has created the required state variables and model equations. Everything is open. You can see these variables and equations by calling the Pyomo method `pprint` on the model, flowsheet, or flash tank objects. Note that this output is very exhaustive, and is not intended to provide any summary information about the model, but rather a complete picture of all of the variables and equations in the model." + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": [ + "## 3 Scaling the Model\n", + "\n", + "Now that the model is built, with properties set and the unit model created and added to the flowsheet, the next step is to scale the model. Ensuring that a model is well scaled is important for increasing the efficiency and reliability of solvers, and users should consider model scaling as an integral part of the modeling process. IDAES provides a number of tool for assisting users with scaling their models, and details on these can be found at https://idaes-pse.readthedocs.io/en/stable/reference_guides/scaling/scaling.html#scaling-toolbox\n", + "\n", + "There are currently two primary methods in scaling the model: manual scaling of each relevant component, or utilizing the AutoScaler Class. The more careful and risk-free method of manually scaling each component is the recommended method for maximum control and assurance that the model will be well-scaled. This comes with the drawback of being more meticulous while the AutoScaler is much simpler to use since it scaled the whole model all at once, it is less precise by lacking direct control over the scaling factor for each component and relying on scaling factors to be estimated. Both methods will be shown below" + ] }, { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = 15571.428571428569\n" - ] + "metadata": {}, + "cell_type": "markdown", + "source": [ + "### 3.1 Manual Scaling\n", + "The `set_scaling_factor` function is imported from `idaes.core.scaling.util` and is called with and used on each relevant component that needs to be well scaled. The component is the first argument and its scaling factor is the second argument.\n", + "\n", + "
\n", + "Inline Exercise:\n", + "Execute the following two cells to import the `set_scaling_factor` and set the scaling factor for both temperature and pressure\n", + "
\n", + "\n", + "Both `temperature` and `pressure` can be found at `m.fs.flash.inlet`." + ] }, { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = 16428.571428571428\n" - ] + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:49.795252Z", + "start_time": "2025-06-06T16:45:49.792075Z" + } + }, + "cell_type": "code", + "source": "from idaes.core.scaling.util import set_scaling_factor", + "outputs": [], + "execution_count": 11 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:49.809330Z", + "start_time": "2025-06-06T16:45:49.806174Z" + } + }, + "cell_type": "code", + "source": [ + "set_scaling_factor(m.fs.flash.inlet.temperature, 300)\n", + "set_scaling_factor(m.fs.flash.inlet.pressure, 1e6)" + ], + "outputs": [], + "execution_count": 12 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": [ + "### 3.2 Scaling with AutoScaler\n", + "The `AutoScaler` class is imported from `idaes.core.scaling.autoscaling` and an instance of the class is created. This instance contains the method `scale_model` which is used to scale the whole model at once. This can be a useful option but is generally more risky than manually scaling the model components since it has less direct control and specification.\n", + "\n", + "
\n", + "Inline Exercise:\n", + "Execute the following two cells to import the `AutoScaler` class and create an autoscaler instance that scaled the whole model at once\n", + "
" + ] }, { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = 17285.714285714283\n" - ] + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:49.826276Z", + "start_time": "2025-06-06T16:45:49.823030Z" + } + }, + "cell_type": "code", + "source": "from idaes.core.scaling.autoscaling import AutoScaler", + "outputs": [], + "execution_count": 13 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:50.217776Z", + "start_time": "2025-06-06T16:45:49.836920Z" + } + }, + "cell_type": "code", + "source": [ + "autoscaler = AutoScaler()\n", + "autoscaler.scale_model(m)" + ], + "outputs": [], + "execution_count": 14 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "" + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": [ + "## 4 Set Operating Conditions\n", + "\n", + "Now that we have created our unit model and scaled it, we can specify the necessary operating conditions. The inlet specifications for this flash tank are:\n", + "\n", + "Inlet Specifications:\n", + "* Mole fraction (Benzene) = 0.5\n", + "* Mole fraction (Toluene) = 0.5\n", + "* Pressure = 101325 Pa\n", + "* Temperature = 368 K\n", + "\n", + "\n" + ] }, { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = 18142.857142857145\n" - ] + "metadata": {}, + "cell_type": "markdown", + "source": [ + "### 4.1 Degrees of Freedom\n", + "\n", + "It is often very useful to first determine the degrees of freedom before we specify any conditions.\n", + "\n", + "The `idaes.core.util.model_statistics` package has a function `degrees_of_freedom`. To see how to use this function, we can make use of the Python function `help(func)`. This function prints the appropriate documentation string for the function.\n" + ] }, { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = 19000.0\n" - ] + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:50.240605Z", + "start_time": "2025-06-06T16:45:50.237594Z" + }, + "tags": [ + "solution" + ] + }, + "cell_type": "code", + "source": [ + "# Todo: import the degrees_of_freedom function from the idaes.core.util.model_statistics package\n", + "from idaes.core.util.model_statistics import degrees_of_freedom\n", + "\n", + "# Todo: Call the python help on the degrees_of_freedom function\n", + "help(degrees_of_freedom)" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Help on function degrees_of_freedom in module idaes.core.util.model_statistics:\n", + "\n", + "degrees_of_freedom(block)\n", + " Method to return the degrees of freedom of a model.\n", + "\n", + " Args:\n", + " block : model to be studied\n", + "\n", + " Returns:\n", + " Number of degrees of freedom in block.\n", + "\n" + ] + } + ], + "execution_count": 16 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:50.297399Z", + "start_time": "2025-06-06T16:45:50.291352Z" + }, + "tags": [ + "solution" + ] + }, + "cell_type": "code", + "source": [ + "# Todo: print the degrees of freedom for your model\n", + "print(\"Degrees of Freedom =\", degrees_of_freedom(m))" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Degrees of Freedom = 7\n" + ] + } + ], + "execution_count": 18 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": [ + "### 4.2 Specify Inlet Conditions\n", + "\n", + "To satisfy our degrees of freedom, we will first specify the inlet conditions. We can specify these values through the `inlet` port of the flash unit.\n", + "\n", + "**To see the list of naming conventions for variables within the IDAES framework, consult the online documentation at: https://idaes-pse.readthedocs.io/en/stable/explanations/conventions.html#standard-naming-format**\n", + "\n", + "As an example, to fix the molar flow of the inlet to be 1.0, you can use the following notation:\n", + "```python\n", + "m.fs.flash.inlet.flow_mol.fix(1.0)\n", + "```\n", + "\n", + "To specify variables that are indexed by components, you can use the following notation:\n", + "```python\n", + "m.fs.flash.inlet.mole_frac_comp[0, \"benzene\"].fix(0.5)\n", + "```\n", + "\n", + "
\n", + "Note:\n", + "The \"0\" in the indexing of the component mole fraction is present because IDAES models support both dynamic and steady state simulation, and the \"0\" refers to a timestep. Dynamic modeling is beyond the scope of this workshop. Since we are performing steady state modeling, there is only a single timestep in the model.\n", + "
\n", + "\n", + "In the next cell, we will specify the inlet conditions. To satisfy the remaining degrees of freedom, we will make two additional specifications on the flash tank itself. The names of the key variables within the Flash unit model can also be found in the online documentation: https://idaes-pse.readthedocs.io/en/stable/reference_guides/model_libraries/generic/unit_models/flash.html#variables.\n", + "\n", + "\n", + "To specify the value of a variable on the unit itself, use the following notation.\n", + "\n", + "```python\n", + "m.fs.flash.heat_duty.fix(0)\n", + "```\n", + "\n", + "For this module, we will use the following specifications:\n", + "* inlet overall molar flow = 1.0 (`flow_mol`)\n", + "* inlet temperature = 368 K (`temperature`)\n", + "* inlet pressure = 101325 Pa (`pressure`)\n", + "* inlet mole fraction (benzene) = 0.5 (`mole_frac_comp[0, \"benzene\"]`)\n", + "* inlet mole fraction (toluene) = 0.5 (`mole_frac_comp[0, \"toluene\"]`)\n", + "* The heat duty on the flash set to 0 (`heat_duty`)\n", + "* The pressure drop across the flash tank set to 0 (`deltaP`)\n", + "\n" + ] }, { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = 19857.142857142855\n" - ] + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:50.396800Z", + "start_time": "2025-06-06T16:45:50.392314Z" + }, + "tags": [ + "solution" + ] + }, + "cell_type": "code", + "source": [ + "# Todo: Add inlet specifications given above\n", + "m.fs.flash.inlet.flow_mol.fix(1)\n", + "m.fs.flash.inlet.temperature.fix(368)\n", + "m.fs.flash.inlet.pressure.fix(101325)\n", + "m.fs.flash.inlet.mole_frac_comp[0, \"benzene\"].fix(0.5)\n", + "m.fs.flash.inlet.mole_frac_comp[0, \"toluene\"].fix(0.5)\n", + "\n", + "# Todo: Add 2 flash unit specifications given above\n", + "m.fs.flash.heat_duty.fix(0)\n", + "m.fs.flash.deltaP.fix(0)" + ], + "outputs": [], + "execution_count": 21 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": [ + "Now that all the inlets have been specified, we can check the degrees of freedom again to ensure the system is square and has a degree of freedom of 0\n", + "\n" + ] }, { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = 20714.28571428571\n" - ] + "metadata": { + "tags": [ + "solution" + ] + }, + "cell_type": "code", + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Degrees of Freedom = 0\n" + ] + } + ], + "execution_count": 23, + "source": [ + "# Todo: print the degrees of freedom for your model\n", + "print(\"Degrees of Freedom =\", degrees_of_freedom(m))" + ] }, { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = 21571.428571428572\n" - ] + "metadata": {}, + "cell_type": "markdown", + "source": [ + "## 5 Initializing the Model\n", + "\n", + "Now that all building steps are complete, the last step before solving the model is to initialize the model, or prepping the solve by giving it a good starting point. This is essentially giving the solver an initial guess for the iterative solver to reach convergence and is essential for both a fast and accurate solution. In IDAES, the current standard for initializing the model is by utilizing initializer instances. These initializer instances contain the initialize method that can be applied to any model type. For more information on initializing in IDAES, visit https://idaes-pse.readthedocs.io/en/stable/reference_guides/initialization/index.html.
\n", + "\n", + "For this tutorial, we will import the initializer class `BlockTriangularizationInitializer` class from the `default_initializer` method from the flash unit model. This is often the simplest way to obtain a compatible initializer for each unit model, but you can also directly important any initializer needed from this source `idaes.core.initialization`. Each initializer instance contains the `initialize()` method that requires an argument to be initialized and in this case its the flash unit model.\n" + ] }, { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = 22428.571428571428\n" - ] + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:51:40.247234Z", + "start_time": "2025-06-06T16:51:39.730044Z" + }, + "tags": [ + "solution" + ] + }, + "cell_type": "code", + "source": [ + "# Todo: initialize the flash unit\n", + "FlashInitializer = m.fs.flash.default_initializer()\n", + "FlashInitializer.initialize(m.fs.flash)" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2025-06-06 10:51:39 [INFO] idaes.init.fs.flash.control_volume.properties_in: Initialization Step 1 optimal - .\n", + "2025-06-06 10:51:39 [INFO] idaes.init.fs.flash.control_volume.properties_in: Initialization Step 2 optimal - .\n", + "2025-06-06 10:51:39 [INFO] idaes.init.fs.flash.control_volume.properties_in: Initialization Step 3 optimal - .\n", + "2025-06-06 10:51:39 [INFO] idaes.init.fs.flash.control_volume.properties_in: Initialization Step 4 optimal - .\n", + "2025-06-06 10:51:39 [INFO] idaes.init.fs.flash.control_volume.properties_in: Initialization Step 5 optimal - .\n", + "2025-06-06 10:51:40 [INFO] idaes.init.fs.flash.control_volume.properties_out: Initialization Step 1 optimal - .\n", + "2025-06-06 10:51:40 [INFO] idaes.init.fs.flash.control_volume.properties_out: Initialization Step 2 optimal - .\n", + "2025-06-06 10:51:40 [INFO] idaes.init.fs.flash.control_volume.properties_out: Initialization Step 3 optimal - .\n", + "2025-06-06 10:51:40 [INFO] idaes.init.fs.flash.control_volume.properties_out: Initialization Step 4 optimal - .\n", + "2025-06-06 10:51:40 [INFO] idaes.init.fs.flash.control_volume.properties_out: Initialization Step 5 optimal - .\n" + ] + }, + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 33, + "metadata": {}, + "output_type": "execute_result" + } + ], + "execution_count": 33 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "Another option for initializing is utilizing the default initializer that is attached to the unit model. Each unit model as a default initializer that is hypothetically the most compatible. It can be called with `m.fs.flash.initialize()`. While this is an option, it is generally preferred to import an initializer object and initialize the model with that to ensure more control over the initialization." + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": [ + "## 6 Solving the Model\n", + "\n", + "Now that the model has been defined and initialized, we can solve the model.\n", + "\n" + ] }, { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = 23285.714285714283\n" - ] + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T17:04:32.439966Z", + "start_time": "2025-06-06T17:04:32.396984Z" + }, + "tags": [ + "solution" + ] + }, + "cell_type": "code", + "source": [ + "# Todo: create the ipopt solver\n", + "solver = SolverFactory(\"ipopt\")\n", + "\n", + "# Todo: solve the model\n", + "status = solver.solve(m, tee=True)" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "Ipopt 3.13.2: \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...: 135\n", + "Number of nonzeros in inequality constraint Jacobian.: 0\n", + "Number of nonzeros in Lagrangian Hessian.............: 72\n", + "\n", + "Total number of variables............................: 41\n", + " variables with only lower bounds: 3\n", + " variables with lower and upper bounds: 10\n", + " variables with only upper bounds: 0\n", + "Total number of equality constraints.................: 41\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 6.22e-05 1.00e+00 -1.0 0.00e+00 - 0.00e+00 0.00e+00 0\n", + " 1 0.0000000e+00 1.46e-11 1.00e-02 -1.0 6.22e-05 - 9.90e-01 1.00e+00h 1\n", + "\n", + "Number of Iterations....: 1\n", + "\n", + " (scaled) (unscaled)\n", + "Objective...............: 0.0000000000000000e+00 0.0000000000000000e+00\n", + "Dual infeasibility......: 0.0000000000000000e+00 0.0000000000000000e+00\n", + "Constraint violation....: 2.0417014662014577e-12 1.4551915228366852e-11\n", + "Complementarity.........: 0.0000000000000000e+00 0.0000000000000000e+00\n", + "Overall NLP error.......: 2.0417014662014577e-12 1.4551915228366852e-11\n", + "\n", + "\n", + "Number of objective function evaluations = 2\n", + "Number of objective gradient evaluations = 2\n", + "Number of equality constraint evaluations = 2\n", + "Number of inequality constraint evaluations = 0\n", + "Number of equality constraint Jacobian evaluations = 2\n", + "Number of inequality constraint Jacobian evaluations = 0\n", + "Number of Lagrangian Hessian evaluations = 1\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" + ] + } + ], + "execution_count": 37 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": [ + "## 7 Viewing the Results\n", + "\n", + "Once a model is solved, the values returned by the solver are loaded into the model object itself. We can access the value of any variable in the model with the `value` function. For example:\n", + "```python\n", + "print('Vap. Outlet Temperature = ', value(m.fs.flash.vap_outlet.temperature[0]))\n", + "```\n", + "\n", + "You can also find more information about a variable or an entire port using the `display` method from Pyomo:\n", + "```python\n", + "m.fs.flash.vap_outlet.temperature.display()\n", + "m.fs.flash.vap_outlet.display()\n", + "```\n", + "\n", + "
\n", + "Inline Exercise:\n", + "Execute the cells below to show the current value of the flash vapor outlet pressure. This cell also shows use of the display function to see the values of the variables in the vap_outlet and the liq_outlet.\n", + "
" + ] }, { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = 24142.857142857145\n" - ] + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T17:04:35.556815Z", + "start_time": "2025-06-06T17:04:35.551070Z" + } + }, + "cell_type": "code", + "source": [ + "# Print the pressure of the flash vapor outlet\n", + "print(\"Pressure =\", value(m.fs.flash.vap_outlet.pressure[0]))\n", + "\n", + "print()\n", + "print(\"Output from display:\")\n", + "# Call display on vap_outlet and liq_outlet of the flash\n", + "m.fs.flash.vap_outlet.display()\n", + "m.fs.flash.liq_outlet.display()" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Pressure = 101325.0\n", + "\n", + "Output from display:\n", + "vap_outlet : Size=1\n", + " Key : Name : Value\n", + " None : flow_mol : {0.0: 0.3961181748774193}\n", + " : mole_frac_comp : {(0.0, 'benzene'): 0.633976648508129, (0.0, 'toluene'): 0.366023351491871}\n", + " : pressure : {0.0: 101325.0}\n", + " : temperature : {0.0: 368.0}\n", + "liq_outlet : Size=1\n", + " Key : Name : Value\n", + " None : flow_mol : {0.0: 0.6038818251225807}\n", + " : mole_frac_comp : {(0.0, 'benzene'): 0.41211759772293044, (0.0, 'toluene'): 0.5878824022770694}\n", + " : pressure : {0.0: 101325.0}\n", + " : temperature : {0.0: 368.0}\n" + ] + } + ], + "execution_count": 39 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": [ + "The output from `display` is quite exhaustive and not really intended to provide quick summary information. Because Pyomo is built on Python, there are opportunities to format the output any way we like. Most IDAES models have a `report` method which provides a summary of the results for the model.\n", + "\n", + "
\n", + "Inline Exercise:\n", + "Execute the cell below which uses the function above to print a summary of the key variables in the flash model, including the inlet, the vapor, and the liquid ports.\n", + "
" + ] }, { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n", - "Simulating with Q = 25000.0\n" - ] + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T17:04:38.727731Z", + "start_time": "2025-06-06T17:04:38.712131Z" + } + }, + "cell_type": "code", + "source": "m.fs.flash.report()", + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "====================================================================================\n", + "Unit : fs.flash Time: 0.0\n", + "------------------------------------------------------------------------------------\n", + " Unit Performance\n", + "\n", + " Variables: \n", + "\n", + " Key : Value : Units : Fixed : Bounds\n", + " Heat Duty : 0.0000 : watt : True : (None, None)\n", + " Pressure Change : 0.0000 : pascal : True : (None, None)\n", + "\n", + "------------------------------------------------------------------------------------\n", + " Stream Table\n", + " Units Inlet Vapor Outlet Liquid Outlet\n", + " flow_mol mole / second 1.0000 0.39612 0.60388 \n", + " mole_frac_comp benzene dimensionless 0.50000 0.63398 0.41212 \n", + " mole_frac_comp toluene dimensionless 0.50000 0.36602 0.58788 \n", + " temperature kelvin 368.00 368.00 368.00 \n", + " pressure pascal 1.0132e+05 1.0132e+05 1.0132e+05 \n", + "====================================================================================\n" + ] + } + ], + "execution_count": 40 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": [ + "## Exercise: Studying Purity as a Function of Heat Duty\n", + "\n", + "Since the entire modeling framework is built upon Python, it includes a complete programming environment for whatever analysis we may want to perform. In this next exercise, we will make use of what we learned in this and the previous module to generate a figure showing some output variables as a function of the heat duty in the flash tank.\n", + "\n", + "First, let's import the matplotlib package for plotting as we did in the previous module.\n", + "
\n", + "Inline Exercise:\n", + "Execute the cell below to import matplotlib appropriately.\n", + "
" + ] }, { - "name": "stdout", - "output_type": "stream", - "text": [ - "... solve successful.\n" - ] + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T17:04:43.426680Z", + "start_time": "2025-06-06T17:04:43.423025Z" + } + }, + "cell_type": "code", + "source": "import matplotlib.pyplot as plt", + "outputs": [], + "execution_count": 42 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": [ + "Exercise specifications:\n", + "* Generate a figure showing the flash tank heat duty (`m.fs.flash.heat_duty[0]`) vs. the vapor flowrate (`m.fs.flash.vap_outlet.flow_mol[0]`)\n", + "* Specify the heat duty from -17000 to 25000 over 50 steps\n", + "\n" + ] }, { - "data": { - "image/png": "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", - "text/plain": [ - "
" + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T17:04:48.800601Z", + "start_time": "2025-06-06T17:04:46.438264Z" + }, + "tags": [ + "solution" + ] + }, + "cell_type": "code", + "source": [ + "# import the solve_successful checking function from workshop tools\n", + "from idaes_examples.mod.tut.workshoptools import solve_successful\n", + "\n", + "# Todo: import numpy\n", + "import numpy as np\n", + "\n", + "# create the empty lists to store the results that will be plotted\n", + "Q = []\n", + "V = []\n", + "\n", + "# re-initialize model\n", + "FlashInitializer.initialize(m.fs.flash)\n", + "\n", + "# Todo: Write the for loop specification using numpy's linspace\n", + "for duty in np.linspace(-17000, 25000, 50):\n", + " # fix the heat duty\n", + " m.fs.flash.heat_duty.fix(duty)\n", + "\n", + " # append the value of the duty to the Q list\n", + " Q.append(duty)\n", + "\n", + " # print the current simulation\n", + " print(\"Simulating with Q = \", value(m.fs.flash.heat_duty[0]))\n", + "\n", + " # Solve the model\n", + " status = solver.solve(m)\n", + "\n", + " # append the value for vapor fraction if the solve was successful\n", + " if solve_successful(status):\n", + " V.append(value(m.fs.flash.vap_outlet.flow_mol[0]))\n", + " print(\"... solve successful.\")\n", + " else:\n", + " V.append(0.0)\n", + " print(\"... solve failed.\")\n", + "\n", + "# Create and show the figure\n", + "plt.figure(\"Vapor Fraction\")\n", + "plt.plot(Q, V)\n", + "plt.grid()\n", + "plt.xlabel(\"Heat Duty [J]\")\n", + "plt.ylabel(\"Vapor Fraction [-]\")\n", + "plt.show()" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2025-06-06 11:04:46 [INFO] idaes.init.fs.flash.control_volume.properties_in: Initialization Step 1 optimal - .\n", + "2025-06-06 11:04:46 [INFO] idaes.init.fs.flash.control_volume.properties_in: Initialization Step 2 optimal - .\n", + "2025-06-06 11:04:46 [INFO] idaes.init.fs.flash.control_volume.properties_in: Initialization Step 3 optimal - .\n", + "2025-06-06 11:04:46 [INFO] idaes.init.fs.flash.control_volume.properties_in: Initialization Step 4 optimal - .\n", + "2025-06-06 11:04:46 [INFO] idaes.init.fs.flash.control_volume.properties_in: Initialization Step 5 optimal - .\n", + "2025-06-06 11:04:46 [INFO] idaes.init.fs.flash.control_volume.properties_out: Initialization Step 1 optimal - .\n", + "2025-06-06 11:04:46 [INFO] idaes.init.fs.flash.control_volume.properties_out: Initialization Step 2 optimal - .\n", + "2025-06-06 11:04:46 [INFO] idaes.init.fs.flash.control_volume.properties_out: Initialization Step 3 optimal - .\n", + "2025-06-06 11:04:46 [INFO] idaes.init.fs.flash.control_volume.properties_out: Initialization Step 4 optimal - .\n", + "2025-06-06 11:04:46 [INFO] idaes.init.fs.flash.control_volume.properties_out: Initialization Step 5 optimal - .\n", + "Simulating with Q = -17000.0\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -16142.857142857143\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -15285.714285714286\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -14428.571428571428\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -13571.428571428572\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -12714.285714285714\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -11857.142857142857\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -11000.0\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -10142.857142857143\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -9285.714285714286\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -8428.57142857143\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -7571.4285714285725\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -6714.285714285714\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -5857.142857142857\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -5000.0\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -4142.857142857143\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -3285.7142857142862\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -2428.5714285714294\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -1571.4285714285725\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -714.2857142857156\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 142.8571428571413\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 1000.0\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 1857.142857142855\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 2714.2857142857138\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 3571.4285714285725\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 4428.5714285714275\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 5285.714285714286\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 6142.857142857141\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 7000.0\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 7857.142857142855\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 8714.285714285714\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 9571.428571428569\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 10428.571428571428\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 11285.714285714286\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 12142.857142857141\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 13000.0\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 13857.142857142855\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 14714.285714285714\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 15571.428571428569\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 16428.571428571428\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 17285.714285714283\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 18142.857142857145\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 19000.0\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 19857.142857142855\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 20714.28571428571\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 21571.428571428572\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 22428.571428571428\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 23285.714285714283\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 24142.857142857145\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 25000.0\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n" + ] + }, + { + "data": { + "text/plain": [ + "
" + ], + "image/png": "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" + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "execution_count": 43 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T17:05:02.101331Z", + "start_time": "2025-06-06T17:05:00.157447Z" + }, + "tags": [ + "solution" + ] + }, + "cell_type": "code", + "source": [ + "# Todo: generate a figure of heat duty vs. mole fraction of Benzene in the vapor\n", + "Q = []\n", + "V = []\n", + "\n", + "for duty in np.linspace(-17000, 25000, 50):\n", + " # fix the heat duty\n", + " m.fs.flash.heat_duty.fix(duty)\n", + "\n", + " # append the value of the duty to the Q list\n", + " Q.append(duty)\n", + "\n", + " # print the current simulation\n", + " print(\"Simulating with Q = \", value(m.fs.flash.heat_duty[0]))\n", + "\n", + " # solve the model\n", + " status = solver.solve(m)\n", + "\n", + " # append the value for vapor fraction if the solve was successful\n", + " if solve_successful(status):\n", + " V.append(value(m.fs.flash.vap_outlet.mole_frac_comp[0, \"benzene\"]))\n", + " print(\"... solve successful.\")\n", + " else:\n", + " V.append(0.0)\n", + " print(\"... solve failed.\")\n", + "\n", + "plt.figure(\"Purity\")\n", + "plt.plot(Q, V)\n", + "plt.grid()\n", + "plt.xlabel(\"Heat Duty [J]\")\n", + "plt.ylabel(\"Vapor Benzene Mole Fraction [-]\")\n", + "plt.show()" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Simulating with Q = -17000.0\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -16142.857142857143\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -15285.714285714286\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -14428.571428571428\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -13571.428571428572\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -12714.285714285714\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -11857.142857142857\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -11000.0\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -10142.857142857143\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -9285.714285714286\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -8428.57142857143\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -7571.4285714285725\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -6714.285714285714\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -5857.142857142857\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -5000.0\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -4142.857142857143\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -3285.7142857142862\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -2428.5714285714294\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -1571.4285714285725\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -714.2857142857156\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 142.8571428571413\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 1000.0\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 1857.142857142855\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 2714.2857142857138\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 3571.4285714285725\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 4428.5714285714275\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 5285.714285714286\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 6142.857142857141\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 7000.0\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 7857.142857142855\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 8714.285714285714\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 9571.428571428569\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 10428.571428571428\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 11285.714285714286\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 12142.857142857141\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 13000.0\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 13857.142857142855\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 14714.285714285714\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 15571.428571428569\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 16428.571428571428\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 17285.714285714283\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 18142.857142857145\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 19000.0\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 19857.142857142855\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 20714.28571428571\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 21571.428571428572\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 22428.571428571428\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 23285.714285714283\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 24142.857142857145\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 25000.0\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n" + ] + }, + { + "data": { + "text/plain": [ + "
" + ], + "image/png": "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" + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "execution_count": 44 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": [ + "Recall that the IDAES framework is an equation-oriented modeling environment. This means that we can specify \"design\" problems natively. That is, there is no need to have our specifications on the inlet alone. We can put specifications on the outlet as long as we retain a well-posed, square system of equations.\n", + "\n", + "For example, we can remove the specification on heat duty and instead specify that we want the mole fraction of Benzene in the vapor outlet to be equal to 0.6. The mole fraction is not a native variable in the property block, so we cannot use \"fix\". We can, however, add a constraint to the model.\n", + "\n", + "Note that we have been executing a number of solves on the problem, and may not be sure of the current state. To help convergence, therefore, we will first call initialize, then add the new constraint and solve the problem. Note that the reference for the mole fraction of Benzene in the vapor outlet is `m.fs.flash.vap_outlet.mole_frac_comp[0, \"benzene\"]`.\n", + "\n" ] - }, - "metadata": { - "filenames": { - "image/png": "C:\\Users\\dkgun\\src\\dangunter\\examples\\idaes_examples\\notebooks\\_build\\jupyter_execute\\docs\\tut\\core\\flash_unit_doc_35_51.png" - } - }, - "output_type": "display_data" - } - ], - "source": [ - "# Todo: generate a figure of heat duty vs. mole fraction of Benzene in the vapor\n", - "Q = []\n", - "V = []\n", - "\n", - "for duty in np.linspace(-17000, 25000, 50):\n", - " # fix the heat duty\n", - " m.fs.flash.heat_duty.fix(duty)\n", - "\n", - " # append the value of the duty to the Q list\n", - " Q.append(duty)\n", - "\n", - " # print the current simulation\n", - " print(\"Simulating with Q = \", value(m.fs.flash.heat_duty[0]))\n", - "\n", - " # solve the model\n", - " status = solver.solve(m)\n", - "\n", - " # append the value for vapor fraction if the solve was successful\n", - " if solve_successful(status):\n", - " V.append(value(m.fs.flash.vap_outlet.mole_frac_comp[0, \"benzene\"]))\n", - " print(\"... solve successful.\")\n", - " else:\n", - " V.append(0.0)\n", - " print(\"... solve failed.\")\n", - "\n", - "plt.figure(\"Purity\")\n", - "plt.plot(Q, V)\n", - "plt.grid()\n", - "plt.xlabel(\"Heat Duty [J]\")\n", - "plt.ylabel(\"Vapor Benzene Mole Fraction [-]\")\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Recall that the IDAES framework is an equation-oriented modeling environment. This means that we can specify \"design\" problems natively. That is, there is no need to have our specifications on the inlet alone. We can put specifications on the outlet as long as we retain a well-posed, square system of equations.\n", - "\n", - "For example, we can remove the specification on heat duty and instead specify that we want the mole fraction of Benzene in the vapor outlet to be equal to 0.6. The mole fraction is not a native variable in the property block, so we cannot use \"fix\". We can, however, add a constraint to the model.\n", - "\n", - "Note that we have been executing a number of solves on the problem, and may not be sure of the current state. To help convergence, therefore, we will first call initialize, then add the new constraint and solve the problem. Note that the reference for the mole fraction of Benzene in the vapor outlet is `m.fs.flash.vap_outlet.mole_frac_comp[0, \"benzene\"]`.\n", - "\n", - "
\n", - "Inline Exercise:\n", - "Fill in the missing code below and add a constraint on the mole fraction of Benzene (to a value of 0.6) to find the required heat duty.\n", - "
\n" - ] - }, - { - "cell_type": "code", - "execution_count": 20, - "metadata": { - "tags": [ - "solution" - ] - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Ipopt 3.13.2: \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...: 137\n", - "Number of nonzeros in inequality constraint Jacobian.: 0\n", - "Number of nonzeros in Lagrangian Hessian.............: 72\n", - "\n", - "Total number of variables............................: 42\n", - " variables with only lower bounds: 3\n", - " variables with lower and upper bounds: 10\n", - " variables with only upper bounds: 0\n", - "Total number of equality constraints.................: 42\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 3.40e-02 1.00e+00 -1.0 0.00e+00 - 0.00e+00 0.00e+00 0\n", - " 1 0.0000000e+00 1.64e+02 7.01e-02 -1.0 5.15e+03 - 9.87e-01 1.00e+00h 1\n", - " 2 0.0000000e+00 9.59e-02 2.03e-03 -1.0 7.07e+01 - 9.90e-01 1.00e+00h 1\n", - " 3 0.0000000e+00 6.95e-08 2.50e-06 -1.0 4.13e-01 - 9.98e-01 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....: 8.9144743362344083e-11 6.9545421865768731e-08\n", - "Complementarity.........: 0.0000000000000000e+00 0.0000000000000000e+00\n", - "Overall NLP error.......: 8.9144743362344083e-11 6.9545421865768731e-08\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.001\n", - "Total CPU secs in NLP function evaluations = 0.000\n", - "\n", - "EXIT: Optimal Solution Found.\n", - "\b\b\b\b\b\b\b\b\b\b\b\b\b\b" - ] }, { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "====================================================================================\n", - "Unit : fs.flash Time: 0.0\n", - "------------------------------------------------------------------------------------\n", - " Unit Performance\n", - "\n", - " Variables: \n", - "\n", - " Key : Value : Units : Fixed : Bounds\n", - " Heat Duty : 5083.6 : watt : False : (None, None)\n", - " Pressure Change : 0.0000 : pascal : True : (None, None)\n", - "\n", - "------------------------------------------------------------------------------------\n", - " Stream Table\n", - " Units Inlet Vapor Outlet Liquid Outlet\n", - " flow_mol mole / second 1.0000 0.54833 0.45167 \n", - " mole_frac_comp benzene dimensionless 0.50000 0.60000 0.37860 \n", - " mole_frac_comp toluene dimensionless 0.50000 0.40000 0.62140 \n", - " temperature kelvin 368.00 369.07 369.07 \n", - " pressure pascal 1.0132e+05 1.0132e+05 1.0132e+05 \n", - "====================================================================================\n" - ] + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T17:05:05.768445Z", + "start_time": "2025-06-06T17:05:05.378930Z" + }, + "tags": [ + "solution" + ] + }, + "cell_type": "code", + "source": [ + "# re-initialize the model - this may or may not be required depending on current state but safe to initialize\n", + "m.fs.flash.heat_duty.fix(0)\n", + "m.fs.flash.initialize(outlvl=idaeslog.WARNING)\n", + "\n", + "# Unfix the heat_duty variable\n", + "m.fs.flash.heat_duty.unfix()\n", + "\n", + "# Todo: Add a new constraint (benzene mole fraction to 0.6)\n", + "m.benz_purity_con = Constraint(\n", + " expr=m.fs.flash.vap_outlet.mole_frac_comp[0, \"benzene\"] == 0.6\n", + ")\n", + "\n", + "# solve the problem\n", + "status = solver.solve(m, tee=True)\n", + "\n", + "# Check stream condition\n", + "m.fs.flash.report()" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "WARNING: model contains export suffix 'scaling_factor' that contains 6\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "Ipopt 3.13.2: \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...: 137\n", + "Number of nonzeros in inequality constraint Jacobian.: 0\n", + "Number of nonzeros in Lagrangian Hessian.............: 72\n", + "\n", + "Total number of variables............................: 42\n", + " variables with only lower bounds: 3\n", + " variables with lower and upper bounds: 10\n", + " variables with only upper bounds: 0\n", + "Total number of equality constraints.................: 42\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 3.40e-02 1.00e+00 -1.0 0.00e+00 - 0.00e+00 0.00e+00 0\n", + " 1 0.0000000e+00 1.64e+02 7.01e-02 -1.0 5.15e+03 - 9.87e-01 1.00e+00h 1\n", + " 2 0.0000000e+00 9.59e-02 2.03e-03 -1.0 7.07e+01 - 9.90e-01 1.00e+00h 1\n", + " 3 0.0000000e+00 6.96e-08 2.50e-06 -1.0 4.13e-01 - 9.98e-01 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....: 8.9151738215745240e-11 6.9550878833979368e-08\n", + "Complementarity.........: 0.0000000000000000e+00 0.0000000000000000e+00\n", + "Overall NLP error.......: 8.9151738215745240e-11 6.9550878833979368e-08\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.001\n", + "Total CPU secs in NLP function evaluations = 0.000\n", + "\n", + "EXIT: Optimal Solution Found.\n", + "\n", + "====================================================================================\n", + "Unit : fs.flash Time: 0.0\n", + "------------------------------------------------------------------------------------\n", + " Unit Performance\n", + "\n", + " Variables: \n", + "\n", + " Key : Value : Units : Fixed : Bounds\n", + " Heat Duty : 5083.6 : watt : False : (None, None)\n", + " Pressure Change : 0.0000 : pascal : True : (None, None)\n", + "\n", + "------------------------------------------------------------------------------------\n", + " Stream Table\n", + " Units Inlet Vapor Outlet Liquid Outlet\n", + " flow_mol mole / second 1.0000 0.54833 0.45167 \n", + " mole_frac_comp benzene dimensionless 0.50000 0.60000 0.37860 \n", + " mole_frac_comp toluene dimensionless 0.50000 0.40000 0.62140 \n", + " temperature kelvin 368.00 369.07 369.07 \n", + " pressure pascal 1.0132e+05 1.0132e+05 1.0132e+05 \n", + "====================================================================================\n" + ] + } + ], + "execution_count": 46 + } + ], + "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.9.12" } - ], - "source": [ - "# re-initialize the model - this may or may not be required depending on current state but safe to initialize\n", - "m.fs.flash.heat_duty.fix(0)\n", - "m.fs.flash.initialize(outlvl=idaeslog.WARNING)\n", - "\n", - "# Unfix the heat_duty variable\n", - "m.fs.flash.heat_duty.unfix()\n", - "\n", - "# Todo: Add a new constraint (benzene mole fraction to 0.6)\n", - "m.benz_purity_con = Constraint(\n", - " expr=m.fs.flash.vap_outlet.mole_frac_comp[0, \"benzene\"] == 0.6\n", - ")\n", - "\n", - "# solve the problem\n", - "status = solver.solve(m, tee=True)\n", - "\n", - "# Check stream condition\n", - "m.fs.flash.report()" - ] - } - ], - "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.11.5" - } - }, - "nbformat": 4, - "nbformat_minor": 3 -} + "nbformat": 4, + "nbformat_minor": 3 +} \ No newline at end of file diff --git a/idaes_examples/notebooks/docs/tut/core/flash_unit_exercise.ipynb b/idaes_examples/notebooks/docs/tut/core/flash_unit_exercise.ipynb index 19e3b691..7c93163d 100644 --- a/idaes_examples/notebooks/docs/tut/core/flash_unit_exercise.ipynb +++ b/idaes_examples/notebooks/docs/tut/core/flash_unit_exercise.ipynb @@ -2,14 +2,16 @@ "cells": [ { "cell_type": "code", - "execution_count": null, "metadata": { "tags": [ "header", "hide-cell" - ] + ], + "ExecuteTime": { + "end_time": "2025-06-06T16:45:45.923673Z", + "start_time": "2025-06-06T16:45:45.919855Z" + } }, - "outputs": [], "source": [ "###############################################################################\n", "# The Institute for the Design of Advanced Energy Systems Integrated Platform\n", @@ -23,37 +25,46 @@ "# All rights reserved. Please see the files COPYRIGHT.md and LICENSE.md\n", "# for full copyright and license information.\n", "###############################################################################" - ] + ], + "outputs": [], + "execution_count": 1 }, { - "cell_type": "markdown", "metadata": {}, + "cell_type": "markdown", "source": [ - "# Flash Unit Model\n", + "# Flash Unit Model Tutorial\n", "\n", - "Author: Jaffer Ghouse \n", - "Maintainer: Andrew Lee \n", - "Updated: 2023-06-01 \n", + "Author: Jaffer Ghouse
\n", + "Maintainer: Tanner Polley
\n", + "Updated: 2025-06-03\n", "\n", - "In this module, we will familiarize ourselves with the IDAES framework by creating and working with a flowsheet that contains a single flash tank. The flash tank will be used to perform separation of Benzene and Toluene. The inlet specifications for this flash tank are:\n", + "In this module, we will familiarize ourselves with the IDAES framework by creating and working with a flowsheet that contains a single flash tank. The flash tank will be used to perform separation of Benzene and Toluene.\n", "\n", - "Inlet Specifications:\n", - "* Mole fraction (Benzene) = 0.5\n", - "* Mole fraction (Toluene) = 0.5\n", - "* Pressure = 101325 Pa\n", - "* Temperature = 368 K\n", + "The general workflow of setting up an IDAES flowsheet is the following:\n", + "\n", + "- 1 Importing Modules\n", + "- 2 Building a Model\n", + "- 3 Scaling the Model\n", + "- 4 Specifying the Model\n", + "- 5 Initializing the Model\n", + "- 6 Solving the Model\n", + "- 7 Analyzing and Visualizing the Results\n", "\n", - "We will complete the following tasks:\n", - "* Create the model and the IDAES Flowsheet object\n", - "* Import the appropriate property packages\n", - "* Create the flash unit and set the operating conditions\n", - "* Initialize the model and simulate the system\n", - "* Demonstrate analyses on this model through some examples and exercises\n", + "We will complete each of these steps as well as demonstrate analyses on this model through some examples and exercises\n", "\n", "## Key links to documentation\n", "* Main IDAES online documentation page: https://idaes-pse.readthedocs.io/en/stable/\n", - "\n", - "## Create the Model and the IDAES Flowsheet\n", + "* General Workflow: https://idaes-pse.readthedocs.io/en/stable/how_to_guides/workflow/general.html\n", + "* Flash Unit Model Documentation: https://idaes-pse.readthedocs.io/en/stable/reference_guides/model_libraries/generic/unit_models/flash.html\n", + "\n" + ] + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": [ + "## 1 Import Modules\n", "\n", "In the next cell, we will perform the necessary imports to get us started. From `pyomo.environ` (a standard import for the Pyomo package), we are importing `ConcreteModel` (to create the Pyomo model that will contain the IDAES flowsheet) and `SolverFactory` (to create the object we will use to solve the equations). We will also import `Constraint` as we will be adding a constraint to the model later in the module. Lastly, we also import `value` from Pyomo. This is a function that can be used to return the current numerical value for variables and parameters in the model. These are all part of Pyomo.\n", "\n", @@ -66,23 +77,31 @@ ] }, { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:49.094293Z", + "start_time": "2025-06-06T16:45:45.938076Z" + } + }, "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], "source": [ "from pyomo.environ import ConcreteModel, SolverFactory, Constraint, value\n", "from idaes.core import FlowsheetBlock\n", "\n", "# Import idaes logger to set output levels\n", - "import idaes.logger as idaeslog" - ] + "import idaes.logger as idaeslog\n", + "%matplotlib inline" + ], + "outputs": [], + "execution_count": 2 }, { - "cell_type": "markdown", "metadata": {}, + "cell_type": "markdown", "source": [ - "In the next cell, we will create the `ConcreteModel` and the `FlowsheetBlock`, and attach the flowsheet block to the Pyomo model.\n", + "## 2 Create the Model and IDAES Flowsheet\n", + "\n", + "In the next cell, we will create the `ConcreteModel` object often named `m` (which comes from Pyomo) and then connect the `FlowsheetBlock` (which comes from IDAES) to `m`. We ensure `dynamic=False` since this is a steady-state problem. This creates our overall model and adds the flowsheet capabilities that IDAES provides to the Pyomo model.\n", "\n", "
\n", "Inline Exercise:\n", @@ -91,20 +110,29 @@ ] }, { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:49.521103Z", + "start_time": "2025-06-06T16:45:49.517229Z" + } + }, "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], "source": [ "m = ConcreteModel()\n", "m.fs = FlowsheetBlock(dynamic=False)" - ] + ], + "outputs": [], + "execution_count": 3 }, { + "metadata": { + "tags": [ + "exercise" + ] + }, "cell_type": "markdown", - "metadata": {}, "source": [ - "At this point, we have a single Pyomo model that contains an (almost) empty flowsheet block.\n", + "At this point, we have a single Pyomo model that contains an (almost) empty flowsheet block. Lets use the `m.pprint()` to investigate the current contents of the model.\n", "\n", "
\n", "Inline Exercise:\n", @@ -113,27 +141,29 @@ ] }, { - "cell_type": "code", - "execution_count": 3, "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:49.531374Z", + "start_time": "2025-06-06T16:45:49.527521Z" + }, "tags": [ "exercise" ] }, + "cell_type": "code", + "source": "# Todo: call pprint on the model", "outputs": [], - "source": [ - "# Todo: call pprint on the model" - ] + "execution_count": 4 }, { - "cell_type": "markdown", "metadata": {}, + "cell_type": "markdown", "source": [ - "## Define Properties\n", + "### 2.1 Define Properties\n", "\n", "We need to define the property package for our flowsheet. In this example, we will be using the ideal property package that is available as part of the IDAES framework. This property package supports ideal gas - ideal liquid, ideal gas - NRTL, and ideal gas - Wilson models for VLE. More details on this property package can be found at: https://idaes-pse.readthedocs.io/en/stable/reference_guides/model_libraries/generic/property_models/activity_coefficient.html\n", "\n", - "IDAES also supports creation of your own property packages that allow for specification of the fluid using any set of valid state variables (e.g., component molar flows vs overall flow and mole fractions). This flexibility is designed to support advanced modeling needs that may rely on specific formulations. To learn about creating your own property package, please consult the online documentation at: https://idaes-pse.readthedocs.io/en/stable/explanations/components/property_package/index.html and look at examples within IDAES\n", + "IDAES also supports creation of your own property packages that will be shown in a later module.\n", "\n", "For this workshop, we will import the BTX_activity_coeff_VLE property parameter block to be used in the flowsheet. This properties block will be passed to our unit model to define the appropriate state variables and equations for performing thermodynamic calculations.\n", "\n", @@ -144,34 +174,44 @@ ] }, { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:49.649106Z", + "start_time": "2025-06-06T16:45:49.626357Z" + } + }, "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [], "source": [ "from idaes.models.properties.activity_coeff_models.BTX_activity_coeff_VLE import (\n", " BTXParameterBlock,\n", ")" - ] + ], + "outputs": [], + "execution_count": 6 }, { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:49.669359Z", + "start_time": "2025-06-06T16:45:49.657658Z" + } + }, "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [], "source": [ "m.fs.properties = BTXParameterBlock(\n", " valid_phase=(\"Liq\", \"Vap\"), activity_coeff_model=\"Ideal\", state_vars=\"FTPz\"\n", ")" - ] + ], + "outputs": [], + "execution_count": 7 }, { - "cell_type": "markdown", "metadata": {}, + "cell_type": "markdown", "source": [ - "## Adding Flash Unit\n", + "### 2.2 Adding Flash Unit\n", "\n", - "Now that we have the flowsheet and the properties defined, we can create the flash unit and add it to the flowsheet. \n", + "Now that we have the flowsheet and the properties defined, we can create the flash unit and add it to the flowsheet.\n", "\n", "**The Unit Model Library within IDAES includes a large set of common unit operations (see the online documentation for details: https://idaes-pse.readthedocs.io/en/stable/reference_guides/model_libraries/generic/unit_models/index.html**\n", "\n", @@ -186,7 +226,7 @@ "* Pressure changing equipment (compressors, expanders, pumps)\n", "* Feed and Product (source / sink) components\n", "\n", - "In this module, we will import the `Flash` unit model from `idaes.models.unit_models` and create an instance of the flash unit, attaching it to the flowsheet. Each IDAES unit model has several configurable options to customize the model behavior, but also includes defaults for these options. In this example, we will specify that the property package to be used with the Flash is the one we created earlier.\n", + "In this module, we will import the `Flash` unit model from `idaes.models.unit_models` and create an instance of the flash unit, attaching it to the flowsheet. Each IDAES unit model has several configurable options to customize the model behavior, but also includes defaults for these options. In this example, we will specify that the property package to be used with the Flash unit model is the one we created earlier by setting `property_package=m.fs.properties` within the `Flash` method.\n", "\n", "
\n", "Inline Exercise:\n", @@ -195,40 +235,183 @@ ] }, { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:49.749283Z", + "start_time": "2025-06-06T16:45:49.678730Z" + } + }, "cell_type": "code", - "execution_count": 7, - "metadata": {}, + "source": "from idaes.models.unit_models import Flash", "outputs": [], - "source": [ - "from idaes.models.unit_models import Flash" - ] + "execution_count": 8 }, { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:49.772441Z", + "start_time": "2025-06-06T16:45:49.758167Z" + } + }, "cell_type": "code", - "execution_count": 8, + "source": "m.fs.flash = Flash(property_package=m.fs.properties)", + "outputs": [], + "execution_count": 9 + }, + { "metadata": {}, + "cell_type": "markdown", + "source": "At this point, we have created a flowsheet and a properties block. We have also created a flash unit and added it to the flowsheet. Under the hood, IDAES has created the required state variables and model equations. Everything is open. You can see these variables and equations by calling the Pyomo method `pprint` on the model, flowsheet, or flash tank objects. Note that this output is very exhaustive, and is not intended to provide any summary information about the model, but rather a complete picture of all of the variables and equations in the model." + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:49.783706Z", + "start_time": "2025-06-06T16:45:49.781688Z" + }, + "tags": [ + "noauto" + ] + }, + "cell_type": "code", + "source": "m.pprint()", "outputs": [], + "execution_count": 10 + }, + { + "metadata": {}, + "cell_type": "markdown", "source": [ - "m.fs.flash = Flash(property_package=m.fs.properties)" + "## 3 Scaling the Model\n", + "\n", + "Now that the model is built, with properties set and the unit model created and added to the flowsheet, the next step is to scale the model. Ensuring that a model is well scaled is important for increasing the efficiency and reliability of solvers, and users should consider model scaling as an integral part of the modeling process. IDAES provides a number of tool for assisting users with scaling their models, and details on these can be found at https://idaes-pse.readthedocs.io/en/stable/reference_guides/scaling/scaling.html#scaling-toolbox\n", + "\n", + "There are currently two primary methods in scaling the model: manual scaling of each relevant component, or utilizing the AutoScaler Class. The more careful and risk-free method of manually scaling each component is the recommended method for maximum control and assurance that the model will be well-scaled. This comes with the drawback of being more meticulous while the AutoScaler is much simpler to use since it scaled the whole model all at once, it is less precise by lacking direct control over the scaling factor for each component and relying on scaling factors to be estimated. Both methods will be shown below" ] }, { + "metadata": {}, "cell_type": "markdown", + "source": [ + "### 3.1 Manual Scaling\n", + "The `set_scaling_factor` function is imported from `idaes.core.scaling.util` and is called with and used on each relevant component that needs to be well scaled. The component is the first argument and its scaling factor is the second argument.\n", + "\n", + "
\n", + "Inline Exercise:\n", + "Execute the following two cells to import the `set_scaling_factor` and set the scaling factor for both temperature and pressure\n", + "
\n", + "\n", + "Both `temperature` and `pressure` can be found at `m.fs.flash.inlet`." + ] + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:49.795252Z", + "start_time": "2025-06-06T16:45:49.792075Z" + } + }, + "cell_type": "code", + "source": "from idaes.core.scaling.util import set_scaling_factor", + "outputs": [], + "execution_count": 11 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:49.809330Z", + "start_time": "2025-06-06T16:45:49.806174Z" + } + }, + "cell_type": "code", + "source": [ + "set_scaling_factor(m.fs.flash.inlet.temperature, 300)\n", + "set_scaling_factor(m.fs.flash.inlet.pressure, 1e6)" + ], + "outputs": [], + "execution_count": 12 + }, + { "metadata": {}, + "cell_type": "markdown", "source": [ - "At this point, we have created a flowsheet and a properties block. We have also created a flash unit and added it to the flowsheet. Under the hood, IDAES has created the required state variables and model equations. Everything is open. You can see these variables and equations by calling the Pyomo method `pprint` on the model, flowsheet, or flash tank objects. Note that this output is very exhaustive, and is not intended to provide any summary information about the model, but rather a complete picture of all of the variables and equations in the model." + "### 3.2 Scaling with AutoScaler\n", + "The `AutoScaler` class is imported from `idaes.core.scaling.autoscaling` and an instance of the class is created. This instance contains the method `scale_model` which is used to scale the whole model at once. This can be a useful option but is generally more risky than manually scaling the model components since it has less direct control and specification.\n", + "\n", + "
\n", + "Inline Exercise:\n", + "Execute the following two cells to import the `AutoScaler` class and create an autoscaler instance that scaled the whole model at once\n", + "
" ] }, { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:49.826276Z", + "start_time": "2025-06-06T16:45:49.823030Z" + } + }, + "cell_type": "code", + "source": "from idaes.core.scaling.autoscaling import AutoScaler", + "outputs": [], + "execution_count": 13 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:50.217776Z", + "start_time": "2025-06-06T16:45:49.836920Z" + } + }, + "cell_type": "code", + "source": [ + "autoscaler = AutoScaler()\n", + "autoscaler.scale_model(m)" + ], + "outputs": [], + "execution_count": 14 + }, + { + "metadata": {}, "cell_type": "markdown", + "source": "" + }, + { "metadata": {}, + "cell_type": "markdown", "source": [ - "## Set Operating Conditions\n", + "## 4 Set Operating Conditions\n", + "\n", + "Now that we have created our unit model and scaled it, we can specify the necessary operating conditions. The inlet specifications for this flash tank are:\n", + "\n", + "Inlet Specifications:\n", + "* Mole fraction (Benzene) = 0.5\n", + "* Mole fraction (Toluene) = 0.5\n", + "* Pressure = 101325 Pa\n", + "* Temperature = 368 K\n", "\n", - "Now that we have created our unit model, we can specify the necessary operating conditions. It is often very useful to determine the degrees of freedom before we specify any conditions.\n", + "\n" + ] + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": [ + "### 4.1 Degrees of Freedom\n", "\n", - "The `idaes.core.util.model_statistics` package has a function `degrees_of_freedom`. To see how to use this function, we can make use of the Python function `help(func)`. This function prints the appropriate documentation string for the function.\n", + "It is often very useful to first determine the degrees of freedom before we specify any conditions.\n", "\n", + "The `idaes.core.util.model_statistics` package has a function `degrees_of_freedom`. To see how to use this function, we can make use of the Python function `help(func)`. This function prints the appropriate documentation string for the function.\n" + ] + }, + { + "metadata": { + "tags": [ + "exercise" + ] + }, + "cell_type": "markdown", + "source": [ "
\n", "Inline Exercise:\n", "Import the degrees_of_freedom function and print the help for the function by calling the Python help function.\n", @@ -236,24 +419,32 @@ ] }, { - "cell_type": "code", - "execution_count": 9, "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:50.224457Z", + "start_time": "2025-06-06T16:45:50.221905Z" + }, "tags": [ "exercise" ] }, - "outputs": [], + "cell_type": "code", "source": [ "# Todo: import the degrees_of_freedom function from the idaes.core.util.model_statistics package\n", "\n", "\n", "# Todo: Call the python help on the degrees_of_freedom function" - ] + ], + "outputs": [], + "execution_count": 15 }, { + "metadata": { + "tags": [ + "exercise" + ] + }, "cell_type": "markdown", - "metadata": {}, "source": [ "
\n", "Inline Exercise:\n", @@ -262,22 +453,26 @@ ] }, { - "cell_type": "code", - "execution_count": 11, "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:50.271361Z", + "start_time": "2025-06-06T16:45:50.268763Z" + }, "tags": [ "exercise" ] }, + "cell_type": "code", + "source": "# Todo: print the degrees of freedom for your model", "outputs": [], - "source": [ - "# Todo: print the degrees of freedom for your model" - ] + "execution_count": 17 }, { - "cell_type": "markdown", "metadata": {}, + "cell_type": "markdown", "source": [ + "### 4.2 Specify Inlet Conditions\n", + "\n", "To satisfy our degrees of freedom, we will first specify the inlet conditions. We can specify these values through the `inlet` port of the flash unit.\n", "\n", "**To see the list of naming conventions for variables within the IDAES framework, consult the online documentation at: https://idaes-pse.readthedocs.io/en/stable/explanations/conventions.html#standard-naming-format**\n", @@ -314,7 +509,17 @@ "* inlet mole fraction (toluene) = 0.5 (`mole_frac_comp[0, \"toluene\"]`)\n", "* The heat duty on the flash set to 0 (`heat_duty`)\n", "* The pressure drop across the flash tank set to 0 (`deltaP`)\n", - "\n", + "\n" + ] + }, + { + "metadata": { + "tags": [ + "exercise" + ] + }, + "cell_type": "markdown", + "source": [ "
\n", "Inline Exercise:\n", "Write the code below to specify the inlet conditions and unit specifications described above\n", @@ -322,24 +527,40 @@ ] }, { - "cell_type": "code", - "execution_count": 14, "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:50.360852Z", + "start_time": "2025-06-06T16:45:50.357341Z" + }, "tags": [ "exercise" ] }, - "outputs": [], + "cell_type": "code", "source": [ "# Todo: Add inlet specifications given above\n", "\n", "\n", "# Todo: Add 2 flash unit specifications given above" + ], + "outputs": [], + "execution_count": 20 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": [ + "Now that all the inlets have been specified, we can check the degrees of freedom again to ensure the system is square and has a degree of freedom of 0\n", + "\n" ] }, { + "metadata": { + "tags": [ + "exercise" + ] + }, "cell_type": "markdown", - "metadata": {}, "source": [ "
\n", "Inline Exercise:\n", @@ -348,51 +569,79 @@ ] }, { - "cell_type": "code", - "execution_count": 16, "metadata": { "tags": [ "exercise" ] }, + "cell_type": "code", "outputs": [], - "source": [ - "# Todo: print the degrees of freedom for your model" - ] + "execution_count": 22, + "source": "# Todo: print the degrees of freedom for your model" }, { - "cell_type": "markdown", "metadata": {}, + "cell_type": "markdown", "source": [ - "## Initializing the Model\n", + "## 5 Initializing the Model\n", "\n", - "IDAES includes pre-written initialization routines for all unit models. You can call this initialize method on the units. In the next module, we will demonstrate the use of a sequential modular solve cycle to initialize flowsheets.\n", + "Now that all building steps are complete, the last step before solving the model is to initialize the model, or prepping the solve by giving it a good starting point. This is essentially giving the solver an initial guess for the iterative solver to reach convergence and is essential for both a fast and accurate solution. In IDAES, the current standard for initializing the model is by utilizing initializer instances. These initializer instances contain the initialize method that can be applied to any model type. For more information on initializing in IDAES, visit https://idaes-pse.readthedocs.io/en/stable/reference_guides/initialization/index.html.
\n", "\n", + "For this tutorial, we will import the initializer class `BlockTriangularizationInitializer` class from the `default_initializer` method from the flash unit model. This is often the simplest way to obtain a compatible initializer for each unit model, but you can also directly important any initializer needed from this source `idaes.core.initialization`. Each initializer instance contains the `initialize()` method that requires an argument to be initialized and in this case its the flash unit model.\n" + ] + }, + { + "metadata": { + "tags": [ + "exercise" + ] + }, + "cell_type": "markdown", + "source": [ "
\n", "Inline Exercise:\n", - "Call the initialize method on the flash unit to initialize the model.\n", + "Define the initializer instance, and initialize the flash unit model\n", "
" ] }, { - "cell_type": "code", - "execution_count": 19, "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:50.499945Z", + "start_time": "2025-06-06T16:45:50.497439Z" + }, "tags": [ "exercise" ] }, + "cell_type": "code", + "source": "# Todo: initialize the flash unit", "outputs": [], - "source": [ - "# Todo: initialize the flash unit" - ] + "execution_count": 25 }, { + "metadata": {}, "cell_type": "markdown", + "source": "Another option for initializing is utilizing the default initializer that is attached to the unit model. Each unit model as a default initializer that is hypothetically the most compatible. It can be called with `m.fs.flash.initialize()`. While this is an option, it is generally preferred to import an initializer object and initialize the model with that to ensure more control over the initialization." + }, + { "metadata": {}, + "cell_type": "markdown", "source": [ - "Now that the model has been defined and initialized, we can solve the model.\n", + "## 6 Solving the Model\n", "\n", + "Now that the model has been defined and initialized, we can solve the model.\n", + "\n" + ] + }, + { + "metadata": { + "tags": [ + "exercise" + ] + }, + "cell_type": "markdown", + "source": [ "
\n", "Inline Exercise:\n", "Using the notation described in the previous model, create an instance of the \"ipopt\" solver and use it to solve the model. Set the tee option to True to see the log output.\n", @@ -400,25 +649,29 @@ ] }, { - "cell_type": "code", - "execution_count": 21, "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T17:04:32.219172Z", + "start_time": "2025-06-06T17:04:32.216161Z" + }, "tags": [ "exercise" ] }, - "outputs": [], + "cell_type": "code", "source": [ "# Todo: create the ipopt solver\n", "\n", "# Todo: solve the model" - ] + ], + "outputs": [], + "execution_count": 36 }, { - "cell_type": "markdown", "metadata": {}, + "cell_type": "markdown", "source": [ - "## Viewing the Results\n", + "## 7 Viewing the Results\n", "\n", "Once a model is solved, the values returned by the solver are loaded into the model object itself. We can access the value of any variable in the model with the `value` function. For example:\n", "```python\n", @@ -438,10 +691,13 @@ ] }, { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T17:04:35.556815Z", + "start_time": "2025-06-06T17:04:35.551070Z" + } + }, "cell_type": "code", - "execution_count": 24, - "metadata": {}, - "outputs": [], "source": [ "# Print the pressure of the flash vapor outlet\n", "print(\"Pressure =\", value(m.fs.flash.vap_outlet.pressure[0]))\n", @@ -451,34 +707,89 @@ "# Call display on vap_outlet and liq_outlet of the flash\n", "m.fs.flash.vap_outlet.display()\n", "m.fs.flash.liq_outlet.display()" - ] + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Pressure = 101325.0\n", + "\n", + "Output from display:\n", + "vap_outlet : Size=1\n", + " Key : Name : Value\n", + " None : flow_mol : {0.0: 0.3961181748774193}\n", + " : mole_frac_comp : {(0.0, 'benzene'): 0.633976648508129, (0.0, 'toluene'): 0.366023351491871}\n", + " : pressure : {0.0: 101325.0}\n", + " : temperature : {0.0: 368.0}\n", + "liq_outlet : Size=1\n", + " Key : Name : Value\n", + " None : flow_mol : {0.0: 0.6038818251225807}\n", + " : mole_frac_comp : {(0.0, 'benzene'): 0.41211759772293044, (0.0, 'toluene'): 0.5878824022770694}\n", + " : pressure : {0.0: 101325.0}\n", + " : temperature : {0.0: 368.0}\n" + ] + } + ], + "execution_count": 39 }, { - "cell_type": "markdown", "metadata": {}, + "cell_type": "markdown", "source": [ "The output from `display` is quite exhaustive and not really intended to provide quick summary information. Because Pyomo is built on Python, there are opportunities to format the output any way we like. Most IDAES models have a `report` method which provides a summary of the results for the model.\n", "\n", "
\n", "Inline Exercise:\n", - "Execute the cell below which uses the function above to print a summary of the key variables in the flash model, including the inlet, the vapor, and the liquid ports. \n", + "Execute the cell below which uses the function above to print a summary of the key variables in the flash model, including the inlet, the vapor, and the liquid ports.\n", "
" ] }, { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T17:04:38.727731Z", + "start_time": "2025-06-06T17:04:38.712131Z" + } + }, "cell_type": "code", - "execution_count": 25, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.flash.report()" - ] + "source": "m.fs.flash.report()", + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "====================================================================================\n", + "Unit : fs.flash Time: 0.0\n", + "------------------------------------------------------------------------------------\n", + " Unit Performance\n", + "\n", + " Variables: \n", + "\n", + " Key : Value : Units : Fixed : Bounds\n", + " Heat Duty : 0.0000 : watt : True : (None, None)\n", + " Pressure Change : 0.0000 : pascal : True : (None, None)\n", + "\n", + "------------------------------------------------------------------------------------\n", + " Stream Table\n", + " Units Inlet Vapor Outlet Liquid Outlet\n", + " flow_mol mole / second 1.0000 0.39612 0.60388 \n", + " mole_frac_comp benzene dimensionless 0.50000 0.63398 0.41212 \n", + " mole_frac_comp toluene dimensionless 0.50000 0.36602 0.58788 \n", + " temperature kelvin 368.00 368.00 368.00 \n", + " pressure pascal 1.0132e+05 1.0132e+05 1.0132e+05 \n", + "====================================================================================\n" + ] + } + ], + "execution_count": 40 }, { - "cell_type": "markdown", "metadata": {}, + "cell_type": "markdown", "source": [ - "## Studying Purity as a Function of Heat Duty\n", + "## Exercise: Studying Purity as a Function of Heat Duty\n", "\n", "Since the entire modeling framework is built upon Python, it includes a complete programming environment for whatever analysis we may want to perform. In this next exercise, we will make use of what we learned in this and the previous module to generate a figure showing some output variables as a function of the heat duty in the flash tank.\n", "\n", @@ -490,22 +801,35 @@ ] }, { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T17:04:43.426680Z", + "start_time": "2025-06-06T17:04:43.423025Z" + } + }, "cell_type": "code", - "execution_count": 27, - "metadata": {}, + "source": "import matplotlib.pyplot as plt", "outputs": [], - "source": [ - "import matplotlib.pyplot as plt" - ] + "execution_count": 42 }, { - "cell_type": "markdown", "metadata": {}, + "cell_type": "markdown", "source": [ "Exercise specifications:\n", "* Generate a figure showing the flash tank heat duty (`m.fs.flash.heat_duty[0]`) vs. the vapor flowrate (`m.fs.flash.vap_outlet.flow_mol[0]`)\n", "* Specify the heat duty from -17000 to 25000 over 50 steps\n", - "\n", + "\n" + ] + }, + { + "metadata": { + "tags": [ + "exercise" + ] + }, + "cell_type": "markdown", + "source": [ "
\n", "Inline Exercise:\n", "Using what you have learned so far, fill in the missing code below to generate the figure specified above. (Hint: import numpy and use the linspace function from the previous module)\n", @@ -513,14 +837,12 @@ ] }, { - "cell_type": "code", - "execution_count": 29, "metadata": { "tags": [ "exercise" ] }, - "outputs": [], + "cell_type": "code", "source": [ "# import the solve_successful checking function from workshop tools\n", "from idaes_examples.mod.tut.workshoptools import solve_successful\n", @@ -533,22 +855,22 @@ "V = []\n", "\n", "# re-initialize model\n", - "m.fs.flash.initialize(outlvl=idaeslog.WARNING)\n", + "UnitModelInitializer.initialize(m.fs.flash)\n", "\n", "# Todo: Write the for loop specification using numpy's linspace\n", "\n", " # fix the heat duty\n", " m.fs.flash.heat_duty.fix(duty)\n", - " \n", + "\n", " # append the value of the duty to the Q list\n", " Q.append(duty)\n", - " \n", + "\n", " # print the current simulation\n", " print(\"Simulating with Q = \", value(m.fs.flash.heat_duty[0]))\n", "\n", " # Solve the model\n", " status = solver.solve(m)\n", - " \n", + "\n", " # append the value for vapor fraction if the solve was successful\n", " if solve_successful(status):\n", " V.append(value(m.fs.flash.vap_outlet.flow_mol[0]))\n", @@ -556,7 +878,7 @@ " else:\n", " V.append(0.0)\n", " print('... solve failed.')\n", - " \n", + "\n", "# Create and show the figure\n", "plt.figure(\"Vapor Fraction\")\n", "plt.plot(Q, V)\n", @@ -564,11 +886,17 @@ "plt.xlabel(\"Heat Duty [J]\")\n", "plt.ylabel(\"Vapor Fraction [-]\")\n", "plt.show()" - ] + ], + "outputs": [], + "execution_count": null }, { + "metadata": { + "tags": [ + "exercise" + ] + }, "cell_type": "markdown", - "metadata": {}, "source": [ "
\n", "Inline Exercise:\n", @@ -577,28 +905,36 @@ ] }, { - "cell_type": "code", - "execution_count": null, "metadata": { "tags": [ "exercise" ] }, + "cell_type": "code", + "source": "# Todo: generate a figure of heat duty vs. mole fraction of Benzene in the vapor", "outputs": [], - "source": [ - "# Todo: generate a figure of heat duty vs. mole fraction of Benzene in the vapor" - ] + "execution_count": null }, { - "cell_type": "markdown", "metadata": {}, + "cell_type": "markdown", "source": [ "Recall that the IDAES framework is an equation-oriented modeling environment. This means that we can specify \"design\" problems natively. That is, there is no need to have our specifications on the inlet alone. We can put specifications on the outlet as long as we retain a well-posed, square system of equations.\n", "\n", "For example, we can remove the specification on heat duty and instead specify that we want the mole fraction of Benzene in the vapor outlet to be equal to 0.6. The mole fraction is not a native variable in the property block, so we cannot use \"fix\". We can, however, add a constraint to the model.\n", "\n", "Note that we have been executing a number of solves on the problem, and may not be sure of the current state. To help convergence, therefore, we will first call initialize, then add the new constraint and solve the problem. Note that the reference for the mole fraction of Benzene in the vapor outlet is `m.fs.flash.vap_outlet.mole_frac_comp[0, \"benzene\"]`.\n", - "\n", + "\n" + ] + }, + { + "metadata": { + "tags": [ + "exercise" + ] + }, + "cell_type": "markdown", + "source": [ "
\n", "Inline Exercise:\n", "Fill in the missing code below and add a constraint on the mole fraction of Benzene (to a value of 0.6) to find the required heat duty.\n", @@ -606,14 +942,16 @@ ] }, { - "cell_type": "code", - "execution_count": null, "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T17:05:05.363302Z", + "start_time": "2025-06-06T17:05:04.960601Z" + }, "tags": [ "exercise" ] }, - "outputs": [], + "cell_type": "code", "source": [ "# re-initialize the model - this may or may not be required depending on current state but safe to initialize\n", "m.fs.flash.heat_duty.fix(0)\n", @@ -629,7 +967,106 @@ "\n", "# Check stream condition\n", "m.fs.flash.report()" - ] + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "WARNING: model contains export suffix 'scaling_factor' that contains 6\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "Ipopt 3.13.2: \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...: 136\n", + "Number of nonzeros in inequality constraint Jacobian.: 0\n", + "Number of nonzeros in Lagrangian Hessian.............: 72\n", + "\n", + "Total number of variables............................: 42\n", + " variables with only lower bounds: 3\n", + " variables with lower and upper bounds: 10\n", + " variables with only upper bounds: 0\n", + "Total number of equality constraints.................: 41\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 3.07e-08 1.01e-04 -1.0 0.00e+00 - 0.00e+00 0.00e+00 0\n", + " 1 0.0000000e+00 1.46e+00 2.42e-01 -1.0 4.86e+02 - 9.90e-01 1.00e+00f 1\n", + " 2 0.0000000e+00 5.55e+01 5.60e-03 -1.0 3.00e+03 - 9.90e-01 1.00e+00h 1\n", + " 3 0.0000000e+00 1.91e+00 4.24e-05 -1.0 5.68e+02 - 1.00e+00 1.00e+00h 1\n", + " 4 0.0000000e+00 1.53e-05 1.90e-06 -2.5 1.35e+00 - 1.00e+00 1.00e+00h 1\n", + " 5 0.0000000e+00 4.66e-10 1.50e-09 -3.8 8.72e-03 - 1.00e+00 1.00e+00h 1\n", + " 6 0.0000000e+00 2.18e-11 1.84e-11 -5.7 1.92e-03 - 1.00e+00 1.00e+00h 1\n", + " 7 0.0000000e+00 1.46e-11 2.51e-14 -8.6 2.10e-04 - 1.00e+00 1.00e+00H 1\n", + "\n", + "Number of Iterations....: 7\n", + "\n", + " (scaled) (unscaled)\n", + "Objective...............: 0.0000000000000000e+00 0.0000000000000000e+00\n", + "Dual infeasibility......: 2.5059035640133008e-14 2.5059035640133008e-14\n", + "Constraint violation....: 4.8801876740451558e-13 1.4551915228366852e-11\n", + "Complementarity.........: 2.5059101787473095e-09 2.5059101787473095e-09\n", + "Overall NLP error.......: 2.5059101787473095e-09 2.5059101787473095e-09\n", + "\n", + "\n", + "Number of objective function evaluations = 9\n", + "Number of objective gradient evaluations = 8\n", + "Number of equality constraint evaluations = 9\n", + "Number of inequality constraint evaluations = 0\n", + "Number of equality constraint Jacobian evaluations = 8\n", + "Number of inequality constraint Jacobian evaluations = 0\n", + "Number of Lagrangian Hessian evaluations = 7\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", + "\n", + "====================================================================================\n", + "Unit : fs.flash Time: 0.0\n", + "------------------------------------------------------------------------------------\n", + " Unit Performance\n", + "\n", + " Variables: \n", + "\n", + " Key : Value : Units : Fixed : Bounds\n", + " Heat Duty : 4059.3 : watt : False : (None, None)\n", + " Pressure Change : 0.0000 : pascal : True : (None, None)\n", + "\n", + "------------------------------------------------------------------------------------\n", + " Stream Table\n", + " Units Inlet Vapor Outlet Liquid Outlet\n", + " flow_mol mole / second 1.0000 0.51773 0.48227 \n", + " mole_frac_comp benzene dimensionless 0.50000 0.60690 0.38524 \n", + " mole_frac_comp toluene dimensionless 0.50000 0.39310 0.61476 \n", + " temperature kelvin 368.00 368.85 368.85 \n", + " pressure pascal 1.0132e+05 1.0132e+05 1.0132e+05 \n", + "====================================================================================\n" + ] + } + ], + "execution_count": 45 } ], "metadata": { @@ -654,4 +1091,4 @@ }, "nbformat": 4, "nbformat_minor": 3 -} +} \ No newline at end of file diff --git a/idaes_examples/notebooks/docs/tut/core/flash_unit_solution.ipynb b/idaes_examples/notebooks/docs/tut/core/flash_unit_solution.ipynb index 6b4b3752..0e823872 100644 --- a/idaes_examples/notebooks/docs/tut/core/flash_unit_solution.ipynb +++ b/idaes_examples/notebooks/docs/tut/core/flash_unit_solution.ipynb @@ -2,14 +2,16 @@ "cells": [ { "cell_type": "code", - "execution_count": null, "metadata": { "tags": [ "header", "hide-cell" - ] + ], + "ExecuteTime": { + "end_time": "2025-06-06T16:45:45.923673Z", + "start_time": "2025-06-06T16:45:45.919855Z" + } }, - "outputs": [], "source": [ "###############################################################################\n", "# The Institute for the Design of Advanced Energy Systems Integrated Platform\n", @@ -23,37 +25,46 @@ "# All rights reserved. Please see the files COPYRIGHT.md and LICENSE.md\n", "# for full copyright and license information.\n", "###############################################################################" - ] + ], + "outputs": [], + "execution_count": 1 }, { - "cell_type": "markdown", "metadata": {}, + "cell_type": "markdown", "source": [ - "# Flash Unit Model\n", + "# Flash Unit Model Tutorial\n", "\n", - "Author: Jaffer Ghouse \n", - "Maintainer: Andrew Lee \n", - "Updated: 2023-06-01 \n", + "Author: Jaffer Ghouse
\n", + "Maintainer: Tanner Polley
\n", + "Updated: 2025-06-03\n", "\n", - "In this module, we will familiarize ourselves with the IDAES framework by creating and working with a flowsheet that contains a single flash tank. The flash tank will be used to perform separation of Benzene and Toluene. The inlet specifications for this flash tank are:\n", + "In this module, we will familiarize ourselves with the IDAES framework by creating and working with a flowsheet that contains a single flash tank. The flash tank will be used to perform separation of Benzene and Toluene.\n", "\n", - "Inlet Specifications:\n", - "* Mole fraction (Benzene) = 0.5\n", - "* Mole fraction (Toluene) = 0.5\n", - "* Pressure = 101325 Pa\n", - "* Temperature = 368 K\n", + "The general workflow of setting up an IDAES flowsheet is the following:\n", + "\n", + "- 1 Importing Modules\n", + "- 2 Building a Model\n", + "- 3 Scaling the Model\n", + "- 4 Specifying the Model\n", + "- 5 Initializing the Model\n", + "- 6 Solving the Model\n", + "- 7 Analyzing and Visualizing the Results\n", "\n", - "We will complete the following tasks:\n", - "* Create the model and the IDAES Flowsheet object\n", - "* Import the appropriate property packages\n", - "* Create the flash unit and set the operating conditions\n", - "* Initialize the model and simulate the system\n", - "* Demonstrate analyses on this model through some examples and exercises\n", + "We will complete each of these steps as well as demonstrate analyses on this model through some examples and exercises\n", "\n", "## Key links to documentation\n", "* Main IDAES online documentation page: https://idaes-pse.readthedocs.io/en/stable/\n", - "\n", - "## Create the Model and the IDAES Flowsheet\n", + "* General Workflow: https://idaes-pse.readthedocs.io/en/stable/how_to_guides/workflow/general.html\n", + "* Flash Unit Model Documentation: https://idaes-pse.readthedocs.io/en/stable/reference_guides/model_libraries/generic/unit_models/flash.html\n", + "\n" + ] + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": [ + "## 1 Import Modules\n", "\n", "In the next cell, we will perform the necessary imports to get us started. From `pyomo.environ` (a standard import for the Pyomo package), we are importing `ConcreteModel` (to create the Pyomo model that will contain the IDAES flowsheet) and `SolverFactory` (to create the object we will use to solve the equations). We will also import `Constraint` as we will be adding a constraint to the model later in the module. Lastly, we also import `value` from Pyomo. This is a function that can be used to return the current numerical value for variables and parameters in the model. These are all part of Pyomo.\n", "\n", @@ -66,23 +77,31 @@ ] }, { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:49.094293Z", + "start_time": "2025-06-06T16:45:45.938076Z" + } + }, "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], "source": [ "from pyomo.environ import ConcreteModel, SolverFactory, Constraint, value\n", "from idaes.core import FlowsheetBlock\n", "\n", "# Import idaes logger to set output levels\n", - "import idaes.logger as idaeslog" - ] + "import idaes.logger as idaeslog\n", + "%matplotlib inline" + ], + "outputs": [], + "execution_count": 2 }, { - "cell_type": "markdown", "metadata": {}, + "cell_type": "markdown", "source": [ - "In the next cell, we will create the `ConcreteModel` and the `FlowsheetBlock`, and attach the flowsheet block to the Pyomo model.\n", + "## 2 Create the Model and IDAES Flowsheet\n", + "\n", + "In the next cell, we will create the `ConcreteModel` object often named `m` (which comes from Pyomo) and then connect the `FlowsheetBlock` (which comes from IDAES) to `m`. We ensure `dynamic=False` since this is a steady-state problem. This creates our overall model and adds the flowsheet capabilities that IDAES provides to the Pyomo model.\n", "\n", "
\n", "Inline Exercise:\n", @@ -91,20 +110,29 @@ ] }, { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:49.521103Z", + "start_time": "2025-06-06T16:45:49.517229Z" + } + }, "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], "source": [ "m = ConcreteModel()\n", "m.fs = FlowsheetBlock(dynamic=False)" - ] + ], + "outputs": [], + "execution_count": 3 }, { + "metadata": { + "tags": [ + "exercise" + ] + }, "cell_type": "markdown", - "metadata": {}, "source": [ - "At this point, we have a single Pyomo model that contains an (almost) empty flowsheet block.\n", + "At this point, we have a single Pyomo model that contains an (almost) empty flowsheet block. Lets use the `m.pprint()` to investigate the current contents of the model.\n", "\n", "
\n", "Inline Exercise:\n", @@ -113,41 +141,64 @@ ] }, { - "cell_type": "code", - "execution_count": 3, "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:49.531374Z", + "start_time": "2025-06-06T16:45:49.527521Z" + }, "tags": [ "exercise" ] }, + "cell_type": "code", + "source": "# Todo: call pprint on the model", "outputs": [], - "source": [ - "# Todo: call pprint on the model" - ] + "execution_count": 4 }, { - "cell_type": "code", - "execution_count": 4, "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:49.577500Z", + "start_time": "2025-06-06T16:45:49.572256Z" + }, "tags": [ "solution" ] }, - "outputs": [], + "cell_type": "code", "source": [ "# Todo: call pprint on the model\n", "m.pprint()" - ] + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1 Block Declarations\n", + " fs : Size=1, Index=None, Active=True\n", + " 1 Set Declarations\n", + " _time : Size=1, Index=None, Ordered=Insertion\n", + " Key : Dimen : Domain : Size : Members\n", + " None : 1 : Any : 1 : {0.0,}\n", + "\n", + " 1 Declarations: _time\n", + "\n", + "1 Declarations: fs\n" + ] + } + ], + "execution_count": 5 }, { - "cell_type": "markdown", "metadata": {}, + "cell_type": "markdown", "source": [ - "## Define Properties\n", + "### 2.1 Define Properties\n", "\n", "We need to define the property package for our flowsheet. In this example, we will be using the ideal property package that is available as part of the IDAES framework. This property package supports ideal gas - ideal liquid, ideal gas - NRTL, and ideal gas - Wilson models for VLE. More details on this property package can be found at: https://idaes-pse.readthedocs.io/en/stable/reference_guides/model_libraries/generic/property_models/activity_coefficient.html\n", "\n", - "IDAES also supports creation of your own property packages that allow for specification of the fluid using any set of valid state variables (e.g., component molar flows vs overall flow and mole fractions). This flexibility is designed to support advanced modeling needs that may rely on specific formulations. To learn about creating your own property package, please consult the online documentation at: https://idaes-pse.readthedocs.io/en/stable/explanations/components/property_package/index.html and look at examples within IDAES\n", + "IDAES also supports creation of your own property packages that will be shown in a later module.\n", "\n", "For this workshop, we will import the BTX_activity_coeff_VLE property parameter block to be used in the flowsheet. This properties block will be passed to our unit model to define the appropriate state variables and equations for performing thermodynamic calculations.\n", "\n", @@ -158,34 +209,44 @@ ] }, { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:49.649106Z", + "start_time": "2025-06-06T16:45:49.626357Z" + } + }, "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [], "source": [ "from idaes.models.properties.activity_coeff_models.BTX_activity_coeff_VLE import (\n", " BTXParameterBlock,\n", ")" - ] + ], + "outputs": [], + "execution_count": 6 }, { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:49.669359Z", + "start_time": "2025-06-06T16:45:49.657658Z" + } + }, "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [], "source": [ "m.fs.properties = BTXParameterBlock(\n", " valid_phase=(\"Liq\", \"Vap\"), activity_coeff_model=\"Ideal\", state_vars=\"FTPz\"\n", ")" - ] + ], + "outputs": [], + "execution_count": 7 }, { - "cell_type": "markdown", "metadata": {}, + "cell_type": "markdown", "source": [ - "## Adding Flash Unit\n", + "### 2.2 Adding Flash Unit\n", "\n", - "Now that we have the flowsheet and the properties defined, we can create the flash unit and add it to the flowsheet. \n", + "Now that we have the flowsheet and the properties defined, we can create the flash unit and add it to the flowsheet.\n", "\n", "**The Unit Model Library within IDAES includes a large set of common unit operations (see the online documentation for details: https://idaes-pse.readthedocs.io/en/stable/reference_guides/model_libraries/generic/unit_models/index.html**\n", "\n", @@ -200,7 +261,7 @@ "* Pressure changing equipment (compressors, expanders, pumps)\n", "* Feed and Product (source / sink) components\n", "\n", - "In this module, we will import the `Flash` unit model from `idaes.models.unit_models` and create an instance of the flash unit, attaching it to the flowsheet. Each IDAES unit model has several configurable options to customize the model behavior, but also includes defaults for these options. In this example, we will specify that the property package to be used with the Flash is the one we created earlier.\n", + "In this module, we will import the `Flash` unit model from `idaes.models.unit_models` and create an instance of the flash unit, attaching it to the flowsheet. Each IDAES unit model has several configurable options to customize the model behavior, but also includes defaults for these options. In this example, we will specify that the property package to be used with the Flash unit model is the one we created earlier by setting `property_package=m.fs.properties` within the `Flash` method.\n", "\n", "
\n", "Inline Exercise:\n", @@ -209,40 +270,183 @@ ] }, { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:49.749283Z", + "start_time": "2025-06-06T16:45:49.678730Z" + } + }, "cell_type": "code", - "execution_count": 7, - "metadata": {}, + "source": "from idaes.models.unit_models import Flash", "outputs": [], - "source": [ - "from idaes.models.unit_models import Flash" - ] + "execution_count": 8 }, { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:49.772441Z", + "start_time": "2025-06-06T16:45:49.758167Z" + } + }, "cell_type": "code", - "execution_count": 8, + "source": "m.fs.flash = Flash(property_package=m.fs.properties)", + "outputs": [], + "execution_count": 9 + }, + { "metadata": {}, + "cell_type": "markdown", + "source": "At this point, we have created a flowsheet and a properties block. We have also created a flash unit and added it to the flowsheet. Under the hood, IDAES has created the required state variables and model equations. Everything is open. You can see these variables and equations by calling the Pyomo method `pprint` on the model, flowsheet, or flash tank objects. Note that this output is very exhaustive, and is not intended to provide any summary information about the model, but rather a complete picture of all of the variables and equations in the model." + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:49.783706Z", + "start_time": "2025-06-06T16:45:49.781688Z" + }, + "tags": [ + "noauto" + ] + }, + "cell_type": "code", + "source": "m.pprint()", "outputs": [], + "execution_count": 10 + }, + { + "metadata": {}, + "cell_type": "markdown", "source": [ - "m.fs.flash = Flash(property_package=m.fs.properties)" + "## 3 Scaling the Model\n", + "\n", + "Now that the model is built, with properties set and the unit model created and added to the flowsheet, the next step is to scale the model. Ensuring that a model is well scaled is important for increasing the efficiency and reliability of solvers, and users should consider model scaling as an integral part of the modeling process. IDAES provides a number of tool for assisting users with scaling their models, and details on these can be found at https://idaes-pse.readthedocs.io/en/stable/reference_guides/scaling/scaling.html#scaling-toolbox\n", + "\n", + "There are currently two primary methods in scaling the model: manual scaling of each relevant component, or utilizing the AutoScaler Class. The more careful and risk-free method of manually scaling each component is the recommended method for maximum control and assurance that the model will be well-scaled. This comes with the drawback of being more meticulous while the AutoScaler is much simpler to use since it scaled the whole model all at once, it is less precise by lacking direct control over the scaling factor for each component and relying on scaling factors to be estimated. Both methods will be shown below" ] }, { + "metadata": {}, "cell_type": "markdown", + "source": [ + "### 3.1 Manual Scaling\n", + "The `set_scaling_factor` function is imported from `idaes.core.scaling.util` and is called with and used on each relevant component that needs to be well scaled. The component is the first argument and its scaling factor is the second argument.\n", + "\n", + "
\n", + "Inline Exercise:\n", + "Execute the following two cells to import the `set_scaling_factor` and set the scaling factor for both temperature and pressure\n", + "
\n", + "\n", + "Both `temperature` and `pressure` can be found at `m.fs.flash.inlet`." + ] + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:49.795252Z", + "start_time": "2025-06-06T16:45:49.792075Z" + } + }, + "cell_type": "code", + "source": "from idaes.core.scaling.util import set_scaling_factor", + "outputs": [], + "execution_count": 11 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:49.809330Z", + "start_time": "2025-06-06T16:45:49.806174Z" + } + }, + "cell_type": "code", + "source": [ + "set_scaling_factor(m.fs.flash.inlet.temperature, 300)\n", + "set_scaling_factor(m.fs.flash.inlet.pressure, 1e6)" + ], + "outputs": [], + "execution_count": 12 + }, + { "metadata": {}, + "cell_type": "markdown", "source": [ - "At this point, we have created a flowsheet and a properties block. We have also created a flash unit and added it to the flowsheet. Under the hood, IDAES has created the required state variables and model equations. Everything is open. You can see these variables and equations by calling the Pyomo method `pprint` on the model, flowsheet, or flash tank objects. Note that this output is very exhaustive, and is not intended to provide any summary information about the model, but rather a complete picture of all of the variables and equations in the model." + "### 3.2 Scaling with AutoScaler\n", + "The `AutoScaler` class is imported from `idaes.core.scaling.autoscaling` and an instance of the class is created. This instance contains the method `scale_model` which is used to scale the whole model at once. This can be a useful option but is generally more risky than manually scaling the model components since it has less direct control and specification.\n", + "\n", + "
\n", + "Inline Exercise:\n", + "Execute the following two cells to import the `AutoScaler` class and create an autoscaler instance that scaled the whole model at once\n", + "
" ] }, { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:49.826276Z", + "start_time": "2025-06-06T16:45:49.823030Z" + } + }, + "cell_type": "code", + "source": "from idaes.core.scaling.autoscaling import AutoScaler", + "outputs": [], + "execution_count": 13 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:50.217776Z", + "start_time": "2025-06-06T16:45:49.836920Z" + } + }, + "cell_type": "code", + "source": [ + "autoscaler = AutoScaler()\n", + "autoscaler.scale_model(m)" + ], + "outputs": [], + "execution_count": 14 + }, + { + "metadata": {}, "cell_type": "markdown", + "source": "" + }, + { "metadata": {}, + "cell_type": "markdown", "source": [ - "## Set Operating Conditions\n", + "## 4 Set Operating Conditions\n", "\n", - "Now that we have created our unit model, we can specify the necessary operating conditions. It is often very useful to determine the degrees of freedom before we specify any conditions.\n", + "Now that we have created our unit model and scaled it, we can specify the necessary operating conditions. The inlet specifications for this flash tank are:\n", "\n", - "The `idaes.core.util.model_statistics` package has a function `degrees_of_freedom`. To see how to use this function, we can make use of the Python function `help(func)`. This function prints the appropriate documentation string for the function.\n", + "Inlet Specifications:\n", + "* Mole fraction (Benzene) = 0.5\n", + "* Mole fraction (Toluene) = 0.5\n", + "* Pressure = 101325 Pa\n", + "* Temperature = 368 K\n", + "\n", + "\n" + ] + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": [ + "### 4.1 Degrees of Freedom\n", + "\n", + "It is often very useful to first determine the degrees of freedom before we specify any conditions.\n", "\n", + "The `idaes.core.util.model_statistics` package has a function `degrees_of_freedom`. To see how to use this function, we can make use of the Python function `help(func)`. This function prints the appropriate documentation string for the function.\n" + ] + }, + { + "metadata": { + "tags": [ + "exercise" + ] + }, + "cell_type": "markdown", + "source": [ "
\n", "Inline Exercise:\n", "Import the degrees_of_freedom function and print the help for the function by calling the Python help function.\n", @@ -250,41 +454,71 @@ ] }, { - "cell_type": "code", - "execution_count": 9, "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:50.224457Z", + "start_time": "2025-06-06T16:45:50.221905Z" + }, "tags": [ "exercise" ] }, - "outputs": [], + "cell_type": "code", "source": [ "# Todo: import the degrees_of_freedom function from the idaes.core.util.model_statistics package\n", "\n", "\n", "# Todo: Call the python help on the degrees_of_freedom function" - ] + ], + "outputs": [], + "execution_count": 15 }, { - "cell_type": "code", - "execution_count": 10, "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:50.240605Z", + "start_time": "2025-06-06T16:45:50.237594Z" + }, "tags": [ "solution" ] }, - "outputs": [], + "cell_type": "code", "source": [ "# Todo: import the degrees_of_freedom function from the idaes.core.util.model_statistics package\n", "from idaes.core.util.model_statistics import degrees_of_freedom\n", "\n", "# Todo: Call the python help on the degrees_of_freedom function\n", "help(degrees_of_freedom)" - ] + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Help on function degrees_of_freedom in module idaes.core.util.model_statistics:\n", + "\n", + "degrees_of_freedom(block)\n", + " Method to return the degrees of freedom of a model.\n", + "\n", + " Args:\n", + " block : model to be studied\n", + "\n", + " Returns:\n", + " Number of degrees of freedom in block.\n", + "\n" + ] + } + ], + "execution_count": 16 }, { + "metadata": { + "tags": [ + "exercise" + ] + }, "cell_type": "markdown", - "metadata": {}, "source": [ "
\n", "Inline Exercise:\n", @@ -293,36 +527,52 @@ ] }, { - "cell_type": "code", - "execution_count": 11, "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:50.271361Z", + "start_time": "2025-06-06T16:45:50.268763Z" + }, "tags": [ "exercise" ] }, + "cell_type": "code", + "source": "# Todo: print the degrees of freedom for your model", "outputs": [], - "source": [ - "# Todo: print the degrees of freedom for your model" - ] + "execution_count": 17 }, { - "cell_type": "code", - "execution_count": 12, "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:50.297399Z", + "start_time": "2025-06-06T16:45:50.291352Z" + }, "tags": [ "solution" ] }, - "outputs": [], + "cell_type": "code", "source": [ "# Todo: print the degrees of freedom for your model\n", "print(\"Degrees of Freedom =\", degrees_of_freedom(m))" - ] + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Degrees of Freedom = 7\n" + ] + } + ], + "execution_count": 18 }, { - "cell_type": "markdown", "metadata": {}, + "cell_type": "markdown", "source": [ + "### 4.2 Specify Inlet Conditions\n", + "\n", "To satisfy our degrees of freedom, we will first specify the inlet conditions. We can specify these values through the `inlet` port of the flash unit.\n", "\n", "**To see the list of naming conventions for variables within the IDAES framework, consult the online documentation at: https://idaes-pse.readthedocs.io/en/stable/explanations/conventions.html#standard-naming-format**\n", @@ -359,7 +609,17 @@ "* inlet mole fraction (toluene) = 0.5 (`mole_frac_comp[0, \"toluene\"]`)\n", "* The heat duty on the flash set to 0 (`heat_duty`)\n", "* The pressure drop across the flash tank set to 0 (`deltaP`)\n", - "\n", + "\n" + ] + }, + { + "metadata": { + "tags": [ + "exercise" + ] + }, + "cell_type": "markdown", + "source": [ "
\n", "Inline Exercise:\n", "Write the code below to specify the inlet conditions and unit specifications described above\n", @@ -367,30 +627,36 @@ ] }, { - "cell_type": "code", - "execution_count": 14, "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:50.360852Z", + "start_time": "2025-06-06T16:45:50.357341Z" + }, "tags": [ "exercise" ] }, - "outputs": [], + "cell_type": "code", "source": [ "# Todo: Add inlet specifications given above\n", "\n", "\n", "# Todo: Add 2 flash unit specifications given above" - ] + ], + "outputs": [], + "execution_count": 20 }, { - "cell_type": "code", - "execution_count": 15, "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:50.396800Z", + "start_time": "2025-06-06T16:45:50.392314Z" + }, "tags": [ "solution" ] }, - "outputs": [], + "cell_type": "code", "source": [ "# Todo: Add inlet specifications given above\n", "m.fs.flash.inlet.flow_mol.fix(1)\n", @@ -402,11 +668,25 @@ "# Todo: Add 2 flash unit specifications given above\n", "m.fs.flash.heat_duty.fix(0)\n", "m.fs.flash.deltaP.fix(0)" + ], + "outputs": [], + "execution_count": 21 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": [ + "Now that all the inlets have been specified, we can check the degrees of freedom again to ensure the system is square and has a degree of freedom of 0\n", + "\n" ] }, { + "metadata": { + "tags": [ + "exercise" + ] + }, "cell_type": "markdown", - "metadata": {}, "source": [ "
\n", "Inline Exercise:\n", @@ -415,79 +695,147 @@ ] }, { - "cell_type": "code", - "execution_count": 16, "metadata": { "tags": [ "exercise" ] }, + "cell_type": "code", "outputs": [], - "source": [ - "# Todo: print the degrees of freedom for your model" - ] + "execution_count": 22, + "source": "# Todo: print the degrees of freedom for your model" }, { - "cell_type": "code", - "execution_count": 17, "metadata": { "tags": [ "solution" ] }, - "outputs": [], + "cell_type": "code", + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Degrees of Freedom = 0\n" + ] + } + ], + "execution_count": 23, "source": [ "# Todo: print the degrees of freedom for your model\n", "print(\"Degrees of Freedom =\", degrees_of_freedom(m))" ] }, { - "cell_type": "markdown", "metadata": {}, + "cell_type": "markdown", "source": [ - "## Initializing the Model\n", + "## 5 Initializing the Model\n", "\n", - "IDAES includes pre-written initialization routines for all unit models. You can call this initialize method on the units. In the next module, we will demonstrate the use of a sequential modular solve cycle to initialize flowsheets.\n", + "Now that all building steps are complete, the last step before solving the model is to initialize the model, or prepping the solve by giving it a good starting point. This is essentially giving the solver an initial guess for the iterative solver to reach convergence and is essential for both a fast and accurate solution. In IDAES, the current standard for initializing the model is by utilizing initializer instances. These initializer instances contain the initialize method that can be applied to any model type. For more information on initializing in IDAES, visit https://idaes-pse.readthedocs.io/en/stable/reference_guides/initialization/index.html.
\n", "\n", + "For this tutorial, we will import the initializer class `BlockTriangularizationInitializer` class from the `default_initializer` method from the flash unit model. This is often the simplest way to obtain a compatible initializer for each unit model, but you can also directly important any initializer needed from this source `idaes.core.initialization`. Each initializer instance contains the `initialize()` method that requires an argument to be initialized and in this case its the flash unit model.\n" + ] + }, + { + "metadata": { + "tags": [ + "exercise" + ] + }, + "cell_type": "markdown", + "source": [ "
\n", "Inline Exercise:\n", - "Call the initialize method on the flash unit to initialize the model.\n", + "Define the initializer instance, and initialize the flash unit model\n", "
" ] }, { - "cell_type": "code", - "execution_count": 19, "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:50.499945Z", + "start_time": "2025-06-06T16:45:50.497439Z" + }, "tags": [ "exercise" ] }, + "cell_type": "code", + "source": "# Todo: initialize the flash unit", "outputs": [], - "source": [ - "# Todo: initialize the flash unit" - ] + "execution_count": 25 }, { - "cell_type": "code", - "execution_count": 20, "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:51:40.247234Z", + "start_time": "2025-06-06T16:51:39.730044Z" + }, "tags": [ "solution" ] }, - "outputs": [], + "cell_type": "code", "source": [ "# Todo: initialize the flash unit\n", - "m.fs.flash.initialize(outlvl=idaeslog.INFO)" - ] + "FlashInitializer = m.fs.flash.default_initializer()\n", + "FlashInitializer.initialize(m.fs.flash)" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2025-06-06 10:51:39 [INFO] idaes.init.fs.flash.control_volume.properties_in: Initialization Step 1 optimal - .\n", + "2025-06-06 10:51:39 [INFO] idaes.init.fs.flash.control_volume.properties_in: Initialization Step 2 optimal - .\n", + "2025-06-06 10:51:39 [INFO] idaes.init.fs.flash.control_volume.properties_in: Initialization Step 3 optimal - .\n", + "2025-06-06 10:51:39 [INFO] idaes.init.fs.flash.control_volume.properties_in: Initialization Step 4 optimal - .\n", + "2025-06-06 10:51:39 [INFO] idaes.init.fs.flash.control_volume.properties_in: Initialization Step 5 optimal - .\n", + "2025-06-06 10:51:40 [INFO] idaes.init.fs.flash.control_volume.properties_out: Initialization Step 1 optimal - .\n", + "2025-06-06 10:51:40 [INFO] idaes.init.fs.flash.control_volume.properties_out: Initialization Step 2 optimal - .\n", + "2025-06-06 10:51:40 [INFO] idaes.init.fs.flash.control_volume.properties_out: Initialization Step 3 optimal - .\n", + "2025-06-06 10:51:40 [INFO] idaes.init.fs.flash.control_volume.properties_out: Initialization Step 4 optimal - .\n", + "2025-06-06 10:51:40 [INFO] idaes.init.fs.flash.control_volume.properties_out: Initialization Step 5 optimal - .\n" + ] + }, + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 33, + "metadata": {}, + "output_type": "execute_result" + } + ], + "execution_count": 33 }, { + "metadata": {}, "cell_type": "markdown", + "source": "Another option for initializing is utilizing the default initializer that is attached to the unit model. Each unit model as a default initializer that is hypothetically the most compatible. It can be called with `m.fs.flash.initialize()`. While this is an option, it is generally preferred to import an initializer object and initialize the model with that to ensure more control over the initialization." + }, + { "metadata": {}, + "cell_type": "markdown", "source": [ - "Now that the model has been defined and initialized, we can solve the model.\n", + "## 6 Solving the Model\n", "\n", + "Now that the model has been defined and initialized, we can solve the model.\n", + "\n" + ] + }, + { + "metadata": { + "tags": [ + "exercise" + ] + }, + "cell_type": "markdown", + "source": [ "
\n", "Inline Exercise:\n", "Using the notation described in the previous model, create an instance of the \"ipopt\" solver and use it to solve the model. Set the tee option to True to see the log output.\n", @@ -495,42 +843,120 @@ ] }, { - "cell_type": "code", - "execution_count": 21, "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T17:04:32.219172Z", + "start_time": "2025-06-06T17:04:32.216161Z" + }, "tags": [ "exercise" ] }, - "outputs": [], + "cell_type": "code", "source": [ "# Todo: create the ipopt solver\n", "\n", "# Todo: solve the model" - ] + ], + "outputs": [], + "execution_count": 36 }, { - "cell_type": "code", - "execution_count": 22, "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T17:04:32.439966Z", + "start_time": "2025-06-06T17:04:32.396984Z" + }, "tags": [ "solution" ] }, - "outputs": [], + "cell_type": "code", "source": [ "# Todo: create the ipopt solver\n", "solver = SolverFactory(\"ipopt\")\n", "\n", "# Todo: solve the model\n", "status = solver.solve(m, tee=True)" - ] + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "Ipopt 3.13.2: \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...: 135\n", + "Number of nonzeros in inequality constraint Jacobian.: 0\n", + "Number of nonzeros in Lagrangian Hessian.............: 72\n", + "\n", + "Total number of variables............................: 41\n", + " variables with only lower bounds: 3\n", + " variables with lower and upper bounds: 10\n", + " variables with only upper bounds: 0\n", + "Total number of equality constraints.................: 41\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 6.22e-05 1.00e+00 -1.0 0.00e+00 - 0.00e+00 0.00e+00 0\n", + " 1 0.0000000e+00 1.46e-11 1.00e-02 -1.0 6.22e-05 - 9.90e-01 1.00e+00h 1\n", + "\n", + "Number of Iterations....: 1\n", + "\n", + " (scaled) (unscaled)\n", + "Objective...............: 0.0000000000000000e+00 0.0000000000000000e+00\n", + "Dual infeasibility......: 0.0000000000000000e+00 0.0000000000000000e+00\n", + "Constraint violation....: 2.0417014662014577e-12 1.4551915228366852e-11\n", + "Complementarity.........: 0.0000000000000000e+00 0.0000000000000000e+00\n", + "Overall NLP error.......: 2.0417014662014577e-12 1.4551915228366852e-11\n", + "\n", + "\n", + "Number of objective function evaluations = 2\n", + "Number of objective gradient evaluations = 2\n", + "Number of equality constraint evaluations = 2\n", + "Number of inequality constraint evaluations = 0\n", + "Number of equality constraint Jacobian evaluations = 2\n", + "Number of inequality constraint Jacobian evaluations = 0\n", + "Number of Lagrangian Hessian evaluations = 1\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" + ] + } + ], + "execution_count": 37 }, { - "cell_type": "markdown", "metadata": {}, + "cell_type": "markdown", "source": [ - "## Viewing the Results\n", + "## 7 Viewing the Results\n", "\n", "Once a model is solved, the values returned by the solver are loaded into the model object itself. We can access the value of any variable in the model with the `value` function. For example:\n", "```python\n", @@ -550,10 +976,13 @@ ] }, { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T17:04:35.556815Z", + "start_time": "2025-06-06T17:04:35.551070Z" + } + }, "cell_type": "code", - "execution_count": 24, - "metadata": {}, - "outputs": [], "source": [ "# Print the pressure of the flash vapor outlet\n", "print(\"Pressure =\", value(m.fs.flash.vap_outlet.pressure[0]))\n", @@ -563,34 +992,89 @@ "# Call display on vap_outlet and liq_outlet of the flash\n", "m.fs.flash.vap_outlet.display()\n", "m.fs.flash.liq_outlet.display()" - ] + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Pressure = 101325.0\n", + "\n", + "Output from display:\n", + "vap_outlet : Size=1\n", + " Key : Name : Value\n", + " None : flow_mol : {0.0: 0.3961181748774193}\n", + " : mole_frac_comp : {(0.0, 'benzene'): 0.633976648508129, (0.0, 'toluene'): 0.366023351491871}\n", + " : pressure : {0.0: 101325.0}\n", + " : temperature : {0.0: 368.0}\n", + "liq_outlet : Size=1\n", + " Key : Name : Value\n", + " None : flow_mol : {0.0: 0.6038818251225807}\n", + " : mole_frac_comp : {(0.0, 'benzene'): 0.41211759772293044, (0.0, 'toluene'): 0.5878824022770694}\n", + " : pressure : {0.0: 101325.0}\n", + " : temperature : {0.0: 368.0}\n" + ] + } + ], + "execution_count": 39 }, { - "cell_type": "markdown", "metadata": {}, + "cell_type": "markdown", "source": [ "The output from `display` is quite exhaustive and not really intended to provide quick summary information. Because Pyomo is built on Python, there are opportunities to format the output any way we like. Most IDAES models have a `report` method which provides a summary of the results for the model.\n", "\n", "
\n", "Inline Exercise:\n", - "Execute the cell below which uses the function above to print a summary of the key variables in the flash model, including the inlet, the vapor, and the liquid ports. \n", + "Execute the cell below which uses the function above to print a summary of the key variables in the flash model, including the inlet, the vapor, and the liquid ports.\n", "
" ] }, { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T17:04:38.727731Z", + "start_time": "2025-06-06T17:04:38.712131Z" + } + }, "cell_type": "code", - "execution_count": 25, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.flash.report()" - ] + "source": "m.fs.flash.report()", + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "====================================================================================\n", + "Unit : fs.flash Time: 0.0\n", + "------------------------------------------------------------------------------------\n", + " Unit Performance\n", + "\n", + " Variables: \n", + "\n", + " Key : Value : Units : Fixed : Bounds\n", + " Heat Duty : 0.0000 : watt : True : (None, None)\n", + " Pressure Change : 0.0000 : pascal : True : (None, None)\n", + "\n", + "------------------------------------------------------------------------------------\n", + " Stream Table\n", + " Units Inlet Vapor Outlet Liquid Outlet\n", + " flow_mol mole / second 1.0000 0.39612 0.60388 \n", + " mole_frac_comp benzene dimensionless 0.50000 0.63398 0.41212 \n", + " mole_frac_comp toluene dimensionless 0.50000 0.36602 0.58788 \n", + " temperature kelvin 368.00 368.00 368.00 \n", + " pressure pascal 1.0132e+05 1.0132e+05 1.0132e+05 \n", + "====================================================================================\n" + ] + } + ], + "execution_count": 40 }, { - "cell_type": "markdown", "metadata": {}, + "cell_type": "markdown", "source": [ - "## Studying Purity as a Function of Heat Duty\n", + "## Exercise: Studying Purity as a Function of Heat Duty\n", "\n", "Since the entire modeling framework is built upon Python, it includes a complete programming environment for whatever analysis we may want to perform. In this next exercise, we will make use of what we learned in this and the previous module to generate a figure showing some output variables as a function of the heat duty in the flash tank.\n", "\n", @@ -602,22 +1086,35 @@ ] }, { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T17:04:43.426680Z", + "start_time": "2025-06-06T17:04:43.423025Z" + } + }, "cell_type": "code", - "execution_count": 27, - "metadata": {}, + "source": "import matplotlib.pyplot as plt", "outputs": [], - "source": [ - "import matplotlib.pyplot as plt" - ] + "execution_count": 42 }, { - "cell_type": "markdown", "metadata": {}, + "cell_type": "markdown", "source": [ "Exercise specifications:\n", "* Generate a figure showing the flash tank heat duty (`m.fs.flash.heat_duty[0]`) vs. the vapor flowrate (`m.fs.flash.vap_outlet.flow_mol[0]`)\n", "* Specify the heat duty from -17000 to 25000 over 50 steps\n", - "\n", + "\n" + ] + }, + { + "metadata": { + "tags": [ + "exercise" + ] + }, + "cell_type": "markdown", + "source": [ "
\n", "Inline Exercise:\n", "Using what you have learned so far, fill in the missing code below to generate the figure specified above. (Hint: import numpy and use the linspace function from the previous module)\n", @@ -625,14 +1122,12 @@ ] }, { - "cell_type": "code", - "execution_count": 29, "metadata": { "tags": [ "exercise" ] }, - "outputs": [], + "cell_type": "code", "source": [ "# import the solve_successful checking function from workshop tools\n", "from idaes_examples.mod.tut.workshoptools import solve_successful\n", @@ -645,22 +1140,22 @@ "V = []\n", "\n", "# re-initialize model\n", - "m.fs.flash.initialize(outlvl=idaeslog.WARNING)\n", + "UnitModelInitializer.initialize(m.fs.flash)\n", "\n", "# Todo: Write the for loop specification using numpy's linspace\n", "\n", " # fix the heat duty\n", " m.fs.flash.heat_duty.fix(duty)\n", - " \n", + "\n", " # append the value of the duty to the Q list\n", " Q.append(duty)\n", - " \n", + "\n", " # print the current simulation\n", " print(\"Simulating with Q = \", value(m.fs.flash.heat_duty[0]))\n", "\n", " # Solve the model\n", " status = solver.solve(m)\n", - " \n", + "\n", " # append the value for vapor fraction if the solve was successful\n", " if solve_successful(status):\n", " V.append(value(m.fs.flash.vap_outlet.flow_mol[0]))\n", @@ -668,7 +1163,7 @@ " else:\n", " V.append(0.0)\n", " print('... solve failed.')\n", - " \n", + "\n", "# Create and show the figure\n", "plt.figure(\"Vapor Fraction\")\n", "plt.plot(Q, V)\n", @@ -676,18 +1171,21 @@ "plt.xlabel(\"Heat Duty [J]\")\n", "plt.ylabel(\"Vapor Fraction [-]\")\n", "plt.show()" - ] + ], + "outputs": [], + "execution_count": null }, { - "cell_type": "code", - "execution_count": 30, "metadata": { - "scrolled": true, + "ExecuteTime": { + "end_time": "2025-06-06T17:04:48.800601Z", + "start_time": "2025-06-06T17:04:46.438264Z" + }, "tags": [ "solution" ] }, - "outputs": [], + "cell_type": "code", "source": [ "# import the solve_successful checking function from workshop tools\n", "from idaes_examples.mod.tut.workshoptools import solve_successful\n", @@ -700,7 +1198,7 @@ "V = []\n", "\n", "# re-initialize model\n", - "m.fs.flash.initialize(outlvl=idaeslog.WARNING)\n", + "FlashInitializer.initialize(m.fs.flash)\n", "\n", "# Todo: Write the for loop specification using numpy's linspace\n", "for duty in np.linspace(-17000, 25000, 50):\n", @@ -731,11 +1229,244 @@ "plt.xlabel(\"Heat Duty [J]\")\n", "plt.ylabel(\"Vapor Fraction [-]\")\n", "plt.show()" - ] + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2025-06-06 11:04:46 [INFO] idaes.init.fs.flash.control_volume.properties_in: Initialization Step 1 optimal - .\n", + "2025-06-06 11:04:46 [INFO] idaes.init.fs.flash.control_volume.properties_in: Initialization Step 2 optimal - .\n", + "2025-06-06 11:04:46 [INFO] idaes.init.fs.flash.control_volume.properties_in: Initialization Step 3 optimal - .\n", + "2025-06-06 11:04:46 [INFO] idaes.init.fs.flash.control_volume.properties_in: Initialization Step 4 optimal - .\n", + "2025-06-06 11:04:46 [INFO] idaes.init.fs.flash.control_volume.properties_in: Initialization Step 5 optimal - .\n", + "2025-06-06 11:04:46 [INFO] idaes.init.fs.flash.control_volume.properties_out: Initialization Step 1 optimal - .\n", + "2025-06-06 11:04:46 [INFO] idaes.init.fs.flash.control_volume.properties_out: Initialization Step 2 optimal - .\n", + "2025-06-06 11:04:46 [INFO] idaes.init.fs.flash.control_volume.properties_out: Initialization Step 3 optimal - .\n", + "2025-06-06 11:04:46 [INFO] idaes.init.fs.flash.control_volume.properties_out: Initialization Step 4 optimal - .\n", + "2025-06-06 11:04:46 [INFO] idaes.init.fs.flash.control_volume.properties_out: Initialization Step 5 optimal - .\n", + "Simulating with Q = -17000.0\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -16142.857142857143\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -15285.714285714286\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -14428.571428571428\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -13571.428571428572\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -12714.285714285714\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -11857.142857142857\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -11000.0\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -10142.857142857143\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -9285.714285714286\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -8428.57142857143\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -7571.4285714285725\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -6714.285714285714\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -5857.142857142857\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -5000.0\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -4142.857142857143\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -3285.7142857142862\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -2428.5714285714294\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -1571.4285714285725\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -714.2857142857156\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 142.8571428571413\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 1000.0\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 1857.142857142855\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 2714.2857142857138\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 3571.4285714285725\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 4428.5714285714275\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 5285.714285714286\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 6142.857142857141\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 7000.0\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 7857.142857142855\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 8714.285714285714\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 9571.428571428569\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 10428.571428571428\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 11285.714285714286\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 12142.857142857141\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 13000.0\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 13857.142857142855\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 14714.285714285714\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 15571.428571428569\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 16428.571428571428\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 17285.714285714283\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 18142.857142857145\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 19000.0\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 19857.142857142855\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 20714.28571428571\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 21571.428571428572\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 22428.571428571428\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 23285.714285714283\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 24142.857142857145\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 25000.0\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n" + ] + }, + { + "data": { + "text/plain": [ + "
" + ], + "image/png": "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" + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "execution_count": 43 }, { + "metadata": { + "tags": [ + "exercise" + ] + }, "cell_type": "markdown", - "metadata": {}, "source": [ "
\n", "Inline Exercise:\n", @@ -744,27 +1475,27 @@ ] }, { - "cell_type": "code", - "execution_count": null, "metadata": { "tags": [ "exercise" ] }, + "cell_type": "code", + "source": "# Todo: generate a figure of heat duty vs. mole fraction of Benzene in the vapor", "outputs": [], - "source": [ - "# Todo: generate a figure of heat duty vs. mole fraction of Benzene in the vapor" - ] + "execution_count": null }, { - "cell_type": "code", - "execution_count": null, "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T17:05:02.101331Z", + "start_time": "2025-06-06T17:05:00.157447Z" + }, "tags": [ "solution" ] }, - "outputs": [], + "cell_type": "code", "source": [ "# Todo: generate a figure of heat duty vs. mole fraction of Benzene in the vapor\n", "Q = []\n", @@ -797,18 +1528,247 @@ "plt.xlabel(\"Heat Duty [J]\")\n", "plt.ylabel(\"Vapor Benzene Mole Fraction [-]\")\n", "plt.show()" - ] + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Simulating with Q = -17000.0\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -16142.857142857143\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -15285.714285714286\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -14428.571428571428\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -13571.428571428572\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -12714.285714285714\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -11857.142857142857\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -11000.0\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -10142.857142857143\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -9285.714285714286\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -8428.57142857143\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -7571.4285714285725\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -6714.285714285714\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -5857.142857142857\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -5000.0\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -4142.857142857143\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -3285.7142857142862\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -2428.5714285714294\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -1571.4285714285725\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -714.2857142857156\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 142.8571428571413\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 1000.0\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 1857.142857142855\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 2714.2857142857138\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 3571.4285714285725\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 4428.5714285714275\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 5285.714285714286\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 6142.857142857141\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 7000.0\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 7857.142857142855\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 8714.285714285714\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 9571.428571428569\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 10428.571428571428\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 11285.714285714286\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 12142.857142857141\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 13000.0\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 13857.142857142855\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 14714.285714285714\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 15571.428571428569\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 16428.571428571428\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 17285.714285714283\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 18142.857142857145\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 19000.0\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 19857.142857142855\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 20714.28571428571\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 21571.428571428572\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 22428.571428571428\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 23285.714285714283\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 24142.857142857145\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 25000.0\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n" + ] + }, + { + "data": { + "text/plain": [ + "
" + ], + "image/png": "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" + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "execution_count": 44 }, { - "cell_type": "markdown", "metadata": {}, + "cell_type": "markdown", "source": [ "Recall that the IDAES framework is an equation-oriented modeling environment. This means that we can specify \"design\" problems natively. That is, there is no need to have our specifications on the inlet alone. We can put specifications on the outlet as long as we retain a well-posed, square system of equations.\n", "\n", "For example, we can remove the specification on heat duty and instead specify that we want the mole fraction of Benzene in the vapor outlet to be equal to 0.6. The mole fraction is not a native variable in the property block, so we cannot use \"fix\". We can, however, add a constraint to the model.\n", "\n", "Note that we have been executing a number of solves on the problem, and may not be sure of the current state. To help convergence, therefore, we will first call initialize, then add the new constraint and solve the problem. Note that the reference for the mole fraction of Benzene in the vapor outlet is `m.fs.flash.vap_outlet.mole_frac_comp[0, \"benzene\"]`.\n", - "\n", + "\n" + ] + }, + { + "metadata": { + "tags": [ + "exercise" + ] + }, + "cell_type": "markdown", + "source": [ "
\n", "Inline Exercise:\n", "Fill in the missing code below and add a constraint on the mole fraction of Benzene (to a value of 0.6) to find the required heat duty.\n", @@ -816,14 +1776,16 @@ ] }, { - "cell_type": "code", - "execution_count": null, "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T17:05:05.363302Z", + "start_time": "2025-06-06T17:05:04.960601Z" + }, "tags": [ "exercise" ] }, - "outputs": [], + "cell_type": "code", "source": [ "# re-initialize the model - this may or may not be required depending on current state but safe to initialize\n", "m.fs.flash.heat_duty.fix(0)\n", @@ -839,17 +1801,118 @@ "\n", "# Check stream condition\n", "m.fs.flash.report()" - ] + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "WARNING: model contains export suffix 'scaling_factor' that contains 6\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "Ipopt 3.13.2: \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...: 136\n", + "Number of nonzeros in inequality constraint Jacobian.: 0\n", + "Number of nonzeros in Lagrangian Hessian.............: 72\n", + "\n", + "Total number of variables............................: 42\n", + " variables with only lower bounds: 3\n", + " variables with lower and upper bounds: 10\n", + " variables with only upper bounds: 0\n", + "Total number of equality constraints.................: 41\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 3.07e-08 1.01e-04 -1.0 0.00e+00 - 0.00e+00 0.00e+00 0\n", + " 1 0.0000000e+00 1.46e+00 2.42e-01 -1.0 4.86e+02 - 9.90e-01 1.00e+00f 1\n", + " 2 0.0000000e+00 5.55e+01 5.60e-03 -1.0 3.00e+03 - 9.90e-01 1.00e+00h 1\n", + " 3 0.0000000e+00 1.91e+00 4.24e-05 -1.0 5.68e+02 - 1.00e+00 1.00e+00h 1\n", + " 4 0.0000000e+00 1.53e-05 1.90e-06 -2.5 1.35e+00 - 1.00e+00 1.00e+00h 1\n", + " 5 0.0000000e+00 4.66e-10 1.50e-09 -3.8 8.72e-03 - 1.00e+00 1.00e+00h 1\n", + " 6 0.0000000e+00 2.18e-11 1.84e-11 -5.7 1.92e-03 - 1.00e+00 1.00e+00h 1\n", + " 7 0.0000000e+00 1.46e-11 2.51e-14 -8.6 2.10e-04 - 1.00e+00 1.00e+00H 1\n", + "\n", + "Number of Iterations....: 7\n", + "\n", + " (scaled) (unscaled)\n", + "Objective...............: 0.0000000000000000e+00 0.0000000000000000e+00\n", + "Dual infeasibility......: 2.5059035640133008e-14 2.5059035640133008e-14\n", + "Constraint violation....: 4.8801876740451558e-13 1.4551915228366852e-11\n", + "Complementarity.........: 2.5059101787473095e-09 2.5059101787473095e-09\n", + "Overall NLP error.......: 2.5059101787473095e-09 2.5059101787473095e-09\n", + "\n", + "\n", + "Number of objective function evaluations = 9\n", + "Number of objective gradient evaluations = 8\n", + "Number of equality constraint evaluations = 9\n", + "Number of inequality constraint evaluations = 0\n", + "Number of equality constraint Jacobian evaluations = 8\n", + "Number of inequality constraint Jacobian evaluations = 0\n", + "Number of Lagrangian Hessian evaluations = 7\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", + "\n", + "====================================================================================\n", + "Unit : fs.flash Time: 0.0\n", + "------------------------------------------------------------------------------------\n", + " Unit Performance\n", + "\n", + " Variables: \n", + "\n", + " Key : Value : Units : Fixed : Bounds\n", + " Heat Duty : 4059.3 : watt : False : (None, None)\n", + " Pressure Change : 0.0000 : pascal : True : (None, None)\n", + "\n", + "------------------------------------------------------------------------------------\n", + " Stream Table\n", + " Units Inlet Vapor Outlet Liquid Outlet\n", + " flow_mol mole / second 1.0000 0.51773 0.48227 \n", + " mole_frac_comp benzene dimensionless 0.50000 0.60690 0.38524 \n", + " mole_frac_comp toluene dimensionless 0.50000 0.39310 0.61476 \n", + " temperature kelvin 368.00 368.85 368.85 \n", + " pressure pascal 1.0132e+05 1.0132e+05 1.0132e+05 \n", + "====================================================================================\n" + ] + } + ], + "execution_count": 45 }, { - "cell_type": "code", - "execution_count": null, "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T17:05:05.768445Z", + "start_time": "2025-06-06T17:05:05.378930Z" + }, "tags": [ "solution" ] }, - "outputs": [], + "cell_type": "code", "source": [ "# re-initialize the model - this may or may not be required depending on current state but safe to initialize\n", "m.fs.flash.heat_duty.fix(0)\n", @@ -868,7 +1931,102 @@ "\n", "# Check stream condition\n", "m.fs.flash.report()" - ] + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "WARNING: model contains export suffix 'scaling_factor' that contains 6\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "Ipopt 3.13.2: \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...: 137\n", + "Number of nonzeros in inequality constraint Jacobian.: 0\n", + "Number of nonzeros in Lagrangian Hessian.............: 72\n", + "\n", + "Total number of variables............................: 42\n", + " variables with only lower bounds: 3\n", + " variables with lower and upper bounds: 10\n", + " variables with only upper bounds: 0\n", + "Total number of equality constraints.................: 42\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 3.40e-02 1.00e+00 -1.0 0.00e+00 - 0.00e+00 0.00e+00 0\n", + " 1 0.0000000e+00 1.64e+02 7.01e-02 -1.0 5.15e+03 - 9.87e-01 1.00e+00h 1\n", + " 2 0.0000000e+00 9.59e-02 2.03e-03 -1.0 7.07e+01 - 9.90e-01 1.00e+00h 1\n", + " 3 0.0000000e+00 6.96e-08 2.50e-06 -1.0 4.13e-01 - 9.98e-01 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....: 8.9151738215745240e-11 6.9550878833979368e-08\n", + "Complementarity.........: 0.0000000000000000e+00 0.0000000000000000e+00\n", + "Overall NLP error.......: 8.9151738215745240e-11 6.9550878833979368e-08\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.001\n", + "Total CPU secs in NLP function evaluations = 0.000\n", + "\n", + "EXIT: Optimal Solution Found.\n", + "\n", + "====================================================================================\n", + "Unit : fs.flash Time: 0.0\n", + "------------------------------------------------------------------------------------\n", + " Unit Performance\n", + "\n", + " Variables: \n", + "\n", + " Key : Value : Units : Fixed : Bounds\n", + " Heat Duty : 5083.6 : watt : False : (None, None)\n", + " Pressure Change : 0.0000 : pascal : True : (None, None)\n", + "\n", + "------------------------------------------------------------------------------------\n", + " Stream Table\n", + " Units Inlet Vapor Outlet Liquid Outlet\n", + " flow_mol mole / second 1.0000 0.54833 0.45167 \n", + " mole_frac_comp benzene dimensionless 0.50000 0.60000 0.37860 \n", + " mole_frac_comp toluene dimensionless 0.50000 0.40000 0.62140 \n", + " temperature kelvin 368.00 369.07 369.07 \n", + " pressure pascal 1.0132e+05 1.0132e+05 1.0132e+05 \n", + "====================================================================================\n" + ] + } + ], + "execution_count": 46 } ], "metadata": { @@ -893,4 +2051,4 @@ }, "nbformat": 4, "nbformat_minor": 3 -} +} \ No newline at end of file diff --git a/idaes_examples/notebooks/docs/tut/core/flash_unit_test.ipynb b/idaes_examples/notebooks/docs/tut/core/flash_unit_test.ipynb index 562af431..62609b47 100644 --- a/idaes_examples/notebooks/docs/tut/core/flash_unit_test.ipynb +++ b/idaes_examples/notebooks/docs/tut/core/flash_unit_test.ipynb @@ -2,14 +2,16 @@ "cells": [ { "cell_type": "code", - "execution_count": null, "metadata": { "tags": [ "header", "hide-cell" - ] + ], + "ExecuteTime": { + "end_time": "2025-06-06T16:45:45.923673Z", + "start_time": "2025-06-06T16:45:45.919855Z" + } }, - "outputs": [], "source": [ "###############################################################################\n", "# The Institute for the Design of Advanced Energy Systems Integrated Platform\n", @@ -23,37 +25,46 @@ "# All rights reserved. Please see the files COPYRIGHT.md and LICENSE.md\n", "# for full copyright and license information.\n", "###############################################################################" - ] + ], + "outputs": [], + "execution_count": 1 }, { - "cell_type": "markdown", "metadata": {}, + "cell_type": "markdown", "source": [ - "# Flash Unit Model\n", + "# Flash Unit Model Tutorial\n", "\n", - "Author: Jaffer Ghouse \n", - "Maintainer: Andrew Lee \n", - "Updated: 2023-06-01 \n", + "Author: Jaffer Ghouse
\n", + "Maintainer: Tanner Polley
\n", + "Updated: 2025-06-03\n", "\n", - "In this module, we will familiarize ourselves with the IDAES framework by creating and working with a flowsheet that contains a single flash tank. The flash tank will be used to perform separation of Benzene and Toluene. The inlet specifications for this flash tank are:\n", + "In this module, we will familiarize ourselves with the IDAES framework by creating and working with a flowsheet that contains a single flash tank. The flash tank will be used to perform separation of Benzene and Toluene.\n", "\n", - "Inlet Specifications:\n", - "* Mole fraction (Benzene) = 0.5\n", - "* Mole fraction (Toluene) = 0.5\n", - "* Pressure = 101325 Pa\n", - "* Temperature = 368 K\n", + "The general workflow of setting up an IDAES flowsheet is the following:\n", + "\n", + "- 1 Importing Modules\n", + "- 2 Building a Model\n", + "- 3 Scaling the Model\n", + "- 4 Specifying the Model\n", + "- 5 Initializing the Model\n", + "- 6 Solving the Model\n", + "- 7 Analyzing and Visualizing the Results\n", "\n", - "We will complete the following tasks:\n", - "* Create the model and the IDAES Flowsheet object\n", - "* Import the appropriate property packages\n", - "* Create the flash unit and set the operating conditions\n", - "* Initialize the model and simulate the system\n", - "* Demonstrate analyses on this model through some examples and exercises\n", + "We will complete each of these steps as well as demonstrate analyses on this model through some examples and exercises\n", "\n", "## Key links to documentation\n", "* Main IDAES online documentation page: https://idaes-pse.readthedocs.io/en/stable/\n", - "\n", - "## Create the Model and the IDAES Flowsheet\n", + "* General Workflow: https://idaes-pse.readthedocs.io/en/stable/how_to_guides/workflow/general.html\n", + "* Flash Unit Model Documentation: https://idaes-pse.readthedocs.io/en/stable/reference_guides/model_libraries/generic/unit_models/flash.html\n", + "\n" + ] + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": [ + "## 1 Import Modules\n", "\n", "In the next cell, we will perform the necessary imports to get us started. From `pyomo.environ` (a standard import for the Pyomo package), we are importing `ConcreteModel` (to create the Pyomo model that will contain the IDAES flowsheet) and `SolverFactory` (to create the object we will use to solve the equations). We will also import `Constraint` as we will be adding a constraint to the model later in the module. Lastly, we also import `value` from Pyomo. This is a function that can be used to return the current numerical value for variables and parameters in the model. These are all part of Pyomo.\n", "\n", @@ -66,23 +77,31 @@ ] }, { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:49.094293Z", + "start_time": "2025-06-06T16:45:45.938076Z" + } + }, "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], "source": [ "from pyomo.environ import ConcreteModel, SolverFactory, Constraint, value\n", "from idaes.core import FlowsheetBlock\n", "\n", "# Import idaes logger to set output levels\n", - "import idaes.logger as idaeslog" - ] + "import idaes.logger as idaeslog\n", + "%matplotlib inline" + ], + "outputs": [], + "execution_count": 2 }, { - "cell_type": "markdown", "metadata": {}, + "cell_type": "markdown", "source": [ - "In the next cell, we will create the `ConcreteModel` and the `FlowsheetBlock`, and attach the flowsheet block to the Pyomo model.\n", + "## 2 Create the Model and IDAES Flowsheet\n", + "\n", + "In the next cell, we will create the `ConcreteModel` object often named `m` (which comes from Pyomo) and then connect the `FlowsheetBlock` (which comes from IDAES) to `m`. We ensure `dynamic=False` since this is a steady-state problem. This creates our overall model and adds the flowsheet capabilities that IDAES provides to the Pyomo model.\n", "\n", "
\n", "Inline Exercise:\n", @@ -91,50 +110,64 @@ ] }, { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:49.521103Z", + "start_time": "2025-06-06T16:45:49.517229Z" + } + }, "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], "source": [ "m = ConcreteModel()\n", "m.fs = FlowsheetBlock(dynamic=False)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "At this point, we have a single Pyomo model that contains an (almost) empty flowsheet block.\n", - "\n", - "
\n", - "Inline Exercise:\n", - "Use the pprint method on the model, i.e. m.pprint(), to see what is currently contained in the model.\n", - "
" - ] + ], + "outputs": [], + "execution_count": 3 }, { - "cell_type": "code", - "execution_count": 4, "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:49.577500Z", + "start_time": "2025-06-06T16:45:49.572256Z" + }, "tags": [ "solution" ] }, - "outputs": [], + "cell_type": "code", "source": [ "# Todo: call pprint on the model\n", "m.pprint()" - ] + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1 Block Declarations\n", + " fs : Size=1, Index=None, Active=True\n", + " 1 Set Declarations\n", + " _time : Size=1, Index=None, Ordered=Insertion\n", + " Key : Dimen : Domain : Size : Members\n", + " None : 1 : Any : 1 : {0.0,}\n", + "\n", + " 1 Declarations: _time\n", + "\n", + "1 Declarations: fs\n" + ] + } + ], + "execution_count": 5 }, { - "cell_type": "markdown", "metadata": {}, + "cell_type": "markdown", "source": [ - "## Define Properties\n", + "### 2.1 Define Properties\n", "\n", "We need to define the property package for our flowsheet. In this example, we will be using the ideal property package that is available as part of the IDAES framework. This property package supports ideal gas - ideal liquid, ideal gas - NRTL, and ideal gas - Wilson models for VLE. More details on this property package can be found at: https://idaes-pse.readthedocs.io/en/stable/reference_guides/model_libraries/generic/property_models/activity_coefficient.html\n", "\n", - "IDAES also supports creation of your own property packages that allow for specification of the fluid using any set of valid state variables (e.g., component molar flows vs overall flow and mole fractions). This flexibility is designed to support advanced modeling needs that may rely on specific formulations. To learn about creating your own property package, please consult the online documentation at: https://idaes-pse.readthedocs.io/en/stable/explanations/components/property_package/index.html and look at examples within IDAES\n", + "IDAES also supports creation of your own property packages that will be shown in a later module.\n", "\n", "For this workshop, we will import the BTX_activity_coeff_VLE property parameter block to be used in the flowsheet. This properties block will be passed to our unit model to define the appropriate state variables and equations for performing thermodynamic calculations.\n", "\n", @@ -145,34 +178,44 @@ ] }, { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:49.649106Z", + "start_time": "2025-06-06T16:45:49.626357Z" + } + }, "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [], "source": [ "from idaes.models.properties.activity_coeff_models.BTX_activity_coeff_VLE import (\n", " BTXParameterBlock,\n", ")" - ] + ], + "outputs": [], + "execution_count": 6 }, { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:49.669359Z", + "start_time": "2025-06-06T16:45:49.657658Z" + } + }, "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [], "source": [ "m.fs.properties = BTXParameterBlock(\n", " valid_phase=(\"Liq\", \"Vap\"), activity_coeff_model=\"Ideal\", state_vars=\"FTPz\"\n", ")" - ] + ], + "outputs": [], + "execution_count": 7 }, { - "cell_type": "markdown", "metadata": {}, + "cell_type": "markdown", "source": [ - "## Adding Flash Unit\n", + "### 2.2 Adding Flash Unit\n", "\n", - "Now that we have the flowsheet and the properties defined, we can create the flash unit and add it to the flowsheet. \n", + "Now that we have the flowsheet and the properties defined, we can create the flash unit and add it to the flowsheet.\n", "\n", "**The Unit Model Library within IDAES includes a large set of common unit operations (see the online documentation for details: https://idaes-pse.readthedocs.io/en/stable/reference_guides/model_libraries/generic/unit_models/index.html**\n", "\n", @@ -187,7 +230,7 @@ "* Pressure changing equipment (compressors, expanders, pumps)\n", "* Feed and Product (source / sink) components\n", "\n", - "In this module, we will import the `Flash` unit model from `idaes.models.unit_models` and create an instance of the flash unit, attaching it to the flowsheet. Each IDAES unit model has several configurable options to customize the model behavior, but also includes defaults for these options. In this example, we will specify that the property package to be used with the Flash is the one we created earlier.\n", + "In this module, we will import the `Flash` unit model from `idaes.models.unit_models` and create an instance of the flash unit, attaching it to the flowsheet. Each IDAES unit model has several configurable options to customize the model behavior, but also includes defaults for these options. In this example, we will specify that the property package to be used with the Flash unit model is the one we created earlier by setting `property_package=m.fs.properties` within the `Flash` method.\n", "\n", "
\n", "Inline Exercise:\n", @@ -196,105 +239,249 @@ ] }, { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:49.749283Z", + "start_time": "2025-06-06T16:45:49.678730Z" + } + }, "cell_type": "code", - "execution_count": 7, - "metadata": {}, + "source": "from idaes.models.unit_models import Flash", "outputs": [], - "source": [ - "from idaes.models.unit_models import Flash" - ] + "execution_count": 8 }, { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:49.772441Z", + "start_time": "2025-06-06T16:45:49.758167Z" + } + }, "cell_type": "code", - "execution_count": 8, - "metadata": {}, + "source": "m.fs.flash = Flash(property_package=m.fs.properties)", "outputs": [], - "source": [ - "m.fs.flash = Flash(property_package=m.fs.properties)" - ] + "execution_count": 9 }, { + "metadata": {}, "cell_type": "markdown", + "source": "At this point, we have created a flowsheet and a properties block. We have also created a flash unit and added it to the flowsheet. Under the hood, IDAES has created the required state variables and model equations. Everything is open. You can see these variables and equations by calling the Pyomo method `pprint` on the model, flowsheet, or flash tank objects. Note that this output is very exhaustive, and is not intended to provide any summary information about the model, but rather a complete picture of all of the variables and equations in the model." + }, + { "metadata": {}, + "cell_type": "markdown", "source": [ - "At this point, we have created a flowsheet and a properties block. We have also created a flash unit and added it to the flowsheet. Under the hood, IDAES has created the required state variables and model equations. Everything is open. You can see these variables and equations by calling the Pyomo method `pprint` on the model, flowsheet, or flash tank objects. Note that this output is very exhaustive, and is not intended to provide any summary information about the model, but rather a complete picture of all of the variables and equations in the model." + "## 3 Scaling the Model\n", + "\n", + "Now that the model is built, with properties set and the unit model created and added to the flowsheet, the next step is to scale the model. Ensuring that a model is well scaled is important for increasing the efficiency and reliability of solvers, and users should consider model scaling as an integral part of the modeling process. IDAES provides a number of tool for assisting users with scaling their models, and details on these can be found at https://idaes-pse.readthedocs.io/en/stable/reference_guides/scaling/scaling.html#scaling-toolbox\n", + "\n", + "There are currently two primary methods in scaling the model: manual scaling of each relevant component, or utilizing the AutoScaler Class. The more careful and risk-free method of manually scaling each component is the recommended method for maximum control and assurance that the model will be well-scaled. This comes with the drawback of being more meticulous while the AutoScaler is much simpler to use since it scaled the whole model all at once, it is less precise by lacking direct control over the scaling factor for each component and relying on scaling factors to be estimated. Both methods will be shown below" ] }, { - "cell_type": "markdown", "metadata": {}, + "cell_type": "markdown", "source": [ - "## Set Operating Conditions\n", + "### 3.1 Manual Scaling\n", + "The `set_scaling_factor` function is imported from `idaes.core.scaling.util` and is called with and used on each relevant component that needs to be well scaled. The component is the first argument and its scaling factor is the second argument.\n", "\n", - "Now that we have created our unit model, we can specify the necessary operating conditions. It is often very useful to determine the degrees of freedom before we specify any conditions.\n", + "
\n", + "Inline Exercise:\n", + "Execute the following two cells to import the `set_scaling_factor` and set the scaling factor for both temperature and pressure\n", + "
\n", "\n", - "The `idaes.core.util.model_statistics` package has a function `degrees_of_freedom`. To see how to use this function, we can make use of the Python function `help(func)`. This function prints the appropriate documentation string for the function.\n", + "Both `temperature` and `pressure` can be found at `m.fs.flash.inlet`." + ] + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:49.795252Z", + "start_time": "2025-06-06T16:45:49.792075Z" + } + }, + "cell_type": "code", + "source": "from idaes.core.scaling.util import set_scaling_factor", + "outputs": [], + "execution_count": 11 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:49.809330Z", + "start_time": "2025-06-06T16:45:49.806174Z" + } + }, + "cell_type": "code", + "source": [ + "set_scaling_factor(m.fs.flash.inlet.temperature, 300)\n", + "set_scaling_factor(m.fs.flash.inlet.pressure, 1e6)" + ], + "outputs": [], + "execution_count": 12 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": [ + "### 3.2 Scaling with AutoScaler\n", + "The `AutoScaler` class is imported from `idaes.core.scaling.autoscaling` and an instance of the class is created. This instance contains the method `scale_model` which is used to scale the whole model at once. This can be a useful option but is generally more risky than manually scaling the model components since it has less direct control and specification.\n", "\n", "
\n", "Inline Exercise:\n", - "Import the degrees_of_freedom function and print the help for the function by calling the Python help function.\n", + "Execute the following two cells to import the `AutoScaler` class and create an autoscaler instance that scaled the whole model at once\n", "
" ] }, { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:49.826276Z", + "start_time": "2025-06-06T16:45:49.823030Z" + } + }, "cell_type": "code", - "execution_count": 10, + "source": "from idaes.core.scaling.autoscaling import AutoScaler", + "outputs": [], + "execution_count": 13 + }, + { "metadata": { - "tags": [ - "solution" - ] + "ExecuteTime": { + "end_time": "2025-06-06T16:45:50.217776Z", + "start_time": "2025-06-06T16:45:49.836920Z" + } }, + "cell_type": "code", + "source": [ + "autoscaler = AutoScaler()\n", + "autoscaler.scale_model(m)" + ], "outputs": [], + "execution_count": 14 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "" + }, + { + "metadata": {}, + "cell_type": "markdown", "source": [ - "# Todo: import the degrees_of_freedom function from the idaes.core.util.model_statistics package\n", - "from idaes.core.util.model_statistics import degrees_of_freedom\n", + "## 4 Set Operating Conditions\n", "\n", - "# Todo: Call the python help on the degrees_of_freedom function\n", - "help(degrees_of_freedom)" + "Now that we have created our unit model and scaled it, we can specify the necessary operating conditions. The inlet specifications for this flash tank are:\n", + "\n", + "Inlet Specifications:\n", + "* Mole fraction (Benzene) = 0.5\n", + "* Mole fraction (Toluene) = 0.5\n", + "* Pressure = 101325 Pa\n", + "* Temperature = 368 K\n", + "\n", + "\n" ] }, { - "cell_type": "markdown", "metadata": {}, + "cell_type": "markdown", "source": [ - "
\n", - "Inline Exercise:\n", - "Now print the degrees of freedom for your model. The result should be 7.\n", - "
" + "### 4.1 Degrees of Freedom\n", + "\n", + "It is often very useful to first determine the degrees of freedom before we specify any conditions.\n", + "\n", + "The `idaes.core.util.model_statistics` package has a function `degrees_of_freedom`. To see how to use this function, we can make use of the Python function `help(func)`. This function prints the appropriate documentation string for the function.\n" ] }, { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:50.240605Z", + "start_time": "2025-06-06T16:45:50.237594Z" + }, + "tags": [ + "solution" + ] + }, "cell_type": "code", - "execution_count": 12, + "source": [ + "# Todo: import the degrees_of_freedom function from the idaes.core.util.model_statistics package\n", + "from idaes.core.util.model_statistics import degrees_of_freedom\n", + "\n", + "# Todo: Call the python help on the degrees_of_freedom function\n", + "help(degrees_of_freedom)" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Help on function degrees_of_freedom in module idaes.core.util.model_statistics:\n", + "\n", + "degrees_of_freedom(block)\n", + " Method to return the degrees of freedom of a model.\n", + "\n", + " Args:\n", + " block : model to be studied\n", + "\n", + " Returns:\n", + " Number of degrees of freedom in block.\n", + "\n" + ] + } + ], + "execution_count": 16 + }, + { "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:50.297399Z", + "start_time": "2025-06-06T16:45:50.291352Z" + }, "tags": [ "solution" ] }, - "outputs": [], + "cell_type": "code", "source": [ "# Todo: print the degrees of freedom for your model\n", "print(\"Degrees of Freedom =\", degrees_of_freedom(m))" - ] + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Degrees of Freedom = 7\n" + ] + } + ], + "execution_count": 18 }, { - "cell_type": "code", - "execution_count": 13, "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:50.334804Z", + "start_time": "2025-06-06T16:45:50.327991Z" + }, "tags": [ "testing" ] }, - "outputs": [], + "cell_type": "code", "source": [ "# Check the degrees of freedom\n", "assert degrees_of_freedom(m) == 7" - ] + ], + "outputs": [], + "execution_count": 19 }, { - "cell_type": "markdown", "metadata": {}, + "cell_type": "markdown", "source": [ + "### 4.2 Specify Inlet Conditions\n", + "\n", "To satisfy our degrees of freedom, we will first specify the inlet conditions. We can specify these values through the `inlet` port of the flash unit.\n", "\n", "**To see the list of naming conventions for variables within the IDAES framework, consult the online documentation at: https://idaes-pse.readthedocs.io/en/stable/explanations/conventions.html#standard-naming-format**\n", @@ -331,22 +518,20 @@ "* inlet mole fraction (toluene) = 0.5 (`mole_frac_comp[0, \"toluene\"]`)\n", "* The heat duty on the flash set to 0 (`heat_duty`)\n", "* The pressure drop across the flash tank set to 0 (`deltaP`)\n", - "\n", - "
\n", - "Inline Exercise:\n", - "Write the code below to specify the inlet conditions and unit specifications described above\n", - "
" + "\n" ] }, { - "cell_type": "code", - "execution_count": 15, "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:50.396800Z", + "start_time": "2025-06-06T16:45:50.392314Z" + }, "tags": [ "solution" ] }, - "outputs": [], + "cell_type": "code", "source": [ "# Todo: Add inlet specifications given above\n", "m.fs.flash.inlet.flow_mol.fix(1)\n", @@ -358,124 +543,242 @@ "# Todo: Add 2 flash unit specifications given above\n", "m.fs.flash.heat_duty.fix(0)\n", "m.fs.flash.deltaP.fix(0)" - ] + ], + "outputs": [], + "execution_count": 21 }, { - "cell_type": "markdown", "metadata": {}, + "cell_type": "markdown", "source": [ - "
\n", - "Inline Exercise:\n", - "Check the degrees of freedom again to ensure that the system is now square. You should see that the degrees of freedom is now 0.\n", - "
" + "Now that all the inlets have been specified, we can check the degrees of freedom again to ensure the system is square and has a degree of freedom of 0\n", + "\n" ] }, { - "cell_type": "code", - "execution_count": 17, "metadata": { "tags": [ "solution" ] }, - "outputs": [], + "cell_type": "code", + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Degrees of Freedom = 0\n" + ] + } + ], + "execution_count": 23, "source": [ "# Todo: print the degrees of freedom for your model\n", "print(\"Degrees of Freedom =\", degrees_of_freedom(m))" ] }, { - "cell_type": "code", - "execution_count": 18, "metadata": { "tags": [ "testing" ] }, + "cell_type": "code", "outputs": [], + "execution_count": 24, "source": [ "# Check the degrees of freedom\n", "assert degrees_of_freedom(m) == 0" ] }, { - "cell_type": "markdown", "metadata": {}, + "cell_type": "markdown", "source": [ - "## Initializing the Model\n", + "## 5 Initializing the Model\n", "\n", - "IDAES includes pre-written initialization routines for all unit models. You can call this initialize method on the units. In the next module, we will demonstrate the use of a sequential modular solve cycle to initialize flowsheets.\n", + "Now that all building steps are complete, the last step before solving the model is to initialize the model, or prepping the solve by giving it a good starting point. This is essentially giving the solver an initial guess for the iterative solver to reach convergence and is essential for both a fast and accurate solution. In IDAES, the current standard for initializing the model is by utilizing initializer instances. These initializer instances contain the initialize method that can be applied to any model type. For more information on initializing in IDAES, visit https://idaes-pse.readthedocs.io/en/stable/reference_guides/initialization/index.html.
\n", "\n", - "
\n", - "Inline Exercise:\n", - "Call the initialize method on the flash unit to initialize the model.\n", - "
" + "For this tutorial, we will import the initializer class `BlockTriangularizationInitializer` class from the `default_initializer` method from the flash unit model. This is often the simplest way to obtain a compatible initializer for each unit model, but you can also directly important any initializer needed from this source `idaes.core.initialization`. Each initializer instance contains the `initialize()` method that requires an argument to be initialized and in this case its the flash unit model.\n" ] }, { - "cell_type": "code", - "execution_count": 20, "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:51:40.247234Z", + "start_time": "2025-06-06T16:51:39.730044Z" + }, "tags": [ "solution" ] }, - "outputs": [], + "cell_type": "code", "source": [ "# Todo: initialize the flash unit\n", - "m.fs.flash.initialize(outlvl=idaeslog.INFO)" - ] + "FlashInitializer = m.fs.flash.default_initializer()\n", + "FlashInitializer.initialize(m.fs.flash)" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2025-06-06 10:51:39 [INFO] idaes.init.fs.flash.control_volume.properties_in: Initialization Step 1 optimal - .\n", + "2025-06-06 10:51:39 [INFO] idaes.init.fs.flash.control_volume.properties_in: Initialization Step 2 optimal - .\n", + "2025-06-06 10:51:39 [INFO] idaes.init.fs.flash.control_volume.properties_in: Initialization Step 3 optimal - .\n", + "2025-06-06 10:51:39 [INFO] idaes.init.fs.flash.control_volume.properties_in: Initialization Step 4 optimal - .\n", + "2025-06-06 10:51:39 [INFO] idaes.init.fs.flash.control_volume.properties_in: Initialization Step 5 optimal - .\n", + "2025-06-06 10:51:40 [INFO] idaes.init.fs.flash.control_volume.properties_out: Initialization Step 1 optimal - .\n", + "2025-06-06 10:51:40 [INFO] idaes.init.fs.flash.control_volume.properties_out: Initialization Step 2 optimal - .\n", + "2025-06-06 10:51:40 [INFO] idaes.init.fs.flash.control_volume.properties_out: Initialization Step 3 optimal - .\n", + "2025-06-06 10:51:40 [INFO] idaes.init.fs.flash.control_volume.properties_out: Initialization Step 4 optimal - .\n", + "2025-06-06 10:51:40 [INFO] idaes.init.fs.flash.control_volume.properties_out: Initialization Step 5 optimal - .\n" + ] + }, + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 33, + "metadata": {}, + "output_type": "execute_result" + } + ], + "execution_count": 33 }, { + "metadata": {}, "cell_type": "markdown", + "source": "Another option for initializing is utilizing the default initializer that is attached to the unit model. Each unit model as a default initializer that is hypothetically the most compatible. It can be called with `m.fs.flash.initialize()`. While this is an option, it is generally preferred to import an initializer object and initialize the model with that to ensure more control over the initialization." + }, + { "metadata": {}, + "cell_type": "markdown", "source": [ - "Now that the model has been defined and initialized, we can solve the model.\n", + "## 6 Solving the Model\n", "\n", - "
\n", - "Inline Exercise:\n", - "Using the notation described in the previous model, create an instance of the \"ipopt\" solver and use it to solve the model. Set the tee option to True to see the log output.\n", - "
" + "Now that the model has been defined and initialized, we can solve the model.\n", + "\n" ] }, { - "cell_type": "code", - "execution_count": 22, "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T17:04:32.439966Z", + "start_time": "2025-06-06T17:04:32.396984Z" + }, "tags": [ "solution" ] }, - "outputs": [], + "cell_type": "code", "source": [ "# Todo: create the ipopt solver\n", "solver = SolverFactory(\"ipopt\")\n", "\n", "# Todo: solve the model\n", "status = solver.solve(m, tee=True)" - ] + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "Ipopt 3.13.2: \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...: 135\n", + "Number of nonzeros in inequality constraint Jacobian.: 0\n", + "Number of nonzeros in Lagrangian Hessian.............: 72\n", + "\n", + "Total number of variables............................: 41\n", + " variables with only lower bounds: 3\n", + " variables with lower and upper bounds: 10\n", + " variables with only upper bounds: 0\n", + "Total number of equality constraints.................: 41\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 6.22e-05 1.00e+00 -1.0 0.00e+00 - 0.00e+00 0.00e+00 0\n", + " 1 0.0000000e+00 1.46e-11 1.00e-02 -1.0 6.22e-05 - 9.90e-01 1.00e+00h 1\n", + "\n", + "Number of Iterations....: 1\n", + "\n", + " (scaled) (unscaled)\n", + "Objective...............: 0.0000000000000000e+00 0.0000000000000000e+00\n", + "Dual infeasibility......: 0.0000000000000000e+00 0.0000000000000000e+00\n", + "Constraint violation....: 2.0417014662014577e-12 1.4551915228366852e-11\n", + "Complementarity.........: 0.0000000000000000e+00 0.0000000000000000e+00\n", + "Overall NLP error.......: 2.0417014662014577e-12 1.4551915228366852e-11\n", + "\n", + "\n", + "Number of objective function evaluations = 2\n", + "Number of objective gradient evaluations = 2\n", + "Number of equality constraint evaluations = 2\n", + "Number of inequality constraint evaluations = 0\n", + "Number of equality constraint Jacobian evaluations = 2\n", + "Number of inequality constraint Jacobian evaluations = 0\n", + "Number of Lagrangian Hessian evaluations = 1\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" + ] + } + ], + "execution_count": 37 }, { - "cell_type": "code", - "execution_count": 23, "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T17:04:32.975773Z", + "start_time": "2025-06-06T17:04:32.972754Z" + }, "tags": [ "testing" ] }, - "outputs": [], + "cell_type": "code", "source": [ - "# Check for optimal solution\n", + "# Check for an optimal solution\n", "from pyomo.environ import TerminationCondition\n", "\n", "assert status.solver.termination_condition == TerminationCondition.optimal" - ] + ], + "outputs": [], + "execution_count": 38 }, { - "cell_type": "markdown", "metadata": {}, + "cell_type": "markdown", "source": [ - "## Viewing the Results\n", + "## 7 Viewing the Results\n", "\n", "Once a model is solved, the values returned by the solver are loaded into the model object itself. We can access the value of any variable in the model with the `value` function. For example:\n", "```python\n", @@ -495,10 +798,13 @@ ] }, { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T17:04:35.556815Z", + "start_time": "2025-06-06T17:04:35.551070Z" + } + }, "cell_type": "code", - "execution_count": 24, - "metadata": {}, - "outputs": [], "source": [ "# Print the pressure of the flash vapor outlet\n", "print(\"Pressure =\", value(m.fs.flash.vap_outlet.pressure[0]))\n", @@ -508,38 +814,95 @@ "# Call display on vap_outlet and liq_outlet of the flash\n", "m.fs.flash.vap_outlet.display()\n", "m.fs.flash.liq_outlet.display()" - ] + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Pressure = 101325.0\n", + "\n", + "Output from display:\n", + "vap_outlet : Size=1\n", + " Key : Name : Value\n", + " None : flow_mol : {0.0: 0.3961181748774193}\n", + " : mole_frac_comp : {(0.0, 'benzene'): 0.633976648508129, (0.0, 'toluene'): 0.366023351491871}\n", + " : pressure : {0.0: 101325.0}\n", + " : temperature : {0.0: 368.0}\n", + "liq_outlet : Size=1\n", + " Key : Name : Value\n", + " None : flow_mol : {0.0: 0.6038818251225807}\n", + " : mole_frac_comp : {(0.0, 'benzene'): 0.41211759772293044, (0.0, 'toluene'): 0.5878824022770694}\n", + " : pressure : {0.0: 101325.0}\n", + " : temperature : {0.0: 368.0}\n" + ] + } + ], + "execution_count": 39 }, { - "cell_type": "markdown", "metadata": {}, + "cell_type": "markdown", "source": [ "The output from `display` is quite exhaustive and not really intended to provide quick summary information. Because Pyomo is built on Python, there are opportunities to format the output any way we like. Most IDAES models have a `report` method which provides a summary of the results for the model.\n", "\n", "
\n", "Inline Exercise:\n", - "Execute the cell below which uses the function above to print a summary of the key variables in the flash model, including the inlet, the vapor, and the liquid ports. \n", + "Execute the cell below which uses the function above to print a summary of the key variables in the flash model, including the inlet, the vapor, and the liquid ports.\n", "
" ] }, { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T17:04:38.727731Z", + "start_time": "2025-06-06T17:04:38.712131Z" + } + }, "cell_type": "code", - "execution_count": 25, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.flash.report()" - ] + "source": "m.fs.flash.report()", + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "====================================================================================\n", + "Unit : fs.flash Time: 0.0\n", + "------------------------------------------------------------------------------------\n", + " Unit Performance\n", + "\n", + " Variables: \n", + "\n", + " Key : Value : Units : Fixed : Bounds\n", + " Heat Duty : 0.0000 : watt : True : (None, None)\n", + " Pressure Change : 0.0000 : pascal : True : (None, None)\n", + "\n", + "------------------------------------------------------------------------------------\n", + " Stream Table\n", + " Units Inlet Vapor Outlet Liquid Outlet\n", + " flow_mol mole / second 1.0000 0.39612 0.60388 \n", + " mole_frac_comp benzene dimensionless 0.50000 0.63398 0.41212 \n", + " mole_frac_comp toluene dimensionless 0.50000 0.36602 0.58788 \n", + " temperature kelvin 368.00 368.00 368.00 \n", + " pressure pascal 1.0132e+05 1.0132e+05 1.0132e+05 \n", + "====================================================================================\n" + ] + } + ], + "execution_count": 40 }, { - "cell_type": "code", - "execution_count": 26, "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T17:04:39.229802Z", + "start_time": "2025-06-06T17:04:39.132903Z" + }, "tags": [ "testing" ] }, - "outputs": [], + "cell_type": "code", "source": [ "# Check optimal solution values\n", "import pytest\n", @@ -563,13 +926,15 @@ ")\n", "assert value(m.fs.flash.vap_outlet.temperature[0]) == pytest.approx(368, abs=1e-3)\n", "assert value(m.fs.flash.vap_outlet.pressure[0]) == pytest.approx(101325, abs=1e-3)" - ] + ], + "outputs": [], + "execution_count": 41 }, { - "cell_type": "markdown", "metadata": {}, + "cell_type": "markdown", "source": [ - "## Studying Purity as a Function of Heat Duty\n", + "## Exercise: Studying Purity as a Function of Heat Duty\n", "\n", "Since the entire modeling framework is built upon Python, it includes a complete programming environment for whatever analysis we may want to perform. In this next exercise, we will make use of what we learned in this and the previous module to generate a figure showing some output variables as a function of the heat duty in the flash tank.\n", "\n", @@ -581,38 +946,38 @@ ] }, { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T17:04:43.426680Z", + "start_time": "2025-06-06T17:04:43.423025Z" + } + }, "cell_type": "code", - "execution_count": 27, - "metadata": {}, + "source": "import matplotlib.pyplot as plt", "outputs": [], - "source": [ - "import matplotlib.pyplot as plt" - ] + "execution_count": 42 }, { - "cell_type": "markdown", "metadata": {}, + "cell_type": "markdown", "source": [ "Exercise specifications:\n", "* Generate a figure showing the flash tank heat duty (`m.fs.flash.heat_duty[0]`) vs. the vapor flowrate (`m.fs.flash.vap_outlet.flow_mol[0]`)\n", "* Specify the heat duty from -17000 to 25000 over 50 steps\n", - "\n", - "
\n", - "Inline Exercise:\n", - "Using what you have learned so far, fill in the missing code below to generate the figure specified above. (Hint: import numpy and use the linspace function from the previous module)\n", - "
" + "\n" ] }, { - "cell_type": "code", - "execution_count": 30, "metadata": { - "scrolled": true, + "ExecuteTime": { + "end_time": "2025-06-06T17:04:48.800601Z", + "start_time": "2025-06-06T17:04:46.438264Z" + }, "tags": [ "solution" ] }, - "outputs": [], + "cell_type": "code", "source": [ "# import the solve_successful checking function from workshop tools\n", "from idaes_examples.mod.tut.workshoptools import solve_successful\n", @@ -625,7 +990,7 @@ "V = []\n", "\n", "# re-initialize model\n", - "m.fs.flash.initialize(outlvl=idaeslog.WARNING)\n", + "FlashInitializer.initialize(m.fs.flash)\n", "\n", "# Todo: Write the for loop specification using numpy's linspace\n", "for duty in np.linspace(-17000, 25000, 50):\n", @@ -656,27 +1021,248 @@ "plt.xlabel(\"Heat Duty [J]\")\n", "plt.ylabel(\"Vapor Fraction [-]\")\n", "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "
\n", - "Inline Exercise:\n", - "Repeat the exercise above, but create a figure showing the heat duty vs. the mole fraction of Benzene in the vapor outlet. Remove any unnecessary printing to create cleaner results.\n", - "
" - ] + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2025-06-06 11:04:46 [INFO] idaes.init.fs.flash.control_volume.properties_in: Initialization Step 1 optimal - .\n", + "2025-06-06 11:04:46 [INFO] idaes.init.fs.flash.control_volume.properties_in: Initialization Step 2 optimal - .\n", + "2025-06-06 11:04:46 [INFO] idaes.init.fs.flash.control_volume.properties_in: Initialization Step 3 optimal - .\n", + "2025-06-06 11:04:46 [INFO] idaes.init.fs.flash.control_volume.properties_in: Initialization Step 4 optimal - .\n", + "2025-06-06 11:04:46 [INFO] idaes.init.fs.flash.control_volume.properties_in: Initialization Step 5 optimal - .\n", + "2025-06-06 11:04:46 [INFO] idaes.init.fs.flash.control_volume.properties_out: Initialization Step 1 optimal - .\n", + "2025-06-06 11:04:46 [INFO] idaes.init.fs.flash.control_volume.properties_out: Initialization Step 2 optimal - .\n", + "2025-06-06 11:04:46 [INFO] idaes.init.fs.flash.control_volume.properties_out: Initialization Step 3 optimal - .\n", + "2025-06-06 11:04:46 [INFO] idaes.init.fs.flash.control_volume.properties_out: Initialization Step 4 optimal - .\n", + "2025-06-06 11:04:46 [INFO] idaes.init.fs.flash.control_volume.properties_out: Initialization Step 5 optimal - .\n", + "Simulating with Q = -17000.0\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -16142.857142857143\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -15285.714285714286\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -14428.571428571428\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -13571.428571428572\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -12714.285714285714\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -11857.142857142857\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -11000.0\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -10142.857142857143\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -9285.714285714286\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -8428.57142857143\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -7571.4285714285725\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -6714.285714285714\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -5857.142857142857\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -5000.0\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -4142.857142857143\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -3285.7142857142862\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -2428.5714285714294\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -1571.4285714285725\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -714.2857142857156\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 142.8571428571413\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 1000.0\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 1857.142857142855\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 2714.2857142857138\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 3571.4285714285725\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 4428.5714285714275\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 5285.714285714286\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 6142.857142857141\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 7000.0\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 7857.142857142855\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 8714.285714285714\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 9571.428571428569\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 10428.571428571428\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 11285.714285714286\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 12142.857142857141\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 13000.0\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 13857.142857142855\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 14714.285714285714\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 15571.428571428569\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 16428.571428571428\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 17285.714285714283\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 18142.857142857145\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 19000.0\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 19857.142857142855\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 20714.28571428571\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 21571.428571428572\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 22428.571428571428\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 23285.714285714283\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 24142.857142857145\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 25000.0\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n" + ] + }, + { + "data": { + "text/plain": [ + "
" + ], + "image/png": "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" + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "execution_count": 43 }, { - "cell_type": "code", - "execution_count": null, "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T17:05:02.101331Z", + "start_time": "2025-06-06T17:05:00.157447Z" + }, "tags": [ "solution" ] }, - "outputs": [], + "cell_type": "code", "source": [ "# Todo: generate a figure of heat duty vs. mole fraction of Benzene in the vapor\n", "Q = []\n", @@ -709,33 +1295,250 @@ "plt.xlabel(\"Heat Duty [J]\")\n", "plt.ylabel(\"Vapor Benzene Mole Fraction [-]\")\n", "plt.show()" - ] + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Simulating with Q = -17000.0\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -16142.857142857143\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -15285.714285714286\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -14428.571428571428\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -13571.428571428572\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -12714.285714285714\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -11857.142857142857\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -11000.0\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -10142.857142857143\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -9285.714285714286\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -8428.57142857143\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -7571.4285714285725\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -6714.285714285714\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -5857.142857142857\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -5000.0\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -4142.857142857143\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -3285.7142857142862\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -2428.5714285714294\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -1571.4285714285725\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -714.2857142857156\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 142.8571428571413\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 1000.0\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 1857.142857142855\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 2714.2857142857138\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 3571.4285714285725\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 4428.5714285714275\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 5285.714285714286\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 6142.857142857141\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 7000.0\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 7857.142857142855\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 8714.285714285714\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 9571.428571428569\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 10428.571428571428\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 11285.714285714286\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 12142.857142857141\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 13000.0\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 13857.142857142855\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 14714.285714285714\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 15571.428571428569\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 16428.571428571428\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 17285.714285714283\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 18142.857142857145\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 19000.0\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 19857.142857142855\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 20714.28571428571\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 21571.428571428572\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 22428.571428571428\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 23285.714285714283\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 24142.857142857145\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 25000.0\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n" + ] + }, + { + "data": { + "text/plain": [ + "
" + ], + "image/png": "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" + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "execution_count": 44 }, { - "cell_type": "markdown", "metadata": {}, + "cell_type": "markdown", "source": [ "Recall that the IDAES framework is an equation-oriented modeling environment. This means that we can specify \"design\" problems natively. That is, there is no need to have our specifications on the inlet alone. We can put specifications on the outlet as long as we retain a well-posed, square system of equations.\n", "\n", "For example, we can remove the specification on heat duty and instead specify that we want the mole fraction of Benzene in the vapor outlet to be equal to 0.6. The mole fraction is not a native variable in the property block, so we cannot use \"fix\". We can, however, add a constraint to the model.\n", "\n", "Note that we have been executing a number of solves on the problem, and may not be sure of the current state. To help convergence, therefore, we will first call initialize, then add the new constraint and solve the problem. Note that the reference for the mole fraction of Benzene in the vapor outlet is `m.fs.flash.vap_outlet.mole_frac_comp[0, \"benzene\"]`.\n", - "\n", - "
\n", - "Inline Exercise:\n", - "Fill in the missing code below and add a constraint on the mole fraction of Benzene (to a value of 0.6) to find the required heat duty.\n", - "
\n" + "\n" ] }, { - "cell_type": "code", - "execution_count": null, "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T17:05:05.768445Z", + "start_time": "2025-06-06T17:05:05.378930Z" + }, "tags": [ "solution" ] }, - "outputs": [], + "cell_type": "code", "source": [ "# re-initialize the model - this may or may not be required depending on current state but safe to initialize\n", "m.fs.flash.heat_duty.fix(0)\n", @@ -754,17 +1557,110 @@ "\n", "# Check stream condition\n", "m.fs.flash.report()" - ] + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "WARNING: model contains export suffix 'scaling_factor' that contains 6\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "Ipopt 3.13.2: \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...: 137\n", + "Number of nonzeros in inequality constraint Jacobian.: 0\n", + "Number of nonzeros in Lagrangian Hessian.............: 72\n", + "\n", + "Total number of variables............................: 42\n", + " variables with only lower bounds: 3\n", + " variables with lower and upper bounds: 10\n", + " variables with only upper bounds: 0\n", + "Total number of equality constraints.................: 42\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 3.40e-02 1.00e+00 -1.0 0.00e+00 - 0.00e+00 0.00e+00 0\n", + " 1 0.0000000e+00 1.64e+02 7.01e-02 -1.0 5.15e+03 - 9.87e-01 1.00e+00h 1\n", + " 2 0.0000000e+00 9.59e-02 2.03e-03 -1.0 7.07e+01 - 9.90e-01 1.00e+00h 1\n", + " 3 0.0000000e+00 6.96e-08 2.50e-06 -1.0 4.13e-01 - 9.98e-01 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....: 8.9151738215745240e-11 6.9550878833979368e-08\n", + "Complementarity.........: 0.0000000000000000e+00 0.0000000000000000e+00\n", + "Overall NLP error.......: 8.9151738215745240e-11 6.9550878833979368e-08\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.001\n", + "Total CPU secs in NLP function evaluations = 0.000\n", + "\n", + "EXIT: Optimal Solution Found.\n", + "\n", + "====================================================================================\n", + "Unit : fs.flash Time: 0.0\n", + "------------------------------------------------------------------------------------\n", + " Unit Performance\n", + "\n", + " Variables: \n", + "\n", + " Key : Value : Units : Fixed : Bounds\n", + " Heat Duty : 5083.6 : watt : False : (None, None)\n", + " Pressure Change : 0.0000 : pascal : True : (None, None)\n", + "\n", + "------------------------------------------------------------------------------------\n", + " Stream Table\n", + " Units Inlet Vapor Outlet Liquid Outlet\n", + " flow_mol mole / second 1.0000 0.54833 0.45167 \n", + " mole_frac_comp benzene dimensionless 0.50000 0.60000 0.37860 \n", + " mole_frac_comp toluene dimensionless 0.50000 0.40000 0.62140 \n", + " temperature kelvin 368.00 369.07 369.07 \n", + " pressure pascal 1.0132e+05 1.0132e+05 1.0132e+05 \n", + "====================================================================================\n" + ] + } + ], + "execution_count": 46 }, { - "cell_type": "code", - "execution_count": null, "metadata": { "tags": [ "testing" ] }, - "outputs": [], + "cell_type": "code", "source": [ "# Check for solver status\n", "assert status.solver.termination_condition == TerminationCondition.optimal\n", @@ -789,7 +1685,9 @@ ")\n", "assert value(m.fs.flash.vap_outlet.temperature[0]) == pytest.approx(369.07, abs=1e-2)\n", "assert value(m.fs.flash.vap_outlet.pressure[0]) == pytest.approx(101325, abs=1e-3)" - ] + ], + "outputs": [], + "execution_count": null } ], "metadata": { @@ -814,4 +1712,4 @@ }, "nbformat": 4, "nbformat_minor": 3 -} +} \ No newline at end of file diff --git a/idaes_examples/notebooks/docs/tut/core/flash_unit_usr.ipynb b/idaes_examples/notebooks/docs/tut/core/flash_unit_usr.ipynb index 6b4b3752..0e823872 100644 --- a/idaes_examples/notebooks/docs/tut/core/flash_unit_usr.ipynb +++ b/idaes_examples/notebooks/docs/tut/core/flash_unit_usr.ipynb @@ -2,14 +2,16 @@ "cells": [ { "cell_type": "code", - "execution_count": null, "metadata": { "tags": [ "header", "hide-cell" - ] + ], + "ExecuteTime": { + "end_time": "2025-06-06T16:45:45.923673Z", + "start_time": "2025-06-06T16:45:45.919855Z" + } }, - "outputs": [], "source": [ "###############################################################################\n", "# The Institute for the Design of Advanced Energy Systems Integrated Platform\n", @@ -23,37 +25,46 @@ "# All rights reserved. Please see the files COPYRIGHT.md and LICENSE.md\n", "# for full copyright and license information.\n", "###############################################################################" - ] + ], + "outputs": [], + "execution_count": 1 }, { - "cell_type": "markdown", "metadata": {}, + "cell_type": "markdown", "source": [ - "# Flash Unit Model\n", + "# Flash Unit Model Tutorial\n", "\n", - "Author: Jaffer Ghouse \n", - "Maintainer: Andrew Lee \n", - "Updated: 2023-06-01 \n", + "Author: Jaffer Ghouse
\n", + "Maintainer: Tanner Polley
\n", + "Updated: 2025-06-03\n", "\n", - "In this module, we will familiarize ourselves with the IDAES framework by creating and working with a flowsheet that contains a single flash tank. The flash tank will be used to perform separation of Benzene and Toluene. The inlet specifications for this flash tank are:\n", + "In this module, we will familiarize ourselves with the IDAES framework by creating and working with a flowsheet that contains a single flash tank. The flash tank will be used to perform separation of Benzene and Toluene.\n", "\n", - "Inlet Specifications:\n", - "* Mole fraction (Benzene) = 0.5\n", - "* Mole fraction (Toluene) = 0.5\n", - "* Pressure = 101325 Pa\n", - "* Temperature = 368 K\n", + "The general workflow of setting up an IDAES flowsheet is the following:\n", + "\n", + "- 1 Importing Modules\n", + "- 2 Building a Model\n", + "- 3 Scaling the Model\n", + "- 4 Specifying the Model\n", + "- 5 Initializing the Model\n", + "- 6 Solving the Model\n", + "- 7 Analyzing and Visualizing the Results\n", "\n", - "We will complete the following tasks:\n", - "* Create the model and the IDAES Flowsheet object\n", - "* Import the appropriate property packages\n", - "* Create the flash unit and set the operating conditions\n", - "* Initialize the model and simulate the system\n", - "* Demonstrate analyses on this model through some examples and exercises\n", + "We will complete each of these steps as well as demonstrate analyses on this model through some examples and exercises\n", "\n", "## Key links to documentation\n", "* Main IDAES online documentation page: https://idaes-pse.readthedocs.io/en/stable/\n", - "\n", - "## Create the Model and the IDAES Flowsheet\n", + "* General Workflow: https://idaes-pse.readthedocs.io/en/stable/how_to_guides/workflow/general.html\n", + "* Flash Unit Model Documentation: https://idaes-pse.readthedocs.io/en/stable/reference_guides/model_libraries/generic/unit_models/flash.html\n", + "\n" + ] + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": [ + "## 1 Import Modules\n", "\n", "In the next cell, we will perform the necessary imports to get us started. From `pyomo.environ` (a standard import for the Pyomo package), we are importing `ConcreteModel` (to create the Pyomo model that will contain the IDAES flowsheet) and `SolverFactory` (to create the object we will use to solve the equations). We will also import `Constraint` as we will be adding a constraint to the model later in the module. Lastly, we also import `value` from Pyomo. This is a function that can be used to return the current numerical value for variables and parameters in the model. These are all part of Pyomo.\n", "\n", @@ -66,23 +77,31 @@ ] }, { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:49.094293Z", + "start_time": "2025-06-06T16:45:45.938076Z" + } + }, "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], "source": [ "from pyomo.environ import ConcreteModel, SolverFactory, Constraint, value\n", "from idaes.core import FlowsheetBlock\n", "\n", "# Import idaes logger to set output levels\n", - "import idaes.logger as idaeslog" - ] + "import idaes.logger as idaeslog\n", + "%matplotlib inline" + ], + "outputs": [], + "execution_count": 2 }, { - "cell_type": "markdown", "metadata": {}, + "cell_type": "markdown", "source": [ - "In the next cell, we will create the `ConcreteModel` and the `FlowsheetBlock`, and attach the flowsheet block to the Pyomo model.\n", + "## 2 Create the Model and IDAES Flowsheet\n", + "\n", + "In the next cell, we will create the `ConcreteModel` object often named `m` (which comes from Pyomo) and then connect the `FlowsheetBlock` (which comes from IDAES) to `m`. We ensure `dynamic=False` since this is a steady-state problem. This creates our overall model and adds the flowsheet capabilities that IDAES provides to the Pyomo model.\n", "\n", "
\n", "Inline Exercise:\n", @@ -91,20 +110,29 @@ ] }, { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:49.521103Z", + "start_time": "2025-06-06T16:45:49.517229Z" + } + }, "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], "source": [ "m = ConcreteModel()\n", "m.fs = FlowsheetBlock(dynamic=False)" - ] + ], + "outputs": [], + "execution_count": 3 }, { + "metadata": { + "tags": [ + "exercise" + ] + }, "cell_type": "markdown", - "metadata": {}, "source": [ - "At this point, we have a single Pyomo model that contains an (almost) empty flowsheet block.\n", + "At this point, we have a single Pyomo model that contains an (almost) empty flowsheet block. Lets use the `m.pprint()` to investigate the current contents of the model.\n", "\n", "
\n", "Inline Exercise:\n", @@ -113,41 +141,64 @@ ] }, { - "cell_type": "code", - "execution_count": 3, "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:49.531374Z", + "start_time": "2025-06-06T16:45:49.527521Z" + }, "tags": [ "exercise" ] }, + "cell_type": "code", + "source": "# Todo: call pprint on the model", "outputs": [], - "source": [ - "# Todo: call pprint on the model" - ] + "execution_count": 4 }, { - "cell_type": "code", - "execution_count": 4, "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:49.577500Z", + "start_time": "2025-06-06T16:45:49.572256Z" + }, "tags": [ "solution" ] }, - "outputs": [], + "cell_type": "code", "source": [ "# Todo: call pprint on the model\n", "m.pprint()" - ] + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1 Block Declarations\n", + " fs : Size=1, Index=None, Active=True\n", + " 1 Set Declarations\n", + " _time : Size=1, Index=None, Ordered=Insertion\n", + " Key : Dimen : Domain : Size : Members\n", + " None : 1 : Any : 1 : {0.0,}\n", + "\n", + " 1 Declarations: _time\n", + "\n", + "1 Declarations: fs\n" + ] + } + ], + "execution_count": 5 }, { - "cell_type": "markdown", "metadata": {}, + "cell_type": "markdown", "source": [ - "## Define Properties\n", + "### 2.1 Define Properties\n", "\n", "We need to define the property package for our flowsheet. In this example, we will be using the ideal property package that is available as part of the IDAES framework. This property package supports ideal gas - ideal liquid, ideal gas - NRTL, and ideal gas - Wilson models for VLE. More details on this property package can be found at: https://idaes-pse.readthedocs.io/en/stable/reference_guides/model_libraries/generic/property_models/activity_coefficient.html\n", "\n", - "IDAES also supports creation of your own property packages that allow for specification of the fluid using any set of valid state variables (e.g., component molar flows vs overall flow and mole fractions). This flexibility is designed to support advanced modeling needs that may rely on specific formulations. To learn about creating your own property package, please consult the online documentation at: https://idaes-pse.readthedocs.io/en/stable/explanations/components/property_package/index.html and look at examples within IDAES\n", + "IDAES also supports creation of your own property packages that will be shown in a later module.\n", "\n", "For this workshop, we will import the BTX_activity_coeff_VLE property parameter block to be used in the flowsheet. This properties block will be passed to our unit model to define the appropriate state variables and equations for performing thermodynamic calculations.\n", "\n", @@ -158,34 +209,44 @@ ] }, { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:49.649106Z", + "start_time": "2025-06-06T16:45:49.626357Z" + } + }, "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [], "source": [ "from idaes.models.properties.activity_coeff_models.BTX_activity_coeff_VLE import (\n", " BTXParameterBlock,\n", ")" - ] + ], + "outputs": [], + "execution_count": 6 }, { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:49.669359Z", + "start_time": "2025-06-06T16:45:49.657658Z" + } + }, "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [], "source": [ "m.fs.properties = BTXParameterBlock(\n", " valid_phase=(\"Liq\", \"Vap\"), activity_coeff_model=\"Ideal\", state_vars=\"FTPz\"\n", ")" - ] + ], + "outputs": [], + "execution_count": 7 }, { - "cell_type": "markdown", "metadata": {}, + "cell_type": "markdown", "source": [ - "## Adding Flash Unit\n", + "### 2.2 Adding Flash Unit\n", "\n", - "Now that we have the flowsheet and the properties defined, we can create the flash unit and add it to the flowsheet. \n", + "Now that we have the flowsheet and the properties defined, we can create the flash unit and add it to the flowsheet.\n", "\n", "**The Unit Model Library within IDAES includes a large set of common unit operations (see the online documentation for details: https://idaes-pse.readthedocs.io/en/stable/reference_guides/model_libraries/generic/unit_models/index.html**\n", "\n", @@ -200,7 +261,7 @@ "* Pressure changing equipment (compressors, expanders, pumps)\n", "* Feed and Product (source / sink) components\n", "\n", - "In this module, we will import the `Flash` unit model from `idaes.models.unit_models` and create an instance of the flash unit, attaching it to the flowsheet. Each IDAES unit model has several configurable options to customize the model behavior, but also includes defaults for these options. In this example, we will specify that the property package to be used with the Flash is the one we created earlier.\n", + "In this module, we will import the `Flash` unit model from `idaes.models.unit_models` and create an instance of the flash unit, attaching it to the flowsheet. Each IDAES unit model has several configurable options to customize the model behavior, but also includes defaults for these options. In this example, we will specify that the property package to be used with the Flash unit model is the one we created earlier by setting `property_package=m.fs.properties` within the `Flash` method.\n", "\n", "
\n", "Inline Exercise:\n", @@ -209,40 +270,183 @@ ] }, { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:49.749283Z", + "start_time": "2025-06-06T16:45:49.678730Z" + } + }, "cell_type": "code", - "execution_count": 7, - "metadata": {}, + "source": "from idaes.models.unit_models import Flash", "outputs": [], - "source": [ - "from idaes.models.unit_models import Flash" - ] + "execution_count": 8 }, { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:49.772441Z", + "start_time": "2025-06-06T16:45:49.758167Z" + } + }, "cell_type": "code", - "execution_count": 8, + "source": "m.fs.flash = Flash(property_package=m.fs.properties)", + "outputs": [], + "execution_count": 9 + }, + { "metadata": {}, + "cell_type": "markdown", + "source": "At this point, we have created a flowsheet and a properties block. We have also created a flash unit and added it to the flowsheet. Under the hood, IDAES has created the required state variables and model equations. Everything is open. You can see these variables and equations by calling the Pyomo method `pprint` on the model, flowsheet, or flash tank objects. Note that this output is very exhaustive, and is not intended to provide any summary information about the model, but rather a complete picture of all of the variables and equations in the model." + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:49.783706Z", + "start_time": "2025-06-06T16:45:49.781688Z" + }, + "tags": [ + "noauto" + ] + }, + "cell_type": "code", + "source": "m.pprint()", "outputs": [], + "execution_count": 10 + }, + { + "metadata": {}, + "cell_type": "markdown", "source": [ - "m.fs.flash = Flash(property_package=m.fs.properties)" + "## 3 Scaling the Model\n", + "\n", + "Now that the model is built, with properties set and the unit model created and added to the flowsheet, the next step is to scale the model. Ensuring that a model is well scaled is important for increasing the efficiency and reliability of solvers, and users should consider model scaling as an integral part of the modeling process. IDAES provides a number of tool for assisting users with scaling their models, and details on these can be found at https://idaes-pse.readthedocs.io/en/stable/reference_guides/scaling/scaling.html#scaling-toolbox\n", + "\n", + "There are currently two primary methods in scaling the model: manual scaling of each relevant component, or utilizing the AutoScaler Class. The more careful and risk-free method of manually scaling each component is the recommended method for maximum control and assurance that the model will be well-scaled. This comes with the drawback of being more meticulous while the AutoScaler is much simpler to use since it scaled the whole model all at once, it is less precise by lacking direct control over the scaling factor for each component and relying on scaling factors to be estimated. Both methods will be shown below" ] }, { + "metadata": {}, "cell_type": "markdown", + "source": [ + "### 3.1 Manual Scaling\n", + "The `set_scaling_factor` function is imported from `idaes.core.scaling.util` and is called with and used on each relevant component that needs to be well scaled. The component is the first argument and its scaling factor is the second argument.\n", + "\n", + "
\n", + "Inline Exercise:\n", + "Execute the following two cells to import the `set_scaling_factor` and set the scaling factor for both temperature and pressure\n", + "
\n", + "\n", + "Both `temperature` and `pressure` can be found at `m.fs.flash.inlet`." + ] + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:49.795252Z", + "start_time": "2025-06-06T16:45:49.792075Z" + } + }, + "cell_type": "code", + "source": "from idaes.core.scaling.util import set_scaling_factor", + "outputs": [], + "execution_count": 11 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:49.809330Z", + "start_time": "2025-06-06T16:45:49.806174Z" + } + }, + "cell_type": "code", + "source": [ + "set_scaling_factor(m.fs.flash.inlet.temperature, 300)\n", + "set_scaling_factor(m.fs.flash.inlet.pressure, 1e6)" + ], + "outputs": [], + "execution_count": 12 + }, + { "metadata": {}, + "cell_type": "markdown", "source": [ - "At this point, we have created a flowsheet and a properties block. We have also created a flash unit and added it to the flowsheet. Under the hood, IDAES has created the required state variables and model equations. Everything is open. You can see these variables and equations by calling the Pyomo method `pprint` on the model, flowsheet, or flash tank objects. Note that this output is very exhaustive, and is not intended to provide any summary information about the model, but rather a complete picture of all of the variables and equations in the model." + "### 3.2 Scaling with AutoScaler\n", + "The `AutoScaler` class is imported from `idaes.core.scaling.autoscaling` and an instance of the class is created. This instance contains the method `scale_model` which is used to scale the whole model at once. This can be a useful option but is generally more risky than manually scaling the model components since it has less direct control and specification.\n", + "\n", + "
\n", + "Inline Exercise:\n", + "Execute the following two cells to import the `AutoScaler` class and create an autoscaler instance that scaled the whole model at once\n", + "
" ] }, { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:49.826276Z", + "start_time": "2025-06-06T16:45:49.823030Z" + } + }, + "cell_type": "code", + "source": "from idaes.core.scaling.autoscaling import AutoScaler", + "outputs": [], + "execution_count": 13 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:50.217776Z", + "start_time": "2025-06-06T16:45:49.836920Z" + } + }, + "cell_type": "code", + "source": [ + "autoscaler = AutoScaler()\n", + "autoscaler.scale_model(m)" + ], + "outputs": [], + "execution_count": 14 + }, + { + "metadata": {}, "cell_type": "markdown", + "source": "" + }, + { "metadata": {}, + "cell_type": "markdown", "source": [ - "## Set Operating Conditions\n", + "## 4 Set Operating Conditions\n", "\n", - "Now that we have created our unit model, we can specify the necessary operating conditions. It is often very useful to determine the degrees of freedom before we specify any conditions.\n", + "Now that we have created our unit model and scaled it, we can specify the necessary operating conditions. The inlet specifications for this flash tank are:\n", "\n", - "The `idaes.core.util.model_statistics` package has a function `degrees_of_freedom`. To see how to use this function, we can make use of the Python function `help(func)`. This function prints the appropriate documentation string for the function.\n", + "Inlet Specifications:\n", + "* Mole fraction (Benzene) = 0.5\n", + "* Mole fraction (Toluene) = 0.5\n", + "* Pressure = 101325 Pa\n", + "* Temperature = 368 K\n", + "\n", + "\n" + ] + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": [ + "### 4.1 Degrees of Freedom\n", + "\n", + "It is often very useful to first determine the degrees of freedom before we specify any conditions.\n", "\n", + "The `idaes.core.util.model_statistics` package has a function `degrees_of_freedom`. To see how to use this function, we can make use of the Python function `help(func)`. This function prints the appropriate documentation string for the function.\n" + ] + }, + { + "metadata": { + "tags": [ + "exercise" + ] + }, + "cell_type": "markdown", + "source": [ "
\n", "Inline Exercise:\n", "Import the degrees_of_freedom function and print the help for the function by calling the Python help function.\n", @@ -250,41 +454,71 @@ ] }, { - "cell_type": "code", - "execution_count": 9, "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:50.224457Z", + "start_time": "2025-06-06T16:45:50.221905Z" + }, "tags": [ "exercise" ] }, - "outputs": [], + "cell_type": "code", "source": [ "# Todo: import the degrees_of_freedom function from the idaes.core.util.model_statistics package\n", "\n", "\n", "# Todo: Call the python help on the degrees_of_freedom function" - ] + ], + "outputs": [], + "execution_count": 15 }, { - "cell_type": "code", - "execution_count": 10, "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:50.240605Z", + "start_time": "2025-06-06T16:45:50.237594Z" + }, "tags": [ "solution" ] }, - "outputs": [], + "cell_type": "code", "source": [ "# Todo: import the degrees_of_freedom function from the idaes.core.util.model_statistics package\n", "from idaes.core.util.model_statistics import degrees_of_freedom\n", "\n", "# Todo: Call the python help on the degrees_of_freedom function\n", "help(degrees_of_freedom)" - ] + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Help on function degrees_of_freedom in module idaes.core.util.model_statistics:\n", + "\n", + "degrees_of_freedom(block)\n", + " Method to return the degrees of freedom of a model.\n", + "\n", + " Args:\n", + " block : model to be studied\n", + "\n", + " Returns:\n", + " Number of degrees of freedom in block.\n", + "\n" + ] + } + ], + "execution_count": 16 }, { + "metadata": { + "tags": [ + "exercise" + ] + }, "cell_type": "markdown", - "metadata": {}, "source": [ "
\n", "Inline Exercise:\n", @@ -293,36 +527,52 @@ ] }, { - "cell_type": "code", - "execution_count": 11, "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:50.271361Z", + "start_time": "2025-06-06T16:45:50.268763Z" + }, "tags": [ "exercise" ] }, + "cell_type": "code", + "source": "# Todo: print the degrees of freedom for your model", "outputs": [], - "source": [ - "# Todo: print the degrees of freedom for your model" - ] + "execution_count": 17 }, { - "cell_type": "code", - "execution_count": 12, "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:50.297399Z", + "start_time": "2025-06-06T16:45:50.291352Z" + }, "tags": [ "solution" ] }, - "outputs": [], + "cell_type": "code", "source": [ "# Todo: print the degrees of freedom for your model\n", "print(\"Degrees of Freedom =\", degrees_of_freedom(m))" - ] + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Degrees of Freedom = 7\n" + ] + } + ], + "execution_count": 18 }, { - "cell_type": "markdown", "metadata": {}, + "cell_type": "markdown", "source": [ + "### 4.2 Specify Inlet Conditions\n", + "\n", "To satisfy our degrees of freedom, we will first specify the inlet conditions. We can specify these values through the `inlet` port of the flash unit.\n", "\n", "**To see the list of naming conventions for variables within the IDAES framework, consult the online documentation at: https://idaes-pse.readthedocs.io/en/stable/explanations/conventions.html#standard-naming-format**\n", @@ -359,7 +609,17 @@ "* inlet mole fraction (toluene) = 0.5 (`mole_frac_comp[0, \"toluene\"]`)\n", "* The heat duty on the flash set to 0 (`heat_duty`)\n", "* The pressure drop across the flash tank set to 0 (`deltaP`)\n", - "\n", + "\n" + ] + }, + { + "metadata": { + "tags": [ + "exercise" + ] + }, + "cell_type": "markdown", + "source": [ "
\n", "Inline Exercise:\n", "Write the code below to specify the inlet conditions and unit specifications described above\n", @@ -367,30 +627,36 @@ ] }, { - "cell_type": "code", - "execution_count": 14, "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:50.360852Z", + "start_time": "2025-06-06T16:45:50.357341Z" + }, "tags": [ "exercise" ] }, - "outputs": [], + "cell_type": "code", "source": [ "# Todo: Add inlet specifications given above\n", "\n", "\n", "# Todo: Add 2 flash unit specifications given above" - ] + ], + "outputs": [], + "execution_count": 20 }, { - "cell_type": "code", - "execution_count": 15, "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:50.396800Z", + "start_time": "2025-06-06T16:45:50.392314Z" + }, "tags": [ "solution" ] }, - "outputs": [], + "cell_type": "code", "source": [ "# Todo: Add inlet specifications given above\n", "m.fs.flash.inlet.flow_mol.fix(1)\n", @@ -402,11 +668,25 @@ "# Todo: Add 2 flash unit specifications given above\n", "m.fs.flash.heat_duty.fix(0)\n", "m.fs.flash.deltaP.fix(0)" + ], + "outputs": [], + "execution_count": 21 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": [ + "Now that all the inlets have been specified, we can check the degrees of freedom again to ensure the system is square and has a degree of freedom of 0\n", + "\n" ] }, { + "metadata": { + "tags": [ + "exercise" + ] + }, "cell_type": "markdown", - "metadata": {}, "source": [ "
\n", "Inline Exercise:\n", @@ -415,79 +695,147 @@ ] }, { - "cell_type": "code", - "execution_count": 16, "metadata": { "tags": [ "exercise" ] }, + "cell_type": "code", "outputs": [], - "source": [ - "# Todo: print the degrees of freedom for your model" - ] + "execution_count": 22, + "source": "# Todo: print the degrees of freedom for your model" }, { - "cell_type": "code", - "execution_count": 17, "metadata": { "tags": [ "solution" ] }, - "outputs": [], + "cell_type": "code", + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Degrees of Freedom = 0\n" + ] + } + ], + "execution_count": 23, "source": [ "# Todo: print the degrees of freedom for your model\n", "print(\"Degrees of Freedom =\", degrees_of_freedom(m))" ] }, { - "cell_type": "markdown", "metadata": {}, + "cell_type": "markdown", "source": [ - "## Initializing the Model\n", + "## 5 Initializing the Model\n", "\n", - "IDAES includes pre-written initialization routines for all unit models. You can call this initialize method on the units. In the next module, we will demonstrate the use of a sequential modular solve cycle to initialize flowsheets.\n", + "Now that all building steps are complete, the last step before solving the model is to initialize the model, or prepping the solve by giving it a good starting point. This is essentially giving the solver an initial guess for the iterative solver to reach convergence and is essential for both a fast and accurate solution. In IDAES, the current standard for initializing the model is by utilizing initializer instances. These initializer instances contain the initialize method that can be applied to any model type. For more information on initializing in IDAES, visit https://idaes-pse.readthedocs.io/en/stable/reference_guides/initialization/index.html.
\n", "\n", + "For this tutorial, we will import the initializer class `BlockTriangularizationInitializer` class from the `default_initializer` method from the flash unit model. This is often the simplest way to obtain a compatible initializer for each unit model, but you can also directly important any initializer needed from this source `idaes.core.initialization`. Each initializer instance contains the `initialize()` method that requires an argument to be initialized and in this case its the flash unit model.\n" + ] + }, + { + "metadata": { + "tags": [ + "exercise" + ] + }, + "cell_type": "markdown", + "source": [ "
\n", "Inline Exercise:\n", - "Call the initialize method on the flash unit to initialize the model.\n", + "Define the initializer instance, and initialize the flash unit model\n", "
" ] }, { - "cell_type": "code", - "execution_count": 19, "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:45:50.499945Z", + "start_time": "2025-06-06T16:45:50.497439Z" + }, "tags": [ "exercise" ] }, + "cell_type": "code", + "source": "# Todo: initialize the flash unit", "outputs": [], - "source": [ - "# Todo: initialize the flash unit" - ] + "execution_count": 25 }, { - "cell_type": "code", - "execution_count": 20, "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T16:51:40.247234Z", + "start_time": "2025-06-06T16:51:39.730044Z" + }, "tags": [ "solution" ] }, - "outputs": [], + "cell_type": "code", "source": [ "# Todo: initialize the flash unit\n", - "m.fs.flash.initialize(outlvl=idaeslog.INFO)" - ] + "FlashInitializer = m.fs.flash.default_initializer()\n", + "FlashInitializer.initialize(m.fs.flash)" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2025-06-06 10:51:39 [INFO] idaes.init.fs.flash.control_volume.properties_in: Initialization Step 1 optimal - .\n", + "2025-06-06 10:51:39 [INFO] idaes.init.fs.flash.control_volume.properties_in: Initialization Step 2 optimal - .\n", + "2025-06-06 10:51:39 [INFO] idaes.init.fs.flash.control_volume.properties_in: Initialization Step 3 optimal - .\n", + "2025-06-06 10:51:39 [INFO] idaes.init.fs.flash.control_volume.properties_in: Initialization Step 4 optimal - .\n", + "2025-06-06 10:51:39 [INFO] idaes.init.fs.flash.control_volume.properties_in: Initialization Step 5 optimal - .\n", + "2025-06-06 10:51:40 [INFO] idaes.init.fs.flash.control_volume.properties_out: Initialization Step 1 optimal - .\n", + "2025-06-06 10:51:40 [INFO] idaes.init.fs.flash.control_volume.properties_out: Initialization Step 2 optimal - .\n", + "2025-06-06 10:51:40 [INFO] idaes.init.fs.flash.control_volume.properties_out: Initialization Step 3 optimal - .\n", + "2025-06-06 10:51:40 [INFO] idaes.init.fs.flash.control_volume.properties_out: Initialization Step 4 optimal - .\n", + "2025-06-06 10:51:40 [INFO] idaes.init.fs.flash.control_volume.properties_out: Initialization Step 5 optimal - .\n" + ] + }, + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 33, + "metadata": {}, + "output_type": "execute_result" + } + ], + "execution_count": 33 }, { + "metadata": {}, "cell_type": "markdown", + "source": "Another option for initializing is utilizing the default initializer that is attached to the unit model. Each unit model as a default initializer that is hypothetically the most compatible. It can be called with `m.fs.flash.initialize()`. While this is an option, it is generally preferred to import an initializer object and initialize the model with that to ensure more control over the initialization." + }, + { "metadata": {}, + "cell_type": "markdown", "source": [ - "Now that the model has been defined and initialized, we can solve the model.\n", + "## 6 Solving the Model\n", "\n", + "Now that the model has been defined and initialized, we can solve the model.\n", + "\n" + ] + }, + { + "metadata": { + "tags": [ + "exercise" + ] + }, + "cell_type": "markdown", + "source": [ "
\n", "Inline Exercise:\n", "Using the notation described in the previous model, create an instance of the \"ipopt\" solver and use it to solve the model. Set the tee option to True to see the log output.\n", @@ -495,42 +843,120 @@ ] }, { - "cell_type": "code", - "execution_count": 21, "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T17:04:32.219172Z", + "start_time": "2025-06-06T17:04:32.216161Z" + }, "tags": [ "exercise" ] }, - "outputs": [], + "cell_type": "code", "source": [ "# Todo: create the ipopt solver\n", "\n", "# Todo: solve the model" - ] + ], + "outputs": [], + "execution_count": 36 }, { - "cell_type": "code", - "execution_count": 22, "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T17:04:32.439966Z", + "start_time": "2025-06-06T17:04:32.396984Z" + }, "tags": [ "solution" ] }, - "outputs": [], + "cell_type": "code", "source": [ "# Todo: create the ipopt solver\n", "solver = SolverFactory(\"ipopt\")\n", "\n", "# Todo: solve the model\n", "status = solver.solve(m, tee=True)" - ] + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "Ipopt 3.13.2: \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...: 135\n", + "Number of nonzeros in inequality constraint Jacobian.: 0\n", + "Number of nonzeros in Lagrangian Hessian.............: 72\n", + "\n", + "Total number of variables............................: 41\n", + " variables with only lower bounds: 3\n", + " variables with lower and upper bounds: 10\n", + " variables with only upper bounds: 0\n", + "Total number of equality constraints.................: 41\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 6.22e-05 1.00e+00 -1.0 0.00e+00 - 0.00e+00 0.00e+00 0\n", + " 1 0.0000000e+00 1.46e-11 1.00e-02 -1.0 6.22e-05 - 9.90e-01 1.00e+00h 1\n", + "\n", + "Number of Iterations....: 1\n", + "\n", + " (scaled) (unscaled)\n", + "Objective...............: 0.0000000000000000e+00 0.0000000000000000e+00\n", + "Dual infeasibility......: 0.0000000000000000e+00 0.0000000000000000e+00\n", + "Constraint violation....: 2.0417014662014577e-12 1.4551915228366852e-11\n", + "Complementarity.........: 0.0000000000000000e+00 0.0000000000000000e+00\n", + "Overall NLP error.......: 2.0417014662014577e-12 1.4551915228366852e-11\n", + "\n", + "\n", + "Number of objective function evaluations = 2\n", + "Number of objective gradient evaluations = 2\n", + "Number of equality constraint evaluations = 2\n", + "Number of inequality constraint evaluations = 0\n", + "Number of equality constraint Jacobian evaluations = 2\n", + "Number of inequality constraint Jacobian evaluations = 0\n", + "Number of Lagrangian Hessian evaluations = 1\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" + ] + } + ], + "execution_count": 37 }, { - "cell_type": "markdown", "metadata": {}, + "cell_type": "markdown", "source": [ - "## Viewing the Results\n", + "## 7 Viewing the Results\n", "\n", "Once a model is solved, the values returned by the solver are loaded into the model object itself. We can access the value of any variable in the model with the `value` function. For example:\n", "```python\n", @@ -550,10 +976,13 @@ ] }, { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T17:04:35.556815Z", + "start_time": "2025-06-06T17:04:35.551070Z" + } + }, "cell_type": "code", - "execution_count": 24, - "metadata": {}, - "outputs": [], "source": [ "# Print the pressure of the flash vapor outlet\n", "print(\"Pressure =\", value(m.fs.flash.vap_outlet.pressure[0]))\n", @@ -563,34 +992,89 @@ "# Call display on vap_outlet and liq_outlet of the flash\n", "m.fs.flash.vap_outlet.display()\n", "m.fs.flash.liq_outlet.display()" - ] + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Pressure = 101325.0\n", + "\n", + "Output from display:\n", + "vap_outlet : Size=1\n", + " Key : Name : Value\n", + " None : flow_mol : {0.0: 0.3961181748774193}\n", + " : mole_frac_comp : {(0.0, 'benzene'): 0.633976648508129, (0.0, 'toluene'): 0.366023351491871}\n", + " : pressure : {0.0: 101325.0}\n", + " : temperature : {0.0: 368.0}\n", + "liq_outlet : Size=1\n", + " Key : Name : Value\n", + " None : flow_mol : {0.0: 0.6038818251225807}\n", + " : mole_frac_comp : {(0.0, 'benzene'): 0.41211759772293044, (0.0, 'toluene'): 0.5878824022770694}\n", + " : pressure : {0.0: 101325.0}\n", + " : temperature : {0.0: 368.0}\n" + ] + } + ], + "execution_count": 39 }, { - "cell_type": "markdown", "metadata": {}, + "cell_type": "markdown", "source": [ "The output from `display` is quite exhaustive and not really intended to provide quick summary information. Because Pyomo is built on Python, there are opportunities to format the output any way we like. Most IDAES models have a `report` method which provides a summary of the results for the model.\n", "\n", "
\n", "Inline Exercise:\n", - "Execute the cell below which uses the function above to print a summary of the key variables in the flash model, including the inlet, the vapor, and the liquid ports. \n", + "Execute the cell below which uses the function above to print a summary of the key variables in the flash model, including the inlet, the vapor, and the liquid ports.\n", "
" ] }, { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T17:04:38.727731Z", + "start_time": "2025-06-06T17:04:38.712131Z" + } + }, "cell_type": "code", - "execution_count": 25, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.flash.report()" - ] + "source": "m.fs.flash.report()", + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "====================================================================================\n", + "Unit : fs.flash Time: 0.0\n", + "------------------------------------------------------------------------------------\n", + " Unit Performance\n", + "\n", + " Variables: \n", + "\n", + " Key : Value : Units : Fixed : Bounds\n", + " Heat Duty : 0.0000 : watt : True : (None, None)\n", + " Pressure Change : 0.0000 : pascal : True : (None, None)\n", + "\n", + "------------------------------------------------------------------------------------\n", + " Stream Table\n", + " Units Inlet Vapor Outlet Liquid Outlet\n", + " flow_mol mole / second 1.0000 0.39612 0.60388 \n", + " mole_frac_comp benzene dimensionless 0.50000 0.63398 0.41212 \n", + " mole_frac_comp toluene dimensionless 0.50000 0.36602 0.58788 \n", + " temperature kelvin 368.00 368.00 368.00 \n", + " pressure pascal 1.0132e+05 1.0132e+05 1.0132e+05 \n", + "====================================================================================\n" + ] + } + ], + "execution_count": 40 }, { - "cell_type": "markdown", "metadata": {}, + "cell_type": "markdown", "source": [ - "## Studying Purity as a Function of Heat Duty\n", + "## Exercise: Studying Purity as a Function of Heat Duty\n", "\n", "Since the entire modeling framework is built upon Python, it includes a complete programming environment for whatever analysis we may want to perform. In this next exercise, we will make use of what we learned in this and the previous module to generate a figure showing some output variables as a function of the heat duty in the flash tank.\n", "\n", @@ -602,22 +1086,35 @@ ] }, { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T17:04:43.426680Z", + "start_time": "2025-06-06T17:04:43.423025Z" + } + }, "cell_type": "code", - "execution_count": 27, - "metadata": {}, + "source": "import matplotlib.pyplot as plt", "outputs": [], - "source": [ - "import matplotlib.pyplot as plt" - ] + "execution_count": 42 }, { - "cell_type": "markdown", "metadata": {}, + "cell_type": "markdown", "source": [ "Exercise specifications:\n", "* Generate a figure showing the flash tank heat duty (`m.fs.flash.heat_duty[0]`) vs. the vapor flowrate (`m.fs.flash.vap_outlet.flow_mol[0]`)\n", "* Specify the heat duty from -17000 to 25000 over 50 steps\n", - "\n", + "\n" + ] + }, + { + "metadata": { + "tags": [ + "exercise" + ] + }, + "cell_type": "markdown", + "source": [ "
\n", "Inline Exercise:\n", "Using what you have learned so far, fill in the missing code below to generate the figure specified above. (Hint: import numpy and use the linspace function from the previous module)\n", @@ -625,14 +1122,12 @@ ] }, { - "cell_type": "code", - "execution_count": 29, "metadata": { "tags": [ "exercise" ] }, - "outputs": [], + "cell_type": "code", "source": [ "# import the solve_successful checking function from workshop tools\n", "from idaes_examples.mod.tut.workshoptools import solve_successful\n", @@ -645,22 +1140,22 @@ "V = []\n", "\n", "# re-initialize model\n", - "m.fs.flash.initialize(outlvl=idaeslog.WARNING)\n", + "UnitModelInitializer.initialize(m.fs.flash)\n", "\n", "# Todo: Write the for loop specification using numpy's linspace\n", "\n", " # fix the heat duty\n", " m.fs.flash.heat_duty.fix(duty)\n", - " \n", + "\n", " # append the value of the duty to the Q list\n", " Q.append(duty)\n", - " \n", + "\n", " # print the current simulation\n", " print(\"Simulating with Q = \", value(m.fs.flash.heat_duty[0]))\n", "\n", " # Solve the model\n", " status = solver.solve(m)\n", - " \n", + "\n", " # append the value for vapor fraction if the solve was successful\n", " if solve_successful(status):\n", " V.append(value(m.fs.flash.vap_outlet.flow_mol[0]))\n", @@ -668,7 +1163,7 @@ " else:\n", " V.append(0.0)\n", " print('... solve failed.')\n", - " \n", + "\n", "# Create and show the figure\n", "plt.figure(\"Vapor Fraction\")\n", "plt.plot(Q, V)\n", @@ -676,18 +1171,21 @@ "plt.xlabel(\"Heat Duty [J]\")\n", "plt.ylabel(\"Vapor Fraction [-]\")\n", "plt.show()" - ] + ], + "outputs": [], + "execution_count": null }, { - "cell_type": "code", - "execution_count": 30, "metadata": { - "scrolled": true, + "ExecuteTime": { + "end_time": "2025-06-06T17:04:48.800601Z", + "start_time": "2025-06-06T17:04:46.438264Z" + }, "tags": [ "solution" ] }, - "outputs": [], + "cell_type": "code", "source": [ "# import the solve_successful checking function from workshop tools\n", "from idaes_examples.mod.tut.workshoptools import solve_successful\n", @@ -700,7 +1198,7 @@ "V = []\n", "\n", "# re-initialize model\n", - "m.fs.flash.initialize(outlvl=idaeslog.WARNING)\n", + "FlashInitializer.initialize(m.fs.flash)\n", "\n", "# Todo: Write the for loop specification using numpy's linspace\n", "for duty in np.linspace(-17000, 25000, 50):\n", @@ -731,11 +1229,244 @@ "plt.xlabel(\"Heat Duty [J]\")\n", "plt.ylabel(\"Vapor Fraction [-]\")\n", "plt.show()" - ] + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2025-06-06 11:04:46 [INFO] idaes.init.fs.flash.control_volume.properties_in: Initialization Step 1 optimal - .\n", + "2025-06-06 11:04:46 [INFO] idaes.init.fs.flash.control_volume.properties_in: Initialization Step 2 optimal - .\n", + "2025-06-06 11:04:46 [INFO] idaes.init.fs.flash.control_volume.properties_in: Initialization Step 3 optimal - .\n", + "2025-06-06 11:04:46 [INFO] idaes.init.fs.flash.control_volume.properties_in: Initialization Step 4 optimal - .\n", + "2025-06-06 11:04:46 [INFO] idaes.init.fs.flash.control_volume.properties_in: Initialization Step 5 optimal - .\n", + "2025-06-06 11:04:46 [INFO] idaes.init.fs.flash.control_volume.properties_out: Initialization Step 1 optimal - .\n", + "2025-06-06 11:04:46 [INFO] idaes.init.fs.flash.control_volume.properties_out: Initialization Step 2 optimal - .\n", + "2025-06-06 11:04:46 [INFO] idaes.init.fs.flash.control_volume.properties_out: Initialization Step 3 optimal - .\n", + "2025-06-06 11:04:46 [INFO] idaes.init.fs.flash.control_volume.properties_out: Initialization Step 4 optimal - .\n", + "2025-06-06 11:04:46 [INFO] idaes.init.fs.flash.control_volume.properties_out: Initialization Step 5 optimal - .\n", + "Simulating with Q = -17000.0\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -16142.857142857143\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -15285.714285714286\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -14428.571428571428\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -13571.428571428572\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -12714.285714285714\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -11857.142857142857\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -11000.0\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -10142.857142857143\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -9285.714285714286\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -8428.57142857143\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -7571.4285714285725\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -6714.285714285714\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -5857.142857142857\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -5000.0\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -4142.857142857143\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -3285.7142857142862\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -2428.5714285714294\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -1571.4285714285725\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -714.2857142857156\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 142.8571428571413\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 1000.0\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 1857.142857142855\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 2714.2857142857138\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 3571.4285714285725\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 4428.5714285714275\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 5285.714285714286\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 6142.857142857141\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 7000.0\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 7857.142857142855\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 8714.285714285714\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 9571.428571428569\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 10428.571428571428\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 11285.714285714286\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 12142.857142857141\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 13000.0\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 13857.142857142855\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 14714.285714285714\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 15571.428571428569\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 16428.571428571428\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 17285.714285714283\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 18142.857142857145\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 19000.0\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 19857.142857142855\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 20714.28571428571\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 21571.428571428572\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 22428.571428571428\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 23285.714285714283\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 24142.857142857145\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 25000.0\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n" + ] + }, + { + "data": { + "text/plain": [ + "
" + ], + "image/png": "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" + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "execution_count": 43 }, { + "metadata": { + "tags": [ + "exercise" + ] + }, "cell_type": "markdown", - "metadata": {}, "source": [ "
\n", "Inline Exercise:\n", @@ -744,27 +1475,27 @@ ] }, { - "cell_type": "code", - "execution_count": null, "metadata": { "tags": [ "exercise" ] }, + "cell_type": "code", + "source": "# Todo: generate a figure of heat duty vs. mole fraction of Benzene in the vapor", "outputs": [], - "source": [ - "# Todo: generate a figure of heat duty vs. mole fraction of Benzene in the vapor" - ] + "execution_count": null }, { - "cell_type": "code", - "execution_count": null, "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T17:05:02.101331Z", + "start_time": "2025-06-06T17:05:00.157447Z" + }, "tags": [ "solution" ] }, - "outputs": [], + "cell_type": "code", "source": [ "# Todo: generate a figure of heat duty vs. mole fraction of Benzene in the vapor\n", "Q = []\n", @@ -797,18 +1528,247 @@ "plt.xlabel(\"Heat Duty [J]\")\n", "plt.ylabel(\"Vapor Benzene Mole Fraction [-]\")\n", "plt.show()" - ] + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Simulating with Q = -17000.0\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -16142.857142857143\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -15285.714285714286\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -14428.571428571428\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -13571.428571428572\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -12714.285714285714\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -11857.142857142857\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -11000.0\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -10142.857142857143\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -9285.714285714286\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -8428.57142857143\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -7571.4285714285725\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -6714.285714285714\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -5857.142857142857\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -5000.0\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -4142.857142857143\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -3285.7142857142862\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -2428.5714285714294\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -1571.4285714285725\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = -714.2857142857156\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 142.8571428571413\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 1000.0\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 1857.142857142855\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 2714.2857142857138\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 3571.4285714285725\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 4428.5714285714275\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 5285.714285714286\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 6142.857142857141\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 7000.0\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 7857.142857142855\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 8714.285714285714\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 9571.428571428569\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 10428.571428571428\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 11285.714285714286\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 12142.857142857141\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 13000.0\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 13857.142857142855\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 14714.285714285714\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 15571.428571428569\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 16428.571428571428\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 17285.714285714283\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 18142.857142857145\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 19000.0\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 19857.142857142855\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 20714.28571428571\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 21571.428571428572\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 22428.571428571428\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 23285.714285714283\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 24142.857142857145\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n", + "Simulating with Q = 25000.0\n", + "WARNING: model contains export suffix 'scaling_factor' that contains 7\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "... solve successful.\n" + ] + }, + { + "data": { + "text/plain": [ + "
" + ], + "image/png": "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" + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "execution_count": 44 }, { - "cell_type": "markdown", "metadata": {}, + "cell_type": "markdown", "source": [ "Recall that the IDAES framework is an equation-oriented modeling environment. This means that we can specify \"design\" problems natively. That is, there is no need to have our specifications on the inlet alone. We can put specifications on the outlet as long as we retain a well-posed, square system of equations.\n", "\n", "For example, we can remove the specification on heat duty and instead specify that we want the mole fraction of Benzene in the vapor outlet to be equal to 0.6. The mole fraction is not a native variable in the property block, so we cannot use \"fix\". We can, however, add a constraint to the model.\n", "\n", "Note that we have been executing a number of solves on the problem, and may not be sure of the current state. To help convergence, therefore, we will first call initialize, then add the new constraint and solve the problem. Note that the reference for the mole fraction of Benzene in the vapor outlet is `m.fs.flash.vap_outlet.mole_frac_comp[0, \"benzene\"]`.\n", - "\n", + "\n" + ] + }, + { + "metadata": { + "tags": [ + "exercise" + ] + }, + "cell_type": "markdown", + "source": [ "
\n", "Inline Exercise:\n", "Fill in the missing code below and add a constraint on the mole fraction of Benzene (to a value of 0.6) to find the required heat duty.\n", @@ -816,14 +1776,16 @@ ] }, { - "cell_type": "code", - "execution_count": null, "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T17:05:05.363302Z", + "start_time": "2025-06-06T17:05:04.960601Z" + }, "tags": [ "exercise" ] }, - "outputs": [], + "cell_type": "code", "source": [ "# re-initialize the model - this may or may not be required depending on current state but safe to initialize\n", "m.fs.flash.heat_duty.fix(0)\n", @@ -839,17 +1801,118 @@ "\n", "# Check stream condition\n", "m.fs.flash.report()" - ] + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "WARNING: model contains export suffix 'scaling_factor' that contains 6\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "Ipopt 3.13.2: \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...: 136\n", + "Number of nonzeros in inequality constraint Jacobian.: 0\n", + "Number of nonzeros in Lagrangian Hessian.............: 72\n", + "\n", + "Total number of variables............................: 42\n", + " variables with only lower bounds: 3\n", + " variables with lower and upper bounds: 10\n", + " variables with only upper bounds: 0\n", + "Total number of equality constraints.................: 41\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 3.07e-08 1.01e-04 -1.0 0.00e+00 - 0.00e+00 0.00e+00 0\n", + " 1 0.0000000e+00 1.46e+00 2.42e-01 -1.0 4.86e+02 - 9.90e-01 1.00e+00f 1\n", + " 2 0.0000000e+00 5.55e+01 5.60e-03 -1.0 3.00e+03 - 9.90e-01 1.00e+00h 1\n", + " 3 0.0000000e+00 1.91e+00 4.24e-05 -1.0 5.68e+02 - 1.00e+00 1.00e+00h 1\n", + " 4 0.0000000e+00 1.53e-05 1.90e-06 -2.5 1.35e+00 - 1.00e+00 1.00e+00h 1\n", + " 5 0.0000000e+00 4.66e-10 1.50e-09 -3.8 8.72e-03 - 1.00e+00 1.00e+00h 1\n", + " 6 0.0000000e+00 2.18e-11 1.84e-11 -5.7 1.92e-03 - 1.00e+00 1.00e+00h 1\n", + " 7 0.0000000e+00 1.46e-11 2.51e-14 -8.6 2.10e-04 - 1.00e+00 1.00e+00H 1\n", + "\n", + "Number of Iterations....: 7\n", + "\n", + " (scaled) (unscaled)\n", + "Objective...............: 0.0000000000000000e+00 0.0000000000000000e+00\n", + "Dual infeasibility......: 2.5059035640133008e-14 2.5059035640133008e-14\n", + "Constraint violation....: 4.8801876740451558e-13 1.4551915228366852e-11\n", + "Complementarity.........: 2.5059101787473095e-09 2.5059101787473095e-09\n", + "Overall NLP error.......: 2.5059101787473095e-09 2.5059101787473095e-09\n", + "\n", + "\n", + "Number of objective function evaluations = 9\n", + "Number of objective gradient evaluations = 8\n", + "Number of equality constraint evaluations = 9\n", + "Number of inequality constraint evaluations = 0\n", + "Number of equality constraint Jacobian evaluations = 8\n", + "Number of inequality constraint Jacobian evaluations = 0\n", + "Number of Lagrangian Hessian evaluations = 7\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", + "\n", + "====================================================================================\n", + "Unit : fs.flash Time: 0.0\n", + "------------------------------------------------------------------------------------\n", + " Unit Performance\n", + "\n", + " Variables: \n", + "\n", + " Key : Value : Units : Fixed : Bounds\n", + " Heat Duty : 4059.3 : watt : False : (None, None)\n", + " Pressure Change : 0.0000 : pascal : True : (None, None)\n", + "\n", + "------------------------------------------------------------------------------------\n", + " Stream Table\n", + " Units Inlet Vapor Outlet Liquid Outlet\n", + " flow_mol mole / second 1.0000 0.51773 0.48227 \n", + " mole_frac_comp benzene dimensionless 0.50000 0.60690 0.38524 \n", + " mole_frac_comp toluene dimensionless 0.50000 0.39310 0.61476 \n", + " temperature kelvin 368.00 368.85 368.85 \n", + " pressure pascal 1.0132e+05 1.0132e+05 1.0132e+05 \n", + "====================================================================================\n" + ] + } + ], + "execution_count": 45 }, { - "cell_type": "code", - "execution_count": null, "metadata": { + "ExecuteTime": { + "end_time": "2025-06-06T17:05:05.768445Z", + "start_time": "2025-06-06T17:05:05.378930Z" + }, "tags": [ "solution" ] }, - "outputs": [], + "cell_type": "code", "source": [ "# re-initialize the model - this may or may not be required depending on current state but safe to initialize\n", "m.fs.flash.heat_duty.fix(0)\n", @@ -868,7 +1931,102 @@ "\n", "# Check stream condition\n", "m.fs.flash.report()" - ] + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "WARNING: model contains export suffix 'scaling_factor' that contains 6\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "Ipopt 3.13.2: \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...: 137\n", + "Number of nonzeros in inequality constraint Jacobian.: 0\n", + "Number of nonzeros in Lagrangian Hessian.............: 72\n", + "\n", + "Total number of variables............................: 42\n", + " variables with only lower bounds: 3\n", + " variables with lower and upper bounds: 10\n", + " variables with only upper bounds: 0\n", + "Total number of equality constraints.................: 42\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 3.40e-02 1.00e+00 -1.0 0.00e+00 - 0.00e+00 0.00e+00 0\n", + " 1 0.0000000e+00 1.64e+02 7.01e-02 -1.0 5.15e+03 - 9.87e-01 1.00e+00h 1\n", + " 2 0.0000000e+00 9.59e-02 2.03e-03 -1.0 7.07e+01 - 9.90e-01 1.00e+00h 1\n", + " 3 0.0000000e+00 6.96e-08 2.50e-06 -1.0 4.13e-01 - 9.98e-01 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....: 8.9151738215745240e-11 6.9550878833979368e-08\n", + "Complementarity.........: 0.0000000000000000e+00 0.0000000000000000e+00\n", + "Overall NLP error.......: 8.9151738215745240e-11 6.9550878833979368e-08\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.001\n", + "Total CPU secs in NLP function evaluations = 0.000\n", + "\n", + "EXIT: Optimal Solution Found.\n", + "\n", + "====================================================================================\n", + "Unit : fs.flash Time: 0.0\n", + "------------------------------------------------------------------------------------\n", + " Unit Performance\n", + "\n", + " Variables: \n", + "\n", + " Key : Value : Units : Fixed : Bounds\n", + " Heat Duty : 5083.6 : watt : False : (None, None)\n", + " Pressure Change : 0.0000 : pascal : True : (None, None)\n", + "\n", + "------------------------------------------------------------------------------------\n", + " Stream Table\n", + " Units Inlet Vapor Outlet Liquid Outlet\n", + " flow_mol mole / second 1.0000 0.54833 0.45167 \n", + " mole_frac_comp benzene dimensionless 0.50000 0.60000 0.37860 \n", + " mole_frac_comp toluene dimensionless 0.50000 0.40000 0.62140 \n", + " temperature kelvin 368.00 369.07 369.07 \n", + " pressure pascal 1.0132e+05 1.0132e+05 1.0132e+05 \n", + "====================================================================================\n" + ] + } + ], + "execution_count": 46 } ], "metadata": { @@ -893,4 +2051,4 @@ }, "nbformat": 4, "nbformat_minor": 3 -} +} \ No newline at end of file diff --git a/idaes_examples/notebooks/docs/tut/core/hda_flowsheet.ipynb b/idaes_examples/notebooks/docs/tut/core/hda_flowsheet.ipynb index a7645f92..2fca2a80 100644 --- a/idaes_examples/notebooks/docs/tut/core/hda_flowsheet.ipynb +++ b/idaes_examples/notebooks/docs/tut/core/hda_flowsheet.ipynb @@ -2,14 +2,16 @@ "cells": [ { "cell_type": "code", - "execution_count": null, "metadata": { "tags": [ "header", "hide-cell" - ] + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:25.418463Z", + "start_time": "2025-06-11T22:13:25.412645Z" + } }, - "outputs": [], "source": [ "###############################################################################\n", "# The Institute for the Design of Advanced Energy Systems Integrated Platform\n", @@ -23,7 +25,9 @@ "# All rights reserved. Please see the files COPYRIGHT.md and LICENSE.md\n", "# for full copyright and license information.\n", "###############################################################################" - ] + ], + "outputs": [], + "execution_count": 1 }, { "cell_type": "markdown", @@ -32,20 +36,34 @@ "\n", "# HDA Flowsheet Simulation and Optimization\n", "\n", - "Author: Jaffer Ghouse \n", - "Maintainer: Brandon Paul \n", - "Updated: 2023-06-01 \n", + "Author: Jaffer Ghouse
\n", + "Maintainer: Tanner Polley
\n", + "Updated: 2025-06-03\n", "\n", "## Learning outcomes\n", "\n", "\n", "- Construct a steady-state flowsheet using the IDAES unit model library\n", - "- Connecting unit models in a flowsheet using Arcs\n", + "- Connecting unit models in a flowsheet using Arcs\n", "- Using the SequentialDecomposition tool to initialize a flowsheet with recycle\n", - "- Fomulate and solve an optimization problem\n", + "- Formulate and solve an optimization problem\n", " - Defining an objective function\n", " - Setting variable bounds\n", - " - Adding additional constraints \n", + " - Adding additional constraints\n", + "\n", + "\n", + "The general workflow of setting up an IDAES flowsheet is the following:\n", + "\n", + "- 1 Importing Modules\n", + "- 2 Building a Model\n", + "- 3 Scaling the Model\n", + "- 4 Specifying the Model\n", + "- 5 Initializing the Model\n", + "- 6 Solving the Model\n", + "- 7 Analyzing and Visualizing the Results\n", + "- 8 Optimizing the Model\n", + "\n", + "We will complete each of these steps as well as demonstrate analyses on this model through some examples and exercises\n", "\n", "\n", "## Problem Statement\n", @@ -81,7 +99,8 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Importing required pyomo and idaes components\n", + "## 1 Importing Modules\n", + "### 1.1 Importing required pyomo and idaes components\n", "\n", "\n", "To construct a flowsheet, we will need several components from the pyomo and idaes package. Let us first import the following components from Pyomo:\n", @@ -100,9 +119,12 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:26.066510Z", + "start_time": "2025-06-11T22:13:25.662098Z" + } + }, "source": [ "from pyomo.environ import (\n", " Constraint,\n", @@ -115,44 +137,60 @@ " value,\n", ")\n", "from pyomo.network import Arc, SequentialDecomposition" - ] + ], + "outputs": [], + "execution_count": 2 }, { "cell_type": "markdown", "metadata": {}, "source": [ "From idaes, we will be needing the FlowsheetBlock and the following unit models:\n", + "- Feed\n", "- Mixer\n", "- Heater\n", "- StoichiometricReactor\n", "- **Flash**\n", "- Separator (splitter) \n", - "- PressureChanger" + "- PressureChanger\n", + "- Product" ] }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:27.637361Z", + "start_time": "2025-06-11T22:13:26.082580Z" + } + }, "source": [ "from idaes.core import FlowsheetBlock" - ] + ], + "outputs": [], + "execution_count": 3 }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:27.752223Z", + "start_time": "2025-06-11T22:13:27.645348Z" + } + }, "source": [ "from idaes.models.unit_models import (\n", - " PressureChanger,\n", - " Mixer,\n", - " Separator as Splitter,\n", + " PressureChanger, IsentropicPressureChangerInitializer,\n", + " Mixer, MixerInitializer,\n", + " Separator as Splitter, SeparatorInitializer,\n", " Heater,\n", " StoichiometricReactor,\n", + " Feed,\n", + " Product,\n", ")" - ] + ], + "outputs": [], + "execution_count": 4 }, { "cell_type": "markdown", @@ -166,30 +204,38 @@ }, { "cell_type": "code", - "execution_count": null, "metadata": { "tags": [ "exercise" - ] + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:27.763417Z", + "start_time": "2025-06-11T22:13:27.761004Z" + } }, - "outputs": [], "source": [ "# Todo: import flash model from idaes.models.unit_models" - ] + ], + "outputs": [], + "execution_count": 5 }, { "cell_type": "code", - "execution_count": null, "metadata": { "tags": [ "solution" - ] + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:27.774708Z", + "start_time": "2025-06-11T22:13:27.772299Z" + } }, - "outputs": [], "source": [ "# Todo: import flash model from idaes.models.unit_models\n", "from idaes.models.unit_models import Flash" - ] + ], + "outputs": [], + "execution_count": 6 }, { "cell_type": "markdown", @@ -200,24 +246,31 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:27.788004Z", + "start_time": "2025-06-11T22:13:27.784891Z" + } + }, "source": [ "from idaes.models.unit_models.pressure_changer import ThermodynamicAssumption\n", "from idaes.core.util.model_statistics import degrees_of_freedom\n", + "from idaes.core.scaling.util import set_scaling_factor\n", + "from idaes.core.scaling.autoscaling import AutoScaler\n", "\n", "# Import idaes logger to set output levels\n", "import idaes.logger as idaeslog\n", "from idaes.core.solvers import get_solver\n", "from idaes.core.util.exceptions import InitializationError" - ] + ], + "outputs": [], + "execution_count": 7 }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Importing required thermo and reaction package\n", + "### 1.2 Importing required thermo and reaction package\n", "\n", "The final set of imports are to import the thermo and reaction package for the HDA process. We have created a custom thermo package that assumes Ideal Gas with support for VLE. \n", "\n", @@ -233,32 +286,42 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:27.806315Z", + "start_time": "2025-06-11T22:13:27.798068Z" + } + }, "source": [ "from idaes_examples.mod.hda import hda_ideal_VLE as thermo_props\n", "from idaes_examples.mod.hda import hda_reaction as reaction_props" - ] + ], + "outputs": [], + "execution_count": 8 }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Constructing the Flowsheet\n", + "## 2 Constructing the Flowsheet\n", "\n", "We have now imported all the components, unit models, and property modules we need to construct a flowsheet. Let us create a ConcreteModel and add the flowsheet block as we did in module 1. " ] }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:27.819615Z", + "start_time": "2025-06-11T22:13:27.814729Z" + } + }, "source": [ "m = ConcreteModel()\n", "m.fs = FlowsheetBlock(dynamic=False)" - ] + ], + "outputs": [], + "execution_count": 9 }, { "cell_type": "markdown", @@ -269,34 +332,48 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:27.841949Z", + "start_time": "2025-06-11T22:13:27.829949Z" + } + }, "source": [ "m.fs.thermo_params = thermo_props.HDAParameterBlock()\n", "m.fs.reaction_params = reaction_props.HDAReactionParameterBlock(\n", " property_package=m.fs.thermo_params\n", ")" - ] + ], + "outputs": [], + "execution_count": 10 }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Adding Unit Models\n", + "### 2.1 Adding Unit Models\n", "\n", - "Let us start adding the unit models we have imported to the flowsheet. Here, we are adding the Mixer (assigned a name M101) and a Heater (assigned a name H101). Note that, all unit models need to be given a property package argument. In addition to that, there are several arguments depending on the unit model, please refer to the documentation for more details (https://idaes-pse.readthedocs.io/en/stable/reference_guides/model_libraries/generic/unit_models/index.html). For example, the Mixer unit model here is given a `list` consisting of names to the three inlets. " + "Let us start adding the unit models we have imported to the flowsheet. Here, we are adding the Feed (assigned a name `I101` for Inlet), `Mixer` (assigned a name `M101`) and a `Heater` (assigned a name `H101`). Note that, all unit models need to be given a property package argument. In addition to that, there are several arguments depending on the unit model, please refer to the documentation for more details (https://idaes-pse.readthedocs.io/en/stable/reference_guides/model_libraries/generic/unit_models/index.html). For example, the `Mixer` unit model here must be specified the number of inlets that it will take in and the `Heater` can have specific settings enabled such as `has_pressure_change` or `has_phase_equilibrium`." ] }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:27.876870Z", + "start_time": "2025-06-11T22:13:27.853517Z" + } + }, "source": [ + "m.fs.I101 = Feed(\n", + " property_package=m.fs.thermo_params)\n", + "\n", + "m.fs.I102 = Feed(\n", + " property_package=m.fs.thermo_params)\n", + "\n", "m.fs.M101 = Mixer(\n", " property_package=m.fs.thermo_params,\n", - " inlet_list=[\"toluene_feed\", \"hydrogen_feed\", \"vapor_recycle\"],\n", + " num_inlets=3,\n", ")\n", "\n", "m.fs.H101 = Heater(\n", @@ -304,11 +381,17 @@ " has_pressure_change=False,\n", " has_phase_equilibrium=True,\n", ")" - ] + ], + "outputs": [], + "execution_count": 11 }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "tags": [ + "exercise" + ] + }, "source": [ "
\n", "Inline Exercise:\n", @@ -325,26 +408,32 @@ }, { "cell_type": "code", - "execution_count": null, "metadata": { "tags": [ "exercise" - ] + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:27.894702Z", + "start_time": "2025-06-11T22:13:27.891599Z" + } }, - "outputs": [], "source": [ "# Todo: Add reactor with the specifications above" - ] + ], + "outputs": [], + "execution_count": 12 }, { "cell_type": "code", - "execution_count": null, "metadata": { "tags": [ "solution" - ] + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:27.921265Z", + "start_time": "2025-06-11T22:13:27.910910Z" + } }, - "outputs": [], "source": [ "# Todo: Add reactor with the specifications above\n", "m.fs.R101 = StoichiometricReactor(\n", @@ -354,7 +443,9 @@ " has_heat_transfer=True,\n", " has_pressure_change=False,\n", ")" - ] + ], + "outputs": [], + "execution_count": 13 }, { "cell_type": "markdown", @@ -370,29 +461,35 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:27.947463Z", + "start_time": "2025-06-11T22:13:27.933993Z" + } + }, "source": [ "m.fs.F101 = Flash(\n", " property_package=m.fs.thermo_params,\n", " has_heat_transfer=True,\n", " has_pressure_change=True,\n", ")" - ] + ], + "outputs": [], + "execution_count": 14 }, { "cell_type": "markdown", "metadata": {}, - "source": [ - "Let us now add the Splitter(S101), PressureChanger(C101) and the second Flash(F102). " - ] + "source": "Let us now add the Splitter(S101) with specific names for its output (purge and recycle), PressureChanger(C101) and the second Flash(F102)." }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:27.987933Z", + "start_time": "2025-06-11T22:13:27.964931Z" + } + }, "source": [ "m.fs.S101 = Splitter(\n", " property_package=m.fs.thermo_params,\n", @@ -412,25 +509,60 @@ " has_heat_transfer=True,\n", " has_pressure_change=True,\n", ")" - ] + ], + "outputs": [], + "execution_count": 15 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "Last, we will add the three Product blocks (P101, P102, P103). We use `Feed` blocks and `Product` blocks for convenience with reporting stream summaries and consistency" + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:28.009893Z", + "start_time": "2025-06-11T22:13:28.001769Z" + } + }, + "cell_type": "code", + "source": [ + "m.fs.P101 = Product(\n", + " property_package=m.fs.thermo_params)\n", + "\n", + "m.fs.P102 = Product(\n", + " property_package=m.fs.thermo_params)\n", + "\n", + "m.fs.P103 = Product(\n", + " property_package=m.fs.thermo_params)" + ], + "outputs": [], + "execution_count": 16 }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Connecting Unit Models using Arcs\n", + "### 2.2 Connecting Unit Models using Arcs\n", "\n", - "We have now added all the unit models we need to the flowsheet. However, we have not yet specified how the units are to be connected. To do this, we will be using the `Arc` which is a pyomo component that takes in two arguments: `source` and `destination`. Let us connect the outlet of the mixer(M101) to the inlet of the heater(H101). " + "We have now added all the unit models we need to the flowsheet. However, we have not yet specified how the units are to be connected. To do this, we will be using the `Arc` which is a pyomo component that takes in two arguments: `source` and `destination`. Let us connect the outlet of the inlets (I101, I102) to the inlet of the mixer (M101) and outlet of the mixer to the inlet of the heater(H101)." ] }, { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:28.034324Z", + "start_time": "2025-06-11T22:13:28.031285Z" + } + }, "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], "source": [ + "m.fs.s01 = Arc(source=m.fs.I101.outlet, destination=m.fs.M101.inlet_1)\n", + "m.fs.s02 = Arc(source=m.fs.I102.outlet, destination=m.fs.M101.inlet_2)\n", "m.fs.s03 = Arc(source=m.fs.M101.outlet, destination=m.fs.H101.inlet)" - ] + ], + "outputs": [], + "execution_count": 17 }, { "cell_type": "markdown", @@ -439,39 +571,57 @@ "\n", "![](HDA_flowsheet.png) \n", "\n", + "\n" + ] + }, + { + "metadata": { + "tags": [ + "exercise" + ] + }, + "cell_type": "markdown", + "source": [ "
\n", "Inline Exercise:\n", - "Now, connect the H101 outlet to the R101 inlet using the cell above as a guide. \n", - "
\n", - "\n" + "Now, connect the H101 outlet to the R101 inlet using the cell above as a guide.\n", + "
" ] }, { "cell_type": "code", - "execution_count": null, "metadata": { "tags": [ "exercise" - ] + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:28.055815Z", + "start_time": "2025-06-11T22:13:28.052507Z" + } }, - "outputs": [], "source": [ "# Todo: Connect the H101 outlet to R101 inlet" - ] + ], + "outputs": [], + "execution_count": 18 }, { "cell_type": "code", - "execution_count": null, "metadata": { "tags": [ "solution" - ] + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:28.084663Z", + "start_time": "2025-06-11T22:13:28.082008Z" + } }, - "outputs": [], "source": [ "# Todo: Connect the H101 outlet to R101 inlet\n", "m.fs.s04 = Arc(source=m.fs.H101.outlet, destination=m.fs.R101.inlet)" - ] + ], + "outputs": [], + "execution_count": 19 }, { "cell_type": "markdown", @@ -482,16 +632,42 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:28.118422Z", + "start_time": "2025-06-11T22:13:28.113732Z" + } + }, "source": [ "m.fs.s05 = Arc(source=m.fs.R101.outlet, destination=m.fs.F101.inlet)\n", "m.fs.s06 = Arc(source=m.fs.F101.vap_outlet, destination=m.fs.S101.inlet)\n", + "m.fs.s07 = Arc(source=m.fs.F101.liq_outlet, destination=m.fs.F102.inlet)\n", "m.fs.s08 = Arc(source=m.fs.S101.recycle, destination=m.fs.C101.inlet)\n", - "m.fs.s09 = Arc(source=m.fs.C101.outlet, destination=m.fs.M101.vapor_recycle)\n", - "m.fs.s10 = Arc(source=m.fs.F101.liq_outlet, destination=m.fs.F102.inlet)" - ] + "m.fs.s09 = Arc(source=m.fs.C101.outlet, destination=m.fs.M101.inlet_3)\n" + ], + "outputs": [], + "execution_count": 20 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "Last we will connect the outlet streams to the inlets of the Product blocks (P101, P102, P103)" + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:28.148879Z", + "start_time": "2025-06-11T22:13:28.144601Z" + } + }, + "cell_type": "code", + "source": [ + "m.fs.s10 = Arc(source=m.fs.F102.vap_outlet, destination=m.fs.P101.inlet)\n", + "m.fs.s11 = Arc(source=m.fs.F102.liq_outlet, destination=m.fs.P102.inlet)\n", + "m.fs.s12 = Arc(source=m.fs.S101.purge, destination=m.fs.P103.inlet)" + ], + "outputs": [], + "execution_count": 21 }, { "cell_type": "markdown", @@ -502,18 +678,23 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:28.198384Z", + "start_time": "2025-06-11T22:13:28.176239Z" + } + }, "source": [ "TransformationFactory(\"network.expand_arcs\").apply_to(m)" - ] + ], + "outputs": [], + "execution_count": 22 }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Adding expressions to compute purity and operating costs\n", + "### 2.3 Adding expressions to compute purity and operating costs\n", "\n", "In this section, we will add a few Expressions that allows us to evaluate the performance. Expressions provide a convenient way of calculating certain values that are a function of the variables defined in the model. For more details on Expressions, please refer to: https://pyomo.readthedocs.io/en/stable/pyomo_modeling_components/Expressions.html\n", "\n", @@ -529,9 +710,12 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:28.222088Z", + "start_time": "2025-06-11T22:13:28.218400Z" + } + }, "source": [ "m.fs.purity = Expression(\n", " expr=m.fs.F102.vap_outlet.flow_mol_phase_comp[0, \"Vap\", \"benzene\"]\n", @@ -540,7 +724,9 @@ " + m.fs.F102.vap_outlet.flow_mol_phase_comp[0, \"Vap\", \"toluene\"]\n", " )\n", ")" - ] + ], + "outputs": [], + "execution_count": 23 }, { "cell_type": "markdown", @@ -551,14 +737,19 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:28.248216Z", + "start_time": "2025-06-11T22:13:28.244244Z" + } + }, "source": [ "m.fs.cooling_cost = Expression(\n", " expr=0.212e-7 * (-m.fs.F101.heat_duty[0]) + 0.212e-7 * (-m.fs.R101.heat_duty[0])\n", ")" - ] + ], + "outputs": [], + "execution_count": 24 }, { "cell_type": "markdown", @@ -575,14 +766,19 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:28.271256Z", + "start_time": "2025-06-11T22:13:28.268284Z" + } + }, "source": [ "m.fs.heating_cost = Expression(\n", " expr=2.2e-7 * m.fs.H101.heat_duty[0] + 1.9e-7 * m.fs.F102.heat_duty[0]\n", ")" - ] + ], + "outputs": [], + "execution_count": 25 }, { "cell_type": "markdown", @@ -593,78 +789,143 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:28.295580Z", + "start_time": "2025-06-11T22:13:28.292627Z" + } + }, "source": [ "m.fs.operating_cost = Expression(\n", " expr=(3600 * 24 * 365 * (m.fs.heating_cost + m.fs.cooling_cost))\n", ")" + ], + "outputs": [], + "execution_count": 26 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": [ + "## 3 Scaling the Model\n", + "\n", + "In this example, we will simply use the ``AutoScaler`` method to scale our model since this example is focused on introductory flowsheet building for a full process system.\n", + "\n", + "For more direct control, a manual, magnitude based approach can be used. If this method is chosen or appropriate, it is highly recommended to look at these documentation:\n", + "- Scaling Toolbox https://idaes-pse.readthedocs.io/en/stable/reference_guides/scaling/scaling.html\n", + "- Scaling Workshop https://idaes-examples.readthedocs.io/en/latest/docs/scaling/scaler_workshop_doc.html.\n", + "\n", + "In this example, we already imported the ``AutoScaler`` class in the beginning so we just create the ``autoscaler`` object and call the ``scale_model`` method on the model `m`." ] }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:28.915668Z", + "start_time": "2025-06-11T22:13:28.317020Z" + } + }, + "cell_type": "code", + "source": [ + "autoscaler = AutoScaler()\n", + "autoscaler.scale_model(m)" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "WARNING: model contains export suffix 'scaling_factor' that contains 2\n", + "component keys that are not exported as part of the NL file. Skipping.\n" + ] + } + ], + "execution_count": 27 + }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Fixing feed conditions\n", + "## 4 Specifying the Model\n", + "### 4.1 Fixing feed conditions\n", "\n", "Let us first check how many degrees of freedom exist for this flowsheet using the `degrees_of_freedom` tool we imported earlier. " ] }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:28.948173Z", + "start_time": "2025-06-11T22:13:28.924210Z" + } + }, "source": [ "print(degrees_of_freedom(m))" - ] + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "29\n" + ] + } + ], + "execution_count": 28 }, { "cell_type": "code", - "execution_count": null, "metadata": { "tags": [ "testing" - ] + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:28.996567Z", + "start_time": "2025-06-11T22:13:28.973555Z" + } }, - "outputs": [], "source": [ "# Check the degrees of freedom\n", "assert degrees_of_freedom(m) == 29" - ] + ], + "outputs": [], + "execution_count": 29 }, { "cell_type": "markdown", "metadata": {}, - "source": [ - "We will now be fixing the toluene feed stream to the conditions shown in the flowsheet above. Please note that though this is a pure toluene feed, the remaining components are still assigned a very small non-zero value to help with convergence and initializing. " - ] + "source": "We will now be fixing the toluene feed (`I101`) stream to the conditions shown in the flowsheet above. Please note that though this is a pure toluene feed, the remaining components are still assigned a very small non-zero value to help with convergence and initializing." }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.012096Z", + "start_time": "2025-06-11T22:13:29.006965Z" + } + }, "source": [ - "m.fs.M101.toluene_feed.flow_mol_phase_comp[0, \"Vap\", \"benzene\"].fix(1e-5)\n", - "m.fs.M101.toluene_feed.flow_mol_phase_comp[0, \"Vap\", \"toluene\"].fix(1e-5)\n", - "m.fs.M101.toluene_feed.flow_mol_phase_comp[0, \"Vap\", \"hydrogen\"].fix(1e-5)\n", - "m.fs.M101.toluene_feed.flow_mol_phase_comp[0, \"Vap\", \"methane\"].fix(1e-5)\n", - "m.fs.M101.toluene_feed.flow_mol_phase_comp[0, \"Liq\", \"benzene\"].fix(1e-5)\n", - "m.fs.M101.toluene_feed.flow_mol_phase_comp[0, \"Liq\", \"toluene\"].fix(0.30)\n", - "m.fs.M101.toluene_feed.flow_mol_phase_comp[0, \"Liq\", \"hydrogen\"].fix(1e-5)\n", - "m.fs.M101.toluene_feed.flow_mol_phase_comp[0, \"Liq\", \"methane\"].fix(1e-5)\n", - "m.fs.M101.toluene_feed.temperature.fix(303.2)\n", - "m.fs.M101.toluene_feed.pressure.fix(350000)" - ] + "m.fs.I101.flow_mol_phase_comp[0, \"Vap\", \"benzene\"].fix(1e-5)\n", + "m.fs.I101.flow_mol_phase_comp[0, \"Vap\", \"toluene\"].fix(1e-5)\n", + "m.fs.I101.flow_mol_phase_comp[0, \"Vap\", \"hydrogen\"].fix(1e-5)\n", + "m.fs.I101.flow_mol_phase_comp[0, \"Vap\", \"methane\"].fix(1e-5)\n", + "m.fs.I101.flow_mol_phase_comp[0, \"Liq\", \"benzene\"].fix(1e-5)\n", + "m.fs.I101.flow_mol_phase_comp[0, \"Liq\", \"toluene\"].fix(0.30)\n", + "m.fs.I101.flow_mol_phase_comp[0, \"Liq\", \"hydrogen\"].fix(1e-5)\n", + "m.fs.I101.flow_mol_phase_comp[0, \"Liq\", \"methane\"].fix(1e-5)\n", + "m.fs.I101.temperature.fix(303.2)\n", + "m.fs.I101.pressure.fix(350000)" + ], + "outputs": [], + "execution_count": 30 }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", - "Similarly, let us fix the hydrogen feed to the following conditions in the next cell:\n", + "Similarly, let us fix the hydrogen feed (`I102`) to the following conditions in the next cell:\n", "
    \n", "
  • FH2 = 0.30 mol/s
  • \n", "
  • FCH4 = 0.02 mol/s
  • \n", @@ -677,39 +938,49 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.036253Z", + "start_time": "2025-06-11T22:13:29.031731Z" + } + }, "source": [ - "m.fs.M101.hydrogen_feed.flow_mol_phase_comp[0, \"Vap\", \"benzene\"].fix(1e-5)\n", - "m.fs.M101.hydrogen_feed.flow_mol_phase_comp[0, \"Vap\", \"toluene\"].fix(1e-5)\n", - "m.fs.M101.hydrogen_feed.flow_mol_phase_comp[0, \"Vap\", \"hydrogen\"].fix(0.30)\n", - "m.fs.M101.hydrogen_feed.flow_mol_phase_comp[0, \"Vap\", \"methane\"].fix(0.02)\n", - "m.fs.M101.hydrogen_feed.flow_mol_phase_comp[0, \"Liq\", \"benzene\"].fix(1e-5)\n", - "m.fs.M101.hydrogen_feed.flow_mol_phase_comp[0, \"Liq\", \"toluene\"].fix(1e-5)\n", - "m.fs.M101.hydrogen_feed.flow_mol_phase_comp[0, \"Liq\", \"hydrogen\"].fix(1e-5)\n", - "m.fs.M101.hydrogen_feed.flow_mol_phase_comp[0, \"Liq\", \"methane\"].fix(1e-5)\n", - "m.fs.M101.hydrogen_feed.temperature.fix(303.2)\n", - "m.fs.M101.hydrogen_feed.pressure.fix(350000)" - ] + "m.fs.I102.flow_mol_phase_comp[0, \"Vap\", \"benzene\"].fix(1e-5)\n", + "m.fs.I102.flow_mol_phase_comp[0, \"Vap\", \"toluene\"].fix(1e-5)\n", + "m.fs.I102.flow_mol_phase_comp[0, \"Vap\", \"hydrogen\"].fix(0.30)\n", + "m.fs.I102.flow_mol_phase_comp[0, \"Vap\", \"methane\"].fix(0.02)\n", + "m.fs.I102.flow_mol_phase_comp[0, \"Liq\", \"benzene\"].fix(1e-5)\n", + "m.fs.I102.flow_mol_phase_comp[0, \"Liq\", \"toluene\"].fix(1e-5)\n", + "m.fs.I102.flow_mol_phase_comp[0, \"Liq\", \"hydrogen\"].fix(1e-5)\n", + "m.fs.I102.flow_mol_phase_comp[0, \"Liq\", \"methane\"].fix(1e-5)\n", + "m.fs.I102.temperature.fix(303.2)\n", + "m.fs.I102.pressure.fix(350000)" + ], + "outputs": [], + "execution_count": 31 }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Fixing unit model specifications\n", + "### 4.2 Fixing unit model specifications\n", "\n", "Now that we have fixed our inlet feed conditions, we will now be fixing the operating conditions for the unit models in the flowsheet. Let us set set the H101 outlet temperature to 600 K. " ] }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.078559Z", + "start_time": "2025-06-11T22:13:29.075531Z" + } + }, "source": [ "m.fs.H101.outlet.temperature.fix(600)" - ] + ], + "outputs": [], + "execution_count": 32 }, { "cell_type": "markdown", @@ -720,9 +991,12 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.111099Z", + "start_time": "2025-06-11T22:13:29.106956Z" + } + }, "source": [ "m.fs.R101.conversion = Var(initialize=0.75, bounds=(0, 1))\n", "\n", @@ -736,7 +1010,9 @@ "\n", "m.fs.R101.conversion.fix(0.75)\n", "m.fs.R101.heat_duty.fix(0)" - ] + ], + "outputs": [], + "execution_count": 33 }, { "cell_type": "markdown", @@ -747,17 +1023,26 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.128972Z", + "start_time": "2025-06-11T22:13:29.125273Z" + } + }, "source": [ "m.fs.F101.vap_outlet.temperature.fix(325.0)\n", "m.fs.F101.deltaP.fix(0)" - ] + ], + "outputs": [], + "execution_count": 34 }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "tags": [ + "exercise" + ] + }, "source": [ "
    \n", "Inline Exercise:\n", @@ -773,30 +1058,38 @@ }, { "cell_type": "code", - "execution_count": null, "metadata": { "tags": [ "exercise" - ] + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.149002Z", + "start_time": "2025-06-11T22:13:29.146476Z" + } }, - "outputs": [], "source": [ "# Todo: Set conditions for Flash F102" - ] + ], + "outputs": [], + "execution_count": 35 }, { "cell_type": "code", - "execution_count": null, "metadata": { "tags": [ "solution" - ] + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.171742Z", + "start_time": "2025-06-11T22:13:29.168352Z" + } }, - "outputs": [], "source": [ "m.fs.F102.vap_outlet.temperature.fix(375)\n", "m.fs.F102.deltaP.fix(-200000)" - ] + ], + "outputs": [], + "execution_count": 36 }, { "cell_type": "markdown", @@ -807,17 +1100,26 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.196247Z", + "start_time": "2025-06-11T22:13:29.192956Z" + } + }, "source": [ "m.fs.S101.split_fraction[0, \"purge\"].fix(0.2)\n", "m.fs.C101.outlet.pressure.fix(350000)" - ] + ], + "outputs": [], + "execution_count": 37 }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "tags": [ + "exercise" + ] + }, "source": [ "
    \n", "Inline Exercise:\n", @@ -829,68 +1131,114 @@ }, { "cell_type": "code", - "execution_count": null, "metadata": { "tags": [ "exercise" - ] + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.220402Z", + "start_time": "2025-06-11T22:13:29.217907Z" + } }, - "outputs": [], "source": [ "# Todo: print the degrees of freedom" - ] + ], + "outputs": [], + "execution_count": 38 }, { "cell_type": "code", - "execution_count": null, "metadata": { "tags": [ "solution" - ] + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.263397Z", + "start_time": "2025-06-11T22:13:29.241129Z" + } }, - "outputs": [], "source": [ "print(degrees_of_freedom(m))" - ] + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0\n" + ] + } + ], + "execution_count": 39 }, { "cell_type": "code", - "execution_count": null, "metadata": { "tags": [ "testing" - ] + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.304978Z", + "start_time": "2025-06-11T22:13:29.282394Z" + } }, - "outputs": [], "source": [ "# Check the degrees of freedom\n", "assert degrees_of_freedom(m) == 0" - ] + ], + "outputs": [], + "execution_count": 40 + }, + { + "metadata": { + "tags": [ + "noauto" + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.316042Z", + "start_time": "2025-06-11T22:13:29.313537Z" + } + }, + "cell_type": "code", + "source": "", + "outputs": [], + "execution_count": null }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Initialization\n", + "## 5 Initializing the Model\n", + "\n", "\n", "\n", - "This section will demonstrate how to use the built-in sequential decomposition tool to initialize our flowsheet.\n", + "When a flowsheet contains a recycle loop, the outlet of a downstream unit becomes the inlet of an upstream unit, creating a cyclic dependency that prevents straightforward calculation of all stream conditions. The tear‐stream method is necessary because it “breaks” this loop: you select one recycle stream as the tear, assign it an initial guess, and then solve the rest of the flowsheet as if it were acyclic. Once the downstream units compute their outputs, you compare the calculated value of the torn stream to your initial guess and iteratively adjust until they coincide. Without tearing, the solver cannot establish a proper topological sequence or drive the recycle to convergence, making initialization—and ultimately steady‐state convergence—impossible.\n", "\n", - "![](HDA_flowsheet.png) \n" + "It is important to determine the tear stream for a flowsheet which will be demonstrated below.\n", + "\n", + "\n", + "![](HDA_flowsheet.png)\n", + "\n", + "Currently, there are two methods of initializing a full flowsheet: using the sequential decomposition tool, or manually propagating through the flowsheet. Both methods will be shown.\n", + "\n", + "### 5.1 Sequential Decomposition\n", + "\n", + "This section will demonstrate how to use the built-in sequential decomposition tool to initialize our flowsheet. Sequential Decomposition is a tool from pyomo where the documentation can be found here https://pyomo.readthedocs.io/en/stable/explanation/modeling/network.html#sequential-decomposition\n" ] }, { "cell_type": "markdown", "metadata": {}, - "source": [ - "Let us first create an object for the SequentialDecomposition and specify our options for this. " - ] + "source": "Let us first create an object for the SequentialDecomposition and specify our options for this. We can also create a graph for our flowsheet to determine the tear set and order." }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.330212Z", + "start_time": "2025-06-11T22:13:29.324872Z" + } + }, "source": [ "seq = SequentialDecomposition()\n", "seq.options.select_tear_method = \"heuristic\"\n", @@ -901,7 +1249,9 @@ "G = seq.create_graph(m)\n", "heuristic_tear_set = seq.tear_set_arcs(G, method=\"heuristic\")\n", "order = seq.calculation_order(G)" - ] + ], + "outputs": [], + "execution_count": 41 }, { "cell_type": "markdown", @@ -912,13 +1262,26 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.353734Z", + "start_time": "2025-06-11T22:13:29.350730Z" + } + }, "source": [ "for o in heuristic_tear_set:\n", " print(o.name)" - ] + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "fs.s03\n" + ] + } + ], + "execution_count": 42 }, { "cell_type": "markdown", @@ -929,15 +1292,32 @@ }, { "cell_type": "code", - "execution_count": null, "metadata": { - "scrolled": true + "scrolled": true, + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.391173Z", + "start_time": "2025-06-11T22:13:29.388153Z" + } }, - "outputs": [], "source": [ "for o in order:\n", " print(o[0].name)" - ] + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "fs.I101\n", + "fs.R101\n", + "fs.F101\n", + "fs.S101\n", + "fs.C101\n", + "fs.M101\n" + ] + } + ], + "execution_count": 43 }, { "cell_type": "markdown", @@ -948,25 +1328,28 @@ "![](HDA_tear_stream.png) \n", "\n", "\n", - "The SequentialDecomposition tool has determined that the tear stream is the mixer outlet. We will need to provide a reasonable guess for this." + "The SequentialDecomposition tool has determined that the tear stream is the mixer outlet. You can see this shown in the picture of the flowsheet above as the outlet of the mixer as the two lines crossing it identifying it as the tear stream. We will need to provide a reasonable guess for this." ] }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.430684Z", + "start_time": "2025-06-11T22:13:29.426921Z" + } + }, "source": [ "tear_guesses = {\n", " \"flow_mol_phase_comp\": {\n", - " (0, \"Vap\", \"benzene\"): 1e-5,\n", - " (0, \"Vap\", \"toluene\"): 1e-5,\n", - " (0, \"Vap\", \"hydrogen\"): 0.30,\n", - " (0, \"Vap\", \"methane\"): 0.02,\n", " (0, \"Liq\", \"benzene\"): 1e-5,\n", " (0, \"Liq\", \"toluene\"): 0.30,\n", - " (0, \"Liq\", \"hydrogen\"): 1e-5,\n", " (0, \"Liq\", \"methane\"): 1e-5,\n", + " (0, \"Liq\", \"hydrogen\"): 1e-5,\n", + " (0, \"Vap\", \"benzene\"): 1e-5,\n", + " (0, \"Vap\", \"toluene\"): 1e-5,\n", + " (0, \"Vap\", \"methane\"): 0.02,\n", + " (0, \"Vap\", \"hydrogen\"): 0.30,\n", " },\n", " \"temperature\": {0: 303},\n", " \"pressure\": {0: 350000},\n", @@ -974,7 +1357,9 @@ "\n", "# Pass the tear_guess to the SD tool\n", "seq.set_guesses_for(m.fs.H101.inlet, tear_guesses)" - ] + ], + "outputs": [], + "execution_count": 44 }, { "cell_type": "markdown", @@ -985,9 +1370,12 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.465203Z", + "start_time": "2025-06-11T22:13:29.462031Z" + } + }, "source": [ "def function(unit):\n", " try:\n", @@ -996,7 +1384,9 @@ " except InitializationError:\n", " solver = get_solver()\n", " solver.solve(unit)" - ] + ], + "outputs": [], + "execution_count": 45 }, { "cell_type": "markdown", @@ -1007,165 +1397,1060 @@ }, { "cell_type": "code", - "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.505027Z", + "start_time": "2025-06-11T22:13:29.501933Z" + } + }, + "source": "# seq.run(m, function)", + "outputs": [], + "execution_count": 46 + }, + { "metadata": {}, + "cell_type": "markdown", + "source": [ + "### 5.2 Manual Propogation Method\n", + "\n", + "This method uses a more direct approach to initialize the flowsheet, utilizing the updated initalizer method and propogating manually throught the flowsheet and solving for the tear stream directly.\n", + "Lets first import a helper function that will help us manually propagate and step through the flowsheet" + ] + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.536579Z", + "start_time": "2025-06-11T22:13:29.533074Z" + } + }, + "cell_type": "code", + "source": "from idaes.core.util.initialization import propagate_state", "outputs": [], + "execution_count": 47 + }, + { + "metadata": {}, + "cell_type": "markdown", "source": [ - "seq.run(m, function)" + "Now we can setup our initial guesses for the tear stream which we know is the outlet of the `Mixer` or the inlet of the `Heater`. We can use the same initial guesses used in the first method. We also want to ensure that the degrees of freedom are consistent while we manually intialize the model.\n", + "\n", + "We will first ensure that are current degrees of freedom is still zero" ] }, { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.583098Z", + "start_time": "2025-06-11T22:13:29.553382Z" + } + }, + "cell_type": "code", + "source": "print(f\"The DOF is {degrees_of_freedom(m)} initially\")", + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The DOF is 0 initially\n" + ] + } + ], + "execution_count": 48 + }, + { + "metadata": {}, "cell_type": "markdown", + "source": "Now we can manually deactivate the tear stream, creating a separation between the `Mixer` and `Heater`. This should reduce the degrees of freedom by 10 since the inlet of the `Heater` now contains no values to solve the unit model. To deactivate a stream, simply use `m.fs.s03_expanded.deactivate()`. This expanded stream is just a different version of the `Arc` stream that is able to be deactivated." + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.633917Z", + "start_time": "2025-06-11T22:13:29.610932Z" + } + }, + "cell_type": "code", + "source": [ + "m.fs.s03_expanded.deactivate()\n", + "\n", + "print(f\"The DOF is {degrees_of_freedom(m)} after deactivating the tear stream\")" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The DOF is 10 after deactivating the tear stream\n" + ] + } + ], + "execution_count": 49 + }, + { "metadata": {}, + "cell_type": "markdown", + "source": "Now we can provide the `Heater` inlet 10 guess values to bring the degrees of freedom back to 0 and start the manual initialization process. We can run this convenient loop to assign each of these guesses to the inlet of the heater." + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.702707Z", + "start_time": "2025-06-11T22:13:29.654459Z" + } + }, + "cell_type": "code", + "source": [ + "tear_guesses = {\n", + " \"flow_mol_phase_comp\": {\n", + " (0, \"Liq\", \"benzene\"): 1e-5,\n", + " (0, \"Liq\", \"toluene\"): 0.30,\n", + " (0, \"Liq\", \"methane\"): 1e-5,\n", + " (0, \"Liq\", \"hydrogen\"): 1e-5,\n", + " (0, \"Vap\", \"benzene\"): 1e-5,\n", + " (0, \"Vap\", \"toluene\"): 1e-5,\n", + " (0, \"Vap\", \"methane\"): 0.02,\n", + " (0, \"Vap\", \"hydrogen\"): 0.30,\n", + "\n", + " },\n", + " \"temperature\": {0: 303},\n", + " \"pressure\": {0: 350000},\n", + "}\n", + "\n", + "for k, v in tear_guesses.items():\n", + " for k1, v1 in v.items():\n", + " getattr(m.fs.s03.destination, k)[k1].fix(v1)\n", + "\n", + "DOF_initial = degrees_of_freedom(m)\n", + "print(f\"The DOF is {degrees_of_freedom(m)} after providing the initial guesses\")" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The DOF is 0 after providing the initial guesses\n" + ] + } + ], + "execution_count": 50 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "The next step is to manually initialize each unit model starting from the `Heater` and then propagate the connection between it and the next unit model. This manual process ensures a strict order to the user's specification if that is desired. The current standard for initializing a unit model is to use an initializer object most compatible for that unit model. This can most often be done by utilizing the `default_initializer()` method attached to the unit model and then to call the `initialize()` method with the unit model as the argument." + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:31.347435Z", + "start_time": "2025-06-11T22:13:29.712721Z" + } + }, + "cell_type": "code", + "source": [ + "m.fs.H101.default_initializer().initialize(m.fs.H101) # Initialize Heater\n", + "propagate_state(m.fs.s04) # Establish connection between Heater and Reactor\n", + "m.fs.R101.default_initializer().initialize(m.fs.R101) # Initialize Reactor\n", + "propagate_state(m.fs.s05) # Establish connection between Reactor and First Flash Unit\n", + "m.fs.F101.default_initializer().initialize(m.fs.F101) # Initialize First Flash Unit\n", + "propagate_state(m.fs.s06) # Establish connection between First Flash Unit and Splitter\n", + "propagate_state(m.fs.s07) # Establish connection between First Flash Unit and Second Flash Unit\n", + "m.fs.S101.default_initializer().initialize(m.fs.S101) # Initialize Splitter\n", + "propagate_state(m.fs.s08) # Establish connection between Splitter and Compressor\n", + "m.fs.C101.default_initializer().initialize(m.fs.C101) # Initialize Compressor\n", + "propagate_state(m.fs.s09) # Establish connection between Compressor and Mixer\n", + "m.fs.I101.default_initializer().initialize(m.fs.I101) # Initialize Toluene Inlet\n", + "propagate_state(m.fs.s01) # Establish connection between Toluene Inlet and Mixer\n", + "m.fs.I102.default_initializer().initialize(m.fs.I102) # Initialize Hydrogen Inlet\n", + "propagate_state(m.fs.s02) # Establish connection between Hydrogen Inlet and Mixer\n", + "m.fs.M101.default_initializer().initialize(m.fs.M101) # Initialize Mixer\n", + "propagate_state(m.fs.s03) # Establish connection between Mixer and Heater\n", + "m.fs.F102.default_initializer().initialize(m.fs.F102) # Initialize Second Flash Unit\n", + "propagate_state(m.fs.s10) # Establish connection between Second Flash Unit and Benzene Product\n", + "propagate_state(m.fs.s11) # Establish connection between Second Flash Unit and Toluene Product\n", + "propagate_state(m.fs.s12) # Establish connection between Splitter and Purge Product\n" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2025-06-11 16:13:29 [INFO] idaes.init.fs.H101.control_volume.properties_in: Initialization Complete\n", + "2025-06-11 16:13:30 [INFO] idaes.init.fs.H101.control_volume.properties_out: Initialization Complete\n", + "2025-06-11 16:13:30 [INFO] idaes.init.fs.R101.control_volume.properties_in: Initialization Complete\n", + "2025-06-11 16:13:30 [INFO] idaes.init.fs.R101.control_volume.properties_out: Initialization Complete\n", + "2025-06-11 16:13:30 [INFO] idaes.init.fs.F101.control_volume.properties_in: Initialization Complete\n", + "2025-06-11 16:13:30 [INFO] idaes.init.fs.F101.control_volume.properties_out: Initialization Complete\n", + "2025-06-11 16:13:30 [INFO] idaes.init.fs.S101.mixed_state: Initialization Complete\n", + "2025-06-11 16:13:30 [INFO] idaes.init.fs.S101.purge_state: Initialization Complete\n", + "2025-06-11 16:13:30 [INFO] idaes.init.fs.S101.recycle_state: Initialization Complete\n", + "2025-06-11 16:13:30 [INFO] idaes.init.fs.S101: Initialization Step 2 Complete: optimal - \n", + "2025-06-11 16:13:30 [INFO] idaes.init.fs.C101.control_volume.properties_in: Initialization Complete\n", + "2025-06-11 16:13:31 [INFO] idaes.init.fs.C101.control_volume.properties_out: Initialization Complete\n", + "2025-06-11 16:13:31 [INFO] idaes.init.fs.I101.properties: Initialization Complete\n", + "2025-06-11 16:13:31 [INFO] idaes.init.fs.I102.properties: Initialization Complete\n", + "2025-06-11 16:13:31 [INFO] idaes.init.fs.M101.inlet_1_state: Initialization Complete\n", + "2025-06-11 16:13:31 [INFO] idaes.init.fs.M101.inlet_2_state: Initialization Complete\n", + "2025-06-11 16:13:31 [INFO] idaes.init.fs.M101.inlet_3_state: Initialization Complete\n", + "2025-06-11 16:13:31 [INFO] idaes.init.fs.M101.mixed_state: Initialization Complete\n", + "2025-06-11 16:13:31 [INFO] idaes.init.fs.M101: Initialization Complete: optimal - \n", + "2025-06-11 16:13:31 [INFO] idaes.init.fs.F102.control_volume.properties_in: Initialization Complete\n", + "2025-06-11 16:13:31 [INFO] idaes.init.fs.F102.control_volume.properties_out: Initialization Complete\n" + ] + } + ], + "execution_count": 51 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "Now we solve the system to allow the outlet of the mixer to reach a converged congruence with the inlet of the heater." + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:31.935414Z", + "start_time": "2025-06-11T22:13:31.357025Z" + } + }, + "cell_type": "code", + "source": [ + "solver = get_solver()\n", + "results = solver.solve(m, tee=True)" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "WARNING: model contains export suffix 'scaling_factor' that contains 40\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "Ipopt 3.13.2: nlp_scaling_method=gradient-based\n", + "tol=1e-06\n", + "max_iter=200\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...: 1112\n", + "Number of nonzeros in inequality constraint Jacobian.: 0\n", + "Number of nonzeros in Lagrangian Hessian.............: 999\n", + "\n", + "Total number of variables............................: 380\n", + " variables with only lower bounds: 0\n", + " variables with lower and upper bounds: 186\n", + " variables with only upper bounds: 0\n", + "Total number of equality constraints.................: 380\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 1.24e+05 0.00e+00 -1.0 0.00e+00 - 0.00e+00 0.00e+00 0\n", + " 1 0.0000000e+00 1.24e+05 2.12e+00 -1.0 1.99e+05 - 1.39e-01 3.68e-04h 10\n", + " 2 0.0000000e+00 1.24e+05 5.49e+00 -1.0 1.99e+05 - 1.43e-01 3.66e-04h 10\n", + " 3 0.0000000e+00 1.24e+05 4.13e+01 -1.0 2.00e+05 - 9.51e-01 3.64e-04h 10\n", + " 4 0.0000000e+00 1.24e+05 7.47e+01 -1.0 2.00e+05 - 1.77e-01 3.63e-04h 10\n", + " 5 0.0000000e+00 1.24e+05 4.07e+02 -1.0 2.01e+05 - 9.90e-01 3.61e-04h 10\n", + " 6 0.0000000e+00 1.24e+05 6.97e+02 -1.0 2.01e+05 - 1.63e-01 3.60e-04h 10\n", + " 7 0.0000000e+00 1.24e+05 3.75e+03 -1.0 2.02e+05 - 9.90e-01 3.58e-04h 10\n", + " 8 0.0000000e+00 1.24e+05 6.44e+03 -1.0 2.02e+05 - 1.63e-01 3.57e-04h 10\n", + " 9 0.0000000e+00 1.24e+05 3.51e+04 -1.0 2.03e+05 - 1.00e+00 3.55e-04h 10\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 10 0.0000000e+00 1.24e+05 6.05e+04 -1.0 2.04e+05 - 1.62e-01 3.54e-04h 10\n", + " 11 0.0000000e+00 2.01e+06 2.73e+05 -1.0 2.04e+05 - 1.00e+00 1.80e-01w 1\n", + " 12 0.0000000e+00 2.01e+06 3.91e+07 -1.0 7.38e+05 - 6.21e-02 5.22e-04w 1\n", + " 13 0.0000000e+00 2.01e+06 6.94e+11 -1.0 7.50e+05 - 9.13e-02 5.14e-06w 1\n", + " 14 0.0000000e+00 1.24e+05 3.32e+05 -1.0 6.22e+04 - 1.00e+00 3.52e-04h 9\n", + " 15 0.0000000e+00 1.24e+05 5.43e+05 -1.0 2.05e+05 - 1.41e-01 3.51e-04h 10\n", + " 16 0.0000000e+00 1.24e+05 3.01e+06 -1.0 2.05e+05 - 1.00e+00 3.49e-04h 10\n", + " 17 0.0000000e+00 1.24e+05 4.76e+06 -1.0 2.06e+05 - 1.28e-01 3.48e-04h 10\n", + " 18 0.0000000e+00 1.24e+05 2.66e+07 -1.0 2.06e+05 - 1.00e+00 3.46e-04h 10\n", + " 19 0.0000000e+00 1.24e+05 4.23e+07 -1.0 2.07e+05 - 1.28e-01 3.45e-04h 10\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 20 0.0000000e+00 1.24e+05 2.38e+08 -1.0 2.08e+05 - 1.00e+00 3.43e-04h 10\n", + " 21 0.0000000e+00 1.24e+05 3.80e+08 -1.0 2.08e+05 - 1.28e-01 3.42e-04h 10\n", + " 22 0.0000000e+00 1.23e+05 2.16e+09 -1.0 2.09e+05 - 1.00e+00 3.40e-04h 10\n", + " 23 0.0000000e+00 1.23e+05 3.45e+09 -1.0 2.09e+05 - 1.28e-01 3.39e-04h 10\n", + " 24 0.0000000e+00 1.96e+06 1.26e+10 -1.0 2.10e+05 - 7.69e-01 1.73e-01w 1\n", + " 25 0.0000000e+00 1.96e+06 1.91e+12 -1.0 7.43e+05 - 6.14e-02 5.08e-04w 1\n", + " 26 0.0000000e+00 1.96e+06 7.01e+16 -1.0 7.55e+05 - 1.84e-01 5.01e-06w 1\n", + " 27 0.0000000e+00 1.23e+05 1.60e+10 -1.0 1.00e+05 - 7.69e-01 3.37e-04h 9\n", + " 28 0.0000000e+00 1.23e+05 2.61e+10 -1.0 2.10e+05 - 1.33e-01 3.36e-04h 10\n", + " 29 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.11e+05 - 1.00e+00 3.35e-04h 10\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 30 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.11e+05 - 1.27e-01 3.33e-04h 10\n", + " 31 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.12e+05 - 5.83e-01 3.32e-04h 10\n", + " 32 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.13e+05 - 1.41e-01 3.30e-04h 10\n", + " 33 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.13e+05 - 1.00e+00 3.29e-04h 10\n", + " 34 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.14e+05 - 1.26e-01 3.28e-04h 10\n", + " 35 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.14e+05 - 6.16e-01 3.26e-04h 10\n", + " 36 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.15e+05 - 1.38e-01 3.25e-04h 10\n", + " 37 0.0000000e+00 1.90e+06 5.27e+11 -1.0 2.15e+05 - 1.00e+00 1.66e-01w 1\n", + " 38 0.0000000e+00 1.90e+06 6.09e+13 -1.0 2.72e+05 - 1.39e-01 1.43e-03w 1\n", + " 39 0.0000000e+00 1.90e+06 1.06e+17 -1.0 7.68e+05 - 9.45e-02 4.82e-06w 1\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 40 0.0000000e+00 1.23e+05 1.04e+11 -1.0 7.68e+05 - 1.00e+00 3.23e-04h 9\n", + " 41 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.16e+05 - 1.26e-01 3.22e-04h 10\n", + " 42 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.17e+05 - 6.03e-01 3.21e-04h 10\n", + " 43 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.17e+05 - 1.38e-01 3.19e-04h 10\n", + " 44 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.18e+05 - 1.00e+00 3.18e-04h 10\n", + " 45 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.18e+05 - 1.25e-01 3.17e-04h 10\n", + " 46 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.19e+05 - 6.03e-01 3.15e-04h 10\n", + " 47 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.20e+05 - 1.38e-01 3.14e-04h 10\n", + " 48 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.20e+05 - 1.00e+00 3.13e-04h 10\n", + " 49 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.21e+05 - 1.25e-01 3.11e-04h 10\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 50 0.0000000e+00 1.85e+06 3.37e+11 -1.0 2.21e+05 - 5.99e-01 1.59e-01w 1\n", + " 51 0.0000000e+00 1.85e+06 5.67e+13 -1.0 7.53e+05 - 6.18e-02 4.82e-04w 1\n", + " 52 0.0000000e+00 1.85e+06 1.08e+17 -1.0 7.64e+05 - 2.20e-01 4.75e-06w 1\n", + " 53 0.0000000e+00 1.23e+05 1.04e+11 -1.0 7.64e+05 - 5.99e-01 3.10e-04h 9\n", + " 54 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.22e+05 - 1.38e-01 3.09e-04h 10\n", + " 55 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.22e+05 - 1.00e+00 3.07e-04h 10\n", + " 56 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.23e+05 - 1.24e-01 3.06e-04h 10\n", + " 57 0.0000000e+00 1.22e+05 1.04e+11 -1.0 2.24e+05 - 5.97e-01 3.05e-04h 10\n", + " 58 0.0000000e+00 1.22e+05 1.04e+11 -1.0 2.24e+05 - 1.37e-01 3.04e-04h 10\n", + " 59 0.0000000e+00 1.22e+05 1.04e+11 -1.0 2.25e+05 - 1.00e+00 3.02e-04h 10\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 60 0.0000000e+00 1.22e+05 1.04e+11 -1.0 2.25e+05 - 1.24e-01 3.01e-04h 10\n", + " 61 0.0000000e+00 1.22e+05 1.04e+11 -1.0 2.26e+05 - 5.94e-01 3.00e-04h 10\n", + " 62 0.0000000e+00 1.22e+05 1.04e+11 -1.0 2.26e+05 - 1.37e-01 2.98e-04h 10\n", + " 63 0.0000000e+00 1.79e+06 6.03e+11 -1.0 2.27e+05 - 1.00e+00 1.52e-01w 1\n", + " 64 0.0000000e+00 1.79e+06 9.13e+13 -1.0 7.58e+05 - 6.05e-02 4.69e-04w 1\n", + " 65 0.0000000e+00 1.79e+06 1.10e+17 -1.0 7.68e+05 - 1.20e-01 4.63e-06w 1\n", + " 66 0.0000000e+00 1.22e+05 1.04e+11 -1.0 7.69e+05 - 1.00e+00 2.97e-04h 9\n", + " 67 0.0000000e+00 1.22e+05 1.04e+11 -1.0 2.28e+05 - 1.23e-01 2.96e-04h 10\n", + " 68 0.0000000e+00 1.22e+05 1.04e+11 -1.0 2.28e+05 - 5.91e-01 2.95e-04h 10\n", + " 69 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.29e+05 - 1.36e-01 2.93e-04h 10\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 70 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.29e+05 - 1.00e+00 2.92e-04h 10\n", + " 71 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.30e+05 - 1.23e-01 2.91e-04h 10\n", + " 72 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.31e+05 - 5.89e-01 2.90e-04h 10\n", + " 73 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.31e+05 - 1.36e-01 2.89e-04h 10\n", + " 74 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.32e+05 - 1.00e+00 2.87e-04h 10\n", + " 75 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.32e+05 - 1.23e-01 2.86e-04h 10\n", + " 76 0.0000000e+00 1.74e+06 3.75e+11 -1.0 2.33e+05 - 5.86e-01 1.46e-01w 1\n", + " 77 0.0000000e+00 1.74e+06 6.57e+13 -1.0 7.62e+05 - 6.18e-02 4.58e-04w 1\n", + " 78 0.0000000e+00 1.74e+06 1.12e+17 -1.0 7.73e+05 - 2.30e-01 4.51e-06w 1\n", + " 79 0.0000000e+00 1.22e+05 1.05e+11 -1.0 7.73e+05 - 5.86e-01 2.85e-04h 9\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 80 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.34e+05 - 1.36e-01 2.84e-04h 10\n", + " 81 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.34e+05 - 1.00e+00 2.83e-04h 10\n", + " 82 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.35e+05 - 1.22e-01 2.81e-04h 10\n", + " 83 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.35e+05 - 5.83e-01 2.80e-04h 10\n", + " 84 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.36e+05 - 1.35e-01 2.79e-04h 10\n", + " 85 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.37e+05 - 1.00e+00 2.78e-04h 10\n", + " 86 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.37e+05 - 1.22e-01 2.77e-04h 10\n", + " 87 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.38e+05 - 5.81e-01 2.76e-04h 10\n", + " 88 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.38e+05 - 1.35e-01 2.74e-04h 10\n", + " 89 0.0000000e+00 1.69e+06 6.88e+11 -1.0 2.39e+05 - 1.00e+00 1.40e-01w 1\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 90 0.0000000e+00 1.69e+06 1.08e+14 -1.0 7.66e+05 - 6.03e-02 4.46e-04w 1\n", + " 91 0.0000000e+00 1.69e+06 1.14e+17 -1.0 7.77e+05 - 1.22e-01 4.40e-06w 1\n", + " 92 0.0000000e+00 1.22e+05 1.05e+11 -1.0 7.77e+05 - 1.00e+00 2.73e-04h 9\n", + " 93 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.39e+05 - 1.21e-01 2.72e-04h 10\n", + " 94 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.40e+05 - 5.78e-01 2.71e-04h 10\n", + " 95 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.41e+05 - 1.34e-01 2.70e-04h 10\n", + " 96 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.41e+05 - 1.00e+00 2.69e-04h 10\n", + " 97 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.42e+05 - 1.21e-01 2.68e-04h 10\n", + " 98 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.42e+05 - 5.76e-01 2.67e-04h 10\n", + " 99 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.43e+05 - 1.34e-01 2.65e-04h 10\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 100 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.44e+05 - 1.00e+00 2.64e-04h 10\n", + " 101 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.44e+05 - 1.20e-01 2.63e-04h 10\n", + " 102 0.0000000e+00 1.64e+06 4.17e+11 -1.0 2.45e+05 - 5.73e-01 1.34e-01w 1\n", + " 103 0.0000000e+00 1.64e+06 7.61e+13 -1.0 7.71e+05 - 6.17e-02 4.35e-04w 1\n", + " 104 0.0000000e+00 1.64e+06 1.17e+17 -1.0 7.81e+05 - 2.38e-01 4.29e-06w 1\n", + " 105 0.0000000e+00 1.21e+05 1.05e+11 -1.0 7.81e+05 - 5.73e-01 2.62e-04h 9\n", + " 106 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.45e+05 - 1.34e-01 2.61e-04h 10\n", + " 107 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.46e+05 - 1.00e+00 2.60e-04h 10\n", + " 108 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.47e+05 - 1.20e-01 2.59e-04h 10\n", + " 109 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.47e+05 - 5.71e-01 2.58e-04h 10\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 110 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.48e+05 - 1.33e-01 2.57e-04h 10\n", + " 111 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.49e+05 - 1.00e+00 2.56e-04h 10\n", + " 112 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.49e+05 - 1.19e-01 2.55e-04h 10\n", + " 113 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.50e+05 - 5.68e-01 2.54e-04h 10\n", + " 114 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.50e+05 - 1.33e-01 2.53e-04h 10\n", + " 115 0.0000000e+00 1.59e+06 7.85e+11 -1.0 2.51e+05 - 1.00e+00 1.29e-01w 1\n", + " 116 0.0000000e+00 1.59e+06 1.28e+14 -1.0 7.75e+05 - 6.01e-02 4.25e-04w 1\n", + " 117 0.0000000e+00 1.59e+06 1.19e+17 -1.0 7.85e+05 - 1.23e-01 4.19e-06w 1\n", + " 118 0.0000000e+00 1.21e+05 1.05e+11 -1.0 7.85e+05 - 1.00e+00 2.52e-04h 9\n", + " 119 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.52e+05 - 1.19e-01 2.51e-04h 10\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 120 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.52e+05 - 5.66e-01 2.50e-04h 10\n", + " 121 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.53e+05 - 1.32e-01 2.49e-04h 10\n", + " 122 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.53e+05 - 1.00e+00 2.47e-04h 10\n", + " 123 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.54e+05 - 1.18e-01 4.93e-04h 9\n", + " 124 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.55e+05 - 5.60e-01 4.89e-04h 9\n", + " 125 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.56e+05 - 1.32e-01 4.85e-04h 9\n", + " 126 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.58e+05 - 1.00e+00 4.81e-04h 9\n", + " 127 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.59e+05 - 1.18e-01 4.77e-04h 9\n", + " 128 0.0000000e+00 1.52e+06 4.74e+11 -1.0 2.60e+05 - 5.55e-01 1.21e-01w 1\n", + " 129 0.0000000e+00 1.52e+06 9.09e+13 -1.0 7.80e+05 - 6.17e-02 4.09e-04w 1\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 130 0.0000000e+00 1.52e+06 1.22e+17 -1.0 7.90e+05 - 2.48e-01 4.04e-06w 1\n", + " 131 0.0000000e+00 1.20e+05 1.05e+11 -1.0 7.90e+05 - 5.55e-01 4.73e-04h 8\n", + " 132 0.0000000e+00 1.20e+05 1.05e+11 -1.0 2.61e+05 - 1.31e-01 4.69e-04h 9\n", + " 133 0.0000000e+00 1.20e+05 1.05e+11 -1.0 2.63e+05 - 1.00e+00 1.86e-03h 7\n", + " 134 0.0000000e+00 1.20e+05 1.05e+11 -1.0 2.67e+05 - 1.16e-01 1.80e-03h 7\n", + " 135 0.0000000e+00 1.20e+05 1.05e+11 -1.0 2.72e+05 - 5.16e-01 1.74e-03h 7\n", + " 136 0.0000000e+00 1.19e+05 1.05e+11 -1.0 2.77e+05 - 1.29e-01 3.38e-03h 6\n", + " 137 0.0000000e+00 1.19e+05 1.05e+11 -1.0 2.87e+05 - 1.00e+00 3.17e-03h 6\n", + " 138 0.0000000e+00 1.19e+05 1.06e+11 -1.0 2.98e+05 - 1.10e-01 2.97e-03h 6\n", + " 139 0.0000000e+00 1.18e+05 1.06e+11 -1.0 3.08e+05 - 4.88e-01 2.79e-03h 6\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 140 0.0000000e+00 1.18e+05 1.07e+11 -1.0 3.18e+05 - 1.22e-01 5.25e-03h 5\n", + " 141 0.0000000e+00 1.01e+06 1.89e+12 -1.0 3.38e+05 - 1.00e+00 7.42e-02w 1\n", + " 142 0.0000000e+00 1.01e+06 3.99e+14 -1.0 8.16e+05 - 5.93e-02 3.07e-04w 1\n", + " 143 0.0000000e+00 1.01e+06 1.56e+17 -1.0 8.24e+05 - 1.44e-01 3.04e-06w 1\n", + " 144 0.0000000e+00 1.17e+05 1.08e+11 -1.0 8.24e+05 - 1.00e+00 4.64e-03h 4\n", + " 145 0.0000000e+00 1.17e+05 1.08e+11 -1.0 3.59e+05 - 1.01e-01 2.06e-03h 6\n", + " 146 0.0000000e+00 1.17e+05 1.08e+11 -1.0 3.69e+05 - 4.67e-01 1.94e-03h 6\n", + " 147 0.0000000e+00 1.17e+05 1.08e+11 -1.0 3.79e+05 - 1.12e-01 1.83e-03h 6\n", + " 148 0.0000000e+00 1.16e+05 1.09e+11 -1.0 3.89e+05 - 1.00e+00 8.66e-04h 7\n", + " 149 0.0000000e+00 1.16e+05 1.09e+11 -1.0 3.94e+05 - 9.61e-02 8.42e-04h 7\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 150 0.0000000e+00 1.16e+05 1.09e+11 -1.0 3.99e+05 - 4.61e-01 4.10e-04h 8\n", + " 151 0.0000000e+00 1.16e+05 1.09e+11 -1.0 4.01e+05 - 1.09e-01 4.04e-04h 8\n", + " 152 0.0000000e+00 1.16e+05 1.09e+11 -1.0 4.04e+05 - 1.00e+00 3.98e-04h 8\n", + " 153 0.0000000e+00 1.16e+05 1.09e+11 -1.0 4.06e+05 - 9.46e-02 9.83e-05h 10\n", + " 154 0.0000000e+00 6.92e+05 1.57e+12 -1.0 4.07e+05 - 4.54e-01 5.01e-02w 1\n", + " 155 0.0000000e+00 6.92e+05 4.69e+14 -1.0 8.35e+05 - 6.23e-02 2.42e-04w 1\n", + " 156 0.0000000e+00 6.92e+05 1.93e+17 -1.0 8.42e+05 - 2.97e-01 2.40e-06w 1\n", + " 157 0.0000000e+00 1.16e+05 1.09e+11 -1.0 8.42e+05 - 4.54e-01 9.79e-05h 9\n", + " 158 0.0000000e+00 1.16e+05 1.09e+11 -1.0 4.08e+05 - 1.09e-01 9.76e-05h 10\n", + " 159 0.0000000e+00 1.16e+05 1.09e+11 -1.0 4.08e+05 - 1.00e+00 9.73e-05h 10\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 160 0.0000000e+00 1.16e+05 1.09e+11 -1.0 4.09e+05 - 9.43e-02 9.70e-05h 10\n", + " 161 0.0000000e+00 1.16e+05 1.09e+11 -1.0 4.09e+05 - 4.54e-01 4.83e-05h 11\n", + " 162 0.0000000e+00 1.16e+05 1.09e+11 -1.0 4.10e+05 - 1.08e-01 4.82e-05h 11\n", + " 163 0.0000000e+00 1.16e+05 1.09e+11 -1.0 4.10e+05 - 1.00e+00 1.20e-05h 13\n", + " 164 0.0000000e+00 1.16e+05 1.09e+11 -1.0 4.10e+05 - 9.42e-02 1.20e-05h 13\n", + " 165 0.0000000e+00 1.16e+05 1.09e+11 -1.0 4.10e+05 - 4.55e-01 9.62e-05h 10\n", + " 166 0.0000000e+00 1.16e+05 1.09e+11 -1.0 4.11e+05 - 1.08e-01 9.59e-05h 10\n", + " 167 0.0000000e+00 6.75e+05 3.70e+12 -1.0 4.11e+05 - 1.00e+00 4.89e-02w 1\n", + " 168 0.0000000e+00 6.74e+05 9.73e+14 -1.0 8.36e+05 - 5.90e-02 2.39e-04w 1\n", + " 169 0.0000000e+00 6.74e+05 1.96e+17 -1.0 8.43e+05 - 1.50e-01 2.37e-06w 1\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 170 0.0000000e+00 1.16e+05 1.09e+11 -1.0 8.43e+05 - 1.00e+00 9.56e-05h 9\n", + " 171 0.0000000e+00 1.16e+05 1.09e+11 -1.0 4.12e+05 - 9.39e-02 2.38e-05h 12\n", + " 172 0.0000000e+00 1.16e+05 1.09e+11 -1.0 4.12e+05 - 4.52e-01 1.19e-05h 13\n", + " 173r 0.0000000e+00 1.16e+05 1.00e+03 0.6 0.00e+00 - 0.00e+00 3.72e-07R 18\n", + " 174r 0.0000000e+00 1.25e+05 1.78e+03 0.6 3.70e+04 - 1.19e-02 1.34e-03f 1\n", + " 175r 0.0000000e+00 1.10e+05 1.54e+03 0.6 7.30e+03 - 3.61e-03 8.33e-03f 1\n", + " 176r 0.0000000e+00 1.02e+05 4.12e+03 0.6 5.25e+03 - 6.33e-02 8.82e-03f 1\n", + " 177r 0.0000000e+00 9.63e+04 4.43e+03 0.6 4.50e+03 - 3.61e-02 2.36e-02f 1\n", + " 178r 0.0000000e+00 9.20e+04 1.63e+04 0.6 3.97e+03 - 2.78e-01 6.99e-02f 1\n", + " 179r 0.0000000e+00 8.79e+04 3.34e+04 0.6 4.66e+02 - 8.68e-01 3.38e-01f 1\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 180r 0.0000000e+00 1.06e+05 3.33e+02 0.6 1.05e+02 - 1.00e+00 1.00e+00f 1\n", + " 181r 0.0000000e+00 1.05e+05 2.26e+02 0.6 2.72e+01 - 1.00e+00 1.00e+00f 1\n", + " 182r 0.0000000e+00 9.53e+04 2.03e+03 -0.1 2.12e+02 - 1.00e+00 7.99e-01f 1\n", + " 183r 0.0000000e+00 8.64e+04 8.95e+01 -0.1 8.37e+01 - 1.00e+00 1.00e+00f 1\n", + " 184r 0.0000000e+00 8.52e+04 9.30e-01 -0.1 4.13e+01 - 1.00e+00 1.00e+00h 1\n", + " 185r 0.0000000e+00 7.70e+04 7.43e+02 -2.2 2.70e+02 - 8.39e-01 7.19e-01f 1\n", + " 186r 0.0000000e+00 7.27e+04 1.42e+03 -2.2 1.95e+03 - 9.32e-01 6.47e-01f 1\n", + " 187r 0.0000000e+00 7.36e+04 5.69e+02 -2.2 4.90e+02 - 9.15e-01 6.73e-01f 1\n", + " 188r 0.0000000e+00 7.91e+04 2.05e+02 -2.2 2.75e+02 - 1.00e+00 8.69e-01f 1\n", + " 189r 0.0000000e+00 7.92e+04 1.27e+03 -2.2 1.37e+02 - 8.68e-01 1.06e-01f 1\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 190r 0.0000000e+00 7.99e+04 8.09e+01 -2.2 1.45e+02 - 1.00e+00 1.00e+00f 1\n", + " 191r 0.0000000e+00 8.00e+04 1.81e+01 -2.2 2.82e+00 - 1.00e+00 1.00e+00f 1\n", + " 192r 0.0000000e+00 8.00e+04 6.93e-02 -2.2 6.43e-02 - 1.00e+00 1.00e+00h 1\n", + " 193r 0.0000000e+00 8.03e+04 1.79e+02 -3.3 1.75e+01 - 9.50e-01 8.57e-01f 1\n", + " 194r 0.0000000e+00 4.29e+04 3.40e+02 -3.3 8.69e+02 - 1.00e+00 6.02e-01f 1\n", + " 195r 0.0000000e+00 1.36e+04 1.76e+01 -3.3 3.71e+02 - 1.00e+00 1.00e+00f 1\n", + " 196r 0.0000000e+00 1.99e+04 5.38e-01 -3.3 7.89e+01 - 1.00e+00 1.00e+00h 1\n", + " 197r 0.0000000e+00 1.73e+04 9.03e-02 -3.3 3.20e+01 - 1.00e+00 1.00e+00h 1\n", + " 198r 0.0000000e+00 1.64e+04 1.32e-02 -3.3 1.07e+01 - 1.00e+00 1.00e+00h 1\n", + " 199r 0.0000000e+00 1.48e+04 9.83e+01 -4.9 2.40e+01 - 8.62e-01 7.80e-01f 1\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 200r 0.0000000e+00 1.44e+04 1.92e+03 -4.9 1.71e+03 - 6.01e-01 1.17e-02f 1\n", + "\n", + "Number of Iterations....: 200\n", + "\n", + " (scaled) (unscaled)\n", + "Objective...............: 0.0000000000000000e+00 0.0000000000000000e+00\n", + "Dual infeasibility......: 8.8349814638582666e+02 8.8349814638582666e+02\n", + "Constraint violation....: 3.3086142754792485e-04 1.4369018418593043e+04\n", + "Complementarity.........: 6.5630241357471754e-04 6.5630241357471754e-04\n", + "Overall NLP error.......: 3.3086142754792485e-04 1.4369018418593043e+04\n", + "\n", + "\n", + "Number of objective function evaluations = 1605\n", + "Number of objective gradient evaluations = 175\n", + "Number of equality constraint evaluations = 1605\n", + "Number of inequality constraint evaluations = 0\n", + "Number of equality constraint Jacobian evaluations = 202\n", + "Number of inequality constraint Jacobian evaluations = 0\n", + "Number of Lagrangian Hessian evaluations = 200\n", + "Total CPU secs in IPOPT (w/o function evaluations) = 0.331\n", + "Total CPU secs in NLP function evaluations = 0.025\n", + "\n", + "EXIT: Maximum Number of Iterations Exceeded.\n", + "WARNING: Loading a SolverResults object with a warning status into\n", + "model.name=\"unknown\";\n", + " - termination condition: maxIterations\n", + " - message from solver: Ipopt 3.13.2\\x3a Maximum Number of Iterations\n", + " Exceeded.\n" + ] + } + ], + "execution_count": 52 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "Now that the flowsheet is initialized, we can unfix the guesses for the `Heater` and reactive the tear stream to complete the final solve." + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:31.967629Z", + "start_time": "2025-06-11T22:13:31.943574Z" + } + }, + "cell_type": "code", + "source": [ + "for k, v in tear_guesses.items():\n", + " for k1, v1 in v.items():\n", + " getattr(m.fs.H101.inlet, k)[k1].unfix()\n", + "\n", + "m.fs.s03_expanded.activate()\n", + "print(f\"The DOF is {degrees_of_freedom(m)} after unfixing the values and reactivating the tear stream\")" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The DOF is 0 after unfixing the values and reactivating the tear stream\n" + ] + } + ], + "execution_count": 53 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "## 6 Solving the Model" + }, + { + "cell_type": "markdown", + "metadata": { + "tags": [] + }, + "source": "We have now initialized the flowsheet. Lets set up some solving options before simulating the flowsheet. We want to specify the scaling method, number of iterations, and tolerance. More specific or advanced options can be found at the documentation for IPOPT https://coin-or.github.io/Ipopt/OPTIONS.html" + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:31.982093Z", + "start_time": "2025-06-11T22:13:31.978929Z" + } + }, + "cell_type": "code", + "source": [ + "optarg = {\n", + " 'nlp_scaling_method': 'user-scaling',\n", + " 'OF_ma57_automatic_scaling': 'yes',\n", + " 'max_iter': 500,\n", + " 'tol': 1e-8,\n", + "}" + ], + "outputs": [], + "execution_count": 54 + }, + { + "metadata": { + "tags": [ + "exercise" + ] + }, + "cell_type": "markdown", "source": [ "
    \n", "Inline Exercise:\n", - "We have now initialized the flowsheet. Let us run the flowsheet in a simulation mode to look at the results. To do this, complete the last line of code where we pass the model to the solver. You will need to type the following:\n", - " \n", + "Let us run the flowsheet in a simulation mode to look at the results. To do this, complete the last line of code where we pass the model to the solver. You will need to type the following:\n", + "\n", + "solver = get_solver(solver_options=optarg)
    \n", "results = solver.solve(m, tee=True)\n", "\n", - "Use Shift+Enter to run the cell once you have typed in your code. \n", - "
    \n", - "\n" + "Use Shift+Enter to run the cell once you have typed in your code.\n", + "
    \n" ] }, { "cell_type": "code", - "execution_count": null, "metadata": { "tags": [ "exercise" - ] + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:32.012062Z", + "start_time": "2025-06-11T22:13:32.008341Z" + } }, - "outputs": [], "source": [ "# Create the solver object\n", "\n", - "\n", "# Solve the model" - ] + ], + "outputs": [], + "execution_count": 55 }, { "cell_type": "code", - "execution_count": null, "metadata": { "tags": [ "solution" - ] + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:32.320588Z", + "start_time": "2025-06-11T22:13:32.041379Z" + } }, - "outputs": [], "source": [ "# Create the solver object\n", - "from idaes.core.solvers import get_solver\n", - "\n", - "solver = get_solver()\n", + "solver = get_solver(solver_options=optarg)\n", "\n", "# Solve the model\n", - "results = solver.solve(m, tee=True)" - ] + "results = solver.solve(m, tee=True)\n" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "WARNING: model contains export suffix 'scaling_factor' that contains 30\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "Ipopt 3.13.2: nlp_scaling_method=user-scaling\n", + "tol=1e-08\n", + "max_iter=500\n", + "option_file_name=C:\\Users\\Tanner\\AppData\\Local\\Temp\\tmp1n1inahr_ipopt.opt\n", + "\n", + "Using option file \"C:\\Users\\Tanner\\AppData\\Local\\Temp\\tmp1n1inahr_ipopt.opt\".\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...: 1163\n", + "Number of nonzeros in inequality constraint Jacobian.: 0\n", + "Number of nonzeros in Lagrangian Hessian.............: 1062\n", + "\n", + "Total number of variables............................: 390\n", + " variables with only lower bounds: 0\n", + " variables with lower and upper bounds: 196\n", + " variables with only upper bounds: 0\n", + "Total number of equality constraints.................: 390\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 7.98e+04 0.00e+00 -1.0 0.00e+00 - 0.00e+00 0.00e+00 0\n", + " 1 0.0000000e+00 7.94e+04 1.91e+01 -1.0 1.01e+05 - 4.59e-03 3.67e-03h 3\n", + " 2 0.0000000e+00 7.91e+04 2.90e+01 -1.0 9.74e+04 - 1.04e-02 2.90e-03h 3\n", + " 3 0.0000000e+00 8.53e+04 3.58e+01 -1.0 1.09e+05 - 3.03e-02 2.27e-03h 3\n", + " 4 0.0000000e+00 9.38e+04 9.70e+01 -1.0 1.22e+05 - 2.53e-02 1.76e-03h 3\n", + " 5 0.0000000e+00 9.86e+04 7.07e+02 -1.0 1.32e+05 - 5.90e-02 1.36e-03h 3\n", + " 6 0.0000000e+00 1.01e+05 5.02e+03 -1.0 1.41e+05 - 2.55e-02 1.04e-03h 3\n", + " 7 0.0000000e+00 1.03e+05 1.80e+05 -1.0 1.47e+05 - 1.27e-01 7.95e-04h 3\n", + " 8 0.0000000e+00 1.04e+05 2.00e+06 -1.0 1.52e+05 - 2.40e-02 6.05e-04h 3\n", + " 9 0.0000000e+00 1.04e+05 1.82e+08 -1.0 1.56e+05 - 1.71e-01 4.59e-04h 3\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 10 0.0000000e+00 1.04e+05 3.34e+09 -1.0 1.59e+05 - 2.46e-02 3.47e-04h 3\n", + " 11 0.0000000e+00 3.24e+05 3.06e+09 -1.0 2.71e-02 10.0 5.85e-01 7.83e-01h 1\n", + " 12 0.0000000e+00 1.46e+06 3.17e+11 -1.0 3.46e+04 - 7.72e-02 1.50e-01f 2\n", + " 13 0.0000000e+00 1.46e+06 2.27e+11 -1.0 1.90e+04 - 3.66e-01 2.41e-03h 3\n", + " 14 0.0000000e+00 1.46e+06 2.32e+12 -1.0 1.90e+04 - 4.42e-01 1.83e-03h 3\n", + " 15 0.0000000e+00 1.45e+06 8.61e+13 -1.0 1.89e+04 - 4.59e-01 1.39e-03h 3\n", + " 16 0.0000000e+00 1.45e+06 5.60e+14 -1.0 1.89e+04 - 3.52e-01 2.10e-03h 2\n", + " 17 0.0000000e+00 1.45e+06 5.54e+14 -1.0 1.88e+04 - 5.07e-01 1.07e-03h 2\n", + " 18 0.0000000e+00 1.45e+06 9.90e+14 -1.0 1.88e+04 - 3.35e-01 1.09e-03h 1\n", + " 19 0.0000000e+00 1.45e+06 9.94e+16 -1.0 1.87e+04 - 9.49e-01 1.09e-05h 1\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 20 0.0000000e+00 1.44e+06 9.35e+18 -1.0 4.45e+03 - 2.73e-01 2.89e-03h 1\n", + " 21r 0.0000000e+00 1.44e+06 1.00e+03 -0.6 0.00e+00 - 0.00e+00 4.43e-07R 17\n", + " 22r 0.0000000e+00 1.89e+06 2.04e+03 -0.6 6.64e+02 - 7.96e-03 2.69e-03f 1\n", + " 23r 0.0000000e+00 1.93e+06 4.07e+03 -0.6 8.47e+02 - 1.23e-02 4.48e-03f 1\n", + " 24r 0.0000000e+00 2.28e+06 1.29e+04 -0.6 9.49e+02 - 4.42e-02 1.55e-02f 1\n", + " 25r 0.0000000e+00 4.06e+06 1.16e+04 -0.6 1.05e+03 - 5.19e-02 5.05e-02f 1\n", + " 26r 0.0000000e+00 4.03e+06 6.56e+03 -0.6 8.19e+02 - 2.50e-03 1.07e-02f 1\n", + " 27r 0.0000000e+00 4.15e+06 4.04e+04 -0.6 6.67e+02 - 9.08e-02 3.63e-02f 1\n", + " 28r 0.0000000e+00 4.11e+06 4.55e+04 -0.6 4.90e+02 - 4.20e-02 3.20e-02f 1\n", + " 29r 0.0000000e+00 4.02e+06 1.82e+05 -0.6 4.75e+02 - 2.06e-01 1.68e-02f 1\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 30r 0.0000000e+00 3.02e+06 8.10e+04 -0.6 4.67e+02 - 6.59e-02 2.37e-01f 1\n", + " 31r 0.0000000e+00 2.70e+06 1.53e+05 -0.6 3.56e+02 - 4.76e-01 4.13e-01f 1\n", + " 32r 0.0000000e+00 2.13e+06 9.21e+04 -0.6 2.09e+02 - 4.07e-01 5.13e-01f 1\n", + " 33r 0.0000000e+00 8.87e+05 5.96e+04 -0.6 1.02e+02 - 5.00e-01 5.95e-01f 1\n", + " 34r 0.0000000e+00 1.36e+05 9.02e+04 -0.6 4.12e+01 - 4.56e-01 1.00e+00f 1\n", + " 35r 0.0000000e+00 4.01e+04 7.97e+04 -0.6 4.66e+00 - 5.29e-01 7.78e-01f 1\n", + " 36r 0.0000000e+00 2.04e+04 5.00e+04 -0.6 4.15e-01 0.0 3.36e-01 7.33e-01h 1\n", + " 37r 0.0000000e+00 1.98e+04 1.42e+04 -0.6 8.68e-02 1.3 1.00e+00 1.72e-01f 1\n", + " 38r 0.0000000e+00 1.85e+04 2.28e+04 -0.6 1.70e+01 - 8.27e-01 5.31e-01f 1\n", + " 39r 0.0000000e+00 2.67e+04 6.30e+03 -0.6 1.08e+01 - 1.00e+00 1.00e+00f 1\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 40r 0.0000000e+00 1.76e+04 3.54e+02 -0.6 3.83e+00 - 1.00e+00 1.00e+00f 1\n", + " 41r 0.0000000e+00 1.66e+04 6.18e+02 -0.6 2.10e+00 - 1.00e+00 1.00e+00f 1\n", + " 42r 0.0000000e+00 1.63e+04 1.63e+00 -0.6 7.37e-02 - 1.00e+00 1.00e+00h 1\n", + " 43r 0.0000000e+00 1.82e+04 9.49e+02 -2.0 8.67e+00 - 8.62e-01 7.60e-01f 1\n", + " 44r 0.0000000e+00 2.02e+04 6.57e+02 -2.0 2.22e+03 - 1.00e+00 7.24e-01f 1\n", + " 45r 0.0000000e+00 1.41e+04 2.64e+01 -2.0 1.93e-02 0.9 1.00e+00 1.00e+00f 1\n", + " 46r 0.0000000e+00 1.41e+04 3.99e+00 -2.0 1.76e-02 0.4 1.00e+00 1.00e+00h 1\n", + " 47r 0.0000000e+00 1.41e+04 5.62e-02 -2.0 3.33e-02 -0.1 1.00e+00 1.00e+00h 1\n", + " 48r 0.0000000e+00 1.42e+04 3.60e+02 -4.4 9.99e-02 -0.6 9.21e-01 7.93e-01f 1\n", + " 49r 0.0000000e+00 4.27e+04 2.99e+03 -4.4 1.29e+00 -1.1 8.82e-01 6.65e-01f 1\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 50r 0.0000000e+00 6.63e+04 1.41e+02 -4.4 3.88e+00 -1.5 9.39e-01 9.16e-01f 1\n", + " 51r 0.0000000e+00 5.62e+04 5.64e+02 -4.4 1.16e+01 -2.0 1.00e+00 1.56e-01f 1\n", + " 52r 0.0000000e+00 5.59e+04 9.07e+02 -4.4 4.50e+04 - 7.98e-02 5.51e-03f 1\n", + " 53r 0.0000000e+00 5.59e+04 1.82e+03 -4.4 2.65e+03 - 1.77e-01 2.25e-05f 1\n", + " 54r 0.0000000e+00 4.87e+04 1.59e+03 -4.4 2.65e+03 - 1.12e-01 1.38e-01f 1\n", + " 55r 0.0000000e+00 4.71e+04 2.05e+03 -4.4 2.28e+03 - 5.83e-01 3.36e-02f 1\n", + " 56r 0.0000000e+00 4.59e+04 2.00e+03 -4.4 2.21e+03 - 2.61e-02 2.52e-02f 1\n", + " 57r 0.0000000e+00 4.21e+04 1.20e+03 -4.4 2.15e+03 - 3.82e-01 9.47e-02f 1\n", + " 58r 0.0000000e+00 3.52e+04 1.00e+03 -4.4 1.95e+03 - 1.89e-01 5.87e-01f 1\n", + " 59r 0.0000000e+00 1.42e+04 3.94e+00 -4.4 8.05e+02 - 1.00e+00 1.00e+00f 1\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 60r 0.0000000e+00 1.42e+04 4.68e-01 -4.4 5.35e-01 - 1.00e+00 1.00e+00h 1\n", + " 61r 0.0000000e+00 1.42e+04 1.29e-02 -4.4 3.24e-03 - 1.00e+00 1.00e+00h 1\n", + " 62r 0.0000000e+00 1.42e+04 5.72e-06 -4.4 5.73e-05 - 1.00e+00 1.00e+00h 1\n", + " 63r 0.0000000e+00 1.42e+04 1.24e+02 -6.6 2.71e-01 - 9.90e-01 8.10e-01f 1\n", + " 64r 0.0000000e+00 8.72e+05 1.88e+02 -6.6 3.29e+04 - 5.03e-01 2.23e-01f 1\n", + " 65r 0.0000000e+00 1.05e+06 2.88e+02 -6.6 2.53e+04 - 6.78e-01 1.82e-01f 1\n", + " 66r 0.0000000e+00 1.04e+06 4.92e+02 -6.6 8.71e+03 - 8.61e-01 1.13e-02f 1\n", + " 67r 0.0000000e+00 6.27e+05 7.17e+01 -6.6 8.61e+03 - 1.00e+00 8.73e-01f 1\n", + " 68r 0.0000000e+00 2.18e+04 1.48e+00 -6.6 1.09e+03 - 1.00e+00 1.00e+00h 1\n", + " 69r 0.0000000e+00 4.71e+02 5.81e-05 -6.6 7.59e-02 - 1.00e+00 1.00e+00h 1\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 70r 0.0000000e+00 4.71e+02 1.03e-08 -6.6 1.22e-05 - 1.00e+00 1.00e+00h 1\n", + " 71r 0.0000000e+00 4.71e+02 8.08e-01 -9.0 4.39e-02 - 1.00e+00 9.74e-01f 1\n", + " 72r 0.0000000e+00 2.45e+03 1.39e+02 -9.0 2.20e+05 - 3.58e-01 2.17e-03f 1\n", + " 73r 0.0000000e+00 2.39e+03 5.81e+02 -9.0 1.06e+04 - 6.57e-01 2.41e-02f 1\n", + " 74r 0.0000000e+00 2.15e+04 7.82e+02 -9.0 1.04e+04 - 1.00e+00 1.42e-01f 1\n", + " 75r 0.0000000e+00 1.15e+04 4.72e+02 -9.0 4.34e+01 - 1.00e+00 4.66e-01h 2\n", + " 76r 0.0000000e+00 6.38e+03 2.52e+02 -9.0 4.28e+01 - 1.00e+00 4.46e-01h 2\n", + " 77r 0.0000000e+00 3.26e+03 1.28e+02 -9.0 2.42e+01 - 1.00e+00 4.89e-01h 2\n", + " 78r 0.0000000e+00 2.99e+03 1.10e+02 -9.0 1.24e+01 - 1.00e+00 1.00e+00h 1\n", + " 79r 0.0000000e+00 9.57e+01 3.79e+00 -9.0 5.11e-03 - 1.00e+00 1.00e+00h 1\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 80r 0.0000000e+00 3.70e-02 1.01e-04 -9.0 3.14e-05 - 1.00e+00 1.00e+00h 1\n", + " 81r 0.0000000e+00 4.15e-06 2.24e-09 -9.0 8.12e-09 - 1.00e+00 1.00e+00h 1\n", + "\n", + "Number of Iterations....: 81\n", + "\n", + " (scaled) (unscaled)\n", + "Objective...............: 0.0000000000000000e+00 0.0000000000000000e+00\n", + "Dual infeasibility......: 0.0000000000000000e+00 0.0000000000000000e+00\n", + "Constraint violation....: 2.0579515874459978e-11 4.1499733924865723e-06\n", + "Complementarity.........: 0.0000000000000000e+00 0.0000000000000000e+00\n", + "Overall NLP error.......: 2.0579515874459978e-11 4.1499733924865723e-06\n", + "\n", + "\n", + "Number of objective function evaluations = 162\n", + "Number of objective gradient evaluations = 23\n", + "Number of equality constraint evaluations = 162\n", + "Number of inequality constraint evaluations = 0\n", + "Number of equality constraint Jacobian evaluations = 83\n", + "Number of inequality constraint Jacobian evaluations = 0\n", + "Number of Lagrangian Hessian evaluations = 81\n", + "Total CPU secs in IPOPT (w/o function evaluations) = 0.119\n", + "Total CPU secs in NLP function evaluations = 0.006\n", + "\n", + "EXIT: Optimal Solution Found.\n" + ] + } + ], + "execution_count": 56 }, { "cell_type": "code", - "execution_count": null, "metadata": { "tags": [ "testing" - ] + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:32.344624Z", + "start_time": "2025-06-11T22:13:32.341522Z" + } }, - "outputs": [], "source": [ "# Check solver solve status\n", "from pyomo.environ import TerminationCondition\n", "\n", "assert results.solver.termination_condition == TerminationCondition.optimal" - ] + ], + "outputs": [], + "execution_count": 57 }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Analyze the results of the square problem\n", - "\n", + "## 7 Analyze the results\n", "\n", - "What is the total operating cost? " + "\n" ] }, { + "metadata": {}, + "cell_type": "markdown", + "source": "If the IDAES UI package was installed with the `idaes-pse` installation or installed separately, you can run the flowsheet visualizer to see a full diagram of the full process that is generated and displayed on a browser window.\n" + }, + { + "metadata": { + "tags": [ + "noauto" + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:33.874186Z", + "start_time": "2025-06-11T22:13:32.362868Z" + } + }, "cell_type": "code", - "execution_count": null, + "source": "m.fs.visualize('HDA-Flowsheet')", + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2025-06-11 16:13:33 [INFO] idaes.idaes_ui.fv.fsvis: Started visualization server\n", + "2025-06-11 16:13:33 [INFO] idaes.idaes_ui.fv.fsvis: Loading saved flowsheet from 'HDA-Flowsheet.json'\n", + "2025-06-11 16:13:33 [INFO] idaes.idaes_ui.fv.fsvis: Saving flowsheet to default file 'HDA-Flowsheet.json' in current directory (C:\\Users\\Tanner\\Documents\\git\\examples\\idaes_examples\\notebooks\\docs\\tut\\core)\n", + "2025-06-11 16:13:33 [INFO] idaes.idaes_ui.fv.fsvis: Flowsheet visualization at: http://localhost:49167/app?id=HDA-Flowsheet\n" + ] + }, + { + "data": { + "text/plain": [ + "VisualizeResult(store=, port=49167, server=, save_diagram=>)" + ] + }, + "execution_count": 58, + "metadata": {}, + "output_type": "execute_result" + } + ], + "execution_count": 58 + }, + { "metadata": {}, - "outputs": [], + "cell_type": "markdown", + "source": "Otherwise, we can run the `m.fs.report()` method to see a full summary of the solved flowsheet. It is recomended to adjust the width of the output as much as possible for the cleanest display." + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:33.985892Z", + "start_time": "2025-06-11T22:13:33.889113Z" + } + }, + "cell_type": "code", + "source": "m.fs.report()", + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "====================================================================================\n", + "Flowsheet : fs Time: 0.0\n", + "------------------------------------------------------------------------------------\n", + " Stream Table\n", + " Units s01 s02 s03 s04 s05 s06 s07 s08 s09 s10 s11 s12 \n", + " flow_mol_phase_comp ('Liq', 'benzene') mole / second 1.0000e-05 1.0000e-05 2.0008e-05 1.2993e-07 1.2993e-07 1.0000e-08 0.20460 8.0000e-09 8.0000e-09 1.0000e-08 0.062620 2.0000e-09\n", + " flow_mol_phase_comp ('Liq', 'toluene') mole / second 0.30000 1.0000e-05 0.30001 8.4149e-07 8.4149e-07 1.0000e-08 0.062520 8.0000e-09 8.0000e-09 1.0000e-08 0.032257 2.0000e-09\n", + " flow_mol_phase_comp ('Liq', 'methane') mole / second 1.0000e-05 1.0000e-05 2.0008e-05 1.0000e-12 1.0000e-12 1.0000e-08 2.6712e-07 8.0000e-09 8.0000e-09 1.0000e-08 9.4877e-08 2.0000e-09\n", + " flow_mol_phase_comp ('Liq', 'hydrogen') mole / second 1.0000e-05 1.0000e-05 2.0008e-05 1.0000e-12 1.0000e-12 1.0000e-08 2.6712e-07 8.0000e-09 8.0000e-09 1.0000e-08 9.4877e-08 2.0000e-09\n", + " flow_mol_phase_comp ('Vap', 'benzene') mole / second 1.0000e-05 1.0000e-05 0.11934 0.11936 0.35374 0.14915 1.0000e-08 0.11932 0.11932 0.14198 1.0000e-08 0.029829\n", + " flow_mol_phase_comp ('Vap', 'toluene') mole / second 1.0000e-05 1.0000e-05 0.012508 0.31252 0.078129 0.015610 1.0000e-08 0.012488 0.012488 0.030264 1.0000e-08 0.0031219\n", + " flow_mol_phase_comp ('Vap', 'methane') mole / second 1.0000e-05 0.020000 1.0377 1.0377 1.2721 1.2721 1.0000e-08 1.0177 1.0177 1.8224e-07 1.0000e-08 0.25442\n", + " flow_mol_phase_comp ('Vap', 'hydrogen') mole / second 1.0000e-05 0.30000 0.56258 0.56260 0.32821 0.32821 1.0000e-08 0.26257 0.26257 1.8224e-07 1.0000e-08 0.065642\n", + " temperature kelvin 303.20 303.20 314.09 600.00 771.85 325.00 325.00 325.00 325.00 375.00 375.00 325.00\n", + " pressure pascal 3.5000e+05 3.5000e+05 3.5000e+05 3.5000e+05 3.5000e+05 3.5000e+05 3.5000e+05 3.5000e+05 3.5000e+05 1.5000e+05 1.5000e+05 3.5000e+05\n", + "====================================================================================\n" + ] + } + ], + "execution_count": 59 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "What is the total operating cost?" + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:34.015352Z", + "start_time": "2025-06-11T22:13:34.012518Z" + } + }, "source": [ "print(\"operating cost = $\", value(m.fs.operating_cost))" - ] + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "operating cost = $ 419122.33874473866\n" + ] + } + ], + "execution_count": 60 }, { "cell_type": "code", - "execution_count": null, "metadata": { "tags": [ "testing" - ] + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:34.166006Z", + "start_time": "2025-06-11T22:13:34.094011Z" + } }, - "outputs": [], "source": [ "import pytest\n", "\n", "assert value(m.fs.operating_cost) == pytest.approx(419122.3387, abs=1e-3)" - ] + ], + "outputs": [], + "execution_count": 61 }, { "cell_type": "markdown", "metadata": {}, - "source": [ - "For this operating cost, what is the amount of benzene we are able to produce and what purity we are able to achieve? " - ] + "source": "For this operating cost, what is the amount of benzene we are able to produce and what purity we are able to achieve? We can look at a specific unit models stream table with the same `report()` method." }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:34.197557Z", + "start_time": "2025-06-11T22:13:34.174732Z" + } + }, "source": [ "m.fs.F102.report()\n", "\n", "print()\n", "print(\"benzene purity = \", value(m.fs.purity))" - ] + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "====================================================================================\n", + "Unit : fs.F102 Time: 0.0\n", + "------------------------------------------------------------------------------------\n", + " Unit Performance\n", + "\n", + " Variables: \n", + "\n", + " Key : Value : Units : Fixed : Bounds\n", + " Heat Duty : 7352.5 : watt : False : (None, None)\n", + " Pressure Change : -2.0000e+05 : pascal : True : (None, None)\n", + "\n", + "------------------------------------------------------------------------------------\n", + " Stream Table\n", + " Units Inlet Vapor Outlet Liquid Outlet\n", + " flow_mol_phase_comp ('Liq', 'benzene') mole / second 0.20460 1.0000e-08 0.062620 \n", + " flow_mol_phase_comp ('Liq', 'toluene') mole / second 0.062520 1.0000e-08 0.032257 \n", + " flow_mol_phase_comp ('Liq', 'methane') mole / second 2.6712e-07 1.0000e-08 9.4877e-08 \n", + " flow_mol_phase_comp ('Liq', 'hydrogen') mole / second 2.6712e-07 1.0000e-08 9.4877e-08 \n", + " flow_mol_phase_comp ('Vap', 'benzene') mole / second 1.0000e-08 0.14198 1.0000e-08 \n", + " flow_mol_phase_comp ('Vap', 'toluene') mole / second 1.0000e-08 0.030264 1.0000e-08 \n", + " flow_mol_phase_comp ('Vap', 'methane') mole / second 1.0000e-08 1.8224e-07 1.0000e-08 \n", + " flow_mol_phase_comp ('Vap', 'hydrogen') mole / second 1.0000e-08 1.8224e-07 1.0000e-08 \n", + " temperature kelvin 325.00 375.00 375.00 \n", + " pressure pascal 3.5000e+05 1.5000e+05 1.5000e+05 \n", + "====================================================================================\n", + "\n", + "benzene purity = 0.8242962943922332\n" + ] + } + ], + "execution_count": 62 }, { "cell_type": "code", - "execution_count": null, "metadata": { "tags": [ "testing" - ] + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:34.213201Z", + "start_time": "2025-06-11T22:13:34.210228Z" + } }, - "outputs": [], "source": [ "assert value(m.fs.purity) == pytest.approx(0.82429, abs=1e-3)\n", - "\n", "assert value(m.fs.F102.heat_duty[0]) == pytest.approx(7352.4828, abs=1e-3)\n", "assert value(m.fs.F102.vap_outlet.pressure[0]) == pytest.approx(1.5000e05, abs=1e-3)" - ] + ], + "outputs": [], + "execution_count": 63 }, { "cell_type": "markdown", "metadata": {}, - "source": [ - "Next, let's look at how much benzene we are losing with the light gases out of F101. IDAES has tools for creating stream tables based on the `Arcs` and/or `Ports` in a flowsheet. Let us create and print a simple stream table showing the stream leaving the reactor and the vapor stream from F101.\n", - "\n", - "
    \n", - "Inline Exercise:\n", - "How much benzene are we losing in the F101 vapor outlet stream?\n", - "
    \n" - ] + "source": "Next, let's look at how much benzene we are losing with the light gases out of F101. IDAES has tools for creating stream tables based on the `Arcs` and/or `Ports` in a flowsheet. Let us create and print a simple stream table showing the stream leaving the reactor and the vapor stream from F101." }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:34.253529Z", + "start_time": "2025-06-11T22:13:34.238100Z" + } + }, "source": [ "from idaes.core.util.tables import (\n", " create_stream_table_dataframe,\n", @@ -1174,25 +2459,33 @@ "\n", "st = create_stream_table_dataframe({\"Reactor\": m.fs.s05, \"Light Gases\": m.fs.s06})\n", "print(stream_table_dataframe_to_string(st))" - ] + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " Units Reactor Light Gases\n", + "flow_mol_phase_comp ('Liq', 'benzene') mole / second 1.2993e-07 1.0000e-08 \n", + "flow_mol_phase_comp ('Liq', 'toluene') mole / second 8.4149e-07 1.0000e-08 \n", + "flow_mol_phase_comp ('Liq', 'methane') mole / second 1.0000e-12 1.0000e-08 \n", + "flow_mol_phase_comp ('Liq', 'hydrogen') mole / second 1.0000e-12 1.0000e-08 \n", + "flow_mol_phase_comp ('Vap', 'benzene') mole / second 0.35374 0.14915 \n", + "flow_mol_phase_comp ('Vap', 'toluene') mole / second 0.078129 0.015610 \n", + "flow_mol_phase_comp ('Vap', 'methane') mole / second 1.2721 1.2721 \n", + "flow_mol_phase_comp ('Vap', 'hydrogen') mole / second 0.32821 0.32821 \n", + "temperature kelvin 771.85 325.00 \n", + "pressure pascal 3.5000e+05 3.5000e+05 \n" + ] + } + ], + "execution_count": 64 }, { "cell_type": "markdown", "metadata": {}, "source": [ - "
    \n", - "Inline Exercise:\n", - "You can query additional variables here if you like. \n", - "\n", - "Use Shift+Enter to run the cell once you have typed in your code. \n", - "
    \n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Optimization\n", + "## 8 Optimization\n", "\n", "\n", "We saw from the results above that the total operating cost for the base case was $419,122 per year. We are producing 0.142 mol/s of benzene at a purity of 82\\%. However, we are losing around 42\\% of benzene in F101 vapor outlet stream. \n", @@ -1219,12 +2512,17 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:34.276719Z", + "start_time": "2025-06-11T22:13:34.271416Z" + } + }, "source": [ "m.fs.objective = Objective(expr=m.fs.operating_cost)" - ] + ], + "outputs": [], + "execution_count": 65 }, { "cell_type": "markdown", @@ -1235,19 +2533,28 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:34.337438Z", + "start_time": "2025-06-11T22:13:34.332415Z" + } + }, "source": [ "m.fs.H101.outlet.temperature.unfix()\n", "m.fs.R101.heat_duty.unfix()\n", "m.fs.F101.vap_outlet.temperature.unfix()\n", "m.fs.F102.vap_outlet.temperature.unfix()" - ] + ], + "outputs": [], + "execution_count": 66 }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "tags": [ + "exercise" + ] + }, "source": [ "
    \n", "Inline Exercise:\n", @@ -1260,43 +2567,55 @@ }, { "cell_type": "code", - "execution_count": null, "metadata": { "tags": [ "exercise" - ] + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:34.399247Z", + "start_time": "2025-06-11T22:13:34.395221Z" + } }, - "outputs": [], "source": [ "# Todo: Unfix deltaP for F102" - ] + ], + "outputs": [], + "execution_count": 67 }, { "cell_type": "code", - "execution_count": null, "metadata": { "tags": [ "solution" - ] + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:34.463653Z", + "start_time": "2025-06-11T22:13:34.459960Z" + } }, - "outputs": [], "source": [ "# Todo: Unfix deltaP for F102\n", "m.fs.F102.deltaP.unfix()" - ] + ], + "outputs": [], + "execution_count": 68 }, { "cell_type": "code", - "execution_count": null, "metadata": { "tags": [ "testing" - ] + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:34.535666Z", + "start_time": "2025-06-11T22:13:34.502099Z" + } }, - "outputs": [], "source": [ "assert degrees_of_freedom(m) == 5" - ] + ], + "outputs": [], + "execution_count": 69 }, { "cell_type": "markdown", @@ -1315,17 +2634,26 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:34.589902Z", + "start_time": "2025-06-11T22:13:34.585528Z" + } + }, "source": [ "m.fs.H101.outlet.temperature[0].setlb(500)\n", "m.fs.H101.outlet.temperature[0].setub(600)" - ] + ], + "outputs": [], + "execution_count": 70 }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "tags": [ + "exercise" + ] + }, "source": [ "
    \n", "Inline Exercise:\n", @@ -1337,31 +2665,39 @@ }, { "cell_type": "code", - "execution_count": null, "metadata": { "tags": [ "exercise" - ] + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:34.678092Z", + "start_time": "2025-06-11T22:13:34.673513Z" + } }, - "outputs": [], "source": [ "# Todo: Set the bounds for reactor outlet temperature" - ] + ], + "outputs": [], + "execution_count": 71 }, { "cell_type": "code", - "execution_count": null, "metadata": { "tags": [ "solution" - ] + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:34.713177Z", + "start_time": "2025-06-11T22:13:34.709843Z" + } }, - "outputs": [], "source": [ "# Todo: Set the bounds for reactor outlet temperature\n", "m.fs.R101.outlet.temperature[0].setlb(600)\n", "m.fs.R101.outlet.temperature[0].setub(800)" - ] + ], + "outputs": [], + "execution_count": 72 }, { "cell_type": "markdown", @@ -1372,9 +2708,12 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:34.763569Z", + "start_time": "2025-06-11T22:13:34.759916Z" + } + }, "source": [ "m.fs.F101.vap_outlet.temperature[0].setlb(298.0)\n", "m.fs.F101.vap_outlet.temperature[0].setub(450.0)\n", @@ -1382,7 +2721,9 @@ "m.fs.F102.vap_outlet.temperature[0].setub(450.0)\n", "m.fs.F102.vap_outlet.pressure[0].setlb(105000)\n", "m.fs.F102.vap_outlet.pressure[0].setub(110000)" - ] + ], + "outputs": [], + "execution_count": 73 }, { "cell_type": "markdown", @@ -1393,19 +2734,28 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:34.806470Z", + "start_time": "2025-06-11T22:13:34.801455Z" + } + }, "source": [ "m.fs.overhead_loss = Constraint(\n", " expr=m.fs.F101.vap_outlet.flow_mol_phase_comp[0, \"Vap\", \"benzene\"]\n", " <= 0.20 * m.fs.R101.outlet.flow_mol_phase_comp[0, \"Vap\", \"benzene\"]\n", ")" - ] + ], + "outputs": [], + "execution_count": 74 }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "tags": [ + "exercise" + ] + }, "source": [ "
    \n", "Inline Exercise:\n", @@ -1417,32 +2767,40 @@ }, { "cell_type": "code", - "execution_count": null, "metadata": { "tags": [ "exercise" - ] + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:34.837136Z", + "start_time": "2025-06-11T22:13:34.832491Z" + } }, - "outputs": [], "source": [ "# Todo: Add minimum product flow constraint" - ] + ], + "outputs": [], + "execution_count": 75 }, { "cell_type": "code", - "execution_count": null, "metadata": { "tags": [ "solution" - ] + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:34.887385Z", + "start_time": "2025-06-11T22:13:34.882862Z" + } }, - "outputs": [], "source": [ "# Todo: Add minimum product flow constraint\n", "m.fs.product_flow = Constraint(\n", " expr=m.fs.F102.vap_outlet.flow_mol_phase_comp[0, \"Vap\", \"benzene\"] >= 0.15\n", ")" - ] + ], + "outputs": [], + "execution_count": 76 }, { "cell_type": "markdown", @@ -1453,12 +2811,17 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:34.917217Z", + "start_time": "2025-06-11T22:13:34.912683Z" + } + }, "source": [ "m.fs.product_purity = Constraint(expr=m.fs.purity >= 0.80)" - ] + ], + "outputs": [], + "execution_count": 77 }, { "cell_type": "markdown", @@ -1472,43 +2835,186 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:35.209235Z", + "start_time": "2025-06-11T22:13:34.962108Z" + } + }, "source": [ "results = solver.solve(m, tee=True)" - ] + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "WARNING: model contains export suffix 'scaling_factor' that contains 25\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "Ipopt 3.13.2: nlp_scaling_method=user-scaling\n", + "tol=1e-08\n", + "max_iter=500\n", + "option_file_name=C:\\Users\\Tanner\\AppData\\Local\\Temp\\tmpd6aww4bg_ipopt.opt\n", + "\n", + "Using option file \"C:\\Users\\Tanner\\AppData\\Local\\Temp\\tmpd6aww4bg_ipopt.opt\".\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...: 1191\n", + "Number of nonzeros in inequality constraint Jacobian.: 5\n", + "Number of nonzeros in Lagrangian Hessian.............: 1065\n", + "\n", + "Total number of variables............................: 395\n", + " variables with only lower bounds: 0\n", + " variables with lower and upper bounds: 199\n", + " variables with only upper bounds: 0\n", + "Total number of equality constraints.................: 390\n", + "Total number of inequality constraints...............: 3\n", + " inequality constraints with only lower bounds: 2\n", + " inequality constraints with lower and upper bounds: 0\n", + " inequality constraints with only upper bounds: 1\n", + "\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 0 4.1912234e+05 2.99e+05 6.94e+00 -1.0 0.00e+00 - 0.00e+00 0.00e+00 0\n", + " 1 4.1133558e+05 2.94e+05 6.94e+00 -1.0 6.60e+07 - 2.90e-06 1.05e-05f 1\n", + " 2 3.2484037e+05 1.80e+06 6.94e+00 -1.0 2.33e+09 - 2.95e-07 4.87e-06f 1\n", + " 3 3.0318192e+05 1.92e+06 6.94e+00 -1.0 6.90e+08 - 1.70e-05 4.13e-06f 1\n", + " 4 3.0263181e+05 1.92e+06 2.20e+01 -1.0 1.43e+07 - 8.92e-05 1.73e-05f 1\n", + " 5 3.0137102e+05 1.92e+06 4.38e+01 -1.0 8.74e+06 - 6.63e-05 1.11e-04f 1\n", + " 6 3.0058759e+05 1.92e+06 1.37e+03 -1.0 2.21e+07 - 8.59e-06 2.17e-04f 1\n", + " 7 3.0058113e+05 1.92e+06 7.51e+04 -1.0 3.24e+05 - 2.49e-01 1.77e-04f 1\n", + " 8 3.0317331e+05 1.38e+06 2.08e+05 -1.0 4.00e+04 - 9.62e-01 2.82e-01h 1\n", + " 9 3.0372038e+05 1.26e+06 1.91e+05 -1.0 2.87e+04 - 1.50e-01 8.31e-02h 1\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 10 3.0372988e+05 1.26e+06 9.32e+05 -1.0 2.63e+04 - 1.00e+00 1.51e-03h 1\n", + " 11 3.0386427e+05 1.24e+06 1.90e+07 -1.0 2.63e+04 - 1.00e+00 2.03e-02h 2\n", + " 12 3.0393928e+05 1.22e+06 7.46e+08 -1.0 2.58e+04 - 1.00e+00 1.15e-02h 2\n", + " 13 3.0397909e+05 1.21e+06 4.87e+10 -1.0 2.55e+04 - 1.00e+00 6.13e-03h 2\n", + " 14 3.0399961e+05 1.21e+06 5.05e+12 -1.0 2.53e+04 - 1.00e+00 3.17e-03h 2\n", + " 15r 3.0399961e+05 1.21e+06 1.00e+03 -0.5 0.00e+00 - 0.00e+00 3.97e-07R 14\n", + " 16r 3.0399912e+05 1.20e+06 2.84e+04 -0.5 8.47e+03 - 4.43e-03 2.34e-03f 1\n", + " 17 3.0399884e+05 1.20e+06 5.51e+05 -1.0 2.92e+04 - 2.81e-01 2.34e-06f 2\n", + " 18 3.0402874e+05 1.19e+06 4.77e+07 -1.0 2.52e+04 - 9.90e-01 4.64e-03h 1\n", + " 19 3.0402891e+05 1.19e+06 3.49e+10 -1.0 2.51e+04 - 1.00e+00 2.64e-05h 2\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 20r 3.0402891e+05 1.19e+06 1.00e+03 -0.8 0.00e+00 - 0.00e+00 3.08e-07R 8\n", + " 21r 3.0403760e+05 1.15e+06 2.58e+04 -0.8 3.66e+03 - 5.75e-03 3.60e-03f 1\n", + " 22r 3.0404498e+05 1.08e+06 3.87e+04 -0.8 3.65e+03 - 7.70e-03 6.44e-03f 1\n", + " 23r 3.0405749e+05 9.30e+05 4.54e+04 -0.8 3.63e+03 - 1.80e-02 1.60e-02f 1\n", + " 24r 3.0406463e+05 7.06e+05 4.01e+04 -0.8 3.57e+03 - 5.64e-02 3.23e-02f 1\n", + " 25r 3.0405635e+05 4.00e+05 4.80e+04 -0.8 3.45e+03 - 1.05e-01 8.20e-02f 1\n", + " 26r 3.0403684e+05 2.04e+05 2.80e+04 -0.8 3.17e+03 - 3.66e-01 1.49e-01f 1\n", + " 27r 3.0401302e+05 2.95e+04 2.21e+04 -0.8 2.70e+03 - 1.94e-01 6.28e-01f 1\n", + " 28 3.0386768e+05 2.95e+04 1.26e+02 -1.0 3.39e+06 - 4.53e-04 3.54e-06f 1\n", + " 29 3.0384876e+05 2.95e+04 8.45e+03 -1.0 1.90e+05 - 3.01e-02 3.65e-04f 1\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 30 3.0412267e+05 2.86e+04 2.69e+05 -1.0 2.35e+04 - 9.90e-01 3.11e-02h 1\n", + " 31 3.0843430e+05 5.11e+05 1.73e+05 -1.0 2.16e+04 - 1.00e+00 4.99e-01h 1\n", + " 32 3.1273887e+05 6.10e+05 5.58e+08 -1.0 1.09e+04 - 1.00e+00 9.90e-01h 1\n", + " 33 3.1276263e+05 3.11e+05 1.78e+08 -1.0 1.01e+03 - 1.00e+00 4.97e-01h 2\n", + " 34 3.1278673e+05 7.74e+02 1.15e+08 -1.0 6.27e+02 - 1.00e+00 9.97e-01H 1\n", + " 35 3.1278674e+05 6.28e+02 5.45e+04 -1.0 1.81e+02 - 1.00e+00 1.00e+00h 1\n", + " 36 3.1278674e+05 8.80e-03 2.62e+00 -1.0 7.71e-01 - 1.00e+00 1.00e+00h 1\n", + " 37 3.1278634e+05 3.48e-05 1.58e+05 -5.7 1.36e+00 - 1.00e+00 1.00e+00f 1\n", + " 38 3.1278634e+05 3.73e-08 6.73e-02 -5.7 2.20e-04 - 1.00e+00 1.00e+00f 1\n", + " 39 3.1278634e+05 2.24e-08 4.40e-05 -5.7 8.48e-04 - 1.00e+00 1.00e+00h 1\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 40 3.1278634e+05 6.71e-08 6.16e-05 -8.6 1.75e-03 - 1.00e+00 1.00e+00h 1\n", + " 41 3.1278634e+05 3.73e-08 4.40e-05 -9.0 8.38e-04 - 1.00e+00 1.00e+00h 1\n", + " 42 3.1278634e+05 5.96e-08 4.40e-05 -9.0 1.47e-03 - 1.00e+00 1.00e+00h 1\n", + " 43 3.1278634e+05 1.49e-08 4.40e-05 -9.0 5.14e-08 - 1.00e+00 1.00e+00h 1\n", + " 44 3.1278634e+05 1.49e-08 4.40e-05 -9.0 7.39e-11 - 1.00e+00 2.44e-04h 13\n", + " 45 3.1278634e+05 1.49e-08 4.40e-05 -9.0 6.89e-11 - 1.00e+00 5.00e-01h 2\n", + " 46 3.1278634e+05 1.49e-08 4.40e-05 -9.0 7.02e-11 - 1.00e+00 1.00e+00h 1\n", + " 47 3.1278634e+05 1.49e-08 4.40e-05 -9.0 6.71e-11 - 1.00e+00 5.00e-01h 2\n", + " 48 3.1278634e+05 2.98e-08 4.40e-05 -9.0 5.67e-11 - 1.00e+00 1.00e+00h 1\n", + " 49 3.1278634e+05 2.98e-08 4.40e-05 -9.0 5.52e-11 - 1.00e+00 5.00e-01h 2\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 50 3.1278634e+05 2.98e-08 4.40e-05 -9.0 4.16e-11 - 1.00e+00 5.00e-01h 2\n", + " 51 3.1278634e+05 2.98e-08 4.40e-05 -9.0 3.65e-11 - 1.00e+00 1.25e-01h 4\n", + "\n", + "Number of Iterations....: 51\n", + "\n", + " (scaled) (unscaled)\n", + "Objective...............: 3.1278633834066673e+05 3.1278633834066673e+05\n", + "Dual infeasibility......: 4.3988857412862944e-05 6.3035118071624454e-05\n", + "Constraint violation....: 2.0579515874459978e-11 2.9802322387695312e-08\n", + "Complementarity.........: 9.2191617461622074e-10 9.2191617461622074e-10\n", + "Overall NLP error.......: 1.6340396115140405e-08 6.3035118071624454e-05\n", + "\n", + "\n", + "Number of objective function evaluations = 141\n", + "Number of objective gradient evaluations = 47\n", + "Number of equality constraint evaluations = 141\n", + "Number of inequality constraint evaluations = 141\n", + "Number of equality constraint Jacobian evaluations = 55\n", + "Number of inequality constraint Jacobian evaluations = 55\n", + "Number of Lagrangian Hessian evaluations = 52\n", + "Total CPU secs in IPOPT (w/o function evaluations) = 0.077\n", + "Total CPU secs in NLP function evaluations = 0.010\n", + "\n", + "EXIT: Solved To Acceptable Level.\n" + ] + } + ], + "execution_count": 78 }, { "cell_type": "code", - "execution_count": null, "metadata": { "tags": [ "testing" - ] + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:35.223076Z", + "start_time": "2025-06-11T22:13:35.219792Z" + } }, - "outputs": [], "source": [ "# Check for solver solve status\n", "from pyomo.environ import TerminationCondition\n", "\n", "assert results.solver.termination_condition == TerminationCondition.optimal" - ] + ], + "outputs": [], + "execution_count": 79 }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Optimization Results\n", + "### 8.1 Optimization Results\n", "\n", "Display the results and product specifications" ] }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:35.321235Z", + "start_time": "2025-06-11T22:13:35.245545Z" + } + }, "source": [ "print(\"operating cost = $\", value(m.fs.operating_cost))\n", "\n", @@ -1523,21 +3029,93 @@ "print()\n", "print(\"Overhead loss in F101\")\n", "m.fs.F101.report()" - ] + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "operating cost = $ 312786.33834066667\n", + "\n", + "Product flow rate and purity in F102\n", + "\n", + "====================================================================================\n", + "Unit : fs.F102 Time: 0.0\n", + "------------------------------------------------------------------------------------\n", + " Unit Performance\n", + "\n", + " Variables: \n", + "\n", + " Key : Value : Units : Fixed : Bounds\n", + " Heat Duty : 8377.0 : watt : False : (None, None)\n", + " Pressure Change : -2.4500e+05 : pascal : False : (None, None)\n", + "\n", + "------------------------------------------------------------------------------------\n", + " Stream Table\n", + " Units Inlet Vapor Outlet Liquid Outlet\n", + " flow_mol_phase_comp ('Liq', 'benzene') mole / second 0.21743 1.0000e-08 0.067425 \n", + " flow_mol_phase_comp ('Liq', 'toluene') mole / second 0.070695 1.0000e-08 0.037507 \n", + " flow_mol_phase_comp ('Liq', 'methane') mole / second 2.8812e-07 1.0000e-08 1.0493e-07 \n", + " flow_mol_phase_comp ('Liq', 'hydrogen') mole / second 2.8812e-07 1.0000e-08 1.0493e-07 \n", + " flow_mol_phase_comp ('Vap', 'benzene') mole / second 1.0000e-08 0.15000 1.0000e-08 \n", + " flow_mol_phase_comp ('Vap', 'toluene') mole / second 1.0000e-08 0.033189 1.0000e-08 \n", + " flow_mol_phase_comp ('Vap', 'methane') mole / second 1.0000e-08 1.9319e-07 1.0000e-08 \n", + " flow_mol_phase_comp ('Vap', 'hydrogen') mole / second 1.0000e-08 1.9319e-07 1.0000e-08 \n", + " temperature kelvin 301.88 362.93 362.93 \n", + " pressure pascal 3.5000e+05 1.0500e+05 1.0500e+05 \n", + "====================================================================================\n", + "\n", + "benzene purity = 0.8188276578115882\n", + "\n", + "Overhead loss in F101\n", + "\n", + "====================================================================================\n", + "Unit : fs.F101 Time: 0.0\n", + "------------------------------------------------------------------------------------\n", + " Unit Performance\n", + "\n", + " Variables: \n", + "\n", + " Key : Value : Units : Fixed : Bounds\n", + " Heat Duty : -56353. : watt : False : (None, None)\n", + " Pressure Change : 0.0000 : pascal : True : (None, None)\n", + "\n", + "------------------------------------------------------------------------------------\n", + " Stream Table\n", + " Units Inlet Vapor Outlet Liquid Outlet\n", + " flow_mol_phase_comp ('Liq', 'benzene') mole / second 4.3534e-08 1.0000e-08 0.21743 \n", + " flow_mol_phase_comp ('Liq', 'toluene') mole / second 7.5866e-07 1.0000e-08 0.070695 \n", + " flow_mol_phase_comp ('Liq', 'methane') mole / second 1.0000e-12 1.0000e-08 2.8812e-07 \n", + " flow_mol_phase_comp ('Liq', 'hydrogen') mole / second 1.0000e-12 1.0000e-08 2.8812e-07 \n", + " flow_mol_phase_comp ('Vap', 'benzene') mole / second 0.27178 0.054356 1.0000e-08 \n", + " flow_mol_phase_comp ('Vap', 'toluene') mole / second 0.076085 0.0053908 1.0000e-08 \n", + " flow_mol_phase_comp ('Vap', 'methane') mole / second 1.2414 1.2414 1.0000e-08 \n", + " flow_mol_phase_comp ('Vap', 'hydrogen') mole / second 0.35887 0.35887 1.0000e-08 \n", + " temperature kelvin 696.11 301.88 301.88 \n", + " pressure pascal 3.5000e+05 3.5000e+05 3.5000e+05 \n", + "====================================================================================\n" + ] + } + ], + "execution_count": 80 }, { "cell_type": "code", - "execution_count": null, "metadata": { "tags": [ "testing" - ] + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:35.348144Z", + "start_time": "2025-06-11T22:13:35.343061Z" + } }, - "outputs": [], "source": [ "assert value(m.fs.operating_cost) == pytest.approx(312786.338, abs=1e-3)\n", "assert value(m.fs.purity) == pytest.approx(0.818827, abs=1e-3)" - ] + ], + "outputs": [], + "execution_count": 81 }, { "cell_type": "markdown", @@ -1548,49 +3126,66 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:35.405538Z", + "start_time": "2025-06-11T22:13:35.398221Z" + } + }, "source": [ - "print(\"Optimal Values\")\n", - "print()\n", - "\n", - "print(\"H101 outlet temperature = \", value(m.fs.H101.outlet.temperature[0]), \"K\")\n", - "\n", - "print()\n", - "print(\"R101 outlet temperature = \", value(m.fs.R101.outlet.temperature[0]), \"K\")\n", - "\n", - "print()\n", - "print(\"F101 outlet temperature = \", value(m.fs.F101.vap_outlet.temperature[0]), \"K\")\n", - "\n", - "print()\n", - "print(\"F102 outlet temperature = \", value(m.fs.F102.vap_outlet.temperature[0]), \"K\")\n", - "print(\"F102 outlet pressure = \", value(m.fs.F102.vap_outlet.pressure[0]), \"Pa\")" - ] + "print(f'''Optimal Values:\n", + "\n", + "H101 outlet temperature = {value(m.fs.H101.outlet.temperature[0]):.3f} K\n", + "\n", + "R101 outlet temperature = {value(m.fs.R101.outlet.temperature[0]):.3f} K\n", + "\n", + "F101 outlet temperature = {value(m.fs.F101.vap_outlet.temperature[0]):.3f} K\n", + "\n", + "F102 outlet temperature = {value(m.fs.F102.vap_outlet.temperature[0]):.3f} K\n", + "F102 outlet pressure = {value(m.fs.F102.vap_outlet.pressure[0]):.3f} Pa\n", + "''')" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Optimal Values:\n", + "\n", + "H101 outlet temperature = 500.000 K\n", + "\n", + "R101 outlet temperature = 696.112 K\n", + "\n", + "F101 outlet temperature = 301.878 K\n", + "\n", + "F102 outlet temperature = 362.935 K\n", + "F102 outlet pressure = 105000.000 Pa\n", + "\n" + ] + } + ], + "execution_count": 82 }, { "cell_type": "code", - "execution_count": null, "metadata": { "tags": [ "testing" - ] + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:35.448075Z", + "start_time": "2025-06-11T22:13:35.443091Z" + } }, - "outputs": [], "source": [ "assert value(m.fs.H101.outlet.temperature[0]) == pytest.approx(500, abs=1e-3)\n", "assert value(m.fs.R101.outlet.temperature[0]) == pytest.approx(696.112, abs=1e-3)\n", "assert value(m.fs.F101.vap_outlet.temperature[0]) == pytest.approx(301.878, abs=1e-3)\n", "assert value(m.fs.F102.vap_outlet.temperature[0]) == pytest.approx(362.935, abs=1e-3)\n", "assert value(m.fs.F102.vap_outlet.pressure[0]) == pytest.approx(105000, abs=1e-2)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, + ], "outputs": [], - "source": [] + "execution_count": 83 } ], "metadata": { diff --git a/idaes_examples/notebooks/docs/tut/core/hda_flowsheet_doc.ipynb b/idaes_examples/notebooks/docs/tut/core/hda_flowsheet_doc.ipynb index 2d862e0d..329c0c99 100644 --- a/idaes_examples/notebooks/docs/tut/core/hda_flowsheet_doc.ipynb +++ b/idaes_examples/notebooks/docs/tut/core/hda_flowsheet_doc.ipynb @@ -1,1363 +1,2698 @@ { - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "header", - "hide-cell" - ] - }, - "outputs": [], - "source": [ - "###############################################################################\n", - "# The Institute for the Design of Advanced Energy Systems Integrated Platform\n", - "# Framework (IDAES IP) was produced under the DOE Institute for the\n", - "# Design of Advanced Energy Systems (IDAES).\n", - "#\n", - "# Copyright (c) 2018-2023 by the software owners: The Regents of the\n", - "# University of California, through Lawrence Berkeley National Laboratory,\n", - "# National Technology & Engineering Solutions of Sandia, LLC, Carnegie Mellon\n", - "# University, West Virginia University Research Corporation, et al.\n", - "# All rights reserved. Please see the files COPYRIGHT.md and LICENSE.md\n", - "# for full copyright and license information.\n", - "###############################################################################" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "# HDA Flowsheet Simulation and Optimization\n", - "\n", - "Author: Jaffer Ghouse \n", - "Maintainer: Brandon Paul \n", - "Updated: 2023-06-01 \n", - "\n", - "## Learning outcomes\n", - "\n", - "\n", - "- Construct a steady-state flowsheet using the IDAES unit model library\n", - "- Connecting unit models in a flowsheet using Arcs\n", - "- Using the SequentialDecomposition tool to initialize a flowsheet with recycle\n", - "- Fomulate and solve an optimization problem\n", - " - Defining an objective function\n", - " - Setting variable bounds\n", - " - Adding additional constraints \n", - "\n", - "\n", - "## Problem Statement\n", - "\n", - "Hydrodealkylation is a chemical reaction that often involves reacting\n", - "an aromatic hydrocarbon in the presence of hydrogen gas to form a\n", - "simpler aromatic hydrocarbon devoid of functional groups. In this\n", - "example, toluene will be reacted with hydrogen gas at high temperatures\n", - " to form benzene via the following reaction:\n", - "\n", - "**C6H5CH3 + H2 → C6H6 + CH4**\n", - "\n", - "\n", - "This reaction is often accompanied by an equilibrium side reaction\n", - "which forms diphenyl, which we will neglect for this example.\n", - "\n", - "This example is based on the 1967 AIChE Student Contest problem as\n", - "present by Douglas, J.M., Chemical Design of Chemical Processes, 1988,\n", - "McGraw-Hill.\n", - "\n", - "The flowsheet that we will be using for this module is shown below with the stream conditions. We will be processing toluene and hydrogen to produce at least 370 TPY of benzene. As shown in the flowsheet, there are two flash tanks, F101 to separate out the non-condensibles and F102 to further separate the benzene-toluene mixture to improve the benzene purity. Note that typically a distillation column is required to obtain high purity benzene but that is beyond the scope of this workshop. The non-condensibles separated out in F101 will be partially recycled back to M101 and the rest will be either purged or combusted for power generation.We will assume ideal gas for this flowsheet. The properties required for this module are available in the same directory:\n", - "\n", - "- hda_ideal_VLE.py\n", - "- hda_reaction.py\n", - "\n", - "The state variables chosen for the property package are **flows of component by phase, temperature and pressure**. The components considered are: **toluene, hydrogen, benzene and methane**. Therefore, every stream has 8 flow variables, 1 temperature and 1 pressure variable. \n", - "\n", - "![](HDA_flowsheet.png)\n", - "\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Importing required pyomo and idaes components\n", - "\n", - "\n", - "To construct a flowsheet, we will need several components from the pyomo and idaes package. Let us first import the following components from Pyomo:\n", - "- Constraint (to write constraints)\n", - "- Var (to declare variables)\n", - "- ConcreteModel (to create the concrete model object)\n", - "- Expression (to evaluate values as a function of variables defined in the model)\n", - "- Objective (to define an objective function for optimization)\n", - "- SolverFactory (to solve the problem)\n", - "- TransformationFactory (to apply certain transformations)\n", - "- Arc (to connect two unit models)\n", - "- SequentialDecomposition (to initialize the flowsheet in a sequential mode)\n", - "\n", - "For further details on these components, please refer to the pyomo documentation: https://pyomo.readthedocs.io/en/stable/\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from pyomo.environ import (\n", - " Constraint,\n", - " Var,\n", - " ConcreteModel,\n", - " Expression,\n", - " Objective,\n", - " SolverFactory,\n", - " TransformationFactory,\n", - " value,\n", - ")\n", - "from pyomo.network import Arc, SequentialDecomposition" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "From idaes, we will be needing the FlowsheetBlock and the following unit models:\n", - "- Mixer\n", - "- Heater\n", - "- StoichiometricReactor\n", - "- **Flash**\n", - "- Separator (splitter) \n", - "- PressureChanger" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from idaes.core import FlowsheetBlock" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from idaes.models.unit_models import (\n", - " PressureChanger,\n", - " Mixer,\n", - " Separator as Splitter,\n", - " Heater,\n", - " StoichiometricReactor,\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "
    \n", - "Inline Exercise:\n", - "Now, import the remaining unit models highlighted in blue above and run the cell using `Shift+Enter` after typing in the code. \n", - "
    \n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "solution" - ] - }, - "outputs": [], - "source": [ - "# Todo: import flash model from idaes.models.unit_models\n", - "from idaes.models.unit_models import Flash" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We will also be needing some utility tools to put together the flowsheet and calculate the degrees of freedom. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from idaes.models.unit_models.pressure_changer import ThermodynamicAssumption\n", - "from idaes.core.util.model_statistics import degrees_of_freedom\n", - "\n", - "# Import idaes logger to set output levels\n", - "import idaes.logger as idaeslog\n", - "from idaes.core.solvers import get_solver\n", - "from idaes.core.util.exceptions import InitializationError" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Importing required thermo and reaction package\n", - "\n", - "The final set of imports are to import the thermo and reaction package for the HDA process. We have created a custom thermo package that assumes Ideal Gas with support for VLE. \n", - "\n", - "The reaction package here is very simple as we will be using only a StochiometricReactor and the reaction package consists of the stochiometric coefficients for the reaction and the parameter for the heat of reaction. \n", - "\n", - "Let us import the following modules and they are in the same directory as this jupyter notebook:\n", - "
      \n", - "
    • hda_ideal_VLE as thermo_props
    • \n", - "
    • hda_reaction as reaction_props
    • \n", - "
    \n", - "
    " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from idaes_examples.mod.hda import hda_ideal_VLE as thermo_props\n", - "from idaes_examples.mod.hda import hda_reaction as reaction_props" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Constructing the Flowsheet\n", - "\n", - "We have now imported all the components, unit models, and property modules we need to construct a flowsheet. Let us create a ConcreteModel and add the flowsheet block as we did in module 1. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m = ConcreteModel()\n", - "m.fs = FlowsheetBlock(dynamic=False)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We now need to add the property packages to the flowsheet. Unlike Module 1, where we only had a thermo property package, for this flowsheet we will also need to add a reaction property package. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.thermo_params = thermo_props.HDAParameterBlock()\n", - "m.fs.reaction_params = reaction_props.HDAReactionParameterBlock(\n", - " property_package=m.fs.thermo_params\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Adding Unit Models\n", - "\n", - "Let us start adding the unit models we have imported to the flowsheet. Here, we are adding the Mixer (assigned a name M101) and a Heater (assigned a name H101). Note that, all unit models need to be given a property package argument. In addition to that, there are several arguments depending on the unit model, please refer to the documentation for more details (https://idaes-pse.readthedocs.io/en/stable/reference_guides/model_libraries/generic/unit_models/index.html). For example, the Mixer unit model here is given a `list` consisting of names to the three inlets. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.M101 = Mixer(\n", - " property_package=m.fs.thermo_params,\n", - " inlet_list=[\"toluene_feed\", \"hydrogen_feed\", \"vapor_recycle\"],\n", - ")\n", - "\n", - "m.fs.H101 = Heater(\n", - " property_package=m.fs.thermo_params,\n", - " has_pressure_change=False,\n", - " has_phase_equilibrium=True,\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "
    \n", - "Inline Exercise:\n", - "Let us now add the StoichiometricReactor(assign the name R101) and pass the following arguments:\n", - "
      \n", - "
    • \"property_package\": m.fs.thermo_params
    • \n", - "
    • \"reaction_package\": m.fs.reaction_params
    • \n", - "
    • \"has_heat_of_reaction\": True
    • \n", - "
    • \"has_heat_transfer\": True
    • \n", - "
    • \"has_pressure_change\": False
    • \n", - "
    \n", - "
    " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "solution" - ] - }, - "outputs": [], - "source": [ - "# Todo: Add reactor with the specifications above\n", - "m.fs.R101 = StoichiometricReactor(\n", - " property_package=m.fs.thermo_params,\n", - " reaction_package=m.fs.reaction_params,\n", - " has_heat_of_reaction=True,\n", - " has_heat_transfer=True,\n", - " has_pressure_change=False,\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let us now add the Flash(assign the name F101) and pass the following arguments:\n", - "
      \n", - "
    • \"property_package\": m.fs.thermo_params
    • \n", - "
    • \"has_heat_transfer\": True
    • \n", - "
    • \"has_pressure_change\": False
    • \n", - "
    " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.F101 = Flash(\n", - " property_package=m.fs.thermo_params,\n", - " has_heat_transfer=True,\n", - " has_pressure_change=True,\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let us now add the Splitter(S101), PressureChanger(C101) and the second Flash(F102). " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.S101 = Splitter(\n", - " property_package=m.fs.thermo_params,\n", - " ideal_separation=False,\n", - " outlet_list=[\"purge\", \"recycle\"],\n", - ")\n", - "\n", - "\n", - "m.fs.C101 = PressureChanger(\n", - " property_package=m.fs.thermo_params,\n", - " compressor=True,\n", - " thermodynamic_assumption=ThermodynamicAssumption.isothermal,\n", - ")\n", - "\n", - "m.fs.F102 = Flash(\n", - " property_package=m.fs.thermo_params,\n", - " has_heat_transfer=True,\n", - " has_pressure_change=True,\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Connecting Unit Models using Arcs\n", - "\n", - "We have now added all the unit models we need to the flowsheet. However, we have not yet specified how the units are to be connected. To do this, we will be using the `Arc` which is a pyomo component that takes in two arguments: `source` and `destination`. Let us connect the outlet of the mixer(M101) to the inlet of the heater(H101). " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.s03 = Arc(source=m.fs.M101.outlet, destination=m.fs.H101.inlet)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "![](HDA_flowsheet.png) \n", - "\n", - "
    \n", - "Inline Exercise:\n", - "Now, connect the H101 outlet to the R101 inlet using the cell above as a guide. \n", - "
    \n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "solution" - ] - }, - "outputs": [], - "source": [ - "# Todo: Connect the H101 outlet to R101 inlet\n", - "m.fs.s04 = Arc(source=m.fs.H101.outlet, destination=m.fs.R101.inlet)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We will now be connecting the rest of the flowsheet as shown below. Notice how the outlet names are different for the flash tanks F101 and F102 as they have a vapor and a liquid outlet. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.s05 = Arc(source=m.fs.R101.outlet, destination=m.fs.F101.inlet)\n", - "m.fs.s06 = Arc(source=m.fs.F101.vap_outlet, destination=m.fs.S101.inlet)\n", - "m.fs.s08 = Arc(source=m.fs.S101.recycle, destination=m.fs.C101.inlet)\n", - "m.fs.s09 = Arc(source=m.fs.C101.outlet, destination=m.fs.M101.vapor_recycle)\n", - "m.fs.s10 = Arc(source=m.fs.F101.liq_outlet, destination=m.fs.F102.inlet)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We have now connected the unit model block using the arcs. However, each of these arcs link to ports on the two unit models that are connected. In this case, the ports consist of the state variables that need to be linked between the unit models. Pyomo provides a convenient method to write these equality constraints for us between two ports and this is done as follows:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "TransformationFactory(\"network.expand_arcs\").apply_to(m)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Adding expressions to compute purity and operating costs\n", - "\n", - "In this section, we will add a few Expressions that allows us to evaluate the performance. Expressions provide a convenient way of calculating certain values that are a function of the variables defined in the model. For more details on Expressions, please refer to: https://pyomo.readthedocs.io/en/stable/pyomo_modeling_components/Expressions.html\n", - "\n", - "For this flowsheet, we are interested in computing the purity of the product Benzene stream (i.e. the mole fraction) and the operating cost which is a sum of the cooling and heating cost. " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let us first add an Expression to compute the mole fraction of benzene in the `vap_outlet` of F102 which is our product stream. Please note that the var flow_mol_phase_comp has the index - [time, phase, component]. As this is a steady-state flowsheet, the time index by default is 0. The valid phases are [\"Liq\", \"Vap\"]. Similarly the valid component list is [\"benzene\", \"toluene\", \"hydrogen\", \"methane\"]." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.purity = Expression(\n", - " expr=m.fs.F102.vap_outlet.flow_mol_phase_comp[0, \"Vap\", \"benzene\"]\n", - " / (\n", - " m.fs.F102.vap_outlet.flow_mol_phase_comp[0, \"Vap\", \"benzene\"]\n", - " + m.fs.F102.vap_outlet.flow_mol_phase_comp[0, \"Vap\", \"toluene\"]\n", - " )\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now, let us add an expression to compute the cooling cost assuming a cost of 0.212E-4 $/kW. Note that cooling utility is required for the reactor (R101) and the first flash (F101). " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.cooling_cost = Expression(\n", - " expr=0.212e-7 * (-m.fs.F101.heat_duty[0]) + 0.212e-7 * (-m.fs.R101.heat_duty[0])\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "Now, let us add an expression to compute the heating cost assuming the utility cost as follows:\n", - "
      \n", - "
    • 2.2E-4 dollars/kW for H101
    • \n", - "
    • 1.9E-4 dollars/kW for F102
    • \n", - "
    \n", - "Note that the heat duty is in units of watt (J/s). " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.heating_cost = Expression(\n", - " expr=2.2e-7 * m.fs.H101.heat_duty[0] + 1.9e-7 * m.fs.F102.heat_duty[0]\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let us now add an expression to compute the total operating cost per year which is basically the sum of the cooling and heating cost we defined above. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.operating_cost = Expression(\n", - " expr=(3600 * 24 * 365 * (m.fs.heating_cost + m.fs.cooling_cost))\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Fixing feed conditions\n", - "\n", - "Let us first check how many degrees of freedom exist for this flowsheet using the `degrees_of_freedom` tool we imported earlier. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "print(degrees_of_freedom(m))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We will now be fixing the toluene feed stream to the conditions shown in the flowsheet above. Please note that though this is a pure toluene feed, the remaining components are still assigned a very small non-zero value to help with convergence and initializing. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.M101.toluene_feed.flow_mol_phase_comp[0, \"Vap\", \"benzene\"].fix(1e-5)\n", - "m.fs.M101.toluene_feed.flow_mol_phase_comp[0, \"Vap\", \"toluene\"].fix(1e-5)\n", - "m.fs.M101.toluene_feed.flow_mol_phase_comp[0, \"Vap\", \"hydrogen\"].fix(1e-5)\n", - "m.fs.M101.toluene_feed.flow_mol_phase_comp[0, \"Vap\", \"methane\"].fix(1e-5)\n", - "m.fs.M101.toluene_feed.flow_mol_phase_comp[0, \"Liq\", \"benzene\"].fix(1e-5)\n", - "m.fs.M101.toluene_feed.flow_mol_phase_comp[0, \"Liq\", \"toluene\"].fix(0.30)\n", - "m.fs.M101.toluene_feed.flow_mol_phase_comp[0, \"Liq\", \"hydrogen\"].fix(1e-5)\n", - "m.fs.M101.toluene_feed.flow_mol_phase_comp[0, \"Liq\", \"methane\"].fix(1e-5)\n", - "m.fs.M101.toluene_feed.temperature.fix(303.2)\n", - "m.fs.M101.toluene_feed.pressure.fix(350000)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "Similarly, let us fix the hydrogen feed to the following conditions in the next cell:\n", - "
      \n", - "
    • FH2 = 0.30 mol/s
    • \n", - "
    • FCH4 = 0.02 mol/s
    • \n", - "
    • Remaining components = 1e-5 mol/s
    • \n", - "
    • T = 303.2 K
    • \n", - "
    • P = 350000 Pa
    • \n", - "
    \n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.M101.hydrogen_feed.flow_mol_phase_comp[0, \"Vap\", \"benzene\"].fix(1e-5)\n", - "m.fs.M101.hydrogen_feed.flow_mol_phase_comp[0, \"Vap\", \"toluene\"].fix(1e-5)\n", - "m.fs.M101.hydrogen_feed.flow_mol_phase_comp[0, \"Vap\", \"hydrogen\"].fix(0.30)\n", - "m.fs.M101.hydrogen_feed.flow_mol_phase_comp[0, \"Vap\", \"methane\"].fix(0.02)\n", - "m.fs.M101.hydrogen_feed.flow_mol_phase_comp[0, \"Liq\", \"benzene\"].fix(1e-5)\n", - "m.fs.M101.hydrogen_feed.flow_mol_phase_comp[0, \"Liq\", \"toluene\"].fix(1e-5)\n", - "m.fs.M101.hydrogen_feed.flow_mol_phase_comp[0, \"Liq\", \"hydrogen\"].fix(1e-5)\n", - "m.fs.M101.hydrogen_feed.flow_mol_phase_comp[0, \"Liq\", \"methane\"].fix(1e-5)\n", - "m.fs.M101.hydrogen_feed.temperature.fix(303.2)\n", - "m.fs.M101.hydrogen_feed.pressure.fix(350000)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Fixing unit model specifications\n", - "\n", - "Now that we have fixed our inlet feed conditions, we will now be fixing the operating conditions for the unit models in the flowsheet. Let us set set the H101 outlet temperature to 600 K. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.H101.outlet.temperature.fix(600)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "For the StoichiometricReactor, we have to define the conversion in terms of toluene. This requires us to create a new variable for specifying the conversion and adding a Constraint that defines the conversion with respect to toluene. The second degree of freedom for the reactor is to define the heat duty. In this case, let us assume the reactor to be adiabatic i.e. Q = 0. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.R101.conversion = Var(initialize=0.75, bounds=(0, 1))\n", - "\n", - "m.fs.R101.conv_constraint = Constraint(\n", - " expr=m.fs.R101.conversion * m.fs.R101.inlet.flow_mol_phase_comp[0, \"Vap\", \"toluene\"]\n", - " == (\n", - " m.fs.R101.inlet.flow_mol_phase_comp[0, \"Vap\", \"toluene\"]\n", - " - m.fs.R101.outlet.flow_mol_phase_comp[0, \"Vap\", \"toluene\"]\n", - " )\n", - ")\n", - "\n", - "m.fs.R101.conversion.fix(0.75)\n", - "m.fs.R101.heat_duty.fix(0)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The Flash conditions for F101 can be set as follows. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.F101.vap_outlet.temperature.fix(325.0)\n", - "m.fs.F101.deltaP.fix(0)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "
    \n", - "Inline Exercise:\n", - "Set the conditions for Flash F102 to the following conditions:\n", - "
      \n", - "
    • T = 375 K
    • \n", - "
    • deltaP = -200000
    • \n", - "
    \n", - "\n", - "Use Shift+Enter to run the cell once you have typed in your code. \n", - "
    " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "solution" - ] - }, - "outputs": [], - "source": [ - "m.fs.F102.vap_outlet.temperature.fix(375)\n", - "m.fs.F102.deltaP.fix(-200000)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let us fix the purge split fraction to 20% and the outlet pressure of the compressor is set to 350000 Pa. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.S101.split_fraction[0, \"purge\"].fix(0.2)\n", - "m.fs.C101.outlet.pressure.fix(350000)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "
    \n", - "Inline Exercise:\n", - "We have now defined all the feed conditions and the inputs required for the unit models. The system should now have 0 degrees of freedom i.e. should be a square problem. Please check that the degrees of freedom is 0. \n", - "\n", - "Use Shift+Enter to run the cell once you have typed in your code. \n", - "
    " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "solution" - ] - }, - "outputs": [], - "source": [ - "print(degrees_of_freedom(m))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Initialization\n", - "\n", - "\n", - "This section will demonstrate how to use the built-in sequential decomposition tool to initialize our flowsheet.\n", - "\n", - "![](HDA_flowsheet.png) \n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let us first create an object for the SequentialDecomposition and specify our options for this. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "seq = SequentialDecomposition()\n", - "seq.options.select_tear_method = \"heuristic\"\n", - "seq.options.tear_method = \"Wegstein\"\n", - "seq.options.iterLim = 3\n", - "\n", - "# Using the SD tool\n", - "G = seq.create_graph(m)\n", - "heuristic_tear_set = seq.tear_set_arcs(G, method=\"heuristic\")\n", - "order = seq.calculation_order(G)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Which is the tear stream? Display tear set and order" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "for o in heuristic_tear_set:\n", - " print(o.name)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "What sequence did the SD tool determine to solve this flowsheet with the least number of tears? " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "scrolled": true - }, - "outputs": [], - "source": [ - "for o in order:\n", - " print(o[0].name)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - " \n", - "\n", - "![](HDA_tear_stream.png) \n", - "\n", - "\n", - "The SequentialDecomposition tool has determined that the tear stream is the mixer outlet. We will need to provide a reasonable guess for this." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "tear_guesses = {\n", - " \"flow_mol_phase_comp\": {\n", - " (0, \"Vap\", \"benzene\"): 1e-5,\n", - " (0, \"Vap\", \"toluene\"): 1e-5,\n", - " (0, \"Vap\", \"hydrogen\"): 0.30,\n", - " (0, \"Vap\", \"methane\"): 0.02,\n", - " (0, \"Liq\", \"benzene\"): 1e-5,\n", - " (0, \"Liq\", \"toluene\"): 0.30,\n", - " (0, \"Liq\", \"hydrogen\"): 1e-5,\n", - " (0, \"Liq\", \"methane\"): 1e-5,\n", - " },\n", - " \"temperature\": {0: 303},\n", - " \"pressure\": {0: 350000},\n", - "}\n", - "\n", - "# Pass the tear_guess to the SD tool\n", - "seq.set_guesses_for(m.fs.H101.inlet, tear_guesses)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Next, we need to tell the tool how to initialize a particular unit. We will be writing a python function which takes in a \"unit\" and calls the initialize method on that unit. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "def function(unit):\n", - " try:\n", - " initializer = unit.default_initializer()\n", - " initializer.initialize(unit, output_level=idaeslog.INFO)\n", - " except InitializationError:\n", - " solver = get_solver()\n", - " solver.solve(unit)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We are now ready to initialize our flowsheet in a sequential mode. Note that we specifically set the iteration limit to be 5 as we are trying to use this tool only to get a good set of initial values such that IPOPT can then take over and solve this flowsheet for us. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "seq.run(m, function)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "
    \n", - "Inline Exercise:\n", - "We have now initialized the flowsheet. Let us run the flowsheet in a simulation mode to look at the results. To do this, complete the last line of code where we pass the model to the solver. You will need to type the following:\n", - " \n", - "results = solver.solve(m, tee=True)\n", - "\n", - "Use Shift+Enter to run the cell once you have typed in your code. \n", - "
    \n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "solution" - ] - }, - "outputs": [], - "source": [ - "# Create the solver object\n", - "from idaes.core.solvers import get_solver\n", - "\n", - "solver = get_solver()\n", - "\n", - "# Solve the model\n", - "results = solver.solve(m, tee=True)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Analyze the results of the square problem\n", - "\n", - "\n", - "What is the total operating cost? " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "print(\"operating cost = $\", value(m.fs.operating_cost))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "For this operating cost, what is the amount of benzene we are able to produce and what purity we are able to achieve? " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.F102.report()\n", - "\n", - "print()\n", - "print(\"benzene purity = \", value(m.fs.purity))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Next, let's look at how much benzene we are losing with the light gases out of F101. IDAES has tools for creating stream tables based on the `Arcs` and/or `Ports` in a flowsheet. Let us create and print a simple stream table showing the stream leaving the reactor and the vapor stream from F101.\n", - "\n", - "
    \n", - "Inline Exercise:\n", - "How much benzene are we losing in the F101 vapor outlet stream?\n", - "
    \n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from idaes.core.util.tables import (\n", - " create_stream_table_dataframe,\n", - " stream_table_dataframe_to_string,\n", - ")\n", - "\n", - "st = create_stream_table_dataframe({\"Reactor\": m.fs.s05, \"Light Gases\": m.fs.s06})\n", - "print(stream_table_dataframe_to_string(st))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "
    \n", - "Inline Exercise:\n", - "You can query additional variables here if you like. \n", - "\n", - "Use Shift+Enter to run the cell once you have typed in your code. \n", - "
    \n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Optimization\n", - "\n", - "\n", - "We saw from the results above that the total operating cost for the base case was $419,122 per year. We are producing 0.142 mol/s of benzene at a purity of 82\\%. However, we are losing around 42\\% of benzene in F101 vapor outlet stream. \n", - "\n", - "Let us try to minimize this cost such that:\n", - "- we are producing at least 0.15 mol/s of benzene in F102 vapor outlet i.e. our product stream\n", - "- purity of benzene i.e. the mole fraction of benzene in F102 vapor outlet is at least 80%\n", - "- restricting the benzene loss in F101 vapor outlet to less than 20%\n", - "\n", - "For this problem, our decision variables are as follows:\n", - "- H101 outlet temperature\n", - "- R101 cooling duty provided\n", - "- F101 outlet temperature\n", - "- F102 outlet temperature\n", - "- F102 deltaP in the flash tank\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let us declare our objective function for this problem. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.objective = Objective(expr=m.fs.operating_cost)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now, we need to unfix the decision variables as we had solved a square problem (degrees of freedom = 0) until now. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.H101.outlet.temperature.unfix()\n", - "m.fs.R101.heat_duty.unfix()\n", - "m.fs.F101.vap_outlet.temperature.unfix()\n", - "m.fs.F102.vap_outlet.temperature.unfix()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "
    \n", - "Inline Exercise:\n", - "Let us now unfix the remaining variable which is F102 pressure drop (F102.deltaP) \n", - "\n", - "Use Shift+Enter to run the cell once you have typed in your code. \n", - "
    \n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "solution" - ] - }, - "outputs": [], - "source": [ - "# Todo: Unfix deltaP for F102\n", - "m.fs.F102.deltaP.unfix()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Next, we need to set bounds on these decision variables to values shown below:\n", - "\n", - " - H101 outlet temperature [500, 600] K\n", - " - R101 outlet temperature [600, 800] K\n", - " - F101 outlet temperature [298, 450] K\n", - " - F102 outlet temperature [298, 450] K\n", - " - F102 outlet pressure [105000, 110000] Pa\n", - "\n", - "Let us first set the variable bound for the H101 outlet temperature as shown below:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.H101.outlet.temperature[0].setlb(500)\n", - "m.fs.H101.outlet.temperature[0].setub(600)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "
    \n", - "Inline Exercise:\n", - "Now, set the variable bound for the R101 outlet temperature.\n", - "\n", - "Use Shift+Enter to run the cell once you have typed in your code. \n", - "
    " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "solution" - ] - }, - "outputs": [], - "source": [ - "# Todo: Set the bounds for reactor outlet temperature\n", - "m.fs.R101.outlet.temperature[0].setlb(600)\n", - "m.fs.R101.outlet.temperature[0].setub(800)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let us fix the bounds for the rest of the decision variables. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.F101.vap_outlet.temperature[0].setlb(298.0)\n", - "m.fs.F101.vap_outlet.temperature[0].setub(450.0)\n", - "m.fs.F102.vap_outlet.temperature[0].setlb(298.0)\n", - "m.fs.F102.vap_outlet.temperature[0].setub(450.0)\n", - "m.fs.F102.vap_outlet.pressure[0].setlb(105000)\n", - "m.fs.F102.vap_outlet.pressure[0].setub(110000)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now, the only things left to define are our constraints on overhead loss in F101, product flow rate and purity in F102. Let us first look at defining a constraint for the overhead loss in F101 where we are restricting the benzene leaving the vapor stream to less than 20 \\% of the benzene available in the reactor outlet. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.overhead_loss = Constraint(\n", - " expr=m.fs.F101.vap_outlet.flow_mol_phase_comp[0, \"Vap\", \"benzene\"]\n", - " <= 0.20 * m.fs.R101.outlet.flow_mol_phase_comp[0, \"Vap\", \"benzene\"]\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "
    \n", - "Inline Exercise:\n", - "Now, add the constraint such that we are producing at least 0.15 mol/s of benzene in the product stream which is the vapor outlet of F102. Let us name this constraint as m.fs.product_flow. \n", - "\n", - "Use Shift+Enter to run the cell once you have typed in your code. \n", - "
    " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "solution" - ] - }, - "outputs": [], - "source": [ - "# Todo: Add minimum product flow constraint\n", - "m.fs.product_flow = Constraint(\n", - " expr=m.fs.F102.vap_outlet.flow_mol_phase_comp[0, \"Vap\", \"benzene\"] >= 0.15\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let us add the final constraint on product purity or the mole fraction of benzene in the product stream such that it is at least greater than 80%. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.product_purity = Constraint(expr=m.fs.purity >= 0.80)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "We have now defined the optimization problem and we are now ready to solve this problem. \n", - "\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "results = solver.solve(m, tee=True)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Optimization Results\n", - "\n", - "Display the results and product specifications" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "print(\"operating cost = $\", value(m.fs.operating_cost))\n", - "\n", - "print()\n", - "print(\"Product flow rate and purity in F102\")\n", - "\n", - "m.fs.F102.report()\n", - "\n", - "print()\n", - "print(\"benzene purity = \", value(m.fs.purity))\n", - "\n", - "print()\n", - "print(\"Overhead loss in F101\")\n", - "m.fs.F101.report()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Display optimal values for the decision variables" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "print(\"Optimal Values\")\n", - "print()\n", - "\n", - "print(\"H101 outlet temperature = \", value(m.fs.H101.outlet.temperature[0]), \"K\")\n", - "\n", - "print()\n", - "print(\"R101 outlet temperature = \", value(m.fs.R101.outlet.temperature[0]), \"K\")\n", - "\n", - "print()\n", - "print(\"F101 outlet temperature = \", value(m.fs.F101.vap_outlet.temperature[0]), \"K\")\n", - "\n", - "print()\n", - "print(\"F102 outlet temperature = \", value(m.fs.F102.vap_outlet.temperature[0]), \"K\")\n", - "print(\"F102 outlet pressure = \", value(m.fs.F102.vap_outlet.pressure[0]), \"Pa\")" - ] - }, - { - "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.8.12" - } - }, - "nbformat": 4, - "nbformat_minor": 3 + "cells": [ + { + "cell_type": "code", + "metadata": { + "tags": [ + "header", + "hide-cell" + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:25.418463Z", + "start_time": "2025-06-11T22:13:25.412645Z" + } + }, + "source": [ + "###############################################################################\n", + "# The Institute for the Design of Advanced Energy Systems Integrated Platform\n", + "# Framework (IDAES IP) was produced under the DOE Institute for the\n", + "# Design of Advanced Energy Systems (IDAES).\n", + "#\n", + "# Copyright (c) 2018-2023 by the software owners: The Regents of the\n", + "# University of California, through Lawrence Berkeley National Laboratory,\n", + "# National Technology & Engineering Solutions of Sandia, LLC, Carnegie Mellon\n", + "# University, West Virginia University Research Corporation, et al.\n", + "# All rights reserved. Please see the files COPYRIGHT.md and LICENSE.md\n", + "# for full copyright and license information.\n", + "###############################################################################" + ], + "outputs": [], + "execution_count": 1 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "# HDA Flowsheet Simulation and Optimization\n", + "\n", + "Author: Jaffer Ghouse
    \n", + "Maintainer: Tanner Polley
    \n", + "Updated: 2025-06-03\n", + "\n", + "## Learning outcomes\n", + "\n", + "\n", + "- Construct a steady-state flowsheet using the IDAES unit model library\n", + "- Connecting unit models in a flowsheet using Arcs\n", + "- Using the SequentialDecomposition tool to initialize a flowsheet with recycle\n", + "- Formulate and solve an optimization problem\n", + " - Defining an objective function\n", + " - Setting variable bounds\n", + " - Adding additional constraints\n", + "\n", + "\n", + "The general workflow of setting up an IDAES flowsheet is the following:\n", + "\n", + "- 1 Importing Modules\n", + "- 2 Building a Model\n", + "- 3 Scaling the Model\n", + "- 4 Specifying the Model\n", + "- 5 Initializing the Model\n", + "- 6 Solving the Model\n", + "- 7 Analyzing and Visualizing the Results\n", + "- 8 Optimizing the Model\n", + "\n", + "We will complete each of these steps as well as demonstrate analyses on this model through some examples and exercises\n", + "\n", + "\n", + "## Problem Statement\n", + "\n", + "Hydrodealkylation is a chemical reaction that often involves reacting\n", + "an aromatic hydrocarbon in the presence of hydrogen gas to form a\n", + "simpler aromatic hydrocarbon devoid of functional groups. In this\n", + "example, toluene will be reacted with hydrogen gas at high temperatures\n", + " to form benzene via the following reaction:\n", + "\n", + "**C6H5CH3 + H2 \u2192 C6H6 + CH4**\n", + "\n", + "\n", + "This reaction is often accompanied by an equilibrium side reaction\n", + "which forms diphenyl, which we will neglect for this example.\n", + "\n", + "This example is based on the 1967 AIChE Student Contest problem as\n", + "present by Douglas, J.M., Chemical Design of Chemical Processes, 1988,\n", + "McGraw-Hill.\n", + "\n", + "The flowsheet that we will be using for this module is shown below with the stream conditions. We will be processing toluene and hydrogen to produce at least 370 TPY of benzene. As shown in the flowsheet, there are two flash tanks, F101 to separate out the non-condensibles and F102 to further separate the benzene-toluene mixture to improve the benzene purity. Note that typically a distillation column is required to obtain high purity benzene but that is beyond the scope of this workshop. The non-condensibles separated out in F101 will be partially recycled back to M101 and the rest will be either purged or combusted for power generation.We will assume ideal gas for this flowsheet. The properties required for this module are available in the same directory:\n", + "\n", + "- hda_ideal_VLE.py\n", + "- hda_reaction.py\n", + "\n", + "The state variables chosen for the property package are **flows of component by phase, temperature and pressure**. The components considered are: **toluene, hydrogen, benzene and methane**. Therefore, every stream has 8 flow variables, 1 temperature and 1 pressure variable. \n", + "\n", + "![](HDA_flowsheet.png)\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1 Importing Modules\n", + "### 1.1 Importing required pyomo and idaes components\n", + "\n", + "\n", + "To construct a flowsheet, we will need several components from the pyomo and idaes package. Let us first import the following components from Pyomo:\n", + "- Constraint (to write constraints)\n", + "- Var (to declare variables)\n", + "- ConcreteModel (to create the concrete model object)\n", + "- Expression (to evaluate values as a function of variables defined in the model)\n", + "- Objective (to define an objective function for optimization)\n", + "- SolverFactory (to solve the problem)\n", + "- TransformationFactory (to apply certain transformations)\n", + "- Arc (to connect two unit models)\n", + "- SequentialDecomposition (to initialize the flowsheet in a sequential mode)\n", + "\n", + "For further details on these components, please refer to the pyomo documentation: https://pyomo.readthedocs.io/en/stable/\n" + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:26.066510Z", + "start_time": "2025-06-11T22:13:25.662098Z" + } + }, + "source": [ + "from pyomo.environ import (\n", + " Constraint,\n", + " Var,\n", + " ConcreteModel,\n", + " Expression,\n", + " Objective,\n", + " SolverFactory,\n", + " TransformationFactory,\n", + " value,\n", + ")\n", + "from pyomo.network import Arc, SequentialDecomposition" + ], + "outputs": [], + "execution_count": 2 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "From idaes, we will be needing the FlowsheetBlock and the following unit models:\n", + "- Feed\n", + "- Mixer\n", + "- Heater\n", + "- StoichiometricReactor\n", + "- **Flash**\n", + "- Separator (splitter) \n", + "- PressureChanger\n", + "- Product" + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:27.637361Z", + "start_time": "2025-06-11T22:13:26.082580Z" + } + }, + "source": [ + "from idaes.core import FlowsheetBlock" + ], + "outputs": [], + "execution_count": 3 + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:27.752223Z", + "start_time": "2025-06-11T22:13:27.645348Z" + } + }, + "source": [ + "from idaes.models.unit_models import (\n", + " PressureChanger, IsentropicPressureChangerInitializer,\n", + " Mixer, MixerInitializer,\n", + " Separator as Splitter, SeparatorInitializer,\n", + " Heater,\n", + " StoichiometricReactor,\n", + " Feed,\n", + " Product,\n", + ")" + ], + "outputs": [], + "execution_count": 4 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "
    \n", + "Inline Exercise:\n", + "Now, import the remaining unit models highlighted in blue above and run the cell using `Shift+Enter` after typing in the code. \n", + "
    \n" + ] + }, + { + "cell_type": "code", + "metadata": { + "tags": [ + "solution" + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:27.774708Z", + "start_time": "2025-06-11T22:13:27.772299Z" + } + }, + "source": [ + "# Todo: import flash model from idaes.models.unit_models\n", + "from idaes.models.unit_models import Flash" + ], + "outputs": [], + "execution_count": 6 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We will also be needing some utility tools to put together the flowsheet and calculate the degrees of freedom. " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:27.788004Z", + "start_time": "2025-06-11T22:13:27.784891Z" + } + }, + "source": [ + "from idaes.models.unit_models.pressure_changer import ThermodynamicAssumption\n", + "from idaes.core.util.model_statistics import degrees_of_freedom\n", + "from idaes.core.scaling.util import set_scaling_factor\n", + "from idaes.core.scaling.autoscaling import AutoScaler\n", + "\n", + "# Import idaes logger to set output levels\n", + "import idaes.logger as idaeslog\n", + "from idaes.core.solvers import get_solver\n", + "from idaes.core.util.exceptions import InitializationError" + ], + "outputs": [], + "execution_count": 7 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 1.2 Importing required thermo and reaction package\n", + "\n", + "The final set of imports are to import the thermo and reaction package for the HDA process. We have created a custom thermo package that assumes Ideal Gas with support for VLE. \n", + "\n", + "The reaction package here is very simple as we will be using only a StochiometricReactor and the reaction package consists of the stochiometric coefficients for the reaction and the parameter for the heat of reaction. \n", + "\n", + "Let us import the following modules and they are in the same directory as this jupyter notebook:\n", + "
      \n", + "
    • hda_ideal_VLE as thermo_props
    • \n", + "
    • hda_reaction as reaction_props
    • \n", + "
    \n", + "
    " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:27.806315Z", + "start_time": "2025-06-11T22:13:27.798068Z" + } + }, + "source": [ + "from idaes_examples.mod.hda import hda_ideal_VLE as thermo_props\n", + "from idaes_examples.mod.hda import hda_reaction as reaction_props" + ], + "outputs": [], + "execution_count": 8 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2 Constructing the Flowsheet\n", + "\n", + "We have now imported all the components, unit models, and property modules we need to construct a flowsheet. Let us create a ConcreteModel and add the flowsheet block as we did in module 1. " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:27.819615Z", + "start_time": "2025-06-11T22:13:27.814729Z" + } + }, + "source": [ + "m = ConcreteModel()\n", + "m.fs = FlowsheetBlock(dynamic=False)" + ], + "outputs": [], + "execution_count": 9 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We now need to add the property packages to the flowsheet. Unlike Module 1, where we only had a thermo property package, for this flowsheet we will also need to add a reaction property package. " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:27.841949Z", + "start_time": "2025-06-11T22:13:27.829949Z" + } + }, + "source": [ + "m.fs.thermo_params = thermo_props.HDAParameterBlock()\n", + "m.fs.reaction_params = reaction_props.HDAReactionParameterBlock(\n", + " property_package=m.fs.thermo_params\n", + ")" + ], + "outputs": [], + "execution_count": 10 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 2.1 Adding Unit Models\n", + "\n", + "Let us start adding the unit models we have imported to the flowsheet. Here, we are adding the Feed (assigned a name `I101` for Inlet), `Mixer` (assigned a name `M101`) and a `Heater` (assigned a name `H101`). Note that, all unit models need to be given a property package argument. In addition to that, there are several arguments depending on the unit model, please refer to the documentation for more details (https://idaes-pse.readthedocs.io/en/stable/reference_guides/model_libraries/generic/unit_models/index.html). For example, the `Mixer` unit model here must be specified the number of inlets that it will take in and the `Heater` can have specific settings enabled such as `has_pressure_change` or `has_phase_equilibrium`." + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:27.876870Z", + "start_time": "2025-06-11T22:13:27.853517Z" + } + }, + "source": [ + "m.fs.I101 = Feed(\n", + " property_package=m.fs.thermo_params)\n", + "\n", + "m.fs.I102 = Feed(\n", + " property_package=m.fs.thermo_params)\n", + "\n", + "m.fs.M101 = Mixer(\n", + " property_package=m.fs.thermo_params,\n", + " num_inlets=3,\n", + ")\n", + "\n", + "m.fs.H101 = Heater(\n", + " property_package=m.fs.thermo_params,\n", + " has_pressure_change=False,\n", + " has_phase_equilibrium=True,\n", + ")" + ], + "outputs": [], + "execution_count": 11 + }, + { + "cell_type": "code", + "metadata": { + "tags": [ + "solution" + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:27.921265Z", + "start_time": "2025-06-11T22:13:27.910910Z" + } + }, + "source": [ + "# Todo: Add reactor with the specifications above\n", + "m.fs.R101 = StoichiometricReactor(\n", + " property_package=m.fs.thermo_params,\n", + " reaction_package=m.fs.reaction_params,\n", + " has_heat_of_reaction=True,\n", + " has_heat_transfer=True,\n", + " has_pressure_change=False,\n", + ")" + ], + "outputs": [], + "execution_count": 13 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let us now add the Flash(assign the name F101) and pass the following arguments:\n", + "
      \n", + "
    • \"property_package\": m.fs.thermo_params
    • \n", + "
    • \"has_heat_transfer\": True
    • \n", + "
    • \"has_pressure_change\": False
    • \n", + "
    " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:27.947463Z", + "start_time": "2025-06-11T22:13:27.933993Z" + } + }, + "source": [ + "m.fs.F101 = Flash(\n", + " property_package=m.fs.thermo_params,\n", + " has_heat_transfer=True,\n", + " has_pressure_change=True,\n", + ")" + ], + "outputs": [], + "execution_count": 14 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": "Let us now add the Splitter(S101) with specific names for its output (purge and recycle), PressureChanger(C101) and the second Flash(F102)." + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:27.987933Z", + "start_time": "2025-06-11T22:13:27.964931Z" + } + }, + "source": [ + "m.fs.S101 = Splitter(\n", + " property_package=m.fs.thermo_params,\n", + " ideal_separation=False,\n", + " outlet_list=[\"purge\", \"recycle\"],\n", + ")\n", + "\n", + "\n", + "m.fs.C101 = PressureChanger(\n", + " property_package=m.fs.thermo_params,\n", + " compressor=True,\n", + " thermodynamic_assumption=ThermodynamicAssumption.isothermal,\n", + ")\n", + "\n", + "m.fs.F102 = Flash(\n", + " property_package=m.fs.thermo_params,\n", + " has_heat_transfer=True,\n", + " has_pressure_change=True,\n", + ")" + ], + "outputs": [], + "execution_count": 15 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "Last, we will add the three Product blocks (P101, P102, P103). We use `Feed` blocks and `Product` blocks for convenience with reporting stream summaries and consistency" + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:28.009893Z", + "start_time": "2025-06-11T22:13:28.001769Z" + } + }, + "cell_type": "code", + "source": [ + "m.fs.P101 = Product(\n", + " property_package=m.fs.thermo_params)\n", + "\n", + "m.fs.P102 = Product(\n", + " property_package=m.fs.thermo_params)\n", + "\n", + "m.fs.P103 = Product(\n", + " property_package=m.fs.thermo_params)" + ], + "outputs": [], + "execution_count": 16 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 2.2 Connecting Unit Models using Arcs\n", + "\n", + "We have now added all the unit models we need to the flowsheet. However, we have not yet specified how the units are to be connected. To do this, we will be using the `Arc` which is a pyomo component that takes in two arguments: `source` and `destination`. Let us connect the outlet of the inlets (I101, I102) to the inlet of the mixer (M101) and outlet of the mixer to the inlet of the heater(H101)." + ] + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:28.034324Z", + "start_time": "2025-06-11T22:13:28.031285Z" + } + }, + "cell_type": "code", + "source": [ + "m.fs.s01 = Arc(source=m.fs.I101.outlet, destination=m.fs.M101.inlet_1)\n", + "m.fs.s02 = Arc(source=m.fs.I102.outlet, destination=m.fs.M101.inlet_2)\n", + "m.fs.s03 = Arc(source=m.fs.M101.outlet, destination=m.fs.H101.inlet)" + ], + "outputs": [], + "execution_count": 17 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "![](HDA_flowsheet.png) \n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "metadata": { + "tags": [ + "solution" + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:28.084663Z", + "start_time": "2025-06-11T22:13:28.082008Z" + } + }, + "source": [ + "# Todo: Connect the H101 outlet to R101 inlet\n", + "m.fs.s04 = Arc(source=m.fs.H101.outlet, destination=m.fs.R101.inlet)" + ], + "outputs": [], + "execution_count": 19 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We will now be connecting the rest of the flowsheet as shown below. Notice how the outlet names are different for the flash tanks F101 and F102 as they have a vapor and a liquid outlet. " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:28.118422Z", + "start_time": "2025-06-11T22:13:28.113732Z" + } + }, + "source": [ + "m.fs.s05 = Arc(source=m.fs.R101.outlet, destination=m.fs.F101.inlet)\n", + "m.fs.s06 = Arc(source=m.fs.F101.vap_outlet, destination=m.fs.S101.inlet)\n", + "m.fs.s07 = Arc(source=m.fs.F101.liq_outlet, destination=m.fs.F102.inlet)\n", + "m.fs.s08 = Arc(source=m.fs.S101.recycle, destination=m.fs.C101.inlet)\n", + "m.fs.s09 = Arc(source=m.fs.C101.outlet, destination=m.fs.M101.inlet_3)\n" + ], + "outputs": [], + "execution_count": 20 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "Last we will connect the outlet streams to the inlets of the Product blocks (P101, P102, P103)" + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:28.148879Z", + "start_time": "2025-06-11T22:13:28.144601Z" + } + }, + "cell_type": "code", + "source": [ + "m.fs.s10 = Arc(source=m.fs.F102.vap_outlet, destination=m.fs.P101.inlet)\n", + "m.fs.s11 = Arc(source=m.fs.F102.liq_outlet, destination=m.fs.P102.inlet)\n", + "m.fs.s12 = Arc(source=m.fs.S101.purge, destination=m.fs.P103.inlet)" + ], + "outputs": [], + "execution_count": 21 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We have now connected the unit model block using the arcs. However, each of these arcs link to ports on the two unit models that are connected. In this case, the ports consist of the state variables that need to be linked between the unit models. Pyomo provides a convenient method to write these equality constraints for us between two ports and this is done as follows:" + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:28.198384Z", + "start_time": "2025-06-11T22:13:28.176239Z" + } + }, + "source": [ + "TransformationFactory(\"network.expand_arcs\").apply_to(m)" + ], + "outputs": [], + "execution_count": 22 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 2.3 Adding expressions to compute purity and operating costs\n", + "\n", + "In this section, we will add a few Expressions that allows us to evaluate the performance. Expressions provide a convenient way of calculating certain values that are a function of the variables defined in the model. For more details on Expressions, please refer to: https://pyomo.readthedocs.io/en/stable/pyomo_modeling_components/Expressions.html\n", + "\n", + "For this flowsheet, we are interested in computing the purity of the product Benzene stream (i.e. the mole fraction) and the operating cost which is a sum of the cooling and heating cost. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let us first add an Expression to compute the mole fraction of benzene in the `vap_outlet` of F102 which is our product stream. Please note that the var flow_mol_phase_comp has the index - [time, phase, component]. As this is a steady-state flowsheet, the time index by default is 0. The valid phases are [\"Liq\", \"Vap\"]. Similarly the valid component list is [\"benzene\", \"toluene\", \"hydrogen\", \"methane\"]." + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:28.222088Z", + "start_time": "2025-06-11T22:13:28.218400Z" + } + }, + "source": [ + "m.fs.purity = Expression(\n", + " expr=m.fs.F102.vap_outlet.flow_mol_phase_comp[0, \"Vap\", \"benzene\"]\n", + " / (\n", + " m.fs.F102.vap_outlet.flow_mol_phase_comp[0, \"Vap\", \"benzene\"]\n", + " + m.fs.F102.vap_outlet.flow_mol_phase_comp[0, \"Vap\", \"toluene\"]\n", + " )\n", + ")" + ], + "outputs": [], + "execution_count": 23 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, let us add an expression to compute the cooling cost assuming a cost of 0.212E-4 $/kW. Note that cooling utility is required for the reactor (R101) and the first flash (F101). " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:28.248216Z", + "start_time": "2025-06-11T22:13:28.244244Z" + } + }, + "source": [ + "m.fs.cooling_cost = Expression(\n", + " expr=0.212e-7 * (-m.fs.F101.heat_duty[0]) + 0.212e-7 * (-m.fs.R101.heat_duty[0])\n", + ")" + ], + "outputs": [], + "execution_count": 24 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "Now, let us add an expression to compute the heating cost assuming the utility cost as follows:\n", + "
      \n", + "
    • 2.2E-4 dollars/kW for H101
    • \n", + "
    • 1.9E-4 dollars/kW for F102
    • \n", + "
    \n", + "Note that the heat duty is in units of watt (J/s). " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:28.271256Z", + "start_time": "2025-06-11T22:13:28.268284Z" + } + }, + "source": [ + "m.fs.heating_cost = Expression(\n", + " expr=2.2e-7 * m.fs.H101.heat_duty[0] + 1.9e-7 * m.fs.F102.heat_duty[0]\n", + ")" + ], + "outputs": [], + "execution_count": 25 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let us now add an expression to compute the total operating cost per year which is basically the sum of the cooling and heating cost we defined above. " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:28.295580Z", + "start_time": "2025-06-11T22:13:28.292627Z" + } + }, + "source": [ + "m.fs.operating_cost = Expression(\n", + " expr=(3600 * 24 * 365 * (m.fs.heating_cost + m.fs.cooling_cost))\n", + ")" + ], + "outputs": [], + "execution_count": 26 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": [ + "## 3 Scaling the Model\n", + "\n", + "In this example, we will simply use the ``AutoScaler`` method to scale our model since this example is focused on introductory flowsheet building for a full process system.\n", + "\n", + "For more direct control, a manual, magnitude based approach can be used. If this method is chosen or appropriate, it is highly recommended to look at these documentation:\n", + "- Scaling Toolbox https://idaes-pse.readthedocs.io/en/stable/reference_guides/scaling/scaling.html\n", + "- Scaling Workshop https://idaes-examples.readthedocs.io/en/latest/docs/scaling/scaler_workshop_doc.html.\n", + "\n", + "In this example, we already imported the ``AutoScaler`` class in the beginning so we just create the ``autoscaler`` object and call the ``scale_model`` method on the model `m`." + ] + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:28.915668Z", + "start_time": "2025-06-11T22:13:28.317020Z" + } + }, + "cell_type": "code", + "source": [ + "autoscaler = AutoScaler()\n", + "autoscaler.scale_model(m)" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "WARNING: model contains export suffix 'scaling_factor' that contains 2\n", + "component keys that are not exported as part of the NL file. Skipping.\n" + ] + } + ], + "execution_count": 27 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 4 Specifying the Model\n", + "### 4.1 Fixing feed conditions\n", + "\n", + "Let us first check how many degrees of freedom exist for this flowsheet using the `degrees_of_freedom` tool we imported earlier. " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:28.948173Z", + "start_time": "2025-06-11T22:13:28.924210Z" + } + }, + "source": [ + "print(degrees_of_freedom(m))" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "29\n" + ] + } + ], + "execution_count": 28 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": "We will now be fixing the toluene feed (`I101`) stream to the conditions shown in the flowsheet above. Please note that though this is a pure toluene feed, the remaining components are still assigned a very small non-zero value to help with convergence and initializing." + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.012096Z", + "start_time": "2025-06-11T22:13:29.006965Z" + } + }, + "source": [ + "m.fs.I101.flow_mol_phase_comp[0, \"Vap\", \"benzene\"].fix(1e-5)\n", + "m.fs.I101.flow_mol_phase_comp[0, \"Vap\", \"toluene\"].fix(1e-5)\n", + "m.fs.I101.flow_mol_phase_comp[0, \"Vap\", \"hydrogen\"].fix(1e-5)\n", + "m.fs.I101.flow_mol_phase_comp[0, \"Vap\", \"methane\"].fix(1e-5)\n", + "m.fs.I101.flow_mol_phase_comp[0, \"Liq\", \"benzene\"].fix(1e-5)\n", + "m.fs.I101.flow_mol_phase_comp[0, \"Liq\", \"toluene\"].fix(0.30)\n", + "m.fs.I101.flow_mol_phase_comp[0, \"Liq\", \"hydrogen\"].fix(1e-5)\n", + "m.fs.I101.flow_mol_phase_comp[0, \"Liq\", \"methane\"].fix(1e-5)\n", + "m.fs.I101.temperature.fix(303.2)\n", + "m.fs.I101.pressure.fix(350000)" + ], + "outputs": [], + "execution_count": 30 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "Similarly, let us fix the hydrogen feed (`I102`) to the following conditions in the next cell:\n", + "
      \n", + "
    • FH2 = 0.30 mol/s
    • \n", + "
    • FCH4 = 0.02 mol/s
    • \n", + "
    • Remaining components = 1e-5 mol/s
    • \n", + "
    • T = 303.2 K
    • \n", + "
    • P = 350000 Pa
    • \n", + "
    \n", + "\n" + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.036253Z", + "start_time": "2025-06-11T22:13:29.031731Z" + } + }, + "source": [ + "m.fs.I102.flow_mol_phase_comp[0, \"Vap\", \"benzene\"].fix(1e-5)\n", + "m.fs.I102.flow_mol_phase_comp[0, \"Vap\", \"toluene\"].fix(1e-5)\n", + "m.fs.I102.flow_mol_phase_comp[0, \"Vap\", \"hydrogen\"].fix(0.30)\n", + "m.fs.I102.flow_mol_phase_comp[0, \"Vap\", \"methane\"].fix(0.02)\n", + "m.fs.I102.flow_mol_phase_comp[0, \"Liq\", \"benzene\"].fix(1e-5)\n", + "m.fs.I102.flow_mol_phase_comp[0, \"Liq\", \"toluene\"].fix(1e-5)\n", + "m.fs.I102.flow_mol_phase_comp[0, \"Liq\", \"hydrogen\"].fix(1e-5)\n", + "m.fs.I102.flow_mol_phase_comp[0, \"Liq\", \"methane\"].fix(1e-5)\n", + "m.fs.I102.temperature.fix(303.2)\n", + "m.fs.I102.pressure.fix(350000)" + ], + "outputs": [], + "execution_count": 31 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 4.2 Fixing unit model specifications\n", + "\n", + "Now that we have fixed our inlet feed conditions, we will now be fixing the operating conditions for the unit models in the flowsheet. Let us set set the H101 outlet temperature to 600 K. " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.078559Z", + "start_time": "2025-06-11T22:13:29.075531Z" + } + }, + "source": [ + "m.fs.H101.outlet.temperature.fix(600)" + ], + "outputs": [], + "execution_count": 32 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For the StoichiometricReactor, we have to define the conversion in terms of toluene. This requires us to create a new variable for specifying the conversion and adding a Constraint that defines the conversion with respect to toluene. The second degree of freedom for the reactor is to define the heat duty. In this case, let us assume the reactor to be adiabatic i.e. Q = 0. " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.111099Z", + "start_time": "2025-06-11T22:13:29.106956Z" + } + }, + "source": [ + "m.fs.R101.conversion = Var(initialize=0.75, bounds=(0, 1))\n", + "\n", + "m.fs.R101.conv_constraint = Constraint(\n", + " expr=m.fs.R101.conversion * m.fs.R101.inlet.flow_mol_phase_comp[0, \"Vap\", \"toluene\"]\n", + " == (\n", + " m.fs.R101.inlet.flow_mol_phase_comp[0, \"Vap\", \"toluene\"]\n", + " - m.fs.R101.outlet.flow_mol_phase_comp[0, \"Vap\", \"toluene\"]\n", + " )\n", + ")\n", + "\n", + "m.fs.R101.conversion.fix(0.75)\n", + "m.fs.R101.heat_duty.fix(0)" + ], + "outputs": [], + "execution_count": 33 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The Flash conditions for F101 can be set as follows. " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.128972Z", + "start_time": "2025-06-11T22:13:29.125273Z" + } + }, + "source": [ + "m.fs.F101.vap_outlet.temperature.fix(325.0)\n", + "m.fs.F101.deltaP.fix(0)" + ], + "outputs": [], + "execution_count": 34 + }, + { + "cell_type": "code", + "metadata": { + "tags": [ + "solution" + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.171742Z", + "start_time": "2025-06-11T22:13:29.168352Z" + } + }, + "source": [ + "m.fs.F102.vap_outlet.temperature.fix(375)\n", + "m.fs.F102.deltaP.fix(-200000)" + ], + "outputs": [], + "execution_count": 36 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let us fix the purge split fraction to 20% and the outlet pressure of the compressor is set to 350000 Pa. " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.196247Z", + "start_time": "2025-06-11T22:13:29.192956Z" + } + }, + "source": [ + "m.fs.S101.split_fraction[0, \"purge\"].fix(0.2)\n", + "m.fs.C101.outlet.pressure.fix(350000)" + ], + "outputs": [], + "execution_count": 37 + }, + { + "cell_type": "code", + "metadata": { + "tags": [ + "solution" + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.263397Z", + "start_time": "2025-06-11T22:13:29.241129Z" + } + }, + "source": [ + "print(degrees_of_freedom(m))" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0\n" + ] + } + ], + "execution_count": 39 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 5 Initializing the Model\n", + "\n", + "\n", + "\n", + "When a flowsheet contains a recycle loop, the outlet of a downstream unit becomes the inlet of an upstream unit, creating a cyclic dependency that prevents straightforward calculation of all stream conditions. The tear\u2010stream method is necessary because it \u201cbreaks\u201d this loop: you select one recycle stream as the tear, assign it an initial guess, and then solve the rest of the flowsheet as if it were acyclic. Once the downstream units compute their outputs, you compare the calculated value of the torn stream to your initial guess and iteratively adjust until they coincide. Without tearing, the solver cannot establish a proper topological sequence or drive the recycle to convergence, making initialization\u2014and ultimately steady\u2010state convergence\u2014impossible.\n", + "\n", + "It is important to determine the tear stream for a flowsheet which will be demonstrated below.\n", + "\n", + "\n", + "![](HDA_flowsheet.png)\n", + "\n", + "Currently, there are two methods of initializing a full flowsheet: using the sequential decomposition tool, or manually propagating through the flowsheet. Both methods will be shown.\n", + "\n", + "### 5.1 Sequential Decomposition\n", + "\n", + "This section will demonstrate how to use the built-in sequential decomposition tool to initialize our flowsheet. Sequential Decomposition is a tool from pyomo where the documentation can be found here https://pyomo.readthedocs.io/en/stable/explanation/modeling/network.html#sequential-decomposition\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": "Let us first create an object for the SequentialDecomposition and specify our options for this. We can also create a graph for our flowsheet to determine the tear set and order." + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.330212Z", + "start_time": "2025-06-11T22:13:29.324872Z" + } + }, + "source": [ + "seq = SequentialDecomposition()\n", + "seq.options.select_tear_method = \"heuristic\"\n", + "seq.options.tear_method = \"Wegstein\"\n", + "seq.options.iterLim = 3\n", + "\n", + "# Using the SD tool\n", + "G = seq.create_graph(m)\n", + "heuristic_tear_set = seq.tear_set_arcs(G, method=\"heuristic\")\n", + "order = seq.calculation_order(G)" + ], + "outputs": [], + "execution_count": 41 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Which is the tear stream? Display tear set and order" + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.353734Z", + "start_time": "2025-06-11T22:13:29.350730Z" + } + }, + "source": [ + "for o in heuristic_tear_set:\n", + " print(o.name)" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "fs.s03\n" + ] + } + ], + "execution_count": 42 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "What sequence did the SD tool determine to solve this flowsheet with the least number of tears? " + ] + }, + { + "cell_type": "code", + "metadata": { + "scrolled": true, + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.391173Z", + "start_time": "2025-06-11T22:13:29.388153Z" + } + }, + "source": [ + "for o in order:\n", + " print(o[0].name)" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "fs.I101\n", + "fs.R101\n", + "fs.F101\n", + "fs.S101\n", + "fs.C101\n", + "fs.M101\n" + ] + } + ], + "execution_count": 43 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + " \n", + "\n", + "![](HDA_tear_stream.png) \n", + "\n", + "\n", + "The SequentialDecomposition tool has determined that the tear stream is the mixer outlet. You can see this shown in the picture of the flowsheet above as the outlet of the mixer as the two lines crossing it identifying it as the tear stream. We will need to provide a reasonable guess for this." + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.430684Z", + "start_time": "2025-06-11T22:13:29.426921Z" + } + }, + "source": [ + "tear_guesses = {\n", + " \"flow_mol_phase_comp\": {\n", + " (0, \"Liq\", \"benzene\"): 1e-5,\n", + " (0, \"Liq\", \"toluene\"): 0.30,\n", + " (0, \"Liq\", \"methane\"): 1e-5,\n", + " (0, \"Liq\", \"hydrogen\"): 1e-5,\n", + " (0, \"Vap\", \"benzene\"): 1e-5,\n", + " (0, \"Vap\", \"toluene\"): 1e-5,\n", + " (0, \"Vap\", \"methane\"): 0.02,\n", + " (0, \"Vap\", \"hydrogen\"): 0.30,\n", + " },\n", + " \"temperature\": {0: 303},\n", + " \"pressure\": {0: 350000},\n", + "}\n", + "\n", + "# Pass the tear_guess to the SD tool\n", + "seq.set_guesses_for(m.fs.H101.inlet, tear_guesses)" + ], + "outputs": [], + "execution_count": 44 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next, we need to tell the tool how to initialize a particular unit. We will be writing a python function which takes in a \"unit\" and calls the initialize method on that unit. " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.465203Z", + "start_time": "2025-06-11T22:13:29.462031Z" + } + }, + "source": [ + "def function(unit):\n", + " try:\n", + " initializer = unit.default_initializer()\n", + " initializer.initialize(unit, output_level=idaeslog.INFO)\n", + " except InitializationError:\n", + " solver = get_solver()\n", + " solver.solve(unit)" + ], + "outputs": [], + "execution_count": 45 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We are now ready to initialize our flowsheet in a sequential mode. Note that we specifically set the iteration limit to be 5 as we are trying to use this tool only to get a good set of initial values such that IPOPT can then take over and solve this flowsheet for us. " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.505027Z", + "start_time": "2025-06-11T22:13:29.501933Z" + } + }, + "source": "# seq.run(m, function)", + "outputs": [], + "execution_count": 46 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": [ + "### 5.2 Manual Propogation Method\n", + "\n", + "This method uses a more direct approach to initialize the flowsheet, utilizing the updated initalizer method and propogating manually throught the flowsheet and solving for the tear stream directly.\n", + "Lets first import a helper function that will help us manually propagate and step through the flowsheet" + ] + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.536579Z", + "start_time": "2025-06-11T22:13:29.533074Z" + } + }, + "cell_type": "code", + "source": "from idaes.core.util.initialization import propagate_state", + "outputs": [], + "execution_count": 47 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": [ + "Now we can setup our initial guesses for the tear stream which we know is the outlet of the `Mixer` or the inlet of the `Heater`. We can use the same initial guesses used in the first method. We also want to ensure that the degrees of freedom are consistent while we manually intialize the model.\n", + "\n", + "We will first ensure that are current degrees of freedom is still zero" + ] + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.583098Z", + "start_time": "2025-06-11T22:13:29.553382Z" + } + }, + "cell_type": "code", + "source": "print(f\"The DOF is {degrees_of_freedom(m)} initially\")", + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The DOF is 0 initially\n" + ] + } + ], + "execution_count": 48 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "Now we can manually deactivate the tear stream, creating a separation between the `Mixer` and `Heater`. This should reduce the degrees of freedom by 10 since the inlet of the `Heater` now contains no values to solve the unit model. To deactivate a stream, simply use `m.fs.s03_expanded.deactivate()`. This expanded stream is just a different version of the `Arc` stream that is able to be deactivated." + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.633917Z", + "start_time": "2025-06-11T22:13:29.610932Z" + } + }, + "cell_type": "code", + "source": [ + "m.fs.s03_expanded.deactivate()\n", + "\n", + "print(f\"The DOF is {degrees_of_freedom(m)} after deactivating the tear stream\")" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The DOF is 10 after deactivating the tear stream\n" + ] + } + ], + "execution_count": 49 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "Now we can provide the `Heater` inlet 10 guess values to bring the degrees of freedom back to 0 and start the manual initialization process. We can run this convenient loop to assign each of these guesses to the inlet of the heater." + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.702707Z", + "start_time": "2025-06-11T22:13:29.654459Z" + } + }, + "cell_type": "code", + "source": [ + "tear_guesses = {\n", + " \"flow_mol_phase_comp\": {\n", + " (0, \"Liq\", \"benzene\"): 1e-5,\n", + " (0, \"Liq\", \"toluene\"): 0.30,\n", + " (0, \"Liq\", \"methane\"): 1e-5,\n", + " (0, \"Liq\", \"hydrogen\"): 1e-5,\n", + " (0, \"Vap\", \"benzene\"): 1e-5,\n", + " (0, \"Vap\", \"toluene\"): 1e-5,\n", + " (0, \"Vap\", \"methane\"): 0.02,\n", + " (0, \"Vap\", \"hydrogen\"): 0.30,\n", + "\n", + " },\n", + " \"temperature\": {0: 303},\n", + " \"pressure\": {0: 350000},\n", + "}\n", + "\n", + "for k, v in tear_guesses.items():\n", + " for k1, v1 in v.items():\n", + " getattr(m.fs.s03.destination, k)[k1].fix(v1)\n", + "\n", + "DOF_initial = degrees_of_freedom(m)\n", + "print(f\"The DOF is {degrees_of_freedom(m)} after providing the initial guesses\")" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The DOF is 0 after providing the initial guesses\n" + ] + } + ], + "execution_count": 50 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "The next step is to manually initialize each unit model starting from the `Heater` and then propagate the connection between it and the next unit model. This manual process ensures a strict order to the user's specification if that is desired. The current standard for initializing a unit model is to use an initializer object most compatible for that unit model. This can most often be done by utilizing the `default_initializer()` method attached to the unit model and then to call the `initialize()` method with the unit model as the argument." + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:31.347435Z", + "start_time": "2025-06-11T22:13:29.712721Z" + } + }, + "cell_type": "code", + "source": [ + "m.fs.H101.default_initializer().initialize(m.fs.H101) # Initialize Heater\n", + "propagate_state(m.fs.s04) # Establish connection between Heater and Reactor\n", + "m.fs.R101.default_initializer().initialize(m.fs.R101) # Initialize Reactor\n", + "propagate_state(m.fs.s05) # Establish connection between Reactor and First Flash Unit\n", + "m.fs.F101.default_initializer().initialize(m.fs.F101) # Initialize First Flash Unit\n", + "propagate_state(m.fs.s06) # Establish connection between First Flash Unit and Splitter\n", + "propagate_state(m.fs.s07) # Establish connection between First Flash Unit and Second Flash Unit\n", + "m.fs.S101.default_initializer().initialize(m.fs.S101) # Initialize Splitter\n", + "propagate_state(m.fs.s08) # Establish connection between Splitter and Compressor\n", + "m.fs.C101.default_initializer().initialize(m.fs.C101) # Initialize Compressor\n", + "propagate_state(m.fs.s09) # Establish connection between Compressor and Mixer\n", + "m.fs.I101.default_initializer().initialize(m.fs.I101) # Initialize Toluene Inlet\n", + "propagate_state(m.fs.s01) # Establish connection between Toluene Inlet and Mixer\n", + "m.fs.I102.default_initializer().initialize(m.fs.I102) # Initialize Hydrogen Inlet\n", + "propagate_state(m.fs.s02) # Establish connection between Hydrogen Inlet and Mixer\n", + "m.fs.M101.default_initializer().initialize(m.fs.M101) # Initialize Mixer\n", + "propagate_state(m.fs.s03) # Establish connection between Mixer and Heater\n", + "m.fs.F102.default_initializer().initialize(m.fs.F102) # Initialize Second Flash Unit\n", + "propagate_state(m.fs.s10) # Establish connection between Second Flash Unit and Benzene Product\n", + "propagate_state(m.fs.s11) # Establish connection between Second Flash Unit and Toluene Product\n", + "propagate_state(m.fs.s12) # Establish connection between Splitter and Purge Product\n" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2025-06-11 16:13:29 [INFO] idaes.init.fs.H101.control_volume.properties_in: Initialization Complete\n", + "2025-06-11 16:13:30 [INFO] idaes.init.fs.H101.control_volume.properties_out: Initialization Complete\n", + "2025-06-11 16:13:30 [INFO] idaes.init.fs.R101.control_volume.properties_in: Initialization Complete\n", + "2025-06-11 16:13:30 [INFO] idaes.init.fs.R101.control_volume.properties_out: Initialization Complete\n", + "2025-06-11 16:13:30 [INFO] idaes.init.fs.F101.control_volume.properties_in: Initialization Complete\n", + "2025-06-11 16:13:30 [INFO] idaes.init.fs.F101.control_volume.properties_out: Initialization Complete\n", + "2025-06-11 16:13:30 [INFO] idaes.init.fs.S101.mixed_state: Initialization Complete\n", + "2025-06-11 16:13:30 [INFO] idaes.init.fs.S101.purge_state: Initialization Complete\n", + "2025-06-11 16:13:30 [INFO] idaes.init.fs.S101.recycle_state: Initialization Complete\n", + "2025-06-11 16:13:30 [INFO] idaes.init.fs.S101: Initialization Step 2 Complete: optimal - \n", + "2025-06-11 16:13:30 [INFO] idaes.init.fs.C101.control_volume.properties_in: Initialization Complete\n", + "2025-06-11 16:13:31 [INFO] idaes.init.fs.C101.control_volume.properties_out: Initialization Complete\n", + "2025-06-11 16:13:31 [INFO] idaes.init.fs.I101.properties: Initialization Complete\n", + "2025-06-11 16:13:31 [INFO] idaes.init.fs.I102.properties: Initialization Complete\n", + "2025-06-11 16:13:31 [INFO] idaes.init.fs.M101.inlet_1_state: Initialization Complete\n", + "2025-06-11 16:13:31 [INFO] idaes.init.fs.M101.inlet_2_state: Initialization Complete\n", + "2025-06-11 16:13:31 [INFO] idaes.init.fs.M101.inlet_3_state: Initialization Complete\n", + "2025-06-11 16:13:31 [INFO] idaes.init.fs.M101.mixed_state: Initialization Complete\n", + "2025-06-11 16:13:31 [INFO] idaes.init.fs.M101: Initialization Complete: optimal - \n", + "2025-06-11 16:13:31 [INFO] idaes.init.fs.F102.control_volume.properties_in: Initialization Complete\n", + "2025-06-11 16:13:31 [INFO] idaes.init.fs.F102.control_volume.properties_out: Initialization Complete\n" + ] + } + ], + "execution_count": 51 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "Now we solve the system to allow the outlet of the mixer to reach a converged congruence with the inlet of the heater." + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:31.935414Z", + "start_time": "2025-06-11T22:13:31.357025Z" + } + }, + "cell_type": "code", + "source": [ + "solver = get_solver()\n", + "results = solver.solve(m, tee=True)" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "WARNING: model contains export suffix 'scaling_factor' that contains 40\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "Ipopt 3.13.2: nlp_scaling_method=gradient-based\n", + "tol=1e-06\n", + "max_iter=200\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...: 1112\n", + "Number of nonzeros in inequality constraint Jacobian.: 0\n", + "Number of nonzeros in Lagrangian Hessian.............: 999\n", + "\n", + "Total number of variables............................: 380\n", + " variables with only lower bounds: 0\n", + " variables with lower and upper bounds: 186\n", + " variables with only upper bounds: 0\n", + "Total number of equality constraints.................: 380\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 1.24e+05 0.00e+00 -1.0 0.00e+00 - 0.00e+00 0.00e+00 0\n", + " 1 0.0000000e+00 1.24e+05 2.12e+00 -1.0 1.99e+05 - 1.39e-01 3.68e-04h 10\n", + " 2 0.0000000e+00 1.24e+05 5.49e+00 -1.0 1.99e+05 - 1.43e-01 3.66e-04h 10\n", + " 3 0.0000000e+00 1.24e+05 4.13e+01 -1.0 2.00e+05 - 9.51e-01 3.64e-04h 10\n", + " 4 0.0000000e+00 1.24e+05 7.47e+01 -1.0 2.00e+05 - 1.77e-01 3.63e-04h 10\n", + " 5 0.0000000e+00 1.24e+05 4.07e+02 -1.0 2.01e+05 - 9.90e-01 3.61e-04h 10\n", + " 6 0.0000000e+00 1.24e+05 6.97e+02 -1.0 2.01e+05 - 1.63e-01 3.60e-04h 10\n", + " 7 0.0000000e+00 1.24e+05 3.75e+03 -1.0 2.02e+05 - 9.90e-01 3.58e-04h 10\n", + " 8 0.0000000e+00 1.24e+05 6.44e+03 -1.0 2.02e+05 - 1.63e-01 3.57e-04h 10\n", + " 9 0.0000000e+00 1.24e+05 3.51e+04 -1.0 2.03e+05 - 1.00e+00 3.55e-04h 10\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 10 0.0000000e+00 1.24e+05 6.05e+04 -1.0 2.04e+05 - 1.62e-01 3.54e-04h 10\n", + " 11 0.0000000e+00 2.01e+06 2.73e+05 -1.0 2.04e+05 - 1.00e+00 1.80e-01w 1\n", + " 12 0.0000000e+00 2.01e+06 3.91e+07 -1.0 7.38e+05 - 6.21e-02 5.22e-04w 1\n", + " 13 0.0000000e+00 2.01e+06 6.94e+11 -1.0 7.50e+05 - 9.13e-02 5.14e-06w 1\n", + " 14 0.0000000e+00 1.24e+05 3.32e+05 -1.0 6.22e+04 - 1.00e+00 3.52e-04h 9\n", + " 15 0.0000000e+00 1.24e+05 5.43e+05 -1.0 2.05e+05 - 1.41e-01 3.51e-04h 10\n", + " 16 0.0000000e+00 1.24e+05 3.01e+06 -1.0 2.05e+05 - 1.00e+00 3.49e-04h 10\n", + " 17 0.0000000e+00 1.24e+05 4.76e+06 -1.0 2.06e+05 - 1.28e-01 3.48e-04h 10\n", + " 18 0.0000000e+00 1.24e+05 2.66e+07 -1.0 2.06e+05 - 1.00e+00 3.46e-04h 10\n", + " 19 0.0000000e+00 1.24e+05 4.23e+07 -1.0 2.07e+05 - 1.28e-01 3.45e-04h 10\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 20 0.0000000e+00 1.24e+05 2.38e+08 -1.0 2.08e+05 - 1.00e+00 3.43e-04h 10\n", + " 21 0.0000000e+00 1.24e+05 3.80e+08 -1.0 2.08e+05 - 1.28e-01 3.42e-04h 10\n", + " 22 0.0000000e+00 1.23e+05 2.16e+09 -1.0 2.09e+05 - 1.00e+00 3.40e-04h 10\n", + " 23 0.0000000e+00 1.23e+05 3.45e+09 -1.0 2.09e+05 - 1.28e-01 3.39e-04h 10\n", + " 24 0.0000000e+00 1.96e+06 1.26e+10 -1.0 2.10e+05 - 7.69e-01 1.73e-01w 1\n", + " 25 0.0000000e+00 1.96e+06 1.91e+12 -1.0 7.43e+05 - 6.14e-02 5.08e-04w 1\n", + " 26 0.0000000e+00 1.96e+06 7.01e+16 -1.0 7.55e+05 - 1.84e-01 5.01e-06w 1\n", + " 27 0.0000000e+00 1.23e+05 1.60e+10 -1.0 1.00e+05 - 7.69e-01 3.37e-04h 9\n", + " 28 0.0000000e+00 1.23e+05 2.61e+10 -1.0 2.10e+05 - 1.33e-01 3.36e-04h 10\n", + " 29 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.11e+05 - 1.00e+00 3.35e-04h 10\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 30 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.11e+05 - 1.27e-01 3.33e-04h 10\n", + " 31 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.12e+05 - 5.83e-01 3.32e-04h 10\n", + " 32 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.13e+05 - 1.41e-01 3.30e-04h 10\n", + " 33 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.13e+05 - 1.00e+00 3.29e-04h 10\n", + " 34 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.14e+05 - 1.26e-01 3.28e-04h 10\n", + " 35 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.14e+05 - 6.16e-01 3.26e-04h 10\n", + " 36 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.15e+05 - 1.38e-01 3.25e-04h 10\n", + " 37 0.0000000e+00 1.90e+06 5.27e+11 -1.0 2.15e+05 - 1.00e+00 1.66e-01w 1\n", + " 38 0.0000000e+00 1.90e+06 6.09e+13 -1.0 2.72e+05 - 1.39e-01 1.43e-03w 1\n", + " 39 0.0000000e+00 1.90e+06 1.06e+17 -1.0 7.68e+05 - 9.45e-02 4.82e-06w 1\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 40 0.0000000e+00 1.23e+05 1.04e+11 -1.0 7.68e+05 - 1.00e+00 3.23e-04h 9\n", + " 41 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.16e+05 - 1.26e-01 3.22e-04h 10\n", + " 42 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.17e+05 - 6.03e-01 3.21e-04h 10\n", + " 43 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.17e+05 - 1.38e-01 3.19e-04h 10\n", + " 44 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.18e+05 - 1.00e+00 3.18e-04h 10\n", + " 45 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.18e+05 - 1.25e-01 3.17e-04h 10\n", + " 46 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.19e+05 - 6.03e-01 3.15e-04h 10\n", + " 47 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.20e+05 - 1.38e-01 3.14e-04h 10\n", + " 48 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.20e+05 - 1.00e+00 3.13e-04h 10\n", + " 49 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.21e+05 - 1.25e-01 3.11e-04h 10\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 50 0.0000000e+00 1.85e+06 3.37e+11 -1.0 2.21e+05 - 5.99e-01 1.59e-01w 1\n", + " 51 0.0000000e+00 1.85e+06 5.67e+13 -1.0 7.53e+05 - 6.18e-02 4.82e-04w 1\n", + " 52 0.0000000e+00 1.85e+06 1.08e+17 -1.0 7.64e+05 - 2.20e-01 4.75e-06w 1\n", + " 53 0.0000000e+00 1.23e+05 1.04e+11 -1.0 7.64e+05 - 5.99e-01 3.10e-04h 9\n", + " 54 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.22e+05 - 1.38e-01 3.09e-04h 10\n", + " 55 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.22e+05 - 1.00e+00 3.07e-04h 10\n", + " 56 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.23e+05 - 1.24e-01 3.06e-04h 10\n", + " 57 0.0000000e+00 1.22e+05 1.04e+11 -1.0 2.24e+05 - 5.97e-01 3.05e-04h 10\n", + " 58 0.0000000e+00 1.22e+05 1.04e+11 -1.0 2.24e+05 - 1.37e-01 3.04e-04h 10\n", + " 59 0.0000000e+00 1.22e+05 1.04e+11 -1.0 2.25e+05 - 1.00e+00 3.02e-04h 10\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 60 0.0000000e+00 1.22e+05 1.04e+11 -1.0 2.25e+05 - 1.24e-01 3.01e-04h 10\n", + " 61 0.0000000e+00 1.22e+05 1.04e+11 -1.0 2.26e+05 - 5.94e-01 3.00e-04h 10\n", + " 62 0.0000000e+00 1.22e+05 1.04e+11 -1.0 2.26e+05 - 1.37e-01 2.98e-04h 10\n", + " 63 0.0000000e+00 1.79e+06 6.03e+11 -1.0 2.27e+05 - 1.00e+00 1.52e-01w 1\n", + " 64 0.0000000e+00 1.79e+06 9.13e+13 -1.0 7.58e+05 - 6.05e-02 4.69e-04w 1\n", + " 65 0.0000000e+00 1.79e+06 1.10e+17 -1.0 7.68e+05 - 1.20e-01 4.63e-06w 1\n", + " 66 0.0000000e+00 1.22e+05 1.04e+11 -1.0 7.69e+05 - 1.00e+00 2.97e-04h 9\n", + " 67 0.0000000e+00 1.22e+05 1.04e+11 -1.0 2.28e+05 - 1.23e-01 2.96e-04h 10\n", + " 68 0.0000000e+00 1.22e+05 1.04e+11 -1.0 2.28e+05 - 5.91e-01 2.95e-04h 10\n", + " 69 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.29e+05 - 1.36e-01 2.93e-04h 10\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 70 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.29e+05 - 1.00e+00 2.92e-04h 10\n", + " 71 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.30e+05 - 1.23e-01 2.91e-04h 10\n", + " 72 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.31e+05 - 5.89e-01 2.90e-04h 10\n", + " 73 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.31e+05 - 1.36e-01 2.89e-04h 10\n", + " 74 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.32e+05 - 1.00e+00 2.87e-04h 10\n", + " 75 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.32e+05 - 1.23e-01 2.86e-04h 10\n", + " 76 0.0000000e+00 1.74e+06 3.75e+11 -1.0 2.33e+05 - 5.86e-01 1.46e-01w 1\n", + " 77 0.0000000e+00 1.74e+06 6.57e+13 -1.0 7.62e+05 - 6.18e-02 4.58e-04w 1\n", + " 78 0.0000000e+00 1.74e+06 1.12e+17 -1.0 7.73e+05 - 2.30e-01 4.51e-06w 1\n", + " 79 0.0000000e+00 1.22e+05 1.05e+11 -1.0 7.73e+05 - 5.86e-01 2.85e-04h 9\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 80 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.34e+05 - 1.36e-01 2.84e-04h 10\n", + " 81 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.34e+05 - 1.00e+00 2.83e-04h 10\n", + " 82 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.35e+05 - 1.22e-01 2.81e-04h 10\n", + " 83 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.35e+05 - 5.83e-01 2.80e-04h 10\n", + " 84 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.36e+05 - 1.35e-01 2.79e-04h 10\n", + " 85 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.37e+05 - 1.00e+00 2.78e-04h 10\n", + " 86 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.37e+05 - 1.22e-01 2.77e-04h 10\n", + " 87 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.38e+05 - 5.81e-01 2.76e-04h 10\n", + " 88 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.38e+05 - 1.35e-01 2.74e-04h 10\n", + " 89 0.0000000e+00 1.69e+06 6.88e+11 -1.0 2.39e+05 - 1.00e+00 1.40e-01w 1\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 90 0.0000000e+00 1.69e+06 1.08e+14 -1.0 7.66e+05 - 6.03e-02 4.46e-04w 1\n", + " 91 0.0000000e+00 1.69e+06 1.14e+17 -1.0 7.77e+05 - 1.22e-01 4.40e-06w 1\n", + " 92 0.0000000e+00 1.22e+05 1.05e+11 -1.0 7.77e+05 - 1.00e+00 2.73e-04h 9\n", + " 93 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.39e+05 - 1.21e-01 2.72e-04h 10\n", + " 94 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.40e+05 - 5.78e-01 2.71e-04h 10\n", + " 95 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.41e+05 - 1.34e-01 2.70e-04h 10\n", + " 96 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.41e+05 - 1.00e+00 2.69e-04h 10\n", + " 97 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.42e+05 - 1.21e-01 2.68e-04h 10\n", + " 98 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.42e+05 - 5.76e-01 2.67e-04h 10\n", + " 99 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.43e+05 - 1.34e-01 2.65e-04h 10\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 100 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.44e+05 - 1.00e+00 2.64e-04h 10\n", + " 101 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.44e+05 - 1.20e-01 2.63e-04h 10\n", + " 102 0.0000000e+00 1.64e+06 4.17e+11 -1.0 2.45e+05 - 5.73e-01 1.34e-01w 1\n", + " 103 0.0000000e+00 1.64e+06 7.61e+13 -1.0 7.71e+05 - 6.17e-02 4.35e-04w 1\n", + " 104 0.0000000e+00 1.64e+06 1.17e+17 -1.0 7.81e+05 - 2.38e-01 4.29e-06w 1\n", + " 105 0.0000000e+00 1.21e+05 1.05e+11 -1.0 7.81e+05 - 5.73e-01 2.62e-04h 9\n", + " 106 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.45e+05 - 1.34e-01 2.61e-04h 10\n", + " 107 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.46e+05 - 1.00e+00 2.60e-04h 10\n", + " 108 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.47e+05 - 1.20e-01 2.59e-04h 10\n", + " 109 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.47e+05 - 5.71e-01 2.58e-04h 10\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 110 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.48e+05 - 1.33e-01 2.57e-04h 10\n", + " 111 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.49e+05 - 1.00e+00 2.56e-04h 10\n", + " 112 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.49e+05 - 1.19e-01 2.55e-04h 10\n", + " 113 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.50e+05 - 5.68e-01 2.54e-04h 10\n", + " 114 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.50e+05 - 1.33e-01 2.53e-04h 10\n", + " 115 0.0000000e+00 1.59e+06 7.85e+11 -1.0 2.51e+05 - 1.00e+00 1.29e-01w 1\n", + " 116 0.0000000e+00 1.59e+06 1.28e+14 -1.0 7.75e+05 - 6.01e-02 4.25e-04w 1\n", + " 117 0.0000000e+00 1.59e+06 1.19e+17 -1.0 7.85e+05 - 1.23e-01 4.19e-06w 1\n", + " 118 0.0000000e+00 1.21e+05 1.05e+11 -1.0 7.85e+05 - 1.00e+00 2.52e-04h 9\n", + " 119 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.52e+05 - 1.19e-01 2.51e-04h 10\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 120 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.52e+05 - 5.66e-01 2.50e-04h 10\n", + " 121 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.53e+05 - 1.32e-01 2.49e-04h 10\n", + " 122 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.53e+05 - 1.00e+00 2.47e-04h 10\n", + " 123 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.54e+05 - 1.18e-01 4.93e-04h 9\n", + " 124 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.55e+05 - 5.60e-01 4.89e-04h 9\n", + " 125 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.56e+05 - 1.32e-01 4.85e-04h 9\n", + " 126 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.58e+05 - 1.00e+00 4.81e-04h 9\n", + " 127 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.59e+05 - 1.18e-01 4.77e-04h 9\n", + " 128 0.0000000e+00 1.52e+06 4.74e+11 -1.0 2.60e+05 - 5.55e-01 1.21e-01w 1\n", + " 129 0.0000000e+00 1.52e+06 9.09e+13 -1.0 7.80e+05 - 6.17e-02 4.09e-04w 1\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 130 0.0000000e+00 1.52e+06 1.22e+17 -1.0 7.90e+05 - 2.48e-01 4.04e-06w 1\n", + " 131 0.0000000e+00 1.20e+05 1.05e+11 -1.0 7.90e+05 - 5.55e-01 4.73e-04h 8\n", + " 132 0.0000000e+00 1.20e+05 1.05e+11 -1.0 2.61e+05 - 1.31e-01 4.69e-04h 9\n", + " 133 0.0000000e+00 1.20e+05 1.05e+11 -1.0 2.63e+05 - 1.00e+00 1.86e-03h 7\n", + " 134 0.0000000e+00 1.20e+05 1.05e+11 -1.0 2.67e+05 - 1.16e-01 1.80e-03h 7\n", + " 135 0.0000000e+00 1.20e+05 1.05e+11 -1.0 2.72e+05 - 5.16e-01 1.74e-03h 7\n", + " 136 0.0000000e+00 1.19e+05 1.05e+11 -1.0 2.77e+05 - 1.29e-01 3.38e-03h 6\n", + " 137 0.0000000e+00 1.19e+05 1.05e+11 -1.0 2.87e+05 - 1.00e+00 3.17e-03h 6\n", + " 138 0.0000000e+00 1.19e+05 1.06e+11 -1.0 2.98e+05 - 1.10e-01 2.97e-03h 6\n", + " 139 0.0000000e+00 1.18e+05 1.06e+11 -1.0 3.08e+05 - 4.88e-01 2.79e-03h 6\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 140 0.0000000e+00 1.18e+05 1.07e+11 -1.0 3.18e+05 - 1.22e-01 5.25e-03h 5\n", + " 141 0.0000000e+00 1.01e+06 1.89e+12 -1.0 3.38e+05 - 1.00e+00 7.42e-02w 1\n", + " 142 0.0000000e+00 1.01e+06 3.99e+14 -1.0 8.16e+05 - 5.93e-02 3.07e-04w 1\n", + " 143 0.0000000e+00 1.01e+06 1.56e+17 -1.0 8.24e+05 - 1.44e-01 3.04e-06w 1\n", + " 144 0.0000000e+00 1.17e+05 1.08e+11 -1.0 8.24e+05 - 1.00e+00 4.64e-03h 4\n", + " 145 0.0000000e+00 1.17e+05 1.08e+11 -1.0 3.59e+05 - 1.01e-01 2.06e-03h 6\n", + " 146 0.0000000e+00 1.17e+05 1.08e+11 -1.0 3.69e+05 - 4.67e-01 1.94e-03h 6\n", + " 147 0.0000000e+00 1.17e+05 1.08e+11 -1.0 3.79e+05 - 1.12e-01 1.83e-03h 6\n", + " 148 0.0000000e+00 1.16e+05 1.09e+11 -1.0 3.89e+05 - 1.00e+00 8.66e-04h 7\n", + " 149 0.0000000e+00 1.16e+05 1.09e+11 -1.0 3.94e+05 - 9.61e-02 8.42e-04h 7\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 150 0.0000000e+00 1.16e+05 1.09e+11 -1.0 3.99e+05 - 4.61e-01 4.10e-04h 8\n", + " 151 0.0000000e+00 1.16e+05 1.09e+11 -1.0 4.01e+05 - 1.09e-01 4.04e-04h 8\n", + " 152 0.0000000e+00 1.16e+05 1.09e+11 -1.0 4.04e+05 - 1.00e+00 3.98e-04h 8\n", + " 153 0.0000000e+00 1.16e+05 1.09e+11 -1.0 4.06e+05 - 9.46e-02 9.83e-05h 10\n", + " 154 0.0000000e+00 6.92e+05 1.57e+12 -1.0 4.07e+05 - 4.54e-01 5.01e-02w 1\n", + " 155 0.0000000e+00 6.92e+05 4.69e+14 -1.0 8.35e+05 - 6.23e-02 2.42e-04w 1\n", + " 156 0.0000000e+00 6.92e+05 1.93e+17 -1.0 8.42e+05 - 2.97e-01 2.40e-06w 1\n", + " 157 0.0000000e+00 1.16e+05 1.09e+11 -1.0 8.42e+05 - 4.54e-01 9.79e-05h 9\n", + " 158 0.0000000e+00 1.16e+05 1.09e+11 -1.0 4.08e+05 - 1.09e-01 9.76e-05h 10\n", + " 159 0.0000000e+00 1.16e+05 1.09e+11 -1.0 4.08e+05 - 1.00e+00 9.73e-05h 10\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 160 0.0000000e+00 1.16e+05 1.09e+11 -1.0 4.09e+05 - 9.43e-02 9.70e-05h 10\n", + " 161 0.0000000e+00 1.16e+05 1.09e+11 -1.0 4.09e+05 - 4.54e-01 4.83e-05h 11\n", + " 162 0.0000000e+00 1.16e+05 1.09e+11 -1.0 4.10e+05 - 1.08e-01 4.82e-05h 11\n", + " 163 0.0000000e+00 1.16e+05 1.09e+11 -1.0 4.10e+05 - 1.00e+00 1.20e-05h 13\n", + " 164 0.0000000e+00 1.16e+05 1.09e+11 -1.0 4.10e+05 - 9.42e-02 1.20e-05h 13\n", + " 165 0.0000000e+00 1.16e+05 1.09e+11 -1.0 4.10e+05 - 4.55e-01 9.62e-05h 10\n", + " 166 0.0000000e+00 1.16e+05 1.09e+11 -1.0 4.11e+05 - 1.08e-01 9.59e-05h 10\n", + " 167 0.0000000e+00 6.75e+05 3.70e+12 -1.0 4.11e+05 - 1.00e+00 4.89e-02w 1\n", + " 168 0.0000000e+00 6.74e+05 9.73e+14 -1.0 8.36e+05 - 5.90e-02 2.39e-04w 1\n", + " 169 0.0000000e+00 6.74e+05 1.96e+17 -1.0 8.43e+05 - 1.50e-01 2.37e-06w 1\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 170 0.0000000e+00 1.16e+05 1.09e+11 -1.0 8.43e+05 - 1.00e+00 9.56e-05h 9\n", + " 171 0.0000000e+00 1.16e+05 1.09e+11 -1.0 4.12e+05 - 9.39e-02 2.38e-05h 12\n", + " 172 0.0000000e+00 1.16e+05 1.09e+11 -1.0 4.12e+05 - 4.52e-01 1.19e-05h 13\n", + " 173r 0.0000000e+00 1.16e+05 1.00e+03 0.6 0.00e+00 - 0.00e+00 3.72e-07R 18\n", + " 174r 0.0000000e+00 1.25e+05 1.78e+03 0.6 3.70e+04 - 1.19e-02 1.34e-03f 1\n", + " 175r 0.0000000e+00 1.10e+05 1.54e+03 0.6 7.30e+03 - 3.61e-03 8.33e-03f 1\n", + " 176r 0.0000000e+00 1.02e+05 4.12e+03 0.6 5.25e+03 - 6.33e-02 8.82e-03f 1\n", + " 177r 0.0000000e+00 9.63e+04 4.43e+03 0.6 4.50e+03 - 3.61e-02 2.36e-02f 1\n", + " 178r 0.0000000e+00 9.20e+04 1.63e+04 0.6 3.97e+03 - 2.78e-01 6.99e-02f 1\n", + " 179r 0.0000000e+00 8.79e+04 3.34e+04 0.6 4.66e+02 - 8.68e-01 3.38e-01f 1\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 180r 0.0000000e+00 1.06e+05 3.33e+02 0.6 1.05e+02 - 1.00e+00 1.00e+00f 1\n", + " 181r 0.0000000e+00 1.05e+05 2.26e+02 0.6 2.72e+01 - 1.00e+00 1.00e+00f 1\n", + " 182r 0.0000000e+00 9.53e+04 2.03e+03 -0.1 2.12e+02 - 1.00e+00 7.99e-01f 1\n", + " 183r 0.0000000e+00 8.64e+04 8.95e+01 -0.1 8.37e+01 - 1.00e+00 1.00e+00f 1\n", + " 184r 0.0000000e+00 8.52e+04 9.30e-01 -0.1 4.13e+01 - 1.00e+00 1.00e+00h 1\n", + " 185r 0.0000000e+00 7.70e+04 7.43e+02 -2.2 2.70e+02 - 8.39e-01 7.19e-01f 1\n", + " 186r 0.0000000e+00 7.27e+04 1.42e+03 -2.2 1.95e+03 - 9.32e-01 6.47e-01f 1\n", + " 187r 0.0000000e+00 7.36e+04 5.69e+02 -2.2 4.90e+02 - 9.15e-01 6.73e-01f 1\n", + " 188r 0.0000000e+00 7.91e+04 2.05e+02 -2.2 2.75e+02 - 1.00e+00 8.69e-01f 1\n", + " 189r 0.0000000e+00 7.92e+04 1.27e+03 -2.2 1.37e+02 - 8.68e-01 1.06e-01f 1\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 190r 0.0000000e+00 7.99e+04 8.09e+01 -2.2 1.45e+02 - 1.00e+00 1.00e+00f 1\n", + " 191r 0.0000000e+00 8.00e+04 1.81e+01 -2.2 2.82e+00 - 1.00e+00 1.00e+00f 1\n", + " 192r 0.0000000e+00 8.00e+04 6.93e-02 -2.2 6.43e-02 - 1.00e+00 1.00e+00h 1\n", + " 193r 0.0000000e+00 8.03e+04 1.79e+02 -3.3 1.75e+01 - 9.50e-01 8.57e-01f 1\n", + " 194r 0.0000000e+00 4.29e+04 3.40e+02 -3.3 8.69e+02 - 1.00e+00 6.02e-01f 1\n", + " 195r 0.0000000e+00 1.36e+04 1.76e+01 -3.3 3.71e+02 - 1.00e+00 1.00e+00f 1\n", + " 196r 0.0000000e+00 1.99e+04 5.38e-01 -3.3 7.89e+01 - 1.00e+00 1.00e+00h 1\n", + " 197r 0.0000000e+00 1.73e+04 9.03e-02 -3.3 3.20e+01 - 1.00e+00 1.00e+00h 1\n", + " 198r 0.0000000e+00 1.64e+04 1.32e-02 -3.3 1.07e+01 - 1.00e+00 1.00e+00h 1\n", + " 199r 0.0000000e+00 1.48e+04 9.83e+01 -4.9 2.40e+01 - 8.62e-01 7.80e-01f 1\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 200r 0.0000000e+00 1.44e+04 1.92e+03 -4.9 1.71e+03 - 6.01e-01 1.17e-02f 1\n", + "\n", + "Number of Iterations....: 200\n", + "\n", + " (scaled) (unscaled)\n", + "Objective...............: 0.0000000000000000e+00 0.0000000000000000e+00\n", + "Dual infeasibility......: 8.8349814638582666e+02 8.8349814638582666e+02\n", + "Constraint violation....: 3.3086142754792485e-04 1.4369018418593043e+04\n", + "Complementarity.........: 6.5630241357471754e-04 6.5630241357471754e-04\n", + "Overall NLP error.......: 3.3086142754792485e-04 1.4369018418593043e+04\n", + "\n", + "\n", + "Number of objective function evaluations = 1605\n", + "Number of objective gradient evaluations = 175\n", + "Number of equality constraint evaluations = 1605\n", + "Number of inequality constraint evaluations = 0\n", + "Number of equality constraint Jacobian evaluations = 202\n", + "Number of inequality constraint Jacobian evaluations = 0\n", + "Number of Lagrangian Hessian evaluations = 200\n", + "Total CPU secs in IPOPT (w/o function evaluations) = 0.331\n", + "Total CPU secs in NLP function evaluations = 0.025\n", + "\n", + "EXIT: Maximum Number of Iterations Exceeded.\n", + "WARNING: Loading a SolverResults object with a warning status into\n", + "model.name=\"unknown\";\n", + " - termination condition: maxIterations\n", + " - message from solver: Ipopt 3.13.2\\x3a Maximum Number of Iterations\n", + " Exceeded.\n" + ] + } + ], + "execution_count": 52 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "Now that the flowsheet is initialized, we can unfix the guesses for the `Heater` and reactive the tear stream to complete the final solve." + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:31.967629Z", + "start_time": "2025-06-11T22:13:31.943574Z" + } + }, + "cell_type": "code", + "source": [ + "for k, v in tear_guesses.items():\n", + " for k1, v1 in v.items():\n", + " getattr(m.fs.H101.inlet, k)[k1].unfix()\n", + "\n", + "m.fs.s03_expanded.activate()\n", + "print(f\"The DOF is {degrees_of_freedom(m)} after unfixing the values and reactivating the tear stream\")" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The DOF is 0 after unfixing the values and reactivating the tear stream\n" + ] + } + ], + "execution_count": 53 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "## 6 Solving the Model" + }, + { + "cell_type": "markdown", + "metadata": { + "tags": [] + }, + "source": "We have now initialized the flowsheet. Lets set up some solving options before simulating the flowsheet. We want to specify the scaling method, number of iterations, and tolerance. More specific or advanced options can be found at the documentation for IPOPT https://coin-or.github.io/Ipopt/OPTIONS.html" + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:31.982093Z", + "start_time": "2025-06-11T22:13:31.978929Z" + } + }, + "cell_type": "code", + "source": [ + "optarg = {\n", + " 'nlp_scaling_method': 'user-scaling',\n", + " 'OF_ma57_automatic_scaling': 'yes',\n", + " 'max_iter': 500,\n", + " 'tol': 1e-8,\n", + "}" + ], + "outputs": [], + "execution_count": 54 + }, + { + "cell_type": "code", + "metadata": { + "tags": [ + "solution" + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:32.320588Z", + "start_time": "2025-06-11T22:13:32.041379Z" + } + }, + "source": [ + "# Create the solver object\n", + "solver = get_solver(solver_options=optarg)\n", + "\n", + "# Solve the model\n", + "results = solver.solve(m, tee=True)\n" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "WARNING: model contains export suffix 'scaling_factor' that contains 30\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "Ipopt 3.13.2: nlp_scaling_method=user-scaling\n", + "tol=1e-08\n", + "max_iter=500\n", + "option_file_name=C:\\Users\\Tanner\\AppData\\Local\\Temp\\tmp1n1inahr_ipopt.opt\n", + "\n", + "Using option file \"C:\\Users\\Tanner\\AppData\\Local\\Temp\\tmp1n1inahr_ipopt.opt\".\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...: 1163\n", + "Number of nonzeros in inequality constraint Jacobian.: 0\n", + "Number of nonzeros in Lagrangian Hessian.............: 1062\n", + "\n", + "Total number of variables............................: 390\n", + " variables with only lower bounds: 0\n", + " variables with lower and upper bounds: 196\n", + " variables with only upper bounds: 0\n", + "Total number of equality constraints.................: 390\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 7.98e+04 0.00e+00 -1.0 0.00e+00 - 0.00e+00 0.00e+00 0\n", + " 1 0.0000000e+00 7.94e+04 1.91e+01 -1.0 1.01e+05 - 4.59e-03 3.67e-03h 3\n", + " 2 0.0000000e+00 7.91e+04 2.90e+01 -1.0 9.74e+04 - 1.04e-02 2.90e-03h 3\n", + " 3 0.0000000e+00 8.53e+04 3.58e+01 -1.0 1.09e+05 - 3.03e-02 2.27e-03h 3\n", + " 4 0.0000000e+00 9.38e+04 9.70e+01 -1.0 1.22e+05 - 2.53e-02 1.76e-03h 3\n", + " 5 0.0000000e+00 9.86e+04 7.07e+02 -1.0 1.32e+05 - 5.90e-02 1.36e-03h 3\n", + " 6 0.0000000e+00 1.01e+05 5.02e+03 -1.0 1.41e+05 - 2.55e-02 1.04e-03h 3\n", + " 7 0.0000000e+00 1.03e+05 1.80e+05 -1.0 1.47e+05 - 1.27e-01 7.95e-04h 3\n", + " 8 0.0000000e+00 1.04e+05 2.00e+06 -1.0 1.52e+05 - 2.40e-02 6.05e-04h 3\n", + " 9 0.0000000e+00 1.04e+05 1.82e+08 -1.0 1.56e+05 - 1.71e-01 4.59e-04h 3\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 10 0.0000000e+00 1.04e+05 3.34e+09 -1.0 1.59e+05 - 2.46e-02 3.47e-04h 3\n", + " 11 0.0000000e+00 3.24e+05 3.06e+09 -1.0 2.71e-02 10.0 5.85e-01 7.83e-01h 1\n", + " 12 0.0000000e+00 1.46e+06 3.17e+11 -1.0 3.46e+04 - 7.72e-02 1.50e-01f 2\n", + " 13 0.0000000e+00 1.46e+06 2.27e+11 -1.0 1.90e+04 - 3.66e-01 2.41e-03h 3\n", + " 14 0.0000000e+00 1.46e+06 2.32e+12 -1.0 1.90e+04 - 4.42e-01 1.83e-03h 3\n", + " 15 0.0000000e+00 1.45e+06 8.61e+13 -1.0 1.89e+04 - 4.59e-01 1.39e-03h 3\n", + " 16 0.0000000e+00 1.45e+06 5.60e+14 -1.0 1.89e+04 - 3.52e-01 2.10e-03h 2\n", + " 17 0.0000000e+00 1.45e+06 5.54e+14 -1.0 1.88e+04 - 5.07e-01 1.07e-03h 2\n", + " 18 0.0000000e+00 1.45e+06 9.90e+14 -1.0 1.88e+04 - 3.35e-01 1.09e-03h 1\n", + " 19 0.0000000e+00 1.45e+06 9.94e+16 -1.0 1.87e+04 - 9.49e-01 1.09e-05h 1\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 20 0.0000000e+00 1.44e+06 9.35e+18 -1.0 4.45e+03 - 2.73e-01 2.89e-03h 1\n", + " 21r 0.0000000e+00 1.44e+06 1.00e+03 -0.6 0.00e+00 - 0.00e+00 4.43e-07R 17\n", + " 22r 0.0000000e+00 1.89e+06 2.04e+03 -0.6 6.64e+02 - 7.96e-03 2.69e-03f 1\n", + " 23r 0.0000000e+00 1.93e+06 4.07e+03 -0.6 8.47e+02 - 1.23e-02 4.48e-03f 1\n", + " 24r 0.0000000e+00 2.28e+06 1.29e+04 -0.6 9.49e+02 - 4.42e-02 1.55e-02f 1\n", + " 25r 0.0000000e+00 4.06e+06 1.16e+04 -0.6 1.05e+03 - 5.19e-02 5.05e-02f 1\n", + " 26r 0.0000000e+00 4.03e+06 6.56e+03 -0.6 8.19e+02 - 2.50e-03 1.07e-02f 1\n", + " 27r 0.0000000e+00 4.15e+06 4.04e+04 -0.6 6.67e+02 - 9.08e-02 3.63e-02f 1\n", + " 28r 0.0000000e+00 4.11e+06 4.55e+04 -0.6 4.90e+02 - 4.20e-02 3.20e-02f 1\n", + " 29r 0.0000000e+00 4.02e+06 1.82e+05 -0.6 4.75e+02 - 2.06e-01 1.68e-02f 1\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 30r 0.0000000e+00 3.02e+06 8.10e+04 -0.6 4.67e+02 - 6.59e-02 2.37e-01f 1\n", + " 31r 0.0000000e+00 2.70e+06 1.53e+05 -0.6 3.56e+02 - 4.76e-01 4.13e-01f 1\n", + " 32r 0.0000000e+00 2.13e+06 9.21e+04 -0.6 2.09e+02 - 4.07e-01 5.13e-01f 1\n", + " 33r 0.0000000e+00 8.87e+05 5.96e+04 -0.6 1.02e+02 - 5.00e-01 5.95e-01f 1\n", + " 34r 0.0000000e+00 1.36e+05 9.02e+04 -0.6 4.12e+01 - 4.56e-01 1.00e+00f 1\n", + " 35r 0.0000000e+00 4.01e+04 7.97e+04 -0.6 4.66e+00 - 5.29e-01 7.78e-01f 1\n", + " 36r 0.0000000e+00 2.04e+04 5.00e+04 -0.6 4.15e-01 0.0 3.36e-01 7.33e-01h 1\n", + " 37r 0.0000000e+00 1.98e+04 1.42e+04 -0.6 8.68e-02 1.3 1.00e+00 1.72e-01f 1\n", + " 38r 0.0000000e+00 1.85e+04 2.28e+04 -0.6 1.70e+01 - 8.27e-01 5.31e-01f 1\n", + " 39r 0.0000000e+00 2.67e+04 6.30e+03 -0.6 1.08e+01 - 1.00e+00 1.00e+00f 1\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 40r 0.0000000e+00 1.76e+04 3.54e+02 -0.6 3.83e+00 - 1.00e+00 1.00e+00f 1\n", + " 41r 0.0000000e+00 1.66e+04 6.18e+02 -0.6 2.10e+00 - 1.00e+00 1.00e+00f 1\n", + " 42r 0.0000000e+00 1.63e+04 1.63e+00 -0.6 7.37e-02 - 1.00e+00 1.00e+00h 1\n", + " 43r 0.0000000e+00 1.82e+04 9.49e+02 -2.0 8.67e+00 - 8.62e-01 7.60e-01f 1\n", + " 44r 0.0000000e+00 2.02e+04 6.57e+02 -2.0 2.22e+03 - 1.00e+00 7.24e-01f 1\n", + " 45r 0.0000000e+00 1.41e+04 2.64e+01 -2.0 1.93e-02 0.9 1.00e+00 1.00e+00f 1\n", + " 46r 0.0000000e+00 1.41e+04 3.99e+00 -2.0 1.76e-02 0.4 1.00e+00 1.00e+00h 1\n", + " 47r 0.0000000e+00 1.41e+04 5.62e-02 -2.0 3.33e-02 -0.1 1.00e+00 1.00e+00h 1\n", + " 48r 0.0000000e+00 1.42e+04 3.60e+02 -4.4 9.99e-02 -0.6 9.21e-01 7.93e-01f 1\n", + " 49r 0.0000000e+00 4.27e+04 2.99e+03 -4.4 1.29e+00 -1.1 8.82e-01 6.65e-01f 1\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 50r 0.0000000e+00 6.63e+04 1.41e+02 -4.4 3.88e+00 -1.5 9.39e-01 9.16e-01f 1\n", + " 51r 0.0000000e+00 5.62e+04 5.64e+02 -4.4 1.16e+01 -2.0 1.00e+00 1.56e-01f 1\n", + " 52r 0.0000000e+00 5.59e+04 9.07e+02 -4.4 4.50e+04 - 7.98e-02 5.51e-03f 1\n", + " 53r 0.0000000e+00 5.59e+04 1.82e+03 -4.4 2.65e+03 - 1.77e-01 2.25e-05f 1\n", + " 54r 0.0000000e+00 4.87e+04 1.59e+03 -4.4 2.65e+03 - 1.12e-01 1.38e-01f 1\n", + " 55r 0.0000000e+00 4.71e+04 2.05e+03 -4.4 2.28e+03 - 5.83e-01 3.36e-02f 1\n", + " 56r 0.0000000e+00 4.59e+04 2.00e+03 -4.4 2.21e+03 - 2.61e-02 2.52e-02f 1\n", + " 57r 0.0000000e+00 4.21e+04 1.20e+03 -4.4 2.15e+03 - 3.82e-01 9.47e-02f 1\n", + " 58r 0.0000000e+00 3.52e+04 1.00e+03 -4.4 1.95e+03 - 1.89e-01 5.87e-01f 1\n", + " 59r 0.0000000e+00 1.42e+04 3.94e+00 -4.4 8.05e+02 - 1.00e+00 1.00e+00f 1\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 60r 0.0000000e+00 1.42e+04 4.68e-01 -4.4 5.35e-01 - 1.00e+00 1.00e+00h 1\n", + " 61r 0.0000000e+00 1.42e+04 1.29e-02 -4.4 3.24e-03 - 1.00e+00 1.00e+00h 1\n", + " 62r 0.0000000e+00 1.42e+04 5.72e-06 -4.4 5.73e-05 - 1.00e+00 1.00e+00h 1\n", + " 63r 0.0000000e+00 1.42e+04 1.24e+02 -6.6 2.71e-01 - 9.90e-01 8.10e-01f 1\n", + " 64r 0.0000000e+00 8.72e+05 1.88e+02 -6.6 3.29e+04 - 5.03e-01 2.23e-01f 1\n", + " 65r 0.0000000e+00 1.05e+06 2.88e+02 -6.6 2.53e+04 - 6.78e-01 1.82e-01f 1\n", + " 66r 0.0000000e+00 1.04e+06 4.92e+02 -6.6 8.71e+03 - 8.61e-01 1.13e-02f 1\n", + " 67r 0.0000000e+00 6.27e+05 7.17e+01 -6.6 8.61e+03 - 1.00e+00 8.73e-01f 1\n", + " 68r 0.0000000e+00 2.18e+04 1.48e+00 -6.6 1.09e+03 - 1.00e+00 1.00e+00h 1\n", + " 69r 0.0000000e+00 4.71e+02 5.81e-05 -6.6 7.59e-02 - 1.00e+00 1.00e+00h 1\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 70r 0.0000000e+00 4.71e+02 1.03e-08 -6.6 1.22e-05 - 1.00e+00 1.00e+00h 1\n", + " 71r 0.0000000e+00 4.71e+02 8.08e-01 -9.0 4.39e-02 - 1.00e+00 9.74e-01f 1\n", + " 72r 0.0000000e+00 2.45e+03 1.39e+02 -9.0 2.20e+05 - 3.58e-01 2.17e-03f 1\n", + " 73r 0.0000000e+00 2.39e+03 5.81e+02 -9.0 1.06e+04 - 6.57e-01 2.41e-02f 1\n", + " 74r 0.0000000e+00 2.15e+04 7.82e+02 -9.0 1.04e+04 - 1.00e+00 1.42e-01f 1\n", + " 75r 0.0000000e+00 1.15e+04 4.72e+02 -9.0 4.34e+01 - 1.00e+00 4.66e-01h 2\n", + " 76r 0.0000000e+00 6.38e+03 2.52e+02 -9.0 4.28e+01 - 1.00e+00 4.46e-01h 2\n", + " 77r 0.0000000e+00 3.26e+03 1.28e+02 -9.0 2.42e+01 - 1.00e+00 4.89e-01h 2\n", + " 78r 0.0000000e+00 2.99e+03 1.10e+02 -9.0 1.24e+01 - 1.00e+00 1.00e+00h 1\n", + " 79r 0.0000000e+00 9.57e+01 3.79e+00 -9.0 5.11e-03 - 1.00e+00 1.00e+00h 1\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 80r 0.0000000e+00 3.70e-02 1.01e-04 -9.0 3.14e-05 - 1.00e+00 1.00e+00h 1\n", + " 81r 0.0000000e+00 4.15e-06 2.24e-09 -9.0 8.12e-09 - 1.00e+00 1.00e+00h 1\n", + "\n", + "Number of Iterations....: 81\n", + "\n", + " (scaled) (unscaled)\n", + "Objective...............: 0.0000000000000000e+00 0.0000000000000000e+00\n", + "Dual infeasibility......: 0.0000000000000000e+00 0.0000000000000000e+00\n", + "Constraint violation....: 2.0579515874459978e-11 4.1499733924865723e-06\n", + "Complementarity.........: 0.0000000000000000e+00 0.0000000000000000e+00\n", + "Overall NLP error.......: 2.0579515874459978e-11 4.1499733924865723e-06\n", + "\n", + "\n", + "Number of objective function evaluations = 162\n", + "Number of objective gradient evaluations = 23\n", + "Number of equality constraint evaluations = 162\n", + "Number of inequality constraint evaluations = 0\n", + "Number of equality constraint Jacobian evaluations = 83\n", + "Number of inequality constraint Jacobian evaluations = 0\n", + "Number of Lagrangian Hessian evaluations = 81\n", + "Total CPU secs in IPOPT (w/o function evaluations) = 0.119\n", + "Total CPU secs in NLP function evaluations = 0.006\n", + "\n", + "EXIT: Optimal Solution Found.\n" + ] + } + ], + "execution_count": 56 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 7 Analyze the results\n", + "\n", + "\n" + ] + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "If the IDAES UI package was installed with the `idaes-pse` installation or installed separately, you can run the flowsheet visualizer to see a full diagram of the full process that is generated and displayed on a browser window.\n" + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "Otherwise, we can run the `m.fs.report()` method to see a full summary of the solved flowsheet. It is recomended to adjust the width of the output as much as possible for the cleanest display." + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:33.985892Z", + "start_time": "2025-06-11T22:13:33.889113Z" + } + }, + "cell_type": "code", + "source": "m.fs.report()", + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "====================================================================================\n", + "Flowsheet : fs Time: 0.0\n", + "------------------------------------------------------------------------------------\n", + " Stream Table\n", + " Units s01 s02 s03 s04 s05 s06 s07 s08 s09 s10 s11 s12 \n", + " flow_mol_phase_comp ('Liq', 'benzene') mole / second 1.0000e-05 1.0000e-05 2.0008e-05 1.2993e-07 1.2993e-07 1.0000e-08 0.20460 8.0000e-09 8.0000e-09 1.0000e-08 0.062620 2.0000e-09\n", + " flow_mol_phase_comp ('Liq', 'toluene') mole / second 0.30000 1.0000e-05 0.30001 8.4149e-07 8.4149e-07 1.0000e-08 0.062520 8.0000e-09 8.0000e-09 1.0000e-08 0.032257 2.0000e-09\n", + " flow_mol_phase_comp ('Liq', 'methane') mole / second 1.0000e-05 1.0000e-05 2.0008e-05 1.0000e-12 1.0000e-12 1.0000e-08 2.6712e-07 8.0000e-09 8.0000e-09 1.0000e-08 9.4877e-08 2.0000e-09\n", + " flow_mol_phase_comp ('Liq', 'hydrogen') mole / second 1.0000e-05 1.0000e-05 2.0008e-05 1.0000e-12 1.0000e-12 1.0000e-08 2.6712e-07 8.0000e-09 8.0000e-09 1.0000e-08 9.4877e-08 2.0000e-09\n", + " flow_mol_phase_comp ('Vap', 'benzene') mole / second 1.0000e-05 1.0000e-05 0.11934 0.11936 0.35374 0.14915 1.0000e-08 0.11932 0.11932 0.14198 1.0000e-08 0.029829\n", + " flow_mol_phase_comp ('Vap', 'toluene') mole / second 1.0000e-05 1.0000e-05 0.012508 0.31252 0.078129 0.015610 1.0000e-08 0.012488 0.012488 0.030264 1.0000e-08 0.0031219\n", + " flow_mol_phase_comp ('Vap', 'methane') mole / second 1.0000e-05 0.020000 1.0377 1.0377 1.2721 1.2721 1.0000e-08 1.0177 1.0177 1.8224e-07 1.0000e-08 0.25442\n", + " flow_mol_phase_comp ('Vap', 'hydrogen') mole / second 1.0000e-05 0.30000 0.56258 0.56260 0.32821 0.32821 1.0000e-08 0.26257 0.26257 1.8224e-07 1.0000e-08 0.065642\n", + " temperature kelvin 303.20 303.20 314.09 600.00 771.85 325.00 325.00 325.00 325.00 375.00 375.00 325.00\n", + " pressure pascal 3.5000e+05 3.5000e+05 3.5000e+05 3.5000e+05 3.5000e+05 3.5000e+05 3.5000e+05 3.5000e+05 3.5000e+05 1.5000e+05 1.5000e+05 3.5000e+05\n", + "====================================================================================\n" + ] + } + ], + "execution_count": 59 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "What is the total operating cost?" + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:34.015352Z", + "start_time": "2025-06-11T22:13:34.012518Z" + } + }, + "source": [ + "print(\"operating cost = $\", value(m.fs.operating_cost))" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "operating cost = $ 419122.33874473866\n" + ] + } + ], + "execution_count": 60 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": "For this operating cost, what is the amount of benzene we are able to produce and what purity we are able to achieve? We can look at a specific unit models stream table with the same `report()` method." + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:34.197557Z", + "start_time": "2025-06-11T22:13:34.174732Z" + } + }, + "source": [ + "m.fs.F102.report()\n", + "\n", + "print()\n", + "print(\"benzene purity = \", value(m.fs.purity))" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "====================================================================================\n", + "Unit : fs.F102 Time: 0.0\n", + "------------------------------------------------------------------------------------\n", + " Unit Performance\n", + "\n", + " Variables: \n", + "\n", + " Key : Value : Units : Fixed : Bounds\n", + " Heat Duty : 7352.5 : watt : False : (None, None)\n", + " Pressure Change : -2.0000e+05 : pascal : True : (None, None)\n", + "\n", + "------------------------------------------------------------------------------------\n", + " Stream Table\n", + " Units Inlet Vapor Outlet Liquid Outlet\n", + " flow_mol_phase_comp ('Liq', 'benzene') mole / second 0.20460 1.0000e-08 0.062620 \n", + " flow_mol_phase_comp ('Liq', 'toluene') mole / second 0.062520 1.0000e-08 0.032257 \n", + " flow_mol_phase_comp ('Liq', 'methane') mole / second 2.6712e-07 1.0000e-08 9.4877e-08 \n", + " flow_mol_phase_comp ('Liq', 'hydrogen') mole / second 2.6712e-07 1.0000e-08 9.4877e-08 \n", + " flow_mol_phase_comp ('Vap', 'benzene') mole / second 1.0000e-08 0.14198 1.0000e-08 \n", + " flow_mol_phase_comp ('Vap', 'toluene') mole / second 1.0000e-08 0.030264 1.0000e-08 \n", + " flow_mol_phase_comp ('Vap', 'methane') mole / second 1.0000e-08 1.8224e-07 1.0000e-08 \n", + " flow_mol_phase_comp ('Vap', 'hydrogen') mole / second 1.0000e-08 1.8224e-07 1.0000e-08 \n", + " temperature kelvin 325.00 375.00 375.00 \n", + " pressure pascal 3.5000e+05 1.5000e+05 1.5000e+05 \n", + "====================================================================================\n", + "\n", + "benzene purity = 0.8242962943922332\n" + ] + } + ], + "execution_count": 62 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": "Next, let's look at how much benzene we are losing with the light gases out of F101. IDAES has tools for creating stream tables based on the `Arcs` and/or `Ports` in a flowsheet. Let us create and print a simple stream table showing the stream leaving the reactor and the vapor stream from F101." + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:34.253529Z", + "start_time": "2025-06-11T22:13:34.238100Z" + } + }, + "source": [ + "from idaes.core.util.tables import (\n", + " create_stream_table_dataframe,\n", + " stream_table_dataframe_to_string,\n", + ")\n", + "\n", + "st = create_stream_table_dataframe({\"Reactor\": m.fs.s05, \"Light Gases\": m.fs.s06})\n", + "print(stream_table_dataframe_to_string(st))" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " Units Reactor Light Gases\n", + "flow_mol_phase_comp ('Liq', 'benzene') mole / second 1.2993e-07 1.0000e-08 \n", + "flow_mol_phase_comp ('Liq', 'toluene') mole / second 8.4149e-07 1.0000e-08 \n", + "flow_mol_phase_comp ('Liq', 'methane') mole / second 1.0000e-12 1.0000e-08 \n", + "flow_mol_phase_comp ('Liq', 'hydrogen') mole / second 1.0000e-12 1.0000e-08 \n", + "flow_mol_phase_comp ('Vap', 'benzene') mole / second 0.35374 0.14915 \n", + "flow_mol_phase_comp ('Vap', 'toluene') mole / second 0.078129 0.015610 \n", + "flow_mol_phase_comp ('Vap', 'methane') mole / second 1.2721 1.2721 \n", + "flow_mol_phase_comp ('Vap', 'hydrogen') mole / second 0.32821 0.32821 \n", + "temperature kelvin 771.85 325.00 \n", + "pressure pascal 3.5000e+05 3.5000e+05 \n" + ] + } + ], + "execution_count": 64 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 8 Optimization\n", + "\n", + "\n", + "We saw from the results above that the total operating cost for the base case was $419,122 per year. We are producing 0.142 mol/s of benzene at a purity of 82\\%. However, we are losing around 42\\% of benzene in F101 vapor outlet stream. \n", + "\n", + "Let us try to minimize this cost such that:\n", + "- we are producing at least 0.15 mol/s of benzene in F102 vapor outlet i.e. our product stream\n", + "- purity of benzene i.e. the mole fraction of benzene in F102 vapor outlet is at least 80%\n", + "- restricting the benzene loss in F101 vapor outlet to less than 20%\n", + "\n", + "For this problem, our decision variables are as follows:\n", + "- H101 outlet temperature\n", + "- R101 cooling duty provided\n", + "- F101 outlet temperature\n", + "- F102 outlet temperature\n", + "- F102 deltaP in the flash tank\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let us declare our objective function for this problem. " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:34.276719Z", + "start_time": "2025-06-11T22:13:34.271416Z" + } + }, + "source": [ + "m.fs.objective = Objective(expr=m.fs.operating_cost)" + ], + "outputs": [], + "execution_count": 65 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, we need to unfix the decision variables as we had solved a square problem (degrees of freedom = 0) until now. " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:34.337438Z", + "start_time": "2025-06-11T22:13:34.332415Z" + } + }, + "source": [ + "m.fs.H101.outlet.temperature.unfix()\n", + "m.fs.R101.heat_duty.unfix()\n", + "m.fs.F101.vap_outlet.temperature.unfix()\n", + "m.fs.F102.vap_outlet.temperature.unfix()" + ], + "outputs": [], + "execution_count": 66 + }, + { + "cell_type": "code", + "metadata": { + "tags": [ + "solution" + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:34.463653Z", + "start_time": "2025-06-11T22:13:34.459960Z" + } + }, + "source": [ + "# Todo: Unfix deltaP for F102\n", + "m.fs.F102.deltaP.unfix()" + ], + "outputs": [], + "execution_count": 68 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next, we need to set bounds on these decision variables to values shown below:\n", + "\n", + " - H101 outlet temperature [500, 600] K\n", + " - R101 outlet temperature [600, 800] K\n", + " - F101 outlet temperature [298, 450] K\n", + " - F102 outlet temperature [298, 450] K\n", + " - F102 outlet pressure [105000, 110000] Pa\n", + "\n", + "Let us first set the variable bound for the H101 outlet temperature as shown below:" + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:34.589902Z", + "start_time": "2025-06-11T22:13:34.585528Z" + } + }, + "source": [ + "m.fs.H101.outlet.temperature[0].setlb(500)\n", + "m.fs.H101.outlet.temperature[0].setub(600)" + ], + "outputs": [], + "execution_count": 70 + }, + { + "cell_type": "code", + "metadata": { + "tags": [ + "solution" + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:34.713177Z", + "start_time": "2025-06-11T22:13:34.709843Z" + } + }, + "source": [ + "# Todo: Set the bounds for reactor outlet temperature\n", + "m.fs.R101.outlet.temperature[0].setlb(600)\n", + "m.fs.R101.outlet.temperature[0].setub(800)" + ], + "outputs": [], + "execution_count": 72 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let us fix the bounds for the rest of the decision variables. " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:34.763569Z", + "start_time": "2025-06-11T22:13:34.759916Z" + } + }, + "source": [ + "m.fs.F101.vap_outlet.temperature[0].setlb(298.0)\n", + "m.fs.F101.vap_outlet.temperature[0].setub(450.0)\n", + "m.fs.F102.vap_outlet.temperature[0].setlb(298.0)\n", + "m.fs.F102.vap_outlet.temperature[0].setub(450.0)\n", + "m.fs.F102.vap_outlet.pressure[0].setlb(105000)\n", + "m.fs.F102.vap_outlet.pressure[0].setub(110000)" + ], + "outputs": [], + "execution_count": 73 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, the only things left to define are our constraints on overhead loss in F101, product flow rate and purity in F102. Let us first look at defining a constraint for the overhead loss in F101 where we are restricting the benzene leaving the vapor stream to less than 20 \\% of the benzene available in the reactor outlet. " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:34.806470Z", + "start_time": "2025-06-11T22:13:34.801455Z" + } + }, + "source": [ + "m.fs.overhead_loss = Constraint(\n", + " expr=m.fs.F101.vap_outlet.flow_mol_phase_comp[0, \"Vap\", \"benzene\"]\n", + " <= 0.20 * m.fs.R101.outlet.flow_mol_phase_comp[0, \"Vap\", \"benzene\"]\n", + ")" + ], + "outputs": [], + "execution_count": 74 + }, + { + "cell_type": "code", + "metadata": { + "tags": [ + "solution" + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:34.887385Z", + "start_time": "2025-06-11T22:13:34.882862Z" + } + }, + "source": [ + "# Todo: Add minimum product flow constraint\n", + "m.fs.product_flow = Constraint(\n", + " expr=m.fs.F102.vap_outlet.flow_mol_phase_comp[0, \"Vap\", \"benzene\"] >= 0.15\n", + ")" + ], + "outputs": [], + "execution_count": 76 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let us add the final constraint on product purity or the mole fraction of benzene in the product stream such that it is at least greater than 80%. " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:34.917217Z", + "start_time": "2025-06-11T22:13:34.912683Z" + } + }, + "source": [ + "m.fs.product_purity = Constraint(expr=m.fs.purity >= 0.80)" + ], + "outputs": [], + "execution_count": 77 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "We have now defined the optimization problem and we are now ready to solve this problem. \n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:35.209235Z", + "start_time": "2025-06-11T22:13:34.962108Z" + } + }, + "source": [ + "results = solver.solve(m, tee=True)" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "WARNING: model contains export suffix 'scaling_factor' that contains 25\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "Ipopt 3.13.2: nlp_scaling_method=user-scaling\n", + "tol=1e-08\n", + "max_iter=500\n", + "option_file_name=C:\\Users\\Tanner\\AppData\\Local\\Temp\\tmpd6aww4bg_ipopt.opt\n", + "\n", + "Using option file \"C:\\Users\\Tanner\\AppData\\Local\\Temp\\tmpd6aww4bg_ipopt.opt\".\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...: 1191\n", + "Number of nonzeros in inequality constraint Jacobian.: 5\n", + "Number of nonzeros in Lagrangian Hessian.............: 1065\n", + "\n", + "Total number of variables............................: 395\n", + " variables with only lower bounds: 0\n", + " variables with lower and upper bounds: 199\n", + " variables with only upper bounds: 0\n", + "Total number of equality constraints.................: 390\n", + "Total number of inequality constraints...............: 3\n", + " inequality constraints with only lower bounds: 2\n", + " inequality constraints with lower and upper bounds: 0\n", + " inequality constraints with only upper bounds: 1\n", + "\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 0 4.1912234e+05 2.99e+05 6.94e+00 -1.0 0.00e+00 - 0.00e+00 0.00e+00 0\n", + " 1 4.1133558e+05 2.94e+05 6.94e+00 -1.0 6.60e+07 - 2.90e-06 1.05e-05f 1\n", + " 2 3.2484037e+05 1.80e+06 6.94e+00 -1.0 2.33e+09 - 2.95e-07 4.87e-06f 1\n", + " 3 3.0318192e+05 1.92e+06 6.94e+00 -1.0 6.90e+08 - 1.70e-05 4.13e-06f 1\n", + " 4 3.0263181e+05 1.92e+06 2.20e+01 -1.0 1.43e+07 - 8.92e-05 1.73e-05f 1\n", + " 5 3.0137102e+05 1.92e+06 4.38e+01 -1.0 8.74e+06 - 6.63e-05 1.11e-04f 1\n", + " 6 3.0058759e+05 1.92e+06 1.37e+03 -1.0 2.21e+07 - 8.59e-06 2.17e-04f 1\n", + " 7 3.0058113e+05 1.92e+06 7.51e+04 -1.0 3.24e+05 - 2.49e-01 1.77e-04f 1\n", + " 8 3.0317331e+05 1.38e+06 2.08e+05 -1.0 4.00e+04 - 9.62e-01 2.82e-01h 1\n", + " 9 3.0372038e+05 1.26e+06 1.91e+05 -1.0 2.87e+04 - 1.50e-01 8.31e-02h 1\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 10 3.0372988e+05 1.26e+06 9.32e+05 -1.0 2.63e+04 - 1.00e+00 1.51e-03h 1\n", + " 11 3.0386427e+05 1.24e+06 1.90e+07 -1.0 2.63e+04 - 1.00e+00 2.03e-02h 2\n", + " 12 3.0393928e+05 1.22e+06 7.46e+08 -1.0 2.58e+04 - 1.00e+00 1.15e-02h 2\n", + " 13 3.0397909e+05 1.21e+06 4.87e+10 -1.0 2.55e+04 - 1.00e+00 6.13e-03h 2\n", + " 14 3.0399961e+05 1.21e+06 5.05e+12 -1.0 2.53e+04 - 1.00e+00 3.17e-03h 2\n", + " 15r 3.0399961e+05 1.21e+06 1.00e+03 -0.5 0.00e+00 - 0.00e+00 3.97e-07R 14\n", + " 16r 3.0399912e+05 1.20e+06 2.84e+04 -0.5 8.47e+03 - 4.43e-03 2.34e-03f 1\n", + " 17 3.0399884e+05 1.20e+06 5.51e+05 -1.0 2.92e+04 - 2.81e-01 2.34e-06f 2\n", + " 18 3.0402874e+05 1.19e+06 4.77e+07 -1.0 2.52e+04 - 9.90e-01 4.64e-03h 1\n", + " 19 3.0402891e+05 1.19e+06 3.49e+10 -1.0 2.51e+04 - 1.00e+00 2.64e-05h 2\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 20r 3.0402891e+05 1.19e+06 1.00e+03 -0.8 0.00e+00 - 0.00e+00 3.08e-07R 8\n", + " 21r 3.0403760e+05 1.15e+06 2.58e+04 -0.8 3.66e+03 - 5.75e-03 3.60e-03f 1\n", + " 22r 3.0404498e+05 1.08e+06 3.87e+04 -0.8 3.65e+03 - 7.70e-03 6.44e-03f 1\n", + " 23r 3.0405749e+05 9.30e+05 4.54e+04 -0.8 3.63e+03 - 1.80e-02 1.60e-02f 1\n", + " 24r 3.0406463e+05 7.06e+05 4.01e+04 -0.8 3.57e+03 - 5.64e-02 3.23e-02f 1\n", + " 25r 3.0405635e+05 4.00e+05 4.80e+04 -0.8 3.45e+03 - 1.05e-01 8.20e-02f 1\n", + " 26r 3.0403684e+05 2.04e+05 2.80e+04 -0.8 3.17e+03 - 3.66e-01 1.49e-01f 1\n", + " 27r 3.0401302e+05 2.95e+04 2.21e+04 -0.8 2.70e+03 - 1.94e-01 6.28e-01f 1\n", + " 28 3.0386768e+05 2.95e+04 1.26e+02 -1.0 3.39e+06 - 4.53e-04 3.54e-06f 1\n", + " 29 3.0384876e+05 2.95e+04 8.45e+03 -1.0 1.90e+05 - 3.01e-02 3.65e-04f 1\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 30 3.0412267e+05 2.86e+04 2.69e+05 -1.0 2.35e+04 - 9.90e-01 3.11e-02h 1\n", + " 31 3.0843430e+05 5.11e+05 1.73e+05 -1.0 2.16e+04 - 1.00e+00 4.99e-01h 1\n", + " 32 3.1273887e+05 6.10e+05 5.58e+08 -1.0 1.09e+04 - 1.00e+00 9.90e-01h 1\n", + " 33 3.1276263e+05 3.11e+05 1.78e+08 -1.0 1.01e+03 - 1.00e+00 4.97e-01h 2\n", + " 34 3.1278673e+05 7.74e+02 1.15e+08 -1.0 6.27e+02 - 1.00e+00 9.97e-01H 1\n", + " 35 3.1278674e+05 6.28e+02 5.45e+04 -1.0 1.81e+02 - 1.00e+00 1.00e+00h 1\n", + " 36 3.1278674e+05 8.80e-03 2.62e+00 -1.0 7.71e-01 - 1.00e+00 1.00e+00h 1\n", + " 37 3.1278634e+05 3.48e-05 1.58e+05 -5.7 1.36e+00 - 1.00e+00 1.00e+00f 1\n", + " 38 3.1278634e+05 3.73e-08 6.73e-02 -5.7 2.20e-04 - 1.00e+00 1.00e+00f 1\n", + " 39 3.1278634e+05 2.24e-08 4.40e-05 -5.7 8.48e-04 - 1.00e+00 1.00e+00h 1\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 40 3.1278634e+05 6.71e-08 6.16e-05 -8.6 1.75e-03 - 1.00e+00 1.00e+00h 1\n", + " 41 3.1278634e+05 3.73e-08 4.40e-05 -9.0 8.38e-04 - 1.00e+00 1.00e+00h 1\n", + " 42 3.1278634e+05 5.96e-08 4.40e-05 -9.0 1.47e-03 - 1.00e+00 1.00e+00h 1\n", + " 43 3.1278634e+05 1.49e-08 4.40e-05 -9.0 5.14e-08 - 1.00e+00 1.00e+00h 1\n", + " 44 3.1278634e+05 1.49e-08 4.40e-05 -9.0 7.39e-11 - 1.00e+00 2.44e-04h 13\n", + " 45 3.1278634e+05 1.49e-08 4.40e-05 -9.0 6.89e-11 - 1.00e+00 5.00e-01h 2\n", + " 46 3.1278634e+05 1.49e-08 4.40e-05 -9.0 7.02e-11 - 1.00e+00 1.00e+00h 1\n", + " 47 3.1278634e+05 1.49e-08 4.40e-05 -9.0 6.71e-11 - 1.00e+00 5.00e-01h 2\n", + " 48 3.1278634e+05 2.98e-08 4.40e-05 -9.0 5.67e-11 - 1.00e+00 1.00e+00h 1\n", + " 49 3.1278634e+05 2.98e-08 4.40e-05 -9.0 5.52e-11 - 1.00e+00 5.00e-01h 2\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 50 3.1278634e+05 2.98e-08 4.40e-05 -9.0 4.16e-11 - 1.00e+00 5.00e-01h 2\n", + " 51 3.1278634e+05 2.98e-08 4.40e-05 -9.0 3.65e-11 - 1.00e+00 1.25e-01h 4\n", + "\n", + "Number of Iterations....: 51\n", + "\n", + " (scaled) (unscaled)\n", + "Objective...............: 3.1278633834066673e+05 3.1278633834066673e+05\n", + "Dual infeasibility......: 4.3988857412862944e-05 6.3035118071624454e-05\n", + "Constraint violation....: 2.0579515874459978e-11 2.9802322387695312e-08\n", + "Complementarity.........: 9.2191617461622074e-10 9.2191617461622074e-10\n", + "Overall NLP error.......: 1.6340396115140405e-08 6.3035118071624454e-05\n", + "\n", + "\n", + "Number of objective function evaluations = 141\n", + "Number of objective gradient evaluations = 47\n", + "Number of equality constraint evaluations = 141\n", + "Number of inequality constraint evaluations = 141\n", + "Number of equality constraint Jacobian evaluations = 55\n", + "Number of inequality constraint Jacobian evaluations = 55\n", + "Number of Lagrangian Hessian evaluations = 52\n", + "Total CPU secs in IPOPT (w/o function evaluations) = 0.077\n", + "Total CPU secs in NLP function evaluations = 0.010\n", + "\n", + "EXIT: Solved To Acceptable Level.\n" + ] + } + ], + "execution_count": 78 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 8.1 Optimization Results\n", + "\n", + "Display the results and product specifications" + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:35.321235Z", + "start_time": "2025-06-11T22:13:35.245545Z" + } + }, + "source": [ + "print(\"operating cost = $\", value(m.fs.operating_cost))\n", + "\n", + "print()\n", + "print(\"Product flow rate and purity in F102\")\n", + "\n", + "m.fs.F102.report()\n", + "\n", + "print()\n", + "print(\"benzene purity = \", value(m.fs.purity))\n", + "\n", + "print()\n", + "print(\"Overhead loss in F101\")\n", + "m.fs.F101.report()" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "operating cost = $ 312786.33834066667\n", + "\n", + "Product flow rate and purity in F102\n", + "\n", + "====================================================================================\n", + "Unit : fs.F102 Time: 0.0\n", + "------------------------------------------------------------------------------------\n", + " Unit Performance\n", + "\n", + " Variables: \n", + "\n", + " Key : Value : Units : Fixed : Bounds\n", + " Heat Duty : 8377.0 : watt : False : (None, None)\n", + " Pressure Change : -2.4500e+05 : pascal : False : (None, None)\n", + "\n", + "------------------------------------------------------------------------------------\n", + " Stream Table\n", + " Units Inlet Vapor Outlet Liquid Outlet\n", + " flow_mol_phase_comp ('Liq', 'benzene') mole / second 0.21743 1.0000e-08 0.067425 \n", + " flow_mol_phase_comp ('Liq', 'toluene') mole / second 0.070695 1.0000e-08 0.037507 \n", + " flow_mol_phase_comp ('Liq', 'methane') mole / second 2.8812e-07 1.0000e-08 1.0493e-07 \n", + " flow_mol_phase_comp ('Liq', 'hydrogen') mole / second 2.8812e-07 1.0000e-08 1.0493e-07 \n", + " flow_mol_phase_comp ('Vap', 'benzene') mole / second 1.0000e-08 0.15000 1.0000e-08 \n", + " flow_mol_phase_comp ('Vap', 'toluene') mole / second 1.0000e-08 0.033189 1.0000e-08 \n", + " flow_mol_phase_comp ('Vap', 'methane') mole / second 1.0000e-08 1.9319e-07 1.0000e-08 \n", + " flow_mol_phase_comp ('Vap', 'hydrogen') mole / second 1.0000e-08 1.9319e-07 1.0000e-08 \n", + " temperature kelvin 301.88 362.93 362.93 \n", + " pressure pascal 3.5000e+05 1.0500e+05 1.0500e+05 \n", + "====================================================================================\n", + "\n", + "benzene purity = 0.8188276578115882\n", + "\n", + "Overhead loss in F101\n", + "\n", + "====================================================================================\n", + "Unit : fs.F101 Time: 0.0\n", + "------------------------------------------------------------------------------------\n", + " Unit Performance\n", + "\n", + " Variables: \n", + "\n", + " Key : Value : Units : Fixed : Bounds\n", + " Heat Duty : -56353. : watt : False : (None, None)\n", + " Pressure Change : 0.0000 : pascal : True : (None, None)\n", + "\n", + "------------------------------------------------------------------------------------\n", + " Stream Table\n", + " Units Inlet Vapor Outlet Liquid Outlet\n", + " flow_mol_phase_comp ('Liq', 'benzene') mole / second 4.3534e-08 1.0000e-08 0.21743 \n", + " flow_mol_phase_comp ('Liq', 'toluene') mole / second 7.5866e-07 1.0000e-08 0.070695 \n", + " flow_mol_phase_comp ('Liq', 'methane') mole / second 1.0000e-12 1.0000e-08 2.8812e-07 \n", + " flow_mol_phase_comp ('Liq', 'hydrogen') mole / second 1.0000e-12 1.0000e-08 2.8812e-07 \n", + " flow_mol_phase_comp ('Vap', 'benzene') mole / second 0.27178 0.054356 1.0000e-08 \n", + " flow_mol_phase_comp ('Vap', 'toluene') mole / second 0.076085 0.0053908 1.0000e-08 \n", + " flow_mol_phase_comp ('Vap', 'methane') mole / second 1.2414 1.2414 1.0000e-08 \n", + " flow_mol_phase_comp ('Vap', 'hydrogen') mole / second 0.35887 0.35887 1.0000e-08 \n", + " temperature kelvin 696.11 301.88 301.88 \n", + " pressure pascal 3.5000e+05 3.5000e+05 3.5000e+05 \n", + "====================================================================================\n" + ] + } + ], + "execution_count": 80 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Display optimal values for the decision variables" + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:35.405538Z", + "start_time": "2025-06-11T22:13:35.398221Z" + } + }, + "source": [ + "print(f'''Optimal Values:\n", + "\n", + "H101 outlet temperature = {value(m.fs.H101.outlet.temperature[0]):.3f} K\n", + "\n", + "R101 outlet temperature = {value(m.fs.R101.outlet.temperature[0]):.3f} K\n", + "\n", + "F101 outlet temperature = {value(m.fs.F101.vap_outlet.temperature[0]):.3f} K\n", + "\n", + "F102 outlet temperature = {value(m.fs.F102.vap_outlet.temperature[0]):.3f} K\n", + "F102 outlet pressure = {value(m.fs.F102.vap_outlet.pressure[0]):.3f} Pa\n", + "''')" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Optimal Values:\n", + "\n", + "H101 outlet temperature = 500.000 K\n", + "\n", + "R101 outlet temperature = 696.112 K\n", + "\n", + "F101 outlet temperature = 301.878 K\n", + "\n", + "F102 outlet temperature = 362.935 K\n", + "F102 outlet pressure = 105000.000 Pa\n", + "\n" + ] + } + ], + "execution_count": 82 + } + ], + "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.8.12" + } + }, + "nbformat": 4, + "nbformat_minor": 3 } \ No newline at end of file diff --git a/idaes_examples/notebooks/docs/tut/core/hda_flowsheet_exercise.ipynb b/idaes_examples/notebooks/docs/tut/core/hda_flowsheet_exercise.ipynb index bcdc90bb..91c14462 100644 --- a/idaes_examples/notebooks/docs/tut/core/hda_flowsheet_exercise.ipynb +++ b/idaes_examples/notebooks/docs/tut/core/hda_flowsheet_exercise.ipynb @@ -1,1344 +1,2698 @@ { - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "header", - "hide-cell" - ] - }, - "outputs": [], - "source": [ - "###############################################################################\n", - "# The Institute for the Design of Advanced Energy Systems Integrated Platform\n", - "# Framework (IDAES IP) was produced under the DOE Institute for the\n", - "# Design of Advanced Energy Systems (IDAES).\n", - "#\n", - "# Copyright (c) 2018-2023 by the software owners: The Regents of the\n", - "# University of California, through Lawrence Berkeley National Laboratory,\n", - "# National Technology & Engineering Solutions of Sandia, LLC, Carnegie Mellon\n", - "# University, West Virginia University Research Corporation, et al.\n", - "# All rights reserved. Please see the files COPYRIGHT.md and LICENSE.md\n", - "# for full copyright and license information.\n", - "###############################################################################" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "# HDA Flowsheet Simulation and Optimization\n", - "\n", - "Author: Jaffer Ghouse \n", - "Maintainer: Brandon Paul \n", - "Updated: 2023-06-01 \n", - "\n", - "## Learning outcomes\n", - "\n", - "\n", - "- Construct a steady-state flowsheet using the IDAES unit model library\n", - "- Connecting unit models in a flowsheet using Arcs\n", - "- Using the SequentialDecomposition tool to initialize a flowsheet with recycle\n", - "- Fomulate and solve an optimization problem\n", - " - Defining an objective function\n", - " - Setting variable bounds\n", - " - Adding additional constraints \n", - "\n", - "\n", - "## Problem Statement\n", - "\n", - "Hydrodealkylation is a chemical reaction that often involves reacting\n", - "an aromatic hydrocarbon in the presence of hydrogen gas to form a\n", - "simpler aromatic hydrocarbon devoid of functional groups. In this\n", - "example, toluene will be reacted with hydrogen gas at high temperatures\n", - " to form benzene via the following reaction:\n", - "\n", - "**C6H5CH3 + H2 → C6H6 + CH4**\n", - "\n", - "\n", - "This reaction is often accompanied by an equilibrium side reaction\n", - "which forms diphenyl, which we will neglect for this example.\n", - "\n", - "This example is based on the 1967 AIChE Student Contest problem as\n", - "present by Douglas, J.M., Chemical Design of Chemical Processes, 1988,\n", - "McGraw-Hill.\n", - "\n", - "The flowsheet that we will be using for this module is shown below with the stream conditions. We will be processing toluene and hydrogen to produce at least 370 TPY of benzene. As shown in the flowsheet, there are two flash tanks, F101 to separate out the non-condensibles and F102 to further separate the benzene-toluene mixture to improve the benzene purity. Note that typically a distillation column is required to obtain high purity benzene but that is beyond the scope of this workshop. The non-condensibles separated out in F101 will be partially recycled back to M101 and the rest will be either purged or combusted for power generation.We will assume ideal gas for this flowsheet. The properties required for this module are available in the same directory:\n", - "\n", - "- hda_ideal_VLE.py\n", - "- hda_reaction.py\n", - "\n", - "The state variables chosen for the property package are **flows of component by phase, temperature and pressure**. The components considered are: **toluene, hydrogen, benzene and methane**. Therefore, every stream has 8 flow variables, 1 temperature and 1 pressure variable. \n", - "\n", - "![](HDA_flowsheet.png)\n", - "\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Importing required pyomo and idaes components\n", - "\n", - "\n", - "To construct a flowsheet, we will need several components from the pyomo and idaes package. Let us first import the following components from Pyomo:\n", - "- Constraint (to write constraints)\n", - "- Var (to declare variables)\n", - "- ConcreteModel (to create the concrete model object)\n", - "- Expression (to evaluate values as a function of variables defined in the model)\n", - "- Objective (to define an objective function for optimization)\n", - "- SolverFactory (to solve the problem)\n", - "- TransformationFactory (to apply certain transformations)\n", - "- Arc (to connect two unit models)\n", - "- SequentialDecomposition (to initialize the flowsheet in a sequential mode)\n", - "\n", - "For further details on these components, please refer to the pyomo documentation: https://pyomo.readthedocs.io/en/stable/\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from pyomo.environ import (\n", - " Constraint,\n", - " Var,\n", - " ConcreteModel,\n", - " Expression,\n", - " Objective,\n", - " SolverFactory,\n", - " TransformationFactory,\n", - " value,\n", - ")\n", - "from pyomo.network import Arc, SequentialDecomposition" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "From idaes, we will be needing the FlowsheetBlock and the following unit models:\n", - "- Mixer\n", - "- Heater\n", - "- StoichiometricReactor\n", - "- **Flash**\n", - "- Separator (splitter) \n", - "- PressureChanger" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from idaes.core import FlowsheetBlock" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from idaes.models.unit_models import (\n", - " PressureChanger,\n", - " Mixer,\n", - " Separator as Splitter,\n", - " Heater,\n", - " StoichiometricReactor,\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "
    \n", - "Inline Exercise:\n", - "Now, import the remaining unit models highlighted in blue above and run the cell using `Shift+Enter` after typing in the code. \n", - "
    \n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "exercise" - ] - }, - "outputs": [], - "source": [ - "# Todo: import flash model from idaes.models.unit_models" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We will also be needing some utility tools to put together the flowsheet and calculate the degrees of freedom. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from idaes.models.unit_models.pressure_changer import ThermodynamicAssumption\n", - "from idaes.core.util.model_statistics import degrees_of_freedom\n", - "\n", - "# Import idaes logger to set output levels\n", - "import idaes.logger as idaeslog\n", - "from idaes.core.solvers import get_solver\n", - "from idaes.core.util.exceptions import InitializationError" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Importing required thermo and reaction package\n", - "\n", - "The final set of imports are to import the thermo and reaction package for the HDA process. We have created a custom thermo package that assumes Ideal Gas with support for VLE. \n", - "\n", - "The reaction package here is very simple as we will be using only a StochiometricReactor and the reaction package consists of the stochiometric coefficients for the reaction and the parameter for the heat of reaction. \n", - "\n", - "Let us import the following modules and they are in the same directory as this jupyter notebook:\n", - "
      \n", - "
    • hda_ideal_VLE as thermo_props
    • \n", - "
    • hda_reaction as reaction_props
    • \n", - "
    \n", - "
    " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from idaes_examples.mod.hda import hda_ideal_VLE as thermo_props\n", - "from idaes_examples.mod.hda import hda_reaction as reaction_props" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Constructing the Flowsheet\n", - "\n", - "We have now imported all the components, unit models, and property modules we need to construct a flowsheet. Let us create a ConcreteModel and add the flowsheet block as we did in module 1. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m = ConcreteModel()\n", - "m.fs = FlowsheetBlock(dynamic=False)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We now need to add the property packages to the flowsheet. Unlike Module 1, where we only had a thermo property package, for this flowsheet we will also need to add a reaction property package. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.thermo_params = thermo_props.HDAParameterBlock()\n", - "m.fs.reaction_params = reaction_props.HDAReactionParameterBlock(\n", - " property_package=m.fs.thermo_params\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Adding Unit Models\n", - "\n", - "Let us start adding the unit models we have imported to the flowsheet. Here, we are adding the Mixer (assigned a name M101) and a Heater (assigned a name H101). Note that, all unit models need to be given a property package argument. In addition to that, there are several arguments depending on the unit model, please refer to the documentation for more details (https://idaes-pse.readthedocs.io/en/stable/reference_guides/model_libraries/generic/unit_models/index.html). For example, the Mixer unit model here is given a `list` consisting of names to the three inlets. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.M101 = Mixer(\n", - " property_package=m.fs.thermo_params,\n", - " inlet_list=[\"toluene_feed\", \"hydrogen_feed\", \"vapor_recycle\"],\n", - ")\n", - "\n", - "m.fs.H101 = Heater(\n", - " property_package=m.fs.thermo_params,\n", - " has_pressure_change=False,\n", - " has_phase_equilibrium=True,\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "
    \n", - "Inline Exercise:\n", - "Let us now add the StoichiometricReactor(assign the name R101) and pass the following arguments:\n", - "
      \n", - "
    • \"property_package\": m.fs.thermo_params
    • \n", - "
    • \"reaction_package\": m.fs.reaction_params
    • \n", - "
    • \"has_heat_of_reaction\": True
    • \n", - "
    • \"has_heat_transfer\": True
    • \n", - "
    • \"has_pressure_change\": False
    • \n", - "
    \n", - "
    " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "exercise" - ] - }, - "outputs": [], - "source": [ - "# Todo: Add reactor with the specifications above" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let us now add the Flash(assign the name F101) and pass the following arguments:\n", - "
      \n", - "
    • \"property_package\": m.fs.thermo_params
    • \n", - "
    • \"has_heat_transfer\": True
    • \n", - "
    • \"has_pressure_change\": False
    • \n", - "
    " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.F101 = Flash(\n", - " property_package=m.fs.thermo_params,\n", - " has_heat_transfer=True,\n", - " has_pressure_change=True,\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let us now add the Splitter(S101), PressureChanger(C101) and the second Flash(F102). " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.S101 = Splitter(\n", - " property_package=m.fs.thermo_params,\n", - " ideal_separation=False,\n", - " outlet_list=[\"purge\", \"recycle\"],\n", - ")\n", - "\n", - "\n", - "m.fs.C101 = PressureChanger(\n", - " property_package=m.fs.thermo_params,\n", - " compressor=True,\n", - " thermodynamic_assumption=ThermodynamicAssumption.isothermal,\n", - ")\n", - "\n", - "m.fs.F102 = Flash(\n", - " property_package=m.fs.thermo_params,\n", - " has_heat_transfer=True,\n", - " has_pressure_change=True,\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Connecting Unit Models using Arcs\n", - "\n", - "We have now added all the unit models we need to the flowsheet. However, we have not yet specified how the units are to be connected. To do this, we will be using the `Arc` which is a pyomo component that takes in two arguments: `source` and `destination`. Let us connect the outlet of the mixer(M101) to the inlet of the heater(H101). " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.s03 = Arc(source=m.fs.M101.outlet, destination=m.fs.H101.inlet)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "![](HDA_flowsheet.png) \n", - "\n", - "
    \n", - "Inline Exercise:\n", - "Now, connect the H101 outlet to the R101 inlet using the cell above as a guide. \n", - "
    \n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "exercise" - ] - }, - "outputs": [], - "source": [ - "# Todo: Connect the H101 outlet to R101 inlet" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We will now be connecting the rest of the flowsheet as shown below. Notice how the outlet names are different for the flash tanks F101 and F102 as they have a vapor and a liquid outlet. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.s05 = Arc(source=m.fs.R101.outlet, destination=m.fs.F101.inlet)\n", - "m.fs.s06 = Arc(source=m.fs.F101.vap_outlet, destination=m.fs.S101.inlet)\n", - "m.fs.s08 = Arc(source=m.fs.S101.recycle, destination=m.fs.C101.inlet)\n", - "m.fs.s09 = Arc(source=m.fs.C101.outlet, destination=m.fs.M101.vapor_recycle)\n", - "m.fs.s10 = Arc(source=m.fs.F101.liq_outlet, destination=m.fs.F102.inlet)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We have now connected the unit model block using the arcs. However, each of these arcs link to ports on the two unit models that are connected. In this case, the ports consist of the state variables that need to be linked between the unit models. Pyomo provides a convenient method to write these equality constraints for us between two ports and this is done as follows:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "TransformationFactory(\"network.expand_arcs\").apply_to(m)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Adding expressions to compute purity and operating costs\n", - "\n", - "In this section, we will add a few Expressions that allows us to evaluate the performance. Expressions provide a convenient way of calculating certain values that are a function of the variables defined in the model. For more details on Expressions, please refer to: https://pyomo.readthedocs.io/en/stable/pyomo_modeling_components/Expressions.html\n", - "\n", - "For this flowsheet, we are interested in computing the purity of the product Benzene stream (i.e. the mole fraction) and the operating cost which is a sum of the cooling and heating cost. " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let us first add an Expression to compute the mole fraction of benzene in the `vap_outlet` of F102 which is our product stream. Please note that the var flow_mol_phase_comp has the index - [time, phase, component]. As this is a steady-state flowsheet, the time index by default is 0. The valid phases are [\"Liq\", \"Vap\"]. Similarly the valid component list is [\"benzene\", \"toluene\", \"hydrogen\", \"methane\"]." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.purity = Expression(\n", - " expr=m.fs.F102.vap_outlet.flow_mol_phase_comp[0, \"Vap\", \"benzene\"]\n", - " / (\n", - " m.fs.F102.vap_outlet.flow_mol_phase_comp[0, \"Vap\", \"benzene\"]\n", - " + m.fs.F102.vap_outlet.flow_mol_phase_comp[0, \"Vap\", \"toluene\"]\n", - " )\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now, let us add an expression to compute the cooling cost assuming a cost of 0.212E-4 $/kW. Note that cooling utility is required for the reactor (R101) and the first flash (F101). " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.cooling_cost = Expression(\n", - " expr=0.212e-7 * (-m.fs.F101.heat_duty[0]) + 0.212e-7 * (-m.fs.R101.heat_duty[0])\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "Now, let us add an expression to compute the heating cost assuming the utility cost as follows:\n", - "
      \n", - "
    • 2.2E-4 dollars/kW for H101
    • \n", - "
    • 1.9E-4 dollars/kW for F102
    • \n", - "
    \n", - "Note that the heat duty is in units of watt (J/s). " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.heating_cost = Expression(\n", - " expr=2.2e-7 * m.fs.H101.heat_duty[0] + 1.9e-7 * m.fs.F102.heat_duty[0]\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let us now add an expression to compute the total operating cost per year which is basically the sum of the cooling and heating cost we defined above. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.operating_cost = Expression(\n", - " expr=(3600 * 24 * 365 * (m.fs.heating_cost + m.fs.cooling_cost))\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Fixing feed conditions\n", - "\n", - "Let us first check how many degrees of freedom exist for this flowsheet using the `degrees_of_freedom` tool we imported earlier. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "print(degrees_of_freedom(m))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We will now be fixing the toluene feed stream to the conditions shown in the flowsheet above. Please note that though this is a pure toluene feed, the remaining components are still assigned a very small non-zero value to help with convergence and initializing. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.M101.toluene_feed.flow_mol_phase_comp[0, \"Vap\", \"benzene\"].fix(1e-5)\n", - "m.fs.M101.toluene_feed.flow_mol_phase_comp[0, \"Vap\", \"toluene\"].fix(1e-5)\n", - "m.fs.M101.toluene_feed.flow_mol_phase_comp[0, \"Vap\", \"hydrogen\"].fix(1e-5)\n", - "m.fs.M101.toluene_feed.flow_mol_phase_comp[0, \"Vap\", \"methane\"].fix(1e-5)\n", - "m.fs.M101.toluene_feed.flow_mol_phase_comp[0, \"Liq\", \"benzene\"].fix(1e-5)\n", - "m.fs.M101.toluene_feed.flow_mol_phase_comp[0, \"Liq\", \"toluene\"].fix(0.30)\n", - "m.fs.M101.toluene_feed.flow_mol_phase_comp[0, \"Liq\", \"hydrogen\"].fix(1e-5)\n", - "m.fs.M101.toluene_feed.flow_mol_phase_comp[0, \"Liq\", \"methane\"].fix(1e-5)\n", - "m.fs.M101.toluene_feed.temperature.fix(303.2)\n", - "m.fs.M101.toluene_feed.pressure.fix(350000)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "Similarly, let us fix the hydrogen feed to the following conditions in the next cell:\n", - "
      \n", - "
    • FH2 = 0.30 mol/s
    • \n", - "
    • FCH4 = 0.02 mol/s
    • \n", - "
    • Remaining components = 1e-5 mol/s
    • \n", - "
    • T = 303.2 K
    • \n", - "
    • P = 350000 Pa
    • \n", - "
    \n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.M101.hydrogen_feed.flow_mol_phase_comp[0, \"Vap\", \"benzene\"].fix(1e-5)\n", - "m.fs.M101.hydrogen_feed.flow_mol_phase_comp[0, \"Vap\", \"toluene\"].fix(1e-5)\n", - "m.fs.M101.hydrogen_feed.flow_mol_phase_comp[0, \"Vap\", \"hydrogen\"].fix(0.30)\n", - "m.fs.M101.hydrogen_feed.flow_mol_phase_comp[0, \"Vap\", \"methane\"].fix(0.02)\n", - "m.fs.M101.hydrogen_feed.flow_mol_phase_comp[0, \"Liq\", \"benzene\"].fix(1e-5)\n", - "m.fs.M101.hydrogen_feed.flow_mol_phase_comp[0, \"Liq\", \"toluene\"].fix(1e-5)\n", - "m.fs.M101.hydrogen_feed.flow_mol_phase_comp[0, \"Liq\", \"hydrogen\"].fix(1e-5)\n", - "m.fs.M101.hydrogen_feed.flow_mol_phase_comp[0, \"Liq\", \"methane\"].fix(1e-5)\n", - "m.fs.M101.hydrogen_feed.temperature.fix(303.2)\n", - "m.fs.M101.hydrogen_feed.pressure.fix(350000)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Fixing unit model specifications\n", - "\n", - "Now that we have fixed our inlet feed conditions, we will now be fixing the operating conditions for the unit models in the flowsheet. Let us set set the H101 outlet temperature to 600 K. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.H101.outlet.temperature.fix(600)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "For the StoichiometricReactor, we have to define the conversion in terms of toluene. This requires us to create a new variable for specifying the conversion and adding a Constraint that defines the conversion with respect to toluene. The second degree of freedom for the reactor is to define the heat duty. In this case, let us assume the reactor to be adiabatic i.e. Q = 0. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.R101.conversion = Var(initialize=0.75, bounds=(0, 1))\n", - "\n", - "m.fs.R101.conv_constraint = Constraint(\n", - " expr=m.fs.R101.conversion * m.fs.R101.inlet.flow_mol_phase_comp[0, \"Vap\", \"toluene\"]\n", - " == (\n", - " m.fs.R101.inlet.flow_mol_phase_comp[0, \"Vap\", \"toluene\"]\n", - " - m.fs.R101.outlet.flow_mol_phase_comp[0, \"Vap\", \"toluene\"]\n", - " )\n", - ")\n", - "\n", - "m.fs.R101.conversion.fix(0.75)\n", - "m.fs.R101.heat_duty.fix(0)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The Flash conditions for F101 can be set as follows. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.F101.vap_outlet.temperature.fix(325.0)\n", - "m.fs.F101.deltaP.fix(0)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "
    \n", - "Inline Exercise:\n", - "Set the conditions for Flash F102 to the following conditions:\n", - "
      \n", - "
    • T = 375 K
    • \n", - "
    • deltaP = -200000
    • \n", - "
    \n", - "\n", - "Use Shift+Enter to run the cell once you have typed in your code. \n", - "
    " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "exercise" - ] - }, - "outputs": [], - "source": [ - "# Todo: Set conditions for Flash F102" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let us fix the purge split fraction to 20% and the outlet pressure of the compressor is set to 350000 Pa. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.S101.split_fraction[0, \"purge\"].fix(0.2)\n", - "m.fs.C101.outlet.pressure.fix(350000)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "
    \n", - "Inline Exercise:\n", - "We have now defined all the feed conditions and the inputs required for the unit models. The system should now have 0 degrees of freedom i.e. should be a square problem. Please check that the degrees of freedom is 0. \n", - "\n", - "Use Shift+Enter to run the cell once you have typed in your code. \n", - "
    " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "exercise" - ] - }, - "outputs": [], - "source": [ - "# Todo: print the degrees of freedom" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Initialization\n", - "\n", - "\n", - "This section will demonstrate how to use the built-in sequential decomposition tool to initialize our flowsheet.\n", - "\n", - "![](HDA_flowsheet.png) \n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let us first create an object for the SequentialDecomposition and specify our options for this. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "seq = SequentialDecomposition()\n", - "seq.options.select_tear_method = \"heuristic\"\n", - "seq.options.tear_method = \"Wegstein\"\n", - "seq.options.iterLim = 3\n", - "\n", - "# Using the SD tool\n", - "G = seq.create_graph(m)\n", - "heuristic_tear_set = seq.tear_set_arcs(G, method=\"heuristic\")\n", - "order = seq.calculation_order(G)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Which is the tear stream? Display tear set and order" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "for o in heuristic_tear_set:\n", - " print(o.name)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "What sequence did the SD tool determine to solve this flowsheet with the least number of tears? " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "scrolled": true - }, - "outputs": [], - "source": [ - "for o in order:\n", - " print(o[0].name)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - " \n", - "\n", - "![](HDA_tear_stream.png) \n", - "\n", - "\n", - "The SequentialDecomposition tool has determined that the tear stream is the mixer outlet. We will need to provide a reasonable guess for this." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "tear_guesses = {\n", - " \"flow_mol_phase_comp\": {\n", - " (0, \"Vap\", \"benzene\"): 1e-5,\n", - " (0, \"Vap\", \"toluene\"): 1e-5,\n", - " (0, \"Vap\", \"hydrogen\"): 0.30,\n", - " (0, \"Vap\", \"methane\"): 0.02,\n", - " (0, \"Liq\", \"benzene\"): 1e-5,\n", - " (0, \"Liq\", \"toluene\"): 0.30,\n", - " (0, \"Liq\", \"hydrogen\"): 1e-5,\n", - " (0, \"Liq\", \"methane\"): 1e-5,\n", - " },\n", - " \"temperature\": {0: 303},\n", - " \"pressure\": {0: 350000},\n", - "}\n", - "\n", - "# Pass the tear_guess to the SD tool\n", - "seq.set_guesses_for(m.fs.H101.inlet, tear_guesses)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Next, we need to tell the tool how to initialize a particular unit. We will be writing a python function which takes in a \"unit\" and calls the initialize method on that unit. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "def function(unit):\n", - " try:\n", - " initializer = unit.default_initializer()\n", - " initializer.initialize(unit, output_level=idaeslog.INFO)\n", - " except InitializationError:\n", - " solver = get_solver()\n", - " solver.solve(unit)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We are now ready to initialize our flowsheet in a sequential mode. Note that we specifically set the iteration limit to be 5 as we are trying to use this tool only to get a good set of initial values such that IPOPT can then take over and solve this flowsheet for us. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "seq.run(m, function)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "
    \n", - "Inline Exercise:\n", - "We have now initialized the flowsheet. Let us run the flowsheet in a simulation mode to look at the results. To do this, complete the last line of code where we pass the model to the solver. You will need to type the following:\n", - " \n", - "results = solver.solve(m, tee=True)\n", - "\n", - "Use Shift+Enter to run the cell once you have typed in your code. \n", - "
    \n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "exercise" - ] - }, - "outputs": [], - "source": [ - "# Create the solver object\n", - "\n", - "\n", - "# Solve the model" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Analyze the results of the square problem\n", - "\n", - "\n", - "What is the total operating cost? " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "print(\"operating cost = $\", value(m.fs.operating_cost))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "For this operating cost, what is the amount of benzene we are able to produce and what purity we are able to achieve? " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.F102.report()\n", - "\n", - "print()\n", - "print(\"benzene purity = \", value(m.fs.purity))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Next, let's look at how much benzene we are losing with the light gases out of F101. IDAES has tools for creating stream tables based on the `Arcs` and/or `Ports` in a flowsheet. Let us create and print a simple stream table showing the stream leaving the reactor and the vapor stream from F101.\n", - "\n", - "
    \n", - "Inline Exercise:\n", - "How much benzene are we losing in the F101 vapor outlet stream?\n", - "
    \n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from idaes.core.util.tables import (\n", - " create_stream_table_dataframe,\n", - " stream_table_dataframe_to_string,\n", - ")\n", - "\n", - "st = create_stream_table_dataframe({\"Reactor\": m.fs.s05, \"Light Gases\": m.fs.s06})\n", - "print(stream_table_dataframe_to_string(st))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "
    \n", - "Inline Exercise:\n", - "You can query additional variables here if you like. \n", - "\n", - "Use Shift+Enter to run the cell once you have typed in your code. \n", - "
    \n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Optimization\n", - "\n", - "\n", - "We saw from the results above that the total operating cost for the base case was $419,122 per year. We are producing 0.142 mol/s of benzene at a purity of 82\\%. However, we are losing around 42\\% of benzene in F101 vapor outlet stream. \n", - "\n", - "Let us try to minimize this cost such that:\n", - "- we are producing at least 0.15 mol/s of benzene in F102 vapor outlet i.e. our product stream\n", - "- purity of benzene i.e. the mole fraction of benzene in F102 vapor outlet is at least 80%\n", - "- restricting the benzene loss in F101 vapor outlet to less than 20%\n", - "\n", - "For this problem, our decision variables are as follows:\n", - "- H101 outlet temperature\n", - "- R101 cooling duty provided\n", - "- F101 outlet temperature\n", - "- F102 outlet temperature\n", - "- F102 deltaP in the flash tank\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let us declare our objective function for this problem. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.objective = Objective(expr=m.fs.operating_cost)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now, we need to unfix the decision variables as we had solved a square problem (degrees of freedom = 0) until now. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.H101.outlet.temperature.unfix()\n", - "m.fs.R101.heat_duty.unfix()\n", - "m.fs.F101.vap_outlet.temperature.unfix()\n", - "m.fs.F102.vap_outlet.temperature.unfix()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "
    \n", - "Inline Exercise:\n", - "Let us now unfix the remaining variable which is F102 pressure drop (F102.deltaP) \n", - "\n", - "Use Shift+Enter to run the cell once you have typed in your code. \n", - "
    \n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "exercise" - ] - }, - "outputs": [], - "source": [ - "# Todo: Unfix deltaP for F102" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Next, we need to set bounds on these decision variables to values shown below:\n", - "\n", - " - H101 outlet temperature [500, 600] K\n", - " - R101 outlet temperature [600, 800] K\n", - " - F101 outlet temperature [298, 450] K\n", - " - F102 outlet temperature [298, 450] K\n", - " - F102 outlet pressure [105000, 110000] Pa\n", - "\n", - "Let us first set the variable bound for the H101 outlet temperature as shown below:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.H101.outlet.temperature[0].setlb(500)\n", - "m.fs.H101.outlet.temperature[0].setub(600)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "
    \n", - "Inline Exercise:\n", - "Now, set the variable bound for the R101 outlet temperature.\n", - "\n", - "Use Shift+Enter to run the cell once you have typed in your code. \n", - "
    " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "exercise" - ] - }, - "outputs": [], - "source": [ - "# Todo: Set the bounds for reactor outlet temperature" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let us fix the bounds for the rest of the decision variables. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.F101.vap_outlet.temperature[0].setlb(298.0)\n", - "m.fs.F101.vap_outlet.temperature[0].setub(450.0)\n", - "m.fs.F102.vap_outlet.temperature[0].setlb(298.0)\n", - "m.fs.F102.vap_outlet.temperature[0].setub(450.0)\n", - "m.fs.F102.vap_outlet.pressure[0].setlb(105000)\n", - "m.fs.F102.vap_outlet.pressure[0].setub(110000)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now, the only things left to define are our constraints on overhead loss in F101, product flow rate and purity in F102. Let us first look at defining a constraint for the overhead loss in F101 where we are restricting the benzene leaving the vapor stream to less than 20 \\% of the benzene available in the reactor outlet. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.overhead_loss = Constraint(\n", - " expr=m.fs.F101.vap_outlet.flow_mol_phase_comp[0, \"Vap\", \"benzene\"]\n", - " <= 0.20 * m.fs.R101.outlet.flow_mol_phase_comp[0, \"Vap\", \"benzene\"]\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "
    \n", - "Inline Exercise:\n", - "Now, add the constraint such that we are producing at least 0.15 mol/s of benzene in the product stream which is the vapor outlet of F102. Let us name this constraint as m.fs.product_flow. \n", - "\n", - "Use Shift+Enter to run the cell once you have typed in your code. \n", - "
    " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "exercise" - ] - }, - "outputs": [], - "source": [ - "# Todo: Add minimum product flow constraint" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let us add the final constraint on product purity or the mole fraction of benzene in the product stream such that it is at least greater than 80%. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.product_purity = Constraint(expr=m.fs.purity >= 0.80)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "We have now defined the optimization problem and we are now ready to solve this problem. \n", - "\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "results = solver.solve(m, tee=True)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Optimization Results\n", - "\n", - "Display the results and product specifications" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "print(\"operating cost = $\", value(m.fs.operating_cost))\n", - "\n", - "print()\n", - "print(\"Product flow rate and purity in F102\")\n", - "\n", - "m.fs.F102.report()\n", - "\n", - "print()\n", - "print(\"benzene purity = \", value(m.fs.purity))\n", - "\n", - "print()\n", - "print(\"Overhead loss in F101\")\n", - "m.fs.F101.report()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Display optimal values for the decision variables" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "print(\"Optimal Values\")\n", - "print()\n", - "\n", - "print(\"H101 outlet temperature = \", value(m.fs.H101.outlet.temperature[0]), \"K\")\n", - "\n", - "print()\n", - "print(\"R101 outlet temperature = \", value(m.fs.R101.outlet.temperature[0]), \"K\")\n", - "\n", - "print()\n", - "print(\"F101 outlet temperature = \", value(m.fs.F101.vap_outlet.temperature[0]), \"K\")\n", - "\n", - "print()\n", - "print(\"F102 outlet temperature = \", value(m.fs.F102.vap_outlet.temperature[0]), \"K\")\n", - "print(\"F102 outlet pressure = \", value(m.fs.F102.vap_outlet.pressure[0]), \"Pa\")" - ] - }, - { - "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.8.12" - } - }, - "nbformat": 4, - "nbformat_minor": 3 + "cells": [ + { + "cell_type": "code", + "metadata": { + "tags": [ + "header", + "hide-cell" + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:25.418463Z", + "start_time": "2025-06-11T22:13:25.412645Z" + } + }, + "source": [ + "###############################################################################\n", + "# The Institute for the Design of Advanced Energy Systems Integrated Platform\n", + "# Framework (IDAES IP) was produced under the DOE Institute for the\n", + "# Design of Advanced Energy Systems (IDAES).\n", + "#\n", + "# Copyright (c) 2018-2023 by the software owners: The Regents of the\n", + "# University of California, through Lawrence Berkeley National Laboratory,\n", + "# National Technology & Engineering Solutions of Sandia, LLC, Carnegie Mellon\n", + "# University, West Virginia University Research Corporation, et al.\n", + "# All rights reserved. Please see the files COPYRIGHT.md and LICENSE.md\n", + "# for full copyright and license information.\n", + "###############################################################################" + ], + "outputs": [], + "execution_count": 1 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "# HDA Flowsheet Simulation and Optimization\n", + "\n", + "Author: Jaffer Ghouse
    \n", + "Maintainer: Tanner Polley
    \n", + "Updated: 2025-06-03\n", + "\n", + "## Learning outcomes\n", + "\n", + "\n", + "- Construct a steady-state flowsheet using the IDAES unit model library\n", + "- Connecting unit models in a flowsheet using Arcs\n", + "- Using the SequentialDecomposition tool to initialize a flowsheet with recycle\n", + "- Formulate and solve an optimization problem\n", + " - Defining an objective function\n", + " - Setting variable bounds\n", + " - Adding additional constraints\n", + "\n", + "\n", + "The general workflow of setting up an IDAES flowsheet is the following:\n", + "\n", + "- 1 Importing Modules\n", + "- 2 Building a Model\n", + "- 3 Scaling the Model\n", + "- 4 Specifying the Model\n", + "- 5 Initializing the Model\n", + "- 6 Solving the Model\n", + "- 7 Analyzing and Visualizing the Results\n", + "- 8 Optimizing the Model\n", + "\n", + "We will complete each of these steps as well as demonstrate analyses on this model through some examples and exercises\n", + "\n", + "\n", + "## Problem Statement\n", + "\n", + "Hydrodealkylation is a chemical reaction that often involves reacting\n", + "an aromatic hydrocarbon in the presence of hydrogen gas to form a\n", + "simpler aromatic hydrocarbon devoid of functional groups. In this\n", + "example, toluene will be reacted with hydrogen gas at high temperatures\n", + " to form benzene via the following reaction:\n", + "\n", + "**C6H5CH3 + H2 \u2192 C6H6 + CH4**\n", + "\n", + "\n", + "This reaction is often accompanied by an equilibrium side reaction\n", + "which forms diphenyl, which we will neglect for this example.\n", + "\n", + "This example is based on the 1967 AIChE Student Contest problem as\n", + "present by Douglas, J.M., Chemical Design of Chemical Processes, 1988,\n", + "McGraw-Hill.\n", + "\n", + "The flowsheet that we will be using for this module is shown below with the stream conditions. We will be processing toluene and hydrogen to produce at least 370 TPY of benzene. As shown in the flowsheet, there are two flash tanks, F101 to separate out the non-condensibles and F102 to further separate the benzene-toluene mixture to improve the benzene purity. Note that typically a distillation column is required to obtain high purity benzene but that is beyond the scope of this workshop. The non-condensibles separated out in F101 will be partially recycled back to M101 and the rest will be either purged or combusted for power generation.We will assume ideal gas for this flowsheet. The properties required for this module are available in the same directory:\n", + "\n", + "- hda_ideal_VLE.py\n", + "- hda_reaction.py\n", + "\n", + "The state variables chosen for the property package are **flows of component by phase, temperature and pressure**. The components considered are: **toluene, hydrogen, benzene and methane**. Therefore, every stream has 8 flow variables, 1 temperature and 1 pressure variable. \n", + "\n", + "![](HDA_flowsheet.png)\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1 Importing Modules\n", + "### 1.1 Importing required pyomo and idaes components\n", + "\n", + "\n", + "To construct a flowsheet, we will need several components from the pyomo and idaes package. Let us first import the following components from Pyomo:\n", + "- Constraint (to write constraints)\n", + "- Var (to declare variables)\n", + "- ConcreteModel (to create the concrete model object)\n", + "- Expression (to evaluate values as a function of variables defined in the model)\n", + "- Objective (to define an objective function for optimization)\n", + "- SolverFactory (to solve the problem)\n", + "- TransformationFactory (to apply certain transformations)\n", + "- Arc (to connect two unit models)\n", + "- SequentialDecomposition (to initialize the flowsheet in a sequential mode)\n", + "\n", + "For further details on these components, please refer to the pyomo documentation: https://pyomo.readthedocs.io/en/stable/\n" + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:26.066510Z", + "start_time": "2025-06-11T22:13:25.662098Z" + } + }, + "source": [ + "from pyomo.environ import (\n", + " Constraint,\n", + " Var,\n", + " ConcreteModel,\n", + " Expression,\n", + " Objective,\n", + " SolverFactory,\n", + " TransformationFactory,\n", + " value,\n", + ")\n", + "from pyomo.network import Arc, SequentialDecomposition" + ], + "outputs": [], + "execution_count": 2 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "From idaes, we will be needing the FlowsheetBlock and the following unit models:\n", + "- Feed\n", + "- Mixer\n", + "- Heater\n", + "- StoichiometricReactor\n", + "- **Flash**\n", + "- Separator (splitter) \n", + "- PressureChanger\n", + "- Product" + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:27.637361Z", + "start_time": "2025-06-11T22:13:26.082580Z" + } + }, + "source": [ + "from idaes.core import FlowsheetBlock" + ], + "outputs": [], + "execution_count": 3 + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:27.752223Z", + "start_time": "2025-06-11T22:13:27.645348Z" + } + }, + "source": [ + "from idaes.models.unit_models import (\n", + " PressureChanger, IsentropicPressureChangerInitializer,\n", + " Mixer, MixerInitializer,\n", + " Separator as Splitter, SeparatorInitializer,\n", + " Heater,\n", + " StoichiometricReactor,\n", + " Feed,\n", + " Product,\n", + ")" + ], + "outputs": [], + "execution_count": 4 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "
    \n", + "Inline Exercise:\n", + "Now, import the remaining unit models highlighted in blue above and run the cell using `Shift+Enter` after typing in the code. \n", + "
    \n" + ] + }, + { + "cell_type": "code", + "metadata": { + "tags": [ + "exercise" + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:27.763417Z", + "start_time": "2025-06-11T22:13:27.761004Z" + } + }, + "source": [ + "# Todo: import flash model from idaes.models.unit_models" + ], + "outputs": [], + "execution_count": 5 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We will also be needing some utility tools to put together the flowsheet and calculate the degrees of freedom. " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:27.788004Z", + "start_time": "2025-06-11T22:13:27.784891Z" + } + }, + "source": [ + "from idaes.models.unit_models.pressure_changer import ThermodynamicAssumption\n", + "from idaes.core.util.model_statistics import degrees_of_freedom\n", + "from idaes.core.scaling.util import set_scaling_factor\n", + "from idaes.core.scaling.autoscaling import AutoScaler\n", + "\n", + "# Import idaes logger to set output levels\n", + "import idaes.logger as idaeslog\n", + "from idaes.core.solvers import get_solver\n", + "from idaes.core.util.exceptions import InitializationError" + ], + "outputs": [], + "execution_count": 7 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 1.2 Importing required thermo and reaction package\n", + "\n", + "The final set of imports are to import the thermo and reaction package for the HDA process. We have created a custom thermo package that assumes Ideal Gas with support for VLE. \n", + "\n", + "The reaction package here is very simple as we will be using only a StochiometricReactor and the reaction package consists of the stochiometric coefficients for the reaction and the parameter for the heat of reaction. \n", + "\n", + "Let us import the following modules and they are in the same directory as this jupyter notebook:\n", + "
      \n", + "
    • hda_ideal_VLE as thermo_props
    • \n", + "
    • hda_reaction as reaction_props
    • \n", + "
    \n", + "
    " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:27.806315Z", + "start_time": "2025-06-11T22:13:27.798068Z" + } + }, + "source": [ + "from idaes_examples.mod.hda import hda_ideal_VLE as thermo_props\n", + "from idaes_examples.mod.hda import hda_reaction as reaction_props" + ], + "outputs": [], + "execution_count": 8 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2 Constructing the Flowsheet\n", + "\n", + "We have now imported all the components, unit models, and property modules we need to construct a flowsheet. Let us create a ConcreteModel and add the flowsheet block as we did in module 1. " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:27.819615Z", + "start_time": "2025-06-11T22:13:27.814729Z" + } + }, + "source": [ + "m = ConcreteModel()\n", + "m.fs = FlowsheetBlock(dynamic=False)" + ], + "outputs": [], + "execution_count": 9 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We now need to add the property packages to the flowsheet. Unlike Module 1, where we only had a thermo property package, for this flowsheet we will also need to add a reaction property package. " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:27.841949Z", + "start_time": "2025-06-11T22:13:27.829949Z" + } + }, + "source": [ + "m.fs.thermo_params = thermo_props.HDAParameterBlock()\n", + "m.fs.reaction_params = reaction_props.HDAReactionParameterBlock(\n", + " property_package=m.fs.thermo_params\n", + ")" + ], + "outputs": [], + "execution_count": 10 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 2.1 Adding Unit Models\n", + "\n", + "Let us start adding the unit models we have imported to the flowsheet. Here, we are adding the Feed (assigned a name `I101` for Inlet), `Mixer` (assigned a name `M101`) and a `Heater` (assigned a name `H101`). Note that, all unit models need to be given a property package argument. In addition to that, there are several arguments depending on the unit model, please refer to the documentation for more details (https://idaes-pse.readthedocs.io/en/stable/reference_guides/model_libraries/generic/unit_models/index.html). For example, the `Mixer` unit model here must be specified the number of inlets that it will take in and the `Heater` can have specific settings enabled such as `has_pressure_change` or `has_phase_equilibrium`." + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:27.876870Z", + "start_time": "2025-06-11T22:13:27.853517Z" + } + }, + "source": [ + "m.fs.I101 = Feed(\n", + " property_package=m.fs.thermo_params)\n", + "\n", + "m.fs.I102 = Feed(\n", + " property_package=m.fs.thermo_params)\n", + "\n", + "m.fs.M101 = Mixer(\n", + " property_package=m.fs.thermo_params,\n", + " num_inlets=3,\n", + ")\n", + "\n", + "m.fs.H101 = Heater(\n", + " property_package=m.fs.thermo_params,\n", + " has_pressure_change=False,\n", + " has_phase_equilibrium=True,\n", + ")" + ], + "outputs": [], + "execution_count": 11 + }, + { + "cell_type": "markdown", + "metadata": { + "tags": [ + "exercise" + ] + }, + "source": [ + "
    \n", + "Inline Exercise:\n", + "Let us now add the StoichiometricReactor(assign the name R101) and pass the following arguments:\n", + "
      \n", + "
    • \"property_package\": m.fs.thermo_params
    • \n", + "
    • \"reaction_package\": m.fs.reaction_params
    • \n", + "
    • \"has_heat_of_reaction\": True
    • \n", + "
    • \"has_heat_transfer\": True
    • \n", + "
    • \"has_pressure_change\": False
    • \n", + "
    \n", + "
    " + ] + }, + { + "cell_type": "code", + "metadata": { + "tags": [ + "exercise" + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:27.894702Z", + "start_time": "2025-06-11T22:13:27.891599Z" + } + }, + "source": [ + "# Todo: Add reactor with the specifications above" + ], + "outputs": [], + "execution_count": 12 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let us now add the Flash(assign the name F101) and pass the following arguments:\n", + "
      \n", + "
    • \"property_package\": m.fs.thermo_params
    • \n", + "
    • \"has_heat_transfer\": True
    • \n", + "
    • \"has_pressure_change\": False
    • \n", + "
    " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:27.947463Z", + "start_time": "2025-06-11T22:13:27.933993Z" + } + }, + "source": [ + "m.fs.F101 = Flash(\n", + " property_package=m.fs.thermo_params,\n", + " has_heat_transfer=True,\n", + " has_pressure_change=True,\n", + ")" + ], + "outputs": [], + "execution_count": 14 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": "Let us now add the Splitter(S101) with specific names for its output (purge and recycle), PressureChanger(C101) and the second Flash(F102)." + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:27.987933Z", + "start_time": "2025-06-11T22:13:27.964931Z" + } + }, + "source": [ + "m.fs.S101 = Splitter(\n", + " property_package=m.fs.thermo_params,\n", + " ideal_separation=False,\n", + " outlet_list=[\"purge\", \"recycle\"],\n", + ")\n", + "\n", + "\n", + "m.fs.C101 = PressureChanger(\n", + " property_package=m.fs.thermo_params,\n", + " compressor=True,\n", + " thermodynamic_assumption=ThermodynamicAssumption.isothermal,\n", + ")\n", + "\n", + "m.fs.F102 = Flash(\n", + " property_package=m.fs.thermo_params,\n", + " has_heat_transfer=True,\n", + " has_pressure_change=True,\n", + ")" + ], + "outputs": [], + "execution_count": 15 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "Last, we will add the three Product blocks (P101, P102, P103). We use `Feed` blocks and `Product` blocks for convenience with reporting stream summaries and consistency" + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:28.009893Z", + "start_time": "2025-06-11T22:13:28.001769Z" + } + }, + "cell_type": "code", + "source": [ + "m.fs.P101 = Product(\n", + " property_package=m.fs.thermo_params)\n", + "\n", + "m.fs.P102 = Product(\n", + " property_package=m.fs.thermo_params)\n", + "\n", + "m.fs.P103 = Product(\n", + " property_package=m.fs.thermo_params)" + ], + "outputs": [], + "execution_count": 16 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 2.2 Connecting Unit Models using Arcs\n", + "\n", + "We have now added all the unit models we need to the flowsheet. However, we have not yet specified how the units are to be connected. To do this, we will be using the `Arc` which is a pyomo component that takes in two arguments: `source` and `destination`. Let us connect the outlet of the inlets (I101, I102) to the inlet of the mixer (M101) and outlet of the mixer to the inlet of the heater(H101)." + ] + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:28.034324Z", + "start_time": "2025-06-11T22:13:28.031285Z" + } + }, + "cell_type": "code", + "source": [ + "m.fs.s01 = Arc(source=m.fs.I101.outlet, destination=m.fs.M101.inlet_1)\n", + "m.fs.s02 = Arc(source=m.fs.I102.outlet, destination=m.fs.M101.inlet_2)\n", + "m.fs.s03 = Arc(source=m.fs.M101.outlet, destination=m.fs.H101.inlet)" + ], + "outputs": [], + "execution_count": 17 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "![](HDA_flowsheet.png) \n", + "\n", + "\n" + ] + }, + { + "metadata": { + "tags": [ + "exercise" + ] + }, + "cell_type": "markdown", + "source": [ + "
    \n", + "Inline Exercise:\n", + "Now, connect the H101 outlet to the R101 inlet using the cell above as a guide.\n", + "
    " + ] + }, + { + "cell_type": "code", + "metadata": { + "tags": [ + "exercise" + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:28.055815Z", + "start_time": "2025-06-11T22:13:28.052507Z" + } + }, + "source": [ + "# Todo: Connect the H101 outlet to R101 inlet" + ], + "outputs": [], + "execution_count": 18 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We will now be connecting the rest of the flowsheet as shown below. Notice how the outlet names are different for the flash tanks F101 and F102 as they have a vapor and a liquid outlet. " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:28.118422Z", + "start_time": "2025-06-11T22:13:28.113732Z" + } + }, + "source": [ + "m.fs.s05 = Arc(source=m.fs.R101.outlet, destination=m.fs.F101.inlet)\n", + "m.fs.s06 = Arc(source=m.fs.F101.vap_outlet, destination=m.fs.S101.inlet)\n", + "m.fs.s07 = Arc(source=m.fs.F101.liq_outlet, destination=m.fs.F102.inlet)\n", + "m.fs.s08 = Arc(source=m.fs.S101.recycle, destination=m.fs.C101.inlet)\n", + "m.fs.s09 = Arc(source=m.fs.C101.outlet, destination=m.fs.M101.inlet_3)\n" + ], + "outputs": [], + "execution_count": 20 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "Last we will connect the outlet streams to the inlets of the Product blocks (P101, P102, P103)" + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:28.148879Z", + "start_time": "2025-06-11T22:13:28.144601Z" + } + }, + "cell_type": "code", + "source": [ + "m.fs.s10 = Arc(source=m.fs.F102.vap_outlet, destination=m.fs.P101.inlet)\n", + "m.fs.s11 = Arc(source=m.fs.F102.liq_outlet, destination=m.fs.P102.inlet)\n", + "m.fs.s12 = Arc(source=m.fs.S101.purge, destination=m.fs.P103.inlet)" + ], + "outputs": [], + "execution_count": 21 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We have now connected the unit model block using the arcs. However, each of these arcs link to ports on the two unit models that are connected. In this case, the ports consist of the state variables that need to be linked between the unit models. Pyomo provides a convenient method to write these equality constraints for us between two ports and this is done as follows:" + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:28.198384Z", + "start_time": "2025-06-11T22:13:28.176239Z" + } + }, + "source": [ + "TransformationFactory(\"network.expand_arcs\").apply_to(m)" + ], + "outputs": [], + "execution_count": 22 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 2.3 Adding expressions to compute purity and operating costs\n", + "\n", + "In this section, we will add a few Expressions that allows us to evaluate the performance. Expressions provide a convenient way of calculating certain values that are a function of the variables defined in the model. For more details on Expressions, please refer to: https://pyomo.readthedocs.io/en/stable/pyomo_modeling_components/Expressions.html\n", + "\n", + "For this flowsheet, we are interested in computing the purity of the product Benzene stream (i.e. the mole fraction) and the operating cost which is a sum of the cooling and heating cost. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let us first add an Expression to compute the mole fraction of benzene in the `vap_outlet` of F102 which is our product stream. Please note that the var flow_mol_phase_comp has the index - [time, phase, component]. As this is a steady-state flowsheet, the time index by default is 0. The valid phases are [\"Liq\", \"Vap\"]. Similarly the valid component list is [\"benzene\", \"toluene\", \"hydrogen\", \"methane\"]." + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:28.222088Z", + "start_time": "2025-06-11T22:13:28.218400Z" + } + }, + "source": [ + "m.fs.purity = Expression(\n", + " expr=m.fs.F102.vap_outlet.flow_mol_phase_comp[0, \"Vap\", \"benzene\"]\n", + " / (\n", + " m.fs.F102.vap_outlet.flow_mol_phase_comp[0, \"Vap\", \"benzene\"]\n", + " + m.fs.F102.vap_outlet.flow_mol_phase_comp[0, \"Vap\", \"toluene\"]\n", + " )\n", + ")" + ], + "outputs": [], + "execution_count": 23 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, let us add an expression to compute the cooling cost assuming a cost of 0.212E-4 $/kW. Note that cooling utility is required for the reactor (R101) and the first flash (F101). " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:28.248216Z", + "start_time": "2025-06-11T22:13:28.244244Z" + } + }, + "source": [ + "m.fs.cooling_cost = Expression(\n", + " expr=0.212e-7 * (-m.fs.F101.heat_duty[0]) + 0.212e-7 * (-m.fs.R101.heat_duty[0])\n", + ")" + ], + "outputs": [], + "execution_count": 24 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "Now, let us add an expression to compute the heating cost assuming the utility cost as follows:\n", + "
      \n", + "
    • 2.2E-4 dollars/kW for H101
    • \n", + "
    • 1.9E-4 dollars/kW for F102
    • \n", + "
    \n", + "Note that the heat duty is in units of watt (J/s). " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:28.271256Z", + "start_time": "2025-06-11T22:13:28.268284Z" + } + }, + "source": [ + "m.fs.heating_cost = Expression(\n", + " expr=2.2e-7 * m.fs.H101.heat_duty[0] + 1.9e-7 * m.fs.F102.heat_duty[0]\n", + ")" + ], + "outputs": [], + "execution_count": 25 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let us now add an expression to compute the total operating cost per year which is basically the sum of the cooling and heating cost we defined above. " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:28.295580Z", + "start_time": "2025-06-11T22:13:28.292627Z" + } + }, + "source": [ + "m.fs.operating_cost = Expression(\n", + " expr=(3600 * 24 * 365 * (m.fs.heating_cost + m.fs.cooling_cost))\n", + ")" + ], + "outputs": [], + "execution_count": 26 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": [ + "## 3 Scaling the Model\n", + "\n", + "In this example, we will simply use the ``AutoScaler`` method to scale our model since this example is focused on introductory flowsheet building for a full process system.\n", + "\n", + "For more direct control, a manual, magnitude based approach can be used. If this method is chosen or appropriate, it is highly recommended to look at these documentation:\n", + "- Scaling Toolbox https://idaes-pse.readthedocs.io/en/stable/reference_guides/scaling/scaling.html\n", + "- Scaling Workshop https://idaes-examples.readthedocs.io/en/latest/docs/scaling/scaler_workshop_doc.html.\n", + "\n", + "In this example, we already imported the ``AutoScaler`` class in the beginning so we just create the ``autoscaler`` object and call the ``scale_model`` method on the model `m`." + ] + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:28.915668Z", + "start_time": "2025-06-11T22:13:28.317020Z" + } + }, + "cell_type": "code", + "source": [ + "autoscaler = AutoScaler()\n", + "autoscaler.scale_model(m)" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "WARNING: model contains export suffix 'scaling_factor' that contains 2\n", + "component keys that are not exported as part of the NL file. Skipping.\n" + ] + } + ], + "execution_count": 27 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 4 Specifying the Model\n", + "### 4.1 Fixing feed conditions\n", + "\n", + "Let us first check how many degrees of freedom exist for this flowsheet using the `degrees_of_freedom` tool we imported earlier. " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:28.948173Z", + "start_time": "2025-06-11T22:13:28.924210Z" + } + }, + "source": [ + "print(degrees_of_freedom(m))" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "29\n" + ] + } + ], + "execution_count": 28 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": "We will now be fixing the toluene feed (`I101`) stream to the conditions shown in the flowsheet above. Please note that though this is a pure toluene feed, the remaining components are still assigned a very small non-zero value to help with convergence and initializing." + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.012096Z", + "start_time": "2025-06-11T22:13:29.006965Z" + } + }, + "source": [ + "m.fs.I101.flow_mol_phase_comp[0, \"Vap\", \"benzene\"].fix(1e-5)\n", + "m.fs.I101.flow_mol_phase_comp[0, \"Vap\", \"toluene\"].fix(1e-5)\n", + "m.fs.I101.flow_mol_phase_comp[0, \"Vap\", \"hydrogen\"].fix(1e-5)\n", + "m.fs.I101.flow_mol_phase_comp[0, \"Vap\", \"methane\"].fix(1e-5)\n", + "m.fs.I101.flow_mol_phase_comp[0, \"Liq\", \"benzene\"].fix(1e-5)\n", + "m.fs.I101.flow_mol_phase_comp[0, \"Liq\", \"toluene\"].fix(0.30)\n", + "m.fs.I101.flow_mol_phase_comp[0, \"Liq\", \"hydrogen\"].fix(1e-5)\n", + "m.fs.I101.flow_mol_phase_comp[0, \"Liq\", \"methane\"].fix(1e-5)\n", + "m.fs.I101.temperature.fix(303.2)\n", + "m.fs.I101.pressure.fix(350000)" + ], + "outputs": [], + "execution_count": 30 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "Similarly, let us fix the hydrogen feed (`I102`) to the following conditions in the next cell:\n", + "
      \n", + "
    • FH2 = 0.30 mol/s
    • \n", + "
    • FCH4 = 0.02 mol/s
    • \n", + "
    • Remaining components = 1e-5 mol/s
    • \n", + "
    • T = 303.2 K
    • \n", + "
    • P = 350000 Pa
    • \n", + "
    \n", + "\n" + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.036253Z", + "start_time": "2025-06-11T22:13:29.031731Z" + } + }, + "source": [ + "m.fs.I102.flow_mol_phase_comp[0, \"Vap\", \"benzene\"].fix(1e-5)\n", + "m.fs.I102.flow_mol_phase_comp[0, \"Vap\", \"toluene\"].fix(1e-5)\n", + "m.fs.I102.flow_mol_phase_comp[0, \"Vap\", \"hydrogen\"].fix(0.30)\n", + "m.fs.I102.flow_mol_phase_comp[0, \"Vap\", \"methane\"].fix(0.02)\n", + "m.fs.I102.flow_mol_phase_comp[0, \"Liq\", \"benzene\"].fix(1e-5)\n", + "m.fs.I102.flow_mol_phase_comp[0, \"Liq\", \"toluene\"].fix(1e-5)\n", + "m.fs.I102.flow_mol_phase_comp[0, \"Liq\", \"hydrogen\"].fix(1e-5)\n", + "m.fs.I102.flow_mol_phase_comp[0, \"Liq\", \"methane\"].fix(1e-5)\n", + "m.fs.I102.temperature.fix(303.2)\n", + "m.fs.I102.pressure.fix(350000)" + ], + "outputs": [], + "execution_count": 31 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 4.2 Fixing unit model specifications\n", + "\n", + "Now that we have fixed our inlet feed conditions, we will now be fixing the operating conditions for the unit models in the flowsheet. Let us set set the H101 outlet temperature to 600 K. " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.078559Z", + "start_time": "2025-06-11T22:13:29.075531Z" + } + }, + "source": [ + "m.fs.H101.outlet.temperature.fix(600)" + ], + "outputs": [], + "execution_count": 32 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For the StoichiometricReactor, we have to define the conversion in terms of toluene. This requires us to create a new variable for specifying the conversion and adding a Constraint that defines the conversion with respect to toluene. The second degree of freedom for the reactor is to define the heat duty. In this case, let us assume the reactor to be adiabatic i.e. Q = 0. " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.111099Z", + "start_time": "2025-06-11T22:13:29.106956Z" + } + }, + "source": [ + "m.fs.R101.conversion = Var(initialize=0.75, bounds=(0, 1))\n", + "\n", + "m.fs.R101.conv_constraint = Constraint(\n", + " expr=m.fs.R101.conversion * m.fs.R101.inlet.flow_mol_phase_comp[0, \"Vap\", \"toluene\"]\n", + " == (\n", + " m.fs.R101.inlet.flow_mol_phase_comp[0, \"Vap\", \"toluene\"]\n", + " - m.fs.R101.outlet.flow_mol_phase_comp[0, \"Vap\", \"toluene\"]\n", + " )\n", + ")\n", + "\n", + "m.fs.R101.conversion.fix(0.75)\n", + "m.fs.R101.heat_duty.fix(0)" + ], + "outputs": [], + "execution_count": 33 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The Flash conditions for F101 can be set as follows. " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.128972Z", + "start_time": "2025-06-11T22:13:29.125273Z" + } + }, + "source": [ + "m.fs.F101.vap_outlet.temperature.fix(325.0)\n", + "m.fs.F101.deltaP.fix(0)" + ], + "outputs": [], + "execution_count": 34 + }, + { + "cell_type": "markdown", + "metadata": { + "tags": [ + "exercise" + ] + }, + "source": [ + "
    \n", + "Inline Exercise:\n", + "Set the conditions for Flash F102 to the following conditions:\n", + "
      \n", + "
    • T = 375 K
    • \n", + "
    • deltaP = -200000
    • \n", + "
    \n", + "\n", + "Use Shift+Enter to run the cell once you have typed in your code. \n", + "
    " + ] + }, + { + "cell_type": "code", + "metadata": { + "tags": [ + "exercise" + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.149002Z", + "start_time": "2025-06-11T22:13:29.146476Z" + } + }, + "source": [ + "# Todo: Set conditions for Flash F102" + ], + "outputs": [], + "execution_count": 35 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let us fix the purge split fraction to 20% and the outlet pressure of the compressor is set to 350000 Pa. " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.196247Z", + "start_time": "2025-06-11T22:13:29.192956Z" + } + }, + "source": [ + "m.fs.S101.split_fraction[0, \"purge\"].fix(0.2)\n", + "m.fs.C101.outlet.pressure.fix(350000)" + ], + "outputs": [], + "execution_count": 37 + }, + { + "cell_type": "markdown", + "metadata": { + "tags": [ + "exercise" + ] + }, + "source": [ + "
    \n", + "Inline Exercise:\n", + "We have now defined all the feed conditions and the inputs required for the unit models. The system should now have 0 degrees of freedom i.e. should be a square problem. Please check that the degrees of freedom is 0. \n", + "\n", + "Use Shift+Enter to run the cell once you have typed in your code. \n", + "
    " + ] + }, + { + "cell_type": "code", + "metadata": { + "tags": [ + "exercise" + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.220402Z", + "start_time": "2025-06-11T22:13:29.217907Z" + } + }, + "source": [ + "# Todo: print the degrees of freedom" + ], + "outputs": [], + "execution_count": 38 + }, + { + "metadata": { + "tags": [ + "noauto" + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.316042Z", + "start_time": "2025-06-11T22:13:29.313537Z" + } + }, + "cell_type": "code", + "source": "", + "outputs": [], + "execution_count": null + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 5 Initializing the Model\n", + "\n", + "\n", + "\n", + "When a flowsheet contains a recycle loop, the outlet of a downstream unit becomes the inlet of an upstream unit, creating a cyclic dependency that prevents straightforward calculation of all stream conditions. The tear\u2010stream method is necessary because it \u201cbreaks\u201d this loop: you select one recycle stream as the tear, assign it an initial guess, and then solve the rest of the flowsheet as if it were acyclic. Once the downstream units compute their outputs, you compare the calculated value of the torn stream to your initial guess and iteratively adjust until they coincide. Without tearing, the solver cannot establish a proper topological sequence or drive the recycle to convergence, making initialization\u2014and ultimately steady\u2010state convergence\u2014impossible.\n", + "\n", + "It is important to determine the tear stream for a flowsheet which will be demonstrated below.\n", + "\n", + "\n", + "![](HDA_flowsheet.png)\n", + "\n", + "Currently, there are two methods of initializing a full flowsheet: using the sequential decomposition tool, or manually propagating through the flowsheet. Both methods will be shown.\n", + "\n", + "### 5.1 Sequential Decomposition\n", + "\n", + "This section will demonstrate how to use the built-in sequential decomposition tool to initialize our flowsheet. Sequential Decomposition is a tool from pyomo where the documentation can be found here https://pyomo.readthedocs.io/en/stable/explanation/modeling/network.html#sequential-decomposition\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": "Let us first create an object for the SequentialDecomposition and specify our options for this. We can also create a graph for our flowsheet to determine the tear set and order." + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.330212Z", + "start_time": "2025-06-11T22:13:29.324872Z" + } + }, + "source": [ + "seq = SequentialDecomposition()\n", + "seq.options.select_tear_method = \"heuristic\"\n", + "seq.options.tear_method = \"Wegstein\"\n", + "seq.options.iterLim = 3\n", + "\n", + "# Using the SD tool\n", + "G = seq.create_graph(m)\n", + "heuristic_tear_set = seq.tear_set_arcs(G, method=\"heuristic\")\n", + "order = seq.calculation_order(G)" + ], + "outputs": [], + "execution_count": 41 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Which is the tear stream? Display tear set and order" + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.353734Z", + "start_time": "2025-06-11T22:13:29.350730Z" + } + }, + "source": [ + "for o in heuristic_tear_set:\n", + " print(o.name)" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "fs.s03\n" + ] + } + ], + "execution_count": 42 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "What sequence did the SD tool determine to solve this flowsheet with the least number of tears? " + ] + }, + { + "cell_type": "code", + "metadata": { + "scrolled": true, + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.391173Z", + "start_time": "2025-06-11T22:13:29.388153Z" + } + }, + "source": [ + "for o in order:\n", + " print(o[0].name)" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "fs.I101\n", + "fs.R101\n", + "fs.F101\n", + "fs.S101\n", + "fs.C101\n", + "fs.M101\n" + ] + } + ], + "execution_count": 43 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + " \n", + "\n", + "![](HDA_tear_stream.png) \n", + "\n", + "\n", + "The SequentialDecomposition tool has determined that the tear stream is the mixer outlet. You can see this shown in the picture of the flowsheet above as the outlet of the mixer as the two lines crossing it identifying it as the tear stream. We will need to provide a reasonable guess for this." + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.430684Z", + "start_time": "2025-06-11T22:13:29.426921Z" + } + }, + "source": [ + "tear_guesses = {\n", + " \"flow_mol_phase_comp\": {\n", + " (0, \"Liq\", \"benzene\"): 1e-5,\n", + " (0, \"Liq\", \"toluene\"): 0.30,\n", + " (0, \"Liq\", \"methane\"): 1e-5,\n", + " (0, \"Liq\", \"hydrogen\"): 1e-5,\n", + " (0, \"Vap\", \"benzene\"): 1e-5,\n", + " (0, \"Vap\", \"toluene\"): 1e-5,\n", + " (0, \"Vap\", \"methane\"): 0.02,\n", + " (0, \"Vap\", \"hydrogen\"): 0.30,\n", + " },\n", + " \"temperature\": {0: 303},\n", + " \"pressure\": {0: 350000},\n", + "}\n", + "\n", + "# Pass the tear_guess to the SD tool\n", + "seq.set_guesses_for(m.fs.H101.inlet, tear_guesses)" + ], + "outputs": [], + "execution_count": 44 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next, we need to tell the tool how to initialize a particular unit. We will be writing a python function which takes in a \"unit\" and calls the initialize method on that unit. " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.465203Z", + "start_time": "2025-06-11T22:13:29.462031Z" + } + }, + "source": [ + "def function(unit):\n", + " try:\n", + " initializer = unit.default_initializer()\n", + " initializer.initialize(unit, output_level=idaeslog.INFO)\n", + " except InitializationError:\n", + " solver = get_solver()\n", + " solver.solve(unit)" + ], + "outputs": [], + "execution_count": 45 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We are now ready to initialize our flowsheet in a sequential mode. Note that we specifically set the iteration limit to be 5 as we are trying to use this tool only to get a good set of initial values such that IPOPT can then take over and solve this flowsheet for us. " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.505027Z", + "start_time": "2025-06-11T22:13:29.501933Z" + } + }, + "source": "# seq.run(m, function)", + "outputs": [], + "execution_count": 46 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": [ + "### 5.2 Manual Propogation Method\n", + "\n", + "This method uses a more direct approach to initialize the flowsheet, utilizing the updated initalizer method and propogating manually throught the flowsheet and solving for the tear stream directly.\n", + "Lets first import a helper function that will help us manually propagate and step through the flowsheet" + ] + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.536579Z", + "start_time": "2025-06-11T22:13:29.533074Z" + } + }, + "cell_type": "code", + "source": "from idaes.core.util.initialization import propagate_state", + "outputs": [], + "execution_count": 47 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": [ + "Now we can setup our initial guesses for the tear stream which we know is the outlet of the `Mixer` or the inlet of the `Heater`. We can use the same initial guesses used in the first method. We also want to ensure that the degrees of freedom are consistent while we manually intialize the model.\n", + "\n", + "We will first ensure that are current degrees of freedom is still zero" + ] + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.583098Z", + "start_time": "2025-06-11T22:13:29.553382Z" + } + }, + "cell_type": "code", + "source": "print(f\"The DOF is {degrees_of_freedom(m)} initially\")", + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The DOF is 0 initially\n" + ] + } + ], + "execution_count": 48 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "Now we can manually deactivate the tear stream, creating a separation between the `Mixer` and `Heater`. This should reduce the degrees of freedom by 10 since the inlet of the `Heater` now contains no values to solve the unit model. To deactivate a stream, simply use `m.fs.s03_expanded.deactivate()`. This expanded stream is just a different version of the `Arc` stream that is able to be deactivated." + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.633917Z", + "start_time": "2025-06-11T22:13:29.610932Z" + } + }, + "cell_type": "code", + "source": [ + "m.fs.s03_expanded.deactivate()\n", + "\n", + "print(f\"The DOF is {degrees_of_freedom(m)} after deactivating the tear stream\")" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The DOF is 10 after deactivating the tear stream\n" + ] + } + ], + "execution_count": 49 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "Now we can provide the `Heater` inlet 10 guess values to bring the degrees of freedom back to 0 and start the manual initialization process. We can run this convenient loop to assign each of these guesses to the inlet of the heater." + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.702707Z", + "start_time": "2025-06-11T22:13:29.654459Z" + } + }, + "cell_type": "code", + "source": [ + "tear_guesses = {\n", + " \"flow_mol_phase_comp\": {\n", + " (0, \"Liq\", \"benzene\"): 1e-5,\n", + " (0, \"Liq\", \"toluene\"): 0.30,\n", + " (0, \"Liq\", \"methane\"): 1e-5,\n", + " (0, \"Liq\", \"hydrogen\"): 1e-5,\n", + " (0, \"Vap\", \"benzene\"): 1e-5,\n", + " (0, \"Vap\", \"toluene\"): 1e-5,\n", + " (0, \"Vap\", \"methane\"): 0.02,\n", + " (0, \"Vap\", \"hydrogen\"): 0.30,\n", + "\n", + " },\n", + " \"temperature\": {0: 303},\n", + " \"pressure\": {0: 350000},\n", + "}\n", + "\n", + "for k, v in tear_guesses.items():\n", + " for k1, v1 in v.items():\n", + " getattr(m.fs.s03.destination, k)[k1].fix(v1)\n", + "\n", + "DOF_initial = degrees_of_freedom(m)\n", + "print(f\"The DOF is {degrees_of_freedom(m)} after providing the initial guesses\")" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The DOF is 0 after providing the initial guesses\n" + ] + } + ], + "execution_count": 50 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "The next step is to manually initialize each unit model starting from the `Heater` and then propagate the connection between it and the next unit model. This manual process ensures a strict order to the user's specification if that is desired. The current standard for initializing a unit model is to use an initializer object most compatible for that unit model. This can most often be done by utilizing the `default_initializer()` method attached to the unit model and then to call the `initialize()` method with the unit model as the argument." + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:31.347435Z", + "start_time": "2025-06-11T22:13:29.712721Z" + } + }, + "cell_type": "code", + "source": [ + "m.fs.H101.default_initializer().initialize(m.fs.H101) # Initialize Heater\n", + "propagate_state(m.fs.s04) # Establish connection between Heater and Reactor\n", + "m.fs.R101.default_initializer().initialize(m.fs.R101) # Initialize Reactor\n", + "propagate_state(m.fs.s05) # Establish connection between Reactor and First Flash Unit\n", + "m.fs.F101.default_initializer().initialize(m.fs.F101) # Initialize First Flash Unit\n", + "propagate_state(m.fs.s06) # Establish connection between First Flash Unit and Splitter\n", + "propagate_state(m.fs.s07) # Establish connection between First Flash Unit and Second Flash Unit\n", + "m.fs.S101.default_initializer().initialize(m.fs.S101) # Initialize Splitter\n", + "propagate_state(m.fs.s08) # Establish connection between Splitter and Compressor\n", + "m.fs.C101.default_initializer().initialize(m.fs.C101) # Initialize Compressor\n", + "propagate_state(m.fs.s09) # Establish connection between Compressor and Mixer\n", + "m.fs.I101.default_initializer().initialize(m.fs.I101) # Initialize Toluene Inlet\n", + "propagate_state(m.fs.s01) # Establish connection between Toluene Inlet and Mixer\n", + "m.fs.I102.default_initializer().initialize(m.fs.I102) # Initialize Hydrogen Inlet\n", + "propagate_state(m.fs.s02) # Establish connection between Hydrogen Inlet and Mixer\n", + "m.fs.M101.default_initializer().initialize(m.fs.M101) # Initialize Mixer\n", + "propagate_state(m.fs.s03) # Establish connection between Mixer and Heater\n", + "m.fs.F102.default_initializer().initialize(m.fs.F102) # Initialize Second Flash Unit\n", + "propagate_state(m.fs.s10) # Establish connection between Second Flash Unit and Benzene Product\n", + "propagate_state(m.fs.s11) # Establish connection between Second Flash Unit and Toluene Product\n", + "propagate_state(m.fs.s12) # Establish connection between Splitter and Purge Product\n" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2025-06-11 16:13:29 [INFO] idaes.init.fs.H101.control_volume.properties_in: Initialization Complete\n", + "2025-06-11 16:13:30 [INFO] idaes.init.fs.H101.control_volume.properties_out: Initialization Complete\n", + "2025-06-11 16:13:30 [INFO] idaes.init.fs.R101.control_volume.properties_in: Initialization Complete\n", + "2025-06-11 16:13:30 [INFO] idaes.init.fs.R101.control_volume.properties_out: Initialization Complete\n", + "2025-06-11 16:13:30 [INFO] idaes.init.fs.F101.control_volume.properties_in: Initialization Complete\n", + "2025-06-11 16:13:30 [INFO] idaes.init.fs.F101.control_volume.properties_out: Initialization Complete\n", + "2025-06-11 16:13:30 [INFO] idaes.init.fs.S101.mixed_state: Initialization Complete\n", + "2025-06-11 16:13:30 [INFO] idaes.init.fs.S101.purge_state: Initialization Complete\n", + "2025-06-11 16:13:30 [INFO] idaes.init.fs.S101.recycle_state: Initialization Complete\n", + "2025-06-11 16:13:30 [INFO] idaes.init.fs.S101: Initialization Step 2 Complete: optimal - \n", + "2025-06-11 16:13:30 [INFO] idaes.init.fs.C101.control_volume.properties_in: Initialization Complete\n", + "2025-06-11 16:13:31 [INFO] idaes.init.fs.C101.control_volume.properties_out: Initialization Complete\n", + "2025-06-11 16:13:31 [INFO] idaes.init.fs.I101.properties: Initialization Complete\n", + "2025-06-11 16:13:31 [INFO] idaes.init.fs.I102.properties: Initialization Complete\n", + "2025-06-11 16:13:31 [INFO] idaes.init.fs.M101.inlet_1_state: Initialization Complete\n", + "2025-06-11 16:13:31 [INFO] idaes.init.fs.M101.inlet_2_state: Initialization Complete\n", + "2025-06-11 16:13:31 [INFO] idaes.init.fs.M101.inlet_3_state: Initialization Complete\n", + "2025-06-11 16:13:31 [INFO] idaes.init.fs.M101.mixed_state: Initialization Complete\n", + "2025-06-11 16:13:31 [INFO] idaes.init.fs.M101: Initialization Complete: optimal - \n", + "2025-06-11 16:13:31 [INFO] idaes.init.fs.F102.control_volume.properties_in: Initialization Complete\n", + "2025-06-11 16:13:31 [INFO] idaes.init.fs.F102.control_volume.properties_out: Initialization Complete\n" + ] + } + ], + "execution_count": 51 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "Now we solve the system to allow the outlet of the mixer to reach a converged congruence with the inlet of the heater." + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:31.935414Z", + "start_time": "2025-06-11T22:13:31.357025Z" + } + }, + "cell_type": "code", + "source": [ + "solver = get_solver()\n", + "results = solver.solve(m, tee=True)" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "WARNING: model contains export suffix 'scaling_factor' that contains 40\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "Ipopt 3.13.2: nlp_scaling_method=gradient-based\n", + "tol=1e-06\n", + "max_iter=200\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...: 1112\n", + "Number of nonzeros in inequality constraint Jacobian.: 0\n", + "Number of nonzeros in Lagrangian Hessian.............: 999\n", + "\n", + "Total number of variables............................: 380\n", + " variables with only lower bounds: 0\n", + " variables with lower and upper bounds: 186\n", + " variables with only upper bounds: 0\n", + "Total number of equality constraints.................: 380\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 1.24e+05 0.00e+00 -1.0 0.00e+00 - 0.00e+00 0.00e+00 0\n", + " 1 0.0000000e+00 1.24e+05 2.12e+00 -1.0 1.99e+05 - 1.39e-01 3.68e-04h 10\n", + " 2 0.0000000e+00 1.24e+05 5.49e+00 -1.0 1.99e+05 - 1.43e-01 3.66e-04h 10\n", + " 3 0.0000000e+00 1.24e+05 4.13e+01 -1.0 2.00e+05 - 9.51e-01 3.64e-04h 10\n", + " 4 0.0000000e+00 1.24e+05 7.47e+01 -1.0 2.00e+05 - 1.77e-01 3.63e-04h 10\n", + " 5 0.0000000e+00 1.24e+05 4.07e+02 -1.0 2.01e+05 - 9.90e-01 3.61e-04h 10\n", + " 6 0.0000000e+00 1.24e+05 6.97e+02 -1.0 2.01e+05 - 1.63e-01 3.60e-04h 10\n", + " 7 0.0000000e+00 1.24e+05 3.75e+03 -1.0 2.02e+05 - 9.90e-01 3.58e-04h 10\n", + " 8 0.0000000e+00 1.24e+05 6.44e+03 -1.0 2.02e+05 - 1.63e-01 3.57e-04h 10\n", + " 9 0.0000000e+00 1.24e+05 3.51e+04 -1.0 2.03e+05 - 1.00e+00 3.55e-04h 10\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 10 0.0000000e+00 1.24e+05 6.05e+04 -1.0 2.04e+05 - 1.62e-01 3.54e-04h 10\n", + " 11 0.0000000e+00 2.01e+06 2.73e+05 -1.0 2.04e+05 - 1.00e+00 1.80e-01w 1\n", + " 12 0.0000000e+00 2.01e+06 3.91e+07 -1.0 7.38e+05 - 6.21e-02 5.22e-04w 1\n", + " 13 0.0000000e+00 2.01e+06 6.94e+11 -1.0 7.50e+05 - 9.13e-02 5.14e-06w 1\n", + " 14 0.0000000e+00 1.24e+05 3.32e+05 -1.0 6.22e+04 - 1.00e+00 3.52e-04h 9\n", + " 15 0.0000000e+00 1.24e+05 5.43e+05 -1.0 2.05e+05 - 1.41e-01 3.51e-04h 10\n", + " 16 0.0000000e+00 1.24e+05 3.01e+06 -1.0 2.05e+05 - 1.00e+00 3.49e-04h 10\n", + " 17 0.0000000e+00 1.24e+05 4.76e+06 -1.0 2.06e+05 - 1.28e-01 3.48e-04h 10\n", + " 18 0.0000000e+00 1.24e+05 2.66e+07 -1.0 2.06e+05 - 1.00e+00 3.46e-04h 10\n", + " 19 0.0000000e+00 1.24e+05 4.23e+07 -1.0 2.07e+05 - 1.28e-01 3.45e-04h 10\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 20 0.0000000e+00 1.24e+05 2.38e+08 -1.0 2.08e+05 - 1.00e+00 3.43e-04h 10\n", + " 21 0.0000000e+00 1.24e+05 3.80e+08 -1.0 2.08e+05 - 1.28e-01 3.42e-04h 10\n", + " 22 0.0000000e+00 1.23e+05 2.16e+09 -1.0 2.09e+05 - 1.00e+00 3.40e-04h 10\n", + " 23 0.0000000e+00 1.23e+05 3.45e+09 -1.0 2.09e+05 - 1.28e-01 3.39e-04h 10\n", + " 24 0.0000000e+00 1.96e+06 1.26e+10 -1.0 2.10e+05 - 7.69e-01 1.73e-01w 1\n", + " 25 0.0000000e+00 1.96e+06 1.91e+12 -1.0 7.43e+05 - 6.14e-02 5.08e-04w 1\n", + " 26 0.0000000e+00 1.96e+06 7.01e+16 -1.0 7.55e+05 - 1.84e-01 5.01e-06w 1\n", + " 27 0.0000000e+00 1.23e+05 1.60e+10 -1.0 1.00e+05 - 7.69e-01 3.37e-04h 9\n", + " 28 0.0000000e+00 1.23e+05 2.61e+10 -1.0 2.10e+05 - 1.33e-01 3.36e-04h 10\n", + " 29 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.11e+05 - 1.00e+00 3.35e-04h 10\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 30 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.11e+05 - 1.27e-01 3.33e-04h 10\n", + " 31 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.12e+05 - 5.83e-01 3.32e-04h 10\n", + " 32 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.13e+05 - 1.41e-01 3.30e-04h 10\n", + " 33 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.13e+05 - 1.00e+00 3.29e-04h 10\n", + " 34 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.14e+05 - 1.26e-01 3.28e-04h 10\n", + " 35 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.14e+05 - 6.16e-01 3.26e-04h 10\n", + " 36 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.15e+05 - 1.38e-01 3.25e-04h 10\n", + " 37 0.0000000e+00 1.90e+06 5.27e+11 -1.0 2.15e+05 - 1.00e+00 1.66e-01w 1\n", + " 38 0.0000000e+00 1.90e+06 6.09e+13 -1.0 2.72e+05 - 1.39e-01 1.43e-03w 1\n", + " 39 0.0000000e+00 1.90e+06 1.06e+17 -1.0 7.68e+05 - 9.45e-02 4.82e-06w 1\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 40 0.0000000e+00 1.23e+05 1.04e+11 -1.0 7.68e+05 - 1.00e+00 3.23e-04h 9\n", + " 41 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.16e+05 - 1.26e-01 3.22e-04h 10\n", + " 42 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.17e+05 - 6.03e-01 3.21e-04h 10\n", + " 43 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.17e+05 - 1.38e-01 3.19e-04h 10\n", + " 44 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.18e+05 - 1.00e+00 3.18e-04h 10\n", + " 45 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.18e+05 - 1.25e-01 3.17e-04h 10\n", + " 46 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.19e+05 - 6.03e-01 3.15e-04h 10\n", + " 47 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.20e+05 - 1.38e-01 3.14e-04h 10\n", + " 48 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.20e+05 - 1.00e+00 3.13e-04h 10\n", + " 49 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.21e+05 - 1.25e-01 3.11e-04h 10\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 50 0.0000000e+00 1.85e+06 3.37e+11 -1.0 2.21e+05 - 5.99e-01 1.59e-01w 1\n", + " 51 0.0000000e+00 1.85e+06 5.67e+13 -1.0 7.53e+05 - 6.18e-02 4.82e-04w 1\n", + " 52 0.0000000e+00 1.85e+06 1.08e+17 -1.0 7.64e+05 - 2.20e-01 4.75e-06w 1\n", + " 53 0.0000000e+00 1.23e+05 1.04e+11 -1.0 7.64e+05 - 5.99e-01 3.10e-04h 9\n", + " 54 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.22e+05 - 1.38e-01 3.09e-04h 10\n", + " 55 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.22e+05 - 1.00e+00 3.07e-04h 10\n", + " 56 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.23e+05 - 1.24e-01 3.06e-04h 10\n", + " 57 0.0000000e+00 1.22e+05 1.04e+11 -1.0 2.24e+05 - 5.97e-01 3.05e-04h 10\n", + " 58 0.0000000e+00 1.22e+05 1.04e+11 -1.0 2.24e+05 - 1.37e-01 3.04e-04h 10\n", + " 59 0.0000000e+00 1.22e+05 1.04e+11 -1.0 2.25e+05 - 1.00e+00 3.02e-04h 10\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 60 0.0000000e+00 1.22e+05 1.04e+11 -1.0 2.25e+05 - 1.24e-01 3.01e-04h 10\n", + " 61 0.0000000e+00 1.22e+05 1.04e+11 -1.0 2.26e+05 - 5.94e-01 3.00e-04h 10\n", + " 62 0.0000000e+00 1.22e+05 1.04e+11 -1.0 2.26e+05 - 1.37e-01 2.98e-04h 10\n", + " 63 0.0000000e+00 1.79e+06 6.03e+11 -1.0 2.27e+05 - 1.00e+00 1.52e-01w 1\n", + " 64 0.0000000e+00 1.79e+06 9.13e+13 -1.0 7.58e+05 - 6.05e-02 4.69e-04w 1\n", + " 65 0.0000000e+00 1.79e+06 1.10e+17 -1.0 7.68e+05 - 1.20e-01 4.63e-06w 1\n", + " 66 0.0000000e+00 1.22e+05 1.04e+11 -1.0 7.69e+05 - 1.00e+00 2.97e-04h 9\n", + " 67 0.0000000e+00 1.22e+05 1.04e+11 -1.0 2.28e+05 - 1.23e-01 2.96e-04h 10\n", + " 68 0.0000000e+00 1.22e+05 1.04e+11 -1.0 2.28e+05 - 5.91e-01 2.95e-04h 10\n", + " 69 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.29e+05 - 1.36e-01 2.93e-04h 10\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 70 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.29e+05 - 1.00e+00 2.92e-04h 10\n", + " 71 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.30e+05 - 1.23e-01 2.91e-04h 10\n", + " 72 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.31e+05 - 5.89e-01 2.90e-04h 10\n", + " 73 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.31e+05 - 1.36e-01 2.89e-04h 10\n", + " 74 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.32e+05 - 1.00e+00 2.87e-04h 10\n", + " 75 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.32e+05 - 1.23e-01 2.86e-04h 10\n", + " 76 0.0000000e+00 1.74e+06 3.75e+11 -1.0 2.33e+05 - 5.86e-01 1.46e-01w 1\n", + " 77 0.0000000e+00 1.74e+06 6.57e+13 -1.0 7.62e+05 - 6.18e-02 4.58e-04w 1\n", + " 78 0.0000000e+00 1.74e+06 1.12e+17 -1.0 7.73e+05 - 2.30e-01 4.51e-06w 1\n", + " 79 0.0000000e+00 1.22e+05 1.05e+11 -1.0 7.73e+05 - 5.86e-01 2.85e-04h 9\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 80 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.34e+05 - 1.36e-01 2.84e-04h 10\n", + " 81 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.34e+05 - 1.00e+00 2.83e-04h 10\n", + " 82 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.35e+05 - 1.22e-01 2.81e-04h 10\n", + " 83 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.35e+05 - 5.83e-01 2.80e-04h 10\n", + " 84 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.36e+05 - 1.35e-01 2.79e-04h 10\n", + " 85 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.37e+05 - 1.00e+00 2.78e-04h 10\n", + " 86 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.37e+05 - 1.22e-01 2.77e-04h 10\n", + " 87 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.38e+05 - 5.81e-01 2.76e-04h 10\n", + " 88 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.38e+05 - 1.35e-01 2.74e-04h 10\n", + " 89 0.0000000e+00 1.69e+06 6.88e+11 -1.0 2.39e+05 - 1.00e+00 1.40e-01w 1\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 90 0.0000000e+00 1.69e+06 1.08e+14 -1.0 7.66e+05 - 6.03e-02 4.46e-04w 1\n", + " 91 0.0000000e+00 1.69e+06 1.14e+17 -1.0 7.77e+05 - 1.22e-01 4.40e-06w 1\n", + " 92 0.0000000e+00 1.22e+05 1.05e+11 -1.0 7.77e+05 - 1.00e+00 2.73e-04h 9\n", + " 93 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.39e+05 - 1.21e-01 2.72e-04h 10\n", + " 94 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.40e+05 - 5.78e-01 2.71e-04h 10\n", + " 95 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.41e+05 - 1.34e-01 2.70e-04h 10\n", + " 96 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.41e+05 - 1.00e+00 2.69e-04h 10\n", + " 97 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.42e+05 - 1.21e-01 2.68e-04h 10\n", + " 98 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.42e+05 - 5.76e-01 2.67e-04h 10\n", + " 99 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.43e+05 - 1.34e-01 2.65e-04h 10\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 100 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.44e+05 - 1.00e+00 2.64e-04h 10\n", + " 101 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.44e+05 - 1.20e-01 2.63e-04h 10\n", + " 102 0.0000000e+00 1.64e+06 4.17e+11 -1.0 2.45e+05 - 5.73e-01 1.34e-01w 1\n", + " 103 0.0000000e+00 1.64e+06 7.61e+13 -1.0 7.71e+05 - 6.17e-02 4.35e-04w 1\n", + " 104 0.0000000e+00 1.64e+06 1.17e+17 -1.0 7.81e+05 - 2.38e-01 4.29e-06w 1\n", + " 105 0.0000000e+00 1.21e+05 1.05e+11 -1.0 7.81e+05 - 5.73e-01 2.62e-04h 9\n", + " 106 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.45e+05 - 1.34e-01 2.61e-04h 10\n", + " 107 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.46e+05 - 1.00e+00 2.60e-04h 10\n", + " 108 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.47e+05 - 1.20e-01 2.59e-04h 10\n", + " 109 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.47e+05 - 5.71e-01 2.58e-04h 10\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 110 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.48e+05 - 1.33e-01 2.57e-04h 10\n", + " 111 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.49e+05 - 1.00e+00 2.56e-04h 10\n", + " 112 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.49e+05 - 1.19e-01 2.55e-04h 10\n", + " 113 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.50e+05 - 5.68e-01 2.54e-04h 10\n", + " 114 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.50e+05 - 1.33e-01 2.53e-04h 10\n", + " 115 0.0000000e+00 1.59e+06 7.85e+11 -1.0 2.51e+05 - 1.00e+00 1.29e-01w 1\n", + " 116 0.0000000e+00 1.59e+06 1.28e+14 -1.0 7.75e+05 - 6.01e-02 4.25e-04w 1\n", + " 117 0.0000000e+00 1.59e+06 1.19e+17 -1.0 7.85e+05 - 1.23e-01 4.19e-06w 1\n", + " 118 0.0000000e+00 1.21e+05 1.05e+11 -1.0 7.85e+05 - 1.00e+00 2.52e-04h 9\n", + " 119 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.52e+05 - 1.19e-01 2.51e-04h 10\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 120 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.52e+05 - 5.66e-01 2.50e-04h 10\n", + " 121 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.53e+05 - 1.32e-01 2.49e-04h 10\n", + " 122 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.53e+05 - 1.00e+00 2.47e-04h 10\n", + " 123 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.54e+05 - 1.18e-01 4.93e-04h 9\n", + " 124 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.55e+05 - 5.60e-01 4.89e-04h 9\n", + " 125 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.56e+05 - 1.32e-01 4.85e-04h 9\n", + " 126 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.58e+05 - 1.00e+00 4.81e-04h 9\n", + " 127 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.59e+05 - 1.18e-01 4.77e-04h 9\n", + " 128 0.0000000e+00 1.52e+06 4.74e+11 -1.0 2.60e+05 - 5.55e-01 1.21e-01w 1\n", + " 129 0.0000000e+00 1.52e+06 9.09e+13 -1.0 7.80e+05 - 6.17e-02 4.09e-04w 1\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 130 0.0000000e+00 1.52e+06 1.22e+17 -1.0 7.90e+05 - 2.48e-01 4.04e-06w 1\n", + " 131 0.0000000e+00 1.20e+05 1.05e+11 -1.0 7.90e+05 - 5.55e-01 4.73e-04h 8\n", + " 132 0.0000000e+00 1.20e+05 1.05e+11 -1.0 2.61e+05 - 1.31e-01 4.69e-04h 9\n", + " 133 0.0000000e+00 1.20e+05 1.05e+11 -1.0 2.63e+05 - 1.00e+00 1.86e-03h 7\n", + " 134 0.0000000e+00 1.20e+05 1.05e+11 -1.0 2.67e+05 - 1.16e-01 1.80e-03h 7\n", + " 135 0.0000000e+00 1.20e+05 1.05e+11 -1.0 2.72e+05 - 5.16e-01 1.74e-03h 7\n", + " 136 0.0000000e+00 1.19e+05 1.05e+11 -1.0 2.77e+05 - 1.29e-01 3.38e-03h 6\n", + " 137 0.0000000e+00 1.19e+05 1.05e+11 -1.0 2.87e+05 - 1.00e+00 3.17e-03h 6\n", + " 138 0.0000000e+00 1.19e+05 1.06e+11 -1.0 2.98e+05 - 1.10e-01 2.97e-03h 6\n", + " 139 0.0000000e+00 1.18e+05 1.06e+11 -1.0 3.08e+05 - 4.88e-01 2.79e-03h 6\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 140 0.0000000e+00 1.18e+05 1.07e+11 -1.0 3.18e+05 - 1.22e-01 5.25e-03h 5\n", + " 141 0.0000000e+00 1.01e+06 1.89e+12 -1.0 3.38e+05 - 1.00e+00 7.42e-02w 1\n", + " 142 0.0000000e+00 1.01e+06 3.99e+14 -1.0 8.16e+05 - 5.93e-02 3.07e-04w 1\n", + " 143 0.0000000e+00 1.01e+06 1.56e+17 -1.0 8.24e+05 - 1.44e-01 3.04e-06w 1\n", + " 144 0.0000000e+00 1.17e+05 1.08e+11 -1.0 8.24e+05 - 1.00e+00 4.64e-03h 4\n", + " 145 0.0000000e+00 1.17e+05 1.08e+11 -1.0 3.59e+05 - 1.01e-01 2.06e-03h 6\n", + " 146 0.0000000e+00 1.17e+05 1.08e+11 -1.0 3.69e+05 - 4.67e-01 1.94e-03h 6\n", + " 147 0.0000000e+00 1.17e+05 1.08e+11 -1.0 3.79e+05 - 1.12e-01 1.83e-03h 6\n", + " 148 0.0000000e+00 1.16e+05 1.09e+11 -1.0 3.89e+05 - 1.00e+00 8.66e-04h 7\n", + " 149 0.0000000e+00 1.16e+05 1.09e+11 -1.0 3.94e+05 - 9.61e-02 8.42e-04h 7\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 150 0.0000000e+00 1.16e+05 1.09e+11 -1.0 3.99e+05 - 4.61e-01 4.10e-04h 8\n", + " 151 0.0000000e+00 1.16e+05 1.09e+11 -1.0 4.01e+05 - 1.09e-01 4.04e-04h 8\n", + " 152 0.0000000e+00 1.16e+05 1.09e+11 -1.0 4.04e+05 - 1.00e+00 3.98e-04h 8\n", + " 153 0.0000000e+00 1.16e+05 1.09e+11 -1.0 4.06e+05 - 9.46e-02 9.83e-05h 10\n", + " 154 0.0000000e+00 6.92e+05 1.57e+12 -1.0 4.07e+05 - 4.54e-01 5.01e-02w 1\n", + " 155 0.0000000e+00 6.92e+05 4.69e+14 -1.0 8.35e+05 - 6.23e-02 2.42e-04w 1\n", + " 156 0.0000000e+00 6.92e+05 1.93e+17 -1.0 8.42e+05 - 2.97e-01 2.40e-06w 1\n", + " 157 0.0000000e+00 1.16e+05 1.09e+11 -1.0 8.42e+05 - 4.54e-01 9.79e-05h 9\n", + " 158 0.0000000e+00 1.16e+05 1.09e+11 -1.0 4.08e+05 - 1.09e-01 9.76e-05h 10\n", + " 159 0.0000000e+00 1.16e+05 1.09e+11 -1.0 4.08e+05 - 1.00e+00 9.73e-05h 10\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 160 0.0000000e+00 1.16e+05 1.09e+11 -1.0 4.09e+05 - 9.43e-02 9.70e-05h 10\n", + " 161 0.0000000e+00 1.16e+05 1.09e+11 -1.0 4.09e+05 - 4.54e-01 4.83e-05h 11\n", + " 162 0.0000000e+00 1.16e+05 1.09e+11 -1.0 4.10e+05 - 1.08e-01 4.82e-05h 11\n", + " 163 0.0000000e+00 1.16e+05 1.09e+11 -1.0 4.10e+05 - 1.00e+00 1.20e-05h 13\n", + " 164 0.0000000e+00 1.16e+05 1.09e+11 -1.0 4.10e+05 - 9.42e-02 1.20e-05h 13\n", + " 165 0.0000000e+00 1.16e+05 1.09e+11 -1.0 4.10e+05 - 4.55e-01 9.62e-05h 10\n", + " 166 0.0000000e+00 1.16e+05 1.09e+11 -1.0 4.11e+05 - 1.08e-01 9.59e-05h 10\n", + " 167 0.0000000e+00 6.75e+05 3.70e+12 -1.0 4.11e+05 - 1.00e+00 4.89e-02w 1\n", + " 168 0.0000000e+00 6.74e+05 9.73e+14 -1.0 8.36e+05 - 5.90e-02 2.39e-04w 1\n", + " 169 0.0000000e+00 6.74e+05 1.96e+17 -1.0 8.43e+05 - 1.50e-01 2.37e-06w 1\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 170 0.0000000e+00 1.16e+05 1.09e+11 -1.0 8.43e+05 - 1.00e+00 9.56e-05h 9\n", + " 171 0.0000000e+00 1.16e+05 1.09e+11 -1.0 4.12e+05 - 9.39e-02 2.38e-05h 12\n", + " 172 0.0000000e+00 1.16e+05 1.09e+11 -1.0 4.12e+05 - 4.52e-01 1.19e-05h 13\n", + " 173r 0.0000000e+00 1.16e+05 1.00e+03 0.6 0.00e+00 - 0.00e+00 3.72e-07R 18\n", + " 174r 0.0000000e+00 1.25e+05 1.78e+03 0.6 3.70e+04 - 1.19e-02 1.34e-03f 1\n", + " 175r 0.0000000e+00 1.10e+05 1.54e+03 0.6 7.30e+03 - 3.61e-03 8.33e-03f 1\n", + " 176r 0.0000000e+00 1.02e+05 4.12e+03 0.6 5.25e+03 - 6.33e-02 8.82e-03f 1\n", + " 177r 0.0000000e+00 9.63e+04 4.43e+03 0.6 4.50e+03 - 3.61e-02 2.36e-02f 1\n", + " 178r 0.0000000e+00 9.20e+04 1.63e+04 0.6 3.97e+03 - 2.78e-01 6.99e-02f 1\n", + " 179r 0.0000000e+00 8.79e+04 3.34e+04 0.6 4.66e+02 - 8.68e-01 3.38e-01f 1\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 180r 0.0000000e+00 1.06e+05 3.33e+02 0.6 1.05e+02 - 1.00e+00 1.00e+00f 1\n", + " 181r 0.0000000e+00 1.05e+05 2.26e+02 0.6 2.72e+01 - 1.00e+00 1.00e+00f 1\n", + " 182r 0.0000000e+00 9.53e+04 2.03e+03 -0.1 2.12e+02 - 1.00e+00 7.99e-01f 1\n", + " 183r 0.0000000e+00 8.64e+04 8.95e+01 -0.1 8.37e+01 - 1.00e+00 1.00e+00f 1\n", + " 184r 0.0000000e+00 8.52e+04 9.30e-01 -0.1 4.13e+01 - 1.00e+00 1.00e+00h 1\n", + " 185r 0.0000000e+00 7.70e+04 7.43e+02 -2.2 2.70e+02 - 8.39e-01 7.19e-01f 1\n", + " 186r 0.0000000e+00 7.27e+04 1.42e+03 -2.2 1.95e+03 - 9.32e-01 6.47e-01f 1\n", + " 187r 0.0000000e+00 7.36e+04 5.69e+02 -2.2 4.90e+02 - 9.15e-01 6.73e-01f 1\n", + " 188r 0.0000000e+00 7.91e+04 2.05e+02 -2.2 2.75e+02 - 1.00e+00 8.69e-01f 1\n", + " 189r 0.0000000e+00 7.92e+04 1.27e+03 -2.2 1.37e+02 - 8.68e-01 1.06e-01f 1\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 190r 0.0000000e+00 7.99e+04 8.09e+01 -2.2 1.45e+02 - 1.00e+00 1.00e+00f 1\n", + " 191r 0.0000000e+00 8.00e+04 1.81e+01 -2.2 2.82e+00 - 1.00e+00 1.00e+00f 1\n", + " 192r 0.0000000e+00 8.00e+04 6.93e-02 -2.2 6.43e-02 - 1.00e+00 1.00e+00h 1\n", + " 193r 0.0000000e+00 8.03e+04 1.79e+02 -3.3 1.75e+01 - 9.50e-01 8.57e-01f 1\n", + " 194r 0.0000000e+00 4.29e+04 3.40e+02 -3.3 8.69e+02 - 1.00e+00 6.02e-01f 1\n", + " 195r 0.0000000e+00 1.36e+04 1.76e+01 -3.3 3.71e+02 - 1.00e+00 1.00e+00f 1\n", + " 196r 0.0000000e+00 1.99e+04 5.38e-01 -3.3 7.89e+01 - 1.00e+00 1.00e+00h 1\n", + " 197r 0.0000000e+00 1.73e+04 9.03e-02 -3.3 3.20e+01 - 1.00e+00 1.00e+00h 1\n", + " 198r 0.0000000e+00 1.64e+04 1.32e-02 -3.3 1.07e+01 - 1.00e+00 1.00e+00h 1\n", + " 199r 0.0000000e+00 1.48e+04 9.83e+01 -4.9 2.40e+01 - 8.62e-01 7.80e-01f 1\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 200r 0.0000000e+00 1.44e+04 1.92e+03 -4.9 1.71e+03 - 6.01e-01 1.17e-02f 1\n", + "\n", + "Number of Iterations....: 200\n", + "\n", + " (scaled) (unscaled)\n", + "Objective...............: 0.0000000000000000e+00 0.0000000000000000e+00\n", + "Dual infeasibility......: 8.8349814638582666e+02 8.8349814638582666e+02\n", + "Constraint violation....: 3.3086142754792485e-04 1.4369018418593043e+04\n", + "Complementarity.........: 6.5630241357471754e-04 6.5630241357471754e-04\n", + "Overall NLP error.......: 3.3086142754792485e-04 1.4369018418593043e+04\n", + "\n", + "\n", + "Number of objective function evaluations = 1605\n", + "Number of objective gradient evaluations = 175\n", + "Number of equality constraint evaluations = 1605\n", + "Number of inequality constraint evaluations = 0\n", + "Number of equality constraint Jacobian evaluations = 202\n", + "Number of inequality constraint Jacobian evaluations = 0\n", + "Number of Lagrangian Hessian evaluations = 200\n", + "Total CPU secs in IPOPT (w/o function evaluations) = 0.331\n", + "Total CPU secs in NLP function evaluations = 0.025\n", + "\n", + "EXIT: Maximum Number of Iterations Exceeded.\n", + "WARNING: Loading a SolverResults object with a warning status into\n", + "model.name=\"unknown\";\n", + " - termination condition: maxIterations\n", + " - message from solver: Ipopt 3.13.2\\x3a Maximum Number of Iterations\n", + " Exceeded.\n" + ] + } + ], + "execution_count": 52 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "Now that the flowsheet is initialized, we can unfix the guesses for the `Heater` and reactive the tear stream to complete the final solve." + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:31.967629Z", + "start_time": "2025-06-11T22:13:31.943574Z" + } + }, + "cell_type": "code", + "source": [ + "for k, v in tear_guesses.items():\n", + " for k1, v1 in v.items():\n", + " getattr(m.fs.H101.inlet, k)[k1].unfix()\n", + "\n", + "m.fs.s03_expanded.activate()\n", + "print(f\"The DOF is {degrees_of_freedom(m)} after unfixing the values and reactivating the tear stream\")" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The DOF is 0 after unfixing the values and reactivating the tear stream\n" + ] + } + ], + "execution_count": 53 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "## 6 Solving the Model" + }, + { + "cell_type": "markdown", + "metadata": { + "tags": [] + }, + "source": "We have now initialized the flowsheet. Lets set up some solving options before simulating the flowsheet. We want to specify the scaling method, number of iterations, and tolerance. More specific or advanced options can be found at the documentation for IPOPT https://coin-or.github.io/Ipopt/OPTIONS.html" + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:31.982093Z", + "start_time": "2025-06-11T22:13:31.978929Z" + } + }, + "cell_type": "code", + "source": [ + "optarg = {\n", + " 'nlp_scaling_method': 'user-scaling',\n", + " 'OF_ma57_automatic_scaling': 'yes',\n", + " 'max_iter': 500,\n", + " 'tol': 1e-8,\n", + "}" + ], + "outputs": [], + "execution_count": 54 + }, + { + "metadata": { + "tags": [ + "exercise" + ] + }, + "cell_type": "markdown", + "source": [ + "
    \n", + "Inline Exercise:\n", + "Let us run the flowsheet in a simulation mode to look at the results. To do this, complete the last line of code where we pass the model to the solver. You will need to type the following:\n", + "\n", + "solver = get_solver(solver_options=optarg)
    \n", + "results = solver.solve(m, tee=True)\n", + "\n", + "Use Shift+Enter to run the cell once you have typed in your code.\n", + "
    \n" + ] + }, + { + "cell_type": "code", + "metadata": { + "tags": [ + "exercise" + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:32.012062Z", + "start_time": "2025-06-11T22:13:32.008341Z" + } + }, + "source": [ + "# Create the solver object\n", + "\n", + "# Solve the model" + ], + "outputs": [], + "execution_count": 55 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 7 Analyze the results\n", + "\n", + "\n" + ] + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "If the IDAES UI package was installed with the `idaes-pse` installation or installed separately, you can run the flowsheet visualizer to see a full diagram of the full process that is generated and displayed on a browser window.\n" + }, + { + "metadata": { + "tags": [ + "noauto" + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:33.874186Z", + "start_time": "2025-06-11T22:13:32.362868Z" + } + }, + "cell_type": "code", + "source": "m.fs.visualize('HDA-Flowsheet')", + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2025-06-11 16:13:33 [INFO] idaes.idaes_ui.fv.fsvis: Started visualization server\n", + "2025-06-11 16:13:33 [INFO] idaes.idaes_ui.fv.fsvis: Loading saved flowsheet from 'HDA-Flowsheet.json'\n", + "2025-06-11 16:13:33 [INFO] idaes.idaes_ui.fv.fsvis: Saving flowsheet to default file 'HDA-Flowsheet.json' in current directory (C:\\Users\\Tanner\\Documents\\git\\examples\\idaes_examples\\notebooks\\docs\\tut\\core)\n", + "2025-06-11 16:13:33 [INFO] idaes.idaes_ui.fv.fsvis: Flowsheet visualization at: http://localhost:49167/app?id=HDA-Flowsheet\n" + ] + }, + { + "data": { + "text/plain": [ + "VisualizeResult(store=, port=49167, server=, save_diagram=>)" + ] + }, + "execution_count": 58, + "metadata": {}, + "output_type": "execute_result" + } + ], + "execution_count": 58 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "Otherwise, we can run the `m.fs.report()` method to see a full summary of the solved flowsheet. It is recomended to adjust the width of the output as much as possible for the cleanest display." + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:33.985892Z", + "start_time": "2025-06-11T22:13:33.889113Z" + } + }, + "cell_type": "code", + "source": "m.fs.report()", + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "====================================================================================\n", + "Flowsheet : fs Time: 0.0\n", + "------------------------------------------------------------------------------------\n", + " Stream Table\n", + " Units s01 s02 s03 s04 s05 s06 s07 s08 s09 s10 s11 s12 \n", + " flow_mol_phase_comp ('Liq', 'benzene') mole / second 1.0000e-05 1.0000e-05 2.0008e-05 1.2993e-07 1.2993e-07 1.0000e-08 0.20460 8.0000e-09 8.0000e-09 1.0000e-08 0.062620 2.0000e-09\n", + " flow_mol_phase_comp ('Liq', 'toluene') mole / second 0.30000 1.0000e-05 0.30001 8.4149e-07 8.4149e-07 1.0000e-08 0.062520 8.0000e-09 8.0000e-09 1.0000e-08 0.032257 2.0000e-09\n", + " flow_mol_phase_comp ('Liq', 'methane') mole / second 1.0000e-05 1.0000e-05 2.0008e-05 1.0000e-12 1.0000e-12 1.0000e-08 2.6712e-07 8.0000e-09 8.0000e-09 1.0000e-08 9.4877e-08 2.0000e-09\n", + " flow_mol_phase_comp ('Liq', 'hydrogen') mole / second 1.0000e-05 1.0000e-05 2.0008e-05 1.0000e-12 1.0000e-12 1.0000e-08 2.6712e-07 8.0000e-09 8.0000e-09 1.0000e-08 9.4877e-08 2.0000e-09\n", + " flow_mol_phase_comp ('Vap', 'benzene') mole / second 1.0000e-05 1.0000e-05 0.11934 0.11936 0.35374 0.14915 1.0000e-08 0.11932 0.11932 0.14198 1.0000e-08 0.029829\n", + " flow_mol_phase_comp ('Vap', 'toluene') mole / second 1.0000e-05 1.0000e-05 0.012508 0.31252 0.078129 0.015610 1.0000e-08 0.012488 0.012488 0.030264 1.0000e-08 0.0031219\n", + " flow_mol_phase_comp ('Vap', 'methane') mole / second 1.0000e-05 0.020000 1.0377 1.0377 1.2721 1.2721 1.0000e-08 1.0177 1.0177 1.8224e-07 1.0000e-08 0.25442\n", + " flow_mol_phase_comp ('Vap', 'hydrogen') mole / second 1.0000e-05 0.30000 0.56258 0.56260 0.32821 0.32821 1.0000e-08 0.26257 0.26257 1.8224e-07 1.0000e-08 0.065642\n", + " temperature kelvin 303.20 303.20 314.09 600.00 771.85 325.00 325.00 325.00 325.00 375.00 375.00 325.00\n", + " pressure pascal 3.5000e+05 3.5000e+05 3.5000e+05 3.5000e+05 3.5000e+05 3.5000e+05 3.5000e+05 3.5000e+05 3.5000e+05 1.5000e+05 1.5000e+05 3.5000e+05\n", + "====================================================================================\n" + ] + } + ], + "execution_count": 59 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "What is the total operating cost?" + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:34.015352Z", + "start_time": "2025-06-11T22:13:34.012518Z" + } + }, + "source": [ + "print(\"operating cost = $\", value(m.fs.operating_cost))" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "operating cost = $ 419122.33874473866\n" + ] + } + ], + "execution_count": 60 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": "For this operating cost, what is the amount of benzene we are able to produce and what purity we are able to achieve? We can look at a specific unit models stream table with the same `report()` method." + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:34.197557Z", + "start_time": "2025-06-11T22:13:34.174732Z" + } + }, + "source": [ + "m.fs.F102.report()\n", + "\n", + "print()\n", + "print(\"benzene purity = \", value(m.fs.purity))" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "====================================================================================\n", + "Unit : fs.F102 Time: 0.0\n", + "------------------------------------------------------------------------------------\n", + " Unit Performance\n", + "\n", + " Variables: \n", + "\n", + " Key : Value : Units : Fixed : Bounds\n", + " Heat Duty : 7352.5 : watt : False : (None, None)\n", + " Pressure Change : -2.0000e+05 : pascal : True : (None, None)\n", + "\n", + "------------------------------------------------------------------------------------\n", + " Stream Table\n", + " Units Inlet Vapor Outlet Liquid Outlet\n", + " flow_mol_phase_comp ('Liq', 'benzene') mole / second 0.20460 1.0000e-08 0.062620 \n", + " flow_mol_phase_comp ('Liq', 'toluene') mole / second 0.062520 1.0000e-08 0.032257 \n", + " flow_mol_phase_comp ('Liq', 'methane') mole / second 2.6712e-07 1.0000e-08 9.4877e-08 \n", + " flow_mol_phase_comp ('Liq', 'hydrogen') mole / second 2.6712e-07 1.0000e-08 9.4877e-08 \n", + " flow_mol_phase_comp ('Vap', 'benzene') mole / second 1.0000e-08 0.14198 1.0000e-08 \n", + " flow_mol_phase_comp ('Vap', 'toluene') mole / second 1.0000e-08 0.030264 1.0000e-08 \n", + " flow_mol_phase_comp ('Vap', 'methane') mole / second 1.0000e-08 1.8224e-07 1.0000e-08 \n", + " flow_mol_phase_comp ('Vap', 'hydrogen') mole / second 1.0000e-08 1.8224e-07 1.0000e-08 \n", + " temperature kelvin 325.00 375.00 375.00 \n", + " pressure pascal 3.5000e+05 1.5000e+05 1.5000e+05 \n", + "====================================================================================\n", + "\n", + "benzene purity = 0.8242962943922332\n" + ] + } + ], + "execution_count": 62 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": "Next, let's look at how much benzene we are losing with the light gases out of F101. IDAES has tools for creating stream tables based on the `Arcs` and/or `Ports` in a flowsheet. Let us create and print a simple stream table showing the stream leaving the reactor and the vapor stream from F101." + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:34.253529Z", + "start_time": "2025-06-11T22:13:34.238100Z" + } + }, + "source": [ + "from idaes.core.util.tables import (\n", + " create_stream_table_dataframe,\n", + " stream_table_dataframe_to_string,\n", + ")\n", + "\n", + "st = create_stream_table_dataframe({\"Reactor\": m.fs.s05, \"Light Gases\": m.fs.s06})\n", + "print(stream_table_dataframe_to_string(st))" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " Units Reactor Light Gases\n", + "flow_mol_phase_comp ('Liq', 'benzene') mole / second 1.2993e-07 1.0000e-08 \n", + "flow_mol_phase_comp ('Liq', 'toluene') mole / second 8.4149e-07 1.0000e-08 \n", + "flow_mol_phase_comp ('Liq', 'methane') mole / second 1.0000e-12 1.0000e-08 \n", + "flow_mol_phase_comp ('Liq', 'hydrogen') mole / second 1.0000e-12 1.0000e-08 \n", + "flow_mol_phase_comp ('Vap', 'benzene') mole / second 0.35374 0.14915 \n", + "flow_mol_phase_comp ('Vap', 'toluene') mole / second 0.078129 0.015610 \n", + "flow_mol_phase_comp ('Vap', 'methane') mole / second 1.2721 1.2721 \n", + "flow_mol_phase_comp ('Vap', 'hydrogen') mole / second 0.32821 0.32821 \n", + "temperature kelvin 771.85 325.00 \n", + "pressure pascal 3.5000e+05 3.5000e+05 \n" + ] + } + ], + "execution_count": 64 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 8 Optimization\n", + "\n", + "\n", + "We saw from the results above that the total operating cost for the base case was $419,122 per year. We are producing 0.142 mol/s of benzene at a purity of 82\\%. However, we are losing around 42\\% of benzene in F101 vapor outlet stream. \n", + "\n", + "Let us try to minimize this cost such that:\n", + "- we are producing at least 0.15 mol/s of benzene in F102 vapor outlet i.e. our product stream\n", + "- purity of benzene i.e. the mole fraction of benzene in F102 vapor outlet is at least 80%\n", + "- restricting the benzene loss in F101 vapor outlet to less than 20%\n", + "\n", + "For this problem, our decision variables are as follows:\n", + "- H101 outlet temperature\n", + "- R101 cooling duty provided\n", + "- F101 outlet temperature\n", + "- F102 outlet temperature\n", + "- F102 deltaP in the flash tank\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let us declare our objective function for this problem. " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:34.276719Z", + "start_time": "2025-06-11T22:13:34.271416Z" + } + }, + "source": [ + "m.fs.objective = Objective(expr=m.fs.operating_cost)" + ], + "outputs": [], + "execution_count": 65 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, we need to unfix the decision variables as we had solved a square problem (degrees of freedom = 0) until now. " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:34.337438Z", + "start_time": "2025-06-11T22:13:34.332415Z" + } + }, + "source": [ + "m.fs.H101.outlet.temperature.unfix()\n", + "m.fs.R101.heat_duty.unfix()\n", + "m.fs.F101.vap_outlet.temperature.unfix()\n", + "m.fs.F102.vap_outlet.temperature.unfix()" + ], + "outputs": [], + "execution_count": 66 + }, + { + "cell_type": "markdown", + "metadata": { + "tags": [ + "exercise" + ] + }, + "source": [ + "
    \n", + "Inline Exercise:\n", + "Let us now unfix the remaining variable which is F102 pressure drop (F102.deltaP) \n", + "\n", + "Use Shift+Enter to run the cell once you have typed in your code. \n", + "
    \n", + "\n" + ] + }, + { + "cell_type": "code", + "metadata": { + "tags": [ + "exercise" + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:34.399247Z", + "start_time": "2025-06-11T22:13:34.395221Z" + } + }, + "source": [ + "# Todo: Unfix deltaP for F102" + ], + "outputs": [], + "execution_count": 67 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next, we need to set bounds on these decision variables to values shown below:\n", + "\n", + " - H101 outlet temperature [500, 600] K\n", + " - R101 outlet temperature [600, 800] K\n", + " - F101 outlet temperature [298, 450] K\n", + " - F102 outlet temperature [298, 450] K\n", + " - F102 outlet pressure [105000, 110000] Pa\n", + "\n", + "Let us first set the variable bound for the H101 outlet temperature as shown below:" + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:34.589902Z", + "start_time": "2025-06-11T22:13:34.585528Z" + } + }, + "source": [ + "m.fs.H101.outlet.temperature[0].setlb(500)\n", + "m.fs.H101.outlet.temperature[0].setub(600)" + ], + "outputs": [], + "execution_count": 70 + }, + { + "cell_type": "markdown", + "metadata": { + "tags": [ + "exercise" + ] + }, + "source": [ + "
    \n", + "Inline Exercise:\n", + "Now, set the variable bound for the R101 outlet temperature.\n", + "\n", + "Use Shift+Enter to run the cell once you have typed in your code. \n", + "
    " + ] + }, + { + "cell_type": "code", + "metadata": { + "tags": [ + "exercise" + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:34.678092Z", + "start_time": "2025-06-11T22:13:34.673513Z" + } + }, + "source": [ + "# Todo: Set the bounds for reactor outlet temperature" + ], + "outputs": [], + "execution_count": 71 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let us fix the bounds for the rest of the decision variables. " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:34.763569Z", + "start_time": "2025-06-11T22:13:34.759916Z" + } + }, + "source": [ + "m.fs.F101.vap_outlet.temperature[0].setlb(298.0)\n", + "m.fs.F101.vap_outlet.temperature[0].setub(450.0)\n", + "m.fs.F102.vap_outlet.temperature[0].setlb(298.0)\n", + "m.fs.F102.vap_outlet.temperature[0].setub(450.0)\n", + "m.fs.F102.vap_outlet.pressure[0].setlb(105000)\n", + "m.fs.F102.vap_outlet.pressure[0].setub(110000)" + ], + "outputs": [], + "execution_count": 73 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, the only things left to define are our constraints on overhead loss in F101, product flow rate and purity in F102. Let us first look at defining a constraint for the overhead loss in F101 where we are restricting the benzene leaving the vapor stream to less than 20 \\% of the benzene available in the reactor outlet. " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:34.806470Z", + "start_time": "2025-06-11T22:13:34.801455Z" + } + }, + "source": [ + "m.fs.overhead_loss = Constraint(\n", + " expr=m.fs.F101.vap_outlet.flow_mol_phase_comp[0, \"Vap\", \"benzene\"]\n", + " <= 0.20 * m.fs.R101.outlet.flow_mol_phase_comp[0, \"Vap\", \"benzene\"]\n", + ")" + ], + "outputs": [], + "execution_count": 74 + }, + { + "cell_type": "markdown", + "metadata": { + "tags": [ + "exercise" + ] + }, + "source": [ + "
    \n", + "Inline Exercise:\n", + "Now, add the constraint such that we are producing at least 0.15 mol/s of benzene in the product stream which is the vapor outlet of F102. Let us name this constraint as m.fs.product_flow. \n", + "\n", + "Use Shift+Enter to run the cell once you have typed in your code. \n", + "
    " + ] + }, + { + "cell_type": "code", + "metadata": { + "tags": [ + "exercise" + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:34.837136Z", + "start_time": "2025-06-11T22:13:34.832491Z" + } + }, + "source": [ + "# Todo: Add minimum product flow constraint" + ], + "outputs": [], + "execution_count": 75 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let us add the final constraint on product purity or the mole fraction of benzene in the product stream such that it is at least greater than 80%. " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:34.917217Z", + "start_time": "2025-06-11T22:13:34.912683Z" + } + }, + "source": [ + "m.fs.product_purity = Constraint(expr=m.fs.purity >= 0.80)" + ], + "outputs": [], + "execution_count": 77 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "We have now defined the optimization problem and we are now ready to solve this problem. \n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:35.209235Z", + "start_time": "2025-06-11T22:13:34.962108Z" + } + }, + "source": [ + "results = solver.solve(m, tee=True)" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "WARNING: model contains export suffix 'scaling_factor' that contains 25\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "Ipopt 3.13.2: nlp_scaling_method=user-scaling\n", + "tol=1e-08\n", + "max_iter=500\n", + "option_file_name=C:\\Users\\Tanner\\AppData\\Local\\Temp\\tmpd6aww4bg_ipopt.opt\n", + "\n", + "Using option file \"C:\\Users\\Tanner\\AppData\\Local\\Temp\\tmpd6aww4bg_ipopt.opt\".\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...: 1191\n", + "Number of nonzeros in inequality constraint Jacobian.: 5\n", + "Number of nonzeros in Lagrangian Hessian.............: 1065\n", + "\n", + "Total number of variables............................: 395\n", + " variables with only lower bounds: 0\n", + " variables with lower and upper bounds: 199\n", + " variables with only upper bounds: 0\n", + "Total number of equality constraints.................: 390\n", + "Total number of inequality constraints...............: 3\n", + " inequality constraints with only lower bounds: 2\n", + " inequality constraints with lower and upper bounds: 0\n", + " inequality constraints with only upper bounds: 1\n", + "\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 0 4.1912234e+05 2.99e+05 6.94e+00 -1.0 0.00e+00 - 0.00e+00 0.00e+00 0\n", + " 1 4.1133558e+05 2.94e+05 6.94e+00 -1.0 6.60e+07 - 2.90e-06 1.05e-05f 1\n", + " 2 3.2484037e+05 1.80e+06 6.94e+00 -1.0 2.33e+09 - 2.95e-07 4.87e-06f 1\n", + " 3 3.0318192e+05 1.92e+06 6.94e+00 -1.0 6.90e+08 - 1.70e-05 4.13e-06f 1\n", + " 4 3.0263181e+05 1.92e+06 2.20e+01 -1.0 1.43e+07 - 8.92e-05 1.73e-05f 1\n", + " 5 3.0137102e+05 1.92e+06 4.38e+01 -1.0 8.74e+06 - 6.63e-05 1.11e-04f 1\n", + " 6 3.0058759e+05 1.92e+06 1.37e+03 -1.0 2.21e+07 - 8.59e-06 2.17e-04f 1\n", + " 7 3.0058113e+05 1.92e+06 7.51e+04 -1.0 3.24e+05 - 2.49e-01 1.77e-04f 1\n", + " 8 3.0317331e+05 1.38e+06 2.08e+05 -1.0 4.00e+04 - 9.62e-01 2.82e-01h 1\n", + " 9 3.0372038e+05 1.26e+06 1.91e+05 -1.0 2.87e+04 - 1.50e-01 8.31e-02h 1\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 10 3.0372988e+05 1.26e+06 9.32e+05 -1.0 2.63e+04 - 1.00e+00 1.51e-03h 1\n", + " 11 3.0386427e+05 1.24e+06 1.90e+07 -1.0 2.63e+04 - 1.00e+00 2.03e-02h 2\n", + " 12 3.0393928e+05 1.22e+06 7.46e+08 -1.0 2.58e+04 - 1.00e+00 1.15e-02h 2\n", + " 13 3.0397909e+05 1.21e+06 4.87e+10 -1.0 2.55e+04 - 1.00e+00 6.13e-03h 2\n", + " 14 3.0399961e+05 1.21e+06 5.05e+12 -1.0 2.53e+04 - 1.00e+00 3.17e-03h 2\n", + " 15r 3.0399961e+05 1.21e+06 1.00e+03 -0.5 0.00e+00 - 0.00e+00 3.97e-07R 14\n", + " 16r 3.0399912e+05 1.20e+06 2.84e+04 -0.5 8.47e+03 - 4.43e-03 2.34e-03f 1\n", + " 17 3.0399884e+05 1.20e+06 5.51e+05 -1.0 2.92e+04 - 2.81e-01 2.34e-06f 2\n", + " 18 3.0402874e+05 1.19e+06 4.77e+07 -1.0 2.52e+04 - 9.90e-01 4.64e-03h 1\n", + " 19 3.0402891e+05 1.19e+06 3.49e+10 -1.0 2.51e+04 - 1.00e+00 2.64e-05h 2\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 20r 3.0402891e+05 1.19e+06 1.00e+03 -0.8 0.00e+00 - 0.00e+00 3.08e-07R 8\n", + " 21r 3.0403760e+05 1.15e+06 2.58e+04 -0.8 3.66e+03 - 5.75e-03 3.60e-03f 1\n", + " 22r 3.0404498e+05 1.08e+06 3.87e+04 -0.8 3.65e+03 - 7.70e-03 6.44e-03f 1\n", + " 23r 3.0405749e+05 9.30e+05 4.54e+04 -0.8 3.63e+03 - 1.80e-02 1.60e-02f 1\n", + " 24r 3.0406463e+05 7.06e+05 4.01e+04 -0.8 3.57e+03 - 5.64e-02 3.23e-02f 1\n", + " 25r 3.0405635e+05 4.00e+05 4.80e+04 -0.8 3.45e+03 - 1.05e-01 8.20e-02f 1\n", + " 26r 3.0403684e+05 2.04e+05 2.80e+04 -0.8 3.17e+03 - 3.66e-01 1.49e-01f 1\n", + " 27r 3.0401302e+05 2.95e+04 2.21e+04 -0.8 2.70e+03 - 1.94e-01 6.28e-01f 1\n", + " 28 3.0386768e+05 2.95e+04 1.26e+02 -1.0 3.39e+06 - 4.53e-04 3.54e-06f 1\n", + " 29 3.0384876e+05 2.95e+04 8.45e+03 -1.0 1.90e+05 - 3.01e-02 3.65e-04f 1\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 30 3.0412267e+05 2.86e+04 2.69e+05 -1.0 2.35e+04 - 9.90e-01 3.11e-02h 1\n", + " 31 3.0843430e+05 5.11e+05 1.73e+05 -1.0 2.16e+04 - 1.00e+00 4.99e-01h 1\n", + " 32 3.1273887e+05 6.10e+05 5.58e+08 -1.0 1.09e+04 - 1.00e+00 9.90e-01h 1\n", + " 33 3.1276263e+05 3.11e+05 1.78e+08 -1.0 1.01e+03 - 1.00e+00 4.97e-01h 2\n", + " 34 3.1278673e+05 7.74e+02 1.15e+08 -1.0 6.27e+02 - 1.00e+00 9.97e-01H 1\n", + " 35 3.1278674e+05 6.28e+02 5.45e+04 -1.0 1.81e+02 - 1.00e+00 1.00e+00h 1\n", + " 36 3.1278674e+05 8.80e-03 2.62e+00 -1.0 7.71e-01 - 1.00e+00 1.00e+00h 1\n", + " 37 3.1278634e+05 3.48e-05 1.58e+05 -5.7 1.36e+00 - 1.00e+00 1.00e+00f 1\n", + " 38 3.1278634e+05 3.73e-08 6.73e-02 -5.7 2.20e-04 - 1.00e+00 1.00e+00f 1\n", + " 39 3.1278634e+05 2.24e-08 4.40e-05 -5.7 8.48e-04 - 1.00e+00 1.00e+00h 1\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 40 3.1278634e+05 6.71e-08 6.16e-05 -8.6 1.75e-03 - 1.00e+00 1.00e+00h 1\n", + " 41 3.1278634e+05 3.73e-08 4.40e-05 -9.0 8.38e-04 - 1.00e+00 1.00e+00h 1\n", + " 42 3.1278634e+05 5.96e-08 4.40e-05 -9.0 1.47e-03 - 1.00e+00 1.00e+00h 1\n", + " 43 3.1278634e+05 1.49e-08 4.40e-05 -9.0 5.14e-08 - 1.00e+00 1.00e+00h 1\n", + " 44 3.1278634e+05 1.49e-08 4.40e-05 -9.0 7.39e-11 - 1.00e+00 2.44e-04h 13\n", + " 45 3.1278634e+05 1.49e-08 4.40e-05 -9.0 6.89e-11 - 1.00e+00 5.00e-01h 2\n", + " 46 3.1278634e+05 1.49e-08 4.40e-05 -9.0 7.02e-11 - 1.00e+00 1.00e+00h 1\n", + " 47 3.1278634e+05 1.49e-08 4.40e-05 -9.0 6.71e-11 - 1.00e+00 5.00e-01h 2\n", + " 48 3.1278634e+05 2.98e-08 4.40e-05 -9.0 5.67e-11 - 1.00e+00 1.00e+00h 1\n", + " 49 3.1278634e+05 2.98e-08 4.40e-05 -9.0 5.52e-11 - 1.00e+00 5.00e-01h 2\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 50 3.1278634e+05 2.98e-08 4.40e-05 -9.0 4.16e-11 - 1.00e+00 5.00e-01h 2\n", + " 51 3.1278634e+05 2.98e-08 4.40e-05 -9.0 3.65e-11 - 1.00e+00 1.25e-01h 4\n", + "\n", + "Number of Iterations....: 51\n", + "\n", + " (scaled) (unscaled)\n", + "Objective...............: 3.1278633834066673e+05 3.1278633834066673e+05\n", + "Dual infeasibility......: 4.3988857412862944e-05 6.3035118071624454e-05\n", + "Constraint violation....: 2.0579515874459978e-11 2.9802322387695312e-08\n", + "Complementarity.........: 9.2191617461622074e-10 9.2191617461622074e-10\n", + "Overall NLP error.......: 1.6340396115140405e-08 6.3035118071624454e-05\n", + "\n", + "\n", + "Number of objective function evaluations = 141\n", + "Number of objective gradient evaluations = 47\n", + "Number of equality constraint evaluations = 141\n", + "Number of inequality constraint evaluations = 141\n", + "Number of equality constraint Jacobian evaluations = 55\n", + "Number of inequality constraint Jacobian evaluations = 55\n", + "Number of Lagrangian Hessian evaluations = 52\n", + "Total CPU secs in IPOPT (w/o function evaluations) = 0.077\n", + "Total CPU secs in NLP function evaluations = 0.010\n", + "\n", + "EXIT: Solved To Acceptable Level.\n" + ] + } + ], + "execution_count": 78 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 8.1 Optimization Results\n", + "\n", + "Display the results and product specifications" + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:35.321235Z", + "start_time": "2025-06-11T22:13:35.245545Z" + } + }, + "source": [ + "print(\"operating cost = $\", value(m.fs.operating_cost))\n", + "\n", + "print()\n", + "print(\"Product flow rate and purity in F102\")\n", + "\n", + "m.fs.F102.report()\n", + "\n", + "print()\n", + "print(\"benzene purity = \", value(m.fs.purity))\n", + "\n", + "print()\n", + "print(\"Overhead loss in F101\")\n", + "m.fs.F101.report()" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "operating cost = $ 312786.33834066667\n", + "\n", + "Product flow rate and purity in F102\n", + "\n", + "====================================================================================\n", + "Unit : fs.F102 Time: 0.0\n", + "------------------------------------------------------------------------------------\n", + " Unit Performance\n", + "\n", + " Variables: \n", + "\n", + " Key : Value : Units : Fixed : Bounds\n", + " Heat Duty : 8377.0 : watt : False : (None, None)\n", + " Pressure Change : -2.4500e+05 : pascal : False : (None, None)\n", + "\n", + "------------------------------------------------------------------------------------\n", + " Stream Table\n", + " Units Inlet Vapor Outlet Liquid Outlet\n", + " flow_mol_phase_comp ('Liq', 'benzene') mole / second 0.21743 1.0000e-08 0.067425 \n", + " flow_mol_phase_comp ('Liq', 'toluene') mole / second 0.070695 1.0000e-08 0.037507 \n", + " flow_mol_phase_comp ('Liq', 'methane') mole / second 2.8812e-07 1.0000e-08 1.0493e-07 \n", + " flow_mol_phase_comp ('Liq', 'hydrogen') mole / second 2.8812e-07 1.0000e-08 1.0493e-07 \n", + " flow_mol_phase_comp ('Vap', 'benzene') mole / second 1.0000e-08 0.15000 1.0000e-08 \n", + " flow_mol_phase_comp ('Vap', 'toluene') mole / second 1.0000e-08 0.033189 1.0000e-08 \n", + " flow_mol_phase_comp ('Vap', 'methane') mole / second 1.0000e-08 1.9319e-07 1.0000e-08 \n", + " flow_mol_phase_comp ('Vap', 'hydrogen') mole / second 1.0000e-08 1.9319e-07 1.0000e-08 \n", + " temperature kelvin 301.88 362.93 362.93 \n", + " pressure pascal 3.5000e+05 1.0500e+05 1.0500e+05 \n", + "====================================================================================\n", + "\n", + "benzene purity = 0.8188276578115882\n", + "\n", + "Overhead loss in F101\n", + "\n", + "====================================================================================\n", + "Unit : fs.F101 Time: 0.0\n", + "------------------------------------------------------------------------------------\n", + " Unit Performance\n", + "\n", + " Variables: \n", + "\n", + " Key : Value : Units : Fixed : Bounds\n", + " Heat Duty : -56353. : watt : False : (None, None)\n", + " Pressure Change : 0.0000 : pascal : True : (None, None)\n", + "\n", + "------------------------------------------------------------------------------------\n", + " Stream Table\n", + " Units Inlet Vapor Outlet Liquid Outlet\n", + " flow_mol_phase_comp ('Liq', 'benzene') mole / second 4.3534e-08 1.0000e-08 0.21743 \n", + " flow_mol_phase_comp ('Liq', 'toluene') mole / second 7.5866e-07 1.0000e-08 0.070695 \n", + " flow_mol_phase_comp ('Liq', 'methane') mole / second 1.0000e-12 1.0000e-08 2.8812e-07 \n", + " flow_mol_phase_comp ('Liq', 'hydrogen') mole / second 1.0000e-12 1.0000e-08 2.8812e-07 \n", + " flow_mol_phase_comp ('Vap', 'benzene') mole / second 0.27178 0.054356 1.0000e-08 \n", + " flow_mol_phase_comp ('Vap', 'toluene') mole / second 0.076085 0.0053908 1.0000e-08 \n", + " flow_mol_phase_comp ('Vap', 'methane') mole / second 1.2414 1.2414 1.0000e-08 \n", + " flow_mol_phase_comp ('Vap', 'hydrogen') mole / second 0.35887 0.35887 1.0000e-08 \n", + " temperature kelvin 696.11 301.88 301.88 \n", + " pressure pascal 3.5000e+05 3.5000e+05 3.5000e+05 \n", + "====================================================================================\n" + ] + } + ], + "execution_count": 80 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Display optimal values for the decision variables" + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:35.405538Z", + "start_time": "2025-06-11T22:13:35.398221Z" + } + }, + "source": [ + "print(f'''Optimal Values:\n", + "\n", + "H101 outlet temperature = {value(m.fs.H101.outlet.temperature[0]):.3f} K\n", + "\n", + "R101 outlet temperature = {value(m.fs.R101.outlet.temperature[0]):.3f} K\n", + "\n", + "F101 outlet temperature = {value(m.fs.F101.vap_outlet.temperature[0]):.3f} K\n", + "\n", + "F102 outlet temperature = {value(m.fs.F102.vap_outlet.temperature[0]):.3f} K\n", + "F102 outlet pressure = {value(m.fs.F102.vap_outlet.pressure[0]):.3f} Pa\n", + "''')" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Optimal Values:\n", + "\n", + "H101 outlet temperature = 500.000 K\n", + "\n", + "R101 outlet temperature = 696.112 K\n", + "\n", + "F101 outlet temperature = 301.878 K\n", + "\n", + "F102 outlet temperature = 362.935 K\n", + "F102 outlet pressure = 105000.000 Pa\n", + "\n" + ] + } + ], + "execution_count": 82 + } + ], + "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.8.12" + } + }, + "nbformat": 4, + "nbformat_minor": 3 } \ No newline at end of file diff --git a/idaes_examples/notebooks/docs/tut/core/hda_flowsheet_solution.ipynb b/idaes_examples/notebooks/docs/tut/core/hda_flowsheet_solution.ipynb index 6f8b47f4..7e8b3ff6 100644 --- a/idaes_examples/notebooks/docs/tut/core/hda_flowsheet_solution.ipynb +++ b/idaes_examples/notebooks/docs/tut/core/hda_flowsheet_solution.ipynb @@ -1,1483 +1,3043 @@ { - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "header", - "hide-cell" - ] - }, - "outputs": [], - "source": [ - "###############################################################################\n", - "# The Institute for the Design of Advanced Energy Systems Integrated Platform\n", - "# Framework (IDAES IP) was produced under the DOE Institute for the\n", - "# Design of Advanced Energy Systems (IDAES).\n", - "#\n", - "# Copyright (c) 2018-2023 by the software owners: The Regents of the\n", - "# University of California, through Lawrence Berkeley National Laboratory,\n", - "# National Technology & Engineering Solutions of Sandia, LLC, Carnegie Mellon\n", - "# University, West Virginia University Research Corporation, et al.\n", - "# All rights reserved. Please see the files COPYRIGHT.md and LICENSE.md\n", - "# for full copyright and license information.\n", - "###############################################################################" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "# HDA Flowsheet Simulation and Optimization\n", - "\n", - "Author: Jaffer Ghouse \n", - "Maintainer: Brandon Paul \n", - "Updated: 2023-06-01 \n", - "\n", - "## Learning outcomes\n", - "\n", - "\n", - "- Construct a steady-state flowsheet using the IDAES unit model library\n", - "- Connecting unit models in a flowsheet using Arcs\n", - "- Using the SequentialDecomposition tool to initialize a flowsheet with recycle\n", - "- Fomulate and solve an optimization problem\n", - " - Defining an objective function\n", - " - Setting variable bounds\n", - " - Adding additional constraints \n", - "\n", - "\n", - "## Problem Statement\n", - "\n", - "Hydrodealkylation is a chemical reaction that often involves reacting\n", - "an aromatic hydrocarbon in the presence of hydrogen gas to form a\n", - "simpler aromatic hydrocarbon devoid of functional groups. In this\n", - "example, toluene will be reacted with hydrogen gas at high temperatures\n", - " to form benzene via the following reaction:\n", - "\n", - "**C6H5CH3 + H2 → C6H6 + CH4**\n", - "\n", - "\n", - "This reaction is often accompanied by an equilibrium side reaction\n", - "which forms diphenyl, which we will neglect for this example.\n", - "\n", - "This example is based on the 1967 AIChE Student Contest problem as\n", - "present by Douglas, J.M., Chemical Design of Chemical Processes, 1988,\n", - "McGraw-Hill.\n", - "\n", - "The flowsheet that we will be using for this module is shown below with the stream conditions. We will be processing toluene and hydrogen to produce at least 370 TPY of benzene. As shown in the flowsheet, there are two flash tanks, F101 to separate out the non-condensibles and F102 to further separate the benzene-toluene mixture to improve the benzene purity. Note that typically a distillation column is required to obtain high purity benzene but that is beyond the scope of this workshop. The non-condensibles separated out in F101 will be partially recycled back to M101 and the rest will be either purged or combusted for power generation.We will assume ideal gas for this flowsheet. The properties required for this module are available in the same directory:\n", - "\n", - "- hda_ideal_VLE.py\n", - "- hda_reaction.py\n", - "\n", - "The state variables chosen for the property package are **flows of component by phase, temperature and pressure**. The components considered are: **toluene, hydrogen, benzene and methane**. Therefore, every stream has 8 flow variables, 1 temperature and 1 pressure variable. \n", - "\n", - "![](HDA_flowsheet.png)\n", - "\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Importing required pyomo and idaes components\n", - "\n", - "\n", - "To construct a flowsheet, we will need several components from the pyomo and idaes package. Let us first import the following components from Pyomo:\n", - "- Constraint (to write constraints)\n", - "- Var (to declare variables)\n", - "- ConcreteModel (to create the concrete model object)\n", - "- Expression (to evaluate values as a function of variables defined in the model)\n", - "- Objective (to define an objective function for optimization)\n", - "- SolverFactory (to solve the problem)\n", - "- TransformationFactory (to apply certain transformations)\n", - "- Arc (to connect two unit models)\n", - "- SequentialDecomposition (to initialize the flowsheet in a sequential mode)\n", - "\n", - "For further details on these components, please refer to the pyomo documentation: https://pyomo.readthedocs.io/en/stable/\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from pyomo.environ import (\n", - " Constraint,\n", - " Var,\n", - " ConcreteModel,\n", - " Expression,\n", - " Objective,\n", - " SolverFactory,\n", - " TransformationFactory,\n", - " value,\n", - ")\n", - "from pyomo.network import Arc, SequentialDecomposition" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "From idaes, we will be needing the FlowsheetBlock and the following unit models:\n", - "- Mixer\n", - "- Heater\n", - "- StoichiometricReactor\n", - "- **Flash**\n", - "- Separator (splitter) \n", - "- PressureChanger" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from idaes.core import FlowsheetBlock" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from idaes.models.unit_models import (\n", - " PressureChanger,\n", - " Mixer,\n", - " Separator as Splitter,\n", - " Heater,\n", - " StoichiometricReactor,\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "
    \n", - "Inline Exercise:\n", - "Now, import the remaining unit models highlighted in blue above and run the cell using `Shift+Enter` after typing in the code. \n", - "
    \n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "exercise" - ] - }, - "outputs": [], - "source": [ - "# Todo: import flash model from idaes.models.unit_models" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "solution" - ] - }, - "outputs": [], - "source": [ - "# Todo: import flash model from idaes.models.unit_models\n", - "from idaes.models.unit_models import Flash" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We will also be needing some utility tools to put together the flowsheet and calculate the degrees of freedom. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from idaes.models.unit_models.pressure_changer import ThermodynamicAssumption\n", - "from idaes.core.util.model_statistics import degrees_of_freedom\n", - "\n", - "# Import idaes logger to set output levels\n", - "import idaes.logger as idaeslog\n", - "from idaes.core.solvers import get_solver\n", - "from idaes.core.util.exceptions import InitializationError" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Importing required thermo and reaction package\n", - "\n", - "The final set of imports are to import the thermo and reaction package for the HDA process. We have created a custom thermo package that assumes Ideal Gas with support for VLE. \n", - "\n", - "The reaction package here is very simple as we will be using only a StochiometricReactor and the reaction package consists of the stochiometric coefficients for the reaction and the parameter for the heat of reaction. \n", - "\n", - "Let us import the following modules and they are in the same directory as this jupyter notebook:\n", - "
      \n", - "
    • hda_ideal_VLE as thermo_props
    • \n", - "
    • hda_reaction as reaction_props
    • \n", - "
    \n", - "
" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from idaes_examples.mod.hda import hda_ideal_VLE as thermo_props\n", - "from idaes_examples.mod.hda import hda_reaction as reaction_props" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Constructing the Flowsheet\n", - "\n", - "We have now imported all the components, unit models, and property modules we need to construct a flowsheet. Let us create a ConcreteModel and add the flowsheet block as we did in module 1. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m = ConcreteModel()\n", - "m.fs = FlowsheetBlock(dynamic=False)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We now need to add the property packages to the flowsheet. Unlike Module 1, where we only had a thermo property package, for this flowsheet we will also need to add a reaction property package. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.thermo_params = thermo_props.HDAParameterBlock()\n", - "m.fs.reaction_params = reaction_props.HDAReactionParameterBlock(\n", - " property_package=m.fs.thermo_params\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Adding Unit Models\n", - "\n", - "Let us start adding the unit models we have imported to the flowsheet. Here, we are adding the Mixer (assigned a name M101) and a Heater (assigned a name H101). Note that, all unit models need to be given a property package argument. In addition to that, there are several arguments depending on the unit model, please refer to the documentation for more details (https://idaes-pse.readthedocs.io/en/stable/reference_guides/model_libraries/generic/unit_models/index.html). For example, the Mixer unit model here is given a `list` consisting of names to the three inlets. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.M101 = Mixer(\n", - " property_package=m.fs.thermo_params,\n", - " inlet_list=[\"toluene_feed\", \"hydrogen_feed\", \"vapor_recycle\"],\n", - ")\n", - "\n", - "m.fs.H101 = Heater(\n", - " property_package=m.fs.thermo_params,\n", - " has_pressure_change=False,\n", - " has_phase_equilibrium=True,\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "
\n", - "Inline Exercise:\n", - "Let us now add the StoichiometricReactor(assign the name R101) and pass the following arguments:\n", - "
    \n", - "
  • \"property_package\": m.fs.thermo_params
  • \n", - "
  • \"reaction_package\": m.fs.reaction_params
  • \n", - "
  • \"has_heat_of_reaction\": True
  • \n", - "
  • \"has_heat_transfer\": True
  • \n", - "
  • \"has_pressure_change\": False
  • \n", - "
\n", - "
" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "exercise" - ] - }, - "outputs": [], - "source": [ - "# Todo: Add reactor with the specifications above" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "solution" - ] - }, - "outputs": [], - "source": [ - "# Todo: Add reactor with the specifications above\n", - "m.fs.R101 = StoichiometricReactor(\n", - " property_package=m.fs.thermo_params,\n", - " reaction_package=m.fs.reaction_params,\n", - " has_heat_of_reaction=True,\n", - " has_heat_transfer=True,\n", - " has_pressure_change=False,\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let us now add the Flash(assign the name F101) and pass the following arguments:\n", - "
    \n", - "
  • \"property_package\": m.fs.thermo_params
  • \n", - "
  • \"has_heat_transfer\": True
  • \n", - "
  • \"has_pressure_change\": False
  • \n", - "
" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.F101 = Flash(\n", - " property_package=m.fs.thermo_params,\n", - " has_heat_transfer=True,\n", - " has_pressure_change=True,\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let us now add the Splitter(S101), PressureChanger(C101) and the second Flash(F102). " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.S101 = Splitter(\n", - " property_package=m.fs.thermo_params,\n", - " ideal_separation=False,\n", - " outlet_list=[\"purge\", \"recycle\"],\n", - ")\n", - "\n", - "\n", - "m.fs.C101 = PressureChanger(\n", - " property_package=m.fs.thermo_params,\n", - " compressor=True,\n", - " thermodynamic_assumption=ThermodynamicAssumption.isothermal,\n", - ")\n", - "\n", - "m.fs.F102 = Flash(\n", - " property_package=m.fs.thermo_params,\n", - " has_heat_transfer=True,\n", - " has_pressure_change=True,\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Connecting Unit Models using Arcs\n", - "\n", - "We have now added all the unit models we need to the flowsheet. However, we have not yet specified how the units are to be connected. To do this, we will be using the `Arc` which is a pyomo component that takes in two arguments: `source` and `destination`. Let us connect the outlet of the mixer(M101) to the inlet of the heater(H101). " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.s03 = Arc(source=m.fs.M101.outlet, destination=m.fs.H101.inlet)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "![](HDA_flowsheet.png) \n", - "\n", - "
\n", - "Inline Exercise:\n", - "Now, connect the H101 outlet to the R101 inlet using the cell above as a guide. \n", - "
\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "exercise" - ] - }, - "outputs": [], - "source": [ - "# Todo: Connect the H101 outlet to R101 inlet" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "solution" - ] - }, - "outputs": [], - "source": [ - "# Todo: Connect the H101 outlet to R101 inlet\n", - "m.fs.s04 = Arc(source=m.fs.H101.outlet, destination=m.fs.R101.inlet)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We will now be connecting the rest of the flowsheet as shown below. Notice how the outlet names are different for the flash tanks F101 and F102 as they have a vapor and a liquid outlet. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.s05 = Arc(source=m.fs.R101.outlet, destination=m.fs.F101.inlet)\n", - "m.fs.s06 = Arc(source=m.fs.F101.vap_outlet, destination=m.fs.S101.inlet)\n", - "m.fs.s08 = Arc(source=m.fs.S101.recycle, destination=m.fs.C101.inlet)\n", - "m.fs.s09 = Arc(source=m.fs.C101.outlet, destination=m.fs.M101.vapor_recycle)\n", - "m.fs.s10 = Arc(source=m.fs.F101.liq_outlet, destination=m.fs.F102.inlet)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We have now connected the unit model block using the arcs. However, each of these arcs link to ports on the two unit models that are connected. In this case, the ports consist of the state variables that need to be linked between the unit models. Pyomo provides a convenient method to write these equality constraints for us between two ports and this is done as follows:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "TransformationFactory(\"network.expand_arcs\").apply_to(m)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Adding expressions to compute purity and operating costs\n", - "\n", - "In this section, we will add a few Expressions that allows us to evaluate the performance. Expressions provide a convenient way of calculating certain values that are a function of the variables defined in the model. For more details on Expressions, please refer to: https://pyomo.readthedocs.io/en/stable/pyomo_modeling_components/Expressions.html\n", - "\n", - "For this flowsheet, we are interested in computing the purity of the product Benzene stream (i.e. the mole fraction) and the operating cost which is a sum of the cooling and heating cost. " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let us first add an Expression to compute the mole fraction of benzene in the `vap_outlet` of F102 which is our product stream. Please note that the var flow_mol_phase_comp has the index - [time, phase, component]. As this is a steady-state flowsheet, the time index by default is 0. The valid phases are [\"Liq\", \"Vap\"]. Similarly the valid component list is [\"benzene\", \"toluene\", \"hydrogen\", \"methane\"]." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.purity = Expression(\n", - " expr=m.fs.F102.vap_outlet.flow_mol_phase_comp[0, \"Vap\", \"benzene\"]\n", - " / (\n", - " m.fs.F102.vap_outlet.flow_mol_phase_comp[0, \"Vap\", \"benzene\"]\n", - " + m.fs.F102.vap_outlet.flow_mol_phase_comp[0, \"Vap\", \"toluene\"]\n", - " )\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now, let us add an expression to compute the cooling cost assuming a cost of 0.212E-4 $/kW. Note that cooling utility is required for the reactor (R101) and the first flash (F101). " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.cooling_cost = Expression(\n", - " expr=0.212e-7 * (-m.fs.F101.heat_duty[0]) + 0.212e-7 * (-m.fs.R101.heat_duty[0])\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "Now, let us add an expression to compute the heating cost assuming the utility cost as follows:\n", - "
    \n", - "
  • 2.2E-4 dollars/kW for H101
  • \n", - "
  • 1.9E-4 dollars/kW for F102
  • \n", - "
\n", - "Note that the heat duty is in units of watt (J/s). " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.heating_cost = Expression(\n", - " expr=2.2e-7 * m.fs.H101.heat_duty[0] + 1.9e-7 * m.fs.F102.heat_duty[0]\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let us now add an expression to compute the total operating cost per year which is basically the sum of the cooling and heating cost we defined above. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.operating_cost = Expression(\n", - " expr=(3600 * 24 * 365 * (m.fs.heating_cost + m.fs.cooling_cost))\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Fixing feed conditions\n", - "\n", - "Let us first check how many degrees of freedom exist for this flowsheet using the `degrees_of_freedom` tool we imported earlier. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "print(degrees_of_freedom(m))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We will now be fixing the toluene feed stream to the conditions shown in the flowsheet above. Please note that though this is a pure toluene feed, the remaining components are still assigned a very small non-zero value to help with convergence and initializing. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.M101.toluene_feed.flow_mol_phase_comp[0, \"Vap\", \"benzene\"].fix(1e-5)\n", - "m.fs.M101.toluene_feed.flow_mol_phase_comp[0, \"Vap\", \"toluene\"].fix(1e-5)\n", - "m.fs.M101.toluene_feed.flow_mol_phase_comp[0, \"Vap\", \"hydrogen\"].fix(1e-5)\n", - "m.fs.M101.toluene_feed.flow_mol_phase_comp[0, \"Vap\", \"methane\"].fix(1e-5)\n", - "m.fs.M101.toluene_feed.flow_mol_phase_comp[0, \"Liq\", \"benzene\"].fix(1e-5)\n", - "m.fs.M101.toluene_feed.flow_mol_phase_comp[0, \"Liq\", \"toluene\"].fix(0.30)\n", - "m.fs.M101.toluene_feed.flow_mol_phase_comp[0, \"Liq\", \"hydrogen\"].fix(1e-5)\n", - "m.fs.M101.toluene_feed.flow_mol_phase_comp[0, \"Liq\", \"methane\"].fix(1e-5)\n", - "m.fs.M101.toluene_feed.temperature.fix(303.2)\n", - "m.fs.M101.toluene_feed.pressure.fix(350000)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "Similarly, let us fix the hydrogen feed to the following conditions in the next cell:\n", - "
    \n", - "
  • FH2 = 0.30 mol/s
  • \n", - "
  • FCH4 = 0.02 mol/s
  • \n", - "
  • Remaining components = 1e-5 mol/s
  • \n", - "
  • T = 303.2 K
  • \n", - "
  • P = 350000 Pa
  • \n", - "
\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.M101.hydrogen_feed.flow_mol_phase_comp[0, \"Vap\", \"benzene\"].fix(1e-5)\n", - "m.fs.M101.hydrogen_feed.flow_mol_phase_comp[0, \"Vap\", \"toluene\"].fix(1e-5)\n", - "m.fs.M101.hydrogen_feed.flow_mol_phase_comp[0, \"Vap\", \"hydrogen\"].fix(0.30)\n", - "m.fs.M101.hydrogen_feed.flow_mol_phase_comp[0, \"Vap\", \"methane\"].fix(0.02)\n", - "m.fs.M101.hydrogen_feed.flow_mol_phase_comp[0, \"Liq\", \"benzene\"].fix(1e-5)\n", - "m.fs.M101.hydrogen_feed.flow_mol_phase_comp[0, \"Liq\", \"toluene\"].fix(1e-5)\n", - "m.fs.M101.hydrogen_feed.flow_mol_phase_comp[0, \"Liq\", \"hydrogen\"].fix(1e-5)\n", - "m.fs.M101.hydrogen_feed.flow_mol_phase_comp[0, \"Liq\", \"methane\"].fix(1e-5)\n", - "m.fs.M101.hydrogen_feed.temperature.fix(303.2)\n", - "m.fs.M101.hydrogen_feed.pressure.fix(350000)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Fixing unit model specifications\n", - "\n", - "Now that we have fixed our inlet feed conditions, we will now be fixing the operating conditions for the unit models in the flowsheet. Let us set set the H101 outlet temperature to 600 K. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.H101.outlet.temperature.fix(600)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "For the StoichiometricReactor, we have to define the conversion in terms of toluene. This requires us to create a new variable for specifying the conversion and adding a Constraint that defines the conversion with respect to toluene. The second degree of freedom for the reactor is to define the heat duty. In this case, let us assume the reactor to be adiabatic i.e. Q = 0. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.R101.conversion = Var(initialize=0.75, bounds=(0, 1))\n", - "\n", - "m.fs.R101.conv_constraint = Constraint(\n", - " expr=m.fs.R101.conversion * m.fs.R101.inlet.flow_mol_phase_comp[0, \"Vap\", \"toluene\"]\n", - " == (\n", - " m.fs.R101.inlet.flow_mol_phase_comp[0, \"Vap\", \"toluene\"]\n", - " - m.fs.R101.outlet.flow_mol_phase_comp[0, \"Vap\", \"toluene\"]\n", - " )\n", - ")\n", - "\n", - "m.fs.R101.conversion.fix(0.75)\n", - "m.fs.R101.heat_duty.fix(0)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The Flash conditions for F101 can be set as follows. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.F101.vap_outlet.temperature.fix(325.0)\n", - "m.fs.F101.deltaP.fix(0)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "
\n", - "Inline Exercise:\n", - "Set the conditions for Flash F102 to the following conditions:\n", - "
    \n", - "
  • T = 375 K
  • \n", - "
  • deltaP = -200000
  • \n", - "
\n", - "\n", - "Use Shift+Enter to run the cell once you have typed in your code. \n", - "
" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "exercise" - ] - }, - "outputs": [], - "source": [ - "# Todo: Set conditions for Flash F102" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "solution" - ] - }, - "outputs": [], - "source": [ - "m.fs.F102.vap_outlet.temperature.fix(375)\n", - "m.fs.F102.deltaP.fix(-200000)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let us fix the purge split fraction to 20% and the outlet pressure of the compressor is set to 350000 Pa. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.S101.split_fraction[0, \"purge\"].fix(0.2)\n", - "m.fs.C101.outlet.pressure.fix(350000)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "
\n", - "Inline Exercise:\n", - "We have now defined all the feed conditions and the inputs required for the unit models. The system should now have 0 degrees of freedom i.e. should be a square problem. Please check that the degrees of freedom is 0. \n", - "\n", - "Use Shift+Enter to run the cell once you have typed in your code. \n", - "
" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "exercise" - ] - }, - "outputs": [], - "source": [ - "# Todo: print the degrees of freedom" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "solution" - ] - }, - "outputs": [], - "source": [ - "print(degrees_of_freedom(m))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Initialization\n", - "\n", - "\n", - "This section will demonstrate how to use the built-in sequential decomposition tool to initialize our flowsheet.\n", - "\n", - "![](HDA_flowsheet.png) \n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let us first create an object for the SequentialDecomposition and specify our options for this. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "seq = SequentialDecomposition()\n", - "seq.options.select_tear_method = \"heuristic\"\n", - "seq.options.tear_method = \"Wegstein\"\n", - "seq.options.iterLim = 3\n", - "\n", - "# Using the SD tool\n", - "G = seq.create_graph(m)\n", - "heuristic_tear_set = seq.tear_set_arcs(G, method=\"heuristic\")\n", - "order = seq.calculation_order(G)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Which is the tear stream? Display tear set and order" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "for o in heuristic_tear_set:\n", - " print(o.name)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "What sequence did the SD tool determine to solve this flowsheet with the least number of tears? " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "scrolled": true - }, - "outputs": [], - "source": [ - "for o in order:\n", - " print(o[0].name)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - " \n", - "\n", - "![](HDA_tear_stream.png) \n", - "\n", - "\n", - "The SequentialDecomposition tool has determined that the tear stream is the mixer outlet. We will need to provide a reasonable guess for this." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "tear_guesses = {\n", - " \"flow_mol_phase_comp\": {\n", - " (0, \"Vap\", \"benzene\"): 1e-5,\n", - " (0, \"Vap\", \"toluene\"): 1e-5,\n", - " (0, \"Vap\", \"hydrogen\"): 0.30,\n", - " (0, \"Vap\", \"methane\"): 0.02,\n", - " (0, \"Liq\", \"benzene\"): 1e-5,\n", - " (0, \"Liq\", \"toluene\"): 0.30,\n", - " (0, \"Liq\", \"hydrogen\"): 1e-5,\n", - " (0, \"Liq\", \"methane\"): 1e-5,\n", - " },\n", - " \"temperature\": {0: 303},\n", - " \"pressure\": {0: 350000},\n", - "}\n", - "\n", - "# Pass the tear_guess to the SD tool\n", - "seq.set_guesses_for(m.fs.H101.inlet, tear_guesses)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Next, we need to tell the tool how to initialize a particular unit. We will be writing a python function which takes in a \"unit\" and calls the initialize method on that unit. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "def function(unit):\n", - " try:\n", - " initializer = unit.default_initializer()\n", - " initializer.initialize(unit, output_level=idaeslog.INFO)\n", - " except InitializationError:\n", - " solver = get_solver()\n", - " solver.solve(unit)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We are now ready to initialize our flowsheet in a sequential mode. Note that we specifically set the iteration limit to be 5 as we are trying to use this tool only to get a good set of initial values such that IPOPT can then take over and solve this flowsheet for us. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "seq.run(m, function)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "
\n", - "Inline Exercise:\n", - "We have now initialized the flowsheet. Let us run the flowsheet in a simulation mode to look at the results. To do this, complete the last line of code where we pass the model to the solver. You will need to type the following:\n", - " \n", - "results = solver.solve(m, tee=True)\n", - "\n", - "Use Shift+Enter to run the cell once you have typed in your code. \n", - "
\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "exercise" - ] - }, - "outputs": [], - "source": [ - "# Create the solver object\n", - "\n", - "\n", - "# Solve the model" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "solution" - ] - }, - "outputs": [], - "source": [ - "# Create the solver object\n", - "from idaes.core.solvers import get_solver\n", - "\n", - "solver = get_solver()\n", - "\n", - "# Solve the model\n", - "results = solver.solve(m, tee=True)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Analyze the results of the square problem\n", - "\n", - "\n", - "What is the total operating cost? " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "print(\"operating cost = $\", value(m.fs.operating_cost))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "For this operating cost, what is the amount of benzene we are able to produce and what purity we are able to achieve? " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.F102.report()\n", - "\n", - "print()\n", - "print(\"benzene purity = \", value(m.fs.purity))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Next, let's look at how much benzene we are losing with the light gases out of F101. IDAES has tools for creating stream tables based on the `Arcs` and/or `Ports` in a flowsheet. Let us create and print a simple stream table showing the stream leaving the reactor and the vapor stream from F101.\n", - "\n", - "
\n", - "Inline Exercise:\n", - "How much benzene are we losing in the F101 vapor outlet stream?\n", - "
\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from idaes.core.util.tables import (\n", - " create_stream_table_dataframe,\n", - " stream_table_dataframe_to_string,\n", - ")\n", - "\n", - "st = create_stream_table_dataframe({\"Reactor\": m.fs.s05, \"Light Gases\": m.fs.s06})\n", - "print(stream_table_dataframe_to_string(st))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "
\n", - "Inline Exercise:\n", - "You can query additional variables here if you like. \n", - "\n", - "Use Shift+Enter to run the cell once you have typed in your code. \n", - "
\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Optimization\n", - "\n", - "\n", - "We saw from the results above that the total operating cost for the base case was $419,122 per year. We are producing 0.142 mol/s of benzene at a purity of 82\\%. However, we are losing around 42\\% of benzene in F101 vapor outlet stream. \n", - "\n", - "Let us try to minimize this cost such that:\n", - "- we are producing at least 0.15 mol/s of benzene in F102 vapor outlet i.e. our product stream\n", - "- purity of benzene i.e. the mole fraction of benzene in F102 vapor outlet is at least 80%\n", - "- restricting the benzene loss in F101 vapor outlet to less than 20%\n", - "\n", - "For this problem, our decision variables are as follows:\n", - "- H101 outlet temperature\n", - "- R101 cooling duty provided\n", - "- F101 outlet temperature\n", - "- F102 outlet temperature\n", - "- F102 deltaP in the flash tank\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let us declare our objective function for this problem. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.objective = Objective(expr=m.fs.operating_cost)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now, we need to unfix the decision variables as we had solved a square problem (degrees of freedom = 0) until now. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.H101.outlet.temperature.unfix()\n", - "m.fs.R101.heat_duty.unfix()\n", - "m.fs.F101.vap_outlet.temperature.unfix()\n", - "m.fs.F102.vap_outlet.temperature.unfix()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "
\n", - "Inline Exercise:\n", - "Let us now unfix the remaining variable which is F102 pressure drop (F102.deltaP) \n", - "\n", - "Use Shift+Enter to run the cell once you have typed in your code. \n", - "
\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "exercise" - ] - }, - "outputs": [], - "source": [ - "# Todo: Unfix deltaP for F102" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "solution" - ] - }, - "outputs": [], - "source": [ - "# Todo: Unfix deltaP for F102\n", - "m.fs.F102.deltaP.unfix()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Next, we need to set bounds on these decision variables to values shown below:\n", - "\n", - " - H101 outlet temperature [500, 600] K\n", - " - R101 outlet temperature [600, 800] K\n", - " - F101 outlet temperature [298, 450] K\n", - " - F102 outlet temperature [298, 450] K\n", - " - F102 outlet pressure [105000, 110000] Pa\n", - "\n", - "Let us first set the variable bound for the H101 outlet temperature as shown below:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.H101.outlet.temperature[0].setlb(500)\n", - "m.fs.H101.outlet.temperature[0].setub(600)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "
\n", - "Inline Exercise:\n", - "Now, set the variable bound for the R101 outlet temperature.\n", - "\n", - "Use Shift+Enter to run the cell once you have typed in your code. \n", - "
" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "exercise" - ] - }, - "outputs": [], - "source": [ - "# Todo: Set the bounds for reactor outlet temperature" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "solution" - ] - }, - "outputs": [], - "source": [ - "# Todo: Set the bounds for reactor outlet temperature\n", - "m.fs.R101.outlet.temperature[0].setlb(600)\n", - "m.fs.R101.outlet.temperature[0].setub(800)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let us fix the bounds for the rest of the decision variables. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.F101.vap_outlet.temperature[0].setlb(298.0)\n", - "m.fs.F101.vap_outlet.temperature[0].setub(450.0)\n", - "m.fs.F102.vap_outlet.temperature[0].setlb(298.0)\n", - "m.fs.F102.vap_outlet.temperature[0].setub(450.0)\n", - "m.fs.F102.vap_outlet.pressure[0].setlb(105000)\n", - "m.fs.F102.vap_outlet.pressure[0].setub(110000)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now, the only things left to define are our constraints on overhead loss in F101, product flow rate and purity in F102. Let us first look at defining a constraint for the overhead loss in F101 where we are restricting the benzene leaving the vapor stream to less than 20 \\% of the benzene available in the reactor outlet. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.overhead_loss = Constraint(\n", - " expr=m.fs.F101.vap_outlet.flow_mol_phase_comp[0, \"Vap\", \"benzene\"]\n", - " <= 0.20 * m.fs.R101.outlet.flow_mol_phase_comp[0, \"Vap\", \"benzene\"]\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "
\n", - "Inline Exercise:\n", - "Now, add the constraint such that we are producing at least 0.15 mol/s of benzene in the product stream which is the vapor outlet of F102. Let us name this constraint as m.fs.product_flow. \n", - "\n", - "Use Shift+Enter to run the cell once you have typed in your code. \n", - "
" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "exercise" - ] - }, - "outputs": [], - "source": [ - "# Todo: Add minimum product flow constraint" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "solution" - ] - }, - "outputs": [], - "source": [ - "# Todo: Add minimum product flow constraint\n", - "m.fs.product_flow = Constraint(\n", - " expr=m.fs.F102.vap_outlet.flow_mol_phase_comp[0, \"Vap\", \"benzene\"] >= 0.15\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let us add the final constraint on product purity or the mole fraction of benzene in the product stream such that it is at least greater than 80%. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.product_purity = Constraint(expr=m.fs.purity >= 0.80)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "We have now defined the optimization problem and we are now ready to solve this problem. \n", - "\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "results = solver.solve(m, tee=True)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Optimization Results\n", - "\n", - "Display the results and product specifications" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "print(\"operating cost = $\", value(m.fs.operating_cost))\n", - "\n", - "print()\n", - "print(\"Product flow rate and purity in F102\")\n", - "\n", - "m.fs.F102.report()\n", - "\n", - "print()\n", - "print(\"benzene purity = \", value(m.fs.purity))\n", - "\n", - "print()\n", - "print(\"Overhead loss in F101\")\n", - "m.fs.F101.report()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Display optimal values for the decision variables" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "print(\"Optimal Values\")\n", - "print()\n", - "\n", - "print(\"H101 outlet temperature = \", value(m.fs.H101.outlet.temperature[0]), \"K\")\n", - "\n", - "print()\n", - "print(\"R101 outlet temperature = \", value(m.fs.R101.outlet.temperature[0]), \"K\")\n", - "\n", - "print()\n", - "print(\"F101 outlet temperature = \", value(m.fs.F101.vap_outlet.temperature[0]), \"K\")\n", - "\n", - "print()\n", - "print(\"F102 outlet temperature = \", value(m.fs.F102.vap_outlet.temperature[0]), \"K\")\n", - "print(\"F102 outlet pressure = \", value(m.fs.F102.vap_outlet.pressure[0]), \"Pa\")" - ] - }, - { - "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.8.12" - } - }, - "nbformat": 4, - "nbformat_minor": 3 + "cells": [ + { + "cell_type": "code", + "metadata": { + "tags": [ + "header", + "hide-cell" + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:25.418463Z", + "start_time": "2025-06-11T22:13:25.412645Z" + } + }, + "source": [ + "###############################################################################\n", + "# The Institute for the Design of Advanced Energy Systems Integrated Platform\n", + "# Framework (IDAES IP) was produced under the DOE Institute for the\n", + "# Design of Advanced Energy Systems (IDAES).\n", + "#\n", + "# Copyright (c) 2018-2023 by the software owners: The Regents of the\n", + "# University of California, through Lawrence Berkeley National Laboratory,\n", + "# National Technology & Engineering Solutions of Sandia, LLC, Carnegie Mellon\n", + "# University, West Virginia University Research Corporation, et al.\n", + "# All rights reserved. Please see the files COPYRIGHT.md and LICENSE.md\n", + "# for full copyright and license information.\n", + "###############################################################################" + ], + "outputs": [], + "execution_count": 1 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "# HDA Flowsheet Simulation and Optimization\n", + "\n", + "Author: Jaffer Ghouse
\n", + "Maintainer: Tanner Polley
\n", + "Updated: 2025-06-03\n", + "\n", + "## Learning outcomes\n", + "\n", + "\n", + "- Construct a steady-state flowsheet using the IDAES unit model library\n", + "- Connecting unit models in a flowsheet using Arcs\n", + "- Using the SequentialDecomposition tool to initialize a flowsheet with recycle\n", + "- Formulate and solve an optimization problem\n", + " - Defining an objective function\n", + " - Setting variable bounds\n", + " - Adding additional constraints\n", + "\n", + "\n", + "The general workflow of setting up an IDAES flowsheet is the following:\n", + "\n", + "- 1 Importing Modules\n", + "- 2 Building a Model\n", + "- 3 Scaling the Model\n", + "- 4 Specifying the Model\n", + "- 5 Initializing the Model\n", + "- 6 Solving the Model\n", + "- 7 Analyzing and Visualizing the Results\n", + "- 8 Optimizing the Model\n", + "\n", + "We will complete each of these steps as well as demonstrate analyses on this model through some examples and exercises\n", + "\n", + "\n", + "## Problem Statement\n", + "\n", + "Hydrodealkylation is a chemical reaction that often involves reacting\n", + "an aromatic hydrocarbon in the presence of hydrogen gas to form a\n", + "simpler aromatic hydrocarbon devoid of functional groups. In this\n", + "example, toluene will be reacted with hydrogen gas at high temperatures\n", + " to form benzene via the following reaction:\n", + "\n", + "**C6H5CH3 + H2 \u2192 C6H6 + CH4**\n", + "\n", + "\n", + "This reaction is often accompanied by an equilibrium side reaction\n", + "which forms diphenyl, which we will neglect for this example.\n", + "\n", + "This example is based on the 1967 AIChE Student Contest problem as\n", + "present by Douglas, J.M., Chemical Design of Chemical Processes, 1988,\n", + "McGraw-Hill.\n", + "\n", + "The flowsheet that we will be using for this module is shown below with the stream conditions. We will be processing toluene and hydrogen to produce at least 370 TPY of benzene. As shown in the flowsheet, there are two flash tanks, F101 to separate out the non-condensibles and F102 to further separate the benzene-toluene mixture to improve the benzene purity. Note that typically a distillation column is required to obtain high purity benzene but that is beyond the scope of this workshop. The non-condensibles separated out in F101 will be partially recycled back to M101 and the rest will be either purged or combusted for power generation.We will assume ideal gas for this flowsheet. The properties required for this module are available in the same directory:\n", + "\n", + "- hda_ideal_VLE.py\n", + "- hda_reaction.py\n", + "\n", + "The state variables chosen for the property package are **flows of component by phase, temperature and pressure**. The components considered are: **toluene, hydrogen, benzene and methane**. Therefore, every stream has 8 flow variables, 1 temperature and 1 pressure variable. \n", + "\n", + "![](HDA_flowsheet.png)\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1 Importing Modules\n", + "### 1.1 Importing required pyomo and idaes components\n", + "\n", + "\n", + "To construct a flowsheet, we will need several components from the pyomo and idaes package. Let us first import the following components from Pyomo:\n", + "- Constraint (to write constraints)\n", + "- Var (to declare variables)\n", + "- ConcreteModel (to create the concrete model object)\n", + "- Expression (to evaluate values as a function of variables defined in the model)\n", + "- Objective (to define an objective function for optimization)\n", + "- SolverFactory (to solve the problem)\n", + "- TransformationFactory (to apply certain transformations)\n", + "- Arc (to connect two unit models)\n", + "- SequentialDecomposition (to initialize the flowsheet in a sequential mode)\n", + "\n", + "For further details on these components, please refer to the pyomo documentation: https://pyomo.readthedocs.io/en/stable/\n" + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:26.066510Z", + "start_time": "2025-06-11T22:13:25.662098Z" + } + }, + "source": [ + "from pyomo.environ import (\n", + " Constraint,\n", + " Var,\n", + " ConcreteModel,\n", + " Expression,\n", + " Objective,\n", + " SolverFactory,\n", + " TransformationFactory,\n", + " value,\n", + ")\n", + "from pyomo.network import Arc, SequentialDecomposition" + ], + "outputs": [], + "execution_count": 2 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "From idaes, we will be needing the FlowsheetBlock and the following unit models:\n", + "- Feed\n", + "- Mixer\n", + "- Heater\n", + "- StoichiometricReactor\n", + "- **Flash**\n", + "- Separator (splitter) \n", + "- PressureChanger\n", + "- Product" + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:27.637361Z", + "start_time": "2025-06-11T22:13:26.082580Z" + } + }, + "source": [ + "from idaes.core import FlowsheetBlock" + ], + "outputs": [], + "execution_count": 3 + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:27.752223Z", + "start_time": "2025-06-11T22:13:27.645348Z" + } + }, + "source": [ + "from idaes.models.unit_models import (\n", + " PressureChanger, IsentropicPressureChangerInitializer,\n", + " Mixer, MixerInitializer,\n", + " Separator as Splitter, SeparatorInitializer,\n", + " Heater,\n", + " StoichiometricReactor,\n", + " Feed,\n", + " Product,\n", + ")" + ], + "outputs": [], + "execution_count": 4 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "
\n", + "Inline Exercise:\n", + "Now, import the remaining unit models highlighted in blue above and run the cell using `Shift+Enter` after typing in the code. \n", + "
\n" + ] + }, + { + "cell_type": "code", + "metadata": { + "tags": [ + "exercise" + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:27.763417Z", + "start_time": "2025-06-11T22:13:27.761004Z" + } + }, + "source": [ + "# Todo: import flash model from idaes.models.unit_models" + ], + "outputs": [], + "execution_count": 5 + }, + { + "cell_type": "code", + "metadata": { + "tags": [ + "solution" + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:27.774708Z", + "start_time": "2025-06-11T22:13:27.772299Z" + } + }, + "source": [ + "# Todo: import flash model from idaes.models.unit_models\n", + "from idaes.models.unit_models import Flash" + ], + "outputs": [], + "execution_count": 6 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We will also be needing some utility tools to put together the flowsheet and calculate the degrees of freedom. " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:27.788004Z", + "start_time": "2025-06-11T22:13:27.784891Z" + } + }, + "source": [ + "from idaes.models.unit_models.pressure_changer import ThermodynamicAssumption\n", + "from idaes.core.util.model_statistics import degrees_of_freedom\n", + "from idaes.core.scaling.util import set_scaling_factor\n", + "from idaes.core.scaling.autoscaling import AutoScaler\n", + "\n", + "# Import idaes logger to set output levels\n", + "import idaes.logger as idaeslog\n", + "from idaes.core.solvers import get_solver\n", + "from idaes.core.util.exceptions import InitializationError" + ], + "outputs": [], + "execution_count": 7 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 1.2 Importing required thermo and reaction package\n", + "\n", + "The final set of imports are to import the thermo and reaction package for the HDA process. We have created a custom thermo package that assumes Ideal Gas with support for VLE. \n", + "\n", + "The reaction package here is very simple as we will be using only a StochiometricReactor and the reaction package consists of the stochiometric coefficients for the reaction and the parameter for the heat of reaction. \n", + "\n", + "Let us import the following modules and they are in the same directory as this jupyter notebook:\n", + "
    \n", + "
  • hda_ideal_VLE as thermo_props
  • \n", + "
  • hda_reaction as reaction_props
  • \n", + "
\n", + "
" + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:27.806315Z", + "start_time": "2025-06-11T22:13:27.798068Z" + } + }, + "source": [ + "from idaes_examples.mod.hda import hda_ideal_VLE as thermo_props\n", + "from idaes_examples.mod.hda import hda_reaction as reaction_props" + ], + "outputs": [], + "execution_count": 8 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2 Constructing the Flowsheet\n", + "\n", + "We have now imported all the components, unit models, and property modules we need to construct a flowsheet. Let us create a ConcreteModel and add the flowsheet block as we did in module 1. " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:27.819615Z", + "start_time": "2025-06-11T22:13:27.814729Z" + } + }, + "source": [ + "m = ConcreteModel()\n", + "m.fs = FlowsheetBlock(dynamic=False)" + ], + "outputs": [], + "execution_count": 9 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We now need to add the property packages to the flowsheet. Unlike Module 1, where we only had a thermo property package, for this flowsheet we will also need to add a reaction property package. " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:27.841949Z", + "start_time": "2025-06-11T22:13:27.829949Z" + } + }, + "source": [ + "m.fs.thermo_params = thermo_props.HDAParameterBlock()\n", + "m.fs.reaction_params = reaction_props.HDAReactionParameterBlock(\n", + " property_package=m.fs.thermo_params\n", + ")" + ], + "outputs": [], + "execution_count": 10 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 2.1 Adding Unit Models\n", + "\n", + "Let us start adding the unit models we have imported to the flowsheet. Here, we are adding the Feed (assigned a name `I101` for Inlet), `Mixer` (assigned a name `M101`) and a `Heater` (assigned a name `H101`). Note that, all unit models need to be given a property package argument. In addition to that, there are several arguments depending on the unit model, please refer to the documentation for more details (https://idaes-pse.readthedocs.io/en/stable/reference_guides/model_libraries/generic/unit_models/index.html). For example, the `Mixer` unit model here must be specified the number of inlets that it will take in and the `Heater` can have specific settings enabled such as `has_pressure_change` or `has_phase_equilibrium`." + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:27.876870Z", + "start_time": "2025-06-11T22:13:27.853517Z" + } + }, + "source": [ + "m.fs.I101 = Feed(\n", + " property_package=m.fs.thermo_params)\n", + "\n", + "m.fs.I102 = Feed(\n", + " property_package=m.fs.thermo_params)\n", + "\n", + "m.fs.M101 = Mixer(\n", + " property_package=m.fs.thermo_params,\n", + " num_inlets=3,\n", + ")\n", + "\n", + "m.fs.H101 = Heater(\n", + " property_package=m.fs.thermo_params,\n", + " has_pressure_change=False,\n", + " has_phase_equilibrium=True,\n", + ")" + ], + "outputs": [], + "execution_count": 11 + }, + { + "cell_type": "markdown", + "metadata": { + "tags": [ + "exercise" + ] + }, + "source": [ + "
\n", + "Inline Exercise:\n", + "Let us now add the StoichiometricReactor(assign the name R101) and pass the following arguments:\n", + "
    \n", + "
  • \"property_package\": m.fs.thermo_params
  • \n", + "
  • \"reaction_package\": m.fs.reaction_params
  • \n", + "
  • \"has_heat_of_reaction\": True
  • \n", + "
  • \"has_heat_transfer\": True
  • \n", + "
  • \"has_pressure_change\": False
  • \n", + "
\n", + "
" + ] + }, + { + "cell_type": "code", + "metadata": { + "tags": [ + "exercise" + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:27.894702Z", + "start_time": "2025-06-11T22:13:27.891599Z" + } + }, + "source": [ + "# Todo: Add reactor with the specifications above" + ], + "outputs": [], + "execution_count": 12 + }, + { + "cell_type": "code", + "metadata": { + "tags": [ + "solution" + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:27.921265Z", + "start_time": "2025-06-11T22:13:27.910910Z" + } + }, + "source": [ + "# Todo: Add reactor with the specifications above\n", + "m.fs.R101 = StoichiometricReactor(\n", + " property_package=m.fs.thermo_params,\n", + " reaction_package=m.fs.reaction_params,\n", + " has_heat_of_reaction=True,\n", + " has_heat_transfer=True,\n", + " has_pressure_change=False,\n", + ")" + ], + "outputs": [], + "execution_count": 13 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let us now add the Flash(assign the name F101) and pass the following arguments:\n", + "
    \n", + "
  • \"property_package\": m.fs.thermo_params
  • \n", + "
  • \"has_heat_transfer\": True
  • \n", + "
  • \"has_pressure_change\": False
  • \n", + "
" + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:27.947463Z", + "start_time": "2025-06-11T22:13:27.933993Z" + } + }, + "source": [ + "m.fs.F101 = Flash(\n", + " property_package=m.fs.thermo_params,\n", + " has_heat_transfer=True,\n", + " has_pressure_change=True,\n", + ")" + ], + "outputs": [], + "execution_count": 14 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": "Let us now add the Splitter(S101) with specific names for its output (purge and recycle), PressureChanger(C101) and the second Flash(F102)." + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:27.987933Z", + "start_time": "2025-06-11T22:13:27.964931Z" + } + }, + "source": [ + "m.fs.S101 = Splitter(\n", + " property_package=m.fs.thermo_params,\n", + " ideal_separation=False,\n", + " outlet_list=[\"purge\", \"recycle\"],\n", + ")\n", + "\n", + "\n", + "m.fs.C101 = PressureChanger(\n", + " property_package=m.fs.thermo_params,\n", + " compressor=True,\n", + " thermodynamic_assumption=ThermodynamicAssumption.isothermal,\n", + ")\n", + "\n", + "m.fs.F102 = Flash(\n", + " property_package=m.fs.thermo_params,\n", + " has_heat_transfer=True,\n", + " has_pressure_change=True,\n", + ")" + ], + "outputs": [], + "execution_count": 15 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "Last, we will add the three Product blocks (P101, P102, P103). We use `Feed` blocks and `Product` blocks for convenience with reporting stream summaries and consistency" + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:28.009893Z", + "start_time": "2025-06-11T22:13:28.001769Z" + } + }, + "cell_type": "code", + "source": [ + "m.fs.P101 = Product(\n", + " property_package=m.fs.thermo_params)\n", + "\n", + "m.fs.P102 = Product(\n", + " property_package=m.fs.thermo_params)\n", + "\n", + "m.fs.P103 = Product(\n", + " property_package=m.fs.thermo_params)" + ], + "outputs": [], + "execution_count": 16 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 2.2 Connecting Unit Models using Arcs\n", + "\n", + "We have now added all the unit models we need to the flowsheet. However, we have not yet specified how the units are to be connected. To do this, we will be using the `Arc` which is a pyomo component that takes in two arguments: `source` and `destination`. Let us connect the outlet of the inlets (I101, I102) to the inlet of the mixer (M101) and outlet of the mixer to the inlet of the heater(H101)." + ] + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:28.034324Z", + "start_time": "2025-06-11T22:13:28.031285Z" + } + }, + "cell_type": "code", + "source": [ + "m.fs.s01 = Arc(source=m.fs.I101.outlet, destination=m.fs.M101.inlet_1)\n", + "m.fs.s02 = Arc(source=m.fs.I102.outlet, destination=m.fs.M101.inlet_2)\n", + "m.fs.s03 = Arc(source=m.fs.M101.outlet, destination=m.fs.H101.inlet)" + ], + "outputs": [], + "execution_count": 17 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "![](HDA_flowsheet.png) \n", + "\n", + "\n" + ] + }, + { + "metadata": { + "tags": [ + "exercise" + ] + }, + "cell_type": "markdown", + "source": [ + "
\n", + "Inline Exercise:\n", + "Now, connect the H101 outlet to the R101 inlet using the cell above as a guide.\n", + "
" + ] + }, + { + "cell_type": "code", + "metadata": { + "tags": [ + "exercise" + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:28.055815Z", + "start_time": "2025-06-11T22:13:28.052507Z" + } + }, + "source": [ + "# Todo: Connect the H101 outlet to R101 inlet" + ], + "outputs": [], + "execution_count": 18 + }, + { + "cell_type": "code", + "metadata": { + "tags": [ + "solution" + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:28.084663Z", + "start_time": "2025-06-11T22:13:28.082008Z" + } + }, + "source": [ + "# Todo: Connect the H101 outlet to R101 inlet\n", + "m.fs.s04 = Arc(source=m.fs.H101.outlet, destination=m.fs.R101.inlet)" + ], + "outputs": [], + "execution_count": 19 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We will now be connecting the rest of the flowsheet as shown below. Notice how the outlet names are different for the flash tanks F101 and F102 as they have a vapor and a liquid outlet. " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:28.118422Z", + "start_time": "2025-06-11T22:13:28.113732Z" + } + }, + "source": [ + "m.fs.s05 = Arc(source=m.fs.R101.outlet, destination=m.fs.F101.inlet)\n", + "m.fs.s06 = Arc(source=m.fs.F101.vap_outlet, destination=m.fs.S101.inlet)\n", + "m.fs.s07 = Arc(source=m.fs.F101.liq_outlet, destination=m.fs.F102.inlet)\n", + "m.fs.s08 = Arc(source=m.fs.S101.recycle, destination=m.fs.C101.inlet)\n", + "m.fs.s09 = Arc(source=m.fs.C101.outlet, destination=m.fs.M101.inlet_3)\n" + ], + "outputs": [], + "execution_count": 20 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "Last we will connect the outlet streams to the inlets of the Product blocks (P101, P102, P103)" + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:28.148879Z", + "start_time": "2025-06-11T22:13:28.144601Z" + } + }, + "cell_type": "code", + "source": [ + "m.fs.s10 = Arc(source=m.fs.F102.vap_outlet, destination=m.fs.P101.inlet)\n", + "m.fs.s11 = Arc(source=m.fs.F102.liq_outlet, destination=m.fs.P102.inlet)\n", + "m.fs.s12 = Arc(source=m.fs.S101.purge, destination=m.fs.P103.inlet)" + ], + "outputs": [], + "execution_count": 21 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We have now connected the unit model block using the arcs. However, each of these arcs link to ports on the two unit models that are connected. In this case, the ports consist of the state variables that need to be linked between the unit models. Pyomo provides a convenient method to write these equality constraints for us between two ports and this is done as follows:" + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:28.198384Z", + "start_time": "2025-06-11T22:13:28.176239Z" + } + }, + "source": [ + "TransformationFactory(\"network.expand_arcs\").apply_to(m)" + ], + "outputs": [], + "execution_count": 22 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 2.3 Adding expressions to compute purity and operating costs\n", + "\n", + "In this section, we will add a few Expressions that allows us to evaluate the performance. Expressions provide a convenient way of calculating certain values that are a function of the variables defined in the model. For more details on Expressions, please refer to: https://pyomo.readthedocs.io/en/stable/pyomo_modeling_components/Expressions.html\n", + "\n", + "For this flowsheet, we are interested in computing the purity of the product Benzene stream (i.e. the mole fraction) and the operating cost which is a sum of the cooling and heating cost. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let us first add an Expression to compute the mole fraction of benzene in the `vap_outlet` of F102 which is our product stream. Please note that the var flow_mol_phase_comp has the index - [time, phase, component]. As this is a steady-state flowsheet, the time index by default is 0. The valid phases are [\"Liq\", \"Vap\"]. Similarly the valid component list is [\"benzene\", \"toluene\", \"hydrogen\", \"methane\"]." + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:28.222088Z", + "start_time": "2025-06-11T22:13:28.218400Z" + } + }, + "source": [ + "m.fs.purity = Expression(\n", + " expr=m.fs.F102.vap_outlet.flow_mol_phase_comp[0, \"Vap\", \"benzene\"]\n", + " / (\n", + " m.fs.F102.vap_outlet.flow_mol_phase_comp[0, \"Vap\", \"benzene\"]\n", + " + m.fs.F102.vap_outlet.flow_mol_phase_comp[0, \"Vap\", \"toluene\"]\n", + " )\n", + ")" + ], + "outputs": [], + "execution_count": 23 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, let us add an expression to compute the cooling cost assuming a cost of 0.212E-4 $/kW. Note that cooling utility is required for the reactor (R101) and the first flash (F101). " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:28.248216Z", + "start_time": "2025-06-11T22:13:28.244244Z" + } + }, + "source": [ + "m.fs.cooling_cost = Expression(\n", + " expr=0.212e-7 * (-m.fs.F101.heat_duty[0]) + 0.212e-7 * (-m.fs.R101.heat_duty[0])\n", + ")" + ], + "outputs": [], + "execution_count": 24 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "Now, let us add an expression to compute the heating cost assuming the utility cost as follows:\n", + "
    \n", + "
  • 2.2E-4 dollars/kW for H101
  • \n", + "
  • 1.9E-4 dollars/kW for F102
  • \n", + "
\n", + "Note that the heat duty is in units of watt (J/s). " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:28.271256Z", + "start_time": "2025-06-11T22:13:28.268284Z" + } + }, + "source": [ + "m.fs.heating_cost = Expression(\n", + " expr=2.2e-7 * m.fs.H101.heat_duty[0] + 1.9e-7 * m.fs.F102.heat_duty[0]\n", + ")" + ], + "outputs": [], + "execution_count": 25 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let us now add an expression to compute the total operating cost per year which is basically the sum of the cooling and heating cost we defined above. " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:28.295580Z", + "start_time": "2025-06-11T22:13:28.292627Z" + } + }, + "source": [ + "m.fs.operating_cost = Expression(\n", + " expr=(3600 * 24 * 365 * (m.fs.heating_cost + m.fs.cooling_cost))\n", + ")" + ], + "outputs": [], + "execution_count": 26 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": [ + "## 3 Scaling the Model\n", + "\n", + "In this example, we will simply use the ``AutoScaler`` method to scale our model since this example is focused on introductory flowsheet building for a full process system.\n", + "\n", + "For more direct control, a manual, magnitude based approach can be used. If this method is chosen or appropriate, it is highly recommended to look at these documentation:\n", + "- Scaling Toolbox https://idaes-pse.readthedocs.io/en/stable/reference_guides/scaling/scaling.html\n", + "- Scaling Workshop https://idaes-examples.readthedocs.io/en/latest/docs/scaling/scaler_workshop_doc.html.\n", + "\n", + "In this example, we already imported the ``AutoScaler`` class in the beginning so we just create the ``autoscaler`` object and call the ``scale_model`` method on the model `m`." + ] + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:28.915668Z", + "start_time": "2025-06-11T22:13:28.317020Z" + } + }, + "cell_type": "code", + "source": [ + "autoscaler = AutoScaler()\n", + "autoscaler.scale_model(m)" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "WARNING: model contains export suffix 'scaling_factor' that contains 2\n", + "component keys that are not exported as part of the NL file. Skipping.\n" + ] + } + ], + "execution_count": 27 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 4 Specifying the Model\n", + "### 4.1 Fixing feed conditions\n", + "\n", + "Let us first check how many degrees of freedom exist for this flowsheet using the `degrees_of_freedom` tool we imported earlier. " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:28.948173Z", + "start_time": "2025-06-11T22:13:28.924210Z" + } + }, + "source": [ + "print(degrees_of_freedom(m))" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "29\n" + ] + } + ], + "execution_count": 28 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": "We will now be fixing the toluene feed (`I101`) stream to the conditions shown in the flowsheet above. Please note that though this is a pure toluene feed, the remaining components are still assigned a very small non-zero value to help with convergence and initializing." + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.012096Z", + "start_time": "2025-06-11T22:13:29.006965Z" + } + }, + "source": [ + "m.fs.I101.flow_mol_phase_comp[0, \"Vap\", \"benzene\"].fix(1e-5)\n", + "m.fs.I101.flow_mol_phase_comp[0, \"Vap\", \"toluene\"].fix(1e-5)\n", + "m.fs.I101.flow_mol_phase_comp[0, \"Vap\", \"hydrogen\"].fix(1e-5)\n", + "m.fs.I101.flow_mol_phase_comp[0, \"Vap\", \"methane\"].fix(1e-5)\n", + "m.fs.I101.flow_mol_phase_comp[0, \"Liq\", \"benzene\"].fix(1e-5)\n", + "m.fs.I101.flow_mol_phase_comp[0, \"Liq\", \"toluene\"].fix(0.30)\n", + "m.fs.I101.flow_mol_phase_comp[0, \"Liq\", \"hydrogen\"].fix(1e-5)\n", + "m.fs.I101.flow_mol_phase_comp[0, \"Liq\", \"methane\"].fix(1e-5)\n", + "m.fs.I101.temperature.fix(303.2)\n", + "m.fs.I101.pressure.fix(350000)" + ], + "outputs": [], + "execution_count": 30 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "Similarly, let us fix the hydrogen feed (`I102`) to the following conditions in the next cell:\n", + "
    \n", + "
  • FH2 = 0.30 mol/s
  • \n", + "
  • FCH4 = 0.02 mol/s
  • \n", + "
  • Remaining components = 1e-5 mol/s
  • \n", + "
  • T = 303.2 K
  • \n", + "
  • P = 350000 Pa
  • \n", + "
\n", + "\n" + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.036253Z", + "start_time": "2025-06-11T22:13:29.031731Z" + } + }, + "source": [ + "m.fs.I102.flow_mol_phase_comp[0, \"Vap\", \"benzene\"].fix(1e-5)\n", + "m.fs.I102.flow_mol_phase_comp[0, \"Vap\", \"toluene\"].fix(1e-5)\n", + "m.fs.I102.flow_mol_phase_comp[0, \"Vap\", \"hydrogen\"].fix(0.30)\n", + "m.fs.I102.flow_mol_phase_comp[0, \"Vap\", \"methane\"].fix(0.02)\n", + "m.fs.I102.flow_mol_phase_comp[0, \"Liq\", \"benzene\"].fix(1e-5)\n", + "m.fs.I102.flow_mol_phase_comp[0, \"Liq\", \"toluene\"].fix(1e-5)\n", + "m.fs.I102.flow_mol_phase_comp[0, \"Liq\", \"hydrogen\"].fix(1e-5)\n", + "m.fs.I102.flow_mol_phase_comp[0, \"Liq\", \"methane\"].fix(1e-5)\n", + "m.fs.I102.temperature.fix(303.2)\n", + "m.fs.I102.pressure.fix(350000)" + ], + "outputs": [], + "execution_count": 31 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 4.2 Fixing unit model specifications\n", + "\n", + "Now that we have fixed our inlet feed conditions, we will now be fixing the operating conditions for the unit models in the flowsheet. Let us set set the H101 outlet temperature to 600 K. " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.078559Z", + "start_time": "2025-06-11T22:13:29.075531Z" + } + }, + "source": [ + "m.fs.H101.outlet.temperature.fix(600)" + ], + "outputs": [], + "execution_count": 32 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For the StoichiometricReactor, we have to define the conversion in terms of toluene. This requires us to create a new variable for specifying the conversion and adding a Constraint that defines the conversion with respect to toluene. The second degree of freedom for the reactor is to define the heat duty. In this case, let us assume the reactor to be adiabatic i.e. Q = 0. " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.111099Z", + "start_time": "2025-06-11T22:13:29.106956Z" + } + }, + "source": [ + "m.fs.R101.conversion = Var(initialize=0.75, bounds=(0, 1))\n", + "\n", + "m.fs.R101.conv_constraint = Constraint(\n", + " expr=m.fs.R101.conversion * m.fs.R101.inlet.flow_mol_phase_comp[0, \"Vap\", \"toluene\"]\n", + " == (\n", + " m.fs.R101.inlet.flow_mol_phase_comp[0, \"Vap\", \"toluene\"]\n", + " - m.fs.R101.outlet.flow_mol_phase_comp[0, \"Vap\", \"toluene\"]\n", + " )\n", + ")\n", + "\n", + "m.fs.R101.conversion.fix(0.75)\n", + "m.fs.R101.heat_duty.fix(0)" + ], + "outputs": [], + "execution_count": 33 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The Flash conditions for F101 can be set as follows. " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.128972Z", + "start_time": "2025-06-11T22:13:29.125273Z" + } + }, + "source": [ + "m.fs.F101.vap_outlet.temperature.fix(325.0)\n", + "m.fs.F101.deltaP.fix(0)" + ], + "outputs": [], + "execution_count": 34 + }, + { + "cell_type": "markdown", + "metadata": { + "tags": [ + "exercise" + ] + }, + "source": [ + "
\n", + "Inline Exercise:\n", + "Set the conditions for Flash F102 to the following conditions:\n", + "
    \n", + "
  • T = 375 K
  • \n", + "
  • deltaP = -200000
  • \n", + "
\n", + "\n", + "Use Shift+Enter to run the cell once you have typed in your code. \n", + "
" + ] + }, + { + "cell_type": "code", + "metadata": { + "tags": [ + "exercise" + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.149002Z", + "start_time": "2025-06-11T22:13:29.146476Z" + } + }, + "source": [ + "# Todo: Set conditions for Flash F102" + ], + "outputs": [], + "execution_count": 35 + }, + { + "cell_type": "code", + "metadata": { + "tags": [ + "solution" + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.171742Z", + "start_time": "2025-06-11T22:13:29.168352Z" + } + }, + "source": [ + "m.fs.F102.vap_outlet.temperature.fix(375)\n", + "m.fs.F102.deltaP.fix(-200000)" + ], + "outputs": [], + "execution_count": 36 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let us fix the purge split fraction to 20% and the outlet pressure of the compressor is set to 350000 Pa. " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.196247Z", + "start_time": "2025-06-11T22:13:29.192956Z" + } + }, + "source": [ + "m.fs.S101.split_fraction[0, \"purge\"].fix(0.2)\n", + "m.fs.C101.outlet.pressure.fix(350000)" + ], + "outputs": [], + "execution_count": 37 + }, + { + "cell_type": "markdown", + "metadata": { + "tags": [ + "exercise" + ] + }, + "source": [ + "
\n", + "Inline Exercise:\n", + "We have now defined all the feed conditions and the inputs required for the unit models. The system should now have 0 degrees of freedom i.e. should be a square problem. Please check that the degrees of freedom is 0. \n", + "\n", + "Use Shift+Enter to run the cell once you have typed in your code. \n", + "
" + ] + }, + { + "cell_type": "code", + "metadata": { + "tags": [ + "exercise" + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.220402Z", + "start_time": "2025-06-11T22:13:29.217907Z" + } + }, + "source": [ + "# Todo: print the degrees of freedom" + ], + "outputs": [], + "execution_count": 38 + }, + { + "cell_type": "code", + "metadata": { + "tags": [ + "solution" + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.263397Z", + "start_time": "2025-06-11T22:13:29.241129Z" + } + }, + "source": [ + "print(degrees_of_freedom(m))" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0\n" + ] + } + ], + "execution_count": 39 + }, + { + "metadata": { + "tags": [ + "noauto" + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.316042Z", + "start_time": "2025-06-11T22:13:29.313537Z" + } + }, + "cell_type": "code", + "source": "", + "outputs": [], + "execution_count": null + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 5 Initializing the Model\n", + "\n", + "\n", + "\n", + "When a flowsheet contains a recycle loop, the outlet of a downstream unit becomes the inlet of an upstream unit, creating a cyclic dependency that prevents straightforward calculation of all stream conditions. The tear\u2010stream method is necessary because it \u201cbreaks\u201d this loop: you select one recycle stream as the tear, assign it an initial guess, and then solve the rest of the flowsheet as if it were acyclic. Once the downstream units compute their outputs, you compare the calculated value of the torn stream to your initial guess and iteratively adjust until they coincide. Without tearing, the solver cannot establish a proper topological sequence or drive the recycle to convergence, making initialization\u2014and ultimately steady\u2010state convergence\u2014impossible.\n", + "\n", + "It is important to determine the tear stream for a flowsheet which will be demonstrated below.\n", + "\n", + "\n", + "![](HDA_flowsheet.png)\n", + "\n", + "Currently, there are two methods of initializing a full flowsheet: using the sequential decomposition tool, or manually propagating through the flowsheet. Both methods will be shown.\n", + "\n", + "### 5.1 Sequential Decomposition\n", + "\n", + "This section will demonstrate how to use the built-in sequential decomposition tool to initialize our flowsheet. Sequential Decomposition is a tool from pyomo where the documentation can be found here https://pyomo.readthedocs.io/en/stable/explanation/modeling/network.html#sequential-decomposition\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": "Let us first create an object for the SequentialDecomposition and specify our options for this. We can also create a graph for our flowsheet to determine the tear set and order." + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.330212Z", + "start_time": "2025-06-11T22:13:29.324872Z" + } + }, + "source": [ + "seq = SequentialDecomposition()\n", + "seq.options.select_tear_method = \"heuristic\"\n", + "seq.options.tear_method = \"Wegstein\"\n", + "seq.options.iterLim = 3\n", + "\n", + "# Using the SD tool\n", + "G = seq.create_graph(m)\n", + "heuristic_tear_set = seq.tear_set_arcs(G, method=\"heuristic\")\n", + "order = seq.calculation_order(G)" + ], + "outputs": [], + "execution_count": 41 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Which is the tear stream? Display tear set and order" + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.353734Z", + "start_time": "2025-06-11T22:13:29.350730Z" + } + }, + "source": [ + "for o in heuristic_tear_set:\n", + " print(o.name)" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "fs.s03\n" + ] + } + ], + "execution_count": 42 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "What sequence did the SD tool determine to solve this flowsheet with the least number of tears? " + ] + }, + { + "cell_type": "code", + "metadata": { + "scrolled": true, + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.391173Z", + "start_time": "2025-06-11T22:13:29.388153Z" + } + }, + "source": [ + "for o in order:\n", + " print(o[0].name)" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "fs.I101\n", + "fs.R101\n", + "fs.F101\n", + "fs.S101\n", + "fs.C101\n", + "fs.M101\n" + ] + } + ], + "execution_count": 43 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + " \n", + "\n", + "![](HDA_tear_stream.png) \n", + "\n", + "\n", + "The SequentialDecomposition tool has determined that the tear stream is the mixer outlet. You can see this shown in the picture of the flowsheet above as the outlet of the mixer as the two lines crossing it identifying it as the tear stream. We will need to provide a reasonable guess for this." + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.430684Z", + "start_time": "2025-06-11T22:13:29.426921Z" + } + }, + "source": [ + "tear_guesses = {\n", + " \"flow_mol_phase_comp\": {\n", + " (0, \"Liq\", \"benzene\"): 1e-5,\n", + " (0, \"Liq\", \"toluene\"): 0.30,\n", + " (0, \"Liq\", \"methane\"): 1e-5,\n", + " (0, \"Liq\", \"hydrogen\"): 1e-5,\n", + " (0, \"Vap\", \"benzene\"): 1e-5,\n", + " (0, \"Vap\", \"toluene\"): 1e-5,\n", + " (0, \"Vap\", \"methane\"): 0.02,\n", + " (0, \"Vap\", \"hydrogen\"): 0.30,\n", + " },\n", + " \"temperature\": {0: 303},\n", + " \"pressure\": {0: 350000},\n", + "}\n", + "\n", + "# Pass the tear_guess to the SD tool\n", + "seq.set_guesses_for(m.fs.H101.inlet, tear_guesses)" + ], + "outputs": [], + "execution_count": 44 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next, we need to tell the tool how to initialize a particular unit. We will be writing a python function which takes in a \"unit\" and calls the initialize method on that unit. " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.465203Z", + "start_time": "2025-06-11T22:13:29.462031Z" + } + }, + "source": [ + "def function(unit):\n", + " try:\n", + " initializer = unit.default_initializer()\n", + " initializer.initialize(unit, output_level=idaeslog.INFO)\n", + " except InitializationError:\n", + " solver = get_solver()\n", + " solver.solve(unit)" + ], + "outputs": [], + "execution_count": 45 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We are now ready to initialize our flowsheet in a sequential mode. Note that we specifically set the iteration limit to be 5 as we are trying to use this tool only to get a good set of initial values such that IPOPT can then take over and solve this flowsheet for us. " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.505027Z", + "start_time": "2025-06-11T22:13:29.501933Z" + } + }, + "source": "# seq.run(m, function)", + "outputs": [], + "execution_count": 46 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": [ + "### 5.2 Manual Propogation Method\n", + "\n", + "This method uses a more direct approach to initialize the flowsheet, utilizing the updated initalizer method and propogating manually throught the flowsheet and solving for the tear stream directly.\n", + "Lets first import a helper function that will help us manually propagate and step through the flowsheet" + ] + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.536579Z", + "start_time": "2025-06-11T22:13:29.533074Z" + } + }, + "cell_type": "code", + "source": "from idaes.core.util.initialization import propagate_state", + "outputs": [], + "execution_count": 47 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": [ + "Now we can setup our initial guesses for the tear stream which we know is the outlet of the `Mixer` or the inlet of the `Heater`. We can use the same initial guesses used in the first method. We also want to ensure that the degrees of freedom are consistent while we manually intialize the model.\n", + "\n", + "We will first ensure that are current degrees of freedom is still zero" + ] + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.583098Z", + "start_time": "2025-06-11T22:13:29.553382Z" + } + }, + "cell_type": "code", + "source": "print(f\"The DOF is {degrees_of_freedom(m)} initially\")", + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The DOF is 0 initially\n" + ] + } + ], + "execution_count": 48 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "Now we can manually deactivate the tear stream, creating a separation between the `Mixer` and `Heater`. This should reduce the degrees of freedom by 10 since the inlet of the `Heater` now contains no values to solve the unit model. To deactivate a stream, simply use `m.fs.s03_expanded.deactivate()`. This expanded stream is just a different version of the `Arc` stream that is able to be deactivated." + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.633917Z", + "start_time": "2025-06-11T22:13:29.610932Z" + } + }, + "cell_type": "code", + "source": [ + "m.fs.s03_expanded.deactivate()\n", + "\n", + "print(f\"The DOF is {degrees_of_freedom(m)} after deactivating the tear stream\")" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The DOF is 10 after deactivating the tear stream\n" + ] + } + ], + "execution_count": 49 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "Now we can provide the `Heater` inlet 10 guess values to bring the degrees of freedom back to 0 and start the manual initialization process. We can run this convenient loop to assign each of these guesses to the inlet of the heater." + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.702707Z", + "start_time": "2025-06-11T22:13:29.654459Z" + } + }, + "cell_type": "code", + "source": [ + "tear_guesses = {\n", + " \"flow_mol_phase_comp\": {\n", + " (0, \"Liq\", \"benzene\"): 1e-5,\n", + " (0, \"Liq\", \"toluene\"): 0.30,\n", + " (0, \"Liq\", \"methane\"): 1e-5,\n", + " (0, \"Liq\", \"hydrogen\"): 1e-5,\n", + " (0, \"Vap\", \"benzene\"): 1e-5,\n", + " (0, \"Vap\", \"toluene\"): 1e-5,\n", + " (0, \"Vap\", \"methane\"): 0.02,\n", + " (0, \"Vap\", \"hydrogen\"): 0.30,\n", + "\n", + " },\n", + " \"temperature\": {0: 303},\n", + " \"pressure\": {0: 350000},\n", + "}\n", + "\n", + "for k, v in tear_guesses.items():\n", + " for k1, v1 in v.items():\n", + " getattr(m.fs.s03.destination, k)[k1].fix(v1)\n", + "\n", + "DOF_initial = degrees_of_freedom(m)\n", + "print(f\"The DOF is {degrees_of_freedom(m)} after providing the initial guesses\")" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The DOF is 0 after providing the initial guesses\n" + ] + } + ], + "execution_count": 50 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "The next step is to manually initialize each unit model starting from the `Heater` and then propagate the connection between it and the next unit model. This manual process ensures a strict order to the user's specification if that is desired. The current standard for initializing a unit model is to use an initializer object most compatible for that unit model. This can most often be done by utilizing the `default_initializer()` method attached to the unit model and then to call the `initialize()` method with the unit model as the argument." + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:31.347435Z", + "start_time": "2025-06-11T22:13:29.712721Z" + } + }, + "cell_type": "code", + "source": [ + "m.fs.H101.default_initializer().initialize(m.fs.H101) # Initialize Heater\n", + "propagate_state(m.fs.s04) # Establish connection between Heater and Reactor\n", + "m.fs.R101.default_initializer().initialize(m.fs.R101) # Initialize Reactor\n", + "propagate_state(m.fs.s05) # Establish connection between Reactor and First Flash Unit\n", + "m.fs.F101.default_initializer().initialize(m.fs.F101) # Initialize First Flash Unit\n", + "propagate_state(m.fs.s06) # Establish connection between First Flash Unit and Splitter\n", + "propagate_state(m.fs.s07) # Establish connection between First Flash Unit and Second Flash Unit\n", + "m.fs.S101.default_initializer().initialize(m.fs.S101) # Initialize Splitter\n", + "propagate_state(m.fs.s08) # Establish connection between Splitter and Compressor\n", + "m.fs.C101.default_initializer().initialize(m.fs.C101) # Initialize Compressor\n", + "propagate_state(m.fs.s09) # Establish connection between Compressor and Mixer\n", + "m.fs.I101.default_initializer().initialize(m.fs.I101) # Initialize Toluene Inlet\n", + "propagate_state(m.fs.s01) # Establish connection between Toluene Inlet and Mixer\n", + "m.fs.I102.default_initializer().initialize(m.fs.I102) # Initialize Hydrogen Inlet\n", + "propagate_state(m.fs.s02) # Establish connection between Hydrogen Inlet and Mixer\n", + "m.fs.M101.default_initializer().initialize(m.fs.M101) # Initialize Mixer\n", + "propagate_state(m.fs.s03) # Establish connection between Mixer and Heater\n", + "m.fs.F102.default_initializer().initialize(m.fs.F102) # Initialize Second Flash Unit\n", + "propagate_state(m.fs.s10) # Establish connection between Second Flash Unit and Benzene Product\n", + "propagate_state(m.fs.s11) # Establish connection between Second Flash Unit and Toluene Product\n", + "propagate_state(m.fs.s12) # Establish connection between Splitter and Purge Product\n" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2025-06-11 16:13:29 [INFO] idaes.init.fs.H101.control_volume.properties_in: Initialization Complete\n", + "2025-06-11 16:13:30 [INFO] idaes.init.fs.H101.control_volume.properties_out: Initialization Complete\n", + "2025-06-11 16:13:30 [INFO] idaes.init.fs.R101.control_volume.properties_in: Initialization Complete\n", + "2025-06-11 16:13:30 [INFO] idaes.init.fs.R101.control_volume.properties_out: Initialization Complete\n", + "2025-06-11 16:13:30 [INFO] idaes.init.fs.F101.control_volume.properties_in: Initialization Complete\n", + "2025-06-11 16:13:30 [INFO] idaes.init.fs.F101.control_volume.properties_out: Initialization Complete\n", + "2025-06-11 16:13:30 [INFO] idaes.init.fs.S101.mixed_state: Initialization Complete\n", + "2025-06-11 16:13:30 [INFO] idaes.init.fs.S101.purge_state: Initialization Complete\n", + "2025-06-11 16:13:30 [INFO] idaes.init.fs.S101.recycle_state: Initialization Complete\n", + "2025-06-11 16:13:30 [INFO] idaes.init.fs.S101: Initialization Step 2 Complete: optimal - \n", + "2025-06-11 16:13:30 [INFO] idaes.init.fs.C101.control_volume.properties_in: Initialization Complete\n", + "2025-06-11 16:13:31 [INFO] idaes.init.fs.C101.control_volume.properties_out: Initialization Complete\n", + "2025-06-11 16:13:31 [INFO] idaes.init.fs.I101.properties: Initialization Complete\n", + "2025-06-11 16:13:31 [INFO] idaes.init.fs.I102.properties: Initialization Complete\n", + "2025-06-11 16:13:31 [INFO] idaes.init.fs.M101.inlet_1_state: Initialization Complete\n", + "2025-06-11 16:13:31 [INFO] idaes.init.fs.M101.inlet_2_state: Initialization Complete\n", + "2025-06-11 16:13:31 [INFO] idaes.init.fs.M101.inlet_3_state: Initialization Complete\n", + "2025-06-11 16:13:31 [INFO] idaes.init.fs.M101.mixed_state: Initialization Complete\n", + "2025-06-11 16:13:31 [INFO] idaes.init.fs.M101: Initialization Complete: optimal - \n", + "2025-06-11 16:13:31 [INFO] idaes.init.fs.F102.control_volume.properties_in: Initialization Complete\n", + "2025-06-11 16:13:31 [INFO] idaes.init.fs.F102.control_volume.properties_out: Initialization Complete\n" + ] + } + ], + "execution_count": 51 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "Now we solve the system to allow the outlet of the mixer to reach a converged congruence with the inlet of the heater." + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:31.935414Z", + "start_time": "2025-06-11T22:13:31.357025Z" + } + }, + "cell_type": "code", + "source": [ + "solver = get_solver()\n", + "results = solver.solve(m, tee=True)" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "WARNING: model contains export suffix 'scaling_factor' that contains 40\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "Ipopt 3.13.2: nlp_scaling_method=gradient-based\n", + "tol=1e-06\n", + "max_iter=200\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...: 1112\n", + "Number of nonzeros in inequality constraint Jacobian.: 0\n", + "Number of nonzeros in Lagrangian Hessian.............: 999\n", + "\n", + "Total number of variables............................: 380\n", + " variables with only lower bounds: 0\n", + " variables with lower and upper bounds: 186\n", + " variables with only upper bounds: 0\n", + "Total number of equality constraints.................: 380\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 1.24e+05 0.00e+00 -1.0 0.00e+00 - 0.00e+00 0.00e+00 0\n", + " 1 0.0000000e+00 1.24e+05 2.12e+00 -1.0 1.99e+05 - 1.39e-01 3.68e-04h 10\n", + " 2 0.0000000e+00 1.24e+05 5.49e+00 -1.0 1.99e+05 - 1.43e-01 3.66e-04h 10\n", + " 3 0.0000000e+00 1.24e+05 4.13e+01 -1.0 2.00e+05 - 9.51e-01 3.64e-04h 10\n", + " 4 0.0000000e+00 1.24e+05 7.47e+01 -1.0 2.00e+05 - 1.77e-01 3.63e-04h 10\n", + " 5 0.0000000e+00 1.24e+05 4.07e+02 -1.0 2.01e+05 - 9.90e-01 3.61e-04h 10\n", + " 6 0.0000000e+00 1.24e+05 6.97e+02 -1.0 2.01e+05 - 1.63e-01 3.60e-04h 10\n", + " 7 0.0000000e+00 1.24e+05 3.75e+03 -1.0 2.02e+05 - 9.90e-01 3.58e-04h 10\n", + " 8 0.0000000e+00 1.24e+05 6.44e+03 -1.0 2.02e+05 - 1.63e-01 3.57e-04h 10\n", + " 9 0.0000000e+00 1.24e+05 3.51e+04 -1.0 2.03e+05 - 1.00e+00 3.55e-04h 10\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 10 0.0000000e+00 1.24e+05 6.05e+04 -1.0 2.04e+05 - 1.62e-01 3.54e-04h 10\n", + " 11 0.0000000e+00 2.01e+06 2.73e+05 -1.0 2.04e+05 - 1.00e+00 1.80e-01w 1\n", + " 12 0.0000000e+00 2.01e+06 3.91e+07 -1.0 7.38e+05 - 6.21e-02 5.22e-04w 1\n", + " 13 0.0000000e+00 2.01e+06 6.94e+11 -1.0 7.50e+05 - 9.13e-02 5.14e-06w 1\n", + " 14 0.0000000e+00 1.24e+05 3.32e+05 -1.0 6.22e+04 - 1.00e+00 3.52e-04h 9\n", + " 15 0.0000000e+00 1.24e+05 5.43e+05 -1.0 2.05e+05 - 1.41e-01 3.51e-04h 10\n", + " 16 0.0000000e+00 1.24e+05 3.01e+06 -1.0 2.05e+05 - 1.00e+00 3.49e-04h 10\n", + " 17 0.0000000e+00 1.24e+05 4.76e+06 -1.0 2.06e+05 - 1.28e-01 3.48e-04h 10\n", + " 18 0.0000000e+00 1.24e+05 2.66e+07 -1.0 2.06e+05 - 1.00e+00 3.46e-04h 10\n", + " 19 0.0000000e+00 1.24e+05 4.23e+07 -1.0 2.07e+05 - 1.28e-01 3.45e-04h 10\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 20 0.0000000e+00 1.24e+05 2.38e+08 -1.0 2.08e+05 - 1.00e+00 3.43e-04h 10\n", + " 21 0.0000000e+00 1.24e+05 3.80e+08 -1.0 2.08e+05 - 1.28e-01 3.42e-04h 10\n", + " 22 0.0000000e+00 1.23e+05 2.16e+09 -1.0 2.09e+05 - 1.00e+00 3.40e-04h 10\n", + " 23 0.0000000e+00 1.23e+05 3.45e+09 -1.0 2.09e+05 - 1.28e-01 3.39e-04h 10\n", + " 24 0.0000000e+00 1.96e+06 1.26e+10 -1.0 2.10e+05 - 7.69e-01 1.73e-01w 1\n", + " 25 0.0000000e+00 1.96e+06 1.91e+12 -1.0 7.43e+05 - 6.14e-02 5.08e-04w 1\n", + " 26 0.0000000e+00 1.96e+06 7.01e+16 -1.0 7.55e+05 - 1.84e-01 5.01e-06w 1\n", + " 27 0.0000000e+00 1.23e+05 1.60e+10 -1.0 1.00e+05 - 7.69e-01 3.37e-04h 9\n", + " 28 0.0000000e+00 1.23e+05 2.61e+10 -1.0 2.10e+05 - 1.33e-01 3.36e-04h 10\n", + " 29 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.11e+05 - 1.00e+00 3.35e-04h 10\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 30 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.11e+05 - 1.27e-01 3.33e-04h 10\n", + " 31 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.12e+05 - 5.83e-01 3.32e-04h 10\n", + " 32 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.13e+05 - 1.41e-01 3.30e-04h 10\n", + " 33 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.13e+05 - 1.00e+00 3.29e-04h 10\n", + " 34 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.14e+05 - 1.26e-01 3.28e-04h 10\n", + " 35 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.14e+05 - 6.16e-01 3.26e-04h 10\n", + " 36 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.15e+05 - 1.38e-01 3.25e-04h 10\n", + " 37 0.0000000e+00 1.90e+06 5.27e+11 -1.0 2.15e+05 - 1.00e+00 1.66e-01w 1\n", + " 38 0.0000000e+00 1.90e+06 6.09e+13 -1.0 2.72e+05 - 1.39e-01 1.43e-03w 1\n", + " 39 0.0000000e+00 1.90e+06 1.06e+17 -1.0 7.68e+05 - 9.45e-02 4.82e-06w 1\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 40 0.0000000e+00 1.23e+05 1.04e+11 -1.0 7.68e+05 - 1.00e+00 3.23e-04h 9\n", + " 41 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.16e+05 - 1.26e-01 3.22e-04h 10\n", + " 42 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.17e+05 - 6.03e-01 3.21e-04h 10\n", + " 43 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.17e+05 - 1.38e-01 3.19e-04h 10\n", + " 44 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.18e+05 - 1.00e+00 3.18e-04h 10\n", + " 45 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.18e+05 - 1.25e-01 3.17e-04h 10\n", + " 46 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.19e+05 - 6.03e-01 3.15e-04h 10\n", + " 47 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.20e+05 - 1.38e-01 3.14e-04h 10\n", + " 48 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.20e+05 - 1.00e+00 3.13e-04h 10\n", + " 49 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.21e+05 - 1.25e-01 3.11e-04h 10\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 50 0.0000000e+00 1.85e+06 3.37e+11 -1.0 2.21e+05 - 5.99e-01 1.59e-01w 1\n", + " 51 0.0000000e+00 1.85e+06 5.67e+13 -1.0 7.53e+05 - 6.18e-02 4.82e-04w 1\n", + " 52 0.0000000e+00 1.85e+06 1.08e+17 -1.0 7.64e+05 - 2.20e-01 4.75e-06w 1\n", + " 53 0.0000000e+00 1.23e+05 1.04e+11 -1.0 7.64e+05 - 5.99e-01 3.10e-04h 9\n", + " 54 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.22e+05 - 1.38e-01 3.09e-04h 10\n", + " 55 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.22e+05 - 1.00e+00 3.07e-04h 10\n", + " 56 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.23e+05 - 1.24e-01 3.06e-04h 10\n", + " 57 0.0000000e+00 1.22e+05 1.04e+11 -1.0 2.24e+05 - 5.97e-01 3.05e-04h 10\n", + " 58 0.0000000e+00 1.22e+05 1.04e+11 -1.0 2.24e+05 - 1.37e-01 3.04e-04h 10\n", + " 59 0.0000000e+00 1.22e+05 1.04e+11 -1.0 2.25e+05 - 1.00e+00 3.02e-04h 10\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 60 0.0000000e+00 1.22e+05 1.04e+11 -1.0 2.25e+05 - 1.24e-01 3.01e-04h 10\n", + " 61 0.0000000e+00 1.22e+05 1.04e+11 -1.0 2.26e+05 - 5.94e-01 3.00e-04h 10\n", + " 62 0.0000000e+00 1.22e+05 1.04e+11 -1.0 2.26e+05 - 1.37e-01 2.98e-04h 10\n", + " 63 0.0000000e+00 1.79e+06 6.03e+11 -1.0 2.27e+05 - 1.00e+00 1.52e-01w 1\n", + " 64 0.0000000e+00 1.79e+06 9.13e+13 -1.0 7.58e+05 - 6.05e-02 4.69e-04w 1\n", + " 65 0.0000000e+00 1.79e+06 1.10e+17 -1.0 7.68e+05 - 1.20e-01 4.63e-06w 1\n", + " 66 0.0000000e+00 1.22e+05 1.04e+11 -1.0 7.69e+05 - 1.00e+00 2.97e-04h 9\n", + " 67 0.0000000e+00 1.22e+05 1.04e+11 -1.0 2.28e+05 - 1.23e-01 2.96e-04h 10\n", + " 68 0.0000000e+00 1.22e+05 1.04e+11 -1.0 2.28e+05 - 5.91e-01 2.95e-04h 10\n", + " 69 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.29e+05 - 1.36e-01 2.93e-04h 10\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 70 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.29e+05 - 1.00e+00 2.92e-04h 10\n", + " 71 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.30e+05 - 1.23e-01 2.91e-04h 10\n", + " 72 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.31e+05 - 5.89e-01 2.90e-04h 10\n", + " 73 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.31e+05 - 1.36e-01 2.89e-04h 10\n", + " 74 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.32e+05 - 1.00e+00 2.87e-04h 10\n", + " 75 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.32e+05 - 1.23e-01 2.86e-04h 10\n", + " 76 0.0000000e+00 1.74e+06 3.75e+11 -1.0 2.33e+05 - 5.86e-01 1.46e-01w 1\n", + " 77 0.0000000e+00 1.74e+06 6.57e+13 -1.0 7.62e+05 - 6.18e-02 4.58e-04w 1\n", + " 78 0.0000000e+00 1.74e+06 1.12e+17 -1.0 7.73e+05 - 2.30e-01 4.51e-06w 1\n", + " 79 0.0000000e+00 1.22e+05 1.05e+11 -1.0 7.73e+05 - 5.86e-01 2.85e-04h 9\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 80 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.34e+05 - 1.36e-01 2.84e-04h 10\n", + " 81 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.34e+05 - 1.00e+00 2.83e-04h 10\n", + " 82 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.35e+05 - 1.22e-01 2.81e-04h 10\n", + " 83 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.35e+05 - 5.83e-01 2.80e-04h 10\n", + " 84 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.36e+05 - 1.35e-01 2.79e-04h 10\n", + " 85 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.37e+05 - 1.00e+00 2.78e-04h 10\n", + " 86 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.37e+05 - 1.22e-01 2.77e-04h 10\n", + " 87 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.38e+05 - 5.81e-01 2.76e-04h 10\n", + " 88 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.38e+05 - 1.35e-01 2.74e-04h 10\n", + " 89 0.0000000e+00 1.69e+06 6.88e+11 -1.0 2.39e+05 - 1.00e+00 1.40e-01w 1\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 90 0.0000000e+00 1.69e+06 1.08e+14 -1.0 7.66e+05 - 6.03e-02 4.46e-04w 1\n", + " 91 0.0000000e+00 1.69e+06 1.14e+17 -1.0 7.77e+05 - 1.22e-01 4.40e-06w 1\n", + " 92 0.0000000e+00 1.22e+05 1.05e+11 -1.0 7.77e+05 - 1.00e+00 2.73e-04h 9\n", + " 93 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.39e+05 - 1.21e-01 2.72e-04h 10\n", + " 94 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.40e+05 - 5.78e-01 2.71e-04h 10\n", + " 95 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.41e+05 - 1.34e-01 2.70e-04h 10\n", + " 96 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.41e+05 - 1.00e+00 2.69e-04h 10\n", + " 97 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.42e+05 - 1.21e-01 2.68e-04h 10\n", + " 98 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.42e+05 - 5.76e-01 2.67e-04h 10\n", + " 99 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.43e+05 - 1.34e-01 2.65e-04h 10\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 100 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.44e+05 - 1.00e+00 2.64e-04h 10\n", + " 101 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.44e+05 - 1.20e-01 2.63e-04h 10\n", + " 102 0.0000000e+00 1.64e+06 4.17e+11 -1.0 2.45e+05 - 5.73e-01 1.34e-01w 1\n", + " 103 0.0000000e+00 1.64e+06 7.61e+13 -1.0 7.71e+05 - 6.17e-02 4.35e-04w 1\n", + " 104 0.0000000e+00 1.64e+06 1.17e+17 -1.0 7.81e+05 - 2.38e-01 4.29e-06w 1\n", + " 105 0.0000000e+00 1.21e+05 1.05e+11 -1.0 7.81e+05 - 5.73e-01 2.62e-04h 9\n", + " 106 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.45e+05 - 1.34e-01 2.61e-04h 10\n", + " 107 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.46e+05 - 1.00e+00 2.60e-04h 10\n", + " 108 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.47e+05 - 1.20e-01 2.59e-04h 10\n", + " 109 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.47e+05 - 5.71e-01 2.58e-04h 10\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 110 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.48e+05 - 1.33e-01 2.57e-04h 10\n", + " 111 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.49e+05 - 1.00e+00 2.56e-04h 10\n", + " 112 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.49e+05 - 1.19e-01 2.55e-04h 10\n", + " 113 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.50e+05 - 5.68e-01 2.54e-04h 10\n", + " 114 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.50e+05 - 1.33e-01 2.53e-04h 10\n", + " 115 0.0000000e+00 1.59e+06 7.85e+11 -1.0 2.51e+05 - 1.00e+00 1.29e-01w 1\n", + " 116 0.0000000e+00 1.59e+06 1.28e+14 -1.0 7.75e+05 - 6.01e-02 4.25e-04w 1\n", + " 117 0.0000000e+00 1.59e+06 1.19e+17 -1.0 7.85e+05 - 1.23e-01 4.19e-06w 1\n", + " 118 0.0000000e+00 1.21e+05 1.05e+11 -1.0 7.85e+05 - 1.00e+00 2.52e-04h 9\n", + " 119 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.52e+05 - 1.19e-01 2.51e-04h 10\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 120 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.52e+05 - 5.66e-01 2.50e-04h 10\n", + " 121 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.53e+05 - 1.32e-01 2.49e-04h 10\n", + " 122 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.53e+05 - 1.00e+00 2.47e-04h 10\n", + " 123 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.54e+05 - 1.18e-01 4.93e-04h 9\n", + " 124 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.55e+05 - 5.60e-01 4.89e-04h 9\n", + " 125 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.56e+05 - 1.32e-01 4.85e-04h 9\n", + " 126 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.58e+05 - 1.00e+00 4.81e-04h 9\n", + " 127 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.59e+05 - 1.18e-01 4.77e-04h 9\n", + " 128 0.0000000e+00 1.52e+06 4.74e+11 -1.0 2.60e+05 - 5.55e-01 1.21e-01w 1\n", + " 129 0.0000000e+00 1.52e+06 9.09e+13 -1.0 7.80e+05 - 6.17e-02 4.09e-04w 1\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 130 0.0000000e+00 1.52e+06 1.22e+17 -1.0 7.90e+05 - 2.48e-01 4.04e-06w 1\n", + " 131 0.0000000e+00 1.20e+05 1.05e+11 -1.0 7.90e+05 - 5.55e-01 4.73e-04h 8\n", + " 132 0.0000000e+00 1.20e+05 1.05e+11 -1.0 2.61e+05 - 1.31e-01 4.69e-04h 9\n", + " 133 0.0000000e+00 1.20e+05 1.05e+11 -1.0 2.63e+05 - 1.00e+00 1.86e-03h 7\n", + " 134 0.0000000e+00 1.20e+05 1.05e+11 -1.0 2.67e+05 - 1.16e-01 1.80e-03h 7\n", + " 135 0.0000000e+00 1.20e+05 1.05e+11 -1.0 2.72e+05 - 5.16e-01 1.74e-03h 7\n", + " 136 0.0000000e+00 1.19e+05 1.05e+11 -1.0 2.77e+05 - 1.29e-01 3.38e-03h 6\n", + " 137 0.0000000e+00 1.19e+05 1.05e+11 -1.0 2.87e+05 - 1.00e+00 3.17e-03h 6\n", + " 138 0.0000000e+00 1.19e+05 1.06e+11 -1.0 2.98e+05 - 1.10e-01 2.97e-03h 6\n", + " 139 0.0000000e+00 1.18e+05 1.06e+11 -1.0 3.08e+05 - 4.88e-01 2.79e-03h 6\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 140 0.0000000e+00 1.18e+05 1.07e+11 -1.0 3.18e+05 - 1.22e-01 5.25e-03h 5\n", + " 141 0.0000000e+00 1.01e+06 1.89e+12 -1.0 3.38e+05 - 1.00e+00 7.42e-02w 1\n", + " 142 0.0000000e+00 1.01e+06 3.99e+14 -1.0 8.16e+05 - 5.93e-02 3.07e-04w 1\n", + " 143 0.0000000e+00 1.01e+06 1.56e+17 -1.0 8.24e+05 - 1.44e-01 3.04e-06w 1\n", + " 144 0.0000000e+00 1.17e+05 1.08e+11 -1.0 8.24e+05 - 1.00e+00 4.64e-03h 4\n", + " 145 0.0000000e+00 1.17e+05 1.08e+11 -1.0 3.59e+05 - 1.01e-01 2.06e-03h 6\n", + " 146 0.0000000e+00 1.17e+05 1.08e+11 -1.0 3.69e+05 - 4.67e-01 1.94e-03h 6\n", + " 147 0.0000000e+00 1.17e+05 1.08e+11 -1.0 3.79e+05 - 1.12e-01 1.83e-03h 6\n", + " 148 0.0000000e+00 1.16e+05 1.09e+11 -1.0 3.89e+05 - 1.00e+00 8.66e-04h 7\n", + " 149 0.0000000e+00 1.16e+05 1.09e+11 -1.0 3.94e+05 - 9.61e-02 8.42e-04h 7\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 150 0.0000000e+00 1.16e+05 1.09e+11 -1.0 3.99e+05 - 4.61e-01 4.10e-04h 8\n", + " 151 0.0000000e+00 1.16e+05 1.09e+11 -1.0 4.01e+05 - 1.09e-01 4.04e-04h 8\n", + " 152 0.0000000e+00 1.16e+05 1.09e+11 -1.0 4.04e+05 - 1.00e+00 3.98e-04h 8\n", + " 153 0.0000000e+00 1.16e+05 1.09e+11 -1.0 4.06e+05 - 9.46e-02 9.83e-05h 10\n", + " 154 0.0000000e+00 6.92e+05 1.57e+12 -1.0 4.07e+05 - 4.54e-01 5.01e-02w 1\n", + " 155 0.0000000e+00 6.92e+05 4.69e+14 -1.0 8.35e+05 - 6.23e-02 2.42e-04w 1\n", + " 156 0.0000000e+00 6.92e+05 1.93e+17 -1.0 8.42e+05 - 2.97e-01 2.40e-06w 1\n", + " 157 0.0000000e+00 1.16e+05 1.09e+11 -1.0 8.42e+05 - 4.54e-01 9.79e-05h 9\n", + " 158 0.0000000e+00 1.16e+05 1.09e+11 -1.0 4.08e+05 - 1.09e-01 9.76e-05h 10\n", + " 159 0.0000000e+00 1.16e+05 1.09e+11 -1.0 4.08e+05 - 1.00e+00 9.73e-05h 10\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 160 0.0000000e+00 1.16e+05 1.09e+11 -1.0 4.09e+05 - 9.43e-02 9.70e-05h 10\n", + " 161 0.0000000e+00 1.16e+05 1.09e+11 -1.0 4.09e+05 - 4.54e-01 4.83e-05h 11\n", + " 162 0.0000000e+00 1.16e+05 1.09e+11 -1.0 4.10e+05 - 1.08e-01 4.82e-05h 11\n", + " 163 0.0000000e+00 1.16e+05 1.09e+11 -1.0 4.10e+05 - 1.00e+00 1.20e-05h 13\n", + " 164 0.0000000e+00 1.16e+05 1.09e+11 -1.0 4.10e+05 - 9.42e-02 1.20e-05h 13\n", + " 165 0.0000000e+00 1.16e+05 1.09e+11 -1.0 4.10e+05 - 4.55e-01 9.62e-05h 10\n", + " 166 0.0000000e+00 1.16e+05 1.09e+11 -1.0 4.11e+05 - 1.08e-01 9.59e-05h 10\n", + " 167 0.0000000e+00 6.75e+05 3.70e+12 -1.0 4.11e+05 - 1.00e+00 4.89e-02w 1\n", + " 168 0.0000000e+00 6.74e+05 9.73e+14 -1.0 8.36e+05 - 5.90e-02 2.39e-04w 1\n", + " 169 0.0000000e+00 6.74e+05 1.96e+17 -1.0 8.43e+05 - 1.50e-01 2.37e-06w 1\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 170 0.0000000e+00 1.16e+05 1.09e+11 -1.0 8.43e+05 - 1.00e+00 9.56e-05h 9\n", + " 171 0.0000000e+00 1.16e+05 1.09e+11 -1.0 4.12e+05 - 9.39e-02 2.38e-05h 12\n", + " 172 0.0000000e+00 1.16e+05 1.09e+11 -1.0 4.12e+05 - 4.52e-01 1.19e-05h 13\n", + " 173r 0.0000000e+00 1.16e+05 1.00e+03 0.6 0.00e+00 - 0.00e+00 3.72e-07R 18\n", + " 174r 0.0000000e+00 1.25e+05 1.78e+03 0.6 3.70e+04 - 1.19e-02 1.34e-03f 1\n", + " 175r 0.0000000e+00 1.10e+05 1.54e+03 0.6 7.30e+03 - 3.61e-03 8.33e-03f 1\n", + " 176r 0.0000000e+00 1.02e+05 4.12e+03 0.6 5.25e+03 - 6.33e-02 8.82e-03f 1\n", + " 177r 0.0000000e+00 9.63e+04 4.43e+03 0.6 4.50e+03 - 3.61e-02 2.36e-02f 1\n", + " 178r 0.0000000e+00 9.20e+04 1.63e+04 0.6 3.97e+03 - 2.78e-01 6.99e-02f 1\n", + " 179r 0.0000000e+00 8.79e+04 3.34e+04 0.6 4.66e+02 - 8.68e-01 3.38e-01f 1\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 180r 0.0000000e+00 1.06e+05 3.33e+02 0.6 1.05e+02 - 1.00e+00 1.00e+00f 1\n", + " 181r 0.0000000e+00 1.05e+05 2.26e+02 0.6 2.72e+01 - 1.00e+00 1.00e+00f 1\n", + " 182r 0.0000000e+00 9.53e+04 2.03e+03 -0.1 2.12e+02 - 1.00e+00 7.99e-01f 1\n", + " 183r 0.0000000e+00 8.64e+04 8.95e+01 -0.1 8.37e+01 - 1.00e+00 1.00e+00f 1\n", + " 184r 0.0000000e+00 8.52e+04 9.30e-01 -0.1 4.13e+01 - 1.00e+00 1.00e+00h 1\n", + " 185r 0.0000000e+00 7.70e+04 7.43e+02 -2.2 2.70e+02 - 8.39e-01 7.19e-01f 1\n", + " 186r 0.0000000e+00 7.27e+04 1.42e+03 -2.2 1.95e+03 - 9.32e-01 6.47e-01f 1\n", + " 187r 0.0000000e+00 7.36e+04 5.69e+02 -2.2 4.90e+02 - 9.15e-01 6.73e-01f 1\n", + " 188r 0.0000000e+00 7.91e+04 2.05e+02 -2.2 2.75e+02 - 1.00e+00 8.69e-01f 1\n", + " 189r 0.0000000e+00 7.92e+04 1.27e+03 -2.2 1.37e+02 - 8.68e-01 1.06e-01f 1\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 190r 0.0000000e+00 7.99e+04 8.09e+01 -2.2 1.45e+02 - 1.00e+00 1.00e+00f 1\n", + " 191r 0.0000000e+00 8.00e+04 1.81e+01 -2.2 2.82e+00 - 1.00e+00 1.00e+00f 1\n", + " 192r 0.0000000e+00 8.00e+04 6.93e-02 -2.2 6.43e-02 - 1.00e+00 1.00e+00h 1\n", + " 193r 0.0000000e+00 8.03e+04 1.79e+02 -3.3 1.75e+01 - 9.50e-01 8.57e-01f 1\n", + " 194r 0.0000000e+00 4.29e+04 3.40e+02 -3.3 8.69e+02 - 1.00e+00 6.02e-01f 1\n", + " 195r 0.0000000e+00 1.36e+04 1.76e+01 -3.3 3.71e+02 - 1.00e+00 1.00e+00f 1\n", + " 196r 0.0000000e+00 1.99e+04 5.38e-01 -3.3 7.89e+01 - 1.00e+00 1.00e+00h 1\n", + " 197r 0.0000000e+00 1.73e+04 9.03e-02 -3.3 3.20e+01 - 1.00e+00 1.00e+00h 1\n", + " 198r 0.0000000e+00 1.64e+04 1.32e-02 -3.3 1.07e+01 - 1.00e+00 1.00e+00h 1\n", + " 199r 0.0000000e+00 1.48e+04 9.83e+01 -4.9 2.40e+01 - 8.62e-01 7.80e-01f 1\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 200r 0.0000000e+00 1.44e+04 1.92e+03 -4.9 1.71e+03 - 6.01e-01 1.17e-02f 1\n", + "\n", + "Number of Iterations....: 200\n", + "\n", + " (scaled) (unscaled)\n", + "Objective...............: 0.0000000000000000e+00 0.0000000000000000e+00\n", + "Dual infeasibility......: 8.8349814638582666e+02 8.8349814638582666e+02\n", + "Constraint violation....: 3.3086142754792485e-04 1.4369018418593043e+04\n", + "Complementarity.........: 6.5630241357471754e-04 6.5630241357471754e-04\n", + "Overall NLP error.......: 3.3086142754792485e-04 1.4369018418593043e+04\n", + "\n", + "\n", + "Number of objective function evaluations = 1605\n", + "Number of objective gradient evaluations = 175\n", + "Number of equality constraint evaluations = 1605\n", + "Number of inequality constraint evaluations = 0\n", + "Number of equality constraint Jacobian evaluations = 202\n", + "Number of inequality constraint Jacobian evaluations = 0\n", + "Number of Lagrangian Hessian evaluations = 200\n", + "Total CPU secs in IPOPT (w/o function evaluations) = 0.331\n", + "Total CPU secs in NLP function evaluations = 0.025\n", + "\n", + "EXIT: Maximum Number of Iterations Exceeded.\n", + "WARNING: Loading a SolverResults object with a warning status into\n", + "model.name=\"unknown\";\n", + " - termination condition: maxIterations\n", + " - message from solver: Ipopt 3.13.2\\x3a Maximum Number of Iterations\n", + " Exceeded.\n" + ] + } + ], + "execution_count": 52 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "Now that the flowsheet is initialized, we can unfix the guesses for the `Heater` and reactive the tear stream to complete the final solve." + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:31.967629Z", + "start_time": "2025-06-11T22:13:31.943574Z" + } + }, + "cell_type": "code", + "source": [ + "for k, v in tear_guesses.items():\n", + " for k1, v1 in v.items():\n", + " getattr(m.fs.H101.inlet, k)[k1].unfix()\n", + "\n", + "m.fs.s03_expanded.activate()\n", + "print(f\"The DOF is {degrees_of_freedom(m)} after unfixing the values and reactivating the tear stream\")" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The DOF is 0 after unfixing the values and reactivating the tear stream\n" + ] + } + ], + "execution_count": 53 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "## 6 Solving the Model" + }, + { + "cell_type": "markdown", + "metadata": { + "tags": [] + }, + "source": "We have now initialized the flowsheet. Lets set up some solving options before simulating the flowsheet. We want to specify the scaling method, number of iterations, and tolerance. More specific or advanced options can be found at the documentation for IPOPT https://coin-or.github.io/Ipopt/OPTIONS.html" + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:31.982093Z", + "start_time": "2025-06-11T22:13:31.978929Z" + } + }, + "cell_type": "code", + "source": [ + "optarg = {\n", + " 'nlp_scaling_method': 'user-scaling',\n", + " 'OF_ma57_automatic_scaling': 'yes',\n", + " 'max_iter': 500,\n", + " 'tol': 1e-8,\n", + "}" + ], + "outputs": [], + "execution_count": 54 + }, + { + "metadata": { + "tags": [ + "exercise" + ] + }, + "cell_type": "markdown", + "source": [ + "
\n", + "Inline Exercise:\n", + "Let us run the flowsheet in a simulation mode to look at the results. To do this, complete the last line of code where we pass the model to the solver. You will need to type the following:\n", + "\n", + "solver = get_solver(solver_options=optarg)
\n", + "results = solver.solve(m, tee=True)\n", + "\n", + "Use Shift+Enter to run the cell once you have typed in your code.\n", + "
\n" + ] + }, + { + "cell_type": "code", + "metadata": { + "tags": [ + "exercise" + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:32.012062Z", + "start_time": "2025-06-11T22:13:32.008341Z" + } + }, + "source": [ + "# Create the solver object\n", + "\n", + "# Solve the model" + ], + "outputs": [], + "execution_count": 55 + }, + { + "cell_type": "code", + "metadata": { + "tags": [ + "solution" + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:32.320588Z", + "start_time": "2025-06-11T22:13:32.041379Z" + } + }, + "source": [ + "# Create the solver object\n", + "solver = get_solver(solver_options=optarg)\n", + "\n", + "# Solve the model\n", + "results = solver.solve(m, tee=True)\n" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "WARNING: model contains export suffix 'scaling_factor' that contains 30\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "Ipopt 3.13.2: nlp_scaling_method=user-scaling\n", + "tol=1e-08\n", + "max_iter=500\n", + "option_file_name=C:\\Users\\Tanner\\AppData\\Local\\Temp\\tmp1n1inahr_ipopt.opt\n", + "\n", + "Using option file \"C:\\Users\\Tanner\\AppData\\Local\\Temp\\tmp1n1inahr_ipopt.opt\".\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...: 1163\n", + "Number of nonzeros in inequality constraint Jacobian.: 0\n", + "Number of nonzeros in Lagrangian Hessian.............: 1062\n", + "\n", + "Total number of variables............................: 390\n", + " variables with only lower bounds: 0\n", + " variables with lower and upper bounds: 196\n", + " variables with only upper bounds: 0\n", + "Total number of equality constraints.................: 390\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 7.98e+04 0.00e+00 -1.0 0.00e+00 - 0.00e+00 0.00e+00 0\n", + " 1 0.0000000e+00 7.94e+04 1.91e+01 -1.0 1.01e+05 - 4.59e-03 3.67e-03h 3\n", + " 2 0.0000000e+00 7.91e+04 2.90e+01 -1.0 9.74e+04 - 1.04e-02 2.90e-03h 3\n", + " 3 0.0000000e+00 8.53e+04 3.58e+01 -1.0 1.09e+05 - 3.03e-02 2.27e-03h 3\n", + " 4 0.0000000e+00 9.38e+04 9.70e+01 -1.0 1.22e+05 - 2.53e-02 1.76e-03h 3\n", + " 5 0.0000000e+00 9.86e+04 7.07e+02 -1.0 1.32e+05 - 5.90e-02 1.36e-03h 3\n", + " 6 0.0000000e+00 1.01e+05 5.02e+03 -1.0 1.41e+05 - 2.55e-02 1.04e-03h 3\n", + " 7 0.0000000e+00 1.03e+05 1.80e+05 -1.0 1.47e+05 - 1.27e-01 7.95e-04h 3\n", + " 8 0.0000000e+00 1.04e+05 2.00e+06 -1.0 1.52e+05 - 2.40e-02 6.05e-04h 3\n", + " 9 0.0000000e+00 1.04e+05 1.82e+08 -1.0 1.56e+05 - 1.71e-01 4.59e-04h 3\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 10 0.0000000e+00 1.04e+05 3.34e+09 -1.0 1.59e+05 - 2.46e-02 3.47e-04h 3\n", + " 11 0.0000000e+00 3.24e+05 3.06e+09 -1.0 2.71e-02 10.0 5.85e-01 7.83e-01h 1\n", + " 12 0.0000000e+00 1.46e+06 3.17e+11 -1.0 3.46e+04 - 7.72e-02 1.50e-01f 2\n", + " 13 0.0000000e+00 1.46e+06 2.27e+11 -1.0 1.90e+04 - 3.66e-01 2.41e-03h 3\n", + " 14 0.0000000e+00 1.46e+06 2.32e+12 -1.0 1.90e+04 - 4.42e-01 1.83e-03h 3\n", + " 15 0.0000000e+00 1.45e+06 8.61e+13 -1.0 1.89e+04 - 4.59e-01 1.39e-03h 3\n", + " 16 0.0000000e+00 1.45e+06 5.60e+14 -1.0 1.89e+04 - 3.52e-01 2.10e-03h 2\n", + " 17 0.0000000e+00 1.45e+06 5.54e+14 -1.0 1.88e+04 - 5.07e-01 1.07e-03h 2\n", + " 18 0.0000000e+00 1.45e+06 9.90e+14 -1.0 1.88e+04 - 3.35e-01 1.09e-03h 1\n", + " 19 0.0000000e+00 1.45e+06 9.94e+16 -1.0 1.87e+04 - 9.49e-01 1.09e-05h 1\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 20 0.0000000e+00 1.44e+06 9.35e+18 -1.0 4.45e+03 - 2.73e-01 2.89e-03h 1\n", + " 21r 0.0000000e+00 1.44e+06 1.00e+03 -0.6 0.00e+00 - 0.00e+00 4.43e-07R 17\n", + " 22r 0.0000000e+00 1.89e+06 2.04e+03 -0.6 6.64e+02 - 7.96e-03 2.69e-03f 1\n", + " 23r 0.0000000e+00 1.93e+06 4.07e+03 -0.6 8.47e+02 - 1.23e-02 4.48e-03f 1\n", + " 24r 0.0000000e+00 2.28e+06 1.29e+04 -0.6 9.49e+02 - 4.42e-02 1.55e-02f 1\n", + " 25r 0.0000000e+00 4.06e+06 1.16e+04 -0.6 1.05e+03 - 5.19e-02 5.05e-02f 1\n", + " 26r 0.0000000e+00 4.03e+06 6.56e+03 -0.6 8.19e+02 - 2.50e-03 1.07e-02f 1\n", + " 27r 0.0000000e+00 4.15e+06 4.04e+04 -0.6 6.67e+02 - 9.08e-02 3.63e-02f 1\n", + " 28r 0.0000000e+00 4.11e+06 4.55e+04 -0.6 4.90e+02 - 4.20e-02 3.20e-02f 1\n", + " 29r 0.0000000e+00 4.02e+06 1.82e+05 -0.6 4.75e+02 - 2.06e-01 1.68e-02f 1\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 30r 0.0000000e+00 3.02e+06 8.10e+04 -0.6 4.67e+02 - 6.59e-02 2.37e-01f 1\n", + " 31r 0.0000000e+00 2.70e+06 1.53e+05 -0.6 3.56e+02 - 4.76e-01 4.13e-01f 1\n", + " 32r 0.0000000e+00 2.13e+06 9.21e+04 -0.6 2.09e+02 - 4.07e-01 5.13e-01f 1\n", + " 33r 0.0000000e+00 8.87e+05 5.96e+04 -0.6 1.02e+02 - 5.00e-01 5.95e-01f 1\n", + " 34r 0.0000000e+00 1.36e+05 9.02e+04 -0.6 4.12e+01 - 4.56e-01 1.00e+00f 1\n", + " 35r 0.0000000e+00 4.01e+04 7.97e+04 -0.6 4.66e+00 - 5.29e-01 7.78e-01f 1\n", + " 36r 0.0000000e+00 2.04e+04 5.00e+04 -0.6 4.15e-01 0.0 3.36e-01 7.33e-01h 1\n", + " 37r 0.0000000e+00 1.98e+04 1.42e+04 -0.6 8.68e-02 1.3 1.00e+00 1.72e-01f 1\n", + " 38r 0.0000000e+00 1.85e+04 2.28e+04 -0.6 1.70e+01 - 8.27e-01 5.31e-01f 1\n", + " 39r 0.0000000e+00 2.67e+04 6.30e+03 -0.6 1.08e+01 - 1.00e+00 1.00e+00f 1\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 40r 0.0000000e+00 1.76e+04 3.54e+02 -0.6 3.83e+00 - 1.00e+00 1.00e+00f 1\n", + " 41r 0.0000000e+00 1.66e+04 6.18e+02 -0.6 2.10e+00 - 1.00e+00 1.00e+00f 1\n", + " 42r 0.0000000e+00 1.63e+04 1.63e+00 -0.6 7.37e-02 - 1.00e+00 1.00e+00h 1\n", + " 43r 0.0000000e+00 1.82e+04 9.49e+02 -2.0 8.67e+00 - 8.62e-01 7.60e-01f 1\n", + " 44r 0.0000000e+00 2.02e+04 6.57e+02 -2.0 2.22e+03 - 1.00e+00 7.24e-01f 1\n", + " 45r 0.0000000e+00 1.41e+04 2.64e+01 -2.0 1.93e-02 0.9 1.00e+00 1.00e+00f 1\n", + " 46r 0.0000000e+00 1.41e+04 3.99e+00 -2.0 1.76e-02 0.4 1.00e+00 1.00e+00h 1\n", + " 47r 0.0000000e+00 1.41e+04 5.62e-02 -2.0 3.33e-02 -0.1 1.00e+00 1.00e+00h 1\n", + " 48r 0.0000000e+00 1.42e+04 3.60e+02 -4.4 9.99e-02 -0.6 9.21e-01 7.93e-01f 1\n", + " 49r 0.0000000e+00 4.27e+04 2.99e+03 -4.4 1.29e+00 -1.1 8.82e-01 6.65e-01f 1\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 50r 0.0000000e+00 6.63e+04 1.41e+02 -4.4 3.88e+00 -1.5 9.39e-01 9.16e-01f 1\n", + " 51r 0.0000000e+00 5.62e+04 5.64e+02 -4.4 1.16e+01 -2.0 1.00e+00 1.56e-01f 1\n", + " 52r 0.0000000e+00 5.59e+04 9.07e+02 -4.4 4.50e+04 - 7.98e-02 5.51e-03f 1\n", + " 53r 0.0000000e+00 5.59e+04 1.82e+03 -4.4 2.65e+03 - 1.77e-01 2.25e-05f 1\n", + " 54r 0.0000000e+00 4.87e+04 1.59e+03 -4.4 2.65e+03 - 1.12e-01 1.38e-01f 1\n", + " 55r 0.0000000e+00 4.71e+04 2.05e+03 -4.4 2.28e+03 - 5.83e-01 3.36e-02f 1\n", + " 56r 0.0000000e+00 4.59e+04 2.00e+03 -4.4 2.21e+03 - 2.61e-02 2.52e-02f 1\n", + " 57r 0.0000000e+00 4.21e+04 1.20e+03 -4.4 2.15e+03 - 3.82e-01 9.47e-02f 1\n", + " 58r 0.0000000e+00 3.52e+04 1.00e+03 -4.4 1.95e+03 - 1.89e-01 5.87e-01f 1\n", + " 59r 0.0000000e+00 1.42e+04 3.94e+00 -4.4 8.05e+02 - 1.00e+00 1.00e+00f 1\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 60r 0.0000000e+00 1.42e+04 4.68e-01 -4.4 5.35e-01 - 1.00e+00 1.00e+00h 1\n", + " 61r 0.0000000e+00 1.42e+04 1.29e-02 -4.4 3.24e-03 - 1.00e+00 1.00e+00h 1\n", + " 62r 0.0000000e+00 1.42e+04 5.72e-06 -4.4 5.73e-05 - 1.00e+00 1.00e+00h 1\n", + " 63r 0.0000000e+00 1.42e+04 1.24e+02 -6.6 2.71e-01 - 9.90e-01 8.10e-01f 1\n", + " 64r 0.0000000e+00 8.72e+05 1.88e+02 -6.6 3.29e+04 - 5.03e-01 2.23e-01f 1\n", + " 65r 0.0000000e+00 1.05e+06 2.88e+02 -6.6 2.53e+04 - 6.78e-01 1.82e-01f 1\n", + " 66r 0.0000000e+00 1.04e+06 4.92e+02 -6.6 8.71e+03 - 8.61e-01 1.13e-02f 1\n", + " 67r 0.0000000e+00 6.27e+05 7.17e+01 -6.6 8.61e+03 - 1.00e+00 8.73e-01f 1\n", + " 68r 0.0000000e+00 2.18e+04 1.48e+00 -6.6 1.09e+03 - 1.00e+00 1.00e+00h 1\n", + " 69r 0.0000000e+00 4.71e+02 5.81e-05 -6.6 7.59e-02 - 1.00e+00 1.00e+00h 1\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 70r 0.0000000e+00 4.71e+02 1.03e-08 -6.6 1.22e-05 - 1.00e+00 1.00e+00h 1\n", + " 71r 0.0000000e+00 4.71e+02 8.08e-01 -9.0 4.39e-02 - 1.00e+00 9.74e-01f 1\n", + " 72r 0.0000000e+00 2.45e+03 1.39e+02 -9.0 2.20e+05 - 3.58e-01 2.17e-03f 1\n", + " 73r 0.0000000e+00 2.39e+03 5.81e+02 -9.0 1.06e+04 - 6.57e-01 2.41e-02f 1\n", + " 74r 0.0000000e+00 2.15e+04 7.82e+02 -9.0 1.04e+04 - 1.00e+00 1.42e-01f 1\n", + " 75r 0.0000000e+00 1.15e+04 4.72e+02 -9.0 4.34e+01 - 1.00e+00 4.66e-01h 2\n", + " 76r 0.0000000e+00 6.38e+03 2.52e+02 -9.0 4.28e+01 - 1.00e+00 4.46e-01h 2\n", + " 77r 0.0000000e+00 3.26e+03 1.28e+02 -9.0 2.42e+01 - 1.00e+00 4.89e-01h 2\n", + " 78r 0.0000000e+00 2.99e+03 1.10e+02 -9.0 1.24e+01 - 1.00e+00 1.00e+00h 1\n", + " 79r 0.0000000e+00 9.57e+01 3.79e+00 -9.0 5.11e-03 - 1.00e+00 1.00e+00h 1\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 80r 0.0000000e+00 3.70e-02 1.01e-04 -9.0 3.14e-05 - 1.00e+00 1.00e+00h 1\n", + " 81r 0.0000000e+00 4.15e-06 2.24e-09 -9.0 8.12e-09 - 1.00e+00 1.00e+00h 1\n", + "\n", + "Number of Iterations....: 81\n", + "\n", + " (scaled) (unscaled)\n", + "Objective...............: 0.0000000000000000e+00 0.0000000000000000e+00\n", + "Dual infeasibility......: 0.0000000000000000e+00 0.0000000000000000e+00\n", + "Constraint violation....: 2.0579515874459978e-11 4.1499733924865723e-06\n", + "Complementarity.........: 0.0000000000000000e+00 0.0000000000000000e+00\n", + "Overall NLP error.......: 2.0579515874459978e-11 4.1499733924865723e-06\n", + "\n", + "\n", + "Number of objective function evaluations = 162\n", + "Number of objective gradient evaluations = 23\n", + "Number of equality constraint evaluations = 162\n", + "Number of inequality constraint evaluations = 0\n", + "Number of equality constraint Jacobian evaluations = 83\n", + "Number of inequality constraint Jacobian evaluations = 0\n", + "Number of Lagrangian Hessian evaluations = 81\n", + "Total CPU secs in IPOPT (w/o function evaluations) = 0.119\n", + "Total CPU secs in NLP function evaluations = 0.006\n", + "\n", + "EXIT: Optimal Solution Found.\n" + ] + } + ], + "execution_count": 56 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 7 Analyze the results\n", + "\n", + "\n" + ] + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "If the IDAES UI package was installed with the `idaes-pse` installation or installed separately, you can run the flowsheet visualizer to see a full diagram of the full process that is generated and displayed on a browser window.\n" + }, + { + "metadata": { + "tags": [ + "noauto" + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:33.874186Z", + "start_time": "2025-06-11T22:13:32.362868Z" + } + }, + "cell_type": "code", + "source": "m.fs.visualize('HDA-Flowsheet')", + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2025-06-11 16:13:33 [INFO] idaes.idaes_ui.fv.fsvis: Started visualization server\n", + "2025-06-11 16:13:33 [INFO] idaes.idaes_ui.fv.fsvis: Loading saved flowsheet from 'HDA-Flowsheet.json'\n", + "2025-06-11 16:13:33 [INFO] idaes.idaes_ui.fv.fsvis: Saving flowsheet to default file 'HDA-Flowsheet.json' in current directory (C:\\Users\\Tanner\\Documents\\git\\examples\\idaes_examples\\notebooks\\docs\\tut\\core)\n", + "2025-06-11 16:13:33 [INFO] idaes.idaes_ui.fv.fsvis: Flowsheet visualization at: http://localhost:49167/app?id=HDA-Flowsheet\n" + ] + }, + { + "data": { + "text/plain": [ + "VisualizeResult(store=, port=49167, server=, save_diagram=>)" + ] + }, + "execution_count": 58, + "metadata": {}, + "output_type": "execute_result" + } + ], + "execution_count": 58 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "Otherwise, we can run the `m.fs.report()` method to see a full summary of the solved flowsheet. It is recomended to adjust the width of the output as much as possible for the cleanest display." + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:33.985892Z", + "start_time": "2025-06-11T22:13:33.889113Z" + } + }, + "cell_type": "code", + "source": "m.fs.report()", + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "====================================================================================\n", + "Flowsheet : fs Time: 0.0\n", + "------------------------------------------------------------------------------------\n", + " Stream Table\n", + " Units s01 s02 s03 s04 s05 s06 s07 s08 s09 s10 s11 s12 \n", + " flow_mol_phase_comp ('Liq', 'benzene') mole / second 1.0000e-05 1.0000e-05 2.0008e-05 1.2993e-07 1.2993e-07 1.0000e-08 0.20460 8.0000e-09 8.0000e-09 1.0000e-08 0.062620 2.0000e-09\n", + " flow_mol_phase_comp ('Liq', 'toluene') mole / second 0.30000 1.0000e-05 0.30001 8.4149e-07 8.4149e-07 1.0000e-08 0.062520 8.0000e-09 8.0000e-09 1.0000e-08 0.032257 2.0000e-09\n", + " flow_mol_phase_comp ('Liq', 'methane') mole / second 1.0000e-05 1.0000e-05 2.0008e-05 1.0000e-12 1.0000e-12 1.0000e-08 2.6712e-07 8.0000e-09 8.0000e-09 1.0000e-08 9.4877e-08 2.0000e-09\n", + " flow_mol_phase_comp ('Liq', 'hydrogen') mole / second 1.0000e-05 1.0000e-05 2.0008e-05 1.0000e-12 1.0000e-12 1.0000e-08 2.6712e-07 8.0000e-09 8.0000e-09 1.0000e-08 9.4877e-08 2.0000e-09\n", + " flow_mol_phase_comp ('Vap', 'benzene') mole / second 1.0000e-05 1.0000e-05 0.11934 0.11936 0.35374 0.14915 1.0000e-08 0.11932 0.11932 0.14198 1.0000e-08 0.029829\n", + " flow_mol_phase_comp ('Vap', 'toluene') mole / second 1.0000e-05 1.0000e-05 0.012508 0.31252 0.078129 0.015610 1.0000e-08 0.012488 0.012488 0.030264 1.0000e-08 0.0031219\n", + " flow_mol_phase_comp ('Vap', 'methane') mole / second 1.0000e-05 0.020000 1.0377 1.0377 1.2721 1.2721 1.0000e-08 1.0177 1.0177 1.8224e-07 1.0000e-08 0.25442\n", + " flow_mol_phase_comp ('Vap', 'hydrogen') mole / second 1.0000e-05 0.30000 0.56258 0.56260 0.32821 0.32821 1.0000e-08 0.26257 0.26257 1.8224e-07 1.0000e-08 0.065642\n", + " temperature kelvin 303.20 303.20 314.09 600.00 771.85 325.00 325.00 325.00 325.00 375.00 375.00 325.00\n", + " pressure pascal 3.5000e+05 3.5000e+05 3.5000e+05 3.5000e+05 3.5000e+05 3.5000e+05 3.5000e+05 3.5000e+05 3.5000e+05 1.5000e+05 1.5000e+05 3.5000e+05\n", + "====================================================================================\n" + ] + } + ], + "execution_count": 59 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "What is the total operating cost?" + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:34.015352Z", + "start_time": "2025-06-11T22:13:34.012518Z" + } + }, + "source": [ + "print(\"operating cost = $\", value(m.fs.operating_cost))" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "operating cost = $ 419122.33874473866\n" + ] + } + ], + "execution_count": 60 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": "For this operating cost, what is the amount of benzene we are able to produce and what purity we are able to achieve? We can look at a specific unit models stream table with the same `report()` method." + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:34.197557Z", + "start_time": "2025-06-11T22:13:34.174732Z" + } + }, + "source": [ + "m.fs.F102.report()\n", + "\n", + "print()\n", + "print(\"benzene purity = \", value(m.fs.purity))" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "====================================================================================\n", + "Unit : fs.F102 Time: 0.0\n", + "------------------------------------------------------------------------------------\n", + " Unit Performance\n", + "\n", + " Variables: \n", + "\n", + " Key : Value : Units : Fixed : Bounds\n", + " Heat Duty : 7352.5 : watt : False : (None, None)\n", + " Pressure Change : -2.0000e+05 : pascal : True : (None, None)\n", + "\n", + "------------------------------------------------------------------------------------\n", + " Stream Table\n", + " Units Inlet Vapor Outlet Liquid Outlet\n", + " flow_mol_phase_comp ('Liq', 'benzene') mole / second 0.20460 1.0000e-08 0.062620 \n", + " flow_mol_phase_comp ('Liq', 'toluene') mole / second 0.062520 1.0000e-08 0.032257 \n", + " flow_mol_phase_comp ('Liq', 'methane') mole / second 2.6712e-07 1.0000e-08 9.4877e-08 \n", + " flow_mol_phase_comp ('Liq', 'hydrogen') mole / second 2.6712e-07 1.0000e-08 9.4877e-08 \n", + " flow_mol_phase_comp ('Vap', 'benzene') mole / second 1.0000e-08 0.14198 1.0000e-08 \n", + " flow_mol_phase_comp ('Vap', 'toluene') mole / second 1.0000e-08 0.030264 1.0000e-08 \n", + " flow_mol_phase_comp ('Vap', 'methane') mole / second 1.0000e-08 1.8224e-07 1.0000e-08 \n", + " flow_mol_phase_comp ('Vap', 'hydrogen') mole / second 1.0000e-08 1.8224e-07 1.0000e-08 \n", + " temperature kelvin 325.00 375.00 375.00 \n", + " pressure pascal 3.5000e+05 1.5000e+05 1.5000e+05 \n", + "====================================================================================\n", + "\n", + "benzene purity = 0.8242962943922332\n" + ] + } + ], + "execution_count": 62 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": "Next, let's look at how much benzene we are losing with the light gases out of F101. IDAES has tools for creating stream tables based on the `Arcs` and/or `Ports` in a flowsheet. Let us create and print a simple stream table showing the stream leaving the reactor and the vapor stream from F101." + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:34.253529Z", + "start_time": "2025-06-11T22:13:34.238100Z" + } + }, + "source": [ + "from idaes.core.util.tables import (\n", + " create_stream_table_dataframe,\n", + " stream_table_dataframe_to_string,\n", + ")\n", + "\n", + "st = create_stream_table_dataframe({\"Reactor\": m.fs.s05, \"Light Gases\": m.fs.s06})\n", + "print(stream_table_dataframe_to_string(st))" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " Units Reactor Light Gases\n", + "flow_mol_phase_comp ('Liq', 'benzene') mole / second 1.2993e-07 1.0000e-08 \n", + "flow_mol_phase_comp ('Liq', 'toluene') mole / second 8.4149e-07 1.0000e-08 \n", + "flow_mol_phase_comp ('Liq', 'methane') mole / second 1.0000e-12 1.0000e-08 \n", + "flow_mol_phase_comp ('Liq', 'hydrogen') mole / second 1.0000e-12 1.0000e-08 \n", + "flow_mol_phase_comp ('Vap', 'benzene') mole / second 0.35374 0.14915 \n", + "flow_mol_phase_comp ('Vap', 'toluene') mole / second 0.078129 0.015610 \n", + "flow_mol_phase_comp ('Vap', 'methane') mole / second 1.2721 1.2721 \n", + "flow_mol_phase_comp ('Vap', 'hydrogen') mole / second 0.32821 0.32821 \n", + "temperature kelvin 771.85 325.00 \n", + "pressure pascal 3.5000e+05 3.5000e+05 \n" + ] + } + ], + "execution_count": 64 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 8 Optimization\n", + "\n", + "\n", + "We saw from the results above that the total operating cost for the base case was $419,122 per year. We are producing 0.142 mol/s of benzene at a purity of 82\\%. However, we are losing around 42\\% of benzene in F101 vapor outlet stream. \n", + "\n", + "Let us try to minimize this cost such that:\n", + "- we are producing at least 0.15 mol/s of benzene in F102 vapor outlet i.e. our product stream\n", + "- purity of benzene i.e. the mole fraction of benzene in F102 vapor outlet is at least 80%\n", + "- restricting the benzene loss in F101 vapor outlet to less than 20%\n", + "\n", + "For this problem, our decision variables are as follows:\n", + "- H101 outlet temperature\n", + "- R101 cooling duty provided\n", + "- F101 outlet temperature\n", + "- F102 outlet temperature\n", + "- F102 deltaP in the flash tank\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let us declare our objective function for this problem. " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:34.276719Z", + "start_time": "2025-06-11T22:13:34.271416Z" + } + }, + "source": [ + "m.fs.objective = Objective(expr=m.fs.operating_cost)" + ], + "outputs": [], + "execution_count": 65 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, we need to unfix the decision variables as we had solved a square problem (degrees of freedom = 0) until now. " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:34.337438Z", + "start_time": "2025-06-11T22:13:34.332415Z" + } + }, + "source": [ + "m.fs.H101.outlet.temperature.unfix()\n", + "m.fs.R101.heat_duty.unfix()\n", + "m.fs.F101.vap_outlet.temperature.unfix()\n", + "m.fs.F102.vap_outlet.temperature.unfix()" + ], + "outputs": [], + "execution_count": 66 + }, + { + "cell_type": "markdown", + "metadata": { + "tags": [ + "exercise" + ] + }, + "source": [ + "
\n", + "Inline Exercise:\n", + "Let us now unfix the remaining variable which is F102 pressure drop (F102.deltaP) \n", + "\n", + "Use Shift+Enter to run the cell once you have typed in your code. \n", + "
\n", + "\n" + ] + }, + { + "cell_type": "code", + "metadata": { + "tags": [ + "exercise" + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:34.399247Z", + "start_time": "2025-06-11T22:13:34.395221Z" + } + }, + "source": [ + "# Todo: Unfix deltaP for F102" + ], + "outputs": [], + "execution_count": 67 + }, + { + "cell_type": "code", + "metadata": { + "tags": [ + "solution" + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:34.463653Z", + "start_time": "2025-06-11T22:13:34.459960Z" + } + }, + "source": [ + "# Todo: Unfix deltaP for F102\n", + "m.fs.F102.deltaP.unfix()" + ], + "outputs": [], + "execution_count": 68 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next, we need to set bounds on these decision variables to values shown below:\n", + "\n", + " - H101 outlet temperature [500, 600] K\n", + " - R101 outlet temperature [600, 800] K\n", + " - F101 outlet temperature [298, 450] K\n", + " - F102 outlet temperature [298, 450] K\n", + " - F102 outlet pressure [105000, 110000] Pa\n", + "\n", + "Let us first set the variable bound for the H101 outlet temperature as shown below:" + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:34.589902Z", + "start_time": "2025-06-11T22:13:34.585528Z" + } + }, + "source": [ + "m.fs.H101.outlet.temperature[0].setlb(500)\n", + "m.fs.H101.outlet.temperature[0].setub(600)" + ], + "outputs": [], + "execution_count": 70 + }, + { + "cell_type": "markdown", + "metadata": { + "tags": [ + "exercise" + ] + }, + "source": [ + "
\n", + "Inline Exercise:\n", + "Now, set the variable bound for the R101 outlet temperature.\n", + "\n", + "Use Shift+Enter to run the cell once you have typed in your code. \n", + "
" + ] + }, + { + "cell_type": "code", + "metadata": { + "tags": [ + "exercise" + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:34.678092Z", + "start_time": "2025-06-11T22:13:34.673513Z" + } + }, + "source": [ + "# Todo: Set the bounds for reactor outlet temperature" + ], + "outputs": [], + "execution_count": 71 + }, + { + "cell_type": "code", + "metadata": { + "tags": [ + "solution" + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:34.713177Z", + "start_time": "2025-06-11T22:13:34.709843Z" + } + }, + "source": [ + "# Todo: Set the bounds for reactor outlet temperature\n", + "m.fs.R101.outlet.temperature[0].setlb(600)\n", + "m.fs.R101.outlet.temperature[0].setub(800)" + ], + "outputs": [], + "execution_count": 72 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let us fix the bounds for the rest of the decision variables. " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:34.763569Z", + "start_time": "2025-06-11T22:13:34.759916Z" + } + }, + "source": [ + "m.fs.F101.vap_outlet.temperature[0].setlb(298.0)\n", + "m.fs.F101.vap_outlet.temperature[0].setub(450.0)\n", + "m.fs.F102.vap_outlet.temperature[0].setlb(298.0)\n", + "m.fs.F102.vap_outlet.temperature[0].setub(450.0)\n", + "m.fs.F102.vap_outlet.pressure[0].setlb(105000)\n", + "m.fs.F102.vap_outlet.pressure[0].setub(110000)" + ], + "outputs": [], + "execution_count": 73 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, the only things left to define are our constraints on overhead loss in F101, product flow rate and purity in F102. Let us first look at defining a constraint for the overhead loss in F101 where we are restricting the benzene leaving the vapor stream to less than 20 \\% of the benzene available in the reactor outlet. " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:34.806470Z", + "start_time": "2025-06-11T22:13:34.801455Z" + } + }, + "source": [ + "m.fs.overhead_loss = Constraint(\n", + " expr=m.fs.F101.vap_outlet.flow_mol_phase_comp[0, \"Vap\", \"benzene\"]\n", + " <= 0.20 * m.fs.R101.outlet.flow_mol_phase_comp[0, \"Vap\", \"benzene\"]\n", + ")" + ], + "outputs": [], + "execution_count": 74 + }, + { + "cell_type": "markdown", + "metadata": { + "tags": [ + "exercise" + ] + }, + "source": [ + "
\n", + "Inline Exercise:\n", + "Now, add the constraint such that we are producing at least 0.15 mol/s of benzene in the product stream which is the vapor outlet of F102. Let us name this constraint as m.fs.product_flow. \n", + "\n", + "Use Shift+Enter to run the cell once you have typed in your code. \n", + "
" + ] + }, + { + "cell_type": "code", + "metadata": { + "tags": [ + "exercise" + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:34.837136Z", + "start_time": "2025-06-11T22:13:34.832491Z" + } + }, + "source": [ + "# Todo: Add minimum product flow constraint" + ], + "outputs": [], + "execution_count": 75 + }, + { + "cell_type": "code", + "metadata": { + "tags": [ + "solution" + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:34.887385Z", + "start_time": "2025-06-11T22:13:34.882862Z" + } + }, + "source": [ + "# Todo: Add minimum product flow constraint\n", + "m.fs.product_flow = Constraint(\n", + " expr=m.fs.F102.vap_outlet.flow_mol_phase_comp[0, \"Vap\", \"benzene\"] >= 0.15\n", + ")" + ], + "outputs": [], + "execution_count": 76 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let us add the final constraint on product purity or the mole fraction of benzene in the product stream such that it is at least greater than 80%. " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:34.917217Z", + "start_time": "2025-06-11T22:13:34.912683Z" + } + }, + "source": [ + "m.fs.product_purity = Constraint(expr=m.fs.purity >= 0.80)" + ], + "outputs": [], + "execution_count": 77 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "We have now defined the optimization problem and we are now ready to solve this problem. \n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:35.209235Z", + "start_time": "2025-06-11T22:13:34.962108Z" + } + }, + "source": [ + "results = solver.solve(m, tee=True)" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "WARNING: model contains export suffix 'scaling_factor' that contains 25\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "Ipopt 3.13.2: nlp_scaling_method=user-scaling\n", + "tol=1e-08\n", + "max_iter=500\n", + "option_file_name=C:\\Users\\Tanner\\AppData\\Local\\Temp\\tmpd6aww4bg_ipopt.opt\n", + "\n", + "Using option file \"C:\\Users\\Tanner\\AppData\\Local\\Temp\\tmpd6aww4bg_ipopt.opt\".\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...: 1191\n", + "Number of nonzeros in inequality constraint Jacobian.: 5\n", + "Number of nonzeros in Lagrangian Hessian.............: 1065\n", + "\n", + "Total number of variables............................: 395\n", + " variables with only lower bounds: 0\n", + " variables with lower and upper bounds: 199\n", + " variables with only upper bounds: 0\n", + "Total number of equality constraints.................: 390\n", + "Total number of inequality constraints...............: 3\n", + " inequality constraints with only lower bounds: 2\n", + " inequality constraints with lower and upper bounds: 0\n", + " inequality constraints with only upper bounds: 1\n", + "\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 0 4.1912234e+05 2.99e+05 6.94e+00 -1.0 0.00e+00 - 0.00e+00 0.00e+00 0\n", + " 1 4.1133558e+05 2.94e+05 6.94e+00 -1.0 6.60e+07 - 2.90e-06 1.05e-05f 1\n", + " 2 3.2484037e+05 1.80e+06 6.94e+00 -1.0 2.33e+09 - 2.95e-07 4.87e-06f 1\n", + " 3 3.0318192e+05 1.92e+06 6.94e+00 -1.0 6.90e+08 - 1.70e-05 4.13e-06f 1\n", + " 4 3.0263181e+05 1.92e+06 2.20e+01 -1.0 1.43e+07 - 8.92e-05 1.73e-05f 1\n", + " 5 3.0137102e+05 1.92e+06 4.38e+01 -1.0 8.74e+06 - 6.63e-05 1.11e-04f 1\n", + " 6 3.0058759e+05 1.92e+06 1.37e+03 -1.0 2.21e+07 - 8.59e-06 2.17e-04f 1\n", + " 7 3.0058113e+05 1.92e+06 7.51e+04 -1.0 3.24e+05 - 2.49e-01 1.77e-04f 1\n", + " 8 3.0317331e+05 1.38e+06 2.08e+05 -1.0 4.00e+04 - 9.62e-01 2.82e-01h 1\n", + " 9 3.0372038e+05 1.26e+06 1.91e+05 -1.0 2.87e+04 - 1.50e-01 8.31e-02h 1\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 10 3.0372988e+05 1.26e+06 9.32e+05 -1.0 2.63e+04 - 1.00e+00 1.51e-03h 1\n", + " 11 3.0386427e+05 1.24e+06 1.90e+07 -1.0 2.63e+04 - 1.00e+00 2.03e-02h 2\n", + " 12 3.0393928e+05 1.22e+06 7.46e+08 -1.0 2.58e+04 - 1.00e+00 1.15e-02h 2\n", + " 13 3.0397909e+05 1.21e+06 4.87e+10 -1.0 2.55e+04 - 1.00e+00 6.13e-03h 2\n", + " 14 3.0399961e+05 1.21e+06 5.05e+12 -1.0 2.53e+04 - 1.00e+00 3.17e-03h 2\n", + " 15r 3.0399961e+05 1.21e+06 1.00e+03 -0.5 0.00e+00 - 0.00e+00 3.97e-07R 14\n", + " 16r 3.0399912e+05 1.20e+06 2.84e+04 -0.5 8.47e+03 - 4.43e-03 2.34e-03f 1\n", + " 17 3.0399884e+05 1.20e+06 5.51e+05 -1.0 2.92e+04 - 2.81e-01 2.34e-06f 2\n", + " 18 3.0402874e+05 1.19e+06 4.77e+07 -1.0 2.52e+04 - 9.90e-01 4.64e-03h 1\n", + " 19 3.0402891e+05 1.19e+06 3.49e+10 -1.0 2.51e+04 - 1.00e+00 2.64e-05h 2\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 20r 3.0402891e+05 1.19e+06 1.00e+03 -0.8 0.00e+00 - 0.00e+00 3.08e-07R 8\n", + " 21r 3.0403760e+05 1.15e+06 2.58e+04 -0.8 3.66e+03 - 5.75e-03 3.60e-03f 1\n", + " 22r 3.0404498e+05 1.08e+06 3.87e+04 -0.8 3.65e+03 - 7.70e-03 6.44e-03f 1\n", + " 23r 3.0405749e+05 9.30e+05 4.54e+04 -0.8 3.63e+03 - 1.80e-02 1.60e-02f 1\n", + " 24r 3.0406463e+05 7.06e+05 4.01e+04 -0.8 3.57e+03 - 5.64e-02 3.23e-02f 1\n", + " 25r 3.0405635e+05 4.00e+05 4.80e+04 -0.8 3.45e+03 - 1.05e-01 8.20e-02f 1\n", + " 26r 3.0403684e+05 2.04e+05 2.80e+04 -0.8 3.17e+03 - 3.66e-01 1.49e-01f 1\n", + " 27r 3.0401302e+05 2.95e+04 2.21e+04 -0.8 2.70e+03 - 1.94e-01 6.28e-01f 1\n", + " 28 3.0386768e+05 2.95e+04 1.26e+02 -1.0 3.39e+06 - 4.53e-04 3.54e-06f 1\n", + " 29 3.0384876e+05 2.95e+04 8.45e+03 -1.0 1.90e+05 - 3.01e-02 3.65e-04f 1\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 30 3.0412267e+05 2.86e+04 2.69e+05 -1.0 2.35e+04 - 9.90e-01 3.11e-02h 1\n", + " 31 3.0843430e+05 5.11e+05 1.73e+05 -1.0 2.16e+04 - 1.00e+00 4.99e-01h 1\n", + " 32 3.1273887e+05 6.10e+05 5.58e+08 -1.0 1.09e+04 - 1.00e+00 9.90e-01h 1\n", + " 33 3.1276263e+05 3.11e+05 1.78e+08 -1.0 1.01e+03 - 1.00e+00 4.97e-01h 2\n", + " 34 3.1278673e+05 7.74e+02 1.15e+08 -1.0 6.27e+02 - 1.00e+00 9.97e-01H 1\n", + " 35 3.1278674e+05 6.28e+02 5.45e+04 -1.0 1.81e+02 - 1.00e+00 1.00e+00h 1\n", + " 36 3.1278674e+05 8.80e-03 2.62e+00 -1.0 7.71e-01 - 1.00e+00 1.00e+00h 1\n", + " 37 3.1278634e+05 3.48e-05 1.58e+05 -5.7 1.36e+00 - 1.00e+00 1.00e+00f 1\n", + " 38 3.1278634e+05 3.73e-08 6.73e-02 -5.7 2.20e-04 - 1.00e+00 1.00e+00f 1\n", + " 39 3.1278634e+05 2.24e-08 4.40e-05 -5.7 8.48e-04 - 1.00e+00 1.00e+00h 1\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 40 3.1278634e+05 6.71e-08 6.16e-05 -8.6 1.75e-03 - 1.00e+00 1.00e+00h 1\n", + " 41 3.1278634e+05 3.73e-08 4.40e-05 -9.0 8.38e-04 - 1.00e+00 1.00e+00h 1\n", + " 42 3.1278634e+05 5.96e-08 4.40e-05 -9.0 1.47e-03 - 1.00e+00 1.00e+00h 1\n", + " 43 3.1278634e+05 1.49e-08 4.40e-05 -9.0 5.14e-08 - 1.00e+00 1.00e+00h 1\n", + " 44 3.1278634e+05 1.49e-08 4.40e-05 -9.0 7.39e-11 - 1.00e+00 2.44e-04h 13\n", + " 45 3.1278634e+05 1.49e-08 4.40e-05 -9.0 6.89e-11 - 1.00e+00 5.00e-01h 2\n", + " 46 3.1278634e+05 1.49e-08 4.40e-05 -9.0 7.02e-11 - 1.00e+00 1.00e+00h 1\n", + " 47 3.1278634e+05 1.49e-08 4.40e-05 -9.0 6.71e-11 - 1.00e+00 5.00e-01h 2\n", + " 48 3.1278634e+05 2.98e-08 4.40e-05 -9.0 5.67e-11 - 1.00e+00 1.00e+00h 1\n", + " 49 3.1278634e+05 2.98e-08 4.40e-05 -9.0 5.52e-11 - 1.00e+00 5.00e-01h 2\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 50 3.1278634e+05 2.98e-08 4.40e-05 -9.0 4.16e-11 - 1.00e+00 5.00e-01h 2\n", + " 51 3.1278634e+05 2.98e-08 4.40e-05 -9.0 3.65e-11 - 1.00e+00 1.25e-01h 4\n", + "\n", + "Number of Iterations....: 51\n", + "\n", + " (scaled) (unscaled)\n", + "Objective...............: 3.1278633834066673e+05 3.1278633834066673e+05\n", + "Dual infeasibility......: 4.3988857412862944e-05 6.3035118071624454e-05\n", + "Constraint violation....: 2.0579515874459978e-11 2.9802322387695312e-08\n", + "Complementarity.........: 9.2191617461622074e-10 9.2191617461622074e-10\n", + "Overall NLP error.......: 1.6340396115140405e-08 6.3035118071624454e-05\n", + "\n", + "\n", + "Number of objective function evaluations = 141\n", + "Number of objective gradient evaluations = 47\n", + "Number of equality constraint evaluations = 141\n", + "Number of inequality constraint evaluations = 141\n", + "Number of equality constraint Jacobian evaluations = 55\n", + "Number of inequality constraint Jacobian evaluations = 55\n", + "Number of Lagrangian Hessian evaluations = 52\n", + "Total CPU secs in IPOPT (w/o function evaluations) = 0.077\n", + "Total CPU secs in NLP function evaluations = 0.010\n", + "\n", + "EXIT: Solved To Acceptable Level.\n" + ] + } + ], + "execution_count": 78 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 8.1 Optimization Results\n", + "\n", + "Display the results and product specifications" + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:35.321235Z", + "start_time": "2025-06-11T22:13:35.245545Z" + } + }, + "source": [ + "print(\"operating cost = $\", value(m.fs.operating_cost))\n", + "\n", + "print()\n", + "print(\"Product flow rate and purity in F102\")\n", + "\n", + "m.fs.F102.report()\n", + "\n", + "print()\n", + "print(\"benzene purity = \", value(m.fs.purity))\n", + "\n", + "print()\n", + "print(\"Overhead loss in F101\")\n", + "m.fs.F101.report()" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "operating cost = $ 312786.33834066667\n", + "\n", + "Product flow rate and purity in F102\n", + "\n", + "====================================================================================\n", + "Unit : fs.F102 Time: 0.0\n", + "------------------------------------------------------------------------------------\n", + " Unit Performance\n", + "\n", + " Variables: \n", + "\n", + " Key : Value : Units : Fixed : Bounds\n", + " Heat Duty : 8377.0 : watt : False : (None, None)\n", + " Pressure Change : -2.4500e+05 : pascal : False : (None, None)\n", + "\n", + "------------------------------------------------------------------------------------\n", + " Stream Table\n", + " Units Inlet Vapor Outlet Liquid Outlet\n", + " flow_mol_phase_comp ('Liq', 'benzene') mole / second 0.21743 1.0000e-08 0.067425 \n", + " flow_mol_phase_comp ('Liq', 'toluene') mole / second 0.070695 1.0000e-08 0.037507 \n", + " flow_mol_phase_comp ('Liq', 'methane') mole / second 2.8812e-07 1.0000e-08 1.0493e-07 \n", + " flow_mol_phase_comp ('Liq', 'hydrogen') mole / second 2.8812e-07 1.0000e-08 1.0493e-07 \n", + " flow_mol_phase_comp ('Vap', 'benzene') mole / second 1.0000e-08 0.15000 1.0000e-08 \n", + " flow_mol_phase_comp ('Vap', 'toluene') mole / second 1.0000e-08 0.033189 1.0000e-08 \n", + " flow_mol_phase_comp ('Vap', 'methane') mole / second 1.0000e-08 1.9319e-07 1.0000e-08 \n", + " flow_mol_phase_comp ('Vap', 'hydrogen') mole / second 1.0000e-08 1.9319e-07 1.0000e-08 \n", + " temperature kelvin 301.88 362.93 362.93 \n", + " pressure pascal 3.5000e+05 1.0500e+05 1.0500e+05 \n", + "====================================================================================\n", + "\n", + "benzene purity = 0.8188276578115882\n", + "\n", + "Overhead loss in F101\n", + "\n", + "====================================================================================\n", + "Unit : fs.F101 Time: 0.0\n", + "------------------------------------------------------------------------------------\n", + " Unit Performance\n", + "\n", + " Variables: \n", + "\n", + " Key : Value : Units : Fixed : Bounds\n", + " Heat Duty : -56353. : watt : False : (None, None)\n", + " Pressure Change : 0.0000 : pascal : True : (None, None)\n", + "\n", + "------------------------------------------------------------------------------------\n", + " Stream Table\n", + " Units Inlet Vapor Outlet Liquid Outlet\n", + " flow_mol_phase_comp ('Liq', 'benzene') mole / second 4.3534e-08 1.0000e-08 0.21743 \n", + " flow_mol_phase_comp ('Liq', 'toluene') mole / second 7.5866e-07 1.0000e-08 0.070695 \n", + " flow_mol_phase_comp ('Liq', 'methane') mole / second 1.0000e-12 1.0000e-08 2.8812e-07 \n", + " flow_mol_phase_comp ('Liq', 'hydrogen') mole / second 1.0000e-12 1.0000e-08 2.8812e-07 \n", + " flow_mol_phase_comp ('Vap', 'benzene') mole / second 0.27178 0.054356 1.0000e-08 \n", + " flow_mol_phase_comp ('Vap', 'toluene') mole / second 0.076085 0.0053908 1.0000e-08 \n", + " flow_mol_phase_comp ('Vap', 'methane') mole / second 1.2414 1.2414 1.0000e-08 \n", + " flow_mol_phase_comp ('Vap', 'hydrogen') mole / second 0.35887 0.35887 1.0000e-08 \n", + " temperature kelvin 696.11 301.88 301.88 \n", + " pressure pascal 3.5000e+05 3.5000e+05 3.5000e+05 \n", + "====================================================================================\n" + ] + } + ], + "execution_count": 80 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Display optimal values for the decision variables" + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:35.405538Z", + "start_time": "2025-06-11T22:13:35.398221Z" + } + }, + "source": [ + "print(f'''Optimal Values:\n", + "\n", + "H101 outlet temperature = {value(m.fs.H101.outlet.temperature[0]):.3f} K\n", + "\n", + "R101 outlet temperature = {value(m.fs.R101.outlet.temperature[0]):.3f} K\n", + "\n", + "F101 outlet temperature = {value(m.fs.F101.vap_outlet.temperature[0]):.3f} K\n", + "\n", + "F102 outlet temperature = {value(m.fs.F102.vap_outlet.temperature[0]):.3f} K\n", + "F102 outlet pressure = {value(m.fs.F102.vap_outlet.pressure[0]):.3f} Pa\n", + "''')" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Optimal Values:\n", + "\n", + "H101 outlet temperature = 500.000 K\n", + "\n", + "R101 outlet temperature = 696.112 K\n", + "\n", + "F101 outlet temperature = 301.878 K\n", + "\n", + "F102 outlet temperature = 362.935 K\n", + "F102 outlet pressure = 105000.000 Pa\n", + "\n" + ] + } + ], + "execution_count": 82 + } + ], + "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.8.12" + } + }, + "nbformat": 4, + "nbformat_minor": 3 } \ No newline at end of file diff --git a/idaes_examples/notebooks/docs/tut/core/hda_flowsheet_test.ipynb b/idaes_examples/notebooks/docs/tut/core/hda_flowsheet_test.ipynb index 415cf48c..14dbf8bc 100644 --- a/idaes_examples/notebooks/docs/tut/core/hda_flowsheet_test.ipynb +++ b/idaes_examples/notebooks/docs/tut/core/hda_flowsheet_test.ipynb @@ -1,1498 +1,2868 @@ { - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "header", - "hide-cell" - ] - }, - "outputs": [], - "source": [ - "###############################################################################\n", - "# The Institute for the Design of Advanced Energy Systems Integrated Platform\n", - "# Framework (IDAES IP) was produced under the DOE Institute for the\n", - "# Design of Advanced Energy Systems (IDAES).\n", - "#\n", - "# Copyright (c) 2018-2023 by the software owners: The Regents of the\n", - "# University of California, through Lawrence Berkeley National Laboratory,\n", - "# National Technology & Engineering Solutions of Sandia, LLC, Carnegie Mellon\n", - "# University, West Virginia University Research Corporation, et al.\n", - "# All rights reserved. Please see the files COPYRIGHT.md and LICENSE.md\n", - "# for full copyright and license information.\n", - "###############################################################################" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "# HDA Flowsheet Simulation and Optimization\n", - "\n", - "Author: Jaffer Ghouse \n", - "Maintainer: Brandon Paul \n", - "Updated: 2023-06-01 \n", - "\n", - "## Learning outcomes\n", - "\n", - "\n", - "- Construct a steady-state flowsheet using the IDAES unit model library\n", - "- Connecting unit models in a flowsheet using Arcs\n", - "- Using the SequentialDecomposition tool to initialize a flowsheet with recycle\n", - "- Fomulate and solve an optimization problem\n", - " - Defining an objective function\n", - " - Setting variable bounds\n", - " - Adding additional constraints \n", - "\n", - "\n", - "## Problem Statement\n", - "\n", - "Hydrodealkylation is a chemical reaction that often involves reacting\n", - "an aromatic hydrocarbon in the presence of hydrogen gas to form a\n", - "simpler aromatic hydrocarbon devoid of functional groups. In this\n", - "example, toluene will be reacted with hydrogen gas at high temperatures\n", - " to form benzene via the following reaction:\n", - "\n", - "**C6H5CH3 + H2 → C6H6 + CH4**\n", - "\n", - "\n", - "This reaction is often accompanied by an equilibrium side reaction\n", - "which forms diphenyl, which we will neglect for this example.\n", - "\n", - "This example is based on the 1967 AIChE Student Contest problem as\n", - "present by Douglas, J.M., Chemical Design of Chemical Processes, 1988,\n", - "McGraw-Hill.\n", - "\n", - "The flowsheet that we will be using for this module is shown below with the stream conditions. We will be processing toluene and hydrogen to produce at least 370 TPY of benzene. As shown in the flowsheet, there are two flash tanks, F101 to separate out the non-condensibles and F102 to further separate the benzene-toluene mixture to improve the benzene purity. Note that typically a distillation column is required to obtain high purity benzene but that is beyond the scope of this workshop. The non-condensibles separated out in F101 will be partially recycled back to M101 and the rest will be either purged or combusted for power generation.We will assume ideal gas for this flowsheet. The properties required for this module are available in the same directory:\n", - "\n", - "- hda_ideal_VLE.py\n", - "- hda_reaction.py\n", - "\n", - "The state variables chosen for the property package are **flows of component by phase, temperature and pressure**. The components considered are: **toluene, hydrogen, benzene and methane**. Therefore, every stream has 8 flow variables, 1 temperature and 1 pressure variable. \n", - "\n", - "![](HDA_flowsheet.png)\n", - "\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Importing required pyomo and idaes components\n", - "\n", - "\n", - "To construct a flowsheet, we will need several components from the pyomo and idaes package. Let us first import the following components from Pyomo:\n", - "- Constraint (to write constraints)\n", - "- Var (to declare variables)\n", - "- ConcreteModel (to create the concrete model object)\n", - "- Expression (to evaluate values as a function of variables defined in the model)\n", - "- Objective (to define an objective function for optimization)\n", - "- SolverFactory (to solve the problem)\n", - "- TransformationFactory (to apply certain transformations)\n", - "- Arc (to connect two unit models)\n", - "- SequentialDecomposition (to initialize the flowsheet in a sequential mode)\n", - "\n", - "For further details on these components, please refer to the pyomo documentation: https://pyomo.readthedocs.io/en/stable/\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from pyomo.environ import (\n", - " Constraint,\n", - " Var,\n", - " ConcreteModel,\n", - " Expression,\n", - " Objective,\n", - " SolverFactory,\n", - " TransformationFactory,\n", - " value,\n", - ")\n", - "from pyomo.network import Arc, SequentialDecomposition" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "From idaes, we will be needing the FlowsheetBlock and the following unit models:\n", - "- Mixer\n", - "- Heater\n", - "- StoichiometricReactor\n", - "- **Flash**\n", - "- Separator (splitter) \n", - "- PressureChanger" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from idaes.core import FlowsheetBlock" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from idaes.models.unit_models import (\n", - " PressureChanger,\n", - " Mixer,\n", - " Separator as Splitter,\n", - " Heater,\n", - " StoichiometricReactor,\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "
\n", - "Inline Exercise:\n", - "Now, import the remaining unit models highlighted in blue above and run the cell using `Shift+Enter` after typing in the code. \n", - "
\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "solution" - ] - }, - "outputs": [], - "source": [ - "# Todo: import flash model from idaes.models.unit_models\n", - "from idaes.models.unit_models import Flash" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We will also be needing some utility tools to put together the flowsheet and calculate the degrees of freedom. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from idaes.models.unit_models.pressure_changer import ThermodynamicAssumption\n", - "from idaes.core.util.model_statistics import degrees_of_freedom\n", - "\n", - "# Import idaes logger to set output levels\n", - "import idaes.logger as idaeslog\n", - "from idaes.core.solvers import get_solver\n", - "from idaes.core.util.exceptions import InitializationError" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Importing required thermo and reaction package\n", - "\n", - "The final set of imports are to import the thermo and reaction package for the HDA process. We have created a custom thermo package that assumes Ideal Gas with support for VLE. \n", - "\n", - "The reaction package here is very simple as we will be using only a StochiometricReactor and the reaction package consists of the stochiometric coefficients for the reaction and the parameter for the heat of reaction. \n", - "\n", - "Let us import the following modules and they are in the same directory as this jupyter notebook:\n", - "
    \n", - "
  • hda_ideal_VLE as thermo_props
  • \n", - "
  • hda_reaction as reaction_props
  • \n", - "
\n", - "
" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from idaes_examples.mod.hda import hda_ideal_VLE as thermo_props\n", - "from idaes_examples.mod.hda import hda_reaction as reaction_props" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Constructing the Flowsheet\n", - "\n", - "We have now imported all the components, unit models, and property modules we need to construct a flowsheet. Let us create a ConcreteModel and add the flowsheet block as we did in module 1. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m = ConcreteModel()\n", - "m.fs = FlowsheetBlock(dynamic=False)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We now need to add the property packages to the flowsheet. Unlike Module 1, where we only had a thermo property package, for this flowsheet we will also need to add a reaction property package. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.thermo_params = thermo_props.HDAParameterBlock()\n", - "m.fs.reaction_params = reaction_props.HDAReactionParameterBlock(\n", - " property_package=m.fs.thermo_params\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Adding Unit Models\n", - "\n", - "Let us start adding the unit models we have imported to the flowsheet. Here, we are adding the Mixer (assigned a name M101) and a Heater (assigned a name H101). Note that, all unit models need to be given a property package argument. In addition to that, there are several arguments depending on the unit model, please refer to the documentation for more details (https://idaes-pse.readthedocs.io/en/stable/reference_guides/model_libraries/generic/unit_models/index.html). For example, the Mixer unit model here is given a `list` consisting of names to the three inlets. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.M101 = Mixer(\n", - " property_package=m.fs.thermo_params,\n", - " inlet_list=[\"toluene_feed\", \"hydrogen_feed\", \"vapor_recycle\"],\n", - ")\n", - "\n", - "m.fs.H101 = Heater(\n", - " property_package=m.fs.thermo_params,\n", - " has_pressure_change=False,\n", - " has_phase_equilibrium=True,\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "
\n", - "Inline Exercise:\n", - "Let us now add the StoichiometricReactor(assign the name R101) and pass the following arguments:\n", - "
    \n", - "
  • \"property_package\": m.fs.thermo_params
  • \n", - "
  • \"reaction_package\": m.fs.reaction_params
  • \n", - "
  • \"has_heat_of_reaction\": True
  • \n", - "
  • \"has_heat_transfer\": True
  • \n", - "
  • \"has_pressure_change\": False
  • \n", - "
\n", - "
" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "solution" - ] - }, - "outputs": [], - "source": [ - "# Todo: Add reactor with the specifications above\n", - "m.fs.R101 = StoichiometricReactor(\n", - " property_package=m.fs.thermo_params,\n", - " reaction_package=m.fs.reaction_params,\n", - " has_heat_of_reaction=True,\n", - " has_heat_transfer=True,\n", - " has_pressure_change=False,\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let us now add the Flash(assign the name F101) and pass the following arguments:\n", - "
    \n", - "
  • \"property_package\": m.fs.thermo_params
  • \n", - "
  • \"has_heat_transfer\": True
  • \n", - "
  • \"has_pressure_change\": False
  • \n", - "
" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.F101 = Flash(\n", - " property_package=m.fs.thermo_params,\n", - " has_heat_transfer=True,\n", - " has_pressure_change=True,\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let us now add the Splitter(S101), PressureChanger(C101) and the second Flash(F102). " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.S101 = Splitter(\n", - " property_package=m.fs.thermo_params,\n", - " ideal_separation=False,\n", - " outlet_list=[\"purge\", \"recycle\"],\n", - ")\n", - "\n", - "\n", - "m.fs.C101 = PressureChanger(\n", - " property_package=m.fs.thermo_params,\n", - " compressor=True,\n", - " thermodynamic_assumption=ThermodynamicAssumption.isothermal,\n", - ")\n", - "\n", - "m.fs.F102 = Flash(\n", - " property_package=m.fs.thermo_params,\n", - " has_heat_transfer=True,\n", - " has_pressure_change=True,\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Connecting Unit Models using Arcs\n", - "\n", - "We have now added all the unit models we need to the flowsheet. However, we have not yet specified how the units are to be connected. To do this, we will be using the `Arc` which is a pyomo component that takes in two arguments: `source` and `destination`. Let us connect the outlet of the mixer(M101) to the inlet of the heater(H101). " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.s03 = Arc(source=m.fs.M101.outlet, destination=m.fs.H101.inlet)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "![](HDA_flowsheet.png) \n", - "\n", - "
\n", - "Inline Exercise:\n", - "Now, connect the H101 outlet to the R101 inlet using the cell above as a guide. \n", - "
\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "solution" - ] - }, - "outputs": [], - "source": [ - "# Todo: Connect the H101 outlet to R101 inlet\n", - "m.fs.s04 = Arc(source=m.fs.H101.outlet, destination=m.fs.R101.inlet)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We will now be connecting the rest of the flowsheet as shown below. Notice how the outlet names are different for the flash tanks F101 and F102 as they have a vapor and a liquid outlet. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.s05 = Arc(source=m.fs.R101.outlet, destination=m.fs.F101.inlet)\n", - "m.fs.s06 = Arc(source=m.fs.F101.vap_outlet, destination=m.fs.S101.inlet)\n", - "m.fs.s08 = Arc(source=m.fs.S101.recycle, destination=m.fs.C101.inlet)\n", - "m.fs.s09 = Arc(source=m.fs.C101.outlet, destination=m.fs.M101.vapor_recycle)\n", - "m.fs.s10 = Arc(source=m.fs.F101.liq_outlet, destination=m.fs.F102.inlet)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We have now connected the unit model block using the arcs. However, each of these arcs link to ports on the two unit models that are connected. In this case, the ports consist of the state variables that need to be linked between the unit models. Pyomo provides a convenient method to write these equality constraints for us between two ports and this is done as follows:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "TransformationFactory(\"network.expand_arcs\").apply_to(m)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Adding expressions to compute purity and operating costs\n", - "\n", - "In this section, we will add a few Expressions that allows us to evaluate the performance. Expressions provide a convenient way of calculating certain values that are a function of the variables defined in the model. For more details on Expressions, please refer to: https://pyomo.readthedocs.io/en/stable/pyomo_modeling_components/Expressions.html\n", - "\n", - "For this flowsheet, we are interested in computing the purity of the product Benzene stream (i.e. the mole fraction) and the operating cost which is a sum of the cooling and heating cost. " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let us first add an Expression to compute the mole fraction of benzene in the `vap_outlet` of F102 which is our product stream. Please note that the var flow_mol_phase_comp has the index - [time, phase, component]. As this is a steady-state flowsheet, the time index by default is 0. The valid phases are [\"Liq\", \"Vap\"]. Similarly the valid component list is [\"benzene\", \"toluene\", \"hydrogen\", \"methane\"]." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.purity = Expression(\n", - " expr=m.fs.F102.vap_outlet.flow_mol_phase_comp[0, \"Vap\", \"benzene\"]\n", - " / (\n", - " m.fs.F102.vap_outlet.flow_mol_phase_comp[0, \"Vap\", \"benzene\"]\n", - " + m.fs.F102.vap_outlet.flow_mol_phase_comp[0, \"Vap\", \"toluene\"]\n", - " )\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now, let us add an expression to compute the cooling cost assuming a cost of 0.212E-4 $/kW. Note that cooling utility is required for the reactor (R101) and the first flash (F101). " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.cooling_cost = Expression(\n", - " expr=0.212e-7 * (-m.fs.F101.heat_duty[0]) + 0.212e-7 * (-m.fs.R101.heat_duty[0])\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "Now, let us add an expression to compute the heating cost assuming the utility cost as follows:\n", - "
    \n", - "
  • 2.2E-4 dollars/kW for H101
  • \n", - "
  • 1.9E-4 dollars/kW for F102
  • \n", - "
\n", - "Note that the heat duty is in units of watt (J/s). " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.heating_cost = Expression(\n", - " expr=2.2e-7 * m.fs.H101.heat_duty[0] + 1.9e-7 * m.fs.F102.heat_duty[0]\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let us now add an expression to compute the total operating cost per year which is basically the sum of the cooling and heating cost we defined above. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.operating_cost = Expression(\n", - " expr=(3600 * 24 * 365 * (m.fs.heating_cost + m.fs.cooling_cost))\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Fixing feed conditions\n", - "\n", - "Let us first check how many degrees of freedom exist for this flowsheet using the `degrees_of_freedom` tool we imported earlier. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "print(degrees_of_freedom(m))" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "testing" - ] - }, - "outputs": [], - "source": [ - "# Check the degrees of freedom\n", - "assert degrees_of_freedom(m) == 29" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We will now be fixing the toluene feed stream to the conditions shown in the flowsheet above. Please note that though this is a pure toluene feed, the remaining components are still assigned a very small non-zero value to help with convergence and initializing. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.M101.toluene_feed.flow_mol_phase_comp[0, \"Vap\", \"benzene\"].fix(1e-5)\n", - "m.fs.M101.toluene_feed.flow_mol_phase_comp[0, \"Vap\", \"toluene\"].fix(1e-5)\n", - "m.fs.M101.toluene_feed.flow_mol_phase_comp[0, \"Vap\", \"hydrogen\"].fix(1e-5)\n", - "m.fs.M101.toluene_feed.flow_mol_phase_comp[0, \"Vap\", \"methane\"].fix(1e-5)\n", - "m.fs.M101.toluene_feed.flow_mol_phase_comp[0, \"Liq\", \"benzene\"].fix(1e-5)\n", - "m.fs.M101.toluene_feed.flow_mol_phase_comp[0, \"Liq\", \"toluene\"].fix(0.30)\n", - "m.fs.M101.toluene_feed.flow_mol_phase_comp[0, \"Liq\", \"hydrogen\"].fix(1e-5)\n", - "m.fs.M101.toluene_feed.flow_mol_phase_comp[0, \"Liq\", \"methane\"].fix(1e-5)\n", - "m.fs.M101.toluene_feed.temperature.fix(303.2)\n", - "m.fs.M101.toluene_feed.pressure.fix(350000)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "Similarly, let us fix the hydrogen feed to the following conditions in the next cell:\n", - "
    \n", - "
  • FH2 = 0.30 mol/s
  • \n", - "
  • FCH4 = 0.02 mol/s
  • \n", - "
  • Remaining components = 1e-5 mol/s
  • \n", - "
  • T = 303.2 K
  • \n", - "
  • P = 350000 Pa
  • \n", - "
\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.M101.hydrogen_feed.flow_mol_phase_comp[0, \"Vap\", \"benzene\"].fix(1e-5)\n", - "m.fs.M101.hydrogen_feed.flow_mol_phase_comp[0, \"Vap\", \"toluene\"].fix(1e-5)\n", - "m.fs.M101.hydrogen_feed.flow_mol_phase_comp[0, \"Vap\", \"hydrogen\"].fix(0.30)\n", - "m.fs.M101.hydrogen_feed.flow_mol_phase_comp[0, \"Vap\", \"methane\"].fix(0.02)\n", - "m.fs.M101.hydrogen_feed.flow_mol_phase_comp[0, \"Liq\", \"benzene\"].fix(1e-5)\n", - "m.fs.M101.hydrogen_feed.flow_mol_phase_comp[0, \"Liq\", \"toluene\"].fix(1e-5)\n", - "m.fs.M101.hydrogen_feed.flow_mol_phase_comp[0, \"Liq\", \"hydrogen\"].fix(1e-5)\n", - "m.fs.M101.hydrogen_feed.flow_mol_phase_comp[0, \"Liq\", \"methane\"].fix(1e-5)\n", - "m.fs.M101.hydrogen_feed.temperature.fix(303.2)\n", - "m.fs.M101.hydrogen_feed.pressure.fix(350000)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Fixing unit model specifications\n", - "\n", - "Now that we have fixed our inlet feed conditions, we will now be fixing the operating conditions for the unit models in the flowsheet. Let us set set the H101 outlet temperature to 600 K. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.H101.outlet.temperature.fix(600)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "For the StoichiometricReactor, we have to define the conversion in terms of toluene. This requires us to create a new variable for specifying the conversion and adding a Constraint that defines the conversion with respect to toluene. The second degree of freedom for the reactor is to define the heat duty. In this case, let us assume the reactor to be adiabatic i.e. Q = 0. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.R101.conversion = Var(initialize=0.75, bounds=(0, 1))\n", - "\n", - "m.fs.R101.conv_constraint = Constraint(\n", - " expr=m.fs.R101.conversion * m.fs.R101.inlet.flow_mol_phase_comp[0, \"Vap\", \"toluene\"]\n", - " == (\n", - " m.fs.R101.inlet.flow_mol_phase_comp[0, \"Vap\", \"toluene\"]\n", - " - m.fs.R101.outlet.flow_mol_phase_comp[0, \"Vap\", \"toluene\"]\n", - " )\n", - ")\n", - "\n", - "m.fs.R101.conversion.fix(0.75)\n", - "m.fs.R101.heat_duty.fix(0)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The Flash conditions for F101 can be set as follows. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.F101.vap_outlet.temperature.fix(325.0)\n", - "m.fs.F101.deltaP.fix(0)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "
\n", - "Inline Exercise:\n", - "Set the conditions for Flash F102 to the following conditions:\n", - "
    \n", - "
  • T = 375 K
  • \n", - "
  • deltaP = -200000
  • \n", - "
\n", - "\n", - "Use Shift+Enter to run the cell once you have typed in your code. \n", - "
" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "solution" - ] - }, - "outputs": [], - "source": [ - "m.fs.F102.vap_outlet.temperature.fix(375)\n", - "m.fs.F102.deltaP.fix(-200000)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let us fix the purge split fraction to 20% and the outlet pressure of the compressor is set to 350000 Pa. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.S101.split_fraction[0, \"purge\"].fix(0.2)\n", - "m.fs.C101.outlet.pressure.fix(350000)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "
\n", - "Inline Exercise:\n", - "We have now defined all the feed conditions and the inputs required for the unit models. The system should now have 0 degrees of freedom i.e. should be a square problem. Please check that the degrees of freedom is 0. \n", - "\n", - "Use Shift+Enter to run the cell once you have typed in your code. \n", - "
" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "solution" - ] - }, - "outputs": [], - "source": [ - "print(degrees_of_freedom(m))" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "testing" - ] - }, - "outputs": [], - "source": [ - "# Check the degrees of freedom\n", - "assert degrees_of_freedom(m) == 0" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Initialization\n", - "\n", - "\n", - "This section will demonstrate how to use the built-in sequential decomposition tool to initialize our flowsheet.\n", - "\n", - "![](HDA_flowsheet.png) \n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let us first create an object for the SequentialDecomposition and specify our options for this. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "seq = SequentialDecomposition()\n", - "seq.options.select_tear_method = \"heuristic\"\n", - "seq.options.tear_method = \"Wegstein\"\n", - "seq.options.iterLim = 3\n", - "\n", - "# Using the SD tool\n", - "G = seq.create_graph(m)\n", - "heuristic_tear_set = seq.tear_set_arcs(G, method=\"heuristic\")\n", - "order = seq.calculation_order(G)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Which is the tear stream? Display tear set and order" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "for o in heuristic_tear_set:\n", - " print(o.name)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "What sequence did the SD tool determine to solve this flowsheet with the least number of tears? " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "scrolled": true - }, - "outputs": [], - "source": [ - "for o in order:\n", - " print(o[0].name)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - " \n", - "\n", - "![](HDA_tear_stream.png) \n", - "\n", - "\n", - "The SequentialDecomposition tool has determined that the tear stream is the mixer outlet. We will need to provide a reasonable guess for this." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "tear_guesses = {\n", - " \"flow_mol_phase_comp\": {\n", - " (0, \"Vap\", \"benzene\"): 1e-5,\n", - " (0, \"Vap\", \"toluene\"): 1e-5,\n", - " (0, \"Vap\", \"hydrogen\"): 0.30,\n", - " (0, \"Vap\", \"methane\"): 0.02,\n", - " (0, \"Liq\", \"benzene\"): 1e-5,\n", - " (0, \"Liq\", \"toluene\"): 0.30,\n", - " (0, \"Liq\", \"hydrogen\"): 1e-5,\n", - " (0, \"Liq\", \"methane\"): 1e-5,\n", - " },\n", - " \"temperature\": {0: 303},\n", - " \"pressure\": {0: 350000},\n", - "}\n", - "\n", - "# Pass the tear_guess to the SD tool\n", - "seq.set_guesses_for(m.fs.H101.inlet, tear_guesses)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Next, we need to tell the tool how to initialize a particular unit. We will be writing a python function which takes in a \"unit\" and calls the initialize method on that unit. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "def function(unit):\n", - " try:\n", - " initializer = unit.default_initializer()\n", - " initializer.initialize(unit, output_level=idaeslog.INFO)\n", - " except InitializationError:\n", - " solver = get_solver()\n", - " solver.solve(unit)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We are now ready to initialize our flowsheet in a sequential mode. Note that we specifically set the iteration limit to be 5 as we are trying to use this tool only to get a good set of initial values such that IPOPT can then take over and solve this flowsheet for us. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "seq.run(m, function)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "
\n", - "Inline Exercise:\n", - "We have now initialized the flowsheet. Let us run the flowsheet in a simulation mode to look at the results. To do this, complete the last line of code where we pass the model to the solver. You will need to type the following:\n", - " \n", - "results = solver.solve(m, tee=True)\n", - "\n", - "Use Shift+Enter to run the cell once you have typed in your code. \n", - "
\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "solution" - ] - }, - "outputs": [], - "source": [ - "# Create the solver object\n", - "from idaes.core.solvers import get_solver\n", - "\n", - "solver = get_solver()\n", - "\n", - "# Solve the model\n", - "results = solver.solve(m, tee=True)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "testing" - ] - }, - "outputs": [], - "source": [ - "# Check solver solve status\n", - "from pyomo.environ import TerminationCondition\n", - "\n", - "assert results.solver.termination_condition == TerminationCondition.optimal" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Analyze the results of the square problem\n", - "\n", - "\n", - "What is the total operating cost? " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "print(\"operating cost = $\", value(m.fs.operating_cost))" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "testing" - ] - }, - "outputs": [], - "source": [ - "import pytest\n", - "\n", - "assert value(m.fs.operating_cost) == pytest.approx(419122.3387, abs=1e-3)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "For this operating cost, what is the amount of benzene we are able to produce and what purity we are able to achieve? " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.F102.report()\n", - "\n", - "print()\n", - "print(\"benzene purity = \", value(m.fs.purity))" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "testing" - ] - }, - "outputs": [], - "source": [ - "assert value(m.fs.purity) == pytest.approx(0.82429, abs=1e-3)\n", - "\n", - "assert value(m.fs.F102.heat_duty[0]) == pytest.approx(7352.4828, abs=1e-3)\n", - "assert value(m.fs.F102.vap_outlet.pressure[0]) == pytest.approx(1.5000e05, abs=1e-3)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Next, let's look at how much benzene we are losing with the light gases out of F101. IDAES has tools for creating stream tables based on the `Arcs` and/or `Ports` in a flowsheet. Let us create and print a simple stream table showing the stream leaving the reactor and the vapor stream from F101.\n", - "\n", - "
\n", - "Inline Exercise:\n", - "How much benzene are we losing in the F101 vapor outlet stream?\n", - "
\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from idaes.core.util.tables import (\n", - " create_stream_table_dataframe,\n", - " stream_table_dataframe_to_string,\n", - ")\n", - "\n", - "st = create_stream_table_dataframe({\"Reactor\": m.fs.s05, \"Light Gases\": m.fs.s06})\n", - "print(stream_table_dataframe_to_string(st))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "
\n", - "Inline Exercise:\n", - "You can query additional variables here if you like. \n", - "\n", - "Use Shift+Enter to run the cell once you have typed in your code. \n", - "
\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Optimization\n", - "\n", - "\n", - "We saw from the results above that the total operating cost for the base case was $419,122 per year. We are producing 0.142 mol/s of benzene at a purity of 82\\%. However, we are losing around 42\\% of benzene in F101 vapor outlet stream. \n", - "\n", - "Let us try to minimize this cost such that:\n", - "- we are producing at least 0.15 mol/s of benzene in F102 vapor outlet i.e. our product stream\n", - "- purity of benzene i.e. the mole fraction of benzene in F102 vapor outlet is at least 80%\n", - "- restricting the benzene loss in F101 vapor outlet to less than 20%\n", - "\n", - "For this problem, our decision variables are as follows:\n", - "- H101 outlet temperature\n", - "- R101 cooling duty provided\n", - "- F101 outlet temperature\n", - "- F102 outlet temperature\n", - "- F102 deltaP in the flash tank\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let us declare our objective function for this problem. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.objective = Objective(expr=m.fs.operating_cost)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now, we need to unfix the decision variables as we had solved a square problem (degrees of freedom = 0) until now. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.H101.outlet.temperature.unfix()\n", - "m.fs.R101.heat_duty.unfix()\n", - "m.fs.F101.vap_outlet.temperature.unfix()\n", - "m.fs.F102.vap_outlet.temperature.unfix()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "
\n", - "Inline Exercise:\n", - "Let us now unfix the remaining variable which is F102 pressure drop (F102.deltaP) \n", - "\n", - "Use Shift+Enter to run the cell once you have typed in your code. \n", - "
\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "solution" - ] - }, - "outputs": [], - "source": [ - "# Todo: Unfix deltaP for F102\n", - "m.fs.F102.deltaP.unfix()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "testing" - ] - }, - "outputs": [], - "source": [ - "assert degrees_of_freedom(m) == 5" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Next, we need to set bounds on these decision variables to values shown below:\n", - "\n", - " - H101 outlet temperature [500, 600] K\n", - " - R101 outlet temperature [600, 800] K\n", - " - F101 outlet temperature [298, 450] K\n", - " - F102 outlet temperature [298, 450] K\n", - " - F102 outlet pressure [105000, 110000] Pa\n", - "\n", - "Let us first set the variable bound for the H101 outlet temperature as shown below:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.H101.outlet.temperature[0].setlb(500)\n", - "m.fs.H101.outlet.temperature[0].setub(600)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "
\n", - "Inline Exercise:\n", - "Now, set the variable bound for the R101 outlet temperature.\n", - "\n", - "Use Shift+Enter to run the cell once you have typed in your code. \n", - "
" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "solution" - ] - }, - "outputs": [], - "source": [ - "# Todo: Set the bounds for reactor outlet temperature\n", - "m.fs.R101.outlet.temperature[0].setlb(600)\n", - "m.fs.R101.outlet.temperature[0].setub(800)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let us fix the bounds for the rest of the decision variables. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.F101.vap_outlet.temperature[0].setlb(298.0)\n", - "m.fs.F101.vap_outlet.temperature[0].setub(450.0)\n", - "m.fs.F102.vap_outlet.temperature[0].setlb(298.0)\n", - "m.fs.F102.vap_outlet.temperature[0].setub(450.0)\n", - "m.fs.F102.vap_outlet.pressure[0].setlb(105000)\n", - "m.fs.F102.vap_outlet.pressure[0].setub(110000)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now, the only things left to define are our constraints on overhead loss in F101, product flow rate and purity in F102. Let us first look at defining a constraint for the overhead loss in F101 where we are restricting the benzene leaving the vapor stream to less than 20 \\% of the benzene available in the reactor outlet. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.overhead_loss = Constraint(\n", - " expr=m.fs.F101.vap_outlet.flow_mol_phase_comp[0, \"Vap\", \"benzene\"]\n", - " <= 0.20 * m.fs.R101.outlet.flow_mol_phase_comp[0, \"Vap\", \"benzene\"]\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "
\n", - "Inline Exercise:\n", - "Now, add the constraint such that we are producing at least 0.15 mol/s of benzene in the product stream which is the vapor outlet of F102. Let us name this constraint as m.fs.product_flow. \n", - "\n", - "Use Shift+Enter to run the cell once you have typed in your code. \n", - "
" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "solution" - ] - }, - "outputs": [], - "source": [ - "# Todo: Add minimum product flow constraint\n", - "m.fs.product_flow = Constraint(\n", - " expr=m.fs.F102.vap_outlet.flow_mol_phase_comp[0, \"Vap\", \"benzene\"] >= 0.15\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let us add the final constraint on product purity or the mole fraction of benzene in the product stream such that it is at least greater than 80%. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.product_purity = Constraint(expr=m.fs.purity >= 0.80)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "We have now defined the optimization problem and we are now ready to solve this problem. \n", - "\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "results = solver.solve(m, tee=True)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "testing" - ] - }, - "outputs": [], - "source": [ - "# Check for solver solve status\n", - "from pyomo.environ import TerminationCondition\n", - "\n", - "assert results.solver.termination_condition == TerminationCondition.optimal" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Optimization Results\n", - "\n", - "Display the results and product specifications" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "print(\"operating cost = $\", value(m.fs.operating_cost))\n", - "\n", - "print()\n", - "print(\"Product flow rate and purity in F102\")\n", - "\n", - "m.fs.F102.report()\n", - "\n", - "print()\n", - "print(\"benzene purity = \", value(m.fs.purity))\n", - "\n", - "print()\n", - "print(\"Overhead loss in F101\")\n", - "m.fs.F101.report()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "testing" - ] - }, - "outputs": [], - "source": [ - "assert value(m.fs.operating_cost) == pytest.approx(312786.338, abs=1e-3)\n", - "assert value(m.fs.purity) == pytest.approx(0.818827, abs=1e-3)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Display optimal values for the decision variables" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "print(\"Optimal Values\")\n", - "print()\n", - "\n", - "print(\"H101 outlet temperature = \", value(m.fs.H101.outlet.temperature[0]), \"K\")\n", - "\n", - "print()\n", - "print(\"R101 outlet temperature = \", value(m.fs.R101.outlet.temperature[0]), \"K\")\n", - "\n", - "print()\n", - "print(\"F101 outlet temperature = \", value(m.fs.F101.vap_outlet.temperature[0]), \"K\")\n", - "\n", - "print()\n", - "print(\"F102 outlet temperature = \", value(m.fs.F102.vap_outlet.temperature[0]), \"K\")\n", - "print(\"F102 outlet pressure = \", value(m.fs.F102.vap_outlet.pressure[0]), \"Pa\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "testing" - ] - }, - "outputs": [], - "source": [ - "assert value(m.fs.H101.outlet.temperature[0]) == pytest.approx(500, abs=1e-3)\n", - "assert value(m.fs.R101.outlet.temperature[0]) == pytest.approx(696.112, abs=1e-3)\n", - "assert value(m.fs.F101.vap_outlet.temperature[0]) == pytest.approx(301.878, abs=1e-3)\n", - "assert value(m.fs.F102.vap_outlet.temperature[0]) == pytest.approx(362.935, abs=1e-3)\n", - "assert value(m.fs.F102.vap_outlet.pressure[0]) == pytest.approx(105000, abs=1e-2)" - ] - }, - { - "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.8.12" - } - }, - "nbformat": 4, - "nbformat_minor": 3 + "cells": [ + { + "cell_type": "code", + "metadata": { + "tags": [ + "header", + "hide-cell" + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:25.418463Z", + "start_time": "2025-06-11T22:13:25.412645Z" + } + }, + "source": [ + "###############################################################################\n", + "# The Institute for the Design of Advanced Energy Systems Integrated Platform\n", + "# Framework (IDAES IP) was produced under the DOE Institute for the\n", + "# Design of Advanced Energy Systems (IDAES).\n", + "#\n", + "# Copyright (c) 2018-2023 by the software owners: The Regents of the\n", + "# University of California, through Lawrence Berkeley National Laboratory,\n", + "# National Technology & Engineering Solutions of Sandia, LLC, Carnegie Mellon\n", + "# University, West Virginia University Research Corporation, et al.\n", + "# All rights reserved. Please see the files COPYRIGHT.md and LICENSE.md\n", + "# for full copyright and license information.\n", + "###############################################################################" + ], + "outputs": [], + "execution_count": 1 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "# HDA Flowsheet Simulation and Optimization\n", + "\n", + "Author: Jaffer Ghouse
\n", + "Maintainer: Tanner Polley
\n", + "Updated: 2025-06-03\n", + "\n", + "## Learning outcomes\n", + "\n", + "\n", + "- Construct a steady-state flowsheet using the IDAES unit model library\n", + "- Connecting unit models in a flowsheet using Arcs\n", + "- Using the SequentialDecomposition tool to initialize a flowsheet with recycle\n", + "- Formulate and solve an optimization problem\n", + " - Defining an objective function\n", + " - Setting variable bounds\n", + " - Adding additional constraints\n", + "\n", + "\n", + "The general workflow of setting up an IDAES flowsheet is the following:\n", + "\n", + "- 1 Importing Modules\n", + "- 2 Building a Model\n", + "- 3 Scaling the Model\n", + "- 4 Specifying the Model\n", + "- 5 Initializing the Model\n", + "- 6 Solving the Model\n", + "- 7 Analyzing and Visualizing the Results\n", + "- 8 Optimizing the Model\n", + "\n", + "We will complete each of these steps as well as demonstrate analyses on this model through some examples and exercises\n", + "\n", + "\n", + "## Problem Statement\n", + "\n", + "Hydrodealkylation is a chemical reaction that often involves reacting\n", + "an aromatic hydrocarbon in the presence of hydrogen gas to form a\n", + "simpler aromatic hydrocarbon devoid of functional groups. In this\n", + "example, toluene will be reacted with hydrogen gas at high temperatures\n", + " to form benzene via the following reaction:\n", + "\n", + "**C6H5CH3 + H2 \u2192 C6H6 + CH4**\n", + "\n", + "\n", + "This reaction is often accompanied by an equilibrium side reaction\n", + "which forms diphenyl, which we will neglect for this example.\n", + "\n", + "This example is based on the 1967 AIChE Student Contest problem as\n", + "present by Douglas, J.M., Chemical Design of Chemical Processes, 1988,\n", + "McGraw-Hill.\n", + "\n", + "The flowsheet that we will be using for this module is shown below with the stream conditions. We will be processing toluene and hydrogen to produce at least 370 TPY of benzene. As shown in the flowsheet, there are two flash tanks, F101 to separate out the non-condensibles and F102 to further separate the benzene-toluene mixture to improve the benzene purity. Note that typically a distillation column is required to obtain high purity benzene but that is beyond the scope of this workshop. The non-condensibles separated out in F101 will be partially recycled back to M101 and the rest will be either purged or combusted for power generation.We will assume ideal gas for this flowsheet. The properties required for this module are available in the same directory:\n", + "\n", + "- hda_ideal_VLE.py\n", + "- hda_reaction.py\n", + "\n", + "The state variables chosen for the property package are **flows of component by phase, temperature and pressure**. The components considered are: **toluene, hydrogen, benzene and methane**. Therefore, every stream has 8 flow variables, 1 temperature and 1 pressure variable. \n", + "\n", + "![](HDA_flowsheet.png)\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1 Importing Modules\n", + "### 1.1 Importing required pyomo and idaes components\n", + "\n", + "\n", + "To construct a flowsheet, we will need several components from the pyomo and idaes package. Let us first import the following components from Pyomo:\n", + "- Constraint (to write constraints)\n", + "- Var (to declare variables)\n", + "- ConcreteModel (to create the concrete model object)\n", + "- Expression (to evaluate values as a function of variables defined in the model)\n", + "- Objective (to define an objective function for optimization)\n", + "- SolverFactory (to solve the problem)\n", + "- TransformationFactory (to apply certain transformations)\n", + "- Arc (to connect two unit models)\n", + "- SequentialDecomposition (to initialize the flowsheet in a sequential mode)\n", + "\n", + "For further details on these components, please refer to the pyomo documentation: https://pyomo.readthedocs.io/en/stable/\n" + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:26.066510Z", + "start_time": "2025-06-11T22:13:25.662098Z" + } + }, + "source": [ + "from pyomo.environ import (\n", + " Constraint,\n", + " Var,\n", + " ConcreteModel,\n", + " Expression,\n", + " Objective,\n", + " SolverFactory,\n", + " TransformationFactory,\n", + " value,\n", + ")\n", + "from pyomo.network import Arc, SequentialDecomposition" + ], + "outputs": [], + "execution_count": 2 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "From idaes, we will be needing the FlowsheetBlock and the following unit models:\n", + "- Feed\n", + "- Mixer\n", + "- Heater\n", + "- StoichiometricReactor\n", + "- **Flash**\n", + "- Separator (splitter) \n", + "- PressureChanger\n", + "- Product" + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:27.637361Z", + "start_time": "2025-06-11T22:13:26.082580Z" + } + }, + "source": [ + "from idaes.core import FlowsheetBlock" + ], + "outputs": [], + "execution_count": 3 + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:27.752223Z", + "start_time": "2025-06-11T22:13:27.645348Z" + } + }, + "source": [ + "from idaes.models.unit_models import (\n", + " PressureChanger, IsentropicPressureChangerInitializer,\n", + " Mixer, MixerInitializer,\n", + " Separator as Splitter, SeparatorInitializer,\n", + " Heater,\n", + " StoichiometricReactor,\n", + " Feed,\n", + " Product,\n", + ")" + ], + "outputs": [], + "execution_count": 4 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "
\n", + "Inline Exercise:\n", + "Now, import the remaining unit models highlighted in blue above and run the cell using `Shift+Enter` after typing in the code. \n", + "
\n" + ] + }, + { + "cell_type": "code", + "metadata": { + "tags": [ + "solution" + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:27.774708Z", + "start_time": "2025-06-11T22:13:27.772299Z" + } + }, + "source": [ + "# Todo: import flash model from idaes.models.unit_models\n", + "from idaes.models.unit_models import Flash" + ], + "outputs": [], + "execution_count": 6 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We will also be needing some utility tools to put together the flowsheet and calculate the degrees of freedom. " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:27.788004Z", + "start_time": "2025-06-11T22:13:27.784891Z" + } + }, + "source": [ + "from idaes.models.unit_models.pressure_changer import ThermodynamicAssumption\n", + "from idaes.core.util.model_statistics import degrees_of_freedom\n", + "from idaes.core.scaling.util import set_scaling_factor\n", + "from idaes.core.scaling.autoscaling import AutoScaler\n", + "\n", + "# Import idaes logger to set output levels\n", + "import idaes.logger as idaeslog\n", + "from idaes.core.solvers import get_solver\n", + "from idaes.core.util.exceptions import InitializationError" + ], + "outputs": [], + "execution_count": 7 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 1.2 Importing required thermo and reaction package\n", + "\n", + "The final set of imports are to import the thermo and reaction package for the HDA process. We have created a custom thermo package that assumes Ideal Gas with support for VLE. \n", + "\n", + "The reaction package here is very simple as we will be using only a StochiometricReactor and the reaction package consists of the stochiometric coefficients for the reaction and the parameter for the heat of reaction. \n", + "\n", + "Let us import the following modules and they are in the same directory as this jupyter notebook:\n", + "
    \n", + "
  • hda_ideal_VLE as thermo_props
  • \n", + "
  • hda_reaction as reaction_props
  • \n", + "
\n", + "
" + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:27.806315Z", + "start_time": "2025-06-11T22:13:27.798068Z" + } + }, + "source": [ + "from idaes_examples.mod.hda import hda_ideal_VLE as thermo_props\n", + "from idaes_examples.mod.hda import hda_reaction as reaction_props" + ], + "outputs": [], + "execution_count": 8 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2 Constructing the Flowsheet\n", + "\n", + "We have now imported all the components, unit models, and property modules we need to construct a flowsheet. Let us create a ConcreteModel and add the flowsheet block as we did in module 1. " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:27.819615Z", + "start_time": "2025-06-11T22:13:27.814729Z" + } + }, + "source": [ + "m = ConcreteModel()\n", + "m.fs = FlowsheetBlock(dynamic=False)" + ], + "outputs": [], + "execution_count": 9 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We now need to add the property packages to the flowsheet. Unlike Module 1, where we only had a thermo property package, for this flowsheet we will also need to add a reaction property package. " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:27.841949Z", + "start_time": "2025-06-11T22:13:27.829949Z" + } + }, + "source": [ + "m.fs.thermo_params = thermo_props.HDAParameterBlock()\n", + "m.fs.reaction_params = reaction_props.HDAReactionParameterBlock(\n", + " property_package=m.fs.thermo_params\n", + ")" + ], + "outputs": [], + "execution_count": 10 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 2.1 Adding Unit Models\n", + "\n", + "Let us start adding the unit models we have imported to the flowsheet. Here, we are adding the Feed (assigned a name `I101` for Inlet), `Mixer` (assigned a name `M101`) and a `Heater` (assigned a name `H101`). Note that, all unit models need to be given a property package argument. In addition to that, there are several arguments depending on the unit model, please refer to the documentation for more details (https://idaes-pse.readthedocs.io/en/stable/reference_guides/model_libraries/generic/unit_models/index.html). For example, the `Mixer` unit model here must be specified the number of inlets that it will take in and the `Heater` can have specific settings enabled such as `has_pressure_change` or `has_phase_equilibrium`." + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:27.876870Z", + "start_time": "2025-06-11T22:13:27.853517Z" + } + }, + "source": [ + "m.fs.I101 = Feed(\n", + " property_package=m.fs.thermo_params)\n", + "\n", + "m.fs.I102 = Feed(\n", + " property_package=m.fs.thermo_params)\n", + "\n", + "m.fs.M101 = Mixer(\n", + " property_package=m.fs.thermo_params,\n", + " num_inlets=3,\n", + ")\n", + "\n", + "m.fs.H101 = Heater(\n", + " property_package=m.fs.thermo_params,\n", + " has_pressure_change=False,\n", + " has_phase_equilibrium=True,\n", + ")" + ], + "outputs": [], + "execution_count": 11 + }, + { + "cell_type": "code", + "metadata": { + "tags": [ + "solution" + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:27.921265Z", + "start_time": "2025-06-11T22:13:27.910910Z" + } + }, + "source": [ + "# Todo: Add reactor with the specifications above\n", + "m.fs.R101 = StoichiometricReactor(\n", + " property_package=m.fs.thermo_params,\n", + " reaction_package=m.fs.reaction_params,\n", + " has_heat_of_reaction=True,\n", + " has_heat_transfer=True,\n", + " has_pressure_change=False,\n", + ")" + ], + "outputs": [], + "execution_count": 13 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let us now add the Flash(assign the name F101) and pass the following arguments:\n", + "
    \n", + "
  • \"property_package\": m.fs.thermo_params
  • \n", + "
  • \"has_heat_transfer\": True
  • \n", + "
  • \"has_pressure_change\": False
  • \n", + "
" + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:27.947463Z", + "start_time": "2025-06-11T22:13:27.933993Z" + } + }, + "source": [ + "m.fs.F101 = Flash(\n", + " property_package=m.fs.thermo_params,\n", + " has_heat_transfer=True,\n", + " has_pressure_change=True,\n", + ")" + ], + "outputs": [], + "execution_count": 14 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": "Let us now add the Splitter(S101) with specific names for its output (purge and recycle), PressureChanger(C101) and the second Flash(F102)." + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:27.987933Z", + "start_time": "2025-06-11T22:13:27.964931Z" + } + }, + "source": [ + "m.fs.S101 = Splitter(\n", + " property_package=m.fs.thermo_params,\n", + " ideal_separation=False,\n", + " outlet_list=[\"purge\", \"recycle\"],\n", + ")\n", + "\n", + "\n", + "m.fs.C101 = PressureChanger(\n", + " property_package=m.fs.thermo_params,\n", + " compressor=True,\n", + " thermodynamic_assumption=ThermodynamicAssumption.isothermal,\n", + ")\n", + "\n", + "m.fs.F102 = Flash(\n", + " property_package=m.fs.thermo_params,\n", + " has_heat_transfer=True,\n", + " has_pressure_change=True,\n", + ")" + ], + "outputs": [], + "execution_count": 15 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "Last, we will add the three Product blocks (P101, P102, P103). We use `Feed` blocks and `Product` blocks for convenience with reporting stream summaries and consistency" + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:28.009893Z", + "start_time": "2025-06-11T22:13:28.001769Z" + } + }, + "cell_type": "code", + "source": [ + "m.fs.P101 = Product(\n", + " property_package=m.fs.thermo_params)\n", + "\n", + "m.fs.P102 = Product(\n", + " property_package=m.fs.thermo_params)\n", + "\n", + "m.fs.P103 = Product(\n", + " property_package=m.fs.thermo_params)" + ], + "outputs": [], + "execution_count": 16 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 2.2 Connecting Unit Models using Arcs\n", + "\n", + "We have now added all the unit models we need to the flowsheet. However, we have not yet specified how the units are to be connected. To do this, we will be using the `Arc` which is a pyomo component that takes in two arguments: `source` and `destination`. Let us connect the outlet of the inlets (I101, I102) to the inlet of the mixer (M101) and outlet of the mixer to the inlet of the heater(H101)." + ] + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:28.034324Z", + "start_time": "2025-06-11T22:13:28.031285Z" + } + }, + "cell_type": "code", + "source": [ + "m.fs.s01 = Arc(source=m.fs.I101.outlet, destination=m.fs.M101.inlet_1)\n", + "m.fs.s02 = Arc(source=m.fs.I102.outlet, destination=m.fs.M101.inlet_2)\n", + "m.fs.s03 = Arc(source=m.fs.M101.outlet, destination=m.fs.H101.inlet)" + ], + "outputs": [], + "execution_count": 17 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "![](HDA_flowsheet.png) \n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "metadata": { + "tags": [ + "solution" + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:28.084663Z", + "start_time": "2025-06-11T22:13:28.082008Z" + } + }, + "source": [ + "# Todo: Connect the H101 outlet to R101 inlet\n", + "m.fs.s04 = Arc(source=m.fs.H101.outlet, destination=m.fs.R101.inlet)" + ], + "outputs": [], + "execution_count": 19 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We will now be connecting the rest of the flowsheet as shown below. Notice how the outlet names are different for the flash tanks F101 and F102 as they have a vapor and a liquid outlet. " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:28.118422Z", + "start_time": "2025-06-11T22:13:28.113732Z" + } + }, + "source": [ + "m.fs.s05 = Arc(source=m.fs.R101.outlet, destination=m.fs.F101.inlet)\n", + "m.fs.s06 = Arc(source=m.fs.F101.vap_outlet, destination=m.fs.S101.inlet)\n", + "m.fs.s07 = Arc(source=m.fs.F101.liq_outlet, destination=m.fs.F102.inlet)\n", + "m.fs.s08 = Arc(source=m.fs.S101.recycle, destination=m.fs.C101.inlet)\n", + "m.fs.s09 = Arc(source=m.fs.C101.outlet, destination=m.fs.M101.inlet_3)\n" + ], + "outputs": [], + "execution_count": 20 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "Last we will connect the outlet streams to the inlets of the Product blocks (P101, P102, P103)" + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:28.148879Z", + "start_time": "2025-06-11T22:13:28.144601Z" + } + }, + "cell_type": "code", + "source": [ + "m.fs.s10 = Arc(source=m.fs.F102.vap_outlet, destination=m.fs.P101.inlet)\n", + "m.fs.s11 = Arc(source=m.fs.F102.liq_outlet, destination=m.fs.P102.inlet)\n", + "m.fs.s12 = Arc(source=m.fs.S101.purge, destination=m.fs.P103.inlet)" + ], + "outputs": [], + "execution_count": 21 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We have now connected the unit model block using the arcs. However, each of these arcs link to ports on the two unit models that are connected. In this case, the ports consist of the state variables that need to be linked between the unit models. Pyomo provides a convenient method to write these equality constraints for us between two ports and this is done as follows:" + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:28.198384Z", + "start_time": "2025-06-11T22:13:28.176239Z" + } + }, + "source": [ + "TransformationFactory(\"network.expand_arcs\").apply_to(m)" + ], + "outputs": [], + "execution_count": 22 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 2.3 Adding expressions to compute purity and operating costs\n", + "\n", + "In this section, we will add a few Expressions that allows us to evaluate the performance. Expressions provide a convenient way of calculating certain values that are a function of the variables defined in the model. For more details on Expressions, please refer to: https://pyomo.readthedocs.io/en/stable/pyomo_modeling_components/Expressions.html\n", + "\n", + "For this flowsheet, we are interested in computing the purity of the product Benzene stream (i.e. the mole fraction) and the operating cost which is a sum of the cooling and heating cost. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let us first add an Expression to compute the mole fraction of benzene in the `vap_outlet` of F102 which is our product stream. Please note that the var flow_mol_phase_comp has the index - [time, phase, component]. As this is a steady-state flowsheet, the time index by default is 0. The valid phases are [\"Liq\", \"Vap\"]. Similarly the valid component list is [\"benzene\", \"toluene\", \"hydrogen\", \"methane\"]." + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:28.222088Z", + "start_time": "2025-06-11T22:13:28.218400Z" + } + }, + "source": [ + "m.fs.purity = Expression(\n", + " expr=m.fs.F102.vap_outlet.flow_mol_phase_comp[0, \"Vap\", \"benzene\"]\n", + " / (\n", + " m.fs.F102.vap_outlet.flow_mol_phase_comp[0, \"Vap\", \"benzene\"]\n", + " + m.fs.F102.vap_outlet.flow_mol_phase_comp[0, \"Vap\", \"toluene\"]\n", + " )\n", + ")" + ], + "outputs": [], + "execution_count": 23 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, let us add an expression to compute the cooling cost assuming a cost of 0.212E-4 $/kW. Note that cooling utility is required for the reactor (R101) and the first flash (F101). " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:28.248216Z", + "start_time": "2025-06-11T22:13:28.244244Z" + } + }, + "source": [ + "m.fs.cooling_cost = Expression(\n", + " expr=0.212e-7 * (-m.fs.F101.heat_duty[0]) + 0.212e-7 * (-m.fs.R101.heat_duty[0])\n", + ")" + ], + "outputs": [], + "execution_count": 24 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "Now, let us add an expression to compute the heating cost assuming the utility cost as follows:\n", + "
    \n", + "
  • 2.2E-4 dollars/kW for H101
  • \n", + "
  • 1.9E-4 dollars/kW for F102
  • \n", + "
\n", + "Note that the heat duty is in units of watt (J/s). " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:28.271256Z", + "start_time": "2025-06-11T22:13:28.268284Z" + } + }, + "source": [ + "m.fs.heating_cost = Expression(\n", + " expr=2.2e-7 * m.fs.H101.heat_duty[0] + 1.9e-7 * m.fs.F102.heat_duty[0]\n", + ")" + ], + "outputs": [], + "execution_count": 25 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let us now add an expression to compute the total operating cost per year which is basically the sum of the cooling and heating cost we defined above. " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:28.295580Z", + "start_time": "2025-06-11T22:13:28.292627Z" + } + }, + "source": [ + "m.fs.operating_cost = Expression(\n", + " expr=(3600 * 24 * 365 * (m.fs.heating_cost + m.fs.cooling_cost))\n", + ")" + ], + "outputs": [], + "execution_count": 26 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": [ + "## 3 Scaling the Model\n", + "\n", + "In this example, we will simply use the ``AutoScaler`` method to scale our model since this example is focused on introductory flowsheet building for a full process system.\n", + "\n", + "For more direct control, a manual, magnitude based approach can be used. If this method is chosen or appropriate, it is highly recommended to look at these documentation:\n", + "- Scaling Toolbox https://idaes-pse.readthedocs.io/en/stable/reference_guides/scaling/scaling.html\n", + "- Scaling Workshop https://idaes-examples.readthedocs.io/en/latest/docs/scaling/scaler_workshop_doc.html.\n", + "\n", + "In this example, we already imported the ``AutoScaler`` class in the beginning so we just create the ``autoscaler`` object and call the ``scale_model`` method on the model `m`." + ] + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:28.915668Z", + "start_time": "2025-06-11T22:13:28.317020Z" + } + }, + "cell_type": "code", + "source": [ + "autoscaler = AutoScaler()\n", + "autoscaler.scale_model(m)" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "WARNING: model contains export suffix 'scaling_factor' that contains 2\n", + "component keys that are not exported as part of the NL file. Skipping.\n" + ] + } + ], + "execution_count": 27 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 4 Specifying the Model\n", + "### 4.1 Fixing feed conditions\n", + "\n", + "Let us first check how many degrees of freedom exist for this flowsheet using the `degrees_of_freedom` tool we imported earlier. " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:28.948173Z", + "start_time": "2025-06-11T22:13:28.924210Z" + } + }, + "source": [ + "print(degrees_of_freedom(m))" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "29\n" + ] + } + ], + "execution_count": 28 + }, + { + "cell_type": "code", + "metadata": { + "tags": [ + "testing" + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:28.996567Z", + "start_time": "2025-06-11T22:13:28.973555Z" + } + }, + "source": [ + "# Check the degrees of freedom\n", + "assert degrees_of_freedom(m) == 29" + ], + "outputs": [], + "execution_count": 29 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": "We will now be fixing the toluene feed (`I101`) stream to the conditions shown in the flowsheet above. Please note that though this is a pure toluene feed, the remaining components are still assigned a very small non-zero value to help with convergence and initializing." + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.012096Z", + "start_time": "2025-06-11T22:13:29.006965Z" + } + }, + "source": [ + "m.fs.I101.flow_mol_phase_comp[0, \"Vap\", \"benzene\"].fix(1e-5)\n", + "m.fs.I101.flow_mol_phase_comp[0, \"Vap\", \"toluene\"].fix(1e-5)\n", + "m.fs.I101.flow_mol_phase_comp[0, \"Vap\", \"hydrogen\"].fix(1e-5)\n", + "m.fs.I101.flow_mol_phase_comp[0, \"Vap\", \"methane\"].fix(1e-5)\n", + "m.fs.I101.flow_mol_phase_comp[0, \"Liq\", \"benzene\"].fix(1e-5)\n", + "m.fs.I101.flow_mol_phase_comp[0, \"Liq\", \"toluene\"].fix(0.30)\n", + "m.fs.I101.flow_mol_phase_comp[0, \"Liq\", \"hydrogen\"].fix(1e-5)\n", + "m.fs.I101.flow_mol_phase_comp[0, \"Liq\", \"methane\"].fix(1e-5)\n", + "m.fs.I101.temperature.fix(303.2)\n", + "m.fs.I101.pressure.fix(350000)" + ], + "outputs": [], + "execution_count": 30 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "Similarly, let us fix the hydrogen feed (`I102`) to the following conditions in the next cell:\n", + "
    \n", + "
  • FH2 = 0.30 mol/s
  • \n", + "
  • FCH4 = 0.02 mol/s
  • \n", + "
  • Remaining components = 1e-5 mol/s
  • \n", + "
  • T = 303.2 K
  • \n", + "
  • P = 350000 Pa
  • \n", + "
\n", + "\n" + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.036253Z", + "start_time": "2025-06-11T22:13:29.031731Z" + } + }, + "source": [ + "m.fs.I102.flow_mol_phase_comp[0, \"Vap\", \"benzene\"].fix(1e-5)\n", + "m.fs.I102.flow_mol_phase_comp[0, \"Vap\", \"toluene\"].fix(1e-5)\n", + "m.fs.I102.flow_mol_phase_comp[0, \"Vap\", \"hydrogen\"].fix(0.30)\n", + "m.fs.I102.flow_mol_phase_comp[0, \"Vap\", \"methane\"].fix(0.02)\n", + "m.fs.I102.flow_mol_phase_comp[0, \"Liq\", \"benzene\"].fix(1e-5)\n", + "m.fs.I102.flow_mol_phase_comp[0, \"Liq\", \"toluene\"].fix(1e-5)\n", + "m.fs.I102.flow_mol_phase_comp[0, \"Liq\", \"hydrogen\"].fix(1e-5)\n", + "m.fs.I102.flow_mol_phase_comp[0, \"Liq\", \"methane\"].fix(1e-5)\n", + "m.fs.I102.temperature.fix(303.2)\n", + "m.fs.I102.pressure.fix(350000)" + ], + "outputs": [], + "execution_count": 31 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 4.2 Fixing unit model specifications\n", + "\n", + "Now that we have fixed our inlet feed conditions, we will now be fixing the operating conditions for the unit models in the flowsheet. Let us set set the H101 outlet temperature to 600 K. " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.078559Z", + "start_time": "2025-06-11T22:13:29.075531Z" + } + }, + "source": [ + "m.fs.H101.outlet.temperature.fix(600)" + ], + "outputs": [], + "execution_count": 32 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For the StoichiometricReactor, we have to define the conversion in terms of toluene. This requires us to create a new variable for specifying the conversion and adding a Constraint that defines the conversion with respect to toluene. The second degree of freedom for the reactor is to define the heat duty. In this case, let us assume the reactor to be adiabatic i.e. Q = 0. " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.111099Z", + "start_time": "2025-06-11T22:13:29.106956Z" + } + }, + "source": [ + "m.fs.R101.conversion = Var(initialize=0.75, bounds=(0, 1))\n", + "\n", + "m.fs.R101.conv_constraint = Constraint(\n", + " expr=m.fs.R101.conversion * m.fs.R101.inlet.flow_mol_phase_comp[0, \"Vap\", \"toluene\"]\n", + " == (\n", + " m.fs.R101.inlet.flow_mol_phase_comp[0, \"Vap\", \"toluene\"]\n", + " - m.fs.R101.outlet.flow_mol_phase_comp[0, \"Vap\", \"toluene\"]\n", + " )\n", + ")\n", + "\n", + "m.fs.R101.conversion.fix(0.75)\n", + "m.fs.R101.heat_duty.fix(0)" + ], + "outputs": [], + "execution_count": 33 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The Flash conditions for F101 can be set as follows. " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.128972Z", + "start_time": "2025-06-11T22:13:29.125273Z" + } + }, + "source": [ + "m.fs.F101.vap_outlet.temperature.fix(325.0)\n", + "m.fs.F101.deltaP.fix(0)" + ], + "outputs": [], + "execution_count": 34 + }, + { + "cell_type": "code", + "metadata": { + "tags": [ + "solution" + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.171742Z", + "start_time": "2025-06-11T22:13:29.168352Z" + } + }, + "source": [ + "m.fs.F102.vap_outlet.temperature.fix(375)\n", + "m.fs.F102.deltaP.fix(-200000)" + ], + "outputs": [], + "execution_count": 36 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let us fix the purge split fraction to 20% and the outlet pressure of the compressor is set to 350000 Pa. " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.196247Z", + "start_time": "2025-06-11T22:13:29.192956Z" + } + }, + "source": [ + "m.fs.S101.split_fraction[0, \"purge\"].fix(0.2)\n", + "m.fs.C101.outlet.pressure.fix(350000)" + ], + "outputs": [], + "execution_count": 37 + }, + { + "cell_type": "code", + "metadata": { + "tags": [ + "solution" + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.263397Z", + "start_time": "2025-06-11T22:13:29.241129Z" + } + }, + "source": [ + "print(degrees_of_freedom(m))" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0\n" + ] + } + ], + "execution_count": 39 + }, + { + "cell_type": "code", + "metadata": { + "tags": [ + "testing" + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.304978Z", + "start_time": "2025-06-11T22:13:29.282394Z" + } + }, + "source": [ + "# Check the degrees of freedom\n", + "assert degrees_of_freedom(m) == 0" + ], + "outputs": [], + "execution_count": 40 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 5 Initializing the Model\n", + "\n", + "\n", + "\n", + "When a flowsheet contains a recycle loop, the outlet of a downstream unit becomes the inlet of an upstream unit, creating a cyclic dependency that prevents straightforward calculation of all stream conditions. The tear\u2010stream method is necessary because it \u201cbreaks\u201d this loop: you select one recycle stream as the tear, assign it an initial guess, and then solve the rest of the flowsheet as if it were acyclic. Once the downstream units compute their outputs, you compare the calculated value of the torn stream to your initial guess and iteratively adjust until they coincide. Without tearing, the solver cannot establish a proper topological sequence or drive the recycle to convergence, making initialization\u2014and ultimately steady\u2010state convergence\u2014impossible.\n", + "\n", + "It is important to determine the tear stream for a flowsheet which will be demonstrated below.\n", + "\n", + "\n", + "![](HDA_flowsheet.png)\n", + "\n", + "Currently, there are two methods of initializing a full flowsheet: using the sequential decomposition tool, or manually propagating through the flowsheet. Both methods will be shown.\n", + "\n", + "### 5.1 Sequential Decomposition\n", + "\n", + "This section will demonstrate how to use the built-in sequential decomposition tool to initialize our flowsheet. Sequential Decomposition is a tool from pyomo where the documentation can be found here https://pyomo.readthedocs.io/en/stable/explanation/modeling/network.html#sequential-decomposition\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": "Let us first create an object for the SequentialDecomposition and specify our options for this. We can also create a graph for our flowsheet to determine the tear set and order." + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.330212Z", + "start_time": "2025-06-11T22:13:29.324872Z" + } + }, + "source": [ + "seq = SequentialDecomposition()\n", + "seq.options.select_tear_method = \"heuristic\"\n", + "seq.options.tear_method = \"Wegstein\"\n", + "seq.options.iterLim = 3\n", + "\n", + "# Using the SD tool\n", + "G = seq.create_graph(m)\n", + "heuristic_tear_set = seq.tear_set_arcs(G, method=\"heuristic\")\n", + "order = seq.calculation_order(G)" + ], + "outputs": [], + "execution_count": 41 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Which is the tear stream? Display tear set and order" + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.353734Z", + "start_time": "2025-06-11T22:13:29.350730Z" + } + }, + "source": [ + "for o in heuristic_tear_set:\n", + " print(o.name)" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "fs.s03\n" + ] + } + ], + "execution_count": 42 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "What sequence did the SD tool determine to solve this flowsheet with the least number of tears? " + ] + }, + { + "cell_type": "code", + "metadata": { + "scrolled": true, + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.391173Z", + "start_time": "2025-06-11T22:13:29.388153Z" + } + }, + "source": [ + "for o in order:\n", + " print(o[0].name)" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "fs.I101\n", + "fs.R101\n", + "fs.F101\n", + "fs.S101\n", + "fs.C101\n", + "fs.M101\n" + ] + } + ], + "execution_count": 43 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + " \n", + "\n", + "![](HDA_tear_stream.png) \n", + "\n", + "\n", + "The SequentialDecomposition tool has determined that the tear stream is the mixer outlet. You can see this shown in the picture of the flowsheet above as the outlet of the mixer as the two lines crossing it identifying it as the tear stream. We will need to provide a reasonable guess for this." + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.430684Z", + "start_time": "2025-06-11T22:13:29.426921Z" + } + }, + "source": [ + "tear_guesses = {\n", + " \"flow_mol_phase_comp\": {\n", + " (0, \"Liq\", \"benzene\"): 1e-5,\n", + " (0, \"Liq\", \"toluene\"): 0.30,\n", + " (0, \"Liq\", \"methane\"): 1e-5,\n", + " (0, \"Liq\", \"hydrogen\"): 1e-5,\n", + " (0, \"Vap\", \"benzene\"): 1e-5,\n", + " (0, \"Vap\", \"toluene\"): 1e-5,\n", + " (0, \"Vap\", \"methane\"): 0.02,\n", + " (0, \"Vap\", \"hydrogen\"): 0.30,\n", + " },\n", + " \"temperature\": {0: 303},\n", + " \"pressure\": {0: 350000},\n", + "}\n", + "\n", + "# Pass the tear_guess to the SD tool\n", + "seq.set_guesses_for(m.fs.H101.inlet, tear_guesses)" + ], + "outputs": [], + "execution_count": 44 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next, we need to tell the tool how to initialize a particular unit. We will be writing a python function which takes in a \"unit\" and calls the initialize method on that unit. " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.465203Z", + "start_time": "2025-06-11T22:13:29.462031Z" + } + }, + "source": [ + "def function(unit):\n", + " try:\n", + " initializer = unit.default_initializer()\n", + " initializer.initialize(unit, output_level=idaeslog.INFO)\n", + " except InitializationError:\n", + " solver = get_solver()\n", + " solver.solve(unit)" + ], + "outputs": [], + "execution_count": 45 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We are now ready to initialize our flowsheet in a sequential mode. Note that we specifically set the iteration limit to be 5 as we are trying to use this tool only to get a good set of initial values such that IPOPT can then take over and solve this flowsheet for us. " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.505027Z", + "start_time": "2025-06-11T22:13:29.501933Z" + } + }, + "source": "# seq.run(m, function)", + "outputs": [], + "execution_count": 46 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": [ + "### 5.2 Manual Propogation Method\n", + "\n", + "This method uses a more direct approach to initialize the flowsheet, utilizing the updated initalizer method and propogating manually throught the flowsheet and solving for the tear stream directly.\n", + "Lets first import a helper function that will help us manually propagate and step through the flowsheet" + ] + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.536579Z", + "start_time": "2025-06-11T22:13:29.533074Z" + } + }, + "cell_type": "code", + "source": "from idaes.core.util.initialization import propagate_state", + "outputs": [], + "execution_count": 47 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": [ + "Now we can setup our initial guesses for the tear stream which we know is the outlet of the `Mixer` or the inlet of the `Heater`. We can use the same initial guesses used in the first method. We also want to ensure that the degrees of freedom are consistent while we manually intialize the model.\n", + "\n", + "We will first ensure that are current degrees of freedom is still zero" + ] + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.583098Z", + "start_time": "2025-06-11T22:13:29.553382Z" + } + }, + "cell_type": "code", + "source": "print(f\"The DOF is {degrees_of_freedom(m)} initially\")", + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The DOF is 0 initially\n" + ] + } + ], + "execution_count": 48 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "Now we can manually deactivate the tear stream, creating a separation between the `Mixer` and `Heater`. This should reduce the degrees of freedom by 10 since the inlet of the `Heater` now contains no values to solve the unit model. To deactivate a stream, simply use `m.fs.s03_expanded.deactivate()`. This expanded stream is just a different version of the `Arc` stream that is able to be deactivated." + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.633917Z", + "start_time": "2025-06-11T22:13:29.610932Z" + } + }, + "cell_type": "code", + "source": [ + "m.fs.s03_expanded.deactivate()\n", + "\n", + "print(f\"The DOF is {degrees_of_freedom(m)} after deactivating the tear stream\")" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The DOF is 10 after deactivating the tear stream\n" + ] + } + ], + "execution_count": 49 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "Now we can provide the `Heater` inlet 10 guess values to bring the degrees of freedom back to 0 and start the manual initialization process. We can run this convenient loop to assign each of these guesses to the inlet of the heater." + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.702707Z", + "start_time": "2025-06-11T22:13:29.654459Z" + } + }, + "cell_type": "code", + "source": [ + "tear_guesses = {\n", + " \"flow_mol_phase_comp\": {\n", + " (0, \"Liq\", \"benzene\"): 1e-5,\n", + " (0, \"Liq\", \"toluene\"): 0.30,\n", + " (0, \"Liq\", \"methane\"): 1e-5,\n", + " (0, \"Liq\", \"hydrogen\"): 1e-5,\n", + " (0, \"Vap\", \"benzene\"): 1e-5,\n", + " (0, \"Vap\", \"toluene\"): 1e-5,\n", + " (0, \"Vap\", \"methane\"): 0.02,\n", + " (0, \"Vap\", \"hydrogen\"): 0.30,\n", + "\n", + " },\n", + " \"temperature\": {0: 303},\n", + " \"pressure\": {0: 350000},\n", + "}\n", + "\n", + "for k, v in tear_guesses.items():\n", + " for k1, v1 in v.items():\n", + " getattr(m.fs.s03.destination, k)[k1].fix(v1)\n", + "\n", + "DOF_initial = degrees_of_freedom(m)\n", + "print(f\"The DOF is {degrees_of_freedom(m)} after providing the initial guesses\")" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The DOF is 0 after providing the initial guesses\n" + ] + } + ], + "execution_count": 50 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "The next step is to manually initialize each unit model starting from the `Heater` and then propagate the connection between it and the next unit model. This manual process ensures a strict order to the user's specification if that is desired. The current standard for initializing a unit model is to use an initializer object most compatible for that unit model. This can most often be done by utilizing the `default_initializer()` method attached to the unit model and then to call the `initialize()` method with the unit model as the argument." + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:31.347435Z", + "start_time": "2025-06-11T22:13:29.712721Z" + } + }, + "cell_type": "code", + "source": [ + "m.fs.H101.default_initializer().initialize(m.fs.H101) # Initialize Heater\n", + "propagate_state(m.fs.s04) # Establish connection between Heater and Reactor\n", + "m.fs.R101.default_initializer().initialize(m.fs.R101) # Initialize Reactor\n", + "propagate_state(m.fs.s05) # Establish connection between Reactor and First Flash Unit\n", + "m.fs.F101.default_initializer().initialize(m.fs.F101) # Initialize First Flash Unit\n", + "propagate_state(m.fs.s06) # Establish connection between First Flash Unit and Splitter\n", + "propagate_state(m.fs.s07) # Establish connection between First Flash Unit and Second Flash Unit\n", + "m.fs.S101.default_initializer().initialize(m.fs.S101) # Initialize Splitter\n", + "propagate_state(m.fs.s08) # Establish connection between Splitter and Compressor\n", + "m.fs.C101.default_initializer().initialize(m.fs.C101) # Initialize Compressor\n", + "propagate_state(m.fs.s09) # Establish connection between Compressor and Mixer\n", + "m.fs.I101.default_initializer().initialize(m.fs.I101) # Initialize Toluene Inlet\n", + "propagate_state(m.fs.s01) # Establish connection between Toluene Inlet and Mixer\n", + "m.fs.I102.default_initializer().initialize(m.fs.I102) # Initialize Hydrogen Inlet\n", + "propagate_state(m.fs.s02) # Establish connection between Hydrogen Inlet and Mixer\n", + "m.fs.M101.default_initializer().initialize(m.fs.M101) # Initialize Mixer\n", + "propagate_state(m.fs.s03) # Establish connection between Mixer and Heater\n", + "m.fs.F102.default_initializer().initialize(m.fs.F102) # Initialize Second Flash Unit\n", + "propagate_state(m.fs.s10) # Establish connection between Second Flash Unit and Benzene Product\n", + "propagate_state(m.fs.s11) # Establish connection between Second Flash Unit and Toluene Product\n", + "propagate_state(m.fs.s12) # Establish connection between Splitter and Purge Product\n" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2025-06-11 16:13:29 [INFO] idaes.init.fs.H101.control_volume.properties_in: Initialization Complete\n", + "2025-06-11 16:13:30 [INFO] idaes.init.fs.H101.control_volume.properties_out: Initialization Complete\n", + "2025-06-11 16:13:30 [INFO] idaes.init.fs.R101.control_volume.properties_in: Initialization Complete\n", + "2025-06-11 16:13:30 [INFO] idaes.init.fs.R101.control_volume.properties_out: Initialization Complete\n", + "2025-06-11 16:13:30 [INFO] idaes.init.fs.F101.control_volume.properties_in: Initialization Complete\n", + "2025-06-11 16:13:30 [INFO] idaes.init.fs.F101.control_volume.properties_out: Initialization Complete\n", + "2025-06-11 16:13:30 [INFO] idaes.init.fs.S101.mixed_state: Initialization Complete\n", + "2025-06-11 16:13:30 [INFO] idaes.init.fs.S101.purge_state: Initialization Complete\n", + "2025-06-11 16:13:30 [INFO] idaes.init.fs.S101.recycle_state: Initialization Complete\n", + "2025-06-11 16:13:30 [INFO] idaes.init.fs.S101: Initialization Step 2 Complete: optimal - \n", + "2025-06-11 16:13:30 [INFO] idaes.init.fs.C101.control_volume.properties_in: Initialization Complete\n", + "2025-06-11 16:13:31 [INFO] idaes.init.fs.C101.control_volume.properties_out: Initialization Complete\n", + "2025-06-11 16:13:31 [INFO] idaes.init.fs.I101.properties: Initialization Complete\n", + "2025-06-11 16:13:31 [INFO] idaes.init.fs.I102.properties: Initialization Complete\n", + "2025-06-11 16:13:31 [INFO] idaes.init.fs.M101.inlet_1_state: Initialization Complete\n", + "2025-06-11 16:13:31 [INFO] idaes.init.fs.M101.inlet_2_state: Initialization Complete\n", + "2025-06-11 16:13:31 [INFO] idaes.init.fs.M101.inlet_3_state: Initialization Complete\n", + "2025-06-11 16:13:31 [INFO] idaes.init.fs.M101.mixed_state: Initialization Complete\n", + "2025-06-11 16:13:31 [INFO] idaes.init.fs.M101: Initialization Complete: optimal - \n", + "2025-06-11 16:13:31 [INFO] idaes.init.fs.F102.control_volume.properties_in: Initialization Complete\n", + "2025-06-11 16:13:31 [INFO] idaes.init.fs.F102.control_volume.properties_out: Initialization Complete\n" + ] + } + ], + "execution_count": 51 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "Now we solve the system to allow the outlet of the mixer to reach a converged congruence with the inlet of the heater." + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:31.935414Z", + "start_time": "2025-06-11T22:13:31.357025Z" + } + }, + "cell_type": "code", + "source": [ + "solver = get_solver()\n", + "results = solver.solve(m, tee=True)" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "WARNING: model contains export suffix 'scaling_factor' that contains 40\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "Ipopt 3.13.2: nlp_scaling_method=gradient-based\n", + "tol=1e-06\n", + "max_iter=200\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...: 1112\n", + "Number of nonzeros in inequality constraint Jacobian.: 0\n", + "Number of nonzeros in Lagrangian Hessian.............: 999\n", + "\n", + "Total number of variables............................: 380\n", + " variables with only lower bounds: 0\n", + " variables with lower and upper bounds: 186\n", + " variables with only upper bounds: 0\n", + "Total number of equality constraints.................: 380\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 1.24e+05 0.00e+00 -1.0 0.00e+00 - 0.00e+00 0.00e+00 0\n", + " 1 0.0000000e+00 1.24e+05 2.12e+00 -1.0 1.99e+05 - 1.39e-01 3.68e-04h 10\n", + " 2 0.0000000e+00 1.24e+05 5.49e+00 -1.0 1.99e+05 - 1.43e-01 3.66e-04h 10\n", + " 3 0.0000000e+00 1.24e+05 4.13e+01 -1.0 2.00e+05 - 9.51e-01 3.64e-04h 10\n", + " 4 0.0000000e+00 1.24e+05 7.47e+01 -1.0 2.00e+05 - 1.77e-01 3.63e-04h 10\n", + " 5 0.0000000e+00 1.24e+05 4.07e+02 -1.0 2.01e+05 - 9.90e-01 3.61e-04h 10\n", + " 6 0.0000000e+00 1.24e+05 6.97e+02 -1.0 2.01e+05 - 1.63e-01 3.60e-04h 10\n", + " 7 0.0000000e+00 1.24e+05 3.75e+03 -1.0 2.02e+05 - 9.90e-01 3.58e-04h 10\n", + " 8 0.0000000e+00 1.24e+05 6.44e+03 -1.0 2.02e+05 - 1.63e-01 3.57e-04h 10\n", + " 9 0.0000000e+00 1.24e+05 3.51e+04 -1.0 2.03e+05 - 1.00e+00 3.55e-04h 10\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 10 0.0000000e+00 1.24e+05 6.05e+04 -1.0 2.04e+05 - 1.62e-01 3.54e-04h 10\n", + " 11 0.0000000e+00 2.01e+06 2.73e+05 -1.0 2.04e+05 - 1.00e+00 1.80e-01w 1\n", + " 12 0.0000000e+00 2.01e+06 3.91e+07 -1.0 7.38e+05 - 6.21e-02 5.22e-04w 1\n", + " 13 0.0000000e+00 2.01e+06 6.94e+11 -1.0 7.50e+05 - 9.13e-02 5.14e-06w 1\n", + " 14 0.0000000e+00 1.24e+05 3.32e+05 -1.0 6.22e+04 - 1.00e+00 3.52e-04h 9\n", + " 15 0.0000000e+00 1.24e+05 5.43e+05 -1.0 2.05e+05 - 1.41e-01 3.51e-04h 10\n", + " 16 0.0000000e+00 1.24e+05 3.01e+06 -1.0 2.05e+05 - 1.00e+00 3.49e-04h 10\n", + " 17 0.0000000e+00 1.24e+05 4.76e+06 -1.0 2.06e+05 - 1.28e-01 3.48e-04h 10\n", + " 18 0.0000000e+00 1.24e+05 2.66e+07 -1.0 2.06e+05 - 1.00e+00 3.46e-04h 10\n", + " 19 0.0000000e+00 1.24e+05 4.23e+07 -1.0 2.07e+05 - 1.28e-01 3.45e-04h 10\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 20 0.0000000e+00 1.24e+05 2.38e+08 -1.0 2.08e+05 - 1.00e+00 3.43e-04h 10\n", + " 21 0.0000000e+00 1.24e+05 3.80e+08 -1.0 2.08e+05 - 1.28e-01 3.42e-04h 10\n", + " 22 0.0000000e+00 1.23e+05 2.16e+09 -1.0 2.09e+05 - 1.00e+00 3.40e-04h 10\n", + " 23 0.0000000e+00 1.23e+05 3.45e+09 -1.0 2.09e+05 - 1.28e-01 3.39e-04h 10\n", + " 24 0.0000000e+00 1.96e+06 1.26e+10 -1.0 2.10e+05 - 7.69e-01 1.73e-01w 1\n", + " 25 0.0000000e+00 1.96e+06 1.91e+12 -1.0 7.43e+05 - 6.14e-02 5.08e-04w 1\n", + " 26 0.0000000e+00 1.96e+06 7.01e+16 -1.0 7.55e+05 - 1.84e-01 5.01e-06w 1\n", + " 27 0.0000000e+00 1.23e+05 1.60e+10 -1.0 1.00e+05 - 7.69e-01 3.37e-04h 9\n", + " 28 0.0000000e+00 1.23e+05 2.61e+10 -1.0 2.10e+05 - 1.33e-01 3.36e-04h 10\n", + " 29 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.11e+05 - 1.00e+00 3.35e-04h 10\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 30 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.11e+05 - 1.27e-01 3.33e-04h 10\n", + " 31 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.12e+05 - 5.83e-01 3.32e-04h 10\n", + " 32 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.13e+05 - 1.41e-01 3.30e-04h 10\n", + " 33 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.13e+05 - 1.00e+00 3.29e-04h 10\n", + " 34 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.14e+05 - 1.26e-01 3.28e-04h 10\n", + " 35 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.14e+05 - 6.16e-01 3.26e-04h 10\n", + " 36 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.15e+05 - 1.38e-01 3.25e-04h 10\n", + " 37 0.0000000e+00 1.90e+06 5.27e+11 -1.0 2.15e+05 - 1.00e+00 1.66e-01w 1\n", + " 38 0.0000000e+00 1.90e+06 6.09e+13 -1.0 2.72e+05 - 1.39e-01 1.43e-03w 1\n", + " 39 0.0000000e+00 1.90e+06 1.06e+17 -1.0 7.68e+05 - 9.45e-02 4.82e-06w 1\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 40 0.0000000e+00 1.23e+05 1.04e+11 -1.0 7.68e+05 - 1.00e+00 3.23e-04h 9\n", + " 41 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.16e+05 - 1.26e-01 3.22e-04h 10\n", + " 42 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.17e+05 - 6.03e-01 3.21e-04h 10\n", + " 43 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.17e+05 - 1.38e-01 3.19e-04h 10\n", + " 44 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.18e+05 - 1.00e+00 3.18e-04h 10\n", + " 45 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.18e+05 - 1.25e-01 3.17e-04h 10\n", + " 46 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.19e+05 - 6.03e-01 3.15e-04h 10\n", + " 47 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.20e+05 - 1.38e-01 3.14e-04h 10\n", + " 48 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.20e+05 - 1.00e+00 3.13e-04h 10\n", + " 49 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.21e+05 - 1.25e-01 3.11e-04h 10\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 50 0.0000000e+00 1.85e+06 3.37e+11 -1.0 2.21e+05 - 5.99e-01 1.59e-01w 1\n", + " 51 0.0000000e+00 1.85e+06 5.67e+13 -1.0 7.53e+05 - 6.18e-02 4.82e-04w 1\n", + " 52 0.0000000e+00 1.85e+06 1.08e+17 -1.0 7.64e+05 - 2.20e-01 4.75e-06w 1\n", + " 53 0.0000000e+00 1.23e+05 1.04e+11 -1.0 7.64e+05 - 5.99e-01 3.10e-04h 9\n", + " 54 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.22e+05 - 1.38e-01 3.09e-04h 10\n", + " 55 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.22e+05 - 1.00e+00 3.07e-04h 10\n", + " 56 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.23e+05 - 1.24e-01 3.06e-04h 10\n", + " 57 0.0000000e+00 1.22e+05 1.04e+11 -1.0 2.24e+05 - 5.97e-01 3.05e-04h 10\n", + " 58 0.0000000e+00 1.22e+05 1.04e+11 -1.0 2.24e+05 - 1.37e-01 3.04e-04h 10\n", + " 59 0.0000000e+00 1.22e+05 1.04e+11 -1.0 2.25e+05 - 1.00e+00 3.02e-04h 10\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 60 0.0000000e+00 1.22e+05 1.04e+11 -1.0 2.25e+05 - 1.24e-01 3.01e-04h 10\n", + " 61 0.0000000e+00 1.22e+05 1.04e+11 -1.0 2.26e+05 - 5.94e-01 3.00e-04h 10\n", + " 62 0.0000000e+00 1.22e+05 1.04e+11 -1.0 2.26e+05 - 1.37e-01 2.98e-04h 10\n", + " 63 0.0000000e+00 1.79e+06 6.03e+11 -1.0 2.27e+05 - 1.00e+00 1.52e-01w 1\n", + " 64 0.0000000e+00 1.79e+06 9.13e+13 -1.0 7.58e+05 - 6.05e-02 4.69e-04w 1\n", + " 65 0.0000000e+00 1.79e+06 1.10e+17 -1.0 7.68e+05 - 1.20e-01 4.63e-06w 1\n", + " 66 0.0000000e+00 1.22e+05 1.04e+11 -1.0 7.69e+05 - 1.00e+00 2.97e-04h 9\n", + " 67 0.0000000e+00 1.22e+05 1.04e+11 -1.0 2.28e+05 - 1.23e-01 2.96e-04h 10\n", + " 68 0.0000000e+00 1.22e+05 1.04e+11 -1.0 2.28e+05 - 5.91e-01 2.95e-04h 10\n", + " 69 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.29e+05 - 1.36e-01 2.93e-04h 10\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 70 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.29e+05 - 1.00e+00 2.92e-04h 10\n", + " 71 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.30e+05 - 1.23e-01 2.91e-04h 10\n", + " 72 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.31e+05 - 5.89e-01 2.90e-04h 10\n", + " 73 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.31e+05 - 1.36e-01 2.89e-04h 10\n", + " 74 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.32e+05 - 1.00e+00 2.87e-04h 10\n", + " 75 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.32e+05 - 1.23e-01 2.86e-04h 10\n", + " 76 0.0000000e+00 1.74e+06 3.75e+11 -1.0 2.33e+05 - 5.86e-01 1.46e-01w 1\n", + " 77 0.0000000e+00 1.74e+06 6.57e+13 -1.0 7.62e+05 - 6.18e-02 4.58e-04w 1\n", + " 78 0.0000000e+00 1.74e+06 1.12e+17 -1.0 7.73e+05 - 2.30e-01 4.51e-06w 1\n", + " 79 0.0000000e+00 1.22e+05 1.05e+11 -1.0 7.73e+05 - 5.86e-01 2.85e-04h 9\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 80 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.34e+05 - 1.36e-01 2.84e-04h 10\n", + " 81 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.34e+05 - 1.00e+00 2.83e-04h 10\n", + " 82 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.35e+05 - 1.22e-01 2.81e-04h 10\n", + " 83 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.35e+05 - 5.83e-01 2.80e-04h 10\n", + " 84 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.36e+05 - 1.35e-01 2.79e-04h 10\n", + " 85 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.37e+05 - 1.00e+00 2.78e-04h 10\n", + " 86 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.37e+05 - 1.22e-01 2.77e-04h 10\n", + " 87 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.38e+05 - 5.81e-01 2.76e-04h 10\n", + " 88 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.38e+05 - 1.35e-01 2.74e-04h 10\n", + " 89 0.0000000e+00 1.69e+06 6.88e+11 -1.0 2.39e+05 - 1.00e+00 1.40e-01w 1\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 90 0.0000000e+00 1.69e+06 1.08e+14 -1.0 7.66e+05 - 6.03e-02 4.46e-04w 1\n", + " 91 0.0000000e+00 1.69e+06 1.14e+17 -1.0 7.77e+05 - 1.22e-01 4.40e-06w 1\n", + " 92 0.0000000e+00 1.22e+05 1.05e+11 -1.0 7.77e+05 - 1.00e+00 2.73e-04h 9\n", + " 93 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.39e+05 - 1.21e-01 2.72e-04h 10\n", + " 94 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.40e+05 - 5.78e-01 2.71e-04h 10\n", + " 95 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.41e+05 - 1.34e-01 2.70e-04h 10\n", + " 96 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.41e+05 - 1.00e+00 2.69e-04h 10\n", + " 97 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.42e+05 - 1.21e-01 2.68e-04h 10\n", + " 98 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.42e+05 - 5.76e-01 2.67e-04h 10\n", + " 99 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.43e+05 - 1.34e-01 2.65e-04h 10\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 100 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.44e+05 - 1.00e+00 2.64e-04h 10\n", + " 101 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.44e+05 - 1.20e-01 2.63e-04h 10\n", + " 102 0.0000000e+00 1.64e+06 4.17e+11 -1.0 2.45e+05 - 5.73e-01 1.34e-01w 1\n", + " 103 0.0000000e+00 1.64e+06 7.61e+13 -1.0 7.71e+05 - 6.17e-02 4.35e-04w 1\n", + " 104 0.0000000e+00 1.64e+06 1.17e+17 -1.0 7.81e+05 - 2.38e-01 4.29e-06w 1\n", + " 105 0.0000000e+00 1.21e+05 1.05e+11 -1.0 7.81e+05 - 5.73e-01 2.62e-04h 9\n", + " 106 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.45e+05 - 1.34e-01 2.61e-04h 10\n", + " 107 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.46e+05 - 1.00e+00 2.60e-04h 10\n", + " 108 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.47e+05 - 1.20e-01 2.59e-04h 10\n", + " 109 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.47e+05 - 5.71e-01 2.58e-04h 10\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 110 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.48e+05 - 1.33e-01 2.57e-04h 10\n", + " 111 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.49e+05 - 1.00e+00 2.56e-04h 10\n", + " 112 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.49e+05 - 1.19e-01 2.55e-04h 10\n", + " 113 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.50e+05 - 5.68e-01 2.54e-04h 10\n", + " 114 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.50e+05 - 1.33e-01 2.53e-04h 10\n", + " 115 0.0000000e+00 1.59e+06 7.85e+11 -1.0 2.51e+05 - 1.00e+00 1.29e-01w 1\n", + " 116 0.0000000e+00 1.59e+06 1.28e+14 -1.0 7.75e+05 - 6.01e-02 4.25e-04w 1\n", + " 117 0.0000000e+00 1.59e+06 1.19e+17 -1.0 7.85e+05 - 1.23e-01 4.19e-06w 1\n", + " 118 0.0000000e+00 1.21e+05 1.05e+11 -1.0 7.85e+05 - 1.00e+00 2.52e-04h 9\n", + " 119 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.52e+05 - 1.19e-01 2.51e-04h 10\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 120 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.52e+05 - 5.66e-01 2.50e-04h 10\n", + " 121 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.53e+05 - 1.32e-01 2.49e-04h 10\n", + " 122 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.53e+05 - 1.00e+00 2.47e-04h 10\n", + " 123 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.54e+05 - 1.18e-01 4.93e-04h 9\n", + " 124 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.55e+05 - 5.60e-01 4.89e-04h 9\n", + " 125 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.56e+05 - 1.32e-01 4.85e-04h 9\n", + " 126 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.58e+05 - 1.00e+00 4.81e-04h 9\n", + " 127 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.59e+05 - 1.18e-01 4.77e-04h 9\n", + " 128 0.0000000e+00 1.52e+06 4.74e+11 -1.0 2.60e+05 - 5.55e-01 1.21e-01w 1\n", + " 129 0.0000000e+00 1.52e+06 9.09e+13 -1.0 7.80e+05 - 6.17e-02 4.09e-04w 1\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 130 0.0000000e+00 1.52e+06 1.22e+17 -1.0 7.90e+05 - 2.48e-01 4.04e-06w 1\n", + " 131 0.0000000e+00 1.20e+05 1.05e+11 -1.0 7.90e+05 - 5.55e-01 4.73e-04h 8\n", + " 132 0.0000000e+00 1.20e+05 1.05e+11 -1.0 2.61e+05 - 1.31e-01 4.69e-04h 9\n", + " 133 0.0000000e+00 1.20e+05 1.05e+11 -1.0 2.63e+05 - 1.00e+00 1.86e-03h 7\n", + " 134 0.0000000e+00 1.20e+05 1.05e+11 -1.0 2.67e+05 - 1.16e-01 1.80e-03h 7\n", + " 135 0.0000000e+00 1.20e+05 1.05e+11 -1.0 2.72e+05 - 5.16e-01 1.74e-03h 7\n", + " 136 0.0000000e+00 1.19e+05 1.05e+11 -1.0 2.77e+05 - 1.29e-01 3.38e-03h 6\n", + " 137 0.0000000e+00 1.19e+05 1.05e+11 -1.0 2.87e+05 - 1.00e+00 3.17e-03h 6\n", + " 138 0.0000000e+00 1.19e+05 1.06e+11 -1.0 2.98e+05 - 1.10e-01 2.97e-03h 6\n", + " 139 0.0000000e+00 1.18e+05 1.06e+11 -1.0 3.08e+05 - 4.88e-01 2.79e-03h 6\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 140 0.0000000e+00 1.18e+05 1.07e+11 -1.0 3.18e+05 - 1.22e-01 5.25e-03h 5\n", + " 141 0.0000000e+00 1.01e+06 1.89e+12 -1.0 3.38e+05 - 1.00e+00 7.42e-02w 1\n", + " 142 0.0000000e+00 1.01e+06 3.99e+14 -1.0 8.16e+05 - 5.93e-02 3.07e-04w 1\n", + " 143 0.0000000e+00 1.01e+06 1.56e+17 -1.0 8.24e+05 - 1.44e-01 3.04e-06w 1\n", + " 144 0.0000000e+00 1.17e+05 1.08e+11 -1.0 8.24e+05 - 1.00e+00 4.64e-03h 4\n", + " 145 0.0000000e+00 1.17e+05 1.08e+11 -1.0 3.59e+05 - 1.01e-01 2.06e-03h 6\n", + " 146 0.0000000e+00 1.17e+05 1.08e+11 -1.0 3.69e+05 - 4.67e-01 1.94e-03h 6\n", + " 147 0.0000000e+00 1.17e+05 1.08e+11 -1.0 3.79e+05 - 1.12e-01 1.83e-03h 6\n", + " 148 0.0000000e+00 1.16e+05 1.09e+11 -1.0 3.89e+05 - 1.00e+00 8.66e-04h 7\n", + " 149 0.0000000e+00 1.16e+05 1.09e+11 -1.0 3.94e+05 - 9.61e-02 8.42e-04h 7\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 150 0.0000000e+00 1.16e+05 1.09e+11 -1.0 3.99e+05 - 4.61e-01 4.10e-04h 8\n", + " 151 0.0000000e+00 1.16e+05 1.09e+11 -1.0 4.01e+05 - 1.09e-01 4.04e-04h 8\n", + " 152 0.0000000e+00 1.16e+05 1.09e+11 -1.0 4.04e+05 - 1.00e+00 3.98e-04h 8\n", + " 153 0.0000000e+00 1.16e+05 1.09e+11 -1.0 4.06e+05 - 9.46e-02 9.83e-05h 10\n", + " 154 0.0000000e+00 6.92e+05 1.57e+12 -1.0 4.07e+05 - 4.54e-01 5.01e-02w 1\n", + " 155 0.0000000e+00 6.92e+05 4.69e+14 -1.0 8.35e+05 - 6.23e-02 2.42e-04w 1\n", + " 156 0.0000000e+00 6.92e+05 1.93e+17 -1.0 8.42e+05 - 2.97e-01 2.40e-06w 1\n", + " 157 0.0000000e+00 1.16e+05 1.09e+11 -1.0 8.42e+05 - 4.54e-01 9.79e-05h 9\n", + " 158 0.0000000e+00 1.16e+05 1.09e+11 -1.0 4.08e+05 - 1.09e-01 9.76e-05h 10\n", + " 159 0.0000000e+00 1.16e+05 1.09e+11 -1.0 4.08e+05 - 1.00e+00 9.73e-05h 10\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 160 0.0000000e+00 1.16e+05 1.09e+11 -1.0 4.09e+05 - 9.43e-02 9.70e-05h 10\n", + " 161 0.0000000e+00 1.16e+05 1.09e+11 -1.0 4.09e+05 - 4.54e-01 4.83e-05h 11\n", + " 162 0.0000000e+00 1.16e+05 1.09e+11 -1.0 4.10e+05 - 1.08e-01 4.82e-05h 11\n", + " 163 0.0000000e+00 1.16e+05 1.09e+11 -1.0 4.10e+05 - 1.00e+00 1.20e-05h 13\n", + " 164 0.0000000e+00 1.16e+05 1.09e+11 -1.0 4.10e+05 - 9.42e-02 1.20e-05h 13\n", + " 165 0.0000000e+00 1.16e+05 1.09e+11 -1.0 4.10e+05 - 4.55e-01 9.62e-05h 10\n", + " 166 0.0000000e+00 1.16e+05 1.09e+11 -1.0 4.11e+05 - 1.08e-01 9.59e-05h 10\n", + " 167 0.0000000e+00 6.75e+05 3.70e+12 -1.0 4.11e+05 - 1.00e+00 4.89e-02w 1\n", + " 168 0.0000000e+00 6.74e+05 9.73e+14 -1.0 8.36e+05 - 5.90e-02 2.39e-04w 1\n", + " 169 0.0000000e+00 6.74e+05 1.96e+17 -1.0 8.43e+05 - 1.50e-01 2.37e-06w 1\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 170 0.0000000e+00 1.16e+05 1.09e+11 -1.0 8.43e+05 - 1.00e+00 9.56e-05h 9\n", + " 171 0.0000000e+00 1.16e+05 1.09e+11 -1.0 4.12e+05 - 9.39e-02 2.38e-05h 12\n", + " 172 0.0000000e+00 1.16e+05 1.09e+11 -1.0 4.12e+05 - 4.52e-01 1.19e-05h 13\n", + " 173r 0.0000000e+00 1.16e+05 1.00e+03 0.6 0.00e+00 - 0.00e+00 3.72e-07R 18\n", + " 174r 0.0000000e+00 1.25e+05 1.78e+03 0.6 3.70e+04 - 1.19e-02 1.34e-03f 1\n", + " 175r 0.0000000e+00 1.10e+05 1.54e+03 0.6 7.30e+03 - 3.61e-03 8.33e-03f 1\n", + " 176r 0.0000000e+00 1.02e+05 4.12e+03 0.6 5.25e+03 - 6.33e-02 8.82e-03f 1\n", + " 177r 0.0000000e+00 9.63e+04 4.43e+03 0.6 4.50e+03 - 3.61e-02 2.36e-02f 1\n", + " 178r 0.0000000e+00 9.20e+04 1.63e+04 0.6 3.97e+03 - 2.78e-01 6.99e-02f 1\n", + " 179r 0.0000000e+00 8.79e+04 3.34e+04 0.6 4.66e+02 - 8.68e-01 3.38e-01f 1\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 180r 0.0000000e+00 1.06e+05 3.33e+02 0.6 1.05e+02 - 1.00e+00 1.00e+00f 1\n", + " 181r 0.0000000e+00 1.05e+05 2.26e+02 0.6 2.72e+01 - 1.00e+00 1.00e+00f 1\n", + " 182r 0.0000000e+00 9.53e+04 2.03e+03 -0.1 2.12e+02 - 1.00e+00 7.99e-01f 1\n", + " 183r 0.0000000e+00 8.64e+04 8.95e+01 -0.1 8.37e+01 - 1.00e+00 1.00e+00f 1\n", + " 184r 0.0000000e+00 8.52e+04 9.30e-01 -0.1 4.13e+01 - 1.00e+00 1.00e+00h 1\n", + " 185r 0.0000000e+00 7.70e+04 7.43e+02 -2.2 2.70e+02 - 8.39e-01 7.19e-01f 1\n", + " 186r 0.0000000e+00 7.27e+04 1.42e+03 -2.2 1.95e+03 - 9.32e-01 6.47e-01f 1\n", + " 187r 0.0000000e+00 7.36e+04 5.69e+02 -2.2 4.90e+02 - 9.15e-01 6.73e-01f 1\n", + " 188r 0.0000000e+00 7.91e+04 2.05e+02 -2.2 2.75e+02 - 1.00e+00 8.69e-01f 1\n", + " 189r 0.0000000e+00 7.92e+04 1.27e+03 -2.2 1.37e+02 - 8.68e-01 1.06e-01f 1\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 190r 0.0000000e+00 7.99e+04 8.09e+01 -2.2 1.45e+02 - 1.00e+00 1.00e+00f 1\n", + " 191r 0.0000000e+00 8.00e+04 1.81e+01 -2.2 2.82e+00 - 1.00e+00 1.00e+00f 1\n", + " 192r 0.0000000e+00 8.00e+04 6.93e-02 -2.2 6.43e-02 - 1.00e+00 1.00e+00h 1\n", + " 193r 0.0000000e+00 8.03e+04 1.79e+02 -3.3 1.75e+01 - 9.50e-01 8.57e-01f 1\n", + " 194r 0.0000000e+00 4.29e+04 3.40e+02 -3.3 8.69e+02 - 1.00e+00 6.02e-01f 1\n", + " 195r 0.0000000e+00 1.36e+04 1.76e+01 -3.3 3.71e+02 - 1.00e+00 1.00e+00f 1\n", + " 196r 0.0000000e+00 1.99e+04 5.38e-01 -3.3 7.89e+01 - 1.00e+00 1.00e+00h 1\n", + " 197r 0.0000000e+00 1.73e+04 9.03e-02 -3.3 3.20e+01 - 1.00e+00 1.00e+00h 1\n", + " 198r 0.0000000e+00 1.64e+04 1.32e-02 -3.3 1.07e+01 - 1.00e+00 1.00e+00h 1\n", + " 199r 0.0000000e+00 1.48e+04 9.83e+01 -4.9 2.40e+01 - 8.62e-01 7.80e-01f 1\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 200r 0.0000000e+00 1.44e+04 1.92e+03 -4.9 1.71e+03 - 6.01e-01 1.17e-02f 1\n", + "\n", + "Number of Iterations....: 200\n", + "\n", + " (scaled) (unscaled)\n", + "Objective...............: 0.0000000000000000e+00 0.0000000000000000e+00\n", + "Dual infeasibility......: 8.8349814638582666e+02 8.8349814638582666e+02\n", + "Constraint violation....: 3.3086142754792485e-04 1.4369018418593043e+04\n", + "Complementarity.........: 6.5630241357471754e-04 6.5630241357471754e-04\n", + "Overall NLP error.......: 3.3086142754792485e-04 1.4369018418593043e+04\n", + "\n", + "\n", + "Number of objective function evaluations = 1605\n", + "Number of objective gradient evaluations = 175\n", + "Number of equality constraint evaluations = 1605\n", + "Number of inequality constraint evaluations = 0\n", + "Number of equality constraint Jacobian evaluations = 202\n", + "Number of inequality constraint Jacobian evaluations = 0\n", + "Number of Lagrangian Hessian evaluations = 200\n", + "Total CPU secs in IPOPT (w/o function evaluations) = 0.331\n", + "Total CPU secs in NLP function evaluations = 0.025\n", + "\n", + "EXIT: Maximum Number of Iterations Exceeded.\n", + "WARNING: Loading a SolverResults object with a warning status into\n", + "model.name=\"unknown\";\n", + " - termination condition: maxIterations\n", + " - message from solver: Ipopt 3.13.2\\x3a Maximum Number of Iterations\n", + " Exceeded.\n" + ] + } + ], + "execution_count": 52 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "Now that the flowsheet is initialized, we can unfix the guesses for the `Heater` and reactive the tear stream to complete the final solve." + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:31.967629Z", + "start_time": "2025-06-11T22:13:31.943574Z" + } + }, + "cell_type": "code", + "source": [ + "for k, v in tear_guesses.items():\n", + " for k1, v1 in v.items():\n", + " getattr(m.fs.H101.inlet, k)[k1].unfix()\n", + "\n", + "m.fs.s03_expanded.activate()\n", + "print(f\"The DOF is {degrees_of_freedom(m)} after unfixing the values and reactivating the tear stream\")" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The DOF is 0 after unfixing the values and reactivating the tear stream\n" + ] + } + ], + "execution_count": 53 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "## 6 Solving the Model" + }, + { + "cell_type": "markdown", + "metadata": { + "tags": [] + }, + "source": "We have now initialized the flowsheet. Lets set up some solving options before simulating the flowsheet. We want to specify the scaling method, number of iterations, and tolerance. More specific or advanced options can be found at the documentation for IPOPT https://coin-or.github.io/Ipopt/OPTIONS.html" + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:31.982093Z", + "start_time": "2025-06-11T22:13:31.978929Z" + } + }, + "cell_type": "code", + "source": [ + "optarg = {\n", + " 'nlp_scaling_method': 'user-scaling',\n", + " 'OF_ma57_automatic_scaling': 'yes',\n", + " 'max_iter': 500,\n", + " 'tol': 1e-8,\n", + "}" + ], + "outputs": [], + "execution_count": 54 + }, + { + "cell_type": "code", + "metadata": { + "tags": [ + "solution" + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:32.320588Z", + "start_time": "2025-06-11T22:13:32.041379Z" + } + }, + "source": [ + "# Create the solver object\n", + "solver = get_solver(solver_options=optarg)\n", + "\n", + "# Solve the model\n", + "results = solver.solve(m, tee=True)\n" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "WARNING: model contains export suffix 'scaling_factor' that contains 30\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "Ipopt 3.13.2: nlp_scaling_method=user-scaling\n", + "tol=1e-08\n", + "max_iter=500\n", + "option_file_name=C:\\Users\\Tanner\\AppData\\Local\\Temp\\tmp1n1inahr_ipopt.opt\n", + "\n", + "Using option file \"C:\\Users\\Tanner\\AppData\\Local\\Temp\\tmp1n1inahr_ipopt.opt\".\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...: 1163\n", + "Number of nonzeros in inequality constraint Jacobian.: 0\n", + "Number of nonzeros in Lagrangian Hessian.............: 1062\n", + "\n", + "Total number of variables............................: 390\n", + " variables with only lower bounds: 0\n", + " variables with lower and upper bounds: 196\n", + " variables with only upper bounds: 0\n", + "Total number of equality constraints.................: 390\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 7.98e+04 0.00e+00 -1.0 0.00e+00 - 0.00e+00 0.00e+00 0\n", + " 1 0.0000000e+00 7.94e+04 1.91e+01 -1.0 1.01e+05 - 4.59e-03 3.67e-03h 3\n", + " 2 0.0000000e+00 7.91e+04 2.90e+01 -1.0 9.74e+04 - 1.04e-02 2.90e-03h 3\n", + " 3 0.0000000e+00 8.53e+04 3.58e+01 -1.0 1.09e+05 - 3.03e-02 2.27e-03h 3\n", + " 4 0.0000000e+00 9.38e+04 9.70e+01 -1.0 1.22e+05 - 2.53e-02 1.76e-03h 3\n", + " 5 0.0000000e+00 9.86e+04 7.07e+02 -1.0 1.32e+05 - 5.90e-02 1.36e-03h 3\n", + " 6 0.0000000e+00 1.01e+05 5.02e+03 -1.0 1.41e+05 - 2.55e-02 1.04e-03h 3\n", + " 7 0.0000000e+00 1.03e+05 1.80e+05 -1.0 1.47e+05 - 1.27e-01 7.95e-04h 3\n", + " 8 0.0000000e+00 1.04e+05 2.00e+06 -1.0 1.52e+05 - 2.40e-02 6.05e-04h 3\n", + " 9 0.0000000e+00 1.04e+05 1.82e+08 -1.0 1.56e+05 - 1.71e-01 4.59e-04h 3\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 10 0.0000000e+00 1.04e+05 3.34e+09 -1.0 1.59e+05 - 2.46e-02 3.47e-04h 3\n", + " 11 0.0000000e+00 3.24e+05 3.06e+09 -1.0 2.71e-02 10.0 5.85e-01 7.83e-01h 1\n", + " 12 0.0000000e+00 1.46e+06 3.17e+11 -1.0 3.46e+04 - 7.72e-02 1.50e-01f 2\n", + " 13 0.0000000e+00 1.46e+06 2.27e+11 -1.0 1.90e+04 - 3.66e-01 2.41e-03h 3\n", + " 14 0.0000000e+00 1.46e+06 2.32e+12 -1.0 1.90e+04 - 4.42e-01 1.83e-03h 3\n", + " 15 0.0000000e+00 1.45e+06 8.61e+13 -1.0 1.89e+04 - 4.59e-01 1.39e-03h 3\n", + " 16 0.0000000e+00 1.45e+06 5.60e+14 -1.0 1.89e+04 - 3.52e-01 2.10e-03h 2\n", + " 17 0.0000000e+00 1.45e+06 5.54e+14 -1.0 1.88e+04 - 5.07e-01 1.07e-03h 2\n", + " 18 0.0000000e+00 1.45e+06 9.90e+14 -1.0 1.88e+04 - 3.35e-01 1.09e-03h 1\n", + " 19 0.0000000e+00 1.45e+06 9.94e+16 -1.0 1.87e+04 - 9.49e-01 1.09e-05h 1\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 20 0.0000000e+00 1.44e+06 9.35e+18 -1.0 4.45e+03 - 2.73e-01 2.89e-03h 1\n", + " 21r 0.0000000e+00 1.44e+06 1.00e+03 -0.6 0.00e+00 - 0.00e+00 4.43e-07R 17\n", + " 22r 0.0000000e+00 1.89e+06 2.04e+03 -0.6 6.64e+02 - 7.96e-03 2.69e-03f 1\n", + " 23r 0.0000000e+00 1.93e+06 4.07e+03 -0.6 8.47e+02 - 1.23e-02 4.48e-03f 1\n", + " 24r 0.0000000e+00 2.28e+06 1.29e+04 -0.6 9.49e+02 - 4.42e-02 1.55e-02f 1\n", + " 25r 0.0000000e+00 4.06e+06 1.16e+04 -0.6 1.05e+03 - 5.19e-02 5.05e-02f 1\n", + " 26r 0.0000000e+00 4.03e+06 6.56e+03 -0.6 8.19e+02 - 2.50e-03 1.07e-02f 1\n", + " 27r 0.0000000e+00 4.15e+06 4.04e+04 -0.6 6.67e+02 - 9.08e-02 3.63e-02f 1\n", + " 28r 0.0000000e+00 4.11e+06 4.55e+04 -0.6 4.90e+02 - 4.20e-02 3.20e-02f 1\n", + " 29r 0.0000000e+00 4.02e+06 1.82e+05 -0.6 4.75e+02 - 2.06e-01 1.68e-02f 1\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 30r 0.0000000e+00 3.02e+06 8.10e+04 -0.6 4.67e+02 - 6.59e-02 2.37e-01f 1\n", + " 31r 0.0000000e+00 2.70e+06 1.53e+05 -0.6 3.56e+02 - 4.76e-01 4.13e-01f 1\n", + " 32r 0.0000000e+00 2.13e+06 9.21e+04 -0.6 2.09e+02 - 4.07e-01 5.13e-01f 1\n", + " 33r 0.0000000e+00 8.87e+05 5.96e+04 -0.6 1.02e+02 - 5.00e-01 5.95e-01f 1\n", + " 34r 0.0000000e+00 1.36e+05 9.02e+04 -0.6 4.12e+01 - 4.56e-01 1.00e+00f 1\n", + " 35r 0.0000000e+00 4.01e+04 7.97e+04 -0.6 4.66e+00 - 5.29e-01 7.78e-01f 1\n", + " 36r 0.0000000e+00 2.04e+04 5.00e+04 -0.6 4.15e-01 0.0 3.36e-01 7.33e-01h 1\n", + " 37r 0.0000000e+00 1.98e+04 1.42e+04 -0.6 8.68e-02 1.3 1.00e+00 1.72e-01f 1\n", + " 38r 0.0000000e+00 1.85e+04 2.28e+04 -0.6 1.70e+01 - 8.27e-01 5.31e-01f 1\n", + " 39r 0.0000000e+00 2.67e+04 6.30e+03 -0.6 1.08e+01 - 1.00e+00 1.00e+00f 1\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 40r 0.0000000e+00 1.76e+04 3.54e+02 -0.6 3.83e+00 - 1.00e+00 1.00e+00f 1\n", + " 41r 0.0000000e+00 1.66e+04 6.18e+02 -0.6 2.10e+00 - 1.00e+00 1.00e+00f 1\n", + " 42r 0.0000000e+00 1.63e+04 1.63e+00 -0.6 7.37e-02 - 1.00e+00 1.00e+00h 1\n", + " 43r 0.0000000e+00 1.82e+04 9.49e+02 -2.0 8.67e+00 - 8.62e-01 7.60e-01f 1\n", + " 44r 0.0000000e+00 2.02e+04 6.57e+02 -2.0 2.22e+03 - 1.00e+00 7.24e-01f 1\n", + " 45r 0.0000000e+00 1.41e+04 2.64e+01 -2.0 1.93e-02 0.9 1.00e+00 1.00e+00f 1\n", + " 46r 0.0000000e+00 1.41e+04 3.99e+00 -2.0 1.76e-02 0.4 1.00e+00 1.00e+00h 1\n", + " 47r 0.0000000e+00 1.41e+04 5.62e-02 -2.0 3.33e-02 -0.1 1.00e+00 1.00e+00h 1\n", + " 48r 0.0000000e+00 1.42e+04 3.60e+02 -4.4 9.99e-02 -0.6 9.21e-01 7.93e-01f 1\n", + " 49r 0.0000000e+00 4.27e+04 2.99e+03 -4.4 1.29e+00 -1.1 8.82e-01 6.65e-01f 1\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 50r 0.0000000e+00 6.63e+04 1.41e+02 -4.4 3.88e+00 -1.5 9.39e-01 9.16e-01f 1\n", + " 51r 0.0000000e+00 5.62e+04 5.64e+02 -4.4 1.16e+01 -2.0 1.00e+00 1.56e-01f 1\n", + " 52r 0.0000000e+00 5.59e+04 9.07e+02 -4.4 4.50e+04 - 7.98e-02 5.51e-03f 1\n", + " 53r 0.0000000e+00 5.59e+04 1.82e+03 -4.4 2.65e+03 - 1.77e-01 2.25e-05f 1\n", + " 54r 0.0000000e+00 4.87e+04 1.59e+03 -4.4 2.65e+03 - 1.12e-01 1.38e-01f 1\n", + " 55r 0.0000000e+00 4.71e+04 2.05e+03 -4.4 2.28e+03 - 5.83e-01 3.36e-02f 1\n", + " 56r 0.0000000e+00 4.59e+04 2.00e+03 -4.4 2.21e+03 - 2.61e-02 2.52e-02f 1\n", + " 57r 0.0000000e+00 4.21e+04 1.20e+03 -4.4 2.15e+03 - 3.82e-01 9.47e-02f 1\n", + " 58r 0.0000000e+00 3.52e+04 1.00e+03 -4.4 1.95e+03 - 1.89e-01 5.87e-01f 1\n", + " 59r 0.0000000e+00 1.42e+04 3.94e+00 -4.4 8.05e+02 - 1.00e+00 1.00e+00f 1\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 60r 0.0000000e+00 1.42e+04 4.68e-01 -4.4 5.35e-01 - 1.00e+00 1.00e+00h 1\n", + " 61r 0.0000000e+00 1.42e+04 1.29e-02 -4.4 3.24e-03 - 1.00e+00 1.00e+00h 1\n", + " 62r 0.0000000e+00 1.42e+04 5.72e-06 -4.4 5.73e-05 - 1.00e+00 1.00e+00h 1\n", + " 63r 0.0000000e+00 1.42e+04 1.24e+02 -6.6 2.71e-01 - 9.90e-01 8.10e-01f 1\n", + " 64r 0.0000000e+00 8.72e+05 1.88e+02 -6.6 3.29e+04 - 5.03e-01 2.23e-01f 1\n", + " 65r 0.0000000e+00 1.05e+06 2.88e+02 -6.6 2.53e+04 - 6.78e-01 1.82e-01f 1\n", + " 66r 0.0000000e+00 1.04e+06 4.92e+02 -6.6 8.71e+03 - 8.61e-01 1.13e-02f 1\n", + " 67r 0.0000000e+00 6.27e+05 7.17e+01 -6.6 8.61e+03 - 1.00e+00 8.73e-01f 1\n", + " 68r 0.0000000e+00 2.18e+04 1.48e+00 -6.6 1.09e+03 - 1.00e+00 1.00e+00h 1\n", + " 69r 0.0000000e+00 4.71e+02 5.81e-05 -6.6 7.59e-02 - 1.00e+00 1.00e+00h 1\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 70r 0.0000000e+00 4.71e+02 1.03e-08 -6.6 1.22e-05 - 1.00e+00 1.00e+00h 1\n", + " 71r 0.0000000e+00 4.71e+02 8.08e-01 -9.0 4.39e-02 - 1.00e+00 9.74e-01f 1\n", + " 72r 0.0000000e+00 2.45e+03 1.39e+02 -9.0 2.20e+05 - 3.58e-01 2.17e-03f 1\n", + " 73r 0.0000000e+00 2.39e+03 5.81e+02 -9.0 1.06e+04 - 6.57e-01 2.41e-02f 1\n", + " 74r 0.0000000e+00 2.15e+04 7.82e+02 -9.0 1.04e+04 - 1.00e+00 1.42e-01f 1\n", + " 75r 0.0000000e+00 1.15e+04 4.72e+02 -9.0 4.34e+01 - 1.00e+00 4.66e-01h 2\n", + " 76r 0.0000000e+00 6.38e+03 2.52e+02 -9.0 4.28e+01 - 1.00e+00 4.46e-01h 2\n", + " 77r 0.0000000e+00 3.26e+03 1.28e+02 -9.0 2.42e+01 - 1.00e+00 4.89e-01h 2\n", + " 78r 0.0000000e+00 2.99e+03 1.10e+02 -9.0 1.24e+01 - 1.00e+00 1.00e+00h 1\n", + " 79r 0.0000000e+00 9.57e+01 3.79e+00 -9.0 5.11e-03 - 1.00e+00 1.00e+00h 1\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 80r 0.0000000e+00 3.70e-02 1.01e-04 -9.0 3.14e-05 - 1.00e+00 1.00e+00h 1\n", + " 81r 0.0000000e+00 4.15e-06 2.24e-09 -9.0 8.12e-09 - 1.00e+00 1.00e+00h 1\n", + "\n", + "Number of Iterations....: 81\n", + "\n", + " (scaled) (unscaled)\n", + "Objective...............: 0.0000000000000000e+00 0.0000000000000000e+00\n", + "Dual infeasibility......: 0.0000000000000000e+00 0.0000000000000000e+00\n", + "Constraint violation....: 2.0579515874459978e-11 4.1499733924865723e-06\n", + "Complementarity.........: 0.0000000000000000e+00 0.0000000000000000e+00\n", + "Overall NLP error.......: 2.0579515874459978e-11 4.1499733924865723e-06\n", + "\n", + "\n", + "Number of objective function evaluations = 162\n", + "Number of objective gradient evaluations = 23\n", + "Number of equality constraint evaluations = 162\n", + "Number of inequality constraint evaluations = 0\n", + "Number of equality constraint Jacobian evaluations = 83\n", + "Number of inequality constraint Jacobian evaluations = 0\n", + "Number of Lagrangian Hessian evaluations = 81\n", + "Total CPU secs in IPOPT (w/o function evaluations) = 0.119\n", + "Total CPU secs in NLP function evaluations = 0.006\n", + "\n", + "EXIT: Optimal Solution Found.\n" + ] + } + ], + "execution_count": 56 + }, + { + "cell_type": "code", + "metadata": { + "tags": [ + "testing" + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:32.344624Z", + "start_time": "2025-06-11T22:13:32.341522Z" + } + }, + "source": [ + "# Check solver solve status\n", + "from pyomo.environ import TerminationCondition\n", + "\n", + "assert results.solver.termination_condition == TerminationCondition.optimal" + ], + "outputs": [], + "execution_count": 57 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 7 Analyze the results\n", + "\n", + "\n" + ] + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "If the IDAES UI package was installed with the `idaes-pse` installation or installed separately, you can run the flowsheet visualizer to see a full diagram of the full process that is generated and displayed on a browser window.\n" + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "Otherwise, we can run the `m.fs.report()` method to see a full summary of the solved flowsheet. It is recomended to adjust the width of the output as much as possible for the cleanest display." + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:33.985892Z", + "start_time": "2025-06-11T22:13:33.889113Z" + } + }, + "cell_type": "code", + "source": "m.fs.report()", + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "====================================================================================\n", + "Flowsheet : fs Time: 0.0\n", + "------------------------------------------------------------------------------------\n", + " Stream Table\n", + " Units s01 s02 s03 s04 s05 s06 s07 s08 s09 s10 s11 s12 \n", + " flow_mol_phase_comp ('Liq', 'benzene') mole / second 1.0000e-05 1.0000e-05 2.0008e-05 1.2993e-07 1.2993e-07 1.0000e-08 0.20460 8.0000e-09 8.0000e-09 1.0000e-08 0.062620 2.0000e-09\n", + " flow_mol_phase_comp ('Liq', 'toluene') mole / second 0.30000 1.0000e-05 0.30001 8.4149e-07 8.4149e-07 1.0000e-08 0.062520 8.0000e-09 8.0000e-09 1.0000e-08 0.032257 2.0000e-09\n", + " flow_mol_phase_comp ('Liq', 'methane') mole / second 1.0000e-05 1.0000e-05 2.0008e-05 1.0000e-12 1.0000e-12 1.0000e-08 2.6712e-07 8.0000e-09 8.0000e-09 1.0000e-08 9.4877e-08 2.0000e-09\n", + " flow_mol_phase_comp ('Liq', 'hydrogen') mole / second 1.0000e-05 1.0000e-05 2.0008e-05 1.0000e-12 1.0000e-12 1.0000e-08 2.6712e-07 8.0000e-09 8.0000e-09 1.0000e-08 9.4877e-08 2.0000e-09\n", + " flow_mol_phase_comp ('Vap', 'benzene') mole / second 1.0000e-05 1.0000e-05 0.11934 0.11936 0.35374 0.14915 1.0000e-08 0.11932 0.11932 0.14198 1.0000e-08 0.029829\n", + " flow_mol_phase_comp ('Vap', 'toluene') mole / second 1.0000e-05 1.0000e-05 0.012508 0.31252 0.078129 0.015610 1.0000e-08 0.012488 0.012488 0.030264 1.0000e-08 0.0031219\n", + " flow_mol_phase_comp ('Vap', 'methane') mole / second 1.0000e-05 0.020000 1.0377 1.0377 1.2721 1.2721 1.0000e-08 1.0177 1.0177 1.8224e-07 1.0000e-08 0.25442\n", + " flow_mol_phase_comp ('Vap', 'hydrogen') mole / second 1.0000e-05 0.30000 0.56258 0.56260 0.32821 0.32821 1.0000e-08 0.26257 0.26257 1.8224e-07 1.0000e-08 0.065642\n", + " temperature kelvin 303.20 303.20 314.09 600.00 771.85 325.00 325.00 325.00 325.00 375.00 375.00 325.00\n", + " pressure pascal 3.5000e+05 3.5000e+05 3.5000e+05 3.5000e+05 3.5000e+05 3.5000e+05 3.5000e+05 3.5000e+05 3.5000e+05 1.5000e+05 1.5000e+05 3.5000e+05\n", + "====================================================================================\n" + ] + } + ], + "execution_count": 59 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "What is the total operating cost?" + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:34.015352Z", + "start_time": "2025-06-11T22:13:34.012518Z" + } + }, + "source": [ + "print(\"operating cost = $\", value(m.fs.operating_cost))" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "operating cost = $ 419122.33874473866\n" + ] + } + ], + "execution_count": 60 + }, + { + "cell_type": "code", + "metadata": { + "tags": [ + "testing" + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:34.166006Z", + "start_time": "2025-06-11T22:13:34.094011Z" + } + }, + "source": [ + "import pytest\n", + "\n", + "assert value(m.fs.operating_cost) == pytest.approx(419122.3387, abs=1e-3)" + ], + "outputs": [], + "execution_count": 61 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": "For this operating cost, what is the amount of benzene we are able to produce and what purity we are able to achieve? We can look at a specific unit models stream table with the same `report()` method." + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:34.197557Z", + "start_time": "2025-06-11T22:13:34.174732Z" + } + }, + "source": [ + "m.fs.F102.report()\n", + "\n", + "print()\n", + "print(\"benzene purity = \", value(m.fs.purity))" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "====================================================================================\n", + "Unit : fs.F102 Time: 0.0\n", + "------------------------------------------------------------------------------------\n", + " Unit Performance\n", + "\n", + " Variables: \n", + "\n", + " Key : Value : Units : Fixed : Bounds\n", + " Heat Duty : 7352.5 : watt : False : (None, None)\n", + " Pressure Change : -2.0000e+05 : pascal : True : (None, None)\n", + "\n", + "------------------------------------------------------------------------------------\n", + " Stream Table\n", + " Units Inlet Vapor Outlet Liquid Outlet\n", + " flow_mol_phase_comp ('Liq', 'benzene') mole / second 0.20460 1.0000e-08 0.062620 \n", + " flow_mol_phase_comp ('Liq', 'toluene') mole / second 0.062520 1.0000e-08 0.032257 \n", + " flow_mol_phase_comp ('Liq', 'methane') mole / second 2.6712e-07 1.0000e-08 9.4877e-08 \n", + " flow_mol_phase_comp ('Liq', 'hydrogen') mole / second 2.6712e-07 1.0000e-08 9.4877e-08 \n", + " flow_mol_phase_comp ('Vap', 'benzene') mole / second 1.0000e-08 0.14198 1.0000e-08 \n", + " flow_mol_phase_comp ('Vap', 'toluene') mole / second 1.0000e-08 0.030264 1.0000e-08 \n", + " flow_mol_phase_comp ('Vap', 'methane') mole / second 1.0000e-08 1.8224e-07 1.0000e-08 \n", + " flow_mol_phase_comp ('Vap', 'hydrogen') mole / second 1.0000e-08 1.8224e-07 1.0000e-08 \n", + " temperature kelvin 325.00 375.00 375.00 \n", + " pressure pascal 3.5000e+05 1.5000e+05 1.5000e+05 \n", + "====================================================================================\n", + "\n", + "benzene purity = 0.8242962943922332\n" + ] + } + ], + "execution_count": 62 + }, + { + "cell_type": "code", + "metadata": { + "tags": [ + "testing" + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:34.213201Z", + "start_time": "2025-06-11T22:13:34.210228Z" + } + }, + "source": [ + "assert value(m.fs.purity) == pytest.approx(0.82429, abs=1e-3)\n", + "assert value(m.fs.F102.heat_duty[0]) == pytest.approx(7352.4828, abs=1e-3)\n", + "assert value(m.fs.F102.vap_outlet.pressure[0]) == pytest.approx(1.5000e05, abs=1e-3)" + ], + "outputs": [], + "execution_count": 63 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": "Next, let's look at how much benzene we are losing with the light gases out of F101. IDAES has tools for creating stream tables based on the `Arcs` and/or `Ports` in a flowsheet. Let us create and print a simple stream table showing the stream leaving the reactor and the vapor stream from F101." + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:34.253529Z", + "start_time": "2025-06-11T22:13:34.238100Z" + } + }, + "source": [ + "from idaes.core.util.tables import (\n", + " create_stream_table_dataframe,\n", + " stream_table_dataframe_to_string,\n", + ")\n", + "\n", + "st = create_stream_table_dataframe({\"Reactor\": m.fs.s05, \"Light Gases\": m.fs.s06})\n", + "print(stream_table_dataframe_to_string(st))" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " Units Reactor Light Gases\n", + "flow_mol_phase_comp ('Liq', 'benzene') mole / second 1.2993e-07 1.0000e-08 \n", + "flow_mol_phase_comp ('Liq', 'toluene') mole / second 8.4149e-07 1.0000e-08 \n", + "flow_mol_phase_comp ('Liq', 'methane') mole / second 1.0000e-12 1.0000e-08 \n", + "flow_mol_phase_comp ('Liq', 'hydrogen') mole / second 1.0000e-12 1.0000e-08 \n", + "flow_mol_phase_comp ('Vap', 'benzene') mole / second 0.35374 0.14915 \n", + "flow_mol_phase_comp ('Vap', 'toluene') mole / second 0.078129 0.015610 \n", + "flow_mol_phase_comp ('Vap', 'methane') mole / second 1.2721 1.2721 \n", + "flow_mol_phase_comp ('Vap', 'hydrogen') mole / second 0.32821 0.32821 \n", + "temperature kelvin 771.85 325.00 \n", + "pressure pascal 3.5000e+05 3.5000e+05 \n" + ] + } + ], + "execution_count": 64 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 8 Optimization\n", + "\n", + "\n", + "We saw from the results above that the total operating cost for the base case was $419,122 per year. We are producing 0.142 mol/s of benzene at a purity of 82\\%. However, we are losing around 42\\% of benzene in F101 vapor outlet stream. \n", + "\n", + "Let us try to minimize this cost such that:\n", + "- we are producing at least 0.15 mol/s of benzene in F102 vapor outlet i.e. our product stream\n", + "- purity of benzene i.e. the mole fraction of benzene in F102 vapor outlet is at least 80%\n", + "- restricting the benzene loss in F101 vapor outlet to less than 20%\n", + "\n", + "For this problem, our decision variables are as follows:\n", + "- H101 outlet temperature\n", + "- R101 cooling duty provided\n", + "- F101 outlet temperature\n", + "- F102 outlet temperature\n", + "- F102 deltaP in the flash tank\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let us declare our objective function for this problem. " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:34.276719Z", + "start_time": "2025-06-11T22:13:34.271416Z" + } + }, + "source": [ + "m.fs.objective = Objective(expr=m.fs.operating_cost)" + ], + "outputs": [], + "execution_count": 65 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, we need to unfix the decision variables as we had solved a square problem (degrees of freedom = 0) until now. " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:34.337438Z", + "start_time": "2025-06-11T22:13:34.332415Z" + } + }, + "source": [ + "m.fs.H101.outlet.temperature.unfix()\n", + "m.fs.R101.heat_duty.unfix()\n", + "m.fs.F101.vap_outlet.temperature.unfix()\n", + "m.fs.F102.vap_outlet.temperature.unfix()" + ], + "outputs": [], + "execution_count": 66 + }, + { + "cell_type": "code", + "metadata": { + "tags": [ + "solution" + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:34.463653Z", + "start_time": "2025-06-11T22:13:34.459960Z" + } + }, + "source": [ + "# Todo: Unfix deltaP for F102\n", + "m.fs.F102.deltaP.unfix()" + ], + "outputs": [], + "execution_count": 68 + }, + { + "cell_type": "code", + "metadata": { + "tags": [ + "testing" + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:34.535666Z", + "start_time": "2025-06-11T22:13:34.502099Z" + } + }, + "source": [ + "assert degrees_of_freedom(m) == 5" + ], + "outputs": [], + "execution_count": 69 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next, we need to set bounds on these decision variables to values shown below:\n", + "\n", + " - H101 outlet temperature [500, 600] K\n", + " - R101 outlet temperature [600, 800] K\n", + " - F101 outlet temperature [298, 450] K\n", + " - F102 outlet temperature [298, 450] K\n", + " - F102 outlet pressure [105000, 110000] Pa\n", + "\n", + "Let us first set the variable bound for the H101 outlet temperature as shown below:" + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:34.589902Z", + "start_time": "2025-06-11T22:13:34.585528Z" + } + }, + "source": [ + "m.fs.H101.outlet.temperature[0].setlb(500)\n", + "m.fs.H101.outlet.temperature[0].setub(600)" + ], + "outputs": [], + "execution_count": 70 + }, + { + "cell_type": "code", + "metadata": { + "tags": [ + "solution" + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:34.713177Z", + "start_time": "2025-06-11T22:13:34.709843Z" + } + }, + "source": [ + "# Todo: Set the bounds for reactor outlet temperature\n", + "m.fs.R101.outlet.temperature[0].setlb(600)\n", + "m.fs.R101.outlet.temperature[0].setub(800)" + ], + "outputs": [], + "execution_count": 72 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let us fix the bounds for the rest of the decision variables. " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:34.763569Z", + "start_time": "2025-06-11T22:13:34.759916Z" + } + }, + "source": [ + "m.fs.F101.vap_outlet.temperature[0].setlb(298.0)\n", + "m.fs.F101.vap_outlet.temperature[0].setub(450.0)\n", + "m.fs.F102.vap_outlet.temperature[0].setlb(298.0)\n", + "m.fs.F102.vap_outlet.temperature[0].setub(450.0)\n", + "m.fs.F102.vap_outlet.pressure[0].setlb(105000)\n", + "m.fs.F102.vap_outlet.pressure[0].setub(110000)" + ], + "outputs": [], + "execution_count": 73 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, the only things left to define are our constraints on overhead loss in F101, product flow rate and purity in F102. Let us first look at defining a constraint for the overhead loss in F101 where we are restricting the benzene leaving the vapor stream to less than 20 \\% of the benzene available in the reactor outlet. " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:34.806470Z", + "start_time": "2025-06-11T22:13:34.801455Z" + } + }, + "source": [ + "m.fs.overhead_loss = Constraint(\n", + " expr=m.fs.F101.vap_outlet.flow_mol_phase_comp[0, \"Vap\", \"benzene\"]\n", + " <= 0.20 * m.fs.R101.outlet.flow_mol_phase_comp[0, \"Vap\", \"benzene\"]\n", + ")" + ], + "outputs": [], + "execution_count": 74 + }, + { + "cell_type": "code", + "metadata": { + "tags": [ + "solution" + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:34.887385Z", + "start_time": "2025-06-11T22:13:34.882862Z" + } + }, + "source": [ + "# Todo: Add minimum product flow constraint\n", + "m.fs.product_flow = Constraint(\n", + " expr=m.fs.F102.vap_outlet.flow_mol_phase_comp[0, \"Vap\", \"benzene\"] >= 0.15\n", + ")" + ], + "outputs": [], + "execution_count": 76 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let us add the final constraint on product purity or the mole fraction of benzene in the product stream such that it is at least greater than 80%. " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:34.917217Z", + "start_time": "2025-06-11T22:13:34.912683Z" + } + }, + "source": [ + "m.fs.product_purity = Constraint(expr=m.fs.purity >= 0.80)" + ], + "outputs": [], + "execution_count": 77 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "We have now defined the optimization problem and we are now ready to solve this problem. \n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:35.209235Z", + "start_time": "2025-06-11T22:13:34.962108Z" + } + }, + "source": [ + "results = solver.solve(m, tee=True)" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "WARNING: model contains export suffix 'scaling_factor' that contains 25\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "Ipopt 3.13.2: nlp_scaling_method=user-scaling\n", + "tol=1e-08\n", + "max_iter=500\n", + "option_file_name=C:\\Users\\Tanner\\AppData\\Local\\Temp\\tmpd6aww4bg_ipopt.opt\n", + "\n", + "Using option file \"C:\\Users\\Tanner\\AppData\\Local\\Temp\\tmpd6aww4bg_ipopt.opt\".\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...: 1191\n", + "Number of nonzeros in inequality constraint Jacobian.: 5\n", + "Number of nonzeros in Lagrangian Hessian.............: 1065\n", + "\n", + "Total number of variables............................: 395\n", + " variables with only lower bounds: 0\n", + " variables with lower and upper bounds: 199\n", + " variables with only upper bounds: 0\n", + "Total number of equality constraints.................: 390\n", + "Total number of inequality constraints...............: 3\n", + " inequality constraints with only lower bounds: 2\n", + " inequality constraints with lower and upper bounds: 0\n", + " inequality constraints with only upper bounds: 1\n", + "\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 0 4.1912234e+05 2.99e+05 6.94e+00 -1.0 0.00e+00 - 0.00e+00 0.00e+00 0\n", + " 1 4.1133558e+05 2.94e+05 6.94e+00 -1.0 6.60e+07 - 2.90e-06 1.05e-05f 1\n", + " 2 3.2484037e+05 1.80e+06 6.94e+00 -1.0 2.33e+09 - 2.95e-07 4.87e-06f 1\n", + " 3 3.0318192e+05 1.92e+06 6.94e+00 -1.0 6.90e+08 - 1.70e-05 4.13e-06f 1\n", + " 4 3.0263181e+05 1.92e+06 2.20e+01 -1.0 1.43e+07 - 8.92e-05 1.73e-05f 1\n", + " 5 3.0137102e+05 1.92e+06 4.38e+01 -1.0 8.74e+06 - 6.63e-05 1.11e-04f 1\n", + " 6 3.0058759e+05 1.92e+06 1.37e+03 -1.0 2.21e+07 - 8.59e-06 2.17e-04f 1\n", + " 7 3.0058113e+05 1.92e+06 7.51e+04 -1.0 3.24e+05 - 2.49e-01 1.77e-04f 1\n", + " 8 3.0317331e+05 1.38e+06 2.08e+05 -1.0 4.00e+04 - 9.62e-01 2.82e-01h 1\n", + " 9 3.0372038e+05 1.26e+06 1.91e+05 -1.0 2.87e+04 - 1.50e-01 8.31e-02h 1\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 10 3.0372988e+05 1.26e+06 9.32e+05 -1.0 2.63e+04 - 1.00e+00 1.51e-03h 1\n", + " 11 3.0386427e+05 1.24e+06 1.90e+07 -1.0 2.63e+04 - 1.00e+00 2.03e-02h 2\n", + " 12 3.0393928e+05 1.22e+06 7.46e+08 -1.0 2.58e+04 - 1.00e+00 1.15e-02h 2\n", + " 13 3.0397909e+05 1.21e+06 4.87e+10 -1.0 2.55e+04 - 1.00e+00 6.13e-03h 2\n", + " 14 3.0399961e+05 1.21e+06 5.05e+12 -1.0 2.53e+04 - 1.00e+00 3.17e-03h 2\n", + " 15r 3.0399961e+05 1.21e+06 1.00e+03 -0.5 0.00e+00 - 0.00e+00 3.97e-07R 14\n", + " 16r 3.0399912e+05 1.20e+06 2.84e+04 -0.5 8.47e+03 - 4.43e-03 2.34e-03f 1\n", + " 17 3.0399884e+05 1.20e+06 5.51e+05 -1.0 2.92e+04 - 2.81e-01 2.34e-06f 2\n", + " 18 3.0402874e+05 1.19e+06 4.77e+07 -1.0 2.52e+04 - 9.90e-01 4.64e-03h 1\n", + " 19 3.0402891e+05 1.19e+06 3.49e+10 -1.0 2.51e+04 - 1.00e+00 2.64e-05h 2\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 20r 3.0402891e+05 1.19e+06 1.00e+03 -0.8 0.00e+00 - 0.00e+00 3.08e-07R 8\n", + " 21r 3.0403760e+05 1.15e+06 2.58e+04 -0.8 3.66e+03 - 5.75e-03 3.60e-03f 1\n", + " 22r 3.0404498e+05 1.08e+06 3.87e+04 -0.8 3.65e+03 - 7.70e-03 6.44e-03f 1\n", + " 23r 3.0405749e+05 9.30e+05 4.54e+04 -0.8 3.63e+03 - 1.80e-02 1.60e-02f 1\n", + " 24r 3.0406463e+05 7.06e+05 4.01e+04 -0.8 3.57e+03 - 5.64e-02 3.23e-02f 1\n", + " 25r 3.0405635e+05 4.00e+05 4.80e+04 -0.8 3.45e+03 - 1.05e-01 8.20e-02f 1\n", + " 26r 3.0403684e+05 2.04e+05 2.80e+04 -0.8 3.17e+03 - 3.66e-01 1.49e-01f 1\n", + " 27r 3.0401302e+05 2.95e+04 2.21e+04 -0.8 2.70e+03 - 1.94e-01 6.28e-01f 1\n", + " 28 3.0386768e+05 2.95e+04 1.26e+02 -1.0 3.39e+06 - 4.53e-04 3.54e-06f 1\n", + " 29 3.0384876e+05 2.95e+04 8.45e+03 -1.0 1.90e+05 - 3.01e-02 3.65e-04f 1\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 30 3.0412267e+05 2.86e+04 2.69e+05 -1.0 2.35e+04 - 9.90e-01 3.11e-02h 1\n", + " 31 3.0843430e+05 5.11e+05 1.73e+05 -1.0 2.16e+04 - 1.00e+00 4.99e-01h 1\n", + " 32 3.1273887e+05 6.10e+05 5.58e+08 -1.0 1.09e+04 - 1.00e+00 9.90e-01h 1\n", + " 33 3.1276263e+05 3.11e+05 1.78e+08 -1.0 1.01e+03 - 1.00e+00 4.97e-01h 2\n", + " 34 3.1278673e+05 7.74e+02 1.15e+08 -1.0 6.27e+02 - 1.00e+00 9.97e-01H 1\n", + " 35 3.1278674e+05 6.28e+02 5.45e+04 -1.0 1.81e+02 - 1.00e+00 1.00e+00h 1\n", + " 36 3.1278674e+05 8.80e-03 2.62e+00 -1.0 7.71e-01 - 1.00e+00 1.00e+00h 1\n", + " 37 3.1278634e+05 3.48e-05 1.58e+05 -5.7 1.36e+00 - 1.00e+00 1.00e+00f 1\n", + " 38 3.1278634e+05 3.73e-08 6.73e-02 -5.7 2.20e-04 - 1.00e+00 1.00e+00f 1\n", + " 39 3.1278634e+05 2.24e-08 4.40e-05 -5.7 8.48e-04 - 1.00e+00 1.00e+00h 1\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 40 3.1278634e+05 6.71e-08 6.16e-05 -8.6 1.75e-03 - 1.00e+00 1.00e+00h 1\n", + " 41 3.1278634e+05 3.73e-08 4.40e-05 -9.0 8.38e-04 - 1.00e+00 1.00e+00h 1\n", + " 42 3.1278634e+05 5.96e-08 4.40e-05 -9.0 1.47e-03 - 1.00e+00 1.00e+00h 1\n", + " 43 3.1278634e+05 1.49e-08 4.40e-05 -9.0 5.14e-08 - 1.00e+00 1.00e+00h 1\n", + " 44 3.1278634e+05 1.49e-08 4.40e-05 -9.0 7.39e-11 - 1.00e+00 2.44e-04h 13\n", + " 45 3.1278634e+05 1.49e-08 4.40e-05 -9.0 6.89e-11 - 1.00e+00 5.00e-01h 2\n", + " 46 3.1278634e+05 1.49e-08 4.40e-05 -9.0 7.02e-11 - 1.00e+00 1.00e+00h 1\n", + " 47 3.1278634e+05 1.49e-08 4.40e-05 -9.0 6.71e-11 - 1.00e+00 5.00e-01h 2\n", + " 48 3.1278634e+05 2.98e-08 4.40e-05 -9.0 5.67e-11 - 1.00e+00 1.00e+00h 1\n", + " 49 3.1278634e+05 2.98e-08 4.40e-05 -9.0 5.52e-11 - 1.00e+00 5.00e-01h 2\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 50 3.1278634e+05 2.98e-08 4.40e-05 -9.0 4.16e-11 - 1.00e+00 5.00e-01h 2\n", + " 51 3.1278634e+05 2.98e-08 4.40e-05 -9.0 3.65e-11 - 1.00e+00 1.25e-01h 4\n", + "\n", + "Number of Iterations....: 51\n", + "\n", + " (scaled) (unscaled)\n", + "Objective...............: 3.1278633834066673e+05 3.1278633834066673e+05\n", + "Dual infeasibility......: 4.3988857412862944e-05 6.3035118071624454e-05\n", + "Constraint violation....: 2.0579515874459978e-11 2.9802322387695312e-08\n", + "Complementarity.........: 9.2191617461622074e-10 9.2191617461622074e-10\n", + "Overall NLP error.......: 1.6340396115140405e-08 6.3035118071624454e-05\n", + "\n", + "\n", + "Number of objective function evaluations = 141\n", + "Number of objective gradient evaluations = 47\n", + "Number of equality constraint evaluations = 141\n", + "Number of inequality constraint evaluations = 141\n", + "Number of equality constraint Jacobian evaluations = 55\n", + "Number of inequality constraint Jacobian evaluations = 55\n", + "Number of Lagrangian Hessian evaluations = 52\n", + "Total CPU secs in IPOPT (w/o function evaluations) = 0.077\n", + "Total CPU secs in NLP function evaluations = 0.010\n", + "\n", + "EXIT: Solved To Acceptable Level.\n" + ] + } + ], + "execution_count": 78 + }, + { + "cell_type": "code", + "metadata": { + "tags": [ + "testing" + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:35.223076Z", + "start_time": "2025-06-11T22:13:35.219792Z" + } + }, + "source": [ + "# Check for solver solve status\n", + "from pyomo.environ import TerminationCondition\n", + "\n", + "assert results.solver.termination_condition == TerminationCondition.optimal" + ], + "outputs": [], + "execution_count": 79 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 8.1 Optimization Results\n", + "\n", + "Display the results and product specifications" + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:35.321235Z", + "start_time": "2025-06-11T22:13:35.245545Z" + } + }, + "source": [ + "print(\"operating cost = $\", value(m.fs.operating_cost))\n", + "\n", + "print()\n", + "print(\"Product flow rate and purity in F102\")\n", + "\n", + "m.fs.F102.report()\n", + "\n", + "print()\n", + "print(\"benzene purity = \", value(m.fs.purity))\n", + "\n", + "print()\n", + "print(\"Overhead loss in F101\")\n", + "m.fs.F101.report()" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "operating cost = $ 312786.33834066667\n", + "\n", + "Product flow rate and purity in F102\n", + "\n", + "====================================================================================\n", + "Unit : fs.F102 Time: 0.0\n", + "------------------------------------------------------------------------------------\n", + " Unit Performance\n", + "\n", + " Variables: \n", + "\n", + " Key : Value : Units : Fixed : Bounds\n", + " Heat Duty : 8377.0 : watt : False : (None, None)\n", + " Pressure Change : -2.4500e+05 : pascal : False : (None, None)\n", + "\n", + "------------------------------------------------------------------------------------\n", + " Stream Table\n", + " Units Inlet Vapor Outlet Liquid Outlet\n", + " flow_mol_phase_comp ('Liq', 'benzene') mole / second 0.21743 1.0000e-08 0.067425 \n", + " flow_mol_phase_comp ('Liq', 'toluene') mole / second 0.070695 1.0000e-08 0.037507 \n", + " flow_mol_phase_comp ('Liq', 'methane') mole / second 2.8812e-07 1.0000e-08 1.0493e-07 \n", + " flow_mol_phase_comp ('Liq', 'hydrogen') mole / second 2.8812e-07 1.0000e-08 1.0493e-07 \n", + " flow_mol_phase_comp ('Vap', 'benzene') mole / second 1.0000e-08 0.15000 1.0000e-08 \n", + " flow_mol_phase_comp ('Vap', 'toluene') mole / second 1.0000e-08 0.033189 1.0000e-08 \n", + " flow_mol_phase_comp ('Vap', 'methane') mole / second 1.0000e-08 1.9319e-07 1.0000e-08 \n", + " flow_mol_phase_comp ('Vap', 'hydrogen') mole / second 1.0000e-08 1.9319e-07 1.0000e-08 \n", + " temperature kelvin 301.88 362.93 362.93 \n", + " pressure pascal 3.5000e+05 1.0500e+05 1.0500e+05 \n", + "====================================================================================\n", + "\n", + "benzene purity = 0.8188276578115882\n", + "\n", + "Overhead loss in F101\n", + "\n", + "====================================================================================\n", + "Unit : fs.F101 Time: 0.0\n", + "------------------------------------------------------------------------------------\n", + " Unit Performance\n", + "\n", + " Variables: \n", + "\n", + " Key : Value : Units : Fixed : Bounds\n", + " Heat Duty : -56353. : watt : False : (None, None)\n", + " Pressure Change : 0.0000 : pascal : True : (None, None)\n", + "\n", + "------------------------------------------------------------------------------------\n", + " Stream Table\n", + " Units Inlet Vapor Outlet Liquid Outlet\n", + " flow_mol_phase_comp ('Liq', 'benzene') mole / second 4.3534e-08 1.0000e-08 0.21743 \n", + " flow_mol_phase_comp ('Liq', 'toluene') mole / second 7.5866e-07 1.0000e-08 0.070695 \n", + " flow_mol_phase_comp ('Liq', 'methane') mole / second 1.0000e-12 1.0000e-08 2.8812e-07 \n", + " flow_mol_phase_comp ('Liq', 'hydrogen') mole / second 1.0000e-12 1.0000e-08 2.8812e-07 \n", + " flow_mol_phase_comp ('Vap', 'benzene') mole / second 0.27178 0.054356 1.0000e-08 \n", + " flow_mol_phase_comp ('Vap', 'toluene') mole / second 0.076085 0.0053908 1.0000e-08 \n", + " flow_mol_phase_comp ('Vap', 'methane') mole / second 1.2414 1.2414 1.0000e-08 \n", + " flow_mol_phase_comp ('Vap', 'hydrogen') mole / second 0.35887 0.35887 1.0000e-08 \n", + " temperature kelvin 696.11 301.88 301.88 \n", + " pressure pascal 3.5000e+05 3.5000e+05 3.5000e+05 \n", + "====================================================================================\n" + ] + } + ], + "execution_count": 80 + }, + { + "cell_type": "code", + "metadata": { + "tags": [ + "testing" + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:35.348144Z", + "start_time": "2025-06-11T22:13:35.343061Z" + } + }, + "source": [ + "assert value(m.fs.operating_cost) == pytest.approx(312786.338, abs=1e-3)\n", + "assert value(m.fs.purity) == pytest.approx(0.818827, abs=1e-3)" + ], + "outputs": [], + "execution_count": 81 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Display optimal values for the decision variables" + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:35.405538Z", + "start_time": "2025-06-11T22:13:35.398221Z" + } + }, + "source": [ + "print(f'''Optimal Values:\n", + "\n", + "H101 outlet temperature = {value(m.fs.H101.outlet.temperature[0]):.3f} K\n", + "\n", + "R101 outlet temperature = {value(m.fs.R101.outlet.temperature[0]):.3f} K\n", + "\n", + "F101 outlet temperature = {value(m.fs.F101.vap_outlet.temperature[0]):.3f} K\n", + "\n", + "F102 outlet temperature = {value(m.fs.F102.vap_outlet.temperature[0]):.3f} K\n", + "F102 outlet pressure = {value(m.fs.F102.vap_outlet.pressure[0]):.3f} Pa\n", + "''')" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Optimal Values:\n", + "\n", + "H101 outlet temperature = 500.000 K\n", + "\n", + "R101 outlet temperature = 696.112 K\n", + "\n", + "F101 outlet temperature = 301.878 K\n", + "\n", + "F102 outlet temperature = 362.935 K\n", + "F102 outlet pressure = 105000.000 Pa\n", + "\n" + ] + } + ], + "execution_count": 82 + }, + { + "cell_type": "code", + "metadata": { + "tags": [ + "testing" + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:35.448075Z", + "start_time": "2025-06-11T22:13:35.443091Z" + } + }, + "source": [ + "assert value(m.fs.H101.outlet.temperature[0]) == pytest.approx(500, abs=1e-3)\n", + "assert value(m.fs.R101.outlet.temperature[0]) == pytest.approx(696.112, abs=1e-3)\n", + "assert value(m.fs.F101.vap_outlet.temperature[0]) == pytest.approx(301.878, abs=1e-3)\n", + "assert value(m.fs.F102.vap_outlet.temperature[0]) == pytest.approx(362.935, abs=1e-3)\n", + "assert value(m.fs.F102.vap_outlet.pressure[0]) == pytest.approx(105000, abs=1e-2)" + ], + "outputs": [], + "execution_count": 83 + } + ], + "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.8.12" + } + }, + "nbformat": 4, + "nbformat_minor": 3 } \ No newline at end of file diff --git a/idaes_examples/notebooks/docs/tut/core/hda_flowsheet_usr.ipynb b/idaes_examples/notebooks/docs/tut/core/hda_flowsheet_usr.ipynb index 6f8b47f4..7e8b3ff6 100644 --- a/idaes_examples/notebooks/docs/tut/core/hda_flowsheet_usr.ipynb +++ b/idaes_examples/notebooks/docs/tut/core/hda_flowsheet_usr.ipynb @@ -1,1483 +1,3043 @@ { - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "header", - "hide-cell" - ] - }, - "outputs": [], - "source": [ - "###############################################################################\n", - "# The Institute for the Design of Advanced Energy Systems Integrated Platform\n", - "# Framework (IDAES IP) was produced under the DOE Institute for the\n", - "# Design of Advanced Energy Systems (IDAES).\n", - "#\n", - "# Copyright (c) 2018-2023 by the software owners: The Regents of the\n", - "# University of California, through Lawrence Berkeley National Laboratory,\n", - "# National Technology & Engineering Solutions of Sandia, LLC, Carnegie Mellon\n", - "# University, West Virginia University Research Corporation, et al.\n", - "# All rights reserved. Please see the files COPYRIGHT.md and LICENSE.md\n", - "# for full copyright and license information.\n", - "###############################################################################" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "# HDA Flowsheet Simulation and Optimization\n", - "\n", - "Author: Jaffer Ghouse \n", - "Maintainer: Brandon Paul \n", - "Updated: 2023-06-01 \n", - "\n", - "## Learning outcomes\n", - "\n", - "\n", - "- Construct a steady-state flowsheet using the IDAES unit model library\n", - "- Connecting unit models in a flowsheet using Arcs\n", - "- Using the SequentialDecomposition tool to initialize a flowsheet with recycle\n", - "- Fomulate and solve an optimization problem\n", - " - Defining an objective function\n", - " - Setting variable bounds\n", - " - Adding additional constraints \n", - "\n", - "\n", - "## Problem Statement\n", - "\n", - "Hydrodealkylation is a chemical reaction that often involves reacting\n", - "an aromatic hydrocarbon in the presence of hydrogen gas to form a\n", - "simpler aromatic hydrocarbon devoid of functional groups. In this\n", - "example, toluene will be reacted with hydrogen gas at high temperatures\n", - " to form benzene via the following reaction:\n", - "\n", - "**C6H5CH3 + H2 → C6H6 + CH4**\n", - "\n", - "\n", - "This reaction is often accompanied by an equilibrium side reaction\n", - "which forms diphenyl, which we will neglect for this example.\n", - "\n", - "This example is based on the 1967 AIChE Student Contest problem as\n", - "present by Douglas, J.M., Chemical Design of Chemical Processes, 1988,\n", - "McGraw-Hill.\n", - "\n", - "The flowsheet that we will be using for this module is shown below with the stream conditions. We will be processing toluene and hydrogen to produce at least 370 TPY of benzene. As shown in the flowsheet, there are two flash tanks, F101 to separate out the non-condensibles and F102 to further separate the benzene-toluene mixture to improve the benzene purity. Note that typically a distillation column is required to obtain high purity benzene but that is beyond the scope of this workshop. The non-condensibles separated out in F101 will be partially recycled back to M101 and the rest will be either purged or combusted for power generation.We will assume ideal gas for this flowsheet. The properties required for this module are available in the same directory:\n", - "\n", - "- hda_ideal_VLE.py\n", - "- hda_reaction.py\n", - "\n", - "The state variables chosen for the property package are **flows of component by phase, temperature and pressure**. The components considered are: **toluene, hydrogen, benzene and methane**. Therefore, every stream has 8 flow variables, 1 temperature and 1 pressure variable. \n", - "\n", - "![](HDA_flowsheet.png)\n", - "\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Importing required pyomo and idaes components\n", - "\n", - "\n", - "To construct a flowsheet, we will need several components from the pyomo and idaes package. Let us first import the following components from Pyomo:\n", - "- Constraint (to write constraints)\n", - "- Var (to declare variables)\n", - "- ConcreteModel (to create the concrete model object)\n", - "- Expression (to evaluate values as a function of variables defined in the model)\n", - "- Objective (to define an objective function for optimization)\n", - "- SolverFactory (to solve the problem)\n", - "- TransformationFactory (to apply certain transformations)\n", - "- Arc (to connect two unit models)\n", - "- SequentialDecomposition (to initialize the flowsheet in a sequential mode)\n", - "\n", - "For further details on these components, please refer to the pyomo documentation: https://pyomo.readthedocs.io/en/stable/\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from pyomo.environ import (\n", - " Constraint,\n", - " Var,\n", - " ConcreteModel,\n", - " Expression,\n", - " Objective,\n", - " SolverFactory,\n", - " TransformationFactory,\n", - " value,\n", - ")\n", - "from pyomo.network import Arc, SequentialDecomposition" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "From idaes, we will be needing the FlowsheetBlock and the following unit models:\n", - "- Mixer\n", - "- Heater\n", - "- StoichiometricReactor\n", - "- **Flash**\n", - "- Separator (splitter) \n", - "- PressureChanger" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from idaes.core import FlowsheetBlock" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from idaes.models.unit_models import (\n", - " PressureChanger,\n", - " Mixer,\n", - " Separator as Splitter,\n", - " Heater,\n", - " StoichiometricReactor,\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "
\n", - "Inline Exercise:\n", - "Now, import the remaining unit models highlighted in blue above and run the cell using `Shift+Enter` after typing in the code. \n", - "
\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "exercise" - ] - }, - "outputs": [], - "source": [ - "# Todo: import flash model from idaes.models.unit_models" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "solution" - ] - }, - "outputs": [], - "source": [ - "# Todo: import flash model from idaes.models.unit_models\n", - "from idaes.models.unit_models import Flash" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We will also be needing some utility tools to put together the flowsheet and calculate the degrees of freedom. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from idaes.models.unit_models.pressure_changer import ThermodynamicAssumption\n", - "from idaes.core.util.model_statistics import degrees_of_freedom\n", - "\n", - "# Import idaes logger to set output levels\n", - "import idaes.logger as idaeslog\n", - "from idaes.core.solvers import get_solver\n", - "from idaes.core.util.exceptions import InitializationError" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Importing required thermo and reaction package\n", - "\n", - "The final set of imports are to import the thermo and reaction package for the HDA process. We have created a custom thermo package that assumes Ideal Gas with support for VLE. \n", - "\n", - "The reaction package here is very simple as we will be using only a StochiometricReactor and the reaction package consists of the stochiometric coefficients for the reaction and the parameter for the heat of reaction. \n", - "\n", - "Let us import the following modules and they are in the same directory as this jupyter notebook:\n", - "
    \n", - "
  • hda_ideal_VLE as thermo_props
  • \n", - "
  • hda_reaction as reaction_props
  • \n", - "
\n", - "
" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from idaes_examples.mod.hda import hda_ideal_VLE as thermo_props\n", - "from idaes_examples.mod.hda import hda_reaction as reaction_props" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Constructing the Flowsheet\n", - "\n", - "We have now imported all the components, unit models, and property modules we need to construct a flowsheet. Let us create a ConcreteModel and add the flowsheet block as we did in module 1. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m = ConcreteModel()\n", - "m.fs = FlowsheetBlock(dynamic=False)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We now need to add the property packages to the flowsheet. Unlike Module 1, where we only had a thermo property package, for this flowsheet we will also need to add a reaction property package. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.thermo_params = thermo_props.HDAParameterBlock()\n", - "m.fs.reaction_params = reaction_props.HDAReactionParameterBlock(\n", - " property_package=m.fs.thermo_params\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Adding Unit Models\n", - "\n", - "Let us start adding the unit models we have imported to the flowsheet. Here, we are adding the Mixer (assigned a name M101) and a Heater (assigned a name H101). Note that, all unit models need to be given a property package argument. In addition to that, there are several arguments depending on the unit model, please refer to the documentation for more details (https://idaes-pse.readthedocs.io/en/stable/reference_guides/model_libraries/generic/unit_models/index.html). For example, the Mixer unit model here is given a `list` consisting of names to the three inlets. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.M101 = Mixer(\n", - " property_package=m.fs.thermo_params,\n", - " inlet_list=[\"toluene_feed\", \"hydrogen_feed\", \"vapor_recycle\"],\n", - ")\n", - "\n", - "m.fs.H101 = Heater(\n", - " property_package=m.fs.thermo_params,\n", - " has_pressure_change=False,\n", - " has_phase_equilibrium=True,\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "
\n", - "Inline Exercise:\n", - "Let us now add the StoichiometricReactor(assign the name R101) and pass the following arguments:\n", - "
    \n", - "
  • \"property_package\": m.fs.thermo_params
  • \n", - "
  • \"reaction_package\": m.fs.reaction_params
  • \n", - "
  • \"has_heat_of_reaction\": True
  • \n", - "
  • \"has_heat_transfer\": True
  • \n", - "
  • \"has_pressure_change\": False
  • \n", - "
\n", - "
" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "exercise" - ] - }, - "outputs": [], - "source": [ - "# Todo: Add reactor with the specifications above" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "solution" - ] - }, - "outputs": [], - "source": [ - "# Todo: Add reactor with the specifications above\n", - "m.fs.R101 = StoichiometricReactor(\n", - " property_package=m.fs.thermo_params,\n", - " reaction_package=m.fs.reaction_params,\n", - " has_heat_of_reaction=True,\n", - " has_heat_transfer=True,\n", - " has_pressure_change=False,\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let us now add the Flash(assign the name F101) and pass the following arguments:\n", - "
    \n", - "
  • \"property_package\": m.fs.thermo_params
  • \n", - "
  • \"has_heat_transfer\": True
  • \n", - "
  • \"has_pressure_change\": False
  • \n", - "
" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.F101 = Flash(\n", - " property_package=m.fs.thermo_params,\n", - " has_heat_transfer=True,\n", - " has_pressure_change=True,\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let us now add the Splitter(S101), PressureChanger(C101) and the second Flash(F102). " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.S101 = Splitter(\n", - " property_package=m.fs.thermo_params,\n", - " ideal_separation=False,\n", - " outlet_list=[\"purge\", \"recycle\"],\n", - ")\n", - "\n", - "\n", - "m.fs.C101 = PressureChanger(\n", - " property_package=m.fs.thermo_params,\n", - " compressor=True,\n", - " thermodynamic_assumption=ThermodynamicAssumption.isothermal,\n", - ")\n", - "\n", - "m.fs.F102 = Flash(\n", - " property_package=m.fs.thermo_params,\n", - " has_heat_transfer=True,\n", - " has_pressure_change=True,\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Connecting Unit Models using Arcs\n", - "\n", - "We have now added all the unit models we need to the flowsheet. However, we have not yet specified how the units are to be connected. To do this, we will be using the `Arc` which is a pyomo component that takes in two arguments: `source` and `destination`. Let us connect the outlet of the mixer(M101) to the inlet of the heater(H101). " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.s03 = Arc(source=m.fs.M101.outlet, destination=m.fs.H101.inlet)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "![](HDA_flowsheet.png) \n", - "\n", - "
\n", - "Inline Exercise:\n", - "Now, connect the H101 outlet to the R101 inlet using the cell above as a guide. \n", - "
\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "exercise" - ] - }, - "outputs": [], - "source": [ - "# Todo: Connect the H101 outlet to R101 inlet" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "solution" - ] - }, - "outputs": [], - "source": [ - "# Todo: Connect the H101 outlet to R101 inlet\n", - "m.fs.s04 = Arc(source=m.fs.H101.outlet, destination=m.fs.R101.inlet)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We will now be connecting the rest of the flowsheet as shown below. Notice how the outlet names are different for the flash tanks F101 and F102 as they have a vapor and a liquid outlet. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.s05 = Arc(source=m.fs.R101.outlet, destination=m.fs.F101.inlet)\n", - "m.fs.s06 = Arc(source=m.fs.F101.vap_outlet, destination=m.fs.S101.inlet)\n", - "m.fs.s08 = Arc(source=m.fs.S101.recycle, destination=m.fs.C101.inlet)\n", - "m.fs.s09 = Arc(source=m.fs.C101.outlet, destination=m.fs.M101.vapor_recycle)\n", - "m.fs.s10 = Arc(source=m.fs.F101.liq_outlet, destination=m.fs.F102.inlet)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We have now connected the unit model block using the arcs. However, each of these arcs link to ports on the two unit models that are connected. In this case, the ports consist of the state variables that need to be linked between the unit models. Pyomo provides a convenient method to write these equality constraints for us between two ports and this is done as follows:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "TransformationFactory(\"network.expand_arcs\").apply_to(m)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Adding expressions to compute purity and operating costs\n", - "\n", - "In this section, we will add a few Expressions that allows us to evaluate the performance. Expressions provide a convenient way of calculating certain values that are a function of the variables defined in the model. For more details on Expressions, please refer to: https://pyomo.readthedocs.io/en/stable/pyomo_modeling_components/Expressions.html\n", - "\n", - "For this flowsheet, we are interested in computing the purity of the product Benzene stream (i.e. the mole fraction) and the operating cost which is a sum of the cooling and heating cost. " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let us first add an Expression to compute the mole fraction of benzene in the `vap_outlet` of F102 which is our product stream. Please note that the var flow_mol_phase_comp has the index - [time, phase, component]. As this is a steady-state flowsheet, the time index by default is 0. The valid phases are [\"Liq\", \"Vap\"]. Similarly the valid component list is [\"benzene\", \"toluene\", \"hydrogen\", \"methane\"]." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.purity = Expression(\n", - " expr=m.fs.F102.vap_outlet.flow_mol_phase_comp[0, \"Vap\", \"benzene\"]\n", - " / (\n", - " m.fs.F102.vap_outlet.flow_mol_phase_comp[0, \"Vap\", \"benzene\"]\n", - " + m.fs.F102.vap_outlet.flow_mol_phase_comp[0, \"Vap\", \"toluene\"]\n", - " )\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now, let us add an expression to compute the cooling cost assuming a cost of 0.212E-4 $/kW. Note that cooling utility is required for the reactor (R101) and the first flash (F101). " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.cooling_cost = Expression(\n", - " expr=0.212e-7 * (-m.fs.F101.heat_duty[0]) + 0.212e-7 * (-m.fs.R101.heat_duty[0])\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "Now, let us add an expression to compute the heating cost assuming the utility cost as follows:\n", - "
    \n", - "
  • 2.2E-4 dollars/kW for H101
  • \n", - "
  • 1.9E-4 dollars/kW for F102
  • \n", - "
\n", - "Note that the heat duty is in units of watt (J/s). " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.heating_cost = Expression(\n", - " expr=2.2e-7 * m.fs.H101.heat_duty[0] + 1.9e-7 * m.fs.F102.heat_duty[0]\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let us now add an expression to compute the total operating cost per year which is basically the sum of the cooling and heating cost we defined above. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.operating_cost = Expression(\n", - " expr=(3600 * 24 * 365 * (m.fs.heating_cost + m.fs.cooling_cost))\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Fixing feed conditions\n", - "\n", - "Let us first check how many degrees of freedom exist for this flowsheet using the `degrees_of_freedom` tool we imported earlier. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "print(degrees_of_freedom(m))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We will now be fixing the toluene feed stream to the conditions shown in the flowsheet above. Please note that though this is a pure toluene feed, the remaining components are still assigned a very small non-zero value to help with convergence and initializing. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.M101.toluene_feed.flow_mol_phase_comp[0, \"Vap\", \"benzene\"].fix(1e-5)\n", - "m.fs.M101.toluene_feed.flow_mol_phase_comp[0, \"Vap\", \"toluene\"].fix(1e-5)\n", - "m.fs.M101.toluene_feed.flow_mol_phase_comp[0, \"Vap\", \"hydrogen\"].fix(1e-5)\n", - "m.fs.M101.toluene_feed.flow_mol_phase_comp[0, \"Vap\", \"methane\"].fix(1e-5)\n", - "m.fs.M101.toluene_feed.flow_mol_phase_comp[0, \"Liq\", \"benzene\"].fix(1e-5)\n", - "m.fs.M101.toluene_feed.flow_mol_phase_comp[0, \"Liq\", \"toluene\"].fix(0.30)\n", - "m.fs.M101.toluene_feed.flow_mol_phase_comp[0, \"Liq\", \"hydrogen\"].fix(1e-5)\n", - "m.fs.M101.toluene_feed.flow_mol_phase_comp[0, \"Liq\", \"methane\"].fix(1e-5)\n", - "m.fs.M101.toluene_feed.temperature.fix(303.2)\n", - "m.fs.M101.toluene_feed.pressure.fix(350000)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "Similarly, let us fix the hydrogen feed to the following conditions in the next cell:\n", - "
    \n", - "
  • FH2 = 0.30 mol/s
  • \n", - "
  • FCH4 = 0.02 mol/s
  • \n", - "
  • Remaining components = 1e-5 mol/s
  • \n", - "
  • T = 303.2 K
  • \n", - "
  • P = 350000 Pa
  • \n", - "
\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.M101.hydrogen_feed.flow_mol_phase_comp[0, \"Vap\", \"benzene\"].fix(1e-5)\n", - "m.fs.M101.hydrogen_feed.flow_mol_phase_comp[0, \"Vap\", \"toluene\"].fix(1e-5)\n", - "m.fs.M101.hydrogen_feed.flow_mol_phase_comp[0, \"Vap\", \"hydrogen\"].fix(0.30)\n", - "m.fs.M101.hydrogen_feed.flow_mol_phase_comp[0, \"Vap\", \"methane\"].fix(0.02)\n", - "m.fs.M101.hydrogen_feed.flow_mol_phase_comp[0, \"Liq\", \"benzene\"].fix(1e-5)\n", - "m.fs.M101.hydrogen_feed.flow_mol_phase_comp[0, \"Liq\", \"toluene\"].fix(1e-5)\n", - "m.fs.M101.hydrogen_feed.flow_mol_phase_comp[0, \"Liq\", \"hydrogen\"].fix(1e-5)\n", - "m.fs.M101.hydrogen_feed.flow_mol_phase_comp[0, \"Liq\", \"methane\"].fix(1e-5)\n", - "m.fs.M101.hydrogen_feed.temperature.fix(303.2)\n", - "m.fs.M101.hydrogen_feed.pressure.fix(350000)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Fixing unit model specifications\n", - "\n", - "Now that we have fixed our inlet feed conditions, we will now be fixing the operating conditions for the unit models in the flowsheet. Let us set set the H101 outlet temperature to 600 K. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.H101.outlet.temperature.fix(600)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "For the StoichiometricReactor, we have to define the conversion in terms of toluene. This requires us to create a new variable for specifying the conversion and adding a Constraint that defines the conversion with respect to toluene. The second degree of freedom for the reactor is to define the heat duty. In this case, let us assume the reactor to be adiabatic i.e. Q = 0. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.R101.conversion = Var(initialize=0.75, bounds=(0, 1))\n", - "\n", - "m.fs.R101.conv_constraint = Constraint(\n", - " expr=m.fs.R101.conversion * m.fs.R101.inlet.flow_mol_phase_comp[0, \"Vap\", \"toluene\"]\n", - " == (\n", - " m.fs.R101.inlet.flow_mol_phase_comp[0, \"Vap\", \"toluene\"]\n", - " - m.fs.R101.outlet.flow_mol_phase_comp[0, \"Vap\", \"toluene\"]\n", - " )\n", - ")\n", - "\n", - "m.fs.R101.conversion.fix(0.75)\n", - "m.fs.R101.heat_duty.fix(0)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The Flash conditions for F101 can be set as follows. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.F101.vap_outlet.temperature.fix(325.0)\n", - "m.fs.F101.deltaP.fix(0)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "
\n", - "Inline Exercise:\n", - "Set the conditions for Flash F102 to the following conditions:\n", - "
    \n", - "
  • T = 375 K
  • \n", - "
  • deltaP = -200000
  • \n", - "
\n", - "\n", - "Use Shift+Enter to run the cell once you have typed in your code. \n", - "
" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "exercise" - ] - }, - "outputs": [], - "source": [ - "# Todo: Set conditions for Flash F102" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "solution" - ] - }, - "outputs": [], - "source": [ - "m.fs.F102.vap_outlet.temperature.fix(375)\n", - "m.fs.F102.deltaP.fix(-200000)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let us fix the purge split fraction to 20% and the outlet pressure of the compressor is set to 350000 Pa. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.S101.split_fraction[0, \"purge\"].fix(0.2)\n", - "m.fs.C101.outlet.pressure.fix(350000)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "
\n", - "Inline Exercise:\n", - "We have now defined all the feed conditions and the inputs required for the unit models. The system should now have 0 degrees of freedom i.e. should be a square problem. Please check that the degrees of freedom is 0. \n", - "\n", - "Use Shift+Enter to run the cell once you have typed in your code. \n", - "
" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "exercise" - ] - }, - "outputs": [], - "source": [ - "# Todo: print the degrees of freedom" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "solution" - ] - }, - "outputs": [], - "source": [ - "print(degrees_of_freedom(m))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Initialization\n", - "\n", - "\n", - "This section will demonstrate how to use the built-in sequential decomposition tool to initialize our flowsheet.\n", - "\n", - "![](HDA_flowsheet.png) \n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let us first create an object for the SequentialDecomposition and specify our options for this. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "seq = SequentialDecomposition()\n", - "seq.options.select_tear_method = \"heuristic\"\n", - "seq.options.tear_method = \"Wegstein\"\n", - "seq.options.iterLim = 3\n", - "\n", - "# Using the SD tool\n", - "G = seq.create_graph(m)\n", - "heuristic_tear_set = seq.tear_set_arcs(G, method=\"heuristic\")\n", - "order = seq.calculation_order(G)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Which is the tear stream? Display tear set and order" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "for o in heuristic_tear_set:\n", - " print(o.name)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "What sequence did the SD tool determine to solve this flowsheet with the least number of tears? " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "scrolled": true - }, - "outputs": [], - "source": [ - "for o in order:\n", - " print(o[0].name)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - " \n", - "\n", - "![](HDA_tear_stream.png) \n", - "\n", - "\n", - "The SequentialDecomposition tool has determined that the tear stream is the mixer outlet. We will need to provide a reasonable guess for this." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "tear_guesses = {\n", - " \"flow_mol_phase_comp\": {\n", - " (0, \"Vap\", \"benzene\"): 1e-5,\n", - " (0, \"Vap\", \"toluene\"): 1e-5,\n", - " (0, \"Vap\", \"hydrogen\"): 0.30,\n", - " (0, \"Vap\", \"methane\"): 0.02,\n", - " (0, \"Liq\", \"benzene\"): 1e-5,\n", - " (0, \"Liq\", \"toluene\"): 0.30,\n", - " (0, \"Liq\", \"hydrogen\"): 1e-5,\n", - " (0, \"Liq\", \"methane\"): 1e-5,\n", - " },\n", - " \"temperature\": {0: 303},\n", - " \"pressure\": {0: 350000},\n", - "}\n", - "\n", - "# Pass the tear_guess to the SD tool\n", - "seq.set_guesses_for(m.fs.H101.inlet, tear_guesses)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Next, we need to tell the tool how to initialize a particular unit. We will be writing a python function which takes in a \"unit\" and calls the initialize method on that unit. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "def function(unit):\n", - " try:\n", - " initializer = unit.default_initializer()\n", - " initializer.initialize(unit, output_level=idaeslog.INFO)\n", - " except InitializationError:\n", - " solver = get_solver()\n", - " solver.solve(unit)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We are now ready to initialize our flowsheet in a sequential mode. Note that we specifically set the iteration limit to be 5 as we are trying to use this tool only to get a good set of initial values such that IPOPT can then take over and solve this flowsheet for us. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "seq.run(m, function)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "
\n", - "Inline Exercise:\n", - "We have now initialized the flowsheet. Let us run the flowsheet in a simulation mode to look at the results. To do this, complete the last line of code where we pass the model to the solver. You will need to type the following:\n", - " \n", - "results = solver.solve(m, tee=True)\n", - "\n", - "Use Shift+Enter to run the cell once you have typed in your code. \n", - "
\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "exercise" - ] - }, - "outputs": [], - "source": [ - "# Create the solver object\n", - "\n", - "\n", - "# Solve the model" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "solution" - ] - }, - "outputs": [], - "source": [ - "# Create the solver object\n", - "from idaes.core.solvers import get_solver\n", - "\n", - "solver = get_solver()\n", - "\n", - "# Solve the model\n", - "results = solver.solve(m, tee=True)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Analyze the results of the square problem\n", - "\n", - "\n", - "What is the total operating cost? " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "print(\"operating cost = $\", value(m.fs.operating_cost))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "For this operating cost, what is the amount of benzene we are able to produce and what purity we are able to achieve? " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.F102.report()\n", - "\n", - "print()\n", - "print(\"benzene purity = \", value(m.fs.purity))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Next, let's look at how much benzene we are losing with the light gases out of F101. IDAES has tools for creating stream tables based on the `Arcs` and/or `Ports` in a flowsheet. Let us create and print a simple stream table showing the stream leaving the reactor and the vapor stream from F101.\n", - "\n", - "
\n", - "Inline Exercise:\n", - "How much benzene are we losing in the F101 vapor outlet stream?\n", - "
\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from idaes.core.util.tables import (\n", - " create_stream_table_dataframe,\n", - " stream_table_dataframe_to_string,\n", - ")\n", - "\n", - "st = create_stream_table_dataframe({\"Reactor\": m.fs.s05, \"Light Gases\": m.fs.s06})\n", - "print(stream_table_dataframe_to_string(st))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "
\n", - "Inline Exercise:\n", - "You can query additional variables here if you like. \n", - "\n", - "Use Shift+Enter to run the cell once you have typed in your code. \n", - "
\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Optimization\n", - "\n", - "\n", - "We saw from the results above that the total operating cost for the base case was $419,122 per year. We are producing 0.142 mol/s of benzene at a purity of 82\\%. However, we are losing around 42\\% of benzene in F101 vapor outlet stream. \n", - "\n", - "Let us try to minimize this cost such that:\n", - "- we are producing at least 0.15 mol/s of benzene in F102 vapor outlet i.e. our product stream\n", - "- purity of benzene i.e. the mole fraction of benzene in F102 vapor outlet is at least 80%\n", - "- restricting the benzene loss in F101 vapor outlet to less than 20%\n", - "\n", - "For this problem, our decision variables are as follows:\n", - "- H101 outlet temperature\n", - "- R101 cooling duty provided\n", - "- F101 outlet temperature\n", - "- F102 outlet temperature\n", - "- F102 deltaP in the flash tank\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let us declare our objective function for this problem. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.objective = Objective(expr=m.fs.operating_cost)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now, we need to unfix the decision variables as we had solved a square problem (degrees of freedom = 0) until now. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.H101.outlet.temperature.unfix()\n", - "m.fs.R101.heat_duty.unfix()\n", - "m.fs.F101.vap_outlet.temperature.unfix()\n", - "m.fs.F102.vap_outlet.temperature.unfix()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "
\n", - "Inline Exercise:\n", - "Let us now unfix the remaining variable which is F102 pressure drop (F102.deltaP) \n", - "\n", - "Use Shift+Enter to run the cell once you have typed in your code. \n", - "
\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "exercise" - ] - }, - "outputs": [], - "source": [ - "# Todo: Unfix deltaP for F102" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "solution" - ] - }, - "outputs": [], - "source": [ - "# Todo: Unfix deltaP for F102\n", - "m.fs.F102.deltaP.unfix()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Next, we need to set bounds on these decision variables to values shown below:\n", - "\n", - " - H101 outlet temperature [500, 600] K\n", - " - R101 outlet temperature [600, 800] K\n", - " - F101 outlet temperature [298, 450] K\n", - " - F102 outlet temperature [298, 450] K\n", - " - F102 outlet pressure [105000, 110000] Pa\n", - "\n", - "Let us first set the variable bound for the H101 outlet temperature as shown below:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.H101.outlet.temperature[0].setlb(500)\n", - "m.fs.H101.outlet.temperature[0].setub(600)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "
\n", - "Inline Exercise:\n", - "Now, set the variable bound for the R101 outlet temperature.\n", - "\n", - "Use Shift+Enter to run the cell once you have typed in your code. \n", - "
" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "exercise" - ] - }, - "outputs": [], - "source": [ - "# Todo: Set the bounds for reactor outlet temperature" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "solution" - ] - }, - "outputs": [], - "source": [ - "# Todo: Set the bounds for reactor outlet temperature\n", - "m.fs.R101.outlet.temperature[0].setlb(600)\n", - "m.fs.R101.outlet.temperature[0].setub(800)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let us fix the bounds for the rest of the decision variables. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.F101.vap_outlet.temperature[0].setlb(298.0)\n", - "m.fs.F101.vap_outlet.temperature[0].setub(450.0)\n", - "m.fs.F102.vap_outlet.temperature[0].setlb(298.0)\n", - "m.fs.F102.vap_outlet.temperature[0].setub(450.0)\n", - "m.fs.F102.vap_outlet.pressure[0].setlb(105000)\n", - "m.fs.F102.vap_outlet.pressure[0].setub(110000)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now, the only things left to define are our constraints on overhead loss in F101, product flow rate and purity in F102. Let us first look at defining a constraint for the overhead loss in F101 where we are restricting the benzene leaving the vapor stream to less than 20 \\% of the benzene available in the reactor outlet. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.overhead_loss = Constraint(\n", - " expr=m.fs.F101.vap_outlet.flow_mol_phase_comp[0, \"Vap\", \"benzene\"]\n", - " <= 0.20 * m.fs.R101.outlet.flow_mol_phase_comp[0, \"Vap\", \"benzene\"]\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "
\n", - "Inline Exercise:\n", - "Now, add the constraint such that we are producing at least 0.15 mol/s of benzene in the product stream which is the vapor outlet of F102. Let us name this constraint as m.fs.product_flow. \n", - "\n", - "Use Shift+Enter to run the cell once you have typed in your code. \n", - "
" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "exercise" - ] - }, - "outputs": [], - "source": [ - "# Todo: Add minimum product flow constraint" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "solution" - ] - }, - "outputs": [], - "source": [ - "# Todo: Add minimum product flow constraint\n", - "m.fs.product_flow = Constraint(\n", - " expr=m.fs.F102.vap_outlet.flow_mol_phase_comp[0, \"Vap\", \"benzene\"] >= 0.15\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let us add the final constraint on product purity or the mole fraction of benzene in the product stream such that it is at least greater than 80%. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m.fs.product_purity = Constraint(expr=m.fs.purity >= 0.80)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "We have now defined the optimization problem and we are now ready to solve this problem. \n", - "\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "results = solver.solve(m, tee=True)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Optimization Results\n", - "\n", - "Display the results and product specifications" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "print(\"operating cost = $\", value(m.fs.operating_cost))\n", - "\n", - "print()\n", - "print(\"Product flow rate and purity in F102\")\n", - "\n", - "m.fs.F102.report()\n", - "\n", - "print()\n", - "print(\"benzene purity = \", value(m.fs.purity))\n", - "\n", - "print()\n", - "print(\"Overhead loss in F101\")\n", - "m.fs.F101.report()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Display optimal values for the decision variables" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "print(\"Optimal Values\")\n", - "print()\n", - "\n", - "print(\"H101 outlet temperature = \", value(m.fs.H101.outlet.temperature[0]), \"K\")\n", - "\n", - "print()\n", - "print(\"R101 outlet temperature = \", value(m.fs.R101.outlet.temperature[0]), \"K\")\n", - "\n", - "print()\n", - "print(\"F101 outlet temperature = \", value(m.fs.F101.vap_outlet.temperature[0]), \"K\")\n", - "\n", - "print()\n", - "print(\"F102 outlet temperature = \", value(m.fs.F102.vap_outlet.temperature[0]), \"K\")\n", - "print(\"F102 outlet pressure = \", value(m.fs.F102.vap_outlet.pressure[0]), \"Pa\")" - ] - }, - { - "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.8.12" - } - }, - "nbformat": 4, - "nbformat_minor": 3 + "cells": [ + { + "cell_type": "code", + "metadata": { + "tags": [ + "header", + "hide-cell" + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:25.418463Z", + "start_time": "2025-06-11T22:13:25.412645Z" + } + }, + "source": [ + "###############################################################################\n", + "# The Institute for the Design of Advanced Energy Systems Integrated Platform\n", + "# Framework (IDAES IP) was produced under the DOE Institute for the\n", + "# Design of Advanced Energy Systems (IDAES).\n", + "#\n", + "# Copyright (c) 2018-2023 by the software owners: The Regents of the\n", + "# University of California, through Lawrence Berkeley National Laboratory,\n", + "# National Technology & Engineering Solutions of Sandia, LLC, Carnegie Mellon\n", + "# University, West Virginia University Research Corporation, et al.\n", + "# All rights reserved. Please see the files COPYRIGHT.md and LICENSE.md\n", + "# for full copyright and license information.\n", + "###############################################################################" + ], + "outputs": [], + "execution_count": 1 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "# HDA Flowsheet Simulation and Optimization\n", + "\n", + "Author: Jaffer Ghouse
\n", + "Maintainer: Tanner Polley
\n", + "Updated: 2025-06-03\n", + "\n", + "## Learning outcomes\n", + "\n", + "\n", + "- Construct a steady-state flowsheet using the IDAES unit model library\n", + "- Connecting unit models in a flowsheet using Arcs\n", + "- Using the SequentialDecomposition tool to initialize a flowsheet with recycle\n", + "- Formulate and solve an optimization problem\n", + " - Defining an objective function\n", + " - Setting variable bounds\n", + " - Adding additional constraints\n", + "\n", + "\n", + "The general workflow of setting up an IDAES flowsheet is the following:\n", + "\n", + "- 1 Importing Modules\n", + "- 2 Building a Model\n", + "- 3 Scaling the Model\n", + "- 4 Specifying the Model\n", + "- 5 Initializing the Model\n", + "- 6 Solving the Model\n", + "- 7 Analyzing and Visualizing the Results\n", + "- 8 Optimizing the Model\n", + "\n", + "We will complete each of these steps as well as demonstrate analyses on this model through some examples and exercises\n", + "\n", + "\n", + "## Problem Statement\n", + "\n", + "Hydrodealkylation is a chemical reaction that often involves reacting\n", + "an aromatic hydrocarbon in the presence of hydrogen gas to form a\n", + "simpler aromatic hydrocarbon devoid of functional groups. In this\n", + "example, toluene will be reacted with hydrogen gas at high temperatures\n", + " to form benzene via the following reaction:\n", + "\n", + "**C6H5CH3 + H2 \u2192 C6H6 + CH4**\n", + "\n", + "\n", + "This reaction is often accompanied by an equilibrium side reaction\n", + "which forms diphenyl, which we will neglect for this example.\n", + "\n", + "This example is based on the 1967 AIChE Student Contest problem as\n", + "present by Douglas, J.M., Chemical Design of Chemical Processes, 1988,\n", + "McGraw-Hill.\n", + "\n", + "The flowsheet that we will be using for this module is shown below with the stream conditions. We will be processing toluene and hydrogen to produce at least 370 TPY of benzene. As shown in the flowsheet, there are two flash tanks, F101 to separate out the non-condensibles and F102 to further separate the benzene-toluene mixture to improve the benzene purity. Note that typically a distillation column is required to obtain high purity benzene but that is beyond the scope of this workshop. The non-condensibles separated out in F101 will be partially recycled back to M101 and the rest will be either purged or combusted for power generation.We will assume ideal gas for this flowsheet. The properties required for this module are available in the same directory:\n", + "\n", + "- hda_ideal_VLE.py\n", + "- hda_reaction.py\n", + "\n", + "The state variables chosen for the property package are **flows of component by phase, temperature and pressure**. The components considered are: **toluene, hydrogen, benzene and methane**. Therefore, every stream has 8 flow variables, 1 temperature and 1 pressure variable. \n", + "\n", + "![](HDA_flowsheet.png)\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1 Importing Modules\n", + "### 1.1 Importing required pyomo and idaes components\n", + "\n", + "\n", + "To construct a flowsheet, we will need several components from the pyomo and idaes package. Let us first import the following components from Pyomo:\n", + "- Constraint (to write constraints)\n", + "- Var (to declare variables)\n", + "- ConcreteModel (to create the concrete model object)\n", + "- Expression (to evaluate values as a function of variables defined in the model)\n", + "- Objective (to define an objective function for optimization)\n", + "- SolverFactory (to solve the problem)\n", + "- TransformationFactory (to apply certain transformations)\n", + "- Arc (to connect two unit models)\n", + "- SequentialDecomposition (to initialize the flowsheet in a sequential mode)\n", + "\n", + "For further details on these components, please refer to the pyomo documentation: https://pyomo.readthedocs.io/en/stable/\n" + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:26.066510Z", + "start_time": "2025-06-11T22:13:25.662098Z" + } + }, + "source": [ + "from pyomo.environ import (\n", + " Constraint,\n", + " Var,\n", + " ConcreteModel,\n", + " Expression,\n", + " Objective,\n", + " SolverFactory,\n", + " TransformationFactory,\n", + " value,\n", + ")\n", + "from pyomo.network import Arc, SequentialDecomposition" + ], + "outputs": [], + "execution_count": 2 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "From idaes, we will be needing the FlowsheetBlock and the following unit models:\n", + "- Feed\n", + "- Mixer\n", + "- Heater\n", + "- StoichiometricReactor\n", + "- **Flash**\n", + "- Separator (splitter) \n", + "- PressureChanger\n", + "- Product" + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:27.637361Z", + "start_time": "2025-06-11T22:13:26.082580Z" + } + }, + "source": [ + "from idaes.core import FlowsheetBlock" + ], + "outputs": [], + "execution_count": 3 + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:27.752223Z", + "start_time": "2025-06-11T22:13:27.645348Z" + } + }, + "source": [ + "from idaes.models.unit_models import (\n", + " PressureChanger, IsentropicPressureChangerInitializer,\n", + " Mixer, MixerInitializer,\n", + " Separator as Splitter, SeparatorInitializer,\n", + " Heater,\n", + " StoichiometricReactor,\n", + " Feed,\n", + " Product,\n", + ")" + ], + "outputs": [], + "execution_count": 4 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "
\n", + "Inline Exercise:\n", + "Now, import the remaining unit models highlighted in blue above and run the cell using `Shift+Enter` after typing in the code. \n", + "
\n" + ] + }, + { + "cell_type": "code", + "metadata": { + "tags": [ + "exercise" + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:27.763417Z", + "start_time": "2025-06-11T22:13:27.761004Z" + } + }, + "source": [ + "# Todo: import flash model from idaes.models.unit_models" + ], + "outputs": [], + "execution_count": 5 + }, + { + "cell_type": "code", + "metadata": { + "tags": [ + "solution" + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:27.774708Z", + "start_time": "2025-06-11T22:13:27.772299Z" + } + }, + "source": [ + "# Todo: import flash model from idaes.models.unit_models\n", + "from idaes.models.unit_models import Flash" + ], + "outputs": [], + "execution_count": 6 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We will also be needing some utility tools to put together the flowsheet and calculate the degrees of freedom. " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:27.788004Z", + "start_time": "2025-06-11T22:13:27.784891Z" + } + }, + "source": [ + "from idaes.models.unit_models.pressure_changer import ThermodynamicAssumption\n", + "from idaes.core.util.model_statistics import degrees_of_freedom\n", + "from idaes.core.scaling.util import set_scaling_factor\n", + "from idaes.core.scaling.autoscaling import AutoScaler\n", + "\n", + "# Import idaes logger to set output levels\n", + "import idaes.logger as idaeslog\n", + "from idaes.core.solvers import get_solver\n", + "from idaes.core.util.exceptions import InitializationError" + ], + "outputs": [], + "execution_count": 7 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 1.2 Importing required thermo and reaction package\n", + "\n", + "The final set of imports are to import the thermo and reaction package for the HDA process. We have created a custom thermo package that assumes Ideal Gas with support for VLE. \n", + "\n", + "The reaction package here is very simple as we will be using only a StochiometricReactor and the reaction package consists of the stochiometric coefficients for the reaction and the parameter for the heat of reaction. \n", + "\n", + "Let us import the following modules and they are in the same directory as this jupyter notebook:\n", + "
    \n", + "
  • hda_ideal_VLE as thermo_props
  • \n", + "
  • hda_reaction as reaction_props
  • \n", + "
\n", + "
" + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:27.806315Z", + "start_time": "2025-06-11T22:13:27.798068Z" + } + }, + "source": [ + "from idaes_examples.mod.hda import hda_ideal_VLE as thermo_props\n", + "from idaes_examples.mod.hda import hda_reaction as reaction_props" + ], + "outputs": [], + "execution_count": 8 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2 Constructing the Flowsheet\n", + "\n", + "We have now imported all the components, unit models, and property modules we need to construct a flowsheet. Let us create a ConcreteModel and add the flowsheet block as we did in module 1. " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:27.819615Z", + "start_time": "2025-06-11T22:13:27.814729Z" + } + }, + "source": [ + "m = ConcreteModel()\n", + "m.fs = FlowsheetBlock(dynamic=False)" + ], + "outputs": [], + "execution_count": 9 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We now need to add the property packages to the flowsheet. Unlike Module 1, where we only had a thermo property package, for this flowsheet we will also need to add a reaction property package. " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:27.841949Z", + "start_time": "2025-06-11T22:13:27.829949Z" + } + }, + "source": [ + "m.fs.thermo_params = thermo_props.HDAParameterBlock()\n", + "m.fs.reaction_params = reaction_props.HDAReactionParameterBlock(\n", + " property_package=m.fs.thermo_params\n", + ")" + ], + "outputs": [], + "execution_count": 10 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 2.1 Adding Unit Models\n", + "\n", + "Let us start adding the unit models we have imported to the flowsheet. Here, we are adding the Feed (assigned a name `I101` for Inlet), `Mixer` (assigned a name `M101`) and a `Heater` (assigned a name `H101`). Note that, all unit models need to be given a property package argument. In addition to that, there are several arguments depending on the unit model, please refer to the documentation for more details (https://idaes-pse.readthedocs.io/en/stable/reference_guides/model_libraries/generic/unit_models/index.html). For example, the `Mixer` unit model here must be specified the number of inlets that it will take in and the `Heater` can have specific settings enabled such as `has_pressure_change` or `has_phase_equilibrium`." + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:27.876870Z", + "start_time": "2025-06-11T22:13:27.853517Z" + } + }, + "source": [ + "m.fs.I101 = Feed(\n", + " property_package=m.fs.thermo_params)\n", + "\n", + "m.fs.I102 = Feed(\n", + " property_package=m.fs.thermo_params)\n", + "\n", + "m.fs.M101 = Mixer(\n", + " property_package=m.fs.thermo_params,\n", + " num_inlets=3,\n", + ")\n", + "\n", + "m.fs.H101 = Heater(\n", + " property_package=m.fs.thermo_params,\n", + " has_pressure_change=False,\n", + " has_phase_equilibrium=True,\n", + ")" + ], + "outputs": [], + "execution_count": 11 + }, + { + "cell_type": "markdown", + "metadata": { + "tags": [ + "exercise" + ] + }, + "source": [ + "
\n", + "Inline Exercise:\n", + "Let us now add the StoichiometricReactor(assign the name R101) and pass the following arguments:\n", + "
    \n", + "
  • \"property_package\": m.fs.thermo_params
  • \n", + "
  • \"reaction_package\": m.fs.reaction_params
  • \n", + "
  • \"has_heat_of_reaction\": True
  • \n", + "
  • \"has_heat_transfer\": True
  • \n", + "
  • \"has_pressure_change\": False
  • \n", + "
\n", + "
" + ] + }, + { + "cell_type": "code", + "metadata": { + "tags": [ + "exercise" + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:27.894702Z", + "start_time": "2025-06-11T22:13:27.891599Z" + } + }, + "source": [ + "# Todo: Add reactor with the specifications above" + ], + "outputs": [], + "execution_count": 12 + }, + { + "cell_type": "code", + "metadata": { + "tags": [ + "solution" + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:27.921265Z", + "start_time": "2025-06-11T22:13:27.910910Z" + } + }, + "source": [ + "# Todo: Add reactor with the specifications above\n", + "m.fs.R101 = StoichiometricReactor(\n", + " property_package=m.fs.thermo_params,\n", + " reaction_package=m.fs.reaction_params,\n", + " has_heat_of_reaction=True,\n", + " has_heat_transfer=True,\n", + " has_pressure_change=False,\n", + ")" + ], + "outputs": [], + "execution_count": 13 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let us now add the Flash(assign the name F101) and pass the following arguments:\n", + "
    \n", + "
  • \"property_package\": m.fs.thermo_params
  • \n", + "
  • \"has_heat_transfer\": True
  • \n", + "
  • \"has_pressure_change\": False
  • \n", + "
" + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:27.947463Z", + "start_time": "2025-06-11T22:13:27.933993Z" + } + }, + "source": [ + "m.fs.F101 = Flash(\n", + " property_package=m.fs.thermo_params,\n", + " has_heat_transfer=True,\n", + " has_pressure_change=True,\n", + ")" + ], + "outputs": [], + "execution_count": 14 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": "Let us now add the Splitter(S101) with specific names for its output (purge and recycle), PressureChanger(C101) and the second Flash(F102)." + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:27.987933Z", + "start_time": "2025-06-11T22:13:27.964931Z" + } + }, + "source": [ + "m.fs.S101 = Splitter(\n", + " property_package=m.fs.thermo_params,\n", + " ideal_separation=False,\n", + " outlet_list=[\"purge\", \"recycle\"],\n", + ")\n", + "\n", + "\n", + "m.fs.C101 = PressureChanger(\n", + " property_package=m.fs.thermo_params,\n", + " compressor=True,\n", + " thermodynamic_assumption=ThermodynamicAssumption.isothermal,\n", + ")\n", + "\n", + "m.fs.F102 = Flash(\n", + " property_package=m.fs.thermo_params,\n", + " has_heat_transfer=True,\n", + " has_pressure_change=True,\n", + ")" + ], + "outputs": [], + "execution_count": 15 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "Last, we will add the three Product blocks (P101, P102, P103). We use `Feed` blocks and `Product` blocks for convenience with reporting stream summaries and consistency" + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:28.009893Z", + "start_time": "2025-06-11T22:13:28.001769Z" + } + }, + "cell_type": "code", + "source": [ + "m.fs.P101 = Product(\n", + " property_package=m.fs.thermo_params)\n", + "\n", + "m.fs.P102 = Product(\n", + " property_package=m.fs.thermo_params)\n", + "\n", + "m.fs.P103 = Product(\n", + " property_package=m.fs.thermo_params)" + ], + "outputs": [], + "execution_count": 16 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 2.2 Connecting Unit Models using Arcs\n", + "\n", + "We have now added all the unit models we need to the flowsheet. However, we have not yet specified how the units are to be connected. To do this, we will be using the `Arc` which is a pyomo component that takes in two arguments: `source` and `destination`. Let us connect the outlet of the inlets (I101, I102) to the inlet of the mixer (M101) and outlet of the mixer to the inlet of the heater(H101)." + ] + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:28.034324Z", + "start_time": "2025-06-11T22:13:28.031285Z" + } + }, + "cell_type": "code", + "source": [ + "m.fs.s01 = Arc(source=m.fs.I101.outlet, destination=m.fs.M101.inlet_1)\n", + "m.fs.s02 = Arc(source=m.fs.I102.outlet, destination=m.fs.M101.inlet_2)\n", + "m.fs.s03 = Arc(source=m.fs.M101.outlet, destination=m.fs.H101.inlet)" + ], + "outputs": [], + "execution_count": 17 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "![](HDA_flowsheet.png) \n", + "\n", + "\n" + ] + }, + { + "metadata": { + "tags": [ + "exercise" + ] + }, + "cell_type": "markdown", + "source": [ + "
\n", + "Inline Exercise:\n", + "Now, connect the H101 outlet to the R101 inlet using the cell above as a guide.\n", + "
" + ] + }, + { + "cell_type": "code", + "metadata": { + "tags": [ + "exercise" + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:28.055815Z", + "start_time": "2025-06-11T22:13:28.052507Z" + } + }, + "source": [ + "# Todo: Connect the H101 outlet to R101 inlet" + ], + "outputs": [], + "execution_count": 18 + }, + { + "cell_type": "code", + "metadata": { + "tags": [ + "solution" + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:28.084663Z", + "start_time": "2025-06-11T22:13:28.082008Z" + } + }, + "source": [ + "# Todo: Connect the H101 outlet to R101 inlet\n", + "m.fs.s04 = Arc(source=m.fs.H101.outlet, destination=m.fs.R101.inlet)" + ], + "outputs": [], + "execution_count": 19 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We will now be connecting the rest of the flowsheet as shown below. Notice how the outlet names are different for the flash tanks F101 and F102 as they have a vapor and a liquid outlet. " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:28.118422Z", + "start_time": "2025-06-11T22:13:28.113732Z" + } + }, + "source": [ + "m.fs.s05 = Arc(source=m.fs.R101.outlet, destination=m.fs.F101.inlet)\n", + "m.fs.s06 = Arc(source=m.fs.F101.vap_outlet, destination=m.fs.S101.inlet)\n", + "m.fs.s07 = Arc(source=m.fs.F101.liq_outlet, destination=m.fs.F102.inlet)\n", + "m.fs.s08 = Arc(source=m.fs.S101.recycle, destination=m.fs.C101.inlet)\n", + "m.fs.s09 = Arc(source=m.fs.C101.outlet, destination=m.fs.M101.inlet_3)\n" + ], + "outputs": [], + "execution_count": 20 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "Last we will connect the outlet streams to the inlets of the Product blocks (P101, P102, P103)" + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:28.148879Z", + "start_time": "2025-06-11T22:13:28.144601Z" + } + }, + "cell_type": "code", + "source": [ + "m.fs.s10 = Arc(source=m.fs.F102.vap_outlet, destination=m.fs.P101.inlet)\n", + "m.fs.s11 = Arc(source=m.fs.F102.liq_outlet, destination=m.fs.P102.inlet)\n", + "m.fs.s12 = Arc(source=m.fs.S101.purge, destination=m.fs.P103.inlet)" + ], + "outputs": [], + "execution_count": 21 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We have now connected the unit model block using the arcs. However, each of these arcs link to ports on the two unit models that are connected. In this case, the ports consist of the state variables that need to be linked between the unit models. Pyomo provides a convenient method to write these equality constraints for us between two ports and this is done as follows:" + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:28.198384Z", + "start_time": "2025-06-11T22:13:28.176239Z" + } + }, + "source": [ + "TransformationFactory(\"network.expand_arcs\").apply_to(m)" + ], + "outputs": [], + "execution_count": 22 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 2.3 Adding expressions to compute purity and operating costs\n", + "\n", + "In this section, we will add a few Expressions that allows us to evaluate the performance. Expressions provide a convenient way of calculating certain values that are a function of the variables defined in the model. For more details on Expressions, please refer to: https://pyomo.readthedocs.io/en/stable/pyomo_modeling_components/Expressions.html\n", + "\n", + "For this flowsheet, we are interested in computing the purity of the product Benzene stream (i.e. the mole fraction) and the operating cost which is a sum of the cooling and heating cost. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let us first add an Expression to compute the mole fraction of benzene in the `vap_outlet` of F102 which is our product stream. Please note that the var flow_mol_phase_comp has the index - [time, phase, component]. As this is a steady-state flowsheet, the time index by default is 0. The valid phases are [\"Liq\", \"Vap\"]. Similarly the valid component list is [\"benzene\", \"toluene\", \"hydrogen\", \"methane\"]." + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:28.222088Z", + "start_time": "2025-06-11T22:13:28.218400Z" + } + }, + "source": [ + "m.fs.purity = Expression(\n", + " expr=m.fs.F102.vap_outlet.flow_mol_phase_comp[0, \"Vap\", \"benzene\"]\n", + " / (\n", + " m.fs.F102.vap_outlet.flow_mol_phase_comp[0, \"Vap\", \"benzene\"]\n", + " + m.fs.F102.vap_outlet.flow_mol_phase_comp[0, \"Vap\", \"toluene\"]\n", + " )\n", + ")" + ], + "outputs": [], + "execution_count": 23 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, let us add an expression to compute the cooling cost assuming a cost of 0.212E-4 $/kW. Note that cooling utility is required for the reactor (R101) and the first flash (F101). " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:28.248216Z", + "start_time": "2025-06-11T22:13:28.244244Z" + } + }, + "source": [ + "m.fs.cooling_cost = Expression(\n", + " expr=0.212e-7 * (-m.fs.F101.heat_duty[0]) + 0.212e-7 * (-m.fs.R101.heat_duty[0])\n", + ")" + ], + "outputs": [], + "execution_count": 24 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "Now, let us add an expression to compute the heating cost assuming the utility cost as follows:\n", + "
    \n", + "
  • 2.2E-4 dollars/kW for H101
  • \n", + "
  • 1.9E-4 dollars/kW for F102
  • \n", + "
\n", + "Note that the heat duty is in units of watt (J/s). " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:28.271256Z", + "start_time": "2025-06-11T22:13:28.268284Z" + } + }, + "source": [ + "m.fs.heating_cost = Expression(\n", + " expr=2.2e-7 * m.fs.H101.heat_duty[0] + 1.9e-7 * m.fs.F102.heat_duty[0]\n", + ")" + ], + "outputs": [], + "execution_count": 25 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let us now add an expression to compute the total operating cost per year which is basically the sum of the cooling and heating cost we defined above. " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:28.295580Z", + "start_time": "2025-06-11T22:13:28.292627Z" + } + }, + "source": [ + "m.fs.operating_cost = Expression(\n", + " expr=(3600 * 24 * 365 * (m.fs.heating_cost + m.fs.cooling_cost))\n", + ")" + ], + "outputs": [], + "execution_count": 26 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": [ + "## 3 Scaling the Model\n", + "\n", + "In this example, we will simply use the ``AutoScaler`` method to scale our model since this example is focused on introductory flowsheet building for a full process system.\n", + "\n", + "For more direct control, a manual, magnitude based approach can be used. If this method is chosen or appropriate, it is highly recommended to look at these documentation:\n", + "- Scaling Toolbox https://idaes-pse.readthedocs.io/en/stable/reference_guides/scaling/scaling.html\n", + "- Scaling Workshop https://idaes-examples.readthedocs.io/en/latest/docs/scaling/scaler_workshop_doc.html.\n", + "\n", + "In this example, we already imported the ``AutoScaler`` class in the beginning so we just create the ``autoscaler`` object and call the ``scale_model`` method on the model `m`." + ] + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:28.915668Z", + "start_time": "2025-06-11T22:13:28.317020Z" + } + }, + "cell_type": "code", + "source": [ + "autoscaler = AutoScaler()\n", + "autoscaler.scale_model(m)" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "WARNING: model contains export suffix 'scaling_factor' that contains 2\n", + "component keys that are not exported as part of the NL file. Skipping.\n" + ] + } + ], + "execution_count": 27 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 4 Specifying the Model\n", + "### 4.1 Fixing feed conditions\n", + "\n", + "Let us first check how many degrees of freedom exist for this flowsheet using the `degrees_of_freedom` tool we imported earlier. " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:28.948173Z", + "start_time": "2025-06-11T22:13:28.924210Z" + } + }, + "source": [ + "print(degrees_of_freedom(m))" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "29\n" + ] + } + ], + "execution_count": 28 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": "We will now be fixing the toluene feed (`I101`) stream to the conditions shown in the flowsheet above. Please note that though this is a pure toluene feed, the remaining components are still assigned a very small non-zero value to help with convergence and initializing." + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.012096Z", + "start_time": "2025-06-11T22:13:29.006965Z" + } + }, + "source": [ + "m.fs.I101.flow_mol_phase_comp[0, \"Vap\", \"benzene\"].fix(1e-5)\n", + "m.fs.I101.flow_mol_phase_comp[0, \"Vap\", \"toluene\"].fix(1e-5)\n", + "m.fs.I101.flow_mol_phase_comp[0, \"Vap\", \"hydrogen\"].fix(1e-5)\n", + "m.fs.I101.flow_mol_phase_comp[0, \"Vap\", \"methane\"].fix(1e-5)\n", + "m.fs.I101.flow_mol_phase_comp[0, \"Liq\", \"benzene\"].fix(1e-5)\n", + "m.fs.I101.flow_mol_phase_comp[0, \"Liq\", \"toluene\"].fix(0.30)\n", + "m.fs.I101.flow_mol_phase_comp[0, \"Liq\", \"hydrogen\"].fix(1e-5)\n", + "m.fs.I101.flow_mol_phase_comp[0, \"Liq\", \"methane\"].fix(1e-5)\n", + "m.fs.I101.temperature.fix(303.2)\n", + "m.fs.I101.pressure.fix(350000)" + ], + "outputs": [], + "execution_count": 30 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "Similarly, let us fix the hydrogen feed (`I102`) to the following conditions in the next cell:\n", + "
    \n", + "
  • FH2 = 0.30 mol/s
  • \n", + "
  • FCH4 = 0.02 mol/s
  • \n", + "
  • Remaining components = 1e-5 mol/s
  • \n", + "
  • T = 303.2 K
  • \n", + "
  • P = 350000 Pa
  • \n", + "
\n", + "\n" + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.036253Z", + "start_time": "2025-06-11T22:13:29.031731Z" + } + }, + "source": [ + "m.fs.I102.flow_mol_phase_comp[0, \"Vap\", \"benzene\"].fix(1e-5)\n", + "m.fs.I102.flow_mol_phase_comp[0, \"Vap\", \"toluene\"].fix(1e-5)\n", + "m.fs.I102.flow_mol_phase_comp[0, \"Vap\", \"hydrogen\"].fix(0.30)\n", + "m.fs.I102.flow_mol_phase_comp[0, \"Vap\", \"methane\"].fix(0.02)\n", + "m.fs.I102.flow_mol_phase_comp[0, \"Liq\", \"benzene\"].fix(1e-5)\n", + "m.fs.I102.flow_mol_phase_comp[0, \"Liq\", \"toluene\"].fix(1e-5)\n", + "m.fs.I102.flow_mol_phase_comp[0, \"Liq\", \"hydrogen\"].fix(1e-5)\n", + "m.fs.I102.flow_mol_phase_comp[0, \"Liq\", \"methane\"].fix(1e-5)\n", + "m.fs.I102.temperature.fix(303.2)\n", + "m.fs.I102.pressure.fix(350000)" + ], + "outputs": [], + "execution_count": 31 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 4.2 Fixing unit model specifications\n", + "\n", + "Now that we have fixed our inlet feed conditions, we will now be fixing the operating conditions for the unit models in the flowsheet. Let us set set the H101 outlet temperature to 600 K. " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.078559Z", + "start_time": "2025-06-11T22:13:29.075531Z" + } + }, + "source": [ + "m.fs.H101.outlet.temperature.fix(600)" + ], + "outputs": [], + "execution_count": 32 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For the StoichiometricReactor, we have to define the conversion in terms of toluene. This requires us to create a new variable for specifying the conversion and adding a Constraint that defines the conversion with respect to toluene. The second degree of freedom for the reactor is to define the heat duty. In this case, let us assume the reactor to be adiabatic i.e. Q = 0. " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.111099Z", + "start_time": "2025-06-11T22:13:29.106956Z" + } + }, + "source": [ + "m.fs.R101.conversion = Var(initialize=0.75, bounds=(0, 1))\n", + "\n", + "m.fs.R101.conv_constraint = Constraint(\n", + " expr=m.fs.R101.conversion * m.fs.R101.inlet.flow_mol_phase_comp[0, \"Vap\", \"toluene\"]\n", + " == (\n", + " m.fs.R101.inlet.flow_mol_phase_comp[0, \"Vap\", \"toluene\"]\n", + " - m.fs.R101.outlet.flow_mol_phase_comp[0, \"Vap\", \"toluene\"]\n", + " )\n", + ")\n", + "\n", + "m.fs.R101.conversion.fix(0.75)\n", + "m.fs.R101.heat_duty.fix(0)" + ], + "outputs": [], + "execution_count": 33 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The Flash conditions for F101 can be set as follows. " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.128972Z", + "start_time": "2025-06-11T22:13:29.125273Z" + } + }, + "source": [ + "m.fs.F101.vap_outlet.temperature.fix(325.0)\n", + "m.fs.F101.deltaP.fix(0)" + ], + "outputs": [], + "execution_count": 34 + }, + { + "cell_type": "markdown", + "metadata": { + "tags": [ + "exercise" + ] + }, + "source": [ + "
\n", + "Inline Exercise:\n", + "Set the conditions for Flash F102 to the following conditions:\n", + "
    \n", + "
  • T = 375 K
  • \n", + "
  • deltaP = -200000
  • \n", + "
\n", + "\n", + "Use Shift+Enter to run the cell once you have typed in your code. \n", + "
" + ] + }, + { + "cell_type": "code", + "metadata": { + "tags": [ + "exercise" + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.149002Z", + "start_time": "2025-06-11T22:13:29.146476Z" + } + }, + "source": [ + "# Todo: Set conditions for Flash F102" + ], + "outputs": [], + "execution_count": 35 + }, + { + "cell_type": "code", + "metadata": { + "tags": [ + "solution" + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.171742Z", + "start_time": "2025-06-11T22:13:29.168352Z" + } + }, + "source": [ + "m.fs.F102.vap_outlet.temperature.fix(375)\n", + "m.fs.F102.deltaP.fix(-200000)" + ], + "outputs": [], + "execution_count": 36 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let us fix the purge split fraction to 20% and the outlet pressure of the compressor is set to 350000 Pa. " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.196247Z", + "start_time": "2025-06-11T22:13:29.192956Z" + } + }, + "source": [ + "m.fs.S101.split_fraction[0, \"purge\"].fix(0.2)\n", + "m.fs.C101.outlet.pressure.fix(350000)" + ], + "outputs": [], + "execution_count": 37 + }, + { + "cell_type": "markdown", + "metadata": { + "tags": [ + "exercise" + ] + }, + "source": [ + "
\n", + "Inline Exercise:\n", + "We have now defined all the feed conditions and the inputs required for the unit models. The system should now have 0 degrees of freedom i.e. should be a square problem. Please check that the degrees of freedom is 0. \n", + "\n", + "Use Shift+Enter to run the cell once you have typed in your code. \n", + "
" + ] + }, + { + "cell_type": "code", + "metadata": { + "tags": [ + "exercise" + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.220402Z", + "start_time": "2025-06-11T22:13:29.217907Z" + } + }, + "source": [ + "# Todo: print the degrees of freedom" + ], + "outputs": [], + "execution_count": 38 + }, + { + "cell_type": "code", + "metadata": { + "tags": [ + "solution" + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.263397Z", + "start_time": "2025-06-11T22:13:29.241129Z" + } + }, + "source": [ + "print(degrees_of_freedom(m))" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0\n" + ] + } + ], + "execution_count": 39 + }, + { + "metadata": { + "tags": [ + "noauto" + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.316042Z", + "start_time": "2025-06-11T22:13:29.313537Z" + } + }, + "cell_type": "code", + "source": "", + "outputs": [], + "execution_count": null + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 5 Initializing the Model\n", + "\n", + "\n", + "\n", + "When a flowsheet contains a recycle loop, the outlet of a downstream unit becomes the inlet of an upstream unit, creating a cyclic dependency that prevents straightforward calculation of all stream conditions. The tear\u2010stream method is necessary because it \u201cbreaks\u201d this loop: you select one recycle stream as the tear, assign it an initial guess, and then solve the rest of the flowsheet as if it were acyclic. Once the downstream units compute their outputs, you compare the calculated value of the torn stream to your initial guess and iteratively adjust until they coincide. Without tearing, the solver cannot establish a proper topological sequence or drive the recycle to convergence, making initialization\u2014and ultimately steady\u2010state convergence\u2014impossible.\n", + "\n", + "It is important to determine the tear stream for a flowsheet which will be demonstrated below.\n", + "\n", + "\n", + "![](HDA_flowsheet.png)\n", + "\n", + "Currently, there are two methods of initializing a full flowsheet: using the sequential decomposition tool, or manually propagating through the flowsheet. Both methods will be shown.\n", + "\n", + "### 5.1 Sequential Decomposition\n", + "\n", + "This section will demonstrate how to use the built-in sequential decomposition tool to initialize our flowsheet. Sequential Decomposition is a tool from pyomo where the documentation can be found here https://pyomo.readthedocs.io/en/stable/explanation/modeling/network.html#sequential-decomposition\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": "Let us first create an object for the SequentialDecomposition and specify our options for this. We can also create a graph for our flowsheet to determine the tear set and order." + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.330212Z", + "start_time": "2025-06-11T22:13:29.324872Z" + } + }, + "source": [ + "seq = SequentialDecomposition()\n", + "seq.options.select_tear_method = \"heuristic\"\n", + "seq.options.tear_method = \"Wegstein\"\n", + "seq.options.iterLim = 3\n", + "\n", + "# Using the SD tool\n", + "G = seq.create_graph(m)\n", + "heuristic_tear_set = seq.tear_set_arcs(G, method=\"heuristic\")\n", + "order = seq.calculation_order(G)" + ], + "outputs": [], + "execution_count": 41 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Which is the tear stream? Display tear set and order" + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.353734Z", + "start_time": "2025-06-11T22:13:29.350730Z" + } + }, + "source": [ + "for o in heuristic_tear_set:\n", + " print(o.name)" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "fs.s03\n" + ] + } + ], + "execution_count": 42 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "What sequence did the SD tool determine to solve this flowsheet with the least number of tears? " + ] + }, + { + "cell_type": "code", + "metadata": { + "scrolled": true, + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.391173Z", + "start_time": "2025-06-11T22:13:29.388153Z" + } + }, + "source": [ + "for o in order:\n", + " print(o[0].name)" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "fs.I101\n", + "fs.R101\n", + "fs.F101\n", + "fs.S101\n", + "fs.C101\n", + "fs.M101\n" + ] + } + ], + "execution_count": 43 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + " \n", + "\n", + "![](HDA_tear_stream.png) \n", + "\n", + "\n", + "The SequentialDecomposition tool has determined that the tear stream is the mixer outlet. You can see this shown in the picture of the flowsheet above as the outlet of the mixer as the two lines crossing it identifying it as the tear stream. We will need to provide a reasonable guess for this." + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.430684Z", + "start_time": "2025-06-11T22:13:29.426921Z" + } + }, + "source": [ + "tear_guesses = {\n", + " \"flow_mol_phase_comp\": {\n", + " (0, \"Liq\", \"benzene\"): 1e-5,\n", + " (0, \"Liq\", \"toluene\"): 0.30,\n", + " (0, \"Liq\", \"methane\"): 1e-5,\n", + " (0, \"Liq\", \"hydrogen\"): 1e-5,\n", + " (0, \"Vap\", \"benzene\"): 1e-5,\n", + " (0, \"Vap\", \"toluene\"): 1e-5,\n", + " (0, \"Vap\", \"methane\"): 0.02,\n", + " (0, \"Vap\", \"hydrogen\"): 0.30,\n", + " },\n", + " \"temperature\": {0: 303},\n", + " \"pressure\": {0: 350000},\n", + "}\n", + "\n", + "# Pass the tear_guess to the SD tool\n", + "seq.set_guesses_for(m.fs.H101.inlet, tear_guesses)" + ], + "outputs": [], + "execution_count": 44 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next, we need to tell the tool how to initialize a particular unit. We will be writing a python function which takes in a \"unit\" and calls the initialize method on that unit. " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.465203Z", + "start_time": "2025-06-11T22:13:29.462031Z" + } + }, + "source": [ + "def function(unit):\n", + " try:\n", + " initializer = unit.default_initializer()\n", + " initializer.initialize(unit, output_level=idaeslog.INFO)\n", + " except InitializationError:\n", + " solver = get_solver()\n", + " solver.solve(unit)" + ], + "outputs": [], + "execution_count": 45 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We are now ready to initialize our flowsheet in a sequential mode. Note that we specifically set the iteration limit to be 5 as we are trying to use this tool only to get a good set of initial values such that IPOPT can then take over and solve this flowsheet for us. " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.505027Z", + "start_time": "2025-06-11T22:13:29.501933Z" + } + }, + "source": "# seq.run(m, function)", + "outputs": [], + "execution_count": 46 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": [ + "### 5.2 Manual Propogation Method\n", + "\n", + "This method uses a more direct approach to initialize the flowsheet, utilizing the updated initalizer method and propogating manually throught the flowsheet and solving for the tear stream directly.\n", + "Lets first import a helper function that will help us manually propagate and step through the flowsheet" + ] + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.536579Z", + "start_time": "2025-06-11T22:13:29.533074Z" + } + }, + "cell_type": "code", + "source": "from idaes.core.util.initialization import propagate_state", + "outputs": [], + "execution_count": 47 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": [ + "Now we can setup our initial guesses for the tear stream which we know is the outlet of the `Mixer` or the inlet of the `Heater`. We can use the same initial guesses used in the first method. We also want to ensure that the degrees of freedom are consistent while we manually intialize the model.\n", + "\n", + "We will first ensure that are current degrees of freedom is still zero" + ] + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.583098Z", + "start_time": "2025-06-11T22:13:29.553382Z" + } + }, + "cell_type": "code", + "source": "print(f\"The DOF is {degrees_of_freedom(m)} initially\")", + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The DOF is 0 initially\n" + ] + } + ], + "execution_count": 48 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "Now we can manually deactivate the tear stream, creating a separation between the `Mixer` and `Heater`. This should reduce the degrees of freedom by 10 since the inlet of the `Heater` now contains no values to solve the unit model. To deactivate a stream, simply use `m.fs.s03_expanded.deactivate()`. This expanded stream is just a different version of the `Arc` stream that is able to be deactivated." + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.633917Z", + "start_time": "2025-06-11T22:13:29.610932Z" + } + }, + "cell_type": "code", + "source": [ + "m.fs.s03_expanded.deactivate()\n", + "\n", + "print(f\"The DOF is {degrees_of_freedom(m)} after deactivating the tear stream\")" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The DOF is 10 after deactivating the tear stream\n" + ] + } + ], + "execution_count": 49 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "Now we can provide the `Heater` inlet 10 guess values to bring the degrees of freedom back to 0 and start the manual initialization process. We can run this convenient loop to assign each of these guesses to the inlet of the heater." + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:29.702707Z", + "start_time": "2025-06-11T22:13:29.654459Z" + } + }, + "cell_type": "code", + "source": [ + "tear_guesses = {\n", + " \"flow_mol_phase_comp\": {\n", + " (0, \"Liq\", \"benzene\"): 1e-5,\n", + " (0, \"Liq\", \"toluene\"): 0.30,\n", + " (0, \"Liq\", \"methane\"): 1e-5,\n", + " (0, \"Liq\", \"hydrogen\"): 1e-5,\n", + " (0, \"Vap\", \"benzene\"): 1e-5,\n", + " (0, \"Vap\", \"toluene\"): 1e-5,\n", + " (0, \"Vap\", \"methane\"): 0.02,\n", + " (0, \"Vap\", \"hydrogen\"): 0.30,\n", + "\n", + " },\n", + " \"temperature\": {0: 303},\n", + " \"pressure\": {0: 350000},\n", + "}\n", + "\n", + "for k, v in tear_guesses.items():\n", + " for k1, v1 in v.items():\n", + " getattr(m.fs.s03.destination, k)[k1].fix(v1)\n", + "\n", + "DOF_initial = degrees_of_freedom(m)\n", + "print(f\"The DOF is {degrees_of_freedom(m)} after providing the initial guesses\")" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The DOF is 0 after providing the initial guesses\n" + ] + } + ], + "execution_count": 50 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "The next step is to manually initialize each unit model starting from the `Heater` and then propagate the connection between it and the next unit model. This manual process ensures a strict order to the user's specification if that is desired. The current standard for initializing a unit model is to use an initializer object most compatible for that unit model. This can most often be done by utilizing the `default_initializer()` method attached to the unit model and then to call the `initialize()` method with the unit model as the argument." + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:31.347435Z", + "start_time": "2025-06-11T22:13:29.712721Z" + } + }, + "cell_type": "code", + "source": [ + "m.fs.H101.default_initializer().initialize(m.fs.H101) # Initialize Heater\n", + "propagate_state(m.fs.s04) # Establish connection between Heater and Reactor\n", + "m.fs.R101.default_initializer().initialize(m.fs.R101) # Initialize Reactor\n", + "propagate_state(m.fs.s05) # Establish connection between Reactor and First Flash Unit\n", + "m.fs.F101.default_initializer().initialize(m.fs.F101) # Initialize First Flash Unit\n", + "propagate_state(m.fs.s06) # Establish connection between First Flash Unit and Splitter\n", + "propagate_state(m.fs.s07) # Establish connection between First Flash Unit and Second Flash Unit\n", + "m.fs.S101.default_initializer().initialize(m.fs.S101) # Initialize Splitter\n", + "propagate_state(m.fs.s08) # Establish connection between Splitter and Compressor\n", + "m.fs.C101.default_initializer().initialize(m.fs.C101) # Initialize Compressor\n", + "propagate_state(m.fs.s09) # Establish connection between Compressor and Mixer\n", + "m.fs.I101.default_initializer().initialize(m.fs.I101) # Initialize Toluene Inlet\n", + "propagate_state(m.fs.s01) # Establish connection between Toluene Inlet and Mixer\n", + "m.fs.I102.default_initializer().initialize(m.fs.I102) # Initialize Hydrogen Inlet\n", + "propagate_state(m.fs.s02) # Establish connection between Hydrogen Inlet and Mixer\n", + "m.fs.M101.default_initializer().initialize(m.fs.M101) # Initialize Mixer\n", + "propagate_state(m.fs.s03) # Establish connection between Mixer and Heater\n", + "m.fs.F102.default_initializer().initialize(m.fs.F102) # Initialize Second Flash Unit\n", + "propagate_state(m.fs.s10) # Establish connection between Second Flash Unit and Benzene Product\n", + "propagate_state(m.fs.s11) # Establish connection between Second Flash Unit and Toluene Product\n", + "propagate_state(m.fs.s12) # Establish connection between Splitter and Purge Product\n" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2025-06-11 16:13:29 [INFO] idaes.init.fs.H101.control_volume.properties_in: Initialization Complete\n", + "2025-06-11 16:13:30 [INFO] idaes.init.fs.H101.control_volume.properties_out: Initialization Complete\n", + "2025-06-11 16:13:30 [INFO] idaes.init.fs.R101.control_volume.properties_in: Initialization Complete\n", + "2025-06-11 16:13:30 [INFO] idaes.init.fs.R101.control_volume.properties_out: Initialization Complete\n", + "2025-06-11 16:13:30 [INFO] idaes.init.fs.F101.control_volume.properties_in: Initialization Complete\n", + "2025-06-11 16:13:30 [INFO] idaes.init.fs.F101.control_volume.properties_out: Initialization Complete\n", + "2025-06-11 16:13:30 [INFO] idaes.init.fs.S101.mixed_state: Initialization Complete\n", + "2025-06-11 16:13:30 [INFO] idaes.init.fs.S101.purge_state: Initialization Complete\n", + "2025-06-11 16:13:30 [INFO] idaes.init.fs.S101.recycle_state: Initialization Complete\n", + "2025-06-11 16:13:30 [INFO] idaes.init.fs.S101: Initialization Step 2 Complete: optimal - \n", + "2025-06-11 16:13:30 [INFO] idaes.init.fs.C101.control_volume.properties_in: Initialization Complete\n", + "2025-06-11 16:13:31 [INFO] idaes.init.fs.C101.control_volume.properties_out: Initialization Complete\n", + "2025-06-11 16:13:31 [INFO] idaes.init.fs.I101.properties: Initialization Complete\n", + "2025-06-11 16:13:31 [INFO] idaes.init.fs.I102.properties: Initialization Complete\n", + "2025-06-11 16:13:31 [INFO] idaes.init.fs.M101.inlet_1_state: Initialization Complete\n", + "2025-06-11 16:13:31 [INFO] idaes.init.fs.M101.inlet_2_state: Initialization Complete\n", + "2025-06-11 16:13:31 [INFO] idaes.init.fs.M101.inlet_3_state: Initialization Complete\n", + "2025-06-11 16:13:31 [INFO] idaes.init.fs.M101.mixed_state: Initialization Complete\n", + "2025-06-11 16:13:31 [INFO] idaes.init.fs.M101: Initialization Complete: optimal - \n", + "2025-06-11 16:13:31 [INFO] idaes.init.fs.F102.control_volume.properties_in: Initialization Complete\n", + "2025-06-11 16:13:31 [INFO] idaes.init.fs.F102.control_volume.properties_out: Initialization Complete\n" + ] + } + ], + "execution_count": 51 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "Now we solve the system to allow the outlet of the mixer to reach a converged congruence with the inlet of the heater." + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:31.935414Z", + "start_time": "2025-06-11T22:13:31.357025Z" + } + }, + "cell_type": "code", + "source": [ + "solver = get_solver()\n", + "results = solver.solve(m, tee=True)" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "WARNING: model contains export suffix 'scaling_factor' that contains 40\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "Ipopt 3.13.2: nlp_scaling_method=gradient-based\n", + "tol=1e-06\n", + "max_iter=200\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...: 1112\n", + "Number of nonzeros in inequality constraint Jacobian.: 0\n", + "Number of nonzeros in Lagrangian Hessian.............: 999\n", + "\n", + "Total number of variables............................: 380\n", + " variables with only lower bounds: 0\n", + " variables with lower and upper bounds: 186\n", + " variables with only upper bounds: 0\n", + "Total number of equality constraints.................: 380\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 1.24e+05 0.00e+00 -1.0 0.00e+00 - 0.00e+00 0.00e+00 0\n", + " 1 0.0000000e+00 1.24e+05 2.12e+00 -1.0 1.99e+05 - 1.39e-01 3.68e-04h 10\n", + " 2 0.0000000e+00 1.24e+05 5.49e+00 -1.0 1.99e+05 - 1.43e-01 3.66e-04h 10\n", + " 3 0.0000000e+00 1.24e+05 4.13e+01 -1.0 2.00e+05 - 9.51e-01 3.64e-04h 10\n", + " 4 0.0000000e+00 1.24e+05 7.47e+01 -1.0 2.00e+05 - 1.77e-01 3.63e-04h 10\n", + " 5 0.0000000e+00 1.24e+05 4.07e+02 -1.0 2.01e+05 - 9.90e-01 3.61e-04h 10\n", + " 6 0.0000000e+00 1.24e+05 6.97e+02 -1.0 2.01e+05 - 1.63e-01 3.60e-04h 10\n", + " 7 0.0000000e+00 1.24e+05 3.75e+03 -1.0 2.02e+05 - 9.90e-01 3.58e-04h 10\n", + " 8 0.0000000e+00 1.24e+05 6.44e+03 -1.0 2.02e+05 - 1.63e-01 3.57e-04h 10\n", + " 9 0.0000000e+00 1.24e+05 3.51e+04 -1.0 2.03e+05 - 1.00e+00 3.55e-04h 10\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 10 0.0000000e+00 1.24e+05 6.05e+04 -1.0 2.04e+05 - 1.62e-01 3.54e-04h 10\n", + " 11 0.0000000e+00 2.01e+06 2.73e+05 -1.0 2.04e+05 - 1.00e+00 1.80e-01w 1\n", + " 12 0.0000000e+00 2.01e+06 3.91e+07 -1.0 7.38e+05 - 6.21e-02 5.22e-04w 1\n", + " 13 0.0000000e+00 2.01e+06 6.94e+11 -1.0 7.50e+05 - 9.13e-02 5.14e-06w 1\n", + " 14 0.0000000e+00 1.24e+05 3.32e+05 -1.0 6.22e+04 - 1.00e+00 3.52e-04h 9\n", + " 15 0.0000000e+00 1.24e+05 5.43e+05 -1.0 2.05e+05 - 1.41e-01 3.51e-04h 10\n", + " 16 0.0000000e+00 1.24e+05 3.01e+06 -1.0 2.05e+05 - 1.00e+00 3.49e-04h 10\n", + " 17 0.0000000e+00 1.24e+05 4.76e+06 -1.0 2.06e+05 - 1.28e-01 3.48e-04h 10\n", + " 18 0.0000000e+00 1.24e+05 2.66e+07 -1.0 2.06e+05 - 1.00e+00 3.46e-04h 10\n", + " 19 0.0000000e+00 1.24e+05 4.23e+07 -1.0 2.07e+05 - 1.28e-01 3.45e-04h 10\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 20 0.0000000e+00 1.24e+05 2.38e+08 -1.0 2.08e+05 - 1.00e+00 3.43e-04h 10\n", + " 21 0.0000000e+00 1.24e+05 3.80e+08 -1.0 2.08e+05 - 1.28e-01 3.42e-04h 10\n", + " 22 0.0000000e+00 1.23e+05 2.16e+09 -1.0 2.09e+05 - 1.00e+00 3.40e-04h 10\n", + " 23 0.0000000e+00 1.23e+05 3.45e+09 -1.0 2.09e+05 - 1.28e-01 3.39e-04h 10\n", + " 24 0.0000000e+00 1.96e+06 1.26e+10 -1.0 2.10e+05 - 7.69e-01 1.73e-01w 1\n", + " 25 0.0000000e+00 1.96e+06 1.91e+12 -1.0 7.43e+05 - 6.14e-02 5.08e-04w 1\n", + " 26 0.0000000e+00 1.96e+06 7.01e+16 -1.0 7.55e+05 - 1.84e-01 5.01e-06w 1\n", + " 27 0.0000000e+00 1.23e+05 1.60e+10 -1.0 1.00e+05 - 7.69e-01 3.37e-04h 9\n", + " 28 0.0000000e+00 1.23e+05 2.61e+10 -1.0 2.10e+05 - 1.33e-01 3.36e-04h 10\n", + " 29 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.11e+05 - 1.00e+00 3.35e-04h 10\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 30 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.11e+05 - 1.27e-01 3.33e-04h 10\n", + " 31 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.12e+05 - 5.83e-01 3.32e-04h 10\n", + " 32 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.13e+05 - 1.41e-01 3.30e-04h 10\n", + " 33 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.13e+05 - 1.00e+00 3.29e-04h 10\n", + " 34 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.14e+05 - 1.26e-01 3.28e-04h 10\n", + " 35 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.14e+05 - 6.16e-01 3.26e-04h 10\n", + " 36 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.15e+05 - 1.38e-01 3.25e-04h 10\n", + " 37 0.0000000e+00 1.90e+06 5.27e+11 -1.0 2.15e+05 - 1.00e+00 1.66e-01w 1\n", + " 38 0.0000000e+00 1.90e+06 6.09e+13 -1.0 2.72e+05 - 1.39e-01 1.43e-03w 1\n", + " 39 0.0000000e+00 1.90e+06 1.06e+17 -1.0 7.68e+05 - 9.45e-02 4.82e-06w 1\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 40 0.0000000e+00 1.23e+05 1.04e+11 -1.0 7.68e+05 - 1.00e+00 3.23e-04h 9\n", + " 41 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.16e+05 - 1.26e-01 3.22e-04h 10\n", + " 42 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.17e+05 - 6.03e-01 3.21e-04h 10\n", + " 43 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.17e+05 - 1.38e-01 3.19e-04h 10\n", + " 44 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.18e+05 - 1.00e+00 3.18e-04h 10\n", + " 45 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.18e+05 - 1.25e-01 3.17e-04h 10\n", + " 46 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.19e+05 - 6.03e-01 3.15e-04h 10\n", + " 47 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.20e+05 - 1.38e-01 3.14e-04h 10\n", + " 48 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.20e+05 - 1.00e+00 3.13e-04h 10\n", + " 49 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.21e+05 - 1.25e-01 3.11e-04h 10\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 50 0.0000000e+00 1.85e+06 3.37e+11 -1.0 2.21e+05 - 5.99e-01 1.59e-01w 1\n", + " 51 0.0000000e+00 1.85e+06 5.67e+13 -1.0 7.53e+05 - 6.18e-02 4.82e-04w 1\n", + " 52 0.0000000e+00 1.85e+06 1.08e+17 -1.0 7.64e+05 - 2.20e-01 4.75e-06w 1\n", + " 53 0.0000000e+00 1.23e+05 1.04e+11 -1.0 7.64e+05 - 5.99e-01 3.10e-04h 9\n", + " 54 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.22e+05 - 1.38e-01 3.09e-04h 10\n", + " 55 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.22e+05 - 1.00e+00 3.07e-04h 10\n", + " 56 0.0000000e+00 1.23e+05 1.04e+11 -1.0 2.23e+05 - 1.24e-01 3.06e-04h 10\n", + " 57 0.0000000e+00 1.22e+05 1.04e+11 -1.0 2.24e+05 - 5.97e-01 3.05e-04h 10\n", + " 58 0.0000000e+00 1.22e+05 1.04e+11 -1.0 2.24e+05 - 1.37e-01 3.04e-04h 10\n", + " 59 0.0000000e+00 1.22e+05 1.04e+11 -1.0 2.25e+05 - 1.00e+00 3.02e-04h 10\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 60 0.0000000e+00 1.22e+05 1.04e+11 -1.0 2.25e+05 - 1.24e-01 3.01e-04h 10\n", + " 61 0.0000000e+00 1.22e+05 1.04e+11 -1.0 2.26e+05 - 5.94e-01 3.00e-04h 10\n", + " 62 0.0000000e+00 1.22e+05 1.04e+11 -1.0 2.26e+05 - 1.37e-01 2.98e-04h 10\n", + " 63 0.0000000e+00 1.79e+06 6.03e+11 -1.0 2.27e+05 - 1.00e+00 1.52e-01w 1\n", + " 64 0.0000000e+00 1.79e+06 9.13e+13 -1.0 7.58e+05 - 6.05e-02 4.69e-04w 1\n", + " 65 0.0000000e+00 1.79e+06 1.10e+17 -1.0 7.68e+05 - 1.20e-01 4.63e-06w 1\n", + " 66 0.0000000e+00 1.22e+05 1.04e+11 -1.0 7.69e+05 - 1.00e+00 2.97e-04h 9\n", + " 67 0.0000000e+00 1.22e+05 1.04e+11 -1.0 2.28e+05 - 1.23e-01 2.96e-04h 10\n", + " 68 0.0000000e+00 1.22e+05 1.04e+11 -1.0 2.28e+05 - 5.91e-01 2.95e-04h 10\n", + " 69 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.29e+05 - 1.36e-01 2.93e-04h 10\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 70 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.29e+05 - 1.00e+00 2.92e-04h 10\n", + " 71 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.30e+05 - 1.23e-01 2.91e-04h 10\n", + " 72 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.31e+05 - 5.89e-01 2.90e-04h 10\n", + " 73 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.31e+05 - 1.36e-01 2.89e-04h 10\n", + " 74 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.32e+05 - 1.00e+00 2.87e-04h 10\n", + " 75 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.32e+05 - 1.23e-01 2.86e-04h 10\n", + " 76 0.0000000e+00 1.74e+06 3.75e+11 -1.0 2.33e+05 - 5.86e-01 1.46e-01w 1\n", + " 77 0.0000000e+00 1.74e+06 6.57e+13 -1.0 7.62e+05 - 6.18e-02 4.58e-04w 1\n", + " 78 0.0000000e+00 1.74e+06 1.12e+17 -1.0 7.73e+05 - 2.30e-01 4.51e-06w 1\n", + " 79 0.0000000e+00 1.22e+05 1.05e+11 -1.0 7.73e+05 - 5.86e-01 2.85e-04h 9\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 80 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.34e+05 - 1.36e-01 2.84e-04h 10\n", + " 81 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.34e+05 - 1.00e+00 2.83e-04h 10\n", + " 82 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.35e+05 - 1.22e-01 2.81e-04h 10\n", + " 83 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.35e+05 - 5.83e-01 2.80e-04h 10\n", + " 84 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.36e+05 - 1.35e-01 2.79e-04h 10\n", + " 85 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.37e+05 - 1.00e+00 2.78e-04h 10\n", + " 86 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.37e+05 - 1.22e-01 2.77e-04h 10\n", + " 87 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.38e+05 - 5.81e-01 2.76e-04h 10\n", + " 88 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.38e+05 - 1.35e-01 2.74e-04h 10\n", + " 89 0.0000000e+00 1.69e+06 6.88e+11 -1.0 2.39e+05 - 1.00e+00 1.40e-01w 1\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 90 0.0000000e+00 1.69e+06 1.08e+14 -1.0 7.66e+05 - 6.03e-02 4.46e-04w 1\n", + " 91 0.0000000e+00 1.69e+06 1.14e+17 -1.0 7.77e+05 - 1.22e-01 4.40e-06w 1\n", + " 92 0.0000000e+00 1.22e+05 1.05e+11 -1.0 7.77e+05 - 1.00e+00 2.73e-04h 9\n", + " 93 0.0000000e+00 1.22e+05 1.05e+11 -1.0 2.39e+05 - 1.21e-01 2.72e-04h 10\n", + " 94 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.40e+05 - 5.78e-01 2.71e-04h 10\n", + " 95 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.41e+05 - 1.34e-01 2.70e-04h 10\n", + " 96 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.41e+05 - 1.00e+00 2.69e-04h 10\n", + " 97 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.42e+05 - 1.21e-01 2.68e-04h 10\n", + " 98 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.42e+05 - 5.76e-01 2.67e-04h 10\n", + " 99 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.43e+05 - 1.34e-01 2.65e-04h 10\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 100 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.44e+05 - 1.00e+00 2.64e-04h 10\n", + " 101 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.44e+05 - 1.20e-01 2.63e-04h 10\n", + " 102 0.0000000e+00 1.64e+06 4.17e+11 -1.0 2.45e+05 - 5.73e-01 1.34e-01w 1\n", + " 103 0.0000000e+00 1.64e+06 7.61e+13 -1.0 7.71e+05 - 6.17e-02 4.35e-04w 1\n", + " 104 0.0000000e+00 1.64e+06 1.17e+17 -1.0 7.81e+05 - 2.38e-01 4.29e-06w 1\n", + " 105 0.0000000e+00 1.21e+05 1.05e+11 -1.0 7.81e+05 - 5.73e-01 2.62e-04h 9\n", + " 106 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.45e+05 - 1.34e-01 2.61e-04h 10\n", + " 107 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.46e+05 - 1.00e+00 2.60e-04h 10\n", + " 108 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.47e+05 - 1.20e-01 2.59e-04h 10\n", + " 109 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.47e+05 - 5.71e-01 2.58e-04h 10\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 110 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.48e+05 - 1.33e-01 2.57e-04h 10\n", + " 111 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.49e+05 - 1.00e+00 2.56e-04h 10\n", + " 112 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.49e+05 - 1.19e-01 2.55e-04h 10\n", + " 113 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.50e+05 - 5.68e-01 2.54e-04h 10\n", + " 114 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.50e+05 - 1.33e-01 2.53e-04h 10\n", + " 115 0.0000000e+00 1.59e+06 7.85e+11 -1.0 2.51e+05 - 1.00e+00 1.29e-01w 1\n", + " 116 0.0000000e+00 1.59e+06 1.28e+14 -1.0 7.75e+05 - 6.01e-02 4.25e-04w 1\n", + " 117 0.0000000e+00 1.59e+06 1.19e+17 -1.0 7.85e+05 - 1.23e-01 4.19e-06w 1\n", + " 118 0.0000000e+00 1.21e+05 1.05e+11 -1.0 7.85e+05 - 1.00e+00 2.52e-04h 9\n", + " 119 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.52e+05 - 1.19e-01 2.51e-04h 10\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 120 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.52e+05 - 5.66e-01 2.50e-04h 10\n", + " 121 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.53e+05 - 1.32e-01 2.49e-04h 10\n", + " 122 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.53e+05 - 1.00e+00 2.47e-04h 10\n", + " 123 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.54e+05 - 1.18e-01 4.93e-04h 9\n", + " 124 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.55e+05 - 5.60e-01 4.89e-04h 9\n", + " 125 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.56e+05 - 1.32e-01 4.85e-04h 9\n", + " 126 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.58e+05 - 1.00e+00 4.81e-04h 9\n", + " 127 0.0000000e+00 1.21e+05 1.05e+11 -1.0 2.59e+05 - 1.18e-01 4.77e-04h 9\n", + " 128 0.0000000e+00 1.52e+06 4.74e+11 -1.0 2.60e+05 - 5.55e-01 1.21e-01w 1\n", + " 129 0.0000000e+00 1.52e+06 9.09e+13 -1.0 7.80e+05 - 6.17e-02 4.09e-04w 1\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 130 0.0000000e+00 1.52e+06 1.22e+17 -1.0 7.90e+05 - 2.48e-01 4.04e-06w 1\n", + " 131 0.0000000e+00 1.20e+05 1.05e+11 -1.0 7.90e+05 - 5.55e-01 4.73e-04h 8\n", + " 132 0.0000000e+00 1.20e+05 1.05e+11 -1.0 2.61e+05 - 1.31e-01 4.69e-04h 9\n", + " 133 0.0000000e+00 1.20e+05 1.05e+11 -1.0 2.63e+05 - 1.00e+00 1.86e-03h 7\n", + " 134 0.0000000e+00 1.20e+05 1.05e+11 -1.0 2.67e+05 - 1.16e-01 1.80e-03h 7\n", + " 135 0.0000000e+00 1.20e+05 1.05e+11 -1.0 2.72e+05 - 5.16e-01 1.74e-03h 7\n", + " 136 0.0000000e+00 1.19e+05 1.05e+11 -1.0 2.77e+05 - 1.29e-01 3.38e-03h 6\n", + " 137 0.0000000e+00 1.19e+05 1.05e+11 -1.0 2.87e+05 - 1.00e+00 3.17e-03h 6\n", + " 138 0.0000000e+00 1.19e+05 1.06e+11 -1.0 2.98e+05 - 1.10e-01 2.97e-03h 6\n", + " 139 0.0000000e+00 1.18e+05 1.06e+11 -1.0 3.08e+05 - 4.88e-01 2.79e-03h 6\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 140 0.0000000e+00 1.18e+05 1.07e+11 -1.0 3.18e+05 - 1.22e-01 5.25e-03h 5\n", + " 141 0.0000000e+00 1.01e+06 1.89e+12 -1.0 3.38e+05 - 1.00e+00 7.42e-02w 1\n", + " 142 0.0000000e+00 1.01e+06 3.99e+14 -1.0 8.16e+05 - 5.93e-02 3.07e-04w 1\n", + " 143 0.0000000e+00 1.01e+06 1.56e+17 -1.0 8.24e+05 - 1.44e-01 3.04e-06w 1\n", + " 144 0.0000000e+00 1.17e+05 1.08e+11 -1.0 8.24e+05 - 1.00e+00 4.64e-03h 4\n", + " 145 0.0000000e+00 1.17e+05 1.08e+11 -1.0 3.59e+05 - 1.01e-01 2.06e-03h 6\n", + " 146 0.0000000e+00 1.17e+05 1.08e+11 -1.0 3.69e+05 - 4.67e-01 1.94e-03h 6\n", + " 147 0.0000000e+00 1.17e+05 1.08e+11 -1.0 3.79e+05 - 1.12e-01 1.83e-03h 6\n", + " 148 0.0000000e+00 1.16e+05 1.09e+11 -1.0 3.89e+05 - 1.00e+00 8.66e-04h 7\n", + " 149 0.0000000e+00 1.16e+05 1.09e+11 -1.0 3.94e+05 - 9.61e-02 8.42e-04h 7\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 150 0.0000000e+00 1.16e+05 1.09e+11 -1.0 3.99e+05 - 4.61e-01 4.10e-04h 8\n", + " 151 0.0000000e+00 1.16e+05 1.09e+11 -1.0 4.01e+05 - 1.09e-01 4.04e-04h 8\n", + " 152 0.0000000e+00 1.16e+05 1.09e+11 -1.0 4.04e+05 - 1.00e+00 3.98e-04h 8\n", + " 153 0.0000000e+00 1.16e+05 1.09e+11 -1.0 4.06e+05 - 9.46e-02 9.83e-05h 10\n", + " 154 0.0000000e+00 6.92e+05 1.57e+12 -1.0 4.07e+05 - 4.54e-01 5.01e-02w 1\n", + " 155 0.0000000e+00 6.92e+05 4.69e+14 -1.0 8.35e+05 - 6.23e-02 2.42e-04w 1\n", + " 156 0.0000000e+00 6.92e+05 1.93e+17 -1.0 8.42e+05 - 2.97e-01 2.40e-06w 1\n", + " 157 0.0000000e+00 1.16e+05 1.09e+11 -1.0 8.42e+05 - 4.54e-01 9.79e-05h 9\n", + " 158 0.0000000e+00 1.16e+05 1.09e+11 -1.0 4.08e+05 - 1.09e-01 9.76e-05h 10\n", + " 159 0.0000000e+00 1.16e+05 1.09e+11 -1.0 4.08e+05 - 1.00e+00 9.73e-05h 10\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 160 0.0000000e+00 1.16e+05 1.09e+11 -1.0 4.09e+05 - 9.43e-02 9.70e-05h 10\n", + " 161 0.0000000e+00 1.16e+05 1.09e+11 -1.0 4.09e+05 - 4.54e-01 4.83e-05h 11\n", + " 162 0.0000000e+00 1.16e+05 1.09e+11 -1.0 4.10e+05 - 1.08e-01 4.82e-05h 11\n", + " 163 0.0000000e+00 1.16e+05 1.09e+11 -1.0 4.10e+05 - 1.00e+00 1.20e-05h 13\n", + " 164 0.0000000e+00 1.16e+05 1.09e+11 -1.0 4.10e+05 - 9.42e-02 1.20e-05h 13\n", + " 165 0.0000000e+00 1.16e+05 1.09e+11 -1.0 4.10e+05 - 4.55e-01 9.62e-05h 10\n", + " 166 0.0000000e+00 1.16e+05 1.09e+11 -1.0 4.11e+05 - 1.08e-01 9.59e-05h 10\n", + " 167 0.0000000e+00 6.75e+05 3.70e+12 -1.0 4.11e+05 - 1.00e+00 4.89e-02w 1\n", + " 168 0.0000000e+00 6.74e+05 9.73e+14 -1.0 8.36e+05 - 5.90e-02 2.39e-04w 1\n", + " 169 0.0000000e+00 6.74e+05 1.96e+17 -1.0 8.43e+05 - 1.50e-01 2.37e-06w 1\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 170 0.0000000e+00 1.16e+05 1.09e+11 -1.0 8.43e+05 - 1.00e+00 9.56e-05h 9\n", + " 171 0.0000000e+00 1.16e+05 1.09e+11 -1.0 4.12e+05 - 9.39e-02 2.38e-05h 12\n", + " 172 0.0000000e+00 1.16e+05 1.09e+11 -1.0 4.12e+05 - 4.52e-01 1.19e-05h 13\n", + " 173r 0.0000000e+00 1.16e+05 1.00e+03 0.6 0.00e+00 - 0.00e+00 3.72e-07R 18\n", + " 174r 0.0000000e+00 1.25e+05 1.78e+03 0.6 3.70e+04 - 1.19e-02 1.34e-03f 1\n", + " 175r 0.0000000e+00 1.10e+05 1.54e+03 0.6 7.30e+03 - 3.61e-03 8.33e-03f 1\n", + " 176r 0.0000000e+00 1.02e+05 4.12e+03 0.6 5.25e+03 - 6.33e-02 8.82e-03f 1\n", + " 177r 0.0000000e+00 9.63e+04 4.43e+03 0.6 4.50e+03 - 3.61e-02 2.36e-02f 1\n", + " 178r 0.0000000e+00 9.20e+04 1.63e+04 0.6 3.97e+03 - 2.78e-01 6.99e-02f 1\n", + " 179r 0.0000000e+00 8.79e+04 3.34e+04 0.6 4.66e+02 - 8.68e-01 3.38e-01f 1\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 180r 0.0000000e+00 1.06e+05 3.33e+02 0.6 1.05e+02 - 1.00e+00 1.00e+00f 1\n", + " 181r 0.0000000e+00 1.05e+05 2.26e+02 0.6 2.72e+01 - 1.00e+00 1.00e+00f 1\n", + " 182r 0.0000000e+00 9.53e+04 2.03e+03 -0.1 2.12e+02 - 1.00e+00 7.99e-01f 1\n", + " 183r 0.0000000e+00 8.64e+04 8.95e+01 -0.1 8.37e+01 - 1.00e+00 1.00e+00f 1\n", + " 184r 0.0000000e+00 8.52e+04 9.30e-01 -0.1 4.13e+01 - 1.00e+00 1.00e+00h 1\n", + " 185r 0.0000000e+00 7.70e+04 7.43e+02 -2.2 2.70e+02 - 8.39e-01 7.19e-01f 1\n", + " 186r 0.0000000e+00 7.27e+04 1.42e+03 -2.2 1.95e+03 - 9.32e-01 6.47e-01f 1\n", + " 187r 0.0000000e+00 7.36e+04 5.69e+02 -2.2 4.90e+02 - 9.15e-01 6.73e-01f 1\n", + " 188r 0.0000000e+00 7.91e+04 2.05e+02 -2.2 2.75e+02 - 1.00e+00 8.69e-01f 1\n", + " 189r 0.0000000e+00 7.92e+04 1.27e+03 -2.2 1.37e+02 - 8.68e-01 1.06e-01f 1\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 190r 0.0000000e+00 7.99e+04 8.09e+01 -2.2 1.45e+02 - 1.00e+00 1.00e+00f 1\n", + " 191r 0.0000000e+00 8.00e+04 1.81e+01 -2.2 2.82e+00 - 1.00e+00 1.00e+00f 1\n", + " 192r 0.0000000e+00 8.00e+04 6.93e-02 -2.2 6.43e-02 - 1.00e+00 1.00e+00h 1\n", + " 193r 0.0000000e+00 8.03e+04 1.79e+02 -3.3 1.75e+01 - 9.50e-01 8.57e-01f 1\n", + " 194r 0.0000000e+00 4.29e+04 3.40e+02 -3.3 8.69e+02 - 1.00e+00 6.02e-01f 1\n", + " 195r 0.0000000e+00 1.36e+04 1.76e+01 -3.3 3.71e+02 - 1.00e+00 1.00e+00f 1\n", + " 196r 0.0000000e+00 1.99e+04 5.38e-01 -3.3 7.89e+01 - 1.00e+00 1.00e+00h 1\n", + " 197r 0.0000000e+00 1.73e+04 9.03e-02 -3.3 3.20e+01 - 1.00e+00 1.00e+00h 1\n", + " 198r 0.0000000e+00 1.64e+04 1.32e-02 -3.3 1.07e+01 - 1.00e+00 1.00e+00h 1\n", + " 199r 0.0000000e+00 1.48e+04 9.83e+01 -4.9 2.40e+01 - 8.62e-01 7.80e-01f 1\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 200r 0.0000000e+00 1.44e+04 1.92e+03 -4.9 1.71e+03 - 6.01e-01 1.17e-02f 1\n", + "\n", + "Number of Iterations....: 200\n", + "\n", + " (scaled) (unscaled)\n", + "Objective...............: 0.0000000000000000e+00 0.0000000000000000e+00\n", + "Dual infeasibility......: 8.8349814638582666e+02 8.8349814638582666e+02\n", + "Constraint violation....: 3.3086142754792485e-04 1.4369018418593043e+04\n", + "Complementarity.........: 6.5630241357471754e-04 6.5630241357471754e-04\n", + "Overall NLP error.......: 3.3086142754792485e-04 1.4369018418593043e+04\n", + "\n", + "\n", + "Number of objective function evaluations = 1605\n", + "Number of objective gradient evaluations = 175\n", + "Number of equality constraint evaluations = 1605\n", + "Number of inequality constraint evaluations = 0\n", + "Number of equality constraint Jacobian evaluations = 202\n", + "Number of inequality constraint Jacobian evaluations = 0\n", + "Number of Lagrangian Hessian evaluations = 200\n", + "Total CPU secs in IPOPT (w/o function evaluations) = 0.331\n", + "Total CPU secs in NLP function evaluations = 0.025\n", + "\n", + "EXIT: Maximum Number of Iterations Exceeded.\n", + "WARNING: Loading a SolverResults object with a warning status into\n", + "model.name=\"unknown\";\n", + " - termination condition: maxIterations\n", + " - message from solver: Ipopt 3.13.2\\x3a Maximum Number of Iterations\n", + " Exceeded.\n" + ] + } + ], + "execution_count": 52 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "Now that the flowsheet is initialized, we can unfix the guesses for the `Heater` and reactive the tear stream to complete the final solve." + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:31.967629Z", + "start_time": "2025-06-11T22:13:31.943574Z" + } + }, + "cell_type": "code", + "source": [ + "for k, v in tear_guesses.items():\n", + " for k1, v1 in v.items():\n", + " getattr(m.fs.H101.inlet, k)[k1].unfix()\n", + "\n", + "m.fs.s03_expanded.activate()\n", + "print(f\"The DOF is {degrees_of_freedom(m)} after unfixing the values and reactivating the tear stream\")" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The DOF is 0 after unfixing the values and reactivating the tear stream\n" + ] + } + ], + "execution_count": 53 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "## 6 Solving the Model" + }, + { + "cell_type": "markdown", + "metadata": { + "tags": [] + }, + "source": "We have now initialized the flowsheet. Lets set up some solving options before simulating the flowsheet. We want to specify the scaling method, number of iterations, and tolerance. More specific or advanced options can be found at the documentation for IPOPT https://coin-or.github.io/Ipopt/OPTIONS.html" + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:31.982093Z", + "start_time": "2025-06-11T22:13:31.978929Z" + } + }, + "cell_type": "code", + "source": [ + "optarg = {\n", + " 'nlp_scaling_method': 'user-scaling',\n", + " 'OF_ma57_automatic_scaling': 'yes',\n", + " 'max_iter': 500,\n", + " 'tol': 1e-8,\n", + "}" + ], + "outputs": [], + "execution_count": 54 + }, + { + "metadata": { + "tags": [ + "exercise" + ] + }, + "cell_type": "markdown", + "source": [ + "
\n", + "Inline Exercise:\n", + "Let us run the flowsheet in a simulation mode to look at the results. To do this, complete the last line of code where we pass the model to the solver. You will need to type the following:\n", + "\n", + "solver = get_solver(solver_options=optarg)
\n", + "results = solver.solve(m, tee=True)\n", + "\n", + "Use Shift+Enter to run the cell once you have typed in your code.\n", + "
\n" + ] + }, + { + "cell_type": "code", + "metadata": { + "tags": [ + "exercise" + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:32.012062Z", + "start_time": "2025-06-11T22:13:32.008341Z" + } + }, + "source": [ + "# Create the solver object\n", + "\n", + "# Solve the model" + ], + "outputs": [], + "execution_count": 55 + }, + { + "cell_type": "code", + "metadata": { + "tags": [ + "solution" + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:32.320588Z", + "start_time": "2025-06-11T22:13:32.041379Z" + } + }, + "source": [ + "# Create the solver object\n", + "solver = get_solver(solver_options=optarg)\n", + "\n", + "# Solve the model\n", + "results = solver.solve(m, tee=True)\n" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "WARNING: model contains export suffix 'scaling_factor' that contains 30\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "Ipopt 3.13.2: nlp_scaling_method=user-scaling\n", + "tol=1e-08\n", + "max_iter=500\n", + "option_file_name=C:\\Users\\Tanner\\AppData\\Local\\Temp\\tmp1n1inahr_ipopt.opt\n", + "\n", + "Using option file \"C:\\Users\\Tanner\\AppData\\Local\\Temp\\tmp1n1inahr_ipopt.opt\".\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...: 1163\n", + "Number of nonzeros in inequality constraint Jacobian.: 0\n", + "Number of nonzeros in Lagrangian Hessian.............: 1062\n", + "\n", + "Total number of variables............................: 390\n", + " variables with only lower bounds: 0\n", + " variables with lower and upper bounds: 196\n", + " variables with only upper bounds: 0\n", + "Total number of equality constraints.................: 390\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 7.98e+04 0.00e+00 -1.0 0.00e+00 - 0.00e+00 0.00e+00 0\n", + " 1 0.0000000e+00 7.94e+04 1.91e+01 -1.0 1.01e+05 - 4.59e-03 3.67e-03h 3\n", + " 2 0.0000000e+00 7.91e+04 2.90e+01 -1.0 9.74e+04 - 1.04e-02 2.90e-03h 3\n", + " 3 0.0000000e+00 8.53e+04 3.58e+01 -1.0 1.09e+05 - 3.03e-02 2.27e-03h 3\n", + " 4 0.0000000e+00 9.38e+04 9.70e+01 -1.0 1.22e+05 - 2.53e-02 1.76e-03h 3\n", + " 5 0.0000000e+00 9.86e+04 7.07e+02 -1.0 1.32e+05 - 5.90e-02 1.36e-03h 3\n", + " 6 0.0000000e+00 1.01e+05 5.02e+03 -1.0 1.41e+05 - 2.55e-02 1.04e-03h 3\n", + " 7 0.0000000e+00 1.03e+05 1.80e+05 -1.0 1.47e+05 - 1.27e-01 7.95e-04h 3\n", + " 8 0.0000000e+00 1.04e+05 2.00e+06 -1.0 1.52e+05 - 2.40e-02 6.05e-04h 3\n", + " 9 0.0000000e+00 1.04e+05 1.82e+08 -1.0 1.56e+05 - 1.71e-01 4.59e-04h 3\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 10 0.0000000e+00 1.04e+05 3.34e+09 -1.0 1.59e+05 - 2.46e-02 3.47e-04h 3\n", + " 11 0.0000000e+00 3.24e+05 3.06e+09 -1.0 2.71e-02 10.0 5.85e-01 7.83e-01h 1\n", + " 12 0.0000000e+00 1.46e+06 3.17e+11 -1.0 3.46e+04 - 7.72e-02 1.50e-01f 2\n", + " 13 0.0000000e+00 1.46e+06 2.27e+11 -1.0 1.90e+04 - 3.66e-01 2.41e-03h 3\n", + " 14 0.0000000e+00 1.46e+06 2.32e+12 -1.0 1.90e+04 - 4.42e-01 1.83e-03h 3\n", + " 15 0.0000000e+00 1.45e+06 8.61e+13 -1.0 1.89e+04 - 4.59e-01 1.39e-03h 3\n", + " 16 0.0000000e+00 1.45e+06 5.60e+14 -1.0 1.89e+04 - 3.52e-01 2.10e-03h 2\n", + " 17 0.0000000e+00 1.45e+06 5.54e+14 -1.0 1.88e+04 - 5.07e-01 1.07e-03h 2\n", + " 18 0.0000000e+00 1.45e+06 9.90e+14 -1.0 1.88e+04 - 3.35e-01 1.09e-03h 1\n", + " 19 0.0000000e+00 1.45e+06 9.94e+16 -1.0 1.87e+04 - 9.49e-01 1.09e-05h 1\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 20 0.0000000e+00 1.44e+06 9.35e+18 -1.0 4.45e+03 - 2.73e-01 2.89e-03h 1\n", + " 21r 0.0000000e+00 1.44e+06 1.00e+03 -0.6 0.00e+00 - 0.00e+00 4.43e-07R 17\n", + " 22r 0.0000000e+00 1.89e+06 2.04e+03 -0.6 6.64e+02 - 7.96e-03 2.69e-03f 1\n", + " 23r 0.0000000e+00 1.93e+06 4.07e+03 -0.6 8.47e+02 - 1.23e-02 4.48e-03f 1\n", + " 24r 0.0000000e+00 2.28e+06 1.29e+04 -0.6 9.49e+02 - 4.42e-02 1.55e-02f 1\n", + " 25r 0.0000000e+00 4.06e+06 1.16e+04 -0.6 1.05e+03 - 5.19e-02 5.05e-02f 1\n", + " 26r 0.0000000e+00 4.03e+06 6.56e+03 -0.6 8.19e+02 - 2.50e-03 1.07e-02f 1\n", + " 27r 0.0000000e+00 4.15e+06 4.04e+04 -0.6 6.67e+02 - 9.08e-02 3.63e-02f 1\n", + " 28r 0.0000000e+00 4.11e+06 4.55e+04 -0.6 4.90e+02 - 4.20e-02 3.20e-02f 1\n", + " 29r 0.0000000e+00 4.02e+06 1.82e+05 -0.6 4.75e+02 - 2.06e-01 1.68e-02f 1\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 30r 0.0000000e+00 3.02e+06 8.10e+04 -0.6 4.67e+02 - 6.59e-02 2.37e-01f 1\n", + " 31r 0.0000000e+00 2.70e+06 1.53e+05 -0.6 3.56e+02 - 4.76e-01 4.13e-01f 1\n", + " 32r 0.0000000e+00 2.13e+06 9.21e+04 -0.6 2.09e+02 - 4.07e-01 5.13e-01f 1\n", + " 33r 0.0000000e+00 8.87e+05 5.96e+04 -0.6 1.02e+02 - 5.00e-01 5.95e-01f 1\n", + " 34r 0.0000000e+00 1.36e+05 9.02e+04 -0.6 4.12e+01 - 4.56e-01 1.00e+00f 1\n", + " 35r 0.0000000e+00 4.01e+04 7.97e+04 -0.6 4.66e+00 - 5.29e-01 7.78e-01f 1\n", + " 36r 0.0000000e+00 2.04e+04 5.00e+04 -0.6 4.15e-01 0.0 3.36e-01 7.33e-01h 1\n", + " 37r 0.0000000e+00 1.98e+04 1.42e+04 -0.6 8.68e-02 1.3 1.00e+00 1.72e-01f 1\n", + " 38r 0.0000000e+00 1.85e+04 2.28e+04 -0.6 1.70e+01 - 8.27e-01 5.31e-01f 1\n", + " 39r 0.0000000e+00 2.67e+04 6.30e+03 -0.6 1.08e+01 - 1.00e+00 1.00e+00f 1\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 40r 0.0000000e+00 1.76e+04 3.54e+02 -0.6 3.83e+00 - 1.00e+00 1.00e+00f 1\n", + " 41r 0.0000000e+00 1.66e+04 6.18e+02 -0.6 2.10e+00 - 1.00e+00 1.00e+00f 1\n", + " 42r 0.0000000e+00 1.63e+04 1.63e+00 -0.6 7.37e-02 - 1.00e+00 1.00e+00h 1\n", + " 43r 0.0000000e+00 1.82e+04 9.49e+02 -2.0 8.67e+00 - 8.62e-01 7.60e-01f 1\n", + " 44r 0.0000000e+00 2.02e+04 6.57e+02 -2.0 2.22e+03 - 1.00e+00 7.24e-01f 1\n", + " 45r 0.0000000e+00 1.41e+04 2.64e+01 -2.0 1.93e-02 0.9 1.00e+00 1.00e+00f 1\n", + " 46r 0.0000000e+00 1.41e+04 3.99e+00 -2.0 1.76e-02 0.4 1.00e+00 1.00e+00h 1\n", + " 47r 0.0000000e+00 1.41e+04 5.62e-02 -2.0 3.33e-02 -0.1 1.00e+00 1.00e+00h 1\n", + " 48r 0.0000000e+00 1.42e+04 3.60e+02 -4.4 9.99e-02 -0.6 9.21e-01 7.93e-01f 1\n", + " 49r 0.0000000e+00 4.27e+04 2.99e+03 -4.4 1.29e+00 -1.1 8.82e-01 6.65e-01f 1\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 50r 0.0000000e+00 6.63e+04 1.41e+02 -4.4 3.88e+00 -1.5 9.39e-01 9.16e-01f 1\n", + " 51r 0.0000000e+00 5.62e+04 5.64e+02 -4.4 1.16e+01 -2.0 1.00e+00 1.56e-01f 1\n", + " 52r 0.0000000e+00 5.59e+04 9.07e+02 -4.4 4.50e+04 - 7.98e-02 5.51e-03f 1\n", + " 53r 0.0000000e+00 5.59e+04 1.82e+03 -4.4 2.65e+03 - 1.77e-01 2.25e-05f 1\n", + " 54r 0.0000000e+00 4.87e+04 1.59e+03 -4.4 2.65e+03 - 1.12e-01 1.38e-01f 1\n", + " 55r 0.0000000e+00 4.71e+04 2.05e+03 -4.4 2.28e+03 - 5.83e-01 3.36e-02f 1\n", + " 56r 0.0000000e+00 4.59e+04 2.00e+03 -4.4 2.21e+03 - 2.61e-02 2.52e-02f 1\n", + " 57r 0.0000000e+00 4.21e+04 1.20e+03 -4.4 2.15e+03 - 3.82e-01 9.47e-02f 1\n", + " 58r 0.0000000e+00 3.52e+04 1.00e+03 -4.4 1.95e+03 - 1.89e-01 5.87e-01f 1\n", + " 59r 0.0000000e+00 1.42e+04 3.94e+00 -4.4 8.05e+02 - 1.00e+00 1.00e+00f 1\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 60r 0.0000000e+00 1.42e+04 4.68e-01 -4.4 5.35e-01 - 1.00e+00 1.00e+00h 1\n", + " 61r 0.0000000e+00 1.42e+04 1.29e-02 -4.4 3.24e-03 - 1.00e+00 1.00e+00h 1\n", + " 62r 0.0000000e+00 1.42e+04 5.72e-06 -4.4 5.73e-05 - 1.00e+00 1.00e+00h 1\n", + " 63r 0.0000000e+00 1.42e+04 1.24e+02 -6.6 2.71e-01 - 9.90e-01 8.10e-01f 1\n", + " 64r 0.0000000e+00 8.72e+05 1.88e+02 -6.6 3.29e+04 - 5.03e-01 2.23e-01f 1\n", + " 65r 0.0000000e+00 1.05e+06 2.88e+02 -6.6 2.53e+04 - 6.78e-01 1.82e-01f 1\n", + " 66r 0.0000000e+00 1.04e+06 4.92e+02 -6.6 8.71e+03 - 8.61e-01 1.13e-02f 1\n", + " 67r 0.0000000e+00 6.27e+05 7.17e+01 -6.6 8.61e+03 - 1.00e+00 8.73e-01f 1\n", + " 68r 0.0000000e+00 2.18e+04 1.48e+00 -6.6 1.09e+03 - 1.00e+00 1.00e+00h 1\n", + " 69r 0.0000000e+00 4.71e+02 5.81e-05 -6.6 7.59e-02 - 1.00e+00 1.00e+00h 1\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 70r 0.0000000e+00 4.71e+02 1.03e-08 -6.6 1.22e-05 - 1.00e+00 1.00e+00h 1\n", + " 71r 0.0000000e+00 4.71e+02 8.08e-01 -9.0 4.39e-02 - 1.00e+00 9.74e-01f 1\n", + " 72r 0.0000000e+00 2.45e+03 1.39e+02 -9.0 2.20e+05 - 3.58e-01 2.17e-03f 1\n", + " 73r 0.0000000e+00 2.39e+03 5.81e+02 -9.0 1.06e+04 - 6.57e-01 2.41e-02f 1\n", + " 74r 0.0000000e+00 2.15e+04 7.82e+02 -9.0 1.04e+04 - 1.00e+00 1.42e-01f 1\n", + " 75r 0.0000000e+00 1.15e+04 4.72e+02 -9.0 4.34e+01 - 1.00e+00 4.66e-01h 2\n", + " 76r 0.0000000e+00 6.38e+03 2.52e+02 -9.0 4.28e+01 - 1.00e+00 4.46e-01h 2\n", + " 77r 0.0000000e+00 3.26e+03 1.28e+02 -9.0 2.42e+01 - 1.00e+00 4.89e-01h 2\n", + " 78r 0.0000000e+00 2.99e+03 1.10e+02 -9.0 1.24e+01 - 1.00e+00 1.00e+00h 1\n", + " 79r 0.0000000e+00 9.57e+01 3.79e+00 -9.0 5.11e-03 - 1.00e+00 1.00e+00h 1\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 80r 0.0000000e+00 3.70e-02 1.01e-04 -9.0 3.14e-05 - 1.00e+00 1.00e+00h 1\n", + " 81r 0.0000000e+00 4.15e-06 2.24e-09 -9.0 8.12e-09 - 1.00e+00 1.00e+00h 1\n", + "\n", + "Number of Iterations....: 81\n", + "\n", + " (scaled) (unscaled)\n", + "Objective...............: 0.0000000000000000e+00 0.0000000000000000e+00\n", + "Dual infeasibility......: 0.0000000000000000e+00 0.0000000000000000e+00\n", + "Constraint violation....: 2.0579515874459978e-11 4.1499733924865723e-06\n", + "Complementarity.........: 0.0000000000000000e+00 0.0000000000000000e+00\n", + "Overall NLP error.......: 2.0579515874459978e-11 4.1499733924865723e-06\n", + "\n", + "\n", + "Number of objective function evaluations = 162\n", + "Number of objective gradient evaluations = 23\n", + "Number of equality constraint evaluations = 162\n", + "Number of inequality constraint evaluations = 0\n", + "Number of equality constraint Jacobian evaluations = 83\n", + "Number of inequality constraint Jacobian evaluations = 0\n", + "Number of Lagrangian Hessian evaluations = 81\n", + "Total CPU secs in IPOPT (w/o function evaluations) = 0.119\n", + "Total CPU secs in NLP function evaluations = 0.006\n", + "\n", + "EXIT: Optimal Solution Found.\n" + ] + } + ], + "execution_count": 56 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 7 Analyze the results\n", + "\n", + "\n" + ] + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "If the IDAES UI package was installed with the `idaes-pse` installation or installed separately, you can run the flowsheet visualizer to see a full diagram of the full process that is generated and displayed on a browser window.\n" + }, + { + "metadata": { + "tags": [ + "noauto" + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:33.874186Z", + "start_time": "2025-06-11T22:13:32.362868Z" + } + }, + "cell_type": "code", + "source": "m.fs.visualize('HDA-Flowsheet')", + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2025-06-11 16:13:33 [INFO] idaes.idaes_ui.fv.fsvis: Started visualization server\n", + "2025-06-11 16:13:33 [INFO] idaes.idaes_ui.fv.fsvis: Loading saved flowsheet from 'HDA-Flowsheet.json'\n", + "2025-06-11 16:13:33 [INFO] idaes.idaes_ui.fv.fsvis: Saving flowsheet to default file 'HDA-Flowsheet.json' in current directory (C:\\Users\\Tanner\\Documents\\git\\examples\\idaes_examples\\notebooks\\docs\\tut\\core)\n", + "2025-06-11 16:13:33 [INFO] idaes.idaes_ui.fv.fsvis: Flowsheet visualization at: http://localhost:49167/app?id=HDA-Flowsheet\n" + ] + }, + { + "data": { + "text/plain": [ + "VisualizeResult(store=, port=49167, server=, save_diagram=>)" + ] + }, + "execution_count": 58, + "metadata": {}, + "output_type": "execute_result" + } + ], + "execution_count": 58 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "Otherwise, we can run the `m.fs.report()` method to see a full summary of the solved flowsheet. It is recomended to adjust the width of the output as much as possible for the cleanest display." + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:33.985892Z", + "start_time": "2025-06-11T22:13:33.889113Z" + } + }, + "cell_type": "code", + "source": "m.fs.report()", + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "====================================================================================\n", + "Flowsheet : fs Time: 0.0\n", + "------------------------------------------------------------------------------------\n", + " Stream Table\n", + " Units s01 s02 s03 s04 s05 s06 s07 s08 s09 s10 s11 s12 \n", + " flow_mol_phase_comp ('Liq', 'benzene') mole / second 1.0000e-05 1.0000e-05 2.0008e-05 1.2993e-07 1.2993e-07 1.0000e-08 0.20460 8.0000e-09 8.0000e-09 1.0000e-08 0.062620 2.0000e-09\n", + " flow_mol_phase_comp ('Liq', 'toluene') mole / second 0.30000 1.0000e-05 0.30001 8.4149e-07 8.4149e-07 1.0000e-08 0.062520 8.0000e-09 8.0000e-09 1.0000e-08 0.032257 2.0000e-09\n", + " flow_mol_phase_comp ('Liq', 'methane') mole / second 1.0000e-05 1.0000e-05 2.0008e-05 1.0000e-12 1.0000e-12 1.0000e-08 2.6712e-07 8.0000e-09 8.0000e-09 1.0000e-08 9.4877e-08 2.0000e-09\n", + " flow_mol_phase_comp ('Liq', 'hydrogen') mole / second 1.0000e-05 1.0000e-05 2.0008e-05 1.0000e-12 1.0000e-12 1.0000e-08 2.6712e-07 8.0000e-09 8.0000e-09 1.0000e-08 9.4877e-08 2.0000e-09\n", + " flow_mol_phase_comp ('Vap', 'benzene') mole / second 1.0000e-05 1.0000e-05 0.11934 0.11936 0.35374 0.14915 1.0000e-08 0.11932 0.11932 0.14198 1.0000e-08 0.029829\n", + " flow_mol_phase_comp ('Vap', 'toluene') mole / second 1.0000e-05 1.0000e-05 0.012508 0.31252 0.078129 0.015610 1.0000e-08 0.012488 0.012488 0.030264 1.0000e-08 0.0031219\n", + " flow_mol_phase_comp ('Vap', 'methane') mole / second 1.0000e-05 0.020000 1.0377 1.0377 1.2721 1.2721 1.0000e-08 1.0177 1.0177 1.8224e-07 1.0000e-08 0.25442\n", + " flow_mol_phase_comp ('Vap', 'hydrogen') mole / second 1.0000e-05 0.30000 0.56258 0.56260 0.32821 0.32821 1.0000e-08 0.26257 0.26257 1.8224e-07 1.0000e-08 0.065642\n", + " temperature kelvin 303.20 303.20 314.09 600.00 771.85 325.00 325.00 325.00 325.00 375.00 375.00 325.00\n", + " pressure pascal 3.5000e+05 3.5000e+05 3.5000e+05 3.5000e+05 3.5000e+05 3.5000e+05 3.5000e+05 3.5000e+05 3.5000e+05 1.5000e+05 1.5000e+05 3.5000e+05\n", + "====================================================================================\n" + ] + } + ], + "execution_count": 59 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "What is the total operating cost?" + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:34.015352Z", + "start_time": "2025-06-11T22:13:34.012518Z" + } + }, + "source": [ + "print(\"operating cost = $\", value(m.fs.operating_cost))" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "operating cost = $ 419122.33874473866\n" + ] + } + ], + "execution_count": 60 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": "For this operating cost, what is the amount of benzene we are able to produce and what purity we are able to achieve? We can look at a specific unit models stream table with the same `report()` method." + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:34.197557Z", + "start_time": "2025-06-11T22:13:34.174732Z" + } + }, + "source": [ + "m.fs.F102.report()\n", + "\n", + "print()\n", + "print(\"benzene purity = \", value(m.fs.purity))" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "====================================================================================\n", + "Unit : fs.F102 Time: 0.0\n", + "------------------------------------------------------------------------------------\n", + " Unit Performance\n", + "\n", + " Variables: \n", + "\n", + " Key : Value : Units : Fixed : Bounds\n", + " Heat Duty : 7352.5 : watt : False : (None, None)\n", + " Pressure Change : -2.0000e+05 : pascal : True : (None, None)\n", + "\n", + "------------------------------------------------------------------------------------\n", + " Stream Table\n", + " Units Inlet Vapor Outlet Liquid Outlet\n", + " flow_mol_phase_comp ('Liq', 'benzene') mole / second 0.20460 1.0000e-08 0.062620 \n", + " flow_mol_phase_comp ('Liq', 'toluene') mole / second 0.062520 1.0000e-08 0.032257 \n", + " flow_mol_phase_comp ('Liq', 'methane') mole / second 2.6712e-07 1.0000e-08 9.4877e-08 \n", + " flow_mol_phase_comp ('Liq', 'hydrogen') mole / second 2.6712e-07 1.0000e-08 9.4877e-08 \n", + " flow_mol_phase_comp ('Vap', 'benzene') mole / second 1.0000e-08 0.14198 1.0000e-08 \n", + " flow_mol_phase_comp ('Vap', 'toluene') mole / second 1.0000e-08 0.030264 1.0000e-08 \n", + " flow_mol_phase_comp ('Vap', 'methane') mole / second 1.0000e-08 1.8224e-07 1.0000e-08 \n", + " flow_mol_phase_comp ('Vap', 'hydrogen') mole / second 1.0000e-08 1.8224e-07 1.0000e-08 \n", + " temperature kelvin 325.00 375.00 375.00 \n", + " pressure pascal 3.5000e+05 1.5000e+05 1.5000e+05 \n", + "====================================================================================\n", + "\n", + "benzene purity = 0.8242962943922332\n" + ] + } + ], + "execution_count": 62 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": "Next, let's look at how much benzene we are losing with the light gases out of F101. IDAES has tools for creating stream tables based on the `Arcs` and/or `Ports` in a flowsheet. Let us create and print a simple stream table showing the stream leaving the reactor and the vapor stream from F101." + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:34.253529Z", + "start_time": "2025-06-11T22:13:34.238100Z" + } + }, + "source": [ + "from idaes.core.util.tables import (\n", + " create_stream_table_dataframe,\n", + " stream_table_dataframe_to_string,\n", + ")\n", + "\n", + "st = create_stream_table_dataframe({\"Reactor\": m.fs.s05, \"Light Gases\": m.fs.s06})\n", + "print(stream_table_dataframe_to_string(st))" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " Units Reactor Light Gases\n", + "flow_mol_phase_comp ('Liq', 'benzene') mole / second 1.2993e-07 1.0000e-08 \n", + "flow_mol_phase_comp ('Liq', 'toluene') mole / second 8.4149e-07 1.0000e-08 \n", + "flow_mol_phase_comp ('Liq', 'methane') mole / second 1.0000e-12 1.0000e-08 \n", + "flow_mol_phase_comp ('Liq', 'hydrogen') mole / second 1.0000e-12 1.0000e-08 \n", + "flow_mol_phase_comp ('Vap', 'benzene') mole / second 0.35374 0.14915 \n", + "flow_mol_phase_comp ('Vap', 'toluene') mole / second 0.078129 0.015610 \n", + "flow_mol_phase_comp ('Vap', 'methane') mole / second 1.2721 1.2721 \n", + "flow_mol_phase_comp ('Vap', 'hydrogen') mole / second 0.32821 0.32821 \n", + "temperature kelvin 771.85 325.00 \n", + "pressure pascal 3.5000e+05 3.5000e+05 \n" + ] + } + ], + "execution_count": 64 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 8 Optimization\n", + "\n", + "\n", + "We saw from the results above that the total operating cost for the base case was $419,122 per year. We are producing 0.142 mol/s of benzene at a purity of 82\\%. However, we are losing around 42\\% of benzene in F101 vapor outlet stream. \n", + "\n", + "Let us try to minimize this cost such that:\n", + "- we are producing at least 0.15 mol/s of benzene in F102 vapor outlet i.e. our product stream\n", + "- purity of benzene i.e. the mole fraction of benzene in F102 vapor outlet is at least 80%\n", + "- restricting the benzene loss in F101 vapor outlet to less than 20%\n", + "\n", + "For this problem, our decision variables are as follows:\n", + "- H101 outlet temperature\n", + "- R101 cooling duty provided\n", + "- F101 outlet temperature\n", + "- F102 outlet temperature\n", + "- F102 deltaP in the flash tank\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let us declare our objective function for this problem. " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:34.276719Z", + "start_time": "2025-06-11T22:13:34.271416Z" + } + }, + "source": [ + "m.fs.objective = Objective(expr=m.fs.operating_cost)" + ], + "outputs": [], + "execution_count": 65 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, we need to unfix the decision variables as we had solved a square problem (degrees of freedom = 0) until now. " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:34.337438Z", + "start_time": "2025-06-11T22:13:34.332415Z" + } + }, + "source": [ + "m.fs.H101.outlet.temperature.unfix()\n", + "m.fs.R101.heat_duty.unfix()\n", + "m.fs.F101.vap_outlet.temperature.unfix()\n", + "m.fs.F102.vap_outlet.temperature.unfix()" + ], + "outputs": [], + "execution_count": 66 + }, + { + "cell_type": "markdown", + "metadata": { + "tags": [ + "exercise" + ] + }, + "source": [ + "
\n", + "Inline Exercise:\n", + "Let us now unfix the remaining variable which is F102 pressure drop (F102.deltaP) \n", + "\n", + "Use Shift+Enter to run the cell once you have typed in your code. \n", + "
\n", + "\n" + ] + }, + { + "cell_type": "code", + "metadata": { + "tags": [ + "exercise" + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:34.399247Z", + "start_time": "2025-06-11T22:13:34.395221Z" + } + }, + "source": [ + "# Todo: Unfix deltaP for F102" + ], + "outputs": [], + "execution_count": 67 + }, + { + "cell_type": "code", + "metadata": { + "tags": [ + "solution" + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:34.463653Z", + "start_time": "2025-06-11T22:13:34.459960Z" + } + }, + "source": [ + "# Todo: Unfix deltaP for F102\n", + "m.fs.F102.deltaP.unfix()" + ], + "outputs": [], + "execution_count": 68 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next, we need to set bounds on these decision variables to values shown below:\n", + "\n", + " - H101 outlet temperature [500, 600] K\n", + " - R101 outlet temperature [600, 800] K\n", + " - F101 outlet temperature [298, 450] K\n", + " - F102 outlet temperature [298, 450] K\n", + " - F102 outlet pressure [105000, 110000] Pa\n", + "\n", + "Let us first set the variable bound for the H101 outlet temperature as shown below:" + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:34.589902Z", + "start_time": "2025-06-11T22:13:34.585528Z" + } + }, + "source": [ + "m.fs.H101.outlet.temperature[0].setlb(500)\n", + "m.fs.H101.outlet.temperature[0].setub(600)" + ], + "outputs": [], + "execution_count": 70 + }, + { + "cell_type": "markdown", + "metadata": { + "tags": [ + "exercise" + ] + }, + "source": [ + "
\n", + "Inline Exercise:\n", + "Now, set the variable bound for the R101 outlet temperature.\n", + "\n", + "Use Shift+Enter to run the cell once you have typed in your code. \n", + "
" + ] + }, + { + "cell_type": "code", + "metadata": { + "tags": [ + "exercise" + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:34.678092Z", + "start_time": "2025-06-11T22:13:34.673513Z" + } + }, + "source": [ + "# Todo: Set the bounds for reactor outlet temperature" + ], + "outputs": [], + "execution_count": 71 + }, + { + "cell_type": "code", + "metadata": { + "tags": [ + "solution" + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:34.713177Z", + "start_time": "2025-06-11T22:13:34.709843Z" + } + }, + "source": [ + "# Todo: Set the bounds for reactor outlet temperature\n", + "m.fs.R101.outlet.temperature[0].setlb(600)\n", + "m.fs.R101.outlet.temperature[0].setub(800)" + ], + "outputs": [], + "execution_count": 72 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let us fix the bounds for the rest of the decision variables. " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:34.763569Z", + "start_time": "2025-06-11T22:13:34.759916Z" + } + }, + "source": [ + "m.fs.F101.vap_outlet.temperature[0].setlb(298.0)\n", + "m.fs.F101.vap_outlet.temperature[0].setub(450.0)\n", + "m.fs.F102.vap_outlet.temperature[0].setlb(298.0)\n", + "m.fs.F102.vap_outlet.temperature[0].setub(450.0)\n", + "m.fs.F102.vap_outlet.pressure[0].setlb(105000)\n", + "m.fs.F102.vap_outlet.pressure[0].setub(110000)" + ], + "outputs": [], + "execution_count": 73 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, the only things left to define are our constraints on overhead loss in F101, product flow rate and purity in F102. Let us first look at defining a constraint for the overhead loss in F101 where we are restricting the benzene leaving the vapor stream to less than 20 \\% of the benzene available in the reactor outlet. " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:34.806470Z", + "start_time": "2025-06-11T22:13:34.801455Z" + } + }, + "source": [ + "m.fs.overhead_loss = Constraint(\n", + " expr=m.fs.F101.vap_outlet.flow_mol_phase_comp[0, \"Vap\", \"benzene\"]\n", + " <= 0.20 * m.fs.R101.outlet.flow_mol_phase_comp[0, \"Vap\", \"benzene\"]\n", + ")" + ], + "outputs": [], + "execution_count": 74 + }, + { + "cell_type": "markdown", + "metadata": { + "tags": [ + "exercise" + ] + }, + "source": [ + "
\n", + "Inline Exercise:\n", + "Now, add the constraint such that we are producing at least 0.15 mol/s of benzene in the product stream which is the vapor outlet of F102. Let us name this constraint as m.fs.product_flow. \n", + "\n", + "Use Shift+Enter to run the cell once you have typed in your code. \n", + "
" + ] + }, + { + "cell_type": "code", + "metadata": { + "tags": [ + "exercise" + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:34.837136Z", + "start_time": "2025-06-11T22:13:34.832491Z" + } + }, + "source": [ + "# Todo: Add minimum product flow constraint" + ], + "outputs": [], + "execution_count": 75 + }, + { + "cell_type": "code", + "metadata": { + "tags": [ + "solution" + ], + "ExecuteTime": { + "end_time": "2025-06-11T22:13:34.887385Z", + "start_time": "2025-06-11T22:13:34.882862Z" + } + }, + "source": [ + "# Todo: Add minimum product flow constraint\n", + "m.fs.product_flow = Constraint(\n", + " expr=m.fs.F102.vap_outlet.flow_mol_phase_comp[0, \"Vap\", \"benzene\"] >= 0.15\n", + ")" + ], + "outputs": [], + "execution_count": 76 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let us add the final constraint on product purity or the mole fraction of benzene in the product stream such that it is at least greater than 80%. " + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:34.917217Z", + "start_time": "2025-06-11T22:13:34.912683Z" + } + }, + "source": [ + "m.fs.product_purity = Constraint(expr=m.fs.purity >= 0.80)" + ], + "outputs": [], + "execution_count": 77 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "We have now defined the optimization problem and we are now ready to solve this problem. \n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:35.209235Z", + "start_time": "2025-06-11T22:13:34.962108Z" + } + }, + "source": [ + "results = solver.solve(m, tee=True)" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "WARNING: model contains export suffix 'scaling_factor' that contains 25\n", + "component keys that are not exported as part of the NL file. Skipping.\n", + "Ipopt 3.13.2: nlp_scaling_method=user-scaling\n", + "tol=1e-08\n", + "max_iter=500\n", + "option_file_name=C:\\Users\\Tanner\\AppData\\Local\\Temp\\tmpd6aww4bg_ipopt.opt\n", + "\n", + "Using option file \"C:\\Users\\Tanner\\AppData\\Local\\Temp\\tmpd6aww4bg_ipopt.opt\".\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...: 1191\n", + "Number of nonzeros in inequality constraint Jacobian.: 5\n", + "Number of nonzeros in Lagrangian Hessian.............: 1065\n", + "\n", + "Total number of variables............................: 395\n", + " variables with only lower bounds: 0\n", + " variables with lower and upper bounds: 199\n", + " variables with only upper bounds: 0\n", + "Total number of equality constraints.................: 390\n", + "Total number of inequality constraints...............: 3\n", + " inequality constraints with only lower bounds: 2\n", + " inequality constraints with lower and upper bounds: 0\n", + " inequality constraints with only upper bounds: 1\n", + "\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 0 4.1912234e+05 2.99e+05 6.94e+00 -1.0 0.00e+00 - 0.00e+00 0.00e+00 0\n", + " 1 4.1133558e+05 2.94e+05 6.94e+00 -1.0 6.60e+07 - 2.90e-06 1.05e-05f 1\n", + " 2 3.2484037e+05 1.80e+06 6.94e+00 -1.0 2.33e+09 - 2.95e-07 4.87e-06f 1\n", + " 3 3.0318192e+05 1.92e+06 6.94e+00 -1.0 6.90e+08 - 1.70e-05 4.13e-06f 1\n", + " 4 3.0263181e+05 1.92e+06 2.20e+01 -1.0 1.43e+07 - 8.92e-05 1.73e-05f 1\n", + " 5 3.0137102e+05 1.92e+06 4.38e+01 -1.0 8.74e+06 - 6.63e-05 1.11e-04f 1\n", + " 6 3.0058759e+05 1.92e+06 1.37e+03 -1.0 2.21e+07 - 8.59e-06 2.17e-04f 1\n", + " 7 3.0058113e+05 1.92e+06 7.51e+04 -1.0 3.24e+05 - 2.49e-01 1.77e-04f 1\n", + " 8 3.0317331e+05 1.38e+06 2.08e+05 -1.0 4.00e+04 - 9.62e-01 2.82e-01h 1\n", + " 9 3.0372038e+05 1.26e+06 1.91e+05 -1.0 2.87e+04 - 1.50e-01 8.31e-02h 1\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 10 3.0372988e+05 1.26e+06 9.32e+05 -1.0 2.63e+04 - 1.00e+00 1.51e-03h 1\n", + " 11 3.0386427e+05 1.24e+06 1.90e+07 -1.0 2.63e+04 - 1.00e+00 2.03e-02h 2\n", + " 12 3.0393928e+05 1.22e+06 7.46e+08 -1.0 2.58e+04 - 1.00e+00 1.15e-02h 2\n", + " 13 3.0397909e+05 1.21e+06 4.87e+10 -1.0 2.55e+04 - 1.00e+00 6.13e-03h 2\n", + " 14 3.0399961e+05 1.21e+06 5.05e+12 -1.0 2.53e+04 - 1.00e+00 3.17e-03h 2\n", + " 15r 3.0399961e+05 1.21e+06 1.00e+03 -0.5 0.00e+00 - 0.00e+00 3.97e-07R 14\n", + " 16r 3.0399912e+05 1.20e+06 2.84e+04 -0.5 8.47e+03 - 4.43e-03 2.34e-03f 1\n", + " 17 3.0399884e+05 1.20e+06 5.51e+05 -1.0 2.92e+04 - 2.81e-01 2.34e-06f 2\n", + " 18 3.0402874e+05 1.19e+06 4.77e+07 -1.0 2.52e+04 - 9.90e-01 4.64e-03h 1\n", + " 19 3.0402891e+05 1.19e+06 3.49e+10 -1.0 2.51e+04 - 1.00e+00 2.64e-05h 2\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 20r 3.0402891e+05 1.19e+06 1.00e+03 -0.8 0.00e+00 - 0.00e+00 3.08e-07R 8\n", + " 21r 3.0403760e+05 1.15e+06 2.58e+04 -0.8 3.66e+03 - 5.75e-03 3.60e-03f 1\n", + " 22r 3.0404498e+05 1.08e+06 3.87e+04 -0.8 3.65e+03 - 7.70e-03 6.44e-03f 1\n", + " 23r 3.0405749e+05 9.30e+05 4.54e+04 -0.8 3.63e+03 - 1.80e-02 1.60e-02f 1\n", + " 24r 3.0406463e+05 7.06e+05 4.01e+04 -0.8 3.57e+03 - 5.64e-02 3.23e-02f 1\n", + " 25r 3.0405635e+05 4.00e+05 4.80e+04 -0.8 3.45e+03 - 1.05e-01 8.20e-02f 1\n", + " 26r 3.0403684e+05 2.04e+05 2.80e+04 -0.8 3.17e+03 - 3.66e-01 1.49e-01f 1\n", + " 27r 3.0401302e+05 2.95e+04 2.21e+04 -0.8 2.70e+03 - 1.94e-01 6.28e-01f 1\n", + " 28 3.0386768e+05 2.95e+04 1.26e+02 -1.0 3.39e+06 - 4.53e-04 3.54e-06f 1\n", + " 29 3.0384876e+05 2.95e+04 8.45e+03 -1.0 1.90e+05 - 3.01e-02 3.65e-04f 1\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 30 3.0412267e+05 2.86e+04 2.69e+05 -1.0 2.35e+04 - 9.90e-01 3.11e-02h 1\n", + " 31 3.0843430e+05 5.11e+05 1.73e+05 -1.0 2.16e+04 - 1.00e+00 4.99e-01h 1\n", + " 32 3.1273887e+05 6.10e+05 5.58e+08 -1.0 1.09e+04 - 1.00e+00 9.90e-01h 1\n", + " 33 3.1276263e+05 3.11e+05 1.78e+08 -1.0 1.01e+03 - 1.00e+00 4.97e-01h 2\n", + " 34 3.1278673e+05 7.74e+02 1.15e+08 -1.0 6.27e+02 - 1.00e+00 9.97e-01H 1\n", + " 35 3.1278674e+05 6.28e+02 5.45e+04 -1.0 1.81e+02 - 1.00e+00 1.00e+00h 1\n", + " 36 3.1278674e+05 8.80e-03 2.62e+00 -1.0 7.71e-01 - 1.00e+00 1.00e+00h 1\n", + " 37 3.1278634e+05 3.48e-05 1.58e+05 -5.7 1.36e+00 - 1.00e+00 1.00e+00f 1\n", + " 38 3.1278634e+05 3.73e-08 6.73e-02 -5.7 2.20e-04 - 1.00e+00 1.00e+00f 1\n", + " 39 3.1278634e+05 2.24e-08 4.40e-05 -5.7 8.48e-04 - 1.00e+00 1.00e+00h 1\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 40 3.1278634e+05 6.71e-08 6.16e-05 -8.6 1.75e-03 - 1.00e+00 1.00e+00h 1\n", + " 41 3.1278634e+05 3.73e-08 4.40e-05 -9.0 8.38e-04 - 1.00e+00 1.00e+00h 1\n", + " 42 3.1278634e+05 5.96e-08 4.40e-05 -9.0 1.47e-03 - 1.00e+00 1.00e+00h 1\n", + " 43 3.1278634e+05 1.49e-08 4.40e-05 -9.0 5.14e-08 - 1.00e+00 1.00e+00h 1\n", + " 44 3.1278634e+05 1.49e-08 4.40e-05 -9.0 7.39e-11 - 1.00e+00 2.44e-04h 13\n", + " 45 3.1278634e+05 1.49e-08 4.40e-05 -9.0 6.89e-11 - 1.00e+00 5.00e-01h 2\n", + " 46 3.1278634e+05 1.49e-08 4.40e-05 -9.0 7.02e-11 - 1.00e+00 1.00e+00h 1\n", + " 47 3.1278634e+05 1.49e-08 4.40e-05 -9.0 6.71e-11 - 1.00e+00 5.00e-01h 2\n", + " 48 3.1278634e+05 2.98e-08 4.40e-05 -9.0 5.67e-11 - 1.00e+00 1.00e+00h 1\n", + " 49 3.1278634e+05 2.98e-08 4.40e-05 -9.0 5.52e-11 - 1.00e+00 5.00e-01h 2\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 50 3.1278634e+05 2.98e-08 4.40e-05 -9.0 4.16e-11 - 1.00e+00 5.00e-01h 2\n", + " 51 3.1278634e+05 2.98e-08 4.40e-05 -9.0 3.65e-11 - 1.00e+00 1.25e-01h 4\n", + "\n", + "Number of Iterations....: 51\n", + "\n", + " (scaled) (unscaled)\n", + "Objective...............: 3.1278633834066673e+05 3.1278633834066673e+05\n", + "Dual infeasibility......: 4.3988857412862944e-05 6.3035118071624454e-05\n", + "Constraint violation....: 2.0579515874459978e-11 2.9802322387695312e-08\n", + "Complementarity.........: 9.2191617461622074e-10 9.2191617461622074e-10\n", + "Overall NLP error.......: 1.6340396115140405e-08 6.3035118071624454e-05\n", + "\n", + "\n", + "Number of objective function evaluations = 141\n", + "Number of objective gradient evaluations = 47\n", + "Number of equality constraint evaluations = 141\n", + "Number of inequality constraint evaluations = 141\n", + "Number of equality constraint Jacobian evaluations = 55\n", + "Number of inequality constraint Jacobian evaluations = 55\n", + "Number of Lagrangian Hessian evaluations = 52\n", + "Total CPU secs in IPOPT (w/o function evaluations) = 0.077\n", + "Total CPU secs in NLP function evaluations = 0.010\n", + "\n", + "EXIT: Solved To Acceptable Level.\n" + ] + } + ], + "execution_count": 78 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 8.1 Optimization Results\n", + "\n", + "Display the results and product specifications" + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:35.321235Z", + "start_time": "2025-06-11T22:13:35.245545Z" + } + }, + "source": [ + "print(\"operating cost = $\", value(m.fs.operating_cost))\n", + "\n", + "print()\n", + "print(\"Product flow rate and purity in F102\")\n", + "\n", + "m.fs.F102.report()\n", + "\n", + "print()\n", + "print(\"benzene purity = \", value(m.fs.purity))\n", + "\n", + "print()\n", + "print(\"Overhead loss in F101\")\n", + "m.fs.F101.report()" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "operating cost = $ 312786.33834066667\n", + "\n", + "Product flow rate and purity in F102\n", + "\n", + "====================================================================================\n", + "Unit : fs.F102 Time: 0.0\n", + "------------------------------------------------------------------------------------\n", + " Unit Performance\n", + "\n", + " Variables: \n", + "\n", + " Key : Value : Units : Fixed : Bounds\n", + " Heat Duty : 8377.0 : watt : False : (None, None)\n", + " Pressure Change : -2.4500e+05 : pascal : False : (None, None)\n", + "\n", + "------------------------------------------------------------------------------------\n", + " Stream Table\n", + " Units Inlet Vapor Outlet Liquid Outlet\n", + " flow_mol_phase_comp ('Liq', 'benzene') mole / second 0.21743 1.0000e-08 0.067425 \n", + " flow_mol_phase_comp ('Liq', 'toluene') mole / second 0.070695 1.0000e-08 0.037507 \n", + " flow_mol_phase_comp ('Liq', 'methane') mole / second 2.8812e-07 1.0000e-08 1.0493e-07 \n", + " flow_mol_phase_comp ('Liq', 'hydrogen') mole / second 2.8812e-07 1.0000e-08 1.0493e-07 \n", + " flow_mol_phase_comp ('Vap', 'benzene') mole / second 1.0000e-08 0.15000 1.0000e-08 \n", + " flow_mol_phase_comp ('Vap', 'toluene') mole / second 1.0000e-08 0.033189 1.0000e-08 \n", + " flow_mol_phase_comp ('Vap', 'methane') mole / second 1.0000e-08 1.9319e-07 1.0000e-08 \n", + " flow_mol_phase_comp ('Vap', 'hydrogen') mole / second 1.0000e-08 1.9319e-07 1.0000e-08 \n", + " temperature kelvin 301.88 362.93 362.93 \n", + " pressure pascal 3.5000e+05 1.0500e+05 1.0500e+05 \n", + "====================================================================================\n", + "\n", + "benzene purity = 0.8188276578115882\n", + "\n", + "Overhead loss in F101\n", + "\n", + "====================================================================================\n", + "Unit : fs.F101 Time: 0.0\n", + "------------------------------------------------------------------------------------\n", + " Unit Performance\n", + "\n", + " Variables: \n", + "\n", + " Key : Value : Units : Fixed : Bounds\n", + " Heat Duty : -56353. : watt : False : (None, None)\n", + " Pressure Change : 0.0000 : pascal : True : (None, None)\n", + "\n", + "------------------------------------------------------------------------------------\n", + " Stream Table\n", + " Units Inlet Vapor Outlet Liquid Outlet\n", + " flow_mol_phase_comp ('Liq', 'benzene') mole / second 4.3534e-08 1.0000e-08 0.21743 \n", + " flow_mol_phase_comp ('Liq', 'toluene') mole / second 7.5866e-07 1.0000e-08 0.070695 \n", + " flow_mol_phase_comp ('Liq', 'methane') mole / second 1.0000e-12 1.0000e-08 2.8812e-07 \n", + " flow_mol_phase_comp ('Liq', 'hydrogen') mole / second 1.0000e-12 1.0000e-08 2.8812e-07 \n", + " flow_mol_phase_comp ('Vap', 'benzene') mole / second 0.27178 0.054356 1.0000e-08 \n", + " flow_mol_phase_comp ('Vap', 'toluene') mole / second 0.076085 0.0053908 1.0000e-08 \n", + " flow_mol_phase_comp ('Vap', 'methane') mole / second 1.2414 1.2414 1.0000e-08 \n", + " flow_mol_phase_comp ('Vap', 'hydrogen') mole / second 0.35887 0.35887 1.0000e-08 \n", + " temperature kelvin 696.11 301.88 301.88 \n", + " pressure pascal 3.5000e+05 3.5000e+05 3.5000e+05 \n", + "====================================================================================\n" + ] + } + ], + "execution_count": 80 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Display optimal values for the decision variables" + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-06-11T22:13:35.405538Z", + "start_time": "2025-06-11T22:13:35.398221Z" + } + }, + "source": [ + "print(f'''Optimal Values:\n", + "\n", + "H101 outlet temperature = {value(m.fs.H101.outlet.temperature[0]):.3f} K\n", + "\n", + "R101 outlet temperature = {value(m.fs.R101.outlet.temperature[0]):.3f} K\n", + "\n", + "F101 outlet temperature = {value(m.fs.F101.vap_outlet.temperature[0]):.3f} K\n", + "\n", + "F102 outlet temperature = {value(m.fs.F102.vap_outlet.temperature[0]):.3f} K\n", + "F102 outlet pressure = {value(m.fs.F102.vap_outlet.pressure[0]):.3f} Pa\n", + "''')" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Optimal Values:\n", + "\n", + "H101 outlet temperature = 500.000 K\n", + "\n", + "R101 outlet temperature = 696.112 K\n", + "\n", + "F101 outlet temperature = 301.878 K\n", + "\n", + "F102 outlet temperature = 362.935 K\n", + "F102 outlet pressure = 105000.000 Pa\n", + "\n" + ] + } + ], + "execution_count": 82 + } + ], + "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.8.12" + } + }, + "nbformat": 4, + "nbformat_minor": 3 } \ No newline at end of file From 3604a411280ab0a915f8fa029d49383fa5782926 Mon Sep 17 00:00:00 2001 From: tannerpolley Date: Thu, 12 Jun 2025 18:37:53 -0600 Subject: [PATCH 2/9] Flash Unite and HDA Flowsheet Tutorial Revisions to reflect changes to initialization and scaling --- .../notebooks/docs/tut/core/flash_unit.ipynb | 1 + .../docs/tut/core/flash_unit_doc.ipynb | 1 + .../docs/tut/core/flash_unit_exercise.ipynb | 1 + .../docs/tut/core/flash_unit_solution.ipynb | 1 + .../docs/tut/core/flash_unit_test.ipynb | 1 + .../docs/tut/core/flash_unit_usr.ipynb | 1 + .../docs/tut/core/hda_flowsheet.ipynb | 107 ++++++++++-------- .../docs/tut/core/hda_flowsheet_doc.ipynb | 103 +++++++++-------- .../tut/core/hda_flowsheet_exercise.ipynb | 105 +++++++++-------- .../tut/core/hda_flowsheet_solution.ipynb | 107 ++++++++++-------- .../docs/tut/core/hda_flowsheet_test.ipynb | 103 +++++++++-------- .../docs/tut/core/hda_flowsheet_usr.ipynb | 107 ++++++++++-------- 12 files changed, 347 insertions(+), 291 deletions(-) diff --git a/idaes_examples/notebooks/docs/tut/core/flash_unit.ipynb b/idaes_examples/notebooks/docs/tut/core/flash_unit.ipynb index 2fe01927..b01b3183 100644 --- a/idaes_examples/notebooks/docs/tut/core/flash_unit.ipynb +++ b/idaes_examples/notebooks/docs/tut/core/flash_unit.ipynb @@ -90,6 +90,7 @@ "\n", "# Import idaes logger to set output levels\n", "import idaes.logger as idaeslog\n", + "\n", "%matplotlib inline" ], "outputs": [], diff --git a/idaes_examples/notebooks/docs/tut/core/flash_unit_doc.ipynb b/idaes_examples/notebooks/docs/tut/core/flash_unit_doc.ipynb index 5f3b9777..27292328 100644 --- a/idaes_examples/notebooks/docs/tut/core/flash_unit_doc.ipynb +++ b/idaes_examples/notebooks/docs/tut/core/flash_unit_doc.ipynb @@ -90,6 +90,7 @@ "\n", "# Import idaes logger to set output levels\n", "import idaes.logger as idaeslog\n", + "\n", "%matplotlib inline" ], "outputs": [], diff --git a/idaes_examples/notebooks/docs/tut/core/flash_unit_exercise.ipynb b/idaes_examples/notebooks/docs/tut/core/flash_unit_exercise.ipynb index 7c93163d..8f3789d9 100644 --- a/idaes_examples/notebooks/docs/tut/core/flash_unit_exercise.ipynb +++ b/idaes_examples/notebooks/docs/tut/core/flash_unit_exercise.ipynb @@ -90,6 +90,7 @@ "\n", "# Import idaes logger to set output levels\n", "import idaes.logger as idaeslog\n", + "\n", "%matplotlib inline" ], "outputs": [], diff --git a/idaes_examples/notebooks/docs/tut/core/flash_unit_solution.ipynb b/idaes_examples/notebooks/docs/tut/core/flash_unit_solution.ipynb index 0e823872..0557e0df 100644 --- a/idaes_examples/notebooks/docs/tut/core/flash_unit_solution.ipynb +++ b/idaes_examples/notebooks/docs/tut/core/flash_unit_solution.ipynb @@ -90,6 +90,7 @@ "\n", "# Import idaes logger to set output levels\n", "import idaes.logger as idaeslog\n", + "\n", "%matplotlib inline" ], "outputs": [], diff --git a/idaes_examples/notebooks/docs/tut/core/flash_unit_test.ipynb b/idaes_examples/notebooks/docs/tut/core/flash_unit_test.ipynb index 62609b47..9bce54d7 100644 --- a/idaes_examples/notebooks/docs/tut/core/flash_unit_test.ipynb +++ b/idaes_examples/notebooks/docs/tut/core/flash_unit_test.ipynb @@ -90,6 +90,7 @@ "\n", "# Import idaes logger to set output levels\n", "import idaes.logger as idaeslog\n", + "\n", "%matplotlib inline" ], "outputs": [], diff --git a/idaes_examples/notebooks/docs/tut/core/flash_unit_usr.ipynb b/idaes_examples/notebooks/docs/tut/core/flash_unit_usr.ipynb index 0e823872..0557e0df 100644 --- a/idaes_examples/notebooks/docs/tut/core/flash_unit_usr.ipynb +++ b/idaes_examples/notebooks/docs/tut/core/flash_unit_usr.ipynb @@ -90,6 +90,7 @@ "\n", "# Import idaes logger to set output levels\n", "import idaes.logger as idaeslog\n", + "\n", "%matplotlib inline" ], "outputs": [], diff --git a/idaes_examples/notebooks/docs/tut/core/hda_flowsheet.ipynb b/idaes_examples/notebooks/docs/tut/core/hda_flowsheet.ipynb index 2fca2a80..af8206a3 100644 --- a/idaes_examples/notebooks/docs/tut/core/hda_flowsheet.ipynb +++ b/idaes_examples/notebooks/docs/tut/core/hda_flowsheet.ipynb @@ -180,9 +180,12 @@ }, "source": [ "from idaes.models.unit_models import (\n", - " PressureChanger, IsentropicPressureChangerInitializer,\n", - " Mixer, MixerInitializer,\n", - " Separator as Splitter, SeparatorInitializer,\n", + " PressureChanger,\n", + " IsentropicPressureChangerInitializer,\n", + " Mixer,\n", + " MixerInitializer,\n", + " Separator as Splitter,\n", + " SeparatorInitializer,\n", " Heater,\n", " StoichiometricReactor,\n", " Feed,\n", @@ -365,11 +368,9 @@ } }, "source": [ - "m.fs.I101 = Feed(\n", - " property_package=m.fs.thermo_params)\n", + "m.fs.I101 = Feed(property_package=m.fs.thermo_params)\n", "\n", - "m.fs.I102 = Feed(\n", - " property_package=m.fs.thermo_params)\n", + "m.fs.I102 = Feed(property_package=m.fs.thermo_params)\n", "\n", "m.fs.M101 = Mixer(\n", " property_package=m.fs.thermo_params,\n", @@ -527,14 +528,11 @@ }, "cell_type": "code", "source": [ - "m.fs.P101 = Product(\n", - " property_package=m.fs.thermo_params)\n", + "m.fs.P101 = Product(property_package=m.fs.thermo_params)\n", "\n", - "m.fs.P102 = Product(\n", - " property_package=m.fs.thermo_params)\n", + "m.fs.P102 = Product(property_package=m.fs.thermo_params)\n", "\n", - "m.fs.P103 = Product(\n", - " property_package=m.fs.thermo_params)" + "m.fs.P103 = Product(property_package=m.fs.thermo_params)" ], "outputs": [], "execution_count": 16 @@ -643,7 +641,7 @@ "m.fs.s06 = Arc(source=m.fs.F101.vap_outlet, destination=m.fs.S101.inlet)\n", "m.fs.s07 = Arc(source=m.fs.F101.liq_outlet, destination=m.fs.F102.inlet)\n", "m.fs.s08 = Arc(source=m.fs.S101.recycle, destination=m.fs.C101.inlet)\n", - "m.fs.s09 = Arc(source=m.fs.C101.outlet, destination=m.fs.M101.inlet_3)\n" + "m.fs.s09 = Arc(source=m.fs.C101.outlet, destination=m.fs.M101.inlet_3)" ], "outputs": [], "execution_count": 20 @@ -1411,9 +1409,9 @@ "metadata": {}, "cell_type": "markdown", "source": [ - "### 5.2 Manual Propogation Method\n", + "### 5.2 Manual Propagation Method\n", "\n", - "This method uses a more direct approach to initialize the flowsheet, utilizing the updated initalizer method and propogating manually throught the flowsheet and solving for the tear stream directly.\n", + "This method uses a more direct approach to initialize the flowsheet, utilizing the updated initializer method and propagating manually through the flowsheet and solving for the tear stream directly.\n", "Lets first import a helper function that will help us manually propagate and step through the flowsheet" ] }, @@ -1433,7 +1431,7 @@ "metadata": {}, "cell_type": "markdown", "source": [ - "Now we can setup our initial guesses for the tear stream which we know is the outlet of the `Mixer` or the inlet of the `Heater`. We can use the same initial guesses used in the first method. We also want to ensure that the degrees of freedom are consistent while we manually intialize the model.\n", + "Now we can setup our initial guesses for the tear stream which we know is the outlet of the `Mixer` or the inlet of the `Heater`. We can use the same initial guesses used in the first method. We also want to ensure that the degrees of freedom are consistent while we manually initialize the model.\n", "\n", "We will first ensure that are current degrees of freedom is still zero" ] @@ -1511,7 +1509,6 @@ " (0, \"Vap\", \"toluene\"): 1e-5,\n", " (0, \"Vap\", \"methane\"): 0.02,\n", " (0, \"Vap\", \"hydrogen\"): 0.30,\n", - "\n", " },\n", " \"temperature\": {0: 303},\n", " \"pressure\": {0: 350000},\n", @@ -1549,27 +1546,33 @@ }, "cell_type": "code", "source": [ - "m.fs.H101.default_initializer().initialize(m.fs.H101) # Initialize Heater\n", - "propagate_state(m.fs.s04) # Establish connection between Heater and Reactor\n", - "m.fs.R101.default_initializer().initialize(m.fs.R101) # Initialize Reactor\n", - "propagate_state(m.fs.s05) # Establish connection between Reactor and First Flash Unit\n", - "m.fs.F101.default_initializer().initialize(m.fs.F101) # Initialize First Flash Unit\n", - "propagate_state(m.fs.s06) # Establish connection between First Flash Unit and Splitter\n", - "propagate_state(m.fs.s07) # Establish connection between First Flash Unit and Second Flash Unit\n", - "m.fs.S101.default_initializer().initialize(m.fs.S101) # Initialize Splitter\n", - "propagate_state(m.fs.s08) # Establish connection between Splitter and Compressor\n", - "m.fs.C101.default_initializer().initialize(m.fs.C101) # Initialize Compressor\n", - "propagate_state(m.fs.s09) # Establish connection between Compressor and Mixer\n", - "m.fs.I101.default_initializer().initialize(m.fs.I101) # Initialize Toluene Inlet\n", - "propagate_state(m.fs.s01) # Establish connection between Toluene Inlet and Mixer\n", - "m.fs.I102.default_initializer().initialize(m.fs.I102) # Initialize Hydrogen Inlet\n", - "propagate_state(m.fs.s02) # Establish connection between Hydrogen Inlet and Mixer\n", - "m.fs.M101.default_initializer().initialize(m.fs.M101) # Initialize Mixer\n", - "propagate_state(m.fs.s03) # Establish connection between Mixer and Heater\n", - "m.fs.F102.default_initializer().initialize(m.fs.F102) # Initialize Second Flash Unit\n", - "propagate_state(m.fs.s10) # Establish connection between Second Flash Unit and Benzene Product\n", - "propagate_state(m.fs.s11) # Establish connection between Second Flash Unit and Toluene Product\n", - "propagate_state(m.fs.s12) # Establish connection between Splitter and Purge Product\n" + "m.fs.H101.default_initializer().initialize(m.fs.H101) # Initialize Heater\n", + "propagate_state(m.fs.s04) # Establish connection between Heater and Reactor\n", + "m.fs.R101.default_initializer().initialize(m.fs.R101) # Initialize Reactor\n", + "propagate_state(m.fs.s05) # Establish connection between Reactor and First Flash Unit\n", + "m.fs.F101.default_initializer().initialize(m.fs.F101) # Initialize First Flash Unit\n", + "propagate_state(m.fs.s06) # Establish connection between First Flash Unit and Splitter\n", + "propagate_state(\n", + " m.fs.s07\n", + ") # Establish connection between First Flash Unit and Second Flash Unit\n", + "m.fs.S101.default_initializer().initialize(m.fs.S101) # Initialize Splitter\n", + "propagate_state(m.fs.s08) # Establish connection between Splitter and Compressor\n", + "m.fs.C101.default_initializer().initialize(m.fs.C101) # Initialize Compressor\n", + "propagate_state(m.fs.s09) # Establish connection between Compressor and Mixer\n", + "m.fs.I101.default_initializer().initialize(m.fs.I101) # Initialize Toluene Inlet\n", + "propagate_state(m.fs.s01) # Establish connection between Toluene Inlet and Mixer\n", + "m.fs.I102.default_initializer().initialize(m.fs.I102) # Initialize Hydrogen Inlet\n", + "propagate_state(m.fs.s02) # Establish connection between Hydrogen Inlet and Mixer\n", + "m.fs.M101.default_initializer().initialize(m.fs.M101) # Initialize Mixer\n", + "propagate_state(m.fs.s03) # Establish connection between Mixer and Heater\n", + "m.fs.F102.default_initializer().initialize(m.fs.F102) # Initialize Second Flash Unit\n", + "propagate_state(\n", + " m.fs.s10\n", + ") # Establish connection between Second Flash Unit and Benzene Product\n", + "propagate_state(\n", + " m.fs.s11\n", + ") # Establish connection between Second Flash Unit and Toluene Product\n", + "propagate_state(m.fs.s12) # Establish connection between Splitter and Purge Product" ], "outputs": [ { @@ -1938,7 +1941,9 @@ " getattr(m.fs.H101.inlet, k)[k1].unfix()\n", "\n", "m.fs.s03_expanded.activate()\n", - "print(f\"The DOF is {degrees_of_freedom(m)} after unfixing the values and reactivating the tear stream\")" + "print(\n", + " f\"The DOF is {degrees_of_freedom(m)} after unfixing the values and reactivating the tear stream\"\n", + ")" ], "outputs": [ { @@ -1973,10 +1978,10 @@ "cell_type": "code", "source": [ "optarg = {\n", - " 'nlp_scaling_method': 'user-scaling',\n", - " 'OF_ma57_automatic_scaling': 'yes',\n", - " 'max_iter': 500,\n", - " 'tol': 1e-8,\n", + " \"nlp_scaling_method\": \"user-scaling\",\n", + " \"OF_ma57_automatic_scaling\": \"yes\",\n", + " \"max_iter\": 500,\n", + " \"tol\": 1e-8,\n", "}" ], "outputs": [], @@ -2036,7 +2041,7 @@ "solver = get_solver(solver_options=optarg)\n", "\n", "# Solve the model\n", - "results = solver.solve(m, tee=True)\n" + "results = solver.solve(m, tee=True)" ], "outputs": [ { @@ -2250,7 +2255,9 @@ } }, "cell_type": "code", - "source": "m.fs.visualize('HDA-Flowsheet')", + "source": [ + "m.fs.visualize(\"HDA-Flowsheet\")" + ], "outputs": [ { "name": "stdout", @@ -2278,7 +2285,7 @@ { "metadata": {}, "cell_type": "markdown", - "source": "Otherwise, we can run the `m.fs.report()` method to see a full summary of the solved flowsheet. It is recomended to adjust the width of the output as much as possible for the cleanest display." + "source": "Otherwise, we can run the `m.fs.report()` method to see a full summary of the solved flowsheet. It is recommended to adjust the width of the output as much as possible for the cleanest display." }, { "metadata": { @@ -3133,7 +3140,8 @@ } }, "source": [ - "print(f'''Optimal Values:\n", + "print(\n", + " f\"\"\"Optimal Values:\n", "\n", "H101 outlet temperature = {value(m.fs.H101.outlet.temperature[0]):.3f} K\n", "\n", @@ -3143,7 +3151,8 @@ "\n", "F102 outlet temperature = {value(m.fs.F102.vap_outlet.temperature[0]):.3f} K\n", "F102 outlet pressure = {value(m.fs.F102.vap_outlet.pressure[0]):.3f} Pa\n", - "''')" + "\"\"\"\n", + ")" ], "outputs": [ { diff --git a/idaes_examples/notebooks/docs/tut/core/hda_flowsheet_doc.ipynb b/idaes_examples/notebooks/docs/tut/core/hda_flowsheet_doc.ipynb index 329c0c99..be783094 100644 --- a/idaes_examples/notebooks/docs/tut/core/hda_flowsheet_doc.ipynb +++ b/idaes_examples/notebooks/docs/tut/core/hda_flowsheet_doc.ipynb @@ -180,9 +180,12 @@ }, "source": [ "from idaes.models.unit_models import (\n", - " PressureChanger, IsentropicPressureChangerInitializer,\n", - " Mixer, MixerInitializer,\n", - " Separator as Splitter, SeparatorInitializer,\n", + " PressureChanger,\n", + " IsentropicPressureChangerInitializer,\n", + " Mixer,\n", + " MixerInitializer,\n", + " Separator as Splitter,\n", + " SeparatorInitializer,\n", " Heater,\n", " StoichiometricReactor,\n", " Feed,\n", @@ -348,11 +351,9 @@ } }, "source": [ - "m.fs.I101 = Feed(\n", - " property_package=m.fs.thermo_params)\n", + "m.fs.I101 = Feed(property_package=m.fs.thermo_params)\n", "\n", - "m.fs.I102 = Feed(\n", - " property_package=m.fs.thermo_params)\n", + "m.fs.I102 = Feed(property_package=m.fs.thermo_params)\n", "\n", "m.fs.M101 = Mixer(\n", " property_package=m.fs.thermo_params,\n", @@ -472,14 +473,11 @@ }, "cell_type": "code", "source": [ - "m.fs.P101 = Product(\n", - " property_package=m.fs.thermo_params)\n", + "m.fs.P101 = Product(property_package=m.fs.thermo_params)\n", "\n", - "m.fs.P102 = Product(\n", - " property_package=m.fs.thermo_params)\n", + "m.fs.P102 = Product(property_package=m.fs.thermo_params)\n", "\n", - "m.fs.P103 = Product(\n", - " property_package=m.fs.thermo_params)" + "m.fs.P103 = Product(property_package=m.fs.thermo_params)" ], "outputs": [], "execution_count": 16 @@ -557,7 +555,7 @@ "m.fs.s06 = Arc(source=m.fs.F101.vap_outlet, destination=m.fs.S101.inlet)\n", "m.fs.s07 = Arc(source=m.fs.F101.liq_outlet, destination=m.fs.F102.inlet)\n", "m.fs.s08 = Arc(source=m.fs.S101.recycle, destination=m.fs.C101.inlet)\n", - "m.fs.s09 = Arc(source=m.fs.C101.outlet, destination=m.fs.M101.inlet_3)\n" + "m.fs.s09 = Arc(source=m.fs.C101.outlet, destination=m.fs.M101.inlet_3)" ], "outputs": [], "execution_count": 20 @@ -1204,9 +1202,9 @@ "metadata": {}, "cell_type": "markdown", "source": [ - "### 5.2 Manual Propogation Method\n", + "### 5.2 Manual Propagation Method\n", "\n", - "This method uses a more direct approach to initialize the flowsheet, utilizing the updated initalizer method and propogating manually throught the flowsheet and solving for the tear stream directly.\n", + "This method uses a more direct approach to initialize the flowsheet, utilizing the updated initializer method and propagating manually through the flowsheet and solving for the tear stream directly.\n", "Lets first import a helper function that will help us manually propagate and step through the flowsheet" ] }, @@ -1226,7 +1224,7 @@ "metadata": {}, "cell_type": "markdown", "source": [ - "Now we can setup our initial guesses for the tear stream which we know is the outlet of the `Mixer` or the inlet of the `Heater`. We can use the same initial guesses used in the first method. We also want to ensure that the degrees of freedom are consistent while we manually intialize the model.\n", + "Now we can setup our initial guesses for the tear stream which we know is the outlet of the `Mixer` or the inlet of the `Heater`. We can use the same initial guesses used in the first method. We also want to ensure that the degrees of freedom are consistent while we manually initialize the model.\n", "\n", "We will first ensure that are current degrees of freedom is still zero" ] @@ -1304,7 +1302,6 @@ " (0, \"Vap\", \"toluene\"): 1e-5,\n", " (0, \"Vap\", \"methane\"): 0.02,\n", " (0, \"Vap\", \"hydrogen\"): 0.30,\n", - "\n", " },\n", " \"temperature\": {0: 303},\n", " \"pressure\": {0: 350000},\n", @@ -1342,27 +1339,33 @@ }, "cell_type": "code", "source": [ - "m.fs.H101.default_initializer().initialize(m.fs.H101) # Initialize Heater\n", - "propagate_state(m.fs.s04) # Establish connection between Heater and Reactor\n", - "m.fs.R101.default_initializer().initialize(m.fs.R101) # Initialize Reactor\n", - "propagate_state(m.fs.s05) # Establish connection between Reactor and First Flash Unit\n", - "m.fs.F101.default_initializer().initialize(m.fs.F101) # Initialize First Flash Unit\n", - "propagate_state(m.fs.s06) # Establish connection between First Flash Unit and Splitter\n", - "propagate_state(m.fs.s07) # Establish connection between First Flash Unit and Second Flash Unit\n", - "m.fs.S101.default_initializer().initialize(m.fs.S101) # Initialize Splitter\n", - "propagate_state(m.fs.s08) # Establish connection between Splitter and Compressor\n", - "m.fs.C101.default_initializer().initialize(m.fs.C101) # Initialize Compressor\n", - "propagate_state(m.fs.s09) # Establish connection between Compressor and Mixer\n", - "m.fs.I101.default_initializer().initialize(m.fs.I101) # Initialize Toluene Inlet\n", - "propagate_state(m.fs.s01) # Establish connection between Toluene Inlet and Mixer\n", - "m.fs.I102.default_initializer().initialize(m.fs.I102) # Initialize Hydrogen Inlet\n", - "propagate_state(m.fs.s02) # Establish connection between Hydrogen Inlet and Mixer\n", - "m.fs.M101.default_initializer().initialize(m.fs.M101) # Initialize Mixer\n", - "propagate_state(m.fs.s03) # Establish connection between Mixer and Heater\n", - "m.fs.F102.default_initializer().initialize(m.fs.F102) # Initialize Second Flash Unit\n", - "propagate_state(m.fs.s10) # Establish connection between Second Flash Unit and Benzene Product\n", - "propagate_state(m.fs.s11) # Establish connection between Second Flash Unit and Toluene Product\n", - "propagate_state(m.fs.s12) # Establish connection between Splitter and Purge Product\n" + "m.fs.H101.default_initializer().initialize(m.fs.H101) # Initialize Heater\n", + "propagate_state(m.fs.s04) # Establish connection between Heater and Reactor\n", + "m.fs.R101.default_initializer().initialize(m.fs.R101) # Initialize Reactor\n", + "propagate_state(m.fs.s05) # Establish connection between Reactor and First Flash Unit\n", + "m.fs.F101.default_initializer().initialize(m.fs.F101) # Initialize First Flash Unit\n", + "propagate_state(m.fs.s06) # Establish connection between First Flash Unit and Splitter\n", + "propagate_state(\n", + " m.fs.s07\n", + ") # Establish connection between First Flash Unit and Second Flash Unit\n", + "m.fs.S101.default_initializer().initialize(m.fs.S101) # Initialize Splitter\n", + "propagate_state(m.fs.s08) # Establish connection between Splitter and Compressor\n", + "m.fs.C101.default_initializer().initialize(m.fs.C101) # Initialize Compressor\n", + "propagate_state(m.fs.s09) # Establish connection between Compressor and Mixer\n", + "m.fs.I101.default_initializer().initialize(m.fs.I101) # Initialize Toluene Inlet\n", + "propagate_state(m.fs.s01) # Establish connection between Toluene Inlet and Mixer\n", + "m.fs.I102.default_initializer().initialize(m.fs.I102) # Initialize Hydrogen Inlet\n", + "propagate_state(m.fs.s02) # Establish connection between Hydrogen Inlet and Mixer\n", + "m.fs.M101.default_initializer().initialize(m.fs.M101) # Initialize Mixer\n", + "propagate_state(m.fs.s03) # Establish connection between Mixer and Heater\n", + "m.fs.F102.default_initializer().initialize(m.fs.F102) # Initialize Second Flash Unit\n", + "propagate_state(\n", + " m.fs.s10\n", + ") # Establish connection between Second Flash Unit and Benzene Product\n", + "propagate_state(\n", + " m.fs.s11\n", + ") # Establish connection between Second Flash Unit and Toluene Product\n", + "propagate_state(m.fs.s12) # Establish connection between Splitter and Purge Product" ], "outputs": [ { @@ -1731,7 +1734,9 @@ " getattr(m.fs.H101.inlet, k)[k1].unfix()\n", "\n", "m.fs.s03_expanded.activate()\n", - "print(f\"The DOF is {degrees_of_freedom(m)} after unfixing the values and reactivating the tear stream\")" + "print(\n", + " f\"The DOF is {degrees_of_freedom(m)} after unfixing the values and reactivating the tear stream\"\n", + ")" ], "outputs": [ { @@ -1766,10 +1771,10 @@ "cell_type": "code", "source": [ "optarg = {\n", - " 'nlp_scaling_method': 'user-scaling',\n", - " 'OF_ma57_automatic_scaling': 'yes',\n", - " 'max_iter': 500,\n", - " 'tol': 1e-8,\n", + " \"nlp_scaling_method\": \"user-scaling\",\n", + " \"OF_ma57_automatic_scaling\": \"yes\",\n", + " \"max_iter\": 500,\n", + " \"tol\": 1e-8,\n", "}" ], "outputs": [], @@ -1791,7 +1796,7 @@ "solver = get_solver(solver_options=optarg)\n", "\n", "# Solve the model\n", - "results = solver.solve(m, tee=True)\n" + "results = solver.solve(m, tee=True)" ], "outputs": [ { @@ -1977,7 +1982,7 @@ { "metadata": {}, "cell_type": "markdown", - "source": "Otherwise, we can run the `m.fs.report()` method to see a full summary of the solved flowsheet. It is recomended to adjust the width of the output as much as possible for the cleanest display." + "source": "Otherwise, we can run the `m.fs.report()` method to see a full summary of the solved flowsheet. It is recommended to adjust the width of the output as much as possible for the cleanest display." }, { "metadata": { @@ -2639,7 +2644,8 @@ } }, "source": [ - "print(f'''Optimal Values:\n", + "print(\n", + " f\"\"\"Optimal Values:\n", "\n", "H101 outlet temperature = {value(m.fs.H101.outlet.temperature[0]):.3f} K\n", "\n", @@ -2649,7 +2655,8 @@ "\n", "F102 outlet temperature = {value(m.fs.F102.vap_outlet.temperature[0]):.3f} K\n", "F102 outlet pressure = {value(m.fs.F102.vap_outlet.pressure[0]):.3f} Pa\n", - "''')" + "\"\"\"\n", + ")" ], "outputs": [ { diff --git a/idaes_examples/notebooks/docs/tut/core/hda_flowsheet_exercise.ipynb b/idaes_examples/notebooks/docs/tut/core/hda_flowsheet_exercise.ipynb index 91c14462..6a51e39c 100644 --- a/idaes_examples/notebooks/docs/tut/core/hda_flowsheet_exercise.ipynb +++ b/idaes_examples/notebooks/docs/tut/core/hda_flowsheet_exercise.ipynb @@ -180,9 +180,12 @@ }, "source": [ "from idaes.models.unit_models import (\n", - " PressureChanger, IsentropicPressureChangerInitializer,\n", - " Mixer, MixerInitializer,\n", - " Separator as Splitter, SeparatorInitializer,\n", + " PressureChanger,\n", + " IsentropicPressureChangerInitializer,\n", + " Mixer,\n", + " MixerInitializer,\n", + " Separator as Splitter,\n", + " SeparatorInitializer,\n", " Heater,\n", " StoichiometricReactor,\n", " Feed,\n", @@ -347,11 +350,9 @@ } }, "source": [ - "m.fs.I101 = Feed(\n", - " property_package=m.fs.thermo_params)\n", + "m.fs.I101 = Feed(property_package=m.fs.thermo_params)\n", "\n", - "m.fs.I102 = Feed(\n", - " property_package=m.fs.thermo_params)\n", + "m.fs.I102 = Feed(property_package=m.fs.thermo_params)\n", "\n", "m.fs.M101 = Mixer(\n", " property_package=m.fs.thermo_params,\n", @@ -485,14 +486,11 @@ }, "cell_type": "code", "source": [ - "m.fs.P101 = Product(\n", - " property_package=m.fs.thermo_params)\n", + "m.fs.P101 = Product(property_package=m.fs.thermo_params)\n", "\n", - "m.fs.P102 = Product(\n", - " property_package=m.fs.thermo_params)\n", + "m.fs.P102 = Product(property_package=m.fs.thermo_params)\n", "\n", - "m.fs.P103 = Product(\n", - " property_package=m.fs.thermo_params)" + "m.fs.P103 = Product(property_package=m.fs.thermo_params)" ], "outputs": [], "execution_count": 16 @@ -583,7 +581,7 @@ "m.fs.s06 = Arc(source=m.fs.F101.vap_outlet, destination=m.fs.S101.inlet)\n", "m.fs.s07 = Arc(source=m.fs.F101.liq_outlet, destination=m.fs.F102.inlet)\n", "m.fs.s08 = Arc(source=m.fs.S101.recycle, destination=m.fs.C101.inlet)\n", - "m.fs.s09 = Arc(source=m.fs.C101.outlet, destination=m.fs.M101.inlet_3)\n" + "m.fs.s09 = Arc(source=m.fs.C101.outlet, destination=m.fs.M101.inlet_3)" ], "outputs": [], "execution_count": 20 @@ -1272,9 +1270,9 @@ "metadata": {}, "cell_type": "markdown", "source": [ - "### 5.2 Manual Propogation Method\n", + "### 5.2 Manual Propagation Method\n", "\n", - "This method uses a more direct approach to initialize the flowsheet, utilizing the updated initalizer method and propogating manually throught the flowsheet and solving for the tear stream directly.\n", + "This method uses a more direct approach to initialize the flowsheet, utilizing the updated initializer method and propagating manually through the flowsheet and solving for the tear stream directly.\n", "Lets first import a helper function that will help us manually propagate and step through the flowsheet" ] }, @@ -1294,7 +1292,7 @@ "metadata": {}, "cell_type": "markdown", "source": [ - "Now we can setup our initial guesses for the tear stream which we know is the outlet of the `Mixer` or the inlet of the `Heater`. We can use the same initial guesses used in the first method. We also want to ensure that the degrees of freedom are consistent while we manually intialize the model.\n", + "Now we can setup our initial guesses for the tear stream which we know is the outlet of the `Mixer` or the inlet of the `Heater`. We can use the same initial guesses used in the first method. We also want to ensure that the degrees of freedom are consistent while we manually initialize the model.\n", "\n", "We will first ensure that are current degrees of freedom is still zero" ] @@ -1372,7 +1370,6 @@ " (0, \"Vap\", \"toluene\"): 1e-5,\n", " (0, \"Vap\", \"methane\"): 0.02,\n", " (0, \"Vap\", \"hydrogen\"): 0.30,\n", - "\n", " },\n", " \"temperature\": {0: 303},\n", " \"pressure\": {0: 350000},\n", @@ -1410,27 +1407,33 @@ }, "cell_type": "code", "source": [ - "m.fs.H101.default_initializer().initialize(m.fs.H101) # Initialize Heater\n", - "propagate_state(m.fs.s04) # Establish connection between Heater and Reactor\n", - "m.fs.R101.default_initializer().initialize(m.fs.R101) # Initialize Reactor\n", - "propagate_state(m.fs.s05) # Establish connection between Reactor and First Flash Unit\n", - "m.fs.F101.default_initializer().initialize(m.fs.F101) # Initialize First Flash Unit\n", - "propagate_state(m.fs.s06) # Establish connection between First Flash Unit and Splitter\n", - "propagate_state(m.fs.s07) # Establish connection between First Flash Unit and Second Flash Unit\n", - "m.fs.S101.default_initializer().initialize(m.fs.S101) # Initialize Splitter\n", - "propagate_state(m.fs.s08) # Establish connection between Splitter and Compressor\n", - "m.fs.C101.default_initializer().initialize(m.fs.C101) # Initialize Compressor\n", - "propagate_state(m.fs.s09) # Establish connection between Compressor and Mixer\n", - "m.fs.I101.default_initializer().initialize(m.fs.I101) # Initialize Toluene Inlet\n", - "propagate_state(m.fs.s01) # Establish connection between Toluene Inlet and Mixer\n", - "m.fs.I102.default_initializer().initialize(m.fs.I102) # Initialize Hydrogen Inlet\n", - "propagate_state(m.fs.s02) # Establish connection between Hydrogen Inlet and Mixer\n", - "m.fs.M101.default_initializer().initialize(m.fs.M101) # Initialize Mixer\n", - "propagate_state(m.fs.s03) # Establish connection between Mixer and Heater\n", - "m.fs.F102.default_initializer().initialize(m.fs.F102) # Initialize Second Flash Unit\n", - "propagate_state(m.fs.s10) # Establish connection between Second Flash Unit and Benzene Product\n", - "propagate_state(m.fs.s11) # Establish connection between Second Flash Unit and Toluene Product\n", - "propagate_state(m.fs.s12) # Establish connection between Splitter and Purge Product\n" + "m.fs.H101.default_initializer().initialize(m.fs.H101) # Initialize Heater\n", + "propagate_state(m.fs.s04) # Establish connection between Heater and Reactor\n", + "m.fs.R101.default_initializer().initialize(m.fs.R101) # Initialize Reactor\n", + "propagate_state(m.fs.s05) # Establish connection between Reactor and First Flash Unit\n", + "m.fs.F101.default_initializer().initialize(m.fs.F101) # Initialize First Flash Unit\n", + "propagate_state(m.fs.s06) # Establish connection between First Flash Unit and Splitter\n", + "propagate_state(\n", + " m.fs.s07\n", + ") # Establish connection between First Flash Unit and Second Flash Unit\n", + "m.fs.S101.default_initializer().initialize(m.fs.S101) # Initialize Splitter\n", + "propagate_state(m.fs.s08) # Establish connection between Splitter and Compressor\n", + "m.fs.C101.default_initializer().initialize(m.fs.C101) # Initialize Compressor\n", + "propagate_state(m.fs.s09) # Establish connection between Compressor and Mixer\n", + "m.fs.I101.default_initializer().initialize(m.fs.I101) # Initialize Toluene Inlet\n", + "propagate_state(m.fs.s01) # Establish connection between Toluene Inlet and Mixer\n", + "m.fs.I102.default_initializer().initialize(m.fs.I102) # Initialize Hydrogen Inlet\n", + "propagate_state(m.fs.s02) # Establish connection between Hydrogen Inlet and Mixer\n", + "m.fs.M101.default_initializer().initialize(m.fs.M101) # Initialize Mixer\n", + "propagate_state(m.fs.s03) # Establish connection between Mixer and Heater\n", + "m.fs.F102.default_initializer().initialize(m.fs.F102) # Initialize Second Flash Unit\n", + "propagate_state(\n", + " m.fs.s10\n", + ") # Establish connection between Second Flash Unit and Benzene Product\n", + "propagate_state(\n", + " m.fs.s11\n", + ") # Establish connection between Second Flash Unit and Toluene Product\n", + "propagate_state(m.fs.s12) # Establish connection between Splitter and Purge Product" ], "outputs": [ { @@ -1799,7 +1802,9 @@ " getattr(m.fs.H101.inlet, k)[k1].unfix()\n", "\n", "m.fs.s03_expanded.activate()\n", - "print(f\"The DOF is {degrees_of_freedom(m)} after unfixing the values and reactivating the tear stream\")" + "print(\n", + " f\"The DOF is {degrees_of_freedom(m)} after unfixing the values and reactivating the tear stream\"\n", + ")" ], "outputs": [ { @@ -1834,10 +1839,10 @@ "cell_type": "code", "source": [ "optarg = {\n", - " 'nlp_scaling_method': 'user-scaling',\n", - " 'OF_ma57_automatic_scaling': 'yes',\n", - " 'max_iter': 500,\n", - " 'tol': 1e-8,\n", + " \"nlp_scaling_method\": \"user-scaling\",\n", + " \"OF_ma57_automatic_scaling\": \"yes\",\n", + " \"max_iter\": 500,\n", + " \"tol\": 1e-8,\n", "}" ], "outputs": [], @@ -1906,7 +1911,9 @@ } }, "cell_type": "code", - "source": "m.fs.visualize('HDA-Flowsheet')", + "source": [ + "m.fs.visualize(\"HDA-Flowsheet\")" + ], "outputs": [ { "name": "stdout", @@ -1934,7 +1941,7 @@ { "metadata": {}, "cell_type": "markdown", - "source": "Otherwise, we can run the `m.fs.report()` method to see a full summary of the solved flowsheet. It is recomended to adjust the width of the output as much as possible for the cleanest display." + "source": "Otherwise, we can run the `m.fs.report()` method to see a full summary of the solved flowsheet. It is recommended to adjust the width of the output as much as possible for the cleanest display." }, { "metadata": { @@ -2639,7 +2646,8 @@ } }, "source": [ - "print(f'''Optimal Values:\n", + "print(\n", + " f\"\"\"Optimal Values:\n", "\n", "H101 outlet temperature = {value(m.fs.H101.outlet.temperature[0]):.3f} K\n", "\n", @@ -2649,7 +2657,8 @@ "\n", "F102 outlet temperature = {value(m.fs.F102.vap_outlet.temperature[0]):.3f} K\n", "F102 outlet pressure = {value(m.fs.F102.vap_outlet.pressure[0]):.3f} Pa\n", - "''')" + "\"\"\"\n", + ")" ], "outputs": [ { diff --git a/idaes_examples/notebooks/docs/tut/core/hda_flowsheet_solution.ipynb b/idaes_examples/notebooks/docs/tut/core/hda_flowsheet_solution.ipynb index 7e8b3ff6..0d19ffad 100644 --- a/idaes_examples/notebooks/docs/tut/core/hda_flowsheet_solution.ipynb +++ b/idaes_examples/notebooks/docs/tut/core/hda_flowsheet_solution.ipynb @@ -180,9 +180,12 @@ }, "source": [ "from idaes.models.unit_models import (\n", - " PressureChanger, IsentropicPressureChangerInitializer,\n", - " Mixer, MixerInitializer,\n", - " Separator as Splitter, SeparatorInitializer,\n", + " PressureChanger,\n", + " IsentropicPressureChangerInitializer,\n", + " Mixer,\n", + " MixerInitializer,\n", + " Separator as Splitter,\n", + " SeparatorInitializer,\n", " Heater,\n", " StoichiometricReactor,\n", " Feed,\n", @@ -365,11 +368,9 @@ } }, "source": [ - "m.fs.I101 = Feed(\n", - " property_package=m.fs.thermo_params)\n", + "m.fs.I101 = Feed(property_package=m.fs.thermo_params)\n", "\n", - "m.fs.I102 = Feed(\n", - " property_package=m.fs.thermo_params)\n", + "m.fs.I102 = Feed(property_package=m.fs.thermo_params)\n", "\n", "m.fs.M101 = Mixer(\n", " property_package=m.fs.thermo_params,\n", @@ -527,14 +528,11 @@ }, "cell_type": "code", "source": [ - "m.fs.P101 = Product(\n", - " property_package=m.fs.thermo_params)\n", + "m.fs.P101 = Product(property_package=m.fs.thermo_params)\n", "\n", - "m.fs.P102 = Product(\n", - " property_package=m.fs.thermo_params)\n", + "m.fs.P102 = Product(property_package=m.fs.thermo_params)\n", "\n", - "m.fs.P103 = Product(\n", - " property_package=m.fs.thermo_params)" + "m.fs.P103 = Product(property_package=m.fs.thermo_params)" ], "outputs": [], "execution_count": 16 @@ -643,7 +641,7 @@ "m.fs.s06 = Arc(source=m.fs.F101.vap_outlet, destination=m.fs.S101.inlet)\n", "m.fs.s07 = Arc(source=m.fs.F101.liq_outlet, destination=m.fs.F102.inlet)\n", "m.fs.s08 = Arc(source=m.fs.S101.recycle, destination=m.fs.C101.inlet)\n", - "m.fs.s09 = Arc(source=m.fs.C101.outlet, destination=m.fs.M101.inlet_3)\n" + "m.fs.s09 = Arc(source=m.fs.C101.outlet, destination=m.fs.M101.inlet_3)" ], "outputs": [], "execution_count": 20 @@ -1375,9 +1373,9 @@ "metadata": {}, "cell_type": "markdown", "source": [ - "### 5.2 Manual Propogation Method\n", + "### 5.2 Manual Propagation Method\n", "\n", - "This method uses a more direct approach to initialize the flowsheet, utilizing the updated initalizer method and propogating manually throught the flowsheet and solving for the tear stream directly.\n", + "This method uses a more direct approach to initialize the flowsheet, utilizing the updated initializer method and propagating manually through the flowsheet and solving for the tear stream directly.\n", "Lets first import a helper function that will help us manually propagate and step through the flowsheet" ] }, @@ -1397,7 +1395,7 @@ "metadata": {}, "cell_type": "markdown", "source": [ - "Now we can setup our initial guesses for the tear stream which we know is the outlet of the `Mixer` or the inlet of the `Heater`. We can use the same initial guesses used in the first method. We also want to ensure that the degrees of freedom are consistent while we manually intialize the model.\n", + "Now we can setup our initial guesses for the tear stream which we know is the outlet of the `Mixer` or the inlet of the `Heater`. We can use the same initial guesses used in the first method. We also want to ensure that the degrees of freedom are consistent while we manually initialize the model.\n", "\n", "We will first ensure that are current degrees of freedom is still zero" ] @@ -1475,7 +1473,6 @@ " (0, \"Vap\", \"toluene\"): 1e-5,\n", " (0, \"Vap\", \"methane\"): 0.02,\n", " (0, \"Vap\", \"hydrogen\"): 0.30,\n", - "\n", " },\n", " \"temperature\": {0: 303},\n", " \"pressure\": {0: 350000},\n", @@ -1513,27 +1510,33 @@ }, "cell_type": "code", "source": [ - "m.fs.H101.default_initializer().initialize(m.fs.H101) # Initialize Heater\n", - "propagate_state(m.fs.s04) # Establish connection between Heater and Reactor\n", - "m.fs.R101.default_initializer().initialize(m.fs.R101) # Initialize Reactor\n", - "propagate_state(m.fs.s05) # Establish connection between Reactor and First Flash Unit\n", - "m.fs.F101.default_initializer().initialize(m.fs.F101) # Initialize First Flash Unit\n", - "propagate_state(m.fs.s06) # Establish connection between First Flash Unit and Splitter\n", - "propagate_state(m.fs.s07) # Establish connection between First Flash Unit and Second Flash Unit\n", - "m.fs.S101.default_initializer().initialize(m.fs.S101) # Initialize Splitter\n", - "propagate_state(m.fs.s08) # Establish connection between Splitter and Compressor\n", - "m.fs.C101.default_initializer().initialize(m.fs.C101) # Initialize Compressor\n", - "propagate_state(m.fs.s09) # Establish connection between Compressor and Mixer\n", - "m.fs.I101.default_initializer().initialize(m.fs.I101) # Initialize Toluene Inlet\n", - "propagate_state(m.fs.s01) # Establish connection between Toluene Inlet and Mixer\n", - "m.fs.I102.default_initializer().initialize(m.fs.I102) # Initialize Hydrogen Inlet\n", - "propagate_state(m.fs.s02) # Establish connection between Hydrogen Inlet and Mixer\n", - "m.fs.M101.default_initializer().initialize(m.fs.M101) # Initialize Mixer\n", - "propagate_state(m.fs.s03) # Establish connection between Mixer and Heater\n", - "m.fs.F102.default_initializer().initialize(m.fs.F102) # Initialize Second Flash Unit\n", - "propagate_state(m.fs.s10) # Establish connection between Second Flash Unit and Benzene Product\n", - "propagate_state(m.fs.s11) # Establish connection between Second Flash Unit and Toluene Product\n", - "propagate_state(m.fs.s12) # Establish connection between Splitter and Purge Product\n" + "m.fs.H101.default_initializer().initialize(m.fs.H101) # Initialize Heater\n", + "propagate_state(m.fs.s04) # Establish connection between Heater and Reactor\n", + "m.fs.R101.default_initializer().initialize(m.fs.R101) # Initialize Reactor\n", + "propagate_state(m.fs.s05) # Establish connection between Reactor and First Flash Unit\n", + "m.fs.F101.default_initializer().initialize(m.fs.F101) # Initialize First Flash Unit\n", + "propagate_state(m.fs.s06) # Establish connection between First Flash Unit and Splitter\n", + "propagate_state(\n", + " m.fs.s07\n", + ") # Establish connection between First Flash Unit and Second Flash Unit\n", + "m.fs.S101.default_initializer().initialize(m.fs.S101) # Initialize Splitter\n", + "propagate_state(m.fs.s08) # Establish connection between Splitter and Compressor\n", + "m.fs.C101.default_initializer().initialize(m.fs.C101) # Initialize Compressor\n", + "propagate_state(m.fs.s09) # Establish connection between Compressor and Mixer\n", + "m.fs.I101.default_initializer().initialize(m.fs.I101) # Initialize Toluene Inlet\n", + "propagate_state(m.fs.s01) # Establish connection between Toluene Inlet and Mixer\n", + "m.fs.I102.default_initializer().initialize(m.fs.I102) # Initialize Hydrogen Inlet\n", + "propagate_state(m.fs.s02) # Establish connection between Hydrogen Inlet and Mixer\n", + "m.fs.M101.default_initializer().initialize(m.fs.M101) # Initialize Mixer\n", + "propagate_state(m.fs.s03) # Establish connection between Mixer and Heater\n", + "m.fs.F102.default_initializer().initialize(m.fs.F102) # Initialize Second Flash Unit\n", + "propagate_state(\n", + " m.fs.s10\n", + ") # Establish connection between Second Flash Unit and Benzene Product\n", + "propagate_state(\n", + " m.fs.s11\n", + ") # Establish connection between Second Flash Unit and Toluene Product\n", + "propagate_state(m.fs.s12) # Establish connection between Splitter and Purge Product" ], "outputs": [ { @@ -1902,7 +1905,9 @@ " getattr(m.fs.H101.inlet, k)[k1].unfix()\n", "\n", "m.fs.s03_expanded.activate()\n", - "print(f\"The DOF is {degrees_of_freedom(m)} after unfixing the values and reactivating the tear stream\")" + "print(\n", + " f\"The DOF is {degrees_of_freedom(m)} after unfixing the values and reactivating the tear stream\"\n", + ")" ], "outputs": [ { @@ -1937,10 +1942,10 @@ "cell_type": "code", "source": [ "optarg = {\n", - " 'nlp_scaling_method': 'user-scaling',\n", - " 'OF_ma57_automatic_scaling': 'yes',\n", - " 'max_iter': 500,\n", - " 'tol': 1e-8,\n", + " \"nlp_scaling_method\": \"user-scaling\",\n", + " \"OF_ma57_automatic_scaling\": \"yes\",\n", + " \"max_iter\": 500,\n", + " \"tol\": 1e-8,\n", "}" ], "outputs": [], @@ -2000,7 +2005,7 @@ "solver = get_solver(solver_options=optarg)\n", "\n", "# Solve the model\n", - "results = solver.solve(m, tee=True)\n" + "results = solver.solve(m, tee=True)" ], "outputs": [ { @@ -2194,7 +2199,9 @@ } }, "cell_type": "code", - "source": "m.fs.visualize('HDA-Flowsheet')", + "source": [ + "m.fs.visualize(\"HDA-Flowsheet\")" + ], "outputs": [ { "name": "stdout", @@ -2222,7 +2229,7 @@ { "metadata": {}, "cell_type": "markdown", - "source": "Otherwise, we can run the `m.fs.report()` method to see a full summary of the solved flowsheet. It is recomended to adjust the width of the output as much as possible for the cleanest display." + "source": "Otherwise, we can run the `m.fs.report()` method to see a full summary of the solved flowsheet. It is recommended to adjust the width of the output as much as possible for the cleanest display." }, { "metadata": { @@ -2984,7 +2991,8 @@ } }, "source": [ - "print(f'''Optimal Values:\n", + "print(\n", + " f\"\"\"Optimal Values:\n", "\n", "H101 outlet temperature = {value(m.fs.H101.outlet.temperature[0]):.3f} K\n", "\n", @@ -2994,7 +3002,8 @@ "\n", "F102 outlet temperature = {value(m.fs.F102.vap_outlet.temperature[0]):.3f} K\n", "F102 outlet pressure = {value(m.fs.F102.vap_outlet.pressure[0]):.3f} Pa\n", - "''')" + "\"\"\"\n", + ")" ], "outputs": [ { diff --git a/idaes_examples/notebooks/docs/tut/core/hda_flowsheet_test.ipynb b/idaes_examples/notebooks/docs/tut/core/hda_flowsheet_test.ipynb index 14dbf8bc..16908e20 100644 --- a/idaes_examples/notebooks/docs/tut/core/hda_flowsheet_test.ipynb +++ b/idaes_examples/notebooks/docs/tut/core/hda_flowsheet_test.ipynb @@ -180,9 +180,12 @@ }, "source": [ "from idaes.models.unit_models import (\n", - " PressureChanger, IsentropicPressureChangerInitializer,\n", - " Mixer, MixerInitializer,\n", - " Separator as Splitter, SeparatorInitializer,\n", + " PressureChanger,\n", + " IsentropicPressureChangerInitializer,\n", + " Mixer,\n", + " MixerInitializer,\n", + " Separator as Splitter,\n", + " SeparatorInitializer,\n", " Heater,\n", " StoichiometricReactor,\n", " Feed,\n", @@ -348,11 +351,9 @@ } }, "source": [ - "m.fs.I101 = Feed(\n", - " property_package=m.fs.thermo_params)\n", + "m.fs.I101 = Feed(property_package=m.fs.thermo_params)\n", "\n", - "m.fs.I102 = Feed(\n", - " property_package=m.fs.thermo_params)\n", + "m.fs.I102 = Feed(property_package=m.fs.thermo_params)\n", "\n", "m.fs.M101 = Mixer(\n", " property_package=m.fs.thermo_params,\n", @@ -472,14 +473,11 @@ }, "cell_type": "code", "source": [ - "m.fs.P101 = Product(\n", - " property_package=m.fs.thermo_params)\n", + "m.fs.P101 = Product(property_package=m.fs.thermo_params)\n", "\n", - "m.fs.P102 = Product(\n", - " property_package=m.fs.thermo_params)\n", + "m.fs.P102 = Product(property_package=m.fs.thermo_params)\n", "\n", - "m.fs.P103 = Product(\n", - " property_package=m.fs.thermo_params)" + "m.fs.P103 = Product(property_package=m.fs.thermo_params)" ], "outputs": [], "execution_count": 16 @@ -557,7 +555,7 @@ "m.fs.s06 = Arc(source=m.fs.F101.vap_outlet, destination=m.fs.S101.inlet)\n", "m.fs.s07 = Arc(source=m.fs.F101.liq_outlet, destination=m.fs.F102.inlet)\n", "m.fs.s08 = Arc(source=m.fs.S101.recycle, destination=m.fs.C101.inlet)\n", - "m.fs.s09 = Arc(source=m.fs.C101.outlet, destination=m.fs.M101.inlet_3)\n" + "m.fs.s09 = Arc(source=m.fs.C101.outlet, destination=m.fs.M101.inlet_3)" ], "outputs": [], "execution_count": 20 @@ -1240,9 +1238,9 @@ "metadata": {}, "cell_type": "markdown", "source": [ - "### 5.2 Manual Propogation Method\n", + "### 5.2 Manual Propagation Method\n", "\n", - "This method uses a more direct approach to initialize the flowsheet, utilizing the updated initalizer method and propogating manually throught the flowsheet and solving for the tear stream directly.\n", + "This method uses a more direct approach to initialize the flowsheet, utilizing the updated initializer method and propagating manually through the flowsheet and solving for the tear stream directly.\n", "Lets first import a helper function that will help us manually propagate and step through the flowsheet" ] }, @@ -1262,7 +1260,7 @@ "metadata": {}, "cell_type": "markdown", "source": [ - "Now we can setup our initial guesses for the tear stream which we know is the outlet of the `Mixer` or the inlet of the `Heater`. We can use the same initial guesses used in the first method. We also want to ensure that the degrees of freedom are consistent while we manually intialize the model.\n", + "Now we can setup our initial guesses for the tear stream which we know is the outlet of the `Mixer` or the inlet of the `Heater`. We can use the same initial guesses used in the first method. We also want to ensure that the degrees of freedom are consistent while we manually initialize the model.\n", "\n", "We will first ensure that are current degrees of freedom is still zero" ] @@ -1340,7 +1338,6 @@ " (0, \"Vap\", \"toluene\"): 1e-5,\n", " (0, \"Vap\", \"methane\"): 0.02,\n", " (0, \"Vap\", \"hydrogen\"): 0.30,\n", - "\n", " },\n", " \"temperature\": {0: 303},\n", " \"pressure\": {0: 350000},\n", @@ -1378,27 +1375,33 @@ }, "cell_type": "code", "source": [ - "m.fs.H101.default_initializer().initialize(m.fs.H101) # Initialize Heater\n", - "propagate_state(m.fs.s04) # Establish connection between Heater and Reactor\n", - "m.fs.R101.default_initializer().initialize(m.fs.R101) # Initialize Reactor\n", - "propagate_state(m.fs.s05) # Establish connection between Reactor and First Flash Unit\n", - "m.fs.F101.default_initializer().initialize(m.fs.F101) # Initialize First Flash Unit\n", - "propagate_state(m.fs.s06) # Establish connection between First Flash Unit and Splitter\n", - "propagate_state(m.fs.s07) # Establish connection between First Flash Unit and Second Flash Unit\n", - "m.fs.S101.default_initializer().initialize(m.fs.S101) # Initialize Splitter\n", - "propagate_state(m.fs.s08) # Establish connection between Splitter and Compressor\n", - "m.fs.C101.default_initializer().initialize(m.fs.C101) # Initialize Compressor\n", - "propagate_state(m.fs.s09) # Establish connection between Compressor and Mixer\n", - "m.fs.I101.default_initializer().initialize(m.fs.I101) # Initialize Toluene Inlet\n", - "propagate_state(m.fs.s01) # Establish connection between Toluene Inlet and Mixer\n", - "m.fs.I102.default_initializer().initialize(m.fs.I102) # Initialize Hydrogen Inlet\n", - "propagate_state(m.fs.s02) # Establish connection between Hydrogen Inlet and Mixer\n", - "m.fs.M101.default_initializer().initialize(m.fs.M101) # Initialize Mixer\n", - "propagate_state(m.fs.s03) # Establish connection between Mixer and Heater\n", - "m.fs.F102.default_initializer().initialize(m.fs.F102) # Initialize Second Flash Unit\n", - "propagate_state(m.fs.s10) # Establish connection between Second Flash Unit and Benzene Product\n", - "propagate_state(m.fs.s11) # Establish connection between Second Flash Unit and Toluene Product\n", - "propagate_state(m.fs.s12) # Establish connection between Splitter and Purge Product\n" + "m.fs.H101.default_initializer().initialize(m.fs.H101) # Initialize Heater\n", + "propagate_state(m.fs.s04) # Establish connection between Heater and Reactor\n", + "m.fs.R101.default_initializer().initialize(m.fs.R101) # Initialize Reactor\n", + "propagate_state(m.fs.s05) # Establish connection between Reactor and First Flash Unit\n", + "m.fs.F101.default_initializer().initialize(m.fs.F101) # Initialize First Flash Unit\n", + "propagate_state(m.fs.s06) # Establish connection between First Flash Unit and Splitter\n", + "propagate_state(\n", + " m.fs.s07\n", + ") # Establish connection between First Flash Unit and Second Flash Unit\n", + "m.fs.S101.default_initializer().initialize(m.fs.S101) # Initialize Splitter\n", + "propagate_state(m.fs.s08) # Establish connection between Splitter and Compressor\n", + "m.fs.C101.default_initializer().initialize(m.fs.C101) # Initialize Compressor\n", + "propagate_state(m.fs.s09) # Establish connection between Compressor and Mixer\n", + "m.fs.I101.default_initializer().initialize(m.fs.I101) # Initialize Toluene Inlet\n", + "propagate_state(m.fs.s01) # Establish connection between Toluene Inlet and Mixer\n", + "m.fs.I102.default_initializer().initialize(m.fs.I102) # Initialize Hydrogen Inlet\n", + "propagate_state(m.fs.s02) # Establish connection between Hydrogen Inlet and Mixer\n", + "m.fs.M101.default_initializer().initialize(m.fs.M101) # Initialize Mixer\n", + "propagate_state(m.fs.s03) # Establish connection between Mixer and Heater\n", + "m.fs.F102.default_initializer().initialize(m.fs.F102) # Initialize Second Flash Unit\n", + "propagate_state(\n", + " m.fs.s10\n", + ") # Establish connection between Second Flash Unit and Benzene Product\n", + "propagate_state(\n", + " m.fs.s11\n", + ") # Establish connection between Second Flash Unit and Toluene Product\n", + "propagate_state(m.fs.s12) # Establish connection between Splitter and Purge Product" ], "outputs": [ { @@ -1767,7 +1770,9 @@ " getattr(m.fs.H101.inlet, k)[k1].unfix()\n", "\n", "m.fs.s03_expanded.activate()\n", - "print(f\"The DOF is {degrees_of_freedom(m)} after unfixing the values and reactivating the tear stream\")" + "print(\n", + " f\"The DOF is {degrees_of_freedom(m)} after unfixing the values and reactivating the tear stream\"\n", + ")" ], "outputs": [ { @@ -1802,10 +1807,10 @@ "cell_type": "code", "source": [ "optarg = {\n", - " 'nlp_scaling_method': 'user-scaling',\n", - " 'OF_ma57_automatic_scaling': 'yes',\n", - " 'max_iter': 500,\n", - " 'tol': 1e-8,\n", + " \"nlp_scaling_method\": \"user-scaling\",\n", + " \"OF_ma57_automatic_scaling\": \"yes\",\n", + " \"max_iter\": 500,\n", + " \"tol\": 1e-8,\n", "}" ], "outputs": [], @@ -1827,7 +1832,7 @@ "solver = get_solver(solver_options=optarg)\n", "\n", "# Solve the model\n", - "results = solver.solve(m, tee=True)\n" + "results = solver.solve(m, tee=True)" ], "outputs": [ { @@ -2033,7 +2038,7 @@ { "metadata": {}, "cell_type": "markdown", - "source": "Otherwise, we can run the `m.fs.report()` method to see a full summary of the solved flowsheet. It is recomended to adjust the width of the output as much as possible for the cleanest display." + "source": "Otherwise, we can run the `m.fs.report()` method to see a full summary of the solved flowsheet. It is recommended to adjust the width of the output as much as possible for the cleanest display." }, { "metadata": { @@ -2788,7 +2793,8 @@ } }, "source": [ - "print(f'''Optimal Values:\n", + "print(\n", + " f\"\"\"Optimal Values:\n", "\n", "H101 outlet temperature = {value(m.fs.H101.outlet.temperature[0]):.3f} K\n", "\n", @@ -2798,7 +2804,8 @@ "\n", "F102 outlet temperature = {value(m.fs.F102.vap_outlet.temperature[0]):.3f} K\n", "F102 outlet pressure = {value(m.fs.F102.vap_outlet.pressure[0]):.3f} Pa\n", - "''')" + "\"\"\"\n", + ")" ], "outputs": [ { diff --git a/idaes_examples/notebooks/docs/tut/core/hda_flowsheet_usr.ipynb b/idaes_examples/notebooks/docs/tut/core/hda_flowsheet_usr.ipynb index 7e8b3ff6..0d19ffad 100644 --- a/idaes_examples/notebooks/docs/tut/core/hda_flowsheet_usr.ipynb +++ b/idaes_examples/notebooks/docs/tut/core/hda_flowsheet_usr.ipynb @@ -180,9 +180,12 @@ }, "source": [ "from idaes.models.unit_models import (\n", - " PressureChanger, IsentropicPressureChangerInitializer,\n", - " Mixer, MixerInitializer,\n", - " Separator as Splitter, SeparatorInitializer,\n", + " PressureChanger,\n", + " IsentropicPressureChangerInitializer,\n", + " Mixer,\n", + " MixerInitializer,\n", + " Separator as Splitter,\n", + " SeparatorInitializer,\n", " Heater,\n", " StoichiometricReactor,\n", " Feed,\n", @@ -365,11 +368,9 @@ } }, "source": [ - "m.fs.I101 = Feed(\n", - " property_package=m.fs.thermo_params)\n", + "m.fs.I101 = Feed(property_package=m.fs.thermo_params)\n", "\n", - "m.fs.I102 = Feed(\n", - " property_package=m.fs.thermo_params)\n", + "m.fs.I102 = Feed(property_package=m.fs.thermo_params)\n", "\n", "m.fs.M101 = Mixer(\n", " property_package=m.fs.thermo_params,\n", @@ -527,14 +528,11 @@ }, "cell_type": "code", "source": [ - "m.fs.P101 = Product(\n", - " property_package=m.fs.thermo_params)\n", + "m.fs.P101 = Product(property_package=m.fs.thermo_params)\n", "\n", - "m.fs.P102 = Product(\n", - " property_package=m.fs.thermo_params)\n", + "m.fs.P102 = Product(property_package=m.fs.thermo_params)\n", "\n", - "m.fs.P103 = Product(\n", - " property_package=m.fs.thermo_params)" + "m.fs.P103 = Product(property_package=m.fs.thermo_params)" ], "outputs": [], "execution_count": 16 @@ -643,7 +641,7 @@ "m.fs.s06 = Arc(source=m.fs.F101.vap_outlet, destination=m.fs.S101.inlet)\n", "m.fs.s07 = Arc(source=m.fs.F101.liq_outlet, destination=m.fs.F102.inlet)\n", "m.fs.s08 = Arc(source=m.fs.S101.recycle, destination=m.fs.C101.inlet)\n", - "m.fs.s09 = Arc(source=m.fs.C101.outlet, destination=m.fs.M101.inlet_3)\n" + "m.fs.s09 = Arc(source=m.fs.C101.outlet, destination=m.fs.M101.inlet_3)" ], "outputs": [], "execution_count": 20 @@ -1375,9 +1373,9 @@ "metadata": {}, "cell_type": "markdown", "source": [ - "### 5.2 Manual Propogation Method\n", + "### 5.2 Manual Propagation Method\n", "\n", - "This method uses a more direct approach to initialize the flowsheet, utilizing the updated initalizer method and propogating manually throught the flowsheet and solving for the tear stream directly.\n", + "This method uses a more direct approach to initialize the flowsheet, utilizing the updated initializer method and propagating manually through the flowsheet and solving for the tear stream directly.\n", "Lets first import a helper function that will help us manually propagate and step through the flowsheet" ] }, @@ -1397,7 +1395,7 @@ "metadata": {}, "cell_type": "markdown", "source": [ - "Now we can setup our initial guesses for the tear stream which we know is the outlet of the `Mixer` or the inlet of the `Heater`. We can use the same initial guesses used in the first method. We also want to ensure that the degrees of freedom are consistent while we manually intialize the model.\n", + "Now we can setup our initial guesses for the tear stream which we know is the outlet of the `Mixer` or the inlet of the `Heater`. We can use the same initial guesses used in the first method. We also want to ensure that the degrees of freedom are consistent while we manually initialize the model.\n", "\n", "We will first ensure that are current degrees of freedom is still zero" ] @@ -1475,7 +1473,6 @@ " (0, \"Vap\", \"toluene\"): 1e-5,\n", " (0, \"Vap\", \"methane\"): 0.02,\n", " (0, \"Vap\", \"hydrogen\"): 0.30,\n", - "\n", " },\n", " \"temperature\": {0: 303},\n", " \"pressure\": {0: 350000},\n", @@ -1513,27 +1510,33 @@ }, "cell_type": "code", "source": [ - "m.fs.H101.default_initializer().initialize(m.fs.H101) # Initialize Heater\n", - "propagate_state(m.fs.s04) # Establish connection between Heater and Reactor\n", - "m.fs.R101.default_initializer().initialize(m.fs.R101) # Initialize Reactor\n", - "propagate_state(m.fs.s05) # Establish connection between Reactor and First Flash Unit\n", - "m.fs.F101.default_initializer().initialize(m.fs.F101) # Initialize First Flash Unit\n", - "propagate_state(m.fs.s06) # Establish connection between First Flash Unit and Splitter\n", - "propagate_state(m.fs.s07) # Establish connection between First Flash Unit and Second Flash Unit\n", - "m.fs.S101.default_initializer().initialize(m.fs.S101) # Initialize Splitter\n", - "propagate_state(m.fs.s08) # Establish connection between Splitter and Compressor\n", - "m.fs.C101.default_initializer().initialize(m.fs.C101) # Initialize Compressor\n", - "propagate_state(m.fs.s09) # Establish connection between Compressor and Mixer\n", - "m.fs.I101.default_initializer().initialize(m.fs.I101) # Initialize Toluene Inlet\n", - "propagate_state(m.fs.s01) # Establish connection between Toluene Inlet and Mixer\n", - "m.fs.I102.default_initializer().initialize(m.fs.I102) # Initialize Hydrogen Inlet\n", - "propagate_state(m.fs.s02) # Establish connection between Hydrogen Inlet and Mixer\n", - "m.fs.M101.default_initializer().initialize(m.fs.M101) # Initialize Mixer\n", - "propagate_state(m.fs.s03) # Establish connection between Mixer and Heater\n", - "m.fs.F102.default_initializer().initialize(m.fs.F102) # Initialize Second Flash Unit\n", - "propagate_state(m.fs.s10) # Establish connection between Second Flash Unit and Benzene Product\n", - "propagate_state(m.fs.s11) # Establish connection between Second Flash Unit and Toluene Product\n", - "propagate_state(m.fs.s12) # Establish connection between Splitter and Purge Product\n" + "m.fs.H101.default_initializer().initialize(m.fs.H101) # Initialize Heater\n", + "propagate_state(m.fs.s04) # Establish connection between Heater and Reactor\n", + "m.fs.R101.default_initializer().initialize(m.fs.R101) # Initialize Reactor\n", + "propagate_state(m.fs.s05) # Establish connection between Reactor and First Flash Unit\n", + "m.fs.F101.default_initializer().initialize(m.fs.F101) # Initialize First Flash Unit\n", + "propagate_state(m.fs.s06) # Establish connection between First Flash Unit and Splitter\n", + "propagate_state(\n", + " m.fs.s07\n", + ") # Establish connection between First Flash Unit and Second Flash Unit\n", + "m.fs.S101.default_initializer().initialize(m.fs.S101) # Initialize Splitter\n", + "propagate_state(m.fs.s08) # Establish connection between Splitter and Compressor\n", + "m.fs.C101.default_initializer().initialize(m.fs.C101) # Initialize Compressor\n", + "propagate_state(m.fs.s09) # Establish connection between Compressor and Mixer\n", + "m.fs.I101.default_initializer().initialize(m.fs.I101) # Initialize Toluene Inlet\n", + "propagate_state(m.fs.s01) # Establish connection between Toluene Inlet and Mixer\n", + "m.fs.I102.default_initializer().initialize(m.fs.I102) # Initialize Hydrogen Inlet\n", + "propagate_state(m.fs.s02) # Establish connection between Hydrogen Inlet and Mixer\n", + "m.fs.M101.default_initializer().initialize(m.fs.M101) # Initialize Mixer\n", + "propagate_state(m.fs.s03) # Establish connection between Mixer and Heater\n", + "m.fs.F102.default_initializer().initialize(m.fs.F102) # Initialize Second Flash Unit\n", + "propagate_state(\n", + " m.fs.s10\n", + ") # Establish connection between Second Flash Unit and Benzene Product\n", + "propagate_state(\n", + " m.fs.s11\n", + ") # Establish connection between Second Flash Unit and Toluene Product\n", + "propagate_state(m.fs.s12) # Establish connection between Splitter and Purge Product" ], "outputs": [ { @@ -1902,7 +1905,9 @@ " getattr(m.fs.H101.inlet, k)[k1].unfix()\n", "\n", "m.fs.s03_expanded.activate()\n", - "print(f\"The DOF is {degrees_of_freedom(m)} after unfixing the values and reactivating the tear stream\")" + "print(\n", + " f\"The DOF is {degrees_of_freedom(m)} after unfixing the values and reactivating the tear stream\"\n", + ")" ], "outputs": [ { @@ -1937,10 +1942,10 @@ "cell_type": "code", "source": [ "optarg = {\n", - " 'nlp_scaling_method': 'user-scaling',\n", - " 'OF_ma57_automatic_scaling': 'yes',\n", - " 'max_iter': 500,\n", - " 'tol': 1e-8,\n", + " \"nlp_scaling_method\": \"user-scaling\",\n", + " \"OF_ma57_automatic_scaling\": \"yes\",\n", + " \"max_iter\": 500,\n", + " \"tol\": 1e-8,\n", "}" ], "outputs": [], @@ -2000,7 +2005,7 @@ "solver = get_solver(solver_options=optarg)\n", "\n", "# Solve the model\n", - "results = solver.solve(m, tee=True)\n" + "results = solver.solve(m, tee=True)" ], "outputs": [ { @@ -2194,7 +2199,9 @@ } }, "cell_type": "code", - "source": "m.fs.visualize('HDA-Flowsheet')", + "source": [ + "m.fs.visualize(\"HDA-Flowsheet\")" + ], "outputs": [ { "name": "stdout", @@ -2222,7 +2229,7 @@ { "metadata": {}, "cell_type": "markdown", - "source": "Otherwise, we can run the `m.fs.report()` method to see a full summary of the solved flowsheet. It is recomended to adjust the width of the output as much as possible for the cleanest display." + "source": "Otherwise, we can run the `m.fs.report()` method to see a full summary of the solved flowsheet. It is recommended to adjust the width of the output as much as possible for the cleanest display." }, { "metadata": { @@ -2984,7 +2991,8 @@ } }, "source": [ - "print(f'''Optimal Values:\n", + "print(\n", + " f\"\"\"Optimal Values:\n", "\n", "H101 outlet temperature = {value(m.fs.H101.outlet.temperature[0]):.3f} K\n", "\n", @@ -2994,7 +3002,8 @@ "\n", "F102 outlet temperature = {value(m.fs.F102.vap_outlet.temperature[0]):.3f} K\n", "F102 outlet pressure = {value(m.fs.F102.vap_outlet.pressure[0]):.3f} Pa\n", - "''')" + "\"\"\"\n", + ")" ], "outputs": [ { From 6359e8170e1892057574255a5f81b541bd09e5c4 Mon Sep 17 00:00:00 2001 From: tannerpolley Date: Fri, 13 Jun 2025 17:14:09 -0600 Subject: [PATCH 3/9] Updated version numbers for GitHub Actions to version 4 --- .github/workflows/core.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/core.yml b/.github/workflows/core.yml index 0151406d..ec53a8f5 100644 --- a/.github/workflows/core.yml +++ b/.github/workflows/core.yml @@ -66,7 +66,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout source - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Run Spell Checker uses: crate-ci/typos@master @@ -92,7 +92,7 @@ jobs: - os: win64 runner-image: windows-2022 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Conda environment uses: conda-incubator/setup-miniconda@v2.2.0 with: @@ -112,7 +112,7 @@ jobs: pytest -v idaes_examples --ignore=idaes_examples/notebooks/docs/surrogates/sco2/alamo/ - name: Upload pytest-xdist worker logs if: success() || failure() - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: pytest_worker_logs path: "tests_*.log" From f819b5e53c2b75c2d2544204c6a8835f8938e26a Mon Sep 17 00:00:00 2001 From: tannerpolley Date: Mon, 16 Jun 2025 11:16:17 -0700 Subject: [PATCH 4/9] Updated version numbers for GitHub Actions to version 4 and other issues that were causing a failed pytest --- .github/workflows/core.yml | 24 ++++++------------------ 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/.github/workflows/core.yml b/.github/workflows/core.yml index ec53a8f5..59bf436a 100644 --- a/.github/workflows/core.yml +++ b/.github/workflows/core.yml @@ -40,8 +40,7 @@ env: defaults: run: - # -l: login shell, needed when using Conda run: - shell: bash -l {0} + shell: ${{ runner.os == 'Windows' && 'pwsh' || 'bash -l {0}' }} jobs: @@ -78,19 +77,8 @@ jobs: strategy: fail-fast: false matrix: - python-version: - - '3.9' - - '3.10' - - '3.11' - - '3.12' - os: - - linux - - win64 - include: - - os: linux - runner-image: ubuntu-20.04 - - os: win64 - runner-image: windows-2022 + python-version: [3.9, 3.10, 3.11, 3.12] + os: [ubuntu-20.04, windows-2022] steps: - uses: actions/checkout@v4 - name: Set up Conda environment @@ -111,8 +99,8 @@ jobs: ls idaes_examples pytest -v idaes_examples --ignore=idaes_examples/notebooks/docs/surrogates/sco2/alamo/ - name: Upload pytest-xdist worker logs - if: success() || failure() + if: always() uses: actions/upload-artifact@v4 with: - name: pytest_worker_logs - path: "tests_*.log" + name: pytest_worker_logs-${{ matrix.os }}-py${{ matrix.python-version }} + path: tests_*.log \ No newline at end of file From 66753458239048d1edd2a9bbdf71c10c48ed4932 Mon Sep 17 00:00:00 2001 From: tannerpolley Date: Mon, 16 Jun 2025 12:03:58 -0700 Subject: [PATCH 5/9] Updated version numbers for GitHub Actions to version 4 and other issues that were causing a failed pytest --- .github/workflows/core.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/core.yml b/.github/workflows/core.yml index 59bf436a..6d292414 100644 --- a/.github/workflows/core.yml +++ b/.github/workflows/core.yml @@ -40,7 +40,8 @@ env: defaults: run: - shell: ${{ runner.os == 'Windows' && 'pwsh' || 'bash -l {0}' }} + # -l: login shell, needed when using Conda run: + shell: bash -l {0} jobs: From 4e40e56469b7b5e36b2180ff664cf6e374a1d3e2 Mon Sep 17 00:00:00 2001 From: tannerpolley Date: Mon, 16 Jun 2025 13:14:50 -0700 Subject: [PATCH 6/9] Updated version numbers for GitHub Actions to version 4 and other issues that were causing a failed pytest --- .github/workflows/core.yml | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/.github/workflows/core.yml b/.github/workflows/core.yml index 6d292414..1375e99e 100644 --- a/.github/workflows/core.yml +++ b/.github/workflows/core.yml @@ -66,7 +66,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout source - uses: actions/checkout@v4 + uses: actions/checkout@v3 - name: Run Spell Checker uses: crate-ci/typos@master @@ -78,10 +78,21 @@ jobs: strategy: fail-fast: false matrix: - python-version: [3.9, 3.10, 3.11, 3.12] - os: [ubuntu-20.04, windows-2022] + python-version: + - '3.9' + - '3.10' + - '3.11' + - '3.12' + os: + - linux + - win64 + include: + - os: linux + runner-image: ubuntu-20.04 + - os: win64 + runner-image: windows-2022 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v3 - name: Set up Conda environment uses: conda-incubator/setup-miniconda@v2.2.0 with: @@ -98,10 +109,10 @@ jobs: run: | pwd ls idaes_examples - pytest -v idaes_examples --ignore=idaes_examples/notebooks/docs/surrogates/sco2/alamo/ + pytest -v idaes_examples --ignore=idaes_examples/notebooks/docs/surrogates/sco2/alamo/ - name: Upload pytest-xdist worker logs - if: always() - uses: actions/upload-artifact@v4 + if: success() || failure() + uses: actions/upload-artifact@v3 with: - name: pytest_worker_logs-${{ matrix.os }}-py${{ matrix.python-version }} - path: tests_*.log \ No newline at end of file + name: pytest_worker_logs + path: "tests_*.log" \ No newline at end of file From 0051c10ac1fd7bced472533afacb1c5438b67ab5 Mon Sep 17 00:00:00 2001 From: tannerpolley Date: Mon, 16 Jun 2025 13:18:44 -0700 Subject: [PATCH 7/9] Updated version numbers for GitHub Actions to version 4 and other issues that were causing a failed pytest --- .github/workflows/core.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/core.yml b/.github/workflows/core.yml index 1375e99e..70f5c84a 100644 --- a/.github/workflows/core.yml +++ b/.github/workflows/core.yml @@ -112,7 +112,7 @@ jobs: pytest -v idaes_examples --ignore=idaes_examples/notebooks/docs/surrogates/sco2/alamo/ - name: Upload pytest-xdist worker logs if: success() || failure() - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: pytest_worker_logs path: "tests_*.log" \ No newline at end of file From e352c2d03113c19537d04a4147eac9bc2c576c53 Mon Sep 17 00:00:00 2001 From: tannerpolley Date: Tue, 17 Jun 2025 10:25:11 -0700 Subject: [PATCH 8/9] Updated version numbers for GitHub Actions to version 4 and other issues that were causing a failed pytest --- .github/workflows/core.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/core.yml b/.github/workflows/core.yml index 70f5c84a..b0ec0cca 100644 --- a/.github/workflows/core.yml +++ b/.github/workflows/core.yml @@ -114,5 +114,5 @@ jobs: if: success() || failure() uses: actions/upload-artifact@v4 with: - name: pytest_worker_logs + name: pytest-xdist-logs-${{ matrix.python-version }}-${{ runner.os }} path: "tests_*.log" \ No newline at end of file From f8f3af4482f6b2f1d09cb47e148416106e52bff2 Mon Sep 17 00:00:00 2001 From: tannerpolley Date: Tue, 17 Jun 2025 17:33:50 -0700 Subject: [PATCH 9/9] Updated version numbers for GitHub Actions to version 4 and other issues that were causing a failed pytest --- .github/workflows/core.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/core.yml b/.github/workflows/core.yml index b0ec0cca..42610e1f 100644 --- a/.github/workflows/core.yml +++ b/.github/workflows/core.yml @@ -88,7 +88,7 @@ jobs: - win64 include: - os: linux - runner-image: ubuntu-20.04 + runner-image: ubuntu-latest - os: win64 runner-image: windows-2022 steps: