How-to guide for the UI API
Contents
Overview
There are two distinct intended users for this API:

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.

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

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.

Creating an interface to a flowsheet
There are three steps for creating a user interface to a flowsheet:
Define a function called flowsheet_interface() that creates the
FlowsheetInterface
object.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
Set the action functions into the flowsheet interface before returning it to the user.

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.

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)

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

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}")

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()}")

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)

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.

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.