Source code for prommis.properties.sulfuric_acid_leaching_properties

#####################################################################################################
# “PrOMMiS” was produced under the DOE Process Optimization and Modeling for Minerals Sustainability
# (“PrOMMiS”) initiative, and is copyright (c) 2023-2026 by the software owners: The Regents of the
# University of California, through Lawrence Berkeley National Laboratory, et al. All rights reserved.
# Please see the files COPYRIGHT.md and LICENSE.md for full copyright and license information.
#####################################################################################################
"""
Initial property package for REE leach solutions from coal refuse.

Authors: Andrew Lee
"""

from pyomo.environ import (
    Constraint,
    Param,
    PositiveReals,
    Reals,
    Set,
    units,
    Var,
    value,
)

from idaes.core import (
    Component,
    MaterialFlowBasis,
    Phase,
    PhysicalParameterBlock,
    StateBlock,
    StateBlockData,
    declare_process_block_class,
)
from idaes.core.util.initialization import fix_state_vars
from idaes.core.util.misc import add_object_reference
from idaes.core.scaling import CustomScalerBase

contaminant_list = ["Fe", "Al", "Ca"]
ree_list = ["Sc", "Y", "La", "Ce", "Pr", "Nd", "Sm", "Gd", "Dy"]


[docs] class SulfuricAcidLeachingPropertiesScaler(CustomScalerBase): """ Scaler for leach solution property package. """ CONFIG = CustomScalerBase.CONFIG DEFAULT_SCALING_FACTORS = { "flow_vol": 1e-2, "pressure": 1e-5, "temperature": 1 / 300, "conc_mass_comp[H2O]": 1e-6, "conc_mass_comp[H]": 1e-1, "conc_mass_comp[SO4]": 1e-2, "conc_mass_comp[HSO4]": 1e-3, "conc_mass_comp[Cl]": 1e-3, # Use a large scaling factor for pH # to encourage the solver to take # smaller steps "pH_phase": 10, } for ree in ree_list: DEFAULT_SCALING_FACTORS[f"conc_mass_comp[{ree}]"] = 10 for contaminant in contaminant_list: DEFAULT_SCALING_FACTORS[f"conc_mass_comp[{contaminant}]"] = 1e-2
[docs] def variable_scaling_routine( self, model, overwrite: bool = False, submodel_scalers: dict = None ): # Scale state variables self.scale_variable_by_default(model.flow_vol, overwrite=overwrite) self.scale_variable_by_default(model.pressure, overwrite=overwrite) self.scale_variable_by_default(model.temperature, overwrite=overwrite) for idx, var in model.conc_mass_comp.items(): self.scale_variable_by_default(var, overwrite=overwrite) # Scale other variables params = model.params if model.is_property_constructed("pH_phase"): self.scale_variable_by_default( model.pH_phase["liquid"], overwrite=overwrite ) for idx, var in model.conc_mol_comp.items(): sf = self.get_scaling_factor(model.conc_mass_comp[idx]) * value( units.convert(params.mw[idx], to_units=units.mg / units.mol) ) self.set_variable_scaling_factor(var, sf, overwrite=overwrite)
[docs] def constraint_scaling_routine( self, model, overwrite: bool = False, submodel_scalers: dict = None ): for idx, condata in model.molar_concentration_constraint.items(): self.scale_constraint_by_component( condata, model.conc_mass_comp[idx], overwrite=overwrite ) if model.is_property_constructed("pH_phase"): self.scale_constraint_by_component( model.pH_constraint["liquid"], model.conc_mol_comp["H"], overwrite=overwrite, ) if model.is_property_constructed("h2o_concentration"): self.scale_constraint_by_component( model.h2o_concentration, model.conc_mass_comp["H2O"], overwrite=overwrite, ) if model.is_property_constructed("hso4_dissociation"): sf = self.get_scaling_factor( model.conc_mol_comp["H"] ) * self.get_scaling_factor(model.conc_mol_comp["SO4"]) self.set_constraint_scaling_factor( model.hso4_dissociation, sf, overwrite=overwrite )
# ----------------------------------------------------------------------------- # Leach solution property package
[docs] @declare_process_block_class("SulfuricAcidLeachingParameters") class SulfuricAcidLeachingParameterData(PhysicalParameterBlock): """ Property package for leach solution generated by West Kentucky No. 13 coal and H2SO4. Includes the following components: * Acid components: H2O, H, HSO4, SO4 * Rare Earths: Sc, Y, La, Ce, Pr, Nd, Sm, Gd, Dy * Impurities: Al, Ca, Fe First dissociation of H2SO4 is assumed to be complete. Second dissociation governed by equilibrium (Ka2) - inherent reaction. """
[docs] def build(self): super().build() self.liquid = Phase() # Solvent self.H2O = Component() # Acid related species self.H = Component() self.HSO4 = Component() self.SO4 = Component() self.Cl = Component() # REEs self.Sc = Component() self.Y = Component() self.La = Component() self.Ce = Component() self.Pr = Component() self.Nd = Component() self.Sm = Component() self.Gd = Component() self.Dy = Component() # Contaminants self.Al = Component() self.Ca = Component() self.Fe = Component() self.mw = Param( self.component_list, units=units.kg / units.mol, initialize={ "H2O": 18e-3, "H": 1e-3, "HSO4": 97e-3, "SO4": 96e-3, "Cl": 35.453e-3, "Sc": 44.946e-3, "Y": 88.905e-3, "La": 138.905e-3, "Ce": 140.116e-3, "Pr": 140.907e-3, "Nd": 144.242e-3, "Sm": 150.36e-3, "Gd": 157.25e-3, "Dy": 162.50e-3, "Al": 26.982e-3, "Ca": 40.078e-3, "Fe": 55.845e-3, }, ) # Inherent reaction for partial dissociation of HSO4 self._has_inherent_reactions = True self.inherent_reaction_idx = Set(initialize=["Ka2"]) self.inherent_reaction_stoichiometry = { ("Ka2", "liquid", "H"): 1, ("Ka2", "liquid", "HSO4"): -1, ("Ka2", "liquid", "SO4"): 1, ("Ka2", "liquid", "H2O"): 0, ("Ka2", "liquid", "Cl"): 0, ("Ka2", "liquid", "Sc"): 0, ("Ka2", "liquid", "Y"): 0, ("Ka2", "liquid", "La"): 0, ("Ka2", "liquid", "Ce"): 0, ("Ka2", "liquid", "Pr"): 0, ("Ka2", "liquid", "Nd"): 0, ("Ka2", "liquid", "Sm"): 0, ("Ka2", "liquid", "Gd"): 0, ("Ka2", "liquid", "Dy"): 0, ("Ka2", "liquid", "Al"): 0, ("Ka2", "liquid", "Ca"): 0, ("Ka2", "liquid", "Fe"): 0, } self.Ka2 = Param( initialize=10**-1.99, mutable=True, units=units.mol / units.L, ) # Assume dilute acid, density of pure water self.dens_mass = Param( initialize=1, units=units.kg / units.litre, mutable=True, ) # Heat capacity of water self.cp_mol = Param( mutable=True, initialize=75.327, doc="Molar heat capacity of water [J/mol.K]", units=units.J / units.mol / units.K, ) self.temperature_ref = Param( within=PositiveReals, mutable=True, default=298.15, doc="Reference temperature [K]", units=units.K, ) self._state_block_class = SulfuricAcidLeachingStateBlock
[docs] @classmethod def define_metadata(cls, obj): obj.add_properties( { "flow_vol": {"method": None}, "conc_mass_comp": {"method": None}, "conc_mol_comp": {"method": None}, "dens_mass": {"method": "_dens_mass"}, } ) obj.define_custom_properties( { "pH_phase": {"method": "_pH_phase"}, } ) obj.add_default_units( { "time": units.hour, "length": units.m, "mass": units.kg, "amount": units.mol, "temperature": units.K, } )
class _SulfuricAcidLeachingStateBlock(StateBlock): default_scaler = SulfuricAcidLeachingPropertiesScaler def fix_initialization_states(self): """ Fixes state variables for state blocks. Returns: None """ # Fix state variables fix_state_vars(self) # Deactivate inherent reaction # and water density constraints for sbd in self.values(): if not sbd.config.defined_state: sbd.conc_mass_comp["H2O"].unfix() sbd.conc_mass_comp["HSO4"].unfix()
[docs] @declare_process_block_class( "SulfuricAcidLeachingStateBlock", block_class=_SulfuricAcidLeachingStateBlock ) class SulfuricAcidLeachingStateBlockData(StateBlockData): """ State block for leach solution of West Kentucky No. 13 coal by H2SO4. """ default_scaler = SulfuricAcidLeachingPropertiesScaler
[docs] def build(self): super().build() self.flow_vol = Var( units=units.L / units.hour, initialize=10, bounds=(1e-8, None), ) self.conc_mass_comp = Var( self.params.component_list, units=units.mg / units.L, initialize=1e-8, bounds=(1e-20, None), ) self.conc_mol_comp = Var( self.params.component_list, units=units.mol / units.L, initialize=1e-5, bounds=(1e-20, None), ) self.temperature = Var( domain=Reals, initialize=298.15, bounds=(298.1, None), doc="State temperature [K]", units=units.K, ) self.pressure = Var( domain=Reals, initialize=101325.0, bounds=(1e3, 1e6), doc="State pressure [Pa]", units=units.Pa, ) # Concentration conversion constraint @self.Constraint(self.params.component_list) def molar_concentration_constraint(b, j): return ( units.convert( b.conc_mol_comp[j] * b.params.mw[j], to_units=units.mg / units.litre ) == b.conc_mass_comp[j] ) if not self.config.defined_state: # Concentration of H2O based on assumed density self.h2o_concentration = Constraint( expr=self.conc_mass_comp["H2O"] == 1e6 * units.mg / units.L ) # Equilibrium for partial dissociation of HSO4 self.hso4_dissociation = Constraint( expr=self.conc_mol_comp["HSO4"] * self.params.Ka2 == self.conc_mol_comp["H"] * self.conc_mol_comp["SO4"] )
def _dens_mass(self): add_object_reference(self, "dens_mass", self.params.dens_mass) def _pH_phase(self): self.pH_phase = Var( self.params.phase_list, initialize=2, doc="pH of the solution" ) @self.Constraint(self.phase_list) def pH_constraint(b, p): return 10 ** (-b.pH_phase[p]) == b.conc_mol_comp["H"] * units.L / units.mol
[docs] def get_material_flow_terms(self, p, j): # Note conversion to mol/hour if j == "H2O": # Assume constant density of 1 kg/L return units.convert( self.flow_vol * self.params.dens_mass / self.params.mw[j], to_units=units.mol / units.hour, ) else: # Need to convert from moles to mass return units.convert( self.flow_vol * self.conc_mass_comp[j] / self.params.mw[j], to_units=units.mol / units.hour, )
[docs] def get_material_density_terms(self, p, j): if j == "H2O": return units.convert( self.params.dens_mass / self.params.mw[j], to_units=units.mol / units.m**3, ) else: return units.convert( self.conc_mass_comp[j] / self.params.mw[j], to_units=units.mol / units.m**3, )
[docs] def get_material_flow_basis(self): return MaterialFlowBasis.molar
[docs] def define_state_vars(self): return { "flow_vol": self.flow_vol, "conc_mass_comp": self.conc_mass_comp, "temperature": self.temperature, "pressure": self.pressure, }