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

refactor(createpackages): use jinja for mf6 module code generation #2333

Open
wants to merge 95 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
95 commits
Select commit Hold shift + click to select a range
660d27d
refactor(createpackages): use jinja for mf6 module code generation
wpbonelli Sep 20, 2024
e04a3f9
add templates to package-data, misc
wpbonelli Oct 10, 2024
d7c8c10
shim fixes
wpbonelli Oct 10, 2024
9825462
cleanup
wpbonelli Oct 11, 2024
0a86dcc
appease codacy
wpbonelli Oct 11, 2024
213ac94
remove unneeded imports from context template
wpbonelli Oct 11, 2024
aea8673
remove unneeded conditional from init template
wpbonelli Oct 11, 2024
c55e968
cleanup
wpbonelli Oct 11, 2024
c17279b
fix codegen tests
wpbonelli Oct 11, 2024
0f067f2
fix job name in rtd.yml
wpbonelli Oct 11, 2024
eb93289
import Union
wpbonelli Oct 11, 2024
090249a
cleanup
wpbonelli Oct 11, 2024
6d33045
correction
wpbonelli Oct 11, 2024
60910a3
shim fixes
wpbonelli Oct 11, 2024
0acf294
hacky shim fix
wpbonelli Oct 11, 2024
bdbc62f
ruff
wpbonelli Oct 11, 2024
4e4e1d4
restructuring, passing more tests
wpbonelli Oct 14, 2024
ed3932e
ruff
wpbonelli Oct 15, 2024
e8a8f99
cleanup
wpbonelli Oct 15, 2024
77ee043
docstring fixes
wpbonelli Oct 15, 2024
97c3e4b
3.9 syntax?
wpbonelli Oct 15, 2024
05b1e87
cleanup
wpbonelli Oct 15, 2024
72d63e5
cleanup
wpbonelli Oct 15, 2024
5a17cab
docs/comments
wpbonelli Oct 15, 2024
a55eb3c
backcompat fix?
wpbonelli Oct 15, 2024
1b4fcb9
remove unused statement, fix mermaid diagrams in mf6_dev_guide, add b…
wpbonelli Oct 16, 2024
fb03225
bound boltons >= 1
wpbonelli Oct 16, 2024
4aa28bc
hints
wpbonelli Oct 16, 2024
19d250e
Moving code from shim to jinja
deltamarnix Oct 16, 2024
9e94460
move more from shim to jinja
wpbonelli Oct 27, 2024
1a558cb
codacy fixes
wpbonelli Oct 28, 2024
801648d
boltons and jinja as dev dependencies, cleanup
wpbonelli Oct 29, 2024
eb6885c
gen -> codegen dep group
wpbonelli Oct 29, 2024
a36667c
mention deps in generate_classes docs
wpbonelli Oct 29, 2024
da39cca
handle missing jinja
wpbonelli Oct 29, 2024
3433009
fixes
wpbonelli Oct 29, 2024
484707a
fixes after review
wpbonelli Oct 30, 2024
fef17f0
docstring indentation fix
wpbonelli Oct 30, 2024
a1c00aa
formatting fixes
wpbonelli Oct 30, 2024
3177a5b
fmt
wpbonelli Oct 30, 2024
9589282
remove description from context, just use name.description
wpbonelli Oct 31, 2024
c5ee66c
replace latex quotes in descriptions
wpbonelli Oct 31, 2024
7c52a55
consolidate ref param replacement in shim, remove children, fix descr…
wpbonelli Oct 31, 2024
5cf3644
trim shim
wpbonelli Oct 31, 2024
3d134af
add Dfn.load_all()
wpbonelli Oct 31, 2024
1c097d3
dataclasses -> typeddicts
wpbonelli Oct 31, 2024
a4f7874
remove unneeded imports
wpbonelli Oct 31, 2024
ddba336
add params macro
wpbonelli Oct 31, 2024
2ca3ec9
initial toml support
wpbonelli Oct 31, 2024
bf94fd9
Revert "add params macro"
wpbonelli Oct 31, 2024
2a0a7b8
fix renderable
wpbonelli Oct 31, 2024
1238f09
support var type directly (as str for now)
wpbonelli Oct 31, 2024
c3b6373
revisions
wpbonelli Nov 4, 2024
0d8f65b
ruff
wpbonelli Nov 4, 2024
97dcff5
remove shim and renderable, much cleanup
wpbonelli Nov 5, 2024
f7deff0
cleanup
wpbonelli Nov 5, 2024
4e17e7e
update dev guide
wpbonelli Nov 5, 2024
7ac2173
file record squashing as a filter
wpbonelli Nov 5, 2024
9ffdac2
prep for version support
wpbonelli Nov 5, 2024
0051a84
cleaner subpkg ref passing
wpbonelli Nov 5, 2024
24766cd
add new deps to environment.yml
wpbonelli Nov 6, 2024
198d552
fix filein/fileout in toml conversion
wpbonelli Nov 7, 2024
50ce758
switch dfn dataclass -> typeddict, owns-a (not is-a) var dict, cleare…
wpbonelli Nov 7, 2024
8a80999
order
wpbonelli Nov 7, 2024
aecbddd
improve docstrings, misc cleanup
wpbonelli Nov 10, 2024
00474eb
improve dev guide
wpbonelli Nov 10, 2024
104dcc5
revisions
wpbonelli Nov 13, 2024
eedfb66
whitespace mgmt
wpbonelli Nov 14, 2024
4dff13c
better composite variable docstring format
wpbonelli Nov 14, 2024
1d39225
rebase and ruff
wpbonelli Nov 14, 2024
cdecc74
indentation
wpbonelli Nov 14, 2024
c74a84a
cleanup
wpbonelli Nov 20, 2024
54a8fff
use devtools dfn utils, convert to toml before codegen, remove dfn at…
wpbonelli Dec 17, 2024
d45edaa
Remove load_from_dfn_files feature
wpbonelli Jan 15, 2025
6339fba
remove link between package container and mfstructure
wpbonelli Jan 15, 2025
5b20fc8
structure dfn attr (still broken)
wpbonelli Jan 15, 2025
0b843cf
fix filters
wpbonelli Jan 17, 2025
1899ebe
tidy
wpbonelli Jan 20, 2025
028203b
use develop version of devtools for now
wpbonelli Jan 24, 2025
9a3bb18
legacy dfn attr for now
wpbonelli Jan 25, 2025
afc72c8
wip
wpbonelli Jan 30, 2025
3ea7cc8
use https protocol for devtools dependency
wpbonelli Feb 3, 2025
2bc369d
cleanup
wpbonelli Feb 3, 2025
b746f7f
closer to passing tests ugly hack for the legacy dfn attribute
wpbonelli Feb 3, 2025
5303f02
closer
wpbonelli Feb 4, 2025
048eed9
mvr/gnc/mvt fix
wpbonelli Feb 4, 2025
61cbafa
deferred imports
wpbonelli Feb 4, 2025
138f745
remove uv lockfile
wpbonelli Feb 4, 2025
aae885e
temp tests no longer needed
wpbonelli Feb 4, 2025
139d953
fix
wpbonelli Feb 4, 2025
cafa7c2
revisions
wpbonelli Feb 5, 2025
327ab20
remove gen-classes command
wpbonelli Feb 5, 2025
052b1b8
simplify filters
wpbonelli Feb 6, 2025
51b25c5
omit subpackage containers where not needed
wpbonelli Feb 6, 2025
5c2a043
add autogenerated comment
wpbonelli Feb 6, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .docs/md/generate_classes.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@

MODFLOW 6 input continues to evolve as new models, packages, and options are developed, updated, and supported. All MODFLOW 6 input is described by DFN (definition) files, which are simple text files that describe the blocks and keywords in each input file. These definition files are used to build the input and output guide for MODFLOW 6. These definition files are also used to automatically generate FloPy classes for creating, reading and writing MODFLOW 6 models, packages, and options. FloPy and MODFLOW 6 are kept in sync by these DFN (definition) files, and therefore, it may be necessary for a user to update FloPy using a custom set of definition files, or a set of definition files from a previous release.

The FloPy classes for MODFLOW 6 are largely generated by a utility which converts DFN files in a modflow6 repository on GitHub or on the local machine into Python source files in your local FloPy install. For instance (output much abbreviated):
The FloPy classes for MODFLOW 6 are largely generated by a utility which converts DFN files in a modflow6 repository on GitHub or on the local machine into Python source files in your local FloPy install.

**Note**: to use this functionality, the `codegen` optional dependency group must be installed.

```bash
$ python -m flopy.mf6.utils.generate_classes
Expand Down
4 changes: 3 additions & 1 deletion .github/workflows/examples.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ jobs:
powershell

- name: Install FloPy
run: pip install .
run: |
pip install .
pip install ".[codegen]"

- name: OpenGL workaround on Linux
if: runner.os == 'Linux'
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/rtd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ concurrency:
cancel-in-progress: true
jobs:
set_options:
name: Set release options
name: Set options
runs-on: ubuntu-22.04
outputs:
ref: ${{ steps.set_ref.outputs.ref }}
Expand Down
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -105,5 +105,8 @@ app
# DFN backups
flopy/mf6/data/dfn_backup/

# DFN TOML dir
flopy/mf6/data/toml/

# uv lockfile
uv.lock
uv.lock
80 changes: 56 additions & 24 deletions docs/mf6_dev_guide.md
Original file line number Diff line number Diff line change
@@ -1,47 +1,79 @@
Introduction
-----------------------------------------------
# Developing FloPy for MF6

This file provides an overview of how FloPy for MODFLOW 6 (FPMF6) works under the hood and is intended for anyone who wants to add a new package, new model type, or new features to this library. FloPy library files that support MODFLOW 6 can be found in the flopy/mf6 folder and sub-folders.
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->

Package Meta-Data and Package Files
-----------------------------------------------
- [Introduction](#introduction)
- [Code generation](#code-generation)
- [Input specification](#input-specification)

FPMF6 uses meta-data files located in flopy/mf6/data/dfn to define the model and package types supported by MODFLOW 6. When additional model and package types are added to MODFLOW 6, additional meta-data files can be added to this folder and flopy/mf6/utils/createpackages.py can be run to add new packages to the FloPy library. createpackages.py uses flopy/mf6/data/mfstructure.py to read meta-data files (*.dfn) and use that meta-data to create the package files found in flopy/mf6/modflow (do not directly modify any of the files in this folder, they are all automatically generated). The automatically generated package files contain an interface for accessing package data and data documentation generated from the meta-data files. Additionally, meta-data describing package data types and shapes is stored in the dfn attribute. flopy/mf6/data/mfstructure.py can load structure information using the dfn attribute (instead of loading it from the meta-data files). This allows for flopy to be installed without the dfn files.
<!-- END doctoc generated TOC please keep comment here to allow auto update -->

All meta-data can be accessed from the flopy.mf6.data.mfstructure.MFStructure class. This is a singleton class, meaning only one instance of this class can be created. The class contains a sim_struct attribute (which is a flopy.mf6.data.mfstructure.MFSimulationStructure object) which contains all of the meta-data for all package files. Meta-data is stored in a structured format. MFSimulationStructure contains MFModelStructure and MFInputFileStructure objects, which contain the meta-data for each model type and each "simulation-level" package (tdis, ims, ...). MFModelStructure contains model specific meta-data and a MFInputFileStructure object for each package in that model. MFInputFileStructure contains package specific meta-data and a MFBlockStructure object for each block contained in the package file. MFBlockStructure contains block specific meta-data and a MFDataStructure object for each data structure defined in the block, and MFDataStructure contains data structure specific meta-data and a MFDataItemStructure object for each data item contained in the data structure. Data structures define the structure of data that is naturally grouped together, for example, the data in a numpy recarray. Data item structures define the structure of specific pieces of data, for example, a single column of a numpy recarray. The meta-data defined in these classes provides all the information FloPy needs to read and write MODFLOW 6 package and name files, create the Flopy interface, and check the data for various constraints.
## Introduction

This file provides an overview of how FloPy's MODFLOW 6 module `flopy.mf6` works under the hood. It is intended for FloPy maintainers, as well as anyone who wants to add a new package, new model, or new features to this library.

***
MFStructure --+ MFSimulationStructure --+ MFModelStructure --+ MFInputFileStructure --+ MFBlockStructure --+ MFDataStructure --+ MFDataItemStructure
## Code generation

Figure 1: FPMF6 generic data structure classes. Lines connecting classes show a relationship defined between the two connected classes. A "*" next to the class means that the class is a sub-class of the connected class. A "+" next to the class means that the class is contained within the connected class.
***
MODFLOW 6 describes its input specification with definition (DFN) files.

Package and Data Base Classes
-----------------------------------------------
Definition files describe components (e.g. simulations, models, packages) in the MODFLOW 6 input hierarchy. Definition files are used to generate both source code and documentation.

The package and data classes are related as shown below in figure 2. On the top of the figure 2 is the MFPackage class, which is the base class for all packages. MFPackage contains generic methods for building data objects and reading and writing the package to a file. MFPackage contains a MFInputFileStructure object that defines how the data is structured in the package file. MFPackage also contains a dictionary of blocks (MFBlock). The MFBlock class is a generic class used to represent a block within a package. MFBlock contains a MFBlockStructure object that defines how the data in the block is structured. MFBlock also contains a dictionary of data objects (subclasses of MFData) contained in the block and a list of block headers (MFBlockHeader) for that block. Block headers contain the block's name and optionally data items (eg. iprn).
FloPy can generate a MODFLOW 6 compatibility layer for itself, given a set of definition files:

- `flopy/mf6/utils/createpackages.py`: assumes definition files are in `flopy/mf6/data/dfn`
- `flopy/mf6/utils/generate_classes.py`: downloads DFNs then runs `createpackages.py`

For instance, to sync with DFNs from the MODFLOW 6 develop branch:

```shell
python -m flopy.mf6.utils.generate_classes --ref develop --no-backup
```

***
MFPackage --+ MFBlock --+ MFData
Generated files are created in `flopy/mf6/modflow/`.

MFPackage --+ MFInputFileStructure
The code generation utility downloads DFN files, loads them, and uses Jinja to generate corresponding source files. A definition file typically maps 1-1 to a source file and component class, but 1-many is also possible (e.g. a model definition file yields a model class/file and namefile package class/file).

MFBlock --+ MFBlockStructure
**Note**: Code generation requires a few extra dependencies, grouped in the `codegen` optional dependency group: `Jinja2` and `modflow-devtools`.

MFData --+ MFDataStructure
## Input specification

MFData --* MFArray --* MFTransientArray
The `flopy.mf6.data.mfstructure.MFStructure` class represents an input specification. The class is a singleton, meaning only one instance of this class can be created. The class contains a sim_struct attribute (which is a flopy.mf6.data.mfstructure.MFSimulationStructure object) which contains all of the meta-data for all package files. Meta-data is stored in a structured format. MFSimulationStructure contains MFModelStructure and MFInputFileStructure objects, which contain the meta-data for each model type and each "simulation-level" package (tdis, ims, ...). MFModelStructure contains model specific meta-data and a MFInputFileStructure object for each package in that model. MFInputFileStructure contains package specific meta-data and a MFBlockStructure object for each block contained in the package file. MFBlockStructure contains block specific meta-data and a MFDataStructure object for each data structure defined in the block, and MFDataStructure contains data structure specific meta-data and a MFDataItemStructure object for each data item contained in the data structure. Data structures define the structure of data that is naturally grouped together, for example, the data in a numpy recarray. Data item structures define the structure of specific pieces of data, for example, a single column of a numpy recarray. The meta-data defined in these classes provides all the information FloPy needs to read and write MODFLOW 6 package and name files, create the Flopy interface, and check the data for various constraints.

MFData --* MFList --* MFTransientList
```mermaid
classDiagram
MFStructure *-- "1" MFSimulationStructure : has
MFSimulationStructure *-- "1+" MFModelStructure : has
MFModelStructure *-- "1" MFInputFileStructure : has
MFInputFileStructure *-- "1+" MFBlockStructure : has
MFBlockStructure *-- "1+" MFDataStructure : has
MFDataStructure *-- "1+" MFDataItemStructure : has
```

MFData --* MFScalar --* MFTransientScalar
Figure 1: Generic data structure hierarchy. Connections show composition relationships.

The package and data classes are related as shown below in figure 2. On the top of the figure 2 is the MFPackage class, which is the base class for all packages. MFPackage contains generic methods for building data objects and reading and writing the package to a file. MFPackage contains a MFInputFileStructure object that defines how the data is structured in the package file. MFPackage also contains a dictionary of blocks (MFBlock). The MFBlock class is a generic class used to represent a block within a package. MFBlock contains a MFBlockStructure object that defines how the data in the block is structured. MFBlock also contains a dictionary of data objects (subclasses of MFData) contained in the block and a list of block headers (MFBlockHeader) for that block. Block headers contain the block's name and optionally data items (eg. iprn).

MFTransientData --* MFTransientArray, MFTransientList, MFTransientScalar
```mermaid
classDiagram

MFPackage *-- "1+" MFBlock : has
MFBlock *-- "1+" MFData : has
MFPackage *-- "1" MFInputFileStructure : has
MFBlock *-- "1" MFBlockStructure : has
MFData *-- "1" MFDataStructure : has
MFData <|-- MFArray
MFArray <|-- MFTransientArray
MFData <|-- MFList
MFList <|-- MFTransientList
MFData <|-- MFScalar
MFScalar <|-- MFTransientScalar
MFTransientData <|-- MFTransientArray
MFTransientData <|-- MFTransientList
MFTransientData <|-- MFTransientScalar
```

Figure 2: FPMF6 package and data classes. Lines connecting classes show a relationship defined between the two connected classes. A "*" next to the class means that the class is a sub-class of the connected class. A "+" next to the class means that the class is contained within the connected class.
***

There are three main types of data, MFList, MFArray, and MFScalar data. All three of these data types are derived from the MFData abstract base class. MFList data is the type of data stored in a spreadsheet with different column headings. For example, the data describing a flow barrier are of type MFList. MFList data is stored in numpy recarrays. MFArray data is data of a single type (eg. all integer values). For example, the model's HK values are of type MFArray. MFArrays are stored in numpy ndarrays. MFScalar data is a single data item. Most MFScalar data are options. All MFData subclasses contain an MFDataStructure object that defines the expected structure and types of the data.

Expand Down
8 changes: 8 additions & 0 deletions etc/environment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,14 @@ dependencies:
- matplotlib>=1.4.0
- pandas>=2.0.0

# codegen
- boltons>=1.0
wpbonelli marked this conversation as resolved.
Show resolved Hide resolved
- Jinja2>=3.0
- tomli
- tomli-w
- pip:
- git+https://github.com/MODFLOW-USGS/modflow-devtools.git
wpbonelli marked this conversation as resolved.
Show resolved Hide resolved

# lint
- cffconvert
- codespell>=2.2.2
Expand Down
2 changes: 2 additions & 0 deletions flopy/mf6/data/dfn/utl-ts.dfn
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ valid stepwise linear linearend
shape time_series_names
tagged false
reader urword
in_record true
optional false
in_record true
longname
Expand Down Expand Up @@ -112,6 +113,7 @@ name sfacs
type keyword
shape
reader urword
in_record true
optional false
in_record true
longname
Expand Down
2 changes: 1 addition & 1 deletion flopy/mf6/data/mfdatastorage.py
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,7 @@ def __init__(
self.data_structure_type = data_structure_type
package_dim = self.data_dimensions.package_dim
self.in_model = (
self.data_dimensions is not None
package_dim is not None
and len(package_dim.package_path) > 1
and package_dim.model_dim[0].model_name is not None
and package_dim.model_dim[0].model_name.lower()
Expand Down
86 changes: 0 additions & 86 deletions flopy/mf6/data/mfdatautil.py
Original file line number Diff line number Diff line change
Expand Up @@ -1054,89 +1054,3 @@ def empty(
return template
else:
return rec_array


class MFDocString:
wpbonelli marked this conversation as resolved.
Show resolved Hide resolved
"""
Helps build a python class doc string

Parameters
----------
description : string
description of the class

Attributes
----------
indent: string
indent to use in doc string
description : string
description of the class
parameter_header : string
header for parameter section of doc string
parameters : list
list of docstrings for class parameters

Methods
-------
add_parameter : (param_descr : string, beginning_of_list : bool)
adds doc string for a parameter with description 'param_descr' to the
end of the list unless beginning_of_list is True
get_doc_string : () : string
builds and returns the docstring for the class
"""

def __init__(self, description):
self.indent = " "
self.description = description
self.parameter_header = (
f"{self.indent}Parameters\n{self.indent}----------"
)
self.parameters = []
self.model_parameters = []

def add_parameter(
self, param_descr, beginning_of_list=False, model_parameter=False
):
if beginning_of_list:
self.parameters.insert(0, param_descr)
if model_parameter:
self.model_parameters.insert(0, param_descr)
else:
self.parameters.append(param_descr)
if model_parameter:
self.model_parameters.append(param_descr)

def get_doc_string(self, model_doc_string=False, sim_doc_string=False):
doc_string = '{}"""\n{}{}\n\n{}\n'.format(
self.indent, self.indent, self.description, self.parameter_header
)
if model_doc_string:
param_list = self.model_parameters
doc_string = (
"{} modelname : string\n name of the "
"model\n model_nam_file : string\n"
" relative path to the model name file from "
"model working folder\n version : string\n"
" version of modflow\n exe_name : string\n"
" model executable name\n"
" model_ws : string\n"
" model working folder path"
"\n".format(doc_string)
)
else:
param_list = self.parameters
for parameter in param_list:
if sim_doc_string:
pclean = parameter.strip()
if (
pclean.startswith("simulation")
or pclean.startswith("loading_package")
or pclean.startswith("filename")
or pclean.startswith("pname")
or pclean.startswith("parent_file")
):
continue
doc_string += f"{parameter}\n"
if not (model_doc_string or sim_doc_string):
doc_string += f'\n{self.indent}"""'
return doc_string
Loading
Loading