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

Adding StreamScaler unit model, along with the test and rst file. #1517

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,8 +213,8 @@ def main() -> int:
"-t",
"--timeout",
dest="timeout",
help="Timeout (in seconds) for sphinx-build (default=180)",
default=180,
help="Timeout (in seconds) for sphinx-build (default=360)",
default=360,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you meant to change this?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this was an intended change that I asked Dan about. When I ran build.py, it would timeout every time. He said it was okay to change this so it could be a topic of discussion

type=int,
)
prs.add_argument(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ Unit Models
skeleton_unit
statejunction
stoichiometric_reactor
stream_scaler
translator
turbine
valve
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
Stream Scaler Block
===================

Stream Scaler Blocks are used to adjust size of streams to represent, for example, a stream being split across several identical units, which are then all modeled as a single IDAES unit
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An explanation of how this is done is important - some of this could be done by expanding the sections below.


Degrees of Freedom
------------------

Stream Scaler blocks generally have zero degrees of freedom.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at the code, there is one degree of freedom (and an additional variable).


Model Structure
---------------

Stream Scaler Blocks consists of a single StateBlock (named properties), each with an inlet and outlet port.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not correct from reading the code. The inlet state has a different name, and there is a separate (empty) outlet Block.


Additional Constraints
----------------------

Stream Scaler Blocks write no additional constraints to the model.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is also not correct - the model is adding constraints.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, it actually isn't . It forwards VarLikeExpressions on the outlet port that have adjusted extensive variables. Additional Constraints arise from the additional arc constraints, but nothing internal to the unit model.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or rather, I should say no additional constraints* (besides those naturally occurring in StateBlocks).


Variables
---------

Stream Scaler blocks add no additional Variables.

.. module:: idaes.models.unit_models.stream_scaler


Initialization
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do these actually exist?

--------------

.. autoclass:: StreamScalerInitializer
:members: initialization_routine

StreamScaler Class
------------------

.. autoclass:: StreamScaler
:members:

StreamScalerData Class
----------------------

.. autoclass:: StreamScalerData
:members:

1 change: 1 addition & 0 deletions idaes/models/unit_models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
)
from .shell_and_tube_1d import ShellAndTube1D, ShellAndTubeInitializer
from .skeleton_model import SkeletonUnitModel, SkeletonUnitModelData
from .stream_scaler import StreamScaler
Copy link
Member

@andrewlee94 andrewlee94 Oct 31, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We generally include both the main class and the data class here.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay thanks for the feedback

from .statejunction import StateJunction, StateJunctionInitializer
from .stoichiometric_reactor import StoichiometricReactor
from .translator import Translator
Expand Down
239 changes: 239 additions & 0 deletions idaes/models/unit_models/stream_scaler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
#################################################################################
# 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.
#################################################################################
"""
Unit model to adjust size of streams to represent, for example, a stream being split across several identical units,
which are then all modeled as a single IDAES unit
"""
from functools import partial

from pyomo.environ import (
Block,
PositiveReals,
units as pyunits,
Var,
)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These pylint issues need to be addressed for the tests to pass.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On it!

from pyomo.network import Port
from pyomo.common.config import ConfigBlock, ConfigValue, In

from idaes.core import (
declare_process_block_class,
UnitModelBlockData,
useDefault,
)
from idaes.core.util.config import (
is_physical_parameter_block,
)
from idaes.core.base.var_like_expression import VarLikeExpression
from idaes.core.util.tables import create_stream_table_dataframe
import idaes.core.util.scaling as iscale
import idaes.logger as idaeslog

from idaes.models.unit_models.feed import FeedInitializer as StreamScalerInitializer

__author__ = "Douglas Allan"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should @tannerpolley be one of the authors here?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually didnt build the actual unit model itself and didn't edit any of the code here. I was just asked to implement the integration of the unit model into the main repo



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


@declare_process_block_class("StreamScaler")
class StreamScalerData(UnitModelBlockData):
"""
Unit model to adjust size of streams to represent, for example, a stream being split across several identical units,
which are then all modeled as a single IDAES unit
"""

default_initializer = StreamScalerInitializer

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. Scaler blocks are always steady-state.""",
),
)
CONFIG.declare(
"has_holdup",
ConfigValue(
default=False,
domain=In([False]),
description="Holdup construction flag - must be False",
doc="Scaler blocks do not contain holdup, thus this must be False.",
),
)
CONFIG.declare(
"property_package",
ConfigValue(
default=useDefault,
domain=is_physical_parameter_block,
description="Property package to use for StreamScaler",
doc="""Property parameter object used to define property
calculations, **default** - useDefault.
**Valid values:** {
**useDefault** - use default package from parent model or flowsheet,
**PropertyParameterObject** - a PropertyParameterBlock object.}""",
),
)
CONFIG.declare(
"property_package_args",
ConfigBlock(
implicit=True,
description="Arguments to use for constructing property packages",
doc="""A ConfigBlock with arguments to be passed to a property
block(s) and used when constructing these,
**default** - None.
**Valid values:** {
see property package for documentation.}""",
),
)

def build(self):
"""
General build method for StreamScalerData. This method calls a number
of sub-methods which automate the construction of expected attributes
of unit models.

Inheriting models should call `super().build`.

Args:
None

Returns:
None
"""
# Call super.build()
super(StreamScalerData, self).build()

tmp_dict = dict(**self.config.property_package_args)
tmp_dict["has_phase_equilibrium"] = False
tmp_dict["defined_state"] = True

# Call setup methods from ControlVolumeBlockData
self._get_property_package()
self._get_indexing_sets()

self.inlet_block = self.config.property_package.build_state_block(
self.flowsheet().time, doc="Material properties at inlet", **tmp_dict
)
self.outlet_block = Block()
self.multiplier = Var(
initialize=1,
domain=PositiveReals,
units=pyunits.dimensionless,
doc="Factor by which to scale dimensionless streams",
)
self.add_inlet_port(name="inlet", block=self.inlet_block)
self.outlet = Port(doc="Outlet port")

def rule_scale_var(b, *args, var=None):
return self.multiplier * var[args]

def rule_no_scale_var(b, *args, var=None):
return var[args]

for var_name in self.inlet.vars.keys():
var = getattr(self.inlet, var_name)
if "flow" in var_name:
rule = partial(rule_scale_var, var=var)
else:
rule = partial(rule_no_scale_var, var=var)
self.outlet_block.add_component(
var_name, VarLikeExpression(var.index_set(), rule=rule)
)
expr = getattr(self.outlet_block, var_name)
self.outlet.add(expr, var_name)

def initialize_build(
blk, outlvl=idaeslog.NOTSET, optarg=None, solver=None, hold_state=False
):
"""
Initialization routine for StreamScaler.

Keyword Arguments:
outlvl : sets output level of initialization routine
optarg : solver options dictionary object (default=None, use
default solver options)
solver : str indicating which solver to use during
initialization (default = None, use default solver)
hold_state : flag indicating whether the initialization routine
should unfix any state variables fixed during
initialization, **default** - False. **Valid values:**
**True** - states variables are not unfixed, and a dict of
returned containing flags for which states were fixed
during initialization, **False** - state variables are
unfixed after initialization by calling the release_state
method.

Returns:
If hold_states is True, returns a dict containing flags for which
states were fixed during initialization.
"""
# init_log = idaeslog.getInitLogger(blk.name, outlvl, tag="unit")
# solve_log = idaeslog.getSolveLogger(blk.name, outlvl, tag="unit")

# Create solver

# Initialize inlet state blocks
flags = blk.inlet_block.initialize(
outlvl=outlvl,
optarg=optarg,
solver=solver,
hold_state=True,
)

if hold_state is True:
return flags
else:
blk.release_state(flags, outlvl=outlvl)

def release_state(blk, flags, outlvl=idaeslog.NOTSET):
"""
Method to release state variables fixed during initialization.

Keyword Arguments:
flags : dict containing information of which state variables
were fixed during initialization, and should now be
unfixed. This dict is returned by initialize if
hold_state = True.
outlvl : sets output level of logging

Returns:
None
"""
blk.inlet_block.release_state(flags, outlvl=outlvl)

def _get_stream_table_contents(self, time_point=0):
io_dict = {
"Inlet": self.inlet,
# "Outlet": self.outlet,
}
return create_stream_table_dataframe(io_dict, time_point=time_point)

def calculate_scaling_factors(self):
# Scaling factors for the property block are calculated automatically
super().calculate_scaling_factors()

# Need to pass on scaling factors from the property block to the outlet
# VarLikeExpressions so arcs get scaled right
scale = 1 / self.multiplier.value
for var_name in self.inlet.vars.keys():
var = getattr(self.inlet, var_name)
outlet_expr = getattr(self.outlet, var_name)
for key, subvar in var.items():
sf = iscale.get_scaling_factor(subvar, default=1, warning=True)
iscale.set_scaling_factor(outlet_expr[key], scale * sf)
Loading
Loading