Source code for watertap.unit_models.electrocoagulation

#################################################################################
# WaterTAP Copyright (c) 2020-2025, The Regents of the University of California,
# through Lawrence Berkeley National Laboratory, Oak Ridge National Laboratory,
# National Renewable Energy Laboratory, and National Energy Technology
# Laboratory (subject to receipt of any required approvals from the U.S. Dept.
# of Energy). All rights reserved.
#
# Please see the files COPYRIGHT.md and LICENSE.md for full copyright and license
# information, respectively. These files are also available online at the URL
# "https://github.com/watertap-org/watertap/"
#################################################################################

from copy import deepcopy

# Import Pyomo libraries
from pyomo.environ import (
    Var,
    check_optimal_termination,
    Param,
    Suffix,
    log,
    units as pyunits,
)
from pyomo.common.config import ConfigBlock, ConfigValue, In

# Import IDAES cores
from idaes.core import (
    declare_process_block_class,
    UnitModelBlockData,
    useDefault,
)
from idaes.core.util.config import is_physical_parameter_block
from idaes.core.util.constants import Constants
from idaes.core.util.exceptions import ConfigurationError, InitializationError
from idaes.core.util.misc import StrEnum
from idaes.core.util.tables import create_stream_table_dataframe
import idaes.core.util.scaling as iscale
import idaes.logger as idaeslog

from watertap.core import InitializationMixin
from watertap.core.solvers import get_solver
from watertap.costing.unit_models.electrocoagulation import cost_electrocoagulation

__author__ = "Kurban Sitterley, Mukta Hardikar"


[docs]class ElectrodeMaterial(StrEnum): aluminum = "aluminum" iron = "iron"
[docs]class ReactorMaterial(StrEnum): pvc = "pvc" carbon_steel = "carbon_steel" stainless_steel = "stainless_steel"
[docs]class OverpotentialCalculation(StrEnum): detailed = "detailed" fixed = "fixed" regression = "regression"
""" References: K. L. Dubrawski, C. Du and M. Mohseni (2014) Electrochimica Acta 2014 Vol. 129 Pages 187-195 DOI: 10.1016/j.electacta.2014.02.089 Z. Gu, Z. Liao, M. Schulz, J. R. Davis, J. C. Baygents and J. Farrell (2009) Industrial & Engineering Chemistry Research 2009 Vol. 48 Issue 6 Pages 3112-3117 DOI: 10.1021/ie801086c Bratsch, S. G. (1989). Standard Electrode Potentials and Temperature Coefficients in Water at 298.15 K. Journal of Physical and Chemical Reference Data, 18(1), 1-21. DOI: 10.1063/1.555839 Zhang, F., Yang, C., Zhu, H., Li, Y., & Gui, W. (2020). An integrated prediction model of heavy metal ion concentration for iron electrocoagulation process. Chemical Engineering Journal, 391, 123628. DOI: 10.1016/j.cej.2019.123628 R. Holze (2007) M.D. Lechner (ed.) Electrochemical Thermodynamics and Kinetics Table 5.1. Exchange current densities and rate constants in aqueous systems Landolt-Börnstein - Group IV Physical Chemistry 9A DOI: 10.1007/978-3-540-45316-1 """
[docs]@declare_process_block_class("Electrocoagulation") class ElectrocoagulationData(InitializationMixin, UnitModelBlockData): """ Electrocoagulation model """ CONFIG = ConfigBlock() CONFIG.declare( "dynamic", ConfigValue( domain=In([False]), default=False, description="Dynamic model flag - must be False", doc="""Indicates whether this model will be dynamic or not, **default** = False.""", ), ) CONFIG.declare( "has_holdup", ConfigValue( default=False, domain=In([False]), description="Holdup construction flag - must be False", doc="""Indicates whether holdup terms should be constructed or not. **default** - False.""", ), ) CONFIG.declare( "property_package", ConfigValue( default=useDefault, domain=is_physical_parameter_block, description="Property package to use for control volume", doc="""Property parameter object used to define property calculations, **default** - useDefault. **Valid values:** { **useDefault** - use default package from parent model or flowsheet, **PhysicalParameterObject** - a PhysicalParameterBlock object.}""", ), ) CONFIG.declare( "property_package_args", ConfigBlock( implicit=True, description="Arguments to use for constructing property packages", doc="""A ConfigBlock with arguments to be passed to a property block(s) and used when constructing these, **default** - None. **Valid values:** { see property package for documentation.}""", ), ) CONFIG.declare( "electrode_material", ConfigValue( default="aluminum", domain=In(ElectrodeMaterial), description="Electrode material", ), ) CONFIG.declare( "reactor_material", ConfigValue( default="carbon_steel", domain=In(ReactorMaterial), description="Reactor material", ), ) CONFIG.declare( "overpotential_calculation", ConfigValue( default="fixed", domain=In(OverpotentialCalculation), description="Determination of overpotential", ), )
[docs] def build(self): super().build() solutes = self.config.property_package.solute_set if "TDS" not in solutes: raise ConfigurationError( "TDS must be in feed stream for solution conductivity estimation." ) self.scaling_factor = Suffix(direction=Suffix.EXPORT) tmp_dict = dict(**self.config.property_package_args) tmp_dict["has_phase_equilibrium"] = False tmp_dict["parameters"] = self.config.property_package tmp_dict["defined_state"] = True self.properties_in = self.config.property_package.state_block_class( self.flowsheet().config.time, doc="Material properties of inlet", **tmp_dict ) tmp_dict["defined_state"] = False self.properties_out = self.config.property_package.state_block_class( self.flowsheet().config.time, doc="Material properties of outlet", **tmp_dict, ) self.properties_byproduct = self.config.property_package.state_block_class( self.flowsheet().config.time, doc="Material properties of waste", **tmp_dict ) prop_in = self.properties_in[0] prop_out = self.properties_out[0] prop_byproduct = self.properties_byproduct[0] self.add_port(name="inlet", block=self.properties_in) self.add_port(name="outlet", block=self.properties_out) self.add_port(name="byproduct", block=self.properties_byproduct) removal_frac_dict = dict( zip( solutes, [0.7 for _ in solutes], ) ) self.removal_frac_mass_comp = Param( solutes, initialize=removal_frac_dict, mutable=True, units=pyunits.dimensionless, doc="Component removal efficiency on mass basis", ) self.recovery_frac_mass_H2O = Param( initialize=0.99, mutable=True, units=pyunits.dimensionless, doc="Water recovery on mass basis", ) self.tds_to_cond_conversion = Param( initialize=5e3, mutable=True, units=(pyunits.mg * pyunits.m) / (pyunits.liter * pyunits.S), doc="Conversion factor for mg/L TDS to S/m", ) self.standard_temperature = Param( initialize=298.15, # 25C mutable=True, units=pyunits.degK, doc="Standard temperature", ) self.mw_electrode_material = Param( initialize=100e-3, mutable=True, units=pyunits.kg / pyunits.mol, doc="Molecular weight of coagulant species", ) self.charge_transfer_number = Param( initialize=1, mutable=True, units=pyunits.dimensionless, doc="Charge transfer number of coagulant species", ) self.stoic_coeff = Param( initialize=1, mutable=True, units=pyunits.dimensionless, doc="Stoichiometric coefficient for electrode material", ) self.density_electrode_material = Param( initialize=2000, mutable=True, units=pyunits.kg / pyunits.m**3, doc="Density of electrode material", ) self.frac_increase_temperature = Param( initialize=1.05, mutable=True, units=pyunits.dimensionless, doc="Fractional increase in water temperature from inlet to outlet", ) if self.config.overpotential_calculation == OverpotentialCalculation.detailed: self.anode_cell_potential_std = Param( initialize=-0.5, mutable=True, units=pyunits.volt, doc="Anodic non-equilibrium cell potential, standard @ 25C", ) self.anode_entropy_change_std = Param( initialize=1e-4, mutable=True, units=pyunits.volt / pyunits.K, doc="Entropy change", ) self.anodic_exchange_current_density = Param( initialize=2e-5, mutable=True, units=pyunits.ampere / pyunits.m**2, doc="Anodic exchange current density", ) self.cathodic_exchange_current_density = Param( initialize=1e-4, mutable=True, units=pyunits.ampere / pyunits.m**2, doc="Cathodic exchange current density", ) self.cathode_surface_pH = Param( initialize=11, mutable=True, units=pyunits.dimensionless, doc="pH at cathode surface", ) self.cathode_cell_potential_std = Param( initialize=-0.83, mutable=True, units=pyunits.volt, doc="Cathode equilibrium cell potential, standard H2 @ 25C", ) self.cathode_entropy_change_std = Param( initialize=-0.000836, # = S / (Z * F) mutable=True, units=pyunits.volt / pyunits.K, doc="Entropy change for H2", ) self.partial_pressure_H2 = Param( initialize=1, mutable=True, units=pyunits.atm, doc="Partial pressure of hydrogen gas", ) self.tafel_slope_cathode = Var( initialize=0.146 / 2.303, # default value used log10 bounds=(0, None), units=pyunits.volt, # volt per 10x change (decade) doc="Tafel slope for cathode", ) self.tafel_slope_anode = Var( initialize=0.093 / 2.303, # default value used log10 bounds=(0, None), units=pyunits.volt, # volt per 10x change (decade) doc="Tafel slope for anode", ) if self.config.electrode_material == ElectrodeMaterial.aluminum: """ Reaction at anode is: Al_3+ + 3e_- <--> Al(s); Ea0 = -1.66 V Reaction in bulk solution is: Al_3+ + 3OH_- <--> Al(OH)3(s) """ self.mw_electrode_material.set_value(26.98e-3) self.charge_transfer_number.set_value(3) self.stoic_coeff.set_value(1) self.density_electrode_material.set_value(2710) if ( self.config.overpotential_calculation == OverpotentialCalculation.detailed ): # Dubrawski et al., 2014; Zhang et al., 2020 self.anode_cell_potential_std.set_value(-1.66) # = S / (Z * F); Bratsch, 1989; Dubrawski et al., 2014 self.anode_entropy_change_std.set_value(0.000533) # Electrochemical Thermodynamics and Kinetics, pg 276 self.anodic_exchange_current_density.set_value(2.602e-5) self.cathodic_exchange_current_density.set_value(1.0e-4) if self.config.electrode_material == ElectrodeMaterial.iron: """ Reaction at anode is: Fe_2+ + 2e_- <--> Fe(s); Ea0 = -0.41 V Reaction in bulk solution is: Fe_2+ + 2OH_- <--> Fe(OH)2(s) """ self.mw_electrode_material.set_value(55.845e-3) self.charge_transfer_number.set_value(2) self.stoic_coeff.set_value(1) self.density_electrode_material.set_value(7860) if ( self.config.overpotential_calculation == OverpotentialCalculation.detailed ): # Dubrawski et al., 2014; Zhang et al., 2020 self.anode_cell_potential_std.set_value(-0.41) # = S / (Z * F); Bratsch, 1989; Dubrawski et al., 2014 self.anode_entropy_change_std.set_value(7e-5) # Dubrawski et al., 2014 self.anodic_exchange_current_density.set_value(2.5e-4) # Dubrawski et al., 2014 self.cathodic_exchange_current_density.set_value(1e-3) self.floc_basin_vol = Var( initialize=1, bounds=(0, None), units=pyunits.m**3, doc="Floc basin volume total (flotation + sedimentation)", ) self.floc_retention_time = Var( initialize=30, bounds=(2, 200), units=pyunits.minute, doc="Floc basin retention time", ) self.coagulant_dose = Var( initialize=1, bounds=(0, None), units=pyunits.g / pyunits.liter, doc="Assumed coagulant dose to the feed in g/L", ) self.electrode_thickness = Var( initialize=0.001, bounds=(0, 0.1), units=pyunits.m, doc="Electrode thickness", ) self.electrode_mass = Var( initialize=10, bounds=(0, None), units=pyunits.kg, doc="Electrode mass", ) self.electrode_volume = Var( initialize=1, bounds=(0, None), units=pyunits.m**3, doc="Electrode volume", ) self.cathode_area = Var( initialize=1, bounds=(0, None), units=pyunits.m**2, doc="Area of cathode", ) self.anode_area = Var( initialize=1, bounds=(1e-6, None), units=pyunits.m**2, doc="Area of anode", ) self.electrode_gap = Var( initialize=0.005, bounds=(0.00001, 0.5), units=pyunits.m, doc="Electrode gap", ) self.electrolysis_time = Var( initialize=30, bounds=(0.1, None), units=pyunits.minute, doc="Electrolysis time", ) self.reactor_volume = Var( initialize=1, bounds=(0, None), units=pyunits.m**3, doc="Volume of electrocoagulation reactor", ) self.current_density = Var( initialize=1, bounds=(0, None), units=pyunits.ampere / pyunits.m**2, doc="Current density", ) self.applied_current = Var( initialize=1e4, bounds=(0, None), units=pyunits.ampere, doc="Applied current", ) self.ohmic_resistance = Var( initialize=0.1, bounds=(0, None), units=pyunits.ohm * pyunits.m**2, doc="Ohmic resistance of solution", ) self.charge_loading_rate = Var( initialize=1, bounds=(0, None), units=pyunits.coulomb / pyunits.liter, doc="Charge loading rate", ) self.current_efficiency = Var( initialize=1, bounds=(0, None), units=pyunits.kg / pyunits.kg, doc="Current efficiency", ) self.cell_voltage = Var( initialize=1, bounds=(0, None), units=pyunits.volt, doc="Cell voltage", ) self.overpotential = Var( initialize=1, bounds=(0, None), units=pyunits.volt, doc="Overpotential", ) @self.Expression(doc="Conductivity") def conductivity(b): return pyunits.convert( prop_in.conc_mass_phase_comp["Liq", "TDS"] / b.tds_to_cond_conversion, to_units=pyunits.S / pyunits.m, ) @self.Expression(doc="Ohmic potential") def ohmic_potential(b): return pyunits.convert( b.applied_current * (b.ohmic_resistance / b.anode_area), to_units=pyunits.volt, ) @self.Expression(doc="Theoretical coagulant dose") def theoretical_coagulant_dose(b): return pyunits.convert( (b.applied_current * b.mw_electrode_material) / ( Constants.faraday_constant * b.charge_transfer_number * prop_in.flow_vol_phase["Liq"] ), to_units=pyunits.g / pyunits.liter, ) @self.Expression(doc="Total electrode area") def electrode_area_total(b): return b.anode_area + b.cathode_area @self.Expression(doc="Power required") def power_required(b): return pyunits.convert( b.cell_voltage * b.applied_current, to_units=pyunits.kW ) @self.Expression(doc="Power density Faradaic") def power_density_faradaic(b): return pyunits.convert( (b.overpotential * b.applied_current) / b.anode_area, to_units=pyunits.microwatts / pyunits.cm**2, ) @self.Expression(doc="Power density total") def power_density_total(b): return pyunits.convert( b.power_required / b.anode_area, to_units=pyunits.microwatts / pyunits.cm**2, ) if self.config.overpotential_calculation == OverpotentialCalculation.regression: self.overpotential_k1 = Var( units=pyunits.millivolt, doc="Constant k1 in overpotential equation from Gu et al. (2009)", ) self.overpotential_k2 = Var( units=pyunits.millivolt, doc="Constant k2 in overpotential equation from Gu et al. (2009)", ) @self.Constraint( doc="Overpotential calculation - adapted from Eq. 18 in Gu et al. (2009)" ) def eq_overpotential(b): cd_dimensionless = pyunits.convert( b.current_density * pyunits.cm**2 / pyunits.milliampere, to_units=pyunits.dimensionless, ) return b.overpotential == pyunits.convert( (b.overpotential_k1 * log(cd_dimensionless) + b.overpotential_k2), to_units=pyunits.volt, ) if self.config.overpotential_calculation == OverpotentialCalculation.detailed: @self.Expression(doc="Hydroxide concentration at cathode surface") def cathode_conc_mol_hydroxide(b): pOH = 14 - b.cathode_surface_pH return 10 ** (-pOH) * pyunits.mol / pyunits.liter @self.Expression(doc="Change in effluent temperature relative to standard") def temp_diff_std(b): return prop_out.temperature - b.standard_temperature @self.Expression( doc="Anode equilibrium potential adjusted for outlet temperature" ) def anode_cell_potential_temp_adj(b): return pyunits.convert( b.anode_cell_potential_std + b.anode_entropy_change_std * b.temp_diff_std, to_units=pyunits.volt, ) @self.Expression( doc="Anode non-equilibrium cell potential via Nernst equation" ) def anode_cell_potential(b): ec_dose_dimensionless = pyunits.convert( (b.coagulant_dose / b.mw_electrode_material) * pyunits.liter * pyunits.mol**-1, to_units=pyunits.dimensionless, ) return b.anode_cell_potential_temp_adj - ( pyunits.convert( ( (Constants.gas_constant * prop_out.temperature) / (Constants.faraday_constant * b.charge_transfer_number) ), to_units=pyunits.volt, ) * log(ec_dose_dimensionless**-b.stoic_coeff) ) @self.Expression( doc="Cathode equilibrium potential adjusted for outlet temperature" ) def cathode_cell_potential_temp_adj(b): return pyunits.convert( b.cathode_cell_potential_std + b.cathode_entropy_change_std * b.temp_diff_std, to_units=pyunits.volt, ) @self.Expression(doc="Cathode cell potential via Nernst equation") def cathode_cell_potential(b): ccmh_dimensionless = pyunits.convert( b.cathode_conc_mol_hydroxide * pyunits.liter / pyunits.mol, to_units=pyunits.dimensionless, ) ph2_dimensionless = pyunits.convert( b.partial_pressure_H2 * pyunits.atm**-1, to_units=pyunits.dimensionless, ) return b.cathode_cell_potential_temp_adj - pyunits.convert( ( (Constants.gas_constant * prop_out.temperature) / (Constants.faraday_constant * b.charge_transfer_number) ), to_units=pyunits.volt, ) * log(ccmh_dimensionless**2 * ph2_dimensionless) # Eq. 6 in Zhang et al. (2020); Eq. 12 in Dubrawski et al. (2014) @self.Expression(doc="Anode activation overpotential") def anode_overpotential(b): return b.tafel_slope_anode * log( pyunits.convert( b.current_density / b.anodic_exchange_current_density, to_units=pyunits.dimensionless, ) ) # Eq. 7 in Zhang et al. (2020); Eq. 11 in Dubrawski et al. (2014) @self.Expression(doc="Cathode activation overpotential") def cathode_overpotential(b): return -1 * ( b.tafel_slope_cathode * log( pyunits.convert( b.current_density / b.cathodic_exchange_current_density, to_units=pyunits.dimensionless, ) ) ) # See Eq. 3 in Zhang et al. (2020), Eq. 10 in Dubrawski et al. (2014) # neglecting concentration/mass-transfer overpotential @self.Constraint(doc="Overpotential calculation") def eq_total_overpotential(b): return b.overpotential == abs( b.cathode_cell_potential - b.anode_cell_potential ) + b.anode_overpotential + abs(b.cathode_overpotential) @self.Constraint(doc="Temperature change") def eq_temperature_change(b): return ( prop_out.temperature == b.frac_increase_temperature * prop_in.temperature ) @self.Constraint(doc="Water recovery") def eq_water_recovery(b): return ( prop_out.flow_mass_phase_comp["Liq", "H2O"] == prop_in.flow_mass_phase_comp["Liq", "H2O"] * b.recovery_frac_mass_H2O ) @self.Constraint(doc="Water mass balance") def eq_water_mass_balance(b): return ( prop_in.flow_mass_phase_comp["Liq", "H2O"] == prop_out.flow_mass_phase_comp["Liq", "H2O"] + prop_byproduct.flow_mass_phase_comp["Liq", "H2O"] ) @self.Constraint(solutes, doc="Component removal") def eq_component_removal(b, j): return ( b.removal_frac_mass_comp[j] * prop_in.flow_mass_phase_comp["Liq", j] == prop_byproduct.flow_mass_phase_comp["Liq", j] ) @self.Constraint(solutes, doc="Component mass balance") def eq_comp_mass_balance(b, j): return ( prop_in.flow_mass_phase_comp["Liq", j] == prop_out.flow_mass_phase_comp["Liq", j] + prop_byproduct.flow_mass_phase_comp["Liq", j] ) @self.Constraint(doc="Charge loading rate equation") def eq_charge_loading_rate(b): return ( pyunits.convert( b.charge_loading_rate * prop_in.flow_vol_phase["Liq"], to_units=pyunits.ampere, ) == b.applied_current ) @self.Constraint(doc="Total flocculation tank volume") def eq_floc_reactor_volume(b): return b.floc_basin_vol == pyunits.convert( prop_in.flow_vol_phase["Liq"] * b.floc_retention_time, to_units=pyunits.m**3, ) @self.Constraint(doc="Faraday's Law") def eq_faraday(b): return ( b.applied_current * b.current_efficiency * b.mw_electrode_material == pyunits.convert( prop_in.flow_vol_phase["Liq"] * b.coagulant_dose * b.charge_transfer_number * Constants.faraday_constant, to_units=(pyunits.kg * pyunits.amp) / pyunits.mol, ) ) @self.Constraint(doc="Total anode area required") def eq_anode_area(b): return b.anode_area == pyunits.convert( b.applied_current / b.current_density, to_units=pyunits.m**2 ) @self.Constraint(doc="Cell voltage") def eq_cell_voltage(b): return b.cell_voltage == pyunits.convert( b.overpotential + b.ohmic_potential, to_units=pyunits.volt, ) @self.Constraint(doc="Electrode volume") def eq_electrode_volume(b): return ( b.electrode_volume == (b.anode_area + b.cathode_area) * b.electrode_thickness ) @self.Constraint(doc="Cathode and anode areas are equal") def eq_cathode_anode(b): return b.cathode_area == b.anode_area @self.Constraint(doc="Total reactor volume") def eq_reactor_volume(b): return b.reactor_volume == pyunits.convert( prop_in.flow_vol_phase["Liq"] * b.electrolysis_time, to_units=pyunits.m**3, ) @self.Constraint(doc="Ohmic resistance") def eq_ohmic_resistance(b): return ( pyunits.convert(b.ohmic_resistance * b.conductivity, to_units=pyunits.m) == b.electrode_gap ) @self.Constraint(doc="Electrode mass") def eq_electrode_mass(b): return b.electrode_mass == b.electrode_volume * b.density_electrode_material
[docs] def initialize_build( self, state_args=None, outlvl=idaeslog.NOTSET, solver=None, optarg=None, ): """ General wrapper for initialization routines Keyword Arguments: state_args : a dict of arguments to be passed to the property package(s) to provide an initial state for initialization (see documentation of the specific property package) (default = {}). outlvl : sets output level of initialization routine optarg : solver options dictionary object (default=None) solver : str indicating which solver to use during initialization (default = None) Returns: None """ init_log = idaeslog.getInitLogger(self.name, outlvl, tag="unit") solve_log = idaeslog.getSolveLogger(self.name, outlvl, tag="unit") opt = get_solver(solver, optarg) # --------------------------------------------------------------------- flags = self.properties_in.initialize( outlvl=outlvl, optarg=optarg, solver=solver, state_args=state_args, hold_state=True, ) init_log.info("Initialization Step 1a Complete.") # --------------------------------------------------------------------- # Initialize other state blocks # Set state_args from inlet state if state_args is None: self.state_args = state_args = {} state_dict = self.properties_in[ self.flowsheet().config.time.first() ].define_port_members() for k in state_dict.keys(): if state_dict[k].is_indexed(): state_args[k] = {} for m in state_dict[k].keys(): state_args[k][m] = state_dict[k][m].value else: state_args[k] = state_dict[k].value state_args_out = deepcopy(state_args) for j in self.properties_out.component_list: if j == "H2O": state_args_out["flow_mass_phase_comp"][("Liq", j)] = ( state_args["flow_mass_phase_comp"][("Liq", j)] * self.recovery_frac_mass_H2O.value ) continue else: state_args_out["flow_mass_phase_comp"][("Liq", j)] = state_args[ "flow_mass_phase_comp" ][("Liq", j)] * (1 - self.removal_frac_mass_comp[j].value) self.properties_out.initialize( outlvl=outlvl, optarg=optarg, solver=solver, state_args=state_args_out, ) init_log.info("Initialization Step 1b Complete.") state_args_waste = deepcopy(state_args) for j in self.properties_byproduct.component_list: if j == "H2O": state_args_waste["flow_mass_phase_comp"][("Liq", j)] = state_args[ "flow_mass_phase_comp" ][("Liq", j)] * (1 - self.recovery_frac_mass_H2O.value) else: state_args_waste["flow_mass_phase_comp"][("Liq", j)] = ( state_args["flow_mass_phase_comp"][("Liq", j)] * self.removal_frac_mass_comp[j].value ) self.properties_byproduct.initialize( outlvl=outlvl, optarg=optarg, solver=solver, state_args=state_args_waste, ) init_log.info("Initialization Step 1c Complete.") # Solve unit with idaeslog.solver_log(solve_log, idaeslog.DEBUG) as slc: res = opt.solve(self, tee=slc.tee) if not check_optimal_termination(res): init_log.warning( f"Trouble solving unit model {self.name}, trying one more time" ) res = opt.solve(self, tee=slc.tee) init_log.info("Initialization Step 2 {}.".format(idaeslog.condition(res))) # --------------------------------------------------------------------- # Release Inlet state self.properties_in.release_state(flags, outlvl=outlvl) init_log.info("Initialization Complete: {}".format(idaeslog.condition(res))) if not check_optimal_termination(res): raise InitializationError(f"Unit model {self.name} failed to initialize")
def calculate_scaling_factors(self): super().calculate_scaling_factors() if iscale.get_scaling_factor(self.electrode_thickness) is None: iscale.set_scaling_factor(self.electrode_thickness, 1e3) if iscale.get_scaling_factor(self.electrode_mass) is None: iscale.set_scaling_factor(self.electrode_mass, 1) if iscale.get_scaling_factor(self.electrode_gap) is None: iscale.set_scaling_factor(self.electrode_gap, 10) if iscale.get_scaling_factor(self.electrolysis_time) is None: iscale.set_scaling_factor(self.electrolysis_time, 0.1) if iscale.get_scaling_factor(self.applied_current) is None: iscale.set_scaling_factor(self.applied_current, 1) if iscale.get_scaling_factor(self.current_efficiency) is None: iscale.set_scaling_factor(self.current_efficiency, 1) if iscale.get_scaling_factor(self.reactor_volume) is None: iscale.set_scaling_factor(self.reactor_volume, 0.1) if iscale.get_scaling_factor(self.ohmic_resistance) is None: iscale.set_scaling_factor(self.ohmic_resistance, 1e3) if iscale.get_scaling_factor(self.charge_loading_rate) is None: iscale.set_scaling_factor(self.charge_loading_rate, 1e-2) if iscale.get_scaling_factor(self.current_density) is None: iscale.set_scaling_factor(self.current_density, 1e-2) if iscale.get_scaling_factor(self.cell_voltage) is None: iscale.set_scaling_factor(self.cell_voltage, 0.1) if iscale.get_scaling_factor(self.overpotential) is None: iscale.set_scaling_factor(self.overpotential, 0.1) sf = iscale.get_scaling_factor(self.charge_loading_rate) iscale.constraint_scaling_transform(self.eq_charge_loading_rate, sf) sf = iscale.get_scaling_factor(self.ohmic_resistance) iscale.constraint_scaling_transform(self.eq_ohmic_resistance, sf) def _get_stream_table_contents(self, time_point=0): return create_stream_table_dataframe( { "Feed Inlet": self.inlet, "Liquid Outlet": self.outlet, "Byproduct Outlet": self.byproduct, }, time_point=time_point, ) def _get_performance_contents(self, time_point=0): var_dict = {} var_dict["Voltage Required"] = self.cell_voltage var_dict["Overpotential"] = self.overpotential var_dict["Applied Current"] = self.applied_current var_dict["Current Density"] = self.current_density var_dict["Charge Loading Rate"] = self.charge_loading_rate var_dict["Ohmic Resistance"] = self.ohmic_resistance var_dict["Coagulant Dose"] = self.coagulant_dose var_dict["Electrode Mass"] = self.electrode_mass var_dict["Electrode Volume"] = self.electrode_volume var_dict["Electrode Thickness"] = self.electrode_thickness var_dict["Electrode Gap"] = self.electrode_gap var_dict["Anode Area"] = self.anode_area var_dict["Cathode Area"] = self.cathode_area var_dict["Electrolysis Time"] = self.electrolysis_time var_dict["Current Efficiency"] = self.current_efficiency var_dict["Flocculation Basin Volume"] = self.floc_basin_vol var_dict["Flocculation Retention Time"] = self.floc_retention_time var_dict["EC Reactor Volume"] = self.reactor_volume if self.config.overpotential_calculation == OverpotentialCalculation.regression: var_dict["Overpotential Equation k1 Parameter"] = self.overpotential_k1 var_dict["Overpotential Equation k2 Parameter"] = self.overpotential_k2 if self.config.overpotential_calculation == OverpotentialCalculation.detailed: var_dict["Electrode Material Tafel Slope Anode"] = self.tafel_slope_anode var_dict["Electrode Material Tafel Slope Cathode"] = ( self.tafel_slope_cathode ) expr_dict = {} expr_dict["Conductivity"] = self.conductivity expr_dict["Total Power Required"] = self.power_required expr_dict["Power Density Faradaic"] = self.power_density_faradaic expr_dict["Power Density Total"] = self.power_density_total expr_dict["Total Electrode Area"] = self.electrode_area_total if self.config.overpotential_calculation == OverpotentialCalculation.detailed: expr_dict["Temp. Adjusted Std Anode Potential"] = ( self.anode_cell_potential_temp_adj ) expr_dict["Temp. Adjusted Std Cathode Potential"] = ( self.cathode_cell_potential_temp_adj ) expr_dict["Anode Cell Potential"] = self.anode_cell_potential expr_dict["Cathode Cell Potential"] = self.cathode_cell_potential expr_dict["Anode Activation Overpotential"] = self.anode_overpotential expr_dict["Cathode Activation Overpotential"] = self.cathode_overpotential param_dict = {} for s in self.config.property_package.solute_set: param_dict["Removal Fraction " + s] = self.removal_frac_mass_comp[s] param_dict["Water Recovery Fraction"] = self.recovery_frac_mass_H2O param_dict["TDS to Conductivity Conversion"] = self.tds_to_cond_conversion param_dict["Standard Temperature"] = self.standard_temperature param_dict["Electrode Material Density"] = self.density_electrode_material param_dict["Electrode Material MW"] = self.mw_electrode_material param_dict["Electrode Material Stoichiometric Coefficient"] = self.stoic_coeff param_dict["Electrode Material Charge Transfer Number"] = ( self.charge_transfer_number ) if self.config.overpotential_calculation == OverpotentialCalculation.detailed: param_dict["Electrode Material Standard Anodic Potential"] = ( self.anode_cell_potential_std ) param_dict["Electrode Material Anodic Exchange Current Density"] = ( self.anodic_exchange_current_density ) param_dict["Electrode Material Anodic Entropy Change"] = ( self.anode_entropy_change_std ) param_dict["Electrode Material Standard Cathodic Potential"] = ( self.cathode_cell_potential_std ) param_dict["Electrode Material Cathodic Exchange Current Density"] = ( self.cathodic_exchange_current_density ) param_dict["Electrode Material Cathodic Entropy Change"] = ( self.cathode_entropy_change_std ) return {"vars": var_dict, "params": param_dict, "exprs": expr_dict} @property def default_costing_method(self): return cost_electrocoagulation