idaes-pse
idaes-pse copied to clipboard
IDAES v2 initialization improvements
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_orderattribute. 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
initializemethod 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 currentinitializemethod, and would be expected to initialize all components constructed by thebuildmethod 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:
- 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. - Do we expect all components to declare the three component methods (maybe with
NotImplementedErroras a default)? What do we do if one is not present? My initial thought is to haveNotImplementedErroras the default and wrap each call in atry/except(i.e .assume that if it isn't there the developer didn't need it, probably logging a debug message at the time).