{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Gotchas!" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "BioSTEAM reduces the time needed to develop and evaluate a production process by simplifying pressure handling and by providing optional modeling assumptions and a range of alternative specifications. New users may not expect some of these \"BioSTEAM\" behaviours. This chapter lists common \"gotchas\" so that you won't have to scratch your brain too hard. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Mixer outlet pressure" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "When streams at different pressures are mixed, BioSTEAM assumes valves reduce the pressure of inlet streams to prevent backflow:" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Stream: s_out from \n", "phase: 'l', T: 298.15 K, P: 101325 Pa\n", "flow (kmol/hr): Water 0.111\n" ] } ], "source": [ "import biosteam as bst\n", "from biorefineries.sugarcane import chemicals\n", "bst.nbtutorial()\n", "bst.settings.set_thermo(chemicals)\n", "\n", "# Mix streams at different pressures\n", "s_in1 = bst.Stream('s_in1', Water=1, units='kg/hr',P=2*101325)\n", "s_in2 = bst.Stream('s_in2', Water=1, units='kg/hr', P=101325)\n", "M1 = bst.Mixer('M1', ins=[s_in1, s_in2], outs='s_out')\n", "M1.simulate()\n", "M1.outs[0].show() # Note how the outlet stream pressure is the minimum inlet stream pressure" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Optional phase equilibrium." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Mixing streams with different phases does not perform vapor-liquid equilibrium (VLE) by default:" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Stream: s_out from \n", "phase: 'l', T: 581.71 K, P: 101325 Pa\n", "flow (kmol/hr): Water 0.111\n" ] } ], "source": [ "s_in2.phase = 'g'\n", "s_in2.T = 380\n", "M1.simulate()\n", "M1.outs[0].show() # Notice how the outlet is liquid and above the boiling point (energy balance is conserved)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "For rigorous VLE during mixing, set `rigorous=True`:" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "MultiStream: s_out from \n", "phases: ('g', 'l'), T: 373.12 K, P: 101325 Pa\n", "flow (kmol/hr): (g) Water 0.0481\n", " (l) Water 0.0629\n" ] } ], "source": [ "M1.rigorous = True\n", "M1.simulate()\n", "M1.outs[0].show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Heat exchangers also don't perform VLE by default:" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Stream: outlet from \n", "phase: 'l', T: 400 K, P: 101325 Pa\n", "flow (kmol/hr): Water 55.5\n" ] } ], "source": [ "H1 = bst.HXutility(\n", " 'H1', ins=bst.Stream('feed', Water=1000, units='kg/hr'), outs='outlet', T=400\n", ")\n", "H1.simulate()\n", "H1.outs[0].show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Set `rigorous=True` for rigorous VLE during:" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "MultiStream: outlet from \n", "phases: ('g', 'l'), T: 400 K, P: 101325 Pa\n", "flow (kmol/hr): (g) Water 55.5\n" ] } ], "source": [ "H1.rigorous = True\n", "H1.simulate()\n", "H1.outs[0].show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Streams do not perform phase equlibrium when initialized:" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Stream: s1\n", "phase: 'l', T: 400 K, P: 101325 Pa\n", "flow (kmol/hr): Water 1\n", " Ethanol 1\n" ] } ], "source": [ "s1 = bst.Stream('s1', Water=1, Ethanol=1, T=400, P=101325)\n", "s1.show() # It should be a gas, but the phase defaults to liquid" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To perform phase equilibrium during initialization, pass `vlle=True`:" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Stream: s2\n", "phase: 'g', T: 400 K, P: 101325 Pa\n", "flow (kmol/hr): Water 1\n", " Ethanol 1\n" ] } ], "source": [ "s2 = bst.Stream('s2', Water=1, Ethanol=1, T=400, P=101325, vlle=True)\n", "s2.show() # Now it is gas, as it should be" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Simplified pressure handling" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Pressure drops of unit operations are not accounted for due to the tremendous amount of detail required to do so (e.g., specifying pipe fittings and dimensions). Ultimately, we just want to estimate pump sizes and motor requirements. As a preliminary assumption, we can specify the design pressure gain that a pump must operate, `dP_design`, to estimate these requirements:" ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "tags": [ "nbval-ignore-output" ] }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Stream: s4 from \n", "phase: 'l', T: 298.15 K, P: 101325 Pa\n", "flow (kmol/hr): Water 55.5\n", "\n", "Pump Units P1\n", "Electricity Power kW 0.321\n", " Cost USD/hr 0.0251\n", "Design Type Centrifugal\n", " Ideal power hp 0.151\n", " Flow rate gpm 4.42\n", " Efficiency 0.352\n", " Power hp 0.43\n", " Head ft 386\n", " Motor size hp 0.5\n", "Purchase cost Pump USD 4.32e+03\n", " Motor USD 289\n", "Total purchase cost USD 4.61e+03\n", "Utility cost USD/hr 0.0251\n" ] } ], "source": [ "s3 = bst.Stream('s3', Water=1000, units='kg/hr')\n", "P1 = bst.Pump('P1', ins=s3, outs='s4', P=101325, dP_design=4 * 101325)\n", "P1.simulate()\n", "# Note how, although inlet and outlet pressures are the same, the pump is assummed to work at a 4 atm pressure gain\n", "P1.outs[0].show() \n", "print()\n", "print(P1.results())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Indexers for everything!" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Indexers are very powerful tools for faster development in BioSTEAM, but may lead to some confusion. Here we look into the many use cases for clarity. In BioSTEAM, all flow rate data is stored in mol, but we can easily access mass flow rate data through indexers which reference molar data:" ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "tags": [ "nbval-ignore-output" ] }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "55.508435061791985 1000.0\n" ] } ], "source": [ "s5 = bst.Stream('s5', Water=1000, units='kg/hr')\n", "print(s5.imol['Water'], s5.imass['Water'])" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([1000., 0.])" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "s5.imass['Water', 'Ethanol']" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Chemical groups (e.g., the \"fiber\" group defined below) can be used to get and set \"lumped\" chemical flow rates in a stream. However, they are not linked to the stream's flow rate data:" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Stream: s6\n", "phase: 'l', T: 298.15 K, P: 101325 Pa\n", "flow (kg/hr): Water 1\n", " Cellulose 0.47\n", " Hemicellulose 0.28\n", " Lignin 0.25\n" ] } ], "source": [ "# Define a lignocellulose chemical group with composition\n", "# 25, 47, and 28 wt. % lignin, cellulose, and hemicellulose, respectively\n", "chemicals.define_group(\n", " name='fiber', \n", " IDs=['Lignin', 'Cellulose', 'Hemicellulose'],\n", " composition=[0.25, 0.47, 0.28], \n", " wt=True,\n", ")\n", "\n", "s6 = bst.Stream('s6', Water=1, fiber=1, units='kg/hr')\n", "s6.show(flow='kg/hr')" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "1.0" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "s6.imass['fiber'] " ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([1., 1.])" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "s6.imass['fiber', 'Water'] # All data indexed with chemical groups are also floats" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Stream: s6\n", "phase: 'l', T: 298.15 K, P: 101325 Pa\n", "flow (kmol/hr): Water 5.55\n", " Cellulose 0.29\n", " Hemicellulose 0.212\n", " Lignin 0.164\n" ] } ], "source": [ "s6.imass['fiber', 'Water'] = [100, 100] \n", "s6.show() # Note how we can still set flow rates this way" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "When chemical groups are used to define chemicals splits (e.g., at a splitter), they work as nested indices with broadcasting:" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "SplitIndexer:\n", "Water 0.1\n", "Cellulose 0.5\n", "Hemicellulose 0.5\n", "Lignin 0.5\n" ] } ], "source": [ "s7 = bst.Stream('s7', Water=1, fiber=1, units='kg/hr')\n", "S1 = bst.Splitter('S1', ins=s7, split=dict(Water=0.1, fiber=0.5))\n", "S1.isplit.show() # Note how the split is broadcasted to all components" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([0.5, 0.5, 0.5])" ] }, "execution_count": 16, "metadata": {}, "output_type": "execute_result" } ], "source": [ "S1.isplit['fiber'] # Note how the splits for each component is returned (not the sum)" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([array([0.5, 0.5, 0.5]), 0.1], dtype=object)" ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "S1.isplit['fiber', 'Water'] # Note how the splits are nested" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "SplitIndexer:\n", "Water 0.1\n", "Cellulose 0.5\n", "Hemicellulose 0.7\n", "Lignin 0.2\n" ] } ], "source": [ "S1.isplit['fiber'] = [0.2, 0.5, 0.7] # This is also valid\n", "S1.isplit.show()" ] } ], "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.4" } }, "nbformat": 4, "nbformat_minor": 2 }