-
Notifications
You must be signed in to change notification settings - Fork 234
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
Scaler for equilibrium reactor and saponification properties #1500
Changes from all commits
528a4f7
0faabba
47d9d73
47beac6
c39f256
04f1463
3ce08dd
4aa1405
7353472
e88bd83
d5135f4
9ccc8b4
9423501
6754964
f6079ca
ad475fd
dda16b3
bc8e411
4273e24
542914a
6578d01
84e9d69
c3615a1
6a7750c
8be4d5a
dbdbdd4
adc2809
d9f1904
5f4fe46
72a183d
a6ef9cb
a12cb95
b3aafb0
c51672f
861ef58
3805792
fbedaf4
76bec99
eec789e
d80eed6
c88879e
5a29597
154a622
a6d61fa
86faf86
ac2b639
f3f6330
ce8c202
05537fd
3c302e6
937e596
ef9c5e9
6662fc7
0308f1e
ec49414
f7eda46
e3b169d
1370bbe
a5de0b7
0964c7a
0af66e1
0f6d262
bd1b6aa
0cad4ed
3b6ae69
f3940a1
c71fbd1
10fbf3d
794f1ed
21ef587
2d1dd54
19212fa
8f56b88
1e3163f
1d55e06
fb5d2a9
1f200db
c8e7408
148c2f3
c18f464
2aa07a0
2759010
7f32d73
cd82ad8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -31,11 +31,13 @@ | |
from idaes.core.util.misc import add_object_reference | ||
from idaes.core.util.constants import Constants as const | ||
import idaes.logger as idaeslog | ||
from idaes.core.scaling import CustomScalerBase | ||
|
||
|
||
# Some more information about this module | ||
__author__ = "Andrew Lee" | ||
|
||
from idaes.core.util.scaling import get_scaling_factor | ||
|
||
# Set up logger | ||
_log = idaeslog.getLogger(__name__) | ||
|
@@ -111,12 +113,84 @@ def define_metadata(cls, obj): | |
) | ||
|
||
|
||
class SaponificationReactionScaler(CustomScalerBase): | ||
""" | ||
Scaler for saponification reaction package. | ||
Variables are scaled by nominal order of magnitude, and constraints | ||
using the inverse maximum scheme. | ||
""" | ||
|
||
DEFAULT_SCALING_FACTORS = {"reaction_rate": 1e2} | ||
|
||
def variable_scaling_routine( | ||
self, model, overwrite: bool = False, submodel_scalers: dict = None | ||
): | ||
if model.is_property_constructed("k_rxn"): | ||
# First check to see if k_rxn is already scaled | ||
sf = get_scaling_factor(model.k_rxn) | ||
|
||
if sf is not None and not overwrite: | ||
# k_rxn already has a scaling factor and we are not set to overwrite - move on | ||
pass | ||
else: | ||
# Hopefully temperature has been scaled, so we can get the nominal value of k_rxn | ||
# by walking the expression in the constraint. | ||
nominals = self.get_expression_nominal_values(model.arrhenius_eqn) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Saponification has a rather tight range of temperatures. This is probably overkill. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For this specific case probably, but this also stands as an example of how to do it for a more general case. |
||
|
||
# We should get two values, k_rxn (LHS) and the Arrhenius equation (RHS) | ||
# As of 10/3/2024, the LHS will be the 0-th element of the list, and the RHS the 1st | ||
# However, we cannot assume this will always be the case | ||
|
||
# If LHS has been scaled, nominal will be 1/sf, otherwise it will be k_rxn.value | ||
# Find the value which does NOT match this - guess that this is the 1st element | ||
if nominals[1] != model.k_rxn.value and sf is None: | ||
# This is the most likely case, so check it first | ||
nominal = nominals[1] | ||
elif sf is not None and nominals[1] != 1 / sf: | ||
# Next, check for case where k_rxn was already scaled | ||
nominal = nominals[1] | ||
else: | ||
# Otherwise we have the case where something changed in Pyomo since 10/3/2024 | ||
nominal = nominals[0] | ||
|
||
self.set_variable_scaling_factor( | ||
model.k_rxn, | ||
1 / nominal, | ||
overwrite=overwrite, | ||
) | ||
Comment on lines
+157
to
+161
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Order of magnitude scaling is probably not appropriate here. Based on heuristic analysis, a fixed scaling factor of 1/3 would work. |
||
|
||
if model.is_property_constructed("reaction_rate"): | ||
for j in model.reaction_rate.values(): | ||
self.scale_variable_by_default(j, overwrite=overwrite) | ||
Comment on lines
+163
to
+165
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If the user provides a default, we should do that, but we can get a decent scaling factor by multiplying the maximum scaling factor among the reactants provided by some period of time (I used 3600 s). (Equivalent to dividing the minimum default value by that period of time). |
||
|
||
def constraint_scaling_routine( | ||
self, model, overwrite: bool = False, submodel_scalers: dict = None | ||
): | ||
if model.is_property_constructed("arrhenius_eqn"): | ||
self.scale_constraint_by_nominal_value( | ||
model.arrhenius_eqn, | ||
scheme="inverse_maximum", | ||
overwrite=overwrite, | ||
) | ||
|
||
if model.is_property_constructed("rate_expression"): | ||
for j in model.rate_expression.values(): | ||
self.scale_constraint_by_nominal_value( | ||
j, | ||
scheme="inverse_maximum", | ||
overwrite=overwrite, | ||
) | ||
|
||
|
||
class _ReactionBlock(ReactionBlockBase): | ||
""" | ||
This Class contains methods which should be applied to Reaction Blocks as a | ||
whole, rather than individual elements of indexed Reaction Blocks. | ||
""" | ||
|
||
default_scaler = SaponificationReactionScaler | ||
|
||
def initialize(blk, outlvl=idaeslog.NOTSET, **kwargs): | ||
""" | ||
Initialization routine for reaction package. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -44,6 +44,7 @@ | |
from idaes.core.util.model_statistics import degrees_of_freedom | ||
from idaes.core.util.initialization import fix_state_vars, revert_state_vars | ||
import idaes.logger as idaeslog | ||
from idaes.core.scaling import CustomScalerBase | ||
|
||
# Some more information about this module | ||
__author__ = "Andrew Lee" | ||
|
@@ -135,12 +136,57 @@ def define_metadata(cls, obj): | |
) | ||
|
||
|
||
class SaponificationPropertiesScaler(CustomScalerBase): | ||
""" | ||
Scaler for saponification properties package. | ||
|
||
Flow and concentration are scaled by default value (if no user input provided), | ||
pressure is scaled assuming order of magnitude of 1e5 Pa, and temperature is | ||
scaled using the average of the bounds. Constraints using the inverse maximum | ||
scheme. | ||
""" | ||
|
||
UNIT_SCALING_FACTORS = { | ||
# "QuantityName: (reference units, scaling factor) | ||
"Pressure": (units.Pa, 1e-5), | ||
} | ||
|
||
DEFAULT_SCALING_FACTORS = { | ||
"flow_vol": 1e2, | ||
"conc_mol_comp": 1e-2, | ||
} | ||
|
||
def variable_scaling_routine( | ||
self, model, overwrite: bool = False, submodel_scalers: dict = None | ||
): | ||
self.scale_variable_by_default(model.flow_vol, overwrite=overwrite) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a way for the user to provide a default value, rather than just using whatever happens to be the default default value? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, although it might not be the best way to do it. The default values are in a class attribute (dict) which users can interact with. That said, that is probably not the most obvious API for it (we could have a method to update the defaults). |
||
self.scale_variable_by_units(model.pressure, overwrite=overwrite) | ||
self.scale_variable_by_bounds(model.temperature, overwrite=overwrite) | ||
for k, v in model.conc_mol_comp.items(): | ||
if k == "H2O": | ||
self.set_variable_scaling_factor(v, 1e-4, overwrite=overwrite) | ||
else: | ||
self.scale_variable_by_default(v, overwrite=overwrite) | ||
|
||
def constraint_scaling_routine( | ||
self, model, overwrite: bool = False, submodel_scalers: dict = None | ||
): | ||
if model.is_property_constructed("conc_water_eqn"): | ||
self.set_constraint_scaling_factor( | ||
model.conc_water_eqn, | ||
1e-4, | ||
overwrite=overwrite, | ||
) | ||
|
||
|
||
class _StateBlock(StateBlock): | ||
""" | ||
This Class contains methods which should be applied to Property Blocks as a | ||
whole, rather than individual elements of indexed Property Blocks. | ||
""" | ||
|
||
default_scaler = SaponificationPropertiesScaler | ||
|
||
def fix_initialization_states(self): | ||
""" | ||
Fixes state variables for state blocks. | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How does the scaling framework know to use this value?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When you tell it to
scale_variable_by_default
orscale_constraint_by_default
.