From 2affa777d6d44bc5ccf25a586005800fb6bae261 Mon Sep 17 00:00:00 2001 From: Morgan Wang Date: Mon, 18 Mar 2024 10:43:17 -0400 Subject: [PATCH 01/33] Add CCUS file structure --- idaes/models_extra/ccus/__init__.py | 0 idaes/models_extra/ccus/unit_models/README.md | 1 + idaes/models_extra/ccus/unit_models/__init__.py | 0 idaes/models_extra/ccus/unit_models/membrane_1d.py | 0 idaes/models_extra/ccus/unit_models/tests/__init__.py | 0 idaes/models_extra/ccus/unit_models/tests/test_membrane_1d.py | 0 6 files changed, 1 insertion(+) create mode 100644 idaes/models_extra/ccus/__init__.py create mode 100644 idaes/models_extra/ccus/unit_models/README.md create mode 100644 idaes/models_extra/ccus/unit_models/__init__.py create mode 100644 idaes/models_extra/ccus/unit_models/membrane_1d.py create mode 100644 idaes/models_extra/ccus/unit_models/tests/__init__.py create mode 100644 idaes/models_extra/ccus/unit_models/tests/test_membrane_1d.py diff --git a/idaes/models_extra/ccus/__init__.py b/idaes/models_extra/ccus/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/idaes/models_extra/ccus/unit_models/README.md b/idaes/models_extra/ccus/unit_models/README.md new file mode 100644 index 0000000000..c562e71fb1 --- /dev/null +++ b/idaes/models_extra/ccus/unit_models/README.md @@ -0,0 +1 @@ +This directory contains theunit models of Carbon Capture, Utilisation and Storage(CCUS) diff --git a/idaes/models_extra/ccus/unit_models/__init__.py b/idaes/models_extra/ccus/unit_models/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/idaes/models_extra/ccus/unit_models/membrane_1d.py b/idaes/models_extra/ccus/unit_models/membrane_1d.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/idaes/models_extra/ccus/unit_models/tests/__init__.py b/idaes/models_extra/ccus/unit_models/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/idaes/models_extra/ccus/unit_models/tests/test_membrane_1d.py b/idaes/models_extra/ccus/unit_models/tests/test_membrane_1d.py new file mode 100644 index 0000000000..e69de29bb2 From b0a336a2d5e98001d191ae164dbf7ed60cfa72cc Mon Sep 17 00:00:00 2001 From: Morgan Wang Date: Mon, 18 Mar 2024 11:15:59 -0400 Subject: [PATCH 02/33] One-dimensional membrane model --- .../ccus/unit_models/membrane_1d.py | 339 ++++++++++++++++++ 1 file changed, 339 insertions(+) diff --git a/idaes/models_extra/ccus/unit_models/membrane_1d.py b/idaes/models_extra/ccus/unit_models/membrane_1d.py index e69de29bb2..0137c9bcd3 100644 --- a/idaes/models_extra/ccus/unit_models/membrane_1d.py +++ b/idaes/models_extra/ccus/unit_models/membrane_1d.py @@ -0,0 +1,339 @@ +################################################################################# +# 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-2023 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. +################################################################################# + + +from enum import Enum +from pyomo.common.config import Bool, ConfigDict, ConfigValue,In +from pyomo.environ import Constraint, Param, Var, units, Expression +from pyomo.network import Port + +from idaes.core import ( + FlowDirection, + UnitModelBlockData, + declare_process_block_class, + useDefault, + MaterialFlowBasis, +) +from idaes.core.util.config import is_physical_parameter_block +from idaes.models.unit_models.mscontactor import MSContactor +from idaes.core.util.exceptions import ConfigurationError, InitializationError + +#TODO: add robust initialization +__author__ = "Maojian Wang" + + +class MembraneFlowPattern(Enum): + """ + Enum of supported flow patterns for membrane. + So far only support countercurrent and cocurrent flow + """ + countercurrent = 1 + cocurrent = 2 + crossflow = 3 + + + +@declare_process_block_class("Membrane1D") +class Membrane1DData(UnitModelBlockData): + """Standard Membrane 1D Unit Model Class.""" + + CONFIG = UnitModelBlockData.CONFIG() + + Stream_Config = ConfigDict() + + Stream_Config.declare( + "property_package", + ConfigValue( + default=useDefault, + domain=is_physical_parameter_block, + description="Property package to use for given stream", + doc="""Property parameter object used to define property calculations for given stream, + **default** - useDefault. + **Valid values:** { + **useDefault** - use default package from parent model or flowsheet, + **PhysicalParameterObject** - a PhysicalParameterBlock object.}""", + ), + ) + + Stream_Config.declare( + "property_package_args", + ConfigDict( + implicit=True, + description="Dict of arguments to use for constructing property package", + doc="""A ConfigDict with arguments to be passed to property block(s) + and used when constructing these, + **default** - None. + **Valid values:** { + see property package for documentation.}""", + ), + ) + + Stream_Config.declare( + "flow_direction", + ConfigValue( + default=FlowDirection.forward, + domain=In(FlowDirection), + doc="Direction of flow for stream", + description="FlowDirection Enum indicating direction of " + "flow for given stream. Default=FlowDirection.forward.", + ), + ) + + Stream_Config.declare( + "has_energy_balance", + ConfigValue( + default=True, + domain=Bool, + doc="Bool indicating whether to include energy balance for stream. Default=True.", + ), + ) + + Stream_Config.declare( + "has_pressure_balance", + ConfigValue( + default=True, + domain=Bool, + doc="Bool indicating whether to include pressure balance for stream. Default=True.", + ), + ) + + Stream_Config.declare( + "has_feed", + ConfigValue( + default=True, + domain=Bool, + doc="Bool indicating whether stream has a feed.", + description="Bool indicating whether stream has a feed Port and inlet " + "state, or if all flow is provided via mass transfer. Default=True.", + ), + ) + CONFIG.declare( + "sweep_flow", + ConfigValue( + default=True, + domain=Bool, + doc="Bool indicating whether there is a sweep flow in the permeate side.", + description="Bool indicating whether stream has a feed Port and inlet " + "state, or if all flow is provided via mass transfer. Default=True.", + ), + ) + + + CONFIG.declare( + "finite_elements", + ConfigValue( + default=5, + domain=int, + description="Number of finite elements in length domain", + doc="""Number of finite elements to use when discretizing length + domain (default=5)""", + ), + ) + + + CONFIG.declare( + "flow_type", + ConfigValue( + default=MembraneFlowPattern.countercurrent, + domain=In(MembraneFlowPattern), + description="Flow configuration of membrane", + doc="""Flow configuration of membrane + - MembraneFlowPattern.cocurrent: feed and sweep flows from 0 to 1 + - MembraneFlowPattern.countercurrent: feed side flows from 0 to 1 + sweep side flows from 1 to 0 (default)""" , + ), + ) + + CONFIG.declare( + "property_package", + ConfigValue( + default=None, + domain=is_physical_parameter_block, + description="Property package to use for control volume", + doc="""Property parameter object used to define property + calculations + (default = 'use_parent_value') + - 'use_parent_value' - get package from parent (default = None) + - a ParameterBlock object""", + ), + ) + + CONFIG.declare( + "property_package_args", + ConfigValue( + default={}, + description="Arguments for constructing property package", + doc="""A dict of arguments to be passed to the PropertyBlockData + and used when constructing these + (default = 'use_parent_value') + - 'use_parent_value' - get package from parent (default = None) + - a dict (see property package for documentation)""", + ), + ) + + for side_name in ['feed', 'sweep']: + CONFIG.declare(side_name+'_side', Stream_Config(),) + + + + def build(self): + super().build() + + if self.config.sweep_flow == False: + self.config.sweep_side.has_feed = False + + + # Set flow directions + if self.config.flow_type == MembraneFlowPattern.cocurrent: + self.config.feed_side.flow_direction = FlowDirection.forward + self.config.sweep_side.flow_direction = FlowDirection.forward + elif self.config.flow_type == MembraneFlowPattern.countercurrent: + self.config.feed_side.flow_direction = FlowDirection.forward + self.config.sweep_side.flow_direction = FlowDirection.backward + + else: + raise ConfigurationError( + "{} Membrane1D only supports cocurrent and " + "countercurrent flow patterns, but flow_type configuration" + " argument was set to {}.".format(self.name, self.config.flow_type) + ) + + if self.config.property_package != None: + if self.config.feed_side.property_package == useDefault: + self.config.feed_side.property_package = self.config.property_package + if self.config.sweep_side.property_package == useDefault: + self.config.sweep_side.property_package = self.config.property_package + + + streams_dict = { + "feed_side": self.config.feed_side, + "sweep_side": self.config.sweep_side, + } + self.mscontactor = MSContactor( + streams=streams_dict, + number_of_finite_elements=self.config.finite_elements, + + ) + + self.feed_side_inlet = Port(extends=self.mscontactor.feed_side_inlet) + self.feed_side_outlet = Port(extends=self.mscontactor.feed_side_outlet) + if self.config.sweep_flow == True: + self.sweep_side_inlet = Port(extends=self.mscontactor.sweep_side_inlet) + self.sweep_side_outlet = Port(extends=self.mscontactor.sweep_side_outlet) + + self._make_geometry() + self._make_performance() + + def _make_geometry(self): + feed_side_units = self.config.feed_side.property_package.get_metadata().derived_units + + self.area = Var( + initialize=100, + units = units.cm**2, + doc="The membrane area" + ) + + self.length = Var( + initialize=100, + units = units.cm, + doc="The membrane length" + ) + self.cell_length = Expression(expr=self.length/self.config.finite_elements) + + self.cell_area = Var( + initialize=100, + units = units.cm**2, + doc="The membrane area" + ) + @self.Constraint() + def area_per_cell(self): + return self.cell_area == self.area/self.config.finite_elements + + + def _make_performance(self): + feed_side_units = self.config.feed_side.property_package.get_metadata().derived_units + + self.permeance = Var( + self.flowsheet().time, + self.mscontactor.elements, + self.mscontactor.feed_side.component_list, + initialize=1, + doc="Values in Gas Permeance Unit(GPU)", + units=units.dimensionless, + ) + + self.gpu_factor = Param( + default=10e-8/13333.2239, + units=units.m/units.s/units.Pa, + mutable=True, + ) + + self.selectivity = Var( + self.flowsheet().time, + self.mscontactor.elements, + self.mscontactor.feed_side.component_list, + self.mscontactor.feed_side.component_list, + initialize=1, + units=units.dimensionless, + ) + + @self.Constraint( + self.flowsheet().time, + self.mscontactor.elements, + self.mscontactor.feed_side.component_list, + self.mscontactor.feed_side.component_list, + doc = "permeance calculation", + ) + def permeance_calculation(self, t, e, a , b ): + return ( + self.permeance[t,e,a] * self.selectivity[t,e,a,b] == self.permeance[t,e,b] + ) + + + p_units = feed_side_units.PRESSURE + + @self.Constraint( + self.flowsheet().time, + self.mscontactor.elements, + self.mscontactor.feed_side.component_list, + doc = "permeability calculation", + ) + def permeability_calculation(self, t, s, m): + feed_side_state = self.mscontactor.feed_side[t,s] + if feed_side_state.get_material_flow_basis() is MaterialFlowBasis.molar: + mb_units = feed_side_units.FLOW_MOLE + rho = self.mscontactor.feed_side[t,s].dens_mol + elif feed_side_state.get_material_flow_basis() is MaterialFlowBasis.mass: + mb_units = feed_side_units.FLOW_MASS + rho = self.mscontactor.feed_side[t,s].dens_mass + else: + raise TypeError("Undefined flow basis, please define the flow basis") + + return ( + self.mscontactor.material_transfer_term[t, s, 'feed_side', 'sweep_side', m] + == - units.convert(( + rho * self.gpu_factor * self.permeance[t,s,m] * self.cell_area * + (self.mscontactor.feed_side[t,s].pressure * self.mscontactor.feed_side[t, s].mole_frac_comp[m] + -units.convert(self.mscontactor.sweep_side[t,s].pressure, to_units=p_units) * self.mscontactor.sweep_side[t, s].mole_frac_comp[m]) + ), + to_units = mb_units,) + ) + + @self.Constraint( + self.flowsheet().time, + self.mscontactor.elements, + doc = "Energy balance" , + ) + def energy_transfer(self, t, s): + return self.mscontactor.feed_side[t, s].temperature == self.mscontactor.sweep_side[t, s].temperature + From 775b27a32e8cd19a3ccc53dfb882ab3c776c420f Mon Sep 17 00:00:00 2001 From: Morgan Wang Date: Mon, 18 Mar 2024 11:17:43 -0400 Subject: [PATCH 03/33] add the unit test file --- .../unit_models/tests/test_membrane_1d.py | 250 ++++++++++++++++++ 1 file changed, 250 insertions(+) diff --git a/idaes/models_extra/ccus/unit_models/tests/test_membrane_1d.py b/idaes/models_extra/ccus/unit_models/tests/test_membrane_1d.py index e69de29bb2..3b4c4f15ab 100644 --- a/idaes/models_extra/ccus/unit_models/tests/test_membrane_1d.py +++ b/idaes/models_extra/ccus/unit_models/tests/test_membrane_1d.py @@ -0,0 +1,250 @@ +################################################################################# +# 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-2023 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. +################################################################################# +""" +Tests for Membrane 1D model +""" +__author__ = "Maojian Wang" + +import pytest + +from pyomo.environ import ( + check_optimal_termination, + ConcreteModel, + value, +) +from idaes.core import FlowsheetBlock +from idaes.core.util.model_statistics import ( + number_variables, + number_total_constraints, + number_unused_variables, +) +from idaes.core.solvers import get_solver +from idaes.core.initialization import ( + BlockTriangularizationInitializer, +) +from idaes.core.util import DiagnosticsToolbox +from idaes.models_extra.power_generation.properties.natural_gas_PR import get_prop, EosType +from idaes.models.properties.modular_properties.base.generic_property import GenericParameterBlock +from membrane_unit import Membrane1D, MembraneFlowPattern + + + +# ----------------------------------------------------------------------------- +# Get default solver for testing +solver = get_solver() + +# ----------------------------------------------------------------------------- +@pytest.mark.unit +def test_config(): + m = ConcreteModel() + m.fs = FlowsheetBlock(dynamic=False) + + m.fs.properties = GenericParameterBlock( + **get_prop(["CO2", "H2O", "N2"], ["Vap"], eos=EosType.IDEAL), + doc="Key flue gas property parameters", + ) + + m.fs.unit = Membrane1D( + finite_elements = 3, + dynamic=False, + sweep_flow = True, + flow_type = MembraneFlowPattern.countercurrent, + property_package = m.fs.properties, + + ) + + # Check unit config arguments + assert len(m.fs.unit.config) == 9 + assert not m.fs.unit.config.dynamic + assert not m.fs.unit.config.has_holdup + assert m.fs.unit.config.property_package is m.fs.properties + + + +class TestMembrane(object): + @pytest.fixture(scope="class") + def membrane(self): + m = ConcreteModel() + m.fs = FlowsheetBlock(dynamic=False) + + m.fs.properties = GenericParameterBlock( + **get_prop(["CO2", "H2O", "N2"], ["Vap"], eos=EosType.IDEAL), + doc="Key flue gas property parameters", + ) + m.fs.unit = Membrane1D( + finite_elements = 3, + dynamic=False, + sweep_flow = True, + flow_type = MembraneFlowPattern.countercurrent, + property_package = m.fs.properties, + ) + + + m.fs.unit.permeance[:,:,'CO2'].fix(1500) + m.fs.unit.permeance[:,:,'H2O'].fix(1500/25) + m.fs.unit.permeance[:,:,'N2'].fix(1500/25) + m.fs.unit.area.fix(100) + m.fs.unit.length.fix(10) + + + m.fs.unit.feed_side_inlet.flow_mol[0].fix(100) + m.fs.unit.feed_side_inlet.temperature[0].fix(365) + m.fs.unit.feed_side_inlet.pressure[0].fix(120000) + m.fs.unit.feed_side_inlet.mole_frac_comp[0, "N2"].fix(0.76) + m.fs.unit.feed_side_inlet.mole_frac_comp[0, "CO2"].fix(0.13) + m.fs.unit.feed_side_inlet.mole_frac_comp[0, "H2O"].fix(0.11) + + + m.fs.unit.sweep_side_inlet.flow_mol[0].fix(0.01) + m.fs.unit.sweep_side_inlet.temperature[0].fix(300) + m.fs.unit.sweep_side_inlet.pressure[0].fix(51325) + m.fs.unit.sweep_side_inlet.mole_frac_comp[0, "H2O"].fix(0.9986) + m.fs.unit.sweep_side_inlet.mole_frac_comp[0, "CO2"].fix(0.0003) + m.fs.unit.sweep_side_inlet.mole_frac_comp[0, "N2"].fix(0.0001) + + return m + + @pytest.mark.build + @pytest.mark.unit + def test_build(self, membrane): + assert hasattr(membrane.fs.unit, "feed_side_inlet") + assert len(membrane.fs.unit.feed_side_inlet.vars) == 4 + assert hasattr(membrane.fs.unit.feed_side_inlet, "flow_mol") + assert hasattr(membrane.fs.unit.feed_side_inlet, "mole_frac_comp") + assert hasattr(membrane.fs.unit.feed_side_inlet, "temperature") + assert hasattr(membrane.fs.unit.feed_side_inlet, "pressure") + + assert hasattr(membrane.fs.unit, "sweep_side_inlet") + assert len(membrane.fs.unit.sweep_side_inlet.vars) == 4 + assert hasattr(membrane.fs.unit.sweep_side_inlet, "flow_mol") + assert hasattr(membrane.fs.unit.sweep_side_inlet, "mole_frac_comp") + assert hasattr(membrane.fs.unit.sweep_side_inlet, "temperature") + assert hasattr(membrane.fs.unit.sweep_side_inlet, "pressure") + + assert hasattr(membrane.fs.unit, "feed_side_outlet") + assert len(membrane.fs.unit.feed_side_outlet.vars) == 4 + assert hasattr(membrane.fs.unit.feed_side_outlet, "flow_mol") + assert hasattr(membrane.fs.unit.feed_side_outlet, "mole_frac_comp") + assert hasattr(membrane.fs.unit.feed_side_outlet, "temperature") + assert hasattr(membrane.fs.unit.feed_side_outlet, "pressure") + + assert hasattr(membrane.fs.unit, "sweep_side_outlet") + assert len(membrane.fs.unit.sweep_side_outlet.vars) == 4 + assert hasattr(membrane.fs.unit.sweep_side_outlet, "flow_mol") + assert hasattr(membrane.fs.unit.sweep_side_outlet, "mole_frac_comp") + assert hasattr(membrane.fs.unit.sweep_side_outlet, "temperature") + assert hasattr(membrane.fs.unit.sweep_side_outlet, "pressure") + + + assert hasattr(membrane.fs.unit, "mscontactor") + assert hasattr(membrane.fs.unit, "permeability_calculation") + assert hasattr(membrane.fs.unit, "energy_transfer") + + assert number_variables(membrane) == 184 + assert number_total_constraints(membrane) == 116 + assert number_unused_variables(membrane) == 28 + + @pytest.mark.component + def test_structural_issues(self, membrane): + dt = DiagnosticsToolbox(membrane) + dt.assert_no_structural_warnings() + + @pytest.mark.solver + @pytest.mark.skipif(solver is None, reason="Solver not available") + @pytest.mark.component + def test_solve(self, membrane): + initializer = BlockTriangularizationInitializer(constraint_tolerance=2e-5) + initializer.initialize(membrane.fs.unit) + results = solver.solve(membrane) + # Check for optimal solution + assert check_optimal_termination(results) + + @pytest.mark.solver + @pytest.mark.skipif(solver is None, reason="Solver not available") + @pytest.mark.component + def test_numerical_issues(self, membrane): + dt = DiagnosticsToolbox(membrane) + dt.assert_no_numerical_warnings() + + + @pytest.mark.solver + @pytest.mark.skipif(solver is None, reason="Solver not available") + @pytest.mark.component + def test_feed_solution(self, membrane): + assert pytest.approx(99.99, abs=1e-2) == value( + membrane.fs.unit.feed_side_outlet.flow_mol[0] + ) + assert pytest.approx(0.1299, abs=1e-4) == value( + membrane.fs.unit.feed_side_outlet.mole_frac_comp[0, "CO2"] + ) + assert pytest.approx(0.11, abs=1e-4) == value( + membrane.fs.unit.feed_side_outlet.mole_frac_comp[0, "H2O"] + ) + assert pytest.approx(0.76, abs=1e-4) == value( + membrane.fs.unit.feed_side_outlet.mole_frac_comp[0, "N2"] + ) + + assert pytest.approx(365, abs=1e-2) == value(membrane.fs.unit.feed_side_outlet.temperature[0]) + assert pytest.approx(120000, abs=1e-2) == value( + membrane.fs.unit.feed_side_outlet.pressure[0] + ) + + @pytest.mark.solver + @pytest.mark.skipif(solver is None, reason="Solver not available") + @pytest.mark.component + def test_sweep_side_solution(self, membrane): + assert pytest.approx(0.01006, abs=1e-4) == value( + membrane.fs.unit.sweep_side_outlet.flow_mol[0] + ) + assert pytest.approx( 0.0070, abs=1e-4) == value( + membrane.fs.unit.sweep_side_outlet.mole_frac_comp[0, "CO2"] + ) + assert pytest.approx(0.99120, abs=1e-4) == value( + membrane.fs.unit.sweep_side_outlet.mole_frac_comp[0, "H2O"] + ) + assert pytest.approx(0.001710, abs=1e-4) == value( + membrane.fs.unit.sweep_side_outlet.mole_frac_comp[0, "N2"] + ) + + assert pytest.approx(365, abs=1e-2) == value(membrane.fs.unit.sweep_side_outlet.temperature[0]) + assert pytest.approx(51325.0, abs=1e-2) == value( + membrane.fs.unit.sweep_side_outlet.pressure[0] + ) + + @pytest.mark.solver + @pytest.mark.skipif(solver is None, reason="Solver not available") + @pytest.mark.component + def test_enthalpy_balance(self, membrane): + + assert ( + abs( + value( + ( + membrane.fs.unit.feed_side_inlet.flow_mol[0] + * membrane.fs.unit.mscontactor.feed_side_inlet_state[0].enth_mol_phase["Vap"] + + membrane.fs.unit.sweep_side_inlet.flow_mol[0] + * membrane.fs.unit.mscontactor.sweep_side_inlet_state[0].enth_mol_phase["Vap"] + - membrane.fs.unit.feed_side_outlet.flow_mol[0] + * membrane.fs.unit.mscontactor.feed_side[0,3].enth_mol_phase["Vap"] + - membrane.fs.unit.sweep_side_outlet.flow_mol[0] + * membrane.fs.unit.mscontactor.sweep_side[0,1].enth_mol_phase["Vap"] + + ) + + ) + ) + <= 1e-6 + ) + + + From dd437b7c4b2542d989da59c834e491bba7b766b5 Mon Sep 17 00:00:00 2001 From: Morgan Wang Date: Mon, 18 Mar 2024 11:19:52 -0400 Subject: [PATCH 04/33] reformatted by black --- .../unit_models/tests/test_membrane_1d.py | 100 +++++++++--------- 1 file changed, 52 insertions(+), 48 deletions(-) diff --git a/idaes/models_extra/ccus/unit_models/tests/test_membrane_1d.py b/idaes/models_extra/ccus/unit_models/tests/test_membrane_1d.py index 3b4c4f15ab..97c7331d10 100644 --- a/idaes/models_extra/ccus/unit_models/tests/test_membrane_1d.py +++ b/idaes/models_extra/ccus/unit_models/tests/test_membrane_1d.py @@ -33,10 +33,14 @@ BlockTriangularizationInitializer, ) from idaes.core.util import DiagnosticsToolbox -from idaes.models_extra.power_generation.properties.natural_gas_PR import get_prop, EosType -from idaes.models.properties.modular_properties.base.generic_property import GenericParameterBlock -from membrane_unit import Membrane1D, MembraneFlowPattern - +from idaes.models_extra.power_generation.properties.natural_gas_PR import ( + get_prop, + EosType, +) +from idaes.models.properties.modular_properties.base.generic_property import ( + GenericParameterBlock, +) +from membrane_unit import Membrane1D, MembraneFlowPattern # ----------------------------------------------------------------------------- @@ -50,18 +54,17 @@ def test_config(): m.fs = FlowsheetBlock(dynamic=False) m.fs.properties = GenericParameterBlock( - **get_prop(["CO2", "H2O", "N2"], ["Vap"], eos=EosType.IDEAL), - doc="Key flue gas property parameters", + **get_prop(["CO2", "H2O", "N2"], ["Vap"], eos=EosType.IDEAL), + doc="Key flue gas property parameters", ) m.fs.unit = Membrane1D( - finite_elements = 3, - dynamic=False, - sweep_flow = True, - flow_type = MembraneFlowPattern.countercurrent, - property_package = m.fs.properties, - - ) + finite_elements=3, + dynamic=False, + sweep_flow=True, + flow_type=MembraneFlowPattern.countercurrent, + property_package=m.fs.properties, + ) # Check unit config arguments assert len(m.fs.unit.config) == 9 @@ -70,7 +73,6 @@ def test_config(): assert m.fs.unit.config.property_package is m.fs.properties - class TestMembrane(object): @pytest.fixture(scope="class") def membrane(self): @@ -78,36 +80,33 @@ def membrane(self): m.fs = FlowsheetBlock(dynamic=False) m.fs.properties = GenericParameterBlock( - **get_prop(["CO2", "H2O", "N2"], ["Vap"], eos=EosType.IDEAL), - doc="Key flue gas property parameters", - ) + **get_prop(["CO2", "H2O", "N2"], ["Vap"], eos=EosType.IDEAL), + doc="Key flue gas property parameters", + ) m.fs.unit = Membrane1D( - finite_elements = 3, + finite_elements=3, dynamic=False, - sweep_flow = True, - flow_type = MembraneFlowPattern.countercurrent, - property_package = m.fs.properties, + sweep_flow=True, + flow_type=MembraneFlowPattern.countercurrent, + property_package=m.fs.properties, ) - - m.fs.unit.permeance[:,:,'CO2'].fix(1500) - m.fs.unit.permeance[:,:,'H2O'].fix(1500/25) - m.fs.unit.permeance[:,:,'N2'].fix(1500/25) + m.fs.unit.permeance[:, :, "CO2"].fix(1500) + m.fs.unit.permeance[:, :, "H2O"].fix(1500 / 25) + m.fs.unit.permeance[:, :, "N2"].fix(1500 / 25) m.fs.unit.area.fix(100) m.fs.unit.length.fix(10) - - m.fs.unit.feed_side_inlet.flow_mol[0].fix(100) - m.fs.unit.feed_side_inlet.temperature[0].fix(365) - m.fs.unit.feed_side_inlet.pressure[0].fix(120000) + m.fs.unit.feed_side_inlet.flow_mol[0].fix(100) + m.fs.unit.feed_side_inlet.temperature[0].fix(365) + m.fs.unit.feed_side_inlet.pressure[0].fix(120000) m.fs.unit.feed_side_inlet.mole_frac_comp[0, "N2"].fix(0.76) m.fs.unit.feed_side_inlet.mole_frac_comp[0, "CO2"].fix(0.13) m.fs.unit.feed_side_inlet.mole_frac_comp[0, "H2O"].fix(0.11) - - m.fs.unit.sweep_side_inlet.flow_mol[0].fix(0.01) - m.fs.unit.sweep_side_inlet.temperature[0].fix(300) - m.fs.unit.sweep_side_inlet.pressure[0].fix(51325) + m.fs.unit.sweep_side_inlet.flow_mol[0].fix(0.01) + m.fs.unit.sweep_side_inlet.temperature[0].fix(300) + m.fs.unit.sweep_side_inlet.pressure[0].fix(51325) m.fs.unit.sweep_side_inlet.mole_frac_comp[0, "H2O"].fix(0.9986) m.fs.unit.sweep_side_inlet.mole_frac_comp[0, "CO2"].fix(0.0003) m.fs.unit.sweep_side_inlet.mole_frac_comp[0, "N2"].fix(0.0001) @@ -145,7 +144,6 @@ def test_build(self, membrane): assert hasattr(membrane.fs.unit.sweep_side_outlet, "temperature") assert hasattr(membrane.fs.unit.sweep_side_outlet, "pressure") - assert hasattr(membrane.fs.unit, "mscontactor") assert hasattr(membrane.fs.unit, "permeability_calculation") assert hasattr(membrane.fs.unit, "energy_transfer") @@ -168,7 +166,7 @@ def test_solve(self, membrane): results = solver.solve(membrane) # Check for optimal solution assert check_optimal_termination(results) - + @pytest.mark.solver @pytest.mark.skipif(solver is None, reason="Solver not available") @pytest.mark.component @@ -176,7 +174,6 @@ def test_numerical_issues(self, membrane): dt = DiagnosticsToolbox(membrane) dt.assert_no_numerical_warnings() - @pytest.mark.solver @pytest.mark.skipif(solver is None, reason="Solver not available") @pytest.mark.component @@ -194,7 +191,9 @@ def test_feed_solution(self, membrane): membrane.fs.unit.feed_side_outlet.mole_frac_comp[0, "N2"] ) - assert pytest.approx(365, abs=1e-2) == value(membrane.fs.unit.feed_side_outlet.temperature[0]) + assert pytest.approx(365, abs=1e-2) == value( + membrane.fs.unit.feed_side_outlet.temperature[0] + ) assert pytest.approx(120000, abs=1e-2) == value( membrane.fs.unit.feed_side_outlet.pressure[0] ) @@ -206,7 +205,7 @@ def test_sweep_side_solution(self, membrane): assert pytest.approx(0.01006, abs=1e-4) == value( membrane.fs.unit.sweep_side_outlet.flow_mol[0] ) - assert pytest.approx( 0.0070, abs=1e-4) == value( + assert pytest.approx(0.0070, abs=1e-4) == value( membrane.fs.unit.sweep_side_outlet.mole_frac_comp[0, "CO2"] ) assert pytest.approx(0.99120, abs=1e-4) == value( @@ -216,7 +215,9 @@ def test_sweep_side_solution(self, membrane): membrane.fs.unit.sweep_side_outlet.mole_frac_comp[0, "N2"] ) - assert pytest.approx(365, abs=1e-2) == value(membrane.fs.unit.sweep_side_outlet.temperature[0]) + assert pytest.approx(365, abs=1e-2) == value( + membrane.fs.unit.sweep_side_outlet.temperature[0] + ) assert pytest.approx(51325.0, abs=1e-2) == value( membrane.fs.unit.sweep_side_outlet.pressure[0] ) @@ -231,20 +232,23 @@ def test_enthalpy_balance(self, membrane): value( ( membrane.fs.unit.feed_side_inlet.flow_mol[0] - * membrane.fs.unit.mscontactor.feed_side_inlet_state[0].enth_mol_phase["Vap"] + * membrane.fs.unit.mscontactor.feed_side_inlet_state[ + 0 + ].enth_mol_phase["Vap"] + membrane.fs.unit.sweep_side_inlet.flow_mol[0] - * membrane.fs.unit.mscontactor.sweep_side_inlet_state[0].enth_mol_phase["Vap"] + * membrane.fs.unit.mscontactor.sweep_side_inlet_state[ + 0 + ].enth_mol_phase["Vap"] - membrane.fs.unit.feed_side_outlet.flow_mol[0] - * membrane.fs.unit.mscontactor.feed_side[0,3].enth_mol_phase["Vap"] + * membrane.fs.unit.mscontactor.feed_side[0, 3].enth_mol_phase[ + "Vap" + ] - membrane.fs.unit.sweep_side_outlet.flow_mol[0] - * membrane.fs.unit.mscontactor.sweep_side[0,1].enth_mol_phase["Vap"] - + * membrane.fs.unit.mscontactor.sweep_side[0, 1].enth_mol_phase[ + "Vap" + ] ) - ) ) <= 1e-6 ) - - - From 693fd689df1c414cb6ef517eddb364d4314ea00d Mon Sep 17 00:00:00 2001 From: Morgan Wang Date: Mon, 18 Mar 2024 11:20:50 -0400 Subject: [PATCH 05/33] reformat using black --- .../ccus/unit_models/membrane_1d.py | 146 +++++++++--------- 1 file changed, 74 insertions(+), 72 deletions(-) diff --git a/idaes/models_extra/ccus/unit_models/membrane_1d.py b/idaes/models_extra/ccus/unit_models/membrane_1d.py index 0137c9bcd3..ddfd3f09e7 100644 --- a/idaes/models_extra/ccus/unit_models/membrane_1d.py +++ b/idaes/models_extra/ccus/unit_models/membrane_1d.py @@ -13,7 +13,7 @@ from enum import Enum -from pyomo.common.config import Bool, ConfigDict, ConfigValue,In +from pyomo.common.config import Bool, ConfigDict, ConfigValue, In from pyomo.environ import Constraint, Param, Var, units, Expression from pyomo.network import Port @@ -28,7 +28,7 @@ from idaes.models.unit_models.mscontactor import MSContactor from idaes.core.util.exceptions import ConfigurationError, InitializationError -#TODO: add robust initialization +# TODO: add robust initialization __author__ = "Maojian Wang" @@ -37,12 +37,12 @@ class MembraneFlowPattern(Enum): Enum of supported flow patterns for membrane. So far only support countercurrent and cocurrent flow """ + countercurrent = 1 cocurrent = 2 crossflow = 3 - @declare_process_block_class("Membrane1D") class Membrane1DData(UnitModelBlockData): """Standard Membrane 1D Unit Model Class.""" @@ -118,17 +118,16 @@ class Membrane1DData(UnitModelBlockData): ), ) CONFIG.declare( - "sweep_flow", - ConfigValue( - default=True, - domain=Bool, - doc="Bool indicating whether there is a sweep flow in the permeate side.", - description="Bool indicating whether stream has a feed Port and inlet " - "state, or if all flow is provided via mass transfer. Default=True.", - ), + "sweep_flow", + ConfigValue( + default=True, + domain=Bool, + doc="Bool indicating whether there is a sweep flow in the permeate side.", + description="Bool indicating whether stream has a feed Port and inlet " + "state, or if all flow is provided via mass transfer. Default=True.", + ), ) - CONFIG.declare( "finite_elements", ConfigValue( @@ -140,7 +139,6 @@ class Membrane1DData(UnitModelBlockData): ), ) - CONFIG.declare( "flow_type", ConfigValue( @@ -150,7 +148,7 @@ class Membrane1DData(UnitModelBlockData): doc="""Flow configuration of membrane - MembraneFlowPattern.cocurrent: feed and sweep flows from 0 to 1 - MembraneFlowPattern.countercurrent: feed side flows from 0 to 1 - sweep side flows from 1 to 0 (default)""" , + sweep side flows from 1 to 0 (default)""", ), ) @@ -178,28 +176,28 @@ class Membrane1DData(UnitModelBlockData): (default = 'use_parent_value') - 'use_parent_value' - get package from parent (default = None) - a dict (see property package for documentation)""", - ), + ), ) - for side_name in ['feed', 'sweep']: - CONFIG.declare(side_name+'_side', Stream_Config(),) - - + for side_name in ["feed", "sweep"]: + CONFIG.declare( + side_name + "_side", + Stream_Config(), + ) def build(self): super().build() - + if self.config.sweep_flow == False: self.config.sweep_side.has_feed = False - # Set flow directions if self.config.flow_type == MembraneFlowPattern.cocurrent: self.config.feed_side.flow_direction = FlowDirection.forward - self.config.sweep_side.flow_direction = FlowDirection.forward + self.config.sweep_side.flow_direction = FlowDirection.forward elif self.config.flow_type == MembraneFlowPattern.countercurrent: self.config.feed_side.flow_direction = FlowDirection.forward - self.config.sweep_side.flow_direction = FlowDirection.backward + self.config.sweep_side.flow_direction = FlowDirection.backward else: raise ConfigurationError( @@ -207,14 +205,13 @@ def build(self): "countercurrent flow patterns, but flow_type configuration" " argument was set to {}.".format(self.name, self.config.flow_type) ) - + if self.config.property_package != None: if self.config.feed_side.property_package == useDefault: self.config.feed_side.property_package = self.config.property_package if self.config.sweep_side.property_package == useDefault: self.config.sweep_side.property_package = self.config.property_package - streams_dict = { "feed_side": self.config.feed_side, "sweep_side": self.config.sweep_side, @@ -222,7 +219,6 @@ def build(self): self.mscontactor = MSContactor( streams=streams_dict, number_of_finite_elements=self.config.finite_elements, - ) self.feed_side_inlet = Port(extends=self.mscontactor.feed_side_inlet) @@ -230,38 +226,32 @@ def build(self): if self.config.sweep_flow == True: self.sweep_side_inlet = Port(extends=self.mscontactor.sweep_side_inlet) self.sweep_side_outlet = Port(extends=self.mscontactor.sweep_side_outlet) - + self._make_geometry() self._make_performance() - - def _make_geometry(self): - feed_side_units = self.config.feed_side.property_package.get_metadata().derived_units - self.area = Var( - initialize=100, - units = units.cm**2, - doc="The membrane area" + def _make_geometry(self): + feed_side_units = ( + self.config.feed_side.property_package.get_metadata().derived_units ) - self.length = Var( - initialize=100, - units = units.cm, - doc="The membrane length" - ) - self.cell_length = Expression(expr=self.length/self.config.finite_elements) + self.area = Var(initialize=100, units=units.cm**2, doc="The membrane area") + + self.length = Var(initialize=100, units=units.cm, doc="The membrane length") + self.cell_length = Expression(expr=self.length / self.config.finite_elements) self.cell_area = Var( - initialize=100, - units = units.cm**2, - doc="The membrane area" + initialize=100, units=units.cm**2, doc="The membrane area" ) + @self.Constraint() def area_per_cell(self): - return self.cell_area == self.area/self.config.finite_elements - + return self.cell_area == self.area / self.config.finite_elements - def _make_performance(self): - feed_side_units = self.config.feed_side.property_package.get_metadata().derived_units + def _make_performance(self): + feed_side_units = ( + self.config.feed_side.property_package.get_metadata().derived_units + ) self.permeance = Var( self.flowsheet().time, @@ -273,11 +263,11 @@ def _make_performance(self): ) self.gpu_factor = Param( - default=10e-8/13333.2239, - units=units.m/units.s/units.Pa, + default=10e-8 / 13333.2239, + units=units.m / units.s / units.Pa, mutable=True, ) - + self.selectivity = Var( self.flowsheet().time, self.mscontactor.elements, @@ -292,48 +282,60 @@ def _make_performance(self): self.mscontactor.elements, self.mscontactor.feed_side.component_list, self.mscontactor.feed_side.component_list, - doc = "permeance calculation", + doc="permeance calculation", ) - def permeance_calculation(self, t, e, a , b ): + def permeance_calculation(self, t, e, a, b): return ( - self.permeance[t,e,a] * self.selectivity[t,e,a,b] == self.permeance[t,e,b] + self.permeance[t, e, a] * self.selectivity[t, e, a, b] + == self.permeance[t, e, b] ) - p_units = feed_side_units.PRESSURE @self.Constraint( self.flowsheet().time, self.mscontactor.elements, self.mscontactor.feed_side.component_list, - doc = "permeability calculation", + doc="permeability calculation", ) def permeability_calculation(self, t, s, m): - feed_side_state = self.mscontactor.feed_side[t,s] + feed_side_state = self.mscontactor.feed_side[t, s] if feed_side_state.get_material_flow_basis() is MaterialFlowBasis.molar: mb_units = feed_side_units.FLOW_MOLE - rho = self.mscontactor.feed_side[t,s].dens_mol + rho = self.mscontactor.feed_side[t, s].dens_mol elif feed_side_state.get_material_flow_basis() is MaterialFlowBasis.mass: mb_units = feed_side_units.FLOW_MASS - rho = self.mscontactor.feed_side[t,s].dens_mass + rho = self.mscontactor.feed_side[t, s].dens_mass else: raise TypeError("Undefined flow basis, please define the flow basis") - return ( - self.mscontactor.material_transfer_term[t, s, 'feed_side', 'sweep_side', m] - == - units.convert(( - rho * self.gpu_factor * self.permeance[t,s,m] * self.cell_area * - (self.mscontactor.feed_side[t,s].pressure * self.mscontactor.feed_side[t, s].mole_frac_comp[m] - -units.convert(self.mscontactor.sweep_side[t,s].pressure, to_units=p_units) * self.mscontactor.sweep_side[t, s].mole_frac_comp[m]) - ), - to_units = mb_units,) - ) - + return self.mscontactor.material_transfer_term[ + t, s, "feed_side", "sweep_side", m + ] == -units.convert( + ( + rho + * self.gpu_factor + * self.permeance[t, s, m] + * self.cell_area + * ( + self.mscontactor.feed_side[t, s].pressure + * self.mscontactor.feed_side[t, s].mole_frac_comp[m] + - units.convert( + self.mscontactor.sweep_side[t, s].pressure, to_units=p_units + ) + * self.mscontactor.sweep_side[t, s].mole_frac_comp[m] + ) + ), + to_units=mb_units, + ) + @self.Constraint( self.flowsheet().time, - self.mscontactor.elements, - doc = "Energy balance" , + self.mscontactor.elements, + doc="Energy balance", ) def energy_transfer(self, t, s): - return self.mscontactor.feed_side[t, s].temperature == self.mscontactor.sweep_side[t, s].temperature - + return ( + self.mscontactor.feed_side[t, s].temperature + == self.mscontactor.sweep_side[t, s].temperature + ) From 5505b1fefc99b467e80e91a7e1f76708c5322432 Mon Sep 17 00:00:00 2001 From: Morgan Wang Date: Mon, 18 Mar 2024 12:01:53 -0400 Subject: [PATCH 06/33] fixed the path issue in testing --- idaes/models_extra/ccus/unit_models/__init__.py | 2 ++ idaes/models_extra/ccus/unit_models/tests/test_membrane_1d.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/idaes/models_extra/ccus/unit_models/__init__.py b/idaes/models_extra/ccus/unit_models/__init__.py index e69de29bb2..149b4a3069 100644 --- a/idaes/models_extra/ccus/unit_models/__init__.py +++ b/idaes/models_extra/ccus/unit_models/__init__.py @@ -0,0 +1,2 @@ + +from .membrane_1d import Membrane1D, MembraneFlowPattern \ No newline at end of file diff --git a/idaes/models_extra/ccus/unit_models/tests/test_membrane_1d.py b/idaes/models_extra/ccus/unit_models/tests/test_membrane_1d.py index 97c7331d10..49a3fe47bf 100644 --- a/idaes/models_extra/ccus/unit_models/tests/test_membrane_1d.py +++ b/idaes/models_extra/ccus/unit_models/tests/test_membrane_1d.py @@ -40,7 +40,7 @@ from idaes.models.properties.modular_properties.base.generic_property import ( GenericParameterBlock, ) -from membrane_unit import Membrane1D, MembraneFlowPattern +from idaes.models_extra.ccus.unit_models import Membrane1D, MembraneFlowPattern # ----------------------------------------------------------------------------- From f91e825846bd6d0037e4332a29ad2adf93ecd720 Mon Sep 17 00:00:00 2001 From: Morgan Wang Date: Mon, 18 Mar 2024 12:09:05 -0400 Subject: [PATCH 07/33] added the missing heading --- idaes/models_extra/ccus/unit_models/__init__.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/idaes/models_extra/ccus/unit_models/__init__.py b/idaes/models_extra/ccus/unit_models/__init__.py index 149b4a3069..97726a0e7f 100644 --- a/idaes/models_extra/ccus/unit_models/__init__.py +++ b/idaes/models_extra/ccus/unit_models/__init__.py @@ -1,2 +1,13 @@ - +################################################################################# +# 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-2023 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. +################################################################################# from .membrane_1d import Membrane1D, MembraneFlowPattern \ No newline at end of file From 446951bf6559012f4850c5efa70ff22f9f83045b Mon Sep 17 00:00:00 2001 From: Morgan Wang Date: Tue, 19 Mar 2024 13:35:03 -0400 Subject: [PATCH 08/33] refined the workspace name --- .../{ccus => co2_capture_and_utilization}/__init__.py | 0 .../{ccus => co2_capture_and_utilization}/unit_models/README.md | 0 .../{ccus => co2_capture_and_utilization}/unit_models/__init__.py | 0 .../unit_models/membrane_1d.py | 0 .../unit_models/tests/__init__.py | 0 .../unit_models/tests/test_membrane_1d.py | 0 6 files changed, 0 insertions(+), 0 deletions(-) rename idaes/models_extra/{ccus => co2_capture_and_utilization}/__init__.py (100%) rename idaes/models_extra/{ccus => co2_capture_and_utilization}/unit_models/README.md (100%) rename idaes/models_extra/{ccus => co2_capture_and_utilization}/unit_models/__init__.py (100%) rename idaes/models_extra/{ccus => co2_capture_and_utilization}/unit_models/membrane_1d.py (100%) rename idaes/models_extra/{ccus => co2_capture_and_utilization}/unit_models/tests/__init__.py (100%) rename idaes/models_extra/{ccus => co2_capture_and_utilization}/unit_models/tests/test_membrane_1d.py (100%) diff --git a/idaes/models_extra/ccus/__init__.py b/idaes/models_extra/co2_capture_and_utilization/__init__.py similarity index 100% rename from idaes/models_extra/ccus/__init__.py rename to idaes/models_extra/co2_capture_and_utilization/__init__.py diff --git a/idaes/models_extra/ccus/unit_models/README.md b/idaes/models_extra/co2_capture_and_utilization/unit_models/README.md similarity index 100% rename from idaes/models_extra/ccus/unit_models/README.md rename to idaes/models_extra/co2_capture_and_utilization/unit_models/README.md diff --git a/idaes/models_extra/ccus/unit_models/__init__.py b/idaes/models_extra/co2_capture_and_utilization/unit_models/__init__.py similarity index 100% rename from idaes/models_extra/ccus/unit_models/__init__.py rename to idaes/models_extra/co2_capture_and_utilization/unit_models/__init__.py diff --git a/idaes/models_extra/ccus/unit_models/membrane_1d.py b/idaes/models_extra/co2_capture_and_utilization/unit_models/membrane_1d.py similarity index 100% rename from idaes/models_extra/ccus/unit_models/membrane_1d.py rename to idaes/models_extra/co2_capture_and_utilization/unit_models/membrane_1d.py diff --git a/idaes/models_extra/ccus/unit_models/tests/__init__.py b/idaes/models_extra/co2_capture_and_utilization/unit_models/tests/__init__.py similarity index 100% rename from idaes/models_extra/ccus/unit_models/tests/__init__.py rename to idaes/models_extra/co2_capture_and_utilization/unit_models/tests/__init__.py diff --git a/idaes/models_extra/ccus/unit_models/tests/test_membrane_1d.py b/idaes/models_extra/co2_capture_and_utilization/unit_models/tests/test_membrane_1d.py similarity index 100% rename from idaes/models_extra/ccus/unit_models/tests/test_membrane_1d.py rename to idaes/models_extra/co2_capture_and_utilization/unit_models/tests/test_membrane_1d.py From db77a6566a3d0bb84a88dbc72cda47ff2b6bf435 Mon Sep 17 00:00:00 2001 From: Morgan Wang Date: Tue, 19 Mar 2024 13:48:25 -0400 Subject: [PATCH 09/33] formatting issue resolved for GitHub test --- .../co2_capture_and_utilization/unit_models/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/idaes/models_extra/co2_capture_and_utilization/unit_models/__init__.py b/idaes/models_extra/co2_capture_and_utilization/unit_models/__init__.py index 97726a0e7f..03c96ba57b 100644 --- a/idaes/models_extra/co2_capture_and_utilization/unit_models/__init__.py +++ b/idaes/models_extra/co2_capture_and_utilization/unit_models/__init__.py @@ -10,4 +10,4 @@ # All rights reserved. Please see the files COPYRIGHT.md and LICENSE.md # for full copyright and license information. ################################################################################# -from .membrane_1d import Membrane1D, MembraneFlowPattern \ No newline at end of file +from .membrane_1d import Membrane1D, MembraneFlowPattern From 508f10c723c4c42700641eccef09baa7aa28e7e3 Mon Sep 17 00:00:00 2001 From: Morgan Wang Date: Tue, 19 Mar 2024 14:23:51 -0400 Subject: [PATCH 10/33] fixed typo --- .../co2_capture_and_utilization/unit_models/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/idaes/models_extra/co2_capture_and_utilization/unit_models/README.md b/idaes/models_extra/co2_capture_and_utilization/unit_models/README.md index c562e71fb1..f31b88e6d7 100644 --- a/idaes/models_extra/co2_capture_and_utilization/unit_models/README.md +++ b/idaes/models_extra/co2_capture_and_utilization/unit_models/README.md @@ -1 +1 @@ -This directory contains theunit models of Carbon Capture, Utilisation and Storage(CCUS) +This directory contains the unit models for Carbon Capture and Utilization From d31a4cbd06cb2f5751fd98f315acb522ef0a4d74 Mon Sep 17 00:00:00 2001 From: Morgan Wang Date: Fri, 29 Mar 2024 11:34:02 -0400 Subject: [PATCH 11/33] fixed the unit model importing issue --- .../unit_models/tests/test_membrane_1d.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/idaes/models_extra/co2_capture_and_utilization/unit_models/tests/test_membrane_1d.py b/idaes/models_extra/co2_capture_and_utilization/unit_models/tests/test_membrane_1d.py index 49a3fe47bf..46e5965325 100644 --- a/idaes/models_extra/co2_capture_and_utilization/unit_models/tests/test_membrane_1d.py +++ b/idaes/models_extra/co2_capture_and_utilization/unit_models/tests/test_membrane_1d.py @@ -40,8 +40,7 @@ from idaes.models.properties.modular_properties.base.generic_property import ( GenericParameterBlock, ) -from idaes.models_extra.ccus.unit_models import Membrane1D, MembraneFlowPattern - +from idaes.models_extra.co2_capture_and_utilization.unit_models import Membrane1D, MembraneFlowPattern # ----------------------------------------------------------------------------- # Get default solver for testing From e228592dc9f082d558241fd43d17bb9c7c4e45f5 Mon Sep 17 00:00:00 2001 From: Morgan Wang Date: Fri, 29 Mar 2024 11:56:27 -0400 Subject: [PATCH 12/33] fixed linter warnings --- .../unit_models/membrane_1d.py | 40 ++++++++++--------- .../unit_models/tests/test_membrane_1d.py | 4 +- 2 files changed, 23 insertions(+), 21 deletions(-) diff --git a/idaes/models_extra/co2_capture_and_utilization/unit_models/membrane_1d.py b/idaes/models_extra/co2_capture_and_utilization/unit_models/membrane_1d.py index ddfd3f09e7..e1eb92561b 100644 --- a/idaes/models_extra/co2_capture_and_utilization/unit_models/membrane_1d.py +++ b/idaes/models_extra/co2_capture_and_utilization/unit_models/membrane_1d.py @@ -11,10 +11,14 @@ # for full copyright and license information. ################################################################################# +""" +One-dimensional membrane class for CO2 gas separation +""" + from enum import Enum from pyomo.common.config import Bool, ConfigDict, ConfigValue, In -from pyomo.environ import Constraint, Param, Var, units, Expression +from pyomo.environ import Constraint, Param, Var, units, Expression #pylint: disable = unused-import from pyomo.network import Port from idaes.core import ( @@ -26,21 +30,22 @@ ) from idaes.core.util.config import is_physical_parameter_block from idaes.models.unit_models.mscontactor import MSContactor -from idaes.core.util.exceptions import ConfigurationError, InitializationError +from idaes.core.util.exceptions import ConfigurationError -# TODO: add robust initialization __author__ = "Maojian Wang" + + class MembraneFlowPattern(Enum): """ Enum of supported flow patterns for membrane. So far only support countercurrent and cocurrent flow """ - countercurrent = 1 - cocurrent = 2 - crossflow = 3 + COUNTERCURRENT = 1 + COCURRENT = 2 + CROSSFLOW = 3 @declare_process_block_class("Membrane1D") @@ -142,12 +147,12 @@ class Membrane1DData(UnitModelBlockData): CONFIG.declare( "flow_type", ConfigValue( - default=MembraneFlowPattern.countercurrent, + default=MembraneFlowPattern.COUNTERCURRENT, domain=In(MembraneFlowPattern), description="Flow configuration of membrane", doc="""Flow configuration of membrane - - MembraneFlowPattern.cocurrent: feed and sweep flows from 0 to 1 - - MembraneFlowPattern.countercurrent: feed side flows from 0 to 1 + - MembraneFlowPattern.COCURRENT: feed and sweep flows from 0 to 1 + - MembraneFlowPattern.COUNTERCURRENT: feed side flows from 0 to 1 sweep side flows from 1 to 0 (default)""", ), ) @@ -188,25 +193,25 @@ class Membrane1DData(UnitModelBlockData): def build(self): super().build() - if self.config.sweep_flow == False: + if self.config.sweep_flow is False: self.config.sweep_side.has_feed = False # Set flow directions - if self.config.flow_type == MembraneFlowPattern.cocurrent: + if self.config.flow_type == MembraneFlowPattern.COCURRENT: self.config.feed_side.flow_direction = FlowDirection.forward self.config.sweep_side.flow_direction = FlowDirection.forward - elif self.config.flow_type == MembraneFlowPattern.countercurrent: + elif self.config.flow_type == MembraneFlowPattern.COUNTERCURRENT: self.config.feed_side.flow_direction = FlowDirection.forward self.config.sweep_side.flow_direction = FlowDirection.backward else: raise ConfigurationError( - "{} Membrane1D only supports cocurrent and " + f"{self.name} Membrane1D only supports cocurrent and " "countercurrent flow patterns, but flow_type configuration" - " argument was set to {}.".format(self.name, self.config.flow_type) + " argument was set to {config.flow_type}." ) - if self.config.property_package != None: + if self.config.property_package is not None: if self.config.feed_side.property_package == useDefault: self.config.feed_side.property_package = self.config.property_package if self.config.sweep_side.property_package == useDefault: @@ -223,7 +228,7 @@ def build(self): self.feed_side_inlet = Port(extends=self.mscontactor.feed_side_inlet) self.feed_side_outlet = Port(extends=self.mscontactor.feed_side_outlet) - if self.config.sweep_flow == True: + if self.config.sweep_flow is True: self.sweep_side_inlet = Port(extends=self.mscontactor.sweep_side_inlet) self.sweep_side_outlet = Port(extends=self.mscontactor.sweep_side_outlet) @@ -231,9 +236,6 @@ def build(self): self._make_performance() def _make_geometry(self): - feed_side_units = ( - self.config.feed_side.property_package.get_metadata().derived_units - ) self.area = Var(initialize=100, units=units.cm**2, doc="The membrane area") diff --git a/idaes/models_extra/co2_capture_and_utilization/unit_models/tests/test_membrane_1d.py b/idaes/models_extra/co2_capture_and_utilization/unit_models/tests/test_membrane_1d.py index 46e5965325..c0bcc89674 100644 --- a/idaes/models_extra/co2_capture_and_utilization/unit_models/tests/test_membrane_1d.py +++ b/idaes/models_extra/co2_capture_and_utilization/unit_models/tests/test_membrane_1d.py @@ -61,7 +61,7 @@ def test_config(): finite_elements=3, dynamic=False, sweep_flow=True, - flow_type=MembraneFlowPattern.countercurrent, + flow_type=MembraneFlowPattern.COUNTERCURRENT, property_package=m.fs.properties, ) @@ -86,7 +86,7 @@ def membrane(self): finite_elements=3, dynamic=False, sweep_flow=True, - flow_type=MembraneFlowPattern.countercurrent, + flow_type=MembraneFlowPattern.COUNTERCURRENT, property_package=m.fs.properties, ) From feda25c9d31a98cd4b3142705afcd373e95b7ef4 Mon Sep 17 00:00:00 2001 From: Morgan Wang Date: Fri, 29 Mar 2024 12:27:12 -0400 Subject: [PATCH 13/33] resolved the comments --- .../unit_models/membrane_1d.py | 69 ++++++------------- .../unit_models/tests/test_membrane_1d.py | 5 +- 2 files changed, 25 insertions(+), 49 deletions(-) diff --git a/idaes/models_extra/co2_capture_and_utilization/unit_models/membrane_1d.py b/idaes/models_extra/co2_capture_and_utilization/unit_models/membrane_1d.py index e1eb92561b..bee55e4a57 100644 --- a/idaes/models_extra/co2_capture_and_utilization/unit_models/membrane_1d.py +++ b/idaes/models_extra/co2_capture_and_utilization/unit_models/membrane_1d.py @@ -18,7 +18,13 @@ from enum import Enum from pyomo.common.config import Bool, ConfigDict, ConfigValue, In -from pyomo.environ import Constraint, Param, Var, units, Expression #pylint: disable = unused-import +from pyomo.environ import ( + Constraint, + Param, + Var, + units, + Expression, +) # pylint: disable = unused-import from pyomo.network import Port from idaes.core import ( @@ -35,8 +41,6 @@ __author__ = "Maojian Wang" - - class MembraneFlowPattern(Enum): """ Enum of supported flow patterns for membrane. @@ -69,7 +73,6 @@ class Membrane1DData(UnitModelBlockData): **PhysicalParameterObject** - a PhysicalParameterBlock object.}""", ), ) - Stream_Config.declare( "property_package_args", ConfigDict( @@ -82,18 +85,6 @@ class Membrane1DData(UnitModelBlockData): see property package for documentation.}""", ), ) - - Stream_Config.declare( - "flow_direction", - ConfigValue( - default=FlowDirection.forward, - domain=In(FlowDirection), - doc="Direction of flow for stream", - description="FlowDirection Enum indicating direction of " - "flow for given stream. Default=FlowDirection.forward.", - ), - ) - Stream_Config.declare( "has_energy_balance", ConfigValue( @@ -102,7 +93,6 @@ class Membrane1DData(UnitModelBlockData): doc="Bool indicating whether to include energy balance for stream. Default=True.", ), ) - Stream_Config.declare( "has_pressure_balance", ConfigValue( @@ -112,16 +102,6 @@ class Membrane1DData(UnitModelBlockData): ), ) - Stream_Config.declare( - "has_feed", - ConfigValue( - default=True, - domain=Bool, - doc="Bool indicating whether stream has a feed.", - description="Bool indicating whether stream has a feed Port and inlet " - "state, or if all flow is provided via mass transfer. Default=True.", - ), - ) CONFIG.declare( "sweep_flow", ConfigValue( @@ -132,7 +112,6 @@ class Membrane1DData(UnitModelBlockData): "state, or if all flow is provided via mass transfer. Default=True.", ), ) - CONFIG.declare( "finite_elements", ConfigValue( @@ -143,7 +122,6 @@ class Membrane1DData(UnitModelBlockData): domain (default=5)""", ), ) - CONFIG.declare( "flow_type", ConfigValue( @@ -156,7 +134,6 @@ class Membrane1DData(UnitModelBlockData): sweep side flows from 1 to 0 (default)""", ), ) - CONFIG.declare( "property_package", ConfigValue( @@ -170,7 +147,6 @@ class Membrane1DData(UnitModelBlockData): - a ParameterBlock object""", ), ) - CONFIG.declare( "property_package_args", ConfigValue( @@ -193,17 +169,20 @@ class Membrane1DData(UnitModelBlockData): def build(self): super().build() - if self.config.sweep_flow is False: - self.config.sweep_side.has_feed = False + if self.config.property_package is not None: + if self.config.feed_side.property_package == useDefault: + self.config.feed_side.property_package = self.config.property_package + if self.config.sweep_side.property_package == useDefault: + self.config.sweep_side.property_package = self.config.property_package + + feed_dict = dict(self.config.feed_side) + sweep_dict = dict(self.config.sweep_side) - # Set flow directions + feed_dict["flow_direction"] = FlowDirection.forward if self.config.flow_type == MembraneFlowPattern.COCURRENT: - self.config.feed_side.flow_direction = FlowDirection.forward - self.config.sweep_side.flow_direction = FlowDirection.forward + sweep_dict["flow_direction"] = FlowDirection.forward elif self.config.flow_type == MembraneFlowPattern.COUNTERCURRENT: - self.config.feed_side.flow_direction = FlowDirection.forward - self.config.sweep_side.flow_direction = FlowDirection.backward - + sweep_dict["flow_direction"] = FlowDirection.backward else: raise ConfigurationError( f"{self.name} Membrane1D only supports cocurrent and " @@ -211,16 +190,10 @@ def build(self): " argument was set to {config.flow_type}." ) - if self.config.property_package is not None: - if self.config.feed_side.property_package == useDefault: - self.config.feed_side.property_package = self.config.property_package - if self.config.sweep_side.property_package == useDefault: - self.config.sweep_side.property_package = self.config.property_package + if self.config.sweep_flow is False: + sweep_dict["has_feed"] = False - streams_dict = { - "feed_side": self.config.feed_side, - "sweep_side": self.config.sweep_side, - } + streams_dict = {"feed_side": feed_dict, "sweep_side": sweep_dict} self.mscontactor = MSContactor( streams=streams_dict, number_of_finite_elements=self.config.finite_elements, diff --git a/idaes/models_extra/co2_capture_and_utilization/unit_models/tests/test_membrane_1d.py b/idaes/models_extra/co2_capture_and_utilization/unit_models/tests/test_membrane_1d.py index c0bcc89674..e261fc5737 100644 --- a/idaes/models_extra/co2_capture_and_utilization/unit_models/tests/test_membrane_1d.py +++ b/idaes/models_extra/co2_capture_and_utilization/unit_models/tests/test_membrane_1d.py @@ -40,7 +40,10 @@ from idaes.models.properties.modular_properties.base.generic_property import ( GenericParameterBlock, ) -from idaes.models_extra.co2_capture_and_utilization.unit_models import Membrane1D, MembraneFlowPattern +from idaes.models_extra.co2_capture_and_utilization.unit_models import ( + Membrane1D, + MembraneFlowPattern, +) # ----------------------------------------------------------------------------- # Get default solver for testing From 054808fc006547d0a53a6fe136406b33829bcc36 Mon Sep 17 00:00:00 2001 From: Morgan Wang Date: Fri, 29 Mar 2024 14:51:32 -0400 Subject: [PATCH 14/33] fix linter issues --- .../co2_capture_and_utilization/unit_models/membrane_1d.py | 6 +++--- .../unit_models/tests/test_membrane_1d.py | 7 ++++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/idaes/models_extra/co2_capture_and_utilization/unit_models/membrane_1d.py b/idaes/models_extra/co2_capture_and_utilization/unit_models/membrane_1d.py index bee55e4a57..d01c5bb88e 100644 --- a/idaes/models_extra/co2_capture_and_utilization/unit_models/membrane_1d.py +++ b/idaes/models_extra/co2_capture_and_utilization/unit_models/membrane_1d.py @@ -15,16 +15,16 @@ One-dimensional membrane class for CO2 gas separation """ - +# pylint: disable=unused-import from enum import Enum from pyomo.common.config import Bool, ConfigDict, ConfigValue, In from pyomo.environ import ( - Constraint, + Constraint, Param, Var, units, Expression, -) # pylint: disable = unused-import +) from pyomo.network import Port from idaes.core import ( diff --git a/idaes/models_extra/co2_capture_and_utilization/unit_models/tests/test_membrane_1d.py b/idaes/models_extra/co2_capture_and_utilization/unit_models/tests/test_membrane_1d.py index e261fc5737..f561364893 100644 --- a/idaes/models_extra/co2_capture_and_utilization/unit_models/tests/test_membrane_1d.py +++ b/idaes/models_extra/co2_capture_and_utilization/unit_models/tests/test_membrane_1d.py @@ -15,13 +15,14 @@ """ __author__ = "Maojian Wang" +# pylint: disable=unused-import import pytest -from pyomo.environ import ( +from pyomo.environ import ( check_optimal_termination, ConcreteModel, value, -) +) from idaes.core import FlowsheetBlock from idaes.core.util.model_statistics import ( number_variables, @@ -39,7 +40,7 @@ ) from idaes.models.properties.modular_properties.base.generic_property import ( GenericParameterBlock, -) +) from idaes.models_extra.co2_capture_and_utilization.unit_models import ( Membrane1D, MembraneFlowPattern, From f6dc03b4a406dfadc45bf2aa3e0b7b2dbbb26f43 Mon Sep 17 00:00:00 2001 From: Morgan Wang Date: Fri, 29 Mar 2024 14:54:50 -0400 Subject: [PATCH 15/33] formated --- .../co2_capture_and_utilization/unit_models/membrane_1d.py | 4 ++-- .../unit_models/tests/test_membrane_1d.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/idaes/models_extra/co2_capture_and_utilization/unit_models/membrane_1d.py b/idaes/models_extra/co2_capture_and_utilization/unit_models/membrane_1d.py index d01c5bb88e..1cf1feb4a6 100644 --- a/idaes/models_extra/co2_capture_and_utilization/unit_models/membrane_1d.py +++ b/idaes/models_extra/co2_capture_and_utilization/unit_models/membrane_1d.py @@ -19,12 +19,12 @@ from enum import Enum from pyomo.common.config import Bool, ConfigDict, ConfigValue, In from pyomo.environ import ( - Constraint, + Constraint, Param, Var, units, Expression, -) +) from pyomo.network import Port from idaes.core import ( diff --git a/idaes/models_extra/co2_capture_and_utilization/unit_models/tests/test_membrane_1d.py b/idaes/models_extra/co2_capture_and_utilization/unit_models/tests/test_membrane_1d.py index f561364893..821a763493 100644 --- a/idaes/models_extra/co2_capture_and_utilization/unit_models/tests/test_membrane_1d.py +++ b/idaes/models_extra/co2_capture_and_utilization/unit_models/tests/test_membrane_1d.py @@ -18,11 +18,11 @@ # pylint: disable=unused-import import pytest -from pyomo.environ import ( +from pyomo.environ import ( check_optimal_termination, ConcreteModel, value, -) +) from idaes.core import FlowsheetBlock from idaes.core.util.model_statistics import ( number_variables, @@ -40,7 +40,7 @@ ) from idaes.models.properties.modular_properties.base.generic_property import ( GenericParameterBlock, -) +) from idaes.models_extra.co2_capture_and_utilization.unit_models import ( Membrane1D, MembraneFlowPattern, From 14d3856d59a53bed0366f2e2459b1ee75460106d Mon Sep 17 00:00:00 2001 From: Maojian Wang Date: Mon, 13 May 2024 13:54:09 -0400 Subject: [PATCH 16/33] remove the unit level property config --- .../unit_models/tests/test_membrane_1d.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/idaes/models_extra/co2_capture_and_utilization/unit_models/tests/test_membrane_1d.py b/idaes/models_extra/co2_capture_and_utilization/unit_models/tests/test_membrane_1d.py index 821a763493..6e46ff0c1b 100644 --- a/idaes/models_extra/co2_capture_and_utilization/unit_models/tests/test_membrane_1d.py +++ b/idaes/models_extra/co2_capture_and_utilization/unit_models/tests/test_membrane_1d.py @@ -66,14 +66,15 @@ def test_config(): dynamic=False, sweep_flow=True, flow_type=MembraneFlowPattern.COUNTERCURRENT, - property_package=m.fs.properties, + feed_side = { "property_package" : m.fs.properties }, + sweep_side = {"property_package" : m.fs.properties }, ) # Check unit config arguments - assert len(m.fs.unit.config) == 9 + assert len(m.fs.unit.config) == 7 assert not m.fs.unit.config.dynamic assert not m.fs.unit.config.has_holdup - assert m.fs.unit.config.property_package is m.fs.properties + class TestMembrane(object): @@ -91,8 +92,10 @@ def membrane(self): dynamic=False, sweep_flow=True, flow_type=MembraneFlowPattern.COUNTERCURRENT, - property_package=m.fs.properties, + feed_side = { "property_package" : m.fs.properties }, + sweep_side = {"property_package" : m.fs.properties }, ) + m.fs.unit.permeance[:, :, "CO2"].fix(1500) m.fs.unit.permeance[:, :, "H2O"].fix(1500 / 25) From e8bc9280d0bf19810eab2c37a38c8f14780a4892 Mon Sep 17 00:00:00 2001 From: Maojian Wang Date: Tue, 14 May 2024 16:23:57 -0400 Subject: [PATCH 17/33] save changes --- .../unit_models/membrane_1d.py | 32 +------------------ 1 file changed, 1 insertion(+), 31 deletions(-) diff --git a/idaes/models_extra/co2_capture_and_utilization/unit_models/membrane_1d.py b/idaes/models_extra/co2_capture_and_utilization/unit_models/membrane_1d.py index 1cf1feb4a6..1300758f3f 100644 --- a/idaes/models_extra/co2_capture_and_utilization/unit_models/membrane_1d.py +++ b/idaes/models_extra/co2_capture_and_utilization/unit_models/membrane_1d.py @@ -134,31 +134,7 @@ class Membrane1DData(UnitModelBlockData): sweep side flows from 1 to 0 (default)""", ), ) - CONFIG.declare( - "property_package", - ConfigValue( - default=None, - domain=is_physical_parameter_block, - description="Property package to use for control volume", - doc="""Property parameter object used to define property - calculations - (default = 'use_parent_value') - - 'use_parent_value' - get package from parent (default = None) - - a ParameterBlock object""", - ), - ) - CONFIG.declare( - "property_package_args", - ConfigValue( - default={}, - description="Arguments for constructing property package", - doc="""A dict of arguments to be passed to the PropertyBlockData - and used when constructing these - (default = 'use_parent_value') - - 'use_parent_value' - get package from parent (default = None) - - a dict (see property package for documentation)""", - ), - ) + for side_name in ["feed", "sweep"]: CONFIG.declare( @@ -169,12 +145,6 @@ class Membrane1DData(UnitModelBlockData): def build(self): super().build() - if self.config.property_package is not None: - if self.config.feed_side.property_package == useDefault: - self.config.feed_side.property_package = self.config.property_package - if self.config.sweep_side.property_package == useDefault: - self.config.sweep_side.property_package = self.config.property_package - feed_dict = dict(self.config.feed_side) sweep_dict = dict(self.config.sweep_side) From e721692df071b28e0ceb6e171e163151a5ea5b8e Mon Sep 17 00:00:00 2001 From: Maojian Wang Date: Tue, 5 Nov 2024 12:49:07 -0500 Subject: [PATCH 18/33] added test for different configs and added stream table display --- .../unit_models/membrane_1d.py | 15 ++++++++++- .../unit_models/tests/test_membrane_1d.py | 26 ++++++++++++++++++- 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/idaes/models_extra/co2_capture_and_utilization/unit_models/membrane_1d.py b/idaes/models_extra/co2_capture_and_utilization/unit_models/membrane_1d.py index 1300758f3f..ead0760b11 100644 --- a/idaes/models_extra/co2_capture_and_utilization/unit_models/membrane_1d.py +++ b/idaes/models_extra/co2_capture_and_utilization/unit_models/membrane_1d.py @@ -37,6 +37,7 @@ from idaes.core.util.config import is_physical_parameter_block from idaes.models.unit_models.mscontactor import MSContactor from idaes.core.util.exceptions import ConfigurationError +from idaes.core.util.tables import create_stream_table_dataframe __author__ = "Maojian Wang" @@ -49,7 +50,7 @@ class MembraneFlowPattern(Enum): COUNTERCURRENT = 1 COCURRENT = 2 - CROSSFLOW = 3 + @declare_process_block_class("Membrane1D") @@ -284,3 +285,15 @@ def energy_transfer(self, t, s): self.mscontactor.feed_side[t, s].temperature == self.mscontactor.sweep_side[t, s].temperature ) + + def _get_stream_table_contents(self, time_point=0): + return create_stream_table_dataframe( + { + "Feed Inlet": self.feed_side_inlet, + "Feed Outlet": self.feed_side_outlet, + "Permeate Inlet": self.sweep_side_inlet, + "Permeate Outlet": self.sweep_side_outlet, + }, + time_point=time_point, + ) + diff --git a/idaes/models_extra/co2_capture_and_utilization/unit_models/tests/test_membrane_1d.py b/idaes/models_extra/co2_capture_and_utilization/unit_models/tests/test_membrane_1d.py index 6e46ff0c1b..c30f2fa2d4 100644 --- a/idaes/models_extra/co2_capture_and_utilization/unit_models/tests/test_membrane_1d.py +++ b/idaes/models_extra/co2_capture_and_utilization/unit_models/tests/test_membrane_1d.py @@ -52,7 +52,7 @@ # ----------------------------------------------------------------------------- @pytest.mark.unit -def test_config(): +def test_config_countercurrent(): m = ConcreteModel() m.fs = FlowsheetBlock(dynamic=False) @@ -75,6 +75,30 @@ def test_config(): assert not m.fs.unit.config.dynamic assert not m.fs.unit.config.has_holdup +@pytest.mark.unit +def test_congif_cocurrent(): + m = ConcreteModel() + m.fs = FlowsheetBlock(dynamic=False) + + m.fs.properties = GenericParameterBlock( + **get_prop(["CO2", "H2O", "N2"], ["Vap"], eos=EosType.IDEAL), + doc="Key flue gas property parameters", + ) + + m.fs.unit = Membrane1D( + finite_elements=3, + dynamic=False, + sweep_flow=True, + flow_type=MembraneFlowPattern.COCURRENT, + feed_side = { "property_package" : m.fs.properties }, + sweep_side = {"property_package" : m.fs.properties }, + ) + + # Check unit config arguments + assert len(m.fs.unit.config) == 7 + assert not m.fs.unit.config.dynamic + assert not m.fs.unit.config.has_holdup + class TestMembrane(object): From 2cd61a4ec4d8941800d77879944cb14982416bd8 Mon Sep 17 00:00:00 2001 From: Maojian Wang Date: Tue, 5 Nov 2024 13:10:48 -0500 Subject: [PATCH 19/33] added more docs to explain the models and settings --- .../unit_models/membrane_1d.py | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/idaes/models_extra/co2_capture_and_utilization/unit_models/membrane_1d.py b/idaes/models_extra/co2_capture_and_utilization/unit_models/membrane_1d.py index ead0760b11..bc5ef523a0 100644 --- a/idaes/models_extra/co2_capture_and_utilization/unit_models/membrane_1d.py +++ b/idaes/models_extra/co2_capture_and_utilization/unit_models/membrane_1d.py @@ -144,6 +144,18 @@ class Membrane1DData(UnitModelBlockData): ) def build(self): + + """ + This is a one-dimensional model for gas separation in CO₂ capture applications. + The model will be discretized in the flow direction, and it supports two flow patterns: + counter-current flow and co-current flow. The model was customized for gas-phase separation + in CO₂ capture with a single-layer design. If a multi-layer design is needed, multiple units + can be connected for this application. The two sides of the membrane are called the feed side + and sweep side. The sweep stream inlet is optional. The driving force across the membrane is the + partial pressure difference in this gas separation application. Additionally, the energy balance + assumes that temperature remains constant on each side of the membrane. + + """ super().build() feed_dict = dict(self.config.feed_side) @@ -212,8 +224,15 @@ def _make_performance(self): default=10e-8 / 13333.2239, units=units.m / units.s / units.Pa, mutable=True, + doc=" This is a coefficient that will convert the unit of permeability from GPU to SI units for further calculation", ) + + """ + Selectivity is defined for cases where some permeabilities are unavailable. Only define the selectivity + between different components; others should be set to 1. If the permeabilities of all components are defined + there is no need to define selectivity, as this may overdefine the problem. + """ self.selectivity = Var( self.flowsheet().time, self.mscontactor.elements, @@ -223,6 +242,7 @@ def _make_performance(self): units=units.dimensionless, ) + @self.Constraint( self.flowsheet().time, self.mscontactor.elements, From 3e3dda8ffa92a09e56e6a6aab716475ef6bc16f9 Mon Sep 17 00:00:00 2001 From: Maojian Wang Date: Tue, 5 Nov 2024 13:36:00 -0500 Subject: [PATCH 20/33] Addressed the comments to support different property packages --- .../unit_models/membrane_1d.py | 40 +++++++++---------- .../unit_models/tests/test_membrane_1d.py | 23 ++++++----- 2 files changed, 33 insertions(+), 30 deletions(-) diff --git a/idaes/models_extra/co2_capture_and_utilization/unit_models/membrane_1d.py b/idaes/models_extra/co2_capture_and_utilization/unit_models/membrane_1d.py index bc5ef523a0..e4b8cb55d1 100644 --- a/idaes/models_extra/co2_capture_and_utilization/unit_models/membrane_1d.py +++ b/idaes/models_extra/co2_capture_and_utilization/unit_models/membrane_1d.py @@ -52,7 +52,6 @@ class MembraneFlowPattern(Enum): COCURRENT = 2 - @declare_process_block_class("Membrane1D") class Membrane1DData(UnitModelBlockData): """Standard Membrane 1D Unit Model Class.""" @@ -86,6 +85,7 @@ class Membrane1DData(UnitModelBlockData): see property package for documentation.}""", ), ) + Stream_Config.declare( "has_energy_balance", ConfigValue( @@ -136,7 +136,6 @@ class Membrane1DData(UnitModelBlockData): ), ) - for side_name in ["feed", "sweep"]: CONFIG.declare( side_name + "_side", @@ -144,17 +143,16 @@ class Membrane1DData(UnitModelBlockData): ) def build(self): - """ - This is a one-dimensional model for gas separation in CO₂ capture applications. - The model will be discretized in the flow direction, and it supports two flow patterns: - counter-current flow and co-current flow. The model was customized for gas-phase separation - in CO₂ capture with a single-layer design. If a multi-layer design is needed, multiple units - can be connected for this application. The two sides of the membrane are called the feed side - and sweep side. The sweep stream inlet is optional. The driving force across the membrane is the - partial pressure difference in this gas separation application. Additionally, the energy balance + This is a one-dimensional model for gas separation in CO₂ capture applications. + The model will be discretized in the flow direction, and it supports two flow patterns: + counter-current flow and co-current flow. The model was customized for gas-phase separation + in CO₂ capture with a single-layer design. If a multi-layer design is needed, multiple units + can be connected for this application. The two sides of the membrane are called the feed side + and sweep side. The sweep stream inlet is optional. The driving force across the membrane is the + partial pressure difference in this gas separation application. Additionally, the energy balance assumes that temperature remains constant on each side of the membrane. - + """ super().build() @@ -198,9 +196,7 @@ def _make_geometry(self): self.length = Var(initialize=100, units=units.cm, doc="The membrane length") self.cell_length = Expression(expr=self.length / self.config.finite_elements) - self.cell_area = Var( - initialize=100, units=units.cm**2, doc="The membrane area" - ) + self.cell_area = Var(initialize=100, units=units.cm**2, doc="The membrane area") @self.Constraint() def area_per_cell(self): @@ -227,7 +223,6 @@ def _make_performance(self): doc=" This is a coefficient that will convert the unit of permeability from GPU to SI units for further calculation", ) - """ Selectivity is defined for cases where some permeabilities are unavailable. Only define the selectivity between different components; others should be set to 1. If the permeabilities of all components are defined @@ -242,7 +237,6 @@ def _make_performance(self): units=units.dimensionless, ) - @self.Constraint( self.flowsheet().time, self.mscontactor.elements, @@ -258,10 +252,15 @@ def permeance_calculation(self, t, e, a, b): p_units = feed_side_units.PRESSURE + crossover_component_list = list( + set(self.mscontactor.feed_side.component_list) + & set(self.mscontactor.sweep_side.component_list) + ) + @self.Constraint( self.flowsheet().time, self.mscontactor.elements, - self.mscontactor.feed_side.component_list, + crossover_component_list, doc="permeability calculation", ) def permeability_calculation(self, t, s, m): @@ -273,7 +272,9 @@ def permeability_calculation(self, t, s, m): mb_units = feed_side_units.FLOW_MASS rho = self.mscontactor.feed_side[t, s].dens_mass else: - raise TypeError("Undefined flow basis, please define the flow basis") + raise TypeError( + "This model only supports MaterialFlowBasis equal to molar or mass" + ) return self.mscontactor.material_transfer_term[ t, s, "feed_side", "sweep_side", m @@ -305,7 +306,7 @@ def energy_transfer(self, t, s): self.mscontactor.feed_side[t, s].temperature == self.mscontactor.sweep_side[t, s].temperature ) - + def _get_stream_table_contents(self, time_point=0): return create_stream_table_dataframe( { @@ -316,4 +317,3 @@ def _get_stream_table_contents(self, time_point=0): }, time_point=time_point, ) - diff --git a/idaes/models_extra/co2_capture_and_utilization/unit_models/tests/test_membrane_1d.py b/idaes/models_extra/co2_capture_and_utilization/unit_models/tests/test_membrane_1d.py index c30f2fa2d4..7458b29662 100644 --- a/idaes/models_extra/co2_capture_and_utilization/unit_models/tests/test_membrane_1d.py +++ b/idaes/models_extra/co2_capture_and_utilization/unit_models/tests/test_membrane_1d.py @@ -20,6 +20,7 @@ from pyomo.environ import ( check_optimal_termination, + assert_optimal_termination, ConcreteModel, value, ) @@ -50,6 +51,7 @@ # Get default solver for testing solver = get_solver() + # ----------------------------------------------------------------------------- @pytest.mark.unit def test_config_countercurrent(): @@ -66,15 +68,18 @@ def test_config_countercurrent(): dynamic=False, sweep_flow=True, flow_type=MembraneFlowPattern.COUNTERCURRENT, - feed_side = { "property_package" : m.fs.properties }, - sweep_side = {"property_package" : m.fs.properties }, + feed_side={"property_package": m.fs.properties}, + sweep_side={"property_package": m.fs.properties}, ) # Check unit config arguments + print("=====================================================") + print(len(m.fs.unit.config)) assert len(m.fs.unit.config) == 7 assert not m.fs.unit.config.dynamic assert not m.fs.unit.config.has_holdup + @pytest.mark.unit def test_congif_cocurrent(): m = ConcreteModel() @@ -90,8 +95,8 @@ def test_congif_cocurrent(): dynamic=False, sweep_flow=True, flow_type=MembraneFlowPattern.COCURRENT, - feed_side = { "property_package" : m.fs.properties }, - sweep_side = {"property_package" : m.fs.properties }, + feed_side={"property_package": m.fs.properties}, + sweep_side={"property_package": m.fs.properties}, ) # Check unit config arguments @@ -100,8 +105,7 @@ def test_congif_cocurrent(): assert not m.fs.unit.config.has_holdup - -class TestMembrane(object): +class TestMembrane: @pytest.fixture(scope="class") def membrane(self): m = ConcreteModel() @@ -116,10 +120,9 @@ def membrane(self): dynamic=False, sweep_flow=True, flow_type=MembraneFlowPattern.COUNTERCURRENT, - feed_side = { "property_package" : m.fs.properties }, - sweep_side = {"property_package" : m.fs.properties }, + feed_side={"property_package": m.fs.properties}, + sweep_side={"property_package": m.fs.properties}, ) - m.fs.unit.permeance[:, :, "CO2"].fix(1500) m.fs.unit.permeance[:, :, "H2O"].fix(1500 / 25) @@ -195,7 +198,7 @@ def test_solve(self, membrane): initializer.initialize(membrane.fs.unit) results = solver.solve(membrane) # Check for optimal solution - assert check_optimal_termination(results) + assert_optimal_termination(results) @pytest.mark.solver @pytest.mark.skipif(solver is None, reason="Solver not available") From 278345b406f6a89ff705a7403d5f8d81873a4cd4 Mon Sep 17 00:00:00 2001 From: Maojian Wang Date: Tue, 5 Nov 2024 13:46:32 -0500 Subject: [PATCH 21/33] add linebreak --- .../co2_capture_and_utilization/unit_models/membrane_1d.py | 1 + 1 file changed, 1 insertion(+) diff --git a/idaes/models_extra/co2_capture_and_utilization/unit_models/membrane_1d.py b/idaes/models_extra/co2_capture_and_utilization/unit_models/membrane_1d.py index e4b8cb55d1..8fd8ed122d 100644 --- a/idaes/models_extra/co2_capture_and_utilization/unit_models/membrane_1d.py +++ b/idaes/models_extra/co2_capture_and_utilization/unit_models/membrane_1d.py @@ -54,6 +54,7 @@ class MembraneFlowPattern(Enum): @declare_process_block_class("Membrane1D") class Membrane1DData(UnitModelBlockData): + """Standard Membrane 1D Unit Model Class.""" CONFIG = UnitModelBlockData.CONFIG() From c30f6f9d9ee36bf4b8c8f72ecf0ad761b77b58aa Mon Sep 17 00:00:00 2001 From: Maojian Wang Date: Tue, 5 Nov 2024 13:50:40 -0500 Subject: [PATCH 22/33] corrected copyright info --- .../co2_capture_and_utilization/unit_models/membrane_1d.py | 4 ++-- .../unit_models/tests/test_membrane_1d.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/idaes/models_extra/co2_capture_and_utilization/unit_models/membrane_1d.py b/idaes/models_extra/co2_capture_and_utilization/unit_models/membrane_1d.py index 8fd8ed122d..79ee341610 100644 --- a/idaes/models_extra/co2_capture_and_utilization/unit_models/membrane_1d.py +++ b/idaes/models_extra/co2_capture_and_utilization/unit_models/membrane_1d.py @@ -3,7 +3,7 @@ # Framework (IDAES IP) was produced under the DOE Institute for the # Design of Advanced Energy Systems (IDAES). # -# Copyright (c) 2018-2023 by the software owners: The Regents of the +# 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. @@ -54,7 +54,7 @@ class MembraneFlowPattern(Enum): @declare_process_block_class("Membrane1D") class Membrane1DData(UnitModelBlockData): - + """Standard Membrane 1D Unit Model Class.""" CONFIG = UnitModelBlockData.CONFIG() diff --git a/idaes/models_extra/co2_capture_and_utilization/unit_models/tests/test_membrane_1d.py b/idaes/models_extra/co2_capture_and_utilization/unit_models/tests/test_membrane_1d.py index 7458b29662..4e51a2c3a4 100644 --- a/idaes/models_extra/co2_capture_and_utilization/unit_models/tests/test_membrane_1d.py +++ b/idaes/models_extra/co2_capture_and_utilization/unit_models/tests/test_membrane_1d.py @@ -3,7 +3,7 @@ # Framework (IDAES IP) was produced under the DOE Institute for the # Design of Advanced Energy Systems (IDAES). # -# Copyright (c) 2018-2023 by the software owners: The Regents of the +# 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. From 0b8a57dcb8b3d45e2a4ae2589fffbf55d889dce7 Mon Sep 17 00:00:00 2001 From: Maojian Wang Date: Tue, 5 Nov 2024 14:04:36 -0500 Subject: [PATCH 23/33] reformatted file to pass test --- .../co2_capture_and_utilization/unit_models/membrane_1d.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/idaes/models_extra/co2_capture_and_utilization/unit_models/membrane_1d.py b/idaes/models_extra/co2_capture_and_utilization/unit_models/membrane_1d.py index 79ee341610..3867886444 100644 --- a/idaes/models_extra/co2_capture_and_utilization/unit_models/membrane_1d.py +++ b/idaes/models_extra/co2_capture_and_utilization/unit_models/membrane_1d.py @@ -54,7 +54,6 @@ class MembraneFlowPattern(Enum): @declare_process_block_class("Membrane1D") class Membrane1DData(UnitModelBlockData): - """Standard Membrane 1D Unit Model Class.""" CONFIG = UnitModelBlockData.CONFIG() @@ -192,7 +191,9 @@ def build(self): def _make_geometry(self): - self.area = Var(initialize=100, units=units.cm**2, doc="The membrane area") + self.area = Var( + initialize=100, units=units.cm**2, doc="Area per cell (or finite element)" + ) self.length = Var(initialize=100, units=units.cm, doc="The membrane length") self.cell_length = Expression(expr=self.length / self.config.finite_elements) From d8264330dfbf051b1ce66410cc86c1c1f51aa899 Mon Sep 17 00:00:00 2001 From: Maojian Wang Date: Tue, 5 Nov 2024 14:14:33 -0500 Subject: [PATCH 24/33] fixed copyright info --- .../co2_capture_and_utilization/unit_models/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/idaes/models_extra/co2_capture_and_utilization/unit_models/__init__.py b/idaes/models_extra/co2_capture_and_utilization/unit_models/__init__.py index 03c96ba57b..fae1ee123d 100644 --- a/idaes/models_extra/co2_capture_and_utilization/unit_models/__init__.py +++ b/idaes/models_extra/co2_capture_and_utilization/unit_models/__init__.py @@ -3,7 +3,7 @@ # Framework (IDAES IP) was produced under the DOE Institute for the # Design of Advanced Energy Systems (IDAES). # -# Copyright (c) 2018-2023 by the software owners: The Regents of the +# 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. From 093de8ee169afb2a78f3fc4df90101f6e95d18d8 Mon Sep 17 00:00:00 2001 From: Maojian Wang Date: Tue, 5 Nov 2024 14:20:29 -0500 Subject: [PATCH 25/33] added material conservation test --- .../unit_models/tests/test_membrane_1d.py | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/idaes/models_extra/co2_capture_and_utilization/unit_models/tests/test_membrane_1d.py b/idaes/models_extra/co2_capture_and_utilization/unit_models/tests/test_membrane_1d.py index 4e51a2c3a4..53c8a7eab2 100644 --- a/idaes/models_extra/co2_capture_and_utilization/unit_models/tests/test_membrane_1d.py +++ b/idaes/models_extra/co2_capture_and_utilization/unit_models/tests/test_membrane_1d.py @@ -285,3 +285,23 @@ def test_enthalpy_balance(self, membrane): ) <= 1e-6 ) + + + @pytest.mark.solver + @pytest.mark.skipif(solver is None, reason="Solver not available") + @pytest.mark.component + def test_material_balance(self, membrane): + + assert ( + abs( + value( + ( + membrane.fs.unit.feed_side_inlet.flow_mol[0] + + membrane.fs.unit.sweep_side_inlet.flow_mol[0] + - membrane.fs.unit.feed_side_outlet.flow_mol[0] + - membrane.fs.unit.sweep_side_outlet.flow_mol[0] + ) + ) + ) + <= 1e-3 + ) \ No newline at end of file From 448394fa836a5f8a2fa0e109760a00ad2a21c06e Mon Sep 17 00:00:00 2001 From: Maojian Wang Date: Tue, 5 Nov 2024 14:24:08 -0500 Subject: [PATCH 26/33] reformatted --- .../unit_models/tests/test_membrane_1d.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/idaes/models_extra/co2_capture_and_utilization/unit_models/tests/test_membrane_1d.py b/idaes/models_extra/co2_capture_and_utilization/unit_models/tests/test_membrane_1d.py index 53c8a7eab2..6d4cb40483 100644 --- a/idaes/models_extra/co2_capture_and_utilization/unit_models/tests/test_membrane_1d.py +++ b/idaes/models_extra/co2_capture_and_utilization/unit_models/tests/test_membrane_1d.py @@ -286,7 +286,6 @@ def test_enthalpy_balance(self, membrane): <= 1e-6 ) - @pytest.mark.solver @pytest.mark.skipif(solver is None, reason="Solver not available") @pytest.mark.component @@ -304,4 +303,4 @@ def test_material_balance(self, membrane): ) ) <= 1e-3 - ) \ No newline at end of file + ) From d6a977ce41294eee3de7bb9cf4084f9d6db9d5e8 Mon Sep 17 00:00:00 2001 From: Maojian Wang Date: Tue, 5 Nov 2024 15:03:46 -0500 Subject: [PATCH 27/33] address comments --- .../unit_models/membrane_1d.py | 74 +++++++------------ .../unit_models/tests/test_membrane_1d.py | 4 +- 2 files changed, 30 insertions(+), 48 deletions(-) diff --git a/idaes/models_extra/co2_capture_and_utilization/unit_models/membrane_1d.py b/idaes/models_extra/co2_capture_and_utilization/unit_models/membrane_1d.py index 3867886444..89ffc540aa 100644 --- a/idaes/models_extra/co2_capture_and_utilization/unit_models/membrane_1d.py +++ b/idaes/models_extra/co2_capture_and_utilization/unit_models/membrane_1d.py @@ -15,7 +15,7 @@ One-dimensional membrane class for CO2 gas separation """ -# pylint: disable=unused-import + from enum import Enum from pyomo.common.config import Bool, ConfigDict, ConfigValue, In from pyomo.environ import ( @@ -208,13 +208,17 @@ def _make_performance(self): feed_side_units = ( self.config.feed_side.property_package.get_metadata().derived_units ) + crossover_component_list = list( + set(self.mscontactor.feed_side.component_list) + & set(self.mscontactor.sweep_side.component_list) + ) self.permeance = Var( self.flowsheet().time, self.mscontactor.elements, - self.mscontactor.feed_side.component_list, + crossover_component_list, initialize=1, - doc="Values in Gas Permeance Unit(GPU)", + doc="Values in Gas Permeance Unit (GPU)", units=units.dimensionless, ) @@ -222,43 +226,11 @@ def _make_performance(self): default=10e-8 / 13333.2239, units=units.m / units.s / units.Pa, mutable=True, - doc=" This is a coefficient that will convert the unit of permeability from GPU to SI units for further calculation", - ) - - """ - Selectivity is defined for cases where some permeabilities are unavailable. Only define the selectivity - between different components; others should be set to 1. If the permeabilities of all components are defined - there is no need to define selectivity, as this may overdefine the problem. - """ - self.selectivity = Var( - self.flowsheet().time, - self.mscontactor.elements, - self.mscontactor.feed_side.component_list, - self.mscontactor.feed_side.component_list, - initialize=1, - units=units.dimensionless, - ) - - @self.Constraint( - self.flowsheet().time, - self.mscontactor.elements, - self.mscontactor.feed_side.component_list, - self.mscontactor.feed_side.component_list, - doc="permeance calculation", + # This is a coefficient that will convert the unit of permeability from GPU to SI units for further calculation" ) - def permeance_calculation(self, t, e, a, b): - return ( - self.permeance[t, e, a] * self.selectivity[t, e, a, b] - == self.permeance[t, e, b] - ) p_units = feed_side_units.PRESSURE - crossover_component_list = list( - set(self.mscontactor.feed_side.component_list) - & set(self.mscontactor.sweep_side.component_list) - ) - @self.Constraint( self.flowsheet().time, self.mscontactor.elements, @@ -301,7 +273,7 @@ def permeability_calculation(self, t, s, m): @self.Constraint( self.flowsheet().time, self.mscontactor.elements, - doc="Energy balance", + doc="isothermal constraint", ) def energy_transfer(self, t, s): return ( @@ -310,12 +282,22 @@ def energy_transfer(self, t, s): ) def _get_stream_table_contents(self, time_point=0): - return create_stream_table_dataframe( - { - "Feed Inlet": self.feed_side_inlet, - "Feed Outlet": self.feed_side_outlet, - "Permeate Inlet": self.sweep_side_inlet, - "Permeate Outlet": self.sweep_side_outlet, - }, - time_point=time_point, - ) + if self.config.sweep_flow: + return create_stream_table_dataframe( + { + "Feed Inlet": self.feed_side_inlet, + "Feed Outlet": self.feed_side_outlet, + "Permeate Inlet": self.sweep_side_inlet, + "Permeate Outlet": self.sweep_side_outlet, + }, + time_point=time_point, + ) + else: + return create_stream_table_dataframe( + { + "Feed Inlet": self.feed_side_inlet, + "Feed Outlet": self.feed_side_outlet, + "Permeate Outlet": self.sweep_side_outlet, + }, + time_point=time_point, + ) diff --git a/idaes/models_extra/co2_capture_and_utilization/unit_models/tests/test_membrane_1d.py b/idaes/models_extra/co2_capture_and_utilization/unit_models/tests/test_membrane_1d.py index 6d4cb40483..eec1439b86 100644 --- a/idaes/models_extra/co2_capture_and_utilization/unit_models/tests/test_membrane_1d.py +++ b/idaes/models_extra/co2_capture_and_utilization/unit_models/tests/test_membrane_1d.py @@ -181,8 +181,8 @@ def test_build(self, membrane): assert hasattr(membrane.fs.unit, "permeability_calculation") assert hasattr(membrane.fs.unit, "energy_transfer") - assert number_variables(membrane) == 184 - assert number_total_constraints(membrane) == 116 + assert number_variables(membrane) == 157 + assert number_total_constraints(membrane) == 89 assert number_unused_variables(membrane) == 28 @pytest.mark.component From 75cfb243df6dffb2f633b1efcff2b220938c4a3c Mon Sep 17 00:00:00 2001 From: Maojian Wang Date: Tue, 5 Nov 2024 15:15:14 -0500 Subject: [PATCH 28/33] added basic documentation --- .../model_libraries/models_extra/index.rst | 2 ++ .../models_extra/membrane_model/1d_membrane.rst | 14 ++++++++++++++ 2 files changed, 16 insertions(+) create mode 100644 docs/reference_guides/model_libraries/models_extra/membrane_model/1d_membrane.rst diff --git a/docs/reference_guides/model_libraries/models_extra/index.rst b/docs/reference_guides/model_libraries/models_extra/index.rst index 069761e4a7..2f932d0981 100644 --- a/docs/reference_guides/model_libraries/models_extra/index.rst +++ b/docs/reference_guides/model_libraries/models_extra/index.rst @@ -6,3 +6,5 @@ Additional IDAES Model Libraries phe temperature_swing_adsorption/fixed_bed_tsa0d + membrane_model/1d_membrane + diff --git a/docs/reference_guides/model_libraries/models_extra/membrane_model/1d_membrane.rst b/docs/reference_guides/model_libraries/models_extra/membrane_model/1d_membrane.rst new file mode 100644 index 0000000000..675396c6d3 --- /dev/null +++ b/docs/reference_guides/model_libraries/models_extra/membrane_model/1d_membrane.rst @@ -0,0 +1,14 @@ +One-dimensional membrane class for CO2 gas separation +============================================= + +This is a one-dimensional model for gas separation in CO₂ capture applications. +The model will be discretized in the flow direction, and it supports two flow patterns: +counter-current flow and co-current flow. The model was customized for gas-phase separation +in CO₂ capture with a single-layer design. If a multi-layer design is needed, multiple units +can be connected for this application. The two sides of the membrane are called the feed side +and sweep side. The sweep stream inlet is optional. The driving force across the membrane is the +partial pressure difference in this gas separation application. Additionally, the energy balance +assumes that temperature remains constant on each side of the membrane. + + + From c6d3677af0e8c2b2f4709586eef076f4f8a8803b Mon Sep 17 00:00:00 2001 From: Maojian Wang Date: Tue, 5 Nov 2024 15:28:27 -0500 Subject: [PATCH 29/33] fix pylint test --- .../co2_capture_and_utilization/unit_models/membrane_1d.py | 1 - 1 file changed, 1 deletion(-) diff --git a/idaes/models_extra/co2_capture_and_utilization/unit_models/membrane_1d.py b/idaes/models_extra/co2_capture_and_utilization/unit_models/membrane_1d.py index 89ffc540aa..77a5adabde 100644 --- a/idaes/models_extra/co2_capture_and_utilization/unit_models/membrane_1d.py +++ b/idaes/models_extra/co2_capture_and_utilization/unit_models/membrane_1d.py @@ -19,7 +19,6 @@ from enum import Enum from pyomo.common.config import Bool, ConfigDict, ConfigValue, In from pyomo.environ import ( - Constraint, Param, Var, units, From 83529cfb30a7c94ae1d1fde943496ec2648119df Mon Sep 17 00:00:00 2001 From: Maojian Wang Date: Tue, 5 Nov 2024 15:29:53 -0500 Subject: [PATCH 30/33] fix doc strings --- .../model_libraries/models_extra/membrane_model/1d_membrane.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/reference_guides/model_libraries/models_extra/membrane_model/1d_membrane.rst b/docs/reference_guides/model_libraries/models_extra/membrane_model/1d_membrane.rst index 675396c6d3..b993d282ab 100644 --- a/docs/reference_guides/model_libraries/models_extra/membrane_model/1d_membrane.rst +++ b/docs/reference_guides/model_libraries/models_extra/membrane_model/1d_membrane.rst @@ -1,5 +1,5 @@ One-dimensional membrane class for CO2 gas separation -============================================= +================================================================ This is a one-dimensional model for gas separation in CO₂ capture applications. The model will be discretized in the flow direction, and it supports two flow patterns: From 80befeedc977814b6417a334ee46653aaa721241 Mon Sep 17 00:00:00 2001 From: Maojian Wang Date: Tue, 5 Nov 2024 15:39:45 -0500 Subject: [PATCH 31/33] fix the doc string --- .../co2_capture_and_utilization/unit_models/membrane_1d.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/idaes/models_extra/co2_capture_and_utilization/unit_models/membrane_1d.py b/idaes/models_extra/co2_capture_and_utilization/unit_models/membrane_1d.py index 77a5adabde..0586f38f1d 100644 --- a/idaes/models_extra/co2_capture_and_utilization/unit_models/membrane_1d.py +++ b/idaes/models_extra/co2_capture_and_utilization/unit_models/membrane_1d.py @@ -129,9 +129,8 @@ class Membrane1DData(UnitModelBlockData): domain=In(MembraneFlowPattern), description="Flow configuration of membrane", doc="""Flow configuration of membrane - - MembraneFlowPattern.COCURRENT: feed and sweep flows from 0 to 1 - - MembraneFlowPattern.COUNTERCURRENT: feed side flows from 0 to 1 - sweep side flows from 1 to 0 (default)""", + MembraneFlowPattern.COCURRENT - feed and sweep flows from 0 to 1 + MembraneFlowPattern.COUNTERCURRENT - feed side flows from 0 to 1 and sweep side flows from 1 to 0 (default)""", ), ) From 6f3f8a67680f3b6a4a5c68db92419a9da41fd6a8 Mon Sep 17 00:00:00 2001 From: Maojian Wang Date: Tue, 5 Nov 2024 15:52:17 -0500 Subject: [PATCH 32/33] added what the inputs/degrees of freedom --- .../membrane_model/1d_membrane.rst | 25 +++++++++++++++++++ .../unit_models/membrane_1d.py | 2 +- .../unit_models/tests/test_membrane_1d.py | 2 -- 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/docs/reference_guides/model_libraries/models_extra/membrane_model/1d_membrane.rst b/docs/reference_guides/model_libraries/models_extra/membrane_model/1d_membrane.rst index b993d282ab..678ca9858d 100644 --- a/docs/reference_guides/model_libraries/models_extra/membrane_model/1d_membrane.rst +++ b/docs/reference_guides/model_libraries/models_extra/membrane_model/1d_membrane.rst @@ -10,5 +10,30 @@ and sweep side. The sweep stream inlet is optional. The driving force across the partial pressure difference in this gas separation application. Additionally, the energy balance assumes that temperature remains constant on each side of the membrane. +Variables +--------- + +Model Inputs - symbol: + +* Membrane length - :math:`L` +* Membrane Area - :math:`A` +* Permeance - :math:`per` +* Feed flowrate - :math:`F_fr` +* Feed compositions - :math:`x` +* Feed pressure - :math:`P` +* Feed temperature - :math:`T` + + +Model Outputs : + +* Permeate compositions +* Permeate flowrate + +Degrees of Freedom +------------------ + +The DOF should be 0 for square problem simulations. + + diff --git a/idaes/models_extra/co2_capture_and_utilization/unit_models/membrane_1d.py b/idaes/models_extra/co2_capture_and_utilization/unit_models/membrane_1d.py index 0586f38f1d..9feb1f1e8e 100644 --- a/idaes/models_extra/co2_capture_and_utilization/unit_models/membrane_1d.py +++ b/idaes/models_extra/co2_capture_and_utilization/unit_models/membrane_1d.py @@ -273,7 +273,7 @@ def permeability_calculation(self, t, s, m): self.mscontactor.elements, doc="isothermal constraint", ) - def energy_transfer(self, t, s): + def isothermal_constraint(self, t, s): return ( self.mscontactor.feed_side[t, s].temperature == self.mscontactor.sweep_side[t, s].temperature diff --git a/idaes/models_extra/co2_capture_and_utilization/unit_models/tests/test_membrane_1d.py b/idaes/models_extra/co2_capture_and_utilization/unit_models/tests/test_membrane_1d.py index eec1439b86..ed07a55c5c 100644 --- a/idaes/models_extra/co2_capture_and_utilization/unit_models/tests/test_membrane_1d.py +++ b/idaes/models_extra/co2_capture_and_utilization/unit_models/tests/test_membrane_1d.py @@ -73,8 +73,6 @@ def test_config_countercurrent(): ) # Check unit config arguments - print("=====================================================") - print(len(m.fs.unit.config)) assert len(m.fs.unit.config) == 7 assert not m.fs.unit.config.dynamic assert not m.fs.unit.config.has_holdup From 93038d4f664c98298f7c1b9e142c9596fbe6e253 Mon Sep 17 00:00:00 2001 From: Maojian Wang Date: Tue, 5 Nov 2024 16:24:36 -0500 Subject: [PATCH 33/33] fix pytest --- .../unit_models/tests/test_membrane_1d.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/idaes/models_extra/co2_capture_and_utilization/unit_models/tests/test_membrane_1d.py b/idaes/models_extra/co2_capture_and_utilization/unit_models/tests/test_membrane_1d.py index ed07a55c5c..8c129ebffe 100644 --- a/idaes/models_extra/co2_capture_and_utilization/unit_models/tests/test_membrane_1d.py +++ b/idaes/models_extra/co2_capture_and_utilization/unit_models/tests/test_membrane_1d.py @@ -177,7 +177,7 @@ def test_build(self, membrane): assert hasattr(membrane.fs.unit, "mscontactor") assert hasattr(membrane.fs.unit, "permeability_calculation") - assert hasattr(membrane.fs.unit, "energy_transfer") + assert hasattr(membrane.fs.unit, "isothermal_constraint") assert number_variables(membrane) == 157 assert number_total_constraints(membrane) == 89