idaes-pse icon indicating copy to clipboard operation
idaes-pse copied to clipboard

IDAES v2 initialization improvements

Open andrewlee94 opened this issue 3 years ago • 0 comments

Builds off #724 and costing improvements

During discussion of the new costing implementation and its effects on initialization, it was suggested that we could generalize costing across all IDAES components and provide far greater flexibility to developers and users (including the ability to inject custom sequences into initialization).

Based on the discussion and some subsequent thought, my idea of how this would look is (names are all TBD):

Process Base:

  • Add a new _initialization_order attribute. This would be a list of objects related to the current ProcessBlock (primarily IDAES/Pyomo Blocks) indicating the order in which they should be initialized.
  • A general initialize method which will do the following:
def initialize(blk, **kwargs):
    for c in blk._initialization_order:
        c.initialize_prepare()
    for c in blk._initialization_order:
        if c is blk:
            # If this is the current object, we want to initialize what was built in blk.build
            c.initialize_build()
        else:
            # Otherwise, it is something attached to blk, so we want to call c.initialize
            c.initialize()
    for c in reversed(blk._initialization_order):
        c.initialize_finalize()

Each derived component would then declare the following methods:

  • initialize_prepare - this would be responsible for preparing the component for initialization. E.g. a unit model would fix the inlet states, whilst a costing block would deactivate itself (so as not to interfere with unit model initialization).
  • initialize_build - this would replace most of the current initialize method, and would be expected to initialize all components constructed by the build method of the associated object.
  • initialize_finalize - would do any final clean up to revert the model to its original state r.e. fixed/active components. E.g. a costing block would re-activate itself, and a unit model would could a final overall solve of the unit (including all add-ons).

Each component would also need to add itself to the initialization_order of its parent as part of the build method if required (and we might even be able to just put this in the ProcessBlockData.build method).

This would add a lot of flexibility to how we initialize things and allow for future expansion (new components would just need to declare the expected methods and add themselves to the initialization order in the right place). It would also allow advanced users to inject custom code at any point in initialization by creating an functor with the expected methods and adding it to the initialization order. Finally, as I see it this structure is universal, and the base initialize method and associated code all lives in ProcessBlockData, meaning we would have a way to define initialization for e.g. flowsheets if we wanted to.

Some outstanding questions in my mind:

  1. How do we parse out arguments to the component methods during initialization? I.e. a user will pass in a set of arguments to initialize, but those will needs to be sent to different component methods. Given that we want this to be general, we would need a general way to handle arguments.
  2. Do we expect all components to declare the three component methods (maybe with NotImplementedError as a default)? What do we do if one is not present? My initial thought is to have NotImplementedError as the default and wrap each call in a try/except (i.e .assume that if it isn't there the developer didn't need it, probably logging a debug message at the time).

andrewlee94 avatar Mar 01 '22 20:03 andrewlee94