##############################################################################
# Institute for the Design of Advanced Energy Systems Process Systems
# Engineering Framework (IDAES PSE Framework) Copyright (c) 2018, 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.txt and LICENSE.txt for full copyright and
# license information, respectively. Both files are also available online
# at the URL "https://github.com/IDAES/idaes".
##############################################################################
from __future__ import division
"""Transformation to create a process synthesis model."""
import textwrap
import fnmatch
from pyomo.core.base import Transformation
from pyomo.common.plugin import alias
from pyomo.environ import Block, Var, Binary, Constraint
from pyomo.network import Port
from pyomo.core.kernel.component_set import ComponentSet
from pyomo.gdp import Disjunct, Disjunction
def _ReturnFalse():
return False
[docs]class Pyosyn(Transformation):
"""Transformation to create a process synthesis model."""
alias('idaes.conceptual_design',
doc=textwrap.fill(textwrap.dedent(__doc__.strip())))
def _apply_to(self, instance):
"""Apply the transformation.
Args:
instance (Model): an IDAES model for which
to enable conceptual design.
"""
# TODO need special handling if processing indexed unit
# Iterate through all the active Blocks and Disjuncts in the model
for blk in instance.component_data_objects(
ctype=(Block, Disjunct), active=True,
descend_into=(Block, Disjunct)):
if not getattr(blk, 'is_process_unit', _ReturnFalse)():
# If the block or disjunct is not a process unit, skip it.
continue
blk_parent = blk.parent_block()
# for each process unit, convert into a disjunct and create
# corresponding does-not-exist disjunct
blk_parent.reclassify_component_type(blk, Disjunct)
blk.indicator_var = Var(
domain=Binary, doc="Indicates if the unit exists.")
def _deactivate_without_fixing_indicator(self):
self.deactivate()
blk._deactivate_without_fixing_indicator = \
_deactivate_without_fixing_indicator.__get__(blk)
def _activate_without_unfixing_indicator(self):
self.activate()
blk._activate_without_unfixing_indicator = \
_activate_without_unfixing_indicator.__get__(blk)
# TODO overwrite activate/deactivate functions to do what a
# Disjunct does
# Create "does not exist" Disjunct
blk_not_exist = Disjunct()
setattr(blk_parent, "%s_not_exist" % blk.local_name, blk_not_exist)
disj_not_exist = getattr(blk_parent,
"%s_not_exist" % blk.local_name)
# Populate not-exist Disjunct
# Find all Ports in object
# This treats indexed Ports as seperate objects
# Need to test if Port has been already handled
disj_not_exist.ports = ComponentSet()
for connect in blk.component_data_objects(ctype=Port):
# Get name of port
c_name = connect.local_name.split("[")[0]
# Determine type of Connecotr, and name of associated holdup
if fnmatch.fnmatch(c_name, "*inlet"):
# Port is an inlet
con_type = 'in'
if c_name == 'inlet':
h_str = "holdup"
else:
h_str = c_name.split("_")[0]
elif fnmatch.fnmatch(c_name, "*outlet"):
# Port is an outlet
con_type = 'out'
if c_name == 'outlet':
h_str = "holdup"
else:
h_str = c_name.split("_")[0]
elif c_name in ("feed", "product"):
# Port is a feed or product object
con_type = c_name
h_str = "holdup"
else:
# Not a standard port - move on
continue
# Check if port has already been handled
c_obj = getattr(blk, c_name)
if c_obj in disj_not_exist.ports:
continue
else:
# Add to list of handled ports
disj_not_exist.ports.add(c_obj)
# Get associated holdup block object
hblock = getattr(blk, h_str)
# Get associated property block object
try:
# Assume mutiple inlets/outlets - get mixer/splitter
if con_type in ("in", "product"):
mixsplit = getattr(hblock, "inlet_mixer")
else:
mixsplit = getattr(hblock, "outlet_splitter")
# Feed/Product use holdup props, else mixsplit props
if con_type in ("feed", "product"):
pblock = hblock.properties
else:
pblock = mixsplit.properties
except AttributeError:
# Single inlet/outlet, get block from holdup
mixsplit = None
try:
# Guess Holdup0D and try in/out props as relevant
pblock = getattr(hblock, "properties_"+con_type)
except AttributeError:
# Otherwise only one prop block (maybe indexed)
pblock = getattr(hblock, "properties")
# If Port is a feed or product object, only create
# Constraints if there are multiple inlets/outlets.
# Single inlet/outlet cases will be handled by the inlet/outlet
# Port
if (con_type in ('feed', 'product')) and (mixsplit is None):
# If no mixer/splitter, unit has single inlet/outlet
continue
# Add flow constraints to not_exist disjunct
if (con_type in ("feed", "product")) or (mixsplit is None):
# No mixer/splitter - indexed only by time
if not hblock.config.material_balance_type == 'none':
# Term to substitute material balance term
def material_balance_term(t, p, j):
try:
# Assume Holdup1D
if hblock.config.flow_direction is "forward":
return (pblock[t, hblock.ldomain.first()]
.material_balance_term[p, j])
else:
return (pblock[t, hblock.ldomain.first()]
.material_balance_term[p, j])
except AttributeError:
return pblock[t].material_balance_term[p, j]
# Material flow constraint
def material_flow_rule(b, t, p, j):
return 0 == material_balance_term(t, p, j)
c = Constraint(hblock.time,
hblock.phase_list,
hblock.component_list,
rule=material_flow_rule)
setattr(disj_not_exist, c_name+"_material", c)
if not hblock.config.energy_balance_type == 'none':
# Term to substitute energy balance term
def energy_balance_term(t, p):
try:
# Assume Holdup1D
if hblock.config.flow_direction is "forward":
return (pblock[t, hblock.ldomain.first()]
.energy_balance_term[p])
else:
return (pblock[t, hblock.ldomain.first()]
.energy_balance_term[p])
except AttributeError:
return pblock[t].energy_balance_term[p]
# Energy flow constraint
def energy_flow_rule(b, t, p):
return 0 == energy_balance_term(t, p)
c = Constraint(hblock.time,
hblock.phase_list,
rule=energy_flow_rule)
setattr(disj_not_exist, c_name+"_energy", c)
else:
# Mixer/splitter present - indexed by time and inlet/outlet
# Get indexing set for inlets/outlets
if con_type == "in":
mixsplit_idx = mixsplit.inlet_idx
else:
mixsplit_idx = mixsplit.outlet_idx
if not hblock.config.material_balance_type == 'none':
# Term to substitute material balance term
def material_balance_term(t, i, p, j):
return pblock[t, i].material_balance_term[p, j]
# Material flow constraint
def material_flow_rule(b, t, i, p, j):
return 0 == material_balance_term(t, i, p, j)
c = Constraint(hblock.time,
mixsplit_idx,
hblock.phase_list,
hblock.component_list,
rule=material_flow_rule)
setattr(disj_not_exist, c_name+"_material", c)
if not hblock.config.energy_balance_type == 'none':
# Term to substitute energy balance term
def energy_balance_term(t, i, p):
return pblock[t, i].energy_balance_term[p]
# Energy flow constraint
def energy_flow_rule(b, t, i, p):
return 0 == energy_balance_term(t, i, p)
c = Constraint(hblock.time,
mixsplit_idx,
hblock.phase_list,
rule=energy_flow_rule)
setattr(disj_not_exist, c_name+"_energy", c)
# Create disjunction
setattr(blk_parent, "%s_existence_disjunction" % blk.local_name,
Disjunction(expr=[blk, blk_not_exist]))