Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Solid-Liquid Separator Base Model #1283

Merged
merged 11 commits into from
Nov 7, 2023
2 changes: 1 addition & 1 deletion idaes/models/unit_models/flash.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ class FlashData(UnitModelBlockData):
**default** - EnergySplittingType.equal_temperature.
**Valid values:** {
**EnergySplittingType.equal_temperature** - outlet temperatures equal inlet
**EnergySplittingType.equal_molar_enthalpy** - oulet molar enthalpies equal
**EnergySplittingType.equal_molar_enthalpy** - outlet molar enthalpies equal
inlet,
**EnergySplittingType.enthalpy_split** - apply split fractions to enthalpy
flows.}""",
Expand Down
45 changes: 44 additions & 1 deletion idaes/models/unit_models/mscontactor.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
"""
from functools import partial

from pandas import DataFrame

# Import Pyomo libraries
from pyomo.environ import Block, Constraint, RangeSet, Reals, Set, units, Var
from pyomo.common.config import ConfigDict, ConfigValue, Bool, In
Expand Down Expand Up @@ -42,6 +44,7 @@
from idaes.core.solvers import get_solver
from idaes.core.util.model_serializer import to_json, from_json
import idaes.logger as idaeslog
from idaes.core.util.units_of_measurement import report_quantity

__author__ = "Andrew Lee"

Expand Down Expand Up @@ -902,7 +905,47 @@
)

def _get_performance_contents(self, time_point=0):
raise NotImplementedError()
# Due to the flexibility of the MSContactor and the number of possible terms
# that could be included here, we will leave this up to the user to define.
return {}

def _get_stream_table_contents(self, time_point=0):
stream_attributes = {}
stream_attributes["Units"] = {}

sblocks = {}
for stream, pconfig in self.config.streams.items():
sblock = getattr(self, stream)
flow_dir = pconfig.flow_direction

if pconfig.has_feed:
inlet_state = getattr(self, stream + "_inlet_state")
sblocks[stream + " Inlet"] = inlet_state[time_point]

if flow_dir == FlowDirection.forward:
outlet = self.elements.last()
elif flow_dir == FlowDirection.backward:
outlet = self.elements.first()

Check warning on line 928 in idaes/models/unit_models/mscontactor.py

View check run for this annotation

Codecov / codecov/patch

idaes/models/unit_models/mscontactor.py#L928

Added line #L928 was not covered by tests
else:
raise BurntToast("If/else overrun when constructing stream table")

Check warning on line 930 in idaes/models/unit_models/mscontactor.py

View check run for this annotation

Codecov / codecov/patch

idaes/models/unit_models/mscontactor.py#L930

Added line #L930 was not covered by tests

sblocks[stream + " Outlet"] = sblock[time_point, outlet]

for n, v in sblocks.items():
dvars = v.define_display_vars()

stream_attributes[n] = {}

for k in dvars:
for i in dvars[k].keys():
stream_key = k if i is None else f"{k} {i}"

quant = report_quantity(dvars[k][i])

stream_attributes[n][stream_key] = quant.m
stream_attributes["Units"][stream_key] = quant.u

return DataFrame.from_dict(stream_attributes, orient="columns")


def _get_state_blocks(blk, t, s, stream):
Expand Down
13 changes: 13 additions & 0 deletions idaes/models/unit_models/solid_liquid/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#################################################################################
# The Institute for the Design of Advanced Energy Systems Integrated Platform
# Framework (IDAES IP) was produced under the DOE Institute for the
# Design of Advanced Energy Systems (IDAES).
#
# Copyright (c) 2018-2023 by the software owners: The Regents of the
# University of California, through Lawrence Berkeley National Laboratory,
# National Technology & Engineering Solutions of Sandia, LLC, Carnegie Mellon
# University, West Virginia University Research Corporation, et al.
# All rights reserved. Please see the files COPYRIGHT.md and LICENSE.md
# for full copyright and license information.
#################################################################################
from .sl_separator import SLSeparator
302 changes: 302 additions & 0 deletions idaes/models/unit_models/solid_liquid/sl_separator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,302 @@
#################################################################################
# The Institute for the Design of Advanced Energy Systems Integrated Platform
# Framework (IDAES IP) was produced under the DOE Institute for the
# Design of Advanced Energy Systems (IDAES).
#
# Copyright (c) 2018-2023 by the software owners: The Regents of the
# University of California, through Lawrence Berkeley National Laboratory,
# National Technology & Engineering Solutions of Sandia, LLC, Carnegie Mellon
# University, West Virginia University Research Corporation, et al.
# All rights reserved. Please see the files COPYRIGHT.md and LICENSE.md
# for full copyright and license information.
#################################################################################
"""
Base model for solid-liquid separations.

This model is intended to form the basis for solid-liquid separations where a mixed
stream enters the unit and is separated into a concentrated solid-liquid stream and
a stream of pure liquid (e.g. filers, thickeners, etc.)
andrewlee94 marked this conversation as resolved.
Show resolved Hide resolved

This model assumes:

* separate property packages and ports for the solid and liquid
streams.
* two inlet Ports (solid and liquid)
* three outlet Ports (solids, liquid with solids, separated liquids).

"""
# Import Python libraries
import logging
from pandas import DataFrame

# Import Pyomo libraries
from pyomo.environ import Reference
from pyomo.common.config import ConfigBlock, ConfigValue, In
from pyomo.network import Port

# Import IDAES cores
from idaes.core import (
declare_process_block_class,
MaterialBalanceType,
MomentumBalanceType,
UnitModelBlockData,
useDefault,
)
from idaes.models.unit_models.separator import (
Separator,
SplittingType,
EnergySplittingType,
)
from idaes.core.initialization import BlockTriangularizationInitializer
from idaes.core.util.config import is_physical_parameter_block
from idaes.core.util.units_of_measurement import report_quantity


__author__ = "Andrew Lee"


# Set up logger
logger = logging.getLogger("idaes.unit_model")


@declare_process_block_class("SLSeparator")
class SLSeparatorData(UnitModelBlockData):
"""
Standard Solid-Liquid Separator Unit Model Class
"""

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. Flash units do not support dynamic behavior.""",
),
)
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. Flash units do not have defined volume, thus
this must be False.""",
),
)
CONFIG.declare(
"material_balance_type",
ConfigValue(
default=MaterialBalanceType.useDefault,
domain=In(MaterialBalanceType),
description="Material balance construction flag",
doc="""Indicates what type of mass balance should be constructed,
**default** - MaterialBalanceType.useDefault.
**Valid values:** {
**MaterialBalanceType.useDefault - refer to property package for default
balance type
**MaterialBalanceType.none** - exclude material balances,
**MaterialBalanceType.componentPhase** - use phase component balances,
**MaterialBalanceType.componentTotal** - use total component balances,
**MaterialBalanceType.elementTotal** - use total element balances,
**MaterialBalanceType.total** - use total material balance.}""",
),
)
CONFIG.declare(
"momentum_balance_type",
ConfigValue(
default=MomentumBalanceType.pressureTotal,
domain=In(MomentumBalanceType),
description="Momentum balance construction flag",
doc="""Indicates what type of momentum balance should be constructed,
**default** - MomentumBalanceType.pressureTotal.
**Valid values:** {
**MomentumBalanceType.none** - exclude momentum balances,
**MomentumBalanceType.pressureTotal** - single pressure balance for material,
**MomentumBalanceType.pressurePhase** - pressure balances for each phase,
**MomentumBalanceType.momentumTotal** - single momentum balance for material,
**MomentumBalanceType.momentumPhase** - momentum balances for each phase.}""",
),
)
CONFIG.declare(
"energy_split_basis",
ConfigValue(
default=EnergySplittingType.equal_temperature,
domain=EnergySplittingType,
description="Type of constraint to write for energy splitting",
doc="""Argument indicating basis to use for splitting energy this is
not used for when ideal_separation == True.
**default** - EnergySplittingType.equal_temperature.
**Valid values:** {
**EnergySplittingType.equal_temperature** - outlet temperatures equal inlet
**EnergySplittingType.equal_molar_enthalpy** - outlet molar enthalpies equal
inlet,
**EnergySplittingType.enthalpy_split** - apply split fractions to enthalpy
flows.}""",
),
)
CONFIG.declare(
"solid_property_package",
ConfigValue(
default=useDefault,
domain=is_physical_parameter_block,
description="Property package to use for solid phase",
doc="""Property parameter object used to define solid phase property
calculations,
**default** - useDefault.
**Valid values:** {
**useDefault** - use default package from parent model or flowsheet,
**PropertyParameterObject** - a PropertyParameterBlock object.}""",
),
)
CONFIG.declare(
"solid_property_package_args",
ConfigBlock(
implicit=True,
description="Arguments to use for constructing solid phase property packages",
doc="""A ConfigBlock with arguments to be passed to a solid phase property
block(s) and used when constructing these,
**default** - None.
**Valid values:** {
see property package for documentation.}""",
),
)
CONFIG.declare(
"liquid_property_package",
ConfigValue(
default=useDefault,
domain=is_physical_parameter_block,
description="Property package to use for liquid phase",
doc="""Property parameter object used to define liquid phase property
calculations,
**default** - useDefault.
**Valid values:** {
**useDefault** - use default package from parent model or flowsheet,
**PropertyParameterObject** - a PropertyParameterBlock object.}""",
),
)
CONFIG.declare(
"liquid_property_package_args",
ConfigBlock(
implicit=True,
description="Arguments to use for constructing liquid phase property packages",
doc="""A ConfigBlock with arguments to be passed to a liquid phase property
block(s) and used when constructing these,
**default** - None.
**Valid values:** {
see property package for documentation.}""",
),
)

default_initializer = BlockTriangularizationInitializer

def build(self):
"""
Begin building model (pre-DAE transformation).

Args:
None

Returns:
None
"""
# Call UnitModel.build to setup dynamics
super().build()

# Build Solid Phase - inlet=outlet so only need a StateBlock
# Setup StateBlock argument dict
tmp_dict = dict(**self.config.solid_property_package_args)
tmp_dict["has_phase_equilibrium"] = False
tmp_dict["defined_state"] = True

self.solid_state = self.config.solid_property_package.build_state_block(
self.flowsheet().time, doc="Solid properties in separator", **tmp_dict
)

# Add Solids Ports
self.add_port(
name="solid_inlet", block=self.solid_state, doc="Solid inlet to separator"
)
self.add_port(
name="solid_outlet",
block=self.solid_state,
doc="Solid outlet from separator",
)

# Add Liquid Inlet
# Setup StateBlock argument dict
tmp_dict = dict(**self.config.liquid_property_package_args)
tmp_dict["has_phase_equilibrium"] = False
tmp_dict["defined_state"] = True

self.liquid_inlet_state = self.config.liquid_property_package.build_state_block(
self.flowsheet().time,
doc="Liquid properties at inlet to separator",
**tmp_dict,
)

self.split = Separator(
property_package=self.config.liquid_property_package,
property_package_args=self.config.liquid_property_package_args,
outlet_list=["recovered", "retained"],
split_basis=SplittingType.totalFlow,
ideal_separation=False,
mixed_state_block=self.liquid_inlet_state,
has_phase_equilibrium=False,
material_balance_type=self.config.material_balance_type,
momentum_balance_type=self.config.momentum_balance_type,
energy_split_basis=self.config.energy_split_basis,
)

# Add liquid ports
self.add_port(
name="liquid_inlet",
block=self.liquid_inlet_state,
doc="Liquid inlet to separator",
)
self.recovered_liquid_outlet = Port(extends=self.split.recovered)
self.retained_liquid_outlet = Port(extends=self.split.retained)

# Add liquid recovery
self.liquid_recovery = Reference(self.split.split_fraction[:, "recovered"])

def initialize(self, **kwargs):
raise NotImplementedError(

Check warning on line 267 in idaes/models/unit_models/solid_liquid/sl_separator.py

View check run for this annotation

Codecov / codecov/patch

idaes/models/unit_models/solid_liquid/sl_separator.py#L267

Added line #L267 was not covered by tests
"The SLSeparator unit model does not support the old initialization API. "
"Please use the new API (InitializerObjects) instead."
)

def _get_performance_contents(self, time_point=0):

return {"vars": {"Liquid Recovery": self.liquid_recovery[time_point]}}

def _get_stream_table_contents(self, time_point=0):
stream_attributes = {}
stream_attributes["Units"] = {}

sblocks = {
"Solid Inlet": self.solid_state,
"Liquid Inlet": self.liquid_inlet_state,
"Solid Outlet": self.solid_state,
"Liquid in Solids Outlet": self.split.retained_state,
"Recovered Liquid Outlet": self.split.recovered_state,
}

for n, v in sblocks.items():
dvars = v[time_point].define_display_vars()

stream_attributes[n] = {}

for k in dvars:
for i in dvars[k].keys():
stream_key = k if i is None else f"{k} {i}"

quant = report_quantity(dvars[k][i])

stream_attributes[n][stream_key] = quant.m
stream_attributes["Units"][stream_key] = quant.u

return DataFrame.from_dict(stream_attributes, orient="columns")
Empty file.
Loading
Loading