Skip to content

Commit

Permalink
Merge pull request watertap-org#1 from bknueven/flash_calcs
Browse files Browse the repository at this point in the history
Merge main
  • Loading branch information
veccp authored Dec 1, 2023
2 parents d0fb36b + 4c17ab1 commit 596ada7
Show file tree
Hide file tree
Showing 100 changed files with 7,292 additions and 2,216 deletions.
2 changes: 1 addition & 1 deletion .binder/apt.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
libgfortran4
libgfortran5
libgomp1
liblapack3
libblas3
2 changes: 1 addition & 1 deletion .github/workflows/mpi4py-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ jobs:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
conda install --quiet --yes -c conda-forge mpi4py
conda install --quiet --yes mpi4py
python -m pip install --upgrade pip
pip install -r requirements-dev.txt
idaes get-extensions --verbose
Expand Down
34 changes: 17 additions & 17 deletions docs/how_to_guides/how_to_use_loopTool_to_explore_flowsheets.rst
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ The general structure starts with the analysis name, which will be used in the f
sweep_param_configs
sweep_param_name_b:
sweep_param_configs
sweep_parm_etc:
sweep_param_etc:
etc
**Examples for RO with ERD**
Expand All @@ -232,26 +232,26 @@ Here we setup a simple run on RO erd flowsheet, requesting loopTool to run PS to
- pressure_exchanger
- pump_as_turbine
sweep_param_loop
membrane_cost: # Runs over differnt membrnae costs
membrane_cost: # Runs over different membrane costs, generating 3 steps
type: LinearSample
param: fs.costing.reverse_osmosis.membrane_cost
lower_limit: 10
upper_limit: 30
num_samples: 3
factor_membrane_replacement: # Runs over membrane_replacment costs, generating 10 steps
factor_membrane_replacement: # Runs over membrane_replacement costs, generating 3 steps
type: LinearSample
param: fs.costing.reverse_osmosis.factor_membrane_replacement
lower_limit: 0.1
upper_limit: 0.2
num_samples: 3
map_sweep: # Will run meshgrid sweep over feed_mass_nacl and ro_recovery, generating 100 samples
feed_mass_nacl: # Runs over salt mass flow rate only, generating 10 steps
map_sweep: # Will run meshgrid sweep over feed_mass_nacl and ro_recovery, generating 9 samples
feed_mass_nacl: # Runs over salt mass flow rate only, generating 3 steps
type: LinearSample
param: fs.feed.properties[0].flow_mass_phase_comp[Liq,NaCl]
lower_limit: 0.03
upper_limit: 0.04
num_samples: 3
ro_recovery: # Runs over ro recovery, generating 10 steps
ro_recovery: # Runs over ro recovery, generating 3 steps
type: LinearSample
param: fs.RO.recovery_mass_phase_comp[0,Liq,H2O]
lower_limit: 0.3
Expand Down Expand Up @@ -298,7 +298,7 @@ Example when we don't do any build loops, init loops etc., this will simply run
Example of setting up differential sweep.

*Briefly, this type of analysis can provide insight into how reducing membrane cost and increasing membrane lifespan( reducing replacement rate), can reduce RO costs, even when the exact membrane cost is not known. This sweep will produce reference cost-optimal RO designs with different membrane cost, and for each design, we will simulate a differential design where only the membrane cost, or membrane replacement rate is reduced. The difference between the reference designs and differential design in LCOW provides a quantitative measure in how reducing membrane cost or reducing replacement cost would typically impact cost of RO. In practice, we would vary many design and decision variables for RO as they are uncertain, and this could include replacement factors, membrane performance metrics, pump costs, etc. All of these could be added to sweep_reference_params.*
*Briefly, this type of analysis can provide insight into how reducing membrane cost and increasing membrane lifespan (reducing replacement rate), can reduce RO costs, even when the exact membrane cost is not known. This sweep will produce reference cost-optimal RO designs with different membrane cost, and for each design, we will simulate a differential design where only the membrane cost, or membrane replacement rate is reduced. The difference between the reference designs and differential design in LCOW provides a quantitative measure in how reducing membrane cost or reducing replacement cost would typically impact cost of RO. In practice, we would vary many design and decision variables for RO as they are uncertain, and this could include replacement factors, membrane performance metrics, pump costs, etc. All of these could be added to sweep_reference_params.*

*Additionally, diff spec params in diff_param_loop can be provided in param_groups like with the standard parameter sweep shown above, allowing changing multiple parameters at the same time.*

Expand Down Expand Up @@ -335,7 +335,7 @@ Example of setting up differential sweep.
param: fs.costing.reverse_osmosis.membrane_cost
lower_limit: 10
upper_limit: 30
num_samples: 10
num_samples: 10
Setting up the loopTool
-------------------------------------
Expand All @@ -354,7 +354,7 @@ To loopTool can be executed by passing in the flowsheet functions, .yaml configu
* number_of_subprocesses (optional): user defined number of subprocesses to use for parallel run should be 1 or greater
* parallel_back_end (optional): backend to use for a parallel run if MPI is not used and number_of_subprocesses > 1
* custom_do_param_sweep (optional): custom param function (refer to parameter sweep tool)
* custom_do_param_sweep_kwargs (optional): custom parm kwargs (refer to parameter sweep tool)
* custom_do_param_sweep_kwargs (optional): custom param kwargs (refer to parameter sweep tool)
* execute_simulations (optional): sets if looptool should execute simulations upon setup, otherwise user can call build_run_dict, and run_simulations call manually
* h5_backup (optional): Set location for backup file, if set to False, no backup will be created, otherwise the backup will be auto-created

Expand Down Expand Up @@ -430,7 +430,7 @@ The loopTool will store data in h5 file, with a structure similar to that of the
We can readily access the data for processing by using h5py.
To visually explore the h5 file, use HDFviewer ( https://www.hdfgroup.org/downloads/hdfview/ )
All the parameters will use their parm names or object keys as reference (e.g. if you set up yaml file to sweep over 'RO_recovery' for which param is 'fs.ro.ro_recovery', the file will store data for RO_recovery under key 'fs.ro.ro_recovery'. Alternatively, if you provided build_outputs_kwargs, and specified a name for a key (e.g., *RO recovery: fs.ro.ro_recovery* than this result will be saved under *RO recovery*.
All the parameters will use their param names or object keys as reference (e.g. if you set up yaml file to sweep over 'RO_recovery' for which param is 'fs.ro.ro_recovery', the file will store data for RO_recovery under key 'fs.ro.ro_recovery'. Alternatively, if you provided build_outputs_kwargs, and specified a name for a key (e.g., *RO recovery: fs.ro.ro_recovery* than this result will be saved under *RO recovery*.

.. code-block::
Expand Down Expand Up @@ -476,22 +476,22 @@ Example of use as follows:
min_num_samples: int (if set, loopTool will only re-run if there is fewer samples in the backup file than the set value)
expected_num_samples: int (if set, loopTool will only re-run if the expected_num_samples differs from the number of samples saved in the backup file)
force_rerun: False or True (if set to True, will force to always rerun given parameter sweep, if False, will not re-run the parameter sweep)
map_sweep: # Will run meshgrid sweep over feed_mass_nacl and ro_recovery, generating 100 samples
feed_mass_nacl: # Runs over salt mass flow rate only, generating 10 steps
map_sweep: # Will run meshgrid sweep over feed_mass_nacl and ro_recovery, generating 9 samples
feed_mass_nacl: # Runs over salt mass flow rate only, generating 3 steps
type: LinearSample
param: fs.feed.properties[0].flow_mass_phase_comp[Liq,NaCl]
lower_limit: 0.03
upper_limit: 0.04
num_samples: 3
ro_recovery: # Runs over ro recovery, generating 10 steps
ro_recovery: # Runs over ro recovery, generating 3 steps
type: LinearSample
param: fs.RO.recovery_mass_phase_comp[0,Liq,H2O]
lower_limit: 0.3
upper_limit: 0.5
num_samples: 3
min_num_samples: int (if set, loopTool will only re-run if there is less samples in the backup file than the set value)
expected_num_samples: int (if set, loopTool will only re-run if the expected_num_samples differs from the number of samples saved in the backup file)
force_rerun: False or True (if set to True, will force to always rerun given parameter sweep, if False, will not re-run the parameter sweep)
num_samples: 3
min_num_samples: int (if set, loopTool will only re-run if there is less samples in the backup file than the set value)
expected_num_samples: int (if set, loopTool will only re-run if the expected_num_samples differs from the number of samples saved in the backup file)
force_rerun: False or True (if set to True, will force to always rerun given parameter sweep, if False, will not re-run the parameter sweep)
This feature is designed to help with getting complete simulation sets, without needing to re-run all the looped options. For example, you might run a certain build loop, where only in one build option, some solutions failed. After fixing the reason, you can rerun the loopTool, and it will only rerun those build options that failed to solve.

Expand Down
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`.
1 change: 1 addition & 0 deletions docs/technical_reference/costing/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ Costing Components
costing_base
watertap_costing
zero_order_costing
multiple_choice_costing_block
util
Loading

0 comments on commit 596ada7

Please sign in to comment.