From ce3458bf6224e17d1ccde48e6f3450d3ccb4d7d0 Mon Sep 17 00:00:00 2001 From: Dan Gunter Date: Thu, 2 Nov 2023 10:45:02 -0700 Subject: [PATCH 01/12] outputs --- idaes/core/base/flowsheet_model.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/idaes/core/base/flowsheet_model.py b/idaes/core/base/flowsheet_model.py index d0775ac2b7..f8e92c104b 100644 --- a/idaes/core/base/flowsheet_model.py +++ b/idaes/core/base/flowsheet_model.py @@ -74,10 +74,7 @@ def __init__(self): self.visualize = self._visualize_null self.installed = False else: - # FIXME the explicit submodule import is needed because the idaes_ui doesn't import its fv submodule - # otherwise, you get "AttributeError: module 'idaes_ui' has no 'fv' attribute" - import idaes_ui.fv - + import idaes_ui self.visualize = idaes_ui.fv.visualize self.installed = True From aa77236093a985edeb8df68500d73ed551e40459 Mon Sep 17 00:00:00 2001 From: Dan Gunter Date: Wed, 2 Oct 2024 07:55:04 -0700 Subject: [PATCH 02/12] initial code --- .../fixed_bed_tsa0d_ui.py | 24 +++++++++++++++++++ .../tests/test_fixed_bed_tsa0d_ui.py | 7 ++++++ 2 files changed, 31 insertions(+) create mode 100644 idaes/models_extra/temperature_swing_adsorption/fixed_bed_tsa0d_ui.py create mode 100644 idaes/models_extra/temperature_swing_adsorption/tests/test_fixed_bed_tsa0d_ui.py diff --git a/idaes/models_extra/temperature_swing_adsorption/fixed_bed_tsa0d_ui.py b/idaes/models_extra/temperature_swing_adsorption/fixed_bed_tsa0d_ui.py new file mode 100644 index 0000000000..2ff2c54f15 --- /dev/null +++ b/idaes/models_extra/temperature_swing_adsorption/fixed_bed_tsa0d_ui.py @@ -0,0 +1,24 @@ +################################################################################# +# The Institute for the Design of Advanced Energy Systems Integrated Platform +# Framework (IDAES IP) was produced under the DOE Institute for the +# Design of Advanced Energy Systems (IDAES). +# +# Copyright (c) 2018-2024 by the software owners: The Regents of the +# University of California, through Lawrence Berkeley National Laboratory, +# National Technology & Engineering Solutions of Sandia, LLC, Carnegie Mellon +# University, West Virginia University Research Corporation, et al. +# All rights reserved. Please see the files COPYRIGHT.md and LICENSE.md +# for full copyright and license information. +################################################################################# + +""" +UI exports for 0D Fixed Bed TSA unit model. +""" +from .fixed_bed_tsa0d import FixedBedTSA0D as Model +# from idaes.core.ui import fsapi as w -- the world according to dang +from watertap.ui import fsapi as w + + +def export_to_ui() -> w.FlowsheetInterface: + # TO-DO: The wrapper + return None \ No newline at end of file diff --git a/idaes/models_extra/temperature_swing_adsorption/tests/test_fixed_bed_tsa0d_ui.py b/idaes/models_extra/temperature_swing_adsorption/tests/test_fixed_bed_tsa0d_ui.py new file mode 100644 index 0000000000..01261961bd --- /dev/null +++ b/idaes/models_extra/temperature_swing_adsorption/tests/test_fixed_bed_tsa0d_ui.py @@ -0,0 +1,7 @@ +import pytest +from ..fixed_bed_tsa0d_ui import export_to_ui + +@pytest.mark.component +def test_export(): + ui = export_to_ui() + assert ui is not None From b0c516c137e1a16e6b3d70d668a63bc58cff1fd1 Mon Sep 17 00:00:00 2001 From: Dan Gunter Date: Thu, 10 Oct 2024 08:44:26 -0700 Subject: [PATCH 03/12] working with a couple of feed vars and model options --- .../fixed_bed_tsa0d_ui.py | 142 +++++++++++++++++- .../tests/test_fixed_bed_tsa0d_ui.py | 12 +- 2 files changed, 148 insertions(+), 6 deletions(-) diff --git a/idaes/models_extra/temperature_swing_adsorption/fixed_bed_tsa0d_ui.py b/idaes/models_extra/temperature_swing_adsorption/fixed_bed_tsa0d_ui.py index 2ff2c54f15..98b3480c58 100644 --- a/idaes/models_extra/temperature_swing_adsorption/fixed_bed_tsa0d_ui.py +++ b/idaes/models_extra/temperature_swing_adsorption/fixed_bed_tsa0d_ui.py @@ -14,11 +14,143 @@ """ UI exports for 0D Fixed Bed TSA unit model. """ -from .fixed_bed_tsa0d import FixedBedTSA0D as Model -# from idaes.core.ui import fsapi as w -- the world according to dang -from watertap.ui import fsapi as w +from watertap.ui import fsapi as api +from pyomo.environ import ConcreteModel, units +from idaes.core.solvers import get_solver +from idaes.core import FlowsheetBlock +from idaes.models_extra.temperature_swing_adsorption import ( + FixedBedTSA0D, + Adsorbent, + FixedBedTSA0DInitializer, + SteamCalculationType, + TransformationScheme, +) +desc = """The Fixed Bed TSA0d model is implemented as a Temperature Swing Adsorption +(TSA) cycle. The model is an 0D equilibrium-based shortcut model. The model +assumes local adsorption equilibrium and takes into account heat transfer +mechanisms but neglects mass transfer resistances.""".replace( + "\n", " " +) -def export_to_ui() -> w.FlowsheetInterface: +unit_name = "FixedBedTSA0D" + + +def export_to_ui() -> api.FlowsheetInterface: + fsi = api.FlowsheetInterface( + name="0D Fixed Bed TSA", + description=desc, + do_export=export, + do_build=build, + do_solve=solve, + build_options={ + "adsorbent": { + "name": "Adsorbent", + "display_name": "Adsorbent material", + "values_allowed": [ + "zeolite_13x", + "mmen_mg_mof_74", + "polystyrene_amine", + ], + "value": "zeolite_13x", + "category": unit_name, + }, + "number_of_beds": { + "name": "number_of_beds", + "display_name": "Number of beds", + "description": "Number of beds in fixed bed TSA system used to split the mole flow rate at feed", + "min_val": 1, + "max_val": 100000, + "value": 120, + "values_allowed": "int", + "category": unit_name, + }, + }, + ) # TO-DO: The wrapper - return None \ No newline at end of file + return fsi + + +def build(build_options=None, **kwargs): + unit_opt = {k: v for k, v in build_options.items() if v.category == unit_name} + m = ConcreteModel() + m.fs = FlowsheetBlock(dynamic=False) + m.fs.unit = FixedBedTSA0D( + adsorbent=Adsorbent[unit_opt["adsorbent"].value], + number_of_beds=unit_opt["number_of_beds"].value, + transformation_method="dae.collocation", + transformation_scheme=TransformationScheme.lagrangeRadau, + finite_elements=20, + collocation_points=6, + compressor=False, + steam_calculation=SteamCalculationType.none, + ) + + m.fs.unit.inlet.flow_mol_comp[0, "H2O"].fix(0) + m.fs.unit.inlet.flow_mol_comp[0, "CO2"].fix(40) + m.fs.unit.inlet.flow_mol_comp[0, "N2"].fix(99960) + m.fs.unit.inlet.flow_mol_comp[0, "O2"].fix(0) + m.fs.unit.inlet.temperature.fix(303.15) + m.fs.unit.inlet.pressure.fix(100000) + + m.fs.unit.temperature_desorption.fix(470) + m.fs.unit.temperature_adsorption.fix(310) + m.fs.unit.temperature_heating.fix(500) + m.fs.unit.temperature_cooling.fix(300) + m.fs.unit.bed_diameter.fix(4) + m.fs.unit.bed_height.fix(8) + + return m + + +def export(flowsheet=None, exports=None, build_options=None, **kwargs): + fs = flowsheet + # Feeds + for compound in "H2O", "CO2", "N2", "O2": + exports.add( + obj=fs.unit.inlet.flow_mol_comp[0, compound], + name=f"{compound} molar flow rate", + ui_units=units.mole / units.s, + display_units="mol/s", + rounding=2, + description=f"Inlet molar flow rate for {compound}", + is_input=True, + input_category="Feed", + is_output=False, + ) + exports.add( + obj=fs.unit.inlet.temperature[0], + name="Temperature", + ui_units=units.K, + display_units="K", + is_input=True, + input_category="Feed", + ) + exports.add( + obj=fs.unit.inlet.pressure[0], + name="Pressure", + ui_units=units.bar, + display_units="bar", + is_input=True, + input_category="Feed", + ) + # TODO: export all the variables + # Parameters + # Products + + +def initialize(fs, **kwargs): + """Initialize the model.""" + initializer = FixedBedTSA0DInitializer() + initializer.initialize(fs.unit, heating_time_guess=70000, cooling_time_guess=110000) + + +solver = get_solver() + + +def solve(flowsheet=None, **kwargs): + """Solve the model.""" + fs = flowsheet + initialize(fs) # XXX: skip when do_initialize is in FlowsheetInterface ctor + results = solver.solve(fs.model()) + return results diff --git a/idaes/models_extra/temperature_swing_adsorption/tests/test_fixed_bed_tsa0d_ui.py b/idaes/models_extra/temperature_swing_adsorption/tests/test_fixed_bed_tsa0d_ui.py index 01261961bd..f92f4ee196 100644 --- a/idaes/models_extra/temperature_swing_adsorption/tests/test_fixed_bed_tsa0d_ui.py +++ b/idaes/models_extra/temperature_swing_adsorption/tests/test_fixed_bed_tsa0d_ui.py @@ -1,7 +1,17 @@ import pytest -from ..fixed_bed_tsa0d_ui import export_to_ui +from pyomo.environ import check_optimal_termination +from ..fixed_bed_tsa0d_ui import export_to_ui, build + @pytest.mark.component def test_export(): ui = export_to_ui() assert ui is not None + + +@pytest.mark.component +def test_solve_base(): + ui = export_to_ui() + ui.build(build_options=ui.fs_exp.build_options) + r = ui.solve() + assert check_optimal_termination(r) From 8ab63fbff6dcc1cfc2313aa3864dd6265d7800c6 Mon Sep 17 00:00:00 2001 From: Sheng Pang Date: Thu, 14 Nov 2024 12:48:27 -0800 Subject: [PATCH 04/12] update test set test for default build params --- .../tests/test_fixed_bed_tsa0d_ui.py | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/idaes/models_extra/temperature_swing_adsorption/tests/test_fixed_bed_tsa0d_ui.py b/idaes/models_extra/temperature_swing_adsorption/tests/test_fixed_bed_tsa0d_ui.py index f92f4ee196..3bc169fb9a 100644 --- a/idaes/models_extra/temperature_swing_adsorption/tests/test_fixed_bed_tsa0d_ui.py +++ b/idaes/models_extra/temperature_swing_adsorption/tests/test_fixed_bed_tsa0d_ui.py @@ -1,6 +1,14 @@ import pytest +import logging from pyomo.environ import check_optimal_termination -from ..fixed_bed_tsa0d_ui import export_to_ui, build +from idaes.models_extra.temperature_swing_adsorption.fixed_bed_tsa0d_ui import ( + export_to_ui, + build, + initialize, + solve, +) + +_log = logging.getLogger(__name__) @pytest.mark.component @@ -15,3 +23,22 @@ def test_solve_base(): ui.build(build_options=ui.fs_exp.build_options) r = ui.solve() assert check_optimal_termination(r) + + +@pytest.mark.component +def test_default_build_options(): + """test default build options, option values from jupyter notebook example""" + default_build_options = { + "adsorbent": "zeolite_13x", + "number_of_beds": 1, + "transformation_method": "dae.collocation", + "transformation_scheme": "lagrangeRadau", + "finite_elements": 20, + "collocation_points": 6, + } + ui = export_to_ui() + ui.build(build_options=ui.fs_exp.build_options) + + for key, val in ui.fs_exp.build_options.items(): + assert default_build_options[key] == val.value + assert True From 5d40a2b133f6d5af2a9dd5527ba1fa22db36119f Mon Sep 17 00:00:00 2001 From: Sheng Pang Date: Thu, 14 Nov 2024 12:52:13 -0800 Subject: [PATCH 05/12] wrap tsa model, todo set correct rounding --- .../fixed_bed_tsa0d_ui.py | 543 ++++++++++++++++-- 1 file changed, 491 insertions(+), 52 deletions(-) diff --git a/idaes/models_extra/temperature_swing_adsorption/fixed_bed_tsa0d_ui.py b/idaes/models_extra/temperature_swing_adsorption/fixed_bed_tsa0d_ui.py index 98b3480c58..aaed5a9af0 100644 --- a/idaes/models_extra/temperature_swing_adsorption/fixed_bed_tsa0d_ui.py +++ b/idaes/models_extra/temperature_swing_adsorption/fixed_bed_tsa0d_ui.py @@ -14,32 +14,44 @@ """ UI exports for 0D Fixed Bed TSA unit model. """ +from datetime import datetime from watertap.ui import fsapi as api -from pyomo.environ import ConcreteModel, units -from idaes.core.solvers import get_solver +from pyomo.environ import ( + ConcreteModel, + SolverFactory, + value, + units, +) + from idaes.core import FlowsheetBlock +from idaes.core.util.model_statistics import degrees_of_freedom +import idaes.core.util as iutil +from idaes.core.solvers import get_solver +from idaes.core.util.exceptions import ConfigurationError +import idaes.logger as idaeslog + + from idaes.models_extra.temperature_swing_adsorption import ( FixedBedTSA0D, - Adsorbent, FixedBedTSA0DInitializer, + Adsorbent, SteamCalculationType, - TransformationScheme, ) -desc = """The Fixed Bed TSA0d model is implemented as a Temperature Swing Adsorption -(TSA) cycle. The model is an 0D equilibrium-based shortcut model. The model -assumes local adsorption equilibrium and takes into account heat transfer -mechanisms but neglects mass transfer resistances.""".replace( - "\n", " " +from idaes.models_extra.temperature_swing_adsorption.util import ( + tsa_summary, + plot_tsa_profiles, ) +model_name = "0D Fixed Bed TSA" +model_name_for_ui = f"0D Fixed Bed TSA - {datetime.now()}" unit_name = "FixedBedTSA0D" def export_to_ui() -> api.FlowsheetInterface: fsi = api.FlowsheetInterface( name="0D Fixed Bed TSA", - description=desc, + description=model_name_for_ui, # The water tap UI use description as flowsheet name do_export=export, do_build=build, do_solve=solve, @@ -61,96 +73,523 @@ def export_to_ui() -> api.FlowsheetInterface: "description": "Number of beds in fixed bed TSA system used to split the mole flow rate at feed", "min_val": 1, "max_val": 100000, - "value": 120, + "value": 1, + "values_allowed": "int", + "category": unit_name, + }, + "transformation_method": { + "name": "transformation_method", + "display_name": "Transformation Method", + "description": "Method to use for DAE transformation", + "values_allowed": ["dae.finite_difference", "dae.collocation"], + "value": "dae.collocation", + "category": unit_name, + }, + "transformation_scheme": { + "name": "transformation_scheme", + "display_name": "Transformation Scheme", + "values_allowed": [ + "useDefault", + "backward", + "forward", + "lagrangeRadau", + ], + "value": "lagrangeRadau", + "category": unit_name, + }, + "finite_elements": { + "name": "finite_elements", + "display_name": "Number of Finite Elements", + "description": "Number of finite elements to use when discretizing time domain", + "min_val": 0, + "max_val": 10000, + "value": 20, + "values_allowed": "int", + "category": unit_name, + }, + "collocation_points": { + "name": "collocation_points", + "display_name": "Collocation Points", + "description": "Number of collocation points per finite element", + "min_val": 0, + "max_val": 10000, + "value": 6, "values_allowed": "int", "category": unit_name, }, }, ) - # TO-DO: The wrapper return fsi def build(build_options=None, **kwargs): - unit_opt = {k: v for k, v in build_options.items() if v.category == unit_name} m = ConcreteModel() m.fs = FlowsheetBlock(dynamic=False) - m.fs.unit = FixedBedTSA0D( - adsorbent=Adsorbent[unit_opt["adsorbent"].value], - number_of_beds=unit_opt["number_of_beds"].value, - transformation_method="dae.collocation", - transformation_scheme=TransformationScheme.lagrangeRadau, - finite_elements=20, - collocation_points=6, - compressor=False, + + unit_opt = {k: v for k, v in build_options.items() if v.category == unit_name} + + m.fs.tsa = FixedBedTSA0D( + adsorbent=Adsorbent[unit_opt["adsorbent"].value], # Use zeolite_13x + number_of_beds=unit_opt["number_of_beds"].value, # Set to 1 + transformation_method=unit_opt[ + "transformation_method" + ].value, # Use dae.collocation + transformation_scheme=unit_opt[ + "transformation_scheme" + ].value, # default Use lagrangeRadau + finite_elements=unit_opt["finite_elements"].value, # Set to 20 + collocation_points=unit_opt["collocation_points"].value, # Set to 6 steam_calculation=SteamCalculationType.none, ) - m.fs.unit.inlet.flow_mol_comp[0, "H2O"].fix(0) - m.fs.unit.inlet.flow_mol_comp[0, "CO2"].fix(40) - m.fs.unit.inlet.flow_mol_comp[0, "N2"].fix(99960) - m.fs.unit.inlet.flow_mol_comp[0, "O2"].fix(0) - m.fs.unit.inlet.temperature.fix(303.15) - m.fs.unit.inlet.pressure.fix(100000) + flue_gas = { + "flow_mol_comp": { + "H2O": 0.0, + "CO2": 0.00960 * 0.12, + "N2": 0.00960 * 0.88, + "O2": 0.0, + }, + "temperature": 300.0, + "pressure": 1.0e5, + } + for i in m.fs.tsa.component_list: + m.fs.tsa.inlet.flow_mol_comp[:, i].fix(flue_gas["flow_mol_comp"][i]) + m.fs.tsa.inlet.temperature.fix(flue_gas["temperature"]) + m.fs.tsa.inlet.pressure.fix(flue_gas["pressure"]) - m.fs.unit.temperature_desorption.fix(470) - m.fs.unit.temperature_adsorption.fix(310) - m.fs.unit.temperature_heating.fix(500) - m.fs.unit.temperature_cooling.fix(300) - m.fs.unit.bed_diameter.fix(4) - m.fs.unit.bed_height.fix(8) + m.fs.tsa.temperature_desorption.fix(430) + m.fs.tsa.temperature_adsorption.fix(310) + m.fs.tsa.temperature_heating.fix(440) + m.fs.tsa.temperature_cooling.fix(300) + m.fs.tsa.bed_diameter.fix(3 / 100) + m.fs.tsa.bed_height.fix(1.2) return m def export(flowsheet=None, exports=None, build_options=None, **kwargs): fs = flowsheet - # Feeds + for compound in "H2O", "CO2", "N2", "O2": exports.add( - obj=fs.unit.inlet.flow_mol_comp[0, compound], + obj=fs.tsa.inlet.flow_mol_comp[0, compound], name=f"{compound} molar flow rate", ui_units=units.mole / units.s, display_units="mol/s", - rounding=2, description=f"Inlet molar flow rate for {compound}", - is_input=True, + rounding=6, input_category="Feed", - is_output=False, + output_category="Feed", + is_input=True, + is_output=True, ) + exports.add( - obj=fs.unit.inlet.temperature[0], + obj=fs.tsa.inlet.temperature[0], name="Temperature", ui_units=units.K, display_units="K", - is_input=True, + rounding=6, input_category="Feed", + output_category="Feed", + is_input=True, + is_output=True, + ) + + # equipment parameters + exports.add( + obj=fs.tsa.number_beds, # Number of beds + name="Number of beds", + ui_units=units.dimensionless, + display_units="-", + rounding=6, + output_category="Equipment Parameters", + is_input=False, + is_output=True, ) exports.add( - obj=fs.unit.inlet.pressure[0], - name="Pressure", - ui_units=units.bar, - display_units="bar", + obj=fs.tsa.bed_diameter, + name="Bed diameter", + ui_units=units.m, + display_units="m", + rounding=6, + input_category="Equipment Parameters", + output_category="Equipment Parameters", is_input=True, - input_category="Feed", + is_output=True, + ) + exports.add( + obj=fs.tsa.bed_height, + name="Bed length", + ui_units=units.m, + display_units="m", + rounding=6, + input_category="Equipment Parameters", + output_category="Equipment Parameters", + is_input=True, + is_output=True, + ) + + exports.add( + obj=fs.tsa.temperature_adsorption, + name="Adsorption temperature", + ui_units=units.K, + display_units="K", + rounding=6, + input_category="Operating Parameters", + output_category="Operating Parameters", + is_input=True, + is_output=True, + ) + + exports.add( + obj=fs.tsa.temperature_desorption, + name="Desorption temperature", + ui_units=units.K, + display_units="K", + rounding=6, + input_category="Operating Parameters", + output_category="Operating Parameters", + is_input=True, + is_output=True, + ) + exports.add( + obj=fs.tsa.temperature_heating, + name="Heating temperature", + ui_units=units.K, + display_units="K", + rounding=6, + input_category="Operating Parameters", + output_category="Operating Parameters", + is_input=True, + is_output=True, + ) + exports.add( + obj=fs.tsa.temperature_cooling, + name="Cooling temperature", + ui_units=units.K, + display_units="K", + rounding=6, + input_category="Operating Parameters", + output_category="Operating Parameters", + is_input=True, + is_output=True, + ) + + exports.add( + obj=fs.tsa.bed_volume, + name="Column volume", + ui_units=units.m**3, + display_units="m3", + rounding=6, + output_category="Operating Parameters", + is_input=False, + is_output=True, + ) + exports.add( + obj=fs.tsa.mole_frac_in["CO2"] * 100, + name="CO2 mole fraction at feed", + ui_units=units.dimensionless, + display_units="%", + rounding=6, + output_category="Operating Parameters", + is_input=False, + is_output=True, + ) + + exports.add( + obj=fs.tsa.flow_mol_in_total, + name="Feed flow rate", + ui_units=units.mol / units.s, + display_units="mol/s", + rounding=6, + input_category="Operating Parameters", + output_category="Operating Parameters", + is_input=True, + is_output=True, + ) + + exports.add( + obj=fs.tsa.velocity_in, + name="Feed velocity", + ui_units=units.m / units.s, + display_units="m/s", + rounding=6, + output_category="Operating Parameters", + is_input=False, + is_output=True, + ) + + # Minimum fluidization velocity + exports.add( + obj=fs.tsa.velocity_mf, + name="Minimum fluidization velocity", + ui_units=units.m / units.s, + display_units="m/s", + rounding=6, + input_category="Operating Parameters", + output_category="Operating Parameters", + is_input=True, + is_output=True, + ) + + # Time steps + exports.add( + obj=fs.tsa.heating.time, # Time of heating step + name="Time of heating step", + ui_units=units.h, + display_units="h", + rounding=6, + input_category="Operating Parameters", + output_category="Operating Parameters", + is_input=True, + is_output=True, + ) + + exports.add( + obj=fs.tsa.cooling.time, # Time of cooling step + name="Time of cooling step", + ui_units=units.h, + display_units="h", + rounding=6, + input_category="Operating Parameters", + output_category="Operating Parameters", + is_input=True, + is_output=True, + ) + + exports.add( + obj=fs.tsa.pressurization.time, # Time of pressurization step + name="Time of pressurization step", + ui_units=units.h, + display_units="h", + rounding=6, + input_category="Operating Parameters", + output_category="Operating Parameters", + is_input=True, + is_output=True, + ) + + exports.add( + obj=fs.tsa.adsorption.time, # Time of adsorption step + name="Time of adsorption step", + ui_units=units.h, + display_units="h", + rounding=6, + input_category="Operating Parameters", + output_category="Operating Parameters", + is_input=True, + is_output=True, + ) + + exports.add( + obj=fs.tsa.cycle_time, # Cycle time + name="Cycle time", + ui_units=units.h, + display_units="h", + rounding=6, + input_category="Operating Parameters", + output_category="Operating Parameters", + is_input=True, + is_output=True, + ) + + # Pressure and Physical Parameters + exports.add( + obj=fs.tsa.pressure_drop, # Pressure drop + name="Pressure drop", + ui_units=units.Pa, + display_units="Pa", + rounding=6, + input_category="Operating Parameters", + output_category="Operating Parameters", + is_input=True, + is_output=True, + ) + + # Performance metrics + exports.add( + obj=fs.tsa.purity, # Purity + name="Purity", + ui_units=units.dimensionless, + display_units="-", + rounding=6, + input_category="Performance Metrics", + output_category="Performance Metrics", + is_input=True, + is_output=True, + ) + + exports.add( + obj=fs.tsa.recovery, # Recovery + name="Recovery", + ui_units=units.dimensionless, + display_units="-", + rounding=6, + input_category="Performance Metrics", + output_category="Performance Metrics", + is_input=True, + is_output=True, + ) + + exports.add( + obj=fs.tsa.productivity, # Productivity + name="Productivity", + ui_units=units.kg / units.ton / units.h, + display_units="kg CO2/ton/h", + rounding=6, + input_category="Performance Metrics", + output_category="Performance Metrics", + is_input=True, + is_output=True, + ) + + exports.add( + obj=fs.tsa.specific_energy, + name="Specific energy", + ui_units=units.MJ / units.kg, + display_units="MJ/kg CO2", + rounding=6, + input_category="Performance Metrics", + output_category="Performance Metrics", + is_input=True, + is_output=True, + ) + + exports.add( + obj=fs.tsa.heat_duty_bed, + name="Heat duty per bed", + ui_units=units.MW, + display_units="MW", + rounding=6, + output_category="Performance Metrics", + is_input=False, + is_output=True, + ) + + exports.add( + obj=fs.tsa.heat_duty_total, + name="Total heat duty", + ui_units=units.MW, + display_units="MW", + rounding=6, + output_category="Performance Metrics", + is_input=False, + is_output=True, + ) + + # CO2 Capture Performance + exports.add( + obj=fs.tsa.CO2_captured_bed_cycle, # CO2 captured per cycle per bed + name="CO2 captured in one cycle per bed", + ui_units=units.kg, + display_units="kg/cycle", + rounding=6, + output_category="Performance Metrics", + is_input=False, + is_output=True, + ) + + exports.add( + obj=fs.tsa.cycles_year, + name="Cycles per year", + ui_units=1 / units.year, + display_units="cycles/year", + rounding=6, + output_category="Performance Metrics", + is_input=False, + is_output=True, + ) + + exports.add( + obj=fs.tsa.total_CO2_captured_year, # Total CO2 captured per year + name="Total CO2 captured per year", + ui_units=units.tonne / units.year, + display_units="tonne/year", + rounding=6, + output_category="Performance Metrics", + is_input=False, + is_output=True, + ) + + # Flue Gas Processing + exports.add( + obj=fs.tsa.flue_gas_processed_year, + name="Amount of flue gas processed per year", + ui_units=units.Gmol / units.year, # Gmol/year + display_units="Gmol/year", + rounding=6, + output_category="Performance Metrics", + is_input=False, + is_output=True, + ) + + exports.add( + obj=fs.tsa.flue_gas_processed_year_target, + name="Amount of flue gas processed per year (target)", + ui_units=units.Gmol / units.year, + display_units="Gmol/year", + rounding=6, + output_category="Performance Metrics", + is_input=False, + is_output=True, + ) + + # CO2 Emissions + exports.add( + obj=fs.tsa.emissions_co2, + name="Amount of CO2 to atmosphere", + ui_units=units.mol / units.s, + display_units="mol/s", + rounding=6, + output_category="Performance Metrics", + is_input=False, + is_output=True, + ) + + exports.add( + obj=fs.tsa.emissions_co2_ppm, # CO2 concentration in emissions + name="Concentration of CO2 emitted to atmosphere", + ui_units=units.dimensionless, + display_units="ppm", + rounding=6, + output_category="Performance Metrics", + is_input=False, + is_output=True, ) - # TODO: export all the variables - # Parameters - # Products def initialize(fs, **kwargs): """Initialize the model.""" - initializer = FixedBedTSA0DInitializer() - initializer.initialize(fs.unit, heating_time_guess=70000, cooling_time_guess=110000) + solver_options = { + "nlp_scaling_method": "user-scaling", + "tol": 1e-6, + } + + initializer = FixedBedTSA0DInitializer( + output_level=idaeslog.INFO, solver_options=solver_options + ) -solver = get_solver() + initializer.initialize(fs.tsa) + return solver_options def solve(flowsheet=None, **kwargs): """Solve the model.""" fs = flowsheet - initialize(fs) # XXX: skip when do_initialize is in FlowsheetInterface ctor + + iutil.scaling.calculate_scaling_factors(fs.tsa) + + solver_options = initialize( + fs + ) # XXX: skip when do_initialize is in FlowsheetInterface ctor + + solver = SolverFactory("ipopt") + solver.options = solver_options + results = solver.solve(fs.model()) + + tsa_summary(fs.tsa) + return results From 1b986859db4c7d18c6f6ad2857e489471140f293 Mon Sep 17 00:00:00 2001 From: Sheng Pang Date: Thu, 14 Nov 2024 13:02:52 -0800 Subject: [PATCH 06/12] run black format file --- idaes/core/base/flowsheet_model.py | 1 + 1 file changed, 1 insertion(+) diff --git a/idaes/core/base/flowsheet_model.py b/idaes/core/base/flowsheet_model.py index 7b9fbf0aa9..dff8958f32 100644 --- a/idaes/core/base/flowsheet_model.py +++ b/idaes/core/base/flowsheet_model.py @@ -75,6 +75,7 @@ def __init__(self): self.installed = False else: import idaes_ui + self.visualize = idaes_ui.fv.visualize self.installed = True From aec023c94ba751ef9010f8a4e7789e1a6a8b0ad0 Mon Sep 17 00:00:00 2001 From: Sheng Pang Date: Sun, 24 Nov 2024 18:00:55 -0800 Subject: [PATCH 07/12] add ui out put value test --- .../tests/test_fixed_bed_tsa0d_ui.py | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/idaes/models_extra/temperature_swing_adsorption/tests/test_fixed_bed_tsa0d_ui.py b/idaes/models_extra/temperature_swing_adsorption/tests/test_fixed_bed_tsa0d_ui.py index 3bc169fb9a..868854b7f8 100644 --- a/idaes/models_extra/temperature_swing_adsorption/tests/test_fixed_bed_tsa0d_ui.py +++ b/idaes/models_extra/temperature_swing_adsorption/tests/test_fixed_bed_tsa0d_ui.py @@ -1,12 +1,16 @@ import pytest import logging +import pandas as pd from pyomo.environ import check_optimal_termination +from pyomo.environ import value +from idaes.models_extra.temperature_swing_adsorption.util import tsa_summary from idaes.models_extra.temperature_swing_adsorption.fixed_bed_tsa0d_ui import ( export_to_ui, build, initialize, solve, ) +from idaes.core.util.tables import stream_table_dataframe_to_string _log = logging.getLogger(__name__) @@ -42,3 +46,65 @@ def test_default_build_options(): for key, val in ui.fs_exp.build_options.items(): assert default_build_options[key] == val.value assert True + + +@pytest.mark.integration +def test_ui_output(): + """test ui data""" + # get model options from UI export to ui + ui_model_options = export_to_ui() + build_options = ui_model_options.fs_exp.build_options + + # build and solve model + m = build(build_options=build_options) + initialize(m.fs) + solve(m.fs) + + # base on UI output build data frame + var_dict = m.fs.tsa.get_var_dict() + ui_df = tsa_summary(m.fs.tsa) + + # base on jupyter notebook example data, build data frame + jupyter_tsa_model_output_data = { + "Adsorption temperature [K]": 310.00, + "Desorption temperature [K]": 430.00, + "Heating temperature [K]": 440.00, + "Cooling temperature [K]": 300.00, + "Column diameter [m]": 0.030000, + "Column length [m]": 1.2000, + "Column volume [m3]": 0.00084823, + "CO2 mole fraction at feed [%]": 12.000, + "Feed flow rate [mol/s]": 0.0096000, + "Feed velocity [m/s]": 0.50008, + "Minimum fluidization velocity [m/s]": 1.5207, + "Time of heating step [h]": 0.37030, + "Time of cooling step [h]": 0.20826, + "Time of pressurization step [h]": 0.0051098, + "Time of adsorption step [h]": 0.25221, + "Cycle time [h]": 0.83588, + "Purity [-]": 0.90219, + "Recovery [-]": 0.89873, + "Productivity [kg CO2/ton/h]": 84.085, + "Specific energy [MJ/kg CO2]": 3.6532, + "Heat duty per bed [MW]": 5.1244e-05, + "Heat duty total [MW]": 0.00016646, + "Pressure drop [Pa]": 5263.6, + "Number of beds": 3.2484, + "CO2 captured in one cycle per bed [kg/cycle]": 0.042210, + "Cycles per year": 10480.0, + "Total CO2 captured per year [tonne/year]": 1.4369, + "Amount of flue gas processed per year [Gmol/year]": 0.00030275, + "Amount of flue gas processed per year (target) [Gmol/year]": 0.00030275, + "Amount of CO2 to atmosphere [mol/s]": 0.00011667, + "Concentration of CO2 emitted to atmosphere [ppm]": 13803.0, + } + + # build jupyter notebook data frame + jupyter_df = pd.DataFrame.from_dict( + jupyter_tsa_model_output_data, orient="index", columns=["Value"] + ) + + # compare data frame string + jupyter_df_string = stream_table_dataframe_to_string(jupyter_df) + ui_df_string = stream_table_dataframe_to_string(ui_df) + assert jupyter_df_string == ui_df_string From 280e5d6c9421b889e3173823fd43f6d20ca17bd7 Mon Sep 17 00:00:00 2001 From: Sheng Pang Date: Sun, 24 Nov 2024 20:18:00 -0800 Subject: [PATCH 08/12] fix rounding and units move flue gas back into build function --- .../fixed_bed_tsa0d_ui.py | 67 ++++++++++--------- 1 file changed, 34 insertions(+), 33 deletions(-) diff --git a/idaes/models_extra/temperature_swing_adsorption/fixed_bed_tsa0d_ui.py b/idaes/models_extra/temperature_swing_adsorption/fixed_bed_tsa0d_ui.py index aaed5a9af0..7ee101b6bd 100644 --- a/idaes/models_extra/temperature_swing_adsorption/fixed_bed_tsa0d_ui.py +++ b/idaes/models_extra/temperature_swing_adsorption/fixed_bed_tsa0d_ui.py @@ -16,19 +16,14 @@ """ from datetime import datetime from watertap.ui import fsapi as api -from pyomo.environ import ( - ConcreteModel, - SolverFactory, - value, - units, -) +from pyomo.environ import ConcreteModel, SolverFactory, value, units, Var, Expression from idaes.core import FlowsheetBlock from idaes.core.util.model_statistics import degrees_of_freedom import idaes.core.util as iutil from idaes.core.solvers import get_solver from idaes.core.util.exceptions import ConfigurationError -import idaes.logger as idaeslog +import idaes.logger as idaes_log from idaes.models_extra.temperature_swing_adsorption import ( @@ -43,6 +38,8 @@ plot_tsa_profiles, ) +_log = idaes_log.getLogger(__name__) + model_name = "0D Fixed Bed TSA" model_name_for_ui = f"0D Fixed Bed TSA - {datetime.now()}" unit_name = "FixedBedTSA0D" @@ -152,6 +149,7 @@ def build(build_options=None, **kwargs): "temperature": 300.0, "pressure": 1.0e5, } + for i in m.fs.tsa.component_list: m.fs.tsa.inlet.flow_mol_comp[:, i].fix(flue_gas["flow_mol_comp"][i]) m.fs.tsa.inlet.temperature.fix(flue_gas["temperature"]) @@ -164,6 +162,9 @@ def build(build_options=None, **kwargs): m.fs.tsa.bed_diameter.fix(3 / 100) m.fs.tsa.bed_height.fix(1.2) + DOF = degrees_of_freedom(m) + _log.info(f"The DOF of the TSA unit is {DOF}") + return m @@ -202,14 +203,14 @@ def export(flowsheet=None, exports=None, build_options=None, **kwargs): name="Number of beds", ui_units=units.dimensionless, display_units="-", - rounding=6, + rounding=4, output_category="Equipment Parameters", is_input=False, is_output=True, ) exports.add( obj=fs.tsa.bed_diameter, - name="Bed diameter", + name="Column diameter", ui_units=units.m, display_units="m", rounding=6, @@ -220,7 +221,7 @@ def export(flowsheet=None, exports=None, build_options=None, **kwargs): ) exports.add( obj=fs.tsa.bed_height, - name="Bed length", + name="Column length", ui_units=units.m, display_units="m", rounding=6, @@ -281,7 +282,7 @@ def export(flowsheet=None, exports=None, build_options=None, **kwargs): name="Column volume", ui_units=units.m**3, display_units="m3", - rounding=6, + rounding=8, output_category="Operating Parameters", is_input=False, is_output=True, @@ -314,7 +315,7 @@ def export(flowsheet=None, exports=None, build_options=None, **kwargs): name="Feed velocity", ui_units=units.m / units.s, display_units="m/s", - rounding=6, + rounding=5, output_category="Operating Parameters", is_input=False, is_output=True, @@ -326,7 +327,7 @@ def export(flowsheet=None, exports=None, build_options=None, **kwargs): name="Minimum fluidization velocity", ui_units=units.m / units.s, display_units="m/s", - rounding=6, + rounding=4, input_category="Operating Parameters", output_category="Operating Parameters", is_input=True, @@ -351,7 +352,7 @@ def export(flowsheet=None, exports=None, build_options=None, **kwargs): name="Time of cooling step", ui_units=units.h, display_units="h", - rounding=6, + rounding=5, input_category="Operating Parameters", output_category="Operating Parameters", is_input=True, @@ -363,7 +364,7 @@ def export(flowsheet=None, exports=None, build_options=None, **kwargs): name="Time of pressurization step", ui_units=units.h, display_units="h", - rounding=6, + rounding=7, input_category="Operating Parameters", output_category="Operating Parameters", is_input=True, @@ -375,7 +376,7 @@ def export(flowsheet=None, exports=None, build_options=None, **kwargs): name="Time of adsorption step", ui_units=units.h, display_units="h", - rounding=6, + rounding=5, input_category="Operating Parameters", output_category="Operating Parameters", is_input=True, @@ -400,7 +401,7 @@ def export(flowsheet=None, exports=None, build_options=None, **kwargs): name="Pressure drop", ui_units=units.Pa, display_units="Pa", - rounding=6, + rounding=1, input_category="Operating Parameters", output_category="Operating Parameters", is_input=True, @@ -413,7 +414,7 @@ def export(flowsheet=None, exports=None, build_options=None, **kwargs): name="Purity", ui_units=units.dimensionless, display_units="-", - rounding=6, + rounding=5, input_category="Performance Metrics", output_category="Performance Metrics", is_input=True, @@ -425,7 +426,7 @@ def export(flowsheet=None, exports=None, build_options=None, **kwargs): name="Recovery", ui_units=units.dimensionless, display_units="-", - rounding=6, + rounding=5, input_category="Performance Metrics", output_category="Performance Metrics", is_input=True, @@ -433,14 +434,14 @@ def export(flowsheet=None, exports=None, build_options=None, **kwargs): ) exports.add( - obj=fs.tsa.productivity, # Productivity + obj=fs.tsa.productivity, name="Productivity", - ui_units=units.kg / units.ton / units.h, + ui_units=units.kg / units.metric_ton / units.h, display_units="kg CO2/ton/h", - rounding=6, + rounding=3, input_category="Performance Metrics", output_category="Performance Metrics", - is_input=True, + is_input=False, is_output=True, ) @@ -449,7 +450,7 @@ def export(flowsheet=None, exports=None, build_options=None, **kwargs): name="Specific energy", ui_units=units.MJ / units.kg, display_units="MJ/kg CO2", - rounding=6, + rounding=4, input_category="Performance Metrics", output_category="Performance Metrics", is_input=True, @@ -459,9 +460,9 @@ def export(flowsheet=None, exports=None, build_options=None, **kwargs): exports.add( obj=fs.tsa.heat_duty_bed, name="Heat duty per bed", - ui_units=units.MW, + ui_units=units.MJ / units.seconds, display_units="MW", - rounding=6, + rounding=9, output_category="Performance Metrics", is_input=False, is_output=True, @@ -469,10 +470,10 @@ def export(flowsheet=None, exports=None, build_options=None, **kwargs): exports.add( obj=fs.tsa.heat_duty_total, - name="Total heat duty", + name="Heat duty total", ui_units=units.MW, display_units="MW", - rounding=6, + rounding=8, output_category="Performance Metrics", is_input=False, is_output=True, @@ -495,7 +496,7 @@ def export(flowsheet=None, exports=None, build_options=None, **kwargs): name="Cycles per year", ui_units=1 / units.year, display_units="cycles/year", - rounding=6, + rounding=1, output_category="Performance Metrics", is_input=False, is_output=True, @@ -506,7 +507,7 @@ def export(flowsheet=None, exports=None, build_options=None, **kwargs): name="Total CO2 captured per year", ui_units=units.tonne / units.year, display_units="tonne/year", - rounding=6, + rounding=4, output_category="Performance Metrics", is_input=False, is_output=True, @@ -518,7 +519,7 @@ def export(flowsheet=None, exports=None, build_options=None, **kwargs): name="Amount of flue gas processed per year", ui_units=units.Gmol / units.year, # Gmol/year display_units="Gmol/year", - rounding=6, + rounding=8, output_category="Performance Metrics", is_input=False, is_output=True, @@ -529,7 +530,7 @@ def export(flowsheet=None, exports=None, build_options=None, **kwargs): name="Amount of flue gas processed per year (target)", ui_units=units.Gmol / units.year, display_units="Gmol/year", - rounding=6, + rounding=8, output_category="Performance Metrics", is_input=False, is_output=True, @@ -568,7 +569,7 @@ def initialize(fs, **kwargs): } initializer = FixedBedTSA0DInitializer( - output_level=idaeslog.INFO, solver_options=solver_options + output_level=idaes_log.INFO, solver_options=solver_options ) initializer.initialize(fs.tsa) From 72d1a88604c23389b8dfccc5e52a965cb240eebc Mon Sep 17 00:00:00 2001 From: Sheng Pang Date: Sun, 24 Nov 2024 20:20:01 -0800 Subject: [PATCH 09/12] cleanup import --- .../tests/test_fixed_bed_tsa0d_ui.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/idaes/models_extra/temperature_swing_adsorption/tests/test_fixed_bed_tsa0d_ui.py b/idaes/models_extra/temperature_swing_adsorption/tests/test_fixed_bed_tsa0d_ui.py index 868854b7f8..030d6317bc 100644 --- a/idaes/models_extra/temperature_swing_adsorption/tests/test_fixed_bed_tsa0d_ui.py +++ b/idaes/models_extra/temperature_swing_adsorption/tests/test_fixed_bed_tsa0d_ui.py @@ -1,8 +1,6 @@ import pytest import logging import pandas as pd -from pyomo.environ import check_optimal_termination -from pyomo.environ import value from idaes.models_extra.temperature_swing_adsorption.util import tsa_summary from idaes.models_extra.temperature_swing_adsorption.fixed_bed_tsa0d_ui import ( export_to_ui, @@ -107,4 +105,5 @@ def test_ui_output(): # compare data frame string jupyter_df_string = stream_table_dataframe_to_string(jupyter_df) ui_df_string = stream_table_dataframe_to_string(ui_df) + print(ui_df_string) assert jupyter_df_string == ui_df_string From 6c89225c674683d08ea2c825398b1c7a43f53ae5 Mon Sep 17 00:00:00 2001 From: Ludovico Bianchi Date: Mon, 9 Dec 2024 18:54:05 -0600 Subject: [PATCH 10/12] Add entry point group for idaes-flowsheet-processor --- setup.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 407a027159..44153a0c81 100644 --- a/setup.py +++ b/setup.py @@ -96,7 +96,10 @@ def __getitem__(self, key): entry_points={ "console_scripts": [ "idaes = idaes.commands.base:command_base", - ] + ], + "idaes.flowsheets": [ + "0D Fixed Bed TSA = idaes.models_extra.temperature_swing_adsorption.fixed_bed_tsa0d_ui", + ], }, # Only installed if [] is added to package name extras_require=dict(ExtraDependencies()), From d0209e723258cdfffe7c50a7c36fde29b038368a Mon Sep 17 00:00:00 2001 From: Ludovico Bianchi Date: Wed, 11 Dec 2024 07:44:50 -0600 Subject: [PATCH 11/12] Update import to idaes_flowsheet_processor --- .../temperature_swing_adsorption/fixed_bed_tsa0d_ui.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/idaes/models_extra/temperature_swing_adsorption/fixed_bed_tsa0d_ui.py b/idaes/models_extra/temperature_swing_adsorption/fixed_bed_tsa0d_ui.py index 7ee101b6bd..29762ee77f 100644 --- a/idaes/models_extra/temperature_swing_adsorption/fixed_bed_tsa0d_ui.py +++ b/idaes/models_extra/temperature_swing_adsorption/fixed_bed_tsa0d_ui.py @@ -15,16 +15,15 @@ UI exports for 0D Fixed Bed TSA unit model. """ from datetime import datetime -from watertap.ui import fsapi as api -from pyomo.environ import ConcreteModel, SolverFactory, value, units, Var, Expression +from pyomo.environ import ConcreteModel, SolverFactory, value, units, Var, Expression from idaes.core import FlowsheetBlock from idaes.core.util.model_statistics import degrees_of_freedom import idaes.core.util as iutil from idaes.core.solvers import get_solver from idaes.core.util.exceptions import ConfigurationError import idaes.logger as idaes_log - +from idaes_flowsheet_processor import api from idaes.models_extra.temperature_swing_adsorption import ( FixedBedTSA0D, From 9f962d423c9493d57aafff92284f92513780fa59 Mon Sep 17 00:00:00 2001 From: Sheng Pang Date: Sat, 14 Dec 2024 18:21:20 -0800 Subject: [PATCH 12/12] base on meeting with Daison update tsa ui for flowsheet processer remove useless input and output, update field name --- .../fixed_bed_tsa0d_ui.py | 209 ++++++------------ 1 file changed, 69 insertions(+), 140 deletions(-) diff --git a/idaes/models_extra/temperature_swing_adsorption/fixed_bed_tsa0d_ui.py b/idaes/models_extra/temperature_swing_adsorption/fixed_bed_tsa0d_ui.py index 29762ee77f..2c644dd3ca 100644 --- a/idaes/models_extra/temperature_swing_adsorption/fixed_bed_tsa0d_ui.py +++ b/idaes/models_extra/temperature_swing_adsorption/fixed_bed_tsa0d_ui.py @@ -11,6 +11,12 @@ # for full copyright and license information. ################################################################################# +# TODO: +# 1. export_to_ui collocation_points and finite_elements need to be conditional render based on transformation_method +# 2. Optimization confusing user need add one option simulation +# 3. Question Generally missing: Costing metrics (Alex Noring added them) +# 4. Build out graph (go run Jupyter at TSA0D model last step) + """ UI exports for 0D Fixed Bed TSA unit model. """ @@ -40,7 +46,7 @@ _log = idaes_log.getLogger(__name__) model_name = "0D Fixed Bed TSA" -model_name_for_ui = f"0D Fixed Bed TSA - {datetime.now()}" +model_name_for_ui = f"0D Fixed Bed TSA" unit_name = "FixedBedTSA0D" @@ -93,16 +99,19 @@ def export_to_ui() -> api.FlowsheetInterface: "value": "lagrangeRadau", "category": unit_name, }, - "finite_elements": { - "name": "finite_elements", - "display_name": "Number of Finite Elements", - "description": "Number of finite elements to use when discretizing time domain", - "min_val": 0, - "max_val": 10000, - "value": 20, - "values_allowed": "int", - "category": unit_name, - }, + # TODO This parts need to conditional render based on transformation_method + # TODO: this is only show when dae.finite_difference is selected + # "finite_elements": { + # "name": "finite_elements", + # "display_name": "Number of Finite Elements", + # "description": "Number of finite elements to use when discretizing time domain", + # "min_val": 0, + # "max_val": 10000, + # "value": 20, + # "values_allowed": "int", + # "category": unit_name, + # }, + # TODO: this only show when dae.collocation is selected "collocation_points": { "name": "collocation_points", "display_name": "Collocation Points", @@ -124,19 +133,31 @@ def build(build_options=None, **kwargs): unit_opt = {k: v for k, v in build_options.items() if v.category == unit_name} - m.fs.tsa = FixedBedTSA0D( - adsorbent=Adsorbent[unit_opt["adsorbent"].value], # Use zeolite_13x - number_of_beds=unit_opt["number_of_beds"].value, # Set to 1 - transformation_method=unit_opt[ + # build args for m.fs.tsa + tsa_params = { + "adsorbent": Adsorbent[unit_opt["adsorbent"].value], # Use zeolite_13x + "number_of_beds": unit_opt["number_of_beds"].value, # Set to 1 + "transformation_method": unit_opt[ "transformation_method" ].value, # Use dae.collocation - transformation_scheme=unit_opt[ + "transformation_scheme": unit_opt[ "transformation_scheme" ].value, # default Use lagrangeRadau - finite_elements=unit_opt["finite_elements"].value, # Set to 20 - collocation_points=unit_opt["collocation_points"].value, # Set to 6 - steam_calculation=SteamCalculationType.none, - ) + "steam_calculation": SteamCalculationType.none, + } + + # Conditional parameters collocation_points base on transformation_method is dae.collocation + if ( + "collocation_points" in unit_opt + and unit_opt["collocation_points"].value is not None + ): + tsa_params["collocation_points"] = unit_opt["collocation_points"].value + + # Conditional parameters finite_elements base on transformation_method is dae.finite_difference + if "finite_elements" in unit_opt and unit_opt["finite_elements"].value is not None: + tsa_params["finite_elements"] = unit_opt["finite_elements"].value + + m.fs.tsa = FixedBedTSA0D(**tsa_params) flue_gas = { "flow_mol_comp": { @@ -184,6 +205,19 @@ def export(flowsheet=None, exports=None, build_options=None, **kwargs): is_output=True, ) + # Feed + exports.add( + obj=fs.tsa.inlet.pressure[0], + name="Pressure", + ui_units=units.Pa, + display_units="Pa", + rounding=1, + input_category="Feed", + output_category="Feed", + is_input=True, + is_output=True, + ) + exports.add( obj=fs.tsa.inlet.temperature[0], name="Temperature", @@ -209,7 +243,7 @@ def export(flowsheet=None, exports=None, build_options=None, **kwargs): ) exports.add( obj=fs.tsa.bed_diameter, - name="Column diameter", + name="Bed diameter", # column diameter from original model ui_units=units.m, display_units="m", rounding=6, @@ -220,7 +254,7 @@ def export(flowsheet=None, exports=None, build_options=None, **kwargs): ) exports.add( obj=fs.tsa.bed_height, - name="Column length", + name="Bed length", # column length from original model ui_units=units.m, display_units="m", rounding=6, @@ -278,7 +312,7 @@ def export(flowsheet=None, exports=None, build_options=None, **kwargs): exports.add( obj=fs.tsa.bed_volume, - name="Column volume", + name="Bed volume", # column volume from original model ui_units=units.m**3, display_units="m3", rounding=8, @@ -305,7 +339,7 @@ def export(flowsheet=None, exports=None, build_options=None, **kwargs): rounding=6, input_category="Operating Parameters", output_category="Operating Parameters", - is_input=True, + is_input=False, is_output=True, ) @@ -329,56 +363,7 @@ def export(flowsheet=None, exports=None, build_options=None, **kwargs): rounding=4, input_category="Operating Parameters", output_category="Operating Parameters", - is_input=True, - is_output=True, - ) - - # Time steps - exports.add( - obj=fs.tsa.heating.time, # Time of heating step - name="Time of heating step", - ui_units=units.h, - display_units="h", - rounding=6, - input_category="Operating Parameters", - output_category="Operating Parameters", - is_input=True, - is_output=True, - ) - - exports.add( - obj=fs.tsa.cooling.time, # Time of cooling step - name="Time of cooling step", - ui_units=units.h, - display_units="h", - rounding=5, - input_category="Operating Parameters", - output_category="Operating Parameters", - is_input=True, - is_output=True, - ) - - exports.add( - obj=fs.tsa.pressurization.time, # Time of pressurization step - name="Time of pressurization step", - ui_units=units.h, - display_units="h", - rounding=7, - input_category="Operating Parameters", - output_category="Operating Parameters", - is_input=True, - is_output=True, - ) - - exports.add( - obj=fs.tsa.adsorption.time, # Time of adsorption step - name="Time of adsorption step", - ui_units=units.h, - display_units="h", - rounding=5, - input_category="Operating Parameters", - output_category="Operating Parameters", - is_input=True, + is_input=False, is_output=True, ) @@ -390,7 +375,7 @@ def export(flowsheet=None, exports=None, build_options=None, **kwargs): rounding=6, input_category="Operating Parameters", output_category="Operating Parameters", - is_input=True, + is_input=False, is_output=True, ) @@ -403,7 +388,7 @@ def export(flowsheet=None, exports=None, build_options=None, **kwargs): rounding=1, input_category="Operating Parameters", output_category="Operating Parameters", - is_input=True, + is_input=False, is_output=True, ) @@ -416,7 +401,7 @@ def export(flowsheet=None, exports=None, build_options=None, **kwargs): rounding=5, input_category="Performance Metrics", output_category="Performance Metrics", - is_input=True, + is_input=False, is_output=True, ) @@ -428,18 +413,6 @@ def export(flowsheet=None, exports=None, build_options=None, **kwargs): rounding=5, input_category="Performance Metrics", output_category="Performance Metrics", - is_input=True, - is_output=True, - ) - - exports.add( - obj=fs.tsa.productivity, - name="Productivity", - ui_units=units.kg / units.metric_ton / units.h, - display_units="kg CO2/ton/h", - rounding=3, - input_category="Performance Metrics", - output_category="Performance Metrics", is_input=False, is_output=True, ) @@ -452,16 +425,17 @@ def export(flowsheet=None, exports=None, build_options=None, **kwargs): rounding=4, input_category="Performance Metrics", output_category="Performance Metrics", - is_input=True, + is_input=False, is_output=True, ) exports.add( - obj=fs.tsa.heat_duty_bed, - name="Heat duty per bed", - ui_units=units.MJ / units.seconds, - display_units="MW", - rounding=9, + obj=fs.tsa.productivity, + name="Productivity", + ui_units=units.kg / units.metric_ton / units.h, + display_units="kg CO2/ton/h", + rounding=3, + input_category="Performance Metrics", output_category="Performance Metrics", is_input=False, is_output=True, @@ -490,51 +464,6 @@ def export(flowsheet=None, exports=None, build_options=None, **kwargs): is_output=True, ) - exports.add( - obj=fs.tsa.cycles_year, - name="Cycles per year", - ui_units=1 / units.year, - display_units="cycles/year", - rounding=1, - output_category="Performance Metrics", - is_input=False, - is_output=True, - ) - - exports.add( - obj=fs.tsa.total_CO2_captured_year, # Total CO2 captured per year - name="Total CO2 captured per year", - ui_units=units.tonne / units.year, - display_units="tonne/year", - rounding=4, - output_category="Performance Metrics", - is_input=False, - is_output=True, - ) - - # Flue Gas Processing - exports.add( - obj=fs.tsa.flue_gas_processed_year, - name="Amount of flue gas processed per year", - ui_units=units.Gmol / units.year, # Gmol/year - display_units="Gmol/year", - rounding=8, - output_category="Performance Metrics", - is_input=False, - is_output=True, - ) - - exports.add( - obj=fs.tsa.flue_gas_processed_year_target, - name="Amount of flue gas processed per year (target)", - ui_units=units.Gmol / units.year, - display_units="Gmol/year", - rounding=8, - output_category="Performance Metrics", - is_input=False, - is_output=True, - ) - # CO2 Emissions exports.add( obj=fs.tsa.emissions_co2,