UKy Flowsheet Tutorial#

This tutorial will show how to build, initialize, and simulate the West Kentucky No.13 Coal Refuse flowsheet. The inputs into this flowsheet are case study-specific, so users should not expect the flowsheet to solve if the values are significantly altered.

uky_flowsheet.png

Step 1: Import the necessary tools#

# Import the essentials from Pyomo
from pyomo.environ import (
    ConcreteModel,
    Constraint,
    SolverFactory,
    Suffix,
    TransformationFactory,
    Var,
    units,
)
from pyomo.network import Arc, SequentialDecomposition
from pyomo.util.check_units import assert_units_consistent

# Import the essentials from IDAES
from idaes.models.properties.modular_properties.base.generic_property import (
    GenericParameterBlock,
)
from idaes.models_extra.power_generation.properties.natural_gas_PR import (
    EosType,
    get_prop,
)
from idaes.core import (
    FlowDirection,
    FlowsheetBlock,
    MaterialBalanceType,
    MomentumBalanceType,
)
import idaes.logger as idaeslog

# Import initializtion and diagnostic tools from IDAES
from idaes.core.initialization import BlockTriangularizationInitializer
from idaes.core.util.initialization import propagate_state
from idaes.core.util.model_diagnostics import DiagnosticsToolbox
from idaes.core.util.model_statistics import degrees_of_freedom

# Import unit models from IDAES
from idaes.models.unit_models.feed import Feed, FeedInitializer
from idaes.models.unit_models.mixer import (
    Mixer,
    MixingType,
    MomentumMixingType,
    MixerInitializer,
)
from idaes.models.unit_models.mscontactor import MSContactor, MSContactorInitializer
from idaes.models.unit_models.product import Product, ProductInitializer
from idaes.models.unit_models.separator import (
    EnergySplittingType,
    Separator,
    SplittingType,
    SeparatorInitializer,
)
from idaes.models.unit_models.solid_liquid import SLSeparator

# Import the UKy-specific unit and property models
from prommis.leaching.leach_reactions import CoalRefuseLeachingReactions
from prommis.leaching.leach_solids_properties import CoalRefuseParameters
from prommis.leaching.leach_solution_properties import LeachSolutionParameters
from prommis.precipitate.precipitate_liquid_properties import AqueousParameter
from prommis.precipitate.precipitate_solids_properties import PrecipitateParameters
from prommis.precipitate.precipitator import Precipitator
from prommis.roasting.ree_oxalate_roaster import REEOxalateRoaster
from prommis.solvent_extraction.ree_og_distribution import REESolExOgParameters
from prommis.solvent_extraction.solvent_extraction import SolventExtraction

# Set up logger
_log = idaeslog.getLogger(__name__)

Step 2 Flowsheet building#

Step 2.1: Create Flowsheet#

Start by creating a pyomo model and a flowsheet.

m = ConcreteModel()

m.fs = FlowsheetBlock(dynamic=False)

Then begin assembling the unit, property, and reaction models section-by-section. These variables will be created in chronological order - beginning with the leaching section of the flowsheet and concluding with the product roasting section.

Step 2.2 Create variables for the leaching section#

Specify the necessary unit, property, and reaction models for the leaching section.

# Leaching property models
m.fs.leach_soln = LeachSolutionParameters()
m.fs.coal = CoalRefuseParameters()

# Leaching reaction model
m.fs.leach_rxns = CoalRefuseLeachingReactions()

# Leaching unit model
m.fs.leach = MSContactor(
    number_of_finite_elements=2,
    streams={
        "liquid": {
            "property_package": m.fs.leach_soln,
            "has_energy_balance": False,
            "has_pressure_balance": False,
        },
        "solid": {
            "property_package": m.fs.coal,
            "has_energy_balance": False,
            "has_pressure_balance": False,
        },
    },
    heterogeneous_reactions=m.fs.leach_rxns,
)

# Define leach tank volume
m.fs.leach.volume = Var(
    m.fs.time,
    m.fs.leach.elements,
    initialize=1,
    units=units.litre,
    doc="Volume of each finite element.",
)


# Define constraint for heterogeneous reaction extent
def rule_heterogeneous_reaction_extent(b, t, s, r):
    return (
        b.heterogeneous_reaction_extent[t, s, r]
        == b.heterogeneous_reactions[t, s].reaction_rate[r] * b.volume[t, s]
    )


m.fs.leach.heterogeneous_reaction_extent_constraint = Constraint(
    m.fs.time,
    m.fs.leach.elements,
    m.fs.leach_rxns.reaction_idx,
    rule=rule_heterogeneous_reaction_extent,
)

# Solid-liquid separator used to approximate a filter press
m.fs.sl_sep1 = SLSeparator(
    solid_property_package=m.fs.coal,
    liquid_property_package=m.fs.leach_soln,
    material_balance_type=MaterialBalanceType.componentTotal,
    # Ignore momentum balance since the property package does not have pressure or momentum terms
    momentum_balance_type=MomentumBalanceType.none,
    # Ignore energy split basis since the property package does not have temperature terms
    energy_split_basis=EnergySplittingType.none,
)

# Recycle loop mixer
m.fs.leach_mixer = Mixer(
    property_package=m.fs.leach_soln,
    num_inlets=3,
    inlet_list=["load_recycle", "scrub_recycle", "feed"],
    material_balance_type=MaterialBalanceType.componentTotal,
    # Ignore mixing type since the property package does not have enthalpy terms
    energy_mixing_type=MixingType.none,
    # Ignore momentum mixing since the property package does not have pressure or momentum terms
    momentum_mixing_type=MomentumMixingType.none,
)

# Define inlets into the flowsheet
m.fs.leach_liquid_feed = Feed(property_package=m.fs.leach_soln)
m.fs.leach_solid_feed = Feed(property_package=m.fs.coal)

# Define outlets from the flowsheet
m.fs.leach_filter_cake = Product(property_package=m.fs.coal)
m.fs.leach_filter_cake_liquid = Product(property_package=m.fs.leach_soln)

Step 2.3 Create variables for the solvent extraction section#

Specify the necessary unit, property, and reaction models for the solvent extraction section.

# Solvent extraction property models
m.fs.prop_o = REESolExOgParameters()

m.fs.rougher_org_make_up = Feed(property_package=m.fs.prop_o)

m.fs.solex_rougher_load = SolventExtraction(
    number_of_finite_elements=3,
    dynamic=False,
    aqueous_stream={
        "property_package": m.fs.leach_soln,
        "flow_direction": FlowDirection.forward,
        "has_energy_balance": False,
        "has_pressure_balance": False,
    },
    organic_stream={
        "property_package": m.fs.prop_o,
        "flow_direction": FlowDirection.backward,
        "has_energy_balance": False,
        "has_pressure_balance": False,
    },
    aqueous_to_organic=True,
)

# Define partition coefficients for each finite element in solex_rougher_load
m.fs.solex_rougher_load.partition_coefficient[1, "aqueous", "organic", "Al"] = 5.2 / 100
m.fs.solex_rougher_load.partition_coefficient[1, "aqueous", "organic", "Ca"] = 3.0 / 100
m.fs.solex_rougher_load.partition_coefficient[1, "aqueous", "organic", "Fe"] = (
    24.7 / 100
)
m.fs.solex_rougher_load.partition_coefficient[1, "aqueous", "organic", "Sc"] = (
    99.9 / 100
)
m.fs.solex_rougher_load.partition_coefficient[1, "aqueous", "organic", "Y"] = 99.9 / 100
m.fs.solex_rougher_load.partition_coefficient[1, "aqueous", "organic", "La"] = (
    32.4 / 100
)
m.fs.solex_rougher_load.partition_coefficient[1, "aqueous", "organic", "Ce"] = (
    58.2 / 100
)
m.fs.solex_rougher_load.partition_coefficient[1, "aqueous", "organic", "Pr"] = (
    58.2 / 100
)
m.fs.solex_rougher_load.partition_coefficient[1, "aqueous", "organic", "Nd"] = (
    87.6 / 100
)
m.fs.solex_rougher_load.partition_coefficient[1, "aqueous", "organic", "Sm"] = (
    99.9 / 100
)
m.fs.solex_rougher_load.partition_coefficient[1, "aqueous", "organic", "Gd"] = (
    69.8 / 100
)
m.fs.solex_rougher_load.partition_coefficient[1, "aqueous", "organic", "Dy"] = (
    96.6 / 100
)

m.fs.solex_rougher_load.partition_coefficient[2, "aqueous", "organic", "Al"] = 4.9 / 100
m.fs.solex_rougher_load.partition_coefficient[2, "aqueous", "organic", "Ca"] = (
    12.3 / 100
)
m.fs.solex_rougher_load.partition_coefficient[2, "aqueous", "organic", "Fe"] = 6.4 / 100
m.fs.solex_rougher_load.partition_coefficient[2, "aqueous", "organic", "Sc"] = (
    16.7 / 100
)
m.fs.solex_rougher_load.partition_coefficient[2, "aqueous", "organic", "Y"] = 99.9 / 100
m.fs.solex_rougher_load.partition_coefficient[2, "aqueous", "organic", "La"] = (
    23.2 / 100
)
m.fs.solex_rougher_load.partition_coefficient[2, "aqueous", "organic", "Ce"] = (
    24.9 / 100
)
m.fs.solex_rougher_load.partition_coefficient[2, "aqueous", "organic", "Pr"] = (
    15.1 / 100
)
m.fs.solex_rougher_load.partition_coefficient[2, "aqueous", "organic", "Nd"] = (
    99.9 / 100
)
m.fs.solex_rougher_load.partition_coefficient[2, "aqueous", "organic", "Sm"] = (
    99.9 / 100
)
m.fs.solex_rougher_load.partition_coefficient[2, "aqueous", "organic", "Gd"] = 7.6 / 100
m.fs.solex_rougher_load.partition_coefficient[2, "aqueous", "organic", "Dy"] = 5.0 / 100

m.fs.solex_rougher_load.partition_coefficient[3, "aqueous", "organic", "Al"] = 4.9 / 100
m.fs.solex_rougher_load.partition_coefficient[3, "aqueous", "organic", "Ca"] = (
    12.3 / 100
)
m.fs.solex_rougher_load.partition_coefficient[3, "aqueous", "organic", "Fe"] = 6.4 / 100
m.fs.solex_rougher_load.partition_coefficient[3, "aqueous", "organic", "Sc"] = (
    16.7 / 100
)
m.fs.solex_rougher_load.partition_coefficient[3, "aqueous", "organic", "Y"] = 99.9 / 100
m.fs.solex_rougher_load.partition_coefficient[3, "aqueous", "organic", "La"] = (
    23.2 / 100
)
m.fs.solex_rougher_load.partition_coefficient[3, "aqueous", "organic", "Ce"] = (
    24.9 / 100
)
m.fs.solex_rougher_load.partition_coefficient[3, "aqueous", "organic", "Pr"] = (
    15.1 / 100
)
m.fs.solex_rougher_load.partition_coefficient[3, "aqueous", "organic", "Nd"] = (
    99.9 / 100
)
m.fs.solex_rougher_load.partition_coefficient[3, "aqueous", "organic", "Sm"] = (
    99.9 / 100
)
m.fs.solex_rougher_load.partition_coefficient[3, "aqueous", "organic", "Gd"] = 7.6 / 100
m.fs.solex_rougher_load.partition_coefficient[3, "aqueous", "organic", "Dy"] = 5.0 / 100

# Dilute HCl feed
m.fs.acid_feed1 = Feed(property_package=m.fs.leach_soln)

m.fs.solex_rougher_scrub = SolventExtraction(
    number_of_finite_elements=1,
    dynamic=False,
    aqueous_stream={
        "property_package": m.fs.leach_soln,
        "flow_direction": FlowDirection.backward,
        "has_energy_balance": False,
        "has_pressure_balance": False,
    },
    organic_stream={
        "property_package": m.fs.prop_o,
        "flow_direction": FlowDirection.forward,
        "has_energy_balance": False,
        "has_pressure_balance": False,
    },
    aqueous_to_organic=False,
)

# Define partition coefficients for each finite element in solex_rougher_scrub
m.fs.solex_rougher_scrub.partition_coefficient[1, "aqueous", "organic", "Al"] = (
    100 - 0.12
) / 100
m.fs.solex_rougher_scrub.partition_coefficient[1, "aqueous", "organic", "Ca"] = (
    100 - 0.55
) / 100
m.fs.solex_rougher_scrub.partition_coefficient[1, "aqueous", "organic", "Fe"] = (
    100 - 0.007
) / 100
m.fs.solex_rougher_scrub.partition_coefficient[1, "aqueous", "organic", "Sc"] = (
    100 - 99.9
) / 100
m.fs.solex_rougher_scrub.partition_coefficient[1, "aqueous", "organic", "Y"] = (
    100 - 99.9
) / 100
m.fs.solex_rougher_scrub.partition_coefficient[1, "aqueous", "organic", "La"] = (
    100 - 99.8
) / 100
m.fs.solex_rougher_scrub.partition_coefficient[1, "aqueous", "organic", "Ce"] = (
    100 - 99.9
) / 100
m.fs.solex_rougher_scrub.partition_coefficient[1, "aqueous", "organic", "Pr"] = (
    100 - 99.9
) / 100
m.fs.solex_rougher_scrub.partition_coefficient[1, "aqueous", "organic", "Nd"] = (
    100 - 99.9
) / 100
m.fs.solex_rougher_scrub.partition_coefficient[1, "aqueous", "organic", "Sm"] = (
    100 - 99.9
) / 100
m.fs.solex_rougher_scrub.partition_coefficient[1, "aqueous", "organic", "Gd"] = (
    100 - 99.9
) / 100
m.fs.solex_rougher_scrub.partition_coefficient[1, "aqueous", "organic", "Dy"] = (
    100 - 99.9
) / 100

m.fs.acid_feed2 = Feed(property_package=m.fs.leach_soln)

m.fs.solex_rougher_strip = SolventExtraction(
    number_of_finite_elements=2,
    dynamic=False,
    aqueous_stream={
        "property_package": m.fs.leach_soln,
        "flow_direction": FlowDirection.backward,
        "has_energy_balance": False,
        "has_pressure_balance": False,
    },
    organic_stream={
        "property_package": m.fs.prop_o,
        "flow_direction": FlowDirection.forward,
        "has_energy_balance": False,
        "has_pressure_balance": False,
    },
    aqueous_to_organic=False,
)

# Define partition coefficients for each finite element in solex_rougher_strip
m.fs.solex_rougher_strip.partition_coefficient[:, "aqueous", "organic", "Al"] = (
    100 - 0.5
) / 100
m.fs.solex_rougher_strip.partition_coefficient[:, "aqueous", "organic", "Ca"] = (
    100 - 0.5
) / 100
m.fs.solex_rougher_strip.partition_coefficient[:, "aqueous", "organic", "Fe"] = (
    100 - 0.5
) / 100
m.fs.solex_rougher_strip.partition_coefficient[:, "aqueous", "organic", "Sc"] = (
    100 - 98.5
) / 100
m.fs.solex_rougher_strip.partition_coefficient[:, "aqueous", "organic", "Y"] = (
    100 - 0.5
) / 100
m.fs.solex_rougher_strip.partition_coefficient[:, "aqueous", "organic", "La"] = (
    100 - 0.5
) / 100
m.fs.solex_rougher_strip.partition_coefficient[:, "aqueous", "organic", "Ce"] = (
    100 - 0.5
) / 100
m.fs.solex_rougher_strip.partition_coefficient[:, "aqueous", "organic", "Pr"] = (
    100 - 0.5
) / 100
m.fs.solex_rougher_strip.partition_coefficient[:, "aqueous", "organic", "Nd"] = (
    100 - 0.5
) / 100
m.fs.solex_rougher_strip.partition_coefficient[:, "aqueous", "organic", "Sm"] = (
    100 - 0.5
) / 100
m.fs.solex_rougher_strip.partition_coefficient[:, "aqueous", "organic", "Gd"] = (
    100 - 0.5
) / 100
m.fs.solex_rougher_strip.partition_coefficient[:, "aqueous", "organic", "Dy"] = (
    100 - 0.5
) / 100

# Separator for organic stream
m.fs.rougher_sep = Separator(
    property_package=m.fs.prop_o,
    outlet_list=["recycle", "purge"],
    split_basis=SplittingType.totalFlow,
    material_balance_type=MaterialBalanceType.componentTotal,
    momentum_balance_type=MomentumBalanceType.none,
    energy_split_basis=EnergySplittingType.none,
)
m.fs.rougher_mixer = Mixer(
    property_package=m.fs.prop_o,
    num_inlets=2,
    inlet_list=["make_up", "recycle"],
    material_balance_type=MaterialBalanceType.componentTotal,
    energy_mixing_type=MixingType.none,
    momentum_mixing_type=MomentumMixingType.none,
)

# Separators for aqueous streams
m.fs.load_sep = Separator(
    property_package=m.fs.leach_soln,
    outlet_list=["recycle", "purge"],
    split_basis=SplittingType.totalFlow,
    material_balance_type=MaterialBalanceType.componentTotal,
    momentum_balance_type=MomentumBalanceType.none,
    energy_split_basis=EnergySplittingType.none,
)
m.fs.scrub_sep = Separator(
    property_package=m.fs.leach_soln,
    outlet_list=["recycle", "purge"],
    split_basis=SplittingType.totalFlow,
    material_balance_type=MaterialBalanceType.componentTotal,
    momentum_balance_type=MomentumBalanceType.none,
    energy_split_basis=EnergySplittingType.none,
)

m.fs.sc_circuit_purge = Product(property_package=m.fs.prop_o)

m.fs.solex_cleaner_load = SolventExtraction(
    number_of_finite_elements=3,
    dynamic=False,
    aqueous_stream={
        "property_package": m.fs.leach_soln,
        "flow_direction": FlowDirection.forward,
        "has_energy_balance": False,
        "has_pressure_balance": False,
    },
    organic_stream={
        "property_package": m.fs.prop_o,
        "flow_direction": FlowDirection.backward,
        "has_energy_balance": False,
        "has_pressure_balance": False,
    },
    aqueous_to_organic=True,
)

# Define partition coefficients for each finite element in solex_cleaner_load
m.fs.solex_cleaner_load.partition_coefficient[:, "aqueous", "organic", "Al"] = 3.6 / 100
m.fs.solex_cleaner_load.partition_coefficient[:, "aqueous", "organic", "Ca"] = 3.7 / 100
m.fs.solex_cleaner_load.partition_coefficient[:, "aqueous", "organic", "Fe"] = 2.1 / 100
m.fs.solex_cleaner_load.partition_coefficient[:, "aqueous", "organic", "Sc"] = (
    99.9 / 100
)
m.fs.solex_cleaner_load.partition_coefficient[:, "aqueous", "organic", "Y"] = 99.9 / 100
m.fs.solex_cleaner_load.partition_coefficient[:, "aqueous", "organic", "La"] = (
    75.2 / 100
)
m.fs.solex_cleaner_load.partition_coefficient[:, "aqueous", "organic", "Ce"] = (
    95.7 / 100
)
m.fs.solex_cleaner_load.partition_coefficient[:, "aqueous", "organic", "Pr"] = (
    96.5 / 100
)
m.fs.solex_cleaner_load.partition_coefficient[:, "aqueous", "organic", "Nd"] = (
    99.2 / 100
)
m.fs.solex_cleaner_load.partition_coefficient[:, "aqueous", "organic", "Sm"] = (
    99.9 / 100
)
m.fs.solex_cleaner_load.partition_coefficient[:, "aqueous", "organic", "Gd"] = (
    98.6 / 100
)
m.fs.solex_cleaner_load.partition_coefficient[:, "aqueous", "organic", "Dy"] = (
    99.9 / 100
)

m.fs.solex_cleaner_strip = SolventExtraction(
    number_of_finite_elements=3,
    dynamic=False,
    aqueous_stream={
        "property_package": m.fs.leach_soln,
        "flow_direction": FlowDirection.backward,
        "has_energy_balance": False,
        "has_pressure_balance": False,
    },
    organic_stream={
        "property_package": m.fs.prop_o,
        "flow_direction": FlowDirection.forward,
        "has_energy_balance": False,
        "has_pressure_balance": False,
    },
    aqueous_to_organic=False,
)
m.fs.solex_cleaner_strip.partition_coefficient[:, "aqueous", "organic", "Al"] = (
    100 - 0.5
) / 100
m.fs.solex_cleaner_strip.partition_coefficient[:, "aqueous", "organic", "Ca"] = (
    100 - 0.5
) / 100
m.fs.solex_cleaner_strip.partition_coefficient[:, "aqueous", "organic", "Fe"] = (
    100 - 5
) / 100
m.fs.solex_cleaner_strip.partition_coefficient[:, "aqueous", "organic", "Sc"] = (
    100 - 98.5
) / 100
m.fs.solex_cleaner_strip.partition_coefficient[:, "aqueous", "organic", "Y"] = (
    100 - 0.5
) / 100
m.fs.solex_cleaner_strip.partition_coefficient[:, "aqueous", "organic", "La"] = (
    100 - 0.5
) / 100
m.fs.solex_cleaner_strip.partition_coefficient[:, "aqueous", "organic", "Ce"] = (
    100 - 0.5
) / 100
m.fs.solex_cleaner_strip.partition_coefficient[:, "aqueous", "organic", "Pr"] = (
    100 - 0.5
) / 100
m.fs.solex_cleaner_strip.partition_coefficient[:, "aqueous", "organic", "Nd"] = (
    100 - 0.5
) / 100
m.fs.solex_cleaner_strip.partition_coefficient[:, "aqueous", "organic", "Sm"] = (
    100 - 0.5
) / 100
m.fs.solex_cleaner_strip.partition_coefficient[:, "aqueous", "organic", "Gd"] = (
    100 - 0.5
) / 100
m.fs.solex_cleaner_strip.partition_coefficient[:, "aqueous", "organic", "Dy"] = (
    100 - 0.5
) / 100

m.fs.cleaner_org_make_up = Feed(property_package=m.fs.prop_o)

# Separator and mixer for organic stream
m.fs.cleaner_sep = Separator(
    property_package=m.fs.prop_o,
    outlet_list=["recycle", "purge"],
    split_basis=SplittingType.totalFlow,
    material_balance_type=MaterialBalanceType.componentTotal,
    momentum_balance_type=MomentumBalanceType.none,
    energy_split_basis=EnergySplittingType.none,
)
m.fs.cleaner_mixer = Mixer(
    property_package=m.fs.prop_o,
    num_inlets=2,
    inlet_list=["make_up", "recycle"],
    material_balance_type=MaterialBalanceType.componentTotal,
    energy_mixing_type=MixingType.none,
    momentum_mixing_type=MomentumMixingType.none,
)

m.fs.leach_sx_mixer = Mixer(
    property_package=m.fs.leach_soln,
    num_inlets=2,
    inlet_list=["leach", "cleaner"],
    material_balance_type=MaterialBalanceType.componentTotal,
    energy_mixing_type=MixingType.none,
    momentum_mixing_type=MomentumMixingType.none,
)

m.fs.acid_feed3 = Feed(property_package=m.fs.leach_soln)
m.fs.cleaner_purge = Product(property_package=m.fs.prop_o)

Step 2.3 Create variables for the precipitation section#

Specify the necessary unit, property, and reaction models for the precipitation section.

# Precipitation property packages
m.fs.properties_aq = AqueousParameter()
m.fs.properties_solid = PrecipitateParameters()

# Precipitation unit model
m.fs.precipitator = Precipitator(
    property_package_aqueous=m.fs.properties_aq,
    property_package_precipitate=m.fs.properties_solid,
)

# Solid-liquid separator used to approximate a filter press
m.fs.sl_sep2 = SLSeparator(
    solid_property_package=m.fs.properties_solid,
    liquid_property_package=m.fs.leach_soln,
    material_balance_type=MaterialBalanceType.componentTotal,
    momentum_balance_type=MomentumBalanceType.none,
    energy_split_basis=EnergySplittingType.none,
)

m.fs.precip_sep = Separator(
    property_package=m.fs.leach_soln,
    outlet_list=["recycle", "purge"],
    split_basis=SplittingType.totalFlow,
    material_balance_type=MaterialBalanceType.componentTotal,
    momentum_balance_type=MomentumBalanceType.none,
    energy_split_basis=EnergySplittingType.none,
)

m.fs.precip_sx_mixer = Mixer(
    property_package=m.fs.leach_soln,
    num_inlets=2,
    inlet_list=["precip", "rougher"],
    material_balance_type=MaterialBalanceType.componentTotal,
    energy_mixing_type=MixingType.none,
    momentum_mixing_type=MomentumMixingType.none,
)

# Define outlets from the flowsheet
m.fs.precip_purge = Product(property_package=m.fs.properties_aq)

Step 2.3 Create variables for the product roaster section#

Specify the necessary unit, property, and reaction models for the roaster section.

# Define the relevant gas species
gas_species = {"O2", "H2O", "CO2", "N2"}

# Roaster property packages
m.fs.prop_gas = GenericParameterBlock(
    **get_prop(gas_species, ["Vap"], EosType.IDEAL),
    doc="gas property",
)
m.fs.prop_solid = PrecipitateParameters()

# Roaster unit model
m.fs.roaster = REEOxalateRoaster(
    property_package_gas=m.fs.prop_gas,
    property_package_precipitate_solid=m.fs.prop_solid,
    property_package_precipitate_liquid=m.fs.properties_aq,
    has_holdup=False,
    has_heat_transfer=True,
    has_pressure_change=True,
)

Step 2.4 Connect the unit models#

Next, use Pyomo arcs as streams to connect the units as portrayed in the flowsheet image above.

m.fs.leaching_sol_feed = Arc(
    source=m.fs.leach_solid_feed.outlet, destination=m.fs.leach.solid_inlet
)
m.fs.leaching_liq_feed = Arc(
    source=m.fs.leach_liquid_feed.outlet, destination=m.fs.leach_mixer.feed
)
m.fs.leaching_feed_mixture = Arc(
    source=m.fs.leach_mixer.outlet, destination=m.fs.leach.liquid_inlet
)
m.fs.leaching_solid_outlet = Arc(
    source=m.fs.leach.solid_outlet, destination=m.fs.sl_sep1.solid_inlet
)
m.fs.leaching_liquid_outlet = Arc(
    source=m.fs.leach.liquid_outlet, destination=m.fs.sl_sep1.liquid_inlet
)
m.fs.sl_sep1_solid_outlet = Arc(
    source=m.fs.sl_sep1.solid_outlet, destination=m.fs.leach_filter_cake.inlet
)
m.fs.sl_sep1_retained_liquid_outlet = Arc(
    source=m.fs.sl_sep1.retained_liquid_outlet,
    destination=m.fs.leach_filter_cake_liquid.inlet,
)
m.fs.sl_sep1_liquid_outlet = Arc(
    source=m.fs.sl_sep1.recovered_liquid_outlet,
    destination=m.fs.leach_sx_mixer.leach,
)
m.fs.sx_rougher_load_aq_feed = Arc(
    source=m.fs.leach_sx_mixer.outlet,
    destination=m.fs.solex_rougher_load.mscontactor.aqueous_inlet,
)
m.fs.sx_rougher_org_feed = Arc(
    source=m.fs.rougher_org_make_up.outlet, destination=m.fs.rougher_mixer.make_up
)
m.fs.sx_rougher_mixed_org_recycle = Arc(
    source=m.fs.rougher_mixer.outlet,
    destination=m.fs.solex_rougher_load.mscontactor.organic_inlet,
)
m.fs.sx_rougher_load_aq_outlet = Arc(
    source=m.fs.solex_rougher_load.mscontactor.aqueous_outlet,
    destination=m.fs.load_sep.inlet,
)
m.fs.sx_rougher_load_aq_recycle = Arc(
    source=m.fs.load_sep.recycle, destination=m.fs.leach_mixer.load_recycle
)
m.fs.sx_rougher_load_org_outlet = Arc(
    source=m.fs.solex_rougher_load.mscontactor.organic_outlet,
    destination=m.fs.solex_rougher_scrub.mscontactor.organic_inlet,
)
m.fs.sx_rougher_scrub_acid_feed = Arc(
    source=m.fs.acid_feed1.outlet,
    destination=m.fs.solex_rougher_scrub.mscontactor.aqueous_inlet,
)
m.fs.sx_rougher_scrub_aq_outlet = Arc(
    source=m.fs.solex_rougher_scrub.mscontactor.aqueous_outlet,
    destination=m.fs.scrub_sep.inlet,
)
m.fs.sx_rougher_scrub_aq_recycle = Arc(
    source=m.fs.scrub_sep.recycle, destination=m.fs.leach_mixer.scrub_recycle
)
m.fs.sx_rougher_scrub_org_outlet = Arc(
    source=m.fs.solex_rougher_scrub.mscontactor.organic_outlet,
    destination=m.fs.solex_rougher_strip.mscontactor.organic_inlet,
)
m.fs.sx_rougher_strip_acid_feed = Arc(
    source=m.fs.acid_feed2.outlet,
    destination=m.fs.solex_rougher_strip.mscontactor.aqueous_inlet,
)
m.fs.sx_rougher_strip_org_outlet = Arc(
    source=m.fs.solex_rougher_strip.mscontactor.organic_outlet,
    destination=m.fs.rougher_sep.inlet,
)
m.fs.sx_rougher_strip_org_purge = Arc(
    source=m.fs.rougher_sep.purge, destination=m.fs.sc_circuit_purge.inlet
)
m.fs.sx_rougher_strip_org_recycle = Arc(
    source=m.fs.rougher_sep.recycle, destination=m.fs.rougher_mixer.recycle
)
m.fs.sx_rougher_strip_aq_outlet = Arc(
    source=m.fs.solex_rougher_strip.mscontactor.aqueous_outlet,
    destination=m.fs.precip_sx_mixer.rougher,
)
m.fs.sx_cleaner_load_aq_feed = Arc(
    source=m.fs.precip_sx_mixer.outlet,
    destination=m.fs.solex_cleaner_load.mscontactor.aqueous_inlet,
)
m.fs.sx_cleaner_org_feed = Arc(
    source=m.fs.cleaner_org_make_up.outlet, destination=m.fs.cleaner_mixer.make_up
)
m.fs.sx_cleaner_mixed_org_recycle = Arc(
    source=m.fs.cleaner_mixer.outlet,
    destination=m.fs.solex_cleaner_load.mscontactor.organic_inlet,
)
m.fs.sx_cleaner_load_aq_outlet = Arc(
    source=m.fs.solex_cleaner_load.mscontactor.aqueous_outlet,
    destination=m.fs.leach_sx_mixer.cleaner,
)
m.fs.sx_cleaner_strip_acid_feed = Arc(
    source=m.fs.acid_feed3.outlet,
    destination=m.fs.solex_cleaner_strip.mscontactor.aqueous_inlet,
)
m.fs.sx_cleaner_load_org_outlet = Arc(
    source=m.fs.solex_cleaner_load.mscontactor.organic_outlet,
    destination=m.fs.solex_cleaner_strip.mscontactor.organic_inlet,
)
m.fs.sx_cleaner_strip_org_outlet = Arc(
    source=m.fs.solex_cleaner_strip.mscontactor.organic_outlet,
    destination=m.fs.cleaner_sep.inlet,
)
m.fs.sx_cleaner_strip_org_purge = Arc(
    source=m.fs.cleaner_sep.purge, destination=m.fs.cleaner_purge.inlet
)
m.fs.sx_cleaner_strip_org_recycle = Arc(
    source=m.fs.cleaner_sep.recycle, destination=m.fs.cleaner_mixer.recycle
)
m.fs.sx_cleaner_strip_aq_outlet = Arc(
    source=m.fs.solex_cleaner_strip.mscontactor.aqueous_outlet,
    destination=m.fs.precipitator.aqueous_inlet,
)
m.fs.precip_solid_outlet = Arc(
    source=m.fs.precipitator.precipitate_outlet,
    destination=m.fs.sl_sep2.solid_inlet,
)
m.fs.precip_aq_outlet = Arc(
    source=m.fs.precipitator.aqueous_outlet, destination=m.fs.sl_sep2.liquid_inlet
)
m.fs.sl_sep2_solid_outlet = Arc(
    source=m.fs.sl_sep2.solid_outlet, destination=m.fs.roaster.solid_inlet
)
m.fs.sl_sep2_liquid_outlet = Arc(
    source=m.fs.sl_sep2.recovered_liquid_outlet, destination=m.fs.precip_sep.inlet
)
m.fs.sl_sep2_retained_liquid_outlet = Arc(
    source=m.fs.sl_sep2.retained_liquid_outlet,
    destination=m.fs.roaster.liquid_inlet,
)
m.fs.sl_sep2_aq_purge = Arc(
    source=m.fs.precip_sep.purge, destination=m.fs.precip_purge.inlet
)
m.fs.sl_sep2_aq_recycle = Arc(
    source=m.fs.precip_sep.recycle,
    destination=m.fs.precip_sx_mixer.precip,
)

TransformationFactory("network.expand_arcs").apply_to(m)

Step 2.5 Apply scaling#

In order for the flowsheet to solve, variables will need to be scaled appropriately. While variables often have a default scaling set, it is important to re-scale those with poor initial scaling in this flowsheet as there are many trace components resulting in variables with very small magnitudes.

def set_scaling(m):
    m.scaling_factor = Suffix(direction=Suffix.EXPORT)

    # Create sets to target variables with these specific components
    aqueous_component_set = [
        "H2O",
        "H",
        "HSO4",
        "SO4",
        "Cl",
        "Sc",
        "Y",
        "La",
        "Ce",
        "Pr",
        "Nd",
        "Sm",
        "Gd",
        "Dy",
        "Al",
        "Ca",
        "Fe",
    ]

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

    for component in aqueous_component_set:
        m.scaling_factor[m.fs.leach.liquid[0, 1].conc_mol_comp[component]] = 1e5
        m.scaling_factor[m.fs.leach.liquid[0, 2].conc_mol_comp[component]] = 1e5
        m.scaling_factor[m.fs.leach.liquid_inlet_state[0].conc_mol_comp[component]] = (
            1e5
        )
        m.scaling_factor[
            m.fs.leach_liquid_feed.properties[0].conc_mol_comp[component]
        ] = 1e5
        m.scaling_factor[
            m.fs.sl_sep1.liquid_inlet_state[0].conc_mol_comp[component]
        ] = 1e5
        m.scaling_factor[
            m.fs.sl_sep1.split.recovered_state[0].conc_mol_comp[component]
        ] = 1e5
        m.scaling_factor[
            m.fs.sl_sep1.split.retained_state[0].conc_mol_comp[component]
        ] = 1e5
        m.scaling_factor[
            m.fs.leach_filter_cake_liquid.properties[0].conc_mol_comp[component]
        ] = 1e5
        m.scaling_factor[m.fs.leach.liquid_inlet_state[0].conc_mol_comp[component]] = (
            1e5
        )
        m.scaling_factor[m.fs.leach.liquid_inlet_state[0].conc_mol_comp[component]] = (
            1e5
        )
        m.scaling_factor[m.fs.leach.liquid_inlet_state[0].conc_mol_comp[component]] = (
            1e5
        )
        m.scaling_factor[m.fs.leach.liquid_inlet_state[0].conc_mol_comp[component]] = (
            1e5
        )
        m.scaling_factor[
            m.fs.leach_mixer.load_recycle_state[0].conc_mol_comp[component]
        ] = 1e5
        m.scaling_factor[
            m.fs.leach_mixer.scrub_recycle_state[0].conc_mol_comp[component]
        ] = 1e5
        m.scaling_factor[m.fs.leach_mixer.feed_state[0].conc_mol_comp[component]] = 1e5
        m.scaling_factor[m.fs.leach_mixer.mixed_state[0].conc_mol_comp[component]] = 1e5
        m.scaling_factor[
            m.fs.solex_rougher_load.mscontactor.aqueous[0, 1].conc_mol_comp[component]
        ] = 1e5
        m.scaling_factor[
            m.fs.solex_rougher_scrub.mscontactor.aqueous[0, 1].conc_mol_comp[component]
        ] = 1e5
        m.scaling_factor[
            m.fs.solex_rougher_strip.mscontactor.aqueous[0, 1].conc_mol_comp[component]
        ] = 1e5

        m.scaling_factor[
            m.fs.solex_rougher_load.mscontactor.aqueous[0, 1].conc_mol_comp[component]
        ] = 1e5
        m.scaling_factor[
            m.fs.solex_rougher_load.mscontactor.aqueous[0, 2].conc_mol_comp[component]
        ] = 1e5
        m.scaling_factor[
            m.fs.solex_rougher_load.mscontactor.aqueous[0, 3].conc_mol_comp[component]
        ] = 1e5
        m.scaling_factor[
            m.fs.solex_rougher_load.mscontactor.aqueous_inlet_state[0].conc_mol_comp[
                component
            ]
        ] = 1e5
        m.scaling_factor[m.fs.acid_feed1.properties[0].conc_mol_comp[component]] = 1e5
        m.scaling_factor[
            m.fs.solex_rougher_scrub.mscontactor.aqueous[0, 1].conc_mol_comp[component]
        ] = 1e5
        m.scaling_factor[
            m.fs.solex_rougher_scrub.mscontactor.aqueous_inlet_state[0].conc_mol_comp[
                component
            ]
        ] = 1e5
        m.scaling_factor[m.fs.acid_feed2.properties[0].conc_mol_comp[component]] = 1e5
        m.scaling_factor[
            m.fs.solex_rougher_strip.mscontactor.aqueous[0, 1].conc_mol_comp[component]
        ] = 1e5
        m.scaling_factor[
            m.fs.solex_rougher_strip.mscontactor.aqueous[0, 2].conc_mol_comp[component]
        ] = 1e5
        m.scaling_factor[
            m.fs.solex_rougher_strip.mscontactor.aqueous_inlet_state[0].conc_mol_comp[
                component
            ]
        ] = 1e5
        m.scaling_factor[
            m.fs.solex_cleaner_load.mscontactor.aqueous[0, 1].conc_mol_comp[component]
        ] = 1e5
        m.scaling_factor[
            m.fs.solex_cleaner_load.mscontactor.aqueous[0, 2].conc_mol_comp[component]
        ] = 1e5
        m.scaling_factor[
            m.fs.solex_cleaner_load.mscontactor.aqueous[0, 3].conc_mol_comp[component]
        ] = 1e5
        m.scaling_factor[
            m.fs.solex_cleaner_load.mscontactor.aqueous_inlet_state[0].conc_mol_comp[
                component
            ]
        ] = 1e5
        m.scaling_factor[
            m.fs.leach_sx_mixer.leach_state[0].conc_mol_comp[component]
        ] = 1e5
        m.scaling_factor[
            m.fs.leach_sx_mixer.cleaner_state[0].conc_mol_comp[component]
        ] = 1e5
        m.scaling_factor[
            m.fs.leach_sx_mixer.mixed_state[0].conc_mol_comp[component]
        ] = 1e5
        m.scaling_factor[
            m.fs.solex_cleaner_strip.mscontactor.aqueous[0, 1].conc_mol_comp[component]
        ] = 1e5
        m.scaling_factor[
            m.fs.solex_cleaner_strip.mscontactor.aqueous[0, 2].conc_mol_comp[component]
        ] = 1e5
        m.scaling_factor[
            m.fs.solex_cleaner_strip.mscontactor.aqueous[0, 3].conc_mol_comp[component]
        ] = 1e5
        m.scaling_factor[
            m.fs.solex_cleaner_strip.mscontactor.aqueous_inlet_state[0].conc_mol_comp[
                component
            ]
        ] = 1e5
        m.scaling_factor[
            m.fs.sl_sep2.liquid_inlet_state[0].conc_mol_comp[component]
        ] = 1e5
        m.scaling_factor[
            m.fs.sl_sep2.split.retained_state[0].conc_mol_comp[component]
        ] = 1e5
        m.scaling_factor[
            m.fs.sl_sep2.split.recovered_state[0].conc_mol_comp[component]
        ] = 1e5
        m.scaling_factor[m.fs.load_sep.mixed_state[0].conc_mol_comp[component]] = 1e5
        m.scaling_factor[m.fs.load_sep.recycle_state[0].conc_mol_comp[component]] = 1e5
        m.scaling_factor[m.fs.load_sep.purge_state[0].conc_mol_comp[component]] = 1e5
        m.scaling_factor[m.fs.scrub_sep.mixed_state[0].conc_mol_comp[component]] = 1e5
        m.scaling_factor[m.fs.scrub_sep.recycle_state[0].conc_mol_comp[component]] = 1e5
        m.scaling_factor[m.fs.scrub_sep.purge_state[0].conc_mol_comp[component]] = 1e5
        m.scaling_factor[m.fs.precip_sep.mixed_state[0].conc_mol_comp[component]] = 1e5
        m.scaling_factor[m.fs.precip_sep.recycle_state[0].conc_mol_comp[component]] = (
            1e5
        )
        m.scaling_factor[m.fs.precip_sep.purge_state[0].conc_mol_comp[component]] = 1e5
        m.scaling_factor[m.fs.precip_purge.properties[0].conc_mol_comp[component]] = 1e5
        m.scaling_factor[
            m.fs.precip_sx_mixer.precip_state[0].conc_mol_comp[component]
        ] = 1e5
        m.scaling_factor[
            m.fs.precip_sx_mixer.rougher_state[0].conc_mol_comp[component]
        ] = 1e5
        m.scaling_factor[
            m.fs.precip_sx_mixer.mixed_state[0].conc_mol_comp[component]
        ] = 1e5
        m.scaling_factor[m.fs.acid_feed3.properties[0].conc_mol_comp[component]] = 1e5
        m.scaling_factor[m.fs.precip_purge.properties[0].conc_mol_comp[component]] = 1
        m.scaling_factor[
            m.fs.precipitator.cv_aqueous.properties_in[0].conc_mol_comp[component]
        ] = 1
        m.scaling_factor[
            m.fs.precipitator.cv_aqueous.properties_out[0].conc_mol_comp[component]
        ] = 1

    for component in organic_component_set:
        m.scaling_factor[
            m.fs.rougher_org_make_up.properties[0].conc_mol_comp[component]
        ] = 1e5
        m.scaling_factor[
            m.fs.solex_rougher_load.mscontactor.organic[0, 1].conc_mol_comp[component]
        ] = 1e5
        m.scaling_factor[
            m.fs.solex_rougher_load.mscontactor.organic[0, 2].conc_mol_comp[component]
        ] = 1e5
        m.scaling_factor[
            m.fs.solex_rougher_load.mscontactor.organic[0, 3].conc_mol_comp[component]
        ] = 1e5
        m.scaling_factor[
            m.fs.solex_rougher_load.mscontactor.organic_inlet_state[0].conc_mol_comp[
                component
            ]
        ] = 1e5
        m.scaling_factor[
            m.fs.solex_rougher_scrub.mscontactor.organic[0, 1].conc_mol_comp[component]
        ] = 1e5
        m.scaling_factor[
            m.fs.solex_rougher_scrub.mscontactor.organic_inlet_state[0].conc_mol_comp[
                component
            ]
        ] = 1e5
        m.scaling_factor[
            m.fs.solex_rougher_strip.mscontactor.organic[0, 1].conc_mol_comp[component]
        ] = 1e5
        m.scaling_factor[
            m.fs.solex_rougher_strip.mscontactor.organic[0, 2].conc_mol_comp[component]
        ] = 1e5
        m.scaling_factor[
            m.fs.solex_rougher_strip.mscontactor.organic_inlet_state[0].conc_mol_comp[
                component
            ]
        ] = 1e5
        m.scaling_factor[
            m.fs.rougher_mixer.make_up_state[0].conc_mol_comp[component]
        ] = 1e5
        m.scaling_factor[
            m.fs.rougher_mixer.recycle_state[0].conc_mol_comp[component]
        ] = 1e5
        m.scaling_factor[m.fs.rougher_mixer.mixed_state[0].conc_mol_comp[component]] = (
            1e5
        )
        m.scaling_factor[m.fs.rougher_sep.mixed_state[0].conc_mol_comp[component]] = 1e5
        m.scaling_factor[m.fs.rougher_sep.recycle_state[0].conc_mol_comp[component]] = (
            1e5
        )
        m.scaling_factor[m.fs.rougher_sep.purge_state[0].conc_mol_comp[component]] = 1e5
        m.scaling_factor[
            m.fs.rougher_mixer.make_up_state[0].conc_mol_comp[component]
        ] = 1e5
        m.scaling_factor[
            m.fs.rougher_mixer.recycle_state[0].conc_mol_comp[component]
        ] = 1e5
        m.scaling_factor[m.fs.rougher_mixer.mixed_state[0].conc_mol_comp[component]] = (
            1e5
        )
        m.scaling_factor[
            m.fs.sc_circuit_purge.properties[0].conc_mol_comp[component]
        ] = 1e5
        m.scaling_factor[
            m.fs.cleaner_mixer.make_up_state[0].conc_mol_comp[component]
        ] = 1e5
        m.scaling_factor[
            m.fs.cleaner_mixer.recycle_state[0].conc_mol_comp[component]
        ] = 1e5
        m.scaling_factor[m.fs.cleaner_mixer.mixed_state[0].conc_mol_comp[component]] = (
            1e5
        )
        m.scaling_factor[
            m.fs.sc_circuit_purge.properties[0].conc_mol_comp[component]
        ] = 1e5
        m.scaling_factor[
            m.fs.solex_cleaner_load.mscontactor.organic[0, 1].conc_mol_comp[component]
        ] = 1e5
        m.scaling_factor[
            m.fs.solex_cleaner_load.mscontactor.organic[0, 2].conc_mol_comp[component]
        ] = 1e5
        m.scaling_factor[
            m.fs.solex_cleaner_load.mscontactor.organic[0, 3].conc_mol_comp[component]
        ] = 1e5
        m.scaling_factor[
            m.fs.solex_cleaner_load.mscontactor.organic_inlet_state[0].conc_mol_comp[
                component
            ]
        ] = 1e5
        m.scaling_factor[
            m.fs.solex_cleaner_load.mscontactor.organic[0, 1].conc_mol_comp[component]
        ] = 1e5
        m.scaling_factor[m.fs.leach.liquid_inlet_state[0].conc_mol_comp[component]] = (
            1e5
        )
        m.scaling_factor[m.fs.cleaner_sep.mixed_state[0].conc_mol_comp[component]] = 1e5
        m.scaling_factor[m.fs.cleaner_sep.recycle_state[0].conc_mol_comp[component]] = (
            1e5
        )
        m.scaling_factor[m.fs.cleaner_sep.purge_state[0].conc_mol_comp[component]] = 1e5
        m.scaling_factor[
            m.fs.cleaner_org_make_up.properties[0].conc_mol_comp[component]
        ] = 1e5

        m.scaling_factor[m.fs.cleaner_purge.properties[0].conc_mol_comp[component]] = (
            1e5
        )
        m.scaling_factor[
            m.fs.solex_cleaner_strip.mscontactor.organic[0, 1].conc_mol_comp[component]
        ] = 1e5
        m.scaling_factor[
            m.fs.solex_cleaner_strip.mscontactor.organic[0, 2].conc_mol_comp[component]
        ] = 1e5
        m.scaling_factor[
            m.fs.solex_cleaner_strip.mscontactor.organic[0, 3].conc_mol_comp[component]
        ] = 1e5
        m.scaling_factor[
            m.fs.solex_cleaner_strip.mscontactor.organic_inlet_state[0].conc_mol_comp[
                component
            ]
        ] = 1e5

    m.scaling_factor[m.fs.solex_cleaner_load.mscontactor.aqueous[0, 1].flow_vol] = 1e-2
    m.scaling_factor[m.fs.solex_cleaner_load.mscontactor.organic[0, 1].flow_vol] = 1e-2

    m.scaling_factor[m.fs.solex_cleaner_strip.mscontactor.aqueous[0, 1].flow_vol] = 1e-2
    m.scaling_factor[m.fs.solex_cleaner_strip.mscontactor.aqueous[0, 2].flow_vol] = 1e-2
    m.scaling_factor[m.fs.solex_cleaner_strip.mscontactor.aqueous[0, 3].flow_vol] = 1e-2
    m.scaling_factor[
        m.fs.solex_cleaner_strip.mscontactor.aqueous_inlet_state[0].flow_vol
    ] = 1e-2
    m.scaling_factor[m.fs.solex_cleaner_strip.mscontactor.organic[0, 1].flow_vol] = 1e-2

    m.scaling_factor[m.fs.sl_sep2.solid_state[0].temperature] = 1e-2
    m.scaling_factor[m.fs.sl_sep2.liquid_inlet_state[0].flow_vol] = 1e-2
    m.scaling_factor[m.fs.sl_sep2.split.recovered_state[0].flow_vol] = 1e-2
    m.scaling_factor[m.fs.sl_sep2.split.retained_state[0].flow_vol] = 1e-2

    m.scaling_factor[m.fs.precip_sep.mixed_state[0].flow_vol] = 1e-2
    m.scaling_factor[m.fs.precip_sep.recycle_state[0].flow_vol] = 1e-2
    m.scaling_factor[m.fs.precip_sep.purge_state[0].flow_vol] = 1e-2
    m.scaling_factor[m.fs.precip_purge.properties[0].flow_vol] = 1e-2

    m.scaling_factor[m.fs.precipitator.cv_precipitate[0].temperature] = 1e2

    m.scaling_factor[m.fs.precipitator.cv_aqueous.properties_in[0].flow_vol] = 1e-2
    m.scaling_factor[m.fs.precipitator.cv_aqueous.properties_out[0].flow_vol] = 1e-2

    m.scaling_factor[m.fs.roaster.gas_in[0].flow_mol] = 1e-3
    m.scaling_factor[m.fs.roaster.gas_in[0].flow_mol_phase["Vap"]] = 1e-3
    m.scaling_factor[m.fs.roaster.gas_in[0].temperature] = 1e-2
    m.scaling_factor[m.fs.roaster.gas_in[0].pressure] = 1e-5
    m.scaling_factor[m.fs.roaster.gas_out[0].flow_mol_phase["Vap"]] = 1e-3
    m.scaling_factor[m.fs.roaster.gas_out[0].flow_mol] = 1e-3
    m.scaling_factor[m.fs.roaster.gas_out[0].temperature] = 1e-2
    m.scaling_factor[m.fs.roaster.gas_out[0].pressure] = 1e-5
    m.scaling_factor[m.fs.roaster.solid_in[0].temperature] = 1e-2

    scaling = TransformationFactory("core.scale_model")
    scaled_model = scaling.create_using(m, rename=False)

    return scaled_model

Step 2.6 Set the operating conditions#

Now specify the inlet feed conditions into the flowsheet and fix any necessary unit model variables such that the degrees of freedom of the system are equal to zero.

eps = 1e-7 * units.mg / units.L

m.fs.leach_liquid_feed.flow_vol.fix(224.3 * units.L / units.hour)
m.fs.leach_liquid_feed.conc_mass_comp.fix(1e-10 * units.mg / units.L)
m.fs.leach_liquid_feed.conc_mass_comp[0, "H"].fix(2 * 0.05 * 1e3 * units.mg / units.L)
m.fs.leach_liquid_feed.conc_mass_comp[0, "HSO4"].fix(1e-8 * units.mg / units.L)
m.fs.leach_liquid_feed.conc_mass_comp[0, "SO4"].fix(0.05 * 96e3 * units.mg / units.L)

m.fs.leach_solid_feed.flow_mass.fix(22.68 * units.kg / units.hour)
m.fs.leach_solid_feed.mass_frac_comp[0, "inerts"].fix(0.6952 * units.kg / units.kg)
m.fs.leach_solid_feed.mass_frac_comp[0, "Al2O3"].fix(0.237 * units.kg / units.kg)
m.fs.leach_solid_feed.mass_frac_comp[0, "Fe2O3"].fix(0.0642 * units.kg / units.kg)
m.fs.leach_solid_feed.mass_frac_comp[0, "CaO"].fix(3.31e-3 * units.kg / units.kg)
m.fs.leach_solid_feed.mass_frac_comp[0, "Sc2O3"].fix(2.77966e-05 * units.kg / units.kg)
m.fs.leach_solid_feed.mass_frac_comp[0, "Y2O3"].fix(3.28653e-05 * units.kg / units.kg)
m.fs.leach_solid_feed.mass_frac_comp[0, "La2O3"].fix(6.77769e-05 * units.kg / units.kg)
m.fs.leach_solid_feed.mass_frac_comp[0, "Ce2O3"].fix(0.000156161 * units.kg / units.kg)
m.fs.leach_solid_feed.mass_frac_comp[0, "Pr2O3"].fix(1.71438e-05 * units.kg / units.kg)
m.fs.leach_solid_feed.mass_frac_comp[0, "Nd2O3"].fix(6.76618e-05 * units.kg / units.kg)
m.fs.leach_solid_feed.mass_frac_comp[0, "Sm2O3"].fix(1.47926e-05 * units.kg / units.kg)
m.fs.leach_solid_feed.mass_frac_comp[0, "Gd2O3"].fix(1.0405e-05 * units.kg / units.kg)
m.fs.leach_solid_feed.mass_frac_comp[0, "Dy2O3"].fix(7.54827e-06 * units.kg / units.kg)

m.fs.leach.volume.fix(100 * units.gallon)

m.fs.load_sep.split_fraction[:, "recycle"].fix(0.9)
m.fs.scrub_sep.split_fraction[:, "recycle"].fix(0.9)

# Note: This stream + m.fs.s09 = 62.01 L/hr
m.fs.rougher_org_make_up.flow_vol.fix(6.201)

m.fs.rougher_org_make_up.conc_mass_comp[0, "Al"].fix(eps)
m.fs.rougher_org_make_up.conc_mass_comp[0, "Ca"].fix(eps)
m.fs.rougher_org_make_up.conc_mass_comp[0, "Fe"].fix(eps)
m.fs.rougher_org_make_up.conc_mass_comp[0, "Sc"].fix(eps)
m.fs.rougher_org_make_up.conc_mass_comp[0, "Y"].fix(eps)
m.fs.rougher_org_make_up.conc_mass_comp[0, "La"].fix(eps)
m.fs.rougher_org_make_up.conc_mass_comp[0, "Ce"].fix(eps)
m.fs.rougher_org_make_up.conc_mass_comp[0, "Pr"].fix(eps)
m.fs.rougher_org_make_up.conc_mass_comp[0, "Nd"].fix(eps)
m.fs.rougher_org_make_up.conc_mass_comp[0, "Sm"].fix(eps)
m.fs.rougher_org_make_up.conc_mass_comp[0, "Gd"].fix(eps)
m.fs.rougher_org_make_up.conc_mass_comp[0, "Dy"].fix(eps)

m.fs.acid_feed1.flow_vol.fix(0.09)
m.fs.acid_feed1.conc_mass_comp[0, "H2O"].fix(1000000)
m.fs.acid_feed1.conc_mass_comp[0, "H"].fix(10.36)
m.fs.acid_feed1.conc_mass_comp[0, "SO4"].fix(eps)
m.fs.acid_feed1.conc_mass_comp[0, "HSO4"].fix(eps)
m.fs.acid_feed1.conc_mass_comp[0, "Cl"].fix(359.64)
m.fs.acid_feed1.conc_mass_comp[0, "Al"].fix(eps)
m.fs.acid_feed1.conc_mass_comp[0, "Ca"].fix(eps)
m.fs.acid_feed1.conc_mass_comp[0, "Fe"].fix(eps)
m.fs.acid_feed1.conc_mass_comp[0, "Sc"].fix(eps)
m.fs.acid_feed1.conc_mass_comp[0, "Y"].fix(eps)
m.fs.acid_feed1.conc_mass_comp[0, "La"].fix(eps)
m.fs.acid_feed1.conc_mass_comp[0, "Ce"].fix(eps)
m.fs.acid_feed1.conc_mass_comp[0, "Pr"].fix(eps)
m.fs.acid_feed1.conc_mass_comp[0, "Nd"].fix(eps)
m.fs.acid_feed1.conc_mass_comp[0, "Sm"].fix(eps)
m.fs.acid_feed1.conc_mass_comp[0, "Gd"].fix(eps)
m.fs.acid_feed1.conc_mass_comp[0, "Dy"].fix(eps)

# TODO: flow rate and HCl concentration are not defined in REESim
m.fs.acid_feed2.flow_vol.fix(0.09)
m.fs.acid_feed2.conc_mass_comp[0, "H2O"].fix(1000000)
m.fs.acid_feed2.conc_mass_comp[0, "H"].fix(
    10.36 * 4
)  # Arbitrarily choose 4x the dilute solution
m.fs.acid_feed2.conc_mass_comp[0, "SO4"].fix(eps)
m.fs.acid_feed2.conc_mass_comp[0, "HSO4"].fix(eps)
m.fs.acid_feed2.conc_mass_comp[0, "Cl"].fix(359.64 * 4)
m.fs.acid_feed2.conc_mass_comp[0, "Al"].fix(eps)
m.fs.acid_feed2.conc_mass_comp[0, "Ca"].fix(eps)
m.fs.acid_feed2.conc_mass_comp[0, "Fe"].fix(eps)
m.fs.acid_feed2.conc_mass_comp[0, "Sc"].fix(eps)
m.fs.acid_feed2.conc_mass_comp[0, "Y"].fix(eps)
m.fs.acid_feed2.conc_mass_comp[0, "La"].fix(eps)
m.fs.acid_feed2.conc_mass_comp[0, "Ce"].fix(eps)
m.fs.acid_feed2.conc_mass_comp[0, "Pr"].fix(eps)
m.fs.acid_feed2.conc_mass_comp[0, "Nd"].fix(eps)
m.fs.acid_feed2.conc_mass_comp[0, "Sm"].fix(eps)
m.fs.acid_feed2.conc_mass_comp[0, "Gd"].fix(eps)
m.fs.acid_feed2.conc_mass_comp[0, "Dy"].fix(eps)

m.fs.rougher_sep.split_fraction[:, "recycle"].fix(0.9)

# TODO: flow rate and HCl concentration are not defined in REESim
m.fs.acid_feed3.flow_vol.fix(9)
m.fs.acid_feed3.conc_mass_comp[0, "H2O"].fix(1000000)
m.fs.acid_feed3.conc_mass_comp[0, "H"].fix(
    10.36 * 4
)  # Arbitrarily choose 4x the dilute solution
m.fs.acid_feed3.conc_mass_comp[0, "SO4"].fix(eps)
m.fs.acid_feed3.conc_mass_comp[0, "HSO4"].fix(eps)
m.fs.acid_feed3.conc_mass_comp[0, "Cl"].fix(359.64 * 4)
m.fs.acid_feed3.conc_mass_comp[0, "Al"].fix(eps)
m.fs.acid_feed3.conc_mass_comp[0, "Ca"].fix(eps)
m.fs.acid_feed3.conc_mass_comp[0, "Fe"].fix(eps)
m.fs.acid_feed3.conc_mass_comp[0, "Sc"].fix(eps)
m.fs.acid_feed3.conc_mass_comp[0, "Y"].fix(eps)
m.fs.acid_feed3.conc_mass_comp[0, "La"].fix(eps)
m.fs.acid_feed3.conc_mass_comp[0, "Ce"].fix(eps)
m.fs.acid_feed3.conc_mass_comp[0, "Pr"].fix(eps)
m.fs.acid_feed3.conc_mass_comp[0, "Nd"].fix(eps)
m.fs.acid_feed3.conc_mass_comp[0, "Sm"].fix(eps)
m.fs.acid_feed3.conc_mass_comp[0, "Gd"].fix(eps)
m.fs.acid_feed3.conc_mass_comp[0, "Dy"].fix(eps)

# Note: This stream + m.fs.s18 = 62.01 L/hr
m.fs.cleaner_org_make_up.flow_vol.fix(6.201)

m.fs.cleaner_org_make_up.conc_mass_comp[0, "Al"].fix(eps)
m.fs.cleaner_org_make_up.conc_mass_comp[0, "Ca"].fix(eps)
m.fs.cleaner_org_make_up.conc_mass_comp[0, "Fe"].fix(eps)
m.fs.cleaner_org_make_up.conc_mass_comp[0, "Sc"].fix(eps)
m.fs.cleaner_org_make_up.conc_mass_comp[0, "Y"].fix(eps)
m.fs.cleaner_org_make_up.conc_mass_comp[0, "La"].fix(eps)
m.fs.cleaner_org_make_up.conc_mass_comp[0, "Ce"].fix(eps)
m.fs.cleaner_org_make_up.conc_mass_comp[0, "Pr"].fix(eps)
m.fs.cleaner_org_make_up.conc_mass_comp[0, "Nd"].fix(eps)
m.fs.cleaner_org_make_up.conc_mass_comp[0, "Sm"].fix(eps)
m.fs.cleaner_org_make_up.conc_mass_comp[0, "Gd"].fix(eps)
m.fs.cleaner_org_make_up.conc_mass_comp[0, "Dy"].fix(eps)

m.fs.cleaner_sep.split_fraction[:, "recycle"].fix(0.9)

m.fs.sl_sep1.liquid_recovery.fix(0.7)
m.fs.sl_sep2.liquid_recovery.fix(0.88)

m.fs.precipitator.cv_precipitate[0].temperature.fix(348.15 * units.K)

m.fs.precip_sep.split_fraction[:, "recycle"].fix(0.9)

# Roaster gas feed
m.fs.roaster.deltaP.fix(0)
m.fs.roaster.gas_inlet.temperature.fix(1330)
m.fs.roaster.gas_inlet.pressure.fix(101325)
# Inlet flue gas mole flow rate
fgas = 0.00781
# Inlet flue gas composition, typical flue gas by burning CH4 with air with stoichiometric ratio of 2.3
gas_comp = {
    "O2": 0.1118,
    "H2O": 0.1005,
    "CO2": 0.0431,
    "N2": 0.7446,
}
for i, v in gas_comp.items():
    m.fs.roaster.gas_inlet.mole_frac_comp[0, i].fix(v)
m.fs.roaster.gas_inlet.flow_mol.fix(fgas)

# Fix outlet product temperature
m.fs.roaster.gas_outlet.temperature.fix(873.15)

# Fix operating conditions
m.fs.roaster.frac_comp_recovery.fix(0.95)

Step 3: Solve the square problem#

Step 3.1: Initialize the system#

Since there are multiple recycle loops involved, sequential decomposition will be used to initialize the flowsheet and tear sets must be specified to successfully initialize the system.

def initialize_system(m):
    # Initialize the model with sequential decomposition
    seq = SequentialDecomposition()
    seq.options.tear_method = "Direct"
    seq.options.iterLim = 1
    # Identify recycle streams
    seq.options.tear_set = [
        m.fs.leaching_feed_mixture,
        m.fs.sx_rougher_load_aq_feed,
        m.fs.sx_rougher_mixed_org_recycle,
        m.fs.sx_cleaner_load_aq_feed,
        m.fs.sx_cleaner_mixed_org_recycle,
    ]

    # Print initialization order for user visibility
    G = seq.create_graph(m)
    order = seq.calculation_order(G)
    print("Initialization Order")
    for o in order:
        print(o[0].name)

    # Supply tear guesses with initial values that are close to the solution
    tear_guesses1 = {
        "flow_vol": {0: 747.99},
        "conc_mass_comp": {
            (0, "Al"): 180.84,
            (0, "Ca"): 28.93,
            (0, "Ce"): 5.48,
            (0, "Dy"): 4.46e-11,
            (0, "Fe"): 269.98,
            (0, "Gd"): 2.60e-7,
            (0, "H"): 20.06,
            (0, "H2O"): 1000000,
            (0, "HSO4"): 963.06,
            (0, "Cl"): 1e-8,
            (0, "La"): 0.0037,
            (0, "Nd"): 1.81e-7,
            (0, "Pr"): 3.65e-6,
            (0, "SO4"): 486.24,
            (0, "Sc"): 4.17e-11,
            (0, "Sm"): 6.30e-10,
            (0, "Y"): 7.18e-11,
        },
    }
    tear_guesses2 = {
        "flow_vol": {0: 62.01},
        "conc_mass_comp": {
            (0, "Al"): 1e-9,
            (0, "Ca"): 1e-9,
            (0, "Ce"): 1e-4,
            (0, "Dy"): 1e-7,
            (0, "Fe"): 1e-7,
            (0, "Gd"): 1e-6,
            (0, "La"): 1e-5,
            (0, "Nd"): 1e-4,
            (0, "Pr"): 1e-6,
            (0, "Sc"): 250,
            (0, "Sm"): 1e-6,
            (0, "Y"): 1e-6,
        },
    }
    tear_guesses3 = {
        "flow_vol": {0: 520},
        "conc_mass_comp": {
            (0, "Al"): 430,
            (0, "Ca"): 99,
            (0, "Ce"): 2,
            (0, "Dy"): 0.01,
            (0, "Fe"): 660,
            (0, "Gd"): 0.1,
            (0, "H"): 2,
            (0, "H2O"): 1000000,
            (0, "HSO4"): 900,
            (0, "Cl"): 0.1,
            (0, "La"): 1,
            (0, "Nd"): 1,
            (0, "Pr"): 0.1,
            (0, "SO4"): 4000,
            (0, "Sc"): 0.05,
            (0, "Sm"): 0.07,
            (0, "Y"): 0.1,
        },
    }
    tear_guesses4 = {
        "flow_vol": {0: 64},
        "conc_mass_comp": {
            (0, "Al"): 1e-9,
            (0, "Ca"): 1e-9,
            (0, "Ce"): 1e-5,
            (0, "Dy"): 1e-7,
            (0, "Fe"): 1e-7,
            (0, "Gd"): 1e-6,
            (0, "La"): 1e-5,
            (0, "Nd"): 1e-5,
            (0, "Pr"): 1e-6,
            (0, "Sc"): 321.34,
            (0, "Sm"): 1e-6,
            (0, "Y"): 1e-6,
        },
    }
    tear_guesses5 = {
        "flow_vol": {0: 5.7},
        "conc_mass_comp": {
            (0, "Al"): 5,
            (0, "Ca"): 16,
            (0, "Ce"): 346,
            (0, "Dy"): 6,
            (0, "Fe"): 1,
            (0, "Gd"): 22,
            (0, "H"): 14,
            (0, "H2O"): 1000000,
            (0, "HSO4"): 1e-7,
            (0, "Cl"): 1400,
            (0, "La"): 160,
            (0, "Nd"): 121,
            (0, "Pr"): 30,
            (0, "SO4"): 1e-7,
            (0, "Sc"): 149.2,
            (0, "Sm"): 13,
            (0, "Y"): 18,
        },
    }

    # Pass the tear guesses to the sequential decomposition tool
    seq.set_guesses_for(m.fs.leach.liquid_inlet, tear_guesses1)
    seq.set_guesses_for(
        m.fs.solex_rougher_load.mscontactor.organic_inlet, tear_guesses2
    )
    seq.set_guesses_for(
        m.fs.solex_rougher_load.mscontactor.aqueous_inlet, tear_guesses3
    )
    seq.set_guesses_for(
        m.fs.solex_cleaner_load.mscontactor.organic_inlet, tear_guesses4
    )
    seq.set_guesses_for(
        m.fs.solex_cleaner_load.mscontactor.aqueous_inlet, tear_guesses5
    )

    # Associate units with specialized initializers
    initializer_feed = FeedInitializer()
    feed_units = [
        m.fs.leach_liquid_feed,
        m.fs.leach_solid_feed,
        m.fs.rougher_org_make_up,
        m.fs.acid_feed1,
        m.fs.acid_feed2,
        m.fs.acid_feed3,
        m.fs.cleaner_org_make_up,
    ]

    initializer_product = ProductInitializer()
    product_units = [
        m.fs.leach_filter_cake,
        m.fs.leach_filter_cake_liquid,
        m.fs.cleaner_purge,
        m.fs.sc_circuit_purge,
        m.fs.precip_purge,
    ]

    initializer_sep = SeparatorInitializer()
    sep_units = [
        m.fs.scrub_sep,
        m.fs.precip_sep,
        m.fs.cleaner_sep,
        m.fs.rougher_sep,
    ]

    initializer_mix = MixerInitializer()
    mix_units = [
        m.fs.precip_sx_mixer,
        m.fs.cleaner_mixer,
        m.fs.rougher_mixer,
    ]

    # The BT Initializer will be used for any units not handled by the above initializers
    initializer_bt = BlockTriangularizationInitializer()

    # Initialize units using their respective initializers
    # For units that cannot be initialized with the initializers, the unit is manually fixed, solved, and then unfixed
    def function(unit):
        if unit in feed_units:
            _log.info(f"Initializing {unit}")
            initializer_feed.initialize(unit)
        elif unit in product_units:
            _log.info(f"Initializing {unit}")
            initializer_product.initialize(unit)
        elif unit in sep_units:
            _log.info(f"Initializing {unit}")
            initializer_sep.initialize(unit)
        elif unit in mix_units:
            _log.info(f"Initializing {unit}")
            initializer_mix.initialize(unit)
        elif unit == m.fs.leach:
            _log.info(f"Initializing {unit}")
            # Fix feed states
            m.fs.leach.liquid_inlet.flow_vol.fix()
            m.fs.leach.liquid_inlet.conc_mass_comp.fix()
            m.fs.leach.solid_inlet.flow_mass.fix()
            m.fs.leach.solid_inlet.mass_frac_comp.fix()
            # Re-solve unit
            solver = SolverFactory("ipopt")
            solver.solve(m.fs.leach, tee=False)
            # Unfix feed states
            m.fs.leach.liquid_inlet.flow_vol.unfix()
            m.fs.leach.liquid_inlet.conc_mass_comp.unfix()
            m.fs.leach.solid_inlet.flow_mass.unfix()
            m.fs.leach.solid_inlet.mass_frac_comp.unfix()
        elif unit == m.fs.solex_rougher_load.mscontactor:
            _log.info(f"Initializing {unit}")
            # Fix feed states
            m.fs.solex_rougher_load.mscontactor.organic_inlet_state[0].flow_vol.fix()
            m.fs.solex_rougher_load.mscontactor.aqueous_inlet_state[0].flow_vol.fix()
            m.fs.solex_rougher_load.mscontactor.organic_inlet_state[
                0
            ].conc_mass_comp.fix()
            m.fs.solex_rougher_load.mscontactor.aqueous_inlet_state[
                0
            ].conc_mass_comp.fix()
            # Re-solve unit
            solver = SolverFactory("ipopt")
            solver.solve(m.fs.solex_rougher_load, tee=False)
            # Unfix feed states
            m.fs.solex_rougher_load.mscontactor.organic_inlet_state[0].flow_vol.unfix()
            m.fs.solex_rougher_load.mscontactor.aqueous_inlet_state[0].flow_vol.unfix()
            m.fs.solex_rougher_load.mscontactor.organic_inlet_state[
                0
            ].conc_mass_comp.unfix()
            m.fs.solex_rougher_load.mscontactor.aqueous_inlet_state[
                0
            ].conc_mass_comp.unfix()
        elif unit == m.fs.solex_rougher_scrub.mscontactor:
            _log.info(f"Initializing {unit}")
            # Fix feed states
            m.fs.solex_rougher_scrub.mscontactor.organic_inlet_state[0].flow_vol.fix()
            m.fs.solex_rougher_scrub.mscontactor.aqueous_inlet_state[0].flow_vol.fix()
            m.fs.solex_rougher_scrub.mscontactor.organic_inlet_state[
                0
            ].conc_mass_comp.fix()
            m.fs.solex_rougher_scrub.mscontactor.aqueous_inlet_state[
                0
            ].conc_mass_comp.fix()
            # Re-solve unit
            solver = SolverFactory("ipopt")
            solver.solve(m.fs.solex_rougher_scrub, tee=False)
            # Unfix feed states
            m.fs.solex_rougher_scrub.mscontactor.organic_inlet_state[0].flow_vol.unfix()
            m.fs.solex_rougher_scrub.mscontactor.aqueous_inlet_state[0].flow_vol.unfix()
            m.fs.solex_rougher_scrub.mscontactor.organic_inlet_state[
                0
            ].conc_mass_comp.unfix()
            m.fs.solex_rougher_scrub.mscontactor.aqueous_inlet_state[
                0
            ].conc_mass_comp.unfix()
        elif unit == m.fs.solex_rougher_strip.mscontactor:
            _log.info(f"Initializing {unit}")
            # Fix feed states
            m.fs.solex_rougher_strip.mscontactor.organic_inlet_state[0].flow_vol.fix()
            m.fs.solex_rougher_strip.mscontactor.aqueous_inlet_state[0].flow_vol.fix()
            m.fs.solex_rougher_strip.mscontactor.organic_inlet_state[
                0
            ].conc_mass_comp.fix()
            m.fs.solex_rougher_strip.mscontactor.aqueous_inlet_state[
                0
            ].conc_mass_comp.fix()
            # Re-solve unit
            solver = SolverFactory("ipopt")
            solver.solve(m.fs.solex_rougher_strip, tee=False)
            # Unfix feed states
            m.fs.solex_rougher_strip.mscontactor.organic_inlet_state[0].flow_vol.unfix()
            m.fs.solex_rougher_strip.mscontactor.aqueous_inlet_state[0].flow_vol.unfix()
            m.fs.solex_rougher_strip.mscontactor.organic_inlet_state[
                0
            ].conc_mass_comp.unfix()
            m.fs.solex_rougher_strip.mscontactor.aqueous_inlet_state[
                0
            ].conc_mass_comp.unfix()
        elif unit == m.fs.solex_cleaner_load.mscontactor:
            _log.info(f"Initializing {unit}")
            # Fix feed states
            m.fs.solex_cleaner_load.mscontactor.organic_inlet_state[0].flow_vol.fix()
            m.fs.solex_cleaner_load.mscontactor.aqueous_inlet_state[0].flow_vol.fix()
            m.fs.solex_cleaner_load.mscontactor.organic_inlet_state[
                0
            ].conc_mass_comp.fix()
            m.fs.solex_cleaner_load.mscontactor.aqueous_inlet_state[
                0
            ].conc_mass_comp.fix()
            # Re-solve unit
            solver = SolverFactory("ipopt")
            solver.solve(m.fs.solex_cleaner_load, tee=False)
            # Unfix feed states
            m.fs.solex_cleaner_load.mscontactor.organic_inlet_state[0].flow_vol.unfix()
            m.fs.solex_cleaner_load.mscontactor.aqueous_inlet_state[0].flow_vol.unfix()
            m.fs.solex_cleaner_load.mscontactor.organic_inlet_state[
                0
            ].conc_mass_comp.unfix()
            m.fs.solex_cleaner_load.mscontactor.aqueous_inlet_state[
                0
            ].conc_mass_comp.unfix()
        elif unit == m.fs.solex_cleaner_strip.mscontactor:
            _log.info(f"Initializing {unit}")
            # Fix feed states
            m.fs.solex_cleaner_strip.mscontactor.organic_inlet_state[0].flow_vol.fix()
            m.fs.solex_cleaner_strip.mscontactor.aqueous_inlet_state[0].flow_vol.fix()
            m.fs.solex_cleaner_strip.mscontactor.organic_inlet_state[
                0
            ].conc_mass_comp.fix()
            m.fs.solex_cleaner_strip.mscontactor.aqueous_inlet_state[
                0
            ].conc_mass_comp.fix()
            # Re-solve unit
            solver = SolverFactory("ipopt")
            solver.solve(m.fs.solex_cleaner_strip, tee=False)
            # Unfix feed states
            m.fs.solex_cleaner_strip.mscontactor.organic_inlet_state[0].flow_vol.unfix()
            m.fs.solex_cleaner_strip.mscontactor.aqueous_inlet_state[0].flow_vol.unfix()
            m.fs.solex_cleaner_strip.mscontactor.organic_inlet_state[
                0
            ].conc_mass_comp.unfix()
            m.fs.solex_cleaner_strip.mscontactor.aqueous_inlet_state[
                0
            ].conc_mass_comp.unfix()
        else:
            _log.info(f"Initializing {unit}")
            initializer_bt.initialize(unit)

    seq.run(m, function)

Step 3.2: Add solver#

Solve the model by running the flowsheet using the ipopt solver.

def solve(m):
    solver = SolverFactory("ipopt")
    results = solver.solve(m, tee=True)

Step 3.3 Solve the system#

Scale, initialize, and solve the model.

# Applies scaling to the model after the models are constructed and operating conditions are set
scaled_model = set_scaling(m)

# Initializes the scaled model
initialize_system(scaled_model)

# Solves the scaled model
solve(scaled_model)
Initialization Order
fs.leach_solid_feed
fs.leach
fs.sl_sep1
fs.leach_mixer
fs.sc_circuit_purge
fs.precip_sx_mixer
2024-08-26 15:11:44 [INFO] idaes.__main__: Initializing fs.leach_solid_feed
2024-08-26 15:11:44 [INFO] idaes.__main__: Initializing fs.leach_liquid_feed
2024-08-26 15:11:44 [INFO] idaes.__main__: Initializing fs.solex_rougher_load.mscontactor
2024-08-26 15:11:44 [INFO] idaes.__main__: Initializing fs.rougher_org_make_up
2024-08-26 15:11:44 [INFO] idaes.__main__: Initializing fs.acid_feed1
2024-08-26 15:11:45 [INFO] idaes.__main__: Initializing fs.acid_feed2
2024-08-26 15:11:45 [INFO] idaes.__main__: Initializing fs.solex_cleaner_load.mscontactor
WARNING: Loading a SolverResults object with a warning status into
model.name="fs.solex_cleaner_load";
    - termination condition: infeasible
    - message from solver: Ipopt 3.13.2\x3a Converged to a locally infeasible
      point. Problem may be infeasible.
2024-08-26 15:11:45 [INFO] idaes.__main__: Initializing fs.cleaner_org_make_up
2024-08-26 15:11:45 [INFO] idaes.__main__: Initializing fs.acid_feed3
2024-08-26 15:11:45 [INFO] idaes.__main__: Initializing fs.leach
WARNING: Loading a SolverResults object with a warning status into
model.name="fs.leach";
    - termination condition: infeasible
    - message from solver: Ipopt 3.13.2\x3a Converged to a locally infeasible
      point. Problem may be infeasible.
2024-08-26 15:11:45 [INFO] idaes.__main__: Initializing fs.load_sep
2024-08-26 15:11:45 [INFO] idaes.__main__: Initializing fs.solex_rougher_scrub.mscontactor
WARNING: Loading a SolverResults object with a warning status into
model.name="fs.solex_rougher_scrub";
    - termination condition: infeasible
    - message from solver: Ipopt 3.13.2\x3a Converged to a locally infeasible
      point. Problem may be infeasible.
2024-08-26 15:11:45 [INFO] idaes.__main__: Initializing fs.solex_cleaner_strip.mscontactor
2024-08-26 15:11:45 [INFO] idaes.__main__: Initializing fs.sl_sep1
2024-08-26 15:11:46 [INFO] idaes.__main__: Initializing fs.scrub_sep
2024-08-26 15:11:46 [INFO] idaes.init.fs.scrub_sep: Initialization Step 2 Complete: optimal - Optimal Solution Found
2024-08-26 15:11:46 [INFO] idaes.__main__: Initializing fs.solex_rougher_strip.mscontactor
WARNING: Loading a SolverResults object with a warning status into
model.name="fs.solex_rougher_strip";
    - termination condition: infeasible
    - message from solver: Ipopt 3.13.2\x3a Converged to a locally infeasible
      point. Problem may be infeasible.
2024-08-26 15:11:46 [INFO] idaes.__main__: Initializing fs.cleaner_sep
2024-08-26 15:11:46 [INFO] idaes.init.fs.cleaner_sep: Initialization Step 2 Complete: optimal - Optimal Solution Found
2024-08-26 15:11:46 [INFO] idaes.__main__: Initializing fs.precipitator
2024-08-26 15:11:47 [INFO] idaes.__main__: Initializing fs.leach_mixer
2024-08-26 15:11:47 [INFO] idaes.__main__: Initializing fs.leach_filter_cake
2024-08-26 15:11:47 [INFO] idaes.__main__: Initializing fs.leach_filter_cake_liquid
2024-08-26 15:11:47 [INFO] idaes.__main__: Initializing fs.leach_sx_mixer
2024-08-26 15:11:47 [INFO] idaes.__main__: Initializing fs.rougher_sep
2024-08-26 15:11:47 [INFO] idaes.init.fs.rougher_sep: Initialization Step 2 Complete: optimal - Optimal Solution Found
2024-08-26 15:11:47 [INFO] idaes.__main__: Initializing fs.cleaner_mixer
2024-08-26 15:11:47 [INFO] idaes.init.fs.cleaner_mixer: Initialization Complete: optimal - Optimal Solution Found
2024-08-26 15:11:47 [INFO] idaes.__main__: Initializing fs.cleaner_purge
2024-08-26 15:11:47 [INFO] idaes.__main__: Initializing fs.sl_sep2
2024-08-26 15:11:47 [INFO] idaes.__main__: Initializing fs.sc_circuit_purge
2024-08-26 15:11:47 [INFO] idaes.__main__: Initializing fs.rougher_mixer
2024-08-26 15:11:48 [INFO] idaes.init.fs.rougher_mixer: Initialization Complete: optimal - Optimal Solution Found
2024-08-26 15:11:48 [INFO] idaes.__main__: Initializing fs.roaster
2024-08-26 15:11:48 [INFO] idaes.__main__: Initializing fs.precip_sep
2024-08-26 15:11:48 [INFO] idaes.init.fs.precip_sep: Initialization Step 2 Complete: optimal - Optimal Solution Found
2024-08-26 15:11:48 [INFO] idaes.__main__: Initializing fs.precip_sx_mixer
2024-08-26 15:11:48 [INFO] idaes.init.fs.precip_sx_mixer: Initialization Complete: optimal - Optimal Solution Found
2024-08-26 15:11:48 [INFO] idaes.__main__: Initializing fs.precip_purge
2024-08-26 15:11:48 [INFO] idaes.__main__: Initializing fs.leach_solid_feed
2024-08-26 15:11:48 [INFO] idaes.__main__: Initializing fs.leach_liquid_feed
2024-08-26 15:11:48 [INFO] idaes.__main__: Initializing fs.rougher_org_make_up
2024-08-26 15:11:48 [INFO] idaes.__main__: Initializing fs.acid_feed1
2024-08-26 15:11:48 [INFO] idaes.__main__: Initializing fs.acid_feed2
2024-08-26 15:11:48 [INFO] idaes.__main__: Initializing fs.cleaner_org_make_up
2024-08-26 15:11:48 [INFO] idaes.__main__: Initializing fs.acid_feed3
2024-08-26 15:11:48 [INFO] idaes.__main__: Initializing fs.solex_cleaner_load.mscontactor
2024-08-26 15:11:48 [INFO] idaes.__main__: Initializing fs.solex_rougher_load.mscontactor
2024-08-26 15:11:48 [INFO] idaes.__main__: Initializing fs.leach
2024-08-26 15:11:48 [INFO] idaes.__main__: Initializing fs.solex_cleaner_strip.mscontactor
2024-08-26 15:11:49 [INFO] idaes.__main__: Initializing fs.solex_rougher_scrub.mscontactor
2024-08-26 15:11:49 [INFO] idaes.__main__: Initializing fs.load_sep
2024-08-26 15:11:49 [INFO] idaes.__main__: Initializing fs.sl_sep1
2024-08-26 15:11:49 [INFO] idaes.__main__: Initializing fs.leach_sx_mixer
2024-08-26 15:11:49 [INFO] idaes.__main__: Initializing fs.precipitator
2024-08-26 15:11:49 [INFO] idaes.__main__: Initializing fs.cleaner_sep
2024-08-26 15:11:49 [INFO] idaes.init.fs.cleaner_sep: Initialization Step 2 Complete: optimal - Optimal Solution Found
2024-08-26 15:11:49 [INFO] idaes.__main__: Initializing fs.solex_rougher_strip.mscontactor
2024-08-26 15:11:49 [INFO] idaes.__main__: Initializing fs.scrub_sep
2024-08-26 15:11:50 [INFO] idaes.init.fs.scrub_sep: Initialization Step 2 Complete: optimal - Optimal Solution Found
2024-08-26 15:11:50 [INFO] idaes.__main__: Initializing fs.sl_sep2
2024-08-26 15:11:50 [INFO] idaes.__main__: Initializing fs.cleaner_mixer
2024-08-26 15:11:50 [INFO] idaes.init.fs.cleaner_mixer: Initialization Complete: optimal - Optimal Solution Found
2024-08-26 15:11:50 [INFO] idaes.__main__: Initializing fs.rougher_sep
2024-08-26 15:11:50 [INFO] idaes.init.fs.rougher_sep: Initialization Step 2 Complete: optimal - Optimal Solution Found
2024-08-26 15:11:50 [INFO] idaes.__main__: Initializing fs.leach_mixer
2024-08-26 15:11:50 [INFO] idaes.__main__: Initializing fs.precip_sep
2024-08-26 15:11:50 [INFO] idaes.init.fs.precip_sep: Initialization Step 2 Complete: optimal - Optimal Solution Found
2024-08-26 15:11:50 [INFO] idaes.__main__: Initializing fs.rougher_mixer
2024-08-26 15:11:50 [INFO] idaes.init.fs.rougher_mixer: Initialization Complete: optimal - Optimal Solution Found
2024-08-26 15:11:50 [INFO] idaes.__main__: Initializing fs.precip_sx_mixer
2024-08-26 15:11:51 [INFO] idaes.init.fs.precip_sx_mixer: Initialization Complete: optimal - Optimal Solution Found
WARNING: Direct failed to converge in 1 iterations
2024-08-26 15:11:51 [INFO] idaes.__main__: Initializing fs.leach_filter_cake
2024-08-26 15:11:51 [INFO] idaes.__main__: Initializing fs.leach_filter_cake_liquid
2024-08-26 15:11:51 [INFO] idaes.__main__: Initializing fs.sc_circuit_purge
2024-08-26 15:11:51 [INFO] idaes.__main__: Initializing fs.cleaner_purge
2024-08-26 15:11:51 [INFO] idaes.__main__: Initializing fs.roaster
2024-08-26 15:11:51 [INFO] idaes.__main__: Initializing fs.precip_purge
Ipopt 3.13.2: 

******************************************************************************
This program contains Ipopt, a library for large-scale nonlinear optimization.
 Ipopt is released as open source code under the Eclipse Public License (EPL).
         For more information visit http://projects.coin-or.org/Ipopt

This version of Ipopt was compiled from source code available at
    https://github.com/IDAES/Ipopt as part of the Institute for the Design of
    Advanced Energy Systems Process Systems Engineering Framework (IDAES PSE
    Framework) Copyright (c) 2018-2019. See https://github.com/IDAES/idaes-pse.

This version of Ipopt was compiled using HSL, a collection of Fortran codes
    for large-scale scientific computation.  All technical papers, sales and
    publicity material resulting from use of the HSL codes within IPOPT must
    contain the following acknowledgement:
        HSL, a collection of Fortran codes for large-scale scientific
        computation. See http://www.hsl.rl.ac.uk.
******************************************************************************

This is Ipopt version 3.13.2, running with linear solver ma27.

Number of nonzeros in equality constraint Jacobian...:     9784
Number of nonzeros in inequality constraint Jacobian.:        0
Number of nonzeros in Lagrangian Hessian.............:     1535

Total number of variables............................:     3697
                     variables with only lower bounds:     2796
                variables with lower and upper bounds:       16
                     variables with only upper bounds:       78
Total number of equality constraints.................:     3697
Total number of inequality constraints...............:        0
        inequality constraints with only lower bounds:        0
   inequality constraints with lower and upper bounds:        0
        inequality constraints with only upper bounds:        0

iter    objective    inf_pr   inf_du lg(mu)  ||d||  lg(rg) alpha_du alpha_pr  ls
   0  0.0000000e+00 1.58e+06 1.00e+00  -1.0 0.00e+00    -  0.00e+00 0.00e+00   0
   1  0.0000000e+00 5.65e+05 3.62e+07  -1.0 8.18e+05    -  7.43e-05 6.42e-01h  1
   2  0.0000000e+00 2.03e+04 5.33e+06  -1.0 1.27e+05    -  2.26e-02 9.64e-01h  1
   3  0.0000000e+00 2.05e+02 3.16e+05  -1.0 2.42e+04    -  8.10e-01 9.90e-01h  1
   4  0.0000000e+00 1.90e+00 2.28e+03  -1.0 2.58e+02    -  9.89e-01 9.92e-01h  1
   5  0.0000000e+00 9.48e-05 4.81e+04  -1.0 2.15e+00    -  9.90e-01 1.00e+00h  1

Number of Iterations....: 5

                                   (scaled)                 (unscaled)
Objective...............:   0.0000000000000000e+00    0.0000000000000000e+00
Dual infeasibility......:   0.0000000000000000e+00    0.0000000000000000e+00
Constraint violation....:   2.5574220220920055e-10    9.4795362088007109e-05
Complementarity.........:   0.0000000000000000e+00    0.0000000000000000e+00
Overall NLP error.......:   2.5574220220920055e-10    9.4795362088007109e-05


Number of objective function evaluations             = 6
Number of objective gradient evaluations             = 6
Number of equality constraint evaluations            = 6
Number of inequality constraint evaluations          = 0
Number of equality constraint Jacobian evaluations   = 6
Number of inequality constraint Jacobian evaluations = 0
Number of Lagrangian Hessian evaluations             = 5
Total CPU secs in IPOPT (w/o function evaluations)   =      0.074
Total CPU secs in NLP function evaluations           =      0.004

EXIT: Optimal Solution Found.