Skip to content

Commit

Permalink
Add build options to UI api; add example to nf_ui.py (watertap-org#1152)
Browse files Browse the repository at this point in the history
* add build options to fsapi; update ui exports to include build options; add option for nf flowsheet (with bypass option); remove nf with bypass flowsheet from setup.py

* add build options to fsapi; update ui exports to include build options; add option for nf flowsheet (with bypass option); remove nf with bypass flowsheet from setup.py

* remove changes made to nf.py and nf_with_bypass.py

* revert changes to nf.py and nf_with_bypass.py

* revert changes to nf.py

* add validator for model option values

* add build_options as an argument for build function - eliminates need for multiple build functions

* update doc for how to use ui api

* remove some commented out stuff

* add build_options parameter to ui export build functions

* added second build option for polarization type

* updated formatting for black check

* update fsapi tests

* add build options parameter to bsm2_ui; add init file to bsm2_ui directory

* un-comment out nf_with_bypass entry point

* remove unused function

* updated flowsheet_interface test to increase code cov

* update formatting for black check

* remove parentheses around build options in test

* replace .value with ['value']

* add **kwargs to build and export_variables functions

* add build options parameter, **kwargs to new ro flowshee UI exportt

* remove nf_with_bypass from setup

* add nf_with_bypass back

* [value] -> .value

* add add_objective to build function

* update test to use ModelOption object

* fix formatting for black check

* only update values, bounds, fixed if it has changed; use the set_functions for these actions

* update formatting for black check

* change if statement to check for attribute, bounds

* remove unused imports
  • Loading branch information
MichaelPesce authored Nov 22, 2023
1 parent 1d22d6f commit b00f683
Show file tree
Hide file tree
Showing 23 changed files with 451 additions and 172 deletions.
231 changes: 155 additions & 76 deletions docs/how_to_guides/how_to_use_ui_api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,14 @@ How-to guide for the UI API
Overview
--------

There are two distinct intended users for this API:

.. image:: /_static/terminal-icon.png
:height: 30px
:align: left

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.

.. image:: /_static/menu-icon.png
:height: 22px
:align: left

User interface developers can "discover" flowsheets for inclusion in the UI, and use the API to serialize and update from serialized data.

The rest of this page will provide more detail on how to do tasks for each type of user.
This API is intended for model developers who would like to connect their flowsheets to the UI.
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 should also
specify how to build and solve the flowsheets.

See also: :ref:`the UI flowsheet API reference section <ref_ui-fsapi>`.

Expand All @@ -44,67 +36,154 @@ Add an interface to your flowsheet
In some Python module, define the function ``export_to_ui``, which will look
similar to this::

def export_to_ui():
return FlowsheetInterface(
do_build=build_flowsheet,
do_export=export_variables,
do_solve=solve_flowsheet,
name="My Flowsheet")

See :class:`FlowsheetInterface` for details on the arguments.

User Interface Developers
--------------------------

.. image:: /_static/menu-icon.png
:height: 22px
:align: left

.. _howto_api-find:

Find flowsheets
^^^^^^^^^^^^^^^^
Use the method :meth:`FlowsheetInterface.find` to get a mapping of module names to functions
that, when called, will create the flowsheet interface::

results = fsapi.FlowsheetInterface.find("watertap")

Note that the returned interface will not create the flowsheet object and export the variables until the ``build`` method is invoked::

first_module = list(results.keys())[0]
interface = results[first_module]
# at this point the name and description of the flowsheet are available
interface.build()
# at this point the flowsheet is built and all variables exported


.. image:: /_static/menu-icon.png
:height: 22px
:align: left

.. _howto_api-serialize:

Serialize flowsheet
^^^^^^^^^^^^^^^^^^^^
Use the ``dict()`` method to serialize the flowsheet::

data = flowsheet.dict()

.. image:: /_static/menu-icon.png
:height: 22px
:align: left

.. _howto_api-load:

Load a serialized flowsheet
^^^^^^^^^^^^^^^^^^^^^^^^^^^
Use the ``load()`` method to load values from a serialized flowsheet.
This will raise a ``MissingObjectError`` if any of the incoming values are not found in the target flowsheet::

try:
flowsheet.load(data)
except fsapi.MissingObjectError as err:
print(f"Error loading data: {err}")
# print contents of list of missing items (key and variable name)
for item in err.missing:
print(f"Missing item: key={item.key}, name={item.name}")
from watertap.ui.fsapi import FlowsheetInterface, FlowsheetCategory
def export_to_ui():
return FlowsheetInterface(
name="NF-DSPM-DE",
do_export=export_variables,
do_build=build_flowsheet,
do_solve=solve_flowsheet,
get_diagram=get_diagram,
requires_idaes_solver=True,
category=FlowsheetCategory.wastewater,
build_options={
"Bypass": {
"name": "bypass option",
"display_name": "With Bypass",
"values_allowed": ['false', 'true'],
"value": "false"
}
}
)

There are 3 required functions:

1. ``do_export`` - This function defines the variables that will be displayed on the UI. See example below::

def export_variables(flowsheet=None, exports=None, build_options=None, **kwargs):
fs = flowsheet
exports.add(
obj=fs.feed.properties[0].flow_vol_phase["Liq"],
name="Volumetric flow rate",
ui_units=pyunits.L / pyunits.hr,
display_units="L/h",
rounding=2,
description="Inlet volumetric flow rate",
is_input=True,
input_category="Feed",
is_output=False,
output_category="Feed",
)
exports.add(
obj=fs.NF.pump.outlet.pressure[0],
name="NF pump pressure",
ui_units=pyunits.bar,
display_units="bar",
rounding=2,
description="NF pump pressure",
is_input=True,
input_category="NF design",
is_output=True,
output_category="NF design",
)
exports.add(
obj=fs.NF.product.properties[0].flow_vol_phase["Liq"],
name="NF product volume flow",
ui_units=pyunits.L / pyunits.hr,
display_units="L/h",
rounding=2,
description="NF design",
is_input=False,
input_category="NF design",
is_output=True,
output_category="NF design",
)

2. ``do_build`` - This function defines the build function for a flowsheet. See example below::

from watertap.examples.flowsheets.case_studies.wastewater_resource_recovery.metab.metab import (
build,
set_operating_conditions,
initialize_system,
solve,
add_costing,
adjust_default_parameters,
)
def build_flowsheet():
# build and solve initial flowsheet
m = build()

set_operating_conditions(m)
assert_degrees_of_freedom(m, 0)
assert_units_consistent(m)

initialize_system(m)

results = solve(m)
assert_optimal_termination(results)

add_costing(m)
assert_degrees_of_freedom(m, 0)
m.fs.costing.initialize()

adjust_default_parameters(m)

results = solve(m)
assert_optimal_termination(results)
return m


3. ``do_solve`` - This function defines the solve function for a flowsheet. See example below::

from watertap.examples.flowsheets.case_studies.wastewater_resource_recovery.metab.metab import solve
def solve_flowsheet(flowsheet=None):
fs = flowsheet
results = solve(fs)
return results

Additionally, there are optional parameters to assign a category, provide build options,
and provide a diagram function among others. See additional examples below.

Build function using build options::

def build_flowsheet(build_options=None, **kwargs):
# build and solve initial flowsheet
if build_options is not None:
if build_options["Bypass"].value == "true": #build with bypass
solver = get_solver()
m = nf_with_bypass.build()
nf_with_bypass.initialize(m, solver)
nf_with_bypass.unfix_opt_vars(m)
else: # build without bypass
solver = get_solver()
m = nf.build()
nf.initialize(m, solver)
nf.add_objective(m)
nf.unfix_opt_vars(m)
else: # build without bypass
solver = get_solver()
m = nf.build()
nf.initialize(m, solver)
nf.add_objective(m)
nf.unfix_opt_vars(m)
return m

Custom diagram function::

def get_diagram(build_options):
if build_options["Bypass"].value == "true":
return "nf_with_bypass_ui.png"
else:
return "nf_ui.png"

Enable UI to discover flowsheet - In order for the UI to discover a flowsheet, an
entrypoint must be defined in setup.py with the path to the export file. For examples, see below::

entry_points={
"watertap.flowsheets": [
"nf = watertap.examples.flowsheets.nf_dspmde.nf_ui",
"metab = watertap.examples.flowsheets.case_studies.wastewater_resource_recovery.metab.metab_ui",
]


For a complete overview of all arguments, see :class:`FlowsheetInterface`.
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def export_to_ui():
)


def export_variables(flowsheet=None, exports=None):
def export_variables(flowsheet=None, exports=None, build_options=None, **kwargs):
fs = flowsheet
# --- Input data ---
# Feed conditions
Expand Down Expand Up @@ -373,7 +373,7 @@ def export_variables(flowsheet=None, exports=None):
)


def build_flowsheet(erd_type=ERDtype.pump_as_turbine):
def build_flowsheet(erd_type=ERDtype.pump_as_turbine, build_options=None, **kwargs):
# build and solve initial flowsheet
m = build()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def export_to_ui():
)


def export_variables(flowsheet=None, exports=None):
def export_variables(flowsheet=None, exports=None, build_options=None, **kwargs):
"""
Exports the variables to the GUI.
"""
Expand Down Expand Up @@ -2844,7 +2844,7 @@ def export_variables(flowsheet=None, exports=None):
)


def build_flowsheet():
def build_flowsheet(build_options=None, **kwargs):
"""
Builds the initial flowsheet.
"""
Expand Down
Empty file.
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ def export_to_ui():
)


def export_variables(flowsheet=None, exports=None):
def export_variables(flowsheet=None, exports=None, build_options=None, **kwargs):
fs = flowsheet
# --- Input data ---
# Feed conditions
Expand Down Expand Up @@ -424,7 +424,7 @@ def export_variables(flowsheet=None, exports=None):
)


def build_flowsheet():
def build_flowsheet(build_options=None, **kwargs):
# build and solve initial flowsheet
m = build()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ def export_to_ui():
)


def export_variables(flowsheet=None, exports=None):
def export_variables(flowsheet=None, exports=None, build_options=None, **kwargs):
fs = flowsheet

def _base_curr(x):
Expand Down Expand Up @@ -760,7 +760,7 @@ def _base_curr(x):
)


def build_flowsheet():
def build_flowsheet(build_options=None, **kwargs):
# build and solve initial flowsheet
m = build()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ def export_to_ui():
)


def export_variables(flowsheet=None, exports=None):
def export_variables(flowsheet=None, exports=None, build_options=None, **kwargs):
fs = flowsheet
# --- Input data ---
# Feed conditions
Expand Down Expand Up @@ -694,7 +694,7 @@ def export_variables(flowsheet=None, exports=None):
)


def build_flowsheet():
def build_flowsheet(build_options=None, **kwargs):
# build and solve initial flowsheet
m = build()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ def export_to_ui():
)


def export_variables(flowsheet=None, exports=None):
def export_variables(flowsheet=None, exports=None, build_options=None, **kwargs):
fs = flowsheet
# --- Input data ---
# Feed conditions
Expand Down Expand Up @@ -807,7 +807,7 @@ def export_variables(flowsheet=None, exports=None):
)


def build_flowsheet():
def build_flowsheet(build_options=None, **kwargs):
# build and solve initial flowsheet
m = build()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ def export_to_ui():
)


def export_variables(flowsheet=None, exports=None):
def export_variables(flowsheet=None, exports=None, build_options=None, **kwargs):
fs = flowsheet
# --- Input data ---
# Feed conditions
Expand Down Expand Up @@ -906,7 +906,7 @@ def export_variables(flowsheet=None, exports=None):
)


def build_flowsheet():
def build_flowsheet(build_options=None, **kwargs):
# build and solve initial flowsheet
m = build()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ def export_to_ui():
)


def export_variables(flowsheet=None, exports=None):
def export_variables(flowsheet=None, exports=None, build_options=None, **kwargs):
fs = flowsheet
# --- Input data ---
# Feed conditions
Expand Down Expand Up @@ -657,7 +657,7 @@ def export_variables(flowsheet=None, exports=None):
)


def build_flowsheet():
def build_flowsheet(build_options=None, **kwargs):
# build and solve initial flowsheet
m = build()

Expand Down
Loading

0 comments on commit b00f683

Please sign in to comment.