diff --git a/watertap/property_models/selective_oil_permeation_prop_pack.py b/watertap/property_models/selective_oil_permeation_prop_pack.py index b55e47abc4..3adadb06f1 100644 --- a/watertap/property_models/selective_oil_permeation_prop_pack.py +++ b/watertap/property_models/selective_oil_permeation_prop_pack.py @@ -80,7 +80,7 @@ def build(self): self.phase_list, self.component_list, mutable=True, - initialize={("Liq", "H2O"): 1e-3, ("Liq", "oil"): 3.5e-3}, + initialize={("Liq", "H2O"): 1e-3, ("Liq", "oil"): 3.56e-3}, units=units.kg * units.m**-1 * units.s**-1, doc="Dynamic viscosity", ) diff --git a/watertap/property_models/tests/test_selective_oil_permeation_prop_pack.py b/watertap/property_models/tests/test_selective_oil_permeation_prop_pack.py index 342ff76d76..ff0b7214bf 100644 --- a/watertap/property_models/tests/test_selective_oil_permeation_prop_pack.py +++ b/watertap/property_models/tests/test_selective_oil_permeation_prop_pack.py @@ -33,7 +33,7 @@ def configure(self): } self.default_solution = { ("visc_d_phase_comp", ("Liq", "H2O")): 1e-3, - ("visc_d_phase_comp", ("Liq", "oil")): 3.5e-3, + ("visc_d_phase_comp", ("Liq", "oil")): 3.56e-3, ("dens_mass_phase_comp", ("Liq", "H2O")): 1e3, ("dens_mass_phase_comp", ("Liq", "oil")): 780, ("mass_frac_phase_comp", ("Liq", "H2O")): 0.990099, @@ -66,7 +66,7 @@ def configure(self): } self.regression_solution = { ("visc_d_phase_comp", ("Liq", "H2O")): 1e-3, - ("visc_d_phase_comp", ("Liq", "oil")): 3.5e-3, + ("visc_d_phase_comp", ("Liq", "oil")): 3.56e-3, ("dens_mass_phase_comp", ("Liq", "H2O")): 1e3, ("dens_mass_phase_comp", ("Liq", "oil")): 780, ("mass_frac_phase_comp", ("Liq", "H2O")): 0.5, diff --git a/watertap/tools/parameter_sweep/parameter_sweep.py b/watertap/tools/parameter_sweep/parameter_sweep.py index 76bae6852e..ac95753e05 100644 --- a/watertap/tools/parameter_sweep/parameter_sweep.py +++ b/watertap/tools/parameter_sweep/parameter_sweep.py @@ -13,6 +13,8 @@ import pyomo.environ as pyo import warnings import copy +import requests +import time from abc import abstractmethod, ABC from idaes.core.solvers import get_solver @@ -125,6 +127,24 @@ class _ParameterSweepBase(ABC): ), ) + CONFIG.declare( + "publish_progress", + ConfigValue( + default=False, + domain=bool, + description="Boolean to decide whether information about how many iterations of the parameter sweep have completed should be sent.", + ), + ) + + CONFIG.declare( + "publish_address", + ConfigValue( + default="http://localhost:8888", + domain=str, + description="Address to which the parameter sweep progress will be sent.", + ), + ) + def __init__( self, **options, @@ -160,6 +180,18 @@ def _assign_variable_names(model, outputs): exprs[output_name] = _pyo_obj outputs[output_name] = exprs[output_name] + def _publish_updates(self, iteration, solve_status, solve_time): + + if self.config.publish_progress: + publish_dict = { + "worker_number": self.comm.Get_rank(), + "iteration": iteration, + "solve_status": solve_status, + "solve_time": solve_time, + } + + return requests.put(self.config.publish_address, data=publish_dict) + def _build_combinations(self, d, sampling_type, num_samples): num_var_params = len(d) @@ -561,6 +593,7 @@ def _do_param_sweep(self, model, sweep_params, outputs, local_values): # ================================================================ for k in range(local_num_cases): + start_time = time.time() run_successful = self._run_sample( model, reinitialize_values, @@ -569,7 +602,9 @@ def _do_param_sweep(self, model, sweep_params, outputs, local_values): sweep_params, local_output_dict, ) + time_elapsed = time.time() - start_time local_solve_successful_list.append(run_successful) + self._publish_updates(k, run_successful, time_elapsed) local_output_dict["solve_successful"] = local_solve_successful_list diff --git a/watertap/tools/parameter_sweep/tests/test_parameter_sweep.py b/watertap/tools/parameter_sweep/tests/test_parameter_sweep.py index 59146d0b15..fe6424853c 100644 --- a/watertap/tools/parameter_sweep/tests/test_parameter_sweep.py +++ b/watertap/tools/parameter_sweep/tests/test_parameter_sweep.py @@ -13,6 +13,7 @@ import pytest import os import numpy as np +import requests import pyomo.environ as pyo from pyomo.environ import value @@ -180,6 +181,15 @@ def test_reverse_geom_build_combinations(self): assert global_combo_array[-1, 1] == pytest.approx(range_B[1]) assert global_combo_array[-1, 2] == pytest.approx(range_C[1]) + @pytest.mark.component + def test_status_publishing(self): + ps = ParameterSweep( + publish_progress=True, publish_address="http://localhost:8888" + ) + + with pytest.raises(requests.exceptions.ConnectionError): + r = ps._publish_updates(1, True, 5.0) + def test_random_build_combinations(self): ps = ParameterSweep() diff --git a/watertap/ui/fsapi.py b/watertap/ui/fsapi.py index c3222bc784..fb0a50ff4e 100644 --- a/watertap/ui/fsapi.py +++ b/watertap/ui/fsapi.py @@ -89,6 +89,7 @@ class ModelExport(BaseModel): lb: Union[None, float] = 0.0 ub: Union[None, float] = 0.0 has_bounds: bool = True + is_sweep: bool = False class Config: arbitrary_types_allowed = True @@ -429,6 +430,7 @@ def load(self, data: Dict): dst.obj.fixed = src.fixed dst.fixed = src.fixed + dst.is_sweep = src.is_sweep # update bounds if src.lb is None or src.lb == "": diff --git a/watertap/unit_models/selective_oil_permeation.py b/watertap/unit_models/selective_oil_permeation.py index bb2594141b..968ba26191 100644 --- a/watertap/unit_models/selective_oil_permeation.py +++ b/watertap/unit_models/selective_oil_permeation.py @@ -21,7 +21,6 @@ NonNegativeReals, Reference, units, - value, ) from pyomo.common.config import ConfigBlock, ConfigValue, In @@ -191,7 +190,7 @@ def build(self): # Add unit parameters self.pore_diameter = Param( mutable=True, - initialize=4e-8, + initialize=4.7e-8, units=units_meta("length"), doc="Average membrane pore diameter", ) @@ -305,6 +304,22 @@ def build(self): doc="Effective fraction of membrane being used for oil transfer", ) + self.effective_area_ratio_num = Var( + self.flowsheet().config.time, + initialize=0.1, + bounds=(0.0, None), + units=units.dimensionless, + doc="Effective area ratio numerator", + ) + + self.effective_area_ratio_den = Var( + self.flowsheet().config.time, + initialize=1, + bounds=(0.0, None), + units=units.dimensionless, + doc="Effective area ratio denominator", + ) + self.recovery_frac_oil = Var( self.flowsheet().config.time, initialize=0.5, @@ -500,49 +515,53 @@ def eq_flux_vol_oil_pure(b, t): 4 / 3 ) - def convert_dimensionless(expr, to_units=None): - """ - Given a pyomo expression and desired units, detect the units of the - expression and convert to the desired units, and return only the - numerical value. - """ - return units.convert_value( - value(expr), from_units=units.get_units(expr), to_units=to_units + @self.Constraint( + self.flowsheet().config.time, + doc="Effective area ratio numerator", + ) + def eq_effective_area_ratio_num(b, t): + return b.effective_area_ratio_num[t] == ( + b.num_constant + * b.feed_side.properties_in[t].vol_frac_phase_comp["Liq", "oil"] + * ( + b.feed_side.properties_in[t].visc_d_phase_comp["Liq", "oil"] + * (units.Pa * units.s) ** -1 + ) + ** b.num_mu_exp + * (b.pressure_transmemb_avg[t] * units.Pa**-1) ** b.num_PT_exp + * (b.liquid_velocity_in[t] * (units.m * units.s**-1) ** -1) + ** b.num_v_exp ) @self.Constraint( self.flowsheet().config.time, - doc="Effective area ratio for oil transfer", + doc="Effective area ratio denominator", ) - def eq_effective_area_ratio(b, t): - return b.effective_area_ratio[ - t - ] == b.num_constant * b.feed_side.properties_in[t].vol_frac_phase_comp[ - "Liq", "oil" - ] * convert_dimensionless( - b.feed_side.properties_in[t].visc_d_phase_comp["Liq", "oil"], - to_units=units.Pa * units.s, - ) ** b.num_mu_exp * convert_dimensionless( - b.pressure_transmemb_avg[t], to_units=units.Pa - ) ** b.num_PT_exp * convert_dimensionless( - b.liquid_velocity_in[t], to_units=units.m * units.s**-1 - ) ** b.num_v_exp / ( + def eq_effective_area_ratio_den(b, t): + return b.effective_area_ratio_den[t] == ( 1 + b.den_constant * b.feed_side.properties_in[t].vol_frac_phase_comp["Liq", "oil"] - * convert_dimensionless( - b.feed_side.properties_in[t].visc_d_phase_comp["Liq", "oil"], - to_units=units.Pa * units.s, + * ( + b.feed_side.properties_in[t].visc_d_phase_comp["Liq", "oil"] + * (units.Pa * units.s) ** -1 ) ** b.den_mu_exp - * convert_dimensionless(b.pressure_transmemb_avg[t], to_units=units.Pa) - ** b.den_PT_exp - * convert_dimensionless( - b.liquid_velocity_in[t], to_units=units.m * units.s**-1 - ) + * (b.pressure_transmemb_avg[t] * units.Pa**-1) ** b.den_PT_exp + * (b.liquid_velocity_in[t] * (units.m * units.s**-1) ** -1) ** b.den_v_exp ) + @self.Constraint( + self.flowsheet().config.time, + doc="Effective area ratio for oil transfer", + ) + def eq_effective_area_ratio(b, t): + return ( + b.effective_area_ratio_num[t] + == b.effective_area_ratio_den[t] * b.effective_area_ratio[t] + ) + @self.Constraint( self.flowsheet().config.time, doc="Recovery fraction of oil, mass basis", @@ -607,6 +626,7 @@ def initialize_build( else: state_args[k] = state_dict[k].value + state_args["flow_mass_phase_comp"][("Liq", "H2O")] = 0 self.properties_permeate.initialize( outlvl=outlvl, optarg=optarg, @@ -641,6 +661,12 @@ def _get_performance_contents(self, time_point=0): var_dict["Oil volumetric flux"] = self.flux_vol_oil[time_point] var_dict["Pure oil volumetric flux"] = self.flux_vol_oil_pure[time_point] var_dict["Effective area ratio"] = self.effective_area_ratio[time_point] + var_dict["Eff. area ratio numerator"] = self.effective_area_ratio_num[ + time_point + ] + var_dict["Eff. area ratio denominator"] = self.effective_area_ratio_den[ + time_point + ] var_dict["Oil recovery"] = self.recovery_frac_oil[time_point] var_dict["Shell side liquid velocity in"] = self.liquid_velocity_in[time_point] return {"vars": var_dict, "exprs": expr_dict} @@ -661,7 +687,9 @@ def calculate_scaling_factors(self): iscale.set_scaling_factor(self.area, 1e-1) iscale.set_scaling_factor(self.flux_vol_oil, 1e9) iscale.set_scaling_factor(self.flux_vol_oil_pure, 1e6) - iscale.set_scaling_factor(self.effective_area_ratio, 1e2) + iscale.set_scaling_factor(self.effective_area_ratio, 1e1) + iscale.set_scaling_factor(self.effective_area_ratio_num, 1e1) + iscale.set_scaling_factor(self.effective_area_ratio_den, 1) iscale.set_scaling_factor(self.recovery_frac_oil, 1) iscale.set_scaling_factor(self.liquid_velocity_in, 1e2) iscale.set_scaling_factor( diff --git a/watertap/unit_models/tests/test_selective_oil_permeation.py b/watertap/unit_models/tests/test_selective_oil_permeation.py index 4db391aa3f..f2e46cbf82 100644 --- a/watertap/unit_models/tests/test_selective_oil_permeation.py +++ b/watertap/unit_models/tests/test_selective_oil_permeation.py @@ -81,15 +81,14 @@ def unit_frame(self): # fully specify system m.fs.unit.feed_side.properties_in[0].temperature.fix(298.15) # temp in K - m.fs.unit.feed_side.properties_in[0].pressure.fix(1.48 * units.bar) - # The below feed data corresponds roughly to 3.8 L/min flow with 500 ppm oil + m.fs.unit.feed_side.properties_in[0].pressure.fix(2.52 * units.bar) m.fs.unit.feed_side.properties_in[0].flow_mass_phase_comp["Liq", "H2O"].fix( - 6.3e-2 + 6.27e-2 ) # H2O flow in kg/s m.fs.unit.feed_side.properties_in[0].flow_mass_phase_comp["Liq", "oil"].fix( - 3.2e-5 + 4.94e-4 ) # oil flow in kg/s - m.fs.unit.feed_side.properties_out[0].pressure.fix(1.20 * units.bar) + m.fs.unit.feed_side.properties_out[0].pressure.fix(2.24 * units.bar) m.fs.unit.area.fix(1.4) # membrane area in m^2 m.fs.unit.properties_permeate[0].pressure.fix(1 * units.bar) @@ -108,8 +107,8 @@ def test_build(self, unit_frame): ) # number of state variables for SOP property package assert isinstance(port, Port) - assert number_variables(m) == 28 - assert number_total_constraints(m) == 20 + assert number_variables(m) == 30 + assert number_total_constraints(m) == 22 assert number_unused_variables(m) == 0 assert_units_consistent(m) @@ -192,25 +191,31 @@ def test_conservation(self, unit_frame): @pytest.mark.component def test_solution(self, unit_frame): m = unit_frame - assert pytest.approx(6.1178e-6, rel=1e-3) == value( + assert pytest.approx(3.32237e-4, rel=1e-3) == value( m.fs.unit.properties_permeate[0].flow_mass_phase_comp["Liq", "oil"] ) - assert pytest.approx(3.4e4, rel=1e-3) == value( + assert pytest.approx(1.38e5, rel=1e-3) == value( m.fs.unit.pressure_transmemb_avg[0] ) assert pytest.approx(-2.8e4, rel=1e-3) == value(m.fs.unit.deltaP[0]) - assert pytest.approx(5.6024e-9, rel=1e-3) == value(m.fs.unit.flux_vol_oil[0]) - assert pytest.approx(6.0346e-7, rel=1e-3) == value( + assert pytest.approx(3.0425e-07, rel=1e-3) == value(m.fs.unit.flux_vol_oil[0]) + assert pytest.approx(3.3246e-06, rel=1e-3) == value( m.fs.unit.flux_vol_oil_pure[0] ) - assert pytest.approx(9.2802e-3, rel=1e-3) == value( + assert pytest.approx(0.091512, rel=1e-3) == value( m.fs.unit.effective_area_ratio[0] ) - assert pytest.approx(0.19118, rel=1e-3) == value(m.fs.unit.recovery_frac_oil[0]) - assert pytest.approx(1.9596e-2, rel=1e-3) == value( + assert pytest.approx(0.10799, rel=1e-3) == value( + m.fs.unit.effective_area_ratio_num[0] + ) + assert pytest.approx(1.1801, rel=1e-3) == value( + m.fs.unit.effective_area_ratio_den[0] + ) + assert pytest.approx(0.67254, rel=1e-3) == value(m.fs.unit.recovery_frac_oil[0]) + assert pytest.approx(0.019687, rel=1e-3) == value( m.fs.unit.liquid_velocity_in[0] ) - assert pytest.approx(6.1178e-6, rel=1e-3) == value( + assert pytest.approx(3.32237e-4, rel=1e-3) == value( m.fs.unit.mass_transfer_oil[0] )