diff --git a/docs/technical_reference/unit_models/index.rst b/docs/technical_reference/unit_models/index.rst index b386edefb4..dd8508b40a 100644 --- a/docs/technical_reference/unit_models/index.rst +++ b/docs/technical_reference/unit_models/index.rst @@ -23,6 +23,7 @@ Unit Models generic_separator ion_exchange_0D membrane_distillation_0D + membrane_distillation_1D mvc nanofiltration_ZO nanofiltration_dspmde_0D diff --git a/docs/technical_reference/unit_models/membrane_distillation_0D.rst b/docs/technical_reference/unit_models/membrane_distillation_0D.rst index 5d8833fb29..94d8c805b6 100644 --- a/docs/technical_reference/unit_models/membrane_distillation_0D.rst +++ b/docs/technical_reference/unit_models/membrane_distillation_0D.rst @@ -1,48 +1,60 @@ +========================================= Membrane Distillation (0D) ========================================= -This Membrane Distillation (MD) unit model - * is developed for direct contact configuration (other configurations are under development) - * is developed for couterflow mode (parallel flow is under development) - * is 0-dimensional - * supports steady-state only - * Assumes heat loss in equipment is negligible - * Assumes permeate exits the membrane pores with zero salinity - * Assumes no concentration polarization for the cold channel - * Assumes complete vapor condensation on the cold channel - +This Membrane Distillation (MD) unit model: + * supports the following configurations: -.. index:: - pair: watertap.unit_models.MD;MD + - **DCMD** (Direct Contact Membrane Distillation) + - **VMD** (Vacuum Membrane Distillation) + - **GMD** (Permeate Gap/Conductive Gap Membrane Distillation) + + * is 0-dimensional + * supports steady-state only + * assumes heat loss in equipment is negligible + * assumes permeate exits the membrane pores with zero salinity + * assumes no concentration polarization for the cold channel + * assumes complete vapor condensation for the cold channel (in DCMD and GMD) + * accounts for vapor expansion in VMD + * assumes linear temperature change across gap channel (in GMD) + * assumes no pressure change and temperature polarization in VMD vacuum channel -.. currentmodule:: watertap.unit_models.MD Degrees of Freedom ------------------ -In addition to the hot channel and cold channel inlet state variables (i.e, temperature, pressure, and component flowrates), the MD model has -at least 4 degrees of freedom that should be fixed for the unit to be fully specified. Typically, the following variables are fixed for the MD model: - - * Membrane permeability coefficient - * Membrane thickness - * Membrane thermal conductivity - * Recovery *or* membrane area - +In addition to the hot channel and cold channel inlet state variables (i.e, temperature, pressure, and component flowrates) for the **DCMD** and **GMD** configurations, the MD model has at least **4 degrees of freedom** for all configurations that should be fixed for the unit to be fully specified. Typically, the following variables are fixed: + +- Membrane permeability coefficient +- Membrane thickness +- Membrane thermal conductivity +- Recovery *or* membrane area + +**Additional degress of freedom**: + +- **VMD** introduces vacuum pressure at the cold side. +- **GMD** introduces gap thermal conductivity and gap thickness. + Configuring the MD unit to calculate temperature polarization, concentration polarization, mass transfer coefficient, and pressure drop would result in five additional degrees of freedom. In this case, in addition to the previously fixed variables, we typically fix the following variables to fully specify the unit: * Hot channel spacer porosity * Hot channel height - * Cold channel spacer porosity - * Cold channel height + * Cold channel spacer porosity (in DCMD and GMD) + * Cold channel height (in DCMD and GMD) * Membrane length *or* membrane width Model Structure ------------------- -This MD model consists of a separate MDchannel0Dblock for the hot channel and the cold channel of the module. +--------------- +The MD model consists of a separate `MDchannel0Dblock` for each channel depending on the configuration: -* Each MDchannel0Dblock includes 6 stateblocks: 2 stateBlocks for the bulk properites at the inlet and outlet (properties_in and properties_out), which are used for mass, energy, and momentum balances; 2 StateBlocks for the conditions at the membrane interface, and 2 stateblocks for the vapor phase at the membrane interface. Property packages must be declared for each MD channel block for the liquid (bulk and interface) and vapor Phases. +- **DCMD**: Includes **hot channel** and **cold channel**. +- **VMD**: Includes **hot channel** and **vacuum (cold) channel**. +- **GMD**: Includes **hot channel**, **gap channel**, and **cold channel**. +- **hot and cold channels in all configurations** includes bulk properties at the inlet and outlet (`properties_in` and `properties_out`) which are used for mass, energy, and momentum balances +- **hot channel in all configurations, cold channel in DCMD and GMD, and gap channel in GMD** includes 2 StateBlocks for the conditions at the membrane interface and gap interface +- **hot channel in all configurations, cold channel in DCMD, and gap channel in GMD** includes Vapor properties at the membrane interface (for **DCMD** and **VMD** configurations). Sets ---- @@ -56,25 +68,40 @@ Sets \*Solute depends on the imported property model. -.. _MD_variables: +.. _0MD_variables: -Variables ----------- +Variables +--------- .. csv-table:: :header: "Description", "Symbol", "Variable Name", "Index", "Units" "Membrane permeability coefficient", ":math:`B_0`", "permeability_coef", "[t]", ":math:`\text{kg/m/Pa/s}`" "Membrane thickness", ":math:`\sigma`", "membrane_thickness", "None", ":math:`\text{m}`" "Membrane thermal conductivity", ":math:`k_m`", "membrane_tc", "None", ":math:`\text{W/K/m}`" - "Mass density of solvent", ":math:`\rho_{solvent}`", "dens_solvent", "[p]", ":math:`\text{kg/}\text{m}^3`" "Mass flux across membrane", ":math:`J`", "flux_mass", "[t, x]", ":math:`\text{kg/s}\text{/m}^2`" "Conduction heat flux across membrane", ":math:`q_{cond}`", "flux_conduction_heat", "[t, x]", ":math:`\text{W}\text{/m}^2`" "Evaporation heat flux from hot channel", ":math:`q_{evap}`", "flux_enth_hot", "[t, x]", ":math:`\text{W}\text{/m}^2`" "Condensation heat flux to cold channel", ":math:`q_{conden}`", "flux_enth_cold", "[t, x]", ":math:`\text{W}\text{/m}^2`" "Membrane area", ":math:`A_m`", "area", "None", ":math:`\text{m}^2`" "Recovery rate", ":math:`R`", "recovery_mass", "[t]", ":math:`\text{dimensionless}`" - + +**Additional Variables for VMD**: + +.. csv-table:: + :header: "Description", "Symbol", "Variable Name", "Index", "Units" + + "Vapor expansion heat flux", ":math:`q_{exp}`", "flux_expansion_heat", "[t, x]", ":math:`\text{W}\text{/m}^2`" + +**Additional Variables for GMD**: + +.. csv-table:: + :header: "Description", "Symbol", "Variable Name", "Index", "Units" + + "Gap thermal conductivity", ":math:`k_{gap}`", "gap_thermal_conductivity", "None", ":math:`\text{W/K/m}`" + "Gap thickness", ":math:`\sigma_{gap}`", "gap_thickness", "None", ":math:`\text{m}`" + "gap conduction heat flux", ":math:`q_{gap}`", "flux_conduction_heat_gap", "[t, x]", ":math:`\text{W}\text{/m}^2`" + The following variables are only built when specific configuration key-value pairs are selected. if ``has_pressure_change`` is set to ``True``: @@ -174,32 +201,60 @@ if ``pressure_change_type`` is set to ``PressureChangeType.calculated``: "Cold channel velocity", ":math:`v_c`", "cold_ch.velocity", "[t, x]", ":math:`\text{m/s}`" "Pressure drop per unit length of cold channel at inlet/outlet", ":math:`(ΔP/Δx)_c`", "cold_ch.dP_dx", "[t, x]", ":math:`\text{Pa/m}`" -.. _MD_equations: +.. _0MD_equations: Equations ------------ +--------- -.. csv-table:: +if ``MD_configuration_type`` is set to ``MDconfigurationType.DCMD``: + +.. csv-table:: :header: "Description", "Equation" "Vapor flux across membrane", ":math:`J(t, x) = \frac{B_0(t)}{\sigma} \times \left( P_{\text{sat, hot}}(t, x) - P_{\text{sat, cold}}(t, x) \right)`" - "Average flux across membrane", ":math:`J_{avg, j} = \frac{1}{2}\sum_{x} J_{x, j}`" - "hot channel membrane-interface solute concentration", ":math:`C_{\text{interface, j, h}}(t, x) = C_{\text{bulk, j, h}}(t, x) \times \exp\left( \frac{J(t, x)}{\rho_{\text{solvent}} \times k_h(t, x, j)} \right)`" - "Evaporation heat flux from hot channel", ":math:`q_{\text{evap}}(t, x) = J(t, x) \times \widehat{H}_{\text{h}}(t, x, Vap)`" "Condensation heat flux to cold channel", ":math:`q_{\text{conden}}(t, x) = J(t, x) \times \widehat{H}_{\text{c}}(t, x, Vap)`" - "Average evaporation flux from hot channel", ":math:`\overline{q}_{\text{evap}}(t) = \frac{1}{2} \sum_{x} q_{\text{evap}}(t, x)`" "Average condensation flux to cold channel", ":math:`\overline{q}_{\text{conden}}(t) = \frac{1}{2} \sum_{x} q_{\text{conden}}(t, x)`" - "Hot channel convective heat transfer", ":math:`h_{\text{conv}, h}(t, x) \left( T_{\text{bulk}, h}(t, x) - T_{\text{interface}, h}(t, x) \right) = q_{\text{cond}}(t, x) + q_{\text{evap}}(t, x) - J(t, x) \cdot \widehat{H}_{\text{bulk, h}}(t, x, Liq)`" "Cold channel convective heat transfer", ":math:`h_{\text{conv}, c}(t, x) \left( T_{\text{interface}, c}(t, x) - T_{\text{bulk}, c}(t, x) \right) = q_{\text{cond}}(t, x) + q_{\text{conden}}(t, x) - J(t, x) \cdot \widehat{H}_{\text{bulk}, c}(t, x, Liq)`" + "Hot channel convective heat transfer", ":math:`h_{\text{conv}, h}(t, x) \left( T_{\text{bulk}, h}(t, x) - T_{\text{interface}, h}(t, x) \right) = q_{\text{cond}}(t, x) + q_{\text{evap}}(t, x) - J(t, x) \cdot \widehat{H}_{\text{bulk, h}}(t, x, Liq)`" "Conduction heat flux across membrane", ":math:`q_{\text{cond}}(t, x) = \frac{k_{\text{m}}}{\sigma} \left( T_{\text{interface}, h}(t, x) - T_{\text{interface}, c}(t, x) \right)`" - "Average conduction heat across membrane", ":math:`q_{\text{cond, avg}}(t) = \frac{1}{N} \sum_{x} q_{\text{cond}}(t, x)`" - "Total permeate production", ":math:`M_p = A \cdot J_{\text{avg}}`" - "Total conduction heat transfer", ":math:`q_{\text{cond,total}} = - A \cdot q_{\text{cond,avg}}`" - "Hot channel total evapration heat", ":math:`q_{\text{evap,total}} = - A \cdot \overline{\widehat{H}_h}`" - "Cold channel total condensation heat", ":math:`q_{\text{conden,total}} = A \cdot \overline{\widehat{H}_c}`" + "Mass transfer balance between hot and cold channel", ":math:`\dot{m}_{\text{cold}}(t, x, p, j) = -\dot{m}_{\text{hot}}(t, x, p, j)`" + "Conductive heat transfer to cold channel", ":math:`q_{\text{cond, hot}}(t, x) = -q_{\text{cond, cold}}(t, x)`" + +if ``MD_configuration_type`` is set to ``MDconfigurationType.VMD``: + +.. csv-table:: + :header: "Description", "Equation" + + "Vapor flux across membrane", ":math:`J(t, x) = \frac{B_0(t)}{\sigma} \times \left( P_{\text{sat, hot}}(t, x) - P_{\text{vaccuum, cold}}(t, x) \right)`" + "Hot channel convective heat transfer", ":math:`h_{\text{conv}, h}(t, x) \left( T_{\text{bulk}, h}(t, x) - T_{\text{interface}, h}(t, x) \right) = q_{\text{exp}}(t, x) + q_{\text{evap}}(t, x) - J(t, x) \cdot \widehat{H}_{\text{bulk, h}}(t, x, Liq)`" + "Vapor expansion heat flux", ":math:`q_{\text{exp}}(t, x) = \frac{R \cdot T}{M} \ln\left( \frac{P_f}{P_p} \right) \cdot J(t, x)`" + "Mass transfer from vapor phase to vacuum channel", ":math:`\dot{m}_{\text{cold}}(t, x, Vap, j) = -\dot{m}_{\text{hot}}(t, x, Liq, j)`" + "Conductive heat transfer to cold channel", ":math:`q_{\text{cond, hot}}(t, x) = -q_{\text{exp}}(t, x)`" + "Cold channel inlet temperature", ":math:`T_{\text{cold, in}}(t) = T_{\text{hot, in}}(t)`" + +if ``MD_configuration_type`` is set to ``MDconfigurationType.GMD``: + +.. csv-table:: + :header: "Description", "Equation" + + "Vapor flux across membrane", ":math:`J(t, x) = \frac{B_0(t)}{\sigma} \times \left( P_{\text{sat, hot}}(t, x) - P_{\text{sat, gap}}(t, x) \right)`" + "Cold channel convective heat transfer", ":math:`h_{\text{conv}, c}(t, x) \left( T_{\text{interface}, c}(t, x) - T_{\text{bulk}, c}(t, x) \right) = q_{\text{gap}}(t, x)`" + "Hot channel convective heat transfer", ":math:`h_{\text{conv}, h}(t, x) \left( T_{\text{bulk}, h}(t, x) - T_{\text{interface}, h}(t, x) \right) = q_{\text{cond}}(t, x) + q_{\text{evap}}(t, x) - J(t, x) \cdot \widehat{H}_{\text{bulk, h}}(t, x, Liq)`" + "Conduction heat flux across membrane", ":math:`q_{\text{cond}}(t, x) = \frac{k_{\text{m}}}{\sigma} \left( T_{\text{interface}, h}(t, x) - T_{\text{interface}, gap}(t, x) \right)`" + "Conduction heat flux across gap", ":math:`q_{\text{gap}}(t, x) = \frac{k_{\text{m}}}{\sigma} \left( T_{\text{interface}, gap}(t, x) - T_{\text{interface}, c}(t, x) \right)`" + "Mass transfer balance between hot and gap channel", ":math:`\dot{m}_{\text{gap}}(t, x, Liq, H2O) = -\dot{m}_{\text{hot}}(t, x, Liq, H2O)`" + "Conductive heat transfer between channels", ":math:`q_{\text{cold}}(t, x) = -q_{\text{hot}}(t, x) - ΔH_{\text{hot}}(t, x) - ΔH_{\text{gap}}(t, x)`" + +Common in all configurations: + +.. csv-table:: + :header: "Description", "Equation" + + "Average flux across membrane", ":math:`J_{avg, j} = \frac{1}{2}\sum_{x} J_{x, j}`" + "Hot channel membrane-interface solute concentration", ":math:`C_{\text{interface, j, h}}(t, x) = C_{\text{bulk, j, h}}(t, x) \times \exp\left( \frac{J(t, x)}{\rho_{\text{solvent}} \times k_h(t, x, j)} \right)`" + "Evaporation heat flux from hot channel", ":math:`q_{\text{evap}}(t, x) = J(t, x) \times \widehat{H}_{\text{h}}(t, x, Vap)`" + "Average evaporation flux from hot channel", ":math:`\overline{q}_{\text{evap}}(t) = \frac{1}{2} \sum_{x} q_{\text{evap}}(t, x)`" "Convective heat transfer coefficient", ":math:`h_{\text{conv},(t, x)} = \frac{\kappa_{(t, x)} \cdot \text{Nu}_{(t, x)}}{d_h}`" - "Nusselt number", ":math:`Nu[t, x] == 0.162 * (Re[t, x] ** 0.656) * (Pr[t, x] ** 0.333)`" - "Prandtl number", ":math:`Pr(t, x) = \frac{\mu(t, x) \cdot C_p(t, x)}{\kappa}`" "Effectiveness", ":math:`\epsilon(t) = \frac{T_{\text{cold, first}}(t) - T_{\text{c, last}}(t)}{T_{\text{h, first}}(t) - T_{\text{c, last}}(t)}`" "Thermal efficiency", ":math:`\eta(t) = \frac{q_{\text{evap,total}}(t)}{q_{\text{evap,total}}(t) + q_{\text{cond,total}}(t)}`" "Concentration polarization modulus",":math:`CP_{mod} = C_{interface}/C_{bulk}`" @@ -224,4 +279,4 @@ Class Documentation * :mod:`watertap.unit_models.MD.membrane_distillation_0D` * :mod:`watertap.unit_models.MD.membrane_distillation_base` * :mod:`watertap.unit_models.MD.MD_channel_0D` -* :mod:`watertap.unit_models.MD.MD_channel_base` +* :mod:`watertap.unit_models.MD.MD_channel_base` \ No newline at end of file diff --git a/docs/technical_reference/unit_models/membrane_distillation_1D.rst b/docs/technical_reference/unit_models/membrane_distillation_1D.rst new file mode 100644 index 0000000000..5071fd6ac3 --- /dev/null +++ b/docs/technical_reference/unit_models/membrane_distillation_1D.rst @@ -0,0 +1,88 @@ +Membrane Distillation (1D) +========================================= + +This Membrane Distillation (MD) unit model: + * supports the following configurations: + + - **DCMD** (Direct Contact Membrane Distillation) + - **VMD** (Vacuum Membrane Distillation) + - **GMD** (Permeate Gap/Conductive Gap Membrane Distillation) + + * is 1-dimensional + * supports steady-state only + * assumes heat loss in equipment is negligible + * assumes permeate exits the membrane pores with zero salinity + * assumes no concentration polarization for the cold channel + * assumes complete vapor condensation for the cold channel (in DCMD and GMD) + * accounts for vapor expansion in VMD + * assumes linear temperature change across gap channel (in GMD) + * assumes no pressure change and temperature polarization in VMD vaccuum channel + + +Degrees of Freedom +------------------ +In addition to the hot channel and cold channel inlet state variables (i.e, temperature, pressure, and component flowrates) for the **DCMD** and **GMD** configurations, the MD model has at least **4 degrees of freedom** for all configurations that should be fixed for the unit to be fully specified. Typically, the following variables are fixed: + +- Membrane permeability coefficient +- Membrane thickness +- Membrane thermal conductivity +- Recovery *or* membrane area + +**Additional degress of freedom**: + +- **VMD** introduces vacuum pressure at the cold side. +- **GMD** introduces gap thermal conductivity and gap thickness. + +Configuring the MD unit to calculate temperature polarization, concentration polarization, mass transfer +coefficient, and pressure drop would result in five additional degrees of freedom. In this case, in addition to the +previously fixed variables, we typically fix the following variables to fully specify the unit: + + * Hot channel spacer porosity + * Hot channel height + * Cold channel spacer porosity (in DCMD and GMD) + * Cold channel height (in DCMD and GMD) + * Membrane length *or* membrane width + +Model Structure +--------------- +The MD model consists of a separate `MDchannel1Dblock` for each channel depending on the configuration: + +- **DCMD**: Includes **hot channel** and **cold channel**. +- **VMD**: Includes **hot channel** and **vacuum (cold) channel**. +- **GMD**: Includes **hot channel**, **gap channel**, and **cold channel**. + +- **hot and cold channels in all configurations** includes bulk properties StateBlocks indexed by time and space which are used for mass, energy, and momentum balances +- **hot channel in all configurations, cold channel in DCMD and GMD, and gap channel in GMD** includes StateBlocks indexed by time and space for the conditions at the membrane interface and gap interface +- **hot channel in all configurations, cold channel in DCMD, and gap channel in GMD** includes StateBlocks indexed by time and space for Vapor properties at the membrane interface (for **DCMD** and **VMD** configurations). + +Sets +---- +.. csv-table:: + :header: "Description", "Symbol", "Indices" + + "Time", ":math:`t`", "[0]" + "Space", ":math:`x`", "None" + "Phases", ":math:`p`", "['Liq', 'Vap']" + "Components", ":math:`j`", "['H2O', solute]*" + +\*Solute depends on the imported property model. + +Variables +---------- + +Refer to the :any:`0MD_variables` section in the 0D MD model. + +.. _1MD_equations: + +Equations +----------- + +Refer to the :any:`0MD_equations` section in the 0D MD model. + +Class Documentation +------------------- + +* :mod:`watertap.unit_models.MD.membrane_distillation_1D` +* :mod:`watertap.unit_models.MD.membrane_distillation_base` +* :mod:`watertap.unit_models.MD.MD_channel_1D` +* :mod:`watertap.unit_models.MD.MD_channel_base` diff --git a/watertap/unit_models/MD/MD_channel_0D.py b/watertap/unit_models/MD/MD_channel_0D.py index 854a17fea1..42363cd546 100644 --- a/watertap/unit_models/MD/MD_channel_0D.py +++ b/watertap/unit_models/MD/MD_channel_0D.py @@ -53,8 +53,6 @@ def add_geometry( def add_state_blocks( self, has_phase_equilibrium=None, - property_package_vapor=None, - property_package_args_vapor=None, ): super().add_state_blocks(has_phase_equilibrium=has_phase_equilibrium) @@ -91,13 +89,6 @@ def add_state_blocks( add_object_reference(self, "properties", properties_dict) - self._add_interface_stateblock(has_phase_equilibrium) - self._add_vapor_stateblock( - property_package_vapor, - property_package_args_vapor, - has_phase_equilibrium=False, - ) - def _add_pressure_change(self, pressure_change_type=PressureChangeType.calculated): if pressure_change_type == PressureChangeType.fixed_per_stage: return @@ -231,25 +222,27 @@ def initialize( raise ConfigurationError( "Either hot_ch or cold_ch must be set in the configuration." ) - state_args_interface = self._get_state_args_interface( - state_args_properties_in, state_args_properties_out - ) - self.properties_interface.initialize( - outlvl=outlvl, - optarg=optarg, - solver=solver, - state_args=state_args_interface, - ) + if hasattr(self, "properties_interface"): + state_args_interface = self._get_state_args_interface( + state_args_properties_in, state_args_properties_out + ) + self.properties_interface.initialize( + outlvl=outlvl, + optarg=optarg, + solver=solver, + state_args=state_args_interface, + ) - state_args_vapor = self._get_state_args_vapor( - state_args_properties_in, state_args_properties_out - ) - self.properties_vapor.initialize( - outlvl=outlvl, - optarg=optarg, - solver=solver, - state_args=state_args_vapor, - ) + if hasattr(self, "properties_vapor"): + state_args_vapor = self._get_state_args_vapor( + state_args_properties_in, state_args_properties_out + ) + self.properties_vapor.initialize( + outlvl=outlvl, + optarg=optarg, + solver=solver, + state_args=state_args_vapor, + ) init_log.info("Initialization Complete") diff --git a/watertap/unit_models/MD/MD_channel_1D.py b/watertap/unit_models/MD/MD_channel_1D.py index 3ca3f99968..b387d6ca02 100644 --- a/watertap/unit_models/MD/MD_channel_1D.py +++ b/watertap/unit_models/MD/MD_channel_1D.py @@ -85,30 +85,6 @@ def add_geometry( ) add_object_reference(self, "width", width_var) - def add_state_blocks( - self, - has_phase_equilibrium=None, - property_package_vapor=None, - property_package_args_vapor=None, - ): - """ - This method constructs the state blocks for the - control volume. - - Args: - has_phase_equilibrium: indicates whether equilibrium calculations - will be required in state blocks - Returns: - None - """ - super().add_state_blocks(has_phase_equilibrium=has_phase_equilibrium) - self._add_interface_stateblock(has_phase_equilibrium) - self._add_vapor_stateblock( - property_package_vapor, - property_package_args_vapor, - has_phase_equilibrium=False, - ) - def _add_pressure_change(self, pressure_change_type=PressureChangeType.calculated): add_object_reference(self, "dP_dx", self.deltaP) @@ -207,25 +183,35 @@ def initialize( "Either hot_ch or cold_ch must be set in the configuration." ) - state_args_interface = self._get_state_args_interface( - state_args_properties_in, state_args_properties_out - ) - self.properties_interface.initialize( - outlvl=outlvl, - optarg=optarg, - solver=solver, - state_args=state_args_interface, - ) + if hasattr(self, "properties_interface"): + state_args_interface = self._get_state_args_interface( + state_args_properties_in, state_args_properties_out + ) - state_args_vapor = self._get_state_args_vapor( - state_args_properties_in, state_args_properties_out - ) - self.properties_vapor.initialize( - outlvl=outlvl, - optarg=optarg, - solver=solver, - state_args=state_args_vapor, - ) + state_args_interface = self._get_state_args_interface( + state_args_properties_in, state_args_properties_out + ) + self.properties_interface.initialize( + outlvl=outlvl, + optarg=optarg, + solver=solver, + state_args=state_args_interface, + ) + + if hasattr(self, "properties_vapor"): + state_args_vapor = self._get_state_args_vapor( + state_args_properties_in, state_args_properties_out + ) + + state_args_vapor = self._get_state_args_vapor( + state_args_properties_in, state_args_properties_out + ) + self.properties_vapor.initialize( + outlvl=outlvl, + optarg=optarg, + solver=solver, + state_args=state_args_vapor, + ) init_log.info("Initialization Complete") diff --git a/watertap/unit_models/MD/MD_channel_base.py b/watertap/unit_models/MD/MD_channel_base.py index 3d0de1b95f..85e13b2b21 100644 --- a/watertap/unit_models/MD/MD_channel_base.py +++ b/watertap/unit_models/MD/MD_channel_base.py @@ -121,9 +121,14 @@ def add_total_pressure_balances( doc="Pressure at interface", ) def eq_equal_pressure_interface(b, t, x): - if b._skip_element(x): + if hasattr(self, "properties_interface"): + if b._skip_element(x): + return Constraint.Skip + return ( + b.properties_interface[t, x].pressure == b.properties[t, x].pressure + ) + else: return Constraint.Skip - return b.properties_interface[t, x].pressure == b.properties[t, x].pressure if has_pressure_change: self._add_pressure_change(pressure_change_type=pressure_change_type) @@ -146,12 +151,13 @@ def add_temperature_polarization( doc="No temperature polarization", ) def eq_no_temp_pol(b, t, x): - if b._skip_element(x): - return Constraint.Skip - return ( - b.properties_interface[t, x].temperature - == b.properties[t, x].temperature - ) + if hasattr(self, "properties_interface"): + if b._skip_element(x): + return Constraint.Skip + return ( + b.properties_interface[t, x].temperature + == b.properties[t, x].temperature + ) return self.eq_no_temp_pol diff --git a/watertap/unit_models/MD/membrane_distillation_0D.py b/watertap/unit_models/MD/membrane_distillation_0D.py index 57160f9a1f..c6e20f3ba9 100644 --- a/watertap/unit_models/MD/membrane_distillation_0D.py +++ b/watertap/unit_models/MD/membrane_distillation_0D.py @@ -14,6 +14,7 @@ from pyomo.environ import Constraint from pyomo.common.config import Bool, ConfigDict, ConfigValue, ConfigBlock, In from idaes.core import FlowDirection +from idaes.core.util import scaling as iscale from .MD_channel_base import ( ConcentrationPolarizationType, @@ -39,6 +40,7 @@ from .MD_channel_0D import MDChannel0DBlock from .membrane_distillation_base import ( MembraneDistillationBaseData, + MDconfigurationType, ) from idaes.core.util.config import is_physical_parameter_block @@ -206,7 +208,7 @@ class MembraneDistillationData(MembraneDistillationBaseData): _CONFIG_Template.declare( "temperature_polarization_type", ConfigValue( - default=TemperaturePolarizationType.calculated, + default=TemperaturePolarizationType.none, domain=In(TemperaturePolarizationType), description="External temperature polarization effect", doc=""" @@ -227,7 +229,7 @@ class MembraneDistillationData(MembraneDistillationBaseData): _CONFIG_Template.declare( "concentration_polarization_type", ConfigValue( - default=ConcentrationPolarizationType.calculated, + default=ConcentrationPolarizationType.none, domain=In(ConcentrationPolarizationType), description="External concentration polarization effect in RO", doc=""" @@ -248,7 +250,7 @@ class MembraneDistillationData(MembraneDistillationBaseData): _CONFIG_Template.declare( "mass_transfer_coefficient", ConfigValue( - default=MassTransferCoefficient.calculated, + default=MassTransferCoefficient.none, domain=In(MassTransferCoefficient), description="Mass transfer coefficient in RO feed channel", doc=""" @@ -353,6 +355,28 @@ class MembraneDistillationData(MembraneDistillationBaseData): CONFIG.declare("hot_ch", _CONFIG_Template(doc="hot channel config arguments")) CONFIG.declare("cold_ch", _CONFIG_Template(doc="cold channel config arguments")) + CONFIG.declare("gap_ch", _CONFIG_Template(doc="gap channel config arguments")) + + CONFIG.declare( + "MD_configuration_Type", + ConfigValue( + default=MDconfigurationType.DCMD, + domain=In(MDconfigurationType), + description="Options for selecting membrane distillation process configurations", + doc=""" + Options for selecting membrane distillation process configurations + **default** - ``MDconfigurationType.DCMD`` + + .. csv-table:: + :header: "Configuration Options", "Description" + + "``MDconfigurationType.DCMD``", "Direct Contact Membrane Distillation" + "``MDconfigurationType.VMD``", "Vacuum Membrane Distillation" + "``MDconfigurationType.GMD``", "Permeate Gap or Coductive Gap Membrane Distillation" + "``MDconfigurationType.AGMD``", "Air Gap Membrane Distillation" + """, + ), + ) def _make_MD_channel_control_volume(self, name_ch, common_config, config): @@ -411,13 +435,43 @@ def _add_mass_transfer(self): def eq_connect_mass_transfer(b, t, p, j): if p == "Liq": + if self.config.MD_configuration_Type == MDconfigurationType.DCMD: + return ( + b.cold_ch.mass_transfer_term[t, p, j] + == -b.hot_ch.mass_transfer_term[t, p, j] + ) + + else: + return Constraint.Skip + + else: + if self.config.MD_configuration_Type == MDconfigurationType.DCMD: + b.cold_ch.mass_transfer_term[t, p, j].fix(0) + return Constraint.Skip + elif self.config.MD_configuration_Type == MDconfigurationType.VMD: + b.cold_ch.mass_transfer_term[t, "Liq", j].fix(0) + return ( + b.cold_ch.mass_transfer_term[t, p, j] + == -b.hot_ch.mass_transfer_term[t, "Liq", j] + ) + + else: + return Constraint.Skip + + @self.Constraint( + self.flowsheet().config.time, + doc="Mass transfer from feed to permeate", + ) + def eq_connect_mass_transfer_gap(b, t): + + if self.config.MD_configuration_Type == MDconfigurationType.GMD: + b.gap_ch.mass_transfer_term[t, "Vap", "H2O"].fix(0) return ( - b.cold_ch.mass_transfer_term[t, "Liq", j] - == -b.hot_ch.mass_transfer_term[t, "Liq", j] + b.gap_ch.mass_transfer_term[t, "Liq", "H2O"] + == -b.hot_ch.mass_transfer_term[t, "Liq", "H2O"] ) else: - b.cold_ch.mass_transfer_term[t, p, j].fix(0) return Constraint.Skip @self.Constraint( @@ -445,14 +499,39 @@ def _add_heat_transfer(self): doc="Conductive heat transfer to cold channel", ) def eq_conductive_heat_transfer_hot(b, t): - return b.hot_ch.heat[t] == -b.area * b.flux_conduction_heat_avg[t] + if self.config.MD_configuration_Type == MDconfigurationType.VMD: + return b.hot_ch.heat[t] == -b.area * b.flux_expansion_heat_avg[t] + else: + return b.hot_ch.heat[t] == -b.area * b.flux_conduction_heat_avg[t] @self.Constraint( self.flowsheet().config.time, + # self.difference_elements, doc="Conductive heat transfer to cold channel", ) - def eq_conductive_heat_transfer_cold(b, t): - return b.cold_ch.heat[t] == -b.hot_ch.heat[t] + def eq_conductive_heat_transfer_term_cold(b, t): + if self.config.MD_configuration_Type == MDconfigurationType.DCMD: + return b.cold_ch.heat[t] == -b.hot_ch.heat[t] + elif self.config.MD_configuration_Type == MDconfigurationType.GMD: + return ( + b.cold_ch.heat[t] + == -b.hot_ch.heat[t] + - b.hot_ch.enthalpy_transfer[t] + - b.gap_ch.enthalpy_transfer[t] + ) + elif self.config.MD_configuration_Type == MDconfigurationType.VMD: + return Constraint.Skip + + @self.Constraint( + self.flowsheet().config.time, + self.difference_elements, + doc="Connecting the cold channel conductive heat transfer to conductive heat across the gap", + ) + def eq_conductive_heat_transfer_gap(b, t, x): + if self.config.MD_configuration_Type == MDconfigurationType.GMD: + return b.cold_ch.heat[t] == b.flux_conduction_heat_gap_avg[t] * b.area + else: + return Constraint.Skip @self.Constraint( self.flowsheet().config.time, @@ -466,4 +545,34 @@ def eq_enthalpy_transfer_hot(b, t): doc="Enthalpy heat transfer to the cold channel", ) def eq_enthalpy_transfer_cold(b, t): - return b.cold_ch.enthalpy_transfer[t] == b.area * b.flux_enth_cold_avg[t] + if self.config.MD_configuration_Type == MDconfigurationType.DCMD: + + return ( + b.cold_ch.enthalpy_transfer[t] == b.area * b.flux_enth_cold_avg[t] + ) + else: + return Constraint.Skip + + def calculate_scaling_factors(self): + if self.config.MD_configuration_Type == MDconfigurationType.GMD: + iscale.set_scaling_factor( + self.gap_ch.properties_in[0.0].enth_flow_phase["Liq"], + 4.0, + ) + + iscale.set_scaling_factor( + self.gap_ch.properties_in[0.0].flow_vol_phase["Liq"], + 1e9, + ) + + iscale.set_scaling_factor( + self.gap_ch.properties_interface[0.0, 0.0].flow_mass_phase_comp[ + "Liq", "H2O" + ], + 1e8, + ) + iscale.set_scaling_factor( + self.gap_ch.properties_interface[0.0, 0.0].flow_vol_phase["Liq"], 1e10 + ) + + super().calculate_scaling_factors() diff --git a/watertap/unit_models/MD/membrane_distillation_1D.py b/watertap/unit_models/MD/membrane_distillation_1D.py index a2df27707f..75502412b6 100644 --- a/watertap/unit_models/MD/membrane_distillation_1D.py +++ b/watertap/unit_models/MD/membrane_distillation_1D.py @@ -14,6 +14,7 @@ from pyomo.environ import Constraint from pyomo.common.config import Bool, ConfigDict, ConfigValue, ConfigBlock, In from idaes.core import FlowDirection +from idaes.core.util import scaling as iscale from .MD_channel_base import ( ConcentrationPolarizationType, @@ -40,6 +41,7 @@ from .MD_channel_1D import MDChannel1DBlock from .membrane_distillation_base import ( MembraneDistillationBaseData, + MDconfigurationType, ) from idaes.core.util.config import is_physical_parameter_block import idaes.logger as idaeslog @@ -358,9 +360,31 @@ class MembraneDistillationData(MembraneDistillationBaseData): CONFIG.declare("hot_ch", _CONFIG_Template(doc="hot channel config arguments")) CONFIG.declare("cold_ch", _CONFIG_Template(doc="cold channel config arguments")) + CONFIG.declare("gap_ch", _CONFIG_Template(doc="gap channel config arguments")) # Common config args for both sides + CONFIG.declare( + "MD_configuration_Type", + ConfigValue( + default=MDconfigurationType.DCMD, + domain=In(MDconfigurationType), + description="Options for selecting membrane distillation process configurations", + doc=""" + Options for selecting membrane distillation process configurations + **default** - ``TemperaturePolarizationType.calculated`` + + .. csv-table:: + :header: "Configuration Options", "Description" + + "``MDconfigurationType.DCMD``", "Direct Contact Membrane Distillation" + "``MDconfigurationType.VMD``", "Vacuum Membrane Distillation" + "``MDconfigurationType.GMD``", "Permeate Gap or Coductive Gap Membrane Distillation" + "``MDconfigurationType.AGMD``", "Air Gap Membrane Distillation" + """, + ), + ) + CONFIG.declare( "area_definition", ConfigValue( @@ -485,12 +509,40 @@ def _add_mass_transfer(self): def eq_connect_mass_transfer(b, t, x, p, j): if p == "Liq": + if self.config.MD_configuration_Type == MDconfigurationType.DCMD: + return ( + b.cold_ch.mass_transfer_term[t, x, p, j] + == -b.hot_ch.mass_transfer_term[t, x, p, j] + ) + else: + return Constraint.Skip + else: + if self.config.MD_configuration_Type == MDconfigurationType.DCMD: + b.cold_ch.mass_transfer_term[t, x, p, j].fix(0) + return Constraint.Skip + elif self.config.MD_configuration_Type == MDconfigurationType.GMD: + return Constraint.Skip + elif self.config.MD_configuration_Type == MDconfigurationType.VMD: + b.cold_ch.mass_transfer_term[t, x, "Liq", j].fix(0) + return ( + b.cold_ch.mass_transfer_term[t, x, p, j] + == -b.hot_ch.mass_transfer_term[t, x, "Liq", j] + ) + + @self.Constraint( + self.flowsheet().config.time, + self.difference_elements, + doc="Mass transfer from feed to gap", + ) + def eq_connect_mass_transfer_gap(b, t, x): + + if self.config.MD_configuration_Type == MDconfigurationType.GMD: + b.gap_ch.mass_transfer_term[t, x, "Vap", "H2O"].fix(0) return ( - b.cold_ch.mass_transfer_term[t, x, p, j] - == -b.hot_ch.mass_transfer_term[t, x, p, j] + b.gap_ch.mass_transfer_term[t, x, "Liq", "H2O"] + == -b.hot_ch.mass_transfer_term[t, x, "Liq", "H2O"] ) else: - b.cold_ch.mass_transfer_term[t, x, p, j].fix(0) return Constraint.Skip @self.Constraint( @@ -521,7 +573,10 @@ def _add_heat_transfer(self): doc="Conductive heat transfer to cold channel", ) def eq_conductive_heat_transfer_hot(b, t, x): - return b.hot_ch.heat[t, x] == -b.width * b.flux_conduction_heat[t, x] + if self.config.MD_configuration_Type == MDconfigurationType.VMD: + return b.hot_ch.heat[t, x] == -b.width * b.flux_expansion_heat[t, x] + else: + return b.hot_ch.heat[t, x] == -b.width * b.flux_conduction_heat[t, x] @self.Constraint( self.flowsheet().config.time, @@ -529,7 +584,30 @@ def eq_conductive_heat_transfer_hot(b, t, x): doc="Conductive heat transfer to cold channel", ) def eq_conductive_heat_transfer_cold(b, t, x): - return b.cold_ch.heat[t, x] == -b.hot_ch.heat[t, x] + if self.config.MD_configuration_Type == MDconfigurationType.DCMD: + return b.cold_ch.heat[t, x] == -b.hot_ch.heat[t, x] + elif self.config.MD_configuration_Type == MDconfigurationType.GMD: + return ( + b.cold_ch.heat[t, x] + == -b.hot_ch.heat[t, x] + - b.hot_ch.enthalpy_transfer[t, x] + - b.gap_ch.enthalpy_transfer[t, x] + ) + elif self.config.MD_configuration_Type == MDconfigurationType.VMD: + return Constraint.Skip + + @self.Constraint( + self.flowsheet().config.time, + self.difference_elements, + doc="Connecting the cold channel conductive heat transfer to conductive heat across the gap", + ) + def eq_conductive_heat_transfer_gap(b, t, x): + if self.config.MD_configuration_Type == MDconfigurationType.GMD: + return ( + b.cold_ch.heat[t, x] == b.flux_conduction_heat_gap[t, x] * b.width + ) + else: + return Constraint.Skip @self.Constraint( self.flowsheet().config.time, @@ -545,4 +623,168 @@ def eq_enthalpy_transfer_hot(b, t, x): doc="Enthalpy heat transfer to the cold channel", ) def eq_enthalpy_transfer_cold(b, t, x): - return b.cold_ch.enthalpy_transfer[t, x] == b.width * b.flux_enth_cold[t, x] + if self.config.MD_configuration_Type in [ + MDconfigurationType.DCMD, + ]: + return ( + b.cold_ch.enthalpy_transfer[t, x] + == b.width * b.flux_enth_cold[t, x] + ) + else: + return Constraint.Skip + + @self.Constraint( + self.flowsheet().config.time, doc="Gap interface pressure equality" + ) + def eq_equal_pressure_interface_gap(b, t): + if self.config.MD_configuration_Type == MDconfigurationType.GMD: + return ( + b.gap_ch.properties_interface[ + t, self.gap_ch.length_domain.first() + ].pressure + == b.gap_ch.properties[ + t, self.gap_ch.length_domain.first() + ].pressure + ) + else: + return Constraint.Skip + + @self.Constraint( + self.flowsheet().config.time, + doc="gap bulk temperature in GMD", + ) + def gap_bulk_temperature_interface_gap(b, t): + + if self.config.MD_configuration_Type == MDconfigurationType.GMD: + return ( + b.gap_ch.properties_interface[ + t, self.gap_ch.length_domain.first() + ].temperature + == b.gap_ch.properties[ + t, self.gap_ch.length_domain.first() + ].temperature + ) + + else: + return Constraint.Skip + + @self.Constraint( + self.flowsheet().config.time, + doc="gap bulk temperature inlet GMD", + ) + def gap_bulk_temperature_interface_inlet(b, t): + + if self.config.MD_configuration_Type == MDconfigurationType.GMD: + if ( + self.config.gap_ch.temperature_polarization_type + == TemperaturePolarizationType.fixed + ): + return ( + b.hot_ch.properties[ + t, self.hot_ch.length_domain.first() + ].temperature + + b.cold_ch.properties[ + t, self.cold_ch.length_domain.last() + ].temperature + == b.gap_ch.properties[ + t, self.gap_ch.length_domain.first() + ].temperature + * 2 + ) + + else: + return Constraint.Skip + + if self.config.MD_configuration_Type == MDconfigurationType.GMD: + for t in self.flowsheet().config.time: + self.gap_ch.properties_interface[ + t, self.gap_ch.length_domain.first() + ].flow_mass_phase_comp["Liq", "H2O"].fix(0) + + @self.Constraint(self.flowsheet().config.time, doc="VMD inlet temperature") + def eq_vmd_inlet_temp(b, t): + if self.config.MD_configuration_Type == MDconfigurationType.VMD: + return ( + b.cold_ch.properties[ + t, self.cold_ch.length_domain.first() + ].temperature + == b.hot_ch.properties[ + t, self.hot_ch.length_domain.first() + ].temperature + ) + else: + return Constraint.Skip + + def calculate_scaling_factors(self): + + super().calculate_scaling_factors() + + if self.config.MD_configuration_Type == MDconfigurationType.GMD: + iscale.set_scaling_factor( + self.gap_ch.properties_interface[0.0, 0.0].flow_mass_phase_comp[ + "Liq", "H2O" + ], + 1e7, + ) + iscale.set_scaling_factor( + self.gap_ch.properties_interface[0.0, 0.0].flow_vol_phase["Liq"], 1e10 + ) + + for (t, x, p, j), v in self.gap_ch._flow_terms.items(): + iscale.set_scaling_factor(v, 1e3) + + for (t, x), v in self.gap_ch.enthalpy_transfer.items(): + iscale.set_scaling_factor(v, 1e-3) + + for (t, x, p), v in self.gap_ch._enthalpy_flow.items(): + iscale.set_scaling_factor(v, 1e-2) + + for t in self.flowsheet().config.time: + for x in self.gap_ch.length_domain: + for p in self.gap_ch.config.property_package.phase_list: + for j in self.gap_ch.config.property_package.component_list: + if (p, j) in self.gap_ch.properties[ + t, x + ].flow_mass_phase_comp: + v = self.gap_ch.properties[t, x].flow_mass_phase_comp[ + p, j + ] + iscale.set_scaling_factor(v, 1e4) + + for t in self.flowsheet().config.time: + for x in self.gap_ch.length_domain: + for p in self.gap_ch.config.property_package.phase_list: + if p in self.gap_ch.properties[t, x].flow_vol_phase: + v = self.gap_ch.properties[t, x].flow_vol_phase[p] + iscale.set_scaling_factor(v, 1e9) + + for t in self.flowsheet().config.time: + for x in self.gap_ch.length_domain: + for p in self.gap_ch.config.property_package.phase_list: + for j in self.gap_ch.config.property_package.component_list: + if (p, j) in self.gap_ch.properties_interface[ + t, x + ].flow_mass_phase_comp: + var = self.gap_ch.properties_interface[ + t, x + ].flow_mass_phase_comp[p, j] + iscale.set_scaling_factor(var, 1e3) + + for t in self.flowsheet().config.time: + for x in self.gap_ch.length_domain: + for p in self.gap_ch.config.property_package.phase_list: + if p in self.gap_ch.properties_interface[t, x].flow_vol_phase: + v = self.gap_ch.properties_interface[t, x].flow_vol_phase[p] + iscale.set_scaling_factor(v, 1e9) + + for t in self.flowsheet().config.time: + for x in self.gap_ch.length_domain: + for p in self.gap_ch.config.property_package.phase_list: + if p in self.gap_ch.properties[t, x].enth_flow_phase: + v = self.gap_ch.properties[t, x].enth_flow_phase[p] + iscale.set_scaling_factor(v, 1e-3) + + if self.config.MD_configuration_Type == MDconfigurationType.VMD: + for (t, x, p, j), v in self.cold_ch._flow_terms.items(): + if p == "Vap" and j == "H2O": + iscale.set_scaling_factor(v, 1e4) diff --git a/watertap/unit_models/MD/membrane_distillation_base.py b/watertap/unit_models/MD/membrane_distillation_base.py index 34758acde0..60c049cf25 100644 --- a/watertap/unit_models/MD/membrane_distillation_base.py +++ b/watertap/unit_models/MD/membrane_distillation_base.py @@ -18,7 +18,10 @@ check_optimal_termination, exp, units as pyunits, + Constraint, + log, ) +from enum import Enum, auto from idaes.core import UnitModelBlockData from watertap.core.solvers import get_solver @@ -40,9 +43,24 @@ ) from watertap.core.util.initialization import interval_initializer + __author__ = "Elmira Shamlou" +class MDconfigurationType(Enum): + """ + DCMD: Direct Contact Membrane Distillation + VMD: Vacuum Membrane Distillation + GMD: Permeate Gap or Conductive Gap Membrane distillation + AGMD: Air Gap Membrane Distillation + """ + + DCMD = auto() + VMD = auto() + GMD = auto() + AGMD = auto() + + class MembraneDistillationBaseData(InitializationMixin, UnitModelBlockData): def build(self): """ @@ -56,8 +74,13 @@ def build(self): self.hot_ch.add_state_blocks( has_phase_equilibrium=False, + ) + + self.hot_ch._add_interface_stateblock(has_phase_equilibrium=False) + self.hot_ch._add_vapor_stateblock( property_package_vapor=self.config.hot_ch.property_package_vapor, property_package_args_vapor=self.config.hot_ch.property_package_args_vapor, + has_phase_equilibrium=False, ) # self.hot_ch.set_config(self.config.hot_ch) @@ -101,32 +124,85 @@ def build(self): "cold_ch", self.config, self.config.cold_ch ) - self.cold_ch.add_state_blocks( - has_phase_equilibrium=False, - property_package_vapor=self.config.cold_ch.property_package_vapor, - property_package_args_vapor=self.config.cold_ch.property_package_args_vapor, - ) + self.cold_ch.add_state_blocks(has_phase_equilibrium=False) - self.cold_ch.add_material_balances( - balance_type=self.config.cold_ch.material_balance_type, - has_mass_transfer=True, - ) + # cold channel + # vacuum channel is VMD is also named as cold_ch - self.cold_ch.add_momentum_balances( - balance_type=self.config.cold_ch.momentum_balance_type, - pressure_change_type=self.config.cold_ch.pressure_change_type, - has_pressure_change=self.config.cold_ch.has_pressure_change, - friction_factor=self.config.cold_ch.friction_factor, - ) + if self.config.MD_configuration_Type in [ + MDconfigurationType.DCMD, + MDconfigurationType.GMD, + MDconfigurationType.AGMD, + ]: + self.cold_ch._add_interface_stateblock(has_phase_equilibrium=False) - self.cold_ch.add_energy_balances( - balance_type=self.config.cold_ch.energy_balance_type, - has_heat_transfer=True, - has_enthalpy_transfer=True, - ) + # Add constraint for volumetric flow equality between interface and bulk + self.cold_ch.add_extensive_flow_to_interface() - # Add constraint for volumetric flow equality between interface and bulk - self.cold_ch.add_extensive_flow_to_interface() + if self.config.MD_configuration_Type == MDconfigurationType.DCMD: + self.cold_ch._add_vapor_stateblock( + property_package_vapor=self.config.cold_ch.property_package_vapor, + property_package_args_vapor=self.config.cold_ch.property_package_args_vapor, + has_phase_equilibrium=False, + ) + + if self.config.MD_configuration_Type in [ + MDconfigurationType.DCMD, + MDconfigurationType.VMD, + ]: + self.cold_ch.add_material_balances( + balance_type=self.config.cold_ch.material_balance_type, + has_mass_transfer=True, + ) + elif self.config.MD_configuration_Type in [ + MDconfigurationType.GMD, + MDconfigurationType.AGMD, + ]: + self.cold_ch.add_material_balances( + balance_type=self.config.cold_ch.material_balance_type, + has_mass_transfer=False, + ) + + if self.config.MD_configuration_Type in [ + MDconfigurationType.DCMD, + MDconfigurationType.GMD, + MDconfigurationType.AGMD, + ]: + self.cold_ch.add_momentum_balances( + balance_type=self.config.cold_ch.momentum_balance_type, + pressure_change_type=self.config.cold_ch.pressure_change_type, + has_pressure_change=self.config.cold_ch.has_pressure_change, + friction_factor=self.config.cold_ch.friction_factor, + ) + + if self.config.MD_configuration_Type == MDconfigurationType.VMD: + self.cold_ch.add_momentum_balances( + balance_type=self.config.cold_ch.momentum_balance_type, + pressure_change_type=self.config.cold_ch.pressure_change_type, + has_pressure_change=False, + friction_factor=self.config.cold_ch.friction_factor, + ) + + if self.config.MD_configuration_Type == MDconfigurationType.DCMD: + self.cold_ch.add_energy_balances( + balance_type=self.config.cold_ch.energy_balance_type, + has_heat_transfer=True, + has_enthalpy_transfer=True, + ) + elif self.config.MD_configuration_Type in [ + MDconfigurationType.GMD, + MDconfigurationType.AGMD, + ]: + self.cold_ch.add_energy_balances( + balance_type=self.config.cold_ch.energy_balance_type, + has_heat_transfer=True, + has_enthalpy_transfer=False, + ) + elif self.config.MD_configuration_Type == MDconfigurationType.VMD: + self.cold_ch.add_energy_balances( + balance_type=self.config.cold_ch.energy_balance_type, + has_enthalpy_transfer=True, + ) # Concentration polarization constraint is not accounted for in the below method; it is # written later in the base model (eq_concentration_polarization) @@ -136,30 +212,118 @@ def build(self): concentration_polarization_type=ConcentrationPolarizationType.none, mass_transfer_coefficient=MassTransferCoefficient.none, ) - - self.cold_ch.add_temperature_polarization( - temperature_polarization_type=self.config.cold_ch.temperature_polarization_type, - ) + if self.config.MD_configuration_Type in [ + MDconfigurationType.DCMD, + MDconfigurationType.GMD, + MDconfigurationType.AGMD, + ]: + self.cold_ch.add_temperature_polarization( + temperature_polarization_type=self.config.cold_ch.temperature_polarization_type, + ) try: self.cold_ch.apply_transformation() except AttributeError: pass - for t in self.flowsheet().config.time: - for x in self.cold_ch.length_domain: - # Check if 'Vap' phase and 'H2O' component are defined in the property package - if ( - "Vap" in self.cold_ch.config.property_package.phase_list - and "H2O" in self.cold_ch.config.property_package.component_list - ): - # If so, fix the flow of 'H2O' in the 'Vap' phase to 0 - self.cold_ch.properties[t, x].flow_mass_phase_comp[ - "Vap", "H2O" - ].fix(0) - self.cold_ch.properties_interface[t, x].flow_mass_phase_comp[ - "Vap", "H2O" - ].fix(0) + if self.config.MD_configuration_Type in [ + MDconfigurationType.DCMD, + MDconfigurationType.GMD, + MDconfigurationType.AGMD, + ]: + for t in self.flowsheet().config.time: + for x in self.cold_ch.length_domain: + + if ( + "Vap" in self.cold_ch.config.property_package.phase_list + and "H2O" in self.cold_ch.config.property_package.component_list + ): + + self.cold_ch.properties[t, x].flow_mass_phase_comp[ + "Vap", "H2O" + ].fix(0) + self.cold_ch.properties_interface[t, x].flow_mass_phase_comp[ + "Vap", "H2O" + ].fix(0) + + if self.config.MD_configuration_Type == MDconfigurationType.VMD: + for t in self.flowsheet().config.time: + for x in self.cold_ch.length_domain: + if ( + "Vap" in self.cold_ch.config.property_package.phase_list + and "H2O" in self.cold_ch.config.property_package.component_list + ): + + self.cold_ch.properties[t, x].flow_mass_phase_comp[ + "Liq", "H2O" + ].fix(0) + self.cold_ch.properties[ + t, self.cold_ch.length_domain.first() + ].flow_mass_phase_comp["Vap", "H2O"].fix(0) + + # gap channel + # modification required for AGMD + if self.config.MD_configuration_Type in [ + MDconfigurationType.GMD, + MDconfigurationType.AGMD, + ]: + self._make_MD_channel_control_volume( + "gap_ch", self.config, self.config.gap_ch + ) + + self.gap_ch.add_state_blocks( + has_phase_equilibrium=False, + ) + + self.gap_ch._add_interface_stateblock(has_phase_equilibrium=False) + + self.gap_ch.add_material_balances( + balance_type=self.config.gap_ch.material_balance_type, + has_mass_transfer=True, + ) + + self.gap_ch.add_momentum_balances( + balance_type=self.config.gap_ch.momentum_balance_type, + pressure_change_type=self.config.gap_ch.pressure_change_type, + has_pressure_change=self.config.gap_ch.has_pressure_change, + friction_factor=self.config.gap_ch.friction_factor, + ) + + self.gap_ch.add_energy_balances( + balance_type=self.config.gap_ch.energy_balance_type, + has_heat_transfer=False, + has_enthalpy_transfer=True, + ) + + self.gap_ch.add_extensive_flow_to_interface() + + try: + self.gap_ch.apply_transformation() + except AttributeError: + pass + + if self.config.MD_configuration_Type in [ + MDconfigurationType.GMD, + ]: + + for t in self.flowsheet().config.time: + for x in self.gap_ch.length_domain: + self.gap_ch.properties[ + t, self.gap_ch.length_domain.first() + ].flow_mass_phase_comp["Liq", "H2O"].fix(0) + + if ( + "Vap" in self.gap_ch.config.property_package.phase_list + and "H2O" in self.gap_ch.config.property_package.component_list + ): + + self.gap_ch.properties[t, x].flow_mass_phase_comp[ + "Vap", "H2O" + ].fix(0) + + self.gap_ch.properties_interface[t, x].flow_mass_phase_comp[ + "Vap", "H2O" + ].fix(0) add_object_reference(self, "length_domain", self.hot_ch.length_domain) add_object_reference( @@ -173,6 +337,9 @@ def build(self): self.add_outlet_port(name="hot_ch_outlet", block=self.hot_ch) self.add_inlet_port(name="cold_ch_inlet", block=self.cold_ch) self.add_outlet_port(name="cold_ch_outlet", block=self.cold_ch) + if self.config.MD_configuration_Type == MDconfigurationType.GMD: + self.add_inlet_port(name="gap_ch_inlet", block=self.gap_ch) + self.add_outlet_port(name="gap_ch_outlet", block=self.gap_ch) self._add_heat_flux() self._add_mass_flux() @@ -188,8 +355,6 @@ def build(self): self.recovery_mass = Var( self.flowsheet().config.time, - # self.config.hot_ch.property_package.phase_list, - # self.config.hot_ch.property_package.component_list, initialize=0.1, bounds=(1e-11, 0.99), units=pyunits.dimensionless, @@ -206,11 +371,11 @@ def eq_recovery_mass(b, t): * b.hot_ch.properties[t, b.first_element].flow_mass_phase_comp[ "Liq", "H2O" ] - == b.cold_ch.properties[t, b.first_element].flow_mass_phase_comp[ + == b.hot_ch.properties[t, b.first_element].flow_mass_phase_comp[ "Liq", "H2O" ] - - b.cold_ch.properties[ - t, b.cold_ch.length_domain.last() + - b.hot_ch.properties[ + t, b.hot_ch.length_domain.last() ].flow_mass_phase_comp["Liq", "H2O"] ) @@ -222,28 +387,46 @@ def eq_recovery_mass(b, t): doc="Average thermal efficiency", ) def thermal_efficiency(b, t): - total_enth_flux = sum( - b.flux_enth_hot[t, x] for x in self.difference_elements - ) - total_cond_heat_flux = sum( - b.flux_conduction_heat[t, x] for x in self.difference_elements - ) - - return total_enth_flux / (total_enth_flux + total_cond_heat_flux) - - @self.Expression( - self.flowsheet().config.time, - doc="module heat recovery: ratio of the actual heat recovered by cold side to the maximum ideal heat recovery", - ) - def effectiveness(b, t): + if self.config.MD_configuration_Type in [ + MDconfigurationType.DCMD, + MDconfigurationType.GMD, + MDconfigurationType.AGMD, + ]: + + total_enth_flux = sum( + b.flux_enth_hot[t, x] for x in self.difference_elements + ) + total_cond_heat_flux = sum( + b.flux_conduction_heat[t, x] for x in self.difference_elements + ) - return ( - b.cold_ch.properties[t, b.first_element].temperature - - b.cold_ch.properties[t, b.cold_ch.length_domain.last()].temperature - ) / ( - b.hot_ch.properties[t, b.first_element].temperature - - b.cold_ch.properties[t, b.cold_ch.length_domain.last()].temperature + return total_enth_flux / (total_enth_flux + total_cond_heat_flux) + else: + return Constraint.Skip + + if self.config.MD_configuration_Type in [ + MDconfigurationType.DCMD, + MDconfigurationType.GMD, + MDconfigurationType.AGMD, + ]: + # to do: define effectiveness at the flowsheet level, particularly for VMD and DCMD configs + @self.Expression( + self.flowsheet().config.time, + doc="module heat recovery: ratio of the actual heat recovered by cold side to the maximum ideal heat recovery", ) + def effectiveness(b, t): + + return ( + b.cold_ch.properties[t, b.first_element].temperature + - b.cold_ch.properties[ + t, b.cold_ch.length_domain.last() + ].temperature + ) / ( + b.hot_ch.properties[t, b.first_element].temperature + - b.cold_ch.properties[ + t, b.cold_ch.length_domain.last() + ].temperature + ) def _add_mass_flux(self): @@ -280,7 +463,7 @@ def _add_mass_flux(self): self.flowsheet().config.time, self.difference_elements, initialize=1e3, - bounds=(1e-10, 1e5), + bounds=(1e-10, 1e10), units=pyunits.J * pyunits.s**-1 * pyunits.m**-2, doc="hot side evaporation enthalpy flux", ) @@ -289,11 +472,22 @@ def _add_mass_flux(self): self.flowsheet().config.time, self.difference_elements, initialize=1e3, - bounds=(1e-10, 1e5), + bounds=(1e-10, 1e10), units=pyunits.J * pyunits.s**-1 * pyunits.m**-2, doc="cold side condensation enthalpy flux", ) + if self.config.MD_configuration_Type == MDconfigurationType.VMD: + + self.flux_expansion_heat = Var( + self.flowsheet().config.time, + self.difference_elements, + initialize=1e3, + bounds=(1e-10, 1e10), + units=pyunits.J * pyunits.s**-1 * pyunits.m**-2, + doc="heat of vapor expansion in VMD configuration", + ) + self.dens_solvent = Param( initialize=1000, units=units_meta("mass") * units_meta("length") ** -3, @@ -306,12 +500,31 @@ def _add_mass_flux(self): doc="Solvent mass flux", ) def eq_flux_mass(b, t, x): - return b.flux_mass[t, x] == b.permeability_coef[ - t - ] / b.membrane_thickness * ( - b.hot_ch.properties_interface[t, x].pressure_sat - - b.cold_ch.properties_interface[t, x].pressure_sat - ) + + if self.config.MD_configuration_Type == MDconfigurationType.DCMD: + return b.flux_mass[t, x] == b.permeability_coef[ + t + ] / b.membrane_thickness * ( + b.hot_ch.properties_interface[t, x].pressure_sat + - b.cold_ch.properties_interface[t, x].pressure_sat + ) + elif self.config.MD_configuration_Type in [ + MDconfigurationType.GMD, + MDconfigurationType.AGMD, + ]: + return b.flux_mass[t, x] == b.permeability_coef[ + t + ] / b.membrane_thickness * ( + b.hot_ch.properties_interface[t, x].pressure_sat + - b.gap_ch.properties_interface[t, x].pressure_sat + ) + elif self.config.MD_configuration_Type == MDconfigurationType.VMD: + return b.flux_mass[t, x] == b.permeability_coef[ + t + ] / b.membrane_thickness * ( + b.hot_ch.properties_interface[t, x].pressure_sat + - b.cold_ch.properties[t, x].pressure + ) @self.Expression( self.flowsheet().config.time, @@ -358,11 +571,44 @@ def eq_flux_hot_enth(b, t, x): doc="cold side evaporation enthalpy flux", ) def eq_flux_cold_enth(b, t, x): - return ( - b.flux_enth_cold[t, x] - == b.flux_mass[t, x] - * b.cold_ch.properties_vapor[t, x].enth_mass_phase["Vap"] - ) + if self.config.MD_configuration_Type == MDconfigurationType.DCMD: + return ( + b.flux_enth_cold[t, x] + == b.flux_mass[t, x] + * b.cold_ch.properties_vapor[t, x].enth_mass_phase["Vap"] + ) + elif self.config.MD_configuration_Type == MDconfigurationType.GMD: + return ( + b.flux_enth_cold[t, x] + == b.flux_mass[t, x] + * b.gap_ch.properties[t, x].enth_mass_phase["Liq"] + ) + elif self.config.MD_configuration_Type == MDconfigurationType.VMD: + return ( + b.flux_enth_cold[t, x] + == b.flux_mass[t, x] + * b.cold_ch.properties[t, x].enth_mass_phase["Vap"] + ) + + @self.Constraint( + self.flowsheet().config.time, + self.difference_elements, + doc="vapor expansion heat flux", + ) + def eq_flux_expansion_heat(b, t, x): + if self.config.MD_configuration_Type == MDconfigurationType.VMD: + + R = 8.3146261 * pyunits.J / pyunits.mol / pyunits.K + molar_density = 18.01528e-3 * pyunits.kg / pyunits.mol + T = b.hot_ch.properties_vapor[t, x].temperature + P_f = b.hot_ch.properties_vapor[t, x].pressure + P_p = b.cold_ch.properties[t, x].pressure + + return b.flux_expansion_heat[t, x] == b.flux_mass[ + t, x + ] / molar_density * R * T * log(P_f / P_p) + else: + return Constraint.Skip @self.Expression( self.flowsheet().config.time, @@ -382,6 +628,19 @@ def flux_enth_cold_avg(b, t): sum(b.flux_enth_cold[t, x] for x in self.difference_elements) / self.nfe ) + @self.Expression( + self.flowsheet().config.time, + doc="Average expansion heat", + ) + def flux_expansion_heat_avg(b, t): + if self.config.MD_configuration_Type == MDconfigurationType.VMD: + return ( + sum(b.flux_expansion_heat[t, x] for x in self.difference_elements) + / self.nfe + ) + else: + return Constraint.Skip + @self.Constraint( self.flowsheet().time, self.difference_elements, @@ -410,10 +669,40 @@ def eq_vapor_pressure_hot(b, t, x): doc="cold side Vapor temperature", ) def eq_vapor_temperature_cold(b, t, x): - return ( - b.cold_ch.properties_vapor[t, x].temperature - == b.cold_ch.properties_interface[t, x].temperature - ) + if self.config.MD_configuration_Type == MDconfigurationType.DCMD: + return ( + b.cold_ch.properties_vapor[t, x].temperature + == b.cold_ch.properties_interface[t, x].temperature + ) + elif self.config.MD_configuration_Type == MDconfigurationType.VMD: + return ( + b.cold_ch.properties[t, x].temperature + == b.hot_ch.properties_interface[t, x].temperature + ) + else: + return Constraint.Skip + + @self.Constraint( + self.flowsheet().config.time, + self.difference_elements, + doc="gap bulk temperature in GMD", + ) + def gap_bulk_temperature(b, t, x): + # assuming linear temperature change across the gap + + if self.config.MD_configuration_Type == MDconfigurationType.GMD: + if ( + self.config.gap_ch.temperature_polarization_type + == TemperaturePolarizationType.fixed + ): + return ( + b.gap_ch.properties_interface[t, x].temperature + + b.cold_ch.properties_interface[t, x].temperature + == 2 * b.gap_ch.properties[t, x].temperature + ) + + else: + return Constraint.Skip @self.Constraint( self.flowsheet().time, @@ -421,10 +710,13 @@ def eq_vapor_temperature_cold(b, t, x): doc="cold side Vapor pressure", ) def eq_vapor_pressure_cold(b, t, x): - return ( - b.cold_ch.properties_vapor[t, x].pressure - == b.cold_ch.properties_interface[t, x].pressure_sat - ) + if self.config.MD_configuration_Type == MDconfigurationType.DCMD: + return ( + b.cold_ch.properties_vapor[t, x].pressure + == b.cold_ch.properties_interface[t, x].pressure_sat + ) + else: + return Constraint.Skip @self.Constraint( self.flowsheet().config.time, @@ -448,12 +740,16 @@ def eq_vapor_flow(b, t, x): def eq_vapor_flow_equal(b, t, x): lb = b.hot_ch.properties_vapor[t, x].flow_mass_phase_comp["Liq", "H2O"].lb b.hot_ch.properties_vapor[t, x].flow_mass_phase_comp["Liq", "H2O"].fix(lb) - return ( - b.hot_ch.properties_vapor[t, x].flow_mass_phase_comp["Vap", "H2O"] - == b.cold_ch.properties_vapor[t, x].flow_mass_phase_comp["Vap", "H2O"] - ) + if self.config.MD_configuration_Type == MDconfigurationType.DCMD: + return ( + b.hot_ch.properties_vapor[t, x].flow_mass_phase_comp["Vap", "H2O"] + == b.cold_ch.properties_vapor[t, x].flow_mass_phase_comp[ + "Vap", "H2O" + ] + ) + else: + return Constraint.Skip - # Check for hot channel temperature polarization type if ( self.config.hot_ch.temperature_polarization_type != TemperaturePolarizationType.none @@ -465,19 +761,33 @@ def eq_vapor_flow_equal(b, t, x): doc="Temperature polarization in hot channel", ) def eq_temperature_polarization_hot(b, t, x): - return ( - b.hot_ch.h_conv[t, x] - * ( - b.hot_ch.properties[t, x].temperature - - b.hot_ch.properties_interface[t, x].temperature + if self.config.MD_configuration_Type == MDconfigurationType.VMD: + return ( + b.hot_ch.h_conv[t, x] + * ( + b.hot_ch.properties[t, x].temperature + - b.hot_ch.properties_interface[t, x].temperature + ) + == b.flux_expansion_heat[t, x] + + b.flux_enth_hot[t, x] + - b.flux_mass[t, x] + * b.hot_ch.properties[t, x].enth_mass_phase["Liq"] + ) + + else: + + return ( + b.hot_ch.h_conv[t, x] + * ( + b.hot_ch.properties[t, x].temperature + - b.hot_ch.properties_interface[t, x].temperature + ) + == b.flux_conduction_heat[t, x] + + b.flux_enth_hot[t, x] + - b.flux_mass[t, x] + * b.hot_ch.properties[t, x].enth_mass_phase["Liq"] ) - == b.flux_conduction_heat[t, x] - + b.flux_enth_hot[t, x] - - b.flux_mass[t, x] - * b.hot_ch.properties[t, x].enth_mass_phase["Liq"] - ) - # Check for cold channel temperature polarization type if ( self.config.cold_ch.temperature_polarization_type != TemperaturePolarizationType.none @@ -489,19 +799,31 @@ def eq_temperature_polarization_hot(b, t, x): doc="Temperature polarization in cold channel", ) def eq_temperature_polarization_cold(b, t, x): - return ( - b.cold_ch.h_conv[t, x] - * ( - -b.cold_ch.properties[t, x].temperature - + b.cold_ch.properties_interface[t, x].temperature + if self.config.MD_configuration_Type == MDconfigurationType.GMD: + return ( + b.cold_ch.h_conv[t, x] + * ( + -b.cold_ch.properties[t, x].temperature + + b.cold_ch.properties_interface[t, x].temperature + ) + == b.flux_conduction_heat_gap[t, x] ) - == b.flux_conduction_heat[t, x] - + b.flux_enth_cold[t, x] - - b.flux_mass[t, x] - * b.cold_ch.properties[t, x].enth_mass_phase["Liq"] - ) + elif self.config.MD_configuration_Type == MDconfigurationType.DCMD: + return ( + b.cold_ch.h_conv[t, x] + * ( + -b.cold_ch.properties[t, x].temperature + + b.cold_ch.properties_interface[t, x].temperature + ) + == b.flux_conduction_heat[t, x] + + b.flux_enth_cold[t, x] + - b.flux_mass[t, x] + * b.cold_ch.properties[t, x].enth_mass_phase["Liq"] + ) + else: + return Constraint.Skip - return self.eq_flux_mass + return self.eq_flux_mass def _add_heat_flux(self): @@ -513,21 +835,46 @@ def _add_heat_flux(self): units=pyunits.m, ) - self.membrane_thermal_conductivity = Var( - initialize=0.2, - bounds=(0, 1), - units=pyunits.J * pyunits.s**-1 * pyunits.K**-1 * pyunits.m**-1, - doc="Thermal conductivity coefficient of the membrane", - ) + if self.config.MD_configuration_Type != MDconfigurationType.VMD: - self.flux_conduction_heat = Var( - self.flowsheet().config.time, - self.difference_elements, - initialize=1e-4, - bounds=(0, 1e10), - units=pyunits.J * pyunits.s**-1 * pyunits.m**-2, - doc="conduction heat flux", - ) + self.flux_conduction_heat = Var( + self.flowsheet().config.time, + self.difference_elements, + initialize=1e3, + bounds=(0, 1e10), + units=pyunits.J * pyunits.s**-1 * pyunits.m**-2, + doc="conduction heat flux", + ) + self.membrane_thermal_conductivity = Var( + initialize=0.2, + bounds=(0, 1), + units=pyunits.J * pyunits.s**-1 * pyunits.K**-1 * pyunits.m**-1, + doc="Thermal conductivity coefficient of the membrane", + ) + + if self.config.MD_configuration_Type == MDconfigurationType.GMD: + self.gap_thermal_conductivity = Var( + initialize=0.06, + bounds=(0, 1), + units=pyunits.J * pyunits.s**-1 * pyunits.K**-1 * pyunits.m**-1, + doc="Thermal conductivity coefficient of the gap", + ) + + self.gap_thickness = Var( + initialize=1e-4, + bounds=(1e-5, 1e-2), + doc="gap thickness", + units=pyunits.m, + ) + + self.flux_conduction_heat_gap = Var( + self.flowsheet().config.time, + self.difference_elements, + initialize=10e3, + bounds=(0, 1e20), + units=pyunits.J * pyunits.s**-1 * pyunits.m**-2, + doc="conduction heat across the gap", + ) @self.Constraint( self.flowsheet().config.time, @@ -535,22 +882,68 @@ def _add_heat_flux(self): doc="conduction heat flux", ) def eq_flux_heat(b, t, x): - return b.flux_conduction_heat[ - t, x - ] == b.membrane_thermal_conductivity / b.membrane_thickness * ( - b.hot_ch.properties_interface[t, x].temperature - - b.cold_ch.properties_interface[t, x].temperature - ) + if self.config.MD_configuration_Type == MDconfigurationType.DCMD: + return b.flux_conduction_heat[ + t, x + ] == b.membrane_thermal_conductivity / b.membrane_thickness * ( + b.hot_ch.properties_interface[t, x].temperature + - b.cold_ch.properties_interface[t, x].temperature + ) + elif self.config.MD_configuration_Type == MDconfigurationType.GMD: + return b.flux_conduction_heat[ + t, x + ] == b.membrane_thermal_conductivity / b.membrane_thickness * ( + b.hot_ch.properties_interface[t, x].temperature + - b.gap_ch.properties_interface[t, x].temperature + ) + elif self.config.MD_configuration_Type == MDconfigurationType.VMD: + return Constraint.Skip @self.Expression( self.flowsheet().config.time, doc="Average conduction heat flux expression", ) def flux_conduction_heat_avg(b, t): - return ( - sum(b.flux_conduction_heat[t, x] for x in self.difference_elements) - / self.nfe - ) + if self.config.MD_configuration_Type != MDconfigurationType.VMD: + return ( + sum(b.flux_conduction_heat[t, x] for x in self.difference_elements) + / self.nfe + ) + else: + return Constraint.Skip + + @self.Constraint( + self.flowsheet().config.time, + self.difference_elements, + doc="heat conduction across the gap in GMD configuration", + ) + def eq_flux_conduction_gap(b, t, x): + if self.config.MD_configuration_Type == MDconfigurationType.GMD: + + return b.flux_conduction_heat_gap[ + t, x + ] == b.gap_thermal_conductivity / b.gap_thickness * ( + b.gap_ch.properties_interface[t, x].temperature + - b.cold_ch.properties_interface[t, x].temperature + ) + else: + return Constraint.Skip + + @self.Expression( + self.flowsheet().config.time, + doc="Average conduction heat flux across the gap", + ) + def flux_conduction_heat_gap_avg(b, t): + if self.config.MD_configuration_Type == MDconfigurationType.GMD: + return ( + sum( + b.flux_conduction_heat_gap[t, x] + for x in self.difference_elements + ) + / self.nfe + ) + else: + return Constraint.Skip return self.eq_flux_heat @@ -578,7 +971,7 @@ def _add_area(self, include_constraint=True): if not hasattr(self, "area"): self.area = Var( initialize=10, - bounds=(1e-1, 1e3), + bounds=(1e-10, 1e10), domain=NonNegativeReals, units=pyunits.m**2, doc="Total Membrane area", @@ -586,7 +979,7 @@ def _add_area(self, include_constraint=True): if include_constraint: if not hasattr(self, "eq_area"): - # Membrane area equation + @self.Constraint(doc="Total Membrane area") def eq_area(b): return b.area == b.length * b.width @@ -618,7 +1011,7 @@ def initialize_build( type="hot_ch", ) - init_log.info_high("Initialization Step 1a (hot channel) Complete") + init_log.info("Initialization Step 1a (hot channel) Complete") cold_ch_flags = self.cold_ch.initialize( state_args=state_args_cold_ch, @@ -629,7 +1022,27 @@ def initialize_build( type="cold_ch", ) - init_log.info_high("Initialization Step 1b (cold channel) Complete") + init_log.info("Initialization Step 1b (cold channel) Complete") + + if self.config.MD_configuration_Type == MDconfigurationType.GMD: + gap_ch_flags = self.gap_ch.initialize( + state_args=state_args_cold_ch, + outlvl=outlvl, + optarg=optarg, + solver=solver, + initialize_guess=initialize_guess, + type="cold_ch", + ) + + init_log.info("Initialization Step 1c (gap channel) Complete") + + if self.config.MD_configuration_Type == MDconfigurationType.VMD: + for x in [self.cold_ch.length_domain.first()]: + self.cold_ch_inlet.temperature[0].unfix() + + if self.config.MD_configuration_Type == MDconfigurationType.GMD: + for x in [self.gap_ch.length_domain.first()]: + self.gap_ch_inlet.temperature[0].unfix() if degrees_of_freedom(self) != 0: raise Exception( @@ -649,15 +1062,15 @@ def initialize_build( with idaeslog.solver_log(solve_log, idaeslog.DEBUG) as slc: res = opt.solve(self, tee=slc.tee) - init_log.info_high(f"Initialization Step 2 (No Flux) {idaeslog.condition(res)}") + init_log.info(f"Initialization Step 2 (No Flux) {idaeslog.condition(res)}") # Activate only the heat flux equations self.eq_flux_heat.activate() with idaeslog.solver_log(solve_log, idaeslog.DEBUG) as slc: res = opt.solve(self, tee=slc.tee) - init_log.info_high( - f"Initialization Step 3 (Heat Flux Only) {idaeslog.condition(res)}" + init_log.info( + f"Initialization Step 2 (heat Flux only) {idaeslog.condition(res)}" ) # Activate mass flux equations as well @@ -665,13 +1078,15 @@ def initialize_build( with idaeslog.solver_log(solve_log, idaeslog.DEBUG) as slc: res = opt.solve(self, tee=slc.tee) - init_log.info_high( + init_log.info( f"Initialization Step 4 (Heat and Mass Flux) {idaeslog.condition(res)}" ) # Release inlet state self.cold_ch.release_state(cold_ch_flags, outlvl) self.hot_ch.release_state(hot_ch_flags, outlvl) + if self.config.MD_configuration_Type == MDconfigurationType.GMD: + self.gap_ch.release_state(gap_ch_flags, outlvl) init_log.info(f"Initialization Complete: {idaeslog.condition(res)}") @@ -692,7 +1107,6 @@ def _get_stream_table_contents(self, time_point=0): def _get_performance_contents(self, time_point=0): var_dict = {} expr_dict = {} - var_dict["Mass Recovery Rate"] = self.recovery_mass[time_point] var_dict["Membrane Area"] = self.area if hasattr(self, "length"): @@ -701,8 +1115,6 @@ def _get_performance_contents(self, time_point=0): var_dict["Membrane Width"] = self.width expr_dict["Average Solute Flux"] = self.flux_mass_avg[time_point] - expr_dict["Thermal efficiency (%)"] = self.thermal_efficiency[time_point] - expr_dict["Effectiveness (%)"] = self.effectiveness[time_point] return {"vars": var_dict, "exprs": expr_dict} @@ -730,8 +1142,17 @@ def calculate_scaling_factors(self): if iscale.get_scaling_factor(self.membrane_thickness) is None: iscale.set_scaling_factor(self.membrane_thickness, 1e4) - if iscale.get_scaling_factor(self.membrane_thermal_conductivity) is None: - iscale.set_scaling_factor(self.membrane_thermal_conductivity, 10) + if hasattr(self, "membrane_thermal_conductivity"): + if iscale.get_scaling_factor(self.membrane_thermal_conductivity) is None: + iscale.set_scaling_factor(self.membrane_thermal_conductivity, 10) + + if hasattr(self, "gap_thermal_conductivity"): + if iscale.get_scaling_factor(self.gap_thermal_conductivity) is None: + iscale.set_scaling_factor(self.gap_thermal_conductivity, 10) + + if hasattr(self, "gap_thermal_conductivity"): + if iscale.get_scaling_factor(self.gap_thickness) is None: + iscale.set_scaling_factor(self.gap_thickness, 1e4) for (t, x), v in self.flux_mass.items(): if iscale.get_scaling_factor(v) is None: @@ -755,20 +1176,6 @@ def calculate_scaling_factors(self): self.hot_ch.properties_vapor[t, x].enth_mass_phase["Vap"] ) iscale.set_scaling_factor(v, sf_flux_enth) - - for (t, x), v in self.flux_enth_cold.items(): - if iscale.get_scaling_factor(v) is None: - sf_flux_enth = sf_flux * iscale.get_scaling_factor( - self.cold_ch.properties_vapor[t, x].enth_mass_phase["Vap"] - ) - iscale.set_scaling_factor(v, sf_flux_enth) - sf = iscale.get_scaling_factor( - self.cold_ch.properties_vapor[t, x].flow_mass_phase_comp["Vap", "H2O"] - ) - iscale.set_scaling_factor( - self.cold_ch.properties_vapor[t, x].flow_mass_phase_comp["Vap", "H2O"], - sf * 1000, - ) sf = iscale.get_scaling_factor( self.hot_ch.properties_vapor[t, x].flow_mass_phase_comp["Vap", "H2O"] ) @@ -777,16 +1184,74 @@ def calculate_scaling_factors(self): sf * 1000, ) - for (t, x), v in self.flux_conduction_heat.items(): + for (t, x), v in self.flux_enth_cold.items(): if iscale.get_scaling_factor(v) is None: - sf_flux_cond = ( - iscale.get_scaling_factor(self.membrane_thermal_conductivity) - / iscale.get_scaling_factor(self.membrane_thickness) - * iscale.get_scaling_factor( - self.hot_ch.properties_interface[t, x].temperature + if self.config.MD_configuration_Type == MDconfigurationType.VMD: + sf_flux_enth = sf_flux * iscale.get_scaling_factor( + self.cold_ch.properties[t, x].enth_mass_phase["Vap"] ) - ) - iscale.set_scaling_factor(v, sf_flux_cond) + iscale.set_scaling_factor(v, sf_flux_enth) + sf = iscale.get_scaling_factor( + self.cold_ch.properties[t, x].flow_mass_phase_comp["Vap", "H2O"] + ) + iscale.set_scaling_factor( + self.cold_ch.properties[t, x].flow_mass_phase_comp[ + "Vap", "H2O" + ], + sf * 50, + ) + + elif self.config.MD_configuration_Type == MDconfigurationType.DCMD: + sf_flux_enth = sf_flux * iscale.get_scaling_factor( + self.cold_ch.properties_vapor[t, x].enth_mass_phase["Vap"] + ) + iscale.set_scaling_factor(v, sf_flux_enth) + sf = iscale.get_scaling_factor( + self.cold_ch.properties_vapor[t, x].flow_mass_phase_comp[ + "Vap", "H2O" + ] + ) + iscale.set_scaling_factor( + self.cold_ch.properties_vapor[t, x].flow_mass_phase_comp[ + "Vap", "H2O" + ], + sf * 1000, + ) + + iscale.set_scaling_factor( + self.cold_ch.properties[t, x].pressure, + 1e-4, + ) + + if hasattr(self, "flux_conduction_heat"): + for (t, x), v in self.flux_conduction_heat.items(): + if iscale.get_scaling_factor(v) is None: + sf_flux_cond = ( + iscale.get_scaling_factor(self.membrane_thermal_conductivity) + / iscale.get_scaling_factor(self.membrane_thickness) + * iscale.get_scaling_factor( + self.hot_ch.properties_interface[t, x].temperature + ) + ) + iscale.set_scaling_factor(v, sf_flux_cond) + + if hasattr(self, "flux_conduction_heat_gap"): + for (t, x), v in self.flux_conduction_heat_gap.items(): + if iscale.get_scaling_factor(v) is None: + sf_flux_cond = ( + iscale.get_scaling_factor(self.membrane_thermal_conductivity) + / iscale.get_scaling_factor(self.membrane_thickness) + * iscale.get_scaling_factor( + self.hot_ch.properties_interface[t, x].temperature + ) + ) + iscale.set_scaling_factor(v, sf_flux_cond) + + if hasattr(self, "flux_expansion_heat"): + for (t, x), v in self.flux_expansion_heat.items(): + if iscale.get_scaling_factor(v) is None: + sf_flux_expansion = 1e-3 + iscale.set_scaling_factor(v, sf_flux_expansion) if hasattr(self, "length"): if iscale.get_scaling_factor(self.length) is None: diff --git a/watertap/unit_models/tests/test_membrane_distillation_0D.py b/watertap/unit_models/tests/test_membrane_distillation_0D.py index 3ff98dba3e..e331f37e17 100644 --- a/watertap/unit_models/tests/test_membrane_distillation_0D.py +++ b/watertap/unit_models/tests/test_membrane_distillation_0D.py @@ -31,6 +31,7 @@ PressureChangeType, ) +from watertap.unit_models.MD.membrane_distillation_base import MDconfigurationType from watertap.unit_models.tests.unit_test_harness import UnitTestHarness solver = get_solver() @@ -63,6 +64,7 @@ def build(): "pressure_change_type": PressureChangeType.fixed_per_stage, "flow_direction": FlowDirection.backward, }, + MD_configuration_Type=MDconfigurationType.DCMD, ) # fully specify system @@ -929,3 +931,521 @@ def configure(self): } return m + + +def build_temperature_polarization_none_vmd(): + m = ConcreteModel() + m.fs = FlowsheetBlock(dynamic=False) + m.fs.properties_hot_ch = props_sw.SeawaterParameterBlock() + m.fs.properties_cold_ch = props_w.WaterParameterBlock() + m.fs.properties_vapor = props_w.WaterParameterBlock() + m.fs.unit = MembraneDistillation0D( + MD_configuration_Type=MDconfigurationType.VMD, + hot_ch={ + "property_package": m.fs.properties_hot_ch, + "property_package_vapor": m.fs.properties_vapor, + "has_pressure_change": False, + "temperature_polarization_type": TemperaturePolarizationType.none, + "concentration_polarization_type": ConcentrationPolarizationType.none, + "mass_transfer_coefficient": MassTransferCoefficient.none, + "flow_direction": FlowDirection.forward, + }, + cold_ch={ + "property_package": m.fs.properties_cold_ch, + "temperature_polarization_type": TemperaturePolarizationType.none, + "has_pressure_change": False, + "flow_direction": FlowDirection.forward, + }, + ) + + # Fully specify the system + hot_ch_flow_mass = 1.0 + hot_ch_mass_frac_TDS = 0.035 + hot_ch_pressure = 7e5 + membrane_area = 1.0 + hot_ch_mass_frac_H2O = 1 - hot_ch_mass_frac_TDS + + m.fs.unit.hot_ch_inlet.flow_mass_phase_comp[0, "Liq", "TDS"].fix( + hot_ch_flow_mass * hot_ch_mass_frac_TDS + ) + m.fs.unit.hot_ch_inlet.flow_mass_phase_comp[0, "Liq", "H2O"].fix( + hot_ch_flow_mass * hot_ch_mass_frac_H2O + ) + m.fs.unit.hot_ch_inlet.pressure[0].fix(hot_ch_pressure) + m.fs.unit.hot_ch_inlet.temperature[0].fix(273.15 + 90) + m.fs.unit.area.fix(membrane_area) + m.fs.unit.permeability_coef.fix(1e-10) + m.fs.unit.membrane_thickness.fix(1e-4) + + m.fs.unit.cold_ch_inlet.flow_mass_phase_comp[0, "Vap", "H2O"].fix(0) + m.fs.unit.cold_ch_inlet.pressure[0].fix(10000) + + iscale.calculate_scaling_factors(m) + + return m + + +class TestMembraneDisillation0D_temperature_polarization_none_vmd(UnitTestHarness): + def configure(self): + m = build_temperature_polarization_none_vmd() + + self.unit_solutions[ + m.fs.unit.hot_ch_outlet.flow_mass_phase_comp[0, "Liq", "H2O"] + ] = 0.9285175510218185 + self.unit_solutions[m.fs.unit.hot_ch_outlet.temperature[0]] = 337.7047629447829 + self.unit_solutions[m.fs.unit.cold_ch_outlet.temperature[0]] = 337.7047629447829 + self.unit_solutions[m.fs.unit.flux_mass_avg[0]] = 0.03648244897818147 + + self.conservation_equality = { + "Check 1": { + "in": m.fs.unit.hot_ch_inlet.flow_mass_phase_comp[0, "Liq", "TDS"] + + m.fs.unit.hot_ch_inlet.flow_mass_phase_comp[0, "Liq", "H2O"] + + m.fs.unit.cold_ch_inlet.flow_mass_phase_comp[0, "Vap", "H2O"], + "out": m.fs.unit.hot_ch_outlet.flow_mass_phase_comp[0, "Liq", "TDS"] + + m.fs.unit.hot_ch_outlet.flow_mass_phase_comp[0, "Liq", "H2O"] + + m.fs.unit.cold_ch_outlet.flow_mass_phase_comp[0, "Vap", "H2O"], + }, + } + + return m + + +def build_temperature_polarization_fixed_hot_vmd(): + m = ConcreteModel() + m.fs = FlowsheetBlock(dynamic=False) + m.fs.properties_hot_ch = props_sw.SeawaterParameterBlock() + m.fs.properties_cold_ch = props_w.WaterParameterBlock() + m.fs.properties_vapor = props_w.WaterParameterBlock() + m.fs.unit = MembraneDistillation0D( + MD_configuration_Type=MDconfigurationType.VMD, + hot_ch={ + "property_package": m.fs.properties_hot_ch, + "property_package_vapor": m.fs.properties_vapor, + "has_pressure_change": False, + "temperature_polarization_type": TemperaturePolarizationType.fixed, + "concentration_polarization_type": ConcentrationPolarizationType.none, + "mass_transfer_coefficient": MassTransferCoefficient.none, + "flow_direction": FlowDirection.forward, + }, + cold_ch={ + "property_package": m.fs.properties_cold_ch, + "temperature_polarization_type": TemperaturePolarizationType.none, + "has_pressure_change": False, + "flow_direction": FlowDirection.forward, + }, + ) + + # Fully specify the system + hot_ch_flow_mass = 1.0 + hot_ch_mass_frac_TDS = 0.035 + hot_ch_pressure = 7e5 + membrane_area = 1.0 + hot_ch_mass_frac_H2O = 1 - hot_ch_mass_frac_TDS + + m.fs.unit.hot_ch_inlet.flow_mass_phase_comp[0, "Liq", "TDS"].fix( + hot_ch_flow_mass * hot_ch_mass_frac_TDS + ) + m.fs.unit.hot_ch_inlet.flow_mass_phase_comp[0, "Liq", "H2O"].fix( + hot_ch_flow_mass * hot_ch_mass_frac_H2O + ) + m.fs.unit.hot_ch_inlet.pressure[0].fix(hot_ch_pressure) + m.fs.unit.hot_ch_inlet.temperature[0].fix(273.15 + 90) + m.fs.unit.area.fix(membrane_area) + m.fs.unit.permeability_coef.fix(1e-10) + m.fs.unit.membrane_thickness.fix(1e-4) + + m.fs.unit.cold_ch_inlet.flow_mass_phase_comp[0, "Vap", "H2O"].fix(0) + m.fs.unit.cold_ch_inlet.pressure[0].fix(10000) + m.fs.unit.hot_ch.h_conv.fix(2400) + + iscale.calculate_scaling_factors(m) + + return m + + +class TestMembraneDisillation0D_temperature_polarization_fixed_hot_vmd(UnitTestHarness): + def configure(self): + m = build_temperature_polarization_fixed_hot_vmd() + + self.unit_solutions[ + m.fs.unit.hot_ch_outlet.flow_mass_phase_comp[0, "Liq", "H2O"] + ] = 0.9479100277176233 + self.unit_solutions[m.fs.unit.hot_ch_outlet.temperature[0]] = 352.1551430126428 + self.unit_solutions[m.fs.unit.cold_ch_outlet.temperature[0]] = 337.7492369004734 + self.unit_solutions[m.fs.unit.flux_mass_avg[0]] = 0.01708997228237665 + + self.conservation_equality = { + "Check 1": { + "in": m.fs.unit.hot_ch_inlet.flow_mass_phase_comp[0, "Liq", "TDS"] + + m.fs.unit.hot_ch_inlet.flow_mass_phase_comp[0, "Liq", "H2O"] + + m.fs.unit.cold_ch_inlet.flow_mass_phase_comp[0, "Vap", "H2O"], + "out": m.fs.unit.hot_ch_outlet.flow_mass_phase_comp[0, "Liq", "TDS"] + + m.fs.unit.hot_ch_outlet.flow_mass_phase_comp[0, "Liq", "H2O"] + + m.fs.unit.cold_ch_outlet.flow_mass_phase_comp[0, "Vap", "H2O"], + }, + } + + return m + + +def build_temperature_polarization_calculated_hot_vmd(): + m = ConcreteModel() + m.fs = FlowsheetBlock(dynamic=False) + m.fs.properties_hot_ch = props_sw.SeawaterParameterBlock() + m.fs.properties_cold_ch = props_w.WaterParameterBlock() + m.fs.properties_vapor = props_w.WaterParameterBlock() + m.fs.unit = MembraneDistillation0D( + MD_configuration_Type=MDconfigurationType.VMD, + hot_ch={ + "property_package": m.fs.properties_hot_ch, + "property_package_vapor": m.fs.properties_vapor, + "has_pressure_change": False, + "temperature_polarization_type": TemperaturePolarizationType.calculated, + "concentration_polarization_type": ConcentrationPolarizationType.none, + "mass_transfer_coefficient": MassTransferCoefficient.none, + "flow_direction": FlowDirection.forward, + }, + cold_ch={ + "property_package": m.fs.properties_cold_ch, + "temperature_polarization_type": TemperaturePolarizationType.none, + "has_pressure_change": False, + "flow_direction": FlowDirection.forward, + }, + ) + + # Fully specify the system + hot_ch_flow_mass = 1.0 + hot_ch_mass_frac_TDS = 0.035 + hot_ch_pressure = 7e5 + membrane_area = 1.0 + hot_ch_mass_frac_H2O = 1 - hot_ch_mass_frac_TDS + + m.fs.unit.hot_ch_inlet.flow_mass_phase_comp[0, "Liq", "TDS"].fix( + hot_ch_flow_mass * hot_ch_mass_frac_TDS + ) + m.fs.unit.hot_ch_inlet.flow_mass_phase_comp[0, "Liq", "H2O"].fix( + hot_ch_flow_mass * hot_ch_mass_frac_H2O + ) + m.fs.unit.hot_ch_inlet.pressure[0].fix(hot_ch_pressure) + m.fs.unit.hot_ch_inlet.temperature[0].fix(273.15 + 90) + m.fs.unit.area.fix(membrane_area) + m.fs.unit.permeability_coef.fix(1e-10) + m.fs.unit.membrane_thickness.fix(1e-4) + m.fs.unit.length.fix(1) + + m.fs.unit.cold_ch_inlet.flow_mass_phase_comp[0, "Vap", "H2O"].fix(0) + m.fs.unit.cold_ch_inlet.pressure[0].fix(10000) + + m.fs.unit.hot_ch.channel_height.fix(0.0019) + m.fs.unit.hot_ch.spacer_porosity.fix(0.77) + + iscale.calculate_scaling_factors(m) + + return m + + +class TestMembraneDisillation0D_temperature_polarization_calculated_hot_vmd( + UnitTestHarness +): + def configure(self): + m = build_temperature_polarization_calculated_hot_vmd() + + self.unit_solutions[ + m.fs.unit.hot_ch_outlet.flow_mass_phase_comp[0, "Liq", "H2O"] + ] = 0.9348449235681139 + self.unit_solutions[m.fs.unit.hot_ch_outlet.temperature[0]] = 342.68302106371556 + self.unit_solutions[m.fs.unit.cold_ch_outlet.temperature[0]] = 339.9911385947164 + self.unit_solutions[m.fs.unit.flux_mass_avg[0]] = 0.03015507643188607 + + self.conservation_equality = { + "Check 1": { + "in": m.fs.unit.hot_ch_inlet.flow_mass_phase_comp[0, "Liq", "TDS"] + + m.fs.unit.hot_ch_inlet.flow_mass_phase_comp[0, "Liq", "H2O"] + + m.fs.unit.cold_ch_inlet.flow_mass_phase_comp[0, "Vap", "H2O"], + "out": m.fs.unit.hot_ch_outlet.flow_mass_phase_comp[0, "Liq", "TDS"] + + m.fs.unit.hot_ch_outlet.flow_mass_phase_comp[0, "Liq", "H2O"] + + m.fs.unit.cold_ch_outlet.flow_mass_phase_comp[0, "Vap", "H2O"], + }, + } + + return m + + +def build_temperature_polarization_concentration_polarization_calculated_hot_vmd(): + m = ConcreteModel() + m.fs = FlowsheetBlock(dynamic=False) + m.fs.properties_hot_ch = props_sw.SeawaterParameterBlock() + m.fs.properties_cold_ch = props_w.WaterParameterBlock() + m.fs.properties_vapor = props_w.WaterParameterBlock() + m.fs.unit = MembraneDistillation0D( + MD_configuration_Type=MDconfigurationType.VMD, + hot_ch={ + "property_package": m.fs.properties_hot_ch, + "property_package_vapor": m.fs.properties_vapor, + "has_pressure_change": False, + "temperature_polarization_type": TemperaturePolarizationType.calculated, + "concentration_polarization_type": ConcentrationPolarizationType.calculated, + "mass_transfer_coefficient": MassTransferCoefficient.calculated, + "flow_direction": FlowDirection.forward, + }, + cold_ch={ + "property_package": m.fs.properties_cold_ch, + "temperature_polarization_type": TemperaturePolarizationType.none, + "has_pressure_change": False, + "flow_direction": FlowDirection.forward, + }, + ) + + # Fully specify the system + hot_ch_flow_mass = 1.0 + hot_ch_mass_frac_TDS = 0.035 + hot_ch_pressure = 7e5 + membrane_area = 1.0 + hot_ch_mass_frac_H2O = 1 - hot_ch_mass_frac_TDS + + m.fs.unit.hot_ch_inlet.flow_mass_phase_comp[0, "Liq", "TDS"].fix( + hot_ch_flow_mass * hot_ch_mass_frac_TDS + ) + m.fs.unit.hot_ch_inlet.flow_mass_phase_comp[0, "Liq", "H2O"].fix( + hot_ch_flow_mass * hot_ch_mass_frac_H2O + ) + m.fs.unit.hot_ch_inlet.pressure[0].fix(hot_ch_pressure) + m.fs.unit.hot_ch_inlet.temperature[0].fix(273.15 + 90) + m.fs.unit.area.fix(membrane_area) + m.fs.unit.permeability_coef.fix(1e-10) + m.fs.unit.membrane_thickness.fix(1e-4) + m.fs.unit.length.fix(1) + + m.fs.unit.cold_ch_inlet.flow_mass_phase_comp[0, "Vap", "H2O"].fix(0) + m.fs.unit.cold_ch_inlet.pressure[0].fix(10000) + + m.fs.unit.hot_ch.channel_height.fix(0.0019) + m.fs.unit.hot_ch.spacer_porosity.fix(0.77) + + iscale.calculate_scaling_factors(m) + + return m + + +class TestMembraneDisillation0D_temperature_polarization_concentration_polarization_calculated_hot_vmd( + UnitTestHarness +): + def configure(self): + m = ( + build_temperature_polarization_concentration_polarization_calculated_hot_vmd() + ) + + self.unit_solutions[ + m.fs.unit.hot_ch_outlet.flow_mass_phase_comp[0, "Liq", "H2O"] + ] = 0.9348449235681139 + self.unit_solutions[m.fs.unit.hot_ch_outlet.temperature[0]] = 342.68302106371556 + self.unit_solutions[m.fs.unit.cold_ch_outlet.temperature[0]] = 339.9911385947164 + self.unit_solutions[m.fs.unit.flux_mass_avg[0]] = 0.03004295222746866 + + self.conservation_equality = { + "Check 1": { + "in": m.fs.unit.hot_ch_inlet.flow_mass_phase_comp[0, "Liq", "TDS"] + + m.fs.unit.hot_ch_inlet.flow_mass_phase_comp[0, "Liq", "H2O"] + + m.fs.unit.cold_ch_inlet.flow_mass_phase_comp[0, "Vap", "H2O"], + "out": m.fs.unit.hot_ch_outlet.flow_mass_phase_comp[0, "Liq", "TDS"] + + m.fs.unit.hot_ch_outlet.flow_mass_phase_comp[0, "Liq", "H2O"] + + m.fs.unit.cold_ch_outlet.flow_mass_phase_comp[0, "Vap", "H2O"], + }, + } + + return m + + +def build_temperature_polarization_concentration_polarization_pressure_calculated_hot_vmd(): + m = ConcreteModel() + m.fs = FlowsheetBlock(dynamic=False) + m.fs.properties_hot_ch = props_sw.SeawaterParameterBlock() + m.fs.properties_cold_ch = props_w.WaterParameterBlock() + m.fs.properties_vapor = props_w.WaterParameterBlock() + m.fs.unit = MembraneDistillation0D( + MD_configuration_Type=MDconfigurationType.VMD, + hot_ch={ + "property_package": m.fs.properties_hot_ch, + "property_package_vapor": m.fs.properties_vapor, + "has_pressure_change": True, + "pressure_change_type": PressureChangeType.calculated, + "temperature_polarization_type": TemperaturePolarizationType.calculated, + "concentration_polarization_type": ConcentrationPolarizationType.calculated, + "mass_transfer_coefficient": MassTransferCoefficient.calculated, + "flow_direction": FlowDirection.forward, + }, + cold_ch={ + "property_package": m.fs.properties_cold_ch, + "temperature_polarization_type": TemperaturePolarizationType.none, + "has_pressure_change": False, + "flow_direction": FlowDirection.forward, + }, + ) + + # Fully specify the system + hot_ch_flow_mass = 1.0 + hot_ch_mass_frac_TDS = 0.035 + hot_ch_pressure = 7e5 + membrane_area = 1.0 + hot_ch_mass_frac_H2O = 1 - hot_ch_mass_frac_TDS + + m.fs.unit.hot_ch_inlet.flow_mass_phase_comp[0, "Liq", "TDS"].fix( + hot_ch_flow_mass * hot_ch_mass_frac_TDS + ) + m.fs.unit.hot_ch_inlet.flow_mass_phase_comp[0, "Liq", "H2O"].fix( + hot_ch_flow_mass * hot_ch_mass_frac_H2O + ) + m.fs.unit.hot_ch_inlet.pressure[0].fix(hot_ch_pressure) + m.fs.unit.hot_ch_inlet.temperature[0].fix(273.15 + 90) + m.fs.unit.area.fix(membrane_area) + m.fs.unit.permeability_coef.fix(1e-10) + m.fs.unit.membrane_thickness.fix(1e-4) + m.fs.unit.length.fix(1) + + m.fs.unit.cold_ch_inlet.flow_mass_phase_comp[0, "Vap", "H2O"].fix(0) + m.fs.unit.cold_ch_inlet.pressure[0].fix(10000) + + m.fs.unit.hot_ch.channel_height.fix(0.0019) + m.fs.unit.hot_ch.spacer_porosity.fix(0.77) + + iscale.calculate_scaling_factors(m) + + return m + + +class TestMembraneDisillation0D_temperature_polarization_concentration_polarization_pressure_calculated_hot_vmd( + UnitTestHarness +): + def configure(self): + m = ( + build_temperature_polarization_concentration_polarization_pressure_calculated_hot_vmd() + ) + + self.unit_solutions[ + m.fs.unit.hot_ch_outlet.flow_mass_phase_comp[0, "Liq", "H2O"] + ] = 0.9348449235681139 + self.unit_solutions[m.fs.unit.hot_ch_outlet.temperature[0]] = 342.68302106371556 + self.unit_solutions[m.fs.unit.cold_ch_outlet.temperature[0]] = 339.9911385947164 + self.unit_solutions[m.fs.unit.flux_mass_avg[0]] = 0.03004295222746866 + self.unit_solutions[m.fs.unit.hot_ch.deltaP[0]] = -73462.05019358391 + + self.conservation_equality = { + "Check 1": { + "in": m.fs.unit.hot_ch_inlet.flow_mass_phase_comp[0, "Liq", "TDS"] + + m.fs.unit.hot_ch_inlet.flow_mass_phase_comp[0, "Liq", "H2O"] + + m.fs.unit.cold_ch_inlet.flow_mass_phase_comp[0, "Vap", "H2O"], + "out": m.fs.unit.hot_ch_outlet.flow_mass_phase_comp[0, "Liq", "TDS"] + + m.fs.unit.hot_ch_outlet.flow_mass_phase_comp[0, "Liq", "H2O"] + + m.fs.unit.cold_ch_outlet.flow_mass_phase_comp[0, "Vap", "H2O"], + }, + } + + return m + + +def build_temperature_polarization_none_pgmd(): + m = ConcreteModel() + m.fs = FlowsheetBlock(dynamic=False) + m.fs.properties_hot_ch = props_sw.SeawaterParameterBlock() + m.fs.properties_cold_ch = props_w.WaterParameterBlock() + m.fs.properties_vapor = props_w.WaterParameterBlock() + m.fs.unit = MembraneDistillation0D( + MD_configuration_Type=MDconfigurationType.GMD, + hot_ch={ + "property_package": m.fs.properties_hot_ch, + "property_package_vapor": m.fs.properties_vapor, + "has_pressure_change": False, + "temperature_polarization_type": TemperaturePolarizationType.none, + "concentration_polarization_type": ConcentrationPolarizationType.none, + "mass_transfer_coefficient": MassTransferCoefficient.none, + "flow_direction": FlowDirection.forward, + }, + cold_ch={ + "property_package": m.fs.properties_hot_ch, + "temperature_polarization_type": TemperaturePolarizationType.none, + "concentration_polarization_type": ConcentrationPolarizationType.none, + "mass_transfer_coefficient": MassTransferCoefficient.none, + "has_pressure_change": False, + "flow_direction": FlowDirection.backward, + }, + gap_ch={ + "property_package": m.fs.properties_cold_ch, + "temperature_polarization_type": TemperaturePolarizationType.fixed, + "has_pressure_change": False, + "flow_direction": FlowDirection.forward, + }, + ) + + # Fully specify the system + hot_ch_flow_mass = 1.0 + hot_ch_mass_frac_TDS = 0.035 + hot_ch_pressure = 7e5 + membrane_area = 3.0 + hot_ch_mass_frac_H2O = 1 - hot_ch_mass_frac_TDS + + m.fs.unit.hot_ch_inlet.flow_mass_phase_comp[0, "Liq", "TDS"].fix( + hot_ch_flow_mass * hot_ch_mass_frac_TDS + ) + m.fs.unit.hot_ch_inlet.flow_mass_phase_comp[0, "Liq", "H2O"].fix( + hot_ch_flow_mass * hot_ch_mass_frac_H2O + ) + m.fs.unit.hot_ch_inlet.pressure[0].fix(hot_ch_pressure) + m.fs.unit.hot_ch_inlet.temperature[0].fix(273.15 + 90) + m.fs.unit.area.fix(membrane_area) + m.fs.unit.permeability_coef.fix(1e-10) + m.fs.unit.membrane_thickness.fix(1e-4) + m.fs.unit.gap_thickness.fix(0.0004) + m.fs.unit.membrane_thermal_conductivity.fix(0.2) + m.fs.unit.gap_thermal_conductivity.fix(0.06) + + m.fs.unit.cold_ch_inlet.flow_mass_phase_comp[0, "Liq", "H2O"].fix( + hot_ch_flow_mass * hot_ch_mass_frac_H2O + ) + m.fs.unit.cold_ch_inlet.flow_mass_phase_comp[0, "Liq", "TDS"].fix( + hot_ch_flow_mass * hot_ch_mass_frac_TDS + ) + m.fs.unit.cold_ch_inlet.temperature[0].fix(273.15 + 25) + m.fs.unit.cold_ch_inlet.pressure[0].fix(100000) + + m.fs.unit.gap_ch_inlet.flow_mass_phase_comp[0, "Liq", "H2O"].fix(1e-8) + m.fs.unit.gap_ch_inlet.flow_mass_phase_comp[0, "Vap", "H2O"].fix(0) + m.fs.unit.gap_ch_inlet.temperature[0].set_value(273.15 + 30) + m.fs.unit.gap_ch_inlet.pressure[0].fix(100000) + + iscale.calculate_scaling_factors(m) + + return m + + +class TestMembraneDisillation0D_temperature_polarization_none_pgmd(UnitTestHarness): + def configure(self): + m = build_temperature_polarization_none_pgmd() + + self.unit_solutions[ + m.fs.unit.hot_ch_outlet.flow_mass_phase_comp[0, "Liq", "H2O"] + ] = 0.9580840582054698 + self.unit_solutions[m.fs.unit.hot_ch_outlet.temperature[0]] = 356.6334998833635 + self.unit_solutions[m.fs.unit.cold_ch_outlet.temperature[0]] = ( + 304.69178989433595 + ) + self.unit_solutions[m.fs.unit.flux_mass_avg[0]] = 0.0023053139451937005 + self.unit_solutions[m.fs.unit.gap_ch_outlet.temperature[0]] = 326.62398751842414 + + self.conservation_equality = { + "Check 1": { + "in": m.fs.unit.hot_ch_inlet.flow_mass_phase_comp[0, "Liq", "TDS"] + + m.fs.unit.hot_ch_inlet.flow_mass_phase_comp[0, "Liq", "H2O"] + + m.fs.unit.cold_ch_inlet.flow_mass_phase_comp[0, "Liq", "TDS"] + + m.fs.unit.cold_ch_inlet.flow_mass_phase_comp[0, "Liq", "H2O"] + + m.fs.unit.gap_ch_inlet.flow_mass_phase_comp[0, "Liq", "H2O"] + + m.fs.unit.gap_ch_inlet.flow_mass_phase_comp[0, "Vap", "H2O"], + "out": m.fs.unit.hot_ch_outlet.flow_mass_phase_comp[0, "Liq", "TDS"] + + m.fs.unit.hot_ch_outlet.flow_mass_phase_comp[0, "Liq", "H2O"] + + m.fs.unit.cold_ch_outlet.flow_mass_phase_comp[0, "Liq", "TDS"] + + m.fs.unit.cold_ch_outlet.flow_mass_phase_comp[0, "Liq", "H2O"] + + m.fs.unit.gap_ch_outlet.flow_mass_phase_comp[0, "Liq", "H2O"] + + m.fs.unit.gap_ch_outlet.flow_mass_phase_comp[0, "Vap", "H2O"], + }, + } + + return m diff --git a/watertap/unit_models/tests/test_membrane_distillation_1D.py b/watertap/unit_models/tests/test_membrane_distillation_1D.py index 15c1aababd..0672ae6e64 100644 --- a/watertap/unit_models/tests/test_membrane_distillation_1D.py +++ b/watertap/unit_models/tests/test_membrane_distillation_1D.py @@ -30,6 +30,7 @@ MassTransferCoefficient, PressureChangeType, ) +from watertap.unit_models.MD.membrane_distillation_base import MDconfigurationType from watertap.unit_models.tests.unit_test_harness import UnitTestHarness @@ -952,3 +953,662 @@ def configure(self): } return m + + +def build_temperature_polarization_none_vmd(): + m = ConcreteModel() + m.fs = FlowsheetBlock(dynamic=False) + m.fs.properties_hot_ch = props_sw.SeawaterParameterBlock() + m.fs.properties_cold_ch = props_w.WaterParameterBlock() + m.fs.properties_vapor = props_w.WaterParameterBlock() + m.fs.unit = MembraneDistillation1D( + MD_configuration_Type=MDconfigurationType.VMD, + hot_ch={ + "property_package": m.fs.properties_hot_ch, + "property_package_vapor": m.fs.properties_vapor, + "has_pressure_change": False, + "temperature_polarization_type": TemperaturePolarizationType.none, + "concentration_polarization_type": ConcentrationPolarizationType.none, + "mass_transfer_coefficient": MassTransferCoefficient.none, + "flow_direction": FlowDirection.forward, + }, + cold_ch={ + "property_package": m.fs.properties_cold_ch, + "temperature_polarization_type": TemperaturePolarizationType.none, + "has_pressure_change": False, + "flow_direction": FlowDirection.forward, + }, + ) + + m.fs.unit.length.fix(0.1) + + # Fully specify the system + hot_ch_flow_mass = 1.0 + hot_ch_mass_frac_TDS = 0.035 + hot_ch_pressure = 7e5 + membrane_area = 1.0 + hot_ch_mass_frac_H2O = 1 - hot_ch_mass_frac_TDS + + m.fs.unit.hot_ch_inlet.flow_mass_phase_comp[0, "Liq", "TDS"].fix( + hot_ch_flow_mass * hot_ch_mass_frac_TDS + ) + m.fs.unit.hot_ch_inlet.flow_mass_phase_comp[0, "Liq", "H2O"].fix( + hot_ch_flow_mass * hot_ch_mass_frac_H2O + ) + m.fs.unit.hot_ch_inlet.pressure[0].fix(hot_ch_pressure) + m.fs.unit.hot_ch_inlet.temperature[0].fix(273.15 + 90) + m.fs.unit.area.fix(membrane_area) + m.fs.unit.permeability_coef.fix(1e-10) + m.fs.unit.membrane_thickness.fix(1e-4) + + m.fs.unit.cold_ch_inlet.flow_mass_phase_comp[0, "Vap", "H2O"].fix(0) + m.fs.unit.cold_ch_inlet.pressure[0].fix(10000) + + iscale.calculate_scaling_factors(m) + + return m + + +class TestMembraneDisillation1D_temperature_polarization_none_vmd(UnitTestHarness): + def configure(self): + m = build_temperature_polarization_none_vmd() + + self.unit_solutions[ + m.fs.unit.hot_ch_outlet.flow_mass_phase_comp[0, "Liq", "H2O"] + ] = 0.9333282339579946 + self.unit_solutions[m.fs.unit.hot_ch_outlet.temperature[0]] = 341.6477118118322 + self.unit_solutions[m.fs.unit.cold_ch_outlet.temperature[0]] = 341.6477118118322 + self.unit_solutions[m.fs.unit.flux_mass_avg[0]] = 0.03167176604200531 + + self.conservation_equality = { + "Check 1": { + "in": m.fs.unit.hot_ch_inlet.flow_mass_phase_comp[0, "Liq", "TDS"] + + m.fs.unit.hot_ch_inlet.flow_mass_phase_comp[0, "Liq", "H2O"] + + m.fs.unit.cold_ch_inlet.flow_mass_phase_comp[0, "Vap", "H2O"], + "out": m.fs.unit.hot_ch_outlet.flow_mass_phase_comp[0, "Liq", "TDS"] + + m.fs.unit.hot_ch_outlet.flow_mass_phase_comp[0, "Liq", "H2O"] + + m.fs.unit.cold_ch_outlet.flow_mass_phase_comp[0, "Vap", "H2O"], + }, + } + + return m + + +def build_temperature_polarization_fixed_hot_vmd(): + m = ConcreteModel() + m.fs = FlowsheetBlock(dynamic=False) + m.fs.properties_hot_ch = props_sw.SeawaterParameterBlock() + m.fs.properties_cold_ch = props_w.WaterParameterBlock() + m.fs.properties_vapor = props_w.WaterParameterBlock() + m.fs.unit = MembraneDistillation1D( + MD_configuration_Type=MDconfigurationType.VMD, + hot_ch={ + "property_package": m.fs.properties_hot_ch, + "property_package_vapor": m.fs.properties_vapor, + "has_pressure_change": False, + "temperature_polarization_type": TemperaturePolarizationType.fixed, + "concentration_polarization_type": ConcentrationPolarizationType.none, + "mass_transfer_coefficient": MassTransferCoefficient.none, + "flow_direction": FlowDirection.forward, + }, + cold_ch={ + "property_package": m.fs.properties_cold_ch, + "temperature_polarization_type": TemperaturePolarizationType.none, + "has_pressure_change": False, + "flow_direction": FlowDirection.forward, + }, + ) + + m.fs.unit.length.fix(0.1) + + # Fully specify the system + hot_ch_flow_mass = 1.0 + hot_ch_mass_frac_TDS = 0.035 + hot_ch_pressure = 7e5 + membrane_area = 1.0 + hot_ch_mass_frac_H2O = 1 - hot_ch_mass_frac_TDS + + m.fs.unit.hot_ch_inlet.flow_mass_phase_comp[0, "Liq", "TDS"].fix( + hot_ch_flow_mass * hot_ch_mass_frac_TDS + ) + m.fs.unit.hot_ch_inlet.flow_mass_phase_comp[0, "Liq", "H2O"].fix( + hot_ch_flow_mass * hot_ch_mass_frac_H2O + ) + m.fs.unit.hot_ch_inlet.pressure[0].fix(hot_ch_pressure) + m.fs.unit.hot_ch_inlet.temperature[0].fix(273.15 + 90) + m.fs.unit.area.fix(membrane_area) + m.fs.unit.permeability_coef.fix(1e-10) + m.fs.unit.membrane_thickness.fix(1e-4) + + m.fs.unit.cold_ch_inlet.flow_mass_phase_comp[0, "Vap", "H2O"].fix(0) + m.fs.unit.cold_ch_inlet.pressure[0].fix(10000) + m.fs.unit.hot_ch.h_conv.fix(2400) + + iscale.calculate_scaling_factors(m) + + return m + + +class TestMembraneDisillation1D_temperature_polarization_fixed_hot_vmd(UnitTestHarness): + def configure(self): + m = build_temperature_polarization_fixed_hot_vmd() + + self.unit_solutions[ + m.fs.unit.hot_ch_outlet.flow_mass_phase_comp[0, "Liq", "H2O"] + ] = 0.9482002512081292 + self.unit_solutions[m.fs.unit.hot_ch_outlet.temperature[0]] = 352.36321353466815 + self.unit_solutions[m.fs.unit.cold_ch_outlet.temperature[0]] = ( + 337.84769812257065 + ) + self.unit_solutions[m.fs.unit.flux_mass_avg[0]] = 0.016799748791871325 + + self.conservation_equality = { + "Check 1": { + "in": m.fs.unit.hot_ch_inlet.flow_mass_phase_comp[0, "Liq", "TDS"] + + m.fs.unit.hot_ch_inlet.flow_mass_phase_comp[0, "Liq", "H2O"] + + m.fs.unit.cold_ch_inlet.flow_mass_phase_comp[0, "Vap", "H2O"], + "out": m.fs.unit.hot_ch_outlet.flow_mass_phase_comp[0, "Liq", "TDS"] + + m.fs.unit.hot_ch_outlet.flow_mass_phase_comp[0, "Liq", "H2O"] + + m.fs.unit.cold_ch_outlet.flow_mass_phase_comp[0, "Vap", "H2O"], + }, + } + + return m + + +def build_temperature_polarization_calculated_hot_vmd(): + m = ConcreteModel() + m.fs = FlowsheetBlock(dynamic=False) + m.fs.properties_hot_ch = props_sw.SeawaterParameterBlock() + m.fs.properties_cold_ch = props_w.WaterParameterBlock() + m.fs.properties_vapor = props_w.WaterParameterBlock() + m.fs.unit = MembraneDistillation1D( + MD_configuration_Type=MDconfigurationType.VMD, + hot_ch={ + "property_package": m.fs.properties_hot_ch, + "property_package_vapor": m.fs.properties_vapor, + "has_pressure_change": False, + "temperature_polarization_type": TemperaturePolarizationType.calculated, + "concentration_polarization_type": ConcentrationPolarizationType.none, + "mass_transfer_coefficient": MassTransferCoefficient.none, + "flow_direction": FlowDirection.forward, + }, + cold_ch={ + "property_package": m.fs.properties_cold_ch, + "temperature_polarization_type": TemperaturePolarizationType.none, + "has_pressure_change": False, + "flow_direction": FlowDirection.forward, + }, + ) + + m.fs.unit.length.fix(0.1) + + # Fully specify the system + hot_ch_flow_mass = 1.0 + hot_ch_mass_frac_TDS = 0.035 + hot_ch_pressure = 7e5 + membrane_area = 1.0 + hot_ch_mass_frac_H2O = 1 - hot_ch_mass_frac_TDS + + m.fs.unit.hot_ch_inlet.flow_mass_phase_comp[0, "Liq", "TDS"].fix( + hot_ch_flow_mass * hot_ch_mass_frac_TDS + ) + m.fs.unit.hot_ch_inlet.flow_mass_phase_comp[0, "Liq", "H2O"].fix( + hot_ch_flow_mass * hot_ch_mass_frac_H2O + ) + m.fs.unit.hot_ch_inlet.pressure[0].fix(hot_ch_pressure) + m.fs.unit.hot_ch_inlet.temperature[0].fix(273.15 + 90) + m.fs.unit.area.fix(membrane_area) + m.fs.unit.permeability_coef.fix(1e-10) + m.fs.unit.membrane_thickness.fix(1e-4) + + m.fs.unit.cold_ch_inlet.flow_mass_phase_comp[0, "Vap", "H2O"].fix(0) + m.fs.unit.cold_ch_inlet.pressure[0].fix(10000) + + m.fs.unit.hot_ch.channel_height.fix(0.0019) + m.fs.unit.hot_ch.spacer_porosity.fix(0.77) + + iscale.calculate_scaling_factors(m) + + return m + + +class TestMembraneDisillation1D_temperature_polarization_calculated_hot_vmd( + UnitTestHarness +): + def configure(self): + m = build_temperature_polarization_calculated_hot_vmd() + + self.unit_solutions[ + m.fs.unit.hot_ch_outlet.flow_mass_phase_comp[0, "Liq", "H2O"] + ] = 0.9450790917786648 + self.unit_solutions[m.fs.unit.hot_ch_outlet.temperature[0]] = 350.19642692723755 + self.unit_solutions[m.fs.unit.cold_ch_outlet.temperature[0]] = 339.2750084657155 + self.unit_solutions[m.fs.unit.flux_mass_avg[0]] = 0.019920908221335205 + + self.conservation_equality = { + "Check 1": { + "in": m.fs.unit.hot_ch_inlet.flow_mass_phase_comp[0, "Liq", "TDS"] + + m.fs.unit.hot_ch_inlet.flow_mass_phase_comp[0, "Liq", "H2O"] + + m.fs.unit.cold_ch_inlet.flow_mass_phase_comp[0, "Vap", "H2O"], + "out": m.fs.unit.hot_ch_outlet.flow_mass_phase_comp[0, "Liq", "TDS"] + + m.fs.unit.hot_ch_outlet.flow_mass_phase_comp[0, "Liq", "H2O"] + + m.fs.unit.cold_ch_outlet.flow_mass_phase_comp[0, "Vap", "H2O"], + }, + } + + return m + + +def build_temperature_polarization_concentration_polarization_calculated_hot_vmd(): + m = ConcreteModel() + m.fs = FlowsheetBlock(dynamic=False) + m.fs.properties_hot_ch = props_sw.SeawaterParameterBlock() + m.fs.properties_cold_ch = props_w.WaterParameterBlock() + m.fs.properties_vapor = props_w.WaterParameterBlock() + m.fs.unit = MembraneDistillation1D( + MD_configuration_Type=MDconfigurationType.VMD, + hot_ch={ + "property_package": m.fs.properties_hot_ch, + "property_package_vapor": m.fs.properties_vapor, + "has_pressure_change": False, + "temperature_polarization_type": TemperaturePolarizationType.calculated, + "concentration_polarization_type": ConcentrationPolarizationType.calculated, + "mass_transfer_coefficient": MassTransferCoefficient.calculated, + "flow_direction": FlowDirection.forward, + }, + cold_ch={ + "property_package": m.fs.properties_cold_ch, + "temperature_polarization_type": TemperaturePolarizationType.none, + "has_pressure_change": False, + "flow_direction": FlowDirection.forward, + }, + ) + + m.fs.unit.length.fix(0.1) + + # Fully specify the system + hot_ch_flow_mass = 1.0 + hot_ch_mass_frac_TDS = 0.035 + hot_ch_pressure = 7e5 + membrane_area = 1.0 + hot_ch_mass_frac_H2O = 1 - hot_ch_mass_frac_TDS + + m.fs.unit.hot_ch_inlet.flow_mass_phase_comp[0, "Liq", "TDS"].fix( + hot_ch_flow_mass * hot_ch_mass_frac_TDS + ) + m.fs.unit.hot_ch_inlet.flow_mass_phase_comp[0, "Liq", "H2O"].fix( + hot_ch_flow_mass * hot_ch_mass_frac_H2O + ) + m.fs.unit.hot_ch_inlet.pressure[0].fix(hot_ch_pressure) + m.fs.unit.hot_ch_inlet.temperature[0].fix(273.15 + 90) + m.fs.unit.area.fix(membrane_area) + m.fs.unit.permeability_coef.fix(1e-10) + m.fs.unit.membrane_thickness.fix(1e-4) + + m.fs.unit.cold_ch_inlet.flow_mass_phase_comp[0, "Vap", "H2O"].fix(0) + m.fs.unit.cold_ch_inlet.pressure[0].fix(10000) + + m.fs.unit.hot_ch.channel_height.fix(0.0019) + m.fs.unit.hot_ch.spacer_porosity.fix(0.77) + + iscale.calculate_scaling_factors(m) + + return m + + +class TestMembraneDisillation1D_temperature_polarization_concentration_polarization_calculated_hot_vmd( + UnitTestHarness +): + def configure(self): + m = ( + build_temperature_polarization_concentration_polarization_calculated_hot_vmd() + ) + + self.unit_solutions[ + m.fs.unit.hot_ch_outlet.flow_mass_phase_comp[0, "Liq", "H2O"] + ] = 0.9450790917786648 + self.unit_solutions[m.fs.unit.hot_ch_outlet.temperature[0]] = 350.19642692723755 + self.unit_solutions[m.fs.unit.cold_ch_outlet.temperature[0]] = 339.2750084657155 + self.unit_solutions[m.fs.unit.flux_mass_avg[0]] = 0.019769720894804336 + + self.conservation_equality = { + "Check 1": { + "in": m.fs.unit.hot_ch_inlet.flow_mass_phase_comp[0, "Liq", "TDS"] + + m.fs.unit.hot_ch_inlet.flow_mass_phase_comp[0, "Liq", "H2O"] + + m.fs.unit.cold_ch_inlet.flow_mass_phase_comp[0, "Vap", "H2O"], + "out": m.fs.unit.hot_ch_outlet.flow_mass_phase_comp[0, "Liq", "TDS"] + + m.fs.unit.hot_ch_outlet.flow_mass_phase_comp[0, "Liq", "H2O"] + + m.fs.unit.cold_ch_outlet.flow_mass_phase_comp[0, "Vap", "H2O"], + }, + } + + return m + + +def build_temperature_polarization_concentration_polarization_pressure_calculated_hot_vmd(): + m = ConcreteModel() + m.fs = FlowsheetBlock(dynamic=False) + m.fs.properties_hot_ch = props_sw.SeawaterParameterBlock() + m.fs.properties_cold_ch = props_w.WaterParameterBlock() + m.fs.properties_vapor = props_w.WaterParameterBlock() + m.fs.unit = MembraneDistillation1D( + MD_configuration_Type=MDconfigurationType.VMD, + hot_ch={ + "property_package": m.fs.properties_hot_ch, + "property_package_vapor": m.fs.properties_vapor, + "has_pressure_change": True, + "pressure_change_type": PressureChangeType.calculated, + "temperature_polarization_type": TemperaturePolarizationType.calculated, + "concentration_polarization_type": ConcentrationPolarizationType.calculated, + "mass_transfer_coefficient": MassTransferCoefficient.calculated, + "flow_direction": FlowDirection.forward, + }, + cold_ch={ + "property_package": m.fs.properties_cold_ch, + "temperature_polarization_type": TemperaturePolarizationType.none, + "has_pressure_change": False, + "flow_direction": FlowDirection.forward, + }, + ) + + m.fs.unit.length.fix(0.1) + + # Fully specify the system + hot_ch_flow_mass = 1.0 + hot_ch_mass_frac_TDS = 0.035 + hot_ch_pressure = 7e5 + membrane_area = 1.0 + hot_ch_mass_frac_H2O = 1 - hot_ch_mass_frac_TDS + + m.fs.unit.hot_ch_inlet.flow_mass_phase_comp[0, "Liq", "TDS"].fix( + hot_ch_flow_mass * hot_ch_mass_frac_TDS + ) + m.fs.unit.hot_ch_inlet.flow_mass_phase_comp[0, "Liq", "H2O"].fix( + hot_ch_flow_mass * hot_ch_mass_frac_H2O + ) + m.fs.unit.hot_ch_inlet.pressure[0].fix(hot_ch_pressure) + m.fs.unit.hot_ch_inlet.temperature[0].fix(273.15 + 90) + m.fs.unit.area.fix(membrane_area) + m.fs.unit.permeability_coef.fix(1e-10) + m.fs.unit.membrane_thickness.fix(1e-4) + + m.fs.unit.cold_ch_inlet.flow_mass_phase_comp[0, "Vap", "H2O"].fix(0) + m.fs.unit.cold_ch_inlet.pressure[0].fix(10000) + + m.fs.unit.hot_ch.channel_height.fix(0.0019) + m.fs.unit.hot_ch.spacer_porosity.fix(0.77) + + iscale.calculate_scaling_factors(m) + + return m + + +class TestMembraneDisillation1D_temperature_polarization_concentration_polarization_pressure_calculated_hot_vmd( + UnitTestHarness +): + def configure(self): + m = ( + build_temperature_polarization_concentration_polarization_pressure_calculated_hot_vmd() + ) + + self.unit_solutions[ + m.fs.unit.hot_ch_outlet.flow_mass_phase_comp[0, "Liq", "H2O"] + ] = 0.9450790917786648 + self.unit_solutions[m.fs.unit.hot_ch_outlet.temperature[0]] = 350.19642692723755 + self.unit_solutions[m.fs.unit.cold_ch_outlet.temperature[0]] = 339.2750084657155 + self.unit_solutions[m.fs.unit.flux_mass_avg[0]] = 0.019769720894804336 + + self.conservation_equality = { + "Check 1": { + "in": m.fs.unit.hot_ch_inlet.flow_mass_phase_comp[0, "Liq", "TDS"] + + m.fs.unit.hot_ch_inlet.flow_mass_phase_comp[0, "Liq", "H2O"] + + m.fs.unit.cold_ch_inlet.flow_mass_phase_comp[0, "Vap", "H2O"], + "out": m.fs.unit.hot_ch_outlet.flow_mass_phase_comp[0, "Liq", "TDS"] + + m.fs.unit.hot_ch_outlet.flow_mass_phase_comp[0, "Liq", "H2O"] + + m.fs.unit.cold_ch_outlet.flow_mass_phase_comp[0, "Vap", "H2O"], + }, + } + + return m + + +def build_temperature_polarization_none_pgmd(): + m = ConcreteModel() + m.fs = FlowsheetBlock(dynamic=False) + m.fs.properties_hot_ch = props_sw.SeawaterParameterBlock() + m.fs.properties_cold_ch = props_w.WaterParameterBlock() + m.fs.properties_vapor = props_w.WaterParameterBlock() + m.fs.unit = MembraneDistillation1D( + MD_configuration_Type=MDconfigurationType.GMD, + hot_ch={ + "property_package": m.fs.properties_hot_ch, + "property_package_vapor": m.fs.properties_vapor, + "has_pressure_change": False, + "temperature_polarization_type": TemperaturePolarizationType.none, + "concentration_polarization_type": ConcentrationPolarizationType.none, + "mass_transfer_coefficient": MassTransferCoefficient.none, + "flow_direction": FlowDirection.forward, + }, + cold_ch={ + "property_package": m.fs.properties_hot_ch, + "temperature_polarization_type": TemperaturePolarizationType.none, + "concentration_polarization_type": ConcentrationPolarizationType.none, + "mass_transfer_coefficient": MassTransferCoefficient.none, + "has_pressure_change": False, + "flow_direction": FlowDirection.backward, + }, + gap_ch={ + "property_package": m.fs.properties_cold_ch, + "temperature_polarization_type": TemperaturePolarizationType.fixed, + "has_pressure_change": False, + "flow_direction": FlowDirection.forward, + }, + ) + + m.fs.unit.length.fix(2) + + # fully specify system + hot_ch_flow_mass = 1 + hot_ch_mass_frac_TDS = 0.035 + hot_ch_pressure = 7e5 + membrane_area = 3 + hot_ch_mass_frac_H2O = 1 - hot_ch_mass_frac_TDS + m.fs.unit.hot_ch_inlet.flow_mass_phase_comp[0, "Liq", "TDS"].fix( + hot_ch_flow_mass * hot_ch_mass_frac_TDS + ) + m.fs.unit.hot_ch_inlet.flow_mass_phase_comp[0, "Liq", "H2O"].fix( + hot_ch_flow_mass * hot_ch_mass_frac_H2O + ) + + m.fs.unit.hot_ch_inlet.pressure[0].fix(hot_ch_pressure) + m.fs.unit.hot_ch_inlet.temperature[0].fix(273.15 + 90) + m.fs.unit.area.fix(membrane_area) + m.fs.unit.length.fix(2) + + m.fs.unit.permeability_coef.fix(1e-10) + m.fs.unit.membrane_thickness.fix(1e-4) + m.fs.unit.gap_thickness.fix(0.0004) + m.fs.unit.membrane_thermal_conductivity.fix(0.2) + m.fs.unit.gap_thermal_conductivity.fix(0.06) + m.fs.unit.cold_ch_inlet.flow_mass_phase_comp[0, "Liq", "H2O"].fix( + hot_ch_flow_mass * hot_ch_mass_frac_H2O + ) + m.fs.unit.cold_ch_inlet.flow_mass_phase_comp[0, "Liq", "TDS"].fix( + hot_ch_flow_mass * hot_ch_mass_frac_TDS + ) + + m.fs.unit.gap_ch_inlet.flow_mass_phase_comp[0, "Liq", "H2O"].fix(0) + m.fs.unit.gap_ch_inlet.flow_mass_phase_comp[0, "Vap", "H2O"].fix(0) + + m.fs.unit.cold_ch_inlet.pressure[0].fix(100000) + + m.fs.unit.gap_ch_inlet.pressure[0].fix(100000) + + m.fs.unit.cold_ch_inlet.temperature[0].fix(273.15 + 25) + + iscale.calculate_scaling_factors(m) + + return m + + +class TestMembraneDisillation1D_temperature_polarization_none_pgmd(UnitTestHarness): + def configure(self): + m = build_temperature_polarization_none_pgmd() + + self.unit_solutions[ + m.fs.unit.hot_ch_outlet.flow_mass_phase_comp[0, "Liq", "H2O"] + ] = 0.9580840582054698 + self.unit_solutions[m.fs.unit.hot_ch_outlet.temperature[0]] = 356.6334998833635 + self.unit_solutions[m.fs.unit.cold_ch_outlet.temperature[0]] = ( + 304.69178989433595 + ) + self.unit_solutions[m.fs.unit.flux_mass_avg[0]] = 0.0023053139451937005 + self.unit_solutions[m.fs.unit.gap_ch_outlet.temperature[0]] = 326.62398751842414 + + self.conservation_equality = { + "Check 1": { + "in": m.fs.unit.hot_ch_inlet.flow_mass_phase_comp[0, "Liq", "TDS"] + + m.fs.unit.hot_ch_inlet.flow_mass_phase_comp[0, "Liq", "H2O"] + + m.fs.unit.cold_ch_inlet.flow_mass_phase_comp[0, "Liq", "TDS"] + + m.fs.unit.cold_ch_inlet.flow_mass_phase_comp[0, "Liq", "H2O"] + + m.fs.unit.gap_ch_inlet.flow_mass_phase_comp[0, "Liq", "H2O"] + + m.fs.unit.gap_ch_inlet.flow_mass_phase_comp[0, "Vap", "H2O"], + "out": m.fs.unit.hot_ch_outlet.flow_mass_phase_comp[0, "Liq", "TDS"] + + m.fs.unit.hot_ch_outlet.flow_mass_phase_comp[0, "Liq", "H2O"] + + m.fs.unit.cold_ch_outlet.flow_mass_phase_comp[0, "Liq", "TDS"] + + m.fs.unit.cold_ch_outlet.flow_mass_phase_comp[0, "Liq", "H2O"] + + m.fs.unit.gap_ch_outlet.flow_mass_phase_comp[0, "Liq", "H2O"] + + m.fs.unit.gap_ch_outlet.flow_mass_phase_comp[0, "Vap", "H2O"], + }, + } + + return m + + +######## + + +def build_temperature_polarization_concentration_polarization_pressure_calculated_pgmd(): + m = ConcreteModel() + m.fs = FlowsheetBlock(dynamic=False) + m.fs.properties_hot_ch = props_sw.SeawaterParameterBlock() + m.fs.properties_cold_ch = props_w.WaterParameterBlock() + m.fs.properties_vapor = props_w.WaterParameterBlock() + m.fs.unit = MembraneDistillation1D( + MD_configuration_Type=MDconfigurationType.GMD, + hot_ch={ + "property_package": m.fs.properties_hot_ch, + "property_package_vapor": m.fs.properties_vapor, + "has_pressure_change": True, + "pressure_change_type": PressureChangeType.calculated, + "temperature_polarization_type": TemperaturePolarizationType.calculated, + "concentration_polarization_type": ConcentrationPolarizationType.calculated, + "mass_transfer_coefficient": MassTransferCoefficient.calculated, + "flow_direction": FlowDirection.forward, + }, + cold_ch={ + "property_package": m.fs.properties_hot_ch, + "temperature_polarization_type": TemperaturePolarizationType.calculated, + "concentration_polarization_type": ConcentrationPolarizationType.none, + "mass_transfer_coefficient": MassTransferCoefficient.none, + "has_pressure_change": True, + "pressure_change_type": PressureChangeType.calculated, + "flow_direction": FlowDirection.backward, + }, + gap_ch={ + "property_package": m.fs.properties_cold_ch, + "temperature_polarization_type": TemperaturePolarizationType.fixed, + "has_pressure_change": False, + "flow_direction": FlowDirection.forward, + }, + ) + + m.fs.unit.length.fix(2) + + # fully specify system + hot_ch_flow_mass = 1 + hot_ch_mass_frac_TDS = 0.035 + hot_ch_pressure = 7e5 + membrane_area = 3 + hot_ch_mass_frac_H2O = 1 - hot_ch_mass_frac_TDS + m.fs.unit.hot_ch_inlet.flow_mass_phase_comp[0, "Liq", "TDS"].fix( + hot_ch_flow_mass * hot_ch_mass_frac_TDS + ) + m.fs.unit.hot_ch_inlet.flow_mass_phase_comp[0, "Liq", "H2O"].fix( + hot_ch_flow_mass * hot_ch_mass_frac_H2O + ) + + m.fs.unit.hot_ch_inlet.pressure[0].fix(hot_ch_pressure) + m.fs.unit.hot_ch_inlet.temperature[0].fix(273.15 + 90) + m.fs.unit.area.fix(membrane_area) + m.fs.unit.length.fix(2) + + m.fs.unit.permeability_coef.fix(1e-10) + m.fs.unit.membrane_thickness.fix(1e-4) + m.fs.unit.gap_thickness.fix(0.0004) + m.fs.unit.membrane_thermal_conductivity.fix(0.2) + m.fs.unit.gap_thermal_conductivity.fix(0.06) + m.fs.unit.cold_ch_inlet.flow_mass_phase_comp[0, "Liq", "H2O"].fix( + hot_ch_flow_mass * hot_ch_mass_frac_H2O + ) + m.fs.unit.cold_ch_inlet.flow_mass_phase_comp[0, "Liq", "TDS"].fix( + hot_ch_flow_mass * hot_ch_mass_frac_TDS + ) + + m.fs.unit.gap_ch_inlet.flow_mass_phase_comp[0, "Liq", "H2O"].fix(0) + m.fs.unit.gap_ch_inlet.flow_mass_phase_comp[0, "Vap", "H2O"].fix(0) + + m.fs.unit.cold_ch_inlet.pressure[0].fix(100000) + + m.fs.unit.gap_ch_inlet.pressure[0].fix(100000) + + m.fs.unit.cold_ch_inlet.temperature[0].fix(273.15 + 25) + + m.fs.unit.hot_ch.channel_height.fix(0.0019) + m.fs.unit.hot_ch.spacer_porosity.fix(0.77) + + m.fs.unit.cold_ch.channel_height.fix(0.0019) + m.fs.unit.cold_ch.spacer_porosity.fix(0.77) + + iscale.calculate_scaling_factors(m) + + return m + + +class TestMembraneDisillation1D_temperature_polarization_concentration_polarization_pressure_calculated_pgmd( + UnitTestHarness +): + def configure(self): + m = ( + build_temperature_polarization_concentration_polarization_pressure_calculated_pgmd() + ) + + self.unit_solutions[ + m.fs.unit.hot_ch_outlet.flow_mass_phase_comp[0, "Liq", "H2O"] + ] = 0.9580840582054698 + self.unit_solutions[m.fs.unit.hot_ch_outlet.temperature[0]] = 356.6334998833635 + self.unit_solutions[m.fs.unit.cold_ch_outlet.temperature[0]] = ( + 304.69178989433595 + ) + self.unit_solutions[m.fs.unit.flux_mass_avg[0]] = 0.0022284349420104385 + self.unit_solutions[m.fs.unit.gap_ch_outlet.temperature[0]] = 326.62398751842414 + + self.conservation_equality = { + "Check 1": { + "in": m.fs.unit.hot_ch_inlet.flow_mass_phase_comp[0, "Liq", "TDS"] + + m.fs.unit.hot_ch_inlet.flow_mass_phase_comp[0, "Liq", "H2O"] + + m.fs.unit.cold_ch_inlet.flow_mass_phase_comp[0, "Liq", "TDS"] + + m.fs.unit.cold_ch_inlet.flow_mass_phase_comp[0, "Liq", "H2O"] + + m.fs.unit.gap_ch_inlet.flow_mass_phase_comp[0, "Liq", "H2O"] + + m.fs.unit.gap_ch_inlet.flow_mass_phase_comp[0, "Vap", "H2O"], + "out": m.fs.unit.hot_ch_outlet.flow_mass_phase_comp[0, "Liq", "TDS"] + + m.fs.unit.hot_ch_outlet.flow_mass_phase_comp[0, "Liq", "H2O"] + + m.fs.unit.cold_ch_outlet.flow_mass_phase_comp[0, "Liq", "TDS"] + + m.fs.unit.cold_ch_outlet.flow_mass_phase_comp[0, "Liq", "H2O"] + + m.fs.unit.gap_ch_outlet.flow_mass_phase_comp[0, "Liq", "H2O"] + + m.fs.unit.gap_ch_outlet.flow_mass_phase_comp[0, "Vap", "H2O"], + }, + } + + return m