-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #16 from waikato-ahuora-smart-energy-systems/fix-fphx
Generic property package fixes
- Loading branch information
Showing
26 changed files
with
1,576 additions
and
910 deletions.
There are no files selected for viewing
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
from pyomo.environ import Block, Constraint | ||
from pyomo.core.base.expression import ScalarExpression, Expression, _GeneralExpressionData, ExpressionData | ||
from pyomo.core.base.var import ScalarVar, _GeneralVarData, VarData, IndexedVar, Var | ||
|
||
from property_packages.utils.add_extra_expressions import add_extra_expressions | ||
|
||
|
||
class StateBlockConstraints: | ||
|
||
""" | ||
Extended state blocks should inherit from this class. | ||
Adds additional expressions and constraints to a state block. | ||
""" | ||
|
||
def build(blk, *args): | ||
add_extra_expressions(blk) | ||
blk.constraints = Block() | ||
|
||
|
||
def constrain(blk, name: str, value: float) -> Constraint | Var | None: | ||
"""constrain a component by name to a value""" | ||
var = getattr(blk, name) | ||
return blk.constrain_component(var, value) | ||
|
||
|
||
def constrain_component(blk, component: Var | Expression, value: float) -> Constraint | Var | None: | ||
""" | ||
Constrain a component to a value | ||
""" | ||
if type(component) == ScalarExpression: | ||
c = Constraint(expr=component == value) | ||
c.defining_state_var = True | ||
blk.constraints.add_component(component.local_name, c) | ||
return c | ||
elif type(component) in (ScalarVar, _GeneralVarData, VarData, IndexedVar): | ||
component.fix(value) | ||
return component | ||
elif type(component) in (_GeneralExpressionData, ExpressionData): | ||
# allowed, but we don't need to fix it (eg. mole_frac_comp in helmholtz) | ||
return None | ||
else: | ||
raise Exception( | ||
f"Component {component} is not a Var or Expression: {type(component)}" | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,101 +1,87 @@ | ||
# extends the IDAES helmholtz property package to include additional properties and methods. | ||
from pyomo.environ import ( | ||
Expression, | ||
SolverFactory, | ||
check_optimal_termination, | ||
units | ||
) | ||
from idaes.models.properties.general_helmholtz.helmholtz_state import HelmholtzStateBlockData, _StateBlock | ||
from idaes.models.properties.general_helmholtz.helmholtz_functions import HelmholtzParameterBlockData | ||
from idaes.core import declare_process_block_class | ||
from property_packages.utils.add_extra_expressions import add_extra_expressions | ||
from pyomo.environ import Constraint, Block | ||
from pyomo.core.base.expression import Expression, ScalarExpression, _GeneralExpressionData, ExpressionData | ||
from pyomo.core.base.var import IndexedVar, ScalarVar, Var, _GeneralVarData,VarData | ||
from idaes.core.util.initialization import solve_indexed_blocks, revert_state_vars | ||
from idaes.core.util.model_statistics import degrees_of_freedom | ||
from idaes.core.util.exceptions import InitializationError | ||
import idaes.logger as idaeslog | ||
|
||
from property_packages.base.state_block_constraints import StateBlockConstraints | ||
from property_packages.utils.fix_state_vars import fix_state_vars | ||
|
||
|
||
class _ExtendedStateBlock(_StateBlock): | ||
""" | ||
This class contains methods which should be applied to Property Blocks as a | ||
whole, rather than individual elements of indexed Property Blocks. | ||
""" | ||
def __init__(self, *args, **kwargs): | ||
super().__init__(*args, **kwargs) | ||
|
||
def initialize(self, *args, **kwargs): | ||
hold_state = kwargs.pop("hold_state", False) | ||
for i, v in self.items(): | ||
v.constraints.deactivate() | ||
res = super().initialize(*args, **kwargs) | ||
flags = {} | ||
for i, v in self.items(): | ||
v.constraints.activate() | ||
flags[i] = {} | ||
if hold_state: | ||
# Fix the required state variables for zero degrees of freedom, and return a dictionary of the flags. | ||
if not hasattr(v.constraints,"flow_mass") and not v.flow_mol.is_fixed(): | ||
# We need to fix the flow_mol variable | ||
flags[i]["flow_mol"] = True | ||
v.flow_mol.fix() | ||
avaliable_constraints = ["enth_mass","temperature","total_energy_flow","entr_mass","entr_mol","smooth_temperature","custom_vapor_frac"] | ||
if not v.enth_mol.is_fixed(): | ||
# check if any of the constraints exist | ||
found_constraint = False | ||
for constraint in avaliable_constraints: | ||
if hasattr(v.constraints,constraint): | ||
# we don't need to fix the variable | ||
# but we need to remove this from the list of constraints (it can't be used to define pressure) | ||
avaliable_constraints.remove(constraint) | ||
found_constraint = True | ||
break | ||
if not found_constraint: | ||
# we need to fix the variable | ||
flags[i]["enth_mol"] = True | ||
v.enth_mol.fix() | ||
if not v.pressure.is_fixed(): | ||
# check if any of the constraints exist | ||
found_constraint = False | ||
for constraint in avaliable_constraints: | ||
if hasattr(v.constraints,constraint): | ||
# we don't need to fix the variable | ||
# but we need to remove this from the list of constraints (it can't be used to define pressure) | ||
avaliable_constraints.remove(constraint) | ||
found_constraint = True | ||
break | ||
if not found_constraint: | ||
# we need to fix the variable | ||
flags[i]["pressure"] = True | ||
v.pressure.fix() | ||
return flags | ||
|
||
def release_state(self, flags, outlvl=idaeslog.NOTSET): | ||
for i, v in self.items(): | ||
for key in flags[i]: | ||
getattr(v,key).unfix() | ||
def initialize(blk, *args, **kwargs): | ||
outlvl = kwargs.get("outlvl", idaeslog.NOTSET) | ||
init_log = idaeslog.getInitLogger(blk.name, outlvl, tag="properties") | ||
solve_log = idaeslog.getSolveLogger(blk.name, outlvl, tag="properties") | ||
|
||
flag_dict = fix_state_vars(blk, kwargs.get("state_args", None)) | ||
|
||
dof = degrees_of_freedom(blk) | ||
if dof != 0: | ||
raise InitializationError( | ||
f"{blk.name} Unexpected degrees of freedom during " | ||
f"initialization at property initialization step: {dof}." | ||
) | ||
|
||
res = None | ||
opt = SolverFactory('ipopt') | ||
with idaeslog.solver_log(solve_log, idaeslog.DEBUG) as slc: | ||
try: | ||
res = solve_indexed_blocks(opt, [blk], tee=slc.tee) | ||
except ValueError as e: | ||
if str(e).startswith("No variables appear"): | ||
# https://github.com/Pyomo/pyomo/pull/3445 | ||
pass | ||
else: | ||
raise e | ||
|
||
if res is not None and not check_optimal_termination(res): | ||
raise InitializationError( | ||
f"{blk.name} failed to initialize successfully. Please check " | ||
f"the output logs for more information." | ||
) | ||
|
||
if kwargs.get("hold_state") is True: | ||
return flag_dict | ||
else: | ||
blk.release_state(flag_dict) | ||
|
||
init_log.info( | ||
"Property package initialization: {}.".format(idaeslog.condition(res)) | ||
) | ||
|
||
def release_state(blk, flags, outlvl=idaeslog.NOTSET): | ||
revert_state_vars(blk, flags) | ||
|
||
|
||
@declare_process_block_class("HelmholtzExtendedStateBlock", block_class=_ExtendedStateBlock) | ||
class HelmholtzExtendedStateBlockData(HelmholtzStateBlockData): | ||
class HelmholtzExtendedStateBlockData(HelmholtzStateBlockData, StateBlockConstraints): | ||
|
||
def build(blk, *args): | ||
HelmholtzStateBlockData.build(blk, *args) | ||
StateBlockConstraints.build(blk, *args) | ||
|
||
# rename temperature to old_temperature | ||
old_temperature = blk.temperature | ||
blk.del_component("temperature") | ||
blk.add_component("old_temperature", old_temperature) | ||
# create smooth temperature expression | ||
blk.add_component("temperature", Expression( | ||
expr=blk.old_temperature + (blk.enth_mol / (1 * units.J/units.mol))*0.000001 * units.K | ||
)) | ||
|
||
def build(self, *args): | ||
super().build(*args) | ||
# Add expressions for smooth_temperature, enthalpy in terms of mass, etc. | ||
add_extra_expressions(self) | ||
# Add a block for constraints, so we can disable or enable them in bulk | ||
self.constraints = Block() | ||
|
||
def constrain(self,name:str,value:float): | ||
# Value must be a float. TODO: Handle unit conversion. | ||
var = getattr(self,name) | ||
if type(var) == ScalarExpression: | ||
self.constraints.add_component(name, Constraint(expr=var == value)) | ||
elif type(var) in (ScalarVar, _GeneralVarData, VarData): | ||
var.fix(value) | ||
elif type(var) in ( _GeneralExpressionData, ExpressionData) : | ||
# allowed, but we don't need to fix it (eg. mole_frac_comp in helmholtz) | ||
print(f"Variable {self} {name} is an Expression: {type(var)}") | ||
pass | ||
else: | ||
raise Exception(f"Variable {self} {name} is not a Var or Expression: {type(var)}") | ||
|
||
@declare_process_block_class("HelmholtzExtendedParameterBlock") | ||
class HelmholtzExtendedParameterBlockData(HelmholtzParameterBlockData): | ||
def build(self): | ||
super().build() | ||
# set that we should use the extended state block | ||
self._state_block_class = HelmholtzExtendedStateBlock # type: ignore because it'll get created with declare_process_block_class | ||
self._state_block_class = HelmholtzExtendedStateBlock # noqa: F821 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.