Skip to content
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
2 changes: 2 additions & 0 deletions idaes/core/base/control_volume0d.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ class ControlVolume0DScaler(ControlVolumeScalerBase):
"phase_fraction": 10, # May have already been created by property package
}

_state_block_ref = "properties_out"

def variable_scaling_routine(
self, model, overwrite: bool = False, submodel_scalers: ComponentMap = None
):
Expand Down
270 changes: 269 additions & 1 deletion idaes/core/base/control_volume1d.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

# Import Pyomo libraries
from pyomo.environ import (
ComponentMap,
Constraint,
Expression,
Param,
Expand All @@ -47,8 +48,11 @@
MaterialFlowBasis,
MaterialBalanceType,
)
from idaes.core.base.control_volume_base import ControlVolumeScalerBase
from idaes.core.scaling import DefaultScalingRecommendation
from idaes.core.util.exceptions import (
BalanceTypeNotSupportedError,
BurntToast,
ConfigurationError,
PropertyNotSupportedError,
)
Expand All @@ -58,7 +62,7 @@

import idaes.logger as idaeslog

__author__ = "Andrew Lee, Jaffer Ghouse"
__author__ = "Andrew Lee, Jaffer Ghouse, Douglas Allan"


_log = idaeslog.getLogger(__name__)
Expand All @@ -77,6 +81,258 @@ class DistributedVars(Enum):
uniform = 1


class ControlVolume1DScaler(ControlVolumeScalerBase):
"""
Scaler object for the ControlVolume1D
"""

DEFAULT_SCALING_FACTORS = {
# We could scale length and area by magnitude if it were
# being fixed by the user, but we often have the volume
# given by an equality constraint involving geometry in
# the parent unit model.
"area": DefaultScalingRecommendation.userInputRequired,
"length": DefaultScalingRecommendation.userInputRequired,
"phase_fraction": 10, # May have already been created by property package
}

_state_block_ref = "properties"
_weight_attr_name = "length"

def variable_scaling_routine(
self, model, overwrite: bool = False, submodel_scalers: ComponentMap = None
):
"""
Routine to apply scaling factors to variables in model.

Derived classes must overload this method.

Args:
model: model to be scaled
overwrite: whether to overwrite existing scaling factors
submodel_scalers: ComponentMap of Scalers to use for sub-models

Returns:
None
"""
if model.flow_direction is FlowDirection.forward:
x_in = model.properties.index_set().first()
elif model.flow_direction is FlowDirection.backward:
x_in = model.properties.index_set().last()
elif model.flow_direction is FlowDirection.notSet:
raise RuntimeError(
"Scaler called on ControlVolume1D without a flow "
"direction set. The unit model containing the ControlVolume1D "
"should use the add_geometry method to specify a flow direction "
"as part of model construction."
)
else:
raise BurntToast(
"Unknown flow direction specified. This indicates "
"a new flow direction was added without support being "
"extended to the scaler. Please contact the IDAES "
"development team with this error."
)

self.propagate_state_scaling(
target_state=model.properties,
source_state=model.properties[x_in],
overwrite=overwrite,
)
self.call_submodel_scaler_method(
submodel=model.properties,
submodel_scalers=submodel_scalers,
method="variable_scaling_routine",
overwrite=overwrite,
)
for v in model.area.values():
self.scale_variable_by_default(v, overwrite=overwrite)

self.scale_variable_by_default(model.length, overwrite=overwrite)
if hasattr(model, "phase_fraction"):
for v in model.phase_fraction.values():
self.scale_variable_by_default(v, overwrite=overwrite)

super().variable_scaling_routine(
model, overwrite=overwrite, submodel_scalers=submodel_scalers
)

if hasattr(model, "_flow_terms"): # pylint: disable=protected-access
for idx, v in model._flow_terms.items():
self.scale_variable_by_definition_constraint(
v, model.material_flow_linking_constraints[idx], overwrite=overwrite
)

if hasattr(model, "material_flow_dx"):
for idx, v in model._flow_terms.items(): # pylint: disable=protected-access
# As domain is normalized, derivative should have same
# scale as flow
self.scale_variable_by_component(
model.material_flow_dx[idx], v, overwrite=overwrite
)

# TODO elemental flows
# if hasattr(self, "elemental_flow_term"):
# for (t, x, e), v in self.elemental_flow_term.items():
# flow_basis = self.properties[t, x].get_material_flow_basis()

# sf = iscale.min_scaling_factor(
# [
# self.properties[t, x].get_material_density_terms(p, j)
# for (p, j) in phase_component_set
# ],
# default=1,
# warning=True,
# )
# if flow_basis == MaterialFlowBasis.molar:
# sf *= 1
# elif flow_basis == MaterialFlowBasis.mass:
# # MW scaling factor is the inverse of its value
# sf *= value(self.properties[t, x].mw_comp[j])

# iscale.set_scaling_factor(v, sf)

# if hasattr(self, "elemental_flow_dx"):
# for (t, x, e), v in self.elemental_flow_dx.items():
# if iscale.get_scaling_factor(v) is None:
# # As domain is normalized, scale should be equal to flow
# sf = iscale.get_scaling_factor(self.elemental_flow_term[t, x, e])
# iscale.set_scaling_factor(v, sf)

if hasattr(model, "_enthalpy_flow"):
for (
idx,
v,
) in model._enthalpy_flow.items(): # pylint: disable=protected-access
self.scale_variable_by_definition_constraint(
v, model.enthalpy_flow_linking_constraint[idx], overwrite=overwrite
)

if hasattr(model, "enthalpy_flow_dx"):
for (
idx,
v,
) in model._enthalpy_flow.items(): # pylint: disable=protected-access
# Normalized domain, so scale should be the same as flow
# TODO is this correct?
self.scale_variable_by_component(
model.enthalpy_flow_dx[idx], v, overwrite=overwrite
)
if hasattr(model, "pressure_dx"):
for (t, x), v in model.pressure_dx.items():
self.scale_variable_by_component(
v, model.properties[t, x].pressure, overwrite=overwrite
)

def constraint_scaling_routine(
self, model, overwrite: bool = False, submodel_scalers: ComponentMap = None
):
"""
Routine to apply scaling factors to constraints in model.

Derived classes must overload this method.

Args:
model: model to be scaled
overwrite: whether to overwrite existing scaling factors
submodel_scalers: ComponentMap of Scalers to use for sub-models

Returns:
None
"""
self.call_submodel_scaler_method(
submodel=model.properties,
submodel_scalers=submodel_scalers,
method="constraint_scaling_routine",
overwrite=overwrite,
)

super().constraint_scaling_routine(
model, overwrite=overwrite, submodel_scalers=submodel_scalers
)

if hasattr(model, "material_flow_linking_constraints"):
for idx, v in model._flow_terms.items(): # pylint: disable=protected-access
self.scale_constraint_by_component(
model.material_flow_linking_constraints[idx], v, overwrite=overwrite
)

# TODO element balance
# if hasattr(model, "elemental_flow_constraint"):
# for idx, c in model.elemental_flow_constraint.items():
# self.scale_constraint_by_component(
# c,
# model.elemental_flow_term[idx],
# overwrite=overwrite
# )

if hasattr(model, "enthalpy_flow_linking_constraint"):
for idx, v in model._enthalpy_flow.items():
self.scale_constraint_by_component(
model.enthalpy_flow_linking_constraint[idx], v, overwrite=overwrite
)

if hasattr(model, "material_flow_dx_disc_eq"):
for idx, c in model.material_flow_dx_disc_eq.items():
self.scale_constraint_by_component(
c, model.material_flow_dx[idx], overwrite=overwrite
)

if hasattr(model, "_flow_terms_length_domain_cont_eq"):
for (
idx,
c,
) in (
model._flow_terms_length_domain_cont_eq.items()
): # pylint: disable=protected-access
self.scale_constraint_by_component(
c,
model._flow_terms[idx], # pylint: disable=protected-access
overwrite=overwrite,
)

if hasattr(model, "enthalpy_flow_dx_disc_eq"):
for idx, c in model.enthalpy_flow_dx_disc_eq.items():
self.scale_constraint_by_component(
c, model.enthalpy_flow_dx[idx], overwrite=overwrite
)

if hasattr(model, "_enthalpy_flow_length_domain_cont_eq"):
for (
idx,
c,
) in (
model._enthalpy_flow_length_domain_cont_eq.items()
): # pylint: disable=protected-access
self.scale_constraint_by_component(
c,
model._enthalpy_flow[idx], # pylint: disable=protected-access
overwrite=overwrite,
)

if hasattr(model, "pressure_dx_disc_eq"):
for idx, c in model.pressure_dx_disc_eq.items():
self.scale_constraint_by_component(
c, model.pressure_dx[idx], overwrite=overwrite
)

if hasattr(model, "pressure_length_domain_cont_eq"):
for (t, x), c in model.pressure_length_domain_cont_eq.items():
self.scale_constraint_by_component(
c, model.properties[t, x].pressure, overwrite=overwrite
)

# TODO element flow
# if hasattr(model, "element_flow_dx_disc_eq"):
# for idx, c in model.element_flow_dx_disc_eq.items():
# self.scale_constraint_by_component(
# c,
# model.element_flow_dx[idx],
# overwrite=overwrite
# )
# element_flow_dx_cont_eq


@declare_process_block_class(
"ControlVolume1DBlock",
doc="""
Expand All @@ -99,6 +355,8 @@ class ControlVolume1DBlockData(ControlVolumeBlockData):
specified in the chosen property package.
"""

default_scaler = ControlVolume1DScaler

CONFIG = ControlVolumeBlockData.CONFIG()
CONFIG.declare(
"area_definition",
Expand Down Expand Up @@ -155,6 +413,13 @@ class ControlVolume1DBlockData(ControlVolumeBlockData):
),
)

@property
def flow_direction(self):
"""
Property giving the flow direction in the ControlVolume1D
"""
return self._flow_direction

def build(self):
"""
Build method for ControlVolume1DBlock blocks.
Expand All @@ -166,6 +431,7 @@ def build(self):
super(ControlVolume1DBlockData, self).build()

self._validate_config_args()
self._flow_direction = FlowDirection.notSet

def _validate_config_args(self):
# Validate DAE config arguments
Expand Down Expand Up @@ -2437,6 +2703,8 @@ def calculate_scaling_factors(self):
)
iscale.constraint_scaling_transform(c, sf, overwrite=False)

# TODO Inherent reaction stoichiometry constraint missing

if hasattr(self, "material_balances"):
mb_type = self._constructed_material_balance_type
if mb_type == MaterialBalanceType.componentPhase:
Expand Down
Loading
Loading