#####################################################################################################
# “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 precipitate.
Authors: Alejandro Garciadiego
"""
from pyomo.environ import Param, Set, Var, units
import idaes.core.util.scaling as iscale
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
# -----------------------------------------------------------------------------
# Precipitate solution property package
[docs]
@declare_process_block_class("AqueousParameter")
class AqueousParameterData(PhysicalParameterBlock):
"""
Property package for aqueous solution generated in oxalate precipitator.
Includes the following components:
* Rare Earths: Sc, Y, La, Ce, Pr, Nd, Sm, Gd, Dy
* Impurities: Al, Ca, Fe
Assumes the equilibrium reaction has a fixed rate and fixed partition coefficients
based on:
Wang, Y., Ziemkiewicz, P., Noble, A., A Hybrid Experimental and Theoretical Approach
to Optimize Recovery of Rare Earth Elements from Acid Mine Drainage,
Minerals, 2022, 12. 236
self.split can be substituted by surrogate model
"""
[docs]
def build(self):
super().build()
self.liquid = Phase()
# Solvents
self.H2O = Component()
# Contaminants
self.Al = Component()
self.Ca = Component()
self.Fe = Component()
self.H = Component()
self.Cl = Component()
self.HSO4 = Component()
self.SO4 = 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()
# parameter based on pH 1.28
# TODO add surrogate model/equation
self.split = Param(
self.component_list,
units=units.kg / units.kg,
initialize={
"H2O": 1e-20,
"Sc": 31.61,
"Y": 74.46,
"La": 51.51,
"Ce": 68.07,
"Pr": 78,
"Nd": 81.55,
"Sm": 87.35,
"Gd": 88.01,
"Dy": 87.16,
"Al": 0.9,
"Ca": 20.50,
"Fe": 2.44,
"H": 1e-20,
"Cl": 1e-20,
"HSO4": 1e-20,
"SO4": 1e-20,
},
)
self.mw = Param(
self.component_list,
units=units.kg / units.mol,
initialize={
"H2O": 18e-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,
"H": 1.008e-3,
"Cl": 35.453e-3,
"HSO4": 97.064e-3,
"SO4": 96.056e-3,
},
)
self.dissolved_elements = Set(
initialize=[
"Al",
"Ca",
"Fe",
"Sc",
"Y",
"La",
"Ce",
"Pr",
"Nd",
"Sm",
"Gd",
"Dy",
"H",
"Cl",
"HSO4",
"SO4",
"H2O",
]
)
# Assume dilute acid, density of pure water
self.dens_mass = Param(
initialize=1,
units=units.kg / units.litre,
mutable=True,
)
self._state_block_class = AqueousStateBlock
class _AqueousStateBlock(StateBlock):
def fix_initialization_states(self):
"""
Fixes state variables for state blocks.
Returns:
None
"""
# Fix state variables
fix_state_vars(self)
[docs]
@declare_process_block_class("AqueousStateBlock", block_class=_AqueousStateBlock)
class AqueousStateBlockkData(StateBlockData):
"""
State block for aqueous solution generated in oxalate precipitator.
"""
[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.dissolved_elements,
units=units.mg / units.L,
initialize=1e-5,
bounds=(1e-20, None),
)
self.flow_mol_comp = Var(
self.params.dissolved_elements,
units=units.mol / units.hour,
initialize=1e-5,
bounds=(1e-20, None),
)
self.conc_mol_comp = Var(
self.params.dissolved_elements,
units=units.mol / units.L,
initialize=1e-5,
bounds=(1e-20, None),
)
# Concentration conversion constraint
@self.Constraint(self.params.dissolved_elements)
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]
)
# Concentration conversion constraint
@self.Constraint(self.params.dissolved_elements)
def flow_mol_constraint(b, j):
return (
units.convert(
b.flow_vol * b.conc_mass_comp[j] / b.params.mw[j],
to_units=units.mol / units.hour,
)
== b.flow_mol_comp[j]
)
iscale.set_scaling_factor(self.flow_vol, 1e1)
iscale.set_scaling_factor(self.conc_mass_comp, 1e2)
iscale.set_scaling_factor(self.flow_mol_comp, 1e3)
iscale.set_scaling_factor(self.conc_mol_comp, 1e5)
def _dens_mass(self):
add_object_reference(self, "dens_mass", self.params.dens_mass)
[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 self.flow_vol * self.params.dens_mass / self.params.mw[j]
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_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,
}