Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(flopy plugins) #1746

Closed
wants to merge 40 commits into from
Closed

feat(flopy plugins) #1746

wants to merge 40 commits into from

Conversation

spaulins-usgs
Copy link
Contributor

FloPy plugins are FloPy-integrated components that modify MODFLOW-6 functionality using the modflowapi library. The FloPy plugins architecture streamlines the process of modifying MODFLOW-6 functionality by:

  • Providing template generators to help rapidly set up your plugin’s interface
  • Automatically loading, saving, and running FloPy plugins as part of a MODFLOW-6 simulation
  • Providing an interface that allows multiple FloPy plugins to run together
  • Providing support for running FloPy plugins installed as other Python packages

@codecov
Copy link

codecov bot commented Mar 15, 2023

Codecov Report

Merging #1746 (ca10958) into develop (656751a) will decrease coverage by 0.4%.
The diff coverage is 49.5%.

@@            Coverage Diff            @@
##           develop   #1746     +/-   ##
=========================================
- Coverage     72.1%   71.7%   -0.4%     
=========================================
  Files          253     262      +9     
  Lines        56086   57252   +1166     
=========================================
+ Hits         40452   41097    +645     
- Misses       15634   16155    +521     
Impacted Files Coverage Δ
flopy/mf6/utils/createpackages.py 7.1% <6.8%> (+<0.1%) ⬆️
flopy/mf6/utils/flopy_plugins/plugin_template.py 7.8% <7.8%> (ø)
flopy/mf6/data/mfstructure.py 63.8% <52.5%> (-0.1%) ⬇️
flopy/mf6/utils/flopy_plugins/plugin_interface.py 61.3% <61.3%> (ø)
flopy/mf6/data/mffileaccess.py 64.8% <62.5%> (-0.1%) ⬇️
flopy/mf6/utils/flopy_plugins/plugin_runner.py 77.7% <77.7%> (ø)
...f6/utils/flopy_plugins/plugins/flopy_rvp_plugin.py 88.1% <88.1%> (ø)
...f6/utils/flopy_plugins/plugins/flopy_rvc_plugin.py 98.0% <98.0%> (ø)
flopy/mf6/data/mfdatalist.py 73.3% <100.0%> (+<0.1%) ⬆️
flopy/mf6/mfmodel.py 57.7% <100.0%> (+0.1%) ⬆️
... and 7 more

... and 16 files with indirect coverage changes

pyproject.toml Outdated Show resolved Hide resolved
Copy link
Contributor

@langevin-usgs langevin-usgs left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @spaulins-usgs, lots of really great stuff here. This is some tremendous new functionality. I tried to go through it all and make some comments. I added quite a few, but hopefully they will be helpful. Happy to discuss further.

@@ -200,6 +200,36 @@ Contents:
./source/flopy.mf6.utils.reference.rst
./source/flopy.mf6.utils.lakpak_utils.rst

FloPy for MODFLOW 6 Plugins
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From what I can tell, this section is for a plugin developer. What about a plugin user? Presumably these instructions do not apply, and installing a flopy plugin will automatically make it available.

Also, are these instructions up to date with the recent changes you've made for plugin development?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This section is referring to the plugin_interface.py and plugin_template.py files, which are for the plugin developer, not the user. However, I agree that there should be a section here for the plugin user. I therefore added a section describing how to use FloPy plugins and reference the documentation of the “package interface” files of the RVC and RVP plugins (the two plugins included with FloPy) to help the user get started.

The instructions are up to date with the recent changes I made.

.docs/code.rst Outdated
Comment on lines 208 to 209
change the behavior of an existing MODFLOW 6 package or behave like a new
stand-alone MODFLOW 6 package. There are three ways FloPy can detect plugins.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
change the behavior of an existing MODFLOW 6 package or behave like a new
stand-alone MODFLOW 6 package. There are three ways FloPy can detect plugins.
change the behavior of an existing MODFLOW 6 package or create a new
stand-alone MODFLOW 6 package. There are three ways FloPy can detect plugins.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed

"source": [
"## Working with flopy_npc_plugin.py\n",
"\n",
"The plugin template generator creates and modifies several files. The plugin code file that you will edit in order to add functionality to your plugin is in the in the python working folder (folder containing this notebook) and in this case is named \"flopy_npc_plugin.py\". This file contains a class derived from FPBMIPluginInterface with several methods. \n",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"The plugin template generator creates and modifies several files. The plugin code file that you will edit in order to add functionality to your plugin is in the in the python working folder (folder containing this notebook) and in this case is named \"flopy_npc_plugin.py\". This file contains a class derived from FPBMIPluginInterface with several methods. \n",
"The plugin template generator creates and modifies several files. The plugin code file that you will edit in order to add functionality to your plugin is in the python working folder (folder containing this notebook) and in this case is named \"flopy_npc_plugin.py\". This file contains a class derived from FPBMIPluginInterface with several methods. \n",

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed

"source": [
"## Working with flopy_npc_plugin.py\n",
"\n",
"The plugin template generator creates and modifies several files. The plugin code file that you will edit in order to add functionality to your plugin is in the in the python working folder (folder containing this notebook) and in this case is named \"flopy_npc_plugin.py\". This file contains a class derived from FPBMIPluginInterface with several methods. \n",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the name of this folder really have a .py extension?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is the file name not the folder name. For clarity I reworded the sentence describing this.

"id": "0c0c0f91",
"metadata": {},
"source": [
"Next define any user-specified settings for your plugin. These settings will exist in a \"package\" file similar to the files used to store MODFLOW-6 package user-specified settings. The FloPy plugin template generator can set up an options, package, and stress period block for your \"package\" settings file.\n",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"Next define any user-specified settings for your plugin. These settings will exist in a \"package\" file similar to the files used to store MODFLOW-6 package user-specified settings. The FloPy plugin template generator can set up an options, package, and stress period block for your \"package\" settings file.\n",
"Next define any input that will be provided by the user of the plugin. This input will be provided by the user in a \"package\" input file similar to the MODFLOW 6 files provided as input to a regular MODFLOW 6 simulation. The FloPy plugin template generator can set up an options, package, and stress period block for your \"package\" settings file.\n",

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like there is a lot of new flopy plugin code that was added here. Would it be possible to move this into a plugin folder somewhere as most of it is not needed for the dominant regular flopy use case.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cleaned this up moving ~90% of the new code in MFSimulation into a new “plugin_runner.py” file.

return cell_id[0] + 1

@staticmethod
def sq_saturation(top, bot, x, c1=None, c2=None):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe there is good reason for this, but this does not seem like a good place for replicated low-level GWF NPF calculation routines.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method was included because I originally found it useful for some plugins I was writing. Since nothing in this PR currently uses this method, I just removed it.

return y

@staticmethod
def sq_saturation_derivative(top, bot, x, c1=None, c2=None):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same comment as before

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed, as commented above.

Package data retrieval methods
"""

def mf6_idomain_val(self, cell_id):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems dangerous to have all these hard-wired routines here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure I understand why these properties are dangerous. They are just there to make it easier for the FloPy plugin developer to get common grid geometry information from FloPy. If you do not think these are appropriate here, I can remove them. Maybe I can add a utility method class for these and the low-level GWF NPF calculations?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we including these plugins in flopy now? Or should we make separate repos for them and require users to install them as plugins? I think requiring them to be installed would offer some nice benefits and would also require us to think about the structure of flopy plugin repos.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would like to include these plugins in flopy now. I think it is nice to have some easily accessible plugin examples with FloPy, and the autotest code I added to test FloPy plugins uses these plugins to test this feature. I am all for minimizing the effort for users who want to just quickly try out FloPy plugins and see what it is all about. I agree that we should talk about the structure of flopy plugin repos and if/how we are going to support identifying a list of available (unsupported) plugins.

Copy link
Contributor

@jlarsen-usgs jlarsen-usgs left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not really convinced that the name os this should FloPy Plugins. This is not general use plugin code that allows any user to create python packages that modifies the behavior of FloPy. Instead this is plugin code that allows users to develop python packages and api callback containers for modflow 6 that then can be used as plugins with flopy.

For example, with this implementation I couldn't develop specialized plugin code that could handle reading modflow-owhm or modify pygsflow to be an optional plugin/extension for flopy. Some general plugin hooks to make things like this possible would be really beneficial. I could imagine in the future we could then have something like flopy[core] (which is the core gwf and gwt functionality), and as modflow 6 grows additional supported components (e.g. support for the surface water model being developed) could be installed as plugins to core functionality. This is similar to how "ray" packages their different functionalities.

The actual plugin code is really buried in flopy.mf6.utils.plugins, is there a better location for this and if we develop generalized plugin hooks too, we should think about where it should live?

I've left some comments where I think the code could be simplified or flopy is duplicating work that is already being done by modflowapi.

Overall, the Modflow 6 plugin system you've developed seems really useful and I think it'll be a nice addition to flopy and modflowapi! Let me know if you have any questions about my review comments.

Comment on lines +332 to +335
slnobj = list(mf6_sim.solutions.values())[0]
converged = (
mf6_sim.allow_convergence and mf6_sim.iteration < slnobj.mxiter
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think flopy should be checking convergence status. The modflowapi runner already does this, and this code block would break the support for ATS that's in the modflowapi.

Comment on lines +327 to +329
mf6_sim.allow_convergence = (
mf6_sim.allow_convergence and allow_cvg
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think flopy should have a seperate allow convergence flag. Instead it should directly set the ApiSolution object's allow_convergence attribute.

Comment on lines +12 to +32
class FPPluginInterface:
"""
Base class of flopy plugins for MODFLOW 6. Your flopy
plugins should not directly inherent from this class, inherent from
the FPAPIPluginInterface instead.

Attributes
----------
simulation : MFSimulation
Simulation object that this plugin is a part of. Simulation object
acquired when simulation is run by flopy and the receive_vars
method is called.
model : MFModel
Model object that this plugin is a part of.
package : MFPackage
Package data that the flopy plugin for MODFLOW 6 uses to determine
the plugin's user settings.
mg : Grid
Model grid object for the model this plugin is a part of.

"""
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a good reason for this to be a parent class? It's currently only inherited by FPAPIPluginInterface

Comment on lines +109 to +120
def val_from_cellid(self, cell_id, grid_array):
"""This method retrieves a value from an array in the shape of the
model grid for a particular cell id. Structured, vertex and
unstructured grids are supported.

Parameters
----------
cell_id : tuple
Cell ID corresponding to the location in the array where a
value should be retrieved.
grid_array: numpy ndarray
Array containing value to be retrieved.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

grid_array naming convention could be a little confusing. It implies to me that it's an array of grid data.

Comment on lines +166 to +168
elif self.mg.grid_type == "vertex":
assert len(cell_id) == 2
return self.mg.get_node([cell_id])[0]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

vertex model grids do not currently have a get_node() method. This could either be added to the VertexGrid class or calculated here as [cellid[0] * ncpl] + cellid[1].

It would be great to maintain support for passing in lists or ndarrays of cellids that the mg.get_node function already has.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same formatting comment as listed with the other notebooks

Comment on lines +206 to +213
FloPy plugins are python code designed to change the behavior of MODFLOW 6.
Using FloPy plugins is similar to using a MODFLOW 6 package in FloPy. Two
example plugins are included with FloPy which behave similar to the RIV
package, but allow for different conductances in the upward and downward
directions. To use FloPy plugins instantiate a plugin input class
similar to the "package" input classes FloPy uses to add a package to an
existing model. Documentation for the plugin input classes of the two
example plugins are included below.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not entirely convinced that these plugins should be included in the flopy repo. It might be better to have the example plugins adjacent and have users install them, so they get used to working with plugins.

Additionally, they are the same calculation that is done with two different approaches: 1) Using the API package, and 2) overriding the functionality of the RIV package.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like Josh, I am still uncertain if this functionality should be in flopy.

Comment on lines +10 to +11
ModflowGwffp_Rvp defines a fp_rvp package that is a flopy plugin extension
of a gwf6 model.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a way to generate better docstings? This does not give any information about what the RVP package is used for.

Comment on lines +10 to +11
ModflowGwffp_Rvc defines a fp_rvc package that is a flopy plugin extension
of a gwf6 model.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a way to generate better docstings? This doesn't give any information about what the rvc package does or is used for

Comment on lines +53 to +54
_notebooks/tutorial01_mf6_plugin
_notebooks/tutorial02_mf6_plugin
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It'd be nice to have a tutorial on how to override functionality in an existing package. The current set of tutorials shows how to use an existing plugin and how to develop one in the API package. Modifying existing package behavior is an important topic.

@langevin-usgs
Copy link
Contributor

@jlarsen-usgs, these are great comments. Lots to consider. Makes me wonder if flopy is the right place to put these "plugins". They might be more aptly named plugins if they were included in modflowapi instead. That way the "plugin" term in flopy could be leveraged for the uses cases that you mention.

Copy link
Contributor

@jdhughes-usgs jdhughes-usgs left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is alot here to digest. I am still not convinced this belongs in flopy. It belongs somewhere but maybe not in flopy.

I like Josh's idea that a flopy plugin would extend flopy functionality. I could see a flopy plugin providing input and output support for MODFLOW-OWHM or MODFLOW-USG. That way it would be up to the person developing the plugin to support it and flopy could support what we want it to. We could think about a version of flopy (flopy4) that only supports mf6 and base functionality (modelgrid, geoproocessing, plotting, etc) and move old MODFLOW, MT3D, SEAWAT, etc functionality out as a plugin (this is an idea Chris brought up).

What you are calling a "flopy plugin" is providing a pre-canned way to use the API that seems like flopy. Josh's additions to the modflowapi package already helps alot in allowing users to interact with the API in a flopy way. So maybe "flopy plugins" should just be additional helper functionality in the modflowapi package.

I think more thought needs to be given to naming. Function names and variables should provide users with an unambiguous idea of what is going on.

I don't like that flopy dfns are in the repo. It seems like an opportunity for confusion over who owns dfns.

I am not completely clear on the difference between the rvc and rvp examples. They do seem overly complicated. I think one of the cases should be using a standard river package with an aux variable that defines either the inflow or outflow conductance and the code picks the appropriate one depending on the flow direction. Maybe I missed something.

]

api = [
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why has a separate api block been included in the toml? This should just be in the existing optional block. The standard modflowapi release should be used not the repo.

Comment on lines +206 to +213
FloPy plugins are python code designed to change the behavior of MODFLOW 6.
Using FloPy plugins is similar to using a MODFLOW 6 package in FloPy. Two
example plugins are included with FloPy which behave similar to the RIV
package, but allow for different conductances in the upward and downward
directions. To use FloPy plugins instantiate a plugin input class
similar to the "package" input classes FloPy uses to add a package to an
existing model. Documentation for the plugin input classes of the two
example plugins are included below.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like Josh, I am still uncertain if this functionality should be in flopy.

is_flopy_package = (
self.structure.name == "packages"
and arr_line_len > 1
and arr_line[1].startswith("FP_")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be better to use PI_ or better yet plugin_ to be clearer. Here and throughout the code.

@@ -0,0 +1,97 @@
from flopy.mf6.utils.flopy_plugins import FPAPIPluginInterface
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FPAPIPluginInterface is a terrible name. PluginInterface would be better.

import numpy as np


class FPPluginInterface:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FPAPIPluginInterface is a terrible name. PluginInterface would be better

"""
return True

def val_from_cellid(self, cell_id, grid_array):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like this returns a single value. If true couldn't this function be value_from_cellid().

grid_array can just be array or arr

Why would it ever be another modelgrid.grid_type? If you want to provide protection it should issue an error if it another grid_type.

return f"{self.lib_name}.dylib"
return ""

def get_api_dll(self):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

get_api_library


Returns
--------
success : bool
buff : list of lines of stdout

"""
if debug_mode:
fd_debug = open("debug_run_sim.txt", "w")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need to think about if we want flopy to open a separate file for writing information. Users can always specify print_input and print_budget to have information written to the mf6 lst file.

@spaulins-usgs
Copy link
Contributor Author

I am closing this PR since Flopy Plugins (now Flopy API Plugins) is now going to be its own library.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants