Source code for idaes.core.plugins.pyosyn

##############################################################################
# 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]))