How-to guide for the UI API

Overview

There are two distinct intended users for this API:

../_images/terminal-icon.png

Model developers can select which variables to “export” to the UI for each component of the model, and provide extra metadata (display name, description) for them. For flowsheets, they also should specify how to build and solve the flowsheets.

../_images/menu-icon.png

User interface developers can “discover” flowsheets for inclusion in the UI, and use the API to provide JSON versions of their contents (and results).

The rest of this page will provide more detail on how to do tasks for each type of user.

See also: the UI API reference section.

Model Developers

../_images/terminal-icon.png

Exporting variables with export_variables()

For each IDAES component and/or Pyomo block the developer should list the names of the variables that should be “exported” to the user interface. Optionally, users may provide additional information as well (see export_variables() for details). This should be done in the component’s build method. Each component/block need only worry about its own variables. The API will take care of gathering all the components for a flowsheet together. For example, the last line of the zero-order feed’s build method is:

export_variables(self, name="Feed Z0", desc="Zero-Order feed block",
                     variables=["flow_vol", "conc_mass_comp"])

At the time of that call, self is the feed block that was just built. It is saying to export two variables called “flow_vol” and “conc_mass_comp”.

There are other things about variables that can be specified. For details see export_variables().

For example, if you wanted to mark “flow_vol” as read-only, then you could do:

export_variables(self, name="Feed Z0", desc="Zero-Order feed block",
                     variables={"flow_vol": {"readonly": True},
                                "conc_mass_comp": {}})

Note: Marking a variable as read-only does change the IDAES variable.

../_images/terminal-icon.png

Creating an interface to a flowsheet

There are three steps for creating a user interface to a flowsheet:

  1. Define a function called flowsheet_interface() that creates the FlowsheetInterface object.

  2. Define flowsheet actions. See Create an “action” function. All interfaces should define the following actions:

    build

    Build a flowsheet. At the end this will set the built flowsheet into the provided ‘ui’ parameter with ui.set_block(<flowsheet-that-was-built>).

    solve

    Solve a flowsheet

  3. Set the action functions into the flowsheet interface before returning it to the user.

../_images/terminal-icon.png

Define the flowsheet_interface function

The flowsheet interface is created by a function with this signature:

watertap.ui.api.flowsheet_interface() FlowsheetInterface

In other words, it takes no arguments and returns a FlowsheetInterface object. This object is not yet connected to an IDAES flowsheet block. The function should return a flowsheet interface, see Creating an interface to a flowsheet.

Note that you only need to add variables that are not already exported by the model, and that there are reasonable defaults for things like the name, display_name (same as name), and description. So in most cases this will be a very simple call; the extended version was shown for didactic purposes.

../_images/terminal-icon.png

Create an “action” function

The action functions should operate on optional keywords for the flowsheet block and the FlowsheetInterface instance. It will call the WaterTAP code to perform the appropriate actions. The function should have the following signature:

watertap.ui.api.action_function([block=None, ui=None, ]**kwargs)

Perform an action.

Parameters
  • block (Block) – Flowsheet block

  • ui (FlowsheetInterface) – FlowsheetInterface instance

  • kwargs (dict) – Additional key/value pairs specific to this action

The action function will be passed to FlowsheetInterface.set_action() in the flowsheet_interface() function as shown here.

For example:

# import module for the full flowsheet
from watertap.examples.flowsheets.case_studies.foo.bar import my_flowsheet

def build_flowsheet(ui=None, **kwargs):
    # Invoke the method (here, 'build') that builds the flowsheet
    # and call other methods needed to initialize it.
    model = my_flowsheet.build()
    my_flowsheet.set_operating_conditions(model)
    my_flowsheet.assert_degrees_of_freedom(model, 0)
    my_flowsheet.assert_units_consistent(model)
    my_flowsheet.add_costing(model)
    model.fs.costing.initialize()

    # Export some additional [costing] variables
    export_variables(
        model.fs.costing,
        name="My costing",
        desc="Costing variables for my flowsheet",
        category="costing",
        variables=[
            "utilization_factor",
            "TIC",
            "maintenance_costs_percent_FCI",
        ],
    )

    # Finish setting up the flowsheet
    my_flowsheet.adjust_default_parameters(model)
    my_flowsheet.assert_degrees_of_freedom(model, 0)

    # ** IMPORTANT **
    # Set built flowsheet as the top-level block for the interface
    ui.set_block(model.fs)
../_images/terminal-icon.png

Set actions for a flowsheet

Once you have created a flowsheet action, you can set it in the flowsheet by calling FlowsheetInterface.set_action(). For example:

from watertap.ui.api import FlowsheetInterface, WorkflowActions
def flowsheet_interface():
    fsi = FlowsheetInterface({"display_name": "My treatment train",
                              "description": "Treatment train to show off my model"})
    fsi.set_action(WorkflowActions.build, build_flowsheet)
    fsi.set_action(WorkflowActions.solve, solve_flowsheet)
    return fsi

User Interface Developers

../_images/menu-icon.png

Find flowsheets

Use the function find_flowsheet_interfaces() to get a list of FlowsheetInterface objects representing the available interfaces. By default this function will find all interfaces in the watertap Python package in which it is situated:

from watertap.ui.api import find_flowsheet_interfaces
for fsi in find_flowsheet_interfaces():
    print(f"Got flowsheet: {fsi.display_name}")

You can add a configuration file or dict to specify alternate or additional places to look. Just remember when doing so to provide the default watertap location if you want it included. For example:

from watertap.ui.api import find_flowsheet_interfaces
interface_list = find_flowsheet_interfaces(config={
    "packages": ["watertap", "my_other_package"]})
for fsi in interface_list:
    print(f"Got flowsheet: {fsi.display_name}")
../_images/menu-icon.png

Fetch and update flowsheet values

The values for all variables exported by the flowsheet are available via the dict() method. The format of the returned value is documented in the api module header.

Note: if you want to write these values as JSON to a stream, use FlowsheetInterface.save(). For example:

from watertap.ui.api import find_flowsheet_interfaces
for fsi in find_flowsheet_interfaces():
    print(f"Got flowsheet: {fsi.display_name}")
    print(f"Flowsheet contents: {fsi.dict()}")
../_images/menu-icon.png

Run flowsheet actions

Once flowsheet actions are created, you invoke them with the FlowsheetInterface.run_action() method. The name of the action, if it is a standard one, will be an attribute in the WorkflowActions class. For example:

from watertap.ui.api import FlowsheetInterface

def run_build(fsi: FlowsheetInterface):
    fsi.run_action(WorkflowActions.build)
../_images/menu-icon.png

Save flowsheet values in a file

The values of variables that were exported for a given flowsheet can be saved to a file. For example:

from watertap.ui.api import FlowsheetInterface
fsi = FlowsheetInterface()
fsi.set_block(my_flowsheet)
fsi.save("my_flowsheet_saved.json")

Note: The method is simply a wrapper that calls dict() and feeds the result to a JSON serializer.

../_images/menu-icon.png

Load flowsheet values from a file

The values of variables that were saved for a given flowsheet can be loaded back into the model. This operation changes the matching values in the actual underlying model to the values that are stored in the file. In other words, unlike the FlowsheetInterface.save() method, this method changes what is stored in memory. Invoking load is straightforward:

from watertap.ui.api import FlowsheetInterface
# Create and save
fsi = FlowsheetInterface()
fsi.set_block(my_flowsheet)
fsi.save("my_flowsheet_saved.json")
# Load back
fsi.load("my_flowsheet_saved.json")

To handle situations where the model is changed between the save and subsequent load, so that the loaded and current model variables do not all match, there are two methods you can use after you are done loading:

FlowsheetInterface.get_var_missing()

Returns variables in the loaded data, but not in the current model.

FlowsheetInterface.get_var_extra()

Returns variables in (and exported by) the current model, but not in the loaded data.

Both methods return a mapping with the full name of the block (e.g., flowsheet.component.subcomponent) as the key and a list of variable names as the value.