From aae5c3b375b6f53871b93600aa6d34fd067def4c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 9 Dec 2022 20:57:08 +0000 Subject: [PATCH 001/123] ci(release): update version to 6.5.0 development mode --- DISCLAIMER.md | 17 ++++++++--------- README.md | 19 +++++++++---------- code.json | 4 ++-- doc/version.py | 6 +++--- doc/version.tex | 2 +- src/Utilities/version.f90 | 28 +++++++++++++--------------- version.txt | 6 +++--- 7 files changed, 39 insertions(+), 43 deletions(-) diff --git a/DISCLAIMER.md b/DISCLAIMER.md index 9226475a939..f32778cb5e4 100644 --- a/DISCLAIMER.md +++ b/DISCLAIMER.md @@ -1,12 +1,11 @@ Disclaimer ---------- -This software has been approved for release by the U.S. Geological Survey -(USGS). Although the software has been subjected to rigorous review, the USGS -reserves the right to update the software as needed pursuant to further analysis -and review. No warranty, expressed or implied, is made by the USGS or the U.S. -Government as to the functionality of the software and related material nor -shall the fact of release constitute any such warranty. Furthermore, the -software is released on condition that neither the USGS nor the U.S. Government -shall be held liable for any damages resulting from its authorized or -unauthorized use. +This software is preliminary or provisional and is subject to revision. It is +being provided to meet the need for timely best science. The software has not +received final approval by the U.S. Geological Survey (USGS). No warranty, +expressed or implied, is made by the USGS or the U.S. Government as to the +functionality of the software and related material nor shall the fact of release +constitute any such warranty. The software is provided on the condition that +neither the USGS nor the U.S. Government shall be held liable for any damages +resulting from the authorized or unauthorized use of the software. diff --git a/README.md b/README.md index 5a7bda3c421..41c81a7c0ea 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ This is the development repository for the USGS MODFLOW 6 Hydrologic Model. The official USGS distribution is available at [USGS Release Page](https://water.usgs.gov/ogw/modflow/MODFLOW.html). -### Version 6.4.1 +### Version 6.5.0 Release Candidate [![MODFLOW 6 continuous integration](https://github.com/MODFLOW-USGS/modflow6/actions/workflows/ci.yml/badge.svg)](https://github.com/MODFLOW-USGS/modflow6/actions/workflows/ci.yml) [![MODFLOW 6 documentation](https://github.com/MODFLOW-USGS/modflow6/actions/workflows/docs.yml/badge.svg)](https://github.com/MODFLOW-USGS/modflow6/actions/workflows/docs.yml) @@ -105,13 +105,12 @@ Citations for specific versions are included with the [releases](https://github. Disclaimer ---------- -This software has been approved for release by the U.S. Geological Survey -(USGS). Although the software has been subjected to rigorous review, the USGS -reserves the right to update the software as needed pursuant to further analysis -and review. No warranty, expressed or implied, is made by the USGS or the U.S. -Government as to the functionality of the software and related material nor -shall the fact of release constitute any such warranty. Furthermore, the -software is released on condition that neither the USGS nor the U.S. Government -shall be held liable for any damages resulting from its authorized or -unauthorized use. +This software is preliminary or provisional and is subject to revision. It is +being provided to meet the need for timely best science. The software has not +received final approval by the U.S. Geological Survey (USGS). No warranty, +expressed or implied, is made by the USGS or the U.S. Government as to the +functionality of the software and related material nor shall the fact of release +constitute any such warranty. The software is provided on the condition that +neither the USGS nor the U.S. Government shall be held liable for any damages +resulting from the authorized or unauthorized use of the software. diff --git a/code.json b/code.json index 4332d9eca92..54ba297ac4d 100755 --- a/code.json +++ b/code.json @@ -1,6 +1,6 @@ [ { - "status": "Release", + "status": "Release Candidate", "languages": [ "Fortran2008" ], @@ -18,7 +18,7 @@ "email": "langevin@usgs.gov" }, "laborHours": -1, - "version": "6.4.1", + "version": "6.5.0", "date": { "metadataLastUpdated": "2022-12-09" }, diff --git a/doc/version.py b/doc/version.py index 547c0d85ce6..a4e12af98ec 100644 --- a/doc/version.py +++ b/doc/version.py @@ -1,7 +1,7 @@ # MODFLOW 6 version file automatically created using...update_version.py -# created on...December 09, 2022 20:27:24 +# created on...December 09, 2022 20:57:08 major = 6 -minor = 4 -micro = 1 +minor = 5 +micro = 0 __version__ = '{:d}.{:d}.{:d}'.format(major, minor, micro) diff --git a/doc/version.tex b/doc/version.tex index ca64ed2b2cb..ea81231afb8 100644 --- a/doc/version.tex +++ b/doc/version.tex @@ -1,3 +1,3 @@ -\newcommand{\modflowversion}{mf6.4.1} +\newcommand{\modflowversion}{mf6.5.0rc} \newcommand{\modflowdate}{December 09, 2022} \newcommand{\currentmodflowversion}{Version \modflowversion---\modflowdate} diff --git a/src/Utilities/version.f90 b/src/Utilities/version.f90 index 69431bfd4ba..08b08afb624 100644 --- a/src/Utilities/version.f90 +++ b/src/Utilities/version.f90 @@ -14,9 +14,9 @@ module VersionModule implicit none public ! -- modflow 6 version - integer(I4B), parameter :: IDEVELOPMODE = 0 - character(len=*), parameter :: VERSIONNUMBER = '6.4.1' - character(len=*), parameter :: VERSIONTAG = ' Release 12/09/2022' + integer(I4B), parameter :: IDEVELOPMODE = 1 + character(len=*), parameter :: VERSIONNUMBER = '6.5.0' + character(len=*), parameter :: VERSIONTAG = ' Release Candidate 12/09/2022' character(len=40), parameter :: VERSION = VERSIONNUMBER//VERSIONTAG character(len=10), parameter :: MFVNAM = ' 6' character(len=*), parameter :: MFTITLE = & @@ -58,18 +58,16 @@ module VersionModule ! -- disclaimer must be appropriate for version (release or release candidate) character(len=*), parameter :: FMTDISCLAIMER = & "(/,& - &'This software has been approved for release by the U.S. Geological ',/,& - &'Survey (USGS). Although the software has been subjected to rigorous ',/,& - &'review, the USGS reserves the right to update the software as needed ',/,& - &'pursuant to further analysis and review. No warranty, expressed or ',/,& - &'implied, is made by the USGS or the U.S. Government as to the ',/,& - &'functionality of the software and related material nor shall the ',/,& - &'fact of release constitute any such warranty. Furthermore, the ',/,& - &'software is released on condition that neither the USGS nor the U.S. ',/,& - &'Government shall be held liable for any damages resulting from its ',/,& - &'authorized or unauthorized use. Also refer to the USGS Water ',/,& - &'Resources Software User Rights Notice for complete use, copyright, ',/,& - &'and distribution information.',/)" + &'This software is preliminary or provisional and is subject to ',/,& + &'revision. It is being provided to meet the need for timely best ',/,& + &'science. The software has not received final approval by the U.S. ',/,& + &'Geological Survey (USGS). No warranty, expressed or implied, is made ',/,& + &'by the USGS or the U.S. Government as to the functionality of the ',/,& + &'software and related material nor shall the fact of release ',/,& + &'constitute any such warranty. The software is provided on the ',/,& + &'condition that neither the USGS nor the U.S. Government shall be held ',/,& + &'liable for any damages resulting from the authorized or unauthorized ',/,& + &'use of the software.',/)" contains diff --git a/version.txt b/version.txt index 547c0d85ce6..a4e12af98ec 100644 --- a/version.txt +++ b/version.txt @@ -1,7 +1,7 @@ # MODFLOW 6 version file automatically created using...update_version.py -# created on...December 09, 2022 20:27:24 +# created on...December 09, 2022 20:57:08 major = 6 -minor = 4 -micro = 1 +minor = 5 +micro = 0 __version__ = '{:d}.{:d}.{:d}'.format(major, minor, micro) From 092ed449620336a978729a9c71507c1efc150a03 Mon Sep 17 00:00:00 2001 From: w-bonelli Date: Fri, 9 Dec 2022 16:49:03 -0500 Subject: [PATCH 002/123] fix(distribution): miscellaneous * skip developmode tests on master CI * fix distribution folder name in docs * only build docs in build_dist.py for full release --- .github/workflows/ci.yml | 71 +++++++++++++++++------------------ .github/workflows/release.yml | 15 +++----- distribution/build_dist.py | 21 +++++------ 3 files changed, 51 insertions(+), 56 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8eda3547c8d..c181447651d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,7 +8,6 @@ on: paths-ignore: - '**.md' - 'doc/**' - - '.github/workflows/release.yml' pull_request: branches: - master @@ -16,7 +15,6 @@ on: paths-ignore: - '**.md' - 'doc/**' - - '.github/workflows/release.yml' jobs: lint: name: Lint (fprettify) @@ -36,8 +34,7 @@ jobs: cache-env: true - name: Check Fortran source formatting - run: | - .github/common/fortran-format-check.sh + run: .github/common/fortran-format-check.sh build: name: Build (gfortran 12) @@ -66,16 +63,13 @@ jobs: cache-env: true - name: Meson setup - run: | - meson setup builddir -Ddebug=false -Dwerror=true + run: meson setup builddir -Ddebug=false -Dwerror=true - name: Meson compile - run: | - meson compile -C builddir + run: meson compile -C builddir - name: Meson test - run: | - meson test --verbose --no-rebuild -C builddir + run: meson test --verbose --no-rebuild -C builddir test_gfortran_latest: name: Test (gfortran 12) @@ -140,9 +134,7 @@ jobs: - name: Build example models if: steps.cache-examples.outputs.cache-hit != 'true' working-directory: modflow6-examples/etc - run: | - python ci_build_files.py - ls -lh ../examples/ + run: python ci_build_files.py - name: Build modflow6 working-directory: modflow6 @@ -161,7 +153,12 @@ jobs: - name: Test programs working-directory: modflow6/autotest - run: pytest -v -n auto --durations 0 + run: | + if [ "${{ github.ref_name }}" == "master" ]; then + pytest -v -n auto --durations 0 -m "not developmode" + else + pytest -v -n auto --durations 0 + fi - name: Test scripts working-directory: modflow6/distribution @@ -213,8 +210,7 @@ jobs: - name: Update flopy working-directory: modflow6/autotest - run: | - python update_flopy.py + run: python update_flopy.py - name: Build modflow6 working-directory: modflow6 @@ -225,13 +221,16 @@ jobs: - name: Get executables working-directory: modflow6/autotest - run: | - pytest -v --durations 0 get_exes.py + run: pytest -v --durations 0 get_exes.py - name: Test modflow6 working-directory: modflow6/autotest run: | - pytest -v -n auto --durations 0 + if [ "${{ github.ref_name }}" == "master" ]; then + pytest -v -n auto --durations 0 -m "not developmode" + else + pytest -v -n auto --durations 0 + fi test_ifort: name: Test (ifort) @@ -293,15 +292,12 @@ jobs: - name: Install extra Python packages if: steps.cache-examples.outputs.cache-hit != 'true' working-directory: modflow6-examples/etc - run: | - pip install -r requirements.pip.txt + run: pip install -r requirements.pip.txt - name: Build example models if: steps.cache-examples.outputs.cache-hit != 'true' working-directory: modflow6-examples/etc - run: | - python ci_build_files.py - ls -lh ../examples/ + run: python ci_build_files.py - name: Update version files working-directory: modflow6/distribution @@ -326,42 +322,46 @@ jobs: - name: Update flopy working-directory: modflow6/autotest - run: | - python update_flopy.py + run: python update_flopy.py - name: Get executables if: runner.os != 'Windows' working-directory: modflow6/autotest - run: | - pytest -v --durations 0 get_exes.py + run: pytest -v --durations 0 get_exes.py - name: Get executables (Windows) if: runner.os == 'Windows' working-directory: modflow6/autotest shell: pwsh - run: | - pytest -v --durations 0 get_exes.py + run: pytest -v --durations 0 get_exes.py - name: Test programs if: runner.os != 'Windows' working-directory: modflow6/autotest run: | - pytest -v -n auto --durations 0 + if [ "${{ github.ref_name }}" == "master" ]; then + pytest -v -n auto --durations 0 -m "not developmode" + else + pytest -v -n auto --durations 0 + fi - name: Test programs (Windows) if: runner.os == 'Windows' working-directory: modflow6/autotest shell: pwsh run: | - pytest -v -n auto --durations 0 + if ( "${{ github.ref_name }}" -eq "master" ) { + pytest -v -n auto --durations 0 -m "not developmode" + } else { + pytest -v -n auto --durations 0 + } - name: Test scripts if: runner.os != 'Windows' working-directory: modflow6/distribution env: GITHUB_TOKEN: ${{ github.token }} - run: | - pytest -v --durations 0 + run: pytest -v --durations 0 - name: Test scripts (Windows) if: runner.os == 'Windows' @@ -369,5 +369,4 @@ jobs: shell: pwsh env: GITHUB_TOKEN: ${{ github.token }} - run: | - pytest -v --durations 0 + run: pytest -v --durations 0 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8d8cb3bae22..cfaf5383c94 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -226,12 +226,8 @@ jobs: python update_version.py -v "${ver#"v"}" --approve fi - # set dist name, format is 'mf_' - if [ "${{ runner.os }}" == "Windows" ]; then - distname="mf${ref#"v"}" - else - distname="mf${ref#"v"}_${{ matrix.ostag }}" - fi + # set dist name, format is 'mf' for docs (no os tag) + distname="mf${ref#"v"}" echo "DISTNAME=$distname" >> $GITHUB_ENV - name: Create directory structure @@ -486,7 +482,9 @@ jobs: body=' # MODFLOW '$ver' release - This release can be approved by merging this PR into `master`. Doing so triggers a final job, to: + The release can be approved by merging this PR into `master`. Merging rather than squashing is necessary to preserve the commit history. + + When this PR is merged, a final job will be triggered to: 1) create and tag a draft GitHub release, then upload assets (OS distributions and release notes) 2) open a PR to update `develop` from `master`, resetting version files and setting `IDEVELOPMODE=1` ' @@ -593,7 +591,6 @@ jobs: body=' # Reinitialize for development - Updates the `develop` branch from `master` following a successful release. - Increments the minor version number and resets `IDEVELOPMODE` back to `1`. + Updates the `develop` branch from `master` following a successful release. Increments the minor version number and resets `IDEVELOPMODE` back to `1`. ' gh pr create -B "develop" -H "$reset_branch" --title "Reinitialize develop branch" --draft --body "$body" \ No newline at end of file diff --git a/distribution/build_dist.py b/distribution/build_dist.py index 1775398526f..87307667e1f 100644 --- a/distribution/build_dist.py +++ b/distribution/build_dist.py @@ -325,6 +325,7 @@ def build_distribution( bin_path=output_path / "bin", overwrite=overwrite) + # full releases include examples, source code, makefiles and docs if not development: # examples setup_examples( @@ -332,23 +333,21 @@ def build_distribution( examples_path=output_path / "examples", overwrite=overwrite) - # docs - build_documentation( - bin_path=output_path / "bin", - output_path=output_path / "doc", - examples_repo_path=examples_repo_path, - # benchmarks_path=_benchmarks_path / "run-time-comparison.md", - development=development, - overwrite=overwrite) - - # full releases include source code and makefiles - if not development: # copy source code files copy_sources(output_path=output_path) # build and copy makefiles build_makefiles(output_path=output_path) + # docs + build_documentation( + bin_path=output_path / "bin", + output_path=output_path / "doc", + examples_repo_path=examples_repo_path, + # benchmarks_path=_benchmarks_path / "run-time-comparison.md", + development=development, + overwrite=overwrite) + @requires_exe("pdflatex") @pytest.mark.skip(reason="manual testing") From 6304fc591576dc1f13055890ce78a372f7e86f58 Mon Sep 17 00:00:00 2001 From: langevin-usgs Date: Sun, 11 Dec 2022 08:21:02 -0600 Subject: [PATCH 003/123] docs(releasenotes): reorganize release notes (#1112) * initialize for v6.5.0 * reorganize appendix A --- doc/ReleaseNotes/ReleaseNotes.tex | 3 +- doc/ReleaseNotes/appendixA.tex | 615 +------------------- doc/ReleaseNotes/previous/v0.9.00.tex | 4 + doc/ReleaseNotes/previous/v0.9.01.tex | 6 + doc/ReleaseNotes/previous/v0.9.02.tex | 11 + doc/ReleaseNotes/previous/v0.9.03.tex | 37 ++ doc/ReleaseNotes/previous/v6.0.0.tex | 26 + doc/ReleaseNotes/previous/v6.0.1.tex | 38 ++ doc/ReleaseNotes/previous/v6.0.2.tex | 30 + doc/ReleaseNotes/previous/v6.0.3.tex | 30 + doc/ReleaseNotes/previous/v6.0.4.tex | 35 ++ doc/ReleaseNotes/previous/v6.1.0.tex | 39 ++ doc/ReleaseNotes/previous/v6.1.1.tex | 55 ++ doc/ReleaseNotes/previous/v6.2.0.tex | 38 ++ doc/ReleaseNotes/previous/v6.2.1.tex | 56 ++ doc/ReleaseNotes/previous/v6.2.2.tex | 57 ++ doc/ReleaseNotes/previous/v6.3.0.tex | 52 ++ doc/ReleaseNotes/previous/v6.4.0.tex | 53 ++ doc/ReleaseNotes/previous/v6.4.1.tex | 8 + doc/ReleaseNotes/{v6.4.1.tex => v6.5.0.tex} | 30 +- 20 files changed, 608 insertions(+), 615 deletions(-) create mode 100644 doc/ReleaseNotes/previous/v0.9.00.tex create mode 100644 doc/ReleaseNotes/previous/v0.9.01.tex create mode 100644 doc/ReleaseNotes/previous/v0.9.02.tex create mode 100644 doc/ReleaseNotes/previous/v0.9.03.tex create mode 100644 doc/ReleaseNotes/previous/v6.0.0.tex create mode 100644 doc/ReleaseNotes/previous/v6.0.1.tex create mode 100644 doc/ReleaseNotes/previous/v6.0.2.tex create mode 100644 doc/ReleaseNotes/previous/v6.0.3.tex create mode 100644 doc/ReleaseNotes/previous/v6.0.4.tex create mode 100644 doc/ReleaseNotes/previous/v6.1.0.tex create mode 100644 doc/ReleaseNotes/previous/v6.1.1.tex create mode 100644 doc/ReleaseNotes/previous/v6.2.0.tex create mode 100644 doc/ReleaseNotes/previous/v6.2.1.tex create mode 100644 doc/ReleaseNotes/previous/v6.2.2.tex create mode 100644 doc/ReleaseNotes/previous/v6.3.0.tex create mode 100644 doc/ReleaseNotes/previous/v6.4.0.tex create mode 100644 doc/ReleaseNotes/previous/v6.4.1.tex rename doc/ReleaseNotes/{v6.4.1.tex => v6.5.0.tex} (56%) diff --git a/doc/ReleaseNotes/ReleaseNotes.tex b/doc/ReleaseNotes/ReleaseNotes.tex index 26faa919635..98c0aedc232 100644 --- a/doc/ReleaseNotes/ReleaseNotes.tex +++ b/doc/ReleaseNotes/ReleaseNotes.tex @@ -175,6 +175,7 @@ \section{Release History} 6.3.0 & March 4, 2022 & \url{https://doi.org/10.5066/P97FFF9M} \\ 6.4.0 & November 30, 2022 & \url{https://doi.org/10.5066/P9FL1JCC} \\ 6.4.1 & December 9, 2022 & \url{https://doi.org/10.5066/P9FL1JCC} \\ +6.5.0 & Month x, 202x & assigned at release \\ \hline \label{tab:releases} \end{tabular*} @@ -188,7 +189,7 @@ \section{Changes Introduced in this Release} This section describes changes introduced into MODFLOW~6 for the current release. These changes may substantially affect users. \begin{itemize} -\input{v6.4.1.tex} +\input{v6.5.0.tex} \end{itemize} % ------------------------------------------------- diff --git a/doc/ReleaseNotes/appendixA.tex b/doc/ReleaseNotes/appendixA.tex index 0e9745b1f23..2bad0245dce 100644 --- a/doc/ReleaseNotes/appendixA.tex +++ b/doc/ReleaseNotes/appendixA.tex @@ -1,599 +1,20 @@ This appendix describes changes introduced into MODFLOW~6 in previous releases. These changes may substantially affect users. -\begin{itemize} - - \item Version mf6.4.0--November 30, 2022 - - \underline{NEW FUNCTIONALITY} - \begin{itemize} - \item A new Viscosity (VSC) package for the Groundwater Flow (GWF) Model is introduced in this release. The effects of viscosity are accounted for by updates to intercell conductance, as well as the conductance between the aquifer and head-dependent boundaries, based on simulated concentrations and\/or temperatures. The VSC Package is activated by specifying ``VSC6'' as the file type in a GWF name file. Changes to the code and input may be required in the future in response to user needs and testing. Implementation details for the VSC Package are described in the Supplemental Technical Information guide, which is included with the MODFLOW 6 distribution. For this first implementation, the VSC Package cannot be used for a GWF Model that is connected to another GWF Model with a GWF-GWF Exchange. - \end{itemize} - - \underline{EXAMPLES} - \begin{itemize} - \item A new example called ex-gwt-stallman was added. This new problem uses the GWT Model as a surrogate for simulating heat flow. The example represents one-dimensional heat convection and conduction in the subsurface in response to a periodic temperature boundary condition imposed at land surface. Results from the MODFLOW 6 simulation are in good agreement with an analytical solution. - \end{itemize} - - \textbf{\underline{BUG FIXES AND OTHER CHANGES TO EXISTING FUNCTIONALITY}} \\ - \underline{BASIC FUNCTIONALITY} - \begin{itemize} - \item Corrected programming error in XT3D functionality that could affect coupled flow models or coupled transport models. The XT3D code would result in a memory access error when a child model with a much larger level of refinement was coupled to a coarser parent model. The XT3D code was generalized to handle this situation. - \item Corrected a programming error in which the final message would be written twice to the screen and twice to mfsim.lst when the simulation terminated prematurely. - \item Terminate with error if METHOD or METHODS not specified in time series input files. Prior to this change, the program would continue without an interpolated value for one or more time series records. - \item When a GWF Model and a corresponding GWT model are solved in the same simulation, the GWF Model must be solved before the corresponding GWT model. The GWF Model must also be solved by a different IMS than the GWT Model. There was not a check for this in previous versions and if these conditions were not met, the solution would often not converge or it would give erroneous results. - \item The DISV Package would not raise an error if a model cell was defined as a line. The program was modified to check for the case where the calculated cell area is equal to zero. If the calculated cell area is equal to zero, the program terminates with an error. - \item When searching for a required block in an input file, the program would not terminate with a sensible error message if the end of file was found instead of the required block. Program now indicates that the required block was not found. - \item This release contains a first step toward implementation of generic input routines to read input files. The new input routines were implemented for the DIS, DISV, and DISU Packages of the GWF and GWT Models, for the NPF Package of the GWF Model, and the DSP Package of the GWT Model. Output summaries written to the GWF and GWT Model listing files are different from summaries written using previous versions of MODFLOW 6. For packages that use the new input data model, the IPRN capability of the READARRAY utility (described in mf6io.pdf) is no longer supported as a way to write input arrays to the model listing file. The IPRN capability may not be supported in future versions as the new generic input routines are implemented for other packages. - \item Corrected an error in ZONEBUDGET for MODFLOW 6 that prevented the program from running properly in workspaces that contain one or more spaces in the path. - \end{itemize} - - \underline{INTERNAL FLOW PACKAGES} - \begin{itemize} - \item Corrected programming error in the Time-Variable Hydraulic Conductivity (TVK) Package in which the vertical hydraulic conductivity was not reset properly if the K33OVERK option was invoked in the Node Property Flow (NPF) Package. - \item The Node Property Flow (NPF) Package had an error in how the saturated thickness at the interface between two cells was calculated for a DISU connection that is marked as vertically staggered (IHC = 2). The calculation was corrected so that the thickness for two confined cells is based on the overlap of the cells rather than the average thickness of the two cells. - \end{itemize} - - \underline{STRESS PACKAGES} - \begin{itemize} - \item The Evapotranspiration (EVT) Package was modified to include a new error check if the segmented evapotranspiration capability is active. If the number of ET segments is greater than 1, then the user must specify values for PXDP (as well as PETM). For a cell, PXDP is a one-dimensional array of size NSEG - 1. Values in this array must be greater than zero and less than one. Furthermore, the values in PXDP must increase monotonically. The program now checks for these conditions and terminates with an error if these conditions are not met. The segmented ET capability can be used for list-based EVT Package input. Provided that the PXDP conditions are met, this new error check should have no effect on simulation results. - \item The Evapotranspiration (EVT) Package would throw an index error when SURF\_RATE\_SPECIFIED was specified in the OPTIONS block and NSEG was set equal to 1. The code now supports this combination of input options. - \end{itemize} - - \underline{ADVANCED STRESS PACKAGES} - \begin{itemize} - \item When the LAK Package was used with a combination of outlets that routed water to another lake and outlets that did not, then the budget information stored for the LAK Package had uninitialized records for LAK to LAK flows. These uninitialized records were used by the LKT Package and possibly other programs. The LAK to LAK budget information was modified to include only valid records. - \item When a WITHDRAWAL value was specified for lakes, only the withdrawal value for the last lake would be reported in budget files, lake budget tables, and in lake withdrawal observations. This error would also be propagated to the GWT Lake Transport (LKT) Package, if active. This error would only show up for models with more than one lake and if the lake withdrawal term was included. - \item When lakes were assigned with the STATUS CONSTANT setting to prescribe the lake stage, the CONSTANT term used in the lake budget table was tabulated using an incorrect sign for aquifer leakage. This error would result in inaccurate budget tables. The program modified to use the correct leakage values for calculating the CONSTANT term. - \item There were several problems in the observation utility for the Streamflow Transport (SFT), Lake Transport (LKT), Multi-Aquifer Well Transport (MWT), and Unsaturated Zone Transport (UZT) Packages. These issues were corrected as well as the descriptions for the observations in the user input and output guide. - \item The BUDGETCSV option for the advanced stress packages would intermittently cause an error due to a variable not being set properly. The BUDGETCSV option did not work at all for the GWT advanced packages. The BUDGETCSV option was fixed to work properly. - \item For multi-layer GWF Models, the UZF Package should generally have UZF cells assigned to each GWF cell that can be dry or partially saturated. If a UZF cell was assigned to an upper layer of a GWF Model, but not to underlying GWF layers, then outflow from the upper UZF cell would not always flow to the water table. The program was modified so that outflow from UZF cells is transferred into the GWF Model when there are no underlying UZF cells. This routing of water to GWF may not work properly unless the Newton-Raphson formulation is active. - \end{itemize} - - \underline{EXCHANGES} - \begin{itemize} - \item The GWT-GWT Exchange did not work when the XT3D\_OFF option was specified. The program was fixed so that the XT3D dispersion terms can be shut off for a GWT-GWT Exchange. GWT-GWT Exchange keywords were renamed from ADVSCHEME, XT3D\_OFF, and XT3D\_RHS to ADV\_SCHEME, DSP\_XT3D\_OFF, and DSP\_XT3D\_RHS, respectively, to more clearly indicate how the keywords relate to the underlying processes. - \end{itemize} - - - \item Version mf6.3.0--March 4, 2022 - - \underline{NEW FUNCTIONALITY} - \begin{itemize} - \item New publications describing MODFLOW~6 were released, including \cite{modflow6api}, \cite{modflow6gwt}, and \cite{modflow6csub}. - \item The GWF-GWF Exchange was updated to support XT3D flow calculations along the edges of connected GWF Models. The XT3D flow calculation is an alternative to the ghost-node correction and has been shown to provide accurate flow calculations for cell connections that do not meet the control-volume finite-difference requirements. This new capability allows the GWF-GWF Exchange to tightly couple a wide variety of parent and child grid configurations. The new XT3D option for the GWF-GWF Exchange can be activated by specifying XT3D in the options block of the GWF-GWF Exchange input file. The XT3D implementation for GWF-GWF is based on a new generalized coupling method that is described in the Supplemental Technical Information document distributed with this release. - \item A new capability was added to support tight coupling of GWT Models through a new GWT-GWT Exchange. The new GWT-GWT Exchange can be used to connect any two GWT Models in a manner similar to the GWF-GWF Exchange. This allows transport to be represented from one GWT Model to another. The GWT-GWT Exchange calculates advective and dispersive fluxes and also supports the Mover Transport (MVT) Package. This first release of the GWT-GWT Exchange is limited to simulations in which the corresponding GWF Models and GWF-GWF Exchanges are run concurrently within the same simulation; the new capability does not support simulation of transport using groundwater flows saved from a previous simulation; however, this may be supported in the future. The new GWT-GWT functionality is activated by creating a GWT-GWT input file and specifying a GWT6-GWT6 exchange in the EXCHANGEDATA block of the simulation name file (mfsim.nam). The GWT-GWT Exchange is based on a new generalized coupling method that is described in the Supplemental Technical Information document distributed with this release. - \item This release introduces the Time-Varying hydraulic conductivity (TVK) and the Time-Varying Storage (TVS) options for the Node Property Flow (NPF) and Storage (STO) Packages, respectively, of the GWF Model. The TVK option is activated by specifying the TVK6 keyword in the OPTIONS block of the NPF Package and by providing a TVK6 input file. The TVS option is activated by specifying the TVS6 keyword in the OPTIONS block of the STO Package, and by providing a TVS6 input file. The TVK6 and TVS6 input files are described in input and output guide (mf6io.pdf). Technical information about the TVK and TVS options is provided in the Supplemental Technical Information report that is provided with the distribution. - \item The option already existed to provide binary budget and head file information from a GWF simulation for only the first time step of a given stress period, in which case that information will be used for all time steps in that stress period in a GWT simulation that reads from those files. The behavior of this option has been extended such that if the binary budget and head file information provided for the final stress period of the GWF simulation is for only one time step, that information will be used for any subsequent time steps and stress periods in the GWT simulation. This extended behavior includes as a special case the use of binary budget and head file information from only one time step in only one stress period (for example, a single steady-state GWF stress period) for all time steps in all stress periods in a GWT simulation. - \item Added new file-based input capability for the GWT Source and Sink Mixing (SSM) Package. The SSM Package previously required that all source and sink concentrations be provided as auxiliary variables for corresponding GWF stress packages. The SSM Package now supports an optional new FILEINPUT block, which can be provided with package names and file names. Files referenced in the FILEINPUT block can be used to provide time-varying source and sink concentrations. Descriptions for this new capability are described in the input and output guide (mf6io.pdf) under the GWT SSM Package, and the GWT Stress Package Concentrations (SPC) for list-based input and array-based input. If used, a single SPC6 input file can be provided for a corresponding GWF stress package. Either an SPC6 input file or the auxiliary variable approach can be used to supply concentrations for a single GWF stress package, but not both. - \item Added BUDGETCSV option to GWF and GWT model output control and to advanced packages (SFR, LAK, MAW, UZF, MVR, SFT, LKT, MWT, UZT, and MVT) that produce summary tables of budget information. If activated, this option will cause a comma-separated value (CSV) file of the model budget terms to be written to a user-specified output file. These output CSV files can be easily read into a spreadsheet or scripting language for plotting of budget terms. - \item Added option to use irregular cross sections to define the reach geometry for individual reaches in the SFR Package. Cross-section data for irregular cross-sections are defined in a separate Cross-Section Table Input File. The station-elevation data for an irregular cross section are specified as xfraction and height data, which is converted to station position using the specified reach width (RWID) and elevation using the specified bottom elevation of the reach (RTP). Manning's roughness coefficient fractions can optionally be specified with the xfraction-height data for a irregular cross section to represent roughness coefficient variations in a channel (for example, different channel and overbank Manning's roughness coefficients). SFR Package irregular cross sections and the method used to solve for streamflow in a reach with non-uniform Manning's roughness coefficients is a generalization of the methods used for 8-point cross-sections in the SFR Package for previous versions of MODFLOW \citep{modflowsfr1pack}. - \end{itemize} - - \underline{EXAMPLES} - \begin{itemize} - \item A new example, ex-gwtgwt-mt3dms-p10, was added to demonstrate the new GWT-GWT Exchange. - \end{itemize} - - \textbf{\underline{BUG FIXES AND OTHER CHANGES TO EXISTING FUNCTIONALITY}} \\ - \underline{BASIC FUNCTIONALITY} - \begin{itemize} - \item Fixed a bug that caused the last binary budget and head file information provided by a GWF simulation in a given stress period to be used for all subsequent GWT simulation time steps in that stress period, even if binary budget and head file information was provided for more than one time step in that stress period. In that situation, the GWF time steps must match the GWT time steps one-for-one. - \item Boundary packages, such as WEL, DRN and GHB, for example, read lists of data using a general list reader. For text input, the list of data would not be read correctly if it was longer than 300 characters wide. Most lists would normally be less than 300 characters unless a large number of auxiliary variables were specified. In this case, information beyond 300 characters would not be read. The list reader was modified to use an unlimited character length so that any number of auxiliary variables can be specified. - \item The Observation (OBS) functionality can optionally write simulated values to a comma-separated value (CSV) output file. The OBS input file contains a DIGITS option, which controls the number of digits written to the CSV output file. In some cases the ``E'' character was not written, making it difficult to read the CSV output file with other programs. New functionality was programmed to write observations using the maximum number of digits stored internally by the program using the G0 Fortran specifier. The default value for DIGITS was five, but this has been changed to this maximum number of digits. This maximum number of digits can also be activated by specifying a zero for DIGITS. For most applications, observations should be written with the default number of digits so that no precision is lost in the output. - \item Added a check to the Flow Model Interface (FMI) Package of the GWT Model that will cause the program to terminate with an error if flow and budget files needed from a flow model cannot be located. - \item The Output Control Package did not correctly determine if the end of a stress period was reached if the Adaptive Time Stepping (ATS) option was active. Output Control works correctly now with ATS. - \item Budget information for individual boundary package entries can be written to the model list file in a table form. The table no longer includes boundaries that are in cells that are dry. - \item When the simulated concentration in a cell is negative, which can occur due to the numerical methods used to solve the transport equation, then any sinks present in the cell should not add or remove mass. The program was modified so that transport through sinks is deactivated if the simulated concentration in a cell is negative. - \item Parsing of observation input was corrected so that observation names can have spaces if the observation name is enclosed in quotations. - \item The GWF Node Property Flow (NPF) Package can optionally calculate the three components of specific discharge. The program calculates these components using an interpolation method based on the XT3D method, the simulated flows across each cell face, and distances from the center of the cell to each cell face. This release contains a fix for a coding error in the calculation of the distance from the center of the cell to the cell face. The fix will affect the calculated specific discharge for model grids that have variable cell sizes. Because specific discharge is used in the calculation of the dispersion coefficients for GWT Models, simulated concentrations with this release may be different from simulated concentrations from previous releases; however, these concentration differences are expected to be minor. - \end{itemize} - - \underline{STRESS PACKAGES} - \begin{itemize} - \item Fixed a bug in the Recharge (RCH) and Evapotranspiration (EVT) Packages that would occur with array-based input (READASARRAYS) combined with the Time-Array Series (TAS) functionality. Because the TAS functionality only works for DIS and DISV models, this fix does not have any effect on models that use DISU discretization. The bug would occur when one or more cells in the upper layer were removed using the IDOMAIN capability. In this special case, the time-array series values did not carry over to the correct locations in the RCH and EVT arrays. This error would be difficult to identify for complex models, because the model would run to completion without any errors. The RCH and EVT Packages were modified to maintain direct correspondence between the \texttt{nodelist} and \texttt{bound} arrays with the arrays provided to the TAS utility. A small and intended consequence of this fix is that the ID2 value written to the binary budget file for array-based RCH and EVT Packages will correspond to the consecutive cell number in the top layer. - \item Recharge and evapotranspiration flows that are written to the GWF Model binary budget file are marked with the text headers ``RCH'' and ``EVT'', respectively. In order to support the array-based input for SSM concentrations, these text headers are marked as ``RCHA'' and ``EVTA'', respectively, if the READASARRAYS option is specified for the package. This change will have no effect on simulation results; however, post processing capabilities may need to be adjusted to account for this minor renaming convention. - \item When the PRINT\_FLOWS option was used with the Source and Sink Mixing (SSM) Package, the cell ID numbers written to the listing file were incorrect. The SSM Package was fixed to output the correct cell ID. - \item Add new AUTO\_FLOW\_REDUCE\_CSV option for the Well Package. If activated, then this option will result in information being written to a comma-separated value file for each well and for each time step in which the extraction rate is reduced. Well extraction rates can be reduced for some groundwater conditions if the AUTO\_FLOW\_REDUCE option is activated. Information is not written for wells in which the extraction rate is equal to the user-specified extraction rate. - \end{itemize} - - \underline{ADVANCED STRESS PACKAGES} - \begin{itemize} - \item The Streamflow Routing (SFR) Package would terminate with a floating point error when calculating the stream depth as a function of flow rate, if the flow rate was slightly negative. Added a conditional check to ensure that the stream depth is calculated as zero if the calculated flow rate is zero or less. - \end{itemize} - - \underline{SOLUTION} - \begin{itemize} - \item The way in which constant head and constant concentration boundary conditions are handled when the conjugate gradient method is used for the linear solve was modified. In previous versions, constant head and concentration conditions would result in an asymmetric coefficient matrix. The program has been modified so that if the conjugate gradient method is selected for the linear solution, then matrix symmetry is preserved by adding adding flows into and out of constant head and concentration cells to the right-hand side of connected cells. - \end{itemize} - -\end{itemize} - - -\begin{itemize} - \item Version mf6.2.2--July 30, 2021 - - \underline{NEW FUNCTIONALITY} - \begin{itemize} - \item A new Adaptive Time Step (ATS) utility was added. The ATS utility allows any stress period to be overridden with an alternative time stepping approach. The ATS utility implements two main capabilities (1) the capability to retry failed time steps with a shorter time step repeatedly until convergence is achieved, and (2) the capability to shorten and lengthen time steps based on simulation behavior. These capabilities are described in the user input and output guide in a new section on the ATS utility. - \item A new option for printing water contents to a dedicated output file has been added to UZF. To activate, the keyword WATER\_CONTENT is added to the OPTIONS block of UZF, followed by FILEOUT, followed by the user-specified output file name, for example ``water-content.uzf.bin''. The approach is analogous to the STAGE option within the SFR options block. Contents of the new file will be written in binary and can be read using flopy's binaryfile utility. - \item The residual balance error for groundwater flow and solute transport is now written to the diagonal position of the flowja array, which is marked with the text description ``FLOW-JA-FACE''. The flowja array is optionally written to the binary model budget file according to user settings in the output control file and other package input files. - \item A new option for simulating specific storage changes only when a cell is fully saturated has been added to the storage (STO) package. To activate, the SS\_CONFINED\_ONLY keyword is added to the OPTIONS block in the STO Package. This option is identical to the approach used to calculate storage changes under confined conditions in MODFLOW-2005. - \item A new observation type called ``wel-reduction'' was added for the Well Package. This observation type reports the reduction in the well discharge that can occur when the \texttt{AUTO\_FLOW\_REDUCE} option is specified. - \end{itemize} - - \underline{EXAMPLES} - \begin{itemize} - \item Added the following new examples: - \begin{itemize} - \item ex-gwt-hecht-mendez - \item ex-gwf-capture (This example is described in mf6examples.pdf to demonstrate functionality of the Application Programming Interface; it is not included in the examples folder of this distribution as it requires python and several python packages) - \end{itemize} - \item Added new citation to this document. The \cite{morway2021} paper describes the use of the Water Mover Package in MODFLOW~6 to represent natural and managed hydrologic connections. - \end{itemize} - - \textbf{\underline{BUG FIXES AND OTHER CHANGES TO EXISTING FUNCTIONALITY}} \\ - \underline{BASIC FUNCTIONALITY} - \begin{itemize} - \item The specific storage formulation in the storage (STO) package has been modified to eliminate the dependency of the original formulation on the vertical datum. The original specific storage formulation also overestimated storage changes for cells that resaturated or desaturated in successive time steps. Furthermore, the sign of the specific storage change was incorrect in cells with negative heads and resaturated or desaturated in successive time steps. The revised specific storage formulation resolves all of the deficiencies of the original formulation and accurately simulates specific storage changes under water table conditions but will change the results for existing models. Testing indicates that the differences between models run with the original and revised specific storage formulation are generally small but tend to increase in models with large specific storage values or have cells that repeatedly resaturated or desaturated in successive time steps. - \item The convergence failure message message written to GWF and GWT listing files (FAILED TO MEET SOLVER CONVERGENCE CRITERIA) is now written after the budget summary tables. In previous releases this convergence failure message was written prior to printing heads and concentrations, which often resulted in this message being unnoticed by users. - \item The order of output written to the GWF and GWT listing files for a time step was reorganized in a consistent manner with model and package flows coming first, followed by dependent variables, and then concluding with budget summary tables. - \item The DISU Package checks to make sure that the top of a cell is not higher than the bottom of an overlying cell. A new option was added to the DISU Package to allow the user to specify the vertical offset tolerance used in this check. This new optional input variable is VERTICAL\_OFFSET\_TOLERANCE. - \item Add DISU Package check to ensure that JA(IA(n)) is equal to n and that no values in JA are less than zero or greater than nodes. - \item When IDOMAIN is used with the DISU Package and any IDOMAIN value is zero, then the program was expecting all JA values to be positive. The program is supposed to allow a negative JA value to be specified for the corresponding cell (in the diagonal position), but this was not working. A fix was implemented to allow a negative cell number to be specified in the diagonal position of the JA array when the IDOMAIN capability is active. - \item A new check was added to the Horizontal Flow Barrier (HFB) Package to ensure that barriers are between cells that are horizontally connected. The program would previously continue running if a barrier was between vertically connected cells. - \item There was no check to prevent the zero-order decay functionality of the Mobile Storage and Transfer (MST) and Immobile Storage and Transfer (IST) Packages in the GWT Model from producing negative concentrations. The program now reduces the zero-order decay rate for the aqueous and sorbed phases (for the mobile and immobile domains) to ensure that decay does not consume more mass than is available. These changes do not affect zero-order growth. - \item If a binary budget file from a GWF Model was larger than about 2 Gigabytes, then it could not be used as input for a subsequent GWT Model. The program was modified to use a long integer to store the byte position. - \item The program was terminating with a non-zero return code if the simulation did not converge. This is the intended behavior, unless the CONTINUE option is specified in the simulation name file. The program now terminates with a return code of zero if the simulation does not converge, but the CONTINUE option is set and the program reaches the end of the simulation. - \end{itemize} - -% \underline{STRESS PACKAGES} -% \begin{itemize} -% \item xxx -% \item xxx -% \item xxx -% \end{itemize} - - \underline{ADVANCED STRESS PACKAGES} - \begin{itemize} - \item The UZF water-content observation by depth was giving an error, because a check was using the wrong index to retrieve the cell top and bottom elevations for the requested observation. The program was modified to use the correct index, and the output is now as expected. Note that this bug is not related to the new WC keyword in the OPTIONS block, but rather is related to OBS6 output option. - \item Amend surfdep error check with landflag. Deep cells (non-land surface cells) should not require surfdep > 0 - \item In the LAK observation package, users can specify ``lak'' to get a summary of lake-groundwater exchange. Users could specify a lake number without specifying a specific connection number (variable ``iconn''). Code will now stop if lake number is provided without a matching connection number. Code will still provide a summary of total lake-groundwater exchange when BOUNDNAME is entered for the variable ID. This also will fix a similar issue for the observation types ``wetted-area'' and ``conductance'', since both require ID2 when ID is an integer corresponding to a lake number. - \item In the MAW observation package, users can specify ``maw'' to get a summary of well-groundwater exchange. The code was allowing users to specify a well number without requiring specification of a connection number (variable ``icon''). Code will now stop if well number is provided without a matching connection number. Code will still provide a summary of total well-groundwater exchange when BOUNDNAME is entered for the variable ID. This also will fix a similar issue for the observation type ``conductance'', since both require ID2 when ID is an integer corresponding to a well number. - \end{itemize} - - \underline{SOLUTION} - \begin{itemize} - \item An optional new input variable called ATS\_OUTER\_MAXIMUM\_FRACTION can now be entered for the IMS Package. This variable has no effect unless the new ATS capability is active. -% \item xxx -% \item xxx - \end{itemize} - -\end{itemize} - - - -\begin{itemize} - \item Version mf6.2.1--February 18, 2021 - - \underline{NEW FUNCTIONALITY} - \begin{itemize} - \item The Source and Sink Mixing (SSM) Package for the Groundwater Transport Model was modified to include an alternative option for the concentration value assigned to sinks. A new AUXMIXED option was added to represent evaporation-like sinks where the solute or a portion of the solute may be left behind. The AUXMIXED option provides an alternative method for determining the groundwater sink concentration. If the cell concentration is larger than the user-specified sink concentration, then the concentration of the sink will be assigned as the specified concentration. Alternatively, if the specified concentration is larger than the cell concentration, then water will be withdrawn at the cell concentration. Thus, the AUXMIXED option is designed to work with the Evapotranspiration and Recharge packages where water may be withdrawn at a concentration that is less than the cell concentration. - \item Add support for the Freundlich and Langmuir isotherms to the Mass Storage and Transfer (MST) Package of the Groundwater Transport Model. - \end{itemize} - - \underline{EXAMPLES} - \begin{itemize} - \item Added the following new examples: - \begin{itemize} - \item ex-gwt-mt3dms-p02 - \item ex-gwt-rotate - \item ex-gwt-saltlake - \item ex-gwt-uzt-2d - \end{itemize} - \end{itemize} - - \textbf{\underline{BUG FIXES AND OTHER CHANGES TO EXISTING FUNCTIONALITY}} \\ - \underline{BASIC FUNCTIONALITY} - \begin{itemize} - \item The way in which the dispersion coefficients are calculated with the simple option (XT3D\_OFF) for the Dispersion Package was modified. When the velocity within a cell is not aligned with a principal grid direction, the dispersion coefficients are calculated using a simple arithmetic weighting, rather than harmonic weighting as is done for the simple option for anisotropic flow in the NPF Package. The arithmetic weighting option eliminates a possible discontinuity when a principal flow-aligned dispersion component is zero. - \item The mass flow between two cells is calculated and optionally written to the GWT budget file. There was an error in this calculation of mass flow when the TVD scheme was specified in the Advection (ADV) Package. Consequently, the mass flows written to the budget file were not correct in this situation. Because these mass flows are also used in the budget calculations for the Constant Concentration (CNC) Package, reported CNC mass flows were also not correct. This could result in large budget discrepancies in the GWT budget table. Simulated concentrations were not affected by this error. A small correction was made to the routine that adds the advective mass flow for the TVD scheme. - \item Several packages had input blocks that could not be specified using the OPEN/CLOSE keyword. The program was modified so that OPEN/CLOSE is supported for all intended blocks. - \item The Immobile domain Storage and Transfer (IST) Package for the GWT Model is based on a conceptual model in which the immobile domain is always fully saturated, and so the saturation of the immobile domain does not depend on head in a cell. The program was modified so that none of the immobile domain terms include saturation, except for the mass transfer equation itself, in which the transfer of solute between the mobile and immobile domain is multiplied by the cell saturation. - \item Budget terms for the Immobile domain Storage and Transfer (IST) Package were not being written to the binary budget file for the GWT Model. The package was modified to write these rate terms to the GWT binary budget file using the settings specified in the GWT Output Control file. - \item Bulk density does not need to be specified for the Immobile domain Storage and Transfer (IST) Package if sorption is not active; however the program was trying to access bulk density even though it is not needed, which resulted in an access violation. Program was fixed so that bulk density does not need to be specified by the user unless sorption is active for the IST Package. - \item Budget tables printed to the listing file had numeric values that were missing the `E` character if the exponent had three digits (e.g. 1.e-100 or 1.e100). Writing of the budget table was modified to include the `E` character in this case. This change should make it easier for programs written in other languages to parse these tables. - \item In the Mass Transfer and Storage (MST) and the Immobile Storage and Transfer (IST) Packages, the keyword to activate sorption was changed from SORBTION to SORPTION. The program will still accept SORBTION, but this keyword will be deprecated in the future. - \item Revised several of the text strings written to the headers within the GWT binary budget file. A table of possible text strings for the GWT binary budget file are now included in the mf6io.pdf document. - \end{itemize} - -% \underline{STRESS PACKAGES} -% \begin{itemize} -% \item xxx -% \item xxx -% \item xxx -% \end{itemize} - - \underline{ADVANCED STRESS PACKAGES} - \begin{itemize} - \item The CONSTANT term used to report the rate of mass provided by a constant-concentration condition in the LKT, SFT, MWT, and UZT did not include the contribution to adjacent package features. For example, if a stream reach was marked as constant-concentration boundary and it had flow into a downstream reach, then that flow was not included in the budget calculations. Consequently, reported budgets in the listing file would show discrepancies that were larger than what was actually simulated by the model. The program code was modified to include these mass flows to adjacent features. - \item The ET formulation in UZF was not reducing the residual pET passed to deeper UZF objects when the extinction depth spanned multiple UZF objects (layers). As a result, too much water was removed when the water table was shallow. Or, in some cases, water was removed from dry cells that were above the water table but within the extinction depth interval. The ET code within the UZF package was modified to remove only eligible water from the unsaturated and saturated zones. - \item The UZF package should exit with an appropriate error message when SURFDEP > cell thickness. When this condition is not enforced, bad mass balances may result. - \item The FLOW\_IMBALANCE\_CORRECTION implemented in the GWT FMI Package did not work properly with the GWF UZF Package. The issue was fixed by ensuring that the FMI Package could accurately calculate the flow residual for cells that had a UZF entry. - \item The SFR package should exit with an appropriate error message when a diversion has a cprior type of FRACTION but the divflow value is outside the range 0.0 to 1.0 as stated in the documentation. - \end{itemize} - -% \underline{SOLUTION} -% \begin{itemize} -% \item xxx -% \item xxx -% \item xxx -% \end{itemize} - -\end{itemize} - - - -\begin{itemize} - - \item Version mf6.2.0--October 20, 2020 - - \underline{NEW FUNCTIONALITY} - \begin{itemize} - \item A new Buoyancy (BUY) Package for the Groundwater Flow (GWF) Model is introduced in this release as a way to represent variable-density groundwater flow. The BUY Package is based on the hydraulic head formulation described by \cite{langevin2020hydraulic}. Extensive testing of the BUY Package has been performed but changes to the code and input may be required in response to user needs and testing. - \item A new Groundwater Transport (GWT) Model is introduced in this release as a way to simulate the fate and transport of a dissolved solute. Extensive testing of the GWT Model has been performed but changes to the code and input may be required in response to user needs and testing. - \item The Basic Model Interface (BMI) capabilities were first released in version 6.1.1. Extensive testing of the BMI capabilities has been performed but changes to the code and calling procedures be required in response to user needs and testing. - \end{itemize} - - \underline{EXAMPLES} - \begin{itemize} - \item The format for the examples included in the distribution has changed. The examples are now described in the modflow6-examples.pdf file in the doc folder. The examples have been renamed, and they are no longer numbered. Most of the examples are the same as those distributed with the previous release; however some have been modified, updated, combined or eliminated based on standardization of example construction, testing, and documentation. - \end{itemize} - - \textbf{\underline{BUG FIXES AND OTHER CHANGES TO EXISTING FUNCTIONALITY}} \\ - \underline{BASIC FUNCTIONALITY} - \begin{itemize} - \item The observation routines were improved to handle very large numbers of observations written to the same comma-separated-value (CSV) file. Non-advancing input-output is now used to write the CSV header instead of constructing a header string. This change should substantially improve memory and runtime problems with models containing thousands of observations. - \item If the CONTINUE option is specified in mfsim.nam, do not force models to write budget tables when the solver does not converge. Instead, always use Output Control options to determine when budget tables are written if CONTINUE option is specified and the solver does not converge. Also, if the CONTINUE option is specified, calculate and write observations even if the model does not converge. Observations were being written as zero if the model did not converge, but the CONTINUE flag was set. - \item Allow the program to read input files with very long lines. Previously, the program was limited to a maximum line length of 50,000 characters. The program now uses dynamic memory allocation, when necessary, to read any sized line in an input file. - \item Fixed an error in the implementation of the Newton-Raphson correction for XT3D. The error in the code would have only affected simulations that used the NEWTON option together with the XT3D RHS option. - \end{itemize} - - \underline{ADVANCED STRESS PACKAGES} - \begin{itemize} - \item Fix error in calculated Newton-Raphson MAW-GWF connection terms for the MAW Package. This correction should improve model convergence and may change existing model results. This correction does not affect simulations that use the FLOW\_CORRECTION option introduced in version 6.1.1. - \item The program will now terminate with an error message if the skin radius for a GWF connection in the MAW Package is less than or equal to the well radius. Warning messages are also issued when the well bottom, screen top for a GWF connection, or screen bottom for a GWF connection are reset by the program for one or more MAW Package wells. - \item An SFR reach can have zero specified connections. In this case, an entry is still required in the CONNECTIONDATA block for that reach. The error check for this required entry was not implemented and so the program would continue with unexpected results. The program now verifies that an entry is present in CONNECTIONDATA for every reach, even those with zero connections. - \item If a GWF ``NONE'' connection was specified for SFR then the program would terminate with an error or proceed with unexpected results if GWF or SFR flow terms were written to binary output files. The program was fixed so that GWF ``NONE'' connections for SFR are not written to the binary budget files. - \item Increase the length of boundname to its intended size of 40 characters. Boundnames were being truncated after 16 characters for the LAK, MAW, and SFR Packages. - \end{itemize} - - \underline{SOLUTION} - \begin{itemize} - \item A fix was implemented in the biconjugate gradient stabilized linear solver routine so that the maximum change in the dependent value is calculated and stored correctly. - \item Corrected the SIMPLE and COOLEY under-relaxation schemes in the Iterative Model Solution (IMS). The methods were not applying the correct under-relaxation factor. The SIMPLE scheme now uses the user-specified value for gamma as the factor. The COOLEY scheme updates the factor based on solver history. - \end{itemize} - - - - \item Version mf6.1.1--June 12, 2020 - - \underline{NEW FUNCTIONALITY} - \begin{itemize} - \item Refactor the source code to support the \href{https://csdms.colorado.edu/wiki/BMI_Description}{Basic Model Interface} (BMI) developed by the \href{https://csdms.colorado.edu/wiki/Main_Page}{Community Surface Dynamics Modeling System} (CSDMS) group. BMI is a set of standard control and query functions that, when added to a model code, make that model both easier to learn and easier to couple with other software elements \citep{PECKHAM20133}. Furthermore, the BMI makes it possible to control MODFLOW~6 execution from scripting languages using bindings for the BMI (for example, python bindings for the BMI available through \href{https://csdms.colorado.edu/wiki/PyMT}{pymt}). The BMI in this version is considered preliminary (alpha release). Limited testing of the BMI has been performed but significant changes are expected in future releases. User support for the MODFLOW 6 BMI may be provided in the future. - \item Add silent command line switch (\texttt{-s} or \texttt{\doubledash silent}) that sends all screen output (\texttt{STDOUT}) to a text file (with the name ``mfsim.stdout''). - \item Add screen output command line switch (\texttt{-l } or \texttt{\doubledash level }) that controls output to the screen (\texttt{STDOUT}). If \texttt{} is \texttt{summary}, stress period and time step data are not written to \texttt{STDOUT}. If \texttt{} is \texttt{debug}, normal and debug output are written to \texttt{STDOUT}. - \item Add simulation mode command line switch (\texttt{-m } or \texttt{\doubledash mode }) that controls the solution mode. If \texttt{} is \texttt{validate}, model input will be read and checked for errors, but the coefficient matrix or matrices will not be assembled or solved and solution output will not be written. - \item Add SAVE\_SATURATION option to the Node Property Flow Package. When invoked, cell saturation is written to the binary budget file as an auxiliary column for a record with the name ``DATA-SAT''. The cell saturation can be used by post-processors to determine how much of the cell is saturated without having to know the value for ICELLTYPE or the value for head. If a cell is marked as confined (ICELLTYPE=0) then saturation is always one. If ICELLTYPE is one, then saturation ranges between zero and one. - \item Add option for saving package convergence for the CSUB Package to a comma-separated values (CSV) file. Package convergence is enabled by specifying PACKAGE\_CONVERGENCE FILEOUT $<$package\_convergence\_filename$>$ in the options block for the package. - \item Add option for saving package convergence for the LAK, SFR, and UZF Packages to comma-separated values (CSV) files. Package convergence for the LAK, SFR, and UZF Packages is enabled by specifying PACKAGE\_CONVERGENCE FILEOUT $<$package\_convergence\_filename$>$ in the options block for the package. - \item Add CSV\_OUTER\_OUTPUT output option to save outer iteration information to a comma-separated values (CSV) file. The maximum of the model or package dependent-variable change for the outer iteration is written to the CSV file at the end of each outer iteration. - \item Add CSV\_INNER\_OUTPUT output option to save inner iteration information to a comma-separated values (CSV) file. The CSV output also the includes maximum dependent-variable change and maximum residual convergence information for the solution and each model (if the solution includes more than one model) and linear acceleration information for each inner iteration. The inner iteration CSV output, which contains a separate line for each inner iteration, is written to the CSV file all at once at the end of each outer iteration. - \item Add OUTER\_DVCLOSE and INNER\_DVCLOSE variables to replace OUTER\_HCLOSE and INNER\_HCLOSE variables in the IMS Package input file. ``DV'' is used now instead to more generally refer to dependent variable. Warning messages will be issued if OUTER\_HCLOSE and/or INNER\_HCLOSE variables are specified. OUTER\_HCLOSE and INNER\_HCLOSE variables will eventually be deprecated. - \item Add option to scale drain conductance as a function of simulated head over a user-defined range (drainage depth). Linear-conductance scaling is used with the Standard Formulation. Cubic-conductance scaling is used with the Newton-Raphson Formulation. The additional drainage depth variable is specified as an auxiliary variable and AUXDEPTHNAME is used to identify the auxiliary variable defining the drainage depth. The cubic-conductance scaling can be used as a replacement for the groundwater seepage option in the UZF Package. The scaled drainage conductance option can also be used to represent vertical seepage faces and improve model convergence in cells where simulated heads fluctuate around the elevation where the drain begins to discharge groundwater. - \item Add timeseries support for the reach upstream fraction variable in the SFR package. - \item Add Picard iterations for the SFR package to minimize differences in SFR package results between subsequent GWF Picard (non-linear) iterations as a result of non-optimal reach numbering. The number of SFR package Picard iterations can be controlled by specifying the maximum number of Picard iteration to be used in the OPTIONS block (MAXIMUM\_PICARD\_ITERATIONS). If reaches are numbered in order, from upstream to downstream, MAXIMUM\_PICARD\_ITERATIONS can be set to 1 to reduce model run time. Specifying MAXIMUM\_PICARD\_ITERATIONS to 1 will result in identical SFR package performance to previous versions of MODFLOW~6. - \item Add flow correction option for the MAW package that corrects the MAW-GWF exchange in cases where the head in a multi-aquifer well is below the bottom of the screen for a connection or the head in a convertible cell connected to a multi-aquifer well is below the cell bottom. When flow corrections are activated, unit head gradients are used to calculate the flow between a multi-aquifer well and a connected GWF cell. This option is identical to the MODFLOW-USG ``flow-to-dry-cell'' option for flow between a CLN cell and a GWF cell if the cell is convertible \citep{modflowusg}. Flow corrections are enabled by specifying FLOW\_CORRECTION in the OPTIONS block. By default, flow corrections are not made. \emph{Prior to this release (version 6.1.1), flow corrections were made anytime the head in a multi-aquifer well was below the bottom of the screen for a connection--this may result in different results for existing models that can be resolved by using the FLOW\_CORRECTION option.} - \item Add new document, ``MODFLOW 6 -- Supplemental Technical Information,'' to the doc folder. This document contains information that was in the mf6io.pdf appendices. This technical information document may expand with future versions as new features are added. - \end{itemize} - - \textbf{\underline{BUG FIXES AND OTHER CHANGES TO EXISTING FUNCTIONALITY}} \\ - \underline{BASIC FUNCTIONALITY} - \begin{itemize} - \item Correct an error in how the discretization package (for regular MODFLOW grids) calculates the distance between two cells when one or both of the cells are unconfined. The error in the code would have only affected XT3D simulations with a regular grid, unconfined conditions, and specification of ANGLE2 in the NPF Package. - \item Correct an error in the use of the AUXMULTNAME option for boundary packages when time series are used. A problem remains when time series are used for AUXMULTNAME but not for the column that is scaled by AUXMULTNAME. This situation should be avoided. - \end{itemize} - - \underline{STRESS PACKAGES} - \begin{itemize} - \item Fix a bug in binary budget file header for CSUB Package budget data written using IMETH=6 (CSUB-ELASTIC and CSUB-INELASTIC) . - \item Add information on the CSUB Package budget terms and compaction data written the the Input/Output document in the `Description of Groundwater Flow (GWF) Model Binary Output Files' section. - \item Prior to this release, calculated flows between a standard stress package (WEL, DRN, RIV, GHB, RCH, and EVT) and the connected model cell were based on the RHS and HCOF terms from the previous iteration. This was not consistent with previous MODFLOW versions. These packages were modified so that the flows are recalculated using the final converged head solution. As a result of this change, simulated groundwater flows for these packages may be slightly different (compared to previous releases) if the package HCOF and RHS values depend on the simulated groundwater head. - \end{itemize} - - \underline{ADVANCED STRESS PACKAGES} - \begin{itemize} - \item The code for saving the budget terms for the advanced packages was refactored to use common routines. These changes should have no affect on simulation results. - \item In previous releases, the LAK Package would accept negative user-input values for RAINFALL, EVAPORATION, RUNOFF, INFLOW, and WITHDRAWAL even though the user guide mentioned that negative values are not allowed for these flow terms. Error checks were added to ensure these values are specified as positive. - \item Add a storage term to the SFR Package binary output file. This term is always zero with the present implementation. An auxiliary variable, called VOLUME, is also written with the storage budget term. This term contains the calculated water volume in the reach. - \item Refactor the SFR Package to remove use of RectangularChGeometry objects and added required functionality as private methods in the SFR module. - \item Improve error trapping in the MAW Package to catch divide by zero errors when calculating the saturated conductance for wells using the SKIN CONDEQN in connections where the cell transmissivity (the product of geometric mean of the horizontal hydraulic conductivity and cell thickness) and well transmissivity (the product of HK\_SKIN and screen thickness) is equal to one. Also add error trapping for well connections using the 1) SKIN CONDEQN where the contrast between the cell and well transmissivities are less than one and 2) SKIN and MEAN CONDEQN where the calculated connection saturated conductance is less than zero. - \item For the Lake Package, the outlet number was written as ID1 and ID2 for the TO-MVR record in the binary budget file. This has been changed so that the lake number of the connected outlet is written to ID1 and ID2. This change was implemented so that lake budgets can be calculated using the information in the lake budget file. - \item The Lake, Streamflow Routing, and Multi-Aquifer Well Packages were modified to save the user-specified stage or head to the binary output file for lakes, reaches, or wells that are specified as being CONSTANT. Prior to this change, a no-flow value was written to the package binary output files for constant stage lakes and streams and constant head multi-aquifer wells. The no-flow value is still written for those lakes, streams, or wells that are specified by the user as being inactive. This change should make it easier to post-process the results from these packages. - \end{itemize} - - \underline{SOLUTION} - \begin{itemize} - \item Fix a bug in the linear solver when using the STRICT RCLOSE\_OPTION that prevented termination of inner iterations when the INNER\_DVCLOSE and INNER\_RCLOSE criteria were met but the inner iteration count was greater than one. The inner iterations are now terminated when the INNER\_DVCLOSE and INNER\_RCLOSE criteria are met but the linear solver is considered non-converged if the inner iteration count is greater than one. - \item Deprecate the CSV\_OUTPUT output option in the OPTIONS BLOCK because the output to the comma-separated values (CSV) file was based on the PRINT\_OPTION option. If CSV\_OUTPUT is specified, it is used to define the file name for the CSV\_OUTER\_OUTPUT output option. - \item Modify the outer iteration information written to the simulation listing file when PRINT\_OPTION is not NONE to improve the ability of users to evaluate model convergence. Added Package convergence data, eliminated dependent variable changes adjusted by under-relaxation, and flags to indicate when an outer iterations step is considered converged. Information is also provided if PTC causes non-convergence for a outer iteration (even if the model is converged) and if NEWTON UNDER\_RELAXATION resets outer iteration convergence from FALSE to TRUE. Dependent-variable changes for the under-relaxation step in an outer iteration are no longer reported because under-relaxation is only applied if the model or package outer iteration steps do not converge and by definition reduce dependent-variable changes and are not used to evaluate outer iteration convergence. - \item Deprecate the OUTER\_RCLOSEBND optional variable in the NONLINEAR BLOCK because OUTER\_DVCLOSE is used for all terms used to evaluate package convergence. An warning will be issued if OUTER\_RCLOSEBND is specified. - \item Deprecate the CSV\_OUTPUT output option. A warning will be issued if the CSV\_OUTPUT option is specified and outer iteration information will be saved to the specified FILEOUT comma-separated values (CSV) file. - \end{itemize} - -\end{itemize} - -\begin{itemize} - \item Version mf6.1.0--December 12, 2019 - - \underline{NEW FUNCTIONALITY} - \begin{itemize} - \item Added the Skeletal Storage, Compaction, and Subsidence (CSUB) Package. The one-dimensional effective-stress based compaction theory implemented in the CSUB Package is documented in \cite{leake2007modflow}. The numerical approach used for delay interbeds in the CSUB package is documented in \cite{hoffmann2003modflow} and uses the same one-dimensional effective-stress based compaction theory as coarse-grained and fine-grained no-delay interbed sediments. A number of example problems that use the CSUB Package are documented in the ``MODFLOW 6 CSUB Package Example Problems'' pdf document included in this and subsequent releases. - \end{itemize} - - \underline{BASIC FUNCTIONALITY} - \begin{itemize} - \item Added an error check to the DISU Package that ensures that an underlying cell has a top elevation that is less than or equal to the bottom of an overlying cell. An underlying cell is one in which the IHC value for the connection is zero and the connecting node number is greater than the cell node number. - \item Added restricted IDOMAIN support for DISU grids. Users can specify an optional IDOMAIN in the DISU Package input file. IDOMAIN values must be zero or one. Vertical pass-through cells (specified with an IDOMAIN value of -1 in the DIS or DISV Package input files) are not supported for DISU. - \item NPF Package will now write a message to the GWF Model list file to indicate when the SAVE\_SPECIFIC\_DISCHARGE option is invoked. - \item Added two new options to the NPF Package. The K22OVERK option allows the user to enter the anisotropy ratio for K22. If activated, the K22 values entered by the user in the NPF input file will be multiplied by the K values entered in the NPF input file. The K33OVERK option allows the user to enter the anisotropy ratio for K33. If activated, the K33 values entered by the user in the NPF input file will be multiplied by the K values entered in the NPF input file. With this K33OVERK option, for example, the user can specify a value of 0.1 for K33 and have all K33 values be one tenth of the values specified for K. The program will terminate with an error if these options are invoked, but arrays for K22 and/or K33 are not provided in the NPF input file. - \item Added new MAXERRORS option to mfsim.nam. If specified, the maximum number of errors stored and printed will be limited to this number. This can prevent a situation where memory will run out when there are an excessive number of errors. - \item Refactored many parts of the code to remove unused variables, conform to stricter FORTRAN standard checks, and allow for new development efforts to be included in the code base. - \end{itemize} - - \underline{STRESS PACKAGES} - \begin{itemize} - \item There was an error in the calculation of the segmented evapotranspiration rate for the case where the rate did not decrease with depth. There was another error in which PETM0 was being used as the evapotranspiration rate at the surface instead of the proportion of the evapotranspiration rate at the surface. - \end{itemize} - - \underline{ADVANCED STRESS PACKAGES} - \begin{itemize} - \item Corrected the way auxiliary variables are handled for the advanced packages. In some cases, values for auxiliary variables were not being correctly written to the GWF Model budget file or to the advanced package budget file. A consistent approach for updating and saving auxiliary variables was implemented for the MAW, SFR, LAK, and UZF Packages. - \item The user guide was updated to include a missing laksetting that was omitted from the PERIOD block. The laksetting description now includes an INFLOW option; a description for INFLOW is also now included. - \item The LAK package was incorrectly making an error check against NOUTLETS instead of NLAKES. - \item For the advanced stress packages, values assigned to the auxiliary variables were not written correctly to the GWF Model budget file, but the values were correct in the advanced package budget file. Program was modified so that auxiliary variables are correctly written to the GWF Model budget file. - \item Corrected several error messages issued by the SFR Package that were not formatted correctly. - \item Fixed a bug in which the lake stage stable would sometimes result in touching numbers. This only occurred for negative lake stages. - \item The UZF Package was built on the UZFKinematicType, which used an array of structures. A large array like this, can cause memory problems. The UZFKinematicType was replaced with a new UzfCellGroupType, which is a structure of arrays and is much more memory efficient. The underlying UZF algorithm did not change. - \end{itemize} - - \underline{SOLUTION} - \begin{itemize} - \item Add ALL and FIRST options to optional NO\_PTC optional keyword in OPTIONS block. If NO\_PTC option is FIRST, PTC is disabled for the first stress period but is applied in all subsequent steady-state stress periods. If NO\_PTC option is ALL, PTC is disabled for all steady-state stress periods. If the NO\_PTC options is not defined, PTC is disabled for all steady-state stress periods (this is consistent with the behaviour of the NO\_PTC option in previous versions). - \end{itemize} - - \item Version mf6.0.4--Feb. 27, 2019 - - \underline{BASIC FUNCTIONALITY} - \begin{itemize} - \item Addressed issue with pointing contiguous pointer vectors/arrays to non-contiguous pointer vectors/arrays that caused code compilation failure with gfortran-8. A consequence of addressing this issue is that all pointer vectors/arrays that are allocated or pointed to using the memory manager must be defined to be contiguous. - \item Corrected a problem with the reading of grid data from a binary file, in which the program was reading a binary header for each row of data. - \item Added a new error check for very small time steps. If the value of the starting time is equal to the ending time (starting time plus the time step length), then the time step is too small to be differentiated by the program based on the precision of floating point numbers. The program will terminate with an error in this case. The program will also terminate if the storage package with a transient stress period has a time step length of zero. - \item The observation package was modified to use non-advancing output instead of fixed length strings when writing ascii output. The previous use of fixed length strings resulted in truncation of ascii observation output when the product of user-specified \texttt{digits} + 7 and the number of observations exceeded 5000. - \item Corrected an error in the GWF-GWF Exchange module that caused the specific discharge values in the child model to be calculated incorrectly. The calculation was incorrect because the face normal for the child model was pointing toward the center of the cell instead of outward. - \item Minor refactoring to improve code clarity. - \end{itemize} - - \underline{STRESS PACKAGES} - \begin{itemize} - \item Minor refactoring to improve code clarity. - \end{itemize} - - \underline{ADVANCED STRESS PACKAGES} - \begin{itemize} - \item Modified the Multi-Aquifer Well (MAW) Package so that the HEAD\_LIMIT and RATE\_SCALING options work for injection wells. Prior to this change, these options only worked for extraction wells. These options can be used to reduce or even shut off well injection as the head in the well rises above user-specified levels. - \item Added stage and residual convergence checks to the SFR package to make sure that stage and upstream flow changes between successive outer iterations are less than OUTER\_HCLOSE and OUTER\_RCLOSEBND, respectively. This addition is expected to be useful for steady-state simulations with complicated networks and simple reaches. - \item Modified the final convergence check for the LAK package to use OUTER\_HCLOSE when evaluating lake stage changes between successive outer iterations. - \item Modified the final convergence check for the UZF package to use OUTER\_RCLOSEBND when evaluating rejected infiltration, groundwater recharge, and groundwater seepage changes between successive outer iterations. - \item Minor refactoring to improve code clarity. - \end{itemize} - - \underline{SOLUTION} - \begin{itemize} - \item Modified pseudo-transient continuation (PTC) approach to use PTC for steady-state stress period for models using the Newton-Raphson formulation for problems with and without the storage (STO) package. Previously, PTC was only used with problems that did not include the STO package (this was not the intended behavior of PTC). - \item Added NO\_PTC option to disable PTC for problems where PTC degrades/prevents model convergence. Option only applies to steady-state stress periods for models using the Newton-Raphson formulation. For many problems, PTC can significantly improve convergence behavior for steady-state simulations, and for this reason it is active by default. In some cases, however, PTC can worsen the convergence behavior, especially when the initial conditions are similar to the solution. When the initial conditions are similar to, or exactly the same as, the solution and convergence is slow, then this NO\_PTC option should be used to deactivate PTC. This NO\_PTC option should also be used in order to compare convergence behavior with other MODFLOW versions, as PTC is only available in MODFLOW~6. - \item Small improvements to PTC to reduce the initial PTCDEL value loaded on the diagonal. This reduces the number of iterations required to achieve convergence for steady-state stress periods for most problems. - \item Added OUTER\_RCLOSEBND variable that is used when performing final convergence checks on model packages that solve a separate equation not solved by the IMS linear solver. This value represents the maximum allowable residual at any single model package element between successive outer iterations. An example of a model package that would use OUTER\_RCLOSEBND to evaluate convergence is the SFR package which solves a continuity equation for each reach. - \item Minor refactoring to improve code clarity. - \end{itemize} - - \item Version mf6.0.3--Aug. 9, 2018 - - \underline{BASIC FUNCTIONALITY} - \begin{itemize} - \item Fixed issues with observations specified using boundnames that are enclosed in quotes. Previously, the closing quote was retained on a boundname enclosed in quotes and resulted in an error (the erroneous observation boundname could not be found in the package). - \end{itemize} - - \underline{STRESS PACKAGES} - \begin{itemize} - \item If the AUXMULTNAME keyword was used in combination with time series, then the multiplier was erroneously applied to all time series, and not just the time series in the column to be scaled. - \item For the array-based recharge and evapotranspiration packages, the IRCH and IEVT variables (if specified) must be specified as the first variable listed in the PERIOD block. A check was added so that the program will terminate with an error if IRCH or IEVT is not the first variable listed in the PERIOD block. - \item For the standard boundary packages, the ``to mover'' term (such as DRN-TO-MVR) written to the GWF Model budget was incorrect. The budget terms were incorrect because the accumulator variables were not initialized to zero. - \item For regular MODFLOW grids, the recharge and evapotranspiration arrays of size (NCOL, NROW) were being echoed to the listing file (if requested by the user) of size (NCOL * NROW). - \end{itemize} - - \underline{ADVANCED STRESS PACKAGES} - \begin{itemize} - \item Fixed spelling of the THIEM keyword in the source code and in the input instructions of the MAW Package. - \item Fixed an issue with the SFR package when the specified evaporation exceeds the sum of specified and calculated reach inflows, rainfall, and specified runoff. In this case, evaporation is set equal to the sum of specified and calculated reach inflows, rainfall, and specified runoff. Also if a negative runoff is specified and this value exceeds specified and calculated reach inflows, and rainfall then runoff is set to the sum of reach inflows and evaporation is set to zero. - \item Fixed an issue in the MAW package budget information written to the listing file and MAW cell-by-cell budget file when a previously active well is inactivated. The ratesim variable was not being reset to zero for these wells and the simulated rate from the last stress period when the well was active was being reported. - \item Program now terminates with an error if the OUTLETS block is present in the LAK package file and NOUTLETS is not specified or specified to be zero in the DIMENSIONS block. Previously, this did not cause an error condition in the LAK package but would result in a segmentation fault error in the MVR package if LAK package OUTLETS are specified as providers. - \item Program now terminates with an error when a DIVERSION block is present in a SFR package file but no diversions (all ndiv values are 0) are specified in the PACKAGEDATA block. - \end{itemize} - - \underline{SOLUTION} - \begin{itemize} - \item Fixed bug related to not allocating the preconditioner work array if a non-zero drop tolerance is specified but the number of levels is not specified or specified to be zero. In the case where the number of levels is not specified or specified to be zero the preconditioner work array is dimensioned to the product of the number of cells (NEQ) and the maximum number of connections for any cell. - \item Updated linear solver output so number of levels and drop tolerance are output if either are specified to be greater than zero. - \end{itemize} - - \item Version mf6.0.2--Feb 23, 2018 - - \underline{BASIC FUNCTIONALITY} - \begin{itemize} - \item Added a new option, called SAVE\_SPECIFIC\_DISCHARGE to the Node Property Flow Package. When invoked, $x$, $y$, and $z$ specific discharge components are calculated for the center of each model cell and written to the binary budget file. - \item For binary input of grid data, such as initial heads, the array reading utility was not reading a header record consisting of KSTP, KPER, PERTIM, TOTIM, TEXT, NLAY, NROW, NCOL. This meant that a binary head file written by MODFLOW could not be used as input for a subsequent simulation. For binary input, the array reading utility now reads a header record before reading the array values. - \item The NOGRB option in the discretization packages was not working. This option will now prevent the binary grid file from being written. - \item Removed the PRIVATE attribute for two methods of the discretization packages so that the program works as intended with the latest Intel Fortran release. - \item Switched to using a long integer for the memory manager so that memory usage is calculated correctly for large models. - \end{itemize} - - \underline{STRESS PACKAGES} - \begin{itemize} - \item If a steady-state stress period followed a transient stress period, the storage terms written to the budget file were not being reset to zero. The program now initializes these budget values to zero for steady-state periods before they are written. - \end{itemize} - - \underline{ADVANCED STRESS PACKAGES} - \begin{itemize} - \item The STATUS INACTIVE option was not working correctly for the MAW Package. - \item Modified the MAW connection conductance calculation so that a linear relation between the water level in a cell and saturation is used for the standard formulation. In the previous version, the same quadratic saturation function was being used for the standard and Newton-Raphson formulation to calculate the MAW connection conductance. - \item Modified the MAW Package so that the top and bottom of the screen for a connection are reset to the top and bottom of the cell, respectively, for SPECIFIED, THEIM, SKIN, and CUMULATIVE conductance equations (CONDEQN). Also, the program will now terminate with an error if a MAW well using SPECIFIED, THEIM, SKIN, or CUMULATIVE conductance equations has more than one connection to a single GWF cell. - \item Modified the MAW package so that the well bottom (BOTTOM) is reset to the cell bottom in the lowermost GWF cell connection in cases where the specified well bottom is above the bottom of this GWF cell. - \end{itemize} - - \underline{SOLUTION} - \begin{itemize} - \item Prior to applying pseudo transient continuation terms, the Iterative Model Solution confirms that the L2-norm exceeds the previous L2-norm. If it doesn't then pseudo transient continuation is turned off. This fixes a rare situation in which convergence could not be achieved for consecutive steady state solutions with the same or similar answers. - \end{itemize} - - - \item Version mf6.0.1--Sep 28, 2017 - - \underline{BASIC FUNCTIONALITY} - \begin{itemize} - \item There is no requirement that FTYPE entries in the GWF name file should be upper case; however, an upper case convention was being enforced. FTYPE entries can now be specified using any case. - \item Tab characters within model input files were not being skipped correctly. This has been fixed. - \item The program was updated to use the ``approved for release'' disclaimer. The previous version was still using a ``preliminary software'' disclaimer. - \item The source code for time series and time array series was refactored. Included in the refactoring was a correction to time array series to allow the time array to change from one stress period to the next. The source file TimeSeriesGroupList.f90 was renamed to TimeSeriesFileList.f90. - \end{itemize} - - \underline{STRESS PACKAGES} - \begin{itemize} - \item Fixed inconsistency with CHD package observation name in code (\texttt{chd-flow}) and name in the input-output document (\texttt{chd}). Using name defined in input-output document (\texttt{chd}). - \item The cell area was not being used in the calculation of recharge and evapotranspiration when list input was used with time series. - \item The AUXMULTNAME option was not being applied for recharge and evapotranspiration when the READASARRAYS option was used. - \item The program was not terminating with an error if a PERIOD block was encountered with an iper value equal to the previous iper value. Program now terminates with an error. - \end{itemize} - - \underline{ADVANCED STRESS PACKAGES} - \begin{itemize} - \item Fixed incorrect sign for SFR package exchange with GWF model (\texttt{sfr}). - \item Added option to specify \texttt{none} as the \texttt{bedleak} for a lake-\texttt{GWF} connection in lake (LAK) package. This option makes the lake-\texttt{GWF} connection conductance solely a function of aquifer properties in the connected \texttt{GWF} cell and lakebed sediments are assumed to be absent for this connection. - \item Fixed bug in lake (LAK) and multi-aquifer well (MAW) packages that only reset steady-state flag if lake and/or multi-aquifer data are read for a stress period (in the pak\_rp() routines). Using pointer to GWF iss variable in the LAK package and resetting the MAW steady state flag in maw\_rp() routine every stress period, regardless of whether MAW data are specified for a stress period. - \item Added a convergence check routine to the GWF Mover Package that requires at least two outer iterations if there are any active movers. Because mover rates are lagged by one outer iteration, at least two outer iterations are required for some problems. - \item Changed the behavior of the LAK Package so that recharge and evapotranspiration are applied to a vertically connected GWF model cell if the lake status is INACTIVE. Prior to this change, recharge and evapotranspiration were only applied to an underlying GWF model cell if the lake was dry. - \end{itemize} - - \underline{SOLUTION} - \begin{itemize} - \item Fixed bug in IMS that allowed convergence when outer iteration HCLOSE value was satisfied but the model did not converge during the inner iterations. - \item Added STRICT rclose\_option that uses a infinity-Norm RCLOSE criteria but requires HCLOSE and RCLOSE be satisfied on the first inner iteration of an outer iteration. The STRICT option is identical to the closure criteria approach use in the PCG Package in MODFLOW-2005. - \end{itemize} - - \underline{EXCHANGES} - \begin{itemize} - \item Use of an OPEN/CLOSE file was not being allowed for the OPTIONS and DIMENSIONS blocks of the GWF6-GWF6 exchange input file. OPEN/CLOSE input is now allowed for both of these blocks. - \end{itemize} - - \item - Version mf6.0.0---August 10, 2017 - - \underline{BASIC FUNCTIONALITY} - \begin{itemize} - \item Removed support for the SINGLE observation type. All observations must be CONTINUOUS, which means observation values are written for every time step. - \item Added support for a no-data value (3.0E30), which can be used as a placeholder in a time-series file containing multiple time series. Use of the no-data value facilitates combining separate time series into a single file when the time series contain records for differing simulation times. - \item Model names specified in the simulation name file cannot have spaces in them. A check was implemented to terminate with an error if the model name contains spaces. Model names cannot exceed 16 characters. Trailing spaces are allowed. - \item The name and version of the compiler used to make the run file is now written to the terminal and to the simulation list file. - \item Many of the Fortran source files were modified and reformatted. Unused variables were removed. - \end{itemize} - - \underline{ADVANCED STRESS PACKAGES} - \begin{itemize} - \item Updated MAW package so that well connection conductance calculations correctly account for THICKSTRT in the NPF package for layers that use THICKSTRT (and are confined). - \item Added \texttt{CUMULATIVE} \texttt{coneqn} (conductance) option to MAW package. - \item Fixed bug in LAK package weir lake outlet calculation. - \item Fixed bug in LAK package when internal outlets were specified and combined with the MVR package that was also moving water internally in the same LAK package. - \item Updated the table created when PRINT\_FLOWS is specified in the LAK package OPTIONS block to include internal flow terms if NOUTLETS is greater than 0. - \item Renamed Lake Tables DIMENSIONS block NENTRIES to NROW and added NCOL to DIMENSIONS block. - \item Eliminated MAXIMUM\_OUTLET\_DEPTH = 10 [L] as default behavior for MANNING and WEIR LAK package lake outlet types. The maximum depth threshold was used in MODFLOW-2005 lake package because a table was used to calculate lake outflows to SFR. Can still use maximum depth threshold in develop versions of MODFLOW~6 by specifying MAXIMUM\_OUTLET\_DEPTH in the options block with a value. - \item Removed MULTILAYER option for UZF package---this option didn't actually do anything. - \item Added the requirement that the UZF number be specified as the first value on each line in the PACKAGEDATA block. - \item Renamed MAXBOUND in the DIMENSIONS block of the SFR Package to be NREACHES. - \item Implemented a check in the SFR Package to make sure that information is specified in the PACKAGEDATA block for every reach. Program terminates with an error if information for a reach is not found. - \end{itemize} - - \item - Version mf6beta0.9.03---June 23, 2017 - - \underline{BASIC FUNCTIONALITY} - \begin{itemize} - \item Renamed all FTYPE keywords to version 6. They were named with an 8. So, for example, the GHB Package is now activated in the GWF name file using ``GHB6'' instead of ``GHB8''. - \item Keywords in the simulation name file must now be specified as TDIS6, GWF6, and GWF6-GWF6 to be consistent. - \item The DIS Package had grid offsets (XOFFSET and YOFFSET) that could be specified as options. These offsets were relative to the upper-left corner of the model grid. The default value for YOFFSET was set to the sum of DELR so that (0, 0) would correspond to the lower-left corner of the model grid. These options have been removed and replaced with XORIGIN and YORIGIN, which is the coordinate of the lower-left corner of the model grid. The default value is zero for XORIGIN and YORIGIN. - \item Can now specify XORIGIN, YORIGIN, and ANGROT as options for the DISV and DISU packages. These values are written to the binary grid file, which can be used by post-processors to locate the model grid in space. These options have no affect on the simulation results. The default value is 0.0 if not specified. - \item Added a new option to the TDIS input file called START\_DATE\_TIME. This is a 30 character string that represents the simulation starting date and time, preferably in the format described at https://www.w3.org/TR/NOTE-datetime. The value provided by the user has no affect on the simulation, but if it is provided, the value is written to the simulation list file. - \item Changed default behavior for how memory usage is written to the end of the simulation list file. Added new MEMORY\_PRINT\_OPTION to simulation options to control how memory usage is written. - \item Corrections were made to the memory manager to ensure that all memory is deallocated at the end of a simulation. - \end{itemize} - - \underline{INTERNAL FLOW PACKAGES} - \begin{itemize} - \item Changed the way hydraulic conductivity is specified in the NPF Package. Users no longer specify HK, VK, and HANI. Hydraulic conductivity is now specified as ``K''. If hydraulic conductivity is isotropic, then this is all that needs to be specified. For anisotropic cases, the user can specify an optional ``K22'' array and an optional ``K33'' array. For an unrotated conductivity ellipsoid ``K22'' corresponds to hydraulic conductivity in the y direction and ``K33'' corresponds to hydraulic conductivity in the z direction, respectively. - \end {itemize} - - \underline{ADVANCED STRESS PACKAGES} - \begin{itemize} - \item Modified the MAW Package to include the effects of aquifer anisotropy in the calculation of conductance. - \item Simplified the SFR Package connectivity to reflect feedback from beta users. There is no longer a requirement to connect reaches that do not have flow between them. Program will now terminate with an error if this condition is encountered. - \item Added simple routing option to SFR package. This is the equivalent of the specified depth option (icalc=0) in previous versions of MODFLOW. If water is available in the reach, then there can be leakage from the SFR reach into the aquifer. If no water is available, then no leakage is applied. STAGE keyword also added and only applies to reaches that use the simple routing option. If the STAGE keyword is not specified for reaches that use the simple routing option the specified stage is set to the top of the reach (depth = 0). - \item Added functionality to pass SFR leakage to the aquifer to the highest active layer. - \item Converted SFR Manning's to a time-varying, time series aware variable. - \item Updated LAK package so that conductance calculations correctly account for THICKSTRT in the NPF package for layers that use THICKSTRT (and are confined). Also updated EMBEDDEDH and EMBEDDEDV so that the conductance for these connection types are constant for confined layers. - \item Converted UZF stress period data to time series aware data. - \item Added time-series aware AUXILIARY variables to UZF package. - \item Implemented AUXMULTNAME in options block for UZF package (AUXILIARY variables have to be specified). AUXMULTNAME is applied to the GWF cell area and is used to simulated more than one UZF cell per GWF cell. This could be used to simulate different land use classifications (i.e., agricultural and natural land use types) in the same GWF cell. - \end{itemize} - - \underline{SOLUTION} - \begin{itemize} - \item Reworked IMS convergence information so that model specific convergence information is also printed to each model listing file when PRINT\_OPTION ALL is specified in the IMS OPTIONS block. - \item Added csv output option for IMS convergence information. Solution convergence information and model specific convergence information (if the solution includes more than one model) is written to a comma separated value file. If PRINT\_OPTION is NONE or SUMMARY, csv output includes maximum head change convergence information at the end of each outer iteration for each time step. If PRINT\_OPTION is ALL, csv output includes maximum head change and maximum residual convergence information for the solution and each model (if the solution includes more than one model) and linear acceleration information for each inner iteration. - \end{itemize} - - \item - Version mf6beta0.9.02---May 19, 2017 - \begin{itemize} - \item Renamed gwf3.f90 to be lower case. - \item Added the missing ``divrate'' variable to the ``sfrsetting'' description in mf6io.pdf. - \item Added additional error trapping to the array reading utilities. - \item There was a problem with the binary budget file when a GWF Exchange was used to connect a GWF Model with itself. This has been fixed. - \item Standardized `\texttt{to-mvr}' cell-by-cell item in standard stress packages and UZF package. - \item Fixed incorrect `\texttt{UZF-EVT}' budget accumulator used in GWF listing budget. - \item Standardized justification of cell-by-cell `\texttt{text}' strings. - \item Standardized use of AUXILIARY keyword. - \end{itemize} - - \item - Version mf6beta0.9.01---May 11, 2017 - \begin{itemize} - \item Added a copy of the third MODFLOW~6 report. - \item Made several minor corrections to doc/mf6io.pdf. - \item If vertices were specified for DISU, then the last header line was not written to the binary grid file. This has been corrected. - \end{itemize} - - \item - Version mf6beta0.9.00---May 10, 2017 - \begin{itemize} - \item First public release of MODFLOW~6 in beta form. - \end{itemize} - -\end{itemize} \ No newline at end of file + \input{./previous/v6.4.1.tex} + \input{./previous/v6.4.0.tex} + \input{./previous/v6.3.0.tex} + \input{./previous/v6.2.2.tex} + \input{./previous/v6.2.1.tex} + \input{./previous/v6.2.0.tex} + \input{./previous/v6.1.1.tex} + \input{./previous/v6.1.0.tex} + \input{./previous/v6.0.4.tex} + \input{./previous/v6.0.3.tex} + \input{./previous/v6.0.2.tex} + \input{./previous/v6.0.1.tex} + \input{./previous/v6.0.0.tex} + \input{./previous/v0.9.03.tex} + \input{./previous/v0.9.02.tex} + \input{./previous/v0.9.01.tex} + \input{./previous/v0.9.00.tex} + \ No newline at end of file diff --git a/doc/ReleaseNotes/previous/v0.9.00.tex b/doc/ReleaseNotes/previous/v0.9.00.tex new file mode 100644 index 00000000000..6c6b102c354 --- /dev/null +++ b/doc/ReleaseNotes/previous/v0.9.00.tex @@ -0,0 +1,4 @@ + \subsection{Version mf6beta0.9.00---May 10, 2017} + \begin{itemize} + \item First public release of MODFLOW~6 in beta form. + \end{itemize} diff --git a/doc/ReleaseNotes/previous/v0.9.01.tex b/doc/ReleaseNotes/previous/v0.9.01.tex new file mode 100644 index 00000000000..637127153c2 --- /dev/null +++ b/doc/ReleaseNotes/previous/v0.9.01.tex @@ -0,0 +1,6 @@ + \subsection{Version mf6beta0.9.01---May 11, 2017} + \begin{itemize} + \item Added a copy of the third MODFLOW~6 report. + \item Made several minor corrections to doc/mf6io.pdf. + \item If vertices were specified for DISU, then the last header line was not written to the binary grid file. This has been corrected. + \end{itemize} diff --git a/doc/ReleaseNotes/previous/v0.9.02.tex b/doc/ReleaseNotes/previous/v0.9.02.tex new file mode 100644 index 00000000000..e75069f5e89 --- /dev/null +++ b/doc/ReleaseNotes/previous/v0.9.02.tex @@ -0,0 +1,11 @@ + \subsection{Version mf6beta0.9.02---May 19, 2017} + \begin{itemize} + \item Renamed gwf3.f90 to be lower case. + \item Added the missing ``divrate'' variable to the ``sfrsetting'' description in mf6io.pdf. + \item Added additional error trapping to the array reading utilities. + \item There was a problem with the binary budget file when a GWF Exchange was used to connect a GWF Model with itself. This has been fixed. + \item Standardized `\texttt{to-mvr}' cell-by-cell item in standard stress packages and UZF package. + \item Fixed incorrect `\texttt{UZF-EVT}' budget accumulator used in GWF listing budget. + \item Standardized justification of cell-by-cell `\texttt{text}' strings. + \item Standardized use of AUXILIARY keyword. + \end{itemize} diff --git a/doc/ReleaseNotes/previous/v0.9.03.tex b/doc/ReleaseNotes/previous/v0.9.03.tex new file mode 100644 index 00000000000..9ec31b8e9c4 --- /dev/null +++ b/doc/ReleaseNotes/previous/v0.9.03.tex @@ -0,0 +1,37 @@ + \subsection{Version mf6beta0.9.03---June 23, 2017} + + \underline{BASIC FUNCTIONALITY} + \begin{itemize} + \item Renamed all FTYPE keywords to version 6. They were named with an 8. So, for example, the GHB Package is now activated in the GWF name file using ``GHB6'' instead of ``GHB8''. + \item Keywords in the simulation name file must now be specified as TDIS6, GWF6, and GWF6-GWF6 to be consistent. + \item The DIS Package had grid offsets (XOFFSET and YOFFSET) that could be specified as options. These offsets were relative to the upper-left corner of the model grid. The default value for YOFFSET was set to the sum of DELR so that (0, 0) would correspond to the lower-left corner of the model grid. These options have been removed and replaced with XORIGIN and YORIGIN, which is the coordinate of the lower-left corner of the model grid. The default value is zero for XORIGIN and YORIGIN. + \item Can now specify XORIGIN, YORIGIN, and ANGROT as options for the DISV and DISU packages. These values are written to the binary grid file, which can be used by post-processors to locate the model grid in space. These options have no affect on the simulation results. The default value is 0.0 if not specified. + \item Added a new option to the TDIS input file called START\_DATE\_TIME. This is a 30 character string that represents the simulation starting date and time, preferably in the format described at https://www.w3.org/TR/NOTE-datetime. The value provided by the user has no affect on the simulation, but if it is provided, the value is written to the simulation list file. + \item Changed default behavior for how memory usage is written to the end of the simulation list file. Added new MEMORY\_PRINT\_OPTION to simulation options to control how memory usage is written. + \item Corrections were made to the memory manager to ensure that all memory is deallocated at the end of a simulation. + \end{itemize} + + \underline{INTERNAL FLOW PACKAGES} + \begin{itemize} + \item Changed the way hydraulic conductivity is specified in the NPF Package. Users no longer specify HK, VK, and HANI. Hydraulic conductivity is now specified as ``K''. If hydraulic conductivity is isotropic, then this is all that needs to be specified. For anisotropic cases, the user can specify an optional ``K22'' array and an optional ``K33'' array. For an unrotated conductivity ellipsoid ``K22'' corresponds to hydraulic conductivity in the y direction and ``K33'' corresponds to hydraulic conductivity in the z direction, respectively. + \end {itemize} + + \underline{ADVANCED STRESS PACKAGES} + \begin{itemize} + \item Modified the MAW Package to include the effects of aquifer anisotropy in the calculation of conductance. + \item Simplified the SFR Package connectivity to reflect feedback from beta users. There is no longer a requirement to connect reaches that do not have flow between them. Program will now terminate with an error if this condition is encountered. + \item Added simple routing option to SFR package. This is the equivalent of the specified depth option (icalc=0) in previous versions of MODFLOW. If water is available in the reach, then there can be leakage from the SFR reach into the aquifer. If no water is available, then no leakage is applied. STAGE keyword also added and only applies to reaches that use the simple routing option. If the STAGE keyword is not specified for reaches that use the simple routing option the specified stage is set to the top of the reach (depth = 0). + \item Added functionality to pass SFR leakage to the aquifer to the highest active layer. + \item Converted SFR Manning's to a time-varying, time series aware variable. + \item Updated LAK package so that conductance calculations correctly account for THICKSTRT in the NPF package for layers that use THICKSTRT (and are confined). Also updated EMBEDDEDH and EMBEDDEDV so that the conductance for these connection types are constant for confined layers. + \item Converted UZF stress period data to time series aware data. + \item Added time-series aware AUXILIARY variables to UZF package. + \item Implemented AUXMULTNAME in options block for UZF package (AUXILIARY variables have to be specified). AUXMULTNAME is applied to the GWF cell area and is used to simulated more than one UZF cell per GWF cell. This could be used to simulate different land use classifications (i.e., agricultural and natural land use types) in the same GWF cell. + \end{itemize} + + \underline{SOLUTION} + \begin{itemize} + \item Reworked IMS convergence information so that model specific convergence information is also printed to each model listing file when PRINT\_OPTION ALL is specified in the IMS OPTIONS block. + \item Added csv output option for IMS convergence information. Solution convergence information and model specific convergence information (if the solution includes more than one model) is written to a comma separated value file. If PRINT\_OPTION is NONE or SUMMARY, csv output includes maximum head change convergence information at the end of each outer iteration for each time step. If PRINT\_OPTION is ALL, csv output includes maximum head change and maximum residual convergence information for the solution and each model (if the solution includes more than one model) and linear acceleration information for each inner iteration. + \end{itemize} + diff --git a/doc/ReleaseNotes/previous/v6.0.0.tex b/doc/ReleaseNotes/previous/v6.0.0.tex new file mode 100644 index 00000000000..7aae6f3a54d --- /dev/null +++ b/doc/ReleaseNotes/previous/v6.0.0.tex @@ -0,0 +1,26 @@ + \subsection{Version mf6.0.0---August 10, 2017} + + \underline{BASIC FUNCTIONALITY} + \begin{itemize} + \item Removed support for the SINGLE observation type. All observations must be CONTINUOUS, which means observation values are written for every time step. + \item Added support for a no-data value (3.0E30), which can be used as a placeholder in a time-series file containing multiple time series. Use of the no-data value facilitates combining separate time series into a single file when the time series contain records for differing simulation times. + \item Model names specified in the simulation name file cannot have spaces in them. A check was implemented to terminate with an error if the model name contains spaces. Model names cannot exceed 16 characters. Trailing spaces are allowed. + \item The name and version of the compiler used to make the run file is now written to the terminal and to the simulation list file. + \item Many of the Fortran source files were modified and reformatted. Unused variables were removed. + \end{itemize} + + \underline{ADVANCED STRESS PACKAGES} + \begin{itemize} + \item Updated MAW package so that well connection conductance calculations correctly account for THICKSTRT in the NPF package for layers that use THICKSTRT (and are confined). + \item Added \texttt{CUMULATIVE} \texttt{coneqn} (conductance) option to MAW package. + \item Fixed bug in LAK package weir lake outlet calculation. + \item Fixed bug in LAK package when internal outlets were specified and combined with the MVR package that was also moving water internally in the same LAK package. + \item Updated the table created when PRINT\_FLOWS is specified in the LAK package OPTIONS block to include internal flow terms if NOUTLETS is greater than 0. + \item Renamed Lake Tables DIMENSIONS block NENTRIES to NROW and added NCOL to DIMENSIONS block. + \item Eliminated MAXIMUM\_OUTLET\_DEPTH = 10 [L] as default behavior for MANNING and WEIR LAK package lake outlet types. The maximum depth threshold was used in MODFLOW-2005 lake package because a table was used to calculate lake outflows to SFR. Can still use maximum depth threshold in develop versions of MODFLOW~6 by specifying MAXIMUM\_OUTLET\_DEPTH in the options block with a value. + \item Removed MULTILAYER option for UZF package---this option didn't actually do anything. + \item Added the requirement that the UZF number be specified as the first value on each line in the PACKAGEDATA block. + \item Renamed MAXBOUND in the DIMENSIONS block of the SFR Package to be NREACHES. + \item Implemented a check in the SFR Package to make sure that information is specified in the PACKAGEDATA block for every reach. Program terminates with an error if information for a reach is not found. + \end{itemize} + \ No newline at end of file diff --git a/doc/ReleaseNotes/previous/v6.0.1.tex b/doc/ReleaseNotes/previous/v6.0.1.tex new file mode 100644 index 00000000000..210e99bc9ff --- /dev/null +++ b/doc/ReleaseNotes/previous/v6.0.1.tex @@ -0,0 +1,38 @@ + + \subsection{Version mf6.0.1--Sep 28, 2017} + + \underline{BASIC FUNCTIONALITY} + \begin{itemize} + \item There is no requirement that FTYPE entries in the GWF name file should be upper case; however, an upper case convention was being enforced. FTYPE entries can now be specified using any case. + \item Tab characters within model input files were not being skipped correctly. This has been fixed. + \item The program was updated to use the ``approved for release'' disclaimer. The previous version was still using a ``preliminary software'' disclaimer. + \item The source code for time series and time array series was refactored. Included in the refactoring was a correction to time array series to allow the time array to change from one stress period to the next. The source file TimeSeriesGroupList.f90 was renamed to TimeSeriesFileList.f90. + \end{itemize} + + \underline{STRESS PACKAGES} + \begin{itemize} + \item Fixed inconsistency with CHD package observation name in code (\texttt{chd-flow}) and name in the input-output document (\texttt{chd}). Using name defined in input-output document (\texttt{chd}). + \item The cell area was not being used in the calculation of recharge and evapotranspiration when list input was used with time series. + \item The AUXMULTNAME option was not being applied for recharge and evapotranspiration when the READASARRAYS option was used. + \item The program was not terminating with an error if a PERIOD block was encountered with an iper value equal to the previous iper value. Program now terminates with an error. + \end{itemize} + + \underline{ADVANCED STRESS PACKAGES} + \begin{itemize} + \item Fixed incorrect sign for SFR package exchange with GWF model (\texttt{sfr}). + \item Added option to specify \texttt{none} as the \texttt{bedleak} for a lake-\texttt{GWF} connection in lake (LAK) package. This option makes the lake-\texttt{GWF} connection conductance solely a function of aquifer properties in the connected \texttt{GWF} cell and lakebed sediments are assumed to be absent for this connection. + \item Fixed bug in lake (LAK) and multi-aquifer well (MAW) packages that only reset steady-state flag if lake and/or multi-aquifer data are read for a stress period (in the pak\_rp() routines). Using pointer to GWF iss variable in the LAK package and resetting the MAW steady state flag in maw\_rp() routine every stress period, regardless of whether MAW data are specified for a stress period. + \item Added a convergence check routine to the GWF Mover Package that requires at least two outer iterations if there are any active movers. Because mover rates are lagged by one outer iteration, at least two outer iterations are required for some problems. + \item Changed the behavior of the LAK Package so that recharge and evapotranspiration are applied to a vertically connected GWF model cell if the lake status is INACTIVE. Prior to this change, recharge and evapotranspiration were only applied to an underlying GWF model cell if the lake was dry. + \end{itemize} + + \underline{SOLUTION} + \begin{itemize} + \item Fixed bug in IMS that allowed convergence when outer iteration HCLOSE value was satisfied but the model did not converge during the inner iterations. + \item Added STRICT rclose\_option that uses a infinity-Norm RCLOSE criteria but requires HCLOSE and RCLOSE be satisfied on the first inner iteration of an outer iteration. The STRICT option is identical to the closure criteria approach use in the PCG Package in MODFLOW-2005. + \end{itemize} + + \underline{EXCHANGES} + \begin{itemize} + \item Use of an OPEN/CLOSE file was not being allowed for the OPTIONS and DIMENSIONS blocks of the GWF6-GWF6 exchange input file. OPEN/CLOSE input is now allowed for both of these blocks. + \end{itemize} diff --git a/doc/ReleaseNotes/previous/v6.0.2.tex b/doc/ReleaseNotes/previous/v6.0.2.tex new file mode 100644 index 00000000000..256df4b9f60 --- /dev/null +++ b/doc/ReleaseNotes/previous/v6.0.2.tex @@ -0,0 +1,30 @@ + + \subsection{Version mf6.0.2--Feb 23, 2018} + + \underline{BASIC FUNCTIONALITY} + \begin{itemize} + \item Added a new option, called SAVE\_SPECIFIC\_DISCHARGE to the Node Property Flow Package. When invoked, $x$, $y$, and $z$ specific discharge components are calculated for the center of each model cell and written to the binary budget file. + \item For binary input of grid data, such as initial heads, the array reading utility was not reading a header record consisting of KSTP, KPER, PERTIM, TOTIM, TEXT, NLAY, NROW, NCOL. This meant that a binary head file written by MODFLOW could not be used as input for a subsequent simulation. For binary input, the array reading utility now reads a header record before reading the array values. + \item The NOGRB option in the discretization packages was not working. This option will now prevent the binary grid file from being written. + \item Removed the PRIVATE attribute for two methods of the discretization packages so that the program works as intended with the latest Intel Fortran release. + \item Switched to using a long integer for the memory manager so that memory usage is calculated correctly for large models. + \end{itemize} + + \underline{STRESS PACKAGES} + \begin{itemize} + \item If a steady-state stress period followed a transient stress period, the storage terms written to the budget file were not being reset to zero. The program now initializes these budget values to zero for steady-state periods before they are written. + \end{itemize} + + \underline{ADVANCED STRESS PACKAGES} + \begin{itemize} + \item The STATUS INACTIVE option was not working correctly for the MAW Package. + \item Modified the MAW connection conductance calculation so that a linear relation between the water level in a cell and saturation is used for the standard formulation. In the previous version, the same quadratic saturation function was being used for the standard and Newton-Raphson formulation to calculate the MAW connection conductance. + \item Modified the MAW Package so that the top and bottom of the screen for a connection are reset to the top and bottom of the cell, respectively, for SPECIFIED, THEIM, SKIN, and CUMULATIVE conductance equations (CONDEQN). Also, the program will now terminate with an error if a MAW well using SPECIFIED, THEIM, SKIN, or CUMULATIVE conductance equations has more than one connection to a single GWF cell. + \item Modified the MAW package so that the well bottom (BOTTOM) is reset to the cell bottom in the lowermost GWF cell connection in cases where the specified well bottom is above the bottom of this GWF cell. + \end{itemize} + + \underline{SOLUTION} + \begin{itemize} + \item Prior to applying pseudo transient continuation terms, the Iterative Model Solution confirms that the L2-norm exceeds the previous L2-norm. If it doesn't then pseudo transient continuation is turned off. This fixes a rare situation in which convergence could not be achieved for consecutive steady state solutions with the same or similar answers. + \end{itemize} + diff --git a/doc/ReleaseNotes/previous/v6.0.3.tex b/doc/ReleaseNotes/previous/v6.0.3.tex new file mode 100644 index 00000000000..9c1e8cf3ca1 --- /dev/null +++ b/doc/ReleaseNotes/previous/v6.0.3.tex @@ -0,0 +1,30 @@ + \subsection{Version mf6.0.3--Aug. 9, 2018} + + \underline{BASIC FUNCTIONALITY} + \begin{itemize} + \item Fixed issues with observations specified using boundnames that are enclosed in quotes. Previously, the closing quote was retained on a boundname enclosed in quotes and resulted in an error (the erroneous observation boundname could not be found in the package). + \end{itemize} + + \underline{STRESS PACKAGES} + \begin{itemize} + \item If the AUXMULTNAME keyword was used in combination with time series, then the multiplier was erroneously applied to all time series, and not just the time series in the column to be scaled. + \item For the array-based recharge and evapotranspiration packages, the IRCH and IEVT variables (if specified) must be specified as the first variable listed in the PERIOD block. A check was added so that the program will terminate with an error if IRCH or IEVT is not the first variable listed in the PERIOD block. + \item For the standard boundary packages, the ``to mover'' term (such as DRN-TO-MVR) written to the GWF Model budget was incorrect. The budget terms were incorrect because the accumulator variables were not initialized to zero. + \item For regular MODFLOW grids, the recharge and evapotranspiration arrays of size (NCOL, NROW) were being echoed to the listing file (if requested by the user) of size (NCOL * NROW). + \end{itemize} + + \underline{ADVANCED STRESS PACKAGES} + \begin{itemize} + \item Fixed spelling of the THIEM keyword in the source code and in the input instructions of the MAW Package. + \item Fixed an issue with the SFR package when the specified evaporation exceeds the sum of specified and calculated reach inflows, rainfall, and specified runoff. In this case, evaporation is set equal to the sum of specified and calculated reach inflows, rainfall, and specified runoff. Also if a negative runoff is specified and this value exceeds specified and calculated reach inflows, and rainfall then runoff is set to the sum of reach inflows and evaporation is set to zero. + \item Fixed an issue in the MAW package budget information written to the listing file and MAW cell-by-cell budget file when a previously active well is inactivated. The ratesim variable was not being reset to zero for these wells and the simulated rate from the last stress period when the well was active was being reported. + \item Program now terminates with an error if the OUTLETS block is present in the LAK package file and NOUTLETS is not specified or specified to be zero in the DIMENSIONS block. Previously, this did not cause an error condition in the LAK package but would result in a segmentation fault error in the MVR package if LAK package OUTLETS are specified as providers. + \item Program now terminates with an error when a DIVERSION block is present in a SFR package file but no diversions (all ndiv values are 0) are specified in the PACKAGEDATA block. + \end{itemize} + + \underline{SOLUTION} + \begin{itemize} + \item Fixed bug related to not allocating the preconditioner work array if a non-zero drop tolerance is specified but the number of levels is not specified or specified to be zero. In the case where the number of levels is not specified or specified to be zero the preconditioner work array is dimensioned to the product of the number of cells (NEQ) and the maximum number of connections for any cell. + \item Updated linear solver output so number of levels and drop tolerance are output if either are specified to be greater than zero. + \end{itemize} + \ No newline at end of file diff --git a/doc/ReleaseNotes/previous/v6.0.4.tex b/doc/ReleaseNotes/previous/v6.0.4.tex new file mode 100644 index 00000000000..8c5a8ccf151 --- /dev/null +++ b/doc/ReleaseNotes/previous/v6.0.4.tex @@ -0,0 +1,35 @@ + \subsection{Version mf6.0.4--Feb. 27, 2019} + + \underline{BASIC FUNCTIONALITY} + \begin{itemize} + \item Addressed issue with pointing contiguous pointer vectors/arrays to non-contiguous pointer vectors/arrays that caused code compilation failure with gfortran-8. A consequence of addressing this issue is that all pointer vectors/arrays that are allocated or pointed to using the memory manager must be defined to be contiguous. + \item Corrected a problem with the reading of grid data from a binary file, in which the program was reading a binary header for each row of data. + \item Added a new error check for very small time steps. If the value of the starting time is equal to the ending time (starting time plus the time step length), then the time step is too small to be differentiated by the program based on the precision of floating point numbers. The program will terminate with an error in this case. The program will also terminate if the storage package with a transient stress period has a time step length of zero. + \item The observation package was modified to use non-advancing output instead of fixed length strings when writing ascii output. The previous use of fixed length strings resulted in truncation of ascii observation output when the product of user-specified \texttt{digits} + 7 and the number of observations exceeded 5000. + \item Corrected an error in the GWF-GWF Exchange module that caused the specific discharge values in the child model to be calculated incorrectly. The calculation was incorrect because the face normal for the child model was pointing toward the center of the cell instead of outward. + \item Minor refactoring to improve code clarity. + \end{itemize} + + \underline{STRESS PACKAGES} + \begin{itemize} + \item Minor refactoring to improve code clarity. + \end{itemize} + + \underline{ADVANCED STRESS PACKAGES} + \begin{itemize} + \item Modified the Multi-Aquifer Well (MAW) Package so that the HEAD\_LIMIT and RATE\_SCALING options work for injection wells. Prior to this change, these options only worked for extraction wells. These options can be used to reduce or even shut off well injection as the head in the well rises above user-specified levels. + \item Added stage and residual convergence checks to the SFR package to make sure that stage and upstream flow changes between successive outer iterations are less than OUTER\_HCLOSE and OUTER\_RCLOSEBND, respectively. This addition is expected to be useful for steady-state simulations with complicated networks and simple reaches. + \item Modified the final convergence check for the LAK package to use OUTER\_HCLOSE when evaluating lake stage changes between successive outer iterations. + \item Modified the final convergence check for the UZF package to use OUTER\_RCLOSEBND when evaluating rejected infiltration, groundwater recharge, and groundwater seepage changes between successive outer iterations. + \item Minor refactoring to improve code clarity. + \end{itemize} + + \underline{SOLUTION} + \begin{itemize} + \item Modified pseudo-transient continuation (PTC) approach to use PTC for steady-state stress period for models using the Newton-Raphson formulation for problems with and without the storage (STO) package. Previously, PTC was only used with problems that did not include the STO package (this was not the intended behavior of PTC). + \item Added NO\_PTC option to disable PTC for problems where PTC degrades/prevents model convergence. Option only applies to steady-state stress periods for models using the Newton-Raphson formulation. For many problems, PTC can significantly improve convergence behavior for steady-state simulations, and for this reason it is active by default. In some cases, however, PTC can worsen the convergence behavior, especially when the initial conditions are similar to the solution. When the initial conditions are similar to, or exactly the same as, the solution and convergence is slow, then this NO\_PTC option should be used to deactivate PTC. This NO\_PTC option should also be used in order to compare convergence behavior with other MODFLOW versions, as PTC is only available in MODFLOW~6. + \item Small improvements to PTC to reduce the initial PTCDEL value loaded on the diagonal. This reduces the number of iterations required to achieve convergence for steady-state stress periods for most problems. + \item Added OUTER\_RCLOSEBND variable that is used when performing final convergence checks on model packages that solve a separate equation not solved by the IMS linear solver. This value represents the maximum allowable residual at any single model package element between successive outer iterations. An example of a model package that would use OUTER\_RCLOSEBND to evaluate convergence is the SFR package which solves a continuity equation for each reach. + \item Minor refactoring to improve code clarity. + \end{itemize} + diff --git a/doc/ReleaseNotes/previous/v6.1.0.tex b/doc/ReleaseNotes/previous/v6.1.0.tex new file mode 100644 index 00000000000..b2f167714d4 --- /dev/null +++ b/doc/ReleaseNotes/previous/v6.1.0.tex @@ -0,0 +1,39 @@ + + \subsection{Version mf6.1.0--December 12, 2019} + + \underline{NEW FUNCTIONALITY} + \begin{itemize} + \item Added the Skeletal Storage, Compaction, and Subsidence (CSUB) Package. The one-dimensional effective-stress based compaction theory implemented in the CSUB Package is documented in \cite{leake2007modflow}. The numerical approach used for delay interbeds in the CSUB package is documented in \cite{hoffmann2003modflow} and uses the same one-dimensional effective-stress based compaction theory as coarse-grained and fine-grained no-delay interbed sediments. A number of example problems that use the CSUB Package are documented in the ``MODFLOW 6 CSUB Package Example Problems'' pdf document included in this and subsequent releases. + \end{itemize} + + \underline{BASIC FUNCTIONALITY} + \begin{itemize} + \item Added an error check to the DISU Package that ensures that an underlying cell has a top elevation that is less than or equal to the bottom of an overlying cell. An underlying cell is one in which the IHC value for the connection is zero and the connecting node number is greater than the cell node number. + \item Added restricted IDOMAIN support for DISU grids. Users can specify an optional IDOMAIN in the DISU Package input file. IDOMAIN values must be zero or one. Vertical pass-through cells (specified with an IDOMAIN value of -1 in the DIS or DISV Package input files) are not supported for DISU. + \item NPF Package will now write a message to the GWF Model list file to indicate when the SAVE\_SPECIFIC\_DISCHARGE option is invoked. + \item Added two new options to the NPF Package. The K22OVERK option allows the user to enter the anisotropy ratio for K22. If activated, the K22 values entered by the user in the NPF input file will be multiplied by the K values entered in the NPF input file. The K33OVERK option allows the user to enter the anisotropy ratio for K33. If activated, the K33 values entered by the user in the NPF input file will be multiplied by the K values entered in the NPF input file. With this K33OVERK option, for example, the user can specify a value of 0.1 for K33 and have all K33 values be one tenth of the values specified for K. The program will terminate with an error if these options are invoked, but arrays for K22 and/or K33 are not provided in the NPF input file. + \item Added new MAXERRORS option to mfsim.nam. If specified, the maximum number of errors stored and printed will be limited to this number. This can prevent a situation where memory will run out when there are an excessive number of errors. + \item Refactored many parts of the code to remove unused variables, conform to stricter FORTRAN standard checks, and allow for new development efforts to be included in the code base. + \end{itemize} + + \underline{STRESS PACKAGES} + \begin{itemize} + \item There was an error in the calculation of the segmented evapotranspiration rate for the case where the rate did not decrease with depth. There was another error in which PETM0 was being used as the evapotranspiration rate at the surface instead of the proportion of the evapotranspiration rate at the surface. + \end{itemize} + + \underline{ADVANCED STRESS PACKAGES} + \begin{itemize} + \item Corrected the way auxiliary variables are handled for the advanced packages. In some cases, values for auxiliary variables were not being correctly written to the GWF Model budget file or to the advanced package budget file. A consistent approach for updating and saving auxiliary variables was implemented for the MAW, SFR, LAK, and UZF Packages. + \item The user guide was updated to include a missing laksetting that was omitted from the PERIOD block. The laksetting description now includes an INFLOW option; a description for INFLOW is also now included. + \item The LAK package was incorrectly making an error check against NOUTLETS instead of NLAKES. + \item For the advanced stress packages, values assigned to the auxiliary variables were not written correctly to the GWF Model budget file, but the values were correct in the advanced package budget file. Program was modified so that auxiliary variables are correctly written to the GWF Model budget file. + \item Corrected several error messages issued by the SFR Package that were not formatted correctly. + \item Fixed a bug in which the lake stage stable would sometimes result in touching numbers. This only occurred for negative lake stages. + \item The UZF Package was built on the UZFKinematicType, which used an array of structures. A large array like this, can cause memory problems. The UZFKinematicType was replaced with a new UzfCellGroupType, which is a structure of arrays and is much more memory efficient. The underlying UZF algorithm did not change. + \end{itemize} + + \underline{SOLUTION} + \begin{itemize} + \item Add ALL and FIRST options to optional NO\_PTC optional keyword in OPTIONS block. If NO\_PTC option is FIRST, PTC is disabled for the first stress period but is applied in all subsequent steady-state stress periods. If NO\_PTC option is ALL, PTC is disabled for all steady-state stress periods. If the NO\_PTC options is not defined, PTC is disabled for all steady-state stress periods (this is consistent with the behaviour of the NO\_PTC option in previous versions). + \end{itemize} + diff --git a/doc/ReleaseNotes/previous/v6.1.1.tex b/doc/ReleaseNotes/previous/v6.1.1.tex new file mode 100644 index 00000000000..fb85784dcdc --- /dev/null +++ b/doc/ReleaseNotes/previous/v6.1.1.tex @@ -0,0 +1,55 @@ + \subsection{Version mf6.1.1--June 12, 2020} + + \underline{NEW FUNCTIONALITY} + \begin{itemize} + \item Refactor the source code to support the \href{https://csdms.colorado.edu/wiki/BMI_Description}{Basic Model Interface} (BMI) developed by the \href{https://csdms.colorado.edu/wiki/Main_Page}{Community Surface Dynamics Modeling System} (CSDMS) group. BMI is a set of standard control and query functions that, when added to a model code, make that model both easier to learn and easier to couple with other software elements \citep{PECKHAM20133}. Furthermore, the BMI makes it possible to control MODFLOW~6 execution from scripting languages using bindings for the BMI (for example, python bindings for the BMI available through \href{https://csdms.colorado.edu/wiki/PyMT}{pymt}). The BMI in this version is considered preliminary (alpha release). Limited testing of the BMI has been performed but significant changes are expected in future releases. User support for the MODFLOW 6 BMI may be provided in the future. + \item Add silent command line switch (\texttt{-s} or \texttt{\doubledash silent}) that sends all screen output (\texttt{STDOUT}) to a text file (with the name ``mfsim.stdout''). + \item Add screen output command line switch (\texttt{-l } or \texttt{\doubledash level }) that controls output to the screen (\texttt{STDOUT}). If \texttt{} is \texttt{summary}, stress period and time step data are not written to \texttt{STDOUT}. If \texttt{} is \texttt{debug}, normal and debug output are written to \texttt{STDOUT}. + \item Add simulation mode command line switch (\texttt{-m } or \texttt{\doubledash mode }) that controls the solution mode. If \texttt{} is \texttt{validate}, model input will be read and checked for errors, but the coefficient matrix or matrices will not be assembled or solved and solution output will not be written. + \item Add SAVE\_SATURATION option to the Node Property Flow Package. When invoked, cell saturation is written to the binary budget file as an auxiliary column for a record with the name ``DATA-SAT''. The cell saturation can be used by post-processors to determine how much of the cell is saturated without having to know the value for ICELLTYPE or the value for head. If a cell is marked as confined (ICELLTYPE=0) then saturation is always one. If ICELLTYPE is one, then saturation ranges between zero and one. + \item Add option for saving package convergence for the CSUB Package to a comma-separated values (CSV) file. Package convergence is enabled by specifying PACKAGE\_CONVERGENCE FILEOUT $<$package\_convergence\_filename$>$ in the options block for the package. + \item Add option for saving package convergence for the LAK, SFR, and UZF Packages to comma-separated values (CSV) files. Package convergence for the LAK, SFR, and UZF Packages is enabled by specifying PACKAGE\_CONVERGENCE FILEOUT $<$package\_convergence\_filename$>$ in the options block for the package. + \item Add CSV\_OUTER\_OUTPUT output option to save outer iteration information to a comma-separated values (CSV) file. The maximum of the model or package dependent-variable change for the outer iteration is written to the CSV file at the end of each outer iteration. + \item Add CSV\_INNER\_OUTPUT output option to save inner iteration information to a comma-separated values (CSV) file. The CSV output also the includes maximum dependent-variable change and maximum residual convergence information for the solution and each model (if the solution includes more than one model) and linear acceleration information for each inner iteration. The inner iteration CSV output, which contains a separate line for each inner iteration, is written to the CSV file all at once at the end of each outer iteration. + \item Add OUTER\_DVCLOSE and INNER\_DVCLOSE variables to replace OUTER\_HCLOSE and INNER\_HCLOSE variables in the IMS Package input file. ``DV'' is used now instead to more generally refer to dependent variable. Warning messages will be issued if OUTER\_HCLOSE and/or INNER\_HCLOSE variables are specified. OUTER\_HCLOSE and INNER\_HCLOSE variables will eventually be deprecated. + \item Add option to scale drain conductance as a function of simulated head over a user-defined range (drainage depth). Linear-conductance scaling is used with the Standard Formulation. Cubic-conductance scaling is used with the Newton-Raphson Formulation. The additional drainage depth variable is specified as an auxiliary variable and AUXDEPTHNAME is used to identify the auxiliary variable defining the drainage depth. The cubic-conductance scaling can be used as a replacement for the groundwater seepage option in the UZF Package. The scaled drainage conductance option can also be used to represent vertical seepage faces and improve model convergence in cells where simulated heads fluctuate around the elevation where the drain begins to discharge groundwater. + \item Add timeseries support for the reach upstream fraction variable in the SFR package. + \item Add Picard iterations for the SFR package to minimize differences in SFR package results between subsequent GWF Picard (non-linear) iterations as a result of non-optimal reach numbering. The number of SFR package Picard iterations can be controlled by specifying the maximum number of Picard iteration to be used in the OPTIONS block (MAXIMUM\_PICARD\_ITERATIONS). If reaches are numbered in order, from upstream to downstream, MAXIMUM\_PICARD\_ITERATIONS can be set to 1 to reduce model run time. Specifying MAXIMUM\_PICARD\_ITERATIONS to 1 will result in identical SFR package performance to previous versions of MODFLOW~6. + \item Add flow correction option for the MAW package that corrects the MAW-GWF exchange in cases where the head in a multi-aquifer well is below the bottom of the screen for a connection or the head in a convertible cell connected to a multi-aquifer well is below the cell bottom. When flow corrections are activated, unit head gradients are used to calculate the flow between a multi-aquifer well and a connected GWF cell. This option is identical to the MODFLOW-USG ``flow-to-dry-cell'' option for flow between a CLN cell and a GWF cell if the cell is convertible \citep{modflowusg}. Flow corrections are enabled by specifying FLOW\_CORRECTION in the OPTIONS block. By default, flow corrections are not made. \emph{Prior to this release (version 6.1.1), flow corrections were made anytime the head in a multi-aquifer well was below the bottom of the screen for a connection--this may result in different results for existing models that can be resolved by using the FLOW\_CORRECTION option.} + \item Add new document, ``MODFLOW 6 -- Supplemental Technical Information,'' to the doc folder. This document contains information that was in the mf6io.pdf appendices. This technical information document may expand with future versions as new features are added. + \end{itemize} + + \textbf{\underline{BUG FIXES AND OTHER CHANGES TO EXISTING FUNCTIONALITY}} \\ + \underline{BASIC FUNCTIONALITY} + \begin{itemize} + \item Correct an error in how the discretization package (for regular MODFLOW grids) calculates the distance between two cells when one or both of the cells are unconfined. The error in the code would have only affected XT3D simulations with a regular grid, unconfined conditions, and specification of ANGLE2 in the NPF Package. + \item Correct an error in the use of the AUXMULTNAME option for boundary packages when time series are used. A problem remains when time series are used for AUXMULTNAME but not for the column that is scaled by AUXMULTNAME. This situation should be avoided. + \end{itemize} + + \underline{STRESS PACKAGES} + \begin{itemize} + \item Fix a bug in binary budget file header for CSUB Package budget data written using IMETH=6 (CSUB-ELASTIC and CSUB-INELASTIC) . + \item Add information on the CSUB Package budget terms and compaction data written the the Input/Output document in the `Description of Groundwater Flow (GWF) Model Binary Output Files' section. + \item Prior to this release, calculated flows between a standard stress package (WEL, DRN, RIV, GHB, RCH, and EVT) and the connected model cell were based on the RHS and HCOF terms from the previous iteration. This was not consistent with previous MODFLOW versions. These packages were modified so that the flows are recalculated using the final converged head solution. As a result of this change, simulated groundwater flows for these packages may be slightly different (compared to previous releases) if the package HCOF and RHS values depend on the simulated groundwater head. + \end{itemize} + + \underline{ADVANCED STRESS PACKAGES} + \begin{itemize} + \item The code for saving the budget terms for the advanced packages was refactored to use common routines. These changes should have no affect on simulation results. + \item In previous releases, the LAK Package would accept negative user-input values for RAINFALL, EVAPORATION, RUNOFF, INFLOW, and WITHDRAWAL even though the user guide mentioned that negative values are not allowed for these flow terms. Error checks were added to ensure these values are specified as positive. + \item Add a storage term to the SFR Package binary output file. This term is always zero with the present implementation. An auxiliary variable, called VOLUME, is also written with the storage budget term. This term contains the calculated water volume in the reach. + \item Refactor the SFR Package to remove use of RectangularChGeometry objects and added required functionality as private methods in the SFR module. + \item Improve error trapping in the MAW Package to catch divide by zero errors when calculating the saturated conductance for wells using the SKIN CONDEQN in connections where the cell transmissivity (the product of geometric mean of the horizontal hydraulic conductivity and cell thickness) and well transmissivity (the product of HK\_SKIN and screen thickness) is equal to one. Also add error trapping for well connections using the 1) SKIN CONDEQN where the contrast between the cell and well transmissivities are less than one and 2) SKIN and MEAN CONDEQN where the calculated connection saturated conductance is less than zero. + \item For the Lake Package, the outlet number was written as ID1 and ID2 for the TO-MVR record in the binary budget file. This has been changed so that the lake number of the connected outlet is written to ID1 and ID2. This change was implemented so that lake budgets can be calculated using the information in the lake budget file. + \item The Lake, Streamflow Routing, and Multi-Aquifer Well Packages were modified to save the user-specified stage or head to the binary output file for lakes, reaches, or wells that are specified as being CONSTANT. Prior to this change, a no-flow value was written to the package binary output files for constant stage lakes and streams and constant head multi-aquifer wells. The no-flow value is still written for those lakes, streams, or wells that are specified by the user as being inactive. This change should make it easier to post-process the results from these packages. + \end{itemize} + + \underline{SOLUTION} + \begin{itemize} + \item Fix a bug in the linear solver when using the STRICT RCLOSE\_OPTION that prevented termination of inner iterations when the INNER\_DVCLOSE and INNER\_RCLOSE criteria were met but the inner iteration count was greater than one. The inner iterations are now terminated when the INNER\_DVCLOSE and INNER\_RCLOSE criteria are met but the linear solver is considered non-converged if the inner iteration count is greater than one. + \item Deprecate the CSV\_OUTPUT output option in the OPTIONS BLOCK because the output to the comma-separated values (CSV) file was based on the PRINT\_OPTION option. If CSV\_OUTPUT is specified, it is used to define the file name for the CSV\_OUTER\_OUTPUT output option. + \item Modify the outer iteration information written to the simulation listing file when PRINT\_OPTION is not NONE to improve the ability of users to evaluate model convergence. Added Package convergence data, eliminated dependent variable changes adjusted by under-relaxation, and flags to indicate when an outer iterations step is considered converged. Information is also provided if PTC causes non-convergence for a outer iteration (even if the model is converged) and if NEWTON UNDER\_RELAXATION resets outer iteration convergence from FALSE to TRUE. Dependent-variable changes for the under-relaxation step in an outer iteration are no longer reported because under-relaxation is only applied if the model or package outer iteration steps do not converge and by definition reduce dependent-variable changes and are not used to evaluate outer iteration convergence. + \item Deprecate the OUTER\_RCLOSEBND optional variable in the NONLINEAR BLOCK because OUTER\_DVCLOSE is used for all terms used to evaluate package convergence. An warning will be issued if OUTER\_RCLOSEBND is specified. + \item Deprecate the CSV\_OUTPUT output option. A warning will be issued if the CSV\_OUTPUT option is specified and outer iteration information will be saved to the specified FILEOUT comma-separated values (CSV) file. + \end{itemize} + diff --git a/doc/ReleaseNotes/previous/v6.2.0.tex b/doc/ReleaseNotes/previous/v6.2.0.tex new file mode 100644 index 00000000000..a2ae9a9c7d8 --- /dev/null +++ b/doc/ReleaseNotes/previous/v6.2.0.tex @@ -0,0 +1,38 @@ + \subsection{Version mf6.2.0--October 20, 2020} + + \underline{NEW FUNCTIONALITY} + \begin{itemize} + \item A new Buoyancy (BUY) Package for the Groundwater Flow (GWF) Model is introduced in this release as a way to represent variable-density groundwater flow. The BUY Package is based on the hydraulic head formulation described by \cite{langevin2020hydraulic}. Extensive testing of the BUY Package has been performed but changes to the code and input may be required in response to user needs and testing. + \item A new Groundwater Transport (GWT) Model is introduced in this release as a way to simulate the fate and transport of a dissolved solute. Extensive testing of the GWT Model has been performed but changes to the code and input may be required in response to user needs and testing. + \item The Basic Model Interface (BMI) capabilities were first released in version 6.1.1. Extensive testing of the BMI capabilities has been performed but changes to the code and calling procedures be required in response to user needs and testing. + \end{itemize} + + \underline{EXAMPLES} + \begin{itemize} + \item The format for the examples included in the distribution has changed. The examples are now described in the modflow6-examples.pdf file in the doc folder. The examples have been renamed, and they are no longer numbered. Most of the examples are the same as those distributed with the previous release; however some have been modified, updated, combined or eliminated based on standardization of example construction, testing, and documentation. + \end{itemize} + + \textbf{\underline{BUG FIXES AND OTHER CHANGES TO EXISTING FUNCTIONALITY}} \\ + \underline{BASIC FUNCTIONALITY} + \begin{itemize} + \item The observation routines were improved to handle very large numbers of observations written to the same comma-separated-value (CSV) file. Non-advancing input-output is now used to write the CSV header instead of constructing a header string. This change should substantially improve memory and runtime problems with models containing thousands of observations. + \item If the CONTINUE option is specified in mfsim.nam, do not force models to write budget tables when the solver does not converge. Instead, always use Output Control options to determine when budget tables are written if CONTINUE option is specified and the solver does not converge. Also, if the CONTINUE option is specified, calculate and write observations even if the model does not converge. Observations were being written as zero if the model did not converge, but the CONTINUE flag was set. + \item Allow the program to read input files with very long lines. Previously, the program was limited to a maximum line length of 50,000 characters. The program now uses dynamic memory allocation, when necessary, to read any sized line in an input file. + \item Fixed an error in the implementation of the Newton-Raphson correction for XT3D. The error in the code would have only affected simulations that used the NEWTON option together with the XT3D RHS option. + \end{itemize} + + \underline{ADVANCED STRESS PACKAGES} + \begin{itemize} + \item Fix error in calculated Newton-Raphson MAW-GWF connection terms for the MAW Package. This correction should improve model convergence and may change existing model results. This correction does not affect simulations that use the FLOW\_CORRECTION option introduced in version 6.1.1. + \item The program will now terminate with an error message if the skin radius for a GWF connection in the MAW Package is less than or equal to the well radius. Warning messages are also issued when the well bottom, screen top for a GWF connection, or screen bottom for a GWF connection are reset by the program for one or more MAW Package wells. + \item An SFR reach can have zero specified connections. In this case, an entry is still required in the CONNECTIONDATA block for that reach. The error check for this required entry was not implemented and so the program would continue with unexpected results. The program now verifies that an entry is present in CONNECTIONDATA for every reach, even those with zero connections. + \item If a GWF ``NONE'' connection was specified for SFR then the program would terminate with an error or proceed with unexpected results if GWF or SFR flow terms were written to binary output files. The program was fixed so that GWF ``NONE'' connections for SFR are not written to the binary budget files. + \item Increase the length of boundname to its intended size of 40 characters. Boundnames were being truncated after 16 characters for the LAK, MAW, and SFR Packages. + \end{itemize} + + \underline{SOLUTION} + \begin{itemize} + \item A fix was implemented in the biconjugate gradient stabilized linear solver routine so that the maximum change in the dependent value is calculated and stored correctly. + \item Corrected the SIMPLE and COOLEY under-relaxation schemes in the Iterative Model Solution (IMS). The methods were not applying the correct under-relaxation factor. The SIMPLE scheme now uses the user-specified value for gamma as the factor. The COOLEY scheme updates the factor based on solver history. + \end{itemize} + diff --git a/doc/ReleaseNotes/previous/v6.2.1.tex b/doc/ReleaseNotes/previous/v6.2.1.tex new file mode 100644 index 00000000000..e9fb9ac0350 --- /dev/null +++ b/doc/ReleaseNotes/previous/v6.2.1.tex @@ -0,0 +1,56 @@ + \subsection{Version mf6.2.1--February 18, 2021} + + \underline{NEW FUNCTIONALITY} + \begin{itemize} + \item The Source and Sink Mixing (SSM) Package for the Groundwater Transport Model was modified to include an alternative option for the concentration value assigned to sinks. A new AUXMIXED option was added to represent evaporation-like sinks where the solute or a portion of the solute may be left behind. The AUXMIXED option provides an alternative method for determining the groundwater sink concentration. If the cell concentration is larger than the user-specified sink concentration, then the concentration of the sink will be assigned as the specified concentration. Alternatively, if the specified concentration is larger than the cell concentration, then water will be withdrawn at the cell concentration. Thus, the AUXMIXED option is designed to work with the Evapotranspiration and Recharge packages where water may be withdrawn at a concentration that is less than the cell concentration. + \item Add support for the Freundlich and Langmuir isotherms to the Mass Storage and Transfer (MST) Package of the Groundwater Transport Model. + \end{itemize} + + \underline{EXAMPLES} + \begin{itemize} + \item Added the following new examples: + \begin{itemize} + \item ex-gwt-mt3dms-p02 + \item ex-gwt-rotate + \item ex-gwt-saltlake + \item ex-gwt-uzt-2d + \end{itemize} + \end{itemize} + + \textbf{\underline{BUG FIXES AND OTHER CHANGES TO EXISTING FUNCTIONALITY}} \\ + \underline{BASIC FUNCTIONALITY} + \begin{itemize} + \item The way in which the dispersion coefficients are calculated with the simple option (XT3D\_OFF) for the Dispersion Package was modified. When the velocity within a cell is not aligned with a principal grid direction, the dispersion coefficients are calculated using a simple arithmetic weighting, rather than harmonic weighting as is done for the simple option for anisotropic flow in the NPF Package. The arithmetic weighting option eliminates a possible discontinuity when a principal flow-aligned dispersion component is zero. + \item The mass flow between two cells is calculated and optionally written to the GWT budget file. There was an error in this calculation of mass flow when the TVD scheme was specified in the Advection (ADV) Package. Consequently, the mass flows written to the budget file were not correct in this situation. Because these mass flows are also used in the budget calculations for the Constant Concentration (CNC) Package, reported CNC mass flows were also not correct. This could result in large budget discrepancies in the GWT budget table. Simulated concentrations were not affected by this error. A small correction was made to the routine that adds the advective mass flow for the TVD scheme. + \item Several packages had input blocks that could not be specified using the OPEN/CLOSE keyword. The program was modified so that OPEN/CLOSE is supported for all intended blocks. + \item The Immobile domain Storage and Transfer (IST) Package for the GWT Model is based on a conceptual model in which the immobile domain is always fully saturated, and so the saturation of the immobile domain does not depend on head in a cell. The program was modified so that none of the immobile domain terms include saturation, except for the mass transfer equation itself, in which the transfer of solute between the mobile and immobile domain is multiplied by the cell saturation. + \item Budget terms for the Immobile domain Storage and Transfer (IST) Package were not being written to the binary budget file for the GWT Model. The package was modified to write these rate terms to the GWT binary budget file using the settings specified in the GWT Output Control file. + \item Bulk density does not need to be specified for the Immobile domain Storage and Transfer (IST) Package if sorption is not active; however the program was trying to access bulk density even though it is not needed, which resulted in an access violation. Program was fixed so that bulk density does not need to be specified by the user unless sorption is active for the IST Package. + \item Budget tables printed to the listing file had numeric values that were missing the `E` character if the exponent had three digits (e.g. 1.e-100 or 1.e100). Writing of the budget table was modified to include the `E` character in this case. This change should make it easier for programs written in other languages to parse these tables. + \item In the Mass Transfer and Storage (MST) and the Immobile Storage and Transfer (IST) Packages, the keyword to activate sorption was changed from SORBTION to SORPTION. The program will still accept SORBTION, but this keyword will be deprecated in the future. + \item Revised several of the text strings written to the headers within the GWT binary budget file. A table of possible text strings for the GWT binary budget file are now included in the mf6io.pdf document. + \end{itemize} + +% \underline{STRESS PACKAGES} +% \begin{itemize} +% \item xxx +% \item xxx +% \item xxx +% \end{itemize} + + \underline{ADVANCED STRESS PACKAGES} + \begin{itemize} + \item The CONSTANT term used to report the rate of mass provided by a constant-concentration condition in the LKT, SFT, MWT, and UZT did not include the contribution to adjacent package features. For example, if a stream reach was marked as constant-concentration boundary and it had flow into a downstream reach, then that flow was not included in the budget calculations. Consequently, reported budgets in the listing file would show discrepancies that were larger than what was actually simulated by the model. The program code was modified to include these mass flows to adjacent features. + \item The ET formulation in UZF was not reducing the residual pET passed to deeper UZF objects when the extinction depth spanned multiple UZF objects (layers). As a result, too much water was removed when the water table was shallow. Or, in some cases, water was removed from dry cells that were above the water table but within the extinction depth interval. The ET code within the UZF package was modified to remove only eligible water from the unsaturated and saturated zones. + \item The UZF package should exit with an appropriate error message when SURFDEP > cell thickness. When this condition is not enforced, bad mass balances may result. + \item The FLOW\_IMBALANCE\_CORRECTION implemented in the GWT FMI Package did not work properly with the GWF UZF Package. The issue was fixed by ensuring that the FMI Package could accurately calculate the flow residual for cells that had a UZF entry. + \item The SFR package should exit with an appropriate error message when a diversion has a cprior type of FRACTION but the divflow value is outside the range 0.0 to 1.0 as stated in the documentation. + \end{itemize} + +% \underline{SOLUTION} +% \begin{itemize} +% \item xxx +% \item xxx +% \item xxx +% \end{itemize} + diff --git a/doc/ReleaseNotes/previous/v6.2.2.tex b/doc/ReleaseNotes/previous/v6.2.2.tex new file mode 100644 index 00000000000..45a7e79e199 --- /dev/null +++ b/doc/ReleaseNotes/previous/v6.2.2.tex @@ -0,0 +1,57 @@ + \subsection{Version mf6.2.2--July 30, 2021} + + \underline{NEW FUNCTIONALITY} + \begin{itemize} + \item A new Adaptive Time Step (ATS) utility was added. The ATS utility allows any stress period to be overridden with an alternative time stepping approach. The ATS utility implements two main capabilities (1) the capability to retry failed time steps with a shorter time step repeatedly until convergence is achieved, and (2) the capability to shorten and lengthen time steps based on simulation behavior. These capabilities are described in the user input and output guide in a new section on the ATS utility. + \item A new option for printing water contents to a dedicated output file has been added to UZF. To activate, the keyword WATER\_CONTENT is added to the OPTIONS block of UZF, followed by FILEOUT, followed by the user-specified output file name, for example ``water-content.uzf.bin''. The approach is analogous to the STAGE option within the SFR options block. Contents of the new file will be written in binary and can be read using flopy's binaryfile utility. + \item The residual balance error for groundwater flow and solute transport is now written to the diagonal position of the flowja array, which is marked with the text description ``FLOW-JA-FACE''. The flowja array is optionally written to the binary model budget file according to user settings in the output control file and other package input files. + \item A new option for simulating specific storage changes only when a cell is fully saturated has been added to the storage (STO) package. To activate, the SS\_CONFINED\_ONLY keyword is added to the OPTIONS block in the STO Package. This option is identical to the approach used to calculate storage changes under confined conditions in MODFLOW-2005. + \item A new observation type called ``wel-reduction'' was added for the Well Package. This observation type reports the reduction in the well discharge that can occur when the \texttt{AUTO\_FLOW\_REDUCE} option is specified. + \end{itemize} + + \underline{EXAMPLES} + \begin{itemize} + \item Added the following new examples: + \begin{itemize} + \item ex-gwt-hecht-mendez + \item ex-gwf-capture (This example is described in mf6examples.pdf to demonstrate functionality of the Application Programming Interface; it is not included in the examples folder of this distribution as it requires python and several python packages) + \end{itemize} + \item Added new citation to this document. The \cite{morway2021} paper describes the use of the Water Mover Package in MODFLOW~6 to represent natural and managed hydrologic connections. + \end{itemize} + + \textbf{\underline{BUG FIXES AND OTHER CHANGES TO EXISTING FUNCTIONALITY}} \\ + \underline{BASIC FUNCTIONALITY} + \begin{itemize} + \item The specific storage formulation in the storage (STO) package has been modified to eliminate the dependency of the original formulation on the vertical datum. The original specific storage formulation also overestimated storage changes for cells that resaturated or desaturated in successive time steps. Furthermore, the sign of the specific storage change was incorrect in cells with negative heads and resaturated or desaturated in successive time steps. The revised specific storage formulation resolves all of the deficiencies of the original formulation and accurately simulates specific storage changes under water table conditions but will change the results for existing models. Testing indicates that the differences between models run with the original and revised specific storage formulation are generally small but tend to increase in models with large specific storage values or have cells that repeatedly resaturated or desaturated in successive time steps. + \item The convergence failure message message written to GWF and GWT listing files (FAILED TO MEET SOLVER CONVERGENCE CRITERIA) is now written after the budget summary tables. In previous releases this convergence failure message was written prior to printing heads and concentrations, which often resulted in this message being unnoticed by users. + \item The order of output written to the GWF and GWT listing files for a time step was reorganized in a consistent manner with model and package flows coming first, followed by dependent variables, and then concluding with budget summary tables. + \item The DISU Package checks to make sure that the top of a cell is not higher than the bottom of an overlying cell. A new option was added to the DISU Package to allow the user to specify the vertical offset tolerance used in this check. This new optional input variable is VERTICAL\_OFFSET\_TOLERANCE. + \item Add DISU Package check to ensure that JA(IA(n)) is equal to n and that no values in JA are less than zero or greater than nodes. + \item When IDOMAIN is used with the DISU Package and any IDOMAIN value is zero, then the program was expecting all JA values to be positive. The program is supposed to allow a negative JA value to be specified for the corresponding cell (in the diagonal position), but this was not working. A fix was implemented to allow a negative cell number to be specified in the diagonal position of the JA array when the IDOMAIN capability is active. + \item A new check was added to the Horizontal Flow Barrier (HFB) Package to ensure that barriers are between cells that are horizontally connected. The program would previously continue running if a barrier was between vertically connected cells. + \item There was no check to prevent the zero-order decay functionality of the Mobile Storage and Transfer (MST) and Immobile Storage and Transfer (IST) Packages in the GWT Model from producing negative concentrations. The program now reduces the zero-order decay rate for the aqueous and sorbed phases (for the mobile and immobile domains) to ensure that decay does not consume more mass than is available. These changes do not affect zero-order growth. + \item If a binary budget file from a GWF Model was larger than about 2 Gigabytes, then it could not be used as input for a subsequent GWT Model. The program was modified to use a long integer to store the byte position. + \item The program was terminating with a non-zero return code if the simulation did not converge. This is the intended behavior, unless the CONTINUE option is specified in the simulation name file. The program now terminates with a return code of zero if the simulation does not converge, but the CONTINUE option is set and the program reaches the end of the simulation. + \end{itemize} + +% \underline{STRESS PACKAGES} +% \begin{itemize} +% \item xxx +% \item xxx +% \item xxx +% \end{itemize} + + \underline{ADVANCED STRESS PACKAGES} + \begin{itemize} + \item The UZF water-content observation by depth was giving an error, because a check was using the wrong index to retrieve the cell top and bottom elevations for the requested observation. The program was modified to use the correct index, and the output is now as expected. Note that this bug is not related to the new WC keyword in the OPTIONS block, but rather is related to OBS6 output option. + \item Amend surfdep error check with landflag. Deep cells (non-land surface cells) should not require surfdep > 0 + \item In the LAK observation package, users can specify ``lak'' to get a summary of lake-groundwater exchange. Users could specify a lake number without specifying a specific connection number (variable ``iconn''). Code will now stop if lake number is provided without a matching connection number. Code will still provide a summary of total lake-groundwater exchange when BOUNDNAME is entered for the variable ID. This also will fix a similar issue for the observation types ``wetted-area'' and ``conductance'', since both require ID2 when ID is an integer corresponding to a lake number. + \item In the MAW observation package, users can specify ``maw'' to get a summary of well-groundwater exchange. The code was allowing users to specify a well number without requiring specification of a connection number (variable ``icon''). Code will now stop if well number is provided without a matching connection number. Code will still provide a summary of total well-groundwater exchange when BOUNDNAME is entered for the variable ID. This also will fix a similar issue for the observation type ``conductance'', since both require ID2 when ID is an integer corresponding to a well number. + \end{itemize} + + \underline{SOLUTION} + \begin{itemize} + \item An optional new input variable called ATS\_OUTER\_MAXIMUM\_FRACTION can now be entered for the IMS Package. This variable has no effect unless the new ATS capability is active. +% \item xxx +% \item xxx + \end{itemize} diff --git a/doc/ReleaseNotes/previous/v6.3.0.tex b/doc/ReleaseNotes/previous/v6.3.0.tex new file mode 100644 index 00000000000..d774613ef2d --- /dev/null +++ b/doc/ReleaseNotes/previous/v6.3.0.tex @@ -0,0 +1,52 @@ + \subsection{Version mf6.3.0--March 4, 2022} + + \underline{NEW FUNCTIONALITY} + \begin{itemize} + \item New publications describing MODFLOW~6 were released, including \cite{modflow6api}, \cite{modflow6gwt}, and \cite{modflow6csub}. + \item The GWF-GWF Exchange was updated to support XT3D flow calculations along the edges of connected GWF Models. The XT3D flow calculation is an alternative to the ghost-node correction and has been shown to provide accurate flow calculations for cell connections that do not meet the control-volume finite-difference requirements. This new capability allows the GWF-GWF Exchange to tightly couple a wide variety of parent and child grid configurations. The new XT3D option for the GWF-GWF Exchange can be activated by specifying XT3D in the options block of the GWF-GWF Exchange input file. The XT3D implementation for GWF-GWF is based on a new generalized coupling method that is described in the Supplemental Technical Information document distributed with this release. + \item A new capability was added to support tight coupling of GWT Models through a new GWT-GWT Exchange. The new GWT-GWT Exchange can be used to connect any two GWT Models in a manner similar to the GWF-GWF Exchange. This allows transport to be represented from one GWT Model to another. The GWT-GWT Exchange calculates advective and dispersive fluxes and also supports the Mover Transport (MVT) Package. This first release of the GWT-GWT Exchange is limited to simulations in which the corresponding GWF Models and GWF-GWF Exchanges are run concurrently within the same simulation; the new capability does not support simulation of transport using groundwater flows saved from a previous simulation; however, this may be supported in the future. The new GWT-GWT functionality is activated by creating a GWT-GWT input file and specifying a GWT6-GWT6 exchange in the EXCHANGEDATA block of the simulation name file (mfsim.nam). The GWT-GWT Exchange is based on a new generalized coupling method that is described in the Supplemental Technical Information document distributed with this release. + \item This release introduces the Time-Varying hydraulic conductivity (TVK) and the Time-Varying Storage (TVS) options for the Node Property Flow (NPF) and Storage (STO) Packages, respectively, of the GWF Model. The TVK option is activated by specifying the TVK6 keyword in the OPTIONS block of the NPF Package and by providing a TVK6 input file. The TVS option is activated by specifying the TVS6 keyword in the OPTIONS block of the STO Package, and by providing a TVS6 input file. The TVK6 and TVS6 input files are described in input and output guide (mf6io.pdf). Technical information about the TVK and TVS options is provided in the Supplemental Technical Information report that is provided with the distribution. + \item The option already existed to provide binary budget and head file information from a GWF simulation for only the first time step of a given stress period, in which case that information will be used for all time steps in that stress period in a GWT simulation that reads from those files. The behavior of this option has been extended such that if the binary budget and head file information provided for the final stress period of the GWF simulation is for only one time step, that information will be used for any subsequent time steps and stress periods in the GWT simulation. This extended behavior includes as a special case the use of binary budget and head file information from only one time step in only one stress period (for example, a single steady-state GWF stress period) for all time steps in all stress periods in a GWT simulation. + \item Added new file-based input capability for the GWT Source and Sink Mixing (SSM) Package. The SSM Package previously required that all source and sink concentrations be provided as auxiliary variables for corresponding GWF stress packages. The SSM Package now supports an optional new FILEINPUT block, which can be provided with package names and file names. Files referenced in the FILEINPUT block can be used to provide time-varying source and sink concentrations. Descriptions for this new capability are described in the input and output guide (mf6io.pdf) under the GWT SSM Package, and the GWT Stress Package Concentrations (SPC) for list-based input and array-based input. If used, a single SPC6 input file can be provided for a corresponding GWF stress package. Either an SPC6 input file or the auxiliary variable approach can be used to supply concentrations for a single GWF stress package, but not both. + \item Added BUDGETCSV option to GWF and GWT model output control and to advanced packages (SFR, LAK, MAW, UZF, MVR, SFT, LKT, MWT, UZT, and MVT) that produce summary tables of budget information. If activated, this option will cause a comma-separated value (CSV) file of the model budget terms to be written to a user-specified output file. These output CSV files can be easily read into a spreadsheet or scripting language for plotting of budget terms. + \item Added option to use irregular cross sections to define the reach geometry for individual reaches in the SFR Package. Cross-section data for irregular cross-sections are defined in a separate Cross-Section Table Input File. The station-elevation data for an irregular cross section are specified as xfraction and height data, which is converted to station position using the specified reach width (RWID) and elevation using the specified bottom elevation of the reach (RTP). Manning's roughness coefficient fractions can optionally be specified with the xfraction-height data for a irregular cross section to represent roughness coefficient variations in a channel (for example, different channel and overbank Manning's roughness coefficients). SFR Package irregular cross sections and the method used to solve for streamflow in a reach with non-uniform Manning's roughness coefficients is a generalization of the methods used for 8-point cross-sections in the SFR Package for previous versions of MODFLOW \citep{modflowsfr1pack}. + \end{itemize} + + \underline{EXAMPLES} + \begin{itemize} + \item A new example, ex-gwtgwt-mt3dms-p10, was added to demonstrate the new GWT-GWT Exchange. + \end{itemize} + + \textbf{\underline{BUG FIXES AND OTHER CHANGES TO EXISTING FUNCTIONALITY}} \\ + \underline{BASIC FUNCTIONALITY} + \begin{itemize} + \item Fixed a bug that caused the last binary budget and head file information provided by a GWF simulation in a given stress period to be used for all subsequent GWT simulation time steps in that stress period, even if binary budget and head file information was provided for more than one time step in that stress period. In that situation, the GWF time steps must match the GWT time steps one-for-one. + \item Boundary packages, such as WEL, DRN and GHB, for example, read lists of data using a general list reader. For text input, the list of data would not be read correctly if it was longer than 300 characters wide. Most lists would normally be less than 300 characters unless a large number of auxiliary variables were specified. In this case, information beyond 300 characters would not be read. The list reader was modified to use an unlimited character length so that any number of auxiliary variables can be specified. + \item The Observation (OBS) functionality can optionally write simulated values to a comma-separated value (CSV) output file. The OBS input file contains a DIGITS option, which controls the number of digits written to the CSV output file. In some cases the ``E'' character was not written, making it difficult to read the CSV output file with other programs. New functionality was programmed to write observations using the maximum number of digits stored internally by the program using the G0 Fortran specifier. The default value for DIGITS was five, but this has been changed to this maximum number of digits. This maximum number of digits can also be activated by specifying a zero for DIGITS. For most applications, observations should be written with the default number of digits so that no precision is lost in the output. + \item Added a check to the Flow Model Interface (FMI) Package of the GWT Model that will cause the program to terminate with an error if flow and budget files needed from a flow model cannot be located. + \item The Output Control Package did not correctly determine if the end of a stress period was reached if the Adaptive Time Stepping (ATS) option was active. Output Control works correctly now with ATS. + \item Budget information for individual boundary package entries can be written to the model list file in a table form. The table no longer includes boundaries that are in cells that are dry. + \item When the simulated concentration in a cell is negative, which can occur due to the numerical methods used to solve the transport equation, then any sinks present in the cell should not add or remove mass. The program was modified so that transport through sinks is deactivated if the simulated concentration in a cell is negative. + \item Parsing of observation input was corrected so that observation names can have spaces if the observation name is enclosed in quotations. + \item The GWF Node Property Flow (NPF) Package can optionally calculate the three components of specific discharge. The program calculates these components using an interpolation method based on the XT3D method, the simulated flows across each cell face, and distances from the center of the cell to each cell face. This release contains a fix for a coding error in the calculation of the distance from the center of the cell to the cell face. The fix will affect the calculated specific discharge for model grids that have variable cell sizes. Because specific discharge is used in the calculation of the dispersion coefficients for GWT Models, simulated concentrations with this release may be different from simulated concentrations from previous releases; however, these concentration differences are expected to be minor. + \end{itemize} + + \underline{STRESS PACKAGES} + \begin{itemize} + \item Fixed a bug in the Recharge (RCH) and Evapotranspiration (EVT) Packages that would occur with array-based input (READASARRAYS) combined with the Time-Array Series (TAS) functionality. Because the TAS functionality only works for DIS and DISV models, this fix does not have any effect on models that use DISU discretization. The bug would occur when one or more cells in the upper layer were removed using the IDOMAIN capability. In this special case, the time-array series values did not carry over to the correct locations in the RCH and EVT arrays. This error would be difficult to identify for complex models, because the model would run to completion without any errors. The RCH and EVT Packages were modified to maintain direct correspondence between the \texttt{nodelist} and \texttt{bound} arrays with the arrays provided to the TAS utility. A small and intended consequence of this fix is that the ID2 value written to the binary budget file for array-based RCH and EVT Packages will correspond to the consecutive cell number in the top layer. + \item Recharge and evapotranspiration flows that are written to the GWF Model binary budget file are marked with the text headers ``RCH'' and ``EVT'', respectively. In order to support the array-based input for SSM concentrations, these text headers are marked as ``RCHA'' and ``EVTA'', respectively, if the READASARRAYS option is specified for the package. This change will have no effect on simulation results; however, post processing capabilities may need to be adjusted to account for this minor renaming convention. + \item When the PRINT\_FLOWS option was used with the Source and Sink Mixing (SSM) Package, the cell ID numbers written to the listing file were incorrect. The SSM Package was fixed to output the correct cell ID. + \item Add new AUTO\_FLOW\_REDUCE\_CSV option for the Well Package. If activated, then this option will result in information being written to a comma-separated value file for each well and for each time step in which the extraction rate is reduced. Well extraction rates can be reduced for some groundwater conditions if the AUTO\_FLOW\_REDUCE option is activated. Information is not written for wells in which the extraction rate is equal to the user-specified extraction rate. + \end{itemize} + + \underline{ADVANCED STRESS PACKAGES} + \begin{itemize} + \item The Streamflow Routing (SFR) Package would terminate with a floating point error when calculating the stream depth as a function of flow rate, if the flow rate was slightly negative. Added a conditional check to ensure that the stream depth is calculated as zero if the calculated flow rate is zero or less. + \end{itemize} + + \underline{SOLUTION} + \begin{itemize} + \item The way in which constant head and constant concentration boundary conditions are handled when the conjugate gradient method is used for the linear solve was modified. In previous versions, constant head and concentration conditions would result in an asymmetric coefficient matrix. The program has been modified so that if the conjugate gradient method is selected for the linear solution, then matrix symmetry is preserved by adding adding flows into and out of constant head and concentration cells to the right-hand side of connected cells. + \end{itemize} + + diff --git a/doc/ReleaseNotes/previous/v6.4.0.tex b/doc/ReleaseNotes/previous/v6.4.0.tex new file mode 100644 index 00000000000..a1d51de1705 --- /dev/null +++ b/doc/ReleaseNotes/previous/v6.4.0.tex @@ -0,0 +1,53 @@ + \subsection{Version mf6.4.0--November 30, 2022} + + \underline{NEW FUNCTIONALITY} + \begin{itemize} + \item A new Viscosity (VSC) package for the Groundwater Flow (GWF) Model is introduced in this release. The effects of viscosity are accounted for by updates to intercell conductance, as well as the conductance between the aquifer and head-dependent boundaries, based on simulated concentrations and\/or temperatures. The VSC Package is activated by specifying ``VSC6'' as the file type in a GWF name file. Changes to the code and input may be required in the future in response to user needs and testing. Implementation details for the VSC Package are described in the Supplemental Technical Information guide, which is included with the MODFLOW 6 distribution. For this first implementation, the VSC Package cannot be used for a GWF Model that is connected to another GWF Model with a GWF-GWF Exchange. + \end{itemize} + + \underline{EXAMPLES} + \begin{itemize} + \item A new example called ex-gwt-stallman was added. This new problem uses the GWT Model as a surrogate for simulating heat flow. The example represents one-dimensional heat convection and conduction in the subsurface in response to a periodic temperature boundary condition imposed at land surface. Results from the MODFLOW 6 simulation are in good agreement with an analytical solution. + \end{itemize} + + \textbf{\underline{BUG FIXES AND OTHER CHANGES TO EXISTING FUNCTIONALITY}} \\ + \underline{BASIC FUNCTIONALITY} + \begin{itemize} + \item Corrected programming error in XT3D functionality that could affect coupled flow models or coupled transport models. The XT3D code would result in a memory access error when a child model with a much larger level of refinement was coupled to a coarser parent model. The XT3D code was generalized to handle this situation. + \item Corrected a programming error in which the final message would be written twice to the screen and twice to mfsim.lst when the simulation terminated prematurely. + \item Terminate with error if METHOD or METHODS not specified in time series input files. Prior to this change, the program would continue without an interpolated value for one or more time series records. + \item When a GWF Model and a corresponding GWT model are solved in the same simulation, the GWF Model must be solved before the corresponding GWT model. The GWF Model must also be solved by a different IMS than the GWT Model. There was not a check for this in previous versions and if these conditions were not met, the solution would often not converge or it would give erroneous results. + \item The DISV Package would not raise an error if a model cell was defined as a line. The program was modified to check for the case where the calculated cell area is equal to zero. If the calculated cell area is equal to zero, the program terminates with an error. + \item When searching for a required block in an input file, the program would not terminate with a sensible error message if the end of file was found instead of the required block. Program now indicates that the required block was not found. + \item This release contains a first step toward implementation of generic input routines to read input files. The new input routines were implemented for the DIS, DISV, and DISU Packages of the GWF and GWT Models, for the NPF Package of the GWF Model, and the DSP Package of the GWT Model. Output summaries written to the GWF and GWT Model listing files are different from summaries written using previous versions of MODFLOW 6. For packages that use the new input data model, the IPRN capability of the READARRAY utility (described in mf6io.pdf) is no longer supported as a way to write input arrays to the model listing file. The IPRN capability may not be supported in future versions as the new generic input routines are implemented for other packages. + \item Corrected an error in ZONEBUDGET for MODFLOW 6 that prevented the program from running properly in workspaces that contain one or more spaces in the path. + \end{itemize} + + \underline{INTERNAL FLOW PACKAGES} + \begin{itemize} + \item Corrected programming error in the Time-Variable Hydraulic Conductivity (TVK) Package in which the vertical hydraulic conductivity was not reset properly if the K33OVERK option was invoked in the Node Property Flow (NPF) Package. + \item The Node Property Flow (NPF) Package had an error in how the saturated thickness at the interface between two cells was calculated for a DISU connection that is marked as vertically staggered (IHC = 2). The calculation was corrected so that the thickness for two confined cells is based on the overlap of the cells rather than the average thickness of the two cells. + \end{itemize} + + \underline{STRESS PACKAGES} + \begin{itemize} + \item The Evapotranspiration (EVT) Package was modified to include a new error check if the segmented evapotranspiration capability is active. If the number of ET segments is greater than 1, then the user must specify values for PXDP (as well as PETM). For a cell, PXDP is a one-dimensional array of size NSEG - 1. Values in this array must be greater than zero and less than one. Furthermore, the values in PXDP must increase monotonically. The program now checks for these conditions and terminates with an error if these conditions are not met. The segmented ET capability can be used for list-based EVT Package input. Provided that the PXDP conditions are met, this new error check should have no effect on simulation results. + \item The Evapotranspiration (EVT) Package would throw an index error when SURF\_RATE\_SPECIFIED was specified in the OPTIONS block and NSEG was set equal to 1. The code now supports this combination of input options. + \end{itemize} + + \underline{ADVANCED STRESS PACKAGES} + \begin{itemize} + \item When the LAK Package was used with a combination of outlets that routed water to another lake and outlets that did not, then the budget information stored for the LAK Package had uninitialized records for LAK to LAK flows. These uninitialized records were used by the LKT Package and possibly other programs. The LAK to LAK budget information was modified to include only valid records. + \item When a WITHDRAWAL value was specified for lakes, only the withdrawal value for the last lake would be reported in budget files, lake budget tables, and in lake withdrawal observations. This error would also be propagated to the GWT Lake Transport (LKT) Package, if active. This error would only show up for models with more than one lake and if the lake withdrawal term was included. + \item When lakes were assigned with the STATUS CONSTANT setting to prescribe the lake stage, the CONSTANT term used in the lake budget table was tabulated using an incorrect sign for aquifer leakage. This error would result in inaccurate budget tables. The program modified to use the correct leakage values for calculating the CONSTANT term. + \item There were several problems in the observation utility for the Streamflow Transport (SFT), Lake Transport (LKT), Multi-Aquifer Well Transport (MWT), and Unsaturated Zone Transport (UZT) Packages. These issues were corrected as well as the descriptions for the observations in the user input and output guide. + \item The BUDGETCSV option for the advanced stress packages would intermittently cause an error due to a variable not being set properly. The BUDGETCSV option did not work at all for the GWT advanced packages. The BUDGETCSV option was fixed to work properly. + \item For multi-layer GWF Models, the UZF Package should generally have UZF cells assigned to each GWF cell that can be dry or partially saturated. If a UZF cell was assigned to an upper layer of a GWF Model, but not to underlying GWF layers, then outflow from the upper UZF cell would not always flow to the water table. The program was modified so that outflow from UZF cells is transferred into the GWF Model when there are no underlying UZF cells. This routing of water to GWF may not work properly unless the Newton-Raphson formulation is active. + \end{itemize} + + \underline{EXCHANGES} + \begin{itemize} + \item The GWT-GWT Exchange did not work when the XT3D\_OFF option was specified. The program was fixed so that the XT3D dispersion terms can be shut off for a GWT-GWT Exchange. GWT-GWT Exchange keywords were renamed from ADVSCHEME, XT3D\_OFF, and XT3D\_RHS to ADV\_SCHEME, DSP\_XT3D\_OFF, and DSP\_XT3D\_RHS, respectively, to more clearly indicate how the keywords relate to the underlying processes. + \end{itemize} + + diff --git a/doc/ReleaseNotes/previous/v6.4.1.tex b/doc/ReleaseNotes/previous/v6.4.1.tex new file mode 100644 index 00000000000..c3d5721c0cd --- /dev/null +++ b/doc/ReleaseNotes/previous/v6.4.1.tex @@ -0,0 +1,8 @@ + \subsection{Version mf6.4.1--December 9, 2022} + + \textbf{\underline{BUG FIXES AND OTHER CHANGES TO EXISTING FUNCTIONALITY}} \\ + + \underline{INTERNAL FLOW PACKAGES} + \begin{itemize} + \item The Viscosity (VSC) Package introduced in version 6.4.0 would report an error for certain types of stress packages if the IDOMAIN capability was activated by the user. In some cases, the error message would indicate that a boundary was not assigned to a valid cell. This invalid error would prevent the simulation from proceeding. The error was corrected by removing the invalid check so that the program can proceed normally. + \end{itemize} diff --git a/doc/ReleaseNotes/v6.4.1.tex b/doc/ReleaseNotes/v6.5.0.tex similarity index 56% rename from doc/ReleaseNotes/v6.4.1.tex rename to doc/ReleaseNotes/v6.5.0.tex index c53a1dcca09..e30621c0af9 100644 --- a/doc/ReleaseNotes/v6.4.1.tex +++ b/doc/ReleaseNotes/v6.5.0.tex @@ -1,15 +1,14 @@ % Use this template for starting initializing the release notes % after a release has just been made. -%\begin{itemize} - \item Version mf6.4.1--December 9, 2022 + \item Version mf6.x.x--Month xx, 202x - %\underline{NEW FUNCTIONALITY} - %\begin{itemize} - % \item xxx + \underline{NEW FUNCTIONALITY} + \begin{itemize} + \item xxx % \item xxx % \item xxx - %\end{itemize} + \end{itemize} %\underline{EXAMPLES} %\begin{itemize} @@ -19,20 +18,19 @@ %\end{itemize} \textbf{\underline{BUG FIXES AND OTHER CHANGES TO EXISTING FUNCTIONALITY}} \\ - - %\underline{BASIC FUNCTIONALITY} - %\begin{itemize} - % \item + \underline{BASIC FUNCTIONALITY} + \begin{itemize} + \item xxx % \item xxx % \item xxx - %\end{itemize} + \end{itemize} - \underline{INTERNAL FLOW PACKAGES} - \begin{itemize} - \item The Viscosity (VSC) Package introduced in version 6.4.0 would report an error for certain types of stress packages if the IDOMAIN capability was activated by the user. In some cases, the error message would indicate that a boundary was not assigned to a valid cell. This invalid error would prevent the simulation from proceeding. The error was corrected by removing the invalid check so that the program can proceed normally. + %\underline{INTERNAL FLOW PACKAGES} + %\begin{itemize} % \item xxx % \item xxx - \end{itemize} + % \item xxx + %\end{itemize} %\underline{STRESS PACKAGES} %\begin{itemize} @@ -62,5 +60,3 @@ % \item xxx %\end{itemize} - -%\end{itemize} From b518b9a3d895630ad82a4a529cfd8c40c441b473 Mon Sep 17 00:00:00 2001 From: w-bonelli Date: Tue, 13 Dec 2022 15:55:48 -0500 Subject: [PATCH 004/123] docs(readme): misc updates (#1113) * add latest release badge * remove badge for removed nightly build workflow * mention code.json in Nightly Builds section * expand explanation of branching/releases --- README.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 41c81a7c0ea..f4e8f192285 100644 --- a/README.md +++ b/README.md @@ -5,27 +5,26 @@ This is the development repository for the USGS MODFLOW 6 Hydrologic Model. The ### Version 6.5.0 Release Candidate +[![GitHub release](https://img.shields.io/github/release/MODFLOW-USGS/modflow6.svg)](https://github.com/MODFLOW-USGS/modflow6/releases/latest) [![MODFLOW 6 continuous integration](https://github.com/MODFLOW-USGS/modflow6/actions/workflows/ci.yml/badge.svg)](https://github.com/MODFLOW-USGS/modflow6/actions/workflows/ci.yml) [![MODFLOW 6 documentation](https://github.com/MODFLOW-USGS/modflow6/actions/workflows/docs.yml/badge.svg)](https://github.com/MODFLOW-USGS/modflow6/actions/workflows/docs.yml) [![MODFLOW 6 large models](https://github.com/MODFLOW-USGS/modflow6/actions/workflows/large.yml/badge.svg)](https://github.com/MODFLOW-USGS/modflow6/actions/workflows/large.yml) - [![MODFLOW 6 intel nightly build](https://github.com/MODFLOW-USGS/modflow6-nightly-build/actions/workflows/nightly-build-intel.yml/badge.svg)](https://github.com/MODFLOW-USGS/modflow6-nightly-build/actions/workflows/nightly-build-intel.yml) -[![MODFLOW 6 nightly build](https://github.com/MODFLOW-USGS/modflow6-nightly-build/actions/workflows/nightly-build.yml/badge.svg)](https://github.com/MODFLOW-USGS/modflow6-nightly-build/actions/workflows/nightly-build.yml) ## Branches This repository contains branches of ongoing MODFLOW 6 development. The two main branches in this repository are: -* `master` -- the state of the MODFLOW 6 repository corresponding to the last official USGS release -* `develop` -- the current development version of the MODFLOW 6 program +* `master`: the state of the MODFLOW 6 repository corresponding to the last official USGS release +* `develop`: the current development version of the MODFLOW 6 program -The `develop` branch is under active and frequent updates by the MODFLOW development team and other interested contributors. We follow a fork and pull request workflow and require that pull requests pass our test suite before they are considered a possible candidate to merge into develop. +The `develop` branch is under active and frequent updates by the MODFLOW development team and other interested contributors. We follow a fork and pull request workflow and require that pull requests pass our test suite before they are considered a possible candidate to merge into `develop`. The `master` branch is only updated immediately prior to each new release. This repository may contain other branches with various levels of development code; however, these branches may be merged into develop or deleted without notice. ## Nightly Builds -The `develop` branch often contains bug fixes and new features that are not yet part of the official USGS release. The updated user guide (mf6io.pdf) and binary executables for several operating systems of the `develop` branch are built and posted each night to the [nightly-build repository](https://github.com/MODFLOW-USGS/modflow6-nightly-build/releases). +This repository's `develop` branch often contains bug fixes and new features that are not yet part of the official USGS release. Binaries for Linux, macOS and Windows are built from the `develop` branch and posted to the [`MODFLOW-USGS/modflow6-nightly-build` repository](https://github.com/MODFLOW-USGS/modflow6-nightly-build/releases) each night. The updated user guide `mf6io.pdf` is also included, as well as the `code.json` metadata file. ## Releases From c0b02d476044ecd6db7046614d8be41387f51926 Mon Sep 17 00:00:00 2001 From: Eric Morway Date: Wed, 14 Dec 2022 12:26:40 -0800 Subject: [PATCH 005/123] fix(typo): patch-up for relatively inconsequential typo in gwt-sft.dfn (#1115) * fix(typo): patch-up for relatively inconsequential typo * one more * another --- doc/mf6io/mf6ivar/dfn/gwt-sft.dfn | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/mf6io/mf6ivar/dfn/gwt-sft.dfn b/doc/mf6io/mf6ivar/dfn/gwt-sft.dfn index 73d7e206677..dc98b6a2bed 100644 --- a/doc/mf6io/mf6ivar/dfn/gwt-sft.dfn +++ b/doc/mf6io/mf6ivar/dfn/gwt-sft.dfn @@ -50,7 +50,7 @@ name print_concentration type keyword reader urword optional true -longname print calculated stages to listing file +longname print calculated concentrations to listing file description REPLACE print_concentration {'{#1}': 'reach', '{#2}': 'concentration', '{#3}': 'CONCENTRATION'} block options @@ -87,7 +87,7 @@ in_record true reader urword tagged true optional false -longname stage keyword +longname concentration keyword description keyword to specify that record corresponds to concentration. block options @@ -255,7 +255,7 @@ longname obs6 input filename description REPLACE obs6_filename {'{#1}': 'SFT'} -# --------------------- gwt lkt packagedata --------------------- +# --------------------- gwt sft packagedata --------------------- block packagedata name packagedata From 92aefa218e024d6784fda5aacdebb15f72feae07 Mon Sep 17 00:00:00 2001 From: Eric Morway Date: Fri, 16 Dec 2022 05:25:55 -0800 Subject: [PATCH 006/123] fix(sft): clean-up some minor typos in SFT (#1117) * fix(sft): clean-up some minor typos in SFT * comment cleanup --- src/Model/GroundWaterTransport/gwt1sft1.f90 | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Model/GroundWaterTransport/gwt1sft1.f90 b/src/Model/GroundWaterTransport/gwt1sft1.f90 index f9fdfabee9c..8b91cb3fbc8 100644 --- a/src/Model/GroundWaterTransport/gwt1sft1.f90 +++ b/src/Model/GroundWaterTransport/gwt1sft1.f90 @@ -91,7 +91,7 @@ module GwtSftModule subroutine sft_create(packobj, id, ibcnum, inunit, iout, namemodel, pakname, & fmi) ! ****************************************************************************** -! mwt_create -- Create a New MWT Package +! sft_create -- Create a New SFT Package ! ****************************************************************************** ! ! SPECIFICATIONS: @@ -106,19 +106,19 @@ subroutine sft_create(packobj, id, ibcnum, inunit, iout, namemodel, pakname, & character(len=*), intent(in) :: pakname type(GwtFmiType), pointer :: fmi ! -- local - type(GwtSftType), pointer :: lktobj + type(GwtSftType), pointer :: sftobj ! ------------------------------------------------------------------------------ ! ! -- allocate the object and assign values to object variables - allocate (lktobj) - packobj => lktobj + allocate (sftobj) + packobj => sftobj ! ! -- create name and memory path call packobj%set_names(ibcnum, namemodel, pakname, ftype) packobj%text = text ! ! -- allocate scalars - call lktobj%allocate_scalars() + call sftobj%allocate_scalars() ! ! -- initialize package call packobj%pack_initialize() @@ -133,7 +133,7 @@ subroutine sft_create(packobj, id, ibcnum, inunit, iout, namemodel, pakname, & ! -- Store pointer to flow model interface. When the GwfGwt exchange is ! created, it sets fmi%bndlist so that the GWT model has access to all ! the flow packages - lktobj%fmi => fmi + sftobj%fmi => fmi ! ! -- return return @@ -141,7 +141,7 @@ end subroutine sft_create subroutine find_sft_package(this) ! ****************************************************************************** -! find corresponding lkt package +! find corresponding sft package ! ****************************************************************************** ! ! SPECIFICATIONS: @@ -266,7 +266,7 @@ end subroutine find_sft_package subroutine sft_fc_expanded(this, rhs, ia, idxglo, amatsln) ! ****************************************************************************** ! sft_fc_expanded -- this will be called from GwtAptType%apt_fc_expanded() -! in order to add matrix terms specifically for LKT +! in order to add matrix terms specifically for SFT ! **************************************************************************** ! ! SPECIFICATIONS: From ebf33d915d7cc5d3ad76dd15de4ac31d21afc6f4 Mon Sep 17 00:00:00 2001 From: w-bonelli Date: Mon, 19 Dec 2022 10:23:22 -0500 Subject: [PATCH 007/123] ci: add nightly job to cache intel compilers (#1118) --- .github/workflows/large.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/workflows/large.yml b/.github/workflows/large.yml index ed253c76369..3abe23604ff 100644 --- a/.github/workflows/large.yml +++ b/.github/workflows/large.yml @@ -3,6 +3,16 @@ on: schedule: - cron: '0 6 * * *' # run at 6 AM UTC every day jobs: + cache_ifort: + name: Cache Intel OneAPI compilers + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ ubuntu-22.04, macos-12, windows-2022 ] + steps: + - name: Setup Intel Fortran + uses: modflowpy/install-intelfortran-action@v1 test: name: Test runs-on: ubuntu-22.04 From 455aff1da5c60362af705baf3bc316c37f3feded Mon Sep 17 00:00:00 2001 From: Eric Morway Date: Wed, 21 Dec 2022 13:36:58 -0800 Subject: [PATCH 008/123] fix(sfr): evaporation from sfr reaches should use wetted top width, not rwid (#1114) --- autotest/test_gwf_sfr_evap.py | 483 +++++++++++++++++++++++++ doc/ReleaseNotes/v6.5.0.tex | 2 +- src/Model/GroundWaterFlow/gwf3sfr8.f90 | 6 +- 3 files changed, 488 insertions(+), 3 deletions(-) create mode 100644 autotest/test_gwf_sfr_evap.py diff --git a/autotest/test_gwf_sfr_evap.py b/autotest/test_gwf_sfr_evap.py new file mode 100644 index 00000000000..8f57ab4c9bd --- /dev/null +++ b/autotest/test_gwf_sfr_evap.py @@ -0,0 +1,483 @@ +# Test evap in SFR reaches (no interaction with gwf) + +# Imports + +import os +import sys + +import numpy as np +import pytest + +try: + import flopy +except: + msg = "Error. FloPy package is not available.\n" + msg += "Try installing using the following command:\n" + msg += " pip install flopy" + raise Exception(msg) + +from framework import testing_framework +from simulation import Simulation + +ex = ["sfr-evap"] +exdirs = [] +for s in ex: + exdirs.append(os.path.join("temp", s)) + + +# Model units +length_units = "m" +time_units = "days" + +# model domain and grid definition +Lx = 500.0 +Ly = 300.0 +nrow = 3 +ncol = 5 +nlay = 1 +delr = Lx / ncol +delc = Ly / nrow +xmax = ncol * delr +ymax = nrow * delc +X, Y = np.meshgrid( + np.linspace(delr / 2, xmax - delr / 2, ncol), + np.linspace(ymax - delc / 2, 0 + delc / 2, nrow), +) +ibound = np.ones((nlay, nrow, ncol)) +# Because eqn uses negative values in the Y direction, need to do a little manipulation +Y_m = -1 * np.flipud(Y) +top = np.array( + [ + [101.25, 101.00, 100.75, 100.50, 100.25], + [101.00, 100.75, 100.50, 100.25, 100.00], + [101.25, 101.00, 100.75, 100.50, 100.25], + ] +) + +botm = np.zeros(top.shape) +strthd = top - 1.0 + +# NPF parameters +k11 = 1 +ss = 0.00001 +sy = 0.20 +hani = 1 +laytyp = 1 + +# Package boundary conditions +surf_Q_in = 86400 # 1 m^3/s +sfr_evaprate = 0.1 +streambed_K = 0.0 + +# time params +steady = {0: True, 1: False} +transient = {0: False, 1: True} +nstp = [1, 1] +tsmult = [1, 1] +perlen = [1, 1] + +nouter, ninner = 1000, 300 +hclose, rclose, relax = 1e-3, 1e-4, 0.97 + +# +# MODFLOW 6 flopy GWF object +# + + +def build_model(idx, dir): + # Base simulation and model name and workspace + ws = dir + name = ex[idx] + + print("Building model...{}".format(name)) + + # generate names for each model + gwfname_trapezoidal = "gwf-" + name + "-t" + gwfname_rectangular = "gwf-" + name + "-r" + + sim = flopy.mf6.MFSimulation( + sim_name=name, sim_ws=ws, exe_name="mf6", version="mf6" + ) + + # Instantiating time discretization + tdis_rc = [] + for i in range(len(nstp)): + tdis_rc.append((perlen[i], nstp[i], tsmult[i])) + + flopy.mf6.ModflowTdis( + sim, nper=len(nstp), perioddata=tdis_rc, time_units=time_units + ) + + gwft = flopy.mf6.ModflowGwf( + sim, + modelname=gwfname_trapezoidal, + save_flows=True, + newtonoptions="newton", + ) + + # Instantiating solver + ims = flopy.mf6.ModflowIms( + sim, + print_option="ALL", + outer_dvclose=hclose, + outer_maximum=nouter, + under_relaxation="cooley", + inner_maximum=ninner, + inner_dvclose=hclose, + rcloserecord=rclose, + linear_acceleration="BICGSTAB", + scaling_method="NONE", + reordering_method="NONE", + relaxation_factor=relax, + filename="{}.ims".format(gwfname_trapezoidal), + ) + sim.register_ims_package(ims, [gwfname_trapezoidal]) + + # Instantiate discretization package + flopy.mf6.ModflowGwfdis( + gwft, + length_units=length_units, + nlay=nlay, + nrow=nrow, + ncol=ncol, + delr=delr, + delc=delc, + top=top, + botm=botm, + ) + + # Instantiate node property flow package + flopy.mf6.ModflowGwfnpf( + gwft, + save_specific_discharge=True, + icelltype=1, # >0 means saturated thickness varies with computed head + k=k11, + ) + + # Instantiate storage package + flopy.mf6.ModflowGwfsto( + gwft, + save_flows=False, + iconvert=laytyp, + ss=ss, + sy=sy, + steady_state=steady, + transient=transient, + ) + + # Instantiate initial conditions package + flopy.mf6.ModflowGwfic(gwft, strt=strthd) + + # Instantiate output control package + flopy.mf6.ModflowGwfoc( + gwft, + budget_filerecord=f"{gwfname_trapezoidal}.cbc", + head_filerecord=f"{gwfname_trapezoidal}.hds", + headprintrecord=[("COLUMNS", 10, "WIDTH", 15, "DIGITS", 6, "GENERAL")], + saverecord=[("HEAD", "ALL"), ("BUDGET", "ALL")], + printrecord=[("HEAD", "ALL"), ("BUDGET", "ALL")], + ) + + # Instantiate streamflow routing package + # Determine the middle row and store in rMid (account for 0-base) + rMid = 1 + # sfr data + nreaches = ncol + rlen = delr + rwid = 9.0 + roughness = 0.035 + rbth = 1.0 + rhk = streambed_K + strm_up = 100 + strm_dn = 99 + slope = (strm_up - strm_dn) / ((ncol - 1) * delr) + ustrf = 1.0 + ndv = 0 + strm_incision = 1.0 + + # use trapezoidal cross-section for channel geometry + x_coord = [0.0, 2.0, 4.0, 5.0, 7.0, 9.0] + x_xsec = [val / rwid for val in x_coord] + y_xsec = [0.66666667, 0.33333333, 0.0, 0.0, 0.33333333, 0.66666667] + x_sec_tab = [[x, h] for x, h, in zip(x_xsec, y_xsec)] + + sfr_xsec_table_name = "{}.xsec.tab".format(gwfname_trapezoidal) + crosssections = [] + for n in range(nreaches): + crosssections.append([n, sfr_xsec_table_name]) + + flopy.mf6.ModflowUtlsfrtab( + gwft, + nrow=len(x_xsec), + ncol=2, + table=x_sec_tab, + filename=sfr_xsec_table_name, + pname=f"sfrxsectable", + ) + + packagedata = [] + for irch in range(nreaches): + nconn = 1 + if 0 < irch < nreaches - 1: + nconn += 1 + rp = [ + irch, + (0, rMid, irch), + rlen, + rwid, + slope, + top[rMid, irch] - strm_incision, + rbth, + rhk, + roughness, + nconn, + ustrf, + ndv, + ] + packagedata.append(rp) + + connectiondata = [] + for irch in range(nreaches): + rc = [irch] + if irch > 0: + rc.append(irch - 1) + if irch < nreaches - 1: + rc.append(-(irch + 1)) + connectiondata.append(rc) + + sfrbndx = [] + for i in np.arange(nreaches): + if i == 0: + sfrbndx.append([i, "INFLOW", surf_Q_in]) + sfrbndx.append([i, "EVAPORATION", sfr_evaprate]) + + sfr_perioddata = {0: sfrbndx} + + # Instantiate SFR observation points + sfr_obs = { + "{}.sfrobs".format(gwfname_trapezoidal): [ + ("rch1_in", "ext-inflow", 1), # For now, these need to be 1-based + ("rch1_rain", "rainfall", 1), + ("rch1_evap", "evaporation", 1), + ("rch1_runoff", "runoff", 1), + ("rch1_flow", "sfr", 1), + ("rch1_out", "downstream-flow", 1), + ("rch1_xsec_area", "wet-area", 1), + ("rch1_top_width", "wet-width", 1), + ("rch1_depth", "depth", 1), + ("rch1_stg", "stage", 1), + ], + "digits": 8, + "print_input": True, + "filename": name + ".sfr.obs", + } + + budpth = f"{gwfname_trapezoidal}.sfr.cbc" + flopy.mf6.ModflowGwfsfr( + gwft, + save_flows=True, + print_stage=True, + print_flows=True, + print_input=True, + unit_conversion=1.0 * 86400, + budget_filerecord=budpth, + mover=False, + nreaches=nreaches, + packagedata=packagedata, + connectiondata=connectiondata, + crosssections=crosssections, + perioddata=sfr_perioddata, + observations=sfr_obs, + pname="SFR-1", + filename="{}.sfr".format(gwfname_trapezoidal), + ) + + # --------------------------------------------------------------------------- + # Add a second GWF model that simulates a typical rectangular stream channel + # --------------------------------------------------------------------------- + + gwfr = flopy.mf6.ModflowGwf( + sim, + modelname=gwfname_rectangular, + save_flows=True, + newtonoptions="newton", + ) + + # Instantiating solver + # (use same settings as above) + ims2 = flopy.mf6.ModflowIms( + sim, + print_option="ALL", + outer_dvclose=hclose, + outer_maximum=nouter, + under_relaxation="cooley", + inner_maximum=ninner, + inner_dvclose=hclose, + rcloserecord=rclose, + linear_acceleration="BICGSTAB", + scaling_method="NONE", + reordering_method="NONE", + relaxation_factor=relax, + filename="{}.ims".format(gwfname_trapezoidal), + ) + sim.register_ims_package(ims2, [gwfname_rectangular]) + + # Instantiate discretization package + flopy.mf6.ModflowGwfdis( + gwfr, + length_units=length_units, + nlay=nlay, + nrow=nrow, + ncol=ncol, + delr=delr, + delc=delc, + top=top, + botm=botm, + ) + + # Instantiate node property flow package + flopy.mf6.ModflowGwfnpf( + gwfr, + save_specific_discharge=True, + icelltype=1, # >0 means saturated thickness varies with computed head + k=k11, + ) + + # Instantiate storage package + flopy.mf6.ModflowGwfsto( + gwfr, + save_flows=False, + iconvert=laytyp, + ss=ss, + sy=sy, + steady_state=steady, + transient=transient, + ) + + # Instantiate initial conditions package + flopy.mf6.ModflowGwfic(gwfr, strt=strthd) + + # Instantiate output control package + flopy.mf6.ModflowGwfoc( + gwfr, + budget_filerecord=f"{gwfname_rectangular}.cbc", + head_filerecord=f"{gwfname_rectangular}.hds", + headprintrecord=[("COLUMNS", 10, "WIDTH", 15, "DIGITS", 6, "GENERAL")], + saverecord=[("HEAD", "ALL"), ("BUDGET", "ALL")], + printrecord=[("HEAD", "ALL"), ("BUDGET", "ALL")], + ) + + # Instantiate streamflow routing package + # Uses same datasets established above except does not include x-sections + budpth = f"{gwfname_rectangular}.sfr.cbc" + flopy.mf6.ModflowGwfsfr( + gwfr, + save_flows=True, + print_stage=True, + print_flows=True, + print_input=True, + unit_conversion=1.0 * 86400, + budget_filerecord=budpth, + mover=False, + nreaches=nreaches, + packagedata=packagedata, + connectiondata=connectiondata, + perioddata=sfr_perioddata, + pname="SFR-2", + filename="{}.sfr".format(gwfname_rectangular), + ) + + return sim, None + + +def eval_results(sim): + print("evaluating results...") + + # read flow results from model + name = ex[sim.idxsim] + gwfname_t = "gwf-" + name + "-t" + gwfname_r = "gwf-" + name + "-r" + + fname = gwfname_t + ".sfr.cbc" + fname = os.path.join(sim.simpath, fname) + assert os.path.isfile(fname) + + sfrobj = flopy.utils.binaryfile.CellBudgetFile(fname, precision="double") + sfrevap = sfrobj.get_data(text="EVAPORATION") + + # Extract evap + sim_evap = [] + for i in range(ncol): + sim_evap.append(sfrevap[-1][i][2]) + + sim_evap = np.array(sim_evap) + + # Establish known answer: + stored_strm_evap = np.array( + [ + -66.01388942706727, + -65.99953139524196, + -65.98517173673817, + -65.97081044940869, + -65.95644753110243, + ] + ) + + msg = "The SFR evaporation test with n-point x-section (trapezoid) is failing." + assert np.allclose(stored_strm_evap, sim_evap, atol=1e-4), msg + + # Now check results from standard rectangular x-section setup (not an n-point channel) + fname2 = gwfname_r + ".sfr.cbc" + fname2 = os.path.join(sim.simpath, fname2) + assert os.path.isfile(fname2) + + sfrobj = flopy.utils.binaryfile.CellBudgetFile(fname2, precision="double") + sfrevap_r = sfrobj.get_data(text="EVAPORATION") + + # Extract evap + sim_evap_r = [] + for i in range(ncol): + sim_evap_r.append(sfrevap_r[-1][i][2]) + + sim_evap_r = np.array(sim_evap_r) + + # Establish known answer: + stored_strm_evap_r = np.array([-90.0] * 5) + + msg = "The SFR evaporation test with rectangular geometry is failing." + assert np.allclose(stored_strm_evap_r, sim_evap_r, atol=1e-4), msg + + +# - No need to change any code below +@pytest.mark.parametrize( + "idx, dir", + list(enumerate(exdirs)), +) +def test_mf6model(idx, dir): + # initialize testing framework + test = testing_framework() + + # build the model + test.build_mf6_models(build_model, idx, dir) + + # run the test model + test.run_mf6(Simulation(dir, exfunc=eval_results, idxsim=idx)) + + +def main(): + # initialize testing framework + test = testing_framework() + + # run the test model + for idx, dir in enumerate(exdirs): + test.build_mf6_models(build_model, idx, dir) + sim = Simulation(dir, exfunc=eval_results, idxsim=idx) + test.run_mf6(sim) + + +if __name__ == "__main__": + # print message + print(f"standalone run of {os.path.basename(__file__)}") + + # run main routine + main() diff --git a/doc/ReleaseNotes/v6.5.0.tex b/doc/ReleaseNotes/v6.5.0.tex index e30621c0af9..dde1bd149b9 100644 --- a/doc/ReleaseNotes/v6.5.0.tex +++ b/doc/ReleaseNotes/v6.5.0.tex @@ -20,7 +20,7 @@ \textbf{\underline{BUG FIXES AND OTHER CHANGES TO EXISTING FUNCTIONALITY}} \\ \underline{BASIC FUNCTIONALITY} \begin{itemize} - \item xxx + \item When n-point cross-sections are active in SFR, the evaporation calculation uses the variable rwid (see MF6io.pdf) to calculate the total amount of evaporation even though the wetted topwidth is less than rwid. For example, using a trapezoidal cross-section geometry with an rwid of 10, an rlen of 100, and prescribed evaporation rate of 0.1, the calculated evaporative losses would equal 100 even when the wetted top width was only 5.0 units wide. With this bug fix, the evaporation in this example results in only 50 units of evaporation loss. A new autotests confirms the evaporation calculation using an n-point cross-section and common rectangular geometries in the same simulation. It is also worth mentioning that the precipitation calculation currently uses rwid. Since the precipitation falling outside the margins of the wetted top width but within rwid would likely be accumulated in a channel, it makes sense to leave this calculation as is. % \item xxx % \item xxx \end{itemize} diff --git a/src/Model/GroundWaterFlow/gwf3sfr8.f90 b/src/Model/GroundWaterFlow/gwf3sfr8.f90 index 649445fa55e..c470fb7c042 100644 --- a/src/Model/GroundWaterFlow/gwf3sfr8.f90 +++ b/src/Model/GroundWaterFlow/gwf3sfr8.f90 @@ -3272,6 +3272,7 @@ subroutine sfr_solve(this, n, h, hcof, rhs, update) integer(I4B) :: ibflg real(DP) :: hgwf real(DP) :: sa + real(DP) :: sa_wet real(DP) :: qu real(DP) :: qi real(DP) :: qr @@ -3355,9 +3356,10 @@ subroutine sfr_solve(this, n, h, hcof, rhs, update) this%usflow(n) = qu ! -- calculate remaining terms sa = this%calc_surface_area(n) + sa_wet = this%calc_surface_area_wet(n, this%depth(n)) qi = this%inflow(n) qr = this%rain(n) * sa - qe = this%evap(n) * sa + qe = this%evap(n) * sa_wet qro = this%runoff(n) ! ! -- Water mover term; assume that it goes in at the upstream end of the reach @@ -3831,7 +3833,7 @@ subroutine sfr_calc_qsource(this, n, depth, qsrc) a = this%calc_surface_area(n) ae = this%calc_surface_area_wet(n, depth) qr = this%rain(n) * a - qe = this%evap(n) * a + qe = this%evap(n) * ae ! ! -- calculate mover term qfrommvr = DZERO From f4fdf8049266e560ce43e0afced5e4d6e669a634 Mon Sep 17 00:00:00 2001 From: mjr-deltares <45555666+mjr-deltares@users.noreply.github.com> Date: Thu, 22 Dec 2022 16:18:33 +0100 Subject: [PATCH 009/123] refactor(gwfgwf) (#1121) * - split model budget routines in Exchange for parallel dev - allocate NPF angle arrays at 0 size when not present * - fprettify * - suppressoutput uninitialized... (it was always 0) * - fix: reverse nodes for bd w.r.t. model2 * - fprettify * - collateral from refactoring... * arghh, one more... --- autotest/test_gwf_ifmod_xt3d01.py | 2 +- src/Exchange/GwfGwfExchange.f90 | 255 ++++++++++--------------- src/Model/GroundWaterFlow/gwf3npf8.f90 | 6 +- 3 files changed, 104 insertions(+), 159 deletions(-) diff --git a/autotest/test_gwf_ifmod_xt3d01.py b/autotest/test_gwf_ifmod_xt3d01.py index a34da82307c..dbbd2d74349 100644 --- a/autotest/test_gwf_ifmod_xt3d01.py +++ b/autotest/test_gwf_ifmod_xt3d01.py @@ -475,7 +475,7 @@ def exact(x): ), "exchange observations do not match parent exchange flows" assert np.allclose( obsvalues, -child_exchange_flows - ), "exchange observations do not match chile exchange flows" + ), "exchange observations do not match child exchange flows" # Read the lumped boundname observations values fpth = os.path.join(sim.simpath, "gwf_obs_boundnames.csv") diff --git a/src/Exchange/GwfGwfExchange.f90 b/src/Exchange/GwfGwfExchange.f90 index 2af258c19ab..5e33e8dca28 100644 --- a/src/Exchange/GwfGwfExchange.f90 +++ b/src/Exchange/GwfGwfExchange.f90 @@ -103,11 +103,12 @@ module GwfGwfExchangeModule procedure, private :: rewet procedure, private :: qcalc procedure :: gwf_gwf_bdsav + procedure, private :: gwf_gwf_bdsav_model procedure, private :: gwf_gwf_df_obs procedure, private :: gwf_gwf_rp_obs procedure, public :: gwf_gwf_save_simvals procedure, private :: gwf_gwf_calc_simvals - procedure, public :: gwf_gwf_set_spdis + procedure, public :: gwf_gwf_set_simvals_to_npf procedure, private :: validate_exchange procedure :: gwf_gwf_add_to_flowja end type GwfExchangeType @@ -786,8 +787,8 @@ subroutine gwf_gwf_cq(this, icnvg, isuppress_output, isolnid) ! -- calculate flow and store in simvals call this%gwf_gwf_calc_simvals() ! - ! -- calculate specific discharge and set to model - call this%gwf_gwf_set_spdis() + ! -- set rates to model edges in npf for spdis calculation + call this%gwf_gwf_set_simvals_to_npf() ! ! -- add exchange flow to model 1 and 2 flowja array diagonal position call this%gwf_gwf_add_to_flowja() @@ -854,7 +855,7 @@ end subroutine gwf_gwf_add_to_flowja !> @brief Calculate specific discharge from flow rates !< and set them to the models - subroutine gwf_gwf_set_spdis(this) + subroutine gwf_gwf_set_simvals_to_npf(this) use ConstantsModule, only: DZERO, DPIO180 use GwfNpfModule, only: thksatnm class(GwfExchangeType) :: this !< GwfExchangeType @@ -963,7 +964,7 @@ subroutine gwf_gwf_set_spdis(this) end do ! return - end subroutine gwf_gwf_set_spdis + end subroutine gwf_gwf_set_simvals_to_npf !> @ brief Budget !! @@ -1015,47 +1016,76 @@ end subroutine gwf_gwf_bd !< subroutine gwf_gwf_bdsav(this) ! -- modules - use ConstantsModule, only: DZERO, LENBUDTXT, LENPACKAGENAME - use TdisModule, only: kstp, kper ! -- dummy class(GwfExchangeType) :: this !< GwfExchangeType ! -- local - character(len=LENBOUNDNAME) :: bname - character(len=LENPACKAGENAME + 4) :: packname1 - character(len=LENPACKAGENAME + 4) :: packname2 + integer(I4B) :: icbcfl, ibudfl + ! + ! -- budget for model1 + call this%gwf_gwf_bdsav_model(this%gwfmodel1) + ! + ! -- budget for model2 + call this%gwf_gwf_bdsav_model(this%gwfmodel2) + ! + ! -- Set icbcfl, ibudfl to zero so that flows will be printed and + ! saved, if the options were set in the MVR package + icbcfl = 1 + ibudfl = 1 + ! + ! -- Call mvr bd routine + if (this%inmvr > 0) call this%mvr%mvr_bdsav(icbcfl, ibudfl, 0) + ! + ! -- Calculate and write simulated values for observations + if (this%inobs /= 0) then + call this%gwf_gwf_save_simvals() + end if + ! + ! -- return + return + end subroutine gwf_gwf_bdsav + + subroutine gwf_gwf_bdsav_model(this, model) + use ConstantsModule, only: DZERO, LENBUDTXT, LENPACKAGENAME + use TdisModule, only: kstp, kper + class(GwfExchangeType) :: this !< this exchange + type(GwfModelType), pointer :: model !< the model to save budget for + ! local + character(len=LENPACKAGENAME + 4) :: packname character(len=LENBUDTXT), dimension(1) :: budtxt + type(TableType), pointer :: output_tab + class(GwfModelType), pointer :: nbr_model character(len=20) :: nodestr + character(len=LENBOUNDNAME) :: bname integer(I4B) :: ntabrows integer(I4B) :: nodeu integer(I4B) :: i, n1, n2, n1u, n2u - integer(I4B) :: ibinun1, ibinun2 - integer(I4B) :: icbcfl, ibudfl + integer(I4B) :: ibinun real(DP) :: ratin, ratout, rrate - integer(I4B) :: isuppress_output - ! -- formats - ! - ! -- initialize local variables - isuppress_output = 0 + logical(LGP) :: is_for_model1 + budtxt(1) = ' FLOW-JA-FACE' - packname1 = 'EXG '//this%name - packname1 = adjustr(packname1) - packname2 = 'EXG '//this%name - packname2 = adjustr(packname2) + packname = 'EXG '//this%name + packname = adjustr(packname) + if (associated(model, this%gwfmodel1)) then + output_tab => this%outputtab1 + nbr_model => this%gwfmodel2 + is_for_model1 = .true. + else + output_tab => this%outputtab2 + nbr_model => this%gwfmodel1 + is_for_model1 = .false. + end if ! ! -- update output tables if (this%iprflow /= 0) then ! ! -- update titles - if (this%gwfmodel1%oc%oc_save('BUDGET')) then - call this%outputtab1%set_title(packname1) - end if - if (this%gwfmodel2%oc%oc_save('BUDGET')) then - call this%outputtab2%set_title(packname2) + if (model%oc%oc_save('BUDGET')) then + call output_tab%set_title(packname) end if ! ! -- set table kstp and kper - call this%outputtab1%set_kstpkper(kstp, kper) - call this%outputtab2%set_kstpkper(kstp, kper) + call output_tab%set_kstpkper(kstp, kper) ! ! -- update maxbound of tables ntabrows = 0 @@ -1064,43 +1094,38 @@ subroutine gwf_gwf_bdsav(this) n2 = this%nodem2(i) ! ! -- If both cells are active then calculate flow rate - if (this%gwfmodel1%ibound(n1) /= 0 .and. & - this%gwfmodel2%ibound(n2) /= 0) then + if (this%model1%ibound(n1) /= 0 .and. this%model2%ibound(n2) /= 0) then ntabrows = ntabrows + 1 end if end do if (ntabrows > 0) then - call this%outputtab1%set_maxbound(ntabrows) - call this%outputtab2%set_maxbound(ntabrows) + call output_tab%set_maxbound(ntabrows) end if end if ! - ! -- Print and write budget terms for model 1 + ! -- Print and write budget terms ! ! -- Set binary unit numbers for saving flows if (this%ipakcb /= 0) then - ibinun1 = this%gwfmodel1%oc%oc_save_unit('BUDGET') + ibinun = model%oc%oc_save_unit('BUDGET') else - ibinun1 = 0 + ibinun = 0 end if ! ! -- If save budget flag is zero for this stress period, then ! shut off saving - if (.not. this%gwfmodel1%oc%oc_save('BUDGET')) ibinun1 = 0 - if (isuppress_output /= 0) then - ibinun1 = 0 - end if + if (.not. model%oc%oc_save('BUDGET')) ibinun = 0 ! ! -- If cell-by-cell flows will be saved as a list, write header. - if (ibinun1 /= 0) then - call this%gwfmodel1%dis%record_srcdst_list_header(budtxt(1), & - this%gwfmodel1%name, & - this%name, & - this%gwfmodel2%name, & - this%name, & - this%naux, this%auxname, & - ibinun1, this%nexg, & - this%gwfmodel1%iout) + if (ibinun /= 0) then + call model%dis%record_srcdst_list_header(budtxt(1), & + model%name, & + this%name, & + nbr_model%name, & + this%name, & + this%naux, this%auxname, & + ibinun, this%nexg, & + model%iout) end if ! ! Initialize accumulators @@ -1123,19 +1148,27 @@ subroutine gwf_gwf_bdsav(this) n2 = this%nodem2(i) ! ! -- If both cells are active then calculate flow rate - if (this%gwfmodel1%ibound(n1) /= 0 .and. & - this%gwfmodel2%ibound(n2) /= 0) then + if (this%model1%ibound(n1) /= 0 .and. & + this%model2%ibound(n2) /= 0) then rrate = this%simvals(i) ! ! -- Print the individual rates to model list files if requested if (this%iprflow /= 0) then - if (this%gwfmodel1%oc%oc_save('BUDGET')) then + if (model%oc%oc_save('BUDGET')) then ! ! -- set nodestr and write outputtab table - nodeu = this%gwfmodel1%dis%get_nodeuser(n1) - call this%gwfmodel1%dis%nodeu_to_string(nodeu, nodestr) - call this%outputtab1%print_list_entry(i, trim(adjustl(nodestr)), & - rrate, bname) + if (is_for_model1) then + nodeu = model%dis%get_nodeuser(n1) + call model%dis%nodeu_to_string(nodeu, nodestr) + call output_tab%print_list_entry(i, trim(adjustl(nodestr)), & + rrate, bname) + else + nodeu = model%dis%get_nodeuser(n2) + call model%dis%nodeu_to_string(nodeu, nodestr) + call output_tab%print_list_entry(i, trim(adjustl(nodestr)), & + -rrate, bname) + end if + end if end if if (rrate < DZERO) then @@ -1146,111 +1179,23 @@ subroutine gwf_gwf_bdsav(this) end if ! ! -- If saving cell-by-cell flows in list, write flow - n1u = this%gwfmodel1%dis%get_nodeuser(n1) - n2u = this%gwfmodel2%dis%get_nodeuser(n2) - if (ibinun1 /= 0) & - call this%gwfmodel1%dis%record_mf6_list_entry( & - ibinun1, n1u, n2u, rrate, this%naux, this%auxvar(:, i), & - .false., .false.) - ! - end do - ! - ! -- Print and write budget terms for model 2 - ! - ! -- Set binary unit numbers for saving flows - if (this%ipakcb /= 0) then - ibinun2 = this%gwfmodel2%oc%oc_save_unit('BUDGET') - else - ibinun2 = 0 - end if - ! - ! -- If save budget flag is zero for this stress period, then - ! shut off saving - if (.not. this%gwfmodel2%oc%oc_save('BUDGET')) ibinun2 = 0 - if (isuppress_output /= 0) then - ibinun2 = 0 - end if - ! - ! -- If cell-by-cell flows will be saved as a list, write header. - if (ibinun2 /= 0) then - call this%gwfmodel2%dis%record_srcdst_list_header(budtxt(1), & - this%gwfmodel2%name, & - this%name, & - this%gwfmodel1%name, & - this%name, & - this%naux, this%auxname, & - ibinun2, this%nexg, & - this%gwfmodel2%iout) - end if - ! - ! Initialize accumulators - ratin = DZERO - ratout = DZERO - ! - ! -- Loop through all exchanges - do i = 1, this%nexg - ! - ! -- Assign boundary name - if (this%inamedbound > 0) then - bname = this%boundname(i) - else - bname = '' - end if - ! - ! -- Calculate the flow rate between n1 and n2 - rrate = DZERO - n1 = this%nodem1(i) - n2 = this%nodem2(i) - ! - ! -- If both cells are active then calculate flow rate - if (this%gwfmodel1%ibound(n1) /= 0 .and. & - this%gwfmodel2%ibound(n2) /= 0) then - rrate = this%simvals(i) - ! - ! -- Print the individual rates to model list files if requested - if (this%iprflow /= 0) then - if (this%gwfmodel2%oc%oc_save('BUDGET')) then - ! - ! -- set nodestr and write outputtab table - nodeu = this%gwfmodel2%dis%get_nodeuser(n2) - call this%gwfmodel2%dis%nodeu_to_string(nodeu, nodestr) - call this%outputtab2%print_list_entry(i, trim(adjustl(nodestr)), & - -rrate, bname) - end if - end if - if (rrate < DZERO) then - ratout = ratout - rrate + n1u = this%model1%dis%get_nodeuser(n1) + n2u = this%model2%dis%get_nodeuser(n2) + if (ibinun /= 0) then + if (is_for_model1) then + call model%dis%record_mf6_list_entry(ibinun, n1u, n2u, rrate, & + this%naux, this%auxvar(:, i), & + .false., .false.) else - ratin = ratin + rrate + call model%dis%record_mf6_list_entry(ibinun, n2u, n1u, -rrate, & + this%naux, this%auxvar(:, i), & + .false., .false.) end if end if ! - ! -- If saving cell-by-cell flows in list, write flow - n1u = this%gwfmodel1%dis%get_nodeuser(n1) - n2u = this%gwfmodel2%dis%get_nodeuser(n2) - if (ibinun2 /= 0) & - call this%gwfmodel2%dis%record_mf6_list_entry( & - ibinun2, n2u, n1u, -rrate, this%naux, this%auxvar(:, i), & - .false., .false.) - ! end do - ! - ! -- Set icbcfl, ibudfl to zero so that flows will be printed and - ! saved, if the options were set in the MVR package - icbcfl = 1 - ibudfl = 1 - ! - ! -- Call mvr bd routine - if (this%inmvr > 0) call this%mvr%mvr_bdsav(icbcfl, ibudfl, isuppress_output) - ! - ! -- Calculate and write simulated values for observations - if (this%inobs /= 0) then - call this%gwf_gwf_save_simvals() - end if - ! - ! -- return - return - end subroutine gwf_gwf_bdsav + + end subroutine gwf_gwf_bdsav_model !> @ brief Output !! diff --git a/src/Model/GroundWaterFlow/gwf3npf8.f90 b/src/Model/GroundWaterFlow/gwf3npf8.f90 index 7d38192c9ec..6a3b8aa8483 100644 --- a/src/Model/GroundWaterFlow/gwf3npf8.f90 +++ b/src/Model/GroundWaterFlow/gwf3npf8.f90 @@ -1817,11 +1817,11 @@ subroutine source_griddata(this) if (.not. found%wetdry) call mem_reallocate(this%wetdry, 1, 'WETDRY', & trim(this%memoryPath)) if (.not. found%angle1 .and. this%ixt3d == 0) & - call mem_reallocate(this%angle1, 1, 'ANGLE1', trim(this%memoryPath)) + call mem_reallocate(this%angle1, 0, 'ANGLE1', trim(this%memoryPath)) if (.not. found%angle2 .and. this%ixt3d == 0) & - call mem_reallocate(this%angle2, 1, 'ANGLE2', trim(this%memoryPath)) + call mem_reallocate(this%angle2, 0, 'ANGLE2', trim(this%memoryPath)) if (.not. found%angle3 .and. this%ixt3d == 0) & - call mem_reallocate(this%angle3, 1, 'ANGLE3', trim(this%memoryPath)) + call mem_reallocate(this%angle3, 0, 'ANGLE3', trim(this%memoryPath)) ! ! -- log griddata if (this%iout > 0) then From 7e7140ccc21e6cb3b34eb14b27a23b262ac1deaa Mon Sep 17 00:00:00 2001 From: mjr-deltares <45555666+mjr-deltares@users.noreply.github.com> Date: Thu, 22 Dec 2022 16:31:56 +0100 Subject: [PATCH 010/123] refactor(amat): implement MatrixBaseType for amat (#1078) * - refactored ia,ja,amat into new matrix type for all packages, models, exchanges --- make/makedefaults | 2 +- make/makefile | 36 +- msvs/mf6core.vfproj | 3 + pymake/makefile | 343 +++++++++--------- src/Exchange/GhostNode.f90 | 97 ++--- src/Exchange/GwfGwfExchange.f90 | 70 ++-- src/Exchange/GwtGwtExchange.f90 | 8 +- src/Exchange/NumericalExchange.f90 | 34 +- src/Model/Connection/GwfGwfConnection.f90 | 29 +- src/Model/Connection/GwtGwtConnection.f90 | 31 +- .../Connection/SpatialModelConnection.f90 | 66 ++-- src/Model/GroundWaterFlow/gwf3.f90 | 79 ++-- src/Model/GroundWaterFlow/gwf3api8.f90 | 7 +- src/Model/GroundWaterFlow/gwf3buy8.f90 | 10 +- src/Model/GroundWaterFlow/gwf3chd8.f90 | 5 +- src/Model/GroundWaterFlow/gwf3csub8.f90 | 27 +- src/Model/GroundWaterFlow/gwf3drn8.f90 | 13 +- src/Model/GroundWaterFlow/gwf3evt8.f90 | 7 +- src/Model/GroundWaterFlow/gwf3ghb8.f90 | 7 +- src/Model/GroundWaterFlow/gwf3hfb8.f90 | 18 +- src/Model/GroundWaterFlow/gwf3lak8.f90 | 13 +- src/Model/GroundWaterFlow/gwf3maw8.f90 | 75 ++-- src/Model/GroundWaterFlow/gwf3npf8.f90 | 56 +-- src/Model/GroundWaterFlow/gwf3rch8.f90 | 7 +- src/Model/GroundWaterFlow/gwf3riv8.f90 | 7 +- src/Model/GroundWaterFlow/gwf3sfr8.f90 | 13 +- src/Model/GroundWaterFlow/gwf3sto8.f90 | 19 +- src/Model/GroundWaterFlow/gwf3uzf8.f90 | 13 +- src/Model/GroundWaterFlow/gwf3wel8.f90 | 13 +- src/Model/GroundWaterTransport/gwt1.f90 | 33 +- src/Model/GroundWaterTransport/gwt1adv1.f90 | 9 +- src/Model/GroundWaterTransport/gwt1apt1.f90 | 77 ++-- src/Model/GroundWaterTransport/gwt1cnc1.f90 | 5 +- src/Model/GroundWaterTransport/gwt1dsp.f90 | 23 +- src/Model/GroundWaterTransport/gwt1fmi1.f90 | 16 +- src/Model/GroundWaterTransport/gwt1ist1.f90 | 7 +- src/Model/GroundWaterTransport/gwt1lkt1.f90 | 17 +- src/Model/GroundWaterTransport/gwt1mst1.f90 | 42 +-- src/Model/GroundWaterTransport/gwt1mwt1.f90 | 13 +- src/Model/GroundWaterTransport/gwt1sft1.f90 | 15 +- src/Model/GroundWaterTransport/gwt1src1.f90 | 7 +- src/Model/GroundWaterTransport/gwt1ssm1.f90 | 7 +- src/Model/GroundWaterTransport/gwt1uzt1.f90 | 14 +- src/Model/ModelUtilities/BoundaryPackage.f90 | 16 +- .../ModelUtilities/DiscretizationBase.f90 | 15 +- src/Model/ModelUtilities/Xt3dInterface.f90 | 118 +++--- src/Model/NumericalModel.f90 | 24 +- src/Solution/LinearMethods/ims8linear.f90 | 26 +- src/Solution/NumericalSolution.f90 | 185 +++++----- src/Utilities/Matrix/MatrixBase.f90 | 127 +++++++ src/Utilities/Matrix/SparseMatrix.f90 | 237 ++++++++++++ src/meson.build | 2 + utils/mf5to6/make/makedefaults | 2 +- utils/mf5to6/make/makefile | 8 +- utils/zonebudget/make/makedefaults | 2 +- utils/zonebudget/make/makefile | 2 +- 56 files changed, 1211 insertions(+), 946 deletions(-) create mode 100644 src/Utilities/Matrix/MatrixBase.f90 create mode 100644 src/Utilities/Matrix/SparseMatrix.f90 diff --git a/make/makedefaults b/make/makedefaults index ea05064e2a2..7bbd4dd3293 100644 --- a/make/makedefaults +++ b/make/makedefaults @@ -1,4 +1,4 @@ -# makedefaults created by pymake (version 1.2.5) for the 'mf6' executable. +# makedefaults created by pymake (version 1.2.7) for the 'mf6' executable. # determine OS ifeq ($(OS), Windows_NT) diff --git a/make/makefile b/make/makefile index 2cf7532c530..6791ed45b92 100644 --- a/make/makefile +++ b/make/makefile @@ -1,4 +1,4 @@ -# makefile created by pymake (version 1.2.5) for the 'mf6' executable. +# makefile created by pymake (version 1.2.7) for the 'mf6' executable. include ./makedefaults @@ -7,27 +7,28 @@ include ./makedefaults SOURCEDIR1=../src SOURCEDIR2=../src/Exchange SOURCEDIR3=../src/Model -SOURCEDIR4=../src/Model/Connection -SOURCEDIR5=../src/Model/Geometry -SOURCEDIR6=../src/Model/GroundWaterFlow +SOURCEDIR4=../src/Model/Geometry +SOURCEDIR5=../src/Model/ModelUtilities +SOURCEDIR6=../src/Model/Connection SOURCEDIR7=../src/Model/GroundWaterTransport -SOURCEDIR8=../src/Model/ModelUtilities +SOURCEDIR8=../src/Model/GroundWaterFlow SOURCEDIR9=../src/Solution SOURCEDIR10=../src/Solution/LinearMethods SOURCEDIR11=../src/Timing SOURCEDIR12=../src/Utilities -SOURCEDIR13=../src/Utilities/ArrayRead -SOURCEDIR14=../src/Utilities/Idm -SOURCEDIR15=../src/Utilities/Libraries -SOURCEDIR16=../src/Utilities/Libraries/blas -SOURCEDIR17=../src/Utilities/Libraries/daglib -SOURCEDIR18=../src/Utilities/Libraries/rcm -SOURCEDIR19=../src/Utilities/Libraries/sparsekit -SOURCEDIR20=../src/Utilities/Libraries/sparskit2 -SOURCEDIR21=../src/Utilities/Memory +SOURCEDIR13=../src/Utilities/TimeSeries +SOURCEDIR14=../src/Utilities/Libraries +SOURCEDIR15=../src/Utilities/Libraries/rcm +SOURCEDIR16=../src/Utilities/Libraries/sparsekit +SOURCEDIR17=../src/Utilities/Libraries/sparskit2 +SOURCEDIR18=../src/Utilities/Libraries/blas +SOURCEDIR19=../src/Utilities/Libraries/daglib +SOURCEDIR20=../src/Utilities/Idm +SOURCEDIR21=../src/Utilities/Matrix SOURCEDIR22=../src/Utilities/Observation SOURCEDIR23=../src/Utilities/OutputControl -SOURCEDIR24=../src/Utilities/TimeSeries +SOURCEDIR24=../src/Utilities/Memory +SOURCEDIR25=../src/Utilities/ArrayRead VPATH = \ ${SOURCEDIR1} \ @@ -53,7 +54,8 @@ ${SOURCEDIR20} \ ${SOURCEDIR21} \ ${SOURCEDIR22} \ ${SOURCEDIR23} \ -${SOURCEDIR24} +${SOURCEDIR24} \ +${SOURCEDIR25} .SUFFIXES: .f90 .F90 .o @@ -91,6 +93,7 @@ $(OBJDIR)/DisvGeom.o \ $(OBJDIR)/ArrayReaders.o \ $(OBJDIR)/TimeSeriesManager.o \ $(OBJDIR)/SmoothingFunctions.o \ +$(OBJDIR)/MatrixBase.o \ $(OBJDIR)/ListReader.o \ $(OBJDIR)/Connections.o \ $(OBJDIR)/DiscretizationBase.o \ @@ -216,6 +219,7 @@ $(OBJDIR)/GhostNode.o \ $(OBJDIR)/gwf3evt8.o \ $(OBJDIR)/gwf3chd8.o \ $(OBJDIR)/ims8reordering.o \ +$(OBJDIR)/SparseMatrix.o \ $(OBJDIR)/GridConnection.o \ $(OBJDIR)/DistributedData.o \ $(OBJDIR)/gwt1.o \ diff --git a/msvs/mf6core.vfproj b/msvs/mf6core.vfproj index 15eb52dea77..6d7f29668ac 100644 --- a/msvs/mf6core.vfproj +++ b/msvs/mf6core.vfproj @@ -193,6 +193,9 @@ + + + diff --git a/pymake/makefile b/pymake/makefile index 26cb285a460..64bfd8e3e89 100644 --- a/pymake/makefile +++ b/pymake/makefile @@ -6,28 +6,29 @@ include ./makedefaults # Define the source file directories SOURCEDIR1=../src SOURCEDIR2=../src/Exchange -SOURCEDIR3=../src/Solution -SOURCEDIR4=../src/Solution/LinearMethods -SOURCEDIR5=../src/Timing -SOURCEDIR6=../src/Utilities -SOURCEDIR7=../src/Utilities/Idm -SOURCEDIR8=../src/Utilities/TimeSeries -SOURCEDIR9=../src/Utilities/Memory -SOURCEDIR10=../src/Utilities/OutputControl -SOURCEDIR11=../src/Utilities/ArrayRead -SOURCEDIR12=../src/Utilities/Libraries -SOURCEDIR13=../src/Utilities/Libraries/rcm -SOURCEDIR14=../src/Utilities/Libraries/blas -SOURCEDIR15=../src/Utilities/Libraries/sparskit2 -SOURCEDIR16=../src/Utilities/Libraries/daglib -SOURCEDIR17=../src/Utilities/Libraries/sparsekit -SOURCEDIR18=../src/Utilities/Observation -SOURCEDIR19=../src/Model -SOURCEDIR20=../src/Model/Connection -SOURCEDIR21=../src/Model/GroundWaterTransport -SOURCEDIR22=../src/Model/ModelUtilities -SOURCEDIR23=../src/Model/GroundWaterFlow -SOURCEDIR24=../src/Model/Geometry +SOURCEDIR3=../src/Model +SOURCEDIR4=../src/Model/Geometry +SOURCEDIR5=../src/Model/ModelUtilities +SOURCEDIR6=../src/Model/Connection +SOURCEDIR7=../src/Model/GroundWaterTransport +SOURCEDIR8=../src/Model/GroundWaterFlow +SOURCEDIR9=../src/Solution +SOURCEDIR10=../src/Solution/LinearMethods +SOURCEDIR11=../src/Timing +SOURCEDIR12=../src/Utilities +SOURCEDIR13=../src/Utilities/TimeSeries +SOURCEDIR14=../src/Utilities/Libraries +SOURCEDIR15=../src/Utilities/Libraries/rcm +SOURCEDIR16=../src/Utilities/Libraries/sparsekit +SOURCEDIR17=../src/Utilities/Libraries/sparskit2 +SOURCEDIR18=../src/Utilities/Libraries/blas +SOURCEDIR19=../src/Utilities/Libraries/daglib +SOURCEDIR20=../src/Utilities/Idm +SOURCEDIR21=../src/Utilities/Matrix +SOURCEDIR22=../src/Utilities/Observation +SOURCEDIR23=../src/Utilities/OutputControl +SOURCEDIR24=../src/Utilities/Memory +SOURCEDIR25=../src/Utilities/ArrayRead VPATH = \ ${SOURCEDIR1} \ @@ -53,198 +54,202 @@ ${SOURCEDIR20} \ ${SOURCEDIR21} \ ${SOURCEDIR22} \ ${SOURCEDIR23} \ -${SOURCEDIR24} +${SOURCEDIR24} \ +${SOURCEDIR25} .SUFFIXES: .f90 .F90 .o OBJECTS = \ +$(OBJDIR)/rcm.o \ +$(OBJDIR)/CharString.o \ +$(OBJDIR)/OpenSpec.o \ $(OBJDIR)/kind.o \ +$(OBJDIR)/InputDefinition.o \ +$(OBJDIR)/sparsekit.o \ +$(OBJDIR)/ilut.o \ +$(OBJDIR)/CsrUtils.o \ +$(OBJDIR)/gwf3dis8idm.o \ +$(OBJDIR)/BaseGeometry.o \ $(OBJDIR)/Constants.o \ $(OBJDIR)/SimVariables.o \ -$(OBJDIR)/genericutils.o \ +$(OBJDIR)/SfrCrossSectionUtils.o \ +$(OBJDIR)/ims8misc.o \ +$(OBJDIR)/GwtAdvOptions.o \ +$(OBJDIR)/HashTable.o \ +$(OBJDIR)/SmoothingFunctions.o \ +$(OBJDIR)/ims8reordering.o \ +$(OBJDIR)/gwf3npf8idm.o \ +$(OBJDIR)/Sparse.o \ +$(OBJDIR)/blas1_d.o \ +$(OBJDIR)/gwt1dspidm.o \ +$(OBJDIR)/IdmLogger.o \ +$(OBJDIR)/dag_module.o \ +$(OBJDIR)/GwfNpfOptions.o \ +$(OBJDIR)/gwf3disv8idm.o \ +$(OBJDIR)/GwtDspOptions.o \ +$(OBJDIR)/gwf3disu8idm.o \ +$(OBJDIR)/InterfaceMap.o \ +$(OBJDIR)/Xt3dAlgorithm.o \ +$(OBJDIR)/GwfBuyInputData.o \ $(OBJDIR)/compilerversion.o \ -$(OBJDIR)/ArrayHandlers.o \ +$(OBJDIR)/GwfStorageUtils.o \ +$(OBJDIR)/defmacro.o \ +$(OBJDIR)/genericutils.o \ +$(OBJDIR)/MatrixBase.o \ $(OBJDIR)/version.o \ +$(OBJDIR)/Timer.o \ +$(OBJDIR)/ArrayHandlers.o \ +$(OBJDIR)/List.o \ +$(OBJDIR)/mf6lists.o \ +$(OBJDIR)/StringList.o \ +$(OBJDIR)/ObsOutput.o \ +$(OBJDIR)/TimeSeriesRecord.o \ $(OBJDIR)/Message.o \ -$(OBJDIR)/defmacro.o \ $(OBJDIR)/Sim.o \ -$(OBJDIR)/OpenSpec.o \ +$(OBJDIR)/sort.o \ +$(OBJDIR)/VectorInt.o \ $(OBJDIR)/InputOutput.o \ +$(OBJDIR)/InputDefinitionSelector.o \ +$(OBJDIR)/comarg.o \ +$(OBJDIR)/Iunit.o \ +$(OBJDIR)/BudgetFileReader.o \ +$(OBJDIR)/MemoryHelper.o \ +$(OBJDIR)/ObsOutputList.o \ +$(OBJDIR)/HeadFileReader.o \ +$(OBJDIR)/DisvGeom.o \ +$(OBJDIR)/PrintSaveManager.o \ +$(OBJDIR)/ModflowInput.o \ +$(OBJDIR)/ArrayReaders.o \ +$(OBJDIR)/CircularGeometry.o \ +$(OBJDIR)/StructVector.o \ +$(OBJDIR)/RectangularGeometry.o \ +$(OBJDIR)/BlockParser.o \ +$(OBJDIR)/Budget.o \ +$(OBJDIR)/NameFile.o \ +$(OBJDIR)/ArrayReaderBase.o \ +$(OBJDIR)/ims8base.o \ +$(OBJDIR)/Double1dReader.o \ +$(OBJDIR)/TimeSeries.o \ $(OBJDIR)/TableTerm.o \ +$(OBJDIR)/Double2dReader.o \ +$(OBJDIR)/TimeSeriesFileList.o \ +$(OBJDIR)/TimeSeriesLink.o \ +$(OBJDIR)/Integer2dReader.o \ $(OBJDIR)/Table.o \ -$(OBJDIR)/MemoryHelper.o \ -$(OBJDIR)/CharString.o \ +$(OBJDIR)/ListReader.o \ +$(OBJDIR)/SfrCrossSectionManager.o \ +$(OBJDIR)/Integer1dReader.o \ $(OBJDIR)/Memory.o \ -$(OBJDIR)/List.o \ +$(OBJDIR)/LayeredArrayReader.o \ $(OBJDIR)/MemoryList.o \ -$(OBJDIR)/TimeSeriesRecord.o \ -$(OBJDIR)/BlockParser.o \ $(OBJDIR)/MemoryManager.o \ -$(OBJDIR)/TimeSeries.o \ +$(OBJDIR)/GwfMvrPeriodData.o \ +$(OBJDIR)/SparseMatrix.o \ +$(OBJDIR)/ims8linear.o \ $(OBJDIR)/ats.o \ -$(OBJDIR)/TimeSeriesLink.o \ -$(OBJDIR)/TimeSeriesFileList.o \ +$(OBJDIR)/MemoryManagerExt.o \ +$(OBJDIR)/MappedVariable.o \ +$(OBJDIR)/MemorySetHandler.o \ $(OBJDIR)/tdis.o \ -$(OBJDIR)/HashTable.o \ -$(OBJDIR)/Sparse.o \ -$(OBJDIR)/DisvGeom.o \ -$(OBJDIR)/ArrayReaders.o \ +$(OBJDIR)/UzfCellGroup.o \ +$(OBJDIR)/BaseModel.o \ $(OBJDIR)/TimeSeriesManager.o \ -$(OBJDIR)/SmoothingFunctions.o \ -$(OBJDIR)/ListReader.o \ +$(OBJDIR)/DistributedData.o \ +$(OBJDIR)/PackageBudget.o \ +$(OBJDIR)/PackageMover.o \ +$(OBJDIR)/StructArray.o \ $(OBJDIR)/Connections.o \ +$(OBJDIR)/BaseExchange.o \ +$(OBJDIR)/LoadMf6FileType.o \ $(OBJDIR)/DiscretizationBase.o \ -$(OBJDIR)/TimeArray.o \ -$(OBJDIR)/ObsOutput.o \ -$(OBJDIR)/TimeArraySeries.o \ -$(OBJDIR)/ObsOutputList.o \ +$(OBJDIR)/Mover.o \ +$(OBJDIR)/BaseSolution.o \ +$(OBJDIR)/Xt3dInterface.o \ $(OBJDIR)/Observe.o \ -$(OBJDIR)/InputDefinition.o \ -$(OBJDIR)/TimeArraySeriesLink.o \ -$(OBJDIR)/ObsUtility.o \ -$(OBJDIR)/ObsContainer.o \ -$(OBJDIR)/VectorInt.o \ -$(OBJDIR)/gwt1dspidm.o \ -$(OBJDIR)/gwf3npf8idm.o \ -$(OBJDIR)/gwf3disv8idm.o \ -$(OBJDIR)/gwf3disu8idm.o \ -$(OBJDIR)/gwf3dis8idm.o \ -$(OBJDIR)/ArrayReaderBase.o \ -$(OBJDIR)/TimeArraySeriesManager.o \ -$(OBJDIR)/PackageMover.o \ -$(OBJDIR)/Obs3.o \ +$(OBJDIR)/IdmMf6FileLoader.o \ +$(OBJDIR)/SolutionGroup.o \ $(OBJDIR)/NumericalPackage.o \ -$(OBJDIR)/Budget.o \ -$(OBJDIR)/BudgetFileReader.o \ -$(OBJDIR)/StructVector.o \ -$(OBJDIR)/IdmLogger.o \ -$(OBJDIR)/InputDefinitionSelector.o \ -$(OBJDIR)/Integer2dReader.o \ -$(OBJDIR)/Double2dReader.o \ -$(OBJDIR)/BoundaryPackage.o \ -$(OBJDIR)/BaseModel.o \ -$(OBJDIR)/BudgetTerm.o \ -$(OBJDIR)/StructArray.o \ -$(OBJDIR)/ModflowInput.o \ -$(OBJDIR)/Integer1dReader.o \ -$(OBJDIR)/Double1dReader.o \ -$(OBJDIR)/NumericalModel.o \ -$(OBJDIR)/mf6lists.o \ -$(OBJDIR)/PackageBudget.o \ -$(OBJDIR)/HeadFileReader.o \ -$(OBJDIR)/BudgetObject.o \ -$(OBJDIR)/sort.o \ -$(OBJDIR)/SfrCrossSectionUtils.o \ -$(OBJDIR)/PrintSaveManager.o \ -$(OBJDIR)/Xt3dAlgorithm.o \ +$(OBJDIR)/gwf3dis8.o \ +$(OBJDIR)/gwf3disu8.o \ $(OBJDIR)/gwf3tvbase8.o \ -$(OBJDIR)/LoadMf6FileType.o \ -$(OBJDIR)/DistributedModel.o \ -$(OBJDIR)/BaseExchange.o \ -$(OBJDIR)/UzfCellGroup.o \ -$(OBJDIR)/gwt1fmi1.o \ -$(OBJDIR)/SfrCrossSectionManager.o \ -$(OBJDIR)/dag_module.o \ +$(OBJDIR)/TimeArray.o \ $(OBJDIR)/OutputControlData.o \ +$(OBJDIR)/TimeArraySeries.o \ +$(OBJDIR)/TimeArraySeriesLink.o \ +$(OBJDIR)/BudgetTerm.o \ +$(OBJDIR)/TimeArraySeriesManager.o \ $(OBJDIR)/gwf3ic8.o \ -$(OBJDIR)/Xt3dInterface.o \ -$(OBJDIR)/gwf3tvk8.o \ -$(OBJDIR)/MemoryManagerExt.o \ -$(OBJDIR)/IdmMf6FileLoader.o \ -$(OBJDIR)/GwfNpfOptions.o \ -$(OBJDIR)/CellWithNbrs.o \ -$(OBJDIR)/NumericalExchange.o \ -$(OBJDIR)/Iunit.o \ -$(OBJDIR)/gwf3uzf8.o \ -$(OBJDIR)/gwt1apt1.o \ -$(OBJDIR)/GwtSpc.o \ -$(OBJDIR)/gwf3sfr8.o \ -$(OBJDIR)/OutputControl.o \ +$(OBJDIR)/ObsContainer.o \ +$(OBJDIR)/gwf3hfb8.o \ $(OBJDIR)/gwt1ic1.o \ -$(OBJDIR)/gwf3maw8.o \ -$(OBJDIR)/gwf3lak8.o \ -$(OBJDIR)/gwt1mst1.o \ -$(OBJDIR)/GwtDspOptions.o \ -$(OBJDIR)/gwf3npf8.o \ -$(OBJDIR)/GwtAdvOptions.o \ -$(OBJDIR)/gwf3tvs8.o \ -$(OBJDIR)/GwfStorageUtils.o \ -$(OBJDIR)/Mover.o \ -$(OBJDIR)/GwfMvrPeriodData.o \ -$(OBJDIR)/ims8misc.o \ -$(OBJDIR)/GwfBuyInputData.o \ -$(OBJDIR)/InterfaceMap.o \ -$(OBJDIR)/gwf3disu8.o \ -$(OBJDIR)/GridSorting.o \ -$(OBJDIR)/DisConnExchange.o \ -$(OBJDIR)/CsrUtils.o \ -$(OBJDIR)/MappedVariable.o \ -$(OBJDIR)/TransportModel.o \ -$(OBJDIR)/NameFile.o \ -$(OBJDIR)/gwt1uzt1.o \ -$(OBJDIR)/gwt1ssm1.o \ -$(OBJDIR)/gwt1src1.o \ -$(OBJDIR)/gwt1sft1.o \ -$(OBJDIR)/gwt1oc1.o \ -$(OBJDIR)/gwt1obs1.o \ -$(OBJDIR)/gwt1mwt1.o \ -$(OBJDIR)/gwt1mvt1.o \ -$(OBJDIR)/gwt1lkt1.o \ -$(OBJDIR)/gwt1ist1.o \ -$(OBJDIR)/gwt1dsp.o \ -$(OBJDIR)/gwt1cnc1.o \ -$(OBJDIR)/gwt1adv1.o \ +$(OBJDIR)/ObsUtility.o \ +$(OBJDIR)/gwf3tvk8.o \ $(OBJDIR)/gwf3disv8.o \ -$(OBJDIR)/gwf3dis8.o \ -$(OBJDIR)/gwf3api8.o \ -$(OBJDIR)/gwf3wel8.o \ -$(OBJDIR)/gwf3riv8.o \ -$(OBJDIR)/gwf3rch8.o \ +$(OBJDIR)/gwf3tvs8.o \ $(OBJDIR)/gwf3sto8.o \ +$(OBJDIR)/OutputControl.o \ +$(OBJDIR)/GwtSpc.o \ +$(OBJDIR)/BudgetObject.o \ +$(OBJDIR)/Obs3.o \ $(OBJDIR)/gwf3oc8.o \ +$(OBJDIR)/gwt1oc1.o \ $(OBJDIR)/gwf3obs8.o \ $(OBJDIR)/gwf3mvr8.o \ -$(OBJDIR)/gwf3hfb8.o \ +$(OBJDIR)/gwf3npf8.o \ $(OBJDIR)/gwf3csub8.o \ -$(OBJDIR)/gwf3buy8.o \ -$(OBJDIR)/GhostNode.o \ +$(OBJDIR)/gwt1obs1.o \ +$(OBJDIR)/BoundaryPackage.o \ +$(OBJDIR)/gwf3uzf8.o \ +$(OBJDIR)/gwf3api8.o \ +$(OBJDIR)/gwt1cnc1.o \ $(OBJDIR)/gwf3ghb8.o \ -$(OBJDIR)/gwf3evt8.o \ -$(OBJDIR)/gwf3drn8.o \ +$(OBJDIR)/gwt1fmi1.o \ +$(OBJDIR)/gwf3lak8.o \ +$(OBJDIR)/gwt1src1.o \ +$(OBJDIR)/gwf3wel8.o \ +$(OBJDIR)/gwf3sfr8.o \ $(OBJDIR)/gwf3chd8.o \ -$(OBJDIR)/ims8reordering.o \ -$(OBJDIR)/GridConnection.o \ -$(OBJDIR)/DistributedData.o \ +$(OBJDIR)/gwf3maw8.o \ +$(OBJDIR)/NumericalModel.o \ +$(OBJDIR)/gwt1dsp.o \ +$(OBJDIR)/gwf3drn8.o \ +$(OBJDIR)/gwf3riv8.o \ +$(OBJDIR)/gwt1mvt1.o \ +$(OBJDIR)/gwf3evt8.o \ +$(OBJDIR)/gwf3rch8.o \ +$(OBJDIR)/TransportModel.o \ +$(OBJDIR)/gwt1apt1.o \ +$(OBJDIR)/gwt1ssm1.o \ +$(OBJDIR)/gwt1mst1.o \ +$(OBJDIR)/gwt1uzt1.o \ +$(OBJDIR)/gwt1adv1.o \ +$(OBJDIR)/DistributedModel.o \ +$(OBJDIR)/gwt1ist1.o \ +$(OBJDIR)/gwt1lkt1.o \ +$(OBJDIR)/NumericalExchange.o \ +$(OBJDIR)/gwt1sft1.o \ +$(OBJDIR)/DisConnExchange.o \ +$(OBJDIR)/GhostNode.o \ +$(OBJDIR)/gwf3buy8.o \ +$(OBJDIR)/NumericalSolution.o \ +$(OBJDIR)/gwt1mwt1.o \ +$(OBJDIR)/CellWithNbrs.o \ +$(OBJDIR)/GridSorting.o \ $(OBJDIR)/gwt1.o \ $(OBJDIR)/gwf3.o \ -$(OBJDIR)/ims8base.o \ -$(OBJDIR)/SpatialModelConnection.o \ -$(OBJDIR)/GwtInterfaceModel.o \ $(OBJDIR)/GwtGwtExchange.o \ -$(OBJDIR)/GwfInterfaceModel.o \ $(OBJDIR)/GwfGwfExchange.o \ -$(OBJDIR)/BaseSolution.o \ -$(OBJDIR)/Timer.o \ -$(OBJDIR)/ims8linear.o \ +$(OBJDIR)/GridConnection.o \ +$(OBJDIR)/GwtInterfaceModel.o \ +$(OBJDIR)/SpatialModelConnection.o \ +$(OBJDIR)/GwfInterfaceModel.o \ $(OBJDIR)/GwtGwtConnection.o \ $(OBJDIR)/GwfGwfConnection.o \ -$(OBJDIR)/SolutionGroup.o \ -$(OBJDIR)/NumericalSolution.o \ $(OBJDIR)/GwfGwtExchange.o \ -$(OBJDIR)/SimulationCreate.o \ $(OBJDIR)/ConnectionBuilder.o \ -$(OBJDIR)/comarg.o \ +$(OBJDIR)/SimulationCreate.o \ $(OBJDIR)/mf6core.o \ -$(OBJDIR)/BaseGeometry.o \ -$(OBJDIR)/mf6.o \ -$(OBJDIR)/StringList.o \ -$(OBJDIR)/MemorySetHandler.o \ -$(OBJDIR)/ilut.o \ -$(OBJDIR)/sparsekit.o \ -$(OBJDIR)/rcm.o \ -$(OBJDIR)/blas1_d.o \ -$(OBJDIR)/RectangularGeometry.o \ -$(OBJDIR)/CircularGeometry.o +$(OBJDIR)/mf6.o # Define the objects that make up the program $(PROGRAM) : $(OBJECTS) diff --git a/src/Exchange/GhostNode.f90 b/src/Exchange/GhostNode.f90 index b0831682432..549eef9be74 100644 --- a/src/Exchange/GhostNode.f90 +++ b/src/Exchange/GhostNode.f90 @@ -5,6 +5,7 @@ module GhostNodeModule use NumericalModelModule, only: NumericalModelType use NumericalPackageModule, only: NumericalPackageType use BlockParserModule, only: BlockParserType + use MatrixModule implicit none @@ -182,7 +183,7 @@ subroutine gnc_ac(this, sparse) return end subroutine gnc_ac - subroutine gnc_mc(this, iasln, jasln) + subroutine gnc_mc(this, matrix_sln) ! ****************************************************************************** ! gnc_mc -- Single or Two-Model GNC Map Connections ! Subroutine: (1) Fill the following mapping arrays: @@ -199,11 +200,10 @@ subroutine gnc_mc(this, iasln, jasln) use SimModule, only: store_error, store_error_unit, count_errors ! -- dummy class(GhostNodeType) :: this - integer(I4B), dimension(:), intent(in) :: iasln - integer(I4B), dimension(:), intent(in) :: jasln + class(MatrixBaseType), pointer :: matrix_sln ! -- local character(len=LINELENGTH) :: errmsg - integer(I4B) :: noden, nodem, ipos, j, ignc, jidx, nodej + integer(I4B) :: noden, nodem, ipos, ignc, jidx, nodej ! -- formats character(len=*), parameter :: fmterr = & "('GHOST NODE ERROR. Cell ', i0, ' in model ', a, & @@ -217,35 +217,19 @@ subroutine gnc_mc(this, iasln, jasln) nodem = this%nodem2(ignc) + this%m2%moffset ! ! -- store diagonal positions in idiagn and idiagm - this%idiagn(ignc) = iasln(noden) - this%idiagm(ignc) = iasln(nodem) + this%idiagn(ignc) = matrix_sln%get_position_diag(noden) + this%idiagm(ignc) = matrix_sln%get_position_diag(nodem) !if(this%implicit) then ! this%idiagn(ignc) = iasln(noden) ! this%idiagm(ignc) = iasln(nodem) !endif ! - ! -- find location of m in row n of global solution - this%idxglo(ignc) = 0 - searchloopnm: do ipos = iasln(noden) + 1, iasln(noden + 1) - 1 - j = jasln(ipos) - if (j == nodem) then - this%idxglo(ignc) = ipos - exit searchloopnm - end if - end do searchloopnm - ! - ! -- find location of n in row m of global solution and store in idxsymglo - this%idxsymglo(ignc) = 0 - searchloopmn: do ipos = iasln(nodem), iasln(nodem + 1) - 1 - j = jasln(ipos) - if (j == noden) then - this%idxsymglo(ignc) = ipos - exit searchloopmn - end if - end do searchloopmn + ! -- find location of m in row n of global solution, and v.v. + this%idxglo(ignc) = matrix_sln%get_position(noden, nodem) + this%idxsymglo(ignc) = matrix_sln%get_position(nodem, noden) ! - ! -- Check to make sure idxglo is non-zero - if (this%idxglo(ignc) == 0) then + ! -- Check to make sure idxglo is set + if (this%idxglo(ignc) == -1) then write (errmsg, fmterr) this%nodem1(ignc), trim(this%m1%name), & this%nodem2(ignc), trim(this%m2%name) call store_error(errmsg) @@ -273,13 +257,7 @@ subroutine gnc_mc(this, iasln, jasln) ipos = 0 this%jposinrown(jidx, ignc) = ipos else - searchloopn: do ipos = iasln(noden), iasln(noden + 1) - 1 - j = jasln(ipos) - if (j == nodej) then - this%jposinrown(jidx, ignc) = ipos - exit searchloopn - end if - end do searchloopn + this%jposinrown(jidx, ignc) = matrix_sln%get_position(noden, nodej) end if ! ! -- search for nodej in row m @@ -287,13 +265,7 @@ subroutine gnc_mc(this, iasln, jasln) ipos = 0 this%jposinrowm(jidx, ignc) = ipos else - searchloopm: do ipos = iasln(nodem) + 1, iasln(nodem + 1) - 1 - j = jasln(ipos) - if (j == nodej) then - this%jposinrowm(jidx, ignc) = ipos - exit searchloopm - end if - end do searchloopm + this%jposinrowm(jidx, ignc) = matrix_sln%get_position(nodem, nodej) end if end do end do @@ -303,7 +275,7 @@ subroutine gnc_mc(this, iasln, jasln) return end subroutine gnc_mc - subroutine gnc_fmsav(this, kiter, amatsln) + subroutine gnc_fmsav(this, kiter, matrix) ! ****************************************************************************** ! gnc_fmsav -- Store the n-m Picard conductance in cond prior to the Newton ! terms being added. @@ -316,7 +288,7 @@ subroutine gnc_fmsav(this, kiter, amatsln) ! -- dummy class(GhostNodeType) :: this integer(I4B), intent(in) :: kiter - real(DP), dimension(:), intent(inout) :: amatsln + class(MatrixBaseType), pointer :: matrix ! -- local integer(I4B) :: ignc, ipos real(DP) :: cond @@ -327,7 +299,7 @@ subroutine gnc_fmsav(this, kiter, amatsln) gncloop: do ignc = 1, this%nexg ipos = this%idxglo(ignc) if (ipos > 0) then - cond = amatsln(ipos) + cond = matrix%get_value_pos(ipos) else cond = DZERO end if @@ -338,10 +310,10 @@ subroutine gnc_fmsav(this, kiter, amatsln) return end subroutine gnc_fmsav - subroutine gnc_fc(this, kiter, amatsln) + subroutine gnc_fc(this, kiter, matrix) ! ****************************************************************************** ! gnc_fc -- Fill matrix terms -! Subroutine: (1) Add the GNC terms to the solution amat or model rhs depending +! Subroutine: (1) Add the GNC terms to the solution matrix or model rhs depending ! on whether GNC is implicit or explicit ! ****************************************************************************** ! @@ -352,17 +324,17 @@ subroutine gnc_fc(this, kiter, amatsln) ! -- dummy class(GhostNodeType) :: this integer(I4B), intent(in) :: kiter - real(DP), dimension(:), intent(inout) :: amatsln + class(MatrixBaseType), pointer :: matrix ! -- local integer(I4B) :: ignc, j, noden, nodem, ipos, jidx, iposjn, iposjm real(DP) :: cond, alpha, aterm, rterm ! ------------------------------------------------------------------------------ ! ! -- If this is a single model gnc (not an exchange across models), then - ! pull conductances out of amatsln and store them in this%cond - if (this%smgnc) call this%gnc_fmsav(kiter, amatsln) + ! pull conductances out of the system matrix and store them in this%cond + if (this%smgnc) call this%gnc_fmsav(kiter, matrix) ! - ! -- Add gnc terms to rhs or to amat depending on whether gnc is implicit + ! -- Add gnc terms to rhs or to the matrix depending on whether gnc is implicit ! or explicit gncloop: do ignc = 1, this%nexg noden = this%nodem1(ignc) @@ -380,10 +352,10 @@ subroutine gnc_fc(this, kiter, amatsln) if (this%implicit) then iposjn = this%jposinrown(jidx, ignc) iposjm = this%jposinrowm(jidx, ignc) - amatsln(this%idiagn(ignc)) = amatsln(this%idiagn(ignc)) + aterm - amatsln(iposjn) = amatsln(iposjn) - aterm - amatsln(this%idxsymglo(ignc)) = amatsln(this%idxsymglo(ignc)) - aterm - amatsln(iposjm) = amatsln(iposjm) + aterm + call matrix%add_value_pos(this%idiagn(ignc), aterm) + call matrix%add_value_pos(iposjn, -aterm) + call matrix%add_value_pos(this%idxsymglo(ignc), -aterm) + call matrix%add_value_pos(iposjm, aterm) else rterm = aterm * (this%m1%x(noden) - this%m1%x(j)) this%m1%rhs(noden) = this%m1%rhs(noden) - rterm @@ -396,15 +368,14 @@ subroutine gnc_fc(this, kiter, amatsln) return end subroutine gnc_fc - subroutine gnc_fn(this, kiter, njasln, amatsln, condsat, ihc_opt, & + subroutine gnc_fn(this, kiter, matrix_sln, condsat, ihc_opt, & ivarcv_opt, ictm1_opt, ictm2_opt) ! ****************************************************************************** ! gnc_fn -- Fill GNC Newton terms ! ! Required arguments: ! kiter : outer iteration number -! njasln : size of amatsln -! amatsln : coefficient matrix for the solution +! matrix_sln: the solution matrix ! condsat is of size(njas) if single model, otherwise nexg ! ! Optional arguments: @@ -423,8 +394,7 @@ subroutine gnc_fn(this, kiter, njasln, amatsln, condsat, ihc_opt, & ! -- dummy class(GhostNodeType) :: this integer(I4B) :: kiter - integer(I4B), intent(in) :: njasln - real(DP), dimension(njasln), intent(inout) :: amatsln + class(MatrixBaseType), pointer :: matrix_sln real(DP), dimension(:), intent(in) :: condsat integer(I4B), dimension(:), optional :: ihc_opt integer(I4B), optional :: ivarcv_opt @@ -504,17 +474,16 @@ subroutine gnc_fn(this, kiter, njasln, amatsln, condsat, ihc_opt, & derv = sQuadraticSaturationDerivative(topup, botup, xup) term = consterm * derv if (iups == 0) then - amatsln(this%idiagn(ignc)) = amatsln(this%idiagn(ignc)) + term + call matrix_sln%add_value_pos(this%idiagn(ignc), term) if (this%m2%ibound(nodem) > 0) then - amatsln(this%idxsymglo(ignc)) = amatsln(this%idxsymglo(ignc)) - & - term + call matrix_sln%add_value_pos(this%idxsymglo(ignc), -term) end if this%m1%rhs(noden) = this%m1%rhs(noden) + term * this%m1%x(noden) this%m2%rhs(nodem) = this%m2%rhs(nodem) - term * this%m1%x(noden) else - amatsln(this%idiagm(ignc)) = amatsln(this%idiagm(ignc)) - term + call matrix_sln%add_value_pos(this%idiagm(ignc), -term) if (this%m1%ibound(noden) > 0) then - amatsln(this%idxglo(ignc)) = amatsln(this%idxglo(ignc)) + term + call matrix_sln%add_value_pos(this%idxglo(ignc), term) end if this%m1%rhs(noden) = this%m1%rhs(noden) + term * this%m2%x(nodem) this%m2%rhs(nodem) = this%m2%rhs(nodem) - term * this%m2%x(nodem) diff --git a/src/Exchange/GwfGwfExchange.f90 b/src/Exchange/GwfGwfExchange.f90 index 5e33e8dca28..fb1627ebaf5 100644 --- a/src/Exchange/GwfGwfExchange.f90 +++ b/src/Exchange/GwfGwfExchange.f90 @@ -29,6 +29,7 @@ module GwfGwfExchangeModule use SimVariablesModule, only: errmsg use BlockParserModule, only: BlockParserType use TableModule, only: TableType, table_cr + use MatrixModule implicit none @@ -374,39 +375,26 @@ end subroutine gwf_gwf_ac !! Map the connections in the global matrix !! !< - subroutine gwf_gwf_mc(this, iasln, jasln) + subroutine gwf_gwf_mc(this, matrix_sln) ! -- modules use SparseModule, only: sparsematrix ! -- dummy class(GwfExchangeType) :: this !< GwfExchangeType - integer(I4B), dimension(:), intent(in) :: iasln - integer(I4B), dimension(:), intent(in) :: jasln + class(MatrixBaseType), pointer :: matrix_sln !< the system matrix ! -- local - integer(I4B) :: n, iglo, jglo, ipos + integer(I4B) :: n, iglo, jglo ! ! -- map exchange connections do n = 1, this%nexg iglo = this%nodem1(n) + this%gwfmodel1%moffset jglo = this%nodem2(n) + this%gwfmodel2%moffset - ! -- find jglobal value in row iglo and store in idxglo - do ipos = iasln(iglo), iasln(iglo + 1) - 1 - if (jglo == jasln(ipos)) then - this%idxglo(n) = ipos - exit - end if - end do - ! -- find and store symmetric location - do ipos = iasln(jglo), iasln(jglo + 1) - 1 - if (iglo == jasln(ipos)) then - this%idxsymglo(n) = ipos - exit - end if - end do + this%idxglo(n) = matrix_sln%get_position(iglo, jglo) + this%idxsymglo(n) = matrix_sln%get_position(jglo, iglo) end do ! ! -- map gnc connections if (this%ingnc > 0) then - call this%gnc%gnc_mc(iasln, jasln) + call this%gnc%gnc_mc(matrix_sln) end if ! ! -- Return @@ -584,21 +572,19 @@ end subroutine gwf_gwf_cf !! Calculate conductance and fill coefficient matrix !! !< - subroutine gwf_gwf_fc(this, kiter, iasln, amatsln, rhssln, inwtflag) + subroutine gwf_gwf_fc(this, kiter, matrix_sln, rhs_sln, inwtflag) ! -- modules use ConstantsModule, only: DHALF use GwfNpfModule, only: hcond, vcond ! -- dummy class(GwfExchangeType) :: this !< GwfExchangeType integer(I4B), intent(in) :: kiter - integer(I4B), dimension(:), intent(in) :: iasln - real(DP), dimension(:), intent(inout) :: amatsln - real(DP), dimension(:), intent(inout) :: rhssln + class(MatrixBaseType), pointer :: matrix_sln + real(DP), dimension(:), intent(inout) :: rhs_sln integer(I4B), optional, intent(in) :: inwtflag ! -- local integer(I4B) :: inwt, iexg - integer(I4B) :: i, nodem1sln, nodem2sln, idiagsln - integer(I4B) :: njasln + integer(I4B) :: i, nodem1sln, nodem2sln ! ! -- calculate the conductance for each exchange connection call this%condcalc() @@ -613,19 +599,18 @@ subroutine gwf_gwf_fc(this, kiter, iasln, amatsln, rhssln, inwtflag) ! ! -- Put this%cond into amatsln do i = 1, this%nexg - amatsln(this%idxglo(i)) = this%cond(i) - amatsln(this%idxsymglo(i)) = this%cond(i) + call matrix_sln%set_value_pos(this%idxglo(i), this%cond(i)) + call matrix_sln%set_value_pos(this%idxsymglo(i), this%cond(i)) + nodem1sln = this%nodem1(i) + this%gwfmodel1%moffset nodem2sln = this%nodem2(i) + this%gwfmodel2%moffset - idiagsln = iasln(nodem1sln) - amatsln(idiagsln) = amatsln(idiagsln) - this%cond(i) - idiagsln = iasln(nodem2sln) - amatsln(idiagsln) = amatsln(idiagsln) - this%cond(i) + call matrix_sln%add_diag_value(nodem1sln, -this%cond(i)) + call matrix_sln%add_diag_value(nodem2sln, -this%cond(i)) end do ! ! -- Fill the gnc terms in the solution matrix if (this%ingnc > 0) then - call this%gnc%gnc_fc(kiter, amatsln) + call this%gnc%gnc_fc(kiter, matrix_sln) end if ! ! -- Call mvr fc routine @@ -637,14 +622,13 @@ subroutine gwf_gwf_fc(this, kiter, iasln, amatsln, rhssln, inwtflag) if (inwtflag == 0) inwt = 0 end if if (inwt /= 0) then - call this%exg_fn(kiter, iasln, amatsln) + call this%exg_fn(kiter, matrix_sln) end if ! ! -- Ghost node Newton-Raphson if (this%ingnc > 0) then if (inwt /= 0) then - njasln = size(amatsln) - call this%gnc%gnc_fn(kiter, njasln, amatsln, this%condsat, & + call this%gnc%gnc_fn(kiter, matrix_sln, this%condsat, & ihc_opt=this%ihc, ivarcv_opt=this%ivarcv, & ictm1_opt=this%gwfmodel1%npf%icelltype, & ictm2_opt=this%gwfmodel2%npf%icelltype) @@ -660,21 +644,19 @@ end subroutine gwf_gwf_fc !! Fill amatsln with Newton terms !! !< - subroutine gwf_gwf_fn(this, kiter, iasln, amatsln) + subroutine gwf_gwf_fn(this, kiter, matrix_sln) ! -- modules use SmoothingModule, only: sQuadraticSaturationDerivative ! -- dummy class(GwfExchangeType) :: this !< GwfExchangeType integer(I4B), intent(in) :: kiter - integer(I4B), dimension(:), intent(in) :: iasln - real(DP), dimension(:), intent(inout) :: amatsln + class(MatrixBaseType), pointer :: matrix_sln ! -- local logical :: nisup integer(I4B) :: iexg integer(I4B) :: n, m integer(I4B) :: nodensln, nodemsln integer(I4B) :: ibdn, ibdm - integer(I4B) :: idiagnsln, idiagmsln real(DP) :: topn, topm real(DP) :: botn, botm real(DP) :: topup, botup @@ -739,17 +721,15 @@ subroutine gwf_gwf_fn(this, kiter, iasln, amatsln) ! -- compute terms consterm = -cond * (hup - hdn) derv = sQuadraticSaturationDerivative(topup, botup, hup) - idiagnsln = iasln(nodensln) - idiagmsln = iasln(nodemsln) if (nisup) then ! ! -- fill jacobian with n being upstream term = consterm * derv this%gwfmodel1%rhs(n) = this%gwfmodel1%rhs(n) + term * hn this%gwfmodel2%rhs(m) = this%gwfmodel2%rhs(m) - term * hn - amatsln(idiagnsln) = amatsln(idiagnsln) + term + call matrix_sln%add_diag_value(nodensln, term) if (ibdm > 0) then - amatsln(this%idxsymglo(iexg)) = amatsln(this%idxsymglo(iexg)) - term + call matrix_sln%add_value_pos(this%idxsymglo(iexg), -term) end if else ! @@ -757,9 +737,9 @@ subroutine gwf_gwf_fn(this, kiter, iasln, amatsln) term = -consterm * derv this%gwfmodel1%rhs(n) = this%gwfmodel1%rhs(n) + term * hm this%gwfmodel2%rhs(m) = this%gwfmodel2%rhs(m) - term * hm - amatsln(idiagmsln) = amatsln(idiagmsln) - term + call matrix_sln%add_diag_value(nodemsln, -term) if (ibdn > 0) then - amatsln(this%idxglo(iexg)) = amatsln(this%idxglo(iexg)) + term + call matrix_sln%add_value_pos(this%idxglo(iexg), term) end if end if end if diff --git a/src/Exchange/GwtGwtExchange.f90 b/src/Exchange/GwtGwtExchange.f90 index b12e5c30c04..1c388b2ddae 100644 --- a/src/Exchange/GwtGwtExchange.f90 +++ b/src/Exchange/GwtGwtExchange.f90 @@ -30,6 +30,7 @@ module GwtGwtExchangeModule use SimVariablesModule, only: errmsg use BlockParserModule, only: BlockParserType use TableModule, only: TableType, table_cr + use MatrixModule implicit none @@ -378,14 +379,13 @@ end subroutine gwt_gwt_ad !! Calculate conductance and fill coefficient matrix !! !< - subroutine gwt_gwt_fc(this, kiter, iasln, amatsln, rhssln, inwtflag) + subroutine gwt_gwt_fc(this, kiter, matrix_sln, rhs_sln, inwtflag) ! -- modules ! -- dummy class(GwtExchangeType) :: this !< GwtExchangeType integer(I4B), intent(in) :: kiter - integer(I4B), dimension(:), intent(in) :: iasln - real(DP), dimension(:), intent(inout) :: amatsln - real(DP), dimension(:), intent(inout) :: rhssln + class(MatrixBaseType), pointer :: matrix_sln + real(DP), dimension(:), intent(inout) :: rhs_sln integer(I4B), optional, intent(in) :: inwtflag ! -- local ! diff --git a/src/Exchange/NumericalExchange.f90 b/src/Exchange/NumericalExchange.f90 index 8b9897f86d0..a4efe010307 100644 --- a/src/Exchange/NumericalExchange.f90 +++ b/src/Exchange/NumericalExchange.f90 @@ -5,6 +5,7 @@ module NumericalExchangeModule use BaseExchangeModule, only: BaseExchangeType, AddBaseExchangeToList use NumericalModelModule, only: NumericalModelType use ListModule, only: ListType + use MatrixModule implicit none @@ -23,7 +24,6 @@ module NumericalExchangeModule procedure :: exg_ad procedure :: exg_cf procedure :: exg_fc - procedure :: exg_nr procedure :: exg_cc procedure :: exg_cq procedure :: exg_bd @@ -72,7 +72,7 @@ subroutine exg_ac(this, sparse) return end subroutine exg_ac - subroutine exg_mc(this, iasln, jasln) + subroutine exg_mc(this, matrix_sln) ! ****************************************************************************** ! exg_mc -- Map the connections in the global matrix ! ****************************************************************************** @@ -83,8 +83,7 @@ subroutine exg_mc(this, iasln, jasln) use SparseModule, only: sparsematrix ! -- dummy class(NumericalExchangeType) :: this - integer(I4B), dimension(:), intent(in) :: iasln - integer(I4B), dimension(:), intent(in) :: jasln + class(MatrixBaseType), pointer :: matrix_sln ! -- local ! ------------------------------------------------------------------------------ ! @@ -140,7 +139,7 @@ subroutine exg_cf(this, kiter) return end subroutine exg_cf - subroutine exg_fc(this, kiter, iasln, amatsln, rhssln, inwtflag) + subroutine exg_fc(this, kiter, matrix_sln, rhs_sln, inwtflag) ! ****************************************************************************** ! exg_fc -- Fill the matrix ! ****************************************************************************** @@ -150,9 +149,8 @@ subroutine exg_fc(this, kiter, iasln, amatsln, rhssln, inwtflag) ! -- dummy class(NumericalExchangeType) :: this integer(I4B), intent(in) :: kiter - integer(I4B), dimension(:), intent(in) :: iasln - real(DP), dimension(:), intent(inout) :: amatsln - real(DP), dimension(:), intent(inout) :: rhssln + class(MatrixBaseType), pointer :: matrix_sln + real(DP), dimension(:), intent(inout) :: rhs_sln integer(I4B), optional, intent(in) :: inwtflag ! -- local ! ------------------------------------------------------------------------------ @@ -161,26 +159,6 @@ subroutine exg_fc(this, kiter, iasln, amatsln, rhssln, inwtflag) return end subroutine exg_fc - subroutine exg_nr(this, kiter, iasln, amatsln, inwtflag) -! ****************************************************************************** -! exg_nr -- Add Newton-Raphson terms to the solution -! ****************************************************************************** -! -! SPECIFICATIONS: -! ------------------------------------------------------------------------------ - ! -- dummy - class(NumericalExchangeType) :: this - integer(I4B), intent(in) :: kiter - integer(I4B), dimension(:), intent(in) :: iasln - real(DP), dimension(:), intent(inout) :: amatsln - integer(I4B), optional, intent(in) :: inwtflag - ! -- local -! ------------------------------------------------------------------------------ - ! - ! -- return - return - end subroutine exg_nr - subroutine exg_cc(this, icnvg) ! ****************************************************************************** ! exg_cc -- Additional convergence check diff --git a/src/Model/Connection/GwfGwfConnection.f90 b/src/Model/Connection/GwfGwfConnection.f90 index 625aad620af..2929b21bd82 100644 --- a/src/Model/Connection/GwfGwfConnection.f90 +++ b/src/Model/Connection/GwfGwfConnection.f90 @@ -19,6 +19,7 @@ module GwfGwfConnectionModule use ConnectionsModule, only: ConnectionsType use CellWithNbrsModule, only: GlobalCellType use DistributedDataModule + use MatrixModule implicit none private @@ -313,9 +314,7 @@ subroutine gwfgwfcon_cf(this, kiter) integer(I4B) :: i ! reset interface system - do i = 1, this%nja - this%amat(i) = 0.0_DP - end do + call this%matrix%zero_entries() do i = 1, this%neq this%rhs(i) = 0.0_DP end do @@ -327,18 +326,20 @@ end subroutine gwfgwfcon_cf !> @brief Write the calculated coefficients into the global !< system matrix and the rhs - subroutine gwfgwfcon_fc(this, kiter, iasln, amatsln, rhssln, inwtflag) + subroutine gwfgwfcon_fc(this, kiter, matrix_sln, rhs_sln, inwtflag) class(GwfGwfConnectionType) :: this !< this connection integer(I4B), intent(in) :: kiter !< the iteration counter - integer(I4B), dimension(:), intent(in) :: iasln !< global system's IA array - real(DP), dimension(:), intent(inout) :: amatsln !< global system matrix coefficients - real(DP), dimension(:), intent(inout) :: rhssln !< global right-hand-side + class(MatrixBaseType), pointer :: matrix_sln !< global system matrix coefficients + real(DP), dimension(:), intent(inout) :: rhs_sln !< global right-hand-side integer(I4B), optional, intent(in) :: inwtflag !< newton-raphson flag ! local - integer(I4B) :: n, ipos, nglo + integer(I4B) :: n, nglo + integer(I4B) :: ipos, icol_start, icol_end + class(MatrixBaseType), pointer :: matrix_base ! fill (and add to...) coefficients for interface - call this%gwfInterfaceModel%model_fc(kiter, this%amat, this%nja, inwtflag) + matrix_base => this%matrix + call this%gwfInterfaceModel%model_fc(kiter, matrix_base, inwtflag) ! map back to solution matrix do n = 1, this%neq @@ -352,11 +353,13 @@ subroutine gwfgwfcon_fc(this, kiter, iasln, amatsln, rhssln, inwtflag) nglo = this%gridConnection%idxToGlobal(n)%index + & this%gridConnection%idxToGlobal(n)%dmodel%moffset - rhssln(nglo) = rhssln(nglo) + this%rhs(n) + rhs_sln(nglo) = rhs_sln(nglo) + this%rhs(n) - do ipos = this%ia(n), this%ia(n + 1) - 1 - amatsln(this%mapIdxToSln(ipos)) = amatsln(this%mapIdxToSln(ipos)) + & - this%amat(ipos) + icol_start = this%matrix%get_first_col_pos(n) + icol_end = this%matrix%get_last_col_pos(n) + do ipos = icol_start, icol_end + call matrix_sln%add_value_pos(this%mapIdxToSln(ipos), & + this%matrix%get_value_pos(ipos)) end do end do diff --git a/src/Model/Connection/GwtGwtConnection.f90 b/src/Model/Connection/GwtGwtConnection.f90 index 8c0e9ec1efa..fcbae658254 100644 --- a/src/Model/Connection/GwtGwtConnection.f90 +++ b/src/Model/Connection/GwtGwtConnection.f90 @@ -14,6 +14,7 @@ module GwtGwtConnectionModule use ConnectionsModule, only: ConnectionsType use CellWithNbrsModule, only: GlobalCellType use DistributedDataModule + use MatrixModule implicit none private @@ -397,9 +398,7 @@ subroutine gwtgwtcon_cf(this, kiter) integer(I4B) :: i ! reset interface system - do i = 1, this%nja - this%amat(i) = 0.0_DP - end do + call this%matrix%zero_entries() do i = 1, this%neq this%rhs(i) = 0.0_DP end do @@ -408,17 +407,19 @@ subroutine gwtgwtcon_cf(this, kiter) end subroutine gwtgwtcon_cf - subroutine gwtgwtcon_fc(this, kiter, iasln, amatsln, rhssln, inwtflag) + subroutine gwtgwtcon_fc(this, kiter, matrix_sln, rhs_sln, inwtflag) class(GwtGwtConnectionType) :: this !< the connection integer(I4B), intent(in) :: kiter !< the iteration counter - integer(I4B), dimension(:), intent(in) :: iasln !< global system's IA array - real(DP), dimension(:), intent(inout) :: amatsln !< global system matrix coefficients - real(DP), dimension(:), intent(inout) :: rhssln !< global right-hand-side + class(MatrixBaseType), pointer :: matrix_sln !< the system matrix + real(DP), dimension(:), intent(inout) :: rhs_sln !< global right-hand-side integer(I4B), optional, intent(in) :: inwtflag !< newton-raphson flag ! local - integer(I4B) :: n, nglo, ipos + integer(I4B) :: n, nglo + integer(I4B) :: icol_start, icol_end, ipos + class(MatrixBaseType), pointer :: matrix_base - call this%gwtInterfaceModel%model_fc(kiter, this%amat, this%nja, inwtflag) + matrix_base => this%matrix + call this%gwtInterfaceModel%model_fc(kiter, matrix_base, inwtflag) ! map back to solution matrix do n = 1, this%neq @@ -430,18 +431,20 @@ subroutine gwtgwtcon_fc(this, kiter, iasln, amatsln, rhssln, inwtflag) nglo = this%gridConnection%idxToGlobal(n)%index + & this%gridConnection%idxToGlobal(n)%dmodel%moffset - rhssln(nglo) = rhssln(nglo) + this%rhs(n) + rhs_sln(nglo) = rhs_sln(nglo) + this%rhs(n) - do ipos = this%ia(n), this%ia(n + 1) - 1 - amatsln(this%mapIdxToSln(ipos)) = amatsln(this%mapIdxToSln(ipos)) + & - this%amat(ipos) + icol_start = this%matrix%get_first_col_pos(n) + icol_end = this%matrix%get_last_col_pos(n) + do ipos = icol_start, icol_end + call matrix_sln%add_value_pos(this%mapIdxToSln(ipos), & + this%matrix%get_value_pos(ipos)) end do end do ! FC the movers through the exchange; we can call ! exg_fc() directly because it only handles mover terms (unlike in GwfExchange%exg_fc) if (this%exchangeIsOwned) then - call this%gwtExchange%exg_fc(kiter, iasln, amatsln, rhssln, inwtflag) + call this%gwtExchange%exg_fc(kiter, matrix_sln, rhs_sln, inwtflag) end if end subroutine gwtgwtcon_fc diff --git a/src/Model/Connection/SpatialModelConnection.f90 b/src/Model/Connection/SpatialModelConnection.f90 index c06bcdeb7e4..981747a7a1f 100644 --- a/src/Model/Connection/SpatialModelConnection.f90 +++ b/src/Model/Connection/SpatialModelConnection.f90 @@ -13,6 +13,8 @@ module SpatialModelConnectionModule use InterfaceMapModule use DistributedDataModule use ListModule, only: ListType + use MatrixModule + use SparseMatrixModule implicit none private @@ -42,10 +44,7 @@ module SpatialModelConnectionModule ! The following variables are equivalent to those in Numerical Solution: integer(I4B), pointer :: neq => null() !< nr. of equations in matrix system - integer(I4B), pointer :: nja => null() !< nr. of nonzero matrix elements - integer(I4B), dimension(:), pointer, contiguous :: ia => null() !< sparse indexing IA - integer(I4B), dimension(:), pointer, contiguous :: ja => null() !< sparse indexing JA - real(DP), dimension(:), pointer, contiguous :: amat => null() !< matrix coefficients + class(SparseMatrixType), pointer :: matrix => null() !< system matrix for the interface real(DP), dimension(:), pointer, contiguous :: rhs => null() !< rhs of interface system real(DP), dimension(:), pointer, contiguous :: x => null() !< dependent variable of interface system integer(I4B), dimension(:), pointer, contiguous :: active => null() !< cell status (c.f. ibound) of interface system @@ -110,6 +109,7 @@ subroutine spatialConnection_ctor(this, model, exchange, name) this%primaryExchange => exchange allocate (this%gridConnection) + allocate (this%matrix) call this%allocateScalars() this%internalStencilDepth = 1 @@ -198,6 +198,7 @@ subroutine spatialcon_connect(this) class(SpatialModelConnectionType) :: this !< this connection ! local type(sparsematrix) :: sparse + class(MatrixBaseType), pointer :: matrix_base call sparse%init(this%neq, this%neq, 7) call this%interfaceModel%model_ac(sparse) @@ -207,7 +208,8 @@ subroutine spatialcon_connect(this) call sparse%destroy() ! map connections - call this%interfaceModel%model_mc(this%ia, this%ja) + matrix_base => this%matrix + call this%interfaceModel%model_mc(matrix_base) call this%maskOwnerConnections() end subroutine spatialcon_connect @@ -275,6 +277,7 @@ subroutine spatialcon_ac(this, sparse) type(sparsematrix), intent(inout) :: sparse !< sparse matrix to store the connections ! local integer(I4B) :: n, m, ipos + integer(I4B) :: icol_start, icol_end integer(I4B) :: nglo, mglo do n = 1, this%neq @@ -282,48 +285,52 @@ subroutine spatialcon_ac(this, sparse) ! only add connections for own model to global matrix cycle end if + nglo = this%gridConnection%idxToGlobal(n)%index + & this%gridConnection%idxToGlobal(n)%dmodel%moffset - do ipos = this%ia(n) + 1, this%ia(n + 1) - 1 - m = this%ja(ipos) + + icol_start = this%matrix%get_first_col_pos(n) + icol_end = this%matrix%get_last_col_pos(n) + do ipos = icol_start, icol_end + m = this%matrix%get_column(ipos) + if (m == n) cycle mglo = this%gridConnection%idxToGlobal(m)%index + & this%gridConnection%idxToGlobal(m)%dmodel%moffset - call sparse%addconnection(nglo, mglo, 1) end do + end do end subroutine spatialcon_ac !> @brief Creates the mapping from the local system !< matrix to the global one - subroutine spatialcon_mc(this, iasln, jasln) + subroutine spatialcon_mc(this, matrix_sln) use SimModule, only: ustop class(SpatialModelConnectionType) :: this !< this connection - integer(I4B), dimension(:), intent(in) :: iasln !< global IA array - integer(I4B), dimension(:), intent(in) :: jasln !< global JA array + class(MatrixBaseType), pointer :: matrix_sln !< global matrix ! local - integer(I4B) :: m, n, mglo, nglo, ipos, csrIdx + integer(I4B) :: m, n, mglo, nglo, ipos, ipos_sln logical(LGP) :: isOwned - allocate (this%mapIdxToSln(this%nja)) + allocate (this%mapIdxToSln(this%matrix%nja)) do n = 1, this%neq isOwned = (this%gridConnection%idxToGlobal(n)%dmodel == this%owner) - do ipos = this%ia(n), this%ia(n + 1) - 1 - m = this%ja(ipos) + do ipos = this%matrix%ia(n), this%matrix%ia(n + 1) - 1 + m = this%matrix%ja(ipos) nglo = this%gridConnection%idxToGlobal(n)%index + & this%gridConnection%idxToGlobal(n)%dmodel%moffset mglo = this%gridConnection%idxToGlobal(m)%index + & this%gridConnection%idxToGlobal(m)%dmodel%moffset - csrIdx = getCSRIndex(nglo, mglo, iasln, jasln) - if (csrIdx == -1 .and. isOwned) then + ipos_sln = matrix_sln%get_position(nglo, mglo) + if (ipos_sln == -1 .and. isOwned) then ! this should not be possible write (*, *) 'Error: cannot find cell connection in global system' call ustop() end if - this%mapIdxToSln(ipos) = csrIdx + this%mapIdxToSln(ipos) = ipos_sln end do end do @@ -335,19 +342,17 @@ subroutine spatialcon_da(this) class(SpatialModelConnectionType) :: this !< this connection call mem_deallocate(this%neq) - call mem_deallocate(this%nja) call mem_deallocate(this%internalStencilDepth) call mem_deallocate(this%exchangeStencilDepth) call mem_deallocate(this%nrOfConnections) - call mem_deallocate(this%ia) - call mem_deallocate(this%ja) - call mem_deallocate(this%amat) - call mem_deallocate(this%x) call mem_deallocate(this%rhs) call mem_deallocate(this%active) + call this%matrix%destroy() + deallocate (this%matrix) + call this%gridConnection%destroy() call this%distVarList%Clear(destroy=.true.) deallocate (this%gridConnection) @@ -387,7 +392,6 @@ subroutine allocateScalars(this) class(SpatialModelConnectionType) :: this !< this connection call mem_allocate(this%neq, 'NEQ', this%memoryPath) - call mem_allocate(this%nja, 'NJA', this%memoryPath) call mem_allocate(this%internalStencilDepth, 'INTSTDEPTH', this%memoryPath) call mem_allocate(this%exchangeStencilDepth, 'EXGSTDEPTH', this%memoryPath) call mem_allocate(this%nrOfConnections, 'NROFCONNS', this%memoryPath) @@ -433,21 +437,9 @@ subroutine createCoefficientMatrix(this, sparse) use SimModule, only: ustop class(SpatialModelConnectionType) :: this !< this connection type(sparsematrix), intent(inout) :: sparse !< the sparse matrix with the cell connections - ! local - integer(I4B) :: ierror - - this%nja = sparse%nnz - call mem_allocate(this%ia, this%neq + 1, 'IA', this%memoryPath) - call mem_allocate(this%ja, this%nja, 'JA', this%memoryPath) - call mem_allocate(this%amat, this%nja, 'AMAT', this%memoryPath) call sparse%sort() - call sparse%filliaja(this%ia, this%ja, ierror) - - if (ierror /= 0) then - write (*, *) 'Error: cannot fill ia/ja for model connection' - call ustop() - end if + call this%matrix%create(sparse, this%memoryPath) end subroutine createCoefficientMatrix diff --git a/src/Model/GroundWaterFlow/gwf3.f90 b/src/Model/GroundWaterFlow/gwf3.f90 index 0961b2e81c8..46dcab65d32 100644 --- a/src/Model/GroundWaterFlow/gwf3.f90 +++ b/src/Model/GroundWaterFlow/gwf3.f90 @@ -22,6 +22,7 @@ module GwfModule use GwfObsModule, only: GwfObsType, gwf_obs_cr use SimModule, only: count_errors, store_error use BaseModelModule, only: BaseModelType + use MatrixModule implicit none @@ -376,11 +377,10 @@ end subroutine gwf_ac !> @brief Map the positions of this models connections in the !! numerical solution coefficient matrix. !< - subroutine gwf_mc(this, iasln, jasln) + subroutine gwf_mc(this, matrix_sln) ! -- dummy class(GwfModelType) :: this - integer(I4B), dimension(:), intent(in) :: iasln - integer(I4B), dimension(:), intent(in) :: jasln + class(MatrixBaseType), pointer :: matrix_sln ! -- local class(BndType), pointer :: packobj integer(I4B) :: ip @@ -388,20 +388,20 @@ subroutine gwf_mc(this, iasln, jasln) ! ! -- Find the position of each connection in the global ia, ja structure ! and store them in idxglo. - call this%dis%dis_mc(this%moffset, this%idxglo, iasln, jasln) + call this%dis%dis_mc(this%moffset, this%idxglo, matrix_sln) ! ! -- Map any additional connections that NPF may need - if (this%innpf > 0) call this%npf%npf_mc(this%moffset, iasln, jasln) + if (this%innpf > 0) call this%npf%npf_mc(this%moffset, matrix_sln) ! ! -- Map any package connections do ip = 1, this%bndlist%Count() packobj => GetBndFromList(this%bndlist, ip) - call packobj%bnd_mc(this%moffset, iasln, jasln) + call packobj%bnd_mc(this%moffset, matrix_sln) end do ! ! -- For implicit gnc, need to store positions of gnc connections ! in solution matrix connection - if (this%ingnc > 0) call this%gnc%gnc_mc(iasln, jasln) + if (this%ingnc > 0) call this%gnc%gnc_mc(matrix_sln) ! ! -- return return @@ -574,12 +574,11 @@ subroutine gwf_cf(this, kiter) end subroutine gwf_cf !> @brief GroundWater Flow Model fill coefficients - subroutine gwf_fc(this, kiter, amatsln, njasln, inwtflag) + subroutine gwf_fc(this, kiter, matrix_sln, inwtflag) ! -- dummy class(GwfModelType) :: this integer(I4B), intent(in) :: kiter - integer(I4B), intent(in) :: njasln - real(DP), dimension(njasln), intent(inout) :: amatsln + class(MatrixBaseType), pointer :: matrix_sln integer(I4B), intent(in) :: inwtflag ! -- local class(BndType), pointer :: packobj @@ -600,41 +599,40 @@ subroutine gwf_fc(this, kiter, amatsln, njasln, inwtflag) end if ! ! -- Fill standard conductance terms - if (this%innpf > 0) call this%npf%npf_fc(kiter, njasln, amatsln, & - this%idxglo, this%rhs, this%x) - if (this%inbuy > 0) call this%buy%buy_fc(kiter, njasln, amatsln, & - this%idxglo, this%rhs, this%x) - if (this%inhfb > 0) call this%hfb%hfb_fc(kiter, njasln, amatsln, & - this%idxglo, this%rhs, this%x) - if (this%ingnc > 0) call this%gnc%gnc_fc(kiter, amatsln) + if (this%innpf > 0) call this%npf%npf_fc(kiter, matrix_sln, this%idxglo, & + this%rhs, this%x) + if (this%inbuy > 0) call this%buy%buy_fc(kiter, matrix_sln, this%idxglo, & + this%rhs, this%x) + if (this%inhfb > 0) call this%hfb%hfb_fc(kiter, matrix_sln, this%idxglo, & + this%rhs, this%x) + if (this%ingnc > 0) call this%gnc%gnc_fc(kiter, matrix_sln) ! -- storage if (this%insto > 0) then - call this%sto%sto_fc(kiter, this%xold, this%x, njasln, amatsln, & + call this%sto%sto_fc(kiter, this%xold, this%x, matrix_sln, & this%idxglo, this%rhs) end if ! -- skeletal storage, compaction, and land subsidence if (this%incsub > 0) then - call this%csub%csub_fc(kiter, this%xold, this%x, njasln, amatsln, & + call this%csub%csub_fc(kiter, this%xold, this%x, matrix_sln, & this%idxglo, this%rhs) end if if (this%inmvr > 0) call this%mvr%mvr_fc() do ip = 1, this%bndlist%Count() packobj => GetBndFromList(this%bndlist, ip) - call packobj%bnd_fc(this%rhs, this%ia, this%idxglo, amatsln) + call packobj%bnd_fc(this%rhs, this%ia, this%idxglo, matrix_sln) end do ! !--Fill newton terms if (this%innpf > 0) then if (inwt /= 0) then - call this%npf%npf_fn(kiter, njasln, amatsln, this%idxglo, this%rhs, & - this%x) + call this%npf%npf_fn(kiter, matrix_sln, this%idxglo, this%rhs, this%x) end if end if ! ! -- Fill newton terms for ghost nodes if (this%ingnc > 0) then if (inwt /= 0) then - call this%gnc%gnc_fn(kiter, njasln, amatsln, this%npf%condsat, & + call this%gnc%gnc_fn(kiter, matrix_sln, this%npf%condsat, & ivarcv_opt=this%npf%ivarcv, & ictm1_opt=this%npf%icelltype, & ictm2_opt=this%npf%icelltype) @@ -644,7 +642,7 @@ subroutine gwf_fc(this, kiter, amatsln, njasln, inwtflag) ! -- Fill newton terms for storage if (this%insto > 0) then if (inwtsto /= 0) then - call this%sto%sto_fn(kiter, this%xold, this%x, njasln, amatsln, & + call this%sto%sto_fn(kiter, this%xold, this%x, matrix_sln, & this%idxglo, this%rhs) end if end if @@ -652,7 +650,7 @@ subroutine gwf_fc(this, kiter, amatsln, njasln, inwtflag) ! -- Fill newton terms for skeletal storage, compaction, and land subsidence if (this%incsub > 0) then if (inwtcsub /= 0) then - call this%csub%csub_fn(kiter, this%xold, this%x, njasln, amatsln, & + call this%csub%csub_fn(kiter, this%xold, this%x, matrix_sln, & this%idxglo, this%rhs) end if end if @@ -663,7 +661,7 @@ subroutine gwf_fc(this, kiter, amatsln, njasln, inwtflag) inwtpak = inwtflag if (inwtflag == 1) inwtpak = packobj%inewton if (inwtpak /= 0) then - call packobj%bnd_fn(this%rhs, this%ia, this%idxglo, amatsln) + call packobj%bnd_fn(this%rhs, this%ia, this%idxglo, matrix_sln) end if end do ! @@ -746,27 +744,24 @@ end subroutine gwf_ptcchk !! for the current outer iteration !! !< - subroutine gwf_ptc(this, kiter, neqsln, njasln, ia, ja, & - x, rhs, amatsln, iptc, ptcf) + subroutine gwf_ptc(this, kiter, neqsln, matrix, & + x, rhs, iptc, ptcf) ! modules use ConstantsModule, only: DONE, DP9 ! -- dummy class(GwfModelType) :: this integer(I4B), intent(in) :: kiter integer(I4B), intent(in) :: neqsln - integer(I4B), intent(in) :: njasln - integer(I4B), dimension(neqsln + 1), intent(in) :: ia - integer(I4B), dimension(njasln), intent(in) :: ja + class(MatrixBaseType), pointer :: matrix real(DP), dimension(neqsln), intent(in) :: x real(DP), dimension(neqsln), intent(in) :: rhs - real(DP), dimension(njasln), intent(in) :: amatsln integer(I4B), intent(inout) :: iptc real(DP), intent(inout) :: ptcf ! -- local integer(I4B) :: iptct integer(I4B) :: n - integer(I4B) :: jcol - integer(I4B) :: j, jj + integer(I4B) :: jrow + integer(I4B) :: j real(DP) :: v real(DP) :: resid real(DP) :: ptcdelem1 @@ -774,6 +769,7 @@ subroutine gwf_ptc(this, kiter, neqsln, njasln, ia, ja, & real(DP) :: diagcnt real(DP) :: diagmin real(DP) :: diagmax + integer(I4B) :: first_col, last_col ! ------------------------------------------------------------------------------ ! -- set temporary flag indicating if pseudo-transient continuation should ! be used for this model and time step @@ -795,18 +791,20 @@ subroutine gwf_ptc(this, kiter, neqsln, njasln, ia, ja, & diagcnt = DZERO do n = 1, this%dis%nodes if (this%npf%ibound(n) < 1) cycle - jcol = n + this%moffset + ! TODO_MJR: why is this jcol and not jrow? + jrow = n + this%moffset ! ! get the maximum volume of the cell (head at top of cell) v = this%dis%get_cell_volume(n, this%dis%top(n)) ! ! -- calculate the residual for the cell resid = DZERO - do j = ia(jcol), ia(jcol + 1) - 1 - jj = ja(j) - resid = resid + amatsln(j) * x(jcol) + first_col = matrix%get_first_col_pos(jrow) + last_col = matrix%get_last_col_pos(jrow) + do j = first_col, last_col + resid = resid + matrix%get_value_pos(j) * x(jrow) end do - resid = resid - rhs(jcol) + resid = resid - rhs(jrow) ! ! -- calculate the reciprocal of the pseudo-time step ! resid [L3/T] / volume [L3] = [1/T] @@ -818,8 +816,7 @@ subroutine gwf_ptc(this, kiter, neqsln, njasln, ia, ja, & if (ptcdelem1 > ptcf) ptcf = ptcdelem1 ! ! -- determine minimum and maximum diagonal entries - j = ia(jcol) - diag = abs(amatsln(j)) + diag = abs(matrix%get_diag_value(jrow)) diagcnt = diagcnt + DONE if (diag > DZERO) then if (diag < diagmin) diagmin = diag diff --git a/src/Model/GroundWaterFlow/gwf3api8.f90 b/src/Model/GroundWaterFlow/gwf3api8.f90 index 2e381f14149..583d4cff942 100644 --- a/src/Model/GroundWaterFlow/gwf3api8.f90 +++ b/src/Model/GroundWaterFlow/gwf3api8.f90 @@ -17,6 +17,7 @@ module apimodule use ObsModule, only: DefaultObsIdProcessor use TimeSeriesLinkModule, only: TimeSeriesLinkType, & GetTimeSeriesLinkFromList + use MatrixModule ! implicit none ! @@ -130,13 +131,13 @@ end subroutine api_rp !! package terms. !! !< - subroutine api_fc(this, rhs, ia, idxglo, amatsln) + subroutine api_fc(this, rhs, ia, idxglo, matrix_sln) ! -- dummy variables class(ApiType) :: this real(DP), dimension(:), intent(inout) :: rhs !< right-hand side vector integer(I4B), dimension(:), intent(in) :: ia !< pointer to the rows in A matrix integer(I4B), dimension(:), intent(in) :: idxglo !< position of entries in A matrix - real(DP), dimension(:), intent(inout) :: amatsln !< A matrix for solution + class(MatrixBaseType), pointer :: matrix_sln !< A matrix for solution ! -- local variables integer(I4B) :: i integer(I4B) :: n @@ -153,7 +154,7 @@ subroutine api_fc(this, rhs, ia, idxglo, amatsln) n = this%nodelist(i) rhs(n) = rhs(n) + this%rhs(i) ipos = ia(n) - amatsln(idxglo(ipos)) = amatsln(idxglo(ipos)) + this%hcof(i) + call matrix_sln%add_value_pos(idxglo(ipos), this%hcof(i)) ! ! -- If mover is active and this boundary is discharging, ! store available water (as positive value). diff --git a/src/Model/GroundWaterFlow/gwf3buy8.f90 b/src/Model/GroundWaterFlow/gwf3buy8.f90 index 835f09f2d40..ca37fe8970c 100644 --- a/src/Model/GroundWaterFlow/gwf3buy8.f90 +++ b/src/Model/GroundWaterFlow/gwf3buy8.f90 @@ -13,6 +13,7 @@ module GwfBuyModule use BaseDisModule, only: DisBaseType use GwfNpfModule, only: GwfNpfType use GwfBuyInputDataModule, only: GwfBuyInputDataType + use MatrixModule implicit none @@ -937,7 +938,7 @@ subroutine buy_cf_maw(packobj, hnew, dense, elev, denseref, locdense, & return end subroutine buy_cf_maw - subroutine buy_fc(this, kiter, njasln, amat, idxglo, rhs, hnew) + subroutine buy_fc(this, kiter, matrix_sln, idxglo, rhs, hnew) ! ****************************************************************************** ! buy_fc -- Fill coefficients ! ****************************************************************************** @@ -947,8 +948,7 @@ subroutine buy_fc(this, kiter, njasln, amat, idxglo, rhs, hnew) ! -- dummy class(GwfBuyType) :: this integer(I4B) :: kiter - integer, intent(in) :: njasln - real(DP), dimension(njasln), intent(inout) :: amat + class(MatrixBaseType), pointer :: matrix_sln integer(I4B), intent(in), dimension(:) :: idxglo real(DP), dimension(:), intent(inout) :: rhs real(DP), intent(inout), dimension(:) :: hnew @@ -976,8 +976,8 @@ subroutine buy_fc(this, kiter, njasln, amat, idxglo, rhs, hnew) ! ! -- Add terms to rhs, diagonal, and off diagonal rhs(n) = rhs(n) - rhsterm - amat(idxglo(idiag)) = amat(idxglo(idiag)) - amatnn - amat(idxglo(ipos)) = amat(idxglo(ipos)) + amatnm + call matrix_sln%add_value_pos(idxglo(idiag), -amatnn) + call matrix_sln%add_value_pos(idxglo(ipos), amatnm) end do end do ! diff --git a/src/Model/GroundWaterFlow/gwf3chd8.f90 b/src/Model/GroundWaterFlow/gwf3chd8.f90 index e65e95789d9..f146b455059 100644 --- a/src/Model/GroundWaterFlow/gwf3chd8.f90 +++ b/src/Model/GroundWaterFlow/gwf3chd8.f90 @@ -9,6 +9,7 @@ module ChdModule use ObserveModule, only: ObserveType use TimeSeriesLinkModule, only: TimeSeriesLinkType, & GetTimeSeriesLinkFromList + use MatrixModule ! implicit none ! @@ -252,7 +253,7 @@ subroutine chd_ck(this) return end subroutine chd_ck - subroutine chd_fc(this, rhs, ia, idxglo, amatsln) + subroutine chd_fc(this, rhs, ia, idxglo, matrix_sln) ! ************************************************************************** ! chd_fc -- Override bnd_fc and do nothing ! ************************************************************************** @@ -264,7 +265,7 @@ subroutine chd_fc(this, rhs, ia, idxglo, amatsln) real(DP), dimension(:), intent(inout) :: rhs integer(I4B), dimension(:), intent(in) :: ia integer(I4B), dimension(:), intent(in) :: idxglo - real(DP), dimension(:), intent(inout) :: amatsln + class(MatrixBaseType), pointer :: matrix_sln ! -- local ! -------------------------------------------------------------------------- ! diff --git a/src/Model/GroundWaterFlow/gwf3csub8.f90 b/src/Model/GroundWaterFlow/gwf3csub8.f90 index e03f2623b3c..a6f377bbb40 100644 --- a/src/Model/GroundWaterFlow/gwf3csub8.f90 +++ b/src/Model/GroundWaterFlow/gwf3csub8.f90 @@ -42,6 +42,7 @@ module GwfCsubModule use TableModule, only: TableType, table_cr ! use IMSLinearMisc, only: ims_misc_thomas + use MatrixModule ! implicit none ! @@ -2783,7 +2784,7 @@ end subroutine csub_ad !! Fill the coefficient matrix and right-hand side with the CSUB package terms. !! !< - subroutine csub_fc(this, kiter, hold, hnew, njasln, amat, idxglo, rhs) + subroutine csub_fc(this, kiter, hold, hnew, matrix_sln, idxglo, rhs) ! -- modules use TdisModule, only: delt ! -- dummy variables @@ -2791,8 +2792,7 @@ subroutine csub_fc(this, kiter, hold, hnew, njasln, amat, idxglo, rhs) integer(I4B), intent(in) :: kiter !< outer iteration numbed real(DP), intent(in), dimension(:) :: hold !< previous heads real(DP), intent(in), dimension(:) :: hnew !< current heads - integer(I4B), intent(in) :: njasln !< size of the A matrix for the solution - real(DP), dimension(njasln), intent(inout) :: amat !< A matrix + class(MatrixBaseType), pointer :: matrix_sln !< A matrix integer(I4B), intent(in), dimension(:) :: idxglo !< global index model to solution real(DP), intent(inout), dimension(:) :: rhs !< right-hand side ! -- local variables @@ -2841,7 +2841,7 @@ subroutine csub_fc(this, kiter, hold, hnew, njasln, amat, idxglo, rhs) hcof, rhsterm) ! ! -- add coarse-grained storage terms to amat and rhs for coarse-grained storage - amat(idxglo(idiag)) = amat(idxglo(idiag)) + hcof + call matrix_sln%add_value_pos(idxglo(idiag), hcof) rhs(node) = rhs(node) + rhsterm ! ! -- calculate coarse-grained water compressibility @@ -2852,7 +2852,7 @@ subroutine csub_fc(this, kiter, hold, hnew, njasln, amat, idxglo, rhs) ! ! -- add water compression storage terms to amat and rhs for ! coarse-grained storage - amat(idxglo(idiag)) = amat(idxglo(idiag)) + hcof + call matrix_sln%add_value_pos(idxglo(idiag), hcof) rhs(node) = rhs(node) + rhsterm end if end do @@ -2869,7 +2869,7 @@ subroutine csub_fc(this, kiter, hold, hnew, njasln, amat, idxglo, rhs) area = this%dis%get_area(node) call this%csub_interbed_fc(ib, node, area, hnew(node), hold(node), & hcof, rhsterm) - amat(idxglo(idiag)) = amat(idxglo(idiag)) + hcof + call matrix_sln%add_value_pos(idxglo(idiag), hcof) rhs(node) = rhs(node) + rhsterm ! ! -- calculate interbed water compressibility terms @@ -2879,7 +2879,7 @@ subroutine csub_fc(this, kiter, hold, hnew, njasln, amat, idxglo, rhs) hcof, rhsterm) ! ! -- add water compression storage terms to amat and rhs for interbed - amat(idxglo(idiag)) = amat(idxglo(idiag)) + hcof + call matrix_sln%add_value_pos(idxglo(idiag), hcof) rhs(node) = rhs(node) + rhsterm end if end do @@ -2904,7 +2904,7 @@ end subroutine csub_fc !! @param[in,out] rhs right-hand side !! !< - subroutine csub_fn(this, kiter, hold, hnew, njasln, amat, idxglo, rhs) + subroutine csub_fn(this, kiter, hold, hnew, matrix_sln, idxglo, rhs) ! -- modules use TdisModule, only: delt ! -- dummy variables @@ -2912,8 +2912,7 @@ subroutine csub_fn(this, kiter, hold, hnew, njasln, amat, idxglo, rhs) integer(I4B), intent(in) :: kiter !< outer iteration number real(DP), intent(in), dimension(:) :: hold !< previous heads real(DP), intent(in), dimension(:) :: hnew !< current heads - integer(I4B), intent(in) :: njasln !< size of the A matrix for the solution - real(DP), dimension(njasln), intent(inout) :: amat !< A matrix + class(MatrixBaseType), pointer :: matrix_sln !< A matrix integer(I4B), intent(in), dimension(:) :: idxglo !< global index model to solution real(DP), intent(inout), dimension(:) :: rhs !< right-hand side ! -- local variables @@ -2944,7 +2943,7 @@ subroutine csub_fn(this, kiter, hold, hnew, njasln, amat, idxglo, rhs) ! ! -- add coarse-grained storage newton terms to amat and rhs for ! coarse-grained storage - amat(idxglo(idiag)) = amat(idxglo(idiag)) + hcof + call matrix_sln%add_value_pos(idxglo(idiag), hcof) rhs(node) = rhs(node) + rhsterm ! ! -- calculate coarse-grained water compressibility storage @@ -2955,7 +2954,7 @@ subroutine csub_fn(this, kiter, hold, hnew, njasln, amat, idxglo, rhs) ! ! -- add water compression storage newton terms to amat and rhs for ! coarse-grained storage - amat(idxglo(idiag)) = amat(idxglo(idiag)) + hcof + call matrix_sln%add_value_pos(idxglo(idiag), hcof) rhs(node) = rhs(node) + rhsterm end if end do @@ -2979,7 +2978,7 @@ subroutine csub_fn(this, kiter, hold, hnew, njasln, amat, idxglo, rhs) hcof, rhsterm) ! ! -- add interbed newton terms to amat and rhs - amat(idxglo(idiag)) = amat(idxglo(idiag)) + hcof + call matrix_sln%add_value_pos(idxglo(idiag), hcof) rhs(node) = rhs(node) + rhsterm ! ! -- calculate interbed water compressibility terms @@ -2989,7 +2988,7 @@ subroutine csub_fn(this, kiter, hold, hnew, njasln, amat, idxglo, rhs) hcof, rhsterm) ! ! -- add interbed water compression newton terms to amat and rhs - amat(idxglo(idiag)) = amat(idxglo(idiag)) + hcof + call matrix_sln%add_value_pos(idxglo(idiag), hcof) rhs(node) = rhs(node) + rhsterm end if end do diff --git a/src/Model/GroundWaterFlow/gwf3drn8.f90 b/src/Model/GroundWaterFlow/gwf3drn8.f90 index 45a329f9faa..08804ade1e2 100644 --- a/src/Model/GroundWaterFlow/gwf3drn8.f90 +++ b/src/Model/GroundWaterFlow/gwf3drn8.f90 @@ -9,6 +9,7 @@ module DrnModule use ObsModule, only: DefaultObsIdProcessor use TimeSeriesLinkModule, only: TimeSeriesLinkType, & GetTimeSeriesLinkFromList + use MatrixModule ! implicit none ! @@ -343,7 +344,7 @@ subroutine drn_cf(this, reset_mover) return end subroutine drn_cf - subroutine drn_fc(this, rhs, ia, idxglo, amatsln) + subroutine drn_fc(this, rhs, ia, idxglo, matrix_sln) ! ************************************************************************** ! drn_fc -- Copy rhs and hcof into solution rhs and amat ! ************************************************************************** @@ -355,7 +356,7 @@ subroutine drn_fc(this, rhs, ia, idxglo, amatsln) real(DP), dimension(:), intent(inout) :: rhs integer(I4B), dimension(:), intent(in) :: ia integer(I4B), dimension(:), intent(in) :: idxglo - real(DP), dimension(:), intent(inout) :: amatsln + class(MatrixBaseType), pointer :: matrix_sln ! -- local integer(I4B) :: i integer(I4B) :: n @@ -376,7 +377,7 @@ subroutine drn_fc(this, rhs, ia, idxglo, amatsln) n = this%nodelist(i) rhs(n) = rhs(n) + this%rhs(i) ipos = ia(n) - amatsln(idxglo(ipos)) = amatsln(idxglo(ipos)) + this%hcof(i) + call matrix_sln%add_value_pos(idxglo(ipos), this%hcof(i)) ! ! -- calculate the drainage scaling factor call this%get_drain_factor(i, fact, drnbot) @@ -394,7 +395,7 @@ subroutine drn_fc(this, rhs, ia, idxglo, amatsln) return end subroutine drn_fc - subroutine drn_fn(this, rhs, ia, idxglo, amatsln) + subroutine drn_fn(this, rhs, ia, idxglo, matrix_sln) ! ************************************************************************** ! drn_fn -- Fill newton terms ! ************************************************************************** @@ -407,7 +408,7 @@ subroutine drn_fn(this, rhs, ia, idxglo, amatsln) real(DP), dimension(:), intent(inout) :: rhs integer(I4B), dimension(:), intent(in) :: ia integer(I4B), dimension(:), intent(in) :: idxglo - real(DP), dimension(:), intent(inout) :: amatsln + class(MatrixBaseType), pointer :: matrix_sln ! -- local integer(I4B) :: i integer(I4B) :: node @@ -447,7 +448,7 @@ subroutine drn_fn(this, rhs, ia, idxglo, amatsln) ! ! -- fill amat and rhs with newton-raphson terms ipos = ia(node) - amatsln(idxglo(ipos)) = amatsln(idxglo(ipos)) + drterm + call matrix_sln%add_value_pos(idxglo(ipos), drterm) rhs(node) = rhs(node) + drterm * xnew end if end do diff --git a/src/Model/GroundWaterFlow/gwf3evt8.f90 b/src/Model/GroundWaterFlow/gwf3evt8.f90 index f7d7382225b..f8782013f3f 100644 --- a/src/Model/GroundWaterFlow/gwf3evt8.f90 +++ b/src/Model/GroundWaterFlow/gwf3evt8.f90 @@ -11,6 +11,7 @@ module EvtModule use TimeSeriesLinkModule, only: TimeSeriesLinkType, & GetTimeSeriesLinkFromList use BlockParserModule, only: BlockParserType + use MatrixModule ! implicit none ! @@ -720,7 +721,7 @@ subroutine evt_cf(this, reset_mover) return end subroutine evt_cf - subroutine evt_fc(this, rhs, ia, idxglo, amatsln) + subroutine evt_fc(this, rhs, ia, idxglo, matrix_sln) ! ************************************************************************** ! evt_fc -- Copy rhs and hcof into solution rhs and amat ! ************************************************************************** @@ -732,7 +733,7 @@ subroutine evt_fc(this, rhs, ia, idxglo, amatsln) real(DP), dimension(:), intent(inout) :: rhs integer(I4B), dimension(:), intent(in) :: ia integer(I4B), dimension(:), intent(in) :: idxglo - real(DP), dimension(:), intent(inout) :: amatsln + class(MatrixBaseType), pointer :: matrix_sln ! -- local integer(I4B) :: i, n, ipos ! -------------------------------------------------------------------------- @@ -749,7 +750,7 @@ subroutine evt_fc(this, rhs, ia, idxglo, amatsln) end if rhs(n) = rhs(n) + this%rhs(i) ipos = ia(n) - amatsln(idxglo(ipos)) = amatsln(idxglo(ipos)) + this%hcof(i) + call matrix_sln%add_value_pos(idxglo(ipos), this%hcof(i)) end do ! ! -- return diff --git a/src/Model/GroundWaterFlow/gwf3ghb8.f90 b/src/Model/GroundWaterFlow/gwf3ghb8.f90 index 9757b1a16c4..b6f1f1dd25e 100644 --- a/src/Model/GroundWaterFlow/gwf3ghb8.f90 +++ b/src/Model/GroundWaterFlow/gwf3ghb8.f90 @@ -6,6 +6,7 @@ module ghbmodule use ObsModule, only: DefaultObsIdProcessor use TimeSeriesLinkModule, only: TimeSeriesLinkType, & GetTimeSeriesLinkFromList + use MatrixModule ! implicit none ! @@ -195,7 +196,7 @@ subroutine ghb_cf(this, reset_mover) return end subroutine ghb_cf - subroutine ghb_fc(this, rhs, ia, idxglo, amatsln) + subroutine ghb_fc(this, rhs, ia, idxglo, matrix_sln) ! ************************************************************************** ! ghb_fc -- Copy rhs and hcof into solution rhs and amat ! ************************************************************************** @@ -207,7 +208,7 @@ subroutine ghb_fc(this, rhs, ia, idxglo, amatsln) real(DP), dimension(:), intent(inout) :: rhs integer(I4B), dimension(:), intent(in) :: ia integer(I4B), dimension(:), intent(in) :: idxglo - real(DP), dimension(:), intent(inout) :: amatsln + class(MatrixBaseType), pointer :: matrix_sln ! -- local integer(I4B) :: i, n, ipos real(DP) :: cond, bhead, qghb @@ -223,7 +224,7 @@ subroutine ghb_fc(this, rhs, ia, idxglo, amatsln) n = this%nodelist(i) rhs(n) = rhs(n) + this%rhs(i) ipos = ia(n) - amatsln(idxglo(ipos)) = amatsln(idxglo(ipos)) + this%hcof(i) + call matrix_sln%add_value_pos(idxglo(ipos), this%hcof(i)) ! ! -- If mover is active and this boundary is discharging, ! store available water (as positive value). diff --git a/src/Model/GroundWaterFlow/gwf3hfb8.f90 b/src/Model/GroundWaterFlow/gwf3hfb8.f90 index ffec99e84bd..c555013acfc 100644 --- a/src/Model/GroundWaterFlow/gwf3hfb8.f90 +++ b/src/Model/GroundWaterFlow/gwf3hfb8.f90 @@ -7,6 +7,7 @@ module GwfHfbModule use NumericalPackageModule, only: NumericalPackageType use BlockParserModule, only: BlockParserType use BaseDisModule, only: DisBaseType + use MatrixModule implicit none @@ -223,7 +224,7 @@ subroutine hfb_rp(this) return end subroutine hfb_rp - subroutine hfb_fc(this, kiter, njasln, amat, idxglo, rhs, hnew) + subroutine hfb_fc(this, kiter, matrix_sln, idxglo, rhs, hnew) ! ****************************************************************************** ! hfb_fc -- Fill amatsln for the following conditions: ! 1. Not Newton, and @@ -239,8 +240,7 @@ subroutine hfb_fc(this, kiter, njasln, amat, idxglo, rhs, hnew) ! -- dummy class(GwfHfbType) :: this integer(I4B) :: kiter - integer(I4B), intent(in) :: njasln - real(DP), dimension(njasln), intent(inout) :: amat + class(MatrixBaseType), pointer :: matrix_sln integer(I4B), intent(in), dimension(:) :: idxglo real(DP), intent(inout), dimension(:) :: rhs real(DP), intent(inout), dimension(:) :: hnew @@ -306,7 +306,7 @@ subroutine hfb_fc(this, kiter, njasln, amat, idxglo, rhs, hnew) condhfb = this%hydchr(ihfb) end if ! -- Make hfb corrections for xt3d - call this%xt3d%xt3d_fhfb(kiter, nodes, nja, njasln, amat, idxglo, & + call this%xt3d%xt3d_fhfb(kiter, nodes, nja, matrix_sln, idxglo, & rhs, hnew, n, m, condhfb) end do ! @@ -316,7 +316,7 @@ subroutine hfb_fc(this, kiter, njasln, amat, idxglo, rhs, hnew) if (this%inewton == 0) then do ihfb = 1, this%nhfb ipos = this%idxloc(ihfb) - aterm = amat(idxglo(ipos)) + aterm = matrix_sln%get_value_pos(idxglo(ipos)) n = this%noden(ihfb) m = this%nodem(ihfb) if (this%ibound(n) == 0 .or. this%ibound(m) == 0) cycle @@ -358,14 +358,14 @@ subroutine hfb_fc(this, kiter, njasln, amat, idxglo, rhs, hnew) ! ! -- Fill row n diag and off diag idiag = this%ia(n) - amat(idxglo(idiag)) = amat(idxglo(idiag)) + aterm - cond - amat(idxglo(ipos)) = cond + call matrix_sln%add_value_pos(idxglo(idiag), aterm - cond) + call matrix_sln%set_value_pos(idxglo(ipos), cond) ! ! -- Fill row m diag and off diag isymcon = this%isym(ipos) idiag = this%ia(m) - amat(idxglo(idiag)) = amat(idxglo(idiag)) + aterm - cond - amat(idxglo(isymcon)) = cond + call matrix_sln%add_value_pos(idxglo(idiag), aterm - cond) + call matrix_sln%set_value_pos(idxglo(isymcon), cond) ! end if end do diff --git a/src/Model/GroundWaterFlow/gwf3lak8.f90 b/src/Model/GroundWaterFlow/gwf3lak8.f90 index cfb921248fa..b46efce652b 100644 --- a/src/Model/GroundWaterFlow/gwf3lak8.f90 +++ b/src/Model/GroundWaterFlow/gwf3lak8.f90 @@ -29,6 +29,7 @@ module LakModule use BlockParserModule, only: BlockParserType use BaseDisModule, only: DisBaseType use SimVariablesModule, only: errmsg + use MatrixModule ! implicit none ! @@ -3889,7 +3890,7 @@ subroutine lak_cf(this, reset_mover) return end subroutine lak_cf - subroutine lak_fc(this, rhs, ia, idxglo, amatsln) + subroutine lak_fc(this, rhs, ia, idxglo, matrix_sln) ! ************************************************************************** ! lak_fc -- Copy rhs and hcof into solution rhs and amat ! ************************************************************************** @@ -3901,7 +3902,7 @@ subroutine lak_fc(this, rhs, ia, idxglo, amatsln) real(DP), dimension(:), intent(inout) :: rhs integer(I4B), dimension(:), intent(in) :: ia integer(I4B), dimension(:), intent(in) :: idxglo - real(DP), dimension(:), intent(inout) :: amatsln + class(MatrixBaseType), pointer :: matrix_sln ! -- local integer(I4B) :: j, n integer(I4B) :: igwfnode @@ -3922,7 +3923,7 @@ subroutine lak_fc(this, rhs, ia, idxglo, amatsln) igwfnode = this%cellid(j) if (this%ibound(igwfnode) < 1) cycle ipossymd = idxglo(ia(igwfnode)) - amatsln(ipossymd) = amatsln(ipossymd) + this%hcof(j) + call matrix_sln%add_value_pos(ipossymd, this%hcof(j)) rhs(igwfnode) = rhs(igwfnode) + this%rhs(j) end do end do @@ -3931,7 +3932,7 @@ subroutine lak_fc(this, rhs, ia, idxglo, amatsln) return end subroutine lak_fc - subroutine lak_fn(this, rhs, ia, idxglo, amatsln) + subroutine lak_fn(this, rhs, ia, idxglo, matrix_sln) ! ************************************************************************** ! lak_fn -- Fill newton terms ! ************************************************************************** @@ -3943,7 +3944,7 @@ subroutine lak_fn(this, rhs, ia, idxglo, amatsln) real(DP), dimension(:), intent(inout) :: rhs integer(I4B), dimension(:), intent(in) :: ia integer(I4B), dimension(:), intent(in) :: idxglo - real(DP), dimension(:), intent(inout) :: amatsln + class(MatrixBaseType), pointer :: matrix_sln ! -- local integer(I4B) :: j, n integer(I4B) :: ipos @@ -3986,7 +3987,7 @@ subroutine lak_fn(this, rhs, ia, idxglo, amatsln) drterm = (q1 - q) / this%delh ! -- add terms to convert conductance formulation into ! newton-raphson formulation - amatsln(idxglo(ipos)) = amatsln(idxglo(ipos)) + drterm - this%hcof(j) + call matrix_sln%add_value_pos(idxglo(ipos), drterm - this%hcof(j)) rhs(igwfnode) = rhs(igwfnode) - rterm + drterm * head end if end if diff --git a/src/Model/GroundWaterFlow/gwf3maw8.f90 b/src/Model/GroundWaterFlow/gwf3maw8.f90 index 295b84d8472..7d9cae2bcf3 100644 --- a/src/Model/GroundWaterFlow/gwf3maw8.f90 +++ b/src/Model/GroundWaterFlow/gwf3maw8.f90 @@ -28,6 +28,7 @@ module MawModule use MemoryManagerModule, only: mem_allocate, mem_reallocate, mem_setptr, & mem_deallocate use MemoryHelperModule, only: create_mem_path + use MatrixModule ! implicit none @@ -1681,7 +1682,7 @@ subroutine maw_ac(this, moffset, sparse) return end subroutine maw_ac - subroutine maw_mc(this, moffset, iasln, jasln) + subroutine maw_mc(this, moffset, matrix_sln) ! ****************************************************************************** ! bnd_ac -- map package connection to matrix ! ****************************************************************************** @@ -1693,13 +1694,11 @@ subroutine maw_mc(this, moffset, iasln, jasln) ! -- dummy class(MawType), intent(inout) :: this integer(I4B), intent(in) :: moffset - integer(I4B), dimension(:), intent(in) :: iasln - integer(I4B), dimension(:), intent(in) :: jasln + class(MatrixBaseType), pointer :: matrix_sln ! -- local integer(I4B) :: n integer(I4B) :: j integer(I4B) :: ii - integer(I4B) :: jj integer(I4B) :: iglo integer(I4B) :: jglo integer(I4B) :: ipos @@ -1728,13 +1727,8 @@ subroutine maw_mc(this, moffset, iasln, jasln) do ii = 1, this%ngwfnodes(n) j = this%get_gwfnode(n, ii) jglo = j + moffset - searchloop: do jj = iasln(iglo), iasln(iglo + 1) - 1 - if (jglo == jasln(jj)) then - this%idxdglo(ipos) = iasln(iglo) - this%idxoffdglo(ipos) = jj - exit searchloop - end if - end do searchloop + this%idxdglo(ipos) = matrix_sln%get_position_diag(iglo) + this%idxoffdglo(ipos) = matrix_sln%get_position(iglo, jglo) ipos = ipos + 1 end do end do @@ -1744,13 +1738,8 @@ subroutine maw_mc(this, moffset, iasln, jasln) do ii = 1, this%ngwfnodes(n) iglo = this%get_gwfnode(n, ii) + moffset jglo = moffset + this%dis%nodes + this%ioffset + n - symsearchloop: do jj = iasln(iglo), iasln(iglo + 1) - 1 - if (jglo == jasln(jj)) then - this%idxsymdglo(ipos) = iasln(iglo) - this%idxsymoffdglo(ipos) = jj - exit symsearchloop - end if - end do symsearchloop + this%idxsymdglo(ipos) = matrix_sln%get_position_diag(iglo) + this%idxsymoffdglo(ipos) = matrix_sln%get_position(iglo, jglo) ipos = ipos + 1 end do end do @@ -2317,7 +2306,7 @@ subroutine maw_cf(this, reset_mover) return end subroutine maw_cf - subroutine maw_fc(this, rhs, ia, idxglo, amatsln) + subroutine maw_fc(this, rhs, ia, idxglo, matrix_sln) ! ****************************************************************************** ! maw_fc -- Copy rhs and hcof into solution rhs and amat ! ****************************************************************************** @@ -2331,7 +2320,7 @@ subroutine maw_fc(this, rhs, ia, idxglo, amatsln) real(DP), dimension(:), intent(inout) :: rhs integer(I4B), dimension(:), intent(in) :: ia integer(I4B), dimension(:), intent(in) :: idxglo - real(DP), dimension(:), intent(inout) :: amatsln + class(MatrixBaseType), pointer :: matrix_sln ! -- local integer(I4B) :: j integer(I4B) :: n @@ -2402,7 +2391,7 @@ subroutine maw_fc(this, rhs, ia, idxglo, amatsln) this%xsto(n) = bt end if this%fwcondsim(n) = cfw - amatsln(iposd) = amatsln(iposd) - cfw + call matrix_sln%add_value_pos(iposd, -cfw) rhs(iloc) = rhs(iloc) - cfw * bt ratefw = cfw * (bt - hmaw) end if @@ -2411,7 +2400,7 @@ subroutine maw_fc(this, rhs, ia, idxglo, amatsln) ! -- add maw storage changes if (this%imawiss /= 1) then if (this%ifwdischarge(n) /= 1) then - amatsln(iposd) = amatsln(iposd) - (this%area(n) / delt) + call matrix_sln%add_value_pos(iposd, -this%area(n) / delt) rhs(iloc) = rhs(iloc) - (this%area(n) * this%xoldsto(n) / delt) else cterm = this%xoldsto(n) - this%fwelev(n) @@ -2450,8 +2439,8 @@ subroutine maw_fc(this, rhs, ia, idxglo, amatsln) ! -- add to maw row iposd = this%idxdglo(idx) iposoffd = this%idxoffdglo(idx) - amatsln(iposd) = amatsln(iposd) - term - amatsln(iposoffd) = term + call matrix_sln%add_value_pos(iposd, -term) + call matrix_sln%set_value_pos(iposoffd, term) ! ! -- add correction term rhs(iloc) = rhs(iloc) - cterm @@ -2461,8 +2450,8 @@ subroutine maw_fc(this, rhs, ia, idxglo, amatsln) isymloc = ia(isymnode) ipossymd = this%idxsymdglo(idx) ipossymoffd = this%idxsymoffdglo(idx) - amatsln(ipossymd) = amatsln(ipossymd) - term - amatsln(ipossymoffd) = term + call matrix_sln%add_value_pos(ipossymd, -term) + call matrix_sln%set_value_pos(ipossymoffd, term) ! ! -- add correction term to gwf row rhs(isymnode) = rhs(isymnode) + cterm @@ -2477,7 +2466,7 @@ subroutine maw_fc(this, rhs, ia, idxglo, amatsln) return end subroutine maw_fc - subroutine maw_fn(this, rhs, ia, idxglo, amatsln) + subroutine maw_fn(this, rhs, ia, idxglo, matrix_sln) ! ************************************************************************** ! maw_fn -- Fill newton terms ! ************************************************************************** @@ -2490,7 +2479,7 @@ subroutine maw_fn(this, rhs, ia, idxglo, amatsln) real(DP), dimension(:), intent(inout) :: rhs integer(I4B), dimension(:), intent(in) :: ia integer(I4B), dimension(:), intent(in) :: idxglo - real(DP), dimension(:), intent(inout) :: amatsln + class(MatrixBaseType), pointer :: matrix_sln ! -- local integer(I4B) :: j integer(I4B) :: n @@ -2542,7 +2531,7 @@ subroutine maw_fn(this, rhs, ia, idxglo, amatsln) drterm = (rate2 - rate) / DEM4 ! !-- fill amat and rhs with newton-raphson terms - amatsln(iposd) = amatsln(iposd) + drterm + call matrix_sln%add_value_pos(iposd, drterm) rhs(iloc) = rhs(iloc) + drterm * hmaw ! ! -- add flowing well @@ -2566,8 +2555,8 @@ subroutine maw_fn(this, rhs, ia, idxglo, amatsln) drterm = -(cfw + this%fwcond(n) * derv * (hmaw - bt)) ! ! -- fill amat and rhs with newton-raphson terms - amatsln(iposd) = amatsln(iposd) - & - this%fwcond(n) * derv * (hmaw - bt) + call matrix_sln%add_value_pos(iposd, & + -this%fwcond(n) * derv * (hmaw - bt)) rhs(iloc) = rhs(iloc) - rterm + drterm * hmaw end if end if @@ -2602,17 +2591,17 @@ subroutine maw_fn(this, rhs, ia, idxglo, amatsln) rhs(iloc) = rhs(iloc) + rhsterm rhs(isymnode) = rhs(isymnode) - rhsterm if (this%iboundpak(n) > 0) then - amatsln(iposd) = amatsln(iposd) + term - amatsln(iposoffd) = amatsln(iposoffd) + term2 + call matrix_sln%add_value_pos(iposd, term) + call matrix_sln%add_value_pos(iposoffd, term2) end if - amatsln(ipossymd) = amatsln(ipossymd) - term2 - amatsln(ipossymoffd) = amatsln(ipossymoffd) - term + call matrix_sln%add_value_pos(ipossymd, -term2) + call matrix_sln%add_value_pos(ipossymoffd, -term) else rhs(iloc) = rhs(iloc) + term * hmaw rhs(isymnode) = rhs(isymnode) - term * hmaw - amatsln(iposd) = amatsln(iposd) + term + call matrix_sln%add_value_pos(iposd, term) if (this%ibound(igwfnode) > 0) then - amatsln(ipossymoffd) = amatsln(ipossymoffd) - term + call matrix_sln%add_value_pos(ipossymoffd, -term) end if end if ! @@ -2623,18 +2612,18 @@ subroutine maw_fn(this, rhs, ia, idxglo, amatsln) rhs(iloc) = rhs(iloc) + rhsterm rhs(isymnode) = rhs(isymnode) - rhsterm if (this%iboundpak(n) > 0) then - amatsln(iposd) = amatsln(iposd) + term2 - amatsln(iposoffd) = amatsln(iposoffd) + term + call matrix_sln%add_value_pos(iposd, term2) + call matrix_sln%add_value_pos(iposoffd, term) end if - amatsln(ipossymd) = amatsln(ipossymd) - term - amatsln(ipossymoffd) = amatsln(ipossymoffd) - term2 + call matrix_sln%add_value_pos(ipossymd, -term) + call matrix_sln%add_value_pos(ipossymoffd, -term2) else rhs(iloc) = rhs(iloc) + term * hgwf rhs(isymnode) = rhs(isymnode) - term * hgwf if (this%iboundpak(n) > 0) then - amatsln(iposoffd) = amatsln(iposoffd) + term + call matrix_sln%add_value_pos(iposoffd, term) end if - amatsln(ipossymd) = amatsln(ipossymd) - term + call matrix_sln%add_value_pos(ipossymd, -term) end if end if end if diff --git a/src/Model/GroundWaterFlow/gwf3npf8.f90 b/src/Model/GroundWaterFlow/gwf3npf8.f90 index 6a3b8aa8483..93e68055722 100644 --- a/src/Model/GroundWaterFlow/gwf3npf8.f90 +++ b/src/Model/GroundWaterFlow/gwf3npf8.f90 @@ -19,6 +19,7 @@ module GwfNpfModule use MemoryManagerModule, only: mem_allocate, mem_reallocate, & mem_deallocate, mem_setptr, & mem_reassignptr + use MatrixModule implicit none @@ -294,7 +295,7 @@ subroutine npf_ac(this, moffset, sparse) return end subroutine npf_ac - subroutine npf_mc(this, moffset, iasln, jasln) + subroutine npf_mc(this, moffset, matrix_sln) ! ****************************************************************************** ! npf_mc -- Map connections and construct iax, jax, and idxglox ! ****************************************************************************** @@ -305,12 +306,11 @@ subroutine npf_mc(this, moffset, iasln, jasln) ! -- dummy class(GwfNpftype) :: this integer(I4B), intent(in) :: moffset - integer(I4B), dimension(:), intent(in) :: iasln - integer(I4B), dimension(:), intent(in) :: jasln + class(MatrixBaseType), pointer :: matrix_sln ! -- local ! ------------------------------------------------------------------------------ ! - if (this%ixt3d /= 0) call this%xt3d%xt3d_mc(moffset, iasln, jasln) + if (this%ixt3d /= 0) call this%xt3d%xt3d_mc(moffset, matrix_sln) ! ! -- Return return @@ -526,7 +526,7 @@ subroutine npf_cf(this, kiter, nodes, hnew) return end subroutine npf_cf - subroutine npf_fc(this, kiter, njasln, amat, idxglo, rhs, hnew) + subroutine npf_fc(this, kiter, matrix_sln, idxglo, rhs, hnew) ! ****************************************************************************** ! npf_fc -- Formulate ! ****************************************************************************** @@ -538,8 +538,7 @@ subroutine npf_fc(this, kiter, njasln, amat, idxglo, rhs, hnew) ! -- dummy class(GwfNpfType) :: this integer(I4B) :: kiter - integer(I4B), intent(in) :: njasln - real(DP), dimension(njasln), intent(inout) :: amat + class(MatrixBaseType), pointer :: matrix_sln integer(I4B), intent(in), dimension(:) :: idxglo real(DP), intent(inout), dimension(:) :: rhs real(DP), intent(inout), dimension(:) :: hnew @@ -553,7 +552,7 @@ subroutine npf_fc(this, kiter, njasln, amat, idxglo, rhs, hnew) ! -- Calculate conductance and put into amat ! if (this%ixt3d /= 0) then - call this%xt3d%xt3d_fc(kiter, njasln, amat, idxglo, rhs, hnew) + call this%xt3d%xt3d_fc(kiter, matrix_sln, idxglo, rhs, hnew) else ! do n = 1, this%dis%nodes @@ -591,11 +590,11 @@ subroutine npf_fc(this, kiter, njasln, amat, idxglo, rhs, hnew) ! -- Fill row n idiag = this%dis%con%ia(n) rhs(n) = rhs(n) - cond * this%dis%bot(n) - amat(idxglo(idiag)) = amat(idxglo(idiag)) - cond + call matrix_sln%add_value_pos(idxglo(idiag), -cond) ! ! -- Fill row m isymcon = this%dis%con%isym(ii) - amat(idxglo(isymcon)) = amat(idxglo(isymcon)) + cond + call matrix_sln%add_value_pos(idxglo(isymcon), cond) rhs(m) = rhs(m) + cond * this%dis%bot(n) ! ! -- cycle the connection loop @@ -624,14 +623,14 @@ subroutine npf_fc(this, kiter, njasln, amat, idxglo, rhs, hnew) ! ! -- Fill row n idiag = this%dis%con%ia(n) - amat(idxglo(ii)) = amat(idxglo(ii)) + cond - amat(idxglo(idiag)) = amat(idxglo(idiag)) - cond + call matrix_sln%add_value_pos(idxglo(ii), cond) + call matrix_sln%add_value_pos(idxglo(idiag), -cond) ! ! -- Fill row m isymcon = this%dis%con%isym(ii) idiagm = this%dis%con%ia(m) - amat(idxglo(isymcon)) = amat(idxglo(isymcon)) + cond - amat(idxglo(idiagm)) = amat(idxglo(idiagm)) - cond + call matrix_sln%add_value_pos(idxglo(isymcon), cond) + call matrix_sln%add_value_pos(idxglo(idiagm), -cond) end do end do ! @@ -641,7 +640,7 @@ subroutine npf_fc(this, kiter, njasln, amat, idxglo, rhs, hnew) return end subroutine npf_fc - subroutine npf_fn(this, kiter, njasln, amat, idxglo, rhs, hnew) + subroutine npf_fn(this, kiter, matrix_sln, idxglo, rhs, hnew) ! ****************************************************************************** ! npf_fn -- Fill newton terms ! ****************************************************************************** @@ -651,8 +650,7 @@ subroutine npf_fn(this, kiter, njasln, amat, idxglo, rhs, hnew) ! -- dummy class(GwfNpfType) :: this integer(I4B) :: kiter - integer(I4B), intent(in) :: njasln - real(DP), dimension(njasln), intent(inout) :: amat + class(MatrixBaseType), pointer :: matrix_sln integer(I4B), intent(in), dimension(:) :: idxglo real(DP), intent(inout), dimension(:) :: rhs real(DP), intent(inout), dimension(:) :: hnew @@ -680,7 +678,7 @@ subroutine npf_fn(this, kiter, njasln, amat, idxglo, rhs, hnew) nodes = this%dis%nodes nja = this%dis%con%nja if (this%ixt3d /= 0) then - call this%xt3d%xt3d_fn(kiter, nodes, nja, njasln, amat, idxglo, rhs, hnew) + call this%xt3d%xt3d_fn(kiter, nodes, nja, matrix_sln, idxglo, rhs, hnew) else ! do n = 1, nodes @@ -730,7 +728,7 @@ subroutine npf_fn(this, kiter, njasln, amat, idxglo, rhs, hnew) ! compute additional term consterm = -cond * (hnew(iups) - hnew(idn)) !needs to use hwadi instead of hnew(idn) !filledterm = cond - filledterm = amat(idxglo(ii)) + filledterm = matrix_sln%get_value_pos(idxglo(ii)) derv = sQuadraticSaturationDerivative(topup, botup, hnew(iups), & this%satomega, this%satmin) idiagm = this%dis%con%ia(m) @@ -742,16 +740,18 @@ subroutine npf_fn(this, kiter, njasln, amat, idxglo, rhs, hnew) rhs(n) = rhs(n) + term * hnew(n) !+ amat(idxglo(isymcon)) * (dwadi * hds - hds) !need to add dwadi rhs(m) = rhs(m) - term * hnew(n) !- amat(idxglo(isymcon)) * (dwadi * hds - hds) !need to add dwadi ! fill in row of n - amat(idxglo(idiag)) = amat(idxglo(idiag)) + term + call matrix_sln%add_value_pos(idxglo(idiag), term) ! fill newton term in off diagonal if active cell if (this%ibound(n) > 0) then - amat(idxglo(ii)) = amat(idxglo(ii)) !* dwadi !need to add dwadi + filledterm = matrix_sln%get_value_pos(idxglo(ii)) + call matrix_sln%set_value_pos(idxglo(ii), filledterm) !* dwadi !need to add dwadi end if !fill row of m - amat(idxglo(idiagm)) = amat(idxglo(idiagm)) !- filledterm * (dwadi - DONE) !need to add dwadi + filledterm = matrix_sln%get_value_pos(idxglo(idiagm)) + call matrix_sln%set_value_pos(idxglo(idiagm), filledterm) !- filledterm * (dwadi - DONE) !need to add dwadi ! fill newton term in off diagonal if active cell if (this%ibound(m) > 0) then - amat(idxglo(isymcon)) = amat(idxglo(isymcon)) - term + call matrix_sln%add_value_pos(idxglo(isymcon), -term) end if ! fill jacobian for m being the upstream node else @@ -760,16 +760,18 @@ subroutine npf_fn(this, kiter, njasln, amat, idxglo, rhs, hnew) rhs(n) = rhs(n) + term * hnew(m) !+ amat(idxglo(ii)) * (dwadi * hds - hds) !need to add dwadi rhs(m) = rhs(m) - term * hnew(m) !- amat(idxglo(ii)) * (dwadi * hds - hds) !need to add dwadi ! fill in row of n - amat(idxglo(idiag)) = amat(idxglo(idiag)) !- filledterm * (dwadi - DONE) !need to add dwadi + filledterm = matrix_sln%get_value_pos(idxglo(idiag)) + call matrix_sln%set_value_pos(idxglo(idiag), filledterm) !- filledterm * (dwadi - DONE) !need to add dwadi ! fill newton term in off diagonal if active cell if (this%ibound(n) > 0) then - amat(idxglo(ii)) = amat(idxglo(ii)) + term + call matrix_sln%add_value_pos(idxglo(ii), term) end if !fill row of m - amat(idxglo(idiagm)) = amat(idxglo(idiagm)) - term + call matrix_sln%add_value_pos(idxglo(idiagm), -term) ! fill newton term in off diagonal if active cell if (this%ibound(m) > 0) then - amat(idxglo(isymcon)) = amat(idxglo(isymcon)) !* dwadi !need to add dwadi + filledterm = matrix_sln%get_value_pos(idxglo(isymcon)) + call matrix_sln%set_value_pos(idxglo(isymcon), filledterm) !* dwadi !need to add dwadi end if end if end if diff --git a/src/Model/GroundWaterFlow/gwf3rch8.f90 b/src/Model/GroundWaterFlow/gwf3rch8.f90 index e91e9594cfd..5d3596486b5 100644 --- a/src/Model/GroundWaterFlow/gwf3rch8.f90 +++ b/src/Model/GroundWaterFlow/gwf3rch8.f90 @@ -11,6 +11,7 @@ module RchModule use TimeSeriesLinkModule, only: TimeSeriesLinkType, & GetTimeSeriesLinkFromList use BlockParserModule, only: BlockParserType + use MatrixModule ! implicit none ! @@ -701,7 +702,7 @@ subroutine rch_cf(this, reset_mover) return end subroutine rch_cf - subroutine rch_fc(this, rhs, ia, idxglo, amatsln) + subroutine rch_fc(this, rhs, ia, idxglo, matrix_sln) ! ************************************************************************** ! rch_fc -- Copy rhs and hcof into solution rhs and amat ! ************************************************************************** @@ -713,7 +714,7 @@ subroutine rch_fc(this, rhs, ia, idxglo, amatsln) real(DP), dimension(:), intent(inout) :: rhs integer(I4B), dimension(:), intent(in) :: ia integer(I4B), dimension(:), intent(in) :: idxglo - real(DP), dimension(:), intent(inout) :: amatsln + class(MatrixBaseType), pointer :: matrix_sln ! -- local integer(I4B) :: i, n, ipos ! -------------------------------------------------------------------------- @@ -730,7 +731,7 @@ subroutine rch_fc(this, rhs, ia, idxglo, amatsln) end if rhs(n) = rhs(n) + this%rhs(i) ipos = ia(n) - amatsln(idxglo(ipos)) = amatsln(idxglo(ipos)) + this%hcof(i) + call matrix_sln%add_value_pos(idxglo(ipos), this%hcof(i)) end do ! ! -- return diff --git a/src/Model/GroundWaterFlow/gwf3riv8.f90 b/src/Model/GroundWaterFlow/gwf3riv8.f90 index f14e93fa2f7..a64871045d6 100644 --- a/src/Model/GroundWaterFlow/gwf3riv8.f90 +++ b/src/Model/GroundWaterFlow/gwf3riv8.f90 @@ -6,6 +6,7 @@ module rivmodule use ObsModule, only: DefaultObsIdProcessor use TimeSeriesLinkModule, only: TimeSeriesLinkType, & GetTimeSeriesLinkFromList + use MatrixModule ! implicit none ! @@ -224,7 +225,7 @@ subroutine riv_cf(this, reset_mover) return end subroutine riv_cf - subroutine riv_fc(this, rhs, ia, idxglo, amatsln) + subroutine riv_fc(this, rhs, ia, idxglo, matrix_sln) ! ************************************************************************** ! riv_fc -- Copy rhs and hcof into solution rhs and amat ! ************************************************************************** @@ -236,7 +237,7 @@ subroutine riv_fc(this, rhs, ia, idxglo, amatsln) real(DP), dimension(:), intent(inout) :: rhs integer(I4B), dimension(:), intent(in) :: ia integer(I4B), dimension(:), intent(in) :: idxglo - real(DP), dimension(:), intent(inout) :: amatsln + class(MatrixBaseType), pointer :: matrix_sln ! -- local integer(I4B) :: i, n, ipos real(DP) :: cond, stage, qriv !, rbot @@ -252,7 +253,7 @@ subroutine riv_fc(this, rhs, ia, idxglo, amatsln) n = this%nodelist(i) rhs(n) = rhs(n) + this%rhs(i) ipos = ia(n) - amatsln(idxglo(ipos)) = amatsln(idxglo(ipos)) + this%hcof(i) + call matrix_sln%add_value_pos(idxglo(ipos), this%hcof(i)) ! ! -- If mover is active and this river cell is discharging, ! store available water (as positive value). diff --git a/src/Model/GroundWaterFlow/gwf3sfr8.f90 b/src/Model/GroundWaterFlow/gwf3sfr8.f90 index c470fb7c042..acd52c69f23 100644 --- a/src/Model/GroundWaterFlow/gwf3sfr8.f90 +++ b/src/Model/GroundWaterFlow/gwf3sfr8.f90 @@ -37,6 +37,7 @@ module SfrModule get_cross_section_area, & get_mannings_section use dag_module, only: dag + use MatrixModule ! implicit none ! @@ -1958,13 +1959,13 @@ end subroutine sfr_cf !! coefficient matrix and right-hand side vector. !! !< - subroutine sfr_fc(this, rhs, ia, idxglo, amatsln) + subroutine sfr_fc(this, rhs, ia, idxglo, matrix_sln) ! -- dummy variables class(SfrType) :: this !< SfrType object real(DP), dimension(:), intent(inout) :: rhs !< right-hand side vector for model integer(I4B), dimension(:), intent(in) :: ia !< solution CRS row pointers integer(I4B), dimension(:), intent(in) :: idxglo !< mapping vector for model (local) to solution (global) - real(DP), dimension(:), intent(inout) :: amatsln !< solution coefficient matrix + class(MatrixBaseType), pointer :: matrix_sln !< solution coefficient matrix ! -- local variables integer(I4B) :: i integer(I4B) :: j @@ -2049,7 +2050,7 @@ subroutine sfr_fc(this, rhs, ia, idxglo, amatsln) if (node < 1) cycle rhs(node) = rhs(node) + this%rhs(n) ipos = ia(node) - amatsln(idxglo(ipos)) = amatsln(idxglo(ipos)) + this%hcof(n) + call matrix_sln%add_value_pos(idxglo(ipos), this%hcof(n)) end do ! ! -- return @@ -2062,13 +2063,13 @@ end subroutine sfr_fc !! coefficient matrix and right-hand side vector. !! !< - subroutine sfr_fn(this, rhs, ia, idxglo, amatsln) + subroutine sfr_fn(this, rhs, ia, idxglo, matrix_sln) ! -- dummy variables class(SfrType) :: this !< SfrType object real(DP), dimension(:), intent(inout) :: rhs !< right-hand side vector for model integer(I4B), dimension(:), intent(in) :: ia !< solution CRS row pointers integer(I4B), dimension(:), intent(in) :: idxglo !< mapping vector for model (local) to solution (global) - real(DP), dimension(:), intent(inout) :: amatsln !< solution coefficient matrix + class(MatrixBaseType), pointer :: matrix_sln !< solution coefficient matrix ! -- local variables integer(I4B) :: i integer(I4B) :: j @@ -2102,7 +2103,7 @@ subroutine sfr_fn(this, rhs, ia, idxglo, amatsln) drterm = (q2 - q1) / DEM4 ! -- add terms to convert conductance formulation into ! newton-raphson formulation - amatsln(idxglo(ipos)) = amatsln(idxglo(ipos)) + drterm - this%hcof(i) + call matrix_sln%add_value_pos(idxglo(ipos), drterm - this%hcof(i)) rhs(n) = rhs(n) - rterm + drterm * this%xnew(n) end do ! diff --git a/src/Model/GroundWaterFlow/gwf3sto8.f90 b/src/Model/GroundWaterFlow/gwf3sto8.f90 index af6af41b322..afaa7edf693 100644 --- a/src/Model/GroundWaterFlow/gwf3sto8.f90 +++ b/src/Model/GroundWaterFlow/gwf3sto8.f90 @@ -21,6 +21,7 @@ module GwfStoModule use GwfStorageUtilsModule, only: SsCapacity, SyCapacity, SsTerms, SyTerms use InputOutputModule, only: GetUnit, openfile use TvsModule, only: TvsType, tvs_cr + use MatrixModule implicit none public :: GwfStoType, sto_cr @@ -277,7 +278,7 @@ end subroutine sto_ad !! Fill the coefficient matrix and right-hand side with the STO package terms. !! !< - subroutine sto_fc(this, kiter, hold, hnew, njasln, amat, idxglo, rhs) + subroutine sto_fc(this, kiter, hold, hnew, matrix_sln, idxglo, rhs) ! -- modules use TdisModule, only: delt ! -- dummy variables @@ -285,8 +286,7 @@ subroutine sto_fc(this, kiter, hold, hnew, njasln, amat, idxglo, rhs) integer(I4B), intent(in) :: kiter !< outer iteration number real(DP), intent(in), dimension(:) :: hold !< previous heads real(DP), intent(in), dimension(:) :: hnew !< current heads - integer(I4B), intent(in) :: njasln !< size of the A matrix for the solution - real(DP), dimension(njasln), intent(inout) :: amat !< A matrix + class(MatrixBaseType), pointer :: matrix_sln !< A matrix integer(I4B), intent(in), dimension(:) :: idxglo !< global index model to solution real(DP), intent(inout), dimension(:) :: rhs !< right-hand side ! -- local variables @@ -365,7 +365,7 @@ subroutine sto_fc(this, kiter, hold, hnew, njasln, amat, idxglo, rhs) aterm, rhsterm) ! ! -- add specific storage terms to amat and rhs - amat(idxglo(idiag)) = amat(idxglo(idiag)) + aterm + call matrix_sln%add_value_pos(idxglo(idiag), aterm) rhs(n) = rhs(n) + rhsterm ! ! -- specific yield @@ -393,7 +393,7 @@ subroutine sto_fc(this, kiter, hold, hnew, njasln, amat, idxglo, rhs) aterm, rhsterm) ! ! -- add specific yield terms to amat and rhs - amat(idxglo(idiag)) = amat(idxglo(idiag)) + aterm + call matrix_sln%add_value_pos(idxglo(idiag), aterm) rhs(n) = rhs(n) + rhsterm end if end do @@ -408,7 +408,7 @@ end subroutine sto_fc !! with Newton-Raphson terms. !! !< - subroutine sto_fn(this, kiter, hold, hnew, njasln, amat, idxglo, rhs) + subroutine sto_fn(this, kiter, hold, hnew, matrix_sln, idxglo, rhs) ! -- modules use TdisModule, only: delt ! -- dummy variables @@ -416,8 +416,7 @@ subroutine sto_fn(this, kiter, hold, hnew, njasln, amat, idxglo, rhs) integer(I4B), intent(in) :: kiter !< outer iteration number real(DP), intent(in), dimension(:) :: hold !< previous heads real(DP), intent(in), dimension(:) :: hnew !< current heads - integer(I4B), intent(in) :: njasln !< size of the A matrix for the solution - real(DP), dimension(njasln), intent(inout) :: amat !< A matrix + class(MatrixBaseType), pointer :: matrix_sln !< A matrix integer(I4B), intent(in), dimension(:) :: idxglo !< global index model to solution real(DP), intent(inout), dimension(:) :: rhs !< right-hand side ! -- local variables @@ -477,7 +476,7 @@ subroutine sto_fn(this, kiter, hold, hnew, njasln, amat, idxglo, rhs) else drterm = -(rho1 * derv * h) end if - amat(idxglo(idiag)) = amat(idxglo(idiag)) + drterm + call matrix_sln%add_value_pos(idxglo(idiag), drterm) rhs(n) = rhs(n) + drterm * h end if ! @@ -489,7 +488,7 @@ subroutine sto_fn(this, kiter, hold, hnew, njasln, amat, idxglo, rhs) if (snnew > DZERO) then rterm = -rho2 * tthk * snnew drterm = -rho2 * tthk * derv - amat(idxglo(idiag)) = amat(idxglo(idiag)) + drterm + rho2 + call matrix_sln%add_value_pos(idxglo(idiag), drterm + rho2) rhs(n) = rhs(n) - rterm + drterm * h + rho2 * bt end if end if diff --git a/src/Model/GroundWaterFlow/gwf3uzf8.f90 b/src/Model/GroundWaterFlow/gwf3uzf8.f90 index af00f7296db..d3bf9d9022d 100644 --- a/src/Model/GroundWaterFlow/gwf3uzf8.f90 +++ b/src/Model/GroundWaterFlow/gwf3uzf8.f90 @@ -26,6 +26,7 @@ module UzfModule use SimModule, only: count_errors, store_error, store_error_unit use BlockParserModule, only: BlockParserType use TableModule, only: TableType, table_cr + use MatrixModule implicit none @@ -1104,7 +1105,7 @@ subroutine uzf_cf(this, reset_mover) return end subroutine uzf_cf - subroutine uzf_fc(this, rhs, ia, idxglo, amatsln) + subroutine uzf_fc(this, rhs, ia, idxglo, matrix_sln) ! ****************************************************************************** ! uzf_fc -- Copy rhs and hcof into solution rhs and amat ! ****************************************************************************** @@ -1116,7 +1117,7 @@ subroutine uzf_fc(this, rhs, ia, idxglo, amatsln) real(DP), dimension(:), intent(inout) :: rhs integer(I4B), dimension(:), intent(in) :: ia integer(I4B), dimension(:), intent(in) :: idxglo - real(DP), dimension(:), intent(inout) :: amatsln + class(MatrixBaseType), pointer :: matrix_sln ! -- local integer(I4B) :: i, n, ipos ! ------------------------------------------------------------------------------ @@ -1135,14 +1136,14 @@ subroutine uzf_fc(this, rhs, ia, idxglo, amatsln) n = this%nodelist(i) rhs(n) = rhs(n) + this%rhs(i) ipos = ia(n) - amatsln(idxglo(ipos)) = amatsln(idxglo(ipos)) + this%hcof(i) + call matrix_sln%add_value_pos(idxglo(ipos), this%hcof(i)) end do ! ! -- return return end subroutine uzf_fc ! - subroutine uzf_fn(this, rhs, ia, idxglo, amatsln) + subroutine uzf_fn(this, rhs, ia, idxglo, matrix_sln) ! ************************************************************************** ! uzf_fn -- Fill newton terms ! ************************************************************************** @@ -1154,7 +1155,7 @@ subroutine uzf_fn(this, rhs, ia, idxglo, amatsln) real(DP), dimension(:), intent(inout) :: rhs integer(I4B), dimension(:), intent(in) :: ia integer(I4B), dimension(:), intent(in) :: idxglo - real(DP), dimension(:), intent(inout) :: amatsln + class(MatrixBaseType), pointer :: matrix_sln ! -- local integer(I4B) :: i, n integer(I4B) :: ipos @@ -1164,7 +1165,7 @@ subroutine uzf_fn(this, rhs, ia, idxglo, amatsln) do i = 1, this%nodes n = this%nodelist(i) ipos = ia(n) - amatsln(idxglo(ipos)) = amatsln(idxglo(ipos)) + this%deriv(i) + call matrix_sln%add_value_pos(idxglo(ipos), this%deriv(i)) rhs(n) = rhs(n) + this%deriv(i) * this%xnew(n) end do ! diff --git a/src/Model/GroundWaterFlow/gwf3wel8.f90 b/src/Model/GroundWaterFlow/gwf3wel8.f90 index 008580a6475..1f8eca10677 100644 --- a/src/Model/GroundWaterFlow/gwf3wel8.f90 +++ b/src/Model/GroundWaterFlow/gwf3wel8.f90 @@ -27,6 +27,7 @@ module WelModule GetTimeSeriesLinkFromList use BlockParserModule, only: BlockParserType use InputOutputModule, only: GetUnit, openfile + use MatrixModule ! implicit none ! @@ -279,13 +280,13 @@ end subroutine wel_cf !! coefficient matrix and right-hand side vector. !! !< - subroutine wel_fc(this, rhs, ia, idxglo, amatsln) + subroutine wel_fc(this, rhs, ia, idxglo, matrix_sln) ! -- dummy variables class(WelType) :: this !< WelType object real(DP), dimension(:), intent(inout) :: rhs !< right-hand side vector for model integer(I4B), dimension(:), intent(in) :: ia !< solution CRS row pointers integer(I4B), dimension(:), intent(in) :: idxglo !< mapping vector for model (local) to solution (global) - real(DP), dimension(:), intent(inout) :: amatsln !< solution coefficient matrix + class(MatrixBaseType), pointer :: matrix_sln !< solution coefficient matrix ! -- local variables integer(I4B) :: i integer(I4B) :: n @@ -301,7 +302,7 @@ subroutine wel_fc(this, rhs, ia, idxglo, amatsln) n = this%nodelist(i) rhs(n) = rhs(n) + this%rhs(i) ipos = ia(n) - amatsln(idxglo(ipos)) = amatsln(idxglo(ipos)) + this%hcof(i) + call matrix_sln%add_value_pos(idxglo(ipos), this%hcof(i)) ! ! -- If mover is active and this well is discharging, ! store available water (as positive value). @@ -320,13 +321,13 @@ end subroutine wel_fc !! coefficient matrix and right-hand side vector. !! !< - subroutine wel_fn(this, rhs, ia, idxglo, amatsln) + subroutine wel_fn(this, rhs, ia, idxglo, matrix_sln) ! -- dummy variables class(WelType) :: this !< WelType object real(DP), dimension(:), intent(inout) :: rhs !< right-hand side vector for model integer(I4B), dimension(:), intent(in) :: ia !< solution CRS row pointers integer(I4B), dimension(:), intent(in) :: idxglo !< mapping vector for model (local) to solution (global) - real(DP), dimension(:), intent(inout) :: amatsln !< solution coefficient matrix + class(MatrixBaseType), pointer :: matrix_sln !< solution coefficient matrix ! -- local variables integer(I4B) :: i integer(I4B) :: node @@ -361,7 +362,7 @@ subroutine wel_fn(this, rhs, ia, idxglo, amatsln) drterm = sQSaturationDerivative(tp, bt, this%xnew(node)) drterm = drterm * this%bound(1, i) !--fill amat and rhs with newton-raphson terms - amatsln(idxglo(ipos)) = amatsln(idxglo(ipos)) + drterm + call matrix_sln%add_value_pos(idxglo(ipos), drterm) rhs(node) = rhs(node) + drterm * this%xnew(node) end if end if diff --git a/src/Model/GroundWaterTransport/gwt1.f90 b/src/Model/GroundWaterTransport/gwt1.f90 index 9b6c1bf91e7..32d94f25f0f 100644 --- a/src/Model/GroundWaterTransport/gwt1.f90 +++ b/src/Model/GroundWaterTransport/gwt1.f90 @@ -25,6 +25,7 @@ module GwtModule use GwtOcModule, only: GwtOcType use GwtObsModule, only: GwtObsType use BudgetModule, only: BudgetType + use MatrixModule implicit none @@ -349,7 +350,7 @@ subroutine gwt_ac(this, sparse) return end subroutine gwt_ac - subroutine gwt_mc(this, iasln, jasln) + subroutine gwt_mc(this, matrix_sln) ! ****************************************************************************** ! gwt_mc -- Map the positions of this models connections in the ! numerical solution coefficient matrix. @@ -359,8 +360,7 @@ subroutine gwt_mc(this, iasln, jasln) ! ------------------------------------------------------------------------------ ! -- dummy class(GwtModelType) :: this - integer(I4B), dimension(:), intent(in) :: iasln - integer(I4B), dimension(:), intent(in) :: jasln + class(MatrixBaseType), pointer :: matrix_sln !< global system matrix ! -- local class(BndType), pointer :: packobj integer(I4B) :: ip @@ -368,13 +368,13 @@ subroutine gwt_mc(this, iasln, jasln) ! ! -- Find the position of each connection in the global ia, ja structure ! and store them in idxglo. - call this%dis%dis_mc(this%moffset, this%idxglo, iasln, jasln) - if (this%indsp > 0) call this%dsp%dsp_mc(this%moffset, iasln, jasln) + call this%dis%dis_mc(this%moffset, this%idxglo, matrix_sln) + if (this%indsp > 0) call this%dsp%dsp_mc(this%moffset, matrix_sln) ! ! -- Map any package connections do ip = 1, this%bndlist%Count() packobj => GetBndFromList(this%bndlist, ip) - call packobj%bnd_mc(this%moffset, iasln, jasln) + call packobj%bnd_mc(this%moffset, matrix_sln) end do ! ! -- return @@ -553,7 +553,7 @@ subroutine gwt_cf(this, kiter) return end subroutine gwt_cf - subroutine gwt_fc(this, kiter, amatsln, njasln, inwtflag) + subroutine gwt_fc(this, kiter, matrix_sln, inwtflag) ! ****************************************************************************** ! gwt_fc -- GroundWater Transport Model fill coefficients ! ****************************************************************************** @@ -564,8 +564,7 @@ subroutine gwt_fc(this, kiter, amatsln, njasln, inwtflag) ! -- dummy class(GwtModelType) :: this integer(I4B), intent(in) :: kiter - integer(I4B), intent(in) :: njasln - real(DP), dimension(njasln), intent(inout) :: amatsln + class(MatrixBaseType), pointer :: matrix_sln integer(I4B), intent(in) :: inwtflag ! -- local class(BndType), pointer :: packobj @@ -573,31 +572,31 @@ subroutine gwt_fc(this, kiter, amatsln, njasln, inwtflag) ! ------------------------------------------------------------------------------ ! ! -- call fc routines - call this%fmi%fmi_fc(this%dis%nodes, this%xold, this%nja, njasln, & - amatsln, this%idxglo, this%rhs) + call this%fmi%fmi_fc(this%dis%nodes, this%xold, this%nja, matrix_sln, & + this%idxglo, this%rhs) if (this%inmvt > 0) then call this%mvt%mvt_fc(this%x, this%x) end if if (this%inmst > 0) then - call this%mst%mst_fc(this%dis%nodes, this%xold, this%nja, njasln, & - amatsln, this%idxglo, this%x, this%rhs, kiter) + call this%mst%mst_fc(this%dis%nodes, this%xold, this%nja, matrix_sln, & + this%idxglo, this%x, this%rhs, kiter) end if if (this%inadv > 0) then - call this%adv%adv_fc(this%dis%nodes, amatsln, this%idxglo, this%x, & + call this%adv%adv_fc(this%dis%nodes, matrix_sln, this%idxglo, this%x, & this%rhs) end if if (this%indsp > 0) then - call this%dsp%dsp_fc(kiter, this%dis%nodes, this%nja, njasln, amatsln, & + call this%dsp%dsp_fc(kiter, this%dis%nodes, this%nja, matrix_sln, & this%idxglo, this%rhs, this%x) end if if (this%inssm > 0) then - call this%ssm%ssm_fc(amatsln, this%idxglo, this%rhs) + call this%ssm%ssm_fc(matrix_sln, this%idxglo, this%rhs) end if ! ! -- packages do ip = 1, this%bndlist%Count() packobj => GetBndFromList(this%bndlist, ip) - call packobj%bnd_fc(this%rhs, this%ia, this%idxglo, amatsln) + call packobj%bnd_fc(this%rhs, this%ia, this%idxglo, matrix_sln) end do ! ! -- return diff --git a/src/Model/GroundWaterTransport/gwt1adv1.f90 b/src/Model/GroundWaterTransport/gwt1adv1.f90 index 3612b99f2dd..422a0993cd9 100644 --- a/src/Model/GroundWaterTransport/gwt1adv1.f90 +++ b/src/Model/GroundWaterTransport/gwt1adv1.f90 @@ -6,6 +6,7 @@ module GwtAdvModule use BaseDisModule, only: DisBaseType use GwtFmiModule, only: GwtFmiType use GwtAdvOptionsModule, only: GwtAdvOptionsType + use MatrixModule implicit none private @@ -122,7 +123,7 @@ subroutine adv_ar(this, dis, ibound) return end subroutine adv_ar - subroutine adv_fc(this, nodes, amatsln, idxglo, cnew, rhs) + subroutine adv_fc(this, nodes, matrix_sln, idxglo, cnew, rhs) ! ****************************************************************************** ! adv_fc -- Calculate coefficients and fill amat and rhs ! ****************************************************************************** @@ -133,7 +134,7 @@ subroutine adv_fc(this, nodes, amatsln, idxglo, cnew, rhs) ! -- dummy class(GwtAdvType) :: this integer, intent(in) :: nodes - real(DP), dimension(:), intent(inout) :: amatsln + class(MatrixBaseType), pointer :: matrix_sln integer(I4B), intent(in), dimension(:) :: idxglo real(DP), intent(in), dimension(:) :: cnew real(DP), dimension(:), intent(inout) :: rhs @@ -153,8 +154,8 @@ subroutine adv_fc(this, nodes, amatsln, idxglo, cnew, rhs) if (this%ibound(m) == 0) cycle qnm = this%fmi%gwfflowja(ipos) omega = this%adv_weight(this%iadvwt, ipos, n, m, qnm) - amatsln(idxglo(ipos)) = amatsln(idxglo(ipos)) + qnm * (DONE - omega) - amatsln(idxglo(idiag)) = amatsln(idxglo(idiag)) + qnm * omega + call matrix_sln%add_value_pos(idxglo(ipos), qnm * (DONE - omega)) + call matrix_sln%add_value_pos(idxglo(idiag), qnm * omega) end do end do ! diff --git a/src/Model/GroundWaterTransport/gwt1apt1.f90 b/src/Model/GroundWaterTransport/gwt1apt1.f90 index a9db2eb3a1e..f78a4f8b282 100644 --- a/src/Model/GroundWaterTransport/gwt1apt1.f90 +++ b/src/Model/GroundWaterTransport/gwt1apt1.f90 @@ -51,6 +51,7 @@ module GwtAptModule use ObserveModule, only: ObserveType use InputOutputModule, only: extract_idnum_or_bndname use BaseDisModule, only: DisBaseType + use MatrixModule implicit none @@ -232,7 +233,7 @@ subroutine apt_ac(this, moffset, sparse) return end subroutine apt_ac - subroutine apt_mc(this, moffset, iasln, jasln) + subroutine apt_mc(this, moffset, matrix_sln) ! ****************************************************************************** ! apt_mc -- map package connection to matrix ! ****************************************************************************** @@ -243,10 +244,9 @@ subroutine apt_mc(this, moffset, iasln, jasln) ! -- dummy class(GwtAptType), intent(inout) :: this integer(I4B), intent(in) :: moffset - integer(I4B), dimension(:), intent(in) :: iasln - integer(I4B), dimension(:), intent(in) :: jasln + class(MatrixBaseType), pointer :: matrix_sln ! -- local - integer(I4B) :: n, j, jj, iglo, jglo + integer(I4B) :: n, j, iglo, jglo integer(I4B) :: ipos ! -- format ! ------------------------------------------------------------------------------ @@ -265,20 +265,15 @@ subroutine apt_mc(this, moffset, iasln, jasln) do n = 1, this%ncv this%idxlocnode(n) = this%dis%nodes + this%ioffset + n iglo = moffset + this%dis%nodes + this%ioffset + n - this%idxpakdiag(n) = iasln(iglo) + this%idxpakdiag(n) = matrix_sln%get_position_diag(iglo) end do do ipos = 1, this%flowbudptr%budterm(this%idxbudgwf)%nlist n = this%flowbudptr%budterm(this%idxbudgwf)%id1(ipos) j = this%flowbudptr%budterm(this%idxbudgwf)%id2(ipos) iglo = moffset + this%dis%nodes + this%ioffset + n jglo = j + moffset - searchloop: do jj = iasln(iglo), iasln(iglo + 1) - 1 - if (jglo == jasln(jj)) then - this%idxdglo(ipos) = iasln(iglo) - this%idxoffdglo(ipos) = jj - exit searchloop - end if - end do searchloop + this%idxdglo(ipos) = matrix_sln%get_position_diag(iglo) + this%idxoffdglo(ipos) = matrix_sln%get_position(iglo, jglo) end do ! ! -- apt contributions to gwf portion of global matrix @@ -287,13 +282,8 @@ subroutine apt_mc(this, moffset, iasln, jasln) j = this%flowbudptr%budterm(this%idxbudgwf)%id2(ipos) iglo = j + moffset jglo = moffset + this%dis%nodes + this%ioffset + n - symsearchloop: do jj = iasln(iglo), iasln(iglo + 1) - 1 - if (jglo == jasln(jj)) then - this%idxsymdglo(ipos) = iasln(iglo) - this%idxsymoffdglo(ipos) = jj - exit symsearchloop - end if - end do symsearchloop + this%idxsymdglo(ipos) = matrix_sln%get_position_diag(iglo) + this%idxsymoffdglo(ipos) = matrix_sln%get_position(iglo, jglo) end do ! ! -- apt-apt contributions to gwf portion of global matrix @@ -303,13 +293,8 @@ subroutine apt_mc(this, moffset, iasln, jasln) j = this%flowbudptr%budterm(this%idxbudfjf)%id2(ipos) iglo = moffset + this%dis%nodes + this%ioffset + n jglo = moffset + this%dis%nodes + this%ioffset + j - fjfsearchloop: do jj = iasln(iglo), iasln(iglo + 1) - 1 - if (jglo == jasln(jj)) then - this%idxfjfdglo(ipos) = iasln(iglo) - this%idxfjfoffdglo(ipos) = jj - exit fjfsearchloop - end if - end do fjfsearchloop + this%idxfjfdglo(ipos) = matrix_sln%get_position_diag(iglo) + this%idxfjfoffdglo(ipos) = matrix_sln%get_position(iglo, jglo) end do end if end if @@ -759,7 +744,7 @@ subroutine apt_cf(this, reset_mover) return end subroutine apt_cf - subroutine apt_fc(this, rhs, ia, idxglo, amatsln) + subroutine apt_fc(this, rhs, ia, idxglo, matrix_sln) ! ****************************************************************************** ! apt_fc ! **************************************************************************** @@ -772,22 +757,22 @@ subroutine apt_fc(this, rhs, ia, idxglo, amatsln) real(DP), dimension(:), intent(inout) :: rhs integer(I4B), dimension(:), intent(in) :: ia integer(I4B), dimension(:), intent(in) :: idxglo - real(DP), dimension(:), intent(inout) :: amatsln + class(MatrixBaseType), pointer :: matrix_sln ! -- local ! ------------------------------------------------------------------------------ ! ! -- Call fc depending on whether or not a matrix is expanded or not if (this%imatrows == 0) then - call this%apt_fc_nonexpanded(rhs, ia, idxglo, amatsln) + call this%apt_fc_nonexpanded(rhs, ia, idxglo, matrix_sln) else - call this%apt_fc_expanded(rhs, ia, idxglo, amatsln) + call this%apt_fc_expanded(rhs, ia, idxglo, matrix_sln) end if ! ! -- Return return end subroutine apt_fc - subroutine apt_fc_nonexpanded(this, rhs, ia, idxglo, amatsln) + subroutine apt_fc_nonexpanded(this, rhs, ia, idxglo, matrix_sln) ! ****************************************************************************** ! apt_fc_nonexpanded -- formulate for the nonexpanded a matrix case in which ! feature concentrations are solved explicitly @@ -801,7 +786,7 @@ subroutine apt_fc_nonexpanded(this, rhs, ia, idxglo, amatsln) real(DP), dimension(:), intent(inout) :: rhs integer(I4B), dimension(:), intent(in) :: ia integer(I4B), dimension(:), intent(in) :: idxglo - real(DP), dimension(:), intent(inout) :: amatsln + class(MatrixBaseType), pointer :: matrix_sln ! -- local integer(I4B) :: j, igwfnode, idiag ! ------------------------------------------------------------------------------ @@ -814,7 +799,7 @@ subroutine apt_fc_nonexpanded(this, rhs, ia, idxglo, amatsln) igwfnode = this%flowbudptr%budterm(this%idxbudgwf)%id2(j) if (this%ibound(igwfnode) < 1) cycle idiag = idxglo(ia(igwfnode)) - amatsln(idiag) = amatsln(idiag) + this%hcof(j) + call matrix_sln%add_value_pos(idiag, this%hcof(j)) rhs(igwfnode) = rhs(igwfnode) + this%rhs(j) end do ! @@ -822,7 +807,7 @@ subroutine apt_fc_nonexpanded(this, rhs, ia, idxglo, amatsln) return end subroutine apt_fc_nonexpanded - subroutine apt_fc_expanded(this, rhs, ia, idxglo, amatsln) + subroutine apt_fc_expanded(this, rhs, ia, idxglo, matrix_sln) ! ****************************************************************************** ! apt_fc_expanded -- formulate for the expanded matrix case in which new ! rows are added to the system of equations for each feature @@ -836,7 +821,7 @@ subroutine apt_fc_expanded(this, rhs, ia, idxglo, amatsln) real(DP), dimension(:), intent(inout) :: rhs integer(I4B), dimension(:), intent(in) :: ia integer(I4B), dimension(:), intent(in) :: idxglo - real(DP), dimension(:), intent(inout) :: amatsln + class(MatrixBaseType), pointer :: matrix_sln ! -- local integer(I4B) :: j, n, n1, n2 integer(I4B) :: iloc @@ -855,7 +840,7 @@ subroutine apt_fc_expanded(this, rhs, ia, idxglo, amatsln) ! GwtLktType, GwtSftType, GwtMwtType, GwtUztType ! This routine will add terms for rainfall, runoff, or other terms ! specific to the package - call this%pak_fc_expanded(rhs, ia, idxglo, amatsln) + call this%pak_fc_expanded(rhs, ia, idxglo, matrix_sln) ! ! -- mass storage in features do n = 1, this%ncv @@ -863,7 +848,7 @@ subroutine apt_fc_expanded(this, rhs, ia, idxglo, amatsln) iloc = this%idxlocnode(n) iposd = this%idxpakdiag(n) call this%apt_stor_term(n, n1, n2, rrate, rhsval, hcofval) - amatsln(iposd) = amatsln(iposd) + hcofval + call matrix_sln%add_value_pos(iposd, hcofval) rhs(iloc) = rhs(iloc) + rhsval end do ! @@ -873,7 +858,7 @@ subroutine apt_fc_expanded(this, rhs, ia, idxglo, amatsln) call this%apt_tmvr_term(j, n1, n2, rrate, rhsval, hcofval) iloc = this%idxlocnode(n1) iposd = this%idxpakdiag(n1) - amatsln(iposd) = amatsln(iposd) + hcofval + call matrix_sln%add_value_pos(iposd, hcofval) rhs(iloc) = rhs(iloc) + rhsval end do end if @@ -902,14 +887,14 @@ subroutine apt_fc_expanded(this, rhs, ia, idxglo, amatsln) ! -- add to apt row iposd = this%idxdglo(j) iposoffd = this%idxoffdglo(j) - amatsln(iposd) = amatsln(iposd) + omega * qbnd - amatsln(iposoffd) = amatsln(iposoffd) + (DONE - omega) * qbnd + call matrix_sln%add_value_pos(iposd, omega * qbnd) + call matrix_sln%add_value_pos(iposoffd, (DONE - omega) * qbnd) ! ! -- add to gwf row for apt connection ipossymd = this%idxsymdglo(j) ipossymoffd = this%idxsymoffdglo(j) - amatsln(ipossymd) = amatsln(ipossymd) - (DONE - omega) * qbnd - amatsln(ipossymoffd) = amatsln(ipossymoffd) - omega * qbnd + call matrix_sln%add_value_pos(ipossymd, -(DONE - omega) * qbnd) + call matrix_sln%add_value_pos(ipossymoffd, -omega * qbnd) end if end do ! @@ -926,8 +911,8 @@ subroutine apt_fc_expanded(this, rhs, ia, idxglo, amatsln) end if iposd = this%idxfjfdglo(j) iposoffd = this%idxfjfoffdglo(j) - amatsln(iposd) = amatsln(iposd) + omega * qbnd - amatsln(iposoffd) = amatsln(iposoffd) + (DONE - omega) * qbnd + call matrix_sln%add_value_pos(iposd, omega * qbnd) + call matrix_sln%add_value_pos(iposoffd, (DONE - omega) * qbnd) end do end if ! @@ -935,7 +920,7 @@ subroutine apt_fc_expanded(this, rhs, ia, idxglo, amatsln) return end subroutine apt_fc_expanded - subroutine pak_fc_expanded(this, rhs, ia, idxglo, amatsln) + subroutine pak_fc_expanded(this, rhs, ia, idxglo, matrix_sln) ! ****************************************************************************** ! pak_fc_expanded -- allow a subclass advanced transport package to inject ! terms into the matrix assembly. This method must be overridden. @@ -949,7 +934,7 @@ subroutine pak_fc_expanded(this, rhs, ia, idxglo, amatsln) real(DP), dimension(:), intent(inout) :: rhs integer(I4B), dimension(:), intent(in) :: ia integer(I4B), dimension(:), intent(in) :: idxglo - real(DP), dimension(:), intent(inout) :: amatsln + class(MatrixBaseType), pointer :: matrix_sln ! -- local ! ------------------------------------------------------------------------------ ! diff --git a/src/Model/GroundWaterTransport/gwt1cnc1.f90 b/src/Model/GroundWaterTransport/gwt1cnc1.f90 index 28f6a3d4814..42d99aeb038 100644 --- a/src/Model/GroundWaterTransport/gwt1cnc1.f90 +++ b/src/Model/GroundWaterTransport/gwt1cnc1.f90 @@ -8,6 +8,7 @@ module GwtCncModule use ObserveModule, only: ObserveType use TimeSeriesLinkModule, only: TimeSeriesLinkType, & GetTimeSeriesLinkFromList + use MatrixModule ! implicit none ! @@ -244,7 +245,7 @@ subroutine cnc_ck(this) return end subroutine cnc_ck - subroutine cnc_fc(this, rhs, ia, idxglo, amatsln) + subroutine cnc_fc(this, rhs, ia, idxglo, matrix_sln) ! ************************************************************************** ! cnc_fc -- Override bnd_fc and do nothing ! ************************************************************************** @@ -256,7 +257,7 @@ subroutine cnc_fc(this, rhs, ia, idxglo, amatsln) real(DP), dimension(:), intent(inout) :: rhs integer(I4B), dimension(:), intent(in) :: ia integer(I4B), dimension(:), intent(in) :: idxglo - real(DP), dimension(:), intent(inout) :: amatsln + class(MatrixBaseType), pointer :: matrix_sln ! -- local ! -------------------------------------------------------------------------- ! diff --git a/src/Model/GroundWaterTransport/gwt1dsp.f90 b/src/Model/GroundWaterTransport/gwt1dsp.f90 index 8419a508de0..3c421b1af4b 100644 --- a/src/Model/GroundWaterTransport/gwt1dsp.f90 +++ b/src/Model/GroundWaterTransport/gwt1dsp.f90 @@ -7,6 +7,7 @@ module GwtDspModule use GwtFmiModule, only: GwtFmiType use Xt3dModule, only: Xt3dType, xt3d_cr use GwtDspOptionsModule, only: GwtDspOptionsType + use MatrixModule implicit none private @@ -203,7 +204,7 @@ subroutine dsp_ac(this, moffset, sparse) return end subroutine dsp_ac - subroutine dsp_mc(this, moffset, iasln, jasln) + subroutine dsp_mc(this, moffset, matrix_sln) ! ****************************************************************************** ! dsp_mc -- Map connections and construct iax, jax, and idxglox ! ****************************************************************************** @@ -215,13 +216,12 @@ subroutine dsp_mc(this, moffset, iasln, jasln) ! -- dummy class(GwtDspType) :: this integer(I4B), intent(in) :: moffset - integer(I4B), dimension(:), intent(in) :: iasln - integer(I4B), dimension(:), intent(in) :: jasln + class(MatrixBaseType), pointer :: matrix_sln ! -- local ! ------------------------------------------------------------------------------ ! ! -- Call xt3d map connections - if (this%ixt3d > 0) call this%xt3d%xt3d_mc(moffset, iasln, jasln) + if (this%ixt3d > 0) call this%xt3d%xt3d_mc(moffset, matrix_sln) ! ! -- Return return @@ -296,7 +296,7 @@ subroutine dsp_ad(this) return end subroutine dsp_ad - subroutine dsp_fc(this, kiter, nodes, nja, njasln, amatsln, idxglo, rhs, cnew) + subroutine dsp_fc(this, kiter, nodes, nja, matrix_sln, idxglo, rhs, cnew) ! ****************************************************************************** ! dsp_fc -- Calculate coefficients and fill amat and rhs ! ****************************************************************************** @@ -309,8 +309,7 @@ subroutine dsp_fc(this, kiter, nodes, nja, njasln, amatsln, idxglo, rhs, cnew) integer(I4B) :: kiter integer(I4B), intent(in) :: nodes integer(I4B), intent(in) :: nja - integer(I4B), intent(in) :: njasln - real(DP), dimension(njasln), intent(inout) :: amatsln + class(MatrixBaseType), pointer :: matrix_sln integer(I4B), intent(in), dimension(nja) :: idxglo real(DP), intent(inout), dimension(nodes) :: rhs real(DP), intent(inout), dimension(nodes) :: cnew @@ -320,7 +319,7 @@ subroutine dsp_fc(this, kiter, nodes, nja, njasln, amatsln, idxglo, rhs, cnew) ! ------------------------------------------------------------------------------ ! if (this%ixt3d > 0) then - call this%xt3d%xt3d_fc(kiter, njasln, amatsln, idxglo, rhs, cnew) + call this%xt3d%xt3d_fc(kiter, matrix_sln, idxglo, rhs, cnew) else do n = 1, nodes if (this%fmi%ibdgwfsat0(n) == 0) cycle @@ -334,14 +333,14 @@ subroutine dsp_fc(this, kiter, nodes, nja, njasln, amatsln, idxglo, rhs, cnew) dnm = this%dispcoef(isympos) ! ! -- Contribution to row n - amatsln(idxglo(ipos)) = amatsln(idxglo(ipos)) + dnm - amatsln(idxglo(idiag)) = amatsln(idxglo(idiag)) - dnm + call matrix_sln%add_value_pos(idxglo(ipos), dnm) + call matrix_sln%add_value_pos(idxglo(idiag), -dnm) ! ! -- Contribution to row m idiagm = this%dis%con%ia(m) isymcon = this%dis%con%isym(ipos) - amatsln(idxglo(isymcon)) = amatsln(idxglo(isymcon)) + dnm - amatsln(idxglo(idiagm)) = amatsln(idxglo(idiagm)) - dnm + call matrix_sln%add_value_pos(idxglo(isymcon), dnm) + call matrix_sln%add_value_pos(idxglo(idiagm), -dnm) end do end do end if diff --git a/src/Model/GroundWaterTransport/gwt1fmi1.f90 b/src/Model/GroundWaterTransport/gwt1fmi1.f90 index 908a21f8be8..fd724dbe244 100644 --- a/src/Model/GroundWaterTransport/gwt1fmi1.f90 +++ b/src/Model/GroundWaterTransport/gwt1fmi1.f90 @@ -12,6 +12,7 @@ module GwtFmiModule use HeadFileReaderModule, only: HeadFileReaderType use PackageBudgetModule, only: PackageBudgetType use BudgetObjectModule, only: BudgetObjectType, budgetobject_cr_bfr + use MatrixModule implicit none private @@ -376,9 +377,9 @@ subroutine fmi_ad(this, cnew) return end subroutine fmi_ad - subroutine fmi_fc(this, nodes, cold, nja, njasln, amatsln, idxglo, rhs) + subroutine fmi_fc(this, nodes, cold, nja, matrix_sln, idxglo, rhs) ! ****************************************************************************** -! fmi_fc -- Calculate coefficients and fill amat and rhs +! fmi_fc -- Calculate coefficients and fill matrix and rhs ! ****************************************************************************** ! ! SPECIFICATIONS: @@ -390,12 +391,11 @@ subroutine fmi_fc(this, nodes, cold, nja, njasln, amatsln, idxglo, rhs) integer, intent(in) :: nodes real(DP), intent(in), dimension(nodes) :: cold integer(I4B), intent(in) :: nja - integer(I4B), intent(in) :: njasln - real(DP), dimension(njasln), intent(inout) :: amatsln + class(MatrixBaseType), pointer :: matrix_sln integer(I4B), intent(in), dimension(nja) :: idxglo real(DP), intent(inout), dimension(nodes) :: rhs ! -- local - integer(I4B) :: n, ipos, idiag + integer(I4B) :: n, idiag, idiag_sln ! ------------------------------------------------------------------------------ ! ! -- Calculate the flow imbalance error and make a correction for it @@ -404,9 +404,9 @@ subroutine fmi_fc(this, nodes, cold, nja, njasln, amatsln, idxglo, rhs) ! -- Correct the transport solution for the flow imbalance by adding ! the flow residual to the diagonal do n = 1, nodes - idiag = idxglo(this%dis%con%ia(n)) - ipos = this%dis%con%ia(n) - amatsln(idiag) = amatsln(idiag) - this%gwfflowja(ipos) + idiag = this%dis%con%ia(n) + idiag_sln = idxglo(idiag) + call matrix_sln%add_value_pos(idiag_sln, -this%gwfflowja(idiag)) end do end if ! diff --git a/src/Model/GroundWaterTransport/gwt1ist1.f90 b/src/Model/GroundWaterTransport/gwt1ist1.f90 index c19cb08a653..3855709ea5c 100644 --- a/src/Model/GroundWaterTransport/gwt1ist1.f90 +++ b/src/Model/GroundWaterTransport/gwt1ist1.f90 @@ -22,6 +22,7 @@ module GwtIstModule use GwtFmiModule, only: GwtFmiType use GwtMstModule, only: GwtMstType, get_zero_order_decay use OutputControlDataModule, only: OutputControlDataType + use MatrixModule ! implicit none ! @@ -271,7 +272,7 @@ end subroutine ist_ad !! Method to calculate and fill coefficients for the package. !! !< - subroutine ist_fc(this, rhs, ia, idxglo, amatsln) + subroutine ist_fc(this, rhs, ia, idxglo, matrix_sln) ! -- modules use TdisModule, only: delt ! -- dummy @@ -279,7 +280,7 @@ subroutine ist_fc(this, rhs, ia, idxglo, amatsln) real(DP), dimension(:), intent(inout) :: rhs !< right-hand side vector for model integer(I4B), dimension(:), intent(in) :: ia !< solution CRS row pointers integer(I4B), dimension(:), intent(in) :: idxglo !< mapping vector for model (local) to solution (global) - real(DP), dimension(:), intent(inout) :: amatsln !< solution coefficient matrix + class(MatrixBaseType), pointer :: matrix_sln !< solution coefficient matrix ! -- local integer(I4B) :: n, idiag real(DP) :: tled @@ -367,7 +368,7 @@ subroutine ist_fc(this, rhs, ia, idxglo, amatsln) call get_hcofrhs(ddterm, f, cimold, hhcof, rrhs) ! ! -- update solution accumulators - amatsln(idxglo(idiag)) = amatsln(idxglo(idiag)) + hhcof + call matrix_sln%add_value_pos(idxglo(idiag), hhcof) rhs(n) = rhs(n) + rrhs ! end do diff --git a/src/Model/GroundWaterTransport/gwt1lkt1.f90 b/src/Model/GroundWaterTransport/gwt1lkt1.f90 index cf6e7c0e223..b7af593604d 100644 --- a/src/Model/GroundWaterTransport/gwt1lkt1.f90 +++ b/src/Model/GroundWaterTransport/gwt1lkt1.f90 @@ -42,6 +42,7 @@ module GwtLktModule use ObserveModule, only: ObserveType use GwtAptModule, only: GwtAptType, apt_process_obsID, & apt_process_obsID12 + use MatrixModule implicit none @@ -269,7 +270,7 @@ subroutine find_lkt_package(this) return end subroutine find_lkt_package - subroutine lkt_fc_expanded(this, rhs, ia, idxglo, amatsln) + subroutine lkt_fc_expanded(this, rhs, ia, idxglo, matrix_sln) ! ****************************************************************************** ! lkt_fc_expanded -- this will be called from GwtAptType%apt_fc_expanded() ! in order to add matrix terms specifically for LKT @@ -283,7 +284,7 @@ subroutine lkt_fc_expanded(this, rhs, ia, idxglo, amatsln) real(DP), dimension(:), intent(inout) :: rhs integer(I4B), dimension(:), intent(in) :: ia integer(I4B), dimension(:), intent(in) :: idxglo - real(DP), dimension(:), intent(inout) :: amatsln + class(MatrixBaseType), pointer :: matrix_sln ! -- local integer(I4B) :: j, n1, n2 integer(I4B) :: iloc @@ -299,7 +300,7 @@ subroutine lkt_fc_expanded(this, rhs, ia, idxglo, amatsln) call this%lkt_rain_term(j, n1, n2, rrate, rhsval, hcofval) iloc = this%idxlocnode(n1) iposd = this%idxpakdiag(n1) - amatsln(iposd) = amatsln(iposd) + hcofval + call matrix_sln%add_value_pos(iposd, hcofval) rhs(iloc) = rhs(iloc) + rhsval end do end if @@ -310,7 +311,7 @@ subroutine lkt_fc_expanded(this, rhs, ia, idxglo, amatsln) call this%lkt_evap_term(j, n1, n2, rrate, rhsval, hcofval) iloc = this%idxlocnode(n1) iposd = this%idxpakdiag(n1) - amatsln(iposd) = amatsln(iposd) + hcofval + call matrix_sln%add_value_pos(iposd, hcofval) rhs(iloc) = rhs(iloc) + rhsval end do end if @@ -321,7 +322,7 @@ subroutine lkt_fc_expanded(this, rhs, ia, idxglo, amatsln) call this%lkt_roff_term(j, n1, n2, rrate, rhsval, hcofval) iloc = this%idxlocnode(n1) iposd = this%idxpakdiag(n1) - amatsln(iposd) = amatsln(iposd) + hcofval + call matrix_sln%add_value_pos(iposd, hcofval) rhs(iloc) = rhs(iloc) + rhsval end do end if @@ -332,7 +333,7 @@ subroutine lkt_fc_expanded(this, rhs, ia, idxglo, amatsln) call this%lkt_iflw_term(j, n1, n2, rrate, rhsval, hcofval) iloc = this%idxlocnode(n1) iposd = this%idxpakdiag(n1) - amatsln(iposd) = amatsln(iposd) + hcofval + call matrix_sln%add_value_pos(iposd, hcofval) rhs(iloc) = rhs(iloc) + rhsval end do end if @@ -343,7 +344,7 @@ subroutine lkt_fc_expanded(this, rhs, ia, idxglo, amatsln) call this%lkt_wdrl_term(j, n1, n2, rrate, rhsval, hcofval) iloc = this%idxlocnode(n1) iposd = this%idxpakdiag(n1) - amatsln(iposd) = amatsln(iposd) + hcofval + call matrix_sln%add_value_pos(iposd, hcofval) rhs(iloc) = rhs(iloc) + rhsval end do end if @@ -354,7 +355,7 @@ subroutine lkt_fc_expanded(this, rhs, ia, idxglo, amatsln) call this%lkt_outf_term(j, n1, n2, rrate, rhsval, hcofval) iloc = this%idxlocnode(n1) iposd = this%idxpakdiag(n1) - amatsln(iposd) = amatsln(iposd) + hcofval + call matrix_sln%add_value_pos(iposd, hcofval) rhs(iloc) = rhs(iloc) + rhsval end do end if diff --git a/src/Model/GroundWaterTransport/gwt1mst1.f90 b/src/Model/GroundWaterTransport/gwt1mst1.f90 index bef0bb49e1b..dc328cd5273 100644 --- a/src/Model/GroundWaterTransport/gwt1mst1.f90 +++ b/src/Model/GroundWaterTransport/gwt1mst1.f90 @@ -14,6 +14,7 @@ module GwtMstModule use SimVariablesModule, only: errmsg, warnmsg use SimModule, only: store_error, count_errors, & store_warning + use MatrixModule use NumericalPackageModule, only: NumericalPackageType use BaseDisModule, only: DisBaseType use GwtFmiModule, only: GwtFmiType @@ -164,7 +165,7 @@ end subroutine mst_ar !! Method to calculate and fill coefficients for the package. !! !< - subroutine mst_fc(this, nodes, cold, nja, njasln, amatsln, idxglo, cnew, & + subroutine mst_fc(this, nodes, cold, nja, matrix_sln, idxglo, cnew, & rhs, kiter) ! -- modules ! -- dummy @@ -172,8 +173,7 @@ subroutine mst_fc(this, nodes, cold, nja, njasln, amatsln, idxglo, cnew, & integer, intent(in) :: nodes !< number of nodes real(DP), intent(in), dimension(nodes) :: cold !< concentration at end of last time step integer(I4B), intent(in) :: nja !< number of GWT connections - integer(I4B), intent(in) :: njasln !< number of connections in solution - real(DP), dimension(njasln), intent(inout) :: amatsln !< solution coefficient matrix + class(MatrixBaseType), pointer :: matrix_sln !< solution matrix integer(I4B), intent(in), dimension(nja) :: idxglo !< mapping vector for model (local) to solution (global) real(DP), intent(inout), dimension(nodes) :: rhs !< right-hand side vector for model real(DP), intent(in), dimension(nodes) :: cnew !< concentration at end of this time step @@ -181,22 +181,22 @@ subroutine mst_fc(this, nodes, cold, nja, njasln, amatsln, idxglo, cnew, & ! -- local ! ! -- storage contribution - call this%mst_fc_sto(nodes, cold, nja, njasln, amatsln, idxglo, rhs) + call this%mst_fc_sto(nodes, cold, nja, matrix_sln, idxglo, rhs) ! ! -- decay contribution if (this%idcy /= 0) then - call this%mst_fc_dcy(nodes, cold, cnew, nja, njasln, amatsln, idxglo, & + call this%mst_fc_dcy(nodes, cold, cnew, nja, matrix_sln, idxglo, & rhs, kiter) end if ! ! -- sorption contribution if (this%isrb /= 0) then - call this%mst_fc_srb(nodes, cold, nja, njasln, amatsln, idxglo, rhs, cnew) + call this%mst_fc_srb(nodes, cold, nja, matrix_sln, idxglo, rhs, cnew) end if ! ! -- decay sorbed contribution if (this%isrb /= 0 .and. this%idcy /= 0) then - call this%mst_fc_dcy_srb(nodes, cold, nja, njasln, amatsln, idxglo, rhs, & + call this%mst_fc_dcy_srb(nodes, cold, nja, matrix_sln, idxglo, rhs, & cnew, kiter) end if ! @@ -209,7 +209,7 @@ end subroutine mst_fc !! Method to calculate and fill storage coefficients for the package. !! !< - subroutine mst_fc_sto(this, nodes, cold, nja, njasln, amatsln, idxglo, rhs) + subroutine mst_fc_sto(this, nodes, cold, nja, matrix_sln, idxglo, rhs) ! -- modules use TdisModule, only: delt ! -- dummy @@ -217,8 +217,7 @@ subroutine mst_fc_sto(this, nodes, cold, nja, njasln, amatsln, idxglo, rhs) integer, intent(in) :: nodes !< number of nodes real(DP), intent(in), dimension(nodes) :: cold !< concentration at end of last time step integer(I4B), intent(in) :: nja !< number of GWT connections - integer(I4B), intent(in) :: njasln !< number of connections in solution - real(DP), dimension(njasln), intent(inout) :: amatsln !< solution coefficient matrix + class(MatrixBaseType), pointer :: matrix_sln !< solution coefficient matrix integer(I4B), intent(in), dimension(nja) :: idxglo !< mapping vector for model (local) to solution (global) real(DP), intent(inout), dimension(nodes) :: rhs !< right-hand side vector for model ! -- local @@ -247,7 +246,7 @@ subroutine mst_fc_sto(this, nodes, cold, nja, njasln, amatsln, idxglo, rhs) hhcof = -vnew * tled rrhs = -vold * tled * cold(n) idiag = this%dis%con%ia(n) - amatsln(idxglo(idiag)) = amatsln(idxglo(idiag)) + hhcof + call matrix_sln%add_value_pos(idxglo(idiag), hhcof) rhs(n) = rhs(n) + rrhs end do ! @@ -260,7 +259,7 @@ end subroutine mst_fc_sto !! Method to calculate and fill decay coefficients for the package. !! !< - subroutine mst_fc_dcy(this, nodes, cold, cnew, nja, njasln, amatsln, & + subroutine mst_fc_dcy(this, nodes, cold, cnew, nja, matrix_sln, & idxglo, rhs, kiter) ! -- modules use TdisModule, only: delt @@ -270,8 +269,7 @@ subroutine mst_fc_dcy(this, nodes, cold, cnew, nja, njasln, amatsln, & real(DP), intent(in), dimension(nodes) :: cold !< concentration at end of last time step real(DP), intent(in), dimension(nodes) :: cnew !< concentration at end of this time step integer(I4B), intent(in) :: nja !< number of GWT connections - integer(I4B), intent(in) :: njasln !< number of connections in solution - real(DP), dimension(njasln), intent(inout) :: amatsln !< solution coefficient matrix + class(MatrixBaseType), pointer :: matrix_sln !< solution coefficient matrix integer(I4B), intent(in), dimension(nja) :: idxglo !< mapping vector for model (local) to solution (global) real(DP), intent(inout), dimension(nodes) :: rhs !< right-hand side vector for model integer(I4B), intent(in) :: kiter !< solution outer iteration number @@ -299,7 +297,7 @@ subroutine mst_fc_dcy(this, nodes, cold, cnew, nja, njasln, amatsln, & ! -- first order decay rate is a function of concentration, so add ! to left hand side hhcof = -this%decay(n) * vcell * swtpdt * this%porosity(n) - amatsln(idxglo(idiag)) = amatsln(idxglo(idiag)) + hhcof + call matrix_sln%add_value_pos(idxglo(idiag), hhcof) elseif (this%idcy == 2) then ! ! -- Call function to get zero-order decay rate, which may be changed @@ -322,7 +320,7 @@ end subroutine mst_fc_dcy !! Method to calculate and fill sorption coefficients for the package. !! !< - subroutine mst_fc_srb(this, nodes, cold, nja, njasln, amatsln, idxglo, rhs, & + subroutine mst_fc_srb(this, nodes, cold, nja, matrix_sln, idxglo, rhs, & cnew) ! -- modules use TdisModule, only: delt @@ -331,8 +329,7 @@ subroutine mst_fc_srb(this, nodes, cold, nja, njasln, amatsln, idxglo, rhs, & integer, intent(in) :: nodes !< number of nodes real(DP), intent(in), dimension(nodes) :: cold !< concentration at end of last time step integer(I4B), intent(in) :: nja !< number of GWT connections - integer(I4B), intent(in) :: njasln !< number of connections in solution - real(DP), dimension(njasln), intent(inout) :: amatsln !< solution coefficient matrix + class(MatrixBaseType), pointer :: matrix_sln !< solution coefficient matrix integer(I4B), intent(in), dimension(nja) :: idxglo !< mapping vector for model (local) to solution (global) real(DP), intent(inout), dimension(nodes) :: rhs !< right-hand side vector for model real(DP), intent(in), dimension(nodes) :: cnew !< concentration at end of this time step @@ -371,7 +368,7 @@ subroutine mst_fc_srb(this, nodes, cold, nja, njasln, amatsln, idxglo, rhs, & hcofval=hhcof, rhsval=rrhs) ! ! -- Add hhcof to diagonal and rrhs to right-hand side - amatsln(idxglo(idiag)) = amatsln(idxglo(idiag)) + hhcof + call matrix_sln%add_value_pos(idxglo(idiag), hhcof) rhs(n) = rhs(n) + rrhs ! end do @@ -460,7 +457,7 @@ end subroutine mst_srb_term !! Method to calculate and fill sorption-decay coefficients for the package. !! !< - subroutine mst_fc_dcy_srb(this, nodes, cold, nja, njasln, amatsln, idxglo, & + subroutine mst_fc_dcy_srb(this, nodes, cold, nja, matrix_sln, idxglo, & rhs, cnew, kiter) ! -- modules use TdisModule, only: delt @@ -469,8 +466,7 @@ subroutine mst_fc_dcy_srb(this, nodes, cold, nja, njasln, amatsln, idxglo, & integer, intent(in) :: nodes !< number of nodes real(DP), intent(in), dimension(nodes) :: cold !< concentration at end of last time step integer(I4B), intent(in) :: nja !< number of GWT connections - integer(I4B), intent(in) :: njasln !< number of connections in solution - real(DP), dimension(njasln), intent(inout) :: amatsln !< solution coefficient matrix + class(MatrixBaseType), pointer :: matrix_sln !< solution coefficient matrix integer(I4B), intent(in), dimension(nja) :: idxglo !< mapping vector for model (local) to solution (global) real(DP), intent(inout), dimension(nodes) :: rhs !< right-hand side vector for model real(DP), intent(in), dimension(nodes) :: cnew !< concentration at end of this time step @@ -551,7 +547,7 @@ subroutine mst_fc_dcy_srb(this, nodes, cold, nja, njasln, amatsln, idxglo, & end if ! ! -- Add hhcof to diagonal and rrhs to right-hand side - amatsln(idxglo(idiag)) = amatsln(idxglo(idiag)) + hhcof + call matrix_sln%add_value_pos(idxglo(idiag), hhcof) rhs(n) = rhs(n) + rrhs ! end do diff --git a/src/Model/GroundWaterTransport/gwt1mwt1.f90 b/src/Model/GroundWaterTransport/gwt1mwt1.f90 index f6493f80ab1..1031ae06671 100644 --- a/src/Model/GroundWaterTransport/gwt1mwt1.f90 +++ b/src/Model/GroundWaterTransport/gwt1mwt1.f90 @@ -43,6 +43,7 @@ module GwtMwtModule use ObserveModule, only: ObserveType use GwtAptModule, only: GwtAptType, apt_process_obsID, & apt_process_obsID12 + use MatrixModule implicit none @@ -256,7 +257,7 @@ subroutine find_mwt_package(this) return end subroutine find_mwt_package - subroutine mwt_fc_expanded(this, rhs, ia, idxglo, amatsln) + subroutine mwt_fc_expanded(this, rhs, ia, idxglo, matrix_sln) ! ****************************************************************************** ! mwt_fc_expanded -- this will be called from GwtAptType%apt_fc_expanded() ! in order to add matrix terms specifically for this package @@ -270,7 +271,7 @@ subroutine mwt_fc_expanded(this, rhs, ia, idxglo, amatsln) real(DP), dimension(:), intent(inout) :: rhs integer(I4B), dimension(:), intent(in) :: ia integer(I4B), dimension(:), intent(in) :: idxglo - real(DP), dimension(:), intent(inout) :: amatsln + class(MatrixBaseType), pointer :: matrix_sln ! -- local integer(I4B) :: j, n1, n2 integer(I4B) :: iloc @@ -286,7 +287,7 @@ subroutine mwt_fc_expanded(this, rhs, ia, idxglo, amatsln) call this%mwt_rate_term(j, n1, n2, rrate, rhsval, hcofval) iloc = this%idxlocnode(n1) iposd = this%idxpakdiag(n1) - amatsln(iposd) = amatsln(iposd) + hcofval + call matrix_sln%add_value_pos(iposd, hcofval) rhs(iloc) = rhs(iloc) + rhsval end do end if @@ -297,7 +298,7 @@ subroutine mwt_fc_expanded(this, rhs, ia, idxglo, amatsln) call this%mwt_fwrt_term(j, n1, n2, rrate, rhsval, hcofval) iloc = this%idxlocnode(n1) iposd = this%idxpakdiag(n1) - amatsln(iposd) = amatsln(iposd) + hcofval + call matrix_sln%add_value_pos(iposd, hcofval) rhs(iloc) = rhs(iloc) + rhsval end do end if @@ -308,7 +309,7 @@ subroutine mwt_fc_expanded(this, rhs, ia, idxglo, amatsln) call this%mwt_rtmv_term(j, n1, n2, rrate, rhsval, hcofval) iloc = this%idxlocnode(n1) iposd = this%idxpakdiag(n1) - amatsln(iposd) = amatsln(iposd) + hcofval + call matrix_sln%add_value_pos(iposd, hcofval) rhs(iloc) = rhs(iloc) + rhsval end do end if @@ -319,7 +320,7 @@ subroutine mwt_fc_expanded(this, rhs, ia, idxglo, amatsln) call this%mwt_frtm_term(j, n1, n2, rrate, rhsval, hcofval) iloc = this%idxlocnode(n1) iposd = this%idxpakdiag(n1) - amatsln(iposd) = amatsln(iposd) + hcofval + call matrix_sln%add_value_pos(iposd, hcofval) rhs(iloc) = rhs(iloc) + rhsval end do end if diff --git a/src/Model/GroundWaterTransport/gwt1sft1.f90 b/src/Model/GroundWaterTransport/gwt1sft1.f90 index 8b91cb3fbc8..25ee2306bcb 100644 --- a/src/Model/GroundWaterTransport/gwt1sft1.f90 +++ b/src/Model/GroundWaterTransport/gwt1sft1.f90 @@ -41,6 +41,7 @@ module GwtSftModule use ObserveModule, only: ObserveType use GwtAptModule, only: GwtAptType, apt_process_obsID, & apt_process_obsID12 + use MatrixModule implicit none @@ -263,7 +264,7 @@ subroutine find_sft_package(this) return end subroutine find_sft_package - subroutine sft_fc_expanded(this, rhs, ia, idxglo, amatsln) + subroutine sft_fc_expanded(this, rhs, ia, idxglo, matrix_sln) ! ****************************************************************************** ! sft_fc_expanded -- this will be called from GwtAptType%apt_fc_expanded() ! in order to add matrix terms specifically for SFT @@ -277,7 +278,7 @@ subroutine sft_fc_expanded(this, rhs, ia, idxglo, amatsln) real(DP), dimension(:), intent(inout) :: rhs integer(I4B), dimension(:), intent(in) :: ia integer(I4B), dimension(:), intent(in) :: idxglo - real(DP), dimension(:), intent(inout) :: amatsln + class(MatrixBaseType), pointer :: matrix_sln ! -- local integer(I4B) :: j, n1, n2 integer(I4B) :: iloc @@ -293,7 +294,7 @@ subroutine sft_fc_expanded(this, rhs, ia, idxglo, amatsln) call this%sft_rain_term(j, n1, n2, rrate, rhsval, hcofval) iloc = this%idxlocnode(n1) iposd = this%idxpakdiag(n1) - amatsln(iposd) = amatsln(iposd) + hcofval + call matrix_sln%add_value_pos(iposd, hcofval) rhs(iloc) = rhs(iloc) + rhsval end do end if @@ -304,7 +305,7 @@ subroutine sft_fc_expanded(this, rhs, ia, idxglo, amatsln) call this%sft_evap_term(j, n1, n2, rrate, rhsval, hcofval) iloc = this%idxlocnode(n1) iposd = this%idxpakdiag(n1) - amatsln(iposd) = amatsln(iposd) + hcofval + call matrix_sln%add_value_pos(iposd, hcofval) rhs(iloc) = rhs(iloc) + rhsval end do end if @@ -315,7 +316,7 @@ subroutine sft_fc_expanded(this, rhs, ia, idxglo, amatsln) call this%sft_roff_term(j, n1, n2, rrate, rhsval, hcofval) iloc = this%idxlocnode(n1) iposd = this%idxpakdiag(n1) - amatsln(iposd) = amatsln(iposd) + hcofval + call matrix_sln%add_value_pos(iposd, hcofval) rhs(iloc) = rhs(iloc) + rhsval end do end if @@ -326,7 +327,7 @@ subroutine sft_fc_expanded(this, rhs, ia, idxglo, amatsln) call this%sft_iflw_term(j, n1, n2, rrate, rhsval, hcofval) iloc = this%idxlocnode(n1) iposd = this%idxpakdiag(n1) - amatsln(iposd) = amatsln(iposd) + hcofval + call matrix_sln%add_value_pos(iposd, hcofval) rhs(iloc) = rhs(iloc) + rhsval end do end if @@ -337,7 +338,7 @@ subroutine sft_fc_expanded(this, rhs, ia, idxglo, amatsln) call this%sft_outf_term(j, n1, n2, rrate, rhsval, hcofval) iloc = this%idxlocnode(n1) iposd = this%idxpakdiag(n1) - amatsln(iposd) = amatsln(iposd) + hcofval + call matrix_sln%add_value_pos(iposd, hcofval) rhs(iloc) = rhs(iloc) + rhsval end do end if diff --git a/src/Model/GroundWaterTransport/gwt1src1.f90 b/src/Model/GroundWaterTransport/gwt1src1.f90 index 137a931dd15..e372c38d175 100644 --- a/src/Model/GroundWaterTransport/gwt1src1.f90 +++ b/src/Model/GroundWaterTransport/gwt1src1.f90 @@ -7,6 +7,7 @@ module GwtSrcModule use TimeSeriesLinkModule, only: TimeSeriesLinkType, & GetTimeSeriesLinkFromList use BlockParserModule, only: BlockParserType + use MatrixModule ! implicit none ! @@ -166,7 +167,7 @@ subroutine src_cf(this, reset_mover) return end subroutine src_cf - subroutine src_fc(this, rhs, ia, idxglo, amatsln) + subroutine src_fc(this, rhs, ia, idxglo, matrix_sln) ! ************************************************************************** ! src_fc -- Copy rhs and hcof into solution rhs and amat ! ************************************************************************** @@ -178,7 +179,7 @@ subroutine src_fc(this, rhs, ia, idxglo, amatsln) real(DP), dimension(:), intent(inout) :: rhs integer(I4B), dimension(:), intent(in) :: ia integer(I4B), dimension(:), intent(in) :: idxglo - real(DP), dimension(:), intent(inout) :: amatsln + class(MatrixBaseType), pointer :: matrix_sln ! -- local integer(I4B) :: i, n, ipos ! -------------------------------------------------------------------------- @@ -193,7 +194,7 @@ subroutine src_fc(this, rhs, ia, idxglo, amatsln) n = this%nodelist(i) rhs(n) = rhs(n) + this%rhs(i) ipos = ia(n) - amatsln(idxglo(ipos)) = amatsln(idxglo(ipos)) + this%hcof(i) + call matrix_sln%add_value_pos(idxglo(ipos), this%hcof(i)) ! ! -- If mover is active and mass is being withdrawn, ! store available mass (as positive value). diff --git a/src/Model/GroundWaterTransport/gwt1ssm1.f90 b/src/Model/GroundWaterTransport/gwt1ssm1.f90 index 8a1ffca3071..d94daa5663c 100644 --- a/src/Model/GroundWaterTransport/gwt1ssm1.f90 +++ b/src/Model/GroundWaterTransport/gwt1ssm1.f90 @@ -18,6 +18,7 @@ module GwtSsmModule use GwtFmiModule, only: GwtFmiType use TableModule, only: TableType, table_cr use GwtSpcModule, only: GwtSpcType + use MatrixModule implicit none public :: GwtSsmType @@ -401,11 +402,11 @@ end subroutine get_ssm_conc !! updating the a matrix and right-hand side vector. !! !< - subroutine ssm_fc(this, amatsln, idxglo, rhs) + subroutine ssm_fc(this, matrix_sln, idxglo, rhs) ! -- modules ! -- dummy class(GwtSsmType) :: this - real(DP), dimension(:), intent(inout) :: amatsln + class(MatrixBaseType), pointer :: matrix_sln integer(I4B), intent(in), dimension(:) :: idxglo real(DP), intent(inout), dimension(:) :: rhs ! -- local @@ -430,7 +431,7 @@ subroutine ssm_fc(this, amatsln, idxglo, rhs) if (n <= 0) cycle call this%ssm_term(ip, i, rhsval=rhsval, hcofval=hcofval) idiag = idxglo(this%dis%con%ia(n)) - amatsln(idiag) = amatsln(idiag) + hcofval + call matrix_sln%add_value_pos(idiag, hcofval) rhs(n) = rhs(n) + rhsval ! end do diff --git a/src/Model/GroundWaterTransport/gwt1uzt1.f90 b/src/Model/GroundWaterTransport/gwt1uzt1.f90 index 2ed2a4f146a..9ed4a927f6c 100644 --- a/src/Model/GroundWaterTransport/gwt1uzt1.f90 +++ b/src/Model/GroundWaterTransport/gwt1uzt1.f90 @@ -35,7 +35,7 @@ module GwtUztModule use ObserveModule, only: ObserveType use GwtAptModule, only: GwtAptType, apt_process_obsID, & apt_process_obsID12 - + use MatrixModule implicit none public uzt_create @@ -249,7 +249,7 @@ subroutine find_uzt_package(this) return end subroutine find_uzt_package - subroutine uzt_fc_expanded(this, rhs, ia, idxglo, amatsln) + subroutine uzt_fc_expanded(this, rhs, ia, idxglo, matrix_sln) ! ****************************************************************************** ! uzt_fc_expanded -- this will be called from GwtAptType%apt_fc_expanded() ! in order to add matrix terms specifically for this package @@ -263,7 +263,7 @@ subroutine uzt_fc_expanded(this, rhs, ia, idxglo, amatsln) real(DP), dimension(:), intent(inout) :: rhs integer(I4B), dimension(:), intent(in) :: ia integer(I4B), dimension(:), intent(in) :: idxglo - real(DP), dimension(:), intent(inout) :: amatsln + class(MatrixBaseType), pointer :: matrix_sln ! -- local integer(I4B) :: j, n1, n2 integer(I4B) :: iloc @@ -279,7 +279,7 @@ subroutine uzt_fc_expanded(this, rhs, ia, idxglo, amatsln) call this%uzt_infl_term(j, n1, n2, rrate, rhsval, hcofval) iloc = this%idxlocnode(n1) iposd = this%idxpakdiag(n1) - amatsln(iposd) = amatsln(iposd) + hcofval + call matrix_sln%add_value_pos(iposd, hcofval) rhs(iloc) = rhs(iloc) + rhsval end do end if @@ -290,7 +290,7 @@ subroutine uzt_fc_expanded(this, rhs, ia, idxglo, amatsln) call this%uzt_rinf_term(j, n1, n2, rrate, rhsval, hcofval) iloc = this%idxlocnode(n1) iposd = this%idxpakdiag(n1) - amatsln(iposd) = amatsln(iposd) + hcofval + call matrix_sln%add_value_pos(iposd, hcofval) rhs(iloc) = rhs(iloc) + rhsval end do end if @@ -301,7 +301,7 @@ subroutine uzt_fc_expanded(this, rhs, ia, idxglo, amatsln) call this%uzt_uzet_term(j, n1, n2, rrate, rhsval, hcofval) iloc = this%idxlocnode(n1) iposd = this%idxpakdiag(n1) - amatsln(iposd) = amatsln(iposd) + hcofval + call matrix_sln%add_value_pos(iposd, hcofval) rhs(iloc) = rhs(iloc) + rhsval end do end if @@ -312,7 +312,7 @@ subroutine uzt_fc_expanded(this, rhs, ia, idxglo, amatsln) call this%uzt_ritm_term(j, n1, n2, rrate, rhsval, hcofval) iloc = this%idxlocnode(n1) iposd = this%idxpakdiag(n1) - amatsln(iposd) = amatsln(iposd) + hcofval + call matrix_sln%add_value_pos(iposd, hcofval) rhs(iloc) = rhs(iloc) + rhsval end do end if diff --git a/src/Model/ModelUtilities/BoundaryPackage.f90 b/src/Model/ModelUtilities/BoundaryPackage.f90 index c0f3d54ac7d..cdf5d98b4f8 100644 --- a/src/Model/ModelUtilities/BoundaryPackage.f90 +++ b/src/Model/ModelUtilities/BoundaryPackage.f90 @@ -31,6 +31,7 @@ module BndModule use BlockParserModule, only: BlockParserType use TableModule, only: TableType, table_cr use CharacterStringModule, only: CharacterStringType + use MatrixModule implicit none @@ -255,12 +256,11 @@ end subroutine bnd_ac !! MAW package. Base implementation that must be extended. !! !< - subroutine bnd_mc(this, moffset, iasln, jasln) + subroutine bnd_mc(this, moffset, matrix_sln) ! -- dummy variables class(BndType), intent(inout) :: this !< BndType object integer(I4B), intent(in) :: moffset !< solution matrix model offset - integer(I4B), dimension(:), intent(in) :: iasln !< solution CRS row pointers - integer(I4B), dimension(:), intent(in) :: jasln !< solution CRS column pointers + class(MatrixBaseType), pointer :: matrix_sln !< global system matrix ! ! -- return return @@ -471,13 +471,13 @@ end subroutine bnd_cf !! a specific boundary package. !! !< - subroutine bnd_fc(this, rhs, ia, idxglo, amatsln) + subroutine bnd_fc(this, rhs, ia, idxglo, matrix_sln) ! -- dummy variables class(BndType) :: this !< BndType object real(DP), dimension(:), intent(inout) :: rhs !< right-hand side vector for model integer(I4B), dimension(:), intent(in) :: ia !< solution CRS row pointers integer(I4B), dimension(:), intent(in) :: idxglo !< mapping vector for model (local) to solution (global) - real(DP), dimension(:), intent(inout) :: amatsln !< solution coefficient matrix + class(MatrixBaseType), pointer :: matrix_sln !< solution coefficient matrix ! -- local variables integer(I4B) :: i integer(I4B) :: n @@ -488,7 +488,7 @@ subroutine bnd_fc(this, rhs, ia, idxglo, amatsln) n = this%nodelist(i) rhs(n) = rhs(n) + this%rhs(i) ipos = ia(n) - amatsln(idxglo(ipos)) = amatsln(idxglo(ipos)) + this%hcof(i) + call matrix_sln%add_value_pos(idxglo(ipos), this%hcof(i)) end do ! ! -- return @@ -503,13 +503,13 @@ end subroutine bnd_fc !! package needs to add Newton-Raphson terms. !! !< - subroutine bnd_fn(this, rhs, ia, idxglo, amatsln) + subroutine bnd_fn(this, rhs, ia, idxglo, matrix_sln) ! -- dummy variables class(BndType) :: this !< BndType object real(DP), dimension(:), intent(inout) :: rhs !< right-hand side vector for model integer(I4B), dimension(:), intent(in) :: ia !< solution CRS row pointers integer(I4B), dimension(:), intent(in) :: idxglo !< mapping vector for model (local) to solution (global) - real(DP), dimension(:), intent(inout) :: amatsln !< solution coefficient matrix + class(MatrixBaseType), pointer :: matrix_sln !< solution coefficient matrix ! ! -- No addition terms for Newton-Raphson with constant conductance ! boundary conditions diff --git a/src/Model/ModelUtilities/DiscretizationBase.f90 b/src/Model/ModelUtilities/DiscretizationBase.f90 index c178a62caf0..8e77a605987 100644 --- a/src/Model/ModelUtilities/DiscretizationBase.f90 +++ b/src/Model/ModelUtilities/DiscretizationBase.f90 @@ -14,6 +14,7 @@ module BaseDisModule use MemoryHelperModule, only: create_mem_path use TdisModule, only: kstp, kper, pertim, totim, delt use TimeSeriesManagerModule, only: TimeSeriesManagerType + use MatrixModule implicit none @@ -157,7 +158,7 @@ subroutine dis_ac(this, moffset, sparse) return end subroutine dis_ac - subroutine dis_mc(this, moffset, idxglo, iasln, jasln) + subroutine dis_mc(this, moffset, idxglo, matrix_sln) ! ****************************************************************************** ! dis_mc -- Map the positions of cell connections in the numerical solution ! coefficient matrix. @@ -170,10 +171,9 @@ subroutine dis_mc(this, moffset, idxglo, iasln, jasln) class(DisBaseType) :: this integer(I4B), intent(in) :: moffset integer(I4B), dimension(:), intent(inout) :: idxglo - integer(I4B), dimension(:), intent(in) :: iasln - integer(I4B), dimension(:), intent(in) :: jasln + class(MatrixBaseType), pointer :: matrix_sln ! -- local - integer(I4B) :: i, j, ipos, ipossln, iglo, jglo + integer(I4B) :: i, j, ipos, iglo, jglo ! ------------------------------------------------------------------------------ ! do i = 1, this%nodes @@ -181,12 +181,7 @@ subroutine dis_mc(this, moffset, idxglo, iasln, jasln) do ipos = this%con%ia(i), this%con%ia(i + 1) - 1 j = this%con%ja(ipos) jglo = j + moffset - searchloop: do ipossln = iasln(iglo), iasln(iglo + 1) - 1 - if (jglo == jasln(ipossln)) then - idxglo(ipos) = ipossln - exit searchloop - end if - end do searchloop + idxglo(ipos) = matrix_sln%get_position(iglo, jglo) end do end do ! diff --git a/src/Model/ModelUtilities/Xt3dInterface.f90 b/src/Model/ModelUtilities/Xt3dInterface.f90 index 9e57db4443e..b6ca168f187 100644 --- a/src/Model/ModelUtilities/Xt3dInterface.f90 +++ b/src/Model/ModelUtilities/Xt3dInterface.f90 @@ -4,6 +4,7 @@ module Xt3dModule use ConstantsModule, only: DZERO, DHALF, DONE, LENMEMPATH use BaseDisModule, only: DisBaseType use MemoryHelperModule, only: create_mem_path + use MatrixModule implicit none public Xt3dType @@ -208,7 +209,7 @@ subroutine xt3d_ac(this, moffset, sparse) return end subroutine xt3d_ac - subroutine xt3d_mc(this, moffset, iasln, jasln) + subroutine xt3d_mc(this, moffset, matrix_sln) ! ****************************************************************************** ! xt3d_mc -- Map connections and construct iax, jax, and idxglox ! ****************************************************************************** @@ -220,10 +221,10 @@ subroutine xt3d_mc(this, moffset, iasln, jasln) ! -- dummy class(Xt3dType) :: this integer(I4B), intent(in) :: moffset - integer(I4B), dimension(:), intent(in) :: iasln - integer(I4B), dimension(:), intent(in) :: jasln + class(MatrixBaseType), pointer :: matrix_sln ! -- local - integer(I4B) :: i, j, iglo, jglo, jjg, niax, njax, ipos + integer(I4B) :: i, j, iglo, jglo, niax, njax, ipos + integer(I4B) :: ipos_sln, icol_first, icol_last integer(I4B) :: jj_xt3d integer(I4B) :: igfirstnod, iglastnod logical :: isextnbr @@ -254,13 +255,13 @@ subroutine xt3d_mc(this, moffset, iasln, jasln) ! ! -- calculate global node number iglo = i + moffset - ! - ! -- loop over neighbors in global matrix - do jjg = iasln(iglo), iasln(iglo + 1) - 1 + icol_first = matrix_sln%get_first_col_pos(iglo) + icol_last = matrix_sln%get_last_col_pos(iglo) + do ipos_sln = icol_first, icol_last ! ! -- if jglo is in a different model, then it cannot be an extended ! neighbor, so skip over it - jglo = jasln(jjg) + jglo = matrix_sln%get_column(ipos_sln) if (jglo < igfirstnod .or. jglo > iglastnod) then cycle end if @@ -279,8 +280,8 @@ subroutine xt3d_mc(this, moffset, iasln, jasln) ! ! -- if an extended neighbor, add it to jax and idxglox if (isextnbr) then - this%jax(ipos) = jasln(jjg) - moffset - this%idxglox(ipos) = jjg + this%jax(ipos) = matrix_sln%get_column(ipos_sln) - moffset + this%idxglox(ipos) = ipos_sln ipos = ipos + 1 end if end do @@ -405,7 +406,7 @@ subroutine xt3d_ar(this, ibound, k11, ik33, k33, sat, ik22, k22, iangle1, & return end subroutine xt3d_ar - subroutine xt3d_fc(this, kiter, njasln, amat, idxglo, rhs, hnew) + subroutine xt3d_fc(this, kiter, matrix_sln, idxglo, rhs, hnew) ! ****************************************************************************** ! xt3d_fc -- Formulate ! ****************************************************************************** @@ -417,8 +418,7 @@ subroutine xt3d_fc(this, kiter, njasln, amat, idxglo, rhs, hnew) ! -- dummy class(Xt3dType) :: this integer(I4B) :: kiter - integer(I4B), intent(in) :: njasln - real(DP), dimension(njasln), intent(inout) :: amat + class(MatrixBaseType), pointer :: matrix_sln integer(I4B), intent(in), dimension(:) :: idxglo real(DP), intent(inout), dimension(:) :: rhs real(DP), intent(inout), dimension(:) :: hnew @@ -447,10 +447,10 @@ subroutine xt3d_fc(this, kiter, njasln, amat, idxglo, rhs, hnew) nja = this%dis%con%nja if (this%lamatsaved) then do i = 1, this%dis%con%nja - amat(idxglo(i)) = amat(idxglo(i)) + this%amatpc(i) + call matrix_sln%add_value_pos(idxglo(i), this%amatpc(i)) end do do i = 1, this%numextnbrs - amat(this%idxglox(i)) = amat(this%idxglox(i)) + this%amatpcx(i) + call matrix_sln%add_value_pos(this%idxglox(i), this%amatpcx(i)) end do end if ! @@ -515,19 +515,19 @@ subroutine xt3d_fc(this, kiter, njasln, amat, idxglo, rhs, hnew) chat1j = chat1j * ar01 end if ! -- Contribute to rows for cells 0 and 1. - amat(idxglo(ii00)) = amat(idxglo(ii00)) - chat01 - amat(idxglo(ii01)) = amat(idxglo(ii01)) + chat01 - amat(idxglo(ii11)) = amat(idxglo(ii11)) - chat01 - amat(idxglo(ii10)) = amat(idxglo(ii10)) + chat01 + call matrix_sln%add_value_pos(idxglo(ii00), -chat01) + call matrix_sln%add_value_pos(idxglo(ii01), chat01) + call matrix_sln%add_value_pos(idxglo(ii11), -chat01) + call matrix_sln%add_value_pos(idxglo(ii10), chat01) if (this%ixt3d == 1) then - call this%xt3d_amat_nbrs(nodes, n, ii00, nnbr0, nja, njasln, & - inbr0, amat, idxglo, chati0) - call this%xt3d_amat_nbrnbrs(nodes, n, m, ii01, nnbr1, nja, njasln, & - inbr1, amat, idxglo, chat1j) - call this%xt3d_amat_nbrs(nodes, m, ii11, nnbr1, nja, njasln, & - inbr1, amat, idxglo, chat1j) - call this%xt3d_amat_nbrnbrs(nodes, m, n, ii10, nnbr0, nja, njasln, & - inbr0, amat, idxglo, chati0) + call this%xt3d_amat_nbrs(nodes, n, ii00, nnbr0, nja, matrix_sln, & + inbr0, idxglo, chati0) + call this%xt3d_amat_nbrnbrs(nodes, n, m, ii01, nnbr1, nja, matrix_sln, & + inbr1, idxglo, chat1j) + call this%xt3d_amat_nbrs(nodes, m, ii11, nnbr1, nja, matrix_sln, & + inbr1, idxglo, chat1j) + call this%xt3d_amat_nbrnbrs(nodes, m, n, ii10, nnbr0, nja, matrix_sln, & + inbr0, idxglo, chati0) else call this%xt3d_rhs(nodes, n, m, nnbr0, inbr0, chati0, hnew, rhs) call this%xt3d_rhs(nodes, m, n, nnbr1, inbr1, chat1j, hnew, rhs) @@ -626,7 +626,7 @@ subroutine xt3d_fcpc(this, nodes, lsat) return end subroutine xt3d_fcpc - subroutine xt3d_fhfb(this, kiter, nodes, nja, njasln, amat, idxglo, rhs, hnew, & + subroutine xt3d_fhfb(this, kiter, nodes, nja, matrix_sln, idxglo, rhs, hnew, & n, m, condhfb) ! ****************************************************************************** ! xt3d_fhfb -- Formulate HFB correction @@ -641,9 +641,8 @@ subroutine xt3d_fhfb(this, kiter, nodes, nja, njasln, amat, idxglo, rhs, hnew, & integer(I4B) :: kiter integer(I4B), intent(in) :: nodes integer(I4B), intent(in) :: nja - integer(I4B), intent(in) :: njasln + class(MatrixBaseType), pointer :: matrix_sln integer(I4B) :: n, m - real(DP), dimension(njasln), intent(inout) :: amat integer(I4B), intent(in), dimension(nja) :: idxglo real(DP), intent(inout), dimension(nodes) :: rhs real(DP), intent(inout), dimension(nodes) :: hnew @@ -728,19 +727,19 @@ subroutine xt3d_fhfb(this, kiter, nodes, nja, njasln, amat, idxglo, rhs, hnew, & chat1j = chat1j * ar01 end if ! -- Contribute to rows for cells 0 and 1. - amat(idxglo(ii00)) = amat(idxglo(ii00)) - chat01 - amat(idxglo(ii01)) = amat(idxglo(ii01)) + chat01 - amat(idxglo(ii11)) = amat(idxglo(ii11)) - chat01 - amat(idxglo(ii10)) = amat(idxglo(ii10)) + chat01 + call matrix_sln%add_value_pos(idxglo(ii00), -chat01) + call matrix_sln%add_value_pos(idxglo(ii01), chat01) + call matrix_sln%add_value_pos(idxglo(ii11), -chat01) + call matrix_sln%add_value_pos(idxglo(ii10), chat01) if (this%ixt3d == 1) then - call this%xt3d_amat_nbrs(nodes, n, ii00, nnbr0, nja, njasln, & - inbr0, amat, idxglo, chati0) - call this%xt3d_amat_nbrnbrs(nodes, n, m, ii01, nnbr1, nja, njasln, & - inbr1, amat, idxglo, chat1j) - call this%xt3d_amat_nbrs(nodes, m, ii11, nnbr1, nja, njasln, & - inbr1, amat, idxglo, chat1j) - call this%xt3d_amat_nbrnbrs(nodes, m, n, ii10, nnbr0, nja, njasln, & - inbr0, amat, idxglo, chati0) + call this%xt3d_amat_nbrs(nodes, n, ii00, nnbr0, nja, matrix_sln, & + inbr0, idxglo, chati0) + call this%xt3d_amat_nbrnbrs(nodes, n, m, ii01, nnbr1, nja, matrix_sln, & + inbr1, idxglo, chat1j) + call this%xt3d_amat_nbrs(nodes, m, ii11, nnbr1, nja, matrix_sln, & + inbr1, idxglo, chat1j) + call this%xt3d_amat_nbrnbrs(nodes, m, n, ii10, nnbr0, nja, matrix_sln, & + inbr0, idxglo, chati0) else call this%xt3d_rhs(nodes, n, m, nnbr0, inbr0, chati0, hnew, rhs) call this%xt3d_rhs(nodes, m, n, nnbr1, inbr1, chat1j, hnew, rhs) @@ -750,7 +749,7 @@ subroutine xt3d_fhfb(this, kiter, nodes, nja, njasln, amat, idxglo, rhs, hnew, & return end subroutine xt3d_fhfb - subroutine xt3d_fn(this, kiter, nodes, nja, njasln, amat, idxglo, rhs, hnew) + subroutine xt3d_fn(this, kiter, nodes, nja, matrix_sln, idxglo, rhs, hnew) ! ****************************************************************************** ! xt3d_fn -- Fill Newton terms for xt3d ! ****************************************************************************** @@ -764,8 +763,7 @@ subroutine xt3d_fn(this, kiter, nodes, nja, njasln, amat, idxglo, rhs, hnew) integer(I4B) :: kiter integer(I4B), intent(in) :: nodes integer(I4B), intent(in) :: nja - integer(I4B), intent(in) :: njasln - real(DP), dimension(njasln), intent(inout) :: amat + class(MatrixBaseType), pointer :: matrix_sln integer(I4B), intent(in), dimension(nja) :: idxglo real(DP), intent(inout), dimension(nodes) :: rhs real(DP), intent(inout), dimension(nodes) :: hnew @@ -826,18 +824,18 @@ subroutine xt3d_fn(this, kiter, nodes, nja, njasln, amat, idxglo, rhs, hnew) ! fill Jacobian for n being the upstream node if (iups == n) then ! fill in row of n - amat(idxglo(ii00)) = amat(idxglo(ii00)) + term + call matrix_sln%add_value_pos(idxglo(ii00), term) rhs(n) = rhs(n) + term * hnew(n) ! fill in row of m - amat(idxglo(ii10)) = amat(idxglo(ii10)) - term + call matrix_sln%add_value_pos(idxglo(ii10), -term) rhs(m) = rhs(m) - term * hnew(n) ! fill Jacobian for m being the upstream node else ! fill in row of n - amat(idxglo(ii01)) = amat(idxglo(ii01)) + term + call matrix_sln%add_value_pos(idxglo(ii01), term) rhs(n) = rhs(n) + term * hnew(m) ! fill in row of m - amat(idxglo(ii11)) = amat(idxglo(ii11)) - term + call matrix_sln%add_value_pos(idxglo(ii11), -term) rhs(m) = rhs(m) - term * hnew(m) end if end do @@ -1488,7 +1486,7 @@ subroutine xt3d_areas(this, nodes, n, m, jjs01, lsat, ar01, ar10, hnew) end subroutine xt3d_areas subroutine xt3d_amat_nbrs(this, nodes, n, idiag, nnbr, nja, & - njasln, inbr, amat, idxglo, chat) + matrix_sln, inbr, idxglo, chat) ! ****************************************************************************** ! xt3d_amat_nbrs -- Add contributions from neighbors to amat. ! ****************************************************************************** @@ -1499,10 +1497,10 @@ subroutine xt3d_amat_nbrs(this, nodes, n, idiag, nnbr, nja, & ! -- dummy class(Xt3dType) :: this integer(I4B), intent(in) :: nodes - integer(I4B) :: n, idiag, nnbr, nja, njasln + integer(I4B) :: n, idiag, nnbr, nja + class(MatrixBaseType), pointer :: matrix_sln integer(I4B), dimension(this%nbrmax) :: inbr integer(I4B), intent(in), dimension(nja) :: idxglo - real(DP), dimension(njasln), intent(inout) :: amat real(DP), dimension(this%nbrmax) :: chat ! -- local integer(I4B) :: iil, iii @@ -1511,8 +1509,8 @@ subroutine xt3d_amat_nbrs(this, nodes, n, idiag, nnbr, nja, & do iil = 1, nnbr if (inbr(iil) .ne. 0) then iii = this%dis%con%ia(n) + iil - amat(idxglo(idiag)) = amat(idxglo(idiag)) - chat(iil) - amat(idxglo(iii)) = amat(idxglo(iii)) + chat(iil) + call matrix_sln%add_value_pos(idxglo(idiag), -chat(iil)) + call matrix_sln%add_value_pos(idxglo(iii), chat(iil)) end if end do ! @@ -1520,7 +1518,7 @@ subroutine xt3d_amat_nbrs(this, nodes, n, idiag, nnbr, nja, & end subroutine xt3d_amat_nbrs subroutine xt3d_amat_nbrnbrs(this, nodes, n, m, ii01, nnbr, nja, & - njasln, inbr, amat, idxglo, chat) + matrix_sln, inbr, idxglo, chat) ! ****************************************************************************** ! xt3d_amat_nbrnbrs -- Add contributions from neighbors of neighbor to amat. ! ****************************************************************************** @@ -1531,10 +1529,10 @@ subroutine xt3d_amat_nbrnbrs(this, nodes, n, m, ii01, nnbr, nja, & ! -- dummy class(Xt3dType) :: this integer(I4B), intent(in) :: nodes - integer(I4B) :: n, m, ii01, nnbr, nja, njasln + integer(I4B) :: n, m, ii01, nnbr, nja + class(MatrixBaseType), pointer :: matrix_sln integer(I4B), dimension(this%nbrmax) :: inbr integer(I4B), intent(in), dimension(nja) :: idxglo - real(DP), dimension(njasln), intent(inout) :: amat real(DP), dimension(this%nbrmax) :: chat ! -- local integer(I4B) :: iil, iii, jjj, iixjjj, iijjj @@ -1542,15 +1540,15 @@ subroutine xt3d_amat_nbrnbrs(this, nodes, n, m, ii01, nnbr, nja, & ! do iil = 1, nnbr if (inbr(iil) .ne. 0) then - amat(idxglo(ii01)) = amat(idxglo(ii01)) + chat(iil) + call matrix_sln%add_value_pos(idxglo(ii01), chat(iil)) iii = this%dis%con%ia(m) + iil jjj = this%dis%con%ja(iii) call this%xt3d_get_iinmx(n, jjj, iixjjj) if (iixjjj .ne. 0) then - amat(this%idxglox(iixjjj)) = amat(this%idxglox(iixjjj)) - chat(iil) + call matrix_sln%add_value_pos(this%idxglox(iixjjj), -chat(iil)) else call this%xt3d_get_iinm(n, jjj, iijjj) - amat(idxglo(iijjj)) = amat(idxglo(iijjj)) - chat(iil) + call matrix_sln%add_value_pos(idxglo(iijjj), -chat(iil)) end if end if end do diff --git a/src/Model/NumericalModel.f90 b/src/Model/NumericalModel.f90 index ecbebe9b515..ea6d6dfa5f2 100644 --- a/src/Model/NumericalModel.f90 +++ b/src/Model/NumericalModel.f90 @@ -7,6 +7,7 @@ module NumericalModelModule use SparseModule, only: sparsematrix use TimeArraySeriesManagerModule, only: TimeArraySeriesManagerType use ListModule, only: ListType + use MatrixModule implicit none private @@ -87,10 +88,9 @@ subroutine model_ac(this, sparse) type(sparsematrix), intent(inout) :: sparse end subroutine model_ac - subroutine model_mc(this, iasln, jasln) + subroutine model_mc(this, matrix_sln) class(NumericalModelType) :: this - integer(I4B), dimension(:), intent(in) :: iasln - integer(I4B), dimension(:), intent(in) :: jasln + class(MatrixBaseType), pointer :: matrix_sln end subroutine model_mc subroutine model_ar(this) @@ -110,11 +110,10 @@ subroutine model_cf(this, kiter) integer(I4B), intent(in) :: kiter end subroutine model_cf - subroutine model_fc(this, kiter, amatsln, njasln, inwtflag) + subroutine model_fc(this, kiter, matrix_sln, inwtflag) class(NumericalModelType) :: this integer(I4B), intent(in) :: kiter - integer(I4B), intent(in) :: njasln - real(DP), dimension(njasln), intent(inout) :: amatsln + class(MatrixBaseType), pointer :: matrix_sln integer(I4B), intent(in) :: inwtflag end subroutine model_fc @@ -124,26 +123,21 @@ subroutine model_ptcchk(this, iptc) iptc = 0 end subroutine model_ptcchk - subroutine model_ptc(this, kiter, neqsln, njasln, & - ia, ja, x, rhs, amatsln, iptc, ptcf) + subroutine model_ptc(this, kiter, neqsln, matrix, x, rhs, iptc, ptcf) class(NumericalModelType) :: this integer(I4B), intent(in) :: kiter integer(I4B), intent(in) :: neqsln - integer(I4B), intent(in) :: njasln - integer(I4B), dimension(neqsln + 1), intent(in) :: ia - integer(I4B), dimension(njasln), intent(in) :: ja + class(MatrixBaseType), pointer :: matrix real(DP), dimension(neqsln), intent(in) :: x real(DP), dimension(neqsln), intent(in) :: rhs - real(DP), dimension(njasln), intent(in) :: amatsln integer(I4B), intent(inout) :: iptc real(DP), intent(inout) :: ptcf end subroutine model_ptc - subroutine model_nr(this, kiter, amatsln, njasln, inwtflag) + subroutine model_nr(this, kiter, matrix, inwtflag) class(NumericalModelType) :: this integer(I4B), intent(in) :: kiter - integer(I4B), intent(in) :: njasln - real(DP), dimension(njasln), intent(inout) :: amatsln + class(MatrixBaseType), pointer :: matrix integer(I4B), intent(in) :: inwtflag end subroutine model_nr diff --git a/src/Solution/LinearMethods/ims8linear.f90 b/src/Solution/LinearMethods/ims8linear.f90 index c1015d69dfe..b8a3b93835f 100644 --- a/src/Solution/LinearMethods/ims8linear.f90 +++ b/src/Solution/LinearMethods/ims8linear.f90 @@ -12,6 +12,8 @@ MODULE IMSLinearModule ims_base_scale, ims_base_pcu, & ims_base_residual use BlockParserModule, only: BlockParserType + use MatrixModule + use SparseMatrixModule IMPLICIT NONE private @@ -104,8 +106,7 @@ MODULE IMSLinearModule !! !< SUBROUTINE imslinear_ar(this, NAME, parser, IOUT, IPRIMS, MXITER, IFDPARAM, & - IMSLINEARM, NEQ, NJA, IA, JA, AMAT, RHS, X, & - NINNER, LFINDBLOCK) + IMSLINEARM, NEQ, matrix, RHS, X, NINNER, LFINDBLOCK) ! -- modules use MemoryManagerModule, only: mem_allocate use MemoryHelperModule, only: create_mem_path @@ -121,16 +122,14 @@ SUBROUTINE imslinear_ar(this, NAME, parser, IOUT, IPRIMS, MXITER, IFDPARAM, & integer(I4B), INTENT(IN) :: IFDPARAM !< complexity option integer(I4B), INTENT(INOUT) :: IMSLINEARM !< linear method option (1) CG (2) BICGSTAB integer(I4B), TARGET, INTENT(IN) :: NEQ !< number of equations - integer(I4B), TARGET, INTENT(IN) :: NJA !< number of non-zero entries in the coefficient matrix - integer(I4B), DIMENSION(NEQ + 1), TARGET, INTENT(IN) :: IA !< pointer to the start of a row in the coefficient matrix - integer(I4B), DIMENSION(NJA), TARGET, INTENT(IN) :: JA !< column pointer - real(DP), DIMENSION(NJA), TARGET, INTENT(IN) :: AMAT !< coefficient matrix + class(MatrixBaseType), pointer :: matrix real(DP), DIMENSION(NEQ), TARGET, INTENT(INOUT) :: RHS !< right-hand side real(DP), DIMENSION(NEQ), TARGET, INTENT(INOUT) :: X !< dependent variables integer(I4B), TARGET, INTENT(INOUT) :: NINNER !< maximum number of inner iterations integer(I4B), INTENT(IN), OPTIONAL :: LFINDBLOCK !< flag indicating if the linear block is present (1) or missing (0) ! -- local variables + class(SparseMatrixType), pointer :: sparse_matrix => null() LOGICAL :: lreaddata character(len=LINELENGTH) :: errmsg character(len=LINELENGTH) :: warnmsg @@ -146,6 +145,11 @@ SUBROUTINE imslinear_ar(this, NAME, parser, IOUT, IPRIMS, MXITER, IFDPARAM, & integer(I4B) :: iwlu integer(I4B) :: iwk ! + select type (matrix) + class is (SparseMatrixType) + sparse_matrix => matrix + end select + ! ! -- SET LREADDATA IF (PRESENT(LFINDBLOCK)) THEN IF (LFINDBLOCK < 1) THEN @@ -163,10 +167,10 @@ SUBROUTINE imslinear_ar(this, NAME, parser, IOUT, IPRIMS, MXITER, IFDPARAM, & ! -- SET POINTERS TO SOLUTION STORAGE this%IPRIMS => IPRIMS this%NEQ => NEQ - this%NJA => NJA - this%IA => IA - this%JA => JA - this%AMAT => AMAT + this%NJA => sparse_matrix%nja + this%IA => sparse_matrix%ia + this%JA => sparse_matrix%ja + this%AMAT => sparse_matrix%amat this%RHS => RHS this%X => X ! @@ -407,7 +411,7 @@ SUBROUTINE imslinear_ar(this, NAME, parser, IOUT, IPRIMS, MXITER, IFDPARAM, & ELSE iwk = 0 DO n = 1, NEQ - i = IA(n + 1) - IA(n) + i = this%IA(n + 1) - this%IA(n) IF (i > iwk) THEN iwk = i END IF diff --git a/src/Solution/NumericalSolution.f90 b/src/Solution/NumericalSolution.f90 index e820e01afad..d2c9ad9c75b 100644 --- a/src/Solution/NumericalSolution.f90 +++ b/src/Solution/NumericalSolution.f90 @@ -19,6 +19,7 @@ module NumericalSolutionModule use BaseSolutionModule, only: BaseSolutionType, AddBaseSolutionToList use ListModule, only: ListType use ListsModule, only: basesolutionlist + use InputOutputModule, only: getunit use NumericalModelModule, only: NumericalModelType, & AddNumericalModelToList, & GetNumericalModelFromList @@ -30,6 +31,8 @@ module NumericalSolutionModule use BlockParserModule, only: BlockParserType use IMSLinearModule use DistributedDataModule + use MatrixModule + use SparseMatrixModule implicit none private @@ -49,10 +52,7 @@ module NumericalSolutionModule real(DP), pointer :: ttsoln !< timer - total solution time integer(I4B), pointer :: isymmetric => null() !< flag indicating if matrix symmetry is required integer(I4B), pointer :: neq => null() !< number of equations - integer(I4B), pointer :: nja => null() !< number of non-zero entries - integer(I4B), dimension(:), pointer, contiguous :: ia => null() !< CRS row pointers - integer(I4B), dimension(:), pointer, contiguous :: ja => null() !< CRS column pointers - real(DP), dimension(:), pointer, contiguous :: amat => null() !< coefficient matrix + class(MatrixBaseType), pointer :: system_matrix !< sparse A-matrix for the system of equations real(DP), dimension(:), pointer, contiguous :: rhs => null() !< right-hand side vector real(DP), dimension(:), pointer, contiguous :: x => null() !< dependent-variable vector integer(I4B), dimension(:), pointer, contiguous :: active => null() !< active cell array @@ -195,6 +195,7 @@ subroutine solution_create(filename, id) type(NumericalSolutionType), pointer :: solution => null() class(BaseSolutionType), pointer :: solbase => null() character(len=LENSOLUTIONNAME) :: solutionname + class(SparseMatrixType), pointer :: matrix_impl ! ! -- Create a new solution and add it to the basesolutionlist container allocate (solution) @@ -206,6 +207,9 @@ subroutine solution_create(filename, id) allocate (solution%modellist) allocate (solution%exchangelist) ! + allocate (matrix_impl) + solution%system_matrix => matrix_impl + ! call solution%allocate_scalars() ! call AddBaseSolutionToList(basesolutionlist, solbase) @@ -247,7 +251,6 @@ subroutine allocate_scalars(this) call mem_allocate(this%ttsoln, 'TTSOLN', this%memoryPath) call mem_allocate(this%isymmetric, 'ISYMMETRIC', this%memoryPath) call mem_allocate(this%neq, 'NEQ', this%memoryPath) - call mem_allocate(this%nja, 'NJA', this%memoryPath) call mem_allocate(this%dvclose, 'DVCLOSE', this%memoryPath) call mem_allocate(this%bigchold, 'BIGCHOLD', this%memoryPath) call mem_allocate(this%bigch, 'BIGCH', this%memoryPath) @@ -293,7 +296,6 @@ subroutine allocate_scalars(this) this%ttform = DZERO this%ttsoln = DZERO this%neq = 0 - this%nja = 0 this%dvclose = DZERO this%bigchold = DZERO this%bigch = DZERO @@ -354,7 +356,6 @@ subroutine allocate_arrays(this) this%convnmod = this%modellist%Count() ! ! -- allocate arrays - call mem_allocate(this%ia, this%neq + 1, 'IA', this%memoryPath) call mem_allocate(this%x, this%neq, 'X', this%memoryPath) call mem_allocate(this%rhs, this%neq, 'RHS', this%memoryPath) call mem_allocate(this%active, this%neq, 'IACTIVE', this%memoryPath) @@ -860,9 +861,8 @@ subroutine sln_ar(this) call this%imslinear%imslinear_allocate(this%name, this%parser, IOUT, & this%iprims, this%mxiter, & ifdparam, imslinear, & - this%neq, this%nja, this%ia, & - this%ja, this%amat, this%rhs, & - this%x, this%nitermax) + this%neq, this%system_matrix, & + this%rhs, this%x, this%nitermax) WRITE (IOUT, *) if (imslinear .eq. 1) then this%isymmetric = 1 @@ -1145,6 +1145,8 @@ subroutine sln_da(this) call this%exchangelist%Clear() deallocate (this%modellist) deallocate (this%exchangelist) + call this%system_matrix%destroy() + deallocate (this%system_matrix) ! ! -- character arrays deallocate (this%caccel) @@ -1164,9 +1166,6 @@ subroutine sln_da(this) end if ! ! -- arrays - call mem_deallocate(this%ja) - call mem_deallocate(this%amat) - call mem_deallocate(this%ia) call mem_deallocate(this%x) call mem_deallocate(this%rhs) call mem_deallocate(this%active) @@ -1195,7 +1194,6 @@ subroutine sln_da(this) call mem_deallocate(this%ttsoln) call mem_deallocate(this%isymmetric) call mem_deallocate(this%neq) - call mem_deallocate(this%nja) call mem_deallocate(this%dvclose) call mem_deallocate(this%bigchold) call mem_deallocate(this%bigch) @@ -1530,28 +1528,20 @@ subroutine solve(this, kiter) ! (re)build the solution matrix call this%sln_buildsystem(kiter, inewton=1) - ! - ! -- Add exchange Newton-Raphson terms to solution - do ic = 1, this%exchangelist%Count() - cp => GetNumericalExchangeFromList(this%exchangelist, ic) - call cp%exg_nr(kiter, this%ia, this%amat) - end do ! ! -- Calculate pseudo-transient continuation factor for each model iptc = 0 ptcf = DZERO do im = 1, this%modellist%Count() mp => GetNumericalModelFromList(this%modellist, im) - call mp%model_ptc(kiter, this%neq, this%nja, & - this%ia, this%ja, this%x, & - this%rhs, this%amat, & - iptc, ptcf) + call mp%model_ptc(kiter, this%neq, this%system_matrix, & + this%x, this%rhs, iptc, ptcf) end do ! ! -- Add model Newton-Raphson terms to solution do im = 1, this%modellist%Count() mp => GetNumericalModelFromList(this%modellist, im) - call mp%model_nr(kiter, this%amat, this%nja, 1) + call mp%model_nr(kiter, this%system_matrix, 1) end do call code_timer(1, ttform, this%ttform) ! @@ -1928,13 +1918,13 @@ subroutine sln_buildsystem(this, kiter, inewton) ! -- Add exchange coefficients to the solution do ic = 1, this%exchangelist%Count() cp => GetNumericalExchangeFromList(this%exchangelist, ic) - call cp%exg_fc(kiter, this%ia, this%amat, this%rhs, inewton) + call cp%exg_fc(kiter, this%system_matrix, this%rhs, inewton) end do ! ! -- Add model coefficients to the solution do im = 1, this%modellist%Count() mp => GetNumericalModelFromList(this%modellist, im) - call mp%model_fc(kiter, this%amat, this%nja, inewton) + call mp%model_fc(kiter, this%system_matrix, inewton) end do end subroutine sln_buildsystem @@ -2170,19 +2160,22 @@ subroutine save(this, filename) integer(I4B) :: inunit ! ------------------------------------------------------------------------------ ! - inunit = getunit() - open (unit=inunit, file=filename, status='unknown') - write (inunit, *) 'ia' - write (inunit, *) this%ia - write (inunit, *) 'ja' - write (inunit, *) this%ja - write (inunit, *) 'amat' - write (inunit, *) this%amat - write (inunit, *) 'rhs' - write (inunit, *) this%rhs - write (inunit, *) 'x' - write (inunit, *) this%x - close (inunit) + select type (spm => this%system_matrix) + class is (SparseMatrixType) + inunit = getunit() + open (unit=inunit, file=filename, status='unknown') + write (inunit, *) 'ia' + write (inunit, *) spm%ia + write (inunit, *) 'ja' + write (inunit, *) spm%ja + write (inunit, *) 'amat' + write (inunit, *) spm%amat + write (inunit, *) 'rhs' + write (inunit, *) this%rhs + write (inunit, *) 'x' + write (inunit, *) this%x + close (inunit) + end select ! ! -- return return @@ -2278,7 +2271,6 @@ subroutine sln_connect(this) class(NumericalExchangeType), pointer :: cp => null() integer(I4B) :: im integer(I4B) :: ic - integer(I4B) :: ierror ! ! -- Add internal model connections to sparse do im = 1, this%modellist%Count() @@ -2294,11 +2286,8 @@ subroutine sln_connect(this) ! ! -- The number of non-zero array values are now known so ! -- ia and ja can be created from sparse. then destroy sparse - this%nja = this%sparse%nnz - call mem_allocate(this%ja, this%nja, 'JA', this%name) - call mem_allocate(this%amat, this%nja, 'AMAT', this%name) call this%sparse%sort() - call this%sparse%filliaja(this%ia, this%ja, ierror) + call this%system_matrix%create(this%sparse, this%name) call this%sparse%destroy() ! ! -- Create mapping arrays for each model. Mapping assumes @@ -2306,13 +2295,13 @@ subroutine sln_connect(this) ! -- however, rows do not need to be sorted. do im = 1, this%modellist%Count() mp => GetNumericalModelFromList(this%modellist, im) - call mp%model_mc(this%ia, this%ja) + call mp%model_mc(this%system_matrix) end do ! ! -- Create arrays for mapping exchange connections to global solution do ic = 1, this%exchangelist%Count() cp => GetNumericalExchangeFromList(this%exchangelist, ic) - call cp%exg_mc(this%ia, this%ja) + call cp%exg_mc(this%system_matrix) end do ! ! -- return @@ -2332,9 +2321,7 @@ subroutine sln_reset(this) integer(I4B) :: i ! ! -- reset the solution - do i = 1, this%nja - this%amat(i) = DZERO - end do + call this%system_matrix%zero_entries() do i = 1, this%neq this%rhs(i) = DZERO end do @@ -2362,9 +2349,9 @@ subroutine sln_ls(this, kiter, kstp, kper, in_iter, iptc, ptcf) logical :: lsame integer(I4B) :: n integer(I4B) :: itestmat - integer(I4B) :: i - integer(I4B) :: i1 - integer(I4B) :: i2 + integer(I4B) :: ipos + integer(I4B) :: icol_s + integer(I4B) :: icol_e integer(I4B) :: jcol integer(I4B) :: iptct integer(I4B) :: iallowptc @@ -2388,20 +2375,16 @@ subroutine sln_ls(this, kiter, kstp, kper, in_iter, iptc, ptcf) ! -- adjust small diagonal coefficient in an active cell if (this%active(n) > 0) then diagval = -DONE - adiag = abs(this%amat(this%ia(n))) + adiag = abs(this%system_matrix%get_diag_value(n)) if (adiag < DEM15) then - this%amat(this%ia(n)) = diagval + call this%system_matrix%set_diag_value(n, diagval) this%rhs(n) = this%rhs(n) + diagval * this%x(n) end if ! -- Dirichlet boundary or no-flow cell else - this%amat(this%ia(n)) = DONE + call this%system_matrix%set_diag_value(n, DONE) + call this%system_matrix%zero_row_offdiag(n) this%rhs(n) = this%x(n) - i1 = this%ia(n) + 1 - i2 = this%ia(n + 1) - 1 - do i = i1, i2 - this%amat(i) = DZERO - end do end if end do ! @@ -2409,14 +2392,18 @@ subroutine sln_ls(this, kiter, kstp, kper, in_iter, iptc, ptcf) if (this%isymmetric == 1) then do n = 1, this%neq if (this%active(n) > 0) then - i1 = this%ia(n) + 1 - i2 = this%ia(n + 1) - 1 - do i = i1, i2 - jcol = this%ja(i) + icol_s = this%system_matrix%get_first_col_pos(n) + icol_e = this%system_matrix%get_last_col_pos(n) + do ipos = icol_s, icol_e + jcol = this%system_matrix%get_column(ipos) + if (jcol == n) cycle if (this%active(jcol) < 0) then - this%rhs(n) = this%rhs(n) - this%amat(i) * this%x(jcol) - this%amat(i) = DZERO + this%rhs(n) = this%rhs(n) - & + (this%system_matrix%get_value_pos(ipos) * & + this%x(jcol)) + call this%system_matrix%set_value_pos(ipos, DZERO) end if + end do end if end do @@ -2444,9 +2431,8 @@ subroutine sln_ls(this, kiter, kstp, kper, in_iter, iptc, ptcf) ! -- calculate or modify pseudo transient continuation terms and add ! to amat diagonals if (iptct /= 0) then - call this%sln_l2norm(this%neq, this%nja, & - this%ia, this%ja, this%active, & - this%amat, this%rhs, this%x, l2norm) + call this%sln_l2norm(this%neq, this%system_matrix, this%active, & + this%rhs, this%x, l2norm) ! -- confirm that the l2norm exceeds previous l2norm ! if not, there is no need to add ptc terms if (kiter == 1) then @@ -2505,10 +2491,10 @@ subroutine sln_ls(this, kiter, kstp, kper, in_iter, iptc, ptcf) bnorm = DZERO do n = 1, this%neq if (this%active(n) > 0) then - diagval = abs(this%amat(this%ia(n))) + diagval = abs(this%system_matrix%get_diag_value(n)) bnorm = bnorm + this%rhs(n) * this%rhs(n) if (diagval < diagmin) diagmin = diagval - this%amat(this%ia(n)) = this%amat(this%ia(n)) - ptcval + call this%system_matrix%add_diag_value(n, -ptcval) this%rhs(n) = this%rhs(n) - ptcval * this%x(n) end if end do @@ -2528,15 +2514,20 @@ subroutine sln_ls(this, kiter, kstp, kper, in_iter, iptc, ptcf) if (itestmat == 1) then write (fname, fmtfname) this%id, kper, kstp, kiter print *, 'Saving amat to: ', trim(adjustl(fname)) - open (99, file=trim(adjustl(fname))) - WRITE (99, *) 'NODE, RHS, AMAT FOLLOW' - DO N = 1, this%NEQ - I1 = this%IA(N) - I2 = this%IA(N + 1) - 1 - WRITE (99, '(*(G0,:,","))') N, this%RHS(N), (this%ja(i), i=i1, i2), & - (this%AMAT(I), I=I1, I2) - END DO - close (99) + + itestmat = getunit() + open (itestmat, file=trim(adjustl(fname))) + write (itestmat, *) 'NODE, RHS, AMAT FOLLOW' + do n = 1, this%neq + icol_s = this%system_matrix%get_first_col_pos(n) + icol_e = this%system_matrix%get_last_col_pos(n) + write (itestmat, '(*(G0,:,","))') & + n, & + this%rhs(n), & + (this%system_matrix%get_column(ipos), ipos=icol_s, icol_e), & + (this%system_matrix%get_value_pos(ipos), ipos=icol_s, icol_e) + end do + close (itestmat) !stop end if !------------------------------------------------------- @@ -2657,15 +2648,13 @@ subroutine sln_backtracking(this, mp, cp, kiter) ! ! -- calculate initial l2 norm if (kiter == 1) then - call this%sln_l2norm(this%neq, this%nja, & - this%ia, this%ja, this%active, & - this%amat, this%rhs, this%x, this%res_prev) + call this%sln_l2norm(this%neq, this%system_matrix, this%active, & + this%rhs, this%x, this%res_prev) resin = this%res_prev ibflag = 0 else - call this%sln_l2norm(this%neq, this%nja, & - this%ia, this%ja, this%active, & - this%amat, this%rhs, this%x, this%res_new) + call this%sln_l2norm(this%neq, this%system_matrix, this%active, & + this%rhs, this%x, this%res_new) resin = this%res_new end if ibtcnt = 0 @@ -2691,9 +2680,8 @@ subroutine sln_backtracking(this, mp, cp, kiter) ! ! -- calculate updated l2norm - call this%sln_l2norm(this%neq, this%nja, & - this%ia, this%ja, this%active, & - this%amat, this%rhs, this%x, this%res_new) + call this%sln_l2norm(this%neq, this%system_matrix, this%active, & + this%rhs, this%x, this%res_new) ! ! -- evaluate if back tracking can be terminated if (nb == this%numtrack) then @@ -2788,21 +2776,18 @@ end subroutine sln_backtracking_xupdate !! right-hand side vector, and the current dependent-variable vector. !! !< - subroutine sln_l2norm(this, neq, nja, ia, ja, active, amat, rhs, x, l2norm) + subroutine sln_l2norm(this, neq, matrix_sln, active, rhs, x, l2norm) ! -- dummy variables class(NumericalSolutionType), intent(inout) :: this !< NumericalSolutionType instance integer(I4B), intent(in) :: neq !< number of equations - integer(I4B), intent(in) :: nja !< number of non-zero entries - integer(I4B), dimension(neq + 1), intent(in) :: ia !< CRS row pointers - integer(I4B), dimension(nja), intent(in) :: ja !< CRS column pointers + class(MatrixBaseType), pointer :: matrix_sln !< coefficient matrix for solution integer(I4B), dimension(neq), intent(in) :: active !< active cell flag vector (1) inactive (0) - real(DP), dimension(nja), intent(in) :: amat !< coefficient matrix real(DP), dimension(neq), intent(in) :: rhs !< right-hand side vector real(DP), dimension(neq), intent(in) :: x !< dependent-variable vector real(DP), intent(inout) :: l2norm !< calculated L-2 norm ! -- local variables integer(I4B) :: n - integer(I4B) :: j + integer(I4B) :: ipos, icol_s, icol_e integer(I4B) :: jcol real(DP) :: rowsum real(DP) :: residual @@ -2814,9 +2799,11 @@ subroutine sln_l2norm(this, neq, nja, ia, ja, active, amat, rhs, x, l2norm) do n = 1, neq if (active(n) > 0) then rowsum = DZERO - do j = ia(n), ia(n + 1) - 1 - jcol = ja(j) - rowsum = rowsum + amat(j) * x(jcol) + icol_s = matrix_sln%get_first_col_pos(n) + icol_e = matrix_sln%get_last_col_pos(n) + do ipos = icol_s, icol_e + jcol = matrix_sln%get_column(ipos) + rowsum = rowsum + (matrix_sln%get_value_pos(ipos) * x(jcol)) end do ! compute mean square residual from q of each node residual = residual + (rowsum - rhs(n))**2 diff --git a/src/Utilities/Matrix/MatrixBase.f90 b/src/Utilities/Matrix/MatrixBase.f90 new file mode 100644 index 00000000000..4b7de87ed29 --- /dev/null +++ b/src/Utilities/Matrix/MatrixBase.f90 @@ -0,0 +1,127 @@ +module MatrixModule + use ConstantsModule, only: LENMEMPATH + use KindModule, only: I4B, DP + use SparseModule, only: sparsematrix + implicit none + private + + type, public, abstract :: MatrixBaseType + character(len=LENMEMPATH) :: memory_path + contains + procedure(create_if), deferred :: create + procedure(destroy_if), deferred :: destroy + + procedure(get_value_pos_if), deferred :: get_value_pos + procedure(get_diag_value_if), deferred :: get_diag_value + + procedure(set_diag_value_if), deferred :: set_diag_value + procedure(set_value_pos_if), deferred :: set_value_pos + procedure(add_value_pos_if), deferred :: add_value_pos + procedure(add_diag_value_if), deferred :: add_diag_value + procedure(zero_entries_if), deferred :: zero_entries + procedure(zero_row_offdiag_if), deferred :: zero_row_offdiag + + procedure(get_first_col_pos_if), deferred :: get_first_col_pos + procedure(get_last_col_pos_if), deferred :: get_last_col_pos + procedure(get_column_if), deferred :: get_column + procedure(get_position_if), deferred :: get_position + procedure(get_position_diag_if), deferred :: get_position_diag + + end type MatrixBaseType + + abstract interface + subroutine create_if(this, sparse, mem_path) + import MatrixBaseType, sparsematrix + class(MatrixBaseType) :: this + type(sparsematrix) :: sparse + character(len=*) :: mem_path + end subroutine + subroutine destroy_if(this) + import MatrixBaseType + class(MatrixBaseType) :: this + end subroutine + + function get_value_pos_if(this, ipos) result(value) + import MatrixBaseType, I4B, DP + class(MatrixBaseType) :: this + integer(I4B) :: ipos + real(DP) :: value + end function + function get_diag_value_if(this, irow) result(diag_value) + import MatrixBaseType, I4B, DP + class(MatrixBaseType) :: this + integer(I4B) :: irow + real(DP) :: diag_value + end function + + subroutine set_diag_value_if(this, irow, diag_value) + import MatrixBaseType, I4B, DP + class(MatrixBaseType) :: this + integer(I4B) :: irow + real(DP) :: diag_value + end subroutine + subroutine set_value_pos_if(this, ipos, value) + import MatrixBaseType, I4B, DP + class(MatrixBaseType) :: this + integer(I4B) :: ipos + real(DP) :: value + end subroutine + subroutine add_value_pos_if(this, ipos, value) + import MatrixBaseType, I4B, DP + class(MatrixBaseType) :: this + integer(I4B) :: ipos + real(DP) :: value + end subroutine + subroutine add_diag_value_if(this, irow, value) + import MatrixBaseType, I4B, DP + class(MatrixBaseType) :: this + integer(I4B) :: irow + real(DP) :: value + end subroutine + subroutine zero_entries_if(this) + import MatrixBaseType + class(MatrixBaseType) :: this + end subroutine + subroutine zero_row_offdiag_if(this, irow) + import MatrixBaseType, I4B + class(MatrixBaseType) :: this + integer(I4B) :: irow + end subroutine + function get_first_col_pos_if(this, irow) result(first_col_pos) + import MatrixBaseType, I4B + class(MatrixBaseType) :: this + integer(I4B) :: irow + integer(I4B) :: first_col_pos + end function + function get_last_col_pos_if(this, irow) result(last_col_pos) + import MatrixBaseType, I4B + class(MatrixBaseType) :: this + integer(I4B) :: irow + integer(I4B) :: last_col_pos + end function + function get_column_if(this, ipos) result(icol) + import MatrixBaseType, I4B + class(MatrixBaseType) :: this + integer(I4B) :: ipos + integer(I4B) :: icol + end function + + !> @brief Get position index for this (irow,icol) element + !! in the matrix for direct access with the other routines + !< Returns -1 when not found. + function get_position_if(this, irow, icol) result(ipos) + import MatrixBaseType, I4B + class(MatrixBaseType) :: this + integer(I4B) :: irow + integer(I4B) :: icol + integer(I4B) :: ipos + end function + function get_position_diag_if(this, irow) result(ipos_diag) + import MatrixBaseType, I4B + class(MatrixBaseType) :: this + integer(I4B) :: irow + integer(I4B) :: ipos_diag + end function + end interface + +end module MatrixModule diff --git a/src/Utilities/Matrix/SparseMatrix.f90 b/src/Utilities/Matrix/SparseMatrix.f90 new file mode 100644 index 00000000000..cdb654beb75 --- /dev/null +++ b/src/Utilities/Matrix/SparseMatrix.f90 @@ -0,0 +1,237 @@ +module SparseMatrixModule + use KindModule, only: I4B, DP + use ConstantsModule, only: DZERO + use MatrixModule + use SparseModule, only: sparsematrix + use MemoryManagerModule, only: mem_allocate, mem_deallocate + implicit none + private + + type, public, extends(MatrixBaseType) :: SparseMatrixType + integer(I4B), pointer :: nrow + integer(I4B), pointer :: ncol + integer(I4B), pointer :: nja + integer(I4B), dimension(:), pointer, contiguous :: ia !< indexes into ja for columns, sorted: diagonal element first + integer(I4B), dimension(:), pointer, contiguous :: ja + real(DP), dimension(:), pointer, contiguous :: amat + contains + procedure :: create => spm_create + procedure :: destroy => spm_destroy + + procedure :: get_value_pos => spm_get_value_pos + procedure :: get_diag_value => spm_get_diag_value + + procedure :: set_diag_value => spm_set_diag_value + procedure :: set_value_pos => spm_set_value_pos + procedure :: add_value_pos => spm_add_value_pos + procedure :: add_diag_value => spm_add_diag_value + procedure :: zero_entries => spm_zero_entries + procedure :: zero_row_offdiag => spm_zero_row_offdiag + + procedure :: get_first_col_pos => spm_get_first_col_pos + procedure :: get_last_col_pos => spm_get_last_col_pos + procedure :: get_column => spm_get_column + procedure :: get_position => spm_get_position + procedure :: get_position_diag => spm_get_position_diag + + procedure :: allocate_scalars + procedure :: allocate_arrays + end type SparseMatrixType + +contains + + subroutine spm_create(this, sparse, mem_path) + class(SparseMatrixType) :: this + type(sparsematrix) :: sparse + character(len=*) :: mem_path + ! local + integer(I4B) :: ierror + + this%memory_path = mem_path + + call this%allocate_scalars() + + this%nrow = sparse%nrow + this%ncol = sparse%ncol + this%nja = sparse%nnz + + call this%allocate_arrays() + + call sparse%filliaja(this%ia, this%ja, ierror, sort=.false.) + call this%zero_entries() + + end subroutine spm_create + + subroutine spm_destroy(this) + class(SparseMatrixType) :: this + + call mem_deallocate(this%nrow) + call mem_deallocate(this%ncol) + call mem_deallocate(this%nja) + + call mem_deallocate(this%ia) + call mem_deallocate(this%ja) + call mem_deallocate(this%amat) + + end subroutine spm_destroy + + function spm_get_value_pos(this, ipos) result(value) + class(SparseMatrixType) :: this + integer(I4B) :: ipos + real(DP) :: value + + value = this%amat(ipos) + + end function spm_get_value_pos + + function spm_get_diag_value(this, irow) result(diag_value) + class(SparseMatrixType) :: this + integer(I4B) :: irow + real(DP) :: diag_value + + diag_value = this%amat(this%ia(irow)) + + end function spm_get_diag_value + + subroutine spm_set_diag_value(this, irow, diag_value) + class(SparseMatrixType) :: this + integer(I4B) :: irow + real(DP) :: diag_value + + this%amat(this%ia(irow)) = diag_value + + end subroutine spm_set_diag_value + + subroutine spm_set_value_pos(this, ipos, value) + class(SparseMatrixType) :: this + integer(I4B) :: ipos + real(DP) :: value + + this%amat(ipos) = value + + end subroutine spm_set_value_pos + + subroutine spm_add_value_pos(this, ipos, value) + class(SparseMatrixType) :: this + integer(I4B) :: ipos + real(DP) :: value + + this%amat(ipos) = this%amat(ipos) + value + + end subroutine spm_add_value_pos + + subroutine spm_add_diag_value(this, irow, value) + class(SparseMatrixType) :: this + integer(I4B) :: irow + real(DP) :: value + + this%amat(this%ia(irow)) = this%amat(this%ia(irow)) + value + + end subroutine spm_add_diag_value + + function spm_get_first_col_pos(this, irow) result(first_col_pos) + class(SparseMatrixType) :: this + integer(I4B) :: irow + integer(I4B) :: first_col_pos + + first_col_pos = this%ia(irow) + + end function spm_get_first_col_pos + + function spm_get_last_col_pos(this, irow) result(last_col_pos) + class(SparseMatrixType) :: this + integer(I4B) :: irow + integer(I4B) :: last_col_pos + + last_col_pos = this%ia(irow + 1) - 1 + + end function spm_get_last_col_pos + + function spm_get_column(this, ipos) result(icol) + class(SparseMatrixType) :: this + integer(I4B) :: ipos + integer(I4B) :: icol + + icol = this%ja(ipos) + + end function spm_get_column + + !> @brief Return position index for (irow,icol) element + !! in the matrix. This index can be used in other + !! routines for direct access. + !< Returns -1 when not found. + function spm_get_position(this, irow, icol) result(ipos) + class(SparseMatrixType) :: this + integer(I4B) :: irow + integer(I4B) :: icol + integer(I4B) :: ipos + ! local + integer(I4B) :: i, icol_s, icol_e + + ipos = -1 + icol_s = this%get_first_col_pos(irow) + icol_e = this%get_last_col_pos(irow) + do i = icol_s, icol_e + if (this%ja(i) == icol) then + ipos = i + return + end if + end do + + end function spm_get_position + + function spm_get_position_diag(this, irow) result(ipos_diag) + class(SparseMatrixType) :: this + integer(I4B) :: irow + integer(I4B) :: ipos_diag + + ipos_diag = this%ia(irow) + + end function spm_get_position_diag + + subroutine allocate_scalars(this) + class(SparseMatrixType) :: this + + call mem_allocate(this%nrow, 'NROW', this%memory_path) + call mem_allocate(this%ncol, 'NCOL', this%memory_path) + call mem_allocate(this%nja, 'NJA', this%memory_path) + + end subroutine allocate_scalars + + subroutine allocate_arrays(this) + class(SparseMatrixType) :: this + + call mem_allocate(this%ia, this%nrow + 1, 'IA', this%memory_path) + call mem_allocate(this%ja, this%nja, 'JA', this%memory_path) + call mem_allocate(this%amat, this%nja, 'AMAT', this%memory_path) + + end subroutine allocate_arrays + + !> @brief Set all entries in the matrix to zero + !< + subroutine spm_zero_entries(this) + class(SparseMatrixType) :: this + ! local + integer(I4B) :: i + + do i = 1, this%nja + this%amat(i) = DZERO + end do + + end subroutine spm_zero_entries + + !> @brief Set all off-diagonal entries in the matrix to zero + !< + subroutine spm_zero_row_offdiag(this, irow) + class(SparseMatrixType) :: this + integer(I4B) :: irow + ! local + integer(I4B) :: ipos + + do ipos = this%ia(irow) + 1, this%ia(irow + 1) - 1 + this%amat(ipos) = DZERO + end do + + end subroutine spm_zero_row_offdiag + +end module SparseMatrixModule diff --git a/src/meson.build b/src/meson.build index 301b07c431f..f50a97e7b7f 100644 --- a/src/meson.build +++ b/src/meson.build @@ -129,6 +129,8 @@ modflow_sources = files( 'Utilities' / 'Idm' / 'LoadMf6FileType.f90', 'Utilities' / 'Idm' / 'StructArray.f90', 'Utilities' / 'Idm' / 'StructVector.f90', + 'Utilities' / 'Matrix' / 'MatrixBase.f90', + 'Utilities' / 'Matrix' / 'SparseMatrix.f90', 'Utilities' / 'Memory' / 'Memory.f90', 'Utilities' / 'Memory' / 'MemoryHelper.f90', 'Utilities' / 'Memory' / 'MemoryList.f90', diff --git a/utils/mf5to6/make/makedefaults b/utils/mf5to6/make/makedefaults index 1aadc335376..fdc2fdb51cb 100644 --- a/utils/mf5to6/make/makedefaults +++ b/utils/mf5to6/make/makedefaults @@ -1,4 +1,4 @@ -# makedefaults created by pymake (version 1.2.5) for the 'mf5to6' executable. +# makedefaults created by pymake (version 1.2.7) for the 'mf5to6' executable. # determine OS ifeq ($(OS), Windows_NT) diff --git a/utils/mf5to6/make/makefile b/utils/mf5to6/make/makefile index 9708dc02d99..e286aaf1171 100644 --- a/utils/mf5to6/make/makefile +++ b/utils/mf5to6/make/makefile @@ -1,4 +1,4 @@ -# makefile created by pymake (version 1.2.5) for the 'mf5to6' executable. +# makefile created by pymake (version 1.2.7) for the 'mf5to6' executable. include ./makedefaults @@ -6,9 +6,9 @@ include ./makedefaults # Define the source file directories SOURCEDIR1=../src SOURCEDIR2=../src/LGR -SOURCEDIR3=../src/MF2005 -SOURCEDIR4=../src/NWT -SOURCEDIR5=../src/Preproc +SOURCEDIR3=../src/Preproc +SOURCEDIR4=../src/MF2005 +SOURCEDIR5=../src/NWT SOURCEDIR6=../../../src/Utilities/Memory SOURCEDIR7=../../../src/Utilities/TimeSeries SOURCEDIR8=../../../src/Utilities diff --git a/utils/zonebudget/make/makedefaults b/utils/zonebudget/make/makedefaults index 7d681aa2218..919f5ed9b06 100644 --- a/utils/zonebudget/make/makedefaults +++ b/utils/zonebudget/make/makedefaults @@ -1,4 +1,4 @@ -# makedefaults created by pymake (version 1.2.5) for the 'zbud6' executable. +# makedefaults created by pymake (version 1.2.7) for the 'zbud6' executable. # determine OS ifeq ($(OS), Windows_NT) diff --git a/utils/zonebudget/make/makefile b/utils/zonebudget/make/makefile index 4010990d67a..5ba239e2b62 100644 --- a/utils/zonebudget/make/makefile +++ b/utils/zonebudget/make/makefile @@ -1,4 +1,4 @@ -# makefile created by pymake (version 1.2.5) for the 'zbud6' executable. +# makefile created by pymake (version 1.2.7) for the 'zbud6' executable. include ./makedefaults From 310ed4c13b03bee6f114918d4bb6461bdfef7446 Mon Sep 17 00:00:00 2001 From: mjreno Date: Fri, 23 Dec 2022 10:41:51 -0500 Subject: [PATCH 011/123] refactor(idm): variables default to required in input definitions; formatting (#1125) * refactor(idm): run black formatting on dfn2f90.py * refactor(idm): variables default to required in input definitions; formatting Co-authored-by: mjreno --- src/Model/GroundWaterFlow/gwf3npf8idm.f90 | 2 +- utils/idmloader/scripts/dfn2f90.py | 129 ++++++++++++++-------- 2 files changed, 83 insertions(+), 48 deletions(-) diff --git a/src/Model/GroundWaterFlow/gwf3npf8idm.f90 b/src/Model/GroundWaterFlow/gwf3npf8idm.f90 index 9a7d39579d3..696cb2cf032 100644 --- a/src/Model/GroundWaterFlow/gwf3npf8idm.f90 +++ b/src/Model/GroundWaterFlow/gwf3npf8idm.f90 @@ -692,7 +692,7 @@ module GwfNpfInputModule [ & InputBlockDefinitionType( & 'OPTIONS', & ! blockname - .true., & ! required + .false., & ! required .false. & ! aggregate ), & InputBlockDefinitionType( & diff --git a/utils/idmloader/scripts/dfn2f90.py b/utils/idmloader/scripts/dfn2f90.py index 6bc271c5fc0..d0697f9eedd 100644 --- a/utils/idmloader/scripts/dfn2f90.py +++ b/utils/idmloader/scripts/dfn2f90.py @@ -35,44 +35,58 @@ def __init__( self._set_var_d() self._set_param_strs() - def write_f90(self, odspec=None, gwt_name=False): - if gwt_name: - fname = Path(odspec, f"{self.component.lower()}1{self.subcomponent.lower()}idm.f90") - else: - fname = Path(odspec, f"{self.component.lower()}3{self.subcomponent.lower()}8idm.f90") - with open(fname, "w") as f: + def write_f90(self, ofspec=None): + with open(ofspec, "w") as f: # file header f.write(self._source_file_header(self.component, self.subcomponent)) # found type - f.write(f" type {self.component.capitalize()}{self.subcomponent.capitalize()}ParamFoundType\n") + f.write( + f" type {self.component.capitalize()}{self.subcomponent.capitalize()}ParamFoundType\n" + ) for var in self._param_varnames: - varname = var.split(f"{self.component.lower()}{self.subcomponent.lower()}_")[1] - f.write(f" logical :: {varname} = .false.\n") - f.write(f" end type {self.component.capitalize()}{self.subcomponent.capitalize()}ParamFoundType\n\n") + varname = var.split( + f"{self.component.lower()}{self.subcomponent.lower()}_" + )[1] + f.write(f" logical :: {varname} = .false.\n") + f.write( + f" end type {self.component.capitalize()}{self.subcomponent.capitalize()}ParamFoundType\n\n" + ) # params if len(self._param_varnames): f.write(self._param_str) f.write(self._source_params_header(self.component, self.subcomponent)) f.write(" " + ", &\n ".join(self._param_varnames) + " &\n") - f.write(self._source_list_footer(self.component, self.subcomponent) + "\n") + f.write( + self._source_list_footer(self.component, self.subcomponent) + "\n" + ) else: f.write(self._source_params_header(self.component, self.subcomponent)) f.write(self._param_str.rsplit(",", 1)[0] + " &\n") - f.write(self._source_list_footer(self.component, self.subcomponent) + "\n") + f.write( + self._source_list_footer(self.component, self.subcomponent) + "\n" + ) # aggregate types if len(self._aggregate_varnames): f.write(self._aggregate_str) - f.write(self._source_aggregates_header(self.component, self.subcomponent)) + f.write( + self._source_aggregates_header(self.component, self.subcomponent) + ) f.write(" " + ", &\n ".join(self._aggregate_varnames) + " &\n") - f.write(self._source_list_footer(self.component, self.subcomponent) + "\n") + f.write( + self._source_list_footer(self.component, self.subcomponent) + "\n" + ) else: - f.write(self._source_aggregates_header(self.component, self.subcomponent)) + f.write( + self._source_aggregates_header(self.component, self.subcomponent) + ) f.write(self._aggregate_str.rsplit(",", 1)[0] + " &\n") - f.write(self._source_list_footer(self.component, self.subcomponent) + "\n") + f.write( + self._source_list_footer(self.component, self.subcomponent) + "\n" + ) # blocks f.write(self._source_blocks_header(self.component, self.subcomponent)) @@ -165,7 +179,9 @@ def _construct_f90_block_statement( return f90statement - def _construct_f90_param_statement(self, tuple_list, basename, varname, aggregate=False): + def _construct_f90_param_statement( + self, tuple_list, basename, varname, aggregate=False + ): vname = f"{basename.lower()}_{varname.lower()}" if aggregate: self._aggregate_varnames.append(vname) @@ -206,6 +222,7 @@ def _set_blk_param_strs(self, blockname, component, subcomponent): required_l = None required_l = [] is_aggregate_blk = False + aggregate_required = False # comment s = f" ! {component} {subcomponent} {blockname.upper()}\n" @@ -223,7 +240,6 @@ def _set_blk_param_strs(self, blockname, component, subcomponent): v = self._var_d[k] if "block_variable" in v and v["block_variable"].upper() == "TRUE": - # TODO: add to block defn type continue c = component @@ -260,14 +276,6 @@ def _set_blk_param_strs(self, blockname, component, subcomponent): if shape != "" and not aggregate_t and (t == "DOUBLE" or t == "INTEGER"): t = f"{t}{ndim}D" - r = ".true." - if "optional" in v: - if v["optional"] == "true": - r = ".false." - else: - r = ".true." - is_required_blk = True - inrec = ".false." if "in_record" in v: if v["in_record"] == "true": @@ -275,6 +283,13 @@ def _set_blk_param_strs(self, blockname, component, subcomponent): else: inrec = ".false." + r = ".true." + if "optional" in v: + if v["optional"] == "true": + r = ".false." + else: + r = ".true." + preserve_case = ".false." if "preserve_case" in v: if v["preserve_case"] == "true": @@ -289,7 +304,8 @@ def _set_blk_param_strs(self, blockname, component, subcomponent): else: layered = ".false." - required_l.append(r) + if inrec == ".false.": + required_l.append(r) tuple_list = [ (c, "component"), (sc, "subcomponent"), @@ -309,9 +325,13 @@ def _set_blk_param_strs(self, blockname, component, subcomponent): # if necessary if aggregate_t: self._aggregate_str += ( - self._construct_f90_param_statement(tuple_list, f"{component}{subcomponent}", mf6vn, True) + "\n" + self._construct_f90_param_statement( + tuple_list, f"{component}{subcomponent}", mf6vn, True + ) + + "\n" ) is_aggregate_blk = True + aggregate_required = r == ".true." if not shape: self._warnings.append( f"Aggregate type found with no shape: {component}-{subcomponent}-{blockname}: {mf6vn}" @@ -319,13 +339,20 @@ def _set_blk_param_strs(self, blockname, component, subcomponent): else: self._param_str += ( - self._construct_f90_param_statement(tuple_list, f"{component}{subcomponent}", mf6vn) + "\n" + self._construct_f90_param_statement( + tuple_list, f"{component}{subcomponent}", mf6vn + ) + + "\n" ) + if is_aggregate_blk: + required = aggregate_required + else: + required = ".true." in required_l self._block_str += ( self._construct_f90_block_statement( blockname.upper(), - required=(".true." in required_l), + required=required, aggregate=is_aggregate_blk, ) + "\n" @@ -399,25 +426,33 @@ def _source_file_footer(self, component, subcomponent): if __name__ == "__main__": - gwf_dfns = [ - Path("../../../doc/mf6io/mf6ivar/dfn", "gwf-dis.dfn"), - Path("../../../doc/mf6io/mf6ivar/dfn", "gwf-disu.dfn"), - Path("../../../doc/mf6io/mf6ivar/dfn", "gwf-disv.dfn"), - Path("../../../doc/mf6io/mf6ivar/dfn", "gwf-npf.dfn"), - ] - - for dfn in gwf_dfns: - converter = Dfn2F90(dfnfspec=dfn) - converter.write_f90(odspec=os.path.join("..", "..", "..", "src", "Model", "GroundWaterFlow")) - converter.warn() - - gwt_dfns = [ - Path("../../../doc/mf6io/mf6ivar/dfn", "gwt-dsp.dfn"), + dfns = [ + # list per dfn [dfn relative path, model parent dirname, output filename] + [ + Path("../../../doc/mf6io/mf6ivar/dfn", "gwf-dis.dfn"), + Path("../../../src/Model/GroundWaterFlow", "gwf3dis8idm.f90"), + ], + [ + Path("../../../doc/mf6io/mf6ivar/dfn", "gwf-disu.dfn"), + Path("../../../src/Model/GroundWaterFlow", "gwf3disu8idm.f90"), + ], + [ + Path("../../../doc/mf6io/mf6ivar/dfn", "gwf-disv.dfn"), + Path("../../../src/Model/GroundWaterFlow", "gwf3disv8idm.f90"), + ], + [ + Path("../../../doc/mf6io/mf6ivar/dfn", "gwf-npf.dfn"), + Path("../../../src/Model/GroundWaterFlow", "gwf3npf8idm.f90"), + ], + [ + Path("../../../doc/mf6io/mf6ivar/dfn", "gwt-dsp.dfn"), + Path("../../../src/Model/GroundWaterTransport", "gwt1dspidm.f90"), + ], ] - for dfn in gwt_dfns: - converter = Dfn2F90(dfnfspec=dfn) - converter.write_f90(odspec=os.path.join("..", "..", "..", "src", "Model", "GroundWaterTransport"), gwt_name=True) + for dfn in dfns: + converter = Dfn2F90(dfnfspec=dfn[0]) + converter.write_f90(ofspec=dfn[1]) converter.warn() print("\n...done.") From aba247a3bcac8dcbc44bc6040dca0a42290f7729 Mon Sep 17 00:00:00 2001 From: mjreno Date: Fri, 23 Dec 2022 10:44:34 -0500 Subject: [PATCH 012/123] fix(MemoryManager): reallocate_charstr1d and get_isize fixes (#1123) * fix(MemoryManager): set MemoryType reallocated array pointer in reallocate_charstr1d * fix(MemoryManager): fixes * set MemoryType reallocated array pointer in reallocate_charstr1d * get_isize should return -1 when variable not found Co-authored-by: mjreno --- src/Utilities/Memory/MemoryManager.f90 | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Utilities/Memory/MemoryManager.f90 b/src/Utilities/Memory/MemoryManager.f90 index fd16b0012b4..3e85b34472c 100644 --- a/src/Utilities/Memory/MemoryManager.f90 +++ b/src/Utilities/Memory/MemoryManager.f90 @@ -274,13 +274,17 @@ subroutine get_isize(name, mem_path, isize) ! -- local type(MemoryType), pointer :: mt => null() logical(LGP) :: found + logical(LGP) :: terminate ! -- code ! ! -- initialize isize to a value to communicate failure isize = -1 ! + ! -- don't exit program if variable not found + terminate = .false. + ! ! -- get the entry from the memory manager - call get_from_memorylist(name, mem_path, mt, found) + call get_from_memorylist(name, mem_path, mt, found, terminate) ! ! -- set isize if (found) then @@ -1233,6 +1237,7 @@ subroutine reallocate_charstr1d(acharstr1d, ilen, nrow, name, mem_path) deallocate (astrtemp) ! ! -- reset memory manager values + mt%acharstr1d => acharstr1d mt%element_size = ilen mt%isize = isize mt%nrealloc = mt%nrealloc + 1 From 1ab16d0d0a89b753c10f814182619ffd3f8fce0d Mon Sep 17 00:00:00 2001 From: mjreno Date: Fri, 23 Dec 2022 10:45:54 -0500 Subject: [PATCH 013/123] feat(idm): add string type to idm logging (#1124) Co-authored-by: mjreno --- src/Utilities/Idm/IdmLogger.f90 | 13 ++++++++++++- src/Utilities/Idm/LoadMf6FileType.f90 | 1 + 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/Utilities/Idm/IdmLogger.f90 b/src/Utilities/Idm/IdmLogger.f90 index aa7f1f83cca..80820652bf8 100644 --- a/src/Utilities/Idm/IdmLogger.f90 +++ b/src/Utilities/Idm/IdmLogger.f90 @@ -19,7 +19,7 @@ module IdmLoggerModule idm_log_var_int1d, idm_log_var_int2d, & idm_log_var_int3d, idm_log_var_dbl, & idm_log_var_dbl1d, idm_log_var_dbl2d, & - idm_log_var_dbl3d + idm_log_var_dbl3d, idm_log_var_str end interface idm_log_var contains @@ -206,4 +206,15 @@ subroutine idm_log_var_dbl3d(p_mem, varname, mempath, iout) end if end subroutine idm_log_var_dbl3d + !> @brief Log type specific information str + !< + subroutine idm_log_var_str(p_mem, varname, mempath, iout) + character(len=*), intent(in) :: p_mem !< pointer to str scalar + character(len=*), intent(in) :: varname !< variable name + character(len=*), intent(in) :: mempath !< variable memory path + integer(I4B) :: iout + + write (iout, '(3x,a, " = ", a)') trim(varname), trim(p_mem) + end subroutine idm_log_var_str + end module IdmLoggerModule diff --git a/src/Utilities/Idm/LoadMf6FileType.f90 b/src/Utilities/Idm/LoadMf6FileType.f90 index 5f3038955b3..9ef40b14ee9 100644 --- a/src/Utilities/Idm/LoadMf6FileType.f90 +++ b/src/Utilities/Idm/LoadMf6FileType.f90 @@ -363,6 +363,7 @@ subroutine load_string_type(parser, idt, memoryPath, iout) ilen = LINELENGTH call mem_allocate(cstr, ilen, idt%mf6varname, memoryPath) call parser%GetString(cstr, (.not. idt%preserve_case)) + call idm_log_var(cstr, idt%mf6varname, memoryPath, iout) return end subroutine load_string_type From 8758486b2c0420f2affcbdde406f9006b339d393 Mon Sep 17 00:00:00 2001 From: mjreno Date: Fri, 23 Dec 2022 17:47:39 -0500 Subject: [PATCH 014/123] refactor(idm): rework loading of filein/fileout record tags to input context (#1126) * match input definition param on blockname as well as component/subcomponent type * do not rely on fortran short-circuit behavior when evaluating compound logical expressions * refactor(idm): rework loading of filein/fileout record tags to input context Co-authored-by: mjreno --- src/Model/GroundWaterFlow/gwf3dis8.f90 | 3 +- src/Model/GroundWaterFlow/gwf3disu8.f90 | 2 +- src/Model/GroundWaterFlow/gwf3disv8.f90 | 2 +- src/Model/GroundWaterFlow/gwf3npf8.f90 | 2 +- src/Model/GroundWaterTransport/gwt1dsp.f90 | 2 +- src/Utilities/Idm/IdmMf6FileLoader.f90 | 23 ++-- src/Utilities/Idm/InputDefinitionSelector.f90 | 62 ++++++++- src/Utilities/Idm/LoadMf6FileType.f90 | 123 ++++++++++++------ src/Utilities/Idm/ModflowInput.f90 | 19 +-- 9 files changed, 161 insertions(+), 77 deletions(-) diff --git a/src/Model/GroundWaterFlow/gwf3dis8.f90 b/src/Model/GroundWaterFlow/gwf3dis8.f90 index d81cdffafe7..886fab9af76 100644 --- a/src/Model/GroundWaterFlow/gwf3dis8.f90 +++ b/src/Model/GroundWaterFlow/gwf3dis8.f90 @@ -106,8 +106,7 @@ subroutine dis_cr(dis, name_model, inunit, iout) ! ! -- Use the input data model routines to load the input data ! into memory - call input_load(dis%parser, 'DIS6', 'GWF', 'DIS', name_model, 'DIS', & - [character(len=LENPACKAGETYPE) ::], iout) + call input_load(dis%parser, 'DIS6', 'GWF', 'DIS', name_model, 'DIS', iout) end if ! ! -- Return diff --git a/src/Model/GroundWaterFlow/gwf3disu8.f90 b/src/Model/GroundWaterFlow/gwf3disu8.f90 index 02d1ff99b36..13c0bd84fcd 100644 --- a/src/Model/GroundWaterFlow/gwf3disu8.f90 +++ b/src/Model/GroundWaterFlow/gwf3disu8.f90 @@ -127,7 +127,7 @@ subroutine disu_cr(dis, name_model, inunit, iout) ! -- Use the input data model routines to load the input data ! into memory call input_load(dis%parser, 'DISU6', 'GWF', 'DISU', name_model, 'DISU', & - [character(len=LENPACKAGETYPE) ::], iout) + iout) ! ! -- load disu call disnew%disu_load() diff --git a/src/Model/GroundWaterFlow/gwf3disv8.f90 b/src/Model/GroundWaterFlow/gwf3disv8.f90 index 4b5bfc9c7da..5db18e0a428 100644 --- a/src/Model/GroundWaterFlow/gwf3disv8.f90 +++ b/src/Model/GroundWaterFlow/gwf3disv8.f90 @@ -111,7 +111,7 @@ subroutine disv_cr(dis, name_model, inunit, iout) ! -- Use the input data model routines to load the input data ! into memory call input_load(dis%parser, 'DISV6', 'GWF', 'DISV', name_model, 'DISV', & - [character(len=LENPACKAGETYPE) ::], iout) + iout) ! ! -- load disv call disnew%disv_load() diff --git a/src/Model/GroundWaterFlow/gwf3npf8.f90 b/src/Model/GroundWaterFlow/gwf3npf8.f90 index 93e68055722..dc1999cf419 100644 --- a/src/Model/GroundWaterFlow/gwf3npf8.f90 +++ b/src/Model/GroundWaterFlow/gwf3npf8.f90 @@ -195,7 +195,7 @@ subroutine npf_cr(npfobj, name_model, inunit, iout) ! -- Use the input data model routines to load the input data ! into memory call input_load(npfobj%parser, 'NPF6', 'GWF', 'NPF', npfobj%name_model, & - 'NPF', [character(len=LENPACKAGETYPE) :: 'TVK6'], iout) + 'NPF', iout) end if ! ! -- Return diff --git a/src/Model/GroundWaterTransport/gwt1dsp.f90 b/src/Model/GroundWaterTransport/gwt1dsp.f90 index 3c421b1af4b..c61c926267c 100644 --- a/src/Model/GroundWaterTransport/gwt1dsp.f90 +++ b/src/Model/GroundWaterTransport/gwt1dsp.f90 @@ -122,7 +122,7 @@ subroutine dsp_cr(dspobj, name_model, inunit, iout, fmi) ! -- Use the input data model routines to load the input data ! into memory call input_load(dspobj%parser, 'DSP6', 'GWT', 'DSP', dspobj%name_model, & - 'DSP', [character(len=LENPACKAGETYPE) ::], iout) + 'DSP', iout) end if ! ! -- Return diff --git a/src/Utilities/Idm/IdmMf6FileLoader.f90 b/src/Utilities/Idm/IdmMf6FileLoader.f90 index dba8ef7ae0f..5212b1a516f 100644 --- a/src/Utilities/Idm/IdmMf6FileLoader.f90 +++ b/src/Utilities/Idm/IdmMf6FileLoader.f90 @@ -15,6 +15,10 @@ module IdmMf6FileLoaderModule private public :: input_load + interface input_load + module procedure input_load_blockparser + end interface input_load + !> @brief derived type for storing package loader !! !! This derived type is used to store a pointer to a @@ -51,30 +55,29 @@ subroutine generic_mf6_load(parser, mf6_input, iout) call idm_load(parser, mf6_input%file_type, & mf6_input%component_type, mf6_input%subcomponent_type, & mf6_input%component_name, mf6_input%subcomponent_name, & - mf6_input%subpackages, iout) + iout) end subroutine generic_mf6_load !> @brief main entry to mf6 input load !< - subroutine input_load(parser, filetype, & - component_type, subcomponent_type, & - component_name, subcomponent_name, & - subpackages, iout) + subroutine input_load_blockparser(parser, filetype, & + component_type, subcomponent_type, & + component_name, subcomponent_name, & + iout) type(BlockParserType), intent(inout) :: parser !< block parser character(len=*), intent(in) :: filetype !< file type to load, such as DIS6, DISV6, NPF6 character(len=*), intent(in) :: component_type !< component type, such as GWF or GWT character(len=*), intent(in) :: subcomponent_type !< subcomponent type, such as DIS or NPF character(len=*), intent(in) :: component_name !< component name, such as MYGWFMODEL character(len=*), intent(in) :: subcomponent_name !< subcomponent name, such as MYWELLPACKAGE - character(len=*), dimension(:), intent(in) :: subpackages !< array of subpackage types, such as ["TVK6", "OBS6"] integer(I4B), intent(in) :: iout !< unit number for output type(ModflowInputType) :: mf6_input type(PackageLoad) :: pkgloader mf6_input = getModflowInput(filetype, component_type, & subcomponent_type, component_name, & - subcomponent_name, subpackages) + subcomponent_name) ! ! -- set mf6 parser based package loader by file type select case (filetype) @@ -85,8 +88,8 @@ subroutine input_load(parser, filetype, & ! -- invoke the selected load routine call pkgloader%load_package(parser, mf6_input, iout) ! - ! -- release allocated memory - call mf6_input%destroy() - end subroutine input_load + ! -- return + return + end subroutine input_load_blockparser end module IdmMf6FileLoaderModule diff --git a/src/Utilities/Idm/InputDefinitionSelector.f90 b/src/Utilities/Idm/InputDefinitionSelector.f90 index 17e030858b2..c93d997db0d 100644 --- a/src/Utilities/Idm/InputDefinitionSelector.f90 +++ b/src/Utilities/Idm/InputDefinitionSelector.f90 @@ -35,6 +35,7 @@ module InputDefinitionSelectorModule public :: param_definitions public :: get_param_definition_type public :: get_aggregate_definition_type + public :: split_record_definition contains @@ -134,11 +135,13 @@ end subroutine set_block_pointer !> @brief Return parameter definition !< function get_param_definition_type(input_definition_types, component_type, & - subcomponent_type, tagname) result(idt) + subcomponent_type, blockname, tagname) & + result(idt) type(InputParamDefinitionType), dimension(:), intent(in), target :: & input_definition_types character(len=*), intent(in) :: component_type !< component type, such as GWF or GWT character(len=*), intent(in) :: subcomponent_type !< subcomponent type, such as DIS or NPF + character(len=*), intent(in) :: blockname !< name of the block character(len=*), intent(in) :: tagname !< name of the input tag type(InputParamDefinitionType), pointer :: idt !< corresponding InputParameterDefinitionType for this tag type(InputParamDefinitionType), pointer :: tmp_ptr @@ -149,6 +152,7 @@ function get_param_definition_type(input_definition_types, component_type, & tmp_ptr => input_definition_types(i) if (tmp_ptr%component_type == component_type .and. & tmp_ptr%subcomponent_type == subcomponent_type .and. & + tmp_ptr%blockname == blockname .and. & tmp_ptr%tagname == tagname) then idt => input_definition_types(i) exit @@ -193,4 +197,60 @@ function get_aggregate_definition_type(input_definition_types, component_type, & end if end function get_aggregate_definition_type + !> @brief Return aggregate definition + !! + !! Split a component RECORD datatype definition whose second element matches + !! tagname into an array of character tokens + !< + subroutine split_record_definition(input_definition_types, component_type, & + subcomponent_type, tagname, nwords, words) + use InputOutputModule, only: parseline + type(InputParamDefinitionType), dimension(:), intent(in), target :: & + input_definition_types + character(len=*), intent(in) :: component_type !< component type, such as GWF or GWT + character(len=*), intent(in) :: subcomponent_type !< subcomponent type, such as DIS or NPF + character(len=*), intent(in) :: tagname !< name of the input tag + integer(I4B), intent(inout) :: nwords + character(len=40), dimension(:), allocatable, intent(inout) :: words + type(InputParamDefinitionType), pointer :: tmp_ptr + integer(I4B) :: i + character(len=:), allocatable :: parse_str + ! + ! -- initialize to deallocated + if (allocated(words)) deallocate (words) + ! + ! -- return all tokens of multi-record type that matches the first + ! -- tag following the expected first token "RECORD" + do i = 1, size(input_definition_types) + ! + ! -- initialize + nwords = 0 + ! + ! -- set ptr to current definition + tmp_ptr => input_definition_types(i) + ! + ! -- match for definition to split + if (tmp_ptr%component_type == component_type .and. & + tmp_ptr%subcomponent_type == subcomponent_type .and. & + tmp_ptr%datatype(1:6) == 'RECORD') then + ! + ! -- set split string + parse_str = trim(input_definition_types(i)%datatype)//' ' + ! + ! -- split + call parseline(parse_str, nwords, words) + ! + ! -- check for match and manage memory + if (nwords >= 2) then + if (words(1) == 'RECORD' .and. words(2) == tagname) exit + end if + ! + ! -- deallocate + if (allocated(parse_str)) deallocate (parse_str) + if (allocated(words)) deallocate (words) + ! + end if + end do + end subroutine split_record_definition + end module InputDefinitionSelectorModule diff --git a/src/Utilities/Idm/LoadMf6FileType.f90 b/src/Utilities/Idm/LoadMf6FileType.f90 index 9ef40b14ee9..24ff10d6e33 100644 --- a/src/Utilities/Idm/LoadMf6FileType.f90 +++ b/src/Utilities/Idm/LoadMf6FileType.f90 @@ -50,7 +50,7 @@ module LoadMf6FileTypeModule subroutine idm_load_from_blockparser(parser, filetype, & component_type, subcomponent_type, & component_name, subcomponent_name, & - subpackages, iout) + iout) use SimVariablesModule, only: idm_context type(BlockParserType), intent(inout) :: parser !< block parser character(len=*), intent(in) :: filetype !< file type to load, such as DIS6, DISV6, NPF6 @@ -58,7 +58,6 @@ subroutine idm_load_from_blockparser(parser, filetype, & character(len=*), intent(in) :: subcomponent_type !< subcomponent type, such as DIS or NPF character(len=*), intent(in) :: component_name !< component name, such as MYGWFMODEL character(len=*), intent(in) :: subcomponent_name !< subcomponent name, such as MYWELLPACKAGE - character(len=*), dimension(:), intent(in) :: subpackages !< array of subpackage types, such as ["TVK6", "OBS6"] integer(I4B), intent(in) :: iout !< unit number for output integer(I4B) :: iblock !< consecutive block number as defined in definition file type(ModflowInputType) :: mf6_input !< ModflowInputType @@ -68,7 +67,7 @@ subroutine idm_load_from_blockparser(parser, filetype, & ! -- construct input object mf6_input = getModflowInput(filetype, component_type, & subcomponent_type, component_name, & - subcomponent_name, subpackages) + subcomponent_name) ! ! -- model shape memory path componentMemPath = create_mem_path(component=mf6_input%component_name, & @@ -93,9 +92,6 @@ subroutine idm_load_from_blockparser(parser, filetype, & ! -- close logging statement call idm_log_close(mf6_input%component_name, & mf6_input%subcomponent_name, iout) - ! - ! -- release allocated memory - call mf6_input%destroy() end subroutine idm_load_from_blockparser !> @brief procedure to load a block @@ -120,11 +116,13 @@ subroutine parse_block(parser, mf6_input, iblock, mshape, iout) type(MemoryType), pointer :: mt ! ! -- disu vertices/cell2d blocks are contingent on NVERT dimension - if (mf6_input%file_type == 'DISU6' .and. & - (mf6_input%p_block_dfns(iblock)%blockname == 'VERTICES' .or. & - mf6_input%p_block_dfns(iblock)%blockname == 'CELL2D')) then - call get_from_memorylist('NVERT', mf6_input%memoryPath, mt, found, .false.) - if (.not. found .or. mt%intsclr == 0) return + if (mf6_input%file_type == 'DISU6') then + if (mf6_input%p_block_dfns(iblock)%blockname == 'VERTICES' .or. & + mf6_input%p_block_dfns(iblock)%blockname == 'CELL2D') then + call get_from_memorylist('NVERT', mf6_input%memoryPath, mt, found, & + .false.) + if (.not. found .or. mt%intsclr == 0) return + end if end if ! ! -- block open/close support @@ -156,40 +154,65 @@ subroutine parse_block(parser, mf6_input, iblock, mshape, iout) return end subroutine parse_block - !> @brief check subpackage - !! - !! Check and make sure that the subpackage is valid for - !! this input file and load the filename of the subpackage - !! into the memory manager. - !! - !< - subroutine subpackage_check(parser, mf6_input, checktag, iout) + subroutine parse_iofile_tag(parser, mf6_input, iblock, mshape, tag, found, & + iout) + use InputDefinitionSelectorModule, only: split_record_definition type(BlockParserType), intent(inout) :: parser !< block parser type(ModflowInputType), intent(in) :: mf6_input !< ModflowInputType - character(len=LINELENGTH), intent(in) :: checktag !< subpackage string, such as TVK6 + integer(I4B), intent(in) :: iblock !< consecutive block number as defined in definition file + integer(I4B), dimension(:), contiguous, pointer, intent(inout) :: mshape !< model shape + character(len=LINELENGTH), intent(in) :: tag + logical(LGP), intent(inout) :: found !< file tag was identified and loaded integer(I4B), intent(in) :: iout !< unit number for output - character(len=LINELENGTH) :: tag, fname_tag type(InputParamDefinitionType), pointer :: idt !< input data type object describing this record - integer(I4B) :: isubpkg - - do isubpkg = 1, size(mf6_input%subpackages) - if (checktag == mf6_input%subpackages(isubpkg)) then - fname_tag = trim(checktag)//'_FILENAME' - call parser%GetStringCaps(tag) - if (tag == 'FILEIN') then - idt => get_param_definition_type(mf6_input%p_param_dfns, & - mf6_input%component_type, & - mf6_input%subcomponent_type, & - fname_tag) - call load_string_type(parser, idt, mf6_input%memoryPath, iout) - else - errmsg = 'Subpackage keyword must be followed by "FILEIN" '// & - 'then by filename.' + character(len=40), dimension(:), allocatable :: words + integer(I4B) :: nwords + character(len=LINELENGTH) :: io_tag + ! + ! -- initialization + found = .false. + ! + ! -- get tokens in matching definition + call split_record_definition(mf6_input%p_param_dfns, & + mf6_input%component_type, & + mf6_input%subcomponent_type, & + tag, nwords, words) + ! + ! -- a filein/fileout record tag definition has 4 tokens + if (nwords == 4) then + ! + ! -- verify third definition token is FILEIN/FILEOUT + if (words(3) == 'FILEIN' .or. words(3) == 'FILEOUT') then + ! + ! -- read 3rd token + call parser%GetStringCaps(io_tag) + ! + ! -- check if 3rd token matches definition + if (.not. (io_tag == words(3))) then + errmsg = 'Expected "'//trim(words(3))//'" following keyword "'// & + trim(tag)//'" but instead found "'//trim(io_tag)//'"' call store_error(errmsg) + call parser%StoreErrorUnit() + ! + ! -- matches, read and load file name + else + idt => & + get_param_definition_type(mf6_input%p_param_dfns, & + mf6_input%component_type, & + mf6_input%subcomponent_type, & + mf6_input%p_block_dfns(iblock)%blockname, & + words(4)) + call load_string_type(parser, idt, mf6_input%memoryPath, iout) + ! + ! -- io tag loaded + found = .true. end if end if - end do - end subroutine subpackage_check + end if + ! + ! -- deallocate words + if (allocated(words)) deallocate (words) + end subroutine parse_iofile_tag !> @brief load an individual input record into memory !! @@ -208,6 +231,7 @@ recursive subroutine parse_tag(parser, mf6_input, iblock, mshape, iout, & logical(LGP), intent(in) :: recursive_call !< true if recursive call character(len=LINELENGTH) :: tag type(InputParamDefinitionType), pointer :: idt !< input data type object describing this record + logical(LGP) :: found_io_tag ! ! -- read tag name call parser%GetStringCaps(tag) @@ -222,17 +246,31 @@ recursive subroutine parse_tag(parser, mf6_input, iblock, mshape, iout, & idt => get_param_definition_type(mf6_input%p_param_dfns, & mf6_input%component_type, & mf6_input%subcomponent_type, & + mf6_input%p_block_dfns(iblock)%blockname, & tag) ! ! -- allocate and load data type select case (idt%datatype) case ('KEYWORD') - call load_keyword_type(parser, idt, mf6_input%memoryPath, iout) ! - ! -- load filename if subpackage tag - call subpackage_check(parser, mf6_input, tag, iout) + ! -- initialize, not a filein/fileout tag + found_io_tag = .false. + ! + ! -- if in record tag check and load if input/output file + if (idt%in_record) then + ! + ! -- identify and load the file name + call parse_iofile_tag(parser, mf6_input, iblock, mshape, tag, & + found_io_tag, iout) + end if + ! + if (.not. found_io_tag) then + ! + ! -- load standard keyword tag + call load_keyword_type(parser, idt, mf6_input%memoryPath, iout) + end if ! - ! -- set as dev option + ! -- check/set as dev option if (mf6_input%p_block_dfns(iblock)%blockname == 'OPTIONS' .and. & idt%tagname(1:4) == 'DEV_') then call parser%DevOpt() @@ -318,6 +356,7 @@ subroutine parse_structarray_block(parser, mf6_input, iblock, mshape, iout) idt => get_param_definition_type(mf6_input%p_param_dfns, & mf6_input%component_type, & mf6_input%subcomponent_type, & + mf6_input%p_block_dfns(iblock)%blockname, & words(icol + 1)) ! ! -- allocate variable in memory manager diff --git a/src/Utilities/Idm/ModflowInput.f90 b/src/Utilities/Idm/ModflowInput.f90 index 5ed0aaba08a..6bcd78b0d51 100644 --- a/src/Utilities/Idm/ModflowInput.f90 +++ b/src/Utilities/Idm/ModflowInput.f90 @@ -39,12 +39,9 @@ module ModflowInputModule character(len=LENCOMPONENTNAME) :: subcomponent_name character(len=LENMEMPATH) :: memoryPath character(len=LENMEMPATH) :: component - character(len=LENPACKAGETYPE), allocatable, dimension(:) :: subpackages type(InputBlockDefinitionType), dimension(:), pointer :: p_block_dfns type(InputParamDefinitionType), dimension(:), pointer :: p_aggregate_dfns type(InputParamDefinitionType), dimension(:), pointer :: p_param_dfns - contains - procedure :: destroy end type ModflowInputType contains @@ -52,15 +49,13 @@ module ModflowInputModule !> @brief function to return ModflowInputType !< function getModflowInput(ftype, component_type, & - subcomponent_type, component_name, subcomponent_name, & - subpackages) & + subcomponent_type, component_name, subcomponent_name) & result(mf6_input) character(len=*), intent(in) :: ftype !< file type to load, such as DIS6, DISV6, NPF6 character(len=*), intent(in) :: component_type !< component type, such as GWF or GWT character(len=*), intent(in) :: subcomponent_type !< subcomponent type, such as DIS or NPF character(len=*), intent(in) :: component_name !< component name, such as MYGWFMODEL character(len=*), intent(in) :: subcomponent_name !< subcomponent name, such as MYWELLPACKAGE - character(len=*), dimension(:), intent(in) :: subpackages !< array of subpackage types, such as ["TVK6", "OBS6"] type(ModflowInputType) :: mf6_input mf6_input%file_type = trim(ftype) @@ -68,8 +63,6 @@ function getModflowInput(ftype, component_type, & mf6_input%subcomponent_type = trim(subcomponent_type) mf6_input%component_name = trim(component_name) mf6_input%subcomponent_name = trim(subcomponent_name) - allocate (mf6_input%subpackages(size(subpackages))) - mf6_input%subpackages = subpackages mf6_input%memoryPath = create_mem_path(component_name, subcomponent_name, & idm_context) @@ -80,14 +73,4 @@ function getModflowInput(ftype, component_type, & mf6_input%p_param_dfns => param_definitions(mf6_input%component) end function getModflowInput - !> @brief function to release ModflowInputType allocated memory - !< - subroutine destroy(this) - class(ModflowInputType) :: this !< ModflowInputType - - if (allocated(this%subpackages)) then - deallocate (this%subpackages) - end if - end subroutine destroy - end module ModflowInputModule From 84c3893ffa31fd7d19cf6d94d56d61858d5baeea Mon Sep 17 00:00:00 2001 From: mjreno Date: Mon, 26 Dec 2022 11:20:57 -0500 Subject: [PATCH 015/123] refactor(idm): consolidate package input context load in model create (#1127) * consolidate idm input_load calls upward into the model context * run build_makefiles.py * clean-up * place package input load in appropriate model scope * update pymake Co-authored-by: mjreno --- make/makefile | 72 +++++++++++----------- src/Model/GroundWaterFlow/gwf3.f90 | 41 ++++++++++++ src/Model/GroundWaterFlow/gwf3dis8.f90 | 18 ++---- src/Model/GroundWaterFlow/gwf3disu8.f90 | 13 +--- src/Model/GroundWaterFlow/gwf3disv8.f90 | 19 ++---- src/Model/GroundWaterFlow/gwf3npf8.f90 | 27 +++----- src/Model/GroundWaterTransport/gwt1.f90 | 42 ++++++++++++- src/Model/GroundWaterTransport/gwt1dsp.f90 | 8 --- src/Model/NumericalModel.f90 | 36 +++++++++++ src/Utilities/Idm/IdmMf6FileLoader.f90 | 42 ++++++++++++- utils/mf5to6/make/makefile | 6 +- 11 files changed, 219 insertions(+), 105 deletions(-) diff --git a/make/makefile b/make/makefile index 6791ed45b92..15e3c1c565d 100644 --- a/make/makefile +++ b/make/makefile @@ -7,28 +7,28 @@ include ./makedefaults SOURCEDIR1=../src SOURCEDIR2=../src/Exchange SOURCEDIR3=../src/Model -SOURCEDIR4=../src/Model/Geometry -SOURCEDIR5=../src/Model/ModelUtilities -SOURCEDIR6=../src/Model/Connection +SOURCEDIR4=../src/Model/Connection +SOURCEDIR5=../src/Model/Geometry +SOURCEDIR6=../src/Model/GroundWaterFlow SOURCEDIR7=../src/Model/GroundWaterTransport -SOURCEDIR8=../src/Model/GroundWaterFlow +SOURCEDIR8=../src/Model/ModelUtilities SOURCEDIR9=../src/Solution SOURCEDIR10=../src/Solution/LinearMethods SOURCEDIR11=../src/Timing SOURCEDIR12=../src/Utilities -SOURCEDIR13=../src/Utilities/TimeSeries -SOURCEDIR14=../src/Utilities/Libraries -SOURCEDIR15=../src/Utilities/Libraries/rcm -SOURCEDIR16=../src/Utilities/Libraries/sparsekit -SOURCEDIR17=../src/Utilities/Libraries/sparskit2 -SOURCEDIR18=../src/Utilities/Libraries/blas -SOURCEDIR19=../src/Utilities/Libraries/daglib -SOURCEDIR20=../src/Utilities/Idm +SOURCEDIR13=../src/Utilities/ArrayRead +SOURCEDIR14=../src/Utilities/Idm +SOURCEDIR15=../src/Utilities/Libraries +SOURCEDIR16=../src/Utilities/Libraries/blas +SOURCEDIR17=../src/Utilities/Libraries/daglib +SOURCEDIR18=../src/Utilities/Libraries/rcm +SOURCEDIR19=../src/Utilities/Libraries/sparsekit +SOURCEDIR20=../src/Utilities/Libraries/sparskit2 SOURCEDIR21=../src/Utilities/Matrix -SOURCEDIR22=../src/Utilities/Observation -SOURCEDIR23=../src/Utilities/OutputControl -SOURCEDIR24=../src/Utilities/Memory -SOURCEDIR25=../src/Utilities/ArrayRead +SOURCEDIR22=../src/Utilities/Memory +SOURCEDIR23=../src/Utilities/Observation +SOURCEDIR24=../src/Utilities/OutputControl +SOURCEDIR25=../src/Utilities/TimeSeries VPATH = \ ${SOURCEDIR1} \ @@ -96,17 +96,9 @@ $(OBJDIR)/SmoothingFunctions.o \ $(OBJDIR)/MatrixBase.o \ $(OBJDIR)/ListReader.o \ $(OBJDIR)/Connections.o \ -$(OBJDIR)/DiscretizationBase.o \ -$(OBJDIR)/TimeArray.o \ -$(OBJDIR)/ObsOutput.o \ -$(OBJDIR)/TimeArraySeries.o \ -$(OBJDIR)/ObsOutputList.o \ -$(OBJDIR)/Observe.o \ $(OBJDIR)/InputDefinition.o \ $(OBJDIR)/ArrayReaderBase.o \ -$(OBJDIR)/TimeArraySeriesLink.o \ -$(OBJDIR)/ObsUtility.o \ -$(OBJDIR)/ObsContainer.o \ +$(OBJDIR)/DiscretizationBase.o \ $(OBJDIR)/VectorInt.o \ $(OBJDIR)/gwt1dspidm.o \ $(OBJDIR)/gwf3npf8idm.o \ @@ -114,26 +106,36 @@ $(OBJDIR)/gwf3disv8idm.o \ $(OBJDIR)/gwf3disu8idm.o \ $(OBJDIR)/gwf3dis8idm.o \ $(OBJDIR)/Integer2dReader.o \ -$(OBJDIR)/BudgetFileReader.o \ -$(OBJDIR)/TimeArraySeriesManager.o \ -$(OBJDIR)/PackageMover.o \ -$(OBJDIR)/Obs3.o \ -$(OBJDIR)/NumericalPackage.o \ -$(OBJDIR)/Budget.o \ +$(OBJDIR)/TimeArray.o \ +$(OBJDIR)/ObsOutput.o \ $(OBJDIR)/StructVector.o \ $(OBJDIR)/IdmLogger.o \ $(OBJDIR)/InputDefinitionSelector.o \ $(OBJDIR)/Integer1dReader.o \ $(OBJDIR)/Double2dReader.o \ $(OBJDIR)/Double1dReader.o \ +$(OBJDIR)/TimeArraySeries.o \ +$(OBJDIR)/ObsOutputList.o \ +$(OBJDIR)/Observe.o \ +$(OBJDIR)/StructArray.o \ +$(OBJDIR)/ModflowInput.o \ +$(OBJDIR)/LayeredArrayReader.o \ +$(OBJDIR)/TimeArraySeriesLink.o \ +$(OBJDIR)/ObsUtility.o \ +$(OBJDIR)/ObsContainer.o \ +$(OBJDIR)/BudgetFileReader.o \ +$(OBJDIR)/LoadMf6FileType.o \ +$(OBJDIR)/TimeArraySeriesManager.o \ +$(OBJDIR)/PackageMover.o \ +$(OBJDIR)/Obs3.o \ +$(OBJDIR)/NumericalPackage.o \ +$(OBJDIR)/Budget.o \ $(OBJDIR)/sort.o \ $(OBJDIR)/SfrCrossSectionUtils.o \ $(OBJDIR)/BudgetTerm.o \ +$(OBJDIR)/IdmMf6FileLoader.o \ $(OBJDIR)/BoundaryPackage.o \ $(OBJDIR)/BaseModel.o \ -$(OBJDIR)/StructArray.o \ -$(OBJDIR)/ModflowInput.o \ -$(OBJDIR)/LayeredArrayReader.o \ $(OBJDIR)/SfrCrossSectionManager.o \ $(OBJDIR)/dag_module.o \ $(OBJDIR)/BudgetObject.o \ @@ -144,7 +146,6 @@ $(OBJDIR)/HeadFileReader.o \ $(OBJDIR)/PrintSaveManager.o \ $(OBJDIR)/Xt3dAlgorithm.o \ $(OBJDIR)/gwf3tvbase8.o \ -$(OBJDIR)/LoadMf6FileType.o \ $(OBJDIR)/gwf3sfr8.o \ $(OBJDIR)/gwf3riv8.o \ $(OBJDIR)/gwf3maw8.o \ @@ -161,7 +162,6 @@ $(OBJDIR)/gwf3ic8.o \ $(OBJDIR)/Xt3dInterface.o \ $(OBJDIR)/gwf3tvk8.o \ $(OBJDIR)/MemoryManagerExt.o \ -$(OBJDIR)/IdmMf6FileLoader.o \ $(OBJDIR)/gwf3vsc8.o \ $(OBJDIR)/GwfNpfOptions.o \ $(OBJDIR)/CellWithNbrs.o \ diff --git a/src/Model/GroundWaterFlow/gwf3.f90 b/src/Model/GroundWaterFlow/gwf3.f90 index 46dcab65d32..f6d7b52f10f 100644 --- a/src/Model/GroundWaterFlow/gwf3.f90 +++ b/src/Model/GroundWaterFlow/gwf3.f90 @@ -89,6 +89,7 @@ module GwfModule procedure :: gwf_ot_flow procedure :: gwf_ot_dv procedure :: gwf_ot_bdsummary + procedure :: load_input_context => gwf_load_input_context ! end type GwfModelType @@ -253,16 +254,22 @@ subroutine gwf_cr(filename, id, modelname) ! ! -- Create discretization object if (indis6 > 0) then + call this%load_input_context('DIS6', this%name, 'DIS', indis, this%iout) call dis_cr(this%dis, this%name, indis, this%iout) elseif (indisu6 > 0) then + call this%load_input_context('DISU6', this%name, 'DISU', indis, this%iout) call disu_cr(this%dis, this%name, indis, this%iout) elseif (indisv6 > 0) then + call this%load_input_context('DISV6', this%name, 'DISV', indis, this%iout) call disv_cr(this%dis, this%name, indis, this%iout) end if ! ! -- Create utility objects call budget_cr(this%budget, this%name) ! + ! -- Load input context for currently supported packages + call this%load_input_context('NPF6', this%name, 'NPF', this%innpf, this%iout) + ! ! -- Create packages that are tied directly to model call npf_cr(this%npf, this%name, this%innpf, this%iout) call xt3d_cr(this%xt3d, this%name, this%innpf, this%iout) @@ -1552,4 +1559,38 @@ function CastAsGwfModel(model) result(gwfModel) end function CastAsGwfModel + !> @brief Load input context for supported package + !< + subroutine gwf_load_input_context(this, filtyp, modelname, pkgname, inunit, & + iout, ipaknum) + ! -- modules + use IdmMf6FileLoaderModule, only: input_load + ! -- dummy + class(GwfModelType) :: this + character(len=*), intent(in) :: filtyp + character(len=*), intent(in) :: modelname + character(len=*), intent(in) :: pkgname + integer(I4B), intent(in) :: inunit + integer(I4B), intent(in) :: iout + integer(I4B), optional, intent(in) :: ipaknum + ! -- local +! ------------------------------------------------------------------------------ + ! + ! -- only load if there is a file to read + if (inunit <= 0) return + ! + ! -- Load model package input to input context + select case (filtyp) + case ('NPF6') + call input_load('NPF6', 'GWF', 'NPF', modelname, pkgname, inunit, iout) + case default + call this%NumericalModelType%load_input_context(filtyp, modelname, & + pkgname, inunit, iout, & + ipaknum) + end select + ! + ! -- return + return + end subroutine gwf_load_input_context + end module GwfModule diff --git a/src/Model/GroundWaterFlow/gwf3dis8.f90 b/src/Model/GroundWaterFlow/gwf3dis8.f90 index 886fab9af76..123ec841418 100644 --- a/src/Model/GroundWaterFlow/gwf3dis8.f90 +++ b/src/Model/GroundWaterFlow/gwf3dis8.f90 @@ -7,7 +7,6 @@ module GwfDisModule use InputOutputModule, only: get_node, URWORD, ulasav, ulaprufw, ubdsv1, & ubdsv06 use SimModule, only: count_errors, store_error, store_error_unit - use BlockParserModule, only: BlockParserType use MemoryManagerModule, only: mem_allocate use MemoryHelperModule, only: create_mem_path use TdisModule, only: kstp, kper, pertim, totim, delt @@ -100,13 +99,6 @@ subroutine dis_cr(dis, name_model, inunit, iout) if (iout > 0) then write (iout, fmtheader) inunit end if - ! - ! -- Initialize block parser - call dis%parser%Initialize(inunit, iout) - ! - ! -- Use the input data model routines to load the input data - ! into memory - call input_load(dis%parser, 'DIS6', 'GWF', 'DIS', name_model, 'DIS', iout) end if ! ! -- Return @@ -295,17 +287,17 @@ subroutine source_dimensions(this) if (this%nlay < 1) then call store_error( & 'NLAY was not specified or was specified incorrectly.') - call this%parser%StoreErrorUnit() + call store_error_unit(this%inunit) end if if (this%nrow < 1) then call store_error( & 'NROW was not specified or was specified incorrectly.') - call this%parser%StoreErrorUnit() + call store_error_unit(this%inunit) end if if (this%ncol < 1) then call store_error( & 'NCOL was not specified or was specified incorrectly.') - call this%parser%StoreErrorUnit() + call store_error_unit(this%inunit) end if ! ! -- calculate nodesuser @@ -476,7 +468,7 @@ subroutine grid_finalize(this) call store_error('Model does not have any active nodes. & &Ensure IDOMAIN array has some values greater & &than zero.') - call this%parser%StoreErrorUnit() + call store_error_unit(this%inunit) end if ! ! -- Check cell thicknesses @@ -500,7 +492,7 @@ subroutine grid_finalize(this) end do end do if (count_errors() > 0) then - call this%parser%StoreErrorUnit() + call store_error_unit(this%inunit) end if ! ! -- Write message if reduced grid diff --git a/src/Model/GroundWaterFlow/gwf3disu8.f90 b/src/Model/GroundWaterFlow/gwf3disu8.f90 index 13c0bd84fcd..a9bb4eeca35 100644 --- a/src/Model/GroundWaterFlow/gwf3disu8.f90 +++ b/src/Model/GroundWaterFlow/gwf3disu8.f90 @@ -9,7 +9,6 @@ module GwfDisuModule use SimModule, only: count_errors, store_error, store_error_unit use SimVariablesModule, only: errmsg use BaseDisModule, only: DisBaseType - use BlockParserModule, only: BlockParserType use MemoryManagerModule, only: mem_allocate use TdisModule, only: kstp, kper, pertim, totim, delt @@ -121,14 +120,6 @@ subroutine disu_cr(dis, name_model, inunit, iout) write (iout, fmtheader) inunit end if ! - ! -- initialize parser and load the disu input file - call dis%parser%Initialize(inunit, iout) - ! - ! -- Use the input data model routines to load the input data - ! into memory - call input_load(dis%parser, 'DISU6', 'GWF', 'DISU', name_model, 'DISU', & - iout) - ! ! -- load disu call disnew%disu_load() end if @@ -228,7 +219,7 @@ subroutine grid_finalize(this) call store_error('Model does not have any active nodes. & &Ensure IDOMAIN array has some values greater & &than zero.') - call this%parser%StoreErrorUnit() + call store_error_unit(this%inunit) end if ! ! -- Write message if reduced grid @@ -709,7 +700,7 @@ subroutine source_dimensions(this) ! ! -- terminate if errors were detected if (count_errors() > 0) then - call this%parser%StoreErrorUnit() + call store_error_unit(this%inunit) end if ! ! -- allocate vectors that are the size of nodesuser diff --git a/src/Model/GroundWaterFlow/gwf3disv8.f90 b/src/Model/GroundWaterFlow/gwf3disv8.f90 index 5db18e0a428..dcfc20a08e1 100644 --- a/src/Model/GroundWaterFlow/gwf3disv8.f90 +++ b/src/Model/GroundWaterFlow/gwf3disv8.f90 @@ -9,7 +9,6 @@ module GwfDisvModule ubdsv06 use SimModule, only: count_errors, store_error, store_error_unit use DisvGeom, only: DisvGeomType - use BlockParserModule, only: BlockParserType use MemoryManagerModule, only: mem_allocate use MemoryHelperModule, only: create_mem_path use TdisModule, only: kstp, kper, pertim, totim, delt @@ -105,14 +104,6 @@ subroutine disv_cr(dis, name_model, inunit, iout) write (iout, fmtheader) inunit end if ! - ! -- initialize parser and load the disv input file - call dis%parser%Initialize(dis%inunit, dis%iout) - ! - ! -- Use the input data model routines to load the input data - ! into memory - call input_load(dis%parser, 'DISV6', 'GWF', 'DISV', name_model, 'DISV', & - iout) - ! ! -- load disv call disnew%disv_load() end if @@ -324,17 +315,17 @@ subroutine source_dimensions(this) if (this%nlay < 1) then call store_error( & 'NLAY was not specified or was specified incorrectly.') - call this%parser%StoreErrorUnit() + call store_error_unit(this%inunit) end if if (this%ncpl < 1) then call store_error( & 'NCPL was not specified or was specified incorrectly.') - call this%parser%StoreErrorUnit() + call store_error_unit(this%inunit) end if if (this%nvert < 1) then call store_error( & 'NVERT was not specified or was specified incorrectly.') - call this%parser%StoreErrorUnit() + call store_error_unit(this%inunit) end if ! ! -- Calculate nodesuser @@ -487,7 +478,7 @@ subroutine grid_finalize(this) call store_error('Model does not have any active nodes. & &Ensure IDOMAIN array has some values greater & &than zero.') - call this%parser%StoreErrorUnit() + call store_error_unit(this%inunit) end if ! ! -- Check cell thicknesses @@ -509,7 +500,7 @@ subroutine grid_finalize(this) end do end do if (count_errors() > 0) then - call this%parser%StoreErrorUnit() + call store_error_unit(this%inunit) end if ! ! -- Write message if reduced grid diff --git a/src/Model/GroundWaterFlow/gwf3npf8.f90 b/src/Model/GroundWaterFlow/gwf3npf8.f90 index dc1999cf419..39458742db8 100644 --- a/src/Model/GroundWaterFlow/gwf3npf8.f90 +++ b/src/Model/GroundWaterFlow/gwf3npf8.f90 @@ -13,7 +13,6 @@ module GwfNpfModule use GwfIcModule, only: GwfIcType use GwfVscModule, only: GwfVscType use Xt3dModule, only: Xt3dType - use BlockParserModule, only: BlockParserType use InputOutputModule, only: GetUnit, openfile use TvkModule, only: TvkType, tvk_cr use MemoryManagerModule, only: mem_allocate, mem_reallocate, & @@ -188,14 +187,6 @@ subroutine npf_cr(npfobj, name_model, inunit, iout) ! ! -- Print a message identifying the node property flow package. write (iout, fmtheader) inunit - ! - ! -- Initialize block parser and read options - call npfobj%parser%Initialize(inunit, iout) - ! - ! -- Use the input data model routines to load the input data - ! into memory - call input_load(npfobj%parser, 'NPF6', 'GWF', 'NPF', npfobj%name_model, & - 'NPF', iout) end if ! ! -- Return @@ -1581,7 +1572,7 @@ subroutine check_options(this) ! SPECIFICATIONS: ! ------------------------------------------------------------------------------ ! -- modules - use SimModule, only: store_error, count_errors + use SimModule, only: store_error, count_errors, store_error_unit use ConstantsModule, only: LINELENGTH ! -- dummy class(GwfNpftype) :: this @@ -1678,7 +1669,7 @@ subroutine check_options(this) ! ! -- Terminate if errors if (count_errors() > 0) then - call this%parser%StoreErrorUnit() + call store_error_unit(this%inunit) end if ! ! -- Return @@ -1842,7 +1833,7 @@ subroutine prepcheck(this) ! SPECIFICATIONS: ! ------------------------------------------------------------------------------ use ConstantsModule, only: LINELENGTH, DPIO180 - use SimModule, only: store_error, count_errors + use SimModule, only: store_error, count_errors, store_error_unit ! -- dummy class(GwfNpfType) :: this ! -- local @@ -1987,7 +1978,7 @@ subroutine prepcheck(this) ! ! -- terminate if data errors if (count_errors() > 0) then - call this%parser%StoreErrorUnit() + call store_error_unit(this%inunit) end if return @@ -2005,7 +1996,7 @@ end subroutine prepcheck !< subroutine preprocess_input(this) use ConstantsModule, only: LINELENGTH - use SimModule, only: store_error, count_errors + use SimModule, only: store_error, count_errors, store_error_unit class(GwfNpfType) :: this !< the instance of the NPF package ! local integer(I4B) :: n, m, ii, nn @@ -2131,7 +2122,7 @@ subroutine preprocess_input(this) end if end do if (count_errors() > 0) then - call this%parser%StoreErrorUnit() + call store_error_unit(this%inunit) end if ! ! -- Calculate condsat, but only if xt3d is not active. If xt3d is @@ -2320,7 +2311,7 @@ subroutine sgwf_npf_wetdry(this, kiter, hnew) ! ------------------------------------------------------------------------------ ! -- modules use TdisModule, only: kstp, kper - use SimModule, only: store_error + use SimModule, only: store_error, store_error_unit use ConstantsModule, only: LINELENGTH ! -- dummy class(GwfNpfType) :: this @@ -2380,7 +2371,7 @@ subroutine sgwf_npf_wetdry(this, kiter, hnew) call store_error(errmsg) write (errmsg, fmttopbot) ttop, bbot call store_error(errmsg) - call this%parser%StoreErrorUnit() + call store_error_unit(this%inunit) end if ! ! -- Calculate saturated thickness @@ -2402,7 +2393,7 @@ subroutine sgwf_npf_wetdry(this, kiter, hnew) call this%dis%noder_to_string(n, nodestr) write (errmsg, fmtni) trim(adjustl(nodestr)), kiter, kstp, kper call store_error(errmsg) - call this%parser%StoreErrorUnit() + call store_error_unit(this%inunit) end if this%ibound(n) = 0 end if diff --git a/src/Model/GroundWaterTransport/gwt1.f90 b/src/Model/GroundWaterTransport/gwt1.f90 index 32d94f25f0f..aa06b49403d 100644 --- a/src/Model/GroundWaterTransport/gwt1.f90 +++ b/src/Model/GroundWaterTransport/gwt1.f90 @@ -82,7 +82,7 @@ module GwtModule procedure, private :: gwt_ot_dv procedure, private :: gwt_ot_bdsummary procedure, private :: gwt_ot_obs - + procedure :: load_input_context => gwt_load_input_context end type GwtModelType ! -- Module variables constant for simulation @@ -228,16 +228,22 @@ subroutine gwt_cr(filename, id, modelname) ! ! -- Create discretization object if (indis6 > 0) then + call this%load_input_context('DIS6', this%name, 'DIS', indis, this%iout) call dis_cr(this%dis, this%name, indis, this%iout) elseif (indisu6 > 0) then + call this%load_input_context('DISU6', this%name, 'DISU', indis, this%iout) call disu_cr(this%dis, this%name, indis, this%iout) elseif (indisv6 > 0) then + call this%load_input_context('DISV6', this%name, 'DISV', indis, this%iout) call disv_cr(this%dis, this%name, indis, this%iout) end if ! ! -- Create utility objects call budget_cr(this%budget, this%name) ! + ! -- Load input context for currently supported packages + call this%load_input_context('DSP6', this%name, 'DSP', this%indsp, this%iout) + ! ! -- Create packages that are tied directly to model call ic_cr(this%ic, this%name, this%inic, this%iout, this%dis) call fmi_cr(this%fmi, this%name, this%infmi, this%iout) @@ -1282,4 +1288,38 @@ function CastAsGwtModel(model) result(gwtmodel) end function CastAsGwtModel + !> @brief Load input context for supported package + !< + subroutine gwt_load_input_context(this, filtyp, modelname, pkgname, inunit, & + iout, ipaknum) + ! -- modules + use IdmMf6FileLoaderModule, only: input_load + ! -- dummy + class(GwtModelType) :: this + character(len=*), intent(in) :: filtyp + character(len=*), intent(in) :: modelname + character(len=*), intent(in) :: pkgname + integer(I4B), intent(in) :: inunit + integer(I4B), intent(in) :: iout + integer(I4B), optional, intent(in) :: ipaknum + ! -- local +! ------------------------------------------------------------------------------ + ! + ! -- only load if there is a file to read + if (inunit <= 0) return + ! + ! -- Load model package input to input context + select case (filtyp) + case ('DSP6') + call input_load('DSP6', 'GWT', 'DSP', modelname, pkgname, inunit, iout) + case default + call this%NumericalModelType%load_input_context(filtyp, modelname, & + pkgname, inunit, iout, & + ipaknum) + end select + ! + ! -- return + return + end subroutine gwt_load_input_context + end module GwtModule diff --git a/src/Model/GroundWaterTransport/gwt1dsp.f90 b/src/Model/GroundWaterTransport/gwt1dsp.f90 index c61c926267c..e052ffd4778 100644 --- a/src/Model/GroundWaterTransport/gwt1dsp.f90 +++ b/src/Model/GroundWaterTransport/gwt1dsp.f90 @@ -115,14 +115,6 @@ subroutine dsp_cr(dspobj, name_model, inunit, iout, fmi) if (dspobj%iout > 0) then write (dspobj%iout, fmtdsp) dspobj%inunit end if - ! - ! -- Initialize block parser - call dspobj%parser%Initialize(dspobj%inunit, dspobj%iout) - ! - ! -- Use the input data model routines to load the input data - ! into memory - call input_load(dspobj%parser, 'DSP6', 'GWT', 'DSP', dspobj%name_model, & - 'DSP', iout) end if ! ! -- Return diff --git a/src/Model/NumericalModel.f90 b/src/Model/NumericalModel.f90 index ea6d6dfa5f2..96b10b221aa 100644 --- a/src/Model/NumericalModel.f90 +++ b/src/Model/NumericalModel.f90 @@ -73,6 +73,7 @@ module NumericalModelModule procedure :: get_mcellid procedure :: get_mnodeu procedure :: get_iasym + procedure :: load_input_context end type NumericalModelType contains @@ -439,4 +440,39 @@ function GetNumericalModelFromList(list, idx) result(res) return end function GetNumericalModelFromList + !> @brief Load input context for supported package + !< + subroutine load_input_context(this, filtyp, modelname, pkgname, inunit, iout, & + ipaknum) + ! -- modules + use IdmMf6FileLoaderModule, only: input_load + ! -- dummy + class(NumericalModelType) :: this + character(len=*), intent(in) :: filtyp + character(len=*), intent(in) :: modelname + character(len=*), intent(in) :: pkgname + integer(I4B), intent(in) :: inunit + integer(I4B), intent(in) :: iout + integer(I4B), optional, intent(in) :: ipaknum + ! -- local +! ------------------------------------------------------------------------------ + ! + ! -- only load if there is a file to read + if (inunit <= 0) return + ! + ! -- Load model package input to input context + select case (filtyp) + case ('DIS6') + call input_load('DIS6', 'GWF', 'DIS', modelname, pkgname, inunit, iout) + case ('DISU6') + call input_load('DISU6', 'GWF', 'DISU', modelname, pkgname, inunit, iout) + case ('DISV6') + call input_load('DISV6', 'GWF', 'DISV', modelname, pkgname, inunit, iout) + case default + end select + ! + ! -- return + return + end subroutine load_input_context + end module NumericalModelModule diff --git a/src/Utilities/Idm/IdmMf6FileLoader.f90 b/src/Utilities/Idm/IdmMf6FileLoader.f90 index 5212b1a516f..57de69f32d7 100644 --- a/src/Utilities/Idm/IdmMf6FileLoader.f90 +++ b/src/Utilities/Idm/IdmMf6FileLoader.f90 @@ -16,7 +16,7 @@ module IdmMf6FileLoaderModule public :: input_load interface input_load - module procedure input_load_blockparser + module procedure input_load_blockparser, input_load_generic end interface input_load !> @brief derived type for storing package loader @@ -92,4 +92,44 @@ subroutine input_load_blockparser(parser, filetype, & return end subroutine input_load_blockparser + !> @brief main entry to mf6 input load + !< + subroutine input_load_generic(filetype, & + component_type, subcomponent_type, & + component_name, subcomponent_name, & + inunit, iout) + character(len=*), intent(in) :: filetype !< file type to load, such as DIS6, DISV6, NPF6 + character(len=*), intent(in) :: component_type !< component type, such as GWF or GWT + character(len=*), intent(in) :: subcomponent_type !< subcomponent type, such as DIS or NPF + character(len=*), intent(in) :: component_name !< component name, such as MYGWFMODEL + character(len=*), intent(in) :: subcomponent_name !< subcomponent name, such as MYWELLPACKAGE + integer(I4B), intent(in) :: inunit !< unit number for input + integer(I4B), intent(in) :: iout !< unit number for output + type(BlockParserType), allocatable :: parser !< block parser + type(ModflowInputType) :: mf6_input + type(PackageLoad) :: pkgloader + ! + ! -- create description of input + mf6_input = getModflowInput(filetype, component_type, & + subcomponent_type, component_name, & + subcomponent_name) + ! + ! -- set mf6 parser based package loader by file type + select case (filetype) + case default + allocate (parser) + call parser%Initialize(inunit, iout) + pkgloader%load_package => generic_mf6_load + end select + ! + ! -- invoke the selected load routine + call pkgloader%load_package(parser, mf6_input, iout) + ! + ! -- deallocate + if (allocated(parser)) deallocate (parser) + ! + ! -- return + return + end subroutine input_load_generic + end module IdmMf6FileLoaderModule diff --git a/utils/mf5to6/make/makefile b/utils/mf5to6/make/makefile index e286aaf1171..e1c0b88e1e3 100644 --- a/utils/mf5to6/make/makefile +++ b/utils/mf5to6/make/makefile @@ -6,9 +6,9 @@ include ./makedefaults # Define the source file directories SOURCEDIR1=../src SOURCEDIR2=../src/LGR -SOURCEDIR3=../src/Preproc -SOURCEDIR4=../src/MF2005 -SOURCEDIR5=../src/NWT +SOURCEDIR3=../src/MF2005 +SOURCEDIR4=../src/NWT +SOURCEDIR5=../src/Preproc SOURCEDIR6=../../../src/Utilities/Memory SOURCEDIR7=../../../src/Utilities/TimeSeries SOURCEDIR8=../../../src/Utilities From 49876f8af8884773572c692f0c66ca579d85c7fb Mon Sep 17 00:00:00 2001 From: w-bonelli Date: Thu, 29 Dec 2022 13:26:18 -0500 Subject: [PATCH 016/123] fix(version): update meson.build to 6.5.0, autoupdate in update_version.py (#1128) --- distribution/update_version.py | 15 +++++++++++++++ meson.build | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/distribution/update_version.py b/distribution/update_version.py index fefa3000e33..d834b3eb43c 100644 --- a/distribution/update_version.py +++ b/distribution/update_version.py @@ -6,6 +6,7 @@ This script is used to update several files in the modflow6 repository, including: ../version.txt + ../meson.build ../doc/version.tex ../README.md ../DISCLAIMER.md @@ -48,6 +49,7 @@ version_file_path = project_root_path / "version.txt" touched_file_paths = [ version_file_path, + project_root_path / "meson.build", project_root_path / "doc" / "version.tex", project_root_path / "doc" / "version.py", project_root_path / "README.md", @@ -194,6 +196,18 @@ def update_version_txt_and_py( log_update(py_path, release_type, version) +def update_meson_build(release_type: ReleaseType, timestamp: datetime, version: Version): + path = project_root_path / "meson.build" + lines = open(path, "r").read().splitlines() + with open(path, "w") as f: + for line in lines: + if "version:" in line and "meson_version:" not in line: + line = f" version: '{version.major}.{version.minor}.{version.patch}'," + f.write(f"{line}\n") + + log_update(path, release_type, version) + + def update_version_tex( release_type: ReleaseType, timestamp: datetime, version: Version ): @@ -318,6 +332,7 @@ def update_version( with lock: update_version_txt_and_py(release_type, timestamp, version) + update_meson_build(release_type, timestamp, version) update_version_tex(release_type, timestamp, version) update_version_f90(release_type, timestamp, version) update_readme_and_disclaimer(release_type, timestamp, version) diff --git a/meson.build b/meson.build index dafb7893b27..f4e7493ae8e 100644 --- a/meson.build +++ b/meson.build @@ -1,7 +1,7 @@ project( 'MODFLOW 6', 'fortran', - version: '6.4.1', + version: '6.5.0', meson_version: '>= 0.59.0', default_options : [ 'b_vscrt=static_from_buildtype', # Link runtime libraries statically on Windows From ae32bf1b637bbb38155eba5721ca83a021d58d7f Mon Sep 17 00:00:00 2001 From: langevin-usgs Date: Wed, 4 Jan 2023 13:16:57 -0600 Subject: [PATCH 017/123] feat(uzf): add qfrommvr convergence check to UZF, LAK, SFR (#1119) --- autotest/simulation.py | 2 +- autotest/test_z01_testmodels_mf6.py | 5 ++- doc/ReleaseNotes/v6.5.0.tex | 8 ++-- src/Model/GroundWaterFlow/gwf3lak8.f90 | 50 ++++++++++++++++++++++- src/Model/GroundWaterFlow/gwf3sfr8.f90 | 50 ++++++++++++++++++++++- src/Model/GroundWaterFlow/gwf3uzf8.f90 | 42 +++++++++++++++++++ src/Model/ModelUtilities/PackageMover.f90 | 24 +++++++++-- 7 files changed, 167 insertions(+), 14 deletions(-) diff --git a/autotest/simulation.py b/autotest/simulation.py index d9eb0b7cdb6..18fb064f30c 100644 --- a/autotest/simulation.py +++ b/autotest/simulation.py @@ -771,7 +771,7 @@ def _compare_budgets(self, msgall, extensions="cbc"): f"{os.path.basename(fpth0)} - " + f"{key:16s} " + f"difference ({diffmax:10.4g}) " - + f"> {self.pdtol:10.4g} " + + f"> {vmin_tol:10.4g} " + f"at {indices.size} nodes " + f" [first location ({indices[0] + 1})] " + f"at time {t} " diff --git a/autotest/test_z01_testmodels_mf6.py b/autotest/test_z01_testmodels_mf6.py index ad4f6fae38b..de6a5339e94 100644 --- a/autotest/test_z01_testmodels_mf6.py +++ b/autotest/test_z01_testmodels_mf6.py @@ -189,8 +189,8 @@ def set_make_comparison(test): "test051_uzfp3_lakmvr_v2": ("6.2.1",), "test051_uzfp3_wellakmvr_v2": ("6.2.1",), "test045_lake4ss": ("6.2.2",), - "test056_mt3dms_usgs_gwtex_dev": ("6.2.2",), - "test056_mt3dms_usgs_gwtex_IR_dev": ("6.2.2",), + "test056_mt3dms_usgs_gwtex_dev": ("6.4.1",), + "test056_mt3dms_usgs_gwtex_IR_dev": ("6.4.1",), } make_comparison = True if test in compare_tests.keys(): @@ -200,6 +200,7 @@ def set_make_comparison(test): print(f"MODFLOW regression version='{version}'") if version in compare_tests[test]: make_comparison = False + print(f"Make comparison has been set to False.") return make_comparison diff --git a/doc/ReleaseNotes/v6.5.0.tex b/doc/ReleaseNotes/v6.5.0.tex index dde1bd149b9..7abe392aaf6 100644 --- a/doc/ReleaseNotes/v6.5.0.tex +++ b/doc/ReleaseNotes/v6.5.0.tex @@ -39,12 +39,12 @@ % \item xxx %\end{itemize} - %\underline{ADVANCED STRESS PACKAGES} - %\begin{itemize} - % \item xxx + \underline{ADVANCED STRESS PACKAGES} + \begin{itemize} + \item Added additional convergence checks to the Streamflow Routing (SFR), Lake (LAK) and Unsaturated Zone Flow (UZF) Packages to ensure that flows from the Water Mover (MVR) Package meet solver tolerance. Mover flows are converted into depths using the time step length and area, and the depths are compared to the Iterative Model Solution (IMS) DVCLOSE input parameter. If a depth is greater than DVCLOSE, then the iteration is marked as not converged. The maximum depth change between iterations and the advanced package feature number is written for each outer iteration to a comma-separated value file, provided the PACKAGE\_CONVERGENCE option is specified in the options block. % \item xxx % \item xxx - %\end{itemize} + \end{itemize} %\underline{SOLUTION} %\begin{itemize} diff --git a/src/Model/GroundWaterFlow/gwf3lak8.f90 b/src/Model/GroundWaterFlow/gwf3lak8.f90 index b46efce652b..25e4217d56a 100644 --- a/src/Model/GroundWaterFlow/gwf3lak8.f90 +++ b/src/Model/GroundWaterFlow/gwf3lak8.f90 @@ -4024,9 +4024,13 @@ subroutine lak_cc(this, innertot, kiter, iend, icnvgmod, cpak, ipak, dpak) integer(I4B) :: locdhmax integer(I4B) :: locdgwfmax integer(I4B) :: locdqoutmax + integer(I4B) :: locdqfrommvrmax integer(I4B) :: ntabrows integer(I4B) :: ntabcols integer(I4B) :: n + real(DP) :: q + real(DP) :: q0 + real(DP) :: qtolfact real(DP) :: area real(DP) :: gwf0 real(DP) :: gwf @@ -4045,6 +4049,8 @@ subroutine lak_cc(this, innertot, kiter, iend, icnvgmod, cpak, ipak, dpak) real(DP) :: dhmax real(DP) :: dgwfmax real(DP) :: dqoutmax + real(DP) :: dqfrommvr + real(DP) :: dqfrommvrmax ! format ! -------------------------------------------------------------------------- ! @@ -4054,9 +4060,11 @@ subroutine lak_cc(this, innertot, kiter, iend, icnvgmod, cpak, ipak, dpak) locdhmax = 0 locdgwfmax = 0 locdqoutmax = 0 + locdqfrommvrmax = 0 dhmax = DZERO dgwfmax = DZERO dqoutmax = DZERO + dqfrommvrmax = DZERO ! ! -- if not saving package convergence data on check convergence if ! the model is considered converged @@ -4077,6 +4085,9 @@ subroutine lak_cc(this, innertot, kiter, iend, icnvgmod, cpak, ipak, dpak) if (this%noutlets > 0) then ntabcols = ntabcols + 2 end if + if (this%imover == 1) then + ntabcols = ntabcols + 2 + end if ! ! -- setup table call table_cr(this%pakcsvtab, this%packName, '') @@ -4109,6 +4120,12 @@ subroutine lak_cc(this, innertot, kiter, iend, icnvgmod, cpak, ipak, dpak) tag = 'dqoutmax_loc' call this%pakcsvtab%initialize_column(tag, 15, alignment=TABLEFT) end if + if (this%imover == 1) then + tag = 'dqfrommvrmax' + call this%pakcsvtab%initialize_column(tag, 15, alignment=TABLEFT) + tag = 'dqfrommvrmax_loc' + call this%pakcsvtab%initialize_column(tag, 16, alignment=TABLEFT) + end if end if end if ! @@ -4127,12 +4144,15 @@ subroutine lak_cc(this, innertot, kiter, iend, icnvgmod, cpak, ipak, dpak) ! -- calculate surface area call this%lak_calculate_sarea(n, hlak, area) ! + ! -- set the Q to length factor + qtolfact = delt / area + ! ! -- change in gwf exchange dgwf = DZERO if (area > DZERO) then gwf0 = this%qgwf0(n) call this%lak_calculate_exchange(n, hlak, gwf) - dgwf = (gwf0 - gwf) * delt / area + dgwf = (gwf0 - gwf) * qtolfact end if ! ! -- change in outflows @@ -4143,10 +4163,18 @@ subroutine lak_cc(this, innertot, kiter, iend, icnvgmod, cpak, ipak, dpak) call this%lak_calculate_outlet_outflow(n, hlak0, inf, qout0) call this%lak_calculate_available(n, hlak, inf, ra, ro, qinf, ex) call this%lak_calculate_outlet_outflow(n, hlak, inf, qout) - dqout = (qout0 - qout) * delt / area + dqout = (qout0 - qout) * qtolfact end if end if ! + ! -- q from mvr + dqfrommvr = DZERO + if (this%imover == 1) then + q = this%pakmvrobj%get_qfrommvr(n) + q0 = this%pakmvrobj%get_qfrommvr0(n) + dqfrommvr = qtolfact * (q0 - q) + end if + ! ! -- evaluate magnitude of differences if (n == 1) then locdhmax = n @@ -4155,6 +4183,8 @@ subroutine lak_cc(this, innertot, kiter, iend, icnvgmod, cpak, ipak, dpak) dgwfmax = dgwf locdqoutmax = n dqoutmax = dqout + dqfrommvrmax = dqfrommvr + locdqfrommvrmax = n else if (abs(dh) > abs(dhmax)) then locdhmax = n @@ -4168,6 +4198,10 @@ subroutine lak_cc(this, innertot, kiter, iend, icnvgmod, cpak, ipak, dpak) locdqoutmax = n dqoutmax = dqout end if + if (ABS(dqfrommvr) > abs(dqfrommvrmax)) then + dqfrommvrmax = dqfrommvr + locdqfrommvrmax = n + end if end if end do final_check ! @@ -4195,6 +4229,14 @@ subroutine lak_cc(this, innertot, kiter, iend, icnvgmod, cpak, ipak, dpak) cpak = trim(cloc) end if end if + if (this%imover == 1) then + if (ABS(dqfrommvrmax) > abs(dpak)) then + ipak = locdqfrommvrmax + dpak = dqfrommvrmax + write (cloc, "(a,'-',a)") trim(this%packName), 'qfrommvr' + cpak = trim(cloc) + end if + end if ! ! -- write convergence data to package csv if (this%ipakcsv /= 0) then @@ -4213,6 +4255,10 @@ subroutine lak_cc(this, innertot, kiter, iend, icnvgmod, cpak, ipak, dpak) call this%pakcsvtab%add_term(dqoutmax) call this%pakcsvtab%add_term(locdqoutmax) end if + if (this%imover == 1) then + call this%pakcsvtab%add_term(dqfrommvrmax) + call this%pakcsvtab%add_term(locdqfrommvrmax) + end if ! ! -- finalize the package csv if (iend == 1) then diff --git a/src/Model/GroundWaterFlow/gwf3sfr8.f90 b/src/Model/GroundWaterFlow/gwf3sfr8.f90 index acd52c69f23..21e1c67e65f 100644 --- a/src/Model/GroundWaterFlow/gwf3sfr8.f90 +++ b/src/Model/GroundWaterFlow/gwf3sfr8.f90 @@ -2136,13 +2136,19 @@ subroutine sfr_cc(this, innertot, kiter, iend, icnvgmod, cpak, ipak, dpak) integer(I4B) :: ipakfail integer(I4B) :: locdhmax integer(I4B) :: locrmax + integer(I4B) :: locdqfrommvrmax integer(I4B) :: ntabrows integer(I4B) :: ntabcols integer(I4B) :: n + real(DP) :: q + real(DP) :: q0 + real(DP) :: qtolfact real(DP) :: dh real(DP) :: r real(DP) :: dhmax real(DP) :: rmax + real(DP) :: dqfrommvr + real(DP) :: dqfrommvrmax ! ! -- initialize local variables icheck = this%iconvchk @@ -2152,6 +2158,8 @@ subroutine sfr_cc(this, innertot, kiter, iend, icnvgmod, cpak, ipak, dpak) r = DZERO dhmax = DZERO rmax = DZERO + locdqfrommvrmax = 0 + dqfrommvrmax = DZERO ! ! -- if not saving package convergence data on check convergence if ! the model is considered converged @@ -2169,6 +2177,9 @@ subroutine sfr_cc(this, innertot, kiter, iend, icnvgmod, cpak, ipak, dpak) ! -- determine the number of columns and rows ntabrows = 1 ntabcols = 9 + if (this%imover == 1) then + ntabcols = ntabcols + 2 + end if ! ! -- setup table call table_cr(this%pakcsvtab, this%packName, '') @@ -2195,6 +2206,12 @@ subroutine sfr_cc(this, innertot, kiter, iend, icnvgmod, cpak, ipak, dpak) call this%pakcsvtab%initialize_column(tag, 15, alignment=TABLEFT) tag = 'dinflowmax_loc' call this%pakcsvtab%initialize_column(tag, 15, alignment=TABLEFT) + if (this%imover == 1) then + tag = 'dqfrommvrmax' + call this%pakcsvtab%initialize_column(tag, 15, alignment=TABLEFT) + tag = 'dqfrommvrmax_loc' + call this%pakcsvtab%initialize_column(tag, 16, alignment=TABLEFT) + end if end if end if ! @@ -2202,6 +2219,11 @@ subroutine sfr_cc(this, innertot, kiter, iend, icnvgmod, cpak, ipak, dpak) if (icheck /= 0) then final_check: do n = 1, this%maxbound if (this%iboundpak(n) == 0) cycle + ! + ! -- set the Q to length factor + qtolfact = delt / this%calc_surface_area(n) + ! + ! -- calculate stage change dh = this%stage0(n) - this%stage(n) ! ! -- evaluate flow difference if the time step is transient @@ -2209,7 +2231,15 @@ subroutine sfr_cc(this, innertot, kiter, iend, icnvgmod, cpak, ipak, dpak) r = this%usflow0(n) - this%usflow(n) ! ! -- normalize flow difference and convert to a depth - r = r * delt / this%calc_surface_area(n) + r = r * qtolfact + end if + ! + ! -- q from mvr + dqfrommvr = DZERO + if (this%imover == 1) then + q = this%pakmvrobj%get_qfrommvr(n) + q0 = this%pakmvrobj%get_qfrommvr0(n) + dqfrommvr = qtolfact * (q0 - q) end if ! ! -- evaluate magnitude of differences @@ -2218,6 +2248,8 @@ subroutine sfr_cc(this, innertot, kiter, iend, icnvgmod, cpak, ipak, dpak) dhmax = dh locrmax = n rmax = r + dqfrommvrmax = dqfrommvr + locdqfrommvrmax = n else if (abs(dh) > abs(dhmax)) then locdhmax = n @@ -2227,6 +2259,10 @@ subroutine sfr_cc(this, innertot, kiter, iend, icnvgmod, cpak, ipak, dpak) locrmax = n rmax = r end if + if (ABS(dqfrommvr) > abs(dqfrommvrmax)) then + dqfrommvrmax = dqfrommvr + locdqfrommvrmax = n + end if end if end do final_check ! @@ -2243,6 +2279,14 @@ subroutine sfr_cc(this, innertot, kiter, iend, icnvgmod, cpak, ipak, dpak) write (cloc, "(a,'-',a)") trim(this%packName), 'inflow' cpak = trim(cloc) end if + if (this%imover == 1) then + if (ABS(dqfrommvrmax) > abs(dpak)) then + ipak = locdqfrommvrmax + dpak = dqfrommvrmax + write (cloc, "(a,'-',a)") trim(this%packName), 'qfrommvr' + cpak = trim(cloc) + end if + end if ! ! -- write convergence data to package csv if (this%ipakcsv /= 0) then @@ -2257,6 +2301,10 @@ subroutine sfr_cc(this, innertot, kiter, iend, icnvgmod, cpak, ipak, dpak) call this%pakcsvtab%add_term(locdhmax) call this%pakcsvtab%add_term(rmax) call this%pakcsvtab%add_term(locrmax) + if (this%imover == 1) then + call this%pakcsvtab%add_term(dqfrommvrmax) + call this%pakcsvtab%add_term(locdqfrommvrmax) + end if ! ! -- finalize the package csv if (iend == 1) then diff --git a/src/Model/GroundWaterFlow/gwf3uzf8.f90 b/src/Model/GroundWaterFlow/gwf3uzf8.f90 index d3bf9d9022d..5327d5ee779 100644 --- a/src/Model/GroundWaterFlow/gwf3uzf8.f90 +++ b/src/Model/GroundWaterFlow/gwf3uzf8.f90 @@ -1198,9 +1198,12 @@ subroutine uzf_cc(this, innertot, kiter, iend, icnvgmod, cpak, ipak, dpak) integer(I4B) :: locdrejinfmax integer(I4B) :: locdrchmax integer(I4B) :: locdseepmax + integer(I4B) :: locdqfrommvrmax integer(I4B) :: ntabrows integer(I4B) :: ntabcols integer(I4B) :: n + real(DP) :: q + real(DP) :: q0 real(DP) :: qtolfact real(DP) :: drejinf real(DP) :: drejinfmax @@ -1208,6 +1211,8 @@ subroutine uzf_cc(this, innertot, kiter, iend, icnvgmod, cpak, ipak, dpak) real(DP) :: drchmax real(DP) :: dseep real(DP) :: dseepmax + real(DP) :: dqfrommvr + real(DP) :: dqfrommvrmax ! format ! -------------------------------------------------------------------------- ! @@ -1217,9 +1222,11 @@ subroutine uzf_cc(this, innertot, kiter, iend, icnvgmod, cpak, ipak, dpak) locdrejinfmax = 0 locdrchmax = 0 locdseepmax = 0 + locdqfrommvrmax = 0 drejinfmax = DZERO drchmax = DZERO dseepmax = DZERO + dqfrommvrmax = DZERO ! ! -- if not saving package convergence data on check convergence if ! the model is considered converged @@ -1238,6 +1245,9 @@ subroutine uzf_cc(this, innertot, kiter, iend, icnvgmod, cpak, ipak, dpak) if (this%iseepflag == 1) then ntabcols = ntabcols + 2 end if + if (this%imover == 1) then + ntabcols = ntabcols + 2 + end if ! ! -- setup table call table_cr(this%pakcsvtab, this%packName, '') @@ -1270,6 +1280,12 @@ subroutine uzf_cc(this, innertot, kiter, iend, icnvgmod, cpak, ipak, dpak) tag = 'dseepmax_loc' call this%pakcsvtab%initialize_column(tag, 15, alignment=TABLEFT) end if + if (this%imover == 1) then + tag = 'dqfrommvrmax' + call this%pakcsvtab%initialize_column(tag, 15, alignment=TABLEFT) + tag = 'dqfrommvrmax_loc' + call this%pakcsvtab%initialize_column(tag, 16, alignment=TABLEFT) + end if end if end if ! @@ -1292,6 +1308,14 @@ subroutine uzf_cc(this, innertot, kiter, iend, icnvgmod, cpak, ipak, dpak) dseep = qtolfact * (this%gwd0(n) - this%gwd(n)) end if ! + ! -- q from mvr + dqfrommvr = DZERO + if (this%imover == 1) then + q = this%pakmvrobj%get_qfrommvr(n) + q0 = this%pakmvrobj%get_qfrommvr0(n) + dqfrommvr = qtolfact * (q0 - q) + end if + ! ! -- evaluate magnitude of differences if (n == 1) then drejinfmax = drejinf @@ -1300,6 +1324,8 @@ subroutine uzf_cc(this, innertot, kiter, iend, icnvgmod, cpak, ipak, dpak) locdrchmax = n dseepmax = dseep locdseepmax = n + dqfrommvrmax = dqfrommvr + locdqfrommvrmax = n else if (ABS(drejinf) > abs(drejinfmax)) then drejinfmax = drejinf @@ -1313,6 +1339,10 @@ subroutine uzf_cc(this, innertot, kiter, iend, icnvgmod, cpak, ipak, dpak) dseepmax = dseep locdseepmax = n end if + if (ABS(dqfrommvr) > abs(dqfrommvrmax)) then + dqfrommvrmax = dqfrommvr + locdqfrommvrmax = n + end if end if end do final_check ! @@ -1337,6 +1367,14 @@ subroutine uzf_cc(this, innertot, kiter, iend, icnvgmod, cpak, ipak, dpak) cpak = trim(cloc) end if end if + if (this%imover == 1) then + if (ABS(dqfrommvrmax) > abs(dpak)) then + ipak = locdqfrommvrmax + dpak = dqfrommvrmax + write (cloc, "(a,'-',a)") trim(this%packName), 'qfrommvr' + cpak = trim(cloc) + end if + end if ! ! -- write convergence data to package csv if (this%ipakcsv /= 0) then @@ -1355,6 +1393,10 @@ subroutine uzf_cc(this, innertot, kiter, iend, icnvgmod, cpak, ipak, dpak) call this%pakcsvtab%add_term(dseepmax) call this%pakcsvtab%add_term(locdseepmax) end if + if (this%imover == 1) then + call this%pakcsvtab%add_term(dqfrommvrmax) + call this%pakcsvtab%add_term(locdqfrommvrmax) + end if ! ! -- finalize the package csv if (iend == 1) then diff --git a/src/Model/ModelUtilities/PackageMover.f90 b/src/Model/ModelUtilities/PackageMover.f90 index b0c43187838..98dc2a9c625 100644 --- a/src/Model/ModelUtilities/PackageMover.f90 +++ b/src/Model/ModelUtilities/PackageMover.f90 @@ -17,10 +17,11 @@ module PackageMoverModule integer(I4B), pointer :: nproviders integer(I4B), pointer :: nreceivers integer(I4B), dimension(:), pointer, contiguous :: iprmap => null() !< map between id1 and feature (needed for lake to map from outlet to lake number) - real(DP), dimension(:), pointer, contiguous :: qtformvr => null() - real(DP), dimension(:), pointer, contiguous :: qformvr => null() - real(DP), dimension(:), pointer, contiguous :: qtomvr => null() - real(DP), dimension(:), pointer, contiguous :: qfrommvr => null() + real(DP), dimension(:), pointer, contiguous :: qtformvr => null() !< total flow rate available for mover + real(DP), dimension(:), pointer, contiguous :: qformvr => null() !< currently available consumed water (changes during fc) + real(DP), dimension(:), pointer, contiguous :: qtomvr => null() !< actual amount of water sent to mover + real(DP), dimension(:), pointer, contiguous :: qfrommvr => null() !< actual amount of water received from mover + real(DP), dimension(:), pointer, contiguous :: qfrommvr0 => null() !< qfrommvr from previous iteration contains procedure :: ar @@ -31,6 +32,7 @@ module PackageMoverModule procedure :: allocate_scalars procedure :: allocate_arrays procedure :: get_qfrommvr + procedure :: get_qfrommvr0 procedure :: get_qtomvr procedure :: accumulate_qformvr @@ -51,6 +53,7 @@ subroutine set_packagemover_pointer(packagemover, memPath) call mem_setptr(packagemover%qformvr, 'QFORMVR', packagemover%memoryPath) call mem_setptr(packagemover%qtomvr, 'QTOMVR', packagemover%memoryPath) call mem_setptr(packagemover%qfrommvr, 'QFROMMVR', packagemover%memoryPath) + call mem_setptr(packagemover%qfrommvr0, 'QFROMMVR0', packagemover%memoryPath) end subroutine set_packagemover_pointer subroutine nulllify_packagemover_pointer(packagemover) @@ -63,6 +66,7 @@ subroutine nulllify_packagemover_pointer(packagemover) packagemover%qformvr => null() packagemover%qtomvr => null() packagemover%qfrommvr => null() + packagemover%qfrommvr0 => null() end subroutine nulllify_packagemover_pointer subroutine ar(this, nproviders, nreceivers, memoryPath) @@ -102,6 +106,7 @@ subroutine cf(this) ! ! -- set frommvr and qtomvr to zero do i = 1, this%nreceivers + this%qfrommvr0(i) = this%qfrommvr(i) this%qfrommvr(i) = DZERO end do do i = 1, this%nproviders @@ -135,6 +140,7 @@ subroutine da(this) call mem_deallocate(this%qformvr) call mem_deallocate(this%qtomvr) call mem_deallocate(this%qfrommvr) + call mem_deallocate(this%qfrommvr0) ! ! -- scalars call mem_deallocate(this%nproviders) @@ -169,6 +175,8 @@ subroutine allocate_arrays(this) call mem_allocate(this%qformvr, this%nproviders, 'QFORMVR', this%memoryPath) call mem_allocate(this%qtomvr, this%nproviders, 'QTOMVR', this%memoryPath) call mem_allocate(this%qfrommvr, this%nreceivers, 'QFROMMVR', this%memoryPath) + call mem_allocate(this%qfrommvr0, this%nreceivers, 'QFROMMVR0', & + this%memoryPath) ! ! -- initialize do i = 1, this%nproviders @@ -179,6 +187,7 @@ subroutine allocate_arrays(this) end do do i = 1, this%nreceivers this%qfrommvr(i) = DZERO + this%qfrommvr0(i) = DZERO end do ! ! -- return @@ -192,6 +201,13 @@ function get_qfrommvr(this, ireceiver) result(qfrommvr) qfrommvr = this%qfrommvr(ireceiver) end function get_qfrommvr + function get_qfrommvr0(this, ireceiver) result(qfrommvr0) + class(PackageMoverType) :: this + real(DP) :: qfrommvr0 + integer, intent(in) :: ireceiver + qfrommvr0 = this%qfrommvr0(ireceiver) + end function get_qfrommvr0 + function get_qtomvr(this, iprovider) result(qtomvr) class(PackageMoverType) :: this real(DP) :: qtomvr From 29dfdcfe03d1d73d48f244787b8241d3a396d416 Mon Sep 17 00:00:00 2001 From: w-bonelli Date: Fri, 6 Jan 2023 09:28:14 -0500 Subject: [PATCH 018/123] refactor(tests): use devtools fixtures (#1130) * use fixtures from devtools for loading external models and tracking target exes * convert adhoc --model, --package and --original-regression opts to pytest fixtures/CLI opts * use devtools and flopy in build_exes.py and get_exes.py, remove pymake usage * remove pymake usage from common_regression.py and external model tests * use GitHub API token for CI step to download executables * update developer docs --- .github/workflows/ci.yml | 8 + .github/workflows/docs.yml | 2 + .github/workflows/large.yml | 2 + DEVELOPER.md | 88 ++- autotest/build_exes.py | 163 +--- autotest/common_regression.py | 1005 ++++++++++++++++++++---- autotest/conftest.py | 49 ++ autotest/get_exes.py | 174 ++-- autotest/simulation.py | 44 +- autotest/test_gwf_henry_nr.py | 45 +- autotest/test_mf6_tmp_simulations.py | 21 +- autotest/test_z01_testmodels_mf6.py | 298 ++----- autotest/test_z02_testmodels_mf5to6.py | 246 +----- autotest/test_z03_examples.py | 247 +----- autotest/test_z03_largetestmodels.py | 207 +---- 15 files changed, 1250 insertions(+), 1349 deletions(-) create mode 100644 autotest/conftest.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c181447651d..4f8244733ea 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -149,6 +149,8 @@ jobs: - name: Get executables working-directory: modflow6/autotest + env: + GITHUB_TOKEN: ${{ github.token }} run: pytest -v --durations 0 get_exes.py - name: Test programs @@ -221,6 +223,8 @@ jobs: - name: Get executables working-directory: modflow6/autotest + env: + GITHUB_TOKEN: ${{ github.token }} run: pytest -v --durations 0 get_exes.py - name: Test modflow6 @@ -327,12 +331,16 @@ jobs: - name: Get executables if: runner.os != 'Windows' working-directory: modflow6/autotest + env: + GITHUB_TOKEN: ${{ github.token }} run: pytest -v --durations 0 get_exes.py - name: Get executables (Windows) if: runner.os == 'Windows' working-directory: modflow6/autotest shell: pwsh + env: + GITHUB_TOKEN: ${{ github.token }} run: pytest -v --durations 0 get_exes.py - name: Test programs diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index ff97a6ad8f6..b4d4bae2b5a 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -103,6 +103,8 @@ jobs: - name: Run benchmarks working-directory: modflow6/distribution + env: + GITHUB_TOKEN: ${{ github.token }} run: python benchmark.py - name: Run sphinx diff --git a/.github/workflows/large.yml b/.github/workflows/large.yml index 3abe23604ff..b18cff74882 100644 --- a/.github/workflows/large.yml +++ b/.github/workflows/large.yml @@ -93,6 +93,8 @@ jobs: - name: Get executables working-directory: modflow6/autotest + env: + GITHUB_TOKEN: ${{ github.token }} run: | pytest -v --durations 0 get_exes.py diff --git a/DEVELOPER.md b/DEVELOPER.md index ddafa361611..f9aa18a23b3 100644 --- a/DEVELOPER.md +++ b/DEVELOPER.md @@ -152,22 +152,17 @@ We also provide make files which can be used to build MODFLOW 6 with [GNU Make]( For the build instructions we refer to the [GNU Make Manual](https://www.gnu.org/software/make/manual/). -## Running Tests Locally +## Running Tests -For complete testing as done on the CI, clone your fork of the modflow6-testmodels repository (via either ssh or https, ssh shown here): +Tests should pass locally before a PR is opened on Github. All the tests are executed by the CI system and a pull request can only be merged with passing tests. -```shell -git clone git@github.com:/modflow6-testmodels.git -``` -* The modflow6-testmodels repository must be cloned in the same directory that contains the modflow6 repository. - -To run tests first change directory to the `autotest` folder: +Tests must be run from the `autotest` folder: ```shell cd modflow6/autotest ``` -Update your flopy installation by executing +FloPy plugins must first be updated: ```shell python update_flopy.py @@ -186,24 +181,79 @@ Unless you built and installed MODFLOW 6 binaries with meson, you will also have pytest -v build_exes.py ``` -Then the tests can be run with commands similar to these: +Then the tests can be run with `pytest` as usual, for instance: ```shell -# Build MODFLOW 6 tests +# Run all tests with verbose output pytest -v +``` -# Build MODFLOW 6 example tests -pytest -v test_z01_testmodels_mf6.py +Tests can be run in parallel with the `-n` option, which accepts an integer argument for the number of processes to use. If the value `auto` is provided, `pytest-xdist` will use as many processes as your machine has available CPUs: -# Build MODFLOW 5 to 6 converter example tests -pytest -v test_z02_testmodels_mf5to6.py +```shell +pytest -v -n auto ``` -The tests can be run in parallel by adding the flag "-n" which accepts an argument for the specific number of processes to use or "auto" to let pytest decide: +### External model repos + +While many tests create models programmatically, the full suite tests MODFLOW 6 against example models stored in the following external repositories: + +- [`MODFLOW-USGS/modflow6-testmodels`](https://github.com/MODFLOW-USGS/modflow6-testmodels) +- [`MODFLOW-USGS/modflow6-largetestmodels`](https://github.com/MODFLOW-USGS/modflow6-largetestmodels) +- [`MODFLOW-USGS/modflow6-examples`](https://github.com/MODFLOW-USGS/modflow6-examples) + +#### Installing external repos + +By default, the tests expect these repositories side-by-side with (i.e. in the same parent directory as) the `modflow6` repository. If the repos are somewhere else, you can set the `REPOS_PATH` environment variable to point to their parent directory. + +**Note:** a convenient way to persist environment variables needed for tests is to store them in a `.env` file in the `autotest` folder. Each variable should be defined on a separate line, with format `KEY=VALUE`. The `pytest-dotenv` plugin will then automatically load any variables found in this file into the test process' environment. + +##### Test models + +The test model repos can simply be cloned — ideally, into the parent directory of the `modflow6` repository, so that repositories live side-by-side: ```shell -pytest -v -n auto +git clone MODFLOW-USGS/modflow6-testmodels +git clone MODFLOW-USGS/modflow6-largetestmodels ``` -You should execute the test suites before submitting a PR to Github. -All the tests are executed on our Continuous Integration infrastructure and a pull request can only be merged once all tests pass. +##### Example models + +First clone the example models repo: + +```shell +git clone MODFLOW-USGS/modflow6-examples +``` + +The example models require some setup after cloning. Some extra Python dependencies are required to build the examples: + +```shell +cd modflow6-examples/etc +pip install -r requirements.pip.txt +``` + +Then, still from the `etc` folder, run: + +```shell +python ci_build_files.py +``` + +This will build the examples for subsequent use by the tests. + +#### Running external model tests + +External model tests are located in their own files: + +```shell +# Run MODFLOW 6 test models +pytest -v -n auto test_z01_testmodels_mf6.py + +# Run MODFLOW 5 to 6 conversion test models +pytest -v -n auto test_z02_testmodels_mf5to6.py + +# Run example models +pytest -v -n auto test_z03_examples.py + +# Run large test models +pytest -v -n auto test_z03_largetestmodels.py +``` diff --git a/autotest/build_exes.py b/autotest/build_exes.py index fd9b22a51e7..3d71b8da338 100644 --- a/autotest/build_exes.py +++ b/autotest/build_exes.py @@ -1,156 +1,31 @@ -# Build targets +import argparse +from pathlib import Path -# to use ifort on windows, run this -# python build_exes.py -fc ifort +import pytest +from modflow_devtools.build import meson_build -# can compile only mf6 directly using this command: -# python -c "import build_exes; build_exes.test_build_modflow6()" +from conftest import project_root_path -import os -import pathlib as pl -import subprocess as sp -import sys -from contextlib import contextmanager -from framework import running_on_CI +repository = "MODFLOW-USGS/modflow6" +top_bin_path = project_root_path / "bin" -if running_on_CI(): - print("running on CI environment") - os.environ["PYMAKE_DOUBLE"] = "1" -# set OS dependent extensions -eext = "" -soext = ".so" -if sys.platform.lower() == "win32": - eext = ".exe" - soext = ".dll" -elif sys.platform.lower() == "darwin": - soext = ".dylib" +@pytest.fixture +def bin_path(): + return top_bin_path -mfexe_pth = "temp/mfexes" -# use the line below to set fortran compiler using environmental variables -# os.environ["FC"] = "ifort" - -# some flags to check for errors in the code -# add -Werror for compilation to terminate if errors are found -strict_flags = ( - "-fall-intrinsics " - "-Wtabs -Wline-truncation -Wunused-label " - "-Wunused-variable -pedantic -std=f2008 " - "-Wcharacter-truncation" -) - - -@contextmanager -def set_directory(path: str): - """Sets the cwd within the context - - Args: - path (Path): The path to the cwd - - Yields: - None - """ - - origin = os.path.abspath(os.getcwd()) - path = os.path.abspath(path) - try: - os.chdir(path) - print(f"change from {origin} -> {path}") - yield - finally: - os.chdir(origin) - print(f"change from {path} -> {origin}") - - -def relpath_fallback(pth): - try: - # throws ValueError on Windows if pth is on a different drive - return os.path.relpath(pth) - except ValueError: - return os.path.abspath(pth) - - -def create_dir(pth): - # create pth directory - print(f"creating... {os.path.abspath(pth)}") - os.makedirs(pth, exist_ok=True) - - msg = f"could not create... {os.path.abspath(pth)}" - assert os.path.exists(pth), msg - - -def set_compiler_environment_variable(): - fc = None - - # parse command line arguments - for idx, arg in enumerate(sys.argv): - if arg.lower() == "-fc": - fc = sys.argv[idx + 1] - elif arg.lower().startswith("-fc="): - fc = arg.split("=")[1] - - # determine if fc needs to be set to the FC environmental variable - env_var = os.getenv("FC", default="gfortran") - if fc is None and fc != env_var: - fc = env_var - - # validate Fortran compiler - fc_options = ( - "gfortran", - "ifort", +def test_meson_build(bin_path): + meson_build( + project_path=project_root_path, + build_path=project_root_path / "builddir", + bin_path=bin_path ) - if fc not in fc_options: - raise ValueError( - f"Fortran compiler {fc} not supported. Fortran compile must be " - + f"[{', '.join(str(value) for value in fc_options)}]." - ) - - # set FC environment variable - os.environ["FC"] = fc - - -def meson_build( - dir_path: str = "..", - libdir: str = "bin", -): - set_compiler_environment_variable() - is_windows = sys.platform.lower() == "win32" - with set_directory(dir_path): - cmd = ( - "meson setup builddir " - + f"--bindir={os.path.abspath(libdir)} " - + f"--libdir={os.path.abspath(libdir)} " - + "--prefix=" - ) - if is_windows: - cmd += "%CD%" - else: - cmd += "$(pwd)" - if pl.Path("builddir").is_dir(): - cmd += " --wipe" - print(f"setup meson\nrunning...\n {cmd}") - sp.run(cmd, shell=True, check=True) - - cmd = "meson install -C builddir" - print(f"build and install with meson\nrunning...\n {cmd}") - sp.run(cmd, shell=True, check=True) - - -def test_create_dirs(): - pths = [os.path.join("..", "bin"), os.path.join("temp")] - - for pth in pths: - create_dir(pth) - - return - - -def test_meson_build(): - meson_build() if __name__ == "__main__": - test_create_dirs() - test_meson_build() + parser = argparse.ArgumentParser("Rebuild local development version of MODFLOW 6") + parser.add_argument("-p", "--path", help="path to bin directory", default=top_bin_path) + args = parser.parse_args() + test_meson_build(Path(args.path).resolve()) diff --git a/autotest/common_regression.py b/autotest/common_regression.py index 65951f5b6d5..a167d9235ee 100644 --- a/autotest/common_regression.py +++ b/autotest/common_regression.py @@ -1,168 +1,879 @@ import os +import shutil import sys -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) +ignore_ext = ( + ".hds", + ".hed", + ".bud", + ".cbb", + ".cbc", + ".ddn", + ".ucn", + ".glo", + ".lst", + ".list", + ".gwv", + ".mv", + ".out", +) -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) +def model_setup(namefile, dst, remove_existing=True, extrafiles=None): + """Setup MODFLOW-based model files for autotests. -def get_home_dir(): - # determine if CI run - is_CI = "CI" in os.environ + Parameters + ---------- + namefile : str + MODFLOW-based model name file. + dst : str + destination path for comparison model or file(s) + remove_existing : bool + boolean indicating if an existing comparision model or file(s) should + be replaced (default is True) + extrafiles : str or list of str + list of extra files to include in the comparision - home = os.path.expanduser("~") + Returns + ------- - if is_CI: - if sys.platform.lower() == "win32": - home = os.path.normpath(os.path.join(os.getcwd(), "..", "..")) + """ + # Construct src pth from namefile or lgr file + src = os.path.dirname(namefile) + + # Create the destination folder, if required + create_dir = False + if os.path.exists(dst): + if remove_existing: + print("Removing folder " + dst) + shutil.rmtree(dst) + create_dir = True else: - cwd_pth = os.getcwd() - - # convert current working directory to a list - cwd_list = cwd_pth.split(sep=os.path.sep) - - # add leading path separator back into list - for idx, pth in enumerate(cwd_list): - if len(pth) < 1: - cwd_list[idx] = os.path.sep - if pth.endswith(":") and sys.platform.lower() == "win32": - cwd_list[idx] += os.path.sep - - ipos = 0 - for idx, s in enumerate(cwd_list): - if s.lower().startswith("modflow6"): - ipos = idx - break + create_dir = True + if create_dir: + os.mkdir(dst) - home = os.path.join(*cwd_list[:ipos]) + # determine if a namefile is a lgr control file - get individual + # name files out of the lgr control file + namefiles = [namefile] + ext = os.path.splitext(namefile)[1] + if ".lgr" in ext.lower(): + lines = [line.rstrip("\n") for line in open(namefile)] + for line in lines: + if len(line) < 1: + continue + if line[0] == "#": + continue + t = line.split() + if ".nam" in t[0].lower(): + fpth = os.path.join(src, t[0]) + namefiles.append(fpth) - print(f"HOME: {home}") + # Make list of files to copy + files2copy = [] + for fpth in namefiles: + files2copy.append(os.path.basename(fpth)) + ext = os.path.splitext(fpth)[1] + # copy additional files contained in the name file and + # associated package files + if ext.lower() == ".nam": + fname = os.path.abspath(fpth) + files2copy = files2copy + get_input_files(fname) - return home + if extrafiles is not None: + if isinstance(extrafiles, str): + extrafiles = [extrafiles] + for fl in extrafiles: + files2copy.append(os.path.basename(fl)) + # Copy the files + for f in files2copy: + srcf = os.path.join(src, f) + dstf = os.path.join(dst, f) -def set_mf6_regression(): - mf6_regression = True - for arg in sys.argv: - if arg.lower() in ("--original_regression", "-oreg"): - mf6_regression = False - break - return mf6_regression - - -def is_directory_available(example_basedir): - available = False - if example_basedir is not None: - available = os.path.isdir(example_basedir) - if not available: - print(f'"{example_basedir}" does not exist') - print(f"no need to run {os.path.basename(__file__)}") - return available - - -def get_example_basedir(home, find_dir, subdir=None): - example_basedir = None - for root, dirs, files in os.walk(home): - for d in dirs: - if d == find_dir or d == find_dir + ".git": - example_basedir = os.path.join(root, d) - if subdir is not None: - example_basedir = os.path.join(example_basedir, subdir) + # Check to see if dstf is going into a subfolder, and create that + # subfolder if it doesn't exist + sf = os.path.dirname(dstf) + if not os.path.isdir(sf): + os.makedirs(sf) + + # Now copy the file + if os.path.exists(srcf): + print("Copy file '" + srcf + "' -> '" + dstf + "'") + shutil.copy(srcf, dstf) + else: + print(srcf + " does not exist") + + return + + +def setup_comparison(namefile, dst, remove_existing=True): + """Setup a comparison model or comparision file(s) for a MODFLOW-based + model. + + Parameters + ---------- + namefile : str + MODFLOW-based model name file. + dst : str + destination path for comparison model or file(s) + remove_existing : bool + boolean indicating if an existing comparision model or file(s) should + be replaced (default is True) + + + Returns + ------- + + """ + # Construct src pth from namefile + src = os.path.dirname(namefile) + action = None + for root, dirs, files in os.walk(src): + dl = [d.lower() for d in dirs] + if any(".cmp" in s for s in dl): + idx = None + for jdx, d in enumerate(dl): + if ".cmp" in d: + idx = jdx + break + if idx is not None: + if "mf2005.cmp" in dl[idx] or "mf2005" in dl[idx]: + action = dirs[idx] + elif "mfnwt.cmp" in dl[idx] or "mfnwt" in dl[idx]: + action = dirs[idx] + elif "mfusg.cmp" in dl[idx] or "mfusg" in dl[idx]: + action = dirs[idx] + elif "mf6.cmp" in dl[idx] or "mf6" in dl[idx]: + action = dirs[idx] + elif "libmf6.cmp" in dl[idx] or "libmf6" in dl[idx]: + action = dirs[idx] + else: + action = dirs[idx] break - if example_basedir is not None: - example_basedir = os.path.abspath(example_basedir) - print(f"Example base directory: {example_basedir}") - break - return example_basedir - - -def get_example_dirs(example_basedir, exclude, prefix="test", find_sim=True): - example_dirs = [ - d - for d in os.listdir(example_basedir) - if prefix in d and d not in exclude - ] - - # make sure mfsim.nam is present in each directory - if find_sim: - remove_dirs = [] - # add_dirs = [] - for temp_dir in example_dirs: - epth = os.path.join(example_basedir, temp_dir) - fpth = os.path.join(epth, "mfsim.nam") - if not os.path.isfile(fpth): - remove_dirs.append(temp_dir) - # for sub_dir in ("mf6gwf", "mf6gwt"): - # tpth = os.path.join(epth, sub_dir) - # fpth = os.path.join(tpth, "mfsim.nam") - # if os.path.isfile(fpth): - # add_dirs.append(os.path.join(temp_dir, sub_dir)) - - for remove_dir in remove_dirs: - example_dirs.remove(remove_dir) - - # example_dirs += add_dirs - - # sort in numerical order for case sensitive os - example_dirs = sorted( - example_dirs, key=lambda v: (v.upper(), v[0].islower()) + if action is not None: + dst = os.path.join(dst, f"{action}") + if not os.path.isdir(dst): + try: + os.mkdir(dst) + except: + print("Could not make " + dst) + # clean directory + else: + print(f"cleaning...{dst}") + for root, dirs, files in os.walk(dst): + for f in files: + tpth = os.path.join(root, f) + print(f" removing...{tpth}") + os.remove(tpth) + for d in dirs: + tdir = os.path.join(root, d) + print(f" removing...{tdir}") + shutil.rmtree(tdir) + # copy files + cmppth = os.path.join(src, action) + files = os.listdir(cmppth) + files2copy = [] + if action.lower() == ".cmp": + for file in files: + if ".cmp" in os.path.splitext(file)[1].lower(): + files2copy.append(os.path.join(cmppth, file)) + for srcf in files2copy: + f = os.path.basename(srcf) + dstf = os.path.join(dst, f) + # Now copy the file + if os.path.exists(srcf): + print("Copy file '" + srcf + "' -> '" + dstf + "'") + shutil.copy(srcf, dstf) + else: + print(srcf + " does not exist") + else: + for file in files: + if ".nam" in os.path.splitext(file)[1].lower(): + files2copy.append( + os.path.join(cmppth, os.path.basename(file)) + ) + nf = os.path.join(src, action, os.path.basename(file)) + model_setup(nf, dst, remove_existing=remove_existing) + break + + return action + + +def get_input_files(namefile): + """Return a list of all the input files in this model. + + Parameters + ---------- + namefile : str + path to a MODFLOW-based model name file + + Returns + ------- + filelist : list + list of MODFLOW-based model input files + + """ + srcdir = os.path.dirname(namefile) + filelist = [] + fname = os.path.join(srcdir, namefile) + with open(fname, "r") as f: + lines = f.readlines() + + for line in lines: + ll = line.strip().split() + if len(ll) < 2: + continue + if line.strip()[0] in ["#", "!"]: + continue + ext = os.path.splitext(ll[2])[1] + if ext.lower() not in ignore_ext: + if len(ll) > 3: + if "replace" in ll[3].lower(): + continue + filelist.append(ll[2]) + + # Now go through every file and look for other files to copy, + # such as 'OPEN/CLOSE'. If found, then add that file to the + # list of files to copy. + otherfiles = [] + for fname in filelist: + fname = os.path.join(srcdir, fname) + try: + f = open(fname, "r") + for line in f: + + # Skip invalid lines + ll = line.strip().split() + if len(ll) < 2: + continue + if line.strip()[0] in ["#", "!"]: + continue + + if "OPEN/CLOSE" in line.upper(): + for i, s in enumerate(ll): + if "OPEN/CLOSE" in s.upper(): + stmp = ll[i + 1] + stmp = stmp.replace('"', "") + stmp = stmp.replace("'", "") + otherfiles.append(stmp) + break + except: + print(fname + " does not exist") + + filelist = filelist + otherfiles + + return filelist + + +def get_namefiles(pth, exclude=None): + """Search through a path (pth) for all .nam files. + + Parameters + ---------- + pth : str + path to model files + exclude : str or lst + File or list of files to exclude from the search (default is None) + + Returns + ------- + namefiles : lst + List of namefiles with paths + + """ + namefiles = [] + for root, _, files in os.walk(pth): + namefiles += [ + os.path.join(root, file) for file in files if file.endswith(".nam") + ] + if exclude is not None: + if isinstance(exclude, str): + exclude = [exclude] + exclude = [e.lower() for e in exclude] + pop_list = [] + for namefile in namefiles: + for e in exclude: + if e in namefile.lower(): + pop_list.append(namefile) + for e in pop_list: + namefiles.remove(e) + + return namefiles + + +def get_sim_name(namefiles, rootpth=None): + """Get simulation name. + + Parameters + ---------- + namefiles : str or list of strings + path(s) to MODFLOW-based model name files + rootpth : str + optional root directory path (default is None) + + Returns + ------- + simname : list + list of namefiles without the file extension + + """ + if isinstance(namefiles, str): + namefiles = [namefiles] + sim_name = [] + for namefile in namefiles: + t = namefile.split(os.sep) + if rootpth is None: + idx = -1 + else: + idx = t.index(os.path.split(rootpth)[1]) + + # build dst with everything after the rootpth and before + # the namefile file name. + dst = "" + if idx < len(t): + for d in t[idx + 1 : -1]: + dst += f"{d}_" + + # add namefile basename without extension + dst += t[-1].replace(".nam", "") + sim_name.append(dst) + + return sim_name + + +def setup_mf6( + src, dst, mfnamefile="mfsim.nam", extrafiles=None, remove_existing=True +): + """Copy all of the MODFLOW 6 input files from the src directory to the dst + directory. + + Parameters + ---------- + src : src + directory path with original MODFLOW 6 input files + dst : str + directory path that original MODFLOW 6 input files will be copied to + mfnamefile : str + optional MODFLOW 6 simulation name file (default is mfsim.nam) + extrafiles : bool + boolean indicating if extra files should be included (default is None) + remove_existing : bool + boolean indicating if existing file in dst should be removed (default + is True) + + Returns + ------- + mf6inp : list + list of MODFLOW 6 input files + mf6outp : list + list of MODFLOW 6 output files + + """ + + # Create the destination folder + create_dir = False + if os.path.exists(dst): + if remove_existing: + print("Removing folder " + dst) + shutil.rmtree(dst) + create_dir = True + else: + create_dir = True + if create_dir: + os.makedirs(dst) + + # Make list of files to copy + fname = os.path.join(src, mfnamefile) + fname = os.path.abspath(fname) + mf6inp, mf6outp = get_mf6_files(fname) + files2copy = [mfnamefile] + mf6inp + + # determine if there are any .ex files + exinp = [] + for f in mf6outp: + ext = os.path.splitext(f)[1] + if ext.lower() == ".hds": + pth = os.path.join(src, f + ".ex") + if os.path.isfile(pth): + exinp.append(f + ".ex") + if len(exinp) > 0: + files2copy += exinp + if extrafiles is not None: + files2copy += extrafiles + + # Copy the files + for f in files2copy: + srcf = os.path.join(src, f) + dstf = os.path.join(dst, f) + + # Check to see if dstf is going into a subfolder, and create that + # subfolder if it doesn't exist + sf = os.path.dirname(dstf) + if not os.path.isdir(sf): + try: + os.mkdir(sf) + except: + print("Could not make " + sf) + + # Now copy the file + if os.path.exists(srcf): + print("Copy file '" + srcf + "' -> '" + dstf + "'") + shutil.copy(srcf, dstf) + else: + print(srcf + " does not exist") + + return mf6inp, mf6outp + + +def get_mf6_comparison(src): + """Determine comparison type for MODFLOW 6 simulation. + + Parameters + ---------- + src : str + directory path to search for comparison types + + Returns + ------- + action : str + comparison type + + """ + action = None + # Possible comparison - the order matters + optcomp = ( + "compare", + ".cmp", + "mf2005", + "mf2005.cmp", + "mfnwt", + "mfnwt.cmp", + "mfusg", + "mfusg.cmp", + "mflgr", + "mflgr.cmp", + "libmf6", + "libmf6.cmp", + "mf6", + "mf6.cmp", ) + # Construct src pth from namefile + action = None + for _, dirs, _ in os.walk(src): + dl = [d.lower() for d in dirs] + for oc in optcomp: + if any(oc in s for s in dl): + action = oc + break + return action + + +def setup_mf6_comparison(src, dst, remove_existing=True): + """Setup comparision for MODFLOW 6 simulation. + + Parameters + ---------- + src : src + directory path with original MODFLOW 6 input files + dst : str + directory path that original MODFLOW 6 input files will be copied to + remove_existing : bool + boolean indicating if existing file in dst should be removed (default + is True) + + Returns + ------- + action : str + comparison type - return example_dirs - - -def get_select_dirs(select_dirs, dirs): - found_dirs = [] - for d in select_dirs: - if d.endswith("*"): - for test_dir in dirs: - if test_dir.startswith(d.replace("*", "")): - found_dirs.append(test_dir) - elif d.endswith("+"): - dd = d.replace("+", "") - for test_dir in dirs: - sorted_list = sorted([dd, test_dir], reverse=True) - if sorted_list[0] == test_dir: - found_dirs.append(test_dir) - elif d.endswith("-"): - dd = d.replace("-", "") - for test_dir in dirs: - sorted_list = sorted([dd, test_dir]) - if sorted_list[0] == test_dir or dd in sorted_list[0]: - found_dirs.append(test_dir) + """ + # get the type of comparison to use (compare, mf2005, etc.) + action = get_mf6_comparison(src) + + if action is not None: + dst = os.path.join(dst, f"{action}") + if not os.path.isdir(dst): + try: + os.mkdir(dst) + except: + print("Could not make " + dst) + # clean directory + else: + print(f"cleaning...{dst}") + for root, dirs, files in os.walk(dst): + for f in files: + tpth = os.path.join(root, f) + print(f" removing...{tpth}") + os.remove(tpth) + for d in dirs: + tdir = os.path.join(root, d) + print(f" removing...{tdir}") + shutil.rmtree(tdir) + # copy files + cmppth = os.path.join(src, action) + files = os.listdir(cmppth) + files2copy = [] + if action.lower() == "compare" or action.lower() == ".cmp": + for file in files: + if ".cmp" in os.path.splitext(file)[1].lower(): + files2copy.append(os.path.join(cmppth, file)) + for srcf in files2copy: + f = os.path.basename(srcf) + dstf = os.path.join(dst, f) + # Now copy the file + if os.path.exists(srcf): + print("Copy file '" + srcf + "' -> '" + dstf + "'") + shutil.copy(srcf, dstf) + else: + print(srcf + " does not exist") else: - if d in dirs: - found_dirs.append(d) + if "mf6" in action.lower(): + for file in files: + if "mfsim.nam" in file.lower(): + srcf = os.path.join(cmppth, os.path.basename(file)) + files2copy.append(srcf) + srcdir = os.path.join(src, action) + setup_mf6(srcdir, dst, remove_existing=remove_existing) + break + else: + for file in files: + if ".nam" in os.path.splitext(file)[1].lower(): + srcf = os.path.join(cmppth, os.path.basename(file)) + files2copy.append(srcf) + nf = os.path.join(src, action, os.path.basename(file)) + model_setup(nf, dst, remove_existing=remove_existing) + break - return found_dirs + return action -def get_select_packages(select_packages, exdir, dirs): - found_dirs = [] - for d in dirs: - pth = os.path.join(exdir, d) - namefiles = pymake.get_namefiles(pth) - ftypes = [] - for namefile in namefiles: - ftype = pymake.get_mf6_ftypes(namefile, select_packages) - if ftype not in ftypes: - ftypes += ftype - if len(ftypes) > 0: - ftypes = [item.upper() for item in ftypes] - for pak in select_packages: - if pak in ftypes: - found_dirs.append(d) - break - return found_dirs +def get_mf6_nper(tdisfile): + """Return the number of stress periods in the MODFLOW 6 model. + + Parameters + ---------- + tdisfile : str + path to the TDIS file + + Returns + ------- + nper : int + number of stress periods in the simulation + + """ + with open(tdisfile, "r") as f: + lines = f.readlines() + line = [line for line in lines if "NPER" in line.upper()][0] + nper = line.strip().split()[1] + return nper + + +def get_mf6_mshape(disfile): + """Return the shape of the MODFLOW 6 model. + + Parameters + ---------- + disfile : str + path to a MODFLOW 6 discretization file + + Returns + ------- + mshape : tuple + tuple with the shape of the MODFLOW 6 model. + + """ + with open(disfile, "r") as f: + lines = f.readlines() + + d = {} + for line in lines: + + # Skip over blank and commented lines + ll = line.strip().split() + if len(ll) < 2: + continue + if line.strip()[0] in ["#", "!"]: + continue + + for key in ["NODES", "NCPL", "NLAY", "NROW", "NCOL"]: + if ll[0].upper() in key: + d[key] = int(ll[1]) + + if "NODES" in d: + mshape = (d["NODES"],) + elif "NCPL" in d: + mshape = (d["NLAY"], d["NCPL"]) + elif "NLAY" in d: + mshape = (d["NLAY"], d["NROW"], d["NCOL"]) + else: + print(d) + raise Exception("Could not determine model shape") + return mshape + + +def get_mf6_files(mfnamefile): + """Return a list of all the MODFLOW 6 input and output files in this model. + + Parameters + ---------- + mfnamefile : str + path to the MODFLOW 6 simulation name file + + Returns + ------- + filelist : list + list of MODFLOW 6 input files in a simulation + outplist : list + list of MODFLOW 6 output files in a simulation + + """ + + srcdir = os.path.dirname(mfnamefile) + filelist = [] + outplist = [] + + filekeys = ["TDIS6", "GWF6", "GWT", "GWF6-GWF6", "GWF-GWT", "IMS6"] + namefilekeys = ["GWF6", "GWT"] + namefiles = [] + + with open(mfnamefile) as f: + + # Read line and skip comments + lines = f.readlines() + + for line in lines: + + # Skip over blank and commented lines + ll = line.strip().split() + if len(ll) < 2: + continue + if line.strip()[0] in ["#", "!"]: + continue + + for key in filekeys: + if key in ll[0].upper(): + fname = ll[1] + filelist.append(fname) + + for key in namefilekeys: + if key in ll[0].upper(): + fname = ll[1] + namefiles.append(fname) + + # Go through name files and get files + for namefile in namefiles: + fname = os.path.join(srcdir, namefile) + with open(fname, "r") as f: + lines = f.readlines() + insideblock = False + + for line in lines: + ll = line.upper().strip().split() + if len(ll) < 2: + continue + if ll[0] in "BEGIN" and ll[1] in "PACKAGES": + insideblock = True + continue + if ll[0] in "END" and ll[1] in "PACKAGES": + insideblock = False + + if insideblock: + ll = line.strip().split() + if len(ll) < 2: + continue + if line.strip()[0] in ["#", "!"]: + continue + filelist.append(ll[1]) + + # Recursively go through every file and look for other files to copy, + # such as 'OPEN/CLOSE' and 'TIMESERIESFILE'. If found, then + # add that file to the list of files to copy. + flist = filelist + # olist = outplist + while True: + olist = [] + flist, olist = _get_mf6_external_files(srcdir, olist, flist) + # add to filelist + if len(flist) > 0: + filelist = filelist + flist + # add to outplist + if len(olist) > 0: + outplist = outplist + olist + # terminate loop if no additional files + # if len(flist) < 1 and len(olist) < 1: + if len(flist) < 1: + break + + return filelist, outplist + + +def _get_mf6_external_files(srcdir, outplist, files): + """Get list of external files in a MODFLOW 6 simulation. + + Parameters + ---------- + srcdir : str + path to a directory containing a MODFLOW 6 simulation + outplist : list + list of output files in a MODFLOW 6 simulation + files : list + list of MODFLOW 6 name files + + Returns + ------- + + """ + extfiles = [] + + for fname in files: + fname = os.path.join(srcdir, fname) + try: + f = open(fname, "r") + for line in f: + + # Skip invalid lines + ll = line.strip().split() + if len(ll) < 2: + continue + if line.strip()[0] in ["#", "!"]: + continue + + if "OPEN/CLOSE" in line.upper(): + for i, s in enumerate(ll): + if s.upper() == "OPEN/CLOSE": + stmp = ll[i + 1] + stmp = stmp.replace('"', "") + stmp = stmp.replace("'", "") + extfiles.append(stmp) + break + + if "TS6" in line.upper(): + for i, s in enumerate(ll): + if s.upper() == "FILEIN": + stmp = ll[i + 1] + stmp = stmp.replace('"', "") + stmp = stmp.replace("'", "") + extfiles.append(stmp) + break + + if "TAS6" in line.upper(): + for i, s in enumerate(ll): + if s.upper() == "FILEIN": + stmp = ll[i + 1] + stmp = stmp.replace('"', "") + stmp = stmp.replace("'", "") + extfiles.append(stmp) + break + + if "OBS6" in line.upper(): + for i, s in enumerate(ll): + if s.upper() == "FILEIN": + stmp = ll[i + 1] + stmp = stmp.replace('"', "") + stmp = stmp.replace("'", "") + extfiles.append(stmp) + break + + if "EXTERNAL" in line.upper(): + for i, s in enumerate(ll): + if s.upper() == "EXTERNAL": + stmp = ll[i + 1] + stmp = stmp.replace('"', "") + stmp = stmp.replace("'", "") + extfiles.append(stmp) + break + + if "FILE" in line.upper(): + for i, s in enumerate(ll): + if s.upper() == "FILEIN": + stmp = ll[i + 1] + stmp = stmp.replace('"', "") + stmp = stmp.replace("'", "") + extfiles.append(stmp) + break + + if "FILE" in line.upper(): + for i, s in enumerate(ll): + if s.upper() == "FILEOUT": + stmp = ll[i + 1] + stmp = stmp.replace('"', "") + stmp = stmp.replace("'", "") + outplist.append(stmp) + break + + except: + print("could not get a list of external mf6 files") + + return extfiles, outplist + + +def get_mf6_ftypes(namefile, ftypekeys): + """Return a list of FTYPES that are in the name file and in ftypekeys. + + Parameters + ---------- + namefile : str + path to a MODFLOW 6 name file + ftypekeys : list + list of desired FTYPEs + + Returns + ------- + ftypes : list + list of FTYPES that match ftypekeys in namefile + + """ + with open(namefile, "r") as f: + lines = f.readlines() + + ftypes = [] + for line in lines: + + # Skip over blank and commented lines + ll = line.strip().split() + if len(ll) < 2: + continue + if line.strip()[0] in ["#", "!"]: + continue + + for key in ftypekeys: + if ll[0].upper() in key: + ftypes.append(ll[0]) + + return ftypes + + +def get_mf6_blockdata(f, blockstr): + """Return list with all non comments between start and end of block + specified by blockstr. + + Parameters + ---------- + f : file object + open file object + blockstr : str + name of block to search + + Returns + ------- + data : list + list of data in specified block + + """ + data = [] + + # find beginning of block + for line in f: + if line[0] != "#": + t = line.split() + if t[0].lower() == "begin" and t[1].lower() == blockstr.lower(): + break + for line in f: + if line[0] != "#": + t = line.split() + if t[0].lower() == "end" and t[1].lower() == blockstr.lower(): + break + else: + data.append(line.rstrip()) + return data diff --git a/autotest/conftest.py b/autotest/conftest.py new file mode 100644 index 00000000000..5a1c9ffc112 --- /dev/null +++ b/autotest/conftest.py @@ -0,0 +1,49 @@ +from pathlib import Path + +import pytest +from modflow_devtools.executables import Executables, build_default_exe_dict + +pytest_plugins = ["modflow_devtools.fixtures"] +project_root_path = Path(__file__).parent.parent + + +def should_compare(test: str, comparisons: dict, executables: Executables) -> bool: + if test in comparisons.keys(): + version = Executables.get_version(path=executables.mf6) + print(f"MODFLOW 6 development version='{version}'") + version = Executables.get_version(path=executables.mf6_regression) + print(f"MODFLOW 6 regression version='{version}'") + if version in comparisons[test]: + print( + f"Test {test} does not run with versions {comparisons[test]}" + ) + print( + f"Skipping regression test of sim {test} because the version is {version}" + ) + return False + return True + + +@pytest.fixture(scope="session") +def bin_path() -> Path: + return project_root_path / "bin" + + +@pytest.fixture(scope="session") +def targets(bin_path) -> Executables: + return Executables(**build_default_exe_dict(bin_path)) + + +@pytest.fixture +def original_regression(request) -> bool: + oreg = request.config.getoption("--original-regression") + return oreg + + +def pytest_addoption(parser): + parser.addoption( + "--original-regression", + action="store_true", + default=False, + help="TODO" + ) diff --git a/autotest/get_exes.py b/autotest/get_exes.py index 7530f31ca3f..4b2d31c870e 100644 --- a/autotest/get_exes.py +++ b/autotest/get_exes.py @@ -1,119 +1,93 @@ -# Get executables - -import os -import shutil +import argparse +from pathlib import Path +from tempfile import TemporaryDirectory +from warnings import warn +import flopy +import pytest from flaky import flaky -import pymake - -from build_exes import meson_build -from framework import running_on_CI - -if running_on_CI(): - print("running on CI environment") - os.environ["PYMAKE_DOUBLE"] = "1" - -# path to rebuilt executables for previous versions of MODFLOW -rebuilt_bindir = os.path.join("..", "bin", "rebuilt") - -if not os.path.exists(rebuilt_bindir): - os.makedirs(rebuilt_bindir) - -# paths to downloaded for previous versions of MODFLOW -downloaded_bindir = os.path.join("..", "bin", "downloaded") - -if not os.path.exists(downloaded_bindir): - os.makedirs(downloaded_bindir) - - -mfexe_pth = "temp/mfexes" +from modflow_devtools.build import meson_build +from modflow_devtools.download import download_and_unzip, get_release +from modflow_devtools.misc import get_ostag -# use the line below to set fortran compiler using environmental variables -# os.environ["FC"] = "gfortran" +from conftest import project_root_path -# some flags to check for errors in the code -# add -Werror for compilation to terminate if errors are found -strict_flags = ( - "-Wtabs -Wline-truncation -Wunused-label " - "-Wunused-variable -pedantic -std=f2008 " - "-Wcharacter-truncation" -) +repository = "MODFLOW-USGS/modflow6" +top_bin_path = project_root_path / "bin" -def get_compiler_envvar(fc): - env_var = os.environ.get("FC") - if env_var is not None: - if env_var != fc: - fc = env_var - return fc +def get_asset_name(asset: dict) -> str: + ostag = get_ostag() + name = asset["name"] + if "win" in ostag: + return name + else: + prefix = name.rpartition('_')[0] + prefix += f"_{ostag}" + return f"{prefix}.zip" -def create_dir(pth): - # create pth directory - print(f"creating... {os.path.abspath(pth)}") - os.makedirs(pth, exist_ok=True) +@pytest.fixture +def rebuilt_bin_path() -> Path: + return top_bin_path / "rebuilt" - msg = f"could not create... {os.path.abspath(pth)}" - assert os.path.exists(pth), msg - -def rebuild_mf6_release(): - target = "mf6" - download_pth = os.path.join("temp") - target_dict = pymake.usgs_program_data.get_target(target) - - pymake.download_and_unzip( - target_dict["url"], - pth=download_pth, - verbose=True, - ) - - # update IDEVELOP MODE in the release - srcpth = os.path.join( - download_pth, target_dict["dirname"], target_dict["srcdir"] - ) - fpth = os.path.join(srcpth, "Utilities", "version.f90") - with open(fpth) as f: - lines = f.read().splitlines() - assert len(lines) > 0, f"could not update {srcpth}" - - f = open(fpth, "w") - for line in lines: - tag = "IDEVELOPMODE = 0" - if tag in line: - line = line.replace(tag, "IDEVELOPMODE = 1") - f.write(f"{line}\n") - f.close() - - # build release source files with Meson - root_path = os.path.join(download_pth, target_dict["dirname"]) - meson_build(dir_path=root_path, libdir=os.path.abspath(rebuilt_bindir)) - - -def test_create_dirs(): - pths = [os.path.join("..", "bin"), os.path.join("temp")] - - for pth in pths: - create_dir(pth) +@pytest.fixture +def downloaded_bin_path() -> Path: + return top_bin_path / "downloaded" @flaky(max_runs=3) -def test_getmfexes(verify=True): - pymake.getmfexes(mfexe_pth, verify=verify) - for target in os.listdir(mfexe_pth): - srcpth = os.path.join(mfexe_pth, target) - if os.path.isfile(srcpth): - dstpth = os.path.join(downloaded_bindir, target) - print(f"copying {srcpth} -> {dstpth}") - shutil.copy(srcpth, dstpth) +def test_rebuild_release(rebuilt_bin_path: Path): + print(f"Rebuilding and installing last release to: {rebuilt_bin_path}") + release = get_release(repository) + assets = release["assets"] + ostag = get_ostag() + asset = next(iter([a for a in assets if a["name"] == get_asset_name(a)]), None) + if not asset: + warn(f"Couldn't find asset for OS {get_ostag()}, available assets:\n{assets}") + + with TemporaryDirectory() as td: + download_path = Path(td) + download_and_unzip( + asset["browser_download_url"], + path=download_path, + verbose=True, + ) + + # update IDEVELOPMODE + source_files_path = download_path / asset["name"].replace(".zip", "") / "src" + version_file_path = source_files_path / "Utilities" / "version.f90" + with open(version_file_path) as f: + lines = f.read().splitlines() + assert len(lines) > 0, f"File is empty: {source_files_path}" + with open(version_file_path, "w") as f: + for line in lines: + tag = "IDEVELOPMODE = 0" + if tag in line: + line = line.replace(tag, "IDEVELOPMODE = 1") + f.write(f"{line}\n") + + # rebuild with Meson + meson_build( + project_path=source_files_path.parent, + build_path=download_path / "builddir", + bin_path=rebuilt_bin_path + ) @flaky(max_runs=3) -def test_rebuild_mf6_release(): - rebuild_mf6_release() +def test_get_executables(downloaded_bin_path: Path): + print(f"Installing MODFLOW-related executables to: {downloaded_bin_path}") + downloaded_bin_path.mkdir(exist_ok=True, parents=True) + flopy.utils.get_modflow(str(downloaded_bin_path)) if __name__ == "__main__": - test_create_dirs() - test_getmfexes(verify=False) - test_rebuild_mf6_release() + parser = argparse.ArgumentParser("Get executables needed for MODFLOW 6 testing") + parser.add_argument("-p", "--path", help="path to top-level bin directory") + args = parser.parse_args() + bin_path = Path(args.path).resolve() if args.path else top_bin_path + + test_get_executables(bin_path / "downloaded") + test_rebuild_release(bin_path / "rebuilt") diff --git a/autotest/simulation.py b/autotest/simulation.py index 18fb064f30c..a354d5873c3 100644 --- a/autotest/simulation.py +++ b/autotest/simulation.py @@ -4,25 +4,12 @@ import time import numpy as np +import flopy +from flopy.utils.compare import compare_heads -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -import targets +from common_regression import get_namefiles, get_mf6_files, setup_mf6, setup_mf6_comparison, get_mf6_comparison from framework import running_on_CI, set_teardown_test +import targets sfmt = "{:25s} - {}" extdict = { @@ -49,6 +36,7 @@ def __init__( api_func=None, mf6_regression=False, make_comparison=True, + simpath=None, ): teardown_test = set_teardown_test() for idx, arg in enumerate(sys.argv): @@ -85,7 +73,7 @@ def __init__( print(msg) self.name = name self.exfunc = exfunc - self.simpath = None + self.simpath = simpath self.inpt = None self.outp = None self.coutp = None @@ -157,7 +145,7 @@ def set_model(self, pth, testModel=True): # get MODFLOW 6 output file names fpth = os.path.join(pth, "mfsim.nam") - mf6inp, mf6outp = pymake.get_mf6_files(fpth) + mf6inp, mf6outp = get_mf6_files(fpth) self.outp = mf6outp # determine comparison model @@ -168,7 +156,7 @@ def set_model(self, pth, testModel=True): # self.action = pymake.get_mf6_comparison(pth) if self.action is not None: if "mf6" in self.action or "mf6-regression" in self.action: - cinp, self.coutp = pymake.get_mf6_files(fpth) + cinp, self.coutp = get_mf6_files(fpth) def setup(self, src, dst): msg = sfmt.format("Setup test", self.name) @@ -181,7 +169,7 @@ def setup(self, src, dst): + f"{os.path.abspath(os.getcwd())}" ) try: - self.inpt, self.outp = pymake.setup_mf6(src=src, dst=dst) + self.inpt, self.outp = setup_mf6(src=src, dst=dst) print("waiting...") time.sleep(0.5) success = True @@ -225,11 +213,11 @@ def setup_comparison(self, src, dst, testModel=True): shutil.rmtree(pth) shutil.copytree(dst, pth) elif testModel: - action = pymake.setup_mf6_comparison( + action = setup_mf6_comparison( src, dst, remove_existing=self.teardown_test ) else: - action = pymake.get_mf6_comparison(dst) + action = get_mf6_comparison(dst) self.action = action @@ -316,7 +304,7 @@ def run(self): ): nam = None else: - npth = pymake.get_namefiles(cpth)[0] + npth = get_namefiles(cpth)[0] nam = os.path.basename(npth) self.nam_cmp = nam try: @@ -379,7 +367,7 @@ def compare(self): files_cmp.append(file) elif "mf6" in self.action: fpth = os.path.join(cpth, "mfsim.nam") - cinp, self.coutp = pymake.get_mf6_files(fpth) + cinp, self.coutp = get_mf6_files(fpth) head_extensions = ( "hds", @@ -473,7 +461,7 @@ def compare(self): print(txt) # make comparison - success_tst = pymake.compare_heads( + success_tst = compare_heads( None, pth, precision="double", @@ -635,7 +623,7 @@ def _compare_heads(self, msgall, extensions="hds"): outfile = os.path.join( self.simpath, outfile + f".{extension}.cmp.out" ) - success_tst = pymake.compare_heads( + success_tst = compare_heads( None, None, precision="double", @@ -671,7 +659,7 @@ def _compare_concentrations(self, msgall, extensions="ucn"): outfile = os.path.join( self.simpath, outfile + f".{extension}.cmp.out" ) - success_tst = pymake.compare_heads( + success_tst = compare_heads( None, None, precision="double", diff --git a/autotest/test_gwf_henry_nr.py b/autotest/test_gwf_henry_nr.py index ec9ffaacbe2..73c800532f9 100644 --- a/autotest/test_gwf_henry_nr.py +++ b/autotest/test_gwf_henry_nr.py @@ -18,9 +18,9 @@ msg += " pip install flopy" raise Exception(msg) +from conftest import should_compare from framework import testing_framework from simulation import Simulation -from targets import get_mf6_version ex = ["gwf_henrynr01"] exdirs = [] @@ -245,24 +245,12 @@ def build_model(idx, dir): return sim, None -def set_make_comparison(): - version = get_mf6_version() - print(f"MODFLOW version='{version}'") - version = get_mf6_version(version="mf6-regression") - print(f"MODFLOW regression version='{version}'") - if version in ("6.2.1",): - make_comparison = False - else: - make_comparison = True - return make_comparison - - # - No need to change any code below @pytest.mark.parametrize( "idx, dir", list(enumerate(exdirs)), ) -def test_mf6model(idx, dir): +def test_mf6model(idx, dir, targets): # initialize testing framework test = testing_framework() @@ -276,33 +264,6 @@ def test_mf6model(idx, dir): idxsim=idx, mf6_regression=True, cmp_verbose=False, - make_comparison=set_make_comparison(), + make_comparison=should_compare("gwf_henry_nr", comparisons={"gwf_henry_nr": ("6.2.1",)}, executables=targets), ) ) - - -def main(): - # initialize testing framework - test = testing_framework() - - # run the test model - for idx, on_dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation( - on_dir, - idxsim=idx, - mf6_regression=True, - cmp_verbose=True, - make_comparison=set_make_comparison(), - ) - test.run_mf6(sim) - - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() diff --git a/autotest/test_mf6_tmp_simulations.py b/autotest/test_mf6_tmp_simulations.py index c97cea10dc8..c6ee3c22b1a 100644 --- a/autotest/test_mf6_tmp_simulations.py +++ b/autotest/test_mf6_tmp_simulations.py @@ -3,22 +3,7 @@ import pytest -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - +from common_regression import get_mf6_ftypes, get_namefiles from simulation import Simulation exdir = os.path.join("..", "tmp_simulations") @@ -87,10 +72,10 @@ def get_mf6_models(): found_dirs = [] for d in dirs: pth = os.path.join(exdir, d) - namefiles = pymake.get_namefiles(pth) + namefiles = get_namefiles(pth) ftypes = [] for namefile in namefiles: - ftype = pymake.autotest.get_mf6_ftypes( + ftype = get_mf6_ftypes( namefile, select_packages ) if ftype not in ftypes: diff --git a/autotest/test_z01_testmodels_mf6.py b/autotest/test_z01_testmodels_mf6.py index de6a5339e94..57c9c810c79 100644 --- a/autotest/test_z01_testmodels_mf6.py +++ b/autotest/test_z01_testmodels_mf6.py @@ -1,265 +1,55 @@ -import os -import subprocess -import sys - import pytest -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) -from common_regression import ( - get_example_basedir, - get_example_dirs, - get_home_dir, - get_select_dirs, - get_select_packages, - is_directory_available, - set_mf6_regression, -) +from conftest import should_compare from simulation import Simulation -from targets import get_mf6_version - -# find path to examples directory -home = get_home_dir() - -find_dir = "modflow6-testmodels" -example_basedir = get_example_basedir(home, find_dir, subdir="mf6") - -if example_basedir is not None: - assert os.path.isdir(example_basedir) - - -def get_branch(): - try: - # determine current buildstat branch - b = subprocess.Popen( - ("git", "status"), stdout=subprocess.PIPE, stderr=subprocess.STDOUT - ).communicate()[0] - if isinstance(b, bytes): - b = b.decode("utf-8") - - # determine current buildstat branch - for line in b.splitlines(): - if "On branch" in line: - branch = line.replace("On branch ", "").rstrip() - except: - branch = None - - return branch - - -def get_mf6_models(): - """ - Get a list of test models - """ - - # determine if test directory exists - dir_avail = is_directory_available(example_basedir) - if not dir_avail: - return [] - - # determine if running on travis - is_CI = "CI" in os.environ - - # get current branch - if is_CI: - branch = os.path.basename(os.environ["GITHUB_REF"]) - else: - branch = get_branch() - print(f"On branch {branch}") - - # tuple of example files to exclude - # exclude = (None,) - exclude = ("test205_gwtbuy-henrytidal",) - - # update exclude - if is_CI: - exclude_CI = (None,) - exclude = exclude + exclude_CI - exclude = list(exclude) - - # write a summary of the files to exclude - print("list of tests to exclude:") - for idx, ex in enumerate(exclude): - print(f" {idx + 1}: {ex}") - - # build list of directories with valid example files - if example_basedir is not None: - example_dirs = get_example_dirs( - example_basedir, exclude, prefix="test" - ) - else: - example_dirs = [] - - # exclude dev examples on master or release branches - if "master" in branch.lower() or "release" in branch.lower() or branch.lower().startswith("v6"): - drmv = [] - for d in example_dirs: - if "_dev" in d.lower(): - drmv.append(d) - for d in drmv: - example_dirs.remove(d) - - # determine if only a selection of models should be run - select_example_dirs = None - select_packages = None - for idx, arg in enumerate(sys.argv): - if arg.lower() == "--sim": - if len(sys.argv) > idx + 1: - select_example_dirs = sys.argv[idx + 1 :] - break - elif arg.lower() == "--pak": - if len(sys.argv) > idx + 1: - select_packages = sys.argv[idx + 1 :] - select_packages = [item.upper() for item in select_packages] - break - elif arg.lower() == "--match": - if len(sys.argv) > idx + 1: - like = sys.argv[idx + 1] - example_dirs = [item for item in example_dirs if like in item] - break - - # determine if the selection of model is in the test models to evaluate - if select_example_dirs is not None: - example_dirs = get_select_dirs(select_example_dirs, example_dirs) - if len(example_dirs) < 1: - msg = "Selected models not available in test" - print(msg) - # determine if the specified package(s) is in the test models to evaluate - if select_packages is not None: - example_dirs = get_select_packages( - select_packages, example_basedir, example_dirs - ) - if len(example_dirs) < 1: - msg = "Selected packages not available [" - for pak in select_packages: - msg += f" {pak}" - msg += "]" - print(msg) - - return example_dirs +excluded = ["alt_model"] +comparisons = { + "test001e_noUZF_3lay": ("6.2.1",), + "test005_advgw_tidal": ("6.2.1",), + "test017_Crinkle": ("6.2.1",), + "test028_sfr": ("6.2.1",), + "test028_sfr_rewet": ("6.2.1",), + "test028_sfr_rewet_nr": ("6.2.1",), + "test028_sfr_rewet_simple": ("6.2.1",), + "test028_sfr_simple": ("6.2.1",), + "test034_nwtp2": ("6.2.1",), + "test034_nwtp2_1d": ("6.2.1",), + "test045_lake1tr_nr": ("6.2.1",), + "test045_lake2tr": ("6.2.1",), + "test045_lake2tr_nr": ("6.2.1",), + "test051_uzfp2": ("6.2.1",), + "test051_uzfp3_lakmvr_v2": ("6.2.1",), + "test051_uzfp3_wellakmvr_v2": ("6.2.1",), + "test045_lake4ss": ("6.2.2",), + "test056_mt3dms_usgs_gwtex_dev": ("6.4.1",), + "test056_mt3dms_usgs_gwtex_IR_dev": ("6.4.1",), +} + + +def test_model(function_tmpdir, test_model_mf6, targets, original_regression): + exdir = test_model_mf6.parent + name = exdir.name + + if name in excluded: + pytest.skip(f"Excluding mf6 model: {name}") + + sim = Simulation( + name=name, + mf6_regression=not original_regression, + cmp_verbose=False, + make_comparison=should_compare(exdir, comparisons, targets), + simpath=str(exdir) + ) -def run_mf6(sim): - """ - Run the MODFLOW 6 simulation and compare to results generated using - 1) the current MODFLOW 6 release, 2) an existing head file, or 3) or - appropriate MODFLOW-2005, MODFLOW-NWT, MODFLOW-USG, or MODFLOW-LGR run. + src = sim.simpath + dst = str(function_tmpdir) - """ - print("Current working directory: ".format(os.getcwd())) - src = os.path.join(example_basedir, sim.name) - dst = os.path.join("temp", sim.name) + # Run the MODFLOW 6 simulation and compare to results generated using + # 1) the current MODFLOW 6 release, 2) an existing head file, or 3) or + # appropriate MODFLOW-2005, MODFLOW-NWT, MODFLOW-USG, or MODFLOW-LGR run. sim.setup(src, dst) sim.run() sim.compare() - sim.teardown() - - -def set_make_comparison(test): - compare_tests = { - "test001e_noUZF_3lay": ("6.2.1",), - "test005_advgw_tidal": ("6.2.1",), - "test017_Crinkle": ("6.2.1",), - "test028_sfr": ("6.2.1",), - "test028_sfr_rewet": ("6.2.1",), - "test028_sfr_rewet_nr": ("6.2.1",), - "test028_sfr_rewet_simple": ("6.2.1",), - "test028_sfr_simple": ("6.2.1",), - "test034_nwtp2": ("6.2.1",), - "test034_nwtp2_1d": ("6.2.1",), - "test045_lake1tr_nr": ("6.2.1",), - "test045_lake2tr": ("6.2.1",), - "test045_lake2tr_nr": ("6.2.1",), - "test051_uzfp2": ("6.2.1",), - "test051_uzfp3_lakmvr_v2": ("6.2.1",), - "test051_uzfp3_wellakmvr_v2": ("6.2.1",), - "test045_lake4ss": ("6.2.2",), - "test056_mt3dms_usgs_gwtex_dev": ("6.4.1",), - "test056_mt3dms_usgs_gwtex_IR_dev": ("6.4.1",), - } - make_comparison = True - if test in compare_tests.keys(): - version = get_mf6_version() - print(f"MODFLOW version='{version}'") - version = get_mf6_version(version="mf6-regression") - print(f"MODFLOW regression version='{version}'") - if version in compare_tests[test]: - make_comparison = False - print(f"Make comparison has been set to False.") - return make_comparison - - -mf6_models = get_mf6_models() - - -@pytest.mark.parametrize( - "exdir", - mf6_models, -) -def test_mf6model(exdir): - # run the test model - run_mf6( - Simulation( - exdir, - mf6_regression=set_mf6_regression(), - cmp_verbose=False, - make_comparison=set_make_comparison(exdir), - ) - ) - - -def main(): - # write message - tnam = os.path.splitext(os.path.basename(__file__))[0] - msg = f"Running {tnam} test" - print(msg) - - # determine if test directory exists - dir_available = is_directory_available(example_basedir) - if not dir_available: - return - - # get a list of test models to run - example_dirs = get_mf6_models() - - # run the test model - for on_dir in example_dirs: - sim = Simulation( - on_dir, - mf6_regression=set_mf6_regression(), - cmp_verbose=True, - make_comparison=set_make_comparison(on_dir), - ) - run_mf6(sim) - - return - - -if __name__ == "__main__": - - print(f"standalone run of {os.path.basename(__file__)}") - - delFiles = True - for idx, arg in enumerate(sys.argv): - if arg.lower() == "--keep": - if len(sys.argv) > idx + 1: - delFiles = False - break - - # run main routine - main() diff --git a/autotest/test_z02_testmodels_mf5to6.py b/autotest/test_z02_testmodels_mf5to6.py index cd11f613889..420607d9022 100644 --- a/autotest/test_z02_testmodels_mf5to6.py +++ b/autotest/test_z02_testmodels_mf5to6.py @@ -1,133 +1,40 @@ import os -import pathlib -import shutil -import sys -import time +import flopy import pytest -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from common_regression import ( - get_example_basedir, - get_example_dirs, - get_home_dir, - get_select_dirs, - get_select_packages, - is_directory_available, - set_mf6_regression, -) +from conftest import should_compare +from common_regression import model_setup, get_namefiles from simulation import Simulation -from targets import get_mf6_version -from targets import target_dict as target_dict - -# find path to examples directory -home = get_home_dir() - - -def get_mf5to6_models(): - """ - Get a list of test models - """ - - # determine if test directory exists - dir_available = is_directory_available(example_basedir) - if not dir_available: - return [] - - # list of example files to exclude - exclude = (None,) - - # write a summary of the files to exclude - print("list of tests to exclude:") - for idx, ex in enumerate(exclude): - print(f" {idx + 1}: {ex}") - - # build list of directories with valid example files - if example_basedir is not None: - example_dirs = get_example_dirs( - example_basedir, - exclude, - prefix="test", - find_sim=False, - ) - else: - example_dirs = [] - - # determine if only a selection of models should be run - select_dirs = None - select_packages = None - for idx, arg in enumerate(sys.argv): - if arg.lower() == "--sim": - if len(sys.argv) > idx + 1: - select_dirs = sys.argv[idx + 1 :] - break - elif arg.lower() == "--pak": - if len(sys.argv) > idx + 1: - select_packages = sys.argv[idx + 1 :] - select_packages = [item.upper() for item in select_packages] - break - - # determine if the selection of model is in the test models to evaluate - if select_dirs is not None: - example_dirs = get_select_dirs(select_dirs, example_dirs) - if len(example_dirs) < 1: - msg = "Selected models not available in test" - print(msg) - - # determine if the specified package(s) is in the test models to evaluate - if select_packages is not None: - example_dirs = get_select_packages( - select_packages, example_basedir, example_dirs - ) - if len(example_dirs) < 1: - msg = "Selected packages not available [" - for idx, pak in enumerate(select_packages): - msg += f"{pak}" - if idx + 1 < len(select_packages): - msg += ", " - msg += "]" - print(msg) - - return example_dirs - - -find_dir = "modflow6-testmodels" -example_basedir = get_example_basedir(home, find_dir, subdir="mf5to6") - -if example_basedir is not None: - assert os.path.isdir(example_basedir) - -# get a list of test models to run -mf5to6_models = get_mf5to6_models() sfmt = "{:25s} - {}" +excluded = ["alt_model"] +comparisons = { + "testPr2": ("6.2.1",), + "testUzfLakSfr": ("6.2.1",), + "testUzfLakSfr_laketable": ("6.2.1",), + "testWetDry": ("6.2.1",), +} + + +def test_model(function_tmpdir, test_model_mf5to6, targets, original_regression): + exdir = test_model_mf5to6.parent + name = exdir.name + + if name in excluded: + pytest.skip(f"Excluding mf5to6 model: {name}") + + sim = Simulation( + exdir.name, + mf6_regression=not original_regression, + cmp_verbose=False, + make_comparison=should_compare(name, comparisons, targets), + simpath=str(exdir) + ) - -def run_mf5to6(sim): - """ - Run the MODFLOW 6 simulation and compare to existing head file or - appropriate MODFLOW-2005, MODFLOW-NWT, MODFLOW-USG, or MODFLOW-LGR run. - - """ - src = os.path.join(example_basedir, sim.name) - dst = os.path.join("temp", f"z02_mf5to6_{sim.name}") - os.makedirs(dst, exist_ok=True) + src = sim.simpath + dst = str(function_tmpdir) # set lgrpth to None lgrpth = None @@ -146,19 +53,19 @@ def run_mf5to6(sim): # copy lgr files to working directory if lgrpth is not None: npth = lgrpth - pymake.setup(lgrpth, dst) + model_setup(lgrpth, dst) # copy MODFLOW-2005, MODFLOW-NWT, or MODFLOW-USG files to working directory else: - npths = pymake.get_namefiles(src) + npths = get_namefiles(src) if len(npths) < 1: msg = f"No name files in {src}" print(msg) assert False npth = npths[0] - pymake.setup(npth, dst) + model_setup(npth, dst) # run the mf5to6 converter - exe = os.path.abspath(target_dict["mf5to6"]) + exe = os.path.abspath(targets["mf5to6"]) print(sfmt.format("using executable", exe)) nmsg = "Program terminated normally" try: @@ -184,94 +91,13 @@ def run_mf5to6(sim): assert success, msg - # standard setup + # model setup src = dst - dst = os.path.join("temp", f"z02_mf6_{sim.name}") + dst = function_tmpdir / "models" sim.setup(src, dst) - # clean up temp/working directory (src) - if os.path.exists(src): - msg = f"Removing {src} directory" - print(msg) - shutil.rmtree(src) - time.sleep(0.5) - - # standard comparison run + # Run the MODFLOW 6 simulation and compare to existing head file or + # appropriate MODFLOW-2005, MODFLOW-NWT, MODFLOW-USG, or MODFLOW-LGR run. sim.run() sim.compare() sim.teardown() - - -def set_make_comparison(test): - compare_tests = { - "testPr2": ("6.2.1",), - "testUzfLakSfr": ("6.2.1",), - "testUzfLakSfr_laketable": ("6.2.1",), - "testWetDry": ("6.2.1",), - } - make_comparison = True - if test in compare_tests.keys(): - version = get_mf6_version() - print(f"MODFLOW version='{version}'") - version = get_mf6_version(version="mf6-regression") - print(f"MODFLOW regression version='{version}'") - if version in compare_tests[test]: - make_comparison = False - return make_comparison - - -@pytest.mark.parametrize( - "exdir", - mf5to6_models, -) -def test_model(exdir): - run_mf5to6( - Simulation( - exdir, - mf6_regression=set_mf6_regression(), - cmp_verbose=False, - make_comparison=set_make_comparison(exdir), - ) - ) - - return - - -def main(): - # write message - tnam = os.path.splitext(os.path.basename(__file__))[0] - msg = f"Running {tnam} test" - print(msg) - - # get name of current file - module_name = sys.modules[__name__].__file__ - - # # get a list of test models to run - # example_dirs = get_mf5to6_models() - - # run the test model - for on_dir in mf5to6_models: - sim = Simulation( - on_dir, - mf6_regression=set_mf6_regression(), - cmp_verbose=False, - make_comparison=set_make_comparison(on_dir), - ) - run_mf5to6(sim) - - return - - -if __name__ == "__main__": - - print(f"standalone run of {os.path.basename(__file__)}") - - delFiles = True - for idx, arg in enumerate(sys.argv): - if arg.lower() == "--keep": - if len(sys.argv) > idx + 1: - delFiles = False - break - - # run main routine - main() diff --git a/autotest/test_z03_examples.py b/autotest/test_z03_examples.py index 6983e411a9b..36eff7ecb6f 100644 --- a/autotest/test_z03_examples.py +++ b/autotest/test_z03_examples.py @@ -1,214 +1,53 @@ -import os -import sys - import pytest -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from common_regression import ( - get_example_basedir, - get_example_dirs, - get_home_dir, - get_select_dirs, - get_select_packages, - is_directory_available, -) +from conftest import should_compare from simulation import Simulation -from targets import get_mf6_version - -# find path to modflow6-examples directory -home = get_home_dir() - -# get example_basedir -find_dir = "modflow6-examples" -example_basedir = get_example_basedir(home, find_dir, subdir="examples") - - -def get_mf6_models(): - """ - Get a list of test models - """ - - # determine if examples directory exists - dir_available = is_directory_available(example_basedir) - if not dir_available: - return [] - - # determine if running on travis - is_CI = "CI" in os.environ - - # tuple of example files to exclude - exclude = ("ex-gwf-csub-p02c",) - - # update exclude - if is_CI: - exclude_CI = (None,) - exclude = exclude + exclude_CI - exclude = list(exclude) - - # write a summary of the files to exclude - print("list of tests to exclude:") - for idx, ex in enumerate(exclude): - print(f" {idx + 1}: {ex}") - - # build list of directories with valid example files - if example_basedir is not None: - example_dirs = get_example_dirs(example_basedir, exclude, prefix="ex-") - else: - example_dirs = [] - - # determine if only a selection of models should be run - select_dirs = None - select_packages = None - for idx, arg in enumerate(sys.argv): - if arg.lower() == "--sim": - if len(sys.argv) > idx + 1: - select_dirs = sys.argv[idx + 1 :] - break - elif arg.lower() == "--pak": - if len(sys.argv) > idx + 1: - select_packages = sys.argv[idx + 1 :] - select_packages = [item.upper() for item in select_packages] - break - - # determine if the selection of model is in the test models to evaluate - if select_dirs is not None: - example_dirs = get_select_dirs(select_dirs, example_dirs) - if len(example_dirs) < 1: - msg = "Selected models not available in test" - print(msg) - - # determine if the specified package(s) is in the test models to evaluate - if select_packages is not None: - example_dirs = get_select_packages( - select_packages, example_basedir, example_dirs - ) - if len(example_dirs) < 1: - msg = "Selected packages not available [" - for pak in select_packages: - msg += f" {pak}" - msg += "]" - print(msg) - - return example_dirs - -def run_mf6(sim): - """ - Run the MODFLOW 6 simulation and compare to existing head file or - appropriate MODFLOW-2005, MODFLOW-NWT, MODFLOW-USG, or MODFLOW-LGR run. - """ - print(os.getcwd()) - src = os.path.join(example_basedir, sim.name) - dst = os.path.join("temp", sim.name) - sim.setup(src, dst) - sim.run() - sim.compare() - sim.teardown() - - -def set_make_comparison(test): - compare_tests = { - "ex-gwf-capture": ("6.2.1",), - "ex-gwf-sagehen": ("6.2.1",), - "ex-gwf-sfr-p01b": ("6.2.1",), - "ex-gwf-nwt-p02a": ("6.2.1",), - "ex-gwf-lak-p01": ("6.2.1",), - "ex-gwf-lak-p02": ("6.2.1",), - "ex-gwf-nwt-p02b": ("6.2.1",), - "ex-gwf-advtidal": ("6.2.1",), - "ex-gwf-sfr-p01": ("6.2.1",), - "ex-gwf-lgr": ("6.2.2",), - "ex-gwt-rotate": ("6.2.2",), - "ex-gwt-gwtgwt-mt3dms-p10": ("6.3.0",), - } - make_comparison = True - if test in compare_tests.keys(): - version = get_mf6_version() - print(f"MODFLOW version='{version}'") - version = get_mf6_version(version="mf6-regression") - print(f"MODFLOW regression version='{version}'") - if version in compare_tests[test]: - print( - f"Test {test} does not run with versions {compare_tests[test]}" - ) - print( - f"Skipping regression test of sim {test} because the version is {version}" - ) - make_comparison = False - return make_comparison - - -mf6_models = get_mf6_models() - - -@pytest.mark.parametrize( - "exdir", - mf6_models, -) -def test_mf6model(exdir): - # run the test model - run_mf6( - Simulation( - exdir, - mf6_regression=True, - cmp_verbose=False, - make_comparison=set_make_comparison(exdir), - ) - ) - - -def main(): - # write message - tnam = os.path.splitext(os.path.basename(__file__))[0] - msg = f"Running {tnam} test" - print(msg) - - # get a list of test models to run - example_dirs = get_mf6_models() - - # run the test model - for on_dir in example_dirs: - mf6_regression = True - make_comparison = set_make_comparison(on_dir) - if not make_comparison: - mf6_regression = False +excluded = ["ex-gwf-csub-p02c"] +comparisons = { + "ex-gwf-capture": ("6.2.1",), + "ex-gwf-sagehen": ("6.2.1",), + "ex-gwf-sfr-p01b": ("6.2.1",), + "ex-gwf-nwt-p02a": ("6.2.1",), + "ex-gwf-lak-p01": ("6.2.1",), + "ex-gwf-lak-p02": ("6.2.1",), + "ex-gwf-nwt-p02b": ("6.2.1",), + "ex-gwf-advtidal": ("6.2.1",), + "ex-gwf-sfr-p01": ("6.2.1",), + "ex-gwf-lgr": ("6.2.2",), + "ex-gwt-rotate": ("6.2.2",), + "ex-gwt-gwtgwt-mt3dms-p10": ("6.3.0",), +} + + +def test_scenario(function_tmpdir, example_scenario, targets): + name, namefiles = example_scenario + exdirs = [nf.parent for nf in namefiles] + + if name in excluded: + pytest.skip(f"Excluding mf6 model: {name}") + + for exdir in exdirs: + model_name = f"{name}_{exdir.name}" + if exdir.name in ["mf6gwt"]: + pytest.skip(f"Skipping coupled GWT model: {name}/{exdir.name}") + + workspace = function_tmpdir / model_name sim = Simulation( - on_dir, - mf6_regression=mf6_regression, + name=model_name, + mf6_regression=True, cmp_verbose=False, - make_comparison=make_comparison, + make_comparison=should_compare(name, comparisons, targets), + simpath=str(exdir) ) - run_mf6(sim) - - return - - -if __name__ == "__main__": - - print(f"standalone run of {os.path.basename(__file__)}") - delFiles = True - for idx, arg in enumerate(sys.argv): - if arg.lower() == "--keep": - if len(sys.argv) > idx + 1: - delFiles = False - break + src = sim.simpath + dst = str(workspace) - # run main routine - main() + # Run the MODFLOW 6 simulation and compare to existing head file or + # appropriate MODFLOW-2005, MODFLOW-NWT, MODFLOW-USG, or MODFLOW-LGR run. + sim.setup(src, dst) + sim.run() + sim.compare() + sim.teardown() diff --git a/autotest/test_z03_largetestmodels.py b/autotest/test_z03_largetestmodels.py index dc3fa8e2c8f..08196fbf66b 100644 --- a/autotest/test_z03_largetestmodels.py +++ b/autotest/test_z03_largetestmodels.py @@ -1,197 +1,38 @@ -import os -import sys - import pytest -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from common_regression import ( - get_example_basedir, - get_example_dirs, - get_home_dir, - get_select_dirs, - get_select_packages, - is_directory_available, - set_mf6_regression, -) +from conftest import should_compare from simulation import Simulation -from targets import get_mf6_version - -home = get_home_dir() - -find_dir = "modflow6-largetestmodels" -example_basedir = get_example_basedir(home, find_dir) - - -def get_mf6_models(): - """ - Get a list of test models - """ - - # determine if largetest directory exists - dir_available = is_directory_available(example_basedir) - if not dir_available: - return [] - # determine if running on CI - is_CI = "CI" in os.environ - # tuple of example files to exclude - exclude = (None,) +excluded = [] +comparisons = { + "test1004_mvlake_laksfr_tr": ("6.2.2",), + "test1004_mvlake_lak_tr": ("6.2.1",), + "test1003_MNW2_Fig28": ("6.2.1",), + "test1001_Peterson": ("6.2.1",), +} - # update exclude - if is_CI: - exclude_CI = (None,) - exclude = exclude + exclude_CI - exclude = list(exclude) - # write a summary of the files to exclude - print("list of tests to exclude:") - for idx, ex in enumerate(exclude): - print(f" {idx + 1}: {ex}") +def test_model(function_tmpdir, large_test_model, targets, original_regression): + exdir = large_test_model.parent + name = exdir.name - # build list of directories with valid example files - if example_basedir is not None: - example_dirs = get_example_dirs( - example_basedir, exclude, prefix="test" - ) - else: - example_dirs = [] - - # determine if only a selection of models should be run - select_dirs = None - select_packages = None - for idx, arg in enumerate(sys.argv): - if arg.lower() == "--sim": - if len(sys.argv) > idx + 1: - select_dirs = sys.argv[idx + 1 :] - break - elif arg.lower() == "--pak": - if len(sys.argv) > idx + 1: - select_packages = sys.argv[idx + 1 :] - select_packages = [item.upper() for item in select_packages] - break - - # determine if the selection of model is in the test models to evaluate - if select_dirs is not None: - example_dirs = get_select_dirs(select_dirs, example_dirs) - if len(example_dirs) < 1: - msg = "Selected models not available in test" - print(msg) - - # determine if the specified package(s) is in the test models to evaluate - if select_packages is not None: - example_dirs = get_select_packages( - select_packages, example_basedir, example_dirs - ) - if len(example_dirs) < 1: - msg = "Selected packages not available [" - for pak in select_packages: - msg += f" {pak}" - msg += "]" - print(msg) - - return example_dirs + if name in excluded: + pytest.skip(f"Excluding large mf6 model: {name}") + sim = Simulation( + name=name, + mf6_regression=not original_regression, + cmp_verbose=False, + make_comparison=should_compare(name, comparisons, targets), + simpath=str(exdir) + ) -def run_mf6(sim): - """ - Run the MODFLOW 6 simulation and compare to existing head file or - appropriate MODFLOW-2005, MODFLOW-NWT, MODFLOW-USG, or MODFLOW-LGR run. + src = sim.simpath + dst = str(function_tmpdir) - """ - print(os.getcwd()) - src = os.path.join(example_basedir, sim.name) - dst = os.path.join("temp", sim.name) + # Run the MODFLOW 6 simulation and compare to existing head file or + # appropriate MODFLOW-2005, MODFLOW-NWT, MODFLOW-USG, or MODFLOW-LGR run. sim.setup(src, dst) sim.run() sim.compare() - sim.teardown() - - -def set_make_comparison(test): - compare_tests = { - "test1004_mvlake_laksfr_tr": ("6.2.2",), - "test1004_mvlake_lak_tr": ("6.2.1",), - "test1003_MNW2_Fig28": ("6.2.1",), - "test1001_Peterson": ("6.2.1",), - } - make_comparison = True - if test in compare_tests.keys(): - version = get_mf6_version() - print(f"MODFLOW version='{version}'") - version = get_mf6_version(version="mf6-regression") - print(f"MODFLOW regression version='{version}'") - if version in compare_tests[test]: - make_comparison = False - return make_comparison - - -mf6_models = get_mf6_models() - - -@pytest.mark.parametrize( - "exdir", - mf6_models, -) -def test_mf6model(exdir): - # run the test model - run_mf6( - Simulation( - exdir, - mf6_regression=set_mf6_regression(), - cmp_verbose=False, - make_comparison=set_make_comparison(exdir), - ) - ) - - -def main(): - # write message - tnam = os.path.splitext(os.path.basename(__file__))[0] - msg = f"Running {tnam} test" - print(msg) - - # get a list of test models to run - example_dirs = get_mf6_models() - - # run the test model - for on_dir in example_dirs: - sim = Simulation( - on_dir, - mf6_regression=set_mf6_regression(), - cmp_verbose=False, - make_comparison=set_make_comparison(on_dir), - ) - run_mf6(sim) - - return - - -if __name__ == "__main__": - - print(f"standalone run of {os.path.basename(__file__)}") - - delFiles = True - for idx, arg in enumerate(sys.argv): - if arg.lower() == "--keep": - if len(sys.argv) > idx + 1: - delFiles = False - break - - # run main routine - main() From 620ac711642b01f9b85c13a8391c484382d474a9 Mon Sep 17 00:00:00 2001 From: w-bonelli Date: Sun, 15 Jan 2023 17:19:01 -0500 Subject: [PATCH 019/123] ci: fix tests and readthedocs (#1132) * add pytest as a ReadTheDocs dependency * update DEVELOPER.md (TOC, minor fixes) * don't install modflow6-examples for commit-triggered CI * explicitly deselect large/example model tests in commit-triggered CI * fix conftest.should_compare() --- .build_rtd_docs/requirements.rtd.txt | 5 +- .github/workflows/ci.yml | 63 ++++---------------------- DEVELOPER.md | 57 +++++++++++++++-------- autotest/conftest.py | 22 ++++----- autotest/pytest.ini | 4 ++ autotest/test_z01_testmodels_mf6.py | 21 +++++---- autotest/test_z02_testmodels_mf5to6.py | 27 ++++++----- autotest/test_z03_examples.py | 47 ++++++++++++++----- autotest/test_z03_largetestmodels.py | 21 +++++---- 9 files changed, 140 insertions(+), 127 deletions(-) diff --git a/.build_rtd_docs/requirements.rtd.txt b/.build_rtd_docs/requirements.rtd.txt index 04b86269661..a4e1fc13455 100644 --- a/.build_rtd_docs/requirements.rtd.txt +++ b/.build_rtd_docs/requirements.rtd.txt @@ -8,7 +8,4 @@ ipykernel rtds_action myst_parser sphinx_rtd_theme - - - - +pytest diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4f8244733ea..937a35e554a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -99,12 +99,6 @@ jobs: repository: MODFLOW-USGS/modflow6-testmodels path: modflow6-testmodels - - name: Checkout modflow6-examples - uses: actions/checkout@v3 - with: - repository: MODFLOW-USGS/modflow6-examples - path: modflow6-examples - - name: Setup GNU Fortran ${{ env.GCC_V }} uses: awvwgk/setup-fortran@main with: @@ -118,24 +112,6 @@ jobs: cache-downloads: true cache-env: true - - name: Cache modflow6 examples - id: cache-examples - uses: actions/cache@v3 - with: - path: modflow6-examples/examples - key: modflow6-examples-${{ hashFiles('modflow6-examples/scripts/**') }} - - - name: Install extra Python packages - if: steps.cache-examples.outputs.cache-hit != 'true' - working-directory: modflow6-examples/etc - run: | - pip install -r requirements.pip.txt - - - name: Build example models - if: steps.cache-examples.outputs.cache-hit != 'true' - working-directory: modflow6-examples/etc - run: python ci_build_files.py - - name: Build modflow6 working-directory: modflow6 run: | @@ -157,9 +133,9 @@ jobs: working-directory: modflow6/autotest run: | if [ "${{ github.ref_name }}" == "master" ]; then - pytest -v -n auto --durations 0 -m "not developmode" + pytest -v -n auto --durations 0 -m "not large and not developmode" else - pytest -v -n auto --durations 0 + pytest -v -n auto --durations 0 -m "not large" fi - name: Test scripts @@ -231,9 +207,9 @@ jobs: working-directory: modflow6/autotest run: | if [ "${{ github.ref_name }}" == "master" ]; then - pytest -v -n auto --durations 0 -m "not developmode" + pytest -v -n auto --durations 0 -m "not large and not developmode" else - pytest -v -n auto --durations 0 + pytest -v -n auto --durations 0 -m "not large" fi test_ifort: @@ -262,12 +238,6 @@ jobs: repository: MODFLOW-USGS/modflow6-testmodels path: modflow6-testmodels - - name: Checkout modflow6-examples - uses: actions/checkout@v3 - with: - repository: MODFLOW-USGS/modflow6-examples - path: modflow6-examples - - name: Setup Micromamba uses: mamba-org/provision-with-micromamba@main with: @@ -286,23 +256,6 @@ jobs: $mamba_bin = "C:\Users\runneradmin\micromamba-root\envs\modflow6\Scripts" echo $mamba_bin | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append - - name: Cache modflow6 examples - id: cache-examples - uses: actions/cache@v3 - with: - path: modflow6-examples/examples - key: modflow6-examples-${{ hashFiles('modflow6-examples/scripts/**') }} - - - name: Install extra Python packages - if: steps.cache-examples.outputs.cache-hit != 'true' - working-directory: modflow6-examples/etc - run: pip install -r requirements.pip.txt - - - name: Build example models - if: steps.cache-examples.outputs.cache-hit != 'true' - working-directory: modflow6-examples/etc - run: python ci_build_files.py - - name: Update version files working-directory: modflow6/distribution run: python update_version.py @@ -348,9 +301,9 @@ jobs: working-directory: modflow6/autotest run: | if [ "${{ github.ref_name }}" == "master" ]; then - pytest -v -n auto --durations 0 -m "not developmode" + pytest -v -n auto --durations 0 -m "not large and not developmode" else - pytest -v -n auto --durations 0 + pytest -v -n auto --durations 0 -m "not large" fi - name: Test programs (Windows) @@ -359,9 +312,9 @@ jobs: shell: pwsh run: | if ( "${{ github.ref_name }}" -eq "master" ) { - pytest -v -n auto --durations 0 -m "not developmode" + pytest -v -n auto --durations 0 -m "not large and not developmode" } else { - pytest -v -n auto --durations 0 + pytest -v -n auto --durations 0 -m "not large" } - name: Test scripts diff --git a/DEVELOPER.md b/DEVELOPER.md index f9aa18a23b3..78272df0b10 100644 --- a/DEVELOPER.md +++ b/DEVELOPER.md @@ -1,17 +1,39 @@ -# Building and Testing MODFLOW 6 +# Developing MODFLOW 6 This document describes how to set up your development environment to build and test MODFLOW 6. -It also explains the basic mechanics of using `git`. Details on how to contribute your code to the repository are found in the separate document [CONTRIBUTING.md](CONTRIBUTING.md) - -* [Prerequisite Software](#prerequisite-software) -* [Getting the Sources](#getting-the-sources) -* [Building](#building) -* [Running Tests Locally](#running-tests-locally) - -See the [contribution guidelines](https://github.com/MODFLOW-USGS/modflow6/blob/develop/CONTRIBUTING.md) -if you'd like to contribute to MODFLOW 6. - -## Prerequisite Software +It also explains the basic mechanics of using `git`. Details on how to contribute your code to the repository are found in the separate document [CONTRIBUTING.md](CONTRIBUTING.md). + + + + + +- [Requirements](#requirements) + - [Git](#git) + - [gfortran (version 4.9 to 10)](#gfortran-version-49-to-10) + - [Linux](#linux) + - [macOS](#macos) + - [Windows](#windows) + - [Python](#python) + - [ifort (optional)](#ifort-optional) + - [Windows](#windows-1) + - [Doxygen & LaTeX (optional)](#doxygen--latex-optional) + - [fprettify](#fprettify) +- [Installation](#installation) +- [Building](#building) + - [Meson](#meson) + - [Visual Studio](#visual-studio) + - [Pymake](#pymake) + - [Make](#make) +- [Testing](#testing) + - [External model repos](#external-model-repos) + - [Installing external repos](#installing-external-repos) + - [Test models](#test-models) + - [Example models](#example-models) + - [Running external model tests](#running-external-model-tests) + + + +## Requirements Before you can build and test MODFLOW 6, you must install and configure the following products on your development machine. @@ -79,7 +101,7 @@ These programs can be installed from various sources, including by conda, macpor [fprettify](https://github.com/pseewald/fprettify) can be used to format Fortran source code and in combination with the [MODFLOW 6 fprettify configuration](https://github.com/MODFLOW-USGS/modflow6/blob/develop/distribution/.fprettify.yaml) establishes a contribution standard for properly formatted MODFLOW 6 Fortran source. This tool can be installed with `pip` or `conda` and used from the command line or integrated with a [VSCode](https://github.com/MODFLOW-USGS/modflow6/blob/develop/.vscode/README.md) or Visual Studio development environment. See [contribution guidelines](https://github.com/MODFLOW-USGS/modflow6/blob/develop/CONTRIBUTING.md) for additional information. -## Getting the Sources +## Installation Fork and clone the MODFLOW 6 repository: @@ -151,15 +173,14 @@ The README also explains how to build MODFLOW 6 with it. We also provide make files which can be used to build MODFLOW 6 with [GNU Make](https://www.gnu.org/software/make/). For the build instructions we refer to the [GNU Make Manual](https://www.gnu.org/software/make/manual/). - -## Running Tests +## Testing Tests should pass locally before a PR is opened on Github. All the tests are executed by the CI system and a pull request can only be merged with passing tests. -Tests must be run from the `autotest` folder: +Tests must be run from the `autotest` folder. ```shell -cd modflow6/autotest +cd autotest ``` FloPy plugins must first be updated: @@ -204,7 +225,7 @@ While many tests create models programmatically, the full suite tests MODFLOW 6 #### Installing external repos -By default, the tests expect these repositories side-by-side with (i.e. in the same parent directory as) the `modflow6` repository. If the repos are somewhere else, you can set the `REPOS_PATH` environment variable to point to their parent directory. +By default, the tests expect these repositories side-by-side with (i.e. in the same parent directory as) the `modflow6` repository. If the repos are somewhere else, you can set the `REPOS_PATH` environment variable to point to their parent directory. If external model repositories are not found, tests requiring them will be skipped. **Note:** a convenient way to persist environment variables needed for tests is to store them in a `.env` file in the `autotest` folder. Each variable should be defined on a separate line, with format `KEY=VALUE`. The `pytest-dotenv` plugin will then automatically load any variables found in this file into the test process' environment. diff --git a/autotest/conftest.py b/autotest/conftest.py index 5a1c9ffc112..d40ed2d2487 100644 --- a/autotest/conftest.py +++ b/autotest/conftest.py @@ -7,18 +7,18 @@ project_root_path = Path(__file__).parent.parent -def should_compare(test: str, comparisons: dict, executables: Executables) -> bool: +def should_compare( + test: str, comparisons: dict, executables: Executables +) -> bool: if test in comparisons.keys(): - version = Executables.get_version(path=executables.mf6) - print(f"MODFLOW 6 development version='{version}'") - version = Executables.get_version(path=executables.mf6_regression) - print(f"MODFLOW 6 regression version='{version}'") - if version in comparisons[test]: + dev_ver = Executables.get_version(path=executables.mf6).split(' ')[0] + reg_ver = Executables.get_version(path=executables.mf6_regression).split(' ')[0] + print(f"MODFLOW 6 development version: {dev_ver}") + print(f"MODFLOW 6 regression version: {reg_ver}") + excluded = list(comparisons[test]) + if reg_ver in excluded: print( - f"Test {test} does not run with versions {comparisons[test]}" - ) - print( - f"Skipping regression test of sim {test} because the version is {version}" + f"Regression version {reg_ver} not supported for test {test}, skipping comparison" ) return False return True @@ -45,5 +45,5 @@ def pytest_addoption(parser): "--original-regression", action="store_true", default=False, - help="TODO" + help="TODO", ) diff --git a/autotest/pytest.ini b/autotest/pytest.ini index d718b03a102..be844af8904 100644 --- a/autotest/pytest.ini +++ b/autotest/pytest.ini @@ -1,3 +1,7 @@ [pytest] markers = + slow: tests taking more than a few seconds to complete + repo: tests using models loaded from an external repository + large: tests using large models (examples and largetestmodels) + regression: tests comparing results from different versions developmode: tests that should only run with IDEVELOPMODE = 1 \ No newline at end of file diff --git a/autotest/test_z01_testmodels_mf6.py b/autotest/test_z01_testmodels_mf6.py index 57c9c810c79..a58a918d715 100644 --- a/autotest/test_z01_testmodels_mf6.py +++ b/autotest/test_z01_testmodels_mf6.py @@ -1,12 +1,12 @@ import pytest - - from conftest import should_compare from simulation import Simulation - -excluded = ["alt_model"] -comparisons = { +excluded_models = [ + "alt_model", + "test205_gwtbuy-henrytidal" +] +excluded_comparisons = { "test001e_noUZF_3lay": ("6.2.1",), "test005_advgw_tidal": ("6.2.1",), "test017_Crinkle": ("6.2.1",), @@ -29,22 +29,25 @@ } +@pytest.mark.repo +@pytest.mark.regression def test_model(function_tmpdir, test_model_mf6, targets, original_regression): exdir = test_model_mf6.parent name = exdir.name - if name in excluded: + if name in excluded_models: pytest.skip(f"Excluding mf6 model: {name}") sim = Simulation( name=name, + exe_dict=targets.as_dict(), mf6_regression=not original_regression, cmp_verbose=False, - make_comparison=should_compare(exdir, comparisons, targets), - simpath=str(exdir) + make_comparison=should_compare(name, excluded_comparisons, targets), + simpath=str(exdir), ) - src = sim.simpath + src = exdir dst = str(function_tmpdir) # Run the MODFLOW 6 simulation and compare to results generated using diff --git a/autotest/test_z02_testmodels_mf5to6.py b/autotest/test_z02_testmodels_mf5to6.py index 420607d9022..aac388c3b8e 100644 --- a/autotest/test_z02_testmodels_mf5to6.py +++ b/autotest/test_z02_testmodels_mf5to6.py @@ -2,15 +2,16 @@ import flopy import pytest - +from common_regression import get_namefiles, model_setup from conftest import should_compare -from common_regression import model_setup, get_namefiles from simulation import Simulation - sfmt = "{:25s} - {}" -excluded = ["alt_model"] -comparisons = { +excluded_models = [ + "alt_model", + "mf2005" +] +excluded_comparisons = { "testPr2": ("6.2.1",), "testUzfLakSfr": ("6.2.1",), "testUzfLakSfr_laketable": ("6.2.1",), @@ -18,19 +19,24 @@ } -def test_model(function_tmpdir, test_model_mf5to6, targets, original_regression): +@pytest.mark.repo +@pytest.mark.regression +def test_model( + function_tmpdir, test_model_mf5to6, targets, original_regression +): exdir = test_model_mf5to6.parent name = exdir.name - if name in excluded: + if name in excluded_models: pytest.skip(f"Excluding mf5to6 model: {name}") sim = Simulation( - exdir.name, + name=exdir.name, + exe_dict=targets.as_dict(), mf6_regression=not original_regression, cmp_verbose=False, - make_comparison=should_compare(name, comparisons, targets), - simpath=str(exdir) + make_comparison=should_compare(name, excluded_comparisons, targets), + simpath=str(exdir), ) src = sim.simpath @@ -100,4 +106,3 @@ def test_model(function_tmpdir, test_model_mf5to6, targets, original_regression) # appropriate MODFLOW-2005, MODFLOW-NWT, MODFLOW-USG, or MODFLOW-LGR run. sim.run() sim.compare() - sim.teardown() diff --git a/autotest/test_z03_examples.py b/autotest/test_z03_examples.py index 36eff7ecb6f..a5db81a0900 100644 --- a/autotest/test_z03_examples.py +++ b/autotest/test_z03_examples.py @@ -1,11 +1,35 @@ import pytest - from conftest import should_compare from simulation import Simulation - -excluded = ["ex-gwf-csub-p02c"] -comparisons = { +# skip nested models +# ex-gwf-csub-p02c has subdirs like 'es-001', 'hb-100' +# all others just have 2 folders 'mf6gwf' and 'mf6gwt' +excluded_models = [ + "ex-gwf-csub-p02c", + "ex-gwt-hecht-mendez-b", + "ex-gwt-hecht-mendez-c", + "ex-gwt-keating", + "ex-gwt-moc3d-p01a", + "ex-gwt-moc3d-p01b", + "ex-gwt-moc3d-p01c", + "ex-gwt-moc3d-p01d", + "ex-gwt-moc3d-p02", + "ex-gwt-moc3d-p02tg", + "ex-gwt-mt3dms-p02a", + "ex-gwt-mt3dms-p02b", + "ex-gwt-mt3dms-p02c", + "ex-gwt-mt3dms-p02d", + "ex-gwt-mt3dms-p02e", + "ex-gwt-mt3dms-p02f", + "ex-gwt-mt3dsupp631", + "ex-gwt-mt3dsupp632a", + "ex-gwt-mt3dsupp632b", + "ex-gwt-mt3dsupp632c", + "ex-gwt-mt3dsupp82", + "ex-gwt-prudic2004t2", +] +excluded_comparisons = { "ex-gwf-capture": ("6.2.1",), "ex-gwf-sagehen": ("6.2.1",), "ex-gwf-sfr-p01b": ("6.2.1",), @@ -21,25 +45,27 @@ } +@pytest.mark.large +@pytest.mark.repo +@pytest.mark.regression +@pytest.mark.slow def test_scenario(function_tmpdir, example_scenario, targets): name, namefiles = example_scenario exdirs = [nf.parent for nf in namefiles] - if name in excluded: + if name in excluded_models: pytest.skip(f"Excluding mf6 model: {name}") for exdir in exdirs: model_name = f"{name}_{exdir.name}" - if exdir.name in ["mf6gwt"]: - pytest.skip(f"Skipping coupled GWT model: {name}/{exdir.name}") - workspace = function_tmpdir / model_name sim = Simulation( name=model_name, + exe_dict=targets.as_dict(), mf6_regression=True, cmp_verbose=False, - make_comparison=should_compare(name, comparisons, targets), - simpath=str(exdir) + make_comparison=should_compare(name, excluded_comparisons, targets), + simpath=str(exdir), ) src = sim.simpath @@ -50,4 +76,3 @@ def test_scenario(function_tmpdir, example_scenario, targets): sim.setup(src, dst) sim.run() sim.compare() - sim.teardown() diff --git a/autotest/test_z03_largetestmodels.py b/autotest/test_z03_largetestmodels.py index 08196fbf66b..4631a799baf 100644 --- a/autotest/test_z03_largetestmodels.py +++ b/autotest/test_z03_largetestmodels.py @@ -1,11 +1,9 @@ import pytest - from conftest import should_compare from simulation import Simulation - -excluded = [] -comparisons = { +excluded_models = [] +excluded_comparisons = { "test1004_mvlake_laksfr_tr": ("6.2.2",), "test1004_mvlake_lak_tr": ("6.2.1",), "test1003_MNW2_Fig28": ("6.2.1",), @@ -13,19 +11,26 @@ } -def test_model(function_tmpdir, large_test_model, targets, original_regression): +@pytest.mark.large +@pytest.mark.repo +@pytest.mark.regression +@pytest.mark.slow +def test_model( + function_tmpdir, large_test_model, targets, original_regression +): exdir = large_test_model.parent name = exdir.name - if name in excluded: + if name in excluded_models: pytest.skip(f"Excluding large mf6 model: {name}") sim = Simulation( name=name, + exe_dict=targets.as_dict(), mf6_regression=not original_regression, cmp_verbose=False, - make_comparison=should_compare(name, comparisons, targets), - simpath=str(exdir) + make_comparison=should_compare(name, excluded_comparisons, targets), + simpath=str(exdir), ) src = sim.simpath From f30312056bb4624ee8ea2d11b70badda2d0aaca0 Mon Sep 17 00:00:00 2001 From: w-bonelli Date: Mon, 16 Jan 2023 15:19:57 -0500 Subject: [PATCH 020/123] ci: add filelock as readthedocs dependency (#1135) --- .build_rtd_docs/requirements.rtd.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/.build_rtd_docs/requirements.rtd.txt b/.build_rtd_docs/requirements.rtd.txt index a4e1fc13455..e1bd600bf6b 100644 --- a/.build_rtd_docs/requirements.rtd.txt +++ b/.build_rtd_docs/requirements.rtd.txt @@ -9,3 +9,4 @@ rtds_action myst_parser sphinx_rtd_theme pytest +filelock \ No newline at end of file From 31f1e942215cbbc444711e753ba5a78dfd27a646 Mon Sep 17 00:00:00 2001 From: w-bonelli Date: Tue, 17 Jan 2023 10:42:26 -0500 Subject: [PATCH 021/123] ci: fix readthedocs (#1136) * use myst-parser (and remove recommonmark) * add modflow-devtools as rtd dependency * use update_version fn directly in conf.py * add explanatory comment to docs.yml * update copyright year from 2020 to 2023 --- .build_rtd_docs/conf.py | 17 ++++------------- .build_rtd_docs/requirements.rtd.txt | 5 +++-- .doc/conf.py | 4 ++-- .doc/requirements.txt | 2 +- .github/workflows/docs.yml | 28 ++++++++++------------------ 5 files changed, 20 insertions(+), 36 deletions(-) diff --git a/.build_rtd_docs/conf.py b/.build_rtd_docs/conf.py index 6b90638e3bd..bb3cdd98936 100644 --- a/.build_rtd_docs/conf.py +++ b/.build_rtd_docs/conf.py @@ -17,6 +17,7 @@ from subprocess import Popen, PIPE sys.path.insert(0, os.path.abspath(os.path.join("..", "doc"))) +sys.path.insert(0, os.path.abspath(os.path.join("..", "distribution"))) # -- determine if running on readthedocs ------------------------------------ on_rtd = os.environ.get("READTHEDOCS") == "True" @@ -35,18 +36,8 @@ # -- Update the modflow 6 version ------------------------------------------- print("Update the modflow6 version") -pth = os.path.join("..", "distribution") -args = ( - "python", - "update_version.py", -) -# run the command -proc = Popen(args, stdout=PIPE, stderr=PIPE, cwd=pth) -stdout, stderr = proc.communicate() -if stdout: - print(stdout.decode("utf-8")) -if stderr: - print("Errors:\n{}".format(stderr.decode("utf-8"))) +from update_version import update_version +update_version() # -- import version from doc/version.py ------------------------------------- from version import __version__ @@ -92,7 +83,7 @@ # -- Project information ----------------------------------------------------- project = "MODFLOW 6 Program Documentation" -copyright = "2020, MODFLOW Development Team" +copyright = "2023, MODFLOW Development Team" author = "MODFLOW Development Team" # -- Project version --------------------------------------------------------- diff --git a/.build_rtd_docs/requirements.rtd.txt b/.build_rtd_docs/requirements.rtd.txt index e1bd600bf6b..e531a07c623 100644 --- a/.build_rtd_docs/requirements.rtd.txt +++ b/.build_rtd_docs/requirements.rtd.txt @@ -6,7 +6,8 @@ nbsphinx_link ipython ipykernel rtds_action -myst_parser +myst-parser sphinx_rtd_theme pytest -filelock \ No newline at end of file +filelock +modflow-devtools \ No newline at end of file diff --git a/.doc/conf.py b/.doc/conf.py index 9f69c706d69..599c330ac4d 100644 --- a/.doc/conf.py +++ b/.doc/conf.py @@ -49,7 +49,7 @@ # -- Project information ----------------------------------------------------- project = "MODFLOW 6 Program Documentation" -copyright = "2020, MODFLOW Development Team" +copyright = "2023, MODFLOW Development Team" author = "MODFLOW Development Team" # -- General configuration --------------------------------------------------- @@ -70,7 +70,7 @@ "sphinx.ext.viewcode", "IPython.sphinxext.ipython_console_highlighting", # lowercase didn't work "sphinx.ext.autosectionlabel", - "recommonmark", + "myst_parser", "sphinx_markdown_tables", ] diff --git a/.doc/requirements.txt b/.doc/requirements.txt index 87f6ad06464..2174ec428a1 100644 --- a/.doc/requirements.txt +++ b/.doc/requirements.txt @@ -3,4 +3,4 @@ ipython ipykernel rtds_action sphinx_rtd_theme -recommonmark +myst-parser diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index b4d4bae2b5a..fe278c93186 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -22,8 +22,6 @@ jobs: shell: bash -l {0} env: GCC_V: 12 - working-directory: .build_rtd_docs - distribution-directory: distribution steps: - name: Checkout modflow6 @@ -52,12 +50,10 @@ jobs: - name: Install additional packages for Sphinx using pip working-directory: modflow6/.build_rtd_docs - run: | - pip install -r requirements.rtd.txt + run: pip install -r requirements.rtd.txt - name: Print python package versions - run: | - pip list + run: pip list - name: Install TeX Live run: | @@ -66,13 +62,11 @@ jobs: - name: Install USGS LaTeX style files and Univers font working-directory: usgslatex/usgsLaTeX - run: | - sudo ./install.sh --all-users + run: sudo ./install.sh --all-users - name: Test building files from dfn's for LaTeX working-directory: modflow6/autotest - run: | - pytest -v build_mfio_tex.py + run: pytest -v build_mfio_tex.py - name: Setup GNU Fortran ${{ env.GCC_V }} uses: awvwgk/setup-fortran@main @@ -103,21 +97,19 @@ jobs: - name: Run benchmarks working-directory: modflow6/distribution + run: python benchmark.py env: GITHUB_TOKEN: ${{ github.token }} - run: python benchmark.py - name: Run sphinx working-directory: modflow6/.build_rtd_docs - run: | - make html + run: make html - - name: Show results + - name: Show benchmarks working-directory: modflow6/distribution - run: | - cat run-time-comparison.md + run: cat run-time-comparison.md - - name: Upload comparison + - name: Upload benchmarks uses: actions/upload-artifact@v3 with: name: run-time-comparison @@ -128,9 +120,9 @@ jobs: with: name: rtd-files-for-${{ github.sha }} path: | - modflow6/.build_rtd_docs/ modflow6/.build_rtd_docs/index.rst modflow6/.build_rtd_docs/mf6io.rst + # run-time-comparison is moved to _mf6run by conf.py modflow6/.build_rtd_docs/_mf6run/ modflow6/.build_rtd_docs/_mf6io/ modflow6/.build_rtd_docs/_static/ From e51530cd90d701549a00c216b371fcc6281451da Mon Sep 17 00:00:00 2001 From: w-bonelli Date: Wed, 18 Jan 2023 13:21:16 -0500 Subject: [PATCH 022/123] refactor(tests): (#1066) * refactor(tests): refactor test framework * use keepable temporary dirs instead of manually managed temp folder * use pytest-order and add optional --path arg to update_flopy.py CLI * remove binary_file_writer.py, disu_util.py, budget_file_compare.py * rename Simulation -> TestSimulation and Framework -> TestFramework * partially refactor gwf test scripts with pytest-cases * remove mfpymake as a testing dependency * chore(tests): remove print statements referencing pymake * chore(tests): add large model test marker to pytest.ini * fix(tests): use keepable tmpdirs in pytest-cases POC --- .github/workflows/ci.yml | 65 ++ DEVELOPER.md | 321 ++++-- README.md | 1 + autotest/binary_file_writer.py | 202 ---- autotest/budget_file_compare.py | 120 --- autotest/build_exes.py | 14 +- .../{scripts => }/cross_section_functions.py | 0 autotest/disu_util.py | 101 -- autotest/framework.py | 71 +- autotest/get_exes.py | 23 +- autotest/pyproject.toml | 2 +- autotest/pytest.ini | 11 +- autotest/simulation.py | 112 +- autotest/targets.py | 98 -- autotest/test_cli.py | 14 +- autotest/test_gwf.py | 27 + autotest/test_gwf_ats01.py | 77 +- autotest/test_gwf_ats02.py | 86 +- autotest/test_gwf_ats03.py | 73 +- autotest/test_gwf_ats_lak01.py | 92 +- autotest/test_gwf_auxvars.py | 69 +- autotest/test_gwf_auxvars02.py | 69 +- autotest/test_gwf_boundname01.py | 91 +- autotest/test_gwf_buy_lak01.py | 63 +- autotest/test_gwf_buy_lak02.py | 819 ++++++++------- autotest/test_gwf_buy_maw01.py | 64 +- autotest/test_gwf_buy_sfr01.py | 70 +- autotest/test_gwf_chd01.py | 85 +- autotest/test_gwf_csub_db01_nr.py | 92 +- autotest/test_gwf_csub_dbgeo01.py | 87 +- autotest/test_gwf_csub_inelastic.py | 90 +- autotest/test_gwf_csub_ndb01_nr.py | 93 +- autotest/test_gwf_csub_sk01.py | 976 +++++++++--------- autotest/test_gwf_csub_sk02.py | 120 +-- autotest/test_gwf_csub_sk03.py | 116 +-- autotest/test_gwf_csub_sk04_nr.py | 91 +- autotest/test_gwf_csub_sub01.py | 93 +- autotest/test_gwf_csub_sub01_adjmat.py | 103 +- autotest/test_gwf_csub_sub01_elastic.py | 91 +- autotest/test_gwf_csub_sub01_pch.py | 97 +- autotest/test_gwf_csub_sub02.py | 89 +- autotest/test_gwf_csub_sub03.py | 108 +- autotest/test_gwf_csub_subwt01.py | 107 +- autotest/test_gwf_csub_subwt02.py | 109 +- autotest/test_gwf_csub_subwt03.py | 109 +- autotest/test_gwf_csub_wc01.py | 94 +- autotest/test_gwf_csub_wtgeo.py | 130 +-- autotest/test_gwf_csub_zdisp01.py | 119 +-- .../{test_gwf_disu01.py => test_gwf_disu.py} | 79 +- autotest/test_gwf_disv_uzf.py | 82 +- autotest/test_gwf_drn_ddrn01.py | 91 +- autotest/test_gwf_drn_ddrn02.py | 96 +- autotest/test_gwf_errors.py | 127 +-- autotest/test_gwf_evt01.py | 70 +- autotest/test_gwf_evt02.py | 75 +- autotest/test_gwf_henry_nr.py | 55 +- autotest/test_gwf_ifmod_buy.py | 64 +- autotest/test_gwf_ifmod_mult_exg.py | 68 +- autotest/test_gwf_ifmod_rewet.py | 64 +- autotest/test_gwf_ifmod_vert.py | 64 +- autotest/test_gwf_ifmod_xt3d01.py | 68 +- autotest/test_gwf_ifmod_xt3d02.py | 66 +- autotest/test_gwf_ims_rcm_reorder.py | 91 +- autotest/test_gwf_lakobs01.py | 111 +- autotest/test_gwf_libmf6_evt01.py | 87 +- autotest/test_gwf_libmf6_ghb01.py | 83 +- autotest/test_gwf_libmf6_ifmod01.py | 86 +- autotest/test_gwf_libmf6_ifmod02.py | 86 +- autotest/test_gwf_libmf6_ifmod03.py | 84 +- autotest/test_gwf_libmf6_rch01.py | 85 +- autotest/test_gwf_libmf6_rch02.py | 100 +- autotest/test_gwf_libmf6_riv01.py | 86 +- autotest/test_gwf_libmf6_riv02.py | 83 +- autotest/test_gwf_libmf6_sto01.py | 86 +- autotest/test_gwf_maw01.py | 252 ----- autotest/test_gwf_maw02.py | 357 ------- autotest/test_gwf_maw03.py | 275 ----- autotest/test_gwf_maw04.py | 439 ++++---- autotest/test_gwf_maw05.py | 63 +- autotest/test_gwf_maw06.py | 62 +- autotest/test_gwf_maw07.py | 62 +- autotest/test_gwf_maw08.py | 62 +- autotest/test_gwf_maw09.py | 62 +- autotest/test_gwf_maw10.py | 77 +- autotest/test_gwf_maw_cases.py | 817 +++++++++++++++ autotest/test_gwf_maw_obs.py | 65 +- autotest/test_gwf_multimvr.py | 93 +- autotest/test_gwf_mvr01.py | 63 +- autotest/test_gwf_newton01.py | 64 +- autotest/test_gwf_noptc01.py | 82 +- autotest/test_gwf_npf01_75x75.py | 56 +- autotest/test_gwf_npf02_rewet.py | 67 +- autotest/test_gwf_npf03_sfr.py | 83 +- autotest/test_gwf_npf04_spdis.py | 69 +- autotest/test_gwf_npf05_anisotropy.py | 70 +- autotest/test_gwf_npf_tvk01.py | 82 +- autotest/test_gwf_npf_tvk02.py | 79 +- autotest/test_gwf_npf_tvk03.py | 79 +- autotest/test_gwf_npf_tvk04.py | 81 +- autotest/test_gwf_npf_tvk05.py | 81 +- autotest/test_gwf_obs01.py | 76 +- autotest/test_gwf_obs02.py | 80 +- autotest/test_gwf_ptc01.py | 69 +- autotest/test_gwf_rch01.py | 71 +- autotest/test_gwf_rch02.py | 71 +- autotest/test_gwf_rch03.py | 71 +- autotest/test_gwf_returncodes.py | 131 +-- autotest/test_gwf_sfr_badfactor.py | 71 +- autotest/test_gwf_sfr_evap.py | 63 +- autotest/test_gwf_sfr_npoint01.py | 83 +- autotest/test_gwf_sfr_npoint02.py | 76 +- autotest/test_gwf_sfr_npoint03.py | 85 +- autotest/test_gwf_sfr_reorder.py | 89 +- autotest/test_gwf_sto01.py | 96 +- autotest/test_gwf_sto02.py | 76 +- autotest/test_gwf_sto03.py | 96 +- autotest/test_gwf_sto_tvs01.py | 79 +- autotest/test_gwf_ts_lak01.py | 86 +- autotest/test_gwf_ts_maw01.py | 75 +- autotest/test_gwf_ts_sfr01.py | 74 +- autotest/test_gwf_ts_sfr02.py | 74 +- autotest/test_gwf_ts_uzf01.py | 74 +- autotest/test_gwf_utl01_binaryinput.py | 77 +- autotest/test_gwf_utl02_timeseries.py | 62 +- autotest/test_gwf_utl03_obs01.py | 82 +- autotest/test_gwf_utl04_auxmult.py | 60 +- autotest/test_gwf_utl05_budparse.py | 72 +- autotest/test_gwf_utl06_tas.py | 65 +- autotest/test_gwf_uzf01.py | 77 +- autotest/test_gwf_uzf02.py | 83 +- autotest/test_gwf_uzf03.py | 84 +- autotest/test_gwf_uzf04.py | 78 +- autotest/test_gwf_uzf05.py | 81 +- autotest/test_gwf_uzf_gwet.py | 73 +- autotest/test_gwf_uzf_surfdep.py | 64 +- autotest/test_gwf_uzf_wc_output.py | 76 +- autotest/test_gwf_vsc01.py | 67 +- autotest/test_gwf_vsc02.py | 69 +- autotest/test_gwf_vsc03_sfr.py | 60 +- autotest/test_gwf_vsc04_lak.py | 64 +- autotest/test_gwf_vsc05_hfb.py | 65 +- autotest/test_gwf_wel01.py | 71 +- autotest/test_gwf_zb01.py | 114 +- autotest/test_gwfgwf_lgr.py | 65 +- autotest/test_gwt_adv01.py | 67 +- autotest/test_gwt_adv01_fmi.py | 70 +- autotest/test_gwt_adv01_gwtgwt.py | 69 +- autotest/test_gwt_adv02.py | 69 +- autotest/test_gwt_adv03.py | 69 +- autotest/test_gwt_adv04.py | 77 +- autotest/test_gwt_buy_solute_heat.py | 74 +- autotest/test_gwt_disu01.py | 94 +- autotest/test_gwt_dsp01.py | 75 +- autotest/test_gwt_dsp01_fmi.py | 77 +- autotest/test_gwt_dsp01_gwtgwt.py | 69 +- autotest/test_gwt_dsp01_noadv.py | 75 +- autotest/test_gwt_dsp02.py | 69 +- autotest/test_gwt_dsp03.py | 70 +- autotest/test_gwt_dsp04.py | 77 +- autotest/test_gwt_dsp05_noadv.py | 74 +- autotest/test_gwt_fmi01.py | 93 +- autotest/test_gwt_fmi02.py | 66 +- autotest/test_gwt_henry.py | 68 +- autotest/test_gwt_henry_nr.py | 77 +- autotest/test_gwt_henry_openclose.py | 68 +- autotest/test_gwt_ims_issue655.py | 70 +- autotest/test_gwt_ist01.py | 96 +- autotest/test_gwt_lkt01.py | 75 +- autotest/test_gwt_lkt02.py | 65 +- autotest/test_gwt_lkt03.py | 65 +- autotest/test_gwt_lkt04.py | 77 +- autotest/test_gwt_moc3d01.py | 77 +- autotest/test_gwt_moc3d01_zod.py | 81 +- autotest/test_gwt_moc3d02.py | 76 +- autotest/test_gwt_moc3d03.py | 78 +- autotest/test_gwt_mst01.py | 86 +- autotest/test_gwt_mst02.py | 75 +- autotest/test_gwt_mst03.py | 92 +- autotest/test_gwt_mst04_noadv.py | 89 +- autotest/test_gwt_mst05.py | 70 +- autotest/test_gwt_mst06_noadv.py | 89 +- autotest/test_gwt_mt3dms_p01.py | 150 ++- autotest/test_gwt_mvt01.py | 65 +- autotest/test_gwt_mvt02.py | 65 +- autotest/test_gwt_mvt02fmi.py | 68 +- autotest/test_gwt_mwt01.py | 70 +- autotest/test_gwt_mwt02.py | 67 +- autotest/test_gwt_obs01.py | 82 +- autotest/test_gwt_prudic2004t2.py | 104 +- autotest/test_gwt_prudic2004t2fmi.py | 93 +- autotest/test_gwt_prudic2004t2fmiats.py | 94 +- autotest/test_gwt_prudic2004t2gwtgwt.py | 97 +- autotest/test_gwt_sft01.py | 65 +- autotest/test_gwt_sft01gwtgwt.py | 67 +- autotest/test_gwt_src01.py | 86 +- autotest/test_gwt_ssm01fmi.py | 67 +- autotest/test_gwt_ssm02.py | 91 +- autotest/test_gwt_ssm03.py | 87 +- autotest/test_gwt_ssm04.py | 87 +- autotest/test_gwt_ssm05.py | 87 +- autotest/test_gwt_ssm06.py | 56 +- autotest/test_gwt_ssm06fmi.py | 65 +- autotest/test_gwt_uzt01.py | 107 +- autotest/test_gwtgwt_oldexg.py | 67 +- autotest/test_mf6_tmp_simulations.py | 53 +- autotest/test_z01_testmodels_mf6.py | 6 +- autotest/test_z02_testmodels_mf5to6.py | 6 +- autotest/test_z03_examples.py | 4 +- autotest/test_z03_largetestmodels.py | 4 +- autotest/update_flopy.py | 103 +- environment.yml | 1 + 211 files changed, 5585 insertions(+), 14195 deletions(-) delete mode 100644 autotest/binary_file_writer.py delete mode 100644 autotest/budget_file_compare.py rename autotest/{scripts => }/cross_section_functions.py (100%) delete mode 100644 autotest/disu_util.py delete mode 100644 autotest/targets.py create mode 100644 autotest/test_gwf.py rename autotest/{test_gwf_disu01.py => test_gwf_disu.py} (61%) delete mode 100644 autotest/test_gwf_maw01.py delete mode 100644 autotest/test_gwf_maw02.py delete mode 100644 autotest/test_gwf_maw03.py create mode 100644 autotest/test_gwf_maw_cases.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 937a35e554a..e49f7c8edb7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -71,11 +71,66 @@ jobs: - name: Meson test run: meson test --verbose --no-rebuild -C builddir + smoke_test: + name: Smoke test (gfortran 12) + runs-on: ubuntu-22.04 + defaults: + run: + shell: bash -l {0} + env: + FC: gfortran + GCC_V: 12 + steps: + - name: Checkout modflow6 + uses: actions/checkout@v3 + with: + path: modflow6 + + - name: Setup GNU Fortran ${{ env.GCC_V }} + uses: awvwgk/setup-fortran@main + with: + compiler: gcc + version: ${{ env.GCC_V }} + + - name: Setup Micromamba + uses: mamba-org/provision-with-micromamba@main + with: + environment-file: modflow6/environment.yml + cache-downloads: true + cache-env: true + + - name: Build modflow6 + working-directory: modflow6 + run: | + meson setup builddir -Ddebug=false --prefix=$(pwd) --libdir=bin + meson install -C builddir + meson test --verbose --no-rebuild -C builddir + + - name: Update flopy + working-directory: modflow6/autotest + run: python update_flopy.py + + - name: Get executables + working-directory: modflow6/autotest + env: + GITHUB_TOKEN: ${{ github.token }} + run: pytest -v --durations 0 get_exes.py + + - name: Test programs + working-directory: modflow6/autotest + run: | + if [ "${{ github.ref_name }}" == "master" ]; then + pytest -v -n auto --durations 0 -m "not slow and not regression and not developmode" + else + pytest -v -n auto --durations 0 -S + fi + test_gfortran_latest: name: Test (gfortran 12) needs: - lint - build + - smoke_test runs-on: ${{ matrix.os }} strategy: fail-fast: false @@ -131,6 +186,8 @@ jobs: - name: Test programs working-directory: modflow6/autotest + env: + REPOS_PATH: ${{ github.workspace }} run: | if [ "${{ github.ref_name }}" == "master" ]; then pytest -v -n auto --durations 0 -m "not large and not developmode" @@ -149,6 +206,7 @@ jobs: needs: - lint - build + - smoke_test runs-on: ${{ matrix.os }} strategy: fail-fast: false @@ -205,6 +263,8 @@ jobs: - name: Test modflow6 working-directory: modflow6/autotest + env: + REPOS_PATH: ${{ github.workspace }} run: | if [ "${{ github.ref_name }}" == "master" ]; then pytest -v -n auto --durations 0 -m "not large and not developmode" @@ -217,6 +277,7 @@ jobs: needs: - lint - build + - smoke_test runs-on: ${{ matrix.os }} strategy: fail-fast: false @@ -299,6 +360,8 @@ jobs: - name: Test programs if: runner.os != 'Windows' working-directory: modflow6/autotest + env: + REPOS_PATH: ${{ github.workspace }} run: | if [ "${{ github.ref_name }}" == "master" ]; then pytest -v -n auto --durations 0 -m "not large and not developmode" @@ -310,6 +373,8 @@ jobs: if: runner.os == 'Windows' working-directory: modflow6/autotest shell: pwsh + env: + REPOS_PATH: ${{ github.workspace }} run: | if ( "${{ github.ref_name }}" -eq "master" ) { pytest -v -n auto --durations 0 -m "not large and not developmode" diff --git a/DEVELOPER.md b/DEVELOPER.md index 78272df0b10..39207e4c0f5 100644 --- a/DEVELOPER.md +++ b/DEVELOPER.md @@ -1,64 +1,84 @@ # Developing MODFLOW 6 -This document describes how to set up your development environment to build and test MODFLOW 6. -It also explains the basic mechanics of using `git`. Details on how to contribute your code to the repository are found in the separate document [CONTRIBUTING.md](CONTRIBUTING.md). +This document describes how to set up a development environment to modify, build and test MODFLOW 6. Details on how to contribute your code to the repository are found in the separate document [CONTRIBUTING.md](CONTRIBUTING.md). -- [Requirements](#requirements) +- [Prerequisites](#prerequisites) - [Git](#git) - - [gfortran (version 4.9 to 10)](#gfortran-version-49-to-10) - - [Linux](#linux) - - [macOS](#macos) - - [Windows](#windows) + - [Fortran compiler](#fortran-compiler) + - [GNU Fortran](#gnu-fortran) + - [Linux](#linux) + - [macOS](#macos) + - [Windows](#windows) + - [Intel Fortran](#intel-fortran) + - [Windows](#windows-1) - [Python](#python) - - [ifort (optional)](#ifort-optional) - - [Windows](#windows-1) - - [Doxygen & LaTeX (optional)](#doxygen--latex-optional) - - [fprettify](#fprettify) + - [Dependencies](#dependencies) + - [`meson`](#meson) + - [`fprettify`](#fprettify) + - [`mfpymake`](#mfpymake) + - [`flopy`](#flopy) + - [`modflow-devtools`](#modflow-devtools) + - [Optional tools](#optional-tools) + - [GNU Make](#gnu-make) + - [Visual Studio](#visual-studio) + - [Doxygen & LaTeX](#doxygen--latex) - [Installation](#installation) - [Building](#building) - - [Meson](#meson) - - [Visual Studio](#visual-studio) - - [Pymake](#pymake) - - [Make](#make) - [Testing](#testing) - - [External model repos](#external-model-repos) + - [Configuring a test environment](#configuring-a-test-environment) + - [Building development binaries](#building-development-binaries) + - [Rebuilding and installing release binaries](#rebuilding-and-installing-release-binaries) + - [Updating `flopy` plugins](#updating-flopy-plugins) + - [External model repositories](#external-model-repositories) - [Installing external repos](#installing-external-repos) - [Test models](#test-models) - [Example models](#example-models) - - [Running external model tests](#running-external-model-tests) + - [Running Tests](#running-tests) + - [Selecting tests with markers](#selecting-tests-with-markers) + - [External model tests](#external-model-tests) + - [Writing tests](#writing-tests) -## Requirements +## Prerequisites Before you can build and test MODFLOW 6, you must install and configure the -following products on your development machine. +following on your development machine: + +- git +- Python3.8+ +- a modern Fortran compiler + +Some additional, optional tools are also discussed below. ### Git [Git](https://git-scm.com) and/or the **GitHub app** (for [Mac](https://mac.github.com) or [Windows](https://windows.github.com)). [GitHub's Guide to Installing Git](https://help.github.com/articles/set-up-git) is a good source of information. +### Fortran compiler + +The GNU Fortran compiler `gfortran` or the Intel Fortran compiler `ifort` can be used to compile MODFLOW 6. -### gfortran (version 4.9 to 10) +#### GNU Fortran -gfortran can be used to compile MODFLOW 6 and associated utilities and generate distributable files. +GNU Fortran can be installed on all three major platforms. -#### Linux +##### Linux - fedora-based: `dnf install gcc-gfortran` - debian-based: `apt install gfortran` -#### macOS +##### macOS - [Homebrew](https://brew.sh/): `brew install gcc` - [MacPorts](https://www.macports.org/): `sudo port install gcc10` -#### Windows +##### Windows - Download the Minimalist GNU for Windows (MinGW) installer from Source Forge: https://sourceforge.net/projects/mingw-w64/files/Toolchains%20targetting%20Win32/Personal%20Builds/mingw-builds/installer/mingw-w64-install.exe @@ -70,45 +90,95 @@ gfortran can be used to compile MODFLOW 6 and associated utilities and generate `Path` variable in the User Variables (the top table). Click the `New` button and enter the location of the `mingw64/bin` directory. +#### Intel Fortran + +Intel Fortran can also be used to compile MODFLOW 6 and associated utilities. The `ifort` compiler is available in the [Intel oneAPI HPC Toolkit](https://software.intel.com/content/www/us/en/develop/tools/oneapi/hpc-toolkit/download.html). An installer is bundled with the download. A minimal + +A number of environment variables must be set before using Intel Fortran. General information can be found [here](https://www.intel.com/content/www/us/en/develop/documentation/oneapi-programming-guide/top/oneapi-development-environment-setup.html), with specific instructions to configure a shell session for `ifort` [here](https://www.intel.com/content/www/us/en/develop/documentation/fortran-compiler-oneapi-dev-guide-and-reference/top/compiler-setup/use-the-command-line/specifying-the-location-of-compiler-components.html). + +##### Windows + +On Windows, [Visual Studio](https://visualstudio.microsoft.com) and a number of libraries must be installed for `ifort` to work. The required libraries can be installed by ticking the "Desktop Development with C++" checkbox in the Visual Studio Installer's Workloads tab. + +**Note:** Invoking the `setvars.bat` scripts from a Powershell session will *not* put `ifort` on the path, since [batch script environments are local to their process](https://stackoverflow.com/a/49028002/6514033). Either invoke `ifort` from command prompt or relaunch PowerShell, e.g. + +``` +cmd.exe "/K" '"C:\Program Files (x86)\Intel\oneAPI\setvars-vcvarsall.bat" && "C:\Program Files (x86)\Intel\oneAPI\compiler\latest\env\vars.bat" && powershell' +``` + ### Python -Install Python, for example via [miniconda](https://docs.conda.io/en/latest/miniconda.html) or [Anaconda](https://www.anaconda.com/products/individual). -Then create a new environment by executing the following at the root of this repository +Python 3.8+ is required to run MODFLOW 6 tests. A Conda distribution (e.g. [miniconda](https://docs.conda.io/en/latest/miniconda.html) or [Anaconda](https://www.anaconda.com/products/individual) is recommended. Python dependencies are specified in `environment.yml`. To create an environment, run from the project root: + ``` -conda env create --force -f environment.yml +conda env create -f environment.yml ``` -### ifort (optional) +To update an existing environment: -Intel fortran can be used to compile MODFLOW 6 and associated utilities and generate distributable files (if not using gfortran). -Download the Intel oneAPI HPC Toolkit: https://software.intel.com/content/www/us/en/develop/tools/oneapi/hpc-toolkit/download.html +```shell +conda env update -f environment.yml +``` -Documentation describing how to set the intel environment variables can be found [here](https://www.intel.com/content/www/us/en/develop/documentation/oneapi-programming-guide/top/oneapi-development-environment-setup.html). +#### Dependencies -#### Windows +This project depends critically on a few Python packages for building, linting and testing tasks: -- Visual Studio with the appropriate redistributable libraries must be installed for ifort to compile on Windows. -- Install Visual Studio, which can be found [here](https://visualstudio.microsoft.com/). Note: the latest version of Visual Studio, 2022, requires a sufficiently new version of the Intel OneAPI as well. -- The redistributable libraries can installed via ticking the "Desktop Development with C++" checkbox in the Visual Studio Installer in the Workloads tab. +- `meson` +- `fprettify` +- `pymake` +- `flopy` -### Doxygen & LaTeX (optional) +These are each described briefly below. The Conda `environment.yml` contains a number of other dependencies also required for various development tasks, but they are not described in detail here. -[Doxygen](https://www.doxygen.nl/index.html) is used to generate the [MODFLOW 6 source code documentation](https://modflow-usgs.github.io/modflow6/). [Graphviz](https://graphviz.org/) is used by doxygen to produce source code diagrams. [LaTeX](https://www.latex-project.org/) is used to generate the MODFLOW 6 release notes and Input/Output documents (docs/mf6io/mf6io.nightlybuild). -These programs can be installed from various sources, including by conda, macports, or from individual sources such as https://www.tug.org/. Details about USGS LaTeX libraries can be seen in addition to linux installs in the CI workflow for the docs (`.github/workflows/ci-docs.yml`). +##### `meson` + +[Meson](https://mesonbuild.com/index.html) is recommended for building MODFLOW 6 and is included in `environment.yml`. It can also be [installed independently](https://mesonbuild.com/Getting-meson.html) — note that if you do so you will need to manually add the executable to the [PATH](https://en.wikipedia.org/wiki/PATH_(variable)). + +##### `fprettify` + +[`fprettify`](https://github.com/pseewald/fprettify) can be used to format Fortran source code and in combination with the [MODFLOW 6 fprettify configuration](https://github.com/MODFLOW-USGS/modflow6/blob/develop/distribution/.fprettify.yaml) establishes a contribution standard for properly formatted MODFLOW 6 Fortran source. This tool can be installed with `pip` or `conda` and used from the command line or integrated with a [VSCode](https://github.com/MODFLOW-USGS/modflow6/blob/develop/.vscode/README.md) or Visual Studio development environment. The `fprettify` package is included in the Conda environment in `environment.yml`. See [contribution guidelines](https://github.com/MODFLOW-USGS/modflow6/blob/develop/CONTRIBUTING.md) for additional information. + +##### `mfpymake` + +The `mfpymake` package can build MODFLOW 6 and related programs and artifacts (e.g. makefiles), and is used in particular by the `distribution/build_makefiles.py` script. `mfpymake` is included in the Conda environment in `environment.yml`. To install separately, follow the instructions as explained on the README of the [repository](https://github.com/modflowpy/pymake). The README also demonstrates basic usage. -### fprettify +##### `flopy` -[fprettify](https://github.com/pseewald/fprettify) can be used to format Fortran source code and in combination with the [MODFLOW 6 fprettify configuration](https://github.com/MODFLOW-USGS/modflow6/blob/develop/distribution/.fprettify.yaml) establishes a contribution standard for properly formatted MODFLOW 6 Fortran source. This tool can be installed with `pip` or `conda` and used from the command line or integrated with a [VSCode](https://github.com/MODFLOW-USGS/modflow6/blob/develop/.vscode/README.md) or Visual Studio development environment. See [contribution guidelines](https://github.com/MODFLOW-USGS/modflow6/blob/develop/CONTRIBUTING.md) for additional information. +[`flopy`](https://github.com/modflowpy/flopy) is used throughout MODFLOW 6 tests to create, run and post-process models. + +Like MODFLOW 6, `flopy` is modular — for each MODFLOW 6 package there is generally a corresponding `flopy` plugin. Plugins are generated dynamically from DFN files stored in this repository under `doc/mf6io/mf6ivar/dfn`. + +##### `modflow-devtools` + +The tests use a set of shared fixtures and utilities provided by the [`modflow-devtools`](https://github/com/MODFLOW-USGS/modflow-devtools) package. This package is included in the Conda environment in `environment.yml`. + +### Optional tools + +Some other tools are useful but not required to develop MODFLOW 6. + +#### GNU Make + +This repository provides makefiles, generated by `mfpymake`, which can be used to build MODFLOW 6 with [GNU Make](https://www.gnu.org/software/make/). For further instructions we refer to the [GNU Make Manual](https://www.gnu.org/software/make/manual/). + +#### Visual Studio + +Visual Studio installers can be downloaded from the [official website](https://visualstudio.microsoft.com/). MODFLOW 6 solution files can be found in the `msvs` folder. + +#### Doxygen & LaTeX + +[Doxygen](https://www.doxygen.nl/index.html) is used to generate the [MODFLOW 6 source code documentation](https://modflow-usgs.github.io/modflow6/). [Graphviz](https://graphviz.org/) is used by doxygen to produce source code diagrams. [LaTeX](https://www.latex-project.org/) is used to generate the MODFLOW 6 release notes and Input/Output documents (docs/mf6io/mf6io.nightlybuild). + +These programs can be installed from various sources, including by conda, macports, or from individual sources such as https://www.tug.org/. Details about USGS LaTeX libraries can be seen in addition to linux installs in the CI workflow for the docs (`.github/workflows/ci-docs.yml`). ## Installation Fork and clone the MODFLOW 6 repository: -1. Login to your GitHub account or create one by following the instructions given - [here](https://github.com/signup/free). +1. Login to your GitHub account or create one by following the instructions given [here](https://github.com/signup/free). 2. [Fork](http://help.github.com/forking) the [main MODFLOW 6](https://github.com/MODFLOW-USGS/modflow6). -3. Clone your fork of the MODFLOW 6 repository and define an `upstream` remote pointing back to the MODFLOW 6 repository that you forked in the first place. +3. Clone your fork of the MODFLOW 6 repository and create an `upstream` remote pointing back to your fork. ```shell # Clone your GitHub repository: @@ -123,15 +193,15 @@ git remote add upstream https://github.com/MODFLOW-USGS/modflow6.git ## Building -### Meson +Meson is the recommended build tool for MODFLOW 6. [Meson](https://mesonbuild.com/Getting-meson.html) must be installed and on your [PATH](https://en.wikipedia.org/wiki/PATH_(variable)). Creating and activating the Conda environment `environment.yml` should be sufficient for this. -First, install [Meson](https://mesonbuild.com/Getting-meson.html) and assure it is in your [PATH](https://en.wikipedia.org/wiki/PATH_(variable)). -When using Visual Studio Code, you can use tasks as described [here](.vscode/README.md). -For the more general instructions, continue to read this section. +Meson build configuration files are provided for MODFLOW 6 as well as `zbud6` and `mf5to6` utility programs: -First configure the build directory. -Per default it uses the compiler flags for a release build. -If you want to create a debug build, add `-Doptimization=0` to the following `setup` command. +- `meson.build` +- `utils/zonebudget/meson.build` +- `utils/mf5to6/meson.build` + +To build MODFLOW 6, first configure the build directory. By default Meson uses compiler flags for a release build. To create a debug build, add `-Doptimization=0` to the following `setup` command. ```shell # bash (linux and macOS) @@ -153,71 +223,82 @@ In order to run the tests the binaries have to be installed: meson install -C builddir ``` -The binaries can then be found in the `bin` folder. -`meson install` also triggers a compilation if necessary. -Therefore, executing `meson install` is enough to get up-to-date binaries in the `bin` folder. +The binaries can then be found in the `bin` folder. `meson install` also triggers a compilation if necessary, so executing `meson install` is enough to get up-to-date binaries in the `bin` folder. + +**Note:** If using Visual Studio Code, you can use tasks as described [here](.vscode/README.md) to automate the above. -### Visual Studio +## Testing -As of October 2021, debugging with Visual Studio tends to be more convenient than with other solutions. -First, download Visual Studio from the [official website](https://visualstudio.microsoft.com/). -The solution files can be found in the `msvs` folder. +MODFLOW 6 tests are driven with [`pytest`](https://docs.pytest.org/en/7.1.x/), with the help of plugins like `pytest-xdist` and `pytest-cases`. Testing dependencies are included in the Conda environment `environment.yml`. -### Pymake +**Note:** the entire test suite should pass before a pull request is submitted. Tests run in GitHub Actions CI and a PR can only be merged with passing tests. See [`CONTRIBUTING.md`](CONTRIBUTING.md) for more information. -Follow the installation instructions as explained on the README of the [repository](https://github.com/modflowpy/pymake). -The README also explains how to build MODFLOW 6 with it. +### Configuring a test environment -### Make +A few tasks must be completed before running tests: -We also provide make files which can be used to build MODFLOW 6 with [GNU Make](https://www.gnu.org/software/make/). -For the build instructions we refer to the [GNU Make Manual](https://www.gnu.org/software/make/manual/). +- build local MODFLOW 6 development version +- rebuild the last MODFLOW 6 release +- install additional executables +- update FloPy plugins +- clone MODFLOW 6 test model and example repositories -## Testing +Tests expect binaries to live in the `bin` directory relative to the project root, as configured above in the `meson` commands. Binaries are organized as follows: -Tests should pass locally before a PR is opened on Github. All the tests are executed by the CI system and a pull request can only be merged with passing tests. +- local development binaries in the top-level `bin` folder +- executables rebuilt in development mode from the latest release in `bin/rebuilt` +- related programs installed from the [executables distribution](https://github.com/MODFLOW-USGS/executables/releases) live in `bin/downloaded` Tests must be run from the `autotest` folder. -```shell -cd autotest -``` +#### Building development binaries -FloPy plugins must first be updated: +Before running tests, the local development version of MODFLOW 6 must be built with `meson` as described above. The `autotest/build_exes.py` script is provided as a shortcut to easily rebuild local binaries. The script can be run from the project root with: ```shell -python update_flopy.py +python autotest/build_exes.py ``` -The tests require other MODFLOW-related binary executables, distributed from https://github.com/MODFLOW-USGS/executables. -Testing also requires a binary executable of the last MODFLOW 6 officially released version, compiled in develop mode with the currently configured compiler. To download MODFLOW-related binaries and to rebuild the last official MODFLOW 6 release, execute: +Alternatively, it can be run from the `autotest` directory with `pytest`: ```shell -pytest -v get_exes.py +pytest build_exes.py ``` -Unless you built and installed MODFLOW 6 binaries with meson, you will also have to execute the following command to build the binaries: +By default, binaries will be placed in the `bin` directory relative to the project root, as in the `meson` commands described above. To change the location of the binaries, use the `--path` option. + +#### Rebuilding and installing release binaries + +Tests require the latest official MODFLOW 6 release to be compiled in develop mode with the same Fortran compiler as the development version. A number of binaries distributed from the [executables repo](https://github.com/MODFLOW-USGS/executables) must also be installed. The script `autotest/get_exes.py` does both of these things. It can be run from the project root with: ```shell -pytest -v build_exes.py +python autotest/get_exes.py ``` -Then the tests can be run with `pytest` as usual, for instance: +Alternatively, with `pytest` from the `autotest` directory: ```shell -# Run all tests with verbose output -pytest -v +pytest get_exes.py ``` -Tests can be run in parallel with the `-n` option, which accepts an integer argument for the number of processes to use. If the value `auto` is provided, `pytest-xdist` will use as many processes as your machine has available CPUs: +By default, binaries will be placed in the `bin` directory relative to the project root, as in the `meson` commands described above. Nested `bin/downloaded` and `bin/rebuilt` directories are created to contain the rebuilt last release and the downloaded executables, respectively. To change the location of the binaries, use the `--path` option. + +#### Updating `flopy` plugins + +Plugins should be regenerated from DFN files before running tests for the first time or after definition files change. This can be done with the `autotest/update_flopy.py` script, which wipes and regenerates plugin classes for the `flopy` installed in the Python environment. + +**Note:** if you've installed a local version of `flopy` from source, running this script can overwrite files in your repository. + +There is a single optional argument, the path to the folder containing definition files. By default DFN files are assumed to live in `doc/mf6io/mf6ivar/dfn`, making the following identical: ```shell -pytest -v -n auto +python autotest/update_flopy.py +python autotest/update_flopy.py doc/mf6io/mf6ivar/dfn ``` -### External model repos +#### External model repositories -While many tests create models programmatically, the full suite tests MODFLOW 6 against example models stored in the following external repositories: +Some autotests load example models from external repositories: - [`MODFLOW-USGS/modflow6-testmodels`](https://github.com/MODFLOW-USGS/modflow6-testmodels) - [`MODFLOW-USGS/modflow6-largetestmodels`](https://github.com/MODFLOW-USGS/modflow6-largetestmodels) @@ -261,20 +342,84 @@ python ci_build_files.py This will build the examples for subsequent use by the tests. -#### Running external model tests +### Running Tests + +Tests are driven by `pytest` and must be run from the `autotest` folder. To run tests in a particular file, showing verbose output, use: + +```shell +pytest -v +``` + +Tests can be run in parallel with the `-n` option, which accepts an integer argument for the number of parallel processes. If the value `auto` is provided, `pytest-xdist` will use one worker per available processor. + +```shell +pytest -v -n auto +``` + +#### Selecting tests with markers + +Markers can be used to select subsets of tests. Markers provided in `pytest.ini` include: + +- `slow`: tests that take longer than a few seconds to complete +- `repo`: tests that require external model repositories +- `large`: tests using large models (from the `modflow6-examples` and `modflow6-largetestmodels` repos) +- `regression`: tests comparing results from multiple versions + +Markers can be used with the `-m ` option, and can be applied in boolean combinations with `and`, `or` and `not`. For instance, to run fast tests in parallel, excluding regression tests: + +```shell +pytest -v -n auto -m "not slow and not regression" +``` + +The `--smoke` (short `-S`) flag, provided by `modflow-devtools` is an alias for the above: + +```shell +pytest -v -n auto -S +``` + +[Smoke testing](https://modflow-devtools.readthedocs.io/en/latest/md/markers.html#smoke-testing) is a form of integration testing which aims to test a decent fraction of the codebase quickly enough to run often during development. + +#### External model tests + +Tests using models from external repositories can be selected with the `repo` marker: + +```shell +pytest -v -n auto -m "repo" +``` -External model tests are located in their own files: +The `large` marker is a subset of the `repo` marker. To test models excluded from commit-triggered CI and only run on GitHub Actions nightly: ```shell -# Run MODFLOW 6 test models +pytest -v -n auto -m "large" +``` + +Test scripts for external model repositories can also be run independently: + +```shell +# MODFLOW 6 test models pytest -v -n auto test_z01_testmodels_mf6.py -# Run MODFLOW 5 to 6 conversion test models +# MODFLOW 5 to 6 conversion test models pytest -v -n auto test_z02_testmodels_mf5to6.py -# Run example models +# models from modflow6-examples repo pytest -v -n auto test_z03_examples.py -# Run large test models +# models from modflow6-largetestmodels repo pytest -v -n auto test_z03_largetestmodels.py ``` + +Tests load external models from fixtures provided by `modflow-devtools`. External model tests can be selected by model or simulation name, or by packages used. See the [`modflow-devtools` documentation](https://modflow-devtools.readthedocs.io/en/latest/md/fixtures.html#filtering) for usage examples. Note that filtering options only apply to tests using external models, and will not filter tests defining models in code — for that, the `pytest` built-in `-k` option may be used. + +#### Writing tests + +Tests should ideally follow a few conventions for easier maintenance: + +- Use temporary directory fixtures. Tests which write to disk should use `pytest`'s built-in `tmp_path` fixtures or one of the [keepable temporary directory fixtures from `modflow-devtools`](https://modflow-devtools.readthedocs.io/en/latest/md/fixtures.html#keepable-temporary-directories). This prevents tests from polluting one another's state. + +- Use markers for convenient (de-)selection: + - `@pytest.mark.slow` if the test doesn't complete in a few seconds (this preserves the ability to quickly [`--smoke` test](https://modflow-devtools.readthedocs.io/en/latest/md/markers.html#smoke-testing) + - `@pytest.mark.repo` if the test relies on external model repositories + - `@pytest.mark.regression` if the test compares results from different versions + +The test suite must pass before code can be merged, so be sure it passes locally before opening a PR. diff --git a/README.md b/README.md index f4e8f192285..4da440d2e02 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,7 @@ This repository contains an `./autotest` folder with python scripts for building * [modflowpy/pymake](https://github.com/modflowpy/pymake) * [modflowpy/flopy](https://github.com/modflowpy/flopy) +* [MODFLOW-USGS/modflow6-examples](https://github.com/MODFLOW-USGS/modflow6-examples) * [MODFLOW-USGS/modflow6-testmodels](https://github.com/MODFLOW-USGS/modflow6-testmodels) * [MODFLOW-USGS/modflow6-largetestmodels](https://github.com/MODFLOW-USGS/modflow6-largetestmodels) * [MODFLOW-USGS/executables](https://github.com/MODFLOW-USGS/executables) diff --git a/autotest/binary_file_writer.py b/autotest/binary_file_writer.py deleted file mode 100644 index 5d8771a0424..00000000000 --- a/autotest/binary_file_writer.py +++ /dev/null @@ -1,202 +0,0 @@ -import numpy as np - - -def write_head( - fbin, - data, - kstp=1, - kper=1, - pertim=1.0, - totim=1.0, - text=" HEAD", - ilay=1, -): - dt = np.dtype( - [ - ("kstp", np.int32), - ("kper", np.int32), - ("pertim", np.float64), - ("totim", np.float64), - ("text", "S16"), - ("ncol", np.int32), - ("nrow", np.int32), - ("ilay", np.int32), - ] - ) - nrow = data.shape[0] - ncol = data.shape[1] - h = np.array((kstp, kper, pertim, totim, text, ncol, nrow, ilay), dtype=dt) - h.tofile(fbin) - data.tofile(fbin) - return - - -def write_budget( - fbin, - data, - kstp=1, - kper=1, - text=" FLOW-JA-FACE", - imeth=1, - delt=1.0, - pertim=1.0, - totim=1.0, - text1id1=" GWF-1", - text2id1=" GWF-1", - text1id2=" GWF-1", - text2id2=" NPF", -): - dt = np.dtype( - [ - ("kstp", np.int32), - ("kper", np.int32), - ("text", "S16"), - ("ndim1", np.int32), - ("ndim2", np.int32), - ("ndim3", np.int32), - ("imeth", np.int32), - ("delt", np.float64), - ("pertim", np.float64), - ("totim", np.float64), - ] - ) - - if imeth == 1: - ndim1 = data.shape[0] - ndim2 = 1 - ndim3 = -1 - h = np.array( - ( - kstp, - kper, - text, - ndim1, - ndim2, - ndim3, - imeth, - delt, - pertim, - totim, - ), - dtype=dt, - ) - h.tofile(fbin) - data.tofile(fbin) - - elif imeth == 6: - ndim1 = 1 - ndim2 = 1 - ndim3 = -1 - h = np.array( - ( - kstp, - kper, - text, - ndim1, - ndim2, - ndim3, - imeth, - delt, - pertim, - totim, - ), - dtype=dt, - ) - h.tofile(fbin) - - # write text1id1, ... - dt = np.dtype( - [ - ("text1id1", "S16"), - ("text1id2", "S16"), - ("text2id1", "S16"), - ("text2id2", "S16"), - ] - ) - h = np.array((text1id1, text1id2, text2id1, text2id2), dtype=dt) - h.tofile(fbin) - - # write ndat (number of floating point columns) - colnames = data.dtype.names - ndat = len(colnames) - 2 - dt = np.dtype([("ndat", np.int32)]) - h = np.array([(ndat,)], dtype=dt) - h.tofile(fbin) - - # write auxiliary column names - naux = ndat - 1 - if naux > 0: - auxtxt = [f"{colname:16}" for colname in colnames[3:]] - auxtxt = tuple(auxtxt) - dt = np.dtype([(colname, "S16") for colname in colnames[3:]]) - h = np.array(auxtxt, dtype=dt) - h.tofile(fbin) - - # write nlist - nlist = data.shape[0] - dt = np.dtype([("nlist", np.int32)]) - h = np.array([(nlist,)], dtype=dt) - h.tofile(fbin) - - # write the data - data.tofile(fbin) - - pass - else: - raise Exception(f"unknown method code {imeth}") - return - - -def uniform_flow_field(qx, qy, qz, shape, delr=None, delc=None, delv=None): - - nlay, nrow, ncol = shape - - # create spdis array for the uniform flow field - dt = np.dtype( - [ - ("ID1", np.int32), - ("ID2", np.int32), - ("FLOW", np.float64), - ("QX", np.float64), - ("QY", np.float64), - ("QZ", np.float64), - ] - ) - spdis = np.array( - [(id1, id1, 0.0, qx, qy, qz) for id1 in range(nlay * nrow * ncol)], - dtype=dt, - ) - - # create the flowja array for the uniform flow field (assume top-bot = 1) - flowja = [] - if delr is None: - delr = 1.0 - if delc is None: - delc = 1.0 - if delv is None: - delv = 1.0 - for k in range(nlay): - for i in range(nrow): - for j in range(ncol): - # diagonal - flowja.append(0.0) - # up - if k > 0: - flowja.append(-qz * delr * delc) - # back - if i > 0: - flowja.append(-qy * delr * delv) - # left - if j > 0: - flowja.append(qx * delc * delv) - # right - if j < ncol - 1: - flowja.append(-qx * delc * delv) - # front - if i < nrow - 1: - flowja.append(qy * delr * delv) - # bottom - if k < nlay - 1: - flowja.append(qz * delr * delc) - flowja = np.array(flowja, dtype=np.float64) - return spdis, flowja diff --git a/autotest/budget_file_compare.py b/autotest/budget_file_compare.py deleted file mode 100644 index cd69f2ccdac..00000000000 --- a/autotest/budget_file_compare.py +++ /dev/null @@ -1,120 +0,0 @@ -# utility for comparing two MODFLOW 6 budget files - -# To use this eval_bud_diff function on a gwf or gwt budget file, -# the function may need ia, in order to exclude comparison of the residual -# term, which is stored in the diagonal position of the flowja array. -# The following code can be used to extract ia from the grb file. -# get ia/ja from binary grid file -# fname = '{}.dis.grb'.format(os.path.basename(sim.name)) -# fpth = os.path.join(sim.simpath, fname) -# grbobj = flopy.mf6.utils.MfGrdFile(fpth) -# ia = grbobj._datadict['IA'] - 1 - - -import os - -import numpy as np - - -def eval_bud_diff(fpth, b0, b1, ia=None, dtol=1e-6): - diffmax = 0.0 - difftag = "None" - difftime = None - fail = False - - # build list of cbc data to retrieve - avail = b0.get_unique_record_names() - - # initialize list for storing totals for each budget term terms - cbc_keys = [] - for t in avail: - if isinstance(t, bytes): - t = t.decode() - t = t.strip() - cbc_keys.append(t) - - # open a summary file and write header - f = open(fpth, "w") - line = f"{'Time':15s}" - line += f" {'Datatype':15s}" - line += f" {'File 1':15s}" - line += f" {'File 2':15s}" - line += f" {'Difference':15s}" - f.write(line + "\n") - f.write(len(line) * "-" + "\n") - - # get data from cbc file - kk = b0.get_kstpkper() - times = b0.get_times() - for idx, (k, t) in enumerate(zip(kk, times)): - v0sum = 0.0 - v1sum = 0.0 - for key in cbc_keys: - v0 = b0.get_data(kstpkper=k, text=key)[0] - v1 = b1.get_data(kstpkper=k, text=key)[0] - if isinstance(v0, np.recarray): - v0 = v0["q"].sum() - v1 = v1["q"].sum() - else: - v0 = v0.flatten() - v1 = v1.flatten() - if key == "FLOW-JA-FACE": - # Set residual (stored in diagonal of flowja) to zero - if ia is None: - raise Exception("ia is required for model flowja") - idiagidx = ia[:-1] - v0[idiagidx] = 0.0 - v1[idiagidx] = 0.0 - v0 = v0.sum() - v1 = v1.sum() - - # sum all of the values - if key != "AUXILIARY": - v0sum += v0 - v1sum += v1 - - diff = v0 - v1 - if abs(diff) > abs(diffmax): - diffmax = diff - difftag = key - difftime = t - if abs(diff) > dtol: - fail = True - line = f"{t:15g}" - line += f" {key:15s}" - line += f" {v0:15g}" - line += f" {v1:15g}" - line += f" {diff:15g}" - f.write(line + "\n") - - # evaluate the sums - diff = v0sum - v1sum - if abs(diff) > dtol: - fail = True - line = f"{t:15g}" - line += f" {'TOTAL':15s}" - line += f" {v0sum:15g}" - line += f" {v1sum:15g}" - line += f" {diff:15g}" - f.write(line + "\n") - - msg = f"\nSummary of changes in {os.path.basename(fpth)}\n" - msg += "-" * 72 + "\n" - msg += f"Maximum cbc difference: {diffmax}\n" - msg += f"Maximum cbc difference time: {difftime}\n" - msg += f"Maximum cbc datatype: {difftag}\n" - if fail: - msg += f"Maximum cbc criteria exceeded: {dtol}" - assert not fail, msg - - # close summary file and print the final message - f.close() - print(msg) - - msg = f"sum of first cbc file flows ({v0sum}) " + f"exceeds dtol ({dtol})" - assert abs(v0sum) < dtol, msg - - msg = f"sum of second cbc file flows ({v1sum}) " + f"exceeds dtol ({dtol})" - assert abs(v1sum) < dtol, msg - - return diff --git a/autotest/build_exes.py b/autotest/build_exes.py index 3d71b8da338..f63c0401689 100644 --- a/autotest/build_exes.py +++ b/autotest/build_exes.py @@ -2,10 +2,8 @@ from pathlib import Path import pytest -from modflow_devtools.build import meson_build - from conftest import project_root_path - +from modflow_devtools.build import meson_build repository = "MODFLOW-USGS/modflow6" top_bin_path = project_root_path / "bin" @@ -20,12 +18,16 @@ def test_meson_build(bin_path): meson_build( project_path=project_root_path, build_path=project_root_path / "builddir", - bin_path=bin_path + bin_path=bin_path, ) if __name__ == "__main__": - parser = argparse.ArgumentParser("Rebuild local development version of MODFLOW 6") - parser.add_argument("-p", "--path", help="path to bin directory", default=top_bin_path) + parser = argparse.ArgumentParser( + "Rebuild local development version of MODFLOW 6" + ) + parser.add_argument( + "-p", "--path", help="path to bin directory", default=top_bin_path + ) args = parser.parse_args() test_meson_build(Path(args.path).resolve()) diff --git a/autotest/scripts/cross_section_functions.py b/autotest/cross_section_functions.py similarity index 100% rename from autotest/scripts/cross_section_functions.py rename to autotest/cross_section_functions.py diff --git a/autotest/disu_util.py b/autotest/disu_util.py deleted file mode 100644 index 041e728622b..00000000000 --- a/autotest/disu_util.py +++ /dev/null @@ -1,101 +0,0 @@ -import numpy as np - - -def get_disu_kwargs(nlay, nrow, ncol, delr, delc, tp, botm): - """ - Simple utility for creating args needed to construct - a disu package - - """ - - def get_nn(k, i, j): - return k * nrow * ncol + i * ncol + j - - nodes = nlay * nrow * ncol - iac = np.zeros((nodes), dtype=int) - ja = [] - area = np.zeros((nodes), dtype=float) - top = np.zeros((nodes), dtype=float) - bot = np.zeros((nodes), dtype=float) - ihc = [] - cl12 = [] - hwva = [] - for k in range(nlay): - for i in range(nrow): - for j in range(ncol): - # diagonal - n = get_nn(k, i, j) - ja.append(n) - iac[n] += 1 - area[n] = delr[i] * delc[j] - ihc.append(n + 1) - cl12.append(n + 1) - hwva.append(n + 1) - if k == 0: - top[n] = tp - else: - top[n] = botm[k - 1] - bot[n] = botm[k] - # up - if k > 0: - ja.append(get_nn(k - 1, i, j)) - iac[n] += 1 - ihc.append(0) - dz = botm[k - 1] - botm[k] - cl12.append(0.5 * dz) - hwva.append(delr[i] * delc[j]) - # back - if i > 0: - ja.append(get_nn(k, i - 1, j)) - iac[n] += 1 - ihc.append(1) - cl12.append(0.5 * delc[i]) - hwva.append(delr[j]) - # left - if j > 0: - ja.append(get_nn(k, i, j - 1)) - iac[n] += 1 - ihc.append(1) - cl12.append(0.5 * delr[j]) - hwva.append(delc[i]) - # right - if j < ncol - 1: - ja.append(get_nn(k, i, j + 1)) - iac[n] += 1 - ihc.append(1) - cl12.append(0.5 * delr[j]) - hwva.append(delc[i]) - # front - if i < nrow - 1: - ja.append(get_nn(k, i + 1, j)) - iac[n] += 1 - ihc.append(1) - cl12.append(0.5 * delc[i]) - hwva.append(delr[j]) - # bottom - if k < nlay - 1: - ja.append(get_nn(k + 1, i, j)) - iac[n] += 1 - ihc.append(0) - if k == 0: - dz = tp - botm[k] - else: - dz = botm[k - 1] - botm[k] - cl12.append(0.5 * dz) - hwva.append(delr[i] * delc[j]) - ja = np.array(ja, dtype=int) - nja = ja.shape[0] - hwva = np.array(hwva, dtype=float) - kw = {} - kw["nodes"] = nodes - kw["nja"] = nja - kw["nvert"] = None - kw["top"] = top - kw["bot"] = bot - kw["area"] = area - kw["iac"] = iac - kw["ja"] = ja - kw["ihc"] = ihc - kw["cl12"] = cl12 - kw["hwva"] = hwva - return kw diff --git a/autotest/framework.py b/autotest/framework.py index c37bced6250..6e4e89c0832 100644 --- a/autotest/framework.py +++ b/autotest/framework.py @@ -1,40 +1,11 @@ -import os -import sys +import flopy -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) +class TestFramework: + # tell pytest this isn't a test class, don't collect it + __test__ = False - -def running_on_CI(): - return "TRAVIS" in os.environ or "CI" in os.environ - - -def set_teardown_test(): - teardown = True - for idx, arg in enumerate(sys.argv): - if arg.lower() == "--keep": - teardown = False - return teardown - - -class testing_framework(object): - def __init__(self): - return - - def build_mf6_models(self, build_function, idx, exdir): + def build(self, build_function, idx, exdir): """ Build base and regression MODFLOW 6 models @@ -58,28 +29,7 @@ def build_mf6_models(self, build_function, idx, exdir): else: regression.write_input() - def build_mf6_models_legacy(self, build_function, idx, exdir): - """ - Build base and regression for older MODFLOW 6 models - - Parameters - ---------- - build_function : function - user defined function that builds a base model and optionally - builds a regression model. If a regression model is not built - then None must be returned from the function for the regression - model. - idx : int - counter that corresponds to exdir entry - exdir : str - path to regression model files - """ - base, regression = build_function(idx, exdir) - base.write_simulation() - if regression is not None: - regression.write_input() - - def run_mf6(self, sim): + def run(self, sim, workspace=None): """ Run the MODFLOW 6 simulation and compare to existing head file or appropriate MODFLOW-2005, MODFLOW-NWT, MODFLOW-USG, or MODFLOW-LGR run. @@ -90,11 +40,14 @@ def run_mf6(self, sim): MODFLOW 6 autotest simulation object that runs the base and regression models, compares the results, and tears down the test if successful. + workspace : str + The path to the workspace where the test is run. """ - print(os.getcwd()) - sim.set_model(sim.name, testModel=False) + + sim.set_model( + sim.name if workspace is None else workspace, testModel=False + ) sim.run() sim.compare() if sim.exfunc is not None: sim.exfunc(sim) - sim.teardown() diff --git a/autotest/get_exes.py b/autotest/get_exes.py index 4b2d31c870e..0a95dbd3fe2 100644 --- a/autotest/get_exes.py +++ b/autotest/get_exes.py @@ -5,13 +5,12 @@ import flopy import pytest +from conftest import project_root_path from flaky import flaky from modflow_devtools.build import meson_build from modflow_devtools.download import download_and_unzip, get_release from modflow_devtools.misc import get_ostag -from conftest import project_root_path - repository = "MODFLOW-USGS/modflow6" top_bin_path = project_root_path / "bin" @@ -22,7 +21,7 @@ def get_asset_name(asset: dict) -> str: if "win" in ostag: return name else: - prefix = name.rpartition('_')[0] + prefix = name.rpartition("_")[0] prefix += f"_{ostag}" return f"{prefix}.zip" @@ -43,9 +42,13 @@ def test_rebuild_release(rebuilt_bin_path: Path): release = get_release(repository) assets = release["assets"] ostag = get_ostag() - asset = next(iter([a for a in assets if a["name"] == get_asset_name(a)]), None) + asset = next( + iter([a for a in assets if a["name"] == get_asset_name(a)]), None + ) if not asset: - warn(f"Couldn't find asset for OS {get_ostag()}, available assets:\n{assets}") + warn( + f"Couldn't find asset for OS {get_ostag()}, available assets:\n{assets}" + ) with TemporaryDirectory() as td: download_path = Path(td) @@ -56,7 +59,9 @@ def test_rebuild_release(rebuilt_bin_path: Path): ) # update IDEVELOPMODE - source_files_path = download_path / asset["name"].replace(".zip", "") / "src" + source_files_path = ( + download_path / asset["name"].replace(".zip", "") / "src" + ) version_file_path = source_files_path / "Utilities" / "version.f90" with open(version_file_path) as f: lines = f.read().splitlines() @@ -72,7 +77,7 @@ def test_rebuild_release(rebuilt_bin_path: Path): meson_build( project_path=source_files_path.parent, build_path=download_path / "builddir", - bin_path=rebuilt_bin_path + bin_path=rebuilt_bin_path, ) @@ -84,7 +89,9 @@ def test_get_executables(downloaded_bin_path: Path): if __name__ == "__main__": - parser = argparse.ArgumentParser("Get executables needed for MODFLOW 6 testing") + parser = argparse.ArgumentParser( + "Get executables needed for MODFLOW 6 testing" + ) parser.add_argument("-p", "--path", help="path to top-level bin directory") args = parser.parse_args() bin_path = Path(args.path).resolve() if args.path else top_bin_path diff --git a/autotest/pyproject.toml b/autotest/pyproject.toml index dbb4477760a..a3bce3133ee 100644 --- a/autotest/pyproject.toml +++ b/autotest/pyproject.toml @@ -5,4 +5,4 @@ build-backend = "setuptools.build_meta" [tool.black] line-length = 79 -target_version = ["py37"] +target_version = ["py37"] \ No newline at end of file diff --git a/autotest/pytest.ini b/autotest/pytest.ini index be844af8904..013c63a54ff 100644 --- a/autotest/pytest.ini +++ b/autotest/pytest.ini @@ -1,7 +1,16 @@ [pytest] +python_files = + test_*.py + *_test*.py markers = slow: tests taking more than a few seconds to complete repo: tests using models loaded from an external repository large: tests using large models (examples and largetestmodels) regression: tests comparing results from different versions - developmode: tests that should only run with IDEVELOPMODE = 1 \ No newline at end of file + developmode: tests that should only run with IDEVELOPMODE = 1 + gwf: tests for groundwater flow models + gwt: tests for groundwater transport models + ats: tests for adaptive time step package + aux: tests for auxiliary variables + lak: tests for lake package + maw: tests for multi-aquifer well package diff --git a/autotest/simulation.py b/autotest/simulation.py index a354d5873c3..47a45aa0799 100644 --- a/autotest/simulation.py +++ b/autotest/simulation.py @@ -2,14 +2,14 @@ import shutil import sys import time +from traceback import format_exc -import numpy as np import flopy +import numpy as np +from common_regression import (get_mf6_comparison, get_mf6_files, + get_namefiles, setup_mf6, setup_mf6_comparison) from flopy.utils.compare import compare_heads - -from common_regression import get_namefiles, get_mf6_files, setup_mf6, setup_mf6_comparison, get_mf6_comparison -from framework import running_on_CI, set_teardown_test -import targets +from modflow_devtools.misc import is_in_ci sfmt = "{:25s} - {}" extdict = { @@ -21,7 +21,10 @@ } -class Simulation(object): +class TestSimulation: + # tell pytest this isn't a test class, don't collect it + __test__ = False + def __init__( self, name, @@ -38,41 +41,12 @@ def __init__( make_comparison=True, simpath=None, ): - teardown_test = set_teardown_test() - for idx, arg in enumerate(sys.argv): - if arg[2:].lower() in list(targets.target_dict.keys()): - key = arg[2:].lower() - exe0 = targets.target_dict[key] - exe = os.path.join(os.path.dirname(exe0), sys.argv[idx + 1]) - msg = ( - f"replacing {key} executable " - + f'"{targets.target_dict[key]}" with ' - + f'"{exe}".' - ) - print(msg) - targets.target_dict[key] = exe - - if exe_dict is not None: - if not isinstance(exe_dict, dict): - msg = "exe_dict must be a dictionary" - assert False, msg - keys = list(targets.target_dict.keys()) - for key, value in exe_dict.items(): - if key in keys: - exe0 = targets.target_dict[key] - exe = os.path.join(os.path.dirname(exe0), value) - msg = ( - f"replacing {key} executable " - + f'"{targets.target_dict[key]}" with ' - + f'"{exe}".' - ) - print(msg) - targets.target_dict[key] = exe - msg = sfmt.format("Initializing test", name) print(msg) + self.name = name self.exfunc = exfunc + self.targets = exe_dict self.simpath = simpath self.inpt = None self.outp = None @@ -122,11 +96,10 @@ def __init__( # set allow failure self.require_failure = require_failure - self.teardown_test = teardown_test self.success = False # set is_ci - self.is_CI = running_on_CI() + self.is_CI = is_in_ci() return @@ -151,23 +124,18 @@ def set_model(self, pth, testModel=True): # determine comparison model self.setup_comparison(pth, pth, testModel=testModel) # if self.mf6_regression: - # self.action = "mf6-regression" + # self.action = "mf6_regression" # else: - # self.action = pymake.get_mf6_comparison(pth) + # self.action = get_mf6_comparison(pth) if self.action is not None: - if "mf6" in self.action or "mf6-regression" in self.action: + if "mf6" in self.action or "mf6_regression" in self.action: cinp, self.coutp = get_mf6_files(fpth) def setup(self, src, dst): - msg = sfmt.format("Setup test", self.name) + msg = sfmt.format("Setting up test workspace", self.name) print(msg) self.originpath = src self.simpath = dst - # write message - print( - "running pymake.setup_mf6 from " - + f"{os.path.abspath(os.getcwd())}" - ) try: self.inpt, self.outp = setup_mf6(src=src, dst=dst) print("waiting...") @@ -177,7 +145,7 @@ def setup(self, src, dst): success = False print(f"source: {src}") print(f"destination: {dst}") - assert success, "did not run pymake.setup_mf6" + assert success, f"Failed to set up test workspace: {format_exc()}" if success: self.setup_comparison(src, dst) @@ -207,15 +175,13 @@ def setup_comparison(self, src, dst, testModel=True): # Copy comparison simulations if available if self.mf6_regression: - action = "mf6-regression" + action = "mf6_regression" pth = os.path.join(dst, action) if os.path.isdir(pth): shutil.rmtree(pth) shutil.copytree(dst, pth) elif testModel: - action = setup_mf6_comparison( - src, dst, remove_existing=self.teardown_test - ) + action = setup_mf6_comparison(src, dst, remove_existing=True) else: action = get_mf6_comparison(dst) @@ -234,8 +200,7 @@ def run(self): nam = None # run mf6 models - target, ext = os.path.splitext(targets.program) - exe = os.path.abspath(targets.target_dict[target]) + exe = str(self.targets["mf6"].absolute()) msg = sfmt.format("using executable", exe) print(msg) try: @@ -294,13 +259,13 @@ def run(self): else: cpth = os.path.join(self.simpath, self.action) key = self.action.lower().replace(".cmp", "") - exe = os.path.abspath(targets.target_dict[key]) + exe = str(self.targets[key].absolute()) msg = sfmt.format("comparison executable", exe) print(msg) if ( "mf6" in key or "libmf6" in key - or "mf6-regression" in key + or "mf6_regression" in key ): nam = None else: @@ -376,7 +341,7 @@ def compare(self): "ahd", "bin", ) - if "mf6-regression" in self.action: + if "mf6_regression" in self.action: success, msgall = self._compare_heads( msgall, extensions=head_extensions, @@ -486,13 +451,13 @@ def compare(self): msgall += msg + " ... FAILED\n" # compare concentrations - if "mf6-regression" in self.action: + if "mf6_regression" in self.action: success, msgall = self._compare_concentrations(msgall) if not success: self.success = False # compare cbc files - if "mf6-regression" in self.action: + if "mf6_regression" in self.action: cbc_extensions = ( "cbc", "bud", @@ -506,31 +471,6 @@ def compare(self): assert self.success, msgall return - def teardown(self): - """ - Remove the example folder - - """ - if self.success: - if self.teardown_test: - msg = sfmt.format("Teardown test", self.name) - print(msg) - - # wait to delete on windows - if sys.platform.lower() == "win32": - time.sleep(3) - - try: - shutil.rmtree(self.simpath) - success = True - except: - print("Could not remove test " + self.name) - success = False - assert success - else: - print("Retaining test files") - return - def _get_mfsim_listing(self, lst_pth): """Get the tail of the mfsim.lst listing file""" msg = "" @@ -605,7 +545,7 @@ def _regression_files(self, extensions): if file_name.lower().endswith(extension): files0.append(fpth0) fpth1 = os.path.join( - self.simpath, "mf6-regression", file_name + self.simpath, "mf6_regression", file_name ) files1.append(fpth1) break diff --git a/autotest/targets.py b/autotest/targets.py deleted file mode 100644 index 9202f39a3bf..00000000000 --- a/autotest/targets.py +++ /dev/null @@ -1,98 +0,0 @@ -import os -import subprocess -import sys - -import flopy - - -def target_pth(target, pth): - exe_exists = flopy.which(target, path=pth) - # if target does not exist in specified path determine if it - # exists anywhere in the path - if exe_exists is None: - exe_exists = flopy.which(target) - if exe_exists is None: - exe_exists = os.path.abspath(os.path.join(pth, target)) - raise Exception(f"{exe_exists} does not exist or is not executable.") - return os.path.abspath(exe_exists) - - -target_ext = "" -target_so = ".so" -sysinfo = sys.platform.lower() -if sysinfo.lower() == "win32": - target_ext = ".exe" - target_so = ".dll" -elif sysinfo.lower() == "darwin": - target_so = ".dylib" - -# paths to executables for previous versions of MODFLOW -downloaded_bindir = os.path.join("..", "bin", "downloaded") -rebuilt_bindir = os.path.join("..", "bin", "rebuilt") - -# paths to MODFLOW 6 executable, source files, and example files -bindir = os.path.join("..", "bin") - -# create dictionary of valid executable targets for regression tests -target_dict = {} - -target = target_pth(f"mf2005dbl{target_ext}", downloaded_bindir) -target_dict["mf2005"] = target -target = target_pth(f"mfnwtdbl{target_ext}", downloaded_bindir) -target_dict["mfnwt"] = target -target = target_pth(f"mfusgdbl{target_ext}", downloaded_bindir) -target_dict["mfusg"] = target -target = target_pth(f"mflgrdbl{target_ext}", downloaded_bindir) -target_dict["mflgr"] = target -target = target_pth(f"mf2005{target_ext}", downloaded_bindir) -target_dict["mf2005s"] = target -target = target_pth(f"mt3dms{target_ext}", downloaded_bindir) -target_dict["mt3dms"] = target -target = target_pth(f"mf6{target_ext}", rebuilt_bindir) -target_dict["mf6-regression"] = target - -# create MODFLOW 6 target name and add to dictionary -program = f"mf6{target_ext}" -target = os.path.join(bindir, program) -target_dict["mf6"] = target - -# create MODFLOW 6 so/dll target name -tprog = f"libmf6{target_so}" -ttarg = os.path.join(bindir, tprog) -target_dict["libmf6"] = ttarg - -# add MODFLOW 5 to 6 converter to dictionary of valid executable targets -tprog = f"mf5to6{target_ext}" -ttarg = os.path.join(bindir, tprog) -target_dict["mf5to6"] = ttarg - -# add Zonebudget for 6 to dictionary of valid executable targets -tprog = f"zbud6{target_ext}" -ttarg = os.path.join(bindir, tprog) -target_dict["zbud6"] = ttarg - - -def run_exe(argv, ws="."): - buff = [] - proc = subprocess.Popen( - argv, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=ws - ) - result, error = proc.communicate() - if result is not None: - c = result.decode("utf-8") - c = c.rstrip("\r\n") - print(f"{c}") - buff.append(c) - - return proc.returncode, buff - - -def get_mf6_version(version="mf6"): - """Function to get MODFLOW 6 version number""" - exe = target_dict[version] - return_code, buff = run_exe((exe, "-v")) - if return_code == 0: - version = buff[0].split()[1] - else: - version = None - return version diff --git a/autotest/test_cli.py b/autotest/test_cli.py index a8e8f02aca6..366963df3f3 100644 --- a/autotest/test_cli.py +++ b/autotest/test_cli.py @@ -1,19 +1,23 @@ import subprocess -from pathlib import Path -_project_root_path = Path(__file__).parent.parent -_bin_path = _project_root_path / "bin" +from conftest import project_root_path + +bin_path = project_root_path / "bin" def test_cli_version(): - output = ' '.join(subprocess.check_output([str(_bin_path / 'mf6'), "-v"]).decode().split()) + output = " ".join( + subprocess.check_output([str(bin_path / "mf6"), "-v"]).decode().split() + ) assert output.startswith("mf6:") assert output.lower().count("release") == 1 # assert output.lower().count("candidate") <= 1 print(output) - version = output.lower().rpartition(":")[2].rpartition("release")[0].strip() + version = ( + output.lower().rpartition(":")[2].rpartition("release")[0].strip() + ) v_split = version.split(".") assert len(v_split) == 3 assert all(s.isdigit() for s in v_split) diff --git a/autotest/test_gwf.py b/autotest/test_gwf.py new file mode 100644 index 00000000000..59e0d18f6ac --- /dev/null +++ b/autotest/test_gwf.py @@ -0,0 +1,27 @@ +from modflow_devtools.executables import Executables +from pytest_cases import parametrize_with_cases +from simulation import TestSimulation +from test_gwf_maw04 import GwfMaw04Cases +from test_gwf_maw_cases import GwfMawCases + + +@parametrize_with_cases("case", cases=[GwfMawCases, GwfMaw04Cases]) +def test_gwf_models(case, targets: Executables): + data, sim, cmp, exfunc = case + sim.write_simulation() + if cmp: + cmp.write_simulation() + + test = TestSimulation( + name=data.name, + exe_dict=targets, + exfunc=exfunc, + idxsim=0, # TODO: remove parameter from TestSimulation + mf6_regression=True, + require_failure=data.xfail, + make_comparison=data.compare, + ) + + test.set_model(sim.simulation_data.mfpath.get_sim_path(), testModel=False) + test.run() + test.compare() diff --git a/autotest/test_gwf_ats01.py b/autotest/test_gwf_ats01.py index be62b72fdea..167ce57ba35 100644 --- a/autotest/test_gwf_ats01.py +++ b/autotest/test_gwf_ats01.py @@ -5,33 +5,13 @@ import os +import flopy import numpy as np import pytest - -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation ex = ["gwf_ats01a"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) -ddir = "data" nlay, nrow, ncol = 1, 1, 2 # set dt0, dtmin, dtmax, dtadj, dtfailadj @@ -198,11 +178,8 @@ def build_model(idx, dir): def eval_flow(sim): print("evaluating flow...") - name = ex[sim.idxsim] - gwfname = name - # This will fail if budget numbers cannot be read - fpth = os.path.join(sim.simpath, f"{gwfname}.lst") + fpth = os.path.join(sim.simpath, f"{sim.name}.lst") mflist = flopy.utils.Mf6ListBudget(fpth) names = mflist.get_record_names() inc = mflist.get_incremental() @@ -212,7 +189,7 @@ def eval_flow(sim): assert v == 10.0, f"Last time should be 10. Found {v}" # ensure obs results changing monotonically - fpth = os.path.join(sim.simpath, gwfname + ".obs.csv") + fpth = os.path.join(sim.simpath, sim.name + ".obs.csv") try: tc = np.genfromtxt(fpth, names=True, delimiter=",") except: @@ -226,39 +203,19 @@ def eval_flow(sim): assert np.all(np.diff(v) < 0), msg v = tc["time"][-1] assert v == 10.0, f"Last time should be 10. Found {v}" - return -# - No need to change any code below @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the model - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_flow, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_flow, idxsim=idx) - test.run_mf6(sim) - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(idx, name, function_tmpdir, targets): + workspace = str(function_tmpdir) + test = TestFramework() + test.build(build_model, 0, workspace) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_flow, idxsim=0 + ), + workspace, + ) diff --git a/autotest/test_gwf_ats02.py b/autotest/test_gwf_ats02.py index eea0c7c2fdd..7b41db9cfdc 100644 --- a/autotest/test_gwf_ats02.py +++ b/autotest/test_gwf_ats02.py @@ -6,33 +6,13 @@ import os +import flopy import numpy as np import pytest - -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation ex = ["gwf_ats02a"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) -ddir = "data" nlay, nrow, ncol = 5, 1, 1 botm = [80.0, 60.0, 40.0, 20.0, 0.0] @@ -194,11 +174,9 @@ def build_model(idx, dir): def make_plot(sim): print("making plots...") - name = ex[sim.idxsim] - ws = exdirs[sim.idxsim] - gwfname = name + ws = sim.simpath - fname = gwfname + ".hds" + fname = sim.name + ".hds" fname = os.path.join(ws, fname) hobj = flopy.utils.HeadFile(fname, precision="double") head = hobj.get_alldata()[:, :, 0, 0] @@ -231,14 +209,8 @@ def make_plot(sim): def eval_flow(sim): print("evaluating flow...") - name = ex[sim.idxsim] - gwfname = name - - if False: - make_plot(sim) - # This will fail if budget numbers cannot be read - fpth = os.path.join(sim.simpath, f"{gwfname}.lst") + fpth = os.path.join(sim.simpath, f"{sim.name}.lst") mflist = flopy.utils.Mf6ListBudget(fpth) names = mflist.get_record_names() inc = mflist.get_incremental() @@ -248,7 +220,7 @@ def eval_flow(sim): assert v == 20.0, f"Last time should be 20. Found {v}" # ensure obs results changing monotonically - fpth = os.path.join(sim.simpath, gwfname + ".obs.csv") + fpth = os.path.join(sim.simpath, sim.name + ".obs.csv") try: tc = np.genfromtxt(fpth, names=True, delimiter=",") except: @@ -259,38 +231,16 @@ def eval_flow(sim): ), "layer 1 should be dry for this period" -# - No need to change any code below @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the model - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_flow, idxsim=idx)) - - return - - -def main(): - # initialize testing framework - test = testing_framework() - - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_flow, idxsim=idx) - test.run_mf6(sim) - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(idx, name, function_tmpdir, targets): + test = TestFramework() + test.build(build_model, 0, str(function_tmpdir)) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_flow, idxsim=0 + ), + str(function_tmpdir), + ) diff --git a/autotest/test_gwf_ats03.py b/autotest/test_gwf_ats03.py index 8c4ee64715f..b36908ef866 100644 --- a/autotest/test_gwf_ats03.py +++ b/autotest/test_gwf_ats03.py @@ -12,33 +12,13 @@ import os +import flopy import numpy as np import pytest - -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation ex = ["gwf_ats03a"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) -ddir = "data" nlay, nrow, ncol = 1, 1, 10 @@ -214,11 +194,8 @@ def build_model(idx, dir): def eval_flow(sim): print("evaluating flow...") - name = ex[sim.idxsim] - gwfname = name - # ensure obs2 (a constant head time series) drops linearly from 100 to 50 - fpth = os.path.join(sim.simpath, gwfname + ".obs.csv") + fpth = os.path.join(sim.simpath, sim.name + ".obs.csv") try: tc = np.genfromtxt(fpth, names=True, delimiter=",") except: @@ -230,36 +207,16 @@ def eval_flow(sim): assert np.allclose(answer, result), msg -# - No need to change any code below @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the model - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_flow, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_flow, idxsim=idx) - test.run_mf6(sim) - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(idx, name, function_tmpdir, targets): + test = TestFramework() + test.build(build_model, 0, str(function_tmpdir)) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_flow, idxsim=0 + ), + str(function_tmpdir), + ) diff --git a/autotest/test_gwf_ats_lak01.py b/autotest/test_gwf_ats_lak01.py index f362b79a298..5f2381c08f3 100644 --- a/autotest/test_gwf_ats_lak01.py +++ b/autotest/test_gwf_ats_lak01.py @@ -3,28 +3,14 @@ # a smaller time step. import os -import sys +import flopy import numpy as np import pytest - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation ex = ["gwf_ats_lak_01a"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) - -# store global gwf for subsequent plotting gwf = None @@ -236,9 +222,6 @@ def build_model(idx, dir): def make_plot_xsect(sim, headall, stageall): print("making plots...") - name = ex[sim.idxsim] - ws = exdirs[sim.idxsim] - import matplotlib.patches as patches import matplotlib.pyplot as plt from matplotlib.collections import PatchCollection @@ -274,18 +257,13 @@ def make_plot_xsect(sim, headall, stageall): # ax.set_ylim(-10, 5) fname = "fig-xsect.pdf" - fname = os.path.join(ws, fname) + fname = os.path.join(sim.simpath, fname) plt.savefig(fname, bbox_inches="tight") - return - def make_plot(sim, times, headall, stageall): print("making plots...") - name = ex[sim.idxsim] - ws = exdirs[sim.idxsim] - import matplotlib.pyplot as plt fig = plt.figure(figsize=(6, 4)) @@ -296,11 +274,9 @@ def make_plot(sim, times, headall, stageall): ax.plot(times, h, "bo-", label="max head") fname = "fig-timeseries.pdf" - fname = os.path.join(ws, fname) + fname = os.path.join(sim.simpath, fname) plt.savefig(fname, bbox_inches="tight") - return - def get_kij_from_node(node, nrow, ncol): "return zero based k, i, j from zero based node number" @@ -316,16 +292,14 @@ def eval_results(sim): print("evaluating results...") # calculate volume of water and make sure it is conserved - name = ex[sim.idxsim] - gwfname = name - fname = gwfname + ".lak.bin" + fname = sim.name + ".lak.bin" fname = os.path.join(sim.simpath, fname) assert os.path.isfile(fname) bobj = flopy.utils.HeadFile(fname, text="STAGE") times = bobj.get_times() stage = bobj.get_alldata() - fname = gwfname + ".cbc" + fname = sim.name + ".cbc" fname = os.path.join(sim.simpath, fname) bobj = flopy.utils.CellBudgetFile(fname, precision="double", verbose=False) times = bobj.get_times() @@ -369,7 +343,7 @@ def eval_results(sim): print(msg) assert all_passed, "found recharge applied to cell beneath active lake" - fname = gwfname + ".hds" + fname = sim.name + ".hds" fname = os.path.join(sim.simpath, fname) assert os.path.isfile(fname) hobj = flopy.utils.HeadFile(fname) @@ -431,43 +405,21 @@ def eval_results(sim): errmsg = "lake stage does not match known answer" assert np.allclose(stage_answer, stage.flatten()), errmsg - if False: - make_plot(sim, times, head, stage) - make_plot_xsect(sim, head, stage) + # make_plot(sim, times, head, stage) + # make_plot_xsect(sim, head, stage) - return - -# - No need to change any code below +@pytest.mark.slow @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the model - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_results, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_results, idxsim=idx) - test.run_mf6(sim) - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(idx, name, function_tmpdir, targets): + test = TestFramework() + test.build(build_model, 0, str(function_tmpdir)) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_results, idxsim=0 + ), + str(function_tmpdir), + ) diff --git a/autotest/test_gwf_auxvars.py b/autotest/test_gwf_auxvars.py index 8a7704586a7..f19c2fb8dfe 100644 --- a/autotest/test_gwf_auxvars.py +++ b/autotest/test_gwf_auxvars.py @@ -1,32 +1,13 @@ import os import sys +import flopy import numpy as np import pytest - -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation ex = ["aux01"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) auxvar1 = 101.0 auxvar2 = 102.0 @@ -317,39 +298,17 @@ def eval_model(sim): assert np.allclose(r["AUX1"], auxvar1) assert np.allclose(r["AUX2"], auxvar2) - return - -# - No need to change any code below @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the model - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_model, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_model, idxsim=idx) - test.run_mf6(sim) - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(idx, name, function_tmpdir, targets): + test = TestFramework() + test.build(build_model, idx, str(function_tmpdir)) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_model, idxsim=idx + ), + str(function_tmpdir), + ) diff --git a/autotest/test_gwf_auxvars02.py b/autotest/test_gwf_auxvars02.py index 80a38bc6fa3..c9ef34ee4d5 100644 --- a/autotest/test_gwf_auxvars02.py +++ b/autotest/test_gwf_auxvars02.py @@ -1,32 +1,13 @@ import os import sys +import flopy import numpy as np import pytest - -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation ex = ["aux02"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) def build_model(idx, dir): @@ -138,39 +119,17 @@ def eval_model(sim): aname = f"AUX{a}" assert np.allclose(r[aname], a) - return - -# - No need to change any code below @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the model - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_model, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_model, idxsim=idx) - test.run_mf6(sim) - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(idx, name, function_tmpdir, targets): + test = TestFramework() + test.build(build_model, idx, str(function_tmpdir)) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_model, idxsim=idx + ), + str(function_tmpdir), + ) diff --git a/autotest/test_gwf_boundname01.py b/autotest/test_gwf_boundname01.py index d53c5852ce5..8a5ac6050cb 100644 --- a/autotest/test_gwf_boundname01.py +++ b/autotest/test_gwf_boundname01.py @@ -1,34 +1,14 @@ import os +import flopy import numpy as np import pytest - -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation ex = [ "bndname01", ] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) -ddir = "data" def build_model(idx, exdir): @@ -176,71 +156,28 @@ def replace_quotes(idx, exdir): def eval_obs(sim): - idx = sim.idxsim - ws = exdirs[idx] - name = ex[idx] - print("evaluating observations results..." f"({name})") + print("evaluating observations results..." f"({sim.name})") - fpth = os.path.join(ws, f"gwf_{name}.chd.obs.csv") + fpth = os.path.join(sim.simpath, f"gwf_{sim.name}.chd.obs.csv") obs0 = np.genfromtxt(fpth, delimiter=",", names=True) names0 = obs0.dtype.names - fpth = os.path.join(ws, "mf6", f"gwf_{name}.chd.obs.csv") + fpth = os.path.join(sim.simpath, "mf6", f"gwf_{sim.name}.chd.obs.csv") obs1 = np.genfromtxt(fpth, delimiter=",", names=True) names1 = obs1.dtype.names assert names0 == names1, "observation names are not identical" - assert np.array_equal(obs0, obs1), "observations are not identical" - return - -# - No need to change any code below @pytest.mark.parametrize( - "idx, exdir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, exdir): - # initialize testing framework - test = testing_framework() - - # build the model - test.build_mf6_models(build_model, idx, exdir) - - # replace quotes - replace_quotes(idx, exdir) - - # run the test model - test.run_mf6( - Simulation( - exdir, - idxsim=idx, - ) +def test_mf6model(idx, name, function_tmpdir, targets): + test = TestFramework() + test.build(build_model, 0, str(function_tmpdir)) + test.run( + TestSimulation(name=name, exe_dict=targets, idxsim=0), + str(function_tmpdir), ) - - -def main(): - # initialize testing framework - test = testing_framework() - - # build and run the test model - for idx, exdir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, exdir) - sim = Simulation( - exdir, - idxsim=idx, - exfunc=eval_obs, - ) - replace_quotes(idx, exdir) - test.run_mf6(sim) - - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() diff --git a/autotest/test_gwf_buy_lak01.py b/autotest/test_gwf_buy_lak01.py index 0b9a7a8c668..7a12bb2cffa 100644 --- a/autotest/test_gwf_buy_lak01.py +++ b/autotest/test_gwf_buy_lak01.py @@ -10,28 +10,16 @@ # 3. Buoyancy package with lake and aquifer density = 1024.5 import os -import sys +import flopy import numpy as np import pytest - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation ex = ["buy_lak_01a"] # , 'buy_lak_01b', 'buy_lak_01c'] buy_on_list = [False] # , True, True] concbuylist = [0.0] # , 0., 35.] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) def build_model(idx, dir): @@ -224,8 +212,7 @@ def eval_results(sim): print("evaluating results...") # calculate volume of water and make sure it is conserved - name = ex[sim.idxsim] - gwfname = "gwf_" + name + gwfname = "gwf_" + sim.name fname = gwfname + ".lak.bin" fname = os.path.join(sim.simpath, fname) assert os.path.isfile(fname) @@ -265,36 +252,16 @@ def eval_results(sim): # assert False -# - No need to change any code below @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the model - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_results, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_results, idxsim=idx) - test.run_mf6(sim) - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(idx, name, function_tmpdir, targets): + test = TestFramework() + test.build(build_model, 0, str(function_tmpdir)) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_results, idxsim=0 + ), + str(function_tmpdir), + ) diff --git a/autotest/test_gwf_buy_lak02.py b/autotest/test_gwf_buy_lak02.py index 86a8a094e64..514f3348da5 100644 --- a/autotest/test_gwf_buy_lak02.py +++ b/autotest/test_gwf_buy_lak02.py @@ -1,228 +1,90 @@ -# Test the buoyancy package and the variable density flows between the lake -# and the gwf model. This model has 4 layers and a lake incised within it. -# The model is transient and has heads in the aquifer higher than the initial -# stage in the lake. As the model runs, the lake and aquifer equalize and -# should end up at the same level. The test ensures that the initial and -# final water volumes in the entire system are the same. This test is different -# from the previous test in that transport is active. There are four -# different cases: -# 1. lak and aquifer have concentration of 0. -# 2. lak and aquifer have concentration of 35. -# 3. lak has concentration of 0., aquifer is 35. -# 4. lak has concentration of 35., aquifer is 0. - import os -import sys +from typing import NamedTuple +import flopy import numpy as np -import pytest - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation - -ex = ["buy_lak_02a", "buy_lak_02b", "buy_lak_02c", "buy_lak_02d"] -gwt_conc_list = [0.0, 35.0, 35.0, 0.0] -lak_conc_list = [0.0, 35.0, 0.0, 35.0] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) - - -def build_model(idx, dir): - lx = 7.0 - lz = 4.0 - nlay = 4 - nrow = 1 - ncol = 7 - nper = 1 - delc = 1.0 - delr = lx / ncol - delz = lz / nlay - top = 4.0 - botm = [3.0, 2.0, 1.0, 0.0] - - perlen = [50.0] - nstp = [50] - tsmult = [1.0] - - Kh = 1.0 - Kv = 1.0 - - tdis_rc = [] - for i in range(nper): - tdis_rc.append((perlen[i], nstp[i], tsmult[i])) - - nouter, ninner = 700, 300 - hclose, rclose, relax = 1e-8, 1e-6, 0.97 - - name = ex[idx] - - # build MODFLOW 6 files - ws = dir - sim = flopy.mf6.MFSimulation( - sim_name=name, version="mf6", exe_name="mf6", sim_ws=ws - ) - # create tdis package - tdis = flopy.mf6.ModflowTdis( - sim, time_units="DAYS", nper=nper, perioddata=tdis_rc - ) - - # create gwf model - gwfname = "gwf_" + name - gwtname = "gwt_" + name - - gwf = flopy.mf6.ModflowGwf(sim, modelname=gwfname, newtonoptions="NEWTON") - - imsgwf = flopy.mf6.ModflowIms( - sim, - print_option="ALL", - outer_dvclose=hclose, - outer_maximum=nouter, - under_relaxation="NONE", - inner_maximum=ninner, - inner_dvclose=hclose, - rcloserecord=f"{rclose} strict", - linear_acceleration="BICGSTAB", - scaling_method="NONE", - reordering_method="NONE", - relaxation_factor=relax, - filename=f"{gwfname}.ims", - ) - - idomain = np.full((nlay, nrow, ncol), 1) - idomain[0, 0, 1:6] = 0 - idomain[1, 0, 2:5] = 0 - idomain[2, 0, 3:4] = 0 - dis = flopy.mf6.ModflowGwfdis( - gwf, - nlay=nlay, - nrow=nrow, - ncol=ncol, - delr=delr, - delc=delc, - top=top, - botm=botm, - idomain=idomain, - ) - - # initial conditions - strt = np.zeros((nlay, nrow, ncol), dtype=float) - strt[0, 0, :] = 3.5 - strt[1, 0, :] = 3.0 - strt[1, 0, 1:6] = 2.5 - strt[2, 0, :] = 2.0 - strt[3, 0, :] = 1.0 - ic = flopy.mf6.ModflowGwfic(gwf, strt=strt) - - # node property flow - npf = flopy.mf6.ModflowGwfnpf( - gwf, - xt3doptions=False, - save_flows=True, - save_specific_discharge=True, - icelltype=1, - k=Kh, - k33=Kv, - ) - - sto = flopy.mf6.ModflowGwfsto(gwf, sy=0.3, ss=0.0, iconvert=1) - - buy_on = True - if buy_on: - pd = [(0, 0.7, 0.0, gwtname, "CONCENTRATION")] - buy = flopy.mf6.ModflowGwfbuy(gwf, denseref=1000.0, packagedata=pd) - - nlakeconn = 11 # note: number of connections for this lake - # pak_data = [lakeno, strt, nlakeconn, testauxvar, concentration, boundname] - pak_data = [(0, 2.25, nlakeconn, 0.0, 0.0)] - - connlen = delr / 2.0 - connwidth = delc - bedleak = "None" - con_data = [ - # con_data=(lakeno,iconn,(cellid),claktype,bedleak,belev,telev,connlen,connwidth ) - (0, 0, (0, 0, 0), "HORIZONTAL", bedleak, 10, 10, connlen, connwidth), - (0, 1, (1, 0, 1), "VERTICAL", bedleak, 10, 10, connlen, connwidth), - (0, 2, (1, 0, 1), "HORIZONTAL", bedleak, 10, 10, connlen, connwidth), - (0, 3, (2, 0, 2), "VERTICAL", bedleak, 10, 10, connlen, connwidth), - (0, 4, (2, 0, 2), "HORIZONTAL", bedleak, 10, 10, connlen, connwidth), - (0, 5, (3, 0, 3), "VERTICAL", bedleak, 10, 10, connlen, connwidth), - (0, 6, (2, 0, 4), "HORIZONTAL", bedleak, 10, 10, connlen, connwidth), - (0, 7, (2, 0, 4), "VERTICAL", bedleak, 10, 10, connlen, connwidth), - (0, 8, (1, 0, 5), "HORIZONTAL", bedleak, 10, 10, connlen, connwidth), - (0, 9, (1, 0, 5), "VERTICAL", bedleak, 10, 10, connlen, connwidth), - (0, 10, (0, 0, 6), "HORIZONTAL", bedleak, 10, 10, connlen, connwidth), - ] - - # period data - p_data = [ - (0, "STATUS", "ACTIVE"), - ] - - # note: for specifying lake number, use fortran indexing! - fname = f"{gwfname}.lak.obs.csv" - lak_obs = { - fname: [ - ("lakestage", "stage", 1), - ("lakevolume", "volume", 1), - ("lak1", "lak", 1, 1), - ("lak2", "lak", 1, 2), - ("lak3", "lak", 1, 3), - ("lak4", "lak", 1, 4), - ("lak5", "lak", 1, 5), - ("lak6", "lak", 1, 6), - ("lak7", "lak", 1, 7), - ("lak8", "lak", 1, 8), - ("lak9", "lak", 1, 9), - ("lak10", "lak", 1, 10), - ("lak11", "lak", 1, 11), - ], - "digits": 10, - } - - lak = flopy.mf6.modflow.ModflowGwflak( - gwf, - save_flows=True, - print_input=True, - print_flows=True, - print_stage=True, - stage_filerecord=f"{gwfname}.lak.bin", - budget_filerecord=f"{gwfname}.lak.bud", - nlakes=len(pak_data), - ntables=0, - packagedata=pak_data, - pname="LAK-1", - connectiondata=con_data, - perioddata=p_data, - observations=lak_obs, - auxiliary=["TESTAUXVAR", "CONCENTRATION"], - ) - - # output control - oc = flopy.mf6.ModflowGwfoc( - gwf, - budget_filerecord=f"{gwfname}.cbc", - head_filerecord=f"{gwfname}.hds", - headprintrecord=[("COLUMNS", 10, "WIDTH", 15, "DIGITS", 6, "GENERAL")], - saverecord=[("HEAD", "ALL"), ("BUDGET", "ALL")], - printrecord=[("HEAD", "LAST"), ("BUDGET", "LAST")], +from framework import TestFramework +from pytest_cases import parametrize, parametrize_with_cases +from simulation import TestSimulation + + +class GwfBuyLakCases: + """ + Test the buoyancy package and the variable density flows between the lake + and the gwf model. This model has 4 layers and a lake incised within it. + The model is transient and has heads in the aquifer higher than the initial + stage in the lake. As the model runs, the lake and aquifer equalize and + should end up at the same level. The test ensures that the initial and + final water volumes in the entire system are the same. This test is different + from the previous test in that transport is active. There are four + different cases: + 1. lak and aquifer have concentration of 0. + 2. lak and aquifer have concentration of 35. + 3. lak has concentration of 0., aquifer is 35. + 4. lak has concentration of 35., aquifer is 0. + """ + + class Data(NamedTuple): + name: str + gwt_conc: float + lak_conc: float + + @parametrize( + data=[ + Data(name="a", gwt_conc=0, lak_conc=0), + Data(name="b", gwt_conc=35, lak_conc=35), + Data(name="c", gwt_conc=35, lak_conc=0), + Data(name="d", gwt_conc=0, lak_conc=35), + ] ) + def case_generator(self, data, function_tmpdir): + lx = 7.0 + lz = 4.0 + nlay = 4 + nrow = 1 + ncol = 7 + nper = 1 + delc = 1.0 + delr = lx / ncol + delz = lz / nlay + top = 4.0 + botm = [3.0, 2.0, 1.0, 0.0] + + perlen = [50.0] + nstp = [50] + tsmult = [1.0] + + Kh = 1.0 + Kv = 1.0 + + tdis_rc = [] + for i in range(nper): + tdis_rc.append((perlen[i], nstp[i], tsmult[i])) + + nouter, ninner = 700, 300 + hclose, rclose, relax = 1e-8, 1e-6, 0.97 + + # build MODFLOW 6 files + sim = flopy.mf6.MFSimulation( + sim_name=data.name, + version="mf6", + exe_name="mf6", + sim_ws=str(function_tmpdir), + ) + # create tdis package + tdis = flopy.mf6.ModflowTdis( + sim, time_units="DAYS", nper=nper, perioddata=tdis_rc + ) - # create gwt model - transport = True - if transport: + # create gwf model + gwfname = "gwf_" + data.name + gwtname = "gwt_" + data.name - gwt = flopy.mf6.ModflowGwt(sim, modelname=gwtname) + gwf = flopy.mf6.ModflowGwf( + sim, modelname=gwfname, newtonoptions="NEWTON" + ) - imsgwt = flopy.mf6.ModflowIms( + imsgwf = flopy.mf6.ModflowIms( sim, print_option="ALL", outer_dvclose=hclose, @@ -235,12 +97,15 @@ def build_model(idx, dir): scaling_method="NONE", reordering_method="NONE", relaxation_factor=relax, - filename=f"{gwtname}.ims", + filename=f"{gwfname}.ims", ) - sim.register_ims_package(imsgwt, [gwt.name]) - dis = flopy.mf6.ModflowGwtdis( - gwt, + idomain = np.full((nlay, nrow, ncol), 1) + idomain[0, 0, 1:6] = 0 + idomain[1, 0, 2:5] = 0 + idomain[2, 0, 3:4] = 0 + dis = flopy.mf6.ModflowGwfdis( + gwf, nlay=nlay, nrow=nrow, ncol=ncol, @@ -252,181 +117,363 @@ def build_model(idx, dir): ) # initial conditions - strt = gwt_conc_list[idx] - ic = flopy.mf6.ModflowGwtic(gwt, strt=strt) - - # advection - adv = flopy.mf6.ModflowGwtadv(gwt, scheme="UPSTREAM") - - # storage - porosity = 0.30 - sto = flopy.mf6.ModflowGwtmst(gwt, porosity=porosity) + strt = np.zeros((nlay, nrow, ncol), dtype=float) + strt[0, 0, :] = 3.5 + strt[1, 0, :] = 3.0 + strt[1, 0, 1:6] = 2.5 + strt[2, 0, :] = 2.0 + strt[3, 0, :] = 1.0 + ic = flopy.mf6.ModflowGwfic(gwf, strt=strt) + + # node property flow + npf = flopy.mf6.ModflowGwfnpf( + gwf, + xt3doptions=False, + save_flows=True, + save_specific_discharge=True, + icelltype=1, + k=Kh, + k33=Kv, + ) - # sources - sourcerecarray = [ - (), + sto = flopy.mf6.ModflowGwfsto(gwf, sy=0.3, ss=0.0, iconvert=1) + + buy_on = True + if buy_on: + pd = [(0, 0.7, 0.0, gwtname, "CONCENTRATION")] + buy = flopy.mf6.ModflowGwfbuy(gwf, denseref=1000.0, packagedata=pd) + + nlakeconn = 11 # note: number of connections for this lake + # pak_data = [lakeno, strt, nlakeconn, testauxvar, concentration, boundname] + pak_data = [(0, 2.25, nlakeconn, 0.0, 0.0)] + + connlen = delr / 2.0 + connwidth = delc + bedleak = "None" + con_data = [ + # con_data=(lakeno,iconn,(cellid),claktype,bedleak,belev,telev,connlen,connwidth ) + ( + 0, + 0, + (0, 0, 0), + "HORIZONTAL", + bedleak, + 10, + 10, + connlen, + connwidth, + ), + (0, 1, (1, 0, 1), "VERTICAL", bedleak, 10, 10, connlen, connwidth), + ( + 0, + 2, + (1, 0, 1), + "HORIZONTAL", + bedleak, + 10, + 10, + connlen, + connwidth, + ), + (0, 3, (2, 0, 2), "VERTICAL", bedleak, 10, 10, connlen, connwidth), + ( + 0, + 4, + (2, 0, 2), + "HORIZONTAL", + bedleak, + 10, + 10, + connlen, + connwidth, + ), + (0, 5, (3, 0, 3), "VERTICAL", bedleak, 10, 10, connlen, connwidth), + ( + 0, + 6, + (2, 0, 4), + "HORIZONTAL", + bedleak, + 10, + 10, + connlen, + connwidth, + ), + (0, 7, (2, 0, 4), "VERTICAL", bedleak, 10, 10, connlen, connwidth), + ( + 0, + 8, + (1, 0, 5), + "HORIZONTAL", + bedleak, + 10, + 10, + connlen, + connwidth, + ), + (0, 9, (1, 0, 5), "VERTICAL", bedleak, 10, 10, connlen, connwidth), + ( + 0, + 10, + (0, 0, 6), + "HORIZONTAL", + bedleak, + 10, + 10, + connlen, + connwidth, + ), ] - ssm = flopy.mf6.ModflowGwtssm(gwt, sources=sourcerecarray) - lak_conc = lak_conc_list[idx] - lktpackagedata = [ - (0, lak_conc, 99.0, 999.0, "mylake"), + # period data + p_data = [ + (0, "STATUS", "ACTIVE"), ] - lkt = flopy.mf6.modflow.ModflowGwtlkt( - gwt, - boundnames=True, + + # note: for specifying lake number, use fortran indexing! + fname = f"{gwfname}.lak.obs.csv" + lak_obs = { + fname: [ + ("lakestage", "stage", 1), + ("lakevolume", "volume", 1), + ("lak1", "lak", 1, 1), + ("lak2", "lak", 1, 2), + ("lak3", "lak", 1, 3), + ("lak4", "lak", 1, 4), + ("lak5", "lak", 1, 5), + ("lak6", "lak", 1, 6), + ("lak7", "lak", 1, 7), + ("lak8", "lak", 1, 8), + ("lak9", "lak", 1, 9), + ("lak10", "lak", 1, 10), + ("lak11", "lak", 1, 11), + ], + "digits": 10, + } + + lak = flopy.mf6.modflow.ModflowGwflak( + gwf, save_flows=True, print_input=True, print_flows=True, - print_concentration=True, - concentration_filerecord=gwtname + ".lkt.bin", - budget_filerecord="gwtlak1.bud", - packagedata=lktpackagedata, - pname="LKT-1", - flow_package_name="LAK-1", - flow_package_auxiliary_name="CONCENTRATION", - auxiliary=["aux1", "aux2"], + print_stage=True, + stage_filerecord=f"{gwfname}.lak.bin", + budget_filerecord=f"{gwfname}.lak.bud", + nlakes=len(pak_data), + ntables=0, + packagedata=pak_data, + pname="LAK-1", + connectiondata=con_data, + perioddata=p_data, + observations=lak_obs, + auxiliary=["TESTAUXVAR", "CONCENTRATION"], ) + # output control - oc = flopy.mf6.ModflowGwtoc( - gwt, - budget_filerecord=f"{gwtname}.cbc", - concentration_filerecord=f"{gwtname}.ucn", - concentrationprintrecord=[ + oc = flopy.mf6.ModflowGwfoc( + gwf, + budget_filerecord=f"{gwfname}.cbc", + head_filerecord=f"{gwfname}.hds", + headprintrecord=[ ("COLUMNS", 10, "WIDTH", 15, "DIGITS", 6, "GENERAL") ], - saverecord=[("CONCENTRATION", "ALL")], - printrecord=[("CONCENTRATION", "ALL"), ("BUDGET", "ALL")], + saverecord=[("HEAD", "ALL"), ("BUDGET", "ALL")], + printrecord=[("HEAD", "LAST"), ("BUDGET", "LAST")], ) - fmi = flopy.mf6.ModflowGwtfmi(gwt, flow_imbalance_correction=True) - - # GWF GWT exchange - gwfgwt = flopy.mf6.ModflowGwfgwt( - sim, - exgtype="GWF6-GWT6", - exgmnamea=gwfname, - exgmnameb=gwtname, - filename=f"{name}.gwfgwt", - ) - - return sim, None - - -def eval_results(sim): - print("evaluating results...") - - # calculate volume of water and make sure it is conserved - name = ex[sim.idxsim] - gwfname = "gwf_" + name - gwtname = "gwt_" + name - fname = gwfname + ".lak.bin" - fname = os.path.join(sim.simpath, fname) - assert os.path.isfile(fname) - bobj = flopy.utils.HeadFile(fname, text="STAGE") - stage = bobj.get_alldata().flatten() - # print(stage) - - fname = gwfname + ".hds" - fname = os.path.join(sim.simpath, fname) - assert os.path.isfile(fname) - hobj = flopy.utils.HeadFile(fname) - head = hobj.get_data() - # print(head) - - fname = gwtname + ".ucn" - fname = os.path.join(sim.simpath, fname) - assert os.path.isfile(fname) - cobj = flopy.utils.HeadFile(fname, text="CONCENTRATION") - conc = cobj.get_data() - - fname = gwtname + ".lkt.bin" - fname = os.path.join(sim.simpath, fname) - assert os.path.isfile(fname) - cobj = flopy.utils.HeadFile(fname, text="CONCENTRATION") - clak = cobj.get_data().flatten() - - # calculate initial water volume - v0 = 3.5 * 2 # outermost columns - v0 += 2.5 * 2 # next innermost columns - v0 += 2.0 * 2 # next innermost columns - v0 += 1.0 * 1 # middle column - v0 = v0 * 0.3 # specific yield - - m0 = v0 * gwt_conc_list[sim.idxsim] - vl0 = (2.25 - 2.0) * 2 + (2.25 - 1.0) - m0 += vl0 * lak_conc_list[sim.idxsim] - v0 += vl0 - print(f"initial volume of water in model = {v0}") - print(f"initial mass of solute in model = {m0}") - - # calculate ending water volume in model - head = np.where(head > 1e10, -1e10, head) - botm = [3, 2, 1, 0] - top = [4, 3, 2, 1] - nlay, nrow, ncol = head.shape - v = 0 - m = 0.0 - for k in range(nlay): - for i in range(nrow): - for j in range(ncol): - h = min(head[k, i, j], top[k]) - dz = h - botm[k] - vcell = max(dz, 0.0) * 0.3 - v += vcell - m += vcell * conc[k, i, j] - - s = stage[-1] - vl = (s - 2.0) * 2 + (s - 1.0) - v = v + vl - m += vl * clak[0] - print(f"final volume of water in model = {v}") - print(f"final mass of solute in model = {m}") - - # check to make sure starting water volume same as equalized final volume - errmsg = f"initial and final water volume not equal: {v0} {v}" - assert np.allclose(v0, v), errmsg - - # check to make sure starting starting solute mass same as equalized solute mass - errmsg = f"initial and final solute mass not equal: {m0} {m}" - assert np.allclose(m0, m), errmsg - - # todo: add a better check of the lake concentrations - # assert False - - return - - -# - No need to change any code below -@pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + # create gwt model + transport = True + if transport: + gwt = flopy.mf6.ModflowGwt(sim, modelname=gwtname) + + imsgwt = flopy.mf6.ModflowIms( + sim, + print_option="ALL", + outer_dvclose=hclose, + outer_maximum=nouter, + under_relaxation="NONE", + inner_maximum=ninner, + inner_dvclose=hclose, + rcloserecord=f"{rclose} strict", + linear_acceleration="BICGSTAB", + scaling_method="NONE", + reordering_method="NONE", + relaxation_factor=relax, + filename=f"{gwtname}.ims", + ) + sim.register_ims_package(imsgwt, [gwt.name]) + + dis = flopy.mf6.ModflowGwtdis( + gwt, + nlay=nlay, + nrow=nrow, + ncol=ncol, + delr=delr, + delc=delc, + top=top, + botm=botm, + idomain=idomain, + ) + + # initial conditions + strt = data.gwt_conc + ic = flopy.mf6.ModflowGwtic(gwt, strt=strt) + + # advection + adv = flopy.mf6.ModflowGwtadv(gwt, scheme="UPSTREAM") + + # storage + porosity = 0.30 + sto = flopy.mf6.ModflowGwtmst(gwt, porosity=porosity) + + # sources + sourcerecarray = [ + (), + ] + ssm = flopy.mf6.ModflowGwtssm(gwt, sources=sourcerecarray) + + lak_conc = data.lak_conc + lktpackagedata = [ + (0, lak_conc, 99.0, 999.0, "mylake"), + ] + lkt = flopy.mf6.modflow.ModflowGwtlkt( + gwt, + boundnames=True, + save_flows=True, + print_input=True, + print_flows=True, + print_concentration=True, + concentration_filerecord=gwtname + ".lkt.bin", + budget_filerecord="gwtlak1.bud", + packagedata=lktpackagedata, + pname="LKT-1", + flow_package_name="LAK-1", + flow_package_auxiliary_name="CONCENTRATION", + auxiliary=["aux1", "aux2"], + ) + # output control + oc = flopy.mf6.ModflowGwtoc( + gwt, + budget_filerecord=f"{gwtname}.cbc", + concentration_filerecord=f"{gwtname}.ucn", + concentrationprintrecord=[ + ("COLUMNS", 10, "WIDTH", 15, "DIGITS", 6, "GENERAL") + ], + saverecord=[("CONCENTRATION", "ALL")], + printrecord=[("CONCENTRATION", "ALL"), ("BUDGET", "ALL")], + ) + + fmi = flopy.mf6.ModflowGwtfmi(gwt, flow_imbalance_correction=True) + + # GWF GWT exchange + gwfgwt = flopy.mf6.ModflowGwfgwt( + sim, + exgtype="GWF6-GWT6", + exgmnamea=gwfname, + exgmnameb=gwtname, + filename=f"{data.name}.gwfgwt", + ) + + return data, sim, None, self.eval_results + + def eval_results(self, sim, data): + print("evaluating results...") + + # calculate volume of water and make sure it is conserved + gwfname = "gwf_" + data.name + gwtname = "gwt_" + data.name + fname = gwfname + ".lak.bin" + fname = os.path.join(sim.simpath, fname) + assert os.path.isfile(fname) + bobj = flopy.utils.HeadFile(fname, text="STAGE") + stage = bobj.get_alldata().flatten() + # print(stage) + + fname = gwfname + ".hds" + fname = os.path.join(sim.simpath, fname) + assert os.path.isfile(fname) + hobj = flopy.utils.HeadFile(fname) + head = hobj.get_data() + # print(head) + + fname = gwtname + ".ucn" + fname = os.path.join(sim.simpath, fname) + assert os.path.isfile(fname) + cobj = flopy.utils.HeadFile(fname, text="CONCENTRATION") + conc = cobj.get_data() + + fname = gwtname + ".lkt.bin" + fname = os.path.join(sim.simpath, fname) + assert os.path.isfile(fname) + cobj = flopy.utils.HeadFile(fname, text="CONCENTRATION") + clak = cobj.get_data().flatten() + + # calculate initial water volume + v0 = 3.5 * 2 # outermost columns + v0 += 2.5 * 2 # next innermost columns + v0 += 2.0 * 2 # next innermost columns + v0 += 1.0 * 1 # middle column + v0 = v0 * 0.3 # specific yield + + m0 = v0 * data.gwt_conc + vl0 = (2.25 - 2.0) * 2 + (2.25 - 1.0) + m0 += vl0 * data.lak_conc + v0 += vl0 + print(f"initial volume of water in model = {v0}") + print(f"initial mass of solute in model = {m0}") + + # calculate ending water volume in model + head = np.where(head > 1e10, -1e10, head) + botm = [3, 2, 1, 0] + top = [4, 3, 2, 1] + nlay, nrow, ncol = head.shape + v = 0 + m = 0.0 + for k in range(nlay): + for i in range(nrow): + for j in range(ncol): + h = min(head[k, i, j], top[k]) + dz = h - botm[k] + vcell = max(dz, 0.0) * 0.3 + v += vcell + m += vcell * conc[k, i, j] + + s = stage[-1] + vl = (s - 2.0) * 2 + (s - 1.0) + v = v + vl + m += vl * clak[0] + print(f"final volume of water in model = {v}") + print(f"final mass of solute in model = {m}") + + # check to make sure starting water volume same as equalized final volume + errmsg = f"initial and final water volume not equal: {v0} {v}" + assert np.allclose(v0, v), errmsg + + # check to make sure starting starting solute mass same as equalized solute mass + errmsg = f"initial and final solute mass not equal: {m0} {m}" + assert np.allclose(m0, m), errmsg + + # todo: add a better check of the lake concentrations + + +@parametrize_with_cases( + "case", + cases=[ + GwfBuyLakCases, + ], ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the model - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_results, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_results, idxsim=idx) - test.run_mf6(sim) - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(case, targets): + data, sim, cmp, evl = case + sim.write_simulation() + if cmp: + cmp.write_simulation() + + simulation = TestSimulation( + name=data.name, exe_dict=targets, exfunc=evl, idxsim=0 + ) + simulation.set_model( + sim.simulation_data.mfpath.get_sim_path(), testModel=False + ) + simulation.run() + simulation.compare() + evl(simulation, data) diff --git a/autotest/test_gwf_buy_maw01.py b/autotest/test_gwf_buy_maw01.py index 72887e337d7..09e96b37579 100644 --- a/autotest/test_gwf_buy_maw01.py +++ b/autotest/test_gwf_buy_maw01.py @@ -12,26 +12,15 @@ import os import sys +import flopy import numpy as np import pytest - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation ex = ["buy_maw_01a"] # , 'buy_maw_01b', 'buy_maw_01c'] buy_on_list = [False] # , True, True] concbuylist = [0.0] # , 0., 35.] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) def build_model(idx, dir): @@ -200,8 +189,7 @@ def eval_results(sim): print("evaluating results...") # calculate volume of water and make sure it is conserved - name = ex[sim.idxsim] - gwfname = "gwf_" + name + gwfname = "gwf_" + sim.name fname = gwfname + ".maw.bin" fname = os.path.join(sim.simpath, fname) assert os.path.isfile(fname) @@ -262,39 +250,17 @@ def eval_results(sim): print(msg) assert np.allclose(qmaw, -qgwf), msg - return - -# - No need to change any code below @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the model - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_results, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_results, idxsim=idx) - test.run_mf6(sim) - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(idx, name, function_tmpdir, targets): + test = TestFramework() + test.build(build_model, 0, str(function_tmpdir)) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_results, idxsim=0 + ), + str(function_tmpdir), + ) diff --git a/autotest/test_gwf_buy_sfr01.py b/autotest/test_gwf_buy_sfr01.py index 73749476ff3..24f9bb12122 100644 --- a/autotest/test_gwf_buy_sfr01.py +++ b/autotest/test_gwf_buy_sfr01.py @@ -4,24 +4,13 @@ import os import sys +import flopy import numpy as np import pytest - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation ex = ["buy_sfr_01"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) def build_model(idx, dir): @@ -387,9 +376,8 @@ def eval_results(sim): print("evaluating results...") # assign names - name = ex[sim.idxsim] - gwtname = "gwt_" + name - gwfname = "gwf_" + name + gwtname = "gwt_" + sim.name + gwfname = "gwf_" + sim.name # load the sft concentrations and make sure all values are correct fname = gwtname + ".sft.bin" @@ -468,42 +456,14 @@ def eval_results(sim): qcalc, qsim ), f"reach {n} flow {qcalc} not equal {qsim}" - # uncomment when testing - # assert False - - return - - -# - No need to change any code below -@pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), -) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the model - test.build_mf6_models(build_model, idx, dir) - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_results, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_results, idxsim=idx) - test.run_mf6(sim) - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +@pytest.mark.parametrize("name", ex) +def test_mf6model(name, function_tmpdir, targets): + test = TestFramework() + test.build(build_model, 0, str(function_tmpdir)) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_results, idxsim=0 + ), + str(function_tmpdir), + ) diff --git a/autotest/test_gwf_chd01.py b/autotest/test_gwf_chd01.py index 8f3bbf79a40..0dabc3ac06d 100644 --- a/autotest/test_gwf_chd01.py +++ b/autotest/test_gwf_chd01.py @@ -1,35 +1,14 @@ import os -import sys +import flopy import numpy as np import pytest - -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation ex = [ "chd01", ] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) -ddir = "data" def build_model(idx, dir): @@ -142,15 +121,11 @@ def build_model(idx, dir): def eval_model(sim): print("evaluating model...") - name = ex[sim.idxsim] - gwfname = "gwf_" + name + gwfname = "gwf_" + sim.name fpth = os.path.join(sim.simpath, f"{gwfname}.hds") - try: - hobj = flopy.utils.HeadFile(fpth, precision="double") - head = hobj.get_data().flatten() - except: - assert False, f'could not load data from "{fpth}"' + hobj = flopy.utils.HeadFile(fpth, precision="double") + head = hobj.get_data().flatten() # This is the answer to this problem. hres = np.linspace(1, 0, 100) @@ -158,42 +133,14 @@ def eval_model(sim): hres, head ), "simulated head do not match with known solution." - # comment when done testing - # assert False - - return - - -# - No need to change any code below -@pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), -) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the model - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_model, idxsim=idx)) - -def main(): - # initialize testing framework - test = testing_framework() - - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_model, idxsim=idx) - test.run_mf6(sim) - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +@pytest.mark.parametrize("name", ex) +def test_mf6model(name, function_tmpdir, targets): + test = TestFramework() + test.build(build_model, 0, str(function_tmpdir)) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_model, idxsim=0 + ), + str(function_tmpdir), + ) diff --git a/autotest/test_gwf_csub_db01_nr.py b/autotest/test_gwf_csub_db01_nr.py index 1e19d128f54..58b16e09d05 100644 --- a/autotest/test_gwf_csub_db01_nr.py +++ b/autotest/test_gwf_csub_db01_nr.py @@ -1,26 +1,10 @@ import os +import flopy import numpy as np import pytest - -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import running_on_CI, testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation ex = ( "csub_db01a", @@ -30,9 +14,7 @@ "csub_db01e", "csub_db01f", ) -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) + newtons = ( True, True, @@ -65,16 +47,9 @@ False, False, ) - -ddir = "data" - -# set replace_exe to None to use default executable -replace_exe = None - htol = None dtol = 1e-3 budtol = 0.01 - bud_lst = ( "CSUB-CGELASTIC_IN", "CSUB-CGELASTIC_OUT", @@ -432,51 +407,20 @@ def eval_comp(sim): print(" " + msg) -# - No need to change any code below - - @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir): - # determine if running on CI infrastructure - is_CI = running_on_CI() - r_exe = None - if not is_CI: - if replace_exe is not None: - r_exe = replace_exe - - # initialize testing framework - test = testing_framework() - - # build the model - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6( - Simulation( - dir, exfunc=eval_comp, exe_dict=r_exe, htol=htol, idxsim=idx - ) +def test_mf6model(idx, name, function_tmpdir, targets): + test = TestFramework() + test.build(build_model, idx, str(function_tmpdir)) + test.run( + TestSimulation( + name=name, + exe_dict=targets, + exfunc=eval_comp, + htol=htol, + idxsim=idx, + ), + str(function_tmpdir), ) - - -def main(): - # initialize testing framework - test = testing_framework() - - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation( - dir, exfunc=eval_comp, exe_dict=replace_exe, htol=htol, idxsim=idx - ) - test.run_mf6(sim) - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() diff --git a/autotest/test_gwf_csub_dbgeo01.py b/autotest/test_gwf_csub_dbgeo01.py index c0e0dd9678b..c899557c3a8 100644 --- a/autotest/test_gwf_csub_dbgeo01.py +++ b/autotest/test_gwf_csub_dbgeo01.py @@ -1,25 +1,12 @@ import os +import flopy import numpy as np import pytest - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import running_on_CI, testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation ex = ["csub_dbgeo01a"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) -ddir = "data" - ndcell = [19] strt = [0.0] chdh = [0] @@ -27,15 +14,6 @@ bso = [True] # sstate = [None, True] -# run all examples on Travis -# continuous_integration = [True for idx in range(len(exdirs))] -# the delay bed problems only run on the development version of MODFLOW-2005 -# set travis to True when version 1.13.0 is released -continuous_integration = [True for idx in range(len(exdirs))] - -# set replace_exe to None to use default executable -replace_exe = None - # comparison data compdataa = [ 0.6829965295, @@ -345,7 +323,7 @@ def eval_sub(sim): assert False, f'could not load data from "{fpth}"' # set comparison data - tc0 = compdata[sim.idxsim] + tc0 = compdata[0] # calculate maximum absolute error diff = tc["TCOMP"] - tc0[:] @@ -379,53 +357,12 @@ def eval_sub(sim): sim.success = True print(" " + msg) - return - - -# - No need to change any code below - - -@pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), -) -def test_mf6model(idx, dir): - # determine if running on CI infrastructure - is_CI = running_on_CI() - r_exe = None - if not is_CI: - if replace_exe is not None: - r_exe = replace_exe - # initialize testing framework - test = testing_framework() - - # build the models - test.build_mf6_models(build_model, idx, dir) - - # run the test model - if is_CI and not continuous_integration[idx]: - return - test.run_mf6(Simulation(dir, exfunc=eval_sub, exe_dict=r_exe, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation( - dir, exfunc=eval_sub, exe_dict=replace_exe, idxsim=idx - ) - test.run_mf6(sim) - - -# use python testmf6_csub_sub01.py --mf2005 mf2005devdbl -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +@pytest.mark.parametrize("name", ex) +def test_mf6model(name, function_tmpdir, targets): + test = TestFramework() + test.build(build_model, 0, str(function_tmpdir)) + test.run( + TestSimulation(name=name, exe_dict=targets, exfunc=eval_sub, idxsim=0), + str(function_tmpdir), + ) diff --git a/autotest/test_gwf_csub_inelastic.py b/autotest/test_gwf_csub_inelastic.py index c83b33fae4a..9d4e40575b7 100644 --- a/autotest/test_gwf_csub_inelastic.py +++ b/autotest/test_gwf_csub_inelastic.py @@ -1,42 +1,14 @@ import os +import flopy import numpy as np import pytest - -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import running_on_CI, testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation paktest = "csub" budtol = 1e-2 - ex = ["csub_de01a"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) -ddir = "data" - -updatemat = [None, True] -# run all examples on Travis -continuous_integration = [True for idx in range(len(exdirs))] - -# set replace_exe to None to use default executable -replace_exe = None # static model data # spatial discretization @@ -280,50 +252,18 @@ def eval_void(sim): sim.success = True print(" " + msg) - return - -# - No need to change any code below +@pytest.mark.slow @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir): - # determine if running on CI infrastructure - is_CI = running_on_CI() - r_exe = None - if not is_CI: - if replace_exe is not None: - r_exe = replace_exe - - # initialize testing framework - test = testing_framework() - - # build the models - test.build_mf6_models(build_model, idx, dir) - - # run the test model - if is_CI and not continuous_integration[idx]: - return - - test.run_mf6(Simulation(dir, exfunc=eval_void, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_void, idxsim=idx) - test.run_mf6(sim) - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(idx, name, function_tmpdir, targets): + test = TestFramework() + test.build(build_model, idx, str(function_tmpdir)) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_void, idxsim=idx + ), + str(function_tmpdir), + ) diff --git a/autotest/test_gwf_csub_ndb01_nr.py b/autotest/test_gwf_csub_ndb01_nr.py index 9155a1d0260..0360ca612ea 100644 --- a/autotest/test_gwf_csub_ndb01_nr.py +++ b/autotest/test_gwf_csub_ndb01_nr.py @@ -1,26 +1,11 @@ import os +import flopy import numpy as np import pytest - -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import running_on_CI, testing_framework -from simulation import Simulation +from framework import TestFramework +from modflow_devtools.misc import is_in_ci +from simulation import TestSimulation ex = ( "csub_ndb01a", @@ -32,9 +17,6 @@ "csub_ndb01g", "csub_ndb01h", ) -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) newtons = ( True, False, @@ -65,12 +47,6 @@ False, False, ) - -ddir = "data" - -# set replace_exe to None to use default executable -replace_exe = None - htol = None dtol = 1e-3 budtol = 0.01 @@ -403,54 +379,21 @@ def eval_comp(sim): sim.success = True print(" " + msg) - return - - -# - No need to change any code below - @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir): - # determine if running on CI infrastructure - is_CI = running_on_CI() - r_exe = None - if not is_CI: - if replace_exe is not None: - r_exe = replace_exe - - # initialize testing framework - test = testing_framework() - - # build the model - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6( - Simulation( - dir, exfunc=eval_comp, exe_dict=r_exe, htol=htol, idxsim=idx - ) +def test_mf6model(idx, name, function_tmpdir, targets): + test = TestFramework() + test.build(build_model, idx, str(function_tmpdir)) + test.run( + TestSimulation( + name=name, + exe_dict=targets, + exfunc=eval_comp, + htol=htol, + idxsim=idx, + ), + str(function_tmpdir), ) - - -def main(): - # initialize testing framework - test = testing_framework() - - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation( - dir, exfunc=eval_comp, exe_dict=replace_exe, htol=htol, idxsim=idx - ) - test.run_mf6(sim) - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() diff --git a/autotest/test_gwf_csub_sk01.py b/autotest/test_gwf_csub_sk01.py index 168e941d93b..c53ec9fc470 100644 --- a/autotest/test_gwf_csub_sk01.py +++ b/autotest/test_gwf_csub_sk01.py @@ -1,537 +1,485 @@ import os +from typing import List, NamedTuple +import flopy import numpy as np -import pytest - -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import running_on_CI, testing_framework -from simulation import Simulation - -ex = ["csub_sk01a", "csub_sk01b", "csub_sk01c"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) -cvopt = [None, None, None] -constantcv = [True, True, True] -ndelaybeds = [0, 0, 0] - -cmppths = ["mf6-regression", "mf6-regression", "mf6-regression"] -tops = [0.0, 0.0, 150.0] -newtons = [False, True, True] - -ddir = "data" - -## run all examples on Travis -# continuous_integration = [False for idx in range(len(exdirs))] -continuous_integration = [True, True, True] - -# set replace_exe to None to use default executable -replace_exe = None - -htol = [None, None, 0.3] -dtol = 1e-3 -budtol = 0.01 - -bud_lst = [ - "CSUB-CGELASTIC_IN", - "CSUB-CGELASTIC_OUT", - "CSUB-WATERCOMP_IN", - "CSUB-WATERCOMP_OUT", -] - -# static model data -nlay, nrow, ncol = 3, 10, 10 -nper = 31 -perlen = [1.0] + [365.2500000 for i in range(nper - 1)] -nstp = [1] + [6 for i in range(nper - 1)] -tsmult = [1.0] + [1.3 for i in range(nper - 1)] -steady = [True] + [False for i in range(nper - 1)] -delr, delc = 1000.0, 2000.0 -top = 0.0 -botm = [-100, -150.0, -350.0] -zthick = [top - botm[0], botm[0] - botm[1], botm[1] - botm[2]] -strt = 100.0 -hnoflo = 1e30 -hdry = -1e30 - -# calculate hk -hk1fact = 1.0 / zthick[1] -hk1 = np.ones((nrow, ncol), dtype=float) * 0.5 * hk1fact -hk1[0, :] = 1000.0 * hk1fact -hk1[-1, :] = 1000.0 * hk1fact -hk1[:, 0] = 1000.0 * hk1fact -hk1[:, -1] = 1000.0 * hk1fact -hk = [20.0, hk1, 5.0] - -# calculate vka -vka = [1e6, 7.5e-5, 1e6] - -# set rest of npf variables -laytyp = [1, 0, 0] -laytypu = [4, 0, 0] -sy = 0.0 # [0.1, 0., 0.] - -nouter, ninner = 500, 300 -hclose, rclose, relax = 1e-9, 1e-6, 1.0 - -tdis_rc = [] -for idx in range(nper): - tdis_rc.append((perlen[idx], nstp[idx], tsmult[idx])) - -# all cells are active -ib = 1 - -# chd data -c = [] -c6 = [] -ccol = [3, 4, 5, 6] -for j in ccol: - c.append([0, nrow - 1, j, strt, strt]) - c6.append([(0, nrow - 1, j), strt]) -cd = {0: c} -cd6 = {0: c6} -maxchd = len(cd[0]) - -# pumping well data -wr = [0, 0, 0, 0, 1, 1, 2, 2, 3, 3] -wc = [0, 1, 8, 9, 0, 9, 0, 9, 0, 0] -wrp = [2, 2, 3, 3] -wcp = [5, 6, 5, 6] -wq = [-14000.0, -8000.0, -5000.0, -3000.0] -d = [] -d6 = [] -for r, c, q in zip(wrp, wcp, wq): - d.append([2, r, c, q]) - d6.append([(2, r, c), q]) -wd = {1: d} -wd6 = {1: d6} -maxwel = len(wd[1]) - -# recharge data -q = 3000.0 / (delr * delc) -v = np.zeros((nrow, ncol), dtype=float) -for r, c in zip(wr, wc): - v[r, c] = q -rech = {0: v} - -# static ibc and sub data -sgm = 0.0 -sgs = 0.0 -omega = 1.0 -void = 0.82 -theta = void / (1.0 + void) -sw = 4.65120000e-10 * 9806.65000000 * theta - -# no delay bed data -nndb = 3 -lnd = [0, 1, 2] -hc = [botm[-1] for k in range(nlay)] -thicknd0 = [zthick[0], zthick[1], zthick[2]] -ccnd0 = [6e-6, 3e-6, 6e-6] -crnd0 = [6e-6, 3e-6, 6e-6] -sfv = [] -sfe = [] -for k in range(nlay): - sfv.append(ccnd0[k] * thicknd0[k]) - sfe.append(crnd0[k] * thicknd0[k]) - -# sub output data -ds15 = [0, 0, 0, 2052, 0, 0, 0, 0, 0, 0, 0, 0] -ds16 = [0, nper - 1, 0, nstp[-1] - 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1] - -# Build MODFLOW 6 files -def get_model(idx, ws): - name = ex[idx] - newton = newtons[idx] - newtonoptions = None - imsla = "CG" - if newton: - newtonoptions = "NEWTON" - imsla = "BICGSTAB" - - # build MODFLOW 6 files - sim = flopy.mf6.MFSimulation( - sim_name=name, version="mf6", exe_name="mf6", sim_ws=ws - ) - # create tdis package - tdis = flopy.mf6.ModflowTdis( - sim, time_units="DAYS", nper=nper, perioddata=tdis_rc - ) - - # create iterative model solution - ims = flopy.mf6.ModflowIms( - sim, - print_option="SUMMARY", - outer_dvclose=hclose, - outer_maximum=nouter, - under_relaxation="NONE", - inner_maximum=ninner, - inner_dvclose=hclose, - rcloserecord=rclose, - linear_acceleration=imsla, - scaling_method="NONE", - reordering_method="NONE", - relaxation_factor=relax, - ) - - # create gwf model - gwf = flopy.mf6.ModflowGwf( - sim, modelname=name, newtonoptions=newtonoptions - ) - - dis = flopy.mf6.ModflowGwfdis( - gwf, - nlay=nlay, - nrow=nrow, - ncol=ncol, - delr=delr, - delc=delc, - top=tops[idx], - botm=botm, - filename=f"{name}.dis", - ) - - # initial conditions - ic = flopy.mf6.ModflowGwfic(gwf, strt=strt, filename=f"{name}.ic") - - # node property flow - npf = flopy.mf6.ModflowGwfnpf( - gwf, - save_flows=False, - # dev_modflowusg_upstream_weighted_saturation=True, - icelltype=laytyp, - cvoptions=cvopt[idx], - k=hk, - k33=vka, - ) - # storage - sto = flopy.mf6.ModflowGwfsto( - gwf, - save_flows=False, - iconvert=laytyp, - ss=0.0, - sy=sy, - storagecoefficient=True, - steady_state={0: True}, - transient={1: True}, - ) +from framework import TestFramework +from pytest_cases import parametrize, parametrize_with_cases +from simulation import TestSimulation + + +class GwfCsubSkCases: + dtol: float = 1e-3 + budtol: float = 0.01 + bud_lst: List[str] = [ + "CSUB-CGELASTIC_IN", + "CSUB-CGELASTIC_OUT", + "CSUB-WATERCOMP_IN", + "CSUB-WATERCOMP_OUT", + ] - # recharge - rch = flopy.mf6.ModflowGwfrcha(gwf, readasarrays=True, recharge=rech) - - # wel file - wel = flopy.mf6.ModflowGwfwel( - gwf, - print_input=True, - print_flows=True, - maxbound=maxwel, - stress_period_data=wd6, - save_flows=False, + class Data(NamedTuple): + name: str + cvopt: str + constantcv: bool + ndelaybeds: int + top: float + newton: bool + htol: float + + @parametrize( + data=[ + Data("a", None, True, 0, 0, False, None), + Data("b", None, True, 0, 0, True, None), + Data("c", None, True, 0, 15, True, 0.3), + ] ) + def case_generator(self, function_tmpdir, data): + sim = self.get_model(data, function_tmpdir) + cmp = self.get_model(data, function_tmpdir / "mf6_regression") + return data, sim, cmp, self.eval_case + + def get_model(self, data, function_tmpdir): + name = data.name + newton = data.newton + newtonoptions = None + imsla = "CG" + if newton: + newtonoptions = "NEWTON" + imsla = "BICGSTAB" + + # static model data + nlay, nrow, ncol = 3, 10, 10 + nper = 31 + perlen = [1.0] + [365.2500000 for i in range(nper - 1)] + nstp = [1] + [6 for i in range(nper - 1)] + tsmult = [1.0] + [1.3 for i in range(nper - 1)] + steady = [True] + [False for i in range(nper - 1)] + delr, delc = 1000.0, 2000.0 + top = 0.0 + botm = [-100, -150.0, -350.0] + zthick = [top - botm[0], botm[0] - botm[1], botm[1] - botm[2]] + strt = 100.0 + hnoflo = 1e30 + hdry = -1e30 + + # calculate hk + hk1fact = 1.0 / zthick[1] + hk1 = np.ones((nrow, ncol), dtype=float) * 0.5 * hk1fact + hk1[0, :] = 1000.0 * hk1fact + hk1[-1, :] = 1000.0 * hk1fact + hk1[:, 0] = 1000.0 * hk1fact + hk1[:, -1] = 1000.0 * hk1fact + hk = [20.0, hk1, 5.0] + + # calculate vka + vka = [1e6, 7.5e-5, 1e6] + + # set rest of npf variables + laytyp = [1, 0, 0] + laytypu = [4, 0, 0] + sy = 0.0 # [0.1, 0., 0.] + + nouter, ninner = 500, 300 + hclose, rclose, relax = 1e-9, 1e-6, 1.0 + + tdis_rc = [] + for idx in range(nper): + tdis_rc.append((perlen[idx], nstp[idx], tsmult[idx])) + + # all cells are active + ib = 1 + + # chd data + c = [] + c6 = [] + ccol = [3, 4, 5, 6] + for j in ccol: + c.append([0, nrow - 1, j, strt, strt]) + c6.append([(0, nrow - 1, j), strt]) + cd = {0: c} + cd6 = {0: c6} + maxchd = len(cd[0]) + + # pumping well data + wr = [0, 0, 0, 0, 1, 1, 2, 2, 3, 3] + wc = [0, 1, 8, 9, 0, 9, 0, 9, 0, 0] + wrp = [2, 2, 3, 3] + wcp = [5, 6, 5, 6] + wq = [-14000.0, -8000.0, -5000.0, -3000.0] + d = [] + d6 = [] + for r, c, q in zip(wrp, wcp, wq): + d.append([2, r, c, q]) + d6.append([(2, r, c), q]) + wd = {1: d} + wd6 = {1: d6} + maxwel = len(wd[1]) + + # recharge data + q = 3000.0 / (delr * delc) + v = np.zeros((nrow, ncol), dtype=float) + for r, c in zip(wr, wc): + v[r, c] = q + rech = {0: v} + + # static ibc and sub data + sgm = 0.0 + sgs = 0.0 + omega = 1.0 + void = 0.82 + theta = void / (1.0 + void) + sw = 4.65120000e-10 * 9806.65000000 * theta + + # no delay bed data + nndb = 3 + lnd = [0, 1, 2] + hc = [botm[-1] for k in range(nlay)] + thicknd0 = [zthick[0], zthick[1], zthick[2]] + ccnd0 = [6e-6, 3e-6, 6e-6] + crnd0 = [6e-6, 3e-6, 6e-6] + sfv = [] + sfe = [] + for k in range(nlay): + sfv.append(ccnd0[k] * thicknd0[k]) + sfe.append(crnd0[k] * thicknd0[k]) + + # sub output data + ds15 = [0, 0, 0, 2052, 0, 0, 0, 0, 0, 0, 0, 0] + ds16 = [ + 0, + nper - 1, + 0, + nstp[-1] - 1, + 0, + 0, + 1, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + ] + + # build MODFLOW 6 files + sim = flopy.mf6.MFSimulation( + sim_name=name, version="mf6", exe_name="mf6", sim_ws=str(function_tmpdir) + ) + # create tdis package + tdis = flopy.mf6.ModflowTdis( + sim, time_units="DAYS", nper=nper, perioddata=tdis_rc + ) - # chd files - chd = flopy.mf6.modflow.mfgwfchd.ModflowGwfchd( - gwf, maxbound=maxchd, stress_period_data=cd6, save_flows=False - ) - # csub files - opth = f"{name}.csub.obs" - csub = flopy.mf6.ModflowGwfcsub( - gwf, - head_based=True, - save_flows=True, - ninterbeds=0, - cg_theta=theta, - cg_ske_cr=crnd0, - packagedata=None, - ) - obspos = [(0, 4, 4), (1, 4, 4), (2, 4, 4)] - obstype = ["compaction-cell", "csub-cell"] - obstag = ["tcomp", "csub"] - obsarr = [] - for iobs, cobs in enumerate(obstype): - for jobs, otup in enumerate(obspos): - otag = f"{obstag[iobs]}{jobs + 1}" - obsarr.append((otag, cobs, otup)) - - obsarr2 = [] - obstype2 = [ - "csub", - "inelastic-csub", - "elastic-csub", - "sk", - "ske", - "thickness", - "theta", - "interbed-compaction", - "inelastic-compaction", - "elastic-compaction", - "delay-flowtop", - "delay-flowbot", - ] - iobs = 0 - for cobs in obstype2: - iobs += 1 - otag = f"obs{iobs:03d}" - obsarr2.append((otag, cobs, (0,))) - - obstype3 = [ - "delay-preconstress", - "delay-head", - "delay-gstress", - "delay-estress", - "delay-compaction", - "delay-thickness", - "delay-theta", - ] - for cobs in obstype3: - iobs += 1 - otag = f"obs{iobs:03d}" - obsarr2.append((otag, cobs, (0,), (0,))) - - obsarr3 = [] - obstype4 = [ - "gstress-cell", - "estress-cell", - "thickness-cell", - "coarse-csub", - "wcomp-csub-cell", - "coarse-compaction", - "coarse-theta", - "coarse-thickness", - "csub-cell", - "ske-cell", - "sk-cell", - "theta-cell", - "compaction-cell", - ] - for cobs in obstype4: - iobs += 1 - otag = f"obs{iobs:03d}" - obsarr3.append((otag, cobs, obspos[-1])) - - orecarray = {} - orecarray["csub_obs.csv"] = obsarr - orecarray["interbed_obs.csv"] = obsarr2 - orecarray["coarse_cell_obs.csv"] = obsarr3 - - csub_obs_package = csub.obs.initialize( - filename=opth, digits=10, print_input=True, continuous=orecarray - ) + # create iterative model solution + ims = flopy.mf6.ModflowIms( + sim, + print_option="SUMMARY", + outer_dvclose=hclose, + outer_maximum=nouter, + under_relaxation="NONE", + inner_maximum=ninner, + inner_dvclose=hclose, + rcloserecord=rclose, + linear_acceleration=imsla, + scaling_method="NONE", + reordering_method="NONE", + relaxation_factor=relax, + ) - # output control - oc = flopy.mf6.ModflowGwfoc( - gwf, - budget_filerecord=f"{name}.cbc", - head_filerecord=f"{name}.hds", - headprintrecord=[("COLUMNS", 10, "WIDTH", 15, "DIGITS", 6, "GENERAL")], - saverecord=[("HEAD", "ALL"), ("BUDGET", "ALL")], - printrecord=[("HEAD", "LAST"), ("BUDGET", "ALL")], - ) + # create gwf model + gwf = flopy.mf6.ModflowGwf( + sim, modelname=name, newtonoptions=newtonoptions + ) - return sim + dis = flopy.mf6.ModflowGwfdis( + gwf, + nlay=nlay, + nrow=nrow, + ncol=ncol, + delr=delr, + delc=delc, + top=data.top, + botm=botm, + filename=f"{name}.dis", + ) + # initial conditions + ic = flopy.mf6.ModflowGwfic(gwf, strt=strt, filename=f"{name}.ic") + + # node property flow + npf = flopy.mf6.ModflowGwfnpf( + gwf, + save_flows=False, + # dev_modflowusg_upstream_weighted_saturation=True, + icelltype=laytyp, + cvoptions=data.cvopt, + k=hk, + k33=vka, + ) + # storage + sto = flopy.mf6.ModflowGwfsto( + gwf, + save_flows=False, + iconvert=laytyp, + ss=0.0, + sy=sy, + storagecoefficient=True, + steady_state={0: True}, + transient={1: True}, + ) -# SUB package problem 3 -def build_model(idx, dir): - ws = dir - sim = get_model(idx, ws) + # recharge + rch = flopy.mf6.ModflowGwfrcha(gwf, readasarrays=True, recharge=rech) + + # wel file + wel = flopy.mf6.ModflowGwfwel( + gwf, + print_input=True, + print_flows=True, + maxbound=maxwel, + stress_period_data=wd6, + save_flows=False, + ) - ws = os.path.join(dir, cmppths[idx]) - mc = get_model(idx, ws) + # chd files + chd = flopy.mf6.modflow.mfgwfchd.ModflowGwfchd( + gwf, maxbound=maxchd, stress_period_data=cd6, save_flows=False + ) + # csub files + opth = f"{name}.csub.obs" + csub = flopy.mf6.ModflowGwfcsub( + gwf, + head_based=True, + save_flows=True, + ninterbeds=0, + cg_theta=theta, + cg_ske_cr=crnd0, + packagedata=None, + ) + obspos = [(0, 4, 4), (1, 4, 4), (2, 4, 4)] + obstype = ["compaction-cell", "csub-cell"] + obstag = ["tcomp", "csub"] + obsarr = [] + for iobs, cobs in enumerate(obstype): + for jobs, otup in enumerate(obspos): + otag = f"{obstag[iobs]}{jobs + 1}" + obsarr.append((otag, cobs, otup)) + + obsarr2 = [] + obstype2 = [ + "csub", + "inelastic-csub", + "elastic-csub", + "sk", + "ske", + "thickness", + "theta", + "interbed-compaction", + "inelastic-compaction", + "elastic-compaction", + "delay-flowtop", + "delay-flowbot", + ] + iobs = 0 + for cobs in obstype2: + iobs += 1 + otag = f"obs{iobs:03d}" + obsarr2.append((otag, cobs, (0,))) + + obstype3 = [ + "delay-preconstress", + "delay-head", + "delay-gstress", + "delay-estress", + "delay-compaction", + "delay-thickness", + "delay-theta", + ] + for cobs in obstype3: + iobs += 1 + otag = f"obs{iobs:03d}" + obsarr2.append((otag, cobs, (0,), (0,))) + + obsarr3 = [] + obstype4 = [ + "gstress-cell", + "estress-cell", + "thickness-cell", + "coarse-csub", + "wcomp-csub-cell", + "coarse-compaction", + "coarse-theta", + "coarse-thickness", + "csub-cell", + "ske-cell", + "sk-cell", + "theta-cell", + "compaction-cell", + ] + for cobs in obstype4: + iobs += 1 + otag = f"obs{iobs:03d}" + obsarr3.append((otag, cobs, obspos[-1])) + + orecarray = {} + orecarray["csub_obs.csv"] = obsarr + orecarray["interbed_obs.csv"] = obsarr2 + orecarray["coarse_cell_obs.csv"] = obsarr3 + + csub_obs_package = csub.obs.initialize( + filename=opth, digits=10, print_input=True, continuous=orecarray + ) - return sim, mc + # output control + oc = flopy.mf6.ModflowGwfoc( + gwf, + budget_filerecord=f"{name}.cbc", + head_filerecord=f"{name}.hds", + headprintrecord=[ + ("COLUMNS", 10, "WIDTH", 15, "DIGITS", 6, "GENERAL") + ], + saverecord=[("HEAD", "ALL"), ("BUDGET", "ALL")], + printrecord=[("HEAD", "LAST"), ("BUDGET", "ALL")], + ) + return sim -def eval_comp(sim): - print("evaluating compaction...") + def eval_case(self, sim, data): + print("evaluating compaction...") - # MODFLOW 6 total compaction results - fpth = os.path.join(sim.simpath, "csub_obs.csv") - try: + # MODFLOW 6 total compaction results + fpth = os.path.join(sim.simpath, "csub_obs.csv") tc = np.genfromtxt(fpth, names=True, delimiter=",") - except: - assert False, f'could not load data from "{fpth}"' - # regression compaction results - cpth = cmppths[sim.idxsim] - fpth = os.path.join(sim.simpath, cpth, "csub_obs.csv") - try: + # regression compaction results + cpth = "mf6_regression" + fpth = os.path.join(sim.simpath, cpth, "csub_obs.csv") tc0 = np.genfromtxt(fpth, names=True, delimiter=",") - except: - assert False, f'could not load data from "{fpth}"' - # calculate maximum absolute error - diff = tc["TCOMP3"] - tc0["TCOMP3"] - diffmax = np.abs(diff).max() - msg = f"maximum absolute total-compaction difference ({diffmax}) " + # calculate maximum absolute error + diff = tc["TCOMP3"] - tc0["TCOMP3"] + diffmax = np.abs(diff).max() + msg = f"maximum absolute total-compaction difference ({diffmax}) " - # write summary - fpth = os.path.join( - sim.simpath, f"{os.path.basename(sim.name)}.comp.cmp.out" - ) - f = open(fpth, "w") - for i in range(diff.shape[0]): - line = f"{tc0['time'][i]:10.2g}" - line += f"{tc['TCOMP3'][i]:10.2g}" - line += f"{tc0['TCOMP3'][i]:10.2g}" - line += f"{diff[i]:10.2g}" - f.write(line + "\n") - f.close() - - if diffmax > dtol: - sim.success = False - msg += f"exceeds {dtol}" - assert diffmax < dtol, msg - else: - sim.success = True - print(" " + msg) - - # get results from listing file - fpth = os.path.join(sim.simpath, f"{os.path.basename(sim.name)}.lst") - budl = flopy.utils.Mf6ListBudget(fpth) - names = list(bud_lst) - d0 = budl.get_budget(names=names)[0] - dtype = d0.dtype - nbud = d0.shape[0] - - # get results from cbc file - cbc_bud = ["CSUB-CGELASTIC", "CSUB-WATERCOMP"] - d = np.recarray(nbud, dtype=dtype) - for key in bud_lst: - d[key] = 0.0 - fpth = os.path.join(sim.simpath, f"{os.path.basename(sim.name)}.cbc") - cobj = flopy.utils.CellBudgetFile(fpth, precision="double") - kk = cobj.get_kstpkper() - times = cobj.get_times() - for idx, (k, t) in enumerate(zip(kk, times)): - for text in cbc_bud: - qin = 0.0 - qout = 0.0 - v = cobj.get_data(kstpkper=k, text=text)[0] - for kk in range(v.shape[0]): - for ii in range(v.shape[1]): - for jj in range(v.shape[2]): - vv = v[kk, ii, jj] - if vv < 0.0: - qout -= vv - else: - qin += vv - d["totim"][idx] = t - d["time_step"][idx] = k[0] - d["stress_period"] = k[1] - key = f"{text}_IN" - d[key][idx] = qin - key = f"{text}_OUT" - d[key][idx] = qout - - diff = np.zeros((nbud, len(bud_lst)), dtype=float) - for idx, key in enumerate(bud_lst): - diff[:, idx] = d0[key] - d[key] - diffmax = np.abs(diff).max() - msg = f"maximum absolute total-budget difference ({diffmax}) " - - # write summary - fpth = os.path.join( - sim.simpath, f"{os.path.basename(sim.name)}.bud.cmp.out" - ) - f = open(fpth, "w") - for i in range(diff.shape[0]): - if i == 0: - line = f"{'TIME':>10s}" - for idx, key in enumerate(bud_lst): - line += f"{key + '_LST':>25s}" - line += f"{key + '_CBC':>25s}" - line += f"{key + '_DIF':>25s}" + # write summary + fpth = os.path.join( + sim.simpath, f"{os.path.basename(sim.name)}.comp.cmp.out" + ) + f = open(fpth, "w") + for i in range(diff.shape[0]): + line = f"{tc0['time'][i]:10.2g}" + line += f"{tc['TCOMP3'][i]:10.2g}" + line += f"{tc0['TCOMP3'][i]:10.2g}" + line += f"{diff[i]:10.2g}" f.write(line + "\n") - line = f"{d['totim'][i]:10g}" - for idx, key in enumerate(bud_lst): - line += f"{d0[key][i]:25g}" - line += f"{d[key][i]:25g}" - line += f"{diff[i, idx]:25g}" - f.write(line + "\n") - f.close() - - if diffmax > budtol: - sim.success = False - msg += f"exceeds {dtol}" - assert diffmax < dtol, msg - else: - sim.success = True - print(" " + msg) - - return - - -# - No need to change any code below - - -@pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), -) -def test_mf6model(idx, dir): - # determine if running on CI infrastructure - is_CI = running_on_CI() - r_exe = None - if not is_CI: - if replace_exe is not None: - r_exe = replace_exe - - # initialize testing framework - test = testing_framework() - - # build the models - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6( - Simulation( - dir, - exfunc=eval_comp, - htol=htol[idx], - idxsim=idx, - mf6_regression=True, + f.close() + + if diffmax > self.dtol: + sim.success = False + msg += f"exceeds {self.dtol}" + assert diffmax < self.dtol, msg + else: + sim.success = True + print(" " + msg) + + # get results from listing file + fpth = os.path.join(sim.simpath, f"{os.path.basename(sim.name)}.lst") + budl = flopy.utils.Mf6ListBudget(fpth) + names = list(self.bud_lst) + d0 = budl.get_budget(names=names)[0] + dtype = d0.dtype + nbud = d0.shape[0] + + # get results from cbc file + cbc_bud = ["CSUB-CGELASTIC", "CSUB-WATERCOMP"] + d = np.recarray(nbud, dtype=dtype) + for key in self.bud_lst: + d[key] = 0.0 + fpth = os.path.join(sim.simpath, f"{os.path.basename(sim.name)}.cbc") + cobj = flopy.utils.CellBudgetFile(fpth, precision="double") + kk = cobj.get_kstpkper() + times = cobj.get_times() + for idx, (k, t) in enumerate(zip(kk, times)): + for text in cbc_bud: + qin = 0.0 + qout = 0.0 + v = cobj.get_data(kstpkper=k, text=text)[0] + for kk in range(v.shape[0]): + for ii in range(v.shape[1]): + for jj in range(v.shape[2]): + vv = v[kk, ii, jj] + if vv < 0.0: + qout -= vv + else: + qin += vv + d["totim"][idx] = t + d["time_step"][idx] = k[0] + d["stress_period"] = k[1] + key = f"{text}_IN" + d[key][idx] = qin + key = f"{text}_OUT" + d[key][idx] = qout + + diff = np.zeros((nbud, len(self.bud_lst)), dtype=float) + for idx, key in enumerate(self.bud_lst): + diff[:, idx] = d0[key] - d[key] + diffmax = np.abs(diff).max() + msg = f"maximum absolute total-budget difference ({diffmax}) " + + # write summary + fpth = os.path.join( + sim.simpath, f"{os.path.basename(sim.name)}.bud.cmp.out" ) + f = open(fpth, "w") + for i in range(diff.shape[0]): + if i == 0: + line = f"{'TIME':>10s}" + for idx, key in enumerate(self.bud_lst): + line += f"{key + '_LST':>25s}" + line += f"{key + '_CBC':>25s}" + line += f"{key + '_DIF':>25s}" + f.write(line + "\n") + line = f"{d['totim'][i]:10g}" + for idx, key in enumerate(self.bud_lst): + line += f"{d0[key][i]:25g}" + line += f"{d[key][i]:25g}" + line += f"{diff[i, idx]:25g}" + f.write(line + "\n") + f.close() + + if diffmax > self.budtol: + sim.success = False + msg += f"exceeds {self.dtol}" + assert diffmax < self.dtol, msg + else: + sim.success = True + print(" " + msg) + + +@parametrize_with_cases( + "case", + cases=[ + GwfCsubSkCases, + ], +) +def test_mf6model(case, targets): + data, sim, cmp, evl = case + sim.write_simulation() + if cmp: + cmp.write_simulation() + test = TestSimulation( + name=data.name, + exe_dict=targets, + exfunc=evl, + idxsim=0, + mf6_regression=True, ) - - -def main(): - # initialize testing framework - test = testing_framework() - - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation( - dir, - exfunc=eval_comp, - htol=htol[idx], - idxsim=idx, - mf6_regression=True, - ) - test.run_mf6(sim) - - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() + test.set_model(sim.simulation_data.mfpath.get_sim_path(), testModel=False) + test.run() + test.compare() + evl(test, data) diff --git a/autotest/test_gwf_csub_sk02.py b/autotest/test_gwf_csub_sk02.py index d87765feed8..5ae15cf0b0f 100644 --- a/autotest/test_gwf_csub_sk02.py +++ b/autotest/test_gwf_csub_sk02.py @@ -1,52 +1,23 @@ import os +import flopy import numpy as np import pytest - -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import running_on_CI, testing_framework -from simulation import Simulation +from framework import TestFramework +from modflow_devtools.misc import is_in_ci +from simulation import TestSimulation ex = ["csub_sk02a", "csub_sk02b", "csub_sk02c", "csub_sk02d"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) -constantcv = [True for idx in range(len(exdirs))] - -cmppths = ["mf6-regression" for idx in range(len(exdirs))] -tops = [150.0 for idx in range(len(exdirs))] -newtons = [True for idx in range(len(exdirs))] +constantcv = [True for idx in range(len(ex))] +cmppths = ["mf6_regression" for idx in range(len(ex))] +tops = [150.0 for idx in range(len(ex))] +newtons = [True for idx in range(len(ex))] ump = [None, None, True, True] iump = [0, 0, 1, 1] -eslag = [True for idx in range(len(exdirs))] +eslag = [True for idx in range(len(ex))] icrcc = [0, 1, 0, 1] - -ddir = "data" - -## run all examples on Travis -continuous_integration = [True for idx in range(len(exdirs))] - -# set replace_exe to None to use default executable -replace_exe = None - -htol = [None for idx in range(len(exdirs))] +htol = [None for idx in range(len(ex))] dtol = 1e-3 - bud_lst = [ "CSUB-CGELASTIC_IN", "CSUB-CGELASTIC_OUT", @@ -481,71 +452,22 @@ def eval_comp(sim): sim.success = True print(" " + msg) - return - - -# - No need to change any code below - @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir): - # determine if running on CI infrastructure - is_CI = running_on_CI() - r_exe = None - if not is_CI: - if replace_exe is not None: - r_exe = replace_exe - - # initialize testing framework - test = testing_framework() - - # build the models - test.build_mf6_models(build_model, idx, dir) - - # run the test model - if is_CI and not continuous_integration[idx]: - return - test.run_mf6( - Simulation( - dir, +def test_mf6model(idx, name, function_tmpdir, targets): + test = TestFramework() + test.build(build_model, idx, str(function_tmpdir)) + test.run( + TestSimulation( + name=name, + exe_dict=targets, exfunc=eval_comp, - exe_dict=r_exe, htol=htol[idx], idxsim=idx, mf6_regression=True, - ) + ), + str(function_tmpdir), ) - - return - - -def main(): - # initialize testing framework - test = testing_framework() - - # build the models - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation( - dir, - exfunc=eval_comp, - exe_dict=replace_exe, - htol=htol[idx], - idxsim=idx, - mf6_regression=True, - ) - test.run_mf6(sim) - - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() diff --git a/autotest/test_gwf_csub_sk03.py b/autotest/test_gwf_csub_sk03.py index ce7e9e7685c..f886dba8a4b 100644 --- a/autotest/test_gwf_csub_sk03.py +++ b/autotest/test_gwf_csub_sk03.py @@ -1,50 +1,19 @@ import datetime import os +import flopy import numpy as np import pytest - -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import running_on_CI, testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation ex = ["csub_sk03a"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) -constantcv = [True for idx in range(len(exdirs))] - -cmppths = ["mf6-regression" for idx in range(len(exdirs))] -newtons = [True for idx in range(len(exdirs))] - +constantcv = [True for idx in range(len(ex))] +cmppths = ["mf6_regression" for idx in range(len(ex))] +newtons = [True for idx in range(len(ex))] icrcc = [0, 1, 0, 1] - -ddir = "data" - -## run all examples on Travis -continuous_integration = [True for idx in range(len(exdirs))] - -# set replace_exe to None to use default executable -replace_exe = None - -htol = [None for idx in range(len(exdirs))] +htol = [None for idx in range(len(ex))] dtol = 1e-3 - bud_lst = [ "CSUB-CGELASTIC_IN", "CSUB-CGELASTIC_OUT", @@ -628,70 +597,23 @@ def eval_comp(sim): sim.success = True print(" " + msg) - return - - -# - No need to change any code below - +@pytest.mark.slow @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir): - # determine if running on CI infrastructure - is_CI = running_on_CI() - r_exe = None - if not is_CI: - if replace_exe is not None: - r_exe = replace_exe - - # initialize testing framework - test = testing_framework() - - # build the models - test.build_mf6_models(build_model, idx, dir) - - # run the test model - if is_CI and not continuous_integration[idx]: - return - test.run_mf6( - Simulation( - dir, +def test_mf6model(idx, name, function_tmpdir, targets): + test = TestFramework() + test.build(build_model, idx, str(function_tmpdir)) + test.run( + TestSimulation( + name=name, + exe_dict=targets, exfunc=eval_comp, - exe_dict=r_exe, htol=htol[idx], idxsim=idx, mf6_regression=True, - ) + ), + str(function_tmpdir), ) - - return - - -def main(): - # initialize testing framework - test = testing_framework() - - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation( - dir, - exfunc=eval_comp, - exe_dict=replace_exe, - htol=htol[idx], - idxsim=idx, - mf6_regression=True, - ) - test.run_mf6(sim) - - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() diff --git a/autotest/test_gwf_csub_sk04_nr.py b/autotest/test_gwf_csub_sk04_nr.py index 5ba5545ee75..edb9f0409e8 100644 --- a/autotest/test_gwf_csub_sk04_nr.py +++ b/autotest/test_gwf_csub_sk04_nr.py @@ -1,26 +1,10 @@ import os +import flopy import numpy as np import pytest - -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import running_on_CI, testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation ex = ( "csub_sk04a", @@ -28,9 +12,6 @@ "csub_sk04c", "csub_sk04d", ) -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) newtons = (True, False, True, False) stress_lag = ( None, @@ -39,11 +20,6 @@ True, ) -ddir = "data" - -# set replace_exe to None to use default executable -replace_exe = None - htol = None dtol = 1e-3 budtol = 0.01 @@ -351,54 +327,21 @@ def eval_comp(sim): sim.success = True print(" " + msg) - return - - -# - No need to change any code below - @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir): - # determine if running on CI infrastructure - is_CI = running_on_CI() - r_exe = None - if not is_CI: - if replace_exe is not None: - r_exe = replace_exe - - # initialize testing framework - test = testing_framework() - - # build the model - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6( - Simulation( - dir, exfunc=eval_comp, exe_dict=r_exe, htol=htol, idxsim=idx - ) +def test_mf6model(idx, name, function_tmpdir, targets): + test = TestFramework() + test.build(build_model, idx, str(function_tmpdir)) + test.run( + TestSimulation( + name=name, + exe_dict=targets, + exfunc=eval_comp, + htol=htol, + idxsim=idx, + ), + str(function_tmpdir), ) - - -def main(): - # initialize testing framework - test = testing_framework() - - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation( - dir, exfunc=eval_comp, exe_dict=replace_exe, htol=htol, idxsim=idx - ) - test.run_mf6(sim) - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() diff --git a/autotest/test_gwf_csub_sub01.py b/autotest/test_gwf_csub_sub01.py index 5302a6ae736..4327c62e3fb 100644 --- a/autotest/test_gwf_csub_sub01.py +++ b/autotest/test_gwf_csub_sub01.py @@ -1,49 +1,17 @@ import os +import flopy import numpy as np import pytest - -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import running_on_CI, testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation paktest = "csub" budtol = 1e-2 - ex = ["csub_sub01a", "csub_sub01b"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) -ddir = "data" - compression_indices = [None, True] - ndcell = [19] * len(ex) -# run all examples on Travis -# continuous_integration = [True for idx in range(len(exdirs))] -# the delay bed problems only run on the development version of MODFLOW-2005 -# set travis to True when version 1.13.0 is released -continuous_integration = [True for idx in range(len(exdirs))] - -# set replace_exe to None to use default executable -replace_exe = None - # static model data # spatial discretization nlay, nrow, ncol = 1, 1, 3 @@ -254,7 +222,7 @@ def build_model(idx, dir): sim = get_model(idx, ws) # build MODFLOW-2005 files - ws = os.path.join(dir, "mf6-regression") + ws = os.path.join(dir, "mf6_regression") mc = get_model(idx, ws) return sim, mc @@ -271,7 +239,7 @@ def eval_sub(sim): assert False, f'could not load data from "{fpth}"' # comparison total compaction results - fpth = os.path.join(sim.simpath, "mf6-regression", "csub_obs.csv") + fpth = os.path.join(sim.simpath, "mf6_regression", "csub_obs.csv") try: tc0 = np.genfromtxt(fpth, names=True, delimiter=",") except: @@ -409,50 +377,21 @@ def cbc_compare(sim): sim.success = True print(" " + msg) - return - -# - No need to change any code below @pytest.mark.parametrize( - "idx, exdir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, exdir): - # initialize testing framework - test = testing_framework() - - # run the test model - test.build_mf6_models(build_model, idx, exdir) - - test.run_mf6( - Simulation( - exdir, +def test_mf6model(idx, name, function_tmpdir, targets): + test = TestFramework() + test.build(build_model, idx, str(function_tmpdir)) + test.run( + TestSimulation( + name=name, + exe_dict=targets, exfunc=eval_sub, idxsim=idx, mf6_regression=True, - ) + ), + str(function_tmpdir), ) - - -def main(): - # initialize testing framework - test = testing_framework() - - # run the test model - for idx, exdir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, exdir) - sim = Simulation( - exdir, - exfunc=eval_sub, - idxsim=idx, - mf6_regression=True, - ) - test.run_mf6(sim) - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() diff --git a/autotest/test_gwf_csub_sub01_adjmat.py b/autotest/test_gwf_csub_sub01_adjmat.py index de5c09ca06b..694799c26da 100644 --- a/autotest/test_gwf_csub_sub01_adjmat.py +++ b/autotest/test_gwf_csub_sub01_adjmat.py @@ -1,50 +1,19 @@ import os +import flopy import numpy as np import pytest - -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import running_on_CI, testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation paktest = "csub" budtol = 1e-2 compdir = "mf6" ex = ["csub_sub01_adj"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) -ddir = "data" - compression_indices = [None] - ndcell = [19] * len(ex) -# run all examples on Travis -# continuous_integration = [True for idx in range(len(exdirs))] -# the delay bed problems only run on the development version of MODFLOW-2005 -# set travis to True when version 1.13.0 is released -continuous_integration = [True for idx in range(len(exdirs))] - -# set replace_exe to None to use default executable -replace_exe = None - # static model data # spatial discretization nlay, nrow, ncol = 1, 1, 3 @@ -327,8 +296,8 @@ def eval_sub(sim): # calculate theta and porosity from interbed cell compaction calci = np.zeros((comp.shape[0]), dtype=dtype) - thickini = 1.0 / ndcell[sim.idxsim] - for n in range(ndcell[sim.idxsim]): + thickini = 1.0 / ndcell[0] + for n in range(ndcell[0]): tagc = f"DBCOMP{n + 1:02d}" tagb = f"DBTHICK{n + 1:02d}" tagp = f"DBPORO{n + 1:02d}" @@ -378,8 +347,6 @@ def eval_sub(sim): # compare budgets cbc_compare(sim) - return - # compare cbc and lst budgets def cbc_compare(sim): @@ -467,6 +434,8 @@ def cbc_compare(sim): f.write(line + "\n") f.close() + dtol = 1e-6 + if diffmax > budtol: sim.success = False msg += f"exceeds {dtol}" @@ -475,54 +444,12 @@ def cbc_compare(sim): sim.success = True print(" " + msg) - return - - -# - No need to change any code below - -@pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), -) -def test_mf6model(idx, dir): - # determine if running on CI infrastructure - is_CI = running_on_CI() - r_exe = None - if not is_CI: - if replace_exe is not None: - r_exe = replace_exe - - # initialize testing framework - test = testing_framework() - - # build the models - test.build_mf6_models(build_model, idx, dir) - - # run the test model - if is_CI and not continuous_integration[idx]: - return - test.run_mf6(Simulation(dir, exfunc=eval_sub, exe_dict=r_exe, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation( - dir, exfunc=eval_sub, exe_dict=replace_exe, idxsim=idx - ) - test.run_mf6(sim) - return - - -# use python testmf6_csub_sub01.py --mf2005 mf2005devdbl -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +@pytest.mark.parametrize("name", ex) +def test_mf6model(name, function_tmpdir, targets): + test = TestFramework() + test.build(build_model, 0, str(function_tmpdir)) + test.run( + TestSimulation(name=name, exe_dict=targets, exfunc=eval_sub, idxsim=0), + str(function_tmpdir), + ) diff --git a/autotest/test_gwf_csub_sub01_elastic.py b/autotest/test_gwf_csub_sub01_elastic.py index baec5b1bfe5..815910827b4 100644 --- a/autotest/test_gwf_csub_sub01_elastic.py +++ b/autotest/test_gwf_csub_sub01_elastic.py @@ -1,47 +1,18 @@ import os +import flopy import numpy as np import pytest - -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import running_on_CI, testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation cmppth = "mf6" - paktest = "csub" dtol = 1e-3 budtol = 1e-2 - ex = ["csub_sub01_elasa"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) -ddir = "data" - ndcell = [19] -# run all examples on Travis -continuous_integration = [True for idx in range(len(exdirs))] - -# set replace_exe to None to use default executable -replace_exe = None - # static model data # spatial discretization nlay, nrow, ncol = 1, 1, 3 @@ -371,51 +342,17 @@ def cbc_compare(sim): sim.success = True print(" " + msg) - return - - -# - No need to change any code below - @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir): - # determine if running on CI infrastructure - is_CI = running_on_CI() - r_exe = None - if not is_CI: - if replace_exe is not None: - r_exe = replace_exe - - # initialize testing framework - test = testing_framework() - - # build the models - test.build_mf6_models(build_model, idx, dir) - - # run the test model - if is_CI and not continuous_integration[idx]: - return - test.run_mf6(Simulation(dir, exfunc=eval_sub, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_sub, idxsim=idx) - test.run_mf6(sim) - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(idx, name, function_tmpdir, targets): + test = TestFramework() + test.build(build_model, idx, str(function_tmpdir)) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_sub, idxsim=idx + ), + str(function_tmpdir), + ) diff --git a/autotest/test_gwf_csub_sub01_pch.py b/autotest/test_gwf_csub_sub01_pch.py index 73632cf0efb..b4b12c81e5b 100644 --- a/autotest/test_gwf_csub_sub01_pch.py +++ b/autotest/test_gwf_csub_sub01_pch.py @@ -1,48 +1,17 @@ import os +import flopy import numpy as np import pytest - -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import running_on_CI, testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation paktest = "csub" budtol = 1e-2 - compdir = "mf6" ex = ["csub_sub01_pch"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) -ddir = "data" - ndcell = [19] * len(ex) -# run all examples on Travis -# continuous_integration = [True for idx in range(len(exdirs))] -# the delay bed problems only run on the development version of MODFLOW-2005 -# set travis to True when version 1.13.0 is released -continuous_integration = [True for idx in range(len(exdirs))] - -# set replace_exe to None to use default executable -replace_exe = None - # static model data # spatial discretization nlay, nrow, ncol = 1, 1, 3 @@ -304,8 +273,6 @@ def eval_sub(sim): # compare budgets cbc_compare(sim) - return - # compare cbc and lst budgets def cbc_compare(sim): @@ -393,6 +360,8 @@ def cbc_compare(sim): f.write(line + "\n") f.close() + dtol = 1e-6 + if diffmax > budtol: sim.success = False msg += f"exceeds {dtol}" @@ -402,50 +371,16 @@ def cbc_compare(sim): print(" " + msg) -# - No need to change any code below - - @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir): - # determine if running on CI infrastructure - is_CI = running_on_CI() - r_exe = None - if not is_CI: - if replace_exe is not None: - r_exe = replace_exe - - # initialize testing framework - test = testing_framework() - - # build the models - test.build_mf6_models(build_model, idx, dir) - - # run the test model - if is_CI and not continuous_integration[idx]: - return - test.run_mf6(Simulation(dir, exfunc=eval_sub, exe_dict=r_exe, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation( - dir, exfunc=eval_sub, exe_dict=replace_exe, idxsim=idx - ) - test.run_mf6(sim) - - -# use python testmf6_csub_sub01.py --mf2005 mf2005devdbl -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(idx, name, function_tmpdir, targets): + test = TestFramework() + test.build(build_model, idx, str(function_tmpdir)) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_sub, idxsim=idx + ), + str(function_tmpdir), + ) diff --git a/autotest/test_gwf_csub_sub02.py b/autotest/test_gwf_csub_sub02.py index 858bfb6d241..7596dfc1875 100644 --- a/autotest/test_gwf_csub_sub02.py +++ b/autotest/test_gwf_csub_sub02.py @@ -1,25 +1,9 @@ import os +import flopy import pytest - -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import running_on_CI, testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation ex = [ "csub_sub02a", @@ -28,12 +12,7 @@ "csub_sub02d", "csub_sub02e", ] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) -ddir = "data" -cmppth = "mf6-regression" - +cmppth = "mf6_regression" cg_ske = 1.14e-3 / (500.0 - 20.0) cg_S = cg_ske * (500.0 - 20.0) ss = [cg_S, cg_S, cg_ske, cg_ske, cg_S] @@ -41,12 +20,6 @@ cdelay = [False, True, False, True, True] ndelaycells = [None, 19, None, 19, 19] -# run all examples on Travis -continuous_integration = [True for e in ex] - -# set replace_exe to None to use default executable -replace_exe = None - # static model data nlay, nrow, ncol = 1, 1, 1 nper = 10 @@ -236,50 +209,14 @@ def build_model(idx, dir): return sim, mc -# - No need to change any code below - - @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir): - # determine if running on CI infrastructure - is_CI = running_on_CI() - - # initialize testing framework - test = testing_framework() - - # build the models - test.build_mf6_models(build_model, idx, dir) - - if is_CI and not continuous_integration[idx]: - return - - # run the test model - test.run_mf6(Simulation(dir, mf6_regression=True)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # run the test model - for dir in exdirs: - test.build_mf6_models(build_model, idx, dir) - sim = Simulation( - dir, - mf6_regression=True, - ) - test.run_mf6(sim) - - return - - -# use python test_gwf_csub_sub02.py --mf2005 mf2005devdbl -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(idx, name, function_tmpdir, targets): + test = TestFramework() + test.build(build_model, idx, str(function_tmpdir)) + test.run( + TestSimulation(name=name, exe_dict=targets, mf6_regression=True), + str(function_tmpdir), + ) diff --git a/autotest/test_gwf_csub_sub03.py b/autotest/test_gwf_csub_sub03.py index 67b3ab36e5b..dbb848c2b3c 100644 --- a/autotest/test_gwf_csub_sub03.py +++ b/autotest/test_gwf_csub_sub03.py @@ -1,50 +1,20 @@ import os +import flopy import numpy as np import pytest - -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import running_on_CI, testing_framework -from simulation import Simulation +from conftest import project_root_path +from framework import TestFramework +from simulation import TestSimulation ex = ["csub_sub03a", "csub_sub03b"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) -cmppth = "mf6-regression" - - +cmppth = "mf6_regression" cvopt = [None, None, None] constantcv = [True, True] ndelaybeds = [0, 2] ndelaycells = [None, 39] - -ddir = "data" - -## run all examples on Travis -continuous_integration = [True for e in ex] - -# set replace_exe to None to use default executable -replace_exe = None - htol = [None, None, None] dtol = 1e-3 - bud_lst = [ "CSUB-ELASTIC_IN", "CSUB-INELASTIC_IN", @@ -161,7 +131,9 @@ nz = [1, 1] dstart = [] for k in ldnd: - pth = os.path.join(ddir, f"ibc03_dstart{k + 1}.ref") + pth = str( + project_root_path / "autotest" / "data" / f"ibc03_dstart{k + 1}.ref" + ) v = np.genfromtxt(pth) dstart.append(v.copy()) @@ -461,62 +433,22 @@ def eval_comp(sim): sim.success = True print(" " + msg) - return - - -# - No need to change any code below - +@pytest.mark.slow @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir): - # determine if running on CI infrastructure - is_CI = running_on_CI() - - # initialize testing framework - test = testing_framework() - - # build the models - test.build_mf6_models(build_model, idx, dir) - - # run the test model - if is_CI and not continuous_integration[idx]: - return - test.run_mf6( - Simulation( - dir, +def test_mf6model(idx, name, function_tmpdir, targets): + test = TestFramework() + test.build(build_model, idx, str(function_tmpdir)) + test.run( + TestSimulation( + name=name, + exe_dict=targets, exfunc=eval_comp, htol=htol[idx], mf6_regression=True, - ) + ), + str(function_tmpdir), ) - - -def main(): - # initialize testing framework - test = testing_framework() - - # build the models - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation( - dir, - exfunc=eval_comp, - mf6_regression=True, - htol=htol[idx], - ) - test.run_mf6(sim) - - return - - -# use python test_gwf_csub_sub03.py --mf2005 mf2005devdbl -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() diff --git a/autotest/test_gwf_csub_subwt01.py b/autotest/test_gwf_csub_subwt01.py index b4fb615e35c..aad1e15e503 100644 --- a/autotest/test_gwf_csub_subwt01.py +++ b/autotest/test_gwf_csub_subwt01.py @@ -1,51 +1,21 @@ import os -import sys +import flopy import numpy as np import pytest - -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import running_on_CI, testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation ex = ["csub_subwt01a", "csub_subwt01b", "csub_subwt01c", "csub_subwt01d"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) -ddir = "data" -cmppth = "mf6-regression" - +cmppth = "mf6_regression" htol = [None for n in ex] dtol = 1e-3 budtol = 1e-2 - paktest = "csub" - ump = [None, True, None, True] ivoid = [0, 1, 0, 1] gs0 = [0.0, 0.0, 1700.0, 1700.0] -# set travis to True when version 1.13.0 is released -continuous_integration = [True for n in ex] - -# set replace_exe to None to use default executable -replace_exe = None - # temporal discretization nper = 3 perlen = [1.0, 21915.0, 21915.0] @@ -432,67 +402,22 @@ def cbc_compare(sim): sim.success = True print(" " + msg) - return - - -# - No need to change any code below - +@pytest.mark.slow @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir): - # determine if running on CI infrastructure - is_CI = running_on_CI() - r_exe = None - if not is_CI: - if replace_exe is not None: - r_exe = replace_exe - - # initialize testing framework - test = testing_framework() - - # build the models - test.build_mf6_models(build_model, idx, dir) - - # run the test model - if is_CI and not continuous_integration[idx]: - return - test.run_mf6( - Simulation( - dir, - exe_dict=r_exe, +def test_mf6model(idx, name, function_tmpdir, targets): + test = TestFramework() + test.build(build_model, idx, str(function_tmpdir)) + test.run( + TestSimulation( + name=name, + exe_dict=targets, exfunc=eval_comp, htol=htol[idx], mf6_regression=True, - ) + ), + str(function_tmpdir), ) - - -def main(): - # initialize testing framework - test = testing_framework() - - # build the models - # run the test model - for dir in exdirs: - test.build_mf6_models(build_model, idx, dir) - sim = Simulation( - dir, - exe_dict=replace_exe, - exfunc=eval_comp, - htol=htol[idx], - mf6_regression=True, - ) - test.run_mf6(sim) - - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() diff --git a/autotest/test_gwf_csub_subwt02.py b/autotest/test_gwf_csub_subwt02.py index 399390f0bdb..bf9068e4172 100644 --- a/autotest/test_gwf_csub_subwt02.py +++ b/autotest/test_gwf_csub_subwt02.py @@ -1,53 +1,25 @@ import os +import flopy import numpy as np import pytest - -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import running_on_CI, testing_framework -from simulation import Simulation +from conftest import project_root_path +from framework import TestFramework +from simulation import TestSimulation ex = ["csub_subwt02a", "csub_subwt02b", "csub_subwt02c", "csub_subwt02d"] timeseries = [True, False, True, False] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) -ddir = "data" -cmppth = "mf6-regression" - +cmppth = "mf6_regression" htol = [None, None, None, None] dtol = 1e-3 budtol = 1e-2 - paktest = "csub" - ump = [None, True, None, True] ivoid = [0, 1, 0, 1] gs0 = [0.0, 0.0, 1700.0, 1700.0] -# set travis to True when version 1.13.0 is released -continuous_integration = [True, True, True, True] - -# set replace_exe to None to use default executable -replace_exe = None - # static model data -pth = os.path.join(ddir, "ibc01_ibound.ref") +pth = str(project_root_path / "autotest" / "data" / "ibc01_ibound.ref") ib0 = np.genfromtxt(pth) # temporal discretization @@ -598,67 +570,22 @@ def cbc_compare(sim): sim.success = True print(" " + msg) - return - - -# - No need to change any code below - +@pytest.mark.slow @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir): - # determine if running on CI infrastructure - is_CI = running_on_CI() - r_exe = None - if not is_CI: - if replace_exe is not None: - r_exe = replace_exe - - # initialize testing framework - test = testing_framework() - - # build the models - test.build_mf6_models(build_model, idx, dir) - - # run the test model - if is_CI and not continuous_integration[idx]: - return - test.run_mf6( - Simulation( - dir, - exe_dict=r_exe, +def test_mf6model(idx, name, function_tmpdir, targets): + test = TestFramework() + test.build(build_model, idx, str(function_tmpdir)) + test.run( + TestSimulation( + name=name, + exe_dict=targets, exfunc=eval_comp, htol=htol[idx], mf6_regression=True, - ) + ), + str(function_tmpdir), ) - - -def main(): - # initialize testing framework - test = testing_framework() - - # build the models - # run the test model - for dir in exdirs: - test.build_mf6_models(build_model, idx, dir) - sim = Simulation( - dir, - exe_dict=replace_exe, - exfunc=eval_comp, - htol=htol[idx], - mf6_regression=True, - ) - test.run_mf6(sim) - - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() diff --git a/autotest/test_gwf_csub_subwt03.py b/autotest/test_gwf_csub_subwt03.py index 85649fa11fa..abe743ca558 100644 --- a/autotest/test_gwf_csub_subwt03.py +++ b/autotest/test_gwf_csub_subwt03.py @@ -1,53 +1,25 @@ import os -import sys +import flopy import numpy as np import pytest - -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import running_on_CI, testing_framework -from simulation import Simulation +from conftest import project_root_path +from framework import TestFramework +from simulation import TestSimulation ex = ["csub_subwt03a", "csub_subwt03b", "csub_subwt03c", "csub_subwt03d"] nex = len(ex) -exdirs = [os.path.join("temp", s) for s in ex] - -ddir = "data" cmppth = "mf6" - htol = None # 0.1 dtol = 1e-3 budtol = 1e-2 - paktest = "csub" - isnewton = 2 * [None, "NEWTON"] headbased = [True, True, False, False] delay = 4 * [False] -# set travis to True when version 1.13.0 is released -continuous_integration = [True for s in ex] - -# set replace_exe to None to use default executable -replace_exe = None - # static model data -pth = os.path.join(ddir, "ibc01_ibound.ref") +pth = str(project_root_path / "autotest" / "data" / "ibc01_ibound.ref") ib0 = np.genfromtxt(pth) # temporal discretization @@ -440,8 +412,6 @@ def eval_comp(sim): # compare budgets cbc_compare(sim) - return - # compare cbc and lst budgets def cbc_compare(sim): @@ -538,64 +508,23 @@ def cbc_compare(sim): sim.success = True print(" " + msg) - return - - -# - No need to change any code below - +@pytest.mark.slow @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir): - # determine if running on CI infrastructure - is_CI = running_on_CI() - r_exe = None - if not is_CI: - if replace_exe is not None: - r_exe = replace_exe - - # initialize testing framework - test = testing_framework() - - # build the models - test.build_mf6_models(build_model, idx, dir) - - # run the test model - if is_CI and not continuous_integration[idx]: - return - test.run_mf6( - Simulation( - dir, exe_dict=r_exe, exfunc=eval_comp, cmp_verbose=False, htol=htol - ) - ) - - -def main(): - # initialize testing framework - test = testing_framework() - - # build the models - # run the test model - for dir in exdirs: - test.build_mf6_models(build_model, idx, dir) - sim = Simulation( - dir, - exe_dict=replace_exe, +def test_mf6model(idx, name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, idx, ws) + test.run( + TestSimulation( + name=name, + exe_dict=targets, exfunc=eval_comp, cmp_verbose=False, htol=htol, - ) - test.run_mf6(sim) - - return - - -# use python testmf6_csub_subwt03.py -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() + ), + ws, + ) diff --git a/autotest/test_gwf_csub_wc01.py b/autotest/test_gwf_csub_wc01.py index de266afaade..1ec2bd2d69f 100644 --- a/autotest/test_gwf_csub_wc01.py +++ b/autotest/test_gwf_csub_wc01.py @@ -1,49 +1,21 @@ import os -import sys +import flopy import numpy as np import pytest - -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import running_on_CI, testing_framework -from simulation import Simulation +from conftest import project_root_path +from framework import TestFramework +from simulation import TestSimulation ex = ["csub_wc01a", "csub_wc02b"] -exdirs = [os.path.join("temp", s) for s in ex] - -ddir = "data" cmppth = "mf6" - dtol = 1e-3 budtol = 1e-2 - paktest = "csub" - isnewton = [None, "NEWTON"] -# set travis to True when version 1.13.0 is released -continuous_integration = [True for s in ex] - -# set replace_exe to None to use default executable -replace_exe = None - # static model data -pth = os.path.join(ddir, "ibc01_ibound.ref") +pth = str(project_root_path / "autotest" / "data" / "ibc01_ibound.ref") ib0 = np.genfromtxt(pth) # temporal discretization @@ -531,51 +503,15 @@ def cbc_compare(sim): print(" " + msg) -# - No need to change any code below - - +@pytest.mark.slow @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir): - # determine if running on CI infrastructure - is_CI = running_on_CI() - r_exe = None - if not is_CI: - if replace_exe is not None: - r_exe = replace_exe - - # initialize testing framework - test = testing_framework() - - # build the models - test.build_mf6_models(build_model, idx, dir) - - # run the test model - if is_CI and not continuous_integration[idx]: - return - test.run_mf6(Simulation(dir, exe_dict=r_exe, exfunc=eval_wcomp)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # build the models - # run the test model - for dir in exdirs: - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exe_dict=replace_exe, exfunc=eval_wcomp) - test.run_mf6(sim) - - return - - -# main -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(idx, name, function_tmpdir, targets): + test = TestFramework() + test.build(build_model, idx, str(function_tmpdir)) + test.run( + TestSimulation(name=name, exe_dict=targets, exfunc=eval_wcomp), + str(function_tmpdir), + ) diff --git a/autotest/test_gwf_csub_wtgeo.py b/autotest/test_gwf_csub_wtgeo.py index be59a6d0535..6f691516290 100644 --- a/autotest/test_gwf_csub_wtgeo.py +++ b/autotest/test_gwf_csub_wtgeo.py @@ -1,26 +1,10 @@ import os +import flopy import numpy as np import pytest - -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import running_on_CI, testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation ex = [ "csub_wtgeoa", @@ -31,31 +15,19 @@ "csub_wtgeof", "csub_wtgeog", ] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) -constantcv = [True for idx in range(len(exdirs))] - -cmppth = "mf6-regression" -compare = [True for idx in range(len(exdirs))] +constantcv = [True for idx in range(len(ex))] +cmppth = "mf6_regression" +compare = [True for idx in range(len(ex))] tops = [0.0, 0.0, 150.0, 0.0, 0.0, 150.0, 150.0] ump = [None, None, True, None, True, None, True] iump = [0, 0, 1, 0, 1, 0, 1] -eslag = [True for idx in range(len(exdirs) - 2)] + 2 * [False] +eslag = [True for idx in range(len(ex) - 2)] + 2 * [False] # eslag = [True, True, True, False, True, False, False] headformulation = [True, False, False, True, True, False, False] ndc = [None, None, None, 19, 19, 19, 19] delay = [False, False, False, True, True, True, True] # newton = ["", "", "", "", "", None, ""] -newton = ["NEWTON" for idx in range(len(exdirs))] - -ddir = "data" - -## run all examples on Travis -continuous_integration = [True for idx in range(len(exdirs))] - -# set replace_exe to None to use default executable -replace_exe = None +newton = ["NEWTON" for idx in range(len(ex))] htol = [None, None, None, 0.2, None, None, None] dtol = 1e-3 @@ -501,15 +473,8 @@ def get_model(idx, ws): def build_model(idx, dir): - - # build MODFLOW 6 files - ws = dir - sim = get_model(idx, ws) - - # build comparision files - ws = os.path.join(dir, cmppth) - mc = get_model(idx, ws) - + sim = get_model(idx, dir) # modflow6 files + mc = get_model(idx, os.path.join(dir, cmppth)) # build comparison files return sim, mc @@ -568,7 +533,6 @@ def eval_comp(sim): return -# compare cbc and lst budgets def cbc_compare(sim): print("evaluating cbc and budget...") # open cbc file @@ -663,71 +627,21 @@ def cbc_compare(sim): sim.success = True print(" " + msg) - return - - -# - No need to change any code below - -@pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), -) -def test_mf6model(idx, dir): - # determine if running on CI infrastructure - is_CI = running_on_CI() - r_exe = None - if not is_CI: - if replace_exe is not None: - r_exe = replace_exe - - # initialize testing framework - test = testing_framework() - - # build the models - test.build_mf6_models(build_model, idx, dir) - - # run the test model - if is_CI and not continuous_integration[idx]: - return - test.run_mf6( - Simulation( - dir, +@pytest.mark.slow +@pytest.mark.parametrize("idx, name", list(enumerate(ex))) +def test_mf6model(idx, name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, idx, ws) + test.run( + TestSimulation( + name=name, + exe_dict=targets, exfunc=eval_comp, - exe_dict=r_exe, htol=htol[idx], idxsim=idx, mf6_regression=True, - ) + ), + ws, ) - - return - - -def main(): - # initialize testing framework - test = testing_framework() - - # build the models - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation( - dir, - exfunc=eval_comp, - exe_dict=replace_exe, - htol=htol[idx], - idxsim=idx, - mf6_regression=True, - ) - test.run_mf6(sim) - - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() diff --git a/autotest/test_gwf_csub_zdisp01.py b/autotest/test_gwf_csub_zdisp01.py index d5ac7645d3b..f6ee90544e8 100644 --- a/autotest/test_gwf_csub_zdisp01.py +++ b/autotest/test_gwf_csub_zdisp01.py @@ -1,46 +1,17 @@ import os +import flopy import numpy as np import pytest - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -from framework import running_on_CI, testing_framework -from simulation import Simulation +from flopy.utils.compare import compare_heads +from framework import TestFramework +from simulation import TestSimulation ex = ["csub_zdisp01"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) - cmppth = "mfnwt" - -ddir = "data" - -## run all examples on Travis -continuous_integration = [True for idx in range(len(exdirs))] - -# set replace_exe to None to use default executable -replace_exe = None - -htol = [None for idx in range(len(exdirs))] +htol = [None for idx in range(len(ex))] dtol = 1e-3 budtol = 1e-2 - bud_lst = [ "STO-SS_IN", "STO-SS_OUT", @@ -421,15 +392,11 @@ def get_model(idx, dir): backflag=0, idroptol=0, ) - return sim, mc + sim.write_simulation() + mc.write_input() -def build_models(): - for idx, dir in enumerate(exdirs): - sim, mc = get_model(idx, dir) - sim.write_simulation() - mc.write_input() - return + return sim, mc def eval_zdisplacement(sim): @@ -561,7 +528,7 @@ def eval_zdisplacement(sim): sim.simpath, f"{os.path.basename(sim.name)}.z-displacement.bin.out", ) - success_tst = pymake.compare_heads( + success_tst = compare_heads( None, None, text=text1, @@ -581,66 +548,22 @@ def eval_zdisplacement(sim): sim.success = False assert success_tst, msg - return - -# - No need to change any code below +@pytest.mark.slow @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir): - # determine if running on CI infrastructure - is_CI = running_on_CI() - r_exe = None - if not is_CI: - if replace_exe is not None: - r_exe = replace_exe - - # initialize testing framework - test = testing_framework() - - # build the models - build_models() - - # run the test model - if is_CI and not continuous_integration[idx]: - return - test.run_mf6( - Simulation( - dir, +def test_mf6model(idx, name, function_tmpdir, targets): + test = TestFramework() + sim, mc = get_model(idx, str(function_tmpdir)) + test.run( + TestSimulation( + name=name, + exe_dict=targets, exfunc=eval_zdisplacement, - exe_dict=r_exe, htol=htol[idx], idxsim=idx, - ) + ), + str(function_tmpdir), ) - - -def main(): - # initialize testing framework - test = testing_framework() - - # build the models - build_models() - - # run the test model - for idx, dir in enumerate(exdirs): - sim = Simulation( - dir, - exfunc=eval_zdisplacement, - exe_dict=replace_exe, - htol=htol[idx], - idxsim=idx, - ) - test.run_mf6(sim) - - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() diff --git a/autotest/test_gwf_disu01.py b/autotest/test_gwf_disu.py similarity index 61% rename from autotest/test_gwf_disu01.py rename to autotest/test_gwf_disu.py index 6adc1d59f77..85880b95d82 100644 --- a/autotest/test_gwf_disu01.py +++ b/autotest/test_gwf_disu.py @@ -1,54 +1,13 @@ -""" -MODFLOW 6 Autotest -Test to make sure that disu is working correctly - -""" - import os -import shutil -import subprocess +import flopy import numpy as np +from flopy.utils.gridutil import get_disu_kwargs -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -import targets -from framework import set_teardown_test - -mf6_exe = os.path.abspath(targets.target_dict["mf6"]) -testname = "gwf_disu01" -testdir = os.path.join("temp", testname) -everything_was_successful = True - -teardown_test = set_teardown_test() - - -def run_mf6(argv, ws): - buff = [] - proc = subprocess.Popen( - argv, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=ws - ) - result, error = proc.communicate() - if result is not None: - c = result.decode("utf-8") - c = c.rstrip("\r\n") - print(f"{c}") - buff.append(c) - - return proc.returncode, buff - - -def test_disu_simple(): - from disu_util import get_disu_kwargs +def test_disu_simple(tmpdir, targets): + mf6 = targets["mf6"] name = "disu01a" - ws = f"{testdir}_{name}" nlay = 3 nrow = 3 ncol = 3 @@ -58,7 +17,7 @@ def test_disu_simple(): botm = [-10, -20, -30] disukwargs = get_disu_kwargs(nlay, nrow, ncol, delr, delc, top, botm) sim = flopy.mf6.MFSimulation( - sim_name=name, version="mf6", exe_name=mf6_exe, sim_ws=ws + sim_name=name, version="mf6", exe_name=mf6, sim_ws=str(tmpdir) ) tdis = flopy.mf6.ModflowTdis(sim) gwf = flopy.mf6.ModflowGwf(sim, modelname=name) @@ -70,16 +29,11 @@ def test_disu_simple(): chd = flopy.mf6.modflow.mfgwfchd.ModflowGwfchd(gwf, stress_period_data=spd) sim.write_simulation() sim.run_simulation() - if teardown_test: - shutil.rmtree(ws, ignore_errors=True) - return - -def test_disu_idomain_simple(): - from disu_util import get_disu_kwargs +def test_disu_idomain_simple(tmpdir, targets): + mf6 = targets["mf6"] name = "disu01b" - ws = f"{testdir}_{name}" nlay = 3 nrow = 3 ncol = 3 @@ -92,7 +46,7 @@ def test_disu_idomain_simple(): disukwargs = get_disu_kwargs(nlay, nrow, ncol, delr, delc, top, botm) disukwargs["idomain"] = idomain sim = flopy.mf6.MFSimulation( - sim_name=name, version="mf6", exe_name=mf6_exe, sim_ws=ws + sim_name=name, version="mf6", exe_name=mf6, sim_ws=str(tmpdir) ) tdis = flopy.mf6.ModflowTdis(sim) gwf = flopy.mf6.ModflowGwf(sim, modelname=name, save_flows=True) @@ -112,7 +66,7 @@ def test_disu_idomain_simple(): sim.run_simulation() # check binary grid file - fname = os.path.join(ws, name + ".disu.grb") + fname = os.path.join(str(tmpdir), name + ".disu.grb") grbobj = flopy.mf6.utils.MfGrdFile(fname) nodes = grbobj._datadict["NODES"] ia = grbobj._datadict["IA"] @@ -125,24 +79,13 @@ def test_disu_idomain_simple(): assert ja.shape[0] == 126, "ja should have size of 126" # load head array and ensure nodata value in second cell - fname = os.path.join(ws, name + ".hds") + fname = os.path.join(str(tmpdir), name + ".hds") hdsobj = flopy.utils.HeadFile(fname) head = hdsobj.get_alldata().flatten() assert head[1] == 1.0e30 # load flowja to make sure it is the right size - fname = os.path.join(ws, name + ".bud") + fname = os.path.join(str(tmpdir), name + ".bud") budobj = flopy.utils.CellBudgetFile(fname, precision="double") flowja = budobj.get_data(text="FLOW-JA-FACE")[0].flatten() assert flowja.shape[0] == 126 - if teardown_test: - shutil.rmtree(ws, ignore_errors=True) - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - test_disu_simple() - test_disu_idomain_simple() diff --git a/autotest/test_gwf_disv_uzf.py b/autotest/test_gwf_disv_uzf.py index a4dcd4d8be1..cf89372267d 100644 --- a/autotest/test_gwf_disv_uzf.py +++ b/autotest/test_gwf_disv_uzf.py @@ -9,29 +9,15 @@ """ import os -import pytest -import sys -import numpy as np - -try: - import flopy - import flopy.utils.cvfdutil -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - - -from framework import testing_framework -from simulation import Simulation +import flopy +import flopy.utils.cvfdutil +import numpy as np +import pytest +from framework import TestFramework +from simulation import TestSimulation ex = ["disv_with_uzf"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) - nlay = 5 nper = 5 perlen = [10] * 5 @@ -39,10 +25,8 @@ tsmult = len(perlen) * [1.0] botm = [20.0, 15.0, 10.0, 5.0, 0.0] strt = 20 - nouter, ninner = 100, 300 hclose, rclose, relax = 1e-9, 1e-3, 0.97 - ghb_ids = [] @@ -332,18 +316,14 @@ def build_model(idx, dir): def eval_model(sim): print("evaluating model...") - idx = sim.idxsim - name = ex[idx] - ws = os.path.join("temp", name) - # Next, get the binary printed heads - fpth = os.path.join(ws, name + ".hds") + fpth = os.path.join(sim.simpath, sim.name + ".hds") hobj = flopy.utils.HeadFile(fpth, precision="double") hds = hobj.get_alldata() hds = hds.reshape((np.sum(nstp), 5, 10, 10)) # Get the MF6 cell-by-cell fluxes - bpth = os.path.join(ws, name + ".cbc") + bpth = os.path.join(sim.simpath, sim.name + ".cbc") bobj = flopy.utils.CellBudgetFile(bpth, precision="double") bobj.get_unique_record_names() # ' STO-SS' @@ -361,7 +341,7 @@ def eval_model(sim): gwet = gwetv.reshape((np.sum(nstp), 5, 10, 10)) # Also retrieve the binary UZET output - uzpth = os.path.join(ws, name + ".uzf.bud") + uzpth = os.path.join(sim.simpath, sim.name + ".uzf.bud") uzobj = flopy.utils.CellBudgetFile(uzpth, precision="double") uzobj.get_unique_record_names() # b' FLOW-JA-FACE', @@ -473,36 +453,14 @@ def eval_model(sim): print("Finished running checks") -# - No need to change any code below -@pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), -) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the model - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_model, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_model, idxsim=idx) - test.run_mf6(sim) - - -if __name__ == "__main__": - # print message - print("standalone run of {}".format(os.path.basename(__file__))) - - # run main routine - main() +@pytest.mark.slow +@pytest.mark.parametrize("name", ex) +def test_mf6model(name, function_tmpdir, targets): + test = TestFramework() + test.build(build_model, 0, str(function_tmpdir)) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_model, idxsim=0 + ), + str(function_tmpdir), + ) diff --git a/autotest/test_gwf_drn_ddrn01.py b/autotest/test_gwf_drn_ddrn01.py index 11a7ec66fa3..7814053e45c 100644 --- a/autotest/test_gwf_drn_ddrn01.py +++ b/autotest/test_gwf_drn_ddrn01.py @@ -1,44 +1,17 @@ import os +import flopy import numpy as np import pytest - -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import running_on_CI, testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation paktest = "drn" budtol = 1e-2 - ex = ["drn_ddrn01a", "drn_ddrn01b"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) ddir = "data" - newton = [False, True] -# run all examples on Travis -continuous_integration = [True for idx in range(len(exdirs))] - -# set replace_exe to None to use default executable -replace_exe = None - # static model data # spatial discretization nlay, nrow, ncol = 1, 1, 100 @@ -218,54 +191,16 @@ def drain_smoothing(xdiff, xrange, newton=False): return f -# - No need to change any code below - - @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir): - # determine if running on CI infrastructure - is_CI = running_on_CI() - r_exe = None - if not is_CI: - if replace_exe is not None: - r_exe = replace_exe - - # initialize testing framework - test = testing_framework() - - # build the models - test.build_mf6_models(build_model, idx, dir) - - # run the test model - if is_CI and not continuous_integration[idx]: - return - test.run_mf6( - Simulation(dir, exfunc=eval_disch, exe_dict=r_exe, idxsim=idx) +def test_mf6model(idx, name, function_tmpdir, targets): + test = TestFramework() + test.build(build_model, idx, str(function_tmpdir)) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_disch, idxsim=idx + ), + str(function_tmpdir), ) - - -def main(): - # initialize testing framework - test = testing_framework() - - # build the models - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation( - dir, exfunc=eval_disch, exe_dict=replace_exe, idxsim=idx - ) - test.run_mf6(sim) - return - - -# use python testmf6_drn_ddrn01.py -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() diff --git a/autotest/test_gwf_drn_ddrn02.py b/autotest/test_gwf_drn_ddrn02.py index 1e1c61f9f01..6f4cca16c45 100644 --- a/autotest/test_gwf_drn_ddrn02.py +++ b/autotest/test_gwf_drn_ddrn02.py @@ -1,41 +1,14 @@ import os +import flopy import numpy as np import pytest - -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import running_on_CI, testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation paktest = "drn" budtol = 1e-2 - ex = ["drn_ddrn02a"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) -ddir = "data" - -# run all examples on Travis -continuous_integration = [True for idx in range(len(exdirs))] - -# set replace_exe to None to use default executable -replace_exe = None # static model data # spatial discretization @@ -198,57 +171,20 @@ def eval_disch(sim): sim.success = True print(" " + msg) - return - - -# - No need to change any code below - @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir): - # determine if running on CI infrastructure - is_CI = running_on_CI() - r_exe = None - if not is_CI: - if replace_exe is not None: - r_exe = replace_exe - - # initialize testing framework - test = testing_framework() - - # build the models - test.build_mf6_models(build_model, idx, dir) - - # run the test model - if is_CI and not continuous_integration[idx]: - return - test.run_mf6( - Simulation(dir, exfunc=eval_disch, exe_dict=r_exe, idxsim=idx) +def test_mf6model(idx, name, function_tmpdir, targets): + test = TestFramework() + test.build(build_model, idx, str(function_tmpdir)) + test.run( + TestSimulation( + name=str(function_tmpdir), + exe_dict=targets, + exfunc=eval_disch, + idxsim=idx, + ), + str(function_tmpdir), ) - - -def main(): - # initialize testing framework - test = testing_framework() - - # build the models - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation( - dir, exfunc=eval_disch, exe_dict=replace_exe, idxsim=idx - ) - test.run_mf6(sim) - return - - -# use python testmf6_drn_ddrn01.py -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() diff --git a/autotest/test_gwf_errors.py b/autotest/test_gwf_errors.py index fe688406f16..c7ba06ea3b2 100644 --- a/autotest/test_gwf_errors.py +++ b/autotest/test_gwf_errors.py @@ -6,31 +6,12 @@ """ -import os -import shutil import subprocess +import flopy import numpy as np import pytest - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -import targets -from framework import set_teardown_test - -mf6_exe = os.path.abspath(targets.target_dict["mf6"]) -testname = "gwf_errors" -testdir = os.path.join("temp", testname) -os.makedirs(testdir, exist_ok=True) -everything_was_successful = True - -teardown_test = set_teardown_test() +from flopy.utils.gridutil import get_disu_kwargs def run_mf6(argv, ws): @@ -48,11 +29,9 @@ def run_mf6(argv, ws): return proc.returncode, buff -def run_mf6_error(ws, err_str_list): - returncode, buff = run_mf6([mf6_exe], ws) +def run_mf6_error(ws, exe, err_str_list): + returncode, buff = run_mf6([exe], ws) msg = "mf terminated with error" - if teardown_test: - shutil.rmtree(ws, ignore_errors=True) if returncode != 0: if not isinstance(err_str_list, list): err_str_list = list(err_str_list) @@ -68,6 +47,7 @@ def run_mf6_error(ws, err_str_list): def get_minimal_gwf_simulation( ws, + exe, name="test", simkwargs=None, simnamefilekwargs=None, @@ -111,7 +91,7 @@ def get_minimal_gwf_simulation( 0: [[(0, 0, 0), 0], [(0, nr - 1, nc - 1), 1]] } sim = flopy.mf6.MFSimulation( - sim_name=name, version="mf6", exe_name=mf6_exe, sim_ws=ws, **simkwargs + sim_name=name, version="mf6", exe_name=exe, sim_ws=ws, **simkwargs ) if simnamefilekwargs is not None: for k in simnamefilekwargs: @@ -129,47 +109,50 @@ def get_minimal_gwf_simulation( return sim -def test_simple_model_success(): +def test_simple_model_success(function_tmpdir, targets): + mf6 = targets.mf6 + # test a simple model to make sure it runs and terminates correctly - ws = f"{testdir}_sim0" - sim = get_minimal_gwf_simulation(ws) + sim = get_minimal_gwf_simulation(str(function_tmpdir), mf6) sim.write_simulation() - returncode, buff = run_mf6([mf6_exe], ws) + returncode, buff = run_mf6([mf6], str(function_tmpdir)) assert returncode == 0, "mf6 failed for simple model." final_message = "Normal termination of simulation." failure_message = f'mf6 did not terminate with "{final_message}"' assert final_message in buff[-1], failure_message - if teardown_test: - shutil.rmtree(ws, ignore_errors=True) - return -def test_empty_folder(): +def test_empty_folder(function_tmpdir, targets): + mf6 = targets.mf6 with pytest.raises(RuntimeError): # make sure mf6 fails when there is no simulation name file err_str = "mf6: mfsim.nam is not present in working directory." - run_mf6_error(testdir, err_str) + run_mf6_error(str(function_tmpdir), mf6, err_str) -def test_sim_errors(): +def test_sim_errors(function_tmpdir, targets): + mf6 = targets.mf6 + with pytest.raises(RuntimeError): # verify that the correct number of errors are reported - ws = f"{testdir}_sim1" chdkwargs = {} chdkwargs["stress_period_data"] = { 0: [[(0, 0, 0), 0.0] for i in range(10)] } - sim = get_minimal_gwf_simulation(ws, chdkwargs=chdkwargs) + sim = get_minimal_gwf_simulation( + str(function_tmpdir), exe=mf6, chdkwargs=chdkwargs + ) sim.write_simulation() err_str = ["1. Cell is already a constant head ((1,1,1))."] - run_mf6_error(ws, err_str) + run_mf6_error(str(function_tmpdir), mf6, err_str) -def test_sim_maxerrors(): +def test_sim_maxerrors(function_tmpdir, targets): + mf6 = targets.mf6 + with pytest.raises(RuntimeError): # verify that the maxerrors keyword gives the correct error output - ws = f"{testdir}_sim2" simnamefilekwargs = {} simnamefilekwargs["maxerrors"] = 5 chdkwargs = {} @@ -177,7 +160,10 @@ def test_sim_maxerrors(): 0: [[(0, 0, 0), 0.0] for i in range(10)] } sim = get_minimal_gwf_simulation( - ws, simnamefilekwargs=simnamefilekwargs, chdkwargs=chdkwargs + str(function_tmpdir), + exe=mf6, + simnamefilekwargs=simnamefilekwargs, + chdkwargs=chdkwargs, ) sim.write_simulation() err_str = [ @@ -186,14 +172,13 @@ def test_sim_maxerrors(): "UNIT ERROR REPORT:", "1. ERROR OCCURRED WHILE READING FILE 'test.chd'", ] - run_mf6_error(ws, err_str) + run_mf6_error(str(function_tmpdir), mf6, err_str) -def test_disu_errors(): - with pytest.raises(RuntimeError): - from disu_util import get_disu_kwargs +def test_disu_errors(function_tmpdir, targets): + mf6 = targets.mf6 - ws = f"{testdir}_sim3" + with pytest.raises(RuntimeError): disukwargs = get_disu_kwargs( 3, 3, 3, np.ones(3), np.ones(3), 0, [-1, -2, -3] ) @@ -202,7 +187,10 @@ def test_disu_errors(): top[9] = 2.0 bot[9] = 1.0 sim = get_minimal_gwf_simulation( - ws, disukwargs=disukwargs, chdkwargs={"stress_period_data": [[]]} + str(function_tmpdir), + exe=mf6, + disukwargs=disukwargs, + chdkwargs={"stress_period_data": [[]]}, ) sim.write_simulation() err_str = [ @@ -212,34 +200,41 @@ def test_disu_errors(): "UNIT ERROR REPORT:" "1. ERROR OCCURRED WHILE READING FILE './test.disu'", ] - run_mf6_error(ws, err_str) + run_mf6_error(str(function_tmpdir), mf6, err_str) + +def test_solver_fail(function_tmpdir, targets): + mf6 = targets.mf6 -def test_solver_fail(): with pytest.raises(RuntimeError): # test failed to converge - ws = f"{testdir}_sim4" imskwargs = {"inner_maximum": 1, "outer_maximum": 2} - sim = get_minimal_gwf_simulation(ws, imskwargs=imskwargs) + sim = get_minimal_gwf_simulation( + str(function_tmpdir), exe=mf6, imskwargs=imskwargs + ) sim.write_simulation() err_str = [ "Simulation convergence failure occurred 1 time(s).", "Premature termination of simulation.", ] - run_mf6_error(ws, err_str) + run_mf6_error(str(function_tmpdir), mf6, err_str) + +def test_fail_continue_success(function_tmpdir, targets): + mf6 = targets.mf6 -def test_fail_continue_success(): # test continue but failed to converge - ws = f"{testdir}_sim5" tdiskwargs = {"nper": 1, "perioddata": [(10.0, 10, 1.0)]} imskwargs = {"inner_maximum": 1, "outer_maximum": 2} sim = get_minimal_gwf_simulation( - ws, imskwargs=imskwargs, tdiskwargs=tdiskwargs + str(function_tmpdir), + exe=mf6, + imskwargs=imskwargs, + tdiskwargs=tdiskwargs, ) sim.name_file.continue_ = True sim.write_simulation() - returncode, buff = run_mf6([mf6_exe], ws) + returncode, buff = run_mf6([mf6], str(function_tmpdir)) assert returncode == 0, "mf6 failed for simple model." final_message = "Simulation convergence failure occurred 10 time(s)." @@ -249,21 +244,3 @@ def test_fail_continue_success(): final_message = "Normal termination of simulation." failure_message = f'mf6 did not terminate with "{final_message}"' assert final_message in buff[0], failure_message - - if teardown_test: - shutil.rmtree(ws, ignore_errors=True) - - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - test_empty_folder() - test_simple_model_success() - test_sim_errors() - test_sim_maxerrors() - test_disu_errors() - test_solver_fail() - test_fail_continue_success() diff --git a/autotest/test_gwf_evt01.py b/autotest/test_gwf_evt01.py index 0c435b49146..d90d6ccfed5 100644 --- a/autotest/test_gwf_evt01.py +++ b/autotest/test_gwf_evt01.py @@ -1,32 +1,12 @@ import os -import sys +import flopy import numpy as np import pytest - -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation ex = ["evt01"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) def build_model(idx, dir): @@ -196,39 +176,17 @@ def eval_model(sim): msg = f"{kper} {h} {sim_evt_rate} {cal_evt_rate}" assert np.allclose(sim_evt_rate, cal_evt_rate), msg - return - -# - No need to change any code below @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the model - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_model, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_model, idxsim=idx) - test.run_mf6(sim) - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(idx, name, function_tmpdir, targets): + test = TestFramework() + test.build(build_model, idx, str(function_tmpdir)) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_model, idxsim=idx + ), + str(function_tmpdir), + ) diff --git a/autotest/test_gwf_evt02.py b/autotest/test_gwf_evt02.py index 65d013c5e4c..ad1191eb7cc 100644 --- a/autotest/test_gwf_evt02.py +++ b/autotest/test_gwf_evt02.py @@ -1,35 +1,15 @@ import os -import sys +import flopy import numpy as np import pytest - -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation ex = ["evt02"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) -def build_model(idx, dir): +def build_model(idx, dir, exe): nlay, nrow, ncol = 1, 1, 3 chdheads = list(np.linspace(1, 100)) @@ -53,7 +33,7 @@ def build_model(idx, dir): # build MODFLOW 6 files ws = dir sim = flopy.mf6.MFSimulation( - sim_name=name, version="mf6", exe_name="mf6", sim_ws=ws + sim_name=name, version="mf6", exe_name=exe, sim_ws=ws ) # create tdis package tdis = flopy.mf6.ModflowTdis( @@ -178,39 +158,18 @@ def eval_model(sim): fpth = os.path.join(sim.simpath, "evt02.cbc") assert os.path.isfile(fpth), "model did not run with nseg=1 in EVT input" - return - -# - No need to change any code below @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the model - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_model, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_model, idxsim=idx) - test.run_mf6(sim) - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(idx, name, function_tmpdir, targets): + test = TestFramework() + mf6 = targets["mf6"] + test.build(lambda i, w: build_model(i, w, mf6), idx, str(function_tmpdir)) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_model, idxsim=idx + ), + str(function_tmpdir), + ) diff --git a/autotest/test_gwf_henry_nr.py b/autotest/test_gwf_henry_nr.py index 73c800532f9..c072f3f2b58 100644 --- a/autotest/test_gwf_henry_nr.py +++ b/autotest/test_gwf_henry_nr.py @@ -5,27 +5,14 @@ # and DRNs alternate and move up and down along the boundary to represent # the effects of tides on the aquifer. -import os - +import flopy import numpy as np import pytest - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - from conftest import should_compare -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation ex = ["gwf_henrynr01"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) # global model variables nlay = 20 @@ -78,8 +65,7 @@ def sinfunc(a, b, c, d, x): return a * np.sin(b * (x - c)) + d -def build_model(idx, dir): - +def build_model(idx, dir, exe): ws = dir name = ex[idx] @@ -103,7 +89,7 @@ def build_model(idx, dir): # build MODFLOW 6 files sim = flopy.mf6.MFSimulation( - sim_name=name, version="mf6", exe_name="mf6", sim_ws=ws + sim_name=name, version="mf6", exe_name=exe, sim_ws=ws ) sim.name_file.continue_ = False @@ -246,24 +232,25 @@ def build_model(idx, dir): # - No need to change any code below +@pytest.mark.slow @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir, targets): - # initialize testing framework - test = testing_framework() - - # build the models - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6( - Simulation( - dir, +def test_mf6model(idx, name, function_tmpdir, targets): + name = "gwf-henry-nr" + comparisons = {name: ("6.2.1",)} + mf6 = targets["mf6"] + test = TestFramework() + test.build(lambda i, w: build_model(i, w, mf6), idx, str(function_tmpdir)) + test.run( + TestSimulation( + name=name, + exe_dict=targets, idxsim=idx, mf6_regression=True, cmp_verbose=False, - make_comparison=should_compare("gwf_henry_nr", comparisons={"gwf_henry_nr": ("6.2.1",)}, executables=targets), - ) + make_comparison=should_compare(name, comparisons, targets), + ), + str(function_tmpdir), ) diff --git a/autotest/test_gwf_ifmod_buy.py b/autotest/test_gwf_ifmod_buy.py index 65aad010cd6..79be47060a0 100644 --- a/autotest/test_gwf_ifmod_buy.py +++ b/autotest/test_gwf_ifmod_buy.py @@ -1,18 +1,10 @@ import os +import flopy import numpy as np import pytest - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation # General test for the interface model approach. # It compares the result of a single reference model @@ -37,9 +29,6 @@ # for convenience. Finally, the budget error is checked. ex = ["ifmod_buy01"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) # some global convenience...: # model names @@ -655,42 +644,17 @@ def compare_to_ref(sim): cumul_balance_error, mname ) - return - -# - No need to change any code below @pytest.mark.parametrize( - "idx, exdir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -@pytest.mark.developmode -def test_mf6model(idx, exdir): - # initialize testing framework - test = testing_framework() - - # build the model - test.build_mf6_models(build_model, idx, exdir) - - # run the test model - test.run_mf6(Simulation(exdir, exfunc=compare_to_ref, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # run the test models - for idx, exdir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, exdir) - - sim = Simulation(exdir, exfunc=compare_to_ref, idxsim=idx) - test.run_mf6(sim) - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(idx, name, function_tmpdir, targets): + test = TestFramework() + test.build(build_model, idx, str(function_tmpdir)) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=compare_to_ref, idxsim=idx + ), + str(function_tmpdir), + ) diff --git a/autotest/test_gwf_ifmod_mult_exg.py b/autotest/test_gwf_ifmod_mult_exg.py index c4740a8157d..f56269b32e4 100644 --- a/autotest/test_gwf_ifmod_mult_exg.py +++ b/autotest/test_gwf_ifmod_mult_exg.py @@ -23,27 +23,14 @@ """ import os +import flopy import numpy as np import pytest from flopy.utils.lgrutil import Lgr -from modflowapi import ModflowApi - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation ex = ["ifmod_mult_exg"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) - name_parent = "parent" name_child = "child" g_delr = 10.0 @@ -275,8 +262,6 @@ def build_model(idx, exdir): def eval_heads(sim): - name = ex[sim.idxsim] - fpth = os.path.join(sim.simpath, f"{name_parent}.hds") hds = flopy.utils.HeadFile(fpth) heads = hds.get_data() @@ -344,42 +329,17 @@ def exact(x): # assert maxdiff_child_south > maxdiff_child_north - return - -# - No need to change any code below @pytest.mark.parametrize( - "idx, exdir", - list(enumerate(exdirs)), + "name", + ex, ) -@pytest.mark.developmode -def test_mf6model(idx, exdir): - # initialize testing framework - test = testing_framework() - - # build the model - test.build_mf6_models(build_model, idx, exdir) - - # run the test model - test.run_mf6(Simulation(exdir, exfunc=eval_heads, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # run the test models - for idx, exdir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, exdir) - - sim = Simulation(exdir, exfunc=eval_heads, idxsim=idx) - test.run_mf6(sim) - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(name, function_tmpdir, targets): + test = TestFramework() + test.build(build_model, 0, str(function_tmpdir)) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_heads, idxsim=0 + ), + str(function_tmpdir), + ) diff --git a/autotest/test_gwf_ifmod_rewet.py b/autotest/test_gwf_ifmod_rewet.py index c51e816e4af..8a58945edd1 100644 --- a/autotest/test_gwf_ifmod_rewet.py +++ b/autotest/test_gwf_ifmod_rewet.py @@ -1,18 +1,10 @@ import os +import flopy import numpy as np import pytest - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation # General test for the interface model approach. # It compares the result of a single reference model @@ -39,9 +31,6 @@ # solution for convenience. Finally, the budget error is checked. ex = ["ifmod_rewet01"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) # some global convenience...: # model names @@ -399,42 +388,17 @@ def compare_to_ref(sim): cumul_balance_error, mname ) - return - -# - No need to change any code below @pytest.mark.parametrize( - "idx, exdir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -@pytest.mark.developmode -def test_mf6model(idx, exdir): - # initialize testing framework - test = testing_framework() - - # build the model - test.build_mf6_models(build_model, idx, exdir) - - # run the test model - test.run_mf6(Simulation(exdir, exfunc=compare_to_ref, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # run the test models - for idx, exdir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, exdir) - - sim = Simulation(exdir, exfunc=compare_to_ref, idxsim=idx) - test.run_mf6(sim) - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(idx, name, function_tmpdir, targets): + test = TestFramework() + test.build(build_model, idx, str(function_tmpdir)) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=compare_to_ref, idxsim=idx + ), + str(function_tmpdir), + ) diff --git a/autotest/test_gwf_ifmod_vert.py b/autotest/test_gwf_ifmod_vert.py index ac1e10f0673..16fd838da27 100644 --- a/autotest/test_gwf_ifmod_vert.py +++ b/autotest/test_gwf_ifmod_vert.py @@ -32,26 +32,14 @@ """ import os +import flopy import numpy as np import pytest - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - from flopy.utils.lgrutil import Lgr - -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation ex = ["ifmod_vert"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) parent_name = "parent" child_name = "child" @@ -292,41 +280,17 @@ def eval_heads(sim): errmsg = f"min or max residual too large {res.min()} {res.max()}" assert np.allclose(res, 0.0, atol=1.0e-6), errmsg - return - @pytest.mark.parametrize( - "idx, exdir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -@pytest.mark.developmode -def test_mf6model(idx, exdir): - # initialize testing framework - test = testing_framework() - - # build the model - test.build_mf6_models(build_model, idx, exdir) - - # run the test model - test.run_mf6(Simulation(exdir, exfunc=eval_heads, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # run the test models - for idx, exdir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, exdir) - - sim = Simulation(exdir, exfunc=eval_heads, idxsim=idx) - test.run_mf6(sim) - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(idx, name, function_tmpdir, targets): + test = TestFramework() + test.build(build_model, idx, str(function_tmpdir)) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_heads, idxsim=idx + ), + str(function_tmpdir), + ) diff --git a/autotest/test_gwf_ifmod_xt3d01.py b/autotest/test_gwf_ifmod_xt3d01.py index dbbd2d74349..88bd8776ffe 100644 --- a/autotest/test_gwf_ifmod_xt3d01.py +++ b/autotest/test_gwf_ifmod_xt3d01.py @@ -1,20 +1,11 @@ import os +import flopy import numpy as np import pytest - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - from flopy.utils.lgrutil import Lgr - -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation # Test for the interface model approach, when running # with a GWF-GWF exchange and XT3D applied on it. @@ -41,9 +32,6 @@ # confirm that there is no budget error. ex = ["ifmod_xt3d01"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) # globally for convenience... useXT3D = True @@ -314,8 +302,6 @@ def qxqyqz(fname, nlay, nrow, ncol): def eval_heads(sim): print("comparing heads and spec. discharges to analytical result...") - name = ex[sim.idxsim] - fpth = os.path.join(sim.simpath, f"{parent_name}.hds") hds = flopy.utils.HeadFile(fpth) heads = hds.get_data() @@ -475,7 +461,7 @@ def exact(x): ), "exchange observations do not match parent exchange flows" assert np.allclose( obsvalues, -child_exchange_flows - ), "exchange observations do not match child exchange flows" + ), "exchange observations do not match chile exchange flows" # Read the lumped boundname observations values fpth = os.path.join(sim.simpath, "gwf_obs_boundnames.csv") @@ -487,41 +473,17 @@ def exact(x): obsvalues, [-50.0, 50.0, 0, 0.0] ), "boundname observations do not match expected results" - return - -# - No need to change any code below @pytest.mark.parametrize( - "idx, exdir", - list(enumerate(exdirs)), + "name", + ex, ) -def test_mf6model(idx, exdir): - # initialize testing framework - test = testing_framework() - - # build the model - test.build_mf6_models(build_model, idx, exdir) - - # run the test model - test.run_mf6(Simulation(exdir, exfunc=eval_heads, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # run the test models - for idx, exdir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, exdir) - - sim = Simulation(exdir, exfunc=eval_heads, idxsim=idx) - test.run_mf6(sim) - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(name, function_tmpdir, targets): + test = TestFramework() + test.build(build_model, 0, str(function_tmpdir)) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_heads, idxsim=0 + ), + str(function_tmpdir), + ) diff --git a/autotest/test_gwf_ifmod_xt3d02.py b/autotest/test_gwf_ifmod_xt3d02.py index b1e9146fdec..6ab53f467ea 100644 --- a/autotest/test_gwf_ifmod_xt3d02.py +++ b/autotest/test_gwf_ifmod_xt3d02.py @@ -1,20 +1,10 @@ import os +import flopy import numpy as np import pytest - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from flopy.utils.lgrutil import Lgr - -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation # Test for the interface model approach. # It compares the result of a single, strongly anisotropic model @@ -42,10 +32,6 @@ # should be identical. Finally, the budget error is checked. ex = ["ifmod_xt3d02"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) - # global convenience... mname_ref = "refmodel" mname_left = "leftmodel" @@ -423,41 +409,17 @@ def compare_to_ref(sim): cumul_balance_error, mname ) - return - -# - No need to change any code below @pytest.mark.parametrize( - "idx, exdir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, exdir): - # initialize testing framework - test = testing_framework() - - # build the model - test.build_mf6_models(build_model, idx, exdir) - - # run the test model - test.run_mf6(Simulation(exdir, exfunc=compare_to_ref, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # run the test models - for idx, exdir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, exdir) - - sim = Simulation(exdir, exfunc=compare_to_ref, idxsim=idx) - test.run_mf6(sim) - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(idx, name, function_tmpdir, targets): + test = TestFramework() + test.build(build_model, idx, str(function_tmpdir)) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=compare_to_ref, idxsim=idx + ), + str(function_tmpdir), + ) diff --git a/autotest/test_gwf_ims_rcm_reorder.py b/autotest/test_gwf_ims_rcm_reorder.py index c4eca177504..b6b587e5b38 100644 --- a/autotest/test_gwf_ims_rcm_reorder.py +++ b/autotest/test_gwf_ims_rcm_reorder.py @@ -1,26 +1,13 @@ import os +import flopy import pytest - -from budget_file_compare import eval_bud_diff - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from flopy.utils.compare import eval_bud_diff +from framework import TestFramework +from simulation import TestSimulation paktest = "ims" - -ex = [ - "ims_rcm", -] -exdirs = [os.path.join("temp", s) for s in ex] +ex = ["ims_rcm"] # spatial discretization data nlay, nrow, ncol = 2, 5, 30 @@ -31,9 +18,8 @@ chd_left = 10.0 chd_right = 5.0 -# -def build_model(idx, ws): +def build_model(idx, ws): # static model data # temporal discretization nper = 1 @@ -135,20 +121,19 @@ def build_models(idx, base_ws): def eval_flows(sim): - idx = sim.idxsim - name = ex[idx] + name = sim.name print("evaluating flow results..." f"({name})") - fpth = os.path.join(exdirs[idx], f"{name}.dis.grb") + fpth = os.path.join(sim.simpath, f"{name}.dis.grb") ia = flopy.mf6.utils.MfGrdFile(fpth).ia - fpth = os.path.join(exdirs[idx], f"{name}.cbc") + fpth = os.path.join(sim.simpath, f"{name}.cbc") b0 = flopy.utils.CellBudgetFile(fpth, precision="double") - fpth = os.path.join(exdirs[idx], "mf6", f"{name}.cbc") + fpth = os.path.join(sim.simpath, "mf6", f"{name}.cbc") b1 = flopy.utils.CellBudgetFile(fpth, precision="double") - fpth = os.path.join(exdirs[idx], f"{name}.cbc.cmp.out") + fpth = os.path.join(sim.simpath, f"{name}.cbc.cmp.out") eval_bud_diff(fpth, b0, b1, ia=ia) # close the budget files @@ -156,48 +141,20 @@ def eval_flows(sim): b1.close() -# - No need to change any code below @pytest.mark.parametrize( - "idx, exdir", - list(enumerate(exdirs)), + "name", + ex, ) -def test_mf6model(idx, exdir): - # initialize testing framework - test = testing_framework() - - # build the model - test.build_mf6_models(build_models, idx, exdir) - - # run the test models - test.run_mf6( - Simulation( - exdir, +def test_mf6model(name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_models, 0, ws) + test.run( + TestSimulation( + name=name, + exe_dict=targets, exfunc=eval_flows, - idxsim=idx, - ) + idxsim=0, + ), + ws, ) - - -def main(): - # initialize testing framework - test = testing_framework() - - # run the test models - for idx, exdir in enumerate(exdirs): - test.build_mf6_models(build_models, idx, exdir) - - sim = Simulation( - exdir, - exfunc=eval_flows, - idxsim=idx, - ) - test.run_mf6(sim) - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() diff --git a/autotest/test_gwf_lakobs01.py b/autotest/test_gwf_lakobs01.py index 8ff09c449c9..8cfc8ff2de9 100644 --- a/autotest/test_gwf_lakobs01.py +++ b/autotest/test_gwf_lakobs01.py @@ -9,28 +9,13 @@ import shutil import sys +import flopy import numpy as np import pytest - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -import targets -from framework import testing_framework -from simulation import Simulation - -mf6_exe = os.path.abspath(targets.target_dict["mf6"]) +from framework import TestFramework +from simulation import TestSimulation ex = "gwf_lakobs_01a" -exdir = os.path.join("temp", ex) - - -# store global gwf for subsequent plotting gwf = None @@ -42,7 +27,7 @@ def get_idomain(nlay, nrow, ncol, lakend): return idomain -def build_model(): +def build_model(dir, exe): lx = 300.0 lz = 45.0 nlay = 45 @@ -75,8 +60,8 @@ def build_model(): sim = flopy.mf6.MFSimulation( sim_name=name, version="mf6", - exe_name=mf6_exe, - sim_ws=exdir, + exe_name=exe, + sim_ws=dir, ) # create tdis package @@ -220,69 +205,14 @@ def build_model(): return sim -# - No need to change any code below -def test_mf6model(): - # initialize testing framework - test = testing_framework() - - # build the models - sim = build_model() - - # write model input - sim.write_simulation() - - # attempt to run model, should fail - sim.run_simulation() - - # ensure that the error msg is contained in the mfsim.lst file - f = open(os.path.join(exdir, "mfsim.lst"), "r") - lines = f.readlines() - error_count = 0 - expected_msg = False - for line in lines: - if "ID2 (iconn) is missing" in line: - expected_msg = True - error_count += 1 - - assert error_count == 1, ( - "error count = " + str(error_count) + "but should equal 1" - ) - - # fix the error and attempt to rerun model - orig_fl = os.path.join(exdir, ex + ".lak.obs") - new_fl = os.path.join(exdir, ex + ".lak.obs.new") - sr = open(orig_fl, "r") - sw = open(new_fl, "w") - - lines = sr.readlines() - error_free_line = " lak1 lak 1 1\n" - for line in lines: - if " lak " in line: - sw.write(error_free_line) - else: - sw.write(line) +def test_mf6model(function_tmpdir, targets): + mf6 = targets["mf6"] - sr.close() - sw.close() - - # delete original and replace with corrected lab obs input - os.remove(orig_fl) - os.rename(new_fl, orig_fl) - - # rerun the model, should be no errors - sim.run_simulation() - - shutil.rmtree(exdir, ignore_errors=True) - - return - - -def main(): # initialize testing framework - test = testing_framework() + test = TestFramework() # build the models - sim = build_model() + sim = build_model(str(function_tmpdir), mf6) # write model input sim.write_simulation() @@ -291,7 +221,7 @@ def main(): sim.run_simulation() # ensure that the error msg is contained in the mfsim.lst file - f = open(os.path.join(exdir, "mfsim.lst"), "r") + f = open(str(function_tmpdir / "mfsim.lst"), "r") lines = f.readlines() error_count = 0 expected_msg = False @@ -301,12 +231,12 @@ def main(): error_count += 1 assert error_count == 1, ( - "error count = " + str(error_count) + ", but should equal 1" + "error count = " + str(error_count) + "but should equal 1" ) # fix the error and attempt to rerun model - orig_fl = os.path.join(exdir, ex + ".lak.obs") - new_fl = os.path.join(exdir, ex + ".lak.obs.new") + orig_fl = str(function_tmpdir / (ex + ".lak.obs")) + new_fl = str(function_tmpdir / (ex + ".lak.obs.new")) sr = open(orig_fl, "r") sw = open(new_fl, "w") @@ -327,16 +257,3 @@ def main(): # rerun the model, should be no errors sim.run_simulation() - - # clean the working directory - shutil.rmtree(exdir, ignore_errors=True) - - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() diff --git a/autotest/test_gwf_libmf6_evt01.py b/autotest/test_gwf_libmf6_evt01.py index 7bf1f6349d0..b962f653f30 100644 --- a/autotest/test_gwf_libmf6_evt01.py +++ b/autotest/test_gwf_libmf6_evt01.py @@ -7,33 +7,14 @@ import os +import flopy import numpy as np import pytest +from framework import TestFramework from modflowapi import ModflowApi - -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation, api_return +from simulation import TestSimulation ex = ["libgwf_evt01"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) # et variables et_max = 0.1 @@ -172,24 +153,23 @@ def head2et_wellrate(h): def api_func(exe, idx, model_ws=None): - success = False - name = ex[idx].upper() if model_ws is None: model_ws = "." + output_file_path = os.path.join(model_ws, "mfsim.stdout") try: mf6 = ModflowApi(exe, working_directory=model_ws) except Exception as e: print("Failed to load " + exe) print("with message: " + str(e)) - return api_return(success, model_ws) + return False, open(output_file_path).readlines() # initialize the model try: mf6.initialize() except: - return api_return(success, model_ws) + return False, open(output_file_path).readlines() # time loop current_time = mf6.get_current_time() @@ -252,7 +232,8 @@ def api_func(exe, idx, model_ws=None): break if not has_converged: - return api_return(success, model_ws) + print("model did not converge") + return False, open(output_file_path).readlines() # finalize time step mf6.finalize_solve() @@ -263,50 +244,26 @@ def api_func(exe, idx, model_ws=None): # increment counter idx += 1 + # cleanup try: mf6.finalize() - success = True except: - return api_return(success, model_ws) + return False, open(output_file_path).readlines() - # cleanup and return - return api_return(success, model_ws) + return True, open(output_file_path).readlines() -# - No need to change any code below @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the models - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, idxsim=idx, api_func=api_func)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # build the models - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, idxsim=idx, api_func=api_func) - test.run_mf6(sim) - - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(idx, name, function_tmpdir, targets): + test = TestFramework() + test.build(build_model, idx, str(function_tmpdir)) + test.run( + TestSimulation( + name=name, exe_dict=targets, idxsim=idx, api_func=api_func + ), + str(function_tmpdir), + ) diff --git a/autotest/test_gwf_libmf6_ghb01.py b/autotest/test_gwf_libmf6_ghb01.py index 6cb729387ee..43e7ce239f5 100644 --- a/autotest/test_gwf_libmf6_ghb01.py +++ b/autotest/test_gwf_libmf6_ghb01.py @@ -8,34 +8,14 @@ """ import os +import flopy import numpy as np import pytest +from framework import TestFramework from modflowapi import ModflowApi - -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation, api_return +from simulation import TestSimulation ex = ["libgwf_ghb01"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) - # temporal discretization nper = 10 @@ -203,24 +183,23 @@ def api_ghb_pak(hcof, rhs): def api_func(exe, idx, model_ws=None): - success = False - name = ex[idx].upper() if model_ws is None: model_ws = "." + output_file_path = os.path.join(model_ws, "mfsim.stdout") try: mf6 = ModflowApi(exe, working_directory=model_ws) except Exception as e: print("Failed to load " + exe) print("with message: " + str(e)) - return api_return(success, model_ws) + return False, open(output_file_path).readlines() # initialize the model try: mf6.initialize() except: - return api_return(success, model_ws) + return False, open(output_file_path).readlines() # time loop current_time = mf6.get_current_time() @@ -291,47 +270,23 @@ def api_func(exe, idx, model_ws=None): # cleanup try: mf6.finalize() - success = True except: - return api_return(success, model_ws) + return False, open(output_file_path).readlines() # cleanup and return - return api_return(success, model_ws) + return True, open(output_file_path).readlines() -# - No need to change any code below @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the models - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, idxsim=idx, api_func=api_func)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # build the models - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, idxsim=idx, api_func=api_func) - test.run_mf6(sim) - - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(idx, name, function_tmpdir, targets): + test = TestFramework() + test.build(build_model, idx, str(function_tmpdir)) + test.run( + TestSimulation( + name=name, exe_dict=targets, idxsim=idx, api_func=api_func + ), + str(function_tmpdir), + ) diff --git a/autotest/test_gwf_libmf6_ifmod01.py b/autotest/test_gwf_libmf6_ifmod01.py index 3f138e5dc20..8105f35f88d 100644 --- a/autotest/test_gwf_libmf6_ifmod01.py +++ b/autotest/test_gwf_libmf6_ifmod01.py @@ -14,33 +14,13 @@ """ import os -import numpy as np +import flopy import pytest +from framework import TestFramework from modflowapi import ModflowApi - -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation, api_return +from simulation import TestSimulation ex = ["libgwf_ifmod01"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) # global convenience... name_left = "leftmodel" @@ -231,24 +211,22 @@ def build_model(idx, dir): def api_func(exe, idx, model_ws=None): - success = False - - name = ex[idx].upper() if model_ws is None: model_ws = "." + output_file_path = os.path.join(model_ws, "mfsim.stdout") try: mf6 = ModflowApi(exe, working_directory=model_ws) except Exception as e: print("Failed to load " + exe) print("with message: " + str(e)) - return api_return(success, model_ws) + return False, open(output_file_path).readlines() # initialize the model try: mf6.initialize() except: - return api_return(success, model_ws) + return False, open(output_file_path).readlines() # test the interface models check_interface_models(mf6) @@ -260,18 +238,17 @@ def api_func(exe, idx, model_ws=None): try: mf6.update() except: - return api_return(success, model_ws) + return False, open(output_file_path).readlines() current_time = mf6.get_current_time() # finish try: mf6.finalize() - success = True except: - return api_return(success, model_ws) + return False, open(output_file_path).readlines() # cleanup and return - return api_return(success, model_ws) + return True, open(output_file_path).readlines() def check_interface_models(mf6): @@ -330,38 +307,17 @@ def check_interface_models(mf6): ), "AREA in interface model does not match" -# - No need to change any code below @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the model - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, idxsim=idx, api_func=api_func)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # run the test models - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, idxsim=idx, api_func=api_func) - test.run_mf6(sim) - - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(idx, name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, idx, ws) + test.run( + TestSimulation( + name=name, exe_dict=targets, idxsim=idx, api_func=api_func + ), + ws, + ) diff --git a/autotest/test_gwf_libmf6_ifmod02.py b/autotest/test_gwf_libmf6_ifmod02.py index d0a1826f2cd..63dec777796 100644 --- a/autotest/test_gwf_libmf6_ifmod02.py +++ b/autotest/test_gwf_libmf6_ifmod02.py @@ -38,33 +38,14 @@ """ import os +import flopy import numpy as np import pytest +from framework import TestFramework from modflowapi import ModflowApi - -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation, api_return +from simulation import TestSimulation ex = ["libgwf_ifmod02"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) # global convenience... name_tl = "topleft" @@ -324,24 +305,23 @@ def build_model(idx, dir): def api_func(exe, idx, model_ws=None): - success = False - - name = ex[idx].upper() if model_ws is None: model_ws = "." + output_file_path = os.path.join(model_ws, "mfsim.stdout") + try: mf6 = ModflowApi(exe, working_directory=model_ws) except Exception as e: print("Failed to load " + exe) print("with message: " + str(e)) - return api_return(success, model_ws) + return False, open(output_file_path).readlines() # initialize the model try: mf6.initialize() except: - return api_return(success, model_ws) + return False, open(output_file_path).readlines() # test the interface models check_interface_models(mf6) @@ -353,18 +333,17 @@ def api_func(exe, idx, model_ws=None): try: mf6.update() except: - return api_return(success, model_ws) + return False, open(output_file_path).readlines() current_time = mf6.get_current_time() # finish try: mf6.finalize() - success = True except: - return api_return(success, model_ws) + return False, open(output_file_path).readlines() # cleanup and return - return api_return(success, model_ws) + return True, open(output_file_path).readlines() def check_interface_models(mf6): @@ -424,39 +403,16 @@ def check_interface_models(mf6): ) -# - No need to change any code below @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -@pytest.mark.developmode -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the model - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, idxsim=idx, api_func=api_func)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # run the test models - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, idxsim=idx, api_func=api_func) - test.run_mf6(sim) - - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(idx, name, function_tmpdir, targets): + test = TestFramework() + test.build(build_model, idx, str(function_tmpdir)) + test.run( + TestSimulation( + name=name, exe_dict=targets, idxsim=idx, api_func=api_func + ), + str(function_tmpdir), + ) diff --git a/autotest/test_gwf_libmf6_ifmod03.py b/autotest/test_gwf_libmf6_ifmod03.py index 003a03b2d8f..c238d269110 100644 --- a/autotest/test_gwf_libmf6_ifmod03.py +++ b/autotest/test_gwf_libmf6_ifmod03.py @@ -28,33 +28,14 @@ """ import os +import flopy import numpy as np import pytest +from framework import TestFramework from modflowapi import ModflowApi - -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation, api_return +from simulation import TestSimulation ex = ["libgwf_ifmod03"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) # global convenience... name_left = "left" @@ -241,24 +222,23 @@ def build_model(idx, dir): def api_func(exe, idx, model_ws=None): - success = False - - name = ex[idx].upper() if model_ws is None: model_ws = "." + output_file_path = os.path.join(model_ws, "mfsim.stdout") + try: mf6 = ModflowApi(exe, working_directory=model_ws) except Exception as e: print("Failed to load " + exe) print("with message: " + str(e)) - return api_return(success, model_ws) + return False, open(output_file_path).readlines() # initialize the model try: mf6.initialize() except: - return api_return(success, model_ws) + return False, open(output_file_path).readlines() # test the interface models check_interface_models(mf6) @@ -270,7 +250,7 @@ def api_func(exe, idx, model_ws=None): try: mf6.update() except: - return api_return(success, model_ws) + return False, open(output_file_path).readlines() current_time = mf6.get_current_time() # finish @@ -278,10 +258,10 @@ def api_func(exe, idx, model_ws=None): mf6.finalize() success = True except: - return api_return(success, model_ws) + return False, open(output_file_path).readlines() # cleanup and return - return api_return(success, model_ws) + return True, open(output_file_path).readlines() def check_interface_models(mf6): @@ -309,38 +289,16 @@ def check_interface_models(mf6): assert abs(ymax - ymin) < 1e-6 -# - No need to change any code below @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the model - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, idxsim=idx, api_func=api_func)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # run the test models - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, idxsim=idx, api_func=api_func) - test.run_mf6(sim) - - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(idx, name, function_tmpdir, targets): + test = TestFramework() + test.build(build_model, idx, str(function_tmpdir)) + test.run( + TestSimulation( + name=name, exe_dict=targets, idxsim=idx, api_func=api_func + ), + str(function_tmpdir), + ) diff --git a/autotest/test_gwf_libmf6_rch01.py b/autotest/test_gwf_libmf6_rch01.py index 07592d15a94..bb8252e8cff 100644 --- a/autotest/test_gwf_libmf6_rch01.py +++ b/autotest/test_gwf_libmf6_rch01.py @@ -10,33 +10,14 @@ import os +import flopy import numpy as np import pytest +from framework import TestFramework from modflowapi import ModflowApi - -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation, api_return +from simulation import TestSimulation ex = ["libgwf_rch01"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) # recharge package name rch_pname = "RCH-1" @@ -168,24 +149,24 @@ def build_model(idx, dir): def api_func(exe, idx, model_ws=None): - success = False - name = ex[idx].upper() if model_ws is None: model_ws = "." + output_file_path = os.path.join(model_ws, "mfsim.stdout") + try: mf6 = ModflowApi(exe, working_directory=model_ws) except Exception as e: print("Failed to load " + exe) print("with message: " + str(e)) - return api_return(success, model_ws) + return False, open(output_file_path).readlines() # initialize the model try: mf6.initialize() except: - return api_return(success, model_ws) + return False, open(output_file_path).readlines() # time loop current_time = mf6.get_current_time() @@ -229,7 +210,7 @@ def api_func(exe, idx, model_ws=None): break if not has_converged: - return api_return(success, model_ws) + return False, open(output_file_path).readlines() # finalize time step mf6.finalize_solve() @@ -244,47 +225,23 @@ def api_func(exe, idx, model_ws=None): # cleanup try: mf6.finalize() - success = True except: - return api_return(success, model_ws) + return False, open(output_file_path).readlines() # cleanup and return - return api_return(success, model_ws) + return True, open(output_file_path).readlines() -# - No need to change any code below @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the models - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, idxsim=idx, api_func=api_func)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # build the models - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, idxsim=idx, api_func=api_func) - test.run_mf6(sim) - - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(idx, name, function_tmpdir, targets): + test = TestFramework() + test.build(build_model, idx, str(function_tmpdir)) + test.run( + TestSimulation( + name=name, exe_dict=targets, idxsim=idx, api_func=api_func + ), + str(function_tmpdir), + ) diff --git a/autotest/test_gwf_libmf6_rch02.py b/autotest/test_gwf_libmf6_rch02.py index 3ed421c2254..1e7a5797a3e 100644 --- a/autotest/test_gwf_libmf6_rch02.py +++ b/autotest/test_gwf_libmf6_rch02.py @@ -7,33 +7,14 @@ import os +import flopy import numpy as np import pytest +from framework import TestFramework from modflowapi import ModflowApi - -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation, api_return +from simulation import TestSimulation ex = ["libgwf_rch02"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) # recharge package name rch_pname = "RCH-1" @@ -88,7 +69,7 @@ hclose, rclose, relax = 1e-9, 1e-3, 0.97 -def get_model(ws, name, rech=rch_spd): +def get_model(ws, name, exe, rech=rch_spd): sim = flopy.mf6.MFSimulation( sim_name=name, version="mf6", @@ -172,15 +153,15 @@ def get_model(ws, name, rech=rch_spd): return sim -def build_model(idx, dir): +def build_model(idx, dir, exe): # build MODFLOW 6 files ws = dir name = ex[idx] - sim = get_model(ws, name) + sim = get_model(ws, name, exe) # build comparison model ws = os.path.join(dir, "libmf6") - mc = get_model(ws, name, rech=0.0) + mc = get_model(ws, name, exe, rech=0.0) return sim, mc @@ -203,13 +184,14 @@ def run_perturbation(mf6, max_iter, recharge, tag, rch): def api_func(exe, idx, model_ws=None): print("\nBMI implementation test:") - success = False name = ex[idx].upper() init_wd = os.path.abspath(os.getcwd()) if model_ws is not None: os.chdir(model_ws) + output_file_path = os.path.join(model_ws, "mfsim.stdout") + # get the observations from the standard run fpth = os.path.join("..", f"{ex[idx]}.head.obs.csv") hobs = np.genfromtxt(fpth, delimiter=",", names=True)["H1_6_6"] @@ -219,13 +201,13 @@ def api_func(exe, idx, model_ws=None): except Exception as e: print("Failed to load " + exe) print("with message: " + str(e)) - return api_return(success, model_ws) + return False, open(output_file_path).readlines() # initialize the model try: mf6.initialize() except: - return api_return(success, model_ws) + return False, open(output_file_path).readlines() # time loop current_time = mf6.get_current_time() @@ -265,7 +247,7 @@ def api_func(exe, idx, model_ws=None): mf6, max_iter, new_recharge, rch_tag, rch ) if not has_converged: - return api_return(success, model_ws) + return False, open(output_file_path).readlines() h0 = head.reshape((nrow, ncol))[5, 5] r0 = h0 - htarget @@ -274,7 +256,7 @@ def api_func(exe, idx, model_ws=None): mf6, max_iter, new_recharge, rch_tag, rch + drch ) if not has_converged: - return api_return(success, model_ws) + return False, open(output_file_path).readlines() h1 = head.reshape((nrow, ncol))[5, 5] r1 = h1 - htarget @@ -302,7 +284,7 @@ def api_func(exe, idx, model_ws=None): mf6, max_iter, new_recharge, rch_tag, rch ) if not has_converged: - return api_return(success, model_ws) + return False, open(output_file_path).readlines() # finalize time step mf6.finalize_solve() @@ -316,50 +298,30 @@ def api_func(exe, idx, model_ws=None): # cleanup try: mf6.finalize() - success = True except: - return api_return(success, model_ws) + return False, open(output_file_path).readlines() if model_ws is not None: os.chdir(init_wd) # cleanup and return - return api_return(success, model_ws) + return True, open(output_file_path).readlines() -# - No need to change any code below @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the models - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, idxsim=idx, api_func=api_func)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # build the models - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, idxsim=idx, api_func=api_func) - test.run_mf6(sim) - - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(idx, name, function_tmpdir, targets): + test = TestFramework() + test.build( + lambda i, d: build_model(i, d, targets["mf6"]), + idx, + str(function_tmpdir), + ) + test.run( + TestSimulation( + name=name, exe_dict=targets, idxsim=idx, api_func=api_func + ), + str(function_tmpdir), + ) diff --git a/autotest/test_gwf_libmf6_riv01.py b/autotest/test_gwf_libmf6_riv01.py index 7a753137e86..0fc8dd7e598 100644 --- a/autotest/test_gwf_libmf6_riv01.py +++ b/autotest/test_gwf_libmf6_riv01.py @@ -5,34 +5,14 @@ """ import os +import flopy import numpy as np import pytest +from framework import TestFramework from modflowapi import ModflowApi - -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation, api_return +from simulation import TestSimulation ex = ["libgwf_riv01"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) - # temporal discretization nper = 10 @@ -174,24 +154,24 @@ def build_model(idx, dir): def api_func(exe, idx, model_ws=None): - success = False - name = ex[idx].upper() if model_ws is None: model_ws = "." + output_file_path = os.path.join(model_ws, "mfsim.stdout") + try: mf6 = ModflowApi(exe, working_directory=model_ws) except Exception as e: print("Failed to load " + exe) print("with message: " + str(e)) - return api_return(success, model_ws) + return False, open(output_file_path).readlines() # initialize the model try: mf6.initialize() except: - return api_return(success, model_ws) + return False, open(output_file_path).readlines() # time loop current_time = mf6.get_current_time() @@ -240,7 +220,7 @@ def api_func(exe, idx, model_ws=None): break if not has_converged: - return api_return(success, model_ws) + return False, open(output_file_path).readlines() # finalize time step mf6.finalize_solve() @@ -255,47 +235,23 @@ def api_func(exe, idx, model_ws=None): # cleanup try: mf6.finalize() - success = True except: - return api_return(success, model_ws) + return False, open(output_file_path).readlines() # cleanup and return - return api_return(success, model_ws) + return True, open(output_file_path).readlines() -# - No need to change any code below @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the models - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, idxsim=idx, api_func=api_func)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # build the models - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, idxsim=idx, api_func=api_func) - test.run_mf6(sim) - - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(idx, name, function_tmpdir, targets): + test = TestFramework() + test.build(build_model, idx, str(function_tmpdir)) + test.run( + TestSimulation( + name=name, exe_dict=targets, idxsim=idx, api_func=api_func + ), + str(function_tmpdir), + ) diff --git a/autotest/test_gwf_libmf6_riv02.py b/autotest/test_gwf_libmf6_riv02.py index 67d7f04fd20..eb3337c0561 100644 --- a/autotest/test_gwf_libmf6_riv02.py +++ b/autotest/test_gwf_libmf6_riv02.py @@ -5,34 +5,14 @@ """ import os +import flopy import numpy as np import pytest +from framework import TestFramework from modflowapi import ModflowApi - -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation, api_return +from simulation import TestSimulation ex = ["libgwf_riv02"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) - # temporal discretization nper = 10 @@ -188,24 +168,24 @@ def api_riv_pak(stage, h, hcof, rhs): def api_func(exe, idx, model_ws=None): - success = False - name = ex[idx].upper() if model_ws is None: model_ws = "." + output_file_path = os.path.join(model_ws, "mfsim.stdout") + try: mf6 = ModflowApi(exe, working_directory=model_ws) except Exception as e: print("Failed to load " + exe) print("with message: " + str(e)) - return api_return(success, model_ws) + return False, open(output_file_path).readlines() # initialize the model try: mf6.initialize() except: - return api_return(success, model_ws) + return False, open(output_file_path).readlines() # time loop current_time = mf6.get_current_time() @@ -287,45 +267,22 @@ def api_func(exe, idx, model_ws=None): mf6.finalize() success = True except: - return api_return(success, model_ws) + return False, open(output_file_path).readlines() # cleanup and return - return api_return(success, model_ws) + return True, open(output_file_path).readlines() -# - No need to change any code below @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the models - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, idxsim=idx, api_func=api_func)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # build the models - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, idxsim=idx, api_func=api_func) - test.run_mf6(sim) - - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(idx, name, function_tmpdir, targets): + test = TestFramework() + test.build(build_model, idx, str(function_tmpdir)) + test.run( + TestSimulation( + name=name, exe_dict=targets, idxsim=idx, api_func=api_func + ), + str(function_tmpdir), + ) diff --git a/autotest/test_gwf_libmf6_sto01.py b/autotest/test_gwf_libmf6_sto01.py index 80e4365cd59..4cbe8caeb68 100644 --- a/autotest/test_gwf_libmf6_sto01.py +++ b/autotest/test_gwf_libmf6_sto01.py @@ -7,33 +7,14 @@ import os +import flopy import numpy as np import pytest +from framework import TestFramework from modflowapi import ModflowApi - -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation, api_return +from simulation import TestSimulation ex = ["libgwf_sto01"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) # average recharge rate avg_rch = 0.001 @@ -171,24 +152,24 @@ def build_model(idx, dir): def api_func(exe, idx, model_ws=None): - success = False - name = ex[idx].upper() if model_ws is None: model_ws = "." + output_file_path = os.path.join(model_ws, "mfsim.stdout") + try: mf6 = ModflowApi(exe, working_directory=model_ws) except Exception as e: print("Failed to load " + exe) print("with message: " + str(e)) - return api_return(success, model_ws) + return False, open(output_file_path).readlines() # initialize the model try: mf6.initialize() except: - return api_return(success, model_ws) + return False, open(output_file_path).readlines() # time loop current_time = mf6.get_current_time() @@ -209,7 +190,7 @@ def api_func(exe, idx, model_ws=None): try: mf6.update() except: - return api_return(success, model_ws) + return False, open(output_file_path).readlines() # update time current_time = mf6.get_current_time() @@ -220,47 +201,24 @@ def api_func(exe, idx, model_ws=None): # cleanup try: mf6.finalize() - success = True except: - return api_return(success, model_ws) + return False, open(output_file_path).readlines() # cleanup and return - return api_return(success, model_ws) + return True, open(output_file_path).readlines() -# - No need to change any code below +@pytest.mark.slow @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the models - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, idxsim=idx, api_func=api_func)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # build the models - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, idxsim=idx, api_func=api_func) - test.run_mf6(sim) - - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(idx, name, function_tmpdir, targets): + test = TestFramework() + test.build(build_model, idx, str(function_tmpdir)) + test.run( + TestSimulation( + name=name, exe_dict=targets, idxsim=idx, api_func=api_func + ), + str(function_tmpdir), + ) diff --git a/autotest/test_gwf_maw01.py b/autotest/test_gwf_maw01.py deleted file mode 100644 index 425388bf22f..00000000000 --- a/autotest/test_gwf_maw01.py +++ /dev/null @@ -1,252 +0,0 @@ -import os - -import numpy as np -import pytest - -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation - -ex = ["maw01", "maw01nwt", "maw01nwtur"] -newtonoptions = [None, "NEWTON", "NEWTON UNDER_RELAXATION"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) -ddir = "data" - - -def build_model(idx, dir): - - nlay, nrow, ncol = 1, 1, 3 - nper = 3 - perlen = [1.0, 1.0, 1.0] - nstp = [1, 1, 1] - tsmult = [1.0, 1.0, 1.0] - lenx = 300.0 - delr = delc = lenx / float(nrow) - strt = 100.0 - hnoflo = 1e30 - hdry = -1e30 - hk = 1.0 - - nouter, ninner = 100, 300 - hclose, rclose, relax = 1e-9, 1e-3, 1.0 - krylov = ["CG", "BICGSTAB", "BICGSTAB"] - - tdis_rc = [] - for i in range(nper): - tdis_rc.append((perlen[i], nstp[i], tsmult[i])) - - name = ex[idx] - - # build MODFLOW 6 files - ws = dir - sim = flopy.mf6.MFSimulation( - sim_name=name, version="mf6", exe_name="mf6", sim_ws=ws - ) - # create tdis package - tdis = flopy.mf6.ModflowTdis( - sim, time_units="DAYS", nper=nper, perioddata=tdis_rc - ) - - # create gwf model - gwf = flopy.mf6.MFModel( - sim, - model_type="gwf6", - modelname=name, - model_nam_file=f"{name}.nam", - ) - gwf.name_file.newtonoptions = newtonoptions[idx] - - # create iterative model solution and register the gwf model with it - ims = flopy.mf6.ModflowIms( - sim, - print_option="SUMMARY", - outer_dvclose=hclose, - outer_maximum=nouter, - under_relaxation="NONE", - inner_maximum=ninner, - inner_dvclose=hclose, - rcloserecord=rclose, - linear_acceleration=krylov[idx], - scaling_method="NONE", - reordering_method="NONE", - relaxation_factor=relax, - ) - sim.register_ims_package(ims, [gwf.name]) - - dis = flopy.mf6.ModflowGwfdis( - gwf, - nlay=nlay, - nrow=nrow, - ncol=ncol, - delr=delr, - delc=delc, - top=100.0, - botm=0.0, - idomain=1, - filename=f"{name}.dis", - ) - - # initial conditions - ic = flopy.mf6.ModflowGwfic(gwf, strt=strt, filename=f"{name}.ic") - - # node property flow - npf = flopy.mf6.ModflowGwfnpf( - gwf, - save_flows=True, - icelltype=1, - k=hk, - k33=hk, - filename=f"{name}.npf", - ) - # storage - sto = flopy.mf6.ModflowGwfsto( - gwf, - save_flows=True, - iconvert=1, - ss=0.0, - sy=0.1, - steady_state={0: True}, - # transient={1: False}, - filename=f"{name}.sto", - ) - - # chd files - chdlist0 = [] - chdlist0.append([(0, 0, 0), 100.0]) - chdlist0.append([(0, 0, 2), 100.0]) - - chdlist1 = [] - chdlist1.append([(0, 0, 0), 25.0]) - chdlist1.append([(0, 0, 2), 25.0]) - - chdspdict = {0: chdlist0, 1: chdlist1, 2: chdlist0} - chd = flopy.mf6.ModflowGwfchd( - gwf, - stress_period_data=chdspdict, - save_flows=False, - filename=f"{name}.chd", - ) - - # wel files - # wel = flopy.mf6.ModflowGwfwel(gwf, print_input=True, print_flows=True, - # maxbound=len(ws), - # periodrecarray=wd6, - # save_flows=False) - # MAW - opth = f"{name}.maw.obs" - wellbottom = 50.0 - wellrecarray = [[0, 0.1, wellbottom, 100.0, "THIEM", 1]] - wellconnectionsrecarray = [[0, 0, (0, 0, 1), 100.0, wellbottom, 1.0, 0.1]] - wellperiodrecarray = [[0, "rate", 0.0]] - mawo_dict = {} - mawo_dict["maw_obs.csv"] = [("mh1", "head", 1)] - maw = flopy.mf6.ModflowGwfmaw( - gwf, - filename=f"{name}.maw", - print_input=True, - print_head=True, - print_flows=True, - save_flows=True, - observations=mawo_dict, - packagedata=wellrecarray, - connectiondata=wellconnectionsrecarray, - perioddata=wellperiodrecarray, - ) - - # output control - oc = flopy.mf6.ModflowGwfoc( - gwf, - budget_filerecord=f"{name}.cbc", - head_filerecord=f"{name}.hds", - headprintrecord=[("COLUMNS", 10, "WIDTH", 15, "DIGITS", 6, "GENERAL")], - saverecord=[("HEAD", "ALL")], - printrecord=[("HEAD", "ALL"), ("BUDGET", "ALL")], - filename=f"{name}.oc", - ) - - return sim, None - - -def eval_maw(sim): - print("evaluating MAW heads...") - - # MODFLOW 6 maw results - fpth = os.path.join(sim.simpath, "maw_obs.csv") - try: - tc = np.genfromtxt(fpth, names=True, delimiter=",") - except: - assert False, f'could not load data from "{fpth}"' - - # create known results array - tc0 = np.array([100.0, 25.0, 100.0]) - - # calculate maximum absolute error - diff = tc["MH1"] - tc0 - diffmax = np.abs(diff).max() - dtol = 1e-9 - msg = f"maximum absolute maw head difference ({diffmax}) " - - if diffmax > dtol: - sim.success = False - msg += f"exceeds {dtol}" - assert diffmax < dtol, msg - else: - sim.success = True - print(" " + msg) - - return - - -# - No need to change any code below -@pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), -) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the models - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_maw, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # build the models - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_maw, idxsim=idx) - test.run_mf6(sim) - - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() diff --git a/autotest/test_gwf_maw02.py b/autotest/test_gwf_maw02.py deleted file mode 100644 index 745264a71cd..00000000000 --- a/autotest/test_gwf_maw02.py +++ /dev/null @@ -1,357 +0,0 @@ -import os -import sys - -import numpy as np -import pytest - -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation - -ex = ["maw02"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) -ddir = "data" - -budtol = 1e-2 -bud_lst = ["GWF_IN", "GWF_OUT", "RATE_IN", "RATE_OUT"] - -# spatial and temporal data -nlay, nrow, ncol = 1, 1, 3 -shape3d = (nlay, nrow, ncol) -size3d = nlay * nrow * ncol -nper = 5 -perlen = nper * [1.0] -nstp = nper * [1] -tsmult = nper * [1.0] -steady = nper * [True] -lenx = 300.0 -delr = delc = lenx / float(nrow) -botm = [0.0] -strt = 100.0 -hnoflo = 1e30 -hdry = -1e30 -hk = 1.0 - -nouter, ninner = 100, 300 -hclose, rclose, relax = 1e-9, 1e-3, 1.0 -krylov = ["CG"] - -tdis_rc = [] -for i in range(nper): - tdis_rc.append((perlen[i], nstp[i], tsmult[i])) - - -def build_model(idx, dir): - - name = ex[idx] - - # build MODFLOW 6 files - ws = dir - sim = flopy.mf6.MFSimulation( - sim_name=name, version="mf6", exe_name="mf6", sim_ws=ws - ) - # create tdis package - tdis = flopy.mf6.ModflowTdis( - sim, time_units="DAYS", nper=nper, perioddata=tdis_rc - ) - - # create gwf model - gwf = flopy.mf6.MFModel( - sim, - model_type="gwf6", - modelname=name, - model_nam_file=f"{name}.nam", - ) - - # create iterative model solution and register the gwf model with it - ims = flopy.mf6.ModflowIms( - sim, - print_option="SUMMARY", - outer_dvclose=hclose, - outer_maximum=nouter, - under_relaxation="NONE", - inner_maximum=ninner, - inner_dvclose=hclose, - rcloserecord=rclose, - linear_acceleration=krylov[idx], - scaling_method="NONE", - reordering_method="NONE", - relaxation_factor=relax, - ) - sim.register_ims_package(ims, [gwf.name]) - - dis = flopy.mf6.ModflowGwfdis( - gwf, - nlay=nlay, - nrow=nrow, - ncol=ncol, - delr=delr, - delc=delc, - top=100.0, - botm=0.0, - idomain=1, - filename=f"{name}.dis", - ) - - # initial conditions - ic = flopy.mf6.ModflowGwfic(gwf, strt=strt, filename=f"{name}.ic") - - # node property flow - npf = flopy.mf6.ModflowGwfnpf( - gwf, - save_flows=True, - icelltype=1, - k=hk, - k33=hk, - filename=f"{name}.npf", - ) - # storage - sto = flopy.mf6.ModflowGwfsto( - gwf, - save_flows=True, - iconvert=1, - ss=0.0, - sy=0.1, - steady_state={0: True}, - # transient={1: False}, - filename=f"{name}.sto", - ) - - # chd files - chdlist0 = [] - chdlist0.append([(0, 0, 0), 100.0]) - chdlist0.append([(0, 0, 2), 100.0]) - - chdlist1 = [] - chdlist1.append([(0, 0, 0), 25.0]) - chdlist1.append([(0, 0, 2), 25.0]) - - chdspdict = {0: chdlist0, 1: chdlist1, 2: chdlist0} - chd = flopy.mf6.ModflowGwfchd( - gwf, - stress_period_data=chdspdict, - save_flows=False, - filename=f"{name}.chd", - ) - - # MAW - opth = f"{name}.maw.obs" - wellbottom = 0.0 - wellrecarray = [ - [0, 0.1, wellbottom, 100.0, "THIEM", 1], - [1, 0.1, wellbottom, 100.0, "THIEM", 1], - ] - wellconnectionsrecarray = [ - [0, 0, (0, 0, 1), 100.0, wellbottom, 1.0, 0.1], - [1, 0, (0, 0, 1), 100.0, wellbottom, 1.0, 0.1], - ] - wellperiodrecarray = { - 0: [ - [0, "rate", -20.0], - [0, "status", "inactive"], - [0, "rate_scaling", 1.0, 15.0], - [1, "rate", -30.0], - [1, "status", "inactive"], - [1, "rate_scaling", 5.0, 15.0], - ], - 1: [ - [0, "rate", -110.0], - [0, "status", "active"], - [1, "rate", -130.0], - [1, "status", "active"], - ], - 3: [[0, "status", "inactive"]], - 4: [[0, "status", "active"]], - } - mawo_dict = {} - mawo_dict["maw_obs.csv"] = [("mh1", "head", 1)] - maw = flopy.mf6.ModflowGwfmaw( - gwf, - filename=f"{name}.maw", - budget_filerecord=f"{name}.maw.cbc", - print_input=True, - print_head=True, - print_flows=True, - save_flows=True, - observations=mawo_dict, - packagedata=wellrecarray, - connectiondata=wellconnectionsrecarray, - perioddata=wellperiodrecarray, - pname="MAW-1", - ) - - # output control - oc = flopy.mf6.ModflowGwfoc( - gwf, - budget_filerecord=f"{name}.cbc", - head_filerecord=f"{name}.hds", - headprintrecord=[("COLUMNS", 10, "WIDTH", 15, "DIGITS", 6, "GENERAL")], - saverecord=[("HEAD", "ALL"), ("BUDGET", "ALL")], - printrecord=[("HEAD", "ALL"), ("BUDGET", "ALL")], - filename=f"{name}.oc", - ) - - return sim, None - - -def eval_maw(sim): - print("evaluating MAW budgets...") - - # get results from listing file - fpth = os.path.join(sim.simpath, f"{os.path.basename(sim.name)}.lst") - budl = flopy.utils.Mf6ListBudget( - fpth, budgetkey="MAW-1 BUDGET FOR ENTIRE MODEL AT END OF TIME STEP" - ) - names = list(bud_lst) - d0 = budl.get_budget(names=names)[0] - dtype = d0.dtype - nbud = d0.shape[0] - - # get results from cbc file - cbc_bud = ["GWF", "RATE"] - d = np.recarray(nbud, dtype=dtype) - for key in bud_lst: - d[key] = 0.0 - fpth = os.path.join(sim.simpath, f"{os.path.basename(sim.name)}.maw.cbc") - cobj = flopy.utils.CellBudgetFile(fpth, precision="double") - kk = cobj.get_kstpkper() - times = cobj.get_times() - cbc_vals = [] - for idx, (k, t) in enumerate(zip(kk, times)): - for text in cbc_bud: - qin = 0.0 - qout = 0.0 - v = cobj.get_data(kstpkper=k, text=text)[0] - if isinstance(v, np.recarray): - vt = np.zeros(size3d, dtype=float) - wq = [] - for jdx, node in enumerate(v["node"]): - vt[node - 1] += v["q"][jdx] - wq.append(v["q"][jdx]) - v = vt.reshape(shape3d) - if text == cbc_bud[-1]: - cbc_vals.append(wq) - for kk in range(v.shape[0]): - for ii in range(v.shape[1]): - for jj in range(v.shape[2]): - vv = v[kk, ii, jj] - if vv < 0.0: - qout -= vv - else: - qin += vv - d["totim"][idx] = t - d["time_step"][idx] = k[0] - d["stress_period"] = k[1] - key = f"{text}_IN" - d[key][idx] = qin - key = f"{text}_OUT" - d[key][idx] = qout - - maw_vals = [ - [0.000, 0.000], - [-106.11303563809453, -96.22598985147631], - [-110.000, -130.000], - [0.0, -130.000], - [-110.000, -130.000], - ] - - # evaluate if well rates in cbc file are equal to expected values - diffv = [] - for ovs, svs in zip(maw_vals, cbc_vals): - for ov, sv in zip(ovs, svs): - diffv.append(ov - sv) - diffv = np.abs(np.array(diffv)).max() - msg = f"\nmaximum absolute maw rate difference ({diffv})\n" - - # calculate difference between water budget items in the lst and cbc files - diff = np.zeros((nbud, len(bud_lst)), dtype=float) - for idx, key in enumerate(bud_lst): - diff[:, idx] = d0[key] - d[key] - diffmax = np.abs(diff).max() - msg += f"maximum absolute total-budget difference ({diffmax}) " - - # write summary - fpth = os.path.join( - sim.simpath, f"{os.path.basename(sim.name)}.bud.cmp.out" - ) - f = open(fpth, "w") - for i in range(diff.shape[0]): - if i == 0: - line = f"{'TIME':>10s}" - for idx, key in enumerate(bud_lst): - line += f"{key + '_LST':>25s}" - line += f"{key + '_CBC':>25s}" - line += f"{key + '_DIF':>25s}" - f.write(line + "\n") - line = f"{d['totim'][i]:10g}" - for idx, key in enumerate(bud_lst): - line += f"{d0[key][i]:25g}" - line += f"{d[key][i]:25g}" - line += f"{diff[i, idx]:25g}" - f.write(line + "\n") - f.close() - - if diffmax > budtol or diffv > budtol: - sim.success = False - msg += f"\n...exceeds {budtol}" - assert diffmax < budtol, msg - else: - sim.success = True - print(" " + msg) - - return - - -# - No need to change any code below -@pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), -) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the models - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_maw, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # build the models - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_maw, idxsim=idx) - test.run_mf6(sim) - - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() diff --git a/autotest/test_gwf_maw03.py b/autotest/test_gwf_maw03.py deleted file mode 100644 index 0ef3963d939..00000000000 --- a/autotest/test_gwf_maw03.py +++ /dev/null @@ -1,275 +0,0 @@ -""" -MODFLOW 6 Autotest -Test the MAW HEAD_LIMIT and RATE_SCALING options for injection wells. These -options were not originally supported in MODFLOW 6. They were added for -version 6.0.4. - -""" - -import os -import sys - -import numpy as np -import pytest - -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation - -ex = ["maw03a", "maw03b", "maw03c"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) - -# maw settings for runs a, b, and c -mawsetting_a = [ - (0, "rate", 2000.0), -] -mawsetting_b = [(0, "rate", 2000.0), (0, "head_limit", 0.4)] -mawsetting_c = [(0, "rate", 2000.0), (0, "rate_scaling", 0.0, 1.0)] -mawsettings = [mawsetting_a, mawsetting_b, mawsetting_c] - - -def build_model(idx, dir): - - nlay, nrow, ncol = 1, 101, 101 - nper = 1 - perlen = [1000.0] - nstp = [50] - tsmult = [1.2] - delr = delc = 142.0 - top = 0.0 - botm = [-1000.0] - strt = 0.0 - hk = 10.0 - - nouter, ninner = 100, 100 - hclose, rclose, relax = 1e-6, 1e-6, 1.0 - - tdis_rc = [] - for i in range(nper): - tdis_rc.append((perlen[i], nstp[i], tsmult[i])) - - name = ex[idx] - - # build MODFLOW 6 files - ws = dir - sim = flopy.mf6.MFSimulation(sim_name=name, sim_ws=ws) - - # create tdis package - tdis = flopy.mf6.ModflowTdis( - sim, time_units="DAYS", nper=nper, perioddata=tdis_rc - ) - - # create gwf model - gwf = flopy.mf6.MFModel( - sim, - model_type="gwf6", - modelname=name, - model_nam_file=f"{name}.nam", - ) - - # create iterative model solution and register the gwf model with it - ims = flopy.mf6.ModflowIms( - sim, - print_option="SUMMARY", - outer_dvclose=hclose, - outer_maximum=nouter, - under_relaxation="NONE", - inner_maximum=ninner, - inner_dvclose=hclose, - rcloserecord=rclose, - linear_acceleration="CG", - scaling_method="NONE", - reordering_method="NONE", - relaxation_factor=relax, - ) - sim.register_ims_package(ims, [gwf.name]) - - dis = flopy.mf6.ModflowGwfdis( - gwf, - nlay=nlay, - nrow=nrow, - ncol=ncol, - delr=delr, - delc=delc, - top=top, - botm=botm, - idomain=1, - filename=f"{name}.dis", - ) - - # initial conditions - ic = flopy.mf6.ModflowGwfic(gwf, strt=strt, filename=f"{name}.ic") - - # node property flow - npf = flopy.mf6.ModflowGwfnpf( - gwf, - save_flows=True, - icelltype=1, - k=hk, - k33=hk, - filename=f"{name}.npf", - ) - - # storage - sto = flopy.mf6.ModflowGwfsto( - gwf, - save_flows=True, - iconvert=0, - ss=1.0e-5, - sy=0.1, - steady_state={0: False}, - transient={0: True}, - filename=f"{name}.sto", - ) - - # MAW - opth = f"{name}.maw.obs" - wellbottom = -1000 - wellrecarray = [[0, 0.15, wellbottom, 0.0, "THIEM", 1]] - wellconnectionsrecarray = [[0, 0, (0, 50, 50), 0.0, wellbottom, 0.0, 0.0]] - wellperiodrecarray = mawsettings[idx] - mawo_dict = {} - mawo_dict[f"{name}.maw.obs.csv"] = [ - ("m1head", "head", (0,)), - ("m1rate", "rate", (0,)), - ] # is this index one-based? Not if in a tuple - maw = flopy.mf6.ModflowGwfmaw( - gwf, - filename=f"{name}.maw", - print_input=True, - print_head=True, - print_flows=True, - save_flows=True, - observations=mawo_dict, - packagedata=wellrecarray, - connectiondata=wellconnectionsrecarray, - perioddata=wellperiodrecarray, - ) - - # output control - oc = flopy.mf6.ModflowGwfoc( - gwf, - budget_filerecord=f"{name}.cbc", - head_filerecord=f"{name}.hds", - headprintrecord=[ - ("COLUMNS", ncol, "WIDTH", 15, "DIGITS", 6, "GENERAL") - ], - saverecord=[("HEAD", "ALL")], - printrecord=[("HEAD", "ALL"), ("BUDGET", "ALL")], - filename=f"{name}.oc", - ) - - # head observations - obs_data0 = [("head_well_cell", "HEAD", (0, 0, 0))] - obs_recarray = {f"{name}.obs.csv": obs_data0} - obs = flopy.mf6.ModflowUtlobs( - gwf, - pname="head_obs", - filename=f"{name}.obs", - digits=15, - print_input=True, - continuous=obs_recarray, - ) - - return sim, None - - -def eval_maw(sim): - print("evaluating MAW heads...") - - # MODFLOW 6 maw results - idx = sim.idxsim - name = ex[idx] - fpth = os.path.join(sim.simpath, f"{name}.maw.obs.csv") - try: - tc = np.genfromtxt(fpth, names=True, delimiter=",") - except: - assert False, f'could not load data from "{fpth}"' - - if idx == 0: - - # M1RATE should be 2000. - msg = "The injection rate should be 2000. for all times" - assert tc["M1RATE"].min() == tc["M1RATE"].max() == 2000, msg - - elif idx == 1: - - # M1RATE should have a minimum value less than 200 and - # M1HEAD should not exceed 0.400001 - msg = ( - "Injection rate should fall below 200 and the head should not" - "exceed 0.4" - ) - assert tc["M1RATE"].min() < 200.0, msg - assert tc["M1HEAD"].max() < 0.400001, msg - - elif idx == 2: - - # M1RATE should have a minimum value less than 800 - # M1HEAD should not exceed 1.0. - msg = ( - "Min injection rate should be less than 800 and well " - "head should not exceed 1.0" - ) - assert tc["M1RATE"].min() < 800.0 and tc["M1HEAD"].max() < 1.0, msg - - else: - - assert False, f"Test error. idx {idx} not being tested." - - return - - -# - No need to change any code below -@pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), -) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the models - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_maw, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # build the models - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_maw, idxsim=idx) - test.run_mf6(sim) - - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() diff --git a/autotest/test_gwf_maw04.py b/autotest/test_gwf_maw04.py index 403fc8905e4..e77656f0c68 100644 --- a/autotest/test_gwf_maw04.py +++ b/autotest/test_gwf_maw04.py @@ -1,52 +1,10 @@ import os -import sys +from types import SimpleNamespace +import flopy import numpy as np -import pytest - -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import running_on_CI, testing_framework -from simulation import Simulation - -ex = [ - "maw_iss305a", - "maw_iss305b", - "maw_iss305c", - "maw_iss305d", - "maw_iss305e", - "maw_iss305f", -] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) -ddir = "data" -cmppth = "mf2005" - -paktest = "maw" - -require_failure = [True for i in range(len(exdirs))] -require_failure[0] = False - -# set travis to True when version 1.13.0 is released -continuous_integration = [True for n in ex] - -# set replace_exe to None to use default executable -replace_exe = None +from modflow_devtools.case import Case +from pytest_cases import parametrize # temporal discretization nper = 2 @@ -64,6 +22,7 @@ common_ratio = 1.01 nhalf = int(0.5 * ncol) + 1 first_term = 0.5 * xlen / ((1 - common_ratio**nhalf) / (1 - common_ratio)) + delr = np.zeros((ncol), dtype=float) for n in range(nhalf): if n == 0: @@ -105,195 +64,235 @@ radius = 0.25 sradius0 = radius + 0.1 wellq = -100.0 -skin_mult = [0.1, 10.0, 1.0, 0.0, -1.0, 100.0] -condeqn = ["CUMULATIVE", "SKIN", "SKIN", "SKIN", "SPECIFIED", "CUMULATIVE"] -sradius = [sradius0, sradius0, sradius0, sradius0, sradius0, radius0 * 1.5] - -tdis_rc = [] -for idx in range(nper): - tdis_rc.append((perlen[idx], nstp[idx], tsmult[idx])) - +skin_mult = {"a": 0.1, "b": 10.0, "c": 1.0, "d": 0.0, "e": -1.0, "f": 100.0} +condeqn = { + "a": "CUMULATIVE", + "b": "SKIN", + "c": "SKIN", + "d": "SKIN", + "e": "SPECIFIED", + "f": "CUMULATIVE", +} +sradius = { + "a": sradius0, + "b": sradius0, + "c": sradius0, + "d": sradius0, + "e": sradius0, + "f": radius0 * 1.5, +} hclose, rclose = 1e-9, 1e-6 -def build_model(idx, dir): - name = ex[idx] - ws = dir - - # build MODFLOW 6 files - sim = flopy.mf6.MFSimulation( - sim_name=name, version="mf6", exe_name="mf6", sim_ws=ws - ) - # create tdis package - tdis = flopy.mf6.ModflowTdis( - sim, time_units="DAYS", nper=nper, perioddata=tdis_rc - ) - - # create iterative model solution - ims = flopy.mf6.ModflowIms( - sim, inner_dvclose=hclose, rcloserecord=rclose, outer_dvclose=hclose - ) - - # create gwf model - gwf = flopy.mf6.ModflowGwf(sim, modelname=name, save_flows=True) - - # discretization - dis = flopy.mf6.ModflowGwfdis( - gwf, - nlay=nlay, - nrow=nrow, - ncol=ncol, - delr=delr, - delc=delr, - top=top, - botm=botm, - ) - # initial conditions - ic = flopy.mf6.ModflowGwfic(gwf, strt=strt) - - # node property flow - npf = flopy.mf6.ModflowGwfnpf( - gwf, save_flows=False, icelltype=confined, k=hk - ) - # storage - sto = flopy.mf6.ModflowGwfsto( - gwf, - save_flows=False, - iconvert=confined, - ss=ss, - steady_state={0: True}, - transient={1: True}, - ) - # constant head - chd = flopy.mf6.ModflowGwfchd( - gwf, stress_period_data=chd_spd, save_flows=False - ) - # multi-aquifer well - hks = hk * skin_mult[idx] - mpd = [[0, radius, botm[-1], strt, condeqn[idx], 2]] - mcd = [ - [0, 0, (0, nhalf, nhalf), top, botm[0], hks, sradius[idx]], - [0, 1, (1, nhalf, nhalf), botm[0], botm[1], hks, sradius[idx]], +def well4(label): + hks = hk * skin_mult[label] + packagedata = [[0, radius, botm[-1], strt, condeqn[label], 2]] + connectiondata = [ + [0, 0, (0, nhalf, nhalf), top, botm[0], hks, sradius[label]], + [0, 1, (1, nhalf, nhalf), botm[0], botm[1], hks, sradius[label]], ] perioddata = {1: [[0, "RATE", wellq]]} - maw = flopy.mf6.ModflowGwfmaw( - gwf, + return SimpleNamespace( print_input=True, no_well_storage=True, - packagedata=mpd, - connectiondata=mcd, + packagedata=packagedata, + connectiondata=connectiondata, perioddata=perioddata, ) - # output control - oc = flopy.mf6.ModflowGwfoc( - gwf, - budget_filerecord=f"{name}.cbc", - head_filerecord=f"{name}.hds", - saverecord=[("HEAD", "ALL"), ("BUDGET", "ALL")], - ) - # build MODFLOW-2005 files - if require_failure[idx]: - mc = None - else: - ws = os.path.join(dir, cmppth) - mc = flopy.modflow.Modflow(name, model_ws=ws, version=cmppth) - dis = flopy.modflow.ModflowDis( - mc, - nlay=nlay, - nrow=nrow, - ncol=ncol, - nper=nper, - perlen=perlen, - nstp=nstp, - tsmult=tsmult, - steady=steady, - delr=delr, - delc=delr, - top=top, - botm=botm, - ) - bas = flopy.modflow.ModflowBas(mc, strt=strt) - lpf = flopy.modflow.ModflowLpf( - mc, laytyp=confined, hk=hk, vka=hk, ss=ss, sy=0 - ) - chd = flopy.modflow.ModflowChd(mc, stress_period_data=chd5_spd) - # mnw2 - # empty mnw2 file to create recarrays - mnw2 = flopy.modflow.ModflowMnw2(mc) - node_data = mnw2.get_empty_node_data(2) - node_data["ztop"] = np.array([top, botm[0]]) - node_data["zbotm"] = np.array([botm[0], botm[1]]) - node_data["i"] = np.array([nhalf, nhalf]) - node_data["j"] = np.array([nhalf, nhalf]) - node_data["wellid"] = np.array(["well1", "well1"]) - node_data["losstype"] = np.array(["skin", "skin"]) - node_data["rw"] = np.array([radius, radius]) - node_data["rskin"] = np.array([sradius[idx], sradius[idx]]) - node_data["kskin"] = np.array([hks, hks]) - dtype = [("wellid", np.unicode_, 20), ("qdes", " dtol: + config.success = False + msg += f"exceeds {dtol}" + assert diffmax < dtol, msg + else: + config.success = True + print(" " + msg) + + case2 = Case( + name="maw02", + krylov="CG", + nlay=1, + nrow=1, + ncol=3, + nper=5, + delr=300, + delc=300, + perlen=5 * [1], + nstp=5 * [1], + tsmult=5 * [1], + well=well2, + strt=100, + hk=1, + nouter=100, + ninner=300, + hclose=1e-9, + rclose=1e-3, + relaxation_factor=1, + compare=False, + ) + cases2 = [case2] + + @parametrize(data=cases2, ids=[c.name for c in cases2]) + def case_2(self, function_tmpdir, targets, data): + name = data.name + ws = str(function_tmpdir) + sim = flopy.mf6.MFSimulation( + sim_name=name, version="mf6", exe_name=targets["mf6"], sim_ws=ws + ) + + # create tdis package + tdis_rc = [ + (data.perlen[i], data.nstp[i], data.tsmult[i]) + for i in range(data.nper) + ] + tdis = flopy.mf6.ModflowTdis( + sim, time_units="DAYS", nper=data.nper, perioddata=tdis_rc + ) + + # create gwf model + gwf = flopy.mf6.MFModel( + sim, + model_type="gwf6", + modelname=name, + model_nam_file=f"{name}.nam", + ) + + # create iterative model solution and register the gwf model with it + ims = flopy.mf6.ModflowIms( + sim, + print_option="SUMMARY", + outer_dvclose=data.hclose, + outer_maximum=data.nouter, + under_relaxation="NONE", + inner_maximum=data.ninner, + inner_dvclose=data.hclose, + rcloserecord=data.rclose, + linear_acceleration=data.krylov, + scaling_method="NONE", + reordering_method="NONE", + relaxation_factor=data.relaxation_factor, + ) + sim.register_ims_package(ims, [gwf.name]) + + dis = flopy.mf6.ModflowGwfdis( + gwf, + nlay=data.nlay, + nrow=data.nrow, + ncol=data.ncol, + delr=data.delr, + delc=data.delc, + top=100.0, + botm=0.0, + idomain=1, + filename=f"{name}.dis", + ) + + # initial conditions + ic = flopy.mf6.ModflowGwfic(gwf, strt=data.strt, filename=f"{name}.ic") + + # node property flow + npf = flopy.mf6.ModflowGwfnpf( + gwf, + save_flows=True, + icelltype=1, + k=data.hk, + k33=data.hk, + filename=f"{name}.npf", + ) + # storage + sto = flopy.mf6.ModflowGwfsto( + gwf, + save_flows=True, + iconvert=1, + ss=0.0, + sy=0.1, + steady_state={0: True}, + # transient={1: False}, + filename=f"{name}.sto", + ) + + # chd files + chdlist0 = [] + chdlist0.append([(0, 0, 0), 100.0]) + chdlist0.append([(0, 0, 2), 100.0]) + + chdlist1 = [] + chdlist1.append([(0, 0, 0), 25.0]) + chdlist1.append([(0, 0, 2), 25.0]) + + chdspdict = {0: chdlist0, 1: chdlist1, 2: chdlist0} + chd = flopy.mf6.ModflowGwfchd( + gwf, + stress_period_data=chdspdict, + save_flows=False, + filename=f"{name}.chd", + ) + + # MAW + maw = flopy.mf6.ModflowGwfmaw( + gwf, + filename=f"{name}.maw", + budget_filerecord=f"{name}.maw.cbc", + print_input=True, + print_head=True, + print_flows=True, + save_flows=True, + observations=data.well.observations, + packagedata=data.well.packagedata, + connectiondata=data.well.connectiondata, + perioddata=data.well.perioddata, + pname="MAW-1", + ) + + # output control + oc = flopy.mf6.ModflowGwfoc( + gwf, + budget_filerecord=f"{name}.cbc", + head_filerecord=f"{name}.hds", + headprintrecord=[ + ("COLUMNS", 10, "WIDTH", 15, "DIGITS", 6, "GENERAL") + ], + saverecord=[("HEAD", "ALL"), ("BUDGET", "ALL")], + printrecord=[("HEAD", "ALL"), ("BUDGET", "ALL")], + filename=f"{name}.oc", + ) + + return data, sim, None, self.eval_2 + + def eval_2(self, config, data): + print("evaluating MAW budgets...") + + shape3d = (data.nlay, data.nrow, data.ncol) + size3d = data.nlay * data.nrow * data.ncol + + # get results from listing file + fpth = os.path.join( + config.simpath, f"{os.path.basename(config.name)}.lst" + ) + budl = flopy.utils.Mf6ListBudget( + fpth, budgetkey="MAW-1 BUDGET FOR ENTIRE MODEL AT END OF TIME STEP" + ) + names = list(self.bud_lst) + d0 = budl.get_budget(names=names)[0] + dtype = d0.dtype + nbud = d0.shape[0] + + # get results from cbc file + cbc_bud = ["GWF", "RATE"] + d = np.recarray(nbud, dtype=dtype) + for key in self.bud_lst: + d[key] = 0.0 + fpth = os.path.join( + config.simpath, f"{os.path.basename(config.name)}.maw.cbc" + ) + cobj = flopy.utils.CellBudgetFile(fpth, precision="double") + kk = cobj.get_kstpkper() + times = cobj.get_times() + cbc_vals = [] + for idx, (k, t) in enumerate(zip(kk, times)): + for text in cbc_bud: + qin = 0.0 + qout = 0.0 + v = cobj.get_data(kstpkper=k, text=text)[0] + if isinstance(v, np.recarray): + vt = np.zeros(size3d, dtype=float) + wq = [] + for jdx, node in enumerate(v["node"]): + vt[node - 1] += v["q"][jdx] + wq.append(v["q"][jdx]) + v = vt.reshape(shape3d) + if text == cbc_bud[-1]: + cbc_vals.append(wq) + for kk in range(v.shape[0]): + for ii in range(v.shape[1]): + for jj in range(v.shape[2]): + vv = v[kk, ii, jj] + if vv < 0.0: + qout -= vv + else: + qin += vv + d["totim"][idx] = t + d["time_step"][idx] = k[0] + d["stress_period"] = k[1] + key = f"{text}_IN" + d[key][idx] = qin + key = f"{text}_OUT" + d[key][idx] = qout + + maw_vals = [ + [0.000, 0.000], + [-106.11303563809453, -96.22598985147631], + [-110.000, -130.000], + [0.0, -130.000], + [-110.000, -130.000], + ] + + # evaluate if well rates in cbc file are equal to expected values + diffv = [] + for ovs, svs in zip(maw_vals, cbc_vals): + for ov, sv in zip(ovs, svs): + diffv.append(ov - sv) + diffv = np.abs(np.array(diffv)).max() + msg = f"\nmaximum absolute maw rate difference ({diffv})\n" + + # calculate difference between water budget items in the lst and cbc files + diff = np.zeros((nbud, len(self.bud_lst)), dtype=float) + for idx, key in enumerate(self.bud_lst): + diff[:, idx] = d0[key] - d[key] + diffmax = np.abs(diff).max() + msg += f"maximum absolute total-budget difference ({diffmax}) " + + # write summary + fpth = os.path.join( + config.simpath, f"{os.path.basename(config.name)}.bud.cmp.out" + ) + f = open(fpth, "w") + for i in range(diff.shape[0]): + if i == 0: + line = f"{'TIME':>10s}" + for idx, key in enumerate(self.bud_lst): + line += f"{key + '_LST':>25s}" + line += f"{key + '_CBC':>25s}" + line += f"{key + '_DIF':>25s}" + f.write(line + "\n") + line = f"{d['totim'][i]:10g}" + for idx, key in enumerate(self.bud_lst): + line += f"{d0[key][i]:25g}" + line += f"{d[key][i]:25g}" + line += f"{diff[i, idx]:25g}" + f.write(line + "\n") + f.close() + + if diffmax > self.budtol or diffv > self.budtol: + config.success = False + msg += f"\n...exceeds {self.budtol}" + assert diffmax < self.budtol, msg + else: + config.success = True + print(" " + msg) + + case3 = Case( + name="maw03", + krylov="CG", + nlay=1, + nrow=101, + ncol=101, + nper=1, + delr=142, + delc=142, + perlen=[1000], + nstp=[50], + tsmult=[1.2], + strt=0, + hk=10, + nouter=100, + ninner=100, + hclose=1e-6, + rclose=1e-6, + relaxation_factor=1, + compare=False, + ) + cases3 = [ + case3.copy_update( + name="maw03a", + well=well3("maw03a"), + ), + case3.copy_update(name="maw03b", well=well3("maw03b")), + case3.copy_update(name="maw03c", well=well3("maw03c")), + ] + + @parametrize(data=cases3, ids=[c.name for c in cases3]) + def case_3(self, function_tmpdir, targets, data): + top = 0.0 + botm = [-1000.0] + + tdis_rc = [] + for i in range(data.nper): + tdis_rc.append((data.perlen[i], data.nstp[i], data.tsmult[i])) + + name = data.name + ws = str(function_tmpdir) + sim = flopy.mf6.MFSimulation( + sim_name=name, sim_ws=ws, exe_name=targets["mf6"] + ) + + # create tdis package + tdis = flopy.mf6.ModflowTdis( + sim, time_units="DAYS", nper=data.nper, perioddata=tdis_rc + ) + + # create gwf model + gwf = flopy.mf6.MFModel( + sim, + model_type="gwf6", + modelname=name, + model_nam_file=f"{name}.nam", + ) + + # create iterative model solution and register the gwf model with it + ims = flopy.mf6.ModflowIms( + sim, + print_option="SUMMARY", + outer_dvclose=data.hclose, + outer_maximum=data.nouter, + under_relaxation="NONE", + inner_maximum=data.ninner, + inner_dvclose=data.hclose, + rcloserecord=data.rclose, + linear_acceleration=data.krylov, + scaling_method="NONE", + reordering_method="NONE", + relaxation_factor=data.relaxation_factor, + ) + sim.register_ims_package(ims, [gwf.name]) + + dis = flopy.mf6.ModflowGwfdis( + gwf, + nlay=data.nlay, + nrow=data.nrow, + ncol=data.ncol, + delr=data.delr, + delc=data.delc, + top=top, + botm=botm, + idomain=1, + filename=f"{name}.dis", + ) + + # initial conditions + ic = flopy.mf6.ModflowGwfic(gwf, strt=data.strt, filename=f"{name}.ic") + + # node property flow + npf = flopy.mf6.ModflowGwfnpf( + gwf, + save_flows=True, + icelltype=1, + k=data.hk, + k33=data.hk, + filename=f"{name}.npf", + ) + + # storage + sto = flopy.mf6.ModflowGwfsto( + gwf, + save_flows=True, + iconvert=0, + ss=1.0e-5, + sy=0.1, + steady_state={0: False}, + transient={0: True}, + filename=f"{name}.sto", + ) + + # MAW + maw = flopy.mf6.ModflowGwfmaw( + gwf, + filename=f"{name}.maw", + print_input=True, + print_head=True, + print_flows=True, + save_flows=True, + observations=data.well.observations, + packagedata=data.well.packagedata, + connectiondata=data.well.connectiondata, + perioddata=data.well.perioddata, + ) + + # output control + oc = flopy.mf6.ModflowGwfoc( + gwf, + budget_filerecord=f"{name}.cbc", + head_filerecord=f"{name}.hds", + headprintrecord=[ + ("COLUMNS", data.ncol, "WIDTH", 15, "DIGITS", 6, "GENERAL") + ], + saverecord=[("HEAD", "ALL")], + printrecord=[("HEAD", "ALL"), ("BUDGET", "ALL")], + filename=f"{name}.oc", + ) + + # head observations + obs_data0 = [("head_well_cell", "HEAD", (0, 0, 0))] + obs_recarray = {f"{name}.obs.csv": obs_data0} + obs = flopy.mf6.ModflowUtlobs( + gwf, + pname="head_obs", + filename=f"{name}.obs", + digits=15, + print_input=True, + continuous=obs_recarray, + ) + + return data, sim, None, self.eval_3 + + def eval_3(self, config, data): + print("evaluating MAW heads...") + + # MODFLOW 6 maw results + test_name = config.name + case_name = data.name + fpth = os.path.join(config.simpath, f"{test_name}.maw.obs.csv") + tc = np.genfromtxt(fpth, names=True, delimiter=",") + + if case_name == "a": + + # M1RATE should be 2000. + msg = "The injection rate should be 2000. for all times" + assert tc["M1RATE"].min() == tc["M1RATE"].max() == 2000, msg + + elif case_name == "b": + + # M1RATE should have a minimum value less than 200 and + # M1HEAD should not exceed 0.400001 + msg = ( + "Injection rate should fall below 200 and the head should not" + "exceed 0.4" + ) + assert tc["M1RATE"].min() < 200.0, msg + assert tc["M1HEAD"].max() < 0.400001, msg + + elif case_name == "c": + + # M1RATE should have a minimum value less than 800 + # M1HEAD should not exceed 1.0. + msg = ( + "Min injection rate should be less than 800 and well " + "head should not exceed 1.0" + ) + assert tc["M1RATE"].min() < 800.0 and tc["M1HEAD"].max() < 1.0, msg + + # TODO + # case4 = Case( + # name='maw_iss305', + # krylov='CG', + # nlay=2, + # nrow=101, + # ncol=101, + # nper=2, + # delr=142, + # delc=142, + # perlen=[0.0, 365.0], + # nstp=[1, 25], + # tsmult=[1.0, 1.1], + # steady=[True, False], + # well=None, + # strt=0, + # hk=10, + # nouter=100, + # ninner=100, + # hclose=1e-9, + # rclose=1e-6, + # relax=1, + # require_failure=True + # ) + # cases4 = [ + # case4.copy_update({ + # 'name': "maw_iss305a", + # 'well': well4(case4, "CUMULATIVE"), + # 'require_failure': False + # }), + # case4.copy_update({ + # 'name': "maw_iss305b", + # 'well': well4(case4, "SKIN") + # }), + # case4.copy_update({ + # 'name': "maw_iss305c", + # 'well': well4(case4, "SKIN") + # }), + # case4.copy_update({ + # 'name': "maw_iss305d", + # 'well': well4(case4, "SKIN") + # }), + # case4.copy_update({ + # 'name': "maw_iss305e", + # 'well': well4(case4, "SPECIFIED") + # }), + # case4.copy_update({ + # 'name': "maw_iss305f", + # 'well': well4(case4, "CUMULATIVE") + # }) + # ] + + # @parametrize(data=cases4, ids=[c['name'] for c in cases4]) + # def case_4(self, function_tmpdir, targets, data): + # pass + + # def eval_4(self, sim, data): + # pass diff --git a/autotest/test_gwf_maw_obs.py b/autotest/test_gwf_maw_obs.py index 21ee3503b39..7455949fc37 100644 --- a/autotest/test_gwf_maw_obs.py +++ b/autotest/test_gwf_maw_obs.py @@ -6,39 +6,14 @@ import os -import shutil -import numpy as np -import pytest - -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -import targets - -mf6_exe = os.path.abspath(targets.target_dict["mf6"]) +import flopy newtonoptions = [None, "NEWTON", "NEWTON UNDER_RELAXATION"] ex = "maw_obs" -exdir = os.path.join("temp", ex) -ddir = "data" - -def build_model(): +def build_model(dir, exe): nlay, nrow, ncol = 1, 1, 3 nper = 3 @@ -63,9 +38,9 @@ def build_model(): name = ex # build MODFLOW 6 files - ws = exdir + ws = dir sim = flopy.mf6.MFSimulation( - sim_name=name, version="mf6", exe_name=mf6_exe, sim_ws=ws + sim_name=name, version="mf6", exe_name=exe, sim_ws=ws ) # create tdis package tdis = flopy.mf6.ModflowTdis( @@ -192,18 +167,14 @@ def build_model(): return sim -def test_mf6model(): - # build the models - sim = build_model() - - # write model input +def test_mf6model(function_tmpdir, targets): + mf6 = targets["mf6"] + sim = build_model(str(function_tmpdir), mf6) sim.write_simulation() - - # attempt to run model should fail sim.run_simulation() # ensure that the error msg is contained in the mfsim.lst file - f = open(os.path.join(exdir, "mfsim.lst"), "r") + f = open(str(function_tmpdir / "mfsim.lst"), "r") lines = f.readlines() error_count = 0 expected_msg = False @@ -217,8 +188,8 @@ def test_mf6model(): ) # fix the error and attempt to rerun model - orig_fl = os.path.join(exdir, ex + ".maw.obs") - new_fl = os.path.join(exdir, ex + ".maw.obs.new") + orig_fl = str(function_tmpdir / (ex + ".maw.obs")) + new_fl = str(function_tmpdir / (ex + ".maw.obs.new")) sr = open(orig_fl, "r") sw = open(new_fl, "w") @@ -241,19 +212,3 @@ def test_mf6model(): success, buff = sim.run_simulation() assert success, "model rerun failed" - - shutil.rmtree(exdir, ignore_errors=True) - - -def main(): - test_mf6model() - - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() diff --git a/autotest/test_gwf_multimvr.py b/autotest/test_gwf_multimvr.py index 59bcef3a48e..9fddf576be0 100644 --- a/autotest/test_gwf_multimvr.py +++ b/autotest/test_gwf_multimvr.py @@ -1,30 +1,14 @@ import math import os -import shutil +import flopy import numpy as np import pytest +from flopy.utils.lgrutil import Lgr +from framework import TestFramework +from simulation import TestSimulation -import targets - -try: - import flopy - from flopy.utils.lgrutil import Lgr -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation - -mf6exe = os.path.abspath(targets.target_dict["mf6"]) - -name = "gwf" mvr_scens = ["mltmvr", "mltmvr5050", "mltmvr7525"] -ws = os.path.join("temp", name) -exdirs = [f"{ws}-{s}" for s in mvr_scens] sim_workspaces = [] gwf_names = [] @@ -583,9 +567,9 @@ def instantiate_base_simulation(sim_ws, gwfname, gwfnamec): sim_workspaces.append(sim_ws) gwf_names.append(gwfname) sim = flopy.mf6.MFSimulation( - sim_name=name, + sim_name="gwf", version="mf6", - exe_name=mf6exe, + exe_name="mf6", sim_ws=sim_ws, continue_=False, ) @@ -837,7 +821,7 @@ def instantiate_base_simulation(sim_ws, gwfname, gwfnamec): nexg=len(exchange_data), exchangedata=exchange_data, pname="EXG-1", - filename=f"{name}.exg", + filename=f"gwf.exg", ) return sim, gwf, gwfc @@ -982,7 +966,7 @@ def add_sim_mvr(sim, gwfname, gwfnamec, remaining_frac=None): maxpackages=maxpackages, packages=mvrpack_sim, perioddata=mvrspd, - filename=f"{name}.mvr", + filename=f"gwf.mvr", ) @@ -993,8 +977,8 @@ def build_model(idx, sim_ws): scen_conns[idx], parent_mvr_frac[idx], ) - scen_nm_parent = name + "_" + scen_nm + "_p" - scen_nm_child = name + "_" + scen_nm + "_c" + scen_nm_parent = "gwf_" + scen_nm + "_p" + scen_nm_child = "gwf_" + scen_nm + "_c" sim, gwf, gwfc = instantiate_base_simulation( sim_ws, scen_nm_parent, scen_nm_child ) @@ -1021,9 +1005,9 @@ def check_simulation_output(sim): gwf_srch_str2 = " WATER MOVER PACKAGE (MVR) FLOW RATES " sim_srch_str = " WATER MOVER PACKAGE (MVR) FLOW RATES " - # cur_ws, gwfparent = exdirs[idx], gwf_names[idx] - cur_ws = exdirs[idx] - gwfparent = name + "_" + mvr_scens[idx] + "_p" + # cur_ws, gwfparent = ex[idx], gwf_names[idx] + cur_ws = sim.simpath + gwfparent = "gwf_" + mvr_scens[idx] + "_p" with open(os.path.join(cur_ws, gwfparent + ".lst"), "r") as gwf_lst, open( os.path.join(cur_ws, "mfsim.lst"), "r" ) as sim_lst: @@ -1130,48 +1114,19 @@ def check_simulation_output(sim): ) -# - No need to change any code below - - @pytest.mark.parametrize( - "idx, exdir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(mvr_scens)), ) -def test_mf6model(idx, exdir): - # initialize testing framework - test = testing_framework() - - # build the models - test.build_mf6_models(build_model, idx, exdir) - - test.run_mf6( - Simulation( - exdir, +def test_mf6model(idx, name, function_tmpdir, targets): + test = TestFramework() + test.build(build_model, idx, str(function_tmpdir)) + test.run( + TestSimulation( + name=name, + exe_dict=targets, exfunc=check_simulation_output, idxsim=idx, - ) + ), + str(function_tmpdir), ) - - -def main(): - # initialize testing framework - test = testing_framework() - - for idx, exdir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, exdir) - sim = Simulation( - exdir, - exfunc=check_simulation_output, - idxsim=idx, - ) - test.run_mf6(sim) - - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() diff --git a/autotest/test_gwf_mvr01.py b/autotest/test_gwf_mvr01.py index d971945a7c7..8ba4f45b1e1 100644 --- a/autotest/test_gwf_mvr01.py +++ b/autotest/test_gwf_mvr01.py @@ -1,22 +1,13 @@ import os +import flopy import numpy as np import pytest - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation name = "gwf_mvr01" -ws = os.path.join("temp", name) -exdirs = [ws] +ex = [name] def build_model(idx, dir): @@ -45,7 +36,7 @@ def build_model(idx, dir): # build MODFLOW 6 files sim = flopy.mf6.MFSimulation( - sim_name=name, version="mf6", exe_name="mf6", sim_ws=ws + sim_name=name, version="mf6", exe_name="mf6", sim_ws=str(dir) ) # create tdis package tdis = flopy.mf6.ModflowTdis( @@ -453,39 +444,19 @@ def eval_model(sim): ) assert records[24].shape == (0,) - return -# - No need to change any code below @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the model - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_model, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_model, idxsim=idx) - test.run_mf6(sim) - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(idx, name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, idx, ws) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_model, idxsim=idx + ), + ws, + ) diff --git a/autotest/test_gwf_newton01.py b/autotest/test_gwf_newton01.py index ca733eba833..60c83c8f768 100644 --- a/autotest/test_gwf_newton01.py +++ b/autotest/test_gwf_newton01.py @@ -1,26 +1,12 @@ import os -import sys +import flopy import numpy as np import pytest - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation ex = ["newton01"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) -ddir = "data" - nlay = 2 nrow, ncol = 3, 3 top = 20 @@ -31,10 +17,7 @@ chdloc = [(1, i, j) for i in range(nrow) for j in range(ncol)] chd = 7.0 strt = chd - -# recharge data rch = 1.0 - oname = "head_obs.csv" obs_recarray = {oname: [("h1", "HEAD", (0, 1, 1)), ("h2", "HEAD", (1, 1, 1))]} @@ -125,40 +108,13 @@ def eval_head(sim): msg = f"head in layer 2 != 7. ({v['H2']})" assert np.allclose(v["H2"], 7.0), msg - return - -# - No need to change any code below @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the models - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_head)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # build the models - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_head) - test.run_mf6(sim) - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(idx, name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, idx, ws) + test.run(TestSimulation(name=name, exe_dict=targets, exfunc=eval_head), ws) diff --git a/autotest/test_gwf_noptc01.py b/autotest/test_gwf_noptc01.py index 2addf94f33e..d32c04fdecb 100644 --- a/autotest/test_gwf_noptc01.py +++ b/autotest/test_gwf_noptc01.py @@ -1,36 +1,13 @@ import os -import numpy as np +import flopy import pytest - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import running_on_CI, testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation ex = ["gwf_noptc01", "gwf_noptc02", "gwf_noptc03"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) - no_ptcrecords = ["FIRST", "ALL", None] - -ddir = "data" - -## run all examples on Travis -continuous_integration = [True for idx in range(len(exdirs))] - -# set replace_exe to None to use default executable -# replace_exe = {'mf2005': 'mf2005devdbl'} -replace_exe = None - -htol = [None for idx in range(len(exdirs))] +htol = [None for idx in range(len(ex))] # static model data # temporal discretization @@ -156,49 +133,12 @@ def build_model(idx, dir): return sim, mc -# - No need to change any code below @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir): - # determine if running on CI infrastructure - is_CI = running_on_CI() - r_exe = None - if not is_CI: - if replace_exe is not None: - r_exe = replace_exe - - # initialize testing framework - test = testing_framework() - - # build the models - test.build_mf6_models(build_model, idx, dir) - - # run the test model - if is_CI and not continuous_integration[idx]: - return - test.run_mf6(Simulation(dir, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # build the models - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, idxsim=idx) - test.run_mf6(sim) - - return - - -# use python testmf6_csub_sub03.py --mf2005 mf2005devdbl -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(idx, name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, idx, ws) + test.run(TestSimulation(name=name, exe_dict=targets, idxsim=idx), ws) diff --git a/autotest/test_gwf_npf01_75x75.py b/autotest/test_gwf_npf01_75x75.py index 74ca2cb494d..e976784b582 100644 --- a/autotest/test_gwf_npf01_75x75.py +++ b/autotest/test_gwf_npf01_75x75.py @@ -1,29 +1,16 @@ import os -import sys +import flopy import numpy as np import pytest - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation ex = ["npf01a_75x75", "npf01b_75x75"] top = [100.0, 0.0] laytyp = [1, 0] ss = [0.0, 1.0e-4] sy = [0.1, 0.0] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) -ddir = "data" def build_model(idx, dir): @@ -205,35 +192,10 @@ def build_model(idx, dir): # - No need to change any code below @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the models - test.build_mf6_models_legacy(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # build the models - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models_legacy(build_model, idx, dir) - sim = Simulation(dir) - test.run_mf6(sim) - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(idx, name, function_tmpdir, targets): + test = TestFramework() + test.build(build_model, idx, str(function_tmpdir)) + test.run(TestSimulation(name=name, exe_dict=targets), str(function_tmpdir)) diff --git a/autotest/test_gwf_npf02_rewet.py b/autotest/test_gwf_npf02_rewet.py index 353b85bd9cb..96eb0e8d4ab 100644 --- a/autotest/test_gwf_npf02_rewet.py +++ b/autotest/test_gwf_npf02_rewet.py @@ -1,25 +1,12 @@ import os -import sys +import flopy import numpy as np import pytest - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation ex = ["npf02_hreweta", "npf02_hrewetb", "npf02_hrewetc", "npf02_hrewetd"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) -ddir = "data" ncols = [[15], [10, 5], [15], [10, 5]] nlays = [1, 1, 3, 3] @@ -349,42 +336,18 @@ def eval_hds(sim): sim.success = True print(" " + msg) - return - -# - No need to change any code below @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the models - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_hds, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # build the models - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_hds, idxsim=idx) - test.run_mf6(sim) - - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(idx, name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, idx, ws) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_hds, idxsim=idx + ), + ws, + ) diff --git a/autotest/test_gwf_npf03_sfr.py b/autotest/test_gwf_npf03_sfr.py index ce3cc623fb1..d4490ea4146 100644 --- a/autotest/test_gwf_npf03_sfr.py +++ b/autotest/test_gwf_npf03_sfr.py @@ -1,37 +1,18 @@ import os -import sys +import flopy import numpy as np import pytest - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import running_on_CI, testing_framework -from simulation import Simulation +from conftest import project_root_path +from framework import TestFramework +from simulation import TestSimulation ex = ["npf03_sfra", "npf03_sfrb"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) -ddir = "data" - -# run all examples on CI -continuous_integration = [True for idx in range(len(exdirs))] -# continuous_integration = [False for idx in range(len(exdirs))] - -# read hk data -fpth = os.path.join(ddir, "npf03_hk.ref") +fpth = str(project_root_path / "autotest" / "data" / "npf03_hk.ref") shape = (50, 108) hk = flopy.utils.Util2d.load_txt(shape, fpth, dtype=float, fmtin="(FREE)") n1 = hk.shape[1] nd = 40 - ncols = [[n1], [n1 - nd, nd]] # static model data @@ -5794,47 +5775,19 @@ def eval_hds(sim): sim.success = True print(" " + msg) - return - -# - No need to change any code below +@pytest.mark.slow @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir): - # determine if running on CI infrastructure - is_CI = running_on_CI() - - # initialize testing framework - test = testing_framework() - - # build the models - test.build_mf6_models(build_model, idx, dir) - - # run the test model - if is_CI and not continuous_integration[idx]: - return - test.run_mf6(Simulation(dir, exfunc=eval_hds, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # build the models - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_hds, idxsim=idx) - test.run_mf6(sim) - - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(idx, name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, idx, ws) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_hds, idxsim=idx + ), + ws, + ) diff --git a/autotest/test_gwf_npf04_spdis.py b/autotest/test_gwf_npf04_spdis.py index 34e4e2a9ceb..b771b1b4161 100644 --- a/autotest/test_gwf_npf04_spdis.py +++ b/autotest/test_gwf_npf04_spdis.py @@ -9,28 +9,15 @@ """ import os -import sys +import flopy import numpy as np import pytest - -try: - import flopy - from flopy.utils.lgrutil import Lgr -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from flopy.utils.lgrutil import Lgr +from framework import TestFramework +from simulation import TestSimulation ex = ["npf04"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) - namea = "a" nameb = "b" @@ -224,42 +211,18 @@ def eval_mf6(sim): errmsg = f"min or max residual too large {res.min()} {res.max()}" assert np.allclose(res, 0.0, atol=1.0e-6), errmsg - return - -# - No need to change any code below @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the models - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_mf6, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # build the models - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_mf6, idxsim=idx) - test.run_mf6(sim) - - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(idx, name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, idx, ws) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_mf6, idxsim=idx + ), + ws, + ) diff --git a/autotest/test_gwf_npf05_anisotropy.py b/autotest/test_gwf_npf05_anisotropy.py index e9e9e0d4048..cb6e46e0272 100644 --- a/autotest/test_gwf_npf05_anisotropy.py +++ b/autotest/test_gwf_npf05_anisotropy.py @@ -5,34 +5,14 @@ """ import os -import sys +import flopy import numpy as np import pytest - -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation ex = ["npf05a"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) def build_model(idx, dir): @@ -165,39 +145,19 @@ def eval_model(sim): ] answer = np.array(answer) assert np.allclose(heads.flatten(), answer) - return -# - No need to change any code below @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the model - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_model, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_model, idxsim=idx) - test.run_mf6(sim) - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(idx, name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, idx, ws) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_model, idxsim=idx + ), + ws, + ) diff --git a/autotest/test_gwf_npf_tvk01.py b/autotest/test_gwf_npf_tvk01.py index 53ca8c37a43..f7791a3c6cc 100644 --- a/autotest/test_gwf_npf_tvk01.py +++ b/autotest/test_gwf_npf_tvk01.py @@ -1,36 +1,12 @@ import os -import sys +import flopy import numpy as np import pytest +from framework import TestFramework +from simulation import TestSimulation -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation - -ex = [ - "tvk01", -] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) -ddir = "data" - +ex = ["tvk01"] time_varying_k = [1.0, 10.0] @@ -165,8 +141,7 @@ def build_model(idx, dir): def eval_model(sim): print("evaluating model...") - name = ex[sim.idxsim] - gwfname = "gwf_" + name + gwfname = "gwf_" + sim.name # head fpth = os.path.join(sim.simpath, f"{gwfname}.hds") @@ -185,7 +160,7 @@ def eval_model(sim): assert False, f'could not load data from "{fpth}"' # This is the answer to this problem. - hk = time_varying_k[sim.idxsim] + hk = time_varying_k[0] delc = 1.0 delr = 1.0 delz = 1.0 @@ -203,39 +178,18 @@ def eval_model(sim): # comment when done testing # assert False - return - -# - No need to change any code below @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "name", + ex, ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the model - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_model, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_model, idxsim=idx) - test.run_mf6(sim) - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, 0, ws) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_model, idxsim=0 + ), + ws, + ) diff --git a/autotest/test_gwf_npf_tvk02.py b/autotest/test_gwf_npf_tvk02.py index 2cb79d6acca..48d30e65a0d 100644 --- a/autotest/test_gwf_npf_tvk02.py +++ b/autotest/test_gwf_npf_tvk02.py @@ -1,35 +1,12 @@ import os -import sys +import flopy import numpy as np import pytest +from framework import TestFramework +from simulation import TestSimulation -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation - -ex = [ - "tvk02", -] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) -ddir = "data" +ex = ["tvk02"] def build_model(idx, dir): @@ -184,8 +161,7 @@ def build_model(idx, dir): def eval_model(sim): print("evaluating model...") - name = ex[sim.idxsim] - gwfname = "gwf_" + name + gwfname = "gwf_" + sim.name # head fpth = os.path.join(sim.simpath, f"{gwfname}.hds") @@ -228,39 +204,18 @@ def eval_model(sim): # comment when done testing # assert False - return - -# - No need to change any code below @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "name", + ex, ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the model - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_model, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_model, idxsim=idx) - test.run_mf6(sim) - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, 0, ws) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_model, idxsim=0 + ), + ws, + ) diff --git a/autotest/test_gwf_npf_tvk03.py b/autotest/test_gwf_npf_tvk03.py index 20d47db9c75..226ee8bc932 100644 --- a/autotest/test_gwf_npf_tvk03.py +++ b/autotest/test_gwf_npf_tvk03.py @@ -1,35 +1,12 @@ import os -import sys +import flopy import numpy as np import pytest +from framework import TestFramework +from simulation import TestSimulation -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation - -ex = [ - "tvk03", -] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) -ddir = "data" +ex = ["tvk03"] def build_model(idx, dir): @@ -184,8 +161,7 @@ def build_model(idx, dir): def eval_model(sim): print("evaluating model...") - name = ex[sim.idxsim] - gwfname = "gwf_" + name + gwfname = "gwf_" + sim.name # head fpth = os.path.join(sim.simpath, f"{gwfname}.hds") @@ -228,39 +204,18 @@ def eval_model(sim): # comment when done testing # assert False - return - -# - No need to change any code below @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "name", + ex, ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the model - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_model, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_model, idxsim=idx) - test.run_mf6(sim) - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, 0, ws) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_model, idxsim=0 + ), + ws, + ) diff --git a/autotest/test_gwf_npf_tvk04.py b/autotest/test_gwf_npf_tvk04.py index eef8bff16f0..67b3c980c33 100644 --- a/autotest/test_gwf_npf_tvk04.py +++ b/autotest/test_gwf_npf_tvk04.py @@ -1,36 +1,12 @@ import os -import sys +import flopy import numpy as np import pytest +from framework import TestFramework +from simulation import TestSimulation -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation - -ex = [ - "tvk05", -] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) -ddir = "data" - +ex = ["tvk05"] time_varying_k = [1.0, 10.0] @@ -182,11 +158,9 @@ def build_model(idx, dir): def eval_model(sim): print("evaluating model...") - name = ex[sim.idxsim] - # budget try: - fname = f"gwf_{name}.lst" + fname = f"gwf_{sim.name}.lst" ws = sim.simpath fname = os.path.join(ws, fname) lst = flopy.utils.Mf6ListBudget( @@ -209,39 +183,18 @@ def eval_model(sim): errmsg = f"Expect higher flow rate in period 2 compared to period 1, but found equal or higher flow rate in period 1" assert 2.0 * sp_x[0][8] < sp_x[1][8], errmsg - return - -# - No need to change any code below @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "name", + ex, ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the model - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_model, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_model, idxsim=idx) - test.run_mf6(sim) - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() \ No newline at end of file +def test_mf6model(name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, 0, ws) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_model, idxsim=0 + ), + ws, + ) diff --git a/autotest/test_gwf_npf_tvk05.py b/autotest/test_gwf_npf_tvk05.py index 44b265f99d9..99059f5d9f0 100644 --- a/autotest/test_gwf_npf_tvk05.py +++ b/autotest/test_gwf_npf_tvk05.py @@ -1,36 +1,12 @@ import os -import sys +import flopy import numpy as np import pytest +from framework import TestFramework +from simulation import TestSimulation -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation - -ex = [ - "tvk05", -] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) -ddir = "data" - +ex = ["tvk05"] time_varying_k = [1.0, 10.0] @@ -183,11 +159,9 @@ def build_model(idx, dir): def eval_model(sim): print("evaluating model...") - name = ex[sim.idxsim] - # budget try: - fname = f"gwf_{name}.lst" + fname = f"gwf_{sim.name}.lst" ws = sim.simpath fname = os.path.join(ws, fname) lst = flopy.utils.Mf6ListBudget( @@ -210,39 +184,18 @@ def eval_model(sim): errmsg = f"Period 2 budget should be exactly the same as period 1" assert sp_x[0][8] == sp_x[1][8], errmsg - return - -# - No need to change any code below @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "name", + ex, ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the model - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_model, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_model, idxsim=idx) - test.run_mf6(sim) - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() \ No newline at end of file +def test_mf6model(name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, 0, ws) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_model, idxsim=0 + ), + ws, + ) diff --git a/autotest/test_gwf_obs01.py b/autotest/test_gwf_obs01.py index 39256e24893..bcee2743b56 100644 --- a/autotest/test_gwf_obs01.py +++ b/autotest/test_gwf_obs01.py @@ -1,34 +1,13 @@ import os +import flopy import numpy as np import pytest - -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation cell_dimensions = (300,) ex = [f"gwf_obs01{chr(ord('a') + idx)}" for idx in range(len(cell_dimensions))] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) -ddir = "data" - h0, h1 = 1.0, 0.0 @@ -164,42 +143,19 @@ def eval_model(sim): msg = "simulated head observations do not match with known solution." assert np.allclose(hres, obs), msg - return - -# - No need to change any code below +@pytest.mark.slow @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build all of the models - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_model, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # build all of the models - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_model, idxsim=idx) - test.run_mf6(sim) - - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(idx, name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, idx, ws) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_model, idxsim=idx + ), + ws, + ) diff --git a/autotest/test_gwf_obs02.py b/autotest/test_gwf_obs02.py index 5e2cb0aff7b..bfef5b1a50e 100644 --- a/autotest/test_gwf_obs02.py +++ b/autotest/test_gwf_obs02.py @@ -4,35 +4,14 @@ """ import os +import flopy import numpy as np import pytest - -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation cell_dimensions = (300,) ex = ["gwf_obs02"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) -ddir = "data" - h0, h1 = 1.0, 0.0 nlay, nrow, ncol = 1, 10, 10 @@ -147,10 +126,9 @@ def build_model(idx, dir): def eval_model(sim): print("evaluating model observations...") - name = ex[sim.idxsim] headcsv = np.empty((nlay, nrow, ncol), dtype=float) for i in range(nrow): - fname = f"{name}.{i}.obs.csv" + fname = f"{sim.name}.{i}.obs.csv" print(f"Loading and testing {fname}") fname = os.path.join(sim.simpath, fname) rec = np.genfromtxt(fname, names=True, delimiter=",", deletechars="") @@ -164,7 +142,7 @@ def eval_model(sim): assert obsname_true == obsname_found, errmsg headcsv[0, i, :] = np.array(rec.tolist()[1:]) - fn = os.path.join(sim.simpath, f"{name}.hds") + fn = os.path.join(sim.simpath, f"{sim.name}.hds") hobj = flopy.utils.HeadFile(fn) headbin = hobj.get_data() @@ -172,42 +150,18 @@ def eval_model(sim): headcsv, headbin ), "headcsv not equal head from binary file" - return - -# - No need to change any code below @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "name", + ex, ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build all of the models - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_model, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # build all of the models - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_model, idxsim=idx) - test.run_mf6(sim) - - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, 0, ws) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_model, idxsim=0 + ), + ws, + ) diff --git a/autotest/test_gwf_ptc01.py b/autotest/test_gwf_ptc01.py index af8197c9f7f..992deb59313 100644 --- a/autotest/test_gwf_ptc01.py +++ b/autotest/test_gwf_ptc01.py @@ -1,28 +1,15 @@ import os -import sys +import flopy import numpy as np import pytest - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from conftest import project_root_path +from framework import TestFramework +from simulation import TestSimulation ex = ["ptc01"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) -ddir = "data" - -# read bottom data -fpth = os.path.join(ddir, "nwtp03_bot.ref") +data_path = project_root_path / "autotest" / "data" +fpth = str(data_path / "nwtp03_bot.ref") botm = np.loadtxt(fpth, dtype=float) nlay = 1 nrow, ncol = botm.shape @@ -37,7 +24,7 @@ strt = botm + 20.0 # read recharge data -fpth = os.path.join(ddir, "nwtp03_rch.ref") +fpth = str(data_path / "nwtp03_rch.ref") rch = np.loadtxt(fpth, dtype=float) @@ -156,39 +143,13 @@ def build_model(idx, dir): return sim, mc -# - No need to change any code below +@pytest.mark.slow @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the models - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # build the models - # run the test model - for dir in exdirs: - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir) - test.run_mf6(sim) - - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(idx, name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, idx, ws) + test.run(TestSimulation(name=name, exe_dict=targets), ws) diff --git a/autotest/test_gwf_rch01.py b/autotest/test_gwf_rch01.py index f36d6465411..879739313f5 100644 --- a/autotest/test_gwf_rch01.py +++ b/autotest/test_gwf_rch01.py @@ -9,35 +9,15 @@ """ import os -import sys +import flopy import numpy as np import pytest - -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation ex = ["rch01a", "rch01b", "rch01c"] irch = [None, 0, [1, 1, 0, 1, 1]] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) def build_model(idx, dir): @@ -160,39 +140,18 @@ def eval_model(sim): hobj = flopy.utils.HeadFile(fpth, precision="double") heads = hobj.get_alldata() - return - -# - No need to change any code below @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the model - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_model, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_model, idxsim=idx) - test.run_mf6(sim) - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(idx, name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, idx, ws) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_model, idxsim=idx + ), + ws, + ) diff --git a/autotest/test_gwf_rch02.py b/autotest/test_gwf_rch02.py index 69f0d2645cd..e7aced8d6ff 100644 --- a/autotest/test_gwf_rch02.py +++ b/autotest/test_gwf_rch02.py @@ -5,34 +5,14 @@ """ import os -import sys +import flopy import numpy as np import pytest - -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation ex = ["rch02"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) def build_model(idx, dir): @@ -152,39 +132,18 @@ def eval_model(sim): hobj = flopy.utils.HeadFile(fpth, precision="double") heads = hobj.get_alldata() - return - -# - No need to change any code below @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the model - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_model, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_model, idxsim=idx) - test.run_mf6(sim) - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(idx, name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, idx, ws) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_model, idxsim=idx + ), + ws, + ) diff --git a/autotest/test_gwf_rch03.py b/autotest/test_gwf_rch03.py index b4ffea4e22d..c853cd4eaef 100644 --- a/autotest/test_gwf_rch03.py +++ b/autotest/test_gwf_rch03.py @@ -5,34 +5,14 @@ """ import os -import sys +import flopy import numpy as np import pytest - -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation ex = ["rch03"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) def build_model(idx, dir): @@ -178,39 +158,18 @@ def eval_model(sim): hobj = flopy.utils.HeadFile(fpth, precision="double") heads = hobj.get_alldata() - return - -# - No need to change any code below @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the model - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_model, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_model, idxsim=idx) - test.run_mf6(sim) - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(idx, name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, idx, ws) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_model, idxsim=idx + ), + ws, + ) diff --git a/autotest/test_gwf_returncodes.py b/autotest/test_gwf_returncodes.py index 9201054d3df..3eda6b7ef76 100644 --- a/autotest/test_gwf_returncodes.py +++ b/autotest/test_gwf_returncodes.py @@ -1,25 +1,11 @@ import os -import shutil import subprocess import sys +import flopy import pytest -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -import targets - -mf6_exe = os.path.abspath(targets.target_dict["mf6"]) name = "gwf_ret_codes01" -base_ws = os.path.join("temp", name) -if not os.path.isdir(base_ws): - os.makedirs(base_ws, exist_ok=True) app = "mf6" if sys.platform.lower() == "win32": app += ".exe" @@ -40,7 +26,7 @@ def run_mf6(argv, ws): return proc.returncode, buff -def get_sim(ws, idomain, continue_flag=False, nouter=500): +def get_sim(ws, exe, idomain, continue_flag=False, nouter=500): # static model data # temporal discretization nper = 1 @@ -66,7 +52,7 @@ def get_sim(ws, idomain, continue_flag=False, nouter=500): sim = flopy.mf6.MFSimulation( sim_name=name, version="mf6", - exe_name=mf6_exe, + exe_name=exe, sim_ws=ws, continue_=continue_flag, ) @@ -143,17 +129,17 @@ def get_sim(ws, idomain, continue_flag=False, nouter=500): return sim -def normal_termination(): - ws = os.path.join(base_ws, "normal_termination") +def normal_termination(dir, exe): + ws = os.path.join(dir, "normal_termination") # get the simulation - sim = get_sim(ws, idomain=1) + sim = get_sim(ws, exe, idomain=1) # write the input files sim.write_simulation() # run the simulation - returncode, buff = run_mf6([mf6_exe], ws) + returncode, buff = run_mf6([exe], ws) if returncode != 0: msg = ( "The run should have been successful but it terminated " @@ -161,79 +147,69 @@ def normal_termination(): ) raise ValueError(msg) - # clean up working directory - clean(ws) - - return - -def converge_fail_continue(): - ws = os.path.join(base_ws, "converge_fail_continue") +def converge_fail_continue(dir, exe): + ws = os.path.join(dir, "converge_fail_continue") # get the simulation - sim = get_sim(ws, idomain=1, continue_flag=True, nouter=1) + sim = get_sim(ws, exe, idomain=1, continue_flag=True, nouter=1) # write the input files sim.write_simulation() # run the simulation - returncode, buff = run_mf6([mf6_exe], ws) + returncode, buff = run_mf6([exe], ws) msg = ( "The run should have been successful even though it failed, because" " the continue flag was set. But a non-zero error code was " "found: {}".format(returncode) ) assert returncode == 0, msg - return -def converge_fail_nocontinue(): - ws = os.path.join(base_ws, "converge_fail_nocontinue") +def converge_fail_nocontinue(dir, exe): + ws = os.path.join(dir, "converge_fail_nocontinue") with pytest.raises(RuntimeError): # get the simulation - sim = get_sim(ws, idomain=1, continue_flag=False, nouter=1) + sim = get_sim(ws, exe, idomain=1, continue_flag=False, nouter=1) # write the input files sim.write_simulation() # run the simulation - returncode, buff = run_mf6([mf6_exe], ws) + returncode, buff = run_mf6([exe], ws) msg = "This run should fail with a returncode of 1" if returncode == 1: - clean(ws) raise RuntimeError(msg) -def idomain_runtime_error(): - ws = os.path.join(base_ws, "idomain_runtime_error") +def idomain_runtime_error(dir, exe): + ws = os.path.join(dir, "idomain_runtime_error") with pytest.raises(RuntimeError): # get the simulation - sim = get_sim(ws, idomain=0) + sim = get_sim(ws, exe, idomain=0) # write the input files sim.write_simulation() # run the simulation - returncode, buff = run_mf6([mf6_exe], ws) + returncode, buff = run_mf6([exe], ws) msg = f"could not run {sim.name}" if returncode != 0: err_str = "Ensure IDOMAIN array has some" err = any(err_str in s for s in buff) if err: - clean(ws) raise RuntimeError(msg) else: msg += " but IDOMAIN ARRAY ERROR not returned" raise ValueError(msg) -def unknown_keyword_error(): - ws = base_ws - - with pytest.raises(RuntimeError): - returncode, buff = run_mf6([mf6_exe, "--unknown_keyword"], ws) +def unknown_keyword_error(dir, exe): + with pytest.raises((RuntimeError, ValueError)): + returncode, buff = run_mf6([exe, "--unknown_keyword"], dir) msg = "could not run unknown_keyword" if returncode != 0: err_str = f"{app}: illegal option" @@ -245,10 +221,8 @@ def unknown_keyword_error(): raise ValueError(msg) -def run_argv(arg, return_str): - ws = base_ws - - returncode, buff = run_mf6([mf6_exe, arg], ws) +def run_argv(arg, return_str, tempdir, exe): + returncode, buff = run_mf6([exe, arg], tempdir) if returncode == 0: found_str = any(return_str in s for s in buff) if not found_str: @@ -259,62 +233,43 @@ def run_argv(arg, return_str): raise RuntimeError(msg) -def help_argv(): +def help_argv(dir, exe): for arg in ["-h", "--help", "-?"]: return_str = f"{app} [options] retrieve program information" - run_argv(arg, return_str) + run_argv(arg, return_str, dir, exe) -def version_argv(): +def version_argv(dir, exe): for arg in ["-v", "--version"]: return_str = f"{app}: 6" - run_argv(arg, return_str) + run_argv(arg, return_str, dir, exe) -def develop_argv(): +def develop_argv(dir, exe): for arg in ["-dev", "--develop"]: return_str = f"{app}: develop version" - run_argv(arg, return_str) + run_argv(arg, return_str, dir, exe) -def compiler_argv(): +def compiler_argv(dir, exe): for arg in ["-c", "--compiler"]: return_str = f"{app}: MODFLOW 6 compiled" - run_argv(arg, return_str) - - -def clean(dir_pth): - print(f"Cleaning up {dir_pth}") - shutil.rmtree(dir_pth) + run_argv(arg, return_str, dir, exe) @pytest.mark.parametrize( "fn", ( - "idomain_runtime_error()", - "unknown_keyword_error()", - "normal_termination()", - "converge_fail_nocontinue()", - "help_argv()", - "version_argv()", - "develop_argv()", - "compiler_argv()", + "idomain_runtime_error", + "unknown_keyword_error", + "normal_termination", + "converge_fail_nocontinue", + "help_argv", + "version_argv", + "develop_argv", + "compiler_argv", ), ) -def test_main(fn): - eval(fn) - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - idomain_runtime_error() - unknown_keyword_error() - normal_termination() - converge_fail_nocontinue() - help_argv() - version_argv() - develop_argv() - compiler_argv() - clean(base_ws) +def test_main(fn, function_tmpdir, targets): + mf6 = targets.as_dict()["mf6"] + eval(fn)(function_tmpdir, mf6) diff --git a/autotest/test_gwf_sfr_badfactor.py b/autotest/test_gwf_sfr_badfactor.py index 86c4751f93a..23677308164 100644 --- a/autotest/test_gwf_sfr_badfactor.py +++ b/autotest/test_gwf_sfr_badfactor.py @@ -1,33 +1,11 @@ -import os -import shutil -import subprocess -import sys - +import flopy import numpy as np -import pytest - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -import targets -from framework import testing_framework -from simulation import Simulation - -mf6_exe = os.path.abspath(targets.target_dict["mf6"]) paktest = "sfr" testname = "ts_sfr01" -testdir = os.path.join("temp", testname) -os.makedirs(testdir, exist_ok=True) -everything_was_successful = True -def build_model(timeseries=False): +def build_model(ws, exe, timeseries=False): # static model data # temporal discretization nper = 1 @@ -57,9 +35,8 @@ def build_model(timeseries=False): # build MODFLOW 6 files name = testname - ws = testdir sim = flopy.mf6.MFSimulation( - sim_name=name, version="mf6", exe_name=mf6_exe, sim_ws=ws + sim_name=name, version="mf6", exe_name=exe, sim_ws=ws ) # create tdis package tdis = flopy.mf6.ModflowTdis( @@ -529,40 +506,16 @@ def build_model(timeseries=False): return sim -# - No need to change any code below -def test_mf6model(): - # build and run the test model - sim = build_model() - sim.write_simulation() - sim.run_simulation() - - # ensure that the error msg is contained in the mfsim.lst file - f = open(os.path.join(testdir, "mfsim.lst"), "r") - lines = f.readlines() - error_count = 0 - expected_msg = False - for line in lines: - if "cprior" and "divflow not within" in line: - expected_msg = True - error_count += 1 - - assert error_count == 1, ( - "error count = " + str(error_count) + "but should equal 1" - ) - - print("Finished running surfdep check") - - return - +def test_mf6model(function_tmpdir, targets): + mf6 = targets.mf6 -def main(): # build and run the test model - sim = build_model() + sim = build_model(str(function_tmpdir), mf6) sim.write_simulation() sim.run_simulation() # ensure that the error msg is contained in the mfsim.lst file - f = open(os.path.join(testdir, "mfsim.lst"), "r") + f = open(str(function_tmpdir / "mfsim.lst"), "r") lines = f.readlines() error_count = 0 expected_msg = False @@ -576,13 +529,3 @@ def main(): ) print("Finished running surfdep check") - - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() diff --git a/autotest/test_gwf_sfr_evap.py b/autotest/test_gwf_sfr_evap.py index 8f57ab4c9bd..8684476c33b 100644 --- a/autotest/test_gwf_sfr_evap.py +++ b/autotest/test_gwf_sfr_evap.py @@ -1,28 +1,14 @@ # Test evap in SFR reaches (no interaction with gwf) -# Imports - import os -import sys +import flopy import numpy as np import pytest - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation ex = ["sfr-evap"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) # Model units @@ -448,36 +434,17 @@ def eval_results(sim): assert np.allclose(stored_strm_evap_r, sim_evap_r, atol=1e-4), msg -# - No need to change any code below @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the model - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_results, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_results, idxsim=idx) - test.run_mf6(sim) - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(idx, name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, idx, ws) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_results, idxsim=idx + ), + ws, + ) diff --git a/autotest/test_gwf_sfr_npoint01.py b/autotest/test_gwf_sfr_npoint01.py index a63b85f88fb..4106805eac5 100644 --- a/autotest/test_gwf_sfr_npoint01.py +++ b/autotest/test_gwf_sfr_npoint01.py @@ -1,25 +1,13 @@ import os -import sys +import flopy import numpy as np import pytest - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation - -sys.path.append("scripts") from cross_section_functions import get_depths +from framework import TestFramework +from simulation import TestSimulation paktest = "sfr" - ex = [ "sfr_npt01a", "sfr_npt01b", @@ -31,7 +19,6 @@ "sfr_npt01h", "sfr_npt01i", ] -exdirs = [os.path.join("temp", s) for s in ex] xsect_types = ( "wide", @@ -298,12 +285,10 @@ def build_model(idx, ws): return sim, None -def eval_npointq(sim): - idx = sim.idxsim - name = ex[idx] - print("evaluating n-point cross-section results..." f"({name})") +def eval_npointq(sim, idx): + print("evaluating n-point cross-section results..." f"({sim.name})") - obs_pth = os.path.join(exdirs[idx], f"{name}.sfr.obs.csv") + obs_pth = os.path.join(sim.simpath, f"{sim.name}.sfr.obs.csv") obs = flopy.utils.Mf6Obs(obs_pth).get_data() assert np.allclose( @@ -327,51 +312,21 @@ def eval_npointq(sim): f"calculated depth: {d}" ) - return - -# - No need to change any code below @pytest.mark.parametrize( - "idx, exdir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, exdir): - # initialize testing framework - test = testing_framework() - - # build the model - test.build_mf6_models(build_model, idx, exdir) - - # run the test models - test.run_mf6( - Simulation( - exdir, - exfunc=eval_npointq, +def test_mf6model(idx, name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, idx, ws) + test.run( + TestSimulation( + name=name, + exe_dict=targets, + exfunc=lambda s: eval_npointq(s, idx), idxsim=idx, - ) + ), + ws, ) - - -def main(): - # initialize testing framework - test = testing_framework() - - # run the test models - for idx, exdir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, exdir) - - sim = Simulation( - exdir, - exfunc=eval_npointq, - idxsim=idx, - ) - test.run_mf6(sim) - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() diff --git a/autotest/test_gwf_sfr_npoint02.py b/autotest/test_gwf_sfr_npoint02.py index d54d404009f..dd49ecc1a29 100644 --- a/autotest/test_gwf_sfr_npoint02.py +++ b/autotest/test_gwf_sfr_npoint02.py @@ -1,25 +1,16 @@ import os +import flopy import numpy as np import pytest - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation paktest = "sfr" ex = [ "sfr_npt02a", ] -exdirs = [os.path.join("temp", s) for s in ex] # temporal discretization nper = 10 @@ -218,11 +209,10 @@ def build_model(idx, ws): def eval_npointdepth(sim): - idx = sim.idxsim - name = ex[idx] + name = sim.name print("evaluating n-point cross-section results..." f"({name})") - obs_pth = os.path.join(exdirs[idx], f"{name}.sfr.obs.csv") + obs_pth = os.path.join(sim.simpath, f"{name}.sfr.obs.csv") obs = flopy.utils.Mf6Obs(obs_pth).get_data() assert np.allclose( @@ -238,51 +228,21 @@ def eval_npointdepth(sim): obs["DEPTH"], d ), "sfr depth not equal to calculated depth" - return - -# - No need to change any code below @pytest.mark.parametrize( - "idx, exdir", - list(enumerate(exdirs)), + "name", + ex, ) -def test_mf6model(idx, exdir): - # initialize testing framework - test = testing_framework() - - # build the model - test.build_mf6_models(build_model, idx, exdir) - - # run the test models - test.run_mf6( - Simulation( - exdir, +def test_mf6model(name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, 0, ws) + test.run( + TestSimulation( + name=name, + exe_dict=targets, exfunc=eval_npointdepth, - idxsim=idx, - ) + idxsim=0, + ), + ws, ) - - -def main(): - # initialize testing framework - test = testing_framework() - - # run the test models - for idx, exdir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, exdir) - - sim = Simulation( - exdir, - exfunc=eval_npointdepth, - idxsim=idx, - ) - test.run_mf6(sim) - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() diff --git a/autotest/test_gwf_sfr_npoint03.py b/autotest/test_gwf_sfr_npoint03.py index b69e4d88a81..cecbdc67cbf 100644 --- a/autotest/test_gwf_sfr_npoint03.py +++ b/autotest/test_gwf_sfr_npoint03.py @@ -1,25 +1,13 @@ import os -import sys +import flopy import numpy as np import pytest - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation - -sys.path.append("scripts") from cross_section_functions import calculate_rectchan_mannings_discharge +from framework import TestFramework +from simulation import TestSimulation paktest = "sfr" - ex = [ "sfr_npt03a", "sfr_npt03b", @@ -29,7 +17,6 @@ "sfr_npt03f", "sfr_npt03g", ] -exdirs = [os.path.join("temp", s) for s in ex] simulated_depths = ( 0.5, @@ -281,69 +268,37 @@ def build_models(idx, ws): def eval_npointdepth(sim): - idx = sim.idxsim - name = ex[idx] - print("evaluating n-point cross-section results..." f"({name})") + print("evaluating n-point cross-section results..." f"({sim.name})") - obs_pth0 = os.path.join(exdirs[idx], f"{name}.sfr.obs.csv") + obs_pth0 = os.path.join(sim.simpath, f"{sim.name}.sfr.obs.csv") obs0 = np.genfromtxt(obs_pth0, names=True, delimiter=",") - obs_pth1 = os.path.join(exdirs[idx], "mf6", f"{name}.sfr.obs.csv") + obs_pth1 = os.path.join(sim.simpath, "mf6", f"{sim.name}.sfr.obs.csv") obs1 = np.genfromtxt(obs_pth1, names=True, delimiter=",") q0 = obs0["OUTFLOW_DOWNSTREAM"] q1 = obs1["OUTFLOW_DOWNSTREAM"] - assert np.allclose(q0, q1), f"downstream outflows not equal ('{name}')" + assert np.allclose(q0, q1), f"downstream outflows not equal ('{sim.name}')" d0 = obs0["DEPTH_UPSTREAM"] d1 = obs1["DEPTH_UPSTREAM"] - assert np.allclose(d0, d1), f"upstream depths are not equal ('{name}')" + assert np.allclose(d0, d1), f"upstream depths are not equal ('{sim.name}')" - return - -# - No need to change any code below @pytest.mark.parametrize( - "idx, exdir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, exdir): - # initialize testing framework - test = testing_framework() - - # build the model - test.build_mf6_models(build_models, idx, exdir) - - # run the test models - test.run_mf6( - Simulation( - exdir, +def test_mf6model(idx, name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_models, idx, ws) + test.run( + TestSimulation( + name=name, + exe_dict=targets, exfunc=eval_npointdepth, idxsim=idx, - ) + ), + ws, ) - - -def main(): - # initialize testing framework - test = testing_framework() - - # run the test models - for idx, exdir in enumerate(exdirs): - test.build_mf6_models(build_models, idx, exdir) - - sim = Simulation( - exdir, - exfunc=eval_npointdepth, - idxsim=idx, - ) - test.run_mf6(sim) - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() diff --git a/autotest/test_gwf_sfr_reorder.py b/autotest/test_gwf_sfr_reorder.py index 71fdfb352f6..2010b7a24cd 100644 --- a/autotest/test_gwf_sfr_reorder.py +++ b/autotest/test_gwf_sfr_reorder.py @@ -1,29 +1,13 @@ import os -import sys +import flopy import numpy as np import pytest - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation - -sys.path.append("scripts") -from cross_section_functions import get_depths +from framework import TestFramework +from simulation import TestSimulation paktest = "sfr" - -ex = [ - "sfr_reorder", -] -exdirs = [os.path.join("temp", s) for s in ex] +ex = ["sfr_reorder"] # spatial discretization data nlay, nrow, ncol = 1, 1, 1 @@ -43,7 +27,7 @@ ustrf = 1.0 ndv = 0 -# + def build_model(idx, ws): # static model data @@ -221,14 +205,13 @@ def build_models(idx, base_ws): def eval_flows(sim): - idx = sim.idxsim - name = ex[idx] + name = sim.name print("evaluating flow results..." f"({name})") - obs_pth = os.path.join(exdirs[idx], f"{name}.sfr.obs.csv") + obs_pth = os.path.join(sim.simpath, f"{name}.sfr.obs.csv") obs0 = flopy.utils.Mf6Obs(obs_pth).get_data() - obs_pth = os.path.join(exdirs[idx], "mf6", f"{name}.sfr.obs.csv") + obs_pth = os.path.join(sim.simpath, "mf6", f"{name}.sfr.obs.csv") obs1 = flopy.utils.Mf6Obs(obs_pth).get_data() assert np.allclose(obs0["INFLOW"], obs1["INFLOW"]), "inflows are not equal" @@ -237,7 +220,7 @@ def eval_flows(sim): obs0["OUTFLOW"], obs1["OUTFLOW"] ), "outflows are not equal" - fpth = os.path.join(exdirs[idx], f"{name}.lst") + fpth = os.path.join(sim.simpath, f"{name}.lst") with open(fpth, "r") as f: lines = f.read().splitlines() @@ -257,51 +240,21 @@ def eval_flows(sim): order, actual ), "DAG did not correctly reorder reaches." - return - -# - No need to change any code below @pytest.mark.parametrize( - "idx, exdir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, exdir): - # initialize testing framework - test = testing_framework() - - # build the model - test.build_mf6_models(build_models, idx, exdir) - - # run the test models - test.run_mf6( - Simulation( - exdir, +def test_mf6model(idx, name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_models, idx, ws) + test.run( + TestSimulation( + name=name, + exe_dict=targets, exfunc=eval_flows, idxsim=idx, - ) + ), + ws, ) - - -def main(): - # initialize testing framework - test = testing_framework() - - # run the test models - for idx, exdir in enumerate(exdirs): - test.build_mf6_models(build_models, idx, exdir) - - sim = Simulation( - exdir, - exfunc=eval_flows, - idxsim=idx, - ) - test.run_mf6(sim) - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() diff --git a/autotest/test_gwf_sto01.py b/autotest/test_gwf_sto01.py index 08144c20bfe..c5328e0b3f5 100644 --- a/autotest/test_gwf_sto01.py +++ b/autotest/test_gwf_sto01.py @@ -1,39 +1,17 @@ import os +import flopy import numpy as np import pytest - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import running_on_CI, testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation ex = ["gwf_sto01"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) - cmppth = "mfnwt" tops = [0.0] - -ddir = "data" - -## run all examples on Travis -continuous_integration = [True for idx in range(len(exdirs))] - -# use default executable -replace_exe = None - -htol = [None for idx in range(len(exdirs))] +htol = [None for idx in range(len(ex))] dtol = 1e-3 budtol = 1e-2 - bud_lst = ["STO-SS_IN", "STO-SS_OUT", "STO-SY_IN", "STO-SY_OUT"] # static model data @@ -359,62 +337,22 @@ def eval_sto(sim): sim.success = True print(" " + msg) - return - -# - No need to change any code below @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir): - - # determine if running on CI infrastructure - is_CI = running_on_CI() - r_exe = None - if not is_CI: - if replace_exe is not None: - r_exe = replace_exe - - # initialize testing framework - test = testing_framework() - - # build the models - test.build_mf6_models_legacy(build_model, idx, dir) - - # run the test model - if is_CI and not continuous_integration[idx]: - return - test.run_mf6( - Simulation( - dir, exfunc=eval_sto, exe_dict=r_exe, htol=htol[idx], idxsim=idx - ) - ) - - -def main(): - # initialize testing framework - test = testing_framework() - - # build the models - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models_legacy(build_model, idx, dir) - sim = Simulation( - dir, +def test_mf6model(idx, name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, idx, ws) + test.run( + TestSimulation( + name=name, + exe_dict=targets, exfunc=eval_sto, - exe_dict=replace_exe, htol=htol[idx], idxsim=idx, - ) - test.run_mf6(sim) - - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() + ), + ws, + ) diff --git a/autotest/test_gwf_sto02.py b/autotest/test_gwf_sto02.py index f4c471afb8f..b2dd7ea9871 100644 --- a/autotest/test_gwf_sto02.py +++ b/autotest/test_gwf_sto02.py @@ -5,36 +5,14 @@ import os +import flopy import numpy as np import pytest - -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation ex = ["gwf_sto02a", "gwf_sto02b"] ncols = [1, 2] - -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) -ddir = "data" - nlay, nrow, = ( 1, 1, @@ -177,39 +155,21 @@ def eval_flow(sim): inc = mflist.get_incremental() print(inc) - return - -# - No need to change any code below @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the model - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_flow, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_flow, idxsim=idx) - test.run_mf6(sim) - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(idx, name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, idx, ws) + test.run( + TestSimulation( + name=name, + exe_dict=targets, + exfunc=eval_flow, + idxsim=idx, + ), + ws, + ) diff --git a/autotest/test_gwf_sto03.py b/autotest/test_gwf_sto03.py index c90844c6070..b626b632d5a 100644 --- a/autotest/test_gwf_sto03.py +++ b/autotest/test_gwf_sto03.py @@ -1,43 +1,21 @@ import os +import flopy import numpy as np import pytest - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import running_on_CI, testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation ex = [ "gwf_sto03a", "gwf_sto03b", ] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) - newton = ( False, True, ) - cmppth = "mf6" - -ddir = "data" - -## run all examples on Travis -continuous_integration = [True for idx in range(len(exdirs))] - -# use default executable -replace_exe = None - -htol = [None for idx in range(len(exdirs))] +htol = [None for idx in range(len(ex))] dtol = 1e-3 budtol = 1e-2 @@ -282,62 +260,22 @@ def eval_sto(sim): assert max_diff.sum() == 0.0, "simulated storage is not the same" - return - -# - No need to change any code below @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir): - - # determine if running on CI infrastructure - is_CI = running_on_CI() - r_exe = None - if not is_CI: - if replace_exe is not None: - r_exe = replace_exe - - # initialize testing framework - test = testing_framework() - - # build the models - test.build_mf6_models(build_model, idx, dir) - - # run the test model - if is_CI and not continuous_integration[idx]: - return - test.run_mf6( - Simulation( - dir, exfunc=eval_sto, exe_dict=r_exe, htol=htol[idx], idxsim=idx - ) - ) - - -def main(): - # initialize testing framework - test = testing_framework() - - # build the models - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation( - dir, +def test_mf6model(idx, name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, idx, ws) + test.run( + TestSimulation( + name=name, + exe_dict=targets, exfunc=eval_sto, - exe_dict=replace_exe, htol=htol[idx], idxsim=idx, - ) - test.run_mf6(sim) - - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() + ), + ws, + ) diff --git a/autotest/test_gwf_sto_tvs01.py b/autotest/test_gwf_sto_tvs01.py index 5f169bc6219..1f0f9da6023 100644 --- a/autotest/test_gwf_sto_tvs01.py +++ b/autotest/test_gwf_sto_tvs01.py @@ -1,35 +1,12 @@ import os -import sys +import flopy import numpy as np import pytest +from framework import TestFramework +from simulation import TestSimulation -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation - -ex = [ - "tvs01", -] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) -ddir = "data" +ex = ["tvs01"] def build_model(idx, dir): @@ -188,8 +165,7 @@ def build_model(idx, dir): def eval_model(sim): print("evaluating model...") - name = ex[sim.idxsim] - gwfname = "gwf_" + name + gwfname = "gwf_" + sim.name # head fpth = os.path.join(sim.simpath, f"{gwfname}.hds") @@ -228,39 +204,18 @@ def eval_model(sim): # comment when done testing # assert False - return - -# - No need to change any code below @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "name", + ex, ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the model - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_model, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_model, idxsim=idx) - test.run_mf6(sim) - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, 0, ws) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_model, idxsim=0 + ), + ws, + ) diff --git a/autotest/test_gwf_ts_lak01.py b/autotest/test_gwf_ts_lak01.py index e9fea2f9c8d..4a75c181cff 100644 --- a/autotest/test_gwf_ts_lak01.py +++ b/autotest/test_gwf_ts_lak01.py @@ -1,41 +1,15 @@ import os +import flopy import numpy as np import pytest - -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from flopy.utils.compare import eval_bud_diff +from framework import TestFramework +from simulation import TestSimulation paktest = "lak" budtol = 1e-2 - ex = ["ts_lak01"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) -ddir = "data" - -# run all examples on Travis -continuous_integration = [True for idx in range(len(exdirs))] - -# set replace_exe to None to use default executable -replace_exe = None # static model data # spatial discretization @@ -355,7 +329,6 @@ def build_model(idx, dir): def eval_budget(sim): print("evaluating budgets...") - from budget_file_compare import eval_bud_diff # get ia/ja from binary grid file fname = f"{os.path.basename(sim.name)}.dis.grb" @@ -378,44 +351,19 @@ def eval_budget(sim): fpth = os.path.join(sim.simpath, fname) eval_bud_diff(fpth, cobj0, cobj1, ia, dtol=0.1) - return - - -# - No need to change any code below - +@pytest.mark.slow @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the models - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_budget, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # build the models - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_budget, idxsim=idx) - test.run_mf6(sim) - return - - -# use python testmf6_drn_ddrn01.py -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(idx, name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, idx, ws) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_budget, idxsim=idx + ), + ws, + ) diff --git a/autotest/test_gwf_ts_maw01.py b/autotest/test_gwf_ts_maw01.py index 0eb277a98af..53a89583870 100644 --- a/autotest/test_gwf_ts_maw01.py +++ b/autotest/test_gwf_ts_maw01.py @@ -1,30 +1,14 @@ import os +import flopy import numpy as np import pytest - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from flopy.utils.compare import eval_bud_diff +from framework import TestFramework +from simulation import TestSimulation paktest = "maw" ex = [f"ts_{paktest}01"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) - -# run all examples on Travis -continuous_integration = [True for idx in range(len(exdirs))] - -# set replace_exe to None to use default executable -replace_exe = None def get_model(ws, name, timeseries=False): @@ -431,8 +415,6 @@ def build_model(idx, dir): def eval_model(sim): print("evaluating model budgets...") - from budget_file_compare import eval_bud_diff - # get ia/ja from binary grid file fname = f"{os.path.basename(sim.name)}.dis.grb" fpth = os.path.join(sim.simpath, fname) @@ -469,43 +451,18 @@ def eval_model(sim): fpth = os.path.join(sim.simpath, fname) eval_bud_diff(fpth, cobj0, cobj1) - return - - -# - No need to change any code below - @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the model - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_model, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # build the models - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_model, idxsim=idx) - test.run_mf6(sim) - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(idx, name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, idx, ws) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_model, idxsim=idx + ), + ws, + ) diff --git a/autotest/test_gwf_ts_sfr01.py b/autotest/test_gwf_ts_sfr01.py index faa6b60b334..adde4b870d4 100644 --- a/autotest/test_gwf_ts_sfr01.py +++ b/autotest/test_gwf_ts_sfr01.py @@ -1,30 +1,14 @@ import os +import flopy import numpy as np import pytest - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from flopy.utils.compare import eval_bud_diff +from framework import TestFramework +from simulation import TestSimulation paktest = "sfr" ex = ["ts_sfr01"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) - -# run all examples on Travis -continuous_integration = [True for idx in range(len(exdirs))] - -# set replace_exe to None to use default executable -replace_exe = None def get_model(ws, name, timeseries=False): @@ -546,7 +530,6 @@ def build_model(idx, dir): def eval_model(sim): print("evaluating model budgets...") - from budget_file_compare import eval_bud_diff # get ia/ja from binary grid file fname = f"{os.path.basename(sim.name)}.dis.grb" @@ -610,43 +593,18 @@ def eval_model(sim): check = np.array([0.0, 0.0, -2.5e-2, 0.0, -2.0e-2, 0.0]) assert np.allclose(v0, check), "FROM-MVR failed" - return - - -# - No need to change any code below - @pytest.mark.parametrize( - "idx, exdir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, exdir): - # initialize testing framework - test = testing_framework() - - # build the model - test.build_mf6_models(build_model, idx, exdir) - - # run the test model - test.run_mf6(Simulation(exdir, exfunc=eval_model, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # build the models - # run the test model - for idx, exdir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, exdir) - sim = Simulation(exdir, exfunc=eval_model, idxsim=idx) - test.run_mf6(sim) - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(idx, name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, idx, ws) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_model, idxsim=idx + ), + ws, + ) diff --git a/autotest/test_gwf_ts_sfr02.py b/autotest/test_gwf_ts_sfr02.py index 6cf9220424a..3a8442c86d0 100644 --- a/autotest/test_gwf_ts_sfr02.py +++ b/autotest/test_gwf_ts_sfr02.py @@ -1,30 +1,14 @@ import os +import flopy import numpy as np import pytest - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from flopy.utils.compare import eval_bud_diff +from framework import TestFramework +from simulation import TestSimulation paktest = "sfr" ex = ["ts_sfr02"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) - -# run all examples on Travis -continuous_integration = [True for idx in range(len(exdirs))] - -# set replace_exe to None to use default executable -replace_exe = None def get_model(ws, name, timeseries=False): @@ -537,7 +521,6 @@ def build_model(idx, dir): def eval_model(sim): print("evaluating model budgets...") - from budget_file_compare import eval_bud_diff # get ia/ja from binary grid file fname = f"{os.path.basename(sim.name)}.dis.grb" @@ -616,43 +599,18 @@ def eval_model(sim): check = np.array([-2.5e-2, 0.0, 0.0, 0.0, -2.0e-2, 0.0]) assert np.allclose(v0, check), "TO-MVR failed" - return - - -# - No need to change any code below - @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the model - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_model, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # build the models - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_model, idxsim=idx) - test.run_mf6(sim) - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(idx, name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, idx, ws) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_model, idxsim=idx + ), + ws, + ) diff --git a/autotest/test_gwf_ts_uzf01.py b/autotest/test_gwf_ts_uzf01.py index 6714a31f637..4af7b20511f 100644 --- a/autotest/test_gwf_ts_uzf01.py +++ b/autotest/test_gwf_ts_uzf01.py @@ -1,30 +1,14 @@ import os +import flopy import numpy as np import pytest - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from flopy.utils.compare import eval_bud_diff +from framework import TestFramework +from simulation import TestSimulation paktest = "uzf" ex = ["ts_uzf01"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) - -# run all examples on Travis -continuous_integration = [True for idx in range(len(exdirs))] - -# set replace_exe to None to use default executable -replace_exe = None def get_model(ws, name, timeseries=False): @@ -664,7 +648,6 @@ def build_model(idx, dir): def eval_model(sim): print("evaluating model budgets...") - from budget_file_compare import eval_bud_diff # get ia/ja from binary grid file fname = f"{os.path.basename(sim.name)}.dis.grb" @@ -702,43 +685,18 @@ def eval_model(sim): fpth = os.path.join(sim.simpath, fname) eval_bud_diff(fpth, cobj0, cobj1) - return - - -# - No need to change any code below - @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the model - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_model, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # build the models - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_model, idxsim=idx) - test.run_mf6(sim) - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(idx, name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, idx, ws) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_model, idxsim=idx + ), + ws, + ) diff --git a/autotest/test_gwf_utl01_binaryinput.py b/autotest/test_gwf_utl01_binaryinput.py index 0bf84dd7db6..a7ef3faef37 100644 --- a/autotest/test_gwf_utl01_binaryinput.py +++ b/autotest/test_gwf_utl01_binaryinput.py @@ -4,25 +4,13 @@ import os +import flopy import numpy as np import pytest - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation ex = ["binary01", "binary02"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) -ddir = "data" def build_model(idx, dir): @@ -88,7 +76,7 @@ def build_model(idx, dir): # write top to a binary file text = "TOP" fname = "top.bin" - pth = os.path.join(exdirs[idx], fname) + pth = os.path.join(dir, fname) f = open(pth, "wb") header = flopy.utils.BinaryHeader.create( bintype="HEAD", @@ -123,7 +111,7 @@ def build_model(idx, dir): for k in range(nlay): text = f"BOTM_L{k + 1}" fname = f"botm.l{k + 1:02d}.bin" - pth = os.path.join(exdirs[idx], fname) + pth = os.path.join(dir, fname) f = open(pth, "wb") header = flopy.utils.BinaryHeader.create( bintype="HEAD", @@ -155,7 +143,7 @@ def build_model(idx, dir): ) elif idx == 1: fname = "botm.bin" - pth = os.path.join(exdirs[idx], fname) + pth = os.path.join(dir, fname) f = open(pth, "wb") tarr = np.ones((nlay, nrow, ncol), dtype=np.float64) for k in range(nlay): @@ -189,7 +177,7 @@ def build_model(idx, dir): for k in range(nlay): text = f"IDOMAIN_L{k + 1}" fname = f"idomain.l{k + 1:02d}.bin" - pth = os.path.join(exdirs[idx], fname) + pth = os.path.join(dir, fname) f = open(pth, "wb") header = flopy.utils.BinaryHeader.create( bintype="HEAD", @@ -221,7 +209,7 @@ def build_model(idx, dir): ) elif idx == 1: fname = "idomain.bin" - pth = os.path.join(exdirs[idx], fname) + pth = os.path.join(dir, fname) f = open(pth, "wb") header = flopy.utils.BinaryHeader.create( bintype="HEAD", @@ -270,7 +258,7 @@ def build_model(idx, dir): for k in range(nlay): text = f"IC_L{k + 1}" fname = f"ic.strt_l{k + 1:02d}.bin" - pth = os.path.join(exdirs[idx], fname) + pth = os.path.join(dir, fname) f = open(pth, "wb") header = flopy.utils.BinaryHeader.create( bintype="HEAD", @@ -302,7 +290,7 @@ def build_model(idx, dir): ) elif idx == 1: fname = "ic.strt.bin" - pth = os.path.join(exdirs[idx], fname) + pth = os.path.join(dir, fname) f = open(pth, "wb") header = flopy.utils.BinaryHeader.create( bintype="HEAD", @@ -339,7 +327,7 @@ def build_model(idx, dir): icelltype = [] for k in range(nlay): fname = f"npf.icelltype.l{k + 1}.bin" - pth = os.path.join(exdirs[idx], fname) + pth = os.path.join(dir, fname) f = open(pth, "wb") header = flopy.utils.BinaryHeader.create( bintype="head", @@ -372,7 +360,7 @@ def build_model(idx, dir): ) elif idx == 1: fname = "npf.icelltype.bin" - pth = os.path.join(exdirs[idx], fname) + pth = os.path.join(dir, fname) f = open(pth, "wb") header = flopy.utils.BinaryHeader.create( bintype="head", @@ -439,39 +427,12 @@ def build_model(idx, dir): return sim, None -# - No need to change any code below @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the models - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # build the models - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir) - test.run_mf6(sim) - - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(idx, name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, idx, ws) + test.run(TestSimulation(name=name, exe_dict=targets), ws) diff --git a/autotest/test_gwf_utl02_timeseries.py b/autotest/test_gwf_utl02_timeseries.py index 573c2989f0e..3f06e46042a 100644 --- a/autotest/test_gwf_utl02_timeseries.py +++ b/autotest/test_gwf_utl02_timeseries.py @@ -1,24 +1,9 @@ -import os - -import numpy as np +import flopy import pytest - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation ex = ["ts01"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) -ddir = "data" def build_model(idx, dir): @@ -163,39 +148,12 @@ def build_model(idx, dir): return sim, None -# - No need to change any code below @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the models - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # build the models - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir) - test.run_mf6(sim) - - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(idx, name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, idx, ws) + test.run(TestSimulation(name=name, exe_dict=targets), ws) diff --git a/autotest/test_gwf_utl03_obs01.py b/autotest/test_gwf_utl03_obs01.py index 4f5944833a6..a8df98865b0 100644 --- a/autotest/test_gwf_utl03_obs01.py +++ b/autotest/test_gwf_utl03_obs01.py @@ -1,25 +1,12 @@ import os -import sys +import flopy import numpy as np import pytest - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation ex = ["utl03_obs"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) -ddir = "data" # temporal discretization nper = 2 @@ -61,12 +48,12 @@ hclose, rclose, relax = 1e-6, 0.01, 1.0 -def build_mf6(idx, ws, binaryobs=True): +def build_mf6(idx, ws, exe, binaryobs=True): name = ex[idx] # build MODFLOW 6 files sim = flopy.mf6.MFSimulation( - sim_name=name, version="mf6", exe_name="mf6", sim_ws=ws + sim_name=name, version="mf6", exe_name=exe, sim_ws=ws ) # create tdis package flopy.mf6.ModflowTdis( @@ -150,21 +137,25 @@ def build_mf6(idx, ws, binaryobs=True): return sim -def build_model(idx, dir): +def build_model(idx, dir, exe): ws = dir # build mf6 with ascii observation output - sim = build_mf6(idx, ws, binaryobs=False) + sim = build_mf6(idx, ws, exe=exe, binaryobs=False) # build mf6 with binary observation output wsc = os.path.join(ws, "mf6") - mc = build_mf6(idx, wsc, binaryobs=True) + mc = build_mf6(idx, wsc, exe=exe, binaryobs=True) + + sim.write_simulation() + mc.write_simulation() + hack_binary_obs(idx, dir) return sim, mc -def build_models(): - for idx, dir in enumerate(exdirs): - sim, mc = build_model(idx, dir) +def build_models(dir, exe): + for idx, name in enumerate(ex): + sim, mc = build_model(idx, dir, exe) sim.write_simulation() mc.write_simulation() hack_binary_obs(idx, dir) @@ -224,41 +215,14 @@ def eval_obs(sim): ) assert np.allclose(d0[name], d1[name], rtol=1e-5), msg - return - -# - No need to change any code below @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the models - build_models() - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_obs)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # build the models - build_models() - - # run the test model - for dir in exdirs: - sim = Simulation(dir, exfunc=eval_obs) - test.run_mf6(sim) - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(idx, name, function_tmpdir, targets): + ws = str(function_tmpdir) + mf6 = targets["mf6"] + test = TestFramework() + build_models(ws, mf6) + test.run(TestSimulation(name=name, exe_dict=targets, exfunc=eval_obs), ws) diff --git a/autotest/test_gwf_utl04_auxmult.py b/autotest/test_gwf_utl04_auxmult.py index 9c925f36336..78ae0f8f55d 100644 --- a/autotest/test_gwf_utl04_auxmult.py +++ b/autotest/test_gwf_utl04_auxmult.py @@ -6,25 +6,13 @@ import os +import flopy import numpy as np import pytest - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation ex = ["auxmult01"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) -ddir = "data" def build_model(idx, dir): @@ -191,39 +179,15 @@ def eval_model(sim): # assert False - return - -# - No need to change any code below @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the model - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_model, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_model, idxsim=idx) - test.run_mf6(sim) - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(idx, name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, idx, ws) + test.run( + TestSimulation(name=name, exe_dict=targets, exfunc=eval_model), ws + ) diff --git a/autotest/test_gwf_utl05_budparse.py b/autotest/test_gwf_utl05_budparse.py index d62d7f74ed2..216413bf199 100644 --- a/autotest/test_gwf_utl05_budparse.py +++ b/autotest/test_gwf_utl05_budparse.py @@ -5,36 +5,16 @@ import os +import flopy import numpy as np import pytest - -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation ex = ["gwf_utl05"] laytyp = [1] ss = [1.0e-10] sy = [0.1] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) -ddir = "data" nlay, nrow, ncol = 1, 1, 1 @@ -164,8 +144,7 @@ def build_model(idx, dir): def eval_flow(sim): print("evaluating flow...") - name = ex[sim.idxsim] - gwfname = "gwf_" + name + gwfname = "gwf_" + sim.name # This will fail if budget numbers cannot be read fpth = os.path.join(sim.simpath, f"{gwfname}.lst") @@ -180,36 +159,17 @@ def eval_flow(sim): assert np.allclose(inc["WEL_OUT"], 0.0) -# - No need to change any code below @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "name", + ex, ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the model - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_flow, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_flow, idxsim=idx) - test.run_mf6(sim) - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, 0, ws) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_flow, idxsim=0 + ), + ws, + ) diff --git a/autotest/test_gwf_utl06_tas.py b/autotest/test_gwf_utl06_tas.py index 0f5b3bd8e05..dbde41bec9a 100644 --- a/autotest/test_gwf_utl06_tas.py +++ b/autotest/test_gwf_utl06_tas.py @@ -6,19 +6,11 @@ import os +import flopy import numpy as np import pytest - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation ex = [ "utl06_tas_a", @@ -26,9 +18,6 @@ "utl06_tas_c", "utl06_tas_d", ] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) nlay, nrow, ncol = 3, 5, 5 idomain_lay0 = [ @@ -375,42 +364,18 @@ def eval_transport(sim): qa = [a * rate * frac for a, rate in zip(area, id2a)] assert np.allclose(q, qa), f"{q} /=\n {qa}" - return - -# - No need to change any code below @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the models - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_transport, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # build the models - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_transport, idxsim=idx) - test.run_mf6(sim) - - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(idx, name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, idx, ws) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_transport, idxsim=idx + ), + ws, + ) diff --git a/autotest/test_gwf_uzf01.py b/autotest/test_gwf_uzf01.py index 1fcbc24890c..0d168da3265 100644 --- a/autotest/test_gwf_uzf01.py +++ b/autotest/test_gwf_uzf01.py @@ -6,33 +6,13 @@ import os +import flopy import numpy as np import pytest - -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation ex = ["gwf_uzf01a"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) -ddir = "data" nlay, nrow, ncol = 100, 1, 1 @@ -236,8 +216,8 @@ def build_model(idx, exdir): def eval_flow(sim): print("evaluating flow...") - name = ex[sim.idxsim] - ws = exdirs[sim.idxsim] + name = sim.name + ws = sim.simpath # check binary grid file fname = os.path.join(ws, name + ".dis.grb") @@ -281,41 +261,18 @@ def eval_flow(sim): "data in the cell-by-cell file." ) - return - -# - No need to change any code below @pytest.mark.parametrize( - "idx, exdir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, exdir): - # initialize testing framework - test = testing_framework() - - # build the model - test.build_mf6_models(build_model, idx, exdir) - - # run the test model - test.run_mf6(Simulation(exdir, exfunc=eval_flow, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # run the test model - for idx, exdir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, exdir) - sim = Simulation(exdir, exfunc=eval_flow, idxsim=idx) - test.run_mf6(sim) - - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(idx, name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, idx, ws) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_flow, idxsim=idx + ), + ws, + ) diff --git a/autotest/test_gwf_uzf02.py b/autotest/test_gwf_uzf02.py index 0e0eed7dc40..03996c8efe6 100644 --- a/autotest/test_gwf_uzf02.py +++ b/autotest/test_gwf_uzf02.py @@ -5,33 +5,13 @@ import os +import flopy import numpy as np import pytest - -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation ex = ["gwf_uzf02a"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) -ddir = "data" nlay, nrow, ncol = 1, 1, 1 @@ -248,9 +228,6 @@ def build_model(idx, dir): def make_plot(sim, obsvals): print("making plots...") - name = ex[sim.idxsim] - ws = exdirs[sim.idxsim] - # shows curves for times 2.5, 7.5, 12.6, 17.7 # which are indices 24, 74, 125, and -1 idx = [24, 74, 125, -1] @@ -276,17 +253,15 @@ def make_plot(sim, obsvals): plt.legend() fname = "fig-xsect.pdf" - fname = os.path.join(ws, fname) + fname = os.path.join(sim.simpath, fname) plt.savefig(fname, bbox_inches="tight") - return - def eval_flow(sim): print("evaluating flow...") - name = ex[sim.idxsim] - ws = exdirs[sim.idxsim] + name = sim.name + ws = sim.simpath # check binary grid file fname = os.path.join(ws, name + ".dis.grb") @@ -330,41 +305,19 @@ def eval_flow(sim): assert False, f'could not load data from "{fpth}"' if False: make_plot(sim, obsvals) - return -# - No need to change any code below @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "name", + ex, ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the model - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_flow, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_flow, idxsim=idx) - test.run_mf6(sim) - - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, 0, ws) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_flow, idxsim=0 + ), + ws, + ) diff --git a/autotest/test_gwf_uzf03.py b/autotest/test_gwf_uzf03.py index cbcad1e6dbf..361036a4c01 100644 --- a/autotest/test_gwf_uzf03.py +++ b/autotest/test_gwf_uzf03.py @@ -6,38 +6,17 @@ import os +import flopy import numpy as np import pytest - -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation ex = ["gwf_uzf03a"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) -ddir = "data" nlay, nrow, ncol = 15, 1, 1 def build_model(idx, dir): - perlen = [17.7] nper = len(perlen) nstp = [177] @@ -250,9 +229,6 @@ def build_model(idx, dir): def make_plot(sim, obsvals): print("making plots...") - name = ex[sim.idxsim] - ws = exdirs[sim.idxsim] - # shows curves for times 2.5, 7.5, 12.6, 17.7 # which are indices 24, 74, 125, and -1 idx = [24, 74, 125, -1] @@ -277,17 +253,15 @@ def make_plot(sim, obsvals): plt.legend() fname = "fig-xsect.pdf" - fname = os.path.join(ws, fname) + fname = os.path.join(sim.simpath, fname) plt.savefig(fname, bbox_inches="tight") - return - def eval_flow(sim): print("evaluating flow...") - name = ex[sim.idxsim] - ws = exdirs[sim.idxsim] + name = sim.name + ws = sim.simpath # check binary grid file fname = os.path.join(ws, name + ".dis.grb") @@ -331,41 +305,19 @@ def eval_flow(sim): assert False, f'could not load data from "{fpth}"' if False: make_plot(sim, obsvals) - return -# - No need to change any code below @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "name", + ex, ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the model - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_flow, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_flow, idxsim=idx) - test.run_mf6(sim) - - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, 0, ws) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_flow, idxsim=0 + ), + ws, + ) diff --git a/autotest/test_gwf_uzf04.py b/autotest/test_gwf_uzf04.py index 05ea7f8025d..4662ea98e2d 100644 --- a/autotest/test_gwf_uzf04.py +++ b/autotest/test_gwf_uzf04.py @@ -11,35 +11,14 @@ import os +import flopy import numpy as np import pytest - -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation ex = ["gwf_uzf04a"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) -ddir = "data" nlay, nrow, ncol = 1, 1, 1 - thts = 0.30 # saturated water content thtr = 0.05 # residual water content thti = 0.10 # initial water content @@ -239,8 +218,8 @@ def build_model(idx, dir): def eval_flow(sim): print("evaluating flow...") - name = ex[sim.idxsim] - ws = exdirs[sim.idxsim] + name = sim.name + ws = sim.simpath fname = os.path.join(ws, f"{name}.uzf.bin") wobj = flopy.utils.HeadFile(fname, text="WATER-CONTENT") @@ -277,41 +256,18 @@ def eval_flow(sim): vw, volume_mobile_sim ), "Simulated mobile water volume in aux does not match known result" - return - -# - No need to change any code below @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the model - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_flow, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_flow, idxsim=idx) - test.run_mf6(sim) - - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(idx, name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, idx, ws) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_flow, idxsim=idx + ), + ws, + ) diff --git a/autotest/test_gwf_uzf05.py b/autotest/test_gwf_uzf05.py index 7258a5a03cf..457ffd8b7b8 100644 --- a/autotest/test_gwf_uzf05.py +++ b/autotest/test_gwf_uzf05.py @@ -7,33 +7,13 @@ import os +import flopy import numpy as np import pytest - -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation ex = ["gwf_uzf05a"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) -ddir = "data" nlay, nrow, ncol = 3, 1, 1 thts = 0.30 # saturated water content @@ -59,7 +39,7 @@ def build_model(idx, dir): # unsat props hk = 10.0 - infiltration_rate = 10. + infiltration_rate = 10.0 evapotranspiration_rate = 0.0 evt_extinction_depth = 2.0 brooks_corey_epsilon = 3.5 # brooks corey exponent @@ -237,8 +217,8 @@ def build_model(idx, dir): def eval_flow(sim): print("evaluating flow...") - name = ex[sim.idxsim] - ws = exdirs[sim.idxsim] + name = sim.name + ws = sim.simpath fname = os.path.join(ws, f"{name}.uzf.bin") wobj = flopy.utils.HeadFile(fname, text="WATER-CONTENT") @@ -256,43 +236,20 @@ def eval_flow(sim): node, node2, q, flow_area = flowtogwf[0] assert node == 1, "uzf node should be 1" assert node2 == 1, "GWF node should be 1" - assert np.isclose(q, -4.), "Flow from UZF to node 1 should be -4." - - return + assert np.isclose(q, -4.0), "Flow from UZF to node 1 should be -4." -# - No need to change any code below @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the model - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_flow, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_flow, idxsim=idx) - test.run_mf6(sim) - - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(idx, name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, idx, ws) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_flow, idxsim=idx + ), + ws, + ) diff --git a/autotest/test_gwf_uzf_gwet.py b/autotest/test_gwf_uzf_gwet.py index 171d984a903..ab7d74556d8 100644 --- a/autotest/test_gwf_uzf_gwet.py +++ b/autotest/test_gwf_uzf_gwet.py @@ -1,34 +1,14 @@ import os -import sys +import flopy import numpy as np import pytest - -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation ex = ["uzf_3lay"] -exdirs = [] iuz_cell_dict = {} cell_iuz_dict = {} -for s in ex: - exdirs.append(os.path.join("temp", s)) def build_model(idx, dir): @@ -280,8 +260,7 @@ def build_model(idx, dir): def eval_model(sim): print("evaluating model...") - name = ex[sim.idxsim] - ws = exdirs[sim.idxsim] + ws = sim.simpath sim = flopy.mf6.MFSimulation.load(sim_ws=ws) fpth = os.path.join(ws, "uzf_3lay.hds") @@ -391,36 +370,14 @@ def eval_model(sim): print("Finished running checks") -# - No need to change any code below -@pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), -) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the model - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_model, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_model, idxsim=idx) - test.run_mf6(sim) - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +@pytest.mark.parametrize("name", ex) +def test_mf6model(name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, 0, ws) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_model, idxsim=0 + ), + ws, + ) diff --git a/autotest/test_gwf_uzf_surfdep.py b/autotest/test_gwf_uzf_surfdep.py index b9d101fd682..824727a9fff 100644 --- a/autotest/test_gwf_uzf_surfdep.py +++ b/autotest/test_gwf_uzf_surfdep.py @@ -1,40 +1,12 @@ -import os -import shutil -import subprocess -import sys - -import numpy as np -import pytest - -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -import targets - -mf6_exe = os.path.abspath(targets.target_dict["mf6"]) +import flopy + testname = "uzf_3lay_srfdchk" -testdir = os.path.join("temp", testname) -os.makedirs(testdir, exist_ok=True) -everything_was_successful = True iuz_cell_dict = {} cell_iuz_dict = {} -def build_model(): +def build_model(dir, exe): nlay, nrow, ncol = 3, 1, 10 nper = 1 @@ -61,9 +33,9 @@ def build_model(): name = testname # build MODFLOW 6 files - ws = testdir + ws = dir sim = flopy.mf6.MFSimulation( - sim_name=name, version="mf6", exe_name=mf6_exe, sim_ws=ws + sim_name=name, version="mf6", exe_name=exe, sim_ws=ws ) # create tdis package @@ -213,15 +185,15 @@ def build_model(): return sim -# - No need to change any code below -def test_mf6model(): +def test_mf6model(function_tmpdir, targets): # build and run the test model - sim = build_model() + mf6 = targets.mf6 + sim = build_model(str(function_tmpdir), mf6) sim.write_simulation() sim.run_simulation() # ensure that the error msg is contained in the mfsim.lst file - f = open(os.path.join(testdir, "mfsim.lst"), "r") + f = open(str(function_tmpdir / "mfsim.lst"), "r") lines = f.readlines() error_count = 0 expected_msg = False @@ -235,21 +207,3 @@ def test_mf6model(): ) print("Finished running surfdep check") - - shutil.rmtree(testdir, ignore_errors=True) - - return - - -def main(): - test_mf6model() - - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() diff --git a/autotest/test_gwf_uzf_wc_output.py b/autotest/test_gwf_uzf_wc_output.py index 1404ca4b21f..bd43193ace4 100644 --- a/autotest/test_gwf_uzf_wc_output.py +++ b/autotest/test_gwf_uzf_wc_output.py @@ -1,33 +1,15 @@ import os +import flopy +import flopy.utils.binaryfile as bf import numpy as np import pytest - -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -import flopy.utils.binaryfile as bf - -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation include_NWT = False ex = ["uzf_3lay_wc_chk"] -exdirs = [os.path.join("temp", name) for name in ex] iuz_cell_dict = {} cell_iuz_dict = {} @@ -470,9 +452,8 @@ def build_model(idx, ws): def eval_model(sim): print("evaluating model...") - idx = sim.idxsim - name = ex[idx] - ws = os.path.join("temp", name) + name = sim.name + ws = sim.simpath # Get the MF6 heads fpth = os.path.join(ws, "uzf_3lay_wc_chk.hds") @@ -560,39 +541,20 @@ def eval_model(sim): print("Finished running checks") -# - No need to change any code below @pytest.mark.parametrize( - "idx, exdir", - list(enumerate(exdirs)), + "name", + ex, ) -def test_mf6model(idx, exdir): - # initialize testing framework - test = testing_framework() - - # build the model - test.build_mf6_models(build_model, idx, exdir) - - # run the test models - test.run_mf6( - Simulation( - exdir, +def test_mf6model(name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, 0, ws) + test.run( + TestSimulation( + name=name, + exe_dict=targets, exfunc=eval_model, - idxsim=idx, - ) + idxsim=0, + ), + ws, ) - return - - -def main(): - for idx, exdir in enumerate(exdirs): - test_mf6model(idx, exdir) - - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() diff --git a/autotest/test_gwf_vsc01.py b/autotest/test_gwf_vsc01.py index 0c547db2e77..5b27cebda76 100644 --- a/autotest/test_gwf_vsc01.py +++ b/autotest/test_gwf_vsc01.py @@ -11,27 +11,16 @@ import os import sys +import flopy import numpy as np import pytest - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation hyd_cond = [1205.49396942506, 864.0] # Hydraulic conductivity (m/d) ex = ["no-vsc01-bnd", "vsc01-bnd", "no-vsc01-k"] viscosity_on = [False, True, False] hydraulic_conductivity = [hyd_cond[0], hyd_cond[1], hyd_cond[1]] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) # Model units @@ -287,7 +276,7 @@ def eval_results(sim): # Ensure latest simulated value hasn't changed from stored answer assert np.allclose( sim_val_1, stored_ans, atol=1e-4 - ), "Flow in the " + exdirs[ + ), "Flow in the " + ex[ 0 ] + " test problem (doesn't simulate " "viscosity) has changed,\n should be " + str( stored_ans @@ -302,7 +291,7 @@ def eval_results(sim): # Ensure latest simulated value hasn't changed from stored answer assert np.allclose( sim_val_2, stored_ans, atol=1e-4 - ), "Flow in the " + exdirs[ + ), "Flow in the " + ex[ 1 ] + " test problem (simulates " "viscosity) has changed,\n should be " + str( stored_ans @@ -317,43 +306,25 @@ def eval_results(sim): # Ensure the flow leaving model 3 is less than what leaves model 2 assert abs(stored_ans) > abs(sim_val_3), ( "Exit flow from model " - + exdirs[1] + + ex[1] + " should be greater than flow exiting " - + exdirs[2] + + ex[2] + ", but it is not." ) # - No need to change any code below @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the model - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_results, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_results, idxsim=idx) - test.run_mf6(sim) - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(idx, name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, idx, ws) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_results, idxsim=idx + ), + ws, + ) diff --git a/autotest/test_gwf_vsc02.py b/autotest/test_gwf_vsc02.py index 2e6039d5116..a3fb061646e 100644 --- a/autotest/test_gwf_vsc02.py +++ b/autotest/test_gwf_vsc02.py @@ -12,29 +12,17 @@ import os import sys +import flopy import numpy as np import pytest - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -# Import common functionality -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation # Setup scenario input hyd_cond = [1205.49396942506, 864.0] # Hydraulic conductivity (m/d) ex = ["no-vsc02-bnd", "vsc02-bnd", "no-vsc02-k"] viscosity_on = [False, True, False] hydraulic_conductivity = [hyd_cond[0], hyd_cond[1], hyd_cond[1]] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) # Model units @@ -291,7 +279,7 @@ def eval_results(sim): # Ensure latest simulated value hasn't changed from stored answer assert np.allclose( sim_val_1, stored_ans, atol=1e-4 - ), "Flow in the " + exdirs[ + ), "Flow in the " + ex[ 0 ] + " test problem (doesn't simulate " "viscosity) has changed,\n should be " + str( stored_ans @@ -306,7 +294,7 @@ def eval_results(sim): # Ensure latest simulated value hasn't changed from stored answer assert np.allclose( sim_val_2, stored_ans, atol=1e-4 - ), "Flow in the " + exdirs[ + ), "Flow in the " + ex[ 1 ] + " test problem (simulates " "viscosity) has changed,\n should be " + str( stored_ans @@ -321,43 +309,24 @@ def eval_results(sim): # Ensure the flow leaving model 3 is less than what leaves model 2 assert abs(stored_ans) > abs(sim_val_3), ( "Exit flow from model " - + exdirs[1] + + ex[1] + " should be greater than flow existing " - + exdirs[2] + + ex[2] + ", but it is not." ) -# - No need to change any code below @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the model - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_results, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_results, idxsim=idx) - test.run_mf6(sim) - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(idx, name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, idx, ws) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_results, idxsim=idx + ), + ws, + ) diff --git a/autotest/test_gwf_vsc03_sfr.py b/autotest/test_gwf_vsc03_sfr.py index a3c79f0620b..97e0148e148 100644 --- a/autotest/test_gwf_vsc03_sfr.py +++ b/autotest/test_gwf_vsc03_sfr.py @@ -12,25 +12,14 @@ import os import sys +import flopy import numpy as np import pytest - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation ex = ["no-vsc-sfr01", "vsc-sfr01"] viscosity_on = [False, True] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) # Equation for determining land surface elevation with a stream running down the middle def topElev_sfrCentered(x, y): @@ -541,36 +530,17 @@ def eval_results(sim): ) -# - No need to change any code below @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the model - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_results, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_results, idxsim=idx) - test.run_mf6(sim) - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(idx, name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, idx, ws) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_results, idxsim=idx + ), + ws, + ) diff --git a/autotest/test_gwf_vsc04_lak.py b/autotest/test_gwf_vsc04_lak.py index 54757eca6e5..912b2772f5e 100644 --- a/autotest/test_gwf_vsc04_lak.py +++ b/autotest/test_gwf_vsc04_lak.py @@ -14,27 +14,15 @@ # import os -import sys +import flopy import numpy as np import pytest - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation ex = ["no-vsc04-lak", "vsc04-lak"] viscosity_on = [False, True] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) # Model units length_units = "m" @@ -174,11 +162,10 @@ # -def build_model(idx, dir): +def build_model(idx, ws): global lak_lkup_dict # Base simulation and model name and workspace - ws = dir name = ex[idx] print("Building model...{}".format(name)) @@ -787,36 +774,17 @@ def eval_results(sim): ) -# - No need to change any code below @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the model - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_results, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_results, idxsim=idx) - test.run_mf6(sim) - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(idx, name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, idx, ws) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_results, idxsim=idx + ), + ws, + ) diff --git a/autotest/test_gwf_vsc05_hfb.py b/autotest/test_gwf_vsc05_hfb.py index 6846226fd78..490139b73e6 100644 --- a/autotest/test_gwf_vsc05_hfb.py +++ b/autotest/test_gwf_vsc05_hfb.py @@ -23,27 +23,16 @@ import os import sys +import flopy import numpy as np import pytest - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation hyd_cond = [1205.49396942506, 864.0] # Hydraulic conductivity (m/d) ex = ["no-vsc05-hfb", "vsc05-hfb", "no-vsc05-k"] viscosity_on = [False, True, False] hydraulic_conductivity = [hyd_cond[0], hyd_cond[1], hyd_cond[1]] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) # Model units @@ -350,7 +339,7 @@ def eval_results(sim): no_vsc_bud_last[:, 2], stored_ans[:, 2], atol=1e-3 ), ( "Flow in models " - + exdirs[0] + + ex[0] + " and the established answer should be approximately " "equal, but are not." ) @@ -362,7 +351,7 @@ def eval_results(sim): with_vsc_bud_last[:, 2], stored_ans[:, 2], atol=1e-3 ), ( "Flow in models " - + exdirs[1] + + ex[1] + " and the established answer should be approximately " "equal, but are not." ) @@ -375,41 +364,23 @@ def eval_results(sim): assert np.less(no_vsc_low_k_bud_last[:, 2], stored_ans[:, 2]).all(), ( "Exit flow from model the established answer " "should be greater than flow existing " - + exdirs[2] + + ex[2] + ", but it is not." ) # - No need to change any code below @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the model - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_results, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_results, idxsim=idx) - test.run_mf6(sim) - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(idx, name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, idx, ws) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_results, idxsim=idx + ), + ws, + ) diff --git a/autotest/test_gwf_wel01.py b/autotest/test_gwf_wel01.py index c6923112ccc..719504765b5 100644 --- a/autotest/test_gwf_wel01.py +++ b/autotest/test_gwf_wel01.py @@ -5,33 +5,13 @@ """ import os +import flopy import numpy as np import pytest - -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation ex = ["wel01"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) -ddir = "data" # set static data nper = 1 @@ -201,39 +181,18 @@ def eval_obs(sim): errmsg += f"{a1} /= {a2}" assert np.allclose(a1, a2), errmsg - return - -# - No need to change any code below @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the models - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_obs, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_obs, idxsim=idx) - test.run_mf6(sim) - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(idx, name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, idx, ws) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_obs, idxsim=idx + ), + ws, + ) diff --git a/autotest/test_gwf_zb01.py b/autotest/test_gwf_zb01.py index 36fa5c8ca83..a408e2b7b7b 100644 --- a/autotest/test_gwf_zb01.py +++ b/autotest/test_gwf_zb01.py @@ -1,37 +1,15 @@ import os +import flopy import numpy as np import pytest - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -import targets -from framework import running_on_CI, testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation ex = ["zbud6_zb01"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) - -ddir = "data" - -## run all examples on Travis -continuous_integration = [True for idx in range(len(exdirs))] - -# set replace_exe to None to use default executable -replace_exe = None - -htol = [None for idx in range(len(exdirs))] +htol = [None for idx in range(len(ex))] dtol = 1e-3 budtol = 1e-2 - bud_lst = [ "STO-SS_IN", "STO-SS_OUT", @@ -132,13 +110,13 @@ # variant SUB package problem 3 -def build_model(idx, dir): +def build_model(idx, dir, exe): name = ex[idx] # build MODFLOW 6 files ws = dir sim = flopy.mf6.MFSimulation( - sim_name=name, version="mf6", exe_name="mf6", sim_ws=ws + sim_name=name, version="mf6", exe_name=exe, sim_ws=ws ) # create tdis package tdis = flopy.mf6.ModflowTdis( @@ -238,8 +216,7 @@ def build_model(idx, dir): return sim, None -def eval_zb6(sim): - +def eval_zb6(sim, exe): print("evaluating zonebudget...") # build zonebudget files @@ -267,21 +244,18 @@ def eval_zb6(sim): f.close() # run zonebudget - zbexe = os.path.abspath(targets.target_dict["zbud6"]) success, buff = flopy.run_model( - zbexe, + exe, "zonebudget.nam", model_ws=sim.simpath, silent=False, report=True, ) if success: - print(f"successfully ran...{os.path.basename(zbexe)}") sim.success = True else: sim.success = False - msg = f"could not run...{zbexe}" - assert success, msg + assert success # read data from csv file fpth = os.path.join(sim.simpath, "zonebudget.csv") @@ -414,62 +388,24 @@ def eval_zb6(sim): sim.success = True print(" " + msg) - return - -# - No need to change any code below @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir): - - # determine if running on CI infrastructure - is_CI = running_on_CI() - r_exe = None - if not is_CI: - if replace_exe is not None: - r_exe = replace_exe - - # initialize testing framework - test = testing_framework() - - # build the models - test.build_mf6_models_legacy(build_model, idx, dir) - - # run the test model - if is_CI and not continuous_integration[idx]: - return - test.run_mf6( - Simulation( - dir, exfunc=eval_zb6, exe_dict=r_exe, htol=htol[idx], idxsim=idx - ) - ) - - -def main(): - # initialize testing framework - test = testing_framework() - - # build the models - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models_legacy(build_model, idx, dir) - sim = Simulation( - dir, - exfunc=eval_zb6, - exe_dict=replace_exe, +def test_mf6model(idx, name, function_tmpdir, targets): + ws = str(function_tmpdir) + mf6 = targets.mf6 + zb6 = targets.zbud6 + test = TestFramework() + test.build(lambda i, w: build_model(i, w, mf6), idx, ws) + test.run( + TestSimulation( + name=name, + exe_dict=targets, + exfunc=lambda s: eval_zb6(s, zb6), htol=htol[idx], idxsim=idx, - ) - test.run_mf6(sim) - - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() + ), + ws, + ) diff --git a/autotest/test_gwfgwf_lgr.py b/autotest/test_gwfgwf_lgr.py index 74b94a63d69..7404f10805b 100644 --- a/autotest/test_gwfgwf_lgr.py +++ b/autotest/test_gwfgwf_lgr.py @@ -32,27 +32,15 @@ """ import os +import flopy import numpy as np import pytest - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - from flopy.utils.lgrutil import Lgr - -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation ex = ["gwfgwf_lgr_classic", "gwfgwf_lgr_ifmod"] ifmod = [False, True] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) parent_name = "parent" child_name = "child" @@ -287,41 +275,18 @@ def eval_heads(sim): errmsg = f"min or max residual too large {res.min()} {res.max()}" assert np.allclose(res, 0.0, atol=1.0e-6), errmsg - return - @pytest.mark.parametrize( - "idx, exdir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -@pytest.mark.developmode -def test_mf6model(idx, exdir): - # initialize testing framework - test = testing_framework() - - # build the model - test.build_mf6_models(build_model, idx, exdir) - - # run the test model - test.run_mf6(Simulation(exdir, exfunc=eval_heads, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # run the test models - for idx, exdir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, exdir) - - sim = Simulation(exdir, exfunc=eval_heads, idxsim=idx) - test.run_mf6(sim) - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(idx, name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, idx, ws) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_heads, idxsim=idx + ), + ws, + ) diff --git a/autotest/test_gwt_adv01.py b/autotest/test_gwt_adv01.py index 3afcf22e605..7132f16d38e 100644 --- a/autotest/test_gwt_adv01.py +++ b/autotest/test_gwt_adv01.py @@ -6,28 +6,15 @@ """ import os -import sys +import flopy import numpy as np import pytest - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation ex = ["adv01a", "adv01b", "adv01c"] scheme = ["upstream", "central", "tvd"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) -ddir = "data" def build_model(idx, dir): @@ -601,42 +588,18 @@ def eval_transport(sim): creslist[sim.idxsim], conc ), "simulated concentrations do not match with known solution." - return - -# - No need to change any code below @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the models - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_transport, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # build the models - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_transport, idxsim=idx) - test.run_mf6(sim) - - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(idx, name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, idx, ws) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_transport, idxsim=idx + ), + ws, + ) diff --git a/autotest/test_gwt_adv01_fmi.py b/autotest/test_gwt_adv01_fmi.py index 98a08712210..84faee0f9a0 100644 --- a/autotest/test_gwt_adv01_fmi.py +++ b/autotest/test_gwt_adv01_fmi.py @@ -6,29 +6,17 @@ """ import os -import sys +import flopy import numpy as np import pytest - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from binary_file_writer import uniform_flow_field, write_budget, write_head -from framework import testing_framework -from simulation import Simulation +from flopy.utils.binaryfile import write_budget, write_head +from flopy.utils.gridutil import uniform_flow_field +from framework import TestFramework +from simulation import TestSimulation ex = ["adv01a_fmi", "adv01b_fmi", "adv01c_fmi"] scheme = ["upstream", "central", "tvd"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) -ddir = "data" def build_model(idx, dir): @@ -576,42 +564,18 @@ def eval_transport(sim): creslist[sim.idxsim], conc ), "simulated concentrations do not match with known solution." - return - -# - No need to change any code below @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the models - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_transport, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # build the models - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_transport, idxsim=idx) - test.run_mf6(sim) - - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(idx, name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, idx, ws) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_transport, idxsim=idx + ), + ws, + ) diff --git a/autotest/test_gwt_adv01_gwtgwt.py b/autotest/test_gwt_adv01_gwtgwt.py index 42c47128abf..ed325110506 100644 --- a/autotest/test_gwt_adv01_gwtgwt.py +++ b/autotest/test_gwt_adv01_gwtgwt.py @@ -7,28 +7,14 @@ import os +import flopy import numpy as np import pytest -from matplotlib import pyplot as plt - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation ex = ["adv01a_gwtgwt", "adv01b_gwtgwt", "adv01c_gwtgwt"] scheme = ["upstream", "central", "tvd"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) -ddir = "data" - gdelr = 1.0 @@ -739,43 +725,18 @@ def eval_transport(sim): # TODO: this is not implemented yet: # assert np.allclose(res, 0.0, atol=1.0e-6), errmsg - return - -# - No need to change any code below @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -@pytest.mark.developmode -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the models - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_transport, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # build the models - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_transport, idxsim=idx) - test.run_mf6(sim) - - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(idx, name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, idx, ws) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_transport, idxsim=idx + ), + ws, + ) diff --git a/autotest/test_gwt_adv02.py b/autotest/test_gwt_adv02.py index 39942b02189..4d0a5ba635b 100644 --- a/autotest/test_gwt_adv02.py +++ b/autotest/test_gwt_adv02.py @@ -8,29 +8,16 @@ """ import os -import sys +import flopy +import flopy.utils.cvfdutil import numpy as np import pytest - -try: - import flopy - import flopy.utils.cvfdutil -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation ex = ["adv02a", "adv02b", "adv02c"] scheme = ["upstream", "central", "tvd"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) -ddir = "data" def grid_triangulator(itri, delr, delc): @@ -954,42 +941,18 @@ def eval_transport(sim): creslist[sim.idxsim], conc ), "simulated concentrations do not match with known solution." - return - -# - No need to change any code below @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the models - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_transport, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # build the models - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_transport, idxsim=idx) - test.run_mf6(sim) - - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(idx, name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, idx, ws) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_transport, idxsim=idx + ), + ws, + ) diff --git a/autotest/test_gwt_adv03.py b/autotest/test_gwt_adv03.py index db6014f586d..5134fe038f7 100644 --- a/autotest/test_gwt_adv03.py +++ b/autotest/test_gwt_adv03.py @@ -8,29 +8,16 @@ """ import os -import sys +import flopy +import flopy.utils.cvfdutil import numpy as np import pytest - -try: - import flopy - import flopy.utils.cvfdutil -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation ex = ["adv03a", "adv03b", "adv03c"] scheme = ["upstream", "central", "tvd"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) -ddir = "data" def grid_triangulator(itri, delr, delc): @@ -506,42 +493,18 @@ def eval_transport(sim): conc[0, 0, -ncellsperrow:], ) - return - -# - No need to change any code below @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the models - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_transport, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # build the models - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_transport, idxsim=idx) - test.run_mf6(sim) - - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(idx, name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, idx, ws) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_transport, idxsim=idx + ), + ws, + ) diff --git a/autotest/test_gwt_adv04.py b/autotest/test_gwt_adv04.py index 9bb743fe91d..49c1ca45690 100644 --- a/autotest/test_gwt_adv04.py +++ b/autotest/test_gwt_adv04.py @@ -7,36 +7,15 @@ """ import os -import sys +import flopy import numpy as np import pytest - -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation ex = ["adv04a", "adv04b", "adv04c"] scheme = ["upstream", "central", "tvd"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) -ddir = "data" def build_model(idx, dir): @@ -277,44 +256,18 @@ def eval_transport(sim): "symmetric in left-right direction." ) - return - - -# - No need to change any code below - @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the models - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_transport, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # build the models - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_transport, idxsim=idx) - test.run_mf6(sim) - - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(idx, name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, idx, ws) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_transport, idxsim=idx + ), + ws, + ) diff --git a/autotest/test_gwt_buy_solute_heat.py b/autotest/test_gwt_buy_solute_heat.py index b1a7f0b818a..173dfd60b04 100644 --- a/autotest/test_gwt_buy_solute_heat.py +++ b/autotest/test_gwt_buy_solute_heat.py @@ -1,24 +1,12 @@ import os -import sys +import flopy import numpy as np import pytest - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation ex = ["gwtbuy"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) def build_model(idx, dir): @@ -342,8 +330,8 @@ def build_model(idx, dir): def make_plot(sim): print("making plots...") - name = ex[sim.idxsim] - ws = exdirs[sim.idxsim] + name = sim.name + ws = sim.simpath sim = flopy.mf6.MFSimulation.load(sim_ws=ws) gwfname = "flow" gwtsname = "salinity" @@ -429,7 +417,6 @@ def eval_transport(sim): if makeplot: make_plot(sim) - name = ex[sim.idxsim] ws = sim.simpath gwfname = "flow" gwtsname = "salinity" @@ -473,44 +460,19 @@ def eval_transport(sim): np.savetxt(fname, densecalculated.reshape(200, 100)) assert False, "density is not correct" - # assert False - - return - -# - No need to change any code below +@pytest.mark.slow @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "name", + ex, ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the models - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_transport, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # build the models - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_transport, idxsim=idx) - test.run_mf6(sim) - - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, 0, ws) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_transport, idxsim=0 + ), + ws, + ) diff --git a/autotest/test_gwt_disu01.py b/autotest/test_gwt_disu01.py index 45e33a5602e..a9aad3dfdf6 100644 --- a/autotest/test_gwt_disu01.py +++ b/autotest/test_gwt_disu01.py @@ -7,41 +7,18 @@ """ import os -import sys +import flopy import numpy as np import pytest +from flopy.utils.gridutil import get_disu_kwargs +from framework import TestFramework +from simulation import TestSimulation -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from disu_util import get_disu_kwargs -from framework import testing_framework -from simulation import Simulation - -ex = [ - "disu01a", -] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) -ddir = "data" - - -def build_model(idx, dir): +ex = ["disu01a"] + + +def build_model(idx, dir, exe): nlay, nrow, ncol = 1, 21, 21 nper = 1 perlen = [5.0] @@ -87,7 +64,7 @@ def get_nn(k, i, j): # build MODFLOW 6 files ws = dir sim = flopy.mf6.MFSimulation( - sim_name=name, version="mf6", exe_name="mf6", sim_ws=ws + sim_name=name, version="mf6", exe_name=exe, sim_ws=ws ) # create tdis package tdis = flopy.mf6.ModflowTdis( @@ -246,7 +223,7 @@ def get_nn(k, i, j): def eval_transport(sim): print("evaluating transport...") - name = ex[sim.idxsim] + name = sim.name gwtname = "gwt_" + name fpth = os.path.join(sim.simpath, f"{gwtname}.ucn") @@ -272,44 +249,19 @@ def eval_transport(sim): "symmetric in left-right direction." ) - return - - -# - No need to change any code below - @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "name", + ex, ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the models - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_transport, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # build the models - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_transport, idxsim=idx) - test.run_mf6(sim) - - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(name, function_tmpdir, targets): + ws = str(function_tmpdir) + mf6 = targets.mf6 + test = TestFramework() + test.build(lambda i, w: build_model(i, w, mf6), 0, ws) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_transport, idxsim=0 + ), + ws, + ) diff --git a/autotest/test_gwt_dsp01.py b/autotest/test_gwt_dsp01.py index ad11fc51c19..7d392cb225d 100644 --- a/autotest/test_gwt_dsp01.py +++ b/autotest/test_gwt_dsp01.py @@ -1,34 +1,13 @@ import os -import sys +import flopy import numpy as np import pytest - -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation ex = ["dsp01a", "dsp01b"] xt3d = [False, True] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) -ddir = "data" def build_model(idx, dir): @@ -406,42 +385,18 @@ def eval_transport(sim): # comment when done testing # assert False - return - -# - No need to change any code below @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the models - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_transport, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # build the models - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_transport, idxsim=idx) - test.run_mf6(sim) - - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(idx, name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, idx, ws) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_transport, idxsim=idx + ), + ws, + ) diff --git a/autotest/test_gwt_dsp01_fmi.py b/autotest/test_gwt_dsp01_fmi.py index 10a7e5c5189..424d467a702 100644 --- a/autotest/test_gwt_dsp01_fmi.py +++ b/autotest/test_gwt_dsp01_fmi.py @@ -1,35 +1,14 @@ import os -import sys +import flopy import numpy as np import pytest - -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from binary_file_writer import write_budget, write_head -from framework import testing_framework -from simulation import Simulation +from flopy.utils.binaryfile import write_budget, write_head +from framework import TestFramework +from simulation import TestSimulation ex = ["dsp01a_fmi", "dsp01b_fmi"] xt3d = [False, True] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) -ddir = "data" def build_model(idx, dir): @@ -340,42 +319,18 @@ def eval_transport(sim): cres, conc ), "simulated concentrations do not match with known solution." - return - -# - No need to change any code below @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the models - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_transport, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # build the models - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_transport, idxsim=idx) - test.run_mf6(sim) - - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(idx, name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, idx, ws) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_transport, idxsim=idx + ), + ws, + ) diff --git a/autotest/test_gwt_dsp01_gwtgwt.py b/autotest/test_gwt_dsp01_gwtgwt.py index d7d01e6fe05..156269c0e05 100644 --- a/autotest/test_gwt_dsp01_gwtgwt.py +++ b/autotest/test_gwt_dsp01_gwtgwt.py @@ -6,27 +6,13 @@ import os +import flopy import numpy as np import pytest -from matplotlib import pyplot as plt - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation ex = ["dsp01_gwtgwt"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) -ddir = "data" - gdelr = 1.0 @@ -311,43 +297,18 @@ def eval_transport(sim): # no loss of solute assert abs(np.sum(conc1) + np.sum(conc2) - 100.0) < 1e-6 - return - -# - No need to change any code below @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -@pytest.mark.developmode -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the models - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_transport, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # build the models - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_transport, idxsim=idx) - test.run_mf6(sim) - - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(idx, name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, idx, ws) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_transport, idxsim=idx + ), + ws, + ) diff --git a/autotest/test_gwt_dsp01_noadv.py b/autotest/test_gwt_dsp01_noadv.py index 230d3711752..b5864f0bb19 100644 --- a/autotest/test_gwt_dsp01_noadv.py +++ b/autotest/test_gwt_dsp01_noadv.py @@ -1,34 +1,13 @@ import os -import sys +import flopy import numpy as np import pytest - -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation ex = ["dsp01a_noadv", "dsp01b_noadv"] xt3d = [False, True] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) -ddir = "data" def build_model(idx, dir): @@ -284,42 +263,18 @@ def eval_transport(sim): cres, conc ), "simulated concentrations do not match with known solution." - return - -# - No need to change any code below @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the models - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_transport, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # build the models - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_transport, idxsim=idx) - test.run_mf6(sim) - - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(idx, name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, idx, ws) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_transport, idxsim=idx + ), + ws, + ) diff --git a/autotest/test_gwt_dsp02.py b/autotest/test_gwt_dsp02.py index d2e0d7f0613..0b245e78058 100644 --- a/autotest/test_gwt_dsp02.py +++ b/autotest/test_gwt_dsp02.py @@ -8,29 +8,16 @@ """ import os -import sys +import flopy +import flopy.utils.cvfdutil import numpy as np import pytest - -try: - import flopy - import flopy.utils.cvfdutil -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation ex = ["dsp02a", "dsp02b"] xt3d = [True, False] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) -ddir = "data" def grid_triangulator(itri, delr, delc): @@ -725,42 +712,18 @@ def eval_transport(sim): creslist[sim.idxsim], conc ), "simulated concentrations do not match with known solution." - return - -# - No need to change any code below @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the models - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_transport, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # build the models - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_transport, idxsim=idx) - test.run_mf6(sim) - - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(idx, name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, idx, ws) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_transport, idxsim=idx + ), + ws, + ) diff --git a/autotest/test_gwt_dsp03.py b/autotest/test_gwt_dsp03.py index 960de6fe324..7eb77075c88 100644 --- a/autotest/test_gwt_dsp03.py +++ b/autotest/test_gwt_dsp03.py @@ -8,29 +8,16 @@ """ import os -import sys +import flopy +import flopy.utils.cvfdutil import numpy as np import pytest - -try: - import flopy - import flopy.utils.cvfdutil -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation ex = ["dsp03a", "dsp03b"] xt3d = [False, True] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) -ddir = "data" def grid_triangulator(itri, delr, delc): @@ -451,42 +438,19 @@ def eval_transport(sim): conc[0, 0, -ncellsperrow:], ) - return - -# - No need to change any code below +@pytest.mark.slow @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the models - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_transport, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # build the models - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_transport, idxsim=idx) - test.run_mf6(sim) - - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(idx, name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, idx, ws) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_transport, idxsim=idx + ), + ws, + ) diff --git a/autotest/test_gwt_dsp04.py b/autotest/test_gwt_dsp04.py index 85724e4856d..aa9d1b02206 100644 --- a/autotest/test_gwt_dsp04.py +++ b/autotest/test_gwt_dsp04.py @@ -1,35 +1,14 @@ import os -import sys +import flopy import numpy as np import pytest - -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation # test dispersion without and with xt3d ex = ["dsp04a", "dsp04b"] xt3d = [None, True] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) -ddir = "data" def build_model(idx, dir): @@ -271,44 +250,18 @@ def eval_transport(sim): "symmetric in left-right direction." ) - return - - -# - No need to change any code below - @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the models - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_transport, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # build the models - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_transport, idxsim=idx) - test.run_mf6(sim) - - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(idx, name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, idx, ws) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_transport, idxsim=idx + ), + ws, + ) diff --git a/autotest/test_gwt_dsp05_noadv.py b/autotest/test_gwt_dsp05_noadv.py index a5852b2132e..93af5361d4d 100644 --- a/autotest/test_gwt_dsp05_noadv.py +++ b/autotest/test_gwt_dsp05_noadv.py @@ -7,34 +7,14 @@ """ import os +import flopy import numpy as np import pytest - -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation ex = ["dsp05a_noadv", "dsp01b_noadv"] xt3d = [False, True] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) -ddir = "data" def build_model(idx, dir): @@ -180,42 +160,18 @@ def eval_transport(sim): msg = f"simulated concentrations do not match with known solution. {conc} {cres}" assert np.allclose(cres, conc.flatten()), msg - return - -# - No need to change any code below @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the models - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_transport, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # build the models - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_transport, idxsim=idx) - test.run_mf6(sim) - - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(idx, name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, idx, ws) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_transport, idxsim=idx + ), + ws, + ) diff --git a/autotest/test_gwt_fmi01.py b/autotest/test_gwt_fmi01.py index 52d4c3680f6..fcf6a9e0251 100644 --- a/autotest/test_gwt_fmi01.py +++ b/autotest/test_gwt_fmi01.py @@ -1,37 +1,15 @@ import os -import sys +import flopy import numpy as np import pytest +from flopy.utils.binaryfile import write_budget, write_head +from flopy.utils.gridutil import uniform_flow_field +from framework import TestFramework +from simulation import TestSimulation -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from binary_file_writer import uniform_flow_field, write_budget, write_head -from framework import testing_framework -from simulation import Simulation - -ex = [ - "fmi01a_fc", -] +ex = ["fmi01a_fc"] xt3d = [False, True] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) -ddir = "data" def build_model(idx, dir): @@ -198,17 +176,12 @@ def build_model(idx, dir): def eval_transport(sim): print("evaluating transport...") - name = ex[sim.idxsim] + name = sim.name gwtname = "gwt_" + name fpth = os.path.join(sim.simpath, f"{gwtname}.ucn") - try: - cobj = flopy.utils.HeadFile( - fpth, precision="double", text="CONCENTRATION" - ) - conc = cobj.get_data() - except: - assert False, f'could not load data from "{fpth}"' + cobj = flopy.utils.HeadFile(fpth, precision="double", text="CONCENTRATION") + conc = cobj.get_data() # This is the answer to this problem. Concentration should not change cres = [[[10, 10, 10]]] @@ -217,42 +190,18 @@ def eval_transport(sim): errmsg += f"cres: {cres}\ncans:{conc}" assert np.allclose(cres, conc), errmsg - return - -# - No need to change any code below @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "name", + ex, ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the models - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_transport, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # build the models - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_transport, idxsim=idx) - test.run_mf6(sim) - - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, 0, ws) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_transport, idxsim=0 + ), + ws, + ) diff --git a/autotest/test_gwt_fmi02.py b/autotest/test_gwt_fmi02.py index 032d47b309f..545428e55e9 100644 --- a/autotest/test_gwt_fmi02.py +++ b/autotest/test_gwt_fmi02.py @@ -1,45 +1,16 @@ # tests to ability to run flow model first followed by transport model import os -import shutil -import numpy as np -import pytest +import flopy -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - - -import targets - -exe_name_mf6 = targets.target_dict["mf6"] -exe_name_mf6 = os.path.abspath(exe_name_mf6) -testdir = "./temp" testgroup = "fmi02" -d = os.path.join(testdir, testgroup) -if os.path.isdir(d): - shutil.rmtree(d) -def run_flow_model(): +def run_flow_model(dir, exe): name = "flow" - ws = os.path.join(testdir, testgroup, name) - sim = flopy.mf6.MFSimulation( - sim_name=name, sim_ws=ws, exe_name=exe_name_mf6 - ) + ws = os.path.join(dir, testgroup, name) + sim = flopy.mf6.MFSimulation(sim_name=name, sim_ws=ws, exe_name=exe) pd = [(1.0, 1, 1.0), (1.0, 1, 1.0)] tdis = flopy.mf6.ModflowTdis(sim, nper=len(pd), perioddata=pd) ims = flopy.mf6.ModflowIms(sim) @@ -70,15 +41,12 @@ def run_flow_model(): assert os.path.isfile(fname) fname = os.path.join(ws, head_file) assert os.path.isfile(fname) - return -def run_transport_model(): +def run_transport_model(dir, exe): name = "transport" - ws = os.path.join(testdir, testgroup, name) - sim = flopy.mf6.MFSimulation( - sim_name=name, sim_ws=ws, exe_name=exe_name_mf6 - ) + ws = os.path.join(dir, testgroup, name) + sim = flopy.mf6.MFSimulation(sim_name=name, sim_ws=ws, exe_name=exe) pd = [(1.0, 10, 1.0), (1.0, 10, 1.0)] tdis = flopy.mf6.ModflowTdis(sim, nper=len(pd), perioddata=pd) ims = flopy.mf6.ModflowIms(sim, linear_acceleration="BICGSTAB") @@ -106,21 +74,9 @@ def run_transport_model(): assert os.path.isfile(fname) fname = os.path.join(ws, concentration_file) assert os.path.isfile(fname) - return - - -def test_fmi(): - run_flow_model() - run_transport_model() - d = os.path.join(testdir, testgroup) - if os.path.isdir(d): - shutil.rmtree(d) - return - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - # run tests - test_fmi() +def test_fmi(function_tmpdir, targets): + mf6 = targets["mf6"] + run_flow_model(str(function_tmpdir), mf6) + run_transport_model(str(function_tmpdir), mf6) diff --git a/autotest/test_gwt_henry.py b/autotest/test_gwt_henry.py index 6b7eae4478c..19d3e93fd40 100644 --- a/autotest/test_gwt_henry.py +++ b/autotest/test_gwt_henry.py @@ -1,24 +1,12 @@ import os -import sys +import flopy import numpy as np import pytest - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation ex = ["henry01"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) def build_model(idx, dir): @@ -246,7 +234,7 @@ def chd_value(k): def eval_transport(sim): print("evaluating transport...") - name = ex[sim.idxsim] + name = sim.name gwtname = "gwt_" + name fpth = os.path.join(sim.simpath, f"{gwtname}.ucn") @@ -300,42 +288,18 @@ def eval_transport(sim): conc[-1, :, :], ) - return - -# - No need to change any code below @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "name", + ex, ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the models - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_transport, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # build the models - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_transport, idxsim=idx) - test.run_mf6(sim) - - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, 0, ws) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_transport, idxsim=0 + ), + ws, + ) diff --git a/autotest/test_gwt_henry_nr.py b/autotest/test_gwt_henry_nr.py index b410def2e20..3c652cd7ae0 100644 --- a/autotest/test_gwt_henry_nr.py +++ b/autotest/test_gwt_henry_nr.py @@ -6,26 +6,14 @@ # the effects of tides on the aquifer. import os -import sys +import flopy import numpy as np import pytest - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation ex = ["henrynr01"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) # global model variables nlay = 20 @@ -388,8 +376,8 @@ def get_patch_collection(modelgrid, head, conc, cmap="jet", zorder=None): def make_plot(sim, headall, concall): print("making plots...") - name = ex[sim.idxsim] - ws = exdirs[sim.idxsim] + name = sim.name + ws = sim.simpath sim = flopy.mf6.MFSimulation.load(sim_ws=ws) gwfname = "gwf_" + name gwtname = "gwt_" + name @@ -472,14 +460,12 @@ def make_plot(sim, headall, concall): fname = os.path.join(ws, fname) plt.savefig(fname, bbox_inches="tight") - return - def eval_transport(sim): print("evaluating transport...") - name = ex[sim.idxsim] - ws = exdirs[sim.idxsim] + name = sim.name + ws = sim.simpath gwfname = "gwf_" + name gwtname = "gwt_" + name @@ -540,42 +526,19 @@ def eval_transport(sim): make_plot(sim, head, conc) assert False - return - -# - No need to change any code below +@pytest.mark.slow @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the models - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_transport, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # build the models - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_transport, idxsim=idx) - test.run_mf6(sim) - - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(idx, name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, idx, ws) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_transport, idxsim=idx + ), + ws, + ) diff --git a/autotest/test_gwt_henry_openclose.py b/autotest/test_gwt_henry_openclose.py index 596d9e84708..86d8ff5b075 100644 --- a/autotest/test_gwt_henry_openclose.py +++ b/autotest/test_gwt_henry_openclose.py @@ -1,24 +1,12 @@ import os -import sys +import flopy import numpy as np import pytest - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation ex = ["henry_ext"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) def build_model(idx, dir): @@ -249,7 +237,7 @@ def chd_value(k): def eval_transport(sim): print("evaluating transport...") - name = ex[sim.idxsim] + name = sim.name gwtname = "gwt_" + name fpth = os.path.join(sim.simpath, f"{gwtname}.ucn") @@ -303,42 +291,18 @@ def eval_transport(sim): conc[-1, :, :], ) - return - -# - No need to change any code below @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "name", + ex, ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the models - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_transport, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # build the models - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_transport, idxsim=idx) - test.run_mf6(sim) - - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, 0, ws) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_transport, idxsim=0 + ), + ws, + ) diff --git a/autotest/test_gwt_ims_issue655.py b/autotest/test_gwt_ims_issue655.py index 07ef7b8bf6c..497c2585bad 100644 --- a/autotest/test_gwt_ims_issue655.py +++ b/autotest/test_gwt_ims_issue655.py @@ -8,27 +8,11 @@ import os +import flopy import numpy as np import pytest - -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation ex = ["issue655a", "issue655b"] newton = [ @@ -38,9 +22,6 @@ laytyp = [1] ss = [1.0e-10] sy = [0.1] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) nlay, nrow, ncol = 1, 11, 11 @@ -310,39 +291,18 @@ def eval_transport(sim): # assert np.allclose(c, conc_calc, atol=0.001), msg # vold = v - return - -# - No need to change any code below @pytest.mark.parametrize( - "idx, exdir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, exdir): - # initialize testing framework - test = testing_framework() - - # build the model - test.build_mf6_models(build_model, idx, exdir) - - # run the test model - test.run_mf6(Simulation(exdir, exfunc=eval_transport, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # build and run the test model - for idx, exdir in enumerate(exdirs): - test_mf6model(idx, exdir) - - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(idx, name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, idx, ws) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_transport, idxsim=idx + ), + ws, + ) diff --git a/autotest/test_gwt_ist01.py b/autotest/test_gwt_ist01.py index b9ea2a9ea4e..a87d6be5de0 100644 --- a/autotest/test_gwt_ist01.py +++ b/autotest/test_gwt_ist01.py @@ -6,27 +6,11 @@ import os +import flopy import numpy as np import pytest - -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation ex = ["ist01"] laytyp = [1] @@ -34,10 +18,6 @@ sy = [0.1] thetaim = [0.05] zetaim = [0.1] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) -ddir = "data" nlay, nrow, ncol = 1, 1, 1 @@ -243,27 +223,19 @@ def build_model(idx, dir): def eval_transport(sim): print("evaluating transport...") - name = ex[sim.idxsim] + name = sim.name gwtname = "gwt_" + name gwfname = "gwf_" + name # head fpth = os.path.join(sim.simpath, f"{gwfname}.hds") - try: - hobj = flopy.utils.HeadFile(fpth, precision="double") - head = hobj.get_alldata().flatten() - except: - assert False, f'could not load data from "{fpth}"' + hobj = flopy.utils.HeadFile(fpth, precision="double") + head = hobj.get_alldata().flatten() # mobile concentration fpth = os.path.join(sim.simpath, f"{gwtname}.ucn") - try: - cobj = flopy.utils.HeadFile( - fpth, precision="double", text="CONCENTRATION" - ) - conc = cobj.get_alldata().flatten() - except: - assert False, f'could not load data from "{fpth}"' + cobj = flopy.utils.HeadFile(fpth, precision="double", text="CONCENTRATION") + conc = cobj.get_alldata().flatten() # immobile concentration fpth = os.path.join(sim.simpath, f"{gwtname}.ist.ucn") @@ -287,49 +259,23 @@ def eval_transport(sim): rate_sim = immrate[i]["q"][0] saturation = 0.5 volume = 10.0 * 10.0 * 10.0 - rate_calc = ( - (cim[i] - conc[i]) * zetaim[sim.idxsim] * saturation * volume - ) + rate_calc = (cim[i] - conc[i]) * zetaim[0] * saturation * volume print(t, conc[i], cim[i], rate_sim, rate_calc) msg = f"Rate: {rate_sim} /= {rate_calc} for time {t}" assert np.allclose(rate_sim, rate_calc), msg - return - -# - No need to change any code below @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "name", + ex, ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the models - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_transport, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # build the models - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_transport, idxsim=idx) - test.run_mf6(sim) - - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, 0, ws) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_transport, idxsim=0 + ), + ws, + ) diff --git a/autotest/test_gwt_lkt01.py b/autotest/test_gwt_lkt01.py index e3407bbafdb..4971e5c9412 100644 --- a/autotest/test_gwt_lkt01.py +++ b/autotest/test_gwt_lkt01.py @@ -4,26 +4,14 @@ # leaks into the aquifer. import os -import sys +import flopy import numpy as np import pytest - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation ex = ["lkt_01"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) def build_model(idx, dir): @@ -346,14 +334,14 @@ def build_model(idx, dir): def get_mfsim(testsim): - ws = exdirs[testsim.idxsim] + ws = testsim.simpath sim = flopy.mf6.MFSimulation.load(sim_ws=ws) return sim def eval_csv_information(testsim): sim = get_mfsim(testsim) - name = ex[testsim.idxsim] + name = testsim.name gwfname = "gwf_" + name gwtname = "gwt_" + name gwf = sim.get_model(gwfname) @@ -362,9 +350,9 @@ def eval_csv_information(testsim): lak_budget = gwf.lak.output.budgetcsv().data result = lak_budget["PERCENT_DIFFERENCE"] answer = np.zeros(result.shape) - assert np.allclose(result, answer), f"Lake package does not have zero mass balance error: {result}" - - return + assert np.allclose( + result, answer + ), f"Lake package does not have zero mass balance error: {result}" def eval_results(sim): @@ -374,7 +362,7 @@ def eval_results(sim): eval_csv_information(sim) # ensure lake concentrations were saved - name = ex[sim.idxsim] + name = sim.name gwtname = "gwt_" + name fname = gwtname + ".lkt.bin" fname = os.path.join(sim.simpath, fname) @@ -444,39 +432,18 @@ def eval_results(sim): # uncomment when testing # assert False - return - -# - No need to change any code below @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the model - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_results, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_results, idxsim=idx) - test.run_mf6(sim) - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(idx, name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, idx, ws) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_results, idxsim=idx + ), + ws, + ) diff --git a/autotest/test_gwt_lkt02.py b/autotest/test_gwt_lkt02.py index 412ebc19ae9..15c29b6b9aa 100644 --- a/autotest/test_gwt_lkt02.py +++ b/autotest/test_gwt_lkt02.py @@ -2,26 +2,14 @@ # move solute from one lake to another. import os -import sys +import flopy import numpy as np import pytest - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation ex = ["lkt_02"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) def build_model(idx, dir): @@ -368,7 +356,7 @@ def eval_results(sim): print("evaluating results...") # ensure lake concentrations were saved - name = ex[sim.idxsim] + name = sim.name gwtname = "gwt_" + name fname = gwtname + ".lkt.bin" fname = os.path.join(sim.simpath, fname) @@ -497,39 +485,18 @@ def eval_results(sim): # uncomment when testing # assert False - return - -# - No need to change any code below @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "name", + ex, ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the model - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_results, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_results, idxsim=idx) - test.run_mf6(sim) - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, 0, ws) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_results, idxsim=0 + ), + ws, + ) diff --git a/autotest/test_gwt_lkt03.py b/autotest/test_gwt_lkt03.py index d89912be6ec..efd98105085 100644 --- a/autotest/test_gwt_lkt03.py +++ b/autotest/test_gwt_lkt03.py @@ -2,26 +2,14 @@ # such as rainfall to make sure mixing is calculated correctly. import os -import sys +import flopy import numpy as np import pytest - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation ex = ["lkt_03"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) def build_model(idx, dir): @@ -348,7 +336,7 @@ def eval_results(sim): print("evaluating results...") # ensure lake concentrations were saved - name = ex[sim.idxsim] + name = sim.name gwtname = "gwt_" + name fname = gwtname + ".lkt.bin" fname = os.path.join(sim.simpath, fname) @@ -393,39 +381,18 @@ def eval_results(sim): # uncomment when testing # assert False - return - -# - No need to change any code below @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "name", + ex, ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the model - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_results, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_results, idxsim=idx) - test.run_mf6(sim) - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, 0, ws) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_results, idxsim=0 + ), + ws, + ) diff --git a/autotest/test_gwt_lkt04.py b/autotest/test_gwt_lkt04.py index 7a906bb9000..f8bb2cf2c65 100644 --- a/autotest/test_gwt_lkt04.py +++ b/autotest/test_gwt_lkt04.py @@ -5,27 +5,17 @@ # should remain at zero. import os + +import flopy import numpy as np import pytest - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation ex = ["lkt_04"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) -def build_model(idx, dir): +def build_model(idx, dir, exe): lx = 5.0 lz = 1.0 nlay = 1 @@ -59,9 +49,8 @@ def build_model(idx, dir): name = ex[idx] # build MODFLOW 6 files - ws = dir sim = flopy.mf6.MFSimulation( - sim_name=name, version="mf6", exe_name="mf6", sim_ws=ws + sim_name=name, version="mf6", exe_name=exe, sim_ws=dir ) # create tdis package tdis = flopy.mf6.ModflowTdis( @@ -345,14 +334,14 @@ def build_model(idx, dir): def get_mfsim(testsim): - ws = exdirs[testsim.idxsim] + ws = testsim.simpath sim = flopy.mf6.MFSimulation.load(sim_ws=ws) return sim def eval_csv_information(testsim): sim = get_mfsim(testsim) - name = ex[testsim.idxsim] + name = testsim.name gwfname = "gwf_" + name gwtname = "gwt_" + name gwf = sim.get_model(gwfname) @@ -381,8 +370,6 @@ def eval_csv_information(testsim): assert success, f"One or more errors encountered in budget checks" - return - def eval_results(sim): print("evaluating results...") @@ -391,7 +378,7 @@ def eval_results(sim): eval_csv_information(sim) # ensure lake concentrations were saved - name = ex[sim.idxsim] + name = sim.name gwtname = "gwt_" + name fname = gwtname + ".lkt.bin" fname = os.path.join(sim.simpath, fname) @@ -485,39 +472,19 @@ def eval_results(sim): # uncomment when testing # assert False - return - -# - No need to change any code below @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the model - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_results, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_results, idxsim=idx) - test.run_mf6(sim) - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(idx, name, function_tmpdir, targets): + ws = str(function_tmpdir) + mf6 = targets["mf6"] + test = TestFramework() + test.build(lambda i, w: build_model(i, w, mf6), idx, ws) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_results, idxsim=idx + ), + ws, + ) diff --git a/autotest/test_gwt_moc3d01.py b/autotest/test_gwt_moc3d01.py index 6aa18374340..f9a084e5160 100644 --- a/autotest/test_gwt_moc3d01.py +++ b/autotest/test_gwt_moc3d01.py @@ -1,27 +1,10 @@ import os -import sys +import flopy import numpy as np import pytest - -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation ex = [ "moc3d01a", @@ -38,10 +21,6 @@ retardation = [None, None, None, None, 40.0, 4.0, 2.0, None] perlens = 4 * [120.0] + 3 * [240.0] + [120.0] decay = 7 * [None] + [0.01] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) -ddir = "data" def build_model(idx, dir): @@ -371,44 +350,18 @@ def eval_transport(sim): tsres, tssim ), "simulated concentrations do not match with known solution." - return - - -# - No need to change any code below - @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the models - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_transport, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # build the models - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_transport, idxsim=idx) - test.run_mf6(sim) - - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(idx, name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, idx, ws) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_transport, idxsim=idx + ), + ws, + ) diff --git a/autotest/test_gwt_moc3d01_zod.py b/autotest/test_gwt_moc3d01_zod.py index 30ad34d1b99..395298e0243 100644 --- a/autotest/test_gwt_moc3d01_zod.py +++ b/autotest/test_gwt_moc3d01_zod.py @@ -5,29 +5,12 @@ # where concentrations are zero. import os -import sys +import flopy import numpy as np import pytest - -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation ex = [ "moc3d01zoda", @@ -38,10 +21,6 @@ retardation = [None, 40, None, 40] decay = [0.01, 0.01, 0.1, 0.1] ist_package = [False, False, True, True] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) -ddir = "data" def build_model(idx, dir): @@ -441,11 +420,11 @@ def eval_transport(sim): makeplot = False if makeplot: fname = "fig-ct.pdf" - fname = os.path.join(exdirs[sim.idxsim], fname) + fname = os.path.join(ex[sim.idxsim], fname) make_plot_ct(tssim, fname) fname = "fig-cd.pdf" - fname = os.path.join(exdirs[sim.idxsim], fname) + fname = os.path.join(ex[sim.idxsim], fname) make_plot_cd(cobj, fname) tssim = tssim[::10] @@ -577,44 +556,18 @@ def eval_transport(sim): if tsres is not None: assert np.allclose(tsres, tssim), errmsg - return - - -# - No need to change any code below - @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the models - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_transport, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # build the models - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_transport, idxsim=idx) - test.run_mf6(sim) - - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(idx, name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, idx, ws) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_transport, idxsim=idx + ), + ws, + ) diff --git a/autotest/test_gwt_moc3d02.py b/autotest/test_gwt_moc3d02.py index 7b349b471aa..4d2b476c3dc 100644 --- a/autotest/test_gwt_moc3d02.py +++ b/autotest/test_gwt_moc3d02.py @@ -1,34 +1,13 @@ import os -import sys +import flopy import numpy as np import pytest - -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation ex = ["moc3d02a", "moc3d02b"] xt3d = [None, True] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) -ddir = "data" def build_model(idx, dir): @@ -329,42 +308,19 @@ def eval_transport(sim): cres, csim, rtol=1.0e-4 ), "simulated concentrations do not match with known solution." - return - -# - No need to change any code below +@pytest.mark.slow @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the models - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_transport, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # build the models - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_transport, idxsim=idx) - test.run_mf6(sim) - - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(idx, name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, idx, ws) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_transport, idxsim=idx + ), + ws, + ) diff --git a/autotest/test_gwt_moc3d03.py b/autotest/test_gwt_moc3d03.py index 866c2d9df1e..f236107ad1d 100644 --- a/autotest/test_gwt_moc3d03.py +++ b/autotest/test_gwt_moc3d03.py @@ -1,33 +1,12 @@ import os -import sys +import flopy import numpy as np import pytest - -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation ex = ["moc3d03"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) -ddir = "data" def build_model(idx, dir): @@ -258,7 +237,7 @@ def build_model(idx, dir): def eval_transport(sim): print("evaluating transport...") - name = ex[sim.idxsim] + name = sim.name gwtname = "gwt_" + name fpth = os.path.join(sim.simpath, f"{gwtname}.ucn") @@ -311,42 +290,19 @@ def eval_transport(sim): cres, csim.diagonal().ravel() ), "simulated concentrations do not match with known solution." - return - -# - No need to change any code below +@pytest.mark.slow @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "name", + ex, ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the models - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_transport, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # build the models - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_transport, idxsim=idx) - test.run_mf6(sim) - - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, 0, ws) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_transport, idxsim=0 + ), + ws, + ) diff --git a/autotest/test_gwt_mst01.py b/autotest/test_gwt_mst01.py index e732c1c1ce7..d1f53e59118 100644 --- a/autotest/test_gwt_mst01.py +++ b/autotest/test_gwt_mst01.py @@ -1,36 +1,15 @@ import os -import sys +import flopy import numpy as np import pytest - -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation ex = ["mst01"] laytyp = [1] ss = [0.0] sy = [0.1] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) -ddir = "data" nlay, nrow, ncol = 4, 1, 1 @@ -240,17 +219,12 @@ def build_model(idx, dir): def eval_transport(sim): print("evaluating transport...") - name = ex[sim.idxsim] + name = sim.name gwtname = "gwt_" + name fpth = os.path.join(sim.simpath, f"{gwtname}.ucn") - try: - cobj = flopy.utils.HeadFile( - fpth, precision="double", text="CONCENTRATION" - ) - conc1 = cobj.get_data(totim=3.0) - except: - assert False, f'could not load data from "{fpth}"' + cobj = flopy.utils.HeadFile(fpth, precision="double", text="CONCENTRATION") + conc1 = cobj.get_data(totim=3.0) # end of stress period 1 cres1 = np.ones((nlay, nrow, ncol), float) @@ -258,42 +232,18 @@ def eval_transport(sim): "simulated concentrations do not match " "with known solution." ) - return - -# - No need to change any code below @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "name", + ex, ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the models - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_transport, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # build the models - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_transport, idxsim=idx) - test.run_mf6(sim) - - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, 0, ws) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_transport, idxsim=0 + ), + ws, + ) diff --git a/autotest/test_gwt_mst02.py b/autotest/test_gwt_mst02.py index 410ec71309d..18a2a1e33b6 100644 --- a/autotest/test_gwt_mst02.py +++ b/autotest/test_gwt_mst02.py @@ -3,36 +3,15 @@ """ import os -import sys +import flopy import numpy as np import pytest - -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation ex = ["mst02a", "mst02b"] distcoef = [0.0, 1.0] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) -ddir = "data" nlay, nrow, ncol = 1, 1, 2 # time series answers for the two simulations @@ -277,42 +256,18 @@ def eval_transport(sim): except: assert False, f'could not load data from "{fpth}"' - return - -# - No need to change any code below @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the models - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_transport, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # build the models - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_transport, idxsim=idx) - test.run_mf6(sim) - - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(idx, name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, idx, ws) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_transport, idxsim=idx + ), + ws, + ) diff --git a/autotest/test_gwt_mst03.py b/autotest/test_gwt_mst03.py index 7a4874e966c..036cfa4f296 100644 --- a/autotest/test_gwt_mst03.py +++ b/autotest/test_gwt_mst03.py @@ -7,36 +7,16 @@ import os +import flopy import numpy as np import pytest - -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation ex = ["mst03"] laytyp = [1] ss = [1.0e-10] sy = [0.1] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) -ddir = "data" nlay, nrow, ncol = 1, 1, 1 @@ -238,25 +218,17 @@ def build_model(idx, dir): def eval_transport(sim): print("evaluating transport...") - name = ex[sim.idxsim] + name = sim.name gwtname = "gwt_" + name gwfname = "gwf_" + name fpth = os.path.join(sim.simpath, f"{gwfname}.hds") - try: - hobj = flopy.utils.HeadFile(fpth, precision="double") - head = hobj.get_alldata().flatten() - except: - assert False, f'could not load data from "{fpth}"' + hobj = flopy.utils.HeadFile(fpth, precision="double") + head = hobj.get_alldata().flatten() fpth = os.path.join(sim.simpath, f"{gwtname}.ucn") - try: - cobj = flopy.utils.HeadFile( - fpth, precision="double", text="CONCENTRATION" - ) - conc = cobj.get_alldata().flatten() - except: - assert False, f'could not load data from "{fpth}"' + cobj = flopy.utils.HeadFile(fpth, precision="double", text="CONCENTRATION") + conc = cobj.get_alldata().flatten() # calculations times = hobj.get_times() @@ -312,42 +284,18 @@ def eval_transport(sim): errmsg = f"{conc}\n{canswer}" assert np.allclose(conc, canswer, atol=1.0e-8), errmsg - return - -# - No need to change any code below @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "name", + ex, ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the models - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_transport, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # build the models - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_transport, idxsim=idx) - test.run_mf6(sim) - - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, 0, ws) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_transport, idxsim=0 + ), + ws, + ) diff --git a/autotest/test_gwt_mst04_noadv.py b/autotest/test_gwt_mst04_noadv.py index d4fc01c690e..14d711d7b16 100644 --- a/autotest/test_gwt_mst04_noadv.py +++ b/autotest/test_gwt_mst04_noadv.py @@ -6,35 +6,13 @@ """ import os +import flopy import numpy as np import pytest +from framework import TestFramework +from simulation import TestSimulation -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation - -ex = [ - "mst04_noadv", -] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) -ddir = "data" +ex = ["mst04_noadv"] def build_model(idx, dir): @@ -134,59 +112,30 @@ def build_model(idx, dir): def eval_transport(sim): print("evaluating transport...") - name = ex[sim.idxsim] + name = sim.name gwtname = "gwt_" + name fpth = os.path.join(sim.simpath, f"{gwtname}.ucn") - try: - cobj = flopy.utils.HeadFile( - fpth, precision="double", text="CONCENTRATION" - ) - conc = cobj.get_data() - except: - assert False, f'could not load data from "{fpth}"' + cobj = flopy.utils.HeadFile(fpth, precision="double", text="CONCENTRATION") + conc = cobj.get_data() # The answer 1 cres = np.array([10.0]) msg = f"simulated concentrations do not match with known solution. {conc} {cres}" assert np.allclose(cres, conc.flatten()), msg - return - -# - No need to change any code below @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "name", + ex, ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the models - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_transport, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # build the models - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_transport, idxsim=idx) - test.run_mf6(sim) - - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, 0, ws) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_transport, idxsim=0 + ), + ws, + ) diff --git a/autotest/test_gwt_mst05.py b/autotest/test_gwt_mst05.py index 4999f5a3c89..c305d3ff3f7 100644 --- a/autotest/test_gwt_mst05.py +++ b/autotest/test_gwt_mst05.py @@ -5,22 +5,14 @@ """ import os -import sys +import flopy import numpy as np import pytest - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from binary_file_writer import uniform_flow_field, write_budget, write_head -from framework import testing_framework -from simulation import Simulation +from flopy.utils.binaryfile import write_budget, write_head +from flopy.utils.gridutil import uniform_flow_field +from framework import TestFramework +from simulation import TestSimulation ex = ["mst05a", "mst05b"] isotherm = ["freundlich", "langmuir"] @@ -28,10 +20,6 @@ sp2 = [0.7, 0.003] xmax_plot = [1500, 500] ymax_plot = [0.5, 1.0] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) -ddir = "data" def build_model(idx, dir): @@ -319,42 +307,18 @@ def eval_transport(sim): fname = os.path.join(sim.simpath, "results.png") plt.savefig(fname) - return - -# - No need to change any code below @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the models - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_transport, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # build the models - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_transport, idxsim=idx) - test.run_mf6(sim) - - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(idx, name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, idx, ws) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_transport, idxsim=idx + ), + ws, + ) diff --git a/autotest/test_gwt_mst06_noadv.py b/autotest/test_gwt_mst06_noadv.py index 26710b34e9d..95dcc8882e2 100644 --- a/autotest/test_gwt_mst06_noadv.py +++ b/autotest/test_gwt_mst06_noadv.py @@ -7,35 +7,13 @@ """ import os +import flopy import numpy as np import pytest +from framework import TestFramework +from simulation import TestSimulation -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation - -ex = [ - "mst06_noadv", -] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) -ddir = "data" +ex = ["mst06_noadv"] def build_model(idx, dir): @@ -144,17 +122,12 @@ def build_model(idx, dir): def eval_transport(sim): print("evaluating transport...") - name = ex[sim.idxsim] + name = sim.name gwtname = "gwt_" + name fpth = os.path.join(sim.simpath, f"{gwtname}.ucn") - try: - cobj = flopy.utils.HeadFile( - fpth, precision="double", text="CONCENTRATION" - ) - conc = cobj.get_ts((0, 0, 0)) - except: - assert False, f'could not load data from "{fpth}"' + cobj = flopy.utils.HeadFile(fpth, precision="double", text="CONCENTRATION") + conc = cobj.get_ts((0, 0, 0)) # The answer # print(conc[:, 1]) @@ -191,42 +164,18 @@ def eval_transport(sim): ) assert np.allclose(decay_rate, decay_rate_answer), msg - return - -# - No need to change any code below @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "name", + ex, ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the models - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_transport, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # build the models - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_transport, idxsim=idx) - test.run_mf6(sim) - - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, 0, ws) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_transport, idxsim=0 + ), + ws, + ) diff --git a/autotest/test_gwt_mt3dms_p01.py b/autotest/test_gwt_mt3dms_p01.py index 43e8da72055..1a7d0998c9b 100644 --- a/autotest/test_gwt_mt3dms_p01.py +++ b/autotest/test_gwt_mt3dms_p01.py @@ -20,36 +20,11 @@ """ import os -import shutil -import sys +import flopy import numpy as np -import pytest - -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -import targets - -exe_name_mf = targets.target_dict["mf2005s"] -exe_name_mt = targets.target_dict["mt3dms"] -exe_name_mf6 = targets.target_dict["mf6"] -testdir = "./temp" + testgroup = "mt3dms_p01" -remove_files = True def p01mt3d( @@ -62,6 +37,8 @@ def p01mt3d( prsity2=None, rc2=None, zero_order_decay=False, + mf2005s="mf2005s", + mt3dms="mt3dms", ): nlay = 1 nrow = 1 @@ -85,7 +62,7 @@ def p01mt3d( modelname_mf = "p01_mf" mf = flopy.modflow.Modflow( - modelname=modelname_mf, model_ws=model_ws, exe_name=exe_name_mf + modelname=modelname_mf, model_ws=model_ws, exe_name=mf2005s ) dis = flopy.modflow.ModflowDis( mf, @@ -115,7 +92,7 @@ def p01mt3d( mt = flopy.mt3d.Mt3dms( modelname=modelname_mt, model_ws=model_ws, - exe_name=exe_name_mt, + exe_name=mt3dms, modflowmodel=mf, ) c0 = 1.0 @@ -209,6 +186,7 @@ def p01mf6( prsity2=None, onelambda=False, zero_order_decay=False, + exe="mf6", ): name = "p01" nlay, nrow, ncol = 1, 1, 101 @@ -242,9 +220,8 @@ def p01mf6( tdis_rc.append((perlen[i], nstp[i], tsmult[i])) ws = model_ws - exe_name = os.path.abspath(exe_name_mf6) sim = flopy.mf6.MFSimulation( - sim_name=name, version="mf6", exe_name=exe_name, sim_ws=ws + sim_name=name, version="mf6", exe_name=exe, sim_ws=ws ) from flopy.mf6.mfbase import VerbosityLevel @@ -481,8 +458,7 @@ def p01mf6( return sim, conc -def test_mt3dmsp01a(): - +def test_mt3dmsp01a(function_tmpdir, targets): longitudinal_dispersivity = 0.0 retardation = 1.0 decay_rate = 0.00 @@ -490,7 +466,8 @@ def test_mt3dmsp01a(): zeta = None prsity2 = None - mf6_ws = os.path.join(testdir, testgroup + "a") + mf6 = targets["mf6"] + mf6_ws = str(function_tmpdir / (testgroup + "a")) sim, conc_mf6 = p01mf6( mf6_ws, longitudinal_dispersivity, @@ -499,8 +476,11 @@ def test_mt3dmsp01a(): mixelm, zeta, prsity2, + exe=mf6, ) + mf2005 = targets["mf2005s"] + mt3dms = targets["mt3dms"] mt3d_ws = os.path.join(mf6_ws, "mt3d") mf, mt, conc_mt3d, cvt, mvt = p01mt3d( mt3d_ws, @@ -510,6 +490,8 @@ def test_mt3dmsp01a(): mixelm, zeta, prsity2, + mf2005s=mf2005, + mt3dms=mt3dms, ) msg = f"concentrations not equal {conc_mt3d} {conc_mf6}" @@ -540,13 +522,8 @@ def test_mt3dmsp01a(): bobj.file.close() assert np.allclose(0.0, storage_sorbed), f"{storage_sorbed}" - if remove_files: - shutil.rmtree(mf6_ws) - return - - -def test_mt3dmsp01b(): +def test_mt3dmsp01b(function_tmpdir, targets): longitudinal_dispersivity = 10.0 retardation = 1.0 decay_rate = 0.00 @@ -554,7 +531,8 @@ def test_mt3dmsp01b(): zeta = None prsity2 = None - mf6_ws = os.path.join(testdir, testgroup + "b") + mf6 = targets["mf6"] + mf6_ws = str(function_tmpdir / (testgroup + "b")) sim, conc_mf6 = p01mf6( mf6_ws, longitudinal_dispersivity, @@ -563,8 +541,11 @@ def test_mt3dmsp01b(): mixelm, zeta, prsity2, + exe=mf6, ) + mf2005 = targets["mf2005s"] + mt3dms = targets["mt3dms"] mt3d_ws = os.path.join(mf6_ws, "mt3d") mf, mt, conc_mt3d, cvt, mvt = p01mt3d( mt3d_ws, @@ -574,17 +555,15 @@ def test_mt3dmsp01b(): mixelm, zeta, prsity2, + mf2005s=mf2005, + mt3dms=mt3dms, ) msg = f"concentrations not equal {conc_mt3d} {conc_mf6}" assert np.allclose(conc_mt3d, conc_mf6, atol=1e-4), msg - if remove_files: - shutil.rmtree(mf6_ws) - return - -def test_mt3dmsp01c(): +def test_mt3dmsp01c(function_tmpdir, targets): longitudinal_dispersivity = 10.0 retardation = 1.5 decay_rate = 0.00 @@ -592,7 +571,8 @@ def test_mt3dmsp01c(): zeta = None prsity2 = None - mf6_ws = os.path.join(testdir, testgroup + "c") + mf6 = targets["mf6"] + mf6_ws = str(function_tmpdir / (testgroup + "c")) sim, conc_mf6 = p01mf6( mf6_ws, longitudinal_dispersivity, @@ -601,8 +581,11 @@ def test_mt3dmsp01c(): mixelm, zeta, prsity2, + exe=mf6, ) + mf2005 = targets["mf2005s"] + mt3dms = targets["mt3dms"] mt3d_ws = os.path.join(mf6_ws, "mt3d") mf, mt, conc_mt3d, cvt, mvt = p01mt3d( mt3d_ws, @@ -612,17 +595,15 @@ def test_mt3dmsp01c(): mixelm, zeta, prsity2, + mf2005s=mf2005, + mt3dms=mt3dms, ) msg = f"concentrations not equal {conc_mt3d} {conc_mf6}" assert np.allclose(conc_mt3d, conc_mf6, atol=1e-4), msg - if remove_files: - shutil.rmtree(mf6_ws) - return -def test_mt3dmsp01d(): - +def test_mt3dmsp01d(function_tmpdir, targets): longitudinal_dispersivity = 10.0 retardation = 1.5 decay_rate = 0.002 @@ -630,7 +611,8 @@ def test_mt3dmsp01d(): zeta = None prsity2 = None - mf6_ws = os.path.join(testdir, testgroup + "d") + mf6 = targets["mf6"] + mf6_ws = str(function_tmpdir / (testgroup + "d")) sim, conc_mf6 = p01mf6( mf6_ws, longitudinal_dispersivity, @@ -639,8 +621,11 @@ def test_mt3dmsp01d(): mixelm, zeta, prsity2, + exe=mf6, ) + mf2005 = targets["mf2005s"] + mt3dms = targets["mt3dms"] mt3d_ws = os.path.join(mf6_ws, "mt3d") mf, mt, conc_mt3d, cvt, mvt = p01mt3d( mt3d_ws, @@ -650,17 +635,15 @@ def test_mt3dmsp01d(): mixelm, zeta, prsity2, + mf2005s=mf2005, + mt3dms=mt3dms, ) msg = f"concentrations not equal {conc_mt3d} {conc_mf6}" assert np.allclose(conc_mt3d, conc_mf6, atol=1e-4), msg - if remove_files: - shutil.rmtree(mf6_ws) - return - -def test_mt3dmsp01e(): +def test_mt3dmsp01e(function_tmpdir, targets): longitudinal_dispersivity = 10.0 retardation = 1.5 decay_rate = 0.002 @@ -668,7 +651,8 @@ def test_mt3dmsp01e(): zeta = 0.1 prsity2 = 0.05 - mf6_ws = os.path.join(testdir, testgroup + "e") + mf6 = targets["mf6"] + mf6_ws = str(function_tmpdir / (testgroup + "e")) sim, conc_mf6 = p01mf6( mf6_ws, longitudinal_dispersivity, @@ -677,8 +661,11 @@ def test_mt3dmsp01e(): mixelm, zeta, prsity2, + exe=mf6, ) + mf2005 = targets["mf2005s"] + mt3dms = targets["mt3dms"] mt3d_ws = os.path.join(mf6_ws, "mt3d") mf, mt, conc_mt3d, cvt, mvt = p01mt3d( mt3d_ws, @@ -688,17 +675,15 @@ def test_mt3dmsp01e(): mixelm, zeta, prsity2, + mf2005s=mf2005, + mt3dms=mt3dms, ) msg = f"concentrations not equal {conc_mt3d} {conc_mf6}" assert np.allclose(conc_mt3d, conc_mf6, atol=1e-1), msg - if remove_files: - shutil.rmtree(mf6_ws) - return -def test_mt3dmsp01f(): - +def test_mt3dmsp01f(function_tmpdir, targets): longitudinal_dispersivity = 10.0 retardation = 1.5 decay_rate = 0.002 @@ -706,7 +691,8 @@ def test_mt3dmsp01f(): zeta = 0.1 prsity2 = 0.05 - mf6_ws = os.path.join(testdir, testgroup + "f") + mf6 = targets["mf6"] + mf6_ws = str(function_tmpdir / (testgroup + "f")) sim, conc_mf6 = p01mf6( mf6_ws, longitudinal_dispersivity, @@ -716,8 +702,11 @@ def test_mt3dmsp01f(): zeta, prsity2, onelambda=True, + exe=mf6, ) + mf2005 = targets["mf2005s"] + mt3dms = targets["mt3dms"] mt3d_ws = os.path.join(mf6_ws, "mt3d") mf, mt, conc_mt3d, cvt, mvt = p01mt3d( mt3d_ws, @@ -727,17 +716,15 @@ def test_mt3dmsp01f(): mixelm, zeta, prsity2, + mf2005s=mf2005, + mt3dms=mt3dms, ) msg = f"concentrations not equal {conc_mt3d} {conc_mf6}" assert np.allclose(conc_mt3d, conc_mf6, atol=1e-1), msg - if remove_files: - shutil.rmtree(mf6_ws) - return - -def test_mt3dmsp01g(): +def test_mt3dmsp01g(function_tmpdir, targets): longitudinal_dispersivity = 0.0 retardation = 1.0 decay_rate = -1.0 @@ -745,7 +732,8 @@ def test_mt3dmsp01g(): zeta = None prsity2 = None - mf6_ws = os.path.join(testdir, testgroup + "g") + mf6 = targets["mf6"] + mf6_ws = str(function_tmpdir / (testgroup + "g")) sim, conc_mf6 = p01mf6( mf6_ws, longitudinal_dispersivity, @@ -755,8 +743,11 @@ def test_mt3dmsp01g(): zeta, prsity2, zero_order_decay=True, + exe=mf6, ) + mf2005 = targets["mf2005s"] + mt3dms = targets["mt3dms"] mt3d_ws = os.path.join(mf6_ws, "mt3d") mf, mt, conc_mt3d, cvt, mvt = p01mt3d( mt3d_ws, @@ -768,22 +759,9 @@ def test_mt3dmsp01g(): prsity2, rc2=0.0, zero_order_decay=True, + mf2005s=mf2005, + mt3dms=mt3dms, ) msg = f"concentrations not equal {conc_mt3d} {conc_mf6}" assert np.allclose(conc_mt3d, conc_mf6, atol=1.0e-4), msg - if remove_files: - shutil.rmtree(mf6_ws) - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - test_mt3dmsp01a() - test_mt3dmsp01b() - test_mt3dmsp01c() - test_mt3dmsp01d() - test_mt3dmsp01e() - test_mt3dmsp01f() - test_mt3dmsp01g() diff --git a/autotest/test_gwt_mvt01.py b/autotest/test_gwt_mvt01.py index eb69289bf9e..1818b8f5724 100644 --- a/autotest/test_gwt_mvt01.py +++ b/autotest/test_gwt_mvt01.py @@ -5,26 +5,14 @@ # There is no flow between the stream and the aquifer. import os -import sys +import flopy import numpy as np import pytest - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation ex = ["mvt_01"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) def build_model(idx, dir): @@ -497,7 +485,7 @@ def eval_results(sim): print("evaluating results...") # ensure lake concentrations were saved - name = ex[sim.idxsim] + name = sim.name gwtname = "gwt_" + name fname = gwtname + ".sft.bin" fname = os.path.join(sim.simpath, fname) @@ -558,39 +546,18 @@ def eval_results(sim): # uncomment when testing so files aren't deleted # assert False - return - -# - No need to change any code below @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "name", + ex, ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the model - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_results, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_results, idxsim=idx) - test.run_mf6(sim) - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, 0, ws) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_results, idxsim=0 + ), + ws, + ) diff --git a/autotest/test_gwt_mvt02.py b/autotest/test_gwt_mvt02.py index 573f4a098c2..20dfa5e4762 100644 --- a/autotest/test_gwt_mvt02.py +++ b/autotest/test_gwt_mvt02.py @@ -5,26 +5,14 @@ # There is no flow between the stream and the aquifer. import os -import sys +import flopy import numpy as np import pytest - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation ex = ["mvt_02"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) def build_model(idx, dir): @@ -398,7 +386,7 @@ def build_model(idx, dir): def eval_results(sim): print("evaluating results...") - name = ex[sim.idxsim] + name = sim.name gwtname = "gwt_" + name # Load csv budget and make sure names are correct @@ -489,39 +477,18 @@ def eval_results(sim): # uncomment when testing so files aren't deleted # assert False - return - -# - No need to change any code below @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "name", + ex, ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the model - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_results, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_results, idxsim=idx) - test.run_mf6(sim) - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, 0, ws) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_results, idxsim=0 + ), + ws, + ) diff --git a/autotest/test_gwt_mvt02fmi.py b/autotest/test_gwt_mvt02fmi.py index 3147d066fa6..702b9a80697 100644 --- a/autotest/test_gwt_mvt02fmi.py +++ b/autotest/test_gwt_mvt02fmi.py @@ -5,35 +5,13 @@ # There is no flow between the stream and the aquifer. import os -import shutil +from os.path import join +import flopy import numpy as np -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -import targets -from framework import set_teardown_test - -exe_name_mf6 = targets.target_dict["mf6"] -exe_name_mf6 = os.path.abspath(exe_name_mf6) - -testdir = "./temp" testgroup = "mvt02fmi" -d = os.path.join(testdir, testgroup) -if os.path.isdir(d): - shutil.rmtree(d) - - ex = ["mvt02fmi"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) # parameters lx = 7.0 @@ -61,14 +39,11 @@ hclose, rclose, relax = 1e-8, 1e-6, 0.97 -def run_flow_model(): - +def run_flow_model(dir, exe): name = "flow" gwfname = name - wsf = os.path.join(testdir, testgroup, name) - sim = flopy.mf6.MFSimulation( - sim_name=name, sim_ws=wsf, exe_name=exe_name_mf6 - ) + wsf = join(dir, testgroup, name) + sim = flopy.mf6.MFSimulation(sim_name=name, sim_ws=wsf, exe_name=exe) # create tdis package tdis = flopy.mf6.ModflowTdis( @@ -262,18 +237,15 @@ def run_flow_model(): errmsg = f"flow model did not terminate successfully\n{buff}" assert success, errmsg - return - - -def run_transport_model(): +def run_transport_model(dir, exe): name = "transport" gwtname = name - wst = os.path.join(testdir, testgroup, name) + wst = join(dir, testgroup, name) sim = flopy.mf6.MFSimulation( sim_name=name, version="mf6", - exe_name=exe_name_mf6, + exe_name=exe, sim_ws=wst, continue_=False, memory_print_option=["ALL"], @@ -496,24 +468,8 @@ def run_transport_model(): # uncomment when testing so files aren't deleted # assert False - return - - -def test_mvt02fmi(): - run_flow_model() - run_transport_model() - d = os.path.join(testdir, testgroup) - - teardowntest = set_teardown_test() - if teardowntest: - if os.path.isdir(d): - shutil.rmtree(d) - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - # run tests - test_mvt02fmi() +def test_mvt02fmi(function_tmpdir, targets): + mf6 = targets.mf6 + run_flow_model(str(function_tmpdir), mf6) + run_transport_model(str(function_tmpdir), mf6) diff --git a/autotest/test_gwt_mwt01.py b/autotest/test_gwt_mwt01.py index 971aa819f63..bf70531e86f 100644 --- a/autotest/test_gwt_mwt01.py +++ b/autotest/test_gwt_mwt01.py @@ -4,26 +4,14 @@ # flows into the aquifer. import os -import sys +import flopy import numpy as np import pytest - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation ex = ["mwt_01"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) def build_model(idx, dir): @@ -320,8 +308,8 @@ def build_model(idx, dir): def check_obs(sim): print("checking obs...") - name = ex[sim.idxsim] - ws = exdirs[sim.idxsim] + name = sim.name + ws = sim.simpath sim = flopy.mf6.MFSimulation.load(sim_ws=ws) gwfname = "gwf_" + name gwtname = "gwt_" + name @@ -385,14 +373,13 @@ def check_obs(sim): ) assert success, "One or more MWT obs checks did not pass" - return def eval_results(sim): print("evaluating results...") # ensure mwt concentrations were saved - name = ex[sim.idxsim] + name = sim.name gwtname = "gwt_" + name fname = gwtname + ".mwt.bin" fname = os.path.join(sim.simpath, fname) @@ -405,39 +392,18 @@ def eval_results(sim): check_obs(sim) - return - -# - No need to change any code below @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the model - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_results, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_results, idxsim=idx) - test.run_mf6(sim) - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(idx, name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, idx, ws) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_results, idxsim=idx + ), + ws, + ) diff --git a/autotest/test_gwt_mwt02.py b/autotest/test_gwt_mwt02.py index f91f2a92c6f..37129a329a1 100644 --- a/autotest/test_gwt_mwt02.py +++ b/autotest/test_gwt_mwt02.py @@ -2,26 +2,14 @@ # information. import os -import sys +import flopy import numpy as np import pytest - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation ex = ["mwt_02"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) def build_model(idx, dir): @@ -406,8 +394,8 @@ def build_model(idx, dir): def make_plot(sim): print("making plots...") - name = ex[sim.idxsim] - ws = exdirs[sim.idxsim] + name = sim.name + ws = sim.simpath sim = flopy.mf6.MFSimulation.load(sim_ws=ws) gwfname = "gwf_" + name gwtname = "gwt_" + name @@ -489,39 +477,14 @@ def eval_results(sim): # uncomment when testing # assert False - return - - -# - No need to change any code below -@pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), -) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the model - test.build_mf6_models(build_model, idx, dir) - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_results, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_results, idxsim=idx) - test.run_mf6(sim) - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +@pytest.mark.slow +@pytest.mark.parametrize("name", ex) +def test_mf6model(name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, 0, ws) + test.run( + TestSimulation(name, exe_dict=targets, exfunc=eval_results, idxsim=0), + ws, + ) diff --git a/autotest/test_gwt_obs01.py b/autotest/test_gwt_obs01.py index 6dab2574d55..97416f1d721 100644 --- a/autotest/test_gwt_obs01.py +++ b/autotest/test_gwt_obs01.py @@ -6,28 +6,16 @@ import os +import flopy import numpy as np import pytest - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation ex = [ "gwt_obs01a", ] scheme = ["upstream"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) -ddir = "data" def build_model(idx, dir): @@ -257,25 +245,17 @@ def build_model(idx, dir): def eval_transport(sim): print("evaluating transport...") - name = ex[sim.idxsim] + name = sim.name gwtname = "gwt_" + name # MODFLOW 6 output control concentrations fpth = os.path.join(sim.simpath, f"{gwtname}.ucn") - try: - cobj = flopy.utils.HeadFile( - fpth, precision="double", text="CONCENTRATION" - ) - conc = cobj.get_alldata() - except: - assert False, f'could not load data from "{fpth}"' + cobj = flopy.utils.HeadFile(fpth, precision="double", text="CONCENTRATION") + conc = cobj.get_alldata() # MODFLOW 6 observation package concentrations fpth = os.path.join(sim.simpath, "conc_obs.csv") - try: - tc = np.genfromtxt(fpth, names=True, delimiter=",") - except: - assert False, f'could not load data from "{fpth}"' + tc = np.genfromtxt(fpth, names=True, delimiter=",") assert np.allclose( tc["1_1_10"], conc[:, 0, 0, 9] @@ -285,42 +265,18 @@ def eval_transport(sim): tc["1_1_50"], conc[:, 0, 0, 49] ), "obs concentrations do not match oc concentrations." - return - -# - No need to change any code below @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "name", + ex, ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the models - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_transport, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # build the models - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_transport, idxsim=idx) - test.run_mf6(sim) - - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, 0, ws) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_transport, idxsim=0 + ), + ws, + ) diff --git a/autotest/test_gwt_prudic2004t2.py b/autotest/test_gwt_prudic2004t2.py index 2d1f2d411ee..6c052338a59 100644 --- a/autotest/test_gwt_prudic2004t2.py +++ b/autotest/test_gwt_prudic2004t2.py @@ -7,27 +7,17 @@ import os import sys +import flopy import numpy as np import pytest - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from conftest import project_root_path +from framework import TestFramework +from simulation import TestSimulation ex = ["prudic2004t2"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) - -data_ws = "./data/prudic2004test2/" -fname = os.path.join(data_ws, "lakibd.dat") +data_path = project_root_path / "autotest" / "data" +model_path = data_path / "prudic2004test2" +fname = str(model_path / "lakibd.dat") lakibd = np.loadtxt(fname, dtype=int) @@ -84,10 +74,10 @@ def build_model(idx, dir): delr = 405.665 delc = 403.717 top = 100.0 - fname = os.path.join(data_ws, "bot1.dat") + fname = str(model_path / "bot1.dat") bot0 = np.loadtxt(fname) botm = [bot0] + [bot0 - (15.0 * k) for k in range(1, nlay)] - fname = os.path.join(data_ws, "idomain1.dat") + fname = str(model_path / "idomain1.dat") idomain0 = np.loadtxt(fname, dtype=int) idomain = nlay * [idomain0] dis = flopy.mf6.ModflowGwfdis( @@ -100,7 +90,6 @@ def build_model(idx, dir): top=top, botm=botm, idomain=idomain, - length_units="feet", ) idomain = dis.idomain.array @@ -146,7 +135,7 @@ def build_model(idx, dir): ) chdlist = [] - fname = os.path.join(data_ws, "chd.dat") + fname = str(model_path / "chd.dat") for line in open(fname, "r").readlines(): ll = line.strip().split() if len(ll) == 4: @@ -166,7 +155,7 @@ def build_model(idx, dir): ) rivlist = [] - fname = os.path.join(data_ws, "riv.dat") + fname = str(model_path / "riv.dat") for line in open(fname, "r").readlines(): ll = line.strip().split() if len(ll) == 7: @@ -189,7 +178,7 @@ def build_model(idx, dir): )[0] for i, t in enumerate(rivlist): rivra[i] = tuple(t) - sfrpd = np.genfromtxt(data_ws + "sfr-packagedata.dat", names=True) + sfrpd = np.genfromtxt(model_path / "sfr-packagedata.dat", names=True) sfrpackagedata = flopy.mf6.ModflowGwfsfr.packagedata.empty( gwf, boundnames=True, maxbound=sfrpd.shape[0] ) @@ -200,7 +189,7 @@ def build_model(idx, dir): if name in sfrpd.dtype.names: sfrpackagedata[name] = sfrpd[name] sfrpackagedata["boundname"] = rivra["boundname"] - with open(data_ws + "sfr-connectiondata.dat") as f: + with open(model_path / "sfr-connectiondata.dat") as f: lines = f.readlines() sfrconnectiondata = [] for line in lines: @@ -327,16 +316,13 @@ def build_model(idx, dir): [1, 35.2, nlakecon[1], "lake2"], ] # - outlets = [ - [0, 0, -1, "MANNING", 44.5, 3.36493214532915, 0.03, 0.2187500e-02] - ] + outlets = [[0, 0, -1, "MANNING", 44.5, 5.000000, 0.03, 0.2187500e-02]] lake_on = True if lake_on: lak = flopy.mf6.ModflowGwflak( gwf, time_conversion=86400.000, - length_conversion=3.28081, print_stage=True, print_flows=True, stage_filerecord=gwfname + ".lak.bin", @@ -599,8 +585,8 @@ def build_model(idx, dir): def make_concentration_vs_time(sim): print("making plot of concentration versus time...") - name = ex[sim.idxsim] - ws = exdirs[sim.idxsim] + name = sim.name + ws = sim.simpath sim = flopy.mf6.MFSimulation.load(sim_ws=ws) gwfname = "gwf_" + name gwtname = "gwt_" + name @@ -658,8 +644,8 @@ def make_concentration_map(sim): 500, ] - name = ex[sim.idxsim] - ws = exdirs[sim.idxsim] + name = sim.name + ws = sim.simpath simfp = flopy.mf6.MFSimulation.load(sim_ws=ws) gwfname = "gwf_" + name gwtname = "gwt_" + name @@ -697,8 +683,8 @@ def make_concentration_map(sim): def check_obs(sim): print("checking obs...") - name = ex[sim.idxsim] - ws = exdirs[sim.idxsim] + name = sim.name + ws = sim.simpath sim = flopy.mf6.MFSimulation.load(sim_ws=ws) gwfname = "gwf_" + name gwtname = "gwt_" + name @@ -843,8 +829,8 @@ def eval_results(sim): make_concentration_map(sim) # ensure concentrations were saved - ws = exdirs[sim.idxsim] - name = ex[sim.idxsim] + ws = sim.simpath + name = sim.name gwtname = "gwt_" + name check_obs(sim) @@ -984,39 +970,19 @@ def eval_results(sim): # uncomment when testing # assert False - return - -# - No need to change any code below +@pytest.mark.slow @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the model - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_results, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_results, idxsim=idx) - test.run_mf6(sim) - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(idx, name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, idx, ws) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_results, idxsim=idx + ), + ws, + ) diff --git a/autotest/test_gwt_prudic2004t2fmi.py b/autotest/test_gwt_prudic2004t2fmi.py index 00551a1d3a6..94378e064d0 100644 --- a/autotest/test_gwt_prudic2004t2fmi.py +++ b/autotest/test_gwt_prudic2004t2fmi.py @@ -1,40 +1,16 @@ # tests to ability to run flow model first followed by transport model import os -import shutil +from os.path import join +import flopy import numpy as np import pytest +from conftest import project_root_path -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - - -import targets -from framework import set_teardown_test - -exe_name_mf6 = targets.target_dict["mf6"] -exe_name_mf6 = os.path.abspath(exe_name_mf6) - -data_ws = os.path.abspath("./data/prudic2004test2/") -testdir = "./temp" +data_path = project_root_path / "autotest" / "data" +model_path = str(data_path / "prudic2004test2") testgroup = "prudic2004t2fmi" -d = os.path.join(testdir, testgroup) -if os.path.isdir(d): - shutil.rmtree(d) nlay = 8 nrow = 36 @@ -42,22 +18,20 @@ delr = 405.665 delc = 403.717 top = 100.0 -fname = os.path.join(data_ws, "bot1.dat") +fname = os.path.join(model_path, "bot1.dat") bot0 = np.loadtxt(fname) botm = [bot0] + [bot0 - (15.0 * k) for k in range(1, nlay)] -fname = os.path.join(data_ws, "idomain1.dat") +fname = os.path.join(model_path, "idomain1.dat") idomain0 = np.loadtxt(fname, dtype=int) idomain = nlay * [idomain0] -def run_flow_model(): +def run_flow_model(dir, exe): global idomain name = "flow" gwfname = name - wsf = os.path.join(testdir, testgroup, name) - sim = flopy.mf6.MFSimulation( - sim_name=name, sim_ws=wsf, exe_name=exe_name_mf6 - ) + wsf = join(dir, testgroup, name) + sim = flopy.mf6.MFSimulation(sim_name=name, sim_ws=wsf, exe_name=exe) tdis_rc = [(1.0, 1, 1.0), (365.25 * 25, 1, 1.0)] nper = len(tdis_rc) tdis = flopy.mf6.ModflowTdis( @@ -144,7 +118,7 @@ def run_flow_model(): ) chdlist = [] - fname = os.path.join(data_ws, "chd.dat") + fname = os.path.join(model_path, "chd.dat") for line in open(fname, "r").readlines(): ll = line.strip().split() if len(ll) == 4: @@ -164,7 +138,7 @@ def run_flow_model(): ) rivlist = [] - fname = os.path.join(data_ws, "riv.dat") + fname = os.path.join(model_path, "riv.dat") for line in open(fname, "r").readlines(): ll = line.strip().split() if len(ll) == 7: @@ -187,7 +161,7 @@ def run_flow_model(): )[0] for i, t in enumerate(rivlist): rivra[i] = tuple(t) - fname = os.path.join(data_ws, "sfr-packagedata.dat") + fname = os.path.join(model_path, "sfr-packagedata.dat") sfrpd = np.genfromtxt(fname, names=True) sfrpackagedata = flopy.mf6.ModflowGwfsfr.packagedata.empty( gwf, boundnames=True, maxbound=sfrpd.shape[0] @@ -199,7 +173,7 @@ def run_flow_model(): if name in sfrpd.dtype.names: sfrpackagedata[name] = sfrpd[name] sfrpackagedata["boundname"] = rivra["boundname"] - fname = os.path.join(data_ws, "sfr-connectiondata.dat") + fname = os.path.join(model_path, "sfr-connectiondata.dat") with open(fname) as f: lines = f.readlines() sfrconnectiondata = [] @@ -243,7 +217,7 @@ def run_flow_model(): observations=sfr_obs, ) - fname = os.path.join(data_ws, "lakibd.dat") + fname = os.path.join(model_path, "lakibd.dat") lakibd = np.loadtxt(fname, dtype=int) lakeconnectiondata = [] nlakecon = [0, 0] @@ -354,16 +328,13 @@ def run_flow_model(): [1, 35.2, nlakecon[1], "lake2"], ] # - outlets = [ - [0, 0, -1, "MANNING", 44.5, 3.36493214532915, 0.03, 0.2187500e-02] - ] + outlets = [[0, 0, -1, "MANNING", 44.5, 5.000000, 0.03, 0.2187500e-02]] lake_on = True if lake_on: lak = flopy.mf6.ModflowGwflak( gwf, time_conversion=86400.000, - length_conversion=3.28081, print_stage=True, print_flows=True, stage_filerecord=gwfname + ".lak.bin", @@ -458,17 +429,15 @@ def run_flow_model(): for node, node2, q in d: print(p1, node, p2, node2, q) - return - -def run_transport_model(): +def run_transport_model(dir, exe): name = "transport" gwtname = name - wst = os.path.join(testdir, testgroup, name) + wst = join(dir, testgroup, name) sim = flopy.mf6.MFSimulation( sim_name=name, version="mf6", - exe_name=exe_name_mf6, + exe_name=exe, sim_ws=wst, continue_=False, ) @@ -515,7 +484,6 @@ def run_transport_model(): top=top, botm=botm, idomain=idomain, - length_units="feet", ) ic = flopy.mf6.ModflowGwtic(gwt, strt=0.0) sto = flopy.mf6.ModflowGwtmst(gwt, porosity=0.3) @@ -811,23 +779,10 @@ def run_transport_model(): for rate1, rate2 in zip(csvra[name1], lstra[name2]): print(rate1, rate2) assert success_all, f"Comparisons failed for {failed_list}" - return - - -def test_prudic2004t2fmi(): - run_flow_model() - run_transport_model() - d = os.path.join(testdir, testgroup) - teardowntest = set_teardown_test() - if teardowntest: - if os.path.isdir(d): - shutil.rmtree(d) - return - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - # run tests - test_prudic2004t2fmi() +@pytest.mark.slow +def test_prudic2004t2fmi(function_tmpdir, targets): + mf6 = targets.mf6 + run_flow_model(str(function_tmpdir), mf6) + run_transport_model(str(function_tmpdir), mf6) diff --git a/autotest/test_gwt_prudic2004t2fmiats.py b/autotest/test_gwt_prudic2004t2fmiats.py index 68b1651eec9..446aec445e8 100644 --- a/autotest/test_gwt_prudic2004t2fmiats.py +++ b/autotest/test_gwt_prudic2004t2fmiats.py @@ -5,40 +5,16 @@ # failure occurs. import os -import shutil +from os.path import join +import flopy import numpy as np import pytest +from conftest import project_root_path -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - - -import targets -from framework import set_teardown_test - -exe_name_mf6 = targets.target_dict["mf6"] -exe_name_mf6 = os.path.abspath(exe_name_mf6) - -data_ws = os.path.abspath("./data/prudic2004test2/") -testdir = "./temp" +data_path = project_root_path / "autotest" / "data" +model_path = str(data_path / "prudic2004test2") testgroup = "prudic2004t2fmiats" -d = os.path.join(testdir, testgroup) -if os.path.isdir(d): - shutil.rmtree(d) nlay = 8 nrow = 36 @@ -46,22 +22,20 @@ delr = 405.665 delc = 403.717 top = 100.0 -fname = os.path.join(data_ws, "bot1.dat") +fname = os.path.join(model_path, "bot1.dat") bot0 = np.loadtxt(fname) botm = [bot0] + [bot0 - (15.0 * k) for k in range(1, nlay)] -fname = os.path.join(data_ws, "idomain1.dat") +fname = os.path.join(model_path, "idomain1.dat") idomain0 = np.loadtxt(fname, dtype=int) idomain = nlay * [idomain0] -def run_flow_model(): +def run_flow_model(dir, exe): global idomain name = "flow" gwfname = name - wsf = os.path.join(testdir, testgroup, name) - sim = flopy.mf6.MFSimulation( - sim_name=name, sim_ws=wsf, exe_name=exe_name_mf6 - ) + wsf = join(dir, testgroup, name) + sim = flopy.mf6.MFSimulation(sim_name=name, sim_ws=wsf, exe_name=exe) tdis_rc = [(1.0, 1, 1.0), (365.25 * 25, 1, 1.0)] nper = len(tdis_rc) tdis = flopy.mf6.ModflowTdis( @@ -102,7 +76,6 @@ def run_flow_model(): top=top, botm=botm, idomain=idomain, - length_units="feet", ) idomain = dis.idomain.array @@ -149,7 +122,7 @@ def run_flow_model(): ) chdlist = [] - fname = os.path.join(data_ws, "chd.dat") + fname = os.path.join(model_path, "chd.dat") for line in open(fname, "r").readlines(): ll = line.strip().split() if len(ll) == 4: @@ -169,7 +142,7 @@ def run_flow_model(): ) rivlist = [] - fname = os.path.join(data_ws, "riv.dat") + fname = os.path.join(model_path, "riv.dat") for line in open(fname, "r").readlines(): ll = line.strip().split() if len(ll) == 7: @@ -192,7 +165,7 @@ def run_flow_model(): )[0] for i, t in enumerate(rivlist): rivra[i] = tuple(t) - fname = os.path.join(data_ws, "sfr-packagedata.dat") + fname = os.path.join(model_path, "sfr-packagedata.dat") sfrpd = np.genfromtxt(fname, names=True) sfrpackagedata = flopy.mf6.ModflowGwfsfr.packagedata.empty( gwf, boundnames=True, maxbound=sfrpd.shape[0] @@ -204,7 +177,7 @@ def run_flow_model(): if name in sfrpd.dtype.names: sfrpackagedata[name] = sfrpd[name] sfrpackagedata["boundname"] = rivra["boundname"] - fname = os.path.join(data_ws, "sfr-connectiondata.dat") + fname = os.path.join(model_path, "sfr-connectiondata.dat") with open(fname) as f: lines = f.readlines() sfrconnectiondata = [] @@ -248,7 +221,7 @@ def run_flow_model(): observations=sfr_obs, ) - fname = os.path.join(data_ws, "lakibd.dat") + fname = os.path.join(model_path, "lakibd.dat") lakibd = np.loadtxt(fname, dtype=int) lakeconnectiondata = [] nlakecon = [0, 0] @@ -359,16 +332,13 @@ def run_flow_model(): [1, 35.2, nlakecon[1], "lake2"], ] # - outlets = [ - [0, 0, -1, "MANNING", 44.5, 3.36493214532915, 0.03, 0.2187500e-02] - ] + outlets = [[0, 0, -1, "MANNING", 44.5, 5.000000, 0.03, 0.2187500e-02]] lake_on = True if lake_on: lak = flopy.mf6.ModflowGwflak( gwf, time_conversion=86400.000, - length_conversion=3.28081, print_stage=True, print_flows=True, stage_filerecord=gwfname + ".lak.bin", @@ -454,17 +424,15 @@ def run_flow_model(): for node, node2, q in d: print(p1, node, p2, node2, q) - return - -def run_transport_model(): +def run_transport_model(dir, exe): name = "transport" gwtname = name - wst = os.path.join(testdir, testgroup, name) + wst = join(dir, testgroup, name) sim = flopy.mf6.MFSimulation( sim_name=name, version="mf6", - exe_name=exe_name_mf6, + exe_name=exe, sim_ws=wst, continue_=False, ) @@ -879,23 +847,9 @@ def run_transport_model(): all_found ), "One or more required text strings not found in mfsim.lst" - return - - -def test_prudic2004t2fmiats(): - run_flow_model() - run_transport_model() - d = os.path.join(testdir, testgroup) - teardowntest = set_teardown_test() - if teardowntest: - if os.path.isdir(d): - shutil.rmtree(d) - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - # run tests - test_prudic2004t2fmiats() +@pytest.mark.slow +def test_prudic2004t2fmiats(function_tmpdir, targets): + mf6 = targets.mf6 + run_flow_model(dir=str(function_tmpdir), exe=mf6) + run_transport_model(dir=str(function_tmpdir), exe=mf6) diff --git a/autotest/test_gwt_prudic2004t2gwtgwt.py b/autotest/test_gwt_prudic2004t2gwtgwt.py index 022a9ddc17d..71577f18af4 100644 --- a/autotest/test_gwt_prudic2004t2gwtgwt.py +++ b/autotest/test_gwt_prudic2004t2gwtgwt.py @@ -7,26 +7,16 @@ import os import sys +import flopy import numpy as np import pytest +from conftest import project_root_path +from framework import TestFramework +from simulation import TestSimulation -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation - -data_ws = "./data/prudic2004test2gwtgwt/" +data_path = project_root_path / "autotest" / "data" +model_path = str(data_path / "prudic2004test2gwtgwt") ex = ["prudic2004t2gwtgwt"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) - gwfnames = ["flow1", "flow2"] gwtnames = ["transport1", "transport2"] @@ -37,7 +27,7 @@ delr = 405.665 delc = 403.717 top = 100.0 -fname = os.path.join(data_ws, "bot1.dat") +fname = os.path.join(model_path, "bot1.dat") bot0 = np.loadtxt(fname) botm = [bot0] + [bot0 - (15.0 * k) for k in range(1, nlay)] @@ -57,12 +47,12 @@ across_model_mvt_on = True and across_model_mvr_on # setup idomain -fname = os.path.join(data_ws, "idomain1.dat") +fname = os.path.join(model_path, "idomain1.dat") idomain0 = np.loadtxt(fname, dtype=int) idomain = nlay * [idomain0] idomain = np.array(idomain) -fname = os.path.join(data_ws, "lakibd.dat") +fname = os.path.join(model_path, "lakibd.dat") lakibd = np.loadtxt(fname, dtype=int) @@ -353,7 +343,7 @@ def build_gwfgwt_combo( else: fname = "chd_south.dat" chdlist = [] - fname = os.path.join(data_ws, fname) + fname = os.path.join(model_path, fname) print(f"Setting CHD information from: {fname}") for line in open(fname, "r").readlines(): ll = line.strip().split() @@ -383,10 +373,10 @@ def build_gwfgwt_combo( [0, "inflow", 8640.0], ] } - fname = os.path.join(data_ws, f"sfr-packdata-{isfrseg}.dat") + fname = os.path.join(model_path, f"sfr-packdata-{isfrseg}.dat") sfrpd = sfr_packagedata_to_list(fname, gwf) nreaches = len(sfrpd) - fname = os.path.join(data_ws, f"sfr-conndata-{isfrseg}.dat") + fname = os.path.join(model_path, f"sfr-conndata-{isfrseg}.dat") sfrcd = sfr_connectiondata_to_list(fname) print(f"Setting nreaches to {nreaches}") sfr = flopy.mf6.ModflowGwfsfr( @@ -504,9 +494,7 @@ def build_gwfgwt_combo( if icombo == 1: lakpackagedata = [[0, 44.0, nlakecon[0], "lake1"]] # - outlets = [ - [0, 0, -1, "MANNING", 44.5, 3.36493214532915, 0.03, 0.2187500e-02] - ] + outlets = [[0, 0, -1, "MANNING", 44.5, 5.000000, 0.03, 0.2187500e-02]] noutlets = 1 elif icombo == 2: lakpackagedata = [[0, 35.2, nlakecon[0], "lake2"]] @@ -521,7 +509,6 @@ def build_gwfgwt_combo( lak = flopy.mf6.ModflowGwflak( gwf, time_conversion=86400.000, - length_conversion=3.28081, print_stage=True, print_flows=True, stage_filerecord=gwfname + ".lak.bin", @@ -892,18 +879,16 @@ def make_concentration_map(sim, ws): print(f"Creating {fname}") plt.savefig(fname) - return - def eval_results(sim): print("evaluating results...") # these answer files are results from autotest/prudic2004test2 - fname = os.path.join(data_ws, "result_conc_lak1.txt") + fname = os.path.join(model_path, "result_conc_lak1.txt") ans_lak1 = np.loadtxt(fname) - fname = os.path.join(data_ws, "result_conc_sfr3.txt") + fname = os.path.join(model_path, "result_conc_sfr3.txt") ans_sfr3 = np.loadtxt(fname) - fname = os.path.join(data_ws, "result_conc_sfr4.txt") + fname = os.path.join(model_path, "result_conc_sfr4.txt") ans_sfr4 = np.loadtxt(fname) makeplot = False @@ -911,7 +896,7 @@ def eval_results(sim): if arg.lower() == "--makeplot": makeplot = True - ws = exdirs[sim.idxsim] + ws = sim.simpath simfp = flopy.mf6.MFSimulation.load(sim_ws=ws, strict=False) if makeplot: @@ -921,7 +906,7 @@ def eval_results(sim): make_concentration_map(simfp, ws) # ensure concentrations were saved - ws = exdirs[sim.idxsim] + ws = sim.simpath gwfname = gwfnames[0] gwtname = gwtnames[0] @@ -975,39 +960,19 @@ def eval_results(sim): # uncomment when testing # assert False - return - -# - No need to change any code below +@pytest.mark.slow @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the model - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_results, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_results, idxsim=idx) - test.run_mf6(sim) - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(idx, name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, idx, ws) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_results, idxsim=idx + ), + ws, + ) diff --git a/autotest/test_gwt_sft01.py b/autotest/test_gwt_sft01.py index 81e75c0bd65..69228d1abcc 100644 --- a/autotest/test_gwt_sft01.py +++ b/autotest/test_gwt_sft01.py @@ -6,26 +6,14 @@ # There is no flow between the stream and the aquifer. import os -import sys +import flopy import numpy as np import pytest - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation ex = ["sft_01"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) def build_model(idx, dir): @@ -381,7 +369,7 @@ def eval_results(sim): print("evaluating results...") # ensure lake concentrations were saved - name = ex[sim.idxsim] + name = sim.name gwtname = "gwt_" + name fname = gwtname + ".sft.bin" fname = os.path.join(sim.simpath, fname) @@ -436,39 +424,18 @@ def eval_results(sim): # uncomment when testing # assert False - return - -# - No need to change any code below @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "name", + ex, ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the model - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_results, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_results, idxsim=idx) - test.run_mf6(sim) - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, 0, ws) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_results, idxsim=0 + ), + ws, + ) diff --git a/autotest/test_gwt_sft01gwtgwt.py b/autotest/test_gwt_sft01gwtgwt.py index 005374a5934..ba63d36a3a2 100644 --- a/autotest/test_gwt_sft01gwtgwt.py +++ b/autotest/test_gwt_sft01gwtgwt.py @@ -16,27 +16,13 @@ # gwt 1 2 3 4 5 6 7 gwtgwt => 1 2 3 4 5 6 7 -import os -import sys - +import flopy import numpy as np import pytest - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation ex = ["sft01gwtgwt"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) # properties for each model combination lx = 7.0 @@ -499,7 +485,7 @@ def eval_results(sim): print("evaluating results...") # load the simulations - ws = exdirs[sim.idxsim] + ws = sim.simpath sim = flopy.mf6.MFSimulation.load(sim_ws=ws) # construct head and conc for combined models @@ -526,39 +512,18 @@ def eval_results(sim): conc, sfrconc ), "aquifer concentration does not equal sfr concentration" - return - -# - No need to change any code below @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the model - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_results, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_results, idxsim=idx) - test.run_mf6(sim) - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(idx, name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, idx, ws) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_results, idxsim=idx + ), + ws, + ) diff --git a/autotest/test_gwt_src01.py b/autotest/test_gwt_src01.py index 4121f4fb4dc..1535cdbd909 100644 --- a/autotest/test_gwt_src01.py +++ b/autotest/test_gwt_src01.py @@ -7,36 +7,15 @@ """ import os -import sys +import flopy import numpy as np import pytest - -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation ex = ["src01a"] xt3d = [False] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) -ddir = "data" def build_model(idx, dir): @@ -262,17 +241,12 @@ def build_model(idx, dir): def eval_transport(sim): print("evaluating transport...") - name = ex[sim.idxsim] + name = sim.name gwtname = "gwt_" + name fpth = os.path.join(sim.simpath, f"{gwtname}.ucn") - try: - cobj = flopy.utils.HeadFile( - fpth, precision="double", text="CONCENTRATION" - ) - conc = cobj.get_data() - except: - assert False, f'could not load data from "{fpth}"' + cobj = flopy.utils.HeadFile(fpth, precision="double", text="CONCENTRATION") + conc = cobj.get_data() # This is the answer to this problem. These concentrations are for # steady state and calculated from F = D * (c1 - c2) / L @@ -281,42 +255,18 @@ def eval_transport(sim): cres, conc ), "simulated concentrations do not match with known solution." - return - -# - No need to change any code below @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "name", + ex, ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the models - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_transport, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # build the models - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_transport, idxsim=idx) - test.run_mf6(sim) - - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, 0, ws) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_transport, idxsim=0 + ), + ws, + ) diff --git a/autotest/test_gwt_ssm01fmi.py b/autotest/test_gwt_ssm01fmi.py index 7c3c83aa145..f25cb062679 100644 --- a/autotest/test_gwt_ssm01fmi.py +++ b/autotest/test_gwt_ssm01fmi.py @@ -4,38 +4,12 @@ # be 100. import os -import shutil +from os.path import join +import flopy import numpy as np -import pytest -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - - -import targets - -exe_name_mf6 = targets.target_dict["mf6"] -exe_name_mf6 = os.path.abspath(exe_name_mf6) - -testdir = "./temp" testgroup = "ssm01" -d = os.path.join(testdir, testgroup) -if os.path.isdir(d): - shutil.rmtree(d) nlay = 1 nrow = 10 @@ -46,14 +20,12 @@ botm = 0.0 -def run_flow_model(): +def run_flow_model(dir, exe): global idomain name = "flow" gwfname = name - wsf = os.path.join(testdir, testgroup, name) - sim = flopy.mf6.MFSimulation( - sim_name=name, sim_ws=wsf, exe_name=exe_name_mf6 - ) + wsf = join(dir, testgroup, name) + sim = flopy.mf6.MFSimulation(sim_name=name, sim_ws=wsf, exe_name=exe) tdis_rc = [(100.0, 1, 1.0), (100.0, 1, 1.0)] nper = len(tdis_rc) tdis = flopy.mf6.ModflowTdis( @@ -194,17 +166,15 @@ def run_flow_model(): errmsg = f"flow model did not terminate successfully\n{buff}" assert success, errmsg - return - -def run_transport_model(): +def run_transport_model(dir, exe): name = "transport" gwtname = name - wst = os.path.join(testdir, testgroup, name) + wst = join(dir, testgroup, name) sim = flopy.mf6.MFSimulation( sim_name=name, version="mf6", - exe_name=exe_name_mf6, + exe_name=exe, sim_ws=wst, continue_=False, ) @@ -338,21 +308,8 @@ def run_transport_model(): ) assert np.all(simulated_concentration == 100.0), errmsg - return - - -def test_ssm01fmi(): - run_flow_model() - run_transport_model() - d = os.path.join(testdir, testgroup) - if os.path.isdir(d): - shutil.rmtree(d) - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - # run tests - test_ssm01fmi() +def test_ssm01fmi(function_tmpdir, targets): + mf6 = targets.mf6 + run_flow_model(str(function_tmpdir), mf6) + run_transport_model(str(function_tmpdir), mf6) diff --git a/autotest/test_gwt_ssm02.py b/autotest/test_gwt_ssm02.py index b746e5a07b7..251bd3dc49c 100644 --- a/autotest/test_gwt_ssm02.py +++ b/autotest/test_gwt_ssm02.py @@ -9,35 +9,16 @@ import os +import flopy import numpy as np import pytest - -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation ex = ["ssm02"] laytyp = [1] ss = [1.0e-10] sy = [0.1] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) nlay, nrow, ncol = 1, 1, 1 @@ -239,25 +220,17 @@ def build_model(idx, dir): def eval_transport(sim): print("evaluating transport...") - name = ex[sim.idxsim] + name = sim.name gwtname = "gwt_" + name gwfname = "gwf_" + name fpth = os.path.join(sim.simpath, f"{gwfname}.hds") - try: - hobj = flopy.utils.HeadFile(fpth, precision="double") - head = hobj.get_alldata().flatten() - except: - assert False, f'could not load data from "{fpth}"' + hobj = flopy.utils.HeadFile(fpth, precision="double") + head = hobj.get_alldata().flatten() fpth = os.path.join(sim.simpath, f"{gwtname}.ucn") - try: - cobj = flopy.utils.HeadFile( - fpth, precision="double", text="CONCENTRATION" - ) - conc = cobj.get_alldata().flatten() - except: - assert False, f'could not load data from "{fpth}"' + cobj = flopy.utils.HeadFile(fpth, precision="double", text="CONCENTRATION") + conc = cobj.get_alldata().flatten() # calculations times = hobj.get_times() @@ -278,42 +251,18 @@ def eval_transport(sim): assert np.allclose(c, conc_calc, atol=0.001), msg vold = v - return - -# - No need to change any code below @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "name", + ex, ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the models - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_transport, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # build the models - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_transport, idxsim=idx) - test.run_mf6(sim) - - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, 0, ws) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_transport, idxsim=0 + ), + ws, + ) diff --git a/autotest/test_gwt_ssm03.py b/autotest/test_gwt_ssm03.py index d94d3f91ab6..02f8c05fad9 100644 --- a/autotest/test_gwt_ssm03.py +++ b/autotest/test_gwt_ssm03.py @@ -7,24 +7,13 @@ import os +import flopy import numpy as np import pytest - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation ex = ["ssm03"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) def build_model(idx, dir): @@ -243,28 +232,20 @@ def build_model(idx, dir): def eval_transport(sim): print("evaluating transport...") - name = ex[sim.idxsim] + name = sim.name gwtname = "gwt_" + name # load concentration file fpth = os.path.join(sim.simpath, f"{gwtname}.ucn") - try: - cobj = flopy.utils.HeadFile( - fpth, precision="double", text="CONCENTRATION" - ) - conc = cobj.get_data() - except: - assert False, f'could not load data from "{fpth}"' + cobj = flopy.utils.HeadFile(fpth, precision="double", text="CONCENTRATION") + conc = cobj.get_data() # load transport budget file fpth = os.path.join(sim.simpath, f"{gwtname}.cbc") - try: - bobj = flopy.utils.CellBudgetFile( - fpth, - precision="double", - ) - except: - assert False, f'could not load data from "{fpth}"' + bobj = flopy.utils.CellBudgetFile( + fpth, + precision="double", + ) ssmbudall = bobj.get_data(text="SOURCE-SINK MIX") for ssmbud in ssmbudall: @@ -279,42 +260,18 @@ def eval_transport(sim): assert node2 == 1, "node2 location for chd must be 1 (first chd)" assert q < 0.0, "mass flux for chd must be less than zero" - return - -# - No need to change any code below @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "name", + ex, ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the models - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_transport, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # build the models - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_transport, idxsim=idx) - test.run_mf6(sim) - - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, 0, ws) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_transport, idxsim=0 + ), + ws, + ) diff --git a/autotest/test_gwt_ssm04.py b/autotest/test_gwt_ssm04.py index 84485de916e..af29c01539c 100644 --- a/autotest/test_gwt_ssm04.py +++ b/autotest/test_gwt_ssm04.py @@ -13,24 +13,13 @@ import os +import flopy import numpy as np import pytest - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation ex = ["ssm04"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) nlay, nrow, ncol = 3, 5, 5 idomain_lay0 = [ @@ -413,28 +402,20 @@ def build_model(idx, dir): def eval_transport(sim): print("evaluating transport...") - name = ex[sim.idxsim] + name = sim.name gwtname = "gwt_" + name # load concentration file fpth = os.path.join(sim.simpath, f"{gwtname}.ucn") - try: - cobj = flopy.utils.HeadFile( - fpth, precision="double", text="CONCENTRATION" - ) - conc = cobj.get_data() - except: - assert False, f'could not load data from "{fpth}"' + cobj = flopy.utils.HeadFile(fpth, precision="double", text="CONCENTRATION") + conc = cobj.get_data() # load transport budget file fpth = os.path.join(sim.simpath, f"{gwtname}.cbc") - try: - bobj = flopy.utils.CellBudgetFile( - fpth, - precision="double", - ) - except: - assert False, f'could not load data from "{fpth}"' + bobj = flopy.utils.CellBudgetFile( + fpth, + precision="double", + ) ssmbudall = bobj.get_data(text="SOURCE-SINK MIX") times = cobj.get_times() @@ -500,42 +481,18 @@ def eval_transport(sim): istart = istop - return - -# - No need to change any code below @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "name", + ex, ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the models - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_transport, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # build the models - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_transport, idxsim=idx) - test.run_mf6(sim) - - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, 0, ws) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_transport, idxsim=0 + ), + ws, + ) diff --git a/autotest/test_gwt_ssm05.py b/autotest/test_gwt_ssm05.py index e6a84fd536d..0945813cbd9 100644 --- a/autotest/test_gwt_ssm05.py +++ b/autotest/test_gwt_ssm05.py @@ -7,24 +7,13 @@ import os +import flopy import numpy as np import pytest - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation ex = ["ssm05"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) nlay, nrow, ncol = 3, 5, 5 idomain_lay0 = [ @@ -252,28 +241,20 @@ def build_model(idx, dir): def eval_transport(sim): print("evaluating transport...") - name = ex[sim.idxsim] + name = sim.name gwtname = "gwt_" + name # load concentration file fpth = os.path.join(sim.simpath, f"{gwtname}.ucn") - try: - cobj = flopy.utils.HeadFile( - fpth, precision="double", text="CONCENTRATION" - ) - conc = cobj.get_data() - except: - assert False, f'could not load data from "{fpth}"' + cobj = flopy.utils.HeadFile(fpth, precision="double", text="CONCENTRATION") + conc = cobj.get_data() # load transport budget file fpth = os.path.join(sim.simpath, f"{gwtname}.cbc") - try: - bobj = flopy.utils.CellBudgetFile( - fpth, - precision="double", - ) - except: - assert False, f'could not load data from "{fpth}"' + bobj = flopy.utils.CellBudgetFile( + fpth, + precision="double", + ) ssmbudall = bobj.get_data(text="SOURCE-SINK MIX") times = cobj.get_times() @@ -332,42 +313,18 @@ def eval_transport(sim): istart = istop - return - -# - No need to change any code below @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "name", + ex, ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the models - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_transport, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # build the models - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_transport, idxsim=idx) - test.run_mf6(sim) - - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, 0, ws) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_transport, idxsim=0 + ), + ws, + ) diff --git a/autotest/test_gwt_ssm06.py b/autotest/test_gwt_ssm06.py index 4755fa610f9..1371aec43e2 100644 --- a/autotest/test_gwt_ssm06.py +++ b/autotest/test_gwt_ssm06.py @@ -2,38 +2,11 @@ # See test_gwt_ssm06fmi.py for additional detail on what this test is about. import os -import shutil +import flopy import numpy as np -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - - -import targets - -exe_name_mf6 = targets.target_dict["mf6"] -exe_name_mf6 = os.path.abspath(exe_name_mf6) - -testdir = "./temp" testgroup = "ssm06" -d = os.path.join(testdir, testgroup) -if os.path.isdir(d): - shutil.rmtree(d) - nlay = 1 nrow = 10 @@ -66,13 +39,11 @@ ndv = 0 -def run_flw_and_trnprt_models(): +def run_flw_and_trnprt_models(dir, exe): global idomain gwfname = "gwf-" + testgroup - ws = os.path.join(testdir, testgroup) - sim = flopy.mf6.MFSimulation( - sim_name=testgroup, sim_ws=ws, exe_name=exe_name_mf6 - ) + ws = dir + sim = flopy.mf6.MFSimulation(sim_name=testgroup, sim_ws=ws, exe_name=exe) tdis_rc = [(100.0, 10, 1.0), (100.0, 10, 1.0)] nper = len(tdis_rc) tdis = flopy.mf6.ModflowTdis( @@ -374,20 +345,7 @@ def run_flw_and_trnprt_models(): d0 = np.genfromtxt(fname, names=True, delimiter=",", deletechars="") print(d0.dtype.names) - return - - -def test_ssm06(): - run_flw_and_trnprt_models() - d = os.path.join(testdir, testgroup) - if os.path.isdir(d): - shutil.rmtree(d) - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - # run tests - test_ssm06() +def test_ssm06(function_tmpdir, targets): + mf6 = targets.mf6 + run_flw_and_trnprt_models(str(function_tmpdir), mf6) diff --git a/autotest/test_gwt_ssm06fmi.py b/autotest/test_gwt_ssm06fmi.py index 47db31182a9..372b1efd1d4 100644 --- a/autotest/test_gwt_ssm06fmi.py +++ b/autotest/test_gwt_ssm06fmi.py @@ -8,38 +8,11 @@ # separately never threw the error. import os -import shutil +import flopy import numpy as np -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - - -import targets - -exe_name_mf6 = targets.target_dict["mf6"] -exe_name_mf6 = os.path.abspath(exe_name_mf6) - -testdir = "./temp" testgroup = "ssm06fmi" -d = os.path.join(testdir, testgroup) -if os.path.isdir(d): - shutil.rmtree(d) - nlay = 1 nrow = 10 @@ -72,14 +45,12 @@ ndv = 0 -def run_flow_model(): +def run_flow_model(dir, exe): global idomain name = "flow" gwfname = name - wsf = os.path.join(testdir, testgroup, name) - sim = flopy.mf6.MFSimulation( - sim_name=name, sim_ws=wsf, exe_name=exe_name_mf6 - ) + wsf = os.path.join(dir, testgroup, name) + sim = flopy.mf6.MFSimulation(sim_name=name, sim_ws=wsf, exe_name=exe) tdis_rc = [(100.0, 1, 1.0), (100.0, 1, 1.0)] nper = len(tdis_rc) tdis = flopy.mf6.ModflowTdis( @@ -269,17 +240,15 @@ def run_flow_model(): errmsg = f"flow model did not terminate successfully\n{buff}" assert success, errmsg - return - -def run_transport_model(): +def run_transport_model(dir, exe): name = "transport" gwtname = name - wst = os.path.join(testdir, testgroup, name) + wst = os.path.join(dir, testgroup, name) sim = flopy.mf6.MFSimulation( sim_name=name, version="mf6", - exe_name=exe_name_mf6, + exe_name=exe, sim_ws=wst, continue_=False, ) @@ -408,21 +377,9 @@ def run_transport_model(): fname = os.path.join(wst, fname) d0 = np.genfromtxt(fname, names=True, delimiter=",", deletechars="") print(d0.dtype.names) - return - - -def test_ssm06fmi(): - run_flow_model() - run_transport_model() - d = os.path.join(testdir, testgroup) - if os.path.isdir(d): - shutil.rmtree(d) - return - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - # run tests - test_ssm06fmi() +def test_ssm06fmi(function_tmpdir, targets): + mf6 = targets.mf6 + run_flow_model(str(function_tmpdir), mf6) + run_transport_model(str(function_tmpdir), mf6) diff --git a/autotest/test_gwt_uzt01.py b/autotest/test_gwt_uzt01.py index 01946fb5b5d..50f1f482bb9 100644 --- a/autotest/test_gwt_uzt01.py +++ b/autotest/test_gwt_uzt01.py @@ -8,33 +8,13 @@ import os +import flopy import numpy as np import pytest - -try: - import pymake -except: - msg = "Error. Pymake package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install https://github.com/modflowpy/pymake/zipball/master" - raise Exception(msg) - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation ex = ["uzt01a"] -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) -ddir = "data" nlay, nrow, ncol = 15, 1, 1 @@ -341,13 +321,8 @@ def build_model(idx, dir): if id1 < ncv - 1: id2list.append(id1 + 1) for id2 in id2list: - obs1.append( - (f"uzt{id1 + 1}x{id2 + 1}", obstype, id1 + 1, id2 + 1) - ) - obs2 = [ - (f"buzt{i + 1}", obstype, f"myuzt{i + 1}") - for i in range(ncv) - ] + obs1.append((f"uzt{id1 + 1}x{id2 + 1}", obstype, id1 + 1, id2 + 1)) + obs2 = [(f"buzt{i + 1}", obstype, f"myuzt{i + 1}") for i in range(ncv)] uzt_obs[fname] = obs1 + obs2 # append additional obs attributes to obs dictionary @@ -403,8 +378,8 @@ def build_model(idx, dir): def make_plot(sim, obsvals): print("making plots...") - name = ex[sim.idxsim] - ws = exdirs[sim.idxsim] + name = sim.name + ws = sim.simpath # shows curves for times 2.5, 7.5, 12.6, 17.7 # which are indices 24, 74, 125, and -1 @@ -438,8 +413,8 @@ def make_plot(sim, obsvals): def check_obs(sim): print("checking obs...") - name = ex[sim.idxsim] - ws = exdirs[sim.idxsim] + name = sim.name + ws = sim.simpath sim = flopy.mf6.MFSimulation.load(sim_ws=ws) gwfname = "gwf_" + name gwtname = "gwt_" + name @@ -467,7 +442,9 @@ def check_obs(sim): # print(f" Checking control volume {icv + 1}") if ".concentration.csv" in csvfile: - is_same = np.allclose(conc_ra[f"BUZT{icv + 1}"], conc_uzt[:, icv]) + is_same = np.allclose( + conc_ra[f"BUZT{icv + 1}"], conc_uzt[:, icv] + ) if not is_same: success = False print( @@ -506,16 +483,15 @@ def check_obs(sim): ) assert success, "One or more UZT obs checks did not pass" - return def eval_flow(sim): print("evaluating flow...") - name = ex[sim.idxsim] + name = sim.name gwfname = "gwf_" + name gwtname = "gwt_" + name - ws = exdirs[sim.idxsim] + ws = sim.simpath # check binary grid file fname = os.path.join(ws, gwfname + ".dis.grb") @@ -581,47 +557,22 @@ def eval_flow(sim): # Make plot of obs fpth = os.path.join(sim.simpath, gwtname + ".uzt.obs.concentration.csv") - try: - obsvals = np.genfromtxt(fpth, names=True, delimiter=",") - except: - assert False, f'could not load data from "{fpth}"' - if False: - make_plot(sim, obsvals) - return + obsvals = np.genfromtxt(fpth, names=True, delimiter=",") + + # make_plot(sim, obsvals) -# - No need to change any code below @pytest.mark.parametrize( - "idx, dir", - list(enumerate(exdirs)), + "name", + ex, ) -def test_mf6model(idx, dir): - # initialize testing framework - test = testing_framework() - - # build the model - test.build_mf6_models(build_model, idx, dir) - - # run the test model - test.run_mf6(Simulation(dir, exfunc=eval_flow, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # run the test model - for idx, dir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, dir) - sim = Simulation(dir, exfunc=eval_flow, idxsim=idx) - test.run_mf6(sim) - - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, 0, ws) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_flow, idxsim=0 + ), + ws, + ) diff --git a/autotest/test_gwtgwt_oldexg.py b/autotest/test_gwtgwt_oldexg.py index 21e91f109d6..48b9ba073cf 100644 --- a/autotest/test_gwtgwt_oldexg.py +++ b/autotest/test_gwtgwt_oldexg.py @@ -1,18 +1,10 @@ import os +import flopy import numpy as np import pytest - -try: - import flopy -except: - msg = "Error. FloPy package is not available.\n" - msg += "Try installing using the following command:\n" - msg += " pip install flopy" - raise Exception(msg) - -from framework import testing_framework -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation # Test compatibility of GWT-GWT with the 'classic' GWF exchange. # It compares the result of a single reference model @@ -38,10 +30,6 @@ ex = ["gwtgwt_oldexg"] use_ifmod = False -exdirs = [] -for s in ex: - exdirs.append(os.path.join("temp", s)) - # some global convenience...: # model names mname_ref = "refmodel" @@ -719,8 +707,6 @@ def compare_gwf_to_ref(sim): errmsg = f"min or max residual too large {res.min()} {res.max()}" assert np.allclose(res, 0.0, atol=1.0e-6), errmsg - return - def compare_gwt_to_ref(sim): print("comparing concentration to single model reference...") @@ -781,41 +767,18 @@ def compare_gwt_to_ref(sim): errmsg = f"min or max residual too large {res.min()} {res.max()}" assert np.allclose(res, 0.0, atol=1.0e-6), errmsg - return - -# - No need to change any code below @pytest.mark.parametrize( - "idx, exdir", - list(enumerate(exdirs)), + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(idx, exdir): - # initialize testing framework - test = testing_framework() - - # build the model - test.build_mf6_models(build_model, idx, exdir) - - # run the test model - test.run_mf6(Simulation(exdir, exfunc=compare_to_ref, idxsim=idx)) - - -def main(): - # initialize testing framework - test = testing_framework() - - # run the test models - for idx, exdir in enumerate(exdirs): - test.build_mf6_models(build_model, idx, exdir) - - sim = Simulation(exdir, exfunc=compare_to_ref, idxsim=idx) - test.run_mf6(sim) - return - - -if __name__ == "__main__": - # print message - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() +def test_mf6model(idx, name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, idx, ws) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=compare_to_ref, idxsim=idx + ), + ws, + ) diff --git a/autotest/test_mf6_tmp_simulations.py b/autotest/test_mf6_tmp_simulations.py index c6ee3c22b1a..65600a5ab30 100644 --- a/autotest/test_mf6_tmp_simulations.py +++ b/autotest/test_mf6_tmp_simulations.py @@ -2,9 +2,9 @@ import sys import pytest - from common_regression import get_mf6_ftypes, get_namefiles -from simulation import Simulation +from framework import TestFramework +from simulation import TestSimulation exdir = os.path.join("..", "tmp_simulations") testpaths = os.path.join("..", exdir) @@ -75,9 +75,7 @@ def get_mf6_models(): namefiles = get_namefiles(pth) ftypes = [] for namefile in namefiles: - ftype = get_mf6_ftypes( - namefile, select_packages - ) + ftype = get_mf6_ftypes(namefile, select_packages) if ftype not in ftypes: ftypes += ftype if len(ftypes) > 0: @@ -97,7 +95,7 @@ def get_mf6_models(): return dirs -def run_mf6(sim): +def run_mf6(sim, ws): """ Run the MODFLOW 6 simulation and compare to existing head file or appropriate MODFLOW-2005, MODFLOW-NWT, MODFLOW-USG, or MODFLOW-LGR run. @@ -105,49 +103,16 @@ def run_mf6(sim): """ print(os.getcwd()) src = os.path.join(exdir, sim.name) - dst = os.path.join("temp", sim.name) + dst = os.path.join(ws, sim.name) sim.setup(src, dst) sim.run() sim.compare() - sim.teardown() @pytest.mark.parametrize( - "idx, dir", + "idx, name", list(enumerate(get_mf6_models())), ) -def test_mf6model(idx, dir): - # run the test model - run_mf6(Simulation(dir)) - - -def main(): - # write message - tnam = os.path.splitext(os.path.basename(__file__))[0] - msg = f"Running {tnam} test" - print(msg) - - # get a list of test models to run - dirs = get_mf6_models() - - # run the test model - for dir in dirs: - sim = Simulation(dir) - run_mf6(sim) - - return - - -if __name__ == "__main__": - - print(f"standalone run of {os.path.basename(__file__)}") - - delFiles = True - for idx, arg in enumerate(sys.argv): - if arg.lower() == "--keep": - if len(sys.argv) > idx + 1: - delFiles = False - break - - # run main routine - main() +def test_mf6model(idx, name, function_tmpdir, targets): + ws = str(function_tmpdir) + run_mf6(TestSimulation(name=name, exe_dict=targets), ws) diff --git a/autotest/test_z01_testmodels_mf6.py b/autotest/test_z01_testmodels_mf6.py index a58a918d715..1869f3d9cc2 100644 --- a/autotest/test_z01_testmodels_mf6.py +++ b/autotest/test_z01_testmodels_mf6.py @@ -1,6 +1,6 @@ import pytest from conftest import should_compare -from simulation import Simulation +from simulation import TestSimulation excluded_models = [ "alt_model", @@ -38,9 +38,9 @@ def test_model(function_tmpdir, test_model_mf6, targets, original_regression): if name in excluded_models: pytest.skip(f"Excluding mf6 model: {name}") - sim = Simulation( + sim = TestSimulation( name=name, - exe_dict=targets.as_dict(), + exe_dict=targets, mf6_regression=not original_regression, cmp_verbose=False, make_comparison=should_compare(name, excluded_comparisons, targets), diff --git a/autotest/test_z02_testmodels_mf5to6.py b/autotest/test_z02_testmodels_mf5to6.py index aac388c3b8e..d665cc3922e 100644 --- a/autotest/test_z02_testmodels_mf5to6.py +++ b/autotest/test_z02_testmodels_mf5to6.py @@ -4,7 +4,7 @@ import pytest from common_regression import get_namefiles, model_setup from conftest import should_compare -from simulation import Simulation +from simulation import TestSimulation sfmt = "{:25s} - {}" excluded_models = [ @@ -30,9 +30,9 @@ def test_model( if name in excluded_models: pytest.skip(f"Excluding mf5to6 model: {name}") - sim = Simulation( + sim = TestSimulation( name=exdir.name, - exe_dict=targets.as_dict(), + exe_dict=targets, mf6_regression=not original_regression, cmp_verbose=False, make_comparison=should_compare(name, excluded_comparisons, targets), diff --git a/autotest/test_z03_examples.py b/autotest/test_z03_examples.py index a5db81a0900..fc972e75e6c 100644 --- a/autotest/test_z03_examples.py +++ b/autotest/test_z03_examples.py @@ -1,6 +1,6 @@ import pytest from conftest import should_compare -from simulation import Simulation +from simulation import TestSimulation # skip nested models # ex-gwf-csub-p02c has subdirs like 'es-001', 'hb-100' @@ -59,7 +59,7 @@ def test_scenario(function_tmpdir, example_scenario, targets): for exdir in exdirs: model_name = f"{name}_{exdir.name}" workspace = function_tmpdir / model_name - sim = Simulation( + sim = TestSimulation( name=model_name, exe_dict=targets.as_dict(), mf6_regression=True, diff --git a/autotest/test_z03_largetestmodels.py b/autotest/test_z03_largetestmodels.py index 4631a799baf..81d616c7c3f 100644 --- a/autotest/test_z03_largetestmodels.py +++ b/autotest/test_z03_largetestmodels.py @@ -1,6 +1,6 @@ import pytest from conftest import should_compare -from simulation import Simulation +from simulation import TestSimulation excluded_models = [] excluded_comparisons = { @@ -24,7 +24,7 @@ def test_model( if name in excluded_models: pytest.skip(f"Excluding large mf6 model: {name}") - sim = Simulation( + sim = TestSimulation( name=name, exe_dict=targets.as_dict(), mf6_regression=not original_regression, diff --git a/autotest/update_flopy.py b/autotest/update_flopy.py index 42db0b3539e..60f9874e557 100644 --- a/autotest/update_flopy.py +++ b/autotest/update_flopy.py @@ -1,27 +1,21 @@ +import argparse import importlib import os import shutil import subprocess -from contextlib import contextmanager +from pathlib import Path import flopy +import pytest +from conftest import project_root_path -flopypth = flopy.__path__[0] -print(f"flopy is installed in {flopypth}") - - -@contextmanager -def cwd(path): - oldpwd = os.getcwd() - os.chdir(path) - try: - yield - finally: - os.chdir(oldpwd) +dfn_path = project_root_path / "doc" / "mf6io" / "mf6ivar" / "dfn" +fpy_path = flopy.__path__[0] +print(f"flopy is installed in {fpy_path}") def test_delete_mf6(): - pth = os.path.join(flopypth, "mf6", "modflow") + pth = os.path.join(fpy_path, "mf6", "modflow") files = [ entry for entry in os.listdir(pth) @@ -30,8 +24,9 @@ def test_delete_mf6(): delete_files(files, pth, exclude="mfsimulation.py") +@pytest.mark.order(after="test_delete_mf6") def test_delete_dfn(): - pth = os.path.join(flopypth, "mf6", "data", "dfn") + pth = os.path.join(fpy_path, "mf6", "data", "dfn") files = [ entry for entry in os.listdir(pth) @@ -40,29 +35,31 @@ def test_delete_dfn(): delete_files(files, pth, exclude="flopy.dfn") -def test_copy_dfn(): - pth0 = os.path.join("..", "doc", "mf6io", "mf6ivar", "dfn") +@pytest.mark.order(after="test_delete_dfn") +@pytest.mark.parametrize("path", [dfn_path]) +def test_copy_dfn(path): files = [ entry - for entry in os.listdir(pth0) - if os.path.isfile(os.path.join(pth0, entry)) + for entry in os.listdir(path) + if os.path.isfile(os.path.join(path, entry)) ] - pth1 = os.path.join(flopypth, "mf6", "data", "dfn") + pth1 = os.path.join(fpy_path, "mf6", "data", "dfn") for fn in files: ext = os.path.splitext(fn)[1].lower() if "dfn" in ext: - fpth0 = os.path.join(pth0, fn) + fpth0 = os.path.join(path, fn) fpth1 = os.path.join(pth1, fn) - print(f'copying {fn} from "{pth0}" to "{pth1}"') + print(f'copying {fn} from "{path}" to "{pth1}"') shutil.copyfile(fpth0, fpth1) +@pytest.mark.order(after="test_copy_dfn") def test_create_packages(): # get list of files in mf6/modflow - pth = os.path.join(flopypth, "mf6", "modflow") + pth = os.path.join(fpy_path, "mf6", "modflow") list_files(pth) - pth = os.path.join(flopypth, "mf6", "utils") + pth = os.path.join(fpy_path, "mf6", "utils") fn = "createpackages.py" # determine if createpackages.py exists @@ -71,19 +68,16 @@ def test_create_packages(): exist = os.path.isfile(fpth) assert exist, f'"{fpth}" does not exist' - # run createrpackages.py script + # run createpackages.py script print(f"running...{fn}") - cmd = ["python", fn] - buff, ierr = run_command(cmd, pth) - assert ierr == 0, f"could not run {fn}" - print(f"successfully ran...{fn}") + subprocess.check_output(["python", "createpackages.py"], cwd=pth) # reload flopy print("reloading flopy") importlib.reload(flopy) # get updated list of files in mf6/modflow - pth = os.path.join(flopypth, "mf6", "modflow") + pth = os.path.join(fpy_path, "mf6", "modflow") list_files(pth) @@ -100,7 +94,6 @@ def list_files(pth, exts=["py"]): if ext in exts: idx += 1 print(f" {idx:5d} - {fn}") - return def delete_files(files, pth, allow_failure=False, exclude=None): @@ -124,47 +117,17 @@ def delete_files(files, pth, allow_failure=False, exclude=None): return True -def run_command(argv, pth, timeout=10): - with subprocess.Popen( - argv, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=pth - ) as process: - try: - output, unused_err = process.communicate(timeout=timeout) - buff = output.decode("utf-8") - ierr = process.returncode - except subprocess.TimeoutExpired: - process.kill() - output, unused_err = process.communicate() - buff = output.decode("utf-8") - ierr = 100 - except: - output, unused_err = process.communicate() - buff = output.decode("utf-8") - ierr = 101 - - return buff, ierr - +if __name__ == "__main__": + parser = argparse.ArgumentParser("Update flopy from DFN files") + parser.add_argument( + "-p", "--path", help="path to DFN files", default=str(dfn_path) + ) + args = parser.parse_args() -def main(): - # write message - tnam = os.path.splitext(os.path.basename(__file__))[0] - msg = f"Running {tnam} test" - print(msg) + path = Path(args.path).resolve() + print(f"Updating flopy packages from DFN files in: {path}") - print("deleting existing MODFLOW 6 FloPy files") test_delete_mf6() - print("deleting existing MODFLOW 6 dfn files") test_delete_dfn() - print("copying MODFLOW 6 repo dfn files") - test_copy_dfn() - print("creating MODFLOW 6 packages from repo dfn files") + test_copy_dfn(path) test_create_packages() - - return - - -if __name__ == "__main__": - print(f"standalone run of {os.path.basename(__file__)}") - - # run main routine - main() diff --git a/environment.yml b/environment.yml index 2933f2dc9ac..12633dc18a5 100644 --- a/environment.yml +++ b/environment.yml @@ -23,5 +23,6 @@ dependencies: - pytest - pytest-cases - pytest-dotenv + - pytest-order - pytest-xdist - flaky From aa331f5e4edefc145d596ff385f23670caa55335 Mon Sep 17 00:00:00 2001 From: jdhughes-usgs Date: Mon, 23 Jan 2023 17:01:02 -0600 Subject: [PATCH 023/123] fix(pertim): modify Mf6Run in mf6core.f90 to accept pertim = 0 (#1139) Add autotest for fix (test_gwf_pertim.py) and run black on autotest subdirectory. --- autotest/conftest.py | 6 +- autotest/simulation.py | 9 +- autotest/test_gwf_csub_sk01.py | 5 +- autotest/test_gwf_pertim.py | 152 +++++++++++++++++++++++++ autotest/test_z01_testmodels_mf6.py | 5 +- autotest/test_z02_testmodels_mf5to6.py | 5 +- autotest/test_z03_examples.py | 4 +- src/mf6core.f90 | 4 +- 8 files changed, 174 insertions(+), 16 deletions(-) create mode 100644 autotest/test_gwf_pertim.py diff --git a/autotest/conftest.py b/autotest/conftest.py index d40ed2d2487..1743ecff592 100644 --- a/autotest/conftest.py +++ b/autotest/conftest.py @@ -11,8 +11,10 @@ def should_compare( test: str, comparisons: dict, executables: Executables ) -> bool: if test in comparisons.keys(): - dev_ver = Executables.get_version(path=executables.mf6).split(' ')[0] - reg_ver = Executables.get_version(path=executables.mf6_regression).split(' ')[0] + dev_ver = Executables.get_version(path=executables.mf6).split(" ")[0] + reg_ver = Executables.get_version( + path=executables.mf6_regression + ).split(" ")[0] print(f"MODFLOW 6 development version: {dev_ver}") print(f"MODFLOW 6 regression version: {reg_ver}") excluded = list(comparisons[test]) diff --git a/autotest/simulation.py b/autotest/simulation.py index 47a45aa0799..070ef8d1d04 100644 --- a/autotest/simulation.py +++ b/autotest/simulation.py @@ -6,8 +6,13 @@ import flopy import numpy as np -from common_regression import (get_mf6_comparison, get_mf6_files, - get_namefiles, setup_mf6, setup_mf6_comparison) +from common_regression import ( + get_mf6_comparison, + get_mf6_files, + get_namefiles, + setup_mf6, + setup_mf6_comparison, +) from flopy.utils.compare import compare_heads from modflow_devtools.misc import is_in_ci diff --git a/autotest/test_gwf_csub_sk01.py b/autotest/test_gwf_csub_sk01.py index c53ec9fc470..ed222a07720 100644 --- a/autotest/test_gwf_csub_sk01.py +++ b/autotest/test_gwf_csub_sk01.py @@ -168,7 +168,10 @@ def get_model(self, data, function_tmpdir): # build MODFLOW 6 files sim = flopy.mf6.MFSimulation( - sim_name=name, version="mf6", exe_name="mf6", sim_ws=str(function_tmpdir) + sim_name=name, + version="mf6", + exe_name="mf6", + sim_ws=str(function_tmpdir), ) # create tdis package tdis = flopy.mf6.ModflowTdis( diff --git a/autotest/test_gwf_pertim.py b/autotest/test_gwf_pertim.py new file mode 100644 index 00000000000..d3adcccc358 --- /dev/null +++ b/autotest/test_gwf_pertim.py @@ -0,0 +1,152 @@ +import os + +import flopy +import numpy as np +import pytest +from framework import TestFramework +from simulation import TestSimulation + +ex = [ + "gwf_pertim", +] + +# static model data +# temporal discretization +nper = 1 +perlen = [0.0] +nstp = [1] +tsmult = [1.0] +tdis_rc = [] +for idx in range(nper): + tdis_rc.append((perlen[idx], nstp[idx], tsmult[idx])) + +# spatial discretization data +nlay, nrow, ncol = 3, 21, 20 +shape3d = (nlay, nrow, ncol) +size3d = nlay * nrow * ncol +delr, delc = 500.0, 500.0 +top = 330.0 +botm = [220.0, 200.0, 0.0] +strt = 330.0 + +# calculate hk +hk = [50.0, 0.01, 200.0] +k33 = [10.0, 0.01, 20.0] + +# chd data +canal_spd = [(0, i, 0, 330.0, "canal") for i in range(nrow)] +river_spd = [(0, i, ncol - 1, 320.0, "river") for i in range(nrow)] + + +def build_model(idx, dir): + name = ex[idx] + + # build MODFLOW 6 files + ws = dir + sim = flopy.mf6.MFSimulation( + sim_name=name, version="mf6", exe_name="mf6", sim_ws=ws + ) + ims = flopy.mf6.ModflowIms( + sim, + print_option="ALL", + complexity="simple", + ) + tdis = flopy.mf6.ModflowTdis( + sim, time_units="DAYS", nper=nper, perioddata=tdis_rc + ) + + gwf = flopy.mf6.ModflowGwf( + sim, + modelname=name, + ) + + # create iterative model solution and register the gwf model with it + + dis = flopy.mf6.ModflowGwfdis( + gwf, + nlay=nlay, + nrow=nrow, + ncol=ncol, + delr=delr, + delc=delc, + top=top, + botm=botm, + ) + + # initial conditions + ic = flopy.mf6.ModflowGwfic( + gwf, + strt=strt, + ) + + # node property flow + npf = flopy.mf6.ModflowGwfnpf( + gwf, + save_flows=False, + k=hk, + k33=k33, + ) + + # chd files + chd_canal = flopy.mf6.ModflowGwfchd( + gwf, + boundnames=True, + stress_period_data=canal_spd, + pname="CHD-CANAL", + filename=f"{name}_canal.chd", + ) + chd_river = flopy.mf6.ModflowGwfchd( + gwf, + boundnames=True, + stress_period_data=river_spd, + pname="CHD-RIVER", + filename=f"{name}_river.chd", + ) + + # output control + oc = flopy.mf6.ModflowGwfoc( + gwf, + printrecord=[ + (("BUDGET", "ALL")), + ], + ) + + return sim, None + + +def eval_model(sim): + + print("evaluating results...") + + fpth = os.path.join(sim.simpath, f"{sim.name}.lst") + mflist = flopy.utils.Mf6ListBudget(fpth) + inc = mflist.get_incremental() + + q_in = 99928.4941 + q_out = 99928.5036 + q_in_sim = inc["CHD_IN"] + q_out_sim = inc["CHD2_OUT"] + + assert np.allclose([q_in_sim], [q_in]), f"CHD_IN <> {q_in} ({q_in_sim})" + assert np.allclose( + [q_out_sim], [q_out] + ), f"CHD2_OUT <> {q_out} ({q_out_sim})" + + +@pytest.mark.parametrize( + "idx, name", + list(enumerate(ex)), +) +def test_mf6model(idx, name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, idx, ws) + test.run( + TestSimulation( + name=name, + exe_dict=targets, + exfunc=eval_model, + idxsim=idx, + ), + ws, + ) diff --git a/autotest/test_z01_testmodels_mf6.py b/autotest/test_z01_testmodels_mf6.py index 1869f3d9cc2..2d1756b6c96 100644 --- a/autotest/test_z01_testmodels_mf6.py +++ b/autotest/test_z01_testmodels_mf6.py @@ -2,10 +2,7 @@ from conftest import should_compare from simulation import TestSimulation -excluded_models = [ - "alt_model", - "test205_gwtbuy-henrytidal" -] +excluded_models = ["alt_model", "test205_gwtbuy-henrytidal"] excluded_comparisons = { "test001e_noUZF_3lay": ("6.2.1",), "test005_advgw_tidal": ("6.2.1",), diff --git a/autotest/test_z02_testmodels_mf5to6.py b/autotest/test_z02_testmodels_mf5to6.py index d665cc3922e..9ae811d793d 100644 --- a/autotest/test_z02_testmodels_mf5to6.py +++ b/autotest/test_z02_testmodels_mf5to6.py @@ -7,10 +7,7 @@ from simulation import TestSimulation sfmt = "{:25s} - {}" -excluded_models = [ - "alt_model", - "mf2005" -] +excluded_models = ["alt_model", "mf2005"] excluded_comparisons = { "testPr2": ("6.2.1",), "testUzfLakSfr": ("6.2.1",), diff --git a/autotest/test_z03_examples.py b/autotest/test_z03_examples.py index fc972e75e6c..eb59985597d 100644 --- a/autotest/test_z03_examples.py +++ b/autotest/test_z03_examples.py @@ -64,7 +64,9 @@ def test_scenario(function_tmpdir, example_scenario, targets): exe_dict=targets.as_dict(), mf6_regression=True, cmp_verbose=False, - make_comparison=should_compare(name, excluded_comparisons, targets), + make_comparison=should_compare( + name, excluded_comparisons, targets + ), simpath=str(exdir), ) diff --git a/src/mf6core.f90 b/src/mf6core.f90 index 1f3545f7a9a..c3d6619b605 100644 --- a/src/mf6core.f90 +++ b/src/mf6core.f90 @@ -29,7 +29,7 @@ module Mf6CoreModule subroutine Mf6Run ! -- modules use CommandArguments, only: GetCommandLineArguments - use TdisModule, only: totim, totalsimtime + use TdisModule, only: endofsimulation use KindModule, only: DP ! -- local logical(LGP) :: hasConverged @@ -41,7 +41,7 @@ subroutine Mf6Run call Mf6Initialize() ! ! -- time loop - do while (totim < totalsimtime) + do while (.not. endofsimulation) ! perform a time step hasConverged = Mf6Update() From 20e0ca3a6e78aaaee226d0e258424c66e8a832ea Mon Sep 17 00:00:00 2001 From: langevin-usgs Date: Thu, 26 Jan 2023 14:35:55 -0600 Subject: [PATCH 024/123] feat(multi-domain sorption): generalize formulation (#1137) * close #851 * add new chapter to Supplemental Technical Information * update release notes * revise and simplify code * change definitions for MST and IST bulk density * final revisions to sorption writeup in tech doc --- autotest/test_gwt_moc3d01_zod.py | 11 +- doc/ReleaseNotes/v6.5.0.tex | 2 +- doc/SuppTechInfo/Tables/mf6enhancements.tex | 2 + doc/SuppTechInfo/Tables/sorption_params1.tex | 38 ++++ doc/SuppTechInfo/Tables/sorption_params2.tex | 36 ++++ doc/SuppTechInfo/body.tex | 6 + doc/SuppTechInfo/mf6suptechinfo.bbl | 9 +- doc/SuppTechInfo/mf6suptechinfo.tex | 4 + doc/SuppTechInfo/sorption.tex | 187 +++++++++++++++++++ doc/mf6io/mf6ivar/dfn/gwt-ist.dfn | 2 +- doc/mf6io/mf6ivar/dfn/gwt-mst.dfn | 2 +- doc/mf6io/mf6ivar/md/mf6ivar.md | 4 +- doc/mf6io/mf6ivar/tex/gwf-disv-griddata.dat | 4 +- doc/mf6io/mf6ivar/tex/gwf-npf-desc.tex | 4 +- doc/mf6io/mf6ivar/tex/gwf-npf-options.dat | 3 +- doc/mf6io/mf6ivar/tex/gwt-ist-desc.tex | 2 +- doc/mf6io/mf6ivar/tex/gwt-mst-desc.tex | 2 +- src/Model/GroundWaterTransport/gwt1ist1.f90 | 44 ++--- src/Model/GroundWaterTransport/gwt1mst1.f90 | 118 ++---------- 19 files changed, 336 insertions(+), 144 deletions(-) create mode 100644 doc/SuppTechInfo/Tables/sorption_params1.tex create mode 100644 doc/SuppTechInfo/Tables/sorption_params2.tex create mode 100644 doc/SuppTechInfo/sorption.tex diff --git a/autotest/test_gwt_moc3d01_zod.py b/autotest/test_gwt_moc3d01_zod.py index 395298e0243..9ec0e22f3e7 100644 --- a/autotest/test_gwt_moc3d01_zod.py +++ b/autotest/test_gwt_moc3d01_zod.py @@ -219,10 +219,15 @@ def build_model(idx, dir): rtd = retardation[idx] sorption = None kd = None - rhob = None + rhobm = None + rhobim = None if rtd is not None: rhob = 1.0 kd = (rtd - 1.0) * porosity / rhob + rhobm = rhob + if ist_package[idx]: + rhobm = .5 * rhob + rhobim = .5 * rhob sorption = "linear" decay_rate = decay[idx] @@ -239,7 +244,7 @@ def build_model(idx, dir): decay_sorbed=decay_rate, sorption=sorption, distcoef=kd, - bulk_density=rhob, + bulk_density=rhobm, ) if ist_package[idx]: @@ -252,7 +257,7 @@ def build_model(idx, dir): thetaim=porosity, zetaim=1.0, decay=decay_rate, - bulk_density=rhob, + bulk_density=rhobim, distcoef=kd, decay_sorbed=decay_rate, ) diff --git a/doc/ReleaseNotes/v6.5.0.tex b/doc/ReleaseNotes/v6.5.0.tex index 7abe392aaf6..18fc32de5d3 100644 --- a/doc/ReleaseNotes/v6.5.0.tex +++ b/doc/ReleaseNotes/v6.5.0.tex @@ -5,7 +5,7 @@ \underline{NEW FUNCTIONALITY} \begin{itemize} - \item xxx + \item The sorption formulation for the Groundwater Transport (GWT) model was modified for simulations involving a mobile domain and one or more immobile domains. The modifications do not affect GWT Models without the Immobile Storage and Transfer (IST) Package or GWT models without sorption. Prior to these changes, the multi-domain sorption formulation required the bulk density to be specified in both the Mobile Storage and Transfer (MST) Package and the IST Package. To generalize the formulation, the definition for bulk density in the MST Package was changed to be the mass of aquifer solid material in the mobile domain per unit volume of aquifer. The bulk density specified in the IST Package was changed to be the mass of aquifer solid material in the immobile domain per unit volume of aquifer. For multi-domain GWT Models that include sorption (and prepared for MODFLOW version 6.4.1 or earlier), it will be necessary to change the bulk density values specified in the MST and IST Packages according to the new definitions. A full description of the revised sorption formulation for multi-domain GWT Models is included in the MODFLOW 6 Supplemental Technical Information document included with the distribution. % \item xxx % \item xxx \end{itemize} diff --git a/doc/SuppTechInfo/Tables/mf6enhancements.tex b/doc/SuppTechInfo/Tables/mf6enhancements.tex index 4e4183bc02a..5f191a686bf 100644 --- a/doc/SuppTechInfo/Tables/mf6enhancements.tex +++ b/doc/SuppTechInfo/Tables/mf6enhancements.tex @@ -27,6 +27,8 @@ \rowcolor{Gray} Generalized Coupling of Numerical Models & \ref{ch:gencouple} & 6.3.0 & -- \\ VSC Package & \ref{ch:vscpackage} & 6.4.0 & -- \\ +\rowcolor{Gray} + Sorption in Mobile and Immobile Domains & \ref{ch:sorption} & 6.5.0 & -- \\ \hline \end{tabular} \label{table:mf6enhance} diff --git a/doc/SuppTechInfo/Tables/sorption_params1.tex b/doc/SuppTechInfo/Tables/sorption_params1.tex new file mode 100644 index 00000000000..6b6d61e79ac --- /dev/null +++ b/doc/SuppTechInfo/Tables/sorption_params1.tex @@ -0,0 +1,38 @@ +\begin{table}[!ht] + \small + \centering + \caption{Symbols, descriptions, and definitions of mobile and immobile domain input parameters and related variables relevant to the new, generalized formulation of sorption in \mf. Division of the aquifer into domains is conceptualized in terms of solid mass fractions, and input parameters are defined on a per-aquifer-volume basis} \tabularnewline + + \begin{tabular}{z{1.50cm} + z{3.50cm} + z{5.00cm} + z{3.50cm} + } + % header + \hline + \rowcolor{Gray} + \multicolumn{1}{ z{1.50cm} }{\textbf{Symbol}} & + \multicolumn{1}{ z{3.50cm} }{\textbf{Description}} & + \multicolumn{1}{ z{5.00cm} }{\textbf{Definition}} & + \multicolumn{1}{ z{3.50cm} }{\textbf{Type}} \\ + \hline + + $f_m$ & solid mass fraction (mobile domain) & $\frac{mobile \; domain \; solid \; mass}{aquifer \; solid \; mass}$ & sorption-related variable in equation~\ref{eqn:gwtpde} (not input) \\ + +\rowcolor{Gray} + $f_{im}$ & solid mass fraction (immobile domain $im$) & $\frac{immobile \; domain \; solid \; mass}{aquifer \; solid \; mass}$ & sorption-related variable in equation~\ref{eqn:gwtistpde} (not input) \\ + + $\theta_m$ & porosity (mobile domain) & $\frac{mobile \; domain \; pore \; volume}{aquifer \; volume}$ & input parameter (definition unchanged, but no longer directly related to sorption) \\ + +\rowcolor{Gray} + $\theta_{im}$ & porosity (immobile domain $im$) & $\frac{immobile \; domain \; pore \; volume}{aquifer \; volume}$ & input parameter (definition unchanged, but no longer directly related to sorption) \\ + + $\rho_{b,m}$ & bulk density (mobile domain) & $\frac{mobile \; domain \; solid \; mass}{aquifer \; volume}$ & sorption-related input parameter \\ + +\rowcolor{Gray} + $\rho_{b,im}$ & bulk density (immobile domain) & $\frac{immobile \; domain \; solid \; mass}{aquifer \; volume}$ & sorption-related input parameter \\ + + \hline + \end{tabular} + \label{table:sorptionparam1} +\end{table} diff --git a/doc/SuppTechInfo/Tables/sorption_params2.tex b/doc/SuppTechInfo/Tables/sorption_params2.tex new file mode 100644 index 00000000000..9bea3339a8f --- /dev/null +++ b/doc/SuppTechInfo/Tables/sorption_params2.tex @@ -0,0 +1,36 @@ +\begin{table}[!ht] + \small + \centering + \caption{Symbols, descriptions, and definitions of an alternative set of sorption-related variables. Division of the aquifer into domains is conceptualized in terms of volume fractions, and porosities and bulk densities are defined on a localized, per-domain-volume basis. If used, these variables must be converted to the parameters in table~\ref{table:sorptionparam1} for input into \mf, as described in this section} \tabularnewline + + \begin{tabular}{z{1.50cm} + z{3.50cm} + z{5.00cm} + } + % header + \hline + \rowcolor{Gray} + \multicolumn{1}{ z{1.50cm} }{\textbf{Symbol}} & + \multicolumn{1}{ z{3.50cm} }{\textbf{Description}} & + \multicolumn{1}{ z{5.00cm} }{\textbf{Definition}} \\ + \hline + + $\hat{f}_m$ & volume fraction (mobile domain) & $\frac{mobile \; domain \; volume}{aquifer \; volume}$ \\ + +\rowcolor{Gray} + $\hat{f}_{im}$ & volume fraction (immobile domain $im$) & $\frac{immobile \; domain \; volume}{aquifer \; volume}$ \\ + + $\phi_m$ & local porosity (mobile domain) & $\frac{mobile \; domain \; pore \; volume}{mobile \; domain \; volume}$ \\ + +\rowcolor{Gray} + $\phi_{im}$ & local porosity (immobile domain $im$) & $\frac{immobile \; domain \; pore \; volume}{immobile \; domain \; volume}$ \\ + + $\tilde{\rho}_{b,m}$ & local bulk density (mobile domain) & $\frac{mobile \; domain \; solid \; mass}{mobile \; domain \; volume}$ \\ + +\rowcolor{Gray} + $\tilde{\rho}_{b,im}$ & local bulk density (immobile domain) & $\frac{immobile \; domain \; solid \; mass}{immobile \; domain \; volume}$ \\ + + \hline + \end{tabular} + \label{table:sorptionparam2} +\end{table} diff --git a/doc/SuppTechInfo/body.tex b/doc/SuppTechInfo/body.tex index 9b4c38dc6ba..7ca521b6cdd 100644 --- a/doc/SuppTechInfo/body.tex +++ b/doc/SuppTechInfo/body.tex @@ -47,6 +47,12 @@ \customlabel{ch:vscpackage}{\thechapno} \input{viscosity.tex} +\newpage +\incchap +\SECTION{Chapter \thechapno. Revised Sorption Formulation for Combined Mobile and Immobile Domain Simulations} +\customlabel{ch:sorption}{\thechapno} +\input{sorption.tex} + \newpage \ifx\usgsdirector\undefined diff --git a/doc/SuppTechInfo/mf6suptechinfo.bbl b/doc/SuppTechInfo/mf6suptechinfo.bbl index 7a42f1fe96b..faee2669651 100644 --- a/doc/SuppTechInfo/mf6suptechinfo.bbl +++ b/doc/SuppTechInfo/mf6suptechinfo.bbl @@ -1,4 +1,4 @@ -\begin{thebibliography}{15} +\begin{thebibliography}{16} \providecommand{\natexlab}[1]{#1} \expandafter\ifx\csname urlstyle\endcsname\relax \providecommand{\doi}[1]{doi:\discretionary{}{}{}#1}\else @@ -67,6 +67,13 @@ Langevin, C.D., Hughes, J.D., Provost, A.M., Banta, E.R., Niswonger, R.G., and Model: {U.S. Geological Survey Techniques and Methods, book 6, chap. A55, 197 p.}, \url{https://doi.org/10.3133/tm6A55}. +\bibitem[{Langevin and others(2022)Langevin, Provost, Panday, and + Hughes}]{modflow6gwt} +Langevin, C.D., Provost, A.M., Panday, Sorab, and Hughes, J.D., 2022, + Documentation for the MODFLOW 6 Groundwater Transport (GWT) Model: {U.S. + Geological Survey Techniques and Methods, book 6, chap. A61, 56 p.}, + \url{https://doi.org/10.3133/tm6A61}. + \bibitem[{Maidment(1993)}]{maidment1993} Maidment, D.R., 1993, Handbook of Hydrology: New York, USA, McGraw-Hill. diff --git a/doc/SuppTechInfo/mf6suptechinfo.tex b/doc/SuppTechInfo/mf6suptechinfo.tex index 0cc08c8801f..d12e6f19fdb 100644 --- a/doc/SuppTechInfo/mf6suptechinfo.tex +++ b/doc/SuppTechInfo/mf6suptechinfo.tex @@ -20,6 +20,10 @@ \usepackage{booktabs} \usepackage[tight-spacing=true]{siunitx} \usepackage{color, colortbl} +\usepackage{xr} + +% to enable cross-referencing between tables in different files (with xr package) +\externaldocument{sorption_params1} %Do not allow a page break to result in a line appearing by itself % https://tex.stackexchange.com/questions/4152/how-do-i-prevent-widow-orphan-lines diff --git a/doc/SuppTechInfo/sorption.tex b/doc/SuppTechInfo/sorption.tex new file mode 100644 index 00000000000..5b7831d43a2 --- /dev/null +++ b/doc/SuppTechInfo/sorption.tex @@ -0,0 +1,187 @@ + +The Groundwater Transport (GWT) Model \citep{modflow6gwt} in \mf can simulate solute transport in aquifer material that includes ``mobile" and ``immobile" domains. In the mobile domain, which is always simulated by the GWT Model, the evolution of solute concentration is governed by the interplay between storage, advection, dispersion (including molecular diffusion), sorption, decay or production, sources and sinks, and exchange with any immobile domains that may exist. In an immobile domain, which may be optionally defined by the user, the evolution of solute concentration is governed by storage, sorption, decay or production, and exchange with the mobile domain. Advection and dispersion (including molecular diffusion) are considered negligible, and, aside from exchange with the mobile domain and optional zero- or first-order decay or production, there are no sources or sinks that add or remove solute mass directly to or from an immobile domain. Mobile and immobile domains are conceptualized as coexisting within a model cell, and the exchange of solute between domains represents a process occurring at the sub-grid scale. + +The Immobile Storage and Transfer (IST) Package \citep{modflow6gwt} for the GWT Model is designed to model the solute-transport processes occurring within a user-defined immobile domain and the exchange of solute between the immobile and mobile domains. Multiple instances of the IST Package may be invoked to represent multiple immobile domains. + +The GWT model allows sorption, as well as decay of sorbed solute, to occur in the mobile and immobile domains. Since sorption involves interaction of solute with aquifer solid material, the rates at which sorption-related processes occur in a domain depend on the amount of solid mass that is in that domain. This chapter summarizes and clarifies sorption parameters for the mobile and immobile domains, and presents a generalized formulation that replaces the formulation presented in \cite{modflow6gwt}. The modified formulation should not have an effect on most existing GWT Models, except for those models that include one or more IST Packages with active sorption processes. + +\subsection{Governing Equations} \label{sec:goveqn1} + +The GWT Model \citep{modflow6gwt} solves a discretized form of the following partial differential equation, which represents the conservation of solute mass at each point in the mobile domain: + +\begin{equation} +\label{eqn:gwtpde} +\begin{aligned} +\frac {\partial \left ( S_w \theta_m C \right )}{\partial t} = +- \nabla \cdot \left ( \matr{q} C \right ) ++ \nabla \cdot \left ( S_w \theta_m \matr{D} \nabla C \right ) ++ q'_s C_s + M_s +- \lambda_1 \theta_m S_w C - \gamma_1 \theta_m S_w \\ +- f_m \rho_b \frac {\partial \left ( S_w \overline{C} \right ) }{\partial t} +- \lambda_2 f_m \rho_b S_w \overline{C} - \gamma_2 f_m \rho_b S_w +- \sum \limits_{im=1}^{nim} \zeta_{im} S_w \left ( C - C_{im} \right ), +\end{aligned} +\end{equation} + +\noindent where $S_w$ is the water saturation (dimensionless) defined as the volume of water per volume of voids, $\theta_m$ is the effective porosity of the mobile domain (dimensionless), defined as volume of voids participating in mobile transport per unit volume of aquifer, $C$ is volumetric concentration of the mobile domain expressed as mass of dissolved solute per unit volume of fluid ($M/L^3$), $t$ is time ($T$), $\matr{q}$ is the vector of specific discharge ($L/T$), $\matr{D}$ is the second-order tensor of hydrodynamic dispersion coefficients ($L^2/T$), $q'_s$ is the volumetric flow rate per unit volume of aquifer (defined as positive for flow into the aquifer) for mass sources and sinks ($1/T$), $C_s$ is the volumetric solute concentration of the source or sink fluid ($M/L^3$), $M_s$ is rate of solute mass loading per unit volume of aquifer ($M/L^3T$), $\lambda_1$ is the first-order decay rate coefficient for the liquid phase ($1/T$), $\gamma_1$ is the zero-order decay rate coefficient for the liquid phase ($M/L^3T$), $\rho_b$ is the bulk density of the aquifer material ($M/L^3$), $\overline{C}$ is the sorbed concentration of solute mass in the mobile domain ($M/M$), $\lambda_2$ is the first-order decay rate coefficient ($1/T$) for the sorbed phase of the mobile domain, $\gamma_2$ is the zero-order decay rate coefficient for the sorbed phase of the mobile domain ($M/MT$), $nim$ is the number of immobile domains, $\zeta_{im}$ is the rate coefficient for the transfer of mass between the mobile domain and immobile domain $im$ ($1/T$), and $C_{im}$ is the solute concentration for immobile domain $im$ ($M/L^3$). In this chapter, the mobile porosity is intentionally given the $\theta_m$ symbol, which is different than the $\theta$ symbol used by \cite{modflow6gwt}. The definition of $f_m$, which appears in the three terms in equation~\ref{eqn:gwtpde} that relate to sorbed solute, is discussed and clarified later in this chapter. + +The Immobile Storage and Transfer (IST) Package for the GWT Model allows users to designate a fraction of a model cell as immobile. Solute transport in an immobile domain is represented by a discretized form of + +\begin{equation} +\label{eqn:gwtistpde} +\begin{split} +\theta_{im} \frac{\partial C_{im} }{\partial t} + f_{im} \rho_b \frac{\partial \overline{C}_{im}}{\partial t} = +- \lambda_{1,im} \theta_{im} C_{im} - \lambda_{2,im} f_{im} \rho_b \overline{C}_{im} \\ +- \gamma_{1,im} \theta_{im} - \gamma_{2,im} f_{im} \rho_b ++ \zeta_{im} S_w \left ( C - C_{im} \right ), +\end{split} +\end{equation} + +\noindent where $\theta_{im}$ is the volume of the immobile pores per volume of aquifer, $\overline{C}_{im}$ is the sorbed concentration of the immobile domain, expressed as the mass of the sorbed chemical per mass of solid, $\lambda_{1,im}$ is the first-order reaction rate coefficient for the liquid phase of the immobile domain ($1/T$), $\lambda_{2,im}$ is the first-order reaction rate coefficient for the sorbed phase of the immobile domain ($1/T$), $\gamma_{1,im}$ is the zero-order reaction rate coefficient for the liquid phase of the immobile domain ($ML^{-3}T^{-1}$), and $\gamma_{2,im}$ is the zero-order reaction rate coefficient for the sorbed phase of the immobile domain ($M M^{-1}T^{-1}$). The definition of $f_{im}$, which appears in the three terms in equation~\ref{eqn:gwtistpde} that relate to sorbed solute, is discussed and clarified below along with the definition of $f_m$. + +\subsection{Definition of Solid Mass Fractions} \label{sec:solidmassfrac0} + +\cite{modflow6gwt} define $f_m$ in equation~\ref{eqn:gwtpde} as ``the fraction of aquifer solid material available for sorptive exchange with the mobile phase under fully saturated conditions." This is correct assuming all of the aquifer solid material in the mobile domain is available for sorptive exchange with the mobile phase, and the fraction considered is the mass fraction. A more generally correct and complete definition of $f_m$ is the fraction of the mass of aquifer solid material that is in the mobile domain. The product $f_m \rho_b$ is then the mass of aquifer solid material in the mobile domain per unit volume of aquifer. This product forms the basis for expressing the three terms in equation~\ref{eqn:gwtpde} that relate to sorbed solute, each of which has units of (sorbed) solute mass per volume of aquifer per time. Note that concentration $\overline{C}$ in equation~\ref{eqn:gwtpde} has units of (sorbed) solute mass per mass of aquifer solid material in the mobile domain. + +Similarly, $f_{im}$ is most correctly and completely defined as the fraction of the mass of aquifer solid material that is in immobile domain $im$. The product $f_{im} \rho_b$ is then the mass of aquifer solid material in immobile domain $im$ per unit volume of aquifer. This product forms the basis for expressing the three terms in equation~\ref{eqn:gwtistpde} that relate to sorbed solute, each of which has units of (sorbed) solute mass per volume of aquifer per time. Note that concentration $\overline{C}_{im}$ in equation~\ref{eqn:gwtistpde} has units of (sorbed) solute mass per mass of aquifer solid material in immobile domain $im$. + +The solid mass fractions in the mobile and immobile domains sum to one. Therefore, the mobile solid mass fraction can be calculated from the immobile solid mass fractions according to + +\begin{equation} +\label{eqn:fm0} +f_m = 1 - \sum_{im}f_{im}, +\end{equation} + +\noindent where the summation is over all immobile domains specified by the user. + +\subsection{Previous Approximation of Solid Mass Fractions} \label{sec:solidmassfrac1} + +Up to and including \mf version 6.4.1, the GWT Model automatically set immobile solid mass fractions to + +\begin{equation} +\label{eqn:fim1} +f_{im} = \frac{\theta_{im}}{\theta_t}, +\end{equation} + +\noindent where $\theta_{im}$ is the immobile domain porosity and $\theta_t$ is the total porosity. Note that the text on p. 7--2 of \cite{modflow6gwt} erroneously states that \mf sets ``$f_{im} = \theta_{im} / \theta$''. This statement is incorrect because the symbol in the denominator, ``$\theta$'', represents the mobile domain porosity rather than the total porosity, $\theta_t$, in \cite{modflow6gwt}. In actual fact, \mf used equation~\ref{eqn:fim1} to set $f_{im}$ values from user-supplied porosities. + +Equations~\ref{eqn:fm0} and~\ref{eqn:fim1}, together with the definition of total porosity, + +\begin{equation} +\label{eqn:thetat1} +\theta_t = \theta_m + \sum_{im}{\theta_{im}}, +\end{equation} + +\noindent imply that the default value of the mobile domain solid mass fraction is given by + +\begin{equation} +\label{eqn:fm1} +f_m = 1 - \sum_{im}f_{im} = 1 - \frac{\sum_{im}\theta_{im}}{\theta_t} = \frac{\theta_m}{\theta_t}. +\end{equation} + +\noindent If there are no immobile domains, the total porosity is the same as the mobile porosity and $f_m$ is one. + +When considering the validity of equations~\ref{eqn:fim1} and~\ref{eqn:fm1}, it is important to note that the domain porosities are defined in terms of pore volume in a domain per volume of aquifer. Specifically, $\theta_m$ is the pore volume in the mobile domain per volume of aquifer (not per volume of mobile domain), and $\theta_{im}$ is the pore volume in immobile domain $im$ per volume of aquifer (not per volume of immobile domain $im$). When porosities are defined in this way, the ratios $\theta_m / \theta_t$ and $\theta_{im} / \theta_t$ can be thought of as ``pore volume fractions" in the same sense that $f_m$ and $f_{im}$ are solid mass fractions. Thus, underlying equations~\ref{eqn:fim1} and~\ref{eqn:fm1} is the assumption that the mass of aquifer solid material is distributed among the various domains in the same proportions as the pore volume is distributed among the domains. This is true, for example, when a volume of aquifer consists of a mobile domain and one immobile domain, and the local porosities (pore volume in the domain per volume of domain) and aquifer solid material densities are the same for both domains, which is a generalization of the example given by \cite{modflow6gwt}. In general, however, the aquifer solid mass need not be distributed between domains in the same proportions as pore volume, and equations~\ref{eqn:fim1} and~\ref{eqn:fm1} may not be good approximations in many cases of practical interest. As described below, the model input has been reformulated in terms of domain bulk densities to give the user the flexibility to specify how solid aquifer mass is distributed between domains. + +\subsection{Generalized Formulation Based on Domain Bulk Densities} \label{sec:solidmassfrac2b} + +Up to and including \mf version 6.4.1 the user was required to provide a value for the overall bulk density, $\rho_b$, in the input for the MST Package. The user was also required to again provide the value for the overall bulk density in the input for each IST Package. A value of $f_{im}$ for each immobile domain was internally calculated by the program using equation~\ref{eqn:fim1}, and the value of $f_{m}$ for the mobile domain was internally calculated using equation~\ref{eqn:fm1}. The generalized formulation presented in this chapter redefines the bulk densities for which the user provides values in the MST and IST Packages. The generalized formulation is more flexible than the previous formulation and no longer requires that the aquifer solid mass be distributed between domains in the same proportions as pore volume as implied by equations~\ref{eqn:fim1} and ~\ref{eqn:fm1}. + +In equations~\ref{eqn:gwtpde} and~\ref{eqn:gwtistpde}, $f_m$ and $f_{im}$ always appear multiplied by the bulk density, $\rho_b$. As noted earlier in this chapter, the products $f_m \rho_b$ and $f_{im} \rho_b$ are the masses of aquifer solid material in the mobile domain and immobile domain $im$, respectively, per unit volume of aquifer. Thus, $f_m \rho_b$ and $f_{im} \rho_b$ are bulk densities defined on a per-aquifer-volume basis: + +\begin{equation} +\label{eqn:rho_b_m_1} +f_m \rho_b = \frac{mobile \; domain \; solid \; mass}{aquifer \: volume} \equiv \rho_{b,m}, +\end{equation} + +\noindent and + +\begin{equation} +\label{eqn:rho_b_im_1} +f_{im} \rho_b = \frac{immobile \; domain \; im \; solid \; mass}{aquifer \: volume} \equiv \rho_{b,im}, +\end{equation} + +\noindent where $\rho_{b,m}$ and $\rho_{b,im}$ are the bulk densities for the mobile domain and immobile domain $im$, respectively. Note that the domain bulk densities are defined on a per-aquifer-volume basis and sum to the overall bulk density: + +\begin{equation} +\label{eqn:rho_b_1} +\rho_{b} = \rho_{b, m} + \sum_{im}{\rho_{b, im}}. +\end{equation} + +With this new, generalized formulation users are required to specify $\rho_{b,m} \left ( \equiv f_m \rho_b \right )$ for the mobile domain in the MST Package and $\rho_{b,im} \left ( \equiv f_{im} \rho_b \right )$ for each immobile domain in the IST Package. There are no additional input parameters required for this generalized formulation; users continue to specify a value of bulk density in the MST Packages and each IST Package. Subsequent to these changes, however, the bulk densities entered into the MST and IST Packages are defined as domain bulk densities (according to equations~\ref{eqn:rho_b_m_1} and ~\ref{eqn:rho_b_im_1}) rather than multiple instances of the overall bulk density (which were required up through \mf version 6.4.1). + +\subsection{Parameter Relations} \label{sec:solidmassfrac3} + +Successful application of the generalized formulation presented here for mobile and immobile domain sorption depends on proper interpretation and assignment of the different input parameters. Input parameters and related variables relevant to the generalized formulation are presented in table~\ref{table:sorptionparam1}. (As shown in equations~\ref{eqn:fm0},~\ref{eqn:thetat1} and~\ref{eqn:rho_b_1}, the domain solid mass fractions sum to 1, the domain porosities sum to the total porosity, $\theta_t$, and the domain bulk densities sum to the overall bulk density for the aquifer, $\rho_b$.) Note that division of the aquifer into mobile and immobile domains is conceptualized in terms solid mass fractions, and porosities and bulk densities are defined on a per-aquifer-volume basis. + +\input{./Tables/sorption_params1.tex} + +When an aquifer can be divided into multiple domains that occupy distinct, well-defined volumes, as when the mobile and immobile domains represent different lithologies, it may be intuitive to think in terms of the domain volume fractions and local domain properties (properties defined on a per-domain-volume basis) presented in table~\ref{table:sorptionparam2}. In that case, domain volume fractions and local domain properties must be converted by the user into domain mass fractions and per-aquifer-volume domain properties for input into \mf. This section presents the mathematical relations between the input parameters read by \mf and domain volume fractions and local domain properties. + +\input{./Tables/sorption_params2.tex} + +As with solid mass fractions, volume fractions for the mobile and immobile domains sum to one by definition, which implies that + +\begin{equation} +\label{eqn:fm5} +\hat{f}_m = 1 - \sum_{im}{\hat{f}_{im}}. +\end{equation} + +\noindent The total porosity, $\theta_t$, is the volume-weighted average of the local domain porosities: + +\begin{equation} +\label{eqn:thetat} +\theta_{t} = \hat{f}_{m} \phi_{m} + \sum_{im}{ \hat{f}_{im} \phi_{im}}, +\end{equation} + +\noindent The overall bulk density for the aquifer, $\rho_b$, is the volume-weighted average of the local domain bulk densities: + +\begin{equation} +\label{eqn:rhob2} +\rho_{b} = \hat{f}_m \tilde{\rho}_{b, m} + \sum_{im}{\hat{f}_{im} \tilde{\rho}_{b, im}}. +\end{equation} + +% here present relations +Conversion between the alternative variables in table~\ref{table:sorptionparam2} to those used in \mf (table~\ref{table:sorptionparam1}) is straightforward. Local porosities for the mobile and immobile domains, denoted by $\phi_m$ and $\phi_{im}$, respectively, can be converted to mobile and immobile domain porosities defined on a per-aquifer-volume basis, denoted by $\theta_m$ and $\theta_{im}$, respectively, using + +\begin{equation} +\label{eqn:theta1} +\theta_{m} = \hat{f}_{m} \phi_{m} +\end{equation} + +\noindent and + +\begin{equation} +\label{eqn:theta2} +\theta_{im} = \hat{f}_{im} \phi_{im}. +\end{equation} + +\noindent Local bulk densities for the mobile and immobile domains, denoted by $\tilde{\rho}_{b, m}$ and $\tilde{\rho}_{b, im}$, respectively, can be converted to mobile and immobile domain bulk densities defined on a per-aquifer-volume basis, denoted by $\rho_{b, m}$ and $\rho_{b, im}$, respectively, using + +\begin{equation} +\label{eqn:rhobm} +\rho_{b,m} = \hat{f}_m \tilde{\rho}_{b, m} +\end{equation} + +\noindent and + +\begin{equation} +\label{eqn:rhobim} +\rho_{b, im} = \hat{f}_{im} \tilde{\rho}_{b, im}. +\end{equation} + +\noindent Volume fractions for the mobile and immobile domains, denoted by $\hat{f}_{m}$ and $\hat{f}_{im}$, respectively, can be converted to mobile and immobile domain solid mass fractions, denoted by $f_{m}$ and $f_{im}$, respectively, using + +\begin{equation} +\label{eqn:fmfm} +f_{m} = \hat{f}_m \frac{\tilde{\rho}_{b, m}}{\rho_{b}} +\end{equation} + +\noindent and + +\begin{equation} +\label{eqn:fimfim} +f_{im} = \hat{f}_{im} \frac{\tilde{\rho}_{b, im}}{\rho_{b}}. +\end{equation} + +\noindent Note that although $f_{m}$ and $f_{im}$ appear in equations~\ref{eqn:gwtpde} and~\ref{eqn:gwtistpde}, they are not (and never have been) input parameters for \mf. Rather, it is the domain bulk densities, $\rho_{b, m} \left ( \equiv f_{m} \rho_b \right )$ and $\rho_{b, im} \left ( \equiv f_{im} \rho_b \right )$, that are input parameters in the new, generalized formulation of sorption. diff --git a/doc/mf6io/mf6ivar/dfn/gwt-ist.dfn b/doc/mf6io/mf6ivar/dfn/gwt-ist.dfn index f31dc11cc4c..9af818288e6 100644 --- a/doc/mf6io/mf6ivar/dfn/gwt-ist.dfn +++ b/doc/mf6io/mf6ivar/dfn/gwt-ist.dfn @@ -285,7 +285,7 @@ shape (nodes) reader readarray layered true longname bulk density -description is the bulk density of the aquifer in mass per length cubed. bulk\_density will have no effect on simulation results unless the SORPTION keyword is specified in the options block. +description is the bulk density of the aquifer in mass per length cubed. bulk\_density will have no effect on simulation results unless the SORPTION keyword is specified in the options block. Bulk density is defined as the immobile domain solid mass per aquifer volume. The definition for this input variable changed after version 6.4.1 was released. The bulk density specified in this package is defined differently from the bulk density defined in the Mobile Storage and Transfer (MST) Package. Additional information on bulk density is included in the MODFLOW 6 Supplemental Technical Information document. block griddata name distcoef diff --git a/doc/mf6io/mf6ivar/dfn/gwt-mst.dfn b/doc/mf6io/mf6ivar/dfn/gwt-mst.dfn index 5fe934bc3e3..d3d9bee0689 100644 --- a/doc/mf6io/mf6ivar/dfn/gwt-mst.dfn +++ b/doc/mf6io/mf6ivar/dfn/gwt-mst.dfn @@ -72,7 +72,7 @@ reader readarray optional true layered true longname bulk density -description is the bulk density of the aquifer in mass per length cubed. bulk\_density is not required unless the SORPTION keyword is specified. +description is the bulk density of the aquifer in mass per length cubed. bulk\_density is not required unless the SORPTION keyword is specified. Bulk density is defined as the mobile domain solid mass per aquifer volume. The definition for this input variable changed after version 6.4.1 was released. The bulk density specified here is defined differently from the bulk density defined in the Immobile Storage and Transfer (IST) Package. Additional information on bulk density is included in the MODFLOW 6 Supplemental Technical Information document. block griddata name distcoef diff --git a/doc/mf6io/mf6ivar/md/mf6ivar.md b/doc/mf6io/mf6ivar/md/mf6ivar.md index 4423337ddb1..181ef6dbed0 100644 --- a/doc/mf6io/mf6ivar/md/mf6ivar.md +++ b/doc/mf6io/mf6ivar/md/mf6ivar.md @@ -964,7 +964,7 @@ | GWT | MST | GRIDDATA | POROSITY | DOUBLE PRECISION (NODES) | is the aquifer porosity. | | GWT | MST | GRIDDATA | DECAY | DOUBLE PRECISION (NODES) | is the rate coefficient for first or zero-order decay for the aqueous phase of the mobile domain. A negative value indicates solute production. The dimensions of decay for first-order decay is one over time. The dimensions of decay for zero-order decay is mass per length cubed per time. decay will have no effect on simulation results unless either first- or zero-order decay is specified in the options block. | | GWT | MST | GRIDDATA | DECAY_SORBED | DOUBLE PRECISION (NODES) | is the rate coefficient for first or zero-order decay for the sorbed phase of the mobile domain. A negative value indicates solute production. The dimensions of decay\_sorbed for first-order decay is one over time. The dimensions of decay\_sorbed for zero-order decay is mass of solute per mass of aquifer per time. If decay\_sorbed is not specified and both decay and sorption are active, then the program will terminate with an error. decay\_sorbed will have no effect on simulation results unless the SORPTION keyword and either first- or zero-order decay are specified in the options block. | -| GWT | MST | GRIDDATA | BULK_DENSITY | DOUBLE PRECISION (NODES) | is the bulk density of the aquifer in mass per length cubed. bulk\_density is not required unless the SORPTION keyword is specified. | +| GWT | MST | GRIDDATA | BULK_DENSITY | DOUBLE PRECISION (NODES) | is the bulk density of the aquifer in mass per length cubed. bulk\_density is not required unless the SORPTION keyword is specified. Bulk density is defined as the mobile domain solid mass per aquifer volume. The definition for this input variable changed after version 6.4.1 was released. The bulk density specified here is defined differently from the bulk density defined in the Immobile Storage and Transfer (IST) Package. Additional information on bulk density is included in the MODFLOW 6 Supplemental Technical Information document. | | GWT | MST | GRIDDATA | DISTCOEF | DOUBLE PRECISION (NODES) | is the distribution coefficient for the equilibrium-controlled linear sorption isotherm in dimensions of length cubed per mass. distcoef is not required unless the SORPTION keyword is specified. | | GWT | MST | GRIDDATA | SP2 | DOUBLE PRECISION (NODES) | is the exponent for the Freundlich isotherm and the sorption capacity for the Langmuir isotherm. | | GWT | IST | OPTIONS | SAVE_FLOWS | KEYWORD | keyword to indicate that IST flow terms will be written to the file specified with ``BUDGET FILEOUT'' in Output Control. | @@ -988,7 +988,7 @@ | GWT | IST | GRIDDATA | ZETAIM | DOUBLE PRECISION (NODES) | mass transfer rate coefficient between the mobile and immobile domains, in dimensions of per time. | | GWT | IST | GRIDDATA | DECAY | DOUBLE PRECISION (NODES) | is the rate coefficient for first or zero-order decay for the aqueous phase of the immobile domain. A negative value indicates solute production. The dimensions of decay for first-order decay is one over time. The dimensions of decay for zero-order decay is mass per length cubed per time. Decay will have no effect on simulation results unless either first- or zero-order decay is specified in the options block. | | GWT | IST | GRIDDATA | DECAY_SORBED | DOUBLE PRECISION (NODES) | is the rate coefficient for first or zero-order decay for the sorbed phase of the immobile domain. A negative value indicates solute production. The dimensions of decay\_sorbed for first-order decay is one over time. The dimensions of decay\_sorbed for zero-order decay is mass of solute per mass of aquifer per time. If decay\_sorbed is not specified and both decay and sorption are active, then the program will terminate with an error. decay\_sorbed will have no effect on simulation results unless the SORPTION keyword and either first- or zero-order decay are specified in the options block. | -| GWT | IST | GRIDDATA | BULK_DENSITY | DOUBLE PRECISION (NODES) | is the bulk density of the aquifer in mass per length cubed. bulk\_density will have no effect on simulation results unless the SORPTION keyword is specified in the options block. | +| GWT | IST | GRIDDATA | BULK_DENSITY | DOUBLE PRECISION (NODES) | is the bulk density of the aquifer in mass per length cubed. bulk\_density will have no effect on simulation results unless the SORPTION keyword is specified in the options block. Bulk density is defined as the immobile domain solid mass per aquifer volume. The definition for this input variable changed after version 6.4.1 was released. The bulk density specified in this package is defined differently from the bulk density defined in the Mobile Storage and Transfer (MST) Package. Additional information on bulk density is included in the MODFLOW 6 Supplemental Technical Information document. | | GWT | IST | GRIDDATA | DISTCOEF | DOUBLE PRECISION (NODES) | is the distribution coefficient for the equilibrium-controlled linear sorption isotherm in dimensions of length cubed per mass. distcoef will have no effect on simulation results unless the SORPTION keyword is specified in the options block. | | GWT | SFT | OPTIONS | FLOW_PACKAGE_NAME | STRING | keyword to specify the name of the corresponding flow package. If not specified, then the corresponding flow package must have the same name as this advanced transport package (the name associated with this package in the GWT name file). | | GWT | SFT | OPTIONS | AUXILIARY | STRING (NAUX) | defines an array of one or more auxiliary variable names. There is no limit on the number of auxiliary variables that can be provided on this line; however, lists of information provided in subsequent blocks must have a column of data for each auxiliary variable name defined here. The number of auxiliary variables detected on this line determines the value for naux. Comments cannot be provided anywhere on this line as they will be interpreted as auxiliary variable names. Auxiliary variables may not be used by the package, but they will be available for use by other parts of the program. The program will terminate with an error if auxiliary variables are specified on more than one line in the options block. | diff --git a/doc/mf6io/mf6ivar/tex/gwf-disv-griddata.dat b/doc/mf6io/mf6ivar/tex/gwf-disv-griddata.dat index a9db9563a42..e263cb1d7bb 100644 --- a/doc/mf6io/mf6ivar/tex/gwf-disv-griddata.dat +++ b/doc/mf6io/mf6ivar/tex/gwf-disv-griddata.dat @@ -2,7 +2,7 @@ BEGIN GRIDDATA TOP -- READARRAY BOTM [LAYERED] - -- READARRAY + -- READARRAY [IDOMAIN [LAYERED] - -- READARRAY] + -- READARRAY] END GRIDDATA diff --git a/doc/mf6io/mf6ivar/tex/gwf-npf-desc.tex b/doc/mf6io/mf6ivar/tex/gwf-npf-desc.tex index 14f0c5401f0..b63abdfd0f1 100644 --- a/doc/mf6io/mf6ivar/tex/gwf-npf-desc.tex +++ b/doc/mf6io/mf6ivar/tex/gwf-npf-desc.tex @@ -5,6 +5,8 @@ \begin{description} \item \texttt{SAVE\_FLOWS}---keyword to indicate that budget flow terms will be written to the file specified with ``BUDGET SAVE FILE'' in Output Control. +\item \texttt{PRINT\_FLOWS}---keyword to indicate that calculated flows between cells will be printed to the listing file for every stress period time step in which ``BUDGET PRINT'' is specified in Output Control. If there is no Output Control option and ``PRINT\_FLOWS'' is specified, then flow rates are printed for the last time step of each stress period. This option can produce extremely large list files because all cell-by-cell flows are printed. It should only be used with the NPF Package for models that have a small number of cells. + \item \texttt{alternative\_cell\_averaging}---is a text keyword to indicate that an alternative method will be used for calculating the conductance for horizontal cell connections. The text value for ALTERNATIVE\_CELL\_AVERAGING can be ``LOGARITHMIC'', ``AMT-LMK'', or ``AMT-HMK''. ``AMT-LMK'' signifies that the conductance will be calculated using arithmetic-mean thickness and logarithmic-mean hydraulic conductivity. ``AMT-HMK'' signifies that the conductance will be calculated using arithmetic-mean thickness and harmonic-mean hydraulic conductivity. If the user does not specify a value for ALTERNATIVE\_CELL\_AVERAGING, then the harmonic-mean method will be used. This option cannot be used if the XT3D option is invoked. \item \texttt{THICKSTRT}---indicates that cells having a negative ICELLTYPE are confined, and their cell thickness for conductance calculations will be computed as STRT-BOT rather than TOP-BOT. @@ -39,7 +41,7 @@ \item \texttt{FILEIN}---keyword to specify that an input filename is expected next. -\item \texttt{tvk\_filename}---defines a time-varying hydraulic conductivity (TVK) input file. Records in the TVK file can be used to change hydraulic conductivity properties at specified times or stress periods. +\item \texttt{tvk6\_filename}---defines a time-varying hydraulic conductivity (TVK) input file. Records in the TVK file can be used to change hydraulic conductivity properties at specified times or stress periods. \end{description} \item \textbf{Block: GRIDDATA} diff --git a/doc/mf6io/mf6ivar/tex/gwf-npf-options.dat b/doc/mf6io/mf6ivar/tex/gwf-npf-options.dat index fa382e4fc38..e5f2534b933 100644 --- a/doc/mf6io/mf6ivar/tex/gwf-npf-options.dat +++ b/doc/mf6io/mf6ivar/tex/gwf-npf-options.dat @@ -1,5 +1,6 @@ BEGIN OPTIONS [SAVE_FLOWS] + [PRINT_FLOWS] [ALTERNATIVE_CELL_AVERAGING ] [THICKSTRT] [VARIABLECV [DEWATERED]] @@ -10,5 +11,5 @@ BEGIN OPTIONS [SAVE_SATURATION] [K22OVERK] [K33OVERK] - [TVK6 FILEIN ] + [TVK6 FILEIN ] END OPTIONS diff --git a/doc/mf6io/mf6ivar/tex/gwt-ist-desc.tex b/doc/mf6io/mf6ivar/tex/gwt-ist-desc.tex index 768f088d8f8..8959d526f4f 100644 --- a/doc/mf6io/mf6ivar/tex/gwt-ist-desc.tex +++ b/doc/mf6io/mf6ivar/tex/gwt-ist-desc.tex @@ -49,7 +49,7 @@ \item \texttt{decay\_sorbed}---is the rate coefficient for first or zero-order decay for the sorbed phase of the immobile domain. A negative value indicates solute production. The dimensions of decay\_sorbed for first-order decay is one over time. The dimensions of decay\_sorbed for zero-order decay is mass of solute per mass of aquifer per time. If decay\_sorbed is not specified and both decay and sorption are active, then the program will terminate with an error. decay\_sorbed will have no effect on simulation results unless the SORPTION keyword and either first- or zero-order decay are specified in the options block. -\item \texttt{bulk\_density}---is the bulk density of the aquifer in mass per length cubed. bulk\_density will have no effect on simulation results unless the SORPTION keyword is specified in the options block. +\item \texttt{bulk\_density}---is the bulk density of the aquifer in mass per length cubed. bulk\_density will have no effect on simulation results unless the SORPTION keyword is specified in the options block. Bulk density is defined as the immobile domain solid mass per aquifer volume. The definition for this input variable changed after version 6.4.1 was released. The bulk density specified in this package is defined differently from the bulk density defined in the Mobile Storage and Transfer (MST) Package. Additional information on bulk density is included in the MODFLOW 6 Supplemental Technical Information document. \item \texttt{distcoef}---is the distribution coefficient for the equilibrium-controlled linear sorption isotherm in dimensions of length cubed per mass. distcoef will have no effect on simulation results unless the SORPTION keyword is specified in the options block. diff --git a/doc/mf6io/mf6ivar/tex/gwt-mst-desc.tex b/doc/mf6io/mf6ivar/tex/gwt-mst-desc.tex index d439f84e385..90c6e7d5713 100644 --- a/doc/mf6io/mf6ivar/tex/gwt-mst-desc.tex +++ b/doc/mf6io/mf6ivar/tex/gwt-mst-desc.tex @@ -21,7 +21,7 @@ \item \texttt{decay\_sorbed}---is the rate coefficient for first or zero-order decay for the sorbed phase of the mobile domain. A negative value indicates solute production. The dimensions of decay\_sorbed for first-order decay is one over time. The dimensions of decay\_sorbed for zero-order decay is mass of solute per mass of aquifer per time. If decay\_sorbed is not specified and both decay and sorption are active, then the program will terminate with an error. decay\_sorbed will have no effect on simulation results unless the SORPTION keyword and either first- or zero-order decay are specified in the options block. -\item \texttt{bulk\_density}---is the bulk density of the aquifer in mass per length cubed. bulk\_density is not required unless the SORPTION keyword is specified. +\item \texttt{bulk\_density}---is the bulk density of the aquifer in mass per length cubed. bulk\_density is not required unless the SORPTION keyword is specified. Bulk density is defined as the mobile domain solid mass per aquifer volume. The definition for this input variable changed after version 6.4.1 was released. The bulk density specified here is defined differently from the bulk density defined in the Immobile Storage and Transfer (IST) Package. Additional information on bulk density is included in the MODFLOW 6 Supplemental Technical Information document. \item \texttt{distcoef}---is the distribution coefficient for the equilibrium-controlled linear sorption isotherm in dimensions of length cubed per mass. distcoef is not required unless the SORPTION keyword is specified. diff --git a/src/Model/GroundWaterTransport/gwt1ist1.f90 b/src/Model/GroundWaterTransport/gwt1ist1.f90 index 3855709ea5c..16dcc77e2fc 100644 --- a/src/Model/GroundWaterTransport/gwt1ist1.f90 +++ b/src/Model/GroundWaterTransport/gwt1ist1.f90 @@ -188,9 +188,6 @@ subroutine ist_ar(this) this%cimnew(n) = this%cim(n) end do ! - ! -- add thetaim to the prsity2 accumulator in mst package - call this%mst%addto_prsity2(this%thetaim) - ! ! -- setup the immobile domain budget call budget_cr(this%budget, this%memoryPath) call this%budget%budget_df(NBDITEMS, 'MASS', 'M', bdzone=this%packName) @@ -289,10 +286,8 @@ subroutine ist_fc(this, rhs, ia, idxglo, matrix_sln) real(DP) :: vcell real(DP) :: thetaim real(DP) :: zetaim - real(DP) :: thetamfrac - real(DP) :: thetaimfrac real(DP) :: kd - real(DP) :: rhob + real(DP) :: rhobim real(DP) :: lambda1im real(DP) :: lambda2im real(DP) :: gamma1im @@ -323,13 +318,9 @@ subroutine ist_fc(this, rhs, ia, idxglo, matrix_sln) ! -- set exchange coefficient zetaim = this%zetaim(n) ! - ! -- Set thetamfrac and thetaimfrac - thetamfrac = this%mst%get_thetamfrac(n) - thetaimfrac = this%mst%get_thetaimfrac(n, this%thetaim(n)) - ! ! -- Add dual domain mass transfer contributions to rhs and hcof kd = DZERO - rhob = DZERO + rhobim = DZERO lambda1im = DZERO lambda2im = DZERO gamma1im = DZERO @@ -347,7 +338,7 @@ subroutine ist_fc(this, rhs, ia, idxglo, matrix_sln) ! -- setup sorption variables if (this%isrb > 0) then kd = this%distcoef(n) - rhob = this%bulk_density(n) + rhobim = this%bulk_density(n) if (this%idcy == 1) lambda2im = this%decay_sorbed(n) if (this%idcy == 2) then cimsrbold = this%cimold(n) * kd @@ -362,7 +353,7 @@ subroutine ist_fc(this, rhs, ia, idxglo, matrix_sln) ! ! -- calculate the terms and then get the hcof and rhs contributions call get_ddterm(thetaim, vcell, delt, swtpdt, & - thetaimfrac, rhob, kd, lambda1im, lambda2im, & + rhobim, kd, lambda1im, lambda2im, & gamma1im, gamma2im, zetaim, ddterm, f) cimold = this%cimold(n) call get_hcofrhs(ddterm, f, cimold, hhcof, rrhs) @@ -400,10 +391,8 @@ subroutine ist_cq(this, x, flowja, iadv) real(DP) :: vcell real(DP) :: thetaim real(DP) :: zetaim - real(DP) :: thetamfrac - real(DP) :: thetaimfrac real(DP) :: kd - real(DP) :: rhob + real(DP) :: rhobim real(DP) :: lambda1im real(DP) :: lambda2im real(DP) :: gamma1im @@ -436,16 +425,12 @@ subroutine ist_cq(this, x, flowja, iadv) ! -- set exchange coefficient zetaim = this%zetaim(n) ! - ! -- Set thetamfrac and thetaimfrac - thetamfrac = this%mst%get_thetamfrac(n) - thetaimfrac = this%mst%get_thetaimfrac(n, this%thetaim(n)) - ! ! -- Calculate exchange with immobile domain rate = DZERO hhcof = DZERO rrhs = DZERO kd = DZERO - rhob = DZERO + rhobim = DZERO lambda1im = DZERO lambda2im = DZERO gamma1im = DZERO @@ -457,7 +442,7 @@ subroutine ist_cq(this, x, flowja, iadv) end if if (this%isrb > 0) then kd = this%distcoef(n) - rhob = this%bulk_density(n) + rhobim = this%bulk_density(n) if (this%idcy == 1) lambda2im = this%decay_sorbed(n) if (this%idcy == 2) then cimsrbold = this%cimold(n) * kd @@ -471,7 +456,7 @@ subroutine ist_cq(this, x, flowja, iadv) ! ! -- calculate the terms and then get the hcof and rhs contributions call get_ddterm(thetaim, vcell, delt, swtpdt, & - thetaimfrac, rhob, kd, lambda1im, lambda2im, & + rhobim, kd, lambda1im, lambda2im, & gamma1im, gamma2im, zetaim, ddterm, f) cimold = this%cimold(n) call get_hcofrhs(ddterm, f, cimold, hhcof, rrhs) @@ -1137,15 +1122,14 @@ end subroutine read_data !! !< subroutine get_ddterm(thetaim, vcell, delt, swtpdt, & - thetaimfrac, rhob, kd, lambda1im, lambda2im, & + rhobim, kd, lambda1im, lambda2im, & gamma1im, gamma2im, zetaim, ddterm, f) ! -- dummy real(DP), intent(in) :: thetaim !< immobile domain porosity real(DP), intent(in) :: vcell !< volume of cell real(DP), intent(in) :: delt !< length of time step real(DP), intent(in) :: swtpdt !< cell saturation at end of time step - real(DP), intent(in) :: thetaimfrac !< fraction of total porosity this is immobile - real(DP), intent(in) :: rhob !< bulk density + real(DP), intent(in) :: rhobim !< bulk density for the immobile domain (fim * rhob) real(DP), intent(in) :: kd !< distribution coefficient for linear isotherm real(DP), intent(in) :: lambda1im !< first-order decay rate in aqueous phase real(DP), intent(in) :: lambda2im !< first-order decay rate in sorbed phase @@ -1164,12 +1148,12 @@ subroutine get_ddterm(thetaim, vcell, delt, swtpdt, & ! coefficients in equation 7-4 of the GWT model report ddterm(1) = thetaim * vcell * tled ddterm(2) = thetaim * vcell * tled - ddterm(3) = thetaimfrac * rhob * vcell * kd * tled - ddterm(4) = thetaimfrac * rhob * vcell * kd * tled + ddterm(3) = rhobim * vcell * kd * tled + ddterm(4) = rhobim * vcell * kd * tled ddterm(5) = thetaim * lambda1im * vcell - ddterm(6) = thetaimfrac * lambda2im * rhob * kd * vcell + ddterm(6) = lambda2im * rhobim * kd * vcell ddterm(7) = thetaim * gamma1im * vcell - ddterm(8) = thetaimfrac * gamma2im * rhob * vcell + ddterm(8) = gamma2im * rhobim * vcell ddterm(9) = vcell * swtpdt * zetaim ! ! -- calculate denominator term, f diff --git a/src/Model/GroundWaterTransport/gwt1mst1.f90 b/src/Model/GroundWaterTransport/gwt1mst1.f90 index dc328cd5273..f086230d19d 100644 --- a/src/Model/GroundWaterTransport/gwt1mst1.f90 +++ b/src/Model/GroundWaterTransport/gwt1mst1.f90 @@ -38,7 +38,6 @@ module GwtMstModule ! ! -- storage real(DP), dimension(:), pointer, contiguous :: porosity => null() !< porosity - real(DP), dimension(:), pointer, contiguous :: prsity2 => null() !< sum of immobile porosity real(DP), dimension(:), pointer, contiguous :: ratesto => null() !< rate of mobile storage ! ! -- decay @@ -51,7 +50,7 @@ module GwtMstModule ! ! -- sorption integer(I4B), pointer :: isrb => null() !< sorption active flag (0:off, 1:linear, 2:freundlich, 3:langmuir) - real(DP), dimension(:), pointer, contiguous :: bulk_density => null() !< bulk density + real(DP), dimension(:), pointer, contiguous :: bulk_density => null() !< bulk density of mobile domain; mass of mobile domain solid per aquifer volume real(DP), dimension(:), pointer, contiguous :: distcoef => null() !< kd distribution coefficient real(DP), dimension(:), pointer, contiguous :: sp2 => null() !< second sorption parameter real(DP), dimension(:), pointer, contiguous :: ratesrb => null() !< rate of sorption @@ -78,9 +77,6 @@ module GwtMstModule procedure :: mst_ot_flow procedure :: mst_da procedure :: allocate_scalars - procedure :: addto_prsity2 - procedure :: get_thetamfrac - procedure :: get_thetaimfrac procedure, private :: allocate_arrays procedure, private :: read_options procedure, private :: read_data @@ -341,8 +337,7 @@ subroutine mst_fc_srb(this, nodes, cold, nja, matrix_sln, idxglo, rhs, & real(DP) :: vcell real(DP) :: const1 real(DP) :: const2 - real(DP) :: thetamfrac - real(DP) :: rhob + real(DP) :: rhobm ! ! -- set variables tled = DONE / delt @@ -358,12 +353,11 @@ subroutine mst_fc_srb(this, nodes, cold, nja, matrix_sln, idxglo, rhs, & swtpdt = this%fmi%gwfsat(n) swt = this%fmi%gwfsatold(n, delt) idiag = this%dis%con%ia(n) - thetamfrac = this%get_thetamfrac(n) const1 = this%distcoef(n) const2 = 0. if (this%isrb > 1) const2 = this%sp2(n) - rhob = this%bulk_density(n) - call mst_srb_term(this%isrb, thetamfrac, rhob, vcell, tled, cnew(n), & + rhobm = this%bulk_density(n) + call mst_srb_term(this%isrb, rhobm, vcell, tled, cnew(n), & cold(n), swtpdt, swt, const1, const2, & hcofval=hhcof, rhsval=rrhs) ! @@ -382,13 +376,12 @@ end subroutine mst_fc_srb !! Subroutine to calculate sorption terms !! !< - subroutine mst_srb_term(isrb, thetamfrac, rhob, vcell, tled, cnew, cold, & + subroutine mst_srb_term(isrb, rhobm, vcell, tled, cnew, cold, & swnew, swold, const1, const2, rate, hcofval, rhsval) ! -- modules ! -- dummy integer(I4B), intent(in) :: isrb !< sorption flag 1, 2, 3 are linear, freundlich, and langmuir - real(DP), intent(in) :: thetamfrac !< fraction of total porosity that is mobile - real(DP), intent(in) :: rhob !< bulk density + real(DP), intent(in) :: rhobm !< bulk density of mobile domain (fm * rhob) real(DP), intent(in) :: vcell !< volume of cell real(DP), intent(in) :: tled !< one over time step length real(DP), intent(in) :: cnew !< concentration at end of this time step @@ -412,7 +405,7 @@ subroutine mst_srb_term(isrb, thetamfrac, rhob, vcell, tled, cnew, cold, & ! -- Calculate based on type of sorption if (isrb == 1) then ! -- linear - term = -thetamfrac * rhob * vcell * tled * const1 + term = -rhobm * vcell * tled * const1 if (present(hcofval)) hcofval = term * swnew if (present(rhsval)) rhsval = term * swold * cold if (present(rate)) rate = term * swnew * cnew - term * swold * cold @@ -435,7 +428,7 @@ subroutine mst_srb_term(isrb, thetamfrac, rhob, vcell, tled, cnew, cold, & end if ! ! -- calculate hcof, rhs, and rate for freundlich and langmuir - term = -thetamfrac * rhob * vcell * tled + term = -rhobm * vcell * tled cbaravg = (cbarold + cbarnew) * DHALF swavg = (swnew + swold) * DHALF if (present(hcofval)) then @@ -477,7 +470,7 @@ subroutine mst_fc_dcy_srb(this, nodes, cold, nja, matrix_sln, idxglo, & real(DP) :: vcell real(DP) :: swnew real(DP) :: distcoef - real(DP) :: thetamfrac + real(DP) :: rhobm real(DP) :: term real(DP) :: csrb real(DP) :: decay_rate @@ -497,9 +490,8 @@ subroutine mst_fc_dcy_srb(this, nodes, cold, nja, matrix_sln, idxglo, & swnew = this%fmi%gwfsat(n) distcoef = this%distcoef(n) idiag = this%dis%con%ia(n) - thetamfrac = this%get_thetamfrac(n) - term = this%decay_sorbed(n) * thetamfrac * this%bulk_density(n) * & - swnew * vcell + rhobm = this%bulk_density(n) + term = this%decay_sorbed(n) * rhobm * swnew * vcell ! ! -- add sorbed mass decay rate terms to accumulators if (this%idcy == 1) then @@ -541,7 +533,7 @@ subroutine mst_fc_dcy_srb(this, nodes, cold, nja, matrix_sln, idxglo, & this%decayslast(n), & kiter, csrbold, csrbnew, delt) this%decayslast(n) = decay_rate - rrhs = decay_rate * thetamfrac * this%bulk_density(n) * swnew * vcell + rrhs = decay_rate * rhobm * swnew * vcell end if end if @@ -724,10 +716,9 @@ subroutine mst_cq_srb(this, nodes, cnew, cold, flowja) real(DP) :: tled real(DP) :: swt, swtpdt real(DP) :: vcell - real(DP) :: rhob + real(DP) :: rhobm real(DP) :: const1 real(DP) :: const2 - real(DP) :: thetamfrac ! ! -- initialize tled = DONE / delt @@ -745,12 +736,11 @@ subroutine mst_cq_srb(this, nodes, cnew, cold, flowja) vcell = this%dis%area(n) * (this%dis%top(n) - this%dis%bot(n)) swtpdt = this%fmi%gwfsat(n) swt = this%fmi%gwfsatold(n, delt) - thetamfrac = this%get_thetamfrac(n) - rhob = this%bulk_density(n) + rhobm = this%bulk_density(n) const1 = this%distcoef(n) const2 = 0. if (this%isrb > 1) const2 = this%sp2(n) - call mst_srb_term(this%isrb, thetamfrac, rhob, vcell, tled, cnew(n), & + call mst_srb_term(this%isrb, rhobm, vcell, tled, cnew(n), & cold(n), swtpdt, swt, const1, const2, & rate=rate) this%ratesrb(n) = rate @@ -785,7 +775,7 @@ subroutine mst_cq_dcy_srb(this, nodes, cnew, cold, flowja) real(DP) :: vcell real(DP) :: swnew real(DP) :: distcoef - real(DP) :: thetamfrac + real(DP) :: rhobm real(DP) :: term real(DP) :: csrb real(DP) :: csrbnew @@ -808,9 +798,8 @@ subroutine mst_cq_dcy_srb(this, nodes, cnew, cold, flowja) vcell = this%dis%area(n) * (this%dis%top(n) - this%dis%bot(n)) swnew = this%fmi%gwfsat(n) distcoef = this%distcoef(n) - thetamfrac = this%get_thetamfrac(n) - term = this%decay_sorbed(n) * thetamfrac * this%bulk_density(n) * & - swnew * vcell + rhobm = this%bulk_density(n) + term = this%decay_sorbed(n) * rhobm * swnew * vcell ! ! -- add sorbed mass decay rate terms to accumulators if (this%idcy == 1) then @@ -849,7 +838,7 @@ subroutine mst_cq_dcy_srb(this, nodes, cnew, cold, flowja) decay_rate = get_zero_order_decay(this%decay_sorbed(n), & this%decayslast(n), & 0, csrbold, csrbnew, delt) - rrhs = decay_rate * thetamfrac * this%bulk_density(n) * swnew * vcell + rrhs = decay_rate * rhobm * swnew * vcell end if end if ! @@ -986,7 +975,6 @@ subroutine mst_da(this) ! -- Deallocate arrays if package was active if (this%inunit > 0) then call mem_deallocate(this%porosity) - call mem_deallocate(this%prsity2) call mem_deallocate(this%ratesto) call mem_deallocate(this%idcy) call mem_deallocate(this%decay) @@ -1058,7 +1046,6 @@ subroutine allocate_arrays(this, nodes) ! -- Allocate ! -- sto call mem_allocate(this%porosity, nodes, 'POROSITY', this%memoryPath) - call mem_allocate(this%prsity2, nodes, 'PRSITY2', this%memoryPath) call mem_allocate(this%ratesto, nodes, 'RATESTO', this%memoryPath) ! ! -- dcy @@ -1103,7 +1090,6 @@ subroutine allocate_arrays(this, nodes) ! -- Initialize do n = 1, nodes this%porosity(n) = DZERO - this%prsity2(n) = DZERO this%ratesto(n) = DZERO end do do n = 1, size(this%decay) @@ -1400,72 +1386,6 @@ subroutine read_data(this) return end subroutine read_data - !> @ brief Add porosity values to prsity2 - !! - !! Method to add immobile domain porosities, which are stored as a - !! cumulative value in prsity2. - !! - !< - subroutine addto_prsity2(this, thetaim) - ! -- modules - ! -- dummy - class(GwtMstType) :: this !< GwtMstType object - real(DP), dimension(:), intent(in) :: thetaim !< immobile domain porosity that contributes to total porosity - ! -- local - integer(I4B) :: n - ! - ! -- Add to prsity2 - do n = 1, this%dis%nodes - if (this%ibound(n) == 0) cycle - this%prsity2(n) = this%prsity2(n) + thetaim(n) - end do - ! - ! -- Return - return - end subroutine addto_prsity2 - - !> @ brief Return mobile porosity fraction - !! - !! Calculate and return the fraction of the total porosity that is mobile - !! - !< - function get_thetamfrac(this, node) result(thetamfrac) - ! -- modules - ! -- dummy - class(GwtMstType) :: this !< GwtMstType object - integer(I4B), intent(in) :: node !< node number - ! -- return - real(DP) :: thetamfrac - ! - thetamfrac = this%porosity(node) / & - (this%porosity(node) + this%prsity2(node)) - ! - ! -- Return - return - end function get_thetamfrac - - !> @ brief Return immobile porosity fraction - !! - !! Pass in an immobile domain porosity and calculate the fraction - !! of the total porosity that is immobile - !! - !< - function get_thetaimfrac(this, node, thetaim) result(thetaimfrac) - ! -- modules - ! -- dummy - class(GwtMstType) :: this !< GwtMstType object - integer(I4B), intent(in) :: node !< node number - real(DP), intent(in) :: thetaim !< immobile domain porosity - ! -- return - real(DP) :: thetaimfrac - ! - thetaimfrac = thetaim / & - (this%porosity(node) + this%prsity2(node)) - ! - ! -- Return - return - end function get_thetaimfrac - !> @ brief Calculate sorption concentration using Freundlich !! !! Function to calculate sorption concentration using Freundlich From 3427970d9373862f90b9b86f62cecc8d4f9ee234 Mon Sep 17 00:00:00 2001 From: mjr-deltares <45555666+mjr-deltares@users.noreply.github.com> Date: Fri, 27 Jan 2023 08:31:10 +0100 Subject: [PATCH 025/123] fix(obs): convert obs boundname to upper case for exchanges (#1140) --- src/Exchange/GwfGwfExchange.f90 | 2 +- src/Exchange/GwtGwtExchange.f90 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Exchange/GwfGwfExchange.f90 b/src/Exchange/GwfGwfExchange.f90 index fb1627ebaf5..f9ab9d9c46d 100644 --- a/src/Exchange/GwfGwfExchange.f90 +++ b/src/Exchange/GwfGwfExchange.f90 @@ -2087,7 +2087,7 @@ subroutine gwf_gwf_process_obsID(obsrv, dis, inunitobs, iout) strng = obsrv%IDstring icol = 1 ! -- get exchange index - call urword(strng, icol, istart, istop, 0, n, r, iout, inunitobs) + call urword(strng, icol, istart, istop, 1, n, r, iout, inunitobs) read (strng(istart:istop), '(i10)', iostat=istat) iexg if (istat == 0) then obsrv%intPak1 = iexg diff --git a/src/Exchange/GwtGwtExchange.f90 b/src/Exchange/GwtGwtExchange.f90 index 1c388b2ddae..c31a6d8a1e4 100644 --- a/src/Exchange/GwtGwtExchange.f90 +++ b/src/Exchange/GwtGwtExchange.f90 @@ -1331,7 +1331,7 @@ subroutine gwt_gwt_process_obsID(obsrv, dis, inunitobs, iout) strng = obsrv%IDstring icol = 1 ! -- get exchange index - call urword(strng, icol, istart, istop, 0, n, r, iout, inunitobs) + call urword(strng, icol, istart, istop, 1, n, r, iout, inunitobs) read (strng(istart:istop), '(i10)', iostat=istat) iexg if (istat == 0) then obsrv%intPak1 = iexg From 1d391378838a89e71fbdb5a698531ff275e4352c Mon Sep 17 00:00:00 2001 From: mjr-deltares <45555666+mjr-deltares@users.noreply.github.com> Date: Fri, 27 Jan 2023 08:31:39 +0100 Subject: [PATCH 026/123] fix(api): Non convergence is no reason to crash the API (#1141) * Non convergence is no reason to crash the API * fprettify --- srcbmi/mf6xmi.f90 | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/srcbmi/mf6xmi.f90 b/srcbmi/mf6xmi.f90 index 75560d9e42c..3cff0f3ff78 100644 --- a/srcbmi/mf6xmi.f90 +++ b/srcbmi/mf6xmi.f90 @@ -296,14 +296,14 @@ function xmi_finalize_solve(subcomponent_idx) result(bmi_status) & call ns%finalizeSolve(iterationCounter, hasConverged, 0) ! check convergence on solution - if (hasConverged == 1) then - bmi_status = BMI_SUCCESS - else + if (.not. hasConverged == 1) then write (bmi_last_error, fmt_fail_cvg_sol) subcomponent_idx call report_bmi_error(bmi_last_error) - bmi_status = BMI_FAILURE end if + ! non-convergence is no reason to crash the API: + bmi_status = BMI_SUCCESS + ! clear this for safety deallocate (iterationCounter) From 173eebfa2e33c0fd2c0a00e3f3c5bfee6ef9ac72 Mon Sep 17 00:00:00 2001 From: spaulins-usgs Date: Fri, 10 Feb 2023 12:46:11 -0800 Subject: [PATCH 027/123] feat(solver types): support for multiple solver types added to flopy (#1143) --- doc/mf6io/mf6ivar/dfn/sln-ims.dfn | 1 + doc/mf6io/mf6ivar/readme.md | 17 ++++++++++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/doc/mf6io/mf6ivar/dfn/sln-ims.dfn b/doc/mf6io/mf6ivar/dfn/sln-ims.dfn index d5e32adc3c6..65e81b65461 100644 --- a/doc/mf6io/mf6ivar/dfn/sln-ims.dfn +++ b/doc/mf6io/mf6ivar/dfn/sln-ims.dfn @@ -1,4 +1,5 @@ # --------------------- sln ims options --------------------- +# flopy solution_package ims * block options name print_option diff --git a/doc/mf6io/mf6ivar/readme.md b/doc/mf6io/mf6ivar/readme.md index 35731c2cb92..479b58c9ee5 100644 --- a/doc/mf6io/mf6ivar/readme.md +++ b/doc/mf6io/mf6ivar/readme.md @@ -76,12 +76,27 @@ An example below is the second line in the ts subpackage dfn: There are three possible types (or combination of them) that can be used for "parent package type", MFPackage, MFModel, and MFSimulation. If a package supports multiple types of parents (for example, it can be either in the model namefile or in a package, like the obs package), include all the types supported, seperating each type with a / (MFPackage/MFModel). -## Creating Definition Files for a New Model +## Creating Definition Files for New Models To create a new type of model choose a unique three letter model abbreviation ("gwf", "gwt", ...). Create a name file dfn with the naming convention \-nam.dfn. The name file must have only an options and packages block (see gwf-nam.dfn as an example). Create a new dfn file for each of the packages in your new model, following the naming convention described above. When your model is ready for release copy the dfn file to the flopy distribution in the flopy/mf6/data/dfn folder, run createpackages.py, and check in your new dfn files, the package classes, and updated init.py that createpackages.py created. +## Creating Definition Files for New Solvers + +Create a solver definition file as you would any package definition file. When you are done add a commented line at the top of the definition file to let FloPy know that this package is a solver (solution package type). The line should look like this: + +\# solution_package + +For example, the following would tell FloPy the IMS package supports the gwf6 and gwt6 model types: + +\# flopy solution_package ims gwf6 gwt6 + +If a "*" is used instead of the list of model abbreviations, the solver is assumed to support all model types. For example, the following would tell FloPy that the IMS package supports all models: + +\# flopy solution_package ims * + + # Simple Definition File Example This example shows how to construct an options block with a couple of optional keywords. From 2361f32a3e08aae98c6e8603a10a700c883802e7 Mon Sep 17 00:00:00 2001 From: langevin-usgs Date: Tue, 14 Feb 2023 16:11:45 -0600 Subject: [PATCH 028/123] fix(NPF): negative icelltype without THICKSTRT produced unexpected results (#1147) * The ICELLTYPE input variable in the Node Property Flow (NPF) Package behaves differently depending on whether or not the THICKSTRT option is specified by the user. In some cases, the program would give unexpected results if a negative value was specified for ICELLTYPE and the THICKSTRT option was not active. For example, the Horizontal Flow Barrier (HFB) Package did not work property when negative values for ICELLTYPE were specified by the user, but the THICKSTRT option was not activated. The program was modified so that negative ICELLTYPE values provided by the user are automatically reassigned a value of one when the user does not activate the THICKSTRT option. This is the intended behavior and is consistent with the input and output guide. * close #1146 --- autotest/test_gwf_npf_thickstrt.py | 216 +++++++++++++++++++++++++ doc/ReleaseNotes/v6.5.0.tex | 8 +- doc/mf6io/mf6ivar/dfn/gwf-npf.dfn | 4 +- doc/mf6io/mf6ivar/tex/gwf-npf-desc.tex | 4 +- src/Model/GroundWaterFlow/gwf3npf8.f90 | 10 ++ 5 files changed, 234 insertions(+), 8 deletions(-) create mode 100644 autotest/test_gwf_npf_thickstrt.py diff --git a/autotest/test_gwf_npf_thickstrt.py b/autotest/test_gwf_npf_thickstrt.py new file mode 100644 index 00000000000..be92018ad1d --- /dev/null +++ b/autotest/test_gwf_npf_thickstrt.py @@ -0,0 +1,216 @@ +import os + +import flopy +import numpy as np +import pytest +from framework import TestFramework +from simulation import TestSimulation + +ex = [ + "gwf_npf_thickstrt01", # case 01 -- icelltype=0 + "gwf_npf_thickstrt02", # case 02 -- icelltype=0, using thickstrt, but it has no effect + "gwf_npf_thickstrt03", # case 03 -- icelltype=-1, using thickstrt and strt = 5. + "gwf_npf_thickstrt04", # case 04 -- icelltype=1, no thickstrt and strt = 5. + "gwf_npf_thickstrt05", # case 05 -- icelltype=-1, no thickstrt and strt = 5. + "gwf_npf_thickstrt06", # case 06 -- icelltype=0, no thickstrt, has hfb + "gwf_npf_thickstrt07", # case 07 -- icelltype=-1, using thickstrt, has hfb + "gwf_npf_thickstrt08", # case 08 -- icelltype=1, no thickstrt, has hfb + "gwf_npf_thickstrt09", # case 09 -- icelltype=-1, no thickstrt, has hfb +] +thickstrt = [False, True, True, False, False, False, True, False, False] +icelltype = [0, 0, -1, 1, -1, 0, -1, 1, -1] +hfb_on = [False, False, False, False, False, True, True, True, True] + +def build_model(idx, dir): + nlay, nrow, ncol = 1, 1, 6 + nper = 1 + perlen = [1.0] + nstp = [1] + tsmult = [1.0] + delr = 1.0 + delc = 1.0 + top = 10.0 + botm = [0.0] + strt = 5.0 + hk = 1.0 + + c = {0: [[(0, 0, 0), 6.0], [(0, 0, ncol - 1), 4.0]]} + + nouter, ninner = 10, 5 + hclose, rclose, relax = 1e-6, 1e-6, 1.0 + + tdis_rc = [] + for i in range(nper): + tdis_rc.append((perlen[i], nstp[i], tsmult[i])) + + name = "flow" + + # build MODFLOW 6 files + ws = dir + sim = flopy.mf6.MFSimulation( + sim_name=name, version="mf6", exe_name="mf6", sim_ws=ws + ) + # create tdis package + tdis = flopy.mf6.ModflowTdis( + sim, time_units="DAYS", nper=nper, perioddata=tdis_rc + ) + + # create gwf model + gwf = flopy.mf6.MFModel( + sim, + model_type="gwf6", + modelname=name, + model_nam_file=f"{name}.nam", + ) + gwf.name_file.save_flows = True + + # create iterative model solution and register the gwf model with it + imsgwf = flopy.mf6.ModflowIms( + sim, + print_option="SUMMARY", + outer_dvclose=hclose, + outer_maximum=nouter, + under_relaxation="NONE", + inner_maximum=ninner, + inner_dvclose=hclose, + rcloserecord=rclose, + linear_acceleration="CG", + scaling_method="NONE", + reordering_method="NONE", + relaxation_factor=relax, + filename=f"{name}.ims", + ) + sim.register_ims_package(imsgwf, [gwf.name]) + + dis = flopy.mf6.ModflowGwfdis( + gwf, + nlay=nlay, + nrow=nrow, + ncol=ncol, + delr=delr, + delc=delc, + top=top, + botm=botm, + idomain=np.ones((nlay, nrow, ncol), dtype=int), + ) + + # initial conditions + ic = flopy.mf6.ModflowGwfic(gwf, strt=strt) + + # npf + thickstrt_option = thickstrt[idx] + ict = icelltype[idx] + npf = flopy.mf6.ModflowGwfnpf( + gwf, + thickstrt=thickstrt_option, + icelltype=ict, + k=hk, + k33=hk + ) + + if hfb_on[idx]: + hfb = flopy.mf6.ModflowGwfhfb( + gwf, + print_input=True, + maxhfb=1, + stress_period_data=[((0, 0, 2), (0, 0, 3), 1.e-4)], + ) + + # chd files + chd = flopy.mf6.ModflowGwfchd( + gwf, + maxbound=len(c), + stress_period_data=c, + save_flows=False, + print_flows=True, + pname="CHD-1", + ) + + # output control + oc = flopy.mf6.ModflowGwfoc( + gwf, + budget_filerecord=f"{name}.cbc", + head_filerecord=f"{name}.hds", + headprintrecord=[("COLUMNS", 10, "WIDTH", 15, "DIGITS", 6, "GENERAL")], + saverecord=[("HEAD", "LAST"), ("BUDGET", "LAST")], + printrecord=[("HEAD", "LAST"), ("BUDGET", "LAST")], + ) + + return sim, None + + +def eval_model(sim): + print("evaluating model...") + + name = "flow" + + fpth = os.path.join(sim.simpath, f"{name}.hds") + hobj = flopy.utils.HeadFile(fpth, precision="double") + head = hobj.get_data().flatten() + + # This is the answer to this problem. + answer_linear = np.linspace(6, 4, 6) + + answer_water_table = (6.0, 5.65716, 5.29206, 4.89969, 4.47276, 4.0) + answer_water_table = np.array(answer_water_table) + + answer_confined_hfb = (6.0, 5.9998, 5.9996, 4.0004, 4.0002, 4.0) + answer_confined_hfb = np.array(answer_confined_hfb) + + answer_confined_thickstart_hfb = (6., 5.9996004, 5.9992008, 4.0007992, 4.0003996, 4.) + answer_confined_thickstart_hfb = np.array(answer_confined_thickstart_hfb) + + answer_unconfined_hfb = (6., 5.99983342, 5.99966683, 4.00049971, 4.00024986, 4.) + answer_unconfined_hfb = np.array(answer_unconfined_hfb) + + answer_dict = { + 0: answer_linear, + 1: answer_linear, + 2: answer_linear, + 3: answer_water_table, + 4: answer_water_table, + 5: answer_confined_hfb, + 6: answer_confined_thickstart_hfb, + 7: answer_unconfined_hfb, + 8: answer_unconfined_hfb, + } + + hres = answer_dict[sim.idxsim] + assert np.allclose( + hres, head + ), "simulated head do not match with known solution." + + fpth = os.path.join(sim.simpath, f"{name}.cbc") + cobj = flopy.utils.CellBudgetFile(fpth, precision="double") + q_simulated_inflow = cobj.get_data(idx=1)[0]['q'][0] + q_answer_dict = { + 0: 4., + 1: 4., + 2: 2., + 3: 1.9965396769631871, + 4: 1.9965396769631871, + 5: 1.9990E-03, + 6: 1.9980E-03, + 7: 9.9949E-04, + 8: 9.9949E-04, + } + q_answer = q_answer_dict[sim.idxsim] + assert np.allclose( + q_answer, q_simulated_inflow + ), "simulated flow does not match with known solution." + + +@pytest.mark.parametrize( + "idx, name", + list(enumerate(ex)), +) +def test_mf6model(idx, name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, idx, ws) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_model, idxsim=idx + ), + ws, + ) diff --git a/doc/ReleaseNotes/v6.5.0.tex b/doc/ReleaseNotes/v6.5.0.tex index 18fc32de5d3..d5c3717f458 100644 --- a/doc/ReleaseNotes/v6.5.0.tex +++ b/doc/ReleaseNotes/v6.5.0.tex @@ -25,12 +25,12 @@ % \item xxx \end{itemize} - %\underline{INTERNAL FLOW PACKAGES} - %\begin{itemize} - % \item xxx + \underline{INTERNAL FLOW PACKAGES} + \begin{itemize} + \item The ICELLTYPE input variable in the Node Property Flow (NPF) Package behaves differently depending on whether or not the THICKSTRT option is specified by the user. In some cases, the program would give unexpected results if a negative value was specified for ICELLTYPE and the THICKSTRT option was not active. For example, the Horizontal Flow Barrier (HFB) Package did not work properly when negative values for ICELLTYPE were specified by the user, but the THICKSTRT option was not activated. The program was modified so that negative ICELLTYPE values provided by the user are automatically reassigned a value of one when the user does not activate the THICKSTRT option. This is the intended behavior and is consistent with the input and output guide. % \item xxx % \item xxx - %\end{itemize} + \end{itemize} %\underline{STRESS PACKAGES} %\begin{itemize} diff --git a/doc/mf6io/mf6ivar/dfn/gwf-npf.dfn b/doc/mf6io/mf6ivar/dfn/gwf-npf.dfn index 1c18223cc5a..311fbed7065 100644 --- a/doc/mf6io/mf6ivar/dfn/gwf-npf.dfn +++ b/doc/mf6io/mf6ivar/dfn/gwf-npf.dfn @@ -34,7 +34,7 @@ type keyword reader urword optional true longname keyword to activate THICKSTRT option -description indicates that cells having a negative ICELLTYPE are confined, and their cell thickness for conductance calculations will be computed as STRT-BOT rather than TOP-BOT. +description indicates that cells having a negative ICELLTYPE are confined, and their cell thickness for conductance calculations will be computed as STRT-BOT rather than TOP-BOT. This option should be used with caution as it only affects conductance calculations in the NPF Package. mf6internal ithickstrt block options @@ -282,7 +282,7 @@ reader readarray layered true optional longname confined or convertible indicator -description flag for each cell that specifies how saturated thickness is treated. 0 means saturated thickness is held constant; $>$0 means saturated thickness varies with computed head when head is below the cell top; $<$0 means saturated thickness varies with computed head unless the THICKSTRT option is in effect. When THICKSTRT is in effect, a negative value of icelltype indicates that saturated thickness will be computed as STRT-BOT and held constant. +description flag for each cell that specifies how saturated thickness is treated. 0 means saturated thickness is held constant; $>$0 means saturated thickness varies with computed head when head is below the cell top; $<$0 means saturated thickness varies with computed head unless the THICKSTRT option is in effect. When THICKSTRT is in effect, a negative value for ICELLTYPE indicates that the saturated thickness value used in conductance calculations in the NPF Package will be computed as STRT-BOT and held constant. If the THICKSTRT option is not in effect, then negative values provided by the user for ICELLTYPE are automatically reassigned by the program to a value of one. default_value 0 block griddata diff --git a/doc/mf6io/mf6ivar/tex/gwf-npf-desc.tex b/doc/mf6io/mf6ivar/tex/gwf-npf-desc.tex index b63abdfd0f1..ef8953febf6 100644 --- a/doc/mf6io/mf6ivar/tex/gwf-npf-desc.tex +++ b/doc/mf6io/mf6ivar/tex/gwf-npf-desc.tex @@ -9,7 +9,7 @@ \item \texttt{alternative\_cell\_averaging}---is a text keyword to indicate that an alternative method will be used for calculating the conductance for horizontal cell connections. The text value for ALTERNATIVE\_CELL\_AVERAGING can be ``LOGARITHMIC'', ``AMT-LMK'', or ``AMT-HMK''. ``AMT-LMK'' signifies that the conductance will be calculated using arithmetic-mean thickness and logarithmic-mean hydraulic conductivity. ``AMT-HMK'' signifies that the conductance will be calculated using arithmetic-mean thickness and harmonic-mean hydraulic conductivity. If the user does not specify a value for ALTERNATIVE\_CELL\_AVERAGING, then the harmonic-mean method will be used. This option cannot be used if the XT3D option is invoked. -\item \texttt{THICKSTRT}---indicates that cells having a negative ICELLTYPE are confined, and their cell thickness for conductance calculations will be computed as STRT-BOT rather than TOP-BOT. +\item \texttt{THICKSTRT}---indicates that cells having a negative ICELLTYPE are confined, and their cell thickness for conductance calculations will be computed as STRT-BOT rather than TOP-BOT. This option should be used with caution as it only affects conductance calculations in the NPF Package. \item \texttt{VARIABLECV}---keyword to indicate that the vertical conductance will be calculated using the saturated thickness and properties of the overlying cell and the thickness and properties of the underlying cell. If the DEWATERED keyword is also specified, then the vertical conductance is calculated using only the saturated thickness and properties of the overlying cell if the head in the underlying cell is below its top. If these keywords are not specified, then the default condition is to calculate the vertical conductance at the start of the simulation using the initial head and the cell properties. The vertical conductance remains constant for the entire simulation. @@ -47,7 +47,7 @@ \item \textbf{Block: GRIDDATA} \begin{description} -\item \texttt{icelltype}---flag for each cell that specifies how saturated thickness is treated. 0 means saturated thickness is held constant; $>$0 means saturated thickness varies with computed head when head is below the cell top; $<$0 means saturated thickness varies with computed head unless the THICKSTRT option is in effect. When THICKSTRT is in effect, a negative value of icelltype indicates that saturated thickness will be computed as STRT-BOT and held constant. +\item \texttt{icelltype}---flag for each cell that specifies how saturated thickness is treated. 0 means saturated thickness is held constant; $>$0 means saturated thickness varies with computed head when head is below the cell top; $<$0 means saturated thickness varies with computed head unless the THICKSTRT option is in effect. When THICKSTRT is in effect, a negative value for ICELLTYPE indicates that the saturated thickness value used in conductance calculations in the NPF Package will be computed as STRT-BOT and held constant. If the THICKSTRT option is not in effect, then negative values provided by the user for ICELLTYPE are automatically reassigned by the program to a value of one. \item \texttt{k}---is the hydraulic conductivity. For the common case in which the user would like to specify the horizontal hydraulic conductivity and the vertical hydraulic conductivity, then K should be assigned as the horizontal hydraulic conductivity, K33 should be assigned as the vertical hydraulic conductivity, and K22 and the three rotation angles should not be specified. When more sophisticated anisotropy is required, then K corresponds to the K11 hydraulic conductivity axis. All included cells (IDOMAIN $>$ 0) must have a K value greater than zero. diff --git a/src/Model/GroundWaterFlow/gwf3npf8.f90 b/src/Model/GroundWaterFlow/gwf3npf8.f90 index 39458742db8..e0ae647985a 100644 --- a/src/Model/GroundWaterFlow/gwf3npf8.f90 +++ b/src/Model/GroundWaterFlow/gwf3npf8.f90 @@ -2082,6 +2082,16 @@ subroutine preprocess_input(this) end if end if ! + ! -- If THCKSTRT is not active, then loop through icelltype and replace + ! any negative values with 1. + if (this%ithickstrt == 0) then + do n = 1, this%dis%nodes + if (this%icelltype(n) < 0) then + this%icelltype(n) = 1 + end if + end do + end if + ! ! -- Initialize sat to zero for ibound=0 cells, unless the cell can ! rewet. Initialize sat to the saturated fraction based on strt ! if icelltype is negative and the THCKSTRT option is in effect. From 417aaaeab58258c53910fc20586f5724afe93493 Mon Sep 17 00:00:00 2001 From: w-bonelli Date: Wed, 15 Feb 2023 08:30:27 -0500 Subject: [PATCH 029/123] ci: fix release branch matching (#1145) --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cfaf5383c94..66e6fc98310 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -3,7 +3,7 @@ on: push: branches: - master - - v* + - v[0-9]+.[0-9]+.[0-9]+* release: types: - published From 9ee6f6cbcede54911aad3312c2c00a55b9a78205 Mon Sep 17 00:00:00 2001 From: w-bonelli Date: Wed, 15 Feb 2023 17:19:29 -0500 Subject: [PATCH 030/123] refactor(dist scripts): update modflow-devtools usages (#1144) --- autotest/get_exes.py | 1 - distribution/benchmark.py | 9 ++++----- distribution/build_docs.py | 10 ++++------ 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/autotest/get_exes.py b/autotest/get_exes.py index 0a95dbd3fe2..0a071d50fea 100644 --- a/autotest/get_exes.py +++ b/autotest/get_exes.py @@ -41,7 +41,6 @@ def test_rebuild_release(rebuilt_bin_path: Path): print(f"Rebuilding and installing last release to: {rebuilt_bin_path}") release = get_release(repository) assets = release["assets"] - ostag = get_ostag() asset = next( iter([a for a in assets if a["name"] == get_asset_name(a)]), None ) diff --git a/distribution/benchmark.py b/distribution/benchmark.py index 547de7a3c66..ab19213a947 100644 --- a/distribution/benchmark.py +++ b/distribution/benchmark.py @@ -10,9 +10,9 @@ from typing import List, Tuple import flopy -import pymake import pytest from modflow_devtools.build import meson_build +from modflow_devtools.download import download_and_unzip, get_latest_version from modflow_devtools.misc import get_model_paths from utils import get_project_root_path @@ -31,16 +31,15 @@ def download_previous_version(output_path: PathLike) -> Tuple[str, Path]: output_path = Path(output_path).expanduser().absolute() - version = pymake.repo_latest_version(github_repo=_github_repo, verify=_verify) + version = get_latest_version(_github_repo) url = ( f"https://github.com/{_github_repo}" + f"/releases/download/{version}/mf{version}.zip" ) - pymake.download_and_unzip( + download_and_unzip( url, - pth=str(output_path), + path=output_path, verbose=True, - verify=_verify, ) return version, output_path / f"mf{version}" diff --git a/distribution/build_docs.py b/distribution/build_docs.py index 2a6b096e45d..0a52cd5b1fb 100644 --- a/distribution/build_docs.py +++ b/distribution/build_docs.py @@ -3,12 +3,10 @@ import platform import shutil import textwrap -from _warnings import warn from datetime import datetime from os import PathLike from pathlib import Path from pprint import pprint -from shutil import which from tempfile import TemporaryDirectory from typing import List, Optional from urllib.error import HTTPError @@ -96,17 +94,17 @@ def clean_tex_files(): assert not os.path.isfile(str(pth) + ".pdf") -def download_benchmarks(output_path: PathLike, quiet: bool = True) -> Optional[Path]: +def download_benchmarks(output_path: PathLike, verbose: bool = False) -> Optional[Path]: output_path = Path(output_path).expanduser().absolute() name = "run-time-comparison" repo = "w-bonelli/modflow6" - artifacts = list_artifacts(repo, name=name, quiet=quiet) + artifacts = list_artifacts(repo, name=name, verbose=verbose) artifacts = sorted(artifacts, key=lambda a: datetime.strptime(a['created_at'], '%Y-%m-%dT%H:%M:%SZ'), reverse=True) most_recent = next(iter(artifacts), None) print(f"Found most recent benchmarks (artifact {most_recent['id']})") if most_recent: print(f"Downloading benchmarks (artifact {most_recent['id']})") - download_artifact(repo, id=most_recent['id'], path=output_path, quiet=quiet) + download_artifact(repo, id=most_recent['id'], path=output_path, verbose=verbose) print(f"Downloaded benchmarks to {output_path}") path = output_path / f"{name}.md" assert path.is_file() @@ -119,7 +117,7 @@ def download_benchmarks(output_path: PathLike, quiet: bool = True) -> Optional[P @flaky @requires_github def test_download_benchmarks(tmp_path): - path = download_benchmarks(tmp_path, quiet=False) + path = download_benchmarks(tmp_path, verbose=True) if path: assert path.name == "run-time-comparison.md" From 42625140b2e28282829f8a0b60232976d24c2ad3 Mon Sep 17 00:00:00 2001 From: w-bonelli Date: Wed, 15 Feb 2023 19:28:34 -0500 Subject: [PATCH 031/123] fix(dependencies): remove explicit xmipy dependency (#1149) --- environment.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/environment.yml b/environment.yml index 12633dc18a5..7a2294d3884 100644 --- a/environment.yml +++ b/environment.yml @@ -17,7 +17,6 @@ dependencies: - pip: - git+https://github.com/modflowpy/flopy.git - git+https://github.com/modflowpy/pymake.git - - git+https://github.com/Deltares/xmipy.git - git+https://github.com/MODFLOW-USGS/modflowapi.git - modflow-devtools - pytest From bb62e4ef8892d358d344da73447b194016587949 Mon Sep 17 00:00:00 2001 From: mjreno Date: Fri, 17 Feb 2023 15:15:12 -0500 Subject: [PATCH 032/123] refactor(ci): convert fortran lint bash script to python (#1150) Co-authored-by: mjreno --- .github/common/fortran-format-check.sh | 47 ---------- .github/common/fortran_format_check.py | 125 +++++++++++++++++++++++++ .github/workflows/ci.yml | 2 +- 3 files changed, 126 insertions(+), 48 deletions(-) delete mode 100755 .github/common/fortran-format-check.sh create mode 100644 .github/common/fortran_format_check.py diff --git a/.github/common/fortran-format-check.sh b/.github/common/fortran-format-check.sh deleted file mode 100755 index e72b4bb399b..00000000000 --- a/.github/common/fortran-format-check.sh +++ /dev/null @@ -1,47 +0,0 @@ -#! /bin/bash - -SEARCHPATHS=(src srcbmi utils/zonebudget/src) -EXCLUDEDIRS=(src/Utilities/Libraries/blas # external library blas - src/Utilities/Libraries/daglib # external library dag - src/Utilities/Libraries/rcm # external library rcm - src/Utilities/Libraries/sparsekit # external library sparsekit - src/Utilities/Libraries/sparskit2) # external library sparsekit2 -EXCLUDEFILES=(src/Utilities/InputOutput.f90) # excluded until refactored - -fformatfails=() -checkcount=0 - -for path in "${SEARCHPATHS[@]}"; do - readarray -d '' files < <(find "${path}" -type f -print0 | grep -z '\.[fF]9[05]$') - for file in "${files[@]}"; do - exclude=0 - - for d in "${EXCLUDEDIRS[@]}"; do - [[ "${d}" == $(dirname "${file}") ]] && exclude=1 && break; done - if [[ ${exclude} == 1 ]]; then continue; fi - - for f in "${EXCLUDEFILES[@]}"; do - [[ "${f}" == "${file}" ]] && exclude=1 && break; done - if [[ ${exclude} == 1 ]]; then continue; fi - - ((checkcount++)) - - if [[ ! -z $(fprettify -d -c .fprettify.yaml "${file}" 2>&1) ]]; then - fformatfails+=("${file}") - fi - done -done - -echo -e "\nFortran source files checked: ${checkcount}" -echo -e "Fortran source files failed: ${#fformatfails[@]}\n" - -if [[ ${#fformatfails[@]} > 0 ]]; then - for f in "${fformatfails[@]}"; do echo "${f}"; done - - echo -e "\nTo verify file format diff and/or warn in local environment run:" - echo -e " 'fprettify -d -c /distribution/.fprettify.yaml '\n\n" - - exit 1 -fi - -exit 0 diff --git a/.github/common/fortran_format_check.py b/.github/common/fortran_format_check.py new file mode 100644 index 00000000000..8452e5a69c6 --- /dev/null +++ b/.github/common/fortran_format_check.py @@ -0,0 +1,125 @@ +import os +import sys +import argparse +import glob +from pathlib import Path +from subprocess import run + +# MODFLOW 6 repository directories to check (relative to root) +searchpaths = ["src", "srcbmi", "utils/zonebudget/src"] + +# Exclude these directories from checks +excludedirs = [ + "src/Utilities/Libraries/blas", # external library blas + "src/Utilities/Libraries/daglib", # external library dag + "src/Utilities/Libraries/rcm", # external library rcm + "src/Utilities/Libraries/sparsekit", # external library sparsekit + "src/Utilities/Libraries/sparskit2", # external library sparsekit2 +] + +# Exclude these files from checks +excludefiles = ["src/Utilities/InputOutput.f90"] # excluded until refactored + +class FortranFormatCheck: + """ + Verify MODFLOW 6 fortran source code format + """ + + def __init__(self, root: Path, verbose: bool): + self._checkcount = 0 + self._fprettifyfails = [] + self._exclude_dirs = [] + self._exclude_files = [] + self._root = root.resolve() + self._verbose = verbose + self._entrypath = Path().cwd() + + os.chdir(self._root) + + def add_search_path(self, path: Path) -> None: + p = Path(path) + + for f in p.glob("**/*.[fF]9[05]"): + self._check_src_fprettify(f) + + def add_exclude_dirs(self, excl_dirs: list) -> None: + self._exclude_dirs += excl_dirs + + def add_exclude_files(self, excl_files: list) -> None: + self._exclude_files += excl_files + + def clear_exclude_dirs(self) -> None: + self._exclude_dirs = None + self._exclude_dirs = [] + + def clear_exclude_files(self) -> None: + self._exclude_files = None + self._exclude_files = [] + + def report(self) -> None: + print(f"\nFortran source files checked: {self._checkcount}") + print(f"Fortran source files failed: {len(self._fprettifyfails)}\n") + + for f in self._fprettifyfails: + print(f"fprettify -c .fprettify.yaml {f}") + + print() + + def exit(self) -> int: + os.chdir(self._entrypath) + + if len(self._fprettifyfails): + return 1 + + return 0 + + def _check_src_fprettify(self, path: Path) -> None: + if self._excluded(path): + return + + self._checkcount += 1 + + if self._verbose: + print(path) + + cmd = f"fprettify -d -c .fprettify.yaml {path}" + result = run(cmd, capture_output=True, shell=True) + + if result.stdout or result.stderr: + self._fprettifyfails.append(path) + + def _excluded(self, path: Path) -> bool: + for f in self._exclude_files: + if os.path.exists(f) and os.path.samefile(path, f): + return True + + for d in self._exclude_dirs: + if os.path.exists(d) and os.path.samefile(path.parents[0], d): + return True + + return False + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + "MODFLOW 6 fortran format source code verification" + ) + parser.add_argument( + "-r", "--root", help="path to MODFLOW 6 repository root directory" + ) + parser.add_argument( + "-v", "--verbose", action="store_true", help="verbose" + ) + args = parser.parse_args() + + # set MODFLOW 6 repository root + root = Path(args.root).resolve() if args.root else Path(".").resolve() + + fformat_check = FortranFormatCheck(root=root, verbose=args.verbose) + fformat_check.add_exclude_dirs(excl_dirs=excludedirs) + fformat_check.add_exclude_files(excl_files=excludefiles) + + for path in searchpaths: + fformat_check.add_search_path(path=path) + + fformat_check.report() + sys.exit(fformat_check.exit()) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e49f7c8edb7..1a9a2afe79b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,7 +34,7 @@ jobs: cache-env: true - name: Check Fortran source formatting - run: .github/common/fortran-format-check.sh + run: python .github/common/fortran_format_check.py build: name: Build (gfortran 12) From 02edccf50bd6afb491dc8d1f4f2bcae46c2c34a1 Mon Sep 17 00:00:00 2001 From: jdhughes-usgs Date: Wed, 1 Mar 2023 12:09:27 -0600 Subject: [PATCH 033/123] doc: update LaTeX documents to use latest bst file in usgslatex install (#1152) Also remove usgs.bst --- doc/ConverterGuide/converter_mf5to6.bbl | 10 +- doc/ConverterGuide/converter_mf5to6.tex | 2 +- doc/ReleaseNotes/ReleaseNotes.bbl | 36 +- doc/ReleaseNotes/bibliography.tex | 2 +- doc/SuppTechInfo/bibliography.tex | 2 +- doc/SuppTechInfo/mf6suptechinfo.bbl | 36 +- doc/mf6io/bibliography.tex | 2 +- doc/mf6io/mf6io.bbl | 64 +- doc/usgs.bst | 2080 ----------------------- 9 files changed, 77 insertions(+), 2157 deletions(-) delete mode 100644 doc/usgs.bst diff --git a/doc/ConverterGuide/converter_mf5to6.bbl b/doc/ConverterGuide/converter_mf5to6.bbl index fb37557d9ff..749117e1187 100644 --- a/doc/ConverterGuide/converter_mf5to6.bbl +++ b/doc/ConverterGuide/converter_mf5to6.bbl @@ -1,8 +1,8 @@ \begin{thebibliography}{3} \providecommand{\natexlab}[1]{#1} \expandafter\ifx\csname urlstyle\endcsname\relax - \providecommand{\doi}[1]{doi:\discretionary{}{}{}#1}\else - \providecommand{\doi}{doi:\discretionary{}{}{}\begingroup + \providecommand{\doiagency}[1]{doi:\discretionary{}{}{}#1}\else + \providecommand{\doiagency}{doi:\discretionary{}{}{}\begingroup \urlstyle{rm}\Url}\fi \bibitem[{Harbaugh(2005)}]{modflow2005} @@ -20,9 +20,9 @@ Mehl, S.W., and Hill, M.C., 2007, {MODFLOW-2005}, the U.S. Geological Survey \bibitem[{Niswonger and others(2011)Niswonger, Panday, and Ibaraki}]{modflownwt} -Niswonger, R.G., Panday, Sorab, and Ibaraki, Motomu, 2011, MODFLOW-NWT, A - Newton formulation for MODFLOW-2005: {U.S. Geological Survey Techniques and - Methods, book 6, chap. A37, 44 p.}, accessed June 27, 2017, at +Niswonger, R.G., Panday, S., and Ibaraki, M., 2011, MODFLOW-NWT, A Newton + formulation for MODFLOW-2005: {U.S. Geological Survey Techniques and Methods, + book 6, chap. A37, 44 p.}, accessed June 27, 2017, at \url{https://pubs.er.usgs.gov/publication/tm6A37}. \end{thebibliography} diff --git a/doc/ConverterGuide/converter_mf5to6.tex b/doc/ConverterGuide/converter_mf5to6.tex index e96481fb844..6e6aa1a3b05 100644 --- a/doc/ConverterGuide/converter_mf5to6.tex +++ b/doc/ConverterGuide/converter_mf5to6.tex @@ -300,7 +300,7 @@ \section{History} \REFSECTION %\SECTION{References Cited} \bibliography{../MODFLOW6References} -\bibliographystyle{../usgs.bst} +\bibliographystyle{usgs.bst} \justifying \vspace*{\fill} diff --git a/doc/ReleaseNotes/ReleaseNotes.bbl b/doc/ReleaseNotes/ReleaseNotes.bbl index c54254b55e2..d262d6abb29 100644 --- a/doc/ReleaseNotes/ReleaseNotes.bbl +++ b/doc/ReleaseNotes/ReleaseNotes.bbl @@ -1,16 +1,16 @@ \begin{thebibliography}{13} \providecommand{\natexlab}[1]{#1} \expandafter\ifx\csname urlstyle\endcsname\relax - \providecommand{\doi}[1]{doi:\discretionary{}{}{}#1}\else - \providecommand{\doi}{doi:\discretionary{}{}{}\begingroup + \providecommand{\doiagency}[1]{doi:\discretionary{}{}{}#1}\else + \providecommand{\doiagency}{doi:\discretionary{}{}{}\begingroup \urlstyle{rm}\Url}\fi \bibitem[{Hoffmann and others(2003)Hoffmann, Leake, Galloway, and Wilson}]{hoffmann2003modflow} -Hoffmann, J{\"o}rn, Leake, S.A., Galloway, D.L., and Wilson, A.M., 2003, - MODFLOW-2000 Ground-Water Model---User Guide to the Subsidence and - Aquifer-System Compaction (SUB) Package: {U.S. Geological Survey Open-File - Report 03--233, 44 p.}, accessed June 27, 2017, at +Hoffmann, J., Leake, S.A., Galloway, D.L., and Wilson, A.M., 2003, MODFLOW-2000 + Ground-Water Model---User Guide to the Subsidence and Aquifer-System + Compaction (SUB) Package: {U.S. Geological Survey Open-File Report 03--233, + 44 p.}, accessed June 27, 2017, at \url{https://pubs.usgs.gov/of/2003/ofr03-233/}. \bibitem[{Hughes and others(2017)Hughes, Langevin, and @@ -24,7 +24,7 @@ Hughes, J.D., Langevin, C.D., and Banta, E.R., 2017, Documentation for the Hughes, J.D., Russcher, M.J., Langevin, C.D., Morway, E.D., and McDonald, R.R., 2022{\natexlab{a}}, The {MODFLOW Application Programming Interface} for simulation control and software interoperability: Environmental Modelling \& - Software, v. 148, 105257, + Software, v. 148, article 105257, \url{https://doi.org/10.1016/j.envsoft.2021.105257}. \bibitem[{Hughes and others(2022{\natexlab{b}})Hughes, Leake, Galloway, and @@ -37,19 +37,19 @@ Hughes, J.D., Leake, S.A., Galloway, D.L., and White, J.T., 2022{\natexlab{b}}, \bibitem[{Langevin and others(2017)Langevin, Hughes, Provost, Banta, Niswonger, and Panday}]{modflow6gwf} Langevin, C.D., Hughes, J.D., Provost, A.M., Banta, E.R., Niswonger, R.G., and - Panday, Sorab, 2017, Documentation for the MODFLOW 6 Groundwater Flow (GWF) + Panday, S., 2017, Documentation for the MODFLOW 6 Groundwater Flow (GWF) Model: {U.S. Geological Survey Techniques and Methods, book 6, chap. A55, 197 p.}, \url{https://doi.org/10.3133/tm6A55}. \bibitem[{Langevin and others(2020)Langevin, Panday, and Provost}]{langevin2020hydraulic} -Langevin, C.D., Panday, Sorab, and Provost, A.M., 2020, Hydraulic-head - formulation for density-dependent flow and transport: Groundwater, v.~58, - no.~3, p.~349--362. +Langevin, C.D., Panday, S., and Provost, A.M., 2020, Hydraulic-head formulation + for density-dependent flow and transport: Groundwater, v.~58, no.~3, + p.~349--362. \bibitem[{Langevin and others(2022)Langevin, Provost, Panday, and Hughes}]{modflow6gwt} -Langevin, C.D., Provost, A.M., Panday, Sorab, and Hughes, J.D., 2022, +Langevin, C.D., Provost, A.M., Panday, S., and Hughes, J.D., 2022, Documentation for the MODFLOW 6 Groundwater Transport (GWT) Model: {U.S. Geological Survey Techniques and Methods, book 6, chap. A61, 56 p.}, \url{https://doi.org/10.3133/tm6A61}. @@ -69,17 +69,17 @@ Morway, E.D., Langevin, C.D., and Hughes, J.D., 2021, Use of the {MODFLOW 6} \bibitem[{Panday and others(2013)Panday, Langevin, Niswonger, Ibaraki, and Hughes}]{modflowusg} -Panday, Sorab, Langevin, C.D., Niswonger, R.G., Ibaraki, Motomu, and Hughes, - J.D., 2013, MODFLOW-USG version 1---An unstructured grid version of MODFLOW - for simulating groundwater flow and tightly coupled processes using a control +Panday, S., Langevin, C.D., Niswonger, R.G., Ibaraki, M., and Hughes, J.D., + 2013, MODFLOW-USG version 1---An unstructured grid version of MODFLOW for + simulating groundwater flow and tightly coupled processes using a control volume finite-difference formulation: {U.S. Geological Survey Techniques and Methods, book 6, chap. A45, 66 p.}, accessed June 27, 2017, at \url{https://pubs.usgs.gov/tm/06/a45/}. \bibitem[{Peckham and others(2013)Peckham, Hutton, and Norris}]{PECKHAM20133} -Peckham, S.D., Hutton, E.W., and Norris, Boyana, 2013, A component-based - approach to integrated modeling in the geosciences: The design of {CSDMS}: - Computers \& Geosciences, v.~53, p.~3 -- 12, accessed March 24, 2020, at +Peckham, S.D., Hutton, E.W., and Norris, B., 2013, A component-based approach + to integrated modeling in the geosciences: The design of {CSDMS}: Computers + \& Geosciences, v.~53, p.~3 -- 12, accessed March 24, 2020, at \url{https://doi.org/https://doi.org/10.1016/j.cageo.2012.04.002}. \bibitem[{Provost and others(2017)Provost, Langevin, and Hughes}]{modflow6xt3d} diff --git a/doc/ReleaseNotes/bibliography.tex b/doc/ReleaseNotes/bibliography.tex index b5150d27ddc..c485b57c9a7 100644 --- a/doc/ReleaseNotes/bibliography.tex +++ b/doc/ReleaseNotes/bibliography.tex @@ -1,2 +1,2 @@ \bibliography{../MODFLOW6References} -\bibliographystyle{../usgs} +\bibliographystyle{usgs.bst} diff --git a/doc/SuppTechInfo/bibliography.tex b/doc/SuppTechInfo/bibliography.tex index b5150d27ddc..c485b57c9a7 100644 --- a/doc/SuppTechInfo/bibliography.tex +++ b/doc/SuppTechInfo/bibliography.tex @@ -1,2 +1,2 @@ \bibliography{../MODFLOW6References} -\bibliographystyle{../usgs} +\bibliographystyle{usgs.bst} diff --git a/doc/SuppTechInfo/mf6suptechinfo.bbl b/doc/SuppTechInfo/mf6suptechinfo.bbl index faee2669651..ef6f9175094 100644 --- a/doc/SuppTechInfo/mf6suptechinfo.bbl +++ b/doc/SuppTechInfo/mf6suptechinfo.bbl @@ -1,15 +1,15 @@ \begin{thebibliography}{16} \providecommand{\natexlab}[1]{#1} \expandafter\ifx\csname urlstyle\endcsname\relax - \providecommand{\doi}[1]{doi:\discretionary{}{}{}#1}\else - \providecommand{\doi}{doi:\discretionary{}{}{}\begingroup + \providecommand{\doiagency}[1]{doi:\discretionary{}{}{}#1}\else + \providecommand{\doiagency}{doi:\discretionary{}{}{}\begingroup \urlstyle{rm}\Url}\fi \bibitem[{Guo and Langevin(2002)}]{langevin2002seawat} -Guo, Weixing, and Langevin, C.D., 2002, User's Guide to SEAWAT: A Computer - Program for Simulation of Three-Dimensional Variable-Density Ground-Water - Flow: {U.S. Geological Survey Techniques of Water-Resources Investigations - book 6, Chapter A7, 77 p.}, accessed July 25, 2019, at +Guo, W., and Langevin, C.D., 2002, User's Guide to SEAWAT: A Computer Program + for Simulation of Three-Dimensional Variable-Density Ground-Water Flow: {U.S. + Geological Survey Techniques of Water-Resources Investigations book 6, + Chapter A7, 77 p.}, accessed July 25, 2019, at \url{https://pubs.er.usgs.gov/publication/ofr03426}. \bibitem[{Harbaugh(2005)}]{modflow2005} @@ -40,8 +40,8 @@ Hughes, J.D., Langevin, C.D., and Banta, E.R., 2017, Documentation for the chap. A57, 36 p.}, \url{https://doi.org/10.3133/tm6A57}. \bibitem[{Kavetski and Kuczera(2007)}]{doi:10.1029/2006WR005195} -Kavetski, Dmitri, and Kuczera, George, 2007, Model smoothing strategies to - remove microscale discontinuities and spurious secondary optima in objective +Kavetski, D., and Kuczera, G., 2007, Model smoothing strategies to remove + microscale discontinuities and spurious secondary optima in objective functions in hydrological calibration: Water Resources Research, v.~43, no.~3, \url{https://doi.org/10.1029/2006WR005195}, \url{https://agupubs.onlinelibrary.wiley.com/doi/abs/10.1029/2006WR005195}. @@ -54,8 +54,8 @@ Kipp, K.L., 1987, HST3D: A Computer Code for Simulation of Heat and Solute \bibitem[{Langevin and others(2008)Langevin, Thorne~Jr, Dausman, Sukop, and Guo}]{langevin2008seawat} -Langevin, C.D., Thorne~Jr, D.T., Dausman, A.M., Sukop, M.C., and Guo, Weixing, - 2008, {SEAWAT} Version 4---A computer program for simulation of multi-species +Langevin, C.D., Thorne~Jr, D.T., Dausman, A.M., Sukop, M.C., and Guo, W., 2008, + {SEAWAT} Version 4---A computer program for simulation of multi-species solute and heat transport: {U.S. Geological Survey Techniques and Methods, book 6, chap. A22, 39 p.}, accessed June 27, 2017, at \url{https://pubs.er.usgs.gov/publication/tm6A22}. @@ -63,13 +63,13 @@ Langevin, C.D., Thorne~Jr, D.T., Dausman, A.M., Sukop, M.C., and Guo, Weixing, \bibitem[{Langevin and others(2017)Langevin, Hughes, Provost, Banta, Niswonger, and Panday}]{modflow6gwf} Langevin, C.D., Hughes, J.D., Provost, A.M., Banta, E.R., Niswonger, R.G., and - Panday, Sorab, 2017, Documentation for the MODFLOW 6 Groundwater Flow (GWF) + Panday, S., 2017, Documentation for the MODFLOW 6 Groundwater Flow (GWF) Model: {U.S. Geological Survey Techniques and Methods, book 6, chap. A55, 197 p.}, \url{https://doi.org/10.3133/tm6A55}. \bibitem[{Langevin and others(2022)Langevin, Provost, Panday, and Hughes}]{modflow6gwt} -Langevin, C.D., Provost, A.M., Panday, Sorab, and Hughes, J.D., 2022, +Langevin, C.D., Provost, A.M., Panday, S., and Hughes, J.D., 2022, Documentation for the MODFLOW 6 Groundwater Transport (GWT) Model: {U.S. Geological Survey Techniques and Methods, book 6, chap. A61, 56 p.}, \url{https://doi.org/10.3133/tm6A61}. @@ -79,9 +79,9 @@ Maidment, D.R., 1993, Handbook of Hydrology: New York, USA, McGraw-Hill. \bibitem[{Panday and others(2013)Panday, Langevin, Niswonger, Ibaraki, and Hughes}]{modflowusg} -Panday, Sorab, Langevin, C.D., Niswonger, R.G., Ibaraki, Motomu, and Hughes, - J.D., 2013, MODFLOW-USG version 1---An unstructured grid version of MODFLOW - for simulating groundwater flow and tightly coupled processes using a control +Panday, S., Langevin, C.D., Niswonger, R.G., Ibaraki, M., and Hughes, J.D., + 2013, MODFLOW-USG version 1---An unstructured grid version of MODFLOW for + simulating groundwater flow and tightly coupled processes using a control volume finite-difference formulation: {U.S. Geological Survey Techniques and Methods, book 6, chap. A45, 66 p.}, accessed June 27, 2017, at \url{https://pubs.usgs.gov/tm/06/a45/}. @@ -99,11 +99,11 @@ Voss, C.I., 1984, SUTRA---A finite-element simulation model for Geological Survey Water-Resources Investigations Report 84--4369, 409 p.} \bibitem[{Zheng(2010)}]{zheng2010supplemental} -Zheng, Chunmiao, 2010, MT3DMS v5.3, Supplemental User's Guide: {Technical - Report Prepared for the U.S. Army Corps of Engineers, 51 p.} +Zheng, C., 2010, MT3DMS v5.3, Supplemental User's Guide: {Technical Report + Prepared for the U.S. Army Corps of Engineers, 51 p.} \bibitem[{Zheng and Wang(1999)}]{zheng1999mt3dms} -Zheng, Chunmiao, and Wang, P.P., 1999, MT3DMS---A modular three-dimensional +Zheng, C., and Wang, P.P., 1999, MT3DMS---A modular three-dimensional multi-species transport model for simulation of advection, dispersion and chemical reactions of contaminants in groundwater systems; Documentation and user's guide: {Contract report SERDP--99--1: Vicksburg, Miss., U.S. Army diff --git a/doc/mf6io/bibliography.tex b/doc/mf6io/bibliography.tex index b5150d27ddc..c485b57c9a7 100644 --- a/doc/mf6io/bibliography.tex +++ b/doc/mf6io/bibliography.tex @@ -1,2 +1,2 @@ \bibliography{../MODFLOW6References} -\bibliographystyle{../usgs} +\bibliographystyle{usgs.bst} diff --git a/doc/mf6io/mf6io.bbl b/doc/mf6io/mf6io.bbl index c4d7f4a09b6..240c6f51337 100644 --- a/doc/mf6io/mf6io.bbl +++ b/doc/mf6io/mf6io.bbl @@ -1,8 +1,8 @@ \begin{thebibliography}{35} \providecommand{\natexlab}[1]{#1} \expandafter\ifx\csname urlstyle\endcsname\relax - \providecommand{\doi}[1]{doi:\discretionary{}{}{}#1}\else - \providecommand{\doi}{doi:\discretionary{}{}{}\begingroup + \providecommand{\doiagency}[1]{doi:\discretionary{}{}{}#1}\else + \providecommand{\doiagency}{doi:\discretionary{}{}{}\begingroup \urlstyle{rm}\Url}\fi \bibitem[{Anderman and Hill(2000)}]{anderman2000modflow} @@ -19,10 +19,10 @@ Anderman, E.R., and Hill, M.C., 2003, MODFLOW-2000, the U.S. Geological Survey \bibitem[{Bakker and others(2013)Bakker, Schaars, Hughes, Langevin, and Dausman}]{bakker2013documentation} -Bakker, Mark, Schaars, Frans, Hughes, J.D., Langevin, C.D., and Dausman, A.M., - 2013, Documentation of the seawater intrusion (SWI2) package for MODFLOW: - {U.S. Geological Survey Techniques and Methods, book 6, chap. A46, 47 p.}, - accessed June 27, 2017, at \url{https://pubs.er.usgs.gov/publication/tm6A46}. +Bakker, M., Schaars, F., Hughes, J.D., Langevin, C.D., and Dausman, A.M., 2013, + Documentation of the seawater intrusion (SWI2) package for MODFLOW: {U.S. + Geological Survey Techniques and Methods, book 6, chap. A46, 47 p.}, accessed + June 27, 2017, at \url{https://pubs.er.usgs.gov/publication/tm6A46}. \bibitem[{Banta(2000)}]{modflowdrtpack} Banta, E.R., 2000, MODFLOW-2000, the U.S. Geological Survey Modular @@ -80,10 +80,10 @@ Hill, M.C., Banta, E.R., Harbaugh, A.W., and Anderman, E.R., 2000, \bibitem[{Hoffmann and others(2003)Hoffmann, Leake, Galloway, and Wilson}]{hoffmann2003modflow} -Hoffmann, J{\"o}rn, Leake, S.A., Galloway, D.L., and Wilson, A.M., 2003, - MODFLOW-2000 Ground-Water Model---User Guide to the Subsidence and - Aquifer-System Compaction (SUB) Package: {U.S. Geological Survey Open-File - Report 03--233, 44 p.}, accessed June 27, 2017, at +Hoffmann, J., Leake, S.A., Galloway, D.L., and Wilson, A.M., 2003, MODFLOW-2000 + Ground-Water Model---User Guide to the Subsidence and Aquifer-System + Compaction (SUB) Package: {U.S. Geological Survey Open-File Report 03--233, + 44 p.}, accessed June 27, 2017, at \url{https://pubs.usgs.gov/of/2003/ofr03-233/}. \bibitem[{Hsieh and Freckleton(1993)}]{hsieh1993hfb} @@ -113,7 +113,7 @@ Hughes, J.D., Langevin, C.D., and Banta, E.R., 2017, Documentation for the Hughes, J.D., Russcher, M.J., Langevin, C.D., Morway, E.D., and McDonald, R.R., 2022{\natexlab{a}}, The {MODFLOW Application Programming Interface} for simulation control and software interoperability: Environmental Modelling \& - Software, v. 148, 105257, + Software, v. 148, article 105257, \url{https://doi.org/10.1016/j.envsoft.2021.105257}. \bibitem[{Hughes and others(2022{\natexlab{b}})Hughes, Leake, Galloway, and @@ -132,8 +132,8 @@ Konikow, L.F., Hornberger, G.Z., Halford, K.J., and Hanson, R.T., 2009, Revised \bibitem[{Langevin and others(2008)Langevin, Thorne~Jr, Dausman, Sukop, and Guo}]{langevin2008seawat} -Langevin, C.D., Thorne~Jr, D.T., Dausman, A.M., Sukop, M.C., and Guo, Weixing, - 2008, {SEAWAT} Version 4---A computer program for simulation of multi-species +Langevin, C.D., Thorne~Jr, D.T., Dausman, A.M., Sukop, M.C., and Guo, W., 2008, + {SEAWAT} Version 4---A computer program for simulation of multi-species solute and heat transport: {U.S. Geological Survey Techniques and Methods, book 6, chap. A22, 39 p.}, accessed June 27, 2017, at \url{https://pubs.er.usgs.gov/publication/tm6A22}. @@ -141,19 +141,19 @@ Langevin, C.D., Thorne~Jr, D.T., Dausman, A.M., Sukop, M.C., and Guo, Weixing, \bibitem[{Langevin and others(2017)Langevin, Hughes, Provost, Banta, Niswonger, and Panday}]{modflow6gwf} Langevin, C.D., Hughes, J.D., Provost, A.M., Banta, E.R., Niswonger, R.G., and - Panday, Sorab, 2017, Documentation for the MODFLOW 6 Groundwater Flow (GWF) + Panday, S., 2017, Documentation for the MODFLOW 6 Groundwater Flow (GWF) Model: {U.S. Geological Survey Techniques and Methods, book 6, chap. A55, 197 p.}, \url{https://doi.org/10.3133/tm6A55}. \bibitem[{Langevin and others(2020)Langevin, Panday, and Provost}]{langevin2020hydraulic} -Langevin, C.D., Panday, Sorab, and Provost, A.M., 2020, Hydraulic-head - formulation for density-dependent flow and transport: Groundwater, v.~58, - no.~3, p.~349--362. +Langevin, C.D., Panday, S., and Provost, A.M., 2020, Hydraulic-head formulation + for density-dependent flow and transport: Groundwater, v.~58, no.~3, + p.~349--362. \bibitem[{Langevin and others(2022)Langevin, Provost, Panday, and Hughes}]{modflow6gwt} -Langevin, C.D., Provost, A.M., Panday, Sorab, and Hughes, J.D., 2022, +Langevin, C.D., Provost, A.M., Panday, S., and Hughes, J.D., 2022, Documentation for the MODFLOW 6 Groundwater Transport (GWT) Model: {U.S. Geological Survey Techniques and Methods, book 6, chap. A61, 56 p.}, \url{https://doi.org/10.3133/tm6A61}. @@ -174,10 +174,10 @@ Leake, S.A., and Lilly, M.R., 1997, Documentation of computer program (FHB1) \bibitem[{Maddock and others(2012)Maddock, Baird, Hanson, Schmid, and Ajami}]{modflowripetpack} -Maddock, Thomas, III, Baird, K.J., Hanson, R.T., Schmid, Wolfgang, and Ajami, - Hoori, 2012, RIP-ET---A Riparian Evapotranspiration Package for MODFLOW-2005: - {U.S. Geological Survey Techniques and Methods, book 6, chap. A39, 76 p.}, - accessed June 27, 2017, at \url{https://pubs.usgs.gov/tm/tm6a39/}. +Maddock, Thomas, I., Baird, K.J., Hanson, R.T., Schmid, W., and Ajami, H., + 2012, RIP-ET---A Riparian Evapotranspiration Package for MODFLOW-2005: {U.S. + Geological Survey Techniques and Methods, book 6, chap. A39, 76 p.}, accessed + June 27, 2017, at \url{https://pubs.usgs.gov/tm/tm6a39/}. \bibitem[{Merritt and Konikow(2000)}]{modflowlak3pack} Merritt, M.L., and Konikow, L.F., 2000, Documentation of a computer program to @@ -202,9 +202,9 @@ Niswonger, R.G., Prudic, D.E., and Regan, R.S., 2006, Documentation of the \bibitem[{Panday and others(2013)Panday, Langevin, Niswonger, Ibaraki, and Hughes}]{modflowusg} -Panday, Sorab, Langevin, C.D., Niswonger, R.G., Ibaraki, Motomu, and Hughes, - J.D., 2013, MODFLOW-USG version 1---An unstructured grid version of MODFLOW - for simulating groundwater flow and tightly coupled processes using a control +Panday, S., Langevin, C.D., Niswonger, R.G., Ibaraki, M., and Hughes, J.D., + 2013, MODFLOW-USG version 1---An unstructured grid version of MODFLOW for + simulating groundwater flow and tightly coupled processes using a control volume finite-difference formulation: {U.S. Geological Survey Techniques and Methods, book 6, chap. A45, 66 p.}, accessed June 27, 2017, at \url{https://pubs.usgs.gov/tm/06/a45/}. @@ -235,14 +235,14 @@ Voss, C.I., 1984, SUTRA---A finite-element simulation model for Geological Survey Water-Resources Investigations Report 84--4369, 409 p.} \bibitem[{Zheng(2010)}]{zheng2010supplemental} -Zheng, Chunmiao, 2010, MT3DMS v5.3, Supplemental User's Guide: {Technical - Report Prepared for the U.S. Army Corps of Engineers, 51 p.} +Zheng, C., 2010, MT3DMS v5.3, Supplemental User's Guide: {Technical Report + Prepared for the U.S. Army Corps of Engineers, 51 p.} \bibitem[{Zheng and others(2001)Zheng, Hill, and Hsieh}]{zheng2001modflow} -Zheng, Chunmiao, Hill, M.C., and Hsieh, P.A., 2001, MODFLOW-2000, the U.S. - Geological Survey Modular Ground-Water Model---User guide to the LMT6 - package, the linkage with MT3DMS for multi-species mass transport modeling: - {U.S. Geological Survey Open-File Report 01--82, 43 p.}, accessed June 27, - 2017, at \url{https://pubs.er.usgs.gov/publication/ofr0182}. +Zheng, C., Hill, M.C., and Hsieh, P.A., 2001, MODFLOW-2000, the U.S. Geological + Survey Modular Ground-Water Model---User guide to the LMT6 package, the + linkage with MT3DMS for multi-species mass transport modeling: {U.S. + Geological Survey Open-File Report 01--82, 43 p.}, accessed June 27, 2017, at + \url{https://pubs.er.usgs.gov/publication/ofr0182}. \end{thebibliography} diff --git a/doc/usgs.bst b/doc/usgs.bst deleted file mode 100644 index 326cc9cf966..00000000000 --- a/doc/usgs.bst +++ /dev/null @@ -1,2080 +0,0 @@ -%% -%% This is file `usgs.bst', -%%% ADAPTED BY MIKE FIENEN FROM agufull08.bst -%% generated with the docstrip utility. -%% -%% The original source files were: -%% -%% merlin.mbs (with options: `head,ay,nat,seq-labc,nm-rev1,jnrlst,lab,lab-it,keyxyr,blkyear,dt-beg,yr-par,xmth,note-yr,thtit-a,trnum-it,vol-it,volp-com,pgsep-c,num-xser,ser-vol,ser-ed,pg-bk,pg-pre,pre-edn,agu-doi,doi,edpar,bkedcap,edby,blk-com,pp,ed,abr,ednx,xedn,jabr,and-com,em-it,nfss,{}') -%% physjour.mbs (with options: `ay,nat,seq-labc,nm-rev1,jnrlst,lab,lab-it,keyxyr,blkyear,dt-beg,yr-par,xmth,note-yr,thtit-a,trnum-it,vol-it,volp-com,pgsep-c,num-xser,ser-vol,ser-ed,pg-bk,pg-pre,pre-edn,agu-doi,doi,edpar,bkedcap,edby,blk-com,pp,ed,abr,ednx,xedn,jabr,and-com,em-it,nfss,{}') -%% geojour.mbs (with options: `ay,nat,seq-labc,nm-rev1,jnrlst,lab,lab-it,keyxyr,blkyear,dt-beg,yr-par,xmth,note-yr,thtit-a,trnum-it,vol-it,volp-com,pgsep-c,num-xser,ser-vol,ser-ed,pg-bk,pg-pre,pre-edn,agu-doi,doi,edpar,bkedcap,edby,blk-com,pp,ed,abr,ednx,xedn,jabr,and-com,em-it,nfss,{}') -%% photjour.mbs (with options: `ay,nat,seq-labc,nm-rev1,jnrlst,lab,lab-it,keyxyr,blkyear,dt-beg,yr-par,xmth,note-yr,thtit-a,trnum-it,vol-it,volp-com,pgsep-c,num-xser,ser-vol,ser-ed,pg-bk,pg-pre,pre-edn,agu-doi,doi,edpar,bkedcap,edby,blk-com,pp,ed,abr,ednx,xedn,jabr,and-com,em-it,nfss,{}') -%% merlin.mbs (with options: `tail,ay,nat,seq-labc,nm-rev1,jnrlst,lab,lab-it,keyxyr,blkyear,dt-beg,yr-par,xmth,note-yr,thtit-a,trnum-it,vol-it,volp-com,pgsep-c,num-xser,ser-vol,ser-ed,pg-bk,pg-pre,pre-edn,agu-doi,doi,edpar,bkedcap,edby,blk-com,pp,ed,abr,ednx,xedn,jabr,and-com,em-it,nfss,{}') -%% ---------------------------------------- -%% *** For journals of the American Geophysical Union *** -%% *** NOTE: this version does not limit the number of authors in ref list. -%% *** Use agu08.bst to limit authors to maximum 9. -%% *** -%% ---------------------------------------- -%% *** Version 3.1 from 2008/08/27 -%% *** Multiple authors of same first author and year now in order of citation -%% *** and other minor fixes -%% *** Renamed to agu08.bst and agufull08.bst -%% *** -%% *** Version 3.0 from 2004/02/06 -%% *** Changed date format for AGU journals -%% *** The date now appears in parentheses after authors -%% *** -%% *** Version 2.2 from 2003/06/26 -%% *** (with bug fix from 2003/08/19) -%% *** Includes new fields eid and doi -%% *** The eid is what the AGU calls "citation number" -%% *** and doi is the DOI number; both of these are -%% *** used as substitution for page number -%% *** The issue number is now also included as -%% *** 84(3) for vol. 84, nr. 3 -%% *** -%% *** Version 2.1d from 1999/05/20 -%% *** Book editors done right as P. James (Ed.), -%% *** Missing italics with some authors fixed -%% *** -%% *** Version 2.1c from 1999/02/11 -%% *** This version does not crash older BibTeX installations with -%% *** more than 3000 wiz-functions -%% *** -%% *** Version 2.1b from 1997/11/18 -%% *** (page numbers over 9999 are broken with commas, as 12,345) -%% *** -%% *** Version 2.1a from 1997/05/26 -%% *** (contains improvements from copy editor comments, -%% *** notes added with first word lowercase (bug in 2.1 fixed) -%% *** and journal `number' never output -%% *** abbreviation for grl corrected) -%% *** -%% -%% Copyright 1994-2008 Patrick W Daly - % =============================================================== - % IMPORTANT NOTICE: - % This bibliographic style (bst) file has been generated from one or - % more master bibliographic style (mbs) files, listed above. - % - % This generated file can be redistributed and/or modified under the terms - % of the LaTeX Project Public License Distributed from CTAN - % archives in directory macros/latex/base/lppl.txt; either - % version 1 of the License, or any later version. - % =============================================================== - % Name and version information of the main mbs file: - % \ProvidesFile{merlin.mbs}[2008/08/27 4.30 (PWD, AO, DPC)] - % For use with BibTeX version 0.99a or later - %------------------------------------------------------------------- - % This bibliography style file is intended for texts in ENGLISH - % This is an author-year citation style bibliography. As such, it is - % non-standard LaTeX, and requires a special package file to function properly. - % Such a package is natbib.sty by Patrick W. Daly - % The form of the \bibitem entries is - % \bibitem[Jones et al.(1990)]{key}... - % \bibitem[Jones et al.(1990)Jones, Baker, and Smith]{key}... - % The essential feature is that the label (the part in brackets) consists - % of the author names, as they should appear in the citation, with the year - % in parentheses following. There must be no space before the opening - % parenthesis! - % With natbib v5.3, a full list of authors may also follow the year. - % In natbib.sty, it is possible to define the type of enclosures that is - % really wanted (brackets or parentheses), but in either case, there must - % be parentheses in the label. - % The \cite command functions as follows: - % \citet{key} ==>> Jones et al. (1990) - % \citet*{key} ==>> Jones, Baker, and Smith (1990) - % \citep{key} ==>> (Jones et al., 1990) - % \citep*{key} ==>> (Jones, Baker, and Smith, 1990) - % \citep[chap. 2]{key} ==>> (Jones et al., 1990, chap. 2) - % \citep[e.g.][]{key} ==>> (e.g. Jones et al., 1990) - % \citep[e.g.][p. 32]{key} ==>> (e.g. Jones et al., 1990, p. 32) - % \citeauthor{key} ==>> Jones et al. - % \citeauthor*{key} ==>> Jones, Baker, and Smith - % \citeyear{key} ==>> 1990 - %--------------------------------------------------------------------- - -ENTRY - { address - author - booktitle - chapter - doi - urldate - url - urllink - edition - editor - eid - howpublished - institution - journal - key - month - note - number - organization - pages - publisher - school - series - title - type - volume - year - } - {} - { label extra.label sort.label short.list } -INTEGERS { output.state before.all mid.sentence after.sentence after.block } -FUNCTION {init.state.consts} -{ #0 'before.all := - #1 'mid.sentence := - #2 'after.sentence := - #3 'after.block := -} -STRINGS { s t} -FUNCTION {output.nonnull} -{ 's := - output.state mid.sentence = - { ", " * write$ } - { output.state after.block = - { add.period$ write$ - newline$ - "\newblock " write$ - } - { output.state before.all = - 'write$ - { add.period$ " " * write$ } - if$ - } - if$ - mid.sentence 'output.state := - } - if$ - s -} -FUNCTION {outputc.nonnull} -{ 's := - output.state mid.sentence = - { ": " * write$ } - { output.state after.block = - { add.period$ write$ - newline$ - "\newblock " write$ - } - { output.state before.all = - 'write$ - { add.period$ " " * write$ } - if$ - } - if$ - mid.sentence 'output.state := - } - if$ - s -} -FUNCTION {output} -{ duplicate$ empty$ - 'pop$ - 'output.nonnull - if$ -} -FUNCTION {outputc} -{ duplicate$ empty$ - 'pop$ - 'outputc.nonnull - if$ -} -FUNCTION {output.check} -{ 't := - duplicate$ empty$ - { pop$ "empty " t * " in " * cite$ * warning$ } - 'output.nonnull - if$ -} -FUNCTION {outputc.check} -{ 't := - duplicate$ empty$ - { pop$ "empty " t * " in " * cite$ * warning$ } - 'outputc.nonnull - if$ -} -FUNCTION {fin.entry} -{ add.period$ - write$ - newline$ -} - -FUNCTION {new.block} -{ output.state before.all = - 'skip$ - { after.block 'output.state := } - if$ -} -FUNCTION {new.sentence} -{ output.state after.block = - 'skip$ - { output.state before.all = - 'skip$ - { after.sentence 'output.state := } - if$ - } - if$ -} -FUNCTION {add.blank} -{ " " * before.all 'output.state := -} - -FUNCTION {date.block} -{ - skip$ -} - -FUNCTION {not} -{ { #0 } - { #1 } - if$ -} -FUNCTION {and} -{ 'skip$ - { pop$ #0 } - if$ -} -FUNCTION {or} -{ { pop$ #1 } - 'skip$ - if$ -} -FUNCTION {new.block.checkb} -{ empty$ - swap$ empty$ - and - 'skip$ - 'new.block - if$ -} -FUNCTION {field.or.null} -{ duplicate$ empty$ - { pop$ "" } - 'skip$ - if$ -} -FUNCTION {emphasize} -{ duplicate$ empty$ - { pop$ "" } - { "\textit{" swap$ * "}" * } - if$ -} -FUNCTION {cite.name.font} -%{ emphasize } -{ } -FUNCTION {tie.or.space.prefix} -{ duplicate$ text.length$ #3 < - { "~" } - { " " } - if$ - swap$ -} - -FUNCTION {capitalize} -{ "u" change.case$ "t" change.case$ } - -FUNCTION {space.word} -{ " " swap$ * " " * } - % Here are the language-specific definitions for explicit words. - % Each function has a name bbl.xxx where xxx is the English word. - % The language selected here is ENGLISH -FUNCTION {bbl.and} -{ "and" } - -FUNCTION {bbl.etal} -{ "and others" } - -FUNCTION {bbl.andothers} -{ "and others" } - -FUNCTION {bbl.editors} -{ "eds." } - -FUNCTION {bbl.editor} -{ "ed." } - -FUNCTION {bbl.edby} -{ "edited by" } - -FUNCTION {bbl.edition} -{ "ed." } - -FUNCTION {bbl.volume} -{ "v." } - -FUNCTION {bbl.of} -{ "of" } - -FUNCTION {bbl.number} -{ "no." } - -FUNCTION {bbl.nr} -{ "no." } - -FUNCTION {bbl.in} -{ "\emph{in}" } - -FUNCTION {bbl.pages} -{ "p." } - -FUNCTION {bbl.page} -{ "p." } - -FUNCTION {bbl.chapter} -{ "Chap." } - -FUNCTION {bbl.techrep} -%{ "Tech. Rep." } REMOVE THE TECH REPORT WORDS FOR USGS -{""} -FUNCTION {bbl.mthesis} -{ "M.S. thesis" } - -FUNCTION {bbl.phdthesis} -{ "Ph.D. thesis" } - -MACRO {jan} {"Jan."} - -MACRO {feb} {"Feb."} - -MACRO {mar} {"Mar."} - -MACRO {apr} {"Apr."} - -MACRO {may} {"May"} - -MACRO {jun} {"Jun."} - -MACRO {jul} {"Jul."} - -MACRO {aug} {"Aug."} - -MACRO {sep} {"Sep."} - -MACRO {oct} {"Oct."} - -MACRO {nov} {"Nov."} - -MACRO {dec} {"Dec."} - - %------------------------------------------------------------------- - % Begin module: - % \ProvidesFile{physjour.mbs}[2002/01/14 2.2 (PWD)] -MACRO {aa}{"Astron. \& Astrophys."} -MACRO {aasup}{"Astron. \& Astrophys. Suppl. Ser."} -MACRO {aj} {"Astron. J."} -MACRO {aph} {"Acta Phys."} -MACRO {advp} {"Adv. Phys."} -MACRO {ajp} {"Amer. J. Phys."} -MACRO {ajm} {"Amer. J. Math."} -MACRO {amsci} {"Amer. Sci."} -MACRO {anofd} {"Ann. Fluid Dyn."} -MACRO {am} {"Ann. Math."} -MACRO {ap} {"Ann. Phys. (NY)"} -MACRO {adp} {"Ann. Phys. (Leipzig)"} -MACRO {ao} {"Appl. Opt."} -MACRO {apl} {"Appl. Phys. Lett."} -MACRO {app} {"Astroparticle Phys."} -MACRO {apj} {"Astrophys. J."} -MACRO {apjsup} {"Astrophys. J. Suppl."} -MACRO {apss} {"Astrophys. Space Sci."} -MACRO {araa} {"Ann. Rev. Astron. Astrophys."} -MACRO {baas} {"Bull. Amer. Astron. Soc."} -MACRO {baps} {"Bull. Amer. Phys. Soc."} -MACRO {cmp} {"Comm. Math. Phys."} -MACRO {cpam} {"Commun. Pure Appl. Math."} -MACRO {cppcf} {"Comm. Plasma Phys. \& Controlled Fusion"} -MACRO {cpc} {"Comp. Phys. Comm."} -MACRO {cqg} {"Class. Quant. Grav."} -MACRO {cra} {"C. R. Acad. Sci. A"} -MACRO {fed} {"Fusion Eng. \& Design"} -MACRO {ft} {"Fusion Tech."} -MACRO {grg} {"Gen. Relativ. Gravit."} -MACRO {ieeens} {"IEEE Trans. Nucl. Sci."} -MACRO {ieeeps} {"IEEE Trans. Plasma Sci."} -MACRO {ijimw} {"Interntl. J. Infrared \& Millimeter Waves"} -MACRO {ip} {"Infrared Phys."} -MACRO {irp} {"Infrared Phys."} -MACRO {jap} {"J. Appl. Phys."} -MACRO {jasa} {"J. Acoust. Soc. America"} -MACRO {jcp} {"J. Comp. Phys."} -MACRO {jetp} {"Sov. Phys.--JETP"} -MACRO {jfe} {"J. Fusion Energy"} -MACRO {jfm} {"J. Fluid Mech."} -MACRO {jmp} {"J. Math. Phys."} -MACRO {jne} {"J. Nucl. Energy"} -MACRO {jnec} {"J. Nucl. Energy, C: Plasma Phys., Accelerators, Thermonucl. Res."} -MACRO {jnm} {"J. Nucl. Mat."} -MACRO {jpc} {"J. Phys. Chem."} -MACRO {jpp} {"J. Plasma Phys."} -MACRO {jpsj} {"J. Phys. Soc. Japan"} -MACRO {jsi} {"J. Sci. Instrum."} -MACRO {jvst} {"J. Vac. Sci. \& Tech."} -MACRO {nat} {"Nature"} -MACRO {nature} {"Nature"} -MACRO {nedf} {"Nucl. Eng. \& Design/Fusion"} -MACRO {nf} {"Nucl. Fusion"} -MACRO {nim} {"Nucl. Inst. \& Meth."} -MACRO {nimpr} {"Nucl. Inst. \& Meth. in Phys. Res."} -MACRO {np} {"Nucl. Phys."} -MACRO {npb} {"Nucl. Phys. B"} -MACRO {nt/f} {"Nucl. Tech./Fusion"} -MACRO {npbpc} {"Nucl. Phys. B (Proc. Suppl.)"} -MACRO {inc} {"Nuovo Cimento"} -MACRO {nc} {"Nuovo Cimento"} -MACRO {pf} {"Phys. Fluids"} -MACRO {pfa} {"Phys. Fluids A: Fluid Dyn."} -MACRO {pfb} {"Phys. Fluids B: Plasma Phys."} -MACRO {pl} {"Phys. Lett."} -MACRO {pla} {"Phys. Lett. A"} -MACRO {plb} {"Phys. Lett. B"} -MACRO {prep} {"Phys. Rep."} -MACRO {pnas} {"Proc. Nat. Acad. Sci. USA"} -MACRO {pp} {"Phys. Plasmas"} -MACRO {ppcf} {"Plasma Phys. \& Controlled Fusion"} -MACRO {phitrsl} {"Philos. Trans. Roy. Soc. London"} -MACRO {prl} {"Phys. Rev. Lett."} -MACRO {pr} {"Phys. Rev."} -MACRO {physrev} {"Phys. Rev."} -MACRO {pra} {"Phys. Rev. A"} -MACRO {prb} {"Phys. Rev. B"} -MACRO {prc} {"Phys. Rev. C"} -MACRO {prd} {"Phys. Rev. D"} -MACRO {pre} {"Phys. Rev. E"} -MACRO {ps} {"Phys. Scripta"} -MACRO {procrsl} {"Proc. Roy. Soc. London"} -MACRO {rmp} {"Rev. Mod. Phys."} -MACRO {rsi} {"Rev. Sci. Inst."} -MACRO {science} {"Science"} -MACRO {sciam} {"Sci. Am."} -MACRO {sam} {"Stud. Appl. Math."} -MACRO {sjpp} {"Sov. J. Plasma Phys."} -MACRO {spd} {"Sov. Phys.--Doklady"} -MACRO {sptp} {"Sov. Phys.--Tech. Phys."} -MACRO {spu} {"Sov. Phys.--Uspeki"} -MACRO {st} {"Sky and Telesc."} - % End module: physjour.mbs - %------------------------------------------------------------------- - % Begin module: - % \ProvidesFile{geojour.mbs}[2002/07/10 2.0h (PWD)] -MACRO {aisr} {"Adv. Space Res."} -MACRO {ag} {"Ann. Geophys."} -MACRO {anigeo} {"Ann. Geofis."} -MACRO {angl} {"Ann. Glaciol."} -MACRO {andmet} {"Ann. d. Meteor."} -MACRO {andgeo} {"Ann. d. Geophys."} -MACRO {andphy} {"Ann. Phys.-Paris"} -MACRO {afmgb} {"Arch. Meteor. Geophys. Bioklimatol."} -MACRO {atph} {"Atm\'osphera"} -MACRO {aao} {"Atmos. Ocean"} -MACRO {ass}{"Astrophys. Space Sci."} -MACRO {atenv} {"Atmos. Environ."} -MACRO {aujag} {"Aust. J. Agr. Res."} -MACRO {aumet} {"Aust. Meteorol. Mag."} -MACRO {blmet} {"Bound.-Lay. Meteorol."} -MACRO {bams} {"Bull. Amer. Meteorol. Soc."} -MACRO {cch} {"Clim. Change"} -MACRO {cdyn} {"Clim. Dynam."} -MACRO {cbul} {"Climatol. Bull."} -MACRO {cap} {"Contrib. Atmos. Phys."} -MACRO {dsr} {"Deep-Sea Res."} -MACRO {dhz} {"Dtsch. Hydrogr. Z."} -MACRO {dao} {"Dynam. Atmos. Oceans"} -MACRO {eco} {"Ecology"} -MACRO {empl}{"Earth, Moon and Planets"} -MACRO {envres} {"Environ. Res."} -MACRO {envst} {"Environ. Sci. Technol."} -MACRO {ecms} {"Estuarine Coastal Mar. Sci."} -MACRO {expa}{"Exper. Astron."} -MACRO {geoint} {"Geofis. Int."} -MACRO {geopub} {"Geofys. Publ."} -MACRO {geogeo} {"Geol. Geofiz."} -MACRO {gafd} {"Geophys. Astrophys. Fluid Dyn."} -MACRO {gfd} {"Geophys. Fluid Dyn."} -MACRO {geomag} {"Geophys. Mag."} -MACRO {georl} {"Geophys. Res. Lett."} -MACRO {grl} {"Geophys. Res. Lett."} -MACRO {ga} {"Geophysica"} -MACRO {gs} {"Geophysics"} -MACRO {ieeetap} {"IEEE Trans. Antenn. Propag."} -MACRO {ijawp} {"Int. J. Air Water Pollut."} -MACRO {ijc} {"Int. J. Climatol."} -MACRO {ijrs} {"Int. J. Remote Sens."} -MACRO {jam} {"J. Appl. Meteorol."} -MACRO {jaot} {"J. Atmos. Ocean. Technol."} -MACRO {jatp} {"J. Atmos. Terr. Phys."} -MACRO {jastp} {"J. Atmos. Solar-Terr. Phys."} -MACRO {jce} {"J. Climate"} -MACRO {jcam} {"J. Climate Appl. Meteor."} -MACRO {jcm} {"J. Climate Meteor."} -MACRO {jcy} {"J. Climatol."} -MACRO {jgr} {"J. Geophys. Res."} -MACRO {jga} {"J. Glaciol."} -MACRO {jh} {"J. Hydrol."} -MACRO {jmr} {"J. Mar. Res."} -MACRO {jmrj} {"J. Meteor. Res. Japan"} -MACRO {jm} {"J. Meteor."} -MACRO {jpo} {"J. Phys. Oceanogr."} -MACRO {jra} {"J. Rech. Atmos."} -MACRO {jaes} {"J. Aeronaut. Sci."} -MACRO {japca} {"J. Air Pollut. Control Assoc."} -MACRO {jas} {"J. Atmos. Sci."} -MACRO {jmts} {"J. Mar. Technol. Soc."} -MACRO {jmsj} {"J. Meteorol. Soc. Japan"} -MACRO {josj} {"J. Oceanogr. Soc. Japan"} -MACRO {jwm} {"J. Wea. Mod."} -MACRO {lao} {"Limnol. Oceanogr."} -MACRO {mwl} {"Mar. Wea. Log"} -MACRO {mau} {"Mausam"} -MACRO {meteor} {"``Meteor'' Forschungsergeb."} -MACRO {map} {"Meteorol. Atmos. Phys."} -MACRO {metmag} {"Meteor. Mag."} -MACRO {metmon} {"Meteor. Monogr."} -MACRO {metrun} {"Meteor. Rundsch."} -MACRO {metzeit} {"Meteor. Z."} -MACRO {metgid} {"Meteor. Gidrol."} -MACRO {mwr} {"Mon. Weather Rev."} -MACRO {nwd} {"Natl. Weather Dig."} -MACRO {nzjmfr} {"New Zeal. J. Mar. Freshwater Res."} -MACRO {npg} {"Nonlin. Proc. Geophys."} -MACRO {om} {"Oceanogr. Meteorol."} -MACRO {ocac} {"Oceanol. Acta"} -MACRO {oceanus} {"Oceanus"} -MACRO {paleoc} {"Paleoceanography"} -MACRO {pce} {"Phys. Chem. Earth"} -MACRO {pmg} {"Pap. Meteor. Geophys."} -MACRO {ppom} {"Pap. Phys. Oceanogr. Meteor."} -MACRO {physzeit} {"Phys. Z."} -MACRO {pps} {"Planet. Space Sci."} -MACRO {pss} {"Planet. Space Sci."} -MACRO {pag} {"Pure Appl. Geophys."} -MACRO {qjrms} {"Quart. J. Roy. Meteorol. Soc."} -MACRO {quatres} {"Quat. Res."} -MACRO {rsci} {"Radio Sci."} -MACRO {rse} {"Remote Sens. Environ."} -MACRO {rgeo} {"Rev. Geophys."} -MACRO {rgsp} {"Rev. Geophys. Space Phys."} -MACRO {rdgeo} {"Rev. Geofis."} -MACRO {revmeta} {"Rev. Meteorol."} -MACRO {sgp}{"Surveys in Geophys."} -MACRO {sp} {"Solar Phys."} -MACRO {ssr} {"Space Sci. Rev."} -MACRO {tellus} {"Tellus"} -MACRO {tac} {"Theor. Appl. Climatol."} -MACRO {tagu} {"Trans. Am. Geophys. Union (EOS)"} -MACRO {wrr} {"Water Resour. Res."} -MACRO {weather} {"Weather"} -MACRO {wafc} {"Weather Forecast."} -MACRO {ww} {"Weatherwise"} -MACRO {wmob} {"WMO Bull."} -MACRO {zeitmet} {"Z. Meteorol."} - % End module: geojour.mbs - %------------------------------------------------------------------- - % Begin module: - % \ProvidesFile{photjour.mbs}[1999/02/24 2.0b (PWD)] - -MACRO {appopt} {"Appl. Opt."} -MACRO {bell} {"Bell Syst. Tech. J."} -MACRO {ell} {"Electron. Lett."} -MACRO {jasp} {"J. Appl. Spectr."} -MACRO {jqe} {"IEEE J. Quantum Electron."} -MACRO {jlwt} {"J. Lightwave Technol."} -MACRO {jmo} {"J. Mod. Opt."} -MACRO {josa} {"J. Opt. Soc. America"} -MACRO {josaa} {"J. Opt. Soc. Amer.~A"} -MACRO {josab} {"J. Opt. Soc. Amer.~B"} -MACRO {jdp} {"J. Phys. (Paris)"} -MACRO {oc} {"Opt. Commun."} -MACRO {ol} {"Opt. Lett."} -MACRO {phtl} {"IEEE Photon. Technol. Lett."} -MACRO {pspie} {"Proc. Soc. Photo-Opt. Instrum. Eng."} -MACRO {sse} {"Solid-State Electron."} -MACRO {sjot} {"Sov. J. Opt. Technol."} -MACRO {sjqe} {"Sov. J. Quantum Electron."} -MACRO {sleb} {"Sov. Phys.--Leb. Inst. Rep."} -MACRO {stph} {"Sov. Phys.--Techn. Phys."} -MACRO {stphl} {"Sov. Techn. Phys. Lett."} -MACRO {vr} {"Vision Res."} -MACRO {zph} {"Z. f. Physik"} -MACRO {zphb} {"Z. f. Physik~B"} -MACRO {zphd} {"Z. f. Physik~D"} - -MACRO {CLEO} {"CLEO"} -MACRO {ASSL} {"Adv. Sol.-State Lasers"} -MACRO {OSA} {"OSA"} - % End module: photjour.mbs -%% Copyright 1994-2008 Patrick W Daly -MACRO {acmcs} {"ACM Comput. Surv."} - -MACRO {acta} {"Acta Inf."} - -MACRO {cacm} {"Commun. ACM"} - -MACRO {ibmjrd} {"IBM J. Res. Dev."} - -MACRO {ibmsj} {"IBM Syst.~J."} - -MACRO {ieeese} {"IEEE Trans. Software Eng."} - -MACRO {ieeetc} {"IEEE Trans. Comput."} - -MACRO {ieeetcad} - {"IEEE Trans. Comput. Aid. Des."} - -MACRO {ipl} {"Inf. Process. Lett."} - -MACRO {jacm} {"J.~ACM"} - -MACRO {jcss} {"J.~Comput. Syst. Sci."} - -MACRO {scp} {"Sci. Comput. Program."} - -MACRO {sicomp} {"SIAM J. Comput."} - -MACRO {tocs} {"ACM Trans. Comput. Syst."} - -MACRO {tods} {"ACM Trans. Database Syst."} - -MACRO {tog} {"ACM Trans. Graphic."} - -MACRO {toms} {"ACM Trans. Math. Software"} - -MACRO {toois} {"ACM Trans. Office Inf. Syst."} - -MACRO {toplas} {"ACM Trans. Progr. Lang. Syst."} - -MACRO {tcs} {"Theor. Comput. Sci."} - -FUNCTION {bibinfo.check} -{ swap$ - duplicate$ missing$ - { - pop$ pop$ - "" - } - { duplicate$ empty$ - { - swap$ pop$ - } - { swap$ - pop$ - } - if$ - } - if$ -} -FUNCTION {bibinfo.warn} -{ swap$ - duplicate$ missing$ - { - swap$ "missing " swap$ * " in " * cite$ * warning$ pop$ - "" - } - { duplicate$ empty$ - { - swap$ "empty " swap$ * " in " * cite$ * warning$ - } - { swap$ - pop$ - } - if$ - } - if$ -} - - -STRINGS {ss tt fm} -FUNCTION {format.onlyfirst} -{ - 'ss := %% Make a copy of the name in s - %% Extract the First (and possible Medium) names - %% and store it in variable fm - ss #1 "{ff}" format.name$ 'fm := - - %% Note that now fm could contain: - %% * An empty string ("") if the author has no first name (only Last name was provided) - %% * A single word (like "First") if the author has no medium name - %% * A sequence of words (like "First Medium") - %% For the last case we want to abbreviate "Medium", without dot - - %% Test if we are in the first case - fm empty$ { - % If empty (no first name), use the standard formatting - ss #1 "{vv~}{ll}{, f{.}.}{, jj}" format.name$ - }{ % Otherwise, attempt the trick - %% Now the trick. Interpret "First Medium" - %% as if "Medium" were a last name, and abbreviate it - fm #1 "{f.}{l}" format.name$ 'tt := %% And store the result in tt - %% Consider the particular case in which no Medium name is present - %% In this case, "First" will be interpreted as a last name, and - %% thus abbreviated. This can be detected because the resulting - %% string has length 1 - tt text.length$ #1 > { %% If there was a medium name - fm #1 "{f.}{l.}" format.name$ 'tt := - tt %% Store the abbreviated version - }{ %% Else store the original version of the name - fm - } if$ - - %% After the above, the top of the stack will contain - %% either "First" unabbreviated (if the author has not middle name) - %% or "First M", as required - 'tt := %% Copy that value to tt - - %% Now complete the standard formatting of the author, omitting - %% the first name part, which is stored in tt - ss #1 "{vv~}{ll}{, jj}" format.name$ - %% And concatenate to it the value of tt - ", " * - tt * - }if$ %% If the trick has to be done -} - -INTEGERS { nameptr namesleft numnames } -STRINGS { bibinfo} - -FUNCTION {format.names} -{ 'bibinfo := - duplicate$ empty$ 'skip$ { - 's := - "" 't := - #1 'nameptr := - s num.names$ 'numnames := - numnames 'namesleft := - { namesleft #0 > } - { s nameptr - "{vv~}{ll}{, ff}{, jj}" - format.name$ - bibinfo bibinfo.check - format.onlyfirst 't := - nameptr #1 > - { - namesleft #1 > - { ", " * t * } - { - s nameptr "{ll}" format.name$ duplicate$ "others" = - { 't := } - { pop$ } - if$ - numnames #1 > - { "," * } - 'skip$ - if$ - t "others" = - { - " " * bbl.etal * - } - { - bbl.and - space.word * t * - } - if$ - } - if$ - } - 't - if$ - nameptr #1 + 'nameptr := - namesleft #1 - 'namesleft := - } - while$ - } if$ -} - - -FUNCTION {format.names.ed} -{ - 'bibinfo := - duplicate$ empty$ 'skip$ { - 's := - "" 't := - #1 'nameptr := - s num.names$ 'numnames := - numnames 'namesleft := - { namesleft #0 > } - { s nameptr - "{f.~}{vv~}{ll}{, jj}" - format.name$ - bibinfo bibinfo.check - 't := - nameptr #1 > - { - namesleft #1 > - { ", " * t * } - { - s nameptr "{ll}" format.name$ duplicate$ "others" = - { 't := } - { pop$ } - if$ - numnames #2 > - { "," * } - 'skip$ - if$ - t "others" = - { - - " " * bbl.etal * - } - { - bbl.and - space.word * t * - } - if$ - } - if$ - } - 't - if$ - nameptr #1 + 'nameptr := - namesleft #1 - 'namesleft := - } - while$ - } if$ -} -FUNCTION {format.key} -{ empty$ - { key field.or.null } - { "" } - if$ -} - -FUNCTION {format.authors} -{ author "author" format.names -} -FUNCTION {get.bbl.editor} -{ editor num.names$ #1 > 'bbl.editors 'bbl.editor if$ } - -FUNCTION {format.editors} -{ editor "editor" format.names duplicate$ empty$ 'skip$ - { - " " * - get.bbl.editor - capitalize - "(" swap$ * ")" * - * - } - if$ -} -FUNCTION {format.book.pages} -{ pages "pages" bibinfo.check - duplicate$ empty$ 'skip$ - { "~" * bbl.pages * } - if$ -} -FUNCTION {format.doi} -{ doi empty$ - { "" } - { - urldate empty$ - { - "\url{https://doi.org/" doi * "}" * - } - { - "at \url{https://doi.org/" doi * "}" * - } - if$ - } - if$ -} -FUNCTION {format.urldate} -{ urldate empty$ - { "" } - { - url empty$ - { - doi empty$ - { "" } - { - "accessed " urldate * - } - if$ - } - { - "accessed " urldate * - } - if$ - } - if$ -} -FUNCTION {format.url} -{ url empty$ - { "" } - { - urldate empty$ - { - "\url{" url * "}" * - } - { - "at \url{" url * "}" * - } - if$ - } - if$ -} -FUNCTION {format.urllink} -{ urllink empty$ - { "" } - { - " (Available online at \url{" urllink * "})" * - } - if$ -} -FUNCTION {format.note} -{ - note empty$ - { "" } - { note #1 #1 substring$ - duplicate$ "{" = - 'skip$ - { output.state mid.sentence = - { "l" } - { "u" } - if$ - change.case$ - } - if$ - note #2 global.max$ substring$ * "note" bibinfo.check - } - if$ -} - -FUNCTION {format.title} -{ title - duplicate$ empty$ 'skip$ - { "t" change.case$ } - if$ - "title" bibinfo.check -} -FUNCTION {format.full.names} -{'s := - "" 't := - #1 'nameptr := - s num.names$ 'numnames := - numnames 'namesleft := - { namesleft #0 > } - { s nameptr - "{vv~}{ll}" format.name$ - 't := - nameptr #1 > - { - namesleft #1 > - { ", " * t * } - { - s nameptr "{ll}" format.name$ duplicate$ "others" = - { 't := } - { pop$ } - if$ - t "others" = - { - " " * bbl.etal * - cite.name.font - } - { - numnames #2 > - { "," * } - 'skip$ - if$ - bbl.and - space.word * t * - } - if$ - } - if$ - } - 't - if$ - nameptr #1 + 'nameptr := - namesleft #1 - 'namesleft := - } - while$ - t "others" = - 'skip$ - { cite.name.font } - if$ -} - -FUNCTION {author.editor.key.full} -{ author empty$ - { editor empty$ - { key empty$ - { cite$ #1 #3 substring$ } - 'key - if$ - } - { editor format.full.names } - if$ - } - { author format.full.names } - if$ -} - -FUNCTION {author.key.full} -{ author empty$ - { key empty$ - { cite$ #1 #3 substring$ } - 'key - if$ - } - { author format.full.names } - if$ -} - -FUNCTION {editor.key.full} -{ editor empty$ - { key empty$ - { cite$ #1 #3 substring$ } - 'key - if$ - } - { editor format.full.names } - if$ -} - -FUNCTION {make.full.names} -{ type$ "book" = - type$ "inbook" = - or - 'author.editor.key.full - { type$ "proceedings" = - 'editor.key.full - 'author.key.full - if$ - } - if$ -} - -FUNCTION {output.bibitem} -{ newline$ - "\bibitem[{" write$ - label write$ - ")" make.full.names duplicate$ short.list = - { pop$ } - { * } - if$ - "}]{" * write$ - cite$ write$ - "}" write$ - newline$ - "" - before.all 'output.state := -} - -FUNCTION {if.digit} -{ duplicate$ "0" = - swap$ duplicate$ "1" = - swap$ duplicate$ "2" = - swap$ duplicate$ "3" = - swap$ duplicate$ "4" = - swap$ duplicate$ "5" = - swap$ duplicate$ "6" = - swap$ duplicate$ "7" = - swap$ duplicate$ "8" = - swap$ "9" = or or or or or or or or or -} -FUNCTION {n.separate} -{ 't := - "" - #0 'numnames := - { t empty$ not } - { t #-1 #1 substring$ if.digit - { numnames #1 + 'numnames := } - { #0 'numnames := } - if$ - t #-1 #1 substring$ swap$ * - t #-2 global.max$ substring$ 't := - numnames #5 = - { duplicate$ #1 #2 substring$ swap$ - #3 global.max$ substring$ - "," swap$ * * - } - 'skip$ - if$ - } - while$ -} -FUNCTION {n.dashify} -{ - n.separate - 't := - "" - { t empty$ not } - { t #1 #1 substring$ "-" = - { t #1 #2 substring$ "--" = not - { "--" * - t #2 global.max$ substring$ 't := - } - { { t #1 #1 substring$ "-" = } - { "-" * - t #2 global.max$ substring$ 't := - } - while$ - } - if$ - } - { t #1 #1 substring$ * - t #2 global.max$ substring$ 't := - } - if$ - } - while$ -} - -FUNCTION {word.in} -{ bbl.in - " " * } - -FUNCTION {format.date} -{ year "year" bibinfo.check duplicate$ empty$ - { - } - 'skip$ - if$ - extra.label * - before.all 'output.state := - ", " swap$ * "" * -} -FUNCTION {format.btitle} -{ title "title" bibinfo.check - duplicate$ empty$ 'skip$ - { - %emphasize - } - if$ -} -FUNCTION {either.or.check} -{ empty$ - 'pop$ - { "can't use both " swap$ * " fields in " * cite$ * warning$ } - if$ -} -FUNCTION {format.bvolume} -{ volume empty$ - { "" } - { bbl.volume volume tie.or.space.prefix - "volume" bibinfo.check * * - series "series" bibinfo.check - duplicate$ empty$ 'pop$ - - % { emphasize ", " * swap$ * } % REMOVE ITALICS FOR USGS - { ", " * swap$ * } - if$ - "volume and number" number either.or.check - } - if$ -} -FUNCTION {format.number.series} -{ volume empty$ - { number empty$ - { series field.or.null } - { series empty$ - { number "number" bibinfo.check } - { output.state mid.sentence = - { bbl.number } - { bbl.number capitalize } - if$ - number tie.or.space.prefix "number" bibinfo.check * * - bbl.in space.word * - series "series" bibinfo.check * - } - if$ - } - if$ - } - { "" } - if$ -} - -FUNCTION {format.edition} -{ edition duplicate$ empty$ 'skip$ - { - output.state mid.sentence = - { "l" } - { "t" } - if$ change.case$ - "edition" bibinfo.check - " " * bbl.edition * - } - if$ -} -INTEGERS { multiresult } -FUNCTION {multi.page.check} -{ 't := - #0 'multiresult := - { multiresult not - t empty$ not - and - } - { t #1 #1 substring$ - duplicate$ "-" = - swap$ duplicate$ "," = - swap$ "+" = - or or - { #1 'multiresult := } - { t #2 global.max$ substring$ 't := } - if$ - } - while$ - multiresult -} -FUNCTION {format.pages} -{ pages duplicate$ empty$ 'skip$ - { duplicate$ multi.page.check - { - bbl.pages swap$ - n.dashify - } - { - bbl.page swap$ - } - if$ - tie.or.space.prefix - "pages" bibinfo.check - * * - } - if$ -} -FUNCTION {format.journal.pages} -{ pages duplicate$ empty$ 'pop$ - { swap$ duplicate$ empty$ - { pop$ pop$ format.pages } - { - ", p.~" * - swap$ - n.dashify - "pages" bibinfo.check - * - } - if$ - } - if$ -} -FUNCTION {format.journal.eid} -{ eid "eid" bibinfo.check - duplicate$ empty$ 'pop$ - { swap$ duplicate$ empty$ 'skip$ - { - ", " * - } - if$ - swap$ * - } - if$ -} -FUNCTION {format.vol.num.pages} -{ volume field.or.null - duplicate$ empty$ 'skip$ - { - bbl.volume swap$ tie.or.space.prefix - "volume" bibinfo.check - * * - } - if$ - number "number" bibinfo.check duplicate$ empty$ 'skip$ - { - swap$ duplicate$ empty$ - { "there's a number but no volume in " cite$ * warning$ } - 'skip$ - if$ - swap$ - ", " bbl.nr * number tie.or.space.prefix pop$ * swap$ * - } - if$ * - eid empty$ - { format.journal.pages } - { format.journal.eid } - if$ -} - -FUNCTION {format.chapter.pages} -{ chapter empty$ - 'format.pages - { type empty$ - { bbl.chapter } - { type "l" change.case$ - "type" bibinfo.check - } - if$ - chapter tie.or.space.prefix - "chapter" bibinfo.check - * * - pages empty$ - 'skip$ - { ", " * format.pages * } - if$ - } - if$ -} - -FUNCTION {format.booktitle} -{ - booktitle "booktitle" bibinfo.check - %emphasize -} - -FUNCTION {format.editor} -{ - editor "editor" format.names - duplicate$ empty$ 'pop$ - { - %emphasize - } - if$ -} - -FUNCTION {format.in.ed.booktitle} -{ format.booktitle duplicate$ empty$ 'skip$ - { - format.bvolume duplicate$ empty$ 'pop$ - { ", " swap$ * * } - if$ - editor "editor" format.names.ed duplicate$ empty$ 'pop$ - { - bbl.edby - " " * swap$ * - swap$ - "," * - " " * swap$ - * } - if$ - word.in swap$ * - } - if$ -} - -FUNCTION {formatc.in.ed.booktitle} -{ format.editor duplicate$ empty$ 'skip$ - { - format.bvolume duplicate$ empty$ 'pop$ - { ", " swap$ * * } - if$ - format.booktitle duplicate$ empty$ 'pop$ - { - swap$ - "," * - " " * swap$ - * } - if$ - word.in swap$ * - } - if$ -} - -FUNCTION {format.thesis.type} -{ type duplicate$ empty$ - 'pop$ - { swap$ pop$ - "t" change.case$ "type" bibinfo.check - } - if$ -} -FUNCTION {format.tr.number} -{ number "number" bibinfo.check - type duplicate$ empty$ - { pop$ bbl.techrep } - 'skip$ - if$ - "type" bibinfo.check - swap$ duplicate$ empty$ - { pop$ "t" change.case$ } - { tie.or.space.prefix * * } - if$ -} -FUNCTION {format.article.crossref} -{ - word.in - " \cite{" * crossref * "}" * -} -FUNCTION {format.book.crossref} -{ volume duplicate$ empty$ - { "empty volume in " cite$ * "'s crossref of " * crossref * warning$ - pop$ word.in - } - { bbl.volume - swap$ tie.or.space.prefix "volume" bibinfo.check * * bbl.of space.word * - } - if$ - " \cite{" * crossref * "}" * -} -FUNCTION {format.incoll.inproc.crossref} -{ - word.in - " \cite{" * crossref * "}" * -} -FUNCTION {format.org.or.pub} -{ 't := - "" - address empty$ t empty$ and - 'skip$ - { - t empty$ - { address "address" bibinfo.check * - } - { t * - address empty$ - 'skip$ - { ", " * address "address" bibinfo.check * } - if$ - } - if$ - } - if$ -} -FUNCTION {formatc.org.or.pub} -{ 't := - "" - address empty$ t empty$ and - 'skip$ - { - t empty$ - { address "address" bibinfo.check * - } - { t * - address empty$ - 'skip$ - { ": " * address "address" bibinfo.check * } - if$ - } - if$ - } - if$ -} -FUNCTION {format.publisher.address} -{ format.org.or.pub publisher "publisher" bibinfo.warn -} -FUNCTION {formatc.publisher.address} -{ formatc.org.or.pub publisher "publisher" bibinfo.warn -} - -FUNCTION {format.organization.address} -{ organization "organization" bibinfo.check format.org.or.pub -} - -FUNCTION {article} -{ output.bibitem - format.authors "author" output.check - author format.key output - format.date "year" output.check - date.block - format.title "title" output.check - crossref missing$ - { - journal - "journal" bibinfo.check - %emphasize - "journal" outputc.check - format.vol.num.pages output - format.urldate output - format.doi output - format.url output - format.urllink output - } - { format.article.crossref output.nonnull - format.pages output - } - if$ - format.note output - fin.entry -} -FUNCTION {book} -{ output.bibitem - author empty$ - { format.editors "author and editor" output.check - editor format.key output - } - { format.authors output.nonnull - crossref missing$ - { "author and editor" editor either.or.check } - 'skip$ - if$ - } - if$ - format.date "year" output.check - date.block - format.btitle "title" output.check - publisher empty$ - { format.number.series outputc.nonnull } - { formatc.publisher.address output - format.book.pages output - } - if$ - format.urldate output - format.doi output - format.url output - format.urllink output - format.note output - fin.entry -} -FUNCTION {booklet} -{ output.bibitem - format.authors output - author format.key output - format.date "year" output.check - date.block - format.title "title" output.check - howpublished "howpublished" bibinfo.check output - address "address" bibinfo.check output - format.book.pages output - format.urldate output - format.doi output - format.url output - format.urllink output - format.note output - fin.entry -} - -FUNCTION {inbook} -{ output.bibitem - author empty$ - { format.editors "author and editor" output.check - editor format.key output - } - { format.authors output.nonnull - crossref missing$ - { "author and editor" editor either.or.check } - 'skip$ - if$ - } - if$ - format.date "year" output.check - date.block - format.btitle "title" output.check - publisher empty$ - { format.number.series outputc.nonnull } - { formatc.publisher.address output - format.book.pages output - } -% crossref missing$ -% { -% format.bvolume output -% format.chapter.pages "chapter and pages" output.check -% format.number.series output -% format.edition output -% formatc.publisher.address output -% } -% { -% format.chapter.pages "chapter and pages" output.check -% format.book.crossref output.nonnull -% } - if$ - format.urldate output - format.doi output - format.url output - format.urllink output - format.note output - fin.entry -} - -FUNCTION {incollection} -{ output.bibitem - format.authors "author" output.check - author format.key output - format.date "year" output.check - date.block - format.title "title" output.check - publisher empty$ - { formatc.in.ed.booktitle "booktitle" output.check - format.number.series outputc.nonnull } - { formatc.in.ed.booktitle "booktitle" output.check - formatc.publisher.address output - format.book.pages output - } -% crossref missing$ -% { format.in.ed.booktitle "booktitle" output.check -% format.number.series output -% format.edition output -% format.chapter.pages output -% formatc.publisher.address output -% } -% { format.incoll.inproc.crossref output.nonnull -% format.chapter.pages output -% } - if$ - format.urldate output - format.doi output - format.url output - format.urllink output - format.note output - fin.entry -} -FUNCTION {inproceedings} -{ output.bibitem - format.authors "author" output.check - author format.key output - format.date "year" output.check - date.block - format.title "title" output.check - crossref missing$ - { format.in.ed.booktitle "booktitle" output.check - format.number.series output - publisher empty$ - { format.organization.address output } - { organization "organization" bibinfo.check output - format.publisher.address output - } - if$ - format.pages output - } - { format.incoll.inproc.crossref output.nonnull - format.pages output - } - if$ - format.urldate output - format.doi output - format.url output - format.urllink output - format.note output - fin.entry -} -FUNCTION {conference} { inproceedings } -FUNCTION {manual} -{ output.bibitem - format.authors output - author format.key output - format.date "year" output.check - date.block - format.btitle "title" output.check - organization "organization" bibinfo.check output - address "address" bibinfo.check output - format.edition output - format.urldate output - format.doi output - format.url output - format.urllink output - format.note output - fin.entry -} - -FUNCTION {mastersthesis} -{ output.bibitem - format.authors "author" output.check - author format.key output - format.date "year" output.check - date.block - format.title - "title" output.check - bbl.mthesis format.thesis.type output.nonnull - school "school" bibinfo.warn output - address "address" bibinfo.check output - format.urldate output - format.doi output - format.url output - format.urllink output - format.note output - format.book.pages output - fin.entry -} - -FUNCTION {misc} -{ output.bibitem - format.authors output - author format.key output - format.date "year" output.check - date.block - format.title output - howpublished "howpublished" bibinfo.check output - format.urldate output - format.doi output - format.url output - format.urllink output - format.note output - fin.entry -} -FUNCTION {phdthesis} -{ output.bibitem - format.authors "author" output.check - author format.key output - format.date "year" output.check - date.block - format.title - "title" output.check - bbl.phdthesis format.thesis.type output.nonnull - school "school" bibinfo.warn output - address "address" bibinfo.check output - format.urldate output - format.doi output - format.url output - format.urllink output - format.note output - format.book.pages output - fin.entry -} - -FUNCTION {proceedings} -{ output.bibitem - format.editors output - editor format.key output - format.date "year" output.check - date.block - format.btitle "title" output.check - format.bvolume output - format.number.series output - publisher empty$ - { format.organization.address output } - { organization "organization" bibinfo.check output - format.publisher.address output - } - if$ - format.urldate output - format.doi output - format.url output - format.urllink output - format.note output - fin.entry -} - -FUNCTION {techreport} -{ output.bibitem - format.authors "author" output.check - author format.key output - format.date "year" output.check - date.block - format.title - "title" output.check - % format.tr.number emphasize output.nonnull - institution "institution" bibinfo.warn output - address "address" bibinfo.check output - format.book.pages output - fin.entry -} - -FUNCTION {online} -{ output.bibitem - format.authors "author" output.check - author format.key output - format.date "year" output.check - date.block - format.title - "title" output.check - % format.tr.number emphasize output.nonnull - - fin.entry -} - -FUNCTION {unpublished} -{ output.bibitem - format.authors "author" output.check - author format.key output - format.date "year" output.check - date.block - format.title "title" output.check - format.urldate output - format.doi output - format.url output - format.urllink output - format.note "note" output.check - fin.entry -} - -FUNCTION {default.type} { misc } -READ -FUNCTION {sortify} -{ purify$ - "l" change.case$ -} -INTEGERS { len } -FUNCTION {chop.word} -{ 's := - 'len := - s #1 len substring$ = - { s len #1 + global.max$ substring$ } - 's - if$ -} -FUNCTION {format.lab.names} -{ 's := - "" 't := - s #1 "{vv~}{ll}" format.name$ - s num.names$ duplicate$ - #2 > - { pop$ - " " * bbl.etal * - cite.name.font - "others" 't := - } - { #2 < - 'skip$ - { s #2 "{ff }{vv }{ll}{ jj}" format.name$ "others" = - { - " " * bbl.etal * - cite.name.font - "others" 't := - } - { bbl.and space.word * s #2 "{vv~}{ll}" format.name$ - * } - if$ - } - if$ - } - if$ - t "others" = - 'skip$ - { cite.name.font } - if$ -} - -FUNCTION {author.key.label} -{ author empty$ - { key empty$ - { cite$ #1 #3 substring$ } - 'key - if$ - } - { author format.lab.names } - if$ -} - -FUNCTION {author.editor.key.label} -{ author empty$ - { editor empty$ - { key empty$ - { cite$ #1 #3 substring$ } - 'key - if$ - } - { editor format.lab.names } - if$ - } - { author format.lab.names } - if$ -} - -FUNCTION {editor.key.label} -{ editor empty$ - { key empty$ - { cite$ #1 #3 substring$ } - 'key - if$ - } - { editor format.lab.names } - if$ -} - -FUNCTION {calc.short.authors} -{ type$ "book" = - type$ "inbook" = - or - 'author.editor.key.label - { type$ "proceedings" = - 'editor.key.label - 'author.key.label - if$ - } - if$ - 'short.list := -} - -FUNCTION {calc.label} -{ calc.short.authors - short.list - "(" - * - year duplicate$ empty$ - short.list key field.or.null = or - { pop$ "" } - 'skip$ - if$ - * - 'label := -} - -FUNCTION {sort.format.names} -{ 's := - #1 'nameptr := - "" - s num.names$ 'numnames := - numnames 'namesleft := - { namesleft #0 > } - { s nameptr - "{vv{ } }{ll{ }}{ f{ }}{ jj{ }}" - format.name$ 't := - nameptr #1 > - { - " " * - namesleft #1 = t "others" = and - { "zzzzz" 't := } - 'skip$ - if$ - numnames #2 > nameptr #2 = and - { "zz" * year field.or.null * " " * - #1 'namesleft := - } - { t sortify * } - if$ - } - { t sortify * } - if$ - nameptr #1 + 'nameptr := - namesleft #1 - 'namesleft := - } - while$ -} - -FUNCTION {sort.format.title} -{ 't := - "A " #2 - "An " #3 - "The " #4 t chop.word - chop.word - chop.word - sortify - #1 global.max$ substring$ -} -FUNCTION {author.sort} -{ author empty$ - { key empty$ - { "to sort, need author or key in " cite$ * warning$ - "" - } - { key sortify } - if$ - } - { author sort.format.names } - if$ -} -FUNCTION {author.editor.sort} -{ author empty$ - { editor empty$ - { key empty$ - { "to sort, need author, editor, or key in " cite$ * warning$ - "" - } - { key sortify } - if$ - } - { editor sort.format.names } - if$ - } - { author sort.format.names } - if$ -} -FUNCTION {editor.sort} -{ editor empty$ - { key empty$ - { "to sort, need editor or key in " cite$ * warning$ - "" - } - { key sortify } - if$ - } - { editor sort.format.names } - if$ -} -FUNCTION {presort} -{ calc.label - label sortify - " " - * - type$ "book" = - type$ "inbook" = - or - 'author.editor.sort - { type$ "proceedings" = - 'editor.sort - 'author.sort - if$ - } - if$ - #1 entry.max$ substring$ - 'sort.label := - sort.label - * - #1 entry.max$ substring$ - 'sort.key$ := -} - -ITERATE {presort} -SORT -STRINGS { last.label next.extra } -INTEGERS { last.extra.num last.extra.num.extended last.extra.num.blank number.label } -FUNCTION {initialize.extra.label.stuff} -{ #0 int.to.chr$ 'last.label := - "" 'next.extra := - #0 'last.extra.num := - "a" chr.to.int$ #1 - 'last.extra.num.blank := - last.extra.num.blank 'last.extra.num.extended := - #0 'number.label := -} -FUNCTION {forward.pass} -{ last.label label = - { last.extra.num #1 + 'last.extra.num := - last.extra.num "z" chr.to.int$ > - { "a" chr.to.int$ 'last.extra.num := - last.extra.num.extended #1 + 'last.extra.num.extended := - } - 'skip$ - if$ - last.extra.num.extended last.extra.num.blank > - { last.extra.num.extended int.to.chr$ - last.extra.num int.to.chr$ - * 'extra.label := } - { last.extra.num int.to.chr$ 'extra.label := } - if$ - } - { "a" chr.to.int$ 'last.extra.num := - "" 'extra.label := - label 'last.label := - } - if$ - number.label #1 + 'number.label := -} -FUNCTION {reverse.pass} -{ next.extra "b" = - { "a" 'extra.label := } - 'skip$ - if$ - extra.label 'next.extra := - extra.label - duplicate$ empty$ - 'skip$ - { "{\natexlab{" swap$ * "}}" * } - if$ - 'extra.label := - label extra.label * 'label := -} -EXECUTE {initialize.extra.label.stuff} -ITERATE {forward.pass} -REVERSE {reverse.pass} -FUNCTION {bib.sort.order} -{ sort.label - " " - * - year field.or.null sortify - * - #1 entry.max$ substring$ - 'sort.key$ := -} -ITERATE {bib.sort.order} -SORT -FUNCTION {begin.bib} -{ preamble$ empty$ - 'skip$ - { preamble$ write$ newline$ } - if$ - "\begin{thebibliography}{" number.label int.to.str$ * "}" * - write$ newline$ - "\providecommand{\natexlab}[1]{#1}" - write$ newline$ - "\expandafter\ifx\csname urlstyle\endcsname\relax" - write$ newline$ - " \providecommand{\doi}[1]{doi:\discretionary{}{}{}#1}\else" - write$ newline$ - " \providecommand{\doi}{doi:\discretionary{}{}{}\begingroup \urlstyle{rm}\Url}\fi" - write$ newline$ -} -EXECUTE {begin.bib} -EXECUTE {init.state.consts} -ITERATE {call.type$} -FUNCTION {end.bib} -{ newline$ - "\end{thebibliography}" write$ newline$ -} -EXECUTE {end.bib} -%% End of customized bst file -%% -%% End of file `agufull08.bst'. From 1e616142da7eba4419009d9f1c259a3cf0076236 Mon Sep 17 00:00:00 2001 From: mjr-deltares <45555666+mjr-deltares@users.noreply.github.com> Date: Fri, 3 Mar 2023 15:47:20 +0100 Subject: [PATCH 034/123] feat(parallel): add parallel capabilities (with mpi and petsc) without enabling (#1151) * - introduced distributed exchange along side distributed models - removing exchange pointers from interface model code too to be ready for parallel - update meson file, but msvs and makefile await * - add common base for distributed components - further refactoring, now all model _and_ exchange pointer removed from gridconnection * fix memory manager: char string pointer was not reassigned * - preparing to add serial router of distributed data - extend vector module with assignment and get_values * Adding deallocation * Moved distributed concepts to own folder, mf6dist.f90 will serve as entry point * Some refactoring towards mapping and routing of distributed data * - introduced caching in the distributed model - added a (serial) router to copy remote memory to local buffers inside the distributed objects * - cleaned up distributedbase - add merging of maps for router - numerical solution now also takes context for syncing - added vector routines for adding (uniquely) - added functionality to indexmaps for easier merging/adding of maps NB: doesn't compile yet, distr. exchanges need their load() refactored * Finished refactoring of previous commit: tests green * Further progress: first GWF test now runs with non-local distributed models * - add synchronization before ac in num solution to fix moffset issue - add 2d routing for spdis * - refactored ia,ja,amat into new matrix type for all packages, models, exchanges WIP: numerical solution not finished, so tests are all red * - Finished refactoring of amat into Matrix type WIP with failing tests * - some fixes after refactoring using Matrix type * update makefile * update makefile 2 * - add to msvs * fprettify 'm * - fix: typo in buoyancy pkg * - add (petsc) vector type, changed meson script and continue from linux for now (WIP) - renamed VectorInt to avoid mistakes * - adding abstract solver to numerical solution - use of factory to isolate PETSc/MPI deps - activate some preprocessing - include petsc dep in meson * - petsc vector and matrix working now for serial case * - add sorting for CSR to sparse - PetscMatrixType no longer works of SparseMatrixType but has own ia,ja - first examples green: PETSc CG cannot deal with elim. Dirichlet bound * - add PARALLEL option: embarrassingly for now * Trivial parallel case (uncoupled models) working with PETSc/MPI - introduced offset for distributed numerical solution * - petsc vector: veczeroentries requires vecassemblybegin/end call! * Adding virtual data, this will replace the concepts in the distributed model/exchange types * Added virtual data container * add deallocation to virtual data objects * exclude petsc files for makefile * temp. fix for dist models (will turn obsolete) * - add mpi to mf6 - add run controller with factory to obscure mpi implementation - three independent builds possible: classic, with mpi, with mpi+petsc * fix petsc/mpi build for windows * move distributed* to obsolete directory * ANGLE reallocation to use isize==0 again * further implementation of virtual data concept - replaced dd elements in core with simulation run_control object - single models are running again, coupled ones require refactoring the dmodel in exchanges to be replaced by virtual models... * - add virtual exchange: gwf appears to be working - wip: next up is gwt virtual exchange * - adding virtual exchanges - gwfgwf appears to be working, gwtgwt not implemented yet... * - add virtual memory for virtual gwt model - refactor simulation create to deal with non-local components * - restructured virtual data - working on GwfGwfExchange for parallel - starting on MpiRouter * - start implementing mpi messaging - renamed virtualdatastore to virtualdatamanager * - added generic mpi type building - routing of message header and body - add mpi_world concept * we are routing over MPI now! * - cleaned up mpi type managing - generic data communication for ints, dbls, int arrays, dbl arrays seems to work now! * - refactored message building and virtual exchanges to deal with nodem1/nodem2 issue. Not sure about this... * - add 'pointer-containing-struct' for VirtualDataContainer to work with arrays instead of ListType * - removed Distributed*f90 now, has been replaced by Virtual*f90 * - split model budget routines in Exchange for parallel dev - allocate NPF angle arrays at 0 size when not present * - fprettify * - suppressoutput uninitialized... (it was always 0) * - fix: reverse nodes for bd w.r.t. model2 * - fprettify * - collateral from refactoring... * arghh, one more... * - mostly renaming and whitespace * - add GWFSIMVALS to virtual exchange - fix some naming and prepare for duplicate exchanges when in parallel * add parallel flag to meson build cfg * further reduce GwfGwf dependency on model pointers to prepare for parallel * parallel build enabled/fixed on windows * update makefile and exclude mpi sources * fprettify * add dev-mode for option PARALLEL * more fprettify * Updating SpatialModelConnection to drop camelCase for snake_case * bug: Forgot to clear the list with virtual models and exchanges * oops, findloc is only available as of gfortran-9 * fprettify * fix build for ifort * add parallel ci * parallel tests depend on shotgun too * changed extension for preprocessor on linux/macos * update meson.build * update msvs project with new files and preprocessor instructions * add first parallel test case (disabled) * should be only 1 pytest_addoption... * - add parallel option to nam - add parallel build to meson * - fix: null pointer access in gwfgwf replaced with virtual model calls - petsc database file now expected in mf6 run directory (parallel tests runs locally) * - parallel mode is now command line option - bug fix in VirtualModel - now sequential run control also possible with mpi build * - fix warning and fix typo for non-parallel build * - activate parallel test (skip smoke test) * (run faster) * (restore) * add 'make' for petsc to windows ci * make whitespace pretty * - removed windows parallel ci: modflow doesn't build with petsc * add some diagnostics and safe guarding * small cleanup and further documentation * - add some very basic logging to the mpi router, let's see where this goes... * Big cleanup: removed all TODO_MJRs by small edits or created github issues for larger efforts * Extend NumericalSolution with ParallelSolution to allow parallel solution behavior in a separate unit * - improved monitoring mpi traffic - no more sending of empty message bodies * - update msvs project file with latest changes --- .github/workflows/ci.yml | 144 ++++ .vscode/build_vscode.py | 6 + autotest/conftest.py | 16 + autotest/pytest.ini | 1 + autotest/simulation.py | 77 ++- autotest/test_gwf_ifmod_xt3d01.py | 2 +- autotest/test_gwf_libmf6_ifmod01.py | 2 +- autotest/test_par_gwf01.py | 235 +++++++ doc/mf6io/mf6ivar/dfn/sim-nam.dfn | 1 - make/makefile | 368 +++++----- meson.build | 35 +- meson_options.txt | 1 + msvs/mf6core.vfproj | 84 ++- pymake/excludefiles.txt | 13 +- pymake/makebin.py | 2 + pymake/makefile | 298 ++++---- src/Distributed/IndexMap.f90 | 86 +++ src/Distributed/InterfaceMap.f90 | 132 ++++ .../MappedMemory.f90} | 127 ++-- src/Distributed/Mapper.f90 | 320 +++++++++ src/Distributed/MpiMessageBuilder.f90 | 552 +++++++++++++++ src/Distributed/MpiRouter.f90 | 441 ++++++++++++ src/Distributed/MpiRunControl.F90 | 119 ++++ src/Distributed/MpiWorld.f90 | 83 +++ src/Distributed/RouterBase.f90 | 38 ++ src/Distributed/RouterFactory.F90 | 34 + src/Distributed/SerialRouter.f90 | 53 ++ src/Distributed/VirtualBase.f90 | 324 +++++++++ src/Distributed/VirtualDataContainer.f90 | 367 ++++++++++ src/Distributed/VirtualDataLists.f90 | 13 + src/Distributed/VirtualDataManager.f90 | 310 +++++++++ src/Distributed/VirtualExchange.f90 | 283 ++++++++ src/Distributed/VirtualGwfExchange.f90 | 59 ++ src/Distributed/VirtualGwfModel.f90 | 204 ++++++ src/Distributed/VirtualGwtExchange.f90 | 103 +++ src/Distributed/VirtualGwtModel.f90 | 209 ++++++ src/Distributed/VirtualModel.f90 | 364 ++++++++++ src/Distributed/VirtualSolution.f90 | 18 + src/Exchange/DisConnExchange.f90 | 88 ++- src/Exchange/GhostNode.f90 | 2 +- src/Exchange/GwfGwfExchange.f90 | 616 +++++++++-------- src/Exchange/GwfGwtExchange.f90 | 71 +- src/Exchange/GwtGwtExchange.f90 | 46 +- src/Exchange/NumericalExchange.f90 | 2 +- src/Model/Connection/CellWithNbrs.f90 | 10 +- src/Model/Connection/ConnectionBuilder.f90 | 84 +-- src/Model/Connection/DistributedData.f90 | 318 --------- src/Model/Connection/DistributedModel.f90 | 244 ------- src/Model/Connection/DistributedVariable.f90 | 59 ++ src/Model/Connection/GridConnection.f90 | 642 ++++++++---------- src/Model/Connection/GridSorting.f90 | 35 +- src/Model/Connection/GwfGwfConnection.f90 | 141 ++-- src/Model/Connection/GwfInterfaceModel.f90 | 2 +- src/Model/Connection/GwtGwtConnection.f90 | 95 ++- src/Model/Connection/InterfaceMap.f90 | 30 - .../Connection/SpatialModelConnection.f90 | 366 ++++++---- src/Model/GroundWaterFlow/gwf3.f90 | 4 +- src/Model/GroundWaterFlow/gwf3api8.f90 | 2 +- src/Model/GroundWaterFlow/gwf3buy8.f90 | 2 +- src/Model/GroundWaterFlow/gwf3chd8.f90 | 2 +- src/Model/GroundWaterFlow/gwf3csub8.f90 | 2 +- src/Model/GroundWaterFlow/gwf3drn8.f90 | 2 +- src/Model/GroundWaterFlow/gwf3evt8.f90 | 2 +- src/Model/GroundWaterFlow/gwf3ghb8.f90 | 2 +- src/Model/GroundWaterFlow/gwf3hfb8.f90 | 2 +- src/Model/GroundWaterFlow/gwf3lak8.f90 | 2 +- src/Model/GroundWaterFlow/gwf3maw8.f90 | 2 +- src/Model/GroundWaterFlow/gwf3npf8.f90 | 2 +- src/Model/GroundWaterFlow/gwf3rch8.f90 | 2 +- src/Model/GroundWaterFlow/gwf3riv8.f90 | 2 +- src/Model/GroundWaterFlow/gwf3sfr8.f90 | 2 +- src/Model/GroundWaterFlow/gwf3sto8.f90 | 2 +- src/Model/GroundWaterFlow/gwf3uzf8.f90 | 2 +- src/Model/GroundWaterFlow/gwf3wel8.f90 | 2 +- src/Model/GroundWaterTransport/gwt1.f90 | 2 +- src/Model/GroundWaterTransport/gwt1adv1.f90 | 2 +- src/Model/GroundWaterTransport/gwt1apt1.f90 | 2 +- src/Model/GroundWaterTransport/gwt1cnc1.f90 | 2 +- src/Model/GroundWaterTransport/gwt1dsp.f90 | 2 +- src/Model/GroundWaterTransport/gwt1fmi1.f90 | 2 +- src/Model/GroundWaterTransport/gwt1ist1.f90 | 2 +- src/Model/GroundWaterTransport/gwt1lkt1.f90 | 2 +- src/Model/GroundWaterTransport/gwt1mst1.f90 | 2 +- src/Model/GroundWaterTransport/gwt1mwt1.f90 | 2 +- src/Model/GroundWaterTransport/gwt1sft1.f90 | 2 +- src/Model/GroundWaterTransport/gwt1src1.f90 | 2 +- src/Model/GroundWaterTransport/gwt1ssm1.f90 | 2 +- src/Model/GroundWaterTransport/gwt1uzt1.f90 | 2 +- src/Model/ModelUtilities/BoundaryPackage.f90 | 2 +- .../ModelUtilities/DiscretizationBase.f90 | 4 +- src/Model/ModelUtilities/Xt3dInterface.f90 | 2 +- src/Model/NumericalModel.f90 | 23 +- src/RunControl.f90 | 171 +++++ src/RunControlFactory.F90 | 39 ++ src/SimulationCreate.f90 | 168 +++-- .../LinearMethods/ImsLinearSolver.f90 | 65 ++ src/Solution/LinearMethods/ims8linear.f90 | 17 +- src/Solution/LinearSolverBase.f90 | 54 ++ src/Solution/LinearSolverFactory.F90 | 41 ++ src/Solution/NumericalSolution.f90 | 260 ++++--- src/Solution/PETSc/PetscConvergence.F90 | 57 ++ src/Solution/PETSc/PetscSolver.F90 | 231 +++++++ src/Solution/ParallelSolution.f90 | 11 + src/Solution/SolutionFactory.F90 | 48 ++ src/Utilities/ArrayHandlers.f90 | 53 +- src/Utilities/Idm/LoadMf6FileType.f90 | 13 +- src/Utilities/Idm/StructArray.f90 | 8 +- src/Utilities/Idm/StructVector.f90 | 4 +- src/Utilities/Matrix/MatrixBase.f90 | 38 +- src/Utilities/Matrix/PetscMatrix.F90 | 344 ++++++++++ src/Utilities/Matrix/SparseMatrix.f90 | 49 +- src/Utilities/STLVecInt.f90 | 211 ++++++ src/Utilities/Sim.f90 | 6 + src/Utilities/SimStages.f90 | 47 ++ src/Utilities/SimVariables.f90 | 11 +- src/Utilities/Sparse.f90 | 42 +- src/Utilities/Timer.f90 | 6 +- src/Utilities/Vector/PetscVector.F90 | 129 ++++ src/Utilities/Vector/SeqVector.f90 | 95 +++ src/Utilities/Vector/VectorBase.f90 | 54 ++ src/Utilities/VectorInt.f90 | 163 ----- src/Utilities/comarg.f90 | 5 +- src/meson.build | 65 +- src/mf6core.f90 | 87 +-- src/mf6lists.f90 | 6 +- 125 files changed, 8576 insertions(+), 2494 deletions(-) create mode 100644 autotest/test_par_gwf01.py create mode 100644 meson_options.txt create mode 100644 src/Distributed/IndexMap.f90 create mode 100644 src/Distributed/InterfaceMap.f90 rename src/{Model/Connection/MappedVariable.f90 => Distributed/MappedMemory.f90} (52%) create mode 100644 src/Distributed/Mapper.f90 create mode 100644 src/Distributed/MpiMessageBuilder.f90 create mode 100644 src/Distributed/MpiRouter.f90 create mode 100644 src/Distributed/MpiRunControl.F90 create mode 100644 src/Distributed/MpiWorld.f90 create mode 100644 src/Distributed/RouterBase.f90 create mode 100644 src/Distributed/RouterFactory.F90 create mode 100644 src/Distributed/SerialRouter.f90 create mode 100644 src/Distributed/VirtualBase.f90 create mode 100644 src/Distributed/VirtualDataContainer.f90 create mode 100644 src/Distributed/VirtualDataLists.f90 create mode 100644 src/Distributed/VirtualDataManager.f90 create mode 100644 src/Distributed/VirtualExchange.f90 create mode 100644 src/Distributed/VirtualGwfExchange.f90 create mode 100644 src/Distributed/VirtualGwfModel.f90 create mode 100644 src/Distributed/VirtualGwtExchange.f90 create mode 100644 src/Distributed/VirtualGwtModel.f90 create mode 100644 src/Distributed/VirtualModel.f90 create mode 100644 src/Distributed/VirtualSolution.f90 delete mode 100644 src/Model/Connection/DistributedData.f90 delete mode 100644 src/Model/Connection/DistributedModel.f90 create mode 100644 src/Model/Connection/DistributedVariable.f90 delete mode 100644 src/Model/Connection/InterfaceMap.f90 create mode 100644 src/RunControl.f90 create mode 100644 src/RunControlFactory.F90 create mode 100644 src/Solution/LinearMethods/ImsLinearSolver.f90 create mode 100644 src/Solution/LinearSolverBase.f90 create mode 100644 src/Solution/LinearSolverFactory.F90 create mode 100644 src/Solution/PETSc/PetscConvergence.F90 create mode 100644 src/Solution/PETSc/PetscSolver.F90 create mode 100644 src/Solution/ParallelSolution.f90 create mode 100644 src/Solution/SolutionFactory.F90 create mode 100644 src/Utilities/Matrix/PetscMatrix.F90 create mode 100644 src/Utilities/STLVecInt.f90 create mode 100644 src/Utilities/SimStages.f90 create mode 100644 src/Utilities/Vector/PetscVector.F90 create mode 100644 src/Utilities/Vector/SeqVector.f90 create mode 100644 src/Utilities/Vector/VectorBase.f90 delete mode 100644 src/Utilities/VectorInt.f90 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1a9a2afe79b..f86da1653c3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -396,3 +396,147 @@ jobs: env: GITHUB_TOKEN: ${{ github.token }} run: pytest -v --durations 0 + + parallel_test: + name: Parallel testing + needs: + - lint + - build + - smoke_test + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ ubuntu-22.04, macos-12 ] #, windows-2022 ] + defaults: + run: + shell: bash -l {0} + steps: + + - name: Checkout modflow6 + uses: actions/checkout@v3 + with: + path: modflow6 + + - name: Setup MSYS2 + if: runner.os == 'Windows' + uses: msys2/setup-msys2@v2 + with: + update: true + install: | + git + make + mingw-w64-x86_64-gcc + mingw-w64-x86_64-python + mingw-w64-x86_64-python-pip + mingw-w64-x86_64-python-pytest + + - name: Setup MPI + if: runner.os == 'Windows' + uses: mpi4py/setup-mpi@v1 + with: + mpi: msmpi + + - name: Setup GNU Fortran 9 + uses: awvwgk/setup-fortran@main + with: + compiler: gcc + version: 9 + + - name: Cache PETSc + id: cache-petsc + uses: actions/cache@v3 + with: + path: petsc + key: ${{ runner.os }}-petsc + + - name: Clone PETSc + if: runner.os != 'Windows' && steps.cache-petsc.outputs.cache-hit != 'true' + run: git clone -b release https://gitlab.com/petsc/petsc.git petsc + + - name: Download PETSc + if: runner.os == 'Windows' + run: | + curl https://ftp.mcs.anl.gov/pub/petsc/release-snapshots/petsc-3.18.4.tar.gz -O -J + mkdir petsc + tar -xzf petsc-3.18.4.tar.gz -C petsc --strip-components=1 + + - name: Configure environment + if: runner.os == 'Linux' + run: | + echo "PKG_CONFIG_PATH=$GITHUB_WORKSPACE/petsc/linux-gnu/lib/pkgconfig" >> $GITHUB_ENV + echo "$GITHUB_WORKSPACE/petsc/linux-gnu/bin" >> $GITHUB_PATH + + - name: Configure environment + if: runner.os == 'macOS' + run: | + echo "PKG_CONFIG_PATH=$GITHUB_WORKSPACE/petsc/arch-darwin-c-debug/lib/pkgconfig" >> $GITHUB_ENV + echo "$GITHUB_WORKSPACE/petsc/arch-darwin-c-debug/bin" >> $GITHUB_PATH + + - name: Configure PETSc + if: runner.os == 'Linux' + working-directory: petsc + run: | + sudo ./configure PETSC_ARCH=linux-gnu --download-fblaslapack --download-openmpi + sudo make all + + - name: Configure PETSc + if: runner.os == 'macOS' + working-directory: petsc + run: | + sudo ./configure PETSC_DIR="$GITHUB_WORKSPACE/petsc" PETSC_ARCH=arch-darwin-c-debug --download-fblaslapack --download-openmpi + sudo make all + + - name: Configure PETSc + if: runner.os == 'Windows' + shell: msys2 {0} + working-directory: petsc + run: | + pacman -Syu + pacman -Sy --noconfirm autoconf automake-wrapper bison bsdcpio make git \ + mingw-w64-x86_64-toolchain patch python flex \ + pkg-config pkgfile tar unzip mingw-w64-x86_64-cmake \ + mingw-w64-x86_64-msmpi mingw-w64-x86_64-openblas mingw-w64-x86_64-jq + /usr/bin/python ./configure --with-mpiexec='/C/Program\ Files/Microsoft\ MPI/Bin/mpiexec' --with-shared-libraries=0 + make all + + - name: Setup Micromamba + uses: mamba-org/provision-with-micromamba@main + with: + environment-file: modflow6/environment.yml + cache-downloads: true + cache-env: true + + - name: Build modflow6 + working-directory: modflow6 + run: | + meson setup builddir -Ddebug=false -Dparallel=true --prefix=$(pwd) --libdir=bin + meson install -C builddir + meson test --verbose --no-rebuild -C builddir + + - name: Show Meson logs + if: failure() + working-directory: modflow6 + run: cat builddir/meson-logs/meson-log.txt + + - name: Update flopy + working-directory: modflow6/autotest + run: python update_flopy.py + + - name: Get executables + working-directory: modflow6/autotest + env: + GITHUB_TOKEN: ${{ github.token }} + run: pytest -v --durations 0 get_exes.py + + - name: Test programs (parallel) + working-directory: modflow6/autotest + env: + REPOS_PATH: ${{ github.workspace }} + run: | + if [ "${{ github.ref_name }}" == "master" ]; then + pytest -v -n auto --parallel --durations 0 -m "not large and not developmode" + else + pytest -v -n auto --parallel --durations 0 -m "not large" + fi + diff --git a/.vscode/build_vscode.py b/.vscode/build_vscode.py index 11fe1ab3c6f..88d3e8b4c97 100644 --- a/.vscode/build_vscode.py +++ b/.vscode/build_vscode.py @@ -13,6 +13,11 @@ os.environ["FC"] = args.compiler builddir = f"builddir_{args.compiler}_{args.buildtype}" +arg_parallel = "-Dparallel=false" +if os.getenv("BUILD_PARALLEL_MF6") is not None: + if os.environ["BUILD_PARALLEL_MF6"] == '1': + arg_parallel = "-Dparallel=true" + if args.action == "rebuild" and os.path.isdir(builddir): shutil.rmtree(builddir) @@ -30,6 +35,7 @@ os.getcwd(), "--libdir", "bin", + arg_parallel, ] + setup_flag print("Run:", shlex.join(command)) subprocess.run( diff --git a/autotest/conftest.py b/autotest/conftest.py index 1743ecff592..78db66c4732 100644 --- a/autotest/conftest.py +++ b/autotest/conftest.py @@ -49,3 +49,19 @@ def pytest_addoption(parser): default=False, help="TODO", ) + parser.addoption( + "--parallel", + action="store_true", + default=False, + help="include parallel test cases" + ) + + +def pytest_collection_modifyitems(config, items): + if config.getoption("--parallel"): + # --parallel given in cli: do not skip parallel tests + return + skip_parallel = pytest.mark.skip(reason="need --parallel option to run") + for item in items: + if "parallel" in item.keywords: + item.add_marker(skip_parallel) diff --git a/autotest/pytest.ini b/autotest/pytest.ini index 013c63a54ff..bc9cdb0cc74 100644 --- a/autotest/pytest.ini +++ b/autotest/pytest.ini @@ -14,3 +14,4 @@ markers = aux: tests for auxiliary variables lak: tests for lake package maw: tests for multi-aquifer well package + parallel: test relying on a parallel (MPI+PETSc) build diff --git a/autotest/simulation.py b/autotest/simulation.py index 070ef8d1d04..3451bcb9059 100644 --- a/autotest/simulation.py +++ b/autotest/simulation.py @@ -3,6 +3,7 @@ import sys import time from traceback import format_exc +from subprocess import PIPE, STDOUT, Popen import flopy import numpy as np @@ -33,6 +34,8 @@ class TestSimulation: def __init__( self, name, + parallel=False, + ncpus=1, exfunc=None, exe_dict=None, htol=None, @@ -50,6 +53,8 @@ def __init__( print(msg) self.name = name + self.parallel = parallel + self.ncpus = ncpus self.exfunc = exfunc self.targets = exe_dict self.simpath = simpath @@ -208,23 +213,36 @@ def run(self): exe = str(self.targets["mf6"].absolute()) msg = sfmt.format("using executable", exe) print(msg) - try: - success, buff = flopy.run_model( - exe, - nam, - model_ws=self.simpath, - silent=False, - report=True, - ) - msg = sfmt.format("MODFLOW 6 run", self.name) - if success: + + if self.parallel: + print("running parallel on", self.ncpus, "processes") + try: + success, buff = self.run_parallel( + exe, + ) + except Exception as exc: + msg = sfmt.format("MODFLOW 6 run", self.name) print(msg) - else: + print(exc) + success = False + else: + try: + success, buff = flopy.run_model( + exe, + nam, + model_ws=self.simpath, + silent=False, + report=True, + ) + msg = sfmt.format("MODFLOW 6 run", self.name) + if success: + print(msg) + else: + print(msg) + except: + msg = sfmt.format("MODFLOW 6 run", self.name) print(msg) - except: - msg = sfmt.format("MODFLOW 6 run", self.name) - print(msg) - success = False + success = False # set failure based on success and require_failure setting if self.require_failure is None: @@ -312,6 +330,35 @@ def run(self): return + def run_parallel(self, exe): + normal_msg="normal termination" + success = False + nr_success = 0 + buff = [] + + mpiexec_cmd = ["mpiexec", "-np", str(self.ncpus), exe, "-p"] + proc = Popen(mpiexec_cmd, stdout=PIPE, stderr=STDOUT, cwd=self.simpath) + + while True: + line = proc.stdout.readline().decode("utf-8") + if line == "" and proc.poll() is not None: + break + if line: + # success is when the success message appears + # in every process of the parallel simulation + if normal_msg in line.lower(): + nr_success = nr_success + 1 + if nr_success == self.ncpus: + success = True + line = line.rstrip("\r\n") + print(line) + buff.append(line) + else: + break + + return success, buff + + def compare(self): """ Compare the model results diff --git a/autotest/test_gwf_ifmod_xt3d01.py b/autotest/test_gwf_ifmod_xt3d01.py index 88bd8776ffe..bcff3b72dc8 100644 --- a/autotest/test_gwf_ifmod_xt3d01.py +++ b/autotest/test_gwf_ifmod_xt3d01.py @@ -461,7 +461,7 @@ def exact(x): ), "exchange observations do not match parent exchange flows" assert np.allclose( obsvalues, -child_exchange_flows - ), "exchange observations do not match chile exchange flows" + ), "exchange observations do not match child exchange flows" # Read the lumped boundname observations values fpth = os.path.join(sim.simpath, "gwf_obs_boundnames.csv") diff --git a/autotest/test_gwf_libmf6_ifmod01.py b/autotest/test_gwf_libmf6_ifmod01.py index 8105f35f88d..51577ed622f 100644 --- a/autotest/test_gwf_libmf6_ifmod01.py +++ b/autotest/test_gwf_libmf6_ifmod01.py @@ -40,7 +40,7 @@ def get_model(dir, name): # solver data nouter, ninner = 100, 300 - hclose, rclose, relax = 10 - 9, 1e-3, 0.97 + hclose, rclose, relax = 10e-9, 1e-3, 0.97 # model spatial discretization nlay = 3 diff --git a/autotest/test_par_gwf01.py b/autotest/test_par_gwf01.py new file mode 100644 index 00000000000..4b178074efb --- /dev/null +++ b/autotest/test_par_gwf01.py @@ -0,0 +1,235 @@ +import os + +import flopy +import numpy as np +import pytest +from framework import TestFramework +from simulation import TestSimulation + +# Test for parallel MODFLOW running on two cpus. +# It contains two coupled models with (nlay,nrow,ncol) = (1,1,5), +# constant head boundaries left=1.0, right=10.0. +# +# idomain: +# +# 'leftmodel' 'rightmodel' +# +# 1 1 1 1 1 1 1 1 1 1 +# +# The result should be a uniform flow field. + +ex = ["par_gwf01"] + +# global convenience... +name_left = "leftmodel" +name_right = "rightmodel" + + +def get_model(idx, dir): + + name = ex[idx] + + # parameters and spd + # tdis + nper = 1 + tdis_rc = [] + for i in range(nper): + tdis_rc.append((1.0, 1, 1)) + + # solver data + nouter, ninner = 100, 300 + hclose, rclose, relax = 10e-9, 1e-3, 0.97 + + # model spatial discretization + nlay = 1 + nrow = 1 + ncol = 5 + + # cell spacing + delr = 100.0 + delc = 100.0 + area = delr * delc + + # shift + shift_x = 5 * delr + shift_y = 0.0 + + # top/bot of the aquifer + tops = [0.0, -100.0, -200] + + # hydraulic conductivity + k11 = 1.0 + + # boundary stress period data + h_left = 1.0 + h_right = 10.0 + + # initial head + h_start = 0.0 + + sim = flopy.mf6.MFSimulation( + sim_name=name, version="mf6", exe_name="mf6", sim_ws=dir, + ) + + tdis = flopy.mf6.ModflowTdis( + sim, time_units="DAYS", nper=nper, perioddata=tdis_rc + ) + + ims = flopy.mf6.ModflowIms( + sim, + print_option="SUMMARY", + outer_dvclose=hclose, + outer_maximum=nouter, + under_relaxation="DBD", + inner_maximum=ninner, + inner_dvclose=hclose, + rcloserecord=rclose, + linear_acceleration="BICGSTAB", + relaxation_factor=relax, + ) + + # submodel on the left: + left_chd = [ + [(ilay, irow, 0), h_left] + for irow in range(nrow) + for ilay in range(nlay) + ] + chd_spd_left = {0: left_chd} + + gwf = flopy.mf6.ModflowGwf(sim, modelname=name_left, save_flows=True) + dis = flopy.mf6.ModflowGwfdis( + gwf, + nlay=nlay, + nrow=nrow, + ncol=ncol, + delr=delr, + delc=delc, + top=tops[0], + botm=tops[1:nlay+1], + ) + ic = flopy.mf6.ModflowGwfic(gwf, strt=h_start) + npf = flopy.mf6.ModflowGwfnpf( + gwf, + save_specific_discharge=True, + save_flows=True, + icelltype=0, + k=k11, + ) + chd = flopy.mf6.ModflowGwfchd(gwf, stress_period_data=chd_spd_left) + oc = flopy.mf6.ModflowGwfoc( + gwf, + head_filerecord=f"{name_left}.hds", + budget_filerecord=f"{name_left}.cbc", + headprintrecord=[("COLUMNS", 10, "WIDTH", 15, "DIGITS", 6, "GENERAL")], + saverecord=[("HEAD", "LAST"), ("BUDGET", "LAST")], + ) + + # submodel on the right: + right_chd = [ + [(ilay, irow, ncol - 1), h_right] + for irow in range(nrow) + for ilay in range(nlay) + ] + chd_spd_right = {0: right_chd} + + gwf = flopy.mf6.ModflowGwf(sim, modelname=name_right, save_flows=True) + dis = flopy.mf6.ModflowGwfdis( + gwf, + nlay=nlay, + nrow=nrow, + ncol=ncol, + delr=delr, + delc=delc, + xorigin=shift_x, + yorigin=shift_y, + top=tops[0], + botm=tops[1:nlay+1], + ) + ic = flopy.mf6.ModflowGwfic(gwf, strt=h_start) + npf = flopy.mf6.ModflowGwfnpf( + gwf, + save_specific_discharge=True, + save_flows=True, + icelltype=0, + k=k11, + ) + chd = flopy.mf6.ModflowGwfchd(gwf, stress_period_data=chd_spd_right) + oc = flopy.mf6.ModflowGwfoc( + gwf, + head_filerecord=f"{name_right}.hds", + budget_filerecord=f"{name_right}.cbc", + headprintrecord=[("COLUMNS", 10, "WIDTH", 15, "DIGITS", 6, "GENERAL")], + saverecord=[("HEAD", "LAST"), ("BUDGET", "LAST")], + ) + + # exchangedata + angldegx = 0.0 + cdist = delr + gwfgwf_data = [ + [ + (ilay, irow, ncol - 1), + (ilay, irow, 0), + 1, + delr / 2.0, + delr / 2.0, + delc, + angldegx, + cdist, + ] + for irow in range(nrow) + for ilay in range(nlay) + ] + gwfgwf = flopy.mf6.ModflowGwfgwf( + sim, + exgtype="GWF6-GWF6", + nexg=len(gwfgwf_data), + exgmnamea=name_left, + exgmnameb=name_right, + exchangedata=gwfgwf_data, + auxiliary=["ANGLDEGX", "CDIST"] + ) + + return sim + +def build_petsc_db(exdir): + petsc_db_file = os.path.join(exdir, ".petscrc") + with open(petsc_db_file, 'w') as petsc_file: + petsc_file.write("-sub_ksp_type bcgs\n") + petsc_file.write("-sub_pc_type ilu\n") + petsc_file.write("-options_left no\n") + #petsc_file.write("-wait_dbg\n") + +def build_model(idx, exdir): + sim = get_model(idx, exdir) + build_petsc_db(exdir) + return sim, None + +def eval_model(sim): + # two coupled models with a uniform flow field, + # here we assert the known head values at the + # cell centers + fpth = os.path.join(sim.simpath, f"{name_left}.hds") + hds = flopy.utils.HeadFile(fpth) + heads_left = hds.get_data().flatten() + fpth = os.path.join(sim.simpath, f"{name_right}.hds") + hds = flopy.utils.HeadFile(fpth) + heads_right = hds.get_data().flatten() + np.testing.assert_array_almost_equal(heads_left[0:5], [1.0, 2.0, 3.0, 4.0, 5.0]) + np.testing.assert_array_almost_equal(heads_right[0:5], [6.0, 7.0, 8.0, 9.0, 10.0]) + +@pytest.mark.parallel +@pytest.mark.parametrize( + "name", + ex, +) +def test_mf6model(name, function_tmpdir, targets): + test = TestFramework() + test.build(build_model, 0, str(function_tmpdir)) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_model, + idxsim=0, make_comparison=False, + parallel=True, ncpus=2, + ), + str(function_tmpdir), + ) diff --git a/doc/mf6io/mf6ivar/dfn/sim-nam.dfn b/doc/mf6io/mf6ivar/dfn/sim-nam.dfn index ac9be58e7bb..55d93c16411 100644 --- a/doc/mf6io/mf6ivar/dfn/sim-nam.dfn +++ b/doc/mf6io/mf6ivar/dfn/sim-nam.dfn @@ -32,7 +32,6 @@ optional true longname maximum number of errors description maximum number of errors that will be stored and printed. - # --------------------- sim nam timing --------------------- block timing diff --git a/make/makefile b/make/makefile index 15e3c1c565d..08ba032bdd5 100644 --- a/make/makefile +++ b/make/makefile @@ -7,28 +7,31 @@ include ./makedefaults SOURCEDIR1=../src SOURCEDIR2=../src/Exchange SOURCEDIR3=../src/Model -SOURCEDIR4=../src/Model/Connection -SOURCEDIR5=../src/Model/Geometry -SOURCEDIR6=../src/Model/GroundWaterFlow +SOURCEDIR4=../src/Model/Geometry +SOURCEDIR5=../src/Model/ModelUtilities +SOURCEDIR6=../src/Model/Connection SOURCEDIR7=../src/Model/GroundWaterTransport -SOURCEDIR8=../src/Model/ModelUtilities -SOURCEDIR9=../src/Solution -SOURCEDIR10=../src/Solution/LinearMethods -SOURCEDIR11=../src/Timing -SOURCEDIR12=../src/Utilities -SOURCEDIR13=../src/Utilities/ArrayRead -SOURCEDIR14=../src/Utilities/Idm -SOURCEDIR15=../src/Utilities/Libraries -SOURCEDIR16=../src/Utilities/Libraries/blas -SOURCEDIR17=../src/Utilities/Libraries/daglib -SOURCEDIR18=../src/Utilities/Libraries/rcm -SOURCEDIR19=../src/Utilities/Libraries/sparsekit -SOURCEDIR20=../src/Utilities/Libraries/sparskit2 -SOURCEDIR21=../src/Utilities/Matrix -SOURCEDIR22=../src/Utilities/Memory -SOURCEDIR23=../src/Utilities/Observation -SOURCEDIR24=../src/Utilities/OutputControl -SOURCEDIR25=../src/Utilities/TimeSeries +SOURCEDIR8=../src/Model/GroundWaterFlow +SOURCEDIR9=../src/Distributed +SOURCEDIR10=../src/Solution +SOURCEDIR11=../src/Solution/PETSc +SOURCEDIR12=../src/Solution/LinearMethods +SOURCEDIR13=../src/Timing +SOURCEDIR14=../src/Utilities +SOURCEDIR15=../src/Utilities/TimeSeries +SOURCEDIR16=../src/Utilities/Libraries +SOURCEDIR17=../src/Utilities/Libraries/rcm +SOURCEDIR18=../src/Utilities/Libraries/sparsekit +SOURCEDIR19=../src/Utilities/Libraries/sparskit2 +SOURCEDIR20=../src/Utilities/Libraries/blas +SOURCEDIR21=../src/Utilities/Libraries/daglib +SOURCEDIR22=../src/Utilities/Idm +SOURCEDIR23=../src/Utilities/Matrix +SOURCEDIR24=../src/Utilities/Vector +SOURCEDIR25=../src/Utilities/Observation +SOURCEDIR26=../src/Utilities/OutputControl +SOURCEDIR27=../src/Utilities/Memory +SOURCEDIR28=../src/Utilities/ArrayRead VPATH = \ ${SOURCEDIR1} \ @@ -55,203 +58,230 @@ ${SOURCEDIR21} \ ${SOURCEDIR22} \ ${SOURCEDIR23} \ ${SOURCEDIR24} \ -${SOURCEDIR25} +${SOURCEDIR25} \ +${SOURCEDIR26} \ +${SOURCEDIR27} \ +${SOURCEDIR28} .SUFFIXES: .f90 .F90 .o OBJECTS = \ +$(OBJDIR)/sparsekit.o \ +$(OBJDIR)/ilut.o \ +$(OBJDIR)/blas1_d.o \ $(OBJDIR)/kind.o \ +$(OBJDIR)/Sparse.o \ +$(OBJDIR)/dag_module.o \ +$(OBJDIR)/OpenSpec.o \ +$(OBJDIR)/GwtAdvOptions.o \ +$(OBJDIR)/InputDefinition.o \ +$(OBJDIR)/rcm.o \ +$(OBJDIR)/VectorBase.o \ +$(OBJDIR)/gwf3dis8idm.o \ +$(OBJDIR)/CharString.o \ +$(OBJDIR)/ims8reordering.o \ $(OBJDIR)/Constants.o \ +$(OBJDIR)/BaseGeometry.o \ +$(OBJDIR)/compilerversion.o \ +$(OBJDIR)/gwf3disu8idm.o \ +$(OBJDIR)/GwfBuyInputData.o \ +$(OBJDIR)/HashTable.o \ +$(OBJDIR)/SimStages.o \ +$(OBJDIR)/defmacro.o \ +$(OBJDIR)/CsrUtils.o \ $(OBJDIR)/SimVariables.o \ $(OBJDIR)/genericutils.o \ -$(OBJDIR)/compilerversion.o \ -$(OBJDIR)/ArrayHandlers.o \ +$(OBJDIR)/gwf3npf8idm.o \ +$(OBJDIR)/gwt1dspidm.o \ +$(OBJDIR)/Xt3dAlgorithm.o \ +$(OBJDIR)/Timer.o \ +$(OBJDIR)/SfrCrossSectionUtils.o \ +$(OBJDIR)/List.o \ +$(OBJDIR)/ims8misc.o \ +$(OBJDIR)/MatrixBase.o \ +$(OBJDIR)/LinearSolverBase.o \ +$(OBJDIR)/gwf3disv8idm.o \ +$(OBJDIR)/GwtDspOptions.o \ +$(OBJDIR)/IdmLogger.o \ +$(OBJDIR)/SmoothingFunctions.o \ +$(OBJDIR)/VirtualDataLists.o \ +$(OBJDIR)/StringList.o \ $(OBJDIR)/version.o \ +$(OBJDIR)/GwfStorageUtils.o \ +$(OBJDIR)/ArrayHandlers.o \ $(OBJDIR)/Message.o \ -$(OBJDIR)/defmacro.o \ +$(OBJDIR)/GwfVscInputData.o \ +$(OBJDIR)/mf6lists.o \ +$(OBJDIR)/IndexMap.o \ +$(OBJDIR)/GwfNpfOptions.o \ +$(OBJDIR)/ObsOutput.o \ +$(OBJDIR)/TimeSeriesRecord.o \ $(OBJDIR)/Sim.o \ -$(OBJDIR)/OpenSpec.o \ +$(OBJDIR)/InterfaceMap.o \ +$(OBJDIR)/MemoryHelper.o \ +$(OBJDIR)/DistributedVariable.o \ +$(OBJDIR)/sort.o \ $(OBJDIR)/InputOutput.o \ +$(OBJDIR)/CircularGeometry.o \ +$(OBJDIR)/ObsOutputList.o \ +$(OBJDIR)/Iunit.o \ +$(OBJDIR)/RectangularGeometry.o \ +$(OBJDIR)/DisvGeom.o \ +$(OBJDIR)/ArrayReaders.o \ +$(OBJDIR)/BlockParser.o \ +$(OBJDIR)/Budget.o \ +$(OBJDIR)/BudgetFileReader.o \ +$(OBJDIR)/PrintSaveManager.o \ +$(OBJDIR)/STLVecInt.o \ +$(OBJDIR)/comarg.o \ +$(OBJDIR)/HeadFileReader.o \ +$(OBJDIR)/InputDefinitionSelector.o \ $(OBJDIR)/TableTerm.o \ +$(OBJDIR)/ModflowInput.o \ +$(OBJDIR)/ArrayReaderBase.o \ +$(OBJDIR)/NameFile.o \ +$(OBJDIR)/StructVector.o \ +$(OBJDIR)/Double2dReader.o \ +$(OBJDIR)/Integer2dReader.o \ +$(OBJDIR)/TimeSeries.o \ +$(OBJDIR)/TimeSeriesFileList.o \ +$(OBJDIR)/ims8base.o \ +$(OBJDIR)/TimeSeriesLink.o \ $(OBJDIR)/Table.o \ -$(OBJDIR)/MemoryHelper.o \ -$(OBJDIR)/CharString.o \ +$(OBJDIR)/Double1dReader.o \ +$(OBJDIR)/Integer1dReader.o \ +$(OBJDIR)/LayeredArrayReader.o \ +$(OBJDIR)/ListReader.o \ +$(OBJDIR)/SfrCrossSectionManager.o \ $(OBJDIR)/Memory.o \ -$(OBJDIR)/List.o \ $(OBJDIR)/MemoryList.o \ -$(OBJDIR)/TimeSeriesRecord.o \ -$(OBJDIR)/BlockParser.o \ $(OBJDIR)/MemoryManager.o \ -$(OBJDIR)/TimeSeries.o \ +$(OBJDIR)/Connections.o \ $(OBJDIR)/ats.o \ -$(OBJDIR)/TimeSeriesLink.o \ -$(OBJDIR)/TimeSeriesFileList.o \ +$(OBJDIR)/MemoryManagerExt.o \ +$(OBJDIR)/MemorySetHandler.o \ $(OBJDIR)/tdis.o \ -$(OBJDIR)/HashTable.o \ -$(OBJDIR)/Sparse.o \ -$(OBJDIR)/DisvGeom.o \ -$(OBJDIR)/ArrayReaders.o \ +$(OBJDIR)/ims8linear.o \ +$(OBJDIR)/SeqVector.o \ +$(OBJDIR)/PackageBudget.o \ +$(OBJDIR)/StructArray.o \ +$(OBJDIR)/MappedMemory.o \ +$(OBJDIR)/BaseModel.o \ +$(OBJDIR)/SparseMatrix.o \ +$(OBJDIR)/GwfMvrPeriodData.o \ +$(OBJDIR)/VirtualBase.o \ +$(OBJDIR)/VirtualDataContainer.o \ +$(OBJDIR)/BaseExchange.o \ +$(OBJDIR)/PackageMover.o \ $(OBJDIR)/TimeSeriesManager.o \ -$(OBJDIR)/SmoothingFunctions.o \ -$(OBJDIR)/MatrixBase.o \ -$(OBJDIR)/ListReader.o \ -$(OBJDIR)/Connections.o \ -$(OBJDIR)/InputDefinition.o \ -$(OBJDIR)/ArrayReaderBase.o \ $(OBJDIR)/DiscretizationBase.o \ -$(OBJDIR)/VectorInt.o \ -$(OBJDIR)/gwt1dspidm.o \ -$(OBJDIR)/gwf3npf8idm.o \ -$(OBJDIR)/gwf3disv8idm.o \ -$(OBJDIR)/gwf3disu8idm.o \ -$(OBJDIR)/gwf3dis8idm.o \ -$(OBJDIR)/Integer2dReader.o \ +$(OBJDIR)/UzfCellGroup.o \ +$(OBJDIR)/BaseSolution.o \ +$(OBJDIR)/OutputControlData.o \ +$(OBJDIR)/LoadMf6FileType.o \ $(OBJDIR)/TimeArray.o \ -$(OBJDIR)/ObsOutput.o \ -$(OBJDIR)/StructVector.o \ -$(OBJDIR)/IdmLogger.o \ -$(OBJDIR)/InputDefinitionSelector.o \ -$(OBJDIR)/Integer1dReader.o \ -$(OBJDIR)/Double2dReader.o \ -$(OBJDIR)/Double1dReader.o \ -$(OBJDIR)/TimeArraySeries.o \ -$(OBJDIR)/ObsOutputList.o \ +$(OBJDIR)/BudgetTerm.o \ +$(OBJDIR)/ImsLinearSolver.o \ +$(OBJDIR)/Mover.o \ +$(OBJDIR)/LinearSolverFactory.o \ +$(OBJDIR)/VirtualSolution.o \ +$(OBJDIR)/NumericalPackage.o \ +$(OBJDIR)/Xt3dInterface.o \ $(OBJDIR)/Observe.o \ -$(OBJDIR)/StructArray.o \ -$(OBJDIR)/ModflowInput.o \ -$(OBJDIR)/LayeredArrayReader.o \ +$(OBJDIR)/IdmMf6FileLoader.o \ +$(OBJDIR)/SolutionGroup.o \ +$(OBJDIR)/OutputControl.o \ +$(OBJDIR)/gwf3tvbase8.o \ +$(OBJDIR)/gwf3ic8.o \ +$(OBJDIR)/BudgetObject.o \ +$(OBJDIR)/gwf3dis8.o \ +$(OBJDIR)/gwf3tvs8.o \ +$(OBJDIR)/TimeArraySeries.o \ +$(OBJDIR)/gwf3tvk8.o \ +$(OBJDIR)/RouterBase.o \ $(OBJDIR)/TimeArraySeriesLink.o \ -$(OBJDIR)/ObsUtility.o \ +$(OBJDIR)/gwf3disu8.o \ $(OBJDIR)/ObsContainer.o \ -$(OBJDIR)/BudgetFileReader.o \ -$(OBJDIR)/LoadMf6FileType.o \ +$(OBJDIR)/gwf3mvr8.o \ $(OBJDIR)/TimeArraySeriesManager.o \ -$(OBJDIR)/PackageMover.o \ +$(OBJDIR)/gwf3disv8.o \ +$(OBJDIR)/ObsUtility.o \ +$(OBJDIR)/GwtSpc.o \ +$(OBJDIR)/gwt1ic1.o \ +$(OBJDIR)/gwf3oc8.o \ +$(OBJDIR)/gwt1oc1.o \ +$(OBJDIR)/gwf3sto8.o \ +$(OBJDIR)/SerialRouter.o \ +$(OBJDIR)/RouterFactory.o \ $(OBJDIR)/Obs3.o \ -$(OBJDIR)/NumericalPackage.o \ -$(OBJDIR)/Budget.o \ -$(OBJDIR)/sort.o \ -$(OBJDIR)/SfrCrossSectionUtils.o \ -$(OBJDIR)/BudgetTerm.o \ -$(OBJDIR)/IdmMf6FileLoader.o \ +$(OBJDIR)/gwf3obs8.o \ $(OBJDIR)/BoundaryPackage.o \ -$(OBJDIR)/BaseModel.o \ -$(OBJDIR)/SfrCrossSectionManager.o \ -$(OBJDIR)/dag_module.o \ -$(OBJDIR)/BudgetObject.o \ +$(OBJDIR)/gwf3csub8.o \ $(OBJDIR)/NumericalModel.o \ -$(OBJDIR)/mf6lists.o \ -$(OBJDIR)/PackageBudget.o \ -$(OBJDIR)/HeadFileReader.o \ -$(OBJDIR)/PrintSaveManager.o \ -$(OBJDIR)/Xt3dAlgorithm.o \ -$(OBJDIR)/gwf3tvbase8.o \ -$(OBJDIR)/gwf3sfr8.o \ +$(OBJDIR)/gwt1cnc1.o \ +$(OBJDIR)/gwf3api8.o \ +$(OBJDIR)/GhostNode.o \ +$(OBJDIR)/gwf3rch8.o \ +$(OBJDIR)/gwf3chd8.o \ $(OBJDIR)/gwf3riv8.o \ -$(OBJDIR)/gwf3maw8.o \ -$(OBJDIR)/gwf3lak8.o \ -$(OBJDIR)/GwfVscInputData.o \ -$(OBJDIR)/gwf3ghb8.o \ -$(OBJDIR)/gwf3drn8.o \ -$(OBJDIR)/DistributedModel.o \ -$(OBJDIR)/BaseExchange.o \ -$(OBJDIR)/UzfCellGroup.o \ $(OBJDIR)/gwt1fmi1.o \ -$(OBJDIR)/OutputControlData.o \ -$(OBJDIR)/gwf3ic8.o \ -$(OBJDIR)/Xt3dInterface.o \ -$(OBJDIR)/gwf3tvk8.o \ -$(OBJDIR)/MemoryManagerExt.o \ -$(OBJDIR)/gwf3vsc8.o \ -$(OBJDIR)/GwfNpfOptions.o \ -$(OBJDIR)/CellWithNbrs.o \ +$(OBJDIR)/gwf3wel8.o \ $(OBJDIR)/NumericalExchange.o \ -$(OBJDIR)/Iunit.o \ +$(OBJDIR)/gwt1ssm1.o \ +$(OBJDIR)/gwt1src1.o \ +$(OBJDIR)/NumericalSolution.o \ +$(OBJDIR)/gwf3sfr8.o \ +$(OBJDIR)/SolutionFactory.o \ +$(OBJDIR)/gwf3drn8.o \ +$(OBJDIR)/gwt1obs1.o \ $(OBJDIR)/gwf3uzf8.o \ -$(OBJDIR)/gwt1apt1.o \ -$(OBJDIR)/GwtSpc.o \ -$(OBJDIR)/OutputControl.o \ -$(OBJDIR)/gwt1ic1.o \ +$(OBJDIR)/gwf3evt8.o \ +$(OBJDIR)/gwf3ghb8.o \ $(OBJDIR)/gwt1mst1.o \ -$(OBJDIR)/GwtDspOptions.o \ -$(OBJDIR)/gwf3npf8.o \ -$(OBJDIR)/GwtAdvOptions.o \ -$(OBJDIR)/gwf3tvs8.o \ -$(OBJDIR)/GwfStorageUtils.o \ -$(OBJDIR)/Mover.o \ -$(OBJDIR)/GwfMvrPeriodData.o \ -$(OBJDIR)/ims8misc.o \ -$(OBJDIR)/GwfBuyInputData.o \ -$(OBJDIR)/InterfaceMap.o \ -$(OBJDIR)/gwf3disu8.o \ -$(OBJDIR)/GridSorting.o \ -$(OBJDIR)/DisConnExchange.o \ -$(OBJDIR)/CsrUtils.o \ -$(OBJDIR)/MappedVariable.o \ +$(OBJDIR)/gwt1adv1.o \ +$(OBJDIR)/gwt1apt1.o \ +$(OBJDIR)/gwf3lak8.o \ +$(OBJDIR)/gwt1mvt1.o \ +$(OBJDIR)/gwf3maw8.o \ +$(OBJDIR)/VirtualModel.o \ $(OBJDIR)/TransportModel.o \ -$(OBJDIR)/NameFile.o \ -$(OBJDIR)/gwt1uzt1.o \ -$(OBJDIR)/gwt1ssm1.o \ -$(OBJDIR)/gwt1src1.o \ +$(OBJDIR)/DisConnExchange.o \ +$(OBJDIR)/gwf3vsc8.o \ +$(OBJDIR)/VirtualGwtModel.o \ +$(OBJDIR)/gwt1ist1.o \ +$(OBJDIR)/CellWithNbrs.o \ $(OBJDIR)/gwt1sft1.o \ -$(OBJDIR)/gwt1oc1.o \ -$(OBJDIR)/gwt1obs1.o \ +$(OBJDIR)/gwt1uzt1.o \ +$(OBJDIR)/GridSorting.o \ +$(OBJDIR)/VirtualExchange.o \ +$(OBJDIR)/gwf3hfb8.o \ +$(OBJDIR)/VirtualGwfExchange.o \ $(OBJDIR)/gwt1mwt1.o \ -$(OBJDIR)/gwt1mvt1.o \ +$(OBJDIR)/VirtualGwfModel.o \ $(OBJDIR)/gwt1lkt1.o \ -$(OBJDIR)/gwt1ist1.o \ +$(OBJDIR)/VirtualGwtExchange.o \ +$(OBJDIR)/gwf3npf8.o \ $(OBJDIR)/gwt1dsp.o \ -$(OBJDIR)/gwt1cnc1.o \ -$(OBJDIR)/gwt1adv1.o \ -$(OBJDIR)/gwf3disv8.o \ -$(OBJDIR)/gwf3dis8.o \ -$(OBJDIR)/gwf3api8.o \ -$(OBJDIR)/gwf3wel8.o \ -$(OBJDIR)/gwf3rch8.o \ -$(OBJDIR)/gwf3sto8.o \ -$(OBJDIR)/gwf3oc8.o \ -$(OBJDIR)/gwf3obs8.o \ -$(OBJDIR)/gwf3mvr8.o \ -$(OBJDIR)/gwf3hfb8.o \ -$(OBJDIR)/gwf3csub8.o \ $(OBJDIR)/gwf3buy8.o \ -$(OBJDIR)/GhostNode.o \ -$(OBJDIR)/gwf3evt8.o \ -$(OBJDIR)/gwf3chd8.o \ -$(OBJDIR)/ims8reordering.o \ -$(OBJDIR)/SparseMatrix.o \ $(OBJDIR)/GridConnection.o \ -$(OBJDIR)/DistributedData.o \ +$(OBJDIR)/SpatialModelConnection.o \ $(OBJDIR)/gwt1.o \ $(OBJDIR)/gwf3.o \ -$(OBJDIR)/ims8base.o \ -$(OBJDIR)/SpatialModelConnection.o \ -$(OBJDIR)/GwtInterfaceModel.o \ +$(OBJDIR)/GwfGwfExchange.o \ +$(OBJDIR)/Mapper.o \ $(OBJDIR)/GwtGwtExchange.o \ +$(OBJDIR)/VirtualDataManager.o \ $(OBJDIR)/GwfInterfaceModel.o \ -$(OBJDIR)/GwfGwfExchange.o \ -$(OBJDIR)/BaseSolution.o \ -$(OBJDIR)/Timer.o \ -$(OBJDIR)/ims8linear.o \ -$(OBJDIR)/GwtGwtConnection.o \ +$(OBJDIR)/GwtInterfaceModel.o \ +$(OBJDIR)/RunControl.o \ +$(OBJDIR)/RunControlFactory.o \ $(OBJDIR)/GwfGwfConnection.o \ -$(OBJDIR)/SolutionGroup.o \ -$(OBJDIR)/NumericalSolution.o \ +$(OBJDIR)/GwtGwtConnection.o \ $(OBJDIR)/GwfGwtExchange.o \ -$(OBJDIR)/SimulationCreate.o \ $(OBJDIR)/ConnectionBuilder.o \ -$(OBJDIR)/comarg.o \ +$(OBJDIR)/SimulationCreate.o \ $(OBJDIR)/mf6core.o \ -$(OBJDIR)/BaseGeometry.o \ -$(OBJDIR)/mf6.o \ -$(OBJDIR)/StringList.o \ -$(OBJDIR)/MemorySetHandler.o \ -$(OBJDIR)/ilut.o \ -$(OBJDIR)/sparsekit.o \ -$(OBJDIR)/rcm.o \ -$(OBJDIR)/blas1_d.o \ -$(OBJDIR)/RectangularGeometry.o \ -$(OBJDIR)/CircularGeometry.o +$(OBJDIR)/mf6.o # Define the objects that make up the program $(PROGRAM) : $(OBJECTS) diff --git a/meson.build b/meson.build index f4e7493ae8e..35ec1961a8e 100644 --- a/meson.build +++ b/meson.build @@ -19,8 +19,9 @@ else profile = 'develop' endif +do_parallel_build = get_option('parallel') message('The used profile is:', profile) - +message('Parallel build:', do_parallel_build) fc = meson.get_compiler('fortran') fc_id = fc.get_id() @@ -33,6 +34,7 @@ if fc_id == 'gcc' compile_args += [ '-fall-intrinsics', '-pedantic', + '-cpp', '-Wcharacter-truncation', '-Wno-unused-dummy-argument', # This makes problems with OOP '-Wno-intrinsic-shadow', # We shadow intrinsics with methods, which should be fine @@ -85,11 +87,38 @@ elif fc_id == 'intel' link_args += '-static-intel' endif +petsc = dependency('PETSc', required : false) +mpi = dependency('mpi', language : 'fortran', required : petsc.found()) + +# on windows only with intel +enable_mpi = do_parallel_build +if build_machine.system() == 'windows' + enable_mpi = do_parallel_build and (fc_id == 'intel-cl') +endif + +# compile with mpi or petsc+mpi when found, +# (not allowing petsc without mpi) +dependencies = [ ] +extra_cmp_args = [ ] +if mpi.found() and enable_mpi + extra_cmp_args = [ '-D__WITH_MPI__' ] + with_mpi = true + dependencies = [ mpi ] +else + with_mpi = false +endif +if petsc.found() and with_mpi + extra_cmp_args = [ '-D__WITH_MPI__', '-D__WITH_PETSC__' ] + with_petsc = true + dependencies = [ petsc, mpi ] +else + with_petsc = false +endif +compile_args += extra_cmp_args + add_project_arguments(fc.get_supported_arguments(compile_args), language: 'fortran') add_project_link_arguments(fc.get_supported_arguments(link_args), language: 'fortran') -mpi = dependency('mpi', language : 'fortran', required : false) - subdir('src') subdir('srcbmi') subdir('utils') diff --git a/meson_options.txt b/meson_options.txt new file mode 100644 index 00000000000..2fd9c44ad78 --- /dev/null +++ b/meson_options.txt @@ -0,0 +1 @@ +option('parallel', type : 'boolean', value : false) \ No newline at end of file diff --git a/msvs/mf6core.vfproj b/msvs/mf6core.vfproj index 6d7f29668ac..22e157f4801 100644 --- a/msvs/mf6core.vfproj +++ b/msvs/mf6core.vfproj @@ -44,6 +44,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -57,16 +92,13 @@ - - + - - @@ -156,9 +188,27 @@ - + + + + + + + + + + + + + + + + + + + @@ -186,6 +236,12 @@ + + + + + + @@ -193,9 +249,6 @@ - - - @@ -217,6 +270,12 @@ + + + + + + @@ -250,17 +309,24 @@ + + - + + + + + + diff --git a/pymake/excludefiles.txt b/pymake/excludefiles.txt index 0b5171d2e2b..cc4f52a2306 100644 --- a/pymake/excludefiles.txt +++ b/pymake/excludefiles.txt @@ -1,4 +1,9 @@ -../src/mf6_duplicate.f90 -../src/bibbab.f90 - - +../src/Solution/PETSc/PetscSolver.F90 +../src/Solution/PETSc/PetscConvergence.F90 +../src/Solution/ParallelSolution.f90 +../src/Utilities/Matrix/PetscMatrix.F90 +../src/Utilities/Vector/PetscVector.F90 +../src/Distributed/MpiMessageBuilder.f90 +../src/Distributed/MpiRouter.f90 +../src/Distributed/MpiRunControl.F90 +../src/Distributed/MpiWorld.f90 diff --git a/pymake/makebin.py b/pymake/makebin.py index 197af0e2cf7..e536066041a 100755 --- a/pymake/makebin.py +++ b/pymake/makebin.py @@ -19,5 +19,7 @@ pmobj.inplace = True pmobj.verbose = True pmobj.makeclean = True +pmobj.excludefiles = "excludefiles.txt" +pmobj.makefile = False pmobj.build() diff --git a/pymake/makefile b/pymake/makefile index 64bfd8e3e89..08ba032bdd5 100644 --- a/pymake/makefile +++ b/pymake/makefile @@ -1,4 +1,4 @@ -# makefile created by pymake (version 1.2.5) for the 'mf6' executable. +# makefile created by pymake (version 1.2.7) for the 'mf6' executable. include ./makedefaults @@ -12,23 +12,26 @@ SOURCEDIR5=../src/Model/ModelUtilities SOURCEDIR6=../src/Model/Connection SOURCEDIR7=../src/Model/GroundWaterTransport SOURCEDIR8=../src/Model/GroundWaterFlow -SOURCEDIR9=../src/Solution -SOURCEDIR10=../src/Solution/LinearMethods -SOURCEDIR11=../src/Timing -SOURCEDIR12=../src/Utilities -SOURCEDIR13=../src/Utilities/TimeSeries -SOURCEDIR14=../src/Utilities/Libraries -SOURCEDIR15=../src/Utilities/Libraries/rcm -SOURCEDIR16=../src/Utilities/Libraries/sparsekit -SOURCEDIR17=../src/Utilities/Libraries/sparskit2 -SOURCEDIR18=../src/Utilities/Libraries/blas -SOURCEDIR19=../src/Utilities/Libraries/daglib -SOURCEDIR20=../src/Utilities/Idm -SOURCEDIR21=../src/Utilities/Matrix -SOURCEDIR22=../src/Utilities/Observation -SOURCEDIR23=../src/Utilities/OutputControl -SOURCEDIR24=../src/Utilities/Memory -SOURCEDIR25=../src/Utilities/ArrayRead +SOURCEDIR9=../src/Distributed +SOURCEDIR10=../src/Solution +SOURCEDIR11=../src/Solution/PETSc +SOURCEDIR12=../src/Solution/LinearMethods +SOURCEDIR13=../src/Timing +SOURCEDIR14=../src/Utilities +SOURCEDIR15=../src/Utilities/TimeSeries +SOURCEDIR16=../src/Utilities/Libraries +SOURCEDIR17=../src/Utilities/Libraries/rcm +SOURCEDIR18=../src/Utilities/Libraries/sparsekit +SOURCEDIR19=../src/Utilities/Libraries/sparskit2 +SOURCEDIR20=../src/Utilities/Libraries/blas +SOURCEDIR21=../src/Utilities/Libraries/daglib +SOURCEDIR22=../src/Utilities/Idm +SOURCEDIR23=../src/Utilities/Matrix +SOURCEDIR24=../src/Utilities/Vector +SOURCEDIR25=../src/Utilities/Observation +SOURCEDIR26=../src/Utilities/OutputControl +SOURCEDIR27=../src/Utilities/Memory +SOURCEDIR28=../src/Utilities/ArrayRead VPATH = \ ${SOURCEDIR1} \ @@ -55,196 +58,225 @@ ${SOURCEDIR21} \ ${SOURCEDIR22} \ ${SOURCEDIR23} \ ${SOURCEDIR24} \ -${SOURCEDIR25} +${SOURCEDIR25} \ +${SOURCEDIR26} \ +${SOURCEDIR27} \ +${SOURCEDIR28} .SUFFIXES: .f90 .F90 .o OBJECTS = \ -$(OBJDIR)/rcm.o \ -$(OBJDIR)/CharString.o \ -$(OBJDIR)/OpenSpec.o \ -$(OBJDIR)/kind.o \ -$(OBJDIR)/InputDefinition.o \ $(OBJDIR)/sparsekit.o \ $(OBJDIR)/ilut.o \ -$(OBJDIR)/CsrUtils.o \ -$(OBJDIR)/gwf3dis8idm.o \ -$(OBJDIR)/BaseGeometry.o \ -$(OBJDIR)/Constants.o \ -$(OBJDIR)/SimVariables.o \ -$(OBJDIR)/SfrCrossSectionUtils.o \ -$(OBJDIR)/ims8misc.o \ -$(OBJDIR)/GwtAdvOptions.o \ -$(OBJDIR)/HashTable.o \ -$(OBJDIR)/SmoothingFunctions.o \ -$(OBJDIR)/ims8reordering.o \ -$(OBJDIR)/gwf3npf8idm.o \ -$(OBJDIR)/Sparse.o \ $(OBJDIR)/blas1_d.o \ -$(OBJDIR)/gwt1dspidm.o \ -$(OBJDIR)/IdmLogger.o \ +$(OBJDIR)/kind.o \ +$(OBJDIR)/Sparse.o \ $(OBJDIR)/dag_module.o \ -$(OBJDIR)/GwfNpfOptions.o \ -$(OBJDIR)/gwf3disv8idm.o \ -$(OBJDIR)/GwtDspOptions.o \ +$(OBJDIR)/OpenSpec.o \ +$(OBJDIR)/GwtAdvOptions.o \ +$(OBJDIR)/InputDefinition.o \ +$(OBJDIR)/rcm.o \ +$(OBJDIR)/VectorBase.o \ +$(OBJDIR)/gwf3dis8idm.o \ +$(OBJDIR)/CharString.o \ +$(OBJDIR)/ims8reordering.o \ +$(OBJDIR)/Constants.o \ +$(OBJDIR)/BaseGeometry.o \ +$(OBJDIR)/compilerversion.o \ $(OBJDIR)/gwf3disu8idm.o \ -$(OBJDIR)/InterfaceMap.o \ -$(OBJDIR)/Xt3dAlgorithm.o \ $(OBJDIR)/GwfBuyInputData.o \ -$(OBJDIR)/compilerversion.o \ -$(OBJDIR)/GwfStorageUtils.o \ +$(OBJDIR)/HashTable.o \ +$(OBJDIR)/SimStages.o \ $(OBJDIR)/defmacro.o \ +$(OBJDIR)/CsrUtils.o \ +$(OBJDIR)/SimVariables.o \ $(OBJDIR)/genericutils.o \ +$(OBJDIR)/gwf3npf8idm.o \ +$(OBJDIR)/gwt1dspidm.o \ +$(OBJDIR)/Xt3dAlgorithm.o \ +$(OBJDIR)/Timer.o \ +$(OBJDIR)/SfrCrossSectionUtils.o \ +$(OBJDIR)/List.o \ +$(OBJDIR)/ims8misc.o \ $(OBJDIR)/MatrixBase.o \ +$(OBJDIR)/LinearSolverBase.o \ +$(OBJDIR)/gwf3disv8idm.o \ +$(OBJDIR)/GwtDspOptions.o \ +$(OBJDIR)/IdmLogger.o \ +$(OBJDIR)/SmoothingFunctions.o \ +$(OBJDIR)/VirtualDataLists.o \ +$(OBJDIR)/StringList.o \ $(OBJDIR)/version.o \ -$(OBJDIR)/Timer.o \ +$(OBJDIR)/GwfStorageUtils.o \ $(OBJDIR)/ArrayHandlers.o \ -$(OBJDIR)/List.o \ +$(OBJDIR)/Message.o \ +$(OBJDIR)/GwfVscInputData.o \ $(OBJDIR)/mf6lists.o \ -$(OBJDIR)/StringList.o \ +$(OBJDIR)/IndexMap.o \ +$(OBJDIR)/GwfNpfOptions.o \ $(OBJDIR)/ObsOutput.o \ $(OBJDIR)/TimeSeriesRecord.o \ -$(OBJDIR)/Message.o \ $(OBJDIR)/Sim.o \ +$(OBJDIR)/InterfaceMap.o \ +$(OBJDIR)/MemoryHelper.o \ +$(OBJDIR)/DistributedVariable.o \ $(OBJDIR)/sort.o \ -$(OBJDIR)/VectorInt.o \ $(OBJDIR)/InputOutput.o \ -$(OBJDIR)/InputDefinitionSelector.o \ -$(OBJDIR)/comarg.o \ -$(OBJDIR)/Iunit.o \ -$(OBJDIR)/BudgetFileReader.o \ -$(OBJDIR)/MemoryHelper.o \ +$(OBJDIR)/CircularGeometry.o \ $(OBJDIR)/ObsOutputList.o \ -$(OBJDIR)/HeadFileReader.o \ +$(OBJDIR)/Iunit.o \ +$(OBJDIR)/RectangularGeometry.o \ $(OBJDIR)/DisvGeom.o \ -$(OBJDIR)/PrintSaveManager.o \ -$(OBJDIR)/ModflowInput.o \ $(OBJDIR)/ArrayReaders.o \ -$(OBJDIR)/CircularGeometry.o \ -$(OBJDIR)/StructVector.o \ -$(OBJDIR)/RectangularGeometry.o \ $(OBJDIR)/BlockParser.o \ $(OBJDIR)/Budget.o \ -$(OBJDIR)/NameFile.o \ -$(OBJDIR)/ArrayReaderBase.o \ -$(OBJDIR)/ims8base.o \ -$(OBJDIR)/Double1dReader.o \ -$(OBJDIR)/TimeSeries.o \ +$(OBJDIR)/BudgetFileReader.o \ +$(OBJDIR)/PrintSaveManager.o \ +$(OBJDIR)/STLVecInt.o \ +$(OBJDIR)/comarg.o \ +$(OBJDIR)/HeadFileReader.o \ +$(OBJDIR)/InputDefinitionSelector.o \ $(OBJDIR)/TableTerm.o \ +$(OBJDIR)/ModflowInput.o \ +$(OBJDIR)/ArrayReaderBase.o \ +$(OBJDIR)/NameFile.o \ +$(OBJDIR)/StructVector.o \ $(OBJDIR)/Double2dReader.o \ +$(OBJDIR)/Integer2dReader.o \ +$(OBJDIR)/TimeSeries.o \ $(OBJDIR)/TimeSeriesFileList.o \ +$(OBJDIR)/ims8base.o \ $(OBJDIR)/TimeSeriesLink.o \ -$(OBJDIR)/Integer2dReader.o \ $(OBJDIR)/Table.o \ +$(OBJDIR)/Double1dReader.o \ +$(OBJDIR)/Integer1dReader.o \ +$(OBJDIR)/LayeredArrayReader.o \ $(OBJDIR)/ListReader.o \ $(OBJDIR)/SfrCrossSectionManager.o \ -$(OBJDIR)/Integer1dReader.o \ $(OBJDIR)/Memory.o \ -$(OBJDIR)/LayeredArrayReader.o \ $(OBJDIR)/MemoryList.o \ $(OBJDIR)/MemoryManager.o \ -$(OBJDIR)/GwfMvrPeriodData.o \ -$(OBJDIR)/SparseMatrix.o \ -$(OBJDIR)/ims8linear.o \ +$(OBJDIR)/Connections.o \ $(OBJDIR)/ats.o \ $(OBJDIR)/MemoryManagerExt.o \ -$(OBJDIR)/MappedVariable.o \ $(OBJDIR)/MemorySetHandler.o \ $(OBJDIR)/tdis.o \ -$(OBJDIR)/UzfCellGroup.o \ -$(OBJDIR)/BaseModel.o \ -$(OBJDIR)/TimeSeriesManager.o \ -$(OBJDIR)/DistributedData.o \ +$(OBJDIR)/ims8linear.o \ +$(OBJDIR)/SeqVector.o \ $(OBJDIR)/PackageBudget.o \ -$(OBJDIR)/PackageMover.o \ $(OBJDIR)/StructArray.o \ -$(OBJDIR)/Connections.o \ +$(OBJDIR)/MappedMemory.o \ +$(OBJDIR)/BaseModel.o \ +$(OBJDIR)/SparseMatrix.o \ +$(OBJDIR)/GwfMvrPeriodData.o \ +$(OBJDIR)/VirtualBase.o \ +$(OBJDIR)/VirtualDataContainer.o \ $(OBJDIR)/BaseExchange.o \ -$(OBJDIR)/LoadMf6FileType.o \ +$(OBJDIR)/PackageMover.o \ +$(OBJDIR)/TimeSeriesManager.o \ $(OBJDIR)/DiscretizationBase.o \ -$(OBJDIR)/Mover.o \ +$(OBJDIR)/UzfCellGroup.o \ $(OBJDIR)/BaseSolution.o \ +$(OBJDIR)/OutputControlData.o \ +$(OBJDIR)/LoadMf6FileType.o \ +$(OBJDIR)/TimeArray.o \ +$(OBJDIR)/BudgetTerm.o \ +$(OBJDIR)/ImsLinearSolver.o \ +$(OBJDIR)/Mover.o \ +$(OBJDIR)/LinearSolverFactory.o \ +$(OBJDIR)/VirtualSolution.o \ +$(OBJDIR)/NumericalPackage.o \ $(OBJDIR)/Xt3dInterface.o \ $(OBJDIR)/Observe.o \ $(OBJDIR)/IdmMf6FileLoader.o \ $(OBJDIR)/SolutionGroup.o \ -$(OBJDIR)/NumericalPackage.o \ -$(OBJDIR)/gwf3dis8.o \ -$(OBJDIR)/gwf3disu8.o \ +$(OBJDIR)/OutputControl.o \ $(OBJDIR)/gwf3tvbase8.o \ -$(OBJDIR)/TimeArray.o \ -$(OBJDIR)/OutputControlData.o \ +$(OBJDIR)/gwf3ic8.o \ +$(OBJDIR)/BudgetObject.o \ +$(OBJDIR)/gwf3dis8.o \ +$(OBJDIR)/gwf3tvs8.o \ $(OBJDIR)/TimeArraySeries.o \ +$(OBJDIR)/gwf3tvk8.o \ +$(OBJDIR)/RouterBase.o \ $(OBJDIR)/TimeArraySeriesLink.o \ -$(OBJDIR)/BudgetTerm.o \ -$(OBJDIR)/TimeArraySeriesManager.o \ -$(OBJDIR)/gwf3ic8.o \ +$(OBJDIR)/gwf3disu8.o \ $(OBJDIR)/ObsContainer.o \ -$(OBJDIR)/gwf3hfb8.o \ -$(OBJDIR)/gwt1ic1.o \ -$(OBJDIR)/ObsUtility.o \ -$(OBJDIR)/gwf3tvk8.o \ +$(OBJDIR)/gwf3mvr8.o \ +$(OBJDIR)/TimeArraySeriesManager.o \ $(OBJDIR)/gwf3disv8.o \ -$(OBJDIR)/gwf3tvs8.o \ -$(OBJDIR)/gwf3sto8.o \ -$(OBJDIR)/OutputControl.o \ +$(OBJDIR)/ObsUtility.o \ $(OBJDIR)/GwtSpc.o \ -$(OBJDIR)/BudgetObject.o \ -$(OBJDIR)/Obs3.o \ +$(OBJDIR)/gwt1ic1.o \ $(OBJDIR)/gwf3oc8.o \ $(OBJDIR)/gwt1oc1.o \ +$(OBJDIR)/gwf3sto8.o \ +$(OBJDIR)/SerialRouter.o \ +$(OBJDIR)/RouterFactory.o \ +$(OBJDIR)/Obs3.o \ $(OBJDIR)/gwf3obs8.o \ -$(OBJDIR)/gwf3mvr8.o \ -$(OBJDIR)/gwf3npf8.o \ -$(OBJDIR)/gwf3csub8.o \ -$(OBJDIR)/gwt1obs1.o \ $(OBJDIR)/BoundaryPackage.o \ -$(OBJDIR)/gwf3uzf8.o \ -$(OBJDIR)/gwf3api8.o \ +$(OBJDIR)/gwf3csub8.o \ +$(OBJDIR)/NumericalModel.o \ $(OBJDIR)/gwt1cnc1.o \ -$(OBJDIR)/gwf3ghb8.o \ +$(OBJDIR)/gwf3api8.o \ +$(OBJDIR)/GhostNode.o \ +$(OBJDIR)/gwf3rch8.o \ +$(OBJDIR)/gwf3chd8.o \ +$(OBJDIR)/gwf3riv8.o \ $(OBJDIR)/gwt1fmi1.o \ -$(OBJDIR)/gwf3lak8.o \ -$(OBJDIR)/gwt1src1.o \ $(OBJDIR)/gwf3wel8.o \ +$(OBJDIR)/NumericalExchange.o \ +$(OBJDIR)/gwt1ssm1.o \ +$(OBJDIR)/gwt1src1.o \ +$(OBJDIR)/NumericalSolution.o \ $(OBJDIR)/gwf3sfr8.o \ -$(OBJDIR)/gwf3chd8.o \ -$(OBJDIR)/gwf3maw8.o \ -$(OBJDIR)/NumericalModel.o \ -$(OBJDIR)/gwt1dsp.o \ +$(OBJDIR)/SolutionFactory.o \ $(OBJDIR)/gwf3drn8.o \ -$(OBJDIR)/gwf3riv8.o \ -$(OBJDIR)/gwt1mvt1.o \ +$(OBJDIR)/gwt1obs1.o \ +$(OBJDIR)/gwf3uzf8.o \ $(OBJDIR)/gwf3evt8.o \ -$(OBJDIR)/gwf3rch8.o \ -$(OBJDIR)/TransportModel.o \ -$(OBJDIR)/gwt1apt1.o \ -$(OBJDIR)/gwt1ssm1.o \ +$(OBJDIR)/gwf3ghb8.o \ $(OBJDIR)/gwt1mst1.o \ -$(OBJDIR)/gwt1uzt1.o \ $(OBJDIR)/gwt1adv1.o \ -$(OBJDIR)/DistributedModel.o \ -$(OBJDIR)/gwt1ist1.o \ -$(OBJDIR)/gwt1lkt1.o \ -$(OBJDIR)/NumericalExchange.o \ -$(OBJDIR)/gwt1sft1.o \ +$(OBJDIR)/gwt1apt1.o \ +$(OBJDIR)/gwf3lak8.o \ +$(OBJDIR)/gwt1mvt1.o \ +$(OBJDIR)/gwf3maw8.o \ +$(OBJDIR)/VirtualModel.o \ +$(OBJDIR)/TransportModel.o \ $(OBJDIR)/DisConnExchange.o \ -$(OBJDIR)/GhostNode.o \ -$(OBJDIR)/gwf3buy8.o \ -$(OBJDIR)/NumericalSolution.o \ -$(OBJDIR)/gwt1mwt1.o \ +$(OBJDIR)/gwf3vsc8.o \ +$(OBJDIR)/VirtualGwtModel.o \ +$(OBJDIR)/gwt1ist1.o \ $(OBJDIR)/CellWithNbrs.o \ +$(OBJDIR)/gwt1sft1.o \ +$(OBJDIR)/gwt1uzt1.o \ $(OBJDIR)/GridSorting.o \ +$(OBJDIR)/VirtualExchange.o \ +$(OBJDIR)/gwf3hfb8.o \ +$(OBJDIR)/VirtualGwfExchange.o \ +$(OBJDIR)/gwt1mwt1.o \ +$(OBJDIR)/VirtualGwfModel.o \ +$(OBJDIR)/gwt1lkt1.o \ +$(OBJDIR)/VirtualGwtExchange.o \ +$(OBJDIR)/gwf3npf8.o \ +$(OBJDIR)/gwt1dsp.o \ +$(OBJDIR)/gwf3buy8.o \ +$(OBJDIR)/GridConnection.o \ +$(OBJDIR)/SpatialModelConnection.o \ $(OBJDIR)/gwt1.o \ $(OBJDIR)/gwf3.o \ -$(OBJDIR)/GwtGwtExchange.o \ $(OBJDIR)/GwfGwfExchange.o \ -$(OBJDIR)/GridConnection.o \ -$(OBJDIR)/GwtInterfaceModel.o \ -$(OBJDIR)/SpatialModelConnection.o \ +$(OBJDIR)/Mapper.o \ +$(OBJDIR)/GwtGwtExchange.o \ +$(OBJDIR)/VirtualDataManager.o \ $(OBJDIR)/GwfInterfaceModel.o \ -$(OBJDIR)/GwtGwtConnection.o \ +$(OBJDIR)/GwtInterfaceModel.o \ +$(OBJDIR)/RunControl.o \ +$(OBJDIR)/RunControlFactory.o \ $(OBJDIR)/GwfGwfConnection.o \ +$(OBJDIR)/GwtGwtConnection.o \ $(OBJDIR)/GwfGwtExchange.o \ $(OBJDIR)/ConnectionBuilder.o \ $(OBJDIR)/SimulationCreate.o \ diff --git a/src/Distributed/IndexMap.f90 b/src/Distributed/IndexMap.f90 new file mode 100644 index 00000000000..951649eda59 --- /dev/null +++ b/src/Distributed/IndexMap.f90 @@ -0,0 +1,86 @@ +module IndexMapModule + use KindModule, only: I4B + use ArrayHandlersModule, only: ConcatArray + implicit none + private + + type, public :: IndexMapType + integer(I4B), dimension(:), pointer, contiguous :: src_idx => null() + integer(I4B), dimension(:), pointer, contiguous :: tgt_idx => null() + contains + procedure :: add => add_map + procedure :: copy => copy_map + procedure, private :: add_map + procedure, private :: copy_map + end type IndexMapType + + type, public :: IndexMapSgnType + integer(I4B), dimension(:), pointer, contiguous :: src_idx => null() + integer(I4B), dimension(:), pointer, contiguous :: tgt_idx => null() + integer(I4B), dimension(:), pointer, contiguous :: sign => null() + contains + procedure :: add => add_signed_map + procedure :: copy => copy_signed_map + procedure, private :: add_signed_map + procedure, private :: copy_signed_map + end type IndexMapSgnType + +contains + + subroutine add_map(this, map) + class(IndexMapType) :: this + class(IndexMapType) :: map + + call ConcatArray(this%src_idx, map%src_idx) + call ConcatArray(this%tgt_idx, map%tgt_idx) + + end subroutine add_map + + subroutine copy_map(this, map) + class(IndexMapType) :: this + class(IndexMapType) :: map + ! local + integer(I4B) :: i + + allocate (this%src_idx(size(map%src_idx))) + allocate (this%tgt_idx(size(map%tgt_idx))) + do i = 1, size(map%src_idx) + this%src_idx(i) = map%src_idx(i) + end do + do i = 1, size(map%tgt_idx) + this%tgt_idx(i) = map%tgt_idx(i) + end do + + end subroutine copy_map + + subroutine add_signed_map(this, signed_map) + class(IndexMapSgnType) :: this + class(IndexMapSgnType) :: signed_map + + call ConcatArray(this%src_idx, signed_map%src_idx) + call ConcatArray(this%tgt_idx, signed_map%tgt_idx) + call ConcatArray(this%sign, signed_map%sign) + + end subroutine add_signed_map + + subroutine copy_signed_map(this, signed_map) + class(IndexMapSgnType) :: this + class(IndexMapSgnType) :: signed_map + ! local + integer(I4B) :: i + + allocate (this%src_idx(size(signed_map%src_idx))) + allocate (this%tgt_idx(size(signed_map%tgt_idx))) + allocate (this%sign(size(signed_map%sign))) + do i = 1, size(signed_map%src_idx) + this%src_idx(i) = signed_map%src_idx(i) + end do + do i = 1, size(signed_map%tgt_idx) + this%tgt_idx(i) = signed_map%tgt_idx(i) + end do + do i = 1, size(signed_map%sign) + this%sign(i) = signed_map%sign(i) + end do + + end subroutine copy_signed_map +end module IndexMapModule diff --git a/src/Distributed/InterfaceMap.f90 b/src/Distributed/InterfaceMap.f90 new file mode 100644 index 00000000000..e006156ab93 --- /dev/null +++ b/src/Distributed/InterfaceMap.f90 @@ -0,0 +1,132 @@ +module InterfaceMapModule + use KindModule, only: I4B + use ConstantsModule, only: LENMODELNAME, LENEXCHANGENAME + use IndexMapModule + + implicit none + private + + type, public :: InterfaceMapType + integer(I4B) :: nr_models + integer(I4B), dimension(:), pointer, contiguous :: model_ids => null() + character(len=LENMODELNAME), dimension(:), & + pointer, contiguous :: model_names => null() + integer(I4B) :: nr_exchanges + integer(I4B), dimension(:), pointer, contiguous :: exchange_ids => null() + character(len=LENEXCHANGENAME), dimension(:), & + pointer, contiguous :: exchange_names => null() + integer(I4B) :: prim_exg_idx + type(IndexMapType), dimension(:), pointer :: node_map => null() + type(IndexMapType), dimension(:), pointer :: connection_map => null() + type(IndexMapSgnType), dimension(:), pointer :: exchange_map => null() + contains + procedure :: init + procedure :: add + procedure :: destroy + end type InterfaceMapType + +contains + + subroutine init(this, nr_models, nr_exchanges) + class(InterfaceMapType) :: this + integer(I4B) :: nr_models + integer(I4B) :: nr_exchanges + + this%nr_models = nr_models + this%nr_exchanges = nr_exchanges + + allocate (this%model_ids(nr_models)) + allocate (this%model_names(nr_models)) + allocate (this%exchange_ids(nr_exchanges)) + allocate (this%exchange_names(nr_exchanges)) + + allocate (this%node_map(nr_models)) + allocate (this%connection_map(nr_models)) + allocate (this%exchange_map(nr_exchanges)) + + ! model id == -1 when not set + this%model_ids = -1 + this%exchange_ids = -1 + + end subroutine init + + !> @ Adds a map, either by extending the existing map + !! for a certain model or exchange, or by assigning + !! the map to an empty slot. + !! + !! The map to which is added, should be properly + !< initialized beforehand + subroutine add(this, map_to_add) + use ArrayHandlersModule, only: ExtendPtrArray, ifind + class(InterfaceMapType) :: this + class(InterfaceMapType) :: map_to_add + ! local + integer(I4B) :: im, ie + integer(I4B) :: m_id, m_index + integer(I4B) :: e_id, e_index + + ! add models + do im = 1, map_to_add%nr_models + m_id = map_to_add%model_ids(im) + m_index = ifind(this%model_ids, m_id) + if (m_index > 0) then + ! extend existing index map + call this%node_map(m_index)%add(map_to_add%node_map(im)) + call this%connection_map(m_index)%add(map_to_add%connection_map(im)) + else + ! place in first empty spot + m_index = ifind(this%model_ids, -1) + this%model_ids(m_index) = m_id + this%model_names(m_index) = map_to_add%model_names(im) + call this%node_map(m_index)%copy(map_to_add%node_map(im)) + call this%connection_map(m_index)%copy(map_to_add%connection_map(im)) + end if + end do + + ! add exchanges + do ie = 1, map_to_add%nr_exchanges + e_id = map_to_add%exchange_ids(ie) + e_index = ifind(this%exchange_ids, e_id) + if (e_index > 0) then + ! extend existing index map + call this%exchange_map(e_index)%add(map_to_add%exchange_map(ie)) + else + ! place in first empty spot + e_index = ifind(this%exchange_ids, -1) + this%exchange_ids(e_index) = e_id + this%exchange_names(e_index) = map_to_add%exchange_names(ie) + call this%exchange_map(e_index)%copy(map_to_add%exchange_map(ie)) + end if + end do + + end subroutine add + + subroutine destroy(this) + class(InterfaceMapType) :: this + ! local + integer(I4B) :: i + + deallocate (this%model_ids) + deallocate (this%model_names) + deallocate (this%exchange_ids) + deallocate (this%exchange_names) + + do i = 1, this%nr_models + deallocate (this%node_map(i)%src_idx) + deallocate (this%node_map(i)%tgt_idx) + deallocate (this%connection_map(i)%src_idx) + deallocate (this%connection_map(i)%tgt_idx) + end do + deallocate (this%node_map) + deallocate (this%connection_map) + + do i = 1, this%nr_exchanges + deallocate (this%exchange_map(i)%src_idx) + deallocate (this%exchange_map(i)%tgt_idx) + deallocate (this%exchange_map(i)%sign) + end do + deallocate (this%exchange_map) + + end subroutine destroy + +end module InterfaceMapModule diff --git a/src/Model/Connection/MappedVariable.f90 b/src/Distributed/MappedMemory.f90 similarity index 52% rename from src/Model/Connection/MappedVariable.f90 rename to src/Distributed/MappedMemory.f90 index 7f0a3abc574..f8db3db46e0 100644 --- a/src/Model/Connection/MappedVariable.f90 +++ b/src/Distributed/MappedMemory.f90 @@ -1,4 +1,4 @@ -module MappedVariableModule +module MappedMemoryModule use KindModule, only: I4B, LGP use ConstantsModule, only: LENMEMPATH, LENVARNAME use MemoryTypeModule, only: MemoryType @@ -7,10 +7,10 @@ module MappedVariableModule implicit none private - public :: CastAsMappedVariable - public :: MappedVariableType + public :: CastAsMappedData + public :: MappedMemoryType - type :: MappedVariableType + type :: MappedMemoryType integer(I4B) :: controller_id integer(I4B) :: sync_stage character(len=LENVARNAME) :: src_name @@ -19,6 +19,7 @@ module MappedVariableModule character(len=LENVARNAME) :: tgt_name character(len=LENMEMPATH) :: tgt_path type(MemoryType), pointer :: tgt !< cached memory item + logical(LGP) :: copy_all !< when true: copy all elements integer(I4B), dimension(:), pointer :: src_idx !< source indexes to copy from integer(I4B), dimension(:), pointer :: tgt_idx !< target indexes to copy to integer(I4B), dimension(:), pointer :: sign !< optional sign (or null) to negate copied value @@ -33,12 +34,12 @@ module MappedVariableModule procedure, private :: sync_dbl2d procedure, private :: apply_sgn_dbl2d - end type MappedVariableType + end type MappedMemoryType contains subroutine sync(this) - class(MappedVariableType) :: this + class(MappedMemoryType) :: this ! local logical(LGP) :: found @@ -63,104 +64,144 @@ subroutine sync(this) end subroutine sync function skip_sync(this) result(skip) - class(MappedVariableType) :: this + class(MappedMemoryType) :: this logical(LGP) :: skip skip = (this%src%isize == 0) end function skip_sync - !> @brief Copy 1d integer array with map. - !< TODO_MJR: should this maybe move to the memory manager for more convenient maintenance? + !> @brief Copy 1d integer array with map subroutine sync_int1d(this) - class(MappedVariableType) :: this + class(MappedMemoryType) :: this ! local integer(I4B) :: i - do i = 1, size(this%tgt_idx) - this%tgt%aint1d(this%tgt_idx(i)) = this%src%aint1d(this%src_idx(i)) - end do + if (this%copy_all) then + do i = 1, this%tgt%isize + this%tgt%aint1d(i) = this%src%aint1d(i) + end do + else + do i = 1, size(this%tgt_idx) + this%tgt%aint1d(this%tgt_idx(i)) = this%src%aint1d(this%src_idx(i)) + end do + end if end subroutine sync_int1d subroutine apply_sgn_int1d(this) - class(MappedVariableType) :: this + class(MappedMemoryType) :: this ! local integer(I4B) :: i - do i = 1, size(this%tgt_idx) - this%tgt%aint1d(this%tgt_idx(i)) = this%tgt%aint1d(this%tgt_idx(i)) * & - this%sign(i) - end do + if (this%copy_all) then + do i = 1, this%tgt%isize + this%tgt%aint1d(i) = this%tgt%aint1d(i) * this%sign(i) + end do + else + do i = 1, size(this%tgt_idx) + this%tgt%aint1d(this%tgt_idx(i)) = this%tgt%aint1d(this%tgt_idx(i)) * & + this%sign(i) + end do + end if end subroutine apply_sgn_int1d !> @brief Copy 1d double array with map. !< subroutine sync_dbl1d(this) - class(MappedVariableType) :: this + class(MappedMemoryType) :: this ! local integer(I4B) :: i - do i = 1, size(this%tgt_idx) - this%tgt%adbl1d(this%tgt_idx(i)) = this%src%adbl1d(this%src_idx(i)) - end do + if (this%copy_all) then + do i = 1, this%tgt%isize + this%tgt%adbl1d(i) = this%src%adbl1d(i) + end do + else + do i = 1, size(this%tgt_idx) + this%tgt%adbl1d(this%tgt_idx(i)) = this%src%adbl1d(this%src_idx(i)) + end do + end if end subroutine sync_dbl1d subroutine apply_sgn_dbl1d(this) - class(MappedVariableType) :: this + class(MappedMemoryType) :: this ! local integer(I4B) :: i - do i = 1, size(this%tgt_idx) - this%tgt%adbl1d(this%tgt_idx(i)) = this%tgt%adbl1d(this%tgt_idx(i)) * & - this%sign(i) - end do + if (this%copy_all) then + do i = 1, this%tgt%isize + this%tgt%adbl1d(i) = this%tgt%adbl1d(i) * this%sign(i) + end do + else + do i = 1, size(this%tgt_idx) + this%tgt%adbl1d(this%tgt_idx(i)) = this%tgt%adbl1d(this%tgt_idx(i)) * & + this%sign(i) + end do + end if end subroutine apply_sgn_dbl1d !> @brief Copy 2d double array with map. !< NB: only dim=2 is mapped. subroutine sync_dbl2d(this) - class(MappedVariableType) :: this + class(MappedMemoryType) :: this ! local integer(I4B) :: i, k - do i = 1, size(this%tgt_idx) - do k = 1, size(this%src%adbl2d, dim=1) - this%tgt%adbl2d(k, this%tgt_idx(i)) = this%src%adbl2d(k, this%src_idx(i)) + if (this%copy_all) then + do i = 1, this%tgt%isize + do k = 1, size(this%src%adbl2d, dim=1) + this%tgt%adbl2d(k, i) = this%src%adbl2d(k, i) + end do end do - end do + else + do i = 1, size(this%tgt_idx) + do k = 1, size(this%src%adbl2d, dim=1) + this%tgt%adbl2d(k, this%tgt_idx(i)) = & + this%src%adbl2d(k, this%src_idx(i)) + end do + end do + end if end subroutine sync_dbl2d subroutine apply_sgn_dbl2d(this) - class(MappedVariableType) :: this + class(MappedMemoryType) :: this ! local integer(I4B) :: i, k - do i = 1, size(this%tgt_idx) - do k = 1, size(this%src%adbl2d, dim=1) - this%tgt%adbl2d(k, this%tgt_idx(i)) = & - this%tgt%adbl2d(k, this%tgt_idx(i)) * this%sign(i) + if (this%copy_all) then + do i = 1, this%tgt%isize + do k = 1, size(this%src%adbl2d, dim=1) + this%tgt%adbl2d(k, i) = this%tgt%adbl2d(k, i) * this%sign(i) + end do end do - end do + else + do i = 1, size(this%tgt_idx) + do k = 1, size(this%src%adbl2d, dim=1) + this%tgt%adbl2d(k, this%tgt_idx(i)) = & + this%tgt%adbl2d(k, this%tgt_idx(i)) * this%sign(i) + end do + end do + end if end subroutine apply_sgn_dbl2d - function CastAsMappedVariable(obj) result(res) + function CastAsMappedData(obj) result(res) implicit none class(*), pointer, intent(inout) :: obj - class(MappedVariableType), pointer :: res + class(MappedMemoryType), pointer :: res res => null() select type (obj) - class is (MappedVariableType) + class is (MappedMemoryType) res => obj end select - end function CastAsMappedVariable + end function CastAsMappedData -end module MappedVariableModule +end module MappedMemoryModule diff --git a/src/Distributed/Mapper.f90 b/src/Distributed/Mapper.f90 new file mode 100644 index 00000000000..aa7fc091d49 --- /dev/null +++ b/src/Distributed/Mapper.f90 @@ -0,0 +1,320 @@ +module MapperModule + use KindModule, only: I4B, LGP + use ConstantsModule, only: LENVARNAME, LENMEMPATH + use MemoryHelperModule, only: create_mem_path + use IndexMapModule + use VirtualModelModule, only: VirtualModelType, get_virtual_model + use VirtualExchangeModule, only: VirtualExchangeType, get_virtual_exchange + use InterfaceMapModule + use DistVariableModule + use MappedMemoryModule + use ListModule + implicit none + private + + public :: MapperType + + type :: MapperType + type(ListType) :: mapped_data_list + contains + procedure :: init + procedure :: add_exchange_vars + procedure :: add_interface_vars + procedure :: scatter + procedure :: destroy + + procedure, private :: add_dist_vars + procedure, private :: map_model_data + procedure, private :: map_exg_data + procedure, private :: map_data + procedure, private :: map_data_full + end type MapperType + +contains + + subroutine init(this) + class(MapperType) :: this + + end subroutine init + + !> @brief Add virtual exchange variables + !< + subroutine add_exchange_vars(this) + use SimStagesModule + use ListsModule, only: baseconnectionlist + use SpatialModelConnectionModule, only: SpatialModelConnectionType, & + get_smc_from_list + use VirtualExchangeModule, only: VirtualExchangeType, get_virtual_exchange + class(MapperType) :: this + ! local + integer(I4B) :: iconn + class(SpatialModelConnectionType), pointer :: conn + class(VirtualExchangeType), pointer :: virt_exg + character(len=LENMEMPATH) :: virt_mem_path + + do iconn = 1, baseconnectionlist%Count() + conn => get_smc_from_list(baseconnectionlist, iconn) + virt_exg => get_virtual_exchange(conn%prim_exchange%id) + if (.not. virt_exg%v_model1%is_local) then + virt_mem_path = virt_exg%get_vrt_mem_path('NODEM1', '') + call this%map_data_full(0, 'NODEM1', conn%prim_exchange%memoryPath, & + 'NODEM1', virt_mem_path, (/STG_BEFORE_DF/)) + end if + if (.not. virt_exg%v_model2%is_local) then + virt_mem_path = virt_exg%get_vrt_mem_path('NODEM2', '') + call this%map_data_full(0, 'NODEM2', conn%prim_exchange%memoryPath, & + 'NODEM2', virt_mem_path, (/STG_BEFORE_DF/)) + end if + end do + + end subroutine add_exchange_vars + + !> @brief Add distributed interface variables as memory mapped items + !< + subroutine add_interface_vars(this) + use ListsModule, only: baseconnectionlist + use SpatialModelConnectionModule, only: SpatialModelConnectionType, & + get_smc_from_list + class(MapperType) :: this + ! local + integer(I4B) :: iconn + class(SpatialModelConnectionType), pointer :: conn + + do iconn = 1, baseconnectionlist%Count() + conn => get_smc_from_list(baseconnectionlist, iconn) + ! add the variables for this interface model to our mapper + call this%add_dist_vars(conn%owner%idsoln, & + conn%iface_dist_vars, & + conn%interface_map) + end do + + end subroutine add_interface_vars + + subroutine add_dist_vars(this, sol_id, var_list, interface_map) + class(MapperType) :: this + integer(I4B) :: sol_id + type(ListType) :: var_list + type(InterfaceMapType) :: interface_map + ! local + integer(I4B) :: i, m, e + type(DistVarType), pointer :: distvar + type(IndexMapType), pointer :: idx_map + + ! loop over variables + do i = 1, var_list%Count() + distvar => GetDistVarFromList(var_list, i) + if (distvar%map_type == SYNC_NODES .or. & + distvar%map_type == SYNC_CONNECTIONS) then + ! map data for all models in this interface + do m = 1, interface_map%nr_models + + ! pick the right index map: connection based or node based + if (distvar%map_type == SYNC_NODES) then + idx_map => interface_map%node_map(m) + else if (distvar%map_type == SYNC_CONNECTIONS) then + idx_map => interface_map%connection_map(m) + end if + + ! and map ... + call this%map_model_data(sol_id, & + distvar%comp_name, & + distvar%subcomp_name, & + distvar%var_name, & + interface_map%model_ids(m), & + idx_map, & + distvar%sync_stages) + end do + else if (distvar%map_type == SYNC_EXCHANGES) then + ! map data from the exchanges to the interface + do e = 1, interface_map%nr_exchanges + call this%map_exg_data(sol_id, & + distvar%comp_name, & + distvar%subcomp_name, & + distvar%var_name, & + interface_map%exchange_ids(e), & + distvar%exg_var_name, & + interface_map%exchange_map(e), & + distvar%sync_stages) + end do + end if + end do + + end subroutine add_dist_vars + + !> @brief Map data from model memory to a target memory entry, + !! with the specified map. The source and target items have + !< the same name and (optionally) subcomponent name. + subroutine map_model_data(this, controller_id, tgt_model_name, & + tgt_subcomp_name, tgt_var_name, src_model_id, & + index_map, stages) + use SimModule, only: ustop + use MemoryManagerModule, only: get_from_memorylist + class(MapperType) :: this + integer(I4B) :: controller_id !< e.g. the numerical solution where synchr. is controlled + character(len=*), intent(in) :: tgt_model_name + character(len=*), intent(in) :: tgt_subcomp_name + character(len=*), intent(in) :: tgt_var_name + integer(I4B), intent(in) :: src_model_id + type(IndexMapType), intent(in) :: index_map + integer(I4B), dimension(:), intent(in) :: stages !< array with 1 or multiple stages for synchronization + ! local + character(len=LENVARNAME) :: src_var_name + character(len=LENMEMPATH) :: src_mem_path, tgt_mem_path + class(VirtualModelType), pointer :: v_model + + v_model => get_virtual_model(src_model_id) + + if (len_trim(tgt_subcomp_name) > 0) then + tgt_mem_path = create_mem_path(tgt_model_name, tgt_subcomp_name) + else + tgt_mem_path = create_mem_path(tgt_model_name) + end if + + src_var_name = tgt_var_name + src_mem_path = v_model%get_vrt_mem_path(src_var_name, tgt_subcomp_name) + call this%map_data(controller_id, & + tgt_var_name, tgt_mem_path, index_map%tgt_idx, & + src_var_name, src_mem_path, index_map%src_idx, & + null(), stages) + + end subroutine map_model_data + + !> @brief Map memory from a Exchange to the specified memory entry, + !< using the index map + subroutine map_exg_data(this, controller_id, tgt_model_name, & + tgt_subcomp_name, tgt_var_name, src_exg_id, & + src_var_name, index_map_sgn, stages) + use SimModule, only: ustop + use MemoryManagerModule, only: get_from_memorylist + class(MapperType) :: this + integer(I4B) :: controller_id !< e.g. the numerical solution where synchr. is controlled + character(len=*), intent(in) :: tgt_model_name + character(len=*), intent(in) :: tgt_subcomp_name + character(len=*), intent(in) :: tgt_var_name + integer(I4B), intent(in) :: src_exg_id + character(len=*), intent(in) :: src_var_name + type(IndexMapSgnType), intent(in) :: index_map_sgn + integer(I4B), dimension(:), intent(in) :: stages !< array with 1 or multiple stages for synchronization + ! local + character(len=LENMEMPATH) :: src_mem_path, tgt_mem_path + class(VirtualExchangeType), pointer :: v_exchange + + v_exchange => get_virtual_exchange(src_exg_id) + + if (len_trim(tgt_subcomp_name) > 0) then + tgt_mem_path = create_mem_path(tgt_model_name, tgt_subcomp_name) + else + tgt_mem_path = create_mem_path(tgt_model_name) + end if + + src_mem_path = v_exchange%get_vrt_mem_path(src_var_name, '') + call this%map_data(controller_id, & + tgt_var_name, tgt_mem_path, index_map_sgn%tgt_idx, & + src_var_name, src_mem_path, index_map_sgn%src_idx, & + index_map_sgn%sign, stages) + + end subroutine map_exg_data + + !> @brief Full copy between two variables in memory + subroutine map_data_full(this, controller_id, tgt_name, tgt_path, & + src_name, src_path, stages) + class(MapperType) :: this + integer(I4B) :: controller_id + character(len=*), intent(in) :: tgt_name + character(len=*), intent(in) :: tgt_path + character(len=*), intent(in) :: src_name + character(len=*), intent(in) :: src_path + integer(I4B), dimension(:), intent(in) :: stages + + call this%map_data(controller_id, tgt_name, tgt_path, null(), & + src_name, src_path, null(), & + null(), stages) + + end subroutine map_data_full + + !> @brief Generic mapping between two variables in memory, using + !< an optional sign conversion + subroutine map_data(this, controller_id, tgt_name, tgt_path, tgt_idx, & + src_name, src_path, src_idx, sign_array, stages) + class(MapperType) :: this + integer(I4B) :: controller_id + character(len=*), intent(in) :: tgt_name + character(len=*), intent(in) :: tgt_path + integer(I4B), dimension(:), pointer :: tgt_idx + character(len=*), intent(in) :: src_name + character(len=*), intent(in) :: src_path + integer(I4B), dimension(:), pointer :: src_idx + integer(I4B), dimension(:), pointer :: sign_array + integer(I4B), dimension(:), intent(in) :: stages + ! local + integer(I4B) :: istage, i + type(MappedMemoryType), pointer :: mapped_data + class(*), pointer :: obj + + ! loop and set stage bits + istage = 0 + do i = 1, size(stages) + istage = ibset(istage, stages(i)) + end do + + ! create MappedVariable and add to list + allocate (mapped_data) + mapped_data%controller_id = controller_id + mapped_data%sync_stage = istage + mapped_data%src_name = src_name + mapped_data%src_path = src_path + mapped_data%src => null() + mapped_data%tgt_name = tgt_name + mapped_data%tgt_path = tgt_path + mapped_data%tgt => null() + mapped_data%copy_all = .not. associated(src_idx) + mapped_data%src_idx => src_idx + mapped_data%tgt_idx => tgt_idx + mapped_data%sign => sign_array + obj => mapped_data + call this%mapped_data_list%Add(obj) + + end subroutine map_data + + !> @brief Scatter the mapped memory, typically into + !< the memory space of the interface models + subroutine scatter(this, controller_id, stage) + class(MapperType) :: this + integer(I4B) :: controller_id + integer(I4B), intent(in) :: stage + ! local + integer(I4B) :: i + class(*), pointer :: obj + class(MappedMemoryType), pointer :: mapped_data + + ! sync all variables (src => tgt) for a given stage + do i = 1, this%mapped_data_list%Count() + obj => this%mapped_data_list%GetItem(i) + mapped_data => CastAsMappedData(obj) + if (controller_id > 0 .and. & + mapped_data%controller_id /= controller_id) cycle + if (.not. check_stage(mapped_data%sync_stage, stage)) cycle + + ! copy data + call mapped_data%sync() + end do + + end subroutine scatter + + function check_stage(var_stage, current_stage) result(is_sync) + integer(I4B) :: var_stage + integer(I4B) :: current_stage + logical(LGP) :: is_sync + + is_sync = iand(var_stage, ibset(0, current_stage)) == ibset(0, current_stage) + + end function check_stage + + subroutine destroy(this) + class(MapperType) :: this + + call this%mapped_data_list%Clear(destroy=.true.) + + end subroutine destroy + +end module MapperModule diff --git a/src/Distributed/MpiMessageBuilder.f90 b/src/Distributed/MpiMessageBuilder.f90 new file mode 100644 index 00000000000..2e092cac093 --- /dev/null +++ b/src/Distributed/MpiMessageBuilder.f90 @@ -0,0 +1,552 @@ +module MpiMessageBuilderModule + use KindModule, only: I4B + use MemoryTypeModule, only: MemoryType + use STLVecIntModule + use VirtualBaseModule + use VirtualDataContainerModule + use mpi + implicit none + private + + type, public :: VdcHeaderType + integer(I4B) :: id + integer(I4B) :: container_type + end type + + type, public :: MpiMessageBuilderType + type(VdcPtrType), dimension(:), pointer :: vdc_models => null() !< the models to be build the message for + type(VdcPtrType), dimension(:), pointer :: vdc_exchanges => null() !< the exchanges to be build the message for + contains + procedure :: attach_data => mb_attach_data + procedure :: release_data => mb_release_data + procedure :: create_header_snd => mb_create_header_snd + procedure :: create_header_rcv => mb_create_header_rcv + procedure :: create_body_rcv => mb_create_body_rcv + procedure :: create_body_snd => mb_create_body_snd + ! private + procedure, private :: get_vdc_from_hdr + procedure, private :: create_vdc_snd_hdr + procedure, private :: create_vdc_snd_body + procedure, private :: create_vdc_rcv_body + procedure, private :: create_vdc_body + end type + +contains + + subroutine mb_attach_data(this, vdc_models, vdc_exchanges) + class(MpiMessageBuilderType) :: this + type(VdcPtrType), dimension(:), pointer :: vdc_models + type(VdcPtrType), dimension(:), pointer :: vdc_exchanges + + this%vdc_models => vdc_models + this%vdc_exchanges => vdc_exchanges + + end subroutine mb_attach_data + + subroutine mb_release_data(this) + class(MpiMessageBuilderType) :: this + + this%vdc_models => null() + this%vdc_exchanges => null() + + end subroutine mb_release_data + + !> @brief Create the header data type to send to + !! the remote process for this particular stage. + !! From these data, the receiver can construct the + !< body to send back to us. + subroutine mb_create_header_snd(this, rank, stage, hdrs_snd_type) + class(MpiMessageBuilderType) :: this + integer(I4B) :: rank + integer(I4B) :: stage + integer :: hdrs_snd_type + ! local + integer(I4B) :: i, offset, nr_types + class(VirtualDataContainerType), pointer :: vdc + integer :: ierr + type(STLVecInt) :: model_idxs, exg_idxs + integer, dimension(:), allocatable :: blk_cnts, types + integer(kind=MPI_ADDRESS_KIND), dimension(:), allocatable :: displs + + call model_idxs%init() + call exg_idxs%init() + + ! determine which containers to include + do i = 1, size(this%vdc_models) + vdc => this%vdc_models(i)%ptr + if (vdc%is_active .and. vdc%orig_rank == rank) then + call model_idxs%push_back(i) + end if + end do + do i = 1, size(this%vdc_exchanges) + vdc => this%vdc_exchanges(i)%ptr + if (vdc%is_active .and. vdc%orig_rank == rank) then + call exg_idxs%push_back(i) + end if + end do + + nr_types = model_idxs%size + exg_idxs%size + allocate (blk_cnts(nr_types)) + allocate (types(nr_types)) + allocate (displs(nr_types)) + + ! loop over containers + do i = 1, model_idxs%size + vdc => this%vdc_models(model_idxs%at(i))%ptr + call MPI_Get_address(vdc%id, displs(i), ierr) + blk_cnts(i) = 1 + types(i) = this%create_vdc_snd_hdr(vdc, stage) + end do + offset = model_idxs%size + do i = 1, exg_idxs%size + vdc => this%vdc_exchanges(exg_idxs%at(i))%ptr + call MPI_Get_address(vdc%id, displs(i + offset), ierr) + blk_cnts(i + offset) = 1 + types(i + offset) = this%create_vdc_snd_hdr(vdc, stage) + end do + + ! create a MPI data type for the headers to send + call MPI_Type_create_struct(nr_types, blk_cnts, displs, types, & + hdrs_snd_type, ierr) + call MPI_Type_commit(hdrs_snd_type, ierr) + do i = 1, nr_types + call MPI_Type_free(types(i), ierr) + end do + + call model_idxs%destroy() + call exg_idxs%destroy() + + deallocate (blk_cnts) + deallocate (types) + deallocate (displs) + + end subroutine mb_create_header_snd + + subroutine mb_create_header_rcv(this, hdr_rcv_type) + class(MpiMessageBuilderType) :: this + integer :: hdr_rcv_type + ! local + integer :: ierr + + ! this will be for one data container, the mpi recv + ! call will accept an array of them, no need to create + ! an overarching contiguous type... + call MPI_Type_contiguous(2, MPI_INTEGER, hdr_rcv_type, ierr) + call MPI_Type_commit(hdr_rcv_type, ierr) + + end subroutine mb_create_header_rcv + + !> @brief Create the body to receive based on the headers + !< that have been sent + subroutine mb_create_body_rcv(this, rank, stage, body_rcv_type) + class(MpiMessageBuilderType) :: this + integer(I4B) :: rank + integer(I4B) :: stage + integer :: body_rcv_type + ! local + integer(I4B) :: i, nr_types, offset + class(VirtualDataContainerType), pointer :: vdc + type(STLVecInt) :: model_idxs, exg_idxs + integer :: ierr + integer, dimension(:), allocatable :: types + integer(kind=MPI_ADDRESS_KIND), dimension(:), allocatable :: displs + integer, dimension(:), allocatable :: blk_cnts + + call model_idxs%init() + call exg_idxs%init() + + ! gather all containers from this rank + do i = 1, size(this%vdc_models) + vdc => this%vdc_models(i)%ptr + if (vdc%is_active .and. vdc%orig_rank == rank) then + call model_idxs%push_back(i) + end if + end do + do i = 1, size(this%vdc_exchanges) + vdc => this%vdc_exchanges(i)%ptr + if (vdc%is_active .and. vdc%orig_rank == rank) then + call exg_idxs%push_back(i) + end if + end do + + nr_types = model_idxs%size + exg_idxs%size + allocate (types(nr_types)) + allocate (displs(nr_types)) + allocate (blk_cnts(nr_types)) + + ! loop over included containers + do i = 1, model_idxs%size + vdc => this%vdc_models(model_idxs%at(i))%ptr + call MPI_Get_address(vdc%id, displs(i), ierr) + types(i) = this%create_vdc_rcv_body(vdc, rank, stage) + blk_cnts(i) = 1 + end do + offset = model_idxs%size + do i = 1, exg_idxs%size + vdc => this%vdc_exchanges(exg_idxs%at(i))%ptr + call MPI_Get_address(vdc%id, displs(i + offset), ierr) + blk_cnts(i + offset) = 1 + types(i + offset) = this%create_vdc_rcv_body(vdc, rank, stage) + end do + + ! create a MPI data type for the virtual data containers to receive + call MPI_Type_create_struct(nr_types, blk_cnts, displs, types, & + body_rcv_type, ierr) + call MPI_Type_commit(body_rcv_type, ierr) + do i = 1, nr_types + call MPI_Type_free(types(i), ierr) + end do + + call model_idxs%destroy() + call exg_idxs%destroy() + deallocate (types) + deallocate (displs) + deallocate (blk_cnts) + + end subroutine mb_create_body_rcv + + !> @brief Create the body to send based on the received headers + !< + subroutine mb_create_body_snd(this, rank, stage, headers, body_snd_type) + class(MpiMessageBuilderType) :: this + integer(I4B) :: rank + integer(I4B) :: stage + type(VdcHeaderType), dimension(:) :: headers + integer :: body_snd_type + ! local + integer(I4B) :: i, nr_headers + class(VirtualDataContainerType), pointer :: vdc + integer :: ierr + integer, dimension(:), allocatable :: types + integer(kind=MPI_ADDRESS_KIND), dimension(:), allocatable :: displs + integer, dimension(:), allocatable :: blk_cnts + + nr_headers = size(headers) + allocate (types(nr_headers)) + allocate (displs(nr_headers)) + allocate (blk_cnts(nr_headers)) + + do i = 1, nr_headers + vdc => this%get_vdc_from_hdr(headers(i)) + call MPI_Get_address(vdc%id, displs(i), ierr) + types(i) = this%create_vdc_snd_body(vdc, rank, stage) + blk_cnts(i) = 1 + end do + + ! create the list of virtual data containers to receive + call MPI_Type_create_struct(nr_headers, blk_cnts, displs, & + types, body_snd_type, ierr) + call MPI_Type_commit(body_snd_type, ierr) + do i = 1, nr_headers + call MPI_Type_free(types(i), ierr) + end do + + deallocate (types) + deallocate (displs) + deallocate (blk_cnts) + + end subroutine mb_create_body_snd + + !> @brief Create send header for virtual data container, relative + !< to the field ...%id + function create_vdc_snd_hdr(this, vdc, stage) result(new_type) + class(MpiMessageBuilderType) :: this + class(VirtualDataContainerType) :: vdc + integer(I4B) :: stage + integer :: new_type ! the created MPI datatype, uncommitted + ! local + integer :: ierr + integer, dimension(2) :: blk_cnts + integer(kind=MPI_ADDRESS_KIND), dimension(2) :: displs + integer, dimension(2) :: types + + call MPI_Get_address(vdc%id, displs(1), ierr) + types(1) = MPI_INTEGER + blk_cnts(1) = 1 + call MPI_Get_address(vdc%container_type, displs(2), ierr) + types(2) = MPI_INTEGER + blk_cnts(2) = 1 + + ! rebase to id field + displs = displs - displs(1) + call MPI_Type_create_struct(2, blk_cnts, displs, types, new_type, ierr) + call MPI_Type_commit(new_type, ierr) + + end function create_vdc_snd_hdr + + function create_vdc_rcv_body(this, vdc, rank, stage) result(new_type) + class(MpiMessageBuilderType) :: this + class(VirtualDataContainerType), pointer :: vdc + integer(I4B) :: rank + integer(I4B) :: stage + integer :: new_type + ! local + type(STLVecInt) :: virtual_items + + call virtual_items%init() + call vdc%get_recv_items(stage, rank, virtual_items) + new_type = this%create_vdc_body(vdc, virtual_items) + call virtual_items%destroy() + + end function create_vdc_rcv_body + + function create_vdc_snd_body(this, vdc, rank, stage) result(new_type) + class(MpiMessageBuilderType) :: this + class(VirtualDataContainerType), pointer :: vdc + integer(I4B) :: rank + integer(I4B) :: stage + integer :: new_type + ! local + type(STLVecInt) :: virtual_items + + call virtual_items%init() + call vdc%get_send_items(stage, rank, virtual_items) + new_type = this%create_vdc_body(vdc, virtual_items) + call virtual_items%destroy() + + end function create_vdc_snd_body + + !> @brief Create data type for this container, relative + !< to its id field. This is used for sending and receiving + function create_vdc_body(this, vdc, items) result(new_type) + class(MpiMessageBuilderType) :: this + class(VirtualDataContainerType), pointer :: vdc + type(STLVecInt) :: items + integer :: new_type + ! local + integer(I4B) :: i + class(VirtualDataType), pointer :: vd + integer :: ierr + integer(kind=MPI_ADDRESS_KIND) :: offset + integer, dimension(:), allocatable :: types + integer(kind=MPI_ADDRESS_KIND), dimension(:), allocatable :: displs + integer, dimension(:), allocatable :: blk_cnts + + allocate (types(items%size)) + allocate (displs(items%size)) + allocate (blk_cnts(items%size)) + + call MPI_Get_address(vdc%id, offset, ierr) + + do i = 1, items%size + vd => get_virtual_data_from_list(vdc%virtual_data_list, items%at(i)) + call get_mpi_datatype(vd, displs(i), types(i)) + blk_cnts(i) = 1 + ! rebase w.r.t. id field + displs(i) = displs(i) - offset + end do + + call MPI_Type_create_struct(items%size, blk_cnts, displs, & + types, new_type, ierr) + call MPI_Type_commit(new_type, ierr) + + do i = 1, items%size + vd => get_virtual_data_from_list(vdc%virtual_data_list, items%at(i)) + call free_mpi_datatype(vd, types(i)) + end do + + deallocate (types) + deallocate (displs) + deallocate (blk_cnts) + + end function create_vdc_body + + function get_vdc_from_hdr(this, header) result(vdc) + class(MpiMessageBuilderType) :: this + type(VdcHeaderType) :: header + class(VirtualDataContainerType), pointer :: vdc + ! local + integer(I4B) :: i + + vdc => null() + if (header%container_type == VDC_GWFMODEL_TYPE .or. & + header%container_type == VDC_GWTMODEL_TYPE) then + do i = 1, size(this%vdc_models) + vdc => this%vdc_models(i)%ptr + if (vdc%id == header%id) return + vdc => null() + end do + else if (header%container_type == VDC_GWFEXG_TYPE .or. & + header%container_type == VDC_GWTEXG_TYPE) then + do i = 1, size(this%vdc_exchanges) + vdc => this%vdc_exchanges(i)%ptr + if (vdc%id == header%id) return + vdc => null() + end do + end if + + end function get_vdc_from_hdr + + !> @brief Local routine to get elemental mpi data types representing + !! the virtual data items. Types are automatically committed unless + !< they are primitives (e.g. MPI_INTEGER) + subroutine get_mpi_datatype(virtual_data, el_displ, el_type) + use SimModule, only: ustop + use SimVariablesModule, only: proc_id + class(VirtualDataType), pointer :: virtual_data + integer(kind=MPI_ADDRESS_KIND) :: el_displ + integer :: el_type + ! local + type(MemoryType), pointer :: mt + + mt => virtual_data%virtual_mt + if (.not. associated(mt)) then + write (*, *) 'not associated: ', virtual_data%var_name, proc_id + end if + if (associated(mt%intsclr)) then + call get_mpitype_for_int(mt, el_displ, el_type) + else if (associated(mt%aint1d)) then + call get_mpitype_for_int1d(mt, el_displ, el_type) + else if (associated(mt%aint2d)) then + call get_mpitype_for_int2d(mt, el_displ, el_type) + else if (associated(mt%aint3d)) then + call get_mpitype_for_int3d(mt, el_displ, el_type) + else if (associated(mt%dblsclr)) then + call get_mpitype_for_dbl(mt, el_displ, el_type) + else if (associated(mt%adbl1d)) then + call get_mpitype_for_dbl1d(mt, el_displ, el_type) + else if (associated(mt%adbl2d)) then + call get_mpitype_for_dbl2d(mt, el_displ, el_type) + else if (associated(mt%adbl3d)) then + call get_mpitype_for_dbl3d(mt, el_displ, el_type) + else + write (*, *) 'unsupported datatype in MPI messaging for ', & + virtual_data%var_name, virtual_data%mem_path + call ustop() + end if + + end subroutine get_mpi_datatype + + !> @brief Local routine to free elemental mpi data types representing + !! the virtual data items. This can't be done generally, because some + !< (scalar) types are primitive and freeing them causes nasty errors... + subroutine free_mpi_datatype(virtual_data, el_type) + class(VirtualDataType), pointer :: virtual_data + integer :: el_type + ! local + type(MemoryType), pointer :: mt + integer :: ierr + + mt => virtual_data%virtual_mt + if (associated(mt%intsclr)) then + ! type is MPI_INTEGER, don't free this! + return + else if (associated(mt%dblsclr)) then + ! type is MPI_DOUBLE_PRECISION, don't free this! + return + else if (associated(mt%logicalsclr)) then + ! type is MPI_LOGICAL, don't free this! + return + else + ! all other types are freed here + call MPI_Type_free(el_type, ierr) + return + end if + + end subroutine free_mpi_datatype + + subroutine get_mpitype_for_int(mem, el_displ, el_type) + type(MemoryType), pointer :: mem + integer(kind=MPI_ADDRESS_KIND) :: el_displ + integer :: el_type + ! local + integer :: ierr + + call MPI_Get_address(mem%intsclr, el_displ, ierr) + el_type = MPI_INTEGER + ! no need to commit primitive type + + end subroutine get_mpitype_for_int + + subroutine get_mpitype_for_int1d(mem, el_displ, el_type) + type(MemoryType), pointer :: mem + integer(kind=MPI_ADDRESS_KIND) :: el_displ + integer :: el_type + ! local + integer :: ierr + + call MPI_Get_address(mem%aint1d, el_displ, ierr) + call MPI_Type_contiguous(mem%isize, MPI_INTEGER, el_type, ierr) + call MPI_Type_commit(el_type, ierr) + + end subroutine get_mpitype_for_int1d + + subroutine get_mpitype_for_int2d(mem, el_displ, el_type) + type(MemoryType), pointer :: mem + integer(kind=MPI_ADDRESS_KIND) :: el_displ + integer :: el_type + ! local + integer :: ierr + + call MPI_Get_address(mem%aint2d, el_displ, ierr) + call MPI_Type_contiguous(mem%isize, MPI_INTEGER, el_type, ierr) + call MPI_Type_commit(el_type, ierr) + + end subroutine get_mpitype_for_int2d + + subroutine get_mpitype_for_int3d(mem, el_displ, el_type) + type(MemoryType), pointer :: mem + integer(kind=MPI_ADDRESS_KIND) :: el_displ + integer :: el_type + ! local + integer :: ierr + + call MPI_Get_address(mem%aint3d, el_displ, ierr) + call MPI_Type_contiguous(mem%isize, MPI_INTEGER, el_type, ierr) + call MPI_Type_commit(el_type, ierr) + + end subroutine get_mpitype_for_int3d + + subroutine get_mpitype_for_dbl(mem, el_displ, el_type) + type(MemoryType), pointer :: mem + integer(kind=MPI_ADDRESS_KIND) :: el_displ + integer :: el_type + ! local + integer :: ierr + + call MPI_Get_address(mem%dblsclr, el_displ, ierr) + el_type = MPI_DOUBLE_PRECISION + ! no need to commit primitive type + + end subroutine get_mpitype_for_dbl + + subroutine get_mpitype_for_dbl1d(mem, el_displ, el_type) + type(MemoryType), pointer :: mem + integer(kind=MPI_ADDRESS_KIND) :: el_displ + integer :: el_type + ! local + integer :: ierr + + call MPI_Get_address(mem%adbl1d, el_displ, ierr) + call MPI_Type_contiguous(mem%isize, MPI_DOUBLE_PRECISION, el_type, ierr) + call MPI_Type_commit(el_type, ierr) + + end subroutine get_mpitype_for_dbl1d + + subroutine get_mpitype_for_dbl2d(mem, el_displ, el_type) + type(MemoryType), pointer :: mem + integer(kind=MPI_ADDRESS_KIND) :: el_displ + integer :: el_type + ! local + integer :: ierr + + call MPI_Get_address(mem%adbl2d, el_displ, ierr) + call MPI_Type_contiguous(mem%isize, MPI_DOUBLE_PRECISION, el_type, ierr) + call MPI_Type_commit(el_type, ierr) + + end subroutine get_mpitype_for_dbl2d + + subroutine get_mpitype_for_dbl3d(mem, el_displ, el_type) + type(MemoryType), pointer :: mem + integer(kind=MPI_ADDRESS_KIND) :: el_displ + integer :: el_type + ! local + integer :: ierr + + call MPI_Get_address(mem%adbl3d, el_displ, ierr) + call MPI_Type_contiguous(mem%isize, MPI_DOUBLE_PRECISION, el_type, ierr) + call MPI_Type_commit(el_type, ierr) + + end subroutine get_mpitype_for_dbl3d + +end module MpiMessageBuilderModule diff --git a/src/Distributed/MpiRouter.f90 b/src/Distributed/MpiRouter.f90 new file mode 100644 index 00000000000..881b4e5b26e --- /dev/null +++ b/src/Distributed/MpiRouter.f90 @@ -0,0 +1,441 @@ +module MpiRouterModule + use RouterBaseModule + use KindModule, only: I4B, LGP + use STLVecIntModule + use SimVariablesModule, only: proc_id, nr_procs + use SimStagesModule, only: STG_TO_STR + use VirtualDataListsModule, only: virtual_model_list, & + virtual_exchange_list + use VirtualDataContainerModule, only: VirtualDataContainerType, & + VdcPtrType, get_vdc_from_list, & + VDC_TYPE_TO_STR + use VirtualExchangeModule, only: VirtualExchangeType + use VirtualSolutionModule + use MpiMessageBuilderModule + use MpiWorldModule + use mpi + implicit none + private + + public :: create_mpi_router + + type, public, extends(RouterBaseType) :: MpiRouterType + integer(I4B), dimension(:), pointer :: model_proc_ids + type(STLVecInt) :: senders !< the process ids to receive data from + type(STLVecInt) :: receivers !< the process ids to send data to + type(VdcPtrType), dimension(:), pointer :: all_models => null() !< all virtual models from the global list + type(VdcPtrType), dimension(:), pointer :: all_exchanges => null() !< all virtual exchanges from the global list + type(VdcPtrType), dimension(:), pointer :: rte_models => null() !< the currently active models to be routed + type(VdcPtrType), dimension(:), pointer :: rte_exchanges => null() !< the currently active exchanges to be routed + type(MpiMessageBuilderType) :: message_builder + type(MpiWorldType), pointer :: mpi_world => null() + integer(I4B) :: imon !< the output file unit for the mpi monitor + logical(LGP) :: enable_monitor !< when true, log diagnostics + contains + procedure :: initialize => mr_initialize + procedure :: route_all => mr_route_all + procedure :: route_sln => mr_route_sln + procedure :: destroy => mr_destroy + ! private + procedure, private :: mr_update_senders + procedure, private :: mr_update_senders_sln + procedure, private :: mr_update_receivers + procedure, private :: mr_update_receivers_sln + procedure, private :: mr_route_active + end type MpiRouterType + +contains + + !> Factory method to create MPI router + !< + function create_mpi_router() result(router) + class(RouterBaseType), pointer :: router + ! local + class(MpiRouterType), pointer :: mpi_router + + allocate (mpi_router) + router => mpi_router + + end function create_mpi_router + + subroutine mr_initialize(this) + use InputOutputModule, only: getunit + use ConstantsModule, only: LINELENGTH + class(MpiRouterType) :: this + ! local + integer :: ierr + integer(I4B) :: i + integer(I4B) :: nr_models, nr_exchanges + class(VirtualDataContainerType), pointer :: vdc + character(len=LINELENGTH) :: monitor_file + + ! to log or not to log + this%enable_monitor = .true. + + ! get mpi world for our process + this%mpi_world => get_mpi_world() + + ! init address list + call this%senders%init() + call this%receivers%init() + + ! find out where models are + nr_models = virtual_model_list%Count() + nr_exchanges = virtual_exchange_list%Count() + allocate (this%model_proc_ids(nr_models)) + allocate (this%all_models(nr_models)) + allocate (this%all_exchanges(nr_exchanges)) + + do i = 1, nr_models + vdc => get_vdc_from_list(virtual_model_list, i) + this%all_models(i)%ptr => vdc + if (vdc%is_local) then + this%model_proc_ids(i) = proc_id + else + this%model_proc_ids(i) = 0 + end if + end do + + call MPI_Allreduce(MPI_IN_PLACE, this%model_proc_ids, nr_models, & + MPI_INTEGER, MPI_SUM, MF6_COMM_WORLD, ierr) + + ! set the process id to the models and exchanges + do i = 1, nr_models + vdc => get_vdc_from_list(virtual_model_list, i) + call vdc%set_orig_rank(this%model_proc_ids(i)) + end do + + do i = 1, nr_exchanges + vdc => get_vdc_from_list(virtual_exchange_list, i) + this%all_exchanges(i)%ptr => vdc + select type (vex => vdc) + class is (VirtualExchangeType) + call vex%set_orig_rank(vex%v_model1%orig_rank) + if (vex%v_model1%is_local) then + call vex%set_orig_rank(vex%v_model2%orig_rank) + end if + end select + end do + + ! open log file + if (this%enable_monitor) then + this%imon = getunit() + write (monitor_file, '(a,i0,a)') "mpi.p", proc_id, ".log" + open (unit=this%imon, file=monitor_file) + + ! write initial info + write (this%imon, '(a,/)') "initialize MPI Router:" + write (this%imon, '(2x,a,i0)') "process id: ", proc_id + write (this%imon, '(2x,a,i0)') "nr. of processes: ", nr_procs + write (this%imon, '(2x,a,i0)') "nr. of models: ", nr_models + write (this%imon, '(2x,a,i0)') "nr. of exchanges: ", nr_exchanges + write (this%imon, '(2x,2a)') "model id, processor id:" + do i = 1, nr_models + write (this%imon, '(4x,2i8)') i, this%model_proc_ids(i) + end do + end if + + end subroutine mr_initialize + + !> @brief This will route all remote data from the + !! global models and exchanges over MPI, for a + !< given stage + subroutine mr_route_all(this, stage) + class(MpiRouterType) :: this + integer(I4B) :: stage + + if (this%enable_monitor) then + write (this%imon, '(/,a)') "routing all" + write (this%imon, '(2a)') "routing stage: ", STG_TO_STR(stage) + end if + + ! data to route + this%rte_models => this%all_models + this%rte_exchanges => this%all_exchanges + call this%message_builder%attach_data(this%rte_models, & + this%rte_exchanges) + + ! route all + call this%mr_route_active(stage) + + ! release + this%rte_models => null() + this%rte_exchanges => null() + call this%message_builder%release_data() + + end subroutine mr_route_all + + !> @brief This will route all remote data from models + !! and exchanges in a particular solution over MPI, + !< for a given stage + subroutine mr_route_sln(this, virtual_sol, stage) + class(MpiRouterType) :: this + type(VirtualSolutionType) :: virtual_sol + integer(I4B) :: stage + + if (this%enable_monitor) then + write (this%imon, '(/,a,i0)') "routing solution: ", virtual_sol%solution_id + write (this%imon, '(2a)') "routing stage: ", STG_TO_STR(stage) + end if + + ! data to route + this%rte_models => virtual_sol%models + this%rte_exchanges => virtual_sol%exchanges + call this%message_builder%attach_data(virtual_sol%models, & + virtual_sol%exchanges) + + ! route for this solution + call this%mr_route_active(stage) + + ! release + this%rte_models => null() + this%rte_exchanges => null() + call this%message_builder%release_data() + + end subroutine mr_route_sln + + !> @brief Routes the models and exchanges + !< + subroutine mr_route_active(this, stage) + class(MpiRouterType) :: this + integer(I4B) :: stage + ! local + integer(I4B) :: i, j, rnk + integer :: ierr, msg_size + ! mpi handles + integer, dimension(:), allocatable :: rcv_req + integer, dimension(:), allocatable :: snd_req + integer, dimension(:, :), allocatable :: rcv_stat + integer, dimension(:, :), allocatable :: snd_stat + ! message header + integer(I4B) :: max_headers + type(VdcHeaderType), dimension(:, :), allocatable :: headers + integer, dimension(:), allocatable :: hdr_rcv_t + integer, dimension(:), allocatable :: hdr_snd_t + integer, dimension(:), allocatable :: hdr_rcv_cnt + ! message body + integer, dimension(:), allocatable :: body_rcv_t + integer, dimension(:), allocatable :: body_snd_t + + ! update adress list + call this%mr_update_senders() + call this%mr_update_receivers() + + if (this%enable_monitor) then + write (this%imon, '(2x,a,*(i0))') "process ids sending data: ", & + this%senders%get_values() + write (this%imon, '(2x,a,*(i0))') "process ids receiving data: ", & + this%receivers%get_values() + end if + + ! allocate handles + allocate (rcv_req(this%receivers%size)) + allocate (snd_req(this%senders%size)) + allocate (rcv_stat(MPI_STATUS_SIZE, this%receivers%size)) + allocate (snd_stat(MPI_STATUS_SIZE, this%senders%size)) + + ! allocate header data + max_headers = size(this%rte_models) + size(this%rte_exchanges) + allocate (hdr_rcv_t(this%receivers%size)) + allocate (hdr_snd_t(this%senders%size)) + allocate (headers(max_headers, this%receivers%size)) + allocate (hdr_rcv_cnt(max_headers)) + + ! allocate body data + allocate (body_rcv_t(this%senders%size)) + allocate (body_snd_t(this%receivers%size)) + + ! first receive headers for outward data + do i = 1, this%receivers%size + rnk = this%receivers%at(i) + call this%message_builder%create_header_rcv(hdr_rcv_t(i)) + call MPI_Irecv(headers(:, i), max_headers, hdr_rcv_t(i), rnk, stage, & + MF6_COMM_WORLD, rcv_req(i), ierr) + ! don't free mpi datatype, we need the count below + end do + + ! send header for incoming data + do i = 1, this%senders%size + rnk = this%senders%at(i) + call this%message_builder%create_header_snd(rnk, stage, hdr_snd_t(i)) + call MPI_Isend(MPI_BOTTOM, 1, hdr_snd_t(i), rnk, stage, & + MF6_COMM_WORLD, snd_req(i), ierr) + + if (this%enable_monitor) then + call MPI_Type_size(hdr_snd_t(i), msg_size, ierr) + write (this%imon, '(4x,a,i0)') "send header to process: ", rnk + end if + + call MPI_Type_free(hdr_snd_t(i), ierr) + end do + + ! wait for exchange of all headers + call MPI_WaitAll(this%receivers%size, rcv_req, rcv_stat, ierr) + + ! after WaitAll we can count incoming headers from statuses + do i = 1, this%receivers%size + call MPI_Get_count(rcv_stat(:, i), hdr_rcv_t(i), hdr_rcv_cnt(i), ierr) + + if (this%enable_monitor) then + rnk = this%senders%at(i) + write (this%imon, '(4x,a,i0)') "received headers from process: ", rnk + write (this%imon, '(4x,a)') "expecting data for:" + do j = 1, hdr_rcv_cnt(i) + write (this%imon, '(6x,a,i0,a,a)') "id: ", headers(j, i)%id, & + " type: ", trim(VDC_TYPE_TO_STR(headers(j, i)%container_type)) + end do + end if + + call MPI_Type_free(hdr_rcv_t(i), ierr) + end do + + ! recv bodies + do i = 1, this%senders%size + rnk = this%senders%at(i) + call this%message_builder%create_body_rcv(rnk, stage, body_rcv_t(i)) + call MPI_Type_size(body_rcv_t(i), msg_size, ierr) + if (msg_size > 0) then + call MPI_Irecv(MPI_BOTTOM, 1, body_rcv_t(i), rnk, stage, & + MF6_COMM_WORLD, snd_req(i), ierr) + end if + + if (this%enable_monitor) then + if (msg_size > 0) then + write (this%imon, '(4x,a,i0)') "receiving from process: ", rnk + write (this%imon, '(6x,a,i0)') "message body size: ", msg_size + else + write (this%imon, '(4x,a,i0)') "no receiving from process: ", rnk + end if + end if + end do + + ! send bodies + do i = 1, this%receivers%size + rnk = this%receivers%at(i) + call this%message_builder%create_body_snd( & + rnk, stage, headers(1:hdr_rcv_cnt(i), i), body_snd_t(i)) + call MPI_Type_size(body_snd_t(i), msg_size, ierr) + if (msg_size > 0) then + call MPI_Isend(MPI_Bottom, 1, body_snd_t(i), rnk, stage, & + MF6_COMM_WORLD, rcv_req(i), ierr) + end if + + if (this%enable_monitor) then + if (msg_size > 0) then + write (this%imon, '(4x,a,i0)') "sending to process: ", rnk + write (this%imon, '(6x,a,i0)') "message body size: ", msg_size + else + write (this%imon, '(4x,a,i0)') "no receiving from process: ", rnk + end if + end if + end do + + ! wait for exchange of all messages + call MPI_WaitAll(this%senders%size, snd_req, snd_stat, ierr) + + ! clean up types + do i = 1, this%senders%size + call MPI_Type_free(body_rcv_t(i), ierr) + end do + do i = 1, this%receivers%size + call MPI_Type_free(body_snd_t(i), ierr) + end do + + deallocate (rcv_req) + deallocate (snd_req) + deallocate (rcv_stat) + deallocate (hdr_rcv_t) + deallocate (hdr_snd_t) + deallocate (headers) + + end subroutine mr_route_active + + subroutine mr_update_senders(this) + class(MpiRouterType) :: this + ! local + integer(I4B) :: i + class(VirtualDataContainerType), pointer :: vdc + + call this%senders%clear() + + do i = 1, size(this%rte_models) + vdc => this%rte_models(i)%ptr + if (.not. vdc%is_local .and. vdc%is_active) then + call this%senders%push_back_unique(vdc%orig_rank) + end if + end do + do i = 1, size(this%rte_exchanges) + vdc => this%rte_exchanges(i)%ptr + if (.not. vdc%is_local .and. vdc%is_active) then + call this%senders%push_back_unique(vdc%orig_rank) + end if + end do + + end subroutine mr_update_senders + + subroutine mr_update_senders_sln(this, virtual_sol) + class(MpiRouterType) :: this + type(VirtualSolutionType) :: virtual_sol + ! local + integer(I4B) :: i + class(VirtualDataContainerType), pointer :: vdc + + call this%senders%clear() + + do i = 1, size(virtual_sol%models) + vdc => virtual_sol%models(i)%ptr + if (.not. vdc%is_local .and. vdc%is_active) then + call this%senders%push_back_unique(vdc%orig_rank) + end if + end do + do i = 1, size(virtual_sol%exchanges) + vdc => virtual_sol%exchanges(i)%ptr + if (.not. vdc%is_local .and. vdc%is_active) then + call this%senders%push_back_unique(vdc%orig_rank) + end if + end do + + end subroutine mr_update_senders_sln + + subroutine mr_update_receivers(this) + class(MpiRouterType) :: this + ! local + integer(I4B) :: i + + call this%receivers%clear() + + ! assuming symmetry for now + do i = 1, this%senders%size + call this%receivers%push_back(this%senders%at(i)) + end do + + end subroutine mr_update_receivers + + subroutine mr_update_receivers_sln(this, virtual_sol) + class(MpiRouterType) :: this + type(VirtualSolutionType) :: virtual_sol + ! local + integer(I4B) :: i + + call this%receivers%clear() + + ! assuming symmetry for now + do i = 1, this%senders%size + call this%receivers%push_back(this%senders%at(i)) + end do + + end subroutine mr_update_receivers_sln + + subroutine mr_destroy(this) + class(MpiRouterType) :: this + + call this%senders%destroy() + call this%receivers%destroy() + + deallocate (this%model_proc_ids) + deallocate (this%all_models) + deallocate (this%all_exchanges) + + end subroutine mr_destroy + +end module MpiRouterModule diff --git a/src/Distributed/MpiRunControl.F90 b/src/Distributed/MpiRunControl.F90 new file mode 100644 index 00000000000..eb2f334bff0 --- /dev/null +++ b/src/Distributed/MpiRunControl.F90 @@ -0,0 +1,119 @@ +module MpiRunControlModule +#if defined(__WITH_PETSC__) +#include + use petscksp +#endif + use mpi + use MpiWorldModule + use SimVariablesModule, only: proc_id, nr_procs + use KindModule, only: I4B, LGP + use RunControlModule, only: RunControlType + implicit none + private + + public :: create_mpi_run_control + + type, public, extends(RunControlType) :: MpiRunControlType + contains + ! override + procedure :: start => mpi_ctrl_start + procedure :: finish => mpi_ctrl_finish + ! private + procedure, private :: wait_for_debugger + end type MpiRunControlType + +contains + + function create_mpi_run_control() result(controller) + class(RunControlType), pointer :: controller + ! local + class(MpiRunControlType), pointer :: mpi_controller + + allocate (mpi_controller) + controller => mpi_controller + + end function create_mpi_run_control + + subroutine mpi_ctrl_start(this) + use SimModule, only: ustop, store_error + + class(MpiRunControlType) :: this + ! local + integer :: ierr + character(len=*), parameter :: petsc_db_file = '.petscrc' + logical(LGP) :: petsc_db_exists, wait_dbg, is_parallel_mode + class(MpiWorldType), pointer :: mpi_world + ! if PETSc we need their initialize + wait_dbg = .false. +#if defined(__WITH_PETSC__) + inquire (file=petsc_db_file, exist=petsc_db_exists) + if (.not. petsc_db_exists) then + write (*, *) 'WARNING. PETSc database file not found: '//petsc_db_file + call PetscInitialize(ierr) + else + call PetscInitialize(petsc_db_file, ierr) + end if + MF6_COMM_WORLD = PETSC_COMM_WORLD + CHKERRQ(ierr) + call PetscOptionsHasName(PETSC_NULL_OPTIONS, PETSC_NULL_CHARACTER, & + '-wait_dbg', wait_dbg, ierr) + call PetscOptionsHasName(PETSC_NULL_OPTIONS, PETSC_NULL_CHARACTER, & + '-p', is_parallel_mode, ierr) + CHKERRQ(ierr) +#else + call MPI_Init(ierr) + MF6_COMM_WORLD = MPI_COMM_WORLD +#endif + + mpi_world => get_mpi_world() + call mpi_world%init() + + call MPI_Comm_size(MF6_COMM_WORLD, nr_procs, ierr) + call MPI_Comm_rank(MF6_COMM_WORLD, proc_id, ierr) + + ! possibly wait to attach debugger here + if (wait_dbg) call this%wait_for_debugger() + + if (is_parallel_mode .and. nr_procs == 1) then + write (*, '(a,/)') '(WARNING. Running parallel mode on only 1 process)' + end if + + ! start everything else by calling parent + call this%RunControlType%start() + + end subroutine mpi_ctrl_start + + subroutine wait_for_debugger(this) + class(MpiRunControlType) :: this + ! local + integer :: ierr + integer(I4B) :: icnt + + if (proc_id == 0) then + icnt = 0 + write (*, *) 'Hit enter to continue...' + read (*, *) + end if + call MPI_Barrier(MF6_COMM_WORLD, ierr) + + end subroutine wait_for_debugger + + subroutine mpi_ctrl_finish(this) + class(MpiRunControlType) :: this + ! local + integer :: ierr + + ! finish mpi +#if defined(__WITH_PETSC__) + call PetscFinalize(ierr) + CHKERRQ(ierr) +#else + call MPI_Finalize(ierr) +#endif + + ! finish everything else by calling parent + call this%RunControlType%finish() + + end subroutine mpi_ctrl_finish + +end module MpiRunControlModule diff --git a/src/Distributed/MpiWorld.f90 b/src/Distributed/MpiWorld.f90 new file mode 100644 index 00000000000..2ffa45fa15b --- /dev/null +++ b/src/Distributed/MpiWorld.f90 @@ -0,0 +1,83 @@ +module MpiWorldModule + use KindModule, only: I4B + use SimVariablesModule, only: nr_procs, proc_id + use mpi + implicit none + private + + public :: get_mpi_world + integer(I4B), public :: MF6_COMM_WORLD = -1 + + type, public :: MpiWorldType + integer(I4B) :: mpi_rank + integer(I4B) :: world_size + contains + procedure :: init => mpiw_init + procedure :: begin_order => mpiw_begin_order + procedure :: end_order => mpiw_end_order + procedure :: destroy => mpiw_destroy + end type MpiWorldType + + ! singleton pattern + type(MpiWorldType), pointer :: global_mpi_world + +contains + + function get_mpi_world() result(world) + type(MpiWorldType), pointer :: world + + if (.not. associated(global_mpi_world)) then + allocate (global_mpi_world) + end if + world => global_mpi_world + + end function get_mpi_world + + subroutine mpiw_init(this) + class(MpiWorldType) :: this + ! local + integer :: ierr + + call MPI_Comm_size(MF6_COMM_WORLD, this%world_size, ierr) + call MPI_Comm_rank(MF6_COMM_WORLD, this%mpi_rank, ierr) + nr_procs = this%world_size + proc_id = this%mpi_rank + + end subroutine mpiw_init + + subroutine mpiw_begin_order(this) + class(MpiWorldType) :: this + ! local + integer :: buffer + integer :: status(MPI_STATUS_SIZE) + integer :: ierr + + if (this%mpi_rank > 0) then + call mpi_recv(buffer, 1, MPI_INTEGER, this%mpi_rank - 1, this%mpi_rank, & + MF6_COMM_WORLD, status, ierr) + end if + + end subroutine mpiw_begin_order + + subroutine mpiw_end_order(this) + class(MpiWorldType) :: this + ! local + integer :: ierr + + if (this%mpi_rank < this%world_size - 1) then + call mpi_send(this%mpi_rank, 1, MPI_INTEGER, this%mpi_rank + 1, & + this%mpi_rank + 1, MF6_COMM_WORLD, ierr) + end if + + end subroutine mpiw_end_order + + subroutine mpiw_destroy(this) + class(MpiWorldType) :: this + + if (associated(global_mpi_world)) then + deallocate (global_mpi_world) + end if + + end subroutine mpiw_destroy + +end module MpiWorldModule diff --git a/src/Distributed/RouterBase.f90 b/src/Distributed/RouterBase.f90 new file mode 100644 index 00000000000..6718b7aabc2 --- /dev/null +++ b/src/Distributed/RouterBase.f90 @@ -0,0 +1,38 @@ +module RouterBaseModule + use KindModule, only: I4B + use VirtualSolutionModule + implicit none + private + + type, abstract, public :: RouterBaseType + + contains + procedure(initialize_if), deferred :: initialize + procedure(route_all_if), deferred :: route_all + procedure(route_sln_if), deferred :: route_sln + procedure(destroy_if), deferred :: destroy + end type RouterBaseType + + abstract interface + subroutine initialize_if(this) + import RouterBaseType + class(RouterBaseType) :: this + end subroutine initialize_if + subroutine route_all_if(this, stage) + import RouterBaseType, I4B + class(RouterBaseType) :: this + integer(I4B) :: stage + end subroutine route_all_if + subroutine route_sln_if(this, virtual_sol, stage) + import RouterBaseType, VirtualSolutionType, I4B + class(RouterBaseType) :: this + type(VirtualSolutionType) :: virtual_sol + integer(I4B) :: stage + end subroutine route_sln_if + subroutine destroy_if(this) + import RouterBaseType + class(RouterBaseType) :: this + end subroutine destroy_if + end interface + +end module RouterBaseModule diff --git a/src/Distributed/RouterFactory.F90 b/src/Distributed/RouterFactory.F90 new file mode 100644 index 00000000000..b8c1218bed3 --- /dev/null +++ b/src/Distributed/RouterFactory.F90 @@ -0,0 +1,34 @@ +module RouterFactoryModule + use RouterBaseModule + use SerialRouterModule, only: create_serial_router +#if defined(__WITH_MPI__) + use MpiRouterModule, only: create_mpi_router +#endif + implicit none + private + + public :: create_router + +contains + + !> @ Brief Create the proper router, depends on + !! simulation mode (parallel or sequential) and type + !! of build (with or without mpi) + !< + function create_router(sim_mode) result(router) + character(len=*) :: sim_mode !< simulation mode: SEQUENTIAL or PARALLEL + class(RouterBaseType), pointer :: router !< the router object + + if (sim_mode == 'SEQUENTIAL') then + router => create_serial_router() +#if defined(__WITH_MPI__) + else if (sim_mode == 'PARALLEL') then + router => create_mpi_router() +#endif + else + router => null() + end if + + end function create_router + +end module RouterFactoryModule diff --git a/src/Distributed/SerialRouter.f90 b/src/Distributed/SerialRouter.f90 new file mode 100644 index 00000000000..96bb3442cb6 --- /dev/null +++ b/src/Distributed/SerialRouter.f90 @@ -0,0 +1,53 @@ +module SerialRouterModule + use RouterBaseModule + use KindModule, only: I4B + use VirtualSolutionModule + implicit none + private + + public :: create_serial_router + + type, public, extends(RouterBaseType) :: SerialRouterType + contains + procedure :: initialize => sr_initialize + procedure :: route_all => sr_route_all + procedure :: route_sln => sr_route_sln + procedure :: destroy => sr_destroy + end type SerialRouterType + +contains + + !> Factory method to create serial router + !< + function create_serial_router() result(router) + class(RouterBaseType), pointer :: router + ! local + class(SerialRouterType), pointer :: serial_router + + allocate (serial_router) + router => serial_router + + end function create_serial_router + + subroutine sr_initialize(this) + class(SerialRouterType) :: this + end subroutine sr_initialize + + subroutine sr_route_all(this, stage) + class(SerialRouterType) :: this + integer(I4B) :: stage + + end subroutine sr_route_all + + subroutine sr_route_sln(this, virtual_sol, stage) + class(SerialRouterType) :: this + type(VirtualSolutionType) :: virtual_sol + integer(I4B) :: stage + + end subroutine sr_route_sln + + subroutine sr_destroy(this) + class(SerialRouterType) :: this + end subroutine sr_destroy + +end module SerialRouterModule diff --git a/src/Distributed/VirtualBase.f90 b/src/Distributed/VirtualBase.f90 new file mode 100644 index 00000000000..f2af85a510e --- /dev/null +++ b/src/Distributed/VirtualBase.f90 @@ -0,0 +1,324 @@ +module VirtualBaseModule + use KindModule, only: I4B, DP, LGP + use ListModule + use ConstantsModule, only: LENVARNAME, LENMEMPATH, LENCOMPONENTNAME + use MemoryTypeModule, only: MemoryType + use MemoryManagerModule, only: mem_allocate, mem_deallocate, & + get_from_memorylist + implicit none + private + + public :: get_virtual_data_from_list + + !> This is a generic data structure to virtualize pieces + !! of memory in 2 distinct ways: + !! + !! 1) Virtualize remote memory + !! This concerns memory residing on another process. + !! Typically, these pieces are subsets of certain model + !! and exchange data and lookup tables are kept with the + !! data to manage their mapping. The stage(s) at which + !! to synchronize the virtual memory is stored as well. + !! + !! 2) Virtualize local memory + !! In this case no virtual memory item is created, no + !! lookup tables and synchronization are necessary. + !! The virtual memory item will be pointed to the + !! original memory location at the requested + !! synchronization stage. + !< + type, abstract, public :: VirtualDataType + logical(LGP) :: is_remote = .false. !< is remote memory, when true (default is false) + character(len=LENVARNAME) :: var_name !< variable name + character(len=LENCOMPONENTNAME) :: subcmp_name !< subcomponent name, e.g. package name + character(len=LENMEMPATH) :: mem_path !< memory path + integer(I4B), dimension(:), allocatable :: sync_stages !< stage(s) at which to synchronize + integer(I4B) :: map_type !< the type of map + integer(I4B), dimension(:), pointer, contiguous :: virtual_to_remote => null() !< contiguous list which maps virtual index to remote + integer(I4B), dimension(:), pointer, contiguous :: remote_to_virtual => null() !< sparse list which maps remote index to virtual + type(MemoryType), pointer :: virtual_mt => null() + contains + procedure(vm_allocate_if), deferred :: vm_allocate + procedure(vm_deallocate_if), deferred :: vm_deallocate + procedure :: to_base => vm_to_base + procedure :: check_stage => vm_check_stage + procedure :: link => vm_link + end type + + integer(I4B), public, parameter :: MAP_ALL_TYPE = 1 + integer(I4B), public, parameter :: MAP_NODE_TYPE = 2 + integer(I4B), public, parameter :: MAP_CONN_TYPE = 3 + + type, public, extends(VirtualDataType) :: VirtualIntType + integer(I4B), private, pointer :: intsclr + contains + procedure :: vm_allocate => vm_allocate_int + procedure :: vm_deallocate => vm_deallocate_int + procedure :: get => get_int + end type + + type, public, extends(VirtualDataType) :: VirtualInt1dType + integer(I4B), dimension(:), pointer, contiguous :: int1d + contains + procedure :: vm_allocate => vm_allocate_int1d + procedure :: vm_deallocate => vm_deallocate_int1d + procedure :: get => get_int1d + procedure :: get_array => get_array_int1d + end type + + type, public, extends(VirtualDataType) :: VirtualDblType + real(DP), private, pointer :: dblsclr + contains + procedure :: vm_allocate => vm_allocate_dbl + procedure :: vm_deallocate => vm_deallocate_dbl + procedure :: get => get_dbl + end type + + type, public, extends(VirtualDataType) :: VirtualDbl1dType + real(DP), dimension(:), pointer, contiguous :: dbl1d + contains + procedure :: vm_allocate => vm_allocate_dbl1d + procedure :: vm_deallocate => vm_deallocate_dbl1d + procedure :: get => get_dbl1d + procedure :: get_array => get_array_dbl1d + end type + + type, public, extends(VirtualDataType) :: VirtualDbl2dType + real(DP), dimension(:, :), pointer, contiguous :: dbl2d + contains + procedure :: vm_allocate => vm_allocate_dbl2D + procedure :: vm_deallocate => vm_deallocate_dbl2D + procedure :: get => get_dbl2d + procedure :: get_array => get_array_dbl2d + end type + + ! etc... + abstract interface + subroutine vm_allocate_if(this, var_name, mem_path, shape) + import VirtualDataType, I4B + class(VirtualDataType) :: this + character(len=*) :: var_name + character(len=*) :: mem_path + integer(I4B), dimension(:) :: shape + end subroutine vm_allocate_if + subroutine vm_deallocate_if(this) + import VirtualDataType + class(VirtualDataType) :: this + end subroutine vm_deallocate_if + end interface + +contains + + function vm_to_base(this) result(base_ptr) + class(VirtualDataType), target :: this + class(VirtualDataType), pointer :: base_ptr + + base_ptr => this + + end function vm_to_base + + !> @brief Check if this data item requires syncing + !< for this particular stage + function vm_check_stage(this, stage) result(has_stage) + use ArrayHandlersModule, only: ifind + class(VirtualDataType), target :: this + integer(I4B) :: stage, stg_idx + logical(LGP) :: has_stage + + has_stage = .false. + if (allocated(this%sync_stages)) then + stg_idx = ifind(this%sync_stages, stage) + has_stage = (stg_idx > 0) + end if + + end function vm_check_stage + + subroutine vm_link(this) + class(VirtualDataType), target :: this + ! local + logical(LGP) :: found + + call get_from_memorylist(this%var_name, this%mem_path, & + this%virtual_mt, found) + + end subroutine vm_link + + subroutine vm_allocate_int(this, var_name, mem_path, shape) + class(VirtualIntType) :: this + character(len=*) :: var_name + character(len=*) :: mem_path + integer(I4B), dimension(:) :: shape + + call mem_allocate(this%intsclr, var_name, mem_path) + + end subroutine vm_allocate_int + + subroutine vm_deallocate_int(this) + class(VirtualIntType) :: this + + if (this%is_remote) call mem_deallocate(this%intsclr) + + end subroutine vm_deallocate_int + + subroutine vm_allocate_int1d(this, var_name, mem_path, shape) + class(VirtualInt1dType) :: this + character(len=*) :: var_name + character(len=*) :: mem_path + integer(I4B), dimension(:) :: shape + + call mem_allocate(this%int1d, shape(1), var_name, mem_path) + + end subroutine vm_allocate_int1d + + subroutine vm_deallocate_int1d(this) + class(VirtualInt1dType) :: this + + if (this%is_remote) call mem_deallocate(this%int1d) + + end subroutine vm_deallocate_int1d + + subroutine vm_allocate_dbl(this, var_name, mem_path, shape) + class(VirtualDblType) :: this + character(len=*) :: var_name + character(len=*) :: mem_path + integer(I4B), dimension(:) :: shape + + call mem_allocate(this%dblsclr, var_name, mem_path) + + end subroutine vm_allocate_dbl + + subroutine vm_deallocate_dbl(this) + class(VirtualDblType) :: this + + if (this%is_remote) call mem_deallocate(this%dblsclr) + + end subroutine vm_deallocate_dbl + + subroutine vm_allocate_dbl1d(this, var_name, mem_path, shape) + class(VirtualDbl1dType) :: this + character(len=*) :: var_name + character(len=*) :: mem_path + integer(I4B), dimension(:) :: shape + + call mem_allocate(this%dbl1d, shape(1), var_name, mem_path) + + end subroutine vm_allocate_dbl1d + + subroutine vm_deallocate_dbl1d(this) + class(VirtualDbl1dType) :: this + + if (this%is_remote) call mem_deallocate(this%dbl1d) + + end subroutine vm_deallocate_dbl1d + + subroutine vm_allocate_dbl2d(this, var_name, mem_path, shape) + class(VirtualDbl2dType) :: this + character(len=*) :: var_name + character(len=*) :: mem_path + integer(I4B), dimension(:) :: shape + + call mem_allocate(this%dbl2d, shape(1), shape(2), var_name, mem_path) + + end subroutine vm_allocate_dbl2d + + subroutine vm_deallocate_dbl2d(this) + class(VirtualDbl2dType) :: this + + if (this%is_remote) call mem_deallocate(this%dbl2d) + + end subroutine vm_deallocate_dbl2d + + function get_int(this) result(val) + class(VirtualIntType) :: this + integer(I4B) :: val + + val = this%virtual_mt%intsclr + + end function get_int + + function get_int1d(this, i_rmt) result(val) + class(VirtualInt1dType) :: this + integer(I4B) :: i_rmt + integer(I4B) :: val + ! local + integer(I4B) :: i_vrt + + i_vrt = i_rmt !this%remote_to_virtual(i_rmt) + val = this%virtual_mt%aint1d(i_vrt) + + end function get_int1d + + function get_array_int1d(this) result(array) + class(VirtualInt1dType) :: this + integer(I4B), dimension(:), pointer, contiguous :: array + + array => this%virtual_mt%aint1d + + end function get_array_int1d + + function get_dbl(this) result(val) + class(VirtualDblType) :: this + real(DP) :: val + + val = this%virtual_mt%dblsclr + + end function get_dbl + + function get_dbl1d(this, i_rmt) result(val) + class(VirtualDbl1dType) :: this + integer(I4B) :: i_rmt + real(DP) :: val + ! local + integer(I4B) :: i_vrt + + i_vrt = i_rmt !this%remote_to_virtual(i_rmt) + val = this%virtual_mt%adbl1d(i_vrt) + + end function get_dbl1d + + function get_array_dbl1d(this) result(array) + class(VirtualDbl1dType) :: this + real(DP), dimension(:), pointer, contiguous :: array + + array => this%virtual_mt%adbl1d + + end function get_array_dbl1d + + function get_dbl2d(this, j_cmp, i_rmt) result(val) + class(VirtualDbl2dType) :: this + integer(I4B) :: j_cmp + integer(I4B) :: i_rmt + real(DP) :: val + ! local + integer(I4B) :: i_vrt + + i_vrt = i_rmt !this%remote_to_virtual(i_rmt) + val = this%virtual_mt%adbl2d(j_cmp, i_vrt) + + end function get_dbl2d + + function get_array_dbl2d(this) result(array) + class(VirtualDbl2dType) :: this + real(DP), dimension(:, :), pointer, contiguous :: array + + array => this%virtual_mt%adbl2d + + end function get_array_dbl2d + + function get_virtual_data_from_list(list, idx) result(vd) + type(ListType) :: list + integer(I4B) :: idx + class(VirtualDataType), pointer :: vd + ! local + class(*), pointer :: obj_ptr + + vd => null() + obj_ptr => list%GetItem(idx) + select type (obj_ptr) + class is (VirtualDataType) + vd => obj_ptr + end select + + end function get_virtual_data_from_list + +end module VirtualBaseModule diff --git a/src/Distributed/VirtualDataContainer.f90 b/src/Distributed/VirtualDataContainer.f90 new file mode 100644 index 00000000000..c93a7ec27f1 --- /dev/null +++ b/src/Distributed/VirtualDataContainer.f90 @@ -0,0 +1,367 @@ +module VirtualDataContainerModule + use VirtualBaseModule + use ListModule + use KindModule, only: I4B, LGP + use STLVecIntModule + use ConstantsModule, only: LENCOMPONENTNAME, LENMEMPATH, LENCONTEXTNAME + use MemoryHelperModule, only: create_mem_path + use MemoryManagerModule, only: get_from_memorylist + implicit none + private + + public :: get_vdc_from_list + public VDC_TYPE_TO_STR + + integer(I4B), public, parameter :: VDC_UNKNOWN_TYPE = 0 + integer(I4B), public, parameter :: VDC_GWFMODEL_TYPE = 1 + integer(I4B), public, parameter :: VDC_GWTMODEL_TYPE = 2 + integer(I4B), public, parameter :: VDC_GWFEXG_TYPE = 3 + integer(I4B), public, parameter :: VDC_GWTEXG_TYPE = 4 + integer(I4B), public, parameter :: VDC_GWFMVR_TYPE = 5 + integer(I4B), public, parameter :: VDC_GWTMVT_TYPE = 6 + + !> @brief Wrapper for virtual data containers + !! + !! We can't have an array of pointers in Fortran, so we use + !! this trick where we wrap the pointer and have an array + !< of VdcPtrType instead. + type, public :: VdcPtrType + class(VirtualDataContainerType), pointer :: ptr => null() + end type VdcPtrType + + !> @brief Container (list) of virtual data items. + !! + !! A virtual model or exchange derives from this base + !! and can add the component-specific items to the list + !! of virtual data items. As far as synchronization + !! of virtual objects is concerned, all that is needed + !< is the list of virtual data items in this container. + type, public :: VirtualDataContainerType + integer(I4B) :: id !< unique identifier matching with the real counterpart + integer(I4B) :: container_type !< to identify the actual type of this container + character(LENCOMPONENTNAME) :: name !< container name (model, exchange, ...) used in the memory path + character(LENCONTEXTNAME) :: vmem_ctx !< prefixes virtual memory located on remote processes + logical(LGP) :: is_local !< when true, the physical object resides on the same process. However, + !! some of its variables can still be remote + logical(LGP) :: is_active !< when true, this container is being synchronized + integer(I4B) :: orig_rank !< the global rank of the process which holds the physical data for this container + + type(ListType) :: virtual_data_list !< a list with all virtual data items for this container + contains + procedure :: vdc_create + generic :: map => map_scalar, map_array1d, map_array2d + procedure :: prepare_stage => vdc_prepare_stage + procedure :: link_items => vdc_link_items + procedure :: get_vrt_mem_path => vdc_get_vrt_mem_path + procedure :: destroy => vdc_destroy + procedure :: set_orig_rank => vdc_set_orig_rank + procedure :: get_send_items => vdc_get_send_items + procedure :: get_recv_items => vdc_get_recv_items + ! protected + procedure :: create_field + ! private + procedure, private :: add_to_list + procedure, private :: map_scalar + procedure, private :: map_array1d + procedure, private :: map_array2d + procedure, private :: map_internal + procedure, private :: vdc_get_virtual_data + procedure, private :: get_items_for_stage + end type VirtualDataContainerType + +contains + + subroutine vdc_create(this, name, id, is_local) + class(VirtualDataContainerType) :: this + character(len=*) :: name + integer(I4B) :: id + logical(LGP) :: is_local + + this%name = name + this%id = id + this%is_local = is_local + this%vmem_ctx = 'undefined' + this%orig_rank = 0 + this%is_active = .true. + this%container_type = VDC_UNKNOWN_TYPE + + end subroutine vdc_create + + !> @brief Create virtual data item, without allocation, + !< and add store it in this container. + subroutine create_field(this, field, var_name, subcmp_name, is_local) + class(VirtualDataContainerType) :: this + class(VirtualDataType), pointer :: field + character(len=*) :: var_name + character(len=*) :: subcmp_name + logical(LGP), optional :: is_local + + field%is_remote = .not. this%is_local + if (present(is_local)) field%is_remote = .not. is_local + field%var_name = var_name + field%subcmp_name = subcmp_name + if (subcmp_name == '') then + field%mem_path = create_mem_path(this%name) + else + field%mem_path = create_mem_path(this%name, subcmp_name) + end if + field%virtual_to_remote => null() + field%remote_to_virtual => null() + field%virtual_mt => null() + call this%add_to_list(field) + + end subroutine create_field + + subroutine add_to_list(this, virtual_data) + class(VirtualDataContainerType) :: this + class(VirtualDataType), pointer :: virtual_data + ! local + class(*), pointer :: vdata_ptr + + vdata_ptr => virtual_data + call this%virtual_data_list%Add(vdata_ptr) + + end subroutine add_to_list + + subroutine vdc_prepare_stage(this, stage) + use SimModule, only: ustop + class(VirtualDataContainerType) :: this + integer(I4B) :: stage + + write (*, *) 'Error: prepare_stage should be overridden' + call ustop() + + end subroutine vdc_prepare_stage + + !> @brief Link all local data items to memory + !< + subroutine vdc_link_items(this, stage) + class(VirtualDataContainerType) :: this + integer(I4B) :: stage + ! local + integer(I4B) :: i + class(*), pointer :: vdi + + do i = 1, this%virtual_data_list%Count() + vdi => this%virtual_data_list%GetItem(i) + select type (vdi) + class is (VirtualDataType) + if (vdi%is_remote) cycle + if (vdi%check_stage(stage)) call vdi%link() + end select + end do + + end subroutine vdc_link_items + + subroutine map_scalar(this, field, stages, map_type) + class(VirtualDataContainerType) :: this + class(VirtualDataType), pointer :: field + integer(I4B), dimension(:) :: stages + integer(I4B) :: map_type + + call this%map_internal(field, (/0/), stages, map_type) + + end subroutine map_scalar + + subroutine map_array1d(this, field, nrow, stages, map_type) + class(VirtualDataContainerType) :: this + class(VirtualDataType), pointer :: field + integer(I4B) :: nrow + integer(I4B), dimension(:) :: stages + integer(I4B) :: map_type + + call this%map_internal(field, (/nrow/), stages, map_type) + + end subroutine map_array1d + + subroutine map_array2d(this, field, ncol, nrow, stages, map_type) + class(VirtualDataContainerType) :: this + class(VirtualDataType), pointer :: field + integer(I4B) :: ncol + integer(I4B) :: nrow + integer(I4B), dimension(:) :: stages + integer(I4B) :: map_type + + call this%map_internal(field, (/ncol, nrow/), stages, map_type) + + end subroutine map_array2d + + subroutine map_internal(this, field, shape, stages, map_type) + class(VirtualDataContainerType) :: this + class(VirtualDataType), pointer :: field + integer(I4B), dimension(:) :: shape + integer(I4B), dimension(:) :: stages + integer(I4B) :: map_type + ! local + character(len=LENMEMPATH) :: vmem_path + logical(LGP) :: found + + field%sync_stages = stages + field%map_type = map_type + if (field%is_remote) then + ! create new virtual memory item + vmem_path = this%get_vrt_mem_path(field%var_name, field%subcmp_name) + call field%vm_allocate(field%var_name, vmem_path, shape) + call get_from_memorylist(field%var_name, vmem_path, field%virtual_mt, found) + end if + + end subroutine map_internal + + !> @brief Get indexes of virtual data items to be + !< sent for a given stage and rank + subroutine vdc_get_send_items(this, stage, rank, virtual_items) + class(VirtualDataContainerType) :: this + integer(I4B) :: stage + integer(I4B) :: rank + type(STLVecInt) :: virtual_items + + call this%get_items_for_stage(stage, virtual_items) + + end subroutine vdc_get_send_items + + !> @brief Get indexes of virtual data items to be + !< received for a given stage and rank + subroutine vdc_get_recv_items(this, stage, rank, virtual_items) + class(VirtualDataContainerType) :: this + integer(I4B) :: stage + integer(I4B) :: rank + type(STLVecInt) :: virtual_items + + call this%get_items_for_stage(stage, virtual_items) + + end subroutine vdc_get_recv_items + + subroutine get_items_for_stage(this, stage, virtual_items) + class(VirtualDataContainerType) :: this + integer(I4B) :: stage + type(STLVecInt) :: virtual_items + ! local + integer(I4B) :: i + class(*), pointer :: obj_ptr + + do i = 1, this%virtual_data_list%Count() + obj_ptr => this%virtual_data_list%GetItem(i) + select type (obj_ptr) + class is (VirtualDataType) + if (.not. obj_ptr%check_stage(stage)) cycle + call virtual_items%push_back(i) + end select + end do + + end subroutine get_items_for_stage + + !> @brief Get virtual memory path for a certain variable + !< + function vdc_get_vrt_mem_path(this, var_name, subcomp_name) result(vrt_path) + class(VirtualDataContainerType) :: this + character(len=*) :: var_name + character(len=*) :: subcomp_name + character(len=LENMEMPATH) :: vrt_path + ! local + class(VirtualDataType), pointer :: vdi + + vdi => this%vdc_get_virtual_data(var_name, subcomp_name) + if (vdi%is_remote) then + if (subcomp_name == '') then + vrt_path = create_mem_path(this%name, context=this%vmem_ctx) + else + vrt_path = create_mem_path(this%name, subcomp_name, context=this%vmem_ctx) + end if + else + if (subcomp_name == '') then + vrt_path = create_mem_path(this%name) + else + vrt_path = create_mem_path(this%name, subcomp_name) + end if + end if + + end function vdc_get_vrt_mem_path + + function vdc_get_virtual_data(this, var_name, subcomp_name) result(virtual_data) + use SimModule, only: ustop + class(VirtualDataContainerType) :: this + character(len=*) :: var_name + character(len=*) :: subcomp_name + class(VirtualDataType), pointer :: virtual_data + ! local + integer(I4B) :: i + class(VirtualDataType), pointer :: vd + + virtual_data => null() + do i = 1, this%virtual_data_list%Count() + vd => get_virtual_data_from_list(this%virtual_data_list, i) + if (vd%var_name == var_name .and. & + vd%subcmp_name == subcomp_name) then + virtual_data => vd + return + end if + end do + + write (*, *) 'Error: unknown virtual variable ', var_name, ' ', subcomp_name + call ustop() + + end function vdc_get_virtual_data + + subroutine vdc_destroy(this) + class(VirtualDataContainerType) :: this + ! local + integer(I4B) :: i + class(*), pointer :: obj + + do i = 1, this%virtual_data_list%Count() + obj => this%virtual_data_list%GetItem(i) + select type (obj) + class is (VirtualDataType) + if (associated(obj%virtual_mt)) then + call obj%vm_deallocate() + end if + end select + end do + call this%virtual_data_list%Clear() + + end subroutine vdc_destroy + + subroutine vdc_set_orig_rank(this, rank) + class(VirtualDataContainerType) :: this + integer(I4B) :: rank + + this%orig_rank = rank + write (this%vmem_ctx, '(a,i0,a)') '__P', rank, '__' + + end subroutine vdc_set_orig_rank + + function get_vdc_from_list(list, idx) result(vdc) + type(ListType) :: list + integer(I4B) :: idx + class(VirtualDataContainerType), pointer :: vdc + ! local + class(*), pointer :: obj_ptr + + vdc => null() + obj_ptr => list%GetItem(idx) + select type (obj_ptr) + class is (VirtualDataContainerType) + vdc => obj_ptr + end select + + end function get_vdc_from_list + + !> @ Converts a virtual container type to its string representation + !< + function VDC_TYPE_TO_STR(cntr_type) result(cntr_str) + integer(I4B) :: cntr_type + character(len=24) :: cntr_str + + if (cntr_type == VDC_UNKNOWN_TYPE) then; cntr_str = "unknown" + else if (VDC_GWFMODEL_TYPE == 1) then; cntr_str = "GWF Model" + else if (VDC_GWTMODEL_TYPE == 2) then; cntr_str = "GWT Model" + else if (VDC_GWFEXG_TYPE == 3) then; cntr_str = "GWF Exchange" + else if (VDC_GWTEXG_TYPE == 4) then; cntr_str = "GWT Exchange" + else if (VDC_GWFMVR_TYPE == 5) then; cntr_str = "GWF Mover" + else if (VDC_GWTMVT_TYPE == 6) then; cntr_str = "GWT Mover" + else; cntr_str = "Undefined" + end if + + end function VDC_TYPE_TO_STR + +end module VirtualDataContainerModule diff --git a/src/Distributed/VirtualDataLists.f90 b/src/Distributed/VirtualDataLists.f90 new file mode 100644 index 00000000000..7a53462b620 --- /dev/null +++ b/src/Distributed/VirtualDataLists.f90 @@ -0,0 +1,13 @@ +module VirtualDataListsModule + use ListModule, only: ListType + implicit none + private + + public :: virtual_model_list + public :: virtual_exchange_list + + type(ListType) :: virtual_model_list + + type(ListType) :: virtual_exchange_list + +end module VirtualDataListsModule diff --git a/src/Distributed/VirtualDataManager.f90 b/src/Distributed/VirtualDataManager.f90 new file mode 100644 index 00000000000..034851f8f31 --- /dev/null +++ b/src/Distributed/VirtualDataManager.f90 @@ -0,0 +1,310 @@ +module VirtualDataManagerModule + use KindModule, only: I4B + use STLVecIntModule + use VirtualDataListsModule, only: virtual_model_list, virtual_exchange_list + use VirtualModelModule, only: get_virtual_model + use VirtualExchangeModule, only: get_virtual_exchange + use VirtualSolutionModule + use VirtualDataContainerModule + use RouterBaseModule + use RouterFactoryModule, only: create_router + use NumericalSolutionModule, only: NumericalSolutionType + use NumericalModelModule, only: NumericalModelType, GetNumericalModelFromList + use NumericalExchangeModule, only: NumericalExchangeType, & + GetNumericalExchangeFromList + use SpatialModelConnectionModule, only: SpatialModelConnectionType, & + get_smc_from_list + implicit none + private + + type, public :: VirtualDataManagerType + integer(I4B) :: nr_solutions + integer(I4B), dimension(:), allocatable :: solution_ids + class(VirtualSolutionType), dimension(:), pointer :: virtual_solutions + class(RouterBaseType), pointer :: router + contains + procedure :: create => vds_create + procedure :: init => vds_init + procedure :: add_solution => vds_add_solution + procedure :: synchronize => vds_synchronize + procedure :: synchronize_sln => vds_synchronize_sln + procedure :: destroy + + ! private + procedure, private :: vds_synchronize + procedure, private :: prepare_all + procedure, private :: link_all + procedure, private :: vds_synchronize_sln + procedure, private :: prepare_sln + procedure, private :: link_sln + procedure, private :: count_nr_solutions + end type VirtualDataManagerType + +contains + + !> @brief Initialize the virtual data store + subroutine vds_create(this, sim_mode) + class(VirtualDataManagerType) :: this + character(len=*) :: sim_mode + ! local + integer(I4B) :: nr_sol + + nr_sol = this%count_nr_solutions() + allocate (this%virtual_solutions(nr_sol)) + allocate (this%solution_ids(nr_sol)) + + ! we use this one as a counter: + this%nr_solutions = 0 + + ! create a router, sequential or parallel + this%router => create_router(sim_mode) + + end subroutine vds_create + + !> @brief Initialize internal components + !< + subroutine vds_init(this) + class(VirtualDataManagerType) :: this + + call this%router%initialize() + + end subroutine + + !> @brief Add the models and exchanges from the passed solution + !! to the virtual data structure. This can then be used + !< to efficiently sync only this particular solution. + subroutine vds_add_solution(this, num_sol) + class(VirtualDataManagerType) :: this + class(NumericalSolutionType), pointer :: num_sol + ! local + integer(I4B) :: i, im, ix, ihm, ihx + class(VirtualSolutionType), pointer :: virt_sol + class(NumericalModelType), pointer :: num_mod + class(NumericalExchangeType), pointer :: num_exg + class(SpatialModelConnectionType), pointer :: conn + integer(I4B) :: model_id, exg_id + type(STLVecInt) :: model_ids, exchange_ids + class(VirtualDataContainerType), pointer :: vdc + + this%nr_solutions = this%nr_solutions + 1 + virt_sol => this%virtual_solutions(this%nr_solutions) + + call model_ids%init() + call exchange_ids%init() + + ! build the virtual solution + this%solution_ids(this%nr_solutions) = num_sol%id + virt_sol%solution_id = num_sol%id + + ! let's start with adding all models and exchanges from the global list + ! (we will make them inactive when they are remote and not part of + ! our halo) + do im = 1, num_sol%modellist%Count() + num_mod => GetNumericalModelFromList(num_sol%modellist, im) + call model_ids%push_back(num_mod%id) + end do + + ! loop over exchanges in solution and get connections + do ix = 1, num_sol%exchangelist%Count() + conn => get_smc_from_list(num_sol%exchangelist, ix) + if (.not. associated(conn)) then + ! it's a classic exchange, add it + num_exg => GetNumericalExchangeFromList(num_sol%exchangelist, ix) + call exchange_ids%push_back_unique(num_exg%id) + cycle + end if + + ! it's an interface model based exchanged, get + ! halo models and halo exchanges from connection + do ihm = 1, conn%halo_models%size + model_id = conn%halo_models%at(ihm) + call model_ids%push_back_unique(model_id) + end do + do ihx = 1, conn%halo_exchanges%size + exg_id = conn%halo_exchanges%at(ihx) + call exchange_ids%push_back_unique(exg_id) + end do + + end do + + allocate (virt_sol%models(model_ids%size)) + allocate (virt_sol%exchanges(exchange_ids%size)) + + ! select virtual containers for models/exchanges + do i = 1, model_ids%size + vdc => get_virtual_model(model_ids%at(i)) + virt_sol%models(i)%ptr => vdc + end do + do i = 1, exchange_ids%size + vdc => get_virtual_exchange(exchange_ids%at(i)) + virt_sol%exchanges(i)%ptr => vdc + end do + + ! cleanup + call model_ids%destroy() + call exchange_ids%destroy() + + end subroutine vds_add_solution + + !> @brief Synchronize the full virtual data store for this stage + !< + subroutine vds_synchronize(this, stage) + class(VirtualDataManagerType) :: this + integer(I4B) :: stage + + call this%prepare_all(stage) + call this%link_all(stage) + call this%router%route_all(stage) + + end subroutine vds_synchronize + + subroutine prepare_all(this, stage) + class(VirtualDataManagerType) :: this + integer(I4B) :: stage + ! local + integer(I4B) :: i + class(VirtualDataContainerType), pointer :: vdc + + ! prepare all virtual data for this stage + do i = 1, virtual_model_list%Count() + vdc => get_vdc_from_list(virtual_model_list, i) + call vdc%prepare_stage(stage) + end do + do i = 1, virtual_exchange_list%Count() + vdc => get_vdc_from_list(virtual_exchange_list, i) + call vdc%prepare_stage(stage) + end do + + end subroutine prepare_all + + subroutine link_all(this, stage) + class(VirtualDataManagerType) :: this + integer(I4B) :: stage + ! local + integer(I4B) :: i + class(VirtualDataContainerType), pointer :: vdc + + ! link all local objects + do i = 1, virtual_model_list%Count() + vdc => get_vdc_from_list(virtual_model_list, i) + call vdc%link_items(stage) + end do + do i = 1, virtual_exchange_list%Count() + vdc => get_vdc_from_list(virtual_exchange_list, i) + call vdc%link_items(stage) + end do + + end subroutine link_all + + !> @brief Synchronize one particular solution for this stage + !< + subroutine vds_synchronize_sln(this, id_sln, stage) + use ArrayHandlersModule, only: ifind + class(VirtualDataManagerType) :: this + integer(I4B) :: id_sln !< the id of the solution + integer(I4B) :: stage + ! local + integer(I4B) :: sol_idx + + sol_idx = ifind(this%solution_ids, id_sln) + call this%prepare_sln(this%virtual_solutions(sol_idx), stage) + call this%link_sln(this%virtual_solutions(sol_idx), stage) + call this%router%route_sln(this%virtual_solutions(sol_idx), stage) + + end subroutine vds_synchronize_sln + + !> @brief Force the virtual data containers (models, + !! exchanges, etc.) to schedule their virtual data + !< items for synchronization + subroutine prepare_sln(this, virtual_sol, stage) + class(VirtualDataManagerType) :: this + type(VirtualSolutionType) :: virtual_sol + integer(I4B) :: stage + ! local + integer(I4B) :: i + class(VirtualDataContainerType), pointer :: vdc + + do i = 1, size(virtual_sol%models) + vdc => virtual_sol%models(i)%ptr + call vdc%prepare_stage(stage) + end do + + do i = 1, size(virtual_sol%exchanges) + vdc => virtual_sol%exchanges(i)%ptr + call vdc%prepare_stage(stage) + end do + + end subroutine prepare_sln + + !> @brief Connect virtual memory items to their + !< sources when they are local for this stage + subroutine link_sln(this, virtual_sol, stage) + class(VirtualDataManagerType) :: this + type(VirtualSolutionType) :: virtual_sol + integer(I4B) :: stage + ! local + integer(I4B) :: i + class(VirtualDataContainerType), pointer :: vdc + + do i = 1, size(virtual_sol%models) + vdc => virtual_sol%models(i)%ptr + call vdc%link_items(stage) + end do + + do i = 1, size(virtual_sol%exchanges) + vdc => virtual_sol%exchanges(i)%ptr + call vdc%link_items(stage) + end do + + end subroutine link_sln + + !> @brief Returns the number of Numerical Solutions + !< in this simulation + function count_nr_solutions(this) result(count) + use ListsModule, only: basesolutionlist + use NumericalSolutionModule, only: NumericalSolutionType + class(VirtualDataManagerType) :: this + integer(I4B) :: count + ! local + integer(I4B) :: isol + class(*), pointer :: sol + + ! count nr. of numerical solutions + count = 0 + do isol = 1, basesolutionlist%Count() + sol => basesolutionlist%GetItem(isol) + select type (sol) + class is (NumericalSolutionType) + count = count + 1 + end select + end do + + end function count_nr_solutions + + subroutine destroy(this) + class(VirtualDataManagerType) :: this + ! local + integer(I4B) :: i + class(VirtualDataContainerType), pointer :: vdc + + do i = 1, virtual_model_list%Count() + vdc => get_vdc_from_list(virtual_model_list, i) + call vdc%destroy() + end do + call virtual_model_list%Clear(destroy=.true.) + + do i = 1, virtual_exchange_list%Count() + vdc => get_vdc_from_list(virtual_exchange_list, i) + call vdc%destroy() + end do + call virtual_exchange_list%Clear(destroy=.true.) + + do i = 1, this%nr_solutions + deallocate (this%virtual_solutions(i)%models) + deallocate (this%virtual_solutions(i)%exchanges) + end do + deallocate (this%virtual_solutions) + + end subroutine destroy + +end module VirtualDataManagerModule diff --git a/src/Distributed/VirtualExchange.f90 b/src/Distributed/VirtualExchange.f90 new file mode 100644 index 00000000000..aff8fe29f93 --- /dev/null +++ b/src/Distributed/VirtualExchange.f90 @@ -0,0 +1,283 @@ +module VirtualExchangeModule + use VirtualBaseModule + use VirtualDataContainerModule + use VirtualModelModule, only: VirtualModelType, get_virtual_model + use KindModule, only: I4B, LGP + use ListModule, only: ListType + use STLVecIntModule + use ConstantsModule, only: LENEXCHANGENAME + use SimStagesModule + implicit none + private + + public :: get_virtual_exchange + public :: get_virtual_exchange_from_list + private :: cast_as_virtual_exchange + + type, public, extends(VirtualDataContainerType) :: VirtualExchangeType + class(VirtualModelType), pointer :: v_model1 => null() + class(VirtualModelType), pointer :: v_model2 => null() + ! scalars + type(VirtualIntType), pointer :: nexg => null() + type(VirtualIntType), pointer :: naux => null() + type(VirtualIntType), pointer :: ianglex => null() + ! arrays + type(VirtualInt1dType), pointer :: nodem1 => null() + type(VirtualInt1dType), pointer :: nodem2 => null() + type(VirtualInt1dType), pointer :: ihc => null() + type(VirtualDbl1dType), pointer :: cl1 => null() + type(VirtualDbl1dType), pointer :: cl2 => null() + type(VirtualDbl1dType), pointer :: hwva => null() + type(VirtualDbl2dType), pointer :: auxvar => null() + contains + procedure :: create => vx_create + procedure :: prepare_stage => vx_prepare_stage + procedure :: get_send_items => vx_get_send_items + procedure :: get_recv_items => vx_get_recv_items + procedure :: destroy => vx_destroy + ! private + procedure, private :: create_virtual_fields + procedure, private :: deallocate_data + end type VirtualExchangeType + +contains + + !> @brief Create the virtual exchange base + !< + subroutine vx_create(this, name, exg_id, m1_id, m2_id) + class(VirtualExchangeType) :: this + character(len=*) :: name + integer(I4B) :: exg_id + integer(I4B) :: m1_id + integer(I4B) :: m2_id + ! local + logical(LGP) :: is_local + + this%v_model1 => get_virtual_model(m1_id) + this%v_model2 => get_virtual_model(m2_id) + + ! - if both models are local, is_local = true + ! - if both are remote, is_local = false + ! - if only one of them is remote, is_local = true and only the + ! remote nodem1/2 array will get its property is_local = false + is_local = this%v_model1%is_local .or. this%v_model2%is_local + call this%VirtualDataContainerType%vdc_create(name, exg_id, is_local) + + ! allocate fields + call this%create_virtual_fields() + + end subroutine vx_create + + subroutine create_virtual_fields(this) + class(VirtualExchangeType) :: this + ! local + logical(LGP) :: is_nodem1_local + logical(LGP) :: is_nodem2_local + + ! exchanges can be hybrid with both local and remote fields + is_nodem1_local = this%is_local .and. this%v_model1%is_local + is_nodem2_local = this%is_local .and. this%v_model2%is_local + + allocate (this%nexg) + call this%create_field(this%nexg%to_base(), 'NEXG', '') + allocate (this%naux) + call this%create_field(this%naux%to_base(), 'NAUX', '') + allocate (this%ianglex) + call this%create_field(this%ianglex%to_base(), 'IANGLEX', '') + allocate (this%nodem1) + call this%create_field(this%nodem1%to_base(), 'NODEM1', '', is_nodem1_local) + allocate (this%nodem2) + call this%create_field(this%nodem2%to_base(), 'NODEM2', '', is_nodem2_local) + allocate (this%ihc) + call this%create_field(this%ihc%to_base(), 'IHC', '') + allocate (this%cl1) + call this%create_field(this%cl1%to_base(), 'CL1', '') + allocate (this%cl2) + call this%create_field(this%cl2%to_base(), 'CL2', '') + allocate (this%hwva) + call this%create_field(this%hwva%to_base(), 'HWVA', '') + allocate (this%auxvar) + call this%create_field(this%auxvar%to_base(), 'AUXVAR', '') + + end subroutine create_virtual_fields + + subroutine vx_prepare_stage(this, stage) + class(VirtualExchangeType) :: this + integer(I4B) :: stage + ! local + integer(I4B) :: nexg, naux + + if (stage == STG_AFTER_EXG_DF) then + + call this%map(this%nexg%to_base(), (/STG_AFTER_EXG_DF/), MAP_ALL_TYPE) + call this%map(this%naux%to_base(), (/STG_AFTER_EXG_DF/), MAP_ALL_TYPE) + call this%map(this%ianglex%to_base(), (/STG_AFTER_EXG_DF/), MAP_ALL_TYPE) + + else if (stage == STG_BEFORE_DF) then + + nexg = this%nexg%get() + naux = this%naux%get() + call this%map(this%nodem1%to_base(), nexg, (/STG_BEFORE_DF/), MAP_ALL_TYPE) + call this%map(this%nodem2%to_base(), nexg, (/STG_BEFORE_DF/), MAP_ALL_TYPE) + call this%map(this%ihc%to_base(), nexg, (/STG_BEFORE_DF/), MAP_ALL_TYPE) + call this%map(this%cl1%to_base(), nexg, (/STG_BEFORE_DF/), MAP_ALL_TYPE) + call this%map(this%cl2%to_base(), nexg, (/STG_BEFORE_DF/), MAP_ALL_TYPE) + call this%map(this%hwva%to_base(), nexg, (/STG_BEFORE_DF/), MAP_ALL_TYPE) + call this%map(this%auxvar%to_base(), naux, nexg, & + (/STG_BEFORE_DF/), MAP_ALL_TYPE) + + end if + + end subroutine vx_prepare_stage + + subroutine vx_get_recv_items(this, stage, rank, virtual_items) + class(VirtualExchangeType) :: this + integer(I4B) :: stage + integer(I4B) :: rank + type(STLVecInt) :: virtual_items + ! local + integer(I4B) :: i, nodem1_idx, nodem2_idx + class(*), pointer :: virtual_data_item + + nodem1_idx = -1 + nodem2_idx = -1 + do i = 1, this%virtual_data_list%Count() + virtual_data_item => this%virtual_data_list%GetItem(i) + if (associated(virtual_data_item, this%nodem1)) then + nodem1_idx = i + end if + if (associated(virtual_data_item, this%nodem2)) then + nodem2_idx = i + end if + end do + + if (.not. this%v_model1%is_local .and. .not. this%v_model2%is_local) then + ! receive all using base + call this%VirtualDataContainerType%get_recv_items(stage, rank, & + virtual_items) + else if (.not. this%v_model2%is_local) then + ! receive for model2 + if (this%nodem2%check_stage(stage)) then + call virtual_items%push_back(nodem2_idx) + end if + else if (.not. this%v_model1%is_local) then + ! receive for model1 + if (this%nodem1%check_stage(stage)) then + call virtual_items%push_back(nodem1_idx) + end if + end if + + end subroutine vx_get_recv_items + + subroutine vx_get_send_items(this, stage, rank, virtual_items) + class(VirtualExchangeType) :: this + integer(I4B) :: stage + integer(I4B) :: rank + type(STLVecInt) :: virtual_items + ! local + integer(I4B) :: i, nodem1_idx, nodem2_idx + class(*), pointer :: virtual_data_item + + nodem1_idx = -1 + nodem2_idx = -1 + do i = 1, this%virtual_data_list%Count() + virtual_data_item => this%virtual_data_list%GetItem(i) + if (associated(virtual_data_item, this%nodem1)) then + nodem1_idx = i + end if + if (associated(virtual_data_item, this%nodem2)) then + nodem2_idx = i + end if + end do + + if (this%v_model1%is_local .and. this%v_model2%is_local) then + ! send all using base + call this%VirtualDataContainerType%get_send_items(stage, rank, & + virtual_items) + else if (this%v_model1%is_local) then + ! send for model1 + if (this%nodem1%check_stage(stage)) then + call virtual_items%push_back(nodem1_idx) + end if + else if (this%v_model2%is_local) then + ! send for model2 + if (this%nodem2%check_stage(stage)) then + call virtual_items%push_back(nodem2_idx) + end if + end if + + end subroutine vx_get_send_items + + subroutine vx_destroy(this) + class(VirtualExchangeType) :: this + + call this%VirtualDataContainerType%destroy() + call this%deallocate_data() + + end subroutine vx_destroy + + subroutine deallocate_data(this) + class(VirtualExchangeType) :: this + + deallocate (this%nexg) + deallocate (this%naux) + deallocate (this%ianglex) + deallocate (this%nodem1) + deallocate (this%nodem2) + deallocate (this%ihc) + deallocate (this%cl1) + deallocate (this%cl2) + deallocate (this%hwva) + deallocate (this%auxvar) + + end subroutine deallocate_data + + !> @brief Returs a virtual exchange with the specified id + !< from the global list + function get_virtual_exchange(exg_id) result(virtual_exg) + use VirtualDataListsModule, only: virtual_exchange_list + integer(I4B) :: exg_id + class(VirtualExchangeType), pointer :: virtual_exg + ! local + integer(I4B) :: i + class(*), pointer :: ve + + virtual_exg => null() + do i = 1, virtual_exchange_list%Count() + ve => virtual_exchange_list%GetItem(i) + select type (ve) + class is (VirtualExchangeType) + if (ve%id == exg_id) then + virtual_exg => ve + return + end if + end select + end do + + end function get_virtual_exchange + + function get_virtual_exchange_from_list(list, idx) result(virtual_exg) + type(ListType) :: list + integer(I4B) :: idx + class(VirtualExchangeType), pointer :: virtual_exg + ! local + class(*), pointer :: obj_ptr + + obj_ptr => list%GetItem(idx) + virtual_exg => cast_as_virtual_exchange(obj_ptr) + + end function get_virtual_exchange_from_list + + function cast_as_virtual_exchange(obj_ptr) result(virtual_exg) + class(*), pointer :: obj_ptr + class(VirtualExchangeType), pointer :: virtual_exg + + virtual_exg => null() + select type (obj_ptr) + class is (VirtualExchangeType) + virtual_exg => obj_ptr + end select + + end function cast_as_virtual_exchange + +end module VirtualExchangeModule diff --git a/src/Distributed/VirtualGwfExchange.f90 b/src/Distributed/VirtualGwfExchange.f90 new file mode 100644 index 00000000000..4c3670d0316 --- /dev/null +++ b/src/Distributed/VirtualGwfExchange.f90 @@ -0,0 +1,59 @@ +module VirtualGwfExchangeModule + use KindModule, only: I4B + use VirtualDataContainerModule, only: VDC_GWFEXG_TYPE + use VirtualExchangeModule + use VirtualDataListsModule, only: virtual_exchange_list + implicit none + private + + public :: add_virtual_gwf_exchange + + type, public, extends(VirtualExchangeType) :: VirtualGwfExchangeType + contains + procedure :: create => vfx_create + procedure :: destroy => vfx_destroy + end type VirtualGwfExchangeType + +contains + +!> @brief Add a virtual GWF-GWF exchange to the simulation +!< + subroutine add_virtual_gwf_exchange(name, exchange_id, model1_id, model2_id) + integer(I4B) :: exchange_id + character(len=*) :: name + integer(I4B) :: model1_id + integer(I4B) :: model2_id + ! local + class(VirtualGwfExchangeType), pointer :: v_exg + class(*), pointer :: obj_ptr + + allocate (v_exg) + call v_exg%create(name, exchange_id, model1_id, model2_id) + + obj_ptr => v_exg + call virtual_exchange_list%Add(obj_ptr) + + end subroutine add_virtual_gwf_exchange + +!> @brief Create a virtual GWF-GWF exchange +!< + subroutine vfx_create(this, name, exg_id, m1_id, m2_id) + class(VirtualGwfExchangeType) :: this + character(len=*) :: name + integer(I4B) :: exg_id + integer(I4B) :: m1_id + integer(I4B) :: m2_id + + call this%VirtualExchangeType%create(name, exg_id, m1_id, m2_id) + this%container_type = VDC_GWFEXG_TYPE + + end subroutine vfx_create + + subroutine vfx_destroy(this) + class(VirtualGwfExchangeType) :: this + + call this%VirtualExchangeType%destroy() + + end subroutine vfx_destroy + +end module VirtualGwfExchangeModule diff --git a/src/Distributed/VirtualGwfModel.f90 b/src/Distributed/VirtualGwfModel.f90 new file mode 100644 index 00000000000..a251b1936dc --- /dev/null +++ b/src/Distributed/VirtualGwfModel.f90 @@ -0,0 +1,204 @@ +module VirtualGwfModelModule + use KindModule, only: I4B + use VirtualBaseModule + use VirtualDataContainerModule, only: VDC_GWFMODEL_TYPE + use VirtualModelModule + use SimStagesModule + use NumericalModelModule, only: NumericalModelType + implicit none + private + + public :: add_virtual_gwf_model + + type, public, extends(VirtualModelType) :: VirtualGwfModelType + ! NPF + type(VirtualIntType), pointer :: npf_iangle1 => null() + type(VirtualIntType), pointer :: npf_iangle2 => null() + type(VirtualIntType), pointer :: npf_iangle3 => null() + type(VirtualIntType), pointer :: npf_iwetdry => null() + type(VirtualInt1dType), pointer :: npf_icelltype => null() + type(VirtualDbl1dType), pointer :: npf_k11 => null() + type(VirtualDbl1dType), pointer :: npf_k22 => null() + type(VirtualDbl1dType), pointer :: npf_k33 => null() + type(VirtualDbl1dType), pointer :: npf_angle1 => null() + type(VirtualDbl1dType), pointer :: npf_angle2 => null() + type(VirtualDbl1dType), pointer :: npf_angle3 => null() + type(VirtualDbl1dType), pointer :: npf_wetdry => null() + contains + ! public + procedure :: create => vgwf_create + procedure :: destroy => vgwf_destroy + procedure :: prepare_stage => vgwf_prepare_stage + ! private + procedure, private :: create_virtual_fields + procedure, private :: deallocate_data + end type VirtualGwfModelType + +contains + + !> @brief Add virtual GWF model + !< + subroutine add_virtual_gwf_model(model_id, model_name, model) + use VirtualDataListsModule, only: virtual_model_list + integer(I4B) :: model_id !< global model id + character(len=*) :: model_name !< model name + class(NumericalModelType), pointer :: model !< the actual model (can be null() when remote) + ! local + class(VirtualGwfModelType), pointer :: virtual_gwf_model + class(*), pointer :: obj + + allocate (virtual_gwf_model) + call virtual_gwf_model%create(model_name, model_id, model) + + obj => virtual_gwf_model + call virtual_model_list%Add(obj) + + end subroutine add_virtual_gwf_model + + subroutine vgwf_create(this, name, id, model) + class(VirtualGwfModelType) :: this + character(len=*) :: name + integer(I4B) :: id + class(NumericalModelType), pointer :: model + + ! create base + call this%VirtualModelType%create(name, id, model) + this%container_type = VDC_GWFMODEL_TYPE + + ! allocate fields + call this%create_virtual_fields() + + end subroutine vgwf_create + + subroutine create_virtual_fields(this) + class(VirtualGwfModelType) :: this + + allocate (this%npf_iangle1) + call this%create_field(this%npf_iangle1%to_base(), 'IANGLE1', 'NPF') + allocate (this%npf_iangle2) + call this%create_field(this%npf_iangle2%to_base(), 'IANGLE2', 'NPF') + allocate (this%npf_iangle3) + call this%create_field(this%npf_iangle3%to_base(), 'IANGLE3', 'NPF') + allocate (this%npf_iwetdry) + call this%create_field(this%npf_iwetdry%to_base(), 'IWETDRY', 'NPF') + allocate (this%npf_icelltype) + call this%create_field(this%npf_icelltype%to_base(), 'ICELLTYPE', 'NPF') + allocate (this%npf_k11) + call this%create_field(this%npf_k11%to_base(), 'K11', 'NPF') + allocate (this%npf_k22) + call this%create_field(this%npf_k22%to_base(), 'K22', 'NPF') + allocate (this%npf_k33) + call this%create_field(this%npf_k33%to_base(), 'K33', 'NPF') + allocate (this%npf_angle1) + call this%create_field(this%npf_angle1%to_base(), 'ANGLE1', 'NPF') + allocate (this%npf_angle2) + call this%create_field(this%npf_angle2%to_base(), 'ANGLE2', 'NPF') + allocate (this%npf_angle3) + call this%create_field(this%npf_angle3%to_base(), 'ANGLE3', 'NPF') + allocate (this%npf_wetdry) + call this%create_field(this%npf_wetdry%to_base(), 'WETDRY', 'NPF') + + end subroutine create_virtual_fields + + subroutine vgwf_prepare_stage(this, stage) + class(VirtualGwfModelType) :: this + integer(I4B) :: stage + ! local + integer(I4B) :: nr_nodes + + ! prepare base (=numerical) model data items + call this%VirtualModelType%prepare_stage(stage) + + if (stage == STG_AFTER_MDL_DF) then + + call this%map(this%npf_iangle1%to_base(), & + (/STG_AFTER_MDL_DF/), MAP_ALL_TYPE) + call this%map(this%npf_iangle2%to_base(), & + (/STG_AFTER_MDL_DF/), MAP_ALL_TYPE) + call this%map(this%npf_iangle3%to_base(), & + (/STG_AFTER_MDL_DF/), MAP_ALL_TYPE) + call this%map(this%npf_iwetdry%to_base(), & + (/STG_AFTER_MDL_DF/), MAP_ALL_TYPE) + + else if (stage == STG_BEFORE_AR) then + + nr_nodes = this%dis_nodes%get() + ! Num. model data + call this%map(this%x%to_base(), nr_nodes, & + (/STG_BEFORE_AR, STG_BEFORE_AD, STG_BEFORE_CF/), & + MAP_NODE_TYPE) + call this%map(this%ibound%to_base(), nr_nodes, & + (/STG_BEFORE_AR, STG_BEFORE_AD, STG_BEFORE_CF/), & + MAP_NODE_TYPE) + call this%map(this%x_old%to_base(), nr_nodes, & + (/STG_BEFORE_AD, STG_BEFORE_CF/), MAP_NODE_TYPE) + ! NPF + call this%map(this%npf_icelltype%to_base(), nr_nodes, & + (/STG_BEFORE_AR/), MAP_NODE_TYPE) + call this%map(this%npf_k11%to_base(), nr_nodes, & + (/STG_BEFORE_AR/), MAP_NODE_TYPE) + call this%map(this%npf_k22%to_base(), nr_nodes, & + (/STG_BEFORE_AR/), MAP_NODE_TYPE) + call this%map(this%npf_k33%to_base(), nr_nodes, & + (/STG_BEFORE_AR/), MAP_NODE_TYPE) + + if (this%npf_iangle1%get() > 0) then + call this%map(this%npf_angle1%to_base(), nr_nodes, & + (/STG_BEFORE_AR/), MAP_NODE_TYPE) + else + call this%map(this%npf_angle1%to_base(), 0, & + (/STG_NEVER/), MAP_NODE_TYPE) + end if + if (this%npf_iangle2%get() > 0) then + call this%map(this%npf_angle2%to_base(), nr_nodes, & + (/STG_BEFORE_AR/), MAP_NODE_TYPE) + else + call this%map(this%npf_angle2%to_base(), 0, & + (/STG_NEVER/), MAP_NODE_TYPE) + end if + if (this%npf_iangle3%get() > 0) then + call this%map(this%npf_angle3%to_base(), nr_nodes, & + (/STG_BEFORE_AR/), MAP_NODE_TYPE) + else + call this%map(this%npf_angle3%to_base(), 0, & + (/STG_NEVER/), MAP_NODE_TYPE) + end if + if (this%npf_iwetdry%get() > 0) then + call this%map(this%npf_wetdry%to_base(), nr_nodes, & + (/STG_BEFORE_AR/), MAP_NODE_TYPE) + else + call this%map(this%npf_wetdry%to_base(), 0, & + (/STG_BEFORE_AR/), MAP_NODE_TYPE) + end if + + end if + + end subroutine vgwf_prepare_stage + + subroutine vgwf_destroy(this) + class(VirtualGwfModelType) :: this + + call this%VirtualModelType%destroy() + call this%deallocate_data() + + end subroutine vgwf_destroy + + subroutine deallocate_data(this) + class(VirtualGwfModelType) :: this + + deallocate (this%npf_iangle1) + deallocate (this%npf_iangle2) + deallocate (this%npf_iangle3) + deallocate (this%npf_iwetdry) + deallocate (this%npf_icelltype) + deallocate (this%npf_k11) + deallocate (this%npf_k22) + deallocate (this%npf_k33) + deallocate (this%npf_angle1) + deallocate (this%npf_angle2) + deallocate (this%npf_angle3) + deallocate (this%npf_wetdry) + + end subroutine deallocate_data + +end module VirtualGwfModelModule diff --git a/src/Distributed/VirtualGwtExchange.f90 b/src/Distributed/VirtualGwtExchange.f90 new file mode 100644 index 00000000000..3de1a862950 --- /dev/null +++ b/src/Distributed/VirtualGwtExchange.f90 @@ -0,0 +1,103 @@ +module VirtualGwtExchangeModule + use KindModule, only: I4B + use SimStagesModule + use VirtualBaseModule + use VirtualDataListsModule, only: virtual_exchange_list + use VirtualDataContainerModule, only: VDC_GWTEXG_TYPE + use VirtualExchangeModule + implicit none + private + + public :: add_virtual_gwt_exchange + + type, public, extends(VirtualExchangeType) :: VirtualGwtExchangeType + type(VirtualDbl1dType), pointer :: gwfsimvals => null() + contains + procedure :: create => vtx_create + procedure :: destroy => vtx_destroy + procedure :: prepare_stage => vtx_prepare_stage + ! private + procedure, private :: create_virtual_fields + procedure, private :: deallocate_data + end type VirtualGwtExchangeType + +contains + +!> @brief Add a virtual GWT-GWT exchange to the simulation +!< + subroutine add_virtual_gwt_exchange(name, exchange_id, model1_id, model2_id) + character(len=*) :: name + integer(I4B) :: exchange_id + integer(I4B) :: model1_id + integer(I4B) :: model2_id + ! local + class(VirtualGwtExchangeType), pointer :: v_exg + class(*), pointer :: obj_ptr + + allocate (v_exg) + call v_exg%create(name, exchange_id, model1_id, model2_id) + + obj_ptr => v_exg + call virtual_exchange_list%Add(obj_ptr) + + end subroutine add_virtual_gwt_exchange + +!> @brief Create a virtual GWT-GWT exchange +!< + subroutine vtx_create(this, name, exg_id, m1_id, m2_id) + class(VirtualGwtExchangeType) :: this + character(len=*) :: name + integer(I4B) :: exg_id + integer(I4B) :: m1_id + integer(I4B) :: m2_id + + ! create base + call this%VirtualExchangeType%create(name, exg_id, m1_id, m2_id) + this%container_type = VDC_GWTEXG_TYPE + + ! allocate gwtgwt field(s) + call this%create_virtual_fields() + + end subroutine vtx_create + + subroutine create_virtual_fields(this) + class(VirtualGwtExchangeType) :: this + + allocate (this%gwfsimvals) + call this%create_field(this%gwfsimvals%to_base(), 'GWFSIMVALS', '') + + end subroutine create_virtual_fields + + subroutine vtx_prepare_stage(this, stage) + class(VirtualGwtExchangeType) :: this + integer(I4B) :: stage + ! local + integer(I4B) :: nexg + + ! prepare base exchange data items + call this%VirtualExchangeType%prepare_stage(stage) + + if (stage == STG_BEFORE_AR) then + nexg = this%nexg%get() + call this%map(this%gwfsimvals%to_base(), nexg, & + (/STG_BEFORE_AD/), MAP_ALL_TYPE) + end if + + end subroutine vtx_prepare_stage + + subroutine vtx_destroy(this) + class(VirtualGwtExchangeType) :: this + + call this%VirtualExchangeType%destroy() + call this%deallocate_data() + + end subroutine vtx_destroy + + subroutine deallocate_data(this) + class(VirtualGwtExchangeType) :: this + + deallocate (this%gwfsimvals) + + end subroutine deallocate_data + +end module VirtualGwtExchangeModule diff --git a/src/Distributed/VirtualGwtModel.f90 b/src/Distributed/VirtualGwtModel.f90 new file mode 100644 index 00000000000..49d4f67ab04 --- /dev/null +++ b/src/Distributed/VirtualGwtModel.f90 @@ -0,0 +1,209 @@ +module VirtualGwtModelModule + use KindModule, only: I4B + use SimStagesModule + use VirtualBaseModule + use VirtualDataContainerModule, only: VDC_GWTMODEL_TYPE + use VirtualModelModule + use NumericalModelModule, only: NumericalModelType + implicit none + private + + public :: add_virtual_gwt_model + + type, extends(VirtualModelType) :: VirtualGwtModelType + ! DSP + type(VirtualIntType), pointer :: dsp_idiffc => null() + type(VirtualIntType), pointer :: dsp_idisp => null() + type(VirtualDbl1dType), pointer :: dsp_diffc => null() + type(VirtualDbl1dType), pointer :: dsp_alh => null() + type(VirtualDbl1dType), pointer :: dsp_alv => null() + type(VirtualDbl1dType), pointer :: dsp_ath1 => null() + type(VirtualDbl1dType), pointer :: dsp_ath2 => null() + type(VirtualDbl1dType), pointer :: dsp_atv => null() + ! FMI + type(VirtualDbl1dType), pointer :: fmi_gwfhead => null() + type(VirtualDbl1dType), pointer :: fmi_gwfsat => null() + type(VirtualDbl2dType), pointer :: fmi_gwfspdis => null() + type(VirtualDbl1dType), pointer :: fmi_gwfflowja => null() + ! MST + type(VirtualDbl1dType), pointer :: mst_porosity => null() + ! GWT Model fields + type(VirtualIntType), pointer :: indsp => null() + type(VirtualIntType), pointer :: inmst => null() + contains + ! public + procedure :: create => vgwt_create + procedure :: prepare_stage => vgwt_prepare_stage + procedure :: destroy => vgwt_destroy + ! private + procedure, private :: create_virtual_fields + procedure, private :: deallocate_data + end type VirtualGwtModelType + +contains + + subroutine add_virtual_gwt_model(model_id, model_name, model) + use VirtualDataListsModule, only: virtual_model_list + integer(I4B) :: model_id !< global model id + character(len=*) :: model_name !< model name + class(NumericalModelType), pointer :: model !< the actual model (can be null() when remote) + ! local + class(VirtualGwtModelType), pointer :: virtual_gwt_model + class(*), pointer :: obj + + allocate (virtual_gwt_model) + call virtual_gwt_model%create(model_name, model_id, model) + + obj => virtual_gwt_model + call virtual_model_list%Add(obj) + + end subroutine add_virtual_gwt_model + + subroutine vgwt_create(this, name, id, model) + class(VirtualGwtModelType) :: this + character(len=*) :: name + integer(I4B) :: id + class(NumericalModelType), pointer :: model + + ! create base + call this%VirtualModelType%create(name, id, model) + this%container_type = VDC_GWTMODEL_TYPE + + ! allocate fields + call this%create_virtual_fields() + + end subroutine vgwt_create + + subroutine create_virtual_fields(this) + class(VirtualGwtModelType) :: this + + allocate (this%dsp_idiffc) + call this%create_field(this%dsp_idiffc%to_base(), 'IDIFFC', 'DSP') + allocate (this%dsp_idisp) + call this%create_field(this%dsp_idisp%to_base(), 'IDISP', 'DSP') + allocate (this%dsp_diffc) + call this%create_field(this%dsp_diffc%to_base(), 'DIFFC', 'DSP') + allocate (this%dsp_alh) + call this%create_field(this%dsp_alh%to_base(), 'ALH', 'DSP') + allocate (this%dsp_alv) + call this%create_field(this%dsp_alv%to_base(), 'ALV', 'DSP') + allocate (this%dsp_ath1) + call this%create_field(this%dsp_ath1%to_base(), 'ATH1', 'DSP') + allocate (this%dsp_ath2) + call this%create_field(this%dsp_ath2%to_base(), 'ATH2', 'DSP') + allocate (this%dsp_atv) + call this%create_field(this%dsp_atv%to_base(), 'ATV', 'DSP') + allocate (this%fmi_gwfhead) + call this%create_field(this%fmi_gwfhead%to_base(), 'GWFHEAD', 'FMI') + allocate (this%fmi_gwfsat) + call this%create_field(this%fmi_gwfsat%to_base(), 'GWFSAT', 'FMI') + allocate (this%fmi_gwfspdis) + call this%create_field(this%fmi_gwfspdis%to_base(), 'GWFSPDIS', 'FMI') + allocate (this%fmi_gwfflowja) + call this%create_field(this%fmi_gwfflowja%to_base(), 'GWFFLOWJA', 'FMI') + allocate (this%mst_porosity) + call this%create_field(this%mst_porosity%to_base(), 'POROSITY', 'MST') + allocate (this%indsp) + call this%create_field(this%indsp%to_base(), 'INDSP', '') + allocate (this%inmst) + call this%create_field(this%inmst%to_base(), 'INMST', '') + + end subroutine create_virtual_fields + + subroutine vgwt_prepare_stage(this, stage) + class(VirtualGwtModelType) :: this + integer(I4B) :: stage + ! local + integer(I4B) :: nr_nodes, nr_conns + + ! prepare base (=numerical) model data items + call this%VirtualModelType%prepare_stage(stage) + + nr_nodes = 0 + nr_conns = 0 + + if (stage == STG_AFTER_MDL_DF) then + + call this%map(this%dsp_idiffc%to_base(), & + (/STG_AFTER_MDL_DF/), MAP_ALL_TYPE) + call this%map(this%dsp_idisp%to_base(), & + (/STG_AFTER_MDL_DF/), MAP_ALL_TYPE) + call this%map(this%indsp%to_base(), & + (/STG_AFTER_MDL_DF/), MAP_ALL_TYPE) + call this%map(this%inmst%to_base(), & + (/STG_AFTER_MDL_DF/), MAP_ALL_TYPE) + + else if (stage == STG_BEFORE_AR) then + + call this%map(this%x%to_base(), nr_nodes, & + (/STG_BEFORE_AR, STG_BEFORE_AD, STG_BEFORE_CF/), & + MAP_NODE_TYPE) + call this%map(this%ibound%to_base(), nr_nodes, & + (/STG_BEFORE_AR/), MAP_NODE_TYPE) + + if (this%dsp_idiffc%get() > 0) then + call this%map(this%dsp_diffc%to_base(), nr_nodes, & + (/STG_BEFORE_AR/), MAP_NODE_TYPE) + end if + + if (this%dsp_idisp%get() > 0) then + call this%map(this%dsp_alh%to_base(), nr_nodes, & + (/STG_BEFORE_AR/), MAP_NODE_TYPE) + call this%map(this%dsp_alv%to_base(), nr_nodes, & + (/STG_BEFORE_AR/), MAP_NODE_TYPE) + call this%map(this%dsp_ath1%to_base(), nr_nodes, & + (/STG_BEFORE_AR/), MAP_NODE_TYPE) + call this%map(this%dsp_ath2%to_base(), nr_nodes, & + (/STG_BEFORE_AR/), MAP_NODE_TYPE) + call this%map(this%dsp_atv%to_base(), nr_nodes, & + (/STG_BEFORE_AR/), MAP_NODE_TYPE) + end if + + call this%map(this%fmi_gwfhead%to_base(), nr_nodes, & + (/STG_BEFORE_AD/), MAP_NODE_TYPE) + call this%map(this%fmi_gwfsat%to_base(), nr_nodes, & + (/STG_BEFORE_AD/), MAP_NODE_TYPE) + call this%map(this%fmi_gwfspdis%to_base(), 3, nr_nodes, & + (/STG_BEFORE_AD/), MAP_NODE_TYPE) + call this%map(this%fmi_gwfflowja%to_base(), nr_conns, & + (/STG_BEFORE_AD/), MAP_NODE_TYPE) + + if (this%indsp%get() > 0 .and. this%inmst%get() > 0) then + call this%map(this%mst_porosity%to_base(), nr_nodes, & + (/STG_AFTER_AR/), MAP_NODE_TYPE) + end if + + end if + + end subroutine vgwt_prepare_stage + + subroutine deallocate_data(this) + class(VirtualGwtModelType) :: this + + deallocate (this%dsp_idiffc) + deallocate (this%dsp_idisp) + deallocate (this%dsp_diffc) + deallocate (this%dsp_alh) + deallocate (this%dsp_alv) + deallocate (this%dsp_ath1) + deallocate (this%dsp_ath2) + deallocate (this%dsp_atv) + deallocate (this%fmi_gwfhead) + deallocate (this%fmi_gwfsat) + deallocate (this%fmi_gwfspdis) + deallocate (this%fmi_gwfflowja) + deallocate (this%mst_porosity) + deallocate (this%indsp) + deallocate (this%inmst) + + end subroutine deallocate_data + + subroutine vgwt_destroy(this) + class(VirtualGwtModelType) :: this + + call this%VirtualModelType%destroy() + call this%deallocate_data() + + end subroutine vgwt_destroy + +end module VirtualGwtModelModule diff --git a/src/Distributed/VirtualModel.f90 b/src/Distributed/VirtualModel.f90 new file mode 100644 index 00000000000..ce15f67e455 --- /dev/null +++ b/src/Distributed/VirtualModel.f90 @@ -0,0 +1,364 @@ +module VirtualModelModule + use VirtualBaseModule + use VirtualDataContainerModule + use ConstantsModule, only: LENMEMPATH + use KindModule, only: I4B, LGP + use ListModule, only: ListType + use SimStagesModule + use NumericalModelModule, only: NumericalModelType + implicit none + private + + public :: cast_as_virtual_model + public :: get_virtual_model_from_list + public :: get_virtual_model + + type, public, extends(VirtualDataContainerType) :: VirtualModelType + class(NumericalModelType), pointer :: local_model + ! CON + type(VirtualInt1dType), pointer :: con_ia => null() + type(VirtualInt1dType), pointer :: con_ja => null() + type(VirtualInt1dType), pointer :: con_jas => null() + type(VirtualInt1dType), pointer :: con_ihc => null() + type(VirtualDbl1dType), pointer :: con_hwva => null() + type(VirtualDbl1dType), pointer :: con_cl1 => null() + type(VirtualDbl1dType), pointer :: con_cl2 => null() + type(VirtualDbl1dType), pointer :: con_anglex => null() + ! DIS + type(VirtualIntType), pointer :: dis_ndim => null() + type(VirtualIntType), pointer :: dis_nodes => null() + type(VirtualIntType), pointer :: dis_nodesuser => null() + type(VirtualInt1dType), pointer :: dis_nodeuser => null() + type(VirtualIntType), pointer :: dis_nja => null() + type(VirtualIntType), pointer :: dis_njas => null() + type(VirtualDblType), pointer :: dis_xorigin => null() + type(VirtualDblType), pointer :: dis_yorigin => null() + type(VirtualDblType), pointer :: dis_angrot => null() + type(VirtualDbl1dType), pointer :: dis_xc => null() + type(VirtualDbl1dType), pointer :: dis_yc => null() + type(VirtualDbl1dType), pointer :: dis_top => null() + type(VirtualDbl1dType), pointer :: dis_bot => null() + type(VirtualDbl1dType), pointer :: dis_area => null() + ! Numerical Model fields + type(VirtualIntType), pointer :: moffset => null() + type(VirtualDbl1dType), pointer :: x => null() + type(VirtualDbl1dType), pointer :: x_old => null() + type(VirtualInt1dType), pointer :: ibound => null() + contains + ! public + procedure :: create => vm_create + procedure :: prepare_stage => vm_prepare_stage + procedure :: destroy => vm_destroy + generic :: operator(==) => eq_virtual_model, eq_numerical_model + + procedure :: dis_get_nodeuser + procedure :: dis_noder_to_string + + ! private + procedure, private :: create_virtual_fields + procedure, private :: deallocate_data + procedure, private :: eq_virtual_model + procedure, private :: eq_numerical_model + end type VirtualModelType + +contains + + subroutine vm_create(this, name, id, model) + class(VirtualModelType) :: this + character(len=*) :: name + integer(I4B) :: id + class(NumericalModelType), pointer :: model + ! local + logical(LGP) :: is_local + + is_local = associated(model) + call this%VirtualDataContainerType%vdc_create(name, id, is_local) + + this%local_model => model + + ! allocate fields + call this%create_virtual_fields() + + end subroutine vm_create + + subroutine create_virtual_fields(this) + class(VirtualModelType) :: this + + ! CON + allocate (this%con_ia) + call this%create_field(this%con_ia%to_base(), 'IA', 'CON') + allocate (this%con_ja) + call this%create_field(this%con_ja%to_base(), 'JA', 'CON') + allocate (this%con_jas) + call this%create_field(this%con_jas%to_base(), 'JAS', 'CON') + allocate (this%con_ihc) + call this%create_field(this%con_ihc%to_base(), 'IHC', 'CON') + allocate (this%con_hwva) + call this%create_field(this%con_hwva%to_base(), 'HWVA', 'CON') + allocate (this%con_cl1) + call this%create_field(this%con_cl1%to_base(), 'CL1', 'CON') + allocate (this%con_cl2) + call this%create_field(this%con_cl2%to_base(), 'CL2', 'CON') + allocate (this%con_anglex) + call this%create_field(this%con_anglex%to_base(), 'ANGLEX', 'CON') + ! DIS + allocate (this%dis_ndim) + call this%create_field(this%dis_ndim%to_base(), 'NDIM', 'DIS') + allocate (this%dis_nodes) + call this%create_field(this%dis_nodes%to_base(), 'NODES', 'DIS') + allocate (this%dis_nodesuser) + call this%create_field(this%dis_nodesuser%to_base(), 'NODESUSER', 'DIS') + allocate (this%dis_nodeuser) + call this%create_field(this%dis_nodeuser%to_base(), 'NODEUSER', 'DIS') + allocate (this%dis_nja) + call this%create_field(this%dis_nja%to_base(), 'NJA', 'DIS') + allocate (this%dis_njas) + call this%create_field(this%dis_njas%to_base(), 'NJAS', 'DIS') + allocate (this%dis_xorigin) + call this%create_field(this%dis_xorigin%to_base(), 'XORIGIN', 'DIS') + allocate (this%dis_yorigin) + call this%create_field(this%dis_yorigin%to_base(), 'YORIGIN', 'DIS') + allocate (this%dis_angrot) + call this%create_field(this%dis_angrot%to_base(), 'ANGROT', 'DIS') + allocate (this%dis_xc) + call this%create_field(this%dis_xc%to_base(), 'XC', 'DIS') + allocate (this%dis_yc) + call this%create_field(this%dis_yc%to_base(), 'YC', 'DIS') + allocate (this%dis_top) + call this%create_field(this%dis_top%to_base(), 'TOP', 'DIS') + allocate (this%dis_bot) + call this%create_field(this%dis_bot%to_base(), 'BOT', 'DIS') + allocate (this%dis_area) + call this%create_field(this%dis_area%to_base(), 'AREA', 'DIS') + ! Numerical model + allocate (this%moffset) + call this%create_field(this%moffset%to_base(), 'MOFFSET', '') + allocate (this%x) + call this%create_field(this%x%to_base(), 'X', '') + allocate (this%x_old) + call this%create_field(this%x_old%to_base(), 'XOLD', '') + allocate (this%ibound) + call this%create_field(this%ibound%to_base(), 'IBOUND', '') + + end subroutine create_virtual_fields + + subroutine vm_prepare_stage(this, stage) + class(VirtualModelType) :: this + integer(I4B) :: stage + ! local + integer(I4B) :: nodes, nodesuser, nja, njas + logical(LGP) :: is_reduced + + if (stage == STG_AFTER_MDL_DF) then + + call this%map(this%dis_ndim%to_base(), & + (/STG_AFTER_MDL_DF/), MAP_ALL_TYPE) + call this%map(this%dis_nodes%to_base(), & + (/STG_AFTER_MDL_DF/), MAP_ALL_TYPE) + call this%map(this%dis_nodesuser%to_base(), & + (/STG_AFTER_MDL_DF/), MAP_ALL_TYPE) + call this%map(this%dis_nja%to_base(), & + (/STG_AFTER_MDL_DF/), MAP_ALL_TYPE) + call this%map(this%dis_njas%to_base(), & + (/STG_AFTER_MDL_DF/), MAP_ALL_TYPE) + + else if (stage == STG_BEFORE_AC) then + + nodes = this%dis_nodes%get() + nodesuser = this%dis_nodesuser%get() + is_reduced = (nodes /= nodesuser) + call this%map(this%moffset%to_base(), & + (/STG_BEFORE_AC/), MAP_ALL_TYPE) + if (is_reduced) then + call this%map(this%dis_nodeuser%to_base(), nodes, & + (/STG_BEFORE_AC/), MAP_ALL_TYPE) + else + ! no reduction, zero sized array, never synchronize + call this%map(this%dis_nodeuser%to_base(), 0, & + (/STG_NEVER/), MAP_ALL_TYPE) + end if + else if (stage == STG_BEFORE_DF) then + + nodes = this%dis_nodes%get() + nja = this%dis_nja%get() + njas = this%dis_njas%get() + ! DIS + call this%map(this%dis_xorigin%to_base(), & + (/STG_BEFORE_DF/), MAP_ALL_TYPE) + call this%map(this%dis_yorigin%to_base(), & + (/STG_BEFORE_DF/), MAP_ALL_TYPE) + call this%map(this%dis_angrot%to_base(), & + (/STG_BEFORE_DF/), MAP_ALL_TYPE) + call this%map(this%dis_xc%to_base(), nodes, & + (/STG_BEFORE_DF/), MAP_ALL_TYPE) + call this%map(this%dis_yc%to_base(), nodes, & + (/STG_BEFORE_DF/), MAP_ALL_TYPE) + call this%map(this%dis_top%to_base(), nodes, & + (/STG_BEFORE_DF/), MAP_ALL_TYPE) + call this%map(this%dis_bot%to_base(), nodes, & + (/STG_BEFORE_DF/), MAP_ALL_TYPE) + call this%map(this%dis_area%to_base(), nodes, & + (/STG_BEFORE_DF/), MAP_ALL_TYPE) + ! CON + call this%map(this%con_ia%to_base(), nodes + 1, & + (/STG_BEFORE_DF/), MAP_ALL_TYPE) + call this%map(this%con_ja%to_base(), nja, & + (/STG_BEFORE_DF/), MAP_ALL_TYPE) + call this%map(this%con_jas%to_base(), nja, & + (/STG_BEFORE_DF/), MAP_ALL_TYPE) + call this%map(this%con_ihc%to_base(), njas, & + (/STG_BEFORE_DF/), MAP_ALL_TYPE) + call this%map(this%con_hwva%to_base(), njas, & + (/STG_BEFORE_DF/), MAP_ALL_TYPE) + call this%map(this%con_cl1%to_base(), njas, & + (/STG_BEFORE_DF/), MAP_ALL_TYPE) + call this%map(this%con_cl2%to_base(), njas, & + (/STG_BEFORE_DF/), MAP_ALL_TYPE) + call this%map(this%con_anglex%to_base(), njas, & + (/STG_BEFORE_DF/), MAP_ALL_TYPE) + end if + + end subroutine vm_prepare_stage + + !> @brief Get user node number from reduced number + !< + function dis_get_nodeuser(this, node_reduced) result(node_user) + class(VirtualModelType) :: this !< this virtual model + integer(I4B), intent(in) :: node_reduced !< the reduced node number + integer(I4B) :: node_user !< the returned user node number + + if (this%dis_nodes%get() < this%dis_nodesuser%get()) then + node_user = this%dis_nodeuser%get(node_reduced) + else + node_user = node_reduced + end if + + end function dis_get_nodeuser + + subroutine dis_noder_to_string(this, node_reduced, node_str) + class(VirtualModelType) :: this !< this virtual model + integer(I4B), intent(in) :: node_reduced !< reduced node number + character(len=*), intent(inout) :: node_str !< the string representative of the user node number + ! local + character(len=11) :: nr_str + + if (this%is_local) then + call this%local_model%dis%noder_to_string(node_reduced, node_str) + else + ! for now this will look like: (102r) + write (nr_str, '(i0)') node_reduced + node_str = '('//trim(adjustl(nr_str))//'r)' + end if + + end subroutine dis_noder_to_string + + subroutine vm_destroy(this) + class(VirtualModelType) :: this + + call this%VirtualDataContainerType%destroy() + call this%deallocate_data() + + end subroutine vm_destroy + + subroutine deallocate_data(this) + class(VirtualModelType) :: this + + ! CON + deallocate (this%con_ia) + deallocate (this%con_ja) + deallocate (this%con_jas) + deallocate (this%con_ihc) + deallocate (this%con_hwva) + deallocate (this%con_cl1) + deallocate (this%con_cl2) + deallocate (this%con_anglex) + ! DIS + deallocate (this%dis_ndim) + deallocate (this%dis_nodes) + deallocate (this%dis_nodeuser) + deallocate (this%dis_nja) + deallocate (this%dis_njas) + deallocate (this%dis_xorigin) + deallocate (this%dis_yorigin) + deallocate (this%dis_angrot) + deallocate (this%dis_xc) + deallocate (this%dis_yc) + deallocate (this%dis_top) + deallocate (this%dis_bot) + deallocate (this%dis_area) + ! Numerical model + deallocate (this%moffset) + deallocate (this%x) + deallocate (this%x_old) + deallocate (this%ibound) + + end subroutine deallocate_data + + function get_virtual_model_from_list(model_list, idx) result(v_model) + type(ListType) :: model_list + integer(I4B) :: idx + class(VirtualModelType), pointer :: v_model + ! local + class(*), pointer :: obj_ptr + + obj_ptr => model_list%GetItem(idx) + v_model => cast_as_virtual_model(obj_ptr) + return + + end function get_virtual_model_from_list + + function cast_as_virtual_model(obj_ptr) result(v_model) + class(*), pointer :: obj_ptr + class(VirtualModelType), pointer :: v_model + + v_model => null() + select type (obj_ptr) + class is (VirtualModelType) + v_model => obj_ptr + end select + + end function cast_as_virtual_model + + function eq_virtual_model(this, v_model) result(is_equal) + class(VirtualModelType), intent(in) :: this + class(VirtualModelType), intent(in) :: v_model + logical(LGP) :: is_equal + + is_equal = (this%id == v_model%id) + + end function eq_virtual_model + + function eq_numerical_model(this, num_model) result(is_equal) + class(VirtualModelType), intent(in) :: this + class(NumericalModelType), intent(in) :: num_model + logical(LGP) :: is_equal + + is_equal = (this%id == num_model%id) + + end function eq_numerical_model + +!> @brief Returns a virtual model with the specified id +!< from the global list + function get_virtual_model(model_id) result(virtual_model) + use VirtualDataListsModule, only: virtual_model_list + integer(I4B) :: model_id + class(VirtualModelType), pointer :: virtual_model + ! local + integer(I4B) :: i + class(*), pointer :: vm + + virtual_model => null() + do i = 1, virtual_model_list%Count() + vm => virtual_model_list%GetItem(i) + select type (vm) + class is (VirtualModelType) + if (vm%id == model_id) then + virtual_model => vm + return + end if + end select + end do + + end function get_virtual_model + +end module VirtualModelModule diff --git a/src/Distributed/VirtualSolution.f90 b/src/Distributed/VirtualSolution.f90 new file mode 100644 index 00000000000..7686c6a34d5 --- /dev/null +++ b/src/Distributed/VirtualSolution.f90 @@ -0,0 +1,18 @@ +module VirtualSolutionModule + use KindModule, only: I4B + use ListModule + use VirtualDataContainerModule, only: VdcPtrType + implicit none + private + + !> This bundles all virtual data for a particular solution + !< for convenience, it never owns any of it + type, public :: VirtualSolutionType + integer(I4B) :: solution_id = -1 + type(VdcPtrType), dimension(:), pointer :: models => null() + type(VdcPtrType), dimension(:), pointer :: exchanges => null() + ! type(ListType) :: exchange_movers + ! type(ListType) :: etc... + end type VirtualSolutionType + +end module VirtualSolutionModule diff --git a/src/Exchange/DisConnExchange.f90 b/src/Exchange/DisConnExchange.f90 index 917095b268a..b8f5118009d 100644 --- a/src/Exchange/DisConnExchange.f90 +++ b/src/Exchange/DisConnExchange.f90 @@ -2,11 +2,12 @@ module DisConnExchangeModule use KindModule, only: I4B, DP, LGP use SimVariablesModule, only: errmsg use ConstantsModule, only: LENAUXNAME, LENBOUNDNAME, LINELENGTH + use CharacterStringModule use ListModule, only: ListType use MemoryManagerModule, only: mem_allocate, mem_reallocate use BlockParserModule, only: BlockParserType use NumericalModelModule, only: NumericalModelType - use DistributedModelModule, only: DistributedModelType + use VirtualModelModule, only: VirtualModelType use NumericalExchangeModule, only: NumericalExchangeType implicit none @@ -24,8 +25,10 @@ module DisConnExchangeModule class(NumericalModelType), pointer :: model1 => null() !< model 1 class(NumericalModelType), pointer :: model2 => null() !< model 2 - class(DistributedModelType), pointer :: dmodel1 => null() !< distributed model 1 - class(DistributedModelType), pointer :: dmodel2 => null() !< distributed model 2 + class(VirtualModelType), pointer :: v_model1 => null() !< virtual model 1 + class(VirtualModelType), pointer :: v_model2 => null() !< virtual model 2 + logical(LGP) :: is_datacopy !< when true, this exchange is just a data copy on another process and + !! not responsible for controlling movers, observations, ... integer(I4B), pointer :: nexg => null() !< number of exchanges integer(I4B), dimension(:), pointer, contiguous :: nodem1 => null() !< node numbers in model 1 @@ -40,6 +43,8 @@ module DisConnExchangeModule character(len=LENAUXNAME), dimension(:), & pointer, contiguous :: auxname => null() !< vector of auxname + type(CharacterStringType), dimension(:), pointer, & + contiguous :: auxname_cst => null() !< copy of vector auxname that can be stored in memory manager real(DP), dimension(:, :), pointer, contiguous :: auxvar => null() !< array of auxiliary variable values integer(I4B), pointer :: ianglex => null() !< flag indicating anglex was read, if read, ianglex is index in auxvar integer(I4B), pointer :: icdist => null() !< flag indicating cdist was read, if read, icdist is index in auxvar @@ -95,9 +100,12 @@ function parse_option(this, keyword, iout) result(parsed) call urdaux(this%naux, this%parser%iuactive, iout, lloc, istart, & istop, caux, line, 'GWF_GWF_Exchange') call mem_reallocate(this%auxname, LENAUXNAME, this%naux, & - 'AUXNAME', trim(this%memoryPath)) + 'AUXNAME', this%memoryPath) + call mem_reallocate(this%auxname_cst, LENAUXNAME, this%naux, & + 'AUXNAME_CST', this%memoryPath) do n = 1, this%naux this%auxname(n) = caux(n) + this%auxname_cst(n) = caux(n) end do deallocate (caux) ! @@ -220,20 +228,28 @@ subroutine read_data(this, iout) lloc = 1 ! ! -- Read and check node 1 - call this%parser%GetCellid(this%model1%dis%ndim, cellid1, & + call this%parser%GetCellid(this%v_model1%dis_ndim%get(), cellid1, & flag_string=.true.) - nodem1 = this%model1%dis%noder_from_cellid(cellid1, & - this%parser%iuactive, & - iout, flag_string=.true.) - this%nodem1(iexg) = nodem1 + if (associated(this%model1)) then + nodem1 = this%model1%dis%noder_from_cellid(cellid1, & + this%parser%iuactive, & + iout, flag_string=.true.) + this%nodem1(iexg) = nodem1 + else + this%nodem1(iexg) = -1 + end if ! ! -- Read and check node 2 - call this%parser%GetCellid(this%model2%dis%ndim, cellid2, & + call this%parser%GetCellid(this%v_model2%dis_ndim%get(), cellid2, & flag_string=.true.) - nodem2 = this%model2%dis%noder_from_cellid(cellid2, & - this%parser%iuactive, & - iout, flag_string=.true.) - this%nodem2(iexg) = nodem2 + if (associated(this%model2)) then + nodem2 = this%model2%dis%noder_from_cellid(cellid2, & + this%parser%iuactive, & + iout, flag_string=.true.) + this%nodem2(iexg) = nodem2 + else + this%nodem2(iexg) = -1 + end if ! ! -- Read rest of input line this%ihc(iexg) = this%parser%GetInteger() @@ -264,21 +280,25 @@ subroutine read_data(this, iout) end if ! ! -- Check to see if nodem1 is outside of active domain - if (nodem1 <= 0) then - write (errmsg, *) & - trim(adjustl(this%model1%name))// & - ' Cell is outside active grid domain ('// & - trim(adjustl(cellid1))//').' - call store_error(errmsg) + if (associated(this%model1)) then + if (nodem1 <= 0) then + write (errmsg, *) & + trim(adjustl(this%model1%name))// & + ' Cell is outside active grid domain ('// & + trim(adjustl(cellid1))//').' + call store_error(errmsg) + end if end if ! ! -- Check to see if nodem2 is outside of active domain - if (nodem2 <= 0) then - write (errmsg, *) & - trim(adjustl(this%model2%name))// & - ' Cell is outside active grid domain ('// & - trim(adjustl(cellid2))//').' - call store_error(errmsg) + if (associated(this%model2)) then + if (nodem2 <= 0) then + write (errmsg, *) & + trim(adjustl(this%model2%name))// & + ' Cell is outside active grid domain ('// & + trim(adjustl(cellid2))//').' + call store_error(errmsg) + end if end if end do ! @@ -318,7 +338,9 @@ subroutine allocate_scalars(this) call mem_allocate(this%inamedbound, 'INAMEDBOUND', this%memoryPath) call mem_allocate(this%auxname, LENAUXNAME, 0, & - 'AUXNAME', trim(this%memoryPath)) + 'AUXNAME', this%memoryPath) + call mem_allocate(this%auxname_cst, LENAUXNAME, 0, & + 'AUXNAME_CST', this%memoryPath) this%nexg = 0 this%naux = 0 @@ -360,12 +382,13 @@ end subroutine allocate_arrays !> @brief Should interface model be used to handle these !! exchanges, to be overridden for inheriting types !< - function use_interface_model(this) result(useIM) + function use_interface_model(this) result(use_im) class(DisConnExchangeType) :: this !< instance of exchange object - logical(LGP) :: useIM !< flag whether interface model should be used - !! for this exchange instead + logical(LGP) :: use_im !< flag whether interface model should be used + !! for this exchange instead - useIM = .false. + ! use im when one of the models is not local + use_im = .not. (this%v_model1%is_local .and. this%v_model2%is_local) end function use_interface_model @@ -389,7 +412,8 @@ subroutine disconnex_da(this) ! scalars call mem_deallocate(this%nexg) call mem_deallocate(this%naux) - call mem_deallocate(this%auxname, 'AUXNAME', trim(this%memoryPath)) + call mem_deallocate(this%auxname, 'AUXNAME', this%memoryPath) + call mem_deallocate(this%auxname_cst, 'AUXNAME_CST', this%memoryPath) call mem_deallocate(this%ianglex) call mem_deallocate(this%icdist) call mem_deallocate(this%ixt3d) diff --git a/src/Exchange/GhostNode.f90 b/src/Exchange/GhostNode.f90 index 549eef9be74..82af8a4617e 100644 --- a/src/Exchange/GhostNode.f90 +++ b/src/Exchange/GhostNode.f90 @@ -5,7 +5,7 @@ module GhostNodeModule use NumericalModelModule, only: NumericalModelType use NumericalPackageModule, only: NumericalPackageType use BlockParserModule, only: BlockParserType - use MatrixModule + use MatrixBaseModule implicit none diff --git a/src/Exchange/GwfGwfExchange.f90 b/src/Exchange/GwfGwfExchange.f90 index f9ab9d9c46d..b8ea6954c45 100644 --- a/src/Exchange/GwfGwfExchange.f90 +++ b/src/Exchange/GwfGwfExchange.f90 @@ -17,19 +17,19 @@ module GwfGwfExchangeModule use ConstantsModule, only: LENBOUNDNAME, NAMEDBOUNDFLAG, LINELENGTH, & TABCENTER, TABLEFT, LENAUXNAME, DNODATA use ListModule, only: ListType - use ListsModule, only: basemodellist, distmodellist + use ListsModule, only: basemodellist use DisConnExchangeModule, only: DisConnExchangeType use GwfModule, only: GwfModelType - use DistributedModelModule, only: DistributedModelType, GetDistModelFromList + use VirtualModelModule, only: VirtualModelType use GhostNodeModule, only: GhostNodeType use GwfMvrModule, only: GwfMvrType use ObserveModule, only: ObserveType use ObsModule, only: ObsType use SimModule, only: count_errors, store_error, store_error_unit - use SimVariablesModule, only: errmsg + use SimVariablesModule, only: errmsg, model_loc_idx use BlockParserModule, only: BlockParserType use TableModule, only: TableType, table_cr - use MatrixModule + use MatrixBaseModule implicit none @@ -100,6 +100,7 @@ module GwfGwfExchangeModule procedure :: parse_option procedure :: read_gnc procedure :: read_mvr + procedure, private :: calc_cond_sat procedure, private :: condcalc procedure, private :: rewet procedure, private :: qcalc @@ -109,7 +110,7 @@ module GwfGwfExchangeModule procedure, private :: gwf_gwf_rp_obs procedure, public :: gwf_gwf_save_simvals procedure, private :: gwf_gwf_calc_simvals - procedure, public :: gwf_gwf_set_simvals_to_npf + procedure, public :: gwf_gwf_set_flow_to_npf procedure, private :: validate_exchange procedure :: gwf_gwf_add_to_flowja end type GwfExchangeType @@ -118,26 +119,26 @@ module GwfGwfExchangeModule !> @ brief Create GWF GWF exchange !! - !! Create a new GWF to GWF exchange object. - !! - !< - subroutine gwfexchange_create(filename, id, m1id, m2id) + !< Create a new GWF to GWF exchange object. + subroutine gwfexchange_create(filename, name, id, m1_id, m2_id) ! -- modules use ConstantsModule, only: LINELENGTH use BaseModelModule, only: BaseModelType + use VirtualModelModule, only: get_virtual_model use ListsModule, only: baseexchangelist use ObsModule, only: obs_cr use MemoryHelperModule, only: create_mem_path ! -- dummy character(len=*), intent(in) :: filename !< filename for reading + character(len=*) :: name !< exchange name integer(I4B), intent(in) :: id !< id for the exchange - integer(I4B), intent(in) :: m1id !< id for model 1 - integer(I4B), intent(in) :: m2id !< id for model 2 + integer(I4B), intent(in) :: m1_id !< id for model 1 + integer(I4B), intent(in) :: m2_id !< id for model 2 ! -- local type(GwfExchangeType), pointer :: exchange class(BaseModelType), pointer :: mb class(BaseExchangeType), pointer :: baseexchange - character(len=20) :: cint + integer(I4B) :: m1_index, m2_index ! ! -- Create a new exchange and add it to the baseexchangelist container allocate (exchange) @@ -146,8 +147,7 @@ subroutine gwfexchange_create(filename, id, m1id, m2id) ! ! -- Assign id and name exchange%id = id - write (cint, '(i0)') id - exchange%name = 'GWF-GWF_'//trim(adjustl(cint)) + exchange%name = name exchange%memoryPath = create_mem_path(exchange%name) ! ! -- allocate scalars and set defaults @@ -156,25 +156,32 @@ subroutine gwfexchange_create(filename, id, m1id, m2id) exchange%typename = 'GWF-GWF' ! ! -- set gwfmodel1 - mb => GetBaseModelFromList(basemodellist, m1id) - select type (mb) - type is (GwfModelType) - exchange%model1 => mb - exchange%gwfmodel1 => mb - end select - exchange%dmodel1 => GetDistModelFromList(distmodellist, m1id) + m1_index = model_loc_idx(m1_id) + if (m1_index > 0) then + mb => GetBaseModelFromList(basemodellist, m1_index) + select type (mb) + type is (GwfModelType) + exchange%model1 => mb + exchange%gwfmodel1 => mb + end select + end if + exchange%v_model1 => get_virtual_model(m1_id) + exchange%is_datacopy = .not. exchange%v_model1%is_local ! ! -- set gwfmodel2 - mb => GetBaseModelFromList(basemodellist, m2id) - select type (mb) - type is (GwfModelType) - exchange%model2 => mb - exchange%gwfmodel2 => mb - end select - exchange%dmodel2 => GetDistModelFromList(distmodellist, m2id) + m2_index = model_loc_idx(m2_id) + if (m2_index > 0) then + mb => GetBaseModelFromList(basemodellist, m2_index) + select type (mb) + type is (GwfModelType) + exchange%model2 => mb + exchange%gwfmodel2 => mb + end select + end if + exchange%v_model2 => get_virtual_model(m2_id) ! ! -- Verify that gwf model1 is of the correct type - if (.not. associated(exchange%gwfmodel1)) then + if (.not. associated(exchange%gwfmodel1) .and. m1_index > 0) then write (errmsg, '(3a)') 'Problem with GWF-GWF exchange ', & trim(exchange%name), & '. First specified GWF Model does not appear to be of the correct type.' @@ -182,7 +189,7 @@ subroutine gwfexchange_create(filename, id, m1id, m2id) end if ! ! -- Verify that gwf model2 is of the correct type - if (.not. associated(exchange%gwfmodel2)) then + if (.not. associated(exchange%gwfmodel2) .and. m2_index > 0) then write (errmsg, '(3a)') 'Problem with GWF-GWF exchange ', & trim(exchange%name), & '. Second specified GWF Model does not appear to be of the correct type.' @@ -219,12 +226,15 @@ subroutine gwf_gwf_df(this) call this%parser%Initialize(inunit, iout) ! ! -- Ensure models are in same solution - if (this%gwfmodel1%idsoln /= this%gwfmodel2%idsoln) then - call store_error('ERROR. TWO MODELS ARE CONNECTED IN A GWF '// & - 'EXCHANGE BUT THEY ARE IN DIFFERENT SOLUTIONS. '// & - 'GWF MODELS MUST BE IN SAME SOLUTION: '// & - trim(this%gwfmodel1%name)//' '//trim(this%gwfmodel2%name)) - call this%parser%StoreErrorUnit() + if (associated(this%gwfmodel1) .and. associated(this%gwfmodel2)) then + if (this%gwfmodel1%idsoln /= this%gwfmodel2%idsoln) then + call store_error('ERROR. TWO MODELS ARE CONNECTED IN A GWF '// & + 'EXCHANGE BUT THEY ARE IN DIFFERENT SOLUTIONS. '// & + 'GWF MODELS MUST BE IN SAME SOLUTION: '// & + trim(this%gwfmodel1%name)//' '// & + trim(this%gwfmodel2%name)) + call this%parser%StoreErrorUnit() + end if end if ! ! -- read options @@ -240,8 +250,12 @@ subroutine gwf_gwf_df(this) call this%read_data(iout) ! ! -- call each model and increase the edge count - call this%gwfmodel1%npf%increase_edge_count(this%nexg) - call this%gwfmodel2%npf%increase_edge_count(this%nexg) + if (associated(this%gwfmodel1)) then + call this%gwfmodel1%npf%increase_edge_count(this%nexg) + end if + if (associated(this%gwfmodel2)) then + call this%gwfmodel2%npf%increase_edge_count(this%nexg) + end if ! ! -- Create and read ghost node information if (this%ingnc > 0) then @@ -259,7 +273,9 @@ subroutine gwf_gwf_df(this) ! ! -- Store obs call this%gwf_gwf_df_obs() - call this%obs%obs_df(iout, this%name, 'GWF-GWF', this%gwfmodel1%dis) + if (associated(this%gwfmodel1)) then + call this%obs%obs_df(iout, this%name, 'GWF-GWF', this%gwfmodel1%dis) + end if ! ! -- validate call this%validate_exchange() @@ -273,6 +289,7 @@ end subroutine gwf_gwf_df subroutine validate_exchange(this) class(GwfExchangeType) :: this !< GwfExchangeType ! local + logical(LGP) :: has_k22, has_spdis, has_vsc ! Periodic boundary condition in exchange don't allow XT3D (=interface model) if (associated(this%model1, this%model2)) then @@ -284,10 +301,35 @@ subroutine validate_exchange(this) end if end if - ! Check to see if horizontal anisotropy is in either model1 or model2. - ! If so, then ANGLDEGX must be provided as an auxiliary variable for this + ! XT3D needs angle information + if (this%ixt3d > 0 .and. this%ianglex == 0) then + write (errmsg, '(3a)') 'GWF-GWF exchange ', trim(this%name), & + ' requires that ANGLDEGX be specified as an'// & + ' auxiliary variable because XT3D is enabled' + call store_error(errmsg, terminate=.TRUE.) + end if + + ! determine if specific functionality is demanded, + ! in model 1 or model 2 (in parallel, only one of + ! the models is checked, but the exchange is duplicated) + has_k22 = .false. + has_spdis = .false. + has_vsc = .false. + if (associated(this%gwfmodel1)) then + has_k22 = (this%gwfmodel1%npf%ik22 /= 0) + has_spdis = (this%gwfmodel1%npf%icalcspdis /= 0) + has_vsc = (this%gwfmodel1%npf%invsc /= 0) + end if + if (associated(this%gwfmodel2)) then + has_k22 = has_k22 .or. (this%gwfmodel2%npf%ik22 /= 0) + has_spdis = has_spdis .or. (this%gwfmodel2%npf%icalcspdis /= 0) + has_vsc = has_vsc .or. (this%gwfmodel2%npf%invsc /= 0) + end if + + ! If horizontal anisotropy is in either model1 or model2, + ! ANGLDEGX must be provided as an auxiliary variable for this ! GWF-GWF exchange (this%ianglex > 0). - if (this%gwfmodel1%npf%ik22 /= 0 .or. this%gwfmodel2%npf%ik22 /= 0) then + if (has_k22) then if (this%ianglex == 0) then write (errmsg, '(3a)') 'GWF-GWF exchange ', trim(this%name), & ' requires that ANGLDEGX be specified as an'// & @@ -297,11 +339,10 @@ subroutine validate_exchange(this) end if end if - ! Check to see if specific discharge is needed for model1 or model2. - ! If so, then ANGLDEGX must be provided as an auxiliary variable for this + ! If specific discharge is needed for model1 or model2, + ! ANGLDEGX must be provided as an auxiliary variable for this ! GWF-GWF exchange (this%ianglex > 0). - if (this%gwfmodel1%npf%icalcspdis /= 0 .or. & - this%gwfmodel2%npf%icalcspdis /= 0) then + if (has_spdis) then if (this%ianglex == 0) then write (errmsg, '(3a)') 'GWF-GWF exchange ', trim(this%name), & ' requires that ANGLDEGX be specified as an'// & @@ -320,17 +361,9 @@ subroutine validate_exchange(this) end if end if - if (this%ixt3d > 0 .and. this%ianglex == 0) then - write (errmsg, '(3a)') 'GWF-GWF exchange ', trim(this%name), & - ' requires that ANGLDEGX be specified as an'// & - ' auxiliary variable because XT3D is enabled' - call store_error(errmsg, terminate=.TRUE.) - end if - ! If viscosity is on in either model, then terminate with an ! error as viscosity package doesn't work yet with exchanges. - if (this%gwfmodel1%npf%invsc /= 0 .or. & - this%gwfmodel2%npf%invsc /= 0) then + if (has_vsc) then write (errmsg, '(3a)') 'GWF-GWF exchange ', trim(this%name), & ' requires that the Viscosity Package is inactive'// & ' in both of the connected models.' @@ -408,93 +441,14 @@ end subroutine gwf_gwf_mc !< subroutine gwf_gwf_ar(this) ! -- modules - use ConstantsModule, only: LINELENGTH, DZERO, DHALF, DONE, DPIO180 - use GwfNpfModule, only: condmean, vcond, hcond ! -- dummy class(GwfExchangeType) :: this !< GwfExchangeType - ! -- local - integer(I4B) :: iexg - integer(I4B) :: n, m, ihc - real(DP) :: topn, topm - real(DP) :: botn, botm - real(DP) :: satn, satm - real(DP) :: thickn, thickm - real(DP) :: angle, hyn, hym - real(DP) :: csat - real(DP) :: fawidth - real(DP), dimension(3) :: vg ! ! -- If mover is active, then call ar routine if (this%inmvr > 0) call this%mvr%mvr_ar() ! - ! -- Go through each connection and calculate the saturated conductance - do iexg = 1, this%nexg - ! - ihc = this%ihc(iexg) - n = this%nodem1(iexg) - m = this%nodem2(iexg) - topn = this%gwfmodel1%dis%top(n) - topm = this%gwfmodel2%dis%top(m) - botn = this%gwfmodel1%dis%bot(n) - botm = this%gwfmodel2%dis%bot(m) - satn = this%gwfmodel1%npf%sat(n) - satm = this%gwfmodel2%npf%sat(m) - thickn = (topn - botn) * satn - thickm = (topm - botm) * satm - ! - ! -- Calculate conductance depending on connection orientation - if (ihc == 0) then - ! - ! -- Vertical conductance for fully saturated conditions - vg(1) = DZERO - vg(2) = DZERO - vg(3) = DONE - hyn = this%gwfmodel1%npf%hy_eff(n, 0, ihc, vg=vg) - hym = this%gwfmodel2%npf%hy_eff(m, 0, ihc, vg=vg) - csat = vcond(1, 1, 1, 1, 0, 1, 1, DONE, & - botn, botm, & - hyn, hym, & - satn, satm, & - topn, topm, & - botn, botm, & - this%hwva(iexg)) - else - ! - ! -- Calculate horizontal conductance - hyn = this%gwfmodel1%npf%k11(n) - hym = this%gwfmodel2%npf%k11(m) - ! - ! -- Check for anisotropy in models, and recalculate hyn and hym - if (this%ianglex > 0) then - angle = this%auxvar(this%ianglex, iexg) * DPIO180 - vg(1) = abs(cos(angle)) - vg(2) = abs(sin(angle)) - vg(3) = DZERO - ! - ! -- anisotropy in model 1 - if (this%gwfmodel1%npf%ik22 /= 0) then - hyn = this%gwfmodel1%npf%hy_eff(n, 0, ihc, vg=vg) - end if - ! - ! -- anisotropy in model 2 - if (this%gwfmodel2%npf%ik22 /= 0) then - hym = this%gwfmodel2%npf%hy_eff(m, 0, ihc, vg=vg) - end if - end if - ! - fawidth = this%hwva(iexg) - csat = hcond(1, 1, 1, 1, this%inewton, 0, ihc, & - this%icellavg, 0, 0, DONE, & - topn, topm, satn, satm, hyn, hym, & - topn, topm, & - botn, botm, & - this%cl1(iexg), this%cl2(iexg), & - fawidth, this%satomega) - end if - ! - ! -- store csat in condsat - this%condsat(iexg) = csat - end do + ! -- Calculate the saturated conductance for all connections + if (.not. this%use_interface_model()) call this%calc_cond_sat() ! ! -- Observation AR call this%obs%obs_ar() @@ -767,10 +721,10 @@ subroutine gwf_gwf_cq(this, icnvg, isuppress_output, isolnid) ! -- calculate flow and store in simvals call this%gwf_gwf_calc_simvals() ! - ! -- set rates to model edges in npf for spdis calculation - call this%gwf_gwf_set_simvals_to_npf() + ! -- set flows to model edges in NPF + call this%gwf_gwf_set_flow_to_npf() ! - ! -- add exchange flow to model 1 and 2 flowja array diagonal position + ! -- add exchange flows to model's flowja diagonal call this%gwf_gwf_add_to_flowja() ! ! -- return @@ -818,24 +772,28 @@ subroutine gwf_gwf_add_to_flowja(this) do i = 1, this%nexg - flow = this%simvals(i) - n = this%nodem1(i) - idiag = this%gwfmodel1%ia(n) - this%gwfmodel1%flowja(idiag) = this%gwfmodel1%flowja(idiag) + flow + if (associated(this%gwfmodel1)) then + flow = this%simvals(i) + n = this%nodem1(i) + idiag = this%gwfmodel1%ia(n) + this%gwfmodel1%flowja(idiag) = this%gwfmodel1%flowja(idiag) + flow + end if - flow = -this%simvals(i) - n = this%nodem2(i) - idiag = this%gwfmodel2%ia(n) - this%gwfmodel2%flowja(idiag) = this%gwfmodel2%flowja(idiag) + flow + if (associated(this%gwfmodel2)) then + flow = -this%simvals(i) + n = this%nodem2(i) + idiag = this%gwfmodel2%ia(n) + this%gwfmodel2%flowja(idiag) = this%gwfmodel2%flowja(idiag) + flow + end if end do return end subroutine gwf_gwf_add_to_flowja - !> @brief Calculate specific discharge from flow rates - !< and set them to the models - subroutine gwf_gwf_set_simvals_to_npf(this) + !> @brief Set flow rates to the edges in the models + !< + subroutine gwf_gwf_set_flow_to_npf(this) use ConstantsModule, only: DZERO, DPIO180 use GwfNpfModule, only: thksatnm class(GwfExchangeType) :: this !< GwfExchangeType @@ -944,7 +902,7 @@ subroutine gwf_gwf_set_simvals_to_npf(this) end do ! return - end subroutine gwf_gwf_set_simvals_to_npf + end subroutine gwf_gwf_set_flow_to_npf !> @ brief Budget !! @@ -973,14 +931,18 @@ subroutine gwf_gwf_bd(this, icnvg, isuppress_output, isolnid) call rate_accumulator(this%simvals, ratin, ratout) ! ! -- Add the budget terms to model 1 - budterm(1, 1) = ratin - budterm(2, 1) = ratout - call this%gwfmodel1%model_bdentry(budterm, budtxt, this%name) + if (associated(this%gwfmodel1)) then + budterm(1, 1) = ratin + budterm(2, 1) = ratout + call this%gwfmodel1%model_bdentry(budterm, budtxt, this%name) + end if ! ! -- Add the budget terms to model 2 - budterm(1, 1) = ratout - budterm(2, 1) = ratin - call this%gwfmodel2%model_bdentry(budterm, budtxt, this%name) + if (associated(this%gwfmodel2)) then + budterm(1, 1) = ratout + budterm(2, 1) = ratin + call this%gwfmodel2%model_bdentry(budterm, budtxt, this%name) + end if ! ! -- Call mvr bd routine if (this%inmvr > 0) call this%mvr%mvr_bd() @@ -1002,10 +964,14 @@ subroutine gwf_gwf_bdsav(this) integer(I4B) :: icbcfl, ibudfl ! ! -- budget for model1 - call this%gwf_gwf_bdsav_model(this%gwfmodel1) + if (associated(this%gwfmodel1)) then + call this%gwf_gwf_bdsav_model(this%gwfmodel1) + end if ! ! -- budget for model2 - call this%gwf_gwf_bdsav_model(this%gwfmodel2) + if (associated(this%gwfmodel2)) then + call this%gwf_gwf_bdsav_model(this%gwfmodel2) + end if ! ! -- Set icbcfl, ibudfl to zero so that flows will be printed and ! saved, if the options were set in the MVR package @@ -1033,7 +999,7 @@ subroutine gwf_gwf_bdsav_model(this, model) character(len=LENPACKAGENAME + 4) :: packname character(len=LENBUDTXT), dimension(1) :: budtxt type(TableType), pointer :: output_tab - class(GwfModelType), pointer :: nbr_model + class(VirtualModelType), pointer :: nbr_model character(len=20) :: nodestr character(len=LENBOUNDNAME) :: bname integer(I4B) :: ntabrows @@ -1048,11 +1014,11 @@ subroutine gwf_gwf_bdsav_model(this, model) packname = adjustr(packname) if (associated(model, this%gwfmodel1)) then output_tab => this%outputtab1 - nbr_model => this%gwfmodel2 + nbr_model => this%v_model2 is_for_model1 = .true. else output_tab => this%outputtab2 - nbr_model => this%gwfmodel1 + nbr_model => this%v_model1 is_for_model1 = .false. end if ! @@ -1074,7 +1040,8 @@ subroutine gwf_gwf_bdsav_model(this, model) n2 = this%nodem2(i) ! ! -- If both cells are active then calculate flow rate - if (this%model1%ibound(n1) /= 0 .and. this%model2%ibound(n2) /= 0) then + if (this%v_model1%ibound%get(n1) /= 0 .and. & + this%v_model2%ibound%get(n2) /= 0) then ntabrows = ntabrows + 1 end if end do @@ -1128,8 +1095,8 @@ subroutine gwf_gwf_bdsav_model(this, model) n2 = this%nodem2(i) ! ! -- If both cells are active then calculate flow rate - if (this%model1%ibound(n1) /= 0 .and. & - this%model2%ibound(n2) /= 0) then + if (this%v_model1%ibound%get(n1) /= 0 .and. & + this%v_model2%ibound%get(n2) /= 0) then rrate = this%simvals(i) ! ! -- Print the individual rates to model list files if requested @@ -1159,8 +1126,8 @@ subroutine gwf_gwf_bdsav_model(this, model) end if ! ! -- If saving cell-by-cell flows in list, write flow - n1u = this%model1%dis%get_nodeuser(n1) - n2u = this%model2%dis%get_nodeuser(n2) + n1u = this%v_model1%dis_get_nodeuser(n1) + n2u = this%v_model2%dis_get_nodeuser(n2) if (ibinun /= 0) then if (is_for_model1) then call model%dis%record_mf6_list_entry(ibinun, n1u, n2u, rrate, & @@ -1223,19 +1190,20 @@ subroutine gwf_gwf_ot(this) n1 = this%nodem1(iexg) n2 = this%nodem2(iexg) flow = this%simvals(iexg) - call this%gwfmodel1%dis%noder_to_string(n1, node1str) - call this%gwfmodel2%dis%noder_to_string(n2, node2str) + call this%v_model1%dis_noder_to_string(n1, node1str) + call this%v_model2%dis_noder_to_string(n2, node2str) + if (this%ingnc > 0) then deltaqgnc = this%gnc%deltaqgnc(iexg) write (iout, fmtdata) trim(adjustl(node1str)), & trim(adjustl(node2str)), & - this%cond(iexg), this%gwfmodel1%x(n1), & - this%gwfmodel2%x(n2), deltaqgnc, flow + this%cond(iexg), this%v_model1%x%get(n1), & + this%v_model2%x%get(n2), deltaqgnc, flow else write (iout, fmtdata) trim(adjustl(node1str)), & trim(adjustl(node2str)), & - this%cond(iexg), this%gwfmodel1%x(n1), & - this%gwfmodel2%x(n2), flow + this%cond(iexg), this%v_model1%x%get(n1), & + this%v_model2%x%get(n2), flow end if end do end if @@ -1324,94 +1292,103 @@ function parse_option(this, keyword, iout) result(parsed) character(len=LINELENGTH) :: fname integer(I4B) :: inobs character(len=LINELENGTH) :: subkey + character(len=:), allocatable :: line parsed = .true. - select case (keyword) + sel_opt:select case(keyword) case ('PRINT_FLOWS') - this%iprflow = 1 - write (iout, '(4x,a)') & - 'EXCHANGE FLOWS WILL BE PRINTED TO LIST FILES.' + this%iprflow = 1 + write (iout, '(4x,a)') & + 'EXCHANGE FLOWS WILL BE PRINTED TO LIST FILES.' case ('SAVE_FLOWS') - this%ipakcb = -1 - write (iout, '(4x,a)') & - 'EXCHANGE FLOWS WILL BE SAVED TO BINARY BUDGET FILES.' + this%ipakcb = -1 + write (iout, '(4x,a)') & + 'EXCHANGE FLOWS WILL BE SAVED TO BINARY BUDGET FILES.' case ('ALTERNATIVE_CELL_AVERAGING') - call this%parser%GetStringCaps(subkey) - select case (subkey) - case ('LOGARITHMIC') - this%icellavg = 1 - case ('AMT-LMK') - this%icellavg = 2 - case default - errmsg = "Unknown cell averaging method '"//trim(subkey)//"'." - call store_error(errmsg) - call this%parser%StoreErrorUnit() - end select - write (iout, '(4x,a,a)') & - 'CELL AVERAGING METHOD HAS BEEN SET TO: ', trim(subkey) + call this%parser%GetStringCaps(subkey) + select case (subkey) + case ('LOGARITHMIC') + this%icellavg = 1 + case ('AMT-LMK') + this%icellavg = 2 + case default + errmsg = "Unknown cell averaging method '"//trim(subkey)//"'." + call store_error(errmsg) + call this%parser%StoreErrorUnit() + end select + write (iout, '(4x,a,a)') & + 'CELL AVERAGING METHOD HAS BEEN SET TO: ', trim(subkey) case ('VARIABLECV') - this%ivarcv = 1 + this%ivarcv = 1 + write (iout, '(4x,a)') & + 'VERTICAL CONDUCTANCE VARIES WITH WATER TABLE.' + call this%parser%GetStringCaps(subkey) + if (subkey == 'DEWATERED') then + this%idewatcv = 1 write (iout, '(4x,a)') & - 'VERTICAL CONDUCTANCE VARIES WITH WATER TABLE.' - call this%parser%GetStringCaps(subkey) - if (subkey == 'DEWATERED') then - this%idewatcv = 1 - write (iout, '(4x,a)') & - 'VERTICAL CONDUCTANCE ACCOUNTS FOR DEWATERED PORTION OF '// & - 'AN UNDERLYING CELL.' - end if + 'VERTICAL CONDUCTANCE ACCOUNTS FOR DEWATERED PORTION OF '// & + 'AN UNDERLYING CELL.' + end if case ('NEWTON') - this%inewton = 1 - write (iout, '(4x,a)') & - 'NEWTON-RAPHSON method used for unconfined cells' + this%inewton = 1 + write (iout, '(4x,a)') & + 'NEWTON-RAPHSON method used for unconfined cells' case ('GNC6') - call this%parser%GetStringCaps(subkey) - if (subkey /= 'FILEIN') then - call store_error('GNC6 KEYWORD MUST BE FOLLOWED BY '// & - '"FILEIN" then by filename.') - call this%parser%StoreErrorUnit() - end if - call this%parser%GetString(fname) - if (fname == '') then - call store_error('NO GNC6 FILE SPECIFIED.') - call this%parser%StoreErrorUnit() - end if - this%ingnc = getunit() - call openfile(this%ingnc, iout, fname, 'GNC') - write (iout, '(4x,a)') & - 'GHOST NODES WILL BE READ FROM ', trim(fname) + call this%parser%GetStringCaps(subkey) + if (subkey /= 'FILEIN') then + call store_error('GNC6 KEYWORD MUST BE FOLLOWED BY '// & + '"FILEIN" then by filename.') + call this%parser%StoreErrorUnit() + end if + call this%parser%GetString(fname) + if (fname == '') then + call store_error('NO GNC6 FILE SPECIFIED.') + call this%parser%StoreErrorUnit() + end if + this%ingnc = getunit() + call openfile(this%ingnc, iout, fname, 'GNC') + write (iout, '(4x,a)') & + 'GHOST NODES WILL BE READ FROM ', trim(fname) case ('MVR6') - call this%parser%GetStringCaps(subkey) - if (subkey /= 'FILEIN') then - call store_error('MVR6 KEYWORD MUST BE FOLLOWED BY '// & - '"FILEIN" then by filename.') - call this%parser%StoreErrorUnit() - end if - call this%parser%GetString(fname) - if (fname == '') then - call store_error('NO MVR6 FILE SPECIFIED.') - call this%parser%StoreErrorUnit() - end if - this%inmvr = getunit() - call openfile(this%inmvr, iout, fname, 'MVR') - write (iout, '(4x,a)') & - 'WATER MOVER INFORMATION WILL BE READ FROM ', trim(fname) + if (this%is_datacopy) then + call this%parser%GetRemainingLine(line) + exit sel_opt + end if + call this%parser%GetStringCaps(subkey) + if (subkey /= 'FILEIN') then + call store_error('MVR6 KEYWORD MUST BE FOLLOWED BY '// & + '"FILEIN" then by filename.') + call this%parser%StoreErrorUnit() + end if + call this%parser%GetString(fname) + if (fname == '') then + call store_error('NO MVR6 FILE SPECIFIED.') + call this%parser%StoreErrorUnit() + end if + this%inmvr = getunit() + call openfile(this%inmvr, iout, fname, 'MVR') + write (iout, '(4x,a)') & + 'WATER MOVER INFORMATION WILL BE READ FROM ', trim(fname) case ('OBS6') - call this%parser%GetStringCaps(subkey) - if (subkey /= 'FILEIN') then - call store_error('OBS8 KEYWORD MUST BE FOLLOWED BY '// & - '"FILEIN" then by filename.') - call this%parser%StoreErrorUnit() - end if - this%obs%active = .true. - call this%parser%GetString(this%obs%inputFilename) - inobs = GetUnit() - call openfile(inobs, iout, this%obs%inputFilename, 'OBS') - this%obs%inUnitObs = inobs + if (this%is_datacopy) then + call this%parser%GetRemainingLine(line) + exit sel_opt + end if + call this%parser%GetStringCaps(subkey) + if (subkey /= 'FILEIN') then + call store_error('OBS8 KEYWORD MUST BE FOLLOWED BY '// & + '"FILEIN" then by filename.') + call this%parser%StoreErrorUnit() + end if + this%obs%active = .true. + call this%parser%GetString(this%obs%inputFilename) + inobs = GetUnit() + call openfile(inobs, iout, this%obs%inputFilename, 'OBS') + this%obs%inUnitObs = inobs case default - parsed = .false. - end select + parsed = .false. + end select sel_opt end function parse_option @@ -1555,6 +1532,94 @@ subroutine rewet(this, kiter) return end subroutine rewet + subroutine calc_cond_sat(this) + ! -- modules + use ConstantsModule, only: LINELENGTH, DZERO, DHALF, DONE, DPIO180 + use GwfNpfModule, only: condmean, vcond, hcond + ! -- dummy + class(GwfExchangeType) :: this !< GwfExchangeType + ! -- local + integer(I4B) :: iexg + integer(I4B) :: n, m, ihc + real(DP) :: topn, topm + real(DP) :: botn, botm + real(DP) :: satn, satm + real(DP) :: thickn, thickm + real(DP) :: angle, hyn, hym + real(DP) :: csat + real(DP) :: fawidth + real(DP), dimension(3) :: vg + + do iexg = 1, this%nexg + ! + ihc = this%ihc(iexg) + n = this%nodem1(iexg) + m = this%nodem2(iexg) + topn = this%gwfmodel1%dis%top(n) + topm = this%gwfmodel2%dis%top(m) + botn = this%gwfmodel1%dis%bot(n) + botm = this%gwfmodel2%dis%bot(m) + satn = this%gwfmodel1%npf%sat(n) + satm = this%gwfmodel2%npf%sat(m) + thickn = (topn - botn) * satn + thickm = (topm - botm) * satm + ! + ! -- Calculate conductance depending on connection orientation + if (ihc == 0) then + ! + ! -- Vertical conductance for fully saturated conditions + vg(1) = DZERO + vg(2) = DZERO + vg(3) = DONE + hyn = this%gwfmodel1%npf%hy_eff(n, 0, ihc, vg=vg) + hym = this%gwfmodel2%npf%hy_eff(m, 0, ihc, vg=vg) + csat = vcond(1, 1, 1, 1, 0, 1, 1, DONE, & + botn, botm, & + hyn, hym, & + satn, satm, & + topn, topm, & + botn, botm, & + this%hwva(iexg)) + else + ! + ! -- Calculate horizontal conductance + hyn = this%gwfmodel1%npf%k11(n) + hym = this%gwfmodel2%npf%k11(m) + ! + ! -- Check for anisotropy in models, and recalculate hyn and hym + if (this%ianglex > 0) then + angle = this%auxvar(this%ianglex, iexg) * DPIO180 + vg(1) = abs(cos(angle)) + vg(2) = abs(sin(angle)) + vg(3) = DZERO + ! + ! -- anisotropy in model 1 + if (this%gwfmodel1%npf%ik22 /= 0) then + hyn = this%gwfmodel1%npf%hy_eff(n, 0, ihc, vg=vg) + end if + ! + ! -- anisotropy in model 2 + if (this%gwfmodel2%npf%ik22 /= 0) then + hym = this%gwfmodel2%npf%hy_eff(m, 0, ihc, vg=vg) + end if + end if + ! + fawidth = this%hwva(iexg) + csat = hcond(1, 1, 1, 1, this%inewton, 0, ihc, & + this%icellavg, 0, 0, DONE, & + topn, topm, satn, satm, hyn, hym, & + topn, topm, & + botn, botm, & + this%cl1(iexg), this%cl2(iexg), & + fawidth, this%satomega) + end if + ! + ! -- store csat in condsat + this%condsat(iexg) = csat + end do + + end subroutine calc_cond_sat + !> @ brief Calculate the conductance !! !! Calculate the conductance based on state @@ -1794,32 +1859,36 @@ subroutine allocate_arrays(this) ! ! -- initialize the output table objects ! outouttab1 - call table_cr(this%outputtab1, this%name, ' ') - call this%outputtab1%table_df(this%nexg, ntabcol, this%gwfmodel1%iout, & - transient=.TRUE.) - text = 'NUMBER' - call this%outputtab1%initialize_column(text, 10, alignment=TABCENTER) - text = 'CELLID' - call this%outputtab1%initialize_column(text, 20, alignment=TABLEFT) - text = 'RATE' - call this%outputtab1%initialize_column(text, 15, alignment=TABCENTER) - if (this%inamedbound > 0) then - text = 'NAME' + if (this%v_model1%is_local) then + call table_cr(this%outputtab1, this%name, ' ') + call this%outputtab1%table_df(this%nexg, ntabcol, this%gwfmodel1%iout, & + transient=.TRUE.) + text = 'NUMBER' + call this%outputtab1%initialize_column(text, 10, alignment=TABCENTER) + text = 'CELLID' call this%outputtab1%initialize_column(text, 20, alignment=TABLEFT) + text = 'RATE' + call this%outputtab1%initialize_column(text, 15, alignment=TABCENTER) + if (this%inamedbound > 0) then + text = 'NAME' + call this%outputtab1%initialize_column(text, 20, alignment=TABLEFT) + end if end if ! outouttab2 - call table_cr(this%outputtab2, this%name, ' ') - call this%outputtab2%table_df(this%nexg, ntabcol, this%gwfmodel2%iout, & - transient=.TRUE.) - text = 'NUMBER' - call this%outputtab2%initialize_column(text, 10, alignment=TABCENTER) - text = 'CELLID' - call this%outputtab2%initialize_column(text, 20, alignment=TABLEFT) - text = 'RATE' - call this%outputtab2%initialize_column(text, 15, alignment=TABCENTER) - if (this%inamedbound > 0) then - text = 'NAME' + if (this%v_model2%is_local) then + call table_cr(this%outputtab2, this%name, ' ') + call this%outputtab2%table_df(this%nexg, ntabcol, this%gwfmodel2%iout, & + transient=.TRUE.) + text = 'NUMBER' + call this%outputtab2%initialize_column(text, 10, alignment=TABCENTER) + text = 'CELLID' call this%outputtab2%initialize_column(text, 20, alignment=TABLEFT) + text = 'RATE' + call this%outputtab2%initialize_column(text, 15, alignment=TABCENTER) + if (this%inamedbound > 0) then + text = 'NAME' + call this%outputtab2%initialize_column(text, 20, alignment=TABLEFT) + end if end if end if ! @@ -2006,11 +2075,12 @@ end function gwf_gwf_connects_model !> @brief Should interface model be used for this exchange !< - function use_interface_model(this) result(useIM) + function use_interface_model(this) result(use_im) class(GwfExchangeType) :: this !< GwfExchangeType - logical(LGP) :: useIM !< true when interface model should be used + logical(LGP) :: use_im !< true when interface model should be used - useIM = (this%ixt3d > 0) + use_im = this%DisConnExchangeType%use_interface_model() + use_im = use_im .or. (this%ixt3d > 0) end function diff --git a/src/Exchange/GwfGwtExchange.f90 b/src/Exchange/GwfGwtExchange.f90 index 44e040b5986..0b8f60a458e 100644 --- a/src/Exchange/GwfGwtExchange.f90 +++ b/src/Exchange/GwfGwtExchange.f90 @@ -7,7 +7,7 @@ module GwfGwtExchangeModule use SimVariablesModule, only: errmsg use BaseExchangeModule, only: BaseExchangeType, AddBaseExchangeToList use SpatialModelConnectionModule, only: SpatialModelConnectionType, & - GetSpatialModelConnectionFromList + get_smc_from_list use GwtGwtConnectionModule, only: GwtGwtConnectionType, CastAsGwtGwtConnection use GwfGwfConnectionModule, only: GwfGwfConnectionType, CastAsGwfGwfConnection use GwfGwfExchangeModule, only: GwfExchangeType, & @@ -23,8 +23,8 @@ module GwfGwtExchangeModule type, extends(BaseExchangeType) :: GwfGwtExchangeType - integer(I4B), pointer :: m1id => null() - integer(I4B), pointer :: m2id => null() + integer(I4B), pointer :: m1_idx => null() !< index into the list of base exchanges for model 1 + integer(I4B), pointer :: m2_idx => null() !< index into the list of base exchanges for model 2 contains @@ -41,7 +41,7 @@ module GwfGwtExchangeModule contains - subroutine gwfgwt_cr(filename, id, m1id, m2id) + subroutine gwfgwt_cr(filename, id, m1_id, m2_id) ! ****************************************************************************** ! gwfgwt_cr -- Create a new GWF to GWT exchange object ! ****************************************************************************** @@ -49,11 +49,12 @@ subroutine gwfgwt_cr(filename, id, m1id, m2id) ! SPECIFICATIONS: ! ------------------------------------------------------------------------------ ! -- modules + use SimVariablesModule, only: model_loc_idx ! -- dummy character(len=*), intent(in) :: filename integer(I4B), intent(in) :: id - integer(I4B), intent(in) :: m1id - integer(I4B), intent(in) :: m2id + integer(I4B), intent(in) :: m1_id + integer(I4B), intent(in) :: m2_id ! -- local class(BaseExchangeType), pointer :: baseexchange => null() type(GwfGwtExchangeType), pointer :: exchange => null() @@ -73,8 +74,10 @@ subroutine gwfgwt_cr(filename, id, m1id, m2id) ! ! -- allocate scalars call exchange%allocate_scalars() - exchange%m1id = m1id - exchange%m2id = m2id + ! + ! -- NB: convert from id to local model index in base model list + exchange%m1_idx = model_loc_idx(m1_id) + exchange%m2_idx = model_loc_idx(m2_id) ! ! -- set model pointers call exchange%set_model_pointers() @@ -101,7 +104,7 @@ subroutine set_model_pointers(this) ! ! -- set gwfmodel gwfmodel => null() - mb => GetBaseModelFromList(basemodellist, this%m1id) + mb => GetBaseModelFromList(basemodellist, this%m1_idx) select type (mb) type is (GwfModelType) gwfmodel => mb @@ -109,7 +112,7 @@ subroutine set_model_pointers(this) ! ! -- set gwtmodel gwtmodel => null() - mb => GetBaseModelFromList(basemodellist, this%m2id) + mb => GetBaseModelFromList(basemodellist, this%m2_idx) select type (mb) type is (GwtModelType) gwtmodel => mb @@ -159,14 +162,14 @@ subroutine exg_df(this) ! ! ! -- set gwfmodel - mb => GetBaseModelFromList(basemodellist, this%m1id) + mb => GetBaseModelFromList(basemodellist, this%m1_idx) select type (mb) type is (GwfModelType) gwfmodel => mb end select ! ! -- set gwtmodel - mb => GetBaseModelFromList(basemodellist, this%m2id) + mb => GetBaseModelFromList(basemodellist, this%m2_idx) select type (mb) type is (GwtModelType) gwtmodel => mb @@ -224,14 +227,14 @@ subroutine exg_ar(this) ! ------------------------------------------------------------------------------ ! ! -- set gwfmodel - mb => GetBaseModelFromList(basemodellist, this%m1id) + mb => GetBaseModelFromList(basemodellist, this%m1_idx) select type (mb) type is (GwfModelType) gwfmodel => mb end select ! ! -- set gwtmodel - mb => GetBaseModelFromList(basemodellist, this%m2id) + mb => GetBaseModelFromList(basemodellist, this%m2_idx) select type (mb) type is (GwtModelType) gwtmodel => mb @@ -324,7 +327,7 @@ subroutine gwfconn2gwtconn(this, gwfModel, gwtModel) ! loop over all connections gwtloop: do ic1 = 1, baseconnectionlist%Count() - conn => GetSpatialModelConnectionFromList(baseconnectionlist, ic1) + conn => get_smc_from_list(baseconnectionlist, ic1) if (.not. associated(conn%owner, gwtModel)) cycle gwtloop ! start with a GWT conn. @@ -335,7 +338,7 @@ subroutine gwfconn2gwtconn(this, gwfModel, gwtModel) ! find matching GWF conn. in same list gwfloop: do ic2 = 1, baseconnectionlist%Count() - conn => GetSpatialModelConnectionFromList(baseconnectionlist, ic2) + conn => get_smc_from_list(baseconnectionlist, ic2) if (associated(conn%owner, gwfModel)) then objPtr => conn @@ -343,15 +346,15 @@ subroutine gwfconn2gwtconn(this, gwfModel, gwtModel) ! for now, connecting the same nodes nrs will be ! sufficient evidence of equality - areEqual = all(gwfConn%primaryExchange%nodem1 == & - gwtConn%primaryExchange%nodem1) - areEqual = areEqual .and. all(gwfConn%primaryExchange%nodem2 == & - gwtConn%primaryExchange%nodem2) + areEqual = all(gwfConn%prim_exchange%nodem1 == & + gwtConn%prim_exchange%nodem1) + areEqual = areEqual .and. all(gwfConn%prim_exchange%nodem2 == & + gwtConn%prim_exchange%nodem2) if (areEqual) then ! same DIS, same exchange: link and go to next GWT conn. write (iout, '(/6a)') 'Linking exchange ', & - trim(gwtConn%primaryExchange%name), & - ' to ', trim(gwfConn%primaryExchange%name), & + trim(gwtConn%prim_exchange%name), & + ' to ', trim(gwfConn%prim_exchange%name), & ' (using interface model) for GWT model ', & trim(gwtModel%name) gwfConnIdx = ic2 @@ -375,13 +378,13 @@ subroutine gwfconn2gwtconn(this, gwfModel, gwtModel) associated(gwfEx%model2, gwfModel)) then ! again, connecting the same nodes nrs will be ! sufficient evidence of equality - areEqual = all(gwfEx%nodem1 == gwtConn%primaryExchange%nodem1) + areEqual = all(gwfEx%nodem1 == gwtConn%prim_exchange%nodem1) areEqual = areEqual .and. & - all(gwfEx%nodem2 == gwtConn%primaryExchange%nodem2) + all(gwfEx%nodem2 == gwtConn%prim_exchange%nodem2) if (areEqual) then ! link exchange to connection write (iout, '(/6a)') 'Linking exchange ', & - trim(gwtConn%primaryExchange%name), & + trim(gwtConn%prim_exchange%name), & ' to ', trim(gwfEx%name), ' for GWT model ', & trim(gwtModel%name) gwfExIdx = iex @@ -415,7 +418,7 @@ subroutine gwfconn2gwtconn(this, gwfModel, gwtModel) ! none found, report write (errmsg, '(/6a)') 'Missing GWF-GWF exchange when connecting GWT'// & ' model ', trim(gwtModel%name), ' with exchange ', & - trim(gwtConn%primaryExchange%name), ' to GWF model ', & + trim(gwtConn%prim_exchange%name), ' to GWF model ', & trim(gwfModel%name) call store_error(errmsg, terminate=.true.) end if @@ -478,8 +481,8 @@ subroutine exg_da(this) ! -- local ! ------------------------------------------------------------------------------ ! - call mem_deallocate(this%m1id) - call mem_deallocate(this%m2id) + call mem_deallocate(this%m1_idx) + call mem_deallocate(this%m2_idx) ! ! -- return return @@ -499,10 +502,10 @@ subroutine allocate_scalars(this) ! -- local ! ------------------------------------------------------------------------------ ! - call mem_allocate(this%m1id, 'M1ID', this%memoryPath) - call mem_allocate(this%m2id, 'M2ID', this%memoryPath) - this%m1id = 0 - this%m2id = 0 + call mem_allocate(this%m1_idx, 'M1ID', this%memoryPath) + call mem_allocate(this%m2_idx, 'M2ID', this%memoryPath) + this%m1_idx = 0 + this%m2_idx = 0 ! ! -- return return @@ -527,14 +530,14 @@ subroutine gwfbnd2gwtfmi(this) ! ------------------------------------------------------------------------------ ! ! -- set gwfmodel - mb => GetBaseModelFromList(basemodellist, this%m1id) + mb => GetBaseModelFromList(basemodellist, this%m1_idx) select type (mb) type is (GwfModelType) gwfmodel => mb end select ! ! -- set gwtmodel - mb => GetBaseModelFromList(basemodellist, this%m2id) + mb => GetBaseModelFromList(basemodellist, this%m2_idx) select type (mb) type is (GwtModelType) gwtmodel => mb diff --git a/src/Exchange/GwtGwtExchange.f90 b/src/Exchange/GwtGwtExchange.f90 index c31a6d8a1e4..f3e52f6d560 100644 --- a/src/Exchange/GwtGwtExchange.f90 +++ b/src/Exchange/GwtGwtExchange.f90 @@ -10,7 +10,7 @@ module GwtGwtExchangeModule use KindModule, only: DP, I4B, LGP - use SimVariablesModule, only: errmsg + use SimVariablesModule, only: errmsg, model_loc_idx use SimModule, only: store_error use BaseModelModule, only: BaseModelType, GetBaseModelFromList use BaseExchangeModule, only: BaseExchangeType, AddBaseExchangeToList @@ -18,10 +18,10 @@ module GwtGwtExchangeModule TABCENTER, TABLEFT, LENAUXNAME, DNODATA, & LENMODELNAME use ListModule, only: ListType - use ListsModule, only: basemodellist, distmodellist + use ListsModule, only: basemodellist + use VirtualModelModule, only: get_virtual_model use DisConnExchangeModule, only: DisConnExchangeType use GwtModule, only: GwtModelType - use DistributedModelModule, only: GetDistModelFromList use GwtMvtModule, only: GwtMvtType use ObserveModule, only: ObserveType use ObsModule, only: ObsType @@ -30,7 +30,7 @@ module GwtGwtExchangeModule use SimVariablesModule, only: errmsg use BlockParserModule, only: BlockParserType use TableModule, only: TableType, table_cr - use MatrixModule + use MatrixBaseModule implicit none @@ -112,7 +112,7 @@ module GwtGwtExchangeModule !! Create a new GWT to GWT exchange object. !! !< - subroutine gwtexchange_create(filename, id, m1id, m2id) + subroutine gwtexchange_create(filename, name, id, m1_id, m2_id) ! -- modules use ConstantsModule, only: LINELENGTH use BaseModelModule, only: BaseModelType @@ -122,13 +122,14 @@ subroutine gwtexchange_create(filename, id, m1id, m2id) ! -- dummy character(len=*), intent(in) :: filename !< filename for reading integer(I4B), intent(in) :: id !< id for the exchange - integer(I4B), intent(in) :: m1id !< id for model 1 - integer(I4B), intent(in) :: m2id !< id for model 2 + character(len=*) :: name !< the exchange name + integer(I4B), intent(in) :: m1_id !< id for model 1 + integer(I4B), intent(in) :: m2_id !< id for model 2 ! -- local type(GwtExchangeType), pointer :: exchange class(BaseModelType), pointer :: mb class(BaseExchangeType), pointer :: baseexchange - character(len=20) :: cint + integer(I4B) :: m1_index, m2_index ! ! -- Create a new exchange and add it to the baseexchangelist container allocate (exchange) @@ -137,8 +138,7 @@ subroutine gwtexchange_create(filename, id, m1id, m2id) ! ! -- Assign id and name exchange%id = id - write (cint, '(i0)') id - exchange%name = 'GWT-GWT_'//trim(adjustl(cint)) + exchange%name = name exchange%memoryPath = create_mem_path(exchange%name) ! ! -- allocate scalars and set defaults @@ -149,25 +149,27 @@ subroutine gwtexchange_create(filename, id, m1id, m2id) exchange%ixt3d = 1 ! ! -- set gwtmodel1 - mb => GetBaseModelFromList(basemodellist, m1id) + m1_index = model_loc_idx(m1_id) + mb => GetBaseModelFromList(basemodellist, m1_index) select type (mb) type is (GwtModelType) exchange%model1 => mb exchange%gwtmodel1 => mb end select - exchange%dmodel1 => GetDistModelFromList(distmodellist, m1id) + exchange%v_model1 => get_virtual_model(m1_id) ! ! -- set gwtmodel2 - mb => GetBaseModelFromList(basemodellist, m2id) + m2_index = model_loc_idx(m2_id) + mb => GetBaseModelFromList(basemodellist, m2_index) select type (mb) type is (GwtModelType) exchange%model2 => mb exchange%gwtmodel2 => mb end select - exchange%dmodel2 => GetDistModelFromList(distmodellist, m2id) + exchange%v_model2 => get_virtual_model(m2_id) ! ! -- Verify that gwt model1 is of the correct type - if (.not. associated(exchange%gwtmodel1)) then + if (.not. associated(exchange%gwtmodel1) .and. m1_index > 0) then write (errmsg, '(3a)') 'Problem with GWT-GWT exchange ', & trim(exchange%name), & '. First specified GWT Model does not appear to be of the correct type.' @@ -175,7 +177,7 @@ subroutine gwtexchange_create(filename, id, m1id, m2id) end if ! ! -- Verify that gwf model2 is of the correct type - if (.not. associated(exchange%gwtmodel2)) then + if (.not. associated(exchange%gwtmodel2) .and. m2_index > 0) then write (errmsg, '(3a)') 'Problem with GWT-GWT exchange ', & trim(exchange%name), & '. Second specified GWT Model does not appear to be of the correct type.' @@ -1244,17 +1246,13 @@ end function gwt_gwt_connects_model !! model flux calculation, then logic should be added here to !! set the return accordingly. !< - function use_interface_model(this) result(useIM) + function use_interface_model(this) result(use_im) class(GwtExchangeType) :: this !< GwtExchangeType - logical(LGP) :: useIM !< true when interface model should be used - - ! if support is added in the future for simpler flow calcuation, - ! then set useIM as follows - !useIM = (this%ixt3d > 0) + logical(LGP) :: use_im !< true when interface model should be used - ! For now set useIM to .true. since the interface model approach + ! For now set use_im to .true. since the interface model approach ! must currently be used for any GWT-GWT exchange. - useIM = .true. + use_im = .true. end function diff --git a/src/Exchange/NumericalExchange.f90 b/src/Exchange/NumericalExchange.f90 index a4efe010307..0d40eac262d 100644 --- a/src/Exchange/NumericalExchange.f90 +++ b/src/Exchange/NumericalExchange.f90 @@ -5,7 +5,7 @@ module NumericalExchangeModule use BaseExchangeModule, only: BaseExchangeType, AddBaseExchangeToList use NumericalModelModule, only: NumericalModelType use ListModule, only: ListType - use MatrixModule + use MatrixBaseModule implicit none diff --git a/src/Model/Connection/CellWithNbrs.f90 b/src/Model/Connection/CellWithNbrs.f90 index 43c28cf4afd..6742e6aeb87 100644 --- a/src/Model/Connection/CellWithNbrs.f90 +++ b/src/Model/Connection/CellWithNbrs.f90 @@ -1,7 +1,7 @@ module CellWithNbrsModule use KindModule, only: I4B, LGP use NumericalModelModule, only: NumericalModelType - use DistributedModelModule, only: DistributedModelType + use VirtualModelModule, only: VirtualModelType implicit none private @@ -12,7 +12,7 @@ module CellWithNbrsModule !< index type, public :: GlobalCellType integer(I4B) :: index !< the index on the model grid - class(DistributedModelType), pointer :: dmodel => null() !< distributed model + class(VirtualModelType), pointer :: v_model => null() !< virtual model end type ! a global cell with neighbors @@ -27,10 +27,10 @@ module CellWithNbrsModule contains - subroutine addNbrCell(this, index, dist_model) + subroutine addNbrCell(this, index, v_model) class(CellWithNbrsType) :: this integer(I4B) :: index - class(DistributedModelType), pointer :: dist_model ! TODO_MJR: this will replace the model pointer entirely + class(VirtualModelType), pointer :: v_model ! local integer(I4B) :: nbrCnt, currentSize, i type(CellWithNbrsType), dimension(:), pointer, contiguous :: newNeighbors @@ -59,7 +59,7 @@ subroutine addNbrCell(this, index, dist_model) end if this%neighbors(nbrCnt + 1)%cell%index = index - this%neighbors(nbrCnt + 1)%cell%dmodel => dist_model + this%neighbors(nbrCnt + 1)%cell%v_model => v_model this%nrOfNbrs = nbrCnt + 1 diff --git a/src/Model/Connection/ConnectionBuilder.f90 b/src/Model/Connection/ConnectionBuilder.f90 index df0988b51a8..4cf244cc59b 100644 --- a/src/Model/Connection/ConnectionBuilder.f90 +++ b/src/Model/Connection/ConnectionBuilder.f90 @@ -10,9 +10,9 @@ module ConnectionBuilderModule GetDisConnExchangeFromList use NumericalModelModule, only: NumericalModelType use SpatialModelConnectionModule, only: SpatialModelConnectionType, & - CastAsSpatialModelConnectionClass, & - GetSpatialModelConnectionFromList, & - AddSpatialModelConnectionToList + cast_as_smc, & + get_smc_from_list, & + add_smc_to_list implicit none private @@ -22,7 +22,7 @@ module ConnectionBuilderModule procedure, pass(this) :: processSolution procedure, private, pass(this) :: processExchanges procedure, private, pass(this) :: setConnectionsToSolution - procedure, private, pass(this) :: assignExchangesToConnections + procedure, private, pass(this) :: createModelConnectivity end type ConnectionBuilderType contains @@ -59,9 +59,8 @@ subroutine processSolution(this, solution) write (iout, '(1x,a,i0,a,a)') 'Created ', newConnections%Count(), & ' model connections for solution ', trim(solution%name) - ! set the global exchanges from this solution to - ! the model connections - call this%assignExchangesToConnections(numSol%exchangelist, newConnections) + ! craete the topology of models participating in the interfaces + call this%createModelConnectivity(newConnections) ! replace numerical exchanges in solution with connections call this%setConnectionsToSolution(newConnections, numSol) @@ -117,22 +116,26 @@ subroutine processExchanges(this, exchanges, newConnections) .or. dev_always_ifmod) then ! we should not get period connections here - isPeriodic = associated(conEx%model1, conEx%model2) + isPeriodic = (conEx%v_model1 == conEx%v_model2) if (isPeriodic) then write (*, *) 'Error (which should never happen): interface model '// & 'does not support periodic boundary condition' call ustop() end if - ! create new model connection for model 1 - modelConnection => createModelConnection(conEx%model1, conEx) - call AddSpatialModelConnectionToList(baseconnectionlist, modelConnection) - call AddSpatialModelConnectionToList(newConnections, modelConnection) + if (conEx%v_model1%is_local) then + ! create model connection for model 1 + modelConnection => createModelConnection(conEx%model1, conEx) + call add_smc_to_list(baseconnectionlist, modelConnection) + call add_smc_to_list(newConnections, modelConnection) + end if - ! and for model 2, unless periodic - modelConnection => createModelConnection(conEx%model2, conEx) - call AddSpatialModelConnectionToList(baseconnectionlist, modelConnection) - call AddSpatialModelConnectionToList(newConnections, modelConnection) + ! and for model 2 + if (conEx%v_model2%is_local) then + modelConnection => createModelConnection(conEx%model2, conEx) + call add_smc_to_list(baseconnectionlist, modelConnection) + call add_smc_to_list(newConnections, modelConnection) + end if ! remove this exchange from the base list, ownership ! now lies with the connection @@ -212,8 +215,8 @@ subroutine setConnectionsToSolution(this, connections, solution) ! will this exchange be replaced by a connection? keepExchange = .true. do iconn = 1, connections%Count() - conn => GetSpatialModelConnectionFromList(connections, iconn) - exPtr2 => conn%primaryExchange + conn => get_smc_from_list(connections, iconn) + exPtr2 => conn%prim_exchange if (associated(exPtr2, exPtr)) then ! if so, don't add it to the list keepExchange = .false. @@ -244,48 +247,25 @@ subroutine setConnectionsToSolution(this, connections, solution) end subroutine setConnectionsToSolution - !> @brief Add global exchanges from a certain numerical solution - !! to the connections. + !> @brief Create connectivity of models which contribute to the interface !! - !! This concerns all exchanges of the proper type. Inside the - !! connection it will be used to extend the interface grid with - !! the possibility to include cells from models which are indirectly - !! connected, through yet another exchange object. - !< - subroutine assignExchangesToConnections(this, exchanges, connections) + !! This loops over all connections and creates a halo with all + !! models from the numerical solution. The model halo will be used to + !! extend the interface grid to include cells from models which are + !< indirectly connected, through yet another exchange object. + subroutine createModelConnectivity(this, connections) class(ConnectionBuilderType) :: this !< the connection builder object - type(ListType), pointer, intent(in) :: exchanges !< all exchanges in a solution type(ListType), intent(inout) :: connections !< all connections that are created for this solution ! local - integer(I4B) :: iex, iconn - class(DisConnExchangeType), pointer :: conEx + integer(I4B) :: iconn class(SpatialModelConnectionType), pointer :: modelConn - class(*), pointer :: exPtr - type(ListType) :: keepList - - ! first filter on exchanges of proper type - do iex = 1, exchanges%Count() - conEx => GetDisConnExchangeFromList(exchanges, iex) - if (.not. associated(conEx)) then - ! if it is not DisConnExchangeType, we should skip it - continue - end if - exPtr => conEx - call keepList%Add(exPtr) - end do - ! now add them to the model connections + ! create halo for the model connections do iconn = 1, connections%Count() - modelConn => GetSpatialModelConnectionFromList(connections, iconn) - do iex = 1, keepList%Count() - exPtr => keepList%GetItem(iex) - call modelConn%globalExchanges%Add(exPtr) - end do + modelConn => get_smc_from_list(connections, iconn) + call modelConn%createModelHalo() end do - ! clean - call keepList%Clear(destroy=.false.) - - end subroutine assignExchangesToConnections + end subroutine createModelConnectivity end module ConnectionBuilderModule diff --git a/src/Model/Connection/DistributedData.f90 b/src/Model/Connection/DistributedData.f90 deleted file mode 100644 index 2426b722908..00000000000 --- a/src/Model/Connection/DistributedData.f90 +++ /dev/null @@ -1,318 +0,0 @@ -module DistributedDataModule - use ConstantsModule, only: LENMEMPATH, LENCOMPONENTNAME, LENVARNAME - use KindModule, only: I4B, LGP - use MemoryTypeModule, only: MemoryType - use MemoryHelperModule, only: create_mem_path - use MemoryListModule, only: MemoryListType - use ListModule, only: ListType - use MappedVariableModule, only: MappedVariableType, CastAsMappedVariable - use InterfaceMapModule - - implicit none - private - - ! stages for synchronization - integer(I4B), public, parameter :: BEFORE_AR = 1 - integer(I4B), public, parameter :: AFTER_AR = 2 - integer(I4B), public, parameter :: BEFORE_AD = 3 - integer(I4B), public, parameter :: BEFORE_CF = 4 - integer(I4B), public, parameter :: BEFORE_FC = 5 - - ! types of variables - integer(I4B), public, parameter :: SYNC_SCALAR = 0 - integer(I4B), public, parameter :: SYNC_NODES = 1 - integer(I4B), public, parameter :: SYNC_CONNECTIONS = 2 - integer(I4B), public, parameter :: SYNC_EXCHANGES = 3 - - type, public :: DistVarType - character(len=LENVARNAME) :: var_name !< name of variable, e.g. "K11" - character(len=LENCOMPONENTNAME) :: subcomp_name !< subcomponent, e.g. "NPF" - character(len=LENCOMPONENTNAME) :: comp_name !< component, e.g. the model or exchange name - integer(I4B) :: map_type !< can be 0 = scalar, 1 = node based, 2 = connection based, - !! 3 = exchange based (connections crossing model boundaries) - character(len=LENVARNAME) :: exg_var_name !< needed for exchange variables, e.g. SIMVALS - integer(I4B), dimension(:), allocatable :: sync_stages !< when to sync, e.g. (/ STAGE_AD, STAGE_CF /) - !! which is before AD and CF - end type DistVarType - - type, public :: DistributedDataType - type(MemoryListType) :: remote_memory_list !< all remote data as a list of MemoryType - type(ListType) :: variable_list !< all distributed variables, NB: not necessarily 1-to-1 - !!with list of data items - contains - procedure :: map_variables - procedure :: get_dist_data - procedure :: synchronize - procedure :: destroy - - procedure, private :: map_model_data - procedure, private :: map_exg_data - procedure, private :: map_data - procedure, private :: print_variables - end type DistributedDataType - - ! HACK: global access - type(DistributedDataType), public :: distributed_data - -contains - - subroutine map_variables(this, sol_id, dist_vars, interface_map) - class(DistributedDataType) :: this - integer(I4B) :: sol_id - type(ListType) :: dist_vars - type(InterfaceMapType), pointer :: interface_map - ! local - integer(I4B) :: i, m, e - type(DistVarType), pointer :: distvar - type(IndexMapType), pointer :: idx_map - - ! loop over variables - do i = 1, dist_vars%Count() - distvar => GetDistVarFromList(dist_vars, i) - if (distvar%map_type == SYNC_NODES .or. & - distvar%map_type == SYNC_CONNECTIONS) then - ! map data for all models in this interface - do m = 1, interface_map%nr_models - - ! pick the right index map: connection based or node based - if (distvar%map_type == SYNC_NODES) then - idx_map => interface_map%node_map(m) - else if (distvar%map_type == SYNC_CONNECTIONS) then - idx_map => interface_map%connection_map(m) - end if - - ! and map ... - call distributed_data%map_model_data(sol_id, & - distvar%comp_name, & - distvar%subcomp_name, & - distvar%var_name, & - interface_map%model_names(m), & - idx_map, & - distvar%sync_stages) - end do - else if (distvar%map_type == SYNC_EXCHANGES) then - ! map data from the exchanges to the interface - do e = 1, interface_map%nr_exchanges - call distributed_data%map_exg_data(sol_id, & - distvar%comp_name, & - distvar%subcomp_name, & - distvar%var_name, & - interface_map%exchange_names(e), & - distvar%exg_var_name, & - interface_map%exchange_map(e), & - distvar%sync_stages) - end do - end if - end do - - end subroutine map_variables - - !> @brief Map data from model memory to a target memory entry, - !! with the specified map. The source and target items have - !< the same name and (optionally) subcomponent name. - subroutine map_model_data(this, controller_id, tgt_model_name, & - tgt_subcomp_name, tgt_var_name, src_model_name, & - index_map, stages) - use SimModule, only: ustop - use MemoryManagerModule, only: get_from_memorylist - class(DistributedDataType) :: this - integer(I4B) :: controller_id !< e.g. the numerical solution where synchr. is controlled - character(len=*), intent(in) :: tgt_model_name - character(len=*), intent(in) :: tgt_subcomp_name - character(len=*), intent(in) :: tgt_var_name - character(len=*), intent(in) :: src_model_name - type(IndexMapType), intent(in) :: index_map - integer(I4B), dimension(:), intent(in) :: stages !< array with 1 or multiple stages for synchronization - ! local - character(len=LENVARNAME) :: src_var_name - character(len=LENMEMPATH) :: src_mem_path, tgt_mem_path - - if (len_trim(tgt_subcomp_name) > 0) then - src_mem_path = create_mem_path(src_model_name, tgt_subcomp_name) - tgt_mem_path = create_mem_path(tgt_model_name, tgt_subcomp_name) - else - src_mem_path = create_mem_path(src_model_name) - tgt_mem_path = create_mem_path(tgt_model_name) - end if - - src_var_name = tgt_var_name - call this%map_data(controller_id, & - tgt_var_name, tgt_mem_path, index_map%tgt_idx, & - src_var_name, src_mem_path, index_map%src_idx, & - null(), stages) - - end subroutine map_model_data - - !> @brief Map memory from a Exchange to the specified memory entry, - !< using the index map - subroutine map_exg_data(this, controller_id, tgt_model_name, & - tgt_subcomp_name, tgt_var_name, src_exg_name, & - src_var_name, index_map_sgn, stages) - use SimModule, only: ustop - use MemoryManagerModule, only: get_from_memorylist - class(DistributedDataType) :: this - integer(I4B) :: controller_id !< e.g. the numerical solution where synchr. is controlled - character(len=*), intent(in) :: tgt_model_name - character(len=*), intent(in) :: tgt_subcomp_name - character(len=*), intent(in) :: tgt_var_name - character(len=*), intent(in) :: src_exg_name - character(len=*), intent(in) :: src_var_name - type(IndexMapSgnType), intent(in) :: index_map_sgn - integer(I4B), dimension(:), intent(in) :: stages !< array with 1 or multiple stages for synchronization - ! local - character(len=LENMEMPATH) :: src_mem_path, tgt_mem_path - - src_mem_path = create_mem_path(src_exg_name) - if (len_trim(tgt_subcomp_name) > 0) then - tgt_mem_path = create_mem_path(tgt_model_name, tgt_subcomp_name) - else - tgt_mem_path = create_mem_path(tgt_model_name) - end if - - call this%map_data(controller_id, & - tgt_var_name, tgt_mem_path, index_map_sgn%tgt_idx, & - src_var_name, src_mem_path, index_map_sgn%src_idx, & - index_map_sgn%sign, stages) - - end subroutine map_exg_data - - !> @brief Generic mapping between two variables in memory, using - !! an optional sign conversion - !< - subroutine map_data(this, controller_id, tgt_name, tgt_path, tgt_idx, & - src_name, src_path, src_idx, sign_array, stages) - class(DistributedDataType) :: this - integer(I4B) :: controller_id - character(len=*), intent(in) :: tgt_name - character(len=*), intent(in) :: tgt_path - integer(I4B), dimension(:), pointer :: tgt_idx - character(len=*), intent(in) :: src_name - character(len=*), intent(in) :: src_path - integer(I4B), dimension(:), pointer :: src_idx - integer(I4B), dimension(:), pointer :: sign_array - integer(I4B), dimension(:), intent(in) :: stages - ! local - integer(I4B) :: istage, i - type(MappedVariableType), pointer :: mapped_var - class(*), pointer :: obj - - ! loop and set stage bits - istage = 0 - do i = 1, size(stages) - istage = ibset(istage, stages(i)) - end do - - ! create MappedVariable and add to list - allocate (mapped_var) - mapped_var%controller_id = controller_id - mapped_var%sync_stage = istage - mapped_var%src_name = src_name - mapped_var%src_path = src_path - mapped_var%src => null() - mapped_var%tgt_name = tgt_name - mapped_var%tgt_path = tgt_path - mapped_var%tgt => null() - mapped_var%src_idx => src_idx - mapped_var%tgt_idx => tgt_idx - mapped_var%sign => sign_array - obj => mapped_var - call this%variable_list%Add(obj) - - end subroutine map_data - - function get_dist_data(this) result(dist_data) - class(DistributedDataType) :: this - type(MemoryType), pointer :: dist_data - - ! get from memory list - - end function get_dist_data - - subroutine synchronize(this, controller_id, stage) - class(DistributedDataType) :: this - integer(I4B) :: controller_id - integer(I4B), intent(in) :: stage - ! local - integer(I4B) :: i - class(*), pointer :: obj - class(MappedVariableType), pointer :: var - - ! sync all variables (src => tgt) for a given stage - do i = 1, this%variable_list%Count() - obj => this%variable_list%GetItem(i) - var => CastAsMappedVariable(obj) - if (controller_id > 0 .and. var%controller_id /= controller_id) cycle - if (.not. check_stage(var%sync_stage, stage)) cycle - - ! copy data - call var%sync() - end do - - end subroutine synchronize - - function check_stage(var_stage, current_stage) result(is_sync) - integer(I4B) :: var_stage - integer(I4B) :: current_stage - logical(LGP) :: is_sync - - is_sync = iand(var_stage, ibset(0, current_stage)) == ibset(0, current_stage) - - end function check_stage - - subroutine destroy(this) - class(DistributedDataType) :: this - - !call this%print_variables() - - call this%variable_list%Clear(destroy=.true.) - call this%remote_memory_list%clear() - - end subroutine destroy - - function GetDistVarFromList(list, idx) result(res) - implicit none - type(ListType), intent(inout) :: list - integer(I4B), intent(in) :: idx - class(DistVarType), pointer :: res - ! local - class(*), pointer :: obj - - obj => list%GetItem(idx) - res => CastAsDistVar(obj) - return - - end function GetDistVarFromList - - function CastAsDistVar(obj) result(res) - implicit none - class(*), pointer, intent(inout) :: obj - class(DistVarType), pointer :: res - - res => null() - if (.not. associated(obj)) return - - select type (obj) - class is (DistVarType) - res => obj - end select - return - end function CastAsDistVar - - subroutine print_variables(this) - class(DistributedDataType) :: this - ! local - integer(I4B) :: i - class(*), pointer :: obj - class(MappedVariableType), pointer :: var - - write (*, *) "Debug: print variables..." - do i = 1, this%variable_list%Count() - obj => this%variable_list%GetItem(i) - var => CastAsMappedVariable(obj) - write (*, *) trim(var%src%name), " ", trim(var%src%path), & - " to ", trim(var%tgt%name), " ", trim(var%tgt%path) - end do - - end subroutine print_variables - -end module DistributedDataModule diff --git a/src/Model/Connection/DistributedModel.f90 b/src/Model/Connection/DistributedModel.f90 deleted file mode 100644 index 92a0b39a6d2..00000000000 --- a/src/Model/Connection/DistributedModel.f90 +++ /dev/null @@ -1,244 +0,0 @@ -module DistributedModelModule - use KindModule, only: I4B, DP, LGP - use ConstantsModule, only: LENMODELNAME, LENCOMPONENTNAME, & - LENVARNAME, LENMEMPATH - use SimModule, only: ustop - use ListModule, only: ListType - use MemoryTypeModule, only: MemoryType - use MemoryManagerModule, only: get_from_memorylist - use MemoryHelperModule, only: create_mem_path - use NumericalModelModule, only: NumericalModelType, GetNumericalModelFromList, & - AddNumericalModelToList - - use ListsModule, only: basemodellist, distmodellist - implicit none - private - - public :: add_dist_model - public :: GetDistModelFromList, AddDistModelToList - - type, public :: DistributedModelType - integer(I4B) :: id !< universal identifier: id of the model - character(len=LENMODELNAME) :: name !< model name - - ! cached variables: - integer(I4B), pointer :: moffset => null() - - ! this is strictly private, use access() instead - class(NumericalModelType), private, pointer :: model !< implementation if local, null otherwise - contains - generic :: create => create_local, create_remote - generic :: load => load_intsclr, load_int1d, load_dblsclr, load_double1d - generic :: operator(==) => equals_dist_model, equals_num_model - procedure :: access - - ! private - procedure, private :: create_local - procedure, private :: create_remote - procedure, private :: load_intsclr - procedure, private :: load_int1d - procedure, private :: load_dblsclr - procedure, private :: load_double1d - procedure, private :: equals_dist_model - procedure, private :: equals_num_model - end type DistributedModelType - -contains - - subroutine add_dist_model(model_index) - integer :: model_index - ! local - class(NumericalModelType), pointer :: num_model - class(DistributedModelType), pointer :: dist_model - - num_model => GetNumericalModelFromList(basemodellist, model_index) - - allocate (dist_model) - call dist_model%create_local(num_model) - call AddDistModelToList(distmodellist, dist_model) - - end subroutine add_dist_model - - subroutine create_local(this, model) - class(DistributedModelType) :: this - class(NumericalModelType), pointer :: model - - this%id = model%id - this%name = model%name - this%model => model - - ! connect cached variables - call this%load(this%moffset, 'MOFFSET') - - end subroutine create_local - - subroutine create_remote(this, m_id) - class(DistributedModelType) :: this - integer(I4B) :: m_id - - this%id = m_id - this%name = 'TBD' - this%model => null() - - ! TODO_MJR: this should prepare a memory space - ! where the remote data can live, and then - ! also connect cache (if we decide to use that) - - end subroutine create_remote - - subroutine load_intsclr(this, intsclr, var_name, subcomp_name) - class(DistributedModelType) :: this - integer(I4B), pointer :: intsclr - character(len=*) :: var_name - character(len=*), optional :: subcomp_name - ! local - type(MemoryType), pointer :: mt - logical(LGP) :: found - character(len=LENMEMPATH) :: mem_path - - if (present(subcomp_name)) then - mem_path = create_mem_path(this%name, subcomp_name) - else - mem_path = create_mem_path(this%name) - end if - - call get_from_memorylist(var_name, mem_path, mt, found) - intsclr => mt%intsclr - - end subroutine load_intsclr - - subroutine load_int1d(this, aint1d, var_name, subcomp_name) - class(DistributedModelType) :: this - integer(I4B), dimension(:), pointer, contiguous :: aint1d - character(len=*) :: var_name - character(len=*), optional :: subcomp_name - ! local - character(len=LENMEMPATH) :: mem_path - type(MemoryType), pointer :: mt - logical(LGP) :: found - - if (present(subcomp_name)) then - mem_path = create_mem_path(this%name, subcomp_name) - else - mem_path = create_mem_path(this%name) - end if - - call get_from_memorylist(var_name, mem_path, mt, found) - aint1d => mt%aint1d - - end subroutine load_int1d - - subroutine load_dblsclr(this, dblsclr, var_name, subcomp_name) - class(DistributedModelType) :: this - real(DP), pointer :: dblsclr - character(len=*) :: var_name - character(len=*), optional :: subcomp_name - ! local - type(MemoryType), pointer :: mt - logical(LGP) :: found - character(len=LENMEMPATH) :: mem_path - - if (present(subcomp_name)) then - mem_path = create_mem_path(this%name, subcomp_name) - else - mem_path = create_mem_path(this%name) - end if - - call get_from_memorylist(var_name, mem_path, mt, found) - dblsclr => mt%dblsclr - - end subroutine load_dblsclr - - subroutine load_double1d(this, adbl1d, var_name, subcomp_name) - class(DistributedModelType) :: this - real(DP), dimension(:), pointer, contiguous :: adbl1d - character(len=*) :: var_name - character(len=*), optional :: subcomp_name - ! local - character(len=LENMEMPATH) :: mem_path - type(MemoryType), pointer :: mt - logical(LGP) :: found - - if (present(subcomp_name)) then - mem_path = create_mem_path(this%name, subcomp_name) - else - mem_path = create_mem_path(this%name) - end if - - call get_from_memorylist(var_name, mem_path, mt, found) - adbl1d => mt%adbl1d - - end subroutine load_double1d - - function equals_dist_model(this, dist_model) result(is_equal) - class(DistributedModelType), intent(in) :: this - class(DistributedModelType), intent(in) :: dist_model - logical(LGP) :: is_equal - - is_equal = (this%id == dist_model%id) - - end function equals_dist_model - - function equals_num_model(this, num_model) result(is_equal) - class(DistributedModelType), intent(in) :: this - class(NumericalModelType), intent(in) :: num_model - logical(LGP) :: is_equal - - is_equal = (this%id == num_model%id) - - end function equals_num_model - - function access(this) result(model) - class(DistributedModelType) :: this - class(NumericalModelType), pointer :: model - - if (associated(this%model)) then - model => this%model - else - write (*, *) 'Error: illegal access to remote memory, abort' - call ustop() - end if - - end function access - - function CastAsDistModelClass(obj) result(res) - class(*), pointer, intent(inout) :: obj - class(DistributedModelType), pointer :: res - - res => null() - if (.not. associated(obj)) return - - select type (obj) - class is (DistributedModelType) - res => obj - end select - return - - end function CastAsDistModelClass - - subroutine AddDistModelToList(list, model) - type(ListType), intent(inout) :: list - class(DistributedModelType), pointer, intent(inout) :: model - ! local - class(*), pointer :: obj - - obj => model - call list%Add(obj) - return - - end subroutine AddDistModelToList - - function GetDistModelFromList(list, idx) result(res) - type(ListType), intent(inout) :: list - integer(I4B), intent(in) :: idx - class(DistributedModelType), pointer :: res - ! local - class(*), pointer :: obj - - obj => list%GetItem(idx) - res => CastAsDistModelClass(obj) - return - - end function GetDistModelFromList - -end module DistributedModelModule diff --git a/src/Model/Connection/DistributedVariable.f90 b/src/Model/Connection/DistributedVariable.f90 new file mode 100644 index 00000000000..5fbb6b057e8 --- /dev/null +++ b/src/Model/Connection/DistributedVariable.f90 @@ -0,0 +1,59 @@ +module DistVariableModule + use ConstantsModule, only: LENCOMPONENTNAME, LENVARNAME + use KindModule, only: I4B + use ListModule, only: ListType + use InterfaceMapModule + + implicit none + private + + public :: GetDistVarFromList + + ! types of variables + integer(I4B), public, parameter :: SYNC_SCALAR = 0 + integer(I4B), public, parameter :: SYNC_NODES = 1 + integer(I4B), public, parameter :: SYNC_CONNECTIONS = 2 + integer(I4B), public, parameter :: SYNC_EXCHANGES = 3 + + type, public :: DistVarType + character(len=LENVARNAME) :: var_name !< name of variable, e.g. "K11" + character(len=LENCOMPONENTNAME) :: subcomp_name !< subcomponent, e.g. "NPF" + character(len=LENCOMPONENTNAME) :: comp_name !< component, e.g. the model or exchange name + integer(I4B) :: map_type !< can be 0 = scalar, 1 = node based, 2 = connection based, + !! 3 = exchange based (connections crossing model boundaries) + character(len=LENVARNAME) :: exg_var_name !< needed for exchange variables, e.g. SIMVALS + integer(I4B), dimension(:), allocatable :: sync_stages !< when to sync, e.g. (/ BEFORE_AD, BEFORE_CF /) + end type DistVarType + +contains + + function GetDistVarFromList(list, idx) result(res) + implicit none + type(ListType), intent(inout) :: list + integer(I4B), intent(in) :: idx + class(DistVarType), pointer :: res + ! local + class(*), pointer :: obj + + obj => list%GetItem(idx) + res => CastAsDistVar(obj) + return + + end function GetDistVarFromList + + function CastAsDistVar(obj) result(res) + implicit none + class(*), pointer, intent(inout) :: obj + class(DistVarType), pointer :: res + + res => null() + if (.not. associated(obj)) return + + select type (obj) + class is (DistVarType) + res => obj + end select + return + end function CastAsDistVar + +end module DistVariableModule diff --git a/src/Model/Connection/GridConnection.f90 b/src/Model/Connection/GridConnection.f90 index 51c25d1f5ff..4c5789f3fec 100644 --- a/src/Model/Connection/GridConnection.f90 +++ b/src/Model/Connection/GridConnection.f90 @@ -6,21 +6,23 @@ module GridConnectionModule use KindModule, only: I4B, DP, LGP use SimModule, only: ustop use ConstantsModule, only: LENMEMPATH, DZERO, DPIO180, LENMODELNAME + use CharacterStringModule use MemoryManagerModule, only: mem_allocate, mem_deallocate use MemoryHelperModule, only: create_mem_path use ListModule, only: ListType, isEqualIface, arePointersEqual use NumericalModelModule use GwfDisuModule use DisConnExchangeModule - use DistributedModelModule, only: DistributedModelType, GetDistModelFromList, & - AddDistModelToList + use VirtualModelModule, only: VirtualModelType, get_virtual_model, & + get_virtual_model_from_list + use VirtualExchangeModule, only: VirtualExchangeType, get_virtual_exchange use CellWithNbrsModule use ConnectionsModule use SparseModule, only: sparsematrix use InterfaceMapModule use BaseDisModule, only: dis_transform_xy - use ListsModule, only: distmodellist use CsrUtilsModule + use STLVecIntModule implicit none private @@ -63,31 +65,33 @@ module GridConnectionModule integer(I4B), pointer :: nrOfBoundaryCells => null() !< nr of boundary cells with connection to another model type(CellWithNbrsType), dimension(:), pointer :: boundaryCells => null() !< cells on our side of the primary connections type(CellWithNbrsType), dimension(:), pointer :: connectedCells => null() !< cells on the neighbors side of the primary connection - type(ListType) :: exchanges !< all relevant exchanges for this connection, up to the required depth + type(STLVecInt), pointer :: haloExchanges !< all exchanges that are potentially part of this interface integer(I4B), pointer :: nrOfCells => null() !< the total number of cells in the interface type(GlobalCellType), dimension(:), pointer :: idxToGlobal => null() !< a map from interface index to global coordinate integer(I4B), dimension(:), pointer, contiguous :: idxToGlobalIdx => null() !< a (flat) map from interface index to global index, !! stored in mem. mgr. so can be used for debugging - - integer(I4B), dimension(:), pointer :: regionalToInterfaceIdxMap => null() !< (sparse) mapping from regional index to interface ixd type(ListType) :: regionalModels !< the models participating in the interface + integer(I4B), dimension(:), pointer :: region_to_iface_map => null() !< (sparse) mapping from regional index to interface ixd integer(I4B), dimension(:), pointer :: regionalModelOffset => null() !< the new offset to compactify the range of indices integer(I4B), pointer :: indexCount => null() !< counts the number of cells in the interface + type(ConnectionsType), pointer :: connections => null() !< sparse matrix with the connections integer(I4B), dimension(:), pointer :: connectionMask => null() !< to mask out connections from the amat coefficient calculation + type(InterfaceMapType), pointer :: interfaceMap => null() !< defining map for the interface + contains ! public procedure, pass(this) :: construct procedure, private, pass(this) :: allocateScalars procedure, pass(this) :: destroy + procedure, pass(this) :: addToRegionalModels procedure, pass(this) :: connectPrimaryExchange - procedure, pass(this) :: findModelNeighbors procedure, pass(this) :: extendConnection procedure, pass(this) :: getDiscretization - procedure, pass(this) :: getInterfaceMap + procedure, pass(this) :: buildInterfaceMap ! 'protected' procedure, pass(this) :: isPeriodic @@ -98,9 +102,7 @@ module GridConnectionModule procedure, private, pass(this) :: addNeighbors procedure, private, pass(this) :: addNeighborCell procedure, private, pass(this) :: addRemoteNeighbors - procedure, private, pass(this) :: addModelNeighbors - procedure, private, pass(this) :: addToRegionalModels - procedure, private, pass(this) :: getRegionalModelOffset + procedure, private, pass(this) :: get_regional_offset generic, private :: getInterfaceIndex => getInterfaceIndexByCell, & getInterfaceIndexByIndexModel procedure, private, pass(this) :: getInterfaceIndexByCell @@ -129,7 +131,7 @@ subroutine construct(this, model, nrOfPrimaries, connectionName) integer(I4B) :: nrOfPrimaries !> the number of primary connections between the two models character(len=*) :: connectionName !> the name, for memory management mostly ! local - class(DistributedModelType), pointer :: dist_model + class(VirtualModelType), pointer :: v_model this%model => model this%memoryPath = create_mem_path(connectionName, 'GC') @@ -140,13 +142,14 @@ subroutine construct(this, model, nrOfPrimaries, connectionName) allocate (this%connectedCells(nrOfPrimaries)) allocate (this%idxToGlobal(2 * nrOfPrimaries)) - dist_model => GetDistModelFromList(distmodellist, model%id) - call this%addToRegionalModels(dist_model) + v_model => get_virtual_model(model%id) + call this%addToRegionalModels(v_model) this%nrOfBoundaryCells = 0 this%internalStencilDepth = 1 this%exchangeStencilDepth = 1 + this%haloExchanges => null() end subroutine construct @@ -163,8 +166,8 @@ subroutine connectPrimaryExchange(this, primEx) ! connect the cells do iconn = 1, primEx%nexg - call this%connectCell(primEx%nodem1(iconn), primEx%dmodel1, & - primEx%nodem2(iconn), primEx%dmodel2) + call this%connectCell(primEx%nodem1(iconn), primEx%v_model1, & + primEx%nodem2(iconn), primEx%v_model2) end do end subroutine connectPrimaryExchange @@ -173,12 +176,12 @@ end subroutine connectPrimaryExchange !! storing them in the boundary cell and connected cell !! arrays !< - subroutine connectCell(this, idx1, dist_model1, idx2, dist_model2) + subroutine connectCell(this, idx1, v_model1, idx2, v_model2) class(GridConnectionType), intent(in) :: this !< this grid connection integer(I4B) :: idx1 !< local index cell 1 - class(DistributedModelType), pointer :: dist_model1 !< model of cell 1 + class(VirtualModelType), pointer :: v_model1 !< model of cell 1 integer(I4B) :: idx2 !< local index cell 2 - class(DistributedModelType), pointer :: dist_model2 !< model of cell 2 + class(VirtualModelType), pointer :: v_model2 !< model of cell 2 ! local type(GlobalCellType), pointer :: bnd_cell, conn_cell @@ -191,16 +194,16 @@ subroutine connectCell(this, idx1, dist_model1, idx2, dist_model2) bnd_cell => this%boundaryCells(this%nrOfBoundaryCells)%cell conn_cell => this%connectedCells(this%nrOfBoundaryCells)%cell - if (dist_model1 == this%model) then + if (v_model1 == this%model) then bnd_cell%index = idx1 - bnd_cell%dmodel => dist_model1 + bnd_cell%v_model => v_model1 conn_cell%index = idx2 - conn_cell%dmodel => dist_model2 - else if (dist_model2 == this%model) then + conn_cell%v_model => v_model2 + else if (v_model2 == this%model) then bnd_cell%index = idx2 - bnd_cell%dmodel => dist_model2 + bnd_cell%v_model => v_model2 conn_cell%index = idx1 - conn_cell%dmodel => dist_model1 + conn_cell%v_model => v_model1 else write (*, *) 'Error: unable to connect cells outside the model' call ustop() @@ -208,113 +211,19 @@ subroutine connectCell(this, idx1, dist_model1, idx2, dist_model2) end subroutine connectCell - !> @brief Create the tree structure with all model nbrs, nbrs-of-nbrs, - !< etc. for this model up to the specified depth - subroutine findModelNeighbors(this, globalExchanges, depth) - class(GridConnectionType), intent(inout) :: this !< this grid connection - type(ListType), intent(inout) :: globalExchanges !< list with global exchanges - integer(I4B) :: depth !< the maximal number of exchanges between - !! any two models in the topology - ! local - class(DistributedModelType), pointer :: dist_model - - dist_model => GetDistModelFromList(distmodellist, this%model%id) - call this%addModelNeighbors(dist_model, globalExchanges, depth) - - end subroutine findModelNeighbors - - !> @brief Add neighbors and nbrs-of-nbrs to the model tree - !< - recursive subroutine addModelNeighbors(this, dist_model, & - globalExchanges, & - depth, mask) - class(GridConnectionType), intent(inout) :: this !< this grid connection - class(DistributedModelType), pointer, intent(inout) :: dist_model !< the model to add neighbors for - type(ListType), intent(inout) :: globalExchanges !< list with all exchanges - integer(I4B) :: depth !< the maximal number of exchanges between - class(DistributedModelType), pointer, optional :: mask !< don't add this one as a neighbor - ! local - integer(I4B) :: i, n - class(DisConnExchangeType), pointer :: connEx - class(DistributedModelType), pointer :: neighborModel - class(DistributedModelType), pointer :: modelMask - type(ListType) :: nbrModels - class(*), pointer :: objPtr - procedure(isEqualIface), pointer :: areEqualMethod - - if (.not. present(mask)) then - modelMask => null() - else - modelMask => mask - end if - - ! first find all direct neighbors of the model and add them, - ! avoiding duplicates - do i = 1, globalExchanges%Count() - neighborModel => null() - connEx => GetDisConnExchangeFromList(globalExchanges, i) - if (connEx%dmodel1 == dist_model) then - neighborModel => connEx%dmodel2 - else if (connEx%dmodel2 == dist_model) then - neighborModel => connEx%dmodel1 - end if - - ! check if there is a neighbor, and it is not masked - ! (to prevent back-and-forth connections) - if (associated(neighborModel)) then - - ! check if masked - if (associated(modelMask)) then - if (neighborModel == modelMask) cycle - end if - - ! add to neighbors - objPtr => neighborModel - if (.not. nbrModels%ContainsObject(objPtr, areEqualMethod)) then - call nbrModels%Add(objPtr) - end if - - ! add to list of regional models - call this%addToRegionalModels(neighborModel) - - ! add to list of relevant exchanges - objPtr => connEx - areEqualMethod => arePointersEqual - if (.not. this%exchanges%ContainsObject(objPtr, areEqualMethod)) then - call this%exchanges%Add(objPtr) - end if - - end if - end do - - ! now recurse on the neighbors up to the specified depth - depth = depth - 1 - if (depth == 0) return - - do n = 1, nbrModels%Count() - neighborModel => GetDistModelFromList(nbrModels, n) - call this%addModelNeighbors(neighborModel, globalExchanges, & - depth, dist_model) - end do - - ! clear list - call nbrModels%Clear(destroy=.false.) - - end subroutine addModelNeighbors - !> @brief Add a model to a list of all regional models !< - subroutine addToRegionalModels(this, modelToAdd) + subroutine addToRegionalModels(this, v_model) class(GridConnectionType), intent(inout) :: this !< this grid connection - class(DistributedModelType), pointer :: modelToAdd !< the model to add to the region + class(VirtualModelType), pointer :: v_model !< the model to add to the region ! local - class(*), pointer :: mPtr + class(*), pointer :: vm_obj procedure(isEqualIface), pointer :: areEqualMethod - mPtr => modelToAdd + vm_obj => v_model areEqualMethod => arePointersEqual - if (.not. this%regionalModels%ContainsObject(mPtr, areEqualMethod)) then - call AddDistModelToList(this%regionalModels, modelToAdd) + if (.not. this%regionalModels%ContainsObject(vm_obj, areEqualMethod)) then + call this%regionalModels%Add(vm_obj) end if end subroutine addToRegionalModels @@ -334,8 +243,8 @@ subroutine extendConnection(this) integer(I4B) :: remoteDepth, localDepth integer(I4B) :: icell integer(I4B) :: imod, regionSize, offset - class(DistributedModelType), pointer :: dist_model - integer(I4B), pointer :: nr_nodes + class(VirtualModelType), pointer :: v_model + !integer(I4B), pointer :: nr_nodes ! we need (stencildepth-1) extra cells for the interior remoteDepth = this%exchangeStencilDepth @@ -361,15 +270,14 @@ subroutine extendConnection(this) regionSize = 0 offset = 0 do imod = 1, this%regionalModels%Count() - dist_model => GetDistModelFromList(this%regionalModels, imod) - call dist_model%load(nr_nodes, 'NODES', 'DIS') - regionSize = regionSize + nr_nodes + v_model => get_virtual_model_from_list(this%regionalModels, imod) + regionSize = regionSize + v_model%dis_nodes%get() this%regionalModelOffset(imod) = offset - offset = offset + nr_nodes + offset = offset + v_model%dis_nodes%get() end do ! init to -1, meaning 'interface index was not assigned yet' - allocate (this%regionalToInterfaceIdxMap(regionSize)) - this%regionalToInterfaceIdxMap = -1 + allocate (this%region_to_iface_map(regionSize)) + this%region_to_iface_map = -1 call this%buildConnections() @@ -462,17 +370,18 @@ end subroutine buildConnections recursive subroutine addNeighbors(this, cellNbrs, depth, mask, interior) use SimModule, only: ustop class(GridConnectionType), intent(inout) :: this !< this grid connection - type(CellWithNbrsType), intent(inout) :: cellNbrs !< cell to add to + type(CellWithNbrsType), intent(inout), target :: cellNbrs !< cell to add to integer(I4B), intent(inout) :: depth !< current depth (typically decreases in recursion) type(GlobalCellType), optional :: mask !< mask to excluded back-and-forth connection between cells logical(LGP) :: interior !< when true, we are adding from the exchange back into the model ! local - integer(I4B) :: nbrIdx, ipos, inbr + type(GlobalCellType), pointer :: cell + integer(I4B) :: ipos, ipos_start, ipos_end + integer(I4B) :: nbrIdx, inbr integer(I4B) :: newDepth - ! TODO_MJR, is this how we are going to do this? - integer(I4B), dimension(:), pointer, contiguous :: ia - integer(I4B), dimension(:), pointer, contiguous :: ja + ! readability + cell => cellNbrs%cell ! if depth == 1, then we are not adding neighbors but use ! the boundary and connected cell only @@ -481,15 +390,12 @@ recursive subroutine addNeighbors(this, cellNbrs, depth, mask, interior) end if newDepth = depth - 1 - ! access through dist. model: - call cellNbrs%cell%dmodel%load(ia, 'IA', 'CON') - call cellNbrs%cell%dmodel%load(ja, 'JA', 'CON') - ! find neighbors local to this cell by looping through grid connections - do ipos = ia(cellNbrs%cell%index) + 1, & - ia(cellNbrs%cell%index + 1) - 1 - nbrIdx = ja(ipos) - call this%addNeighborCell(cellNbrs, nbrIdx, cellNbrs%cell%dmodel, mask) + ipos_start = cell%v_model%con_ia%get(cell%index) + 1 + ipos_end = cell%v_model%con_ia%get(cell%index + 1) - 1 + do ipos = ipos_start, ipos_end + nbrIdx = cell%v_model%con_ja%get(ipos) + call this%addNeighborCell(cellNbrs, nbrIdx, cellNbrs%cell%v_model, mask) end do ! add remote nbr using the data from the exchanges @@ -499,8 +405,8 @@ recursive subroutine addNeighbors(this, cellNbrs, depth, mask, interior) do inbr = 1, cellNbrs%nrOfNbrs ! are we leaving the model through another exchange? - if (interior .and. cellNbrs%cell%dmodel == this%model) then - if (.not. cellNbrs%neighbors(inbr)%cell%dmodel == this%model) then + if (interior .and. cellNbrs%cell%v_model == this%model) then + if (.not. cellNbrs%neighbors(inbr)%cell%v_model == this%model) then ! decrement by 1, because the connection we are crossing is not ! calculated by this interface newDepth = newDepth - 1 @@ -521,29 +427,33 @@ subroutine addRemoteNeighbors(this, cellNbrs, mask) type(GlobalCellType), optional :: mask !< a mask to exclude back-and-forth connections ! local integer(I4B) :: ix, iexg - type(DisConnExchangeType), pointer :: connEx + class(VirtualExchangeType), pointer :: v_exchange + class(VirtualModelType), pointer :: v_m1, v_m2 ! loop over all exchanges - do ix = 1, this%exchanges%Count() - connEx => GetDisConnExchangeFromList(this%exchanges, ix) + do ix = 1, this%haloExchanges%size + + v_exchange => get_virtual_exchange(this%haloExchanges%at(ix)) + v_m1 => v_exchange%v_model1 + v_m2 => v_exchange%v_model2 ! loop over n-m links in the exchange - if (cellNbrs%cell%dmodel == connEx%dmodel1) then - do iexg = 1, connEx%nexg - if (connEx%nodem1(iexg) == cellNbrs%cell%index) then + if (cellNbrs%cell%v_model == v_m1) then + do iexg = 1, v_exchange%nexg%get() + if (v_exchange%nodem1%get(iexg) == cellNbrs%cell%index) then ! we have a link, now add foreign neighbor - call this%addNeighborCell(cellNbrs, connEx%nodem2(iexg), & - connEx%dmodel2, mask) + call this%addNeighborCell( & + cellNbrs, v_exchange%nodem2%get(iexg), v_m2, mask) end if end do end if ! and the reverse - if (cellNbrs%cell%dmodel == connEx%dmodel2) then - do iexg = 1, connEx%nexg - if (connEx%nodem2(iexg) == cellNbrs%cell%index) then + if (cellNbrs%cell%v_model == v_m2) then + do iexg = 1, v_exchange%nexg%get() + if (v_exchange%nodem2%get(iexg) == cellNbrs%cell%index) then ! we have a link, now add foreign neighbor - call this%addNeighborCell(cellNbrs, connEx%nodem1(iexg), & - connEx%dmodel1, mask) + call this%addNeighborCell( & + cellNbrs, v_exchange%nodem1%get(iexg), v_m1, mask) end if end do end if @@ -554,20 +464,20 @@ end subroutine addRemoteNeighbors !> @brief Add neighboring cell to tree structure !< - subroutine addNeighborCell(this, cellNbrs, newNbrIdx, nbr_dist_model, mask) + subroutine addNeighborCell(this, cellNbrs, newNbrIdx, v_nbr_model, mask) class(GridConnectionType), intent(in) :: this !< this grid connection instance type(CellWithNbrsType), intent(inout) :: cellNbrs !< the root cell which to add to integer(I4B), intent(in) :: newNbrIdx !< the neigboring cell's index - class(DistributedModelType), pointer :: nbr_dist_model !< the model where the new neighbor lives + class(VirtualModelType), pointer :: v_nbr_model !< the model where the new neighbor lives type(GlobalCellType), optional :: mask !< don't add connections to this cell (optional) if (present(mask)) then - if (newNbrIdx == mask%index .and. mask%dmodel == nbr_dist_model) then + if (newNbrIdx == mask%index .and. mask%v_model == v_nbr_model) then return end if end if - call cellNbrs%addNbrCell(newNbrIdx, nbr_dist_model) + call cellNbrs%addNbrCell(newNbrIdx, v_nbr_model) end subroutine addNeighborCell @@ -581,14 +491,14 @@ recursive subroutine registerInterfaceCells(this, cellWithNbrs) integer(I4B) :: regionIdx ! unique idx in the region (all connected models) integer(I4B) :: ifaceIdx ! unique idx in the interface grid - offset = this%getRegionalModelOffset(cellWithNbrs%cell%dmodel) + offset = this%get_regional_offset(cellWithNbrs%cell%v_model) regionIdx = offset + cellWithNbrs%cell%index ifaceIdx = this%getInterfaceIndex(cellWithNbrs%cell) if (ifaceIdx == -1) then this%indexCount = this%indexCount + 1 ifaceIdx = this%indexCount call this%addToGlobalMap(ifaceIdx, cellWithNbrs%cell) - this%regionalToInterfaceIdxMap(regionIdx) = ifaceIdx + this%region_to_iface_map(regionIdx) = ifaceIdx end if ! and also for its neighbors @@ -677,17 +587,17 @@ subroutine sortInterfaceGrid(this) deallocate (sortedGlobalMap) ! reorder regional lookup table - allocate (sortedRegionMap(size(this%regionalToInterfaceIdxMap))) + allocate (sortedRegionMap(size(this%region_to_iface_map))) do i = 1, size(sortedRegionMap) - if (this%regionalToInterfaceIdxMap(i) /= -1) then - idxOld = this%regionalToInterfaceIdxMap(i) + if (this%region_to_iface_map(i) /= -1) then + idxOld = this%region_to_iface_map(i) sortedRegionMap(i) = oldToNewIdx(idxOld) else sortedRegionMap(i) = -1 end if end do do i = 1, size(sortedRegionMap) - this%regionalToInterfaceIdxMap(i) = sortedRegionMap(i) + this%region_to_iface_map(i) = sortedRegionMap(i) end do deallocate (sortedRegionMap) @@ -749,11 +659,9 @@ subroutine fillConnectionDataInternal(this) class(GridConnectionType), intent(inout) :: this !< this grid connection instance ! local type(ConnectionsType), pointer :: conn - integer(I4B) :: n, m, ipos, isym, iposOrig, isymOrig + integer(I4B) :: n, m, ipos, isym, ipos_orig, isym_orig type(GlobalCellType), pointer :: ncell, mcell - integer(I4B), dimension(:), pointer, contiguous :: jas, ihc - real(DP), dimension(:), pointer, contiguous :: hwva, cl1, cl2, anglex - integer(I4B), dimension(:), pointer, contiguous :: ia, ja + class(VirtualModelType), pointer :: v_m !< pointer to virtual model (readability) conn => this%connections @@ -765,14 +673,16 @@ subroutine fillConnectionDataInternal(this) isym = conn%jas(ipos) ncell => this%idxToGlobal(n) mcell => this%idxToGlobal(m) - if (ncell%dmodel == mcell%dmodel) then + if (ncell%v_model == mcell%v_model) then + + ! for readability + v_m => ncell%v_model ! within same model, straight copy - call ncell%dmodel%load(ia, 'IA', 'CON') - call ncell%dmodel%load(ja, 'JA', 'CON') - iposOrig = getCSRIndex(ncell%index, mcell%index, ia, ja) + ipos_orig = getCSRIndex(ncell%index, mcell%index, & + v_m%con_ia%get_array(), v_m%con_ja%get_array()) - if (iposOrig == 0) then + if (ipos_orig == 0) then ! periodic boundary conditions can add connections between cells in ! the same model, but they are dealt with through the exchange data if (this%isPeriodic(ncell%index, mcell%index)) cycle @@ -782,63 +692,54 @@ subroutine fillConnectionDataInternal(this) call ustop() end if - ! load distributed data from memory - ! TODO_MJR: this should probably be cached at some point... - call ncell%dmodel%load(jas, 'JAS', 'CON') - call ncell%dmodel%load(ihc, 'IHC', 'CON') - call ncell%dmodel%load(hwva, 'HWVA', 'CON') - call ncell%dmodel%load(cl1, 'CL1', 'CON') - call ncell%dmodel%load(cl2, 'CL2', 'CON') - call ncell%dmodel%load(anglex, 'ANGLEX', 'CON') - - isymOrig = jas(iposOrig) - conn%hwva(isym) = hwva(isymOrig) - conn%ihc(isym) = ihc(isymOrig) + isym_orig = v_m%con_jas%get(ipos_orig) + conn%hwva(isym) = v_m%con_hwva%get(isym_orig) + conn%ihc(isym) = v_m%con_ihc%get(isym_orig) if (ncell%index < mcell%index) then - conn%cl1(isym) = cl1(isymOrig) - conn%cl2(isym) = cl2(isymOrig) - conn%anglex(isym) = anglex(isymOrig) + conn%cl1(isym) = v_m%con_cl1%get(isym_orig) + conn%cl2(isym) = v_m%con_cl2%get(isym_orig) + conn%anglex(isym) = v_m%con_anglex%get(isym_orig) else - conn%cl1(isym) = cl2(isymOrig) - conn%cl2(isym) = cl1(isymOrig) - conn%anglex(isym) = mod(anglex(isymOrig) + DPI, DTWOPI) + conn%cl1(isym) = v_m%con_cl2%get(isym_orig) + conn%cl2(isym) = v_m%con_cl1%get(isym_orig) + conn%anglex(isym) = mod(v_m%con_anglex%get(isym_orig) + DPI, DTWOPI) end if end if end do end do + end subroutine fillConnectionDataInternal !> @brief Fill connection data (ihc, cl1, ...) for !< all exchanges subroutine fillConnectionDataFromExchanges(this) - use ConstantsModule, only: DPI, DTWOPI, DPIO180 - use ArrayHandlersModule, only: ifind + use ConstantsModule, only: DPIO180 class(GridConnectionType), intent(inout) :: this !< this grid connection instance ! local - integer(I4B) :: inx, iexg, ivalAngldegx + integer(I4B) :: inx, iexg integer(I4B) :: ipos, isym integer(I4B) :: nOffset, mOffset, nIfaceIdx, mIfaceIdx - class(DisConnExchangeType), pointer :: connEx + class(VirtualExchangeType), pointer :: v_exg + class(VirtualModelType), pointer :: v_m1, v_m2 type(ConnectionsType), pointer :: conn conn => this%connections - do inx = 1, this%exchanges%Count() - connEx => GetDisConnExchangeFromList(this%exchanges, inx) + do inx = 1, this%haloExchanges%size + v_exg => get_virtual_exchange(this%haloExchanges%at(inx)) - ivalAngldegx = -1 - if (connEx%naux > 0) then - ivalAngldegx = ifind(connEx%auxname, 'ANGLDEGX') - if (ivalAngldegx > 0) then - conn%ianglex = 1 - end if + v_m1 => v_exg%v_model1 + v_m2 => v_exg%v_model2 + + if (v_exg%ianglex%get() > 0) then + conn%ianglex = 1 end if - nOffset = this%getRegionalModelOffset(connEx%dmodel1) - mOffset = this%getRegionalModelOffset(connEx%dmodel2) - do iexg = 1, connEx%nexg - nIfaceIdx = this%regionalToInterfaceIdxMap(noffset + connEx%nodem1(iexg)) - mIfaceIdx = this%regionalToInterfaceIdxMap(moffset + connEx%nodem2(iexg)) + nOffset = this%get_regional_offset(v_m1) + mOffset = this%get_regional_offset(v_m2) + do iexg = 1, v_exg%nexg%get() + nIfaceIdx = this%region_to_iface_map(noffset + v_exg%nodem1%get(iexg)) + mIfaceIdx = this%region_to_iface_map(moffset + v_exg%nodem2%get(iexg)) ! not all nodes from the exchanges are part of the interface grid ! (think of exchanges between neigboring models, and their neighbors) if (nIFaceIdx == -1 .or. mIFaceIdx == -1) then @@ -857,21 +758,22 @@ subroutine fillConnectionDataFromExchanges(this) ! note: cl1 equals L_nm: the length from cell n to the shared ! face with cell m (and cl2 analogously for L_mn) if (nIfaceIdx < mIfaceIdx) then - conn%cl1(isym) = connEx%cl1(iexg) - conn%cl2(isym) = connEx%cl2(iexg) - if (ivalAngldegx > 0) then - conn%anglex(isym) = connEx%auxvar(ivalAngldegx, iexg) * DPIO180 + conn%cl1(isym) = v_exg%cl1%get(iexg) + conn%cl2(isym) = v_exg%cl2%get(iexg) + if (v_exg%ianglex%get() > 0) then + conn%anglex(isym) = & + v_exg%auxvar%get(v_exg%ianglex%get(), iexg) * DPIO180 end if else - conn%cl1(isym) = connEx%cl2(iexg) - conn%cl2(isym) = connEx%cl1(iexg) - if (ivalAngldegx > 0) then - conn%anglex(isym) = mod(connEx%auxvar(ivalAngldegx, iexg) + & - 180.0_DP, 360.0_DP) * DPIO180 + conn%cl1(isym) = v_exg%cl2%get(iexg) + conn%cl2(isym) = v_exg%cl1%get(iexg) + if (v_exg%ianglex%get() > 0) then + conn%anglex(isym) = mod(v_exg%auxvar%get(v_exg%ianglex%get(), iexg) & + + 180.0_DP, 360.0_DP) * DPIO180 end if end if - conn%hwva(isym) = connEx%hwva(iexg) - conn%ihc(isym) = connEx%ihc(iexg) + conn%hwva(isym) = v_exg%hwva%get(iexg) + conn%ihc(isym) = v_exg%ihc%get(iexg) end do end do @@ -954,8 +856,8 @@ recursive subroutine maskInternalConnections(this, cell, nbrCell, level) ! only set the mask for internal connections, leaving the ! others at 0 - if (cell%cell%dmodel == this%model .and. & - nbrCell%cell%dmodel == this%model) then + if (cell%cell%v_model == this%model .and. & + nbrCell%cell%v_model == this%model) then ! this will set a mask on both diagonal, and both cross terms call this%setMaskOnConnection(cell, nbrCell, level) call this%setMaskOnConnection(nbrCell, cell, level) @@ -1004,52 +906,55 @@ end subroutine setMaskOnConnection !> @brief Get interface index from global cell !< - function getInterfaceIndexByCell(this, cell) result(ifaceIdx) + function getInterfaceIndexByCell(this, cell) result(iface_idx) class(GridConnectionType), intent(inout) :: this !< this grid connection instance type(GlobalCellType), intent(in) :: cell !< the global cell to get the interface index for - integer(I4B) :: ifaceIdx !< the index in the interface model + integer(I4B) :: iface_idx !< the index in the interface model ! local - integer(I4B) :: offset, regionIdx + integer(I4B) :: offset, region_idx + + offset = this%get_regional_offset(cell%v_model) + region_idx = offset + cell%index + iface_idx = this%region_to_iface_map(region_idx) - offset = this%getRegionalModelOffset(cell%dmodel) - regionIdx = offset + cell%index - ifaceIdx = this%regionalToInterfaceIdxMap(regionIdx) end function getInterfaceIndexByCell !> @brief Get interface index from a model pointer and the local index !< - function getInterfaceIndexByIndexModel(this, index, dist_model) result(ifaceIdx) + function getInterfaceIndexByIndexModel(this, index, v_model) result(iface_idx) class(GridConnectionType), intent(inout) :: this !< this grid connection instance integer(I4B) :: index !< the local cell index - class(DistributedModelType), pointer :: dist_model !< the cell's model - integer(I4B) :: ifaceIdx !< the index in the interface model + class(VirtualModelType), pointer :: v_model !< the cell's model + integer(I4B) :: iface_idx !< the index in the interface model ! local - integer(I4B) :: offset, regionIdx + integer(I4B) :: offset, region_idx + + offset = this%get_regional_offset(v_model) + region_idx = offset + index + iface_idx = this%region_to_iface_map(region_idx) - offset = this%getRegionalModelOffset(dist_model) - regionIdx = offset + index - ifaceIdx = this%regionalToInterfaceIdxMap(regionIdx) end function getInterfaceIndexByIndexModel !> @brief Get the offset for a regional model !< - function getRegionalModelOffset(this, model) result(offset) + function get_regional_offset(this, v_model) result(offset) class(GridConnectionType), intent(inout) :: this !< this grid connection instance - class(DistributedModelType), pointer :: model !< the model to get the offset for + class(VirtualModelType), pointer :: v_model !< the model to get the offset for integer(I4B) :: offset !< the index offset in the regional domain ! local integer(I4B) :: im - class(DistributedModelType), pointer :: modelInList + class(VirtualModelType), pointer :: vm + offset = 0 do im = 1, this%regionalModels%Count() - modelInList => GetDistModelFromList(this%regionalModels, im) - if (modelInList == model) then + vm => get_virtual_model_from_list(this%regionalModels, im) + if (vm == v_model) then offset = this%regionalModelOffset(im) return end if end do - end function getRegionalModelOffset + end function get_regional_offset !> @brief Allocate scalar data !< @@ -1073,9 +978,7 @@ subroutine getDiscretization(this, disu) class(GwfDisuType), pointer :: disu !< the target disu object ! local integer(I4B) :: icell, nrOfCells, idx - type(DistributedModelType), pointer :: dist_model - real(DP), dimension(:), pointer, contiguous :: dis_xc, dis_yc - real(DP), pointer :: dis_xorigin, dis_yorigin, dis_angrot + class(VirtualModelType), pointer :: v_model real(DP) :: xglo, yglo ! the following is similar to dis_df @@ -1103,19 +1006,16 @@ subroutine getDiscretization(this, disu) ! copy cell x,y do icell = 1, nrOfCells idx = this%idxToGlobal(icell)%index - dist_model => this%idxToGlobal(icell)%dmodel - - call dist_model%load(dis_xc, 'XC', 'DIS') - call dist_model%load(dis_yc, 'YC', 'DIS') - call dist_model%load(dis_xorigin, 'XORIGIN', 'DIS') - call dist_model%load(dis_yorigin, 'YORIGIN', 'DIS') - call dist_model%load(dis_angrot, 'ANGROT', 'DIS') + v_model => this%idxToGlobal(icell)%v_model ! we are merging grids with possibly (likely) different origins, ! transform to global coordinates: - call dis_transform_xy(dis_xc(idx), dis_yc(idx), & - dis_xorigin, dis_yorigin, & - dis_angrot, xglo, yglo) + call dis_transform_xy(v_model%dis_xc%get(idx), & + v_model%dis_yc%get(idx), & + v_model%dis_xorigin%get(), & + v_model%dis_yorigin%get(), & + v_model%dis_angrot%get(), & + xglo, yglo) ! NB: usernodes equals internal nodes for interface disu%cellxy(1, icell) = xglo @@ -1135,120 +1035,121 @@ subroutine getDiscretization(this, disu) end subroutine getDiscretization - !> @brief Build interface map object for outside use, - !< (caller owns the memory) - subroutine getInterfaceMap(this, interfaceMap) + !> @brief Build interface map object for outside use + subroutine buildInterfaceMap(this) use BaseModelModule, only: BaseModelType, GetBaseModelFromList - use VectorIntModule + use STLVecIntModule class(GridConnectionType) :: this !< this grid connection - type(InterfaceMapType), pointer :: interfaceMap !< a pointer to the map (not allocated yet) ! local integer(I4B) :: i, j, iloc, jloc integer(I4B) :: im, ix, mid, n - integer(I4B) :: ipos, iposModel - type(VectorInt) :: modelIds - type(VectorInt) :: srcIdxTmp, tgtIdxTmp, signTmp - class(DisConnExchangeType), pointer :: connEx - integer(I4B), dimension(:), pointer, contiguous :: ia, ja - - allocate (interfaceMap) + integer(I4B) :: ipos, ipos_model + type(STLVecInt) :: model_ids + type(STLVecInt) :: src_idx_tmp, tgt_idx_tmp, sign_tmp + class(VirtualExchangeType), pointer :: v_exg + class(VirtualModelType), pointer :: vm + class(VirtualModelType), pointer :: v_model1, v_model2 + type(InterfaceMapType), pointer :: imap + integer(I4B), dimension(:), pointer, contiguous :: ia_ptr, ja_ptr + + allocate (this%interfaceMap) + imap => this%interfaceMap ! first get the participating models - call modelIds%init() + call model_ids%init() do i = 1, this%nrOfCells - if (.not. modelIds%contains(this%idxToGlobal(i)%dmodel%id)) then - call modelIds%push_back(this%idxToGlobal(i)%dmodel%id) + if (.not. model_ids%contains(this%idxToGlobal(i)%v_model%id)) then + call model_ids%push_back(this%idxToGlobal(i)%v_model%id) end if end do - ! allocate space - interfaceMap%nr_models = modelIds%size - allocate (interfaceMap%model_names(modelIds%size)) - allocate (interfaceMap%node_map(modelIds%size)) - allocate (interfaceMap%connection_map(modelIds%size)) + ! initialize the map + call imap%init(model_ids%size, this%haloExchanges%size) ! for each model part of this interface, ... - do im = 1, modelIds%size - mid = modelIds%at(im) - interfaceMap%model_names(im) = get_model_name(mid) - call srcIdxTmp%init() - call tgtIdxTmp%init() + do im = 1, model_ids%size + mid = model_ids%at(im) + imap%model_ids(im) = mid + vm => get_virtual_model(mid) + imap%model_names(im) = vm%name + call src_idx_tmp%init() + call tgt_idx_tmp%init() ! store the node map for this model do i = 1, this%nrOfCells - if (mid == this%idxToGlobal(i)%dmodel%id) then - call srcIdxTmp%push_back(this%idxToGlobal(i)%index) - call tgtIdxTmp%push_back(i) + if (mid == this%idxToGlobal(i)%v_model%id) then + call src_idx_tmp%push_back(this%idxToGlobal(i)%index) + call tgt_idx_tmp%push_back(i) end if end do ! and copy into interface map - allocate (interfaceMap%node_map(im)%src_idx(srcIdxTmp%size)) - allocate (interfaceMap%node_map(im)%tgt_idx(tgtIdxTmp%size)) - do i = 1, srcIdxTmp%size - interfaceMap%node_map(im)%src_idx(i) = srcIdxTmp%at(i) - interfaceMap%node_map(im)%tgt_idx(i) = tgtIdxTmp%at(i) + allocate (imap%node_map(im)%src_idx(src_idx_tmp%size)) + allocate (imap%node_map(im)%tgt_idx(tgt_idx_tmp%size)) + do i = 1, src_idx_tmp%size + imap%node_map(im)%src_idx(i) = src_idx_tmp%at(i) + imap%node_map(im)%tgt_idx(i) = tgt_idx_tmp%at(i) end do - call srcIdxTmp%destroy() - call tgtIdxTmp%destroy() + call src_idx_tmp%destroy() + call tgt_idx_tmp%destroy() ! and for connections - call srcIdxTmp%init() - call tgtIdxTmp%init() + call src_idx_tmp%init() + call tgt_idx_tmp%init() ! store the connection map for this model do i = 1, this%nrOfCells - if (mid /= this%idxToGlobal(i)%dmodel%id) cycle + if (mid /= this%idxToGlobal(i)%v_model%id) cycle do ipos = this%connections%ia(i), this%connections%ia(i + 1) - 1 j = this%connections%ja(ipos) - if (mid /= this%idxToGlobal(j)%dmodel%id) cycle + if (mid /= this%idxToGlobal(j)%v_model%id) cycle ! i and j are now in same model (mid) iloc = this%idxToGlobal(i)%index jloc = this%idxToGlobal(j)%index - call this%idxToGlobal(i)%dmodel%load(ia, 'IA', 'CON') - call this%idxToGlobal(i)%dmodel%load(ja, 'JA', 'CON') - iposModel = getCSRIndex(iloc, jloc, ia, ja) - - call srcIdxTmp%push_back(iposModel) - call tgtIdxTmp%push_back(ipos) + ia_ptr => this%idxToGlobal(i)%v_model%con_ia%get_array() + ja_ptr => this%idxToGlobal(i)%v_model%con_ja%get_array() + ipos_model = getCSRIndex(iloc, jloc, ia_ptr, ja_ptr) + call src_idx_tmp%push_back(ipos_model) + call tgt_idx_tmp%push_back(ipos) end do end do ! copy into interface map - allocate (interfaceMap%connection_map(im)%src_idx(srcIdxTmp%size)) - allocate (interfaceMap%connection_map(im)%tgt_idx(tgtIdxTmp%size)) - do i = 1, srcIdxTmp%size - interfaceMap%connection_map(im)%src_idx(i) = srcIdxTmp%at(i) - interfaceMap%connection_map(im)%tgt_idx(i) = tgtIdxTmp%at(i) + allocate (imap%connection_map(im)%src_idx(src_idx_tmp%size)) + allocate (imap%connection_map(im)%tgt_idx(tgt_idx_tmp%size)) + do i = 1, src_idx_tmp%size + imap%connection_map(im)%src_idx(i) = src_idx_tmp%at(i) + imap%connection_map(im)%tgt_idx(i) = tgt_idx_tmp%at(i) end do - call srcIdxTmp%destroy() - call tgtIdxTmp%destroy() + call src_idx_tmp%destroy() + call tgt_idx_tmp%destroy() end do - call modelIds%destroy() + call model_ids%destroy() ! for each exchange that is part of this interface - interfaceMap%nr_exchanges = this%exchanges%Count() - allocate (interfaceMap%exchange_names(interfaceMap%nr_exchanges)) - allocate (interfaceMap%exchange_map(interfaceMap%nr_exchanges)) - do ix = 1, this%exchanges%Count() + do ix = 1, this%haloExchanges%size ! all exchanges in this list should have at ! least one relevant connection for this map - connEx => GetDisConnExchangeFromList(this%exchanges, ix) - interfaceMap%exchange_names(ix) = connEx%name + v_exg => get_virtual_exchange(this%haloExchanges%at(ix)) + v_model1 => v_exg%v_model1 + v_model2 => v_exg%v_model2 + + imap%exchange_ids(ix) = v_exg%id + imap%exchange_names(ix) = v_exg%name - call srcIdxTmp%init() - call tgtIdxTmp%init() - call signTmp%init() + call src_idx_tmp%init() + call tgt_idx_tmp%init() + call sign_tmp%init() - do n = 1, connEx%nexg - i = this%getInterfaceIndex(connEx%nodem1(n), connEx%dmodel1) - j = this%getInterfaceIndex(connEx%nodem2(n), connEx%dmodel2) + do n = 1, v_exg%nexg%get() + i = this%getInterfaceIndex(v_exg%nodem1%get(n), v_model1) + j = this%getInterfaceIndex(v_exg%nodem2%get(n), v_model2) if (i == -1 .or. j == -1) cycle ! not all exchange nodes are part of the interface ipos = this%connections%getjaindex(i, j) if (ipos == 0) then @@ -1258,42 +1159,63 @@ subroutine getInterfaceMap(this, interfaceMap) ! (c.f. 'test_gwf_ifmod_mult_exg.py') cycle end if - call srcIdxTmp%push_back(n) - call tgtIdxTmp%push_back(ipos) - call signTmp%push_back(1) + call src_idx_tmp%push_back(n) + call tgt_idx_tmp%push_back(ipos) + call sign_tmp%push_back(1) ! and the reverse connection: - call srcIdxTmp%push_back(n) - call tgtIdxTmp%push_back(this%connections%isym(ipos)) - call signTmp%push_back(-1) + call src_idx_tmp%push_back(n) + call tgt_idx_tmp%push_back(this%connections%isym(ipos)) + call sign_tmp%push_back(-1) end do - allocate (interfaceMap%exchange_map(ix)%src_idx(srcIdxTmp%size)) - allocate (interfaceMap%exchange_map(ix)%tgt_idx(tgtIdxTmp%size)) - allocate (interfaceMap%exchange_map(ix)%sign(signTmp%size)) - do i = 1, srcIdxTmp%size - interfaceMap%exchange_map(ix)%src_idx(i) = srcIdxTmp%at(i) - interfaceMap%exchange_map(ix)%tgt_idx(i) = tgtIdxTmp%at(i) - interfaceMap%exchange_map(ix)%sign(i) = signTmp%at(i) + allocate (imap%exchange_map(ix)%src_idx(src_idx_tmp%size)) + allocate (imap%exchange_map(ix)%tgt_idx(tgt_idx_tmp%size)) + allocate (imap%exchange_map(ix)%sign(sign_tmp%size)) + do i = 1, src_idx_tmp%size + imap%exchange_map(ix)%src_idx(i) = src_idx_tmp%at(i) + imap%exchange_map(ix)%tgt_idx(i) = tgt_idx_tmp%at(i) + imap%exchange_map(ix)%sign(i) = sign_tmp%at(i) end do - call srcIdxTmp%destroy() - call tgtIdxTmp%destroy() - call signTmp%destroy() + call src_idx_tmp%destroy() + call tgt_idx_tmp%destroy() + call sign_tmp%destroy() end do ! set the primary exchange idx ! findloc cannot be used until gfortran 9... - interfaceMap%prim_exg_idx = -1 - do i = 1, interfaceMap%nr_exchanges - if (interfaceMap%exchange_names(i) == this%primaryExchange%name) then - interfaceMap%prim_exg_idx = i + imap%prim_exg_idx = -1 + do i = 1, imap%nr_exchanges + if (imap%exchange_names(i) == this%primaryExchange%name) then + imap%prim_exg_idx = i exit end if end do - end subroutine getInterfaceMap + ! sanity check + ! do i = 1, interfaceMap%nr_models + ! if (size(interfaceMap%node_map(i)%src_idx) == 0) then + ! write(*,*) 'Error: empty node map in interface for ', & + ! this%primaryExchange%name + ! call ustop() + ! end if + ! if (size(interfaceMap%connection_map(i)%src_idx) == 0) then + ! write(*,*) 'Error: empty connection map in interface for ', & + ! this%primaryExchange%name + ! call ustop() + ! end if + ! end do + ! do i = 1, interfaceMap%nr_exchanges + ! if (size(interfaceMap%exchange_map(i)%src_idx) == 0) then + ! write(*,*) 'Error: empty exchange map in interface for ', & + ! this%primaryExchange%name + ! call ustop() + ! end if + ! end do + + end subroutine buildInterfaceMap !> @brief Deallocate grid connection resources !< @@ -1326,8 +1248,8 @@ function isPeriodic(this, n, m) result(periodic) periodic = .false. do icell = 1, this%nrOfBoundaryCells - if (.not. this%boundaryCells(icell)%cell%dmodel == & - this%connectedCells(icell)%cell%dmodel) then + if (.not. this%boundaryCells(icell)%cell%v_model == & + this%connectedCells(icell)%cell%v_model) then cycle end if @@ -1350,28 +1272,4 @@ function isPeriodic(this, n, m) result(periodic) end function - !> @brief Helper function to get model names when ids are given - !< - function get_model_name(id) result(name) - use ConstantsModule, only: LENMODELNAME - use ListsModule, only: basemodellist - use BaseModelModule, only: BaseModelType, GetBaseModelFromList - use MemoryHelperModule, only: create_mem_path - integer(I4B) :: id - character(len=LENMODELNAME) :: name - ! local - class(BaseModelType), pointer :: model - integer(I4B) :: im - - name = '' - do im = 1, basemodellist%Count() - model => GetBaseModelFromList(basemodellist, im) - if (model%id == id) then - name = model%name - return - end if - end do - - end function get_model_name - end module GridConnectionModule diff --git a/src/Model/Connection/GridSorting.f90 b/src/Model/Connection/GridSorting.f90 index dd062730a30..3360b62809d 100644 --- a/src/Model/Connection/GridSorting.f90 +++ b/src/Model/Connection/GridSorting.f90 @@ -36,31 +36,30 @@ function lessThan(n, m) result(isLess) dis_top_m, dis_bot_m real(DP), dimension(:), pointer, contiguous :: dis_xc_n, dis_yc_n, & dis_xc_m, dis_yc_m - real(DP), pointer :: xorigin_n, yorigin_n, angrot_n, & - xorigin_m, yorigin_m, angrot_m + real(DP) :: xorigin_n, yorigin_n, angrot_n, & + xorigin_m, yorigin_m, angrot_m ! get coordinates gcn => idxToGlobal(array(n)) gcm => idxToGlobal(array(m)) - ! load model data - ! TODO_MJR: we should probably cache this + ! get model data ! for n: - call gcn%dmodel%load(dis_top_n, 'TOP', 'DIS') - call gcn%dmodel%load(dis_bot_n, 'BOT', 'DIS') - call gcn%dmodel%load(dis_xc_n, 'XC', 'DIS') - call gcn%dmodel%load(dis_yc_n, 'YC', 'DIS') - call gcn%dmodel%load(xorigin_n, 'XORIGIN', 'DIS') - call gcn%dmodel%load(yorigin_n, 'YORIGIN', 'DIS') - call gcn%dmodel%load(angrot_n, 'ANGROT', 'DIS') + dis_top_n => gcn%v_model%dis_top%get_array() + dis_bot_n => gcn%v_model%dis_bot%get_array() + dis_xc_n => gcn%v_model%dis_xc%get_array() + dis_yc_n => gcn%v_model%dis_yc%get_array() + xorigin_n = gcn%v_model%dis_xorigin%get() + yorigin_n = gcn%v_model%dis_yorigin%get() + angrot_n = gcn%v_model%dis_angrot%get() ! for m: - call gcm%dmodel%load(dis_top_m, 'TOP', 'DIS') - call gcm%dmodel%load(dis_bot_m, 'BOT', 'DIS') - call gcm%dmodel%load(dis_xc_m, 'XC', 'DIS') - call gcm%dmodel%load(dis_yc_m, 'YC', 'DIS') - call gcm%dmodel%load(xorigin_m, 'XORIGIN', 'DIS') - call gcm%dmodel%load(yorigin_m, 'YORIGIN', 'DIS') - call gcm%dmodel%load(angrot_m, 'ANGROT', 'DIS') + dis_top_m => gcm%v_model%dis_top%get_array() + dis_bot_m => gcm%v_model%dis_bot%get_array() + dis_xc_m => gcm%v_model%dis_xc%get_array() + dis_yc_m => gcm%v_model%dis_yc%get_array() + xorigin_m = gcm%v_model%dis_xorigin%get() + yorigin_m = gcm%v_model%dis_yorigin%get() + angrot_m = gcm%v_model%dis_angrot%get() ! convert coordinates call dis_transform_xy(dis_xc_n(gcn%index), dis_yc_n(gcn%index), & diff --git a/src/Model/Connection/GwfGwfConnection.f90 b/src/Model/Connection/GwfGwfConnection.f90 index 2929b21bd82..8d97fa87bca 100644 --- a/src/Model/Connection/GwfGwfConnection.f90 +++ b/src/Model/Connection/GwfGwfConnection.f90 @@ -18,8 +18,9 @@ module GwfGwfConnectionModule use BaseDisModule, only: DisBaseType use ConnectionsModule, only: ConnectionsType use CellWithNbrsModule, only: GlobalCellType - use DistributedDataModule - use MatrixModule + use DistVariableModule + use SimStagesModule + use MatrixBaseModule implicit none private @@ -29,13 +30,16 @@ module GwfGwfConnectionModule !> Connecting a GWF model to other models in space, implements !! NumericalExchangeType so the solution can used this object to determine !! the coefficients for the coupling between two adjacent models. + !! + !! Two connections are created per exchange between model1 and model2: + !! one to manage the coefficients in the matrix rows for model1, and + !! the other to do the same for model2. !< type, public, extends(SpatialModelConnectionType) :: GwfGwfConnectionType type(GwfModelType), pointer :: gwfModel => null() !< the model for which this connection exists type(GwfExchangeType), pointer :: gwfExchange => null() !< the primary exchange, cast to its concrete type - logical(LGP) :: exchangeIsOwned !< there are two connections (in serial) for an exchange, - !! one of them needs to manage/own the exchange (e.g. clean up) + logical(LGP) :: owns_exchange !< when true, this connection has ownership over the exchange (memory) type(GwfInterfaceModelType), pointer :: gwfInterfaceModel => null() !< the interface model integer(I4B), pointer :: iXt3dOnExchange => null() !< run XT3D on the interface, !! 0 = don't, 1 = matrix, 2 = rhs @@ -91,9 +95,13 @@ subroutine gwfGwfConnection_ctor(this, model, gwfEx) objPtr => gwfEx this%gwfExchange => CastAsGwfExchange(objPtr) - this%exchangeIsOwned = associated(gwfEx%model1, model) + if (gwfEx%v_model1%is_local .and. gwfEx%v_model2%is_local) then + this%owns_exchange = (gwfEx%v_model1 == model) + else + this%owns_exchange = .true. + end if - if (this%exchangeIsOwned) then + if (gwfEx%v_model1 == model) then write (name, '(a,i0)') 'GWFCON1_', gwfEx%id else write (name, '(a,i0)') 'GWFCON2_', gwfEx%id @@ -116,10 +124,12 @@ subroutine gwfGwfConnection_ctor(this, model, gwfEx) call this%allocateScalars() this%typename = 'GWF-GWF' - this%iXt3dOnExchange = 0 + + ! determine the required size of the interface grid + call this%setGridExtent() allocate (this%gwfInterfaceModel) - this%interfaceModel => this%gwfInterfaceModel + this%interface_model => this%gwfInterfaceModel end subroutine gwfGwfConnection_ctor @@ -132,9 +142,7 @@ subroutine gwfgwfcon_df(this) class(GwfGwfConnectionType) :: this !< this connection ! local character(len=LENCOMPONENTNAME) :: imName !< the interface model's name - - ! determine the required size of the interface grid - call this%setGridExtent() + integer(I4B) :: i ! this sets up the GridConnection call this%spatialcon_df() @@ -142,19 +150,18 @@ subroutine gwfgwfcon_df(this) ! Now grid conn is defined, we create the interface model ! here, and the remainder of this routine is define. ! we basically follow the logic that is present in sln_df() - if (this%exchangeIsOwned) then + if (this%prim_exchange%v_model1 == this%owner) then write (imName, '(a,i0)') 'GWFIM1_', this%gwfExchange%id else write (imName, '(a,i0)') 'GWFIM2_', this%gwfExchange%id end if - call this%gwfInterfaceModel%gwfifm_cr(imName, this%iout, this%gridConnection) + call this%gwfInterfaceModel%gwfifm_cr(imName, this%iout, this%ig_builder) call this%gwfInterfaceModel%set_idsoln(this%gwfModel%idsoln) this%gwfInterfaceModel%npf%satomega = this%gwfModel%npf%satomega this%gwfInterfaceModel%npf%ixt3d = this%iXt3dOnExchange call this%gwfInterfaceModel%model_df() - ! Take these settings from the owning model, TODO_MJR: - ! what if the owner iangle1 == 0 but the neighbor doesn't? + ! Take these settings from the owning model this%gwfInterfaceModel%npf%ik22 = this%gwfModel%npf%ik22 this%gwfInterfaceModel%npf%ik33 = this%gwfModel%npf%ik33 this%gwfInterfaceModel%npf%iwetdry = this%gwfModel%npf%iwetdry @@ -162,43 +169,30 @@ subroutine gwfgwfcon_df(this) this%gwfInterfaceModel%npf%iangle2 = this%gwfModel%npf%iangle2 this%gwfInterfaceModel%npf%iangle3 = this%gwfModel%npf%iangle3 - call this%addDistVar('X', '', this%gwfInterfaceModel%name, & - SYNC_NODES, '', (/BEFORE_AR, BEFORE_AD, BEFORE_CF/)) - call this%addDistVar('IBOUND', '', this%gwfInterfaceModel%name, & - SYNC_NODES, '', (/BEFORE_AR, BEFORE_AD, BEFORE_CF/)) - call this%addDistVar('XOLD', '', this%gwfInterfaceModel%name, & - SYNC_NODES, '', (/BEFORE_AD, BEFORE_CF/)) - call this%addDistVar('ICELLTYPE', 'NPF', this%gwfInterfaceModel%name, & - SYNC_NODES, '', (/BEFORE_AR/)) - call this%addDistVar('K11', 'NPF', this%gwfInterfaceModel%name, & - SYNC_NODES, '', (/BEFORE_AR/)) - call this%addDistVar('K22', 'NPF', this%gwfInterfaceModel%name, & - SYNC_NODES, '', (/BEFORE_AR/)) - call this%addDistVar('K33', 'NPF', this%gwfInterfaceModel%name, & - SYNC_NODES, '', (/BEFORE_AR/)) + call this%addDistVar('X', '', SYNC_NODES, & + (/STG_BEFORE_AR, STG_BEFORE_AD, STG_BEFORE_CF/)) + call this%addDistVar('IBOUND', '', SYNC_NODES, & + (/STG_BEFORE_AR, STG_BEFORE_AD, STG_BEFORE_CF/)) + call this%addDistVar('XOLD', '', SYNC_NODES, (/STG_BEFORE_AD, STG_BEFORE_CF/)) + call this%addDistVar('ICELLTYPE', 'NPF', SYNC_NODES, (/STG_BEFORE_AR/)) + call this%addDistVar('K11', 'NPF', SYNC_NODES, (/STG_BEFORE_AR/)) + call this%addDistVar('K22', 'NPF', SYNC_NODES, (/STG_BEFORE_AR/)) + call this%addDistVar('K33', 'NPF', SYNC_NODES, (/STG_BEFORE_AR/)) if (this%gwfInterfaceModel%npf%iangle1 == 1) then - call this%addDistVar('ANGLE1', 'NPF', this%gwfInterfaceModel%name, & - SYNC_NODES, '', (/BEFORE_AR/)) + call this%addDistVar('ANGLE1', 'NPF', SYNC_NODES, (/STG_BEFORE_AR/)) end if if (this%gwfInterfaceModel%npf%iangle2 == 1) then - call this%addDistVar('ANGLE2', 'NPF', this%gwfInterfaceModel%name, & - SYNC_NODES, '', (/BEFORE_AR/)) + call this%addDistVar('ANGLE2', 'NPF', SYNC_NODES, (/STG_BEFORE_AR/)) end if if (this%gwfInterfaceModel%npf%iangle3 == 1) then - call this%addDistVar('ANGLE3', 'NPF', this%gwfInterfaceModel%name, & - SYNC_NODES, '', (/BEFORE_AR/)) + call this%addDistVar('ANGLE3', 'NPF', SYNC_NODES, (/STG_BEFORE_AR/)) end if if (this%gwfInterfaceModel%npf%iwetdry == 1) then - call this%addDistVar('WETDRY', 'NPF', this%gwfInterfaceModel%name, & - SYNC_NODES, '', (/BEFORE_AR/)) + call this%addDistVar('WETDRY', 'NPF', SYNC_NODES, (/STG_BEFORE_AR/)) end if - call this%addDistVar('TOP', 'DIS', this%gwfInterfaceModel%name, & - SYNC_NODES, '', (/BEFORE_AR/)) - call this%addDistVar('BOT', 'DIS', this%gwfInterfaceModel%name, & - SYNC_NODES, '', (/BEFORE_AR/)) - call this%addDistVar('AREA', 'DIS', this%gwfInterfaceModel%name, & - SYNC_NODES, '', (/BEFORE_AR/)) - call this%mapVariables() + call this%addDistVar('TOP', 'DIS', SYNC_NODES, (/STG_BEFORE_AR/)) + call this%addDistVar('BOT', 'DIS', SYNC_NODES, (/STG_BEFORE_AR/)) + call this%addDistVar('AREA', 'DIS', SYNC_NODES, (/STG_BEFORE_AR/)) if (this%gwfInterfaceModel%npf%ixt3d > 0) then this%gwfInterfaceModel%npf%iangle1 = 1 @@ -207,10 +201,15 @@ subroutine gwfgwfcon_df(this) end if ! set defaults - ! TODO_MJR: loop this - this%gwfInterfaceModel%npf%angle1 = 0.0_DP - this%gwfInterfaceModel%npf%angle2 = 0.0_DP - this%gwfInterfaceModel%npf%angle3 = 0.0_DP + do i = 1, size(this%gwfInterfaceModel%npf%angle1) + this%gwfInterfaceModel%npf%angle1 = 0.0_DP + end do + do i = 1, size(this%gwfInterfaceModel%npf%angle2) + this%gwfInterfaceModel%npf%angle2 = 0.0_DP + end do + do i = 1, size(this%gwfInterfaceModel%npf%angle3) + this%gwfInterfaceModel%npf%angle3 = 0.0_DP + end do ! point X, RHS, IBOUND to connection call this%spatialcon_setmodelptrs() @@ -228,9 +227,9 @@ subroutine setGridExtent(this) this%iXt3dOnExchange = this%gwfExchange%ixt3d if (this%iXt3dOnExchange > 0) then - this%exchangeStencilDepth = 2 + this%exg_stencil_depth = 2 if (this%gwfModel%npf%ixt3d > 0) then - this%internalStencilDepth = 2 + this%int_stencil_depth = 2 end if end if @@ -266,7 +265,7 @@ subroutine gwfgwfcon_ar(this) call this%gwfInterfaceModel%model_ar() ! AR the movers and obs through the exchange - if (this%exchangeIsOwned) then + if (this%owns_exchange) then if (this%gwfExchange%inmvr > 0) then call this%gwfExchange%mvr%mvr_ar() end if @@ -283,7 +282,7 @@ subroutine gwfgwfcon_rp(this) class(GwfGwfConnectionType) :: this !< this connection ! Call exchange rp routines - if (this%exchangeIsOwned) then + if (this%owns_exchange) then call this%gwfExchange%exg_rp() end if @@ -298,7 +297,7 @@ subroutine gwfgwfcon_ad(this) ! this triggers the BUY density calculation if (this%gwfInterfaceModel%inbuy > 0) call this%gwfInterfaceModel%buy%buy_ad() - if (this%exchangeIsOwned) then + if (this%owns_exchange) then call this%gwfExchange%exg_ad() end if @@ -346,26 +345,27 @@ subroutine gwfgwfcon_fc(this, kiter, matrix_sln, rhs_sln, inwtflag) ! we cannot check with the mask here, because cross-terms are not ! necessarily from primary connections. But, we only need the coefficients ! for our own model (i.e. fluxes into cells belonging to this%owner): - if (.not. this%gridConnection%idxToGlobal(n)%dmodel == this%owner) then + if (.not. this%ig_builder%idxToGlobal(n)%v_model == this%owner) then ! only add connections for own model to global matrix cycle end if - nglo = this%gridConnection%idxToGlobal(n)%index + & - this%gridConnection%idxToGlobal(n)%dmodel%moffset + nglo = this%ig_builder%idxToGlobal(n)%index + & + this%ig_builder%idxToGlobal(n)%v_model%moffset%get() - & + matrix_sln%get_row_offset() rhs_sln(nglo) = rhs_sln(nglo) + this%rhs(n) icol_start = this%matrix%get_first_col_pos(n) icol_end = this%matrix%get_last_col_pos(n) do ipos = icol_start, icol_end - call matrix_sln%add_value_pos(this%mapIdxToSln(ipos), & + call matrix_sln%add_value_pos(this%ipos_to_sln(ipos), & this%matrix%get_value_pos(ipos)) end do end do ! FC the movers through the exchange; we cannot call ! exg_fc() directly because it calculates matrix terms - if (this%exchangeIsOwned) then + if (this%owns_exchange) then if (this%gwfExchange%inmvr > 0) then call this%gwfExchange%mvr%mvr_fc() end if @@ -384,8 +384,8 @@ subroutine validateConnection(this) ! local ! base validation (geometry/spatial) - call this%SpatialModelConnectionType%validateConnection() - call this%validateGwfExchange() + !call this%SpatialModelConnectionType%validateConnection() + !call this%validateGwfExchange() ! abort on errors if (count_errors() > 0) then @@ -496,7 +496,7 @@ subroutine gwfgwfcon_da(this) end if ! we need to deallocate the baseexchange we own: - if (this%exchangeIsOwned) then + if (this%owns_exchange) then call this%gwfExchange%exg_da() end if @@ -528,7 +528,7 @@ subroutine gwfgwfcon_cq(this, icnvg, isuppress_output, isolnid) ! to be done in setNpfEdgeProps, but there was a sign issue ! and flowja was only updated if icalcspdis was 1 (it should ! always be updated. - if (this%exchangeIsOwned) then + if (this%owns_exchange) then call this%gwfExchange%gwf_gwf_add_to_flowja() end if @@ -537,16 +537,16 @@ end subroutine gwfgwfcon_cq !> @brief Set the flows (flowja from interface model) to the !< simvals in the exchange, leaving the budget calcution in there subroutine setFlowToExchange(this) - use InterfaceMapModule + use IndexMapModule class(GwfGwfConnectionType) :: this !< this connection ! local integer(I4B) :: i class(GwfExchangeType), pointer :: gwfEx type(IndexMapSgnType), pointer :: map - if (this%exchangeIsOwned) then + if (this%owns_exchange) then gwfEx => this%gwfExchange - map => this%interfaceMap%exchange_map(this%interfaceMap%prim_exg_idx) + map => this%interface_map%exchange_map(this%interface_map%prim_exg_idx) ! use (half of) the exchange map in reverse: do i = 1, size(map%src_idx) @@ -584,7 +584,7 @@ subroutine setNpfEdgeProps(this) imDis => this%gwfInterfaceModel%dis imCon => this%gwfInterfaceModel%dis%con imNpf => this%gwfInterfaceModel%npf - toGlobal => this%gridConnection%idxToGlobal + toGlobal => this%ig_builder%idxToGlobal nozee = .false. if (imNpf%ixt3d > 0) then @@ -595,7 +595,7 @@ subroutine setNpfEdgeProps(this) ! for flows crossing the boundary, and set flowja for internal ! flows affected by the connection. do n = 1, this%neq - if (.not. toGlobal(n)%dmodel == this%owner) then + if (.not. toGlobal(n)%v_model == this%owner) then ! only add flows to own model cycle end if @@ -611,7 +611,7 @@ subroutine setNpfEdgeProps(this) m = imCon%ja(ipos) mLoc = toGlobal(m)%index - if (.not. toGlobal(m)%dmodel == this%owner) then + if (.not. toGlobal(m)%v_model == this%owner) then ! boundary connection, set edge properties isym = imCon%jas(ipos) ihc = imCon%ihc(isym) @@ -639,7 +639,6 @@ subroutine setNpfEdgeProps(this) nx, ny, dist) else ! internal, need to set flowja for n-m - ! TODO_MJR: should we mask the flowja calculation in the model? iposLoc = getCSRIndex(nLoc, mLoc, this%gwfModel%ia, this%gwfModel%ja) ! update flowja with correct value @@ -661,7 +660,7 @@ subroutine gwfgwfcon_bd(this, icnvg, isuppress_output, isolnid) ! call exchange budget routine, also calls bd ! for movers. - if (this%exchangeIsOwned) then + if (this%owns_exchange) then call this%gwfExchange%exg_bd(icnvg, isuppress_output, isolnid) end if @@ -676,7 +675,7 @@ subroutine gwfgwfcon_ot(this) ! Call exg_ot() here as it handles all output processing ! based on gwfExchange%simvals(:), which was correctly ! filled from gwfgwfcon - if (this%exchangeIsOwned) then + if (this%owns_exchange) then call this%gwfExchange%exg_ot() end if diff --git a/src/Model/Connection/GwfInterfaceModel.f90 b/src/Model/Connection/GwfInterfaceModel.f90 index b8f71e6cdb3..048e39e21ba 100644 --- a/src/Model/Connection/GwfInterfaceModel.f90 +++ b/src/Model/Connection/GwfInterfaceModel.f90 @@ -3,7 +3,7 @@ module GwfInterfaceModelModule use ConstantsModule, only: DZERO use MemoryManagerModule, only: mem_allocate use MemoryHelperModule, only: create_mem_path - use NumericalModelModule, only: NumericalModelType, GetNumericalModelFromList + use NumericalModelModule, only: NumericalModelType use GwfModule, only: GwfModelType, CastAsGwfModel use Xt3dModule, only: xt3d_cr use GwfBuyModule, only: buy_cr diff --git a/src/Model/Connection/GwtGwtConnection.f90 b/src/Model/Connection/GwtGwtConnection.f90 index fcbae658254..e48bb6096f8 100644 --- a/src/Model/Connection/GwtGwtConnection.f90 +++ b/src/Model/Connection/GwtGwtConnection.f90 @@ -13,8 +13,9 @@ module GwtGwtConnectionModule use SparseModule, only: sparsematrix use ConnectionsModule, only: ConnectionsType use CellWithNbrsModule, only: GlobalCellType - use DistributedDataModule - use MatrixModule + use DistVariableModule + use SimStagesModule + use MatrixBaseModule implicit none private @@ -127,7 +128,7 @@ subroutine gwtGwtConnection_ctor(this, model, gwtEx) this%exgflowSign = 1 allocate (this%gwtInterfaceModel) - this%interfaceModel => this%gwtInterfaceModel + this%interface_model => this%gwtInterfaceModel end subroutine gwtGwtConnection_ctor @@ -171,54 +172,38 @@ subroutine gwtgwtcon_df(this) end if call this%gwtInterfaceModel%gwtifmod_cr(imName, & this%iout, & - this%gridConnection) + this%ig_builder) call this%gwtInterfaceModel%set_idsoln(this%gwtModel%idsoln) this%gwtInterfaceModel%iAdvScheme = this%iIfaceAdvScheme this%gwtInterfaceModel%ixt3d = this%iIfaceXt3d call this%gwtInterfaceModel%model_df() - call this%addDistVar('X', '', this%gwtInterfaceModel%name, & - SYNC_NODES, '', (/BEFORE_AR, BEFORE_AD, BEFORE_CF/)) - call this%addDistVar('IBOUND', '', this%gwtInterfaceModel%name, & - SYNC_NODES, '', (/BEFORE_AR/)) - call this%addDistVar('TOP', 'DIS', this%gwtInterfaceModel%name, & - SYNC_NODES, '', (/BEFORE_AR/)) - call this%addDistVar('BOT', 'DIS', this%gwtInterfaceModel%name, & - SYNC_NODES, '', (/BEFORE_AR/)) - call this%addDistVar('AREA', 'DIS', this%gwtInterfaceModel%name, & - SYNC_NODES, '', (/BEFORE_AR/)) + call this%addDistVar('X', '', SYNC_NODES, & + (/STG_BEFORE_AR, STG_BEFORE_AD, STG_BEFORE_CF/)) + call this%addDistVar('IBOUND', '', SYNC_NODES, (/STG_BEFORE_AR/)) + call this%addDistVar('TOP', 'DIS', SYNC_NODES, (/STG_BEFORE_AR/)) + call this%addDistVar('BOT', 'DIS', SYNC_NODES, (/STG_BEFORE_AR/)) + call this%addDistVar('AREA', 'DIS', SYNC_NODES, (/STG_BEFORE_AR/)) if (this%gwtInterfaceModel%dsp%idiffc > 0) then - call this%addDistVar('DIFFC', 'DSP', this%gwtInterfaceModel%name, & - SYNC_NODES, '', (/BEFORE_AR/)) + call this%addDistVar('DIFFC', 'DSP', SYNC_NODES, (/STG_BEFORE_AR/)) end if if (this%gwtInterfaceModel%dsp%idisp > 0) then - call this%addDistVar('ALH', 'DSP', this%gwtInterfaceModel%name, & - SYNC_NODES, '', (/BEFORE_AR/)) - call this%addDistVar('ALV', 'DSP', this%gwtInterfaceModel%name, & - SYNC_NODES, '', (/BEFORE_AR/)) - call this%addDistVar('ATH1', 'DSP', this%gwtInterfaceModel%name, & - SYNC_NODES, '', (/BEFORE_AR/)) - call this%addDistVar('ATH2', 'DSP', this%gwtInterfaceModel%name, & - SYNC_NODES, '', (/BEFORE_AR/)) - call this%addDistVar('ATV', 'DSP', this%gwtInterfaceModel%name, & - SYNC_NODES, '', (/BEFORE_AR/)) + call this%addDistVar('ALH', 'DSP', SYNC_NODES, (/STG_BEFORE_AR/)) + call this%addDistVar('ALV', 'DSP', SYNC_NODES, (/STG_BEFORE_AR/)) + call this%addDistVar('ATH1', 'DSP', SYNC_NODES, (/STG_BEFORE_AR/)) + call this%addDistVar('ATH2', 'DSP', SYNC_NODES, (/STG_BEFORE_AR/)) + call this%addDistVar('ATV', 'DSP', SYNC_NODES, (/STG_BEFORE_AR/)) end if - call this%addDistVar('GWFHEAD', 'FMI', this%gwtInterfaceModel%name, & - SYNC_NODES, '', (/BEFORE_AD/)) - call this%addDistVar('GWFSAT', 'FMI', this%gwtInterfaceModel%name, & - SYNC_NODES, '', (/BEFORE_AD/)) - call this%addDistVar('GWFSPDIS', 'FMI', this%gwtInterfaceModel%name, & - SYNC_NODES, '', (/BEFORE_AD/)) - call this%addDistVar('GWFFLOWJA', 'FMI', this%gwtInterfaceModel%name, & - SYNC_CONNECTIONS, '', (/BEFORE_AD/)) - call this%addDistVar('GWFFLOWJA', 'FMI', this%gwtInterfaceModel%name, & - SYNC_EXCHANGES, 'GWFSIMVALS', (/BEFORE_AD/)) + call this%addDistVar('GWFHEAD', 'FMI', SYNC_NODES, (/STG_BEFORE_AD/)) + call this%addDistVar('GWFSAT', 'FMI', SYNC_NODES, (/STG_BEFORE_AD/)) + call this%addDistVar('GWFSPDIS', 'FMI', SYNC_NODES, (/STG_BEFORE_AD/)) + call this%addDistVar('GWFFLOWJA', 'FMI', SYNC_CONNECTIONS, (/STG_BEFORE_AD/)) + call this%addDistVar('GWFFLOWJA', 'FMI', SYNC_EXCHANGES, (/STG_BEFORE_AD/), & + exg_var_name='GWFSIMVALS') ! fill porosity from mst packages, needed for dsp if (this%gwtModel%indsp > 0 .and. this%gwtModel%inmst > 0) then - call this%addDistVar('POROSITY', 'MST', this%gwtInterfaceModel%name, & - SYNC_NODES, '', (/AFTER_AR/)) + call this%addDistVar('POROSITY', 'MST', SYNC_NODES, (/STG_AFTER_AR/)) end if - call this%mapVariables() call this%allocate_arrays() call this%gwtInterfaceModel%allocate_fmi() @@ -240,7 +225,7 @@ end subroutine gwtgwtcon_df subroutine allocate_arrays(this) class(GwtGwtConnectionType) :: this !< the connection - call mem_allocate(this%exgflowjaGwt, this%gridConnection%nrOfBoundaryCells, & + call mem_allocate(this%exgflowjaGwt, this%ig_builder%nrOfBoundaryCells, & 'EXGFLOWJAGWT', this%memoryPath) end subroutine allocate_arrays @@ -257,18 +242,18 @@ subroutine setGridExtent(this) if (hasAdv) then if (this%iIfaceAdvScheme == 2) then - this%exchangeStencilDepth = 2 + this%exg_stencil_depth = 2 if (this%gwtModel%adv%iadvwt == 2) then - this%internalStencilDepth = 2 + this%int_stencil_depth = 2 end if end if end if if (hasDsp) then if (this%iIfaceXt3d > 0) then - this%exchangeStencilDepth = 2 + this%exg_stencil_depth = 2 if (this%gwtModel%dsp%ixt3d > 0) then - this%internalStencilDepth = 2 + this%int_stencil_depth = 2 end if end if end if @@ -353,11 +338,11 @@ subroutine gwtgwtcon_ac(this, sparse) type(GlobalCellType) :: boundaryCell, connectedCell ! connections to other models - do ic = 1, this%gridConnection%nrOfBoundaryCells - boundaryCell = this%gridConnection%boundaryCells(ic)%cell - connectedCell = this%gridConnection%connectedCells(ic)%cell - iglo = boundaryCell%index + boundaryCell%dmodel%moffset - jglo = connectedCell%index + connectedCell%dmodel%moffset + do ic = 1, this%ig_builder%nrOfBoundaryCells + boundaryCell = this%ig_builder%boundaryCells(ic)%cell + connectedCell = this%ig_builder%connectedCells(ic)%cell + iglo = boundaryCell%index + boundaryCell%v_model%moffset%get() + jglo = connectedCell%index + connectedCell%v_model%moffset%get() call sparse%addconnection(iglo, jglo, 1) call sparse%addconnection(jglo, iglo, 1) end do @@ -425,18 +410,18 @@ subroutine gwtgwtcon_fc(this, kiter, matrix_sln, rhs_sln, inwtflag) do n = 1, this%neq ! We only need the coefficients for our own model ! (i.e. rows in the matrix that belong to this%owner): - if (.not. this%gridConnection%idxToGlobal(n)%dmodel == this%owner) then + if (.not. this%ig_builder%idxToGlobal(n)%v_model == this%owner) then cycle end if - nglo = this%gridConnection%idxToGlobal(n)%index + & - this%gridConnection%idxToGlobal(n)%dmodel%moffset + nglo = this%ig_builder%idxToGlobal(n)%index + & + this%ig_builder%idxToGlobal(n)%v_model%moffset%get() rhs_sln(nglo) = rhs_sln(nglo) + this%rhs(n) icol_start = this%matrix%get_first_col_pos(n) icol_end = this%matrix%get_last_col_pos(n) do ipos = icol_start, icol_end - call matrix_sln%add_value_pos(this%mapIdxToSln(ipos), & + call matrix_sln%add_value_pos(this%ipos_to_sln(ipos), & this%matrix%get_value_pos(ipos)) end do end do @@ -463,7 +448,7 @@ end subroutine gwtgwtcon_cq !> @brief Set the flows (flowja from interface model) to the !< simvals in the exchange, leaving the budget calcution in there subroutine setFlowToExchange(this) - use InterfaceMapModule + use IndexMapModule class(GwtGwtConnectionType) :: this !< this connection ! local integer(I4B) :: i @@ -472,7 +457,7 @@ subroutine setFlowToExchange(this) if (this%exchangeIsOwned) then gwtEx => this%gwtExchange - map => this%interfaceMap%exchange_map(this%interfaceMap%prim_exg_idx) + map => this%interface_map%exchange_map(this%interface_map%prim_exg_idx) ! use (half of) the exchange map in reverse: do i = 1, size(map%src_idx) diff --git a/src/Model/Connection/InterfaceMap.f90 b/src/Model/Connection/InterfaceMap.f90 deleted file mode 100644 index bd4b599fb65..00000000000 --- a/src/Model/Connection/InterfaceMap.f90 +++ /dev/null @@ -1,30 +0,0 @@ -module InterfaceMapModule - use KindModule, only: I4B - use ConstantsModule, only: LENMODELNAME, LENEXCHANGENAME - - implicit none - private - - type, public :: IndexMapType - integer(I4B), dimension(:), pointer :: src_idx - integer(I4B), dimension(:), pointer :: tgt_idx - end type IndexMapType - - type, public :: IndexMapSgnType - integer(I4B), dimension(:), pointer :: src_idx - integer(I4B), dimension(:), pointer :: tgt_idx - integer(I4B), dimension(:), pointer :: sign - end type IndexMapSgnType - - type, public :: InterfaceMapType - integer(I4B) :: nr_models - character(len=LENMODELNAME), dimension(:), pointer :: model_names - integer(I4B) :: nr_exchanges - integer(I4B) :: prim_exg_idx - character(len=LENEXCHANGENAME), dimension(:), pointer :: exchange_names - type(IndexMapType), dimension(:), pointer :: node_map - type(IndexMapType), dimension(:), pointer :: connection_map - type(IndexMapSgnType), dimension(:), pointer :: exchange_map - end type InterfaceMapType - -end module InterfaceMapModule diff --git a/src/Model/Connection/SpatialModelConnection.f90 b/src/Model/Connection/SpatialModelConnection.f90 index 981747a7a1f..82748266198 100644 --- a/src/Model/Connection/SpatialModelConnection.f90 +++ b/src/Model/Connection/SpatialModelConnection.f90 @@ -6,21 +6,26 @@ module SpatialModelConnectionModule use SimModule, only: ustop use NumericalModelModule, only: NumericalModelType use NumericalExchangeModule, only: NumericalExchangeType - use DisConnExchangeModule, only: DisConnExchangeType, GetDisConnExchangeFromList + use DisConnExchangeModule, only: DisConnExchangeType use MemoryManagerModule, only: mem_allocate, mem_deallocate, mem_checkin use MemoryHelperModule, only: create_mem_path use GridConnectionModule, only: GridConnectionType use InterfaceMapModule - use DistributedDataModule + use DistVariableModule + use VirtualDataListsModule, only: virtual_exchange_list + use VirtualModelModule, only: VirtualModelType, get_virtual_model + use VirtualExchangeModule, only: VirtualExchangeType, & + get_virtual_exchange_from_list use ListModule, only: ListType - use MatrixModule + use STLVecIntModule, only: STLVecInt + use MatrixBaseModule use SparseMatrixModule implicit none private - public :: CastAsSpatialModelConnectionClass - public :: AddSpatialModelConnectionToList - public :: GetSpatialModelConnectionFromList + public :: cast_as_smc + public :: add_smc_to_list + public :: get_smc_from_list !> Class to manage spatial connection of a model to one !! or more models of the same type. Spatial connection here @@ -32,15 +37,16 @@ module SpatialModelConnectionModule type, public, extends(NumericalExchangeType) :: SpatialModelConnectionType class(NumericalModelType), pointer :: owner => null() !< the model whose connection this is - class(NumericalModelType), pointer :: interfaceModel => null() !< the interface model - integer(I4B), pointer :: nrOfConnections => null() !< total nr. of connected cells (primary) + class(NumericalModelType), pointer :: interface_model => null() !< the interface model + integer(I4B), pointer :: nr_connections => null() !< total nr. of connected cells (primary) - class(DisConnExchangeType), pointer :: primaryExchange => null() !< the exchange for which the interface model is created - type(ListType) :: globalExchanges !< all exchanges in the same solution - integer(I4B), pointer :: internalStencilDepth => null() !< size of the computational stencil for the interior - !! default = 1, xt3d = 2, ... - integer(I4B), pointer :: exchangeStencilDepth => null() !< size of the computational stencil at the interface - !! default = 1, xt3d = 2, ... + class(DisConnExchangeType), pointer :: prim_exchange => null() !< the exchange for which the interface model is created + type(STLVecInt), pointer :: halo_models !< models that are potentially in the halo of this interface + type(STLVecInt), pointer :: halo_exchanges !< exchanges that are potentially part of the halo of this interface (includes primary) + integer(I4B), pointer :: int_stencil_depth => null() !< size of the computational stencil for the interior + !! default = 1, xt3d = 2, ... + integer(I4B), pointer :: exg_stencil_depth => null() !< size of the computational stencil at the interface + !! default = 1, xt3d = 2, ... ! The following variables are equivalent to those in Numerical Solution: integer(I4B), pointer :: neq => null() !< nr. of equations in matrix system @@ -50,10 +56,10 @@ module SpatialModelConnectionModule integer(I4B), dimension(:), pointer, contiguous :: active => null() !< cell status (c.f. ibound) of interface system ! these are not in the memory manager - class(GridConnectionType), pointer :: gridConnection => null() !< facility to build the interface grid connection structure - integer(I4B), dimension(:), pointer :: mapIdxToSln => null() !< mapping between interface matrix and the solution matrix - type(ListType) :: distVarList !< list with distributed variables - type(InterfaceMapType), pointer :: interfaceMap => null() !< a map of the interface into models and exchanges + class(GridConnectionType), pointer :: ig_builder => null() !< facility to build the interface grid connection structure + integer(I4B), dimension(:), pointer :: ipos_to_sln => null() !< mapping between position in the interface matrix and the solution matrix + type(ListType) :: iface_dist_vars !< list with distributed variables for this interface + type(InterfaceMapType), pointer :: interface_map => null() !< a map of the interface into models and exchanges contains @@ -77,7 +83,7 @@ module SpatialModelConnectionModule procedure, pass(this) :: spatialcon_connect procedure, pass(this) :: validateConnection procedure, pass(this) :: addDistVar - procedure, pass(this) :: mapVariables + procedure, pass(this) :: createModelHalo ! private procedure, private, pass(this) :: setupGridConnection @@ -86,6 +92,7 @@ module SpatialModelConnectionModule procedure, private, pass(this) :: allocateArrays procedure, private, pass(this) :: createCoefficientMatrix procedure, private, pass(this) :: maskOwnerConnections + procedure, private, pass(this) :: addModelNeighbors end type SpatialModelConnectionType @@ -106,36 +113,134 @@ subroutine spatialConnection_ctor(this, model, exchange, name) this%memoryPath = create_mem_path(this%name) this%owner => model - this%primaryExchange => exchange + this%prim_exchange => exchange - allocate (this%gridConnection) + allocate (this%ig_builder) + allocate (this%halo_models) + allocate (this%halo_exchanges) allocate (this%matrix) call this%allocateScalars() - this%internalStencilDepth = 1 - this%exchangeStencilDepth = 1 - this%nrOfConnections = 0 + this%int_stencil_depth = 1 + this%exg_stencil_depth = 1 + this%nr_connections = 0 ! this should be set in derived ctor - this%interfaceModel => null() + this%interface_model => null() end subroutine spatialConnection_ctor + !> @brief Find all models that might participate in this interface + !< + subroutine createModelHalo(this) + class(SpatialModelConnectionType) :: this !< this connection + + call this%halo_models%init() + call this%halo_exchanges%init() + + call this%addModelNeighbors(this%owner%id, virtual_exchange_list, & + this%exg_stencil_depth) + + end subroutine createModelHalo + + !> @brief Add neighbors and nbrs-of-nbrs to the model tree + !< + recursive subroutine addModelNeighbors(this, model_id, & + virtual_exchanges, & + depth, mask) + use VirtualExchangeModule, only: get_virtual_exchange + class(SpatialModelConnectionType) :: this !< this connection + integer(I4B) :: model_id !< the model (id) to add neighbors for + type(ListType) :: virtual_exchanges !< list with all virtual exchanges + integer(I4B), value :: depth !< the maximal number of exchanges between + integer(I4B), optional :: mask !< don't add this one as a neighbor + ! local + integer(I4B) :: i, n + class(VirtualExchangeType), pointer :: v_exg + integer(I4B) :: neighbor_id + integer(I4B) :: model_mask + type(STLVecInt) :: nbr_models + + if (.not. present(mask)) then + model_mask = 0 + else + model_mask = mask + end if + + call nbr_models%init() + + ! first find all direct neighbors of the model and add them, + ! avoiding duplicates + do i = 1, virtual_exchanges%Count() + neighbor_id = -1 + v_exg => get_virtual_exchange_from_list(virtual_exchanges, i) + if (v_exg%v_model1%id == model_id) then + neighbor_id = v_exg%v_model2%id + else if (v_exg%v_model2%id == model_id) then + neighbor_id = v_exg%v_model1%id + end if + + ! check if there is a neighbor, and it is not masked + ! (to prevent back-and-forth connections) + if (neighbor_id > 0) then + + ! check if masked + if (neighbor_id == model_mask) cycle + + if (.not. nbr_models%contains(neighbor_id)) then + call nbr_models%push_back(neighbor_id) + end if + if (.not. this%halo_models%contains(neighbor_id)) then + call this%halo_models%push_back(neighbor_id) + end if + if (.not. this%halo_exchanges%contains(v_exg%id)) then + call this%halo_exchanges%push_back(v_exg%id) + end if + end if + + end do + + depth = depth - 1 + if (depth == 0) then + ! and we're done with this branch + call nbr_models%destroy() + return + end if + + ! now recurse on the neighbors up to the specified depth + do n = 1, nbr_models%size + call this%addModelNeighbors(nbr_models%at(n), virtual_exchanges, & + depth, model_id) + end do + + ! we're done with the tree + call nbr_models%destroy() + + end subroutine addModelNeighbors + !> @brief Define this connection, mostly sets up the grid !< connection, allocates arrays, and links x,rhs, and ibound subroutine spatialcon_df(this) class(SpatialModelConnectionType) :: this !< this connection + ! local + integer(I4B) :: i + class(VirtualModelType), pointer :: v_model ! create the grid connection data structure - this%nrOfConnections = this%getNrOfConnections() - call this%gridConnection%construct(this%owner, & - this%nrOfConnections, & - this%name) - this%gridConnection%internalStencilDepth = this%internalStencilDepth - this%gridConnection%exchangeStencilDepth = this%exchangeStencilDepth + this%nr_connections = this%getNrOfConnections() + call this%ig_builder%construct(this%owner, & + this%nr_connections, & + this%name) + this%ig_builder%internalStencilDepth = this%int_stencil_depth + this%ig_builder%exchangeStencilDepth = this%exg_stencil_depth + this%ig_builder%haloExchanges => this%halo_exchanges + do i = 1, this%halo_models%size + v_model => get_virtual_model(this%halo_models%at(i)) + call this%ig_builder%addToRegionalModels(v_model) + end do call this%setupGridConnection() - this%neq = this%gridConnection%nrOfCells + this%neq = this%ig_builder%nrOfCells call this%allocateArrays() end subroutine spatialcon_df @@ -145,49 +250,37 @@ end subroutine spatialcon_df subroutine spatialcon_ar(this) class(SpatialModelConnectionType) :: this !< this connection ! local - integer(I4B) :: iface_idx + integer(I4B) :: iface_idx, glob_idx class(GridConnectionType), pointer :: gc ! fill mapping to global index (which can be ! done now because moffset is set in sln_df) - gc => this%gridConnection + gc => this%ig_builder do iface_idx = 1, gc%nrOfCells - gc%idxToGlobalIdx(iface_idx) = gc%idxToGlobal(iface_idx)%index + & - gc%idxToGlobal(iface_idx)%dmodel%moffset + glob_idx = gc%idxToGlobal(iface_idx)%index + & + gc%idxToGlobal(iface_idx)%v_model%moffset%get() + gc%idxToGlobalIdx(iface_idx) = glob_idx end do end subroutine spatialcon_ar - !> @brief Map interface variables to the specified - !< source data - subroutine mapVariables(this) - class(SpatialModelConnectionType) :: this !< this connection - - ! map distributed model variables for synchronization - call this%gridConnection%getInterfaceMap(this%interfaceMap) - call distributed_data%map_variables(this%interfaceModel%idsoln, & - this%distVarList, & - this%interfaceMap) - - end subroutine mapVariables - !> @brief set model pointers to connection !< subroutine spatialcon_setmodelptrs(this) class(SpatialModelConnectionType) :: this !< this connection ! point x, ibound, and rhs to connection - this%interfaceModel%x => this%x - call mem_checkin(this%interfaceModel%x, 'X', & - this%interfaceModel%memoryPath, 'X', & + this%interface_model%x => this%x + call mem_checkin(this%interface_model%x, 'X', & + this%interface_model%memoryPath, 'X', & this%memoryPath) - this%interfaceModel%rhs => this%rhs - call mem_checkin(this%interfaceModel%rhs, 'RHS', & - this%interfaceModel%memoryPath, 'RHS', & + this%interface_model%rhs => this%rhs + call mem_checkin(this%interface_model%rhs, 'RHS', & + this%interface_model%memoryPath, 'RHS', & this%memoryPath) - this%interfaceModel%ibound => this%active - call mem_checkin(this%interfaceModel%ibound, 'IBOUND', & - this%interfaceModel%memoryPath, 'IBOUND', & + this%interface_model%ibound => this%active + call mem_checkin(this%interface_model%ibound, 'IBOUND', & + this%interface_model%memoryPath, 'IBOUND', & this%memoryPath) end subroutine spatialcon_setmodelptrs @@ -201,7 +294,7 @@ subroutine spatialcon_connect(this) class(MatrixBaseType), pointer :: matrix_base call sparse%init(this%neq, this%neq, 7) - call this%interfaceModel%model_ac(sparse) + call this%interface_model%model_ac(sparse) ! create amat from sparse call this%createCoefficientMatrix(sparse) @@ -209,7 +302,7 @@ subroutine spatialcon_connect(this) ! map connections matrix_base => this%matrix - call this%interfaceModel%model_mc(matrix_base) + call this%interface_model%model_mc(matrix_base) call this%maskOwnerConnections() end subroutine spatialcon_connect @@ -223,45 +316,45 @@ subroutine maskOwnerConnections(this) use CsrUtilsModule, only: getCSRIndex class(SpatialModelConnectionType) :: this !< the connection ! local - integer(I4B) :: ipos, n, m, nloc, mloc, csrIdx + integer(I4B) :: ipos, n, m, nloc, mloc, csr_idx type(ConnectionsType), pointer :: conn ! set the mask on connections that are calculated by the interface model - conn => this%interfaceModel%dis%con + conn => this%interface_model%dis%con do n = 1, conn%nodes ! only for connections internal to the owning model - if (.not. this%gridConnection%idxToGlobal(n)%dmodel == this%owner) then + if (.not. this%ig_builder%idxToGlobal(n)%v_model == this%owner) then cycle end if - nloc = this%gridConnection%idxToGlobal(n)%index + nloc = this%ig_builder%idxToGlobal(n)%index do ipos = conn%ia(n) + 1, conn%ia(n + 1) - 1 m = conn%ja(ipos) - if (.not. this%gridConnection%idxToGlobal(m)%dmodel == this%owner) then + if (.not. this%ig_builder%idxToGlobal(m)%v_model == this%owner) then cycle end if - mloc = this%gridConnection%idxToGlobal(m)%index + mloc = this%ig_builder%idxToGlobal(m)%index if (conn%mask(ipos) > 0) then ! calculated by interface model, set local model's mask to zero - csrIdx = getCSRIndex(nloc, mloc, this%owner%ia, this%owner%ja) - if (csrIdx == -1) then + csr_idx = getCSRIndex(nloc, mloc, this%owner%ia, this%owner%ja) + if (csr_idx == -1) then ! this can only happen with periodic boundary conditions, ! then there is no need to set the mask - if (this%gridConnection%isPeriodic(nloc, mloc)) cycle + if (this%ig_builder%isPeriodic(nloc, mloc)) cycle write (*, *) 'Error: cannot find cell connection in global system' call ustop() end if - if (this%owner%dis%con%mask(csrIdx) > 0) then - call this%owner%dis%con%set_mask(csrIdx, 0) + if (this%owner%dis%con%mask(csr_idx) > 0) then + call this%owner%dis%con%set_mask(csr_idx, 0) else ! edge case, someone will be calculating this connection - ! so we ignore it here (TODO_MJR: add name) - write (*, *) 'Debug: overlap detected, ignoring connection ', & - nloc, ':', mloc, ' for model ', trim(this%owner%name), & - ' in Exchange ???' + ! so we ignore it here + write (*, *) 'Warning: overlap detected, no mask on connection ', & + nloc, ':', mloc, ' in model ', trim(this%owner%name), & + ' for Exchange ', trim(this%prim_exchange%name) call conn%set_mask(ipos, 0) end if end if @@ -281,21 +374,21 @@ subroutine spatialcon_ac(this, sparse) integer(I4B) :: nglo, mglo do n = 1, this%neq - if (.not. this%gridConnection%idxToGlobal(n)%dmodel == this%owner) then + if (.not. this%ig_builder%idxToGlobal(n)%v_model == this%owner) then ! only add connections for own model to global matrix cycle end if - nglo = this%gridConnection%idxToGlobal(n)%index + & - this%gridConnection%idxToGlobal(n)%dmodel%moffset + nglo = this%ig_builder%idxToGlobal(n)%index + & + this%ig_builder%idxToGlobal(n)%v_model%moffset%get() icol_start = this%matrix%get_first_col_pos(n) icol_end = this%matrix%get_last_col_pos(n) do ipos = icol_start, icol_end m = this%matrix%get_column(ipos) if (m == n) cycle - mglo = this%gridConnection%idxToGlobal(m)%index + & - this%gridConnection%idxToGlobal(m)%dmodel%moffset + mglo = this%ig_builder%idxToGlobal(m)%index + & + this%ig_builder%idxToGlobal(m)%v_model%moffset%get() call sparse%addconnection(nglo, mglo, 1) end do @@ -310,27 +403,33 @@ subroutine spatialcon_mc(this, matrix_sln) class(SpatialModelConnectionType) :: this !< this connection class(MatrixBaseType), pointer :: matrix_sln !< global matrix ! local - integer(I4B) :: m, n, mglo, nglo, ipos, ipos_sln - logical(LGP) :: isOwned + integer(I4B) :: i, m, n, mglo, nglo, ipos, ipos_sln + logical(LGP) :: is_owned - allocate (this%mapIdxToSln(this%matrix%nja)) + allocate (this%ipos_to_sln(this%matrix%nja)) + do i = 1, this%matrix%nja + this%ipos_to_sln(i) = -1 + end do do n = 1, this%neq - isOwned = (this%gridConnection%idxToGlobal(n)%dmodel == this%owner) + is_owned = (this%ig_builder%idxToGlobal(n)%v_model == this%owner) + if (.not. is_owned) cycle + do ipos = this%matrix%ia(n), this%matrix%ia(n + 1) - 1 m = this%matrix%ja(ipos) - nglo = this%gridConnection%idxToGlobal(n)%index + & - this%gridConnection%idxToGlobal(n)%dmodel%moffset - mglo = this%gridConnection%idxToGlobal(m)%index + & - this%gridConnection%idxToGlobal(m)%dmodel%moffset + nglo = this%ig_builder%idxToGlobal(n)%index + & + this%ig_builder%idxToGlobal(n)%v_model%moffset%get() + mglo = this%ig_builder%idxToGlobal(m)%index + & + this%ig_builder%idxToGlobal(m)%v_model%moffset%get() + ipos_sln = matrix_sln%get_position(nglo, mglo) - if (ipos_sln == -1 .and. isOwned) then + if (ipos_sln == -1) then ! this should not be possible write (*, *) 'Error: cannot find cell connection in global system' call ustop() end if + this%ipos_to_sln(ipos) = ipos_sln - this%mapIdxToSln(ipos) = ipos_sln end do end do @@ -342,22 +441,26 @@ subroutine spatialcon_da(this) class(SpatialModelConnectionType) :: this !< this connection call mem_deallocate(this%neq) - call mem_deallocate(this%internalStencilDepth) - call mem_deallocate(this%exchangeStencilDepth) - call mem_deallocate(this%nrOfConnections) + call mem_deallocate(this%int_stencil_depth) + call mem_deallocate(this%exg_stencil_depth) + call mem_deallocate(this%nr_connections) call mem_deallocate(this%x) call mem_deallocate(this%rhs) call mem_deallocate(this%active) + call this%halo_models%destroy() + call this%halo_exchanges%destroy() + deallocate (this%halo_models) + deallocate (this%halo_exchanges) call this%matrix%destroy() deallocate (this%matrix) - call this%gridConnection%destroy() - call this%distVarList%Clear(destroy=.true.) - deallocate (this%gridConnection) - deallocate (this%interfaceMap) - deallocate (this%mapIdxToSln) + call this%ig_builder%destroy() + call this%iface_dist_vars%Clear(destroy=.true.) + deallocate (this%ig_builder) + deallocate (this%interface_map) + deallocate (this%ipos_to_sln) end subroutine spatialcon_da @@ -374,14 +477,14 @@ subroutine setupGridConnection(this) ! local ! connect cells from primary exchange - call this%gridConnection%connectPrimaryExchange(this%primaryExchange) - - ! create topology of models - call this%gridConnection%findModelNeighbors(this%globalExchanges, & - this%exchangeStencilDepth) + call this%ig_builder%connectPrimaryExchange(this%prim_exchange) ! now scan for nbr-of-nbrs and create final data structures - call this%gridConnection%extendConnection() + call this%ig_builder%extendConnection() + + ! construct the interface map + call this%ig_builder%buildInterfaceMap() + this%interface_map => this%ig_builder%interfaceMap end subroutine setupGridConnection @@ -392,9 +495,9 @@ subroutine allocateScalars(this) class(SpatialModelConnectionType) :: this !< this connection call mem_allocate(this%neq, 'NEQ', this%memoryPath) - call mem_allocate(this%internalStencilDepth, 'INTSTDEPTH', this%memoryPath) - call mem_allocate(this%exchangeStencilDepth, 'EXGSTDEPTH', this%memoryPath) - call mem_allocate(this%nrOfConnections, 'NROFCONNS', this%memoryPath) + call mem_allocate(this%int_stencil_depth, 'INTSTDEPTH', this%memoryPath) + call mem_allocate(this%exg_stencil_depth, 'EXGSTDEPTH', this%memoryPath) + call mem_allocate(this%nr_connections, 'NROFCONNS', this%memoryPath) end subroutine allocateScalars @@ -427,7 +530,7 @@ function getNrOfConnections(this) result(nrConns) integer(I4B) :: nrConns !local - nrConns = this%primaryExchange%nexg + nrConns = this%prim_exchange%nexg end function getNrOfConnections @@ -439,7 +542,7 @@ subroutine createCoefficientMatrix(this, sparse) type(sparsematrix), intent(inout) :: sparse !< the sparse matrix with the cell connections call sparse%sort() - call this%matrix%create(sparse, this%memoryPath) + call this%matrix%init(sparse, this%memoryPath) end subroutine createCoefficientMatrix @@ -452,7 +555,7 @@ subroutine validateConnection(this) ! local class(DisConnExchangeType), pointer :: conEx => null() - conEx => this%primaryExchange + conEx => this%prim_exchange if (conEx%ixt3d > 0) then ! if XT3D, we need these angles: if (conEx%model1%dis%con%ianglex == 0) then @@ -471,37 +574,38 @@ subroutine validateConnection(this) end subroutine validateConnection - subroutine addDistVar(this, var_name, subcomp_name, comp_name, & - map_type, exg_var_name, sync_stages) + subroutine addDistVar(this, var_name, subcomp_name, map_type, & + sync_stages, exg_var_name) class(SpatialModelConnectionType) :: this !< this connection character(len=*) :: var_name !< name of variable, e.g. "K11" character(len=*) :: subcomp_name !< subcomponent, e.g. "NPF" - character(len=*) :: comp_name !< component, e.g. the model or exchange name integer(I4B) :: map_type !< can be 0 = scalar, 1 = node based, 2 = connection based, !! 3 = exchange based (connections crossing model boundaries) - character(len=*) :: exg_var_name !< needed for exchange variables, e.g. SIMVALS integer(I4B), dimension(:) :: sync_stages !< when to sync, e.g. (/ STAGE_AD, STAGE_CF /) !! which is before AD and CF + character(len=*), optional :: exg_var_name !< needed for exchange variables, e.g. SIMVALS ! local - type(DistVarType), pointer :: distVar => null() + type(DistVarType), pointer :: dist_var => null() class(*), pointer :: obj - allocate (distVar) - distVar%var_name = var_name - distVar%subcomp_name = subcomp_name - distVar%comp_name = comp_name - distVar%map_type = map_type - distVar%exg_var_name = exg_var_name - distVar%sync_stages = sync_stages + if (.not. present(exg_var_name)) exg_var_name = '' + + allocate (dist_var) + dist_var%var_name = var_name + dist_var%subcomp_name = subcomp_name + dist_var%comp_name = this%interface_model%name + dist_var%map_type = map_type + dist_var%sync_stages = sync_stages + dist_var%exg_var_name = exg_var_name - obj => distVar - call this%distVarList%Add(obj) + obj => dist_var + call this%iface_dist_vars%Add(obj) end subroutine addDistVar !> @brief Cast to SpatialModelConnectionType !< - function CastAsSpatialModelConnectionClass(obj) result(res) + function cast_as_smc(obj) result(res) implicit none class(*), pointer, intent(inout) :: obj !< object to be cast class(SpatialModelConnectionType), pointer :: res !< the instance of SpatialModelConnectionType @@ -514,11 +618,11 @@ function CastAsSpatialModelConnectionClass(obj) result(res) res => obj end select return - end function CastAsSpatialModelConnectionClass + end function cast_as_smc !> @brief Add connection to a list !< - subroutine AddSpatialModelConnectionToList(list, conn) + subroutine add_smc_to_list(list, conn) implicit none ! -- dummy type(ListType), intent(inout) :: list !< the list @@ -530,11 +634,11 @@ subroutine AddSpatialModelConnectionToList(list, conn) call list%Add(obj) ! return - end subroutine AddSpatialModelConnectionToList + end subroutine add_smc_to_list !> @brief Get the connection from a list !< - function GetSpatialModelConnectionFromList(list, idx) result(res) + function get_smc_from_list(list, idx) result(res) type(ListType), intent(inout) :: list !< the list integer(I4B), intent(in) :: idx !< the index of the connection class(SpatialModelConnectionType), pointer :: res !< the returned connection @@ -542,9 +646,9 @@ function GetSpatialModelConnectionFromList(list, idx) result(res) ! local class(*), pointer :: obj obj => list%GetItem(idx) - res => CastAsSpatialModelConnectionClass(obj) + res => cast_as_smc(obj) ! return - end function GetSpatialModelConnectionFromList + end function get_smc_from_list end module SpatialModelConnectionModule diff --git a/src/Model/GroundWaterFlow/gwf3.f90 b/src/Model/GroundWaterFlow/gwf3.f90 index f6d7b52f10f..3b97ba6c14e 100644 --- a/src/Model/GroundWaterFlow/gwf3.f90 +++ b/src/Model/GroundWaterFlow/gwf3.f90 @@ -22,7 +22,7 @@ module GwfModule use GwfObsModule, only: GwfObsType, gwf_obs_cr use SimModule, only: count_errors, store_error use BaseModelModule, only: BaseModelType - use MatrixModule + use MatrixBaseModule implicit none @@ -798,7 +798,7 @@ subroutine gwf_ptc(this, kiter, neqsln, matrix, & diagcnt = DZERO do n = 1, this%dis%nodes if (this%npf%ibound(n) < 1) cycle - ! TODO_MJR: why is this jcol and not jrow? + ! jrow = n + this%moffset ! ! get the maximum volume of the cell (head at top of cell) diff --git a/src/Model/GroundWaterFlow/gwf3api8.f90 b/src/Model/GroundWaterFlow/gwf3api8.f90 index 583d4cff942..f587937545d 100644 --- a/src/Model/GroundWaterFlow/gwf3api8.f90 +++ b/src/Model/GroundWaterFlow/gwf3api8.f90 @@ -17,7 +17,7 @@ module apimodule use ObsModule, only: DefaultObsIdProcessor use TimeSeriesLinkModule, only: TimeSeriesLinkType, & GetTimeSeriesLinkFromList - use MatrixModule + use MatrixBaseModule ! implicit none ! diff --git a/src/Model/GroundWaterFlow/gwf3buy8.f90 b/src/Model/GroundWaterFlow/gwf3buy8.f90 index ca37fe8970c..c23097e22b1 100644 --- a/src/Model/GroundWaterFlow/gwf3buy8.f90 +++ b/src/Model/GroundWaterFlow/gwf3buy8.f90 @@ -13,7 +13,7 @@ module GwfBuyModule use BaseDisModule, only: DisBaseType use GwfNpfModule, only: GwfNpfType use GwfBuyInputDataModule, only: GwfBuyInputDataType - use MatrixModule + use MatrixBaseModule implicit none diff --git a/src/Model/GroundWaterFlow/gwf3chd8.f90 b/src/Model/GroundWaterFlow/gwf3chd8.f90 index f146b455059..5b55207e1cc 100644 --- a/src/Model/GroundWaterFlow/gwf3chd8.f90 +++ b/src/Model/GroundWaterFlow/gwf3chd8.f90 @@ -9,7 +9,7 @@ module ChdModule use ObserveModule, only: ObserveType use TimeSeriesLinkModule, only: TimeSeriesLinkType, & GetTimeSeriesLinkFromList - use MatrixModule + use MatrixBaseModule ! implicit none ! diff --git a/src/Model/GroundWaterFlow/gwf3csub8.f90 b/src/Model/GroundWaterFlow/gwf3csub8.f90 index a6f377bbb40..8b1c8621e26 100644 --- a/src/Model/GroundWaterFlow/gwf3csub8.f90 +++ b/src/Model/GroundWaterFlow/gwf3csub8.f90 @@ -42,7 +42,7 @@ module GwfCsubModule use TableModule, only: TableType, table_cr ! use IMSLinearMisc, only: ims_misc_thomas - use MatrixModule + use MatrixBaseModule ! implicit none ! diff --git a/src/Model/GroundWaterFlow/gwf3drn8.f90 b/src/Model/GroundWaterFlow/gwf3drn8.f90 index 08804ade1e2..0a984df8102 100644 --- a/src/Model/GroundWaterFlow/gwf3drn8.f90 +++ b/src/Model/GroundWaterFlow/gwf3drn8.f90 @@ -9,7 +9,7 @@ module DrnModule use ObsModule, only: DefaultObsIdProcessor use TimeSeriesLinkModule, only: TimeSeriesLinkType, & GetTimeSeriesLinkFromList - use MatrixModule + use MatrixBaseModule ! implicit none ! diff --git a/src/Model/GroundWaterFlow/gwf3evt8.f90 b/src/Model/GroundWaterFlow/gwf3evt8.f90 index f8782013f3f..5283b1bf4b8 100644 --- a/src/Model/GroundWaterFlow/gwf3evt8.f90 +++ b/src/Model/GroundWaterFlow/gwf3evt8.f90 @@ -11,7 +11,7 @@ module EvtModule use TimeSeriesLinkModule, only: TimeSeriesLinkType, & GetTimeSeriesLinkFromList use BlockParserModule, only: BlockParserType - use MatrixModule + use MatrixBaseModule ! implicit none ! diff --git a/src/Model/GroundWaterFlow/gwf3ghb8.f90 b/src/Model/GroundWaterFlow/gwf3ghb8.f90 index b6f1f1dd25e..d3b2b956164 100644 --- a/src/Model/GroundWaterFlow/gwf3ghb8.f90 +++ b/src/Model/GroundWaterFlow/gwf3ghb8.f90 @@ -6,7 +6,7 @@ module ghbmodule use ObsModule, only: DefaultObsIdProcessor use TimeSeriesLinkModule, only: TimeSeriesLinkType, & GetTimeSeriesLinkFromList - use MatrixModule + use MatrixBaseModule ! implicit none ! diff --git a/src/Model/GroundWaterFlow/gwf3hfb8.f90 b/src/Model/GroundWaterFlow/gwf3hfb8.f90 index c555013acfc..72bbac6d6c6 100644 --- a/src/Model/GroundWaterFlow/gwf3hfb8.f90 +++ b/src/Model/GroundWaterFlow/gwf3hfb8.f90 @@ -7,7 +7,7 @@ module GwfHfbModule use NumericalPackageModule, only: NumericalPackageType use BlockParserModule, only: BlockParserType use BaseDisModule, only: DisBaseType - use MatrixModule + use MatrixBaseModule implicit none diff --git a/src/Model/GroundWaterFlow/gwf3lak8.f90 b/src/Model/GroundWaterFlow/gwf3lak8.f90 index 25e4217d56a..654d4dc1f6b 100644 --- a/src/Model/GroundWaterFlow/gwf3lak8.f90 +++ b/src/Model/GroundWaterFlow/gwf3lak8.f90 @@ -29,7 +29,7 @@ module LakModule use BlockParserModule, only: BlockParserType use BaseDisModule, only: DisBaseType use SimVariablesModule, only: errmsg - use MatrixModule + use MatrixBaseModule ! implicit none ! diff --git a/src/Model/GroundWaterFlow/gwf3maw8.f90 b/src/Model/GroundWaterFlow/gwf3maw8.f90 index 7d9cae2bcf3..d7149c5fb47 100644 --- a/src/Model/GroundWaterFlow/gwf3maw8.f90 +++ b/src/Model/GroundWaterFlow/gwf3maw8.f90 @@ -28,7 +28,7 @@ module MawModule use MemoryManagerModule, only: mem_allocate, mem_reallocate, mem_setptr, & mem_deallocate use MemoryHelperModule, only: create_mem_path - use MatrixModule + use MatrixBaseModule ! implicit none diff --git a/src/Model/GroundWaterFlow/gwf3npf8.f90 b/src/Model/GroundWaterFlow/gwf3npf8.f90 index e0ae647985a..3f348e19448 100644 --- a/src/Model/GroundWaterFlow/gwf3npf8.f90 +++ b/src/Model/GroundWaterFlow/gwf3npf8.f90 @@ -18,7 +18,7 @@ module GwfNpfModule use MemoryManagerModule, only: mem_allocate, mem_reallocate, & mem_deallocate, mem_setptr, & mem_reassignptr - use MatrixModule + use MatrixBaseModule implicit none diff --git a/src/Model/GroundWaterFlow/gwf3rch8.f90 b/src/Model/GroundWaterFlow/gwf3rch8.f90 index 5d3596486b5..e1c685cafc8 100644 --- a/src/Model/GroundWaterFlow/gwf3rch8.f90 +++ b/src/Model/GroundWaterFlow/gwf3rch8.f90 @@ -11,7 +11,7 @@ module RchModule use TimeSeriesLinkModule, only: TimeSeriesLinkType, & GetTimeSeriesLinkFromList use BlockParserModule, only: BlockParserType - use MatrixModule + use MatrixBaseModule ! implicit none ! diff --git a/src/Model/GroundWaterFlow/gwf3riv8.f90 b/src/Model/GroundWaterFlow/gwf3riv8.f90 index a64871045d6..8feffb43f43 100644 --- a/src/Model/GroundWaterFlow/gwf3riv8.f90 +++ b/src/Model/GroundWaterFlow/gwf3riv8.f90 @@ -6,7 +6,7 @@ module rivmodule use ObsModule, only: DefaultObsIdProcessor use TimeSeriesLinkModule, only: TimeSeriesLinkType, & GetTimeSeriesLinkFromList - use MatrixModule + use MatrixBaseModule ! implicit none ! diff --git a/src/Model/GroundWaterFlow/gwf3sfr8.f90 b/src/Model/GroundWaterFlow/gwf3sfr8.f90 index 21e1c67e65f..b27964b286c 100644 --- a/src/Model/GroundWaterFlow/gwf3sfr8.f90 +++ b/src/Model/GroundWaterFlow/gwf3sfr8.f90 @@ -37,7 +37,7 @@ module SfrModule get_cross_section_area, & get_mannings_section use dag_module, only: dag - use MatrixModule + use MatrixBaseModule ! implicit none ! diff --git a/src/Model/GroundWaterFlow/gwf3sto8.f90 b/src/Model/GroundWaterFlow/gwf3sto8.f90 index afaa7edf693..63b4051fbca 100644 --- a/src/Model/GroundWaterFlow/gwf3sto8.f90 +++ b/src/Model/GroundWaterFlow/gwf3sto8.f90 @@ -21,7 +21,7 @@ module GwfStoModule use GwfStorageUtilsModule, only: SsCapacity, SyCapacity, SsTerms, SyTerms use InputOutputModule, only: GetUnit, openfile use TvsModule, only: TvsType, tvs_cr - use MatrixModule + use MatrixBaseModule implicit none public :: GwfStoType, sto_cr diff --git a/src/Model/GroundWaterFlow/gwf3uzf8.f90 b/src/Model/GroundWaterFlow/gwf3uzf8.f90 index 5327d5ee779..6dc72bb9ccd 100644 --- a/src/Model/GroundWaterFlow/gwf3uzf8.f90 +++ b/src/Model/GroundWaterFlow/gwf3uzf8.f90 @@ -26,7 +26,7 @@ module UzfModule use SimModule, only: count_errors, store_error, store_error_unit use BlockParserModule, only: BlockParserType use TableModule, only: TableType, table_cr - use MatrixModule + use MatrixBaseModule implicit none diff --git a/src/Model/GroundWaterFlow/gwf3wel8.f90 b/src/Model/GroundWaterFlow/gwf3wel8.f90 index 1f8eca10677..f06e2774b33 100644 --- a/src/Model/GroundWaterFlow/gwf3wel8.f90 +++ b/src/Model/GroundWaterFlow/gwf3wel8.f90 @@ -27,7 +27,7 @@ module WelModule GetTimeSeriesLinkFromList use BlockParserModule, only: BlockParserType use InputOutputModule, only: GetUnit, openfile - use MatrixModule + use MatrixBaseModule ! implicit none ! diff --git a/src/Model/GroundWaterTransport/gwt1.f90 b/src/Model/GroundWaterTransport/gwt1.f90 index aa06b49403d..4197794e6ff 100644 --- a/src/Model/GroundWaterTransport/gwt1.f90 +++ b/src/Model/GroundWaterTransport/gwt1.f90 @@ -25,7 +25,7 @@ module GwtModule use GwtOcModule, only: GwtOcType use GwtObsModule, only: GwtObsType use BudgetModule, only: BudgetType - use MatrixModule + use MatrixBaseModule implicit none diff --git a/src/Model/GroundWaterTransport/gwt1adv1.f90 b/src/Model/GroundWaterTransport/gwt1adv1.f90 index 422a0993cd9..ee3888ee328 100644 --- a/src/Model/GroundWaterTransport/gwt1adv1.f90 +++ b/src/Model/GroundWaterTransport/gwt1adv1.f90 @@ -6,7 +6,7 @@ module GwtAdvModule use BaseDisModule, only: DisBaseType use GwtFmiModule, only: GwtFmiType use GwtAdvOptionsModule, only: GwtAdvOptionsType - use MatrixModule + use MatrixBaseModule implicit none private diff --git a/src/Model/GroundWaterTransport/gwt1apt1.f90 b/src/Model/GroundWaterTransport/gwt1apt1.f90 index f78a4f8b282..b338404b770 100644 --- a/src/Model/GroundWaterTransport/gwt1apt1.f90 +++ b/src/Model/GroundWaterTransport/gwt1apt1.f90 @@ -51,7 +51,7 @@ module GwtAptModule use ObserveModule, only: ObserveType use InputOutputModule, only: extract_idnum_or_bndname use BaseDisModule, only: DisBaseType - use MatrixModule + use MatrixBaseModule implicit none diff --git a/src/Model/GroundWaterTransport/gwt1cnc1.f90 b/src/Model/GroundWaterTransport/gwt1cnc1.f90 index 42d99aeb038..821ba58398a 100644 --- a/src/Model/GroundWaterTransport/gwt1cnc1.f90 +++ b/src/Model/GroundWaterTransport/gwt1cnc1.f90 @@ -8,7 +8,7 @@ module GwtCncModule use ObserveModule, only: ObserveType use TimeSeriesLinkModule, only: TimeSeriesLinkType, & GetTimeSeriesLinkFromList - use MatrixModule + use MatrixBaseModule ! implicit none ! diff --git a/src/Model/GroundWaterTransport/gwt1dsp.f90 b/src/Model/GroundWaterTransport/gwt1dsp.f90 index e052ffd4778..d71316965a8 100644 --- a/src/Model/GroundWaterTransport/gwt1dsp.f90 +++ b/src/Model/GroundWaterTransport/gwt1dsp.f90 @@ -7,7 +7,7 @@ module GwtDspModule use GwtFmiModule, only: GwtFmiType use Xt3dModule, only: Xt3dType, xt3d_cr use GwtDspOptionsModule, only: GwtDspOptionsType - use MatrixModule + use MatrixBaseModule implicit none private diff --git a/src/Model/GroundWaterTransport/gwt1fmi1.f90 b/src/Model/GroundWaterTransport/gwt1fmi1.f90 index fd724dbe244..7dba0f0d801 100644 --- a/src/Model/GroundWaterTransport/gwt1fmi1.f90 +++ b/src/Model/GroundWaterTransport/gwt1fmi1.f90 @@ -12,7 +12,7 @@ module GwtFmiModule use HeadFileReaderModule, only: HeadFileReaderType use PackageBudgetModule, only: PackageBudgetType use BudgetObjectModule, only: BudgetObjectType, budgetobject_cr_bfr - use MatrixModule + use MatrixBaseModule implicit none private diff --git a/src/Model/GroundWaterTransport/gwt1ist1.f90 b/src/Model/GroundWaterTransport/gwt1ist1.f90 index 16dcc77e2fc..71a0ba502c3 100644 --- a/src/Model/GroundWaterTransport/gwt1ist1.f90 +++ b/src/Model/GroundWaterTransport/gwt1ist1.f90 @@ -22,7 +22,7 @@ module GwtIstModule use GwtFmiModule, only: GwtFmiType use GwtMstModule, only: GwtMstType, get_zero_order_decay use OutputControlDataModule, only: OutputControlDataType - use MatrixModule + use MatrixBaseModule ! implicit none ! diff --git a/src/Model/GroundWaterTransport/gwt1lkt1.f90 b/src/Model/GroundWaterTransport/gwt1lkt1.f90 index b7af593604d..338f0675b30 100644 --- a/src/Model/GroundWaterTransport/gwt1lkt1.f90 +++ b/src/Model/GroundWaterTransport/gwt1lkt1.f90 @@ -42,7 +42,7 @@ module GwtLktModule use ObserveModule, only: ObserveType use GwtAptModule, only: GwtAptType, apt_process_obsID, & apt_process_obsID12 - use MatrixModule + use MatrixBaseModule implicit none diff --git a/src/Model/GroundWaterTransport/gwt1mst1.f90 b/src/Model/GroundWaterTransport/gwt1mst1.f90 index f086230d19d..94160af7c38 100644 --- a/src/Model/GroundWaterTransport/gwt1mst1.f90 +++ b/src/Model/GroundWaterTransport/gwt1mst1.f90 @@ -14,7 +14,7 @@ module GwtMstModule use SimVariablesModule, only: errmsg, warnmsg use SimModule, only: store_error, count_errors, & store_warning - use MatrixModule + use MatrixBaseModule use NumericalPackageModule, only: NumericalPackageType use BaseDisModule, only: DisBaseType use GwtFmiModule, only: GwtFmiType diff --git a/src/Model/GroundWaterTransport/gwt1mwt1.f90 b/src/Model/GroundWaterTransport/gwt1mwt1.f90 index 1031ae06671..1dc65492678 100644 --- a/src/Model/GroundWaterTransport/gwt1mwt1.f90 +++ b/src/Model/GroundWaterTransport/gwt1mwt1.f90 @@ -43,7 +43,7 @@ module GwtMwtModule use ObserveModule, only: ObserveType use GwtAptModule, only: GwtAptType, apt_process_obsID, & apt_process_obsID12 - use MatrixModule + use MatrixBaseModule implicit none diff --git a/src/Model/GroundWaterTransport/gwt1sft1.f90 b/src/Model/GroundWaterTransport/gwt1sft1.f90 index 25ee2306bcb..368ce1c2e13 100644 --- a/src/Model/GroundWaterTransport/gwt1sft1.f90 +++ b/src/Model/GroundWaterTransport/gwt1sft1.f90 @@ -41,7 +41,7 @@ module GwtSftModule use ObserveModule, only: ObserveType use GwtAptModule, only: GwtAptType, apt_process_obsID, & apt_process_obsID12 - use MatrixModule + use MatrixBaseModule implicit none diff --git a/src/Model/GroundWaterTransport/gwt1src1.f90 b/src/Model/GroundWaterTransport/gwt1src1.f90 index e372c38d175..1565c40ef09 100644 --- a/src/Model/GroundWaterTransport/gwt1src1.f90 +++ b/src/Model/GroundWaterTransport/gwt1src1.f90 @@ -7,7 +7,7 @@ module GwtSrcModule use TimeSeriesLinkModule, only: TimeSeriesLinkType, & GetTimeSeriesLinkFromList use BlockParserModule, only: BlockParserType - use MatrixModule + use MatrixBaseModule ! implicit none ! diff --git a/src/Model/GroundWaterTransport/gwt1ssm1.f90 b/src/Model/GroundWaterTransport/gwt1ssm1.f90 index d94daa5663c..0238f826bb7 100644 --- a/src/Model/GroundWaterTransport/gwt1ssm1.f90 +++ b/src/Model/GroundWaterTransport/gwt1ssm1.f90 @@ -18,7 +18,7 @@ module GwtSsmModule use GwtFmiModule, only: GwtFmiType use TableModule, only: TableType, table_cr use GwtSpcModule, only: GwtSpcType - use MatrixModule + use MatrixBaseModule implicit none public :: GwtSsmType diff --git a/src/Model/GroundWaterTransport/gwt1uzt1.f90 b/src/Model/GroundWaterTransport/gwt1uzt1.f90 index 9ed4a927f6c..939ee3706d0 100644 --- a/src/Model/GroundWaterTransport/gwt1uzt1.f90 +++ b/src/Model/GroundWaterTransport/gwt1uzt1.f90 @@ -35,7 +35,7 @@ module GwtUztModule use ObserveModule, only: ObserveType use GwtAptModule, only: GwtAptType, apt_process_obsID, & apt_process_obsID12 - use MatrixModule + use MatrixBaseModule implicit none public uzt_create diff --git a/src/Model/ModelUtilities/BoundaryPackage.f90 b/src/Model/ModelUtilities/BoundaryPackage.f90 index cdf5d98b4f8..a7f44c9f025 100644 --- a/src/Model/ModelUtilities/BoundaryPackage.f90 +++ b/src/Model/ModelUtilities/BoundaryPackage.f90 @@ -31,7 +31,7 @@ module BndModule use BlockParserModule, only: BlockParserType use TableModule, only: TableType, table_cr use CharacterStringModule, only: CharacterStringType - use MatrixModule + use MatrixBaseModule implicit none diff --git a/src/Model/ModelUtilities/DiscretizationBase.f90 b/src/Model/ModelUtilities/DiscretizationBase.f90 index 8e77a605987..3dacb29f21e 100644 --- a/src/Model/ModelUtilities/DiscretizationBase.f90 +++ b/src/Model/ModelUtilities/DiscretizationBase.f90 @@ -14,7 +14,7 @@ module BaseDisModule use MemoryHelperModule, only: create_mem_path use TdisModule, only: kstp, kper, pertim, totim, delt use TimeSeriesManagerModule, only: TimeSeriesManagerType - use MatrixModule + use MatrixBaseModule implicit none @@ -636,7 +636,7 @@ subroutine allocate_arrays(this) end if ! ! -- Allocate the arrays - call mem_allocate(this%dbuff, isize, 'DBUFF', this%name_model) ! TODO_MJR: is this correct?? + call mem_allocate(this%dbuff, isize, 'DBUFF', this%name_model) call mem_allocate(this%ibuff, isize, 'IBUFF', this%name_model) ! ! -- Return diff --git a/src/Model/ModelUtilities/Xt3dInterface.f90 b/src/Model/ModelUtilities/Xt3dInterface.f90 index b6ca168f187..df87f773fc3 100644 --- a/src/Model/ModelUtilities/Xt3dInterface.f90 +++ b/src/Model/ModelUtilities/Xt3dInterface.f90 @@ -4,7 +4,7 @@ module Xt3dModule use ConstantsModule, only: DZERO, DHALF, DONE, LENMEMPATH use BaseDisModule, only: DisBaseType use MemoryHelperModule, only: create_mem_path - use MatrixModule + use MatrixBaseModule implicit none public Xt3dType diff --git a/src/Model/NumericalModel.f90 b/src/Model/NumericalModel.f90 index 96b10b221aa..2d46dcd7955 100644 --- a/src/Model/NumericalModel.f90 +++ b/src/Model/NumericalModel.f90 @@ -7,7 +7,7 @@ module NumericalModelModule use SparseModule, only: sparsematrix use TimeArraySeriesManagerModule, only: TimeArraySeriesManagerType use ListModule, only: ListType - use MatrixModule + use MatrixBaseModule implicit none private @@ -300,42 +300,51 @@ subroutine allocate_arrays(this) return end subroutine allocate_arrays - subroutine set_xptr(this, xsln, varNameTgt, memPathTgt) + subroutine set_xptr(this, xsln, sln_offset, varNameTgt, memPathTgt) use MemoryManagerModule, only: mem_checkin ! -- dummy class(NumericalModelType) :: this real(DP), dimension(:), pointer, contiguous, intent(in) :: xsln + integer(I4B) :: sln_offset character(len=*), intent(in) :: varNameTgt character(len=*), intent(in) :: memPathTgt ! -- local + integer(I4B) :: offset ! -- code - this%x => xsln(this%moffset + 1:this%moffset + this%neq) + offset = this%moffset - sln_offset + this%x => xsln(offset + 1:offset + this%neq) call mem_checkin(this%x, 'X', this%memoryPath, varNameTgt, memPathTgt) end subroutine set_xptr - subroutine set_rhsptr(this, rhssln, varNameTgt, memPathTgt) + subroutine set_rhsptr(this, rhssln, sln_offset, varNameTgt, memPathTgt) use MemoryManagerModule, only: mem_checkin ! -- dummy class(NumericalModelType) :: this real(DP), dimension(:), pointer, contiguous, intent(in) :: rhssln + integer(I4B) :: sln_offset character(len=*), intent(in) :: varNameTgt character(len=*), intent(in) :: memPathTgt ! -- local + integer(I4B) :: offset ! -- code - this%rhs => rhssln(this%moffset + 1:this%moffset + this%neq) + offset = this%moffset - sln_offset + this%rhs => rhssln(offset + 1:offset + this%neq) call mem_checkin(this%rhs, 'RHS', this%memoryPath, varNameTgt, memPathTgt) end subroutine set_rhsptr - subroutine set_iboundptr(this, iboundsln, varNameTgt, memPathTgt) + subroutine set_iboundptr(this, iboundsln, sln_offset, varNameTgt, memPathTgt) use MemoryManagerModule, only: mem_checkin ! -- dummy class(NumericalModelType) :: this integer(I4B), dimension(:), pointer, contiguous, intent(in) :: iboundsln + integer(I4B) :: sln_offset character(len=*), intent(in) :: varNameTgt character(len=*), intent(in) :: memPathTgt ! -- local + integer(I4B) :: offset ! -- code - this%ibound => iboundsln(this%moffset + 1:this%moffset + this%neq) + offset = this%moffset - sln_offset + this%ibound => iboundsln(offset + 1:offset + this%neq) call mem_checkin(this%ibound, 'IBOUND', this%memoryPath, varNameTgt, & memPathTgt) end subroutine set_iboundptr diff --git a/src/RunControl.f90 b/src/RunControl.f90 new file mode 100644 index 00000000000..52fd3854bc7 --- /dev/null +++ b/src/RunControl.f90 @@ -0,0 +1,171 @@ +module RunControlModule + use KindModule, only: I4B + use SimStagesModule + use VirtualDataManagerModule + use MapperModule + use ListsModule, only: baseconnectionlist, basesolutionlist + use SpatialModelConnectionModule, only: SpatialModelConnectionType, & + get_smc_from_list + use NumericalSolutionModule, only: NumericalSolutionType + implicit none + private + + public :: create_seq_run_control + + type, public :: RunControlType + class(VirtualDataManagerType), pointer :: virtual_data_store !< contains globally accessible data, timely synchronized + !! by direct linking (local) or message passing (remote) + type(MapperType) :: mapper !< a 'mapper' for filling the interface models: this needs a better name/place + contains + procedure :: start => ctrl_start + procedure :: at_stage => ctrl_at_stage + procedure :: finish => ctrl_finish + ! private + procedure, private :: init_handler + procedure, private :: after_mdl_df_handler + procedure, private :: before_df_handler + procedure, private :: after_df_handler + procedure, private :: before_ar_handler + procedure, private :: after_ar_handler + procedure, private :: destroy + end type RunControlType + +contains + + function create_seq_run_control() result(run_controller) + class(RunControlType), pointer :: run_controller + + allocate (run_controller) + + end function create_seq_run_control + + subroutine ctrl_start(this) + class(RunControlType) :: this + + allocate (this%virtual_data_store) + + end subroutine ctrl_start + + subroutine ctrl_finish(this) + use SimVariablesModule, only: iout + use MemoryManagerModule, only: mem_write_usage, mem_da + use TimerModule, only: elapsed_time + use SimModule, only: final_message + class(RunControlType) :: this + + ! clean up + call this%destroy() + + ! -- Write memory usage, elapsed time and terminate + call mem_write_usage(iout) + call mem_da() + call elapsed_time(iout, 1) + call final_message() + + end subroutine ctrl_finish + + !> @brief This will call the handler for a particular stage + !< in the simulation run + subroutine ctrl_at_stage(this, stage) + class(RunControlType) :: this + integer(I4B) :: stage + + if (stage == STG_INIT) then + call this%init_handler() + else if (stage == STG_AFTER_MDL_DF) then + call this%after_mdl_df_handler() + else if (stage == STG_BEFORE_DF) then + call this%before_df_handler() + else if (stage == STG_AFTER_DF) then + call this%after_df_handler() + else if (stage == STG_BEFORE_AR) then + call this%before_ar_handler() + else if (stage == STG_AFTER_AR) then + call this%after_ar_handler() + end if + + call this%virtual_data_store%synchronize(stage) + call this%mapper%scatter(0, stage) + + end subroutine ctrl_at_stage + + subroutine init_handler(this) + use SimVariablesModule, only: simulation_mode + class(RunControlType), target :: this + + call this%virtual_data_store%create(simulation_mode) + call this%virtual_data_store%init() + call this%mapper%init() + + end subroutine init_handler + + subroutine after_mdl_df_handler(this) + class(RunControlType) :: this + end subroutine after_mdl_df_handler + + subroutine before_df_handler(this) + class(RunControlType), target :: this + ! local + integer(I4B) :: i + class(*), pointer :: obj_ptr + class(NumericalSolutionType), pointer :: sol + + ! Interface models are created now and we know which + ! remote models and exchanges are required in the + ! virtual solution. Also set the synchronization handler + ! to the numerical solutions. + do i = 1, basesolutionlist%Count() + obj_ptr => basesolutionlist%GetItem(i) + select type (obj_ptr) + class is (NumericalSolutionType) + sol => obj_ptr + call this%virtual_data_store%add_solution(sol) + sol%synchronize => rc_solution_sync + sol%synchronize_ctx => this + end select + end do + + call this%mapper%add_exchange_vars() + + end subroutine before_df_handler + + subroutine after_df_handler(this) + class(RunControlType) :: this + + call this%mapper%add_interface_vars() + + end subroutine after_df_handler + + subroutine before_ar_handler(this) + class(RunControlType) :: this + end subroutine before_ar_handler + + subroutine after_ar_handler(this) + class(RunControlType) :: this + end subroutine after_ar_handler + + !> @brief Synchronizes from within numerical solution (delegate) + !< + subroutine rc_solution_sync(num_sol, stage, ctx) + use NumericalSolutionModule, only: NumericalSolutionType + class(NumericalSolutionType) :: num_sol + integer(I4B) :: stage + class(*), pointer :: ctx + + select type (ctx) + class is (RunControlType) + call ctx%virtual_data_store%synchronize_sln(num_sol%id, stage) + call ctx%mapper%scatter(num_sol%id, stage) + end select + + end subroutine rc_solution_sync + + subroutine destroy(this) + class(RunControlType) :: this + + call this%virtual_data_store%destroy() + deallocate (this%virtual_data_store) + + end subroutine destroy + +end module RunControlModule diff --git a/src/RunControlFactory.F90 b/src/RunControlFactory.F90 new file mode 100644 index 00000000000..3f452101c22 --- /dev/null +++ b/src/RunControlFactory.F90 @@ -0,0 +1,39 @@ +module RunControlFactoryModule + use RunControlModule +#if defined(__WITH_MPI__) + use MpiRunControlModule +#endif + use ConstantsModule, only: LINELENGTH + implicit none + private + + public :: create_run_control + +contains + + function create_run_control() result(controller) + use SimModule, only: store_error + use SimVariablesModule, only: simulation_mode + class(RunControlType), pointer :: controller + ! local + character(len=LINELENGTH) :: errmsg + + errmsg = '' +#if defined(__WITH_MPI__) + if (simulation_mode == 'PARALLEL') then + controller => create_mpi_run_control() + else + controller => create_seq_run_control() + end if +#else + if (simulation_mode == 'PARALLEL') then + write (errmsg, '(1x,a)') & + 'ERROR. Can not run parallel mode with this executable: no MPI' + call store_error(errmsg, terminate=.true.) + end if + controller => create_seq_run_control() +#endif + + end function create_run_control + +end module RunControlFactoryModule diff --git a/src/SimulationCreate.f90 b/src/SimulationCreate.f90 index f5d70a1a057..bda9b7efe35 100644 --- a/src/SimulationCreate.f90 +++ b/src/SimulationCreate.f90 @@ -1,8 +1,10 @@ module SimulationCreateModule use KindModule, only: DP, I4B, LGP, write_kindinfo - use ConstantsModule, only: LINELENGTH, LENMODELNAME, LENBIGLINE, DZERO - use SimVariablesModule, only: simfile, simlstfile, iout + use ConstantsModule, only: LINELENGTH, LENMODELNAME, LENBIGLINE, & + DZERO, LENEXCHANGENAME + use SimVariablesModule, only: simfile, simlstfile, iout, simulation_mode, & + proc_id, nr_procs, model_names, model_loc_idx use GenericUtilitiesModule, only: sim_message, write_centered use SimModule, only: store_error, count_errors, & store_error_unit, MaxErrors @@ -14,7 +16,6 @@ module SimulationCreateModule GetBaseSolutionFromList use SolutionGroupModule, only: SolutionGroupType, AddSolutionGroupToList use BaseExchangeModule, only: BaseExchangeType, GetBaseExchangeFromList - use DistributedModelModule, only: add_dist_model use ListsModule, only: basesolutionlist, basemodellist, & solutiongrouplist, baseexchangelist use BaseModelModule, only: GetBaseModelFromList @@ -27,7 +28,6 @@ module SimulationCreateModule public :: simulation_da integer(I4B) :: inunit = 0 - character(len=LENMODELNAME), allocatable, dimension(:) :: modelname type(BlockParserType) :: parser contains @@ -45,6 +45,9 @@ subroutine simulation_cr() ! ! -- Open simulation list file iout = getunit() + if (nr_procs > 1) then + write (simlstfile, '(a,i0,a)') 'mfsim.p', proc_id, '.lst' + end if call openfile(iout, 0, simlstfile, 'LIST', filstat_opt='REPLACE') ! ! -- write simlstfile to stdout @@ -68,7 +71,7 @@ subroutine simulation_da() ! ------------------------------------------------------------------------------ ! ! -- variables - deallocate (modelname) + deallocate (model_names) ! ! -- Return return @@ -250,15 +253,19 @@ subroutine models_create() ! -- modules use GwfModule, only: gwf_cr use GwtModule, only: gwt_cr + use NumericalModelModule, only: NumericalModelType, GetNumericalModelFromList + use VirtualGwfModelModule, only: add_virtual_gwf_model + use VirtualGwtModelModule, only: add_virtual_gwt_model use ConstantsModule, only: LENMODELNAME ! -- dummy ! -- local integer(I4B) :: ierr logical :: isfound, endOfBlock - integer(I4B) :: im + integer(I4B) :: im, id_glo + class(NumericalModelType), pointer :: num_model character(len=LINELENGTH) :: errmsg - character(len=LINELENGTH) :: keyword - character(len=LINELENGTH) :: fname, mname + character(len=LINELENGTH) :: model_type + character(len=LINELENGTH) :: fname, model_name ! ------------------------------------------------------------------------------ ! ! -- Process MODELS block @@ -267,25 +274,57 @@ subroutine models_create() if (isfound) then write (iout, '(/1x,a)') 'READING SIMULATION MODELS' im = 0 + id_glo = 0 do call parser%GetNextLine(endOfBlock) if (endOfBlock) exit - call parser%GetStringCaps(keyword) - select case (keyword) + call parser%GetStringCaps(model_type) + call parser%GetString(fname) + call parser%GetStringCaps(model_name) + + call check_model_name(model_type, model_name) + + ! increment global model id + id_glo = id_glo + 1 + call ExpandArray(model_names) + call ExpandArray(model_loc_idx) + model_names(id_glo) = model_name(1:LENMODELNAME) + model_loc_idx(id_glo) = -1 + + if (nr_procs > 1) then + if (simulation_mode == 'PARALLEL') then + if (model_type == 'GWF6') then + ! for now we assume: model id == rank nr + 1 + if (id_glo /= proc_id + 1) then + call add_virtual_gwf_model(id_glo, model_names(id_glo), null()) + cycle + end if + else + write (errmsg, '(4x,a)') & + '****ERROR. ONLY GWF SUPPORT IN PARALLEL MODE FOR NOW' + call store_error(errmsg) + call parser%StoreErrorUnit() + end if + end if + end if + + ! we will add a new (local) model + im = im + 1 + model_loc_idx(id_glo) = im + + select case (model_type) case ('GWF6') - call parser%GetString(fname) - call add_model(im, 'GWF6', mname) - call gwf_cr(fname, im, modelname(im)) - call add_dist_model(im) + call gwf_cr(fname, id_glo, model_names(id_glo)) + num_model => GetNumericalModelFromList(basemodellist, im) + call add_virtual_gwf_model(id_glo, model_names(id_glo), num_model) case ('GWT6') - call parser%GetString(fname) - call add_model(im, 'GWT6', mname) - call gwt_cr(fname, im, modelname(im)) - call add_dist_model(im) + call gwt_cr(fname, id_glo, model_names(id_glo)) + num_model => GetNumericalModelFromList(basemodellist, im) + call add_virtual_gwt_model(id_glo, model_names(id_glo), num_model) case default write (errmsg, '(4x,a,a)') & '****ERROR. UNKNOWN SIMULATION MODEL: ', & - trim(keyword) + trim(model_type) call store_error(errmsg) call parser%StoreErrorUnit() end select @@ -297,6 +336,14 @@ subroutine models_create() call parser%StoreErrorUnit() end if ! + ! -- sanity check + if (simulation_mode == 'PARALLEL' .and. im == 0) then + write (errmsg, '(4x,a, i0)') & + '****ERROR. No MODELS assigned to process ', proc_id + call store_error(errmsg) + call parser%StoreErrorUnit() + end if + ! ! -- return return end subroutine models_create @@ -308,16 +355,18 @@ subroutine exchanges_create() use GwfGwfExchangeModule, only: gwfexchange_create use GwfGwtExchangeModule, only: gwfgwt_cr use GwtGwtExchangeModule, only: gwtexchange_create + use VirtualGwfExchangeModule, only: add_virtual_gwf_exchange + use VirtualGwtExchangeModule, only: add_virtual_gwt_exchange ! -- dummy ! -- local integer(I4B) :: ierr logical :: isfound, endOfBlock - integer(I4B) :: id - integer(I4B) :: m1 - integer(I4B) :: m2 + integer(I4B) :: exg_id + integer(I4B) :: m1_id, m2_id character(len=LINELENGTH) :: errmsg character(len=LINELENGTH) :: keyword character(len=LINELENGTH) :: fname, name1, name2 + character(len=LENEXCHANGENAME) :: exg_name ! -- formats character(len=*), parameter :: fmtmerr = "('Error in simulation control ', & &'file. Could not find model: ', a)" @@ -326,12 +375,12 @@ subroutine exchanges_create() supportOpenClose=.true.) if (isfound) then write (iout, '(/1x,a)') 'READING SIMULATION EXCHANGES' - id = 0 + exg_id = 0 do call parser%GetNextLine(endOfBlock) if (endOfBlock) exit - id = id + 1 + exg_id = exg_id + 1 call parser%GetStringCaps(keyword) call parser%GetString(fname) @@ -339,29 +388,42 @@ subroutine exchanges_create() call parser%GetStringCaps(name2) ! find model index in list - m1 = ifind(modelname, name1) - if (m1 < 0) then + m1_id = ifind(model_names, name1) + if (m1_id < 0) then write (errmsg, fmtmerr) trim(name1) call store_error(errmsg) call parser%StoreErrorUnit() end if - m2 = ifind(modelname, name2) - if (m2 < 0) then + m2_id = ifind(model_names, name2) + if (m2_id < 0) then write (errmsg, fmtmerr) trim(name2) call store_error(errmsg) call parser%StoreErrorUnit() end if + ! both models on other process? then don't create it here... + if (model_loc_idx(m1_id) == -1 .and. model_loc_idx(m2_id) == -1) then + ! only add virtual + write (exg_name, '(a,i0)') 'GWF-GWF_', exg_id + call add_virtual_gwf_exchange(exg_name, exg_id, m1_id, m2_id) + cycle + end if + write (iout, '(4x,a,a,i0,a,i0,a,i0)') trim(keyword), ' exchange ', & - id, ' will be created to connect model ', m1, ' with model ', m2 + exg_id, ' will be created to connect model ', m1_id, & + ' with model ', m2_id select case (keyword) case ('GWF6-GWF6') - call gwfexchange_create(fname, id, m1, m2) + write (exg_name, '(a,i0)') 'GWF-GWF_', exg_id + call gwfexchange_create(fname, exg_name, exg_id, m1_id, m2_id) + call add_virtual_gwf_exchange(exg_name, exg_id, m1_id, m2_id) case ('GWF6-GWT6') - call gwfgwt_cr(fname, id, m1, m2) + call gwfgwt_cr(fname, exg_id, m1_id, m2_id) case ('GWT6-GWT6') - call gwtexchange_create(fname, id, m1, m2) + write (exg_name, '(a,i0)') 'GWT-GWT_', exg_id + call gwtexchange_create(fname, exg_name, exg_id, m1_id, m2_id) + call add_virtual_gwt_exchange(exg_name, exg_id, m1_id, m2_id) case default write (errmsg, '(4x,a,a)') & '****ERROR. UNKNOWN SIMULATION EXCHANGES: ', & @@ -370,7 +432,9 @@ subroutine exchanges_create() call parser%StoreErrorUnit() end select end do + write (iout, '(1x,a)') 'END OF SIMULATION EXCHANGES' + else call store_error('****ERROR. Did not find EXCHANGES block in '// & 'simulation control file.') @@ -387,10 +451,11 @@ subroutine solution_groups_create() ! -- modules use SolutionGroupModule, only: SolutionGroupType, & solutiongroup_create + use SolutionFactoryModule, only: create_ims_solution use BaseSolutionModule, only: BaseSolutionType use BaseModelModule, only: BaseModelType use BaseExchangeModule, only: BaseExchangeType - use NumericalSolutionModule, only: solution_create + use SimVariablesModule, only: simulation_mode ! -- dummy ! -- local type(SolutionGroupType), pointer :: sgp @@ -402,7 +467,8 @@ subroutine solution_groups_create() integer(I4B) :: isgp integer(I4B) :: isgpsoln integer(I4B) :: sgid - integer(I4B) :: mid + integer(I4B) :: glo_mid + integer(I4B) :: loc_idx logical(LGP) :: blockRequired character(len=LINELENGTH) :: errmsg character(len=LENBIGLINE) :: keyword @@ -469,8 +535,7 @@ subroutine solution_groups_create() ! ! -- Create the solution, retrieve from the list, and add to sgp call parser%GetString(fname) - call solution_create(fname, isoln) - sp => GetBaseSolutionFromList(basesolutionlist, isoln) + sp => create_ims_solution(simulation_mode, fname, isoln) call sgp%add_solution(isoln, sp) ! ! -- Add all of the models that are listed on this line to @@ -483,14 +548,23 @@ subroutine solution_groups_create() if (mname == '') exit ! ! -- Find the model id, and then get model - mid = ifind(modelname, mname) - if (mid <= 0) then - write (errmsg, '(a,a)') 'Error. Invalid modelname: ', & + glo_mid = ifind(model_names, mname) + if (glo_mid == -1) then + write (errmsg, '(a,a)') 'Error. Invalid model name: ', & trim(mname) call store_error(errmsg) call parser%StoreErrorUnit() end if - mp => GetBaseModelFromList(basemodellist, mid) + + loc_idx = model_loc_idx(glo_mid) + if (loc_idx == -1) then + if (simulation_mode == 'PARALLEL') then + ! this is still ok + cycle + end if + end if + + mp => GetBaseModelFromList(basemodellist, loc_idx) ! ! -- Add the model to the solution call sp%add_model(mp) @@ -597,11 +671,10 @@ subroutine assign_exchanges() end do end subroutine assign_exchanges - !> @brief Add the model to the list of modelnames, check that the model name is valid + !> @brief Check that the model name is valid !< - subroutine add_model(im, mtype, mname) + subroutine check_model_name(mtype, mname) ! -- dummy - integer, intent(inout) :: im character(len=*), intent(in) :: mtype character(len=*), intent(inout) :: mname ! -- local @@ -609,9 +682,6 @@ subroutine add_model(im, mtype, mname) integer :: i character(len=LINELENGTH) :: errmsg ! ------------------------------------------------------------------------------ - im = im + 1 - call expandarray(modelname) - call parser%GetStringCaps(mname) ilen = len_trim(mname) if (ilen > LENMODELNAME) then write (errmsg, '(4x,a,a)') & @@ -634,11 +704,9 @@ subroutine add_model(im, mtype, mname) call parser%StoreErrorUnit() end if end do - modelname(im) = mname - write (iout, '(4x,a,i0)') mtype//' model '//trim(mname)// & - ' will be created as model ', im ! ! -- return return - end subroutine add_model + end subroutine check_model_name + end module SimulationCreateModule diff --git a/src/Solution/LinearMethods/ImsLinearSolver.f90 b/src/Solution/LinearMethods/ImsLinearSolver.f90 new file mode 100644 index 00000000000..10a27ca1f6a --- /dev/null +++ b/src/Solution/LinearMethods/ImsLinearSolver.f90 @@ -0,0 +1,65 @@ +module ImsLinearSolverModule + use KindModule, only: I4B + use LinearSolverBaseModule + use MatrixBaseModule + use VectorBaseModule + use SparseMatrixModule + implicit none + private + + public :: create_ims_solver + + type, public, extends(LinearSolverBaseType) :: ImsLinearSolverType + contains + procedure :: initialize => ims_initialize + procedure :: solve => ims_solve + procedure :: get_result => ims_get_result + procedure :: destroy => ims_destroy + + procedure :: create_matrix => ims_create_matrix + end type + +contains + + function create_ims_solver() result(solver) + class(LinearSolverBaseType), pointer :: solver + ! local + class(ImsLinearSolverType), pointer :: ims_solver + + allocate (ims_solver) + solver => ims_solver + + end function create_ims_solver + + subroutine ims_initialize(this, matrix) + class(ImsLinearSolverType) :: this + class(MatrixBaseType), pointer :: matrix + end subroutine ims_initialize + + subroutine ims_solve(this, kiter, rhs, x) + class(ImsLinearSolverType) :: this + integer(I4B) :: kiter + class(VectorBaseType), pointer :: rhs + class(VectorBaseType), pointer :: x + end subroutine ims_solve + + subroutine ims_get_result(this) + class(ImsLinearSolverType) :: this + end subroutine ims_get_result + + subroutine ims_destroy(this) + class(ImsLinearSolverType) :: this + end subroutine ims_destroy + + function ims_create_matrix(this) result(matrix) + class(ImsLinearSolverType) :: this + class(MatrixBaseType), pointer :: matrix + ! local + class(SparseMatrixType), pointer :: ims_matrix + + allocate (ims_matrix) + matrix => ims_matrix + + end function ims_create_matrix + +end module ImsLinearSolverModule diff --git a/src/Solution/LinearMethods/ims8linear.f90 b/src/Solution/LinearMethods/ims8linear.f90 index b8a3b93835f..65a14098452 100644 --- a/src/Solution/LinearMethods/ims8linear.f90 +++ b/src/Solution/LinearMethods/ims8linear.f90 @@ -12,8 +12,7 @@ MODULE IMSLinearModule ims_base_scale, ims_base_pcu, & ims_base_residual use BlockParserModule, only: BlockParserType - use MatrixModule - use SparseMatrixModule + use MatrixBaseModule IMPLICIT NONE private @@ -129,7 +128,6 @@ SUBROUTINE imslinear_ar(this, NAME, parser, IOUT, IPRIMS, MXITER, IFDPARAM, & integer(I4B), INTENT(IN), OPTIONAL :: LFINDBLOCK !< flag indicating if the linear block is present (1) or missing (0) ! -- local variables - class(SparseMatrixType), pointer :: sparse_matrix => null() LOGICAL :: lreaddata character(len=LINELENGTH) :: errmsg character(len=LINELENGTH) :: warnmsg @@ -145,11 +143,6 @@ SUBROUTINE imslinear_ar(this, NAME, parser, IOUT, IPRIMS, MXITER, IFDPARAM, & integer(I4B) :: iwlu integer(I4B) :: iwk ! - select type (matrix) - class is (SparseMatrixType) - sparse_matrix => matrix - end select - ! ! -- SET LREADDATA IF (PRESENT(LFINDBLOCK)) THEN IF (LFINDBLOCK < 1) THEN @@ -167,10 +160,9 @@ SUBROUTINE imslinear_ar(this, NAME, parser, IOUT, IPRIMS, MXITER, IFDPARAM, & ! -- SET POINTERS TO SOLUTION STORAGE this%IPRIMS => IPRIMS this%NEQ => NEQ - this%NJA => sparse_matrix%nja - this%IA => sparse_matrix%ia - this%JA => sparse_matrix%ja - this%AMAT => sparse_matrix%amat + call matrix%get_aij(this%IA, this%JA, this%AMAT) + call mem_allocate(this%NJA, 'NJA', this%memoryPath) + this%NJA = size(this%AMAT) this%RHS => RHS this%X => X ! @@ -780,6 +772,7 @@ subroutine imslinear_da(this) call mem_deallocate(this%njlu) call mem_deallocate(this%njw) call mem_deallocate(this%nwlu) + call mem_deallocate(this%NJA) ! ! -- nullify pointers nullify (this%iprims) diff --git a/src/Solution/LinearSolverBase.f90 b/src/Solution/LinearSolverBase.f90 new file mode 100644 index 00000000000..3bb8566d9c3 --- /dev/null +++ b/src/Solution/LinearSolverBase.f90 @@ -0,0 +1,54 @@ +module LinearSolverBaseModule + use KindModule, only: I4B, DP + use MatrixBaseModule + use VectorBaseModule + implicit none + private + + !> @brief Abstract type for linear solver + !! + !! This serves as the base type for our solvers: + !! sequential, parallel, petsc, block solver, ... + !< + type, public, abstract :: LinearSolverBaseType + integer(I4B) :: nitermax + integer(I4B) :: iteration_number + integer(I4B) :: is_converged + contains + procedure(initialize_if), deferred :: initialize + procedure(solve_if), deferred :: solve + procedure(get_result_if), deferred :: get_result + procedure(destroy_if), deferred :: destroy + + procedure(create_matrix_if), deferred :: create_matrix + end type LinearSolverBaseType + + abstract interface + subroutine initialize_if(this, matrix) + import LinearSolverBaseType, MatrixBaseType + class(LinearSolverBaseType) :: this + class(MatrixBaseType), pointer :: matrix + end subroutine + subroutine solve_if(this, kiter, rhs, x) + import LinearSolverBaseType, I4B, VectorBaseType + class(LinearSolverBaseType) :: this + integer(I4B) :: kiter + class(VectorBaseType), pointer :: rhs + class(VectorBaseType), pointer :: x + end subroutine + subroutine get_result_if(this) + import LinearSolverBaseType + class(LinearSolverBaseType) :: this + end subroutine + subroutine destroy_if(this) + import LinearSolverBaseType + class(LinearSolverBaseType) :: this + end subroutine + function create_matrix_if(this) result(matrix) + import LinearSolverBaseType, MatrixBaseType + class(LinearSolverBaseType) :: this + class(MatrixBaseType), pointer :: matrix + end function + end interface + +end module LinearSolverBaseModule diff --git a/src/Solution/LinearSolverFactory.F90 b/src/Solution/LinearSolverFactory.F90 new file mode 100644 index 00000000000..3a3ce6934b9 --- /dev/null +++ b/src/Solution/LinearSolverFactory.F90 @@ -0,0 +1,41 @@ +module LinearSolverFactory + use SimModule, only: ustop + use LinearSolverBaseModule + use MatrixBaseModule + use SparseMatrixModule + use VectorBaseModule + + use ImsLinearSolverModule +#if defined(__WITH_PETSC__) + use PetscSolverModule, only: create_petsc_solver +#endif + + implicit none + private + + public :: create_linear_solver + +contains + + !> @brief Factory method to create the linear solver object + !< + function create_linear_solver(solver_mode) result(solver) + character(len=*) :: solver_mode + class(LinearSolverBaseType), pointer :: solver + + solver => null() + + if (solver_mode == 'IMS') then + solver => create_ims_solver() + return +#if defined(__WITH_PETSC__) + else if (solver_mode == 'PETSC') then + solver => create_petsc_solver() +#endif + else + call ustop('Unsupported solver mode: '//trim(solver_mode)) + end if + + end function create_linear_solver + +end module LinearSolverFactory diff --git a/src/Solution/NumericalSolution.f90 b/src/Solution/NumericalSolution.f90 index d2c9ad9c75b..97d1131f877 100644 --- a/src/Solution/NumericalSolution.f90 +++ b/src/Solution/NumericalSolution.f90 @@ -28,22 +28,27 @@ module NumericalSolutionModule GetNumericalExchangeFromList use SparseModule, only: sparsematrix use SimVariablesModule, only: iout, isim_mode + use SimStagesModule use BlockParserModule, only: BlockParserType use IMSLinearModule - use DistributedDataModule - use MatrixModule + use MatrixBaseModule + use VectorBaseModule + use LinearSolverBaseModule + use LinearSolverFactory, only: create_linear_solver use SparseMatrixModule + use MatrixBaseModule implicit none private - public :: solution_create public :: NumericalSolutionType public :: GetNumericalSolutionFromList + public :: create_numerical_solution type, extends(BaseSolutionType) :: NumericalSolutionType character(len=LENMEMPATH) :: memoryPath !< the path for storing solution variables in the memory manager character(len=LINELENGTH) :: fname !< input file name + character(len=16) :: solver_mode !< the type of solve: sequential, parallel, mayve block, etc. type(ListType), pointer :: modellist !< list of models in solution type(ListType), pointer :: exchangelist !< list of exchanges in solution integer(I4B), pointer :: id !< solution number @@ -52,9 +57,13 @@ module NumericalSolutionModule real(DP), pointer :: ttsoln !< timer - total solution time integer(I4B), pointer :: isymmetric => null() !< flag indicating if matrix symmetry is required integer(I4B), pointer :: neq => null() !< number of equations + integer(I4B), pointer :: matrix_offset => null() !< offset of linear system when part of distributed solution + class(LinearSolverBaseType), pointer :: linear_solver !< the linear solver for this solution class(MatrixBaseType), pointer :: system_matrix !< sparse A-matrix for the system of equations - real(DP), dimension(:), pointer, contiguous :: rhs => null() !< right-hand side vector - real(DP), dimension(:), pointer, contiguous :: x => null() !< dependent-variable vector + class(VectorBaseType), pointer :: vec_rhs !< the right-hand side vector + class(VectorBaseType), pointer :: vec_x !< the dependent-variable vector + real(DP), dimension(:), pointer, contiguous :: rhs => null() !< right-hand side vector values + real(DP), dimension(:), pointer, contiguous :: x => null() !< dependent-variable vector values integer(I4B), dimension(:), pointer, contiguous :: active => null() !< active cell array real(DP), dimension(:), pointer, contiguous :: xtemp => null() !< temporary vector for previous dependent-variable iterate type(BlockParserType) :: parser !< block parser object @@ -130,6 +139,10 @@ module NumericalSolutionModule ! -- table objects type(TableType), pointer :: innertab => null() !< inner iteration table object type(TableType), pointer :: outertab => null() !< Picard iteration table object + ! + ! -- for synchronization of exchanges + class(*), pointer :: synchronize_ctx => null() + procedure(synchronize_iface), pointer :: synchronize => null() contains procedure :: sln_df @@ -174,47 +187,56 @@ module NumericalSolutionModule end type NumericalSolutionType + abstract interface + subroutine synchronize_iface(solution, stage, ctx) + import NumericalSolutionType + import I4B + class(NumericalSolutionType) :: solution + integer(I4B) :: stage + class(*), pointer :: ctx + end subroutine synchronize_iface + end interface + contains -!> @ brief Create a new solution -!! -!! Create a new solution using the data in filename, assign this new -!! solution an id number and store the solution in the basesolutionlist. -!! Also open the filename for later reading. -!! -!< - subroutine solution_create(filename, id) + !> @ brief Create a new solution + !! + !! Create a new solution using the data in filename, assign this new + !! solution an id number and store the solution in the basesolutionlist. + !! Also open the filename for later reading. + !! + !< + subroutine create_numerical_solution(num_sol, filename, id) ! -- modules use SimVariablesModule, only: iout use InputOutputModule, only: getunit, openfile ! -- dummy variables + class(NumericalSolutionType), pointer :: num_sol !< the create solution character(len=*), intent(in) :: filename !< solution input file name integer(I4B), intent(in) :: id !< solution id ! -- local variables integer(I4B) :: inunit - type(NumericalSolutionType), pointer :: solution => null() class(BaseSolutionType), pointer :: solbase => null() character(len=LENSOLUTIONNAME) :: solutionname class(SparseMatrixType), pointer :: matrix_impl ! ! -- Create a new solution and add it to the basesolutionlist container - allocate (solution) - solbase => solution + solbase => num_sol write (solutionname, '(a, i0)') 'SLN_', id ! - solution%name = solutionname - solution%memoryPath = create_mem_path(solutionname) - allocate (solution%modellist) - allocate (solution%exchangelist) + num_sol%name = solutionname + num_sol%memoryPath = create_mem_path(solutionname) + allocate (num_sol%modellist) + allocate (num_sol%exchangelist) ! allocate (matrix_impl) - solution%system_matrix => matrix_impl + num_sol%system_matrix => matrix_impl ! - call solution%allocate_scalars() + call num_sol%allocate_scalars() ! call AddBaseSolutionToList(basesolutionlist, solbase) ! - solution%id = id + num_sol%id = id ! ! -- Open solution input file for reading later after problem size is known ! Check to see if the file is already opened, which can happen when @@ -222,16 +244,16 @@ subroutine solution_create(filename, id) inquire (file=filename, number=inunit) if (inunit < 0) inunit = getunit() - solution%iu = inunit - write (iout, '(/a,a)') ' Creating solution: ', solution%name - call openfile(solution%iu, iout, filename, 'IMS') + num_sol%iu = inunit + write (iout, '(/a,a)') ' Creating solution: ', num_sol%name + call openfile(num_sol%iu, iout, filename, 'IMS') ! ! -- Initialize block parser - call solution%parser%Initialize(solution%iu, iout) + call num_sol%parser%Initialize(num_sol%iu, iout) ! ! -- return return - end subroutine solution_create + end subroutine create_numerical_solution !> @ brief Allocate scalars !! @@ -251,6 +273,7 @@ subroutine allocate_scalars(this) call mem_allocate(this%ttsoln, 'TTSOLN', this%memoryPath) call mem_allocate(this%isymmetric, 'ISYMMETRIC', this%memoryPath) call mem_allocate(this%neq, 'NEQ', this%memoryPath) + call mem_allocate(this%matrix_offset, 'MATRIX_OFFSET', this%memoryPath) call mem_allocate(this%dvclose, 'DVCLOSE', this%memoryPath) call mem_allocate(this%bigchold, 'BIGCHOLD', this%memoryPath) call mem_allocate(this%bigch, 'BIGCH', this%memoryPath) @@ -356,8 +379,6 @@ subroutine allocate_arrays(this) this%convnmod = this%modellist%Count() ! ! -- allocate arrays - call mem_allocate(this%x, this%neq, 'X', this%memoryPath) - call mem_allocate(this%rhs, this%neq, 'RHS', this%memoryPath) call mem_allocate(this%active, this%neq, 'IACTIVE', this%memoryPath) call mem_allocate(this%xtemp, this%neq, 'XTEMP', this%memoryPath) call mem_allocate(this%dxold, this%neq, 'DXOLD', this%memoryPath) @@ -384,7 +405,6 @@ subroutine allocate_arrays(this) ! ! -- initialize allocated arrays do i = 1, this%neq - this%x(i) = DZERO this%xtemp(i) = DZERO this%dxold(i) = DZERO this%active(i) = 1 !default is active @@ -421,30 +441,59 @@ end subroutine allocate_arrays subroutine sln_df(this) ! modules use MemoryManagerModule, only: mem_allocate + use SimVariablesModule, only: simulation_mode ! -- dummy variables class(NumericalSolutionType) :: this !< NumericalSolutionType instance ! -- local variables class(NumericalModelType), pointer :: mp => null() integer(I4B) :: i integer(I4B), allocatable, dimension(:) :: rowmaxnnz + integer(I4B) :: ncol, irow_start, irow_end + integer(I4B) :: mod_offset ! - ! -- calculate and set offsets + ! -- set sol id and determine nr. of equation in this solution do i = 1, this%modellist%Count() mp => GetNumericalModelFromList(this%modellist, i) call mp%set_idsoln(this%id) - call mp%set_moffset(this%neq) this%neq = this%neq + mp%neq end do ! + ! -- set up the (possibly parallel) linear system + if (simulation_mode == 'PARALLEL') then + this%solver_mode = 'PETSC' + else + this%solver_mode = 'IMS' + end if + ! + this%linear_solver => create_linear_solver(this%solver_mode) + this%system_matrix => this%linear_solver%create_matrix() + this%vec_x => this%system_matrix%create_vector(this%neq, 'X', this%memoryPath) + this%x => this%vec_x%get_array() + this%vec_rhs => this%system_matrix%create_vector(this%neq, 'RHS', & + this%memoryPath) + this%rhs => this%vec_rhs%get_array() + ! + call this%vec_rhs%get_ownership_range(irow_start, irow_end) + ncol = this%vec_rhs%get_size() + ! + ! -- calculate and set offsets + mod_offset = irow_start - 1 + this%matrix_offset = irow_start - 1 + do i = 1, this%modellist%Count() + mp => GetNumericalModelFromList(this%modellist, i) + call mp%set_moffset(mod_offset) + mod_offset = mod_offset + mp%neq + end do + ! ! -- Allocate and initialize solution arrays call this%allocate_arrays() ! ! -- Go through each model and point x, ibound, and rhs to solution do i = 1, this%modellist%Count() mp => GetNumericalModelFromList(this%modellist, i) - call mp%set_xptr(this%x, 'X', this%name) - call mp%set_rhsptr(this%rhs, 'RHS', this%name) - call mp%set_iboundptr(this%active, 'IBOUND', this%name) + call mp%set_xptr(this%x, this%matrix_offset, 'X', this%name) + call mp%set_rhsptr(this%rhs, this%matrix_offset, 'RHS', this%name) + call mp%set_iboundptr(this%active, this%matrix_offset, 'IBOUND', this%name) end do ! ! -- Create the sparsematrix instance @@ -452,7 +501,8 @@ subroutine sln_df(this) do i = 1, this%neq rowmaxnnz(i) = 4 end do - call this%sparse%init(this%neq, this%neq, rowmaxnnz) + call this%sparse%init(this%neq, ncol, rowmaxnnz) + this%sparse%offset = this%matrix_offset deallocate (rowmaxnnz) ! ! -- Assign connections, fill ia/ja, map connections @@ -486,7 +536,7 @@ subroutine sln_ar(this) integer(I4B) :: i integer(I4B) :: im integer(I4B) :: ifdparam, mxvl, npp - integer(I4B) :: imslinear + integer(I4B) :: ims_lin_type integer(I4B) :: ierr logical :: isfound, endOfBlock integer(I4B) :: ival @@ -852,6 +902,11 @@ subroutine sln_ar(this) call store_error(errmsg) end if end if + + if (this%solver_mode == 'PETSC') then + this%linmeth = 2 + end if + ! ! -- call secondary subroutine to initialize and read linear ! solver parameters IMSLINEAR solver @@ -860,20 +915,24 @@ subroutine sln_ar(this) WRITE (IOUT, *) '***IMS LINEAR SOLVER WILL BE USED***' call this%imslinear%imslinear_allocate(this%name, this%parser, IOUT, & this%iprims, this%mxiter, & - ifdparam, imslinear, & + ifdparam, ims_lin_type, & this%neq, this%system_matrix, & this%rhs, this%x, this%nitermax) - WRITE (IOUT, *) - if (imslinear .eq. 1) then + if (ims_lin_type .eq. 1) then this%isymmetric = 1 end if ! ! -- incorrect linear solver flag + else if (this%linmeth == 2) then + call this%linear_solver%initialize(this%system_matrix) + this%nitermax = this%linear_solver%nitermax + this%isymmetric = 0 ELSE WRITE (errmsg, '(a)') & 'INCORRECT VALUE FOR LINEAR SOLUTION METHOD SPECIFIED.' call store_error(errmsg) END IF + ! ! -- write message about matrix symmetry if (this%isymmetric == 1) then @@ -946,7 +1005,9 @@ subroutine sln_ar(this) /1X, 'BACKTRACKING RESIDUAL LIMIT (RES_LIM) = ', E15.6) ! ! -- linear solver data - call this%imslinear%imslinear_summary(this%mxiter) + if (this%linmeth == 1) then + call this%imslinear%imslinear_summary(this%mxiter) + end if ! -- write summary of solver error messages ierr = count_errors() @@ -1111,7 +1172,7 @@ subroutine sln_fp(this) class(NumericalSolutionType) :: this !< NumericalSolutionType instance ! ! -- write timer output - if (IDEVELOPMODE == 1) then + if (IDEVELOPMODE == 1 .and. this%linmeth == 1) then write (this%imslinear%iout, '(//1x,a,1x,a,1x,a)') & 'Solution', trim(adjustl(this%name)), 'summary' write (this%imslinear%iout, "(1x,70('-'))") @@ -1137,16 +1198,24 @@ subroutine sln_da(this) class(NumericalSolutionType) :: this !< NumericalSolutionType instance ! ! -- IMSLinearModule - call this%imslinear%imslinear_da() - deallocate (this%imslinear) + if (this%linmeth == 1) then + call this%imslinear%imslinear_da() + deallocate (this%imslinear) + end if ! ! -- lists call this%modellist%Clear() call this%exchangelist%Clear() deallocate (this%modellist) deallocate (this%exchangelist) + call this%system_matrix%destroy() deallocate (this%system_matrix) + call this%vec_x%destroy() + deallocate (this%vec_x) + call this%vec_rhs%destroy() + deallocate (this%vec_rhs) + ! ! -- character arrays deallocate (this%caccel) @@ -1166,8 +1235,6 @@ subroutine sln_da(this) end if ! ! -- arrays - call mem_deallocate(this%x) - call mem_deallocate(this%rhs) call mem_deallocate(this%active) call mem_deallocate(this%xtemp) call mem_deallocate(this%dxold) @@ -1194,6 +1261,7 @@ subroutine sln_da(this) call mem_deallocate(this%ttsoln) call mem_deallocate(this%isymmetric) call mem_deallocate(this%neq) + call mem_deallocate(this%matrix_offset) call mem_deallocate(this%dvclose) call mem_deallocate(this%bigchold) call mem_deallocate(this%bigch) @@ -1399,7 +1467,7 @@ subroutine prepareSolve(this) class(NumericalModelType), pointer :: mp => null() ! synchronize for AD - call distributed_data%synchronize(this%id, BEFORE_AD) + call this%synchronize(STG_BEFORE_AD, this%synchronize_ctx) ! -- Exchange advance do ic = 1, this%exchangelist%Count() @@ -1708,7 +1776,7 @@ subroutine solve(this, kiter) locmax_nur = 0 do im = 1, this%modellist%Count() mp => GetNumericalModelFromList(this%modellist, im) - i0 = mp%moffset + 1 + i0 = mp%moffset + 1 - this%matrix_offset i1 = i0 + mp%neq - 1 call mp%model_nur(mp%neq, this%x(i0:i1), this%xtemp(i0:i1), & this%dxold(i0:i1), inewtonur, dxmax_nur, locmax_nur) @@ -1896,7 +1964,7 @@ subroutine sln_buildsystem(this, kiter, inewton) call this%sln_reset() ! synchronize for CF - call distributed_data%synchronize(this%id, BEFORE_CF) + call this%synchronize(STG_BEFORE_CF, this%synchronize_ctx) ! ! -- Calculate the matrix terms for each exchange @@ -1912,7 +1980,7 @@ subroutine sln_buildsystem(this, kiter, inewton) end do ! synchronize for FC - call distributed_data%synchronize(this%id, BEFORE_FC) + call this%synchronize(STG_BEFORE_FC, this%synchronize_ctx) ! ! -- Add exchange coefficients to the solution @@ -2278,6 +2346,9 @@ subroutine sln_connect(this) call mp%model_ac(this%sparse) end do ! + ! -- synchronize before AC + call this%synchronize(STG_BEFORE_AC, this%synchronize_ctx) + ! ! -- Add the cross terms to sparse do ic = 1, this%exchangelist%Count() cp => GetNumericalExchangeFromList(this%exchangelist, ic) @@ -2287,7 +2358,7 @@ subroutine sln_connect(this) ! -- The number of non-zero array values are now known so ! -- ia and ja can be created from sparse. then destroy sparse call this%sparse%sort() - call this%system_matrix%create(this%sparse, this%name) + call this%system_matrix%init(this%sparse, this%name) call this%sparse%destroy() ! ! -- Create mapping arrays for each model. Mapping assumes @@ -2317,14 +2388,10 @@ end subroutine sln_connect subroutine sln_reset(this) ! -- dummy variables class(NumericalSolutionType) :: this !< NumericalSolutionType instance - ! -- local variables - integer(I4B) :: i ! ! -- reset the solution call this%system_matrix%zero_entries() - do i = 1, this%neq - this%rhs(i) = DZERO - end do + call this%vec_rhs%zero_entries() ! ! -- return return @@ -2347,7 +2414,8 @@ subroutine sln_ls(this, kiter, kstp, kper, in_iter, iptc, ptcf) real(DP), intent(in) :: ptcf ! -- local variables logical :: lsame - integer(I4B) :: n + integer(I4B) :: ieq + integer(I4B) :: irow integer(I4B) :: itestmat integer(I4B) :: ipos integer(I4B) :: icol_s @@ -2366,41 +2434,47 @@ subroutine sln_ls(this, kiter, kstp, kper, in_iter, iptc, ptcf) &'_', i0, '_', i0, '.txt')" ! ! -- take care of loose ends for all nodes before call to solver - do n = 1, this%neq + do ieq = 1, this%neq + ! + ! -- get (global) cell id + irow = ieq + this%matrix_offset ! ! -- store x in temporary location - this%xtemp(n) = this%x(n) + this%xtemp(ieq) = this%x(ieq) ! ! -- make adjustments to the continuity equation for the node ! -- adjust small diagonal coefficient in an active cell - if (this%active(n) > 0) then + if (this%active(ieq) > 0) then diagval = -DONE - adiag = abs(this%system_matrix%get_diag_value(n)) + adiag = abs(this%system_matrix%get_diag_value(irow)) if (adiag < DEM15) then - call this%system_matrix%set_diag_value(n, diagval) - this%rhs(n) = this%rhs(n) + diagval * this%x(n) + call this%system_matrix%set_diag_value(irow, diagval) + this%rhs(ieq) = this%rhs(ieq) + diagval * this%x(ieq) end if ! -- Dirichlet boundary or no-flow cell else - call this%system_matrix%set_diag_value(n, DONE) - call this%system_matrix%zero_row_offdiag(n) - this%rhs(n) = this%x(n) + call this%system_matrix%set_diag_value(irow, DONE) + call this%system_matrix%zero_row_offdiag(irow) + this%rhs(ieq) = this%x(ieq) end if end do ! ! -- complete adjustments for Dirichlet boundaries for a symmetric matrix if (this%isymmetric == 1) then - do n = 1, this%neq - if (this%active(n) > 0) then - icol_s = this%system_matrix%get_first_col_pos(n) - icol_e = this%system_matrix%get_last_col_pos(n) + do ieq = 1, this%neq + ! + ! -- get (global) row number + irow = ieq + this%matrix_offset + if (this%active(ieq) > 0) then + icol_s = this%system_matrix%get_first_col_pos(irow) + icol_e = this%system_matrix%get_last_col_pos(irow) do ipos = icol_s, icol_e jcol = this%system_matrix%get_column(ipos) - if (jcol == n) cycle - if (this%active(jcol) < 0) then - this%rhs(n) = this%rhs(n) - & - (this%system_matrix%get_value_pos(ipos) * & - this%x(jcol)) + if (jcol == irow) cycle + if (this%active(jcol - this%matrix_offset) < 0) then + this%rhs(ieq) = this%rhs(ieq) - & + (this%system_matrix%get_value_pos(ipos) * & + this%x(jcol - this%matrix_offset)) call this%system_matrix%set_value_pos(ipos, DZERO) end if @@ -2466,9 +2540,9 @@ subroutine sln_ls(this, kiter, kstp, kper, in_iter, iptc, ptcf) this%ptcdel = DONE / ptcf else bnorm = DZERO - do n = 1, this%neq - if (this%active(n) .gt. 0) then - bnorm = bnorm + this%rhs(n) * this%rhs(n) + do ieq = 1, this%neq + if (this%active(ieq) .gt. 0) then + bnorm = bnorm + this%rhs(ieq) * this%rhs(ieq) end if end do bnorm = sqrt(bnorm) @@ -2489,13 +2563,14 @@ subroutine sln_ls(this, kiter, kstp, kper, in_iter, iptc, ptcf) end if diagmin = DEP20 bnorm = DZERO - do n = 1, this%neq - if (this%active(n) > 0) then - diagval = abs(this%system_matrix%get_diag_value(n)) - bnorm = bnorm + this%rhs(n) * this%rhs(n) + do ieq = 1, this%neq + irow = ieq + this%matrix_offset + if (this%active(ieq) > 0) then + diagval = abs(this%system_matrix%get_diag_value(irow)) + bnorm = bnorm + this%rhs(ieq) * this%rhs(ieq) if (diagval < diagmin) diagmin = diagval - call this%system_matrix%add_diag_value(n, -ptcval) - this%rhs(n) = this%rhs(n) - ptcval * this%x(n) + call this%system_matrix%add_diag_value(irow, -ptcval) + this%rhs(ieq) = this%rhs(ieq) - ptcval * this%x(ieq) end if end do bnorm = sqrt(bnorm) @@ -2518,12 +2593,13 @@ subroutine sln_ls(this, kiter, kstp, kper, in_iter, iptc, ptcf) itestmat = getunit() open (itestmat, file=trim(adjustl(fname))) write (itestmat, *) 'NODE, RHS, AMAT FOLLOW' - do n = 1, this%neq - icol_s = this%system_matrix%get_first_col_pos(n) - icol_e = this%system_matrix%get_last_col_pos(n) + do ieq = 1, this%neq + irow = ieq + this%matrix_offset + icol_s = this%system_matrix%get_first_col_pos(irow) + icol_e = this%system_matrix%get_last_col_pos(irow) write (itestmat, '(*(G0,:,","))') & - n, & - this%rhs(n), & + irow, & + this%rhs(ieq), & (this%system_matrix%get_column(ipos), ipos=icol_s, icol_e), & (this%system_matrix%get_value_pos(ipos), ipos=icol_s, icol_e) end do @@ -2544,6 +2620,10 @@ subroutine sln_ls(this, kiter, kstp, kper, in_iter, iptc, ptcf) this%convlocdv, this%convlocdr, & this%dvmax, this%drmax, & this%convdvmax, this%convdrmax) + else if (this%linmeth == 2) then + call this%linear_solver%solve(kiter, this%vec_rhs, this%vec_x) + in_iter = this%linear_solver%iteration_number + this%icnvg = this%linear_solver%is_converged end if ! ! -- ptc finalize - set ratio of ptc value added to the diagonal and the diff --git a/src/Solution/PETSc/PetscConvergence.F90 b/src/Solution/PETSc/PetscConvergence.F90 new file mode 100644 index 00000000000..27f95311f4e --- /dev/null +++ b/src/Solution/PETSc/PetscConvergence.F90 @@ -0,0 +1,57 @@ +module PetscConvergenceModule +#include + use petscksp + use KindModule, only: I4B, DP + implicit none + private + + public :: petsc_check_convergence + + type, public :: PetscContextType + Vec :: x_old + Vec :: delta_x + real(DP) :: dvclose + end type PetscContextType + +contains + + subroutine petsc_check_convergence(ksp, n, rnorm, flag, petsc_context, ierr) + KSP :: ksp !< Iterative context + PetscInt :: n !< Iteration number + PetscReal :: rnorm !< 2-norm (preconditioned) residual value + KSPConvergedReason :: flag !< Converged reason + class(PetscContextType), pointer :: petsc_context !< optional user-defined monitor context + PetscErrorCode :: ierr !< error + ! local + PetscScalar :: alpha = -1.0 + real(DP) :: norm + Vec :: x + + call KSPBuildSolution(ksp, PETSC_NULL_VEC, x, ierr) + CHKERRQ(ierr) + + if (n == 0) then + call VecCopy(x, petsc_context%x_old, ierr) + CHKERRQ(ierr) + flag = KSP_CONVERGED_ITERATING + return + end if + + call VecWAXPY(petsc_context%delta_x, alpha, x, petsc_context%x_old, ierr) + CHKERRQ(ierr) + + call VecNorm(petsc_context%delta_x, NORM_INFINITY, norm, ierr) + CHKERRQ(ierr) + + call VecCopy(x, petsc_context%x_old, ierr) + CHKERRQ(ierr) + + if (norm < petsc_context%dvclose) then + flag = KSP_CONVERGED_HAPPY_BREAKDOWN ! Converged + else + flag = KSP_CONVERGED_ITERATING ! Not yet converged + end if + + end subroutine petsc_check_convergence + +end module PetscConvergenceModule diff --git a/src/Solution/PETSc/PetscSolver.F90 b/src/Solution/PETSc/PetscSolver.F90 new file mode 100644 index 00000000000..b03c8aa9724 --- /dev/null +++ b/src/Solution/PETSc/PetscSolver.F90 @@ -0,0 +1,231 @@ +module PetscSolverModule +#include + use petscksp + use KindModule, only: I4B, DP, LGP + use LinearSolverBaseModule + use MatrixBaseModule + use VectorBaseModule + use PetscMatrixModule + use PetscVectorModule + use PetscConvergenceModule + + implicit none + private + + public :: create_petsc_solver + + type, public, extends(LinearSolverBaseType) :: PetscSolverType + KSP :: ksp_petsc + class(PetscMatrixType), pointer :: matrix + Mat, pointer :: mat_petsc + + integer(I4B) :: lin_accel_type + real(DP) :: dvclose + class(PetscContextType), pointer :: petsc_ctx + contains + procedure :: initialize => petsc_initialize + procedure :: solve => petsc_solve + procedure :: get_result => petsc_get_result + procedure :: destroy => petsc_destroy + procedure :: create_matrix => petsc_create_matrix + + ! private + procedure, private :: get_options + procedure, private :: create_ksp + procedure, private :: create_convergence_check + procedure, private :: print_vec + end type PetscSolverType + +contains + + !> @brief Create a PETSc solver object + !< + function create_petsc_solver() result(solver) + class(LinearSolverBaseType), pointer :: solver !< Uninitialized instance of the PETSc solver + ! local + class(PetscSolverType), pointer :: petsc_solver + + allocate (petsc_solver) + allocate (petsc_solver%petsc_ctx) + + solver => petsc_solver + + end function create_petsc_solver + + !> @brief Initialize PETSc KSP solver with + !< options from the petsc database file + subroutine petsc_initialize(this, matrix) + class(PetscSolverType) :: this !< This solver instance + class(MatrixBaseType), pointer :: matrix !< The solution matrix as KSP operator + + this%mat_petsc => null() + select type (pm => matrix) + class is (PetscMatrixType) + this%matrix => pm + this%mat_petsc => pm%mat + end select + + ! get options from PETSc database file + call this%get_options() + + ! create the solver object + call this%create_ksp() + + ! Create custom convergence check + call this%create_convergence_check() + + end subroutine petsc_initialize + + !> @brief Get the PETSc options from the database + !< + subroutine get_options(this) + class(PetscSolverType) :: this + ! local + PetscErrorCode :: ierr + logical(LGP) :: found + + this%dvclose = 0.01_DP + call PetscOptionsGetReal(PETSC_NULL_OPTIONS, PETSC_NULL_CHARACTER, & + '-dvclose', this%dvclose, found, ierr) + CHKERRQ(ierr) + + this%nitermax = 100 + call PetscOptionsGetInt(PETSC_NULL_OPTIONS, PETSC_NULL_CHARACTER, & + '-nitermax', this%nitermax, found, ierr) + CHKERRQ(ierr) + + end subroutine get_options + + !> @brief Create the PETSc KSP object + !< + subroutine create_ksp(this) + class(PetscSolverType) :: this !< This solver instance + ! local + PetscErrorCode :: ierr + + call KSPCreate(PETSC_COMM_WORLD, this%ksp_petsc, ierr) + CHKERRQ(ierr) + + call KSPSetInitialGuessNonzero(this%ksp_petsc, .true., ierr) + CHKERRQ(ierr) + + call KSPSetFromOptions(this%ksp_petsc, ierr) + CHKERRQ(ierr) + + call KSPSetOperators(this%ksp_petsc, this%mat_petsc, this%mat_petsc, ierr) + CHKERRQ(ierr) + + end subroutine create_ksp + + !> @brief Create and assign a custom convergence + !< check for this solver + subroutine create_convergence_check(this) + class(PetscSolverType) :: this !< This solver instance + ! local + PetscErrorCode :: ierr + + this%petsc_ctx%dvclose = this%dvclose + call MatCreateVecs( & + this%mat_petsc, this%petsc_ctx%x_old, PETSC_NULL_VEC, ierr) + CHKERRQ(ierr) + call MatCreateVecs( & + this%mat_petsc, this%petsc_ctx%delta_x, PETSC_NULL_VEC, ierr) + CHKERRQ(ierr) + + call KSPSetConvergenceTest(this%ksp_petsc, petsc_check_convergence, & + this%petsc_ctx, PETSC_NULL_FUNCTION, ierr) + CHKERRQ(ierr) + + end subroutine create_convergence_check + + subroutine petsc_solve(this, kiter, rhs, x) + class(PetscSolverType) :: this + integer(I4B) :: kiter + class(VectorBaseType), pointer :: rhs + class(VectorBaseType), pointer :: x + ! local + PetscErrorCode :: ierr + class(PetscVectorType), pointer :: rhs_petsc, x_petsc + KSPConvergedReason :: icnvg + + rhs_petsc => null() + select type (rhs) + class is (PetscVectorType) + rhs_petsc => rhs + end select + + x_petsc => null() + select type (x) + class is (PetscVectorType) + x_petsc => x + end select + + this%iteration_number = 0 + this%is_converged = 0 + + ! update matrix coefficients + call this%matrix%update() + call KSPSolve(this%ksp_petsc, rhs_petsc%vec_impl, x_petsc%vec_impl, ierr) + CHKERRQ(ierr) + + call KSPGetIterationNumber(this%ksp_petsc, this%iteration_number, ierr) + call KSPGetConvergedReason(this%ksp_petsc, icnvg, ierr) + if (icnvg > 0) this%is_converged = 1 + + end subroutine petsc_solve + + subroutine petsc_get_result(this) + class(PetscSolverType) :: this + end subroutine petsc_get_result + + subroutine petsc_destroy(this) + class(PetscSolverType) :: this + ! local + PetscErrorCode :: ierr + + call KSPDestroy(this%ksp_petsc, ierr) + CHKERRQ(ierr) + + ! delete context + call VecDestroy(this%petsc_ctx%delta_x, ierr) + CHKERRQ(ierr) + call VecDestroy(this%petsc_ctx%x_old, ierr) + CHKERRQ(ierr) + deallocate (this%petsc_ctx) + + end subroutine petsc_destroy + + function petsc_create_matrix(this) result(matrix) + class(PetscSolverType) :: this + class(MatrixBaseType), pointer :: matrix + ! local + class(PetscMatrixType), pointer :: petsc_matrix + + allocate (petsc_matrix) + matrix => petsc_matrix + + end function petsc_create_matrix + + subroutine print_vec(this, vec, vec_name, kiter) + use TdisModule, only: nper, kstp + class(PetscSolverType) :: this + class(PetscVectorType) :: vec + character(len=*) :: vec_name + integer(I4B) :: kiter + ! local + PetscViewer :: viewer + character(len=24) :: filename + PetscErrorCode :: ierr + + write (filename, '(2a,i0,a,i0,a,i0,a)') vec_name, '_', nper, & + '_', kstp, '_', kiter, '.txt' + call PetscViewerASCIIOpen(PETSC_COMM_WORLD, filename, viewer, ierr) + CHKERRQ(ierr) + call VecView(vec%vec_impl, viewer, ierr) + CHKERRQ(ierr) + call PetscViewerDestroy(viewer, ierr) + CHKERRQ(ierr) + + end subroutine print_vec + +end module PetscSolverModule diff --git a/src/Solution/ParallelSolution.f90 b/src/Solution/ParallelSolution.f90 new file mode 100644 index 00000000000..f5438c9d867 --- /dev/null +++ b/src/Solution/ParallelSolution.f90 @@ -0,0 +1,11 @@ +module ParallelSolutionModule + use NumericalSolutionModule, only: NumericalSolutionType + implicit none + private + + public :: ParallelSolutionType + + type, extends(NumericalSolutionType) :: ParallelSolutionType + end type ParallelSolutionType + +end module ParallelSolutionModule diff --git a/src/Solution/SolutionFactory.F90 b/src/Solution/SolutionFactory.F90 new file mode 100644 index 00000000000..ef7a7b550fc --- /dev/null +++ b/src/Solution/SolutionFactory.F90 @@ -0,0 +1,48 @@ +module SolutionFactoryModule + use KindModule, only: I4B + use SimModule, only: ustop + use BaseSolutionModule + use NumericalSolutionModule, only: NumericalSolutionType, & + create_numerical_solution +#if defined(__WITH_MPI__) + use ParallelSolutionModule, only: ParallelSolutionType +#endif + + implicit none + private + + public :: create_ims_solution + +contains + + !> @brief Create an IMS solution of type NumericalSolution + !! for serial runs or its sub-type ParallelSolution for + !< parallel runs. Returns the base pointer. + function create_ims_solution(sim_mode, filename, sol_id) result(base_sol) + character(len=*) :: sim_mode + character(len=*) :: filename + integer(I4B) :: sol_id + class(BaseSolutionType), pointer :: base_sol + class(NumericalSolutionType), pointer :: num_sol => null() +#if defined(__WITH_MPI__) + class(ParallelSolutionType), pointer :: par_sol => null() +#endif + + if (sim_mode == 'SEQUENTIAL') then + allocate (num_sol) +#if defined(__WITH_MPI__) + else if (sim_mode == 'PARALLEL') then + allocate (par_sol) + num_sol => par_sol +#endif + else + call ustop('Unsupported simulation mode for creating solution: '& + &//trim(sim_mode)) + end if + + call create_numerical_solution(num_sol, filename, sol_id) + base_sol => num_sol + + end function create_ims_solution + +end module diff --git a/src/Utilities/ArrayHandlers.f90 b/src/Utilities/ArrayHandlers.f90 index 7759fb1f9ab..329917244d1 100644 --- a/src/Utilities/ArrayHandlers.f90 +++ b/src/Utilities/ArrayHandlers.f90 @@ -6,6 +6,7 @@ module ArrayHandlersModule use GenericUtilitiesModule, only: sim_message, stop_with_error private public :: ExpandArray, ExpandArrayWrapper, ExtendPtrArray + public :: ConcatArray public :: ifind public :: remove_character @@ -24,7 +25,12 @@ module ArrayHandlersModule interface ExtendPtrArray ! This interface is for use with POINTERS to arrays. - module procedure extend_double, extend_integer + module procedure extend_double, extend_integer, & + extend_string + end interface + + interface ConcatArray + module procedure concat_integer end interface interface ifind @@ -338,6 +344,51 @@ subroutine extend_integer(array, increment) end subroutine extend_integer + !> @brief Grows or allocated the array with the passed increment, + !< the old value of the array pointer is rendered invalid + subroutine extend_string(array, increment) + character(len=*), dimension(:), pointer, contiguous :: array + integer(I4B), optional :: increment + ! local + integer(I4B) :: inc_local + integer(I4B) :: i, old_size, new_size + character(len=len(array)), dimension(:), pointer, contiguous :: temp_array + + if (present(increment)) then + inc_local = increment + else + inc_local = 1 + end if + + if (associated(array)) then + old_size = size(array) + new_size = old_size + inc_local + temp_array => array + allocate (array(new_size)) + do i = 1, old_size + array(i) = temp_array(i) + end do + deallocate (temp_array) + else + allocate (array(inc_local)) + end if + + end subroutine extend_string + + subroutine concat_integer(array, array_to_add) + integer(I4B), dimension(:), pointer, contiguous :: array + integer(I4B), dimension(:), pointer, contiguous :: array_to_add + ! local + integer(I4B) :: i, old_size + + old_size = size(array) + call ExtendPtrArray(array, increment=size(array_to_add)) + do i = 1, size(array_to_add) + array(old_size + i) = array_to_add(i) + end do + + end subroutine concat_integer + function ifind_character(array, str) ! -- Find the first array element containing str ! -- Return -1 if not found. diff --git a/src/Utilities/Idm/LoadMf6FileType.f90 b/src/Utilities/Idm/LoadMf6FileType.f90 index 24ff10d6e33..4971c75df29 100644 --- a/src/Utilities/Idm/LoadMf6FileType.f90 +++ b/src/Utilities/Idm/LoadMf6FileType.f90 @@ -116,13 +116,12 @@ subroutine parse_block(parser, mf6_input, iblock, mshape, iout) type(MemoryType), pointer :: mt ! ! -- disu vertices/cell2d blocks are contingent on NVERT dimension - if (mf6_input%file_type == 'DISU6') then - if (mf6_input%p_block_dfns(iblock)%blockname == 'VERTICES' .or. & - mf6_input%p_block_dfns(iblock)%blockname == 'CELL2D') then - call get_from_memorylist('NVERT', mf6_input%memoryPath, mt, found, & - .false.) - if (.not. found .or. mt%intsclr == 0) return - end if + if (mf6_input%file_type == 'DISU6' .and. & + (mf6_input%p_block_dfns(iblock)%blockname == 'VERTICES' .or. & + mf6_input%p_block_dfns(iblock)%blockname == 'CELL2D')) then + call get_from_memorylist('NVERT', mf6_input%memoryPath, mt, found, .false.) + if (.not. found) return + if (mt%intsclr == 0) return end if ! ! -- block open/close support diff --git a/src/Utilities/Idm/StructArray.f90 b/src/Utilities/Idm/StructArray.f90 index a57a2e211a0..0e9884b8514 100644 --- a/src/Utilities/Idm/StructArray.f90 +++ b/src/Utilities/Idm/StructArray.f90 @@ -12,7 +12,7 @@ module StructArrayModule use StructVectorModule, only: StructVectorType use MemoryManagerModule, only: mem_allocate use CharacterStringModule, only: CharacterStringType - use VectorIntModule, only: VectorInt + use STLVecIntModule, only: STLVecInt use IdmLoggerModule, only: idm_log_var use MemoryManagerModule, only: mem_setptr use BlockParserModule, only: BlockParserType @@ -84,7 +84,7 @@ subroutine mem_create_vector(this, icol, vartype, name, memoryPath, & integer(I4B), dimension(:), pointer, contiguous :: int1d real(DP), dimension(:), pointer, contiguous :: dbl1d type(CharacterStringType), dimension(:), pointer, contiguous :: cstr1d - type(VectorInt), pointer :: intvector + type(STLVecInt), pointer :: intvector integer(I4B) :: j integer(I4B) :: inodata = 999 !todo: create INODATA in constants? @@ -166,7 +166,7 @@ subroutine add_vector_str1d(this, icol, str1d, preserve_case) return end subroutine add_vector_str1d - !> @brief add VectorInt to StructArrayType + !> @brief add STLVecInt to StructArrayType !< subroutine add_vector_intvector(this, varname, memoryPath, varname_shape, & icol, intvector) @@ -175,7 +175,7 @@ subroutine add_vector_intvector(this, varname, memoryPath, varname_shape, & character(len=*), intent(in) :: memoryPath !< memory path to vector character(len=*), intent(in) :: varname_shape !< shape of variable integer(I4B), intent(in) :: icol !< column of the vector - type(VectorInt), pointer, intent(in) :: intvector !< vector to add + type(STLVecInt), pointer, intent(in) :: intvector !< vector to add type(StructVectorType) :: sv call intvector%init() diff --git a/src/Utilities/Idm/StructVector.f90 b/src/Utilities/Idm/StructVector.f90 index 60aa546255f..20e4e81724f 100644 --- a/src/Utilities/Idm/StructVector.f90 +++ b/src/Utilities/Idm/StructVector.f90 @@ -9,7 +9,7 @@ module StructVectorModule use KindModule, only: I4B, DP, LGP use ConstantsModule, only: LENMEMPATH, LENVARNAME use CharacterStringModule, only: CharacterStringType - use VectorIntModule, only: VectorInt + use STLVecIntModule, only: STLVecInt implicit none private @@ -30,7 +30,7 @@ module StructVectorModule real(DP), dimension(:), pointer, contiguous :: dbl1d => null() type(CharacterStringType), dimension(:), pointer, contiguous :: & str1d => null() - type(VectorInt), pointer :: intvector => null() + type(STLVecInt), pointer :: intvector => null() integer(I4B), dimension(:), pointer, contiguous :: intvector_shape => null() end type StructVectorType diff --git a/src/Utilities/Matrix/MatrixBase.f90 b/src/Utilities/Matrix/MatrixBase.f90 index 4b7de87ed29..001ef8e5b19 100644 --- a/src/Utilities/Matrix/MatrixBase.f90 +++ b/src/Utilities/Matrix/MatrixBase.f90 @@ -1,15 +1,17 @@ -module MatrixModule +module MatrixBaseModule use ConstantsModule, only: LENMEMPATH use KindModule, only: I4B, DP use SparseModule, only: sparsematrix + use VectorBaseModule implicit none private type, public, abstract :: MatrixBaseType character(len=LENMEMPATH) :: memory_path contains - procedure(create_if), deferred :: create + procedure(init_if), deferred :: init procedure(destroy_if), deferred :: destroy + procedure(create_vector_if), deferred :: create_vector procedure(get_value_pos_if), deferred :: get_value_pos procedure(get_diag_value_if), deferred :: get_diag_value @@ -27,10 +29,13 @@ module MatrixModule procedure(get_position_if), deferred :: get_position procedure(get_position_diag_if), deferred :: get_position_diag + procedure(get_aij_if), deferred :: get_aij + procedure(get_row_offset_if), deferred :: get_row_offset + end type MatrixBaseType abstract interface - subroutine create_if(this, sparse, mem_path) + subroutine init_if(this, sparse, mem_path) import MatrixBaseType, sparsematrix class(MatrixBaseType) :: this type(sparsematrix) :: sparse @@ -40,7 +45,14 @@ subroutine destroy_if(this) import MatrixBaseType class(MatrixBaseType) :: this end subroutine - + function create_vector_if(this, n, name, mem_path) result(vec) + import MatrixBaseType, VectorBaseType, I4B + class(MatrixBaseType) :: this + integer(I4B) :: n + character(len=*) :: name + character(len=*) :: mem_path + class(VectorBaseType), pointer :: vec + end function function get_value_pos_if(this, ipos) result(value) import MatrixBaseType, I4B, DP class(MatrixBaseType) :: this @@ -105,10 +117,6 @@ function get_column_if(this, ipos) result(icol) integer(I4B) :: ipos integer(I4B) :: icol end function - - !> @brief Get position index for this (irow,icol) element - !! in the matrix for direct access with the other routines - !< Returns -1 when not found. function get_position_if(this, irow, icol) result(ipos) import MatrixBaseType, I4B class(MatrixBaseType) :: this @@ -122,6 +130,18 @@ function get_position_diag_if(this, irow) result(ipos_diag) integer(I4B) :: irow integer(I4B) :: ipos_diag end function + subroutine get_aij_if(this, ia, ja, amat) + import MatrixBaseType, I4B, DP + class(MatrixBaseType) :: this + integer(I4B), dimension(:), pointer, contiguous :: ia + integer(I4B), dimension(:), pointer, contiguous :: ja + real(DP), dimension(:), pointer, contiguous :: amat + end subroutine + function get_row_offset_if(this) result(offset) + import MatrixBaseType, I4B + class(MatrixBaseType) :: this + integer(I4B) :: offset + end function end interface -end module MatrixModule +end module MatrixBaseModule diff --git a/src/Utilities/Matrix/PetscMatrix.F90 b/src/Utilities/Matrix/PetscMatrix.F90 new file mode 100644 index 00000000000..7b2ed516db4 --- /dev/null +++ b/src/Utilities/Matrix/PetscMatrix.F90 @@ -0,0 +1,344 @@ +module PetscMatrixModule +#include + use petscksp + use KindModule, only: I4B, DP, LGP + use ConstantsModule, only: DZERO + use SparseModule, only: sparsematrix + use MemoryManagerModule, only: mem_allocate, mem_deallocate + use MatrixBaseModule + use VectorBaseModule + use PetscVectorModule + implicit none + private + + type, public, extends(MatrixBaseType) :: PetscMatrixType + Mat :: mat + ! offset in the global matrix + integer(I4B) :: nrow + integer(I4B) :: ncol + integer(I4B) :: nnz + integer(I4B) :: offset + integer(I4B), dimension(:), pointer, contiguous :: ia_petsc !< IA(CSR) for petsc, contains 0-based index values + integer(I4B), dimension(:), pointer, contiguous :: ja_petsc !< JA(CSR) for petsc, contains 0-based index values + real(DP), dimension(:), pointer, contiguous :: amat_petsc !< A(CSR) for petsc + logical(LGP) :: is_parallel + contains + ! override + procedure :: init => pm_init + procedure :: destroy => pm_destroy + procedure :: create_vector => pm_create_vector + procedure :: get_value_pos => pm_get_value_pos + procedure :: get_diag_value => pm_get_diag_value + procedure :: set_diag_value => pm_set_diag_value + procedure :: set_value_pos => pm_set_value_pos + procedure :: add_value_pos => pm_add_value_pos + procedure :: add_diag_value => pm_add_diag_value + procedure :: zero_entries => pm_zero_entries + procedure :: zero_row_offdiag => pm_zero_row_offdiag + procedure :: get_first_col_pos => pm_get_first_col_pos + procedure :: get_last_col_pos => pm_get_last_col_pos + procedure :: get_column => pm_get_column + procedure :: get_position => pm_get_position + procedure :: get_position_diag => pm_get_position_diag + procedure :: get_aij => pm_get_aij + procedure :: get_row_offset => pm_get_row_offset + + ! public + procedure :: update => pm_update + + ! private + procedure, private :: pm_get_position + + end type PetscMatrixType + +contains + + subroutine pm_init(this, sparse, mem_path) + use SimVariablesModule, only: simulation_mode, nr_procs + class(PetscMatrixType) :: this + type(sparsematrix) :: sparse + character(len=*) :: mem_path + ! local + PetscErrorCode :: ierr + integer(I4B) :: i, ierror + + this%memory_path = mem_path + this%nrow = sparse%nrow + this%ncol = sparse%ncol + this%nnz = sparse%nnz + this%offset = sparse%offset + + ! allocate the diagonal block of the matrix + allocate (this%ia_petsc(this%nrow + 1)) + allocate (this%ja_petsc(this%nnz)) + allocate (this%amat_petsc(this%nnz)) + + call sparse%sort(with_csr=.true.) !< PETSc has full row sorting, MF6 had diagonal first and then sorted + call sparse%filliaja(this%ia_petsc, this%ja_petsc, ierror) + + ! go to C indexing for PETSc internals + do i = 1, this%nrow + 1 + this%ia_petsc(i) = this%ia_petsc(i) - 1 + end do + do i = 1, this%nnz + this%ja_petsc(i) = this%ja_petsc(i) - 1 + this%amat_petsc(i) = DZERO + end do + + ! create PETSc matrix object + if (simulation_mode == 'PARALLEL' .and. nr_procs > 1) then + this%is_parallel = .true. + call MatCreateMPIAIJWithArrays(PETSC_COMM_WORLD, & + sparse%nrow, sparse%nrow, & + sparse%ncol, sparse%ncol, & + this%ia_petsc, this%ja_petsc, & + this%amat_petsc, this%mat, & + ierr) + else + this%is_parallel = .false. + call MatCreateSeqAIJWithArrays(PETSC_COMM_WORLD, & + sparse%nrow, sparse%ncol, & + this%ia_petsc, this%ja_petsc, & + this%amat_petsc, this%mat, & + ierr) + end if + CHKERRQ(ierr) + + end subroutine pm_init + + !> @brief Copies the values from the CSR array into + !< the PETSc matrix object + subroutine pm_update(this) + class(PetscMatrixType) :: this + ! local + PetscErrorCode :: ierr + + if (this%is_parallel) then + call MatUpdateMPIAIJWithArrays(this%mat, this%nrow, this%nrow, & + this%ncol, this%ncol, & + this%ia_petsc, this%ja_petsc, & + this%amat_petsc, ierr) + CHKERRQ(ierr) + end if + + end subroutine pm_update + + subroutine pm_destroy(this) + class(PetscMatrixType) :: this + ! local + PetscErrorCode :: ierr + + call MatDestroy(this%mat, ierr) + CHKERRQ(ierr) + + deallocate (this%ia_petsc) + deallocate (this%ja_petsc) + deallocate (this%amat_petsc) + + end subroutine pm_destroy + + function pm_create_vector(this, n, name, mem_path) result(vec) + class(PetscMatrixType) :: this + integer(I4B) :: n !< the nr. of elements in the vector + character(len=*) :: name !< the variable name (for access through memory manager) + character(len=*) :: mem_path !< memory path for storing the underlying memory items + class(VectorBaseType), pointer :: vec !< the vector object to return + ! local + class(PetscVectorType), pointer :: petsc_vec + + allocate (petsc_vec) + call petsc_vec%create(n, name, mem_path) + vec => petsc_vec + + end function pm_create_vector + + function pm_get_value_pos(this, ipos) result(value) + class(PetscMatrixType) :: this + integer(I4B) :: ipos + real(DP) :: value + + value = this%amat_petsc(ipos) + + end function pm_get_value_pos + + function pm_get_diag_value(this, irow) result(diag_value) + class(PetscMatrixType) :: this + integer(I4B) :: irow + real(DP) :: diag_value + ! local + integer(I4B) :: idiag + + idiag = this%get_position_diag(irow) + diag_value = this%amat_petsc(idiag) + + end function pm_get_diag_value + + subroutine pm_set_diag_value(this, irow, diag_value) + class(PetscMatrixType) :: this + integer(I4B) :: irow + real(DP) :: diag_value + ! local + integer(I4B) :: idiag + + idiag = this%get_position_diag(irow) + this%amat_petsc(idiag) = diag_value + + end subroutine pm_set_diag_value + + subroutine pm_set_value_pos(this, ipos, value) + class(PetscMatrixType) :: this + integer(I4B) :: ipos + real(DP) :: value + + this%amat_petsc(ipos) = value + + end subroutine pm_set_value_pos + + subroutine pm_add_value_pos(this, ipos, value) + class(PetscMatrixType) :: this + integer(I4B) :: ipos + real(DP) :: value + + this%amat_petsc(ipos) = this%amat_petsc(ipos) + value + + end subroutine pm_add_value_pos + + subroutine pm_add_diag_value(this, irow, value) + class(PetscMatrixType) :: this + integer(I4B) :: irow + real(DP) :: value + ! local + integer(I4B) :: idiag + + idiag = this%get_position_diag(irow) + this%amat_petsc(idiag) = this%amat_petsc(idiag) + value + + end subroutine pm_add_diag_value + + function pm_get_first_col_pos(this, irow) result(first_col_pos) + class(PetscMatrixType) :: this + integer(I4B) :: irow + integer(I4B) :: first_col_pos + ! local + integer(I4B) :: irow_local + + ! convert to local row index + irow_local = irow - this%offset + + ! includes conversion to Fortran's 1-based + first_col_pos = this%ia_petsc(irow_local) + 1 + + end function pm_get_first_col_pos + + function pm_get_last_col_pos(this, irow) result(last_col_pos) + class(PetscMatrixType) :: this + integer(I4B) :: irow + integer(I4B) :: last_col_pos + ! local + integer(I4B) :: irow_local + + ! convert to local row index + irow_local = irow - this%offset + + ! includes conversion to Fortran's 1-based + last_col_pos = this%ia_petsc(irow_local + 1) + + end function pm_get_last_col_pos + + function pm_get_column(this, ipos) result(icol) + class(PetscMatrixType) :: this + integer(I4B) :: ipos + integer(I4B) :: icol + + ! includes conversion to Fortran's 1-based + icol = this%ja_petsc(ipos) + 1 + + end function pm_get_column + + !> @brief Return position index for (irow,icol) element + !! in the matrix. This index can be used in other + !! routines for direct access. + !< Returns -1 when not found. + function pm_get_position(this, irow, icol) result(ipos) + class(PetscMatrixType) :: this + integer(I4B) :: irow + integer(I4B) :: icol + integer(I4B) :: ipos + ! local + integer(I4B) :: ipos_f + integer(I4B) :: irow_local + + ipos = -1 + + ! convert to local row index + irow_local = irow - this%offset + + ! includes conversion to Fortran's 1-based + do ipos_f = this%ia_petsc(irow_local) + 1, this%ia_petsc(irow_local + 1) + if (this%ja_petsc(ipos_f) + 1 == icol) then + ipos = ipos_f + return + end if + end do + + end function pm_get_position + + function pm_get_position_diag(this, irow) result(ipos_diag) + class(PetscMatrixType) :: this + integer(I4B) :: irow + integer(I4B) :: ipos_diag + + ipos_diag = this%pm_get_position(irow, irow) + + end function pm_get_position_diag + + !> @brief Set all entries in the matrix to zero + !< + subroutine pm_zero_entries(this) + class(PetscMatrixType) :: this + ! local + integer(I4B) :: i + + do i = 1, this%nnz + this%amat_petsc(i) = DZERO + end do + + end subroutine pm_zero_entries + + !> @brief Set all off-diagonal entries in the matrix to zero + !< + subroutine pm_zero_row_offdiag(this, irow) + class(PetscMatrixType) :: this + integer(I4B) :: irow + ! local + integer(I4B) :: ipos, idiag + + idiag = this%get_position_diag(irow) + do ipos = this%get_first_col_pos(irow), this%get_last_col_pos(irow) + if (ipos == idiag) cycle + this%amat_petsc(ipos) = DZERO + end do + + end subroutine pm_zero_row_offdiag + + subroutine pm_get_aij(this, ia, ja, amat) + use SimModule, only: ustop + class(PetscMatrixType) :: this + integer(I4B), dimension(:), pointer, contiguous :: ia + integer(I4B), dimension(:), pointer, contiguous :: ja + real(DP), dimension(:), pointer, contiguous :: amat + + write (*, *) 'NOT IMPLEMENTED' + call ustop() + + end subroutine pm_get_aij + + function pm_get_row_offset(this) result(offset) + class(PetscMatrixType) :: this + integer(I4B) :: offset + + offset = this%offset + + end function pm_get_row_offset + +end module PetscMatrixModule diff --git a/src/Utilities/Matrix/SparseMatrix.f90 b/src/Utilities/Matrix/SparseMatrix.f90 index cdb654beb75..701a719d52f 100644 --- a/src/Utilities/Matrix/SparseMatrix.f90 +++ b/src/Utilities/Matrix/SparseMatrix.f90 @@ -1,7 +1,9 @@ module SparseMatrixModule use KindModule, only: I4B, DP use ConstantsModule, only: DZERO - use MatrixModule + use MatrixBaseModule + use VectorBaseModule + use SeqVectorModule use SparseModule, only: sparsematrix use MemoryManagerModule, only: mem_allocate, mem_deallocate implicit none @@ -15,8 +17,9 @@ module SparseMatrixModule integer(I4B), dimension(:), pointer, contiguous :: ja real(DP), dimension(:), pointer, contiguous :: amat contains - procedure :: create => spm_create + procedure :: init => spm_init procedure :: destroy => spm_destroy + procedure :: create_vector => spm_create_vector procedure :: get_value_pos => spm_get_value_pos procedure :: get_diag_value => spm_get_diag_value @@ -33,14 +36,17 @@ module SparseMatrixModule procedure :: get_column => spm_get_column procedure :: get_position => spm_get_position procedure :: get_position_diag => spm_get_position_diag + procedure :: get_aij => spm_get_aij + procedure :: get_row_offset => spm_get_row_offset procedure :: allocate_scalars procedure :: allocate_arrays + end type SparseMatrixType contains - subroutine spm_create(this, sparse, mem_path) + subroutine spm_init(this, sparse, mem_path) class(SparseMatrixType) :: this type(sparsematrix) :: sparse character(len=*) :: mem_path @@ -60,7 +66,7 @@ subroutine spm_create(this, sparse, mem_path) call sparse%filliaja(this%ia, this%ja, ierror, sort=.false.) call this%zero_entries() - end subroutine spm_create + end subroutine spm_init subroutine spm_destroy(this) class(SparseMatrixType) :: this @@ -75,6 +81,21 @@ subroutine spm_destroy(this) end subroutine spm_destroy + function spm_create_vector(this, n, name, mem_path) result(vec) + class(SparseMatrixType) :: this ! this sparse matrix + integer(I4B) :: n !< the nr. of elements in the vector + character(len=*) :: name !< the variable name (for access through memory manager) + character(len=*) :: mem_path !< memory path for storing the underlying memory items + class(VectorBaseType), pointer :: vec ! the vector to create + ! local + class(SeqVectorType), pointer :: seq_vec + + allocate (seq_vec) + call seq_vec%create(n, name, mem_path) + vec => seq_vec + + end function spm_create_vector + function spm_get_value_pos(this, ipos) result(value) class(SparseMatrixType) :: this integer(I4B) :: ipos @@ -234,4 +255,24 @@ subroutine spm_zero_row_offdiag(this, irow) end subroutine spm_zero_row_offdiag + subroutine spm_get_aij(this, ia, ja, amat) + class(SparseMatrixType) :: this + integer(I4B), dimension(:), pointer, contiguous :: ia + integer(I4B), dimension(:), pointer, contiguous :: ja + real(DP), dimension(:), pointer, contiguous :: amat + + ia => this%ia + ja => this%ja + amat => this%amat + + end subroutine spm_get_aij + + function spm_get_row_offset(this) result(offset) + class(SparseMatrixType) :: this + integer(I4B) :: offset + + offset = 0 + + end function spm_get_row_offset + end module SparseMatrixModule diff --git a/src/Utilities/STLVecInt.f90 b/src/Utilities/STLVecInt.f90 new file mode 100644 index 00000000000..6551e697705 --- /dev/null +++ b/src/Utilities/STLVecInt.f90 @@ -0,0 +1,211 @@ +module STLVecIntModule + use KindModule, only: I4B, LGP + use SimModule, only: ustop + use ArrayHandlersModule, only: ExpandArray + implicit none + private + public :: STLVecInt + + integer(I4B), parameter :: defaultInitialCapacity = 4 + + ! This is a dynamic vector type for integers + type :: STLVecInt + integer(I4B), private, allocatable :: values(:) !< the internal array for storage + integer(I4B) :: size !< the number of elements (technically this stuff should be unsigned) + integer(I4B) :: capacity !< the reserved storage + contains + procedure, pass(this) :: init !< allocate memory, init size and capacity + procedure, pass(this) :: push_back !< adds an element at the end of the vector + procedure, pass(this) :: push_back_unique !< adds an element at the end of the vector, if not present yet + procedure, pass(this) :: add_array !< adds elements of array at the end of the vector + procedure, pass(this) :: add_array_unique !< adds elements of array at the end of the vector, if not present yet + procedure, pass(this) :: at !< random access, unsafe, no bounds checking + procedure, pass(this) :: at_safe !< random access with bounds checking + procedure, pass(this) :: clear !< empties the vector, leaves memory unchanged + procedure, pass(this) :: shrink_to_fit !< reduces the allocated memory to fit the actual vector size + procedure, pass(this) :: destroy !< deletes the memory + procedure, pass(this) :: contains !< true when element already present + procedure, pass(this) :: get_values !< returns a copy of the values + ! private + procedure, private, pass(this) :: expand + end type STLVecInt + +contains ! module routines + + subroutine init(this, capacity) + class(STLVecInt), intent(inout) :: this + integer(I4B), intent(in), optional :: capacity ! the initial capacity, when given + + if (present(capacity)) then + this%capacity = capacity + else + this%capacity = defaultInitialCapacity + end if + + allocate (this%values(this%capacity)) + this%size = 0 + + end subroutine init + + subroutine push_back(this, newValue) + class(STLVecInt), intent(inout) :: this + integer(I4B) :: newValue + ! check capacity + if (this%size + 1 > this%capacity) then + call this%expand() + end if + + this%size = this%size + 1 + this%values(this%size) = newValue + + end subroutine push_back + + subroutine push_back_unique(this, newValue) + class(STLVecInt), intent(inout) :: this + integer(I4B) :: newValue + + if (.not. this%contains(newValue)) then + call this%push_back(newValue) + end if + + end subroutine push_back_unique + + subroutine add_array(this, array) + class(STLVecInt), intent(inout) :: this + integer(I4B), dimension(:), pointer :: array + ! local + integer(I4B) :: i + + do i = 1, size(array) + call this%push_back(array(i)) + end do + + end subroutine add_array + + subroutine add_array_unique(this, array) + class(STLVecInt), intent(inout) :: this + integer(I4B), dimension(:), pointer :: array + ! local + integer(I4B) :: i + + do i = 1, size(array) + if (.not. this%contains(array(i))) then + call this%push_back(array(i)) + end if + end do + + end subroutine add_array_unique + + function at(this, idx) result(value) + class(STLVecInt), intent(in) :: this + integer(I4B), intent(in) :: idx + integer(I4B) :: value + + value = this%values(idx) + + end function at + + function at_safe(this, idx) result(value) + class(STLVecInt), intent(inout) :: this + integer(I4B), intent(in) :: idx + integer(I4B) :: value + + if (idx > this%size) then + write (*, *) 'STLVecInt exception: access out of bounds, index ', idx, & + ' exceeds actual size (', this%size, ')' + call ustop() + end if + value = this%at(idx) + + end function at_safe + + subroutine clear(this) + class(STLVecInt), intent(inout) :: this + + ! really, this is all there is to it... + this%size = 0 + + end subroutine clear + + subroutine shrink_to_fit(this) + class(STLVecInt), intent(inout) :: this + ! local + integer(I4B), allocatable :: tempValues(:) + integer(I4B) :: i, newSize + + if (this%size == this%capacity) then + return + end if + + ! store temp + newSize = this%size + allocate (tempValues(newSize)) + do i = 1, newSize + tempValues(i) = this%values(i) + end do + + ! reinit + call this%destroy() + call this%init(newSize) + + ! copy back + do i = 1, newSize + call this%push_back(tempValues(i)) + end do + + end subroutine shrink_to_fit + + subroutine destroy(this) + class(STLVecInt), intent(inout) :: this + + if (allocated(this%values)) then + deallocate (this%values) + this%size = 0 + this%capacity = 0 + else + write (*, *) 'STLVecInt exception: cannot delete an unallocated array' + call ustop() + end if + + end subroutine destroy + + ! expand the array with the given strategy, at + ! least by 1 + subroutine expand(this) + class(STLVecInt), intent(inout) :: this + integer(I4B) :: increment + + ! expansion strategy + increment = this%capacity / 2 + 1 + call ExpandArray(this%values, increment) + this%capacity = this%capacity + increment + + end subroutine expand + + ! check if the element is already present + function contains(this, val) result(res) + class(STLVecInt), intent(inout) :: this + integer(I4B) :: val + logical(LGP) :: res + ! local + integer(I4B) :: i + + res = .false. + do i = 1, this%size + if (this%at(i) == val) then + res = .true. + return + end if + end do + + end function contains + + function get_values(this) result(values) + class(STLVecInt), intent(in) :: this + integer(I4B), dimension(:), allocatable :: values + + values = this%values(1:this%size) + + end function get_values + +end module STLVecIntModule diff --git a/src/Utilities/Sim.f90 b/src/Utilities/Sim.f90 index 50fdfe1340a..e56ea5fc9be 100644 --- a/src/Utilities/Sim.f90 +++ b/src/Utilities/Sim.f90 @@ -498,6 +498,7 @@ end subroutine converge_check subroutine initial_message() ! -- modules use VersionModule, only: write_listfile_header + use SimVariablesModule, only: simulation_mode ! ! -- initialize message lists call sim_errors%init_message() @@ -509,6 +510,11 @@ subroutine initial_message() call write_listfile_header(istdout, write_kind_info=.false., & write_sys_command=.false.) ! + if (simulation_mode == 'PARALLEL') then + call sim_message('(MODFLOW runs in '//trim(simulation_mode)//' mode)', & + skipafter=1) + end if + ! end subroutine initial_message !> @brief Create final message diff --git a/src/Utilities/SimStages.f90 b/src/Utilities/SimStages.f90 new file mode 100644 index 00000000000..15a11f8c104 --- /dev/null +++ b/src/Utilities/SimStages.f90 @@ -0,0 +1,47 @@ +module SimStagesModule + use KindModule, only: I4B + implicit none + private + + public :: STG_TO_STR + + ! stages for synchronization + integer(I4B), public, parameter :: STG_NEVER = 0 + integer(I4B), public, parameter :: STG_INIT = 1 + integer(I4B), public, parameter :: STG_AFTER_MDL_DF = 2 + integer(I4B), public, parameter :: STG_AFTER_EXG_DF = 3 + integer(I4B), public, parameter :: STG_BEFORE_DF = 4 + integer(I4B), public, parameter :: STG_AFTER_DF = 5 + integer(I4B), public, parameter :: STG_BEFORE_AC = 6 + integer(I4B), public, parameter :: STG_BEFORE_AR = 7 + integer(I4B), public, parameter :: STG_AFTER_AR = 8 + integer(I4B), public, parameter :: STG_BEFORE_AD = 9 + integer(I4B), public, parameter :: STG_BEFORE_CF = 10 + integer(I4B), public, parameter :: STG_BEFORE_FC = 11 + +contains + + !> @brief Converts a stage to its string representation + !< + function STG_TO_STR(stage) result(stg_str) + integer(I4B) :: stage + character(len=24) :: stg_str + + if (stage == STG_NEVER) then; stg_str = "STG_NEVER" + else if (stage == STG_INIT) then; stg_str = "STG_INIT" + else if (stage == STG_AFTER_MDL_DF) then; stg_str = "STG_AFTER_MDL_DF" + else if (stage == STG_AFTER_EXG_DF) then; stg_str = "STG_AFTER_EXG_DF" + else if (stage == STG_BEFORE_DF) then; stg_str = "STG_BEFORE_DF" + else if (stage == STG_AFTER_DF) then; stg_str = "STG_AFTER_DF" + else if (stage == STG_BEFORE_AC) then; stg_str = "STG_BEFORE_AC" + else if (stage == STG_BEFORE_AR) then; stg_str = "STG_BEFORE_AR" + else if (stage == STG_AFTER_AR) then; stg_str = "STG_AFTER_AR" + else if (stage == STG_BEFORE_AD) then; stg_str = "STG_BEFORE_AD" + else if (stage == STG_BEFORE_CF) then; stg_str = "STG_BEFORE_CF" + else if (stage == STG_BEFORE_FC) then; stg_str = "STG_BEFORE_FC" + else; stg_str = "UNKNOWN" + end if + + end function STG_TO_STR + +end module SimStagesModule diff --git a/src/Utilities/SimVariables.f90 b/src/Utilities/SimVariables.f90 index 6847d8376c1..70ce490c7b5 100644 --- a/src/Utilities/SimVariables.f90 +++ b/src/Utilities/SimVariables.f90 @@ -9,12 +9,21 @@ module SimVariablesModule use, intrinsic :: iso_fortran_env, only: output_unit use KindModule, only: DP, I4B - use ConstantsModule, only: LINELENGTH, MAXCHARLEN, IUSTART, VALL, MNORMAL + use ConstantsModule, only: LINELENGTH, MAXCHARLEN, IUSTART, & + VALL, MNORMAL, LENMODELNAME public character(len=LINELENGTH) :: simfile = 'mfsim.nam' !< simulation name file character(len=LINELENGTH) :: simlstfile = 'mfsim.lst' !< simulation listing file name character(len=LINELENGTH) :: simstdout = 'mfsim.stdout' !< name of standard out file if screen output is piped to a file character(len=LINELENGTH) :: idm_context = '__INPUT__' + + ! for parallel development + character(len=LINELENGTH) :: simulation_mode = 'SEQUENTIAL' + integer(I4B) :: proc_id = 0 + integer(I4B) :: nr_procs = 1 + character(len=LENMODELNAME), dimension(:), allocatable :: model_names !< all model names in the (global) simulation + integer(I4B), dimension(:), allocatable :: model_loc_idx !< equals the local index into the basemodel list (-1 when not available) + character(len=MAXCHARLEN) :: errmsg !< error message string character(len=MAXCHARLEN) :: warnmsg !< warning message string integer(I4B) :: istdout = output_unit !< unit number for stdout diff --git a/src/Utilities/Sparse.f90 b/src/Utilities/Sparse.f90 index af01ef34d9a..57d3b138f9e 100644 --- a/src/Utilities/Sparse.f90 +++ b/src/Utilities/Sparse.f90 @@ -3,7 +3,7 @@ module SparseModule !of a matrix. Module uses FORTRAN 2003 extensions to manage !the data structures in an object oriented fashion. - use KindModule, only: DP, I4B + use KindModule, only: DP, I4B, LGP implicit none type rowtype @@ -12,10 +12,12 @@ module SparseModule end type rowtype type, public :: sparsematrix - integer(I4B) :: nrow ! number of rows in the matrix - integer(I4B) :: ncol ! number of columns in the matrix - integer(I4B) :: nnz ! number of nonzero matrix entries - type(rowtype), allocatable, dimension(:) :: row ! one rowtype for each matrix row + integer(I4B) :: offset !< global offset for first row in this matrix (default = 0) + integer(I4B) :: nrow !< number of rows in the matrix + integer(I4B) :: ncol !< number of columns in the matrix + integer(I4B) :: nnz !< number of nonzero matrix entries + integer(I4B) :: nnz_od !< number of off-diagonal nonzero matrix entries + type(rowtype), allocatable, dimension(:) :: row !< one rowtype for each matrix row contains generic :: init => initialize, initializefixed procedure :: addconnection @@ -48,9 +50,11 @@ subroutine initialize(this, nrow, ncol, rowmaxnnz) ! -- local integer(I4B) :: i ! -- code + this%offset = 0 this%nrow = nrow this%ncol = ncol this%nnz = 0 + this%nnz_od = 0 allocate (this%row(nrow)) do i = 1, nrow allocate (this%row(i)%icolarray(rowmaxnnz(i))) @@ -151,10 +155,19 @@ subroutine addconnection(this, i, j, inodup, iaddop) integer(I4B), intent(in) :: i, j, inodup integer(I4B), optional, intent(inout) :: iaddop ! -- local + integer(I4B) :: irow_local integer(I4B) :: iadded ! -- code - call insert(j, this%row(i), inodup, iadded) + ! + ! -- when distributed system, reduce row numbers to local range + irow_local = i - this%offset + ! + call insert(j, this%row(irow_local), inodup, iadded) this%nnz = this%nnz + iadded + if (j < this%offset + 1 .or. j > this%offset + this%nrow) then + ! count the off-diagonal entries separately + this%nnz_od = this%nnz_od + iadded + end if if (present(iaddop)) iaddop = iadded ! ! -- return @@ -213,18 +226,27 @@ subroutine insert(j, thisrow, inodup, iadded) return end subroutine insert - subroutine sort(this) + subroutine sort(this, with_csr) !sort the icolarray for each row, but do not include !the diagonal position in the sort so that it stays in front ! -- dummy class(sparsematrix), intent(inout) :: this + logical(LGP), optional :: with_csr ! -- local - integer(I4B) :: i, nval + integer(I4B) :: i, nval, start_idx ! -- code + start_idx = 2 + if (present(with_csr)) then + if (with_csr) then + ! CSR: don't put diagonal up front + start_idx = 1 + end if + end if + do i = 1, this%nrow nval = this%row(i)%nnz - call sortintarray(nval - 1, & - this%row(i)%icolarray(2:nval)) + call sortintarray(nval - start_idx + 1, & + this%row(i)%icolarray(start_idx:nval)) end do ! ! -- return diff --git a/src/Utilities/Timer.f90 b/src/Utilities/Timer.f90 index 14358101ac4..bac77230997 100644 --- a/src/Utilities/Timer.f90 +++ b/src/Utilities/Timer.f90 @@ -5,14 +5,14 @@ module TimerModule use GenericUtilitiesModule, only: sim_message implicit none private - public :: start_time + public :: print_start_time public :: elapsed_time public :: code_timer integer(I4B), dimension(8) :: ibdt contains - subroutine start_time() + subroutine print_start_time() ! ****************************************************************************** ! Start simulation timer ! ****************************************************************************** @@ -36,7 +36,7 @@ subroutine start_time() ! ! -- return return - end subroutine start_time + end subroutine print_start_time SUBROUTINE elapsed_time(iout, iprtim) ! ****************************************************************************** diff --git a/src/Utilities/Vector/PetscVector.F90 b/src/Utilities/Vector/PetscVector.F90 new file mode 100644 index 00000000000..404b77d31b0 --- /dev/null +++ b/src/Utilities/Vector/PetscVector.F90 @@ -0,0 +1,129 @@ +module PetscVectorModule +#include + use petscksp + use VectorBaseModule + use KindModule, only: I4B, DP + use ConstantsModule, only: DZERO + use MemoryManagerModule, only: mem_allocate, mem_deallocate + use SimVariablesModule, only: simulation_mode, nr_procs + implicit none + private + + type, public, extends(VectorBaseType) :: PetscVectorType + real(DP), dimension(:), pointer, contiguous :: array => null() + Vec :: vec_impl + contains + ! override + procedure :: create => petsc_vec_create + procedure :: destroy => petsc_vec_destroy + procedure :: get_array => petsc_vec_get_array + procedure :: get_ownership_range => petsc_vec_get_ownership_range + procedure :: get_size => petsc_vec_get_size + procedure :: zero_entries => petsc_vec_zero_entries + procedure :: print => petsc_vec_print + end type PetscVectorType + +contains + + !> @brief Create a PETSc vector + !< + subroutine petsc_vec_create(this, n, name, mem_path) + class(PetscVectorType) :: this !< this vector + integer(I4B) :: n !< the nr. of elements in the vector + character(len=*) :: name !< the variable name (for access through memory manager) + character(len=*) :: mem_path !< memory path for storing the underlying memory items + ! local + PetscErrorCode :: ierr + + call mem_allocate(this%array, n, name, mem_path) + if (simulation_mode == 'PARALLEL' .and. nr_procs > 1) then + call VecCreateMPIWithArray(PETSC_COMM_WORLD, 1, n, PETSC_DECIDE, & + this%array, this%vec_impl, ierr) + else + call VecCreateSeqWithArray(PETSC_COMM_WORLD, 1, n, this%array, & + this%vec_impl, ierr) + end if + CHKERRQ(ierr) + + call this%zero_entries() + + end subroutine petsc_vec_create + + !> @brief Clean up + !< + subroutine petsc_vec_destroy(this) + class(PetscVectorType) :: this !< this vector + ! local + PetscErrorCode :: ierr + + call VecDestroy(this%vec_impl, ierr) + CHKERRQ(ierr) + call mem_deallocate(this%array) + + end subroutine petsc_vec_destroy + + !> @brief Get a pointer to the underlying data array + !< for this vector + function petsc_vec_get_array(this) result(array) + class(PetscVectorType) :: this !< this vector + real(DP), dimension(:), pointer, contiguous :: array !< the underlying data array for this vector + + array => this%array + + end function petsc_vec_get_array + + subroutine petsc_vec_get_ownership_range(this, start, end) + class(PetscVectorType) :: this !< this vector + integer(I4B) :: start !< the index of the first element (owned by this process) in the global vector + integer(I4B) :: end !< the index of the last element (owned by this process) in the global vector + ! local + PetscErrorCode :: ierr + + ! gets the range as [start, end) but 0-based + call VecGetOwnershipRange(this%vec_impl, start, end, ierr) + + ! now we make it [start, end] and 1-based + start = start + 1 + + CHKERRQ(ierr) + + end subroutine petsc_vec_get_ownership_range + + function petsc_vec_get_size(this) result(size) + class(PetscVectorType) :: this !< this vector + integer(I4B) :: size !< the (global) vector size + ! local + PetscErrorCode :: ierr + + call VecGetSize(this%vec_impl, size, ierr) + CHKERRQ(ierr) + + end function petsc_vec_get_size + + !> @brief set all elements to zero + !< + subroutine petsc_vec_zero_entries(this) + class(PetscVectorType) :: this !< this vector + ! local + PetscErrorCode :: ierr + + call VecZeroEntries(this%vec_impl, ierr) + CHKERRQ(ierr) + call VecAssemblyBegin(this%vec_impl, ierr) + CHKERRQ(ierr) + call VecAssemblyEnd(this%vec_impl, ierr) + CHKERRQ(ierr) + + end subroutine petsc_vec_zero_entries + + subroutine petsc_vec_print(this) + class(PetscVectorType) :: this !< this vector + ! local + PetscErrorCode :: ierr + + call VecView(this%vec_impl, PETSC_VIEWER_STDOUT_WORLD, ierr) + CHKERRQ(ierr) + + end subroutine petsc_vec_print + +end module PetscVectorModule diff --git a/src/Utilities/Vector/SeqVector.f90 b/src/Utilities/Vector/SeqVector.f90 new file mode 100644 index 00000000000..701118adb51 --- /dev/null +++ b/src/Utilities/Vector/SeqVector.f90 @@ -0,0 +1,95 @@ +module SeqVectorModule + use KindModule, only: I4B, DP + use ConstantsModule, only: DZERO + use MemoryManagerModule, only: mem_allocate, mem_deallocate + use VectorBaseModule + implicit none + private + + type, public, extends(VectorBaseType) :: SeqVectorType + integer(I4B) :: size + real(DP), dimension(:), pointer, contiguous :: array + contains + procedure :: create => sqv_create + procedure :: destroy => sqv_destroy + procedure :: get_array => sqv_get_array + procedure :: get_ownership_range => sqv_get_ownership_range + procedure :: get_size => sqv_get_size + procedure :: zero_entries => sqv_zero_entries + procedure :: print => sqv_print + end type SeqVectorType + +contains + + !> @brief Create a sequential vector: the classic MF6 verion + !< + subroutine sqv_create(this, n, name, mem_path) + class(SeqVectorType) :: this !< this vector + integer(I4B) :: n !< the nr. of elements in the vector + character(len=*) :: name !< the variable name (for access through memory manager) + character(len=*) :: mem_path !< memory path for storing the underlying memory items + + this%size = n + call mem_allocate(this%array, n, name, mem_path) + call this%zero_entries() + + end subroutine sqv_create + + !> @brief Clean up + !< + subroutine sqv_destroy(this) + class(SeqVectorType) :: this !< this vector + + call mem_deallocate(this%array) + + end subroutine sqv_destroy + + !> @brief Get a pointer to the underlying data array + !< for this vector + function sqv_get_array(this) result(array) + class(SeqVectorType) :: this !< this vector + real(DP), dimension(:), pointer, contiguous :: array !< the underlying data array for this vector + + array => this%array + + end function sqv_get_array + + subroutine sqv_get_ownership_range(this, start, end) + class(SeqVectorType) :: this !< this vector + integer(I4B) :: start !< the index of the first element in the vector + integer(I4B) :: end !< the index of the last element in the vector + + start = 1 + end = this%size + + end subroutine sqv_get_ownership_range + + function sqv_get_size(this) result(size) + class(SeqVectorType) :: this !< this vector + integer(I4B) :: size !< the vector size + + size = this%size + + end function sqv_get_size + + !> @brief set all elements to zero + !< + subroutine sqv_zero_entries(this) + class(SeqVectorType) :: this !< this vector + ! local + integer(I4B) :: i + + do i = 1, this%size + this%array(i) = DZERO + end do + + end subroutine sqv_zero_entries + + subroutine sqv_print(this) + class(SeqVectorType) :: this !< this vector + + write (*, *) this%array + + end subroutine sqv_print + +end module SeqVectorModule diff --git a/src/Utilities/Vector/VectorBase.f90 b/src/Utilities/Vector/VectorBase.f90 new file mode 100644 index 00000000000..07b604667ef --- /dev/null +++ b/src/Utilities/Vector/VectorBase.f90 @@ -0,0 +1,54 @@ +module VectorBaseModule + use KindModule, only: I4B, DP + implicit none + private + + type, public, abstract :: VectorBaseType + contains + procedure(create_if), deferred :: create + procedure(destroy_if), deferred :: destroy + procedure(get_array_if), deferred :: get_array + procedure(get_ownership_range_if), deferred :: get_ownership_range + procedure(get_size_if), deferred :: get_size + procedure(zero_entries_if), deferred :: zero_entries + procedure(print_if), deferred :: print + end type VectorBaseType + + abstract interface + subroutine create_if(this, n, name, mem_path) + import VectorBaseType, I4B + class(VectorBaseType) :: this !< this vector + integer(I4B) :: n !< the nr. of elements in the (local) vector + character(len=*) :: name !< the variable name (for access through memory manager) + character(len=*) :: mem_path !< memory path for storing the underlying memory items + end subroutine create_if + subroutine destroy_if(this) + import VectorBaseType + class(VectorBaseType) :: this !< this vector + end subroutine destroy_if + function get_array_if(this) result(array) + import VectorBaseType, DP + class(VectorBaseType) :: this !< this vector + real(DP), dimension(:), pointer, contiguous :: array + end function get_array_if + subroutine get_ownership_range_if(this, start, end) + import VectorBaseType, I4B + class(VectorBaseType) :: this !< this vector + integer(I4B) :: start, end + end subroutine + function get_size_if(this) result(size) + import VectorBaseType, I4B + class(VectorBaseType) :: this !< this vector + integer(I4B) :: size + end function + subroutine zero_entries_if(this) + import VectorBaseType + class(VectorBaseType) :: this + end subroutine + subroutine print_if(this) + import VectorBaseType + class(VectorBaseType) :: this + end subroutine print_if + end interface + +end module VectorBaseModule diff --git a/src/Utilities/VectorInt.f90 b/src/Utilities/VectorInt.f90 deleted file mode 100644 index 589c9771875..00000000000 --- a/src/Utilities/VectorInt.f90 +++ /dev/null @@ -1,163 +0,0 @@ -module VectorIntModule - use KindModule, only: I4B, LGP - use SimModule, only: ustop - use ArrayHandlersModule, only: ExpandArray - implicit none - private - public :: VectorInt - - integer(I4B), parameter :: defaultInitialCapacity = 4 - - ! This is a dynamic vector type for integers - type :: VectorInt - integer(I4B), private, allocatable :: values(:) ! the internal array for storage - integer(I4B) :: size ! the number of elements (technically this stuff should be unsigned) - integer(I4B) :: capacity ! the reserved storage - contains - procedure, pass(this) :: init ! allocate memory, init size and capacity - procedure, pass(this) :: push_back ! adds an element at the end of the vector - procedure, pass(this) :: at ! random access, unsafe, no bounds checking - procedure, pass(this) :: at_safe ! random access with bounds checking - procedure, pass(this) :: clear ! empties the vector, leaves memory unchanged - procedure, pass(this) :: shrink_to_fit ! reduces the allocated memory to fit the actual vector size - procedure, pass(this) :: destroy ! deletes the memory - procedure, pass(this) :: contains ! true when element already present - ! private - procedure, private, pass(this) :: expand - end type VectorInt - -contains ! module routines - - subroutine init(this, capacity) - class(VectorInt), intent(inout) :: this - integer(I4B), intent(in), optional :: capacity ! the initial capacity, when given - - if (present(capacity)) then - this%capacity = capacity - else - this%capacity = defaultInitialCapacity - end if - - allocate (this%values(this%capacity)) - this%size = 0 - - end subroutine init - - subroutine push_back(this, newValue) - class(VectorInt), intent(inout) :: this - integer(I4B) :: newValue - ! check capacity - if (this%size + 1 > this%capacity) then - call this%expand() - end if - - this%size = this%size + 1 - this%values(this%size) = newValue - - end subroutine push_back - - function at(this, idx) result(value) - class(VectorInt), intent(inout) :: this - integer(I4B), intent(in) :: idx - integer(I4B) :: value - - value = this%values(idx) - - end function at - - function at_safe(this, idx) result(value) - class(VectorInt), intent(inout) :: this - integer(I4B), intent(in) :: idx - integer(I4B) :: value - - if (idx > this%size) then - write (*, *) 'VectorInt exception: access out of bounds, index ', idx, & - ' exceeds actual size (', this%size, ')' - call ustop() - end if - value = this%at(idx) - - end function at_safe - - subroutine clear(this) - class(VectorInt), intent(inout) :: this - - ! really, this is all there is to it... - this%size = 0 - - end subroutine clear - - subroutine shrink_to_fit(this) - class(VectorInt), intent(inout) :: this - ! local - integer(I4B), allocatable :: tempValues(:) - integer(I4B) :: i, newSize - - if (this%size == this%capacity) then - return - end if - - ! store temp - newSize = this%size - allocate (tempValues(newSize)) - do i = 1, newSize - tempValues(i) = this%values(i) - end do - - ! reinit - call this%destroy() - call this%init(newSize) - - ! copy back - do i = 1, newSize - call this%push_back(tempValues(i)) - end do - - end subroutine shrink_to_fit - - subroutine destroy(this) - class(VectorInt), intent(inout) :: this - - if (allocated(this%values)) then - deallocate (this%values) - this%size = 0 - this%capacity = 0 - else - write (*, *) 'VectorInt exception: cannot delete an unallocated array' - call ustop() - end if - - end subroutine destroy - - ! expand the array with the given strategy, at - ! least by 1 - subroutine expand(this) - class(VectorInt), intent(inout) :: this - integer(I4B) :: increment - - ! expansion strategy - increment = this%capacity / 2 + 1 - call ExpandArray(this%values, increment) - this%capacity = this%capacity + increment - - end subroutine expand - - ! check if the element is already present - function contains(this, val) result(res) - class(VectorInt), intent(inout) :: this - integer(I4B) :: val - logical(LGP) :: res - ! local - integer(I4B) :: i - - res = .false. - do i = 1, this%size - if (this%at(i) == val) then - res = .true. - return - end if - end do - - end function contains - -end module VectorIntModule diff --git a/src/Utilities/comarg.f90 b/src/Utilities/comarg.f90 index 4fba5b32e3d..1fc3eb20801 100644 --- a/src/Utilities/comarg.f90 +++ b/src/Utilities/comarg.f90 @@ -8,7 +8,7 @@ module CommandArguments use CompilerVersion use SimVariablesModule, only: istdout, isim_level, & simfile, simlstfile, simstdout, & - isim_mode + isim_mode, simulation_mode use GenericUtilitiesModule, only: sim_message, write_message use SimModule, only: store_error, ustop use InputOutputModule, only: upcase, getunit @@ -163,6 +163,8 @@ subroutine GetCommandLineArguments() case ('-D', '--DISCLAIMER') lstop = .TRUE. call sim_message('', fmt=FMTDISCLAIMER) + case ('-P', '--PARALLEL') + simulation_mode = 'PARALLEL' case ('-LIC', '--LICENSE') lstop = .TRUE. call sim_message('', fmt=FMTLICENSE) @@ -270,6 +272,7 @@ subroutine write_usage(header, cexe) &' -v --version Display program version information.',/,& &' -dev --develop Display program develop option mode.',/,& &' -d --disclaimer Display program disclaimer.',/,& + &' -p --parallel Run program in parallel mode.',/,& &' -lic --license Display program license information.',/,& &' -c --compiler Display compiler information.',/,& &' -co --compiler-opt Display compiler options.',/,& diff --git a/src/meson.build b/src/meson.build index f50a97e7b7f..bf5873571f4 100644 --- a/src/meson.build +++ b/src/meson.build @@ -7,6 +7,24 @@ external_libraries = files( ) modflow_sources = files( + 'Distributed' / 'IndexMap.f90', + 'Distributed' / 'InterfaceMap.f90', + 'Distributed' / 'Mapper.f90', + 'Distributed' / 'MappedMemory.f90', + 'Distributed' / 'RouterBase.f90', + 'Distributed' / 'RouterFactory.F90', + 'Distributed' / 'SerialRouter.f90', + 'Distributed' / 'VirtualBase.f90', + 'Distributed' / 'VirtualDataContainer.f90', + 'Distributed' / 'VirtualDataLists.f90', + 'Distributed' / 'VirtualDataManager.f90', + 'Distributed' / 'VirtualExchange.f90', + 'Distributed' / 'VirtualGwfExchange.f90', + 'Distributed' / 'VirtualGwtExchange.f90', + 'Distributed' / 'VirtualModel.f90', + 'Distributed' / 'VirtualGwfModel.f90', + 'Distributed' / 'VirtualGwtModel.f90', + 'Distributed' / 'VirtualSolution.f90', 'Exchange' / 'BaseExchange.f90', 'Exchange' / 'DisConnExchange.f90', 'Exchange' / 'GhostNode.f90', @@ -24,10 +42,7 @@ modflow_sources = files( 'Model' / 'Connection' / 'GwfInterfaceModel.f90', 'Model' / 'Connection' / 'GwtInterfaceModel.f90', 'Model' / 'Connection' / 'SpatialModelConnection.f90', - 'Model' / 'Connection' / 'MappedVariable.f90', - 'Model' / 'Connection' / 'DistributedData.f90', - 'Model' / 'Connection' / 'DistributedModel.f90', - 'Model' / 'Connection' / 'InterfaceMap.f90', + 'Model' / 'Connection' / 'DistributedVariable.f90', 'Model' / 'Geometry' / 'BaseGeometry.f90', 'Model' / 'Geometry' / 'CircularGeometry.f90', 'Model' / 'Geometry' / 'RectangularGeometry.f90', @@ -110,8 +125,12 @@ modflow_sources = files( 'Solution' / 'LinearMethods' / 'ims8linear.f90', 'Solution' / 'LinearMethods' / 'ims8reordering.f90', 'Solution' / 'LinearMethods' / 'ims8misc.f90', + 'Solution' / 'LinearMethods' / 'ImsLinearSolver.f90', 'Solution' / 'BaseSolution.f90', + 'Solution' / 'LinearSolverBase.f90', + 'Solution' / 'LinearSolverFactory.F90', 'Solution' / 'NumericalSolution.f90', + 'Solution' / 'SolutionFactory.F90', 'Solution' / 'SolutionGroup.f90', 'Timing' / 'ats.f90', 'Timing' / 'tdis.f90', @@ -155,6 +174,8 @@ modflow_sources = files( 'Utilities' / 'TimeSeries' / 'TimeSeriesLink.f90', 'Utilities' / 'TimeSeries' / 'TimeSeriesManager.f90', 'Utilities' / 'TimeSeries' / 'TimeSeriesRecord.f90', + 'Utilities' / 'Vector' / 'VectorBase.f90', + 'Utilities' / 'Vector' / 'SeqVector.f90', 'Utilities' / 'ArrayHandlers.f90', 'Utilities' / 'ArrayReaders.f90', 'Utilities' / 'BlockParser.f90', @@ -180,23 +201,49 @@ modflow_sources = files( 'Utilities' / 'OpenSpec.f90', 'Utilities' / 'PackageBudget.f90', 'Utilities' / 'Sim.f90', + 'Utilities' / 'SimStages.f90', 'Utilities' / 'SimVariables.f90', 'Utilities' / 'SmoothingFunctions.f90', 'Utilities' / 'sort.f90', - 'Utilities' / 'Sparse.f90', + 'Utilities' / 'Sparse.f90', + 'Utilities' / 'STLVecInt.f90', 'Utilities' / 'StringList.f90', 'Utilities' / 'Table.f90', 'Utilities' / 'TableTerm.f90', 'Utilities' / 'Timer.f90', - 'Utilities' / 'VectorInt.f90', 'Utilities' / 'version.f90', 'mf6core.f90', 'mf6lists.f90', - 'SimulationCreate.f90' + 'SimulationCreate.f90', + 'RunControl.f90', + 'RunControlFactory.F90' ) +modflow_petsc_sources = files( + 'Utilities' / 'Vector' / 'PetscVector.F90', + 'Utilities' / 'Matrix' / 'PetscMatrix.F90', + 'Solution' / 'PETSc' / 'PetscSolver.F90', + 'Solution' / 'PETSc' / 'PetscConvergence.F90' +) + +modflow_mpi_sources = files( + 'Distributed' / 'MpiMessageBuilder.f90', + 'Distributed' / 'MpiRouter.f90', + 'Distributed' / 'MpiRunControl.F90', + 'Distributed' / 'MpiWorld.f90', + 'Solution' / 'ParallelSolution.f90' +) + +if with_petsc + modflow_sources += modflow_petsc_sources +endif + +if with_mpi + modflow_sources += modflow_mpi_sources +endif + mf6_external = static_library('mf6_external', external_libraries) -mf6core = static_library('mf6core', modflow_sources, link_with: [mf6_external]) +mf6core = static_library('mf6core', modflow_sources, dependencies: dependencies, link_with: [mf6_external]) -mf6exe = executable('mf6', 'mf6.f90', link_with: [mf6core], install: true) +mf6exe = executable('mf6', 'mf6.f90', link_with: [mf6core], dependencies: dependencies, install: true) diff --git a/src/mf6core.f90 b/src/mf6core.f90 index c3d6619b605..0f050ff24a1 100644 --- a/src/mf6core.f90 +++ b/src/mf6core.f90 @@ -13,12 +13,15 @@ module Mf6CoreModule use BaseModelModule, only: BaseModelType, GetBaseModelFromList use BaseExchangeModule, only: BaseExchangeType, GetBaseExchangeFromList use SpatialModelConnectionModule, only: SpatialModelConnectionType, & - GetSpatialModelConnectionFromList + get_smc_from_list use BaseSolutionModule, only: BaseSolutionType, GetBaseSolutionFromList use SolutionGroupModule, only: SolutionGroupType, GetSolutionGroupFromList - use DistributedDataModule + use RunControlModule, only: RunControlType + use SimStagesModule implicit none + class(RunControlType), pointer :: run_ctrl => null() !< the run controller for this simulation + contains !> @brief Main controller @@ -30,7 +33,6 @@ subroutine Mf6Run ! -- modules use CommandArguments, only: GetCommandLineArguments use TdisModule, only: endofsimulation - use KindModule, only: DP ! -- local logical(LGP) :: hasConverged ! @@ -66,10 +68,15 @@ end subroutine Mf6Run !< subroutine Mf6Initialize() ! -- modules + use RunControlFactoryModule, only: create_run_control use SimulationCreateModule, only: simulation_cr - ! - ! -- print banner and info to screen - call printInfo() + + ! -- get the run controller for sequential or parallel builds + run_ctrl => create_run_control() + call run_ctrl%start() + + ! -- print info and start timer + call print_info() ! -- create call simulation_cr() @@ -115,12 +122,8 @@ subroutine Mf6Finalize() ! -- modules use, intrinsic :: iso_fortran_env, only: output_unit use ListsModule, only: lists_da - use MemoryManagerModule, only: mem_write_usage, mem_da - use TimerModule, only: elapsed_time - use SimVariablesModule, only: iout use SimulationCreateModule, only: simulation_da use TdisModule, only: tdis_da - use SimModule, only: final_message ! -- local variables integer(I4B) :: im integer(I4B) :: ic @@ -171,7 +174,7 @@ subroutine Mf6Finalize() ! ! -- Deallocate for each connection do ic = 1, baseconnectionlist%Count() - mc => GetSpatialModelConnectionFromList(baseconnectionlist, ic) + mc => get_smc_from_list(baseconnectionlist, ic) call mc%exg_da() deallocate (mc) end do @@ -191,32 +194,25 @@ subroutine Mf6Finalize() end do call simulation_da() call lists_da() - call distributed_data%destroy() ! - ! -- Write memory usage, elapsed time and terminate - call mem_write_usage(iout) - call mem_da() - call elapsed_time(iout, 1) - call final_message() + ! -- finish gently (No calls after this) + call run_ctrl%finish() ! end subroutine Mf6Finalize - !> @brief Print info to screen - !! - !! This subroutine prints the banner to the screen. - !! + !> @brief print initial message !< - subroutine printInfo() + subroutine print_info() use SimModule, only: initial_message - use TimerModule, only: start_time - ! - ! -- print initial message + use TimerModule, only: print_start_time + + ! print initial message call initial_message() - ! - ! -- get start time - call start_time() - return - end subroutine printInfo + + ! get start time + call print_start_time() + + end subroutine print_info !> @brief Define the simulation !! @@ -235,28 +231,43 @@ subroutine simulation_df() class(BaseExchangeType), pointer :: ep => null() class(SpatialModelConnectionType), pointer :: mc => null() + ! -- init virtual data environment + call run_ctrl%at_stage(STG_INIT) + ! -- Define each model do im = 1, basemodellist%Count() mp => GetBaseModelFromList(basemodellist, im) call mp%model_df() end do ! + ! -- synchronize + call run_ctrl%at_stage(STG_AFTER_MDL_DF) + ! ! -- Define each exchange do ic = 1, baseexchangelist%Count() ep => GetBaseExchangeFromList(baseexchangelist, ic) call ep%exg_df() end do ! + ! -- synchronize + call run_ctrl%at_stage(STG_AFTER_EXG_DF) + ! ! -- when needed, this is were the interface models are ! created and added to the numerical solutions call connections_cr() ! + ! -- synchronize + call run_ctrl%at_stage(STG_BEFORE_DF) + ! ! -- Define each connection do ic = 1, baseconnectionlist%Count() - mc => GetSpatialModelConnectionFromList(baseconnectionlist, ic) + mc => get_smc_from_list(baseconnectionlist, ic) call mc%exg_df() end do ! + ! -- synchronize + call run_ctrl%at_stage(STG_AFTER_DF) + ! ! -- Define each solution do is = 1, basesolutionlist%Count() sp => GetBaseSolutionFromList(basesolutionlist, is) @@ -275,7 +286,7 @@ end subroutine simulation_df !! !< subroutine simulation_ar() - use DistributedDataModule + use DistVariableModule ! -- local variables integer(I4B) :: im integer(I4B) :: ic @@ -298,16 +309,16 @@ subroutine simulation_ar() end do ! ! -- Synchronize - call distributed_data%synchronize(0, BEFORE_AR) + call run_ctrl%at_stage(STG_BEFORE_AR) ! ! -- Allocate and read all model connections do ic = 1, baseconnectionlist%Count() - mc => GetSpatialModelConnectionFromList(baseconnectionlist, ic) + mc => get_smc_from_list(baseconnectionlist, ic) call mc%exg_ar() end do ! ! -- Synchronize - call distributed_data%synchronize(0, AFTER_AR) + call run_ctrl%at_stage(STG_AFTER_AR) ! ! -- Allocate and read each solution do is = 1, basesolutionlist%Count() @@ -419,7 +430,7 @@ subroutine Mf6PrepareTimestep() ! ! -- Read and prepare each connection do ic = 1, baseconnectionlist%Count() - mc => GetSpatialModelConnectionFromList(baseconnectionlist, ic) + mc => get_smc_from_list(baseconnectionlist, ic) call mc%exg_rp() end do ! @@ -440,7 +451,7 @@ subroutine Mf6PrepareTimestep() ! ! -- time update for each connection do ic = 1, baseconnectionlist%Count() - mc => GetSpatialModelConnectionFromList(baseconnectionlist, ic) + mc => get_smc_from_list(baseconnectionlist, ic) call mc%exg_calculate_delt() end do ! @@ -598,7 +609,7 @@ function Mf6FinalizeTimestep() result(hasConverged) ! ! -- Write output for each connection do ic = 1, baseconnectionlist%Count() - mc => GetSpatialModelConnectionFromList(baseconnectionlist, ic) + mc => get_smc_from_list(baseconnectionlist, ic) call mc%exg_ot() end do ! diff --git a/src/mf6lists.f90 b/src/mf6lists.f90 index f567dcc8d46..904caddbdc7 100644 --- a/src/mf6lists.f90 +++ b/src/mf6lists.f90 @@ -9,7 +9,7 @@ module ListsModule implicit none private public :: basemodellist, basesolutionlist, solutiongrouplist, & - baseexchangelist, baseconnectionlist, distmodellist + baseexchangelist, baseconnectionlist public :: lists_da ! -- list of all models in simulation @@ -27,9 +27,6 @@ module ListsModule ! -- list of all connections in simulation type(ListType) :: baseconnectionlist - ! -- list of all distributed models - type(ListType) :: distmodellist - contains subroutine lists_da() @@ -46,7 +43,6 @@ subroutine lists_da() call solutiongrouplist%Clear() call baseexchangelist%Clear() call baseconnectionlist%Clear() - call distmodellist%Clear(destroy=.true.) return end subroutine lists_da From fcdebba1fccb7ba11c6c9ec9fd5c66439a3f3609 Mon Sep 17 00:00:00 2001 From: w-bonelli Date: Wed, 8 Mar 2023 09:17:11 -0500 Subject: [PATCH 035/123] fix(GwfGwtExchange): fix error message typo (#1157) --- src/Exchange/GwfGwtExchange.f90 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Exchange/GwfGwtExchange.f90 b/src/Exchange/GwfGwtExchange.f90 index 0b8f60a458e..986b84fd990 100644 --- a/src/Exchange/GwfGwtExchange.f90 +++ b/src/Exchange/GwfGwtExchange.f90 @@ -128,7 +128,7 @@ subroutine set_model_pointers(this) ! -- Verify that gwt model is of the correct type if (.not. associated(gwtmodel)) then write (errmsg, '(3a)') 'Problem with GWF-GWT exchange ', trim(this%name), & - '. Specified GWF Model does not appear to be of the correct type.' + '. Specified GWT Model does not appear to be of the correct type.' call store_error(errmsg, terminate=.true.) end if ! From f6f34efafdcf109ae28ed6775836b87275109c1a Mon Sep 17 00:00:00 2001 From: mjr-deltares <45555666+mjr-deltares@users.noreply.github.com> Date: Wed, 8 Mar 2023 18:52:29 +0100 Subject: [PATCH 036/123] doc(parallel): first version of developer docs for parallel MODFLOW (#1156) * Add parallel developer's docs * Finished first draft of parallel dev docs --- DEVELOPER.md | 4 +- PARALLEL.md | 165 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 168 insertions(+), 1 deletion(-) create mode 100644 PARALLEL.md diff --git a/DEVELOPER.md b/DEVELOPER.md index 39207e4c0f5..d257d6795a7 100644 --- a/DEVELOPER.md +++ b/DEVELOPER.md @@ -1,6 +1,8 @@ # Developing MODFLOW 6 -This document describes how to set up a development environment to modify, build and test MODFLOW 6. Details on how to contribute your code to the repository are found in the separate document [CONTRIBUTING.md](CONTRIBUTING.md). +This document describes how to set up a development environment to modify, build and test MODFLOW 6. Details on how to contribute your code to the repository are found in the separate document [CONTRIBUTING.md](CONTRIBUTING.md). + +To build and test a parallel version of the program, first read the instructions below and then continue in [PARALLEL.md](PARALLEL.md). diff --git a/PARALLEL.md b/PARALLEL.md new file mode 100644 index 00000000000..6b1372b3cbc --- /dev/null +++ b/PARALLEL.md @@ -0,0 +1,165 @@ +# Developing in parallel MODFLOW 6 + +This document describes how to set up your build environment for developing and testing the parallel version of MODFLOW. It further builds on the instructions given in [DEVELOPER.md](DEVELOPER.md) so make sure to read those first. + +--- +**DISCLAIMER** + +*Expectations on platform compatibility* + +The serial version of the MODFLOW 6 program has had no external dependencies and is traditionally available for a variety of platforms (Windows, GNU/linux, macOS) and compatible with the mainstream Fortran compilers (gfortran, ifort). The parallel version comes with dependencies on third party components, most notably the MPI and PETSc libraries. While the goal is a continued support of the above mentioned configurations, this has become more challenging and can generally not be guaranteed. To assist developers as well as end users who are planning to compile the code themselves, a list of successfully tested build configurations will be included in this document. + +--- + + + +## Introduction + +The design philosophy has been to maintain MODFLOW as a single codebase and have it compile to either a serial or a parallel program. The former continues to be a lightweight, highly compatible code which does not require external dependencies other than those provided by the standard compiler libraries. The latter has two distinct dependencies on 3rd party libraries: MPI and PETSc, as described below. Note that the parallel capability is a true extension and the executable will in all cases be capable of *serial* execution with equivalent results. + +## Prerequisites + +### MPI + +The parallel software uses the Message Passing Interface (MPI) to synchronize data between processes. There are a couple of implementations of the MPI standard available. Their applicability usually depends on the platform that is used: + +- Open MPI: https://www.open-mpi.org/ +- MPICH: https://www.mpich.org/ +- Intel MPI: https://www.intel.com/content/www/us/en/developer/tools/oneapi/mpi-library.html +- Microsoft MPI: https://learn.microsoft.com/en-us/message-passing-interface/microsoft-mpi + +On Linux and macOS, if you haven't installed any MPI framework yet, your best bet is to automatically download the MPI implementation upon configuring PETSc with the option flag `--download-openmpi` or `--download-mpich` (see below). Alternatively you can install OpenMPI or MPICH using the package manager of your OS. It general it would be good to check the table below for tested configurations. + +In addition to compiling, the MPI toolset is also required to run a parallel simulation. The implementations above all come with `mpiexec` (or `mpiexec.exe`) to start an executable in parallel. + +### PETSc + +PETSc, the Portable, Extensible Toolkit for Scientific Computation, pronounced PET-see (/ˈpɛt-siː/), is a suite of data structures and routines for the scalable (parallel) solution of scientific applications modeled by partial differential equations: https://petsc.org/release/ + +The PETSc library is used by MODFLOW for its parallel linear solver capabilities and the distributed data formats (vectors and matrices) that go along with it. Parallel PETSc uses MPI internally as well, so setting up this library should typically be coordinated with the installation of the MPI library. A lot of obscure things can happen if the binaries are not compatible, so in general it is a good strategy to compile MPI, PETSc, and MODFLOW with the same compiler toolchain. + +The PETSc website gives details on a large number of configurations, depending on the target platform/OS, and many different ways to configure/make/install the library: https://petsc.org/release/install/. Building on Windows is notoriously challenging and discouraged by the PETSc development team. On Linux, however, it could be as easy as + +``` +$ ./configure --download-openmpi --download-fblaslapack +$ make all +``` + +### pkg-config and setting the `PKG_CONFIG_PATH` variable + +Eventually, the MODFLOW build process has to resolve the installation location of all external dependencies. The pkg-config tool (https://en.wikipedia.org/wiki/Pkg-config) can be used to take care of that. For PETSc, you can check the contents of the folder +``` +$PETSC_DIR/$PETSC_ARCH/lib/pkgconfig/ +``` +and confirm that there are one or more `*.pc` files in there. A similar `pkgconfig/` folder has to present for the MPI installation that was used. For example, for Open MPI on WSL2 this folder is `/lib/x86_64-linux-gnu/pkgconfig/`. + +To connect everything, both of these folder paths have to be added to the `PKG_CONFIG_PATH` variable so that the `pkg-config` executable can resolve the installed libraries. + +## Building + +The primary build system for MODFLOW is Meson (https://mesonbuild.com/). The `meson.build` script takes an additional argument to activate a parallel build of the software. E.g for building and installing a parallel release version: + +``` +meson setup builddir -Ddebug=false -Dparallel=true \ + --prefix=$(pwd) --libdir=bin +meson install -C builddir +meson test --verbose --no-rebuild -C builddir +``` +Note that changing the option flags in the `meson setup` command requires the flag `--reconfigure` to reconfigure the build directory. If the `PKG_CONFIG_PATH` was set as desribed above, the linking to PETSc and MPI is done automatically. + +It's always a good idea to check your `mf6` executable to confirm that it is successfully linked against the external dependencies. You can use the command line tools `ldd` (Linux), `otool` (macOS), or `Dependencies.exe` (Windows, https://github.com/lucasg/Dependencies) to do that. In the list of dependencies, you should be able to identify `libpetsc` and `libmpi` for parallel builds. + +The other build systems in the MODFLOW project (MS Visual Studio, `pymake`, `Makefile`) continue to be supported for *serial* builds only. `pymake` uses the `excludefiles.txt` to ignore those files that can only be build when MPI and PETSc are present on the system. In MS Visual Studio these same files are included in the solution but not in the build process. + +--- + +**IMPORTANT** + +*Don't use MPI and PETSc directly in your code* + +Parallel MODFLOW was designed to have all third party functionality (MPI and PETSc currently) made available through the framework. Developers of models and packages **should not** directly call these libraries and change the set of excluded files described above. If you feel you need to include MPI or PETSc functionality in your code (e.g. you want to `use mpi` in your source file), contact the MODFLOW development team on how to best proceed. + +--- + + +## Testing parallel + +Parallel MODFLOW can be tested using the same test framework as the serial program, with just a few modifications. To run a test inside the `autotest` folder in parallel mode, make sure to add a marker `@pytest.mark.parallel` so that the test is only executed in the Continuous Integration when running a configuration with a parallel build of MODFLOW. + +The `TestSimulation` object that is being run from the framework should be configured for parallel run mode with the flag `parallel=True` and the number of processes `ncpus=...`. As an example, see the test case in `autotest/test_par_gwf01.py`, which can be run with + +``` +$ pytest -s --parallel test_par_gwf01.py +``` +Running without the `--parallel` flag will simply skip the test. + +## Debugging + +The most straightforward way to debug a parallel simulation is to start a run and have it pause to attach the debugger(s). Make sure that the MODFLOW executable was compiled with `-Ddebug=true`. In parallel mode the program uses the PETSc solver and a configuration file `.petscrc` should be present in the same folder as the simulation's `mfsim.nam`. In that PETSc resource file, you should add the following option: + +``` +-wait_dbg +``` +telling MODFLOW to pause immediately after startup. This will give you time to attach one or multiple debuggers to the processes. Then start the parallel program, for example on two cores: + +``` +mpiexec -np 2 mf6 -p +``` +In the process explorer you should now see 2 processes called `mf6` or `mf6.exe`. On the prompt where the command was executed, MODFLOW waits for input: + +``` +$ Hit enter to continue... +``` + +In order to truly debug in parallel, i.e. step through the instructions side-by-side, you will need to start two instances of the debugger and attach them. The following section describes how to do that with VSCode. + +### Debugging with VSCode + +In VSCode parallel debugging is easiest done by duplicating the development environment. First, make sure that you have set up your environment to build a parallel version in debug mode. The VSCode launch.json should contain an entry to attach to a running process: + +``` + { + "name": "Attach to ...", + "type": "cppdbg", + "request": "attach", + "processId": "${command:pickProcess}", + "program": "/mf6", + "MIMode": "gdb", + "miDebuggerPath": "/gdb", + "setupCommands": [ + { + "description": "Enable pretty-printing for gdb", + "text": "-enable-pretty-printing", + "ignoreFailures": true + } + ] + } +``` +After building parallel MODFLOW, press `Ctrl+Shift+p` to execute *Workspaces: Duplicate As Workspace in New Window*. This will open a second VSCode window, identical to the first. Starting the debug process and selecting *"Attach to ..."* pop ups a process selection window with the processes started from the `mpiexec` command described above. Select both, each from their own instance of the VSCode program. Now you can put breakpoints in the code, "Hit enter to continue" on the command prompt, and step through the parallel processes side-by-side. + +--- +**TIP** + +Make sure that you work with gdb versions >= 10. We have found that earlier versions are only partially compatible with the VSCode debugging interface and crash when inspecting the data of Fortran derived types + +--- + + +## Compatibility + +Parallel MODFLOW has been built successfully with the following configurations: + +| Operating System | Toolchain | MPI | PETSc | +|-----------------------|-------------|---------------|--------| +| MS Windows | ? | ? | ? | +| WSL2 (Ubuntu 20.04.5) | gcc 9.4.0 | OpenMPI 4.0.3 | 3.18.2 | +| Ubuntu 22.04 | gcc 9.5.0 | OpenMPI 4.1.4 | 3.18.5 | +| macOS 12.6.3 | gcc 9.5.0 | OpenMPI 4.1.4 | 3.18.5 | + +The most up-to-date configurations are available in the GitHub CI script: `.github/workflows/ci.yml` under the task `parallel_test`. These are being tested upon every change to the `develop` branch of MODFLOW. + +To improve support, we kindly ask you to share your experience with building and running parallel MODFLOW and report back if you have a successful setup that is not in this table. + +## Known issues + +tbd \ No newline at end of file From 1209d62c2624321f93c05bc0c58d67883ebd3183 Mon Sep 17 00:00:00 2001 From: mjr-deltares <45555666+mjr-deltares@users.noreply.github.com> Date: Thu, 9 Mar 2023 14:19:21 +0100 Subject: [PATCH 037/123] File RouterFactory has capital F extension (#1159) --- msvs/mf6core.vfproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/msvs/mf6core.vfproj b/msvs/mf6core.vfproj index 22e157f4801..33070af2fd0 100644 --- a/msvs/mf6core.vfproj +++ b/msvs/mf6core.vfproj @@ -62,7 +62,7 @@ - + From e1c0dc0cd3bfa9e88e7928cc553d17d594cd8db7 Mon Sep 17 00:00:00 2001 From: mjr-deltares <45555666+mjr-deltares@users.noreply.github.com> Date: Thu, 9 Mar 2023 17:41:57 +0100 Subject: [PATCH 038/123] feat(par): check global convergence for parallel solutions (#1158) * to be applied on par-cnvg branch * fprettify --- autotest/test_par_gwf01.py | 37 +++++++++++++++--------------- src/Solution/NumericalSolution.f90 | 21 +++++++++++++---- src/Solution/ParallelSolution.f90 | 29 +++++++++++++++++++++++ 3 files changed, 63 insertions(+), 24 deletions(-) diff --git a/autotest/test_par_gwf01.py b/autotest/test_par_gwf01.py index 4b178074efb..92163a0a96c 100644 --- a/autotest/test_par_gwf01.py +++ b/autotest/test_par_gwf01.py @@ -7,24 +7,22 @@ from simulation import TestSimulation # Test for parallel MODFLOW running on two cpus. -# It contains two coupled models with (nlay,nrow,ncol) = (1,1,5), -# constant head boundaries left=1.0, right=10.0. -# -# idomain: -# -# 'leftmodel' 'rightmodel' -# -# 1 1 1 1 1 1 1 1 1 1 +# It contains two coupled models with +# +# (nlay,nrow,ncol) = (1,1,5), +# (nlay,nrow,ncol) = (1,5,5), +# (nlay,nrow,ncol) = (5,5,5), # +# constant head boundaries left=1.0, right=10.0. # The result should be a uniform flow field. -ex = ["par_gwf01"] +ex = ["par_gwf01-1d", "par_gwf01-2d", "par_gwf01-3d"] +dis_shape = [(1,1,5), (1,5,5), (5,5,5)] # global convenience... name_left = "leftmodel" name_right = "rightmodel" - def get_model(idx, dir): name = ex[idx] @@ -41,9 +39,9 @@ def get_model(idx, dir): hclose, rclose, relax = 10e-9, 1e-3, 0.97 # model spatial discretization - nlay = 1 - nrow = 1 - ncol = 5 + nlay = dis_shape[idx][0] + nrow = dis_shape[idx][1] + ncol = dis_shape[idx][2] # cell spacing delr = 100.0 @@ -55,7 +53,7 @@ def get_model(idx, dir): shift_y = 0.0 # top/bot of the aquifer - tops = [0.0, -100.0, -200] + tops = [0.0, -100.0, -200.0, -300.0, -400.0, -500.0] # hydraulic conductivity k11 = 1.0 @@ -77,7 +75,7 @@ def get_model(idx, dir): ims = flopy.mf6.ModflowIms( sim, - print_option="SUMMARY", + print_option="ALL", outer_dvclose=hclose, outer_maximum=nouter, under_relaxation="DBD", @@ -196,6 +194,7 @@ def build_petsc_db(exdir): with open(petsc_db_file, 'w') as petsc_file: petsc_file.write("-sub_ksp_type bcgs\n") petsc_file.write("-sub_pc_type ilu\n") + petsc_file.write("-dvclose 10e-7\n") petsc_file.write("-options_left no\n") #petsc_file.write("-wait_dbg\n") @@ -219,12 +218,12 @@ def eval_model(sim): @pytest.mark.parallel @pytest.mark.parametrize( - "name", - ex, + "idx, name", + list(enumerate(ex)), ) -def test_mf6model(name, function_tmpdir, targets): +def test_mf6model(idx, name, function_tmpdir, targets): test = TestFramework() - test.build(build_model, 0, str(function_tmpdir)) + test.build(build_model, idx, str(function_tmpdir)) test.run( TestSimulation( name=name, exe_dict=targets, exfunc=eval_model, diff --git a/src/Solution/NumericalSolution.f90 b/src/Solution/NumericalSolution.f90 index 97d1131f877..e3b920d2a31 100644 --- a/src/Solution/NumericalSolution.f90 +++ b/src/Solution/NumericalSolution.f90 @@ -170,6 +170,7 @@ module NumericalSolutionModule procedure, private :: sln_calcdx procedure, private :: sln_underrelax procedure, private :: sln_outer_check + procedure, private :: sln_has_converged procedure, private :: sln_get_loc procedure, private :: sln_get_nodeu procedure, private :: allocate_scalars @@ -1640,11 +1641,9 @@ subroutine solve(this, kiter) ! ! -- check convergence of solution call this%sln_outer_check(this%hncg(kiter), this%lrch(1, kiter)) - if (this%icnvg /= 0) then - this%icnvg = 0 - if (abs(this%hncg(kiter)) <= this%dvclose) then - this%icnvg = 1 - end if + this%icnvg = 0 + if (this%sln_has_converged(this%hncg(kiter))) then + this%icnvg = 1 end if ! ! -- set failure flag @@ -3141,6 +3140,18 @@ subroutine sln_outer_check(this, hncg, lrch) return end subroutine sln_outer_check + function sln_has_converged(this, max_dvc) result(has_converged) + class(NumericalSolutionType) :: this !< NumericalSolutionType instance + real(DP) :: max_dvc !< the maximum dependent variable change + logical(LGP) :: has_converged !< True, when converged + + has_converged = .false. + if (abs(max_dvc) <= this%dvclose) then + has_converged = .true. + end if + + end function sln_has_converged + !> @ brief Get cell location string !! !! Get the cell location string for the provided solution node number. diff --git a/src/Solution/ParallelSolution.f90 b/src/Solution/ParallelSolution.f90 index f5438c9d867..b27711f04e6 100644 --- a/src/Solution/ParallelSolution.f90 +++ b/src/Solution/ParallelSolution.f90 @@ -1,11 +1,40 @@ module ParallelSolutionModule + use KindModule, only: DP, LGP use NumericalSolutionModule, only: NumericalSolutionType + use mpi + use MpiWorldModule implicit none private public :: ParallelSolutionType type, extends(NumericalSolutionType) :: ParallelSolutionType + contains + ! override + procedure, private :: sln_has_converged end type ParallelSolutionType +contains + + !> @brief Check global convergence. The local maximum dependent + !! variable change is reduced over MPI with all other processes + !< that are running this parallel numerical solution. + function sln_has_converged(this, max_dvc) result(has_converged) + class(ParallelSolutionType) :: this !< ParallelSolutionType instance + real(DP) :: max_dvc !< the LOCAL maximum dependent variable change + logical(LGP) :: has_converged !< True, when GLOBALLY converged + ! local + real(DP) :: global_max_dvc + integer :: ierr + + has_converged = .false. + global_max_dvc = huge(0.0) + call MPI_Allreduce(max_dvc, global_max_dvc, 1, MPI_DOUBLE_PRECISION, & + MPI_MIN, MF6_COMM_WORLD, ierr) + if (global_max_dvc <= this%dvclose) then + has_converged = .true. + end if + + end function sln_has_converged + end module ParallelSolutionModule From 304b472e4d9cd802fcccd63bbcc51be262ac1b5a Mon Sep 17 00:00:00 2001 From: mjreno Date: Fri, 10 Mar 2023 11:38:07 -0500 Subject: [PATCH 039/123] feat(idm): update simulation create to source from simnam input context (#1155) * add block_variable to block input definition * add simulation namfile definitions to idm * update idm file loading for block variables and blocks without dimension * add idm simulation module to load simnam input context * update simulation create to source from simnam input context * run fprettify and build_makefiles.py * update distribution build makefie script to exclude petsc dependent source files * some idm refactor and cleanup --------- Co-authored-by: mjreno --- distribution/build_makefiles.py | 2 + doc/mf6io/mf6ivar/dfn/sim-nam.dfn | 1 + make/makefile | 386 ++++---- msvs/mf6core.vfproj | 2 + src/Model/GroundWaterFlow/gwf3dis8idm.f90 | 9 +- src/Model/GroundWaterFlow/gwf3disu8idm.f90 | 18 +- src/Model/GroundWaterFlow/gwf3disv8idm.f90 | 15 +- src/Model/GroundWaterFlow/gwf3npf8idm.f90 | 6 +- src/Model/GroundWaterTransport/gwt1dspidm.f90 | 6 +- src/SimulationCreate.f90 | 829 +++++++++--------- src/Utilities/Idm/IdmMf6FileLoader.f90 | 64 +- src/Utilities/Idm/IdmSimulation.f90 | 176 ++++ src/Utilities/Idm/InputDefinition.f90 | 1 + src/Utilities/Idm/InputDefinitionSelector.f90 | 9 + src/Utilities/Idm/LoadMf6FileType.f90 | 139 ++- src/Utilities/Idm/ModflowInput.f90 | 16 +- src/Utilities/Idm/StructArray.f90 | 508 +++++++++-- src/Utilities/Idm/StructVector.f90 | 6 +- src/meson.build | 2 + src/mf6core.f90 | 55 ++ src/simnamidm.f90 | 397 +++++++++ utils/idmloader/scripts/dfn2f90.py | 18 +- 22 files changed, 1879 insertions(+), 786 deletions(-) create mode 100644 src/Utilities/Idm/IdmSimulation.f90 create mode 100644 src/simnamidm.f90 diff --git a/distribution/build_makefiles.py b/distribution/build_makefiles.py index e4e86be9750..ab06a902b14 100644 --- a/distribution/build_makefiles.py +++ b/distribution/build_makefiles.py @@ -43,6 +43,7 @@ def run_makefile(target): def build_mf6_makefile(): target = "mf6" + excludefiles = str(_project_root_path / "pymake" / "excludefiles.txt") print(f"Creating makefile for {target}") with set_dir(_project_root_path / "make"): pymake.main( @@ -50,6 +51,7 @@ def build_mf6_makefile(): target=target, appdir=str(_project_root_path / "bin"), include_subdirs=True, + excludefiles=excludefiles, inplace=True, dryrun=True, makefile=True, diff --git a/doc/mf6io/mf6ivar/dfn/sim-nam.dfn b/doc/mf6io/mf6ivar/dfn/sim-nam.dfn index 55d93c16411..5a2e1693c4a 100644 --- a/doc/mf6io/mf6ivar/dfn/sim-nam.dfn +++ b/doc/mf6io/mf6ivar/dfn/sim-nam.dfn @@ -23,6 +23,7 @@ reader urword optional true longname memory print option description is a flag that controls printing of detailed memory manager usage to the end of the simulation list file. NONE means do not print detailed information. SUMMARY means print only the total memory for each simulation component. ALL means print information for each variable stored in the memory manager. NONE is default if MEMORY\_PRINT\_OPTION is not specified. +mf6internal prmem block options name maxerrors diff --git a/make/makefile b/make/makefile index 08ba032bdd5..d97c2c84dca 100644 --- a/make/makefile +++ b/make/makefile @@ -5,33 +5,33 @@ include ./makedefaults # Define the source file directories SOURCEDIR1=../src -SOURCEDIR2=../src/Exchange -SOURCEDIR3=../src/Model -SOURCEDIR4=../src/Model/Geometry -SOURCEDIR5=../src/Model/ModelUtilities -SOURCEDIR6=../src/Model/Connection -SOURCEDIR7=../src/Model/GroundWaterTransport -SOURCEDIR8=../src/Model/GroundWaterFlow -SOURCEDIR9=../src/Distributed +SOURCEDIR2=../src/Distributed +SOURCEDIR3=../src/Exchange +SOURCEDIR4=../src/Model +SOURCEDIR5=../src/Model/Connection +SOURCEDIR6=../src/Model/Geometry +SOURCEDIR7=../src/Model/GroundWaterFlow +SOURCEDIR8=../src/Model/GroundWaterTransport +SOURCEDIR9=../src/Model/ModelUtilities SOURCEDIR10=../src/Solution -SOURCEDIR11=../src/Solution/PETSc -SOURCEDIR12=../src/Solution/LinearMethods +SOURCEDIR11=../src/Solution/LinearMethods +SOURCEDIR12=../src/Solution/PETSc SOURCEDIR13=../src/Timing SOURCEDIR14=../src/Utilities -SOURCEDIR15=../src/Utilities/TimeSeries -SOURCEDIR16=../src/Utilities/Libraries -SOURCEDIR17=../src/Utilities/Libraries/rcm -SOURCEDIR18=../src/Utilities/Libraries/sparsekit -SOURCEDIR19=../src/Utilities/Libraries/sparskit2 -SOURCEDIR20=../src/Utilities/Libraries/blas -SOURCEDIR21=../src/Utilities/Libraries/daglib -SOURCEDIR22=../src/Utilities/Idm +SOURCEDIR15=../src/Utilities/ArrayRead +SOURCEDIR16=../src/Utilities/Idm +SOURCEDIR17=../src/Utilities/Libraries +SOURCEDIR18=../src/Utilities/Libraries/blas +SOURCEDIR19=../src/Utilities/Libraries/daglib +SOURCEDIR20=../src/Utilities/Libraries/rcm +SOURCEDIR21=../src/Utilities/Libraries/sparsekit +SOURCEDIR22=../src/Utilities/Libraries/sparskit2 SOURCEDIR23=../src/Utilities/Matrix -SOURCEDIR24=../src/Utilities/Vector +SOURCEDIR24=../src/Utilities/Memory SOURCEDIR25=../src/Utilities/Observation SOURCEDIR26=../src/Utilities/OutputControl -SOURCEDIR27=../src/Utilities/Memory -SOURCEDIR28=../src/Utilities/ArrayRead +SOURCEDIR27=../src/Utilities/TimeSeries +SOURCEDIR28=../src/Utilities/Vector VPATH = \ ${SOURCEDIR1} \ @@ -66,222 +66,224 @@ ${SOURCEDIR28} .SUFFIXES: .f90 .F90 .o OBJECTS = \ -$(OBJDIR)/sparsekit.o \ -$(OBJDIR)/ilut.o \ -$(OBJDIR)/blas1_d.o \ $(OBJDIR)/kind.o \ -$(OBJDIR)/Sparse.o \ -$(OBJDIR)/dag_module.o \ -$(OBJDIR)/OpenSpec.o \ -$(OBJDIR)/GwtAdvOptions.o \ -$(OBJDIR)/InputDefinition.o \ -$(OBJDIR)/rcm.o \ -$(OBJDIR)/VectorBase.o \ -$(OBJDIR)/gwf3dis8idm.o \ -$(OBJDIR)/CharString.o \ -$(OBJDIR)/ims8reordering.o \ $(OBJDIR)/Constants.o \ -$(OBJDIR)/BaseGeometry.o \ -$(OBJDIR)/compilerversion.o \ -$(OBJDIR)/gwf3disu8idm.o \ -$(OBJDIR)/GwfBuyInputData.o \ -$(OBJDIR)/HashTable.o \ -$(OBJDIR)/SimStages.o \ -$(OBJDIR)/defmacro.o \ -$(OBJDIR)/CsrUtils.o \ $(OBJDIR)/SimVariables.o \ $(OBJDIR)/genericutils.o \ -$(OBJDIR)/gwf3npf8idm.o \ -$(OBJDIR)/gwt1dspidm.o \ -$(OBJDIR)/Xt3dAlgorithm.o \ -$(OBJDIR)/Timer.o \ -$(OBJDIR)/SfrCrossSectionUtils.o \ -$(OBJDIR)/List.o \ -$(OBJDIR)/ims8misc.o \ -$(OBJDIR)/MatrixBase.o \ -$(OBJDIR)/LinearSolverBase.o \ -$(OBJDIR)/gwf3disv8idm.o \ -$(OBJDIR)/GwtDspOptions.o \ -$(OBJDIR)/IdmLogger.o \ -$(OBJDIR)/SmoothingFunctions.o \ -$(OBJDIR)/VirtualDataLists.o \ -$(OBJDIR)/StringList.o \ -$(OBJDIR)/version.o \ -$(OBJDIR)/GwfStorageUtils.o \ +$(OBJDIR)/compilerversion.o \ $(OBJDIR)/ArrayHandlers.o \ +$(OBJDIR)/version.o \ $(OBJDIR)/Message.o \ -$(OBJDIR)/GwfVscInputData.o \ -$(OBJDIR)/mf6lists.o \ -$(OBJDIR)/IndexMap.o \ -$(OBJDIR)/GwfNpfOptions.o \ -$(OBJDIR)/ObsOutput.o \ -$(OBJDIR)/TimeSeriesRecord.o \ +$(OBJDIR)/defmacro.o \ $(OBJDIR)/Sim.o \ -$(OBJDIR)/InterfaceMap.o \ -$(OBJDIR)/MemoryHelper.o \ -$(OBJDIR)/DistributedVariable.o \ -$(OBJDIR)/sort.o \ +$(OBJDIR)/OpenSpec.o \ $(OBJDIR)/InputOutput.o \ -$(OBJDIR)/CircularGeometry.o \ -$(OBJDIR)/ObsOutputList.o \ -$(OBJDIR)/Iunit.o \ -$(OBJDIR)/RectangularGeometry.o \ -$(OBJDIR)/DisvGeom.o \ -$(OBJDIR)/ArrayReaders.o \ -$(OBJDIR)/BlockParser.o \ -$(OBJDIR)/Budget.o \ -$(OBJDIR)/BudgetFileReader.o \ -$(OBJDIR)/PrintSaveManager.o \ -$(OBJDIR)/STLVecInt.o \ -$(OBJDIR)/comarg.o \ -$(OBJDIR)/HeadFileReader.o \ -$(OBJDIR)/InputDefinitionSelector.o \ $(OBJDIR)/TableTerm.o \ -$(OBJDIR)/ModflowInput.o \ -$(OBJDIR)/ArrayReaderBase.o \ -$(OBJDIR)/NameFile.o \ -$(OBJDIR)/StructVector.o \ -$(OBJDIR)/Double2dReader.o \ -$(OBJDIR)/Integer2dReader.o \ -$(OBJDIR)/TimeSeries.o \ -$(OBJDIR)/TimeSeriesFileList.o \ -$(OBJDIR)/ims8base.o \ -$(OBJDIR)/TimeSeriesLink.o \ $(OBJDIR)/Table.o \ -$(OBJDIR)/Double1dReader.o \ -$(OBJDIR)/Integer1dReader.o \ -$(OBJDIR)/LayeredArrayReader.o \ -$(OBJDIR)/ListReader.o \ -$(OBJDIR)/SfrCrossSectionManager.o \ +$(OBJDIR)/MemoryHelper.o \ +$(OBJDIR)/CharString.o \ $(OBJDIR)/Memory.o \ +$(OBJDIR)/List.o \ $(OBJDIR)/MemoryList.o \ +$(OBJDIR)/TimeSeriesRecord.o \ +$(OBJDIR)/BlockParser.o \ $(OBJDIR)/MemoryManager.o \ -$(OBJDIR)/Connections.o \ +$(OBJDIR)/TimeSeries.o \ $(OBJDIR)/ats.o \ -$(OBJDIR)/MemoryManagerExt.o \ -$(OBJDIR)/MemorySetHandler.o \ +$(OBJDIR)/TimeSeriesLink.o \ +$(OBJDIR)/TimeSeriesFileList.o \ $(OBJDIR)/tdis.o \ -$(OBJDIR)/ims8linear.o \ -$(OBJDIR)/SeqVector.o \ -$(OBJDIR)/PackageBudget.o \ -$(OBJDIR)/StructArray.o \ -$(OBJDIR)/MappedMemory.o \ -$(OBJDIR)/BaseModel.o \ -$(OBJDIR)/SparseMatrix.o \ -$(OBJDIR)/GwfMvrPeriodData.o \ -$(OBJDIR)/VirtualBase.o \ -$(OBJDIR)/VirtualDataContainer.o \ -$(OBJDIR)/BaseExchange.o \ -$(OBJDIR)/PackageMover.o \ +$(OBJDIR)/HashTable.o \ +$(OBJDIR)/VectorBase.o \ +$(OBJDIR)/Sparse.o \ +$(OBJDIR)/DisvGeom.o \ +$(OBJDIR)/ArrayReaders.o \ $(OBJDIR)/TimeSeriesManager.o \ +$(OBJDIR)/SmoothingFunctions.o \ +$(OBJDIR)/MatrixBase.o \ +$(OBJDIR)/ListReader.o \ +$(OBJDIR)/Connections.o \ +$(OBJDIR)/InputDefinition.o \ +$(OBJDIR)/ArrayReaderBase.o \ $(OBJDIR)/DiscretizationBase.o \ -$(OBJDIR)/UzfCellGroup.o \ -$(OBJDIR)/BaseSolution.o \ -$(OBJDIR)/OutputControlData.o \ -$(OBJDIR)/LoadMf6FileType.o \ +$(OBJDIR)/STLVecInt.o \ +$(OBJDIR)/simnamidm.o \ +$(OBJDIR)/gwt1dspidm.o \ +$(OBJDIR)/gwf3npf8idm.o \ +$(OBJDIR)/gwf3disv8idm.o \ +$(OBJDIR)/gwf3disu8idm.o \ +$(OBJDIR)/gwf3dis8idm.o \ +$(OBJDIR)/Integer2dReader.o \ $(OBJDIR)/TimeArray.o \ -$(OBJDIR)/BudgetTerm.o \ -$(OBJDIR)/ImsLinearSolver.o \ -$(OBJDIR)/Mover.o \ -$(OBJDIR)/LinearSolverFactory.o \ -$(OBJDIR)/VirtualSolution.o \ -$(OBJDIR)/NumericalPackage.o \ -$(OBJDIR)/Xt3dInterface.o \ -$(OBJDIR)/Observe.o \ -$(OBJDIR)/IdmMf6FileLoader.o \ -$(OBJDIR)/SolutionGroup.o \ -$(OBJDIR)/OutputControl.o \ -$(OBJDIR)/gwf3tvbase8.o \ -$(OBJDIR)/gwf3ic8.o \ -$(OBJDIR)/BudgetObject.o \ -$(OBJDIR)/gwf3dis8.o \ -$(OBJDIR)/gwf3tvs8.o \ +$(OBJDIR)/ObsOutput.o \ +$(OBJDIR)/StructVector.o \ +$(OBJDIR)/IdmLogger.o \ +$(OBJDIR)/InputDefinitionSelector.o \ +$(OBJDIR)/Integer1dReader.o \ +$(OBJDIR)/Double2dReader.o \ +$(OBJDIR)/Double1dReader.o \ $(OBJDIR)/TimeArraySeries.o \ -$(OBJDIR)/gwf3tvk8.o \ -$(OBJDIR)/RouterBase.o \ +$(OBJDIR)/ObsOutputList.o \ +$(OBJDIR)/Observe.o \ +$(OBJDIR)/StructArray.o \ +$(OBJDIR)/ModflowInput.o \ +$(OBJDIR)/LayeredArrayReader.o \ $(OBJDIR)/TimeArraySeriesLink.o \ -$(OBJDIR)/gwf3disu8.o \ +$(OBJDIR)/ObsUtility.o \ $(OBJDIR)/ObsContainer.o \ -$(OBJDIR)/gwf3mvr8.o \ +$(OBJDIR)/BudgetFileReader.o \ +$(OBJDIR)/LoadMf6FileType.o \ $(OBJDIR)/TimeArraySeriesManager.o \ -$(OBJDIR)/gwf3disv8.o \ -$(OBJDIR)/ObsUtility.o \ -$(OBJDIR)/GwtSpc.o \ -$(OBJDIR)/gwt1ic1.o \ -$(OBJDIR)/gwf3oc8.o \ -$(OBJDIR)/gwt1oc1.o \ -$(OBJDIR)/gwf3sto8.o \ -$(OBJDIR)/SerialRouter.o \ -$(OBJDIR)/RouterFactory.o \ +$(OBJDIR)/PackageMover.o \ $(OBJDIR)/Obs3.o \ -$(OBJDIR)/gwf3obs8.o \ +$(OBJDIR)/NumericalPackage.o \ +$(OBJDIR)/Budget.o \ +$(OBJDIR)/sort.o \ +$(OBJDIR)/SfrCrossSectionUtils.o \ +$(OBJDIR)/BudgetTerm.o \ +$(OBJDIR)/VirtualBase.o \ +$(OBJDIR)/IdmMf6FileLoader.o \ $(OBJDIR)/BoundaryPackage.o \ -$(OBJDIR)/gwf3csub8.o \ +$(OBJDIR)/BaseModel.o \ +$(OBJDIR)/SfrCrossSectionManager.o \ +$(OBJDIR)/dag_module.o \ +$(OBJDIR)/BudgetObject.o \ +$(OBJDIR)/VirtualDataLists.o \ +$(OBJDIR)/VirtualDataContainer.o \ +$(OBJDIR)/SimStages.o \ $(OBJDIR)/NumericalModel.o \ -$(OBJDIR)/gwt1cnc1.o \ -$(OBJDIR)/gwf3api8.o \ -$(OBJDIR)/GhostNode.o \ -$(OBJDIR)/gwf3rch8.o \ -$(OBJDIR)/gwf3chd8.o \ +$(OBJDIR)/PackageBudget.o \ +$(OBJDIR)/HeadFileReader.o \ +$(OBJDIR)/PrintSaveManager.o \ +$(OBJDIR)/Xt3dAlgorithm.o \ +$(OBJDIR)/gwf3tvbase8.o \ +$(OBJDIR)/gwf3sfr8.o \ $(OBJDIR)/gwf3riv8.o \ +$(OBJDIR)/gwf3maw8.o \ +$(OBJDIR)/mf6lists.o \ +$(OBJDIR)/gwf3lak8.o \ +$(OBJDIR)/GwfVscInputData.o \ +$(OBJDIR)/gwf3ghb8.o \ +$(OBJDIR)/gwf3drn8.o \ +$(OBJDIR)/VirtualModel.o \ +$(OBJDIR)/BaseExchange.o \ +$(OBJDIR)/UzfCellGroup.o \ $(OBJDIR)/gwt1fmi1.o \ -$(OBJDIR)/gwf3wel8.o \ +$(OBJDIR)/OutputControlData.o \ +$(OBJDIR)/gwf3ic8.o \ +$(OBJDIR)/Xt3dInterface.o \ +$(OBJDIR)/gwf3tvk8.o \ +$(OBJDIR)/MemoryManagerExt.o \ +$(OBJDIR)/gwf3vsc8.o \ +$(OBJDIR)/GwfNpfOptions.o \ +$(OBJDIR)/SeqVector.o \ +$(OBJDIR)/IndexMap.o \ +$(OBJDIR)/CellWithNbrs.o \ $(OBJDIR)/NumericalExchange.o \ -$(OBJDIR)/gwt1ssm1.o \ -$(OBJDIR)/gwt1src1.o \ -$(OBJDIR)/NumericalSolution.o \ -$(OBJDIR)/gwf3sfr8.o \ -$(OBJDIR)/SolutionFactory.o \ -$(OBJDIR)/gwf3drn8.o \ -$(OBJDIR)/gwt1obs1.o \ +$(OBJDIR)/Iunit.o \ $(OBJDIR)/gwf3uzf8.o \ -$(OBJDIR)/gwf3evt8.o \ -$(OBJDIR)/gwf3ghb8.o \ -$(OBJDIR)/gwt1mst1.o \ -$(OBJDIR)/gwt1adv1.o \ $(OBJDIR)/gwt1apt1.o \ -$(OBJDIR)/gwf3lak8.o \ -$(OBJDIR)/gwt1mvt1.o \ -$(OBJDIR)/gwf3maw8.o \ -$(OBJDIR)/VirtualModel.o \ -$(OBJDIR)/TransportModel.o \ +$(OBJDIR)/GwtSpc.o \ +$(OBJDIR)/OutputControl.o \ +$(OBJDIR)/gwt1ic1.o \ +$(OBJDIR)/gwt1mst1.o \ +$(OBJDIR)/GwtDspOptions.o \ +$(OBJDIR)/gwf3npf8.o \ +$(OBJDIR)/GwtAdvOptions.o \ +$(OBJDIR)/gwf3tvs8.o \ +$(OBJDIR)/GwfStorageUtils.o \ +$(OBJDIR)/Mover.o \ +$(OBJDIR)/GwfMvrPeriodData.o \ +$(OBJDIR)/ims8misc.o \ +$(OBJDIR)/GwfBuyInputData.o \ +$(OBJDIR)/VirtualSolution.o \ +$(OBJDIR)/SparseMatrix.o \ +$(OBJDIR)/LinearSolverBase.o \ +$(OBJDIR)/ims8reordering.o \ +$(OBJDIR)/VirtualExchange.o \ +$(OBJDIR)/InterfaceMap.o \ +$(OBJDIR)/gwf3disu8.o \ +$(OBJDIR)/GridSorting.o \ $(OBJDIR)/DisConnExchange.o \ -$(OBJDIR)/gwf3vsc8.o \ -$(OBJDIR)/VirtualGwtModel.o \ -$(OBJDIR)/gwt1ist1.o \ -$(OBJDIR)/CellWithNbrs.o \ -$(OBJDIR)/gwt1sft1.o \ +$(OBJDIR)/CsrUtils.o \ +$(OBJDIR)/TransportModel.o \ +$(OBJDIR)/NameFile.o \ $(OBJDIR)/gwt1uzt1.o \ -$(OBJDIR)/GridSorting.o \ -$(OBJDIR)/VirtualExchange.o \ -$(OBJDIR)/gwf3hfb8.o \ -$(OBJDIR)/VirtualGwfExchange.o \ +$(OBJDIR)/gwt1ssm1.o \ +$(OBJDIR)/gwt1src1.o \ +$(OBJDIR)/gwt1sft1.o \ +$(OBJDIR)/gwt1oc1.o \ +$(OBJDIR)/gwt1obs1.o \ $(OBJDIR)/gwt1mwt1.o \ -$(OBJDIR)/VirtualGwfModel.o \ +$(OBJDIR)/gwt1mvt1.o \ $(OBJDIR)/gwt1lkt1.o \ -$(OBJDIR)/VirtualGwtExchange.o \ -$(OBJDIR)/gwf3npf8.o \ +$(OBJDIR)/gwt1ist1.o \ $(OBJDIR)/gwt1dsp.o \ +$(OBJDIR)/gwt1cnc1.o \ +$(OBJDIR)/gwt1adv1.o \ +$(OBJDIR)/gwf3disv8.o \ +$(OBJDIR)/gwf3dis8.o \ +$(OBJDIR)/gwf3api8.o \ +$(OBJDIR)/gwf3wel8.o \ +$(OBJDIR)/gwf3rch8.o \ +$(OBJDIR)/gwf3sto8.o \ +$(OBJDIR)/gwf3oc8.o \ +$(OBJDIR)/gwf3obs8.o \ +$(OBJDIR)/gwf3mvr8.o \ +$(OBJDIR)/gwf3hfb8.o \ +$(OBJDIR)/gwf3csub8.o \ $(OBJDIR)/gwf3buy8.o \ +$(OBJDIR)/GhostNode.o \ +$(OBJDIR)/gwf3evt8.o \ +$(OBJDIR)/gwf3chd8.o \ +$(OBJDIR)/RouterBase.o \ +$(OBJDIR)/ImsLinearSolver.o \ +$(OBJDIR)/ims8base.o \ $(OBJDIR)/GridConnection.o \ -$(OBJDIR)/SpatialModelConnection.o \ +$(OBJDIR)/DistributedVariable.o \ $(OBJDIR)/gwt1.o \ $(OBJDIR)/gwf3.o \ -$(OBJDIR)/GwfGwfExchange.o \ -$(OBJDIR)/Mapper.o \ +$(OBJDIR)/SerialRouter.o \ +$(OBJDIR)/Timer.o \ +$(OBJDIR)/LinearSolverFactory.o \ +$(OBJDIR)/ims8linear.o \ +$(OBJDIR)/BaseSolution.o \ +$(OBJDIR)/SpatialModelConnection.o \ +$(OBJDIR)/GwtInterfaceModel.o \ $(OBJDIR)/GwtGwtExchange.o \ -$(OBJDIR)/VirtualDataManager.o \ $(OBJDIR)/GwfInterfaceModel.o \ -$(OBJDIR)/GwtInterfaceModel.o \ -$(OBJDIR)/RunControl.o \ -$(OBJDIR)/RunControlFactory.o \ -$(OBJDIR)/GwfGwfConnection.o \ +$(OBJDIR)/GwfGwfExchange.o \ +$(OBJDIR)/RouterFactory.o \ +$(OBJDIR)/NumericalSolution.o \ +$(OBJDIR)/MappedMemory.o \ $(OBJDIR)/GwtGwtConnection.o \ +$(OBJDIR)/GwfGwfConnection.o \ +$(OBJDIR)/VirtualDataManager.o \ +$(OBJDIR)/Mapper.o \ +$(OBJDIR)/VirtualGwtModel.o \ +$(OBJDIR)/VirtualGwtExchange.o \ +$(OBJDIR)/VirtualGwfModel.o \ +$(OBJDIR)/VirtualGwfExchange.o \ +$(OBJDIR)/SolutionGroup.o \ +$(OBJDIR)/SolutionFactory.o \ $(OBJDIR)/GwfGwtExchange.o \ -$(OBJDIR)/ConnectionBuilder.o \ +$(OBJDIR)/RunControl.o \ $(OBJDIR)/SimulationCreate.o \ +$(OBJDIR)/RunControlFactory.o \ +$(OBJDIR)/IdmSimulation.o \ +$(OBJDIR)/ConnectionBuilder.o \ +$(OBJDIR)/comarg.o \ $(OBJDIR)/mf6core.o \ -$(OBJDIR)/mf6.o +$(OBJDIR)/BaseGeometry.o \ +$(OBJDIR)/mf6.o \ +$(OBJDIR)/StringList.o \ +$(OBJDIR)/MemorySetHandler.o \ +$(OBJDIR)/ilut.o \ +$(OBJDIR)/sparsekit.o \ +$(OBJDIR)/rcm.o \ +$(OBJDIR)/blas1_d.o \ +$(OBJDIR)/RectangularGeometry.o \ +$(OBJDIR)/CircularGeometry.o # Define the objects that make up the program $(PROGRAM) : $(OBJECTS) diff --git a/msvs/mf6core.vfproj b/msvs/mf6core.vfproj index 33070af2fd0..98edc9d2d55 100644 --- a/msvs/mf6core.vfproj +++ b/msvs/mf6core.vfproj @@ -224,6 +224,7 @@ + @@ -322,6 +323,7 @@ + diff --git a/src/Model/GroundWaterFlow/gwf3dis8idm.f90 b/src/Model/GroundWaterFlow/gwf3dis8idm.f90 index c75fd75a284..56404996a00 100644 --- a/src/Model/GroundWaterFlow/gwf3dis8idm.f90 +++ b/src/Model/GroundWaterFlow/gwf3dis8idm.f90 @@ -261,17 +261,20 @@ module GwfDisInputModule InputBlockDefinitionType( & 'OPTIONS', & ! blockname .false., & ! required - .false. & ! aggregate + .false., & ! aggregate + .false. & ! block_variable ), & InputBlockDefinitionType( & 'DIMENSIONS', & ! blockname .true., & ! required - .false. & ! aggregate + .false., & ! aggregate + .false. & ! block_variable ), & InputBlockDefinitionType( & 'GRIDDATA', & ! blockname .true., & ! required - .false. & ! aggregate + .false., & ! aggregate + .false. & ! block_variable ) & ] diff --git a/src/Model/GroundWaterFlow/gwf3disu8idm.f90 b/src/Model/GroundWaterFlow/gwf3disu8idm.f90 index 42245c2ae42..9dd099b4f7b 100644 --- a/src/Model/GroundWaterFlow/gwf3disu8idm.f90 +++ b/src/Model/GroundWaterFlow/gwf3disu8idm.f90 @@ -546,32 +546,38 @@ module GwfDisuInputModule InputBlockDefinitionType( & 'OPTIONS', & ! blockname .false., & ! required - .false. & ! aggregate + .false., & ! aggregate + .false. & ! block_variable ), & InputBlockDefinitionType( & 'DIMENSIONS', & ! blockname .true., & ! required - .false. & ! aggregate + .false., & ! aggregate + .false. & ! block_variable ), & InputBlockDefinitionType( & 'GRIDDATA', & ! blockname .true., & ! required - .false. & ! aggregate + .false., & ! aggregate + .false. & ! block_variable ), & InputBlockDefinitionType( & 'CONNECTIONDATA', & ! blockname .true., & ! required - .false. & ! aggregate + .false., & ! aggregate + .false. & ! block_variable ), & InputBlockDefinitionType( & 'VERTICES', & ! blockname .true., & ! required - .true. & ! aggregate + .true., & ! aggregate + .false. & ! block_variable ), & InputBlockDefinitionType( & 'CELL2D', & ! blockname .true., & ! required - .true. & ! aggregate + .true., & ! aggregate + .false. & ! block_variable ) & ] diff --git a/src/Model/GroundWaterFlow/gwf3disv8idm.f90 b/src/Model/GroundWaterFlow/gwf3disv8idm.f90 index 86e553c1dfc..40c61e57643 100644 --- a/src/Model/GroundWaterFlow/gwf3disv8idm.f90 +++ b/src/Model/GroundWaterFlow/gwf3disv8idm.f90 @@ -402,27 +402,32 @@ module GwfDisvInputModule InputBlockDefinitionType( & 'OPTIONS', & ! blockname .false., & ! required - .false. & ! aggregate + .false., & ! aggregate + .false. & ! block_variable ), & InputBlockDefinitionType( & 'DIMENSIONS', & ! blockname .true., & ! required - .false. & ! aggregate + .false., & ! aggregate + .false. & ! block_variable ), & InputBlockDefinitionType( & 'GRIDDATA', & ! blockname .true., & ! required - .false. & ! aggregate + .false., & ! aggregate + .false. & ! block_variable ), & InputBlockDefinitionType( & 'VERTICES', & ! blockname .true., & ! required - .true. & ! aggregate + .true., & ! aggregate + .false. & ! block_variable ), & InputBlockDefinitionType( & 'CELL2D', & ! blockname .true., & ! required - .true. & ! aggregate + .true., & ! aggregate + .false. & ! block_variable ) & ] diff --git a/src/Model/GroundWaterFlow/gwf3npf8idm.f90 b/src/Model/GroundWaterFlow/gwf3npf8idm.f90 index 696cb2cf032..d435c10073f 100644 --- a/src/Model/GroundWaterFlow/gwf3npf8idm.f90 +++ b/src/Model/GroundWaterFlow/gwf3npf8idm.f90 @@ -693,12 +693,14 @@ module GwfNpfInputModule InputBlockDefinitionType( & 'OPTIONS', & ! blockname .false., & ! required - .false. & ! aggregate + .false., & ! aggregate + .false. & ! block_variable ), & InputBlockDefinitionType( & 'GRIDDATA', & ! blockname .true., & ! required - .false. & ! aggregate + .false., & ! aggregate + .false. & ! block_variable ) & ] diff --git a/src/Model/GroundWaterTransport/gwt1dspidm.f90 b/src/Model/GroundWaterTransport/gwt1dspidm.f90 index 323346d5d56..5ee187e6102 100644 --- a/src/Model/GroundWaterTransport/gwt1dspidm.f90 +++ b/src/Model/GroundWaterTransport/gwt1dspidm.f90 @@ -171,12 +171,14 @@ module GwtDspInputModule InputBlockDefinitionType( & 'OPTIONS', & ! blockname .false., & ! required - .false. & ! aggregate + .false., & ! aggregate + .false. & ! block_variable ), & InputBlockDefinitionType( & 'GRIDDATA', & ! blockname .false., & ! required - .false. & ! aggregate + .false., & ! aggregate + .false. & ! block_variable ) & ] diff --git a/src/SimulationCreate.f90 b/src/SimulationCreate.f90 index bda9b7efe35..f89de79315d 100644 --- a/src/SimulationCreate.f90 +++ b/src/SimulationCreate.f90 @@ -2,12 +2,12 @@ module SimulationCreateModule use KindModule, only: DP, I4B, LGP, write_kindinfo use ConstantsModule, only: LINELENGTH, LENMODELNAME, LENBIGLINE, & - DZERO, LENEXCHANGENAME - use SimVariablesModule, only: simfile, simlstfile, iout, simulation_mode, & + DZERO, LENEXCHANGENAME, LENMEMPATH, LENPACKAGETYPE + use SimVariablesModule, only: iout, simulation_mode, & proc_id, nr_procs, model_names, model_loc_idx use GenericUtilitiesModule, only: sim_message, write_centered use SimModule, only: store_error, count_errors, & - store_error_unit, MaxErrors + store_error_filename, MaxErrors use VersionModule, only: write_listfile_header use InputOutputModule, only: getunit, urword, openfile use ArrayHandlersModule, only: expandarray, ifind @@ -19,7 +19,6 @@ module SimulationCreateModule use ListsModule, only: basesolutionlist, basemodellist, & solutiongrouplist, baseexchangelist use BaseModelModule, only: GetBaseModelFromList - use BlockParserModule, only: BlockParserType use ListModule, only: ListType implicit none @@ -27,9 +26,6 @@ module SimulationCreateModule public :: simulation_cr public :: simulation_da - integer(I4B) :: inunit = 0 - type(BlockParserType) :: parser - contains !> @brief Read the simulation name file and initialize the models, exchanges @@ -37,27 +33,10 @@ module SimulationCreateModule subroutine simulation_cr() ! -- modules ! -- local - character(len=LINELENGTH) :: line ! ------------------------------------------------------------------------------ ! - ! -- initialize iout - iout = 0 - ! - ! -- Open simulation list file - iout = getunit() - if (nr_procs > 1) then - write (simlstfile, '(a,i0,a)') 'mfsim.p', proc_id, '.lst' - end if - call openfile(iout, 0, simlstfile, 'LIST', filstat_opt='REPLACE') - ! - ! -- write simlstfile to stdout - write (line, '(2(1x,A))') 'Writing simulation list file:', & - trim(adjustl(simlstfile)) - call sim_message(line) - call write_listfile_header(iout) - ! - ! -- Read the simulation name file and create objects - call read_simulation_namefile(trim(adjustl(simfile))) + ! -- Source simulation nam input context and create objects + call source_simulation_nam() ! ! -- Return return @@ -67,8 +46,13 @@ end subroutine simulation_cr !< subroutine simulation_da() ! -- modules + use MemoryManagerExtModule, only: memorylist_remove + use SimVariablesModule, only: idm_context ! -- local ! ------------------------------------------------------------------------------ + ! + ! -- Deallocate input memory + call memorylist_remove('SIM', 'NAM', idm_context) ! ! -- variables deallocate (model_names) @@ -77,30 +61,17 @@ subroutine simulation_da() return end subroutine simulation_da - !> @brief Read the simulation name file + !> @brief Source the simulation name file !! - !! Read the simulation name file and initialize the models, exchanges, - !! solutions, solutions groups. Then add the exchanges to the appropriate - !! solutions. + !! Source from the simulation nam input context to initialize the models, + !! exchanges, solutions, solutions groups. Then add the exchanges to + !! the appropriate solutions. !! !< - subroutine read_simulation_namefile(namfile) + subroutine source_simulation_nam() ! -- dummy - character(len=*), intent(in) :: namfile !< simulation name file ! -- local - character(len=LINELENGTH) :: line ! ------------------------------------------------------------------------------ - ! - ! -- Open simulation name file - inunit = getunit() - call openfile(inunit, iout, namfile, 'NAM') - ! - ! -- write name of namfile to stdout - write (line, '(2(1x,a))') 'Using Simulation name file:', namfile - call sim_message(line, skipafter=1) - ! - ! -- Initialize block parser - call parser%Initialize(inunit, iout) ! ! -- Process OPTIONS block in namfile call options_create() @@ -121,69 +92,77 @@ subroutine read_simulation_namefile(namfile) ! a solution. call check_model_assignment() ! - ! -- Close the file - call parser%Clear() - ! ! -- Go through each solution and assign exchanges accordingly call assign_exchanges() ! ! -- Return return - end subroutine read_simulation_namefile + end subroutine source_simulation_nam !> @brief Set the simulation options !< subroutine options_create() ! -- modules + use MemoryHelperModule, only: create_mem_path + use MemoryManagerModule, only: mem_setptr + use SimVariablesModule, only: idm_context use MemoryManagerModule, only: mem_set_print_option use SimVariablesModule, only: isimcontinue, isimcheck - ! -- local - integer(I4B) :: ierr - integer(I4B) :: imax - logical :: isfound, endOfBlock + ! -- dummy + ! -- locals + character(len=LENMEMPATH) :: input_mempath + integer(I4B), pointer :: simcontinue, nocheck, maxerror + character(len=:), pointer :: prmem character(len=LINELENGTH) :: errmsg - character(len=LINELENGTH) :: keyword -! ------------------------------------------------------------------------------ ! - ! -- Process OPTIONS block - call parser%GetBlock('OPTIONS', isfound, ierr, & - supportOpenClose=.true., blockRequired=.false.) - if (isfound) then + ! -- set input memory path + input_mempath = create_mem_path('SIM', 'NAM', idm_context) + ! + ! -- set pointers to input context option params + call mem_setptr(simcontinue, 'CONTINUE', input_mempath) + call mem_setptr(nocheck, 'NOCHECK', input_mempath) + call mem_setptr(prmem, 'PRMEM', input_mempath) + call mem_setptr(maxerror, 'MAXERRORS', input_mempath) + ! + ! -- update sim options + isimcontinue = simcontinue + ! + isimcheck = nocheck + ! + call MaxErrors(maxerror) + ! + if (prmem /= '') then + errmsg = '' + call mem_set_print_option(iout, prmem, errmsg) + if (errmsg /= '') then + call store_error(errmsg, .true.) + end if + end if + ! + call MaxErrors(maxerror) + ! + ! -- log values to list file + if (iout > 0) then write (iout, '(/1x,a)') 'READING SIMULATION OPTIONS' - do - call parser%GetNextLine(endOfBlock) - if (endOfBlock) exit - call parser%GetStringCaps(keyword) - select case (keyword) - case ('CONTINUE') - isimcontinue = 1 - write (iout, '(4x, a)') & - 'SIMULATION WILL CONTINUE EVEN IF THERE IS NONCONVERGENCE.' - case ('NOCHECK') - isimcheck = 0 - write (iout, '(4x, a)') & - 'MODEL DATA WILL NOT BE CHECKED FOR ERRORS.' - case ('MEMORY_PRINT_OPTION') - errmsg = '' - call parser%GetStringCaps(keyword) - call mem_set_print_option(iout, keyword, errmsg) - if (errmsg /= ' ') then - call store_error(errmsg) - call parser%StoreErrorUnit() - end if - case ('MAXERRORS') - imax = parser%GetInteger() - call MaxErrors(imax) - write (iout, '(4x, a, i0)') & - 'MAXIMUM NUMBER OF ERRORS THAT WILL BE STORED IS ', imax - case default - write (errmsg, '(4x,a,a)') & - '****ERROR. UNKNOWN SIMULATION OPTION: ', & - trim(keyword) - call store_error(errmsg) - call parser%StoreErrorUnit() - end select - end do + ! + if (isimcontinue == 1) then + write (iout, '(4x, a)') & + 'SIMULATION WILL CONTINUE EVEN IF THERE IS NONCONVERGENCE.' + end if + ! + if (isimcheck == 0) then + write (iout, '(4x, a)') & + 'MODEL DATA WILL NOT BE CHECKED FOR ERRORS.' + end if + ! + write (iout, '(4x, a, i0)') & + 'MAXIMUM NUMBER OF ERRORS THAT WILL BE STORED IS ', maxerror + ! + if (prmem /= '') then + write (iout, '(4x, a, a, a)') & + 'MEMORY_PRINT_OPTION SET TO "', trim(prmem), '".' + end if + ! write (iout, '(1x,a)') 'END OF SIMULATION OPTIONS' end if ! @@ -195,53 +174,33 @@ end subroutine options_create !< subroutine timing_create() ! -- modules + use MemoryHelperModule, only: create_mem_path + use MemoryManagerModule, only: mem_setptr + use SimVariablesModule, only: idm_context use TdisModule, only: tdis_cr ! -- dummy - ! -- local - integer(I4B) :: ierr - logical :: isfound, endOfBlock - character(len=LINELENGTH) :: errmsg - character(len=LINELENGTH) :: line, keyword - logical :: found_tdis -! ------------------------------------------------------------------------------ + ! -- locals + character(len=LENMEMPATH) :: input_mempath + character(len=:), pointer :: tdis6 + logical :: terminate = .true. ! - ! -- Initialize - found_tdis = .false. - ! - ! -- Process TIMING block - call parser%GetBlock('TIMING', isfound, ierr, & - supportOpenClose=.true.) - if (isfound) then - write (iout, '(/1x,a)') 'READING SIMULATION TIMING' - do - call parser%GetNextLine(endOfBlock) - if (endOfBlock) exit - call parser%GetStringCaps(keyword) - select case (keyword) - case ('TDIS6') - found_tdis = .true. - call parser%GetString(line) - call tdis_cr(line) - case default - write (errmsg, '(4x,a,a)') & - '****ERROR. UNKNOWN SIMULATION TIMING: ', & - trim(keyword) - call store_error(errmsg) - call parser%StoreErrorUnit() - end select - end do - write (iout, '(1x,a)') 'END OF SIMULATION TIMING' + ! -- set input memory path + input_mempath = create_mem_path('SIM', 'NAM', idm_context) + ! + write (iout, '(/1x,a)') 'READING SIMULATION TIMING' + ! + ! -- set pointers to input context timing params + call mem_setptr(tdis6, 'TDIS6', input_mempath) + ! + ! -- create timing + if (tdis6 /= '') then + call tdis_cr(tdis6) else - call store_error('****ERROR. Did not find TIMING block in simulation'// & - ' control file.') - call parser%StoreErrorUnit() + call store_error('****ERROR. TIMING block variable TDIS6 is unset'// & + ' in simulation control input.', terminate) end if ! - ! -- Ensure that TDIS was found - if (.not. found_tdis) then - call store_error('****ERROR. TDIS not found in TIMING block.') - call parser%StoreErrorUnit() - end if + write (iout, '(1x,a)') 'END OF SIMULATION TIMING' ! ! -- return return @@ -251,6 +210,10 @@ end subroutine timing_create !< subroutine models_create() ! -- modules + use MemoryHelperModule, only: create_mem_path + use MemoryManagerModule, only: mem_setptr + use CharacterStringModule, only: CharacterStringType + use SimVariablesModule, only: idm_context use GwfModule, only: gwf_cr use GwtModule, only: gwt_cr use NumericalModelModule, only: NumericalModelType, GetNumericalModelFromList @@ -258,90 +221,100 @@ subroutine models_create() use VirtualGwtModelModule, only: add_virtual_gwt_model use ConstantsModule, only: LENMODELNAME ! -- dummy - ! -- local - integer(I4B) :: ierr - logical :: isfound, endOfBlock + ! -- locals + character(len=LENMEMPATH) :: input_mempath + type(CharacterStringType), dimension(:), contiguous, & + pointer :: mtypes !< model types + type(CharacterStringType), dimension(:), contiguous, & + pointer :: mfnames !< model file names + type(CharacterStringType), dimension(:), contiguous, & + pointer :: mnames !< model names integer(I4B) :: im, id_glo class(NumericalModelType), pointer :: num_model - character(len=LINELENGTH) :: errmsg character(len=LINELENGTH) :: model_type character(len=LINELENGTH) :: fname, model_name -! ------------------------------------------------------------------------------ + character(len=LINELENGTH) :: errmsg + integer(I4B) :: n + logical :: terminate = .true. ! - ! -- Process MODELS block - call parser%GetBlock('MODELS', isfound, ierr, & - supportOpenClose=.true.) - if (isfound) then - write (iout, '(/1x,a)') 'READING SIMULATION MODELS' - im = 0 - id_glo = 0 - do - call parser%GetNextLine(endOfBlock) - if (endOfBlock) exit - call parser%GetStringCaps(model_type) - call parser%GetString(fname) - call parser%GetStringCaps(model_name) - - call check_model_name(model_type, model_name) - - ! increment global model id - id_glo = id_glo + 1 - call ExpandArray(model_names) - call ExpandArray(model_loc_idx) - model_names(id_glo) = model_name(1:LENMODELNAME) - model_loc_idx(id_glo) = -1 - - if (nr_procs > 1) then - if (simulation_mode == 'PARALLEL') then - if (model_type == 'GWF6') then - ! for now we assume: model id == rank nr + 1 - if (id_glo /= proc_id + 1) then - call add_virtual_gwf_model(id_glo, model_names(id_glo), null()) - cycle - end if - else - write (errmsg, '(4x,a)') & - '****ERROR. ONLY GWF SUPPORT IN PARALLEL MODE FOR NOW' - call store_error(errmsg) - call parser%StoreErrorUnit() + ! -- set input memory path + input_mempath = create_mem_path('SIM', 'NAM', idm_context) + ! + ! -- set pointers to input context model attribute arrays + call mem_setptr(mtypes, 'MTYPE', input_mempath) + call mem_setptr(mfnames, 'MFNAME', input_mempath) + call mem_setptr(mnames, 'MNAME', input_mempath) + ! + ! -- open model logging block + write (iout, '(/1x,a)') 'READING SIMULATION MODELS' + ! + ! -- initialize global and local model ids + id_glo = 0 + im = 0 + ! + ! -- create models + do n = 1, size(mtypes) + ! + ! -- attributes for this model + model_type = mtypes(n) + fname = mfnames(n) + model_name = mnames(n) + ! + call check_model_name(model_type, model_name) + ! + ! increment global model id + id_glo = id_glo + 1 + call ExpandArray(model_names) + call ExpandArray(model_loc_idx) + model_names(id_glo) = model_name(1:LENMODELNAME) + model_loc_idx(id_glo) = -1 + ! + if (nr_procs > 1) then + if (simulation_mode == 'PARALLEL') then + if (model_type == 'GWF6') then + ! for now we assume: model id == rank nr + 1 + if (id_glo /= proc_id + 1) then + call add_virtual_gwf_model(id_glo, model_names(id_glo), null()) + cycle end if + else + write (errmsg, '(4x,a)') & + '****ERROR. ONLY GWF SUPPORT IN PARALLEL MODE FOR NOW' + call store_error(errmsg, terminate) end if end if - - ! we will add a new (local) model - im = im + 1 - model_loc_idx(id_glo) = im - - select case (model_type) - case ('GWF6') - call gwf_cr(fname, id_glo, model_names(id_glo)) - num_model => GetNumericalModelFromList(basemodellist, im) - call add_virtual_gwf_model(id_glo, model_names(id_glo), num_model) - case ('GWT6') - call gwt_cr(fname, id_glo, model_names(id_glo)) - num_model => GetNumericalModelFromList(basemodellist, im) - call add_virtual_gwt_model(id_glo, model_names(id_glo), num_model) - case default - write (errmsg, '(4x,a,a)') & - '****ERROR. UNKNOWN SIMULATION MODEL: ', & - trim(model_type) - call store_error(errmsg) - call parser%StoreErrorUnit() - end select - end do - write (iout, '(1x,a)') 'END OF SIMULATION MODELS' - else - call store_error('****ERROR. Did not find MODELS block in simulation'// & - ' control file.') - call parser%StoreErrorUnit() - end if + end if + ! + ! -- add a new (local) model + im = im + 1 + model_loc_idx(id_glo) = im + ! + ! -- create appropriate models and update modelname + select case (model_type) + case ('GWF6') + call gwf_cr(fname, id_glo, model_names(id_glo)) + num_model => GetNumericalModelFromList(basemodellist, im) + call add_virtual_gwf_model(id_glo, model_names(id_glo), num_model) + case ('GWT6') + call gwt_cr(fname, id_glo, model_names(id_glo)) + num_model => GetNumericalModelFromList(basemodellist, im) + call add_virtual_gwt_model(id_glo, model_names(id_glo), num_model) + case default + write (errmsg, '(4x,a,a)') & + '****ERROR. UNKNOWN SIMULATION MODEL: ', & + trim(model_type) + call store_error(errmsg, terminate) + end select + end do + ! + ! -- close model logging block + write (iout, '(1x,a)') 'END OF SIMULATION MODELS' ! ! -- sanity check if (simulation_mode == 'PARALLEL' .and. im == 0) then write (errmsg, '(4x,a, i0)') & '****ERROR. No MODELS assigned to process ', proc_id - call store_error(errmsg) - call parser%StoreErrorUnit() + call store_error(errmsg, terminate) end if ! ! -- return @@ -352,260 +325,303 @@ end subroutine models_create !< subroutine exchanges_create() ! -- modules + use MemoryHelperModule, only: create_mem_path + use MemoryManagerModule, only: mem_setptr + use CharacterStringModule, only: CharacterStringType + use SimVariablesModule, only: idm_context use GwfGwfExchangeModule, only: gwfexchange_create use GwfGwtExchangeModule, only: gwfgwt_cr use GwtGwtExchangeModule, only: gwtexchange_create use VirtualGwfExchangeModule, only: add_virtual_gwf_exchange use VirtualGwtExchangeModule, only: add_virtual_gwt_exchange ! -- dummy - ! -- local - integer(I4B) :: ierr - logical :: isfound, endOfBlock + ! -- locals + character(len=LENMEMPATH) :: input_mempath + type(CharacterStringType), dimension(:), contiguous, & + pointer :: etypes !< exg types + type(CharacterStringType), dimension(:), contiguous, & + pointer :: efiles !< exg file names + type(CharacterStringType), dimension(:), contiguous, & + pointer :: emnames_a !< model a names + type(CharacterStringType), dimension(:), contiguous, & + pointer :: emnames_b !< model b names + character(len=LINELENGTH) :: exgtype integer(I4B) :: exg_id integer(I4B) :: m1_id, m2_id - character(len=LINELENGTH) :: errmsg - character(len=LINELENGTH) :: keyword character(len=LINELENGTH) :: fname, name1, name2 character(len=LENEXCHANGENAME) :: exg_name + integer(I4B) :: n + character(len=LINELENGTH) :: errmsg + logical :: terminate = .true. ! -- formats character(len=*), parameter :: fmtmerr = "('Error in simulation control ', & &'file. Could not find model: ', a)" -! ------------------------------------------------------------------------------ - call parser%GetBlock('EXCHANGES', isfound, ierr, & - supportOpenClose=.true.) - if (isfound) then - write (iout, '(/1x,a)') 'READING SIMULATION EXCHANGES' - exg_id = 0 - do - call parser%GetNextLine(endOfBlock) - if (endOfBlock) exit - - exg_id = exg_id + 1 - - call parser%GetStringCaps(keyword) - call parser%GetString(fname) - call parser%GetStringCaps(name1) - call parser%GetStringCaps(name2) - - ! find model index in list - m1_id = ifind(model_names, name1) - if (m1_id < 0) then - write (errmsg, fmtmerr) trim(name1) - call store_error(errmsg) - call parser%StoreErrorUnit() - end if - m2_id = ifind(model_names, name2) - if (m2_id < 0) then - write (errmsg, fmtmerr) trim(name2) - call store_error(errmsg) - call parser%StoreErrorUnit() - end if - - ! both models on other process? then don't create it here... - if (model_loc_idx(m1_id) == -1 .and. model_loc_idx(m2_id) == -1) then - ! only add virtual - write (exg_name, '(a,i0)') 'GWF-GWF_', exg_id - call add_virtual_gwf_exchange(exg_name, exg_id, m1_id, m2_id) - cycle - end if - - write (iout, '(4x,a,a,i0,a,i0,a,i0)') trim(keyword), ' exchange ', & - exg_id, ' will be created to connect model ', m1_id, & - ' with model ', m2_id + ! + ! -- set input memory path + input_mempath = create_mem_path('SIM', 'NAM', idm_context) + ! + ! -- set pointers to input context exchange attribute arrays + call mem_setptr(etypes, 'EXGTYPE', input_mempath) + call mem_setptr(efiles, 'EXGFILE', input_mempath) + call mem_setptr(emnames_a, 'EXGMNAMEA', input_mempath) + call mem_setptr(emnames_b, 'EXGMNAMEB', input_mempath) + ! + ! -- open exchange logging block + write (iout, '(/1x,a)') 'READING SIMULATION EXCHANGES' + ! + ! -- initialize + exg_id = 0 + ! + ! -- create exchanges + do n = 1, size(etypes) + ! + ! -- attributes for this exchange + exgtype = etypes(n) + fname = efiles(n) + name1 = emnames_a(n) + name2 = emnames_b(n) + + exg_id = exg_id + 1 + + ! find model index in list + m1_id = ifind(model_names, name1) + if (m1_id < 0) then + write (errmsg, fmtmerr) trim(name1) + call store_error(errmsg, terminate) + end if + m2_id = ifind(model_names, name2) + if (m2_id < 0) then + write (errmsg, fmtmerr) trim(name2) + call store_error(errmsg, terminate) + end if - select case (keyword) - case ('GWF6-GWF6') - write (exg_name, '(a,i0)') 'GWF-GWF_', exg_id - call gwfexchange_create(fname, exg_name, exg_id, m1_id, m2_id) - call add_virtual_gwf_exchange(exg_name, exg_id, m1_id, m2_id) - case ('GWF6-GWT6') - call gwfgwt_cr(fname, exg_id, m1_id, m2_id) - case ('GWT6-GWT6') - write (exg_name, '(a,i0)') 'GWT-GWT_', exg_id - call gwtexchange_create(fname, exg_name, exg_id, m1_id, m2_id) - call add_virtual_gwt_exchange(exg_name, exg_id, m1_id, m2_id) - case default - write (errmsg, '(4x,a,a)') & - '****ERROR. UNKNOWN SIMULATION EXCHANGES: ', & - trim(keyword) - call store_error(errmsg) - call parser%StoreErrorUnit() - end select - end do + ! both models on other process? then don't create it here... + if (model_loc_idx(m1_id) == -1 .and. model_loc_idx(m2_id) == -1) then + ! only add virtual + write (exg_name, '(a,i0)') 'GWF-GWF_', exg_id + call add_virtual_gwf_exchange(exg_name, exg_id, m1_id, m2_id) + cycle + end if - write (iout, '(1x,a)') 'END OF SIMULATION EXCHANGES' + write (iout, '(4x,a,a,i0,a,i0,a,i0)') trim(exgtype), ' exchange ', & + exg_id, ' will be created to connect model ', m1_id, & + ' with model ', m2_id + + select case (exgtype) + case ('GWF6-GWF6') + write (exg_name, '(a,i0)') 'GWF-GWF_', exg_id + call gwfexchange_create(fname, exg_name, exg_id, m1_id, m2_id) + call add_virtual_gwf_exchange(exg_name, exg_id, m1_id, m2_id) + case ('GWF6-GWT6') + call gwfgwt_cr(fname, exg_id, m1_id, m2_id) + case ('GWT6-GWT6') + write (exg_name, '(a,i0)') 'GWT-GWT_', exg_id + call gwtexchange_create(fname, exg_name, exg_id, m1_id, m2_id) + call add_virtual_gwt_exchange(exg_name, exg_id, m1_id, m2_id) + case default + write (errmsg, '(4x,a,a)') & + '****ERROR. UNKNOWN SIMULATION EXCHANGES: ', & + trim(exgtype) + call store_error(errmsg, terminate) + end select + end do + ! + ! -- close exchange logging block + write (iout, '(1x,a)') 'END OF SIMULATION EXCHANGES' + ! + ! -- return + return + end subroutine exchanges_create - else - call store_error('****ERROR. Did not find EXCHANGES block in '// & - 'simulation control file.') - call parser%StoreErrorUnit() + !> @brief Check a solution_group to be used for the simulation + !< + subroutine solution_group_check(sgp, sgid, isgpsoln) + ! -- modules + ! -- dummy + type(SolutionGroupType), pointer, intent(inout) :: sgp + integer(I4B), intent(in) :: sgid + integer(I4B), intent(in) :: isgpsoln + ! -- local + character(len=LINELENGTH) :: errmsg + logical :: terminate = .true. + ! -- formats + character(len=*), parameter :: fmterrmxiter = & + "('ERROR. MXITER IS SET TO ', i0, ' BUT THERE IS ONLY ONE SOLUTION', & + &' IN SOLUTION GROUP ', i0, '. SET MXITER TO 1 IN SIMULATION CONTROL', & + &' FILE.')" + ! + ! -- error check completed group + if (sgid > 0) then + ! + ! -- Make sure there is a solution in this solution group + if (isgpsoln == 0) then + write (errmsg, '(4x,a,i0)') & + 'ERROR. THERE ARE NO SOLUTIONS FOR SOLUTION GROUP ', sgid + call store_error(errmsg, terminate) + end if + ! + ! -- If there is only one solution then mxiter should be 1. + if (isgpsoln == 1 .and. sgp%mxiter > 1) then + write (errmsg, fmterrmxiter) sgp%mxiter, isgpsoln + call store_error(errmsg, terminate) + end if end if ! ! -- return return - end subroutine exchanges_create + end subroutine solution_group_check !> @brief Set the solution_groups to be used for the simulation !< subroutine solution_groups_create() ! -- modules + use MemoryManagerModule, only: mem_setptr + use CharacterStringModule, only: CharacterStringType + use MemoryHelperModule, only: create_mem_path + use SimVariablesModule, only: idm_context, simulation_mode use SolutionGroupModule, only: SolutionGroupType, & solutiongroup_create use SolutionFactoryModule, only: create_ims_solution use BaseSolutionModule, only: BaseSolutionType use BaseModelModule, only: BaseModelType use BaseExchangeModule, only: BaseExchangeType - use SimVariablesModule, only: simulation_mode + use InputOutputModule, only: parseline, upcase ! -- dummy ! -- local + character(len=LENMEMPATH) :: input_mempath + type(CharacterStringType), dimension(:), contiguous, & + pointer :: slntype + type(CharacterStringType), dimension(:), contiguous, & + pointer :: slnfname + type(CharacterStringType), dimension(:), contiguous, & + pointer :: slnmnames + integer(I4B), dimension(:), contiguous, pointer :: blocknum + character(len=LINELENGTH) :: stype, fname, mnames type(SolutionGroupType), pointer :: sgp class(BaseSolutionType), pointer :: sp class(BaseModelType), pointer :: mp - integer(I4B) :: ierr - logical :: isfound, endOfBlock integer(I4B) :: isoln - integer(I4B) :: isgp integer(I4B) :: isgpsoln integer(I4B) :: sgid integer(I4B) :: glo_mid integer(I4B) :: loc_idx - logical(LGP) :: blockRequired + integer(I4B) :: i, j, istat, mxiter + integer(I4B) :: nwords + character(len=LENMODELNAME), dimension(:), allocatable :: words + character(len=:), allocatable :: parse_str character(len=LINELENGTH) :: errmsg - character(len=LENBIGLINE) :: keyword - character(len=LINELENGTH) :: fname, mname - ! -- formats - character(len=*), parameter :: fmterrmxiter = & - "('ERROR. MXITER IS SET TO ', i0, ' BUT THERE IS ONLY ONE SOLUTION', & - &' IN SOLUTION GROUP ', i0, '. SET MXITER TO 1 IN SIMULATION CONTROL', & - &' FILE.')" + logical :: terminate = .true. ! ------------------------------------------------------------------------------ ! - ! -- isoln is the cumulative solution number, isgp is the cumulative - ! solution group number. - isoln = 0 - isgp = 0 + ! -- set memory path + input_mempath = create_mem_path('SIM', 'NAM', idm_context) ! - !Read through the simulation name file and process each SOLUTION_GROUP - sgploop: do - ! - blockRequired = .false. - if (isgp == 0) blockRequired = .true. - call parser%GetBlock('SOLUTIONGROUP', isfound, ierr, & - supportOpenClose=.true., & - blockRequired=blockRequired) - if (ierr /= 0) exit sgploop - if (.not. isfound) exit sgploop - isgp = isgp + 1 - ! - ! -- Get the solutiongroup id and check that it is listed consecutively. - sgid = parser%GetInteger() - if (isgp /= sgid) then - write (errmsg, '(a)') 'Solution groups are not listed consecutively.' - call store_error(errmsg) - write (errmsg, '(a,i0,a,i0)') 'Found ', sgid, ' when looking for ', isgp - call store_error(errmsg) - call parser%StoreErrorUnit() - end if - ! - ! -- Create the solutiongroup and add it to the solutiongrouplist - call solutiongroup_create(sgp, sgid) - call AddSolutionGroupToList(solutiongrouplist, sgp) - ! - ! -- Begin processing the solution group - write (iout, '(/1x,a)') 'READING SOLUTIONGROUP' + ! -- set pointers to input context solution attribute arrays + call mem_setptr(slntype, 'SLNTYPE', input_mempath) + call mem_setptr(slnfname, 'SLNFNAME', input_mempath) + call mem_setptr(slnmnames, 'SLNMNAMES', input_mempath) + call mem_setptr(blocknum, 'SOLUTIONGROUPnum', input_mempath) + ! + ! -- open solution group logging block + write (iout, '(/1x,a)') 'READING SOLUTIONGROUP' + ! + ! -- initialize + sgid = 0 ! integer id of soln group, tracks with blocknum + isoln = 0 ! cumulative solution number + ! + ! -- create solution groups + do i = 1, size(blocknum) ! - ! -- Initialize isgpsoln to 0. isgpsoln is the solution counter for this - ! particular solution group. It goes from 1 to the number of solutions - ! in this group. - isgpsoln = 0 - do - call parser%GetNextLine(endOfBlock) - if (endOfBlock) exit - call parser%GetStringCaps(keyword) - select case (keyword) + ! -- attributes for this solution + stype = slntype(i) + fname = slnfname(i) + mnames = slnmnames(i) + + if (blocknum(i) /= sgid) then + ! + ! -- check for new soln group + if (blocknum(i) == sgid + 1) then ! - case ('MXITER') - sgp%mxiter = parser%GetInteger() + ! -- error check completed group + call solution_group_check(sgp, sgid, isgpsoln) ! - case ('IMS6') + ! -- reinitialize + nullify (sgp) + isgpsoln = 0 ! solution counter for this solution group ! - ! -- Initialize and increment counters - isoln = isoln + 1 - isgpsoln = isgpsoln + 1 + ! -- set sgid + sgid = blocknum(i) ! - ! -- Create the solution, retrieve from the list, and add to sgp - call parser%GetString(fname) - sp => create_ims_solution(simulation_mode, fname, isoln) - call sgp%add_solution(isoln, sp) + ! -- create new soln group and add to global list + call solutiongroup_create(sgp, sgid) + call AddSolutionGroupToList(solutiongrouplist, sgp) + else + write (errmsg, '(a,i0,a,i0,a)') & + 'Solution group blocks are not listed consecutively. Found ', & + blocknum(i), ' when looking for ', sgid + 1, '.' + call store_error(errmsg, terminate) + end if + end if + ! + ! -- + select case (stype) + ! + case ('MXITER') + read (fname, *, iostat=istat) mxiter + if (istat == 0) then + sgp%mxiter = mxiter + end if + case ('IMS6') + ! + ! -- increment solution counters + isoln = isoln + 1 + isgpsoln = isgpsoln + 1 + ! + ! -- create soln and add to group + sp => create_ims_solution(simulation_mode, fname, isoln) + call sgp%add_solution(isoln, sp) + ! + ! -- parse model names + parse_str = trim(mnames)//' ' + call parseline(parse_str, nwords, words) + ! + ! -- Find each model id and get model + do j = 1, nwords + call upcase(words(j)) + glo_mid = ifind(model_names, words(j)) + if (glo_mid == -1) then + write (errmsg, '(a,a)') 'Error. Invalid model name: ', & + trim(words(j)) + call store_error(errmsg, terminate) + end if ! - ! -- Add all of the models that are listed on this line to - ! the current solution (sp) - do - ! - ! -- Set istart and istop to encompass model name. Exit this - ! loop if there are no more models. - call parser%GetStringCaps(mname) - if (mname == '') exit - ! - ! -- Find the model id, and then get model - glo_mid = ifind(model_names, mname) - if (glo_mid == -1) then - write (errmsg, '(a,a)') 'Error. Invalid model name: ', & - trim(mname) - call store_error(errmsg) - call parser%StoreErrorUnit() - end if - - loc_idx = model_loc_idx(glo_mid) - if (loc_idx == -1) then - if (simulation_mode == 'PARALLEL') then - ! this is still ok - cycle - end if + loc_idx = model_loc_idx(glo_mid) + if (loc_idx == -1) then + if (simulation_mode == 'PARALLEL') then + ! this is still ok + cycle end if - - mp => GetBaseModelFromList(basemodellist, loc_idx) - ! - ! -- Add the model to the solution - call sp%add_model(mp) - mp%idsoln = isoln - ! - end do + end if ! - case default - write (errmsg, '(4x,a,a)') & - '****ERROR. UNKNOWN SOLUTIONGROUP ENTRY: ', & - trim(keyword) - call store_error(errmsg) - call parser%StoreErrorUnit() - end select - end do - ! - ! -- Make sure there is a solution in this solution group - if (isgpsoln == 0) then - write (errmsg, '(4x,a,i0)') & - 'ERROR. THERE ARE NO SOLUTIONS FOR SOLUTION GROUP ', isgp - call store_error(errmsg) - call parser%StoreErrorUnit() - end if - ! - ! -- If there is only one solution then mxiter should be 1. - if (isgpsoln == 1 .and. sgp%mxiter > 1) then - write (errmsg, fmterrmxiter) sgp%mxiter, isgpsoln - call store_error(errmsg) - call parser%StoreErrorUnit() - end if - ! - ! -- todo: more error checking? - ! - write (iout, '(1x,a)') 'END OF SIMULATION SOLUTIONGROUP' - ! - end do sgploop + mp => GetBaseModelFromList(basemodellist, loc_idx) + ! + ! -- Add the model to the solution + call sp%add_model(mp) + mp%idsoln = isoln + end do + case default + end select + end do + ! + ! -- error check final group + call solution_group_check(sgp, sgid, isgpsoln) + ! + ! -- close exchange logging block + write (iout, '(1x,a)') 'END OF SOLUTIONGROUP' ! ! -- Check and make sure at least one solution group was found if (solutiongrouplist%Count() == 0) then - call store_error('ERROR. THERE ARE NO SOLUTION GROUPS.') - call parser%StoreErrorUnit() + call store_error('ERROR. THERE ARE NO SOLUTION GROUPS.', terminate) end if ! ! -- return @@ -629,7 +645,7 @@ subroutine check_model_assignment() end if end do if (count_errors() > 0) then - call store_error_unit(inunit) + call store_error_filename('mfsim.nam') end if end subroutine check_model_assignment @@ -681,6 +697,7 @@ subroutine check_model_name(mtype, mname) integer :: ilen integer :: i character(len=LINELENGTH) :: errmsg + logical :: terminate = .true. ! ------------------------------------------------------------------------------ ilen = len_trim(mname) if (ilen > LENMODELNAME) then @@ -690,8 +707,7 @@ subroutine check_model_name(mtype, mname) write (errmsg, '(4x,a,i0,a,i0)') & 'NAME LENGTH OF ', ilen, ' EXCEEDS MAXIMUM LENGTH OF ', & LENMODELNAME - call store_error(errmsg) - call parser%StoreErrorUnit() + call store_error(errmsg, terminate) end if do i = 1, ilen if (mname(i:i) == ' ') then @@ -700,8 +716,7 @@ subroutine check_model_name(mtype, mname) call store_error(errmsg) write (errmsg, '(4x,a)') & 'MODEL NAME CANNOT HAVE SPACES WITHIN IT.' - call store_error(errmsg) - call parser%StoreErrorUnit() + call store_error(errmsg, terminate) end if end do ! diff --git a/src/Utilities/Idm/IdmMf6FileLoader.f90 b/src/Utilities/Idm/IdmMf6FileLoader.f90 index 57de69f32d7..b58b851021e 100644 --- a/src/Utilities/Idm/IdmMf6FileLoader.f90 +++ b/src/Utilities/Idm/IdmMf6FileLoader.f90 @@ -15,10 +15,6 @@ module IdmMf6FileLoaderModule private public :: input_load - interface input_load - module procedure input_load_blockparser, input_load_generic - end interface input_load - !> @brief derived type for storing package loader !! !! This derived type is used to store a pointer to a @@ -52,53 +48,20 @@ subroutine generic_mf6_load(parser, mf6_input, iout) type(ModflowInputType), intent(in) :: mf6_input !< ModflowInputType object that describes the input integer(I4B), intent(in) :: iout !< unit number for output - call idm_load(parser, mf6_input%file_type, & + call idm_load(parser, mf6_input%pkgtype, & mf6_input%component_type, mf6_input%subcomponent_type, & mf6_input%component_name, mf6_input%subcomponent_name, & iout) end subroutine generic_mf6_load - !> @brief main entry to mf6 input load - !< - subroutine input_load_blockparser(parser, filetype, & - component_type, subcomponent_type, & - component_name, subcomponent_name, & - iout) - type(BlockParserType), intent(inout) :: parser !< block parser - character(len=*), intent(in) :: filetype !< file type to load, such as DIS6, DISV6, NPF6 - character(len=*), intent(in) :: component_type !< component type, such as GWF or GWT - character(len=*), intent(in) :: subcomponent_type !< subcomponent type, such as DIS or NPF - character(len=*), intent(in) :: component_name !< component name, such as MYGWFMODEL - character(len=*), intent(in) :: subcomponent_name !< subcomponent name, such as MYWELLPACKAGE - integer(I4B), intent(in) :: iout !< unit number for output - type(ModflowInputType) :: mf6_input - type(PackageLoad) :: pkgloader - - mf6_input = getModflowInput(filetype, component_type, & - subcomponent_type, component_name, & - subcomponent_name) - ! - ! -- set mf6 parser based package loader by file type - select case (filetype) - case default - pkgloader%load_package => generic_mf6_load - end select - ! - ! -- invoke the selected load routine - call pkgloader%load_package(parser, mf6_input, iout) - ! - ! -- return - return - end subroutine input_load_blockparser - - !> @brief main entry to mf6 input load + !> @brief input load for traditional mf6 simulation input file !< - subroutine input_load_generic(filetype, & - component_type, subcomponent_type, & - component_name, subcomponent_name, & - inunit, iout) - character(len=*), intent(in) :: filetype !< file type to load, such as DIS6, DISV6, NPF6 + subroutine input_load(pkgtype, & + component_type, subcomponent_type, & + component_name, subcomponent_name, & + inunit, iout) + character(len=*), intent(in) :: pkgtype !< pkgtype to load, such as DIS6, DISV6, NPF6 character(len=*), intent(in) :: component_type !< component type, such as GWF or GWT character(len=*), intent(in) :: subcomponent_type !< subcomponent type, such as DIS or NPF character(len=*), intent(in) :: component_name !< component name, such as MYGWFMODEL @@ -110,12 +73,12 @@ subroutine input_load_generic(filetype, & type(PackageLoad) :: pkgloader ! ! -- create description of input - mf6_input = getModflowInput(filetype, component_type, & + mf6_input = getModflowInput(pkgtype, component_type, & subcomponent_type, component_name, & subcomponent_name) ! ! -- set mf6 parser based package loader by file type - select case (filetype) + select case (pkgtype) case default allocate (parser) call parser%Initialize(inunit, iout) @@ -125,11 +88,14 @@ subroutine input_load_generic(filetype, & ! -- invoke the selected load routine call pkgloader%load_package(parser, mf6_input, iout) ! - ! -- deallocate - if (allocated(parser)) deallocate (parser) + ! -- close files and deallocate + if (allocated(parser)) then + !call parser%clear() + deallocate (parser) + end if ! ! -- return return - end subroutine input_load_generic + end subroutine input_load end module IdmMf6FileLoaderModule diff --git a/src/Utilities/Idm/IdmSimulation.f90 b/src/Utilities/Idm/IdmSimulation.f90 new file mode 100644 index 00000000000..760d73f1252 --- /dev/null +++ b/src/Utilities/Idm/IdmSimulation.f90 @@ -0,0 +1,176 @@ +!> @brief This module contains the IdmSimulationModule +!! +!! This module contains the high-level routines for loading +!! sim namefile parameters into the input context +!! +!< +module IdmSimulationModule + + use KindModule, only: DP, I4B, LGP + use ConstantsModule, only: LINELENGTH, LENMEMPATH + use SimModule, only: store_error + use SimVariablesModule, only: iout + use InputOutputModule, only: openfile, getunit + use InputDefinitionModule, only: InputParamDefinitionType + use ModflowInputModule, only: ModflowInputType, getModflowInput + use IdmMf6FileLoaderModule, only: input_load + + implicit none + private + public :: simnam_load + public :: simnam_allocate + +contains + + !> @brief MODFLOW 6 mfsim.nam input load routine + !< + subroutine simnam_load() + use SimVariablesModule, only: simfile + use GenericUtilitiesModule, only: sim_message + integer(I4B) :: inunit + logical :: lexist + character(len=LINELENGTH) :: line + ! + ! -- load mfsim.nam if it exists + inquire (file=trim(adjustl(simfile)), exist=lexist) + ! + if (lexist) then + ! + ! -- write name of namfile to stdout + write (line, '(2(1x,a))') 'Using Simulation name file:', & + trim(adjustl(simfile)) + call sim_message(line, skipafter=1) + ! + ! -- open namfile and load to input context + inunit = getunit() + call openfile(inunit, iout, trim(adjustl(simfile)), 'NAM') + call input_load('NAM6', 'SIM', 'NAM', 'SIM', 'NAM', inunit, iout) + ! + close (inunit) + ! + ! -- allocate any unallocated simnam params + call simnam_allocate() + else + ! + ! -- allocate simnam params + call simnam_allocate() + end if + ! + ! --return + return + end subroutine simnam_load + + !> @brief MODFLOW 6 mfsim.nam parameter set default value + !< + subroutine set_default_value(intvar, mf6varname) + use SimVariablesModule, only: isimcontinue, isimcheck + integer(I4B), pointer, intent(in) :: intvar + character(len=*), intent(in) :: mf6varname + character(len=LINELENGTH) :: errmsg + logical(LGP) :: terminate = .true. + ! + ! -- load defaults for keyword/integer types + select case (mf6varname) + ! + case ('CONTINUE') + intvar = isimcontinue + ! + case ('NOCHECK') + intvar = isimcheck + ! + case ('MAXERRORS') + intvar = 1000 !< MessageType max_message + ! + case ('MXITER') + intvar = 1 + ! + case default + write (errmsg, '(4x,a,a)') & + '**ERROR. IdmSimulation set_default_value unhandled variable: ', & + trim(mf6varname) + call store_error(errmsg, terminate) + end select + ! + ! -- return + return + end subroutine set_default_value + + !> @brief MODFLOW 6 mfsim.nam input context parameter allocation + !< + subroutine simnam_allocate() + use MemoryHelperModule, only: create_mem_path + use MemoryTypeModule, only: MemoryType + use MemoryManagerModule, only: get_isize, mem_allocate + use SimVariablesModule, only: idm_context + use CharacterStringModule, only: CharacterStringType + character(len=LENMEMPATH) :: input_mempath + type(ModflowInputType) :: mf6_input + type(InputParamDefinitionType), pointer :: idt + integer(I4B) :: iparam, isize + logical(LGP) :: terminate = .true. + integer(I4B), pointer :: intvar => null() + character(len=LINELENGTH), pointer :: cstr => null() + type(CharacterStringType), dimension(:), & + pointer, contiguous :: acharstr1d => null() !< variable for allocation + character(len=LINELENGTH) :: errmsg + ! + ! -- set memory path + input_mempath = create_mem_path('SIM', 'NAM', idm_context) + ! + ! -- create description of input + mf6_input = getModflowInput('NAM6', 'SIM', 'NAM', 'SIM', 'NAM') + ! + ! -- allocate sim namfile parameters if not in input context + do iparam = 1, size(mf6_input%p_param_dfns) + ! + ! -- assign param definition pointer + idt => mf6_input%p_param_dfns(iparam) + ! + ! -- check if variable is already allocated + call get_isize(idt%mf6varname, input_mempath, isize) + ! + ! -- if not, allocate and set default + if (isize < 0) then + select case (idt%datatype) + case ('KEYWORD', 'INTEGER') + ! + ! -- allocate and set default + call mem_allocate(intvar, idt%mf6varname, input_mempath) + call set_default_value(intvar, idt%mf6varname) + ! + ! -- reset pointer + nullify (intvar) + case ('STRING') + ! + ! -- did this param originate from sim namfile RECARRAY type + if (idt%in_record) then + ! + ! -- allocate 0 size CharacterStringType array + call mem_allocate(acharstr1d, LINELENGTH, 0, idt%mf6varname, & + input_mempath) + ! + ! -- reset pointer + nullify (acharstr1d) + else + ! + ! -- allocate empty string + call mem_allocate(cstr, LINELENGTH, idt%mf6varname, input_mempath) + cstr = '' + ! + ! -- reset pointer + nullify (cstr) + end if + case default + write (errmsg, '(4x,a,a)') & + '**ERROR. IdmSimulation unhandled datatype: ', & + trim(idt%datatype) + call store_error(errmsg, terminate) + end select + end if + end do + ! + ! -- return + return + end subroutine simnam_allocate + +end module IdmSimulationModule diff --git a/src/Utilities/Idm/InputDefinition.f90 b/src/Utilities/Idm/InputDefinition.f90 index 01b67416734..c4dfa5f09bf 100644 --- a/src/Utilities/Idm/InputDefinition.f90 +++ b/src/Utilities/Idm/InputDefinition.f90 @@ -43,6 +43,7 @@ module InputDefinitionModule character(len=100) :: blockname = '' logical(LGP) :: required = .false. logical(LGP) :: aggregate = .false. + logical(LGP) :: block_variable = .false. end type InputBlockDefinitionType end module InputDefinitionModule diff --git a/src/Utilities/Idm/InputDefinitionSelector.f90 b/src/Utilities/Idm/InputDefinitionSelector.f90 index c93d997db0d..4d53cf04316 100644 --- a/src/Utilities/Idm/InputDefinitionSelector.f90 +++ b/src/Utilities/Idm/InputDefinitionSelector.f90 @@ -27,6 +27,9 @@ module InputDefinitionSelectorModule use GwtDspInputModule, only: gwt_dsp_param_definitions, & gwt_dsp_aggregate_definitions, & gwt_dsp_block_definitions + use SimNamInputModule, only: sim_nam_param_definitions, & + sim_nam_aggregate_definitions, & + sim_nam_block_definitions implicit none private @@ -56,6 +59,8 @@ function param_definitions(component) result(input_definition) call set_pointer(input_definition, gwf_npf_param_definitions) case ('GWT/DSP') call set_pointer(input_definition, gwt_dsp_param_definitions) + case ('SIM/NAM') + call set_pointer(input_definition, sim_nam_param_definitions) case default write (warnmsg, '(a,a)') 'IDM Unsupported input type: ', trim(component) call store_warning(warnmsg) @@ -81,6 +86,8 @@ function aggregate_definitions(component) result(input_definition) call set_pointer(input_definition, gwf_npf_aggregate_definitions) case ('GWT/DSP') call set_pointer(input_definition, gwt_dsp_aggregate_definitions) + case ('SIM/NAM') + call set_pointer(input_definition, sim_nam_aggregate_definitions) case default write (warnmsg, '(a,a)') 'IDM Unsupported input type: ', trim(component) call store_warning(warnmsg) @@ -106,6 +113,8 @@ function block_definitions(component) result(input_definition) call set_block_pointer(input_definition, gwf_npf_block_definitions) case ('GWT/DSP') call set_block_pointer(input_definition, gwt_dsp_block_definitions) + case ('SIM/NAM') + call set_block_pointer(input_definition, sim_nam_block_definitions) case default write (warnmsg, '(a,a)') 'IDM Unsupported input type: ', trim(component) call store_warning(warnmsg) diff --git a/src/Utilities/Idm/LoadMf6FileType.f90 b/src/Utilities/Idm/LoadMf6FileType.f90 index 4971c75df29..45a7a867bcb 100644 --- a/src/Utilities/Idm/LoadMf6FileType.f90 +++ b/src/Utilities/Idm/LoadMf6FileType.f90 @@ -8,7 +8,7 @@ module LoadMf6FileTypeModule use KindModule, only: DP, I4B, LGP - use ConstantsModule, only: LINELENGTH, LENMEMPATH + use ConstantsModule, only: LINELENGTH, LENMEMPATH, LENVARNAME use SimVariablesModule, only: errmsg use SimModule, only: store_error use BlockParserModule, only: BlockParserType @@ -35,10 +35,6 @@ module LoadMf6FileTypeModule private public :: idm_load - interface idm_load - module procedure idm_load_from_blockparser - end interface idm_load - contains !> @brief procedure to load a file @@ -47,13 +43,13 @@ module LoadMf6FileTypeModule !! memory context location of the memory manager. !! !< - subroutine idm_load_from_blockparser(parser, filetype, & - component_type, subcomponent_type, & - component_name, subcomponent_name, & - iout) + subroutine idm_load(parser, pkgtype, & + component_type, subcomponent_type, & + component_name, subcomponent_name, & + iout) use SimVariablesModule, only: idm_context type(BlockParserType), intent(inout) :: parser !< block parser - character(len=*), intent(in) :: filetype !< file type to load, such as DIS6, DISV6, NPF6 + character(len=*), intent(in) :: pkgtype !< file type to load, such as DIS6, DISV6, NPF6 character(len=*), intent(in) :: component_type !< component type, such as GWF or GWT character(len=*), intent(in) :: subcomponent_type !< subcomponent type, such as DIS or NPF character(len=*), intent(in) :: component_name !< component name, such as MYGWFMODEL @@ -65,7 +61,7 @@ subroutine idm_load_from_blockparser(parser, filetype, & integer(I4B), dimension(:), contiguous, pointer :: mshape => null() ! ! -- construct input object - mf6_input = getModflowInput(filetype, component_type, & + mf6_input = getModflowInput(pkgtype, component_type, & subcomponent_type, component_name, & subcomponent_name) ! @@ -79,28 +75,30 @@ subroutine idm_load_from_blockparser(parser, filetype, & ! ! -- process blocks do iblock = 1, size(mf6_input%p_block_dfns) - call parse_block(parser, mf6_input, iblock, mshape, iout) + call parse_block(parser, mf6_input, iblock, mshape, iout, .false.) ! ! -- set model shape if discretization dimensions have been read if (mf6_input%p_block_dfns(iblock)%blockname == 'DIMENSIONS' .and. & - filetype(1:3) == 'DIS') then - call set_model_shape(mf6_input%file_type, componentMemPath, & - mf6_input%memoryPath, mshape) + pkgtype(1:3) == 'DIS') then + call set_model_shape(mf6_input%pkgtype, componentMemPath, & + mf6_input%mempath, mshape) end if end do ! ! -- close logging statement call idm_log_close(mf6_input%component_name, & mf6_input%subcomponent_name, iout) - end subroutine idm_load_from_blockparser + end subroutine idm_load !> @brief procedure to load a block !! !! Use parser to load information from a block into the __INPUT__ - !! memory context location of the memory manager. + !! memory context location of the memory manager. Allow for recursive + !! calls for blocks that may appear multiple times in an input file. !! !< - subroutine parse_block(parser, mf6_input, iblock, mshape, iout) + recursive subroutine parse_block(parser, mf6_input, iblock, mshape, iout, & + recursive_call) use MemoryTypeModule, only: MemoryType use MemoryManagerModule, only: get_from_memorylist type(BlockParserType), intent(inout) :: parser !< block parser @@ -108,18 +106,19 @@ subroutine parse_block(parser, mf6_input, iblock, mshape, iout) integer(I4B), intent(in) :: iblock !< consecutive block number as defined in definition file integer(I4B), dimension(:), contiguous, pointer, intent(inout) :: mshape !< model shape integer(I4B), intent(in) :: iout !< unit number for output + logical(LGP), intent(in) :: recursive_call !< true if recursive call logical(LGP) :: isblockfound logical(LGP) :: endOfBlock logical(LGP) :: supportOpenClose integer(I4B) :: ierr - logical(LGP) :: found + logical(LGP) :: found, required type(MemoryType), pointer :: mt ! ! -- disu vertices/cell2d blocks are contingent on NVERT dimension - if (mf6_input%file_type == 'DISU6' .and. & + if (mf6_input%pkgtype == 'DISU6' .and. & (mf6_input%p_block_dfns(iblock)%blockname == 'VERTICES' .or. & mf6_input%p_block_dfns(iblock)%blockname == 'CELL2D')) then - call get_from_memorylist('NVERT', mf6_input%memoryPath, mt, found, .false.) + call get_from_memorylist('NVERT', mf6_input%mempath, mt, found, .false.) if (.not. found) return if (mt%intsclr == 0) return end if @@ -128,9 +127,10 @@ subroutine parse_block(parser, mf6_input, iblock, mshape, iout) supportOpenClose = (mf6_input%p_block_dfns(iblock)%blockname /= 'GRIDDATA') ! ! -- parser search for block + required = mf6_input%p_block_dfns(iblock)%required .and. .not. recursive_call call parser%GetBlock(mf6_input%p_block_dfns(iblock)%blockname, isblockfound, & ierr, supportOpenClose=supportOpenClose, & - blockRequired=mf6_input%p_block_dfns(iblock)%required) + blockRequired=required) ! ! -- process block if (isblockfound) then @@ -149,7 +149,15 @@ subroutine parse_block(parser, mf6_input, iblock, mshape, iout) end do end if end if - + ! + ! -- recurse if block is reloadable and was just read + if (mf6_input%p_block_dfns(iblock)%block_variable) then + if (isblockfound) then + call parse_block(parser, mf6_input, iblock, mshape, iout, .true.) + end if + end if + ! + ! -- return return end subroutine parse_block @@ -192,16 +200,16 @@ subroutine parse_iofile_tag(parser, mf6_input, iblock, mshape, tag, found, & trim(tag)//'" but instead found "'//trim(io_tag)//'"' call store_error(errmsg) call parser%StoreErrorUnit() + else ! ! -- matches, read and load file name - else idt => & get_param_definition_type(mf6_input%p_param_dfns, & mf6_input%component_type, & mf6_input%subcomponent_type, & mf6_input%p_block_dfns(iblock)%blockname, & words(4)) - call load_string_type(parser, idt, mf6_input%memoryPath, iout) + call load_string_type(parser, idt, mf6_input%mempath, iout) ! ! -- io tag loaded found = .true. @@ -266,7 +274,7 @@ recursive subroutine parse_tag(parser, mf6_input, iblock, mshape, iout, & if (.not. found_io_tag) then ! ! -- load standard keyword tag - call load_keyword_type(parser, idt, mf6_input%memoryPath, iout) + call load_keyword_type(parser, idt, mf6_input%mempath, iout) end if ! ! -- check/set as dev option @@ -275,23 +283,23 @@ recursive subroutine parse_tag(parser, mf6_input, iblock, mshape, iout, & call parser%DevOpt() end if case ('STRING') - call load_string_type(parser, idt, mf6_input%memoryPath, iout) + call load_string_type(parser, idt, mf6_input%mempath, iout) case ('INTEGER') - call load_integer_type(parser, idt, mf6_input%memoryPath, iout) + call load_integer_type(parser, idt, mf6_input%mempath, iout) case ('INTEGER1D') - call load_integer1d_type(parser, idt, mf6_input%memoryPath, mshape, iout) + call load_integer1d_type(parser, idt, mf6_input%mempath, mshape, iout) case ('INTEGER2D') - call load_integer2d_type(parser, idt, mf6_input%memoryPath, mshape, iout) + call load_integer2d_type(parser, idt, mf6_input%mempath, mshape, iout) case ('INTEGER3D') - call load_integer3d_type(parser, idt, mf6_input%memoryPath, mshape, iout) + call load_integer3d_type(parser, idt, mf6_input%mempath, mshape, iout) case ('DOUBLE') - call load_double_type(parser, idt, mf6_input%memoryPath, iout) + call load_double_type(parser, idt, mf6_input%mempath, iout) case ('DOUBLE1D') - call load_double1d_type(parser, idt, mf6_input%memoryPath, mshape, iout) + call load_double1d_type(parser, idt, mf6_input%mempath, mshape, iout) case ('DOUBLE2D') - call load_double2d_type(parser, idt, mf6_input%memoryPath, mshape, iout) + call load_double2d_type(parser, idt, mf6_input%mempath, mshape, iout) case ('DOUBLE3D') - call load_double3d_type(parser, idt, mf6_input%memoryPath, mshape, iout) + call load_double3d_type(parser, idt, mf6_input%mempath, mshape, iout) case default write (errmsg, '(4x,a,a)') 'Failure reading data for tag: ', trim(tag) call store_error(errmsg) @@ -325,13 +333,16 @@ subroutine parse_structarray_block(parser, mf6_input, iblock, mshape, iout) integer(I4B), dimension(:), contiguous, pointer, intent(inout) :: mshape !< model shape integer(I4B), intent(in) :: iout !< unit number for output type(InputParamDefinitionType), pointer :: idt !< input data type object describing this record - integer(I4B), pointer :: nrow + integer(I4B) :: blocknum, iwords, ilen + integer(I4B), pointer :: nrow => null() integer(I4B) :: icol integer(I4B) :: ncol integer(I4B) :: nwords character(len=16), dimension(:), allocatable :: words type(StructArrayType), pointer :: struct_array character(len=:), allocatable :: parse_str + character(len=100) :: varname + character(len=3) :: block_suffix = 'num' ! ! -- set input definition for this block idt => get_aggregate_definition_type(mf6_input%p_aggregate_dfns, & @@ -339,34 +350,80 @@ subroutine parse_structarray_block(parser, mf6_input, iblock, mshape, iout) mf6_input%subcomponent_type, & mf6_input%p_block_dfns(iblock)%blockname) ! + ! -- if block is reloadable read the block number + if (mf6_input%p_block_dfns(iblock)%block_variable) then + blocknum = parser%GetInteger() + else + blocknum = 0 + end if + ! ! -- identify variable names, ignore first RECARRAY column parse_str = trim(idt%datatype)//' ' call parseline(parse_str, nwords, words) ncol = nwords - 1 ! + ! -- a column will be prepended if block is reloadable + if (blocknum > 0) ncol = ncol + 1 + ! ! -- use shape to set the max num of rows - call mem_setptr(nrow, idt%shape, mf6_input%memoryPath) + if (idt%shape /= '') then + call mem_setptr(nrow, idt%shape, mf6_input%mempath) + end if ! ! -- create a structured array - struct_array => constructStructArray(ncol, nrow) + struct_array => constructStructArray(ncol, nrow, blocknum) + nullify (nrow) + ! + ! -- create structarray vectors for each column do icol = 1, ncol + ! + ! -- if block is reloadable, block number is first column + if (blocknum > 0) then + if (icol == 1) then + ! + ! -- assign first column as the block number + ilen = len_trim(mf6_input%p_block_dfns(iblock)%blockname) + ! + if (ilen > (LENVARNAME - len(block_suffix))) then + varname = & + mf6_input%p_block_dfns(iblock)% & + blockname(1:(LENVARNAME - len(block_suffix)))//block_suffix + else + varname = trim(mf6_input%p_block_dfns(iblock)%blockname)//block_suffix + end if + ! + call struct_array%mem_create_vector(icol, 'INTEGER', & + varname, & + mf6_input%mempath, '', & + .false.) + ! + ! -- continue as this column managed by internally SA object + cycle + end if + ! + ! -- set indexex (where first column is blocknum) + iwords = icol + else + ! + ! -- set indexes (no blocknum column) + iwords = icol + 1 + end if ! ! -- set pointer to input definition for this 1d vector idt => get_param_definition_type(mf6_input%p_param_dfns, & mf6_input%component_type, & mf6_input%subcomponent_type, & mf6_input%p_block_dfns(iblock)%blockname, & - words(icol + 1)) + words(iwords)) ! ! -- allocate variable in memory manager call struct_array%mem_create_vector(icol, idt%datatype, idt%mf6varname, & - mf6_input%memoryPath, idt%shape, & + mf6_input%mempath, idt%shape, & idt%preserve_case) end do ! ! -- read the structured array call struct_array%read_from_parser(parser, iout) - call parser%terminateblock() ! ! -- destroy the structured array reader call destructStructArray(struct_array) diff --git a/src/Utilities/Idm/ModflowInput.f90 b/src/Utilities/Idm/ModflowInput.f90 index 6bcd78b0d51..f9d70cf61f4 100644 --- a/src/Utilities/Idm/ModflowInput.f90 +++ b/src/Utilities/Idm/ModflowInput.f90 @@ -10,7 +10,7 @@ module ModflowInputModule use KindModule, only: I4B, LGP use ConstantsModule, only: LENMEMPATH, LENCOMPONENTNAME, & - LENPACKAGETYPE, LENFTYPE + LENPACKAGETYPE use MemoryHelperModule, only: create_mem_path use InputDefinitionModule, only: InputParamDefinitionType, & InputBlockDefinitionType @@ -32,12 +32,12 @@ module ModflowInputModule !! !< type ModflowInputType - character(len=LENFTYPE) :: file_type + character(len=LENPACKAGETYPE) :: pkgtype character(len=LENCOMPONENTNAME) :: component_type character(len=LENCOMPONENTNAME) :: subcomponent_type character(len=LENCOMPONENTNAME) :: component_name character(len=LENCOMPONENTNAME) :: subcomponent_name - character(len=LENMEMPATH) :: memoryPath + character(len=LENMEMPATH) :: mempath character(len=LENMEMPATH) :: component type(InputBlockDefinitionType), dimension(:), pointer :: p_block_dfns type(InputParamDefinitionType), dimension(:), pointer :: p_aggregate_dfns @@ -48,24 +48,24 @@ module ModflowInputModule !> @brief function to return ModflowInputType !< - function getModflowInput(ftype, component_type, & + function getModflowInput(pkgtype, component_type, & subcomponent_type, component_name, subcomponent_name) & result(mf6_input) - character(len=*), intent(in) :: ftype !< file type to load, such as DIS6, DISV6, NPF6 + character(len=*), intent(in) :: pkgtype !< package type to load, such as DIS6, DISV6, NPF6 character(len=*), intent(in) :: component_type !< component type, such as GWF or GWT character(len=*), intent(in) :: subcomponent_type !< subcomponent type, such as DIS or NPF character(len=*), intent(in) :: component_name !< component name, such as MYGWFMODEL character(len=*), intent(in) :: subcomponent_name !< subcomponent name, such as MYWELLPACKAGE type(ModflowInputType) :: mf6_input - mf6_input%file_type = trim(ftype) + mf6_input%pkgtype = trim(pkgtype) mf6_input%component_type = trim(component_type) mf6_input%subcomponent_type = trim(subcomponent_type) mf6_input%component_name = trim(component_name) mf6_input%subcomponent_name = trim(subcomponent_name) - mf6_input%memoryPath = create_mem_path(component_name, subcomponent_name, & - idm_context) + mf6_input%mempath = create_mem_path(component_name, subcomponent_name, & + idm_context) mf6_input%component = trim(component_type)//'/'//trim(subcomponent_type) mf6_input%p_block_dfns => block_definitions(mf6_input%component) diff --git a/src/Utilities/Idm/StructArray.f90 b/src/Utilities/Idm/StructArray.f90 index 0e9884b8514..05823f9bb4a 100644 --- a/src/Utilities/Idm/StructArray.f90 +++ b/src/Utilities/Idm/StructArray.f90 @@ -9,8 +9,10 @@ module StructArrayModule use KindModule, only: I4B, DP, LGP use ConstantsModule, only: DNODATA, LINELENGTH + use SimVariablesModule, only: errmsg + use SimModule, only: store_error use StructVectorModule, only: StructVectorType - use MemoryManagerModule, only: mem_allocate + use MemoryManagerModule, only: mem_allocate, mem_reallocate, mem_setptr use CharacterStringModule, only: CharacterStringType use STLVecIntModule, only: STLVecInt use IdmLoggerModule, only: idm_log_var @@ -32,16 +34,21 @@ module StructArrayModule type StructArrayType integer(I4B) :: ncol integer(I4B) :: nrow + integer(I4B) :: blocknum + logical(LGP) :: deferred_shape = .false. + integer(I4B) :: deferred_size_init = 5 type(StructVectorType), dimension(:), allocatable :: struct_vector_1d contains procedure :: mem_create_vector procedure :: add_vector_int1d procedure :: add_vector_dbl1d - procedure :: add_vector_str1d + procedure :: add_vector_charstr1d procedure :: add_vector_intvector procedure :: read_from_parser - procedure :: load_intvector + procedure :: memload_vectors + procedure :: load_deferred_vector procedure :: log_structarray_vars + procedure :: check_reallocate end type StructArrayType @@ -49,14 +56,34 @@ module StructArrayModule !> @brief constructor for a struct_array !< - function constructStructArray(ncol, nrow) result(struct_array) + function constructStructArray(ncol, nrow, blocknum) result(struct_array) integer(I4B), intent(in) :: ncol !< number of columns in the StructArrayType - integer(I4B), intent(in) :: nrow !< number of rows in the StructArrayType + integer(I4B), pointer, intent(in) :: nrow !< number of rows in the StructArrayType + integer(I4B), intent(in) :: blocknum !< valid block number or 0 type(StructArrayType), pointer :: struct_array !< new StructArrayType - + ! + ! -- allocate StructArrayType allocate (struct_array) + ! + ! -- set number of arrays struct_array%ncol = ncol - struct_array%nrow = nrow + ! + ! -- set rows if known or set deferred + if (associated(nrow)) then + struct_array%nrow = nrow + else + struct_array%nrow = 0 + struct_array%deferred_shape = .true. + end if + ! + ! -- set blocknum + if (blocknum > 0) then + struct_array%blocknum = blocknum + else + struct_array%blocknum = 0 + end if + ! + ! -- allocate StructVectorType objects allocate (struct_array%struct_vector_1d(ncol)) end function constructStructArray @@ -83,34 +110,65 @@ subroutine mem_create_vector(this, icol, vartype, name, memoryPath, & logical(LGP), optional, intent(in) :: preserve_case !< flag indicating whether or not to preserve case integer(I4B), dimension(:), pointer, contiguous :: int1d real(DP), dimension(:), pointer, contiguous :: dbl1d - type(CharacterStringType), dimension(:), pointer, contiguous :: cstr1d + type(CharacterStringType), dimension(:), pointer, contiguous :: charstr1d type(STLVecInt), pointer :: intvector integer(I4B) :: j integer(I4B) :: inodata = 999 !todo: create INODATA in constants? - + ! + ! -- allocate array memory for StructVectorType select case (vartype) + ! case ('INTEGER1D') + ! + ! -- allocate intvector object allocate (intvector) + ! + ! -- initialize StructVector and add to StructArray call this%add_vector_intvector(name, memoryPath, varname_shape, icol, & intvector) + ! case ('INTEGER') - call mem_allocate(int1d, this%nrow, name, memoryPath) + ! + if (this%deferred_shape) then + ! -- shape not known, allocate locally + allocate (int1d(this%deferred_size_init)) + else + ! -- shape known, allocate in managed memory + call mem_allocate(int1d, this%nrow, name, memoryPath) + end if + ! + ! -- initialize vector values do j = 1, this%nrow int1d(j) = inodata end do + ! + ! -- initialize StructVector and add to StructArray call this%add_vector_int1d(name, memoryPath, icol, int1d) + ! case ('DOUBLE') + ! call mem_allocate(dbl1d, this%nrow, name, memoryPath) + ! do j = 1, this%nrow dbl1d(j) = DNODATA end do + ! call this%add_vector_dbl1d(name, memoryPath, icol, dbl1d) - case ('STRING') - call mem_allocate(cstr1d, LINELENGTH, this%nrow, name, memoryPath) + ! + case ('STRING', 'KEYWORD') + ! + if (this%deferred_shape) then + allocate (charstr1d(this%deferred_size_init)) + else + call mem_allocate(charstr1d, LINELENGTH, this%nrow, name, memoryPath) + end if + ! do j = 1, this%nrow - cstr1d(j) = '' + charstr1d(j) = '' end do - call this%add_vector_str1d(icol, cstr1d, preserve_case) + ! + call this%add_vector_charstr1d(name, memoryPath, icol, charstr1d, & + varname_shape, preserve_case) end select return @@ -125,11 +183,25 @@ subroutine add_vector_int1d(this, varname, memoryPath, icol, int1d) integer(I4B), intent(in) :: icol !< column of the vector integer(I4B), dimension(:), pointer, contiguous, intent(in) :: int1d !< vector to add type(StructVectorType) :: sv + ! + ! -- initialize StructVectorType sv%varname = varname - sv%memoryPath = memoryPath + sv%shapevar = '' + sv%mempath = memoryPath sv%memtype = 1 sv%int1d => int1d + ! + ! -- set size + if (this%deferred_shape) then + sv%size = this%deferred_size_init + else + sv%size = this%nrow + end if + ! + ! -- set the object in the Struct Array this%struct_vector_1d(icol) = sv + ! + ! -- return return end subroutine add_vector_int1d @@ -142,29 +214,57 @@ subroutine add_vector_dbl1d(this, varname, memoryPath, icol, dbl1d) integer(I4B), intent(in) :: icol !< column of the vector real(DP), dimension(:), pointer, contiguous, intent(in) :: dbl1d !< vector to add type(StructVectorType) :: sv + ! + ! -- initialize StructVectorType sv%varname = varname - sv%memoryPath = memoryPath + sv%shapevar = '' + sv%mempath = memoryPath sv%memtype = 2 sv%dbl1d => dbl1d + sv%size = this%nrow + ! + ! -- set the object in the Struct Array this%struct_vector_1d(icol) = sv + ! + ! -- return return end subroutine add_vector_dbl1d - !> @brief add str1d to StructArrayType + !> @brief add charstr1d to StructArrayType !< - subroutine add_vector_str1d(this, icol, str1d, preserve_case) + subroutine add_vector_charstr1d(this, varname, memoryPath, icol, charstr1d, & + varname_shape, preserve_case) class(StructArrayType) :: this !< StructArrayType integer(I4B), intent(in) :: icol !< column of the vector + character(len=*), intent(in) :: varname !< name of the variable + character(len=*), intent(in) :: memoryPath !< memory path to vector type(CharacterStringType), dimension(:), pointer, contiguous, intent(in) :: & - str1d !< vector to add + charstr1d !< vector to add + character(len=*), intent(in) :: varname_shape !< shape of variable logical(LGP), intent(in) :: preserve_case type(StructVectorType) :: sv + ! + ! -- initialize StructVectorType + sv%varname = varname + sv%shapevar = varname_shape + sv%mempath = memoryPath sv%memtype = 3 sv%preserve_case = preserve_case - sv%str1d => str1d + sv%charstr1d => charstr1d + ! + ! -- set size + if (this%deferred_shape) then + sv%size = this%deferred_size_init + else + sv%size = this%nrow + end if + ! + ! -- set the object in the Struct Array this%struct_vector_1d(icol) = sv + ! + ! -- return return - end subroutine add_vector_str1d + end subroutine add_vector_charstr1d !> @brief add STLVecInt to StructArrayType !< @@ -177,41 +277,170 @@ subroutine add_vector_intvector(this, varname, memoryPath, varname_shape, & integer(I4B), intent(in) :: icol !< column of the vector type(STLVecInt), pointer, intent(in) :: intvector !< vector to add type(StructVectorType) :: sv - + ! + ! -- initialize STLVecInt call intvector%init() + ! + ! -- set pointer to dynamic shape call mem_setptr(sv%intvector_shape, varname_shape, memoryPath) - + ! + ! -- initialize StructVectorType sv%varname = varname - sv%memoryPath = memoryPath + sv%shapevar = varname_shape + sv%mempath = memoryPath sv%memtype = 4 sv%intvector => intvector + sv%size = -1 + ! + ! -- set the object in the Struct Array this%struct_vector_1d(icol) = sv + ! + ! -- return return end subroutine add_vector_intvector - !> @brief load integer vector into StructArrayType + subroutine load_deferred_vector(this, icol) + use MemoryManagerModule, only: get_isize + class(StructArrayType) :: this !< StructArrayType + integer(I4B), intent(in) :: icol + integer(I4B) :: i, isize + integer(I4B), dimension(:), pointer, contiguous :: p_int1d + real(DP), dimension(:), pointer, contiguous :: p_dbl1d + type(CharacterStringType), dimension(:), pointer, contiguous :: p_charstr1d + ! + ! -- check if already mem managed variable + call get_isize(this%struct_vector_1d(icol)%varname, & + this%struct_vector_1d(icol)%mempath, isize) + ! + ! -- allocate and load based on memtype + select case (this%struct_vector_1d(icol)%memtype) + ! + case (1) ! -- memtype integer + ! + if (isize > 0) then + ! -- variable exists, reallocate and append + call mem_setptr(p_int1d, this%struct_vector_1d(icol)%varname, & + this%struct_vector_1d(icol)%mempath) + ! -- Currently deferred vectors are appended to managed + ! memory vectors when they are already allocated + ! (e.g. SIMNAM SolutionGroup) + call mem_reallocate(p_int1d, this%nrow + isize, & + this%struct_vector_1d(icol)%varname, & + this%struct_vector_1d(icol)%mempath) + + do i = 1, this%nrow + p_int1d(isize + i) = this%struct_vector_1d(icol)%int1d(i) + end do + else + ! + ! -- allocate memory manager vector + call mem_allocate(p_int1d, this%nrow, & + this%struct_vector_1d(icol)%varname, & + this%struct_vector_1d(icol)%mempath) + ! + ! -- load local vector to managed memory + do i = 1, this%nrow + p_int1d(i) = this%struct_vector_1d(icol)%int1d(i) + end do + end if + ! + ! -- deallocate local memory + deallocate (this%struct_vector_1d(icol)%int1d) + ! + ! -- update structvector + this%struct_vector_1d(icol)%int1d => p_int1d + this%struct_vector_1d(icol)%size = this%nrow + ! + case (2) ! -- memtype real + ! + call mem_allocate(p_dbl1d, this%nrow, & + this%struct_vector_1d(icol)%varname, & + this%struct_vector_1d(icol)%mempath) + ! + do i = 1, this%nrow + p_dbl1d(i) = this%struct_vector_1d(icol)%dbl1d(i) + end do + ! + deallocate (this%struct_vector_1d(icol)%dbl1d) + ! + ! -- + this%struct_vector_1d(icol)%dbl1d => p_dbl1d + this%struct_vector_1d(icol)%size = this%nrow + ! + case (3) ! -- memtype charstring + if (isize > 0) then + call mem_setptr(p_charstr1d, this%struct_vector_1d(icol)%varname, & + this%struct_vector_1d(icol)%mempath) + call mem_reallocate(p_charstr1d, LINELENGTH, this%nrow + isize, & + this%struct_vector_1d(icol)%varname, & + this%struct_vector_1d(icol)%mempath) + + do i = 1, this%nrow + p_charstr1d(isize + i) = this%struct_vector_1d(icol)%charstr1d(i) + end do + else + ! + call mem_allocate(p_charstr1d, LINELENGTH, this%nrow, & + this%struct_vector_1d(icol)%varname, & + this%struct_vector_1d(icol)%mempath) + ! + do i = 1, this%nrow + p_charstr1d(i) = this%struct_vector_1d(icol)%charstr1d(i) + end do + end if + ! + deallocate (this%struct_vector_1d(icol)%charstr1d) + ! + case (4) ! -- memtype intvector + ! no-op + case default + end select + ! + ! -- return + return + end subroutine load_deferred_vector + + !> @brief load deferred vectors into managed memory !< - subroutine load_intvector(this) + subroutine memload_vectors(this) class(StructArrayType) :: this !< StructArrayType - integer(I4B) :: i, j + integer(I4B) :: icol, j integer(I4B), dimension(:), pointer, contiguous :: p_intvector - ! -- if an allocatable vector has been read, add to MemoryManager - do i = 1, this%ncol - if (this%struct_vector_1d(i)%memtype == 4) then - call this%struct_vector_1d(i)%intvector%shrink_to_fit() - call mem_allocate(p_intvector, this%struct_vector_1d(i)%intvector%size, & - this%struct_vector_1d(i)%varname, & - this%struct_vector_1d(i)%memoryPath) - do j = 1, this%struct_vector_1d(i)%intvector%size - p_intvector(j) = this%struct_vector_1d(i)%intvector%at(j) + ! + do icol = 1, this%ncol + ! + if (this%struct_vector_1d(icol)%memtype == 4) then + ! -- intvectors always need to be loaded + ! + ! -- size intvector to number of values read + call this%struct_vector_1d(icol)%intvector%shrink_to_fit() + ! + ! -- allocate memory manager vector + call mem_allocate(p_intvector, & + this%struct_vector_1d(icol)%intvector%size, & + this%struct_vector_1d(icol)%varname, & + this%struct_vector_1d(icol)%mempath) + ! + ! -- load local vector to managed memory + do j = 1, this%struct_vector_1d(icol)%intvector%size + p_intvector(j) = this%struct_vector_1d(icol)%intvector%at(j) end do - call this%struct_vector_1d(i)%intvector%destroy() - deallocate (this%struct_vector_1d(i)%intvector) - nullify (this%struct_vector_1d(i)%intvector_shape) + ! + ! -- cleanup local memory + call this%struct_vector_1d(icol)%intvector%destroy() + deallocate (this%struct_vector_1d(icol)%intvector) + nullify (this%struct_vector_1d(icol)%intvector_shape) + ! + else if (this%deferred_shape) then + ! + ! -- load as shape wasn't known + call this%load_deferred_vector(icol) end if end do + ! + ! -- return return - end subroutine load_intvector + end subroutine memload_vectors !> @brief log information about the StructArrayType !< @@ -223,26 +452,121 @@ subroutine log_structarray_vars(this, iout) ! ! -- idm variable logging do j = 1, this%ncol + ! + ! -- log based on memtype select case (this%struct_vector_1d(j)%memtype) - case (1) + ! + case (1) ! -- memtype integer + ! call idm_log_var(this%struct_vector_1d(j)%int1d, & this%struct_vector_1d(j)%varname, & - this%struct_vector_1d(j)%memoryPath, iout) - case (2) + this%struct_vector_1d(j)%mempath, iout) + ! + case (2) ! -- memtype real + ! call idm_log_var(this%struct_vector_1d(j)%dbl1d, & this%struct_vector_1d(j)%varname, & - this%struct_vector_1d(j)%memoryPath, iout) - case (4) + this%struct_vector_1d(j)%mempath, iout) + ! + case (4) ! -- memtype intvector + ! call mem_setptr(int1d, this%struct_vector_1d(j)%varname, & - this%struct_vector_1d(j)%memoryPath) + this%struct_vector_1d(j)%mempath) + ! call idm_log_var(int1d, this%struct_vector_1d(j)%varname, & - this%struct_vector_1d(j)%memoryPath, iout) - + this%struct_vector_1d(j)%mempath, iout) + ! end select + ! end do + ! + ! -- return return end subroutine log_structarray_vars + !> @brief reallocate local memory for deferred vectors if necessary + !< + subroutine check_reallocate(this) + class(StructArrayType) :: this !< StructArrayType + integer(I4B) :: i, j, newsize + integer(I4B), dimension(:), pointer, contiguous :: p_int1d + real(DP), dimension(:), pointer, contiguous :: p_dbl1d + type(CharacterStringType), dimension(:), pointer, contiguous :: p_charstr1d + integer(I4B) :: reallocate_mult + ! + ! -- set growth rate + reallocate_mult = 2 + ! + do j = 1, this%ncol + ! + ! -- reallocate based on memtype + select case (this%struct_vector_1d(j)%memtype) + ! + case (1) ! -- memtype integer + ! + ! -- check if more space needed + if (this%nrow > this%struct_vector_1d(j)%size) then + ! + ! -- calculate new size + newsize = this%struct_vector_1d(j)%size * reallocate_mult + ! + ! -- allocate new vector + allocate (p_int1d(newsize)) + ! + ! -- copy from old to new + do i = 1, this%struct_vector_1d(j)%size + p_int1d(i) = this%struct_vector_1d(j)%int1d(i) + end do + ! + ! -- deallocate old vector + deallocate (this%struct_vector_1d(j)%int1d) + ! + ! -- update struct array object + this%struct_vector_1d(j)%int1d => p_int1d + this%struct_vector_1d(j)%size = newsize + end if + ! + case (2) ! -- memtype real + if (this%nrow > this%struct_vector_1d(j)%size) then + ! + newsize = this%struct_vector_1d(j)%size * reallocate_mult + ! + allocate (p_dbl1d(newsize)) + ! + do i = 1, this%struct_vector_1d(j)%size + p_dbl1d(i) = this%struct_vector_1d(j)%dbl1d(i) + end do + ! + deallocate (this%struct_vector_1d(j)%dbl1d) + ! + this%struct_vector_1d(j)%dbl1d => p_dbl1d + this%struct_vector_1d(j)%size = newsize + end if + ! + case (3) ! -- memtype charstring + if (this%nrow > this%struct_vector_1d(j)%size) then + ! + newsize = this%struct_vector_1d(j)%size * reallocate_mult + ! + allocate (p_charstr1d(newsize)) + ! + do i = 1, this%struct_vector_1d(j)%size + p_charstr1d(i) = this%struct_vector_1d(j)%charstr1d(i) + end do + ! + deallocate (this%struct_vector_1d(j)%charstr1d) + ! + this%struct_vector_1d(j)%charstr1d => p_charstr1d + this%struct_vector_1d(j)%size = newsize + end if + case default + end select + end do + ! + ! -- return + return + end subroutine check_reallocate + !> @brief read from the block parser to fill the StructArrayType !< subroutine read_from_parser(this, parser, iout) @@ -250,26 +574,80 @@ subroutine read_from_parser(this, parser, iout) type(BlockParserType) :: parser !< block parser to read from integer(I4B), intent(in) :: iout !< unit number for output logical(LGP) :: endOfBlock - integer(I4B) :: i, j, k + integer(I4B) :: irow, j, k integer(I4B) :: intval, numval - character(len=LINELENGTH) :: str1d + character(len=LINELENGTH) :: str + character(len=:), allocatable :: line ! - ! -- read block - do i = 1, this%nrow + ! -- initialize index irow + irow = 0 + ! + ! -- read entire block + do + ! + ! -- read next line call parser%GetNextLine(endOfBlock) - if (endOfBlock) exit + ! + if (endOfBlock) then + ! -- no more lines + exit + ! + else if (this%deferred_shape) then + ! + ! -- shape unknown, track lines read + this%nrow = this%nrow + 1 + ! + ! -- check and update memory allocation + call this%check_reallocate() + end if + ! + ! -- update irow index + irow = irow + 1 + ! + ! -- handle line reads by column memtype do j = 1, this%ncol + ! select case (this%struct_vector_1d(j)%memtype) - case (1) - this%struct_vector_1d(j)%int1d(i) = parser%GetInteger() - case (2) - this%struct_vector_1d(j)%dbl1d(i) = parser%GetDouble() - case (3) - call parser%GetString(str1d, & - (.not. this%struct_vector_1d(j)%preserve_case)) - this%struct_vector_1d(j)%str1d(i) = str1d - case (4) - numval = this%struct_vector_1d(j)%intvector_shape(i) + ! + case (1) ! -- memtype integer + ! + ! -- if reloadable block and first col, store blocknum + if (j == 1 .and. this%blocknum > 0) then + ! -- store blocknum + this%struct_vector_1d(j)%int1d(irow) = this%blocknum + else + ! -- read and store int + this%struct_vector_1d(j)%int1d(irow) = parser%GetInteger() + end if + ! + case (2) ! -- memtype real + ! + this%struct_vector_1d(j)%dbl1d(irow) = parser%GetDouble() + ! + case (3) ! -- memtype charstring + ! + !if (this%struct_vector_1d(j)%shapevar == ':') then + if (this%struct_vector_1d(j)%shapevar /= '') then + ! -- if last column with any shape, store rest of line + if (j == this%ncol) then + call parser%GetRemainingLine(line) + this%struct_vector_1d(j)%charstr1d(irow) = line + deallocate (line) + end if + else + ! + ! -- read string token + call parser%GetString(str, & + (.not. this%struct_vector_1d(j)%preserve_case)) + this%struct_vector_1d(j)%charstr1d(irow) = str + end if + ! + case (4) ! -- memtype intvector + ! + ! -- get shape for this row + numval = this%struct_vector_1d(j)%intvector_shape(irow) + ! + ! -- read and store row values do k = 1, numval intval = parser%GetInteger() call this%struct_vector_1d(j)%intvector%push_back(intval) @@ -278,8 +656,8 @@ subroutine read_from_parser(this, parser, iout) end do end do ! - ! -- if jagged array was read, load to input path - call this%load_intvector() + ! -- if deferred shape vectors were read, load to input path + call this%memload_vectors() ! ! -- log loaded variables call this%log_structarray_vars(iout) diff --git a/src/Utilities/Idm/StructVector.f90 b/src/Utilities/Idm/StructVector.f90 index 20e4e81724f..abc2bd07345 100644 --- a/src/Utilities/Idm/StructVector.f90 +++ b/src/Utilities/Idm/StructVector.f90 @@ -23,13 +23,15 @@ module StructVectorModule !< type StructVectorType character(len=LENVARNAME) :: varname - character(len=LENMEMPATH) :: memoryPath + character(len=LENVARNAME) :: shapevar + character(len=LENMEMPATH) :: mempath integer(I4B) :: memtype = 0 + integer(I4B) :: size = 0 logical(LGP) :: preserve_case = .false. integer(I4B), dimension(:), pointer, contiguous :: int1d => null() real(DP), dimension(:), pointer, contiguous :: dbl1d => null() type(CharacterStringType), dimension(:), pointer, contiguous :: & - str1d => null() + charstr1d => null() type(STLVecInt), pointer :: intvector => null() integer(I4B), dimension(:), pointer, contiguous :: intvector_shape => null() diff --git a/src/meson.build b/src/meson.build index bf5873571f4..927565f0fc3 100644 --- a/src/meson.build +++ b/src/meson.build @@ -142,6 +142,7 @@ modflow_sources = files( 'Utilities' / 'ArrayRead' / 'LayeredArrayReader.f90', 'Utilities' / 'Idm' / 'IdmLogger.f90', 'Utilities' / 'Idm' / 'IdmMf6FileLoader.f90', + 'Utilities' / 'Idm' / 'IdmSimulation.f90', 'Utilities' / 'Idm' / 'ModflowInput.f90', 'Utilities' / 'Idm' / 'InputDefinition.f90', 'Utilities' / 'Idm' / 'InputDefinitionSelector.f90', @@ -214,6 +215,7 @@ modflow_sources = files( 'Utilities' / 'version.f90', 'mf6core.f90', 'mf6lists.f90', + 'simnamidm.f90', 'SimulationCreate.f90', 'RunControl.f90', 'RunControlFactory.F90' diff --git a/src/mf6core.f90 b/src/mf6core.f90 index 0f050ff24a1..e994af96d2e 100644 --- a/src/mf6core.f90 +++ b/src/mf6core.f90 @@ -78,6 +78,12 @@ subroutine Mf6Initialize() ! -- print info and start timer call print_info() + ! -- create mfsim.lst + call create_lstfile() + + ! -- load input context + call static_input_load() + ! -- create call simulation_cr() @@ -214,6 +220,55 @@ subroutine print_info() end subroutine print_info + !> @brief Set up mfsim list file output logging + !! + !! This subroutine creates the mfsim list file + !! and writes the header. + !! + !< + subroutine create_lstfile() + use ConstantsModule, only: LINELENGTH + use SimVariablesModule, only: proc_id, nr_procs, simlstfile, iout + use InputOutputModule, only: getunit, openfile + use GenericUtilitiesModule, only: sim_message + use VersionModule, only: write_listfile_header + character(len=LINELENGTH) :: line + ! + ! -- Open simulation list file + iout = getunit() + ! + if (nr_procs > 1) then + write (simlstfile, '(a,i0,a)') 'mfsim.p', proc_id, '.lst' + end if + ! + call openfile(iout, 0, simlstfile, 'LIST', filstat_opt='REPLACE') + ! + ! -- write simlstfile to stdout + write (line, '(2(1x,A))') 'Writing simulation list file:', & + trim(adjustl(simlstfile)) + ! + call sim_message(line) + call write_listfile_header(iout) + ! + ! -- return + return + end subroutine create_lstfile + + !> @brief Create simulation input context + !! + !! This subroutine creates the simulation input context + !! + !< + subroutine static_input_load() + use IdmSimulationModule, only: simnam_load + ! + ! -- load input context + call simnam_load() + ! + ! -- return + return + end subroutine static_input_load + !> @brief Define the simulation !! !! This subroutine defined the simulation. Steps include: diff --git a/src/simnamidm.f90 b/src/simnamidm.f90 new file mode 100644 index 00000000000..967c9a88990 --- /dev/null +++ b/src/simnamidm.f90 @@ -0,0 +1,397 @@ +module SimNamInputModule + use InputDefinitionModule, only: InputParamDefinitionType, & + InputBlockDefinitionType + private + public sim_nam_param_definitions + public sim_nam_aggregate_definitions + public sim_nam_block_definitions + public SimNamParamFoundType + + type SimNamParamFoundType + logical :: continue = .false. + logical :: nocheck = .false. + logical :: prmem = .false. + logical :: maxerrors = .false. + logical :: tdis6 = .false. + logical :: mtype = .false. + logical :: mfname = .false. + logical :: mname = .false. + logical :: exgtype = .false. + logical :: exgfile = .false. + logical :: exgmnamea = .false. + logical :: exgmnameb = .false. + logical :: mxiter = .false. + logical :: slntype = .false. + logical :: slnfname = .false. + logical :: slnmnames = .false. + end type SimNamParamFoundType + + type(InputParamDefinitionType), parameter :: & + simnam_continue = InputParamDefinitionType & + ( & + 'SIM', & ! component + 'NAM', & ! subcomponent + 'OPTIONS', & ! block + 'CONTINUE', & ! tag name + 'CONTINUE', & ! fortran variable + 'KEYWORD', & ! type + '', & ! shape + .false., & ! required + .false., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + simnam_nocheck = InputParamDefinitionType & + ( & + 'SIM', & ! component + 'NAM', & ! subcomponent + 'OPTIONS', & ! block + 'NOCHECK', & ! tag name + 'NOCHECK', & ! fortran variable + 'KEYWORD', & ! type + '', & ! shape + .false., & ! required + .false., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + simnam_prmem = InputParamDefinitionType & + ( & + 'SIM', & ! component + 'NAM', & ! subcomponent + 'OPTIONS', & ! block + 'MEMORY_PRINT_OPTION', & ! tag name + 'PRMEM', & ! fortran variable + 'STRING', & ! type + '', & ! shape + .false., & ! required + .false., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + simnam_maxerrors = InputParamDefinitionType & + ( & + 'SIM', & ! component + 'NAM', & ! subcomponent + 'OPTIONS', & ! block + 'MAXERRORS', & ! tag name + 'MAXERRORS', & ! fortran variable + 'INTEGER', & ! type + '', & ! shape + .false., & ! required + .false., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + simnam_tdis6 = InputParamDefinitionType & + ( & + 'SIM', & ! component + 'NAM', & ! subcomponent + 'TIMING', & ! block + 'TDIS6', & ! tag name + 'TDIS6', & ! fortran variable + 'STRING', & ! type + '', & ! shape + .true., & ! required + .false., & ! multi-record + .true., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + simnam_mtype = InputParamDefinitionType & + ( & + 'SIM', & ! component + 'NAM', & ! subcomponent + 'MODELS', & ! block + 'MTYPE', & ! tag name + 'MTYPE', & ! fortran variable + 'STRING', & ! type + '', & ! shape + .true., & ! required + .true., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + simnam_mfname = InputParamDefinitionType & + ( & + 'SIM', & ! component + 'NAM', & ! subcomponent + 'MODELS', & ! block + 'MFNAME', & ! tag name + 'MFNAME', & ! fortran variable + 'STRING', & ! type + '', & ! shape + .true., & ! required + .true., & ! multi-record + .true., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + simnam_mname = InputParamDefinitionType & + ( & + 'SIM', & ! component + 'NAM', & ! subcomponent + 'MODELS', & ! block + 'MNAME', & ! tag name + 'MNAME', & ! fortran variable + 'STRING', & ! type + '', & ! shape + .true., & ! required + .true., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + simnam_exgtype = InputParamDefinitionType & + ( & + 'SIM', & ! component + 'NAM', & ! subcomponent + 'EXCHANGES', & ! block + 'EXGTYPE', & ! tag name + 'EXGTYPE', & ! fortran variable + 'STRING', & ! type + '', & ! shape + .true., & ! required + .true., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + simnam_exgfile = InputParamDefinitionType & + ( & + 'SIM', & ! component + 'NAM', & ! subcomponent + 'EXCHANGES', & ! block + 'EXGFILE', & ! tag name + 'EXGFILE', & ! fortran variable + 'STRING', & ! type + '', & ! shape + .true., & ! required + .true., & ! multi-record + .true., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + simnam_exgmnamea = InputParamDefinitionType & + ( & + 'SIM', & ! component + 'NAM', & ! subcomponent + 'EXCHANGES', & ! block + 'EXGMNAMEA', & ! tag name + 'EXGMNAMEA', & ! fortran variable + 'STRING', & ! type + '', & ! shape + .true., & ! required + .true., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + simnam_exgmnameb = InputParamDefinitionType & + ( & + 'SIM', & ! component + 'NAM', & ! subcomponent + 'EXCHANGES', & ! block + 'EXGMNAMEB', & ! tag name + 'EXGMNAMEB', & ! fortran variable + 'STRING', & ! type + '', & ! shape + .true., & ! required + .true., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + simnam_mxiter = InputParamDefinitionType & + ( & + 'SIM', & ! component + 'NAM', & ! subcomponent + 'SOLUTIONGROUP', & ! block + 'MXITER', & ! tag name + 'MXITER', & ! fortran variable + 'INTEGER', & ! type + '', & ! shape + .false., & ! required + .false., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + simnam_slntype = InputParamDefinitionType & + ( & + 'SIM', & ! component + 'NAM', & ! subcomponent + 'SOLUTIONGROUP', & ! block + 'SLNTYPE', & ! tag name + 'SLNTYPE', & ! fortran variable + 'STRING', & ! type + '', & ! shape + .true., & ! required + .true., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + simnam_slnfname = InputParamDefinitionType & + ( & + 'SIM', & ! component + 'NAM', & ! subcomponent + 'SOLUTIONGROUP', & ! block + 'SLNFNAME', & ! tag name + 'SLNFNAME', & ! fortran variable + 'STRING', & ! type + '', & ! shape + .true., & ! required + .true., & ! multi-record + .true., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + simnam_slnmnames = InputParamDefinitionType & + ( & + 'SIM', & ! component + 'NAM', & ! subcomponent + 'SOLUTIONGROUP', & ! block + 'SLNMNAMES', & ! tag name + 'SLNMNAMES', & ! fortran variable + 'STRING', & ! type + ':', & ! shape + .true., & ! required + .true., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + sim_nam_param_definitions(*) = & + [ & + simnam_continue, & + simnam_nocheck, & + simnam_prmem, & + simnam_maxerrors, & + simnam_tdis6, & + simnam_mtype, & + simnam_mfname, & + simnam_mname, & + simnam_exgtype, & + simnam_exgfile, & + simnam_exgmnamea, & + simnam_exgmnameb, & + simnam_mxiter, & + simnam_slntype, & + simnam_slnfname, & + simnam_slnmnames & + ] + + type(InputParamDefinitionType), parameter :: & + simnam_models = InputParamDefinitionType & + ( & + 'SIM', & ! component + 'NAM', & ! subcomponent + 'MODELS', & ! block + 'MODELS', & ! tag name + 'MODELS', & ! fortran variable + 'RECARRAY MTYPE MFNAME MNAME', & ! type + '', & ! shape + .true., & ! required + .false., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + simnam_exchanges = InputParamDefinitionType & + ( & + 'SIM', & ! component + 'NAM', & ! subcomponent + 'EXCHANGES', & ! block + 'EXCHANGES', & ! tag name + 'EXCHANGES', & ! fortran variable + 'RECARRAY EXGTYPE EXGFILE EXGMNAMEA EXGMNAMEB', & ! type + '', & ! shape + .true., & ! required + .false., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + simnam_solutiongroup = InputParamDefinitionType & + ( & + 'SIM', & ! component + 'NAM', & ! subcomponent + 'SOLUTIONGROUP', & ! block + 'SOLUTIONGROUP', & ! tag name + 'SOLUTIONGROUP', & ! fortran variable + 'RECARRAY SLNTYPE SLNFNAME SLNMNAMES', & ! type + '', & ! shape + .true., & ! required + .false., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + sim_nam_aggregate_definitions(*) = & + [ & + simnam_models, & + simnam_exchanges, & + simnam_solutiongroup & + ] + + type(InputBlockDefinitionType), parameter :: & + sim_nam_block_definitions(*) = & + [ & + InputBlockDefinitionType( & + 'OPTIONS', & ! blockname + .false., & ! required + .false., & ! aggregate + .false. & ! block_variable + ), & + InputBlockDefinitionType( & + 'TIMING', & ! blockname + .true., & ! required + .false., & ! aggregate + .false. & ! block_variable + ), & + InputBlockDefinitionType( & + 'MODELS', & ! blockname + .true., & ! required + .true., & ! aggregate + .false. & ! block_variable + ), & + InputBlockDefinitionType( & + 'EXCHANGES', & ! blockname + .true., & ! required + .true., & ! aggregate + .false. & ! block_variable + ), & + InputBlockDefinitionType( & + 'SOLUTIONGROUP', & ! blockname + .true., & ! required + .true., & ! aggregate + .true. & ! block_variable + ) & + ] + +end module SimNamInputModule diff --git a/utils/idmloader/scripts/dfn2f90.py b/utils/idmloader/scripts/dfn2f90.py index d0697f9eedd..ccc1a939163 100644 --- a/utils/idmloader/scripts/dfn2f90.py +++ b/utils/idmloader/scripts/dfn2f90.py @@ -1,7 +1,6 @@ import os import sys import json -import yaml from pathlib import Path from enum import Enum @@ -163,7 +162,7 @@ def _set_var_d(self): self._var_d = vardict def _construct_f90_block_statement( - self, blockname, required=False, aggregate=False + self, blockname, required=False, aggregate=False, block_var=False ): f90statement = f" InputBlockDefinitionType( &\n" f90statement += f" '{blockname}', & ! blockname\n" @@ -172,9 +171,13 @@ def _construct_f90_block_statement( else: f90statement += f" .false., & ! required\n" if aggregate: - f90statement += f" .true. & ! aggregate\n" + f90statement += f" .true., & ! aggregate\n" else: - f90statement += f" .false. & ! aggregate\n" + f90statement += f" .false., & ! aggregate\n" + if block_var: + f90statement += f" .true. & ! block_variable\n" + else: + f90statement += f" .false. & ! block_variable\n" f90statement += f" ), &" return f90statement @@ -221,6 +224,7 @@ def _set_blk_param_strs(self, blockname, component, subcomponent): required_l = None required_l = [] + has_block_var = False is_aggregate_blk = False aggregate_required = False @@ -240,6 +244,7 @@ def _set_blk_param_strs(self, blockname, component, subcomponent): v = self._var_d[k] if "block_variable" in v and v["block_variable"].upper() == "TRUE": + has_block_var = True continue c = component @@ -354,6 +359,7 @@ def _set_blk_param_strs(self, blockname, component, subcomponent): blockname.upper(), required=required, aggregate=is_aggregate_blk, + block_var=has_block_var, ) + "\n" ) @@ -448,6 +454,10 @@ def _source_file_footer(self, component, subcomponent): Path("../../../doc/mf6io/mf6ivar/dfn", "gwt-dsp.dfn"), Path("../../../src/Model/GroundWaterTransport", "gwt1dspidm.f90"), ], + [ + Path("../../../doc/mf6io/mf6ivar/dfn", "sim-nam.dfn"), + Path("../../../src", "simnamidm.f90"), + ], ] for dfn in dfns: From 31a0e8cd80ef6ef690987efa11be14d315db80cb Mon Sep 17 00:00:00 2001 From: jdhughes-usgs Date: Fri, 10 Mar 2023 14:46:43 -0600 Subject: [PATCH 040/123] feat(sfr): add alternative length_conversion and time_conversion varibles (#1154) Replacement for unit_conversion variable and is consistent with approach used in lake (LAK) package. --- autotest/test_gwf_auxvars.py | 3 +- doc/ReleaseNotes/v6.5.0.tex | 2 + doc/mf6io/mf6io.bbl | 248 ---------------------- doc/mf6io/mf6ivar/dfn/gwf-lak.dfn | 4 +- doc/mf6io/mf6ivar/dfn/gwf-sfr.dfn | 25 ++- doc/mf6io/mf6ivar/md/mf6ivar.md | 18 +- doc/mf6io/mf6ivar/tex/gwf-lak-desc.tex | 4 +- doc/mf6io/mf6ivar/tex/gwf-sfr-desc.tex | 10 +- doc/mf6io/mf6ivar/tex/gwf-sfr-options.dat | 3 +- src/Model/GroundWaterFlow/gwf3sfr8.f90 | 82 ++++++- 10 files changed, 123 insertions(+), 276 deletions(-) diff --git a/autotest/test_gwf_auxvars.py b/autotest/test_gwf_auxvars.py index f19c2fb8dfe..412b53a8385 100644 --- a/autotest/test_gwf_auxvars.py +++ b/autotest/test_gwf_auxvars.py @@ -167,7 +167,8 @@ def build_model(idx, dir): print_flows=True, save_flows=True, budget_filerecord="aux01.sfr.bud", - unit_conversion=128390.00, + length_conversion=3.281348587, + time_conversion=86400.0, nreaches=len(packagedata), packagedata=packagedata, auxiliary=["aux1", "aux2"], diff --git a/doc/ReleaseNotes/v6.5.0.tex b/doc/ReleaseNotes/v6.5.0.tex index d5c3717f458..353b902a125 100644 --- a/doc/ReleaseNotes/v6.5.0.tex +++ b/doc/ReleaseNotes/v6.5.0.tex @@ -6,6 +6,7 @@ \underline{NEW FUNCTIONALITY} \begin{itemize} \item The sorption formulation for the Groundwater Transport (GWT) model was modified for simulations involving a mobile domain and one or more immobile domains. The modifications do not affect GWT Models without the Immobile Storage and Transfer (IST) Package or GWT models without sorption. Prior to these changes, the multi-domain sorption formulation required the bulk density to be specified in both the Mobile Storage and Transfer (MST) Package and the IST Package. To generalize the formulation, the definition for bulk density in the MST Package was changed to be the mass of aquifer solid material in the mobile domain per unit volume of aquifer. The bulk density specified in the IST Package was changed to be the mass of aquifer solid material in the immobile domain per unit volume of aquifer. For multi-domain GWT Models that include sorption (and prepared for MODFLOW version 6.4.1 or earlier), it will be necessary to change the bulk density values specified in the MST and IST Packages according to the new definitions. A full description of the revised sorption formulation for multi-domain GWT Models is included in the MODFLOW 6 Supplemental Technical Information document included with the distribution. + \item Add LENGTH\_CONVERSION and TIME\_CONVERSION variables to replace the UNIT\_CONVERSION variable in the SFR Package input file. The LENGTH\_CONVERSION and TIME\_CONVERSION variables are used to convert user-specified Manning's roughness coefficients from SI units (sec/m$^{1/3}$) to model length and time units. LENGTH\_CONVERSION does not need to be specified if LENGTH\_UNITS are meters. TIME\_CONVERSION does not need to be specified if TIME\_UNITS are seconds. Warning messages will be issued if UNIT\_CONVERSION variable is specified. The model will terminate with an error if UNIT\_CONVERSION and LENGTH\_CONVERSION and TIME\_CONVERSION variables are specified. The UNIT\_CONVERSION variable in the SFR Package input file will eventually be deprecated. % \item xxx % \item xxx \end{itemize} @@ -42,6 +43,7 @@ \underline{ADVANCED STRESS PACKAGES} \begin{itemize} \item Added additional convergence checks to the Streamflow Routing (SFR), Lake (LAK) and Unsaturated Zone Flow (UZF) Packages to ensure that flows from the Water Mover (MVR) Package meet solver tolerance. Mover flows are converted into depths using the time step length and area, and the depths are compared to the Iterative Model Solution (IMS) DVCLOSE input parameter. If a depth is greater than DVCLOSE, then the iteration is marked as not converged. The maximum depth change between iterations and the advanced package feature number is written for each outer iteration to a comma-separated value file, provided the PACKAGE\_CONVERGENCE option is specified in the options block. + % \item xxx % \item xxx \end{itemize} diff --git a/doc/mf6io/mf6io.bbl b/doc/mf6io/mf6io.bbl index 240c6f51337..e69de29bb2d 100644 --- a/doc/mf6io/mf6io.bbl +++ b/doc/mf6io/mf6io.bbl @@ -1,248 +0,0 @@ -\begin{thebibliography}{35} -\providecommand{\natexlab}[1]{#1} -\expandafter\ifx\csname urlstyle\endcsname\relax - \providecommand{\doiagency}[1]{doi:\discretionary{}{}{}#1}\else - \providecommand{\doiagency}{doi:\discretionary{}{}{}\begingroup - \urlstyle{rm}\Url}\fi - -\bibitem[{Anderman and Hill(2000)}]{anderman2000modflow} -Anderman, E.R., and Hill, M.C., 2000, MODFLOW-2000, the U.S. Geological Survey - modular ground-water model-documentation of the Hydrogeologic-Unit Flow (HUF) - Package: {U.S. Geological Survey Open-File Report 2000--342, 89 p.} - -\bibitem[{Anderman and Hill(2003)}]{anderman2003modflow} -Anderman, E.R., and Hill, M.C., 2003, MODFLOW-2000, the U.S. Geological Survey - modular ground-water model---Three additions to the Hydrogeologic-Unit Flow - (HUF) Package: Alternative storage for the uppermost active cells, flows in - hydrogeologic units, and the hydraulic-conductivity depth-dependence (KDEP) - capability: {U.S. Geological Survey Open-File Report 2003--347, 36 p.} - -\bibitem[{Bakker and others(2013)Bakker, Schaars, Hughes, Langevin, and - Dausman}]{bakker2013documentation} -Bakker, M., Schaars, F., Hughes, J.D., Langevin, C.D., and Dausman, A.M., 2013, - Documentation of the seawater intrusion (SWI2) package for MODFLOW: {U.S. - Geological Survey Techniques and Methods, book 6, chap. A46, 47 p.}, accessed - June 27, 2017, at \url{https://pubs.er.usgs.gov/publication/tm6A46}. - -\bibitem[{Banta(2000)}]{modflowdrtpack} -Banta, E.R., 2000, MODFLOW-2000, the U.S. Geological Survey Modular - Ground-Water Model; documentation of packages for simulating - evapotranspiration with a segmented function (ETS1) and drains with return - flow (DRT1): {U.S. Geological Survey Open File Report 2000--466, 127 p}. - -\bibitem[{Banta(2011)}]{banta2011modflow} -Banta, E.R., 2011, MODFLOW-CDSS, a version of MODFLOW-2005 with modifications - for Colorado Decision Support Systems: {U.S. Geological Survey Open-File - Report 2011--1213, 19 p.}, accessed June 27, 2017, at - \url{https://pubs.er.usgs.gov/publication/ofr20111213}. - -\bibitem[{Fenske and others(1996)Fenske, Leake, and - Prudic}]{fenske1996documentation} -Fenske, J.P., Leake, S.A., and Prudic, D.E., 1996, Documentation of a computer - program (RES1) to simulate leakage from reservoirs using the modular - finite-difference ground-water flow model (MODFLOW): {U.S. Geological Survey - Open-File Report 96--364, 51 p.}, accessed June 27, 2017, at - \url{https://pubs.er.usgs.gov/publication/ofr96364}. - -\bibitem[{Halford and Hanson(2002)}]{halford2002} -Halford, K.J., and Hanson, R.T., 2002, User guide for the drawdown-limited, - multi-node well (MNW) package for the U.S. Geological Survey's modular - three-dimensional finite-difference ground-water flow model, versions - MODFLOW-96 and MODFLOW-2000: {U.S. Geological Survey Open-File Report - 02--293, 33 p.} - -\bibitem[{Hanson and Leake(1999)}]{hanson1999documentation} -Hanson, R.T., and Leake, S.A., 1999, Documentation for HYDMOD---A program for - extracting and processing time-series data from the U.S. Geological Survey's - modular three-dimensional finite-difference ground-water flow model: {U.S. - Geological Survey Open-File Report 98--564, 57 p.}, accessed June 27, 2017, - at \url{https://pubs.er.usgs.gov/publication/ofr98564}. - -\bibitem[{Harbaugh(2005)}]{modflow2005} -Harbaugh, A.W., 2005, MODFLOW-2005, the U.S. Geological Survey modular - ground-water model---the Ground-Water Flow Process: {U.S. Geological Survey - Techniques and Methods, book 6, chap. A16, variously paged}, accessed June - 27, 2017, at \url{https://pubs.usgs.gov/tm/2005/tm6A16/}. - -\bibitem[{Hill(1990)}]{hill1990preconditioned} -Hill, M.C., 1990, Preconditioned Conjugate-Gradient 2 (PCG2), a computer - program for solving ground-water flow equations: {U.S. Geological Survey - Water-Resources Investigations Report 90--4048, 25 p.}, accessed June 27, - 2017, at \url{https://pubs.usgs.gov/wri/wrir_90-4048}. - -\bibitem[{Hill and others(2000)Hill, Banta, Harbaugh, and - Anderman}]{hill2000modflow} -Hill, M.C., Banta, E.R., Harbaugh, A.W., and Anderman, E.R., 2000, - MODFLOW-2000, the U.S. Geological Survey modular ground-water model---User - guide to the observation, sensitivity, and parameter-estimation processes and - three post-processing programs: {U.S. Geological Survey Open-File Report - 00--184, 210 p.} - -\bibitem[{Hoffmann and others(2003)Hoffmann, Leake, Galloway, and - Wilson}]{hoffmann2003modflow} -Hoffmann, J., Leake, S.A., Galloway, D.L., and Wilson, A.M., 2003, MODFLOW-2000 - Ground-Water Model---User Guide to the Subsidence and Aquifer-System - Compaction (SUB) Package: {U.S. Geological Survey Open-File Report 03--233, - 44 p.}, accessed June 27, 2017, at - \url{https://pubs.usgs.gov/of/2003/ofr03-233/}. - -\bibitem[{Hsieh and Freckleton(1993)}]{hsieh1993hfb} -Hsieh, P.A., and Freckleton, J.R., 1993, Documentation of a computer program to - simulate horizontal-flow barriers using the U.S. Geological Survey's modular - three-dimensional finite-difference ground-water flow model: {U.S. Geological - Survey Open-File Report 92--477, 32 p.}, accessed June 27, 2017, at - \url{https://pubs.er.usgs.gov/publication/ofr92477}. - -\bibitem[{Hughes and others(2012)Hughes, Langevin, Chartier, and - White}]{hughes2012documentation} -Hughes, J.D., Langevin, C.D., Chartier, K.L., and White, J.T., 2012, - Documentation of the Surface-Water Routing (SWR1) Process for modeling - surface-water flow with the U.S. Geological Survey modular groundwater model - (MODFLOW-2005): {U.S. Geological Survey Techniques and Methods, book 6, chap. - A40 (Version 1.0), 113 p.}, accessed June 27, 2017, at - \url{https://pubs.usgs.gov/tm/6a40/}. - -\bibitem[{Hughes and others(2017)Hughes, Langevin, and - Banta}]{modflow6framework} -Hughes, J.D., Langevin, C.D., and Banta, E.R., 2017, Documentation for the - MODFLOW 6 framework: {U.S. Geological Survey Techniques and Methods, book 6, - chap. A57, 36 p.}, \url{https://doi.org/10.3133/tm6A57}. - -\bibitem[{Hughes and others(2022{\natexlab{a}})Hughes, Russcher, Langevin, - Morway, and McDonald}]{modflow6api} -Hughes, J.D., Russcher, M.J., Langevin, C.D., Morway, E.D., and McDonald, R.R., - 2022{\natexlab{a}}, The {MODFLOW Application Programming Interface} for - simulation control and software interoperability: Environmental Modelling \& - Software, v. 148, article 105257, - \url{https://doi.org/10.1016/j.envsoft.2021.105257}. - -\bibitem[{Hughes and others(2022{\natexlab{b}})Hughes, Leake, Galloway, and - White}]{modflow6csub} -Hughes, J.D., Leake, S.A., Galloway, D.L., and White, J.T., 2022{\natexlab{b}}, - Documentation for the Skeletal Storage, Compaction, and Subsidence (CSUB) - Package of MODFLOW 6: {U.S. Geological Survey Techniques and Methods, book 6, - chap. A62, 57 p.}, \url{https://doi.org/10.3133/tm6A62}. - -\bibitem[{Konikow and others(2009)Konikow, Hornberger, Halford, and - Hanson}]{konikow2009} -Konikow, L.F., Hornberger, G.Z., Halford, K.J., and Hanson, R.T., 2009, Revised - multi-node well (MNW2) package for MODFLOW ground-water flow model: {U.S. - Geological Survey Techniques and Methods, book 6, chap. A30, 67 p.}, accessed - June 27, 2017, at \url{https://pubs.usgs.gov/tm/tm6a30/}. - -\bibitem[{Langevin and others(2008)Langevin, Thorne~Jr, Dausman, Sukop, and - Guo}]{langevin2008seawat} -Langevin, C.D., Thorne~Jr, D.T., Dausman, A.M., Sukop, M.C., and Guo, W., 2008, - {SEAWAT} Version 4---A computer program for simulation of multi-species - solute and heat transport: {U.S. Geological Survey Techniques and Methods, - book 6, chap. A22, 39 p.}, accessed June 27, 2017, at - \url{https://pubs.er.usgs.gov/publication/tm6A22}. - -\bibitem[{Langevin and others(2017)Langevin, Hughes, Provost, Banta, Niswonger, - and Panday}]{modflow6gwf} -Langevin, C.D., Hughes, J.D., Provost, A.M., Banta, E.R., Niswonger, R.G., and - Panday, S., 2017, Documentation for the MODFLOW 6 Groundwater Flow (GWF) - Model: {U.S. Geological Survey Techniques and Methods, book 6, chap. A55, 197 - p.}, \url{https://doi.org/10.3133/tm6A55}. - -\bibitem[{Langevin and others(2020)Langevin, Panday, and - Provost}]{langevin2020hydraulic} -Langevin, C.D., Panday, S., and Provost, A.M., 2020, Hydraulic-head formulation - for density-dependent flow and transport: Groundwater, v.~58, no.~3, - p.~349--362. - -\bibitem[{Langevin and others(2022)Langevin, Provost, Panday, and - Hughes}]{modflow6gwt} -Langevin, C.D., Provost, A.M., Panday, S., and Hughes, J.D., 2022, - Documentation for the MODFLOW 6 Groundwater Transport (GWT) Model: {U.S. - Geological Survey Techniques and Methods, book 6, chap. A61, 56 p.}, - \url{https://doi.org/10.3133/tm6A61}. - -\bibitem[{Leake and Galloway(2007)}]{leake2007modflow} -Leake, S.A., and Galloway, D.L., 2007, MODFLOW Ground-water model---User guide - to the Subsidence and Aquifer-System Compaction Package (SUB-WT) for - Water-Table Aquifers: {U.S. Geological Survey Techniques and Methods, book 6, - chap. A23, 42 p.}, accessed June 27, 2017, at - \url{https://pubs.er.usgs.gov/publication/tm6A23}. - -\bibitem[{Leake and Lilly(1997)}]{leake1997documentation} -Leake, S.A., and Lilly, M.R., 1997, Documentation of computer program (FHB1) - for assignment of transient specified-flow and specified-head boundaries in - applications of the modular finite-diference ground-water flow model - (MODFLOW): {U.S. Geological Survey Open-File Report 97--571, 50 p.}, accessed - June 27, 2017, at \url{https://pubs.er.usgs.gov/publication/ofr97571}. - -\bibitem[{Maddock and others(2012)Maddock, Baird, Hanson, Schmid, and - Ajami}]{modflowripetpack} -Maddock, Thomas, I., Baird, K.J., Hanson, R.T., Schmid, W., and Ajami, H., - 2012, RIP-ET---A Riparian Evapotranspiration Package for MODFLOW-2005: {U.S. - Geological Survey Techniques and Methods, book 6, chap. A39, 76 p.}, accessed - June 27, 2017, at \url{https://pubs.usgs.gov/tm/tm6a39/}. - -\bibitem[{Merritt and Konikow(2000)}]{modflowlak3pack} -Merritt, M.L., and Konikow, L.F., 2000, Documentation of a computer program to - simulate lake-aquifer interaction using the MODFLOW ground-water flow model - and the MOC3D solute-transport model: {U.S. Geological Survey Water-Resources - Investigations Report 00--4167, 146 p.}, accessed June 27, 2017, at - \url{https://pubs.er.usgs.gov/publication/wri004167}. - -\bibitem[{Niswonger and Prudic(2005)}]{modflowsfr2pack} -Niswonger, R.G., and Prudic, D.E., 2005, Documentation of the - Streamflow-Routing (SFR2) Package to include unsaturated flow beneath - streams---A modification to SFR1: {U.S. Geological Survey Techniques and - Methods, book 6, chap. A13, 50 p.}, accessed June 27, 2017, at - \url{https://pubs.er.usgs.gov/publication/tm6A13}. - -\bibitem[{Niswonger and others(2006)Niswonger, Prudic, and Regan}]{UZF} -Niswonger, R.G., Prudic, D.E., and Regan, R.S., 2006, Documentation of the - Unsaturated-Zone Flow (UZF1) Package for modeling unsaturated flow between - the land surface and the water table with {MODFLOW}-2005: {U.S. Geological - Survey Techniques and Methods, book 6, chap. A19, 62 p.}, accessed June 27, - 2017, at \url{https://pubs.usgs.gov/tm/2006/tm6a19/}. - -\bibitem[{Panday and others(2013)Panday, Langevin, Niswonger, Ibaraki, and - Hughes}]{modflowusg} -Panday, S., Langevin, C.D., Niswonger, R.G., Ibaraki, M., and Hughes, J.D., - 2013, MODFLOW-USG version 1---An unstructured grid version of MODFLOW for - simulating groundwater flow and tightly coupled processes using a control - volume finite-difference formulation: {U.S. Geological Survey Techniques and - Methods, book 6, chap. A45, 66 p.}, accessed June 27, 2017, at - \url{https://pubs.usgs.gov/tm/06/a45/}. - -\bibitem[{Provost and others(2017)Provost, Langevin, and Hughes}]{modflow6xt3d} -Provost, A.M., Langevin, C.D., and Hughes, J.D., 2017, Documentation for the - ``XT3D'' Option in the Node Property Flow (NPF) Package of MODFLOW 6: {U.S. - Geological Survey Techniques and Methods, book 6, chap. A56, 46 p.}, - \url{https://doi.org/10.3133/tm6A56}. - -\bibitem[{Prudic(1989)}]{prudic1989str} -Prudic, D.E., 1989, Documentation of a computer program to simulate - stream-aquifer relations using a modular, finite-difference, ground-water - flow model: {U.S. Geological Survey Open-File Report 88--729, 113 p.}, - accessed June 27, 2017, at - \url{https://pubs.er.usgs.gov/publication/ofr88729}. - -\bibitem[{Prudic and others(2004)Prudic, Konikow, and Banta}]{modflowsfr1pack} -Prudic, D.E., Konikow, L.F., and Banta, E.R., 2004, A New Streamflow-Routing - (SFR1) Package to simulate stream-aquifer interaction with MODFLOW-2000: - {U.S. Geological Survey Open File Report 2004--1042, 104 p.}, accessed June - 27, 2017, at \url{https://pubs.er.usgs.gov/publication/ofr20041042}. - -\bibitem[{Voss(1984)}]{Voss1984sutra} -Voss, C.I., 1984, SUTRA---A finite-element simulation model for - saturated-unsaturated fluid-density-dependent ground-water flow with energy - transport or chemically-reactive single-species solute transport: {U.S. - Geological Survey Water-Resources Investigations Report 84--4369, 409 p.} - -\bibitem[{Zheng(2010)}]{zheng2010supplemental} -Zheng, C., 2010, MT3DMS v5.3, Supplemental User's Guide: {Technical Report - Prepared for the U.S. Army Corps of Engineers, 51 p.} - -\bibitem[{Zheng and others(2001)Zheng, Hill, and Hsieh}]{zheng2001modflow} -Zheng, C., Hill, M.C., and Hsieh, P.A., 2001, MODFLOW-2000, the U.S. Geological - Survey Modular Ground-Water Model---User guide to the LMT6 package, the - linkage with MT3DMS for multi-species mass transport modeling: {U.S. - Geological Survey Open-File Report 01--82, 43 p.}, accessed June 27, 2017, at - \url{https://pubs.er.usgs.gov/publication/ofr0182}. - -\end{thebibliography} diff --git a/doc/mf6io/mf6ivar/dfn/gwf-lak.dfn b/doc/mf6io/mf6ivar/dfn/gwf-lak.dfn index 9173aaec300..d49a5382f86 100644 --- a/doc/mf6io/mf6ivar/dfn/gwf-lak.dfn +++ b/doc/mf6io/mf6ivar/dfn/gwf-lak.dfn @@ -291,7 +291,7 @@ type double precision reader urword optional true longname time conversion factor -description value that is used in converting outlet flow terms that use Manning's equation or gravitational acceleration to consistent time units. TIME\_CONVERSION should be set to 1.0, 60.0, 3,600.0, 86,400.0, and 31,557,600.0 when using time units (TIME\_UNITS) of seconds, minutes, hours, days, or years in the simulation, respectively. CONVTIME does not need to be specified if no lake outlets are specified or TIME\_UNITS are seconds. +description real value that is used to convert user-specified Manning's roughness coefficients or gravitational acceleration used to calculate outlet flows from seconds to model time units. TIME\_CONVERSION should be set to 1.0, 60.0, 3,600.0, 86,400.0, and 31,557,600.0 when using time units (TIME\_UNITS) of seconds, minutes, hours, days, or years in the simulation, respectively. CONVTIME does not need to be specified if no lake outlets are specified or TIME\_UNITS are seconds. block options name length_conversion @@ -299,7 +299,7 @@ type double precision reader urword optional true longname length conversion factor -description real value that is used in converting outlet flow terms that use Manning's equation or gravitational acceleration to consistent length units. LENGTH\_CONVERSION should be set to 3.28081, 1.0, and 100.0 when using length units (LENGTH\_UNITS) of feet, meters, or centimeters in the simulation, respectively. LENGTH\_CONVERSION does not need to be specified if no lake outlets are specified or LENGTH\_UNITS are meters. +description real value that is used to convert outlet user-specified Manning's roughness coefficients or gravitational acceleration used to calculate outlet flows from meters to model length units. LENGTH\_CONVERSION should be set to 3.28081, 1.0, and 100.0 when using length units (LENGTH\_UNITS) of feet, meters, or centimeters in the simulation, respectively. LENGTH\_CONVERSION does not need to be specified if no lake outlets are specified or LENGTH\_UNITS are meters. # --------------------- gwf lak dimensions --------------------- diff --git a/doc/mf6io/mf6ivar/dfn/gwf-sfr.dfn b/doc/mf6io/mf6ivar/dfn/gwf-sfr.dfn index bb6e56e3f21..4c8efe3f9a9 100644 --- a/doc/mf6io/mf6ivar/dfn/gwf-sfr.dfn +++ b/doc/mf6io/mf6ivar/dfn/gwf-sfr.dfn @@ -283,7 +283,7 @@ type integer reader urword optional true longname SFR picard iterations -description value that defines the maximum number of Streamflow Routing picard iterations allowed when solving for reach stages and flows as part of the GWF formulate step. Picard iterations are used to minimize differences in SFR package results between subsequent GWF picard (non-linear) iterations as a result of non-optimal reach numbering. If reaches are numbered in order, from upstream to downstream, MAXIMUM\_PICARD\_ITERATIONS can be set to 1 to reduce model run time. By default, MAXIMUM\_PICARD\_ITERATIONS is equal to 100. +description integer value that defines the maximum number of Streamflow Routing picard iterations allowed when solving for reach stages and flows as part of the GWF formulate step. Picard iterations are used to minimize differences in SFR package results between subsequent GWF picard (non-linear) iterations as a result of non-optimal reach numbering. If reaches are numbered in order, from upstream to downstream, MAXIMUM\_PICARD\_ITERATIONS can be set to 1 to reduce model run time. By default, MAXIMUM\_PICARD\_ITERATIONS is equal to 100. block options name maximum_iterations @@ -291,7 +291,7 @@ type integer reader urword optional true longname SFR Newton-Raphson iterations -description value that defines the maximum number of Streamflow Routing Newton-Raphson iterations allowed for a reach. By default, MAXIMUM\_ITERATIONS is equal to 100. +description integer value that defines the maximum number of Streamflow Routing Newton-Raphson iterations allowed for a reach. By default, MAXIMUM\_ITERATIONS is equal to 100. block options name maximum_depth_change @@ -299,15 +299,32 @@ type double precision reader urword optional true longname depth closure tolerance -description value that defines the depth closure tolerance. By default, DMAXCHG is equal to $1 \times 10^{-5}$. +description real value that defines the depth closure tolerance. By default, DMAXCHG is equal to $1 \times 10^{-5}$. block options name unit_conversion type double precision reader urword optional true +deprecated 6.5.0 longname conversion factor -description value (or conversion factor) that is used in calculating stream depth for stream reach. A constant of 1.486 is used for flow units of cubic feet per second, and a constant of 1.0 is used for units of cubic meters per second. The constant must be multiplied by 86,400 when using time units of days in the simulation. +description real value that is used to convert user-specified Manning's roughness coefficients from seconds per meters$^{1/3}$ to model length and time units. A constant of 1.486 is used for flow units of cubic feet per second, and a constant of 1.0 is used for units of cubic meters per second. The constant must be multiplied by 86,400 when using time units of days in the simulation. + +block options +name length_conversion +type double precision +reader urword +optional true +longname length conversion factor +description real value that is used to convert user-specified Manning's roughness coefficients from meters to model length units. LENGTH\_CONVERSION should be set to 3.28081, 1.0, and 100.0 when using length units (LENGTH\_UNITS) of feet, meters, or centimeters in the simulation, respectively. LENGTH\_CONVERSION does not need to be specified if LENGTH\_UNITS are meters. + +block options +name time_conversion +type double precision +reader urword +optional true +longname time conversion factor +description real value that is used to convert user-specified Manning's roughness coefficients from seconds to model time units. TIME\_CONVERSION should be set to 1.0, 60.0, 3,600.0, 86,400.0, and 31,557,600.0 when using time units (TIME\_UNITS) of seconds, minutes, hours, days, or years in the simulation, respectively. TIME\_CONVERSION does not need to be specified if TIME\_UNITS are seconds. # --------------------- gwf sfr dimensions --------------------- diff --git a/doc/mf6io/mf6ivar/md/mf6ivar.md b/doc/mf6io/mf6ivar/md/mf6ivar.md index 181ef6dbed0..0c14419b005 100644 --- a/doc/mf6io/mf6ivar/md/mf6ivar.md +++ b/doc/mf6io/mf6ivar/md/mf6ivar.md @@ -189,7 +189,7 @@ | GWF | NPF | OPTIONS | SAVE_FLOWS | KEYWORD | keyword to indicate that budget flow terms will be written to the file specified with ``BUDGET SAVE FILE'' in Output Control. | | GWF | NPF | OPTIONS | PRINT_FLOWS | KEYWORD | keyword to indicate that calculated flows between cells will be printed to the listing file for every stress period time step in which ``BUDGET PRINT'' is specified in Output Control. If there is no Output Control option and ``PRINT\_FLOWS'' is specified, then flow rates are printed for the last time step of each stress period. This option can produce extremely large list files because all cell-by-cell flows are printed. It should only be used with the NPF Package for models that have a small number of cells. | | GWF | NPF | OPTIONS | ALTERNATIVE_CELL_AVERAGING | STRING | is a text keyword to indicate that an alternative method will be used for calculating the conductance for horizontal cell connections. The text value for ALTERNATIVE\_CELL\_AVERAGING can be ``LOGARITHMIC'', ``AMT-LMK'', or ``AMT-HMK''. ``AMT-LMK'' signifies that the conductance will be calculated using arithmetic-mean thickness and logarithmic-mean hydraulic conductivity. ``AMT-HMK'' signifies that the conductance will be calculated using arithmetic-mean thickness and harmonic-mean hydraulic conductivity. If the user does not specify a value for ALTERNATIVE\_CELL\_AVERAGING, then the harmonic-mean method will be used. This option cannot be used if the XT3D option is invoked. | -| GWF | NPF | OPTIONS | THICKSTRT | KEYWORD | indicates that cells having a negative ICELLTYPE are confined, and their cell thickness for conductance calculations will be computed as STRT-BOT rather than TOP-BOT. | +| GWF | NPF | OPTIONS | THICKSTRT | KEYWORD | indicates that cells having a negative ICELLTYPE are confined, and their cell thickness for conductance calculations will be computed as STRT-BOT rather than TOP-BOT. This option should be used with caution as it only affects conductance calculations in the NPF Package. | | GWF | NPF | OPTIONS | VARIABLECV | KEYWORD | keyword to indicate that the vertical conductance will be calculated using the saturated thickness and properties of the overlying cell and the thickness and properties of the underlying cell. If the DEWATERED keyword is also specified, then the vertical conductance is calculated using only the saturated thickness and properties of the overlying cell if the head in the underlying cell is below its top. If these keywords are not specified, then the default condition is to calculate the vertical conductance at the start of the simulation using the initial head and the cell properties. The vertical conductance remains constant for the entire simulation. | | GWF | NPF | OPTIONS | DEWATERED | KEYWORD | If the DEWATERED keyword is specified, then the vertical conductance is calculated using only the saturated thickness and properties of the overlying cell if the head in the underlying cell is below its top. | | GWF | NPF | OPTIONS | PERCHED | KEYWORD | keyword to indicate that when a cell is overlying a dewatered convertible cell, the head difference used in Darcy's Law is equal to the head in the overlying cell minus the bottom elevation of the overlying cell. If not specified, then the default is to use the head difference between the two cells. | @@ -211,7 +211,7 @@ | GWF | NPF | OPTIONS | DEV_MODFLOWNWT_UPSTREAM_WEIGHTING | KEYWORD | use MODFLOW-NWT approach for upstream weighting | | GWF | NPF | OPTIONS | DEV_MINIMUM_SATURATED_THICKNESS | DOUBLE PRECISION | set minimum allowed saturated thickness | | GWF | NPF | OPTIONS | DEV_OMEGA | DOUBLE PRECISION | set saturation omega value | -| GWF | NPF | GRIDDATA | ICELLTYPE | INTEGER (NODES) | flag for each cell that specifies how saturated thickness is treated. 0 means saturated thickness is held constant; $>$0 means saturated thickness varies with computed head when head is below the cell top; $<$0 means saturated thickness varies with computed head unless the THICKSTRT option is in effect. When THICKSTRT is in effect, a negative value of icelltype indicates that saturated thickness will be computed as STRT-BOT and held constant. | +| GWF | NPF | GRIDDATA | ICELLTYPE | INTEGER (NODES) | flag for each cell that specifies how saturated thickness is treated. 0 means saturated thickness is held constant; $>$0 means saturated thickness varies with computed head when head is below the cell top; $<$0 means saturated thickness varies with computed head unless the THICKSTRT option is in effect. When THICKSTRT is in effect, a negative value for ICELLTYPE indicates that the saturated thickness value used in conductance calculations in the NPF Package will be computed as STRT-BOT and held constant. If the THICKSTRT option is not in effect, then negative values provided by the user for ICELLTYPE are automatically reassigned by the program to a value of one. | | GWF | NPF | GRIDDATA | K | DOUBLE PRECISION (NODES) | is the hydraulic conductivity. For the common case in which the user would like to specify the horizontal hydraulic conductivity and the vertical hydraulic conductivity, then K should be assigned as the horizontal hydraulic conductivity, K33 should be assigned as the vertical hydraulic conductivity, and K22 and the three rotation angles should not be specified. When more sophisticated anisotropy is required, then K corresponds to the K11 hydraulic conductivity axis. All included cells (IDOMAIN $>$ 0) must have a K value greater than zero. | | GWF | NPF | GRIDDATA | K22 | DOUBLE PRECISION (NODES) | is the hydraulic conductivity of the second ellipsoid axis (or the ratio of K22/K if the K22OVERK option is specified); for an unrotated case this is the hydraulic conductivity in the y direction. If K22 is not included in the GRIDDATA block, then K22 is set equal to K. For a regular MODFLOW grid (DIS Package is used) in which no rotation angles are specified, K22 is the hydraulic conductivity along columns in the y direction. For an unstructured DISU grid, the user must assign principal x and y axes and provide the angle for each cell face relative to the assigned x direction. All included cells (IDOMAIN $>$ 0) must have a K22 value greater than zero. | | GWF | NPF | GRIDDATA | K33 | DOUBLE PRECISION (NODES) | is the hydraulic conductivity of the third ellipsoid axis (or the ratio of K33/K if the K33OVERK option is specified); for an unrotated case, this is the vertical hydraulic conductivity. When anisotropy is applied, K33 corresponds to the K33 tensor component. All included cells (IDOMAIN $>$ 0) must have a K33 value greater than zero. | @@ -567,10 +567,12 @@ | GWF | SFR | OPTIONS | OBS6 | KEYWORD | keyword to specify that record corresponds to an observations file. | | GWF | SFR | OPTIONS | OBS6_FILENAME | STRING | name of input file to define observations for the SFR package. See the ``Observation utility'' section for instructions for preparing observation input files. Tables \ref{table:gwf-obstypetable} and \ref{table:gwt-obstypetable} lists observation type(s) supported by the SFR package. | | GWF | SFR | OPTIONS | MOVER | KEYWORD | keyword to indicate that this instance of the SFR Package can be used with the Water Mover (MVR) Package. When the MOVER option is specified, additional memory is allocated within the package to store the available, provided, and received water. | -| GWF | SFR | OPTIONS | MAXIMUM_PICARD_ITERATIONS | INTEGER | value that defines the maximum number of Streamflow Routing picard iterations allowed when solving for reach stages and flows as part of the GWF formulate step. Picard iterations are used to minimize differences in SFR package results between subsequent GWF picard (non-linear) iterations as a result of non-optimal reach numbering. If reaches are numbered in order, from upstream to downstream, MAXIMUM\_PICARD\_ITERATIONS can be set to 1 to reduce model run time. By default, MAXIMUM\_PICARD\_ITERATIONS is equal to 100. | -| GWF | SFR | OPTIONS | MAXIMUM_ITERATIONS | INTEGER | value that defines the maximum number of Streamflow Routing Newton-Raphson iterations allowed for a reach. By default, MAXIMUM\_ITERATIONS is equal to 100. | -| GWF | SFR | OPTIONS | MAXIMUM_DEPTH_CHANGE | DOUBLE PRECISION | value that defines the depth closure tolerance. By default, DMAXCHG is equal to $1 \times 10^{-5}$. | -| GWF | SFR | OPTIONS | UNIT_CONVERSION | DOUBLE PRECISION | value (or conversion factor) that is used in calculating stream depth for stream reach. A constant of 1.486 is used for flow units of cubic feet per second, and a constant of 1.0 is used for units of cubic meters per second. The constant must be multiplied by 86,400 when using time units of days in the simulation. | +| GWF | SFR | OPTIONS | MAXIMUM_PICARD_ITERATIONS | INTEGER | integer value that defines the maximum number of Streamflow Routing picard iterations allowed when solving for reach stages and flows as part of the GWF formulate step. Picard iterations are used to minimize differences in SFR package results between subsequent GWF picard (non-linear) iterations as a result of non-optimal reach numbering. If reaches are numbered in order, from upstream to downstream, MAXIMUM\_PICARD\_ITERATIONS can be set to 1 to reduce model run time. By default, MAXIMUM\_PICARD\_ITERATIONS is equal to 100. | +| GWF | SFR | OPTIONS | MAXIMUM_ITERATIONS | INTEGER | integer value that defines the maximum number of Streamflow Routing Newton-Raphson iterations allowed for a reach. By default, MAXIMUM\_ITERATIONS is equal to 100. | +| GWF | SFR | OPTIONS | MAXIMUM_DEPTH_CHANGE | DOUBLE PRECISION | real value that defines the depth closure tolerance. By default, DMAXCHG is equal to $1 \times 10^{-5}$. | +| GWF | SFR | OPTIONS | UNIT_CONVERSION | DOUBLE PRECISION | real value that is used to convert user-specified Manning's roughness coefficients from seconds per meters$^{1/3}$ to model length and time units. A constant of 1.486 is used for flow units of cubic feet per second, and a constant of 1.0 is used for units of cubic meters per second. The constant must be multiplied by 86,400 when using time units of days in the simulation. | +| GWF | SFR | OPTIONS | LENGTH_CONVERSION | DOUBLE PRECISION | real value that is used to convert user-specified Manning's roughness coefficients from meters to model length units. LENGTH\_CONVERSION should be set to 3.28081, 1.0, and 100.0 when using length units (LENGTH\_UNITS) of feet, meters, or centimeters in the simulation, respectively. LENGTH\_CONVERSION does not need to be specified if LENGTH\_UNITS are meters. | +| GWF | SFR | OPTIONS | TIME_CONVERSION | DOUBLE PRECISION | real value that is used to convert user-specified Manning's roughness coefficients from seconds to model time units. TIME\_CONVERSION should be set to 1.0, 60.0, 3,600.0, 86,400.0, and 31,557,600.0 when using time units (TIME\_UNITS) of seconds, minutes, hours, days, or years in the simulation, respectively. TIME\_CONVERSION does not need to be specified if TIME\_UNITS are seconds. | | GWF | SFR | DIMENSIONS | NREACHES | INTEGER | integer value specifying the number of stream reaches. There must be NREACHES entries in the PACKAGEDATA block. | | GWF | SFR | PACKAGEDATA | RNO | INTEGER | integer value that defines the reach number associated with the specified PACKAGEDATA data on the line. RNO must be greater than zero and less than or equal to NREACHES. Reach information must be specified for every reach or the program will terminate with an error. The program will also terminate with an error if information for a reach is specified more than once. | | GWF | SFR | PACKAGEDATA | CELLID | INTEGER (NCELLDIM) | The keyword `NONE' must be specified for reaches that are not connected to an underlying GWF cell. The keyword `NONE' is used for reaches that are in cells that have IDOMAIN values less than one or are in areas not covered by the GWF model grid. Reach-aquifer flow is not calculated if the keyword `NONE' is specified. | @@ -639,8 +641,8 @@ | GWF | LAK | OPTIONS | OBS6_FILENAME | STRING | name of input file to define observations for the LAK package. See the ``Observation utility'' section for instructions for preparing observation input files. Tables \ref{table:gwf-obstypetable} and \ref{table:gwt-obstypetable} lists observation type(s) supported by the LAK package. | | GWF | LAK | OPTIONS | MOVER | KEYWORD | keyword to indicate that this instance of the LAK Package can be used with the Water Mover (MVR) Package. When the MOVER option is specified, additional memory is allocated within the package to store the available, provided, and received water. | | GWF | LAK | OPTIONS | SURFDEP | DOUBLE PRECISION | real value that defines the surface depression depth for VERTICAL lake-GWF connections. If specified, SURFDEP must be greater than or equal to zero. If SURFDEP is not specified, a default value of zero is used for all vertical lake-GWF connections. | -| GWF | LAK | OPTIONS | TIME_CONVERSION | DOUBLE PRECISION | value that is used in converting outlet flow terms that use Manning's equation or gravitational acceleration to consistent time units. TIME\_CONVERSION should be set to 1.0, 60.0, 3,600.0, 86,400.0, and 31,557,600.0 when using time units (TIME\_UNITS) of seconds, minutes, hours, days, or years in the simulation, respectively. CONVTIME does not need to be specified if no lake outlets are specified or TIME\_UNITS are seconds. | -| GWF | LAK | OPTIONS | LENGTH_CONVERSION | DOUBLE PRECISION | real value that is used in converting outlet flow terms that use Manning's equation or gravitational acceleration to consistent length units. LENGTH\_CONVERSION should be set to 3.28081, 1.0, and 100.0 when using length units (LENGTH\_UNITS) of feet, meters, or centimeters in the simulation, respectively. LENGTH\_CONVERSION does not need to be specified if no lake outlets are specified or LENGTH\_UNITS are meters. | +| GWF | LAK | OPTIONS | TIME_CONVERSION | DOUBLE PRECISION | real value that is used to convert user-specified Manning's roughness coefficients or gravitational acceleration used to calculate outlet flows from seconds to model time units. TIME\_CONVERSION should be set to 1.0, 60.0, 3,600.0, 86,400.0, and 31,557,600.0 when using time units (TIME\_UNITS) of seconds, minutes, hours, days, or years in the simulation, respectively. CONVTIME does not need to be specified if no lake outlets are specified or TIME\_UNITS are seconds. | +| GWF | LAK | OPTIONS | LENGTH_CONVERSION | DOUBLE PRECISION | real value that is used to convert outlet user-specified Manning's roughness coefficients or gravitational acceleration used to calculate outlet flows from meters to model length units. LENGTH\_CONVERSION should be set to 3.28081, 1.0, and 100.0 when using length units (LENGTH\_UNITS) of feet, meters, or centimeters in the simulation, respectively. LENGTH\_CONVERSION does not need to be specified if no lake outlets are specified or LENGTH\_UNITS are meters. | | GWF | LAK | DIMENSIONS | NLAKES | INTEGER | value specifying the number of lakes that will be simulated for all stress periods. | | GWF | LAK | DIMENSIONS | NOUTLETS | INTEGER | value specifying the number of outlets that will be simulated for all stress periods. If NOUTLETS is not specified, a default value of zero is used. | | GWF | LAK | DIMENSIONS | NTABLES | INTEGER | value specifying the number of lakes tables that will be used to define the lake stage, volume relation, and surface area. If NTABLES is not specified, a default value of zero is used. | diff --git a/doc/mf6io/mf6ivar/tex/gwf-lak-desc.tex b/doc/mf6io/mf6ivar/tex/gwf-lak-desc.tex index 984d270a616..786477976dd 100644 --- a/doc/mf6io/mf6ivar/tex/gwf-lak-desc.tex +++ b/doc/mf6io/mf6ivar/tex/gwf-lak-desc.tex @@ -47,9 +47,9 @@ \item \texttt{surfdep}---real value that defines the surface depression depth for VERTICAL lake-GWF connections. If specified, SURFDEP must be greater than or equal to zero. If SURFDEP is not specified, a default value of zero is used for all vertical lake-GWF connections. -\item \texttt{time\_conversion}---value that is used in converting outlet flow terms that use Manning's equation or gravitational acceleration to consistent time units. TIME\_CONVERSION should be set to 1.0, 60.0, 3,600.0, 86,400.0, and 31,557,600.0 when using time units (TIME\_UNITS) of seconds, minutes, hours, days, or years in the simulation, respectively. CONVTIME does not need to be specified if no lake outlets are specified or TIME\_UNITS are seconds. +\item \texttt{time\_conversion}---real value that is used to convert user-specified Manning's roughness coefficients or gravitational acceleration used to calculate outlet flows from seconds to model time units. TIME\_CONVERSION should be set to 1.0, 60.0, 3,600.0, 86,400.0, and 31,557,600.0 when using time units (TIME\_UNITS) of seconds, minutes, hours, days, or years in the simulation, respectively. CONVTIME does not need to be specified if no lake outlets are specified or TIME\_UNITS are seconds. -\item \texttt{length\_conversion}---real value that is used in converting outlet flow terms that use Manning's equation or gravitational acceleration to consistent length units. LENGTH\_CONVERSION should be set to 3.28081, 1.0, and 100.0 when using length units (LENGTH\_UNITS) of feet, meters, or centimeters in the simulation, respectively. LENGTH\_CONVERSION does not need to be specified if no lake outlets are specified or LENGTH\_UNITS are meters. +\item \texttt{length\_conversion}---real value that is used to convert outlet user-specified Manning's roughness coefficients or gravitational acceleration used to calculate outlet flows from meters to model length units. LENGTH\_CONVERSION should be set to 3.28081, 1.0, and 100.0 when using length units (LENGTH\_UNITS) of feet, meters, or centimeters in the simulation, respectively. LENGTH\_CONVERSION does not need to be specified if no lake outlets are specified or LENGTH\_UNITS are meters. \end{description} \item \textbf{Block: DIMENSIONS} diff --git a/doc/mf6io/mf6ivar/tex/gwf-sfr-desc.tex b/doc/mf6io/mf6ivar/tex/gwf-sfr-desc.tex index 3154b605865..aae2ba58c49 100644 --- a/doc/mf6io/mf6ivar/tex/gwf-sfr-desc.tex +++ b/doc/mf6io/mf6ivar/tex/gwf-sfr-desc.tex @@ -45,13 +45,15 @@ \item \texttt{MOVER}---keyword to indicate that this instance of the SFR Package can be used with the Water Mover (MVR) Package. When the MOVER option is specified, additional memory is allocated within the package to store the available, provided, and received water. -\item \texttt{maximum\_picard\_iterations}---value that defines the maximum number of Streamflow Routing picard iterations allowed when solving for reach stages and flows as part of the GWF formulate step. Picard iterations are used to minimize differences in SFR package results between subsequent GWF picard (non-linear) iterations as a result of non-optimal reach numbering. If reaches are numbered in order, from upstream to downstream, MAXIMUM\_PICARD\_ITERATIONS can be set to 1 to reduce model run time. By default, MAXIMUM\_PICARD\_ITERATIONS is equal to 100. +\item \texttt{maximum\_picard\_iterations}---integer value that defines the maximum number of Streamflow Routing picard iterations allowed when solving for reach stages and flows as part of the GWF formulate step. Picard iterations are used to minimize differences in SFR package results between subsequent GWF picard (non-linear) iterations as a result of non-optimal reach numbering. If reaches are numbered in order, from upstream to downstream, MAXIMUM\_PICARD\_ITERATIONS can be set to 1 to reduce model run time. By default, MAXIMUM\_PICARD\_ITERATIONS is equal to 100. -\item \texttt{maximum\_iterations}---value that defines the maximum number of Streamflow Routing Newton-Raphson iterations allowed for a reach. By default, MAXIMUM\_ITERATIONS is equal to 100. +\item \texttt{maximum\_iterations}---integer value that defines the maximum number of Streamflow Routing Newton-Raphson iterations allowed for a reach. By default, MAXIMUM\_ITERATIONS is equal to 100. -\item \texttt{maximum\_depth\_change}---value that defines the depth closure tolerance. By default, DMAXCHG is equal to $1 \times 10^{-5}$. +\item \texttt{maximum\_depth\_change}---real value that defines the depth closure tolerance. By default, DMAXCHG is equal to $1 \times 10^{-5}$. -\item \texttt{unit\_conversion}---value (or conversion factor) that is used in calculating stream depth for stream reach. A constant of 1.486 is used for flow units of cubic feet per second, and a constant of 1.0 is used for units of cubic meters per second. The constant must be multiplied by 86,400 when using time units of days in the simulation. +\item \texttt{length\_conversion}---real value that is used to convert user-specified Manning's roughness coefficients from meters to model length units. LENGTH\_CONVERSION should be set to 3.28081, 1.0, and 100.0 when using length units (LENGTH\_UNITS) of feet, meters, or centimeters in the simulation, respectively. LENGTH\_CONVERSION does not need to be specified if LENGTH\_UNITS are meters. + +\item \texttt{time\_conversion}---real value that is used to convert user-specified Manning's roughness coefficients from seconds to model time units. TIME\_CONVERSION should be set to 1.0, 60.0, 3,600.0, 86,400.0, and 31,557,600.0 when using time units (TIME\_UNITS) of seconds, minutes, hours, days, or years in the simulation, respectively. TIME\_CONVERSION does not need to be specified if TIME\_UNITS are seconds. \end{description} \item \textbf{Block: DIMENSIONS} diff --git a/doc/mf6io/mf6ivar/tex/gwf-sfr-options.dat b/doc/mf6io/mf6ivar/tex/gwf-sfr-options.dat index 213e3edd4cc..6859523e122 100644 --- a/doc/mf6io/mf6ivar/tex/gwf-sfr-options.dat +++ b/doc/mf6io/mf6ivar/tex/gwf-sfr-options.dat @@ -15,5 +15,6 @@ BEGIN OPTIONS [MAXIMUM_PICARD_ITERATIONS ] [MAXIMUM_ITERATIONS ] [MAXIMUM_DEPTH_CHANGE ] - [UNIT_CONVERSION ] + [LENGTH_CONVERSION ] + [TIME_CONVERSION ] END OPTIONS diff --git a/src/Model/GroundWaterFlow/gwf3sfr8.f90 b/src/Model/GroundWaterFlow/gwf3sfr8.f90 index b27964b286c..18e761a6d02 100644 --- a/src/Model/GroundWaterFlow/gwf3sfr8.f90 +++ b/src/Model/GroundWaterFlow/gwf3sfr8.f90 @@ -9,7 +9,8 @@ module SfrModule use KindModule, only: DP, I4B, LGP use ConstantsModule, only: LINELENGTH, LENBOUNDNAME, LENTIMESERIESNAME, & DZERO, DPREC, DEM30, DEM6, DEM5, DEM4, DEM2, & - DHALF, DP6, DTWOTHIRDS, DP7, DP9, DP99, DP999, & + DONETHIRD, DHALF, DP6, DTWOTHIRDS, DP7, & + DP9, DP99, DP999, & DONE, D1P1, DFIVETHIRDS, DTWO, DPI, DEIGHT, & DHUNDRED, DEP20, & NAMEDBOUNDFLAG, LENBOUNDNAME, LENFTYPE, & @@ -29,7 +30,7 @@ module SfrModule use InputOutputModule, only: extract_idnum_or_bndname use BaseDisModule, only: DisBaseType use SimModule, only: count_errors, store_error, store_error_unit, & - store_warning + store_warning, deprecation_warning use SimVariablesModule, only: errmsg, warnmsg use GwfSfrCrossSectionUtilsModule, only: get_saturated_topwidth, & get_wetted_topwidth, & @@ -74,6 +75,8 @@ module SfrModule integer(I4B), pointer :: ianynone => null() !< number of reaches with 'none' connection ! -- double precision real(DP), pointer :: unitconv => NULL() !< unit conversion factor (SI to model units) + real(DP), pointer :: lengthconv => NULL() !< length conversion factor (SI to model units) + real(DP), pointer :: timeconv => NULL() !< time conversion factor (SI to model units) real(DP), pointer :: dmaxchg => NULL() !< maximum depth change allowed real(DP), pointer :: deps => NULL() !< perturbation value ! -- integer vectors @@ -200,6 +203,7 @@ module SfrModule procedure, private :: sfr_calc_reach_depth procedure, private :: sfr_calc_xs_depth ! -- error checking + procedure, private :: sfr_check_conversion procedure, private :: sfr_check_reaches procedure, private :: sfr_check_connections procedure, private :: sfr_check_diversions @@ -292,6 +296,8 @@ subroutine sfr_allocate_scalars(this) call mem_allocate(this%bditems, 'BDITEMS', this%memoryPath) call mem_allocate(this%cbcauxitems, 'CBCAUXITEMS', this%memoryPath) call mem_allocate(this%unitconv, 'UNITCONV', this%memoryPath) + call mem_allocate(this%lengthconv, 'LENGTHCONV', this%memoryPath) + call mem_allocate(this%timeconv, 'TIMECONV', this%memoryPath) call mem_allocate(this%dmaxchg, 'DMAXCHG', this%memoryPath) call mem_allocate(this%deps, 'DEPS', this%memoryPath) call mem_allocate(this%nconn, 'NCONN', this%memoryPath) @@ -316,6 +322,8 @@ subroutine sfr_allocate_scalars(this) this%bditems = 8 this%cbcauxitems = 1 this%unitconv = DONE + this%lengthconv = DNODATA + this%timeconv = DNODATA this%dmaxchg = DEM5 this%deps = DP999 * this%dmaxchg this%nconn = 0 @@ -626,8 +634,10 @@ subroutine sfr_options(this, option, found) character(len=MAXCHARLEN) :: fname character(len=MAXCHARLEN) :: keyword ! -- formats - character(len=*), parameter :: fmtunitconv = & - &"(4x, 'UNIT CONVERSION VALUE (',g0,') SPECIFIED.')" + character(len=*), parameter :: fmttimeconv = & + &"(4x, 'TIME CONVERSION VALUE (',g0,') SPECIFIED.')" + character(len=*), parameter :: fmtlengthconv = & + &"(4x, 'LENGTH CONVERSION VALUE (',g0,') SPECIFIED.')" character(len=*), parameter :: fmtpicard = & &"(4x, 'MAXIMUM SFR PICARD ITERATION VALUE (',i0,') SPECIFIED.')" character(len=*), parameter :: fmtiter = & @@ -699,7 +709,20 @@ subroutine sfr_options(this, option, found) end if case ('UNIT_CONVERSION') this%unitconv = this%parser%GetDouble() - write (this%iout, fmtunitconv) this%unitconv + ! + ! -- create warning message + write (warnmsg, '(a)') & + 'SETTING UNIT_CONVERSION DIRECTLY' + ! + ! -- create deprecation warning + call deprecation_warning('OPTIONS', 'UNIT_CONVERSION', '6.5.0', & + warnmsg, this%parser%GetUnit()) + case ('LENGTH_CONVERSION') + this%lengthconv = this%parser%GetDouble() + write (this%iout, fmtlengthconv) this%lengthconv + case ('TIME_CONVERSION') + this%timeconv = this%parser%GetDouble() + write (this%iout, fmttimeconv) this%timeconv case ('MAXIMUM_PICARD_ITERATIONS') this%maxsfrpicard = this%parser%GetInteger() write (this%iout, fmtpicard) this%maxsfrpicard @@ -777,7 +800,10 @@ subroutine sfr_ar(this) this%nodelist(n) = this%igwfnode(n) end do ! - ! -- check the sfr data + ! -- check the sfr unit conversion data + call this%sfr_check_conversion() + ! + ! -- check the sfr reach data call this%sfr_check_reaches() ! -- check the connection data @@ -2684,6 +2710,8 @@ subroutine sfr_da(this) call mem_deallocate(this%bditems) call mem_deallocate(this%cbcauxitems) call mem_deallocate(this%unitconv) + call mem_deallocate(this%lengthconv) + call mem_deallocate(this%timeconv) call mem_deallocate(this%dmaxchg) call mem_deallocate(this%deps) call mem_deallocate(this%nconn) @@ -4258,6 +4286,46 @@ subroutine sfr_calc_xs_depth(this, n, qrch, d) return end subroutine sfr_calc_xs_depth + !> @brief Check unit conversion data + !! + !! Method to check unit conversion data for a SFR package. This method + !! also calculates unitconv that is used in the Manning's equation. + !! + !< + subroutine sfr_check_conversion(this) + ! -- dummy variables + class(SfrType) :: this !< SfrType object + ! -- local variables + ! -- formats + character(len=*), parameter :: fmtunitconv_error = & + &"('SFR (',a,') UNIT_CONVERSION SPECIFIED VALUE (',g0,') AND', & + &1x,'LENGTH_CONVERSION OR TIME_CONVERSION SPECIFIED.')" + character(len=*), parameter :: fmtunitconv = & + &"(1x,'SFR PACKAGE (',a,') CONVERSION DATA',& + &/4x,'UNIT CONVERSION VALUE (',g0,').',/)" + ! + ! -- check the reach data for simple errors + if (this%lengthconv /= DNODATA .or. this%timeconv /= DNODATA) then + if (this%unitconv /= DONE) then + write (errmsg, fmtunitconv_error) & + trim(adjustl(this%packName)), this%unitconv + call store_error(errmsg) + else + if (this%lengthconv /= DNODATA) then + this%unitconv = this%unitconv * this%lengthconv**DONETHIRD + end if + if (this%timeconv /= DNODATA) then + this%unitconv = this%unitconv * this%timeconv + end if + write (this%iout, fmtunitconv) & + trim(adjustl(this%packName)), this%unitconv + end if + end if + ! + ! -- return + return + end subroutine sfr_check_conversion + !> @brief Check reach data !! !! Method to check specified data for a SFR package. This method @@ -4307,6 +4375,8 @@ subroutine sfr_check_reaches(this) call this%inputtab%initialize_column(text, 12, alignment=TABCENTER) end if ! + ! -- + ! ! -- check the reach data for simple errors do n = 1, this%maxbound write (crch, '(i5)') n From d5fc153858d32cac8ff19ad11cdea2478d9b4a2f Mon Sep 17 00:00:00 2001 From: Mike Taves Date: Tue, 14 Mar 2023 02:02:04 +1300 Subject: [PATCH 041/123] fix(Python paths): use absolute paths for scripts and Python executable (#1162) --- .build_rtd_docs/conf.py | 2 +- autotest/build_exes.py | 2 +- autotest/build_mfio_tex.py | 3 ++- autotest/conftest.py | 2 +- autotest/update_flopy.py | 5 +++-- distribution/build_dist.py | 3 ++- distribution/build_docs.py | 7 ++++--- distribution/conftest.py | 4 ++-- distribution/update_version.py | 2 +- distribution/utils.py | 4 ++-- 10 files changed, 19 insertions(+), 15 deletions(-) diff --git a/.build_rtd_docs/conf.py b/.build_rtd_docs/conf.py index bb3cdd98936..bfd38623e25 100644 --- a/.build_rtd_docs/conf.py +++ b/.build_rtd_docs/conf.py @@ -59,7 +59,7 @@ # -- build the mf6io markdown files ----------------------------------------- print("Build the mf6io markdown files") pth = os.path.join("..", "doc", "mf6io", "mf6ivar") -args = ("python", "mf6ivar.py") +args = (sys.executable, "mf6ivar.py") # run the command proc = Popen(args, stdout=PIPE, stderr=PIPE, cwd=pth) stdout, stderr = proc.communicate() diff --git a/autotest/build_exes.py b/autotest/build_exes.py index f63c0401689..ede1d7a67e7 100644 --- a/autotest/build_exes.py +++ b/autotest/build_exes.py @@ -30,4 +30,4 @@ def test_meson_build(bin_path): "-p", "--path", help="path to bin directory", default=top_bin_path ) args = parser.parse_args() - test_meson_build(Path(args.path).resolve()) + test_meson_build(Path(args.path).expanduser().resolve()) diff --git a/autotest/build_mfio_tex.py b/autotest/build_mfio_tex.py index b95bd6a37c9..ab57f2da85d 100644 --- a/autotest/build_mfio_tex.py +++ b/autotest/build_mfio_tex.py @@ -1,5 +1,6 @@ import os import subprocess +import sys from contextlib import contextmanager # base name for mf6io LaTeX document @@ -60,7 +61,7 @@ def test_rebuild_from_dfn(): os.remove(fpth) # run python - argv = ["python", "mf6ivar.py"] + argv = [sys.executable, "mf6ivar.py"] buff, ierr = run_command(argv, pth) msg = f"\nERROR {ierr}: could not run {argv[0]} with {argv[1]}" assert ierr == 0, buff + msg diff --git a/autotest/conftest.py b/autotest/conftest.py index 78db66c4732..21fb8d1ba88 100644 --- a/autotest/conftest.py +++ b/autotest/conftest.py @@ -4,7 +4,7 @@ from modflow_devtools.executables import Executables, build_default_exe_dict pytest_plugins = ["modflow_devtools.fixtures"] -project_root_path = Path(__file__).parent.parent +project_root_path = Path(__file__).resolve().parent.parent def should_compare( diff --git a/autotest/update_flopy.py b/autotest/update_flopy.py index 60f9874e557..d7c2aedd3a2 100644 --- a/autotest/update_flopy.py +++ b/autotest/update_flopy.py @@ -3,6 +3,7 @@ import os import shutil import subprocess +import sys from pathlib import Path import flopy @@ -70,7 +71,7 @@ def test_create_packages(): # run createpackages.py script print(f"running...{fn}") - subprocess.check_output(["python", "createpackages.py"], cwd=pth) + subprocess.check_output([sys.executable, "createpackages.py"], cwd=pth) # reload flopy print("reloading flopy") @@ -124,7 +125,7 @@ def delete_files(files, pth, allow_failure=False, exclude=None): ) args = parser.parse_args() - path = Path(args.path).resolve() + path = Path(args.path).expanduser().resolve() print(f"Updating flopy packages from DFN files in: {path}") test_delete_mf6() diff --git a/distribution/build_dist.py b/distribution/build_dist.py index 87307667e1f..188aa8e63c5 100644 --- a/distribution/build_dist.py +++ b/distribution/build_dist.py @@ -2,6 +2,7 @@ import os import platform import shutil +import sys import textwrap from collections import namedtuple from os import PathLike, environ @@ -162,7 +163,7 @@ def build_examples(examples_repo_path: PathLike, overwrite: bool = False): ] for script in scripts: argv = [ - "python", + sys.executable, script, "--no_run", "--no_plot", diff --git a/distribution/build_docs.py b/distribution/build_docs.py index 0a52cd5b1fb..abdcb8bd2be 100644 --- a/distribution/build_docs.py +++ b/distribution/build_docs.py @@ -2,6 +2,7 @@ import os import platform import shutil +import sys import textwrap from datetime import datetime from os import PathLike @@ -143,7 +144,7 @@ def build_benchmark_tex(output_path: PathLike, overwrite: bool = False): with set_dir(_release_notes_path): tex_path = Path("run-time-comparison.tex") tex_path.unlink(missing_ok=True) - out, err, ret = run_cmd("python", "mk_runtimecomp.py", benchmarks_path, verbose=True) + out, err, ret = run_cmd(sys.executable, "mk_runtimecomp.py", benchmarks_path, verbose=True) assert not ret, out + err assert tex_path.is_file() @@ -202,7 +203,7 @@ def files_match(tex_path, dfn_path, ignored): f.unlink() # run python script - out, err, ret = run_cmd("python", "mf6ivar.py") + out, err, ret = run_cmd(sys.executable, "mf6ivar.py") assert not ret, out + err # check that dfn and tex files match @@ -246,7 +247,7 @@ def build_tex_folder_structure(overwrite: bool = False): return with set_dir(_release_notes_path): - out, err, ret = run_cmd("python", "mk_folder_struct.py", "-dp", _project_root_path) + out, err, ret = run_cmd(sys.executable, "mk_folder_struct.py", "-dp", _project_root_path) assert not ret, out + err assert path.is_file(), f"Failed to create {path}" diff --git a/distribution/conftest.py b/distribution/conftest.py index b9979c03ae1..5cfa5120e10 100644 --- a/distribution/conftest.py +++ b/distribution/conftest.py @@ -2,8 +2,8 @@ from update_version import Version -_project_root_path = Path(__file__).parent.parent -_dist_dir_path = Path(__file__).parent.parent.parent / f"mf{str(Version.from_file(_project_root_path / 'version.txt'))}" +_project_root_path = Path(__file__).resolve().parent.parent +_dist_dir_path = _project_root_path.parent / f"mf{str(Version.from_file(_project_root_path / 'version.txt'))}" def pytest_addoption(parser): diff --git a/distribution/update_version.py b/distribution/update_version.py index d834b3eb43c..10806c3d0cc 100644 --- a/distribution/update_version.py +++ b/distribution/update_version.py @@ -45,7 +45,7 @@ from utils import get_modified_time project_name = "MODFLOW 6" -project_root_path = Path(__file__).parent.parent +project_root_path = Path(__file__).resolve().parent.parent version_file_path = project_root_path / "version.txt" touched_file_paths = [ version_file_path, diff --git a/distribution/utils.py b/distribution/utils.py index 59d60969270..06b66e6bbfd 100644 --- a/distribution/utils.py +++ b/distribution/utils.py @@ -10,7 +10,7 @@ from modflow_devtools.markers import requires_exe -_project_root_path = Path(__file__).parent.parent +_project_root_path = Path(__file__).resolve().parent.parent def get_project_root_path(): @@ -69,7 +69,7 @@ def get_repo_path() -> Path: warn( f"REPOS_PATH environment variable missing, defaulting to parent of project root" ) - return Path(repo_path) if repo_path else Path(__file__).parent.parent.parent + return Path(repo_path) if repo_path else _project_root_path def copytree(src: PathLike, dst: PathLike, symlinks=False, ignore=None): From 04656e1a445d570ea6a3b6923dbd73231970a118 Mon Sep 17 00:00:00 2001 From: mjr-deltares <45555666+mjr-deltares@users.noreply.github.com> Date: Mon, 13 Mar 2023 14:47:20 +0100 Subject: [PATCH 042/123] feat(par): preparing for parallel build on Windows (#1164) * working parallel windows * temp stash * ifort does not override private procedures * prepare for petsc vars * modify petsc dir in meson * make PETSc not required --- meson.build | 50 +++++++++++++++++++++++-- src/Solution/NumericalSolution.f90 | 5 ++- src/Solution/PETSc/PetscConvergence.F90 | 35 ++++++++++++++++- src/Solution/PETSc/PetscSolver.F90 | 4 +- src/Solution/ParallelSolution.f90 | 6 +-- src/meson.build | 25 +++++++++++-- 6 files changed, 111 insertions(+), 14 deletions(-) diff --git a/meson.build b/meson.build index 35ec1961a8e..fb4bf0bdb35 100644 --- a/meson.build +++ b/meson.build @@ -23,6 +23,10 @@ do_parallel_build = get_option('parallel') message('The used profile is:', profile) message('Parallel build:', do_parallel_build) +# windows options: +petsc_dir = meson.project_source_root() / '..' /'petsc-3.18.5' +petsc_arch = 'arch-ci-mswin-intel-modflow6' + fc = meson.get_compiler('fortran') fc_id = fc.get_id() compile_args = [] @@ -87,19 +91,31 @@ elif fc_id == 'intel' link_args += '-static-intel' endif -petsc = dependency('PETSc', required : false) +if build_machine.system() == 'windows' + # directly look for petsc + petsc_compiled = petsc_dir / petsc_arch + petsc = fc.find_library('libpetsc', dirs: petsc_compiled / 'lib', required : false) +else + petsc = dependency('PETSc', required : false) +endif mpi = dependency('mpi', language : 'fortran', required : petsc.found()) # on windows only with intel enable_mpi = do_parallel_build -if build_machine.system() == 'windows' - enable_mpi = do_parallel_build and (fc_id == 'intel-cl') +if build_machine.system() == 'windows' and do_parallel_build + if fc_id == 'intel-cl' + enable_mpi = true + else + message('Parallel build on Windows only with intel compiler, disabling...') + enable_mpi + endif endif # compile with mpi or petsc+mpi when found, # (not allowing petsc without mpi) dependencies = [ ] extra_cmp_args = [ ] + if mpi.found() and enable_mpi extra_cmp_args = [ '-D__WITH_MPI__' ] with_mpi = true @@ -107,18 +123,44 @@ if mpi.found() and enable_mpi else with_mpi = false endif + if petsc.found() and with_mpi extra_cmp_args = [ '-D__WITH_MPI__', '-D__WITH_PETSC__' ] with_petsc = true - dependencies = [ petsc, mpi ] + dependencies = [ mpi, petsc ] else with_petsc = false endif + compile_args += extra_cmp_args add_project_arguments(fc.get_supported_arguments(compile_args), language: 'fortran') add_project_link_arguments(fc.get_supported_arguments(link_args), language: 'fortran') +if with_petsc and build_machine.system() == 'windows' + message('Compiling PETSc Fortran modudules') + petsc_incdir = include_directories([petsc_dir / 'include', petsc_compiled / 'include']) + petsc_src = petsc_dir / 'src' + sources_petsc = [petsc_src / 'dm/f90-mod/petscdmdamod.F90', + petsc_src / 'dm/f90-mod/petscdmmod.F90', + petsc_src / 'dm/f90-mod/petscdmplexmod.F90', + petsc_src / 'dm/f90-mod/petscdmswarmmod.F90', + petsc_src / 'ksp/f90-mod/petsckspdefmod.F90', + petsc_src / 'ksp/f90-mod/petsckspmod.F90', + petsc_src / 'ksp/f90-mod/petscpcmod.F90', + petsc_src / 'mat/f90-mod/petscmatmod.F90', + petsc_src / 'snes/f90-mod/petscsnesmod.F90', + petsc_src / 'sys/f90-mod/petscsysmod.F90', + petsc_src / 'sys/mpiuni/f90-mod/mpiunimod.F90', + petsc_src / 'tao/f90-mod/petsctaomod.F90', + petsc_src / 'ts/f90-mod/petsctsmod.F90', + petsc_src / 'vec/f90-mod/petscvecmod.F90',] + petsc_modules = static_library('petsc_modules', + sources_petsc, + dependencies: dependencies, + include_directories: petsc_incdir) +endif + subdir('src') subdir('srcbmi') subdir('utils') diff --git a/src/Solution/NumericalSolution.f90 b/src/Solution/NumericalSolution.f90 index e3b920d2a31..11d237da285 100644 --- a/src/Solution/NumericalSolution.f90 +++ b/src/Solution/NumericalSolution.f90 @@ -159,6 +159,10 @@ module NumericalSolutionModule procedure :: get_exchanges procedure :: save + ! 'protected' (this can be overridden) + procedure :: sln_has_converged + + ! private procedure, private :: sln_connect procedure, private :: sln_reset procedure, private :: sln_ls @@ -170,7 +174,6 @@ module NumericalSolutionModule procedure, private :: sln_calcdx procedure, private :: sln_underrelax procedure, private :: sln_outer_check - procedure, private :: sln_has_converged procedure, private :: sln_get_loc procedure, private :: sln_get_nodeu procedure, private :: allocate_scalars diff --git a/src/Solution/PETSc/PetscConvergence.F90 b/src/Solution/PETSc/PetscConvergence.F90 index 27f95311f4e..cd9a6e6d388 100644 --- a/src/Solution/PETSc/PetscConvergence.F90 +++ b/src/Solution/PETSc/PetscConvergence.F90 @@ -2,10 +2,12 @@ module PetscConvergenceModule #include use petscksp use KindModule, only: I4B, DP + use ListModule implicit none private public :: petsc_check_convergence + public :: petsc_add_context type, public :: PetscContextType Vec :: x_old @@ -13,19 +15,48 @@ module PetscConvergenceModule real(DP) :: dvclose end type PetscContextType + type(ListType) :: ctx_list + contains - subroutine petsc_check_convergence(ksp, n, rnorm, flag, petsc_context, ierr) + !> @brief Add a context to the static list. The + !< generated idx can then be used as a handle when + !< calling 'KSPSetConvergenceTest' + subroutine petsc_add_context(ctx, idx) + class(PetscContextType), pointer, intent(in) :: ctx + integer(I4B), intent(out) :: idx + ! local + class(*), pointer :: ctx_ptr + + ctx_ptr => ctx + call ctx_list%Add(ctx_ptr) + idx = ctx_list%Count() + + end subroutine petsc_add_context + + !> @brief Routine to check the convergence. This is called + !< from within PETSc. + subroutine petsc_check_convergence(ksp, n, rnorm, flag, ctx_id, ierr) KSP :: ksp !< Iterative context PetscInt :: n !< Iteration number PetscReal :: rnorm !< 2-norm (preconditioned) residual value KSPConvergedReason :: flag !< Converged reason - class(PetscContextType), pointer :: petsc_context !< optional user-defined monitor context + PetscInt :: ctx_id !< index into the static context list PetscErrorCode :: ierr !< error ! local PetscScalar :: alpha = -1.0 real(DP) :: norm Vec :: x + class(PetscContextType), pointer :: petsc_context + class(*), pointer :: obj_ptr + + ! get the context from the list + petsc_context => null() + obj_ptr => ctx_list%GetItem(ctx_id) + select type (obj_ptr) + class is (PetscContextType) + petsc_context => obj_ptr + end select call KSPBuildSolution(ksp, PETSC_NULL_VEC, x, ierr) CHKERRQ(ierr) diff --git a/src/Solution/PETSc/PetscSolver.F90 b/src/Solution/PETSc/PetscSolver.F90 index b03c8aa9724..208d59f2869 100644 --- a/src/Solution/PETSc/PetscSolver.F90 +++ b/src/Solution/PETSc/PetscSolver.F90 @@ -22,6 +22,7 @@ module PetscSolverModule integer(I4B) :: lin_accel_type real(DP) :: dvclose class(PetscContextType), pointer :: petsc_ctx + integer(I4B) :: ctx_idx contains procedure :: initialize => petsc_initialize procedure :: solve => petsc_solve @@ -131,9 +132,10 @@ subroutine create_convergence_check(this) call MatCreateVecs( & this%mat_petsc, this%petsc_ctx%delta_x, PETSC_NULL_VEC, ierr) CHKERRQ(ierr) + call petsc_add_context(this%petsc_ctx, this%ctx_idx) call KSPSetConvergenceTest(this%ksp_petsc, petsc_check_convergence, & - this%petsc_ctx, PETSC_NULL_FUNCTION, ierr) + this%ctx_idx, PETSC_NULL_FUNCTION, ierr) CHKERRQ(ierr) end subroutine create_convergence_check diff --git a/src/Solution/ParallelSolution.f90 b/src/Solution/ParallelSolution.f90 index b27711f04e6..4ec3f983543 100644 --- a/src/Solution/ParallelSolution.f90 +++ b/src/Solution/ParallelSolution.f90 @@ -11,7 +11,7 @@ module ParallelSolutionModule type, extends(NumericalSolutionType) :: ParallelSolutionType contains ! override - procedure, private :: sln_has_converged + procedure :: sln_has_converged => par_has_converged end type ParallelSolutionType contains @@ -19,7 +19,7 @@ module ParallelSolutionModule !> @brief Check global convergence. The local maximum dependent !! variable change is reduced over MPI with all other processes !< that are running this parallel numerical solution. - function sln_has_converged(this, max_dvc) result(has_converged) + function par_has_converged(this, max_dvc) result(has_converged) class(ParallelSolutionType) :: this !< ParallelSolutionType instance real(DP) :: max_dvc !< the LOCAL maximum dependent variable change logical(LGP) :: has_converged !< True, when GLOBALLY converged @@ -35,6 +35,6 @@ function sln_has_converged(this, max_dvc) result(has_converged) has_converged = .true. end if - end function sln_has_converged + end function par_has_converged end module ParallelSolutionModule diff --git a/src/meson.build b/src/meson.build index 927565f0fc3..566c677bee1 100644 --- a/src/meson.build +++ b/src/meson.build @@ -246,6 +246,25 @@ endif mf6_external = static_library('mf6_external', external_libraries) -mf6core = static_library('mf6core', modflow_sources, dependencies: dependencies, link_with: [mf6_external]) - -mf6exe = executable('mf6', 'mf6.f90', link_with: [mf6core], dependencies: dependencies, install: true) +if build_machine.system() == 'windows' and with_petsc + mf6core = static_library('mf6core', + modflow_sources, + dependencies: dependencies, + link_with: [mf6_external], + include_directories: petsc_incdir) + mf6exe = executable('mf6', + 'mf6.f90', + link_with: [mf6core], + dependencies: dependencies, + install: true) +else + mf6core = static_library('mf6core', + modflow_sources, + dependencies: dependencies, + link_with: [mf6_external]) + mf6exe = executable('mf6', + 'mf6.f90', + link_with: [mf6core], + dependencies: dependencies, + install: true) +endif From 2ba454a05299170349e963c0c48afb892c7d135d Mon Sep 17 00:00:00 2001 From: langevin-usgs Date: Wed, 15 Mar 2023 08:01:49 -0500 Subject: [PATCH 043/123] fix(listreader): add error checking for binary list input (#1169) * fix(listreader): add error checking for binary list input * improve error message for cellid outside grid * fprettify --- doc/ReleaseNotes/v6.5.0.tex | 2 +- src/Utilities/ListReader.f90 | 137 +++++++++++++++++++---------------- 2 files changed, 76 insertions(+), 63 deletions(-) diff --git a/doc/ReleaseNotes/v6.5.0.tex b/doc/ReleaseNotes/v6.5.0.tex index 353b902a125..0b1bda5dd03 100644 --- a/doc/ReleaseNotes/v6.5.0.tex +++ b/doc/ReleaseNotes/v6.5.0.tex @@ -22,7 +22,7 @@ \underline{BASIC FUNCTIONALITY} \begin{itemize} \item When n-point cross-sections are active in SFR, the evaporation calculation uses the variable rwid (see MF6io.pdf) to calculate the total amount of evaporation even though the wetted topwidth is less than rwid. For example, using a trapezoidal cross-section geometry with an rwid of 10, an rlen of 100, and prescribed evaporation rate of 0.1, the calculated evaporative losses would equal 100 even when the wetted top width was only 5.0 units wide. With this bug fix, the evaporation in this example results in only 50 units of evaporation loss. A new autotests confirms the evaporation calculation using an n-point cross-section and common rectangular geometries in the same simulation. It is also worth mentioning that the precipitation calculation currently uses rwid. Since the precipitation falling outside the margins of the wetted top width but within rwid would likely be accumulated in a channel, it makes sense to leave this calculation as is. - % \item xxx + \item The input for some stress packages is read in a list format consisting of a cellid, the form of which depends on the type of discretization package, and stress information on each line. The cellid is checked upon reading to ensure that the cell is within the model grid. If the cell is outside the model grid, the program issues an error message and terminates. This cellid check was not implemented when the list was provided from an OPEN/CLOSE binary input file. The program was modified to include this check for both text and binary input. % \item xxx \end{itemize} diff --git a/src/Utilities/ListReader.f90 b/src/Utilities/ListReader.f90 index d40943f2e31..0f1d52110ff 100644 --- a/src/Utilities/ListReader.f90 +++ b/src/Utilities/ListReader.f90 @@ -4,7 +4,9 @@ module ListReaderModule use KindModule, only: DP, I4B use ConstantsModule, only: LINELENGTH, LENBOUNDNAME, LENTIMESERIESNAME, & LENAUXNAME, LENLISTLABEL, DONE - use SimModule, only: store_error_unit + use SimVariablesModule, only: errmsg + use SimModule, only: store_error, count_errors, store_error_unit + implicit none private public ListReaderType @@ -168,7 +170,6 @@ subroutine set_openclose(this) use InputOutputModule, only: u9rdcom, urword, openfile use OpenSpecModule, only: form, access use ConstantsModule, only: LINELENGTH - use SimModule, only: store_error ! -- dummy class(ListReaderType) :: this ! -- local @@ -177,7 +178,6 @@ subroutine set_openclose(this) logical :: exists integer(I4B) :: nunopn = 99 character(len=LINELENGTH) :: fname - character(len=LINELENGTH) :: errmsg ! -- formats character(len=*), parameter :: fmtocne = & &"('Specified OPEN/CLOSE file ',(A),' does not exist')" @@ -283,13 +283,11 @@ subroutine read_binary(this) ! -- modules use ConstantsModule, only: LINELENGTH, LENBIGLINE use InputOutputModule, only: get_node - use SimModule, only: store_error ! -- dummy class(ListReaderType) :: this ! -- local integer(I4B) :: mxlist, ldim, naux, nod, ii, jj character(len=LINELENGTH) :: fname - character(len=LENBIGLINE) :: errmsg integer(I4B), dimension(:), allocatable :: cellid ! -- formats character(len=*), parameter :: fmtmxlsterronly = & @@ -317,6 +315,9 @@ subroutine read_binary(this) ! -- read layer, row, col, or cell number read (this%inlist, iostat=this%ierr) cellid + ! -- ensure cellid is valid, store an error otherwise + call check_cellid(ii, cellid, this%mshape, this%ndim) + ! -- If not end of record, then store nodenumber, else ! calculate lstend and nlist, and exit readloop select case (this%ierr) @@ -329,7 +330,7 @@ subroutine read_binary(this) call store_error(errmsg, terminate=.TRUE.) end if ! - ! -- Store node number and read the remainder of the record + ! -- Calculate and store user node number if (this%ndim == 1) then nod = cellid(1) elseif (this%ndim == 2) then @@ -340,6 +341,8 @@ subroutine read_binary(this) this%mshape(1), this%mshape(2), this%mshape(3)) end if this%nodelist(ii) = nod + + ! -- Read remainder of record read (this%inlist, iostat=this%ierr) (this%rlist(jj, ii), jj=1, ldim), & (this%auxvar(ii, jj), jj=1, naux) if (this%ierr /= 0) then @@ -373,6 +376,11 @@ subroutine read_binary(this) ! end do readloop ! + ! -- Stop if errors were detected + if (count_errors() > 0) then + call store_error_unit(this%inlist) + end if + ! ! -- return return end subroutine read_binary @@ -387,7 +395,6 @@ subroutine read_ascii(this) ! -- modules use ConstantsModule, only: LENBOUNDNAME, LINELENGTH, DZERO use InputOutputModule, only: u9rdcom, urword, get_node - use SimModule, only: store_error, count_errors use ArrayHandlersModule, only: ExpandArray ! -- dummy class(ListReaderType) :: this @@ -397,7 +404,6 @@ subroutine read_ascii(this) real(DP) :: r integer(I4B), dimension(:), allocatable :: cellid character(len=LINELENGTH) :: fname - character(len=LINELENGTH) :: errmsg ! -- formats character(len=*), parameter :: fmtmxlsterronly = & "('***ERROR READING LIST. & @@ -449,71 +455,33 @@ subroutine read_ascii(this) call store_error_unit(this%inlist) end if ! - ! -- Read layer, row, column or cell number and assign to nodelist + ! -- Initialize locator this%lloc = 1 - if (this%ndim == 3) then - ! - ! -- Grid is structured; read layer, row, column - call urword(this%line, this%lloc, this%istart, this%istop, 2, & - cellid(1), r, this%iout, this%inlist) + ! + ! -- Read cellid + call urword(this%line, this%lloc, this%istart, this%istop, 2, & + cellid(1), r, this%iout, this%inlist) + if (this%ndim > 1) then call urword(this%line, this%lloc, this%istart, this%istop, 2, & cellid(2), r, this%iout, this%inlist) + end if + if (this%ndim > 2) then call urword(this%line, this%lloc, this%istart, this%istop, 2, & cellid(3), r, this%iout, this%inlist) - ! - ! -- Check for illegal grid location - if (cellid(1) < 1 .or. cellid(1) > this%mshape(1)) then - write (errmsg, *) ' Layer number in list is outside of the grid', & - cellid(1) - call store_error(errmsg) - end if - if (cellid(2) < 1 .or. cellid(2) > this%mshape(2)) then - write (errmsg, *) ' Row number in list is outside of the grid', & - cellid(2) - call store_error(errmsg) - end if - if (cellid(3) < 1 .or. cellid(3) > this%mshape(3)) then - write (errmsg, *) ' Column number in list is outside of the grid', & - cellid(3) - call store_error(errmsg) - end if - ! - ! -- Calculate nodenumber and put in nodelist + end if + ! + ! -- ensure cellid is valid, store an error otherwise + call check_cellid(ii, cellid, this%mshape, this%ndim) + ! + ! -- Calculate user node number + if (this%ndim == 3) then nod = get_node(cellid(1), cellid(2), cellid(3), & this%mshape(1), this%mshape(2), this%mshape(3)) elseif (this%ndim == 2) then - ! - ! -- Grid is disv - call urword(this%line, this%lloc, this%istart, this%istop, 2, & - cellid(1), r, this%iout, this%inlist) - call urword(this%line, this%lloc, this%istart, this%istop, 2, & - cellid(2), r, this%iout, this%inlist) - ! - ! -- Check for illegal grid location - if (cellid(1) < 1 .or. cellid(1) > this%mshape(1)) then - write (errmsg, *) ' Layer number in list is outside of the grid', & - cellid(1) - call store_error(errmsg) - end if - if (cellid(2) < 1 .or. cellid(2) > this%mshape(2)) then - write (errmsg, *) ' Cell2d number in list is outside of the grid', & - cellid(2) - call store_error(errmsg) - end if - ! - ! -- Calculate nodenumber and put in nodelist nod = get_node(cellid(1), 1, cellid(2), & this%mshape(1), 1, this%mshape(2)) else - ! - ! -- Grid is unstructured; read layer and celld2d number - call urword(this%line, this%lloc, this%istart, this%istop, 2, nod, r, & - this%iout, this%inlist) - if (nod < 1 .or. nod > this%mshape(1)) then - write (errmsg, *) ' Node number in list is outside of the grid', nod - call store_error(errmsg) - end if - ! + nod = cellid(1) end if ! ! -- Assign nod to nodelist @@ -599,6 +567,51 @@ subroutine read_ascii(this) return end subroutine read_ascii + !> @ brief Check for valid cellid + !< + subroutine check_cellid(ii, cellid, mshape, ndim) + integer(I4B), intent(in) :: ii + integer(I4B), dimension(:), intent(in) :: cellid !< cellid + integer(I4B), dimension(:), intent(in) :: mshape !< model shape + integer(I4B), intent(in) :: ndim !< size of mshape + character(len=20) :: cellstr, mshstr + character(len=*), parameter :: fmterr = & + "('List entry ',i0,' contains cellid ',a,' but this cellid is invalid & + &for model with shape ', a)" + character(len=*), parameter :: fmtndim1 = & + "('(',i0,')')" + character(len=*), parameter :: fmtndim2 = & + "('(',i0,',',i0,')')" + character(len=*), parameter :: fmtndim3 = & + "('(',i0,',',i0,',',i0,')')" + if (ndim == 1) then + if (cellid(1) < 1 .or. cellid(1) > mshape(1)) then + write (cellstr, fmtndim1) cellid(1) + write (mshstr, fmtndim1) mshape(1) + write (errmsg, fmterr) ii, trim(adjustl(cellstr)), trim(adjustl(mshstr)) + call store_error(errmsg) + end if + else if (ndim == 2) then + if (cellid(1) < 1 .or. cellid(1) > mshape(1) .or. & + cellid(2) < 1 .or. cellid(2) > mshape(2)) then + write (cellstr, fmtndim2) cellid(1), cellid(2) + write (mshstr, fmtndim2) mshape(1), mshape(2) + write (errmsg, fmterr) ii, trim(adjustl(cellstr)), trim(adjustl(mshstr)) + call store_error(errmsg) + end if + else if (ndim == 3) then + if (cellid(1) < 1 .or. cellid(1) > mshape(1) .or. & + cellid(2) < 1 .or. cellid(2) > mshape(2) .or. & + cellid(3) < 1 .or. cellid(3) > mshape(3)) then + write (cellstr, fmtndim3) cellid(1), cellid(2), cellid(3) + write (mshstr, fmtndim3) mshape(1), mshape(2), mshape(3) + write (errmsg, fmterr) ii, trim(adjustl(cellstr)), trim(adjustl(mshstr)) + call store_error(errmsg) + end if + end if + return + end subroutine check_cellid + subroutine write_list(this) ! ****************************************************************************** ! write_list -- Write input data to a list From ac2f4d1238ef0f58e325a6c91c11db5a08ececdc Mon Sep 17 00:00:00 2001 From: mjr-deltares <45555666+mjr-deltares@users.noreply.github.com> Date: Wed, 15 Mar 2023 15:18:25 +0100 Subject: [PATCH 044/123] fix(par): remote exchanges are now properly synchronized (#1168) * working parallel windows * temp stash * ifort does not override private procedures * prepare for petsc vars * modify petsc dir in meson * make PETSc not required * starting virt data documentation/cleanup * add next parallel test case * add diagnostics in message building * improved logging (test failing now because of remote exchanges) * - set up parallel test case #2 - solved issue with remote exchanges - add GetIndex method to List * Add better documentation of VirtualExchanges * more doc on virtual exchanges * fprettify * - Clean up petsc context from global list - minor clean up * fprettify hickup --- autotest/simulation.py | 2 +- autotest/test_par_gwf02.py | 256 ++++++++++++++++++ src/Distributed/Mapper.f90 | 4 +- src/Distributed/MpiMessageBuilder.f90 | 72 +++-- src/Distributed/MpiRouter.f90 | 110 +++++--- src/Distributed/VirtualDataContainer.f90 | 30 +- src/Distributed/VirtualDataManager.f90 | 41 ++- src/Distributed/VirtualExchange.f90 | 179 +++++++----- src/Distributed/VirtualGwfModel.f90 | 2 +- src/Distributed/VirtualModel.f90 | 34 +-- .../Connection/SpatialModelConnection.f90 | 18 +- src/RunControl.f90 | 85 +++--- src/Solution/PETSc/PetscConvergence.F90 | 26 +- src/Solution/ParallelSolution.f90 | 2 +- src/Utilities/List.f90 | 39 +-- src/Utilities/SimStages.f90 | 22 +- src/mf6core.f90 | 7 +- 17 files changed, 669 insertions(+), 260 deletions(-) create mode 100644 autotest/test_par_gwf02.py diff --git a/autotest/simulation.py b/autotest/simulation.py index 3451bcb9059..73516fbfdee 100644 --- a/autotest/simulation.py +++ b/autotest/simulation.py @@ -336,7 +336,7 @@ def run_parallel(self, exe): nr_success = 0 buff = [] - mpiexec_cmd = ["mpiexec", "-np", str(self.ncpus), exe, "-p"] + mpiexec_cmd = ["mpiexec", "--oversubscribe", "-np", str(self.ncpus), exe, "-p"] proc = Popen(mpiexec_cmd, stdout=PIPE, stderr=STDOUT, cwd=self.simpath) while True: diff --git a/autotest/test_par_gwf02.py b/autotest/test_par_gwf02.py new file mode 100644 index 00000000000..c8bad7421db --- /dev/null +++ b/autotest/test_par_gwf02.py @@ -0,0 +1,256 @@ +import os + +import flopy +import numpy as np +from decimal import Decimal +import pytest +from framework import TestFramework +from simulation import TestSimulation + +# Test for parallel MODFLOW running a simple +# multi-model setup on different partitionings +# +# +# [M1ny] | ... | ... | [Mnxny] +# ----------------------------------- +# ... | ... | ... | ... +# ----------------------------------- +# [M12] | ... | ... | ... +# ----------------------------------- +# [M11] | [M21] | ... | [Mnx1] +# +# with constant head set at the lower-left corner. +# This constant head should reach all domains, +# no matter the topology of partitions + +ex = ["par_gwf02-a", "par_gwf02-b", "par_gwf02-c", + "par_gwf02-d", "par_gwf02-e", "par_gwf02-f"] +domain_grid = [(1,5), (5,1), (2,2), (3,3), (4,4), (5,5)] + +nlay = 1 +nrow = 3 +ncol = 3 +delr = 100.0 +delc = 100.0 +head_initial = -1.0 +cst_head_south_west = 435.0 +hclose = 1.0e-8 + + +def get_model_name(ix, iy): + return f"model-{ix}-{iy}" + + +def get_simulation(idx, dir): + + name = ex[idx] + nr_models_x = domain_grid[idx][0] + nr_models_y = domain_grid[idx][1] + + # parameters and spd + # tdis + nper = 1 + tdis_rc = [] + for i in range(nper): + tdis_rc.append((1.0, 1, 1)) + + # solver data + nouter, ninner = 100, 300 + rclose, relax = 1e-3, 0.97 + + sim = flopy.mf6.MFSimulation( + sim_name=name, version="mf6", exe_name="mf6", sim_ws=dir, + ) + + tdis = flopy.mf6.ModflowTdis( + sim, time_units="DAYS", nper=nper, perioddata=tdis_rc + ) + + ims = flopy.mf6.ModflowIms( + sim, + print_option="ALL", + outer_dvclose=hclose, + outer_maximum=nouter, + under_relaxation="DBD", + inner_maximum=ninner, + inner_dvclose=hclose, + rcloserecord=rclose, + linear_acceleration="BICGSTAB", + relaxation_factor=relax, + ) + + # create models (and exchanges) + for ix in range(nr_models_x): + for iy in range(nr_models_y): + add_model(sim, ix, iy, nr_models_x, nr_models_y) + + # add exchanges from west to east + for iy in range(nr_models_y): + for ix in range(nr_models_x - 1): + name_west = get_model_name(ix, iy) + name_east = get_model_name(ix + 1, iy) + add_exchange_west_east(sim, name_west, name_east) + + # add exchange from south to north + for ix in range(nr_models_x): + for iy in range(nr_models_y -1 ): + name_south = get_model_name(ix, iy) + name_north = get_model_name(ix, iy + 1) + add_exchange_south_north(sim, name_south, name_north) + + return sim + +def add_model(sim, ix, iy, nr_models_x, nr_models_y): + + # model spatial discretization + shift_x = ix * ncol * delr + shift_y = iy * nrow * delc + model_name = get_model_name(ix, iy) + + # top/bot of the aquifer + tops = [0.0, -100.0] + + # hydraulic conductivity + k11 = 1.0 + + # initial head + h_start = head_initial + + gwf = flopy.mf6.ModflowGwf(sim, modelname=model_name, save_flows=True) + dis = flopy.mf6.ModflowGwfdis( + gwf, + nlay=nlay, + nrow=nrow, + ncol=ncol, + delr=delr, + delc=delc, + top=tops[0], + botm=tops[1:nlay+1], + xorigin=shift_x, + yorigin=shift_y + ) + ic = flopy.mf6.ModflowGwfic(gwf, strt=h_start) + npf = flopy.mf6.ModflowGwfnpf( + gwf, + save_specific_discharge=True, + save_flows=True, + icelltype=0, + k=k11, + ) + oc = flopy.mf6.ModflowGwfoc( + gwf, + head_filerecord=f"{model_name}.hds", + budget_filerecord=f"{model_name}.cbc", + headprintrecord=[("COLUMNS", 10, "WIDTH", 15, "DIGITS", 6, "GENERAL")], + saverecord=[("HEAD", "LAST"), ("BUDGET", "LAST")], + ) + + if ix == 0 and iy == 0: + # add SW corner BC + sw_chd = [[(0, 0, 0), cst_head_south_west]] + chd_spd_sw = {0: sw_chd} + chd = flopy.mf6.ModflowGwfchd(gwf, stress_period_data=chd_spd_sw) + +def add_exchange_west_east(sim, name_west, name_east): + + exg_filename = f"we_{name_west}_{name_east}.gwfgwf" + # exchangedata + angldegx = 0.0 + cdist = delr + gwfgwf_data = [ + [ + (ilay, irow, ncol - 1), + (ilay, irow, 0), + 1, + delr / 2.0, + delr / 2.0, + delc, + angldegx, + cdist, + ] + for irow in range(nrow) + for ilay in range(nlay) + ] + gwfgwf = flopy.mf6.ModflowGwfgwf( + sim, + exgtype="GWF6-GWF6", + nexg=len(gwfgwf_data), + exgmnamea=name_west, + exgmnameb=name_east, + exchangedata=gwfgwf_data, + auxiliary=["ANGLDEGX", "CDIST"], + filename=exg_filename + ) + +def add_exchange_south_north(sim, name_south, name_north): + + exg_filename = f"sn_{name_south}_{name_north}.gwfgwf" + + # exchangedata + angldegx = 90.0 + cdist = delc + gwfgwf_data = [ + [ + (ilay, 0, icol), + (ilay, nrow-1, icol), + 1, + delc / 2.0, + delc / 2.0, + delr, + angldegx, + cdist, + ] + for icol in range(ncol) + for ilay in range(nlay) + ] + gwfgwf = flopy.mf6.ModflowGwfgwf( + sim, + exgtype="GWF6-GWF6", + nexg=len(gwfgwf_data), + exgmnamea=name_south, + exgmnameb=name_north, + exchangedata=gwfgwf_data, + auxiliary=["ANGLDEGX", "CDIST"], + filename=exg_filename + ) + +def build_petsc_db(exdir): + petsc_db_file = os.path.join(exdir, ".petscrc") + with open(petsc_db_file, 'w') as petsc_file: + petsc_file.write("-sub_ksp_type bcgs\n") + petsc_file.write("-sub_pc_type ilu\n") + petsc_file.write(f"-dvclose {Decimal(hclose):.2E}\n") + petsc_file.write("-options_left no\n") + #petsc_file.write("-wait_dbg\n") + +def build_model(idx, exdir): + sim = get_simulation(idx, exdir) + build_petsc_db(exdir) + return sim, None + +def eval_model(sim): + mf6_sim = flopy.mf6.MFSimulation.load(sim_ws=sim.simpath) + for mname in mf6_sim.model_names: + m = mf6_sim.get_model(mname) + hds = m.output.head().get_data().flatten() + hds_compare = cst_head_south_west*np.ones_like(hds) + assert np.allclose(hds, hds_compare, atol=1.0e-6, rtol=0.0) + + +@pytest.mark.parallel +@pytest.mark.parametrize( + "idx, name", + list(enumerate(ex)), +) +def test_mf6model(idx, name, function_tmpdir, targets): + ncpus = domain_grid[idx][0]*domain_grid[idx][1] + test = TestFramework() + test.build(build_model, idx, str(function_tmpdir)) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_model, + idxsim=0, make_comparison=False, + parallel=True, ncpus=ncpus, + ), + str(function_tmpdir), + ) diff --git a/src/Distributed/Mapper.f90 b/src/Distributed/Mapper.f90 index aa7fc091d49..7f14be44e91 100644 --- a/src/Distributed/Mapper.f90 +++ b/src/Distributed/Mapper.f90 @@ -58,12 +58,12 @@ subroutine add_exchange_vars(this) if (.not. virt_exg%v_model1%is_local) then virt_mem_path = virt_exg%get_vrt_mem_path('NODEM1', '') call this%map_data_full(0, 'NODEM1', conn%prim_exchange%memoryPath, & - 'NODEM1', virt_mem_path, (/STG_BEFORE_DF/)) + 'NODEM1', virt_mem_path, (/STG_BEFORE_CON_DF/)) end if if (.not. virt_exg%v_model2%is_local) then virt_mem_path = virt_exg%get_vrt_mem_path('NODEM2', '') call this%map_data_full(0, 'NODEM2', conn%prim_exchange%memoryPath, & - 'NODEM2', virt_mem_path, (/STG_BEFORE_DF/)) + 'NODEM2', virt_mem_path, (/STG_BEFORE_CON_DF/)) end if end do diff --git a/src/Distributed/MpiMessageBuilder.f90 b/src/Distributed/MpiMessageBuilder.f90 index 2e092cac093..022e021666b 100644 --- a/src/Distributed/MpiMessageBuilder.f90 +++ b/src/Distributed/MpiMessageBuilder.f90 @@ -1,5 +1,5 @@ module MpiMessageBuilderModule - use KindModule, only: I4B + use KindModule, only: I4B, LGP use MemoryTypeModule, only: MemoryType use STLVecIntModule use VirtualBaseModule @@ -16,13 +16,17 @@ module MpiMessageBuilderModule type, public :: MpiMessageBuilderType type(VdcPtrType), dimension(:), pointer :: vdc_models => null() !< the models to be build the message for type(VdcPtrType), dimension(:), pointer :: vdc_exchanges => null() !< the exchanges to be build the message for + integer(I4B) :: imon !< the output file unit, set from outside + logical(LGP) :: enable_monitor !< log when true contains - procedure :: attach_data => mb_attach_data - procedure :: release_data => mb_release_data - procedure :: create_header_snd => mb_create_header_snd - procedure :: create_header_rcv => mb_create_header_rcv - procedure :: create_body_rcv => mb_create_body_rcv - procedure :: create_body_snd => mb_create_body_snd + procedure :: init + procedure :: attach_data + procedure :: release_data + procedure :: create_header_snd + procedure :: create_header_rcv + procedure :: create_body_rcv + procedure :: create_body_snd + procedure :: set_monitor ! private procedure, private :: get_vdc_from_hdr procedure, private :: create_vdc_snd_hdr @@ -33,7 +37,14 @@ module MpiMessageBuilderModule contains - subroutine mb_attach_data(this, vdc_models, vdc_exchanges) + subroutine init(this) + class(MpiMessageBuilderType) :: this + + this%imon = -1 + + end subroutine init + + subroutine attach_data(this, vdc_models, vdc_exchanges) class(MpiMessageBuilderType) :: this type(VdcPtrType), dimension(:), pointer :: vdc_models type(VdcPtrType), dimension(:), pointer :: vdc_exchanges @@ -41,21 +52,29 @@ subroutine mb_attach_data(this, vdc_models, vdc_exchanges) this%vdc_models => vdc_models this%vdc_exchanges => vdc_exchanges - end subroutine mb_attach_data + end subroutine attach_data - subroutine mb_release_data(this) + subroutine release_data(this) class(MpiMessageBuilderType) :: this this%vdc_models => null() this%vdc_exchanges => null() - end subroutine mb_release_data + end subroutine release_data + + subroutine set_monitor(this, imon) + class(MpiMessageBuilderType) :: this + integer(I4B) :: imon + + this%imon = imon + + end subroutine set_monitor !> @brief Create the header data type to send to !! the remote process for this particular stage. !! From these data, the receiver can construct the !< body to send back to us. - subroutine mb_create_header_snd(this, rank, stage, hdrs_snd_type) + subroutine create_header_snd(this, rank, stage, hdrs_snd_type) class(MpiMessageBuilderType) :: this integer(I4B) :: rank integer(I4B) :: stage @@ -90,6 +109,13 @@ subroutine mb_create_header_snd(this, rank, stage, hdrs_snd_type) allocate (types(nr_types)) allocate (displs(nr_types)) + if (this%imon > 0) then + write (this%imon, '(6x,a,*(i3))') "create headers for models: ", & + model_idxs%get_values() + write (this%imon, '(6x,a,*(i3))') "create headers for exchange: ", & + exg_idxs%get_values() + end if + ! loop over containers do i = 1, model_idxs%size vdc => this%vdc_models(model_idxs%at(i))%ptr @@ -120,9 +146,9 @@ subroutine mb_create_header_snd(this, rank, stage, hdrs_snd_type) deallocate (types) deallocate (displs) - end subroutine mb_create_header_snd + end subroutine create_header_snd - subroutine mb_create_header_rcv(this, hdr_rcv_type) + subroutine create_header_rcv(this, hdr_rcv_type) class(MpiMessageBuilderType) :: this integer :: hdr_rcv_type ! local @@ -134,11 +160,11 @@ subroutine mb_create_header_rcv(this, hdr_rcv_type) call MPI_Type_contiguous(2, MPI_INTEGER, hdr_rcv_type, ierr) call MPI_Type_commit(hdr_rcv_type, ierr) - end subroutine mb_create_header_rcv + end subroutine create_header_rcv !> @brief Create the body to receive based on the headers !< that have been sent - subroutine mb_create_body_rcv(this, rank, stage, body_rcv_type) + subroutine create_body_rcv(this, rank, stage, body_rcv_type) class(MpiMessageBuilderType) :: this integer(I4B) :: rank integer(I4B) :: stage @@ -159,12 +185,18 @@ subroutine mb_create_body_rcv(this, rank, stage, body_rcv_type) do i = 1, size(this%vdc_models) vdc => this%vdc_models(i)%ptr if (vdc%is_active .and. vdc%orig_rank == rank) then + if (this%imon > 0) then + write (this%imon, '(6x,a,i0)') "expecting model ", vdc%id + end if call model_idxs%push_back(i) end if end do do i = 1, size(this%vdc_exchanges) vdc => this%vdc_exchanges(i)%ptr if (vdc%is_active .and. vdc%orig_rank == rank) then + if (this%imon > 0) then + write (this%imon, '(6x,a,i0)') "expecting exchange ", vdc%id + end if call exg_idxs%push_back(i) end if end do @@ -203,11 +235,11 @@ subroutine mb_create_body_rcv(this, rank, stage, body_rcv_type) deallocate (displs) deallocate (blk_cnts) - end subroutine mb_create_body_rcv + end subroutine create_body_rcv !> @brief Create the body to send based on the received headers !< - subroutine mb_create_body_snd(this, rank, stage, headers, body_snd_type) + subroutine create_body_snd(this, rank, stage, headers, body_snd_type) class(MpiMessageBuilderType) :: this integer(I4B) :: rank integer(I4B) :: stage @@ -245,7 +277,7 @@ subroutine mb_create_body_snd(this, rank, stage, headers, body_snd_type) deallocate (displs) deallocate (blk_cnts) - end subroutine mb_create_body_snd + end subroutine create_body_snd !> @brief Create send header for virtual data container, relative !< to the field ...%id @@ -285,6 +317,7 @@ function create_vdc_rcv_body(this, vdc, rank, stage) result(new_type) call virtual_items%init() call vdc%get_recv_items(stage, rank, virtual_items) + !if (this%imon > 0) call vdc%print_items(this%imon, virtual_items) new_type = this%create_vdc_body(vdc, virtual_items) call virtual_items%destroy() @@ -301,6 +334,7 @@ function create_vdc_snd_body(this, vdc, rank, stage) result(new_type) call virtual_items%init() call vdc%get_send_items(stage, rank, virtual_items) + !if (this%imon > 0) call vdc%print_items(this%imon, virtual_items) new_type = this%create_vdc_body(vdc, virtual_items) call virtual_items%destroy() diff --git a/src/Distributed/MpiRouter.f90 b/src/Distributed/MpiRouter.f90 index 881b4e5b26e..88512deb429 100644 --- a/src/Distributed/MpiRouter.f90 +++ b/src/Distributed/MpiRouter.f90 @@ -37,6 +37,8 @@ module MpiRouterModule procedure :: route_sln => mr_route_sln procedure :: destroy => mr_destroy ! private + procedure, private :: activate + procedure, private :: deactivate procedure, private :: mr_update_senders procedure, private :: mr_update_senders_sln procedure, private :: mr_update_receivers @@ -70,7 +72,10 @@ subroutine mr_initialize(this) character(len=LINELENGTH) :: monitor_file ! to log or not to log - this%enable_monitor = .true. + this%enable_monitor = .false. + + ! initialize the MPI message builder + call this%message_builder%init() ! get mpi world for our process this%mpi_world => get_mpi_world() @@ -122,6 +127,7 @@ subroutine mr_initialize(this) this%imon = getunit() write (monitor_file, '(a,i0,a)') "mpi.p", proc_id, ".log" open (unit=this%imon, file=monitor_file) + call this%message_builder%set_monitor(this%imon) ! write initial info write (this%imon, '(a,/)') "initialize MPI Router:" @@ -137,6 +143,30 @@ subroutine mr_initialize(this) end subroutine mr_initialize + !> @brief Activate models and exchanges for routing + !< + subroutine activate(this, models, exchanges) + class(MpiRouterType) :: this + type(VdcPtrType), dimension(:), pointer :: models + type(VdcPtrType), dimension(:), pointer :: exchanges + + this%rte_models => models + this%rte_exchanges => exchanges + call this%message_builder%attach_data(models, exchanges) + + end subroutine activate + + !> @brief Deactivate data after routing + !< + subroutine deactivate(this) + class(MpiRouterType) :: this + + this%rte_models => null() + this%rte_exchanges => null() + call this%message_builder%release_data() + + end subroutine deactivate + !> @brief This will route all remote data from the !! global models and exchanges over MPI, for a !< given stage @@ -149,19 +179,14 @@ subroutine mr_route_all(this, stage) write (this%imon, '(2a)') "routing stage: ", STG_TO_STR(stage) end if - ! data to route - this%rte_models => this%all_models - this%rte_exchanges => this%all_exchanges - call this%message_builder%attach_data(this%rte_models, & - this%rte_exchanges) - ! route all + call this%activate(this%all_models, this%all_exchanges) call this%mr_route_active(stage) + call this%deactivate() - ! release - this%rte_models => null() - this%rte_exchanges => null() - call this%message_builder%release_data() + if (this%enable_monitor) then + write (this%imon, '(2a)') "end routing all: ", STG_TO_STR(stage) + end if end subroutine mr_route_all @@ -178,19 +203,14 @@ subroutine mr_route_sln(this, virtual_sol, stage) write (this%imon, '(2a)') "routing stage: ", STG_TO_STR(stage) end if - ! data to route - this%rte_models => virtual_sol%models - this%rte_exchanges => virtual_sol%exchanges - call this%message_builder%attach_data(virtual_sol%models, & - virtual_sol%exchanges) - ! route for this solution + call this%activate(virtual_sol%models, virtual_sol%exchanges) call this%mr_route_active(stage) + call this%deactivate() - ! release - this%rte_models => null() - this%rte_exchanges => null() - call this%message_builder%release_data() + if (this%enable_monitor) then + write (this%imon, '(2a)') "end routing solution: ", STG_TO_STR(stage) + end if end subroutine mr_route_sln @@ -222,9 +242,9 @@ subroutine mr_route_active(this, stage) call this%mr_update_receivers() if (this%enable_monitor) then - write (this%imon, '(2x,a,*(i0))') "process ids sending data: ", & + write (this%imon, '(2x,a,*(i3))') "process ids sending data: ", & this%senders%get_values() - write (this%imon, '(2x,a,*(i0))') "process ids receiving data: ", & + write (this%imon, '(2x,a,*(i3))') "process ids receiving data: ", & this%receivers%get_values() end if @@ -245,9 +265,16 @@ subroutine mr_route_active(this, stage) allocate (body_rcv_t(this%senders%size)) allocate (body_snd_t(this%receivers%size)) + if (this%enable_monitor) then + write (this%imon, '(2x,a)') "== communicating headers ==" + end if + ! first receive headers for outward data do i = 1, this%receivers%size rnk = this%receivers%at(i) + if (this%enable_monitor) then + write (this%imon, '(4x,a,i0)') "Ireceive header from process: ", rnk + end if call this%message_builder%create_header_rcv(hdr_rcv_t(i)) call MPI_Irecv(headers(:, i), max_headers, hdr_rcv_t(i), rnk, stage, & MF6_COMM_WORLD, rcv_req(i), ierr) @@ -257,15 +284,12 @@ subroutine mr_route_active(this, stage) ! send header for incoming data do i = 1, this%senders%size rnk = this%senders%at(i) - call this%message_builder%create_header_snd(rnk, stage, hdr_snd_t(i)) - call MPI_Isend(MPI_BOTTOM, 1, hdr_snd_t(i), rnk, stage, & - MF6_COMM_WORLD, snd_req(i), ierr) - if (this%enable_monitor) then - call MPI_Type_size(hdr_snd_t(i), msg_size, ierr) write (this%imon, '(4x,a,i0)') "send header to process: ", rnk end if - + call this%message_builder%create_header_snd(rnk, stage, hdr_snd_t(i)) + call MPI_Isend(MPI_BOTTOM, 1, hdr_snd_t(i), rnk, stage, & + MF6_COMM_WORLD, snd_req(i), ierr) call MPI_Type_free(hdr_snd_t(i), ierr) end do @@ -279,7 +303,7 @@ subroutine mr_route_active(this, stage) if (this%enable_monitor) then rnk = this%senders%at(i) write (this%imon, '(4x,a,i0)') "received headers from process: ", rnk - write (this%imon, '(4x,a)') "expecting data for:" + write (this%imon, '(6x,a)') "expecting data for:" do j = 1, hdr_rcv_cnt(i) write (this%imon, '(6x,a,i0,a,a)') "id: ", headers(j, i)%id, & " type: ", trim(VDC_TYPE_TO_STR(headers(j, i)%container_type)) @@ -289,9 +313,17 @@ subroutine mr_route_active(this, stage) call MPI_Type_free(hdr_rcv_t(i), ierr) end do + if (this%enable_monitor) then + write (this%imon, '(2x,a)') "== communicating bodies ==" + end if + ! recv bodies do i = 1, this%senders%size rnk = this%senders%at(i) + if (this%enable_monitor) then + write (this%imon, '(4x,a,i0)') "receiving from process: ", rnk + end if + call this%message_builder%create_body_rcv(rnk, stage, body_rcv_t(i)) call MPI_Type_size(body_rcv_t(i), msg_size, ierr) if (msg_size > 0) then @@ -300,18 +332,16 @@ subroutine mr_route_active(this, stage) end if if (this%enable_monitor) then - if (msg_size > 0) then - write (this%imon, '(4x,a,i0)') "receiving from process: ", rnk - write (this%imon, '(6x,a,i0)') "message body size: ", msg_size - else - write (this%imon, '(4x,a,i0)') "no receiving from process: ", rnk - end if + write (this%imon, '(6x,a,i0)') "message body size: ", msg_size end if end do ! send bodies do i = 1, this%receivers%size rnk = this%receivers%at(i) + if (this%enable_monitor) then + write (this%imon, '(4x,a,i0)') "sending to process: ", rnk + end if call this%message_builder%create_body_snd( & rnk, stage, headers(1:hdr_rcv_cnt(i), i), body_snd_t(i)) call MPI_Type_size(body_snd_t(i), msg_size, ierr) @@ -321,13 +351,9 @@ subroutine mr_route_active(this, stage) end if if (this%enable_monitor) then - if (msg_size > 0) then - write (this%imon, '(4x,a,i0)') "sending to process: ", rnk - write (this%imon, '(6x,a,i0)') "message body size: ", msg_size - else - write (this%imon, '(4x,a,i0)') "no receiving from process: ", rnk - end if + write (this%imon, '(6x,a,i0)') "message body size: ", msg_size end if + call flush (this%imon) end do ! wait for exchange of all messages diff --git a/src/Distributed/VirtualDataContainer.f90 b/src/Distributed/VirtualDataContainer.f90 index c93a7ec27f1..d9b9711a500 100644 --- a/src/Distributed/VirtualDataContainer.f90 +++ b/src/Distributed/VirtualDataContainer.f90 @@ -57,6 +57,7 @@ module VirtualDataContainerModule procedure :: set_orig_rank => vdc_set_orig_rank procedure :: get_send_items => vdc_get_send_items procedure :: get_recv_items => vdc_get_recv_items + procedure :: print_items ! protected procedure :: create_field ! private @@ -250,6 +251,23 @@ subroutine get_items_for_stage(this, stage, virtual_items) end subroutine get_items_for_stage + subroutine print_items(this, imon, items) + class(VirtualDataContainerType) :: this + integer(I4B) :: imon + type(STLVecInt) :: items + ! local + integer(I4B) :: i + class(VirtualDataType), pointer :: vdi + + write (imon, *) "=====> items" + do i = 1, items%size + vdi => get_virtual_data_from_list(this%virtual_data_list, items%at(i)) + write (imon, *) vdi%var_name, ":", vdi%mem_path + end do + write (imon, *) "<===== items" + + end subroutine print_items + !> @brief Get virtual memory path for a certain variable !< function vdc_get_vrt_mem_path(this, var_name, subcomp_name) result(vrt_path) @@ -353,12 +371,12 @@ function VDC_TYPE_TO_STR(cntr_type) result(cntr_str) character(len=24) :: cntr_str if (cntr_type == VDC_UNKNOWN_TYPE) then; cntr_str = "unknown" - else if (VDC_GWFMODEL_TYPE == 1) then; cntr_str = "GWF Model" - else if (VDC_GWTMODEL_TYPE == 2) then; cntr_str = "GWT Model" - else if (VDC_GWFEXG_TYPE == 3) then; cntr_str = "GWF Exchange" - else if (VDC_GWTEXG_TYPE == 4) then; cntr_str = "GWT Exchange" - else if (VDC_GWFMVR_TYPE == 5) then; cntr_str = "GWF Mover" - else if (VDC_GWTMVT_TYPE == 6) then; cntr_str = "GWT Mover" + else if (cntr_type == VDC_GWFMODEL_TYPE) then; cntr_str = "GWF Model" + else if (cntr_type == VDC_GWTMODEL_TYPE) then; cntr_str = "GWT Model" + else if (cntr_type == VDC_GWFEXG_TYPE) then; cntr_str = "GWF Exchange" + else if (cntr_type == VDC_GWTEXG_TYPE) then; cntr_str = "GWT Exchange" + else if (cntr_type == VDC_GWFMVR_TYPE) then; cntr_str = "GWF Mover" + else if (cntr_type == VDC_GWTMVT_TYPE) then; cntr_str = "GWT Mover" else; cntr_str = "Undefined" end if diff --git a/src/Distributed/VirtualDataManager.f90 b/src/Distributed/VirtualDataManager.f90 index 034851f8f31..c702ff05172 100644 --- a/src/Distributed/VirtualDataManager.f90 +++ b/src/Distributed/VirtualDataManager.f90 @@ -12,6 +12,8 @@ module VirtualDataManagerModule use NumericalModelModule, only: NumericalModelType, GetNumericalModelFromList use NumericalExchangeModule, only: NumericalExchangeType, & GetNumericalExchangeFromList + use DisConnExchangeModule, only: DisConnExchangeType, & + GetDisConnExchangeFromList use SpatialModelConnectionModule, only: SpatialModelConnectionType, & get_smc_from_list implicit none @@ -26,6 +28,7 @@ module VirtualDataManagerModule procedure :: create => vds_create procedure :: init => vds_init procedure :: add_solution => vds_add_solution + procedure :: reduce_halo => vds_reduce_halo procedure :: synchronize => vds_synchronize procedure :: synchronize_sln => vds_synchronize_sln procedure :: destroy @@ -80,7 +83,7 @@ subroutine vds_add_solution(this, num_sol) integer(I4B) :: i, im, ix, ihm, ihx class(VirtualSolutionType), pointer :: virt_sol class(NumericalModelType), pointer :: num_mod - class(NumericalExchangeType), pointer :: num_exg + class(DisConnExchangeType), pointer :: exg class(SpatialModelConnectionType), pointer :: conn integer(I4B) :: model_id, exg_id type(STLVecInt) :: model_ids, exchange_ids @@ -96,23 +99,23 @@ subroutine vds_add_solution(this, num_sol) this%solution_ids(this%nr_solutions) = num_sol%id virt_sol%solution_id = num_sol%id - ! let's start with adding all models and exchanges from the global list - ! (we will make them inactive when they are remote and not part of - ! our halo) + ! 1) adding all local models from the solution do im = 1, num_sol%modellist%Count() num_mod => GetNumericalModelFromList(num_sol%modellist, im) call model_ids%push_back(num_mod%id) end do - ! loop over exchanges in solution and get connections + ! 2) adding all local exchanges + do ix = 1, num_sol%exchangelist%Count() + exg => GetDisConnExchangeFromList(num_sol%exchangelist, ix) + if (.not. associated(exg)) cycle ! interface model is handled separately + call exchange_ids%push_back_unique(exg%id) + end do + + ! 3) add halo models and exchanges from interface models do ix = 1, num_sol%exchangelist%Count() conn => get_smc_from_list(num_sol%exchangelist, ix) - if (.not. associated(conn)) then - ! it's a classic exchange, add it - num_exg => GetNumericalExchangeFromList(num_sol%exchangelist, ix) - call exchange_ids%push_back_unique(num_exg%id) - cycle - end if + if (.not. associated(conn)) cycle ! it's an interface model based exchanged, get ! halo models and halo exchanges from connection @@ -124,7 +127,6 @@ subroutine vds_add_solution(this, num_sol) exg_id = conn%halo_exchanges%at(ihx) call exchange_ids%push_back_unique(exg_id) end do - end do allocate (virt_sol%models(model_ids%size)) @@ -146,11 +148,22 @@ subroutine vds_add_solution(this, num_sol) end subroutine vds_add_solution + !> @brief Reduce the halo for all solutions. This will + !< activate the mapping tables in the virtual data items. + subroutine vds_reduce_halo(this) + class(VirtualDataManagerType) :: this + + ! merge the interface maps over this process + + ! assign reduced maps to virtual data containers + + end subroutine vds_reduce_halo + !> @brief Synchronize the full virtual data store for this stage !< subroutine vds_synchronize(this, stage) - class(VirtualDataManagerType) :: this - integer(I4B) :: stage + class(VirtualDataManagerType) :: this !< this vdm + integer(I4B) :: stage !< the stage to sync call this%prepare_all(stage) call this%link_all(stage) diff --git a/src/Distributed/VirtualExchange.f90 b/src/Distributed/VirtualExchange.f90 index aff8fe29f93..f4d40c4cd54 100644 --- a/src/Distributed/VirtualExchange.f90 +++ b/src/Distributed/VirtualExchange.f90 @@ -14,6 +14,54 @@ module VirtualExchangeModule public :: get_virtual_exchange_from_list private :: cast_as_virtual_exchange + !> The Virtual Exchange is based on two Virtual Models + !! and is therefore not always strictly local or remote. + !! We have to consider three different cases: + !! + !! 1) both virtual models are local + !! + !! RECV: In this case this virtual data container will have + !! no data items to receive from other processes. + !! SEND: Whenever it is called to send its virtual data items + !! to other processes, it simply sends everything. + !! + !! 2) one model is local, one model is remote + !! + !! Consequently, there is another exchange which + !! has the reverse, we call this our _dual_ exchange. + !! + !! RECV: The sender is our dual exchange, and we have all data + !! except its list of reduced model node numbers, either + !! this%nodem1 or this%nodem2. We receive the missing + !! array. Receiving from a sender that is not the dual + !! exchange cannot occur. + !! + !! SEND: here we have to consider two cases + !! a) The receiver is our dual exchange, we return the favor + !! and send the list of model node numbers that is present + !! on this process, this + !! would be either this%nodem1 or this%nodem2 + !! b) The receiver is not the dual exchange. And here we will + !! send everything. + !! + !! 3) both models are remote + !! + !! RECV: we will receive everything. In case the source + !! exchange is fully local, i.e. type 1) above, we get + !! all the data at the first attempt. Otherwise, it will + !! take a second attempt before all the data is in. + !! (To allow for two attempts, the nodem1 and nodem2 + !! arrays are registered to be synchronized at two + !! consecutive stages) + !! + !! SEND: nothing to be sent. + !! + !! + !! This behavior is different from the general VirtualDataContainer, + !! so the get_send_items and get_recv_items subroutines are + !! overridden accordingly. + !! Additionally, for case 2) the container will have a mix of + !< local and remote virtual data items. type, public, extends(VirtualDataContainerType) :: VirtualExchangeType class(VirtualModelType), pointer :: v_model1 => null() class(VirtualModelType), pointer :: v_model2 => null() @@ -56,10 +104,9 @@ subroutine vx_create(this, name, exg_id, m1_id, m2_id) this%v_model1 => get_virtual_model(m1_id) this%v_model2 => get_virtual_model(m2_id) - ! - if both models are local, is_local = true - ! - if both are remote, is_local = false - ! - if only one of them is remote, is_local = true and only the - ! remote nodem1/2 array will get its property is_local = false + ! 1) both models local: is_local = true + ! 2) only one of them: is_local = true + ! 3) both models remote: is_local = false is_local = this%v_model1%is_local .or. this%v_model2%is_local call this%VirtualDataContainerType%vdc_create(name, exg_id, is_local) @@ -74,9 +121,11 @@ subroutine create_virtual_fields(this) logical(LGP) :: is_nodem1_local logical(LGP) :: is_nodem2_local - ! exchanges can be hybrid with both local and remote fields - is_nodem1_local = this%is_local .and. this%v_model1%is_local - is_nodem2_local = this%is_local .and. this%v_model2%is_local + ! exchanges can be hybrid with both local and remote + ! fields, nodem1/2 array only local when corresponding + ! model sits on the same process + is_nodem1_local = this%v_model1%is_local + is_nodem2_local = this%v_model2%is_local allocate (this%nexg) call this%create_field(this%nexg%to_base(), 'NEXG', '') @@ -109,22 +158,33 @@ subroutine vx_prepare_stage(this, stage) if (stage == STG_AFTER_EXG_DF) then - call this%map(this%nexg%to_base(), (/STG_AFTER_EXG_DF/), MAP_ALL_TYPE) - call this%map(this%naux%to_base(), (/STG_AFTER_EXG_DF/), MAP_ALL_TYPE) - call this%map(this%ianglex%to_base(), (/STG_AFTER_EXG_DF/), MAP_ALL_TYPE) + call this%map(this%nexg%to_base(), & + (/STG_AFTER_EXG_DF/), MAP_ALL_TYPE) + call this%map(this%naux%to_base(), & + (/STG_AFTER_EXG_DF/), MAP_ALL_TYPE) + call this%map(this%ianglex%to_base(), & + (/STG_AFTER_EXG_DF/), MAP_ALL_TYPE) - else if (stage == STG_BEFORE_DF) then + else if (stage == STG_AFTER_CON_CR) then nexg = this%nexg%get() naux = this%naux%get() - call this%map(this%nodem1%to_base(), nexg, (/STG_BEFORE_DF/), MAP_ALL_TYPE) - call this%map(this%nodem2%to_base(), nexg, (/STG_BEFORE_DF/), MAP_ALL_TYPE) - call this%map(this%ihc%to_base(), nexg, (/STG_BEFORE_DF/), MAP_ALL_TYPE) - call this%map(this%cl1%to_base(), nexg, (/STG_BEFORE_DF/), MAP_ALL_TYPE) - call this%map(this%cl2%to_base(), nexg, (/STG_BEFORE_DF/), MAP_ALL_TYPE) - call this%map(this%hwva%to_base(), nexg, (/STG_BEFORE_DF/), MAP_ALL_TYPE) + call this%map(this%nodem1%to_base(), nexg, & + (/STG_AFTER_CON_CR, STG_BEFORE_CON_DF/), & + MAP_ALL_TYPE) + call this%map(this%nodem2%to_base(), nexg, & + (/STG_AFTER_CON_CR, STG_BEFORE_CON_DF/), & + MAP_ALL_TYPE) + call this%map(this%ihc%to_base(), nexg, & + (/STG_AFTER_CON_CR/), MAP_ALL_TYPE) + call this%map(this%cl1%to_base(), nexg, & + (/STG_AFTER_CON_CR/), MAP_ALL_TYPE) + call this%map(this%cl2%to_base(), nexg, & + (/STG_AFTER_CON_CR/), MAP_ALL_TYPE) + call this%map(this%hwva%to_base(), nexg, & + (/STG_AFTER_CON_CR/), MAP_ALL_TYPE) call this%map(this%auxvar%to_base(), naux, nexg, & - (/STG_BEFORE_DF/), MAP_ALL_TYPE) + (/STG_AFTER_CON_CR/), MAP_ALL_TYPE) end if @@ -136,35 +196,31 @@ subroutine vx_get_recv_items(this, stage, rank, virtual_items) integer(I4B) :: rank type(STLVecInt) :: virtual_items ! local - integer(I4B) :: i, nodem1_idx, nodem2_idx - class(*), pointer :: virtual_data_item - - nodem1_idx = -1 - nodem2_idx = -1 - do i = 1, this%virtual_data_list%Count() - virtual_data_item => this%virtual_data_list%GetItem(i) - if (associated(virtual_data_item, this%nodem1)) then - nodem1_idx = i - end if - if (associated(virtual_data_item, this%nodem2)) then - nodem2_idx = i - end if - end do - - if (.not. this%v_model1%is_local .and. .not. this%v_model2%is_local) then - ! receive all using base - call this%VirtualDataContainerType%get_recv_items(stage, rank, & - virtual_items) - else if (.not. this%v_model2%is_local) then - ! receive for model2 + integer(I4B) :: nodem1_idx, nodem2_idx + class(*), pointer :: vdi + + vdi => this%nodem1 + nodem1_idx = this%virtual_data_list%GetIndex(vdi) + vdi => this%nodem2 + nodem2_idx = this%virtual_data_list%GetIndex(vdi) + + if (this%v_model1%is_local .and. & + this%v_model2%orig_rank == rank) then + ! this is our dual exchange on the other rank, + ! only receive nodem2 if (this%nodem2%check_stage(stage)) then call virtual_items%push_back(nodem2_idx) end if - else if (.not. this%v_model1%is_local) then - ! receive for model1 + else if (this%v_model2%is_local .and. & + this%v_model1%orig_rank == rank) then + ! the reverse case... if (this%nodem1%check_stage(stage)) then call virtual_items%push_back(nodem1_idx) end if + else + ! receive all using base + call this%VirtualDataContainerType%get_recv_items(stage, rank, & + virtual_items) end if end subroutine vx_get_recv_items @@ -175,35 +231,30 @@ subroutine vx_get_send_items(this, stage, rank, virtual_items) integer(I4B) :: rank type(STLVecInt) :: virtual_items ! local - integer(I4B) :: i, nodem1_idx, nodem2_idx - class(*), pointer :: virtual_data_item - - nodem1_idx = -1 - nodem2_idx = -1 - do i = 1, this%virtual_data_list%Count() - virtual_data_item => this%virtual_data_list%GetItem(i) - if (associated(virtual_data_item, this%nodem1)) then - nodem1_idx = i - end if - if (associated(virtual_data_item, this%nodem2)) then - nodem2_idx = i - end if - end do - - if (this%v_model1%is_local .and. this%v_model2%is_local) then - ! send all using base - call this%VirtualDataContainerType%get_send_items(stage, rank, & - virtual_items) - else if (this%v_model1%is_local) then - ! send for model1 + integer(I4B) :: nodem1_idx, nodem2_idx + class(*), pointer :: vdi + + vdi => this%nodem1 + nodem1_idx = this%virtual_data_list%GetIndex(vdi) + vdi => this%nodem2 + nodem2_idx = this%virtual_data_list%GetIndex(vdi) + if (this%v_model1%is_local .and. & + this%v_model2%orig_rank == rank) then + ! this is our dual exchange on the other rank, + ! only send nodem1 if (this%nodem1%check_stage(stage)) then call virtual_items%push_back(nodem1_idx) end if - else if (this%v_model2%is_local) then - ! send for model2 + else if (this%v_model2%is_local .and. & + this%v_model1%orig_rank == rank) then + ! the reverse case... if (this%nodem2%check_stage(stage)) then call virtual_items%push_back(nodem2_idx) end if + else + ! send all of it + call this%VirtualDataContainerType%get_send_items(stage, rank, & + virtual_items) end if end subroutine vx_get_send_items diff --git a/src/Distributed/VirtualGwfModel.f90 b/src/Distributed/VirtualGwfModel.f90 index a251b1936dc..c1c1648820a 100644 --- a/src/Distributed/VirtualGwfModel.f90 +++ b/src/Distributed/VirtualGwfModel.f90 @@ -168,7 +168,7 @@ subroutine vgwf_prepare_stage(this, stage) (/STG_BEFORE_AR/), MAP_NODE_TYPE) else call this%map(this%npf_wetdry%to_base(), 0, & - (/STG_BEFORE_AR/), MAP_NODE_TYPE) + (/STG_NEVER/), MAP_NODE_TYPE) end if end if diff --git a/src/Distributed/VirtualModel.f90 b/src/Distributed/VirtualModel.f90 index ce15f67e455..3ac84b20918 100644 --- a/src/Distributed/VirtualModel.f90 +++ b/src/Distributed/VirtualModel.f90 @@ -177,45 +177,45 @@ subroutine vm_prepare_stage(this, stage) call this%map(this%dis_nodeuser%to_base(), 0, & (/STG_NEVER/), MAP_ALL_TYPE) end if - else if (stage == STG_BEFORE_DF) then + else if (stage == STG_BEFORE_CON_DF) then nodes = this%dis_nodes%get() nja = this%dis_nja%get() njas = this%dis_njas%get() ! DIS call this%map(this%dis_xorigin%to_base(), & - (/STG_BEFORE_DF/), MAP_ALL_TYPE) + (/STG_BEFORE_CON_DF/), MAP_ALL_TYPE) call this%map(this%dis_yorigin%to_base(), & - (/STG_BEFORE_DF/), MAP_ALL_TYPE) + (/STG_BEFORE_CON_DF/), MAP_ALL_TYPE) call this%map(this%dis_angrot%to_base(), & - (/STG_BEFORE_DF/), MAP_ALL_TYPE) + (/STG_BEFORE_CON_DF/), MAP_ALL_TYPE) call this%map(this%dis_xc%to_base(), nodes, & - (/STG_BEFORE_DF/), MAP_ALL_TYPE) + (/STG_BEFORE_CON_DF/), MAP_ALL_TYPE) call this%map(this%dis_yc%to_base(), nodes, & - (/STG_BEFORE_DF/), MAP_ALL_TYPE) + (/STG_BEFORE_CON_DF/), MAP_ALL_TYPE) call this%map(this%dis_top%to_base(), nodes, & - (/STG_BEFORE_DF/), MAP_ALL_TYPE) + (/STG_BEFORE_CON_DF/), MAP_ALL_TYPE) call this%map(this%dis_bot%to_base(), nodes, & - (/STG_BEFORE_DF/), MAP_ALL_TYPE) + (/STG_BEFORE_CON_DF/), MAP_ALL_TYPE) call this%map(this%dis_area%to_base(), nodes, & - (/STG_BEFORE_DF/), MAP_ALL_TYPE) + (/STG_BEFORE_CON_DF/), MAP_ALL_TYPE) ! CON call this%map(this%con_ia%to_base(), nodes + 1, & - (/STG_BEFORE_DF/), MAP_ALL_TYPE) + (/STG_BEFORE_CON_DF/), MAP_ALL_TYPE) call this%map(this%con_ja%to_base(), nja, & - (/STG_BEFORE_DF/), MAP_ALL_TYPE) + (/STG_BEFORE_CON_DF/), MAP_ALL_TYPE) call this%map(this%con_jas%to_base(), nja, & - (/STG_BEFORE_DF/), MAP_ALL_TYPE) + (/STG_BEFORE_CON_DF/), MAP_ALL_TYPE) call this%map(this%con_ihc%to_base(), njas, & - (/STG_BEFORE_DF/), MAP_ALL_TYPE) + (/STG_BEFORE_CON_DF/), MAP_ALL_TYPE) call this%map(this%con_hwva%to_base(), njas, & - (/STG_BEFORE_DF/), MAP_ALL_TYPE) + (/STG_BEFORE_CON_DF/), MAP_ALL_TYPE) call this%map(this%con_cl1%to_base(), njas, & - (/STG_BEFORE_DF/), MAP_ALL_TYPE) + (/STG_BEFORE_CON_DF/), MAP_ALL_TYPE) call this%map(this%con_cl2%to_base(), njas, & - (/STG_BEFORE_DF/), MAP_ALL_TYPE) + (/STG_BEFORE_CON_DF/), MAP_ALL_TYPE) call this%map(this%con_anglex%to_base(), njas, & - (/STG_BEFORE_DF/), MAP_ALL_TYPE) + (/STG_BEFORE_CON_DF/), MAP_ALL_TYPE) end if end subroutine vm_prepare_stage diff --git a/src/Model/Connection/SpatialModelConnection.f90 b/src/Model/Connection/SpatialModelConnection.f90 index 82748266198..859b5f104b8 100644 --- a/src/Model/Connection/SpatialModelConnection.f90 +++ b/src/Model/Connection/SpatialModelConnection.f90 @@ -186,16 +186,9 @@ recursive subroutine addModelNeighbors(this, model_id, & ! check if masked if (neighbor_id == model_mask) cycle - - if (.not. nbr_models%contains(neighbor_id)) then - call nbr_models%push_back(neighbor_id) - end if - if (.not. this%halo_models%contains(neighbor_id)) then - call this%halo_models%push_back(neighbor_id) - end if - if (.not. this%halo_exchanges%contains(v_exg%id)) then - call this%halo_exchanges%push_back(v_exg%id) - end if + call nbr_models%push_back_unique(neighbor_id) + call this%halo_models%push_back_unique(neighbor_id) + call this%halo_exchanges%push_back_unique(v_exg%id) end if end do @@ -218,8 +211,9 @@ recursive subroutine addModelNeighbors(this, model_id, & end subroutine addModelNeighbors - !> @brief Define this connection, mostly sets up the grid - !< connection, allocates arrays, and links x,rhs, and ibound + !> @brief Define this connection, this is where the + !! discretization (DISU) for the interface model is + !< created! subroutine spatialcon_df(this) class(SpatialModelConnectionType) :: this !< this connection ! local diff --git a/src/RunControl.f90 b/src/RunControl.f90 index 52fd3854bc7..9c2dc724774 100644 --- a/src/RunControl.f90 +++ b/src/RunControl.f90 @@ -13,20 +13,17 @@ module RunControlModule public :: create_seq_run_control type, public :: RunControlType - class(VirtualDataManagerType), pointer :: virtual_data_store !< contains globally accessible data, timely synchronized - !! by direct linking (local) or message passing (remote) - type(MapperType) :: mapper !< a 'mapper' for filling the interface models: this needs a better name/place + class(VirtualDataManagerType), pointer :: virtual_data_mgr !< syncs globally accessible data, timely, by + !! linking (local) or message passing (remote) + type(MapperType) :: mapper !< a 'mapper' for copying data between two memory addresses contains procedure :: start => ctrl_start procedure :: at_stage => ctrl_at_stage procedure :: finish => ctrl_finish ! private procedure, private :: init_handler - procedure, private :: after_mdl_df_handler - procedure, private :: before_df_handler - procedure, private :: after_df_handler - procedure, private :: before_ar_handler - procedure, private :: after_ar_handler + procedure, private :: before_con_df_handler + procedure, private :: after_con_df_handler procedure, private :: destroy end type RunControlType @@ -42,7 +39,7 @@ end function create_seq_run_control subroutine ctrl_start(this) class(RunControlType) :: this - allocate (this%virtual_data_store) + allocate (this%virtual_data_mgr) end subroutine ctrl_start @@ -72,19 +69,13 @@ subroutine ctrl_at_stage(this, stage) if (stage == STG_INIT) then call this%init_handler() - else if (stage == STG_AFTER_MDL_DF) then - call this%after_mdl_df_handler() - else if (stage == STG_BEFORE_DF) then - call this%before_df_handler() - else if (stage == STG_AFTER_DF) then - call this%after_df_handler() - else if (stage == STG_BEFORE_AR) then - call this%before_ar_handler() - else if (stage == STG_AFTER_AR) then - call this%after_ar_handler() + else if (stage == STG_BEFORE_CON_DF) then + call this%before_con_df_handler() + else if (stage == STG_AFTER_CON_DF) then + call this%after_con_df_handler() end if - call this%virtual_data_store%synchronize(stage) + call this%virtual_data_mgr%synchronize(stage) call this%mapper%scatter(0, stage) end subroutine ctrl_at_stage @@ -93,56 +84,58 @@ subroutine init_handler(this) use SimVariablesModule, only: simulation_mode class(RunControlType), target :: this - call this%virtual_data_store%create(simulation_mode) - call this%virtual_data_store%init() + call this%virtual_data_mgr%create(simulation_mode) + call this%virtual_data_mgr%init() call this%mapper%init() end subroutine init_handler - subroutine after_mdl_df_handler(this) - class(RunControlType) :: this - end subroutine after_mdl_df_handler - - subroutine before_df_handler(this) + !> @brief Actions before defining the connections + !! + !! Set up the virtual data manager: + !! The models and exchanges in the halo for this interface + !! have been determined. Add them to the virtual data manager + !! for synchronization. (After which the interface model + !< grids can be constructed) + subroutine before_con_df_handler(this) class(RunControlType), target :: this ! local integer(I4B) :: i class(*), pointer :: obj_ptr class(NumericalSolutionType), pointer :: sol - ! Interface models are created now and we know which - ! remote models and exchanges are required in the - ! virtual solution. Also set the synchronization handler - ! to the numerical solutions. + ! Add (halo) models and exchanges to the virtual + ! solutions. Set the synchronization handler + ! in the numerical solution. do i = 1, basesolutionlist%Count() obj_ptr => basesolutionlist%GetItem(i) select type (obj_ptr) class is (NumericalSolutionType) sol => obj_ptr - call this%virtual_data_store%add_solution(sol) + call this%virtual_data_mgr%add_solution(sol) sol%synchronize => rc_solution_sync sol%synchronize_ctx => this end select end do + ! The remote data fields in exchanges need to + ! be copied in from the virtual exchanges call this%mapper%add_exchange_vars() - end subroutine before_df_handler + end subroutine before_con_df_handler - subroutine after_df_handler(this) + !> @brief Actions after definining connections + !< + subroutine after_con_df_handler(this) class(RunControlType) :: this - call this%mapper%add_interface_vars() - - end subroutine after_df_handler + ! Reduce the halo + call this%virtual_data_mgr%reduce_halo() - subroutine before_ar_handler(this) - class(RunControlType) :: this - end subroutine before_ar_handler + ! Add variables in interface models to the mapper + call this%mapper%add_interface_vars() - subroutine after_ar_handler(this) - class(RunControlType) :: this - end subroutine after_ar_handler + end subroutine after_con_df_handler !> @brief Synchronizes from within numerical solution (delegate) !< @@ -154,7 +147,7 @@ subroutine rc_solution_sync(num_sol, stage, ctx) select type (ctx) class is (RunControlType) - call ctx%virtual_data_store%synchronize_sln(num_sol%id, stage) + call ctx%virtual_data_mgr%synchronize_sln(num_sol%id, stage) call ctx%mapper%scatter(num_sol%id, stage) end select @@ -163,8 +156,8 @@ end subroutine rc_solution_sync subroutine destroy(this) class(RunControlType) :: this - call this%virtual_data_store%destroy() - deallocate (this%virtual_data_store) + call this%virtual_data_mgr%destroy() + deallocate (this%virtual_data_mgr) end subroutine destroy diff --git a/src/Solution/PETSc/PetscConvergence.F90 b/src/Solution/PETSc/PetscConvergence.F90 index cd9a6e6d388..02181ea757c 100644 --- a/src/Solution/PETSc/PetscConvergence.F90 +++ b/src/Solution/PETSc/PetscConvergence.F90 @@ -8,6 +8,7 @@ module PetscConvergenceModule public :: petsc_check_convergence public :: petsc_add_context + public :: petsc_remove_context type, public :: PetscContextType Vec :: x_old @@ -20,20 +21,35 @@ module PetscConvergenceModule contains !> @brief Add a context to the static list. The - !< generated idx can then be used as a handle when - !< calling 'KSPSetConvergenceTest' + !! generated idx can then be used as a handle when + !! calling 'KSPSetConvergenceTest'. Make sure to remove + !< the context from this global list when done. subroutine petsc_add_context(ctx, idx) class(PetscContextType), pointer, intent(in) :: ctx integer(I4B), intent(out) :: idx ! local - class(*), pointer :: ctx_ptr + class(*), pointer :: obj_ptr - ctx_ptr => ctx - call ctx_list%Add(ctx_ptr) + obj_ptr => ctx + call ctx_list%Add(obj_ptr) idx = ctx_list%Count() end subroutine petsc_add_context + !> @brief This will clear the list with context pointers + !< + subroutine petsc_remove_context(ctx) + class(PetscContextType), pointer, intent(in) :: ctx + ! local + integer(I4B) :: idx + class(*), pointer :: obj_ptr + + obj_ptr => ctx + idx = ctx_list%GetIndex(obj_ptr) + call ctx_list%RemoveNode(idx, .false.) + + end subroutine petsc_remove_context + !> @brief Routine to check the convergence. This is called !< from within PETSc. subroutine petsc_check_convergence(ksp, n, rnorm, flag, ctx_id, ierr) diff --git a/src/Solution/ParallelSolution.f90 b/src/Solution/ParallelSolution.f90 index 4ec3f983543..87974464dc9 100644 --- a/src/Solution/ParallelSolution.f90 +++ b/src/Solution/ParallelSolution.f90 @@ -30,7 +30,7 @@ function par_has_converged(this, max_dvc) result(has_converged) has_converged = .false. global_max_dvc = huge(0.0) call MPI_Allreduce(max_dvc, global_max_dvc, 1, MPI_DOUBLE_PRECISION, & - MPI_MIN, MF6_COMM_WORLD, ierr) + MPI_MAX, MF6_COMM_WORLD, ierr) if (global_max_dvc <= this%dvclose) then has_converged = .true. end if diff --git a/src/Utilities/List.f90 b/src/Utilities/List.f90 index dbd807b22ec..1273359f0ce 100644 --- a/src/Utilities/List.f90 +++ b/src/Utilities/List.f90 @@ -3,6 +3,7 @@ module ListModule use KindModule, only: DP, I4B use ConstantsModule, only: LINELENGTH use GenericUtilitiesModule, only: sim_message, stop_with_error + implicit none private public :: ListType, ListNodeType, isEqualIface, arePointersEqual @@ -22,6 +23,7 @@ module ListModule procedure, public :: Count procedure, public :: ContainsObject procedure, public :: DeallocateBackward + procedure, public :: GetIndex procedure, public :: GetNextItem procedure, public :: GetPreviousItem generic, public :: GetItem => get_item_by_index, get_current_item @@ -66,7 +68,6 @@ function isEqualIface(obj1, obj2) result(isEqual) ! -- Public type-bound procedures for ListType subroutine Add(this, objptr) - implicit none ! -- dummy variables class(ListType), intent(inout) :: this class(*), pointer, intent(inout) :: objptr @@ -94,7 +95,6 @@ subroutine Clear(this, destroy) ! ! SPECIFICATIONS: ! -------------------------------------------------------------------------- - implicit none ! -- dummy variables class(ListType) :: this logical, intent(in), optional :: destroy @@ -142,7 +142,6 @@ function Count(this) ! ! SPECIFICATIONS: ! -------------------------------------------------------------------------- - implicit none ! -- return integer(I4B) :: Count ! -- dummy variables @@ -191,7 +190,6 @@ subroutine DeallocateBackward(this, fromNode) ! ! SPECIFICATIONS: ! -------------------------------------------------------------------------- - implicit none ! -- dummy class(ListType), target, intent(inout) :: this type(ListNodeType), pointer, intent(inout) :: fromNode @@ -221,8 +219,26 @@ subroutine DeallocateBackward(this, fromNode) return end subroutine DeallocateBackward + function GetIndex(this, obj) result(idx) + class(ListType), target, intent(inout) :: this + class(*), pointer :: obj + integer(I4B) :: idx + ! local + integer(I4B) :: i + class(*), pointer :: obj_in_list + + idx = -1 + do i = 1, this%Count() + obj_in_list => this%GetItem(i) + if (associated(obj, obj_in_list)) then + idx = i + exit + end if + end do + + end function GetIndex + function GetNextItem(this) result(resultobj) - implicit none class(ListType), target, intent(inout) :: this ! result class(*), pointer :: resultobj @@ -233,7 +249,6 @@ function GetNextItem(this) result(resultobj) end function GetNextItem function GetPreviousItem(this) result(resultobj) - implicit none class(ListType), target, intent(inout) :: this ! result class(*), pointer :: resultobj @@ -244,7 +259,6 @@ function GetPreviousItem(this) result(resultobj) end function GetPreviousItem subroutine InsertAfter(this, objptr, indx) - implicit none ! -- dummy class(ListType), intent(inout) :: this class(*), pointer, intent(inout) :: objptr @@ -282,7 +296,6 @@ end subroutine InsertAfter subroutine InsertBefore(this, objptr, targetNode) ! Insert an object into the list in front of a target node - implicit none ! -- dummy class(ListType), intent(inout) :: this class(*), pointer, intent(inout) :: objptr @@ -316,7 +329,6 @@ subroutine InsertBefore(this, objptr, targetNode) end subroutine InsertBefore subroutine Next(this) - implicit none class(ListType), target, intent(inout) :: this ! if (this%currentNodeIndex == 0) then @@ -340,7 +352,6 @@ subroutine Next(this) end subroutine Next subroutine Previous(this) - implicit none class(ListType), target, intent(inout) :: this ! if (this%currentNodeIndex <= 1) then @@ -353,7 +364,6 @@ subroutine Previous(this) end subroutine Previous subroutine Reset(this) - implicit none class(ListType), target, intent(inout) :: this ! this%currentNode => null() @@ -362,7 +372,6 @@ subroutine Reset(this) end subroutine Reset subroutine remove_node_by_index(this, i, destroyValue) - implicit none ! -- dummy class(ListType), intent(inout) :: this integer(I4B), intent(in) :: i @@ -380,7 +389,6 @@ subroutine remove_node_by_index(this, i, destroyValue) end subroutine remove_node_by_index subroutine remove_this_node(this, node, destroyValue) - implicit none ! -- dummy class(ListType), intent(inout) :: this type(ListNodeType), pointer, intent(inout) :: node @@ -431,7 +439,6 @@ end subroutine remove_this_node ! -- Private type-bound procedures for ListType function get_current_item(this) result(resultobj) - implicit none class(ListType), target, intent(inout) :: this ! result class(*), pointer :: resultobj @@ -451,7 +458,6 @@ function get_item_by_index(this, indx) result(resultobj) ! ! SPECIFICATIONS: ! -------------------------------------------------------------------------- - implicit none ! -- dummy class(ListType), intent(inout) :: this integer(I4B), intent(in) :: indx @@ -516,7 +522,6 @@ function get_node_by_index(this, indx) result(resultnode) ! ! SPECIFICATIONS: ! -------------------------------------------------------------------------- - implicit none ! -- dummy class(ListType), intent(inout) :: this integer(I4B), intent(in) :: indx @@ -580,7 +585,6 @@ function GetItem(this) result(valueObject) ! ! SPECIFICATIONS: ! ------------------------------------------------------------------------ - implicit none class(ListNodeType), intent(inout) :: this class(*), pointer :: valueObject ! @@ -595,7 +599,6 @@ subroutine DeallocValue(this, destroy) ! ! SPECIFICATIONS: ! ------------------------------------------------------------------------ - implicit none class(ListNodeType), intent(inout) :: this logical, intent(in), optional :: destroy ! diff --git a/src/Utilities/SimStages.f90 b/src/Utilities/SimStages.f90 index 15a11f8c104..340c5d204ea 100644 --- a/src/Utilities/SimStages.f90 +++ b/src/Utilities/SimStages.f90 @@ -10,14 +10,15 @@ module SimStagesModule integer(I4B), public, parameter :: STG_INIT = 1 integer(I4B), public, parameter :: STG_AFTER_MDL_DF = 2 integer(I4B), public, parameter :: STG_AFTER_EXG_DF = 3 - integer(I4B), public, parameter :: STG_BEFORE_DF = 4 - integer(I4B), public, parameter :: STG_AFTER_DF = 5 - integer(I4B), public, parameter :: STG_BEFORE_AC = 6 - integer(I4B), public, parameter :: STG_BEFORE_AR = 7 - integer(I4B), public, parameter :: STG_AFTER_AR = 8 - integer(I4B), public, parameter :: STG_BEFORE_AD = 9 - integer(I4B), public, parameter :: STG_BEFORE_CF = 10 - integer(I4B), public, parameter :: STG_BEFORE_FC = 11 + integer(I4B), public, parameter :: STG_AFTER_CON_CR = 4 + integer(I4B), public, parameter :: STG_BEFORE_CON_DF = 5 + integer(I4B), public, parameter :: STG_AFTER_CON_DF = 6 + integer(I4B), public, parameter :: STG_BEFORE_AC = 7 + integer(I4B), public, parameter :: STG_BEFORE_AR = 8 + integer(I4B), public, parameter :: STG_AFTER_AR = 9 + integer(I4B), public, parameter :: STG_BEFORE_AD = 10 + integer(I4B), public, parameter :: STG_BEFORE_CF = 11 + integer(I4B), public, parameter :: STG_BEFORE_FC = 12 contains @@ -31,8 +32,9 @@ function STG_TO_STR(stage) result(stg_str) else if (stage == STG_INIT) then; stg_str = "STG_INIT" else if (stage == STG_AFTER_MDL_DF) then; stg_str = "STG_AFTER_MDL_DF" else if (stage == STG_AFTER_EXG_DF) then; stg_str = "STG_AFTER_EXG_DF" - else if (stage == STG_BEFORE_DF) then; stg_str = "STG_BEFORE_DF" - else if (stage == STG_AFTER_DF) then; stg_str = "STG_AFTER_DF" + else if (stage == STG_AFTER_CON_CR) then; stg_str = "STG_AFTER_CON_CR" + else if (stage == STG_BEFORE_CON_DF) then; stg_str = "STG_BEFORE_CON_DF" + else if (stage == STG_AFTER_CON_DF) then; stg_str = "STG_AFTER_CON_DF" else if (stage == STG_BEFORE_AC) then; stg_str = "STG_BEFORE_AC" else if (stage == STG_BEFORE_AR) then; stg_str = "STG_BEFORE_AR" else if (stage == STG_AFTER_AR) then; stg_str = "STG_AFTER_AR" diff --git a/src/mf6core.f90 b/src/mf6core.f90 index e994af96d2e..4f69764d51c 100644 --- a/src/mf6core.f90 +++ b/src/mf6core.f90 @@ -312,7 +312,10 @@ subroutine simulation_df() call connections_cr() ! ! -- synchronize - call run_ctrl%at_stage(STG_BEFORE_DF) + call run_ctrl%at_stage(STG_AFTER_CON_CR) + ! + ! -- synchronize TODO_MJR: this could be merged with the above, in general + call run_ctrl%at_stage(STG_BEFORE_CON_DF) ! ! -- Define each connection do ic = 1, baseconnectionlist%Count() @@ -321,7 +324,7 @@ subroutine simulation_df() end do ! ! -- synchronize - call run_ctrl%at_stage(STG_AFTER_DF) + call run_ctrl%at_stage(STG_AFTER_CON_DF) ! ! -- Define each solution do is = 1, basesolutionlist%Count() From b98376e6c91c5cb63da807ff20678587827fa78a Mon Sep 17 00:00:00 2001 From: Mike Taves Date: Thu, 16 Mar 2023 03:33:16 +1300 Subject: [PATCH 045/123] refactor(mode): correct file modes (#1166) --- CODE_OF_CONDUCT.md | 0 CONTRIBUTING.md | 0 LICENSE.md | 0 code.json | 0 distribution/update_version.py | 0 doc/ConverterGuide/buildconverter_mf5to6.sh | 0 doc/ReleaseNotes/buildreleasenotes.sh | 0 doc/ReleaseNotes/mk_folder_struct.py | 0 doc/mf6io/buildmf6io.sh | 0 doc/mf6io/mf6ivar/mem_allocate.py | 0 src/Utilities/Libraries/daglib/LICENSE | 0 utils/mf5to6/pymake/makebin.py | 0 utils/mf5to6/src/ChdObsWriter.f90 | 0 utils/mf5to6/src/DrnObsWriter.f90 | 0 utils/mf5to6/src/DrnPackageWriter.f90 | 0 utils/mf5to6/src/EvtPackageWriter.f90 | 0 utils/mf5to6/src/FhbPackageWriter.f90 | 0 utils/mf5to6/src/GhbObsWriter.f90 | 0 utils/mf5to6/src/GhbPackageWriter.f90 | 0 utils/mf5to6/src/HfbPackageWriter.f90 | 0 utils/mf5to6/src/LakPackageWriter.f90 | 0 utils/mf5to6/src/RchPackageWriter.f90 | 0 utils/mf5to6/src/RivObsWriter.f90 | 0 utils/mf5to6/src/UzfPackageWriter.f90 | 0 utils/mf5to6/src/WelPackageWriter.f90 | 0 utils/zonebudget/pymake/makebin.py | 0 26 files changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 CODE_OF_CONDUCT.md mode change 100755 => 100644 CONTRIBUTING.md mode change 100755 => 100644 LICENSE.md mode change 100755 => 100644 code.json mode change 100644 => 100755 distribution/update_version.py mode change 100644 => 100755 doc/ConverterGuide/buildconverter_mf5to6.sh mode change 100644 => 100755 doc/ReleaseNotes/buildreleasenotes.sh mode change 100644 => 100755 doc/ReleaseNotes/mk_folder_struct.py mode change 100644 => 100755 doc/mf6io/buildmf6io.sh mode change 100644 => 100755 doc/mf6io/mf6ivar/mem_allocate.py mode change 100755 => 100644 src/Utilities/Libraries/daglib/LICENSE mode change 100644 => 100755 utils/mf5to6/pymake/makebin.py mode change 100755 => 100644 utils/mf5to6/src/ChdObsWriter.f90 mode change 100755 => 100644 utils/mf5to6/src/DrnObsWriter.f90 mode change 100755 => 100644 utils/mf5to6/src/DrnPackageWriter.f90 mode change 100755 => 100644 utils/mf5to6/src/EvtPackageWriter.f90 mode change 100755 => 100644 utils/mf5to6/src/FhbPackageWriter.f90 mode change 100755 => 100644 utils/mf5to6/src/GhbObsWriter.f90 mode change 100755 => 100644 utils/mf5to6/src/GhbPackageWriter.f90 mode change 100755 => 100644 utils/mf5to6/src/HfbPackageWriter.f90 mode change 100755 => 100644 utils/mf5to6/src/LakPackageWriter.f90 mode change 100755 => 100644 utils/mf5to6/src/RchPackageWriter.f90 mode change 100755 => 100644 utils/mf5to6/src/RivObsWriter.f90 mode change 100755 => 100644 utils/mf5to6/src/UzfPackageWriter.f90 mode change 100755 => 100644 utils/mf5to6/src/WelPackageWriter.f90 mode change 100644 => 100755 utils/zonebudget/pymake/makebin.py diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md old mode 100755 new mode 100644 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md old mode 100755 new mode 100644 diff --git a/LICENSE.md b/LICENSE.md old mode 100755 new mode 100644 diff --git a/code.json b/code.json old mode 100755 new mode 100644 diff --git a/distribution/update_version.py b/distribution/update_version.py old mode 100644 new mode 100755 diff --git a/doc/ConverterGuide/buildconverter_mf5to6.sh b/doc/ConverterGuide/buildconverter_mf5to6.sh old mode 100644 new mode 100755 diff --git a/doc/ReleaseNotes/buildreleasenotes.sh b/doc/ReleaseNotes/buildreleasenotes.sh old mode 100644 new mode 100755 diff --git a/doc/ReleaseNotes/mk_folder_struct.py b/doc/ReleaseNotes/mk_folder_struct.py old mode 100644 new mode 100755 diff --git a/doc/mf6io/buildmf6io.sh b/doc/mf6io/buildmf6io.sh old mode 100644 new mode 100755 diff --git a/doc/mf6io/mf6ivar/mem_allocate.py b/doc/mf6io/mf6ivar/mem_allocate.py old mode 100644 new mode 100755 diff --git a/src/Utilities/Libraries/daglib/LICENSE b/src/Utilities/Libraries/daglib/LICENSE old mode 100755 new mode 100644 diff --git a/utils/mf5to6/pymake/makebin.py b/utils/mf5to6/pymake/makebin.py old mode 100644 new mode 100755 diff --git a/utils/mf5to6/src/ChdObsWriter.f90 b/utils/mf5to6/src/ChdObsWriter.f90 old mode 100755 new mode 100644 diff --git a/utils/mf5to6/src/DrnObsWriter.f90 b/utils/mf5to6/src/DrnObsWriter.f90 old mode 100755 new mode 100644 diff --git a/utils/mf5to6/src/DrnPackageWriter.f90 b/utils/mf5to6/src/DrnPackageWriter.f90 old mode 100755 new mode 100644 diff --git a/utils/mf5to6/src/EvtPackageWriter.f90 b/utils/mf5to6/src/EvtPackageWriter.f90 old mode 100755 new mode 100644 diff --git a/utils/mf5to6/src/FhbPackageWriter.f90 b/utils/mf5to6/src/FhbPackageWriter.f90 old mode 100755 new mode 100644 diff --git a/utils/mf5to6/src/GhbObsWriter.f90 b/utils/mf5to6/src/GhbObsWriter.f90 old mode 100755 new mode 100644 diff --git a/utils/mf5to6/src/GhbPackageWriter.f90 b/utils/mf5to6/src/GhbPackageWriter.f90 old mode 100755 new mode 100644 diff --git a/utils/mf5to6/src/HfbPackageWriter.f90 b/utils/mf5to6/src/HfbPackageWriter.f90 old mode 100755 new mode 100644 diff --git a/utils/mf5to6/src/LakPackageWriter.f90 b/utils/mf5to6/src/LakPackageWriter.f90 old mode 100755 new mode 100644 diff --git a/utils/mf5to6/src/RchPackageWriter.f90 b/utils/mf5to6/src/RchPackageWriter.f90 old mode 100755 new mode 100644 diff --git a/utils/mf5to6/src/RivObsWriter.f90 b/utils/mf5to6/src/RivObsWriter.f90 old mode 100755 new mode 100644 diff --git a/utils/mf5to6/src/UzfPackageWriter.f90 b/utils/mf5to6/src/UzfPackageWriter.f90 old mode 100755 new mode 100644 diff --git a/utils/mf5to6/src/WelPackageWriter.f90 b/utils/mf5to6/src/WelPackageWriter.f90 old mode 100755 new mode 100644 diff --git a/utils/zonebudget/pymake/makebin.py b/utils/zonebudget/pymake/makebin.py old mode 100644 new mode 100755 From dd6c196e167a5e3766234ed2d706607f8cbaa469 Mon Sep 17 00:00:00 2001 From: Mike Taves Date: Thu, 16 Mar 2023 03:44:10 +1300 Subject: [PATCH 046/123] feat(CITATION.cff): add software citation (#1165) --- CITATION.cff | 64 ++++++++++++++++++++++++++++++++++ distribution/update_version.py | 22 ++++++++++++ 2 files changed, 86 insertions(+) create mode 100644 CITATION.cff diff --git a/CITATION.cff b/CITATION.cff new file mode 100644 index 00000000000..6986280242e --- /dev/null +++ b/CITATION.cff @@ -0,0 +1,64 @@ +cff-version: 1.2.0 +message: If you use this software, please cite the software itself. +type: software +title: MODFLOW 6 Modular Hydrologic Model +version: 6.4.1 +date-released: '2022-12-09' +doi: 10.5066/F76Q1VQV +abstract: MODFLOW 6 is an object-oriented program and framework developed to provide + a platform for supporting multiple models and multiple types of models within the + same simulation. Within this framework, a regional-scale groundwater model may be + coupled with multiple local-scale groundwater models. Or, a groundwater flow model + could be coupled to a groundwater transport model of a single solute species. +repository-code: https://github.com/MODFLOW-USGS/modflow6 +license: CC0-1.0 +authors: +- family-names: Langevin + given-names: Christian D. + alias: langevin-usgs + affiliation: U.S. Geological Survey + orcid: https://orcid.org/0000-0001-5610-9759 +- family-names: Hughes + given-names: Joseph D. + alias: jdhughes-usgs + affiliation: U.S. Geological Survey + orcid: https://orcid.org/0000-0003-1311-2354 +- family-names: Provost + given-names: Alden M. + alias: aprovost-usgs + affiliation: U.S. Geological Survey + orcid: https://orcid.org/0000-0002-4443-1107 +- family-names: Russcher + given-names: Martijn + alias: mjr-deltares + affiliation: Deltares + orcid: https://orcid.org/0000-0001-8799-6514 +- family-names: Niswonger + given-names: Richard G. + affiliation: U.S. Geological Survey + orcid: https://orcid.org/0000-0001-6397-2403 +- family-names: Panday + given-names: Sorab + affiliation: GSI Environmental +- family-names: Merrick + given-names: Damian + alias: damianmerrick + affiliation: HydroAlgorithmics +- family-names: Morway + given-names: Eric D. + alias: emorway-usgs + affiliation: U.S. Geological Survey + orcid: https://orcid.org/0000-0002-8553-6140 +- family-names: Reno + given-names: Michael J. + affiliation: U.S. Geological Survey + alias: mjreno +- family-names: Bonelli + given-names: Wesley P. + alias: w-bonelli + affiliation: U.S. Geological Survey + orcid: https://orcid.org/0000-0002-2665-5078 +- family-names: Banta + given-names: Edward R. + affiliation: U.S. Geological Survey + orcid: https://orcid.org/0000-0001-8132-9315 diff --git a/distribution/update_version.py b/distribution/update_version.py index 10806c3d0cc..16622d8ecc6 100755 --- a/distribution/update_version.py +++ b/distribution/update_version.py @@ -41,6 +41,7 @@ import pytest from filelock import FileLock +import yaml from utils import get_modified_time @@ -54,6 +55,7 @@ project_root_path / "doc" / "version.py", project_root_path / "README.md", project_root_path / "DISCLAIMER.md", + project_root_path / "CITATION.cff", project_root_path / "code.json", project_root_path / "src" / "Utilities" / "version.f90", ] @@ -291,6 +293,25 @@ def update_readme_and_disclaimer( log_update(disclaimer_path, release_type, version) +def update_citation_cff(release_type: ReleaseType, timestamp: datetime, version: Version): + path = project_root_path / "CITATION.cff" + citation = yaml.safe_load(path.read_text()) + + # update version and date-released + citation["version"] = str(version) + citation["date-released"] = timestamp.strftime("%Y-%m-%d") + + with open(path, "w") as f: + yaml.safe_dump( + citation, + f, + allow_unicode=True, + default_flow_style=False, + sort_keys=False, + ) + log_update(path, release_type, version) + + def update_codejson(release_type: ReleaseType, timestamp: datetime, version: Version): path = project_root_path / "code.json" with open(path, "r") as f: @@ -336,6 +357,7 @@ def update_version( update_version_tex(release_type, timestamp, version) update_version_f90(release_type, timestamp, version) update_readme_and_disclaimer(release_type, timestamp, version) + update_citation_cff(release_type, timestamp, version) update_codejson(release_type, timestamp, version) finally: lock_path.unlink(missing_ok=True) From a0e8022ae0adcc8b24a4d6ac74ae9d704dde4bdc Mon Sep 17 00:00:00 2001 From: mjr-deltares <45555666+mjr-deltares@users.noreply.github.com> Date: Wed, 15 Mar 2023 20:08:00 +0100 Subject: [PATCH 047/123] move away from gfortran 9 for parallel macos: no longer needed (#1170) --- .github/workflows/ci.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f86da1653c3..16a8c309d2f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -411,6 +411,9 @@ jobs: defaults: run: shell: bash -l {0} + env: + FC: gfortran + GCC_V: 12 steps: - name: Checkout modflow6 @@ -437,11 +440,11 @@ jobs: with: mpi: msmpi - - name: Setup GNU Fortran 9 + - name: Setup GNU Fortran ${{ env.GCC_V }} uses: awvwgk/setup-fortran@main with: compiler: gcc - version: 9 + version: ${{ env.GCC_V }} - name: Cache PETSc id: cache-petsc From dbf61ae2952bc18882cadc3c601edf1a9b12e2eb Mon Sep 17 00:00:00 2001 From: mjr-deltares Date: Wed, 15 Mar 2023 21:10:04 +0100 Subject: [PATCH 048/123] fix(ci): rename build config --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 16a8c309d2f..88f0420b902 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -473,8 +473,8 @@ jobs: - name: Configure environment if: runner.os == 'macOS' run: | - echo "PKG_CONFIG_PATH=$GITHUB_WORKSPACE/petsc/arch-darwin-c-debug/lib/pkgconfig" >> $GITHUB_ENV - echo "$GITHUB_WORKSPACE/petsc/arch-darwin-c-debug/bin" >> $GITHUB_PATH + echo "PKG_CONFIG_PATH=$GITHUB_WORKSPACE/petsc/arch-darwin-gcc-debug/lib/pkgconfig" >> $GITHUB_ENV + echo "$GITHUB_WORKSPACE/petsc/arch-darwin-gcc-debug/bin" >> $GITHUB_PATH - name: Configure PETSc if: runner.os == 'Linux' @@ -487,7 +487,7 @@ jobs: if: runner.os == 'macOS' working-directory: petsc run: | - sudo ./configure PETSC_DIR="$GITHUB_WORKSPACE/petsc" PETSC_ARCH=arch-darwin-c-debug --download-fblaslapack --download-openmpi + sudo ./configure PETSC_DIR="$GITHUB_WORKSPACE/petsc" PETSC_ARCH=arch-darwin-gcc-debug --download-fblaslapack --download-openmpi sudo make all - name: Configure PETSc From 3501e10d0bdaa82b3a6d24c779937fdd22f0727e Mon Sep 17 00:00:00 2001 From: langevin-usgs Date: Thu, 16 Mar 2023 06:35:13 -0500 Subject: [PATCH 049/123] feat(vscode-tasks): add build commands for common tasks (#1171) * run dfn2f90.py * run update_flopy.py * run mf6ivar.py --- .vscode/tasks.json | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 49e18315c10..490baff6416 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -223,5 +223,47 @@ "clear": true } }, + { + "label": "Run dfn2f90.py", + "type": "shell", + "command": "source activate modflow6; pwd; python dfn2f90.py", + "options": {"cwd": "${workspaceFolder}/utils/idmloader/scripts"}, + "group": { + "kind": "build", + "isDefault": true + }, + "presentation": { + "clear": true + }, + "problemMatcher": [] + }, + { + "label": "Run update_flopy.py", + "type": "shell", + "command": "source activate modflow6; pwd; python update_flopy.py", + "options": {"cwd": "${workspaceFolder}/autotest"}, + "group": { + "kind": "build", + "isDefault": true + }, + "presentation": { + "clear": true + }, + "problemMatcher": [] + }, + { + "label": "Run mf6ivar.py", + "type": "shell", + "command": "source activate modflow6; pwd; python mf6ivar.py", + "options": {"cwd": "${workspaceFolder}/doc/mf6io/mf6ivar"}, + "group": { + "kind": "build", + "isDefault": true + }, + "presentation": { + "clear": true + }, + "problemMatcher": [] + }, ] } From 8a86dbc7071823a256bf89ca1d77a7e6ce987287 Mon Sep 17 00:00:00 2001 From: mjr-deltares <45555666+mjr-deltares@users.noreply.github.com> Date: Mon, 20 Mar 2023 18:07:16 +0100 Subject: [PATCH 050/123] fix(msvs): add preprocess for SolutionFactory.F90 (#1176) --- msvs/mf6core.vfproj | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/msvs/mf6core.vfproj b/msvs/mf6core.vfproj index 98edc9d2d55..2ceb57a6320 100644 --- a/msvs/mf6core.vfproj +++ b/msvs/mf6core.vfproj @@ -208,7 +208,11 @@ - + + + + + @@ -323,12 +327,12 @@ - + From a05c4ef364ae2f3ba03a8afc3b475f2c0d810ed4 Mon Sep 17 00:00:00 2001 From: langevin-usgs Date: Wed, 22 Mar 2023 16:51:10 -0400 Subject: [PATCH 051/123] fix(fileout print): trim file name when writing to list file (#1179) --- src/Model/GroundWaterFlow/gwf3lak8.f90 | 12 ++++++++---- src/Model/GroundWaterFlow/gwf3maw8.f90 | 9 ++++++--- src/Model/GroundWaterFlow/gwf3mvr8.f90 | 6 ++++-- src/Model/GroundWaterFlow/gwf3uzf8.f90 | 12 ++++++++---- src/Model/GroundWaterTransport/gwt1ist1.f90 | 6 ++++-- src/Model/GroundWaterTransport/gwt1mvt1.f90 | 6 ++++-- 6 files changed, 34 insertions(+), 17 deletions(-) diff --git a/src/Model/GroundWaterFlow/gwf3lak8.f90 b/src/Model/GroundWaterFlow/gwf3lak8.f90 index 654d4dc1f6b..447b540276f 100644 --- a/src/Model/GroundWaterFlow/gwf3lak8.f90 +++ b/src/Model/GroundWaterFlow/gwf3lak8.f90 @@ -3436,7 +3436,8 @@ subroutine lak_options(this, option, found) this%istageout = getunit() call openfile(this%istageout, this%iout, fname, 'DATA(BINARY)', & form, access, 'REPLACE', mode_opt=MNORMAL) - write (this%iout, fmtlakbin) 'STAGE', fname, this%istageout + write (this%iout, fmtlakbin) 'STAGE', trim(adjustl(fname)), & + this%istageout else call store_error('OPTIONAL STAGE KEYWORD MUST BE FOLLOWED BY FILEOUT') end if @@ -3447,7 +3448,8 @@ subroutine lak_options(this, option, found) this%ibudgetout = getunit() call openfile(this%ibudgetout, this%iout, fname, 'DATA(BINARY)', & form, access, 'REPLACE', mode_opt=MNORMAL) - write (this%iout, fmtlakbin) 'BUDGET', fname, this%ibudgetout + write (this%iout, fmtlakbin) 'BUDGET', trim(adjustl(fname)), & + this%ibudgetout else call store_error('OPTIONAL BUDGET KEYWORD MUST BE FOLLOWED BY FILEOUT') end if @@ -3458,7 +3460,8 @@ subroutine lak_options(this, option, found) this%ibudcsv = getunit() call openfile(this%ibudcsv, this%iout, fname, 'CSV', & filstat_opt='REPLACE') - write (this%iout, fmtlakbin) 'BUDGET CSV', fname, this%ibudcsv + write (this%iout, fmtlakbin) 'BUDGET CSV', trim(adjustl(fname)), & + this%ibudcsv else call store_error('OPTIONAL BUDGETCSV KEYWORD MUST BE FOLLOWED BY & &FILEOUT') @@ -3470,7 +3473,8 @@ subroutine lak_options(this, option, found) this%ipakcsv = getunit() call openfile(this%ipakcsv, this%iout, fname, 'CSV', & filstat_opt='REPLACE', mode_opt=MNORMAL) - write (this%iout, fmtlakbin) 'PACKAGE_CONVERGENCE', fname, this%ipakcsv + write (this%iout, fmtlakbin) 'PACKAGE_CONVERGENCE', & + trim(adjustl(fname)), this%ipakcsv else call store_error('OPTIONAL PACKAGE_CONVERGENCE KEYWORD MUST BE '// & 'FOLLOWED BY FILEOUT') diff --git a/src/Model/GroundWaterFlow/gwf3maw8.f90 b/src/Model/GroundWaterFlow/gwf3maw8.f90 index d7149c5fb47..59c79e205c6 100644 --- a/src/Model/GroundWaterFlow/gwf3maw8.f90 +++ b/src/Model/GroundWaterFlow/gwf3maw8.f90 @@ -1791,7 +1791,8 @@ subroutine maw_read_options(this, option, found) this%iheadout = getunit() call openfile(this%iheadout, this%iout, fname, 'DATA(BINARY)', & form, access, 'REPLACE', mode_opt=MNORMAL) - write (this%iout, fmtmawbin) 'HEAD', fname, this%iheadout + write (this%iout, fmtmawbin) 'HEAD', trim(adjustl(fname)), & + this%iheadout else call store_error('Optional maw stage keyword must be '// & 'followed by fileout.') @@ -1803,7 +1804,8 @@ subroutine maw_read_options(this, option, found) this%ibudgetout = getunit() call openfile(this%ibudgetout, this%iout, fname, 'DATA(BINARY)', & form, access, 'REPLACE', mode_opt=MNORMAL) - write (this%iout, fmtmawbin) 'BUDGET', fname, this%ibudgetout + write (this%iout, fmtmawbin) 'BUDGET', trim(adjustl(fname)), & + this%ibudgetout else call store_error('Optional maw budget keyword must be '// & 'followed by fileout.') @@ -1815,7 +1817,8 @@ subroutine maw_read_options(this, option, found) this%ibudcsv = getunit() call openfile(this%ibudcsv, this%iout, fname, 'CSV', & filstat_opt='REPLACE') - write (this%iout, fmtmawbin) 'BUDGET CSV', fname, this%ibudcsv + write (this%iout, fmtmawbin) 'BUDGET CSV', trim(adjustl(fname)), & + this%ibudcsv else call store_error('OPTIONAL BUDGETCSV KEYWORD MUST BE FOLLOWED BY & &FILEOUT') diff --git a/src/Model/GroundWaterFlow/gwf3mvr8.f90 b/src/Model/GroundWaterFlow/gwf3mvr8.f90 index aca20a35f9a..0fdd6460b92 100644 --- a/src/Model/GroundWaterFlow/gwf3mvr8.f90 +++ b/src/Model/GroundWaterFlow/gwf3mvr8.f90 @@ -787,7 +787,8 @@ subroutine read_options(this) this%ibudgetout = getunit() call openfile(this%ibudgetout, this%iout, fname, 'DATA(BINARY)', & form, access, 'REPLACE') - write (this%iout, fmtmvrbin) 'BUDGET', fname, this%ibudgetout + write (this%iout, fmtmvrbin) 'BUDGET', trim(adjustl(fname)), & + this%ibudgetout else call store_error('OPTIONAL BUDGET KEYWORD MUST & &BE FOLLOWED BY FILEOUT') @@ -799,7 +800,8 @@ subroutine read_options(this) this%ibudcsv = getunit() call openfile(this%ibudcsv, this%iout, fname, 'CSV', & filstat_opt='REPLACE') - write (this%iout, fmtmvrbin) 'BUDGET CSV', fname, this%ibudcsv + write (this%iout, fmtmvrbin) 'BUDGET CSV', trim(adjustl(fname)), & + this%ibudcsv else call store_error('OPTIONAL BUDGETCSV KEYWORD MUST BE FOLLOWED BY & &FILEOUT') diff --git a/src/Model/GroundWaterFlow/gwf3uzf8.f90 b/src/Model/GroundWaterFlow/gwf3uzf8.f90 index 6dc72bb9ccd..5417d68d33e 100644 --- a/src/Model/GroundWaterFlow/gwf3uzf8.f90 +++ b/src/Model/GroundWaterFlow/gwf3uzf8.f90 @@ -453,7 +453,8 @@ subroutine uzf_options(this, option, found) this%iwcontout = getunit() call openfile(this%iwcontout, this%iout, fname, 'DATA(BINARY)', & form, access, 'REPLACE', mode_opt=MNORMAL) - write (this%iout, fmtuzfbin) 'WATER-CONTENT', fname, this%iwcontout + write (this%iout, fmtuzfbin) 'WATER-CONTENT', trim(adjustl(fname)), & + this%iwcontout else call store_error('OPTIONAL WATER_CONTENT KEYWORD & &MUST BE FOLLOWED BY FILEOUT') @@ -465,7 +466,8 @@ subroutine uzf_options(this, option, found) this%ibudgetout = getunit() call openfile(this%ibudgetout, this%iout, fname, 'DATA(BINARY)', & form, access, 'REPLACE', mode_opt=MNORMAL) - write (this%iout, fmtuzfbin) 'BUDGET', fname, this%ibudgetout + write (this%iout, fmtuzfbin) 'BUDGET', trim(adjustl(fname)), & + this%ibudgetout else call store_error('OPTIONAL BUDGET KEYWORD MUST BE FOLLOWED BY FILEOUT') end if @@ -476,7 +478,8 @@ subroutine uzf_options(this, option, found) this%ibudcsv = getunit() call openfile(this%ibudcsv, this%iout, fname, 'CSV', & filstat_opt='REPLACE') - write (this%iout, fmtuzfbin) 'BUDGET CSV', fname, this%ibudcsv + write (this%iout, fmtuzfbin) 'BUDGET CSV', trim(adjustl(fname)), & + this%ibudcsv else call store_error('OPTIONAL BUDGETCSV KEYWORD MUST BE FOLLOWED BY & &FILEOUT') @@ -488,7 +491,8 @@ subroutine uzf_options(this, option, found) this%ipakcsv = getunit() call openfile(this%ipakcsv, this%iout, fname, 'CSV', & filstat_opt='REPLACE', mode_opt=MNORMAL) - write (this%iout, fmtuzfbin) 'PACKAGE_CONVERGENCE', fname, this%ipakcsv + write (this%iout, fmtuzfbin) 'PACKAGE_CONVERGENCE', & + trim(adjustl(fname)), this%ipakcsv else call store_error('OPTIONAL PACKAGE_CONVERGENCE KEYWORD MUST BE '// & 'FOLLOWED BY FILEOUT') diff --git a/src/Model/GroundWaterTransport/gwt1ist1.f90 b/src/Model/GroundWaterTransport/gwt1ist1.f90 index 71a0ba502c3..cfef7874976 100644 --- a/src/Model/GroundWaterTransport/gwt1ist1.f90 +++ b/src/Model/GroundWaterTransport/gwt1ist1.f90 @@ -865,7 +865,8 @@ subroutine read_options(this) this%ibudgetout = getunit() call openfile(this%ibudgetout, this%iout, fname, 'DATA(BINARY)', & form, access, 'REPLACE', mode_opt=MNORMAL) - write (this%iout, fmtistbin) 'BUDGET', fname, this%ibudgetout + write (this%iout, fmtistbin) 'BUDGET', trim(adjustl(fname)), & + this%ibudgetout found = .true. else call store_error('OPTIONAL BUDGET KEYWORD MUST & @@ -878,7 +879,8 @@ subroutine read_options(this) this%ibudcsv = getunit() call openfile(this%ibudcsv, this%iout, fname, 'CSV', & filstat_opt='REPLACE') - write (this%iout, fmtistbin) 'BUDGET CSV', fname, this%ibudcsv + write (this%iout, fmtistbin) 'BUDGET CSV', trim(adjustl(fname)), & + this%ibudcsv else call store_error('OPTIONAL BUDGETCSV KEYWORD MUST BE FOLLOWED BY & &FILEOUT') diff --git a/src/Model/GroundWaterTransport/gwt1mvt1.f90 b/src/Model/GroundWaterTransport/gwt1mvt1.f90 index 8a8242405df..5a5bee11e1c 100644 --- a/src/Model/GroundWaterTransport/gwt1mvt1.f90 +++ b/src/Model/GroundWaterTransport/gwt1mvt1.f90 @@ -718,7 +718,8 @@ subroutine read_options(this) this%ibudgetout = getunit() call openfile(this%ibudgetout, this%iout, fname, 'DATA(BINARY)', & form, access, 'REPLACE') - write (this%iout, fmtflow) 'MVT', 'BUDGET', fname, this%ibudgetout + write (this%iout, fmtflow) 'MVT', 'BUDGET', trim(adjustl(fname)), & + this%ibudgetout else call store_error('OPTIONAL BUDGET KEYWORD MUST & &BE FOLLOWED BY FILEOUT') @@ -730,7 +731,8 @@ subroutine read_options(this) this%ibudcsv = getunit() call openfile(this%ibudcsv, this%iout, fname, 'CSV', & filstat_opt='REPLACE') - write (this%iout, fmtflow) 'MVT', 'BUDGET CSV', fname, this%ibudcsv + write (this%iout, fmtflow) 'MVT', 'BUDGET CSV', & + trim(adjustl(fname)), this%ibudcsv else call store_error('OPTIONAL BUDGETCSV KEYWORD MUST BE FOLLOWED BY & &FILEOUT') From 9c6851532bb72df15a4f053363320b6f52d73d19 Mon Sep 17 00:00:00 2001 From: mjreno Date: Thu, 23 Mar 2023 15:14:16 -0400 Subject: [PATCH 052/123] feat(idm): generate component dfn list selectors from dfn2f90.py (#1177) * ensure options block definition is created even if not in dfn * update dfn2f90.py to generate component dfn list selectors * update idm to use generated component selector modules * fix msvs mf6core project file idm paths --------- Co-authored-by: mjreno --- make/makefile | 42 +- msvs/mf6core.vfproj | 7 +- src/Model/GroundWaterFlow/gwf3dis8idm.f90 | 4 + src/Model/GroundWaterFlow/gwf3disu8idm.f90 | 4 + src/Model/GroundWaterFlow/gwf3disv8idm.f90 | 4 + src/Model/GroundWaterFlow/gwf3npf8idm.f90 | 4 + src/Model/GroundWaterTransport/gwt1dspidm.f90 | 4 + src/Utilities/Idm/IdmDfnSelectorUtils.f90 | 158 ++++++ src/Utilities/Idm/IdmSimulation.f90 | 4 +- src/Utilities/Idm/InputDefinitionSelector.f90 | 265 ---------- src/Utilities/Idm/LoadMf6FileType.f90 | 52 +- src/Utilities/Idm/ModflowInput.f90 | 21 +- src/Utilities/Idm/selector/IdmDfnSelector.f90 | 135 +++++ .../Idm/selector/IdmGwfDfnSelector.f90 | 138 ++++++ .../Idm/selector/IdmGwtDfnSelector.f90 | 96 ++++ .../Idm/selector/IdmSimDfnSelector.f90 | 96 ++++ src/meson.build | 6 +- src/simnamidm.f90 | 4 + utils/idmloader/scripts/dfn2f90.py | 468 ++++++++++++++++-- 19 files changed, 1141 insertions(+), 371 deletions(-) create mode 100644 src/Utilities/Idm/IdmDfnSelectorUtils.f90 delete mode 100644 src/Utilities/Idm/InputDefinitionSelector.f90 create mode 100644 src/Utilities/Idm/selector/IdmDfnSelector.f90 create mode 100644 src/Utilities/Idm/selector/IdmGwfDfnSelector.f90 create mode 100644 src/Utilities/Idm/selector/IdmGwtDfnSelector.f90 create mode 100644 src/Utilities/Idm/selector/IdmSimDfnSelector.f90 diff --git a/make/makefile b/make/makefile index d97c2c84dca..3f80aed761a 100644 --- a/make/makefile +++ b/make/makefile @@ -20,18 +20,19 @@ SOURCEDIR13=../src/Timing SOURCEDIR14=../src/Utilities SOURCEDIR15=../src/Utilities/ArrayRead SOURCEDIR16=../src/Utilities/Idm -SOURCEDIR17=../src/Utilities/Libraries -SOURCEDIR18=../src/Utilities/Libraries/blas -SOURCEDIR19=../src/Utilities/Libraries/daglib -SOURCEDIR20=../src/Utilities/Libraries/rcm -SOURCEDIR21=../src/Utilities/Libraries/sparsekit -SOURCEDIR22=../src/Utilities/Libraries/sparskit2 -SOURCEDIR23=../src/Utilities/Matrix -SOURCEDIR24=../src/Utilities/Memory -SOURCEDIR25=../src/Utilities/Observation -SOURCEDIR26=../src/Utilities/OutputControl -SOURCEDIR27=../src/Utilities/TimeSeries -SOURCEDIR28=../src/Utilities/Vector +SOURCEDIR17=../src/Utilities/Idm/selector +SOURCEDIR18=../src/Utilities/Libraries +SOURCEDIR19=../src/Utilities/Libraries/blas +SOURCEDIR20=../src/Utilities/Libraries/daglib +SOURCEDIR21=../src/Utilities/Libraries/rcm +SOURCEDIR22=../src/Utilities/Libraries/sparsekit +SOURCEDIR23=../src/Utilities/Libraries/sparskit2 +SOURCEDIR24=../src/Utilities/Matrix +SOURCEDIR25=../src/Utilities/Memory +SOURCEDIR26=../src/Utilities/Observation +SOURCEDIR27=../src/Utilities/OutputControl +SOURCEDIR28=../src/Utilities/TimeSeries +SOURCEDIR29=../src/Utilities/Vector VPATH = \ ${SOURCEDIR1} \ @@ -61,7 +62,8 @@ ${SOURCEDIR24} \ ${SOURCEDIR25} \ ${SOURCEDIR26} \ ${SOURCEDIR27} \ -${SOURCEDIR28} +${SOURCEDIR28} \ +${SOURCEDIR29} .SUFFIXES: .f90 .F90 .o @@ -98,27 +100,30 @@ $(OBJDIR)/VectorBase.o \ $(OBJDIR)/Sparse.o \ $(OBJDIR)/DisvGeom.o \ $(OBJDIR)/ArrayReaders.o \ +$(OBJDIR)/InputDefinition.o \ $(OBJDIR)/TimeSeriesManager.o \ $(OBJDIR)/SmoothingFunctions.o \ $(OBJDIR)/MatrixBase.o \ $(OBJDIR)/ListReader.o \ $(OBJDIR)/Connections.o \ -$(OBJDIR)/InputDefinition.o \ -$(OBJDIR)/ArrayReaderBase.o \ -$(OBJDIR)/DiscretizationBase.o \ -$(OBJDIR)/STLVecInt.o \ $(OBJDIR)/simnamidm.o \ $(OBJDIR)/gwt1dspidm.o \ $(OBJDIR)/gwf3npf8idm.o \ $(OBJDIR)/gwf3disv8idm.o \ $(OBJDIR)/gwf3disu8idm.o \ $(OBJDIR)/gwf3dis8idm.o \ +$(OBJDIR)/ArrayReaderBase.o \ +$(OBJDIR)/DiscretizationBase.o \ +$(OBJDIR)/STLVecInt.o \ +$(OBJDIR)/IdmSimDfnSelector.o \ +$(OBJDIR)/IdmGwtDfnSelector.o \ +$(OBJDIR)/IdmGwfDfnSelector.o \ $(OBJDIR)/Integer2dReader.o \ $(OBJDIR)/TimeArray.o \ $(OBJDIR)/ObsOutput.o \ $(OBJDIR)/StructVector.o \ $(OBJDIR)/IdmLogger.o \ -$(OBJDIR)/InputDefinitionSelector.o \ +$(OBJDIR)/IdmDfnSelector.o \ $(OBJDIR)/Integer1dReader.o \ $(OBJDIR)/Double2dReader.o \ $(OBJDIR)/Double1dReader.o \ @@ -128,6 +133,7 @@ $(OBJDIR)/Observe.o \ $(OBJDIR)/StructArray.o \ $(OBJDIR)/ModflowInput.o \ $(OBJDIR)/LayeredArrayReader.o \ +$(OBJDIR)/IdmDfnSelectorUtils.o \ $(OBJDIR)/TimeArraySeriesLink.o \ $(OBJDIR)/ObsUtility.o \ $(OBJDIR)/ObsContainer.o \ diff --git a/msvs/mf6core.vfproj b/msvs/mf6core.vfproj index 2ceb57a6320..098a8ef1cc3 100644 --- a/msvs/mf6core.vfproj +++ b/msvs/mf6core.vfproj @@ -226,11 +226,16 @@ + + + + + + - diff --git a/src/Model/GroundWaterFlow/gwf3dis8idm.f90 b/src/Model/GroundWaterFlow/gwf3dis8idm.f90 index 56404996a00..ff33cf24725 100644 --- a/src/Model/GroundWaterFlow/gwf3dis8idm.f90 +++ b/src/Model/GroundWaterFlow/gwf3dis8idm.f90 @@ -1,3 +1,4 @@ +! ** Do Not Modify! MODFLOW 6 system generated file. ** module GwfDisInputModule use InputDefinitionModule, only: InputParamDefinitionType, & InputBlockDefinitionType @@ -6,6 +7,7 @@ module GwfDisInputModule public gwf_dis_aggregate_definitions public gwf_dis_block_definitions public GwfDisParamFoundType + public gwf_dis_multi_package type GwfDisParamFoundType logical :: length_units = .false. @@ -23,6 +25,8 @@ module GwfDisInputModule logical :: idomain = .false. end type GwfDisParamFoundType + logical :: gwf_dis_multi_package = .false. + type(InputParamDefinitionType), parameter :: & gwfdis_length_units = InputParamDefinitionType & ( & diff --git a/src/Model/GroundWaterFlow/gwf3disu8idm.f90 b/src/Model/GroundWaterFlow/gwf3disu8idm.f90 index 9dd099b4f7b..10a61f84702 100644 --- a/src/Model/GroundWaterFlow/gwf3disu8idm.f90 +++ b/src/Model/GroundWaterFlow/gwf3disu8idm.f90 @@ -1,3 +1,4 @@ +! ** Do Not Modify! MODFLOW 6 system generated file. ** module GwfDisuInputModule use InputDefinitionModule, only: InputParamDefinitionType, & InputBlockDefinitionType @@ -6,6 +7,7 @@ module GwfDisuInputModule public gwf_disu_aggregate_definitions public gwf_disu_block_definitions public GwfDisuParamFoundType + public gwf_disu_multi_package type GwfDisuParamFoundType logical :: length_units = .false. @@ -37,6 +39,8 @@ module GwfDisuInputModule logical :: icvert = .false. end type GwfDisuParamFoundType + logical :: gwf_disu_multi_package = .false. + type(InputParamDefinitionType), parameter :: & gwfdisu_length_units = InputParamDefinitionType & ( & diff --git a/src/Model/GroundWaterFlow/gwf3disv8idm.f90 b/src/Model/GroundWaterFlow/gwf3disv8idm.f90 index 40c61e57643..142d32945d7 100644 --- a/src/Model/GroundWaterFlow/gwf3disv8idm.f90 +++ b/src/Model/GroundWaterFlow/gwf3disv8idm.f90 @@ -1,3 +1,4 @@ +! ** Do Not Modify! MODFLOW 6 system generated file. ** module GwfDisvInputModule use InputDefinitionModule, only: InputParamDefinitionType, & InputBlockDefinitionType @@ -6,6 +7,7 @@ module GwfDisvInputModule public gwf_disv_aggregate_definitions public gwf_disv_block_definitions public GwfDisvParamFoundType + public gwf_disv_multi_package type GwfDisvParamFoundType logical :: length_units = .false. @@ -29,6 +31,8 @@ module GwfDisvInputModule logical :: icvert = .false. end type GwfDisvParamFoundType + logical :: gwf_disv_multi_package = .false. + type(InputParamDefinitionType), parameter :: & gwfdisv_length_units = InputParamDefinitionType & ( & diff --git a/src/Model/GroundWaterFlow/gwf3npf8idm.f90 b/src/Model/GroundWaterFlow/gwf3npf8idm.f90 index d435c10073f..87d2825bed2 100644 --- a/src/Model/GroundWaterFlow/gwf3npf8idm.f90 +++ b/src/Model/GroundWaterFlow/gwf3npf8idm.f90 @@ -1,3 +1,4 @@ +! ** Do Not Modify! MODFLOW 6 system generated file. ** module GwfNpfInputModule use InputDefinitionModule, only: InputParamDefinitionType, & InputBlockDefinitionType @@ -6,6 +7,7 @@ module GwfNpfInputModule public gwf_npf_aggregate_definitions public gwf_npf_block_definitions public GwfNpfParamFoundType + public gwf_npf_multi_package type GwfNpfParamFoundType logical :: ipakcb = .false. @@ -47,6 +49,8 @@ module GwfNpfInputModule logical :: wetdry = .false. end type GwfNpfParamFoundType + logical :: gwf_npf_multi_package = .false. + type(InputParamDefinitionType), parameter :: & gwfnpf_ipakcb = InputParamDefinitionType & ( & diff --git a/src/Model/GroundWaterTransport/gwt1dspidm.f90 b/src/Model/GroundWaterTransport/gwt1dspidm.f90 index 5ee187e6102..63b8cfeeb46 100644 --- a/src/Model/GroundWaterTransport/gwt1dspidm.f90 +++ b/src/Model/GroundWaterTransport/gwt1dspidm.f90 @@ -1,3 +1,4 @@ +! ** Do Not Modify! MODFLOW 6 system generated file. ** module GwtDspInputModule use InputDefinitionModule, only: InputParamDefinitionType, & InputBlockDefinitionType @@ -6,6 +7,7 @@ module GwtDspInputModule public gwt_dsp_aggregate_definitions public gwt_dsp_block_definitions public GwtDspParamFoundType + public gwt_dsp_multi_package type GwtDspParamFoundType logical :: xt3d_off = .false. @@ -18,6 +20,8 @@ module GwtDspInputModule logical :: atv = .false. end type GwtDspParamFoundType + logical :: gwt_dsp_multi_package = .false. + type(InputParamDefinitionType), parameter :: & gwtdsp_xt3d_off = InputParamDefinitionType & ( & diff --git a/src/Utilities/Idm/IdmDfnSelectorUtils.f90 b/src/Utilities/Idm/IdmDfnSelectorUtils.f90 new file mode 100644 index 00000000000..2bd8f88cc09 --- /dev/null +++ b/src/Utilities/Idm/IdmDfnSelectorUtils.f90 @@ -0,0 +1,158 @@ +!> @brief This module contains the InputDefinitionSelectorModule +!! +!! This module contains the routines for getting parameter +!! definitions, aggregate definitions, and block definitions +!! for the different package types. +!! +!< +module IdmDfnSelectorUtilsModule + + use KindModule, only: I4B + use SimVariablesModule, only: errmsg + use SimModule, only: store_error + use InputDefinitionModule, only: InputParamDefinitionType, & + InputBlockDefinitionType + + implicit none + private + public :: get_param_definition_type + public :: get_aggregate_definition_type + public :: split_record_definition + +contains + + !> @brief Return parameter definition + !< + function get_param_definition_type(input_definition_types, component_type, & + subcomponent_type, blockname, tagname) & + result(idt) + type(InputParamDefinitionType), dimension(:), intent(in), target :: & + input_definition_types + character(len=*), intent(in) :: component_type !< component type, such as GWF or GWT + character(len=*), intent(in) :: subcomponent_type !< subcomponent type, such as DIS or NPF + character(len=*), intent(in) :: blockname !< name of the block + character(len=*), intent(in) :: tagname !< name of the input tag + type(InputParamDefinitionType), pointer :: idt !< corresponding InputParameterDefinitionType for this tag + type(InputParamDefinitionType), pointer :: tmp_ptr + integer(I4B) :: i + ! + idt => null() + do i = 1, size(input_definition_types) + tmp_ptr => input_definition_types(i) + if (tmp_ptr%component_type == component_type .and. & + tmp_ptr%subcomponent_type == subcomponent_type .and. & + tmp_ptr%blockname == blockname .and. & + tmp_ptr%tagname == tagname) then + idt => input_definition_types(i) + exit + end if + end do + ! + if (.not. associated(idt)) then + write (errmsg, '(1x,a,a,a,a,a,a,a)') & + 'Idm parameter definition not found: ', trim(tagname), & + '. Component="', trim(component_type), & + '", subcomponent="', trim(subcomponent_type), '".' + call store_error(errmsg, .true.) + end if + ! + ! -- return + return + end function get_param_definition_type + + !> @brief Return aggregate definition + !< + function get_aggregate_definition_type(input_definition_types, component_type, & + subcomponent_type, blockname) result(idt) + type(InputParamDefinitionType), dimension(:), intent(in), target :: & + input_definition_types + character(len=*), intent(in) :: component_type !< component type, such as GWF or GWT + character(len=*), intent(in) :: subcomponent_type !< subcomponent type, such as DIS or NPF + character(len=*), intent(in) :: blockname !< name of the block + type(InputParamDefinitionType), pointer :: idt !< corresponding InputParameterDefinitionType for this block + type(InputParamDefinitionType), pointer :: tmp_ptr + integer(I4B) :: i + ! + idt => null() + do i = 1, size(input_definition_types) + tmp_ptr => input_definition_types(i) + if (tmp_ptr%component_type == component_type .and. & + tmp_ptr%subcomponent_type == subcomponent_type .and. & + tmp_ptr%blockname == blockname) then + idt => input_definition_types(i) + exit + end if + end do + ! + if (.not. associated(idt)) then + write (errmsg, '(1x,a,a,a,a,a,a,a)') & + 'Idm aggregate definition not found: ', trim(blockname), & + '. Component="', trim(component_type), & + '", subcomponent="', trim(subcomponent_type), '".' + call store_error(errmsg, .true.) + end if + ! + ! -- return + return + end function get_aggregate_definition_type + + !> @brief Return aggregate definition + !! + !! Split a component RECORD datatype definition whose second element matches + !! tagname into an array of character tokens + !< + subroutine split_record_definition(input_definition_types, component_type, & + subcomponent_type, tagname, nwords, words) + use InputOutputModule, only: parseline + type(InputParamDefinitionType), dimension(:), intent(in), target :: & + input_definition_types + character(len=*), intent(in) :: component_type !< component type, such as GWF or GWT + character(len=*), intent(in) :: subcomponent_type !< subcomponent type, such as DIS or NPF + character(len=*), intent(in) :: tagname !< name of the input tag + integer(I4B), intent(inout) :: nwords + character(len=40), dimension(:), allocatable, intent(inout) :: words + type(InputParamDefinitionType), pointer :: tmp_ptr + integer(I4B) :: i + character(len=:), allocatable :: parse_str + ! + ! -- initialize to deallocated + if (allocated(words)) deallocate (words) + ! + ! -- return all tokens of multi-record type that matches the first + ! -- tag following the expected first token "RECORD" + do i = 1, size(input_definition_types) + ! + ! -- initialize + nwords = 0 + ! + ! -- set ptr to current definition + tmp_ptr => input_definition_types(i) + ! + ! -- match for definition to split + if (tmp_ptr%component_type == component_type .and. & + tmp_ptr%subcomponent_type == subcomponent_type .and. & + tmp_ptr%datatype(1:6) == 'RECORD') then + ! + ! -- set split string + parse_str = trim(input_definition_types(i)%datatype)//' ' + ! + ! -- split + call parseline(parse_str, nwords, words) + ! + ! -- check for match and manage memory + if (nwords >= 2) then + if (words(1) == 'RECORD' .and. words(2) == tagname) then + if (allocated(parse_str)) deallocate (parse_str) + exit + end if + end if + ! + ! -- deallocate + if (allocated(parse_str)) deallocate (parse_str) + if (allocated(words)) deallocate (words) + ! + end if + end do + end subroutine split_record_definition + +end module IdmDfnSelectorUtilsModule diff --git a/src/Utilities/Idm/IdmSimulation.f90 b/src/Utilities/Idm/IdmSimulation.f90 index 760d73f1252..91f6d039c1c 100644 --- a/src/Utilities/Idm/IdmSimulation.f90 +++ b/src/Utilities/Idm/IdmSimulation.f90 @@ -121,10 +121,10 @@ subroutine simnam_allocate() mf6_input = getModflowInput('NAM6', 'SIM', 'NAM', 'SIM', 'NAM') ! ! -- allocate sim namfile parameters if not in input context - do iparam = 1, size(mf6_input%p_param_dfns) + do iparam = 1, size(mf6_input%param_dfns) ! ! -- assign param definition pointer - idt => mf6_input%p_param_dfns(iparam) + idt => mf6_input%param_dfns(iparam) ! ! -- check if variable is already allocated call get_isize(idt%mf6varname, input_mempath, isize) diff --git a/src/Utilities/Idm/InputDefinitionSelector.f90 b/src/Utilities/Idm/InputDefinitionSelector.f90 deleted file mode 100644 index 4d53cf04316..00000000000 --- a/src/Utilities/Idm/InputDefinitionSelector.f90 +++ /dev/null @@ -1,265 +0,0 @@ -!> @brief This module contains the InputDefinitionSelectorModule -!! -!! This module contains the routines for getting parameter -!! definitions, aggregate definitions, and block definitions -!! for the different package types. -!! -!< -module InputDefinitionSelectorModule - - use KindModule, only: I4B - use SimVariablesModule, only: errmsg, warnmsg - use SimModule, only: store_error, store_warning - use InputDefinitionModule, only: InputParamDefinitionType, & - InputBlockDefinitionType - use GwfDisInputModule, only: gwf_dis_param_definitions, & - gwf_dis_aggregate_definitions, & - gwf_dis_block_definitions - use GwfDisuInputModule, only: gwf_disu_param_definitions, & - gwf_disu_aggregate_definitions, & - gwf_disu_block_definitions - use GwfDisvInputModule, only: gwf_disv_param_definitions, & - gwf_disv_aggregate_definitions, & - gwf_disv_block_definitions - use GwfNpfInputModule, only: gwf_npf_param_definitions, & - gwf_npf_aggregate_definitions, & - gwf_npf_block_definitions - use GwtDspInputModule, only: gwt_dsp_param_definitions, & - gwt_dsp_aggregate_definitions, & - gwt_dsp_block_definitions - use SimNamInputModule, only: sim_nam_param_definitions, & - sim_nam_aggregate_definitions, & - sim_nam_block_definitions - - implicit none - private - public :: block_definitions - public :: aggregate_definitions - public :: param_definitions - public :: get_param_definition_type - public :: get_aggregate_definition_type - public :: split_record_definition - -contains - - !> @brief Return the parameter definition for the specified component - !< - function param_definitions(component) result(input_definition) - character(len=*), intent(in) :: component !< component type, such as GWF/DIS - type(InputParamDefinitionType), dimension(:), pointer :: input_definition !< InputParamDefinitionType for specified component - - select case (component) - case ('GWF/DIS') - call set_pointer(input_definition, gwf_dis_param_definitions) - case ('GWF/DISU') - call set_pointer(input_definition, gwf_disu_param_definitions) - case ('GWF/DISV') - call set_pointer(input_definition, gwf_disv_param_definitions) - case ('GWF/NPF') - call set_pointer(input_definition, gwf_npf_param_definitions) - case ('GWT/DSP') - call set_pointer(input_definition, gwt_dsp_param_definitions) - case ('SIM/NAM') - call set_pointer(input_definition, sim_nam_param_definitions) - case default - write (warnmsg, '(a,a)') 'IDM Unsupported input type: ', trim(component) - call store_warning(warnmsg) - end select - - return - end function param_definitions - - !> @brief Return the aggregate definition for the specified component - !< - function aggregate_definitions(component) result(input_definition) - character(len=*), intent(in) :: component !< component type, such as GWF/DIS - type(InputParamDefinitionType), dimension(:), pointer :: input_definition !< InputParamDefinitionType for specified component - - select case (component) - case ('GWF/DIS') - call set_pointer(input_definition, gwf_dis_aggregate_definitions) - case ('GWF/DISU') - call set_pointer(input_definition, gwf_disu_aggregate_definitions) - case ('GWF/DISV') - call set_pointer(input_definition, gwf_disv_aggregate_definitions) - case ('GWF/NPF') - call set_pointer(input_definition, gwf_npf_aggregate_definitions) - case ('GWT/DSP') - call set_pointer(input_definition, gwt_dsp_aggregate_definitions) - case ('SIM/NAM') - call set_pointer(input_definition, sim_nam_aggregate_definitions) - case default - write (warnmsg, '(a,a)') 'IDM Unsupported input type: ', trim(component) - call store_warning(warnmsg) - end select - - return - end function aggregate_definitions - - !> @brief Return the block definition for the specified component - !< - function block_definitions(component) result(input_definition) - character(len=*), intent(in) :: component !< component type, such as GWF/DIS - type(InputBlockDefinitionType), dimension(:), pointer :: input_definition !< InputParamDefinitionType for specified component - - select case (component) - case ('GWF/DIS') - call set_block_pointer(input_definition, gwf_dis_block_definitions) - case ('GWF/DISU') - call set_block_pointer(input_definition, gwf_disu_block_definitions) - case ('GWF/DISV') - call set_block_pointer(input_definition, gwf_disv_block_definitions) - case ('GWF/NPF') - call set_block_pointer(input_definition, gwf_npf_block_definitions) - case ('GWT/DSP') - call set_block_pointer(input_definition, gwt_dsp_block_definitions) - case ('SIM/NAM') - call set_block_pointer(input_definition, sim_nam_block_definitions) - case default - write (warnmsg, '(a,a)') 'IDM Unsupported input type: ', trim(component) - call store_warning(warnmsg) - end select - - return - end function block_definitions - - !> @brief Set pointer from input_definition to input_definition_target - !< - subroutine set_pointer(input_definition, input_definition_target) - type(InputParamDefinitionType), dimension(:), pointer :: input_definition !< InputParamDefinitionType source - type(InputParamDefinitionType), dimension(:), target :: & - input_definition_target !< InputParamDefinitionType target - input_definition => input_definition_target - end subroutine set_pointer - - !> @brief Set pointer from input_definition to input_definition_target - !< - subroutine set_block_pointer(input_definition, input_definition_target) - type(InputBlockDefinitionType), dimension(:), pointer :: input_definition !< InputParamDefinitionType source - type(InputBlockDefinitionType), dimension(:), target :: & - input_definition_target !< InputParamDefinitionType target - input_definition => input_definition_target - end subroutine set_block_pointer - - !> @brief Return parameter definition - !< - function get_param_definition_type(input_definition_types, component_type, & - subcomponent_type, blockname, tagname) & - result(idt) - type(InputParamDefinitionType), dimension(:), intent(in), target :: & - input_definition_types - character(len=*), intent(in) :: component_type !< component type, such as GWF or GWT - character(len=*), intent(in) :: subcomponent_type !< subcomponent type, such as DIS or NPF - character(len=*), intent(in) :: blockname !< name of the block - character(len=*), intent(in) :: tagname !< name of the input tag - type(InputParamDefinitionType), pointer :: idt !< corresponding InputParameterDefinitionType for this tag - type(InputParamDefinitionType), pointer :: tmp_ptr - integer(I4B) :: i - - idt => null() - do i = 1, size(input_definition_types) - tmp_ptr => input_definition_types(i) - if (tmp_ptr%component_type == component_type .and. & - tmp_ptr%subcomponent_type == subcomponent_type .and. & - tmp_ptr%blockname == blockname .and. & - tmp_ptr%tagname == tagname) then - idt => input_definition_types(i) - exit - end if - end do - - if (.not. associated(idt)) then - write (errmsg, '(4x,a,a)') 'parameter definition not found: ', trim(tagname) - call store_error(errmsg) - end if - - end function get_param_definition_type - - !> @brief Return aggregate definition - !< - function get_aggregate_definition_type(input_definition_types, component_type, & - subcomponent_type, blockname) result(idt) - type(InputParamDefinitionType), dimension(:), intent(in), target :: & - input_definition_types - character(len=*), intent(in) :: component_type !< component type, such as GWF or GWT - character(len=*), intent(in) :: subcomponent_type !< subcomponent type, such as DIS or NPF - character(len=*), intent(in) :: blockname !< name of the block - type(InputParamDefinitionType), pointer :: idt !< corresponding InputParameterDefinitionType for this block - type(InputParamDefinitionType), pointer :: tmp_ptr - integer(I4B) :: i - - idt => null() - do i = 1, size(input_definition_types) - tmp_ptr => input_definition_types(i) - if (tmp_ptr%component_type == component_type .and. & - tmp_ptr%subcomponent_type == subcomponent_type .and. & - tmp_ptr%blockname == blockname) then - idt => input_definition_types(i) - exit - end if - end do - - if (.not. associated(idt)) then - write (errmsg, '(4x,a,a)') 'aggregate definition not found: ', & - trim(blockname) - call store_error(errmsg) - end if - end function get_aggregate_definition_type - - !> @brief Return aggregate definition - !! - !! Split a component RECORD datatype definition whose second element matches - !! tagname into an array of character tokens - !< - subroutine split_record_definition(input_definition_types, component_type, & - subcomponent_type, tagname, nwords, words) - use InputOutputModule, only: parseline - type(InputParamDefinitionType), dimension(:), intent(in), target :: & - input_definition_types - character(len=*), intent(in) :: component_type !< component type, such as GWF or GWT - character(len=*), intent(in) :: subcomponent_type !< subcomponent type, such as DIS or NPF - character(len=*), intent(in) :: tagname !< name of the input tag - integer(I4B), intent(inout) :: nwords - character(len=40), dimension(:), allocatable, intent(inout) :: words - type(InputParamDefinitionType), pointer :: tmp_ptr - integer(I4B) :: i - character(len=:), allocatable :: parse_str - ! - ! -- initialize to deallocated - if (allocated(words)) deallocate (words) - ! - ! -- return all tokens of multi-record type that matches the first - ! -- tag following the expected first token "RECORD" - do i = 1, size(input_definition_types) - ! - ! -- initialize - nwords = 0 - ! - ! -- set ptr to current definition - tmp_ptr => input_definition_types(i) - ! - ! -- match for definition to split - if (tmp_ptr%component_type == component_type .and. & - tmp_ptr%subcomponent_type == subcomponent_type .and. & - tmp_ptr%datatype(1:6) == 'RECORD') then - ! - ! -- set split string - parse_str = trim(input_definition_types(i)%datatype)//' ' - ! - ! -- split - call parseline(parse_str, nwords, words) - ! - ! -- check for match and manage memory - if (nwords >= 2) then - if (words(1) == 'RECORD' .and. words(2) == tagname) exit - end if - ! - ! -- deallocate - if (allocated(parse_str)) deallocate (parse_str) - if (allocated(words)) deallocate (words) - ! - end if - end do - end subroutine split_record_definition - -end module InputDefinitionSelectorModule diff --git a/src/Utilities/Idm/LoadMf6FileType.f90 b/src/Utilities/Idm/LoadMf6FileType.f90 index 45a7a867bcb..fcede292d22 100644 --- a/src/Utilities/Idm/LoadMf6FileType.f90 +++ b/src/Utilities/Idm/LoadMf6FileType.f90 @@ -24,8 +24,8 @@ module LoadMf6FileTypeModule use Integer2dReaderModule, only: read_int2d use InputOutputModule, only: parseline use InputDefinitionModule, only: InputParamDefinitionType - use InputDefinitionSelectorModule, only: get_param_definition_type, & - get_aggregate_definition_type + use IdmDfnSelectorUtilsModule, only: get_param_definition_type, & + get_aggregate_definition_type use ModflowInputModule, only: ModflowInputType, getModflowInput use MemoryManagerModule, only: mem_allocate, mem_setptr use MemoryHelperModule, only: create_mem_path @@ -74,11 +74,11 @@ subroutine idm_load(parser, pkgtype, & mf6_input%subcomponent_name, iout) ! ! -- process blocks - do iblock = 1, size(mf6_input%p_block_dfns) + do iblock = 1, size(mf6_input%block_dfns) call parse_block(parser, mf6_input, iblock, mshape, iout, .false.) ! ! -- set model shape if discretization dimensions have been read - if (mf6_input%p_block_dfns(iblock)%blockname == 'DIMENSIONS' .and. & + if (mf6_input%block_dfns(iblock)%blockname == 'DIMENSIONS' .and. & pkgtype(1:3) == 'DIS') then call set_model_shape(mf6_input%pkgtype, componentMemPath, & mf6_input%mempath, mshape) @@ -116,25 +116,25 @@ recursive subroutine parse_block(parser, mf6_input, iblock, mshape, iout, & ! ! -- disu vertices/cell2d blocks are contingent on NVERT dimension if (mf6_input%pkgtype == 'DISU6' .and. & - (mf6_input%p_block_dfns(iblock)%blockname == 'VERTICES' .or. & - mf6_input%p_block_dfns(iblock)%blockname == 'CELL2D')) then + (mf6_input%block_dfns(iblock)%blockname == 'VERTICES' .or. & + mf6_input%block_dfns(iblock)%blockname == 'CELL2D')) then call get_from_memorylist('NVERT', mf6_input%mempath, mt, found, .false.) if (.not. found) return if (mt%intsclr == 0) return end if ! ! -- block open/close support - supportOpenClose = (mf6_input%p_block_dfns(iblock)%blockname /= 'GRIDDATA') + supportOpenClose = (mf6_input%block_dfns(iblock)%blockname /= 'GRIDDATA') ! ! -- parser search for block - required = mf6_input%p_block_dfns(iblock)%required .and. .not. recursive_call - call parser%GetBlock(mf6_input%p_block_dfns(iblock)%blockname, isblockfound, & + required = mf6_input%block_dfns(iblock)%required .and. .not. recursive_call + call parser%GetBlock(mf6_input%block_dfns(iblock)%blockname, isblockfound, & ierr, supportOpenClose=supportOpenClose, & blockRequired=required) ! ! -- process block if (isblockfound) then - if (mf6_input%p_block_dfns(iblock)%aggregate) then + if (mf6_input%block_dfns(iblock)%aggregate) then ! ! -- process block recarray type, set of variable 1d/2d types call parse_structarray_block(parser, mf6_input, iblock, mshape, iout) @@ -151,7 +151,7 @@ recursive subroutine parse_block(parser, mf6_input, iblock, mshape, iout, & end if ! ! -- recurse if block is reloadable and was just read - if (mf6_input%p_block_dfns(iblock)%block_variable) then + if (mf6_input%block_dfns(iblock)%block_variable) then if (isblockfound) then call parse_block(parser, mf6_input, iblock, mshape, iout, .true.) end if @@ -163,7 +163,7 @@ end subroutine parse_block subroutine parse_iofile_tag(parser, mf6_input, iblock, mshape, tag, found, & iout) - use InputDefinitionSelectorModule, only: split_record_definition + use IdmDfnSelectorUtilsModule, only: split_record_definition type(BlockParserType), intent(inout) :: parser !< block parser type(ModflowInputType), intent(in) :: mf6_input !< ModflowInputType integer(I4B), intent(in) :: iblock !< consecutive block number as defined in definition file @@ -180,7 +180,7 @@ subroutine parse_iofile_tag(parser, mf6_input, iblock, mshape, tag, found, & found = .false. ! ! -- get tokens in matching definition - call split_record_definition(mf6_input%p_param_dfns, & + call split_record_definition(mf6_input%param_dfns, & mf6_input%component_type, & mf6_input%subcomponent_type, & tag, nwords, words) @@ -204,10 +204,10 @@ subroutine parse_iofile_tag(parser, mf6_input, iblock, mshape, tag, found, & ! ! -- matches, read and load file name idt => & - get_param_definition_type(mf6_input%p_param_dfns, & + get_param_definition_type(mf6_input%param_dfns, & mf6_input%component_type, & mf6_input%subcomponent_type, & - mf6_input%p_block_dfns(iblock)%blockname, & + mf6_input%block_dfns(iblock)%blockname, & words(4)) call load_string_type(parser, idt, mf6_input%mempath, iout) ! @@ -250,10 +250,10 @@ recursive subroutine parse_tag(parser, mf6_input, iblock, mshape, iout, & end if ! ! -- find keyword in input definition - idt => get_param_definition_type(mf6_input%p_param_dfns, & + idt => get_param_definition_type(mf6_input%param_dfns, & mf6_input%component_type, & mf6_input%subcomponent_type, & - mf6_input%p_block_dfns(iblock)%blockname, & + mf6_input%block_dfns(iblock)%blockname, & tag) ! ! -- allocate and load data type @@ -278,7 +278,7 @@ recursive subroutine parse_tag(parser, mf6_input, iblock, mshape, iout, & end if ! ! -- check/set as dev option - if (mf6_input%p_block_dfns(iblock)%blockname == 'OPTIONS' .and. & + if (mf6_input%block_dfns(iblock)%blockname == 'OPTIONS' .and. & idt%tagname(1:4) == 'DEV_') then call parser%DevOpt() end if @@ -345,13 +345,13 @@ subroutine parse_structarray_block(parser, mf6_input, iblock, mshape, iout) character(len=3) :: block_suffix = 'num' ! ! -- set input definition for this block - idt => get_aggregate_definition_type(mf6_input%p_aggregate_dfns, & + idt => get_aggregate_definition_type(mf6_input%aggregate_dfns, & mf6_input%component_type, & mf6_input%subcomponent_type, & - mf6_input%p_block_dfns(iblock)%blockname) + mf6_input%block_dfns(iblock)%blockname) ! ! -- if block is reloadable read the block number - if (mf6_input%p_block_dfns(iblock)%block_variable) then + if (mf6_input%block_dfns(iblock)%block_variable) then blocknum = parser%GetInteger() else blocknum = 0 @@ -382,14 +382,14 @@ subroutine parse_structarray_block(parser, mf6_input, iblock, mshape, iout) if (icol == 1) then ! ! -- assign first column as the block number - ilen = len_trim(mf6_input%p_block_dfns(iblock)%blockname) + ilen = len_trim(mf6_input%block_dfns(iblock)%blockname) ! if (ilen > (LENVARNAME - len(block_suffix))) then varname = & - mf6_input%p_block_dfns(iblock)% & + mf6_input%block_dfns(iblock)% & blockname(1:(LENVARNAME - len(block_suffix)))//block_suffix else - varname = trim(mf6_input%p_block_dfns(iblock)%blockname)//block_suffix + varname = trim(mf6_input%block_dfns(iblock)%blockname)//block_suffix end if ! call struct_array%mem_create_vector(icol, 'INTEGER', & @@ -410,10 +410,10 @@ subroutine parse_structarray_block(parser, mf6_input, iblock, mshape, iout) end if ! ! -- set pointer to input definition for this 1d vector - idt => get_param_definition_type(mf6_input%p_param_dfns, & + idt => get_param_definition_type(mf6_input%param_dfns, & mf6_input%component_type, & mf6_input%subcomponent_type, & - mf6_input%p_block_dfns(iblock)%blockname, & + mf6_input%block_dfns(iblock)%blockname, & words(iwords)) ! ! -- allocate variable in memory manager diff --git a/src/Utilities/Idm/ModflowInput.f90 b/src/Utilities/Idm/ModflowInput.f90 index f9d70cf61f4..d8c316ecfc0 100644 --- a/src/Utilities/Idm/ModflowInput.f90 +++ b/src/Utilities/Idm/ModflowInput.f90 @@ -14,9 +14,9 @@ module ModflowInputModule use MemoryHelperModule, only: create_mem_path use InputDefinitionModule, only: InputParamDefinitionType, & InputBlockDefinitionType - use InputDefinitionSelectorModule, only: block_definitions, & - aggregate_definitions, & - param_definitions + use IdmDfnSelectorModule, only: block_definitions, & + aggregate_definitions, & + param_definitions use SimVariablesModule, only: idm_context implicit none @@ -38,10 +38,9 @@ module ModflowInputModule character(len=LENCOMPONENTNAME) :: component_name character(len=LENCOMPONENTNAME) :: subcomponent_name character(len=LENMEMPATH) :: mempath - character(len=LENMEMPATH) :: component - type(InputBlockDefinitionType), dimension(:), pointer :: p_block_dfns - type(InputParamDefinitionType), dimension(:), pointer :: p_aggregate_dfns - type(InputParamDefinitionType), dimension(:), pointer :: p_param_dfns + type(InputBlockDefinitionType), dimension(:), pointer :: block_dfns + type(InputParamDefinitionType), dimension(:), pointer :: aggregate_dfns + type(InputParamDefinitionType), dimension(:), pointer :: param_dfns end type ModflowInputType contains @@ -66,11 +65,11 @@ function getModflowInput(pkgtype, component_type, & mf6_input%mempath = create_mem_path(component_name, subcomponent_name, & idm_context) - mf6_input%component = trim(component_type)//'/'//trim(subcomponent_type) - mf6_input%p_block_dfns => block_definitions(mf6_input%component) - mf6_input%p_aggregate_dfns => aggregate_definitions(mf6_input%component) - mf6_input%p_param_dfns => param_definitions(mf6_input%component) + mf6_input%block_dfns => block_definitions(component_type, subcomponent_type) + mf6_input%aggregate_dfns => aggregate_definitions(component_type, & + subcomponent_type) + mf6_input%param_dfns => param_definitions(component_type, subcomponent_type) end function getModflowInput end module ModflowInputModule diff --git a/src/Utilities/Idm/selector/IdmDfnSelector.f90 b/src/Utilities/Idm/selector/IdmDfnSelector.f90 new file mode 100644 index 00000000000..ee3089c65a3 --- /dev/null +++ b/src/Utilities/Idm/selector/IdmDfnSelector.f90 @@ -0,0 +1,135 @@ +! ** Do Not Modify! MODFLOW 6 system generated file. ** +module IdmDfnSelectorModule + + use SimModule, only: store_error + use InputDefinitionModule, only: InputParamDefinitionType, & + InputBlockDefinitionType + use IdmGwfDfnSelectorModule, only: gwf_param_definitions, & + gwf_aggregate_definitions, & + gwf_block_definitions, & + gwf_idm_multi_package, & + gwf_idm_integrated + use IdmGwtDfnSelectorModule, only: gwt_param_definitions, & + gwt_aggregate_definitions, & + gwt_block_definitions, & + gwt_idm_multi_package, & + gwt_idm_integrated + use IdmSimDfnSelectorModule, only: sim_param_definitions, & + sim_aggregate_definitions, & + sim_block_definitions, & + sim_idm_multi_package, & + sim_idm_integrated + + implicit none + private + public :: param_definitions + public :: aggregate_definitions + public :: block_definitions + public :: idm_multi_package + public :: idm_integrated + +contains + + function param_definitions(component, subcomponent) result(input_definition) + character(len=*), intent(in) :: component + character(len=*), intent(in) :: subcomponent + type(InputParamDefinitionType), dimension(:), pointer :: input_definition + nullify (input_definition) + select case (component) + case ('GWF') + input_definition => gwf_param_definitions(subcomponent) + case ('GWT') + input_definition => gwt_param_definitions(subcomponent) + case ('SIM') + input_definition => sim_param_definitions(subcomponent) + case default + end select + if (.not. associated(input_definition)) then + call store_error('Idm param input definition list not found; '//& + &'component="'//trim(component)//& + &'", subcomponent="'//trim(subcomponent)//'".', .true.) + end if + return + end function param_definitions + + function aggregate_definitions(component, subcomponent) result(input_definition) + character(len=*), intent(in) :: component + character(len=*), intent(in) :: subcomponent + type(InputParamDefinitionType), dimension(:), pointer :: input_definition + nullify (input_definition) + select case (component) + case ('GWF') + input_definition => gwf_aggregate_definitions(subcomponent) + case ('GWT') + input_definition => gwt_aggregate_definitions(subcomponent) + case ('SIM') + input_definition => sim_aggregate_definitions(subcomponent) + case default + end select + if (.not. associated(input_definition)) then + call store_error('Idm aggregate input definition list not found; '//& + &'component="'//trim(component)//& + &'", subcomponent="'//trim(subcomponent)//'".', .true.) + end if + return + end function aggregate_definitions + + function block_definitions(component, subcomponent) result(input_definition) + character(len=*), intent(in) :: component + character(len=*), intent(in) :: subcomponent + type(InputBlockDefinitionType), dimension(:), pointer :: input_definition + nullify (input_definition) + select case (component) + case ('GWF') + input_definition => gwf_block_definitions(subcomponent) + case ('GWT') + input_definition => gwt_block_definitions(subcomponent) + case ('SIM') + input_definition => sim_block_definitions(subcomponent) + case default + end select + if (.not. associated(input_definition)) then + call store_error('Idm block input definition list not found; '//& + &'component="'//trim(component)//& + &'", subcomponent="'//trim(subcomponent)//'".', .true.) + end if + return + end function block_definitions + + function idm_multi_package(component, subcomponent) result(multi_package) + character(len=*), intent(in) :: component + character(len=*), intent(in) :: subcomponent + logical :: multi_package + select case (component) + case ('GWF') + multi_package = gwf_idm_multi_package(subcomponent) + case ('GWT') + multi_package = gwt_idm_multi_package(subcomponent) + case ('SIM') + multi_package = sim_idm_multi_package(subcomponent) + case default + call store_error('Idm selector component not found; '//& + &'component="'//trim(component)//& + &'", subcomponent="'//trim(subcomponent)//'".', .true.) + end select + return + end function idm_multi_package + + function idm_integrated(component, subcomponent) result(integrated) + character(len=*), intent(in) :: component + character(len=*), intent(in) :: subcomponent + logical :: integrated + integrated = .false. + select case (component) + case ('GWF') + integrated = gwf_idm_integrated(subcomponent) + case ('GWT') + integrated = gwt_idm_integrated(subcomponent) + case ('SIM') + integrated = sim_idm_integrated(subcomponent) + case default + end select + return + end function idm_integrated + +end module IdmDfnSelectorModule diff --git a/src/Utilities/Idm/selector/IdmGwfDfnSelector.f90 b/src/Utilities/Idm/selector/IdmGwfDfnSelector.f90 new file mode 100644 index 00000000000..836fb9a8bb0 --- /dev/null +++ b/src/Utilities/Idm/selector/IdmGwfDfnSelector.f90 @@ -0,0 +1,138 @@ +! ** Do Not Modify! MODFLOW 6 system generated file. ** +module IdmGwfDfnSelectorModule + + use SimModule, only: store_error + use InputDefinitionModule, only: InputParamDefinitionType, & + InputBlockDefinitionType + use GwfDisInputModule, only: gwf_dis_param_definitions, & + gwf_dis_aggregate_definitions, & + gwf_dis_block_definitions, & + gwf_dis_multi_package + use GwfDisuInputModule, only: gwf_disu_param_definitions, & + gwf_disu_aggregate_definitions, & + gwf_disu_block_definitions, & + gwf_disu_multi_package + use GwfDisvInputModule, only: gwf_disv_param_definitions, & + gwf_disv_aggregate_definitions, & + gwf_disv_block_definitions, & + gwf_disv_multi_package + use GwfNpfInputModule, only: gwf_npf_param_definitions, & + gwf_npf_aggregate_definitions, & + gwf_npf_block_definitions, & + gwf_npf_multi_package + + implicit none + private + public :: gwf_param_definitions + public :: gwf_aggregate_definitions + public :: gwf_block_definitions + public :: gwf_idm_multi_package + public :: gwf_idm_integrated + +contains + + subroutine set_param_pointer(input_dfn, input_dfn_target) + type(InputParamDefinitionType), dimension(:), pointer :: input_dfn + type(InputParamDefinitionType), dimension(:), target :: input_dfn_target + input_dfn => input_dfn_target + end subroutine set_param_pointer + + subroutine set_block_pointer(input_dfn, input_dfn_target) + type(InputBlockDefinitionType), dimension(:), pointer :: input_dfn + type(InputBlockDefinitionType), dimension(:), target :: input_dfn_target + input_dfn => input_dfn_target + end subroutine set_block_pointer + + function gwf_param_definitions(subcomponent) result(input_definition) + character(len=*), intent(in) :: subcomponent + type(InputParamDefinitionType), dimension(:), pointer :: input_definition + nullify (input_definition) + select case (subcomponent) + case ('DIS') + call set_param_pointer(input_definition, gwf_dis_param_definitions) + case ('DISU') + call set_param_pointer(input_definition, gwf_disu_param_definitions) + case ('DISV') + call set_param_pointer(input_definition, gwf_disv_param_definitions) + case ('NPF') + call set_param_pointer(input_definition, gwf_npf_param_definitions) + case default + end select + return + end function gwf_param_definitions + + function gwf_aggregate_definitions(subcomponent) result(input_definition) + character(len=*), intent(in) :: subcomponent + type(InputParamDefinitionType), dimension(:), pointer :: input_definition + nullify (input_definition) + select case (subcomponent) + case ('DIS') + call set_param_pointer(input_definition, gwf_dis_aggregate_definitions) + case ('DISU') + call set_param_pointer(input_definition, gwf_disu_aggregate_definitions) + case ('DISV') + call set_param_pointer(input_definition, gwf_disv_aggregate_definitions) + case ('NPF') + call set_param_pointer(input_definition, gwf_npf_aggregate_definitions) + case default + end select + return + end function gwf_aggregate_definitions + + function gwf_block_definitions(subcomponent) result(input_definition) + character(len=*), intent(in) :: subcomponent + type(InputBlockDefinitionType), dimension(:), pointer :: input_definition + nullify (input_definition) + select case (subcomponent) + case ('DIS') + call set_block_pointer(input_definition, gwf_dis_block_definitions) + case ('DISU') + call set_block_pointer(input_definition, gwf_disu_block_definitions) + case ('DISV') + call set_block_pointer(input_definition, gwf_disv_block_definitions) + case ('NPF') + call set_block_pointer(input_definition, gwf_npf_block_definitions) + case default + end select + return + end function gwf_block_definitions + + function gwf_idm_multi_package(subcomponent) result(multi_package) + character(len=*), intent(in) :: subcomponent + logical :: multi_package + select case (subcomponent) + case ('DIS') + multi_package = gwf_dis_multi_package + case ('DISU') + multi_package = gwf_disu_multi_package + case ('DISV') + multi_package = gwf_disv_multi_package + case ('NPF') + multi_package = gwf_npf_multi_package + case default + call store_error('Idm selector subcomponent not found; '//& + &'component="GWF"'//& + &', subcomponent="'//trim(subcomponent)//'".', .true.) + end select + return + end function gwf_idm_multi_package + + function gwf_idm_integrated(subcomponent) result(integrated) + character(len=*), intent(in) :: subcomponent + logical :: integrated + integrated = .false. + select case (subcomponent) + case ('DIS') + integrated = .true. + case ('DISU') + integrated = .true. + case ('DISV') + integrated = .true. + case ('NPF') + integrated = .true. + case default + end select + return + end function gwf_idm_integrated + +end module IdmGwfDfnSelectorModule diff --git a/src/Utilities/Idm/selector/IdmGwtDfnSelector.f90 b/src/Utilities/Idm/selector/IdmGwtDfnSelector.f90 new file mode 100644 index 00000000000..64dfbdca24e --- /dev/null +++ b/src/Utilities/Idm/selector/IdmGwtDfnSelector.f90 @@ -0,0 +1,96 @@ +! ** Do Not Modify! MODFLOW 6 system generated file. ** +module IdmGwtDfnSelectorModule + + use SimModule, only: store_error + use InputDefinitionModule, only: InputParamDefinitionType, & + InputBlockDefinitionType + use GwtDspInputModule, only: gwt_dsp_param_definitions, & + gwt_dsp_aggregate_definitions, & + gwt_dsp_block_definitions, & + gwt_dsp_multi_package + + implicit none + private + public :: gwt_param_definitions + public :: gwt_aggregate_definitions + public :: gwt_block_definitions + public :: gwt_idm_multi_package + public :: gwt_idm_integrated + +contains + + subroutine set_param_pointer(input_dfn, input_dfn_target) + type(InputParamDefinitionType), dimension(:), pointer :: input_dfn + type(InputParamDefinitionType), dimension(:), target :: input_dfn_target + input_dfn => input_dfn_target + end subroutine set_param_pointer + + subroutine set_block_pointer(input_dfn, input_dfn_target) + type(InputBlockDefinitionType), dimension(:), pointer :: input_dfn + type(InputBlockDefinitionType), dimension(:), target :: input_dfn_target + input_dfn => input_dfn_target + end subroutine set_block_pointer + + function gwt_param_definitions(subcomponent) result(input_definition) + character(len=*), intent(in) :: subcomponent + type(InputParamDefinitionType), dimension(:), pointer :: input_definition + nullify (input_definition) + select case (subcomponent) + case ('DSP') + call set_param_pointer(input_definition, gwt_dsp_param_definitions) + case default + end select + return + end function gwt_param_definitions + + function gwt_aggregate_definitions(subcomponent) result(input_definition) + character(len=*), intent(in) :: subcomponent + type(InputParamDefinitionType), dimension(:), pointer :: input_definition + nullify (input_definition) + select case (subcomponent) + case ('DSP') + call set_param_pointer(input_definition, gwt_dsp_aggregate_definitions) + case default + end select + return + end function gwt_aggregate_definitions + + function gwt_block_definitions(subcomponent) result(input_definition) + character(len=*), intent(in) :: subcomponent + type(InputBlockDefinitionType), dimension(:), pointer :: input_definition + nullify (input_definition) + select case (subcomponent) + case ('DSP') + call set_block_pointer(input_definition, gwt_dsp_block_definitions) + case default + end select + return + end function gwt_block_definitions + + function gwt_idm_multi_package(subcomponent) result(multi_package) + character(len=*), intent(in) :: subcomponent + logical :: multi_package + select case (subcomponent) + case ('DSP') + multi_package = gwt_dsp_multi_package + case default + call store_error('Idm selector subcomponent not found; '//& + &'component="GWT"'//& + &', subcomponent="'//trim(subcomponent)//'".', .true.) + end select + return + end function gwt_idm_multi_package + + function gwt_idm_integrated(subcomponent) result(integrated) + character(len=*), intent(in) :: subcomponent + logical :: integrated + integrated = .false. + select case (subcomponent) + case ('DSP') + integrated = .true. + case default + end select + return + end function gwt_idm_integrated + +end module IdmGwtDfnSelectorModule diff --git a/src/Utilities/Idm/selector/IdmSimDfnSelector.f90 b/src/Utilities/Idm/selector/IdmSimDfnSelector.f90 new file mode 100644 index 00000000000..dc216101720 --- /dev/null +++ b/src/Utilities/Idm/selector/IdmSimDfnSelector.f90 @@ -0,0 +1,96 @@ +! ** Do Not Modify! MODFLOW 6 system generated file. ** +module IdmSimDfnSelectorModule + + use SimModule, only: store_error + use InputDefinitionModule, only: InputParamDefinitionType, & + InputBlockDefinitionType + use SimNamInputModule, only: sim_nam_param_definitions, & + sim_nam_aggregate_definitions, & + sim_nam_block_definitions, & + sim_nam_multi_package + + implicit none + private + public :: sim_param_definitions + public :: sim_aggregate_definitions + public :: sim_block_definitions + public :: sim_idm_multi_package + public :: sim_idm_integrated + +contains + + subroutine set_param_pointer(input_dfn, input_dfn_target) + type(InputParamDefinitionType), dimension(:), pointer :: input_dfn + type(InputParamDefinitionType), dimension(:), target :: input_dfn_target + input_dfn => input_dfn_target + end subroutine set_param_pointer + + subroutine set_block_pointer(input_dfn, input_dfn_target) + type(InputBlockDefinitionType), dimension(:), pointer :: input_dfn + type(InputBlockDefinitionType), dimension(:), target :: input_dfn_target + input_dfn => input_dfn_target + end subroutine set_block_pointer + + function sim_param_definitions(subcomponent) result(input_definition) + character(len=*), intent(in) :: subcomponent + type(InputParamDefinitionType), dimension(:), pointer :: input_definition + nullify (input_definition) + select case (subcomponent) + case ('NAM') + call set_param_pointer(input_definition, sim_nam_param_definitions) + case default + end select + return + end function sim_param_definitions + + function sim_aggregate_definitions(subcomponent) result(input_definition) + character(len=*), intent(in) :: subcomponent + type(InputParamDefinitionType), dimension(:), pointer :: input_definition + nullify (input_definition) + select case (subcomponent) + case ('NAM') + call set_param_pointer(input_definition, sim_nam_aggregate_definitions) + case default + end select + return + end function sim_aggregate_definitions + + function sim_block_definitions(subcomponent) result(input_definition) + character(len=*), intent(in) :: subcomponent + type(InputBlockDefinitionType), dimension(:), pointer :: input_definition + nullify (input_definition) + select case (subcomponent) + case ('NAM') + call set_block_pointer(input_definition, sim_nam_block_definitions) + case default + end select + return + end function sim_block_definitions + + function sim_idm_multi_package(subcomponent) result(multi_package) + character(len=*), intent(in) :: subcomponent + logical :: multi_package + select case (subcomponent) + case ('NAM') + multi_package = sim_nam_multi_package + case default + call store_error('Idm selector subcomponent not found; '//& + &'component="SIM"'//& + &', subcomponent="'//trim(subcomponent)//'".', .true.) + end select + return + end function sim_idm_multi_package + + function sim_idm_integrated(subcomponent) result(integrated) + character(len=*), intent(in) :: subcomponent + logical :: integrated + integrated = .false. + select case (subcomponent) + case ('NAM') + integrated = .true. + case default + end select + return + end function sim_idm_integrated + +end module IdmSimDfnSelectorModule diff --git a/src/meson.build b/src/meson.build index 566c677bee1..521921ba295 100644 --- a/src/meson.build +++ b/src/meson.build @@ -140,15 +140,19 @@ modflow_sources = files( 'Utilities' / 'ArrayRead' / 'Integer1dReader.f90', 'Utilities' / 'ArrayRead' / 'Integer2dReader.f90', 'Utilities' / 'ArrayRead' / 'LayeredArrayReader.f90', + 'Utilities' / 'Idm' / 'IdmDfnSelectorUtils.f90', 'Utilities' / 'Idm' / 'IdmLogger.f90', 'Utilities' / 'Idm' / 'IdmMf6FileLoader.f90', 'Utilities' / 'Idm' / 'IdmSimulation.f90', 'Utilities' / 'Idm' / 'ModflowInput.f90', 'Utilities' / 'Idm' / 'InputDefinition.f90', - 'Utilities' / 'Idm' / 'InputDefinitionSelector.f90', 'Utilities' / 'Idm' / 'LoadMf6FileType.f90', 'Utilities' / 'Idm' / 'StructArray.f90', 'Utilities' / 'Idm' / 'StructVector.f90', + 'Utilities' / 'Idm' / 'selector' / 'IdmDfnSelector.f90', + 'Utilities' / 'Idm' / 'selector' / 'IdmGwfDfnSelector.f90', + 'Utilities' / 'Idm' / 'selector' / 'IdmGwtDfnSelector.f90', + 'Utilities' / 'Idm' / 'selector' / 'IdmSimDfnSelector.f90', 'Utilities' / 'Matrix' / 'MatrixBase.f90', 'Utilities' / 'Matrix' / 'SparseMatrix.f90', 'Utilities' / 'Memory' / 'Memory.f90', diff --git a/src/simnamidm.f90 b/src/simnamidm.f90 index 967c9a88990..84b59621420 100644 --- a/src/simnamidm.f90 +++ b/src/simnamidm.f90 @@ -1,3 +1,4 @@ +! ** Do Not Modify! MODFLOW 6 system generated file. ** module SimNamInputModule use InputDefinitionModule, only: InputParamDefinitionType, & InputBlockDefinitionType @@ -6,6 +7,7 @@ module SimNamInputModule public sim_nam_aggregate_definitions public sim_nam_block_definitions public SimNamParamFoundType + public sim_nam_multi_package type SimNamParamFoundType logical :: continue = .false. @@ -26,6 +28,8 @@ module SimNamInputModule logical :: slnmnames = .false. end type SimNamParamFoundType + logical :: sim_nam_multi_package = .false. + type(InputParamDefinitionType), parameter :: & simnam_continue = InputParamDefinitionType & ( & diff --git a/utils/idmloader/scripts/dfn2f90.py b/utils/idmloader/scripts/dfn2f90.py index ccc1a939163..642be37abb2 100644 --- a/utils/idmloader/scripts/dfn2f90.py +++ b/utils/idmloader/scripts/dfn2f90.py @@ -27,6 +27,7 @@ def __init__( self._param_varnames = [] self._aggregate_varnames = [] self._warnings = [] + self._multi_package = False self.component, self.subcomponent = self._dfnfspec.stem.upper().split("-") @@ -34,6 +35,15 @@ def __init__( self._set_var_d() self._set_param_strs() + def add_dfn_entry(self, dfn_d=None): + c_key = f"{self.component.upper()}" + sc_key = f"{self.subcomponent.upper()}" + + if c_key not in dfn_d: + dfn_d[c_key] = [] + + dfn_d[c_key].append(sc_key) + def write_f90(self, ofspec=None): with open(ofspec, "w") as f: @@ -42,7 +52,8 @@ def write_f90(self, ofspec=None): # found type f.write( - f" type {self.component.capitalize()}{self.subcomponent.capitalize()}ParamFoundType\n" + f" type {self.component.capitalize()}" + f"{self.subcomponent.capitalize()}ParamFoundType\n" ) for var in self._param_varnames: varname = var.split( @@ -50,7 +61,17 @@ def write_f90(self, ofspec=None): )[1] f.write(f" logical :: {varname} = .false.\n") f.write( - f" end type {self.component.capitalize()}{self.subcomponent.capitalize()}ParamFoundType\n\n" + f" end type {self.component.capitalize()}" + f"{self.subcomponent.capitalize()}ParamFoundType\n\n" + ) + + # multi package + smult = ".false." + if self._multi_package: + smult = ".true." + f.write( + f" logical :: {self.component.lower()}_" + f"{self.subcomponent.lower()}_multi_package = {smult}\n\n" ) # params @@ -137,6 +158,9 @@ def _set_var_d(self): # skip comments if "#" in line.strip()[0]: + # flopy multi-package + if "flopy multi-package" in line.strip(): + self._multi_package = True continue ll = line.strip().split() @@ -166,18 +190,22 @@ def _construct_f90_block_statement( ): f90statement = f" InputBlockDefinitionType( &\n" f90statement += f" '{blockname}', & ! blockname\n" + if required: f90statement += f" .true., & ! required\n" else: f90statement += f" .false., & ! required\n" + if aggregate: f90statement += f" .true., & ! aggregate\n" else: f90statement += f" .false., & ! aggregate\n" + if block_var: f90statement += f" .true. & ! block_variable\n" else: f90statement += f" .false. & ! block_variable\n" + f90statement += f" ), &" return f90statement @@ -186,13 +214,16 @@ def _construct_f90_param_statement( self, tuple_list, basename, varname, aggregate=False ): vname = f"{basename.lower()}_{varname.lower()}" + if aggregate: self._aggregate_varnames.append(vname) else: self._param_varnames.append(vname) + f90statement = f" type(InputParamDefinitionType), parameter :: &\n" f90statement += f" {vname} = InputParamDefinitionType &\n" f90statement += f" ( &\n" + for i, (value, varname) in enumerate(tuple_list): comma = "," if i + 1 == len(tuple_list): @@ -201,12 +232,14 @@ def _construct_f90_param_statement( if value in [".false.", ".true."]: v = f"{value}" f90statement += f" {v}{comma} & ! {varname}\n" + f90statement += f" )\n" return f90statement def _set_param_strs(self): blocknames = self.get_blocknames() + for b in blocknames: self._set_blk_param_strs(b, self.component, self.subcomponent) @@ -259,7 +292,8 @@ def _set_blk_param_strs(self, blockname, component, subcomponent): if len(mf6vn) > MF6_LENVARNAME: self._warnings.append( - f"MF6_LENVARNAME({MF6_LENVARNAME}) exceeded: {component}-{subcomponent}-{blockname}: {mf6vn}" + f"MF6_LENVARNAME({MF6_LENVARNAME}) exceeded: " + f"{component}-{subcomponent}-{blockname}: {mf6vn}" ) t = v["type"].upper() @@ -325,9 +359,6 @@ def _set_blk_param_strs(self, blockname, component, subcomponent): (layered, "layered"), ] - # assumes recarray type appears before and member - # parameter descriptions in dfn file, adjust - # if necessary if aggregate_t: self._aggregate_str += ( self._construct_f90_param_statement( @@ -339,7 +370,8 @@ def _set_blk_param_strs(self, blockname, component, subcomponent): aggregate_required = r == ".true." if not shape: self._warnings.append( - f"Aggregate type found with no shape: {component}-{subcomponent}-{blockname}: {mf6vn}" + f"Aggregate type found with no shape: " + f"{component}-{subcomponent}-{blockname}: {mf6vn}" ) else: @@ -354,6 +386,17 @@ def _set_blk_param_strs(self, blockname, component, subcomponent): required = aggregate_required else: required = ".true." in required_l + + if self._block_str == "" and blockname.upper() != "OPTIONS": + self._block_str += ( + self._construct_f90_block_statement( + "OPTIONS", + required=False, + aggregate=False, + ) + + "\n" + ) + self._block_str += ( self._construct_f90_block_statement( blockname.upper(), @@ -365,60 +408,54 @@ def _set_blk_param_strs(self, blockname, component, subcomponent): ) def _source_file_header(self, component, subcomponent): - s = f"module {component.title()}{subcomponent.title()}InputModule" + "\n" - s += ( - " use InputDefinitionModule, only: InputParamDefinitionType, &" - + "\n" - + " InputBlockDefinitionType" - + "\n" - ) - s += " private" + "\n" - s += ( - f" public {component.lower()}_{subcomponent.lower()}_param_definitions" - + "\n" - ) - s += ( - f" public {component.lower()}_{subcomponent.lower()}_aggregate_definitions" - + "\n" - ) - s += ( - f" public {component.lower()}_{subcomponent.lower()}_block_definitions" - + "\n" - ) - s += ( - f" public {component.capitalize()}{subcomponent.capitalize()}ParamFoundType" - + "\n\n" + s = ( + f"! ** Do Not Modify! MODFLOW 6 system generated file. **\n" + f"module {component.title()}{subcomponent.title()}InputModule\n" + f" use InputDefinitionModule, only: InputParamDefinitionType, &\n" + f" InputBlockDefinitionType\n" + f" private\n" + f" public {component.lower()}_{subcomponent.lower()}_" + f"param_definitions\n" + f" public {component.lower()}_{subcomponent.lower()}_" + f"aggregate_definitions\n" + f" public {component.lower()}_{subcomponent.lower()}_" + f"block_definitions\n" + f" public {component.capitalize()}{subcomponent.capitalize()}" + f"ParamFoundType\n" + f" public {component.lower()}_{subcomponent.lower()}_" + f"multi_package\n\n" ) + return s def _source_params_header(self, component, subcomponent): s = ( - f" type(InputParamDefinitionType), parameter :: &" - + "\n" - + f" {component.lower()}_{subcomponent.lower()}_param_definitions(*) = &" - + "\n" + f" type(InputParamDefinitionType), parameter :: &\n" + f" {component.lower()}_{subcomponent.lower()}_param_" + f"definitions(*) = &\n" + f" [ &\n" ) - s += " [ &" + "\n" + return s def _source_aggregates_header(self, component, subcomponent): s = ( - f" type(InputParamDefinitionType), parameter :: &" - + "\n" - + f" {component.lower()}_{subcomponent.lower()}_aggregate_definitions(*) = &" - + "\n" + f" type(InputParamDefinitionType), parameter :: &\n" + f" {component.lower()}_{subcomponent.lower()}_aggregate_" + f"definitions(*) = &\n" + f" [ &\n" ) - s += " [ &" + "\n" + return s def _source_blocks_header(self, component, subcomponent): s = ( - f" type(InputBlockDefinitionType), parameter :: &" - + "\n" - + f" {component.lower()}_{subcomponent.lower()}_block_definitions(*) = &" - + "\n" + f" type(InputBlockDefinitionType), parameter :: &\n" + f" {component.lower()}_{subcomponent.lower()}_block_" + f"definitions(*) = &\n" + f" [ &\n" ) - s += " [ &" + "\n" + return s def _source_list_footer(self, component, subcomponent): @@ -426,14 +463,347 @@ def _source_list_footer(self, component, subcomponent): return s def _source_file_footer(self, component, subcomponent): - s = f"end module {component.title()}{subcomponent.title()}InputModule" + "\n" + s = f"end module {component.title()}{subcomponent.title()}InputModule\n" return s +class IdmDfnSelector: + """generate idm f90 selector files derived from set of f90 definition files""" + + def __init__( + self, + dfn_d: dict = None, + ): + """IdmDfnSelector init""" + + self._d = dfn_d + + def write(self): + self._write_selectors() + self._write_master() + + def _write_master(self): + ofspec = f"../../../src/Utilities/Idm/selector/IdmDfnSelector.f90" + with open(ofspec, "w") as fh: + self._write_master_decl(fh) + self._write_master_defn(fh, defn="param", dtype="param") + self._write_master_defn(fh, defn="aggregate", dtype="param") + self._write_master_defn(fh, defn="block", dtype="block") + self._write_master_multi(fh) + self._write_master_integration(fh) + fh.write(f"end module IdmDfnSelectorModule\n") + + def _write_selectors(self): + for c in self._d: + ofspec = ( + f"../../../src/Utilities/Idm/selector/Idm{c.title()}DfnSelector.f90" + ) + with open(ofspec, "w") as fh: + self._write_selector_decl(fh, component=c, sc_list=self._d[c]) + self._write_selector_helpers(fh) + self._write_selector_defn( + fh, component=c, sc_list=self._d[c], defn="param", dtype="param" + ) + self._write_selector_defn( + fh, component=c, sc_list=self._d[c], defn="aggregate", dtype="param" + ) + self._write_selector_defn( + fh, component=c, sc_list=self._d[c], defn="block", dtype="block" + ) + self._write_selector_multi(fh, component=c, sc_list=self._d[c]) + self._write_selector_integration(fh, component=c, sc_list=self._d[c]) + fh.write(f"end module Idm{c.title()}DfnSelectorModule\n") + + def _write_selector_decl(self, fh=None, component=None, sc_list=None): + space = " " + c = component + len_c = len(c) + + s = ( + f"! ** Do Not Modify! MODFLOW 6 system generated file. **\n" + f"module Idm{c.title()}DfnSelectorModule\n\n" + f" use SimModule, only: store_error\n" + f" use InputDefinitionModule, only: InputParamDefinitionType, &\n" + f" InputBlockDefinitionType\n" + ) + + for sc in sc_list: + len_sc = len(sc) + spacer = space * (len_c + len_sc) + + s += ( + f" use {c.title()}{sc.title()}InputModule, only: " + f"{c.lower()}_{sc.lower()}_param_definitions, &" + f"\n {spacer}" + f"{c.lower()}_{sc.lower()}_aggregate_definitions, &" + f"\n {spacer}" + f"{c.lower()}_{sc.lower()}_block_definitions, &" + f"\n {spacer}" + f"{c.lower()}_{sc.lower()}_multi_package\n" + ) + + s += ( + f"\n implicit none\n" + f" private\n" + f" public :: {c.lower()}_param_definitions\n" + f" public :: {c.lower()}_aggregate_definitions\n" + f" public :: {c.lower()}_block_definitions\n" + f" public :: {c.lower()}_idm_multi_package\n" + f" public :: {c.lower()}_idm_integrated\n\n" + f"contains\n\n" + ) + + fh.write(s) + + def _write_selector_helpers(self, fh=None): + s = ( + f" subroutine set_param_pointer(input_dfn, input_dfn_target)\n" + f" type(InputParamDefinitionType), dimension(:), " + f"pointer :: input_dfn\n" + f" type(InputParamDefinitionType), dimension(:), " + f"target :: input_dfn_target\n" + f" input_dfn => input_dfn_target\n" + f" end subroutine set_param_pointer\n\n" + ) + + s += ( + f" subroutine set_block_pointer(input_dfn, input_dfn_target)\n" + f" type(InputBlockDefinitionType), dimension(:), " + f"pointer :: input_dfn\n" + f" type(InputBlockDefinitionType), dimension(:), " + f"target :: input_dfn_target\n" + f" input_dfn => input_dfn_target\n" + f" end subroutine set_block_pointer\n\n" + ) + + fh.write(s) + + def _write_selector_defn( + self, fh=None, component=None, sc_list=None, defn=None, dtype=None + ): + c = component + + s = ( + f" function {c.lower()}_{defn.lower()}_definitions(subcomponent) " + f"result(input_definition)\n" + f" character(len=*), intent(in) :: subcomponent\n" + f" type(Input{dtype.title()}DefinitionType), dimension(:), " + f"pointer :: input_definition\n" + f" nullify (input_definition)\n" + f" select case (subcomponent)\n" + ) + + for sc in sc_list: + s += ( + f" case ('{sc}')\n" + f" call set_{dtype.lower()}_pointer(input_definition, " + f"{c.lower()}_{sc.lower()}_{defn.lower()}_definitions)\n" + ) + + s += ( + f" case default\n" + f" end select\n" + f" return\n" + f" end function {c.lower()}_{defn.lower()}_definitions\n\n" + ) + + fh.write(s) + + def _write_selector_multi(self, fh=None, component=None, sc_list=None): + c = component + + s = ( + f" function {c.lower()}_idm_multi_package(subcomponent) " + f"result(multi_package)\n" + f" character(len=*), intent(in) :: subcomponent\n" + f" logical :: multi_package\n" + f" select case (subcomponent)\n" + ) + + for sc in sc_list: + s += ( + f" case ('{sc}')\n" + f" multi_package = {c.lower()}_{sc.lower()}_" + f"multi_package\n" + ) + + s += ( + f" case default\n" + f" call store_error('Idm selector subcomponent " + f"not found; '//&\n" + f" &'component=\"{c.upper()}\"'//&\n" + f" &', subcomponent=\"'//trim(subcomponent)" + f"//'\".', .true.)\n" + f" end select\n" + f" return\n" + f" end function {c.lower()}_idm_multi_package\n\n" + ) + + fh.write(s) + + def _write_selector_integration(self, fh=None, component=None, sc_list=None): + c = component + + s = ( + f" function {c.lower()}_idm_integrated(subcomponent) " + f"result(integrated)\n" + f" character(len=*), intent(in) :: subcomponent\n" + f" logical :: integrated\n" + f" integrated = .false.\n" + f" select case (subcomponent)\n" + ) + + for sc in sc_list: + s += f" case ('{sc}')\n" + s += f" integrated = .true.\n" + + s += ( + f" case default\n" + f" end select\n" + f" return\n" + f" end function {c.lower()}_idm_integrated\n\n" + ) + + fh.write(s) + + def _write_master_decl(self, fh=None): + space = " " + + s = ( + f"! ** Do Not Modify! MODFLOW 6 system generated file. **\n" + f"module IdmDfnSelectorModule\n\n" + f" use SimModule, only: store_error\n" + f" use InputDefinitionModule, only: InputParamDefinitionType, &\n" + f" InputBlockDefinitionType\n" + ) + + for c in self._d: + len_c = len(c) + spacer = space * (len_c) + s += ( + f" use Idm{c.title()}DfnSelectorModule, only: " + f"{c.lower()}_param_definitions, &" + f"\n {spacer}" + f"{c.lower()}_aggregate_definitions, &" + f"\n {spacer}" + f"{c.lower()}_block_definitions, &" + f"\n {spacer}" + f"{c.lower()}_idm_multi_package, &" + f"\n {spacer}" + f"{c.lower()}_idm_integrated\n" + ) + + s += ( + f"\n implicit none\n" + f" private\n" + f" public :: param_definitions\n" + f" public :: aggregate_definitions\n" + f" public :: block_definitions\n" + f" public :: idm_multi_package\n" + f" public :: idm_integrated\n\n" + f"contains\n\n" + ) + + fh.write(s) + + def _write_master_defn(self, fh=None, defn=None, dtype=None): + s = ( + f" function {defn.lower()}_definitions(component, subcomponent) " + f"result(input_definition)\n" + f" character(len=*), intent(in) :: component\n" + f" character(len=*), intent(in) :: subcomponent\n" + f" type(Input{dtype.title()}DefinitionType), dimension(:), " + f"pointer :: input_definition\n" + f" nullify (input_definition)\n" + f" select case (component)\n" + ) + + for c in dfn_d: + s += ( + f" case ('{c}')\n" + f" input_definition => {c.lower()}_{defn.lower()}_" + f"definitions(subcomponent)\n" + ) + + s += ( + f" case default\n" + f" end select\n" + f" if (.not. associated(input_definition)) then\n" + f" call store_error('Idm {defn.lower()} input definition " + f"list not found; '//&\n" + f" &'component=\"'//trim(component)//&\n" + f" &'\", subcomponent=\"'//trim(subcomponent)" + f"//'\".', .true.)\n" + f" end if\n" + f" return\n" + f" end function {defn.lower()}_definitions\n\n" + ) + + fh.write(s) + + def _write_master_multi(self, fh=None): + s = ( + f" function idm_multi_package(component, subcomponent) " + f"result(multi_package)\n" + f" character(len=*), intent(in) :: component\n" + f" character(len=*), intent(in) :: subcomponent\n" + f" logical :: multi_package\n" + f" select case (component)\n" + ) + + for c in dfn_d: + s += ( + f" case ('{c}')\n" + f" multi_package = {c.lower()}_idm_multi_" + f"package(subcomponent)\n" + ) + + s += ( + f" case default\n" + f" call store_error('Idm selector component not found; '//&\n" + f" &'component=\"'//trim(component)//&\n" + f" &'\", subcomponent=\"'//trim(subcomponent)" + f"//'\".', .true.)\n" + f" end select\n" + f" return\n" + f" end function idm_multi_package\n\n" + ) + + fh.write(s) + + def _write_master_integration(self, fh=None): + s = ( + f" function idm_integrated(component, subcomponent) " + f"result(integrated)\n" + f" character(len=*), intent(in) :: component\n" + f" character(len=*), intent(in) :: subcomponent\n" + f" logical :: integrated\n" + f" integrated = .false.\n" + f" select case (component)\n" + ) + + for c in dfn_d: + s += ( + f" case ('{c}')\n" + f" integrated = {c.lower()}_idm_" + f"integrated(subcomponent)\n" + ) + + s += ( + f" case default\n" + f" end select\n" + f" return\n" + f" end function idm_integrated\n\n" + ) + + fh.write(s) + + if __name__ == "__main__": dfns = [ - # list per dfn [dfn relative path, model parent dirname, output filename] + # ** Add a new dfn parameter set to MODFLOW 6 by adding a new entry to this list ** + # [relative path of input dnf, relative path of output f90 definition file] [ Path("../../../doc/mf6io/mf6ivar/dfn", "gwf-dis.dfn"), Path("../../../src/Model/GroundWaterFlow", "gwf3dis8idm.f90"), @@ -460,9 +830,13 @@ def _source_file_footer(self, component, subcomponent): ], ] + dfn_d = {} for dfn in dfns: converter = Dfn2F90(dfnfspec=dfn[0]) converter.write_f90(ofspec=dfn[1]) converter.warn() + converter.add_dfn_entry(dfn_d=dfn_d) + selectors = IdmDfnSelector(dfn_d=dfn_d) + selectors.write() print("\n...done.") From 8698584eff9e7e14bc5916cc7b77085d4a4b979a Mon Sep 17 00:00:00 2001 From: langevin-usgs Date: Fri, 24 Mar 2023 10:31:05 -0500 Subject: [PATCH 053/123] feat(ems): add hooks for explicit model solution (#1161) --- doc/mf6io/mf6ivar/dfn/sln-ems.dfn | 1 + doc/mf6io/mf6ivar/mf6ivar.py | 1 + doc/mf6io/mf6ivar/tex/sln-ems-desc.tex | 3 + make/makefile | 62 ++-- msvs/mf6core.vfproj | 62 +++- src/Model/ExplicitModel.f90 | 174 +++++++++++ src/SimulationCreate.f90 | 40 ++- src/Solution/ExplicitSolution.f90 | 395 +++++++++++++++++++++++++ src/Solution/SolutionFactory.F90 | 33 +++ src/meson.build | 2 + utils/mf5to6/make/makefile | 8 +- 11 files changed, 738 insertions(+), 43 deletions(-) create mode 100644 doc/mf6io/mf6ivar/dfn/sln-ems.dfn create mode 100644 doc/mf6io/mf6ivar/tex/sln-ems-desc.tex create mode 100644 src/Model/ExplicitModel.f90 create mode 100644 src/Solution/ExplicitSolution.f90 diff --git a/doc/mf6io/mf6ivar/dfn/sln-ems.dfn b/doc/mf6io/mf6ivar/dfn/sln-ems.dfn new file mode 100644 index 00000000000..03ff8e88694 --- /dev/null +++ b/doc/mf6io/mf6ivar/dfn/sln-ems.dfn @@ -0,0 +1 @@ +# flopy solution_package ems * diff --git a/doc/mf6io/mf6ivar/mf6ivar.py b/doc/mf6io/mf6ivar/mf6ivar.py index 6e8b1c8d1ac..91474697b28 100644 --- a/doc/mf6io/mf6ivar/mf6ivar.py +++ b/doc/mf6io/mf6ivar/mf6ivar.py @@ -639,6 +639,7 @@ def write_appendix(texdir, allblocks): 'exg-gwfgwt', 'exg-gwtgwt', 'sln-ims', # dfn completed tex updated + 'sln-ems', # dfn completed tex updated 'gwf-nam', # dfn completed tex updated 'gwf-dis', # dfn completed tex updated 'gwf-disv', # dfn completed tex updated diff --git a/doc/mf6io/mf6ivar/tex/sln-ems-desc.tex b/doc/mf6io/mf6ivar/tex/sln-ems-desc.tex new file mode 100644 index 00000000000..bf95ac4119f --- /dev/null +++ b/doc/mf6io/mf6ivar/tex/sln-ems-desc.tex @@ -0,0 +1,3 @@ +% DO NOT MODIFY THIS FILE DIRECTLY. IT IS CREATED BY mf6ivar.py + + diff --git a/make/makefile b/make/makefile index 3f80aed761a..f5add4d6526 100644 --- a/make/makefile +++ b/make/makefile @@ -5,34 +5,35 @@ include ./makedefaults # Define the source file directories SOURCEDIR1=../src -SOURCEDIR2=../src/Distributed -SOURCEDIR3=../src/Exchange -SOURCEDIR4=../src/Model -SOURCEDIR5=../src/Model/Connection -SOURCEDIR6=../src/Model/Geometry -SOURCEDIR7=../src/Model/GroundWaterFlow -SOURCEDIR8=../src/Model/GroundWaterTransport -SOURCEDIR9=../src/Model/ModelUtilities -SOURCEDIR10=../src/Solution -SOURCEDIR11=../src/Solution/LinearMethods -SOURCEDIR12=../src/Solution/PETSc -SOURCEDIR13=../src/Timing -SOURCEDIR14=../src/Utilities -SOURCEDIR15=../src/Utilities/ArrayRead -SOURCEDIR16=../src/Utilities/Idm -SOURCEDIR17=../src/Utilities/Idm/selector -SOURCEDIR18=../src/Utilities/Libraries -SOURCEDIR19=../src/Utilities/Libraries/blas -SOURCEDIR20=../src/Utilities/Libraries/daglib -SOURCEDIR21=../src/Utilities/Libraries/rcm -SOURCEDIR22=../src/Utilities/Libraries/sparsekit -SOURCEDIR23=../src/Utilities/Libraries/sparskit2 -SOURCEDIR24=../src/Utilities/Matrix -SOURCEDIR25=../src/Utilities/Memory -SOURCEDIR26=../src/Utilities/Observation -SOURCEDIR27=../src/Utilities/OutputControl -SOURCEDIR28=../src/Utilities/TimeSeries -SOURCEDIR29=../src/Utilities/Vector +SOURCEDIR2=../src/Exchange +SOURCEDIR3=../src/Distributed +SOURCEDIR4=../src/Solution +SOURCEDIR5=../src/Solution/LinearMethods +SOURCEDIR6=../src/Solution/PETSc +SOURCEDIR7=../src/Timing +SOURCEDIR8=../src/Utilities +SOURCEDIR9=../src/Utilities/Idm +SOURCEDIR10=../src/Utilities/Idm/selector +SOURCEDIR11=../src/Utilities/TimeSeries +SOURCEDIR12=../src/Utilities/Memory +SOURCEDIR13=../src/Utilities/OutputControl +SOURCEDIR14=../src/Utilities/ArrayRead +SOURCEDIR15=../src/Utilities/Libraries +SOURCEDIR16=../src/Utilities/Libraries/rcm +SOURCEDIR17=../src/Utilities/Libraries/blas +SOURCEDIR18=../src/Utilities/Libraries/sparskit2 +SOURCEDIR19=../src/Utilities/Libraries/daglib +SOURCEDIR20=../src/Utilities/Libraries/sparsekit +SOURCEDIR21=../src/Utilities/Vector +SOURCEDIR22=../src/Utilities/Matrix +SOURCEDIR23=../src/Utilities/Observation +SOURCEDIR24=../src/Model +SOURCEDIR25=../src/Model/Connection +SOURCEDIR26=../src/Model/GroundWaterTransport +SOURCEDIR27=../src/Model/ModelUtilities +SOURCEDIR28=../src/Model/GroundWaterFlow +SOURCEDIR29=../src/Model/Geometry +SOURCEDIR30=../src/Model/StreamNetworkFlow VPATH = \ ${SOURCEDIR1} \ @@ -63,7 +64,8 @@ ${SOURCEDIR25} \ ${SOURCEDIR26} \ ${SOURCEDIR27} \ ${SOURCEDIR28} \ -${SOURCEDIR29} +${SOURCEDIR29} \ +${SOURCEDIR30} .SUFFIXES: .f90 .F90 .o @@ -254,6 +256,7 @@ $(OBJDIR)/Timer.o \ $(OBJDIR)/LinearSolverFactory.o \ $(OBJDIR)/ims8linear.o \ $(OBJDIR)/BaseSolution.o \ +$(OBJDIR)/ExplicitModel.o \ $(OBJDIR)/SpatialModelConnection.o \ $(OBJDIR)/GwtInterfaceModel.o \ $(OBJDIR)/GwtGwtExchange.o \ @@ -262,6 +265,7 @@ $(OBJDIR)/GwfGwfExchange.o \ $(OBJDIR)/RouterFactory.o \ $(OBJDIR)/NumericalSolution.o \ $(OBJDIR)/MappedMemory.o \ +$(OBJDIR)/ExplicitSolution.o \ $(OBJDIR)/GwtGwtConnection.o \ $(OBJDIR)/GwfGwfConnection.o \ $(OBJDIR)/VirtualDataManager.o \ diff --git a/msvs/mf6core.vfproj b/msvs/mf6core.vfproj index 098a8ef1cc3..e103b4f706a 100644 --- a/msvs/mf6core.vfproj +++ b/msvs/mf6core.vfproj @@ -50,22 +50,34 @@ + - + + + - + + + - + + + - + + + + + + @@ -180,6 +192,7 @@ + @@ -192,26 +205,41 @@ + - + + + - + + + + + + + + - + + + + + + @@ -249,8 +277,10 @@ + - + + @@ -282,8 +312,10 @@ + - + + @@ -299,12 +331,20 @@ + + + + + + + + @@ -334,9 +374,13 @@ + + + + diff --git a/src/Model/ExplicitModel.f90 b/src/Model/ExplicitModel.f90 new file mode 100644 index 00000000000..ae5630b83da --- /dev/null +++ b/src/Model/ExplicitModel.f90 @@ -0,0 +1,174 @@ +!> @brief Explicit Model Module +!! +!! This module contains the Explicit Model, which is a parent +!! class for models that solve themselves. Explicit models are +!! added to an Explicit Solution, which is simply a container +!! that scrolls through a list of explicit models and calls +!! methods in a prescribed sequence. +!! +!< +module ExplicitModelModule + + use KindModule, only: I4B + use ListModule, only: ListType + use BaseModelModule, only: BaseModelType + use BaseDisModule, only: DisBaseType + + implicit none + private + public :: ExplicitModelType, & + CastAsExplicitModelClass, & + AddExplicitModelToList, & + GetExplicitModelFromList + + !> @brief Derived type for the Explicit Model Type + !! + !! This derived type describes a parent class for explicit + !! models. + !! + !< + type, extends(BaseModelType) :: ExplicitModelType + type(ListType), pointer :: bndlist => null() !< array of boundary packages for this model + class(DisBaseType), pointer :: dis => null() !< discretization object + contains + procedure :: model_ad + procedure :: model_solve + procedure :: model_cq + procedure :: model_bd + procedure :: model_da + procedure :: allocate_scalars + end type ExplicitModelType + +contains + + !> @ brief Advance the model + !< + subroutine model_ad(this) + class(ExplicitModelType) :: this + ! + ! -- return + return + end subroutine model_ad + + !> @ brief Solve the model + !< + subroutine model_solve(this) + class(ExplicitModelType) :: this + ! + ! -- return + return + end subroutine model_solve + + !> @ brief Calculate model flows + !< + subroutine model_cq(this, icnvg, isuppress_output) + class(ExplicitModelType) :: this + integer(I4B), intent(in) :: icnvg + integer(I4B), intent(in) :: isuppress_output + ! + ! -- return + return + end subroutine model_cq + + !> @ brief Calculate model budget + !< + subroutine model_bd(this, icnvg, isuppress_output) + class(ExplicitModelType) :: this + integer(I4B), intent(in) :: icnvg + integer(I4B), intent(in) :: isuppress_output + ! + ! -- return + return + end subroutine model_bd + + !> @ brief Deallocate the model + !< + subroutine model_da(this) + ! -- modules + use MemoryManagerModule, only: mem_deallocate + class(ExplicitModelType) :: this + + ! -- Scalars + ! + ! -- Arrays + ! + ! -- derived types + call this%bndlist%Clear() + deallocate (this%bndlist) + ! + ! -- nullify pointers + ! + ! -- BaseModelType + call this%BaseModelType%model_da() + ! + ! -- Return + return + end subroutine model_da + + !> @ brief Allocate model scalar variables + !< + subroutine allocate_scalars(this, modelname) + use MemoryManagerModule, only: mem_allocate + class(ExplicitModelType) :: this + character(len=*), intent(in) :: modelname + ! + ! -- allocate basetype members + call this%BaseModelType%allocate_scalars(modelname) + ! + ! -- allocate members from this type + allocate (this%bndlist) + ! + ! -- initialize + ! + ! -- return + return + end subroutine allocate_scalars + + !> @ brief Cast a generic object into an explicit model + !< + function CastAsExplicitModelClass(obj) result(res) + class(*), pointer, intent(inout) :: obj + class(ExplicitModelType), pointer :: res + ! + res => null() + if (.not. associated(obj)) return + ! + select type (obj) + class is (ExplicitModelType) + res => obj + end select + return + end function CastAsExplicitModelClass + + !> @ brief Add explicit model to a generic list + !< + subroutine AddExplicitModelToList(list, model) + ! -- dummy + type(ListType), intent(inout) :: list + class(ExplicitModelType), pointer, intent(inout) :: model + ! -- local + class(*), pointer :: obj + ! + obj => model + call list%Add(obj) + ! + return + end subroutine AddExplicitModelToList + + !> @ brief Get generic object from list and return as explicit model + !< + function GetExplicitModelFromList(list, idx) result(res) + ! -- dummy + type(ListType), intent(inout) :: list + integer(I4B), intent(in) :: idx + class(ExplicitModelType), pointer :: res + ! -- local + class(*), pointer :: obj + ! + obj => list%GetItem(idx) + res => CastAsExplicitModelClass(obj) + ! + return + end function GetExplicitModelFromList + +end module ExplicitModelModule diff --git a/src/SimulationCreate.f90 b/src/SimulationCreate.f90 index f89de79315d..d98fc31935e 100644 --- a/src/SimulationCreate.f90 +++ b/src/SimulationCreate.f90 @@ -481,7 +481,7 @@ subroutine solution_groups_create() use SimVariablesModule, only: idm_context, simulation_mode use SolutionGroupModule, only: SolutionGroupType, & solutiongroup_create - use SolutionFactoryModule, only: create_ims_solution + use SolutionFactoryModule, only: create_ims_solution, create_ems_solution use BaseSolutionModule, only: BaseSolutionType use BaseModelModule, only: BaseModelType use BaseExchangeModule, only: BaseExchangeType @@ -609,6 +609,44 @@ subroutine solution_groups_create() call sp%add_model(mp) mp%idsoln = isoln end do + case ('EMS6') + ! + ! -- increment solution counters + isoln = isoln + 1 + isgpsoln = isgpsoln + 1 + ! + ! -- create soln and add to group + sp => create_ems_solution(simulation_mode, fname, isoln) + call sgp%add_solution(isoln, sp) + ! + ! -- parse model names + parse_str = trim(mnames)//' ' + call parseline(parse_str, nwords, words) + ! + ! -- Find each model id and get model + do j = 1, nwords + call upcase(words(j)) + glo_mid = ifind(model_names, words(j)) + if (glo_mid == -1) then + write (errmsg, '(a,a)') 'Error. Invalid model name: ', & + trim(words(j)) + call store_error(errmsg, terminate) + end if + ! + loc_idx = model_loc_idx(glo_mid) + if (loc_idx == -1) then + if (simulation_mode == 'PARALLEL') then + ! this is still ok + cycle + end if + end if + ! + mp => GetBaseModelFromList(basemodellist, loc_idx) + ! + ! -- Add the model to the solution + call sp%add_model(mp) + mp%idsoln = isoln + end do case default end select end do diff --git a/src/Solution/ExplicitSolution.f90 b/src/Solution/ExplicitSolution.f90 new file mode 100644 index 00000000000..fa1618e723b --- /dev/null +++ b/src/Solution/ExplicitSolution.f90 @@ -0,0 +1,395 @@ +!> @brief Explicit Solution Module +!! +!! This module contains the Explicit Solution, which is a +!! class for solving explicit models. The explicit solution +!! scrolls through a list of explicit models and calls +!! methods in a prescribed sequence. +!! +!< +module ExplicitSolutionModule + use KindModule, only: I4B, DP + use TimerModule, only: code_timer + use ConstantsModule, only: LENMEMPATH, LENSOLUTIONNAME, MVALIDATE, & + MNORMAL, LINELENGTH, DZERO + use MemoryHelperModule, only: create_mem_path + use BaseModelModule, only: BaseModelType + use ExplicitModelModule, only: ExplicitModelType, & + AddExplicitModelToList, & + GetExplicitModelFromList + use BaseExchangeModule, only: BaseExchangeType + use BaseSolutionModule, only: BaseSolutionType, AddBaseSolutionToList + use ListModule, only: ListType + use ListsModule, only: basesolutionlist + use SimVariablesModule, only: iout, isim_mode + use BlockParserModule, only: BlockParserType + + implicit none + private + + !> @brief Derived type for the Explicit Solution Type + !! + !! This derived type describes the solution for managing and + !! solving explicit models. + !! + !< + public :: create_explicit_solution + public :: ExplicitSolutionType + + type, extends(BaseSolutionType) :: ExplicitSolutionType + character(len=LENMEMPATH) :: memoryPath !< the path for storing solution variables in the memory manager + type(ListType), pointer :: modellist !< list of models in solution + integer(I4B), pointer :: id !< solution number + integer(I4B), pointer :: iu !< input file unit + real(DP), pointer :: ttsoln !< timer - total solution time + integer(I4B), pointer :: icnvg => null() !< convergence flag + type(BlockParserType) :: parser !< block parser object + contains + procedure :: sln_df + procedure :: sln_ar + procedure :: sln_calculate_delt + procedure :: sln_ad + procedure :: sln_ot + procedure :: sln_ca + procedure :: sln_fp + procedure :: sln_da + procedure :: add_model + procedure :: add_exchange + procedure :: get_models + procedure :: get_exchanges + procedure :: save + + procedure, private :: allocate_scalars + + ! Expose these for use through the BMI/XMI: + procedure, public :: prepareSolve + procedure, public :: solve + procedure, public :: finalizeSolve + + end type ExplicitSolutionType + +contains + + !> @ brief Create a new solution + !! + !! Create a new solution using the data in filename, assign this new + !! solution an id number and store the solution in the basesolutionlist. + !! Also open the filename for later reading. + !! + !< + subroutine create_explicit_solution(exp_sol, filename, id) + ! -- modules + use InputOutputModule, only: getunit, openfile + ! -- dummy variables + class(ExplicitSolutionType), pointer :: exp_sol !< the create solution + character(len=*), intent(in) :: filename !< solution input file name + integer(I4B), intent(in) :: id !< solution id + ! -- local variables + integer(I4B) :: inunit + class(BaseSolutionType), pointer :: solbase => null() + character(len=LENSOLUTIONNAME) :: solutionname + ! + ! -- Create a new solution and add it to the basesolutionlist container + solbase => exp_sol + write (solutionname, '(a, i0)') 'SLN_', id + + exp_sol%name = solutionname + exp_sol%memoryPath = create_mem_path(solutionname) + allocate (exp_sol%modellist) + !todo: do we need this? allocate (exp_sol%exchangelist) + ! + call exp_sol%allocate_scalars() + ! + call AddBaseSolutionToList(basesolutionlist, solbase) + ! + exp_sol%id = id + ! + ! -- Open solution input file for reading later after problem size is known + ! Check to see if the file is already opened, which can happen when + ! running in single model mode + inquire (file=filename, number=inunit) + + if (inunit < 0) inunit = getunit() + exp_sol%iu = inunit + write (iout, '(/a,a/)') ' Creating explicit solution (EMS): ', exp_sol%name + call openfile(exp_sol%iu, iout, filename, 'IMS') + ! + ! -- Initialize block parser + call exp_sol%parser%Initialize(exp_sol%iu, iout) + ! + ! -- return + return + end subroutine create_explicit_solution + + !> @ brief Allocate scalars + !! + !! Allocate scalars for a new solution. + !! + !< + subroutine allocate_scalars(this) + ! -- modules + use MemoryManagerModule, only: mem_allocate + ! -- dummy variables + class(ExplicitSolutionType) :: this !< ExplicitSolutionType instance + ! + ! -- allocate scalars + call mem_allocate(this%id, 'ID', this%memoryPath) + call mem_allocate(this%iu, 'IU', this%memoryPath) + call mem_allocate(this%ttsoln, 'TTSOLN', this%memoryPath) + call mem_allocate(this%icnvg, 'ICNVG', this%memoryPath) + ! + ! -- initialize + this%id = 0 + this%iu = 0 + this%ttsoln = DZERO + this%icnvg = 0 + ! + ! -- return + return + end subroutine allocate_scalars + + !> @ brief Solution define + !< + subroutine sln_df(this) + class(ExplicitSolutionType) :: this + end subroutine + + !> @ brief Solution allocate and read + !< + subroutine sln_ar(this) + ! -- dummy variables + class(ExplicitSolutionType) :: this !< ExplicitSolutionType instance + ! + ! -- close ems input file + call this%parser%Clear() + ! + ! -- return + return + end subroutine sln_ar + + !> @ brief Solution calculate time step length + !< + subroutine sln_calculate_delt(this) + class(ExplicitSolutionType) :: this !< ExplicitSolutionType instance + end subroutine sln_calculate_delt + + !> @ brief Solution advance + !< + subroutine sln_ad(this) + ! -- dummy variables + class(ExplicitSolutionType) :: this !< ExplicitSolutionType instance + ! + ! -- reset convergence flag + this%icnvg = 0 + + return + end subroutine sln_ad + + !> @ brief Solution output + !< + subroutine sln_ot(this) + class(ExplicitSolutionType) :: this !< ExplicitSolutionType instance + end subroutine sln_ot + + subroutine sln_fp(this) + class(ExplicitSolutionType) :: this !< ExplicitSolutionType instance + end subroutine sln_fp + + !> @ brief Solution deallocate + !< + subroutine sln_da(this) + ! -- modules + use MemoryManagerModule, only: mem_deallocate + ! -- dummy variables + class(ExplicitSolutionType) :: this !< ExplicitSolutionType instance + ! + ! -- lists + call this%modellist%Clear() + deallocate (this%modellist) + ! + ! + ! -- Scalars + call mem_deallocate(this%id) + call mem_deallocate(this%iu) + call mem_deallocate(this%ttsoln) + call mem_deallocate(this%icnvg) + ! + ! -- return + return + end subroutine sln_da + + !> @ brief Solution calculate + !< + subroutine sln_ca(this, isgcnvg, isuppress_output) + ! -- modules + ! -- dummy variables + class(ExplicitSolutionType) :: this !< ExplicitSolutionType instance + integer(I4B), intent(inout) :: isgcnvg !< solution group convergence flag + integer(I4B), intent(in) :: isuppress_output !< flag for suppressing output + ! -- local variables + class(ExplicitModelType), pointer :: mp => null() + character(len=LINELENGTH) :: line + character(len=LINELENGTH) :: fmt + integer(I4B) :: im + + ! advance the models and solution + call this%prepareSolve() + + select case (isim_mode) + case (MVALIDATE) + line = 'mode="validation" -- Skipping assembly and solution.' + fmt = "(/,1x,a,/)" + do im = 1, this%modellist%Count() + mp => GetExplicitModelFromList(this%modellist, im) + call mp%model_message(line, fmt=fmt) + end do + case (MNORMAL) + + ! solve the models + call this%solve() + + ! finish up + call this%finalizeSolve(isgcnvg, isuppress_output) + end select + ! + ! -- return + return + + end subroutine sln_ca + + !> @ brief Solution prepare to solve + !< + subroutine prepareSolve(this) + ! -- dummy variables + class(ExplicitSolutionType) :: this !< ExplicitSolutionType instance + ! -- local variables + integer(I4B) :: im + class(ExplicitModelType), pointer :: mp => null() + + ! -- Model advance + do im = 1, this%modellist%Count() + mp => GetExplicitModelFromList(this%modellist, im) + call mp%model_ad() + end do + + ! advance solution + call this%sln_ad() + + end subroutine prepareSolve + + !> @ brief Solution solve each model + !< + subroutine solve(this) + ! -- dummy variables + class(ExplicitSolutionType) :: this !< ExplicitSolutionType instance + ! -- local variables + class(ExplicitModelType), pointer :: mp => null() + integer(I4B) :: im + real(DP) :: ttsoln + ! + ! -- particle solve + call code_timer(0, ttsoln, this%ttsoln) + do im = 1, this%modellist%Count() + mp => GetExplicitModelFromList(this%modellist, im) + call mp%model_solve() + end do + call code_timer(1, ttsoln, this%ttsoln) + ! + this%icnvg = 1 + + end subroutine solve + + !> @ brief Solution finalize solve + !< + subroutine finalizeSolve(this, isgcnvg, isuppress_output) + ! -- dummy variables + class(ExplicitSolutionType) :: this !< ExplicitSolutionType instance + integer(I4B), intent(inout) :: isgcnvg !< solution group convergence flag + integer(I4B), intent(in) :: isuppress_output !< flag for suppressing output + ! -- local variables + integer(I4B) :: im + class(ExplicitModelType), pointer :: mp => null() + ! + ! -- Calculate flow for each model + do im = 1, this%modellist%Count() + mp => GetExplicitModelFromList(this%modellist, im) + call mp%model_cq(this%icnvg, isuppress_output) + end do + ! + ! -- Budget terms for each model + do im = 1, this%modellist%Count() + mp => GetExplicitModelFromList(this%modellist, im) + call mp%model_bd(this%icnvg, isuppress_output) + end do + ! + end subroutine finalizeSolve + + !> @ brief Solution save + !< + subroutine save(this, filename) + ! -- modules + use InputOutputModule, only: getunit + ! -- dummy variables + class(ExplicitSolutionType) :: this !< ExplicitSolutionType instance + character(len=*), intent(in) :: filename !< filename to save solution data + ! -- local variables + integer(I4B) :: inunit + ! + inunit = getunit() + open (unit=inunit, file=filename, status='unknown') + write (inunit, *) 'The save routine currently writes nothing' + close (inunit) + ! + ! -- return + return + end subroutine save + + !> @ brief Solution explicit model to list + !< + subroutine add_model(this, mp) + ! -- dummy variables + class(ExplicitSolutionType) :: this !< ExplicitSolutionType instance + class(BaseModelType), pointer, intent(in) :: mp !< model instance + ! -- local variables + class(ExplicitModelType), pointer :: m => null() + ! + ! -- add a model + select type (mp) + class is (ExplicitModelType) + m => mp + call AddExplicitModelToList(this%modellist, m) + end select + ! + ! -- return + return + end subroutine add_model + + !> @brief Get a list of models + !! + !! Returns a pointer to the list of models in this solution. + !! + !< + function get_models(this) result(models) + ! -- return variable + type(ListType), pointer :: models !< pointer to the model list + ! -- dummy variables + class(ExplicitSolutionType) :: this !< ExplicitSolutionType instance + + models => this%modellist + + end function get_models + + !> @ brief Solution add exchange to list of exchanges + !< + subroutine add_exchange(this, exchange) + class(ExplicitSolutionType) :: this + class(BaseExchangeType), pointer, intent(in) :: exchange + end subroutine add_exchange + + !> @ brief Solution get list of exchanges + !< + function get_exchanges(this) result(exchanges) + class(ExplicitSolutionType) :: this + type(ListType), pointer :: exchanges + end function get_exchanges + +end module ExplicitSolutionModule \ No newline at end of file diff --git a/src/Solution/SolutionFactory.F90 b/src/Solution/SolutionFactory.F90 index ef7a7b550fc..729383139f5 100644 --- a/src/Solution/SolutionFactory.F90 +++ b/src/Solution/SolutionFactory.F90 @@ -4,6 +4,8 @@ module SolutionFactoryModule use BaseSolutionModule use NumericalSolutionModule, only: NumericalSolutionType, & create_numerical_solution + use ExplicitSolutionModule, only: ExplicitSolutionType, & + create_explicit_solution #if defined(__WITH_MPI__) use ParallelSolutionModule, only: ParallelSolutionType #endif @@ -12,6 +14,7 @@ module SolutionFactoryModule private public :: create_ims_solution + public :: create_ems_solution contains @@ -45,4 +48,34 @@ function create_ims_solution(sim_mode, filename, sol_id) result(base_sol) end function create_ims_solution + !> @brief Create an EMS solution of type ExplicitSolution + !! for serial runs or its sub-type ParallelSolution for + !< parallel runs. Returns the base pointer. + function create_ems_solution(sim_mode, filename, sol_id) result(base_sol) + character(len=*) :: sim_mode + character(len=*) :: filename + integer(I4B) :: sol_id + class(BaseSolutionType), pointer :: base_sol + class(ExplicitSolutionType), pointer :: exp_sol => null() +!#if defined(__WITH_MPI__) +! class(ParallelSolutionType), pointer :: par_sol => null() +!#endif + + if (sim_mode == 'SEQUENTIAL') then + allocate (exp_sol) +!#if defined(__WITH_MPI__) +! else if (sim_mode == 'PARALLEL') then +! allocate (par_sol) +! exp_sol => par_sol +!#endif + else + call ustop('Unsupported simulation mode for creating solution: '& + &//trim(sim_mode)) + end if + + call create_explicit_solution(exp_sol, filename, sol_id) + base_sol => exp_sol + + end function create_ems_solution + end module diff --git a/src/meson.build b/src/meson.build index 521921ba295..e53368834f0 100644 --- a/src/meson.build +++ b/src/meson.build @@ -118,6 +118,7 @@ modflow_sources = files( 'Model' / 'ModelUtilities' / 'Xt3dAlgorithm.f90', 'Model' / 'ModelUtilities' / 'Xt3dInterface.f90', 'Model' / 'BaseModel.f90', + 'Model' / 'ExplicitModel.f90', 'Model' / 'NumericalModel.f90', 'Model' / 'NumericalPackage.f90', 'Model' / 'TransportModel.f90', @@ -127,6 +128,7 @@ modflow_sources = files( 'Solution' / 'LinearMethods' / 'ims8misc.f90', 'Solution' / 'LinearMethods' / 'ImsLinearSolver.f90', 'Solution' / 'BaseSolution.f90', + 'Solution' / 'ExplicitSolution.f90', 'Solution' / 'LinearSolverBase.f90', 'Solution' / 'LinearSolverFactory.F90', 'Solution' / 'NumericalSolution.f90', diff --git a/utils/mf5to6/make/makefile b/utils/mf5to6/make/makefile index e1c0b88e1e3..640aac22dea 100644 --- a/utils/mf5to6/make/makefile +++ b/utils/mf5to6/make/makefile @@ -5,10 +5,10 @@ include ./makedefaults # Define the source file directories SOURCEDIR1=../src -SOURCEDIR2=../src/LGR -SOURCEDIR3=../src/MF2005 -SOURCEDIR4=../src/NWT -SOURCEDIR5=../src/Preproc +SOURCEDIR2=../src/NWT +SOURCEDIR3=../src/LGR +SOURCEDIR4=../src/Preproc +SOURCEDIR5=../src/MF2005 SOURCEDIR6=../../../src/Utilities/Memory SOURCEDIR7=../../../src/Utilities/TimeSeries SOURCEDIR8=../../../src/Utilities From 98ffc1f131f7358f04a37f86a0b75794f4692631 Mon Sep 17 00:00:00 2001 From: jdhughes-usgs Date: Fri, 24 Mar 2023 17:27:43 -0500 Subject: [PATCH 054/123] bug(lak): address available water issue for evaporation (#1173) Add residual convergence check in `lak_cc`. Add `lak_bisection` subroutine that results evaluates bisection until the residual is reduced. Exclude a few testmodels examples as a result of LAK package changes. Updated dfn files and release notes. --- autotest/test_gwf_laket.py | 317 ++++++++++++++++++++++ autotest/test_gwt_lkt02.py | 84 +++--- autotest/test_z01_testmodels_mf6.py | 9 + autotest/test_z03_examples.py | 1 + autotest/test_z03_largetestmodels.py | 4 +- doc/ReleaseNotes/v6.5.0.tex | 4 +- doc/mf6io/mf6ivar/dfn/gwf-lak.dfn | 16 ++ doc/mf6io/mf6ivar/dfn/gwf-sfr.dfn | 4 +- doc/mf6io/mf6ivar/md/mf6ivar.md | 6 +- doc/mf6io/mf6ivar/tex/gwf-lak-desc.tex | 4 + doc/mf6io/mf6ivar/tex/gwf-lak-options.dat | 2 + doc/mf6io/mf6ivar/tex/gwf-sfr-desc.tex | 4 +- src/Model/GroundWaterFlow/gwf3evt8.f90 | 7 +- src/Model/GroundWaterFlow/gwf3lak8.f90 | 197 +++++++++++--- src/Model/GroundWaterFlow/gwf3rch8.f90 | 7 +- src/Model/GroundWaterFlow/gwf3sfr8.f90 | 3 +- src/Utilities/Constants.f90 | 2 + 17 files changed, 570 insertions(+), 101 deletions(-) create mode 100644 autotest/test_gwf_laket.py diff --git a/autotest/test_gwf_laket.py b/autotest/test_gwf_laket.py new file mode 100644 index 00000000000..b180ff3baed --- /dev/null +++ b/autotest/test_gwf_laket.py @@ -0,0 +1,317 @@ +# Test for checking lak evaporation. + + +import os +import shutil +import sys + +import flopy +import numpy as np +import pytest +from framework import TestFramework +from simulation import TestSimulation + +ex = [ + "gwf_laket01", + "gwf_laket02", + "gwf_laket03", +] + +bedleak = [0.0, 1.0, 1.0] +strt = [5.0, 6.0, 4.0] +lakestage = [6.0, 5.0, 6.0] + + +def get_model(idx, ws): + name = ex[idx] + nlay = 1 + nrow = 1 + ncol = 1 + nper = 2 + delc = 1.0 + delr = 1.0 + delz = 10.0 + top = 5.0 + botm = [top - (k + 1) * delz for k in range(nlay)] + + perlen = [11.0, 11.0] + nstp = [11, 11] + tsmult = [1.0, 1.0] + + Kh = 1.0 + Kv = 1.0 + + tdis_rc = [] + for i in range(nper): + tdis_rc.append((perlen[i], nstp[i], tsmult[i])) + + # build MODFLOW 6 files + sim = flopy.mf6.MFSimulation( + sim_name=name, + version="mf6", + exe_name="mf6", + sim_ws=ws, + ) + + # create tdis package + tdis = flopy.mf6.ModflowTdis( + sim, + time_units="DAYS", + nper=nper, + perioddata=tdis_rc, + ) + + ims = flopy.mf6.ModflowIms( + sim, + print_option="ALL", + complexity="simple", + linear_acceleration="BICGSTAB", + ) + + # create gwf model + gwfname = name + gwf = flopy.mf6.ModflowGwf(sim, modelname=gwfname, newtonoptions="NEWTON") + + # discretization package + dis = flopy.mf6.ModflowGwfdis( + gwf, + nlay=nlay, + nrow=nrow, + ncol=ncol, + delr=delr, + delc=delc, + top=top, + botm=botm, + ) + + # initial conditions + ic = flopy.mf6.ModflowGwfic(gwf, strt=strt[idx]) + + # node property flow + npf = flopy.mf6.ModflowGwfnpf( + gwf, + xt3doptions=False, + save_specific_discharge=False, + icelltype=1, + k=Kh, + k33=Kv, + ) + + sy = 0.3 + ss = 1e-5 + sto = flopy.mf6.ModflowGwfsto( + gwf, + ss_confined_only=False, + sy=sy, + ss=ss, + iconvert=1, + ) + + lake_vconnect = [ + (0, 0, 0), + ] + nlakeconn = 1 + + # pak_data = [lakeno, strt, nlakeconn] + pak_data = [(0, lakestage[idx], nlakeconn)] + + belev = top + con_data = [ + (0, i, cellid, "VERTICAL", bedleak[idx], belev, -99, -99, -99) + for i, cellid in enumerate(lake_vconnect) + ] + + # period data + p_data = { + 0: [ + (0, "EVAPORATION", 0.1), + ], + 1: [ + (0, "RAINFALL", 0.2), + ], + } + + # note: for specifying lake number, use fortran indexing! + fname = f"{gwfname}.lak.obs.csv" + lak_obs = { + fname: [ + ("lakestage", "stage", (0,)), + ("lakevolume", "volume", (0,)), + ("evap", "evaporation", (0,)), + ], + } + + lak = flopy.mf6.modflow.ModflowGwflak( + gwf, + surfdep=0.0, + print_input=True, + print_flows=True, + print_stage=True, + nlakes=len(pak_data), + ntables=0, + packagedata=pak_data, + pname="LAK-1", + connectiondata=con_data, + perioddata=p_data, + observations=lak_obs, + package_convergence_filerecord=f"{gwfname}.lak.convergence.csv", + ) + + if idx > 0: + ghb = flopy.mf6.modflow.ModflowGwfghb( + gwf, + stress_period_data=[ + (0, 0, 0, strt[idx], 1000.0), + ], + ) + # chd = flopy.mf6.modflow.ModflowGwfchd(gwf, stress_period_data=[(0, 0, 0, strt[idx]),]) + + # output control + oc = flopy.mf6.ModflowGwfoc( + gwf, + printrecord=[("BUDGET", "ALL"), ("HEAD", "ALL")], + ) + + return sim + + +def build_model(idx, dir): + + # build MODFLOW 6 files + sim = get_model(idx, dir) + + return sim, None + + +def eval_laket(sim): + msg = "Evaluating Lake ET. " + + fpth = os.path.join(sim.simpath, f"{sim.name}.lak.obs.csv") + try: + tc = np.genfromtxt(fpth, names=True, delimiter=",") + except: + assert False, f'could not load data from "{fpth}"' + + dtype = [("time", float), ("stage", float), ("evap", float)] + obs = { + 0: np.array( + [ + (1.000000000000, 5.9000000000000004, -0.1), + (2.000000000000, 5.8000000000000007, -0.1), + (3.000000000000, 5.7000000000000011, -0.1), + (4.000000000000, 5.6000000000000014, -0.1), + (5.000000000000, 5.5000000000000018, -0.1), + (6.000000000000, 5.4000000000000021, -0.1), + (7.000000000000, 5.3000000000000025, -0.1), + (8.000000000000, 5.2000000000000028, -0.1), + (9.000000000000, 5.1000000000000032, -0.1), + (10.00000000000, 5.0000000000000036, -0.1), + (11.00000000000, 5.0000000000000000, 0.0), + (12.00000000000, 5.0999999999999996, -0.1), + (13.00000000000, 5.1999999999999993, -0.1), + (14.00000000000, 5.2999999999999989, -0.1), + (15.00000000000, 5.3999999999999986, -0.1), + (16.00000000000, 5.4999999999999982, -0.1), + (17.00000000000, 5.5999999999999979, -0.1), + (18.00000000000, 5.6999999999999975, -0.1), + (19.00000000000, 5.7999999999999972, -0.1), + (20.00000000000, 5.8999999999999968, -0.1), + (21.00000000000, 5.9999999999999964, -0.1), + (22.00000000000, 6.0999999999999961, -0.1), + ], + dtype=dtype, + ), + 1: np.array( + [ + (1.000000000000, 5.0571204119063822, -0.1), + (2.000000000000, 5.1060819299607383, -0.1), + (3.000000000000, 5.1480499445118104, -0.1), + (4.000000000000, 5.1840233847838952, -0.1), + (5.000000000000, 5.2148584962098710, -0.1), + (6.000000000000, 5.2412892209184587, -0.1), + (7.000000000000, 5.2639446671391816, -0.1), + (8.000000000000, 5.2833640833345035, -0.1), + (9.000000000000, 5.3000096934757943, -0.1), + (10.00000000000, 5.3142776989703462, -0.1), + (11.00000000000, 5.3265077091090305, -0.1), + (12.00000000000, 5.5084234768802327, -0.1), + (13.00000000000, 5.6643549898781638, -0.1), + (14.00000000000, 5.7980137542817234, -0.1), + (15.00000000000, 5.9125811368359047, -0.1), + (16.00000000000, 6.0107840882389674, -0.1), + (17.00000000000, 6.0949600504480319, -0.1), + (18.00000000000, 6.1671125928369053, -0.1), + (19.00000000000, 6.2289591014666277, -0.1), + (20.00000000000, 6.2819716565762063, -0.1), + (21.00000000000, 6.3274120712659299, -0.1), + (22.00000000000, 6.3663619253694703, -0.1), + ], + dtype=dtype, + ), + 2: np.array( + [ + (1.000000000000, 5.7714285714285714, -0.1), + (2.000000000000, 5.5755102040816329, -0.1), + (3.000000000000, 5.4075801749271140, -0.1), + (4.000000000000, 5.2636401499375260, -0.1), + (5.000000000000, 5.1402629856607369, -0.1), + (6.000000000000, 5.0345111305663464, -0.1), + (7.000000000000, 5.0000000000000000, -0.0345111305663464), + (8.000000000000, 5.0000000000000000, 0.0), + (9.000000000000, 5.0000000000000000, 0.0), + (10.00000000000, 5.0000000000000000, 0.0), + (11.00000000000, 5.0000000000000000, 0.0), + (12.00000000000, 5.0857142857142854, -0.1), + (13.00000000000, 5.1591836734693874, -0.1), + (14.00000000000, 5.2221574344023320, -0.1), + (15.00000000000, 5.2761349437734273, -0.1), + (16.00000000000, 5.3224013803772232, -0.1), + (17.00000000000, 5.3620583260376202, -0.1), + (18.00000000000, 5.3960499937465318, -0.1), + (19.00000000000, 5.4251857089255990, -0.1), + (20.00000000000, 5.4501591790790851, -0.1), + (21.00000000000, 5.4715650106392157, -0.1), + (22.00000000000, 5.4899128662621850, -0.1), + ], + dtype=dtype, + ), + } + + if sim.idxsim in ( + 0, + 1, + 2, + ): + evap_compare = np.allclose(obs[sim.idxsim]["evap"], tc["EVAP"]) + stage_compare = np.allclose(obs[sim.idxsim]["stage"], tc["LAKESTAGE"]) + else: + evap_compare = True + stage_compare = True + + sim.success = True + if not evap_compare: + sim.success = False + msg += f" Lake evaporation comparison failed." + if not stage_compare: + sim.success = False + msg += f" Lake stage comparison failed." + assert sim.success, msg + + +@pytest.mark.parametrize( + "idx, name", + list(enumerate(ex)), +) +def test_mf6model(idx, name, function_tmpdir, targets): + test = TestFramework() + test.build(build_model, idx, str(function_tmpdir)) + test.run( + TestSimulation( + name=name, + exe_dict=targets, + exfunc=eval_laket, + idxsim=idx, + mf6_regression=False, + ), + str(function_tmpdir), + ) diff --git a/autotest/test_gwt_lkt02.py b/autotest/test_gwt_lkt02.py index 15c29b6b9aa..616fb514397 100644 --- a/autotest/test_gwt_lkt02.py +++ b/autotest/test_gwt_lkt02.py @@ -365,7 +365,7 @@ def eval_results(sim): # load the lake concentrations and make sure all values are correct cobj = flopy.utils.HeadFile(fname, text="CONCENTRATION") clak = cobj.get_data() - answer = np.array([2.20913605e-01, 2.06598617e-03, 1.64112298e-05]) + answer = np.array([2.20917292e-01, 2.06602236e-03, 1.64115202e-05]) assert np.allclose(clak, answer), f"{clak} {answer}" # load the aquifer concentrations and make sure all values are correct @@ -376,12 +376,12 @@ def eval_results(sim): answer = np.array( [ 1.00000000e02, - 8.50686091e00, - 5.71594204e-01, - 1.30062708e-02, - 2.38399700e-04, - 3.30711200e-06, - 7.33445279e-08, + 8.50692758e00, + 5.71603342e-01, + 1.30064549e-02, + 2.38402430e-04, + 3.30714374e-06, + 7.33450747e-08, ] ) assert np.allclose(caq, answer), f"{caq.flatten()} {answer}" @@ -394,46 +394,46 @@ def eval_results(sim): assert False, f'could not load data from "{fpth}"' res = tc["LKT1CONC"] answer = [ - 0.00418347, - 0.01249363, - 0.02487425, - 0.04126975, - 0.06162508, - 0.0858858, - 0.113998, - 0.1459085, - 0.1815644, - 0.2209136, + 0.00418356, + 0.01249386, + 0.02487472, + 0.04127051, + 0.06162619, + 0.08588733, + 0.114, + 0.145911, + 0.1815675, + 0.2209173, ] answer = np.array(answer) assert np.allclose(res, answer), f"{res} {answer}" res = tc["LKT1STOR"] answer = [ - -0.1988482, - -0.3949968, - -0.588474, - -0.779308, - -0.9675264, - -1.153157, - -1.336226, - -1.516762, - -1.694791, - -1.87034, + -0.198852, + -0.3950041, + -0.5884846, + -0.7793216, + -0.9675429, + -1.153176, + -1.336248, + -1.516786, + -1.694817, + -1.870368, ] answer = np.array(answer) assert np.allclose(res, answer), f"{res} {answer}" res = tc["LKT1MYLAKE1"] answer = [ - 0.1992666, - 0.3962462, - 0.5909615, - 0.7834349, - 0.9736889, - 1.161745, - 1.347626, - 1.531353, - 1.712948, - 1.892431, + 0.1992704, + 0.3962535, + 0.5909721, + 0.7834487, + 0.9737055, + 1.161765, + 1.347648, + 1.531377, + 1.712974, + 1.89246, ] answer = np.array(answer) assert np.allclose(res, answer), f"{res} {answer}" @@ -461,8 +461,8 @@ def eval_results(sim): # check the flow-ja-face terms res = bobj.get_data(text="flow-ja-face")[-1] answer = [ - (1, 2, -0.02209136), - (2, 1, 0.02209136), + (1, 2, -0.02209173), + (2, 1, 0.02209173), (2, 3, -0.0002066), (3, 2, 0.0002066), ] @@ -473,9 +473,9 @@ def eval_results(sim): # check the storage terms, which include the total mass in the lake as an aux variable res = bobj.get_data(text="storage")[-1] answer = [ - (1, 1, -1.87033970e00, 1.05004295e-01), - (2, 2, -2.18847617e-02, 8.85953709e-04), - (3, 3, -2.10987695e-04, 6.88867607e-06), + (1, 1, -1.87036784e00, 1.05006009e-01), + (2, 2, -2.18851269e-02, 8.85969084e-04), + (3, 3, -2.10991316e-04, 6.88879732e-06), ] dt = [("node", "] [MOVER] [SURFDEP ] + [MAXIMUM_ITERATIONS ] + [MAXIMUM_STAGE_CHANGE ] [TIME_CONVERSION ] [LENGTH_CONVERSION ] END OPTIONS diff --git a/doc/mf6io/mf6ivar/tex/gwf-sfr-desc.tex b/doc/mf6io/mf6ivar/tex/gwf-sfr-desc.tex index aae2ba58c49..b0664a7f91f 100644 --- a/doc/mf6io/mf6ivar/tex/gwf-sfr-desc.tex +++ b/doc/mf6io/mf6ivar/tex/gwf-sfr-desc.tex @@ -47,9 +47,9 @@ \item \texttt{maximum\_picard\_iterations}---integer value that defines the maximum number of Streamflow Routing picard iterations allowed when solving for reach stages and flows as part of the GWF formulate step. Picard iterations are used to minimize differences in SFR package results between subsequent GWF picard (non-linear) iterations as a result of non-optimal reach numbering. If reaches are numbered in order, from upstream to downstream, MAXIMUM\_PICARD\_ITERATIONS can be set to 1 to reduce model run time. By default, MAXIMUM\_PICARD\_ITERATIONS is equal to 100. -\item \texttt{maximum\_iterations}---integer value that defines the maximum number of Streamflow Routing Newton-Raphson iterations allowed for a reach. By default, MAXIMUM\_ITERATIONS is equal to 100. +\item \texttt{maximum\_iterations}---integer value that defines the maximum number of Streamflow Routing Newton-Raphson iterations allowed for a reach. By default, MAXIMUM\_ITERATIONS is equal to 100. MAXIMUM\_ITERATIONS would only need to be increased from the default value if one or more reach in a simulation has a large water budget error. -\item \texttt{maximum\_depth\_change}---real value that defines the depth closure tolerance. By default, DMAXCHG is equal to $1 \times 10^{-5}$. +\item \texttt{maximum\_depth\_change}---real value that defines the depth closure tolerance. By default, MAXIMUM\_DEPTH\_CHANGE is equal to $1 \times 10^{-5}$. The MAXIMUM\_STAGE\_CHANGE would only need to be increased or decreased from the default value if the water budget error for one or more reach is too small or too large, respectively. \item \texttt{length\_conversion}---real value that is used to convert user-specified Manning's roughness coefficients from meters to model length units. LENGTH\_CONVERSION should be set to 3.28081, 1.0, and 100.0 when using length units (LENGTH\_UNITS) of feet, meters, or centimeters in the simulation, respectively. LENGTH\_CONVERSION does not need to be specified if LENGTH\_UNITS are meters. diff --git a/src/Model/GroundWaterFlow/gwf3evt8.f90 b/src/Model/GroundWaterFlow/gwf3evt8.f90 index 5283b1bf4b8..1be0ecbf2d6 100644 --- a/src/Model/GroundWaterFlow/gwf3evt8.f90 +++ b/src/Model/GroundWaterFlow/gwf3evt8.f90 @@ -1,7 +1,8 @@ module EvtModule ! use KindModule, only: DP, I4B - use ConstantsModule, only: DZERO, DONE, LENFTYPE, LENPACKAGENAME, MAXCHARLEN + use ConstantsModule, only: DZERO, DONE, LENFTYPE, LENPACKAGENAME, MAXCHARLEN, & + IWETLAKE use MemoryHelperModule, only: create_mem_path use BndModule, only: BndType use SimModule, only: store_error, store_error_unit, count_errors @@ -628,7 +629,7 @@ subroutine evt_cf(this, reset_mover) this%hcof(i) = DZERO ! ! -- if ibound is positive and not overlain by a lake, then add terms - if (this%ibound(node) > 0 .and. this%ibound(node) /= 10000) then + if (this%ibound(node) > 0 .and. this%ibound(node) /= IWETLAKE) then ! c = this%bound(2, i) ! RATE -- max. ET rate s = this%bound(1, i) ! SURFACE -- ET surface elevation @@ -743,7 +744,7 @@ subroutine evt_fc(this, rhs, ia, idxglo, matrix_sln) n = this%nodelist(i) if (n <= 0) cycle ! -- reset hcof and rhs for excluded cells - if (this%ibound(n) == 10000) then + if (this%ibound(n) == IWETLAKE) then this%hcof(i) = DZERO this%rhs(i) = DZERO cycle diff --git a/src/Model/GroundWaterFlow/gwf3lak8.f90 b/src/Model/GroundWaterFlow/gwf3lak8.f90 index 447b540276f..c540c5baced 100644 --- a/src/Model/GroundWaterFlow/gwf3lak8.f90 +++ b/src/Model/GroundWaterFlow/gwf3lak8.f90 @@ -2,8 +2,9 @@ module LakModule ! use KindModule, only: DP, I4B use ConstantsModule, only: LINELENGTH, LENBOUNDNAME, LENTIMESERIESNAME, & + IWETLAKE, MAXADPIT, & DZERO, DPREC, DEM30, DEM9, DEM6, DEM5, & - DEM4, DEM2, DEM1, DHALF, DP7, DONE, & + DEM4, DEM2, DEM1, DHALF, DP7, DP999, DONE, & DTWO, DPI, DTHREE, DEIGHT, DTEN, DHUNDRED, DEP20, & DONETHIRD, DTWOTHIRDS, DFIVETHIRDS, & DGRAVITY, DCD, & @@ -25,7 +26,7 @@ module LakModule use InputOutputModule, only: get_node, URWORD, extract_idnum_or_bndname use BaseDisModule, only: DisBaseType use SimModule, only: count_errors, store_error, store_error_unit - use GenericUtilitiesModule, only: sim_message + use GenericUtilitiesModule, only: sim_message, is_same use BlockParserModule, only: BlockParserType use BaseDisModule, only: DisBaseType use SimVariablesModule, only: errmsg @@ -52,6 +53,7 @@ module LakModule ! -- characters character(len=16), dimension(:), pointer, contiguous :: clakbudget => NULL() character(len=16), dimension(:), pointer, contiguous :: cauxcbc => NULL() + ! -- control variables ! -- integers integer(I4B), pointer :: iprhed => null() integer(I4B), pointer :: istageout => null() @@ -68,7 +70,9 @@ module LakModule integer(I4B), pointer :: igwhcopt => NULL() integer(I4B), pointer :: iconvchk => NULL() integer(I4B), pointer :: iconvresidchk => NULL() + integer(I4B), pointer :: maxlakit => NULL() !< maximum number of iterations in LAK solve real(DP), pointer :: surfdep => NULL() + real(DP), pointer :: dmaxchg => NULL() real(DP), pointer :: delh => NULL() real(DP), pointer :: pdmax => NULL() integer(I4B), pointer :: check_attr => NULL() @@ -260,6 +264,7 @@ module LakModule procedure, private :: lak_accumulate_chterm procedure, private :: lak_vol2stage procedure, private :: lak_solve + procedure, private :: lak_bisection procedure, private :: lak_calculate_available procedure, private :: lak_calculate_residual procedure, private :: lak_linear_interpolation @@ -352,7 +357,9 @@ subroutine lak_allocate_scalars(this) call mem_allocate(this%igwhcopt, 'IGWHCOPT', this%memoryPath) call mem_allocate(this%iconvchk, 'ICONVCHK', this%memoryPath) call mem_allocate(this%iconvresidchk, 'ICONVRESIDCHK', this%memoryPath) + call mem_allocate(this%maxlakit, 'MAXLAKIT', this%memoryPath) call mem_allocate(this%surfdep, 'SURFDEP', this%memoryPath) + call mem_allocate(this%dmaxchg, 'DMAXCHG', this%memoryPath) call mem_allocate(this%delh, 'DELH', this%memoryPath) call mem_allocate(this%pdmax, 'PDMAX', this%memoryPath) call mem_allocate(this%check_attr, 'CHECK_ATTR', this%memoryPath) @@ -375,8 +382,10 @@ subroutine lak_allocate_scalars(this) this%igwhcopt = 0 this%iconvchk = 1 this%iconvresidchk = 1 + this%maxlakit = MAXADPIT this%surfdep = DZERO - this%delh = DEM5 + this%dmaxchg = DEM5 + this%delh = DP999 * this%dmaxchg this%pdmax = DEM1 this%bditems = 11 this%cbcauxitems = 1 @@ -2733,7 +2742,11 @@ subroutine lak_calculate_evaporation(this, ilak, stage, avail, ev) call this%lak_calculate_sarea(ilak, stage, sa) ev = sa * this%evaporation(ilak) if (ev > avail) then - ev = -avail + if (is_same(avail, DPREC)) then + ev = DZERO + else + ev = -avail + end if else ev = -ev end if @@ -3421,6 +3434,10 @@ subroutine lak_options(this, option, found) character(len=*), parameter :: fmtlakbin = & "(4x, 'LAK ', 1x, a, 1x, ' WILL BE SAVED TO FILE: ', & &a, /4x, 'OPENED ON UNIT: ', I0)" + character(len=*), parameter :: fmtiter = & + &"(4x, 'MAXIMUM LAK ITERATION VALUE (',i0,') SPECIFIED.')" + character(len=*), parameter :: fmtdmaxchg = & + &"(4x, 'MAXIMUM STAGE CHANGE VALUE (',g0,') SPECIFIED.')" ! ------------------------------------------------------------------------------ ! found = .true. @@ -3495,6 +3512,14 @@ subroutine lak_options(this, option, found) end if this%surfdep = r write (this%iout, fmtlakeopt) 'SURFDEP', this%surfdep + case ('MAXIMUM_ITERATIONS') + this%maxlakit = this%parser%GetInteger() + write (this%iout, fmtiter) this%maxlakit + case ('MAXIMUM_STAGE_CHANGE') + r = this%parser%GetDouble() + this%dmaxchg = r + this%delh = DP999 * r + write (this%iout, fmtdmaxchg) this%dmaxchg ! ! -- right now these are options that are only available in the ! development version and are not included in the documentation. @@ -3875,10 +3900,10 @@ subroutine lak_cf(this, reset_mover) cycle end if ! - ! -- Mark ibound for dry lakes; reset to 1 otherwise + ! -- Mark ibound for wet lakes or inactive lakes; reset to 1 otherwise blak = this%belev(j) if (hlak > blak .or. this%iboundpak(n) == 0) then - this%ibound(igwfnode) = 10000 + this%ibound(igwfnode) = IWETLAKE else this%ibound(igwfnode) = 1 end if @@ -4026,6 +4051,7 @@ subroutine lak_cc(this, innertot, kiter, iend, icnvgmod, cpak, ipak, dpak) integer(I4B) :: icheck integer(I4B) :: ipakfail integer(I4B) :: locdhmax + integer(I4B) :: locresidmax integer(I4B) :: locdgwfmax integer(I4B) :: locdqoutmax integer(I4B) :: locdqfrommvrmax @@ -4039,6 +4065,7 @@ subroutine lak_cc(this, innertot, kiter, iend, icnvgmod, cpak, ipak, dpak) real(DP) :: gwf0 real(DP) :: gwf real(DP) :: dh + real(DP) :: resid real(DP) :: dgwf real(DP) :: hlak0 real(DP) :: hlak @@ -4051,6 +4078,7 @@ subroutine lak_cc(this, innertot, kiter, iend, icnvgmod, cpak, ipak, dpak) real(DP) :: qinf real(DP) :: ex real(DP) :: dhmax + real(DP) :: residmax real(DP) :: dgwfmax real(DP) :: dqoutmax real(DP) :: dqfrommvr @@ -4062,10 +4090,12 @@ subroutine lak_cc(this, innertot, kiter, iend, icnvgmod, cpak, ipak, dpak) icheck = this%iconvchk ipakfail = 0 locdhmax = 0 + locresidmax = 0 locdgwfmax = 0 locdqoutmax = 0 locdqfrommvrmax = 0 dhmax = DZERO + residmax = DZERO dgwfmax = DZERO dqoutmax = DZERO dqfrommvrmax = DZERO @@ -4085,7 +4115,7 @@ subroutine lak_cc(this, innertot, kiter, iend, icnvgmod, cpak, ipak, dpak) ! ! -- determine the number of columns and rows ntabrows = 1 - ntabcols = 9 + ntabcols = 11 if (this%noutlets > 0) then ntabcols = ntabcols + 2 end if @@ -4114,6 +4144,10 @@ subroutine lak_cc(this, innertot, kiter, iend, icnvgmod, cpak, ipak, dpak) call this%pakcsvtab%initialize_column(tag, 15, alignment=TABLEFT) tag = 'dvmax_loc' call this%pakcsvtab%initialize_column(tag, 15, alignment=TABLEFT) + tag = 'residmax' + call this%pakcsvtab%initialize_column(tag, 15, alignment=TABLEFT) + tag = 'residmax_loc' + call this%pakcsvtab%initialize_column(tag, 15, alignment=TABLEFT) tag = 'dgwfmax' call this%pakcsvtab%initialize_column(tag, 15, alignment=TABLEFT) tag = 'dgwfmax_loc' @@ -4149,7 +4183,15 @@ subroutine lak_cc(this, innertot, kiter, iend, icnvgmod, cpak, ipak, dpak) call this%lak_calculate_sarea(n, hlak, area) ! ! -- set the Q to length factor - qtolfact = delt / area + if (area > DZERO) then + qtolfact = delt / area + else + qtolfact = DZERO + end if + ! + ! -- difference in the residual + call this%lak_calculate_residual(n, hlak, resid) + resid = resid * qtolfact ! ! -- change in gwf exchange dgwf = DZERO @@ -4184,6 +4226,8 @@ subroutine lak_cc(this, innertot, kiter, iend, icnvgmod, cpak, ipak, dpak) locdhmax = n dhmax = dh locdgwfmax = n + residmax = resid + locresidmax = n dgwfmax = dgwf locdqoutmax = n dqoutmax = dqout @@ -4194,6 +4238,10 @@ subroutine lak_cc(this, innertot, kiter, iend, icnvgmod, cpak, ipak, dpak) locdhmax = n dhmax = dh end if + if (abs(resid) > abs(residmax)) then + locresidmax = n + residmax = resid + end if if (abs(dgwf) > abs(dgwfmax)) then locdgwfmax = n dgwfmax = dgwf @@ -4217,6 +4265,13 @@ subroutine lak_cc(this, innertot, kiter, iend, icnvgmod, cpak, ipak, dpak) trim(this%packName), 'stage' cpak = trim(cloc) end if + if (ABS(residmax) > abs(dpak)) then + ipak = locresidmax + dpak = residmax + write (cloc, "(a,'-',a)") & + trim(this%packName), 'residual' + cpak = trim(cloc) + end if if (ABS(dgwfmax) > abs(dpak)) then ipak = locdgwfmax dpak = dgwfmax @@ -4253,6 +4308,8 @@ subroutine lak_cc(this, innertot, kiter, iend, icnvgmod, cpak, ipak, dpak) call this%pakcsvtab%add_term(kiter) call this%pakcsvtab%add_term(dhmax) call this%pakcsvtab%add_term(locdhmax) + call this%pakcsvtab%add_term(residmax) + call this%pakcsvtab%add_term(locresidmax) call this%pakcsvtab%add_term(dgwfmax) call this%pakcsvtab%add_term(locdgwfmax) if (this%noutlets > 0) then @@ -4582,7 +4639,9 @@ subroutine lak_da(this) call mem_deallocate(this%igwhcopt) call mem_deallocate(this%iconvchk) call mem_deallocate(this%iconvresidchk) + call mem_deallocate(this%maxlakit) call mem_deallocate(this%surfdep) + call mem_deallocate(this%dmaxchg) call mem_deallocate(this%delh) call mem_deallocate(this%pdmax) call mem_deallocate(this%check_attr) @@ -5401,6 +5460,8 @@ subroutine lak_solve(this, update) real(DP) :: adh0 real(DP) :: delh real(DP) :: ts + real(DP) :: area + real(DP) :: qtolfact ! -------------------------------------------------------------------------- ! ! -- set lupdate @@ -5444,15 +5505,17 @@ subroutine lak_solve(this, update) end do ! ! -- sum up overland runoff, inflows, and external flows into lake - ! (includes lake volume) + ! (includes maximum lake volume) do n = 1, this%nlakes hlak0 = this%xoldpak(n) + hlak = this%xnewpak(n) call this%lak_calculate_runoff(n, ro) call this%lak_calculate_inflow(n, qinf) call this%lak_calculate_external(n, ex) - ! -- call this%lak_calculate_vol(n, hlak0, v0) - this%flwin(n) = this%surfin(n) + ro + qinf + ex + v0 / delt + call this%lak_calculate_vol(n, hlak, v1) + this%flwin(n) = this%surfin(n) + ro + qinf + ex + & + max(v0, v1) / delt end do ! ! -- sum up inflows from upstream outlets @@ -5462,7 +5525,7 @@ subroutine lak_solve(this, update) end do iicnvg = 0 - maxiter = 150 + maxiter = this%maxlakit ! -- outer loop converge: do iter = 1, maxiter @@ -5582,11 +5645,15 @@ subroutine lak_solve(this, update) ! ! -- recalculate flwin hlak0 = this%xoldpak(n) + hlak = this%xnewpak(n) call this%lak_calculate_vol(n, hlak0, v0) + call this%lak_calculate_vol(n, hlak, v1) call this%lak_calculate_runoff(n, ro) call this%lak_calculate_inflow(n, qinf) call this%lak_calculate_external(n, ex) - this%flwin(n) = this%surfin(n) + ro + qinf + ex + v0 / delt + this%flwin(n) = this%surfin(n) + ro + qinf + ex + & + max(v0, v1) / delt + ! ! -- compute new lake stage using Newton's method resid = this%precip(n) + this%evap(n) + this%withr(n) + ro + & @@ -5595,8 +5662,6 @@ subroutine lak_solve(this, update) resid1 = this%precip1(n) + this%evap1(n) + this%withr1(n) + ro + & qinf + ex + this%surfin(n) + & this%surfout1(n) + this%seep1(n) - - !call this%lak_calculate_residual(n, this%xnewpak(n), residb) ! ! -- add storage changes for transient stress periods hlak = this%xnewpak(n) @@ -5605,13 +5670,7 @@ subroutine lak_solve(this, update) resid = resid + (v0 - v1) / delt call this%lak_calculate_vol(n, hlak + delh, v1) resid1 = resid1 + (v0 - v1) / delt - !else - ! call this%lak_calculate_vol(n, hlak, v1) - ! resid = resid - v1 / delt - ! call this%lak_calculate_vol(n, hlak+delh, v1) - ! resid1 = resid1 - v1 / delt end if - ! ! -- determine the derivative and the stage change if (ABS(resid1 - resid) > DZERO) then @@ -5634,14 +5693,12 @@ subroutine lak_solve(this, update) if (iter == 1) this%dh0(n) = dh adh = ABS(dh) adh0 = ABS(this%dh0(n)) - if ((ts >= this%en2(n)) .or. (ts <= this%en1(n))) then + if ((ts >= this%en2(n)) .or. (ts < this%en1(n))) then ! -- use bisection if dh is increasing or updated stage is below the ! bottom of the lake if ((adh > adh0) .or. (ts - this%lakebot(n)) < DPREC) then - ibflg = 1 - ts = DHALF * (this%en1(n) + this%en2(n)) - call this%lak_calculate_residual(n, ts, residb) - dh = hlak - ts + residb = resid + call this%lak_bisection(n, ibflg, hlak, ts, dh, residb) end if end if ! @@ -5671,22 +5728,7 @@ subroutine lak_solve(this, update) ! or when convergence is slow if (ibflg == 1) then if (this%iseepc(n) > 7 .or. this%idhc(n) > 12) then - ibflg = 1 - ts = DHALF * (this%en1(n) + this%en2(n)) - call this%lak_calculate_residual(n, ts, residb) - dh = hlak - ts - end if - end if - if (ibflg == 1) then - ! -- change end points - ! -- root is between r1 and residb - if (this%r1(n) * residb < DZERO) then - this%en2(n) = ts - this%r2(n) = residb - ! -- root is between fp and f2 - else - this%en1(n) = ts - this%r1(n) = residb + call this%lak_bisection(n, ibflg, hlak, ts, dh, residb) end if end if else @@ -5698,7 +5740,23 @@ subroutine lak_solve(this, update) if (hlak < this%lakebot(n)) then hlak = this%lakebot(n) end if - if (ABS(dh) < delh) then + ! + ! -- calculate surface area + call this%lak_calculate_sarea(n, hlak, area) + ! + ! -- set the Q to length factor + if (area > DZERO) then + qtolfact = delt / area + else + qtolfact = DZERO + end if + ! + ! -- recalculate the residual + call this%lak_calculate_residual(n, hlak, resid) + ! + ! -- evaluate convergence + !if (ABS(dh) < delh) then + if (ABS(dh) < delh .and. abs(resid) * qtolfact < this%dmaxchg) then this%ncncvr(n) = 1 end if this%xnewpak(n) = hlak @@ -5726,6 +5784,59 @@ subroutine lak_solve(this, update) return end subroutine lak_solve + !> @ brief Lake package bisection method + !! + !! Use bisection method to find lake stage that reduces the residual + !! + !< + subroutine lak_bisection(this, n, ibflg, hlak, temporary_stage, dh, residual) + ! -- dummy + class(LakType), intent(inout) :: this + integer(I4B), intent(in) :: n !< lake number + integer(I4B), intent(inout) :: ibflg !< bisection flag + real(DP), intent(in) :: hlak !< lake stage + real(DP), intent(inout) :: temporary_stage !< temporary lake stage + real(DP), intent(inout) :: dh !< lake stage change + real(DP), intent(inout) :: residual !< lake residual + ! -- local + integer(I4B) :: i + real(DP) :: temporary_stage0 + real(DP) :: residuala + real(DP) :: endpoint1 + real(DP) :: endpoint2 + ! -- code + ibflg = 1 + temporary_stage0 = hlak + endpoint1 = this%en1(n) + endpoint2 = this%en2(n) + call this%lak_calculate_residual(n, temporary_stage, residuala) + if (hlak > endpoint1 .and. hlak < endpoint2) then + endpoint2 = hlak + end if + do i = 1, this%maxlakit + temporary_stage = DHALF * (endpoint1 + endpoint2) + call this%lak_calculate_residual(n, temporary_stage, residual) + if (abs(residual) == DZERO .or. & + abs(temporary_stage0 - temporary_stage) < this%dmaxchg) then + exit + end if + call this%lak_calculate_residual(n, endpoint1, residuala) + ! -- change end points + ! -- root is between temporary_stage and endpoint2 + if (sign(DONE, residuala) == SIGN(DONE, residual)) then + endpoint1 = temporary_stage + ! -- root is between endpoint1 and temporary_stage + else + endpoint2 = temporary_stage + end if + temporary_stage0 = temporary_stage + end do + dh = hlak - temporary_stage + ! + ! -- return + return + end subroutine lak_bisection + subroutine lak_calculate_available(this, n, hlak, avail, & ra, ro, qinf, ex, headp) ! ************************************************************************** diff --git a/src/Model/GroundWaterFlow/gwf3rch8.f90 b/src/Model/GroundWaterFlow/gwf3rch8.f90 index e1c685cafc8..f0fa647a798 100644 --- a/src/Model/GroundWaterFlow/gwf3rch8.f90 +++ b/src/Model/GroundWaterFlow/gwf3rch8.f90 @@ -1,7 +1,8 @@ module RchModule ! use KindModule, only: DP, I4B - use ConstantsModule, only: DZERO, LENFTYPE, LENPACKAGENAME, MAXCHARLEN + use ConstantsModule, only: DZERO, LENFTYPE, LENPACKAGENAME, MAXCHARLEN, & + IWETLAKE use MemoryHelperModule, only: create_mem_path use BndModule, only: BndType use SimModule, only: store_error, store_error_unit @@ -692,7 +693,7 @@ subroutine rch_cf(this, reset_mover) this%rhs(i) = DZERO cycle end if - if (this%ibound(node) == 10000) then + if (this%ibound(node) == IWETLAKE) then this%rhs(i) = DZERO cycle end if @@ -724,7 +725,7 @@ subroutine rch_fc(this, rhs, ia, idxglo, matrix_sln) n = this%nodelist(i) if (n <= 0) cycle ! -- reset hcof and rhs for excluded cells - if (this%ibound(n) == 10000) then + if (this%ibound(n) == IWETLAKE) then this%hcof(i) = DZERO this%rhs(i) = DZERO cycle diff --git a/src/Model/GroundWaterFlow/gwf3sfr8.f90 b/src/Model/GroundWaterFlow/gwf3sfr8.f90 index 18e761a6d02..93b97110547 100644 --- a/src/Model/GroundWaterFlow/gwf3sfr8.f90 +++ b/src/Model/GroundWaterFlow/gwf3sfr8.f90 @@ -8,6 +8,7 @@ module SfrModule ! use KindModule, only: DP, I4B, LGP use ConstantsModule, only: LINELENGTH, LENBOUNDNAME, LENTIMESERIESNAME, & + MAXADPIT, & DZERO, DPREC, DEM30, DEM6, DEM5, DEM4, DEM2, & DONETHIRD, DHALF, DP6, DTWOTHIRDS, DP7, & DP9, DP99, DP999, & @@ -318,7 +319,7 @@ subroutine sfr_allocate_scalars(this) this%ipakcsv = 0 this%idiversions = 0 this%maxsfrpicard = 100 - this%maxsfrit = 100 + this%maxsfrit = MAXADPIT this%bditems = 8 this%cbcauxitems = 1 this%unitconv = DONE diff --git a/src/Utilities/Constants.f90 b/src/Utilities/Constants.f90 index f7c966916d5..784476c424f 100644 --- a/src/Utilities/Constants.f90 +++ b/src/Utilities/Constants.f90 @@ -48,6 +48,8 @@ module ConstantsModule integer(I4B), parameter :: NAMEDBOUNDFLAG = -9 !< named bound flag integer(I4B), parameter :: LENPAKLOC = 34 !< maximum length of a package location integer(I4B), parameter :: IZERO = 0 !< integer constant zero + integer(I4B), parameter :: IWETLAKE = 10000 !< integer constant for a dry lake + integer(I4B), parameter :: MAXADPIT = 100 !< maximum advanced package Newton-Raphson iterations ! ! -- file constants integer(I4B), parameter :: IUOC = 999 !< open/close file unit number From 8d1de31e2913f34aa1d5c6688d86338df486919b Mon Sep 17 00:00:00 2001 From: w-bonelli Date: Wed, 29 Mar 2023 14:10:11 -0400 Subject: [PATCH 055/123] Update release notes, remove unused scripts (#1186) * docs: remove unused scripts * docs(ReleaseNotes): update compilation instructions --- doc/ReleaseNotes/ReleaseNotes.tex | 8 +- doc/ReleaseNotes/buildreleasenotes.sh | 6 -- doc/ReleaseNotes/mk_example_items.py | 69 ------------- doc/ReleaseNotes/mk_example_table.py | 140 -------------------------- 4 files changed, 5 insertions(+), 218 deletions(-) delete mode 100755 doc/ReleaseNotes/buildreleasenotes.sh delete mode 100644 doc/ReleaseNotes/mk_example_items.py delete mode 100644 doc/ReleaseNotes/mk_example_table.py diff --git a/doc/ReleaseNotes/ReleaseNotes.tex b/doc/ReleaseNotes/ReleaseNotes.tex index 98c0aedc232..9ac94960124 100644 --- a/doc/ReleaseNotes/ReleaseNotes.tex +++ b/doc/ReleaseNotes/ReleaseNotes.tex @@ -251,13 +251,15 @@ \section{Installation and Execution} % ------------------------------------------------- \section{Compiling MODFLOW~6} -MODFLOW~6 has been compiled using Intel Fortran and gfortran on the Windows, Mac, and Linux operating systems. Because the program uses relatively new Fortran extensions, newer versions of the compilers may be required for successful compilation. For example, to use gfortran to compile MODFLOW~6, gfortran version 4.9 or newer must be used. If you have gfortran installed on your computer, you can tell which version it is by entering ``\verb|gfortran --version|'' at a terminal window. +MODFLOW~6 has been compiled using Intel Fortran and GNU Fortran on the Windows, macOS, and Linux operating systems. Because the program uses relatively new Fortran extensions, recent versions of the compilers may be required for successful compilation. MODFLOW~6 is currently tested with gfortran 7-12 on Linux and gfortran 12 on macOS and Windows. If you have gfortran installed on your computer, you can tell which version it is by entering ``\verb|gfortran --version|'' at a terminal window. MODFLOW~6 is currently not compatible with the next-generation Intel Fortran Compiler; a recent version of the Intel Fortran Compiler Classic (e.g. 2021.8.0) must be used. -This distribution contains the Microsoft Visual Studio solution and project files for compiling MODFLOW~6 on Windows using the Intel Fortran Compiler. The files have been used successfully with recent versions of Microsoft Visual Studio Community 2019 and the Intel Fortran Compiler. To compile MODFLOW~6, open the mf6.sln file in the msvs folder and click Build > Build Solution. A separate Visual Studio solution file is also included for building the BMI dynamically linked library version of MODFLOW~6. +Meson is the recommended build tool for MODFLOW~6. For more detailed compilation instructions, please refer to \url{https://github.com/MODFLOW-USGS/modflow6/blob/develop/DEVELOPER.md#building}. + +This distribution contains the Microsoft Visual Studio solution and project files for compiling MODFLOW~6 on Windows using the Intel Fortran Compiler Classic. The files have been used successfully with recent versions of Microsoft Visual Studio Community 2019 and the Intel Fortran Compiler Classic. This distribution also comes with a makefile for compiling MODFLOW~6 with \texttt{gfortran}. The makefile is contained in the \texttt{make} folder. -For those familiar with Python, the pymake package can also be used to compile MODFLOW~6. Additional information on the Python pymake utility can be found at: \url{https://github.com/modflowpy/pymake}. +For those familiar with Python, the pymake package can also be used to compile MODFLOW~6. Additional information on the Python pymake utility can be found at: \url{https://github.com/modflowpy/pymake}. % ------------------------------------------------- \section{System Requirements} diff --git a/doc/ReleaseNotes/buildreleasenotes.sh b/doc/ReleaseNotes/buildreleasenotes.sh deleted file mode 100755 index a88641f7590..00000000000 --- a/doc/ReleaseNotes/buildreleasenotes.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/bash -# Build release notes - -echo "Building release notes..." -pdflatex ReleaseNotes.tex -echo diff --git a/doc/ReleaseNotes/mk_example_items.py b/doc/ReleaseNotes/mk_example_items.py deleted file mode 100644 index 1b2faf0a80f..00000000000 --- a/doc/ReleaseNotes/mk_example_items.py +++ /dev/null @@ -1,69 +0,0 @@ - -import os -import sys - - -def get_distribution_name(versiontexname): - dist = None - fname = versiontexname - with open(fname) as f: - lines = f.readlines() - f.close() - for line in lines: - # \newcommand{\modflowversion}{mf6beta0.9.00} - srchtxt = '\\newcommand{\\modflowversion}' - istart = line.rfind('{') + 1 - istop = line.rfind('}') - if 0 < istart < istop: - dist = line[istart: istop] - return dist - return None - - -# Set up the path to the distribution -fname = os.path.join('..', 'version.tex') -dist = get_distribution_name(fname) -distpth = os.path.join('..', '..', 'distribution', dist) -if not os.path.isdir(distpth): - raise Exception(distpth + ' does not exist. ') - -# Open the file -f = open('example_items.tex', 'w') - -# Write the latex header - -s = r'''\begin{itemize}''' -f.write(s) -f.write('\n') - -expth = os.path.join(distpth, 'examples') -files = os.listdir(expth) -for exname in files: - - # Skip if not a directory - if not os.path.isdir(os.path.join(expth, exname)): - continue - - # example name - s = r'\item {}---'.format(exname) - - line = 'XXX' - - descriptionfile = os.path.join(expth, exname, 'description.txt') - if os.path.isfile(descriptionfile): - with open(descriptionfile, 'r') as fd: - line = fd.readline() - fd.close() - - s += line - f.write(s) - f.write('\n') - - -s = r'''\end{itemize} -''' -f.write(s) - -f.close() - -print('done...') diff --git a/doc/ReleaseNotes/mk_example_table.py b/doc/ReleaseNotes/mk_example_table.py deleted file mode 100644 index 1a73b08d401..00000000000 --- a/doc/ReleaseNotes/mk_example_table.py +++ /dev/null @@ -1,140 +0,0 @@ - -import os -import sys -import pymake - -pth = os.path.join('..', '..') -if pth not in sys.path: - sys.path.append(pth) - - -def get_distribution_name(versiontexname): - dist = None - fname = versiontexname - with open(fname) as f: - lines = f.readlines() - f.close() - for line in lines: - # \newcommand{\modflowversion}{mf6beta0.9.00} - srchtxt = '\\newcommand{\\modflowversion}' - istart = line.rfind('{') + 1 - istop = line.rfind('}') - if 0 < istart < istop: - dist = line[istart: istop] - return dist - return None - - -# Set up the path to the distribution -fname = os.path.join('..', 'version.tex') -dist = get_distribution_name(fname) -distpth = os.path.join('..', '..', 'distribution', dist) -if not os.path.isdir(distpth): - raise Exception(distpth + ' does not exist. ') - -# Open the file -f = open('example_table.tex', 'w') - -# Write the latex header - -s = r''' -\small -\begin{longtable}{p{3cm} p{1cm} p{3cm} p{2.5cm}p{4cm}} -\caption{List of example problems and simulation characteristics}\tabularnewline - - -\hline -\hline -\textbf{Name} & \textbf{NPER} & \textbf{Namefile(s)} & \textbf{Dimensions (NLAY, NROW, NCOL), (NLAY, NCPL) or (NODES)} & \textbf{Stress Packages} \\ -\hline -\endfirsthead - -\hline -\hline -\textbf{Name} & \textbf{NPER} & \textbf{Namefile(s)} & \textbf{Dimensions (NLAY, NROW, NCOL) or (NODES)} & \textbf{Stress Packages} \\ -\hline -\endhead - -''' -f.write(s) -f.write('\n') - -expth = os.path.join(distpth, 'examples') -files = os.listdir(expth) -for exname in files: - - # Skip if not a directory - if not os.path.isdir(os.path.join(expth, exname)): - continue - - # example name - s = '{} '.format(exname) - - # number of models - mfnamefile = os.path.join(expth, exname, 'mfsim.nam') - model_files, outfiles = pymake.autotest.get_mf6_files(mfnamefile) - nmodels = len([w for w in model_files if w.lower().endswith('.nam')]) - # s += '& {} '.format(nmodels) - - - # Number of stress periods - tdis = [w for w in model_files if w.upper().endswith('.TDIS')][0] - nper = pymake.autotest.get_mf6_nper(os.path.join(expth, exname, tdis)) - s += '& {} '.format(nper) - - - # Name files - namefiles = [w for w in model_files if w.lower().endswith('.nam')] - s += '& ' - cellstring = '\parbox[t]{3cm}{' + ''.join(r' {} \\'.format(nf) for nf in namefiles) + '}' - s += cellstring - - # Model shape - dis_files = [w for w in model_files if w.upper().endswith('.DIS') - or w.upper().endswith('.DISU') - or w.upper().endswith('.DISV')] - s += '& ' - mshapes = [] - for disfile in dis_files: - mshape = pymake.autotest.get_mf6_mshape(os.path.join(expth, exname, disfile)) - mshapes.append(mshape) - cellstring = '\parbox[t]{3cm}{' + ''.join(r' {} \\'.format(ms) for ms in mshapes) + '}' - s += cellstring - - # File types - namefiles = [w for w in model_files if w.lower().endswith('.nam')] - s += '& ' - lines = [] - for nf in namefiles: - ftypes = pymake.autotest.get_mf6_ftypes(os.path.join(expth, exname, nf), - ['CHD6', 'WEL6', 'DRN6', 'RIV6', 'GHB6', 'SFR6', 'RCH6', - 'EVT6', 'SFR6', 'UZF6', 'MAW6', 'LAK6', 'MVR6']) - ss = '' - for st in ftypes: - if st[:3] not in ss: - ss += st[:3] + ' ' - if len(ss) == 0: - ss = 'none' - lines.append(ss) - cellstring = '\parbox[t]{4cm}{' + ''.join(r' {} \\'.format(ls) for ls in lines) + '}' - s += cellstring - - # End the table line for this example - s = s.replace('_', r'\_') - s += r'\\' - f.write(s) - f.write('\n') - - f.write(r'\hline' + '\n') - -s = r'''\hline -\end{longtable} -\label{table:examples} -\normalsize - -''' -f.write(s) - -f.close() - -print('done...') From 6f6fd8971fdb697b92f953d25f149a4538c14faf Mon Sep 17 00:00:00 2001 From: w-bonelli Date: Wed, 29 Mar 2023 18:39:49 -0400 Subject: [PATCH 056/123] test: update excluded comparison syntax for external model tests (#1187) --- autotest/test_z01_testmodels_mf6.py | 56 +++++++++++++------------- autotest/test_z02_testmodels_mf5to6.py | 8 ++-- autotest/test_z03_examples.py | 26 ++++++------ autotest/test_z03_largetestmodels.py | 8 ++-- 4 files changed, 49 insertions(+), 49 deletions(-) diff --git a/autotest/test_z01_testmodels_mf6.py b/autotest/test_z01_testmodels_mf6.py index 4322d8b7552..b349598c027 100644 --- a/autotest/test_z01_testmodels_mf6.py +++ b/autotest/test_z01_testmodels_mf6.py @@ -4,34 +4,34 @@ excluded_models = ["alt_model", "test205_gwtbuy-henrytidal"] excluded_comparisons = { - "test001e_noUZF_3lay": ("6.2.1",), - "test005_advgw_tidal": ("6.2.1",), - "test017_Crinkle": ("6.2.1",), - "test028_sfr": ("6.2.1",), - "test028_sfr_rewet": ("6.2.1",), - "test028_sfr_rewet_nr": ("6.2.1",), - "test028_sfr_rewet_simple": ("6.2.1",), - "test028_sfr_simple": ("6.2.1",), - "test034_nwtp2": ("6.2.1",), - "test034_nwtp2_1d": ("6.2.1",), - "test045_lake1tr_nr": ("6.2.1",), - "test045_lake2tr": ("6.2.1",), - "test045_lake2tr_nr": ("6.2.1",), - "test051_uzfp2": ("6.2.1",), - "test051_uzfp3_lakmvr_v2": ("6.2.1",), - "test051_uzfp3_wellakmvr_v2": ("6.2.1",), - "test045_lake4ss": ("6.2.2",), - "test056_mt3dms_usgs_gwtex_dev": ("6.4.1",), - "test056_mt3dms_usgs_gwtex_IR_dev": ("6.4.1",), - "test059_mvlake_lak_ss": ("6.4.1",), - "test045_lake2tr_xsfrc_dev": ("6.4.1",), - "test045_lake2tr_xsfrd_dev": ("6.4.1",), - "test045_lake2tr_xsfre_dev": ("6.4.1",), - "test045_lake4ss": ("6.4.1",), - "test045_lake4ss_dev": ("6.4.1",), - "test045_lake4ss_il_dev": ("6.4.1",), - "test045_lake4ss_nr_dev": ("6.4.1",), - "test045_lake4ss_nr_embedded": ("6.4.1",), + "test001e_noUZF_3lay": ["6.2.1",], + "test005_advgw_tidal": ["6.2.1",], + "test017_Crinkle": ["6.2.1",], + "test028_sfr": ["6.2.1",], + "test028_sfr_rewet": ["6.2.1",], + "test028_sfr_rewet_nr": ["6.2.1",], + "test028_sfr_rewet_simple": ["6.2.1",], + "test028_sfr_simple": ["6.2.1",], + "test034_nwtp2": ["6.2.1",], + "test034_nwtp2_1d": ["6.2.1",], + "test045_lake1tr_nr": ["6.2.1",], + "test045_lake2tr": ["6.2.1",], + "test045_lake2tr_nr": ["6.2.1",], + "test051_uzfp2": ["6.2.1",], + "test051_uzfp3_lakmvr_v2": ["6.2.1",], + "test051_uzfp3_wellakmvr_v2": ["6.2.1",], + "test045_lake4ss": ["6.2.2",], + "test056_mt3dms_usgs_gwtex_dev": ["6.4.1",], + "test056_mt3dms_usgs_gwtex_IR_dev": ["6.4.1",], + "test059_mvlake_lak_ss": ["6.4.1",], + "test045_lake2tr_xsfrc_dev": ["6.4.1",], + "test045_lake2tr_xsfrd_dev": ["6.4.1",], + "test045_lake2tr_xsfre_dev": ["6.4.1",], + "test045_lake4ss": ["6.4.1",], + "test045_lake4ss_dev": ["6.4.1",], + "test045_lake4ss_il_dev": ["6.4.1",], + "test045_lake4ss_nr_dev": ["6.4.1",], + "test045_lake4ss_nr_embedded": ["6.4.1",], } diff --git a/autotest/test_z02_testmodels_mf5to6.py b/autotest/test_z02_testmodels_mf5to6.py index 9ae811d793d..ef030661ad3 100644 --- a/autotest/test_z02_testmodels_mf5to6.py +++ b/autotest/test_z02_testmodels_mf5to6.py @@ -9,10 +9,10 @@ sfmt = "{:25s} - {}" excluded_models = ["alt_model", "mf2005"] excluded_comparisons = { - "testPr2": ("6.2.1",), - "testUzfLakSfr": ("6.2.1",), - "testUzfLakSfr_laketable": ("6.2.1",), - "testWetDry": ("6.2.1",), + "testPr2": ["6.2.1",], + "testUzfLakSfr": ["6.2.1",], + "testUzfLakSfr_laketable": ["6.2.1",], + "testWetDry": ["6.2.1",], } diff --git a/autotest/test_z03_examples.py b/autotest/test_z03_examples.py index 0e2974582eb..124d8f9229a 100644 --- a/autotest/test_z03_examples.py +++ b/autotest/test_z03_examples.py @@ -30,19 +30,19 @@ "ex-gwt-prudic2004t2", ] excluded_comparisons = { - "ex-gwf-capture": ("6.2.1",), - "ex-gwf-sagehen": ("6.2.1",), - "ex-gwf-sfr-p01b": ("6.2.1",), - "ex-gwf-nwt-p02a": ("6.2.1",), - "ex-gwf-lak-p01": ("6.2.1",), - "ex-gwf-lak-p02": ("6.2.1",), - "ex-gwf-nwt-p02b": ("6.2.1",), - "ex-gwf-advtidal": ("6.2.1",), - "ex-gwf-sfr-p01": ("6.2.1",), - "ex-gwf-lgr": ("6.2.2",), - "ex-gwt-rotate": ("6.2.2",), - "ex-gwt-gwtgwt-mt3dms-p10": ("6.3.0",), - "ex-gwf-lak-p01": ("6.4.1"), + "ex-gwf-capture": ["6.2.1"], + "ex-gwf-sagehen": ["6.2.1"], + "ex-gwf-sfr-p01b": ["6.2.1"], + "ex-gwf-nwt-p02a": ["6.2.1"], + "ex-gwf-lak-p01": ["6.2.1"], + "ex-gwf-lak-p02": ["6.2.1"], + "ex-gwf-nwt-p02b": ["6.2.1"], + "ex-gwf-advtidal": ["6.2.1"], + "ex-gwf-sfr-p01": ["6.2.1"], + "ex-gwf-lgr": ["6.2.2"], + "ex-gwt-rotate": ["6.2.2"], + "ex-gwt-gwtgwt-mt3dms-p10": ["6.3.0"], + "ex-gwf-lak-p01": ["6.4.1"], } diff --git a/autotest/test_z03_largetestmodels.py b/autotest/test_z03_largetestmodels.py index 753e36e4abb..a90475cb1a4 100644 --- a/autotest/test_z03_largetestmodels.py +++ b/autotest/test_z03_largetestmodels.py @@ -4,10 +4,10 @@ excluded_models = [] excluded_comparisons = { - "test1004_mvlake_laksfr_tr": ("6.4.1",), - "test1004_mvlake_lak_tr": ("6.4.1",), - "test1003_MNW2_Fig28": ("6.2.1",), - "test1001_Peterson": ("6.2.1",), + "test1004_mvlake_laksfr_tr": ["6.4.1",], + "test1004_mvlake_lak_tr": ["6.4.1",], + "test1003_MNW2_Fig28": ["6.2.1",], + "test1001_Peterson": ["6.2.1",], } From 48001aa6984e181bbb60f1f923ec10e8b1dd2037 Mon Sep 17 00:00:00 2001 From: mjreno Date: Thu, 30 Mar 2023 08:33:39 -0400 Subject: [PATCH 057/123] feat(idm): update simulation to load and use model input context (#1184) * relocate source specific files (mf6 input) to their own directory * add input defs for gwt dis and gwf/gwt namefiles * add idm capability to load model name and package files * update simulation to load and use model input context * some cleanup and renaming of idm files * align idm module names with source file names * remove NameFileModule * load local model only in parallel mode --------- Co-authored-by: mjreno --- doc/mf6io/mf6ivar/dfn/gwt-disu.dfn | 3 + doc/mf6io/mf6ivar/dfn/gwt-disv.dfn | 6 +- make/makefile | 117 ++-- msvs/mf6core.vfproj | 20 +- msvs/mf6lib.vfproj | 1 - src/Model/Connection/GwfInterfaceModel.f90 | 4 +- src/Model/Connection/GwtInterfaceModel.f90 | 4 +- src/Model/GroundWaterFlow/gwf3.f90 | 529 +++++++++------ src/Model/GroundWaterFlow/gwf3dis8.f90 | 87 +-- src/Model/GroundWaterFlow/gwf3disu8.f90 | 131 ++-- src/Model/GroundWaterFlow/gwf3disv8.f90 | 106 ++- src/Model/GroundWaterFlow/gwf3idm.f90 | 241 +++++++ src/Model/GroundWaterFlow/gwf3npf8.f90 | 127 ++-- src/Model/GroundWaterTransport/gwt1.f90 | 484 +++++++------ .../GroundWaterTransport/gwt1dis1idm.f90 | 285 ++++++++ .../GroundWaterTransport/gwt1disu1idm.f90 | 588 ++++++++++++++++ .../GroundWaterTransport/gwt1disv1idm.f90 | 438 ++++++++++++ src/Model/GroundWaterTransport/gwt1dsp.f90 | 53 +- src/Model/GroundWaterTransport/gwt1idm.f90 | 187 +++++ .../ModelUtilities/DiscretizationBase.f90 | 8 + src/Model/NumericalModel.f90 | 36 - src/Model/NumericalPackage.f90 | 10 + src/SimulationCreate.f90 | 5 +- ...SelectorUtils.f90 => DefinitionSelect.f90} | 6 +- src/Utilities/Idm/IdmMf6FileLoader.f90 | 101 --- src/Utilities/Idm/IdmSimulation.f90 | 149 ++-- src/Utilities/Idm/ModelPackageInputs.f90 | 642 ++++++++++++++++++ src/Utilities/Idm/mf6blockfile/IdmMf6File.f90 | 336 +++++++++ .../LoadMf6File.f90} | 12 +- .../Idm/{ => mf6blockfile}/StructArray.f90 | 0 .../Idm/{ => mf6blockfile}/StructVector.f90 | 0 .../Idm/selector/IdmGwfDfnSelector.f90 | 14 + .../Idm/selector/IdmGwtDfnSelector.f90 | 56 ++ src/Utilities/NameFile.f90 | 379 ----------- src/meson.build | 19 +- src/mf6core.f90 | 39 +- utils/idmloader/scripts/dfn2f90.py | 20 + utils/mf5to6/make/makefile | 8 +- 38 files changed, 3890 insertions(+), 1361 deletions(-) create mode 100644 src/Model/GroundWaterFlow/gwf3idm.f90 create mode 100644 src/Model/GroundWaterTransport/gwt1dis1idm.f90 create mode 100644 src/Model/GroundWaterTransport/gwt1disu1idm.f90 create mode 100644 src/Model/GroundWaterTransport/gwt1disv1idm.f90 create mode 100644 src/Model/GroundWaterTransport/gwt1idm.f90 rename src/Utilities/Idm/{IdmDfnSelectorUtils.f90 => DefinitionSelect.f90} (97%) delete mode 100644 src/Utilities/Idm/IdmMf6FileLoader.f90 create mode 100644 src/Utilities/Idm/ModelPackageInputs.f90 create mode 100644 src/Utilities/Idm/mf6blockfile/IdmMf6File.f90 rename src/Utilities/Idm/{LoadMf6FileType.f90 => mf6blockfile/LoadMf6File.f90} (99%) rename src/Utilities/Idm/{ => mf6blockfile}/StructArray.f90 (100%) rename src/Utilities/Idm/{ => mf6blockfile}/StructVector.f90 (100%) delete mode 100644 src/Utilities/NameFile.f90 diff --git a/doc/mf6io/mf6ivar/dfn/gwt-disu.dfn b/doc/mf6io/mf6ivar/dfn/gwt-disu.dfn index a81fdef2dce..09043e92ca5 100644 --- a/doc/mf6io/mf6ivar/dfn/gwt-disu.dfn +++ b/doc/mf6io/mf6ivar/dfn/gwt-disu.dfn @@ -48,6 +48,7 @@ optional true default_value 0.0 longname vertical length dimension for top and bottom checking description checks are performed to ensure that the top of a cell is not higher than the bottom of an overlying cell. This option can be used to specify the tolerance that is used for checking. If top of a cell is above the bottom of an overlying cell by a value less than this tolerance, then the program will not terminate with an error. The default value is zero. This option should generally not be used. +mf6internal voffsettol # --------------------- gwt disu dimensions --------------------- @@ -173,6 +174,7 @@ jagged_array iac block vertices name vertices type recarray iv xv yv +shape (nvert) reader urword optional false longname vertices data @@ -215,6 +217,7 @@ description is the y-coordinate for the vertex. block cell2d name cell2d type recarray icell2d xc yc ncvert icvert +shape (nodes) reader urword optional false longname cell2d data diff --git a/doc/mf6io/mf6ivar/dfn/gwt-disv.dfn b/doc/mf6io/mf6ivar/dfn/gwt-disv.dfn index 91ed3dd4cea..98077265a72 100644 --- a/doc/mf6io/mf6ivar/dfn/gwt-disv.dfn +++ b/doc/mf6io/mf6ivar/dfn/gwt-disv.dfn @@ -79,7 +79,7 @@ description is the top elevation for each cell in the top model layer. block griddata name botm type double precision -shape (nlay, ncpl) +shape (ncpl, nlay) reader readarray layered true longname model bottom elevation @@ -88,7 +88,7 @@ description is the bottom elevation for each cell. block griddata name idomain type integer -shape (nlay, ncpl) +shape (ncpl, nlay) reader readarray layered true optional true @@ -101,6 +101,7 @@ description is an optional array that characterizes the existence status of a ce block vertices name vertices type recarray iv xv yv +shape (nvert) reader urword optional false longname vertices data @@ -143,6 +144,7 @@ description is the y-coordinate for the vertex. block cell2d name cell2d type recarray icell2d xc yc ncvert icvert +shape (ncpl) reader urword optional false longname cell2d data diff --git a/make/makefile b/make/makefile index f5add4d6526..02743cd0f5e 100644 --- a/make/makefile +++ b/make/makefile @@ -5,35 +5,35 @@ include ./makedefaults # Define the source file directories SOURCEDIR1=../src -SOURCEDIR2=../src/Exchange -SOURCEDIR3=../src/Distributed -SOURCEDIR4=../src/Solution -SOURCEDIR5=../src/Solution/LinearMethods -SOURCEDIR6=../src/Solution/PETSc -SOURCEDIR7=../src/Timing -SOURCEDIR8=../src/Utilities -SOURCEDIR9=../src/Utilities/Idm -SOURCEDIR10=../src/Utilities/Idm/selector -SOURCEDIR11=../src/Utilities/TimeSeries -SOURCEDIR12=../src/Utilities/Memory -SOURCEDIR13=../src/Utilities/OutputControl -SOURCEDIR14=../src/Utilities/ArrayRead -SOURCEDIR15=../src/Utilities/Libraries -SOURCEDIR16=../src/Utilities/Libraries/rcm -SOURCEDIR17=../src/Utilities/Libraries/blas -SOURCEDIR18=../src/Utilities/Libraries/sparskit2 -SOURCEDIR19=../src/Utilities/Libraries/daglib -SOURCEDIR20=../src/Utilities/Libraries/sparsekit -SOURCEDIR21=../src/Utilities/Vector -SOURCEDIR22=../src/Utilities/Matrix -SOURCEDIR23=../src/Utilities/Observation -SOURCEDIR24=../src/Model -SOURCEDIR25=../src/Model/Connection -SOURCEDIR26=../src/Model/GroundWaterTransport -SOURCEDIR27=../src/Model/ModelUtilities -SOURCEDIR28=../src/Model/GroundWaterFlow -SOURCEDIR29=../src/Model/Geometry -SOURCEDIR30=../src/Model/StreamNetworkFlow +SOURCEDIR2=../src/Distributed +SOURCEDIR3=../src/Exchange +SOURCEDIR4=../src/Model +SOURCEDIR5=../src/Model/Connection +SOURCEDIR6=../src/Model/Geometry +SOURCEDIR7=../src/Model/GroundWaterFlow +SOURCEDIR8=../src/Model/GroundWaterTransport +SOURCEDIR9=../src/Model/ModelUtilities +SOURCEDIR10=../src/Solution +SOURCEDIR11=../src/Solution/LinearMethods +SOURCEDIR12=../src/Solution/PETSc +SOURCEDIR13=../src/Timing +SOURCEDIR14=../src/Utilities +SOURCEDIR15=../src/Utilities/ArrayRead +SOURCEDIR16=../src/Utilities/Idm +SOURCEDIR17=../src/Utilities/Idm/mf6blockfile +SOURCEDIR18=../src/Utilities/Idm/selector +SOURCEDIR19=../src/Utilities/Libraries +SOURCEDIR20=../src/Utilities/Libraries/blas +SOURCEDIR21=../src/Utilities/Libraries/daglib +SOURCEDIR22=../src/Utilities/Libraries/rcm +SOURCEDIR23=../src/Utilities/Libraries/sparsekit +SOURCEDIR24=../src/Utilities/Libraries/sparskit2 +SOURCEDIR25=../src/Utilities/Matrix +SOURCEDIR26=../src/Utilities/Memory +SOURCEDIR27=../src/Utilities/Observation +SOURCEDIR28=../src/Utilities/OutputControl +SOURCEDIR29=../src/Utilities/TimeSeries +SOURCEDIR30=../src/Utilities/Vector VPATH = \ ${SOURCEDIR1} \ @@ -102,45 +102,21 @@ $(OBJDIR)/VectorBase.o \ $(OBJDIR)/Sparse.o \ $(OBJDIR)/DisvGeom.o \ $(OBJDIR)/ArrayReaders.o \ -$(OBJDIR)/InputDefinition.o \ $(OBJDIR)/TimeSeriesManager.o \ $(OBJDIR)/SmoothingFunctions.o \ $(OBJDIR)/MatrixBase.o \ $(OBJDIR)/ListReader.o \ $(OBJDIR)/Connections.o \ -$(OBJDIR)/simnamidm.o \ -$(OBJDIR)/gwt1dspidm.o \ -$(OBJDIR)/gwf3npf8idm.o \ -$(OBJDIR)/gwf3disv8idm.o \ -$(OBJDIR)/gwf3disu8idm.o \ -$(OBJDIR)/gwf3dis8idm.o \ -$(OBJDIR)/ArrayReaderBase.o \ $(OBJDIR)/DiscretizationBase.o \ -$(OBJDIR)/STLVecInt.o \ -$(OBJDIR)/IdmSimDfnSelector.o \ -$(OBJDIR)/IdmGwtDfnSelector.o \ -$(OBJDIR)/IdmGwfDfnSelector.o \ -$(OBJDIR)/Integer2dReader.o \ $(OBJDIR)/TimeArray.o \ $(OBJDIR)/ObsOutput.o \ -$(OBJDIR)/StructVector.o \ -$(OBJDIR)/IdmLogger.o \ -$(OBJDIR)/IdmDfnSelector.o \ -$(OBJDIR)/Integer1dReader.o \ -$(OBJDIR)/Double2dReader.o \ -$(OBJDIR)/Double1dReader.o \ $(OBJDIR)/TimeArraySeries.o \ $(OBJDIR)/ObsOutputList.o \ $(OBJDIR)/Observe.o \ -$(OBJDIR)/StructArray.o \ -$(OBJDIR)/ModflowInput.o \ -$(OBJDIR)/LayeredArrayReader.o \ -$(OBJDIR)/IdmDfnSelectorUtils.o \ $(OBJDIR)/TimeArraySeriesLink.o \ $(OBJDIR)/ObsUtility.o \ $(OBJDIR)/ObsContainer.o \ $(OBJDIR)/BudgetFileReader.o \ -$(OBJDIR)/LoadMf6FileType.o \ $(OBJDIR)/TimeArraySeriesManager.o \ $(OBJDIR)/PackageMover.o \ $(OBJDIR)/Obs3.o \ @@ -150,9 +126,10 @@ $(OBJDIR)/sort.o \ $(OBJDIR)/SfrCrossSectionUtils.o \ $(OBJDIR)/BudgetTerm.o \ $(OBJDIR)/VirtualBase.o \ -$(OBJDIR)/IdmMf6FileLoader.o \ +$(OBJDIR)/STLVecInt.o \ $(OBJDIR)/BoundaryPackage.o \ $(OBJDIR)/BaseModel.o \ +$(OBJDIR)/InputDefinition.o \ $(OBJDIR)/SfrCrossSectionManager.o \ $(OBJDIR)/dag_module.o \ $(OBJDIR)/BudgetObject.o \ @@ -160,6 +137,17 @@ $(OBJDIR)/VirtualDataLists.o \ $(OBJDIR)/VirtualDataContainer.o \ $(OBJDIR)/SimStages.o \ $(OBJDIR)/NumericalModel.o \ +$(OBJDIR)/simnamidm.o \ +$(OBJDIR)/gwt1idm.o \ +$(OBJDIR)/gwt1dspidm.o \ +$(OBJDIR)/gwt1disv1idm.o \ +$(OBJDIR)/gwt1disu1idm.o \ +$(OBJDIR)/gwt1dis1idm.o \ +$(OBJDIR)/gwf3npf8idm.o \ +$(OBJDIR)/gwf3idm.o \ +$(OBJDIR)/gwf3disv8idm.o \ +$(OBJDIR)/gwf3disu8idm.o \ +$(OBJDIR)/gwf3dis8idm.o \ $(OBJDIR)/PackageBudget.o \ $(OBJDIR)/HeadFileReader.o \ $(OBJDIR)/PrintSaveManager.o \ @@ -175,6 +163,9 @@ $(OBJDIR)/gwf3ghb8.o \ $(OBJDIR)/gwf3drn8.o \ $(OBJDIR)/VirtualModel.o \ $(OBJDIR)/BaseExchange.o \ +$(OBJDIR)/IdmSimDfnSelector.o \ +$(OBJDIR)/IdmGwtDfnSelector.o \ +$(OBJDIR)/IdmGwfDfnSelector.o \ $(OBJDIR)/UzfCellGroup.o \ $(OBJDIR)/gwt1fmi1.o \ $(OBJDIR)/OutputControlData.o \ @@ -188,7 +179,7 @@ $(OBJDIR)/SeqVector.o \ $(OBJDIR)/IndexMap.o \ $(OBJDIR)/CellWithNbrs.o \ $(OBJDIR)/NumericalExchange.o \ -$(OBJDIR)/Iunit.o \ +$(OBJDIR)/IdmDfnSelector.o \ $(OBJDIR)/gwf3uzf8.o \ $(OBJDIR)/gwt1apt1.o \ $(OBJDIR)/GwtSpc.o \ @@ -208,6 +199,7 @@ $(OBJDIR)/VirtualSolution.o \ $(OBJDIR)/SparseMatrix.o \ $(OBJDIR)/LinearSolverBase.o \ $(OBJDIR)/ims8reordering.o \ +$(OBJDIR)/ArrayReaderBase.o \ $(OBJDIR)/VirtualExchange.o \ $(OBJDIR)/InterfaceMap.o \ $(OBJDIR)/gwf3disu8.o \ @@ -215,7 +207,7 @@ $(OBJDIR)/GridSorting.o \ $(OBJDIR)/DisConnExchange.o \ $(OBJDIR)/CsrUtils.o \ $(OBJDIR)/TransportModel.o \ -$(OBJDIR)/NameFile.o \ +$(OBJDIR)/ModelPackageInputs.o \ $(OBJDIR)/gwt1uzt1.o \ $(OBJDIR)/gwt1ssm1.o \ $(OBJDIR)/gwt1src1.o \ @@ -247,6 +239,7 @@ $(OBJDIR)/gwf3chd8.o \ $(OBJDIR)/RouterBase.o \ $(OBJDIR)/ImsLinearSolver.o \ $(OBJDIR)/ims8base.o \ +$(OBJDIR)/Integer2dReader.o \ $(OBJDIR)/GridConnection.o \ $(OBJDIR)/DistributedVariable.o \ $(OBJDIR)/gwt1.o \ @@ -256,6 +249,11 @@ $(OBJDIR)/Timer.o \ $(OBJDIR)/LinearSolverFactory.o \ $(OBJDIR)/ims8linear.o \ $(OBJDIR)/BaseSolution.o \ +$(OBJDIR)/StructVector.o \ +$(OBJDIR)/IdmLogger.o \ +$(OBJDIR)/Integer1dReader.o \ +$(OBJDIR)/Double2dReader.o \ +$(OBJDIR)/Double1dReader.o \ $(OBJDIR)/ExplicitModel.o \ $(OBJDIR)/SpatialModelConnection.o \ $(OBJDIR)/GwtInterfaceModel.o \ @@ -265,11 +263,16 @@ $(OBJDIR)/GwfGwfExchange.o \ $(OBJDIR)/RouterFactory.o \ $(OBJDIR)/NumericalSolution.o \ $(OBJDIR)/MappedMemory.o \ +$(OBJDIR)/StructArray.o \ +$(OBJDIR)/ModflowInput.o \ +$(OBJDIR)/LayeredArrayReader.o \ +$(OBJDIR)/DefinitionSelect.o \ $(OBJDIR)/ExplicitSolution.o \ $(OBJDIR)/GwtGwtConnection.o \ $(OBJDIR)/GwfGwfConnection.o \ $(OBJDIR)/VirtualDataManager.o \ $(OBJDIR)/Mapper.o \ +$(OBJDIR)/LoadMf6File.o \ $(OBJDIR)/VirtualGwtModel.o \ $(OBJDIR)/VirtualGwtExchange.o \ $(OBJDIR)/VirtualGwfModel.o \ @@ -278,6 +281,7 @@ $(OBJDIR)/SolutionGroup.o \ $(OBJDIR)/SolutionFactory.o \ $(OBJDIR)/GwfGwtExchange.o \ $(OBJDIR)/RunControl.o \ +$(OBJDIR)/IdmMf6File.o \ $(OBJDIR)/SimulationCreate.o \ $(OBJDIR)/RunControlFactory.o \ $(OBJDIR)/IdmSimulation.o \ @@ -292,6 +296,7 @@ $(OBJDIR)/ilut.o \ $(OBJDIR)/sparsekit.o \ $(OBJDIR)/rcm.o \ $(OBJDIR)/blas1_d.o \ +$(OBJDIR)/Iunit.o \ $(OBJDIR)/RectangularGeometry.o \ $(OBJDIR)/CircularGeometry.o diff --git a/msvs/mf6core.vfproj b/msvs/mf6core.vfproj index e103b4f706a..7ce104b72bb 100644 --- a/msvs/mf6core.vfproj +++ b/msvs/mf6core.vfproj @@ -134,6 +134,7 @@ + @@ -156,10 +157,14 @@ + + + + @@ -254,20 +259,22 @@ + + + + + - + - - - - - + + @@ -355,7 +362,6 @@ - diff --git a/msvs/mf6lib.vfproj b/msvs/mf6lib.vfproj index 804f4e41c87..e89b383dde7 100644 --- a/msvs/mf6lib.vfproj +++ b/msvs/mf6lib.vfproj @@ -150,7 +150,6 @@ - diff --git a/src/Model/Connection/GwfInterfaceModel.f90 b/src/Model/Connection/GwfInterfaceModel.f90 index 048e39e21ba..1d3fd25744f 100644 --- a/src/Model/Connection/GwfInterfaceModel.f90 +++ b/src/Model/Connection/GwfInterfaceModel.f90 @@ -69,8 +69,8 @@ subroutine gwfifm_cr(this, name, iout, gridConn) end if ! create discretization and packages - call disu_cr(this%dis, this%name, -1, this%iout) - call npf_cr(this%npf, this%name, -this%innpf, this%iout) + call disu_cr(this%dis, this%name, '', -1, this%iout) + call npf_cr(this%npf, this%name, '', -this%innpf, this%iout) call xt3d_cr(this%xt3d, this%name, -this%innpf, this%iout) call buy_cr(this%buy, this%name, this%inbuy, this%iout) diff --git a/src/Model/Connection/GwtInterfaceModel.f90 b/src/Model/Connection/GwtInterfaceModel.f90 index 472a63e980e..72d6ce0a304 100644 --- a/src/Model/Connection/GwtInterfaceModel.f90 +++ b/src/Model/Connection/GwtInterfaceModel.f90 @@ -78,10 +78,10 @@ subroutine gwtifmod_cr(this, name, iout, gridConn) end if ! create dis and packages - call disu_cr(this%dis, this%name, -1, this%iout) + call disu_cr(this%dis, this%name, '', -1, this%iout) call fmi_cr(this%fmi, this%name, 0, this%iout) call adv_cr(this%adv, this%name, adv_unit, this%iout, this%fmi) - call dsp_cr(this%dsp, this%name, -dsp_unit, this%iout, this%fmi) + call dsp_cr(this%dsp, this%name, '', -dsp_unit, this%iout, this%fmi) call gwt_obs_cr(this%obs, inobs) end subroutine gwtifmod_cr diff --git a/src/Model/GroundWaterFlow/gwf3.f90 b/src/Model/GroundWaterFlow/gwf3.f90 index 3b97ba6c14e..021693c0188 100644 --- a/src/Model/GroundWaterFlow/gwf3.f90 +++ b/src/Model/GroundWaterFlow/gwf3.f90 @@ -1,8 +1,9 @@ module GwfModule use KindModule, only: DP, I4B - use InputOutputModule, only: ParseLine, upcase - use ConstantsModule, only: LENFTYPE, LENPAKLOC, DZERO, DEM1, DTEN, DEP20 + use InputOutputModule, only: ParseLine, upcase, lowcase + use ConstantsModule, only: LENFTYPE, LENMEMPATH, LENPAKLOC, DZERO, & + DEM1, DTEN, DEP20 use VersionModule, only: write_listfile_header use NumericalModelModule, only: NumericalModelType use BaseDisModule, only: DisBaseType @@ -20,7 +21,7 @@ module GwfModule use GwfOcModule, only: GwfOcType use GhostNodeModule, only: GhostNodeType, gnc_cr use GwfObsModule, only: GwfObsType, gwf_obs_cr - use SimModule, only: count_errors, store_error + use SimModule, only: count_errors, store_error, store_error_filename use BaseModelModule, only: BaseModelType use MatrixBaseModule @@ -48,7 +49,7 @@ module GwfModule type(BudgetType), pointer :: budget => null() ! budget object integer(I4B), pointer :: inic => null() ! unit number IC integer(I4B), pointer :: inoc => null() ! unit number OC - integer(I4B), pointer :: innpf => null() ! unit number NPF + integer(I4B), pointer :: innpf => null() ! NPF enabled flag integer(I4B), pointer :: inbuy => null() ! unit number BUY integer(I4B), pointer :: invsc => null() ! unit number VSC integer(I4B), pointer :: insto => null() ! unit number STO @@ -89,21 +90,13 @@ module GwfModule procedure :: gwf_ot_flow procedure :: gwf_ot_dv procedure :: gwf_ot_bdsummary - procedure :: load_input_context => gwf_load_input_context + procedure, private :: create_packages + procedure, private :: create_bndpkgs + procedure, private :: create_lstfile + procedure, private :: log_namfile_options ! end type GwfModelType - ! -- Module variables constant for simulation - integer(I4B), parameter :: NIUNIT = 100 - character(len=LENFTYPE), dimension(NIUNIT) :: cunit - data cunit/'IC6 ', 'DIS6 ', 'DISU6', 'OC6 ', 'NPF6 ', & ! 5 - &'STO6 ', 'HFB6 ', 'WEL6 ', 'DRN6 ', 'RIV6 ', & ! 10 - &'GHB6 ', 'RCH6 ', 'EVT6 ', 'OBS6 ', 'GNC6 ', & ! 15 - &'API6 ', 'CHD6 ', ' ', ' ', ' ', & ! 20 - &' ', 'MAW6 ', 'SFR6 ', 'LAK6 ', 'UZF6 ', & ! 25 - &'DISV6', 'MVR6 ', 'CSUB6', 'BUY6 ', 'VSC6 ', & ! 30 - &70*' '/ - contains !> @brief Create a new groundwater flow model object @@ -115,41 +108,23 @@ module GwfModule subroutine gwf_cr(filename, id, modelname) ! -- modules use ListsModule, only: basemodellist - use MemoryHelperModule, only: create_mem_path use BaseModelModule, only: AddBaseModelToList - use SimModule, only: store_error, count_errors - use GenericUtilitiesModule, only: write_centered - use ConstantsModule, only: LINELENGTH, LENPACKAGENAME - use MemoryManagerModule, only: mem_allocate - use GwfDisModule, only: dis_cr - use GwfDisvModule, only: disv_cr - use GwfDisuModule, only: disu_cr - use GwfNpfModule, only: npf_cr - use Xt3dModule, only: xt3d_cr - use GwfBuyModule, only: buy_cr - use GwfVscModule, only: vsc_cr - use GwfStoModule, only: sto_cr - use GwfCsubModule, only: csub_cr - use GwfMvrModule, only: mvr_cr - use GwfHfbModule, only: hfb_cr - use GwfIcModule, only: ic_cr - use GwfOcModule, only: oc_cr + use ConstantsModule, only: LINELENGTH + use MemoryHelperModule, only: create_mem_path + use MemoryManagerExtModule, only: mem_set_value + use SimVariablesModule, only: idm_context + use GwfNamInputModule, only: GwfNamParamFoundType use BudgetModule, only: budget_cr - use NameFileModule, only: NameFileType ! -- dummy character(len=*), intent(in) :: filename integer(I4B), intent(in) :: id character(len=*), intent(in) :: modelname ! -- local - integer(I4B) :: indis, indis6, indisu6, indisv6 - integer(I4B) :: ipakid, i, j, iu, ipaknum - character(len=LINELENGTH) :: errmsg - character(len=LENPACKAGENAME) :: pakname - type(NameFileType) :: namefile_obj type(GwfModelType), pointer :: this class(BaseModelType), pointer :: model - integer(I4B) :: nwords - character(len=LINELENGTH), allocatable, dimension(:) :: words + character(len=LENMEMPATH) :: input_mempath + character(len=LINELENGTH) :: lst_fname + type(GwfNamParamFoundType) :: found ! -- format ! ------------------------------------------------------------------------------ ! @@ -169,135 +144,38 @@ subroutine gwf_cr(filename, id, modelname) this%macronym = 'GWF' this%id = id ! - ! -- Open namefile and set iout - call namefile_obj%init(this%filename, 0) - call namefile_obj%add_cunit(niunit, cunit) - call namefile_obj%openlistfile(this%iout) - ! - ! -- Write header to model list file - call write_listfile_header(this%iout, 'GROUNDWATER FLOW MODEL (GWF)') - ! - ! -- Open files - call namefile_obj%openfiles(this%iout) - ! - ! -- GWF options - if (size(namefile_obj%opts) > 0) then - write (this%iout, '(1x,a)') 'NAMEFILE OPTIONS:' + ! -- set input model namfile memory path + input_mempath = create_mem_path(modelname, 'NAM', idm_context) + ! + ! -- copy option params from input context + call mem_set_value(lst_fname, 'LIST', input_mempath, found%list) + call mem_set_value(this%inewton, 'NEWTON', input_mempath, found%newton) + call mem_set_value(this%inewtonur, 'UNDER_RELAXATION', input_mempath, & + found%under_relaxation) + call mem_set_value(this%iprpak, 'PRINT_INPUT', input_mempath, & + found%print_input) + call mem_set_value(this%iprflow, 'PRINT_FLOWS', input_mempath, & + found%print_flows) + call mem_set_value(this%ipakcb, 'SAVE_FLOWS', input_mempath, found%save_flows) + ! + ! -- create the list file + call this%create_lstfile(lst_fname, filename, found%list) + ! + ! -- activate save_flows if found + if (found%save_flows) then + this%ipakcb = -1 end if ! - ! -- Parse options in the GWF name file - do i = 1, size(namefile_obj%opts) - call ParseLine(namefile_obj%opts(i), nwords, words) - call upcase(words(1)) - select case (words(1)) - case ('NEWTON') - this%inewton = 1 - write (this%iout, '(4x,a)') & - 'NEWTON-RAPHSON method enabled for the model.' - if (nwords > 1) then - call upcase(words(2)) - if (words(2) == 'UNDER_RELAXATION') then - this%inewtonur = 1 - write (this%iout, '(4x,a,a)') & - 'NEWTON-RAPHSON UNDER-RELAXATION based on the bottom ', & - 'elevation of the model will be applied to the model.' - end if - end if - case ('PRINT_INPUT') - this%iprpak = 1 - write (this%iout, '(4x,a)') 'STRESS PACKAGE INPUT WILL BE PRINTED '// & - 'FOR ALL MODEL STRESS PACKAGES' - case ('PRINT_FLOWS') - this%iprflow = 1 - write (this%iout, '(4x,a)') 'PACKAGE FLOWS WILL BE PRINTED '// & - 'FOR ALL MODEL PACKAGES' - case ('SAVE_FLOWS') - this%ipakcb = -1 - write (this%iout, '(4x,a)') & - 'FLOWS WILL BE SAVED TO BUDGET FILE SPECIFIED IN OUTPUT CONTROL' - case default - write (errmsg, '(4x,a,a,a,a)') & - 'Unknown GWF namefile (', & - trim(adjustl(this%filename)), ') option: ', & - trim(adjustl(namefile_obj%opts(i))) - call store_error(errmsg, terminate=.TRUE.) - end select - end do - ! - ! -- Assign unit numbers to attached modules, and remove - ! -- from unitnumber (by specifying 1 for iremove) - ! - indis = 0 - indis6 = 0 - indisu6 = 0 - indisv6 = 0 - call namefile_obj%get_unitnumber('DIS6', indis6, 1) - if (indis6 > 0) indis = indis6 - if (indis <= 0) call namefile_obj%get_unitnumber('DISU6', indisu6, 1) - if (indisu6 > 0) indis = indisu6 - if (indis <= 0) call namefile_obj%get_unitnumber('DISV6', indisv6, 1) - if (indisv6 > 0) indis = indisv6 - call namefile_obj%get_unitnumber('IC6', this%inic, 1) - call namefile_obj%get_unitnumber('OC6', this%inoc, 1) - call namefile_obj%get_unitnumber('NPF6', this%innpf, 1) - call namefile_obj%get_unitnumber('BUY6', this%inbuy, 1) - call namefile_obj%get_unitnumber('VSC6', this%invsc, 1) - call namefile_obj%get_unitnumber('STO6', this%insto, 1) - call namefile_obj%get_unitnumber('CSUB6', this%incsub, 1) - call namefile_obj%get_unitnumber('MVR6', this%inmvr, 1) - call namefile_obj%get_unitnumber('HFB6', this%inhfb, 1) - call namefile_obj%get_unitnumber('GNC6', this%ingnc, 1) - call namefile_obj%get_unitnumber('OBS6', this%inobs, 1) - ! - ! -- Check to make sure that required ftype's have been specified - call this%ftype_check(namefile_obj, indis) - ! - ! -- Create discretization object - if (indis6 > 0) then - call this%load_input_context('DIS6', this%name, 'DIS', indis, this%iout) - call dis_cr(this%dis, this%name, indis, this%iout) - elseif (indisu6 > 0) then - call this%load_input_context('DISU6', this%name, 'DISU', indis, this%iout) - call disu_cr(this%dis, this%name, indis, this%iout) - elseif (indisv6 > 0) then - call this%load_input_context('DISV6', this%name, 'DISV', indis, this%iout) - call disv_cr(this%dis, this%name, indis, this%iout) + ! -- log set options + if (this%iout > 0) then + call this%log_namfile_options(found) end if ! ! -- Create utility objects call budget_cr(this%budget, this%name) ! - ! -- Load input context for currently supported packages - call this%load_input_context('NPF6', this%name, 'NPF', this%innpf, this%iout) - ! - ! -- Create packages that are tied directly to model - call npf_cr(this%npf, this%name, this%innpf, this%iout) - call xt3d_cr(this%xt3d, this%name, this%innpf, this%iout) - call buy_cr(this%buy, this%name, this%inbuy, this%iout) - call vsc_cr(this%vsc, this%name, this%invsc, this%iout) - call gnc_cr(this%gnc, this%name, this%ingnc, this%iout) - call hfb_cr(this%hfb, this%name, this%inhfb, this%iout) - call sto_cr(this%sto, this%name, this%insto, this%iout) - call csub_cr(this%csub, this%name, this%insto, this%sto%packName, & - this%incsub, this%iout) - call ic_cr(this%ic, this%name, this%inic, this%iout, this%dis) - call mvr_cr(this%mvr, this%name, this%inmvr, this%iout, this%dis) - call oc_cr(this%oc, this%name, this%inoc, this%iout) - call gwf_obs_cr(this%obs, this%inobs) - ! - ! -- Create stress packages - ipakid = 1 - do i = 1, niunit - ipaknum = 1 - do j = 1, namefile_obj%get_nval_for_row(i) - iu = namefile_obj%get_unitnumber_rowcol(i, j) - call namefile_obj%get_pakname(i, j, pakname) - call this%package_create(cunit(i), ipakid, ipaknum, pakname, iu, & - this%iout) - ipaknum = ipaknum + 1 - ipakid = ipakid + 1 - end do - end do + ! -- create model packages + call this%create_packages() ! ! -- return return @@ -311,6 +189,7 @@ end subroutine gwf_cr !< subroutine gwf_df(this) ! -- modules + use ModelPackageInputsModule, only: NIUNIT_GWF ! -- dummy class(GwfModelType) :: this ! -- local @@ -322,7 +201,7 @@ subroutine gwf_df(this) call this%dis%dis_df() call this%npf%npf_df(this%dis, this%xt3d, this%ingnc, this%invsc) call this%oc%oc_df() - call this%budget%budget_df(niunit, 'VOLUME', 'L**3') + call this%budget%budget_df(NIUNIT_GWF, 'VOLUME', 'L**3') if (this%inbuy > 0) call this%buy%buy_df(this%dis) if (this%invsc > 0) call this%vsc%vsc_df(this%dis) if (this%ingnc > 0) call this%gnc%gnc_df(this) @@ -1219,12 +1098,18 @@ end subroutine gwf_fp subroutine gwf_da(this) ! -- modules use MemoryManagerModule, only: mem_deallocate + use MemoryManagerExtModule, only: memorylist_remove + use SimVariablesModule, only: idm_context ! -- dummy class(GwfModelType) :: this ! -- local integer(I4B) :: ip class(BndType), pointer :: packobj ! ------------------------------------------------------------------------------ + ! + ! -- Deallocate idm memory + call memorylist_remove(this%name, 'NAM', idm_context) + call memorylist_remove(component=this%name, context=idm_context) ! ! -- Internal flow packages deallocate call this%dis%dis_da() @@ -1478,24 +1363,15 @@ subroutine package_create(this, filtyp, ipakid, ipaknum, pakname, inunit, & end subroutine package_create !> @brief Check to make sure required input files have been specified - subroutine ftype_check(this, namefile_obj, indis) + subroutine ftype_check(this, indis) ! -- modules use ConstantsModule, only: LINELENGTH use SimModule, only: store_error, count_errors - use NameFileModule, only: NameFileType ! -- dummy class(GwfModelType) :: this - type(NameFileType), intent(in) :: namefile_obj integer(I4B), intent(in) :: indis ! -- local character(len=LINELENGTH) :: errmsg - integer(I4B) :: i, iu - character(len=LENFTYPE), dimension(13) :: nodupftype = & - (/'DIS6 ', 'DISU6', 'DISV6', & - 'IC6 ', 'OC6 ', 'NPF6 ', & - 'STO6 ', 'MVR6 ', 'HFB6 ', & - 'GNC6 ', 'BUY6 ', 'VSC6 ', & - 'OBS6 '/) ! ------------------------------------------------------------------------------ ! ! -- Check for IC8, DIS(u), and NPF. Stop if not present. @@ -1514,28 +1390,11 @@ subroutine ftype_check(this, namefile_obj, indis) 'Node Property Flow (NPF6) Package not specified.' call store_error(errmsg) end if + ! if (count_errors() > 0) then write (errmsg, '(1x,a)') 'One or more required package(s) not specified.' call store_error(errmsg) - end if - ! - ! -- Check to make sure that some GWF packages are not specified more - ! than once - do i = 1, size(nodupftype) - call namefile_obj%get_unitnumber(trim(nodupftype(i)), iu, 0) - if (iu > 0) then - write (errmsg, '(1x, a, a, a)') & - 'Duplicate entries for FTYPE ', trim(nodupftype(i)), & - ' not allowed for GWF Model.' - call store_error(errmsg) - end if - end do - ! - ! -- Stop if errors - if (count_errors() > 0) then - write (errmsg, '(a, a)') 'Error occurred while reading file: ', & - trim(namefile_obj%filename) - call store_error(errmsg, terminate=.TRUE.) + call store_error_filename(this%filename) end if ! ! -- return @@ -1559,38 +1418,274 @@ function CastAsGwfModel(model) result(gwfModel) end function CastAsGwfModel - !> @brief Load input context for supported package + !> @brief Source package info and begin to process !< - subroutine gwf_load_input_context(this, filtyp, modelname, pkgname, inunit, & - iout, ipaknum) + subroutine create_bndpkgs(this, bndpkgs, pkgtypes, pkgnames, & + mempaths, inunits) ! -- modules - use IdmMf6FileLoaderModule, only: input_load + use ConstantsModule, only: LINELENGTH, LENPACKAGENAME + use CharacterStringModule, only: CharacterStringType ! -- dummy class(GwfModelType) :: this - character(len=*), intent(in) :: filtyp - character(len=*), intent(in) :: modelname - character(len=*), intent(in) :: pkgname - integer(I4B), intent(in) :: inunit - integer(I4B), intent(in) :: iout - integer(I4B), optional, intent(in) :: ipaknum + integer(I4B), dimension(:), allocatable, intent(inout) :: bndpkgs + type(CharacterStringType), dimension(:), contiguous, & + pointer, intent(inout) :: pkgtypes + type(CharacterStringType), dimension(:), contiguous, & + pointer, intent(inout) :: pkgnames + type(CharacterStringType), dimension(:), contiguous, & + pointer, intent(inout) :: mempaths + integer(I4B), dimension(:), contiguous, & + pointer, intent(inout) :: inunits ! -- local -! ------------------------------------------------------------------------------ + integer(I4B) :: ipakid, ipaknum + character(len=LENFTYPE) :: pkgtype, bndptype + character(len=LENPACKAGENAME) :: pkgname + character(len=LENMEMPATH) :: mempath + integer(I4B), pointer :: inunit + integer(I4B) :: n + + if (allocated(bndpkgs)) then + ! + ! -- create stress packages + ipakid = 1 + bndptype = '' + do n = 1, size(bndpkgs) + ! + pkgtype = pkgtypes(bndpkgs(n)) + pkgname = pkgnames(bndpkgs(n)) + mempath = mempaths(bndpkgs(n)) + inunit => inunits(bndpkgs(n)) + ! + if (bndptype /= pkgtype) then + ipaknum = 1 + bndptype = pkgtype + end if + ! + call this%package_create(pkgtype, ipakid, ipaknum, pkgname, inunit, & + this%iout) + ipakid = ipakid + 1 + ipaknum = ipaknum + 1 + end do + ! + ! -- cleanup + deallocate (bndpkgs) + end if ! - ! -- only load if there is a file to read - if (inunit <= 0) return + ! -- return + return + end subroutine create_bndpkgs + + !> @brief Source package info and begin to process + !< + subroutine create_packages(this) + ! -- modules + use ConstantsModule, only: LINELENGTH, LENPACKAGENAME + use CharacterStringModule, only: CharacterStringType + use ArrayHandlersModule, only: expandarray + use MemoryManagerModule, only: mem_setptr + use MemoryHelperModule, only: create_mem_path + use SimVariablesModule, only: idm_context + use GwfDisModule, only: dis_cr + use GwfDisvModule, only: disv_cr + use GwfDisuModule, only: disu_cr + use GwfNpfModule, only: npf_cr + use Xt3dModule, only: xt3d_cr + use GwfBuyModule, only: buy_cr + use GwfVscModule, only: vsc_cr + use GwfStoModule, only: sto_cr + use GwfCsubModule, only: csub_cr + use GwfMvrModule, only: mvr_cr + use GwfHfbModule, only: hfb_cr + use GwfIcModule, only: ic_cr + use GwfOcModule, only: oc_cr + ! -- dummy + class(GwfModelType) :: this + ! -- local + type(CharacterStringType), dimension(:), contiguous, & + pointer :: pkgtypes => null() + type(CharacterStringType), dimension(:), contiguous, & + pointer :: pkgnames => null() + type(CharacterStringType), dimension(:), contiguous, & + pointer :: mempaths => null() + integer(I4B), dimension(:), contiguous, & + pointer :: inunits => null() + character(len=LENMEMPATH) :: model_mempath + character(len=LENFTYPE) :: pkgtype + character(len=LENPACKAGENAME) :: pkgname + character(len=LENMEMPATH) :: mempath + integer(I4B), pointer :: inunit + integer(I4B), dimension(:), allocatable :: bndpkgs + integer(I4B) :: n + integer(I4B) :: indis = 0 ! DIS enabled flag + character(len=LENMEMPATH) :: mempathnpf = '' ! - ! -- Load model package input to input context - select case (filtyp) - case ('NPF6') - call input_load('NPF6', 'GWF', 'NPF', modelname, pkgname, inunit, iout) - case default - call this%NumericalModelType%load_input_context(filtyp, modelname, & - pkgname, inunit, iout, & - ipaknum) - end select + ! -- set input model memory path + model_mempath = create_mem_path(component=this%name, context=idm_context) + ! + ! -- set pointers to model path package info + call mem_setptr(pkgtypes, 'PKGTYPES', model_mempath) + call mem_setptr(pkgnames, 'PKGNAMES', model_mempath) + call mem_setptr(mempaths, 'MEMPATHS', model_mempath) + call mem_setptr(inunits, 'INUNITS', model_mempath) + ! + do n = 1, size(pkgtypes) + ! + ! attributes for this input package + pkgtype = pkgtypes(n) + pkgname = pkgnames(n) + mempath = mempaths(n) + inunit => inunits(n) + ! + ! -- create dis package as it is a prerequisite for other packages + select case (pkgtype) + case ('DIS6') + indis = 1 + call dis_cr(this%dis, this%name, mempath, indis, this%iout) + case ('DISV6') + indis = 1 + call disv_cr(this%dis, this%name, mempath, indis, this%iout) + case ('DISU6') + indis = 1 + call disu_cr(this%dis, this%name, mempath, indis, this%iout) + case ('NPF6') + this%innpf = 1 + mempathnpf = mempath + case ('BUY6') + this%inbuy = inunit + case ('VSC6') + this%invsc = inunit + case ('GNC6') + this%ingnc = inunit + case ('HFB6') + this%inhfb = inunit + case ('STO6') + this%insto = inunit + case ('CSUB6') + this%incsub = inunit + case ('IC6') + this%inic = inunit + case ('MVR6') + this%inmvr = inunit + case ('OC6') + this%inoc = inunit + case ('OBS6') + this%inobs = inunit + case ('WEL6', 'DRN6', 'RIV6', 'GHB6', 'RCH6', 'EVT6', & + 'API6', 'CHD6', 'MAW6', 'SFR6', 'LAK6', 'UZF6') + call expandarray(bndpkgs) + bndpkgs(size(bndpkgs)) = n + case default + ! TODO + end select + end do + ! + ! -- Create packages that are tied directly to model + call npf_cr(this%npf, this%name, mempathnpf, this%innpf, this%iout) + call xt3d_cr(this%xt3d, this%name, this%innpf, this%iout) + call buy_cr(this%buy, this%name, this%inbuy, this%iout) + call vsc_cr(this%vsc, this%name, this%invsc, this%iout) + call gnc_cr(this%gnc, this%name, this%ingnc, this%iout) + call hfb_cr(this%hfb, this%name, this%inhfb, this%iout) + call sto_cr(this%sto, this%name, this%insto, this%iout) + call csub_cr(this%csub, this%name, this%insto, this%sto%packName, & + this%incsub, this%iout) + call ic_cr(this%ic, this%name, this%inic, this%iout, this%dis) + call mvr_cr(this%mvr, this%name, this%inmvr, this%iout, this%dis) + call oc_cr(this%oc, this%name, this%inoc, this%iout) + call gwf_obs_cr(this%obs, this%inobs) + ! + ! -- Check to make sure that required ftype's have been specified + call this%ftype_check(indis) + ! + call this%create_bndpkgs(bndpkgs, pkgtypes, pkgnames, mempaths, inunits) ! ! -- return return - end subroutine gwf_load_input_context + end subroutine create_packages + + subroutine create_lstfile(this, lst_fname, model_fname, defined) + ! -- modules + use KindModule, only: LGP + use InputOutputModule, only: openfile, getunit + ! -- dummy + class(GwfModelType) :: this + character(len=*), intent(inout) :: lst_fname + character(len=*), intent(in) :: model_fname + logical(LGP), intent(in) :: defined + ! -- local + integer(I4B) :: i, istart, istop + ! + ! -- set list file name if not provided + if (.not. defined) then + ! + ! -- initialize + lst_fname = ' ' + istart = 0 + istop = len_trim(model_fname) + ! + ! -- identify '.' character position from back of string + do i = istop, 1, -1 + if (model_fname(i:i) == '.') then + istart = i + exit + end if + end do + ! + ! -- if not found start from string end + if (istart == 0) istart = istop + 1 + ! + ! -- set list file name + lst_fname = model_fname(1:istart) + istop = istart + 3 + lst_fname(istart:istop) = '.lst' + end if + ! + ! -- create the list file + this%iout = getunit() + call openfile(this%iout, 0, lst_fname, 'LIST', filstat_opt='REPLACE') + ! + ! -- write list file header + call write_listfile_header(this%iout, 'GROUNDWATER FLOW MODEL (GWF)') + ! + ! -- return + return + end subroutine create_lstfile + + !> @brief Write model namfile options to list file + !< + subroutine log_namfile_options(this, found) + use GwfNamInputModule, only: GwfNamParamFoundType + class(GwfModelType) :: this + type(GwfNamParamFoundType), intent(in) :: found + + write (this%iout, '(1x,a)') 'NAMEFILE OPTIONS:' + + if (found%newton) then + write (this%iout, '(4x,a)') & + 'NEWTON-RAPHSON method enabled for the model.' + if (found%under_relaxation) then + write (this%iout, '(4x,a,a)') & + 'NEWTON-RAPHSON UNDER-RELAXATION based on the bottom ', & + 'elevation of the model will be applied to the model.' + end if + end if + + if (found%print_input) then + write (this%iout, '(4x,a)') 'STRESS PACKAGE INPUT WILL BE PRINTED '// & + 'FOR ALL MODEL STRESS PACKAGES' + end if + + if (found%print_flows) then + write (this%iout, '(4x,a)') 'PACKAGE FLOWS WILL BE PRINTED '// & + 'FOR ALL MODEL PACKAGES' + end if + + if (found%save_flows) then + write (this%iout, '(4x,a)') & + 'FLOWS WILL BE SAVED TO BUDGET FILE SPECIFIED IN OUTPUT CONTROL' + end if + + write (this%iout, '(1x,a)') 'END NAMEFILE OPTIONS:' + end subroutine log_namfile_options end module GwfModule diff --git a/src/Model/GroundWaterFlow/gwf3dis8.f90 b/src/Model/GroundWaterFlow/gwf3dis8.f90 index 123ec841418..f13f34ee9f9 100644 --- a/src/Model/GroundWaterFlow/gwf3dis8.f90 +++ b/src/Model/GroundWaterFlow/gwf3dis8.f90 @@ -6,9 +6,9 @@ module GwfDisModule use BaseDisModule, only: DisBaseType use InputOutputModule, only: get_node, URWORD, ulasav, ulaprufw, ubdsv1, & ubdsv06 - use SimModule, only: count_errors, store_error, store_error_unit + use SimModule, only: count_errors, store_error, store_error_unit, & + store_error_filename use MemoryManagerModule, only: mem_allocate - use MemoryHelperModule, only: create_mem_path use TdisModule, only: kstp, kper, pertim, totim, delt implicit none @@ -65,7 +65,7 @@ module GwfDisModule contains - subroutine dis_cr(dis, name_model, inunit, iout) + subroutine dis_cr(dis, name_model, input_mempath, inunit, iout) ! ****************************************************************************** ! dis_cr -- Create a new discretization 3d object ! ****************************************************************************** @@ -73,31 +73,38 @@ subroutine dis_cr(dis, name_model, inunit, iout) ! SPECIFICATIONS: ! ------------------------------------------------------------------------------ ! -- modules - use IdmMf6FileLoaderModule, only: input_load - use ConstantsModule, only: LENPACKAGETYPE + use KindModule, only: LGP + use MemoryManagerExtModule, only: mem_set_value ! -- dummy class(DisBaseType), pointer :: dis character(len=*), intent(in) :: name_model + character(len=*), intent(in) :: input_mempath integer(I4B), intent(in) :: inunit integer(I4B), intent(in) :: iout ! -- locals type(GwfDisType), pointer :: disnew + logical(LGP) :: found_fname character(len=*), parameter :: fmtheader = & "(1X, /1X, 'DIS -- STRUCTURED GRID DISCRETIZATION PACKAGE,', & - &' VERSION 2 : 3/27/2014 - INPUT READ FROM UNIT ', I0, /)" + &' VERSION 2 : 3/27/2014 - INPUT READ FROM MEMPATH: ', A, /)" ! ------------------------------------------------------------------------------ allocate (disnew) dis => disnew call disnew%allocate_scalars(name_model) + dis%input_mempath = input_mempath dis%inunit = inunit dis%iout = iout ! - ! -- if reading from file + ! -- set name of input file + call mem_set_value(dis%input_fname, 'INPUT_FNAME', dis%input_mempath, & + found_fname) + ! + ! -- If dis enabled if (inunit > 0) then ! ! -- Identify package if (iout > 0) then - write (iout, fmtheader) inunit + write (iout, fmtheader) dis%input_mempath end if end if ! @@ -113,12 +120,10 @@ subroutine dis3d_df(this) ! SPECIFICATIONS: ! ------------------------------------------------------------------------------ ! -- modules - use ConstantsModule, only: DNODATA ! -- dummy class(GwfDisType) :: this ! -- locals ! ------------------------------------------------------------------------------ - ! ! -- Transfer the data from the memory manager into this package object if (this%inunit /= 0) then ! @@ -157,8 +162,6 @@ subroutine dis3d_da(this) ! ! -- Deallocate idm memory call memorylist_remove(this%name_model, 'DIS', idm_context) - call memorylist_remove(component=this%name_model, & - context=idm_context) ! ! -- DisBaseType deallocate call this%DisBaseType%dis_da() @@ -187,29 +190,22 @@ end subroutine dis3d_da !< subroutine source_options(this) ! -- modules - use KindModule, only: LGP - use MemoryTypeModule, only: MemoryType use MemoryManagerExtModule, only: mem_set_value - use SimVariablesModule, only: idm_context use GwfDisInputModule, only: GwfDisParamFoundType ! -- dummy class(GwfDisType) :: this ! -- locals - character(len=LENMEMPATH) :: idmMemoryPath character(len=LENVARNAME), dimension(3) :: lenunits = & &[character(len=LENVARNAME) :: 'FEET', 'METERS', 'CENTIMETERS'] type(GwfDisParamFoundType) :: found ! - ! -- set memory path - idmMemoryPath = create_mem_path(this%name_model, 'DIS', idm_context) - ! ! -- update defaults with idm sourced values - call mem_set_value(this%lenuni, 'LENGTH_UNITS', idmMemoryPath, lenunits, & - found%length_units) - call mem_set_value(this%nogrb, 'NOGRB', idmMemoryPath, found%nogrb) - call mem_set_value(this%xorigin, 'XORIGIN', idmMemoryPath, found%xorigin) - call mem_set_value(this%yorigin, 'YORIGIN', idmMemoryPath, found%yorigin) - call mem_set_value(this%angrot, 'ANGROT', idmMemoryPath, found%angrot) + call mem_set_value(this%lenuni, 'LENGTH_UNITS', this%input_mempath, & + lenunits, found%length_units) + call mem_set_value(this%nogrb, 'NOGRB', this%input_mempath, found%nogrb) + call mem_set_value(this%xorigin, 'XORIGIN', this%input_mempath, found%xorigin) + call mem_set_value(this%yorigin, 'YORIGIN', this%input_mempath, found%yorigin) + call mem_set_value(this%angrot, 'ANGROT', this%input_mempath, found%angrot) ! ! -- log values to list file if (this%iout > 0) then @@ -258,25 +254,18 @@ end subroutine log_options !> @brief Copy dimensions from IDM into package !< subroutine source_dimensions(this) - use KindModule, only: LGP - use MemoryTypeModule, only: MemoryType use MemoryManagerExtModule, only: mem_set_value - use SimVariablesModule, only: idm_context use GwfDisInputModule, only: GwfDisParamFoundType ! -- dummy class(GwfDisType) :: this ! -- locals - character(len=LENMEMPATH) :: idmMemoryPath integer(I4B) :: i, j, k type(GwfDisParamFoundType) :: found ! - ! -- set memory path - idmMemoryPath = create_mem_path(this%name_model, 'DIS', idm_context) - ! ! -- update defaults with idm sourced values - call mem_set_value(this%nlay, 'NLAY', idmMemoryPath, found%nlay) - call mem_set_value(this%nrow, 'NROW', idmMemoryPath, found%nrow) - call mem_set_value(this%ncol, 'NCOL', idmMemoryPath, found%ncol) + call mem_set_value(this%nlay, 'NLAY', this%input_mempath, found%nlay) + call mem_set_value(this%nrow, 'NROW', this%input_mempath, found%nrow) + call mem_set_value(this%ncol, 'NCOL', this%input_mempath, found%ncol) ! ! -- log simulation values if (this%iout > 0) then @@ -287,17 +276,17 @@ subroutine source_dimensions(this) if (this%nlay < 1) then call store_error( & 'NLAY was not specified or was specified incorrectly.') - call store_error_unit(this%inunit) + call store_error_filename(this%input_fname) end if if (this%nrow < 1) then call store_error( & 'NROW was not specified or was specified incorrectly.') - call store_error_unit(this%inunit) + call store_error_filename(this%input_fname) end if if (this%ncol < 1) then call store_error( & 'NCOL was not specified or was specified incorrectly.') - call store_error_unit(this%inunit) + call store_error_filename(this%input_fname) end if ! ! -- calculate nodesuser @@ -361,25 +350,20 @@ subroutine source_griddata(this) ! ------------------------------------------------------------------------------ ! -- modules use MemoryManagerExtModule, only: mem_set_value - use SimVariablesModule, only: idm_context use GwfDisInputModule, only: GwfDisParamFoundType ! -- dummy class(GwfDisType) :: this ! -- locals - character(len=LENMEMPATH) :: idmMemoryPath type(GwfDisParamFoundType) :: found ! -- formats ! ------------------------------------------------------------------------------ - ! - ! -- set memory path - idmMemoryPath = create_mem_path(this%name_model, 'DIS', idm_context) ! ! -- update defaults with idm sourced values - call mem_set_value(this%delr, 'DELR', idmMemoryPath, found%delr) - call mem_set_value(this%delc, 'DELC', idmMemoryPath, found%delc) - call mem_set_value(this%top2d, 'TOP', idmMemoryPath, found%top) - call mem_set_value(this%bot3d, 'BOTM', idmMemoryPath, found%botm) - call mem_set_value(this%idomain, 'IDOMAIN', idmMemoryPath, found%idomain) + call mem_set_value(this%delr, 'DELR', this%input_mempath, found%delr) + call mem_set_value(this%delc, 'DELC', this%input_mempath, found%delc) + call mem_set_value(this%top2d, 'TOP', this%input_mempath, found%top) + call mem_set_value(this%bot3d, 'BOTM', this%input_mempath, found%botm) + call mem_set_value(this%idomain, 'IDOMAIN', this%input_mempath, found%idomain) ! ! -- log simulation values if (this%iout > 0) then @@ -468,7 +452,7 @@ subroutine grid_finalize(this) call store_error('Model does not have any active nodes. & &Ensure IDOMAIN array has some values greater & &than zero.') - call store_error_unit(this%inunit) + call store_error_filename(this%input_fname) end if ! ! -- Check cell thicknesses @@ -492,7 +476,7 @@ subroutine grid_finalize(this) end do end do if (count_errors() > 0) then - call store_error_unit(this%inunit) + call store_error_filename(this%input_fname) end if ! ! -- Write message if reduced grid @@ -626,8 +610,7 @@ subroutine write_grb(this, icelltype) ncpl = this%nrow * this%ncol ! ! -- Open the file - inquire (unit=this%inunit, name=fname) - fname = trim(fname)//'.grb' + fname = trim(this%input_fname)//'.grb' iunit = getunit() write (this%iout, fmtgrdsave) iunit, trim(adjustl(fname)) call openfile(iunit, this%iout, trim(adjustl(fname)), 'DATA(BINARY)', & diff --git a/src/Model/GroundWaterFlow/gwf3disu8.f90 b/src/Model/GroundWaterFlow/gwf3disu8.f90 index a9bb4eeca35..003ba6091c8 100644 --- a/src/Model/GroundWaterFlow/gwf3disu8.f90 +++ b/src/Model/GroundWaterFlow/gwf3disu8.f90 @@ -6,7 +6,8 @@ module GwfDisuModule DZERO, DONE use ConnectionsModule, only: iac_to_ia use InputOutputModule, only: URWORD, ulasav, ulaprufw, ubdsv1, ubdsv06 - use SimModule, only: count_errors, store_error, store_error_unit + use SimModule, only: count_errors, store_error, store_error_unit, & + store_error_filename use SimVariablesModule, only: errmsg use BaseDisModule, only: DisBaseType use MemoryManagerModule, only: mem_allocate @@ -81,7 +82,7 @@ module GwfDisuModule contains - subroutine disu_cr(dis, name_model, inunit, iout) + subroutine disu_cr(dis, name_model, input_mempath, inunit, iout) ! ****************************************************************************** ! disu_cr -- Create discretization object ! ****************************************************************************** @@ -89,18 +90,20 @@ subroutine disu_cr(dis, name_model, inunit, iout) ! SPECIFICATIONS: ! ------------------------------------------------------------------------------ ! -- modules - use IdmMf6FileLoaderModule, only: input_load - use ConstantsModule, only: LENPACKAGETYPE + use KindModule, only: LGP + use MemoryManagerExtModule, only: mem_set_value ! -- dummy class(DisBaseType), pointer :: dis character(len=*), intent(in) :: name_model + character(len=*), intent(in) :: input_mempath integer(I4B), intent(in) :: inunit integer(I4B), intent(in) :: iout ! -- local type(GwfDisuType), pointer :: disnew + logical(LGP) :: found_fname character(len=*), parameter :: fmtheader = & "(1X, /1X, 'DISU -- UNSTRUCTURED GRID DISCRETIZATION PACKAGE,', & - &' VERSION 2 : 3/27/2014 - INPUT READ FROM UNIT ', I0, //)" + &' VERSION 2 : 3/27/2014 - INPUT READ FROM MEMPATH: ', A, //)" ! ------------------------------------------------------------------------------ ! ! -- Create a new discretization object @@ -109,15 +112,20 @@ subroutine disu_cr(dis, name_model, inunit, iout) ! ! -- Allocate scalars and assign data call dis%allocate_scalars(name_model) + dis%input_mempath = input_mempath dis%inunit = inunit dis%iout = iout ! - ! -- if reading from file + ! -- set name of input file + call mem_set_value(dis%input_fname, 'INPUT_FNAME', dis%input_mempath, & + found_fname) + ! + ! -- If disu is enabled if (inunit > 0) then ! ! -- Identify package if (iout > 0) then - write (iout, fmtheader) inunit + write (iout, fmtheader) dis%input_mempath end if ! ! -- load disu @@ -135,7 +143,7 @@ subroutine disu_load(this) ! ! SPECIFICATIONS: ! ------------------------------------------------------------------------------ - use MemoryHelperModule, only: create_mem_path + ! -- modules ! -- dummy class(GwfDisuType) :: this ! ------------------------------------------------------------------------------ @@ -219,7 +227,7 @@ subroutine grid_finalize(this) call store_error('Model does not have any active nodes. & &Ensure IDOMAIN array has some values greater & &than zero.') - call store_error_unit(this%inunit) + call store_error_filename(this%input_fname) end if ! ! -- Write message if reduced grid @@ -363,7 +371,7 @@ subroutine disu_ck(this) ! -- terminate if errors found if (count_errors() > 0) then if (this%inunit > 0) then - call store_error_unit(this%inunit) + call store_error_filename(this%input_fname) end if end if ! @@ -398,7 +406,7 @@ subroutine disu_ck(this) this%voffsettol call store_error(errmsg) if (this%inunit > 0) then - call store_error_unit(this%inunit) + call store_error_filename(this%input_fname) end if end if ! @@ -421,7 +429,7 @@ subroutine disu_ck(this) ! -- terminate if errors found if (count_errors() > 0) then if (this%inunit > 0) then - call store_error_unit(this%inunit) + call store_error_filename(this%input_fname) end if end if ! @@ -592,31 +600,24 @@ subroutine source_options(this) ! SPECIFICATIONS: ! ------------------------------------------------------------------------------ ! -- modules - use KindModule, only: LGP - use MemoryHelperModule, only: create_mem_path use MemoryManagerExtModule, only: mem_set_value - use SimVariablesModule, only: idm_context use GwfDisuInputModule, only: GwfDisuParamFoundType ! -- dummy class(GwfDisuType) :: this ! -- locals - character(len=LENMEMPATH) :: idmMemoryPath character(len=LENVARNAME), dimension(3) :: lenunits = & &[character(len=LENVARNAME) :: 'FEET', 'METERS', 'CENTIMETERS'] type(GwfDisuParamFoundType) :: found ! ------------------------------------------------------------------------------ - ! - ! -- set memory path - idmMemoryPath = create_mem_path(this%name_model, 'DISU', idm_context) ! ! -- update defaults with idm sourced values - call mem_set_value(this%lenuni, 'LENGTH_UNITS', idmMemoryPath, lenunits, & - found%length_units) - call mem_set_value(this%nogrb, 'NOGRB', idmMemoryPath, found%nogrb) - call mem_set_value(this%xorigin, 'XORIGIN', idmMemoryPath, found%xorigin) - call mem_set_value(this%yorigin, 'YORIGIN', idmMemoryPath, found%yorigin) - call mem_set_value(this%angrot, 'ANGROT', idmMemoryPath, found%angrot) - call mem_set_value(this%voffsettol, 'VOFFSETTOL', idmMemoryPath, & + call mem_set_value(this%lenuni, 'LENGTH_UNITS', this%input_mempath, & + lenunits, found%length_units) + call mem_set_value(this%nogrb, 'NOGRB', this%input_mempath, found%nogrb) + call mem_set_value(this%xorigin, 'XORIGIN', this%input_mempath, found%xorigin) + call mem_set_value(this%yorigin, 'YORIGIN', this%input_mempath, found%yorigin) + call mem_set_value(this%angrot, 'ANGROT', this%input_mempath, found%angrot) + call mem_set_value(this%voffsettol, 'VOFFSETTOL', this%input_mempath, & found%voffsettol) ! ! -- log values to list file @@ -662,26 +663,19 @@ subroutine source_dimensions(this) ! ! SPECIFICATIONS: ! ------------------------------------------------------------------------------ - use KindModule, only: LGP - use MemoryHelperModule, only: create_mem_path use MemoryManagerExtModule, only: mem_set_value - use SimVariablesModule, only: idm_context use GwfDisuInputModule, only: GwfDisuParamFoundType ! -- dummy class(GwfDisuType) :: this ! -- locals - character(len=LENMEMPATH) :: idmMemoryPath integer(I4B) :: n type(GwfDisuParamFoundType) :: found ! ------------------------------------------------------------------------------ - ! - ! -- set memory path - idmMemoryPath = create_mem_path(this%name_model, 'DISU', idm_context) ! ! -- update defaults with idm sourced values - call mem_set_value(this%nodesuser, 'NODES', idmMemoryPath, found%nodes) - call mem_set_value(this%njausr, 'NJA', idmMemoryPath, found%nja) - call mem_set_value(this%nvert, 'NVERT', idmMemoryPath, found%nvert) + call mem_set_value(this%nodesuser, 'NODES', this%input_mempath, found%nodes) + call mem_set_value(this%njausr, 'NJA', this%input_mempath, found%nja) + call mem_set_value(this%nvert, 'NVERT', this%input_mempath, found%nvert) ! ! -- log simulation values if (this%iout > 0) then @@ -700,7 +694,7 @@ subroutine source_dimensions(this) ! ! -- terminate if errors were detected if (count_errors() > 0) then - call store_error_unit(this%inunit) + call store_error_filename(this%input_fname) end if ! ! -- allocate vectors that are the size of nodesuser @@ -769,26 +763,20 @@ subroutine source_griddata(this) ! SPECIFICATIONS: ! ------------------------------------------------------------------------------ ! -- modules - use MemoryHelperModule, only: create_mem_path use MemoryManagerExtModule, only: mem_set_value - use SimVariablesModule, only: idm_context use GwfDisuInputModule, only: GwfDisuParamFoundType ! -- dummy class(GwfDisuType) :: this ! -- locals - character(len=LENMEMPATH) :: idmMemoryPath type(GwfDisuParamFoundType) :: found ! -- formats ! ------------------------------------------------------------------------------ - ! - ! -- set memory path - idmMemoryPath = create_mem_path(this%name_model, 'DISU', idm_context) ! ! -- update defaults with idm sourced values - call mem_set_value(this%top1d, 'TOP', idmMemoryPath, found%top) - call mem_set_value(this%bot1d, 'BOT', idmMemoryPath, found%bot) - call mem_set_value(this%area1d, 'AREA', idmMemoryPath, found%area) - call mem_set_value(this%idomain, 'IDOMAIN', idmMemoryPath, found%idomain) + call mem_set_value(this%top1d, 'TOP', this%input_mempath, found%top) + call mem_set_value(this%bot1d, 'BOT', this%input_mempath, found%bot) + call mem_set_value(this%area1d, 'AREA', this%input_mempath, found%area) + call mem_set_value(this%idomain, 'IDOMAIN', this%input_mempath, found%idomain) ! ! -- log simulation values if (this%iout > 0) then @@ -845,33 +833,27 @@ subroutine source_connectivity(this) ! SPECIFICATIONS: ! ------------------------------------------------------------------------------ ! -- modules - use MemoryHelperModule, only: create_mem_path use MemoryManagerModule, only: mem_setptr use MemoryManagerExtModule, only: mem_set_value - use SimVariablesModule, only: idm_context use GwfDisuInputModule, only: GwfDisuParamFoundType ! -- dummy class(GwfDisuType) :: this ! -- locals - character(len=LENMEMPATH) :: idmMemoryPath type(GwfDisuParamFoundType) :: found integer(I4B), dimension(:), contiguous, pointer :: iac => null() ! -- formats ! ------------------------------------------------------------------------------ - ! - ! -- set memory path - idmMemoryPath = create_mem_path(this%name_model, 'DISU', idm_context) ! ! -- update defaults with idm sourced values - call mem_set_value(this%jainp, 'JA', idmMemoryPath, found%ja) - call mem_set_value(this%ihcinp, 'IHC', idmMemoryPath, found%ihc) - call mem_set_value(this%cl12inp, 'CL12', idmMemoryPath, found%cl12) - call mem_set_value(this%hwvainp, 'HWVA', idmMemoryPath, found%hwva) - call mem_set_value(this%angldegxinp, 'ANGLDEGX', idmMemoryPath, & + call mem_set_value(this%jainp, 'JA', this%input_mempath, found%ja) + call mem_set_value(this%ihcinp, 'IHC', this%input_mempath, found%ihc) + call mem_set_value(this%cl12inp, 'CL12', this%input_mempath, found%cl12) + call mem_set_value(this%hwvainp, 'HWVA', this%input_mempath, found%hwva) + call mem_set_value(this%angldegxinp, 'ANGLDEGX', this%input_mempath, & found%angldegx) ! ! -- set pointer to iac input array - call mem_setptr(iac, 'IAC', idmMemoryPath) + call mem_setptr(iac, 'IAC', this%input_mempath) ! ! -- Convert iac to ia if (associated(iac)) call iac_to_ia(iac, this%iainp) @@ -897,25 +879,18 @@ subroutine source_vertices(this) ! ------------------------------------------------------------------------------ ! -- modules use MemoryManagerModule, only: mem_setptr - use MemoryHelperModule, only: create_mem_path - use MemoryManagerExtModule, only: mem_set_value - use SimVariablesModule, only: idm_context ! -- dummy class(GwfDisuType) :: this ! -- local integer(I4B) :: i - character(len=LENMEMPATH) :: idmMemoryPath real(DP), dimension(:), contiguous, pointer :: vert_x => null() real(DP), dimension(:), contiguous, pointer :: vert_y => null() ! -- formats ! ------------------------------------------------------------------------------ - ! - ! -- set memory path - idmMemoryPath = create_mem_path(this%name_model, 'DISU', idm_context) ! ! -- set pointers to memory manager input arrays - call mem_setptr(vert_x, 'XV', idmMemoryPath) - call mem_setptr(vert_y, 'YV', idmMemoryPath) + call mem_setptr(vert_x, 'XV', this%input_mempath) + call mem_setptr(vert_y, 'YV', this%input_mempath) ! ! -- set vertices 2d array if (associated(vert_x) .and. associated(vert_y)) then @@ -986,14 +961,10 @@ subroutine source_cell2d(this) ! SPECIFICATIONS: ! ------------------------------------------------------------------------------ ! -- modules - use MemoryHelperModule, only: create_mem_path use MemoryManagerModule, only: mem_setptr - use MemoryManagerExtModule, only: mem_set_value - use SimVariablesModule, only: idm_context ! -- dummy class(GwfDisuType) :: this ! -- locals - character(len=LENMEMPATH) :: idmMemoryPath integer(I4B), dimension(:), contiguous, pointer :: icell2d => null() integer(I4B), dimension(:), contiguous, pointer :: ncvert => null() integer(I4B), dimension(:), contiguous, pointer :: icvert => null() @@ -1002,14 +973,11 @@ subroutine source_cell2d(this) integer(I4B) :: i ! -- formats ! ------------------------------------------------------------------------------ - ! - ! -- set memory path - idmMemoryPath = create_mem_path(this%name_model, 'DISU', idm_context) ! ! -- set pointers to input path ncvert and icvert - call mem_setptr(icell2d, 'ICELL2D', idmMemoryPath) - call mem_setptr(ncvert, 'NCVERT', idmMemoryPath) - call mem_setptr(icvert, 'ICVERT', idmMemoryPath) + call mem_setptr(icell2d, 'ICELL2D', this%input_mempath) + call mem_setptr(ncvert, 'NCVERT', this%input_mempath) + call mem_setptr(icvert, 'ICVERT', this%input_mempath) ! ! -- if (associated(icell2d) .and. associated(ncvert) & @@ -1020,8 +988,8 @@ subroutine source_cell2d(this) end if ! ! -- set pointers to cell center arrays - call mem_setptr(cell_x, 'XC', idmMemoryPath) - call mem_setptr(cell_y, 'YC', idmMemoryPath) + call mem_setptr(cell_x, 'XC', this%input_mempath) + call mem_setptr(cell_y, 'YC', this%input_mempath) ! ! -- set cell centers if (associated(cell_x) .and. associated(cell_y)) then @@ -1071,8 +1039,7 @@ subroutine write_grb(this, icelltype) if (this%nvert > 0) ntxt = ntxt + 5 ! ! -- Open the file - inquire (unit=this%inunit, name=fname) - fname = trim(fname)//'.grb' + fname = trim(this%input_fname)//'.grb' iunit = getunit() write (this%iout, fmtgrdsave) iunit, trim(adjustl(fname)) call openfile(iunit, this%iout, trim(adjustl(fname)), 'DATA(BINARY)', & diff --git a/src/Model/GroundWaterFlow/gwf3disv8.f90 b/src/Model/GroundWaterFlow/gwf3disv8.f90 index dcfc20a08e1..28c5b24951d 100644 --- a/src/Model/GroundWaterFlow/gwf3disv8.f90 +++ b/src/Model/GroundWaterFlow/gwf3disv8.f90 @@ -7,10 +7,10 @@ module GwfDisvModule use BaseDisModule, only: DisBaseType use InputOutputModule, only: get_node, URWORD, ulasav, ulaprufw, ubdsv1, & ubdsv06 - use SimModule, only: count_errors, store_error, store_error_unit + use SimModule, only: count_errors, store_error, store_error_unit, & + store_error_filename use DisvGeom, only: DisvGeomType use MemoryManagerModule, only: mem_allocate - use MemoryHelperModule, only: create_mem_path use TdisModule, only: kstp, kper, pertim, totim, delt implicit none @@ -72,36 +72,43 @@ module GwfDisvModule contains - subroutine disv_cr(dis, name_model, inunit, iout) + subroutine disv_cr(dis, name_model, input_mempath, inunit, iout) ! ****************************************************************************** ! disv_cr -- Create a new discretization by vertices object ! ****************************************************************************** ! ! SPECIFICATIONS: ! ------------------------------------------------------------------------------ - use IdmMf6FileLoaderModule, only: input_load - use ConstantsModule, only: LENPACKAGETYPE + use KindModule, only: LGP + use MemoryManagerExtModule, only: mem_set_value class(DisBaseType), pointer :: dis character(len=*), intent(in) :: name_model + character(len=*), intent(in) :: input_mempath integer(I4B), intent(in) :: inunit integer(I4B), intent(in) :: iout type(GwfDisvType), pointer :: disnew + logical(LGP) :: found_fname character(len=*), parameter :: fmtheader = & "(1X, /1X, 'DISV -- VERTEX GRID DISCRETIZATION PACKAGE,', & - &' VERSION 1 : 12/23/2015 - INPUT READ FROM UNIT ', I0, //)" + &' VERSION 1 : 12/23/2015 - INPUT READ FROM MEMPATH: ', A, //)" ! ------------------------------------------------------------------------------ allocate (disnew) dis => disnew call disnew%allocate_scalars(name_model) + dis%input_mempath = input_mempath dis%inunit = inunit dis%iout = iout ! - ! -- if reading from file + ! -- set name of input file + call mem_set_value(dis%input_fname, 'INPUT_FNAME', dis%input_mempath, & + found_fname) + ! + ! -- If disv enabled if (inunit > 0) then ! ! -- Identify package if (iout > 0) then - write (iout, fmtheader) inunit + write (iout, fmtheader) dis%input_mempath end if ! ! -- load disv @@ -119,6 +126,7 @@ subroutine disv_load(this) ! ! SPECIFICATIONS: ! ------------------------------------------------------------------------------ + ! -- modules ! -- dummy class(GwfDisvType) :: this ! -- locals @@ -209,29 +217,23 @@ subroutine source_options(this) ! SPECIFICATIONS: ! ------------------------------------------------------------------------------ ! -- modules - use KindModule, only: LGP use MemoryManagerExtModule, only: mem_set_value - use SimVariablesModule, only: idm_context use GwfDisvInputModule, only: GwfDisvParamFoundType ! -- dummy class(GwfDisvType) :: this ! -- locals - character(len=LENMEMPATH) :: idmMemoryPath character(len=LENVARNAME), dimension(3) :: lenunits = & &[character(len=LENVARNAME) :: 'FEET', 'METERS', 'CENTIMETERS'] type(GwfDisvParamFoundType) :: found ! ------------------------------------------------------------------------------ - ! - ! -- set memory path - idmMemoryPath = create_mem_path(this%name_model, 'DISV', idm_context) ! ! -- update defaults with idm sourced values - call mem_set_value(this%lenuni, 'LENGTH_UNITS', idmMemoryPath, lenunits, & - found%length_units) - call mem_set_value(this%nogrb, 'NOGRB', idmMemoryPath, found%nogrb) - call mem_set_value(this%xorigin, 'XORIGIN', idmMemoryPath, found%xorigin) - call mem_set_value(this%yorigin, 'YORIGIN', idmMemoryPath, found%yorigin) - call mem_set_value(this%angrot, 'ANGROT', idmMemoryPath, found%angrot) + call mem_set_value(this%lenuni, 'LENGTH_UNITS', this%input_mempath, & + lenunits, found%length_units) + call mem_set_value(this%nogrb, 'NOGRB', this%input_mempath, found%nogrb) + call mem_set_value(this%xorigin, 'XORIGIN', this%input_mempath, found%xorigin) + call mem_set_value(this%yorigin, 'YORIGIN', this%input_mempath, found%yorigin) + call mem_set_value(this%angrot, 'ANGROT', this%input_mempath, found%angrot) ! ! -- log values to list file if (this%iout > 0) then @@ -286,25 +288,19 @@ subroutine source_dimensions(this) ! ! SPECIFICATIONS: ! ------------------------------------------------------------------------------ - use KindModule, only: LGP use MemoryManagerExtModule, only: mem_set_value - use SimVariablesModule, only: idm_context use GwfDisvInputModule, only: GwfDisvParamFoundType ! -- dummy class(GwfDisvType) :: this ! -- locals - character(len=LENMEMPATH) :: idmMemoryPath integer(I4B) :: j, k type(GwfDisvParamFoundType) :: found ! ------------------------------------------------------------------------------ - ! - ! -- set memory path - idmMemoryPath = create_mem_path(this%name_model, 'DISV', idm_context) ! ! -- update defaults with idm sourced values - call mem_set_value(this%nlay, 'NLAY', idmMemoryPath, found%nlay) - call mem_set_value(this%ncpl, 'NCPL', idmMemoryPath, found%ncpl) - call mem_set_value(this%nvert, 'NVERT', idmMemoryPath, found%nvert) + call mem_set_value(this%nlay, 'NLAY', this%input_mempath, found%nlay) + call mem_set_value(this%ncpl, 'NCPL', this%input_mempath, found%ncpl) + call mem_set_value(this%nvert, 'NVERT', this%input_mempath, found%nvert) ! ! -- log simulation values if (this%iout > 0) then @@ -315,17 +311,17 @@ subroutine source_dimensions(this) if (this%nlay < 1) then call store_error( & 'NLAY was not specified or was specified incorrectly.') - call store_error_unit(this%inunit) + call store_error_filename(this%input_fname) end if if (this%ncpl < 1) then call store_error( & 'NCPL was not specified or was specified incorrectly.') - call store_error_unit(this%inunit) + call store_error_filename(this%input_fname) end if if (this%nvert < 1) then call store_error( & 'NVERT was not specified or was specified incorrectly.') - call store_error_unit(this%inunit) + call store_error_filename(this%input_fname) end if ! ! -- Calculate nodesuser @@ -387,23 +383,18 @@ subroutine source_griddata(this) ! ------------------------------------------------------------------------------ ! -- modules use MemoryManagerExtModule, only: mem_set_value - use SimVariablesModule, only: idm_context use GwfDisvInputModule, only: GwfDisvParamFoundType ! -- dummy class(GwfDisvType) :: this ! -- locals - character(len=LENMEMPATH) :: idmMemoryPath type(GwfDisvParamFoundType) :: found ! -- formats ! ------------------------------------------------------------------------------ - ! - ! -- set memory path - idmMemoryPath = create_mem_path(this%name_model, 'DISV', idm_context) ! ! -- update defaults with idm sourced values - call mem_set_value(this%top1d, 'TOP', idmMemoryPath, found%top) - call mem_set_value(this%bot2d, 'BOTM', idmMemoryPath, found%botm) - call mem_set_value(this%idomain, 'IDOMAIN', idmMemoryPath, found%idomain) + call mem_set_value(this%top1d, 'TOP', this%input_mempath, found%top) + call mem_set_value(this%bot2d, 'BOTM', this%input_mempath, found%botm) + call mem_set_value(this%idomain, 'IDOMAIN', this%input_mempath, found%idomain) ! ! -- log simulation values if (this%iout > 0) then @@ -478,7 +469,7 @@ subroutine grid_finalize(this) call store_error('Model does not have any active nodes. & &Ensure IDOMAIN array has some values greater & &than zero.') - call store_error_unit(this%inunit) + call store_error_filename(this%input_fname) end if ! ! -- Check cell thicknesses @@ -500,7 +491,7 @@ subroutine grid_finalize(this) end do end do if (count_errors() > 0) then - call store_error_unit(this%inunit) + call store_error_filename(this%input_fname) end if ! ! -- Write message if reduced grid @@ -585,24 +576,18 @@ subroutine source_vertices(this) ! ------------------------------------------------------------------------------ ! -- modules use MemoryManagerModule, only: mem_setptr - use MemoryManagerExtModule, only: mem_set_value - use SimVariablesModule, only: idm_context ! -- dummy class(GwfDisvType) :: this ! -- local integer(I4B) :: i - character(len=LENMEMPATH) :: idmMemoryPath real(DP), dimension(:), contiguous, pointer :: vert_x => null() real(DP), dimension(:), contiguous, pointer :: vert_y => null() ! -- formats ! ------------------------------------------------------------------------------ - ! - ! -- set memory path - idmMemoryPath = create_mem_path(this%name_model, 'DISV', idm_context) ! ! -- set pointers to memory manager input arrays - call mem_setptr(vert_x, 'XV', idmMemoryPath) - call mem_setptr(vert_y, 'YV', idmMemoryPath) + call mem_setptr(vert_x, 'XV', this%input_mempath) + call mem_setptr(vert_y, 'YV', this%input_mempath) ! ! -- set vertices 2d array if (associated(vert_x) .and. associated(vert_y)) then @@ -674,12 +659,9 @@ subroutine source_cell2d(this) ! ------------------------------------------------------------------------------ ! -- modules use MemoryManagerModule, only: mem_setptr - use MemoryManagerExtModule, only: mem_set_value - use SimVariablesModule, only: idm_context ! -- dummy class(GwfDisvType) :: this ! -- locals - character(len=LENMEMPATH) :: idmMemoryPath integer(I4B), dimension(:), contiguous, pointer :: icell2d => null() integer(I4B), dimension(:), contiguous, pointer :: ncvert => null() integer(I4B), dimension(:), contiguous, pointer :: icvert => null() @@ -688,14 +670,11 @@ subroutine source_cell2d(this) integer(I4B) :: i ! -- formats ! ------------------------------------------------------------------------------ - ! - ! -- set memory path - idmMemoryPath = create_mem_path(this%name_model, 'DISV', idm_context) ! ! -- set pointers to input path ncvert and icvert - call mem_setptr(icell2d, 'ICELL2D', idmMemoryPath) - call mem_setptr(ncvert, 'NCVERT', idmMemoryPath) - call mem_setptr(icvert, 'ICVERT', idmMemoryPath) + call mem_setptr(icell2d, 'ICELL2D', this%input_mempath) + call mem_setptr(ncvert, 'NCVERT', this%input_mempath) + call mem_setptr(icvert, 'ICVERT', this%input_mempath) ! ! -- if (associated(icell2d) .and. associated(ncvert) & @@ -707,8 +686,8 @@ subroutine source_cell2d(this) end if ! ! -- copy cell center idm sourced values to local arrays - call mem_setptr(cell_x, 'XC', idmMemoryPath) - call mem_setptr(cell_y, 'YC', idmMemoryPath) + call mem_setptr(cell_x, 'XC', this%input_mempath) + call mem_setptr(cell_y, 'YC', this%input_mempath) ! ! -- set cell centers if (associated(cell_x) .and. associated(cell_y)) then @@ -790,7 +769,7 @@ subroutine connect(this) &by a valid polygon.' call store_error(errmsg) end if - call store_error_unit(this%inunit) + call store_error_filename(this%input_fname) end if ! ! -- create and fill the connections object @@ -839,8 +818,7 @@ subroutine write_grb(this, icelltype) ntxt = 20 ! ! -- Open the file - inquire (unit=this%inunit, name=fname) - fname = trim(fname)//'.grb' + fname = trim(this%input_fname)//'.grb' iunit = getunit() write (this%iout, fmtgrdsave) iunit, trim(adjustl(fname)) call openfile(iunit, this%iout, trim(adjustl(fname)), 'DATA(BINARY)', & diff --git a/src/Model/GroundWaterFlow/gwf3idm.f90 b/src/Model/GroundWaterFlow/gwf3idm.f90 new file mode 100644 index 00000000000..4645a821583 --- /dev/null +++ b/src/Model/GroundWaterFlow/gwf3idm.f90 @@ -0,0 +1,241 @@ +! ** Do Not Modify! MODFLOW 6 system generated file. ** +module GwfNamInputModule + use InputDefinitionModule, only: InputParamDefinitionType, & + InputBlockDefinitionType + private + public gwf_nam_param_definitions + public gwf_nam_aggregate_definitions + public gwf_nam_block_definitions + public GwfNamParamFoundType + public gwf_nam_multi_package + + type GwfNamParamFoundType + logical :: list = .false. + logical :: print_input = .false. + logical :: print_flows = .false. + logical :: save_flows = .false. + logical :: newtonoptions = .false. + logical :: newton = .false. + logical :: under_relaxation = .false. + logical :: ftype = .false. + logical :: fname = .false. + logical :: pname = .false. + end type GwfNamParamFoundType + + logical :: gwf_nam_multi_package = .false. + + type(InputParamDefinitionType), parameter :: & + gwfnam_list = InputParamDefinitionType & + ( & + 'GWF', & ! component + 'NAM', & ! subcomponent + 'OPTIONS', & ! block + 'LIST', & ! tag name + 'LIST', & ! fortran variable + 'STRING', & ! type + '', & ! shape + .false., & ! required + .false., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + gwfnam_print_input = InputParamDefinitionType & + ( & + 'GWF', & ! component + 'NAM', & ! subcomponent + 'OPTIONS', & ! block + 'PRINT_INPUT', & ! tag name + 'PRINT_INPUT', & ! fortran variable + 'KEYWORD', & ! type + '', & ! shape + .false., & ! required + .false., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + gwfnam_print_flows = InputParamDefinitionType & + ( & + 'GWF', & ! component + 'NAM', & ! subcomponent + 'OPTIONS', & ! block + 'PRINT_FLOWS', & ! tag name + 'PRINT_FLOWS', & ! fortran variable + 'KEYWORD', & ! type + '', & ! shape + .false., & ! required + .false., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + gwfnam_save_flows = InputParamDefinitionType & + ( & + 'GWF', & ! component + 'NAM', & ! subcomponent + 'OPTIONS', & ! block + 'SAVE_FLOWS', & ! tag name + 'SAVE_FLOWS', & ! fortran variable + 'KEYWORD', & ! type + '', & ! shape + .false., & ! required + .false., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + gwfnam_newtonoptions = InputParamDefinitionType & + ( & + 'GWF', & ! component + 'NAM', & ! subcomponent + 'OPTIONS', & ! block + 'NEWTONOPTIONS', & ! tag name + 'NEWTONOPTIONS', & ! fortran variable + 'RECORD NEWTON UNDER_RELAXATION', & ! type + '', & ! shape + .false., & ! required + .false., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + gwfnam_newton = InputParamDefinitionType & + ( & + 'GWF', & ! component + 'NAM', & ! subcomponent + 'OPTIONS', & ! block + 'NEWTON', & ! tag name + 'NEWTON', & ! fortran variable + 'KEYWORD', & ! type + '', & ! shape + .true., & ! required + .true., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + gwfnam_under_relaxation = InputParamDefinitionType & + ( & + 'GWF', & ! component + 'NAM', & ! subcomponent + 'OPTIONS', & ! block + 'UNDER_RELAXATION', & ! tag name + 'UNDER_RELAXATION', & ! fortran variable + 'KEYWORD', & ! type + '', & ! shape + .false., & ! required + .true., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + gwfnam_ftype = InputParamDefinitionType & + ( & + 'GWF', & ! component + 'NAM', & ! subcomponent + 'PACKAGES', & ! block + 'FTYPE', & ! tag name + 'FTYPE', & ! fortran variable + 'STRING', & ! type + '', & ! shape + .true., & ! required + .true., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + gwfnam_fname = InputParamDefinitionType & + ( & + 'GWF', & ! component + 'NAM', & ! subcomponent + 'PACKAGES', & ! block + 'FNAME', & ! tag name + 'FNAME', & ! fortran variable + 'STRING', & ! type + '', & ! shape + .true., & ! required + .true., & ! multi-record + .true., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + gwfnam_pname = InputParamDefinitionType & + ( & + 'GWF', & ! component + 'NAM', & ! subcomponent + 'PACKAGES', & ! block + 'PNAME', & ! tag name + 'PNAME', & ! fortran variable + 'STRING', & ! type + '', & ! shape + .false., & ! required + .true., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + gwf_nam_param_definitions(*) = & + [ & + gwfnam_list, & + gwfnam_print_input, & + gwfnam_print_flows, & + gwfnam_save_flows, & + gwfnam_newtonoptions, & + gwfnam_newton, & + gwfnam_under_relaxation, & + gwfnam_ftype, & + gwfnam_fname, & + gwfnam_pname & + ] + + type(InputParamDefinitionType), parameter :: & + gwfnam_packages = InputParamDefinitionType & + ( & + 'GWF', & ! component + 'NAM', & ! subcomponent + 'PACKAGES', & ! block + 'PACKAGES', & ! tag name + 'PACKAGES', & ! fortran variable + 'RECARRAY FTYPE FNAME PNAME', & ! type + '', & ! shape + .true., & ! required + .false., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + gwf_nam_aggregate_definitions(*) = & + [ & + gwfnam_packages & + ] + + type(InputBlockDefinitionType), parameter :: & + gwf_nam_block_definitions(*) = & + [ & + InputBlockDefinitionType( & + 'OPTIONS', & ! blockname + .false., & ! required + .false., & ! aggregate + .false. & ! block_variable + ), & + InputBlockDefinitionType( & + 'PACKAGES', & ! blockname + .true., & ! required + .true., & ! aggregate + .false. & ! block_variable + ) & + ] + +end module GwfNamInputModule diff --git a/src/Model/GroundWaterFlow/gwf3npf8.f90 b/src/Model/GroundWaterFlow/gwf3npf8.f90 index 3f348e19448..070d825e091 100644 --- a/src/Model/GroundWaterFlow/gwf3npf8.f90 +++ b/src/Model/GroundWaterFlow/gwf3npf8.f90 @@ -147,7 +147,7 @@ module GwfNpfModule contains - subroutine npf_cr(npfobj, name_model, inunit, iout) + subroutine npf_cr(npfobj, name_model, input_mempath, inunit, iout) ! ****************************************************************************** ! npf_cr -- Create a new NPF object. Pass a inunit value of 0 if npf data will ! initialized from memory @@ -156,17 +156,20 @@ subroutine npf_cr(npfobj, name_model, inunit, iout) ! SPECIFICATIONS: ! ------------------------------------------------------------------------------ ! -- modules - use IdmMf6FileLoaderModule, only: input_load - use ConstantsModule, only: LENPACKAGETYPE + use KindModule, only: LGP + use MemoryManagerExtModule, only: mem_set_value ! -- dummy type(GwfNpfType), pointer :: npfobj character(len=*), intent(in) :: name_model + character(len=*), intent(in) :: input_mempath integer(I4B), intent(in) :: inunit integer(I4B), intent(in) :: iout + ! -- locals + logical(LGP) :: found_fname ! -- formats character(len=*), parameter :: fmtheader = & "(1x, /1x, 'NPF -- NODE PROPERTY FLOW PACKAGE, VERSION 1, 3/30/2015', & - &' INPUT READ FROM UNIT ', i0, /)" + &' INPUT READ FROM MEMPATH: ', A, /)" ! ------------------------------------------------------------------------------ ! ! -- Create the object @@ -179,14 +182,19 @@ subroutine npf_cr(npfobj, name_model, inunit, iout) call npfobj%allocate_scalars() ! ! -- Set variables + npfobj%input_mempath = input_mempath npfobj%inunit = inunit npfobj%iout = iout ! - ! -- Check if input file is open + ! -- set name of input file + call mem_set_value(npfobj%input_fname, 'INPUT_FNAME', npfobj%input_mempath, & + found_fname) + ! + ! -- check if npf is enabled if (inunit > 0) then ! ! -- Print a message identifying the node property flow package. - write (iout, fmtheader) inunit + write (iout, fmtheader) input_mempath end if ! ! -- Return @@ -1467,55 +1475,52 @@ subroutine source_options(this) ! SPECIFICATIONS: ! ------------------------------------------------------------------------------ ! -- modules - use KindModule, only: LGP - use MemoryHelperModule, only: create_mem_path use MemoryManagerExtModule, only: mem_set_value - use SimVariablesModule, only: idm_context use GwfNpfInputModule, only: GwfNpfParamFoundType ! -- dummy class(GwfNpftype) :: this ! -- locals - character(len=LENMEMPATH) :: idmMemoryPath character(len=LENVARNAME), dimension(3) :: cellavg_method = & &[character(len=LENVARNAME) :: 'LOGARITHMIC', 'AMT-LMK', 'AMT-HMK'] type(GwfNpfParamFoundType) :: found character(len=LINELENGTH) :: tvk6_filename ! ------------------------------------------------------------------------------ - ! - ! -- set memory path - idmMemoryPath = create_mem_path(this%name_model, 'NPF', idm_context) ! ! -- update defaults with idm sourced values - call mem_set_value(this%iprflow, 'IPRFLOW', idmMemoryPath, found%iprflow) - call mem_set_value(this%ipakcb, 'IPAKCB', idmMemoryPath, found%ipakcb) - call mem_set_value(this%icellavg, 'CELLAVG', idmMemoryPath, cellavg_method, & - found%cellavg) - call mem_set_value(this%ithickstrt, 'ITHICKSTRT', idmMemoryPath, & + call mem_set_value(this%iprflow, 'IPRFLOW', this%input_mempath, found%iprflow) + call mem_set_value(this%ipakcb, 'IPAKCB', this%input_mempath, found%ipakcb) + call mem_set_value(this%icellavg, 'CELLAVG', this%input_mempath, & + cellavg_method, found%cellavg) + call mem_set_value(this%ithickstrt, 'ITHICKSTRT', this%input_mempath, & found%ithickstrt) - call mem_set_value(this%iperched, 'IPERCHED', idmMemoryPath, found%iperched) - call mem_set_value(this%ivarcv, 'IVARCV', idmMemoryPath, found%ivarcv) - call mem_set_value(this%idewatcv, 'IDEWATCV', idmMemoryPath, found%idewatcv) - call mem_set_value(this%ixt3d, 'IXT3D', idmMemoryPath, found%ixt3d) - call mem_set_value(this%ixt3drhs, 'IXT3DRHS', idmMemoryPath, found%ixt3drhs) - call mem_set_value(this%isavspdis, 'ISAVSPDIS', idmMemoryPath, & + call mem_set_value(this%iperched, 'IPERCHED', this%input_mempath, & + found%iperched) + call mem_set_value(this%ivarcv, 'IVARCV', this%input_mempath, found%ivarcv) + call mem_set_value(this%idewatcv, 'IDEWATCV', this%input_mempath, & + found%idewatcv) + call mem_set_value(this%ixt3d, 'IXT3D', this%input_mempath, found%ixt3d) + call mem_set_value(this%ixt3drhs, 'IXT3DRHS', this%input_mempath, & + found%ixt3drhs) + call mem_set_value(this%isavspdis, 'ISAVSPDIS', this%input_mempath, & found%isavspdis) - call mem_set_value(this%isavsat, 'ISAVSAT', idmMemoryPath, found%isavsat) - call mem_set_value(this%ik22overk, 'IK22OVERK', idmMemoryPath, & + call mem_set_value(this%isavsat, 'ISAVSAT', this%input_mempath, found%isavsat) + call mem_set_value(this%ik22overk, 'IK22OVERK', this%input_mempath, & found%ik22overk) - call mem_set_value(this%ik33overk, 'IK33OVERK', idmMemoryPath, & + call mem_set_value(this%ik33overk, 'IK33OVERK', this%input_mempath, & found%ik33overk) - call mem_set_value(tvk6_filename, 'TVK6_FILENAME', idmMemoryPath, & + call mem_set_value(tvk6_filename, 'TVK6_FILENAME', this%input_mempath, & found%tvk6_filename) - call mem_set_value(this%inewton, 'INEWTON', idmMemoryPath, found%inewton) - call mem_set_value(this%iusgnrhc, 'IUSGNRHC', idmMemoryPath, & + call mem_set_value(this%inewton, 'INEWTON', this%input_mempath, found%inewton) + call mem_set_value(this%iusgnrhc, 'IUSGNRHC', this%input_mempath, & found%iusgnrhc) - call mem_set_value(this%inwtupw, 'INWTUPW', idmMemoryPath, found%inwtupw) - call mem_set_value(this%satmin, 'SATMIN', idmMemoryPath, found%satmin) - call mem_set_value(this%satomega, 'SATOMEGA', idmMemoryPath, found%satomega) - call mem_set_value(this%irewet, 'IREWET', idmMemoryPath, found%irewet) - call mem_set_value(this%wetfct, 'WETFCT', idmMemoryPath, found%wetfct) - call mem_set_value(this%iwetit, 'IWETIT', idmMemoryPath, found%iwetit) - call mem_set_value(this%ihdwet, 'IHDWET', idmMemoryPath, found%ihdwet) + call mem_set_value(this%inwtupw, 'INWTUPW', this%input_mempath, found%inwtupw) + call mem_set_value(this%satmin, 'SATMIN', this%input_mempath, found%satmin) + call mem_set_value(this%satomega, 'SATOMEGA', this%input_mempath, & + found%satomega) + call mem_set_value(this%irewet, 'IREWET', this%input_mempath, found%irewet) + call mem_set_value(this%wetfct, 'WETFCT', this%input_mempath, found%wetfct) + call mem_set_value(this%iwetit, 'IWETIT', this%input_mempath, found%iwetit) + call mem_set_value(this%ihdwet, 'IHDWET', this%input_mempath, found%ihdwet) ! ! -- save flows option active if (found%ipakcb) this%ipakcb = -1 @@ -1572,7 +1577,7 @@ subroutine check_options(this) ! SPECIFICATIONS: ! ------------------------------------------------------------------------------ ! -- modules - use SimModule, only: store_error, count_errors, store_error_unit + use SimModule, only: store_error, count_errors, store_error_filename use ConstantsModule, only: LINELENGTH ! -- dummy class(GwfNpftype) :: this @@ -1669,7 +1674,7 @@ subroutine check_options(this) ! ! -- Terminate if errors if (count_errors() > 0) then - call store_error_unit(this%inunit) + call store_error_filename(this%input_fname) end if ! ! -- Return @@ -1734,39 +1739,37 @@ subroutine source_griddata(this) ! ------------------------------------------------------------------------------ ! -- modules use SimModule, only: count_errors, store_error - use MemoryHelperModule, only: create_mem_path use MemoryManagerModule, only: mem_reallocate use MemoryManagerExtModule, only: mem_set_value - use SimVariablesModule, only: idm_context use GwfNpfInputModule, only: GwfNpfParamFoundType ! -- dummy class(GwfNpftype) :: this ! -- locals - character(len=LENMEMPATH) :: idmMemoryPath character(len=LINELENGTH) :: errmsg type(GwfNpfParamFoundType) :: found logical, dimension(2) :: afound integer(I4B), dimension(:), pointer, contiguous :: map ! -- formats ! ------------------------------------------------------------------------------ - ! - ! -- set memory path - idmMemoryPath = create_mem_path(this%name_model, 'NPF', idm_context) ! ! -- set map to convert user input data into reduced data map => null() if (this%dis%nodes < this%dis%nodesuser) map => this%dis%nodeuser ! ! -- update defaults with idm sourced values - call mem_set_value(this%icelltype, 'ICELLTYPE', idmMemoryPath, map, & + call mem_set_value(this%icelltype, 'ICELLTYPE', this%input_mempath, map, & found%icelltype) - call mem_set_value(this%k11, 'K', idmMemoryPath, map, found%k) - call mem_set_value(this%k33, 'K33', idmMemoryPath, map, found%k33) - call mem_set_value(this%k22, 'K22', idmMemoryPath, map, found%k22) - call mem_set_value(this%wetdry, 'WETDRY', idmMemoryPath, map, found%wetdry) - call mem_set_value(this%angle1, 'ANGLE1', idmMemoryPath, map, found%angle1) - call mem_set_value(this%angle2, 'ANGLE2', idmMemoryPath, map, found%angle2) - call mem_set_value(this%angle3, 'ANGLE3', idmMemoryPath, map, found%angle3) + call mem_set_value(this%k11, 'K', this%input_mempath, map, found%k) + call mem_set_value(this%k33, 'K33', this%input_mempath, map, found%k33) + call mem_set_value(this%k22, 'K22', this%input_mempath, map, found%k22) + call mem_set_value(this%wetdry, 'WETDRY', this%input_mempath, map, & + found%wetdry) + call mem_set_value(this%angle1, 'ANGLE1', this%input_mempath, map, & + found%angle1) + call mem_set_value(this%angle2, 'ANGLE2', this%input_mempath, map, & + found%angle2) + call mem_set_value(this%angle3, 'ANGLE3', this%input_mempath, map, & + found%angle3) ! ! -- ensure ICELLTYPE was found if (.not. found%icelltype) then @@ -1802,10 +1805,10 @@ subroutine source_griddata(this) ! ! -- handle not found side effects if (.not. found%k33) then - call mem_set_value(this%k33, 'K', idmMemoryPath, map, afound(1)) + call mem_set_value(this%k33, 'K', this%input_mempath, map, afound(1)) end if if (.not. found%k22) then - call mem_set_value(this%k22, 'K', idmMemoryPath, map, afound(2)) + call mem_set_value(this%k22, 'K', this%input_mempath, map, afound(2)) end if if (.not. found%wetdry) call mem_reallocate(this%wetdry, 1, 'WETDRY', & trim(this%memoryPath)) @@ -1833,7 +1836,7 @@ subroutine prepcheck(this) ! SPECIFICATIONS: ! ------------------------------------------------------------------------------ use ConstantsModule, only: LINELENGTH, DPIO180 - use SimModule, only: store_error, count_errors, store_error_unit + use SimModule, only: store_error, count_errors, store_error_filename ! -- dummy class(GwfNpfType) :: this ! -- local @@ -1978,7 +1981,7 @@ subroutine prepcheck(this) ! ! -- terminate if data errors if (count_errors() > 0) then - call store_error_unit(this%inunit) + call store_error_filename(this%input_fname) end if return @@ -1996,7 +1999,7 @@ end subroutine prepcheck !< subroutine preprocess_input(this) use ConstantsModule, only: LINELENGTH - use SimModule, only: store_error, count_errors, store_error_unit + use SimModule, only: store_error, count_errors, store_error_filename class(GwfNpfType) :: this !< the instance of the NPF package ! local integer(I4B) :: n, m, ii, nn @@ -2132,7 +2135,7 @@ subroutine preprocess_input(this) end if end do if (count_errors() > 0) then - call store_error_unit(this%inunit) + call store_error_filename(this%input_fname) end if ! ! -- Calculate condsat, but only if xt3d is not active. If xt3d is @@ -2321,7 +2324,7 @@ subroutine sgwf_npf_wetdry(this, kiter, hnew) ! ------------------------------------------------------------------------------ ! -- modules use TdisModule, only: kstp, kper - use SimModule, only: store_error, store_error_unit + use SimModule, only: store_error, store_error_filename use ConstantsModule, only: LINELENGTH ! -- dummy class(GwfNpfType) :: this @@ -2381,7 +2384,7 @@ subroutine sgwf_npf_wetdry(this, kiter, hnew) call store_error(errmsg) write (errmsg, fmttopbot) ttop, bbot call store_error(errmsg) - call store_error_unit(this%inunit) + call store_error_filename(this%input_fname) end if ! ! -- Calculate saturated thickness @@ -2403,7 +2406,7 @@ subroutine sgwf_npf_wetdry(this, kiter, hnew) call this%dis%noder_to_string(n, nodestr) write (errmsg, fmtni) trim(adjustl(nodestr)), kiter, kstp, kper call store_error(errmsg) - call store_error_unit(this%inunit) + call store_error_filename(this%input_fname) end if this%ibound(n) = 0 end if diff --git a/src/Model/GroundWaterTransport/gwt1.f90 b/src/Model/GroundWaterTransport/gwt1.f90 index 4197794e6ff..8ae69ce8c81 100644 --- a/src/Model/GroundWaterTransport/gwt1.f90 +++ b/src/Model/GroundWaterTransport/gwt1.f90 @@ -8,8 +8,7 @@ module GwtModule use KindModule, only: DP, I4B - use InputOutputModule, only: ParseLine, upcase - use ConstantsModule, only: LENFTYPE, DZERO, LENPAKLOC + use ConstantsModule, only: LENFTYPE, LENMEMPATH, DZERO, LENPAKLOC use VersionModule, only: write_listfile_header use NumericalModelModule, only: NumericalModelType use TransportModelModule, only: TransportModelType @@ -51,7 +50,7 @@ module GwtModule integer(I4B), pointer :: inmvt => null() ! unit number MVT integer(I4B), pointer :: inmst => null() ! unit number MST integer(I4B), pointer :: inadv => null() ! unit number ADV - integer(I4B), pointer :: indsp => null() ! unit number DSP + integer(I4B), pointer :: indsp => null() ! DSP enabled flag integer(I4B), pointer :: inssm => null() ! unit number SSM integer(I4B), pointer :: inoc => null() ! unit number OC integer(I4B), pointer :: inobs => null() ! unit number OBS @@ -82,19 +81,12 @@ module GwtModule procedure, private :: gwt_ot_dv procedure, private :: gwt_ot_bdsummary procedure, private :: gwt_ot_obs - procedure :: load_input_context => gwt_load_input_context + procedure, private :: create_packages + procedure, private :: create_bndpkgs + procedure, private :: create_lstfile + procedure, private :: log_namfile_options end type GwtModelType - ! -- Module variables constant for simulation - integer(I4B), parameter :: NIUNIT = 100 - character(len=LENFTYPE), dimension(NIUNIT) :: cunit - data cunit/'DIS6 ', 'DISV6', 'DISU6', 'IC6 ', 'MST6 ', & ! 5 - &'ADV6 ', 'DSP6 ', 'SSM6 ', ' ', 'CNC6 ', & ! 10 - &'OC6 ', 'OBS6 ', 'FMI6 ', 'SRC6 ', 'IST6 ', & ! 15 - &'LKT6 ', 'SFT6 ', 'MWT6 ', 'UZT6 ', 'MVT6 ', & ! 20 - &'API6 ', ' ', ' ', ' ', ' ', & ! 25 - &75*' '/ - contains subroutine gwt_cr(filename, id, modelname) @@ -107,45 +99,29 @@ subroutine gwt_cr(filename, id, modelname) ! -- modules use ListsModule, only: basemodellist use BaseModelModule, only: AddBaseModelToList - use SimModule, only: store_error, count_errors - use ConstantsModule, only: LINELENGTH, LENPACKAGENAME - use CompilerVersion - use MemoryManagerModule, only: mem_allocate + use ConstantsModule, only: LINELENGTH use MemoryHelperModule, only: create_mem_path - use GwfDisModule, only: dis_cr - use GwfDisvModule, only: disv_cr - use GwfDisuModule, only: disu_cr - use GwtIcModule, only: ic_cr - use GwtFmiModule, only: fmi_cr - use GwtMstModule, only: mst_cr - use GwtAdvModule, only: adv_cr - use GwtDspModule, only: dsp_cr - use GwtSsmModule, only: ssm_cr - use GwtMvtModule, only: mvt_cr - use GwtOcModule, only: oc_cr - use GwtObsModule, only: gwt_obs_cr + use MemoryManagerExtModule, only: mem_set_value + use SimVariablesModule, only: idm_context + use GwfNamInputModule, only: GwfNamParamFoundType use BudgetModule, only: budget_cr - use NameFileModule, only: NameFileType ! -- dummy character(len=*), intent(in) :: filename integer(I4B), intent(in) :: id character(len=*), intent(in) :: modelname ! -- local - integer(I4B) :: indis, indis6, indisu6, indisv6 - integer(I4B) :: ipakid, i, j, iu, ipaknum - character(len=LINELENGTH) :: errmsg - character(len=LENPACKAGENAME) :: pakname - type(NameFileType) :: namefile_obj type(GwtModelType), pointer :: this class(BaseModelType), pointer :: model - integer(I4B) :: nwords - character(len=LINELENGTH), allocatable, dimension(:) :: words + character(len=LENMEMPATH) :: input_mempath + character(len=LINELENGTH) :: lst_fname + type(GwfNamParamFoundType) :: found + ! -- format ! ------------------------------------------------------------------------------ ! ! -- Allocate a new GWT Model (this) and add it to basemodellist allocate (this) ! - ! -- Set this before any allocs in the memory manager can be done + ! -- Set memory path before allocation in memory manager can be done this%memoryPath = create_mem_path(modelname) ! call this%allocate_scalars(modelname) @@ -158,116 +134,35 @@ subroutine gwt_cr(filename, id, modelname) this%macronym = 'GWT' this%id = id ! - ! -- Open namefile and set iout - call namefile_obj%init(this%filename, 0) - call namefile_obj%add_cunit(niunit, cunit) - call namefile_obj%openlistfile(this%iout) + ! -- set input model namfile memory path + input_mempath = create_mem_path(modelname, 'NAM', idm_context) ! - ! -- Write header to model list file - call write_listfile_header(this%iout, 'GROUNDWATER TRANSPORT MODEL (GWT)') + ! -- copy option params from input context + call mem_set_value(lst_fname, 'LIST', input_mempath, found%list) + call mem_set_value(this%iprpak, 'PRINT_INPUT', input_mempath, & + found%print_input) + call mem_set_value(this%iprflow, 'PRINT_FLOWS', input_mempath, & + found%print_flows) + call mem_set_value(this%ipakcb, 'SAVE_FLOWS', input_mempath, found%save_flows) ! - ! -- Open files - call namefile_obj%openfiles(this%iout) + ! -- create the list file + call this%create_lstfile(lst_fname, filename, found%list) ! - ! -- - if (size(namefile_obj%opts) > 0) then - write (this%iout, '(1x,a)') 'NAMEFILE OPTIONS:' + ! -- activate save_flows if found + if (found%save_flows) then + this%ipakcb = -1 end if ! - ! -- parse options in the gwt name file - do i = 1, size(namefile_obj%opts) - call ParseLine(namefile_obj%opts(i), nwords, words) - call upcase(words(1)) - select case (words(1)) - case ('PRINT_INPUT') - this%iprpak = 1 - write (this%iout, '(4x,a)') 'STRESS PACKAGE INPUT WILL BE PRINTED '// & - 'FOR ALL MODEL STRESS PACKAGES' - case ('PRINT_FLOWS') - this%iprflow = 1 - write (this%iout, '(4x,a)') 'PACKAGE FLOWS WILL BE PRINTED '// & - 'FOR ALL MODEL PACKAGES' - case ('SAVE_FLOWS') - this%ipakcb = -1 - write (this%iout, '(4x,a)') & - 'FLOWS WILL BE SAVED TO BUDGET FILE SPECIFIED IN OUTPUT CONTROL' - case default - write (errmsg, '(4x,a,a,a,a)') & - 'UNKNOWN GWT NAMEFILE (', & - trim(adjustl(this%filename)), ') OPTION: ', & - trim(adjustl(namefile_obj%opts(i))) - call store_error(errmsg, terminate=.TRUE.) - end select - end do - ! - ! -- Assign unit numbers to attached modules, and remove - ! -- from unitnumber (by specifying 1 for iremove) - ! - indis = 0 - indis6 = 0 - indisu6 = 0 - indisv6 = 0 - call namefile_obj%get_unitnumber('DIS6', indis6, 1) - if (indis6 > 0) indis = indis6 - if (indis <= 0) call namefile_obj%get_unitnumber('DISU6', indisu6, 1) - if (indisu6 > 0) indis = indisu6 - if (indis <= 0) call namefile_obj%get_unitnumber('DISV6', indisv6, 1) - if (indisv6 > 0) indis = indisv6 - call namefile_obj%get_unitnumber('IC6', this%inic, 1) - call namefile_obj%get_unitnumber('FMI6', this%infmi, 1) - call namefile_obj%get_unitnumber('MVT6', this%inmvt, 1) - call namefile_obj%get_unitnumber('MST6', this%inmst, 1) - call namefile_obj%get_unitnumber('ADV6', this%inadv, 1) - call namefile_obj%get_unitnumber('DSP6', this%indsp, 1) - call namefile_obj%get_unitnumber('SSM6', this%inssm, 1) - call namefile_obj%get_unitnumber('OC6', this%inoc, 1) - call namefile_obj%get_unitnumber('OBS6', this%inobs, 1) - ! - ! -- Check to make sure that required ftype's have been specified - call this%ftype_check(namefile_obj, indis) - ! - ! -- Create discretization object - if (indis6 > 0) then - call this%load_input_context('DIS6', this%name, 'DIS', indis, this%iout) - call dis_cr(this%dis, this%name, indis, this%iout) - elseif (indisu6 > 0) then - call this%load_input_context('DISU6', this%name, 'DISU', indis, this%iout) - call disu_cr(this%dis, this%name, indis, this%iout) - elseif (indisv6 > 0) then - call this%load_input_context('DISV6', this%name, 'DISV', indis, this%iout) - call disv_cr(this%dis, this%name, indis, this%iout) + ! -- log set options + if (this%iout > 0) then + call this%log_namfile_options(found) end if ! ! -- Create utility objects call budget_cr(this%budget, this%name) ! - ! -- Load input context for currently supported packages - call this%load_input_context('DSP6', this%name, 'DSP', this%indsp, this%iout) - ! - ! -- Create packages that are tied directly to model - call ic_cr(this%ic, this%name, this%inic, this%iout, this%dis) - call fmi_cr(this%fmi, this%name, this%infmi, this%iout) - call mst_cr(this%mst, this%name, this%inmst, this%iout, this%fmi) - call adv_cr(this%adv, this%name, this%inadv, this%iout, this%fmi) - call dsp_cr(this%dsp, this%name, this%indsp, this%iout, this%fmi) - call ssm_cr(this%ssm, this%name, this%inssm, this%iout, this%fmi) - call mvt_cr(this%mvt, this%name, this%inmvt, this%iout, this%fmi) - call oc_cr(this%oc, this%name, this%inoc, this%iout) - call gwt_obs_cr(this%obs, this%inobs) - ! - ! -- Create stress packages - ipakid = 1 - do i = 1, niunit - ipaknum = 1 - do j = 1, namefile_obj%get_nval_for_row(i) - iu = namefile_obj%get_unitnumber_rowcol(i, j) - call namefile_obj%get_pakname(i, j, pakname) - call this%package_create(cunit(i), ipakid, ipaknum, pakname, iu, & - this%iout) - ipaknum = ipaknum + 1 - ipakid = ipakid + 1 - end do - end do + ! -- create model packages + call this%create_packages() ! ! -- return return @@ -283,6 +178,7 @@ subroutine gwt_df(this) ! SPECIFICATIONS: ! ------------------------------------------------------------------------------ ! -- modules + use ModelPackageInputsModule, only: NIUNIT_GWT ! -- dummy class(GwtModelType) :: this ! -- local @@ -298,7 +194,7 @@ subroutine gwt_df(this) if (this%indsp > 0) call this%dsp%dsp_df(this%dis) if (this%inssm > 0) call this%ssm%ssm_df() call this%oc%oc_df() - call this%budget%budget_df(niunit, 'MASS', 'M') + call this%budget%budget_df(NIUNIT_GWT, 'MASS', 'M') ! ! -- Assign or point model members to dis members this%neq = this%dis%nodes @@ -970,12 +866,18 @@ subroutine gwt_da(this) ! ------------------------------------------------------------------------------ ! -- modules use MemoryManagerModule, only: mem_deallocate + use MemoryManagerExtModule, only: memorylist_remove + use SimVariablesModule, only: idm_context ! -- dummy class(GwtModelType) :: this ! -- local integer(I4B) :: ip class(BndType), pointer :: packobj ! ------------------------------------------------------------------------------ + ! + ! -- Deallocate idm memory + call memorylist_remove(this%name, 'NAM', idm_context) + call memorylist_remove(component=this%name, context=idm_context) ! ! -- Internal flow packages deallocate call this%dis%dis_da() @@ -1207,7 +1109,7 @@ subroutine package_create(this, filtyp, ipakid, ipaknum, pakname, inunit, & return end subroutine package_create - subroutine ftype_check(this, namefile_obj, indis) + subroutine ftype_check(this, indis) ! ****************************************************************************** ! ftype_check -- Check to make sure required input files have been specified ! ****************************************************************************** @@ -1216,18 +1118,12 @@ subroutine ftype_check(this, namefile_obj, indis) ! ------------------------------------------------------------------------------ ! -- modules use ConstantsModule, only: LINELENGTH - use SimModule, only: store_error, count_errors - use NameFileModule, only: NameFileType + use SimModule, only: store_error, count_errors, store_error_filename ! -- dummy class(GwtModelType) :: this - type(NameFileType), intent(in) :: namefile_obj integer(I4B), intent(in) :: indis ! -- local character(len=LINELENGTH) :: errmsg - integer(I4B) :: i, iu - character(len=LENFTYPE), dimension(10) :: nodupftype = & - &(/'DIS6 ', 'DISU6', 'DISV6', 'IC6 ', 'MST6 ', 'ADV6 ', 'DSP6 ', & - &'SSM6 ', 'OC6 ', 'OBS6 '/) ! ------------------------------------------------------------------------------ ! ! -- Check for IC6, DIS(u), and MST. Stop if not present. @@ -1246,28 +1142,11 @@ subroutine ftype_check(this, namefile_obj, indis) &PACKAGE NOT SPECIFIED.' call store_error(errmsg) end if + ! if (count_errors() > 0) then write (errmsg, '(1x,a)') 'ERROR. REQUIRED PACKAGE(S) NOT SPECIFIED.' call store_error(errmsg) - end if - ! - ! -- Check to make sure that some GWT packages are not specified more - ! than once - do i = 1, size(nodupftype) - call namefile_obj%get_unitnumber(trim(nodupftype(i)), iu, 0) - if (iu > 0) then - write (errmsg, '(1x, a, a, a)') & - 'DUPLICATE ENTRIES FOR FTYPE ', trim(nodupftype(i)), & - ' NOT ALLOWED FOR GWT MODEL.' - call store_error(errmsg) - end if - end do - ! - ! -- Stop if errors - if (count_errors() > 0) then - write (errmsg, '(a, a)') 'ERROR OCCURRED WHILE READING FILE: ', & - trim(namefile_obj%filename) - call store_error(errmsg, terminate=.TRUE.) + call store_error_filename(this%filename) end if ! ! -- return @@ -1288,38 +1167,263 @@ function CastAsGwtModel(model) result(gwtmodel) end function CastAsGwtModel - !> @brief Load input context for supported package + !> @brief Source package info and begin to process !< - subroutine gwt_load_input_context(this, filtyp, modelname, pkgname, inunit, & - iout, ipaknum) + subroutine create_bndpkgs(this, bndpkgs, pkgtypes, pkgnames, & + mempaths, inunits) ! -- modules - use IdmMf6FileLoaderModule, only: input_load + use ConstantsModule, only: LINELENGTH, LENPACKAGENAME + use CharacterStringModule, only: CharacterStringType ! -- dummy class(GwtModelType) :: this - character(len=*), intent(in) :: filtyp - character(len=*), intent(in) :: modelname - character(len=*), intent(in) :: pkgname - integer(I4B), intent(in) :: inunit - integer(I4B), intent(in) :: iout - integer(I4B), optional, intent(in) :: ipaknum + integer(I4B), dimension(:), allocatable, intent(inout) :: bndpkgs + type(CharacterStringType), dimension(:), contiguous, & + pointer, intent(inout) :: pkgtypes + type(CharacterStringType), dimension(:), contiguous, & + pointer, intent(inout) :: pkgnames + type(CharacterStringType), dimension(:), contiguous, & + pointer, intent(inout) :: mempaths + integer(I4B), dimension(:), contiguous, & + pointer, intent(inout) :: inunits ! -- local -! ------------------------------------------------------------------------------ + integer(I4B) :: ipakid, ipaknum + character(len=LENFTYPE) :: pkgtype, bndptype + character(len=LENPACKAGENAME) :: pkgname + character(len=LENMEMPATH) :: mempath + integer(I4B), pointer :: inunit + integer(I4B) :: n + + if (allocated(bndpkgs)) then + ! + ! -- create stress packages + ipakid = 1 + bndptype = '' + do n = 1, size(bndpkgs) + ! + pkgtype = pkgtypes(bndpkgs(n)) + pkgname = pkgnames(bndpkgs(n)) + mempath = mempaths(bndpkgs(n)) + inunit => inunits(bndpkgs(n)) + ! + if (bndptype /= pkgtype) then + ipaknum = 1 + bndptype = pkgtype + end if + ! + call this%package_create(pkgtype, ipakid, ipaknum, pkgname, inunit, & + this%iout) + ipakid = ipakid + 1 + ipaknum = ipaknum + 1 + end do + ! + ! -- cleanup + deallocate (bndpkgs) + end if + ! + ! -- return + return + end subroutine create_bndpkgs + + !> @brief Source package info and begin to process + !< + subroutine create_packages(this) + ! -- modules + use ConstantsModule, only: LINELENGTH, LENPACKAGENAME + use CharacterStringModule, only: CharacterStringType + use ArrayHandlersModule, only: expandarray + use MemoryManagerModule, only: mem_setptr + use MemoryHelperModule, only: create_mem_path + use SimVariablesModule, only: idm_context + use GwfDisModule, only: dis_cr + use GwfDisvModule, only: disv_cr + use GwfDisuModule, only: disu_cr + use GwtIcModule, only: ic_cr + use GwtFmiModule, only: fmi_cr + use GwtMstModule, only: mst_cr + use GwtAdvModule, only: adv_cr + use GwtDspModule, only: dsp_cr + use GwtSsmModule, only: ssm_cr + use GwtMvtModule, only: mvt_cr + use GwtOcModule, only: oc_cr + use GwtObsModule, only: gwt_obs_cr + ! -- dummy + class(GwtModelType) :: this + ! -- local + type(CharacterStringType), dimension(:), contiguous, & + pointer :: pkgtypes => null() + type(CharacterStringType), dimension(:), contiguous, & + pointer :: pkgnames => null() + type(CharacterStringType), dimension(:), contiguous, & + pointer :: mempaths => null() + integer(I4B), dimension(:), contiguous, & + pointer :: inunits => null() + character(len=LENMEMPATH) :: model_mempath + character(len=LENFTYPE) :: pkgtype + character(len=LENPACKAGENAME) :: pkgname + character(len=LENMEMPATH) :: mempath + integer(I4B), pointer :: inunit + integer(I4B), dimension(:), allocatable :: bndpkgs + integer(I4B) :: n + integer(I4B) :: indis = 0 ! DIS enabled flag + character(len=LENMEMPATH) :: mempathdsp = '' + ! + ! -- set input memory paths, input/model and input/model/namfile + model_mempath = create_mem_path(component=this%name, context=idm_context) + ! + ! -- set pointers to model path package info + call mem_setptr(pkgtypes, 'PKGTYPES', model_mempath) + call mem_setptr(pkgnames, 'PKGNAMES', model_mempath) + call mem_setptr(mempaths, 'MEMPATHS', model_mempath) + call mem_setptr(inunits, 'INUNITS', model_mempath) + ! + do n = 1, size(pkgtypes) + ! + ! attributes for this input package + pkgtype = pkgtypes(n) + pkgname = pkgnames(n) + mempath = mempaths(n) + inunit => inunits(n) + ! + ! -- create dis package as it is a prerequisite for other packages + select case (pkgtype) + case ('DIS6') + indis = 1 + call dis_cr(this%dis, this%name, mempath, indis, this%iout) + case ('DISV6') + indis = 1 + call disv_cr(this%dis, this%name, mempath, indis, this%iout) + case ('DISU6') + indis = 1 + call disu_cr(this%dis, this%name, mempath, indis, this%iout) + case ('IC6') + this%inic = inunit + case ('FMI6') + this%infmi = inunit + case ('MVT6') + this%inmvt = inunit + case ('MST6') + this%inmst = inunit + case ('ADV6') + this%inadv = inunit + case ('DSP6') + this%indsp = 1 + mempathdsp = mempath + case ('SSM6') + this%inssm = inunit + case ('OC6') + this%inoc = inunit + case ('OBS6') + this%inobs = inunit + case ('CNC6', 'SRC6', 'LKT6', 'SFT6', & + 'MWT6', 'UZT6', 'IST6', 'API6') + call expandarray(bndpkgs) + bndpkgs(size(bndpkgs)) = n + case default + ! TODO + end select + end do ! - ! -- only load if there is a file to read - if (inunit <= 0) return + ! -- Create packages that are tied directly to model + call ic_cr(this%ic, this%name, this%inic, this%iout, this%dis) + call fmi_cr(this%fmi, this%name, this%infmi, this%iout) + call mst_cr(this%mst, this%name, this%inmst, this%iout, this%fmi) + call adv_cr(this%adv, this%name, this%inadv, this%iout, this%fmi) + call dsp_cr(this%dsp, this%name, mempathdsp, this%indsp, this%iout, this%fmi) + call ssm_cr(this%ssm, this%name, this%inssm, this%iout, this%fmi) + call mvt_cr(this%mvt, this%name, this%inmvt, this%iout, this%fmi) + call oc_cr(this%oc, this%name, this%inoc, this%iout) + call gwt_obs_cr(this%obs, this%inobs) ! - ! -- Load model package input to input context - select case (filtyp) - case ('DSP6') - call input_load('DSP6', 'GWT', 'DSP', modelname, pkgname, inunit, iout) - case default - call this%NumericalModelType%load_input_context(filtyp, modelname, & - pkgname, inunit, iout, & - ipaknum) - end select + ! -- Check to make sure that required ftype's have been specified + call this%ftype_check(indis) + ! + call this%create_bndpkgs(bndpkgs, pkgtypes, pkgnames, mempaths, inunits) + + end subroutine create_packages + + subroutine create_lstfile(this, lst_fname, model_fname, defined) + ! -- modules + use KindModule, only: LGP + use InputOutputModule, only: openfile, getunit + ! -- dummy + class(GwtModelType) :: this + character(len=*), intent(inout) :: lst_fname + character(len=*), intent(in) :: model_fname + logical(LGP), intent(in) :: defined + ! -- local + integer(I4B) :: i, istart, istop + ! + ! -- set list file name if not provided + if (.not. defined) then + ! + ! -- initialize + lst_fname = ' ' + istart = 0 + istop = len_trim(model_fname) + ! + ! -- identify '.' character position from back of string + do i = istop, 1, -1 + if (model_fname(i:i) == '.') then + istart = i + exit + end if + end do + ! + ! -- if not found start from string end + if (istart == 0) istart = istop + 1 + ! + ! -- set list file name + lst_fname = model_fname(1:istart) + istop = istart + 3 + lst_fname(istart:istop) = '.lst' + end if + ! + ! -- create the list file + this%iout = getunit() + call openfile(this%iout, 0, lst_fname, 'LIST', filstat_opt='REPLACE') + ! + ! -- write list file header + call write_listfile_header(this%iout, 'GROUNDWATER TRANSPORT MODEL (GWT)') ! ! -- return return - end subroutine gwt_load_input_context + end subroutine create_lstfile + + !> @brief Write model namfile options to list file + !< + subroutine log_namfile_options(this, found) + use GwfNamInputModule, only: GwfNamParamFoundType + class(GwtModelType) :: this + type(GwfNamParamFoundType), intent(in) :: found + + write (this%iout, '(1x,a)') 'NAMEFILE OPTIONS:' + + if (found%newton) then + write (this%iout, '(4x,a)') & + 'NEWTON-RAPHSON method enabled for the model.' + if (found%under_relaxation) then + write (this%iout, '(4x,a,a)') & + 'NEWTON-RAPHSON UNDER-RELAXATION based on the bottom ', & + 'elevation of the model will be applied to the model.' + end if + end if + + if (found%print_input) then + write (this%iout, '(4x,a)') 'STRESS PACKAGE INPUT WILL BE PRINTED '// & + 'FOR ALL MODEL STRESS PACKAGES' + end if + + if (found%print_flows) then + write (this%iout, '(4x,a)') 'PACKAGE FLOWS WILL BE PRINTED '// & + 'FOR ALL MODEL PACKAGES' + end if + + if (found%save_flows) then + write (this%iout, '(4x,a)') & + 'FLOWS WILL BE SAVED TO BUDGET FILE SPECIFIED IN OUTPUT CONTROL' + end if + + write (this%iout, '(1x,a)') 'END NAMEFILE OPTIONS:' + end subroutine log_namfile_options end module GwtModule diff --git a/src/Model/GroundWaterTransport/gwt1dis1idm.f90 b/src/Model/GroundWaterTransport/gwt1dis1idm.f90 new file mode 100644 index 00000000000..b80694384aa --- /dev/null +++ b/src/Model/GroundWaterTransport/gwt1dis1idm.f90 @@ -0,0 +1,285 @@ +! ** Do Not Modify! MODFLOW 6 system generated file. ** +module GwtDisInputModule + use InputDefinitionModule, only: InputParamDefinitionType, & + InputBlockDefinitionType + private + public gwt_dis_param_definitions + public gwt_dis_aggregate_definitions + public gwt_dis_block_definitions + public GwtDisParamFoundType + public gwt_dis_multi_package + + type GwtDisParamFoundType + logical :: length_units = .false. + logical :: nogrb = .false. + logical :: xorigin = .false. + logical :: yorigin = .false. + logical :: angrot = .false. + logical :: nlay = .false. + logical :: nrow = .false. + logical :: ncol = .false. + logical :: delr = .false. + logical :: delc = .false. + logical :: top = .false. + logical :: botm = .false. + logical :: idomain = .false. + end type GwtDisParamFoundType + + logical :: gwt_dis_multi_package = .false. + + type(InputParamDefinitionType), parameter :: & + gwtdis_length_units = InputParamDefinitionType & + ( & + 'GWT', & ! component + 'DIS', & ! subcomponent + 'OPTIONS', & ! block + 'LENGTH_UNITS', & ! tag name + 'LENGTH_UNITS', & ! fortran variable + 'STRING', & ! type + '', & ! shape + .false., & ! required + .false., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + gwtdis_nogrb = InputParamDefinitionType & + ( & + 'GWT', & ! component + 'DIS', & ! subcomponent + 'OPTIONS', & ! block + 'NOGRB', & ! tag name + 'NOGRB', & ! fortran variable + 'KEYWORD', & ! type + '', & ! shape + .false., & ! required + .false., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + gwtdis_xorigin = InputParamDefinitionType & + ( & + 'GWT', & ! component + 'DIS', & ! subcomponent + 'OPTIONS', & ! block + 'XORIGIN', & ! tag name + 'XORIGIN', & ! fortran variable + 'DOUBLE', & ! type + '', & ! shape + .false., & ! required + .false., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + gwtdis_yorigin = InputParamDefinitionType & + ( & + 'GWT', & ! component + 'DIS', & ! subcomponent + 'OPTIONS', & ! block + 'YORIGIN', & ! tag name + 'YORIGIN', & ! fortran variable + 'DOUBLE', & ! type + '', & ! shape + .false., & ! required + .false., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + gwtdis_angrot = InputParamDefinitionType & + ( & + 'GWT', & ! component + 'DIS', & ! subcomponent + 'OPTIONS', & ! block + 'ANGROT', & ! tag name + 'ANGROT', & ! fortran variable + 'DOUBLE', & ! type + '', & ! shape + .false., & ! required + .false., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + gwtdis_nlay = InputParamDefinitionType & + ( & + 'GWT', & ! component + 'DIS', & ! subcomponent + 'DIMENSIONS', & ! block + 'NLAY', & ! tag name + 'NLAY', & ! fortran variable + 'INTEGER', & ! type + '', & ! shape + .true., & ! required + .false., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + gwtdis_nrow = InputParamDefinitionType & + ( & + 'GWT', & ! component + 'DIS', & ! subcomponent + 'DIMENSIONS', & ! block + 'NROW', & ! tag name + 'NROW', & ! fortran variable + 'INTEGER', & ! type + '', & ! shape + .true., & ! required + .false., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + gwtdis_ncol = InputParamDefinitionType & + ( & + 'GWT', & ! component + 'DIS', & ! subcomponent + 'DIMENSIONS', & ! block + 'NCOL', & ! tag name + 'NCOL', & ! fortran variable + 'INTEGER', & ! type + '', & ! shape + .true., & ! required + .false., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + gwtdis_delr = InputParamDefinitionType & + ( & + 'GWT', & ! component + 'DIS', & ! subcomponent + 'GRIDDATA', & ! block + 'DELR', & ! tag name + 'DELR', & ! fortran variable + 'DOUBLE1D', & ! type + 'NCOL', & ! shape + .true., & ! required + .false., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + gwtdis_delc = InputParamDefinitionType & + ( & + 'GWT', & ! component + 'DIS', & ! subcomponent + 'GRIDDATA', & ! block + 'DELC', & ! tag name + 'DELC', & ! fortran variable + 'DOUBLE1D', & ! type + 'NROW', & ! shape + .true., & ! required + .false., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + gwtdis_top = InputParamDefinitionType & + ( & + 'GWT', & ! component + 'DIS', & ! subcomponent + 'GRIDDATA', & ! block + 'TOP', & ! tag name + 'TOP', & ! fortran variable + 'DOUBLE2D', & ! type + 'NCOL NROW', & ! shape + .true., & ! required + .false., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + gwtdis_botm = InputParamDefinitionType & + ( & + 'GWT', & ! component + 'DIS', & ! subcomponent + 'GRIDDATA', & ! block + 'BOTM', & ! tag name + 'BOTM', & ! fortran variable + 'DOUBLE3D', & ! type + 'NCOL NROW NLAY', & ! shape + .true., & ! required + .false., & ! multi-record + .false., & ! preserve case + .true. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + gwtdis_idomain = InputParamDefinitionType & + ( & + 'GWT', & ! component + 'DIS', & ! subcomponent + 'GRIDDATA', & ! block + 'IDOMAIN', & ! tag name + 'IDOMAIN', & ! fortran variable + 'INTEGER3D', & ! type + 'NCOL NROW NLAY', & ! shape + .false., & ! required + .false., & ! multi-record + .false., & ! preserve case + .true. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + gwt_dis_param_definitions(*) = & + [ & + gwtdis_length_units, & + gwtdis_nogrb, & + gwtdis_xorigin, & + gwtdis_yorigin, & + gwtdis_angrot, & + gwtdis_nlay, & + gwtdis_nrow, & + gwtdis_ncol, & + gwtdis_delr, & + gwtdis_delc, & + gwtdis_top, & + gwtdis_botm, & + gwtdis_idomain & + ] + + type(InputParamDefinitionType), parameter :: & + gwt_dis_aggregate_definitions(*) = & + [ & + InputParamDefinitionType :: & + ] + + type(InputBlockDefinitionType), parameter :: & + gwt_dis_block_definitions(*) = & + [ & + InputBlockDefinitionType( & + 'OPTIONS', & ! blockname + .false., & ! required + .false., & ! aggregate + .false. & ! block_variable + ), & + InputBlockDefinitionType( & + 'DIMENSIONS', & ! blockname + .true., & ! required + .false., & ! aggregate + .false. & ! block_variable + ), & + InputBlockDefinitionType( & + 'GRIDDATA', & ! blockname + .true., & ! required + .false., & ! aggregate + .false. & ! block_variable + ) & + ] + +end module GwtDisInputModule diff --git a/src/Model/GroundWaterTransport/gwt1disu1idm.f90 b/src/Model/GroundWaterTransport/gwt1disu1idm.f90 new file mode 100644 index 00000000000..1a194976e94 --- /dev/null +++ b/src/Model/GroundWaterTransport/gwt1disu1idm.f90 @@ -0,0 +1,588 @@ +! ** Do Not Modify! MODFLOW 6 system generated file. ** +module GwtDisuInputModule + use InputDefinitionModule, only: InputParamDefinitionType, & + InputBlockDefinitionType + private + public gwt_disu_param_definitions + public gwt_disu_aggregate_definitions + public gwt_disu_block_definitions + public GwtDisuParamFoundType + public gwt_disu_multi_package + + type GwtDisuParamFoundType + logical :: length_units = .false. + logical :: nogrb = .false. + logical :: xorigin = .false. + logical :: yorigin = .false. + logical :: angrot = .false. + logical :: voffsettol = .false. + logical :: nodes = .false. + logical :: nja = .false. + logical :: nvert = .false. + logical :: top = .false. + logical :: bot = .false. + logical :: area = .false. + logical :: idomain = .false. + logical :: iac = .false. + logical :: ja = .false. + logical :: ihc = .false. + logical :: cl12 = .false. + logical :: hwva = .false. + logical :: angldegx = .false. + logical :: iv = .false. + logical :: xv = .false. + logical :: yv = .false. + logical :: icell2d = .false. + logical :: xc = .false. + logical :: yc = .false. + logical :: ncvert = .false. + logical :: icvert = .false. + end type GwtDisuParamFoundType + + logical :: gwt_disu_multi_package = .false. + + type(InputParamDefinitionType), parameter :: & + gwtdisu_length_units = InputParamDefinitionType & + ( & + 'GWT', & ! component + 'DISU', & ! subcomponent + 'OPTIONS', & ! block + 'LENGTH_UNITS', & ! tag name + 'LENGTH_UNITS', & ! fortran variable + 'STRING', & ! type + '', & ! shape + .false., & ! required + .false., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + gwtdisu_nogrb = InputParamDefinitionType & + ( & + 'GWT', & ! component + 'DISU', & ! subcomponent + 'OPTIONS', & ! block + 'NOGRB', & ! tag name + 'NOGRB', & ! fortran variable + 'KEYWORD', & ! type + '', & ! shape + .false., & ! required + .false., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + gwtdisu_xorigin = InputParamDefinitionType & + ( & + 'GWT', & ! component + 'DISU', & ! subcomponent + 'OPTIONS', & ! block + 'XORIGIN', & ! tag name + 'XORIGIN', & ! fortran variable + 'DOUBLE', & ! type + '', & ! shape + .false., & ! required + .false., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + gwtdisu_yorigin = InputParamDefinitionType & + ( & + 'GWT', & ! component + 'DISU', & ! subcomponent + 'OPTIONS', & ! block + 'YORIGIN', & ! tag name + 'YORIGIN', & ! fortran variable + 'DOUBLE', & ! type + '', & ! shape + .false., & ! required + .false., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + gwtdisu_angrot = InputParamDefinitionType & + ( & + 'GWT', & ! component + 'DISU', & ! subcomponent + 'OPTIONS', & ! block + 'ANGROT', & ! tag name + 'ANGROT', & ! fortran variable + 'DOUBLE', & ! type + '', & ! shape + .false., & ! required + .false., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + gwtdisu_voffsettol = InputParamDefinitionType & + ( & + 'GWT', & ! component + 'DISU', & ! subcomponent + 'OPTIONS', & ! block + 'VERTICAL_OFFSET_TOLERANCE', & ! tag name + 'VOFFSETTOL', & ! fortran variable + 'DOUBLE', & ! type + '', & ! shape + .false., & ! required + .false., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + gwtdisu_nodes = InputParamDefinitionType & + ( & + 'GWT', & ! component + 'DISU', & ! subcomponent + 'DIMENSIONS', & ! block + 'NODES', & ! tag name + 'NODES', & ! fortran variable + 'INTEGER', & ! type + '', & ! shape + .true., & ! required + .false., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + gwtdisu_nja = InputParamDefinitionType & + ( & + 'GWT', & ! component + 'DISU', & ! subcomponent + 'DIMENSIONS', & ! block + 'NJA', & ! tag name + 'NJA', & ! fortran variable + 'INTEGER', & ! type + '', & ! shape + .true., & ! required + .false., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + gwtdisu_nvert = InputParamDefinitionType & + ( & + 'GWT', & ! component + 'DISU', & ! subcomponent + 'DIMENSIONS', & ! block + 'NVERT', & ! tag name + 'NVERT', & ! fortran variable + 'INTEGER', & ! type + '', & ! shape + .false., & ! required + .false., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + gwtdisu_top = InputParamDefinitionType & + ( & + 'GWT', & ! component + 'DISU', & ! subcomponent + 'GRIDDATA', & ! block + 'TOP', & ! tag name + 'TOP', & ! fortran variable + 'DOUBLE1D', & ! type + 'NODES', & ! shape + .true., & ! required + .false., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + gwtdisu_bot = InputParamDefinitionType & + ( & + 'GWT', & ! component + 'DISU', & ! subcomponent + 'GRIDDATA', & ! block + 'BOT', & ! tag name + 'BOT', & ! fortran variable + 'DOUBLE1D', & ! type + 'NODES', & ! shape + .true., & ! required + .false., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + gwtdisu_area = InputParamDefinitionType & + ( & + 'GWT', & ! component + 'DISU', & ! subcomponent + 'GRIDDATA', & ! block + 'AREA', & ! tag name + 'AREA', & ! fortran variable + 'DOUBLE1D', & ! type + 'NODES', & ! shape + .true., & ! required + .false., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + gwtdisu_idomain = InputParamDefinitionType & + ( & + 'GWT', & ! component + 'DISU', & ! subcomponent + 'GRIDDATA', & ! block + 'IDOMAIN', & ! tag name + 'IDOMAIN', & ! fortran variable + 'INTEGER1D', & ! type + 'NODES', & ! shape + .false., & ! required + .false., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + gwtdisu_iac = InputParamDefinitionType & + ( & + 'GWT', & ! component + 'DISU', & ! subcomponent + 'CONNECTIONDATA', & ! block + 'IAC', & ! tag name + 'IAC', & ! fortran variable + 'INTEGER1D', & ! type + 'NODES', & ! shape + .true., & ! required + .false., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + gwtdisu_ja = InputParamDefinitionType & + ( & + 'GWT', & ! component + 'DISU', & ! subcomponent + 'CONNECTIONDATA', & ! block + 'JA', & ! tag name + 'JA', & ! fortran variable + 'INTEGER1D', & ! type + 'NJA', & ! shape + .true., & ! required + .false., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + gwtdisu_ihc = InputParamDefinitionType & + ( & + 'GWT', & ! component + 'DISU', & ! subcomponent + 'CONNECTIONDATA', & ! block + 'IHC', & ! tag name + 'IHC', & ! fortran variable + 'INTEGER1D', & ! type + 'NJA', & ! shape + .true., & ! required + .false., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + gwtdisu_cl12 = InputParamDefinitionType & + ( & + 'GWT', & ! component + 'DISU', & ! subcomponent + 'CONNECTIONDATA', & ! block + 'CL12', & ! tag name + 'CL12', & ! fortran variable + 'DOUBLE1D', & ! type + 'NJA', & ! shape + .true., & ! required + .false., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + gwtdisu_hwva = InputParamDefinitionType & + ( & + 'GWT', & ! component + 'DISU', & ! subcomponent + 'CONNECTIONDATA', & ! block + 'HWVA', & ! tag name + 'HWVA', & ! fortran variable + 'DOUBLE1D', & ! type + 'NJA', & ! shape + .true., & ! required + .false., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + gwtdisu_angldegx = InputParamDefinitionType & + ( & + 'GWT', & ! component + 'DISU', & ! subcomponent + 'CONNECTIONDATA', & ! block + 'ANGLDEGX', & ! tag name + 'ANGLDEGX', & ! fortran variable + 'DOUBLE1D', & ! type + 'NJA', & ! shape + .false., & ! required + .false., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + gwtdisu_iv = InputParamDefinitionType & + ( & + 'GWT', & ! component + 'DISU', & ! subcomponent + 'VERTICES', & ! block + 'IV', & ! tag name + 'IV', & ! fortran variable + 'INTEGER', & ! type + '', & ! shape + .true., & ! required + .true., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + gwtdisu_xv = InputParamDefinitionType & + ( & + 'GWT', & ! component + 'DISU', & ! subcomponent + 'VERTICES', & ! block + 'XV', & ! tag name + 'XV', & ! fortran variable + 'DOUBLE', & ! type + '', & ! shape + .true., & ! required + .true., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + gwtdisu_yv = InputParamDefinitionType & + ( & + 'GWT', & ! component + 'DISU', & ! subcomponent + 'VERTICES', & ! block + 'YV', & ! tag name + 'YV', & ! fortran variable + 'DOUBLE', & ! type + '', & ! shape + .true., & ! required + .true., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + gwtdisu_icell2d = InputParamDefinitionType & + ( & + 'GWT', & ! component + 'DISU', & ! subcomponent + 'CELL2D', & ! block + 'ICELL2D', & ! tag name + 'ICELL2D', & ! fortran variable + 'INTEGER', & ! type + '', & ! shape + .true., & ! required + .true., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + gwtdisu_xc = InputParamDefinitionType & + ( & + 'GWT', & ! component + 'DISU', & ! subcomponent + 'CELL2D', & ! block + 'XC', & ! tag name + 'XC', & ! fortran variable + 'DOUBLE', & ! type + '', & ! shape + .true., & ! required + .true., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + gwtdisu_yc = InputParamDefinitionType & + ( & + 'GWT', & ! component + 'DISU', & ! subcomponent + 'CELL2D', & ! block + 'YC', & ! tag name + 'YC', & ! fortran variable + 'DOUBLE', & ! type + '', & ! shape + .true., & ! required + .true., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + gwtdisu_ncvert = InputParamDefinitionType & + ( & + 'GWT', & ! component + 'DISU', & ! subcomponent + 'CELL2D', & ! block + 'NCVERT', & ! tag name + 'NCVERT', & ! fortran variable + 'INTEGER', & ! type + '', & ! shape + .true., & ! required + .true., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + gwtdisu_icvert = InputParamDefinitionType & + ( & + 'GWT', & ! component + 'DISU', & ! subcomponent + 'CELL2D', & ! block + 'ICVERT', & ! tag name + 'ICVERT', & ! fortran variable + 'INTEGER1D', & ! type + 'NCVERT', & ! shape + .true., & ! required + .true., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + gwt_disu_param_definitions(*) = & + [ & + gwtdisu_length_units, & + gwtdisu_nogrb, & + gwtdisu_xorigin, & + gwtdisu_yorigin, & + gwtdisu_angrot, & + gwtdisu_voffsettol, & + gwtdisu_nodes, & + gwtdisu_nja, & + gwtdisu_nvert, & + gwtdisu_top, & + gwtdisu_bot, & + gwtdisu_area, & + gwtdisu_idomain, & + gwtdisu_iac, & + gwtdisu_ja, & + gwtdisu_ihc, & + gwtdisu_cl12, & + gwtdisu_hwva, & + gwtdisu_angldegx, & + gwtdisu_iv, & + gwtdisu_xv, & + gwtdisu_yv, & + gwtdisu_icell2d, & + gwtdisu_xc, & + gwtdisu_yc, & + gwtdisu_ncvert, & + gwtdisu_icvert & + ] + + type(InputParamDefinitionType), parameter :: & + gwtdisu_vertices = InputParamDefinitionType & + ( & + 'GWT', & ! component + 'DISU', & ! subcomponent + 'VERTICES', & ! block + 'VERTICES', & ! tag name + 'VERTICES', & ! fortran variable + 'RECARRAY IV XV YV', & ! type + 'NVERT', & ! shape + .true., & ! required + .false., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + gwtdisu_cell2d = InputParamDefinitionType & + ( & + 'GWT', & ! component + 'DISU', & ! subcomponent + 'CELL2D', & ! block + 'CELL2D', & ! tag name + 'CELL2D', & ! fortran variable + 'RECARRAY ICELL2D XC YC NCVERT ICVERT', & ! type + 'NODES', & ! shape + .true., & ! required + .false., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + gwt_disu_aggregate_definitions(*) = & + [ & + gwtdisu_vertices, & + gwtdisu_cell2d & + ] + + type(InputBlockDefinitionType), parameter :: & + gwt_disu_block_definitions(*) = & + [ & + InputBlockDefinitionType( & + 'OPTIONS', & ! blockname + .false., & ! required + .false., & ! aggregate + .false. & ! block_variable + ), & + InputBlockDefinitionType( & + 'DIMENSIONS', & ! blockname + .true., & ! required + .false., & ! aggregate + .false. & ! block_variable + ), & + InputBlockDefinitionType( & + 'GRIDDATA', & ! blockname + .true., & ! required + .false., & ! aggregate + .false. & ! block_variable + ), & + InputBlockDefinitionType( & + 'CONNECTIONDATA', & ! blockname + .true., & ! required + .false., & ! aggregate + .false. & ! block_variable + ), & + InputBlockDefinitionType( & + 'VERTICES', & ! blockname + .true., & ! required + .true., & ! aggregate + .false. & ! block_variable + ), & + InputBlockDefinitionType( & + 'CELL2D', & ! blockname + .true., & ! required + .true., & ! aggregate + .false. & ! block_variable + ) & + ] + +end module GwtDisuInputModule diff --git a/src/Model/GroundWaterTransport/gwt1disv1idm.f90 b/src/Model/GroundWaterTransport/gwt1disv1idm.f90 new file mode 100644 index 00000000000..4ed35aca51a --- /dev/null +++ b/src/Model/GroundWaterTransport/gwt1disv1idm.f90 @@ -0,0 +1,438 @@ +! ** Do Not Modify! MODFLOW 6 system generated file. ** +module GwtDisvInputModule + use InputDefinitionModule, only: InputParamDefinitionType, & + InputBlockDefinitionType + private + public gwt_disv_param_definitions + public gwt_disv_aggregate_definitions + public gwt_disv_block_definitions + public GwtDisvParamFoundType + public gwt_disv_multi_package + + type GwtDisvParamFoundType + logical :: length_units = .false. + logical :: nogrb = .false. + logical :: xorigin = .false. + logical :: yorigin = .false. + logical :: angrot = .false. + logical :: nlay = .false. + logical :: ncpl = .false. + logical :: nvert = .false. + logical :: top = .false. + logical :: botm = .false. + logical :: idomain = .false. + logical :: iv = .false. + logical :: xv = .false. + logical :: yv = .false. + logical :: icell2d = .false. + logical :: xc = .false. + logical :: yc = .false. + logical :: ncvert = .false. + logical :: icvert = .false. + end type GwtDisvParamFoundType + + logical :: gwt_disv_multi_package = .false. + + type(InputParamDefinitionType), parameter :: & + gwtdisv_length_units = InputParamDefinitionType & + ( & + 'GWT', & ! component + 'DISV', & ! subcomponent + 'OPTIONS', & ! block + 'LENGTH_UNITS', & ! tag name + 'LENGTH_UNITS', & ! fortran variable + 'STRING', & ! type + '', & ! shape + .false., & ! required + .false., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + gwtdisv_nogrb = InputParamDefinitionType & + ( & + 'GWT', & ! component + 'DISV', & ! subcomponent + 'OPTIONS', & ! block + 'NOGRB', & ! tag name + 'NOGRB', & ! fortran variable + 'KEYWORD', & ! type + '', & ! shape + .false., & ! required + .false., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + gwtdisv_xorigin = InputParamDefinitionType & + ( & + 'GWT', & ! component + 'DISV', & ! subcomponent + 'OPTIONS', & ! block + 'XORIGIN', & ! tag name + 'XORIGIN', & ! fortran variable + 'DOUBLE', & ! type + '', & ! shape + .false., & ! required + .false., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + gwtdisv_yorigin = InputParamDefinitionType & + ( & + 'GWT', & ! component + 'DISV', & ! subcomponent + 'OPTIONS', & ! block + 'YORIGIN', & ! tag name + 'YORIGIN', & ! fortran variable + 'DOUBLE', & ! type + '', & ! shape + .false., & ! required + .false., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + gwtdisv_angrot = InputParamDefinitionType & + ( & + 'GWT', & ! component + 'DISV', & ! subcomponent + 'OPTIONS', & ! block + 'ANGROT', & ! tag name + 'ANGROT', & ! fortran variable + 'DOUBLE', & ! type + '', & ! shape + .false., & ! required + .false., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + gwtdisv_nlay = InputParamDefinitionType & + ( & + 'GWT', & ! component + 'DISV', & ! subcomponent + 'DIMENSIONS', & ! block + 'NLAY', & ! tag name + 'NLAY', & ! fortran variable + 'INTEGER', & ! type + '', & ! shape + .true., & ! required + .false., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + gwtdisv_ncpl = InputParamDefinitionType & + ( & + 'GWT', & ! component + 'DISV', & ! subcomponent + 'DIMENSIONS', & ! block + 'NCPL', & ! tag name + 'NCPL', & ! fortran variable + 'INTEGER', & ! type + '', & ! shape + .true., & ! required + .false., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + gwtdisv_nvert = InputParamDefinitionType & + ( & + 'GWT', & ! component + 'DISV', & ! subcomponent + 'DIMENSIONS', & ! block + 'NVERT', & ! tag name + 'NVERT', & ! fortran variable + 'INTEGER', & ! type + '', & ! shape + .true., & ! required + .false., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + gwtdisv_top = InputParamDefinitionType & + ( & + 'GWT', & ! component + 'DISV', & ! subcomponent + 'GRIDDATA', & ! block + 'TOP', & ! tag name + 'TOP', & ! fortran variable + 'DOUBLE1D', & ! type + 'NCPL', & ! shape + .true., & ! required + .false., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + gwtdisv_botm = InputParamDefinitionType & + ( & + 'GWT', & ! component + 'DISV', & ! subcomponent + 'GRIDDATA', & ! block + 'BOTM', & ! tag name + 'BOTM', & ! fortran variable + 'DOUBLE2D', & ! type + 'NCPL NLAY', & ! shape + .true., & ! required + .false., & ! multi-record + .false., & ! preserve case + .true. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + gwtdisv_idomain = InputParamDefinitionType & + ( & + 'GWT', & ! component + 'DISV', & ! subcomponent + 'GRIDDATA', & ! block + 'IDOMAIN', & ! tag name + 'IDOMAIN', & ! fortran variable + 'INTEGER2D', & ! type + 'NCPL NLAY', & ! shape + .false., & ! required + .false., & ! multi-record + .false., & ! preserve case + .true. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + gwtdisv_iv = InputParamDefinitionType & + ( & + 'GWT', & ! component + 'DISV', & ! subcomponent + 'VERTICES', & ! block + 'IV', & ! tag name + 'IV', & ! fortran variable + 'INTEGER', & ! type + '', & ! shape + .true., & ! required + .true., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + gwtdisv_xv = InputParamDefinitionType & + ( & + 'GWT', & ! component + 'DISV', & ! subcomponent + 'VERTICES', & ! block + 'XV', & ! tag name + 'XV', & ! fortran variable + 'DOUBLE', & ! type + '', & ! shape + .true., & ! required + .true., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + gwtdisv_yv = InputParamDefinitionType & + ( & + 'GWT', & ! component + 'DISV', & ! subcomponent + 'VERTICES', & ! block + 'YV', & ! tag name + 'YV', & ! fortran variable + 'DOUBLE', & ! type + '', & ! shape + .true., & ! required + .true., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + gwtdisv_icell2d = InputParamDefinitionType & + ( & + 'GWT', & ! component + 'DISV', & ! subcomponent + 'CELL2D', & ! block + 'ICELL2D', & ! tag name + 'ICELL2D', & ! fortran variable + 'INTEGER', & ! type + '', & ! shape + .true., & ! required + .true., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + gwtdisv_xc = InputParamDefinitionType & + ( & + 'GWT', & ! component + 'DISV', & ! subcomponent + 'CELL2D', & ! block + 'XC', & ! tag name + 'XC', & ! fortran variable + 'DOUBLE', & ! type + '', & ! shape + .true., & ! required + .true., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + gwtdisv_yc = InputParamDefinitionType & + ( & + 'GWT', & ! component + 'DISV', & ! subcomponent + 'CELL2D', & ! block + 'YC', & ! tag name + 'YC', & ! fortran variable + 'DOUBLE', & ! type + '', & ! shape + .true., & ! required + .true., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + gwtdisv_ncvert = InputParamDefinitionType & + ( & + 'GWT', & ! component + 'DISV', & ! subcomponent + 'CELL2D', & ! block + 'NCVERT', & ! tag name + 'NCVERT', & ! fortran variable + 'INTEGER', & ! type + '', & ! shape + .true., & ! required + .true., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + gwtdisv_icvert = InputParamDefinitionType & + ( & + 'GWT', & ! component + 'DISV', & ! subcomponent + 'CELL2D', & ! block + 'ICVERT', & ! tag name + 'ICVERT', & ! fortran variable + 'INTEGER1D', & ! type + 'NCVERT', & ! shape + .true., & ! required + .true., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + gwt_disv_param_definitions(*) = & + [ & + gwtdisv_length_units, & + gwtdisv_nogrb, & + gwtdisv_xorigin, & + gwtdisv_yorigin, & + gwtdisv_angrot, & + gwtdisv_nlay, & + gwtdisv_ncpl, & + gwtdisv_nvert, & + gwtdisv_top, & + gwtdisv_botm, & + gwtdisv_idomain, & + gwtdisv_iv, & + gwtdisv_xv, & + gwtdisv_yv, & + gwtdisv_icell2d, & + gwtdisv_xc, & + gwtdisv_yc, & + gwtdisv_ncvert, & + gwtdisv_icvert & + ] + + type(InputParamDefinitionType), parameter :: & + gwtdisv_vertices = InputParamDefinitionType & + ( & + 'GWT', & ! component + 'DISV', & ! subcomponent + 'VERTICES', & ! block + 'VERTICES', & ! tag name + 'VERTICES', & ! fortran variable + 'RECARRAY IV XV YV', & ! type + 'NVERT', & ! shape + .true., & ! required + .false., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + gwtdisv_cell2d = InputParamDefinitionType & + ( & + 'GWT', & ! component + 'DISV', & ! subcomponent + 'CELL2D', & ! block + 'CELL2D', & ! tag name + 'CELL2D', & ! fortran variable + 'RECARRAY ICELL2D XC YC NCVERT ICVERT', & ! type + 'NCPL', & ! shape + .true., & ! required + .false., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + gwt_disv_aggregate_definitions(*) = & + [ & + gwtdisv_vertices, & + gwtdisv_cell2d & + ] + + type(InputBlockDefinitionType), parameter :: & + gwt_disv_block_definitions(*) = & + [ & + InputBlockDefinitionType( & + 'OPTIONS', & ! blockname + .false., & ! required + .false., & ! aggregate + .false. & ! block_variable + ), & + InputBlockDefinitionType( & + 'DIMENSIONS', & ! blockname + .true., & ! required + .false., & ! aggregate + .false. & ! block_variable + ), & + InputBlockDefinitionType( & + 'GRIDDATA', & ! blockname + .true., & ! required + .false., & ! aggregate + .false. & ! block_variable + ), & + InputBlockDefinitionType( & + 'VERTICES', & ! blockname + .true., & ! required + .true., & ! aggregate + .false. & ! block_variable + ), & + InputBlockDefinitionType( & + 'CELL2D', & ! blockname + .true., & ! required + .true., & ! aggregate + .false. & ! block_variable + ) & + ] + +end module GwtDisvInputModule diff --git a/src/Model/GroundWaterTransport/gwt1dsp.f90 b/src/Model/GroundWaterTransport/gwt1dsp.f90 index d71316965a8..60c385cefbf 100644 --- a/src/Model/GroundWaterTransport/gwt1dsp.f90 +++ b/src/Model/GroundWaterTransport/gwt1dsp.f90 @@ -72,7 +72,7 @@ module GwtDspModule contains - subroutine dsp_cr(dspobj, name_model, inunit, iout, fmi) + subroutine dsp_cr(dspobj, name_model, input_mempath, inunit, iout, fmi) ! ****************************************************************************** ! dsp_cr -- Create a new DSP object ! ****************************************************************************** @@ -80,18 +80,21 @@ subroutine dsp_cr(dspobj, name_model, inunit, iout, fmi) ! SPECIFICATIONS: ! ------------------------------------------------------------------------------ ! -- modules - use IdmMf6FileLoaderModule, only: input_load - use ConstantsModule, only: LENPACKAGETYPE + use KindModule, only: LGP + use MemoryManagerExtModule, only: mem_set_value ! -- dummy type(GwtDspType), pointer :: dspobj character(len=*), intent(in) :: name_model + character(len=*), intent(in) :: input_mempath integer(I4B), intent(in) :: inunit integer(I4B), intent(in) :: iout type(GwtFmiType), intent(in), target :: fmi + ! -- locals + logical(LGP) :: found_fname ! -- formats character(len=*), parameter :: fmtdsp = & "(1x,/1x,'DSP-- DISPERSION PACKAGE, VERSION 1, 1/24/2018', & - &' INPUT READ FROM UNIT ', i0, //)" + &' INPUT READ FROM MEMPATH: ', A, //)" ! ------------------------------------------------------------------------------ ! ! -- Create the object @@ -104,16 +107,20 @@ subroutine dsp_cr(dspobj, name_model, inunit, iout, fmi) call dspobj%allocate_scalars() ! ! -- Set variables + dspobj%input_mempath = input_mempath dspobj%inunit = inunit dspobj%iout = iout dspobj%fmi => fmi ! - ! -- Check if input file is open + ! -- set name of input file + call mem_set_value(dspobj%input_fname, 'INPUT_FNAME', dspobj%input_mempath, & + found_fname) + ! if (dspobj%inunit > 0) then ! ! -- Print a message identifying the dispersion package. if (dspobj%iout > 0) then - write (dspobj%iout, fmtdsp) dspobj%inunit + write (dspobj%iout, fmtdsp) input_mempath end if end if ! @@ -562,25 +569,19 @@ subroutine source_options(this) ! ------------------------------------------------------------------------------ ! -- modules !use KindModule, only: LGP - use MemoryHelperModule, only: create_mem_path - use MemoryTypeModule, only: MemoryType use MemoryManagerExtModule, only: mem_set_value - use SimVariablesModule, only: idm_context - use ConstantsModule, only: LENMEMPATH use GwtDspInputModule, only: GwtDspParamFoundType ! -- dummy class(GwtDspType) :: this ! -- locals - character(len=LENMEMPATH) :: idmMemoryPath type(GwtDspParamFoundType) :: found ! ------------------------------------------------------------------------------ - ! - ! -- set memory path - idmMemoryPath = create_mem_path(this%name_model, 'DSP', idm_context) ! ! -- update defaults with idm sourced values - call mem_set_value(this%ixt3doff, 'XT3D_OFF', idmMemoryPath, found%xt3d_off) - call mem_set_value(this%ixt3drhs, 'XT3D_RHS', idmMemoryPath, found%xt3d_rhs) + call mem_set_value(this%ixt3doff, 'XT3D_OFF', this%input_mempath, & + found%xt3d_off) + call mem_set_value(this%ixt3drhs, 'XT3D_RHS', this%input_mempath, & + found%xt3d_rhs) ! ! -- set xt3d state flag if (found%xt3d_off) this%ixt3d = 0 @@ -641,36 +642,30 @@ subroutine source_griddata(this) ! ------------------------------------------------------------------------------ ! -- modules use SimModule, only: count_errors, store_error - use MemoryHelperModule, only: create_mem_path use MemoryManagerModule, only: mem_reallocate, mem_reassignptr use MemoryManagerExtModule, only: mem_set_value - use SimVariablesModule, only: idm_context - use ConstantsModule, only: LENMEMPATH, LINELENGTH + use ConstantsModule, only: LINELENGTH use GwtDspInputModule, only: GwtDspParamFoundType ! -- dummy class(GwtDsptype) :: this ! -- locals - character(len=LENMEMPATH) :: idmMemoryPath character(len=LINELENGTH) :: errmsg type(GwtDspParamFoundType) :: found integer(I4B), dimension(:), pointer, contiguous :: map ! -- formats ! ------------------------------------------------------------------------------ - ! - ! -- set memory path - idmMemoryPath = create_mem_path(this%name_model, 'DSP', idm_context) ! ! -- set map map => null() if (this%dis%nodes < this%dis%nodesuser) map => this%dis%nodeuser ! ! -- update defaults with idm sourced values - call mem_set_value(this%diffc, 'DIFFC', idmMemoryPath, map, found%diffc) - call mem_set_value(this%alh, 'ALH', idmMemoryPath, map, found%alh) - call mem_set_value(this%alv, 'ALV', idmMemoryPath, map, found%alv) - call mem_set_value(this%ath1, 'ATH1', idmMemoryPath, map, found%ath1) - call mem_set_value(this%ath2, 'ATH2', idmMemoryPath, map, found%ath2) - call mem_set_value(this%atv, 'ATV', idmMemoryPath, map, found%atv) + call mem_set_value(this%diffc, 'DIFFC', this%input_mempath, map, found%diffc) + call mem_set_value(this%alh, 'ALH', this%input_mempath, map, found%alh) + call mem_set_value(this%alv, 'ALV', this%input_mempath, map, found%alv) + call mem_set_value(this%ath1, 'ATH1', this%input_mempath, map, found%ath1) + call mem_set_value(this%ath2, 'ATH2', this%input_mempath, map, found%ath2) + call mem_set_value(this%atv, 'ATV', this%input_mempath, map, found%atv) ! ! -- set active flags if (found%diffc) this%idiffc = 1 diff --git a/src/Model/GroundWaterTransport/gwt1idm.f90 b/src/Model/GroundWaterTransport/gwt1idm.f90 new file mode 100644 index 00000000000..d8991dd96b2 --- /dev/null +++ b/src/Model/GroundWaterTransport/gwt1idm.f90 @@ -0,0 +1,187 @@ +! ** Do Not Modify! MODFLOW 6 system generated file. ** +module GwtNamInputModule + use InputDefinitionModule, only: InputParamDefinitionType, & + InputBlockDefinitionType + private + public gwt_nam_param_definitions + public gwt_nam_aggregate_definitions + public gwt_nam_block_definitions + public GwtNamParamFoundType + public gwt_nam_multi_package + + type GwtNamParamFoundType + logical :: list = .false. + logical :: print_input = .false. + logical :: print_flows = .false. + logical :: save_flows = .false. + logical :: ftype = .false. + logical :: fname = .false. + logical :: pname = .false. + end type GwtNamParamFoundType + + logical :: gwt_nam_multi_package = .false. + + type(InputParamDefinitionType), parameter :: & + gwtnam_list = InputParamDefinitionType & + ( & + 'GWT', & ! component + 'NAM', & ! subcomponent + 'OPTIONS', & ! block + 'LIST', & ! tag name + 'LIST', & ! fortran variable + 'STRING', & ! type + '', & ! shape + .false., & ! required + .false., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + gwtnam_print_input = InputParamDefinitionType & + ( & + 'GWT', & ! component + 'NAM', & ! subcomponent + 'OPTIONS', & ! block + 'PRINT_INPUT', & ! tag name + 'PRINT_INPUT', & ! fortran variable + 'KEYWORD', & ! type + '', & ! shape + .false., & ! required + .false., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + gwtnam_print_flows = InputParamDefinitionType & + ( & + 'GWT', & ! component + 'NAM', & ! subcomponent + 'OPTIONS', & ! block + 'PRINT_FLOWS', & ! tag name + 'PRINT_FLOWS', & ! fortran variable + 'KEYWORD', & ! type + '', & ! shape + .false., & ! required + .false., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + gwtnam_save_flows = InputParamDefinitionType & + ( & + 'GWT', & ! component + 'NAM', & ! subcomponent + 'OPTIONS', & ! block + 'SAVE_FLOWS', & ! tag name + 'SAVE_FLOWS', & ! fortran variable + 'KEYWORD', & ! type + '', & ! shape + .false., & ! required + .false., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + gwtnam_ftype = InputParamDefinitionType & + ( & + 'GWT', & ! component + 'NAM', & ! subcomponent + 'PACKAGES', & ! block + 'FTYPE', & ! tag name + 'FTYPE', & ! fortran variable + 'STRING', & ! type + '', & ! shape + .true., & ! required + .true., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + gwtnam_fname = InputParamDefinitionType & + ( & + 'GWT', & ! component + 'NAM', & ! subcomponent + 'PACKAGES', & ! block + 'FNAME', & ! tag name + 'FNAME', & ! fortran variable + 'STRING', & ! type + '', & ! shape + .true., & ! required + .true., & ! multi-record + .true., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + gwtnam_pname = InputParamDefinitionType & + ( & + 'GWT', & ! component + 'NAM', & ! subcomponent + 'PACKAGES', & ! block + 'PNAME', & ! tag name + 'PNAME', & ! fortran variable + 'STRING', & ! type + '', & ! shape + .false., & ! required + .true., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + gwt_nam_param_definitions(*) = & + [ & + gwtnam_list, & + gwtnam_print_input, & + gwtnam_print_flows, & + gwtnam_save_flows, & + gwtnam_ftype, & + gwtnam_fname, & + gwtnam_pname & + ] + + type(InputParamDefinitionType), parameter :: & + gwtnam_packages = InputParamDefinitionType & + ( & + 'GWT', & ! component + 'NAM', & ! subcomponent + 'PACKAGES', & ! block + 'PACKAGES', & ! tag name + 'PACKAGES', & ! fortran variable + 'RECARRAY FTYPE FNAME PNAME', & ! type + '', & ! shape + .true., & ! required + .false., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) + + type(InputParamDefinitionType), parameter :: & + gwt_nam_aggregate_definitions(*) = & + [ & + gwtnam_packages & + ] + + type(InputBlockDefinitionType), parameter :: & + gwt_nam_block_definitions(*) = & + [ & + InputBlockDefinitionType( & + 'OPTIONS', & ! blockname + .false., & ! required + .false., & ! aggregate + .false. & ! block_variable + ), & + InputBlockDefinitionType( & + 'PACKAGES', & ! blockname + .true., & ! required + .true., & ! aggregate + .false. & ! block_variable + ) & + ] + +end module GwtNamInputModule diff --git a/src/Model/ModelUtilities/DiscretizationBase.f90 b/src/Model/ModelUtilities/DiscretizationBase.f90 index 3dacb29f21e..dd03ac8faa3 100644 --- a/src/Model/ModelUtilities/DiscretizationBase.f90 +++ b/src/Model/ModelUtilities/DiscretizationBase.f90 @@ -25,6 +25,8 @@ module BaseDisModule type :: DisBaseType character(len=LENMEMPATH) :: memoryPath !< path for memory allocation character(len=LENMODELNAME), pointer :: name_model => null() !< name of the model + character(len=LENMEMPATH), pointer :: input_mempath => null() !< input context mempath + character(len=LINELENGTH), pointer :: input_fname => null() !< input file name integer(I4B), pointer :: inunit => null() !< unit number for input file integer(I4B), pointer :: iout => null() !< unit number for output file integer(I4B), pointer :: nodes => null() !< number of nodes in solution @@ -259,6 +261,8 @@ subroutine dis_da(this) ! ! -- Strings deallocate (this%name_model) + deallocate (this%input_mempath) + deallocate (this%input_fname) ! ! -- Scalars call mem_deallocate(this%inunit) @@ -568,6 +572,8 @@ subroutine allocate_scalars(this, name_model) ! ! -- Allocate allocate (this%name_model) + allocate (this%input_mempath) + allocate (this%input_fname) ! call mem_allocate(this%inunit, 'INUNIT', this%memoryPath) call mem_allocate(this%iout, 'IOUT', this%memoryPath) @@ -585,6 +591,8 @@ subroutine allocate_scalars(this, name_model) ! ! -- Initialize this%name_model = name_model + this%input_mempath = '' + this%input_fname = '' this%inunit = 0 this%iout = 0 this%nodes = 0 diff --git a/src/Model/NumericalModel.f90 b/src/Model/NumericalModel.f90 index 2d46dcd7955..97ed6ee9987 100644 --- a/src/Model/NumericalModel.f90 +++ b/src/Model/NumericalModel.f90 @@ -73,7 +73,6 @@ module NumericalModelModule procedure :: get_mcellid procedure :: get_mnodeu procedure :: get_iasym - procedure :: load_input_context end type NumericalModelType contains @@ -449,39 +448,4 @@ function GetNumericalModelFromList(list, idx) result(res) return end function GetNumericalModelFromList - !> @brief Load input context for supported package - !< - subroutine load_input_context(this, filtyp, modelname, pkgname, inunit, iout, & - ipaknum) - ! -- modules - use IdmMf6FileLoaderModule, only: input_load - ! -- dummy - class(NumericalModelType) :: this - character(len=*), intent(in) :: filtyp - character(len=*), intent(in) :: modelname - character(len=*), intent(in) :: pkgname - integer(I4B), intent(in) :: inunit - integer(I4B), intent(in) :: iout - integer(I4B), optional, intent(in) :: ipaknum - ! -- local -! ------------------------------------------------------------------------------ - ! - ! -- only load if there is a file to read - if (inunit <= 0) return - ! - ! -- Load model package input to input context - select case (filtyp) - case ('DIS6') - call input_load('DIS6', 'GWF', 'DIS', modelname, pkgname, inunit, iout) - case ('DISU6') - call input_load('DISU6', 'GWF', 'DISU', modelname, pkgname, inunit, iout) - case ('DISV6') - call input_load('DISV6', 'GWF', 'DISV', modelname, pkgname, inunit, iout) - case default - end select - ! - ! -- return - return - end subroutine load_input_context - end module NumericalModelModule diff --git a/src/Model/NumericalPackage.f90 b/src/Model/NumericalPackage.f90 index e451d272c24..07cd2882e6f 100644 --- a/src/Model/NumericalPackage.f90 +++ b/src/Model/NumericalPackage.f90 @@ -28,6 +28,8 @@ module NumericalPackageModule character(len=LENMEMPATH) :: memoryPath = '' !< the location in the memory manager where the variables are stored character(len=LENMEMPATH) :: memoryPathModel = '' !< the location in the memory manager where the variables !! of the parent model are stored + character(len=LENMEMPATH), pointer :: input_mempath => null() !< input context mempath + character(len=LINELENGTH), pointer :: input_fname => null() !< input file name character(len=LENFTYPE) :: filtyp = '' !< file type (CHD, DRN, RIV, etc.) character(len=LENFTYPE), pointer :: package_type => null() !< package type (same as filtyp) stored in memory manager @@ -113,6 +115,10 @@ subroutine allocate_scalars(this) integer(I4B), pointer :: imodelpakcb => null() ! ! -- allocate scalars + call mem_allocate(this%input_mempath, LENMEMPATH, 'INPUT_MEMPATH', & + this%memoryPath) + call mem_allocate(this%input_fname, LINELENGTH, 'INPUT_FNAME', & + this%memoryPath) call mem_allocate(this%package_type, LENFTYPE, 'PACKAGE_TYPE', & this%memoryPath) call mem_allocate(this%id, 'ID', this%memoryPath) @@ -134,6 +140,8 @@ subroutine allocate_scalars(this) call mem_setptr(imodelpakcb, 'IPAKCB', this%memoryPathModel) ! ! -- initialize + this%input_mempath = '' + this%input_fname = '' this%package_type = this%filtyp this%id = 0 this%inunit = 0 @@ -168,6 +176,8 @@ subroutine da(this) class(NumericalPackageType) :: this !< NumericalPackageType object ! ! -- deallocate + call mem_deallocate(this%input_mempath, 'INPUT_MEMPATH', this%memoryPath) + call mem_deallocate(this%input_fname, 'INPUT_FNAME', this%memoryPath) call mem_deallocate(this%package_type, 'PACKAGE_TYPE', this%memoryPath) call mem_deallocate(this%id) call mem_deallocate(this%inunit) diff --git a/src/SimulationCreate.f90 b/src/SimulationCreate.f90 index d98fc31935e..c9c8693344f 100644 --- a/src/SimulationCreate.f90 +++ b/src/SimulationCreate.f90 @@ -53,6 +53,7 @@ subroutine simulation_da() ! ! -- Deallocate input memory call memorylist_remove('SIM', 'NAM', idm_context) + call memorylist_remove(component='SIM', context=idm_context) ! ! -- variables deallocate (model_names) @@ -126,9 +127,7 @@ subroutine options_create() ! ! -- update sim options isimcontinue = simcontinue - ! isimcheck = nocheck - ! call MaxErrors(maxerror) ! if (prmem /= '') then @@ -139,8 +138,6 @@ subroutine options_create() end if end if ! - call MaxErrors(maxerror) - ! ! -- log values to list file if (iout > 0) then write (iout, '(/1x,a)') 'READING SIMULATION OPTIONS' diff --git a/src/Utilities/Idm/IdmDfnSelectorUtils.f90 b/src/Utilities/Idm/DefinitionSelect.f90 similarity index 97% rename from src/Utilities/Idm/IdmDfnSelectorUtils.f90 rename to src/Utilities/Idm/DefinitionSelect.f90 index 2bd8f88cc09..2f263c1a797 100644 --- a/src/Utilities/Idm/IdmDfnSelectorUtils.f90 +++ b/src/Utilities/Idm/DefinitionSelect.f90 @@ -1,11 +1,11 @@ -!> @brief This module contains the InputDefinitionSelectorModule +!> @brief This module contains the DefinitionSelectModule !! !! This module contains the routines for getting parameter !! definitions, aggregate definitions, and block definitions !! for the different package types. !! !< -module IdmDfnSelectorUtilsModule +module DefinitionSelectModule use KindModule, only: I4B use SimVariablesModule, only: errmsg @@ -155,4 +155,4 @@ subroutine split_record_definition(input_definition_types, component_type, & end do end subroutine split_record_definition -end module IdmDfnSelectorUtilsModule +end module DefinitionSelectModule diff --git a/src/Utilities/Idm/IdmMf6FileLoader.f90 b/src/Utilities/Idm/IdmMf6FileLoader.f90 deleted file mode 100644 index b58b851021e..00000000000 --- a/src/Utilities/Idm/IdmMf6FileLoader.f90 +++ /dev/null @@ -1,101 +0,0 @@ -!> @brief This module contains the IdmMf6FileLoaderModule -!! -!! This module contains the high-level routines for loading -!! a MODFLOW input file into the __INPUT__ memory manager -!! space. -!! -!< -module IdmMf6FileLoaderModule - - use KindModule, only: DP, I4B, LGP - use BlockParserModule, only: BlockParserType - use ModflowInputModule, only: ModflowInputType, getModflowInput - - implicit none - private - public :: input_load - - !> @brief derived type for storing package loader - !! - !! This derived type is used to store a pointer to a - !! package load procedure. This could be used to write - !! a custom package loader as a way to override the - !! generic_mf6_load routine. - !! - !< - type :: PackageLoad - procedure(IPackageLoad), nopass, pointer, public :: load_package => null() !< procedure pointer to the load routine - end type PackageLoad - - abstract interface - subroutine IPackageLoad(parser, mf6_input, iout) - use KindModule, only: DP, I4B - use BlockParserModule, only: BlockParserType - use ModflowInputModule, only: ModflowInputType - type(BlockParserType), intent(inout) :: parser !< block parser - type(ModflowInputType), intent(in) :: mf6_input !< ModflowInputType object that describes the input - integer(I4B), intent(in) :: iout !< unit number for output - end subroutine IPackageLoad - end interface - -contains - - !> @brief generic procedure to MODFLOW 6 load routine - !< - subroutine generic_mf6_load(parser, mf6_input, iout) - use LoadMf6FileTypeModule, only: idm_load - type(BlockParserType), intent(inout) :: parser !< block parser - type(ModflowInputType), intent(in) :: mf6_input !< ModflowInputType object that describes the input - integer(I4B), intent(in) :: iout !< unit number for output - - call idm_load(parser, mf6_input%pkgtype, & - mf6_input%component_type, mf6_input%subcomponent_type, & - mf6_input%component_name, mf6_input%subcomponent_name, & - iout) - - end subroutine generic_mf6_load - - !> @brief input load for traditional mf6 simulation input file - !< - subroutine input_load(pkgtype, & - component_type, subcomponent_type, & - component_name, subcomponent_name, & - inunit, iout) - character(len=*), intent(in) :: pkgtype !< pkgtype to load, such as DIS6, DISV6, NPF6 - character(len=*), intent(in) :: component_type !< component type, such as GWF or GWT - character(len=*), intent(in) :: subcomponent_type !< subcomponent type, such as DIS or NPF - character(len=*), intent(in) :: component_name !< component name, such as MYGWFMODEL - character(len=*), intent(in) :: subcomponent_name !< subcomponent name, such as MYWELLPACKAGE - integer(I4B), intent(in) :: inunit !< unit number for input - integer(I4B), intent(in) :: iout !< unit number for output - type(BlockParserType), allocatable :: parser !< block parser - type(ModflowInputType) :: mf6_input - type(PackageLoad) :: pkgloader - ! - ! -- create description of input - mf6_input = getModflowInput(pkgtype, component_type, & - subcomponent_type, component_name, & - subcomponent_name) - ! - ! -- set mf6 parser based package loader by file type - select case (pkgtype) - case default - allocate (parser) - call parser%Initialize(inunit, iout) - pkgloader%load_package => generic_mf6_load - end select - ! - ! -- invoke the selected load routine - call pkgloader%load_package(parser, mf6_input, iout) - ! - ! -- close files and deallocate - if (allocated(parser)) then - !call parser%clear() - deallocate (parser) - end if - ! - ! -- return - return - end subroutine input_load - -end module IdmMf6FileLoaderModule diff --git a/src/Utilities/Idm/IdmSimulation.f90 b/src/Utilities/Idm/IdmSimulation.f90 index 91f6d039c1c..523029695ac 100644 --- a/src/Utilities/Idm/IdmSimulation.f90 +++ b/src/Utilities/Idm/IdmSimulation.f90 @@ -13,52 +13,49 @@ module IdmSimulationModule use InputOutputModule, only: openfile, getunit use InputDefinitionModule, only: InputParamDefinitionType use ModflowInputModule, only: ModflowInputType, getModflowInput - use IdmMf6FileLoaderModule, only: input_load + use IdmMf6FileModule, only: input_load implicit none private public :: simnam_load - public :: simnam_allocate + public :: load_models contains - !> @brief MODFLOW 6 mfsim.nam input load routine + !> @brief load simulation summary info to input context !< - subroutine simnam_load() - use SimVariablesModule, only: simfile - use GenericUtilitiesModule, only: sim_message - integer(I4B) :: inunit - logical :: lexist - character(len=LINELENGTH) :: line + subroutine simnam_load_dim() + use MemoryHelperModule, only: create_mem_path + use MemoryManagerModule, only: mem_allocate, mem_setptr + use SimVariablesModule, only: idm_context + use CharacterStringModule, only: CharacterStringType + character(len=LENMEMPATH) :: sim_mempath, simnam_mempath + type(CharacterStringType), dimension(:), contiguous, & + pointer :: mtypes !< model types + type(CharacterStringType), dimension(:), contiguous, & + pointer :: etypes !< model types + integer(I4B), pointer :: nummodels => null() + integer(I4B), pointer :: numexchanges => null() ! - ! -- load mfsim.nam if it exists - inquire (file=trim(adjustl(simfile)), exist=lexist) + ! -- set memory paths + sim_mempath = create_mem_path(component='SIM', context=idm_context) + simnam_mempath = create_mem_path('SIM', 'NAM', idm_context) ! - if (lexist) then - ! - ! -- write name of namfile to stdout - write (line, '(2(1x,a))') 'Using Simulation name file:', & - trim(adjustl(simfile)) - call sim_message(line, skipafter=1) - ! - ! -- open namfile and load to input context - inunit = getunit() - call openfile(inunit, iout, trim(adjustl(simfile)), 'NAM') - call input_load('NAM6', 'SIM', 'NAM', 'SIM', 'NAM', inunit, iout) - ! - close (inunit) - ! - ! -- allocate any unallocated simnam params - call simnam_allocate() - else - ! - ! -- allocate simnam params - call simnam_allocate() - end if + ! -- set pointers to loaded simnam arrays + call mem_setptr(mtypes, 'MTYPE', simnam_mempath) + call mem_setptr(etypes, 'EXGTYPE', simnam_mempath) ! - ! --return + ! -- allocate variables + call mem_allocate(nummodels, 'NUMMODELS', sim_mempath) + call mem_allocate(numexchanges, 'NUMEXCHANGES', sim_mempath) + ! + ! -- set values + nummodels = size(mtypes) + numexchanges = size(etypes) + ! + ! -- return return - end subroutine simnam_load + end subroutine simnam_load_dim !> @brief MODFLOW 6 mfsim.nam parameter set default value !< @@ -85,8 +82,8 @@ subroutine set_default_value(intvar, mf6varname) intvar = 1 ! case default - write (errmsg, '(4x,a,a)') & - '**ERROR. IdmSimulation set_default_value unhandled variable: ', & + write (errmsg, '(a,a)') & + 'IdmSimulation set_default_value unhandled variable: ', & trim(mf6varname) call store_error(errmsg, terminate) end select @@ -99,7 +96,6 @@ end subroutine set_default_value !< subroutine simnam_allocate() use MemoryHelperModule, only: create_mem_path - use MemoryTypeModule, only: MemoryType use MemoryManagerModule, only: get_isize, mem_allocate use SimVariablesModule, only: idm_context use CharacterStringModule, only: CharacterStringType @@ -108,10 +104,10 @@ subroutine simnam_allocate() type(InputParamDefinitionType), pointer :: idt integer(I4B) :: iparam, isize logical(LGP) :: terminate = .true. - integer(I4B), pointer :: intvar => null() - character(len=LINELENGTH), pointer :: cstr => null() + integer(I4B), pointer :: intvar + character(len=LINELENGTH), pointer :: cstr type(CharacterStringType), dimension(:), & - pointer, contiguous :: acharstr1d => null() !< variable for allocation + pointer, contiguous :: acharstr1d character(len=LINELENGTH) :: errmsg ! ! -- set memory path @@ -129,17 +125,19 @@ subroutine simnam_allocate() ! -- check if variable is already allocated call get_isize(idt%mf6varname, input_mempath, isize) ! - ! -- if not, allocate and set default if (isize < 0) then + ! + ! -- reset pointers + nullify (intvar) + nullify (acharstr1d) + nullify (cstr) + ! select case (idt%datatype) case ('KEYWORD', 'INTEGER') ! ! -- allocate and set default call mem_allocate(intvar, idt%mf6varname, input_mempath) call set_default_value(intvar, idt%mf6varname) - ! - ! -- reset pointer - nullify (intvar) case ('STRING') ! ! -- did this param originate from sim namfile RECARRAY type @@ -148,21 +146,15 @@ subroutine simnam_allocate() ! -- allocate 0 size CharacterStringType array call mem_allocate(acharstr1d, LINELENGTH, 0, idt%mf6varname, & input_mempath) - ! - ! -- reset pointer - nullify (acharstr1d) else ! ! -- allocate empty string call mem_allocate(cstr, LINELENGTH, idt%mf6varname, input_mempath) cstr = '' - ! - ! -- reset pointer - nullify (cstr) end if case default - write (errmsg, '(4x,a,a)') & - '**ERROR. IdmSimulation unhandled datatype: ', & + write (errmsg, '(a,a)') & + 'IdmSimulation unhandled datatype: ', & trim(idt%datatype) call store_error(errmsg, terminate) end select @@ -173,4 +165,57 @@ subroutine simnam_allocate() return end subroutine simnam_allocate + !> @brief source indenpendent model load entry point + !< + subroutine load_models(model_loadmask, iout) + ! -- modules + use IdmMf6FileModule, only: load_models_mf6 + ! -- dummy + integer(I4B), dimension(:), intent(in) :: model_loadmask + integer(I4B), intent(in) :: iout + ! -- locals + ! + ! -- mf6 blockfile model load + call load_models_mf6(model_loadmask, iout) + ! + ! -- return + return + end subroutine load_models + + !> @brief MODFLOW 6 mfsim.nam input load routine + !< + subroutine simnam_load() + use SimVariablesModule, only: simfile + use GenericUtilitiesModule, only: sim_message + integer(I4B) :: inunit + logical :: lexist + character(len=LINELENGTH) :: line + ! + ! -- load mfsim.nam if it exists + inquire (file=trim(adjustl(simfile)), exist=lexist) + ! + if (lexist) then + ! + ! -- write name of namfile to stdout + write (line, '(2(1x,a))') 'Using Simulation name file:', & + trim(adjustl(simfile)) + call sim_message(line, skipafter=1) + ! + ! -- open namfile and load to input context + inunit = getunit() + call openfile(inunit, iout, trim(adjustl(simfile)), 'NAM') + call input_load('NAM6', 'SIM', 'NAM', 'SIM', 'NAM', inunit, iout) + close (inunit) + end if + ! + ! -- allocate any unallocated simnam params + call simnam_allocate() + ! + ! -- memload summary info + call simnam_load_dim() + ! + ! --return + return + end subroutine simnam_load + end module IdmSimulationModule diff --git a/src/Utilities/Idm/ModelPackageInputs.f90 b/src/Utilities/Idm/ModelPackageInputs.f90 new file mode 100644 index 00000000000..25ea01001a6 --- /dev/null +++ b/src/Utilities/Idm/ModelPackageInputs.f90 @@ -0,0 +1,642 @@ +!> @brief This module contains the ModelPackageInputsModule +!! +!! This module contains the high-level routines for assembling +!! model package information and loading to the input context +!! +!< +module ModelPackageInputsModule + + use KindModule, only: DP, I4B, LGP + use ConstantsModule, only: LINELENGTH, LENMEMPATH, LENMODELNAME, LENFTYPE, & + LENPACKAGETYPE, LENPACKAGENAME + use SimModule, only: store_error, store_error_filename + use SimVariablesModule, only: iout + use ArrayHandlersModule, only: expandarray + use CharacterStringModule, only: CharacterStringType + + implicit none + private + public :: NIUNIT_GWF, NIUNIT_GWT + public :: ModelPackageInputsType + + ! -- GWF base package types, ordered for memload + integer(I4B), parameter :: GWF_NBASEPKG = 50 + character(len=LENPACKAGETYPE), dimension(GWF_NBASEPKG) :: GWF_BASEPKG + data GWF_BASEPKG/'DIS6 ', 'DISV6', 'DISU6', ' ', ' ', & ! 5 + &'NPF6 ', 'BUY6 ', 'VSC6 ', 'GNC6 ', ' ', & ! 10 + &'HFB6 ', 'STO6 ', 'IC6 ', ' ', ' ', & ! 15 + &'MVR6 ', 'OC6 ', 'OBS6 ', ' ', ' ', & ! 20 + &30*' '/ ! 50 + + ! -- GWF multi package types, ordered for memload + integer(I4B), parameter :: GWF_NMULTIPKG = 50 + character(len=LENPACKAGETYPE), dimension(GWF_NMULTIPKG) :: GWF_MULTIPKG + data GWF_MULTIPKG/'WEL6 ', 'DRN6 ', 'RIV6 ', 'GHB6 ', ' ', & ! 5 + &'RCH6 ', 'EVT6 ', 'CHD6 ', 'CSUB6', ' ', & ! 10 + &'MAW6 ', 'SFR6 ', 'LAK6 ', 'UZF6 ', 'API6 ', & ! 15 + &35*' '/ ! 50 + + ! -- GWT base package types, ordered for memload + integer(I4B), parameter :: GWT_NBASEPKG = 50 + character(len=LENPACKAGETYPE), dimension(GWT_NBASEPKG) :: GWT_BASEPKG + data GWT_BASEPKG/'DIS6 ', 'DISV6', 'DISU6', ' ', ' ', & ! 5 + &'IC6 ', 'FMI6 ', 'MST6 ', 'ADV6 ', ' ', & ! 10 + &'DSP6 ', 'SSM6 ', 'MVT6 ', 'OC6 ', ' ', & ! 15 + &'OBS6 ', ' ', ' ', ' ', ' ', & ! 20 + &30*' '/ ! 50 + + ! -- GWT multi package types, ordered for memload + integer(I4B), parameter :: GWT_NMULTIPKG = 50 + character(len=LENPACKAGETYPE), dimension(GWT_NMULTIPKG) :: GWT_MULTIPKG + data GWT_MULTIPKG/'CNC6 ', 'SRC6 ', 'LKT6 ', 'IST6 ', ' ', & ! 5 + &'SFT6 ', 'MWT6 ', 'UZT6 ', 'API6 ', ' ', & ! 10 + &40*' '/ ! 50 + + ! -- size of supported model package arrays + integer(I4B), parameter :: NIUNIT_GWF = GWF_NBASEPKG + GWF_NMULTIPKG + integer(I4B), parameter :: NIUNIT_GWT = GWT_NBASEPKG + GWT_NMULTIPKG + + !> @brief derived type for loadable package type + !! + !! This derived type is used to store package instance + !! desriptions for a supported package type. + !! + !< + type :: LoadablePackageType + ! -- package type, e.g. 'DIS6 or CHD6' + character(len=LENPACKAGETYPE) :: pkgtype + ! -- component type, e.g. 'DIS or CHD' + character(len=LENFTYPE) :: component_type + ! -- package instance attribute arrays + character(len=LINELENGTH), dimension(:), allocatable :: filenames + character(len=LENPACKAGENAME), dimension(:), allocatable :: pkgnames + character(len=LENMEMPATH), dimension(:), allocatable :: mempaths + integer(I4B), dimension(:), allocatable :: inunits + ! -- number of package instances + integer(I4B) :: pnum + contains + procedure :: create => pkgtype_create + procedure :: add => pkgtype_add + procedure :: destroy => pkgtype_destroy + end type LoadablePackageType + + !> @brief derived type for model package inputs type + !! + !! This derived type is used to define input package + !! descriptors for a model and load to managed memory. + !! + !< + type :: ModelPackageInputsType + ! -- model attributes + character(len=LENPACKAGETYPE) :: modeltype ! -- model type, e.g. 'GWF6' + character(len=LINELENGTH) :: modelfname + character(len=LENMODELNAME) :: modelname + ! -- component type + character(len=LENFTYPE) :: component_type ! -- e.g. 'GWF' + ! -- model mempath + character(len=LENMEMPATH) :: model_mempath + ! -- pointers to created managed memory + type(CharacterStringType), dimension(:), contiguous, & + pointer :: pkgtypes => null() + type(CharacterStringType), dimension(:), contiguous, & + pointer :: pkgnames => null() + type(CharacterStringType), dimension(:), contiguous, & + pointer :: mempaths => null() + integer(I4B), dimension(:), contiguous, & + pointer :: inunits => null() + ! -- loadable package type array + type(LoadablePackageType), dimension(:), allocatable :: pkglist + ! -- pkgtype definitions + integer(I4B) :: niunit + character(len=LENPACKAGETYPE), dimension(:), allocatable :: cunit + ! -- out handle + integer(I4B) :: iout + contains + procedure :: init => modelpkgs_init + procedure :: memload => modelpkgs_memload + procedure :: destroy => modelpkgs_destroy + procedure, private :: create => modelpkgs_create + procedure, private :: addpkgs => modelpkgs_addpkgs + procedure, private :: add => modelpkgs_add + procedure, private :: pkgcount => modelpkgs_pkgcount + end type ModelPackageInputsType + +contains + + !> @brief set supported package types for model + !< + subroutine supported_model_packages(mtype, pkgtypes, numpkgs) + ! -- modules + ! -- dummy + character(len=LENFTYPE), intent(in) :: mtype + character(len=LENPACKAGETYPE), dimension(:), allocatable, & + intent(inout) :: pkgtypes + integer(I4B), intent(inout) :: numpkgs + ! -- local + ! + select case (mtype) + case ('GWF6') + numpkgs = GWF_NBASEPKG + GWF_NMULTIPKG + allocate (pkgtypes(numpkgs)) + pkgtypes = [GWF_BASEPKG, GWF_MULTIPKG] + ! + case ('GWT6') + numpkgs = GWT_NBASEPKG + GWT_NMULTIPKG + allocate (pkgtypes(numpkgs)) + pkgtypes = [GWT_BASEPKG, GWT_MULTIPKG] + ! + case default + end select + ! + ! -- return + return + end subroutine supported_model_packages + + !> @brief component from package or model type + !< + function component_type(pkgtype) !result(componenttype) + ! -- modules + ! -- dummy + character(len=LENPACKAGETYPE), intent(in) :: pkgtype + ! -- return + character(len=LENFTYPE) :: component_type + ! -- local + integer(I4B) :: i, ilen + ! + component_type = '' + ! + ilen = len_trim(pkgtype) + do i = 1, ilen + if (pkgtype(i:i) == '6') then + write (component_type, '(a)') trim(pkgtype(1:i - 1)) + end if + end do + ! + ! -- return + return + end function component_type + + !> @brief does model support multiple instances of this package type + !< + function multi_pkg_type(mtype_component, ptype_component, pkgtype) & + result(multi_pkg) + ! -- modules + use IdmDfnSelectorModule, only: idm_integrated, idm_multi_package + ! -- dummy + character(len=LENFTYPE), intent(in) :: mtype_component + character(len=LENFTYPE), intent(in) :: ptype_component + character(len=LENFTYPE), intent(in) :: pkgtype + ! -- return + logical(LGP) :: multi_pkg + ! -- local + integer(I4B) :: n + ! + multi_pkg = .false. + ! + if (idm_integrated(mtype_component, ptype_component)) then + ! + multi_pkg = idm_multi_package(mtype_component, ptype_component) + ! + else + ! + select case (mtype_component) + case ('GWF') + do n = 1, GWF_NMULTIPKG + if (GWF_MULTIPKG(n) == pkgtype) then + multi_pkg = .true. + exit + end if + end do + ! + case ('GWT') + do n = 1, GWT_NMULTIPKG + if (GWT_MULTIPKG(n) == pkgtype) then + multi_pkg = .true. + exit + end if + end do + ! + case default + end select + end if + ! + ! -- return + return + end function multi_pkg_type + + !> @brief create a new package type + !< + subroutine pkgtype_create(this, modelname, pkgtype) + ! -- modules + ! -- dummy + class(LoadablePackageType) :: this + character(len=*), intent(in) :: modelname + character(len=*), intent(in) :: pkgtype + ! -- local + ! + ! -- initialize + this%pkgtype = pkgtype + this%component_type = component_type(pkgtype) + this%pnum = 0 + ! + ! -- allocate arrays + allocate (this%filenames(0)) + allocate (this%pkgnames(0)) + allocate (this%mempaths(0)) + allocate (this%inunits(0)) + ! + ! -- return + return + end subroutine pkgtype_create + + !> @brief add a new package instance to this package type + !< + subroutine pkgtype_add(this, modelname, mtype_component, filetype, & + filename, pkgname, iout) + ! -- modules + use MemoryManagerModule, only: mem_allocate + use MemoryHelperModule, only: create_mem_path + use SimVariablesModule, only: idm_context + use IdmDfnSelectorModule, only: idm_integrated, idm_multi_package + ! -- dummy + class(LoadablePackageType) :: this + character(len=*), intent(in) :: modelname + character(len=*), intent(in) :: mtype_component + character(len=*), intent(in) :: filetype + character(len=*), intent(in) :: filename + character(len=*), intent(in) :: pkgname + integer(I4B), intent(in) :: iout + ! -- local + character(len=LENPACKAGENAME) :: sc_name + character(len=LENMEMPATH) :: mempath + character(len=LINELENGTH), pointer :: cstr + ! + ! -- reallocate + call expandarray(this%filenames) + call expandarray(this%pkgnames) + call expandarray(this%inunits) + call expandarray(this%mempaths) + ! + ! -- add new package instance + this%pnum = this%pnum + 1 + this%filenames(this%pnum) = filename + this%pkgnames(this%pnum) = pkgname + this%inunits(this%pnum) = 0 + ! + ! -- set up input context for model + if (idm_integrated(mtype_component, this%component_type)) then + ! + ! -- set subcomponent name + if (idm_multi_package(mtype_component, this%component_type)) then + ! + sc_name = pkgname + else + ! + sc_name = this%component_type + end if + ! + ! -- create and store the mempath + this%mempaths(this%pnum) = & + create_mem_path(modelname, sc_name, idm_context) + ! + ! -- allocate and initialize filename for package + mempath = create_mem_path(modelname, sc_name, idm_context) + call mem_allocate(cstr, LINELENGTH, 'INPUT_FNAME', mempath) + cstr = filename + else + ! + ! -- set mempath empty + this%mempaths(this%pnum) = '' + end if + ! + ! -- return + return + end subroutine pkgtype_add + + !> @brief deallocate object + !< + subroutine pkgtype_destroy(this) + ! -- modules + ! -- dummy + class(LoadablePackageType) :: this + ! -- local + ! + ! -- deallocate dynamic arrays + deallocate (this%filenames) + deallocate (this%pkgnames) + deallocate (this%inunits) + deallocate (this%mempaths) + ! + ! -- return + return + end subroutine pkgtype_destroy + + !> @brief initialize model package inputs object + !< + subroutine modelpkgs_init(this, modeltype, modelfname, modelname, iout) + ! -- modules + use MemoryHelperModule, only: create_mem_path + use MemoryManagerModule, only: mem_allocate + use SimVariablesModule, only: idm_context + ! -- dummy + class(ModelPackageInputsType) :: this + character(len=*), intent(in) :: modeltype + character(len=*), intent(in) :: modelfname + character(len=*), intent(in) :: modelname + integer(I4B), intent(in) :: iout + ! -- local + ! + ! -- initialize object + this%modeltype = modeltype + this%modelfname = modelfname + this%modelname = modelname + this%component_type = component_type(modeltype) + this%iout = iout + ! + ! -- allocate and set model supported package types + call supported_model_packages(modeltype, this%cunit, this%niunit) + ! + ! -- set model memory path + this%model_mempath = create_mem_path(component=this%modelname, & + context=idm_context) + ! + ! -- allocate managed memory + call mem_allocate(this%pkgtypes, LENPACKAGETYPE, 0, 'PKGTYPES', & + this%model_mempath) + call mem_allocate(this%pkgnames, LENPACKAGENAME, 0, 'PKGNAMES', & + this%model_mempath) + call mem_allocate(this%mempaths, LENMEMPATH, 0, 'MEMPATHS', & + this%model_mempath) + call mem_allocate(this%inunits, 0, 'INUNITS', this%model_mempath) + ! + ! build descriptions of packages + call this%addpkgs() + ! + ! -- return + return + end subroutine modelpkgs_init + + !> @brief create the package type list + !< + subroutine modelpkgs_create(this, ftypes) + ! -- modules + use SortModule, only: qsort + ! -- dummy + class(ModelPackageInputsType) :: this + type(CharacterStringType), dimension(:), contiguous, & + pointer :: ftypes + ! -- local + integer(I4B), dimension(:), allocatable :: cunit_idxs, indx + character(len=LENPACKAGETYPE) :: ftype + integer(I4B) :: n, m + logical(LGP) :: found + character(len=LINELENGTH) :: errmsg + ! + ! -- allocate + allocate (cunit_idxs(0)) + ! + ! -- identify input packages and check that each is supported + do n = 1, size(ftypes) + ! + ! -- type from model name file packages block + ftype = ftypes(n) + found = .false. + ! + ! -- search supported types for this filetype + do m = 1, this%niunit + if (this%cunit(m) == ftype) then + ! -- set found + found = .true. + ! + ! -- add to cunit list if first instance of this type + if (any(cunit_idxs == m)) then + ! no-op + else + call expandarray(cunit_idxs) + cunit_idxs(size(cunit_idxs)) = m + end if + ! + ! -- exit search + exit + end if + end do + ! + ! -- set error if namfile pkg filetype is not supported + if (.not. found) then + write (errmsg, '(a,a,a,a,a)') 'Model package type not supported & + &[model=', trim(this%modelname), ', type=', & + trim(ftype), '].' + call store_error(errmsg) + call store_error_filename(this%modelfname) + end if + end do + ! + ! -- allocate the pkglist + allocate (this%pkglist(size(cunit_idxs))) + ! + ! -- sort cunit indexes + allocate (indx(size(cunit_idxs))) + call qsort(indx, cunit_idxs) + ! + ! -- create sorted LoadablePackageType object list + do n = 1, size(cunit_idxs) + call this%pkglist(n)%create(this%modelname, this%cunit(cunit_idxs(n))) + end do + ! + ! -- cleanup + deallocate (cunit_idxs) + deallocate (indx) + ! + ! -- return + return + end subroutine modelpkgs_create + + !> @brief add a model package instance to package type list + !< + subroutine modelpkgs_add(this, pkgtype, filename, pkgname) + ! -- modules + ! -- dummy + class(ModelPackageInputsType) :: this + character(len=*), intent(in) :: pkgtype + character(len=*), intent(in) :: filename + character(len=*), intent(in) :: pkgname + ! -- local + type(LoadablePackageType) :: pkg + integer(I4B) :: n + ! + ! -- locate index of pkgtype in pkglist + do n = 1, size(this%pkglist) + pkg = this%pkglist(n) + if (pkg%pkgtype == pkgtype) then + call this%pkglist(n)%add(this%modelname, this%component_type, & + pkgtype, filename, pkgname, this%iout) + exit + end if + end do + ! + ! -- return + return + end subroutine modelpkgs_add + + !> @brief build the type list with all model package instances + !< + subroutine modelpkgs_addpkgs(this) + ! -- modules + use MemoryHelperModule, only: create_mem_path + use MemoryManagerModule, only: mem_setptr + use SimVariablesModule, only: idm_context + ! -- dummy + class(ModelPackageInputsType) :: this + ! -- local + type(CharacterStringType), dimension(:), contiguous, & + pointer :: ftypes !< file types + type(CharacterStringType), dimension(:), contiguous, & + pointer :: fnames !< file names + type(CharacterStringType), dimension(:), contiguous, & + pointer :: pnames !< package names + character(len=LENMEMPATH) :: input_mempath + character(len=LINELENGTH) :: ftype, fname, pname + integer(I4B) :: n + ! + ! -- set input memory path + input_mempath = create_mem_path(this%modelname, 'NAM', idm_context) + ! + ! -- set pointers to input context model package attribute arrays + call mem_setptr(ftypes, 'FTYPE', input_mempath) + call mem_setptr(fnames, 'FNAME', input_mempath) + call mem_setptr(pnames, 'PNAME', input_mempath) + ! + ! -- create the package list + call this%create(ftypes) + ! + ! -- load model packages + do n = 1, size(ftypes) + ! + ! -- attributes for this package + ftype = ftypes(n) + fname = fnames(n) + pname = pnames(n) + ! + ! TODO: name pkg here if not provided, this is expected to cause + ! failures for multi-pkg types when names aren't provided + ! + ! -- add this instance to package list + call this%add(ftype, fname, pname) + end do + ! + ! -- + return + end subroutine modelpkgs_addpkgs + + !> @brief get package instance count and verify base or multi of each + !< + function modelpkgs_pkgcount(this) result(pnum) + ! -- modules + ! -- dummy + class(ModelPackageInputsType) :: this + ! + ! -- return + integer(I4B) :: pnum + ! -- local + integer(I4B) :: n + character(len=LINELENGTH) :: errmsg + ! + ! -- initialize + pnum = 0 + ! + ! -- count model package instances + do n = 1, size(this%pkglist) + ! + if (multi_pkg_type(this%component_type, & + this%pkglist(n)%component_type, & + this%pkglist(n)%pkgtype)) then + ! multiple instances ok + else + ! -- set error for unexpected extra packages + if (this%pkglist(n)%pnum > 1) then + write (errmsg, '(a,a,a,a,a)') & + 'Multiple instances specified for model base package type & + &[model=', trim(this%modelname), ', type=', & + trim(this%pkglist(n)%pkgtype), '].' + call store_error(errmsg) + call store_error_filename(this%modelfname) + end if + end if + ! + ! -- add to package count + pnum = pnum + this%pkglist(n)%pnum + end do + ! + ! -- return + return + end function modelpkgs_pkgcount + + !> @brief load package descriptors to managed memory + !< + subroutine modelpkgs_memload(this) + ! -- modules + use MemoryManagerModule, only: mem_reallocate + ! -- dummy + class(ModelPackageInputsType) :: this + ! -- local + integer(I4B) :: n, m, idx + integer(I4B) :: pnum + ! + ! -- initialize load index + idx = 0 + ! + ! -- set total number of package instances + pnum = this%pkgcount() + ! + ! -- reallocate model input package attribute arrays + call mem_reallocate(this%pkgtypes, LENPACKAGETYPE, pnum, 'PKGTYPES', & + this%model_mempath) + call mem_reallocate(this%pkgnames, LENPACKAGENAME, pnum, 'PKGNAMES', & + this%model_mempath) + call mem_reallocate(this%mempaths, LENMEMPATH, pnum, 'MEMPATHS', & + this%model_mempath) + call mem_reallocate(this%inunits, pnum, 'INUNITS', this%model_mempath) + ! + ! -- load pkinfo + do n = 1, size(this%pkglist) + ! + do m = 1, this%pkglist(n)%pnum + ! -- increment index + idx = idx + 1 + ! -- package type like 'CHD6' + this%pkgtypes(idx) = trim(this%pkglist(n)%pkgtype) + ! -- package name like 'CHD-2' + this%pkgnames(idx) = trim(this%pkglist(n)%pkgnames(m)) + ! -- memory path like '__INPUT__/MYMODEL/CHD-2' + this%mempaths(idx) = trim(this%pkglist(n)%mempaths(m)) + ! -- input file unit number + this%inunits(idx) = this%pkglist(n)%inunits(m) + end do + end do + ! + ! -- return + return + end subroutine modelpkgs_memload + + !> @brief deallocate object + !< + subroutine modelpkgs_destroy(this) + ! -- modules + ! -- dummy + class(ModelPackageInputsType) :: this + ! -- local + integer(I4B) :: n + ! + ! -- + do n = 1, size(this%pkglist) + call this%pkglist(n)%destroy() + end do + ! + deallocate (this%pkglist) + deallocate (this%cunit) + ! + ! -- return + return + end subroutine modelpkgs_destroy + +end module ModelPackageInputsModule diff --git a/src/Utilities/Idm/mf6blockfile/IdmMf6File.f90 b/src/Utilities/Idm/mf6blockfile/IdmMf6File.f90 new file mode 100644 index 00000000000..4e844c23fed --- /dev/null +++ b/src/Utilities/Idm/mf6blockfile/IdmMf6File.f90 @@ -0,0 +1,336 @@ +!> @brief This module contains the IdmMf6FileModule +!! +!! This module contains the high-level routines for loading +!! a MODFLOW input file to the input context. +!! +!< +module IdmMf6FileModule + + use KindModule, only: DP, I4B, LGP + use ConstantsModule, only: LINELENGTH, LENMEMPATH, LENMODELNAME, & + LENPACKAGENAME, LENFTYPE, LENPACKAGETYPE + use SimModule, only: store_error, store_error_filename + use InputOutputModule, only: openfile, getunit + use BlockParserModule, only: BlockParserType + use ModflowInputModule, only: ModflowInputType, getModflowInput + use CharacterStringModule, only: CharacterStringType + use ModelPackageInputsModule, only: ModelPackageInputsType + + implicit none + private + public :: input_load ! TODO: remove + public :: load_models_mf6 + + !> @brief derived type for storing package loader + !! + !! This derived type is used to store a pointer to a + !! package load procedure. This could be used to write + !! a custom package loader as a way to override the + !! generic_mf6_load routine. + !! + !< + type :: PackageLoad + procedure(IPackageLoad), nopass, pointer, public :: load_package => null() !< procedure pointer to the load routine + end type PackageLoad + + abstract interface + subroutine IPackageLoad(parser, mf6_input, iout) + use KindModule, only: DP, I4B + use BlockParserModule, only: BlockParserType + use ModflowInputModule, only: ModflowInputType + type(BlockParserType), intent(inout) :: parser !< block parser + type(ModflowInputType), intent(in) :: mf6_input !< ModflowInputType object that describes the input + integer(I4B), intent(in) :: iout !< unit number for output + end subroutine IPackageLoad + end interface + +contains + + !> @brief generic procedure to MODFLOW 6 load routine + !< + subroutine generic_mf6_load(parser, mf6_input, iout) + use LoadMf6FileModule, only: idm_load + type(BlockParserType), intent(inout) :: parser !< block parser + type(ModflowInputType), intent(in) :: mf6_input !< ModflowInputType object that describes the input + integer(I4B), intent(in) :: iout !< unit number for output + + call idm_load(parser, mf6_input%pkgtype, & + mf6_input%component_type, mf6_input%subcomponent_type, & + mf6_input%component_name, mf6_input%subcomponent_name, & + iout) + + end subroutine generic_mf6_load + + !> @brief input load for traditional mf6 simulation input file + !< + subroutine input_load(pkgtype, & + component_type, subcomponent_type, & + component_name, subcomponent_name, & + inunit, iout) + character(len=*), intent(in) :: pkgtype !< pkgtype to load, such as DIS6, DISV6, NPF6 + character(len=*), intent(in) :: component_type !< component type, such as GWF or GWT + character(len=*), intent(in) :: subcomponent_type !< subcomponent type, such as DIS or NPF + character(len=*), intent(in) :: component_name !< component name, such as MYGWFMODEL + character(len=*), intent(in) :: subcomponent_name !< subcomponent name, such as MYWELLPACKAGE + integer(I4B), intent(in) :: inunit !< unit number for input + integer(I4B), intent(in) :: iout !< unit number for output + type(BlockParserType), allocatable :: parser !< block parser + type(ModflowInputType) :: mf6_input + type(PackageLoad) :: pkgloader + ! + ! -- create description of input + mf6_input = getModflowInput(pkgtype, component_type, & + subcomponent_type, component_name, & + subcomponent_name) + ! + ! -- set mf6 parser based package loader by file type + select case (pkgtype) + case default + allocate (parser) + call parser%Initialize(inunit, iout) + pkgloader%load_package => generic_mf6_load + end select + ! + ! -- invoke the selected load routine + call pkgloader%load_package(parser, mf6_input, iout) + ! + ! -- close files and deallocate + if (allocated(parser)) then + !call parser%clear() + deallocate (parser) + end if + ! + ! -- return + return + end subroutine input_load + + !> @brief input load model idm supported package files + !< + subroutine load_model_pkgfiles(model_pkg_inputs, iout) + ! -- modules + use IdmDfnSelectorModule, only: idm_integrated, idm_multi_package + ! -- dummy + type(ModelPackageInputsType), intent(inout) :: model_pkg_inputs + integer(I4B), intent(in) :: iout + ! -- locals + integer(I4B) :: n, m + character(len=LENPACKAGETYPE) :: pkgtype + character(len=LENPACKAGENAME) :: sc_name + ! + do n = 1, size(model_pkg_inputs%pkglist) + ! + ! -- this list package type + pkgtype = model_pkg_inputs%pkglist(n)%pkgtype + ! + ! -- load all idm integrated package type file instances + do m = 1, model_pkg_inputs%pkglist(n)%pnum + ! + if (idm_integrated(model_pkg_inputs%component_type, & + model_pkg_inputs%pkglist(n)%component_type)) then + ! + ! -- set subcomponent name + if (idm_multi_package(model_pkg_inputs%component_type, & + model_pkg_inputs%pkglist(n)%component_type)) then + ! + sc_name = model_pkg_inputs%pkglist(n)%pkgnames(m) + else + ! + sc_name = model_pkg_inputs%pkglist(n)%component_type + end if + ! + ! -- load model package to input context + call input_load(pkgtype, model_pkg_inputs%component_type, & + model_pkg_inputs%pkglist(n)%component_type, & + model_pkg_inputs%modelname, sc_name, & + model_pkg_inputs%pkglist(n)%inunits(m), iout) + ! + ! -- close file and update unit number + close (model_pkg_inputs%pkglist(n)%inunits(m)) + model_pkg_inputs%pkglist(n)%inunits(m) = 0 + ! + else + ! Not an IDM supported package, leave inunit open + end if + end do + end do + ! + ! -- return + return + end subroutine load_model_pkgfiles + + !> @brief open all model package files + !< + subroutine open_model_pkgfiles(model_pkg_inputs, iout) + ! -- modules + ! -- dummy + type(ModelPackageInputsType), intent(inout) :: model_pkg_inputs + integer(I4B), intent(in) :: iout + ! -- locals + integer(I4B) :: n, m + character(len=LINELENGTH) :: filename + character(len=LENPACKAGETYPE) :: filetype + character(len=LINELENGTH) :: errmsg + ! + do n = 1, size(model_pkg_inputs%pkglist) + ! + ! -- this package type + filetype = model_pkg_inputs%pkglist(n)%pkgtype + ! + ! -- open each package type file instance + do m = 1, model_pkg_inputs%pkglist(n)%pnum + ! + ! -- set filename + filename = model_pkg_inputs%pkglist(n)%filenames(m) + ! + if (filename /= '') then + ! + ! -- get unit number, update object and open file + model_pkg_inputs%pkglist(n)%inunits(m) = getunit() + call openfile(model_pkg_inputs%pkglist(n)%inunits(m), iout, & + trim(adjustl(filename)), filetype, 'FORMATTED', & + 'SEQUENTIAL', 'OLD') + ! + else + write (errmsg, '(a,a,a,a,a)') & + 'Package file unspecified, cannot load model package & + &[model=', trim(model_pkg_inputs%modelname), & + ', type=', trim(filetype), '].' + call store_error(errmsg) + call store_error_filename(model_pkg_inputs%modelfname) + end if + end do + end do + ! + ! -- returh + return + end subroutine open_model_pkgfiles + + !> @brief load and make pkg info available to models + !< + subroutine modelpkgs_load(mtype, mfname, mname, iout) + ! -- modules + ! -- dummy + character(len=*), intent(in) :: mtype + character(len=*), intent(in) :: mfname + character(len=*), intent(in) :: mname + integer(I4B), intent(in) :: iout + ! -- locals + type(ModelPackageInputsType) :: model_pkg_inputs + ! + ! -- set baseline state for model package instances + call model_pkg_inputs%init(mtype, mfname, mname, iout) + ! + ! -- open model package files + call open_model_pkgfiles(model_pkg_inputs, iout) + ! + ! -- load model idm integrated package files + call load_model_pkgfiles(model_pkg_inputs, iout) + ! + ! -- load descriptions of packages to model input context + call model_pkg_inputs%memload() + ! + ! -- cleanup + call model_pkg_inputs%destroy() + ! + ! -- return + return + end subroutine modelpkgs_load + + !> @brief input load a single model namfile and model package files + !< + subroutine model_load(mtype, mfname, mname, iout) + ! -- modules + use SimVariablesModule, only: simfile + ! -- dummy + character(len=*), intent(in) :: mtype + character(len=*), intent(in) :: mfname + character(len=*), intent(in) :: mname + integer(I4B), intent(in) :: iout + ! -- locals + character(len=LINELENGTH) :: errmsg + integer(I4B) :: inunit + ! + ! -- open namfile + inunit = getunit() + call openfile(inunit, iout, trim(mfname), 'NAM') + ! + select case (mtype) + case ('GWF6') + ! + ! -- load model namfile to the input context + call input_load('GWF6', 'GWF', 'NAM', mname, 'NAM', inunit, iout) + ! + ! -- load and create descriptions of model package files + call modelpkgs_load(mtype, mfname, mname, iout) + ! + case ('GWT6') + ! + call input_load('GWT6', 'GWT', 'NAM', mname, 'NAM', inunit, iout) + ! + call modelpkgs_load(mtype, mfname, mname, iout) + ! + case default + write (errmsg, '(a,a,a,a,a)') & + 'Unknown simulation model type & + &[model=', trim(mname), & + ', type=', trim(mtype), '].' + call store_error(errmsg) + call store_error_filename(simfile) + end select + ! + ! -- close namfile + close (inunit) + ! + ! -- return + return + end subroutine model_load + + !> @brief input load model namfiles and model package files + !< + subroutine load_models_mf6(model_loadmask, iout) + ! -- modules + use MemoryHelperModule, only: create_mem_path + use MemoryManagerModule, only: mem_setptr + use CharacterStringModule, only: CharacterStringType + use SimVariablesModule, only: idm_context + ! -- dummy + integer(I4B), dimension(:), intent(in) :: model_loadmask + integer(I4B), intent(in) :: iout + ! -- locals + character(len=LENMEMPATH) :: input_mempath + type(CharacterStringType), dimension(:), contiguous, & + pointer :: mtypes !< model types + type(CharacterStringType), dimension(:), contiguous, & + pointer :: mfnames !< model file names + type(CharacterStringType), dimension(:), contiguous, & + pointer :: mnames !< model names + character(len=LINELENGTH) :: mtype, mfname + character(len=LENMODELNAME) :: mname + integer(I4B) :: n + ! + ! -- set input memory path + input_mempath = create_mem_path('SIM', 'NAM', idm_context) + ! + ! -- set pointers to input context model attribute arrays + call mem_setptr(mtypes, 'MTYPE', input_mempath) + call mem_setptr(mfnames, 'MFNAME', input_mempath) + call mem_setptr(mnames, 'MNAME', input_mempath) + ! + do n = 1, size(mtypes) + ! + ! -- attributes for this model + mtype = mtypes(n) + mfname = mfnames(n) + mname = mnames(n) + ! + ! -- load model namfile + if (model_loadmask(n) > 0) then + call model_load(mtype, mfname, mname, iout) + end if + end do + ! + ! -- return + return + end subroutine load_models_mf6 + +end module IdmMf6FileModule diff --git a/src/Utilities/Idm/LoadMf6FileType.f90 b/src/Utilities/Idm/mf6blockfile/LoadMf6File.f90 similarity index 99% rename from src/Utilities/Idm/LoadMf6FileType.f90 rename to src/Utilities/Idm/mf6blockfile/LoadMf6File.f90 index fcede292d22..06597b1da20 100644 --- a/src/Utilities/Idm/LoadMf6FileType.f90 +++ b/src/Utilities/Idm/mf6blockfile/LoadMf6File.f90 @@ -1,11 +1,11 @@ -!> @brief This module contains the LoadMf6FileTypeModule +!> @brief This module contains the LoadMf6FileModule !! !! This module contains the input data model routines for !! loading the data from a MODFLOW 6 input file using the !! block parser. !! !< -module LoadMf6FileTypeModule +module LoadMf6FileModule use KindModule, only: DP, I4B, LGP use ConstantsModule, only: LINELENGTH, LENMEMPATH, LENVARNAME @@ -24,8 +24,8 @@ module LoadMf6FileTypeModule use Integer2dReaderModule, only: read_int2d use InputOutputModule, only: parseline use InputDefinitionModule, only: InputParamDefinitionType - use IdmDfnSelectorUtilsModule, only: get_param_definition_type, & - get_aggregate_definition_type + use DefinitionSelectModule, only: get_param_definition_type, & + get_aggregate_definition_type use ModflowInputModule, only: ModflowInputType, getModflowInput use MemoryManagerModule, only: mem_allocate, mem_setptr use MemoryHelperModule, only: create_mem_path @@ -163,7 +163,7 @@ end subroutine parse_block subroutine parse_iofile_tag(parser, mf6_input, iblock, mshape, tag, found, & iout) - use IdmDfnSelectorUtilsModule, only: split_record_definition + use DefinitionSelectModule, only: split_record_definition type(BlockParserType), intent(inout) :: parser !< block parser type(ModflowInputType), intent(in) :: mf6_input !< ModflowInputType integer(I4B), intent(in) :: iblock !< consecutive block number as defined in definition file @@ -855,4 +855,4 @@ subroutine get_shape_from_string(shape_string, array_shape, memoryPath) end subroutine get_shape_from_string -end module LoadMf6FileTypeModule +end module LoadMf6FileModule diff --git a/src/Utilities/Idm/StructArray.f90 b/src/Utilities/Idm/mf6blockfile/StructArray.f90 similarity index 100% rename from src/Utilities/Idm/StructArray.f90 rename to src/Utilities/Idm/mf6blockfile/StructArray.f90 diff --git a/src/Utilities/Idm/StructVector.f90 b/src/Utilities/Idm/mf6blockfile/StructVector.f90 similarity index 100% rename from src/Utilities/Idm/StructVector.f90 rename to src/Utilities/Idm/mf6blockfile/StructVector.f90 diff --git a/src/Utilities/Idm/selector/IdmGwfDfnSelector.f90 b/src/Utilities/Idm/selector/IdmGwfDfnSelector.f90 index 836fb9a8bb0..4ff5996addd 100644 --- a/src/Utilities/Idm/selector/IdmGwfDfnSelector.f90 +++ b/src/Utilities/Idm/selector/IdmGwfDfnSelector.f90 @@ -20,6 +20,10 @@ module IdmGwfDfnSelectorModule gwf_npf_aggregate_definitions, & gwf_npf_block_definitions, & gwf_npf_multi_package + use GwfNamInputModule, only: gwf_nam_param_definitions, & + gwf_nam_aggregate_definitions, & + gwf_nam_block_definitions, & + gwf_nam_multi_package implicit none private @@ -56,6 +60,8 @@ function gwf_param_definitions(subcomponent) result(input_definition) call set_param_pointer(input_definition, gwf_disv_param_definitions) case ('NPF') call set_param_pointer(input_definition, gwf_npf_param_definitions) + case ('NAM') + call set_param_pointer(input_definition, gwf_nam_param_definitions) case default end select return @@ -74,6 +80,8 @@ function gwf_aggregate_definitions(subcomponent) result(input_definition) call set_param_pointer(input_definition, gwf_disv_aggregate_definitions) case ('NPF') call set_param_pointer(input_definition, gwf_npf_aggregate_definitions) + case ('NAM') + call set_param_pointer(input_definition, gwf_nam_aggregate_definitions) case default end select return @@ -92,6 +100,8 @@ function gwf_block_definitions(subcomponent) result(input_definition) call set_block_pointer(input_definition, gwf_disv_block_definitions) case ('NPF') call set_block_pointer(input_definition, gwf_npf_block_definitions) + case ('NAM') + call set_block_pointer(input_definition, gwf_nam_block_definitions) case default end select return @@ -109,6 +119,8 @@ function gwf_idm_multi_package(subcomponent) result(multi_package) multi_package = gwf_disv_multi_package case ('NPF') multi_package = gwf_npf_multi_package + case ('NAM') + multi_package = gwf_nam_multi_package case default call store_error('Idm selector subcomponent not found; '//& &'component="GWF"'//& @@ -130,6 +142,8 @@ function gwf_idm_integrated(subcomponent) result(integrated) integrated = .true. case ('NPF') integrated = .true. + case ('NAM') + integrated = .true. case default end select return diff --git a/src/Utilities/Idm/selector/IdmGwtDfnSelector.f90 b/src/Utilities/Idm/selector/IdmGwtDfnSelector.f90 index 64dfbdca24e..9c46a00b9b7 100644 --- a/src/Utilities/Idm/selector/IdmGwtDfnSelector.f90 +++ b/src/Utilities/Idm/selector/IdmGwtDfnSelector.f90 @@ -4,10 +4,26 @@ module IdmGwtDfnSelectorModule use SimModule, only: store_error use InputDefinitionModule, only: InputParamDefinitionType, & InputBlockDefinitionType + use GwtDisInputModule, only: gwt_dis_param_definitions, & + gwt_dis_aggregate_definitions, & + gwt_dis_block_definitions, & + gwt_dis_multi_package + use GwtDisuInputModule, only: gwt_disu_param_definitions, & + gwt_disu_aggregate_definitions, & + gwt_disu_block_definitions, & + gwt_disu_multi_package + use GwtDisvInputModule, only: gwt_disv_param_definitions, & + gwt_disv_aggregate_definitions, & + gwt_disv_block_definitions, & + gwt_disv_multi_package use GwtDspInputModule, only: gwt_dsp_param_definitions, & gwt_dsp_aggregate_definitions, & gwt_dsp_block_definitions, & gwt_dsp_multi_package + use GwtNamInputModule, only: gwt_nam_param_definitions, & + gwt_nam_aggregate_definitions, & + gwt_nam_block_definitions, & + gwt_nam_multi_package implicit none private @@ -36,8 +52,16 @@ function gwt_param_definitions(subcomponent) result(input_definition) type(InputParamDefinitionType), dimension(:), pointer :: input_definition nullify (input_definition) select case (subcomponent) + case ('DIS') + call set_param_pointer(input_definition, gwt_dis_param_definitions) + case ('DISU') + call set_param_pointer(input_definition, gwt_disu_param_definitions) + case ('DISV') + call set_param_pointer(input_definition, gwt_disv_param_definitions) case ('DSP') call set_param_pointer(input_definition, gwt_dsp_param_definitions) + case ('NAM') + call set_param_pointer(input_definition, gwt_nam_param_definitions) case default end select return @@ -48,8 +72,16 @@ function gwt_aggregate_definitions(subcomponent) result(input_definition) type(InputParamDefinitionType), dimension(:), pointer :: input_definition nullify (input_definition) select case (subcomponent) + case ('DIS') + call set_param_pointer(input_definition, gwt_dis_aggregate_definitions) + case ('DISU') + call set_param_pointer(input_definition, gwt_disu_aggregate_definitions) + case ('DISV') + call set_param_pointer(input_definition, gwt_disv_aggregate_definitions) case ('DSP') call set_param_pointer(input_definition, gwt_dsp_aggregate_definitions) + case ('NAM') + call set_param_pointer(input_definition, gwt_nam_aggregate_definitions) case default end select return @@ -60,8 +92,16 @@ function gwt_block_definitions(subcomponent) result(input_definition) type(InputBlockDefinitionType), dimension(:), pointer :: input_definition nullify (input_definition) select case (subcomponent) + case ('DIS') + call set_block_pointer(input_definition, gwt_dis_block_definitions) + case ('DISU') + call set_block_pointer(input_definition, gwt_disu_block_definitions) + case ('DISV') + call set_block_pointer(input_definition, gwt_disv_block_definitions) case ('DSP') call set_block_pointer(input_definition, gwt_dsp_block_definitions) + case ('NAM') + call set_block_pointer(input_definition, gwt_nam_block_definitions) case default end select return @@ -71,8 +111,16 @@ function gwt_idm_multi_package(subcomponent) result(multi_package) character(len=*), intent(in) :: subcomponent logical :: multi_package select case (subcomponent) + case ('DIS') + multi_package = gwt_dis_multi_package + case ('DISU') + multi_package = gwt_disu_multi_package + case ('DISV') + multi_package = gwt_disv_multi_package case ('DSP') multi_package = gwt_dsp_multi_package + case ('NAM') + multi_package = gwt_nam_multi_package case default call store_error('Idm selector subcomponent not found; '//& &'component="GWT"'//& @@ -86,8 +134,16 @@ function gwt_idm_integrated(subcomponent) result(integrated) logical :: integrated integrated = .false. select case (subcomponent) + case ('DIS') + integrated = .true. + case ('DISU') + integrated = .true. + case ('DISV') + integrated = .true. case ('DSP') integrated = .true. + case ('NAM') + integrated = .true. case default end select return diff --git a/src/Utilities/NameFile.f90 b/src/Utilities/NameFile.f90 deleted file mode 100644 index 115a8550c26..00000000000 --- a/src/Utilities/NameFile.f90 +++ /dev/null @@ -1,379 +0,0 @@ -module NameFileModule - - use KindModule, only: DP, I4B - use InputOutputModule, only: ParseLine, openfile, getunit - use ConstantsModule, only: LINELENGTH, LENPACKAGENAME - use ArrayHandlersModule, only: ExpandArray, remove_character - use IunitModule, only: IunitType - use BlockParserModule, only: BlockParserType - implicit none - private - public :: NameFileType - - type :: NameFileType - character(len=LINELENGTH) :: filename - logical :: opened_listfile = .false. - character(len=LINELENGTH), dimension(:), allocatable :: opts - character(len=LINELENGTH), dimension(:), allocatable :: input_files - type(IunitType) :: iunit_obj - type(BlockParserType) :: parser - contains - procedure :: init => namefile_init - procedure :: add_cunit => namefile_add_cunit - procedure :: openlistfile => namefile_openlistfile - procedure :: openfiles => namefile_openfiles - procedure :: get_unitnumber => namefile_get_unitnumber - procedure :: get_nval_for_row => namefile_get_nval_for_row - procedure :: get_unitnumber_rowcol => namefile_get_unitnumber_rowcol - procedure :: get_pakname => namefile_get_pakname - end type NameFileType - -contains - - subroutine namefile_init(this, filename, iout) -! ****************************************************************************** -! namefile_init -- initialize the namefile object using the filename. if iout -! is non-zero, then the block information will be written to iout. -! ****************************************************************************** -! -! SPECIFICATIONS: -! ------------------------------------------------------------------------------ - ! -- modules - use SimModule, only: store_error - ! -- dummy - class(NameFileType) :: this - character(len=*), intent(in) :: filename - integer(I4B), intent(in) :: iout - ! -- local - character(len=LINELENGTH) :: errmsg, line - integer(I4B) :: i, ierr, inunit, n - logical :: isFound, endOfBlock - ! -- formats - character(len=*), parameter :: fmtfname = & - "(1x, 'NON-COMMENTED ENTRIES FOUND IN ', /, & - &4X, 'BLOCK: ', a, /, & - &4X, 'FILE: ', a)" - character(len=*), parameter :: fmtbeg = "(/, 1x, A)" - character(len=*), parameter :: fmtline = "(2x, a)" - character(len=*), parameter :: fmtend = "(1x, A, /)" -! ------------------------------------------------------------------------------ - ! - ! -- Store filename and initialize variables - this%filename = filename - allocate (this%opts(0)) - allocate (this%input_files(0)) - ! - ! -- Open the name file and initialize the block parser - inunit = getunit() - call openfile(inunit, iout, filename, 'NAM', filstat_opt='OLD') - call this%parser%Initialize(inunit, iout) - ! - ! -- Read and set the options - call this%parser%GetBlock('OPTIONS', isFound, ierr, & - supportOpenClose=.true., blockRequired=.false.) - if (isFound) then - ! - ! -- Populate this%opts - n = 0 - getopts: do - call this%parser%GetNextLine(endOfBlock) - if (endOfBlock) exit getopts - call this%parser%GetCurrentLine(line) - call ExpandArray(this%opts) - n = n + 1 - this%opts(n) = adjustl(line) - end do getopts - ! - if (iout > 0) then - write (iout, fmtfname) 'OPTIONS', trim(adjustl(filename)) - write (iout, fmtbeg) 'BEGIN OPTIONS' - do i = 1, n - write (iout, fmtline) trim(adjustl(this%opts(i))) - end do - write (iout, fmtend) 'END OPTIONS' - end if - else - if (iout > 0) then - write (iout, '(/, A, /)') 'NO VALID OPTIONS BLOCK DETECTED' - end if - end if - ! - ! -- Read and set the input_files - call this%parser%GetBlock('PACKAGES', isFound, ierr, & - supportOpenClose=.true.) - if (isFound) then - ! - ! -- Populate this%input_files - n = 0 - getpaks: do - call this%parser%GetNextLine(endOfBlock) - if (endOfBlock) exit getpaks - call this%parser%GetCurrentLine(line) - call ExpandArray(this%input_files) - n = n + 1 - this%input_files(n) = adjustl(line) - end do getpaks - ! - ! -- Write to list file - if (iout > 0) then - write (iout, fmtfname) 'PACKAGES', trim(adjustl(filename)) - write (iout, fmtbeg) 'BEGIN PACKAGES' - do i = 1, n - write (iout, fmtline) trim(adjustl(this%input_files(i))) - end do - write (iout, fmtend) 'END PACKAGES' - end if - else - ! - ! -- Package block not found. Terminate with error. - write (errmsg, '(a, a)') 'Error reading PACKAGES from file: ', & - trim(adjustl(filename)) - call store_error(errmsg, terminate=.TRUE.) - end if - ! - ! -- return - return - end subroutine namefile_init - - subroutine namefile_add_cunit(this, niunit, cunit) -! ****************************************************************************** -! namefile_add_cunit -- attach the cunit array to the iunit object -! ****************************************************************************** -! -! SPECIFICATIONS: -! ------------------------------------------------------------------------------ - ! -- dummy - class(NameFileType) :: this - integer(I4B), intent(in) :: niunit - character(len=*), dimension(niunit), intent(in) :: cunit -! ------------------------------------------------------------------------------ - ! - call this%iunit_obj%init(niunit, cunit) - ! - ! -- return - return - end subroutine namefile_add_cunit - - subroutine namefile_openlistfile(this, iout) -! ****************************************************************************** -! namefile_openlistfile -- Open the list file and set iout. -! ****************************************************************************** -! -! SPECIFICATIONS: -! ------------------------------------------------------------------------------ - ! -- modules - use InputOutputModule, only: getunit, upcase - ! -- dummy - class(NameFileType) :: this - integer(I4B), intent(inout) :: iout - ! -- local - logical :: found - character(len=LINELENGTH) :: fname - integer(I4B) :: i, istart, istop - integer(I4B) :: nwords - integer(I4B) :: ipos - character(len=LINELENGTH), allocatable, dimension(:) :: words - ! -- formats -! ------------------------------------------------------------------------------ - ! - ! -- Go through the options and see if LIST was specified - found = .false. - ipos = 0 - findloop: do i = 1, size(this%opts) - call ParseLine(this%opts(i), nwords, words) - call upcase(words(1)) - if (words(1) == 'LIST') then - fname = words(2) - ipos = i - found = .true. - exit findloop - end if - end do findloop - ! - ! -- remove list file from options list - if (ipos > 0) then - call remove_character(this%opts, ipos) - end if - ! - ! -- If LIST was not found, then set name of list file by replacing the - ! namefile extension with '.lst' If no extension then add to end - ! of namefile name. - if (.not. found) then - fname = ' ' - istart = 0 - istop = len_trim(this%filename) - do i = istop, 1, -1 - if (this%filename(i:i) == '.') then - istart = i - exit - end if - end do - if (istart == 0) istart = istop + 1 - fname = this%filename(1:istart) - istop = istart + 3 - fname(istart:istop) = '.lst' - end if - ! - ! -- Open the list file - iout = getunit() - call openfile(iout, 0, trim(fname), 'LIST', filstat_opt='REPLACE') - this%opened_listfile = .true. - ! - ! -- return - return - end subroutine namefile_openlistfile - - subroutine namefile_openfiles(this, iout) -! ****************************************************************************** -! namefile_openfiles -- Open the files. -! ****************************************************************************** -! -! SPECIFICATIONS: -! ------------------------------------------------------------------------------ - ! -- modules - use InputOutputModule, only: getunit, upcase - ! -- dummy - class(NameFileType) :: this - integer(I4B), intent(in) :: iout - ! -- local - character(len=20) :: ftype, accarg, fmtarg, filstat - integer(I4B) :: i, inunit, nwords - character(len=LINELENGTH), allocatable, dimension(:) :: words - ! -- formats -! ------------------------------------------------------------------------------ - ! - ! -- Open the input_files - do i = 1, size(this%input_files) - ! - ! -- Parse the line and set defaults - call ParseLine(this%input_files(i), nwords, words) - call upcase(words(1)) - ftype = words(1) (1:20) - accarg = 'SEQUENTIAL' - fmtarg = 'FORMATTED' - filstat = 'OLD' - ! - ! -- Get a free unit number and assign it to the file - inunit = getunit() - call this%iunit_obj%addfile(ftype, inunit, i, this%filename) - ! - ! -- Open the file - call openfile(inunit, iout, trim(adjustl(words(2))), & - ftype, fmtarg, accarg, filstat) - end do - ! - ! -- return - return - end subroutine namefile_openfiles - - subroutine namefile_get_unitnumber(this, ftype, inunit, iremove) -! ****************************************************************************** -! namefile_get_unitnumber -- Assign the unit number for the ftype to inunit. -! If iremove > 0, then remove this file from iunit_obj. -! ****************************************************************************** -! -! SPECIFICATIONS: -! ------------------------------------------------------------------------------ - class(NameFileType) :: this - character(len=*), intent(in) :: ftype - integer(I4B), intent(inout) :: inunit - integer(I4B), intent(in) :: iremove -! ------------------------------------------------------------------------------ - ! - call this%iunit_obj%getunitnumber(ftype, inunit, iremove) - ! - ! -- return - return - end subroutine namefile_get_unitnumber - - function namefile_get_nval_for_row(this, irow) result(nval) -! ****************************************************************************** -! namefile_get_nval_for_row -- Get the number of entries for the cunit type in -! row irow. For example, return the number of well packages that were -! read from the name file. -! ****************************************************************************** -! -! SPECIFICATIONS: -! ------------------------------------------------------------------------------ - ! -- return - integer(I4B) :: nval - class(NameFileType) :: this - integer(I4B), intent(in) :: irow -! ------------------------------------------------------------------------------ - ! - nval = this%iunit_obj%iunit(irow)%nval - ! - ! -- return - return - end function namefile_get_nval_for_row - - function namefile_get_unitnumber_rowcol(this, irow, jcol) & - result(iu) -! ****************************************************************************** -! namefile_get_unitnumber_rowcol -- Get the unit number for entries in -! cunit(irow) and columns (icol). For example, return the unit number for -! the first, second, or third well package. -! ****************************************************************************** -! -! SPECIFICATIONS: -! ------------------------------------------------------------------------------ - ! -- return - integer(I4B) :: iu - class(NameFileType) :: this - integer(I4B), intent(in) :: irow - integer(I4B), intent(in) :: jcol -! ------------------------------------------------------------------------------ - ! - iu = this%iunit_obj%iunit(irow)%iunit(jcol) - ! - ! -- return - return - end function namefile_get_unitnumber_rowcol - - subroutine namefile_get_pakname(this, irow, jcol, pakname) -! ****************************************************************************** -! namefile_get_pakname -- Assign the unit number for the ftype to inunit. -! If iremove > 0, then remove this file from iunit_obj. -! ****************************************************************************** -! -! SPECIFICATIONS: -! ------------------------------------------------------------------------------ - ! -- modules - use SimModule, only: store_error - use InputOutputModule, only: upcase - ! -- dummy - class(NameFileType) :: this - integer(I4B), intent(in) :: irow - integer(I4B), intent(in) :: jcol - character(len=*), intent(inout) :: pakname - ! -- local - integer(I4B) :: ilen, ipos, nwords - character(len=LINELENGTH) :: errmsg - character(len=LINELENGTH), allocatable, dimension(:) :: words -! ------------------------------------------------------------------------------ - ! - ipos = this%iunit_obj%iunit(irow)%ipos(jcol) - call ParseLine(this%input_files(ipos), nwords, words, & - filename=this%filename) - pakname = '' - if (nwords > 2) then - ilen = len(trim(adjustl(words(3)))) - if (ilen > LENPACKAGENAME) then - write (errmsg, "(a, i0, a)") & - 'ERROR. PACKAGENAME MUST NOT BE GREATER THAN ', & - LENPACKAGENAME, ' CHARACTERS.' - call store_error(errmsg) - call store_error(trim(this%input_files(ipos))) - write (errmsg, '(a, a)') 'Error in PACKAGES block in file: ', & - trim(adjustl(this%filename)) - call store_error(errmsg, terminate=.TRUE.) - end if - pakname = trim(adjustl(words(3))) - call upcase(pakname) - end if - ! - ! -- return - return - end subroutine namefile_get_pakname - -end module NameFileModule diff --git a/src/meson.build b/src/meson.build index e53368834f0..d84924ee05e 100644 --- a/src/meson.build +++ b/src/meson.build @@ -62,6 +62,7 @@ modflow_sources = files( 'Model' / 'GroundWaterFlow' / 'gwf3ghb8.f90', 'Model' / 'GroundWaterFlow' / 'gwf3hfb8.f90', 'Model' / 'GroundWaterFlow' / 'gwf3ic8.f90', + 'Model' / 'GroundWaterFlow' / 'gwf3idm.f90', 'Model' / 'GroundWaterFlow' / 'gwf3lak8.f90', 'Model' / 'GroundWaterFlow' / 'gwf3maw8.f90', 'Model' / 'GroundWaterFlow' / 'gwf3mvr8.f90', @@ -83,10 +84,14 @@ modflow_sources = files( 'Model' / 'GroundWaterTransport' / 'gwt1adv1.f90', 'Model' / 'GroundWaterTransport' / 'gwt1apt1.f90', 'Model' / 'GroundWaterTransport' / 'gwt1cnc1.f90', + 'Model' / 'GroundWaterTransport' / 'gwt1dis1idm.f90', + 'Model' / 'GroundWaterTransport' / 'gwt1disu1idm.f90', + 'Model' / 'GroundWaterTransport' / 'gwt1disv1idm.f90', 'Model' / 'GroundWaterTransport' / 'gwt1dsp.f90', 'Model' / 'GroundWaterTransport' / 'gwt1dspidm.f90', 'Model' / 'GroundWaterTransport' / 'gwt1fmi1.f90', 'Model' / 'GroundWaterTransport' / 'gwt1ic1.f90', + 'Model' / 'GroundWaterTransport' / 'gwt1idm.f90', 'Model' / 'GroundWaterTransport' / 'gwt1ist1.f90', 'Model' / 'GroundWaterTransport' / 'gwt1lkt1.f90', 'Model' / 'GroundWaterTransport' / 'gwt1mst1.f90', @@ -142,15 +147,16 @@ modflow_sources = files( 'Utilities' / 'ArrayRead' / 'Integer1dReader.f90', 'Utilities' / 'ArrayRead' / 'Integer2dReader.f90', 'Utilities' / 'ArrayRead' / 'LayeredArrayReader.f90', - 'Utilities' / 'Idm' / 'IdmDfnSelectorUtils.f90', + 'Utilities' / 'Idm' / 'DefinitionSelect.f90', 'Utilities' / 'Idm' / 'IdmLogger.f90', - 'Utilities' / 'Idm' / 'IdmMf6FileLoader.f90', 'Utilities' / 'Idm' / 'IdmSimulation.f90', - 'Utilities' / 'Idm' / 'ModflowInput.f90', 'Utilities' / 'Idm' / 'InputDefinition.f90', - 'Utilities' / 'Idm' / 'LoadMf6FileType.f90', - 'Utilities' / 'Idm' / 'StructArray.f90', - 'Utilities' / 'Idm' / 'StructVector.f90', + 'Utilities' / 'Idm' / 'ModelPackageInputs.f90', + 'Utilities' / 'Idm' / 'ModflowInput.f90', + 'Utilities' / 'Idm' / 'mf6blockfile' / 'IdmMf6File.f90', + 'Utilities' / 'Idm' / 'mf6blockfile' / 'LoadMf6File.f90', + 'Utilities' / 'Idm' / 'mf6blockfile' / 'StructArray.f90', + 'Utilities' / 'Idm' / 'mf6blockfile' / 'StructVector.f90', 'Utilities' / 'Idm' / 'selector' / 'IdmDfnSelector.f90', 'Utilities' / 'Idm' / 'selector' / 'IdmGwfDfnSelector.f90', 'Utilities' / 'Idm' / 'selector' / 'IdmGwtDfnSelector.f90', @@ -204,7 +210,6 @@ modflow_sources = files( 'Utilities' / 'List.f90', 'Utilities' / 'ListReader.f90', 'Utilities' / 'Message.f90', - 'Utilities' / 'NameFile.f90', 'Utilities' / 'OpenSpec.f90', 'Utilities' / 'PackageBudget.f90', 'Utilities' / 'Sim.f90', diff --git a/src/mf6core.f90 b/src/mf6core.f90 index 4f69764d51c..2851b134e7a 100644 --- a/src/mf6core.f90 +++ b/src/mf6core.f90 @@ -260,11 +260,44 @@ end subroutine create_lstfile !! !< subroutine static_input_load() - use IdmSimulationModule, only: simnam_load - ! - ! -- load input context + ! -- modules + use ConstantsModule, only: LENMEMPATH + use SimVariablesModule, only: simulation_mode, proc_id, iout + use IdmSimulationModule, only: simnam_load, load_models + use MemoryHelperModule, only: create_mem_path + use MemoryManagerModule, only: mem_setptr + use SimVariablesModule, only: idm_context + ! -- dummy + ! -- locals + character(len=LENMEMPATH) :: input_mempath + integer(I4B), dimension(:), allocatable :: model_loadmask + integer(I4B), pointer :: nummodels => null() + ! + ! -- load simnam input context call simnam_load() ! + ! -- allocate model load mask + input_mempath = create_mem_path(component='SIM', context=idm_context) + call mem_setptr(nummodels, 'NUMMODELS', input_mempath) + allocate (model_loadmask(nummodels)) + ! + ! -- initialize mask + model_loadmask = 0 + ! + ! -- set mask + if (simulation_mode == 'PARALLEL') then + ! TODO under development + model_loadmask(proc_id + 1) = 1 + else + model_loadmask = 1 + end if + ! + ! -- load selected models + call load_models(model_loadmask, iout) + ! + ! -- deallocate mask + deallocate (model_loadmask) + ! ! -- return return end subroutine static_input_load diff --git a/utils/idmloader/scripts/dfn2f90.py b/utils/idmloader/scripts/dfn2f90.py index 642be37abb2..cd375a5c24d 100644 --- a/utils/idmloader/scripts/dfn2f90.py +++ b/utils/idmloader/scripts/dfn2f90.py @@ -820,10 +820,30 @@ def _write_master_integration(self, fh=None): Path("../../../doc/mf6io/mf6ivar/dfn", "gwf-npf.dfn"), Path("../../../src/Model/GroundWaterFlow", "gwf3npf8idm.f90"), ], + [ + Path("../../../doc/mf6io/mf6ivar/dfn", "gwt-dis.dfn"), + Path("../../../src/Model/GroundWaterTransport", "gwt1dis1idm.f90"), + ], + [ + Path("../../../doc/mf6io/mf6ivar/dfn", "gwt-disu.dfn"), + Path("../../../src/Model/GroundWaterTransport", "gwt1disu1idm.f90"), + ], + [ + Path("../../../doc/mf6io/mf6ivar/dfn", "gwt-disv.dfn"), + Path("../../../src/Model/GroundWaterTransport", "gwt1disv1idm.f90"), + ], [ Path("../../../doc/mf6io/mf6ivar/dfn", "gwt-dsp.dfn"), Path("../../../src/Model/GroundWaterTransport", "gwt1dspidm.f90"), ], + [ + Path("../../../doc/mf6io/mf6ivar/dfn", "gwf-nam.dfn"), + Path("../../../src/Model/GroundWaterFlow", "gwf3idm.f90"), + ], + [ + Path("../../../doc/mf6io/mf6ivar/dfn", "gwt-nam.dfn"), + Path("../../../src/Model/GroundWaterTransport", "gwt1idm.f90"), + ], [ Path("../../../doc/mf6io/mf6ivar/dfn", "sim-nam.dfn"), Path("../../../src", "simnamidm.f90"), diff --git a/utils/mf5to6/make/makefile b/utils/mf5to6/make/makefile index 640aac22dea..e1c0b88e1e3 100644 --- a/utils/mf5to6/make/makefile +++ b/utils/mf5to6/make/makefile @@ -5,10 +5,10 @@ include ./makedefaults # Define the source file directories SOURCEDIR1=../src -SOURCEDIR2=../src/NWT -SOURCEDIR3=../src/LGR -SOURCEDIR4=../src/Preproc -SOURCEDIR5=../src/MF2005 +SOURCEDIR2=../src/LGR +SOURCEDIR3=../src/MF2005 +SOURCEDIR4=../src/NWT +SOURCEDIR5=../src/Preproc SOURCEDIR6=../../../src/Utilities/Memory SOURCEDIR7=../../../src/Utilities/TimeSeries SOURCEDIR8=../../../src/Utilities From 04fb58dca23e6831d5ade6cbc491f2aac7745c9e Mon Sep 17 00:00:00 2001 From: mjr-deltares <45555666+mjr-deltares@users.noreply.github.com> Date: Fri, 31 Mar 2023 11:52:34 +0200 Subject: [PATCH 058/123] feat(par): reduce communication between domains (#1188) * mrege interface maps over connections * adding hooks/structure for reduction of data in mpi traffic * - exchange index sets over mpi - reduced mpi messages by only communicating interface data (instead of full grid) - parallel tests are green * - fixed mem leak * - cleanup stale routine - check on model id in interfacemap, can be empty * - add nitermax to petsc solver * add more parallel test cases * Test case 3 is working now!! * refined parallel test cases * fprettify * new makefile * update makefile after conflict * fix: idm model mask * fprettification --- autotest/test_par_gwf01.py | 10 +- autotest/test_par_gwf02.py | 6 +- autotest/test_par_gwf03.py | 265 +++++++++++++ make/makefile | 414 ++++++++++---------- pymake/makefile | 301 ++++++++------- src/Distributed/InterfaceMap.f90 | 131 +++++-- src/Distributed/MappedMemory.f90 | 20 + src/Distributed/Mapper.f90 | 148 ++++--- src/Distributed/MpiMessageBuilder.f90 | 445 +++++++++++++++++++--- src/Distributed/MpiRouter.f90 | 121 +++++- src/Distributed/VirtualBase.f90 | 50 ++- src/Distributed/VirtualDataContainer.f90 | 102 ++++- src/Distributed/VirtualDataManager.f90 | 56 ++- src/Distributed/VirtualGwfModel.f90 | 2 +- src/Distributed/VirtualSolution.f90 | 13 +- src/Model/Connection/GridConnection.f90 | 28 +- src/Model/Connection/GwfGwfConnection.f90 | 2 +- src/Model/Connection/GwtGwtConnection.f90 | 2 +- src/Solution/ExplicitSolution.f90 | 2 +- src/Solution/NumericalSolution.f90 | 11 +- src/Solution/PETSc/PetscConvergence.F90 | 13 +- src/Solution/PETSc/PetscSolver.F90 | 1 + src/Utilities/Memory/MemoryManager.f90 | 23 +- src/mf6core.f90 | 4 +- 24 files changed, 1569 insertions(+), 601 deletions(-) create mode 100644 autotest/test_par_gwf03.py diff --git a/autotest/test_par_gwf01.py b/autotest/test_par_gwf01.py index 92163a0a96c..84cb1fb348b 100644 --- a/autotest/test_par_gwf01.py +++ b/autotest/test_par_gwf01.py @@ -9,9 +9,9 @@ # Test for parallel MODFLOW running on two cpus. # It contains two coupled models with # -# (nlay,nrow,ncol) = (1,1,5), -# (nlay,nrow,ncol) = (1,5,5), -# (nlay,nrow,ncol) = (5,5,5), +# 1d: (nlay,nrow,ncol) = (1,1,5), +# 2d: (nlay,nrow,ncol) = (1,5,5), +# 3d: (nlay,nrow,ncol) = (5,5,5), # # constant head boundaries left=1.0, right=10.0. # The result should be a uniform flow field. @@ -192,11 +192,11 @@ def get_model(idx, dir): def build_petsc_db(exdir): petsc_db_file = os.path.join(exdir, ".petscrc") with open(petsc_db_file, 'w') as petsc_file: - petsc_file.write("-sub_ksp_type bcgs\n") + petsc_file.write("-ksp_type cg\n") + petsc_file.write("-pc_type bjacobi\n") petsc_file.write("-sub_pc_type ilu\n") petsc_file.write("-dvclose 10e-7\n") petsc_file.write("-options_left no\n") - #petsc_file.write("-wait_dbg\n") def build_model(idx, exdir): sim = get_model(idx, exdir) diff --git a/autotest/test_par_gwf02.py b/autotest/test_par_gwf02.py index c8bad7421db..d8681868dae 100644 --- a/autotest/test_par_gwf02.py +++ b/autotest/test_par_gwf02.py @@ -147,7 +147,7 @@ def add_model(sim, ix, iy, nr_models_x, nr_models_y): if ix == 0 and iy == 0: # add SW corner BC - sw_chd = [[(0, 0, 0), cst_head_south_west]] + sw_chd = [[(0, nrow - 1, 0), cst_head_south_west]] chd_spd_sw = {0: sw_chd} chd = flopy.mf6.ModflowGwfchd(gwf, stress_period_data=chd_spd_sw) @@ -217,11 +217,11 @@ def add_exchange_south_north(sim, name_south, name_north): def build_petsc_db(exdir): petsc_db_file = os.path.join(exdir, ".petscrc") with open(petsc_db_file, 'w') as petsc_file: - petsc_file.write("-sub_ksp_type bcgs\n") + petsc_file.write("-ksp_type cg\n") + petsc_file.write("-pc_type bjacobi\n") petsc_file.write("-sub_pc_type ilu\n") petsc_file.write(f"-dvclose {Decimal(hclose):.2E}\n") petsc_file.write("-options_left no\n") - #petsc_file.write("-wait_dbg\n") def build_model(idx, exdir): sim = get_simulation(idx, exdir) diff --git a/autotest/test_par_gwf03.py b/autotest/test_par_gwf03.py new file mode 100644 index 00000000000..83a25d04a02 --- /dev/null +++ b/autotest/test_par_gwf03.py @@ -0,0 +1,265 @@ +import os + +import flopy +import numpy as np +from decimal import Decimal +import pytest +from framework import TestFramework +from simulation import TestSimulation + +# Scaling parallel MODFLOW running a simple +# (multi-)model setup on different partitionings +# with constant head set at the lower-left corner. +# +# a: 1 cpus, 1 model +# b: 1 cpus, 4 models +# c: 4 cpus, 4 models +# +# The test is that for all configurations, the head +# converges globally to the specified boundary value. +# In general, the test can be used to compare parallel +# vs. serial behavior on an identical problem. + +ex = ["par_gwf03-a", "par_gwf03-b", "par_gwf03-c"] +ncpus = [1, 1, 4] +domain_grid = [(1, 1), (2, 2), (2, 2)] +dis_shape = [(2, 100, 100), (2, 50, 50), (2, 50, 50)] + +delr = 100.0 +delc = 100.0 +head_initial = -1.0 +cst_head_south_west = 435.0 +hclose = 1.0e-8 + + +def get_model_name(ix, iy): + return f"model-{ix}-{iy}" + + +def get_simulation(idx, dir): + + name = ex[idx] + nr_models_x = domain_grid[idx][0] + nr_models_y = domain_grid[idx][1] + + nlay = dis_shape[idx][0] + nrow = dis_shape[idx][1] + ncol = dis_shape[idx][2] + + # parameters and spd + # tdis + nper = 1 + tdis_rc = [] + for i in range(nper): + tdis_rc.append((1.0, 1, 1)) + + # solver data + nouter, ninner = 100, 300 + rclose, relax = 1e-3, 0.97 + + sim = flopy.mf6.MFSimulation( + sim_name=name, version="mf6", exe_name="mf6", sim_ws=dir, + ) + + tdis = flopy.mf6.ModflowTdis( + sim, time_units="DAYS", nper=nper, perioddata=tdis_rc + ) + + ims = flopy.mf6.ModflowIms( + sim, + print_option="ALL", + outer_dvclose=hclose, + outer_maximum=nouter, + inner_maximum=ninner, + inner_dvclose=hclose, + rcloserecord=rclose, + linear_acceleration="CG", + relaxation_factor=0.0, # turn this off for comparison + ) + + # create models (and exchanges) + for ix in range(nr_models_x): + for iy in range(nr_models_y): + add_model(sim, ix, iy, nr_models_x, nr_models_y, nlay, nrow, ncol) + + # add exchanges from west to east + for iy in range(nr_models_y): + for ix in range(nr_models_x - 1): + name_west = get_model_name(ix, iy) + name_east = get_model_name(ix + 1, iy) + add_exchange_west_east(sim, name_west, name_east, nlay, nrow, ncol) + + # add exchange from south to north + for ix in range(nr_models_x): + for iy in range(nr_models_y -1 ): + name_south = get_model_name(ix, iy) + name_north = get_model_name(ix, iy + 1) + add_exchange_south_north(sim, name_south, name_north, nlay, nrow, ncol) + + return sim + +def add_model(sim, ix, iy, nr_models_x, nr_models_y, nlay, nrow, ncol): + + # model spatial discretization + shift_x = ix * ncol * delr + shift_y = iy * nrow * delc + model_name = get_model_name(ix, iy) + + # top/bot of the aquifer + tops = [-100.0*i for i in range(nlay + 1)] + + # hydraulic conductivity + k11 = 10.0 + + # initial head + h_start = head_initial + + gwf = flopy.mf6.ModflowGwf(sim, modelname=model_name, save_flows=True) + dis = flopy.mf6.ModflowGwfdis( + gwf, + nlay=nlay, + nrow=nrow, + ncol=ncol, + delr=delr, + delc=delc, + top=tops[0], + botm=tops[1:nlay+1], + xorigin=shift_x, + yorigin=shift_y + ) + ic = flopy.mf6.ModflowGwfic(gwf, strt=h_start) + npf = flopy.mf6.ModflowGwfnpf( + gwf, + save_specific_discharge=True, + save_flows=True, + icelltype=0, + k=k11, + ) + oc = flopy.mf6.ModflowGwfoc( + gwf, + head_filerecord=f"{model_name}.hds", + budget_filerecord=f"{model_name}.cbc", + headprintrecord=[("COLUMNS", 10, "WIDTH", 15, "DIGITS", 6, "GENERAL")], + saverecord=[("HEAD", "LAST"), ("BUDGET", "LAST")], + ) + + if ix == 0 and iy == 0: + # add SW corner BC + sw_chd = [[(0, nrow - 1, 0), cst_head_south_west]] + chd_spd_sw = {0: sw_chd} + chd = flopy.mf6.ModflowGwfchd(gwf, stress_period_data=chd_spd_sw) + +def add_exchange_west_east(sim, name_west, name_east, nlay, nrow, ncol): + + exg_filename = f"we_{name_west}_{name_east}.gwfgwf" + # exchangedata + angldegx = 0.0 + cdist = delr + gwfgwf_data = [ + [ + (ilay, irow, ncol - 1), + (ilay, irow, 0), + 1, + delr / 2.0, + delr / 2.0, + delc, + angldegx, + cdist, + ] + for irow in range(nrow) + for ilay in range(nlay) + ] + gwfgwf = flopy.mf6.ModflowGwfgwf( + sim, + exgtype="GWF6-GWF6", + nexg=len(gwfgwf_data), + exgmnamea=name_west, + exgmnameb=name_east, + exchangedata=gwfgwf_data, + auxiliary=["ANGLDEGX", "CDIST"], + filename=exg_filename + ) + +def add_exchange_south_north(sim, name_south, name_north, nlay, nrow, ncol): + + exg_filename = f"sn_{name_south}_{name_north}.gwfgwf" + + # exchangedata + angldegx = 90.0 + cdist = delc + gwfgwf_data = [ + [ + (ilay, 0, icol), + (ilay, nrow-1, icol), + 1, + delc / 2.0, + delc / 2.0, + delr, + angldegx, + cdist, + ] + for icol in range(ncol) + for ilay in range(nlay) + ] + gwfgwf = flopy.mf6.ModflowGwfgwf( + sim, + exgtype="GWF6-GWF6", + nexg=len(gwfgwf_data), + exgmnamea=name_south, + exgmnameb=name_north, + exchangedata=gwfgwf_data, + auxiliary=["ANGLDEGX", "CDIST"], + filename=exg_filename + ) + +def build_petsc_db(idx, exdir): + np = ncpus[idx] + petsc_db_file = os.path.join(exdir, ".petscrc") + with open(petsc_db_file, 'w') as petsc_file: + if np == 1: + petsc_file.write("-ksp_type cg\n") + petsc_file.write("-pc_type ilu\n") + petsc_file.write("-pc_factor_levels 2\n") + petsc_file.write(f"-dvclose {Decimal(hclose):.2E}\n") + petsc_file.write(f"-nitermax {500}\n") + petsc_file.write("-options_left no\n") + else: + petsc_file.write("-ksp_type cg\n") + petsc_file.write("-pc_type bjacobi\n") + petsc_file.write("-sub_pc_type ilu\n") + petsc_file.write("-sub_pc_factor_levels 2\n") + petsc_file.write(f"-dvclose {Decimal(hclose):.2E}\n") + petsc_file.write(f"-nitermax {500}\n") + petsc_file.write("-options_left no\n") + +def build_model(idx, exdir): + sim = get_simulation(idx, exdir) + build_petsc_db(idx, exdir) + return sim, None + +def eval_model(sim): + mf6_sim = flopy.mf6.MFSimulation.load(sim_ws=sim.simpath) + for mname in mf6_sim.model_names: + m = mf6_sim.get_model(mname) + hds = m.output.head().get_data().flatten() + hds_compare = cst_head_south_west*np.ones_like(hds) + assert np.allclose(hds, hds_compare, rtol=1.0e-6, atol=0.0001) + + +@pytest.mark.parallel +@pytest.mark.parametrize( + "idx, name", + list(enumerate(ex)), +) +def test_mf6model(idx, name, function_tmpdir, targets): + np = ncpus[idx] + test = TestFramework() + test.build(build_model, idx, str(function_tmpdir)) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_model, + idxsim=0, make_comparison=False, + parallel=True, ncpus=np, + ), + str(function_tmpdir), + ) diff --git a/make/makefile b/make/makefile index 02743cd0f5e..62617b14b8d 100644 --- a/make/makefile +++ b/make/makefile @@ -5,35 +5,35 @@ include ./makedefaults # Define the source file directories SOURCEDIR1=../src -SOURCEDIR2=../src/Distributed -SOURCEDIR3=../src/Exchange -SOURCEDIR4=../src/Model -SOURCEDIR5=../src/Model/Connection -SOURCEDIR6=../src/Model/Geometry -SOURCEDIR7=../src/Model/GroundWaterFlow -SOURCEDIR8=../src/Model/GroundWaterTransport -SOURCEDIR9=../src/Model/ModelUtilities +SOURCEDIR2=../src/Exchange +SOURCEDIR3=../src/Model +SOURCEDIR4=../src/Model/Geometry +SOURCEDIR5=../src/Model/ModelUtilities +SOURCEDIR6=../src/Model/Connection +SOURCEDIR7=../src/Model/GroundWaterTransport +SOURCEDIR8=../src/Model/GroundWaterFlow +SOURCEDIR9=../src/Distributed SOURCEDIR10=../src/Solution -SOURCEDIR11=../src/Solution/LinearMethods -SOURCEDIR12=../src/Solution/PETSc +SOURCEDIR11=../src/Solution/PETSc +SOURCEDIR12=../src/Solution/LinearMethods SOURCEDIR13=../src/Timing SOURCEDIR14=../src/Utilities -SOURCEDIR15=../src/Utilities/ArrayRead -SOURCEDIR16=../src/Utilities/Idm -SOURCEDIR17=../src/Utilities/Idm/mf6blockfile -SOURCEDIR18=../src/Utilities/Idm/selector -SOURCEDIR19=../src/Utilities/Libraries +SOURCEDIR15=../src/Utilities/TimeSeries +SOURCEDIR16=../src/Utilities/Libraries +SOURCEDIR17=../src/Utilities/Libraries/rcm +SOURCEDIR18=../src/Utilities/Libraries/sparsekit +SOURCEDIR19=../src/Utilities/Libraries/sparskit2 SOURCEDIR20=../src/Utilities/Libraries/blas SOURCEDIR21=../src/Utilities/Libraries/daglib -SOURCEDIR22=../src/Utilities/Libraries/rcm -SOURCEDIR23=../src/Utilities/Libraries/sparsekit -SOURCEDIR24=../src/Utilities/Libraries/sparskit2 +SOURCEDIR22=../src/Utilities/Idm +SOURCEDIR23=../src/Utilities/Idm/selector +SOURCEDIR24=../src/Utilities/Idm/mf6blockfile SOURCEDIR25=../src/Utilities/Matrix -SOURCEDIR26=../src/Utilities/Memory +SOURCEDIR26=../src/Utilities/Vector SOURCEDIR27=../src/Utilities/Observation SOURCEDIR28=../src/Utilities/OutputControl -SOURCEDIR29=../src/Utilities/TimeSeries -SOURCEDIR30=../src/Utilities/Vector +SOURCEDIR29=../src/Utilities/Memory +SOURCEDIR30=../src/Utilities/ArrayRead VPATH = \ ${SOURCEDIR1} \ @@ -70,235 +70,235 @@ ${SOURCEDIR30} .SUFFIXES: .f90 .F90 .o OBJECTS = \ +$(OBJDIR)/ilut.o \ $(OBJDIR)/kind.o \ +$(OBJDIR)/VectorBase.o \ +$(OBJDIR)/IdmLogger.o \ +$(OBJDIR)/BaseGeometry.o \ +$(OBJDIR)/InputDefinition.o \ +$(OBJDIR)/SimStages.o \ +$(OBJDIR)/GwtDspOptions.o \ +$(OBJDIR)/gwf3npf8idm.o \ +$(OBJDIR)/Sparse.o \ +$(OBJDIR)/GwtAdvOptions.o \ +$(OBJDIR)/gwt1disv1idm.o \ +$(OBJDIR)/gwt1dis1idm.o \ +$(OBJDIR)/gwf3dis8idm.o \ +$(OBJDIR)/simnamidm.o \ +$(OBJDIR)/gwf3disu8idm.o \ +$(OBJDIR)/CsrUtils.o \ +$(OBJDIR)/gwt1idm.o \ +$(OBJDIR)/gwf3disv8idm.o \ +$(OBJDIR)/blas1_d.o \ +$(OBJDIR)/gwt1dspidm.o \ +$(OBJDIR)/CharString.o \ +$(OBJDIR)/OpenSpec.o \ +$(OBJDIR)/dag_module.o \ +$(OBJDIR)/ims8reordering.o \ $(OBJDIR)/Constants.o \ +$(OBJDIR)/rcm.o \ +$(OBJDIR)/HashTable.o \ +$(OBJDIR)/sparsekit.o \ +$(OBJDIR)/gwt1disu1idm.o \ +$(OBJDIR)/gwf3idm.o \ +$(OBJDIR)/SfrCrossSectionUtils.o \ +$(OBJDIR)/MatrixBase.o \ +$(OBJDIR)/compilerversion.o \ +$(OBJDIR)/defmacro.o \ $(OBJDIR)/SimVariables.o \ +$(OBJDIR)/SmoothingFunctions.o \ +$(OBJDIR)/GwfVscInputData.o \ +$(OBJDIR)/GwfStorageUtils.o \ +$(OBJDIR)/Xt3dAlgorithm.o \ +$(OBJDIR)/GwfNpfOptions.o \ +$(OBJDIR)/GwfBuyInputData.o \ +$(OBJDIR)/ims8misc.o \ +$(OBJDIR)/LinearSolverBase.o \ $(OBJDIR)/genericutils.o \ -$(OBJDIR)/compilerversion.o \ $(OBJDIR)/ArrayHandlers.o \ +$(OBJDIR)/IndexMap.o \ $(OBJDIR)/version.o \ +$(OBJDIR)/InterfaceMap.o \ $(OBJDIR)/Message.o \ -$(OBJDIR)/defmacro.o \ +$(OBJDIR)/List.o \ $(OBJDIR)/Sim.o \ -$(OBJDIR)/OpenSpec.o \ +$(OBJDIR)/Timer.o \ +$(OBJDIR)/MemoryHelper.o \ +$(OBJDIR)/sort.o \ +$(OBJDIR)/StringList.o \ +$(OBJDIR)/mf6lists.o \ +$(OBJDIR)/IdmGwfDfnSelector.o \ +$(OBJDIR)/IdmGwtDfnSelector.o \ +$(OBJDIR)/ObsOutput.o \ +$(OBJDIR)/Budget.o \ +$(OBJDIR)/IdmSimDfnSelector.o \ +$(OBJDIR)/DistributedVariable.o \ $(OBJDIR)/InputOutput.o \ +$(OBJDIR)/VirtualDataLists.o \ +$(OBJDIR)/DisvGeom.o \ +$(OBJDIR)/Iunit.o \ +$(OBJDIR)/TimeSeriesRecord.o \ $(OBJDIR)/TableTerm.o \ +$(OBJDIR)/HeadFileReader.o \ +$(OBJDIR)/PrintSaveManager.o \ +$(OBJDIR)/IdmDfnSelector.o \ +$(OBJDIR)/DefinitionSelect.o \ +$(OBJDIR)/ArrayReaders.o \ +$(OBJDIR)/comarg.o \ +$(OBJDIR)/STLVecInt.o \ +$(OBJDIR)/BlockParser.o \ +$(OBJDIR)/CircularGeometry.o \ +$(OBJDIR)/BudgetFileReader.o \ +$(OBJDIR)/RectangularGeometry.o \ +$(OBJDIR)/ObsOutputList.o \ +$(OBJDIR)/ims8base.o \ +$(OBJDIR)/TimeSeries.o \ +$(OBJDIR)/TimeSeriesFileList.o \ +$(OBJDIR)/ArrayReaderBase.o \ +$(OBJDIR)/ModflowInput.o \ +$(OBJDIR)/TimeSeriesLink.o \ +$(OBJDIR)/Double1dReader.o \ +$(OBJDIR)/Double2dReader.o \ $(OBJDIR)/Table.o \ -$(OBJDIR)/MemoryHelper.o \ -$(OBJDIR)/CharString.o \ +$(OBJDIR)/Integer2dReader.o \ +$(OBJDIR)/StructVector.o \ +$(OBJDIR)/SfrCrossSectionManager.o \ +$(OBJDIR)/ListReader.o \ $(OBJDIR)/Memory.o \ -$(OBJDIR)/List.o \ $(OBJDIR)/MemoryList.o \ -$(OBJDIR)/TimeSeriesRecord.o \ -$(OBJDIR)/BlockParser.o \ $(OBJDIR)/MemoryManager.o \ -$(OBJDIR)/TimeSeries.o \ $(OBJDIR)/ats.o \ -$(OBJDIR)/TimeSeriesLink.o \ -$(OBJDIR)/TimeSeriesFileList.o \ +$(OBJDIR)/Integer1dReader.o \ +$(OBJDIR)/StructArray.o \ +$(OBJDIR)/MemorySetHandler.o \ +$(OBJDIR)/MappedMemory.o \ +$(OBJDIR)/ModelPackageInputs.o \ +$(OBJDIR)/BaseModel.o \ +$(OBJDIR)/GwfMvrPeriodData.o \ +$(OBJDIR)/ims8linear.o \ +$(OBJDIR)/MemoryManagerExt.o \ +$(OBJDIR)/Connections.o \ +$(OBJDIR)/SeqVector.o \ $(OBJDIR)/tdis.o \ -$(OBJDIR)/HashTable.o \ -$(OBJDIR)/VectorBase.o \ -$(OBJDIR)/Sparse.o \ -$(OBJDIR)/DisvGeom.o \ -$(OBJDIR)/ArrayReaders.o \ +$(OBJDIR)/PackageMover.o \ +$(OBJDIR)/PackageBudget.o \ $(OBJDIR)/TimeSeriesManager.o \ -$(OBJDIR)/SmoothingFunctions.o \ -$(OBJDIR)/MatrixBase.o \ -$(OBJDIR)/ListReader.o \ -$(OBJDIR)/Connections.o \ +$(OBJDIR)/Mover.o \ +$(OBJDIR)/VirtualBase.o \ +$(OBJDIR)/VirtualDataContainer.o \ +$(OBJDIR)/BaseExchange.o \ +$(OBJDIR)/SparseMatrix.o \ $(OBJDIR)/DiscretizationBase.o \ +$(OBJDIR)/UzfCellGroup.o \ +$(OBJDIR)/BudgetTerm.o \ +$(OBJDIR)/Observe.o \ +$(OBJDIR)/OutputControlData.o \ +$(OBJDIR)/gwf3dis8.o \ +$(OBJDIR)/LayeredArrayReader.o \ $(OBJDIR)/TimeArray.o \ -$(OBJDIR)/ObsOutput.o \ +$(OBJDIR)/NumericalPackage.o \ +$(OBJDIR)/LoadMf6File.o \ +$(OBJDIR)/ExplicitModel.o \ +$(OBJDIR)/BaseSolution.o \ +$(OBJDIR)/gwf3disu8.o \ $(OBJDIR)/TimeArraySeries.o \ -$(OBJDIR)/ObsOutputList.o \ -$(OBJDIR)/Observe.o \ -$(OBJDIR)/TimeArraySeriesLink.o \ -$(OBJDIR)/ObsUtility.o \ +$(OBJDIR)/SolutionGroup.o \ $(OBJDIR)/ObsContainer.o \ -$(OBJDIR)/BudgetFileReader.o \ +$(OBJDIR)/ExplicitSolution.o \ +$(OBJDIR)/TimeArraySeriesLink.o \ +$(OBJDIR)/Xt3dInterface.o \ +$(OBJDIR)/gwf3disv8.o \ +$(OBJDIR)/ImsLinearSolver.o \ $(OBJDIR)/TimeArraySeriesManager.o \ -$(OBJDIR)/PackageMover.o \ -$(OBJDIR)/Obs3.o \ -$(OBJDIR)/NumericalPackage.o \ -$(OBJDIR)/Budget.o \ -$(OBJDIR)/sort.o \ -$(OBJDIR)/SfrCrossSectionUtils.o \ -$(OBJDIR)/BudgetTerm.o \ -$(OBJDIR)/VirtualBase.o \ -$(OBJDIR)/STLVecInt.o \ -$(OBJDIR)/BoundaryPackage.o \ -$(OBJDIR)/BaseModel.o \ -$(OBJDIR)/InputDefinition.o \ -$(OBJDIR)/SfrCrossSectionManager.o \ -$(OBJDIR)/dag_module.o \ $(OBJDIR)/BudgetObject.o \ -$(OBJDIR)/VirtualDataLists.o \ -$(OBJDIR)/VirtualDataContainer.o \ -$(OBJDIR)/SimStages.o \ -$(OBJDIR)/NumericalModel.o \ -$(OBJDIR)/simnamidm.o \ -$(OBJDIR)/gwt1idm.o \ -$(OBJDIR)/gwt1dspidm.o \ -$(OBJDIR)/gwt1disv1idm.o \ -$(OBJDIR)/gwt1disu1idm.o \ -$(OBJDIR)/gwt1dis1idm.o \ -$(OBJDIR)/gwf3npf8idm.o \ -$(OBJDIR)/gwf3idm.o \ -$(OBJDIR)/gwf3disv8idm.o \ -$(OBJDIR)/gwf3disu8idm.o \ -$(OBJDIR)/gwf3dis8idm.o \ -$(OBJDIR)/PackageBudget.o \ -$(OBJDIR)/HeadFileReader.o \ -$(OBJDIR)/PrintSaveManager.o \ -$(OBJDIR)/Xt3dAlgorithm.o \ +$(OBJDIR)/ObsUtility.o \ $(OBJDIR)/gwf3tvbase8.o \ -$(OBJDIR)/gwf3sfr8.o \ -$(OBJDIR)/gwf3riv8.o \ -$(OBJDIR)/gwf3maw8.o \ -$(OBJDIR)/mf6lists.o \ -$(OBJDIR)/gwf3lak8.o \ -$(OBJDIR)/GwfVscInputData.o \ -$(OBJDIR)/gwf3ghb8.o \ -$(OBJDIR)/gwf3drn8.o \ -$(OBJDIR)/VirtualModel.o \ -$(OBJDIR)/BaseExchange.o \ -$(OBJDIR)/IdmSimDfnSelector.o \ -$(OBJDIR)/IdmGwtDfnSelector.o \ -$(OBJDIR)/IdmGwfDfnSelector.o \ -$(OBJDIR)/UzfCellGroup.o \ -$(OBJDIR)/gwt1fmi1.o \ -$(OBJDIR)/OutputControlData.o \ +$(OBJDIR)/gwf3tvs8.o \ +$(OBJDIR)/IdmMf6File.o \ +$(OBJDIR)/OutputControl.o \ +$(OBJDIR)/LinearSolverFactory.o \ $(OBJDIR)/gwf3ic8.o \ -$(OBJDIR)/Xt3dInterface.o \ +$(OBJDIR)/Obs3.o \ $(OBJDIR)/gwf3tvk8.o \ -$(OBJDIR)/MemoryManagerExt.o \ -$(OBJDIR)/gwf3vsc8.o \ -$(OBJDIR)/GwfNpfOptions.o \ -$(OBJDIR)/SeqVector.o \ -$(OBJDIR)/IndexMap.o \ -$(OBJDIR)/CellWithNbrs.o \ -$(OBJDIR)/NumericalExchange.o \ -$(OBJDIR)/IdmDfnSelector.o \ -$(OBJDIR)/gwf3uzf8.o \ -$(OBJDIR)/gwt1apt1.o \ $(OBJDIR)/GwtSpc.o \ -$(OBJDIR)/OutputControl.o \ -$(OBJDIR)/gwt1ic1.o \ -$(OBJDIR)/gwt1mst1.o \ -$(OBJDIR)/GwtDspOptions.o \ -$(OBJDIR)/gwf3npf8.o \ -$(OBJDIR)/GwtAdvOptions.o \ -$(OBJDIR)/gwf3tvs8.o \ -$(OBJDIR)/GwfStorageUtils.o \ -$(OBJDIR)/Mover.o \ -$(OBJDIR)/GwfMvrPeriodData.o \ -$(OBJDIR)/ims8misc.o \ -$(OBJDIR)/GwfBuyInputData.o \ -$(OBJDIR)/VirtualSolution.o \ -$(OBJDIR)/SparseMatrix.o \ -$(OBJDIR)/LinearSolverBase.o \ -$(OBJDIR)/ims8reordering.o \ -$(OBJDIR)/ArrayReaderBase.o \ -$(OBJDIR)/VirtualExchange.o \ -$(OBJDIR)/InterfaceMap.o \ -$(OBJDIR)/gwf3disu8.o \ -$(OBJDIR)/GridSorting.o \ -$(OBJDIR)/DisConnExchange.o \ -$(OBJDIR)/CsrUtils.o \ -$(OBJDIR)/TransportModel.o \ -$(OBJDIR)/ModelPackageInputs.o \ -$(OBJDIR)/gwt1uzt1.o \ -$(OBJDIR)/gwt1ssm1.o \ -$(OBJDIR)/gwt1src1.o \ -$(OBJDIR)/gwt1sft1.o \ +$(OBJDIR)/IdmSimulation.o \ $(OBJDIR)/gwt1oc1.o \ +$(OBJDIR)/gwf3mvr8.o \ +$(OBJDIR)/gwt1ic1.o \ $(OBJDIR)/gwt1obs1.o \ +$(OBJDIR)/gwf3obs8.o \ +$(OBJDIR)/gwf3oc8.o \ +$(OBJDIR)/gwf3sto8.o \ +$(OBJDIR)/BoundaryPackage.o \ +$(OBJDIR)/gwf3csub8.o \ +$(OBJDIR)/gwf3uzf8.o \ +$(OBJDIR)/gwt1cnc1.o \ +$(OBJDIR)/gwf3lak8.o \ +$(OBJDIR)/gwt1src1.o \ +$(OBJDIR)/gwf3maw8.o \ +$(OBJDIR)/gwt1fmi1.o \ +$(OBJDIR)/gwf3rch8.o \ +$(OBJDIR)/gwt1apt1.o \ +$(OBJDIR)/gwf3wel8.o \ +$(OBJDIR)/gwf3riv8.o \ +$(OBJDIR)/gwf3drn8.o \ $(OBJDIR)/gwt1mwt1.o \ -$(OBJDIR)/gwt1mvt1.o \ +$(OBJDIR)/gwf3sfr8.o \ +$(OBJDIR)/gwf3api8.o \ +$(OBJDIR)/gwf3evt8.o \ +$(OBJDIR)/NumericalModel.o \ +$(OBJDIR)/gwf3ghb8.o \ +$(OBJDIR)/gwf3chd8.o \ +$(OBJDIR)/gwt1uzt1.o \ +$(OBJDIR)/gwf3vsc8.o \ +$(OBJDIR)/NumericalExchange.o \ +$(OBJDIR)/gwt1ssm1.o \ +$(OBJDIR)/NumericalSolution.o \ +$(OBJDIR)/gwt1adv1.o \ $(OBJDIR)/gwt1lkt1.o \ +$(OBJDIR)/SolutionFactory.o \ +$(OBJDIR)/VirtualModel.o \ +$(OBJDIR)/gwt1sft1.o \ +$(OBJDIR)/gwt1mst1.o \ +$(OBJDIR)/VirtualSolution.o \ +$(OBJDIR)/VirtualGwfModel.o \ $(OBJDIR)/gwt1ist1.o \ +$(OBJDIR)/gwf3npf8.o \ +$(OBJDIR)/TransportModel.o \ +$(OBJDIR)/DisConnExchange.o \ +$(OBJDIR)/GhostNode.o \ +$(OBJDIR)/gwt1mvt1.o \ +$(OBJDIR)/VirtualExchange.o \ $(OBJDIR)/gwt1dsp.o \ -$(OBJDIR)/gwt1cnc1.o \ -$(OBJDIR)/gwt1adv1.o \ -$(OBJDIR)/gwf3disv8.o \ -$(OBJDIR)/gwf3dis8.o \ -$(OBJDIR)/gwf3api8.o \ -$(OBJDIR)/gwf3wel8.o \ -$(OBJDIR)/gwf3rch8.o \ -$(OBJDIR)/gwf3sto8.o \ -$(OBJDIR)/gwf3oc8.o \ -$(OBJDIR)/gwf3obs8.o \ -$(OBJDIR)/gwf3mvr8.o \ -$(OBJDIR)/gwf3hfb8.o \ -$(OBJDIR)/gwf3csub8.o \ $(OBJDIR)/gwf3buy8.o \ -$(OBJDIR)/GhostNode.o \ -$(OBJDIR)/gwf3evt8.o \ -$(OBJDIR)/gwf3chd8.o \ +$(OBJDIR)/VirtualGwfExchange.o \ +$(OBJDIR)/gwf3hfb8.o \ +$(OBJDIR)/VirtualGwtExchange.o \ +$(OBJDIR)/VirtualGwtModel.o \ +$(OBJDIR)/CellWithNbrs.o \ $(OBJDIR)/RouterBase.o \ -$(OBJDIR)/ImsLinearSolver.o \ -$(OBJDIR)/ims8base.o \ -$(OBJDIR)/Integer2dReader.o \ -$(OBJDIR)/GridConnection.o \ -$(OBJDIR)/DistributedVariable.o \ -$(OBJDIR)/gwt1.o \ $(OBJDIR)/gwf3.o \ -$(OBJDIR)/SerialRouter.o \ -$(OBJDIR)/Timer.o \ -$(OBJDIR)/LinearSolverFactory.o \ -$(OBJDIR)/ims8linear.o \ -$(OBJDIR)/BaseSolution.o \ -$(OBJDIR)/StructVector.o \ -$(OBJDIR)/IdmLogger.o \ -$(OBJDIR)/Integer1dReader.o \ -$(OBJDIR)/Double2dReader.o \ -$(OBJDIR)/Double1dReader.o \ -$(OBJDIR)/ExplicitModel.o \ -$(OBJDIR)/SpatialModelConnection.o \ -$(OBJDIR)/GwtInterfaceModel.o \ -$(OBJDIR)/GwtGwtExchange.o \ -$(OBJDIR)/GwfInterfaceModel.o \ +$(OBJDIR)/gwt1.o \ +$(OBJDIR)/GridSorting.o \ $(OBJDIR)/GwfGwfExchange.o \ +$(OBJDIR)/SerialRouter.o \ $(OBJDIR)/RouterFactory.o \ -$(OBJDIR)/NumericalSolution.o \ -$(OBJDIR)/MappedMemory.o \ -$(OBJDIR)/StructArray.o \ -$(OBJDIR)/ModflowInput.o \ -$(OBJDIR)/LayeredArrayReader.o \ -$(OBJDIR)/DefinitionSelect.o \ -$(OBJDIR)/ExplicitSolution.o \ -$(OBJDIR)/GwtGwtConnection.o \ -$(OBJDIR)/GwfGwfConnection.o \ -$(OBJDIR)/VirtualDataManager.o \ +$(OBJDIR)/GwtGwtExchange.o \ +$(OBJDIR)/GridConnection.o \ +$(OBJDIR)/SpatialModelConnection.o \ $(OBJDIR)/Mapper.o \ -$(OBJDIR)/LoadMf6File.o \ -$(OBJDIR)/VirtualGwtModel.o \ -$(OBJDIR)/VirtualGwtExchange.o \ -$(OBJDIR)/VirtualGwfModel.o \ -$(OBJDIR)/VirtualGwfExchange.o \ -$(OBJDIR)/SolutionGroup.o \ -$(OBJDIR)/SolutionFactory.o \ -$(OBJDIR)/GwfGwtExchange.o \ +$(OBJDIR)/VirtualDataManager.o \ +$(OBJDIR)/GwfInterfaceModel.o \ $(OBJDIR)/RunControl.o \ -$(OBJDIR)/IdmMf6File.o \ -$(OBJDIR)/SimulationCreate.o \ +$(OBJDIR)/GwtInterfaceModel.o \ $(OBJDIR)/RunControlFactory.o \ -$(OBJDIR)/IdmSimulation.o \ +$(OBJDIR)/GwfGwfConnection.o \ +$(OBJDIR)/GwtGwtConnection.o \ $(OBJDIR)/ConnectionBuilder.o \ -$(OBJDIR)/comarg.o \ +$(OBJDIR)/GwfGwtExchange.o \ +$(OBJDIR)/SimulationCreate.o \ $(OBJDIR)/mf6core.o \ -$(OBJDIR)/BaseGeometry.o \ -$(OBJDIR)/mf6.o \ -$(OBJDIR)/StringList.o \ -$(OBJDIR)/MemorySetHandler.o \ -$(OBJDIR)/ilut.o \ -$(OBJDIR)/sparsekit.o \ -$(OBJDIR)/rcm.o \ -$(OBJDIR)/blas1_d.o \ -$(OBJDIR)/Iunit.o \ -$(OBJDIR)/RectangularGeometry.o \ -$(OBJDIR)/CircularGeometry.o +$(OBJDIR)/mf6.o # Define the objects that make up the program $(PROGRAM) : $(OBJECTS) diff --git a/pymake/makefile b/pymake/makefile index 08ba032bdd5..62617b14b8d 100644 --- a/pymake/makefile +++ b/pymake/makefile @@ -26,12 +26,14 @@ SOURCEDIR19=../src/Utilities/Libraries/sparskit2 SOURCEDIR20=../src/Utilities/Libraries/blas SOURCEDIR21=../src/Utilities/Libraries/daglib SOURCEDIR22=../src/Utilities/Idm -SOURCEDIR23=../src/Utilities/Matrix -SOURCEDIR24=../src/Utilities/Vector -SOURCEDIR25=../src/Utilities/Observation -SOURCEDIR26=../src/Utilities/OutputControl -SOURCEDIR27=../src/Utilities/Memory -SOURCEDIR28=../src/Utilities/ArrayRead +SOURCEDIR23=../src/Utilities/Idm/selector +SOURCEDIR24=../src/Utilities/Idm/mf6blockfile +SOURCEDIR25=../src/Utilities/Matrix +SOURCEDIR26=../src/Utilities/Vector +SOURCEDIR27=../src/Utilities/Observation +SOURCEDIR28=../src/Utilities/OutputControl +SOURCEDIR29=../src/Utilities/Memory +SOURCEDIR30=../src/Utilities/ArrayRead VPATH = \ ${SOURCEDIR1} \ @@ -61,224 +63,239 @@ ${SOURCEDIR24} \ ${SOURCEDIR25} \ ${SOURCEDIR26} \ ${SOURCEDIR27} \ -${SOURCEDIR28} +${SOURCEDIR28} \ +${SOURCEDIR29} \ +${SOURCEDIR30} .SUFFIXES: .f90 .F90 .o OBJECTS = \ -$(OBJDIR)/sparsekit.o \ $(OBJDIR)/ilut.o \ -$(OBJDIR)/blas1_d.o \ $(OBJDIR)/kind.o \ +$(OBJDIR)/VectorBase.o \ +$(OBJDIR)/IdmLogger.o \ +$(OBJDIR)/BaseGeometry.o \ +$(OBJDIR)/InputDefinition.o \ +$(OBJDIR)/SimStages.o \ +$(OBJDIR)/GwtDspOptions.o \ +$(OBJDIR)/gwf3npf8idm.o \ $(OBJDIR)/Sparse.o \ -$(OBJDIR)/dag_module.o \ -$(OBJDIR)/OpenSpec.o \ $(OBJDIR)/GwtAdvOptions.o \ -$(OBJDIR)/InputDefinition.o \ -$(OBJDIR)/rcm.o \ -$(OBJDIR)/VectorBase.o \ +$(OBJDIR)/gwt1disv1idm.o \ +$(OBJDIR)/gwt1dis1idm.o \ $(OBJDIR)/gwf3dis8idm.o \ +$(OBJDIR)/simnamidm.o \ +$(OBJDIR)/gwf3disu8idm.o \ +$(OBJDIR)/CsrUtils.o \ +$(OBJDIR)/gwt1idm.o \ +$(OBJDIR)/gwf3disv8idm.o \ +$(OBJDIR)/blas1_d.o \ +$(OBJDIR)/gwt1dspidm.o \ $(OBJDIR)/CharString.o \ +$(OBJDIR)/OpenSpec.o \ +$(OBJDIR)/dag_module.o \ $(OBJDIR)/ims8reordering.o \ $(OBJDIR)/Constants.o \ -$(OBJDIR)/BaseGeometry.o \ -$(OBJDIR)/compilerversion.o \ -$(OBJDIR)/gwf3disu8idm.o \ -$(OBJDIR)/GwfBuyInputData.o \ +$(OBJDIR)/rcm.o \ $(OBJDIR)/HashTable.o \ -$(OBJDIR)/SimStages.o \ +$(OBJDIR)/sparsekit.o \ +$(OBJDIR)/gwt1disu1idm.o \ +$(OBJDIR)/gwf3idm.o \ +$(OBJDIR)/SfrCrossSectionUtils.o \ +$(OBJDIR)/MatrixBase.o \ +$(OBJDIR)/compilerversion.o \ $(OBJDIR)/defmacro.o \ -$(OBJDIR)/CsrUtils.o \ $(OBJDIR)/SimVariables.o \ -$(OBJDIR)/genericutils.o \ -$(OBJDIR)/gwf3npf8idm.o \ -$(OBJDIR)/gwt1dspidm.o \ +$(OBJDIR)/SmoothingFunctions.o \ +$(OBJDIR)/GwfVscInputData.o \ +$(OBJDIR)/GwfStorageUtils.o \ $(OBJDIR)/Xt3dAlgorithm.o \ -$(OBJDIR)/Timer.o \ -$(OBJDIR)/SfrCrossSectionUtils.o \ -$(OBJDIR)/List.o \ +$(OBJDIR)/GwfNpfOptions.o \ +$(OBJDIR)/GwfBuyInputData.o \ $(OBJDIR)/ims8misc.o \ -$(OBJDIR)/MatrixBase.o \ $(OBJDIR)/LinearSolverBase.o \ -$(OBJDIR)/gwf3disv8idm.o \ -$(OBJDIR)/GwtDspOptions.o \ -$(OBJDIR)/IdmLogger.o \ -$(OBJDIR)/SmoothingFunctions.o \ -$(OBJDIR)/VirtualDataLists.o \ -$(OBJDIR)/StringList.o \ -$(OBJDIR)/version.o \ -$(OBJDIR)/GwfStorageUtils.o \ +$(OBJDIR)/genericutils.o \ $(OBJDIR)/ArrayHandlers.o \ -$(OBJDIR)/Message.o \ -$(OBJDIR)/GwfVscInputData.o \ -$(OBJDIR)/mf6lists.o \ $(OBJDIR)/IndexMap.o \ -$(OBJDIR)/GwfNpfOptions.o \ -$(OBJDIR)/ObsOutput.o \ -$(OBJDIR)/TimeSeriesRecord.o \ -$(OBJDIR)/Sim.o \ +$(OBJDIR)/version.o \ $(OBJDIR)/InterfaceMap.o \ +$(OBJDIR)/Message.o \ +$(OBJDIR)/List.o \ +$(OBJDIR)/Sim.o \ +$(OBJDIR)/Timer.o \ $(OBJDIR)/MemoryHelper.o \ -$(OBJDIR)/DistributedVariable.o \ $(OBJDIR)/sort.o \ +$(OBJDIR)/StringList.o \ +$(OBJDIR)/mf6lists.o \ +$(OBJDIR)/IdmGwfDfnSelector.o \ +$(OBJDIR)/IdmGwtDfnSelector.o \ +$(OBJDIR)/ObsOutput.o \ +$(OBJDIR)/Budget.o \ +$(OBJDIR)/IdmSimDfnSelector.o \ +$(OBJDIR)/DistributedVariable.o \ $(OBJDIR)/InputOutput.o \ -$(OBJDIR)/CircularGeometry.o \ -$(OBJDIR)/ObsOutputList.o \ -$(OBJDIR)/Iunit.o \ -$(OBJDIR)/RectangularGeometry.o \ +$(OBJDIR)/VirtualDataLists.o \ $(OBJDIR)/DisvGeom.o \ +$(OBJDIR)/Iunit.o \ +$(OBJDIR)/TimeSeriesRecord.o \ +$(OBJDIR)/TableTerm.o \ +$(OBJDIR)/HeadFileReader.o \ +$(OBJDIR)/PrintSaveManager.o \ +$(OBJDIR)/IdmDfnSelector.o \ +$(OBJDIR)/DefinitionSelect.o \ $(OBJDIR)/ArrayReaders.o \ +$(OBJDIR)/comarg.o \ +$(OBJDIR)/STLVecInt.o \ $(OBJDIR)/BlockParser.o \ -$(OBJDIR)/Budget.o \ +$(OBJDIR)/CircularGeometry.o \ $(OBJDIR)/BudgetFileReader.o \ -$(OBJDIR)/PrintSaveManager.o \ -$(OBJDIR)/STLVecInt.o \ -$(OBJDIR)/comarg.o \ -$(OBJDIR)/HeadFileReader.o \ -$(OBJDIR)/InputDefinitionSelector.o \ -$(OBJDIR)/TableTerm.o \ -$(OBJDIR)/ModflowInput.o \ -$(OBJDIR)/ArrayReaderBase.o \ -$(OBJDIR)/NameFile.o \ -$(OBJDIR)/StructVector.o \ -$(OBJDIR)/Double2dReader.o \ -$(OBJDIR)/Integer2dReader.o \ +$(OBJDIR)/RectangularGeometry.o \ +$(OBJDIR)/ObsOutputList.o \ +$(OBJDIR)/ims8base.o \ $(OBJDIR)/TimeSeries.o \ $(OBJDIR)/TimeSeriesFileList.o \ -$(OBJDIR)/ims8base.o \ +$(OBJDIR)/ArrayReaderBase.o \ +$(OBJDIR)/ModflowInput.o \ $(OBJDIR)/TimeSeriesLink.o \ -$(OBJDIR)/Table.o \ $(OBJDIR)/Double1dReader.o \ -$(OBJDIR)/Integer1dReader.o \ -$(OBJDIR)/LayeredArrayReader.o \ -$(OBJDIR)/ListReader.o \ +$(OBJDIR)/Double2dReader.o \ +$(OBJDIR)/Table.o \ +$(OBJDIR)/Integer2dReader.o \ +$(OBJDIR)/StructVector.o \ $(OBJDIR)/SfrCrossSectionManager.o \ +$(OBJDIR)/ListReader.o \ $(OBJDIR)/Memory.o \ $(OBJDIR)/MemoryList.o \ $(OBJDIR)/MemoryManager.o \ -$(OBJDIR)/Connections.o \ $(OBJDIR)/ats.o \ -$(OBJDIR)/MemoryManagerExt.o \ -$(OBJDIR)/MemorySetHandler.o \ -$(OBJDIR)/tdis.o \ -$(OBJDIR)/ims8linear.o \ -$(OBJDIR)/SeqVector.o \ -$(OBJDIR)/PackageBudget.o \ +$(OBJDIR)/Integer1dReader.o \ $(OBJDIR)/StructArray.o \ +$(OBJDIR)/MemorySetHandler.o \ $(OBJDIR)/MappedMemory.o \ +$(OBJDIR)/ModelPackageInputs.o \ $(OBJDIR)/BaseModel.o \ -$(OBJDIR)/SparseMatrix.o \ $(OBJDIR)/GwfMvrPeriodData.o \ +$(OBJDIR)/ims8linear.o \ +$(OBJDIR)/MemoryManagerExt.o \ +$(OBJDIR)/Connections.o \ +$(OBJDIR)/SeqVector.o \ +$(OBJDIR)/tdis.o \ +$(OBJDIR)/PackageMover.o \ +$(OBJDIR)/PackageBudget.o \ +$(OBJDIR)/TimeSeriesManager.o \ +$(OBJDIR)/Mover.o \ $(OBJDIR)/VirtualBase.o \ $(OBJDIR)/VirtualDataContainer.o \ $(OBJDIR)/BaseExchange.o \ -$(OBJDIR)/PackageMover.o \ -$(OBJDIR)/TimeSeriesManager.o \ +$(OBJDIR)/SparseMatrix.o \ $(OBJDIR)/DiscretizationBase.o \ $(OBJDIR)/UzfCellGroup.o \ -$(OBJDIR)/BaseSolution.o \ -$(OBJDIR)/OutputControlData.o \ -$(OBJDIR)/LoadMf6FileType.o \ -$(OBJDIR)/TimeArray.o \ $(OBJDIR)/BudgetTerm.o \ -$(OBJDIR)/ImsLinearSolver.o \ -$(OBJDIR)/Mover.o \ -$(OBJDIR)/LinearSolverFactory.o \ -$(OBJDIR)/VirtualSolution.o \ -$(OBJDIR)/NumericalPackage.o \ -$(OBJDIR)/Xt3dInterface.o \ $(OBJDIR)/Observe.o \ -$(OBJDIR)/IdmMf6FileLoader.o \ -$(OBJDIR)/SolutionGroup.o \ -$(OBJDIR)/OutputControl.o \ -$(OBJDIR)/gwf3tvbase8.o \ -$(OBJDIR)/gwf3ic8.o \ -$(OBJDIR)/BudgetObject.o \ +$(OBJDIR)/OutputControlData.o \ $(OBJDIR)/gwf3dis8.o \ -$(OBJDIR)/gwf3tvs8.o \ -$(OBJDIR)/TimeArraySeries.o \ -$(OBJDIR)/gwf3tvk8.o \ -$(OBJDIR)/RouterBase.o \ -$(OBJDIR)/TimeArraySeriesLink.o \ +$(OBJDIR)/LayeredArrayReader.o \ +$(OBJDIR)/TimeArray.o \ +$(OBJDIR)/NumericalPackage.o \ +$(OBJDIR)/LoadMf6File.o \ +$(OBJDIR)/ExplicitModel.o \ +$(OBJDIR)/BaseSolution.o \ $(OBJDIR)/gwf3disu8.o \ +$(OBJDIR)/TimeArraySeries.o \ +$(OBJDIR)/SolutionGroup.o \ $(OBJDIR)/ObsContainer.o \ -$(OBJDIR)/gwf3mvr8.o \ -$(OBJDIR)/TimeArraySeriesManager.o \ +$(OBJDIR)/ExplicitSolution.o \ +$(OBJDIR)/TimeArraySeriesLink.o \ +$(OBJDIR)/Xt3dInterface.o \ $(OBJDIR)/gwf3disv8.o \ +$(OBJDIR)/ImsLinearSolver.o \ +$(OBJDIR)/TimeArraySeriesManager.o \ +$(OBJDIR)/BudgetObject.o \ $(OBJDIR)/ObsUtility.o \ +$(OBJDIR)/gwf3tvbase8.o \ +$(OBJDIR)/gwf3tvs8.o \ +$(OBJDIR)/IdmMf6File.o \ +$(OBJDIR)/OutputControl.o \ +$(OBJDIR)/LinearSolverFactory.o \ +$(OBJDIR)/gwf3ic8.o \ +$(OBJDIR)/Obs3.o \ +$(OBJDIR)/gwf3tvk8.o \ $(OBJDIR)/GwtSpc.o \ +$(OBJDIR)/IdmSimulation.o \ +$(OBJDIR)/gwt1oc1.o \ +$(OBJDIR)/gwf3mvr8.o \ $(OBJDIR)/gwt1ic1.o \ +$(OBJDIR)/gwt1obs1.o \ +$(OBJDIR)/gwf3obs8.o \ $(OBJDIR)/gwf3oc8.o \ -$(OBJDIR)/gwt1oc1.o \ $(OBJDIR)/gwf3sto8.o \ -$(OBJDIR)/SerialRouter.o \ -$(OBJDIR)/RouterFactory.o \ -$(OBJDIR)/Obs3.o \ -$(OBJDIR)/gwf3obs8.o \ $(OBJDIR)/BoundaryPackage.o \ $(OBJDIR)/gwf3csub8.o \ -$(OBJDIR)/NumericalModel.o \ +$(OBJDIR)/gwf3uzf8.o \ $(OBJDIR)/gwt1cnc1.o \ -$(OBJDIR)/gwf3api8.o \ -$(OBJDIR)/GhostNode.o \ -$(OBJDIR)/gwf3rch8.o \ -$(OBJDIR)/gwf3chd8.o \ -$(OBJDIR)/gwf3riv8.o \ +$(OBJDIR)/gwf3lak8.o \ +$(OBJDIR)/gwt1src1.o \ +$(OBJDIR)/gwf3maw8.o \ $(OBJDIR)/gwt1fmi1.o \ +$(OBJDIR)/gwf3rch8.o \ +$(OBJDIR)/gwt1apt1.o \ $(OBJDIR)/gwf3wel8.o \ -$(OBJDIR)/NumericalExchange.o \ -$(OBJDIR)/gwt1ssm1.o \ -$(OBJDIR)/gwt1src1.o \ -$(OBJDIR)/NumericalSolution.o \ -$(OBJDIR)/gwf3sfr8.o \ -$(OBJDIR)/SolutionFactory.o \ +$(OBJDIR)/gwf3riv8.o \ $(OBJDIR)/gwf3drn8.o \ -$(OBJDIR)/gwt1obs1.o \ -$(OBJDIR)/gwf3uzf8.o \ +$(OBJDIR)/gwt1mwt1.o \ +$(OBJDIR)/gwf3sfr8.o \ +$(OBJDIR)/gwf3api8.o \ $(OBJDIR)/gwf3evt8.o \ +$(OBJDIR)/NumericalModel.o \ $(OBJDIR)/gwf3ghb8.o \ -$(OBJDIR)/gwt1mst1.o \ +$(OBJDIR)/gwf3chd8.o \ +$(OBJDIR)/gwt1uzt1.o \ +$(OBJDIR)/gwf3vsc8.o \ +$(OBJDIR)/NumericalExchange.o \ +$(OBJDIR)/gwt1ssm1.o \ +$(OBJDIR)/NumericalSolution.o \ $(OBJDIR)/gwt1adv1.o \ -$(OBJDIR)/gwt1apt1.o \ -$(OBJDIR)/gwf3lak8.o \ -$(OBJDIR)/gwt1mvt1.o \ -$(OBJDIR)/gwf3maw8.o \ +$(OBJDIR)/gwt1lkt1.o \ +$(OBJDIR)/SolutionFactory.o \ $(OBJDIR)/VirtualModel.o \ -$(OBJDIR)/TransportModel.o \ -$(OBJDIR)/DisConnExchange.o \ -$(OBJDIR)/gwf3vsc8.o \ -$(OBJDIR)/VirtualGwtModel.o \ -$(OBJDIR)/gwt1ist1.o \ -$(OBJDIR)/CellWithNbrs.o \ $(OBJDIR)/gwt1sft1.o \ -$(OBJDIR)/gwt1uzt1.o \ -$(OBJDIR)/GridSorting.o \ -$(OBJDIR)/VirtualExchange.o \ -$(OBJDIR)/gwf3hfb8.o \ -$(OBJDIR)/VirtualGwfExchange.o \ -$(OBJDIR)/gwt1mwt1.o \ +$(OBJDIR)/gwt1mst1.o \ +$(OBJDIR)/VirtualSolution.o \ $(OBJDIR)/VirtualGwfModel.o \ -$(OBJDIR)/gwt1lkt1.o \ -$(OBJDIR)/VirtualGwtExchange.o \ +$(OBJDIR)/gwt1ist1.o \ $(OBJDIR)/gwf3npf8.o \ +$(OBJDIR)/TransportModel.o \ +$(OBJDIR)/DisConnExchange.o \ +$(OBJDIR)/GhostNode.o \ +$(OBJDIR)/gwt1mvt1.o \ +$(OBJDIR)/VirtualExchange.o \ $(OBJDIR)/gwt1dsp.o \ $(OBJDIR)/gwf3buy8.o \ -$(OBJDIR)/GridConnection.o \ -$(OBJDIR)/SpatialModelConnection.o \ -$(OBJDIR)/gwt1.o \ +$(OBJDIR)/VirtualGwfExchange.o \ +$(OBJDIR)/gwf3hfb8.o \ +$(OBJDIR)/VirtualGwtExchange.o \ +$(OBJDIR)/VirtualGwtModel.o \ +$(OBJDIR)/CellWithNbrs.o \ +$(OBJDIR)/RouterBase.o \ $(OBJDIR)/gwf3.o \ +$(OBJDIR)/gwt1.o \ +$(OBJDIR)/GridSorting.o \ $(OBJDIR)/GwfGwfExchange.o \ -$(OBJDIR)/Mapper.o \ +$(OBJDIR)/SerialRouter.o \ +$(OBJDIR)/RouterFactory.o \ $(OBJDIR)/GwtGwtExchange.o \ +$(OBJDIR)/GridConnection.o \ +$(OBJDIR)/SpatialModelConnection.o \ +$(OBJDIR)/Mapper.o \ $(OBJDIR)/VirtualDataManager.o \ $(OBJDIR)/GwfInterfaceModel.o \ -$(OBJDIR)/GwtInterfaceModel.o \ $(OBJDIR)/RunControl.o \ +$(OBJDIR)/GwtInterfaceModel.o \ $(OBJDIR)/RunControlFactory.o \ $(OBJDIR)/GwfGwfConnection.o \ $(OBJDIR)/GwtGwtConnection.o \ -$(OBJDIR)/GwfGwtExchange.o \ $(OBJDIR)/ConnectionBuilder.o \ +$(OBJDIR)/GwfGwtExchange.o \ $(OBJDIR)/SimulationCreate.o \ $(OBJDIR)/mf6core.o \ $(OBJDIR)/mf6.o diff --git a/src/Distributed/InterfaceMap.f90 b/src/Distributed/InterfaceMap.f90 index e006156ab93..d373f014b3d 100644 --- a/src/Distributed/InterfaceMap.f90 +++ b/src/Distributed/InterfaceMap.f90 @@ -1,6 +1,7 @@ module InterfaceMapModule use KindModule, only: I4B use ConstantsModule, only: LENMODELNAME, LENEXCHANGENAME + use ArrayHandlersModule, only: ExtendPtrArray, ifind use IndexMapModule implicit none @@ -16,13 +17,16 @@ module InterfaceMapModule character(len=LENEXCHANGENAME), dimension(:), & pointer, contiguous :: exchange_names => null() integer(I4B) :: prim_exg_idx - type(IndexMapType), dimension(:), pointer :: node_map => null() - type(IndexMapType), dimension(:), pointer :: connection_map => null() - type(IndexMapSgnType), dimension(:), pointer :: exchange_map => null() + type(IndexMapType), dimension(:), pointer :: node_maps => null() + type(IndexMapType), dimension(:), pointer :: conn_maps => null() + type(IndexMapSgnType), dimension(:), pointer :: exchange_maps => null() contains procedure :: init procedure :: add procedure :: destroy + procedure :: get_node_map + procedure :: get_connection_map + procedure :: print_interface end type InterfaceMapType contains @@ -40,9 +44,9 @@ subroutine init(this, nr_models, nr_exchanges) allocate (this%exchange_ids(nr_exchanges)) allocate (this%exchange_names(nr_exchanges)) - allocate (this%node_map(nr_models)) - allocate (this%connection_map(nr_models)) - allocate (this%exchange_map(nr_exchanges)) + allocate (this%node_maps(nr_models)) + allocate (this%conn_maps(nr_models)) + allocate (this%exchange_maps(nr_exchanges)) ! model id == -1 when not set this%model_ids = -1 @@ -57,7 +61,6 @@ end subroutine init !! The map to which is added, should be properly !< initialized beforehand subroutine add(this, map_to_add) - use ArrayHandlersModule, only: ExtendPtrArray, ifind class(InterfaceMapType) :: this class(InterfaceMapType) :: map_to_add ! local @@ -71,15 +74,15 @@ subroutine add(this, map_to_add) m_index = ifind(this%model_ids, m_id) if (m_index > 0) then ! extend existing index map - call this%node_map(m_index)%add(map_to_add%node_map(im)) - call this%connection_map(m_index)%add(map_to_add%connection_map(im)) + call this%node_maps(m_index)%add(map_to_add%node_maps(im)) + call this%conn_maps(m_index)%add(map_to_add%conn_maps(im)) else ! place in first empty spot m_index = ifind(this%model_ids, -1) this%model_ids(m_index) = m_id this%model_names(m_index) = map_to_add%model_names(im) - call this%node_map(m_index)%copy(map_to_add%node_map(im)) - call this%connection_map(m_index)%copy(map_to_add%connection_map(im)) + call this%node_maps(m_index)%copy(map_to_add%node_maps(im)) + call this%conn_maps(m_index)%copy(map_to_add%conn_maps(im)) end if end do @@ -89,43 +92,115 @@ subroutine add(this, map_to_add) e_index = ifind(this%exchange_ids, e_id) if (e_index > 0) then ! extend existing index map - call this%exchange_map(e_index)%add(map_to_add%exchange_map(ie)) + call this%exchange_maps(e_index)%add(map_to_add%exchange_maps(ie)) else ! place in first empty spot e_index = ifind(this%exchange_ids, -1) this%exchange_ids(e_index) = e_id this%exchange_names(e_index) = map_to_add%exchange_names(ie) - call this%exchange_map(e_index)%copy(map_to_add%exchange_map(ie)) + call this%exchange_maps(e_index)%copy(map_to_add%exchange_maps(ie)) end if end do end subroutine add + function get_node_map(this, model_id) result(node_map) + class(InterfaceMapType) :: this + integer(I4B) :: model_id + type(IndexMapType), pointer :: node_map + ! local + integer(I4B) :: m_idx + + node_map => null() + m_idx = ifind(this%model_ids, model_id) + if (m_idx > 0) then + node_map => this%node_maps(m_idx) + end if + + end function get_node_map + + function get_connection_map(this, model_id) result(connection_map) + class(InterfaceMapType) :: this + integer(I4B) :: model_id + type(IndexMapType), pointer :: connection_map + ! local + integer(I4B) :: m_idx + + connection_map => null() + m_idx = ifind(this%model_ids, model_id) + if (m_idx > 0) then + connection_map => this%conn_maps(m_idx) + end if + + end function get_connection_map + + !> @brief Dumps interface data to the screen + !< + subroutine print_interface(this, outunit) + class(InterfaceMapType) :: this + integer(I4B) :: outunit + ! local + integer(I4B) :: i, n + + write (outunit, '(a,i0)') "nr. models: ", this%nr_models + write (outunit, '(a,i0)') "nr. exchanges: ", this%nr_exchanges + do i = 1, this%nr_models + if (this%model_ids(i) == -1) cycle + write (outunit, '(3a,i0,a)') "model: ", trim(this%model_names(i)), & + "[", this%model_ids(i), "]" + write (outunit, *) "node map:" + do n = 1, size(this%node_maps(i)%src_idx) + write (outunit, '(i7,a,i7)') this%node_maps(i)%src_idx(n), & + " ", this%node_maps(i)%tgt_idx(n) + end do + write (outunit, *) "connection map:" + do n = 1, size(this%conn_maps(i)%src_idx) + write (outunit, '(i7,a,i7)') this%conn_maps(i)%src_idx(n), & + " ", this%conn_maps(i)%tgt_idx(n) + end do + end do + + do i = 1, this%nr_exchanges + if (this%exchange_ids(i) == -1) cycle + write (outunit, '(3a,i0,a)') "exchange: ", trim(this%exchange_names(i)), & + "[", this%exchange_ids(i), "]" + write (outunit, *) "exchange map:" + do n = 1, size(this%exchange_maps(i)%src_idx) + write (outunit, '(i7,a,i7,a,i7)') this%exchange_maps(i)%src_idx(n), & + " ", this%exchange_maps(i)%tgt_idx(n), & + " ", this%exchange_maps(i)%sign(n) + end do + end do + + end subroutine print_interface + subroutine destroy(this) class(InterfaceMapType) :: this ! local integer(I4B) :: i - deallocate (this%model_ids) - deallocate (this%model_names) - deallocate (this%exchange_ids) - deallocate (this%exchange_names) - do i = 1, this%nr_models - deallocate (this%node_map(i)%src_idx) - deallocate (this%node_map(i)%tgt_idx) - deallocate (this%connection_map(i)%src_idx) - deallocate (this%connection_map(i)%tgt_idx) + if (this%model_ids(i) == -1) cycle + deallocate (this%node_maps(i)%src_idx) + deallocate (this%node_maps(i)%tgt_idx) + deallocate (this%conn_maps(i)%src_idx) + deallocate (this%conn_maps(i)%tgt_idx) end do - deallocate (this%node_map) - deallocate (this%connection_map) + deallocate (this%node_maps) + deallocate (this%conn_maps) do i = 1, this%nr_exchanges - deallocate (this%exchange_map(i)%src_idx) - deallocate (this%exchange_map(i)%tgt_idx) - deallocate (this%exchange_map(i)%sign) + if (this%exchange_ids(i) == -1) cycle + deallocate (this%exchange_maps(i)%src_idx) + deallocate (this%exchange_maps(i)%tgt_idx) + deallocate (this%exchange_maps(i)%sign) end do - deallocate (this%exchange_map) + deallocate (this%exchange_maps) + + deallocate (this%model_ids) + deallocate (this%model_names) + deallocate (this%exchange_ids) + deallocate (this%exchange_names) end subroutine destroy diff --git a/src/Distributed/MappedMemory.f90 b/src/Distributed/MappedMemory.f90 index f8db3db46e0..27a65e6743c 100644 --- a/src/Distributed/MappedMemory.f90 +++ b/src/Distributed/MappedMemory.f90 @@ -23,6 +23,9 @@ module MappedMemoryModule integer(I4B), dimension(:), pointer :: src_idx !< source indexes to copy from integer(I4B), dimension(:), pointer :: tgt_idx !< target indexes to copy to integer(I4B), dimension(:), pointer :: sign !< optional sign (or null) to negate copied value + integer(I4B), dimension(:), pointer :: lut !< optional lookup table (can be null()), converts + !! src_idx(i) to actual (local) idx when copying + contains procedure :: sync procedure :: skip_sync !< possibility to skip synchronization, e.g. when src variable not allocated and should remain at default @@ -81,6 +84,11 @@ subroutine sync_int1d(this) do i = 1, this%tgt%isize this%tgt%aint1d(i) = this%src%aint1d(i) end do + else if (associated(this%lut)) then + do i = 1, size(this%tgt_idx) + this%tgt%aint1d(this%tgt_idx(i)) = & + this%src%aint1d(this%lut(this%src_idx(i))) + end do else do i = 1, size(this%tgt_idx) this%tgt%aint1d(this%tgt_idx(i)) = this%src%aint1d(this%src_idx(i)) @@ -118,6 +126,11 @@ subroutine sync_dbl1d(this) do i = 1, this%tgt%isize this%tgt%adbl1d(i) = this%src%adbl1d(i) end do + else if (associated(this%lut)) then + do i = 1, size(this%tgt_idx) + this%tgt%adbl1d(this%tgt_idx(i)) = & + this%src%adbl1d(this%lut(this%src_idx(i))) + end do else do i = 1, size(this%tgt_idx) this%tgt%adbl1d(this%tgt_idx(i)) = this%src%adbl1d(this%src_idx(i)) @@ -157,6 +170,13 @@ subroutine sync_dbl2d(this) this%tgt%adbl2d(k, i) = this%src%adbl2d(k, i) end do end do + else if (associated(this%lut)) then + do i = 1, size(this%tgt_idx) + do k = 1, size(this%src%adbl2d, dim=1) + this%tgt%adbl2d(k, this%tgt_idx(i)) = & + this%src%adbl2d(k, this%lut(this%src_idx(i))) + end do + end do else do i = 1, size(this%tgt_idx) do k = 1, size(this%src%adbl2d, dim=1) diff --git a/src/Distributed/Mapper.f90 b/src/Distributed/Mapper.f90 index 7f14be44e91..7d2fe938271 100644 --- a/src/Distributed/Mapper.f90 +++ b/src/Distributed/Mapper.f90 @@ -3,6 +3,7 @@ module MapperModule use ConstantsModule, only: LENVARNAME, LENMEMPATH use MemoryHelperModule, only: create_mem_path use IndexMapModule + use VirtualBaseModule, only: MAP_NODE_TYPE, MAP_CONN_TYPE use VirtualModelModule, only: VirtualModelType, get_virtual_model use VirtualExchangeModule, only: VirtualExchangeType, get_virtual_exchange use InterfaceMapModule @@ -90,51 +91,26 @@ subroutine add_interface_vars(this) end subroutine add_interface_vars - subroutine add_dist_vars(this, sol_id, var_list, interface_map) + subroutine add_dist_vars(this, sol_id, var_list, iface_map) class(MapperType) :: this integer(I4B) :: sol_id type(ListType) :: var_list - type(InterfaceMapType) :: interface_map + type(InterfaceMapType), pointer :: iface_map ! local integer(I4B) :: i, m, e - type(DistVarType), pointer :: distvar - type(IndexMapType), pointer :: idx_map + type(DistVarType), pointer :: dist_var ! loop over variables do i = 1, var_list%Count() - distvar => GetDistVarFromList(var_list, i) - if (distvar%map_type == SYNC_NODES .or. & - distvar%map_type == SYNC_CONNECTIONS) then - ! map data for all models in this interface - do m = 1, interface_map%nr_models - - ! pick the right index map: connection based or node based - if (distvar%map_type == SYNC_NODES) then - idx_map => interface_map%node_map(m) - else if (distvar%map_type == SYNC_CONNECTIONS) then - idx_map => interface_map%connection_map(m) - end if - - ! and map ... - call this%map_model_data(sol_id, & - distvar%comp_name, & - distvar%subcomp_name, & - distvar%var_name, & - interface_map%model_ids(m), & - idx_map, & - distvar%sync_stages) + dist_var => GetDistVarFromList(var_list, i) + if (dist_var%map_type == SYNC_NODES .or. & ! models + dist_var%map_type == SYNC_CONNECTIONS) then + do m = 1, iface_map%nr_models + call this%map_model_data(sol_id, iface_map, m, dist_var) end do - else if (distvar%map_type == SYNC_EXCHANGES) then - ! map data from the exchanges to the interface - do e = 1, interface_map%nr_exchanges - call this%map_exg_data(sol_id, & - distvar%comp_name, & - distvar%subcomp_name, & - distvar%var_name, & - interface_map%exchange_ids(e), & - distvar%exg_var_name, & - interface_map%exchange_map(e), & - distvar%sync_stages) + else if (dist_var%map_type == SYNC_EXCHANGES) then ! exchanges + do e = 1, iface_map%nr_exchanges + call this%map_exg_data(sol_id, iface_map, e, dist_var) end do end if end do @@ -144,74 +120,79 @@ end subroutine add_dist_vars !> @brief Map data from model memory to a target memory entry, !! with the specified map. The source and target items have !< the same name and (optionally) subcomponent name. - subroutine map_model_data(this, controller_id, tgt_model_name, & - tgt_subcomp_name, tgt_var_name, src_model_id, & - index_map, stages) + !call this%map_model_data(sol_id, interface_map%model_ids(m), & + !dist_var, idx_map) + subroutine map_model_data(this, sol_id, iface_map, model_idx, dist_var) use SimModule, only: ustop - use MemoryManagerModule, only: get_from_memorylist - class(MapperType) :: this - integer(I4B) :: controller_id !< e.g. the numerical solution where synchr. is controlled - character(len=*), intent(in) :: tgt_model_name - character(len=*), intent(in) :: tgt_subcomp_name - character(len=*), intent(in) :: tgt_var_name - integer(I4B), intent(in) :: src_model_id - type(IndexMapType), intent(in) :: index_map - integer(I4B), dimension(:), intent(in) :: stages !< array with 1 or multiple stages for synchronization + class(MapperType) :: this !< this mapper instance + integer(I4B) :: sol_id !< the numerical solution where synchr. is controlled + type(InterfaceMapType), pointer :: iface_map !< the full interface map + integer(I4B) :: model_idx !< the model index (not id) in the interface map + type(DistVarType), pointer :: dist_var !< the distributed variable to map ! local character(len=LENVARNAME) :: src_var_name character(len=LENMEMPATH) :: src_mem_path, tgt_mem_path class(VirtualModelType), pointer :: v_model + type(IndexMapType), pointer :: idx_map + integer(I4B), dimension(:), pointer, contiguous :: lookup_table + + v_model => get_virtual_model(iface_map%model_ids(model_idx)) - v_model => get_virtual_model(src_model_id) + ! pick the right index map: connection based or node based + if (dist_var%map_type == SYNC_NODES) then + idx_map => iface_map%node_maps(model_idx) + lookup_table => v_model%element_luts(MAP_NODE_TYPE)%remote_to_virtual + else if (dist_var%map_type == SYNC_CONNECTIONS) then + idx_map => iface_map%conn_maps(model_idx) + lookup_table => v_model%element_luts(MAP_CONN_TYPE)%remote_to_virtual + else + write (*, *) "Unknown map type for distributed variable ", dist_var%var_name + call ustop() + end if - if (len_trim(tgt_subcomp_name) > 0) then - tgt_mem_path = create_mem_path(tgt_model_name, tgt_subcomp_name) + if (len_trim(dist_var%subcomp_name) > 0) then + tgt_mem_path = create_mem_path(dist_var%comp_name, dist_var%subcomp_name) else - tgt_mem_path = create_mem_path(tgt_model_name) + tgt_mem_path = create_mem_path(dist_var%comp_name) end if - src_var_name = tgt_var_name - src_mem_path = v_model%get_vrt_mem_path(src_var_name, tgt_subcomp_name) - call this%map_data(controller_id, & - tgt_var_name, tgt_mem_path, index_map%tgt_idx, & - src_var_name, src_mem_path, index_map%src_idx, & - null(), stages) + src_var_name = dist_var%var_name + src_mem_path = v_model%get_vrt_mem_path(src_var_name, dist_var%subcomp_name) + call this%map_data(sol_id, & + src_var_name, tgt_mem_path, idx_map%tgt_idx, & + src_var_name, src_mem_path, idx_map%src_idx, & + null(), lookup_table, dist_var%sync_stages) end subroutine map_model_data !> @brief Map memory from a Exchange to the specified memory entry, !< using the index map - subroutine map_exg_data(this, controller_id, tgt_model_name, & - tgt_subcomp_name, tgt_var_name, src_exg_id, & - src_var_name, index_map_sgn, stages) - use SimModule, only: ustop - use MemoryManagerModule, only: get_from_memorylist + subroutine map_exg_data(this, sol_id, iface_map, exg_idx, dist_var) class(MapperType) :: this - integer(I4B) :: controller_id !< e.g. the numerical solution where synchr. is controlled - character(len=*), intent(in) :: tgt_model_name - character(len=*), intent(in) :: tgt_subcomp_name - character(len=*), intent(in) :: tgt_var_name - integer(I4B), intent(in) :: src_exg_id - character(len=*), intent(in) :: src_var_name - type(IndexMapSgnType), intent(in) :: index_map_sgn - integer(I4B), dimension(:), intent(in) :: stages !< array with 1 or multiple stages for synchronization + integer(I4B) :: sol_id !< the numerical solution where synchr. is controlled + type(InterfaceMapType), pointer :: iface_map !< the full interface map + integer(I4B), intent(in) :: exg_idx !< the index (not id) for the exchange + type(DistVarType), pointer :: dist_var !< the distributed variable to map ! local character(len=LENMEMPATH) :: src_mem_path, tgt_mem_path class(VirtualExchangeType), pointer :: v_exchange + type(IndexMapSgnType), pointer :: idx_map + + v_exchange => get_virtual_exchange(iface_map%exchange_ids(exg_idx)) - v_exchange => get_virtual_exchange(src_exg_id) + idx_map => iface_map%exchange_maps(exg_idx) - if (len_trim(tgt_subcomp_name) > 0) then - tgt_mem_path = create_mem_path(tgt_model_name, tgt_subcomp_name) + if (len_trim(dist_var%subcomp_name) > 0) then + tgt_mem_path = create_mem_path(dist_var%comp_name, dist_var%subcomp_name) else - tgt_mem_path = create_mem_path(tgt_model_name) + tgt_mem_path = create_mem_path(dist_var%comp_name) end if - src_mem_path = v_exchange%get_vrt_mem_path(src_var_name, '') - call this%map_data(controller_id, & - tgt_var_name, tgt_mem_path, index_map_sgn%tgt_idx, & - src_var_name, src_mem_path, index_map_sgn%src_idx, & - index_map_sgn%sign, stages) + src_mem_path = v_exchange%get_vrt_mem_path(dist_var%exg_var_name, '') + call this%map_data(sol_id, & + dist_var%var_name, tgt_mem_path, idx_map%tgt_idx, & + dist_var%exg_var_name, src_mem_path, idx_map%src_idx, & + idx_map%sign, null(), dist_var%sync_stages) end subroutine map_exg_data @@ -228,14 +209,15 @@ subroutine map_data_full(this, controller_id, tgt_name, tgt_path, & call this%map_data(controller_id, tgt_name, tgt_path, null(), & src_name, src_path, null(), & - null(), stages) + null(), null(), stages) end subroutine map_data_full !> @brief Generic mapping between two variables in memory, using !< an optional sign conversion subroutine map_data(this, controller_id, tgt_name, tgt_path, tgt_idx, & - src_name, src_path, src_idx, sign_array, stages) + src_name, src_path, src_idx, sign_array, & + lookup_table, stages) class(MapperType) :: this integer(I4B) :: controller_id character(len=*), intent(in) :: tgt_name @@ -245,6 +227,7 @@ subroutine map_data(this, controller_id, tgt_name, tgt_path, tgt_idx, & character(len=*), intent(in) :: src_path integer(I4B), dimension(:), pointer :: src_idx integer(I4B), dimension(:), pointer :: sign_array + integer(I4B), dimension(:), pointer :: lookup_table integer(I4B), dimension(:), intent(in) :: stages ! local integer(I4B) :: istage, i @@ -271,6 +254,7 @@ subroutine map_data(this, controller_id, tgt_name, tgt_path, tgt_idx, & mapped_data%src_idx => src_idx mapped_data%tgt_idx => tgt_idx mapped_data%sign => sign_array + mapped_data%lut => lookup_table obj => mapped_data call this%mapped_data_list%Add(obj) diff --git a/src/Distributed/MpiMessageBuilder.f90 b/src/Distributed/MpiMessageBuilder.f90 index 022e021666b..d5ae8f4a0eb 100644 --- a/src/Distributed/MpiMessageBuilder.f90 +++ b/src/Distributed/MpiMessageBuilder.f90 @@ -11,32 +11,68 @@ module MpiMessageBuilderModule type, public :: VdcHeaderType integer(I4B) :: id integer(I4B) :: container_type + integer(I4B), dimension(NR_VDC_ELEMENT_MAPS) :: map_sizes + end type + + type, public :: VdcReceiverMapsType + type(VdcElementMapType), dimension(NR_VDC_ELEMENT_MAPS) :: el_maps + contains + procedure :: create + procedure :: destroy end type type, public :: MpiMessageBuilderType type(VdcPtrType), dimension(:), pointer :: vdc_models => null() !< the models to be build the message for type(VdcPtrType), dimension(:), pointer :: vdc_exchanges => null() !< the exchanges to be build the message for integer(I4B) :: imon !< the output file unit, set from outside - logical(LGP) :: enable_monitor !< log when true contains procedure :: init procedure :: attach_data procedure :: release_data procedure :: create_header_snd procedure :: create_header_rcv + procedure :: create_map_snd + procedure :: create_map_rcv procedure :: create_body_rcv procedure :: create_body_snd procedure :: set_monitor ! private procedure, private :: get_vdc_from_hdr procedure, private :: create_vdc_snd_hdr + procedure, private :: create_vdc_snd_map procedure, private :: create_vdc_snd_body procedure, private :: create_vdc_rcv_body - procedure, private :: create_vdc_body + procedure, private :: create_element_map end type contains + subroutine create(this, map_sizes) + class(VdcReceiverMapsType) :: this + integer(I4B), dimension(NR_VDC_ELEMENT_MAPS) :: map_sizes + ! local + integer(I4B) :: i + + do i = 1, NR_VDC_ELEMENT_MAPS + this%el_maps(i)%nr_virt_elems = map_sizes(i) + allocate (this%el_maps(i)%remote_elem_shift(map_sizes(i))) + end do + + end subroutine create + + subroutine destroy(this) + class(VdcReceiverMapsType) :: this + ! local + integer(I4B) :: i + + do i = 1, NR_VDC_ELEMENT_MAPS + if (associated(this%el_maps(i)%remote_elem_shift)) then + deallocate (this%el_maps(i)%remote_elem_shift) + end if + end do + + end subroutine destroy + subroutine init(this) class(MpiMessageBuilderType) :: this @@ -157,11 +193,129 @@ subroutine create_header_rcv(this, hdr_rcv_type) ! this will be for one data container, the mpi recv ! call will accept an array of them, no need to create ! an overarching contiguous type... - call MPI_Type_contiguous(2, MPI_INTEGER, hdr_rcv_type, ierr) + call MPI_Type_contiguous(NR_VDC_ELEMENT_MAPS + 2, MPI_INTEGER, & + hdr_rcv_type, ierr) call MPI_Type_commit(hdr_rcv_type, ierr) end subroutine create_header_rcv + subroutine create_map_snd(this, rank, stage, map_snd_type) + class(MpiMessageBuilderType) :: this + integer(I4B) :: rank + integer(I4B) :: stage + integer :: map_snd_type + ! local + integer(I4B) :: i, offset, nr_types + class(VirtualDataContainerType), pointer :: vdc + integer :: ierr + type(STLVecInt) :: model_idxs, exg_idxs + integer, dimension(:), allocatable :: blk_cnts, types + integer(kind=MPI_ADDRESS_KIND), dimension(:), allocatable :: displs + + call model_idxs%init() + call exg_idxs%init() + + ! determine which containers to include TODO_MJR: avoid repetition + do i = 1, size(this%vdc_models) + vdc => this%vdc_models(i)%ptr + if (vdc%is_active .and. vdc%orig_rank == rank) then + call model_idxs%push_back(i) + end if + end do + do i = 1, size(this%vdc_exchanges) + vdc => this%vdc_exchanges(i)%ptr + if (vdc%is_active .and. vdc%orig_rank == rank) then + call exg_idxs%push_back(i) + end if + end do + + nr_types = model_idxs%size + exg_idxs%size + allocate (blk_cnts(nr_types)) + allocate (types(nr_types)) + allocate (displs(nr_types)) + + if (this%imon > 0) then + write (this%imon, '(6x,a,*(i3))') "create maps for models: ", & + model_idxs%get_values() + write (this%imon, '(6x,a,*(i3))') "create maps for exchange: ", & + exg_idxs%get_values() + end if + + ! loop over containers + do i = 1, model_idxs%size + vdc => this%vdc_models(model_idxs%at(i))%ptr + call MPI_Get_address(vdc%id, displs(i), ierr) + blk_cnts(i) = 1 + types(i) = this%create_vdc_snd_map(vdc, stage) + end do + offset = model_idxs%size + do i = 1, exg_idxs%size + vdc => this%vdc_exchanges(exg_idxs%at(i))%ptr + call MPI_Get_address(vdc%id, displs(i + offset), ierr) + blk_cnts(i + offset) = 1 + types(i + offset) = this%create_vdc_snd_map(vdc, stage) + end do + + ! create a compound MPI data type for the maps + call MPI_Type_create_struct(nr_types, blk_cnts, displs, types, & + map_snd_type, ierr) + call MPI_Type_commit(map_snd_type, ierr) + + ! free the subtypes + do i = 1, nr_types + call MPI_Type_free(types(i), ierr) + end do + + call model_idxs%destroy() + call exg_idxs%destroy() + + deallocate (blk_cnts) + deallocate (types) + deallocate (displs) + + end subroutine create_map_snd + + subroutine create_map_rcv(this, rcv_map, nr_headers, map_rcv_type) + class(MpiMessageBuilderType) :: this + type(VdcReceiverMapsType), dimension(:) :: rcv_map + integer(I4B) :: nr_headers + integer :: map_rcv_type + ! local + integer(I4B) :: i, j, nr_elems, type_cnt + integer :: ierr, max_nr_maps + integer, dimension(:), allocatable :: types + integer(kind=MPI_ADDRESS_KIND), dimension(:), allocatable :: displs + integer, dimension(:), allocatable :: blk_cnts + + max_nr_maps = nr_headers * NR_VDC_ELEMENT_MAPS + allocate (types(max_nr_maps)) + allocate (displs(max_nr_maps)) + allocate (blk_cnts(max_nr_maps)) + + type_cnt = 0 + do i = 1, nr_headers + do j = 1, NR_VDC_ELEMENT_MAPS + nr_elems = rcv_map(i)%el_maps(j)%nr_virt_elems + if (nr_elems == 0) cycle + + type_cnt = type_cnt + 1 + call MPI_Get_address(rcv_map(i)%el_maps(j)%remote_elem_shift, & + displs(type_cnt), ierr) + call MPI_Type_contiguous(nr_elems, MPI_Integer, types(type_cnt), ierr) + blk_cnts(type_cnt) = 1 + end do + end do + + call MPI_Type_create_struct(type_cnt, blk_cnts, displs, types, & + map_rcv_type, ierr) + call MPI_Type_commit(map_rcv_type, ierr) + + deallocate (types) + deallocate (displs) + deallocate (blk_cnts) + + end subroutine create_map_rcv + !> @brief Create the body to receive based on the headers !< that have been sent subroutine create_body_rcv(this, rank, stage, body_rcv_type) @@ -239,11 +393,12 @@ end subroutine create_body_rcv !> @brief Create the body to send based on the received headers !< - subroutine create_body_snd(this, rank, stage, headers, body_snd_type) + subroutine create_body_snd(this, rank, stage, headers, maps, body_snd_type) class(MpiMessageBuilderType) :: this integer(I4B) :: rank integer(I4B) :: stage type(VdcHeaderType), dimension(:) :: headers + type(VdcReceiverMapsType), dimension(:) :: maps integer :: body_snd_type ! local integer(I4B) :: i, nr_headers @@ -261,7 +416,7 @@ subroutine create_body_snd(this, rank, stage, headers, body_snd_type) do i = 1, nr_headers vdc => this%get_vdc_from_hdr(headers(i)) call MPI_Get_address(vdc%id, displs(i), ierr) - types(i) = this%create_vdc_snd_body(vdc, rank, stage) + types(i) = this%create_vdc_snd_body(vdc, maps(i)%el_maps, rank, stage) blk_cnts(i) = 1 end do @@ -287,10 +442,10 @@ function create_vdc_snd_hdr(this, vdc, stage) result(new_type) integer(I4B) :: stage integer :: new_type ! the created MPI datatype, uncommitted ! local - integer :: ierr - integer, dimension(2) :: blk_cnts - integer(kind=MPI_ADDRESS_KIND), dimension(2) :: displs - integer, dimension(2) :: types + integer :: i, ierr + integer, dimension(NR_VDC_ELEMENT_MAPS + 2) :: blk_cnts + integer(kind=MPI_ADDRESS_KIND), dimension(NR_VDC_ELEMENT_MAPS + 2) :: displs + integer, dimension(NR_VDC_ELEMENT_MAPS + 2) :: types call MPI_Get_address(vdc%id, displs(1), ierr) types(1) = MPI_INTEGER @@ -298,63 +453,142 @@ function create_vdc_snd_hdr(this, vdc, stage) result(new_type) call MPI_Get_address(vdc%container_type, displs(2), ierr) types(2) = MPI_INTEGER blk_cnts(2) = 1 + do i = 1, NR_VDC_ELEMENT_MAPS + call MPI_Get_address(vdc%element_maps(i)%nr_virt_elems, displs(i + 2), ierr) + types(i + 2) = MPI_INTEGER + blk_cnts(i + 2) = 1 + end do ! rebase to id field displs = displs - displs(1) - call MPI_Type_create_struct(2, blk_cnts, displs, types, new_type, ierr) + call MPI_Type_create_struct(NR_VDC_ELEMENT_MAPS + 2, blk_cnts, & + displs, types, new_type, ierr) call MPI_Type_commit(new_type, ierr) end function create_vdc_snd_hdr - function create_vdc_rcv_body(this, vdc, rank, stage) result(new_type) + !> @brief Create a MPI datatype for sending the maps + !< with the type relative to the id field + function create_vdc_snd_map(this, vdc, stage) result(new_type) class(MpiMessageBuilderType) :: this class(VirtualDataContainerType), pointer :: vdc - integer(I4B) :: rank integer(I4B) :: stage integer :: new_type ! local - type(STLVecInt) :: virtual_items + integer(I4B) :: i, type_cnt + integer :: n_elems, ierr + integer(kind=MPI_ADDRESS_KIND) :: offset + integer, dimension(:), allocatable :: types + integer(kind=MPI_ADDRESS_KIND), dimension(:), allocatable :: displs + integer, dimension(:), allocatable :: blk_cnts - call virtual_items%init() - call vdc%get_recv_items(stage, rank, virtual_items) - !if (this%imon > 0) call vdc%print_items(this%imon, virtual_items) - new_type = this%create_vdc_body(vdc, virtual_items) - call virtual_items%destroy() + allocate (types(NR_VDC_ELEMENT_MAPS)) + allocate (displs(NR_VDC_ELEMENT_MAPS)) + allocate (blk_cnts(NR_VDC_ELEMENT_MAPS)) - end function create_vdc_rcv_body + ! displ relative to id field + call MPI_Get_address(vdc%id, offset, ierr) - function create_vdc_snd_body(this, vdc, rank, stage) result(new_type) + type_cnt = 0 + do i = 1, NR_VDC_ELEMENT_MAPS + n_elems = vdc%element_maps(i)%nr_virt_elems + if (n_elems == 0) cycle ! only non-empty maps are sent + + type_cnt = type_cnt + 1 + call MPI_Get_address(vdc%element_maps(i)%remote_elem_shift, & + displs(type_cnt), ierr) + call MPI_Type_contiguous(n_elems, MPI_INTEGER, types(type_cnt), ierr) + call MPI_Type_commit(types(type_cnt), ierr) + blk_cnts(type_cnt) = 1 + displs(type_cnt) = displs(type_cnt) - offset + end do + + call MPI_Type_create_struct(type_cnt, blk_cnts, displs, types, & + new_type, ierr) + call MPI_Type_commit(new_type, ierr) + + do i = 1, type_cnt + call MPI_Type_free(types(i), ierr) + end do + + deallocate (types) + deallocate (displs) + deallocate (blk_cnts) + + end function create_vdc_snd_map + + function create_vdc_rcv_body(this, vdc, rank, stage) result(new_type) class(MpiMessageBuilderType) :: this class(VirtualDataContainerType), pointer :: vdc integer(I4B) :: rank integer(I4B) :: stage integer :: new_type ! local - type(STLVecInt) :: virtual_items + type(STLVecInt) :: items + integer :: ierr + integer(kind=MPI_ADDRESS_KIND) :: offset + integer, dimension(:), allocatable :: types + integer(kind=MPI_ADDRESS_KIND), dimension(:), allocatable :: displs + integer, dimension(:), allocatable :: blk_cnts + integer(I4B) :: i + class(VirtualDataType), pointer :: vd - call virtual_items%init() - call vdc%get_send_items(stage, rank, virtual_items) - !if (this%imon > 0) call vdc%print_items(this%imon, virtual_items) - new_type = this%create_vdc_body(vdc, virtual_items) - call virtual_items%destroy() + call items%init() + call vdc%get_recv_items(stage, rank, items) + !if (this%imon > 0) call vdc%print_items(this%imon, items) - end function create_vdc_snd_body + allocate (types(items%size)) + allocate (displs(items%size)) + allocate (blk_cnts(items%size)) + + call MPI_Get_address(vdc%id, offset, ierr) + + do i = 1, items%size + vd => get_virtual_data_from_list(vdc%virtual_data_list, items%at(i)) + call get_mpi_datatype(this, vd, displs(i), types(i)) + blk_cnts(i) = 1 + ! rebase w.r.t. id field + displs(i) = displs(i) - offset + end do + + call MPI_Type_create_struct(items%size, blk_cnts, displs, & + types, new_type, ierr) + call MPI_Type_commit(new_type, ierr) + + do i = 1, items%size + vd => get_virtual_data_from_list(vdc%virtual_data_list, items%at(i)) + call free_mpi_datatype(vd, types(i)) + end do - !> @brief Create data type for this container, relative - !< to its id field. This is used for sending and receiving - function create_vdc_body(this, vdc, items) result(new_type) + deallocate (types) + deallocate (displs) + deallocate (blk_cnts) + + call items%destroy() + + end function create_vdc_rcv_body + + function create_vdc_snd_body(this, vdc, vdc_maps, rank, stage) result(new_type) class(MpiMessageBuilderType) :: this class(VirtualDataContainerType), pointer :: vdc - type(STLVecInt) :: items + type(VdcElementMapType), dimension(:) :: vdc_maps + integer(I4B) :: rank + integer(I4B) :: stage integer :: new_type ! local - integer(I4B) :: i - class(VirtualDataType), pointer :: vd + type(STLVecInt) :: items integer :: ierr integer(kind=MPI_ADDRESS_KIND) :: offset integer, dimension(:), allocatable :: types integer(kind=MPI_ADDRESS_KIND), dimension(:), allocatable :: displs integer, dimension(:), allocatable :: blk_cnts + integer(I4B) :: i + class(VirtualDataType), pointer :: vd + integer(I4B), dimension(:), pointer, contiguous :: el_map + + call items%init() + call vdc%get_send_items(stage, rank, items) + !if (this%imon > 0) call vdc%print_items(this%imon, items) allocate (types(items%size)) allocate (displs(items%size)) @@ -364,7 +598,12 @@ function create_vdc_body(this, vdc, items) result(new_type) do i = 1, items%size vd => get_virtual_data_from_list(vdc%virtual_data_list, items%at(i)) - call get_mpi_datatype(vd, displs(i), types(i)) + if (vd%map_type > 0) then + el_map => vdc_maps(vd%map_type)%remote_elem_shift + else + el_map => null() + end if + call get_mpi_datatype(this, vd, displs(i), types(i), el_map) blk_cnts(i) = 1 ! rebase w.r.t. id field displs(i) = displs(i) - offset @@ -383,7 +622,36 @@ function create_vdc_body(this, vdc, items) result(new_type) deallocate (displs) deallocate (blk_cnts) - end function create_vdc_body + call items%destroy() + + end function create_vdc_snd_body + + !> @brief Temp. function to generate a dummy (complete) map + !< + function create_element_map(this, rank, vdc, vd) result(el_map) + use MemoryManagerModule, only: get_mem_shape, get_mem_rank + use ConstantsModule, only: MAXMEMRANK + class(MpiMessageBuilderType) :: this + integer(I4B) :: rank + class(VirtualDataContainerType), pointer :: vdc + class(VirtualDataType), pointer :: vd + integer(I4B), dimension(:), pointer, contiguous :: el_map + ! local + integer(I4B), dimension(MAXMEMRANK) :: mem_shp + integer(I4B) :: i, nrow, mem_rank + + el_map => null() + call get_mem_rank(vd%virtual_mt%name, vd%virtual_mt%path, mem_rank) + call get_mem_shape(vd%virtual_mt%name, vd%virtual_mt%path, mem_shp) + if (mem_rank > 0) then + nrow = mem_shp(mem_rank) + allocate (el_map(nrow)) + do i = 1, nrow + el_map(i) = i - 1 + end do + end if + + end function create_element_map function get_vdc_from_hdr(this, header) result(vdc) class(MpiMessageBuilderType) :: this @@ -414,35 +682,48 @@ end function get_vdc_from_hdr !> @brief Local routine to get elemental mpi data types representing !! the virtual data items. Types are automatically committed unless !< they are primitives (e.g. MPI_INTEGER) - subroutine get_mpi_datatype(virtual_data, el_displ, el_type) + subroutine get_mpi_datatype(this, virtual_data, el_displ, el_type, el_map_opt) use SimModule, only: ustop - use SimVariablesModule, only: proc_id + class(MpiMessageBuilderType) :: this class(VirtualDataType), pointer :: virtual_data integer(kind=MPI_ADDRESS_KIND) :: el_displ integer :: el_type + integer(I4B), dimension(:), pointer, contiguous, optional :: el_map_opt !< optional, and can be null ! local type(MemoryType), pointer :: mt + integer(I4B), dimension(:), pointer, contiguous :: el_map - mt => virtual_data%virtual_mt - if (.not. associated(mt)) then - write (*, *) 'not associated: ', virtual_data%var_name, proc_id + el_map => null() + if (present(el_map_opt)) el_map => el_map_opt + + if (this%imon > 0) then + if (.not. associated(el_map)) then + write (this%imon, '(8x,2a,i0)') virtual_data%var_name, ' all ', & + virtual_data%virtual_mt%isize + else + write (this%imon, '(8x,2a,i0)') virtual_data%var_name, & + ' with map size ', size(el_map) + end if end if + + mt => virtual_data%virtual_mt + if (associated(mt%intsclr)) then call get_mpitype_for_int(mt, el_displ, el_type) else if (associated(mt%aint1d)) then - call get_mpitype_for_int1d(mt, el_displ, el_type) + call get_mpitype_for_int1d(mt, el_displ, el_type, el_map) else if (associated(mt%aint2d)) then - call get_mpitype_for_int2d(mt, el_displ, el_type) + call get_mpitype_for_int2d(mt, el_displ, el_type, el_map) else if (associated(mt%aint3d)) then - call get_mpitype_for_int3d(mt, el_displ, el_type) + call get_mpitype_for_int3d(mt, el_displ, el_type, el_map) else if (associated(mt%dblsclr)) then call get_mpitype_for_dbl(mt, el_displ, el_type) else if (associated(mt%adbl1d)) then - call get_mpitype_for_dbl1d(mt, el_displ, el_type) + call get_mpitype_for_dbl1d(mt, el_displ, el_type, el_map) else if (associated(mt%adbl2d)) then - call get_mpitype_for_dbl2d(mt, el_displ, el_type) + call get_mpitype_for_dbl2d(mt, el_displ, el_type, el_map) else if (associated(mt%adbl3d)) then - call get_mpitype_for_dbl3d(mt, el_displ, el_type) + call get_mpitype_for_dbl3d(mt, el_displ, el_type, el_map) else write (*, *) 'unsupported datatype in MPI messaging for ', & virtual_data%var_name, virtual_data%mem_path @@ -492,42 +773,66 @@ subroutine get_mpitype_for_int(mem, el_displ, el_type) end subroutine get_mpitype_for_int - subroutine get_mpitype_for_int1d(mem, el_displ, el_type) + subroutine get_mpitype_for_int1d(mem, el_displ, el_type, el_map) type(MemoryType), pointer :: mem integer(kind=MPI_ADDRESS_KIND) :: el_displ integer :: el_type + integer, dimension(:), pointer :: el_map ! local integer :: ierr call MPI_Get_address(mem%aint1d, el_displ, ierr) - call MPI_Type_contiguous(mem%isize, MPI_INTEGER, el_type, ierr) + if (associated(el_map)) then + call MPI_Type_create_indexed_block( & + size(el_map), 1, el_map, MPI_INTEGER, el_type, ierr) + else + call MPI_Type_contiguous(mem%isize, MPI_INTEGER, el_type, ierr) + end if call MPI_Type_commit(el_type, ierr) end subroutine get_mpitype_for_int1d - subroutine get_mpitype_for_int2d(mem, el_displ, el_type) + subroutine get_mpitype_for_int2d(mem, el_displ, el_type, el_map) type(MemoryType), pointer :: mem integer(kind=MPI_ADDRESS_KIND) :: el_displ integer :: el_type + integer, dimension(:), pointer :: el_map ! local integer :: ierr + integer :: two_integer_type call MPI_Get_address(mem%aint2d, el_displ, ierr) - call MPI_Type_contiguous(mem%isize, MPI_INTEGER, el_type, ierr) + if (associated(el_map)) then + call MPI_Type_contiguous(2, MPI_INTEGER, two_integer_type, ierr) + call MPI_Type_create_indexed_block( & + size(el_map), 1, el_map, two_integer_type, el_type, ierr) + else + call MPI_Type_contiguous(mem%isize, MPI_INTEGER, el_type, ierr) + end if call MPI_Type_commit(el_type, ierr) + call MPI_Type_free(two_integer_type, ierr) end subroutine get_mpitype_for_int2d - subroutine get_mpitype_for_int3d(mem, el_displ, el_type) + subroutine get_mpitype_for_int3d(mem, el_displ, el_type, el_map) type(MemoryType), pointer :: mem integer(kind=MPI_ADDRESS_KIND) :: el_displ integer :: el_type + integer, dimension(:), pointer :: el_map ! local integer :: ierr + integer :: three_integer_type call MPI_Get_address(mem%aint3d, el_displ, ierr) - call MPI_Type_contiguous(mem%isize, MPI_INTEGER, el_type, ierr) + if (associated(el_map)) then + call MPI_Type_contiguous(3, MPI_INTEGER, three_integer_type, ierr) + call MPI_Type_create_indexed_block( & + size(el_map), 1, el_map, three_integer_type, el_type, ierr) + else + call MPI_Type_contiguous(mem%isize, MPI_INTEGER, el_type, ierr) + end if call MPI_Type_commit(el_type, ierr) + call MPI_Type_free(three_integer_type, ierr) end subroutine get_mpitype_for_int3d @@ -544,42 +849,66 @@ subroutine get_mpitype_for_dbl(mem, el_displ, el_type) end subroutine get_mpitype_for_dbl - subroutine get_mpitype_for_dbl1d(mem, el_displ, el_type) + subroutine get_mpitype_for_dbl1d(mem, el_displ, el_type, el_map) type(MemoryType), pointer :: mem integer(kind=MPI_ADDRESS_KIND) :: el_displ integer :: el_type + integer, dimension(:), pointer :: el_map ! local integer :: ierr call MPI_Get_address(mem%adbl1d, el_displ, ierr) - call MPI_Type_contiguous(mem%isize, MPI_DOUBLE_PRECISION, el_type, ierr) + if (associated(el_map)) then + call MPI_Type_create_indexed_block( & + size(el_map), 1, el_map, MPI_DOUBLE_PRECISION, el_type, ierr) + else + call MPI_Type_contiguous(mem%isize, MPI_DOUBLE_PRECISION, el_type, ierr) + end if call MPI_Type_commit(el_type, ierr) end subroutine get_mpitype_for_dbl1d - subroutine get_mpitype_for_dbl2d(mem, el_displ, el_type) + subroutine get_mpitype_for_dbl2d(mem, el_displ, el_type, el_map) type(MemoryType), pointer :: mem integer(kind=MPI_ADDRESS_KIND) :: el_displ integer :: el_type + integer, dimension(:), pointer :: el_map ! local integer :: ierr + integer :: two_double_type call MPI_Get_address(mem%adbl2d, el_displ, ierr) - call MPI_Type_contiguous(mem%isize, MPI_DOUBLE_PRECISION, el_type, ierr) + if (associated(el_map)) then + call MPI_Type_contiguous(2, MPI_DOUBLE_PRECISION, two_double_type, ierr) + call MPI_Type_create_indexed_block( & + size(el_map), 1, el_map, two_double_type, el_type, ierr) + else + call MPI_Type_contiguous(mem%isize, MPI_DOUBLE_PRECISION, el_type, ierr) + end if call MPI_Type_commit(el_type, ierr) + call MPI_Type_free(two_double_type, ierr) end subroutine get_mpitype_for_dbl2d - subroutine get_mpitype_for_dbl3d(mem, el_displ, el_type) + subroutine get_mpitype_for_dbl3d(mem, el_displ, el_type, el_map) type(MemoryType), pointer :: mem integer(kind=MPI_ADDRESS_KIND) :: el_displ integer :: el_type + integer, dimension(:), pointer :: el_map ! local integer :: ierr + integer :: three_double_type call MPI_Get_address(mem%adbl3d, el_displ, ierr) - call MPI_Type_contiguous(mem%isize, MPI_DOUBLE_PRECISION, el_type, ierr) + if (associated(el_map)) then + call MPI_Type_contiguous(3, MPI_DOUBLE_PRECISION, three_double_type, ierr) + call MPI_Type_create_indexed_block( & + size(el_map), 1, el_map, three_double_type, el_type, ierr) + else + call MPI_Type_contiguous(mem%isize, MPI_DOUBLE_PRECISION, el_type, ierr) + end if call MPI_Type_commit(el_type, ierr) + call MPI_Type_free(three_double_type, ierr) end subroutine get_mpitype_for_dbl3d diff --git a/src/Distributed/MpiRouter.f90 b/src/Distributed/MpiRouter.f90 index 88512deb429..3021c036b4b 100644 --- a/src/Distributed/MpiRouter.f90 +++ b/src/Distributed/MpiRouter.f90 @@ -6,9 +6,8 @@ module MpiRouterModule use SimStagesModule, only: STG_TO_STR use VirtualDataListsModule, only: virtual_model_list, & virtual_exchange_list - use VirtualDataContainerModule, only: VirtualDataContainerType, & - VdcPtrType, get_vdc_from_list, & - VDC_TYPE_TO_STR + use VirtualBaseModule, only: NR_VDC_ELEMENT_MAPS + use VirtualDataContainerModule use VirtualExchangeModule, only: VirtualExchangeType use VirtualSolutionModule use MpiMessageBuilderModule @@ -171,6 +170,7 @@ end subroutine deactivate !! global models and exchanges over MPI, for a !< given stage subroutine mr_route_all(this, stage) + use MemoryManagerModule, only: mem_print_detailed class(MpiRouterType) :: this integer(I4B) :: stage @@ -185,7 +185,8 @@ subroutine mr_route_all(this, stage) call this%deactivate() if (this%enable_monitor) then - write (this%imon, '(2a)') "end routing all: ", STG_TO_STR(stage) + write (this%imon, '(2a,/)') "end routing all: ", STG_TO_STR(stage) + !call mem_print_detailed(this%imon) end if end subroutine mr_route_all @@ -214,13 +215,14 @@ subroutine mr_route_sln(this, virtual_sol, stage) end subroutine mr_route_sln - !> @brief Routes the models and exchanges - !< + !> @brief Routes the models and exchanges. This is the + !< workhorse routine subroutine mr_route_active(this, stage) class(MpiRouterType) :: this integer(I4B) :: stage ! local - integer(I4B) :: i, j, rnk + integer(I4B) :: i, j, k + integer(I4B) :: rnk integer :: ierr, msg_size ! mpi handles integer, dimension(:), allocatable :: rcv_req @@ -233,6 +235,12 @@ subroutine mr_route_active(this, stage) integer, dimension(:), allocatable :: hdr_rcv_t integer, dimension(:), allocatable :: hdr_snd_t integer, dimension(:), allocatable :: hdr_rcv_cnt + + ! maps + type(VdcReceiverMapsType), dimension(:, :), allocatable :: rcv_maps + integer, dimension(:), allocatable :: map_rcv_t + integer, dimension(:), allocatable :: map_snd_t + ! message body integer, dimension(:), allocatable :: body_rcv_t integer, dimension(:), allocatable :: body_snd_t @@ -261,6 +269,11 @@ subroutine mr_route_active(this, stage) allocate (headers(max_headers, this%receivers%size)) allocate (hdr_rcv_cnt(max_headers)) + ! allocate map data + allocate (map_snd_t(this%senders%size)) + allocate (map_rcv_t(this%receivers%size)) + allocate (rcv_maps(max_headers, this%receivers%size)) ! for every header, we potentially need the maps + ! allocate body data allocate (body_rcv_t(this%senders%size)) allocate (body_snd_t(this%receivers%size)) @@ -307,12 +320,80 @@ subroutine mr_route_active(this, stage) do j = 1, hdr_rcv_cnt(i) write (this%imon, '(6x,a,i0,a,a)') "id: ", headers(j, i)%id, & " type: ", trim(VDC_TYPE_TO_STR(headers(j, i)%container_type)) + write (this%imon, '(6x,a,99i6)') "map sizes: ", headers(j, i)%map_sizes end do end if call MPI_Type_free(hdr_rcv_t(i), ierr) end do + if (this%enable_monitor) then + write (this%imon, '(2x,a)') "== communicating maps ==" + end if + + ! allocate space for receiving maps + do i = 1, this%receivers%size + do j = 1, hdr_rcv_cnt(i) + call rcv_maps(j, i)%create(headers(j, i)%map_sizes) + end do + end do + + ! receive maps + do i = 1, this%receivers%size + rnk = this%receivers%at(i) + if (this%enable_monitor) then + write (this%imon, '(4x,a,i0)') "Ireceive maps from process: ", rnk + end if + + call this%message_builder%create_map_rcv(rcv_maps(:, i), hdr_rcv_cnt(i), & + map_rcv_t(i)) + + call MPI_Irecv(MPI_BOTTOM, 1, map_rcv_t(i), rnk, stage, & + MF6_COMM_WORLD, rcv_req(i), ierr) + end do + + ! send maps + do i = 1, this%senders%size + rnk = this%senders%at(i) + if (this%enable_monitor) then + write (this%imon, '(4x,a,i0)') "send map to process: ", rnk + end if + call this%message_builder%create_map_snd(rnk, stage, map_snd_t(i)) + call MPI_Isend(MPI_BOTTOM, 1, map_snd_t(i), rnk, stage, & + MF6_COMM_WORLD, snd_req(i), ierr) + end do + + ! wait on receiving maps + call MPI_WaitAll(this%receivers%size, rcv_req, rcv_stat, ierr) + + ! print maps + if (this%enable_monitor) then + do i = 1, this%receivers%size + rnk = this%receivers%at(i) + write (this%imon, '(4x,a,i0)') "received maps from process: ", rnk + do j = 1, hdr_rcv_cnt(i) + write (this%imon, '(6x,a,i0,a,a)') "id: ", headers(j, i)%id, & + " type: ", trim(VDC_TYPE_TO_STR(headers(j, i)%container_type)) + do k = 1, NR_VDC_ELEMENT_MAPS + write (this%imon, '(8x,i0, a,i0)') k, " nr. elements: ", & + rcv_maps(j, i)%el_maps(k)%nr_virt_elems + if (rcv_maps(j, i)%el_maps(k)%nr_virt_elems > 0) then + write (this%imon, '(8x,*(i6))') & + rcv_maps(j, i)%el_maps(k)%remote_elem_shift + end if + end do + end do + end do + end if + + ! clean up types + do i = 1, this%receivers%size + call MPI_Type_free(map_rcv_t(i), ierr) + end do + do i = 1, this%senders%size + call MPI_Type_free(map_snd_t(i), ierr) + end do + if (this%enable_monitor) then write (this%imon, '(2x,a)') "== communicating bodies ==" end if @@ -328,7 +409,7 @@ subroutine mr_route_active(this, stage) call MPI_Type_size(body_rcv_t(i), msg_size, ierr) if (msg_size > 0) then call MPI_Irecv(MPI_BOTTOM, 1, body_rcv_t(i), rnk, stage, & - MF6_COMM_WORLD, snd_req(i), ierr) + MF6_COMM_WORLD, rcv_req(i), ierr) end if if (this%enable_monitor) then @@ -343,11 +424,12 @@ subroutine mr_route_active(this, stage) write (this%imon, '(4x,a,i0)') "sending to process: ", rnk end if call this%message_builder%create_body_snd( & - rnk, stage, headers(1:hdr_rcv_cnt(i), i), body_snd_t(i)) + rnk, stage, headers(1:hdr_rcv_cnt(i), i), & + rcv_maps(:, i), body_snd_t(i)) call MPI_Type_size(body_snd_t(i), msg_size, ierr) if (msg_size > 0) then call MPI_Isend(MPI_Bottom, 1, body_snd_t(i), rnk, stage, & - MF6_COMM_WORLD, rcv_req(i), ierr) + MF6_COMM_WORLD, snd_req(i), ierr) end if if (this%enable_monitor) then @@ -357,7 +439,7 @@ subroutine mr_route_active(this, stage) end do ! wait for exchange of all messages - call MPI_WaitAll(this%senders%size, snd_req, snd_stat, ierr) + call MPI_WaitAll(this%senders%size, rcv_req, snd_stat, ierr) ! clean up types do i = 1, this%senders%size @@ -367,12 +449,19 @@ subroutine mr_route_active(this, stage) call MPI_Type_free(body_snd_t(i), ierr) end do - deallocate (rcv_req) - deallocate (snd_req) - deallocate (rcv_stat) - deallocate (hdr_rcv_t) - deallocate (hdr_snd_t) + ! done sending, clean up element maps + do i = 1, this%receivers%size + do j = 1, hdr_rcv_cnt(i) + call rcv_maps(j, i)%destroy() + end do + end do + + deallocate (rcv_req, snd_req, rcv_stat, snd_stat) + deallocate (hdr_rcv_t, hdr_snd_t, hdr_rcv_cnt) deallocate (headers) + deallocate (map_rcv_t, map_snd_t) + deallocate (rcv_maps) + deallocate (body_rcv_t, body_snd_t) end subroutine mr_route_active diff --git a/src/Distributed/VirtualBase.f90 b/src/Distributed/VirtualBase.f90 index f2af85a510e..1c46147b934 100644 --- a/src/Distributed/VirtualBase.f90 +++ b/src/Distributed/VirtualBase.f90 @@ -10,6 +10,11 @@ module VirtualBaseModule public :: get_virtual_data_from_list + integer(I4B), public, parameter :: MAP_ALL_TYPE = 0 + integer(I4B), public, parameter :: MAP_NODE_TYPE = 1 + integer(I4B), public, parameter :: MAP_CONN_TYPE = 2 + integer(I4B), public, parameter :: NR_VDC_ELEMENT_MAPS = 2 + !> This is a generic data structure to virtualize pieces !! of memory in 2 distinct ways: !! @@ -34,8 +39,13 @@ module VirtualBaseModule character(len=LENMEMPATH) :: mem_path !< memory path integer(I4B), dimension(:), allocatable :: sync_stages !< stage(s) at which to synchronize integer(I4B) :: map_type !< the type of map - integer(I4B), dimension(:), pointer, contiguous :: virtual_to_remote => null() !< contiguous list which maps virtual index to remote - integer(I4B), dimension(:), pointer, contiguous :: remote_to_virtual => null() !< sparse list which maps remote index to virtual + logical(LGP) :: is_reduced !< when true, the discontinuous remote data is compressed + !! into contiguous virtual memory + integer(I4B), dimension(:), & + pointer, contiguous :: remote_elem_shift => null() !< contiguous list with 0-based remote indexes + !! (this is important for creating mpi data types) + integer(I4B), dimension(:), & + pointer, contiguous :: remote_to_virtual => null() !< sparse list which maps remote index to virtual type(MemoryType), pointer :: virtual_mt => null() contains procedure(vm_allocate_if), deferred :: vm_allocate @@ -43,12 +53,9 @@ module VirtualBaseModule procedure :: to_base => vm_to_base procedure :: check_stage => vm_check_stage procedure :: link => vm_link + procedure :: get_element_map end type - integer(I4B), public, parameter :: MAP_ALL_TYPE = 1 - integer(I4B), public, parameter :: MAP_NODE_TYPE = 2 - integer(I4B), public, parameter :: MAP_CONN_TYPE = 3 - type, public, extends(VirtualDataType) :: VirtualIntType integer(I4B), private, pointer :: intsclr contains @@ -143,6 +150,19 @@ subroutine vm_link(this) end subroutine vm_link + !> @brief Return array with offsets for elements + !< mapped in this virtual data item + function get_element_map(this) result(el_map) + class(VirtualDataType), target :: this + integer(I4B), dimension(:), pointer, contiguous :: el_map + + el_map => null() + if (this%map_type > 0) then + el_map => this%remote_elem_shift + end if + + end function get_element_map + subroutine vm_allocate_int(this, var_name, mem_path, shape) class(VirtualIntType) :: this character(len=*) :: var_name @@ -243,7 +263,11 @@ function get_int1d(this, i_rmt) result(val) ! local integer(I4B) :: i_vrt - i_vrt = i_rmt !this%remote_to_virtual(i_rmt) + if (this%is_reduced) then + i_vrt = this%remote_to_virtual(i_rmt) + else + i_vrt = i_rmt + end if val = this%virtual_mt%aint1d(i_vrt) end function get_int1d @@ -271,7 +295,11 @@ function get_dbl1d(this, i_rmt) result(val) ! local integer(I4B) :: i_vrt - i_vrt = i_rmt !this%remote_to_virtual(i_rmt) + if (this%is_reduced) then + i_vrt = this%remote_to_virtual(i_rmt) + else + i_vrt = i_rmt + end if val = this%virtual_mt%adbl1d(i_vrt) end function get_dbl1d @@ -292,7 +320,11 @@ function get_dbl2d(this, j_cmp, i_rmt) result(val) ! local integer(I4B) :: i_vrt - i_vrt = i_rmt !this%remote_to_virtual(i_rmt) + if (this%is_reduced) then + i_vrt = this%remote_to_virtual(i_rmt) + else + i_vrt = i_rmt + end if val = this%virtual_mt%adbl2d(j_cmp, i_vrt) end function get_dbl2d diff --git a/src/Distributed/VirtualDataContainer.f90 b/src/Distributed/VirtualDataContainer.f90 index d9b9711a500..02200b1df01 100644 --- a/src/Distributed/VirtualDataContainer.f90 +++ b/src/Distributed/VirtualDataContainer.f90 @@ -1,5 +1,6 @@ module VirtualDataContainerModule use VirtualBaseModule + use SimModule, only: ustop use ListModule use KindModule, only: I4B, LGP use STLVecIntModule @@ -29,6 +30,16 @@ module VirtualDataContainerModule class(VirtualDataContainerType), pointer :: ptr => null() end type VdcPtrType + type, public :: VdcElementMapType + integer(I4B) :: nr_virt_elems !< nr. of virtualized elements + integer(I4B), dimension(:), pointer, contiguous :: remote_elem_shift => null() !< array with 0-based remote indexes + end type VdcElementMapType + + type :: VdcElementLutType + integer(I4B) :: max_remote_idx !< max. remote index, also size of the lookup table + integer(I4B), dimension(:), pointer, contiguous :: remote_to_virtual => null() !< (sparse) array with local indexes + end type VdcElementLutType + !> @brief Container (list) of virtual data items. !! !! A virtual model or exchange derives from this base @@ -47,11 +58,14 @@ module VirtualDataContainerModule integer(I4B) :: orig_rank !< the global rank of the process which holds the physical data for this container type(ListType) :: virtual_data_list !< a list with all virtual data items for this container + type(VdcElementMapType), dimension(NR_VDC_ELEMENT_MAPS) :: element_maps !< a list with all element maps + type(VdcElementLutType), dimension(NR_VDC_ELEMENT_MAPS) :: element_luts !< lookup tables from remote index to local index contains procedure :: vdc_create generic :: map => map_scalar, map_array1d, map_array2d procedure :: prepare_stage => vdc_prepare_stage procedure :: link_items => vdc_link_items + procedure :: set_element_map => vdc_set_element_map procedure :: get_vrt_mem_path => vdc_get_vrt_mem_path procedure :: destroy => vdc_destroy procedure :: set_orig_rank => vdc_set_orig_rank @@ -77,6 +91,8 @@ subroutine vdc_create(this, name, id, is_local) character(len=*) :: name integer(I4B) :: id logical(LGP) :: is_local + ! local + integer(I4B) :: i this%name = name this%id = id @@ -86,6 +102,15 @@ subroutine vdc_create(this, name, id, is_local) this%is_active = .true. this%container_type = VDC_UNKNOWN_TYPE + do i = 1, size(this%element_maps) + this%element_maps(i)%nr_virt_elems = 0 + this%element_maps(i)%remote_elem_shift => null() + end do + do i = 1, size(this%element_luts) + this%element_luts(i)%max_remote_idx = 0 + this%element_luts(i)%remote_to_virtual => null() + end do + end subroutine vdc_create !> @brief Create virtual data item, without allocation, @@ -106,7 +131,7 @@ subroutine create_field(this, field, var_name, subcmp_name, is_local) else field%mem_path = create_mem_path(this%name, subcmp_name) end if - field%virtual_to_remote => null() + field%remote_elem_shift => null() field%remote_to_virtual => null() field%virtual_mt => null() call this%add_to_list(field) @@ -154,56 +179,96 @@ subroutine vdc_link_items(this, stage) end subroutine vdc_link_items - subroutine map_scalar(this, field, stages, map_type) + !> @brief Add the source indexes associated with map_id + !! as a element map to this container, such that + !< src_indexes(1:n) = (i_orig_1 - 1, ..., i_orig_n - 1) + subroutine vdc_set_element_map(this, src_indexes, map_id) + class(VirtualDataContainerType) :: this + integer(I4B), dimension(:), pointer, contiguous :: src_indexes + integer(I4B) :: map_id + ! local + integer(I4B) :: i, idx_remote, max_remote_idx + + if (this%element_maps(map_id)%nr_virt_elems > 0) then + write (*, *) "Error, VDC element map already set" + call ustop() + end if + + this%element_maps(map_id)%nr_virt_elems = size(src_indexes) + allocate (this%element_maps(map_id)%remote_elem_shift(size(src_indexes))) + do i = 1, size(src_indexes) + this%element_maps(map_id)%remote_elem_shift(i) = src_indexes(i) - 1 + end do + + max_remote_idx = maxval(src_indexes) + this%element_luts(map_id)%max_remote_idx = max_remote_idx + allocate (this%element_luts(map_id)%remote_to_virtual(max_remote_idx)) + do i = 1, max_remote_idx + this%element_luts(map_id)%remote_to_virtual(i) = -1 + end do + do i = 1, size(src_indexes) + idx_remote = src_indexes(i) + this%element_luts(map_id)%remote_to_virtual(idx_remote) = i + end do + + end subroutine vdc_set_element_map + + subroutine map_scalar(this, field, stages, map_id) class(VirtualDataContainerType) :: this class(VirtualDataType), pointer :: field integer(I4B), dimension(:) :: stages - integer(I4B) :: map_type + integer(I4B) :: map_id - call this%map_internal(field, (/0/), stages, map_type) + call this%map_internal(field, (/0/), stages, map_id) end subroutine map_scalar - subroutine map_array1d(this, field, nrow, stages, map_type) + subroutine map_array1d(this, field, nrow, stages, map_id) class(VirtualDataContainerType) :: this class(VirtualDataType), pointer :: field integer(I4B) :: nrow integer(I4B), dimension(:) :: stages - integer(I4B) :: map_type + integer(I4B) :: map_id - call this%map_internal(field, (/nrow/), stages, map_type) + call this%map_internal(field, (/nrow/), stages, map_id) end subroutine map_array1d - subroutine map_array2d(this, field, ncol, nrow, stages, map_type) + subroutine map_array2d(this, field, ncol, nrow, stages, map_id) class(VirtualDataContainerType) :: this class(VirtualDataType), pointer :: field integer(I4B) :: ncol integer(I4B) :: nrow integer(I4B), dimension(:) :: stages - integer(I4B) :: map_type + integer(I4B) :: map_id - call this%map_internal(field, (/ncol, nrow/), stages, map_type) + call this%map_internal(field, (/ncol, nrow/), stages, map_id) end subroutine map_array2d - subroutine map_internal(this, field, shape, stages, map_type) + subroutine map_internal(this, field, shape, stages, map_id) class(VirtualDataContainerType) :: this class(VirtualDataType), pointer :: field integer(I4B), dimension(:) :: shape integer(I4B), dimension(:) :: stages - integer(I4B) :: map_type + integer(I4B) :: map_id ! local character(len=LENMEMPATH) :: vmem_path logical(LGP) :: found field%sync_stages = stages - field%map_type = map_type + field%map_type = map_id + field%is_reduced = .false. if (field%is_remote) then ! create new virtual memory item vmem_path = this%get_vrt_mem_path(field%var_name, field%subcmp_name) call field%vm_allocate(field%var_name, vmem_path, shape) call get_from_memorylist(field%var_name, vmem_path, field%virtual_mt, found) + if (map_id > 0) then + field%is_reduced = .true. + field%remote_to_virtual => this%element_luts(map_id)%remote_to_virtual + field%remote_elem_shift => this%element_maps(map_id)%remote_elem_shift + end if end if end subroutine map_internal @@ -326,6 +391,17 @@ subroutine vdc_destroy(this) integer(I4B) :: i class(*), pointer :: obj + do i = 1, size(this%element_maps) + if (associated(this%element_maps(i)%remote_elem_shift)) then + deallocate (this%element_maps(i)%remote_elem_shift) + end if + end do + do i = 1, size(this%element_luts) + if (associated(this%element_luts(i)%remote_to_virtual)) then + deallocate (this%element_luts(i)%remote_to_virtual) + end if + end do + do i = 1, this%virtual_data_list%Count() obj => this%virtual_data_list%GetItem(i) select type (obj) diff --git a/src/Distributed/VirtualDataManager.f90 b/src/Distributed/VirtualDataManager.f90 index c702ff05172..1d905890f49 100644 --- a/src/Distributed/VirtualDataManager.f90 +++ b/src/Distributed/VirtualDataManager.f90 @@ -2,12 +2,14 @@ module VirtualDataManagerModule use KindModule, only: I4B use STLVecIntModule use VirtualDataListsModule, only: virtual_model_list, virtual_exchange_list + use VirtualBaseModule, only: MAP_NODE_TYPE, MAP_CONN_TYPE use VirtualModelModule, only: get_virtual_model use VirtualExchangeModule, only: get_virtual_exchange use VirtualSolutionModule use VirtualDataContainerModule use RouterBaseModule use RouterFactoryModule, only: create_router + use ListsModule, only: basesolutionlist use NumericalSolutionModule, only: NumericalSolutionType use NumericalModelModule, only: NumericalModelType, GetNumericalModelFromList use NumericalExchangeModule, only: NumericalExchangeType, & @@ -22,7 +24,7 @@ module VirtualDataManagerModule type, public :: VirtualDataManagerType integer(I4B) :: nr_solutions integer(I4B), dimension(:), allocatable :: solution_ids - class(VirtualSolutionType), dimension(:), pointer :: virtual_solutions + type(VirtualSolutionType), dimension(:), pointer :: virtual_solutions class(RouterBaseType), pointer :: router contains procedure :: create => vds_create @@ -81,7 +83,7 @@ subroutine vds_add_solution(this, num_sol) class(NumericalSolutionType), pointer :: num_sol ! local integer(I4B) :: i, im, ix, ihm, ihx - class(VirtualSolutionType), pointer :: virt_sol + type(VirtualSolutionType), pointer :: virt_sol class(NumericalModelType), pointer :: num_mod class(DisConnExchangeType), pointer :: exg class(SpatialModelConnectionType), pointer :: conn @@ -98,6 +100,7 @@ subroutine vds_add_solution(this, num_sol) ! build the virtual solution this%solution_ids(this%nr_solutions) = num_sol%id virt_sol%solution_id = num_sol%id + virt_sol%numerical_solution => num_sol ! 1) adding all local models from the solution do im = 1, num_sol%modellist%Count() @@ -131,6 +134,8 @@ subroutine vds_add_solution(this, num_sol) allocate (virt_sol%models(model_ids%size)) allocate (virt_sol%exchanges(exchange_ids%size)) + allocate (virt_sol%interface_map) + call virt_sol%interface_map%init(model_ids%size, exchange_ids%size) ! select virtual containers for models/exchanges do i = 1, model_ids%size @@ -151,11 +156,56 @@ end subroutine vds_add_solution !> @brief Reduce the halo for all solutions. This will !< activate the mapping tables in the virtual data items. subroutine vds_reduce_halo(this) + use InputOutputModule, only: getunit + use SimVariablesModule, only: proc_id + use IndexMapModule class(VirtualDataManagerType) :: this + ! local + integer(I4B) :: ivm, isol, iexg + integer(I4B) :: outunit + character(len=128) :: monitor_file + type(VirtualSolutionType), pointer :: virt_sol + class(NumericalSolutionType), pointer :: num_sol + class(SpatialModelConnectionType), pointer :: conn + class(VirtualDataContainerType), pointer :: vdc + type(IndexMapType), pointer :: nmap, cmap ! merge the interface maps over this process + do isol = 1, this%nr_solutions + virt_sol => this%virtual_solutions(isol) + num_sol => virt_sol%numerical_solution + do iexg = 1, num_sol%exchangelist%Count() + conn => get_smc_from_list(num_sol%exchangelist, iexg) + if (.not. associated(conn)) cycle + ! these are interface models, now merge their + ! interface maps + call virt_sol%interface_map%add(conn%interface_map) + end do + end do + + ! some testing + outunit = getunit() + write (monitor_file, '(a,i0,a)') "iface.p", proc_id, ".log" + open (unit=outunit, file=monitor_file) + do isol = 1, this%nr_solutions + write (outunit, '(a,i0,/)') "interface mape for solution ", & + this%virtual_solutions(isol)%solution_id + call this%virtual_solutions(isol)%interface_map%print_interface(outunit) + end do + close (outunit) ! assign reduced maps to virtual data containers + do isol = 1, this%nr_solutions + virt_sol => this%virtual_solutions(isol) + do ivm = 1, size(virt_sol%models) ! TODO_MJR: other containers, exchanges? + vdc => virt_sol%models(ivm)%ptr + if (vdc%is_local) cycle + nmap => virt_sol%interface_map%get_node_map(vdc%id) + cmap => virt_sol%interface_map%get_connection_map(vdc%id) + call vdc%set_element_map(nmap%src_idx, MAP_NODE_TYPE) + call vdc%set_element_map(cmap%src_idx, MAP_CONN_TYPE) + end do + end do end subroutine vds_reduce_halo @@ -315,6 +365,8 @@ subroutine destroy(this) do i = 1, this%nr_solutions deallocate (this%virtual_solutions(i)%models) deallocate (this%virtual_solutions(i)%exchanges) + call this%virtual_solutions(i)%interface_map%destroy() + deallocate (this%virtual_solutions(i)%interface_map) end do deallocate (this%virtual_solutions) diff --git a/src/Distributed/VirtualGwfModel.f90 b/src/Distributed/VirtualGwfModel.f90 index c1c1648820a..b68bd7f21f7 100644 --- a/src/Distributed/VirtualGwfModel.f90 +++ b/src/Distributed/VirtualGwfModel.f90 @@ -122,7 +122,7 @@ subroutine vgwf_prepare_stage(this, stage) else if (stage == STG_BEFORE_AR) then - nr_nodes = this%dis_nodes%get() + nr_nodes = this%element_maps(MAP_NODE_TYPE)%nr_virt_elems ! Num. model data call this%map(this%x%to_base(), nr_nodes, & (/STG_BEFORE_AR, STG_BEFORE_AD, STG_BEFORE_CF/), & diff --git a/src/Distributed/VirtualSolution.f90 b/src/Distributed/VirtualSolution.f90 index 7686c6a34d5..94a7c94ca5b 100644 --- a/src/Distributed/VirtualSolution.f90 +++ b/src/Distributed/VirtualSolution.f90 @@ -2,6 +2,8 @@ module VirtualSolutionModule use KindModule, only: I4B use ListModule use VirtualDataContainerModule, only: VdcPtrType + use NumericalSolutionModule ! TODO_MJR: this should not be here!! + use InterfaceMapModule implicit none private @@ -9,10 +11,13 @@ module VirtualSolutionModule !< for convenience, it never owns any of it type, public :: VirtualSolutionType integer(I4B) :: solution_id = -1 - type(VdcPtrType), dimension(:), pointer :: models => null() - type(VdcPtrType), dimension(:), pointer :: exchanges => null() - ! type(ListType) :: exchange_movers - ! type(ListType) :: etc... + type(VdcPtrType), dimension(:), pointer :: models => null() !< the models as virtual data containers (wrapped) + type(VdcPtrType), dimension(:), pointer :: exchanges => null() !< the exchanges as virtual data containers (wrapped) + class(NumericalSolutionType), pointer :: numerical_solution => null() !< points back to the actual numerical solution + type(InterfaceMapType), pointer :: interface_map => null() !< contains the aggregate interface map for the solution + !! NB: the aggregation is over multiple interface models + !! and there is no unique numbering there. The target + !! indexes should therefore be considere invalid. end type VirtualSolutionType end module VirtualSolutionModule diff --git a/src/Model/Connection/GridConnection.f90 b/src/Model/Connection/GridConnection.f90 index 4c5789f3fec..150cb0d9280 100644 --- a/src/Model/Connection/GridConnection.f90 +++ b/src/Model/Connection/GridConnection.f90 @@ -1084,11 +1084,11 @@ subroutine buildInterfaceMap(this) end do ! and copy into interface map - allocate (imap%node_map(im)%src_idx(src_idx_tmp%size)) - allocate (imap%node_map(im)%tgt_idx(tgt_idx_tmp%size)) + allocate (imap%node_maps(im)%src_idx(src_idx_tmp%size)) + allocate (imap%node_maps(im)%tgt_idx(tgt_idx_tmp%size)) do i = 1, src_idx_tmp%size - imap%node_map(im)%src_idx(i) = src_idx_tmp%at(i) - imap%node_map(im)%tgt_idx(i) = tgt_idx_tmp%at(i) + imap%node_maps(im)%src_idx(i) = src_idx_tmp%at(i) + imap%node_maps(im)%tgt_idx(i) = tgt_idx_tmp%at(i) end do call src_idx_tmp%destroy() @@ -1117,11 +1117,11 @@ subroutine buildInterfaceMap(this) end do ! copy into interface map - allocate (imap%connection_map(im)%src_idx(src_idx_tmp%size)) - allocate (imap%connection_map(im)%tgt_idx(tgt_idx_tmp%size)) + allocate (imap%conn_maps(im)%src_idx(src_idx_tmp%size)) + allocate (imap%conn_maps(im)%tgt_idx(tgt_idx_tmp%size)) do i = 1, src_idx_tmp%size - imap%connection_map(im)%src_idx(i) = src_idx_tmp%at(i) - imap%connection_map(im)%tgt_idx(i) = tgt_idx_tmp%at(i) + imap%conn_maps(im)%src_idx(i) = src_idx_tmp%at(i) + imap%conn_maps(im)%tgt_idx(i) = tgt_idx_tmp%at(i) end do call src_idx_tmp%destroy() @@ -1169,13 +1169,13 @@ subroutine buildInterfaceMap(this) call sign_tmp%push_back(-1) end do - allocate (imap%exchange_map(ix)%src_idx(src_idx_tmp%size)) - allocate (imap%exchange_map(ix)%tgt_idx(tgt_idx_tmp%size)) - allocate (imap%exchange_map(ix)%sign(sign_tmp%size)) + allocate (imap%exchange_maps(ix)%src_idx(src_idx_tmp%size)) + allocate (imap%exchange_maps(ix)%tgt_idx(tgt_idx_tmp%size)) + allocate (imap%exchange_maps(ix)%sign(sign_tmp%size)) do i = 1, src_idx_tmp%size - imap%exchange_map(ix)%src_idx(i) = src_idx_tmp%at(i) - imap%exchange_map(ix)%tgt_idx(i) = tgt_idx_tmp%at(i) - imap%exchange_map(ix)%sign(i) = sign_tmp%at(i) + imap%exchange_maps(ix)%src_idx(i) = src_idx_tmp%at(i) + imap%exchange_maps(ix)%tgt_idx(i) = tgt_idx_tmp%at(i) + imap%exchange_maps(ix)%sign(i) = sign_tmp%at(i) end do call src_idx_tmp%destroy() diff --git a/src/Model/Connection/GwfGwfConnection.f90 b/src/Model/Connection/GwfGwfConnection.f90 index 8d97fa87bca..8f0caf24546 100644 --- a/src/Model/Connection/GwfGwfConnection.f90 +++ b/src/Model/Connection/GwfGwfConnection.f90 @@ -546,7 +546,7 @@ subroutine setFlowToExchange(this) if (this%owns_exchange) then gwfEx => this%gwfExchange - map => this%interface_map%exchange_map(this%interface_map%prim_exg_idx) + map => this%interface_map%exchange_maps(this%interface_map%prim_exg_idx) ! use (half of) the exchange map in reverse: do i = 1, size(map%src_idx) diff --git a/src/Model/Connection/GwtGwtConnection.f90 b/src/Model/Connection/GwtGwtConnection.f90 index e48bb6096f8..0132dc9aa91 100644 --- a/src/Model/Connection/GwtGwtConnection.f90 +++ b/src/Model/Connection/GwtGwtConnection.f90 @@ -457,7 +457,7 @@ subroutine setFlowToExchange(this) if (this%exchangeIsOwned) then gwtEx => this%gwtExchange - map => this%interface_map%exchange_map(this%interface_map%prim_exg_idx) + map => this%interface_map%exchange_maps(this%interface_map%prim_exg_idx) ! use (half of) the exchange map in reverse: do i = 1, size(map%src_idx) diff --git a/src/Solution/ExplicitSolution.f90 b/src/Solution/ExplicitSolution.f90 index fa1618e723b..8cd8fb7f24b 100644 --- a/src/Solution/ExplicitSolution.f90 +++ b/src/Solution/ExplicitSolution.f90 @@ -392,4 +392,4 @@ function get_exchanges(this) result(exchanges) type(ListType), pointer :: exchanges end function get_exchanges -end module ExplicitSolutionModule \ No newline at end of file +end module ExplicitSolutionModule diff --git a/src/Solution/NumericalSolution.f90 b/src/Solution/NumericalSolution.f90 index 11d237da285..e9b518fccba 100644 --- a/src/Solution/NumericalSolution.f90 +++ b/src/Solution/NumericalSolution.f90 @@ -1172,17 +1172,18 @@ end subroutine sln_ot !! !< subroutine sln_fp(this) + use SimVariablesModule, only: iout ! -- dummy variables class(NumericalSolutionType) :: this !< NumericalSolutionType instance ! ! -- write timer output - if (IDEVELOPMODE == 1 .and. this%linmeth == 1) then - write (this%imslinear%iout, '(//1x,a,1x,a,1x,a)') & + if (IDEVELOPMODE == 1) then + write (iout, '(//1x,a,1x,a,1x,a)') & 'Solution', trim(adjustl(this%name)), 'summary' - write (this%imslinear%iout, "(1x,70('-'))") - write (this%imslinear%iout, '(1x,a,1x,g0,1x,a)') & + write (iout, "(1x,70('-'))") + write (iout, '(1x,a,1x,g0,1x,a)') & 'Total formulate time: ', this%ttform, 'seconds' - write (this%imslinear%iout, '(1x,a,1x,g0,1x,a,/)') & + write (iout, '(1x,a,1x,g0,1x,a,/)') & 'Total solution time: ', this%ttsoln, 'seconds' end if ! diff --git a/src/Solution/PETSc/PetscConvergence.F90 b/src/Solution/PETSc/PetscConvergence.F90 index 02181ea757c..36241301739 100644 --- a/src/Solution/PETSc/PetscConvergence.F90 +++ b/src/Solution/PETSc/PetscConvergence.F90 @@ -14,6 +14,7 @@ module PetscConvergenceModule Vec :: x_old Vec :: delta_x real(DP) :: dvclose + integer(I4B) :: max_its end type PetscContextType type(ListType) :: ctx_list @@ -53,6 +54,7 @@ end subroutine petsc_remove_context !> @brief Routine to check the convergence. This is called !< from within PETSc. subroutine petsc_check_convergence(ksp, n, rnorm, flag, ctx_id, ierr) + use SimVariablesModule, only: proc_id KSP :: ksp !< Iterative context PetscInt :: n !< Iteration number PetscReal :: rnorm !< 2-norm (preconditioned) residual value @@ -60,7 +62,7 @@ subroutine petsc_check_convergence(ksp, n, rnorm, flag, ctx_id, ierr) PetscInt :: ctx_id !< index into the static context list PetscErrorCode :: ierr !< error ! local - PetscScalar :: alpha = -1.0 + PetscScalar :: alpha real(DP) :: norm Vec :: x class(PetscContextType), pointer :: petsc_context @@ -84,6 +86,7 @@ subroutine petsc_check_convergence(ksp, n, rnorm, flag, ctx_id, ierr) return end if + alpha = -1.0 call VecWAXPY(petsc_context%delta_x, alpha, x, petsc_context%x_old, ierr) CHKERRQ(ierr) @@ -94,9 +97,17 @@ subroutine petsc_check_convergence(ksp, n, rnorm, flag, ctx_id, ierr) CHKERRQ(ierr) if (norm < petsc_context%dvclose) then + if (proc_id == 0) then + write (*, *) "converged: ", norm + end if flag = KSP_CONVERGED_HAPPY_BREAKDOWN ! Converged else flag = KSP_CONVERGED_ITERATING ! Not yet converged + if (n == petsc_context%max_its) then + ! ran out of iterations before convergence + ! has been reached + flag = KSP_DIVERGED_ITS + end if end if end subroutine petsc_check_convergence diff --git a/src/Solution/PETSc/PetscSolver.F90 b/src/Solution/PETSc/PetscSolver.F90 index 208d59f2869..4e518720909 100644 --- a/src/Solution/PETSc/PetscSolver.F90 +++ b/src/Solution/PETSc/PetscSolver.F90 @@ -126,6 +126,7 @@ subroutine create_convergence_check(this) PetscErrorCode :: ierr this%petsc_ctx%dvclose = this%dvclose + this%petsc_ctx%max_its = this%nitermax call MatCreateVecs( & this%mat_petsc, this%petsc_ctx%x_old, PETSC_NULL_VEC, ierr) CHKERRQ(ierr) diff --git a/src/Utilities/Memory/MemoryManager.f90 b/src/Utilities/Memory/MemoryManager.f90 index 3e85b34472c..3f636210151 100644 --- a/src/Utilities/Memory/MemoryManager.f90 +++ b/src/Utilities/Memory/MemoryManager.f90 @@ -38,6 +38,7 @@ module MemoryManagerModule public :: copy_dbl1d public :: memorylist + public :: mem_print_detailed type(MemoryListType) :: memorylist type(TableType), pointer :: memtab => null() @@ -2807,12 +2808,7 @@ subroutine mem_write_usage(iout) ! ! -- Write table with all variables for iprmem == 2 if (iprmem == 2) then - call mem_detailed_table(iout, memorylist%count()) - do ipos = 1, memorylist%count() - mt => memorylist%Get(ipos) - call mt%table_entry(memtab) - end do - call mem_cleanup_table() + call mem_print_detailed(iout) end if ! ! -- Write total memory allocation @@ -2822,6 +2818,21 @@ subroutine mem_write_usage(iout) return end subroutine mem_write_usage + subroutine mem_print_detailed(iout) + integer(I4B) :: iout + ! local + class(MemoryType), pointer :: mt + integer(I4B) :: ipos + + call mem_detailed_table(iout, memorylist%count()) + do ipos = 1, memorylist%count() + mt => memorylist%Get(ipos) + call mt%table_entry(memtab) + end do + call mem_cleanup_table() + + end subroutine mem_print_detailed + !> @brief Deallocate memory in the memory manager !< subroutine mem_da() diff --git a/src/mf6core.f90 b/src/mf6core.f90 index 2851b134e7a..d9e84346244 100644 --- a/src/mf6core.f90 +++ b/src/mf6core.f90 @@ -262,7 +262,7 @@ end subroutine create_lstfile subroutine static_input_load() ! -- modules use ConstantsModule, only: LENMEMPATH - use SimVariablesModule, only: simulation_mode, proc_id, iout + use SimVariablesModule, only: simulation_mode, proc_id, iout, nr_procs use IdmSimulationModule, only: simnam_load, load_models use MemoryHelperModule, only: create_mem_path use MemoryManagerModule, only: mem_setptr @@ -285,7 +285,7 @@ subroutine static_input_load() model_loadmask = 0 ! ! -- set mask - if (simulation_mode == 'PARALLEL') then + if (simulation_mode == 'PARALLEL' .and. nr_procs > 1) then ! TODO under development model_loadmask(proc_id + 1) = 1 else From 2fe84a0446234c3d4b97dad2987f133c4dc03097 Mon Sep 17 00:00:00 2001 From: w-bonelli Date: Sat, 1 Apr 2023 10:16:36 -0400 Subject: [PATCH 059/123] docs: fix minor issues and add some details to dev docs (#1191) --- DEVELOPER.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/DEVELOPER.md b/DEVELOPER.md index d257d6795a7..cc53837d8f0 100644 --- a/DEVELOPER.md +++ b/DEVELOPER.md @@ -64,7 +64,9 @@ Some additional, optional tools are also discussed below. ### Fortran compiler -The GNU Fortran compiler `gfortran` or the Intel Fortran compiler `ifort` can be used to compile MODFLOW 6. +The GNU Fortran compiler `gfortran` or the Intel Fortran Classic compiler `ifort` can be used to compile MODFLOW 6. + +**Note:** the next-generation Intel Fortran compiler `ifx` is not yet compatible with MODFLOW 6. #### GNU Fortran @@ -94,7 +96,7 @@ GNU Fortran can be installed on all three major platforms. #### Intel Fortran -Intel Fortran can also be used to compile MODFLOW 6 and associated utilities. The `ifort` compiler is available in the [Intel oneAPI HPC Toolkit](https://software.intel.com/content/www/us/en/develop/tools/oneapi/hpc-toolkit/download.html). An installer is bundled with the download. A minimal +Intel Fortran can also be used to compile MODFLOW 6 and associated utilities. The `ifort` compiler is available in the [Intel oneAPI HPC Toolkit](https://software.intel.com/content/www/us/en/develop/tools/oneapi/hpc-toolkit/download.html). An installer is bundled with the download. A number of environment variables must be set before using Intel Fortran. General information can be found [here](https://www.intel.com/content/www/us/en/develop/documentation/oneapi-programming-guide/top/oneapi-development-environment-setup.html), with specific instructions to configure a shell session for `ifort` [here](https://www.intel.com/content/www/us/en/develop/documentation/fortran-compiler-oneapi-dev-guide-and-reference/top/compiler-setup/use-the-command-line/specifying-the-location-of-compiler-components.html). @@ -102,13 +104,12 @@ A number of environment variables must be set before using Intel Fortran. Genera On Windows, [Visual Studio](https://visualstudio.microsoft.com) and a number of libraries must be installed for `ifort` to work. The required libraries can be installed by ticking the "Desktop Development with C++" checkbox in the Visual Studio Installer's Workloads tab. -**Note:** Invoking the `setvars.bat` scripts from a Powershell session will *not* put `ifort` on the path, since [batch script environments are local to their process](https://stackoverflow.com/a/49028002/6514033). Either invoke `ifort` from command prompt or relaunch PowerShell, e.g. +**Note:** Invoking the `setvars.bat` scripts from a Powershell session will *not* put `ifort` on the path, since [batch script environments are local to their process](https://stackoverflow.com/a/49028002/6514033). To relaunch PowerShell with oneAPI variables configured: ``` cmd.exe "/K" '"C:\Program Files (x86)\Intel\oneAPI\setvars-vcvarsall.bat" && "C:\Program Files (x86)\Intel\oneAPI\compiler\latest\env\vars.bat" && powershell' ``` - ### Python Python 3.8+ is required to run MODFLOW 6 tests. A Conda distribution (e.g. [miniconda](https://docs.conda.io/en/latest/miniconda.html) or [Anaconda](https://www.anaconda.com/products/individual) is recommended. Python dependencies are specified in `environment.yml`. To create an environment, run from the project root: @@ -242,13 +243,13 @@ A few tasks must be completed before running tests: - build local MODFLOW 6 development version - rebuild the last MODFLOW 6 release - install additional executables -- update FloPy plugins +- update FloPy packages and plugins - clone MODFLOW 6 test model and example repositories Tests expect binaries to live in the `bin` directory relative to the project root, as configured above in the `meson` commands. Binaries are organized as follows: - local development binaries in the top-level `bin` folder -- executables rebuilt in development mode from the latest release in `bin/rebuilt` +- binaries rebuilt in development mode from the latest release in `bin/rebuilt` - related programs installed from the [executables distribution](https://github.com/MODFLOW-USGS/executables/releases) live in `bin/downloaded` Tests must be run from the `autotest` folder. @@ -424,4 +425,4 @@ Tests should ideally follow a few conventions for easier maintenance: - `@pytest.mark.repo` if the test relies on external model repositories - `@pytest.mark.regression` if the test compares results from different versions -The test suite must pass before code can be merged, so be sure it passes locally before opening a PR. +**Note:** If all three external model repositories are not installed as described above, some tests will be skipped. The full test suite includes >750 cases. All must pass before changes can be merged into this repository. From 2603b6c4c5317f0968588d0bbe77cb3c7c98a38a Mon Sep 17 00:00:00 2001 From: mjr-deltares <45555666+mjr-deltares@users.noreply.github.com> Date: Mon, 3 Apr 2023 13:57:05 +0200 Subject: [PATCH 060/123] feat(api): add initialization with MPI to API (#1189) * mpi comm is now assignable (prep. for parallel API) * initialize parallel simulation through API * fix merge damage * conditional compilation for initialize_mpi * add preprocessing msvs * adjust meson file --- msvs/mf6bmi.vfproj | 10 ++++++- src/Distributed/MpiRouter.f90 | 14 +++++----- src/Distributed/MpiRunControl.F90 | 42 ++++++++++++++++++++++------- src/Distributed/MpiWorld.f90 | 44 +++++++++++++++++++++++++------ src/Solution/ParallelSolution.f90 | 5 +++- srcbmi/meson.build | 2 +- srcbmi/{mf6xmi.f90 => mf6xmi.F90} | 23 ++++++++++++++++ 7 files changed, 112 insertions(+), 28 deletions(-) rename srcbmi/{mf6xmi.f90 => mf6xmi.F90} (94%) diff --git a/msvs/mf6bmi.vfproj b/msvs/mf6bmi.vfproj index 46bc5930c52..c96dfca47e5 100644 --- a/msvs/mf6bmi.vfproj +++ b/msvs/mf6bmi.vfproj @@ -53,5 +53,13 @@ - + + + + + + + + + diff --git a/src/Distributed/MpiRouter.f90 b/src/Distributed/MpiRouter.f90 index 3021c036b4b..cf0e544e016 100644 --- a/src/Distributed/MpiRouter.f90 +++ b/src/Distributed/MpiRouter.f90 @@ -101,7 +101,7 @@ subroutine mr_initialize(this) end do call MPI_Allreduce(MPI_IN_PLACE, this%model_proc_ids, nr_models, & - MPI_INTEGER, MPI_SUM, MF6_COMM_WORLD, ierr) + MPI_INTEGER, MPI_SUM, this%mpi_world%comm, ierr) ! set the process id to the models and exchanges do i = 1, nr_models @@ -290,7 +290,7 @@ subroutine mr_route_active(this, stage) end if call this%message_builder%create_header_rcv(hdr_rcv_t(i)) call MPI_Irecv(headers(:, i), max_headers, hdr_rcv_t(i), rnk, stage, & - MF6_COMM_WORLD, rcv_req(i), ierr) + this%mpi_world%comm, rcv_req(i), ierr) ! don't free mpi datatype, we need the count below end do @@ -302,7 +302,7 @@ subroutine mr_route_active(this, stage) end if call this%message_builder%create_header_snd(rnk, stage, hdr_snd_t(i)) call MPI_Isend(MPI_BOTTOM, 1, hdr_snd_t(i), rnk, stage, & - MF6_COMM_WORLD, snd_req(i), ierr) + this%mpi_world%comm, snd_req(i), ierr) call MPI_Type_free(hdr_snd_t(i), ierr) end do @@ -349,7 +349,7 @@ subroutine mr_route_active(this, stage) map_rcv_t(i)) call MPI_Irecv(MPI_BOTTOM, 1, map_rcv_t(i), rnk, stage, & - MF6_COMM_WORLD, rcv_req(i), ierr) + this%mpi_world%comm, rcv_req(i), ierr) end do ! send maps @@ -360,7 +360,7 @@ subroutine mr_route_active(this, stage) end if call this%message_builder%create_map_snd(rnk, stage, map_snd_t(i)) call MPI_Isend(MPI_BOTTOM, 1, map_snd_t(i), rnk, stage, & - MF6_COMM_WORLD, snd_req(i), ierr) + this%mpi_world%comm, snd_req(i), ierr) end do ! wait on receiving maps @@ -409,7 +409,7 @@ subroutine mr_route_active(this, stage) call MPI_Type_size(body_rcv_t(i), msg_size, ierr) if (msg_size > 0) then call MPI_Irecv(MPI_BOTTOM, 1, body_rcv_t(i), rnk, stage, & - MF6_COMM_WORLD, rcv_req(i), ierr) + this%mpi_world%comm, rcv_req(i), ierr) end if if (this%enable_monitor) then @@ -429,7 +429,7 @@ subroutine mr_route_active(this, stage) call MPI_Type_size(body_snd_t(i), msg_size, ierr) if (msg_size > 0) then call MPI_Isend(MPI_Bottom, 1, body_snd_t(i), rnk, stage, & - MF6_COMM_WORLD, snd_req(i), ierr) + this%mpi_world%comm, snd_req(i), ierr) end if if (this%enable_monitor) then diff --git a/src/Distributed/MpiRunControl.F90 b/src/Distributed/MpiRunControl.F90 index eb2f334bff0..c66897327f1 100644 --- a/src/Distributed/MpiRunControl.F90 +++ b/src/Distributed/MpiRunControl.F90 @@ -42,34 +42,52 @@ subroutine mpi_ctrl_start(this) integer :: ierr character(len=*), parameter :: petsc_db_file = '.petscrc' logical(LGP) :: petsc_db_exists, wait_dbg, is_parallel_mode - class(MpiWorldType), pointer :: mpi_world - ! if PETSc we need their initialize + type(MpiWorldType), pointer :: mpi_world + wait_dbg = .false. + mpi_world => get_mpi_world() + + ! if PETSc we need their initialize #if defined(__WITH_PETSC__) + ! PetscInitialize calls MPI_Init only when it is not called yet, + ! which could be through the API. If it is already called, we + ! should assign the MPI communicator to PETSC_COMM_WORLD first + ! (PETSc manual) + if (mpi_world%has_comm()) then + PETSC_COMM_WORLD = mpi_world%comm + end if + inquire (file=petsc_db_file, exist=petsc_db_exists) if (.not. petsc_db_exists) then write (*, *) 'WARNING. PETSc database file not found: '//petsc_db_file call PetscInitialize(ierr) + CHKERRQ(ierr) else call PetscInitialize(petsc_db_file, ierr) + CHKERRQ(ierr) end if - MF6_COMM_WORLD = PETSC_COMM_WORLD - CHKERRQ(ierr) + + if (.not. mpi_world%has_comm()) then + call mpi_world%set_comm(PETSC_COMM_WORLD) + end if + call PetscOptionsHasName(PETSC_NULL_OPTIONS, PETSC_NULL_CHARACTER, & '-wait_dbg', wait_dbg, ierr) + CHKERRQ(ierr) call PetscOptionsHasName(PETSC_NULL_OPTIONS, PETSC_NULL_CHARACTER, & '-p', is_parallel_mode, ierr) CHKERRQ(ierr) #else - call MPI_Init(ierr) - MF6_COMM_WORLD = MPI_COMM_WORLD + if (.not. mpi_world%has_comm()) then + call MPI_Init(ierr) + call mpi_world%set_comm(MPI_COMM_WORLD) + end if #endif - mpi_world => get_mpi_world() call mpi_world%init() - call MPI_Comm_size(MF6_COMM_WORLD, nr_procs, ierr) - call MPI_Comm_rank(MF6_COMM_WORLD, proc_id, ierr) + call MPI_Comm_size(mpi_world%comm, nr_procs, ierr) + call MPI_Comm_rank(mpi_world%comm, proc_id, ierr) ! possibly wait to attach debugger here if (wait_dbg) call this%wait_for_debugger() @@ -88,13 +106,15 @@ subroutine wait_for_debugger(this) ! local integer :: ierr integer(I4B) :: icnt + type(MpiWorldType), pointer :: mpi_world + mpi_world => get_mpi_world() if (proc_id == 0) then icnt = 0 write (*, *) 'Hit enter to continue...' read (*, *) end if - call MPI_Barrier(MF6_COMM_WORLD, ierr) + call MPI_Barrier(mpi_world%comm, ierr) end subroutine wait_for_debugger @@ -105,6 +125,8 @@ subroutine mpi_ctrl_finish(this) ! finish mpi #if defined(__WITH_PETSC__) + ! NB: PetscFinalize calls MPI_Finalize only when MPI_Init + ! was called before PetscInitialize call PetscFinalize(ierr) CHKERRQ(ierr) #else diff --git a/src/Distributed/MpiWorld.f90 b/src/Distributed/MpiWorld.f90 index 2ffa45fa15b..8962c99d757 100644 --- a/src/Distributed/MpiWorld.f90 +++ b/src/Distributed/MpiWorld.f90 @@ -1,17 +1,20 @@ module MpiWorldModule - use KindModule, only: I4B + use KindModule, only: I4B, LGP use SimVariablesModule, only: nr_procs, proc_id use mpi implicit none private public :: get_mpi_world - integer(I4B), public :: MF6_COMM_WORLD = -1 type, public :: MpiWorldType - integer(I4B) :: mpi_rank - integer(I4B) :: world_size + integer(I4B) :: mpi_rank !< the id for this process + integer(I4B) :: world_size !< the total nr. of processes in the MPI job + integer(I4B), pointer :: comm => null() !< the MF6 communicator, either was it passed to + !! use through the API, or we created MPI_COMM_WORLD contains + procedure :: has_comm => mpiw_has_comm + procedure :: set_comm => mpiw_set_comm procedure :: init => mpiw_init procedure :: begin_order => mpiw_begin_order procedure :: end_order => mpiw_end_order @@ -33,13 +36,34 @@ function get_mpi_world() result(world) end function get_mpi_world + !> @brief Returns true when a communicator has been set. + !< + function mpiw_has_comm(this) result(has_comm) + class(MpiWorldType) :: this + logical(LGP) :: has_comm + + has_comm = associated(this%comm) + + end function mpiw_has_comm + + !> @brief Sets a communicator on this world, can + !< be done only once. + subroutine mpiw_set_comm(this, comm) + class(MpiWorldType) :: this + integer(I4B) :: comm + + allocate (this%comm) + this%comm = comm + + end subroutine mpiw_set_comm + subroutine mpiw_init(this) class(MpiWorldType) :: this ! local integer :: ierr - call MPI_Comm_size(MF6_COMM_WORLD, this%world_size, ierr) - call MPI_Comm_rank(MF6_COMM_WORLD, this%mpi_rank, ierr) + call MPI_Comm_size(this%comm, this%world_size, ierr) + call MPI_Comm_rank(this%comm, this%mpi_rank, ierr) nr_procs = this%world_size proc_id = this%mpi_rank @@ -54,7 +78,7 @@ subroutine mpiw_begin_order(this) if (this%mpi_rank > 0) then call mpi_recv(buffer, 1, MPI_INTEGER, this%mpi_rank - 1, this%mpi_rank, & - MF6_COMM_WORLD, status, ierr) + this%comm, status, ierr) end if end subroutine mpiw_begin_order @@ -66,7 +90,7 @@ subroutine mpiw_end_order(this) if (this%mpi_rank < this%world_size - 1) then call mpi_send(this%mpi_rank, 1, MPI_INTEGER, this%mpi_rank + 1, & - this%mpi_rank + 1, MF6_COMM_WORLD, ierr) + this%mpi_rank + 1, this%comm, ierr) end if end subroutine mpiw_end_order @@ -74,6 +98,10 @@ end subroutine mpiw_end_order subroutine mpiw_destroy(this) class(MpiWorldType) :: this + if (associated(this%comm)) then + deallocate (this%comm) + end if + if (associated(global_mpi_world)) then deallocate (global_mpi_world) end if diff --git a/src/Solution/ParallelSolution.f90 b/src/Solution/ParallelSolution.f90 index 87974464dc9..a173e528ab3 100644 --- a/src/Solution/ParallelSolution.f90 +++ b/src/Solution/ParallelSolution.f90 @@ -26,11 +26,14 @@ function par_has_converged(this, max_dvc) result(has_converged) ! local real(DP) :: global_max_dvc integer :: ierr + type(MpiWorldType), pointer :: mpi_world + + mpi_world => get_mpi_world() has_converged = .false. global_max_dvc = huge(0.0) call MPI_Allreduce(max_dvc, global_max_dvc, 1, MPI_DOUBLE_PRECISION, & - MPI_MAX, MF6_COMM_WORLD, ierr) + MPI_MAX, mpi_world%comm, ierr) if (global_max_dvc <= this%dvclose) then has_converged = .true. end if diff --git a/srcbmi/meson.build b/srcbmi/meson.build index ebf1f0bb91b..1e2f6d5d381 100644 --- a/srcbmi/meson.build +++ b/srcbmi/meson.build @@ -4,7 +4,7 @@ bmi_sources = files( 'mf6bmiError.f90', 'mf6bmiGrid.f90', 'mf6bmiUtil.f90', - 'mf6xmi.f90', + 'mf6xmi.F90', ) library('mf6', bmi_sources, link_with: mf6core, name_prefix: 'lib', install: true) diff --git a/srcbmi/mf6xmi.f90 b/srcbmi/mf6xmi.F90 similarity index 94% rename from srcbmi/mf6xmi.f90 rename to srcbmi/mf6xmi.F90 index 3cff0f3ff78..8116eb117a4 100644 --- a/srcbmi/mf6xmi.f90 +++ b/srcbmi/mf6xmi.F90 @@ -97,6 +97,29 @@ module mf6xmi contains +#if defined(__WITH_MPI__) + function xmi_initialize_mpi(mpi_comm) result(bmi_status) & + bind(C, name="initialize_mpi") + use MpiWorldModule + use SimVariablesModule, only: simulation_mode + !DIR$ ATTRIBUTES DLLEXPORT :: xmi_initialize_mpi + ! -- dummy variables + integer(kind=c_int) :: mpi_comm !< the Fortran communicator (as an integer) + integer(kind=c_int) :: bmi_status !< BMI status code + ! -- local variables + type(MpiWorldType), pointer :: mpi_world => null() + + ! set parallel + mpi_world => get_mpi_world() + call mpi_world%set_comm(mpi_comm) + simulation_mode = 'PARALLEL' + + ! regular initialize + bmi_status = bmi_initialize() + + end function xmi_initialize_mpi +#endif + !> @brief Prepare a single time step !! !! The routine takes the time step \p dt as an argument. However, MODFLOW (currently) From 4c882c24ce672f271b282393fa9a2489f55c7929 Mon Sep 17 00:00:00 2001 From: w-bonelli Date: Mon, 3 Apr 2023 08:02:25 -0400 Subject: [PATCH 061/123] refactor(dist): include release notes in dev mode, use version cmds in release notes template (#1190) --- distribution/build_dist.py | 10 ---------- distribution/build_docs.py | 25 ++++++++++++++----------- distribution/update_version.py | 1 - doc/ReleaseNotes/v6.5.0.tex | 2 +- doc/ReleaseNotes/vx.x.x-template.tex | 2 +- 5 files changed, 16 insertions(+), 24 deletions(-) diff --git a/distribution/build_dist.py b/distribution/build_dist.py index 188aa8e63c5..8ebf8014daa 100644 --- a/distribution/build_dist.py +++ b/distribution/build_dist.py @@ -44,16 +44,6 @@ "utils", ] -# LaTex files included in distribution docs -_default_tex_paths = [ - _project_root_path / "doc" / "mf6io" / "mf6io.tex", - _project_root_path / "doc" / "ReleaseNotes" / "ReleaseNotes.tex", - _project_root_path / "doc" / "zonebudget" / "zonebudget.tex", - _project_root_path / "doc" / "ConverterGuide" / "converter_mf5to6.tex", - _project_root_path / "doc" / "SuppTechInfo" / "mf6suptechinfo.tex", -] - - Makefile = namedtuple('Makefile', ['app', 'src_path', 'out_path']) diff --git a/distribution/build_docs.py b/distribution/build_docs.py index abdcb8bd2be..c1d4ce1913b 100644 --- a/distribution/build_docs.py +++ b/distribution/build_docs.py @@ -31,13 +31,16 @@ _distribution_path = _project_root_path / "distribution" _benchmarks_path = _project_root_path / "distribution" / ".benchmarks" _docs_path = _project_root_path / "doc" - -_default_tex_paths = [ - _project_root_path / "doc" / "mf6io" / "mf6io.tex", - _project_root_path / "doc" / "ReleaseNotes" / "ReleaseNotes.tex", - _project_root_path / "doc" / "zonebudget" / "zonebudget.tex", - _project_root_path / "doc" / "ConverterGuide" / "converter_mf5to6.tex", - _project_root_path / "doc" / "SuppTechInfo" / "mf6suptechinfo.tex", +_dev_dist_tex_paths = [ + _docs_path / "mf6io" / "mf6io.tex", + _docs_path / "ReleaseNotes" / "ReleaseNotes.tex", +] +_full_dist_tex_paths = [ + _docs_path / "mf6io" / "mf6io.tex", + _docs_path / "ReleaseNotes" / "ReleaseNotes.tex", + _docs_path / "zonebudget" / "zonebudget.tex", + _docs_path / "ConverterGuide" / "converter_mf5to6.tex", + _docs_path / "SuppTechInfo" / "mf6suptechinfo.tex", ] _system = platform.system() _eext = ".exe" if _system == "Windows" else "" @@ -402,7 +405,7 @@ def build_documentation(bin_path: PathLike, examples_repo_path: PathLike, development: bool = False, overwrite: bool = False): - print(f"Building {'development' if development else 'candidate'} documentation") + print(f"Building {'development' if development else 'full'} documentation") bin_path = Path(bin_path).expanduser().absolute() output_path = Path(output_path).expanduser().absolute() @@ -428,7 +431,7 @@ def build_documentation(bin_path: PathLike, if development: # convert LaTeX to PDF - build_pdfs_from_tex(tex_paths=[_docs_path / "mf6io" / "mf6io.tex"], output_path=output_path) + build_pdfs_from_tex(tex_paths=_dev_dist_tex_paths, output_path=output_path) else: # convert benchmarks to LaTex, running them first if necessary build_benchmark_tex(output_path=output_path, overwrite=overwrite) @@ -452,7 +455,7 @@ def build_documentation(bin_path: PathLike, raise # convert LaTex to PDF - build_pdfs_from_tex(tex_paths=_default_tex_paths, output_path=output_path, overwrite=overwrite) + build_pdfs_from_tex(tex_paths=_full_dist_tex_paths, output_path=output_path, overwrite=overwrite) # enforce os line endings on all text files windows_line_endings = True @@ -533,7 +536,7 @@ def test_build_documentation(tmp_path): help="Whether to recreate and overwrite existing artifacts" ) args = parser.parse_args() - tex_paths = _default_tex_paths + ([Path(p) for p in args.tex_path] if args.tex_path else []) + tex_paths = _full_dist_tex_paths + ([Path(p) for p in args.tex_path] if args.tex_path else []) output_path = Path(args.output_path).expanduser().absolute() output_path.mkdir(parents=True, exist_ok=True) bin_path = Path(args.bin_path).expanduser().absolute() diff --git a/distribution/update_version.py b/distribution/update_version.py index 16622d8ecc6..2b278a09d6c 100755 --- a/distribution/update_version.py +++ b/distribution/update_version.py @@ -231,7 +231,6 @@ def update_version_tex( + "{Version \\modflowversion---\\modflowdate}" ) f.write(f"{line}\n") - f.close() log_update(path, release_type, version) diff --git a/doc/ReleaseNotes/v6.5.0.tex b/doc/ReleaseNotes/v6.5.0.tex index f3dae45d2d1..9188d50b969 100644 --- a/doc/ReleaseNotes/v6.5.0.tex +++ b/doc/ReleaseNotes/v6.5.0.tex @@ -1,7 +1,7 @@ % Use this template for starting initializing the release notes % after a release has just been made. - \item Version mf6.x.x--Month xx, 202x + \item \currentmodflowversion \underline{NEW FUNCTIONALITY} \begin{itemize} diff --git a/doc/ReleaseNotes/vx.x.x-template.tex b/doc/ReleaseNotes/vx.x.x-template.tex index 593280e8595..7771001dfba 100644 --- a/doc/ReleaseNotes/vx.x.x-template.tex +++ b/doc/ReleaseNotes/vx.x.x-template.tex @@ -1,7 +1,7 @@ % Use this template for starting initializing the release notes % after a release has just been made. - \item Version mf6.x.x--Month xx, 202x + \item \currentmodflowversion %\underline{NEW FUNCTIONALITY} %\begin{itemize} From 0b20d2c161ec9d6a28a8c1eef39a31df2c59293d Mon Sep 17 00:00:00 2001 From: mjr-deltares <45555666+mjr-deltares@users.noreply.github.com> Date: Mon, 3 Apr 2023 17:28:28 +0200 Subject: [PATCH 062/123] fix(io): disable debug print (#1192) --- src/Distributed/VirtualDataManager.f90 | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/Distributed/VirtualDataManager.f90 b/src/Distributed/VirtualDataManager.f90 index 1d905890f49..93f8e4e0dbd 100644 --- a/src/Distributed/VirtualDataManager.f90 +++ b/src/Distributed/VirtualDataManager.f90 @@ -184,15 +184,17 @@ subroutine vds_reduce_halo(this) end do ! some testing - outunit = getunit() - write (monitor_file, '(a,i0,a)') "iface.p", proc_id, ".log" - open (unit=outunit, file=monitor_file) - do isol = 1, this%nr_solutions - write (outunit, '(a,i0,/)') "interface mape for solution ", & - this%virtual_solutions(isol)%solution_id - call this%virtual_solutions(isol)%interface_map%print_interface(outunit) - end do - close (outunit) + if (.false.) then + outunit = getunit() + write (monitor_file, '(a,i0,a)') "iface.p", proc_id, ".log" + open (unit=outunit, file=monitor_file) + do isol = 1, this%nr_solutions + write (outunit, '(a,i0,/)') "interface mape for solution ", & + this%virtual_solutions(isol)%solution_id + call this%virtual_solutions(isol)%interface_map%print_interface(outunit) + end do + close (outunit) + end if ! assign reduced maps to virtual data containers do isol = 1, this%nr_solutions From 105626e35505f099eef5953cab498b6873866e2f Mon Sep 17 00:00:00 2001 From: w-bonelli Date: Wed, 5 Apr 2023 12:40:35 -0400 Subject: [PATCH 063/123] docs(zbud): mention support for GWT budget files, add test (#1195) * docs(zbud): mention support for GWT budget files, add test * docs(zbud): add clarification to zonebudget.tex abstract --- autotest/test_gwf_zb01.py | 123 +++++----- autotest/test_gwt_zb01.py | 422 ++++++++++++++++++++++++++++++++++ doc/zonebudget/zonebudget.tex | 6 +- 3 files changed, 481 insertions(+), 70 deletions(-) create mode 100644 autotest/test_gwt_zb01.py diff --git a/autotest/test_gwf_zb01.py b/autotest/test_gwf_zb01.py index a408e2b7b7b..dd29209ac9c 100644 --- a/autotest/test_gwf_zb01.py +++ b/autotest/test_gwf_zb01.py @@ -1,4 +1,5 @@ import os +from pathlib import Path import flopy import numpy as np @@ -218,30 +219,27 @@ def build_model(idx, dir, exe): def eval_zb6(sim, exe): print("evaluating zonebudget...") + simpath = Path(sim.simpath) # build zonebudget files zones = [-1000000, 1000000, 9999999] nzones = len(zones) - fpth = os.path.join(sim.simpath, "zonebudget.nam") - f = open(fpth, "w") - f.write("BEGIN ZONEBUDGET\n") - f.write(f" BUD {os.path.basename(sim.name)}.cbc\n") - f.write(f" ZON {os.path.basename(sim.name)}.zon\n") - f.write(f" GRB {os.path.basename(sim.name)}.dis.grb\n") - f.write("END ZONEBUDGET\n") - f.close() - - fpth = os.path.join(sim.simpath, f"{os.path.basename(sim.name)}.zon") - f = open(fpth, "w") - f.write("BEGIN DIMENSIONS\n") - f.write(f" NCELLS {size3d}\n") - f.write("END DIMENSIONS\n\n") - f.write("BEGIN GRIDDATA\n") - f.write(" IZONE LAYERED\n") - for k in range(nlay): - f.write(f" CONSTANT {zones[k]:>10d}\n") - f.write("END GRIDDATA\n") - f.close() + with open(simpath / "zonebudget.nam", "w") as f: + f.write("BEGIN ZONEBUDGET\n") + f.write(f" BUD {os.path.basename(sim.name)}.cbc\n") + f.write(f" ZON {os.path.basename(sim.name)}.zon\n") + f.write(f" GRB {os.path.basename(sim.name)}.dis.grb\n") + f.write("END ZONEBUDGET\n") + + with open(simpath / f"{os.path.basename(sim.name)}.zon", "w") as f: + f.write("BEGIN DIMENSIONS\n") + f.write(f" NCELLS {size3d}\n") + f.write("END DIMENSIONS\n\n") + f.write("BEGIN GRIDDATA\n") + f.write(" IZONE LAYERED\n") + for k in range(nlay): + f.write(f" CONSTANT {zones[k]:>10d}\n") + f.write("END GRIDDATA\n") # run zonebudget success, buff = flopy.run_model( @@ -251,18 +249,15 @@ def eval_zb6(sim, exe): silent=False, report=True, ) - if success: - sim.success = True - else: - sim.success = False - assert success + + assert success + sim.success = success # read data from csv file - fpth = os.path.join(sim.simpath, "zonebudget.csv") - zbd = np.genfromtxt(fpth, names=True, delimiter=",", deletechars="") + zbd = np.genfromtxt(simpath / "zonebudget.csv", names=True, delimiter=",", deletechars="") # sum the data for all zones - nentries = int(zbd.shape[0] / 3) + nentries = int(zbd.shape[0] / nzones) zbsum = np.zeros(nentries, dtype=zbd.dtype) static = ["totim", "kstp", "kper"] ipos = 0 @@ -276,13 +271,12 @@ def eval_zb6(sim, exe): else: zbsum[name][ipos] += t[name] ion += 1 - if ion == 3: + if ion == nzones: ipos += 1 ion = 0 # get results from listing file - fpth = os.path.join(sim.simpath, f"{os.path.basename(sim.name)}.lst") - budl = flopy.utils.Mf6ListBudget(fpth) + budl = flopy.utils.Mf6ListBudget(simpath / f"{os.path.basename(sim.name)}.lst") names = list(bud_lst) d0 = budl.get_budget(names=names)[0] dtype = d0.dtype @@ -293,8 +287,9 @@ def eval_zb6(sim, exe): d = np.recarray(nbud, dtype=dtype) for key in bud_lst: d[key] = 0.0 - fpth = os.path.join(sim.simpath, f"{os.path.basename(sim.name)}.cbc") - cobj = flopy.utils.CellBudgetFile(fpth, precision="double") + cobj = flopy.utils.CellBudgetFile( + simpath / f"{os.path.basename(sim.name)}.cbc", + precision="double") kk = cobj.get_kstpkper() times = cobj.get_times() for idx, (k, t) in enumerate(zip(kk, times)): @@ -330,25 +325,21 @@ def eval_zb6(sim, exe): msg = f"maximum absolute total-budget difference ({diffmax}) " # write summary - fpth = os.path.join( - sim.simpath, f"{os.path.basename(sim.name)}.bud.cmp.out" - ) - f = open(fpth, "w") - for i in range(diff.shape[0]): - if i == 0: - line = f"{'TIME':>10s}" + with open(simpath / f"{os.path.basename(sim.name)}.bud.cmp.out", "w") as f: + for i in range(diff.shape[0]): + if i == 0: + line = f"{'TIME':>10s}" + for idx, key in enumerate(bud_lst): + line += f"{key + '_LST':>25s}" + line += f"{key + '_CBC':>25s}" + line += f"{key + '_DIF':>25s}" + f.write(line + "\n") + line = f"{d['totim'][i]:10g}" for idx, key in enumerate(bud_lst): - line += f"{key + '_LST':>25s}" - line += f"{key + '_CBC':>25s}" - line += f"{key + '_DIF':>25s}" + line += f"{d0[key][i]:25g}" + line += f"{d[key][i]:25g}" + line += f"{diff[i, idx]:25g}" f.write(line + "\n") - line = f"{d['totim'][i]:10g}" - for idx, key in enumerate(bud_lst): - line += f"{d0[key][i]:25g}" - line += f"{d[key][i]:25g}" - line += f"{diff[i, idx]:25g}" - f.write(line + "\n") - f.close() # compare zone budget to cbc output diffzb = np.zeros((nbud, len(bud_lst)), dtype=float) @@ -360,25 +351,21 @@ def eval_zb6(sim, exe): ) # write summary - fpth = os.path.join( - sim.simpath, f"{os.path.basename(sim.name)}.zbud.cmp.out" - ) - f = open(fpth, "w") - for i in range(diff.shape[0]): - if i == 0: - line = f"{'TIME':>10s}" - for idx, key in enumerate(bud_lst): - line += f"{key + '_ZBUD':>25s}" - line += f"{key + '_CBC':>25s}" - line += f"{key + '_DIF':>25s}" + with open(simpath / f"{os.path.basename(sim.name)}.zbud.cmp.out", "w") as f: + for i in range(diff.shape[0]): + if i == 0: + line = f"{'TIME':>10s}" + for idx, key in enumerate(bud_lst): + line += f"{key + '_ZBUD':>25s}" + line += f"{key + '_CBC':>25s}" + line += f"{key + '_DIF':>25s}" + f.write(line + "\n") + line = f"{d['totim'][i]:10g}" + for idx, (key0, key) in enumerate(zip(zone_lst, bud_lst)): + line += f"{zbsum[key0][i]:25g}" + line += f"{d[key][i]:25g}" + line += f"{diffzb[i, idx]:25g}" f.write(line + "\n") - line = f"{d['totim'][i]:10g}" - for idx, (key0, key) in enumerate(zip(zone_lst, bud_lst)): - line += f"{zbsum[key0][i]:25g}" - line += f"{d[key][i]:25g}" - line += f"{diffzb[i, idx]:25g}" - f.write(line + "\n") - f.close() if diffmax > budtol or diffzbmax > budtol: sim.success = False diff --git a/autotest/test_gwt_zb01.py b/autotest/test_gwt_zb01.py new file mode 100644 index 00000000000..8bd78e8d48c --- /dev/null +++ b/autotest/test_gwt_zb01.py @@ -0,0 +1,422 @@ +# test that zonebudget works on a cell budget file from GWT +# https://github.com/MODFLOW-USGS/modflow6/discussions/1181 + + +import os +from pathlib import Path + +import flopy +import numpy as np +import pytest +from framework import TestFramework +from simulation import TestSimulation + + +name = "zbud6_zb01" +htol = None +dtol = 1e-3 +budtol = 1e-2 +bud_lst = [ + "STORAGE-AQUEOUS_IN", + "STORAGE-AQUEOUS_OUT" +] +zone_lst = [] +for n in bud_lst: + s = n.replace("_", "-") + zone_lst.append(s) + + +nlay, nrow, ncol = 5, 10, 20 +nper = 1 +delr = 1.0 +delc = 1.0 +delz = 1.0 +top = 1.0 +botm = np.linspace(top - delz, top - nlay * delz, nlay) +strt = 1.0 +hk = 1.0 +laytyp = 0 +porosity = 0.1 +qwell = 1.0 +specific_discharge = qwell / delr / delz +timetoend = float(ncol) * delc * porosity / specific_discharge +shape3d = (nlay, nrow, ncol) +size3d = nlay * nrow * ncol + + +def build_model(dir, exe): + perlen = [timetoend] + nstp = [50] + tsmult = [1.0] + steady = [True] + + nouter, ninner = 100, 300 + hclose, rclose, relax = 1e-6, 1e-6, 1.0 + + tdis_rc = [] + for i in range(nper): + tdis_rc.append((perlen[i], nstp[i], tsmult[i])) + + # build MODFLOW 6 files + ws = dir + sim = flopy.mf6.MFSimulation( + sim_name=name, version="mf6", exe_name=exe, sim_ws=ws + ) + # create tdis package + tdis = flopy.mf6.ModflowTdis( + sim, time_units="DAYS", nper=nper, perioddata=tdis_rc + ) + + # create gwf model + gwfname = "gwf_" + name + gwf = flopy.mf6.ModflowGwf( + sim, + modelname=gwfname, + save_flows=True, + model_nam_file=f"{gwfname}.nam", + ) + + # create iterative model solution and register the gwf model with it + imsgwf = flopy.mf6.ModflowIms( + sim, + print_option="SUMMARY", + outer_dvclose=hclose, + outer_maximum=nouter, + under_relaxation="NONE", + inner_maximum=ninner, + inner_dvclose=hclose, + rcloserecord=rclose, + linear_acceleration="BICGSTAB", + scaling_method="NONE", + reordering_method="NONE", + relaxation_factor=relax, + filename=f"{gwfname}.ims", + ) + sim.register_ims_package(imsgwf, [gwf.name]) + + # chd and wel info + chdlist = [] + wellist = [] + for k in range(nlay): + for i in range(nrow): + for j in range(ncol): + if j == ncol - 1: + chdlist.append([(k, i, j), 0.0]) + if j == 0: + wellist.append([(k, i, j), qwell, 1.0]) + c = {0: chdlist} + w = {0: wellist} + + # grid discretization + dis = flopy.mf6.ModflowGwfdis(gwf, nlay=nlay, nrow=nrow, ncol=ncol, + delr=delr, delc=delc, + top=top, botm=botm, + idomain=np.ones((nlay, nrow, ncol), dtype=int), + filename=f"{gwfname}.dis") + + # initial conditions + ic = flopy.mf6.ModflowGwfic(gwf, strt=strt, filename=f"{gwfname}.ic") + + # node property flow + npf = flopy.mf6.ModflowGwfnpf( + gwf, + save_flows=False, + icelltype=laytyp, + xt3doptions=[()], + k=hk, + k33=hk, + save_specific_discharge=True, + ) + + # chd package + chd = flopy.mf6.modflow.mfgwfchd.ModflowGwfchd( + gwf, + maxbound=len(c), + stress_period_data=c, + save_flows=False, + pname="CHD-1", + ) + + # wel package + wel = flopy.mf6.ModflowGwfwel( + gwf, + print_input=True, + print_flows=True, + maxbound=len(w), + stress_period_data=w, + save_flows=False, + auxiliary="CONCENTRATION", + pname="WEL-1", + ) + + # output control + oc = flopy.mf6.ModflowGwfoc( + gwf, + budget_filerecord=f"{gwfname}.cbc", + head_filerecord=f"{gwfname}.hds", + headprintrecord=[("COLUMNS", 10, "WIDTH", 15, "DIGITS", 6, "GENERAL")], + saverecord=[("HEAD", "LAST"), ("BUDGET", "LAST")], + printrecord=[("HEAD", "LAST"), ("BUDGET", "LAST")], + ) + + # create gwt model + gwtname = "gwt_" + name + gwt = flopy.mf6.MFModel( + sim, + model_type="gwt6", + modelname=gwtname, + model_nam_file=f"{gwtname}.nam", + ) + gwt.name_file.save_flows = True + + # create iterative model solution and register the gwt model with it + imsgwt = flopy.mf6.ModflowIms( + sim, + print_option="SUMMARY", + outer_dvclose=hclose, + outer_maximum=nouter, + under_relaxation="NONE", + inner_maximum=ninner, + inner_dvclose=hclose, + rcloserecord=rclose, + linear_acceleration="BICGSTAB", + scaling_method="NONE", + reordering_method="NONE", + relaxation_factor=relax, + filename=f"{gwtname}.ims", + ) + sim.register_ims_package(imsgwt, [gwt.name]) + + # gwt grid discretization + dis = flopy.mf6.ModflowGwtdis(gwt, nlay=nlay, nrow=nrow, ncol=ncol, + delr=delr, delc=delc, + top=top, botm=botm, + idomain=np.ones((nlay, nrow, ncol), dtype=int), + filename=f"{gwtname}.dis") + + # initial conditions + ic = flopy.mf6.ModflowGwtic(gwt, strt=0.0, filename=f"{gwtname}.ic") + + # advection + adv = flopy.mf6.ModflowGwtadv( + gwt, scheme="upstream", filename=f"{gwtname}.adv" + ) + + # mass storage and transfer + mst = flopy.mf6.ModflowGwtmst(gwt, porosity=0.1) + + # sources + sourcerecarray = [("WEL-1", "AUX", "CONCENTRATION")] + ssm = flopy.mf6.ModflowGwtssm( + gwt, sources=sourcerecarray, filename=f"{gwtname}.ssm" + ) + + # output control + oc = flopy.mf6.ModflowGwtoc( + gwt, + budget_filerecord=f"{gwtname}.cbc", + concentration_filerecord=f"{gwtname}.ucn", + concentrationprintrecord=[ + ("COLUMNS", 10, "WIDTH", 15, "DIGITS", 6, "GENERAL") + ], + saverecord=[("CONCENTRATION", "ALL"), ("BUDGET", "LAST")], + printrecord=[("CONCENTRATION", "LAST"), ("BUDGET", "LAST")], + ) + + # GWF GWT exchange + gwfgwt = flopy.mf6.ModflowGwfgwt( + sim, + exgtype="GWF6-GWT6", + exgmnamea=gwfname, + exgmnameb=gwtname, + filename=f"{name}.gwfgwt", + ) + + return sim, None + + +def eval_zb6(sim, exe): + print("evaluating zonebudget...") + ws = Path(sim.simpath) + + # build zonebudget files + # start with 1 since budget isn't calculated for zone 0 + zones = [k + 1 for k in range(nlay)] + nzones = len(zones) + with open(ws / "zonebudget.nam", "w") as f: + f.write("BEGIN ZONEBUDGET\n") + f.write(f" BUD gwt_{sim.name}.cbc\n") + f.write(f" ZON {sim.name}.zon\n") + f.write(f" GRB gwf_{sim.name}.dis.grb\n") + f.write("END ZONEBUDGET\n") + + with open(ws / f"{sim.name}.zon", "w") as f: + f.write("BEGIN DIMENSIONS\n") + f.write(f" NCELLS {size3d}\n") + f.write("END DIMENSIONS\n\n") + f.write("BEGIN GRIDDATA\n") + f.write(" IZONE LAYERED\n") + for k in range(nlay): + f.write(f" CONSTANT {zones[k]:>10d}\n") + f.write("END GRIDDATA\n") + + # run zonebudget + success, buff = flopy.run_model( + exe, + "zonebudget.nam", + model_ws=ws, + silent=False, + report=True, + ) + + assert success + sim.success = success + + # read data from csv file + zbd = np.genfromtxt(ws / "zonebudget.csv", names=True, delimiter=",", deletechars="") + + # sum the data for all zones + nentries = int(zbd.shape[0] / nzones) + zbsum = np.zeros(nentries, dtype=zbd.dtype) + static = ["totim", "kstp", "kper"] + ipos = 0 + ion = 0 + for t in zbd: + for name in zbd.dtype.names: + if name in static: + zbsum[name][ipos] = t[name] + elif name == "zone": + zbsum[name][ipos] = 0 + else: + zbsum[name][ipos] += t[name] + ion += 1 + if ion == nzones: + ipos += 1 + ion = 0 + + # get results from listing file + # todo: should flopy have a subclass for GWT list file? + budl = flopy.utils.mflistfile.ListBudget( + ws / f"gwt_{os.path.basename(sim.name)}.lst", + budgetkey="MASS BUDGET FOR ENTIRE MODEL" + ) + names = list(bud_lst) + found_names = budl.get_record_names() + d0 = budl.get_budget(names=names)[0] + dtype = d0.dtype + nbud = d0.shape[0] + + # get results from cbc file + cbc_bud = [ + "STORAGE-AQUEOUS" + ] + d = np.recarray(nbud, dtype=dtype) + for key in bud_lst: + d[key] = 0.0 + cobj = flopy.utils.CellBudgetFile( + ws / f"gwt_{os.path.basename(sim.name)}.cbc", + precision="double") + rec = cobj.list_records() + kk = cobj.get_kstpkper() + times = cobj.get_times() + for idx, (k, t) in enumerate(zip(kk, times)): + for text in cbc_bud: + qin = 0.0 + qout = 0.0 + v = cobj.get_data(kstpkper=k, text=text)[0] + if isinstance(v, np.recarray): + vt = np.zeros(size3d, dtype=float) + for jdx, node in enumerate(v["node"]): + vt[node - 1] += v["q"][jdx] + v = vt.reshape(shape3d) + for kk in range(v.shape[0]): + for ii in range(v.shape[1]): + for jj in range(v.shape[2]): + vv = v[kk, ii, jj] + if vv < 0.0: + qout -= vv + else: + qin += vv + d["totim"][idx] = t + d["time_step"][idx] = k[0] + d["stress_period"] = k[1] + key = f"{text}_IN" + d[key][idx] = qin + key = f"{text}_OUT" + d[key][idx] = qout + + # calculate absolute difference + diff = np.zeros((nbud, len(bud_lst)), dtype=float) + for idx, key in enumerate(bud_lst): + diff[:, idx] = d0[key] - d[key] + diffmax = np.abs(diff).max() + msg = f"maximum absolute total-budget difference ({diffmax}) " + + # write summary + with open(ws / f"{os.path.basename(sim.name)}.bud.cmp.out", "w") as f: + for i in range(diff.shape[0]): + if i == 0: + line = f"{'TIME':>10s}" + for idx, key in enumerate(bud_lst): + line += f"{key + '_LST':>25s}" + line += f"{key + '_CBC':>25s}" + line += f"{key + '_DIF':>25s}" + f.write(line + "\n") + line = f"{d['totim'][i]:10g}" + for idx, key in enumerate(bud_lst): + line += f"{d0[key][i]:25g}" + line += f"{d[key][i]:25g}" + line += f"{diff[i, idx]:25g}" + f.write(line + "\n") + + # compare zone budget output to cbc output + diffzb = np.zeros((nbud, len(bud_lst)), dtype=float) + for idx, (key0, key) in enumerate(zip(zone_lst, bud_lst)): + diffzb[:, idx] = zbsum[key0] - d[key] + diffzbmax = np.abs(diffzb).max() + msg += ( + f"\nmaximum absolute zonebudget-cell by cell difference ({diffzbmax}) " + ) + + # write summary + with open(ws / f"{os.path.basename(sim.name)}.zbud.cmp.out", "w") as f: + for i in range(diff.shape[0]): + if i == 0: + line = f"{'TIME':>10s}" + for idx, key in enumerate(bud_lst): + line += f"{key + '_ZBUD':>25s}" + line += f"{key + '_CBC':>25s}" + line += f"{key + '_DIF':>25s}" + f.write(line + "\n") + line = f"{d['totim'][i]:10g}" + for idx, (key0, key) in enumerate(zip(zone_lst, bud_lst)): + line += f"{zbsum[key0][i]:25g}" + line += f"{d[key][i]:25g}" + line += f"{diffzb[i, idx]:25g}" + f.write(line + "\n") + + if diffmax > budtol or diffzbmax > budtol: + sim.success = False + msg += f"\n...exceeds {budtol}" + assert diffmax < budtol and diffzbmax < budtol, msg + else: + sim.success = True + print(" " + msg) + + +def test_mf6model(function_tmpdir, targets): + ws = str(function_tmpdir) + mf6 = targets.mf6 + zb6 = targets.zbud6 + test = TestFramework() + test.build(lambda _, w: build_model(w, mf6), 0, ws) + test.run( + TestSimulation( + name=name, + exe_dict=targets, + exfunc=lambda s: eval_zb6(s, zb6), + htol=htol, + idxsim=0, + ), + ws, + ) diff --git a/doc/zonebudget/zonebudget.tex b/doc/zonebudget/zonebudget.tex index a8cf1c6cf86..79941b1e45d 100644 --- a/doc/zonebudget/zonebudget.tex +++ b/doc/zonebudget/zonebudget.tex @@ -95,7 +95,9 @@ % ------------------------------------------------- \section{Abstract} -The computer program ZONEBUDGET 6, which is written in FORTRAN, calculates subregional water budgets using results from MODFLOW 6. ZONEBUDGET uses cell-by-cell flow data saved by the model in order to calculate the budgets. Subregions of the modeled region are designated by zone numbers. The user assigns a zone number for each cell in the model. +The computer program ZONEBUDGET 6, which is written in FORTRAN, calculates subregional water budgets using results from MODFLOW 6. ZONEBUDGET uses cell-by-cell flow data saved by the model in order to calculate the budgets. Subregions of the modeled region are designated by zone numbers. The user assigns a zone number for each cell in the model. + +ZONEBUDGET 6 is designed to work with output from the Groundwater Flow (GWF) and Groundwater Transport (GWT) Models, and the advanced packages for those models. Output from new models that may be added to MODFLOW 6 in the future should also work with ZONEBUDGET 6. % ------------------------------------------------- \section{Introduction} @@ -155,7 +157,7 @@ \section{Instructions for Using the Program} \begin{itemize} \item \texttt{BUD }---is the keyword and name of the MODFLOW 6 budget file. This file contains double precision numeric values of simulated flow. \item \texttt{ZON }---is the keyword and name of the zone input file. -\item \texttt{GRB }---is the keyword and name of the binary grid file created by MODFLOW 6. Note that if the NOGRB keyword is specified in the OPTIONS block of the Discretization Package input file, then MODFLOW 6 will not create the binary grid file needed to run ZONEBUDGET. The binary grid file must be provided if the budget file is for a Groundwater Flow (GWF) Model. The binary grid file is not needed when ZONEBUDGET is used to process the budget file from one of the advanced packages (described later). +\item \texttt{GRB }---is the keyword and name of the binary grid file created by MODFLOW 6. Note that if the NOGRB keyword is specified in the OPTIONS block of the Discretization Package input file, then MODFLOW 6 will not create the binary grid file needed to run ZONEBUDGET. The binary grid file must be provided if the budget file is for a Groundwater Flow (GWF) or Groundwater Transport (GWT) Model. The binary grid file is not needed when ZONEBUDGET is used to process the budget file from one of the advanced packages (described later). \end{itemize} The following is an example of a ZONEBUDGET name file: From 01f6defda454cf5b282962d4384e2d08eaaa26d3 Mon Sep 17 00:00:00 2001 From: Eric Morway Date: Sat, 8 Apr 2023 10:00:03 -0700 Subject: [PATCH 064/123] fix(gwf3drn8): replace auxddrnname with auxdepthname for consistency (#1200) --- src/Model/GroundWaterFlow/gwf3drn8.f90 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Model/GroundWaterFlow/gwf3drn8.f90 b/src/Model/GroundWaterFlow/gwf3drn8.f90 index 0a984df8102..53f74bf3bea 100644 --- a/src/Model/GroundWaterFlow/gwf3drn8.f90 +++ b/src/Model/GroundWaterFlow/gwf3drn8.f90 @@ -204,7 +204,7 @@ subroutine drn_options(this, option, found) ! -- Error if no aux variable specified if (this%naux == 0) then write (errmsg, '(a,2(1x,a))') & - 'AUXDDRNNAME WAS SPECIFIED AS', trim(adjustl(ddrnauxname)), & + 'AUXDEPTHNAME WAS SPECIFIED AS', trim(adjustl(ddrnauxname)), & 'BUT NO AUX VARIABLES SPECIFIED.' call store_error(errmsg) end if @@ -221,7 +221,7 @@ subroutine drn_options(this, option, found) ! -- Error if aux variable cannot be found if (this%iauxddrncol == 0) then write (errmsg, '(a,2(1x,a))') & - 'AUXDDRNNAME WAS SPECIFIED AS', trim(adjustl(ddrnauxname)), & + 'AUXDEPTHNAME WAS SPECIFIED AS', trim(adjustl(ddrnauxname)), & 'BUT NO AUX VARIABLE FOUND WITH THIS NAME.' call store_error(errmsg) end if From 6091ba88eab836a082e5e682187592b918060bea Mon Sep 17 00:00:00 2001 From: mjr-deltares <45555666+mjr-deltares@users.noreply.github.com> Date: Tue, 11 Apr 2023 19:24:45 +0200 Subject: [PATCH 065/123] feat(par): add load balancing for gwf and gwt (#1193) * add load balancer to SimulationCreate * assign gwt models to processes * - print model creation to list file - set model mask in idm to 1 for now * fprettification * fix: forgot to deallocate * made model loading mask for idm in sync with models create * - add model ranks to memory - add global l2 norm for parallel - fixed bug in gwf ptc with row nr - add test case with mult. models per process (2 cpus, 4 models) * temp fix: top/bot are not reduced * move map type to initialization when it is already known * - no lookup tables when virtual data is not reduced - major cleanup to keep things readable... --- autotest/test_par_gwf03.py | 11 +- src/Distributed/Mapper.f90 | 30 +- src/Distributed/VirtualBase.f90 | 2 +- src/Distributed/VirtualDataContainer.f90 | 61 ++--- src/Distributed/VirtualExchange.f90 | 100 ++++--- src/Distributed/VirtualGwfModel.f90 | 133 +++++---- src/Distributed/VirtualGwtExchange.f90 | 26 +- src/Distributed/VirtualGwtModel.f90 | 136 +++++----- src/Distributed/VirtualModel.f90 | 201 +++++++------- src/Model/Connection/DistributedVariable.f90 | 8 +- src/Model/Connection/GwfGwfConnection.f90 | 72 ++--- src/Model/Connection/GwtGwtConnection.f90 | 74 ++--- .../Connection/SpatialModelConnection.f90 | 18 +- src/Model/GroundWaterFlow/gwf3.f90 | 8 +- src/RunControl.f90 | 6 +- src/SimulationCreate.f90 | 256 ++++++++++++++---- .../LinearMethods/ImsLinearSolver.f90 | 12 +- src/Solution/LinearSolverBase.f90 | 9 + src/Solution/NumericalSolution.f90 | 59 ++-- src/Solution/PETSc/PetscConvergence.F90 | 7 +- src/Solution/PETSc/PetscSolver.F90 | 66 +++++ src/Solution/ParallelSolution.f90 | 16 +- src/Utilities/CharString.f90 | 17 +- src/Utilities/SimStages.f90 | 50 ++-- src/Utilities/SimVariables.f90 | 1 + src/mf6core.f90 | 34 +-- 26 files changed, 828 insertions(+), 585 deletions(-) diff --git a/autotest/test_par_gwf03.py b/autotest/test_par_gwf03.py index 83a25d04a02..2b8e3c76fe5 100644 --- a/autotest/test_par_gwf03.py +++ b/autotest/test_par_gwf03.py @@ -13,17 +13,18 @@ # # a: 1 cpus, 1 model # b: 1 cpus, 4 models -# c: 4 cpus, 4 models +# c: 2 cpus, 4 models +# d: 4 cpus, 4 models # # The test is that for all configurations, the head # converges globally to the specified boundary value. # In general, the test can be used to compare parallel # vs. serial behavior on an identical problem. -ex = ["par_gwf03-a", "par_gwf03-b", "par_gwf03-c"] -ncpus = [1, 1, 4] -domain_grid = [(1, 1), (2, 2), (2, 2)] -dis_shape = [(2, 100, 100), (2, 50, 50), (2, 50, 50)] +ex = ["par_gwf03-a", "par_gwf03-b", "par_gwf03-c", "par_gwf03-d"] +ncpus = [1, 1, 2, 4] +domain_grid = [(1, 1), (2, 2), (2, 2), (2, 2)] +dis_shape = [(2, 100, 100), (2, 50, 50), (2, 50, 50), (2, 50, 50)] delr = 100.0 delc = 100.0 diff --git a/src/Distributed/Mapper.f90 b/src/Distributed/Mapper.f90 index 7d2fe938271..d9713d82905 100644 --- a/src/Distributed/Mapper.f90 +++ b/src/Distributed/Mapper.f90 @@ -3,7 +3,7 @@ module MapperModule use ConstantsModule, only: LENVARNAME, LENMEMPATH use MemoryHelperModule, only: create_mem_path use IndexMapModule - use VirtualBaseModule, only: MAP_NODE_TYPE, MAP_CONN_TYPE + use VirtualBaseModule, only: VirtualDataType, MAP_NODE_TYPE, MAP_CONN_TYPE use VirtualModelModule, only: VirtualModelType, get_virtual_model use VirtualExchangeModule, only: VirtualExchangeType, get_virtual_exchange use InterfaceMapModule @@ -59,12 +59,12 @@ subroutine add_exchange_vars(this) if (.not. virt_exg%v_model1%is_local) then virt_mem_path = virt_exg%get_vrt_mem_path('NODEM1', '') call this%map_data_full(0, 'NODEM1', conn%prim_exchange%memoryPath, & - 'NODEM1', virt_mem_path, (/STG_BEFORE_CON_DF/)) + 'NODEM1', virt_mem_path, (/STG_BFR_CON_DF/)) end if if (.not. virt_exg%v_model2%is_local) then virt_mem_path = virt_exg%get_vrt_mem_path('NODEM2', '') call this%map_data_full(0, 'NODEM2', conn%prim_exchange%memoryPath, & - 'NODEM2', virt_mem_path, (/STG_BEFORE_CON_DF/)) + 'NODEM2', virt_mem_path, (/STG_BFR_CON_DF/)) end if end do @@ -103,12 +103,12 @@ subroutine add_dist_vars(this, sol_id, var_list, iface_map) ! loop over variables do i = 1, var_list%Count() dist_var => GetDistVarFromList(var_list, i) - if (dist_var%map_type == SYNC_NODES .or. & ! models - dist_var%map_type == SYNC_CONNECTIONS) then + if (dist_var%map_type == SYNC_NDS .or. & ! models + dist_var%map_type == SYNC_CON) then do m = 1, iface_map%nr_models call this%map_model_data(sol_id, iface_map, m, dist_var) end do - else if (dist_var%map_type == SYNC_EXCHANGES) then ! exchanges + else if (dist_var%map_type == SYNC_EXG) then ! exchanges do e = 1, iface_map%nr_exchanges call this%map_exg_data(sol_id, iface_map, e, dist_var) end do @@ -135,16 +135,24 @@ subroutine map_model_data(this, sol_id, iface_map, model_idx, dist_var) class(VirtualModelType), pointer :: v_model type(IndexMapType), pointer :: idx_map integer(I4B), dimension(:), pointer, contiguous :: lookup_table + class(VirtualDataType), pointer :: vd v_model => get_virtual_model(iface_map%model_ids(model_idx)) + vd => v_model%get_virtual_data(dist_var%var_name, dist_var%subcomp_name) - ! pick the right index map: connection based or node based - if (dist_var%map_type == SYNC_NODES) then + ! pick the right index map: connection based or node based, + ! and reduced data items require a lookup table + lookup_table => null() + if (dist_var%map_type == SYNC_NDS) then idx_map => iface_map%node_maps(model_idx) - lookup_table => v_model%element_luts(MAP_NODE_TYPE)%remote_to_virtual - else if (dist_var%map_type == SYNC_CONNECTIONS) then + if (vd%is_reduced) then + lookup_table => v_model%element_luts(MAP_NODE_TYPE)%remote_to_virtual + end if + else if (dist_var%map_type == SYNC_CON) then idx_map => iface_map%conn_maps(model_idx) - lookup_table => v_model%element_luts(MAP_CONN_TYPE)%remote_to_virtual + if (vd%is_reduced) then + lookup_table => v_model%element_luts(MAP_CONN_TYPE)%remote_to_virtual + end if else write (*, *) "Unknown map type for distributed variable ", dist_var%var_name call ustop() diff --git a/src/Distributed/VirtualBase.f90 b/src/Distributed/VirtualBase.f90 index 1c46147b934..ec2744903c7 100644 --- a/src/Distributed/VirtualBase.f90 +++ b/src/Distributed/VirtualBase.f90 @@ -50,7 +50,7 @@ module VirtualBaseModule contains procedure(vm_allocate_if), deferred :: vm_allocate procedure(vm_deallocate_if), deferred :: vm_deallocate - procedure :: to_base => vm_to_base + procedure :: base => vm_to_base procedure :: check_stage => vm_check_stage procedure :: link => vm_link procedure :: get_element_map diff --git a/src/Distributed/VirtualDataContainer.f90 b/src/Distributed/VirtualDataContainer.f90 index 02200b1df01..1a49f66b8d7 100644 --- a/src/Distributed/VirtualDataContainer.f90 +++ b/src/Distributed/VirtualDataContainer.f90 @@ -71,9 +71,10 @@ module VirtualDataContainerModule procedure :: set_orig_rank => vdc_set_orig_rank procedure :: get_send_items => vdc_get_send_items procedure :: get_recv_items => vdc_get_recv_items + procedure :: get_virtual_data => vdc_get_virtual_data procedure :: print_items ! protected - procedure :: create_field + procedure :: set ! private procedure, private :: add_to_list procedure, private :: map_scalar @@ -113,16 +114,18 @@ subroutine vdc_create(this, name, id, is_local) end subroutine vdc_create - !> @brief Create virtual data item, without allocation, - !< and add store it in this container. - subroutine create_field(this, field, var_name, subcmp_name, is_local) + !> @brief Init virtual data item, without allocation, + !< and store it in this container. + subroutine set(this, field, var_name, subcmp_name, map_id, is_local) class(VirtualDataContainerType) :: this class(VirtualDataType), pointer :: field character(len=*) :: var_name character(len=*) :: subcmp_name + integer(I4B) :: map_id logical(LGP), optional :: is_local field%is_remote = .not. this%is_local + field%map_type = map_id if (present(is_local)) field%is_remote = .not. is_local field%var_name = var_name field%subcmp_name = subcmp_name @@ -131,12 +134,13 @@ subroutine create_field(this, field, var_name, subcmp_name, is_local) else field%mem_path = create_mem_path(this%name, subcmp_name) end if + field%is_reduced = (field%is_remote .and. field%map_type > 0) field%remote_elem_shift => null() field%remote_to_virtual => null() field%virtual_mt => null() call this%add_to_list(field) - end subroutine create_field + end subroutine set subroutine add_to_list(this, virtual_data) class(VirtualDataContainerType) :: this @@ -213,61 +217,54 @@ subroutine vdc_set_element_map(this, src_indexes, map_id) end subroutine vdc_set_element_map - subroutine map_scalar(this, field, stages, map_id) + subroutine map_scalar(this, vd, stages) class(VirtualDataContainerType) :: this - class(VirtualDataType), pointer :: field + class(VirtualDataType), pointer :: vd integer(I4B), dimension(:) :: stages - integer(I4B) :: map_id - call this%map_internal(field, (/0/), stages, map_id) + call this%map_internal(vd, (/0/), stages) end subroutine map_scalar - subroutine map_array1d(this, field, nrow, stages, map_id) + subroutine map_array1d(this, vd, nrow, stages) class(VirtualDataContainerType) :: this - class(VirtualDataType), pointer :: field + class(VirtualDataType), pointer :: vd integer(I4B) :: nrow integer(I4B), dimension(:) :: stages - integer(I4B) :: map_id - call this%map_internal(field, (/nrow/), stages, map_id) + call this%map_internal(vd, (/nrow/), stages) end subroutine map_array1d - subroutine map_array2d(this, field, ncol, nrow, stages, map_id) + subroutine map_array2d(this, vd, ncol, nrow, stages) class(VirtualDataContainerType) :: this - class(VirtualDataType), pointer :: field + class(VirtualDataType), pointer :: vd integer(I4B) :: ncol integer(I4B) :: nrow integer(I4B), dimension(:) :: stages - integer(I4B) :: map_id - call this%map_internal(field, (/ncol, nrow/), stages, map_id) + call this%map_internal(vd, (/ncol, nrow/), stages) end subroutine map_array2d - subroutine map_internal(this, field, shape, stages, map_id) + subroutine map_internal(this, vd, shape, stages) class(VirtualDataContainerType) :: this - class(VirtualDataType), pointer :: field + class(VirtualDataType), pointer :: vd integer(I4B), dimension(:) :: shape integer(I4B), dimension(:) :: stages - integer(I4B) :: map_id ! local - character(len=LENMEMPATH) :: vmem_path + character(len=LENMEMPATH) :: vm_pth logical(LGP) :: found - field%sync_stages = stages - field%map_type = map_id - field%is_reduced = .false. - if (field%is_remote) then + vd%sync_stages = stages + if (vd%is_remote) then ! create new virtual memory item - vmem_path = this%get_vrt_mem_path(field%var_name, field%subcmp_name) - call field%vm_allocate(field%var_name, vmem_path, shape) - call get_from_memorylist(field%var_name, vmem_path, field%virtual_mt, found) - if (map_id > 0) then - field%is_reduced = .true. - field%remote_to_virtual => this%element_luts(map_id)%remote_to_virtual - field%remote_elem_shift => this%element_maps(map_id)%remote_elem_shift + vm_pth = this%get_vrt_mem_path(vd%var_name, vd%subcmp_name) + call vd%vm_allocate(vd%var_name, vm_pth, shape) + call get_from_memorylist(vd%var_name, vm_pth, vd%virtual_mt, found) + if (vd%map_type > 0) then + vd%remote_to_virtual => this%element_luts(vd%map_type)%remote_to_virtual + vd%remote_elem_shift => this%element_maps(vd%map_type)%remote_elem_shift end if end if diff --git a/src/Distributed/VirtualExchange.f90 b/src/Distributed/VirtualExchange.f90 index f4d40c4cd54..f0554dab259 100644 --- a/src/Distributed/VirtualExchange.f90 +++ b/src/Distributed/VirtualExchange.f90 @@ -84,7 +84,8 @@ module VirtualExchangeModule procedure :: get_recv_items => vx_get_recv_items procedure :: destroy => vx_destroy ! private - procedure, private :: create_virtual_fields + procedure, private :: init_virtual_data + procedure, private :: allocate_data procedure, private :: deallocate_data end type VirtualExchangeType @@ -110,12 +111,12 @@ subroutine vx_create(this, name, exg_id, m1_id, m2_id) is_local = this%v_model1%is_local .or. this%v_model2%is_local call this%VirtualDataContainerType%vdc_create(name, exg_id, is_local) - ! allocate fields - call this%create_virtual_fields() + call this%allocate_data() + call this%init_virtual_data() end subroutine vx_create - subroutine create_virtual_fields(this) + subroutine init_virtual_data(this) class(VirtualExchangeType) :: this ! local logical(LGP) :: is_nodem1_local @@ -126,29 +127,20 @@ subroutine create_virtual_fields(this) ! model sits on the same process is_nodem1_local = this%v_model1%is_local is_nodem2_local = this%v_model2%is_local - - allocate (this%nexg) - call this%create_field(this%nexg%to_base(), 'NEXG', '') - allocate (this%naux) - call this%create_field(this%naux%to_base(), 'NAUX', '') - allocate (this%ianglex) - call this%create_field(this%ianglex%to_base(), 'IANGLEX', '') - allocate (this%nodem1) - call this%create_field(this%nodem1%to_base(), 'NODEM1', '', is_nodem1_local) - allocate (this%nodem2) - call this%create_field(this%nodem2%to_base(), 'NODEM2', '', is_nodem2_local) - allocate (this%ihc) - call this%create_field(this%ihc%to_base(), 'IHC', '') - allocate (this%cl1) - call this%create_field(this%cl1%to_base(), 'CL1', '') - allocate (this%cl2) - call this%create_field(this%cl2%to_base(), 'CL2', '') - allocate (this%hwva) - call this%create_field(this%hwva%to_base(), 'HWVA', '') - allocate (this%auxvar) - call this%create_field(this%auxvar%to_base(), 'AUXVAR', '') - - end subroutine create_virtual_fields + call this%set(this%nexg%base(), 'NEXG', '', MAP_ALL_TYPE) + call this%set(this%naux%base(), 'NAUX', '', MAP_ALL_TYPE) + call this%set(this%ianglex%base(), 'IANGLEX', '', MAP_ALL_TYPE) + call this%set(this%nodem1%base(), 'NODEM1', '', & + MAP_ALL_TYPE, is_nodem1_local) + call this%set(this%nodem2%base(), 'NODEM2', '', & + MAP_ALL_TYPE, is_nodem2_local) + call this%set(this%ihc%base(), 'IHC', '', MAP_ALL_TYPE) + call this%set(this%cl1%base(), 'CL1', '', MAP_ALL_TYPE) + call this%set(this%cl2%base(), 'CL2', '', MAP_ALL_TYPE) + call this%set(this%hwva%base(), 'HWVA', '', MAP_ALL_TYPE) + call this%set(this%auxvar%base(), 'AUXVAR', '', MAP_ALL_TYPE) + + end subroutine init_virtual_data subroutine vx_prepare_stage(this, stage) class(VirtualExchangeType) :: this @@ -156,35 +148,25 @@ subroutine vx_prepare_stage(this, stage) ! local integer(I4B) :: nexg, naux - if (stage == STG_AFTER_EXG_DF) then + if (stage == STG_AFT_EXG_DF) then - call this%map(this%nexg%to_base(), & - (/STG_AFTER_EXG_DF/), MAP_ALL_TYPE) - call this%map(this%naux%to_base(), & - (/STG_AFTER_EXG_DF/), MAP_ALL_TYPE) - call this%map(this%ianglex%to_base(), & - (/STG_AFTER_EXG_DF/), MAP_ALL_TYPE) + call this%map(this%nexg%base(), (/STG_AFT_EXG_DF/)) + call this%map(this%naux%base(), (/STG_AFT_EXG_DF/)) + call this%map(this%ianglex%base(), (/STG_AFT_EXG_DF/)) - else if (stage == STG_AFTER_CON_CR) then + else if (stage == STG_AFT_CON_CR) then nexg = this%nexg%get() naux = this%naux%get() - call this%map(this%nodem1%to_base(), nexg, & - (/STG_AFTER_CON_CR, STG_BEFORE_CON_DF/), & - MAP_ALL_TYPE) - call this%map(this%nodem2%to_base(), nexg, & - (/STG_AFTER_CON_CR, STG_BEFORE_CON_DF/), & - MAP_ALL_TYPE) - call this%map(this%ihc%to_base(), nexg, & - (/STG_AFTER_CON_CR/), MAP_ALL_TYPE) - call this%map(this%cl1%to_base(), nexg, & - (/STG_AFTER_CON_CR/), MAP_ALL_TYPE) - call this%map(this%cl2%to_base(), nexg, & - (/STG_AFTER_CON_CR/), MAP_ALL_TYPE) - call this%map(this%hwva%to_base(), nexg, & - (/STG_AFTER_CON_CR/), MAP_ALL_TYPE) - call this%map(this%auxvar%to_base(), naux, nexg, & - (/STG_AFTER_CON_CR/), MAP_ALL_TYPE) + call this%map(this%nodem1%base(), nexg, (/STG_AFT_CON_CR, & + STG_BFR_CON_DF/)) + call this%map(this%nodem2%base(), nexg, (/STG_AFT_CON_CR, & + STG_BFR_CON_DF/)) + call this%map(this%ihc%base(), nexg, (/STG_AFT_CON_CR/)) + call this%map(this%cl1%base(), nexg, (/STG_AFT_CON_CR/)) + call this%map(this%cl2%base(), nexg, (/STG_AFT_CON_CR/)) + call this%map(this%hwva%base(), nexg, (/STG_AFT_CON_CR/)) + call this%map(this%auxvar%base(), naux, nexg, (/STG_AFT_CON_CR/)) end if @@ -267,6 +249,22 @@ subroutine vx_destroy(this) end subroutine vx_destroy + subroutine allocate_data(this) + class(VirtualExchangeType) :: this + + allocate (this%nexg) + allocate (this%naux) + allocate (this%ianglex) + allocate (this%nodem1) + allocate (this%nodem2) + allocate (this%ihc) + allocate (this%cl1) + allocate (this%cl2) + allocate (this%hwva) + allocate (this%auxvar) + + end subroutine allocate_data + subroutine deallocate_data(this) class(VirtualExchangeType) :: this diff --git a/src/Distributed/VirtualGwfModel.f90 b/src/Distributed/VirtualGwfModel.f90 index b68bd7f21f7..86a973e5a13 100644 --- a/src/Distributed/VirtualGwfModel.f90 +++ b/src/Distributed/VirtualGwfModel.f90 @@ -30,7 +30,8 @@ module VirtualGwfModelModule procedure :: destroy => vgwf_destroy procedure :: prepare_stage => vgwf_prepare_stage ! private - procedure, private :: create_virtual_fields + procedure, private :: init_virtual_data + procedure, private :: allocate_data procedure, private :: deallocate_data end type VirtualGwfModelType @@ -65,40 +66,28 @@ subroutine vgwf_create(this, name, id, model) call this%VirtualModelType%create(name, id, model) this%container_type = VDC_GWFMODEL_TYPE - ! allocate fields - call this%create_virtual_fields() + call this%allocate_data() + call this%init_virtual_data() end subroutine vgwf_create - subroutine create_virtual_fields(this) + subroutine init_virtual_data(this) class(VirtualGwfModelType) :: this - allocate (this%npf_iangle1) - call this%create_field(this%npf_iangle1%to_base(), 'IANGLE1', 'NPF') - allocate (this%npf_iangle2) - call this%create_field(this%npf_iangle2%to_base(), 'IANGLE2', 'NPF') - allocate (this%npf_iangle3) - call this%create_field(this%npf_iangle3%to_base(), 'IANGLE3', 'NPF') - allocate (this%npf_iwetdry) - call this%create_field(this%npf_iwetdry%to_base(), 'IWETDRY', 'NPF') - allocate (this%npf_icelltype) - call this%create_field(this%npf_icelltype%to_base(), 'ICELLTYPE', 'NPF') - allocate (this%npf_k11) - call this%create_field(this%npf_k11%to_base(), 'K11', 'NPF') - allocate (this%npf_k22) - call this%create_field(this%npf_k22%to_base(), 'K22', 'NPF') - allocate (this%npf_k33) - call this%create_field(this%npf_k33%to_base(), 'K33', 'NPF') - allocate (this%npf_angle1) - call this%create_field(this%npf_angle1%to_base(), 'ANGLE1', 'NPF') - allocate (this%npf_angle2) - call this%create_field(this%npf_angle2%to_base(), 'ANGLE2', 'NPF') - allocate (this%npf_angle3) - call this%create_field(this%npf_angle3%to_base(), 'ANGLE3', 'NPF') - allocate (this%npf_wetdry) - call this%create_field(this%npf_wetdry%to_base(), 'WETDRY', 'NPF') - - end subroutine create_virtual_fields + call this%set(this%npf_iangle1%base(), 'IANGLE1', 'NPF', MAP_ALL_TYPE) + call this%set(this%npf_iangle2%base(), 'IANGLE2', 'NPF', MAP_ALL_TYPE) + call this%set(this%npf_iangle3%base(), 'IANGLE3', 'NPF', MAP_ALL_TYPE) + call this%set(this%npf_iwetdry%base(), 'IWETDRY', 'NPF', MAP_ALL_TYPE) + call this%set(this%npf_icelltype%base(), 'ICELLTYPE', 'NPF', MAP_NODE_TYPE) + call this%set(this%npf_k11%base(), 'K11', 'NPF', MAP_NODE_TYPE) + call this%set(this%npf_k22%base(), 'K22', 'NPF', MAP_NODE_TYPE) + call this%set(this%npf_k33%base(), 'K33', 'NPF', MAP_NODE_TYPE) + call this%set(this%npf_angle1%base(), 'ANGLE1', 'NPF', MAP_NODE_TYPE) + call this%set(this%npf_angle2%base(), 'ANGLE2', 'NPF', MAP_NODE_TYPE) + call this%set(this%npf_angle3%base(), 'ANGLE3', 'NPF', MAP_NODE_TYPE) + call this%set(this%npf_wetdry%base(), 'WETDRY', 'NPF', MAP_NODE_TYPE) + + end subroutine init_virtual_data subroutine vgwf_prepare_stage(this, stage) class(VirtualGwfModelType) :: this @@ -109,66 +98,48 @@ subroutine vgwf_prepare_stage(this, stage) ! prepare base (=numerical) model data items call this%VirtualModelType%prepare_stage(stage) - if (stage == STG_AFTER_MDL_DF) then + if (stage == STG_AFT_MDL_DF) then - call this%map(this%npf_iangle1%to_base(), & - (/STG_AFTER_MDL_DF/), MAP_ALL_TYPE) - call this%map(this%npf_iangle2%to_base(), & - (/STG_AFTER_MDL_DF/), MAP_ALL_TYPE) - call this%map(this%npf_iangle3%to_base(), & - (/STG_AFTER_MDL_DF/), MAP_ALL_TYPE) - call this%map(this%npf_iwetdry%to_base(), & - (/STG_AFTER_MDL_DF/), MAP_ALL_TYPE) + call this%map(this%npf_iangle1%base(), (/STG_AFT_MDL_DF/)) + call this%map(this%npf_iangle2%base(), (/STG_AFT_MDL_DF/)) + call this%map(this%npf_iangle3%base(), (/STG_AFT_MDL_DF/)) + call this%map(this%npf_iwetdry%base(), (/STG_AFT_MDL_DF/)) - else if (stage == STG_BEFORE_AR) then + else if (stage == STG_BFR_CON_AR) then nr_nodes = this%element_maps(MAP_NODE_TYPE)%nr_virt_elems ! Num. model data - call this%map(this%x%to_base(), nr_nodes, & - (/STG_BEFORE_AR, STG_BEFORE_AD, STG_BEFORE_CF/), & - MAP_NODE_TYPE) - call this%map(this%ibound%to_base(), nr_nodes, & - (/STG_BEFORE_AR, STG_BEFORE_AD, STG_BEFORE_CF/), & - MAP_NODE_TYPE) - call this%map(this%x_old%to_base(), nr_nodes, & - (/STG_BEFORE_AD, STG_BEFORE_CF/), MAP_NODE_TYPE) + call this%map(this%x%base(), nr_nodes, & + (/STG_BFR_CON_AR, STG_BFR_EXG_AD, STG_BFR_EXG_CF/)) + call this%map(this%ibound%base(), nr_nodes, & + (/STG_BFR_CON_AR, STG_BFR_EXG_AD, STG_BFR_EXG_CF/)) + call this%map(this%x_old%base(), nr_nodes, & + (/STG_BFR_EXG_AD, STG_BFR_EXG_CF/)) ! NPF - call this%map(this%npf_icelltype%to_base(), nr_nodes, & - (/STG_BEFORE_AR/), MAP_NODE_TYPE) - call this%map(this%npf_k11%to_base(), nr_nodes, & - (/STG_BEFORE_AR/), MAP_NODE_TYPE) - call this%map(this%npf_k22%to_base(), nr_nodes, & - (/STG_BEFORE_AR/), MAP_NODE_TYPE) - call this%map(this%npf_k33%to_base(), nr_nodes, & - (/STG_BEFORE_AR/), MAP_NODE_TYPE) + call this%map(this%npf_icelltype%base(), nr_nodes, (/STG_BFR_CON_AR/)) + call this%map(this%npf_k11%base(), nr_nodes, (/STG_BFR_CON_AR/)) + call this%map(this%npf_k22%base(), nr_nodes, (/STG_BFR_CON_AR/)) + call this%map(this%npf_k33%base(), nr_nodes, (/STG_BFR_CON_AR/)) if (this%npf_iangle1%get() > 0) then - call this%map(this%npf_angle1%to_base(), nr_nodes, & - (/STG_BEFORE_AR/), MAP_NODE_TYPE) + call this%map(this%npf_angle1%base(), nr_nodes, (/STG_BFR_CON_AR/)) else - call this%map(this%npf_angle1%to_base(), 0, & - (/STG_NEVER/), MAP_NODE_TYPE) + call this%map(this%npf_angle1%base(), 0, (/STG_NEVER/)) end if if (this%npf_iangle2%get() > 0) then - call this%map(this%npf_angle2%to_base(), nr_nodes, & - (/STG_BEFORE_AR/), MAP_NODE_TYPE) + call this%map(this%npf_angle2%base(), nr_nodes, (/STG_BFR_CON_AR/)) else - call this%map(this%npf_angle2%to_base(), 0, & - (/STG_NEVER/), MAP_NODE_TYPE) + call this%map(this%npf_angle2%base(), 0, (/STG_NEVER/)) end if if (this%npf_iangle3%get() > 0) then - call this%map(this%npf_angle3%to_base(), nr_nodes, & - (/STG_BEFORE_AR/), MAP_NODE_TYPE) + call this%map(this%npf_angle3%base(), nr_nodes, (/STG_BFR_CON_AR/)) else - call this%map(this%npf_angle3%to_base(), 0, & - (/STG_NEVER/), MAP_NODE_TYPE) + call this%map(this%npf_angle3%base(), 0, (/STG_NEVER/)) end if if (this%npf_iwetdry%get() > 0) then - call this%map(this%npf_wetdry%to_base(), nr_nodes, & - (/STG_BEFORE_AR/), MAP_NODE_TYPE) + call this%map(this%npf_wetdry%base(), nr_nodes, (/STG_BFR_CON_AR/)) else - call this%map(this%npf_wetdry%to_base(), 0, & - (/STG_NEVER/), MAP_NODE_TYPE) + call this%map(this%npf_wetdry%base(), 0, (/STG_NEVER/)) end if end if @@ -183,6 +154,24 @@ subroutine vgwf_destroy(this) end subroutine vgwf_destroy + subroutine allocate_data(this) + class(VirtualGwfModelType) :: this + + allocate (this%npf_iangle1) + allocate (this%npf_iangle2) + allocate (this%npf_iangle3) + allocate (this%npf_iwetdry) + allocate (this%npf_icelltype) + allocate (this%npf_k11) + allocate (this%npf_k22) + allocate (this%npf_k33) + allocate (this%npf_angle1) + allocate (this%npf_angle2) + allocate (this%npf_angle3) + allocate (this%npf_wetdry) + + end subroutine allocate_data + subroutine deallocate_data(this) class(VirtualGwfModelType) :: this diff --git a/src/Distributed/VirtualGwtExchange.f90 b/src/Distributed/VirtualGwtExchange.f90 index 3de1a862950..3da07500154 100644 --- a/src/Distributed/VirtualGwtExchange.f90 +++ b/src/Distributed/VirtualGwtExchange.f90 @@ -17,7 +17,8 @@ module VirtualGwtExchangeModule procedure :: destroy => vtx_destroy procedure :: prepare_stage => vtx_prepare_stage ! private - procedure, private :: create_virtual_fields + procedure, private :: init_virtual_data + procedure, private :: allocate_data procedure, private :: deallocate_data end type VirtualGwtExchangeType @@ -55,18 +56,17 @@ subroutine vtx_create(this, name, exg_id, m1_id, m2_id) call this%VirtualExchangeType%create(name, exg_id, m1_id, m2_id) this%container_type = VDC_GWTEXG_TYPE - ! allocate gwtgwt field(s) - call this%create_virtual_fields() + call this%allocate_data() + call this%init_virtual_data() end subroutine vtx_create - subroutine create_virtual_fields(this) + subroutine init_virtual_data(this) class(VirtualGwtExchangeType) :: this - allocate (this%gwfsimvals) - call this%create_field(this%gwfsimvals%to_base(), 'GWFSIMVALS', '') + call this%set(this%gwfsimvals%base(), 'GWFSIMVALS', '', MAP_ALL_TYPE) - end subroutine create_virtual_fields + end subroutine init_virtual_data subroutine vtx_prepare_stage(this, stage) class(VirtualGwtExchangeType) :: this @@ -77,10 +77,9 @@ subroutine vtx_prepare_stage(this, stage) ! prepare base exchange data items call this%VirtualExchangeType%prepare_stage(stage) - if (stage == STG_BEFORE_AR) then + if (stage == STG_BFR_CON_AR) then nexg = this%nexg%get() - call this%map(this%gwfsimvals%to_base(), nexg, & - (/STG_BEFORE_AD/), MAP_ALL_TYPE) + call this%map(this%gwfsimvals%base(), nexg, (/STG_BFR_EXG_AD/)) end if end subroutine vtx_prepare_stage @@ -93,6 +92,13 @@ subroutine vtx_destroy(this) end subroutine vtx_destroy + subroutine allocate_data(this) + class(VirtualGwtExchangeType) :: this + + allocate (this%gwfsimvals) + + end subroutine allocate_data + subroutine deallocate_data(this) class(VirtualGwtExchangeType) :: this diff --git a/src/Distributed/VirtualGwtModel.f90 b/src/Distributed/VirtualGwtModel.f90 index 49d4f67ab04..f4b3c8d5d00 100644 --- a/src/Distributed/VirtualGwtModel.f90 +++ b/src/Distributed/VirtualGwtModel.f90 @@ -36,7 +36,8 @@ module VirtualGwtModelModule procedure :: prepare_stage => vgwt_prepare_stage procedure :: destroy => vgwt_destroy ! private - procedure, private :: create_virtual_fields + procedure, private :: init_virtual_data + procedure, private :: allocate_data procedure, private :: deallocate_data end type VirtualGwtModelType @@ -69,46 +70,31 @@ subroutine vgwt_create(this, name, id, model) call this%VirtualModelType%create(name, id, model) this%container_type = VDC_GWTMODEL_TYPE - ! allocate fields - call this%create_virtual_fields() + call this%allocate_data() + call this%init_virtual_data() end subroutine vgwt_create - subroutine create_virtual_fields(this) + subroutine init_virtual_data(this) class(VirtualGwtModelType) :: this - allocate (this%dsp_idiffc) - call this%create_field(this%dsp_idiffc%to_base(), 'IDIFFC', 'DSP') - allocate (this%dsp_idisp) - call this%create_field(this%dsp_idisp%to_base(), 'IDISP', 'DSP') - allocate (this%dsp_diffc) - call this%create_field(this%dsp_diffc%to_base(), 'DIFFC', 'DSP') - allocate (this%dsp_alh) - call this%create_field(this%dsp_alh%to_base(), 'ALH', 'DSP') - allocate (this%dsp_alv) - call this%create_field(this%dsp_alv%to_base(), 'ALV', 'DSP') - allocate (this%dsp_ath1) - call this%create_field(this%dsp_ath1%to_base(), 'ATH1', 'DSP') - allocate (this%dsp_ath2) - call this%create_field(this%dsp_ath2%to_base(), 'ATH2', 'DSP') - allocate (this%dsp_atv) - call this%create_field(this%dsp_atv%to_base(), 'ATV', 'DSP') - allocate (this%fmi_gwfhead) - call this%create_field(this%fmi_gwfhead%to_base(), 'GWFHEAD', 'FMI') - allocate (this%fmi_gwfsat) - call this%create_field(this%fmi_gwfsat%to_base(), 'GWFSAT', 'FMI') - allocate (this%fmi_gwfspdis) - call this%create_field(this%fmi_gwfspdis%to_base(), 'GWFSPDIS', 'FMI') - allocate (this%fmi_gwfflowja) - call this%create_field(this%fmi_gwfflowja%to_base(), 'GWFFLOWJA', 'FMI') - allocate (this%mst_porosity) - call this%create_field(this%mst_porosity%to_base(), 'POROSITY', 'MST') - allocate (this%indsp) - call this%create_field(this%indsp%to_base(), 'INDSP', '') - allocate (this%inmst) - call this%create_field(this%inmst%to_base(), 'INMST', '') - - end subroutine create_virtual_fields + call this%set(this%dsp_idiffc%base(), 'IDIFFC', 'DSP', MAP_ALL_TYPE) + call this%set(this%dsp_idisp%base(), 'IDISP', 'DSP', MAP_ALL_TYPE) + call this%set(this%dsp_diffc%base(), 'DIFFC', 'DSP', MAP_NODE_TYPE) + call this%set(this%dsp_alh%base(), 'ALH', 'DSP', MAP_NODE_TYPE) + call this%set(this%dsp_alv%base(), 'ALV', 'DSP', MAP_NODE_TYPE) + call this%set(this%dsp_ath1%base(), 'ATH1', 'DSP', MAP_NODE_TYPE) + call this%set(this%dsp_ath2%base(), 'ATH2', 'DSP', MAP_NODE_TYPE) + call this%set(this%dsp_atv%base(), 'ATV', 'DSP', MAP_NODE_TYPE) + call this%set(this%fmi_gwfhead%base(), 'GWFHEAD', 'FMI', MAP_NODE_TYPE) + call this%set(this%fmi_gwfsat%base(), 'GWFSAT', 'FMI', MAP_NODE_TYPE) + call this%set(this%fmi_gwfspdis%base(), 'GWFSPDIS', 'FMI', MAP_NODE_TYPE) + call this%set(this%fmi_gwfflowja%base(), 'GWFFLOWJA', 'FMI', MAP_NODE_TYPE) + call this%set(this%mst_porosity%base(), 'POROSITY', 'MST', MAP_NODE_TYPE) + call this%set(this%indsp%base(), 'INDSP', '', MAP_ALL_TYPE) + call this%set(this%inmst%base(), 'INMST', '', MAP_ALL_TYPE) + + end subroutine init_virtual_data subroutine vgwt_prepare_stage(this, stage) class(VirtualGwtModelType) :: this @@ -122,61 +108,65 @@ subroutine vgwt_prepare_stage(this, stage) nr_nodes = 0 nr_conns = 0 - if (stage == STG_AFTER_MDL_DF) then + if (stage == STG_AFT_MDL_DF) then - call this%map(this%dsp_idiffc%to_base(), & - (/STG_AFTER_MDL_DF/), MAP_ALL_TYPE) - call this%map(this%dsp_idisp%to_base(), & - (/STG_AFTER_MDL_DF/), MAP_ALL_TYPE) - call this%map(this%indsp%to_base(), & - (/STG_AFTER_MDL_DF/), MAP_ALL_TYPE) - call this%map(this%inmst%to_base(), & - (/STG_AFTER_MDL_DF/), MAP_ALL_TYPE) + call this%map(this%dsp_idiffc%base(), (/STG_AFT_MDL_DF/)) + call this%map(this%dsp_idisp%base(), (/STG_AFT_MDL_DF/)) + call this%map(this%indsp%base(), (/STG_AFT_MDL_DF/)) + call this%map(this%inmst%base(), (/STG_AFT_MDL_DF/)) - else if (stage == STG_BEFORE_AR) then + else if (stage == STG_BFR_CON_AR) then - call this%map(this%x%to_base(), nr_nodes, & - (/STG_BEFORE_AR, STG_BEFORE_AD, STG_BEFORE_CF/), & - MAP_NODE_TYPE) - call this%map(this%ibound%to_base(), nr_nodes, & - (/STG_BEFORE_AR/), MAP_NODE_TYPE) + call this%map(this%x%base(), nr_nodes, & + (/STG_BFR_CON_AR, STG_BFR_EXG_AD, STG_BFR_EXG_CF/)) + call this%map(this%ibound%base(), nr_nodes, (/STG_BFR_CON_AR/)) if (this%dsp_idiffc%get() > 0) then - call this%map(this%dsp_diffc%to_base(), nr_nodes, & - (/STG_BEFORE_AR/), MAP_NODE_TYPE) + call this%map(this%dsp_diffc%base(), nr_nodes, (/STG_BFR_CON_AR/)) end if if (this%dsp_idisp%get() > 0) then - call this%map(this%dsp_alh%to_base(), nr_nodes, & - (/STG_BEFORE_AR/), MAP_NODE_TYPE) - call this%map(this%dsp_alv%to_base(), nr_nodes, & - (/STG_BEFORE_AR/), MAP_NODE_TYPE) - call this%map(this%dsp_ath1%to_base(), nr_nodes, & - (/STG_BEFORE_AR/), MAP_NODE_TYPE) - call this%map(this%dsp_ath2%to_base(), nr_nodes, & - (/STG_BEFORE_AR/), MAP_NODE_TYPE) - call this%map(this%dsp_atv%to_base(), nr_nodes, & - (/STG_BEFORE_AR/), MAP_NODE_TYPE) + call this%map(this%dsp_alh%base(), nr_nodes, (/STG_BFR_CON_AR/)) + call this%map(this%dsp_alv%base(), nr_nodes, (/STG_BFR_CON_AR/)) + call this%map(this%dsp_ath1%base(), nr_nodes, (/STG_BFR_CON_AR/)) + call this%map(this%dsp_ath2%base(), nr_nodes, (/STG_BFR_CON_AR/)) + call this%map(this%dsp_atv%base(), nr_nodes, (/STG_BFR_CON_AR/)) end if - call this%map(this%fmi_gwfhead%to_base(), nr_nodes, & - (/STG_BEFORE_AD/), MAP_NODE_TYPE) - call this%map(this%fmi_gwfsat%to_base(), nr_nodes, & - (/STG_BEFORE_AD/), MAP_NODE_TYPE) - call this%map(this%fmi_gwfspdis%to_base(), 3, nr_nodes, & - (/STG_BEFORE_AD/), MAP_NODE_TYPE) - call this%map(this%fmi_gwfflowja%to_base(), nr_conns, & - (/STG_BEFORE_AD/), MAP_NODE_TYPE) + call this%map(this%fmi_gwfhead%base(), nr_nodes, (/STG_BFR_EXG_AD/)) + call this%map(this%fmi_gwfsat%base(), nr_nodes, (/STG_BFR_EXG_AD/)) + call this%map(this%fmi_gwfspdis%base(), 3, nr_nodes, (/STG_BFR_EXG_AD/)) + call this%map(this%fmi_gwfflowja%base(), nr_conns, (/STG_BFR_EXG_AD/)) if (this%indsp%get() > 0 .and. this%inmst%get() > 0) then - call this%map(this%mst_porosity%to_base(), nr_nodes, & - (/STG_AFTER_AR/), MAP_NODE_TYPE) + call this%map(this%mst_porosity%base(), nr_nodes, (/STG_AFT_CON_AR/)) end if end if end subroutine vgwt_prepare_stage + subroutine allocate_data(this) + class(VirtualGwtModelType) :: this + + allocate (this%dsp_idiffc) + allocate (this%dsp_idisp) + allocate (this%dsp_diffc) + allocate (this%dsp_alh) + allocate (this%dsp_alv) + allocate (this%dsp_ath1) + allocate (this%dsp_ath2) + allocate (this%dsp_atv) + allocate (this%fmi_gwfhead) + allocate (this%fmi_gwfsat) + allocate (this%fmi_gwfspdis) + allocate (this%fmi_gwfflowja) + allocate (this%mst_porosity) + allocate (this%indsp) + allocate (this%inmst) + + end subroutine allocate_data + subroutine deallocate_data(this) class(VirtualGwtModelType) :: this diff --git a/src/Distributed/VirtualModel.f90 b/src/Distributed/VirtualModel.f90 index 3ac84b20918..4a5f761e865 100644 --- a/src/Distributed/VirtualModel.f90 +++ b/src/Distributed/VirtualModel.f90 @@ -55,7 +55,8 @@ module VirtualModelModule procedure :: dis_noder_to_string ! private - procedure, private :: create_virtual_fields + procedure, private :: init_virtual_data + procedure, private :: allocate_data procedure, private :: deallocate_data procedure, private :: eq_virtual_model procedure, private :: eq_numerical_model @@ -76,71 +77,45 @@ subroutine vm_create(this, name, id, model) this%local_model => model - ! allocate fields - call this%create_virtual_fields() + call this%allocate_data() + call this%init_virtual_data() end subroutine vm_create - subroutine create_virtual_fields(this) + subroutine init_virtual_data(this) class(VirtualModelType) :: this ! CON - allocate (this%con_ia) - call this%create_field(this%con_ia%to_base(), 'IA', 'CON') - allocate (this%con_ja) - call this%create_field(this%con_ja%to_base(), 'JA', 'CON') - allocate (this%con_jas) - call this%create_field(this%con_jas%to_base(), 'JAS', 'CON') - allocate (this%con_ihc) - call this%create_field(this%con_ihc%to_base(), 'IHC', 'CON') - allocate (this%con_hwva) - call this%create_field(this%con_hwva%to_base(), 'HWVA', 'CON') - allocate (this%con_cl1) - call this%create_field(this%con_cl1%to_base(), 'CL1', 'CON') - allocate (this%con_cl2) - call this%create_field(this%con_cl2%to_base(), 'CL2', 'CON') - allocate (this%con_anglex) - call this%create_field(this%con_anglex%to_base(), 'ANGLEX', 'CON') + call this%set(this%con_ia%base(), 'IA', 'CON', MAP_ALL_TYPE) + call this%set(this%con_ja%base(), 'JA', 'CON', MAP_ALL_TYPE) + call this%set(this%con_jas%base(), 'JAS', 'CON', MAP_ALL_TYPE) + call this%set(this%con_ihc%base(), 'IHC', 'CON', MAP_ALL_TYPE) + call this%set(this%con_hwva%base(), 'HWVA', 'CON', MAP_ALL_TYPE) + call this%set(this%con_cl1%base(), 'CL1', 'CON', MAP_ALL_TYPE) + call this%set(this%con_cl2%base(), 'CL2', 'CON', MAP_ALL_TYPE) + call this%set(this%con_anglex%base(), 'ANGLEX', 'CON', MAP_ALL_TYPE) ! DIS - allocate (this%dis_ndim) - call this%create_field(this%dis_ndim%to_base(), 'NDIM', 'DIS') - allocate (this%dis_nodes) - call this%create_field(this%dis_nodes%to_base(), 'NODES', 'DIS') - allocate (this%dis_nodesuser) - call this%create_field(this%dis_nodesuser%to_base(), 'NODESUSER', 'DIS') - allocate (this%dis_nodeuser) - call this%create_field(this%dis_nodeuser%to_base(), 'NODEUSER', 'DIS') - allocate (this%dis_nja) - call this%create_field(this%dis_nja%to_base(), 'NJA', 'DIS') - allocate (this%dis_njas) - call this%create_field(this%dis_njas%to_base(), 'NJAS', 'DIS') - allocate (this%dis_xorigin) - call this%create_field(this%dis_xorigin%to_base(), 'XORIGIN', 'DIS') - allocate (this%dis_yorigin) - call this%create_field(this%dis_yorigin%to_base(), 'YORIGIN', 'DIS') - allocate (this%dis_angrot) - call this%create_field(this%dis_angrot%to_base(), 'ANGROT', 'DIS') - allocate (this%dis_xc) - call this%create_field(this%dis_xc%to_base(), 'XC', 'DIS') - allocate (this%dis_yc) - call this%create_field(this%dis_yc%to_base(), 'YC', 'DIS') - allocate (this%dis_top) - call this%create_field(this%dis_top%to_base(), 'TOP', 'DIS') - allocate (this%dis_bot) - call this%create_field(this%dis_bot%to_base(), 'BOT', 'DIS') - allocate (this%dis_area) - call this%create_field(this%dis_area%to_base(), 'AREA', 'DIS') + call this%set(this%dis_ndim%base(), 'NDIM', 'DIS', MAP_ALL_TYPE) + call this%set(this%dis_nodes%base(), 'NODES', 'DIS', MAP_ALL_TYPE) + call this%set(this%dis_nodesuser%base(), 'NODESUSER', 'DIS', MAP_ALL_TYPE) + call this%set(this%dis_nodeuser%base(), 'NODEUSER', 'DIS', MAP_ALL_TYPE) + call this%set(this%dis_nja%base(), 'NJA', 'DIS', MAP_ALL_TYPE) + call this%set(this%dis_njas%base(), 'NJAS', 'DIS', MAP_ALL_TYPE) + call this%set(this%dis_xorigin%base(), 'XORIGIN', 'DIS', MAP_ALL_TYPE) + call this%set(this%dis_yorigin%base(), 'YORIGIN', 'DIS', MAP_ALL_TYPE) + call this%set(this%dis_angrot%base(), 'ANGROT', 'DIS', MAP_ALL_TYPE) + call this%set(this%dis_xc%base(), 'XC', 'DIS', MAP_ALL_TYPE) + call this%set(this%dis_yc%base(), 'YC', 'DIS', MAP_ALL_TYPE) + call this%set(this%dis_top%base(), 'TOP', 'DIS', MAP_ALL_TYPE) + call this%set(this%dis_bot%base(), 'BOT', 'DIS', MAP_ALL_TYPE) + call this%set(this%dis_area%base(), 'AREA', 'DIS', MAP_ALL_TYPE) ! Numerical model - allocate (this%moffset) - call this%create_field(this%moffset%to_base(), 'MOFFSET', '') - allocate (this%x) - call this%create_field(this%x%to_base(), 'X', '') - allocate (this%x_old) - call this%create_field(this%x_old%to_base(), 'XOLD', '') - allocate (this%ibound) - call this%create_field(this%ibound%to_base(), 'IBOUND', '') + call this%set(this%moffset%base(), 'MOFFSET', '', MAP_ALL_TYPE) + call this%set(this%x%base(), 'X', '', MAP_NODE_TYPE) + call this%set(this%x_old%base(), 'XOLD', '', MAP_NODE_TYPE) + call this%set(this%ibound%base(), 'IBOUND', '', MAP_NODE_TYPE) - end subroutine create_virtual_fields + end subroutine init_virtual_data subroutine vm_prepare_stage(this, stage) class(VirtualModelType) :: this @@ -149,73 +124,51 @@ subroutine vm_prepare_stage(this, stage) integer(I4B) :: nodes, nodesuser, nja, njas logical(LGP) :: is_reduced - if (stage == STG_AFTER_MDL_DF) then + if (stage == STG_AFT_MDL_DF) then - call this%map(this%dis_ndim%to_base(), & - (/STG_AFTER_MDL_DF/), MAP_ALL_TYPE) - call this%map(this%dis_nodes%to_base(), & - (/STG_AFTER_MDL_DF/), MAP_ALL_TYPE) - call this%map(this%dis_nodesuser%to_base(), & - (/STG_AFTER_MDL_DF/), MAP_ALL_TYPE) - call this%map(this%dis_nja%to_base(), & - (/STG_AFTER_MDL_DF/), MAP_ALL_TYPE) - call this%map(this%dis_njas%to_base(), & - (/STG_AFTER_MDL_DF/), MAP_ALL_TYPE) + call this%map(this%dis_ndim%base(), (/STG_AFT_MDL_DF/)) + call this%map(this%dis_nodes%base(), (/STG_AFT_MDL_DF/)) + call this%map(this%dis_nodesuser%base(), (/STG_AFT_MDL_DF/)) + call this%map(this%dis_nja%base(), (/STG_AFT_MDL_DF/)) + call this%map(this%dis_njas%base(), (/STG_AFT_MDL_DF/)) - else if (stage == STG_BEFORE_AC) then + else if (stage == STG_BFR_EXG_AC) then nodes = this%dis_nodes%get() nodesuser = this%dis_nodesuser%get() is_reduced = (nodes /= nodesuser) - call this%map(this%moffset%to_base(), & - (/STG_BEFORE_AC/), MAP_ALL_TYPE) + call this%map(this%moffset%base(), (/STG_BFR_EXG_AC/)) if (is_reduced) then - call this%map(this%dis_nodeuser%to_base(), nodes, & - (/STG_BEFORE_AC/), MAP_ALL_TYPE) + call this%map(this%dis_nodeuser%base(), nodes, (/STG_BFR_EXG_AC/)) else ! no reduction, zero sized array, never synchronize - call this%map(this%dis_nodeuser%to_base(), 0, & - (/STG_NEVER/), MAP_ALL_TYPE) + call this%map(this%dis_nodeuser%base(), 0, (/STG_NEVER/)) end if - else if (stage == STG_BEFORE_CON_DF) then + + else if (stage == STG_BFR_CON_DF) then nodes = this%dis_nodes%get() nja = this%dis_nja%get() njas = this%dis_njas%get() ! DIS - call this%map(this%dis_xorigin%to_base(), & - (/STG_BEFORE_CON_DF/), MAP_ALL_TYPE) - call this%map(this%dis_yorigin%to_base(), & - (/STG_BEFORE_CON_DF/), MAP_ALL_TYPE) - call this%map(this%dis_angrot%to_base(), & - (/STG_BEFORE_CON_DF/), MAP_ALL_TYPE) - call this%map(this%dis_xc%to_base(), nodes, & - (/STG_BEFORE_CON_DF/), MAP_ALL_TYPE) - call this%map(this%dis_yc%to_base(), nodes, & - (/STG_BEFORE_CON_DF/), MAP_ALL_TYPE) - call this%map(this%dis_top%to_base(), nodes, & - (/STG_BEFORE_CON_DF/), MAP_ALL_TYPE) - call this%map(this%dis_bot%to_base(), nodes, & - (/STG_BEFORE_CON_DF/), MAP_ALL_TYPE) - call this%map(this%dis_area%to_base(), nodes, & - (/STG_BEFORE_CON_DF/), MAP_ALL_TYPE) + call this%map(this%dis_xorigin%base(), (/STG_BFR_CON_DF/)) + call this%map(this%dis_yorigin%base(), (/STG_BFR_CON_DF/)) + call this%map(this%dis_angrot%base(), (/STG_BFR_CON_DF/)) + call this%map(this%dis_xc%base(), nodes, (/STG_BFR_CON_DF/)) + call this%map(this%dis_yc%base(), nodes, (/STG_BFR_CON_DF/)) + call this%map(this%dis_top%base(), nodes, (/STG_BFR_CON_DF/)) + call this%map(this%dis_bot%base(), nodes, (/STG_BFR_CON_DF/)) + call this%map(this%dis_area%base(), nodes, (/STG_BFR_CON_DF/)) ! CON - call this%map(this%con_ia%to_base(), nodes + 1, & - (/STG_BEFORE_CON_DF/), MAP_ALL_TYPE) - call this%map(this%con_ja%to_base(), nja, & - (/STG_BEFORE_CON_DF/), MAP_ALL_TYPE) - call this%map(this%con_jas%to_base(), nja, & - (/STG_BEFORE_CON_DF/), MAP_ALL_TYPE) - call this%map(this%con_ihc%to_base(), njas, & - (/STG_BEFORE_CON_DF/), MAP_ALL_TYPE) - call this%map(this%con_hwva%to_base(), njas, & - (/STG_BEFORE_CON_DF/), MAP_ALL_TYPE) - call this%map(this%con_cl1%to_base(), njas, & - (/STG_BEFORE_CON_DF/), MAP_ALL_TYPE) - call this%map(this%con_cl2%to_base(), njas, & - (/STG_BEFORE_CON_DF/), MAP_ALL_TYPE) - call this%map(this%con_anglex%to_base(), njas, & - (/STG_BEFORE_CON_DF/), MAP_ALL_TYPE) + call this%map(this%con_ia%base(), nodes + 1, (/STG_BFR_CON_DF/)) + call this%map(this%con_ja%base(), nja, (/STG_BFR_CON_DF/)) + call this%map(this%con_jas%base(), nja, (/STG_BFR_CON_DF/)) + call this%map(this%con_ihc%base(), njas, (/STG_BFR_CON_DF/)) + call this%map(this%con_hwva%base(), njas, (/STG_BFR_CON_DF/)) + call this%map(this%con_cl1%base(), njas, (/STG_BFR_CON_DF/)) + call this%map(this%con_cl2%base(), njas, (/STG_BFR_CON_DF/)) + call this%map(this%con_anglex%base(), njas, (/STG_BFR_CON_DF/)) + end if end subroutine vm_prepare_stage @@ -260,6 +213,38 @@ subroutine vm_destroy(this) end subroutine vm_destroy + subroutine allocate_data(this) + class(VirtualModelType) :: this + + allocate (this%con_ia) + allocate (this%con_ja) + allocate (this%con_jas) + allocate (this%con_ihc) + allocate (this%con_hwva) + allocate (this%con_cl1) + allocate (this%con_cl2) + allocate (this%con_anglex) + allocate (this%dis_ndim) + allocate (this%dis_nodes) + allocate (this%dis_nodesuser) + allocate (this%dis_nodeuser) + allocate (this%dis_nja) + allocate (this%dis_njas) + allocate (this%dis_xorigin) + allocate (this%dis_yorigin) + allocate (this%dis_angrot) + allocate (this%dis_xc) + allocate (this%dis_yc) + allocate (this%dis_top) + allocate (this%dis_bot) + allocate (this%dis_area) + allocate (this%moffset) + allocate (this%x) + allocate (this%x_old) + allocate (this%ibound) + + end subroutine allocate_data + subroutine deallocate_data(this) class(VirtualModelType) :: this diff --git a/src/Model/Connection/DistributedVariable.f90 b/src/Model/Connection/DistributedVariable.f90 index 5fbb6b057e8..ba18067aea2 100644 --- a/src/Model/Connection/DistributedVariable.f90 +++ b/src/Model/Connection/DistributedVariable.f90 @@ -10,10 +10,10 @@ module DistVariableModule public :: GetDistVarFromList ! types of variables - integer(I4B), public, parameter :: SYNC_SCALAR = 0 - integer(I4B), public, parameter :: SYNC_NODES = 1 - integer(I4B), public, parameter :: SYNC_CONNECTIONS = 2 - integer(I4B), public, parameter :: SYNC_EXCHANGES = 3 + integer(I4B), public, parameter :: SYNC_SCL = 0 !< synchronize as scalar + integer(I4B), public, parameter :: SYNC_NDS = 1 !< synchronize over nodes + integer(I4B), public, parameter :: SYNC_CON = 2 !< synchronize over connections + integer(I4B), public, parameter :: SYNC_EXG = 3 !< synchronize as exchange variable type, public :: DistVarType character(len=LENVARNAME) :: var_name !< name of variable, e.g. "K11" diff --git a/src/Model/Connection/GwfGwfConnection.f90 b/src/Model/Connection/GwfGwfConnection.f90 index 8f0caf24546..d452d3dcfb5 100644 --- a/src/Model/Connection/GwfGwfConnection.f90 +++ b/src/Model/Connection/GwfGwfConnection.f90 @@ -46,7 +46,7 @@ module GwfGwfConnectionModule integer(I4B) :: iout = 0 !< the list file for the interface model contains - procedure, pass(this) :: gwfGwfConnection_ctor + procedure :: gwfGwfConnection_ctor generic, public :: construct => gwfGwfConnection_ctor ! overriding NumericalExchangeType @@ -62,14 +62,15 @@ module GwfGwfConnectionModule procedure :: exg_ot => gwfgwfcon_ot ! overriding 'protected' - procedure, pass(this) :: validateConnection + procedure :: validateConnection ! local stuff - procedure, pass(this), private :: allocateScalars - procedure, pass(this), private :: setGridExtent - procedure, pass(this), private :: validateGwfExchange - procedure, pass(this), private :: setFlowToExchange - procedure, pass(this), private :: setNpfEdgeProps + procedure, private :: cfg_dist_vars + procedure, private :: allocateScalars + procedure, private :: setGridExtent + procedure, private :: validateGwfExchange + procedure, private :: setFlowToExchange + procedure, private :: setNpfEdgeProps end type GwfGwfConnectionType @@ -169,30 +170,7 @@ subroutine gwfgwfcon_df(this) this%gwfInterfaceModel%npf%iangle2 = this%gwfModel%npf%iangle2 this%gwfInterfaceModel%npf%iangle3 = this%gwfModel%npf%iangle3 - call this%addDistVar('X', '', SYNC_NODES, & - (/STG_BEFORE_AR, STG_BEFORE_AD, STG_BEFORE_CF/)) - call this%addDistVar('IBOUND', '', SYNC_NODES, & - (/STG_BEFORE_AR, STG_BEFORE_AD, STG_BEFORE_CF/)) - call this%addDistVar('XOLD', '', SYNC_NODES, (/STG_BEFORE_AD, STG_BEFORE_CF/)) - call this%addDistVar('ICELLTYPE', 'NPF', SYNC_NODES, (/STG_BEFORE_AR/)) - call this%addDistVar('K11', 'NPF', SYNC_NODES, (/STG_BEFORE_AR/)) - call this%addDistVar('K22', 'NPF', SYNC_NODES, (/STG_BEFORE_AR/)) - call this%addDistVar('K33', 'NPF', SYNC_NODES, (/STG_BEFORE_AR/)) - if (this%gwfInterfaceModel%npf%iangle1 == 1) then - call this%addDistVar('ANGLE1', 'NPF', SYNC_NODES, (/STG_BEFORE_AR/)) - end if - if (this%gwfInterfaceModel%npf%iangle2 == 1) then - call this%addDistVar('ANGLE2', 'NPF', SYNC_NODES, (/STG_BEFORE_AR/)) - end if - if (this%gwfInterfaceModel%npf%iangle3 == 1) then - call this%addDistVar('ANGLE3', 'NPF', SYNC_NODES, (/STG_BEFORE_AR/)) - end if - if (this%gwfInterfaceModel%npf%iwetdry == 1) then - call this%addDistVar('WETDRY', 'NPF', SYNC_NODES, (/STG_BEFORE_AR/)) - end if - call this%addDistVar('TOP', 'DIS', SYNC_NODES, (/STG_BEFORE_AR/)) - call this%addDistVar('BOT', 'DIS', SYNC_NODES, (/STG_BEFORE_AR/)) - call this%addDistVar('AREA', 'DIS', SYNC_NODES, (/STG_BEFORE_AR/)) + call this%cfg_dist_vars() if (this%gwfInterfaceModel%npf%ixt3d > 0) then this%gwfInterfaceModel%npf%iangle1 = 1 @@ -219,6 +197,38 @@ subroutine gwfgwfcon_df(this) end subroutine gwfgwfcon_df + !> @brief Configure distributed variables for this interface model + !< + subroutine cfg_dist_vars(this) + class(GwfGwfConnectionType) :: this !< the connection + + call this%cfg_dv('X', '', SYNC_NDS, & + (/STG_BFR_CON_AR, STG_BFR_EXG_AD, STG_BFR_EXG_CF/)) + call this%cfg_dv('IBOUND', '', SYNC_NDS, & + (/STG_BFR_CON_AR, STG_BFR_EXG_AD, STG_BFR_EXG_CF/)) + call this%cfg_dv('XOLD', '', SYNC_NDS, (/STG_BFR_EXG_AD, STG_BFR_EXG_CF/)) + call this%cfg_dv('ICELLTYPE', 'NPF', SYNC_NDS, (/STG_BFR_CON_AR/)) + call this%cfg_dv('K11', 'NPF', SYNC_NDS, (/STG_BFR_CON_AR/)) + call this%cfg_dv('K22', 'NPF', SYNC_NDS, (/STG_BFR_CON_AR/)) + call this%cfg_dv('K33', 'NPF', SYNC_NDS, (/STG_BFR_CON_AR/)) + if (this%gwfInterfaceModel%npf%iangle1 == 1) then + call this%cfg_dv('ANGLE1', 'NPF', SYNC_NDS, (/STG_BFR_CON_AR/)) + end if + if (this%gwfInterfaceModel%npf%iangle2 == 1) then + call this%cfg_dv('ANGLE2', 'NPF', SYNC_NDS, (/STG_BFR_CON_AR/)) + end if + if (this%gwfInterfaceModel%npf%iangle3 == 1) then + call this%cfg_dv('ANGLE3', 'NPF', SYNC_NDS, (/STG_BFR_CON_AR/)) + end if + if (this%gwfInterfaceModel%npf%iwetdry == 1) then + call this%cfg_dv('WETDRY', 'NPF', SYNC_NDS, (/STG_BFR_CON_AR/)) + end if + call this%cfg_dv('TOP', 'DIS', SYNC_NDS, (/STG_BFR_CON_AR/)) + call this%cfg_dv('BOT', 'DIS', SYNC_NDS, (/STG_BFR_CON_AR/)) + call this%cfg_dv('AREA', 'DIS', SYNC_NDS, (/STG_BFR_CON_AR/)) + + end subroutine cfg_dist_vars + !> @brief Set the required size of the interface grid from !< the configuration subroutine setGridExtent(this) diff --git a/src/Model/Connection/GwtGwtConnection.f90 b/src/Model/Connection/GwtGwtConnection.f90 index 0132dc9aa91..bb49ab9afb9 100644 --- a/src/Model/Connection/GwtGwtConnection.f90 +++ b/src/Model/Connection/GwtGwtConnection.f90 @@ -52,7 +52,7 @@ module GwtGwtConnectionModule contains - procedure, pass(this) :: gwtGwtConnection_ctor + procedure :: gwtGwtConnection_ctor generic, public :: construct => gwtGwtConnection_ctor procedure :: exg_ar => gwtgwtcon_ar @@ -68,13 +68,14 @@ module GwtGwtConnectionModule procedure :: exg_ot => gwtgwtcon_ot ! overriding 'protected' - procedure, pass(this) :: validateConnection + procedure :: validateConnection ! local stuff - procedure, pass(this), private :: allocate_scalars - procedure, pass(this), private :: allocate_arrays - procedure, pass(this), private :: setGridExtent - procedure, pass(this), private :: setFlowToExchange + procedure, private :: allocate_scalars + procedure, private :: allocate_arrays + procedure, private :: cfg_dist_vars + procedure, private :: setGridExtent + procedure, private :: setFlowToExchange end type GwtGwtConnectionType @@ -178,32 +179,7 @@ subroutine gwtgwtcon_df(this) this%gwtInterfaceModel%ixt3d = this%iIfaceXt3d call this%gwtInterfaceModel%model_df() - call this%addDistVar('X', '', SYNC_NODES, & - (/STG_BEFORE_AR, STG_BEFORE_AD, STG_BEFORE_CF/)) - call this%addDistVar('IBOUND', '', SYNC_NODES, (/STG_BEFORE_AR/)) - call this%addDistVar('TOP', 'DIS', SYNC_NODES, (/STG_BEFORE_AR/)) - call this%addDistVar('BOT', 'DIS', SYNC_NODES, (/STG_BEFORE_AR/)) - call this%addDistVar('AREA', 'DIS', SYNC_NODES, (/STG_BEFORE_AR/)) - if (this%gwtInterfaceModel%dsp%idiffc > 0) then - call this%addDistVar('DIFFC', 'DSP', SYNC_NODES, (/STG_BEFORE_AR/)) - end if - if (this%gwtInterfaceModel%dsp%idisp > 0) then - call this%addDistVar('ALH', 'DSP', SYNC_NODES, (/STG_BEFORE_AR/)) - call this%addDistVar('ALV', 'DSP', SYNC_NODES, (/STG_BEFORE_AR/)) - call this%addDistVar('ATH1', 'DSP', SYNC_NODES, (/STG_BEFORE_AR/)) - call this%addDistVar('ATH2', 'DSP', SYNC_NODES, (/STG_BEFORE_AR/)) - call this%addDistVar('ATV', 'DSP', SYNC_NODES, (/STG_BEFORE_AR/)) - end if - call this%addDistVar('GWFHEAD', 'FMI', SYNC_NODES, (/STG_BEFORE_AD/)) - call this%addDistVar('GWFSAT', 'FMI', SYNC_NODES, (/STG_BEFORE_AD/)) - call this%addDistVar('GWFSPDIS', 'FMI', SYNC_NODES, (/STG_BEFORE_AD/)) - call this%addDistVar('GWFFLOWJA', 'FMI', SYNC_CONNECTIONS, (/STG_BEFORE_AD/)) - call this%addDistVar('GWFFLOWJA', 'FMI', SYNC_EXCHANGES, (/STG_BEFORE_AD/), & - exg_var_name='GWFSIMVALS') - ! fill porosity from mst packages, needed for dsp - if (this%gwtModel%indsp > 0 .and. this%gwtModel%inmst > 0) then - call this%addDistVar('POROSITY', 'MST', SYNC_NODES, (/STG_AFTER_AR/)) - end if + call this%cfg_dist_vars() call this%allocate_arrays() call this%gwtInterfaceModel%allocate_fmi() @@ -220,6 +196,40 @@ subroutine gwtgwtcon_df(this) end subroutine gwtgwtcon_df + !> @brief Configure distributed variables for this interface model + !< + subroutine cfg_dist_vars(this) + class(GwtGwtConnectionType) :: this !< the connection + + call this%cfg_dv('X', '', SYNC_NDS, & + (/STG_BFR_CON_AR, STG_BFR_EXG_AD, STG_BFR_EXG_CF/)) + call this%cfg_dv('IBOUND', '', SYNC_NDS, (/STG_BFR_CON_AR/)) + call this%cfg_dv('TOP', 'DIS', SYNC_NDS, (/STG_BFR_CON_AR/)) + call this%cfg_dv('BOT', 'DIS', SYNC_NDS, (/STG_BFR_CON_AR/)) + call this%cfg_dv('AREA', 'DIS', SYNC_NDS, (/STG_BFR_CON_AR/)) + if (this%gwtInterfaceModel%dsp%idiffc > 0) then + call this%cfg_dv('DIFFC', 'DSP', SYNC_NDS, (/STG_BFR_CON_AR/)) + end if + if (this%gwtInterfaceModel%dsp%idisp > 0) then + call this%cfg_dv('ALH', 'DSP', SYNC_NDS, (/STG_BFR_CON_AR/)) + call this%cfg_dv('ALV', 'DSP', SYNC_NDS, (/STG_BFR_CON_AR/)) + call this%cfg_dv('ATH1', 'DSP', SYNC_NDS, (/STG_BFR_CON_AR/)) + call this%cfg_dv('ATH2', 'DSP', SYNC_NDS, (/STG_BFR_CON_AR/)) + call this%cfg_dv('ATV', 'DSP', SYNC_NDS, (/STG_BFR_CON_AR/)) + end if + call this%cfg_dv('GWFHEAD', 'FMI', SYNC_NDS, (/STG_BFR_EXG_AD/)) + call this%cfg_dv('GWFSAT', 'FMI', SYNC_NDS, (/STG_BFR_EXG_AD/)) + call this%cfg_dv('GWFSPDIS', 'FMI', SYNC_NDS, (/STG_BFR_EXG_AD/)) + call this%cfg_dv('GWFFLOWJA', 'FMI', SYNC_CON, (/STG_BFR_EXG_AD/)) + call this%cfg_dv('GWFFLOWJA', 'FMI', SYNC_EXG, (/STG_BFR_EXG_AD/), & + exg_var_name='GWFSIMVALS') + ! fill porosity from mst packages, needed for dsp + if (this%gwtModel%indsp > 0 .and. this%gwtModel%inmst > 0) then + call this%cfg_dv('POROSITY', 'MST', SYNC_NDS, (/STG_AFT_CON_AR/)) + end if + + end subroutine cfg_dist_vars + !> @brief Allocate array variables for this connection !< subroutine allocate_arrays(this) diff --git a/src/Model/Connection/SpatialModelConnection.f90 b/src/Model/Connection/SpatialModelConnection.f90 index 859b5f104b8..60c2a72e27b 100644 --- a/src/Model/Connection/SpatialModelConnection.f90 +++ b/src/Model/Connection/SpatialModelConnection.f90 @@ -82,7 +82,7 @@ module SpatialModelConnectionModule procedure, pass(this) :: spatialcon_setmodelptrs procedure, pass(this) :: spatialcon_connect procedure, pass(this) :: validateConnection - procedure, pass(this) :: addDistVar + procedure, pass(this) :: cfg_dv procedure, pass(this) :: createModelHalo ! private @@ -568,15 +568,17 @@ subroutine validateConnection(this) end subroutine validateConnection - subroutine addDistVar(this, var_name, subcomp_name, map_type, & - sync_stages, exg_var_name) + !> @brief Add a variable from the interface model to be + !! synchronized at the configured stages by copying from + !! the source memory in the models/exchanges that are part + !< of this interface. + subroutine cfg_dv(this, var_name, subcomp_name, map_type, & + sync_stages, exg_var_name) class(SpatialModelConnectionType) :: this !< this connection character(len=*) :: var_name !< name of variable, e.g. "K11" character(len=*) :: subcomp_name !< subcomponent, e.g. "NPF" - integer(I4B) :: map_type !< can be 0 = scalar, 1 = node based, 2 = connection based, - !! 3 = exchange based (connections crossing model boundaries) - integer(I4B), dimension(:) :: sync_stages !< when to sync, e.g. (/ STAGE_AD, STAGE_CF /) - !! which is before AD and CF + integer(I4B) :: map_type !< type of variable map + integer(I4B), dimension(:) :: sync_stages !< stages to sync character(len=*), optional :: exg_var_name !< needed for exchange variables, e.g. SIMVALS ! local type(DistVarType), pointer :: dist_var => null() @@ -595,7 +597,7 @@ subroutine addDistVar(this, var_name, subcomp_name, map_type, & obj => dist_var call this%iface_dist_vars%Add(obj) - end subroutine addDistVar + end subroutine cfg_dv !> @brief Cast to SpatialModelConnectionType !< diff --git a/src/Model/GroundWaterFlow/gwf3.f90 b/src/Model/GroundWaterFlow/gwf3.f90 index 021693c0188..78c896b3b91 100644 --- a/src/Model/GroundWaterFlow/gwf3.f90 +++ b/src/Model/GroundWaterFlow/gwf3.f90 @@ -646,7 +646,7 @@ subroutine gwf_ptc(this, kiter, neqsln, matrix, & ! -- local integer(I4B) :: iptct integer(I4B) :: n - integer(I4B) :: jrow + integer(I4B) :: jrow, jrow_loc, matrix_offset integer(I4B) :: j real(DP) :: v real(DP) :: resid @@ -675,10 +675,12 @@ subroutine gwf_ptc(this, kiter, neqsln, matrix, & diagmin = DEP20 diagmax = DZERO diagcnt = DZERO + matrix_offset = matrix%get_row_offset() do n = 1, this%dis%nodes if (this%npf%ibound(n) < 1) cycle ! jrow = n + this%moffset + jrow_loc = jrow - matrix_offset ! ! get the maximum volume of the cell (head at top of cell) v = this%dis%get_cell_volume(n, this%dis%top(n)) @@ -688,9 +690,9 @@ subroutine gwf_ptc(this, kiter, neqsln, matrix, & first_col = matrix%get_first_col_pos(jrow) last_col = matrix%get_last_col_pos(jrow) do j = first_col, last_col - resid = resid + matrix%get_value_pos(j) * x(jrow) + resid = resid + matrix%get_value_pos(j) * x(jrow_loc) end do - resid = resid - rhs(jrow) + resid = resid - rhs(jrow_loc) ! ! -- calculate the reciprocal of the pseudo-time step ! resid [L3/T] / volume [L3] = [1/T] diff --git a/src/RunControl.f90 b/src/RunControl.f90 index 9c2dc724774..fd1b9b0611b 100644 --- a/src/RunControl.f90 +++ b/src/RunControl.f90 @@ -67,11 +67,11 @@ subroutine ctrl_at_stage(this, stage) class(RunControlType) :: this integer(I4B) :: stage - if (stage == STG_INIT) then + if (stage == STG_BFR_MDL_DF) then call this%init_handler() - else if (stage == STG_BEFORE_CON_DF) then + else if (stage == STG_BFR_CON_DF) then call this%before_con_df_handler() - else if (stage == STG_AFTER_CON_DF) then + else if (stage == STG_AFT_CON_DF) then call this%after_con_df_handler() end if diff --git a/src/SimulationCreate.f90 b/src/SimulationCreate.f90 index c9c8693344f..0e7f1d209d8 100644 --- a/src/SimulationCreate.f90 +++ b/src/SimulationCreate.f90 @@ -3,8 +3,11 @@ module SimulationCreateModule use KindModule, only: DP, I4B, LGP, write_kindinfo use ConstantsModule, only: LINELENGTH, LENMODELNAME, LENBIGLINE, & DZERO, LENEXCHANGENAME, LENMEMPATH, LENPACKAGETYPE - use SimVariablesModule, only: iout, simulation_mode, & - proc_id, nr_procs, model_names, model_loc_idx + + use CharacterStringModule, only: CharacterStringType + use SimVariablesModule, only: iout, simulation_mode, proc_id, & + nr_procs, model_names, model_ranks, & + model_loc_idx use GenericUtilitiesModule, only: sim_message, write_centered use SimModule, only: store_error, count_errors, & store_error_filename, MaxErrors @@ -25,6 +28,7 @@ module SimulationCreateModule private public :: simulation_cr public :: simulation_da + public :: create_load_mask ! TODO_MJR: this should go somewhere else contains @@ -46,6 +50,7 @@ end subroutine simulation_cr !< subroutine simulation_da() ! -- modules + use MemoryManagerModule, only: mem_deallocate use MemoryManagerExtModule, only: memorylist_remove use SimVariablesModule, only: idm_context ! -- local @@ -57,6 +62,7 @@ subroutine simulation_da() ! ! -- variables deallocate (model_names) + deallocate (model_loc_idx) ! ! -- Return return @@ -208,8 +214,7 @@ end subroutine timing_create subroutine models_create() ! -- modules use MemoryHelperModule, only: create_mem_path - use MemoryManagerModule, only: mem_setptr - use CharacterStringModule, only: CharacterStringType + use MemoryManagerModule, only: mem_setptr, mem_allocate use SimVariablesModule, only: idm_context use GwfModule, only: gwf_cr use GwtModule, only: gwt_cr @@ -226,12 +231,12 @@ subroutine models_create() pointer :: mfnames !< model file names type(CharacterStringType), dimension(:), contiguous, & pointer :: mnames !< model names - integer(I4B) :: im, id_glo + integer(I4B) :: im class(NumericalModelType), pointer :: num_model character(len=LINELENGTH) :: model_type character(len=LINELENGTH) :: fname, model_name character(len=LINELENGTH) :: errmsg - integer(I4B) :: n + integer(I4B) :: n, nr_models_glob logical :: terminate = .true. ! ! -- set input memory path @@ -242,14 +247,20 @@ subroutine models_create() call mem_setptr(mfnames, 'MFNAME', input_mempath) call mem_setptr(mnames, 'MNAME', input_mempath) ! + ! -- allocate global arrays + nr_models_glob = size(mnames) + call mem_allocate(model_ranks, nr_models_glob, 'MRANKS', input_mempath) + allocate (model_names(nr_models_glob)) + allocate (model_loc_idx(nr_models_glob)) + ! + ! -- assign models to cpu cores (in serial all to rank 0) + call create_load_balance(model_ranks) + ! ! -- open model logging block write (iout, '(/1x,a)') 'READING SIMULATION MODELS' ! - ! -- initialize global and local model ids - id_glo = 0 - im = 0 - ! ! -- create models + im = 0 do n = 1, size(mtypes) ! ! -- attributes for this model @@ -260,42 +271,32 @@ subroutine models_create() call check_model_name(model_type, model_name) ! ! increment global model id - id_glo = id_glo + 1 - call ExpandArray(model_names) - call ExpandArray(model_loc_idx) - model_names(id_glo) = model_name(1:LENMODELNAME) - model_loc_idx(id_glo) = -1 + model_names(n) = model_name(1:LENMODELNAME) + model_loc_idx(n) = -1 + num_model => null() ! - if (nr_procs > 1) then - if (simulation_mode == 'PARALLEL') then - if (model_type == 'GWF6') then - ! for now we assume: model id == rank nr + 1 - if (id_glo /= proc_id + 1) then - call add_virtual_gwf_model(id_glo, model_names(id_glo), null()) - cycle - end if - else - write (errmsg, '(4x,a)') & - '****ERROR. ONLY GWF SUPPORT IN PARALLEL MODE FOR NOW' - call store_error(errmsg, terminate) - end if - end if - end if - ! - ! -- add a new (local) model - im = im + 1 - model_loc_idx(id_glo) = im - ! - ! -- create appropriate models and update modelname + ! -- add a new (local or global) model select case (model_type) case ('GWF6') - call gwf_cr(fname, id_glo, model_names(id_glo)) - num_model => GetNumericalModelFromList(basemodellist, im) - call add_virtual_gwf_model(id_glo, model_names(id_glo), num_model) + if (model_ranks(n) == proc_id) then + im = im + 1 + write (iout, '(4x,2a,i0,a)') trim(model_type), " model ", & + n, " will be created" + call gwf_cr(fname, n, model_names(n)) + num_model => GetNumericalModelFromList(basemodellist, im) + model_loc_idx(n) = im + end if + call add_virtual_gwf_model(n, model_names(n), num_model) case ('GWT6') - call gwt_cr(fname, id_glo, model_names(id_glo)) - num_model => GetNumericalModelFromList(basemodellist, im) - call add_virtual_gwt_model(id_glo, model_names(id_glo), num_model) + if (model_ranks(n) == proc_id) then + im = im + 1 + write (iout, '(4x,2a,i0,a)') trim(model_type), " model ", & + n, " will be created" + call gwt_cr(fname, n, model_names(n)) + num_model => GetNumericalModelFromList(basemodellist, im) + model_loc_idx(n) = im + end if + call add_virtual_gwt_model(n, model_names(n), num_model) case default write (errmsg, '(4x,a,a)') & '****ERROR. UNKNOWN SIMULATION MODEL: ', & @@ -324,7 +325,6 @@ subroutine exchanges_create() ! -- modules use MemoryHelperModule, only: create_mem_path use MemoryManagerModule, only: mem_setptr - use CharacterStringModule, only: CharacterStringType use SimVariablesModule, only: idm_context use GwfGwfExchangeModule, only: gwfexchange_create use GwfGwtExchangeModule, only: gwfgwt_cr @@ -349,7 +349,8 @@ subroutine exchanges_create() character(len=LENEXCHANGENAME) :: exg_name integer(I4B) :: n character(len=LINELENGTH) :: errmsg - logical :: terminate = .true. + logical(LGP) :: terminate = .true. + logical(LGP) :: both_remote, both_local ! -- formats character(len=*), parameter :: fmtmerr = "('Error in simulation control ', & &'file. Could not find model: ', a)" @@ -393,27 +394,32 @@ subroutine exchanges_create() end if ! both models on other process? then don't create it here... - if (model_loc_idx(m1_id) == -1 .and. model_loc_idx(m2_id) == -1) then - ! only add virtual - write (exg_name, '(a,i0)') 'GWF-GWF_', exg_id - call add_virtual_gwf_exchange(exg_name, exg_id, m1_id, m2_id) - cycle + both_remote = (model_loc_idx(m1_id) == -1 .and. & + model_loc_idx(m2_id) == -1) + both_local = (model_loc_idx(m1_id) > 0 .and. & + model_loc_idx(m2_id) > 0) + if (.not. both_remote) then + write (iout, '(4x,a,a,i0,a,i0,a,i0)') trim(exgtype), ' exchange ', & + exg_id, ' will be created to connect model ', m1_id, & + ' with model ', m2_id end if - write (iout, '(4x,a,a,i0,a,i0,a,i0)') trim(exgtype), ' exchange ', & - exg_id, ' will be created to connect model ', m1_id, & - ' with model ', m2_id - select case (exgtype) case ('GWF6-GWF6') write (exg_name, '(a,i0)') 'GWF-GWF_', exg_id - call gwfexchange_create(fname, exg_name, exg_id, m1_id, m2_id) + if (.not. both_remote) then + call gwfexchange_create(fname, exg_name, exg_id, m1_id, m2_id) + end if call add_virtual_gwf_exchange(exg_name, exg_id, m1_id, m2_id) case ('GWF6-GWT6') - call gwfgwt_cr(fname, exg_id, m1_id, m2_id) + if (both_local) then + call gwfgwt_cr(fname, exg_id, m1_id, m2_id) + end if case ('GWT6-GWT6') write (exg_name, '(a,i0)') 'GWT-GWT_', exg_id - call gwtexchange_create(fname, exg_name, exg_id, m1_id, m2_id) + if (.not. both_remote) then + call gwtexchange_create(fname, exg_name, exg_id, m1_id, m2_id) + end if call add_virtual_gwt_exchange(exg_name, exg_id, m1_id, m2_id) case default write (errmsg, '(4x,a,a)') & @@ -473,7 +479,6 @@ end subroutine solution_group_check subroutine solution_groups_create() ! -- modules use MemoryManagerModule, only: mem_setptr - use CharacterStringModule, only: CharacterStringType use MemoryHelperModule, only: create_mem_path use SimVariablesModule, only: idm_context, simulation_mode use SolutionGroupModule, only: SolutionGroupType, & @@ -759,4 +764,141 @@ subroutine check_model_name(mtype, mname) return end subroutine check_model_name + !> @brief Create a load mask to determine which models + !! should be loaded by idm on this process. This is in + !! sync with models create. The mask array should be + !! pre-allocated with size equal to the global number + !! of models. It is returned as (1, 1, 0, 0, ... 0) + !! with each entry being a load mask for the model + !! at the corresponding location in the 'MNAME' array + !< of the IDM. + subroutine create_load_mask(mask_array) + use SimVariablesModule, only: proc_id + integer(I4B), dimension(:) :: mask_array + ! local + integer(I4B) :: i + + call create_load_balance(mask_array) + do i = 1, size(mask_array) + if (mask_array(i) == proc_id) then + mask_array(i) = 1 + else + mask_array(i) = 0 + end if + end do + + end subroutine create_load_mask + + !> @brief Distribute the models over the available + !! processes in a parallel run. Expects an array sized + !< to the number of models in the global simulation + subroutine create_load_balance(mranks) + use SimVariablesModule, only: idm_context + use MemoryHelperModule, only: create_mem_path + use MemoryManagerModule, only: mem_setptr + integer(I4B), dimension(:) :: mranks + ! local + integer(I4B) :: im, imm, ie, ip, cnt + integer(I4B) :: nr_models, nr_gwf_models, nr_gwt_models + integer(I4B) :: nr_exchanges + integer(I4B) :: min_per_proc, nr_left + integer(I4B) :: rank + integer(I4B), dimension(:), allocatable :: nr_models_proc + character(len=:), allocatable :: model_type_str + character(len=LINELENGTH) :: errmsg + character(len=LENMEMPATH) :: input_mempath + type(CharacterStringType), dimension(:), contiguous, & + pointer :: mtypes !< model types + type(CharacterStringType), dimension(:), contiguous, & + pointer :: mnames !< model names + type(CharacterStringType), dimension(:), contiguous, & + pointer :: etypes !< exg types + type(CharacterStringType), dimension(:), contiguous, & + pointer :: emnames_a !< model a names + type(CharacterStringType), dimension(:), contiguous, & + pointer :: emnames_b !< model b names + + mranks = 0 + if (simulation_mode /= "PARALLEL") return + + ! load IDM data + input_mempath = create_mem_path('SIM', 'NAM', idm_context) + call mem_setptr(mtypes, 'MTYPE', input_mempath) + call mem_setptr(mnames, 'MNAME', input_mempath) + call mem_setptr(etypes, 'EXGTYPE', input_mempath) + call mem_setptr(emnames_a, 'EXGMNAMEA', input_mempath) + call mem_setptr(emnames_b, 'EXGMNAMEB', input_mempath) + + ! count flow models + nr_models = size(mnames) + nr_gwf_models = 0 + nr_gwt_models = 0 + do im = 1, nr_models + if (mtypes(im) == "GWF6") then + nr_gwf_models = nr_gwf_models + 1 + else if (mtypes(im) == "GWT6") then + nr_gwt_models = nr_gwt_models + 1 + else + model_type_str = mtypes(im) + write (errmsg, *) "Error. Model type ", model_type_str, & + " not supported in parallel mode" + call store_error(errmsg, terminate=.true.) + end if + end do + + ! calculate nr of flow models for each rank + allocate (nr_models_proc(nr_procs)) + min_per_proc = nr_gwf_models / nr_procs + nr_left = nr_gwf_models - nr_procs * min_per_proc + cnt = 1 + do ip = 1, nr_procs + rank = ip - 1 + nr_models_proc(ip) = min_per_proc + if (rank < nr_left) then + nr_models_proc(ip) = nr_models_proc(ip) + 1 + end if + end do + + ! assign ranks for flow models + rank = 0 + do im = 1, nr_models + if (mtypes(im) == "GWF6") then + if (nr_models_proc(rank + 1) == 0) then + rank = rank + 1 + end if + mranks(im) = rank + nr_models_proc(rank + 1) = nr_models_proc(rank + 1) - 1 + end if + end do + + ! match transport to flow + nr_exchanges = size(etypes) + + do im = 1, nr_models + if (.not. mtypes(im) == "GWT6") cycle + + ! find match + do ie = 1, nr_exchanges + if (etypes(ie) == "GWF6-GWT6" .and. mnames(im) == emnames_b(ie)) then + ! this is the exchange, now find the flow model's rank + rank = 0 + do imm = 1, nr_models + if (mnames(imm) == emnames_a(ie)) then + rank = mranks(imm) + exit + end if + end do + + ! we have our rank, assign and go to next transport model + mranks(im) = rank + exit + end if + end do + end do + + ! cleanup + deallocate (nr_models_proc) + + end subroutine create_load_balance + end module SimulationCreateModule diff --git a/src/Solution/LinearMethods/ImsLinearSolver.f90 b/src/Solution/LinearMethods/ImsLinearSolver.f90 index 10a27ca1f6a..a5305ee36e2 100644 --- a/src/Solution/LinearMethods/ImsLinearSolver.f90 +++ b/src/Solution/LinearMethods/ImsLinearSolver.f90 @@ -1,5 +1,5 @@ module ImsLinearSolverModule - use KindModule, only: I4B + use KindModule, only: I4B, DP use LinearSolverBaseModule use MatrixBaseModule use VectorBaseModule @@ -14,6 +14,7 @@ module ImsLinearSolverModule procedure :: initialize => ims_initialize procedure :: solve => ims_solve procedure :: get_result => ims_get_result + procedure :: get_l2_norm => ims_get_l2_norm procedure :: destroy => ims_destroy procedure :: create_matrix => ims_create_matrix @@ -47,6 +48,15 @@ subroutine ims_get_result(this) class(ImsLinearSolverType) :: this end subroutine ims_get_result + function ims_get_l2_norm(this, x, rhs, active) result(l2norm) + class(ImsLinearSolverType) :: this + class(VectorBaseType), pointer :: x + class(VectorBaseType), pointer :: rhs + integer(I4B), dimension(:), pointer, contiguous :: active + real(DP) :: l2norm + l2norm = 0.0_DP + end function ims_get_l2_norm + subroutine ims_destroy(this) class(ImsLinearSolverType) :: this end subroutine ims_destroy diff --git a/src/Solution/LinearSolverBase.f90 b/src/Solution/LinearSolverBase.f90 index 3bb8566d9c3..3dd2fa00ba6 100644 --- a/src/Solution/LinearSolverBase.f90 +++ b/src/Solution/LinearSolverBase.f90 @@ -18,6 +18,7 @@ module LinearSolverBaseModule procedure(initialize_if), deferred :: initialize procedure(solve_if), deferred :: solve procedure(get_result_if), deferred :: get_result + procedure(get_l2_norm_if), deferred :: get_l2_norm procedure(destroy_if), deferred :: destroy procedure(create_matrix_if), deferred :: create_matrix @@ -40,6 +41,14 @@ subroutine get_result_if(this) import LinearSolverBaseType class(LinearSolverBaseType) :: this end subroutine + function get_l2_norm_if(this, x, rhs, active) result(l2norm) + import LinearSolverBaseType, VectorBaseType, I4B, DP + class(LinearSolverBaseType) :: this + class(VectorBaseType), pointer :: x + class(VectorBaseType), pointer :: rhs + integer(I4B), dimension(:), pointer, contiguous :: active + real(DP) :: l2norm + end function get_l2_norm_if subroutine destroy_if(this) import LinearSolverBaseType class(LinearSolverBaseType) :: this diff --git a/src/Solution/NumericalSolution.f90 b/src/Solution/NumericalSolution.f90 index e9b518fccba..5c080aadda4 100644 --- a/src/Solution/NumericalSolution.f90 +++ b/src/Solution/NumericalSolution.f90 @@ -161,6 +161,7 @@ module NumericalSolutionModule ! 'protected' (this can be overridden) procedure :: sln_has_converged + procedure :: sln_l2norm ! private procedure, private :: sln_connect @@ -169,7 +170,6 @@ module NumericalSolutionModule procedure, private :: sln_setouter procedure, private :: sln_backtracking procedure, private :: sln_backtracking_xupdate - procedure, private :: sln_l2norm procedure, private :: sln_maxval procedure, private :: sln_calcdx procedure, private :: sln_underrelax @@ -468,7 +468,7 @@ subroutine sln_df(this) else this%solver_mode = 'IMS' end if - ! + ! -- create linear system matrix and compatible vectors this%linear_solver => create_linear_solver(this%solver_mode) this%system_matrix => this%linear_solver%create_matrix() this%vec_x => this%system_matrix%create_vector(this%neq, 'X', this%memoryPath) @@ -1472,7 +1472,7 @@ subroutine prepareSolve(this) class(NumericalModelType), pointer :: mp => null() ! synchronize for AD - call this%synchronize(STG_BEFORE_AD, this%synchronize_ctx) + call this%synchronize(STG_BFR_EXG_AD, this%synchronize_ctx) ! -- Exchange advance do ic = 1, this%exchangelist%Count() @@ -1967,7 +1967,7 @@ subroutine sln_buildsystem(this, kiter, inewton) call this%sln_reset() ! synchronize for CF - call this%synchronize(STG_BEFORE_CF, this%synchronize_ctx) + call this%synchronize(STG_BFR_EXG_CF, this%synchronize_ctx) ! ! -- Calculate the matrix terms for each exchange @@ -1983,7 +1983,7 @@ subroutine sln_buildsystem(this, kiter, inewton) end do ! synchronize for FC - call this%synchronize(STG_BEFORE_FC, this%synchronize_ctx) + call this%synchronize(STG_BFR_EXG_FC, this%synchronize_ctx) ! ! -- Add exchange coefficients to the solution @@ -2350,7 +2350,7 @@ subroutine sln_connect(this) end do ! ! -- synchronize before AC - call this%synchronize(STG_BEFORE_AC, this%synchronize_ctx) + call this%synchronize(STG_BFR_EXG_AC, this%synchronize_ctx) ! ! -- Add the cross terms to sparse do ic = 1, this%exchangelist%Count() @@ -2508,8 +2508,7 @@ subroutine sln_ls(this, kiter, kstp, kper, in_iter, iptc, ptcf) ! -- calculate or modify pseudo transient continuation terms and add ! to amat diagonals if (iptct /= 0) then - call this%sln_l2norm(this%neq, this%system_matrix, this%active, & - this%rhs, this%x, l2norm) + call this%sln_l2norm(l2norm) ! -- confirm that the l2norm exceeds previous l2norm ! if not, there is no need to add ptc terms if (kiter == 1) then @@ -2731,13 +2730,11 @@ subroutine sln_backtracking(this, mp, cp, kiter) ! ! -- calculate initial l2 norm if (kiter == 1) then - call this%sln_l2norm(this%neq, this%system_matrix, this%active, & - this%rhs, this%x, this%res_prev) + call this%sln_l2norm(this%res_prev) resin = this%res_prev ibflag = 0 else - call this%sln_l2norm(this%neq, this%system_matrix, this%active, & - this%rhs, this%x, this%res_new) + call this%sln_l2norm(this%res_new) resin = this%res_new end if ibtcnt = 0 @@ -2763,8 +2760,7 @@ subroutine sln_backtracking(this, mp, cp, kiter) ! ! -- calculate updated l2norm - call this%sln_l2norm(this%neq, this%system_matrix, this%active, & - this%rhs, this%x, this%res_new) + call this%sln_l2norm(this%res_new) ! ! -- evaluate if back tracking can be terminated if (nb == this%numtrack) then @@ -2853,20 +2849,22 @@ subroutine sln_backtracking_xupdate(this, btflag) return end subroutine sln_backtracking_xupdate - !> @ brief Calculate the solution L-2 norm + !> @ brief Calculate the solution L-2 norm for all + !! active cells using !! - !! Calculate the solution L-2 norm using the coefficient matrix, the - !! right-hand side vector, and the current dependent-variable vector. + !! A = the linear system matrix + !! x = the dependendent variable vector + !! b = the right-hand side vector + !! m = the mask array for inactive cells, + !! where inactive == 0, active > 0 !! + !! r = A * x - b + !! M = diag(if m_i > 0: 1 else: 0) + !! L2norm = ||M * r||_2 with !< - subroutine sln_l2norm(this, neq, matrix_sln, active, rhs, x, l2norm) + subroutine sln_l2norm(this, l2norm) ! -- dummy variables class(NumericalSolutionType), intent(inout) :: this !< NumericalSolutionType instance - integer(I4B), intent(in) :: neq !< number of equations - class(MatrixBaseType), pointer :: matrix_sln !< coefficient matrix for solution - integer(I4B), dimension(neq), intent(in) :: active !< active cell flag vector (1) inactive (0) - real(DP), dimension(neq), intent(in) :: rhs !< right-hand side vector - real(DP), dimension(neq), intent(in) :: x !< dependent-variable vector real(DP), intent(inout) :: l2norm !< calculated L-2 norm ! -- local variables integer(I4B) :: n @@ -2879,17 +2877,18 @@ subroutine sln_l2norm(this, neq, matrix_sln, active, rhs, x, l2norm) residual = DZERO ! ! -- calculate the L-2 norm - do n = 1, neq - if (active(n) > 0) then + do n = 1, this%neq + if (this%active(n) > 0) then rowsum = DZERO - icol_s = matrix_sln%get_first_col_pos(n) - icol_e = matrix_sln%get_last_col_pos(n) + icol_s = this%system_matrix%get_first_col_pos(n) + icol_e = this%system_matrix%get_last_col_pos(n) do ipos = icol_s, icol_e - jcol = matrix_sln%get_column(ipos) - rowsum = rowsum + (matrix_sln%get_value_pos(ipos) * x(jcol)) + jcol = this%system_matrix%get_column(ipos) + rowsum = rowsum + & + (this%system_matrix%get_value_pos(ipos) * this%x(jcol)) end do ! compute mean square residual from q of each node - residual = residual + (rowsum - rhs(n))**2 + residual = residual + (rowsum - this%rhs(n))**2 end if end do ! -- The L-2 norm is the square root of the sum of the square of the residuals diff --git a/src/Solution/PETSc/PetscConvergence.F90 b/src/Solution/PETSc/PetscConvergence.F90 index 36241301739..a342d4d6bd7 100644 --- a/src/Solution/PETSc/PetscConvergence.F90 +++ b/src/Solution/PETSc/PetscConvergence.F90 @@ -62,7 +62,7 @@ subroutine petsc_check_convergence(ksp, n, rnorm, flag, ctx_id, ierr) PetscInt :: ctx_id !< index into the static context list PetscErrorCode :: ierr !< error ! local - PetscScalar :: alpha + PetscScalar, parameter :: min_one = -1.0 real(DP) :: norm Vec :: x class(PetscContextType), pointer :: petsc_context @@ -86,8 +86,7 @@ subroutine petsc_check_convergence(ksp, n, rnorm, flag, ctx_id, ierr) return end if - alpha = -1.0 - call VecWAXPY(petsc_context%delta_x, alpha, x, petsc_context%x_old, ierr) + call VecWAXPY(petsc_context%delta_x, min_one, x, petsc_context%x_old, ierr) CHKERRQ(ierr) call VecNorm(petsc_context%delta_x, NORM_INFINITY, norm, ierr) @@ -98,7 +97,7 @@ subroutine petsc_check_convergence(ksp, n, rnorm, flag, ctx_id, ierr) if (norm < petsc_context%dvclose) then if (proc_id == 0) then - write (*, *) "converged: ", norm + write (*, *) "converged: ", norm, " iter: ", n end if flag = KSP_CONVERGED_HAPPY_BREAKDOWN ! Converged else diff --git a/src/Solution/PETSc/PetscSolver.F90 b/src/Solution/PETSc/PetscSolver.F90 index 4e518720909..d7c0306ae68 100644 --- a/src/Solution/PETSc/PetscSolver.F90 +++ b/src/Solution/PETSc/PetscSolver.F90 @@ -18,6 +18,7 @@ module PetscSolverModule KSP :: ksp_petsc class(PetscMatrixType), pointer :: matrix Mat, pointer :: mat_petsc + Vec, pointer :: vec_residual integer(I4B) :: lin_accel_type real(DP) :: dvclose @@ -27,6 +28,7 @@ module PetscSolverModule procedure :: initialize => petsc_initialize procedure :: solve => petsc_solve procedure :: get_result => petsc_get_result + procedure :: get_l2_norm => petsc_get_l2_norm procedure :: destroy => petsc_destroy procedure :: create_matrix => petsc_create_matrix @@ -58,6 +60,8 @@ end function create_petsc_solver subroutine petsc_initialize(this, matrix) class(PetscSolverType) :: this !< This solver instance class(MatrixBaseType), pointer :: matrix !< The solution matrix as KSP operator + ! local + PetscErrorCode :: ierr this%mat_petsc => null() select type (pm => matrix) @@ -66,6 +70,10 @@ subroutine petsc_initialize(this, matrix) this%mat_petsc => pm%mat end select + allocate (this%vec_residual) + call MatCreateVecs(this%mat_petsc, this%vec_residual, PETSC_NULL_VEC, ierr) + CHKERRQ(ierr) + ! get options from PETSc database file call this%get_options() @@ -181,6 +189,59 @@ subroutine petsc_get_result(this) class(PetscSolverType) :: this end subroutine petsc_get_result + !> @brief Gets the global L2 norm for active cells using + !< the petsc linear system and solution's active cells array + function petsc_get_l2_norm(this, x, rhs, active) result(l2norm) + use ConstantsModule, only: DONE + class(PetscSolverType) :: this + class(VectorBaseType), pointer :: x + class(VectorBaseType), pointer :: rhs + integer(I4B), dimension(:), pointer, contiguous :: active + real(DP) :: l2norm + ! local + integer(I4B) :: i + class(PetscVectorType), pointer :: x_petsc, rhs_petsc + PetscScalar, parameter :: min_one = -1.0 + PetscScalar, parameter :: zero = 0.0 + PetscScalar :: norm + PetscErrorCode :: ierr + + x_petsc => null() + select type (x) + class is (PetscVectorType) + x_petsc => x + end select + rhs_petsc => null() + select type (rhs) + class is (PetscVectorType) + rhs_petsc => rhs + end select + + ! set up vector with residual elements + call MatMult(this%mat_petsc, x_petsc%vec_impl, this%vec_residual, ierr) ! r = A * x + CHKERRQ(ierr) + call VecAXPY(this%vec_residual, min_one, rhs_petsc%vec_impl, ierr) ! r = r - rhs + CHKERRQ(ierr) + + ! zero out inactive cell contributions + do i = 1, size(active) + if (active(i) == 0) then + call VecSetValueLocal(this%vec_residual, i - 1, zero, INSERT_VALUES, ierr) ! r_i = 0 if active_i == 0 + end if + end do + call VecAssemblyBegin(this%vec_residual, ierr) + CHKERRQ(ierr) + call VecAssemblyEnd(this%vec_residual, ierr) + CHKERRQ(ierr) + + ! collective norm + call VecNorm(this%vec_residual, NORM_2, norm, ierr) ! 2-norm + CHKERRQ(ierr) + + l2norm = norm + + end function petsc_get_l2_norm + subroutine petsc_destroy(this) class(PetscSolverType) :: this ! local @@ -189,6 +250,11 @@ subroutine petsc_destroy(this) call KSPDestroy(this%ksp_petsc, ierr) CHKERRQ(ierr) + ! delete work vector + call VecDestroy(this%vec_residual, ierr) + CHKERRQ(ierr) + deallocate (this%vec_residual) + ! delete context call VecDestroy(this%petsc_ctx%delta_x, ierr) CHKERRQ(ierr) diff --git a/src/Solution/ParallelSolution.f90 b/src/Solution/ParallelSolution.f90 index a173e528ab3..2d1da38f41c 100644 --- a/src/Solution/ParallelSolution.f90 +++ b/src/Solution/ParallelSolution.f90 @@ -12,6 +12,7 @@ module ParallelSolutionModule contains ! override procedure :: sln_has_converged => par_has_converged + procedure :: sln_l2norm => par_l2norm end type ParallelSolutionType contains @@ -20,11 +21,12 @@ module ParallelSolutionModule !! variable change is reduced over MPI with all other processes !< that are running this parallel numerical solution. function par_has_converged(this, max_dvc) result(has_converged) - class(ParallelSolutionType) :: this !< ParallelSolutionType instance + class(ParallelSolutionType) :: this !< parallel solution real(DP) :: max_dvc !< the LOCAL maximum dependent variable change logical(LGP) :: has_converged !< True, when GLOBALLY converged ! local real(DP) :: global_max_dvc + real(DP) :: abs_max_dvc integer :: ierr type(MpiWorldType), pointer :: mpi_world @@ -32,7 +34,8 @@ function par_has_converged(this, max_dvc) result(has_converged) has_converged = .false. global_max_dvc = huge(0.0) - call MPI_Allreduce(max_dvc, global_max_dvc, 1, MPI_DOUBLE_PRECISION, & + abs_max_dvc = abs(max_dvc) + call MPI_Allreduce(abs_max_dvc, global_max_dvc, 1, MPI_DOUBLE_PRECISION, & MPI_MAX, mpi_world%comm, ierr) if (global_max_dvc <= this%dvclose) then has_converged = .true. @@ -40,4 +43,13 @@ function par_has_converged(this, max_dvc) result(has_converged) end function par_has_converged + subroutine par_l2norm(this, l2norm) + class(ParallelSolutionType), intent(inout) :: this !< parallel solution + real(DP), intent(inout) :: l2norm !< calculated L-2 norm + + l2norm = this%linear_solver%get_l2_norm(this%vec_x, this%vec_rhs, & + this%active) + + end subroutine par_l2norm + end module ParallelSolutionModule diff --git a/src/Utilities/CharString.f90 b/src/Utilities/CharString.f90 index c9de347e963..90cefa46cf6 100644 --- a/src/Utilities/CharString.f90 +++ b/src/Utilities/CharString.f90 @@ -28,10 +28,13 @@ module CharacterStringModule procedure, pass(rhs) :: assign_from_charstring procedure, pass(rhs) :: character_eq_charstring procedure, pass(lhs) :: charstring_eq_character + procedure :: charstring_eq_charstring procedure :: write_unformatted procedure :: strlen generic :: assignment(=) => assign_to_charstring, assign_from_charstring - generic :: operator(==) => character_eq_charstring, charstring_eq_character + generic :: operator(==) => character_eq_charstring, & + charstring_eq_character, & + charstring_eq_charstring ! not supported by gfortran 5 and 6 ! disable for now ! generic :: write (unformatted) => write_unformatted @@ -90,6 +93,18 @@ elemental function charstring_eq_character(lhs, rhs) result(equals) end if end function charstring_eq_character + elemental function charstring_eq_charstring(this, rhs) result(equals) + class(CharacterStringType), intent(in) :: this + class(CharacterStringType), intent(in) :: rhs + logical :: equals + + equals = .false. + if (allocated(this%charstring)) then + equals = (rhs == this%charstring) + end if + + end function charstring_eq_charstring + subroutine write_unformatted(this, unit, iostat, iomsg) class(CharacterStringType), intent(in) :: this integer, intent(in) :: unit diff --git a/src/Utilities/SimStages.f90 b/src/Utilities/SimStages.f90 index 340c5d204ea..8094b71b5dc 100644 --- a/src/Utilities/SimStages.f90 +++ b/src/Utilities/SimStages.f90 @@ -6,19 +6,19 @@ module SimStagesModule public :: STG_TO_STR ! stages for synchronization - integer(I4B), public, parameter :: STG_NEVER = 0 - integer(I4B), public, parameter :: STG_INIT = 1 - integer(I4B), public, parameter :: STG_AFTER_MDL_DF = 2 - integer(I4B), public, parameter :: STG_AFTER_EXG_DF = 3 - integer(I4B), public, parameter :: STG_AFTER_CON_CR = 4 - integer(I4B), public, parameter :: STG_BEFORE_CON_DF = 5 - integer(I4B), public, parameter :: STG_AFTER_CON_DF = 6 - integer(I4B), public, parameter :: STG_BEFORE_AC = 7 - integer(I4B), public, parameter :: STG_BEFORE_AR = 8 - integer(I4B), public, parameter :: STG_AFTER_AR = 9 - integer(I4B), public, parameter :: STG_BEFORE_AD = 10 - integer(I4B), public, parameter :: STG_BEFORE_CF = 11 - integer(I4B), public, parameter :: STG_BEFORE_FC = 12 + integer(I4B), public, parameter :: STG_NEVER = 0 !< never + integer(I4B), public, parameter :: STG_BFR_MDL_DF = 1 !< before model define + integer(I4B), public, parameter :: STG_AFT_MDL_DF = 2 !< after model define + integer(I4B), public, parameter :: STG_AFT_EXG_DF = 3 !< after exchange define + integer(I4B), public, parameter :: STG_AFT_CON_CR = 4 !< after connection create + integer(I4B), public, parameter :: STG_BFR_CON_DF = 5 !< before connection define + integer(I4B), public, parameter :: STG_AFT_CON_DF = 6 !< after connection define + integer(I4B), public, parameter :: STG_BFR_EXG_AC = 7 !< before exchange add connections (per solution) + integer(I4B), public, parameter :: STG_BFR_CON_AR = 8 !< before connection allocate read + integer(I4B), public, parameter :: STG_AFT_CON_AR = 9 !< afterr connection allocate read + integer(I4B), public, parameter :: STG_BFR_EXG_AD = 10 !< before exchange advance (per solution) + integer(I4B), public, parameter :: STG_BFR_EXG_CF = 11 !< before exchange calculate (per solution) + integer(I4B), public, parameter :: STG_BFR_EXG_FC = 12 !< before exchange formulate (per solution) contains @@ -29,18 +29,18 @@ function STG_TO_STR(stage) result(stg_str) character(len=24) :: stg_str if (stage == STG_NEVER) then; stg_str = "STG_NEVER" - else if (stage == STG_INIT) then; stg_str = "STG_INIT" - else if (stage == STG_AFTER_MDL_DF) then; stg_str = "STG_AFTER_MDL_DF" - else if (stage == STG_AFTER_EXG_DF) then; stg_str = "STG_AFTER_EXG_DF" - else if (stage == STG_AFTER_CON_CR) then; stg_str = "STG_AFTER_CON_CR" - else if (stage == STG_BEFORE_CON_DF) then; stg_str = "STG_BEFORE_CON_DF" - else if (stage == STG_AFTER_CON_DF) then; stg_str = "STG_AFTER_CON_DF" - else if (stage == STG_BEFORE_AC) then; stg_str = "STG_BEFORE_AC" - else if (stage == STG_BEFORE_AR) then; stg_str = "STG_BEFORE_AR" - else if (stage == STG_AFTER_AR) then; stg_str = "STG_AFTER_AR" - else if (stage == STG_BEFORE_AD) then; stg_str = "STG_BEFORE_AD" - else if (stage == STG_BEFORE_CF) then; stg_str = "STG_BEFORE_CF" - else if (stage == STG_BEFORE_FC) then; stg_str = "STG_BEFORE_FC" + else if (stage == STG_BFR_MDL_DF) then; stg_str = "STG_BFR_MDL_DF" + else if (stage == STG_AFT_MDL_DF) then; stg_str = "STG_AFT_MDL_DF" + else if (stage == STG_AFT_EXG_DF) then; stg_str = "STG_AFT_EXG_DF" + else if (stage == STG_AFT_CON_CR) then; stg_str = "STG_AFT_CON_CR" + else if (stage == STG_BFR_CON_DF) then; stg_str = "STG_BFR_CON_DF" + else if (stage == STG_AFT_CON_DF) then; stg_str = "STG_AFT_CON_DF" + else if (stage == STG_BFR_EXG_AC) then; stg_str = "STG_BFR_EXG_AC" + else if (stage == STG_BFR_CON_AR) then; stg_str = "STG_BFR_CON_AR" + else if (stage == STG_AFT_CON_AR) then; stg_str = "STG_AFT_CON_AR" + else if (stage == STG_BFR_EXG_AD) then; stg_str = "STG_BFR_EXG_AD" + else if (stage == STG_BFR_EXG_CF) then; stg_str = "STG_BFR_EXG_CF" + else if (stage == STG_BFR_EXG_FC) then; stg_str = "STG_BFR_EXG_FC" else; stg_str = "UNKNOWN" end if diff --git a/src/Utilities/SimVariables.f90 b/src/Utilities/SimVariables.f90 index 70ce490c7b5..87c6c5824ef 100644 --- a/src/Utilities/SimVariables.f90 +++ b/src/Utilities/SimVariables.f90 @@ -22,6 +22,7 @@ module SimVariablesModule integer(I4B) :: proc_id = 0 integer(I4B) :: nr_procs = 1 character(len=LENMODELNAME), dimension(:), allocatable :: model_names !< all model names in the (global) simulation + integer(I4B), dimension(:), pointer, contiguous :: model_ranks !< all model processor ids (ranks) in the (global) simulation integer(I4B), dimension(:), allocatable :: model_loc_idx !< equals the local index into the basemodel list (-1 when not available) character(len=MAXCHARLEN) :: errmsg !< error message string diff --git a/src/mf6core.f90 b/src/mf6core.f90 index d9e84346244..f972bfdf63d 100644 --- a/src/mf6core.f90 +++ b/src/mf6core.f90 @@ -262,15 +262,16 @@ end subroutine create_lstfile subroutine static_input_load() ! -- modules use ConstantsModule, only: LENMEMPATH - use SimVariablesModule, only: simulation_mode, proc_id, iout, nr_procs + use SimVariablesModule, only: iout use IdmSimulationModule, only: simnam_load, load_models use MemoryHelperModule, only: create_mem_path - use MemoryManagerModule, only: mem_setptr + use MemoryManagerModule, only: mem_setptr, mem_allocate use SimVariablesModule, only: idm_context + use SimulationCreateModule, only: create_load_mask ! -- dummy ! -- locals character(len=LENMEMPATH) :: input_mempath - integer(I4B), dimension(:), allocatable :: model_loadmask + integer(I4B), dimension(:), pointer, contiguous :: model_loadmask integer(I4B), pointer :: nummodels => null() ! ! -- load simnam input context @@ -282,20 +283,11 @@ subroutine static_input_load() allocate (model_loadmask(nummodels)) ! ! -- initialize mask - model_loadmask = 0 - ! - ! -- set mask - if (simulation_mode == 'PARALLEL' .and. nr_procs > 1) then - ! TODO under development - model_loadmask(proc_id + 1) = 1 - else - model_loadmask = 1 - end if + call create_load_mask(model_loadmask) ! ! -- load selected models call load_models(model_loadmask, iout) ! - ! -- deallocate mask deallocate (model_loadmask) ! ! -- return @@ -320,7 +312,7 @@ subroutine simulation_df() class(SpatialModelConnectionType), pointer :: mc => null() ! -- init virtual data environment - call run_ctrl%at_stage(STG_INIT) + call run_ctrl%at_stage(STG_BFR_MDL_DF) ! -- Define each model do im = 1, basemodellist%Count() @@ -329,7 +321,7 @@ subroutine simulation_df() end do ! ! -- synchronize - call run_ctrl%at_stage(STG_AFTER_MDL_DF) + call run_ctrl%at_stage(STG_AFT_MDL_DF) ! ! -- Define each exchange do ic = 1, baseexchangelist%Count() @@ -338,17 +330,17 @@ subroutine simulation_df() end do ! ! -- synchronize - call run_ctrl%at_stage(STG_AFTER_EXG_DF) + call run_ctrl%at_stage(STG_AFT_EXG_DF) ! ! -- when needed, this is were the interface models are ! created and added to the numerical solutions call connections_cr() ! ! -- synchronize - call run_ctrl%at_stage(STG_AFTER_CON_CR) + call run_ctrl%at_stage(STG_AFT_CON_CR) ! ! -- synchronize TODO_MJR: this could be merged with the above, in general - call run_ctrl%at_stage(STG_BEFORE_CON_DF) + call run_ctrl%at_stage(STG_BFR_CON_DF) ! ! -- Define each connection do ic = 1, baseconnectionlist%Count() @@ -357,7 +349,7 @@ subroutine simulation_df() end do ! ! -- synchronize - call run_ctrl%at_stage(STG_AFTER_CON_DF) + call run_ctrl%at_stage(STG_AFT_CON_DF) ! ! -- Define each solution do is = 1, basesolutionlist%Count() @@ -400,7 +392,7 @@ subroutine simulation_ar() end do ! ! -- Synchronize - call run_ctrl%at_stage(STG_BEFORE_AR) + call run_ctrl%at_stage(STG_BFR_CON_AR) ! ! -- Allocate and read all model connections do ic = 1, baseconnectionlist%Count() @@ -409,7 +401,7 @@ subroutine simulation_ar() end do ! ! -- Synchronize - call run_ctrl%at_stage(STG_AFTER_AR) + call run_ctrl%at_stage(STG_AFT_CON_AR) ! ! -- Allocate and read each solution do is = 1, basesolutionlist%Count() From 134c05f4075a0ad97f89145a1b5377475a9f50e1 Mon Sep 17 00:00:00 2001 From: Eric Morway Date: Tue, 11 Apr 2023 10:25:25 -0700 Subject: [PATCH 066/123] docs(gwf-sfr.dfn): cross_section keyword is a part of sfrsetting (#1198) --- doc/mf6io/mf6ivar/dfn/gwf-sfr.dfn | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/mf6io/mf6ivar/dfn/gwf-sfr.dfn b/doc/mf6io/mf6ivar/dfn/gwf-sfr.dfn index 46f648505d9..1aa6a218a4c 100644 --- a/doc/mf6io/mf6ivar/dfn/gwf-sfr.dfn +++ b/doc/mf6io/mf6ivar/dfn/gwf-sfr.dfn @@ -816,7 +816,7 @@ name cross_sectionrecord type record cross_section tab6 filein tab6_filename shape tagged -in_record false +in_record true reader urword longname description From 3b2fbd50f93e0957b9ffaf3d05abdaea4bf83719 Mon Sep 17 00:00:00 2001 From: mjr-deltares <45555666+mjr-deltares@users.noreply.github.com> Date: Wed, 12 Apr 2023 19:46:11 +0200 Subject: [PATCH 067/123] Add (reuse) two parallel autotests (#1201) --- autotest/test_gwf_ifmod_rewet.py | 3 +- autotest/test_gwf_ifmod_xt3d02.py | 3 +- autotest/test_par_gwf_rewet.py | 55 +++++++++++++++++++++++++ autotest/test_par_gwf_xt3d02.py | 55 +++++++++++++++++++++++++ src/Solution/PETSc/PetscConvergence.F90 | 4 -- 5 files changed, 114 insertions(+), 6 deletions(-) create mode 100644 autotest/test_par_gwf_rewet.py create mode 100644 autotest/test_par_gwf_xt3d02.py diff --git a/autotest/test_gwf_ifmod_rewet.py b/autotest/test_gwf_ifmod_rewet.py index 8a58945edd1..61c0d53a582 100644 --- a/autotest/test_gwf_ifmod_rewet.py +++ b/autotest/test_gwf_ifmod_rewet.py @@ -40,6 +40,7 @@ # solver criterion hclose_check = 1e-9 +max_inner_it = 300 nper = 2 # model spatial discretization @@ -121,7 +122,7 @@ def get_model(idx, dir): tdis_rc.append((1.0, 1, 1)) # solver data - nouter, ninner = 100, 300 + nouter, ninner = 100, max_inner_it hclose, rclose, relax = hclose_check, 1e-3, 0.97 sim = flopy.mf6.MFSimulation( diff --git a/autotest/test_gwf_ifmod_xt3d02.py b/autotest/test_gwf_ifmod_xt3d02.py index 6ab53f467ea..b95c43b931b 100644 --- a/autotest/test_gwf_ifmod_xt3d02.py +++ b/autotest/test_gwf_ifmod_xt3d02.py @@ -37,6 +37,7 @@ mname_left = "leftmodel" mname_right = "rightmodel" hclose_check = 1e-9 +max_inner_it = 300 useXT3D = True @@ -52,7 +53,7 @@ def get_model(idx, dir): tdis_rc.append((1.0, 1, 1)) # solver data - nouter, ninner = 100, 300 + nouter, ninner = 100, max_inner_it hclose, rclose, relax = hclose_check, 1e-3, 0.97 # model spatial discretization diff --git a/autotest/test_par_gwf_rewet.py b/autotest/test_par_gwf_rewet.py new file mode 100644 index 00000000000..f8fdc1fdebd --- /dev/null +++ b/autotest/test_par_gwf_rewet.py @@ -0,0 +1,55 @@ +import os +from decimal import Decimal +import pytest +from framework import TestFramework +from simulation import TestSimulation + +# This tests reuses the simulation data in test_gwf_ifmod_rewet.py +# and runs it in parallel on three cpus with +# +# cpu 0: 'refmodel' +# cpu 1: 'leftmodel' +# cpu 2: 'rightmodel' +# +# so we can compare the parallel coupling of 'leftmodel' + 'rightmodel' +# with a serial 'refmodel' +ex = ["par_rewet"] + +def build_petsc_db(idx, exdir): + from test_gwf_ifmod_rewet import hclose_check, max_inner_it + petsc_db_file = os.path.join(exdir, ".petscrc") + with open(petsc_db_file, 'w') as petsc_file: + petsc_file.write("-ksp_type bicg\n") + petsc_file.write("-pc_type bjacobi\n") + petsc_file.write("-sub_pc_type ilu\n") + petsc_file.write("-sub_pc_factor_levels 2\n") + petsc_file.write(f"-dvclose {Decimal(hclose_check):.2E}\n") + petsc_file.write(f"-nitermax {max_inner_it}\n") + petsc_file.write("-options_left no\n") + +def build_model(idx, exdir): + from test_gwf_ifmod_rewet import build_model as build_model_ext + build_petsc_db(idx, exdir) + sim, dummy = build_model_ext(idx, exdir) + return sim, dummy + +def eval_model(test_sim): + from test_gwf_ifmod_rewet import compare_to_ref + compare_to_ref(test_sim) + +@pytest.mark.parallel +@pytest.mark.parametrize( + "idx, name", + list(enumerate(ex)), +) +def test_mf6model(idx, name, function_tmpdir, targets): + test = TestFramework() + test.build(build_model, idx, str(function_tmpdir)) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_model, + idxsim=0, make_comparison=False, + parallel=True, ncpus=3, + ), + str(function_tmpdir), + ) diff --git a/autotest/test_par_gwf_xt3d02.py b/autotest/test_par_gwf_xt3d02.py new file mode 100644 index 00000000000..d7fc1139a69 --- /dev/null +++ b/autotest/test_par_gwf_xt3d02.py @@ -0,0 +1,55 @@ +import os +from decimal import Decimal +import pytest +from framework import TestFramework +from simulation import TestSimulation + +# This tests reuses the simulation data in test_gwf_ifmod_xt3d02.py +# and runs it in parallel on three cpus with +# +# cpu 0: 'refmodel' +# cpu 1: 'leftmodel' +# cpu 2: 'rightmodel' +# +# so we can compare the parallel coupling of 'leftmodel' + 'rightmodel' +# with a serial 'refmodel' in case of XT3D +ex = ["par_xt3d02"] + +def build_petsc_db(idx, exdir): + from test_gwf_ifmod_xt3d02 import hclose_check, max_inner_it + petsc_db_file = os.path.join(exdir, ".petscrc") + with open(petsc_db_file, 'w') as petsc_file: + petsc_file.write("-ksp_type bicg\n") + petsc_file.write("-pc_type bjacobi\n") + petsc_file.write("-sub_pc_type ilu\n") + petsc_file.write("-sub_pc_factor_levels 2\n") + petsc_file.write(f"-dvclose {Decimal(hclose_check):.2E}\n") + petsc_file.write(f"-nitermax {max_inner_it}\n") + petsc_file.write("-options_left no\n") + +def build_model(idx, exdir): + from test_gwf_ifmod_xt3d02 import build_model as build_model_ext + build_petsc_db(idx, exdir) + sim, dummy = build_model_ext(idx, exdir) + return sim, dummy + +def eval_model(test_sim): + from test_gwf_ifmod_xt3d02 import compare_to_ref + compare_to_ref(test_sim) + +@pytest.mark.parallel +@pytest.mark.parametrize( + "idx, name", + list(enumerate(ex)), +) +def test_mf6model(idx, name, function_tmpdir, targets): + test = TestFramework() + test.build(build_model, idx, str(function_tmpdir)) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_model, + idxsim=0, make_comparison=False, + parallel=True, ncpus=3, + ), + str(function_tmpdir), + ) diff --git a/src/Solution/PETSc/PetscConvergence.F90 b/src/Solution/PETSc/PetscConvergence.F90 index a342d4d6bd7..47dc0d569bb 100644 --- a/src/Solution/PETSc/PetscConvergence.F90 +++ b/src/Solution/PETSc/PetscConvergence.F90 @@ -54,7 +54,6 @@ end subroutine petsc_remove_context !> @brief Routine to check the convergence. This is called !< from within PETSc. subroutine petsc_check_convergence(ksp, n, rnorm, flag, ctx_id, ierr) - use SimVariablesModule, only: proc_id KSP :: ksp !< Iterative context PetscInt :: n !< Iteration number PetscReal :: rnorm !< 2-norm (preconditioned) residual value @@ -96,9 +95,6 @@ subroutine petsc_check_convergence(ksp, n, rnorm, flag, ctx_id, ierr) CHKERRQ(ierr) if (norm < petsc_context%dvclose) then - if (proc_id == 0) then - write (*, *) "converged: ", norm, " iter: ", n - end if flag = KSP_CONVERGED_HAPPY_BREAKDOWN ! Converged else flag = KSP_CONVERGED_ITERATING ! Not yet converged From 46c5854dd3dcceaef93f4a4f32acd756c5449ce4 Mon Sep 17 00:00:00 2001 From: jdhughes-usgs Date: Wed, 12 Apr 2023 13:56:10 -0500 Subject: [PATCH 068/123] fix(ptc): fix ptc residual calculation (#1197) Also modify order of model_ptcchk so that it is not called if NO_PTC ALL or NO_PTC FIRST option are specified. Update the Release Notes. Refactor for parallel solution. --- doc/ReleaseNotes/v6.5.0.tex | 8 ++-- src/Model/GroundWaterFlow/gwf3.f90 | 68 +++++++++++++++++++++--------- src/Model/NumericalModel.f90 | 9 ++-- src/Solution/NumericalSolution.f90 | 15 ++++--- 4 files changed, 67 insertions(+), 33 deletions(-) diff --git a/doc/ReleaseNotes/v6.5.0.tex b/doc/ReleaseNotes/v6.5.0.tex index 9188d50b969..b84cc1b7735 100644 --- a/doc/ReleaseNotes/v6.5.0.tex +++ b/doc/ReleaseNotes/v6.5.0.tex @@ -50,12 +50,12 @@ % \item xxx \end{itemize} - %\underline{SOLUTION} - %\begin{itemize} - % \item xxx + \underline{SOLUTION} + \begin{itemize} + \item Fixed the residual calculation used to calculate the pseudo-transient time-step length used when psuedo-transient continuation is used for steady-state stress periods in models using the Newton-Raphson method. This fix may change the number of iterations required to achieve convergence for existing models that use psuedo-transient continuation. % \item xxx % \item xxx - %\end{itemize} + \end{itemize} %\underline{EXCHANGES} %\begin{itemize} diff --git a/src/Model/GroundWaterFlow/gwf3.f90 b/src/Model/GroundWaterFlow/gwf3.f90 index 78c896b3b91..b9a65aafdb2 100644 --- a/src/Model/GroundWaterFlow/gwf3.f90 +++ b/src/Model/GroundWaterFlow/gwf3.f90 @@ -24,6 +24,7 @@ module GwfModule use SimModule, only: count_errors, store_error, store_error_filename use BaseModelModule, only: BaseModelType use MatrixBaseModule + use VectorBaseModule implicit none @@ -630,26 +631,31 @@ end subroutine gwf_ptcchk !! for the current outer iteration !! !< - subroutine gwf_ptc(this, kiter, neqsln, matrix, & - x, rhs, iptc, ptcf) + subroutine gwf_ptc(this, matrix, & + vec_x, vec_rhs, iptc, ptcf) ! modules use ConstantsModule, only: DONE, DP9 ! -- dummy class(GwfModelType) :: this - integer(I4B), intent(in) :: kiter - integer(I4B), intent(in) :: neqsln class(MatrixBaseType), pointer :: matrix - real(DP), dimension(neqsln), intent(in) :: x - real(DP), dimension(neqsln), intent(in) :: rhs + class(VectorBaseType), pointer :: vec_x + class(VectorBaseType), pointer :: vec_rhs integer(I4B), intent(inout) :: iptc real(DP), intent(inout) :: ptcf ! -- local + integer(I4B) :: neqsln integer(I4B) :: iptct integer(I4B) :: n - integer(I4B) :: jrow, jrow_loc, matrix_offset + integer(I4B) :: jrow + integer(I4B) :: jrow_loc + integer(I4B) :: matrix_offset integer(I4B) :: j + integer(I4B) :: jcol real(DP) :: v real(DP) :: resid + real(DP), dimension(this%dis%nodes) :: resid_vec + real(DP), contiguous, dimension(:), pointer :: x + real(DP), contiguous, dimension(:), pointer :: rhs real(DP) :: ptcdelem1 real(DP) :: diag real(DP) :: diagcnt @@ -657,6 +663,14 @@ subroutine gwf_ptc(this, kiter, neqsln, matrix, & real(DP) :: diagmax integer(I4B) :: first_col, last_col ! ------------------------------------------------------------------------------ + ! + ! get size of x-vector + neqsln = vec_x%get_size() + ! + ! set pointers to vec_x and vec_rhs + x => vec_x%get_array() + rhs => vec_rhs%get_array() + ! ! -- set temporary flag indicating if pseudo-transient continuation should ! be used for this model and time step iptct = 0 @@ -672,27 +686,43 @@ subroutine gwf_ptc(this, kiter, neqsln, matrix, & ! ! -- calculate pseudo-transient continuation factor for model if (iptct > 0) then + matrix_offset = matrix%get_row_offset() + ! + ! calculate the residual + do n = 1, this%dis%nodes + resid = DZERO + if (this%npf%ibound(n) > 0) then + jrow = n + this%moffset + jrow_loc = jrow - matrix_offset + + ! diagonal and off-diagonal elements + first_col = matrix%get_first_col_pos(jrow) + last_col = matrix%get_last_col_pos(jrow) + do j = first_col, last_col + jcol = matrix%get_column(j) + if (jcol > neqsln) cycle ! temporary protection for parallel case + resid = resid + matrix%get_value_pos(j) * x(jcol) + end do + + ! subtract the right-hand side + resid = resid - rhs(jrow_loc) + end if + resid_vec(n) = resid + end do + ! + ! calculate the pseudo-time step with constraints + ! using the calculated residual diagmin = DEP20 diagmax = DZERO diagcnt = DZERO - matrix_offset = matrix%get_row_offset() do n = 1, this%dis%nodes if (this%npf%ibound(n) < 1) cycle ! - jrow = n + this%moffset - jrow_loc = jrow - matrix_offset - ! ! get the maximum volume of the cell (head at top of cell) v = this%dis%get_cell_volume(n, this%dis%top(n)) ! - ! -- calculate the residual for the cell - resid = DZERO - first_col = matrix%get_first_col_pos(jrow) - last_col = matrix%get_last_col_pos(jrow) - do j = first_col, last_col - resid = resid + matrix%get_value_pos(j) * x(jrow_loc) - end do - resid = resid - rhs(jrow_loc) + ! set the residual + resid = resid_vec(n) ! ! -- calculate the reciprocal of the pseudo-time step ! resid [L3/T] / volume [L3] = [1/T] diff --git a/src/Model/NumericalModel.f90 b/src/Model/NumericalModel.f90 index 97ed6ee9987..188f2f1dcb2 100644 --- a/src/Model/NumericalModel.f90 +++ b/src/Model/NumericalModel.f90 @@ -8,6 +8,7 @@ module NumericalModelModule use TimeArraySeriesManagerModule, only: TimeArraySeriesManagerType use ListModule, only: ListType use MatrixBaseModule + use VectorBaseModule implicit none private @@ -123,13 +124,11 @@ subroutine model_ptcchk(this, iptc) iptc = 0 end subroutine model_ptcchk - subroutine model_ptc(this, kiter, neqsln, matrix, x, rhs, iptc, ptcf) + subroutine model_ptc(this, matrix, vec_x, vec_rhs, iptc, ptcf) class(NumericalModelType) :: this - integer(I4B), intent(in) :: kiter - integer(I4B), intent(in) :: neqsln class(MatrixBaseType), pointer :: matrix - real(DP), dimension(neqsln), intent(in) :: x - real(DP), dimension(neqsln), intent(in) :: rhs + class(VectorBaseType), pointer :: vec_x + class(VectorBaseType), pointer :: vec_rhs integer(I4B), intent(inout) :: iptc real(DP), intent(inout) :: ptcf end subroutine model_ptc diff --git a/src/Solution/NumericalSolution.f90 b/src/Solution/NumericalSolution.f90 index 5c080aadda4..db581aa2861 100644 --- a/src/Solution/NumericalSolution.f90 +++ b/src/Solution/NumericalSolution.f90 @@ -1428,8 +1428,6 @@ subroutine writePTCInfoToFile(this, kper) ! -- determine if PTC will be used in any model n = 1 do im = 1, this%modellist%Count() - mp => GetNumericalModelFromList(this%modellist, im) - call mp%model_ptcchk(iptc) ! ! -- set iallowptc ! -- no_ptc_option is FIRST @@ -1443,7 +1441,14 @@ subroutine writePTCInfoToFile(this, kper) else iallowptc = this%iallowptc end if - iptc = iptc * iallowptc + + if (iallowptc > 0) then + mp => GetNumericalModelFromList(this%modellist, im) + call mp%model_ptcchk(iptc) + else + iptc = 0 + end if + if (iptc /= 0) then if (n == 1) then write (iout, '(//)') @@ -1607,8 +1612,8 @@ subroutine solve(this, kiter) ptcf = DZERO do im = 1, this%modellist%Count() mp => GetNumericalModelFromList(this%modellist, im) - call mp%model_ptc(kiter, this%neq, this%system_matrix, & - this%x, this%rhs, iptc, ptcf) + call mp%model_ptc(this%system_matrix, & + this%vec_x, this%vec_rhs, iptc, ptcf) end do ! ! -- Add model Newton-Raphson terms to solution From b88d321fa0eeded0ea2dfa74208bdcc6a620b213 Mon Sep 17 00:00:00 2001 From: mjr-deltares <45555666+mjr-deltares@users.noreply.github.com> Date: Thu, 13 Apr 2023 00:22:42 +0200 Subject: [PATCH 069/123] fix(ci): quick shot at fix for ptc (#1202) * ptc: quick shot at fixing ci * quick check if this revert fixes ci * argh, typo... * collateral from refactoring * now reintroduce the fix for ptc * reverting fix to get ci green for now --- src/Model/GroundWaterFlow/gwf3.f90 | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/Model/GroundWaterFlow/gwf3.f90 b/src/Model/GroundWaterFlow/gwf3.f90 index b9a65aafdb2..9e37a1c7575 100644 --- a/src/Model/GroundWaterFlow/gwf3.f90 +++ b/src/Model/GroundWaterFlow/gwf3.f90 @@ -643,7 +643,6 @@ subroutine gwf_ptc(this, matrix, & integer(I4B), intent(inout) :: iptc real(DP), intent(inout) :: ptcf ! -- local - integer(I4B) :: neqsln integer(I4B) :: iptct integer(I4B) :: n integer(I4B) :: jrow @@ -651,6 +650,7 @@ subroutine gwf_ptc(this, matrix, & integer(I4B) :: matrix_offset integer(I4B) :: j integer(I4B) :: jcol + integer(I4B) :: jcol_loc real(DP) :: v real(DP) :: resid real(DP), dimension(this%dis%nodes) :: resid_vec @@ -663,9 +663,6 @@ subroutine gwf_ptc(this, matrix, & real(DP) :: diagmax integer(I4B) :: first_col, last_col ! ------------------------------------------------------------------------------ - ! - ! get size of x-vector - neqsln = vec_x%get_size() ! ! set pointers to vec_x and vec_rhs x => vec_x%get_array() @@ -700,8 +697,9 @@ subroutine gwf_ptc(this, matrix, & last_col = matrix%get_last_col_pos(jrow) do j = first_col, last_col jcol = matrix%get_column(j) - if (jcol > neqsln) cycle ! temporary protection for parallel case - resid = resid + matrix%get_value_pos(j) * x(jcol) + jcol_loc = jcol - matrix_offset + if (jcol_loc < 1 .or. jcol_loc > size(x)) cycle ! temporary protection for parallel case + resid = resid + matrix%get_value_pos(j) * x(jrow_loc) end do ! subtract the right-hand side @@ -734,6 +732,7 @@ subroutine gwf_ptc(this, matrix, & if (ptcdelem1 > ptcf) ptcf = ptcdelem1 ! ! -- determine minimum and maximum diagonal entries + jrow = n + this%moffset diag = abs(matrix%get_diag_value(jrow)) diagcnt = diagcnt + DONE if (diag > DZERO) then From 4b973d091dfae7fccc8493f2716f6ed4bde3978a Mon Sep 17 00:00:00 2001 From: mjreno Date: Sat, 15 Apr 2023 15:22:32 -0400 Subject: [PATCH 070/123] fix(idm): improved error reporting for unrecognized keywords (#1203) * preserve case of model namefile list filename input parameter * set error if auxiliary input is expected in period block but not found * improve error reporting for unrecognized input file keywords * update makefile * use more source independent filename instead of parser * update release notes for input error handling updates --------- Co-authored-by: mjreno --- doc/ReleaseNotes/v6.5.0.tex | 1 + doc/mf6io/mf6ivar/dfn/gwf-nam.dfn | 1 + doc/mf6io/mf6ivar/dfn/gwt-nam.dfn | 1 + make/makefile | 412 +++++++++--------- src/Model/GroundWaterFlow/gwf3idm.f90 | 2 +- src/Model/GroundWaterTransport/gwt1idm.f90 | 2 +- src/Utilities/Idm/DefinitionSelect.f90 | 19 +- .../Idm/mf6blockfile/LoadMf6File.f90 | 42 +- src/Utilities/Idm/selector/IdmDfnSelector.f90 | 15 - src/Utilities/ListReader.f90 | 8 + utils/idmloader/scripts/dfn2f90.py | 7 - 11 files changed, 257 insertions(+), 253 deletions(-) diff --git a/doc/ReleaseNotes/v6.5.0.tex b/doc/ReleaseNotes/v6.5.0.tex index b84cc1b7735..6992d5ad49c 100644 --- a/doc/ReleaseNotes/v6.5.0.tex +++ b/doc/ReleaseNotes/v6.5.0.tex @@ -24,6 +24,7 @@ \begin{itemize} \item When n-point cross-sections are active in SFR, the evaporation calculation uses the variable rwid (see MF6io.pdf) to calculate the total amount of evaporation even though the wetted topwidth is less than rwid. For example, using a trapezoidal cross-section geometry with an rwid of 10, an rlen of 100, and prescribed evaporation rate of 0.1, the calculated evaporative losses would equal 100 even when the wetted top width was only 5.0 units wide. With this bug fix, the evaporation in this example results in only 50 units of evaporation loss. A new autotests confirms the evaporation calculation using an n-point cross-section and common rectangular geometries in the same simulation. It is also worth mentioning that the precipitation calculation currently uses rwid. Since the precipitation falling outside the margins of the wetted top width but within rwid would likely be accumulated in a channel, it makes sense to leave this calculation as is. \item The input for some stress packages is read in a list format consisting of a cellid, the form of which depends on the type of discretization package, and stress information on each line. The cellid is checked upon reading to ensure that the cell is within the model grid. If the cell is outside the model grid, the program issues an error message and terminates. This cellid check was not implemented when the list was provided from an OPEN/CLOSE binary input file. The program was modified to include this check for both text and binary input. + \item In some cases, unrecognized keywords and invalid auxiliary input did not terminate with a useful error message. The program was corrected to provide error handling for these cases. % \item xxx \end{itemize} diff --git a/doc/mf6io/mf6ivar/dfn/gwf-nam.dfn b/doc/mf6io/mf6ivar/dfn/gwf-nam.dfn index a2735bef01d..b363bca2eca 100644 --- a/doc/mf6io/mf6ivar/dfn/gwf-nam.dfn +++ b/doc/mf6io/mf6ivar/dfn/gwf-nam.dfn @@ -5,6 +5,7 @@ name list type string reader urword optional true +preserve_case true longname name of listing file description is name of the listing file to create for this GWF model. If not specified, then the name of the list file will be the basename of the GWF model name file and the '.lst' extension. For example, if the GWF name file is called ``my.model.nam'' then the list file will be called ``my.model.lst''. diff --git a/doc/mf6io/mf6ivar/dfn/gwt-nam.dfn b/doc/mf6io/mf6ivar/dfn/gwt-nam.dfn index 22daa2d6583..cc68d510f30 100644 --- a/doc/mf6io/mf6ivar/dfn/gwt-nam.dfn +++ b/doc/mf6io/mf6ivar/dfn/gwt-nam.dfn @@ -5,6 +5,7 @@ name list type string reader urword optional true +preserve_case true longname name of listing file description is name of the listing file to create for this GWT model. If not specified, then the name of the list file will be the basename of the GWT model name file and the '.lst' extension. For example, if the GWT name file is called ``my.model.nam'' then the list file will be called ``my.model.lst''. diff --git a/make/makefile b/make/makefile index 62617b14b8d..90d440be5b6 100644 --- a/make/makefile +++ b/make/makefile @@ -5,35 +5,35 @@ include ./makedefaults # Define the source file directories SOURCEDIR1=../src -SOURCEDIR2=../src/Exchange -SOURCEDIR3=../src/Model -SOURCEDIR4=../src/Model/Geometry -SOURCEDIR5=../src/Model/ModelUtilities -SOURCEDIR6=../src/Model/Connection -SOURCEDIR7=../src/Model/GroundWaterTransport -SOURCEDIR8=../src/Model/GroundWaterFlow -SOURCEDIR9=../src/Distributed +SOURCEDIR2=../src/Distributed +SOURCEDIR3=../src/Exchange +SOURCEDIR4=../src/Model +SOURCEDIR5=../src/Model/Connection +SOURCEDIR6=../src/Model/Geometry +SOURCEDIR7=../src/Model/GroundWaterFlow +SOURCEDIR8=../src/Model/GroundWaterTransport +SOURCEDIR9=../src/Model/ModelUtilities SOURCEDIR10=../src/Solution -SOURCEDIR11=../src/Solution/PETSc -SOURCEDIR12=../src/Solution/LinearMethods +SOURCEDIR11=../src/Solution/LinearMethods +SOURCEDIR12=../src/Solution/PETSc SOURCEDIR13=../src/Timing SOURCEDIR14=../src/Utilities -SOURCEDIR15=../src/Utilities/TimeSeries -SOURCEDIR16=../src/Utilities/Libraries -SOURCEDIR17=../src/Utilities/Libraries/rcm -SOURCEDIR18=../src/Utilities/Libraries/sparsekit -SOURCEDIR19=../src/Utilities/Libraries/sparskit2 +SOURCEDIR15=../src/Utilities/ArrayRead +SOURCEDIR16=../src/Utilities/Idm +SOURCEDIR17=../src/Utilities/Idm/mf6blockfile +SOURCEDIR18=../src/Utilities/Idm/selector +SOURCEDIR19=../src/Utilities/Libraries SOURCEDIR20=../src/Utilities/Libraries/blas SOURCEDIR21=../src/Utilities/Libraries/daglib -SOURCEDIR22=../src/Utilities/Idm -SOURCEDIR23=../src/Utilities/Idm/selector -SOURCEDIR24=../src/Utilities/Idm/mf6blockfile +SOURCEDIR22=../src/Utilities/Libraries/rcm +SOURCEDIR23=../src/Utilities/Libraries/sparsekit +SOURCEDIR24=../src/Utilities/Libraries/sparskit2 SOURCEDIR25=../src/Utilities/Matrix -SOURCEDIR26=../src/Utilities/Vector +SOURCEDIR26=../src/Utilities/Memory SOURCEDIR27=../src/Utilities/Observation SOURCEDIR28=../src/Utilities/OutputControl -SOURCEDIR29=../src/Utilities/Memory -SOURCEDIR30=../src/Utilities/ArrayRead +SOURCEDIR29=../src/Utilities/TimeSeries +SOURCEDIR30=../src/Utilities/Vector VPATH = \ ${SOURCEDIR1} \ @@ -70,235 +70,235 @@ ${SOURCEDIR30} .SUFFIXES: .f90 .F90 .o OBJECTS = \ -$(OBJDIR)/ilut.o \ $(OBJDIR)/kind.o \ -$(OBJDIR)/VectorBase.o \ -$(OBJDIR)/IdmLogger.o \ -$(OBJDIR)/BaseGeometry.o \ -$(OBJDIR)/InputDefinition.o \ -$(OBJDIR)/SimStages.o \ -$(OBJDIR)/GwtDspOptions.o \ -$(OBJDIR)/gwf3npf8idm.o \ -$(OBJDIR)/Sparse.o \ -$(OBJDIR)/GwtAdvOptions.o \ -$(OBJDIR)/gwt1disv1idm.o \ -$(OBJDIR)/gwt1dis1idm.o \ -$(OBJDIR)/gwf3dis8idm.o \ -$(OBJDIR)/simnamidm.o \ -$(OBJDIR)/gwf3disu8idm.o \ -$(OBJDIR)/CsrUtils.o \ -$(OBJDIR)/gwt1idm.o \ -$(OBJDIR)/gwf3disv8idm.o \ -$(OBJDIR)/blas1_d.o \ -$(OBJDIR)/gwt1dspidm.o \ -$(OBJDIR)/CharString.o \ -$(OBJDIR)/OpenSpec.o \ -$(OBJDIR)/dag_module.o \ -$(OBJDIR)/ims8reordering.o \ $(OBJDIR)/Constants.o \ -$(OBJDIR)/rcm.o \ -$(OBJDIR)/HashTable.o \ -$(OBJDIR)/sparsekit.o \ -$(OBJDIR)/gwt1disu1idm.o \ -$(OBJDIR)/gwf3idm.o \ -$(OBJDIR)/SfrCrossSectionUtils.o \ -$(OBJDIR)/MatrixBase.o \ -$(OBJDIR)/compilerversion.o \ -$(OBJDIR)/defmacro.o \ $(OBJDIR)/SimVariables.o \ -$(OBJDIR)/SmoothingFunctions.o \ -$(OBJDIR)/GwfVscInputData.o \ -$(OBJDIR)/GwfStorageUtils.o \ -$(OBJDIR)/Xt3dAlgorithm.o \ -$(OBJDIR)/GwfNpfOptions.o \ -$(OBJDIR)/GwfBuyInputData.o \ -$(OBJDIR)/ims8misc.o \ -$(OBJDIR)/LinearSolverBase.o \ $(OBJDIR)/genericutils.o \ +$(OBJDIR)/compilerversion.o \ $(OBJDIR)/ArrayHandlers.o \ -$(OBJDIR)/IndexMap.o \ $(OBJDIR)/version.o \ -$(OBJDIR)/InterfaceMap.o \ $(OBJDIR)/Message.o \ -$(OBJDIR)/List.o \ +$(OBJDIR)/defmacro.o \ $(OBJDIR)/Sim.o \ -$(OBJDIR)/Timer.o \ -$(OBJDIR)/MemoryHelper.o \ -$(OBJDIR)/sort.o \ -$(OBJDIR)/StringList.o \ -$(OBJDIR)/mf6lists.o \ -$(OBJDIR)/IdmGwfDfnSelector.o \ -$(OBJDIR)/IdmGwtDfnSelector.o \ -$(OBJDIR)/ObsOutput.o \ -$(OBJDIR)/Budget.o \ -$(OBJDIR)/IdmSimDfnSelector.o \ -$(OBJDIR)/DistributedVariable.o \ +$(OBJDIR)/OpenSpec.o \ $(OBJDIR)/InputOutput.o \ -$(OBJDIR)/VirtualDataLists.o \ -$(OBJDIR)/DisvGeom.o \ -$(OBJDIR)/Iunit.o \ -$(OBJDIR)/TimeSeriesRecord.o \ $(OBJDIR)/TableTerm.o \ -$(OBJDIR)/HeadFileReader.o \ -$(OBJDIR)/PrintSaveManager.o \ -$(OBJDIR)/IdmDfnSelector.o \ -$(OBJDIR)/DefinitionSelect.o \ -$(OBJDIR)/ArrayReaders.o \ -$(OBJDIR)/comarg.o \ -$(OBJDIR)/STLVecInt.o \ -$(OBJDIR)/BlockParser.o \ -$(OBJDIR)/CircularGeometry.o \ -$(OBJDIR)/BudgetFileReader.o \ -$(OBJDIR)/RectangularGeometry.o \ -$(OBJDIR)/ObsOutputList.o \ -$(OBJDIR)/ims8base.o \ -$(OBJDIR)/TimeSeries.o \ -$(OBJDIR)/TimeSeriesFileList.o \ -$(OBJDIR)/ArrayReaderBase.o \ -$(OBJDIR)/ModflowInput.o \ -$(OBJDIR)/TimeSeriesLink.o \ -$(OBJDIR)/Double1dReader.o \ -$(OBJDIR)/Double2dReader.o \ $(OBJDIR)/Table.o \ -$(OBJDIR)/Integer2dReader.o \ -$(OBJDIR)/StructVector.o \ -$(OBJDIR)/SfrCrossSectionManager.o \ -$(OBJDIR)/ListReader.o \ +$(OBJDIR)/MemoryHelper.o \ +$(OBJDIR)/CharString.o \ $(OBJDIR)/Memory.o \ +$(OBJDIR)/List.o \ $(OBJDIR)/MemoryList.o \ +$(OBJDIR)/TimeSeriesRecord.o \ +$(OBJDIR)/BlockParser.o \ $(OBJDIR)/MemoryManager.o \ +$(OBJDIR)/TimeSeries.o \ $(OBJDIR)/ats.o \ -$(OBJDIR)/Integer1dReader.o \ -$(OBJDIR)/StructArray.o \ -$(OBJDIR)/MemorySetHandler.o \ -$(OBJDIR)/MappedMemory.o \ -$(OBJDIR)/ModelPackageInputs.o \ -$(OBJDIR)/BaseModel.o \ -$(OBJDIR)/GwfMvrPeriodData.o \ -$(OBJDIR)/ims8linear.o \ -$(OBJDIR)/MemoryManagerExt.o \ -$(OBJDIR)/Connections.o \ -$(OBJDIR)/SeqVector.o \ +$(OBJDIR)/TimeSeriesLink.o \ +$(OBJDIR)/TimeSeriesFileList.o \ $(OBJDIR)/tdis.o \ -$(OBJDIR)/PackageMover.o \ -$(OBJDIR)/PackageBudget.o \ +$(OBJDIR)/HashTable.o \ +$(OBJDIR)/VectorBase.o \ +$(OBJDIR)/Sparse.o \ +$(OBJDIR)/DisvGeom.o \ +$(OBJDIR)/ArrayReaders.o \ $(OBJDIR)/TimeSeriesManager.o \ -$(OBJDIR)/Mover.o \ -$(OBJDIR)/VirtualBase.o \ -$(OBJDIR)/VirtualDataContainer.o \ -$(OBJDIR)/BaseExchange.o \ -$(OBJDIR)/SparseMatrix.o \ +$(OBJDIR)/SmoothingFunctions.o \ +$(OBJDIR)/MatrixBase.o \ +$(OBJDIR)/ListReader.o \ +$(OBJDIR)/Connections.o \ $(OBJDIR)/DiscretizationBase.o \ -$(OBJDIR)/UzfCellGroup.o \ -$(OBJDIR)/BudgetTerm.o \ -$(OBJDIR)/Observe.o \ -$(OBJDIR)/OutputControlData.o \ -$(OBJDIR)/gwf3dis8.o \ -$(OBJDIR)/LayeredArrayReader.o \ $(OBJDIR)/TimeArray.o \ -$(OBJDIR)/NumericalPackage.o \ -$(OBJDIR)/LoadMf6File.o \ -$(OBJDIR)/ExplicitModel.o \ -$(OBJDIR)/BaseSolution.o \ -$(OBJDIR)/gwf3disu8.o \ +$(OBJDIR)/ObsOutput.o \ $(OBJDIR)/TimeArraySeries.o \ -$(OBJDIR)/SolutionGroup.o \ -$(OBJDIR)/ObsContainer.o \ -$(OBJDIR)/ExplicitSolution.o \ +$(OBJDIR)/ObsOutputList.o \ +$(OBJDIR)/Observe.o \ $(OBJDIR)/TimeArraySeriesLink.o \ -$(OBJDIR)/Xt3dInterface.o \ -$(OBJDIR)/gwf3disv8.o \ -$(OBJDIR)/ImsLinearSolver.o \ +$(OBJDIR)/ObsUtility.o \ +$(OBJDIR)/ObsContainer.o \ +$(OBJDIR)/BudgetFileReader.o \ $(OBJDIR)/TimeArraySeriesManager.o \ +$(OBJDIR)/PackageMover.o \ +$(OBJDIR)/Obs3.o \ +$(OBJDIR)/NumericalPackage.o \ +$(OBJDIR)/Budget.o \ +$(OBJDIR)/SeqVector.o \ +$(OBJDIR)/sort.o \ +$(OBJDIR)/SfrCrossSectionUtils.o \ +$(OBJDIR)/BudgetTerm.o \ +$(OBJDIR)/BoundaryPackage.o \ +$(OBJDIR)/BaseModel.o \ +$(OBJDIR)/SparseMatrix.o \ +$(OBJDIR)/LinearSolverBase.o \ +$(OBJDIR)/ims8reordering.o \ +$(OBJDIR)/VirtualBase.o \ +$(OBJDIR)/STLVecInt.o \ +$(OBJDIR)/InputDefinition.o \ +$(OBJDIR)/SfrCrossSectionManager.o \ +$(OBJDIR)/dag_module.o \ $(OBJDIR)/BudgetObject.o \ -$(OBJDIR)/ObsUtility.o \ +$(OBJDIR)/NumericalModel.o \ +$(OBJDIR)/BaseExchange.o \ +$(OBJDIR)/ImsLinearSolver.o \ +$(OBJDIR)/ims8base.o \ +$(OBJDIR)/VirtualDataLists.o \ +$(OBJDIR)/VirtualDataContainer.o \ +$(OBJDIR)/SimStages.o \ +$(OBJDIR)/simnamidm.o \ +$(OBJDIR)/gwt1idm.o \ +$(OBJDIR)/gwt1dspidm.o \ +$(OBJDIR)/gwt1disv1idm.o \ +$(OBJDIR)/gwt1disu1idm.o \ +$(OBJDIR)/gwt1dis1idm.o \ +$(OBJDIR)/gwf3npf8idm.o \ +$(OBJDIR)/gwf3idm.o \ +$(OBJDIR)/gwf3disv8idm.o \ +$(OBJDIR)/gwf3disu8idm.o \ +$(OBJDIR)/gwf3dis8idm.o \ +$(OBJDIR)/PackageBudget.o \ +$(OBJDIR)/HeadFileReader.o \ +$(OBJDIR)/PrintSaveManager.o \ +$(OBJDIR)/Xt3dAlgorithm.o \ $(OBJDIR)/gwf3tvbase8.o \ -$(OBJDIR)/gwf3tvs8.o \ -$(OBJDIR)/IdmMf6File.o \ -$(OBJDIR)/OutputControl.o \ +$(OBJDIR)/gwf3sfr8.o \ +$(OBJDIR)/gwf3riv8.o \ +$(OBJDIR)/gwf3maw8.o \ +$(OBJDIR)/mf6lists.o \ +$(OBJDIR)/gwf3lak8.o \ +$(OBJDIR)/GwfVscInputData.o \ +$(OBJDIR)/gwf3ghb8.o \ +$(OBJDIR)/gwf3drn8.o \ +$(OBJDIR)/Timer.o \ +$(OBJDIR)/NumericalExchange.o \ $(OBJDIR)/LinearSolverFactory.o \ +$(OBJDIR)/ims8linear.o \ +$(OBJDIR)/BaseSolution.o \ +$(OBJDIR)/IndexMap.o \ +$(OBJDIR)/VirtualModel.o \ +$(OBJDIR)/IdmSimDfnSelector.o \ +$(OBJDIR)/IdmGwtDfnSelector.o \ +$(OBJDIR)/IdmGwfDfnSelector.o \ +$(OBJDIR)/UzfCellGroup.o \ +$(OBJDIR)/gwt1fmi1.o \ +$(OBJDIR)/OutputControlData.o \ $(OBJDIR)/gwf3ic8.o \ -$(OBJDIR)/Obs3.o \ +$(OBJDIR)/Xt3dInterface.o \ $(OBJDIR)/gwf3tvk8.o \ -$(OBJDIR)/GwtSpc.o \ -$(OBJDIR)/IdmSimulation.o \ -$(OBJDIR)/gwt1oc1.o \ -$(OBJDIR)/gwf3mvr8.o \ -$(OBJDIR)/gwt1ic1.o \ -$(OBJDIR)/gwt1obs1.o \ -$(OBJDIR)/gwf3obs8.o \ -$(OBJDIR)/gwf3oc8.o \ -$(OBJDIR)/gwf3sto8.o \ -$(OBJDIR)/BoundaryPackage.o \ -$(OBJDIR)/gwf3csub8.o \ -$(OBJDIR)/gwf3uzf8.o \ -$(OBJDIR)/gwt1cnc1.o \ -$(OBJDIR)/gwf3lak8.o \ -$(OBJDIR)/gwt1src1.o \ -$(OBJDIR)/gwf3maw8.o \ -$(OBJDIR)/gwt1fmi1.o \ -$(OBJDIR)/gwf3rch8.o \ -$(OBJDIR)/gwt1apt1.o \ -$(OBJDIR)/gwf3wel8.o \ -$(OBJDIR)/gwf3riv8.o \ -$(OBJDIR)/gwf3drn8.o \ -$(OBJDIR)/gwt1mwt1.o \ -$(OBJDIR)/gwf3sfr8.o \ -$(OBJDIR)/gwf3api8.o \ -$(OBJDIR)/gwf3evt8.o \ -$(OBJDIR)/NumericalModel.o \ -$(OBJDIR)/gwf3ghb8.o \ -$(OBJDIR)/gwf3chd8.o \ -$(OBJDIR)/gwt1uzt1.o \ +$(OBJDIR)/MemoryManagerExt.o \ $(OBJDIR)/gwf3vsc8.o \ -$(OBJDIR)/NumericalExchange.o \ -$(OBJDIR)/gwt1ssm1.o \ +$(OBJDIR)/GwfNpfOptions.o \ $(OBJDIR)/NumericalSolution.o \ -$(OBJDIR)/gwt1adv1.o \ -$(OBJDIR)/gwt1lkt1.o \ -$(OBJDIR)/SolutionFactory.o \ -$(OBJDIR)/VirtualModel.o \ -$(OBJDIR)/gwt1sft1.o \ +$(OBJDIR)/InterfaceMap.o \ +$(OBJDIR)/CellWithNbrs.o \ +$(OBJDIR)/IdmDfnSelector.o \ +$(OBJDIR)/gwf3uzf8.o \ +$(OBJDIR)/gwt1apt1.o \ +$(OBJDIR)/GwtSpc.o \ +$(OBJDIR)/OutputControl.o \ +$(OBJDIR)/gwt1ic1.o \ $(OBJDIR)/gwt1mst1.o \ -$(OBJDIR)/VirtualSolution.o \ -$(OBJDIR)/VirtualGwfModel.o \ -$(OBJDIR)/gwt1ist1.o \ +$(OBJDIR)/GwtDspOptions.o \ $(OBJDIR)/gwf3npf8.o \ -$(OBJDIR)/TransportModel.o \ +$(OBJDIR)/GwtAdvOptions.o \ +$(OBJDIR)/gwf3tvs8.o \ +$(OBJDIR)/GwfStorageUtils.o \ +$(OBJDIR)/Mover.o \ +$(OBJDIR)/GwfMvrPeriodData.o \ +$(OBJDIR)/ims8misc.o \ +$(OBJDIR)/GwfBuyInputData.o \ +$(OBJDIR)/VirtualSolution.o \ +$(OBJDIR)/ArrayReaderBase.o \ +$(OBJDIR)/VirtualExchange.o \ +$(OBJDIR)/gwf3disu8.o \ +$(OBJDIR)/GridSorting.o \ $(OBJDIR)/DisConnExchange.o \ -$(OBJDIR)/GhostNode.o \ +$(OBJDIR)/CsrUtils.o \ +$(OBJDIR)/TransportModel.o \ +$(OBJDIR)/ModelPackageInputs.o \ +$(OBJDIR)/gwt1uzt1.o \ +$(OBJDIR)/gwt1ssm1.o \ +$(OBJDIR)/gwt1src1.o \ +$(OBJDIR)/gwt1sft1.o \ +$(OBJDIR)/gwt1oc1.o \ +$(OBJDIR)/gwt1obs1.o \ +$(OBJDIR)/gwt1mwt1.o \ $(OBJDIR)/gwt1mvt1.o \ -$(OBJDIR)/VirtualExchange.o \ +$(OBJDIR)/gwt1lkt1.o \ +$(OBJDIR)/gwt1ist1.o \ $(OBJDIR)/gwt1dsp.o \ -$(OBJDIR)/gwf3buy8.o \ -$(OBJDIR)/VirtualGwfExchange.o \ +$(OBJDIR)/gwt1cnc1.o \ +$(OBJDIR)/gwt1adv1.o \ +$(OBJDIR)/gwf3disv8.o \ +$(OBJDIR)/gwf3dis8.o \ +$(OBJDIR)/gwf3api8.o \ +$(OBJDIR)/gwf3wel8.o \ +$(OBJDIR)/gwf3rch8.o \ +$(OBJDIR)/gwf3sto8.o \ +$(OBJDIR)/gwf3oc8.o \ +$(OBJDIR)/gwf3obs8.o \ +$(OBJDIR)/gwf3mvr8.o \ $(OBJDIR)/gwf3hfb8.o \ -$(OBJDIR)/VirtualGwtExchange.o \ -$(OBJDIR)/VirtualGwtModel.o \ -$(OBJDIR)/CellWithNbrs.o \ +$(OBJDIR)/gwf3csub8.o \ +$(OBJDIR)/gwf3buy8.o \ +$(OBJDIR)/GhostNode.o \ +$(OBJDIR)/gwf3evt8.o \ +$(OBJDIR)/gwf3chd8.o \ $(OBJDIR)/RouterBase.o \ -$(OBJDIR)/gwf3.o \ +$(OBJDIR)/Integer2dReader.o \ +$(OBJDIR)/GridConnection.o \ +$(OBJDIR)/DistributedVariable.o \ $(OBJDIR)/gwt1.o \ -$(OBJDIR)/GridSorting.o \ -$(OBJDIR)/GwfGwfExchange.o \ +$(OBJDIR)/gwf3.o \ $(OBJDIR)/SerialRouter.o \ -$(OBJDIR)/RouterFactory.o \ -$(OBJDIR)/GwtGwtExchange.o \ -$(OBJDIR)/GridConnection.o \ +$(OBJDIR)/StructVector.o \ +$(OBJDIR)/IdmLogger.o \ +$(OBJDIR)/Integer1dReader.o \ +$(OBJDIR)/Double2dReader.o \ +$(OBJDIR)/Double1dReader.o \ +$(OBJDIR)/ExplicitModel.o \ $(OBJDIR)/SpatialModelConnection.o \ -$(OBJDIR)/Mapper.o \ -$(OBJDIR)/VirtualDataManager.o \ -$(OBJDIR)/GwfInterfaceModel.o \ -$(OBJDIR)/RunControl.o \ $(OBJDIR)/GwtInterfaceModel.o \ -$(OBJDIR)/RunControlFactory.o \ -$(OBJDIR)/GwfGwfConnection.o \ +$(OBJDIR)/GwtGwtExchange.o \ +$(OBJDIR)/GwfInterfaceModel.o \ +$(OBJDIR)/GwfGwfExchange.o \ +$(OBJDIR)/RouterFactory.o \ +$(OBJDIR)/MappedMemory.o \ +$(OBJDIR)/StructArray.o \ +$(OBJDIR)/ModflowInput.o \ +$(OBJDIR)/LayeredArrayReader.o \ +$(OBJDIR)/DefinitionSelect.o \ +$(OBJDIR)/ExplicitSolution.o \ $(OBJDIR)/GwtGwtConnection.o \ -$(OBJDIR)/ConnectionBuilder.o \ +$(OBJDIR)/GwfGwfConnection.o \ +$(OBJDIR)/VirtualDataManager.o \ +$(OBJDIR)/Mapper.o \ +$(OBJDIR)/LoadMf6File.o \ +$(OBJDIR)/VirtualGwtModel.o \ +$(OBJDIR)/VirtualGwtExchange.o \ +$(OBJDIR)/VirtualGwfModel.o \ +$(OBJDIR)/VirtualGwfExchange.o \ +$(OBJDIR)/SolutionGroup.o \ +$(OBJDIR)/SolutionFactory.o \ $(OBJDIR)/GwfGwtExchange.o \ +$(OBJDIR)/RunControl.o \ +$(OBJDIR)/IdmMf6File.o \ $(OBJDIR)/SimulationCreate.o \ +$(OBJDIR)/RunControlFactory.o \ +$(OBJDIR)/IdmSimulation.o \ +$(OBJDIR)/ConnectionBuilder.o \ +$(OBJDIR)/comarg.o \ $(OBJDIR)/mf6core.o \ -$(OBJDIR)/mf6.o +$(OBJDIR)/BaseGeometry.o \ +$(OBJDIR)/mf6.o \ +$(OBJDIR)/StringList.o \ +$(OBJDIR)/MemorySetHandler.o \ +$(OBJDIR)/ilut.o \ +$(OBJDIR)/sparsekit.o \ +$(OBJDIR)/rcm.o \ +$(OBJDIR)/blas1_d.o \ +$(OBJDIR)/Iunit.o \ +$(OBJDIR)/RectangularGeometry.o \ +$(OBJDIR)/CircularGeometry.o # Define the objects that make up the program $(PROGRAM) : $(OBJECTS) diff --git a/src/Model/GroundWaterFlow/gwf3idm.f90 b/src/Model/GroundWaterFlow/gwf3idm.f90 index 4645a821583..0aaf3b5ac74 100644 --- a/src/Model/GroundWaterFlow/gwf3idm.f90 +++ b/src/Model/GroundWaterFlow/gwf3idm.f90 @@ -36,7 +36,7 @@ module GwfNamInputModule '', & ! shape .false., & ! required .false., & ! multi-record - .false., & ! preserve case + .true., & ! preserve case .false. & ! layered ) diff --git a/src/Model/GroundWaterTransport/gwt1idm.f90 b/src/Model/GroundWaterTransport/gwt1idm.f90 index d8991dd96b2..e63fd582106 100644 --- a/src/Model/GroundWaterTransport/gwt1idm.f90 +++ b/src/Model/GroundWaterTransport/gwt1idm.f90 @@ -33,7 +33,7 @@ module GwtNamInputModule '', & ! shape .false., & ! required .false., & ! multi-record - .false., & ! preserve case + .true., & ! preserve case .false. & ! layered ) diff --git a/src/Utilities/Idm/DefinitionSelect.f90 b/src/Utilities/Idm/DefinitionSelect.f90 index 2f263c1a797..088d75edd40 100644 --- a/src/Utilities/Idm/DefinitionSelect.f90 +++ b/src/Utilities/Idm/DefinitionSelect.f90 @@ -9,7 +9,7 @@ module DefinitionSelectModule use KindModule, only: I4B use SimVariablesModule, only: errmsg - use SimModule, only: store_error + use SimModule, only: store_error, store_error_filename use InputDefinitionModule, only: InputParamDefinitionType, & InputBlockDefinitionType @@ -23,8 +23,9 @@ module DefinitionSelectModule !> @brief Return parameter definition !< - function get_param_definition_type(input_definition_types, component_type, & - subcomponent_type, blockname, tagname) & + function get_param_definition_type(input_definition_types, & + component_type, subcomponent_type, & + blockname, tagname, filename) & result(idt) type(InputParamDefinitionType), dimension(:), intent(in), target :: & input_definition_types @@ -32,6 +33,7 @@ function get_param_definition_type(input_definition_types, component_type, & character(len=*), intent(in) :: subcomponent_type !< subcomponent type, such as DIS or NPF character(len=*), intent(in) :: blockname !< name of the block character(len=*), intent(in) :: tagname !< name of the input tag + character(len=*), intent(in) :: filename !< input filename type(InputParamDefinitionType), pointer :: idt !< corresponding InputParameterDefinitionType for this tag type(InputParamDefinitionType), pointer :: tmp_ptr integer(I4B) :: i @@ -49,11 +51,12 @@ function get_param_definition_type(input_definition_types, component_type, & end do ! if (.not. associated(idt)) then - write (errmsg, '(1x,a,a,a,a,a,a,a)') & - 'Idm parameter definition not found: ', trim(tagname), & - '. Component="', trim(component_type), & - '", subcomponent="', trim(subcomponent_type), '".' - call store_error(errmsg, .true.) + write (errmsg, '(1x,a,a,a,a,a)') & + 'Input file tag not found: "', trim(tagname), & + '" in block "', trim(blockname), & + '".' + call store_error(errmsg) + call store_error_filename(filename) end if ! ! -- return diff --git a/src/Utilities/Idm/mf6blockfile/LoadMf6File.f90 b/src/Utilities/Idm/mf6blockfile/LoadMf6File.f90 index 06597b1da20..9bce437132e 100644 --- a/src/Utilities/Idm/mf6blockfile/LoadMf6File.f90 +++ b/src/Utilities/Idm/mf6blockfile/LoadMf6File.f90 @@ -59,6 +59,7 @@ subroutine idm_load(parser, pkgtype, & type(ModflowInputType) :: mf6_input !< ModflowInputType character(len=LENMEMPATH) :: componentMemPath integer(I4B), dimension(:), contiguous, pointer :: mshape => null() + character(len=LINELENGTH) :: filename !< input filename ! ! -- construct input object mf6_input = getModflowInput(pkgtype, component_type, & @@ -69,13 +70,16 @@ subroutine idm_load(parser, pkgtype, & componentMemPath = create_mem_path(component=mf6_input%component_name, & context=idm_context) ! + ! -- set filename + inquire (unit=parser%GetUnit(), name=filename) + ! ! -- log lst file header call idm_log_header(mf6_input%component_name, & mf6_input%subcomponent_name, iout) ! ! -- process blocks do iblock = 1, size(mf6_input%block_dfns) - call parse_block(parser, mf6_input, iblock, mshape, iout, .false.) + call parse_block(parser, mf6_input, iblock, mshape, filename, iout, .false.) ! ! -- set model shape if discretization dimensions have been read if (mf6_input%block_dfns(iblock)%blockname == 'DIMENSIONS' .and. & @@ -97,14 +101,15 @@ end subroutine idm_load !! calls for blocks that may appear multiple times in an input file. !! !< - recursive subroutine parse_block(parser, mf6_input, iblock, mshape, iout, & - recursive_call) + recursive subroutine parse_block(parser, mf6_input, iblock, mshape, filename, & + iout, recursive_call) use MemoryTypeModule, only: MemoryType use MemoryManagerModule, only: get_from_memorylist type(BlockParserType), intent(inout) :: parser !< block parser type(ModflowInputType), intent(in) :: mf6_input !< ModflowInputType integer(I4B), intent(in) :: iblock !< consecutive block number as defined in definition file integer(I4B), dimension(:), contiguous, pointer, intent(inout) :: mshape !< model shape + character(len=*), intent(in) :: filename !< input filename integer(I4B), intent(in) :: iout !< unit number for output logical(LGP), intent(in) :: recursive_call !< true if recursive call logical(LGP) :: isblockfound @@ -137,7 +142,8 @@ recursive subroutine parse_block(parser, mf6_input, iblock, mshape, iout, & if (mf6_input%block_dfns(iblock)%aggregate) then ! ! -- process block recarray type, set of variable 1d/2d types - call parse_structarray_block(parser, mf6_input, iblock, mshape, iout) + call parse_structarray_block(parser, mf6_input, iblock, mshape, & + filename, iout) else do ! process each line in block @@ -145,7 +151,8 @@ recursive subroutine parse_block(parser, mf6_input, iblock, mshape, iout, & if (endOfBlock) exit ! ! -- process line as tag(s) - call parse_tag(parser, mf6_input, iblock, mshape, iout, .false.) + call parse_tag(parser, mf6_input, iblock, mshape, filename, iout, & + .false.) end do end if end if @@ -153,7 +160,8 @@ recursive subroutine parse_block(parser, mf6_input, iblock, mshape, iout, & ! -- recurse if block is reloadable and was just read if (mf6_input%block_dfns(iblock)%block_variable) then if (isblockfound) then - call parse_block(parser, mf6_input, iblock, mshape, iout, .true.) + call parse_block(parser, mf6_input, iblock, mshape, filename, iout, & + .true.) end if end if ! @@ -162,7 +170,7 @@ recursive subroutine parse_block(parser, mf6_input, iblock, mshape, iout, & end subroutine parse_block subroutine parse_iofile_tag(parser, mf6_input, iblock, mshape, tag, found, & - iout) + filename, iout) use DefinitionSelectModule, only: split_record_definition type(BlockParserType), intent(inout) :: parser !< block parser type(ModflowInputType), intent(in) :: mf6_input !< ModflowInputType @@ -170,6 +178,7 @@ subroutine parse_iofile_tag(parser, mf6_input, iblock, mshape, tag, found, & integer(I4B), dimension(:), contiguous, pointer, intent(inout) :: mshape !< model shape character(len=LINELENGTH), intent(in) :: tag logical(LGP), intent(inout) :: found !< file tag was identified and loaded + character(len=*), intent(in) :: filename !< input filename integer(I4B), intent(in) :: iout !< unit number for output type(InputParamDefinitionType), pointer :: idt !< input data type object describing this record character(len=40), dimension(:), allocatable :: words @@ -208,7 +217,7 @@ subroutine parse_iofile_tag(parser, mf6_input, iblock, mshape, tag, found, & mf6_input%component_type, & mf6_input%subcomponent_type, & mf6_input%block_dfns(iblock)%blockname, & - words(4)) + words(4), filename) call load_string_type(parser, idt, mf6_input%mempath, iout) ! ! -- io tag loaded @@ -228,12 +237,13 @@ end subroutine parse_iofile_tag !! tags are on a single line. !! !< - recursive subroutine parse_tag(parser, mf6_input, iblock, mshape, iout, & - recursive_call) + recursive subroutine parse_tag(parser, mf6_input, iblock, mshape, filename, & + iout, recursive_call) type(BlockParserType), intent(inout) :: parser !< block parser type(ModflowInputType), intent(in) :: mf6_input !< ModflowInputType integer(I4B), intent(in) :: iblock !< consecutive block number as defined in definition file integer(I4B), dimension(:), contiguous, pointer, intent(inout) :: mshape !< model shape + character(len=*), intent(in) :: filename !< input filename integer(I4B), intent(in) :: iout !< unit number for output logical(LGP), intent(in) :: recursive_call !< true if recursive call character(len=LINELENGTH) :: tag @@ -254,7 +264,7 @@ recursive subroutine parse_tag(parser, mf6_input, iblock, mshape, iout, & mf6_input%component_type, & mf6_input%subcomponent_type, & mf6_input%block_dfns(iblock)%blockname, & - tag) + tag, filename) ! ! -- allocate and load data type select case (idt%datatype) @@ -268,7 +278,7 @@ recursive subroutine parse_tag(parser, mf6_input, iblock, mshape, iout, & ! ! -- identify and load the file name call parse_iofile_tag(parser, mf6_input, iblock, mshape, tag, & - found_io_tag, iout) + found_io_tag, filename, iout) end if ! if (.not. found_io_tag) then @@ -309,7 +319,7 @@ recursive subroutine parse_tag(parser, mf6_input, iblock, mshape, iout, & ! -- continue line if in same record if (idt%in_record) then ! recursively call parse tag again to read rest of line - call parse_tag(parser, mf6_input, iblock, mshape, iout, .true.) + call parse_tag(parser, mf6_input, iblock, mshape, filename, iout, .true.) end if ! ! -- @@ -324,13 +334,15 @@ end subroutine parse_tag !! vector. !! !< - subroutine parse_structarray_block(parser, mf6_input, iblock, mshape, iout) + subroutine parse_structarray_block(parser, mf6_input, iblock, mshape, & + filename, iout) use StructArrayModule, only: StructArrayType, constructStructArray, & destructStructArray type(BlockParserType), intent(inout) :: parser !< block parser type(ModflowInputType), intent(in) :: mf6_input !< ModflowInputType integer(I4B), intent(in) :: iblock !< consecutive block number as defined in definition file integer(I4B), dimension(:), contiguous, pointer, intent(inout) :: mshape !< model shape + character(len=*), intent(in) :: filename !< input filename integer(I4B), intent(in) :: iout !< unit number for output type(InputParamDefinitionType), pointer :: idt !< input data type object describing this record integer(I4B) :: blocknum, iwords, ilen @@ -414,7 +426,7 @@ subroutine parse_structarray_block(parser, mf6_input, iblock, mshape, iout) mf6_input%component_type, & mf6_input%subcomponent_type, & mf6_input%block_dfns(iblock)%blockname, & - words(iwords)) + words(iwords), filename) ! ! -- allocate variable in memory manager call struct_array%mem_create_vector(icol, idt%datatype, idt%mf6varname, & diff --git a/src/Utilities/Idm/selector/IdmDfnSelector.f90 b/src/Utilities/Idm/selector/IdmDfnSelector.f90 index ee3089c65a3..a3e13b0ead0 100644 --- a/src/Utilities/Idm/selector/IdmDfnSelector.f90 +++ b/src/Utilities/Idm/selector/IdmDfnSelector.f90 @@ -44,11 +44,6 @@ function param_definitions(component, subcomponent) result(input_definition) input_definition => sim_param_definitions(subcomponent) case default end select - if (.not. associated(input_definition)) then - call store_error('Idm param input definition list not found; '//& - &'component="'//trim(component)//& - &'", subcomponent="'//trim(subcomponent)//'".', .true.) - end if return end function param_definitions @@ -66,11 +61,6 @@ function aggregate_definitions(component, subcomponent) result(input_definition) input_definition => sim_aggregate_definitions(subcomponent) case default end select - if (.not. associated(input_definition)) then - call store_error('Idm aggregate input definition list not found; '//& - &'component="'//trim(component)//& - &'", subcomponent="'//trim(subcomponent)//'".', .true.) - end if return end function aggregate_definitions @@ -88,11 +78,6 @@ function block_definitions(component, subcomponent) result(input_definition) input_definition => sim_block_definitions(subcomponent) case default end select - if (.not. associated(input_definition)) then - call store_error('Idm block input definition list not found; '//& - &'component="'//trim(component)//& - &'", subcomponent="'//trim(subcomponent)//'".', .true.) - end if return end function block_definitions diff --git a/src/Utilities/ListReader.f90 b/src/Utilities/ListReader.f90 index 0f1d52110ff..7ae360a1f3b 100644 --- a/src/Utilities/ListReader.f90 +++ b/src/Utilities/ListReader.f90 @@ -396,6 +396,7 @@ subroutine read_ascii(this) use ConstantsModule, only: LENBOUNDNAME, LINELENGTH, DZERO use InputOutputModule, only: u9rdcom, urword, get_node use ArrayHandlersModule, only: ExpandArray + use TdisModule, only: kper ! -- dummy class(ListReaderType) :: this ! -- local @@ -537,6 +538,13 @@ subroutine read_ascii(this) this%txtauxvar(this%ntxtauxvar) = this%line(this%istart:this%istop) this%idxtxtauxrow(this%ntxtauxvar) = ii this%idxtxtauxcol(this%ntxtauxvar) = jj + if (len_trim(this%txtauxvar(this%ntxtauxvar)) == 0) then + write (errmsg, '(a,i0,a)') 'Auxiliary data or time series name & + &expected but not found in period & + &block "', kper, '".' + call store_error(errmsg) + call store_error_unit(this%inlist) + end if end if ! end do diff --git a/utils/idmloader/scripts/dfn2f90.py b/utils/idmloader/scripts/dfn2f90.py index cd375a5c24d..f96ffe0f52d 100644 --- a/utils/idmloader/scripts/dfn2f90.py +++ b/utils/idmloader/scripts/dfn2f90.py @@ -728,13 +728,6 @@ def _write_master_defn(self, fh=None, defn=None, dtype=None): s += ( f" case default\n" f" end select\n" - f" if (.not. associated(input_definition)) then\n" - f" call store_error('Idm {defn.lower()} input definition " - f"list not found; '//&\n" - f" &'component=\"'//trim(component)//&\n" - f" &'\", subcomponent=\"'//trim(subcomponent)" - f"//'\".', .true.)\n" - f" end if\n" f" return\n" f" end function {defn.lower()}_definitions\n\n" ) From 961f3c30ce67ae89754ba0f599b934e554400562 Mon Sep 17 00:00:00 2001 From: jdhughes-usgs Date: Mon, 17 Apr 2023 08:15:11 -0500 Subject: [PATCH 071/123] refactor(PTC): fix residual calculation used in calculate PTC timestep (#1205) * refactor(PTC): fix residual calculation used in calculate PTC timestep Also simplified PTC psuedo timestep calculation to eliminate need to evaluate the maximum and minimum non-zero diagonal entry. Also eliminated evaluation of the PTC ratio in the NumericalSolution. Elimination of the evaluation of diagonal entries is in preparation for parallel implementation of PTC. * Trigger notification --- autotest/test_gwf_ptc01.py | 142 ++++++++++++++-------------- autotest/test_z01_testmodels_mf6.py | 1 + src/Model/GroundWaterFlow/gwf3.f90 | 38 ++------ src/Solution/NumericalSolution.f90 | 57 +---------- 4 files changed, 85 insertions(+), 153 deletions(-) diff --git a/autotest/test_gwf_ptc01.py b/autotest/test_gwf_ptc01.py index 992deb59313..cf1737c3020 100644 --- a/autotest/test_gwf_ptc01.py +++ b/autotest/test_gwf_ptc01.py @@ -1,3 +1,10 @@ +""" +This test confirms that there is no difference in +steady-state Newton-Raphson simulations with PTC +if a storage package is included in the model +name file. +""" + import os import flopy @@ -8,38 +15,48 @@ from simulation import TestSimulation ex = ["ptc01"] -data_path = project_root_path / "autotest" / "data" -fpth = str(data_path / "nwtp03_bot.ref") -botm = np.loadtxt(fpth, dtype=float) -nlay = 1 -nrow, ncol = botm.shape -top = 200 -laytyp = 1 -hk = 1.0 +# static model data +# temporal discretization +nper = 1 +tdis_rc = [(1.0, 1, 1.0)] + +# spatial discretization data +nlay, nrow, ncol = 1, 1, 100 +shape3d = (nlay, nrow, ncol) +size3d = nlay * nrow * ncol +delr, delc = 50.0, 1.0 +top = 25.0 +botm = 0.0 +strt = 0.0 + +# hydraulic properties +hk = 50.0 + +# all cells are active and layer 1 is convertible +ib = 1 + +# solver options +nouter, ninner = 500, 300 +hclose, rclose, relax = 1e-9, 1e-6, 1.0 +newtonoptions = "NEWTON" +imsla = "BICGSTAB" + +# chd data +c6 = [] +ccol = [0, ncol - 1] +hc = [20.0, 11.0] ss = 1e-5 -sy = 0.1 -delr = delc = 100.0 -chdloc = [(0, 49, 79), (0, 50, 79), (0, 51, 79)] -chd = 24.0 -strt = botm + 20.0 +sy = 0.2 +for j, h in zip(ccol, hc): + c6.append([(0, 0, j), h]) +cd6 = {0: c6} +maxchd = len(cd6[0]) -# read recharge data -fpth = str(data_path / "nwtp03_rch.ref") -rch = np.loadtxt(fpth, dtype=float) +# recharge data +rech = {0: 0.001} def build_mf6(idx, ws, storage=True): - c6 = [] - for loc in chdloc: - c6.append([loc, chd]) - cd6 = {0: c6} - - nouter, ninner = 100, 300 - hclose, rclose, relax = 1e-6, 0.01, 1.0 - - nper = 1 - tdis_rc = [(1.0, 1, 1.0)] - name = ex[idx] # build MODFLOW 6 files @@ -47,47 +64,31 @@ def build_mf6(idx, ws, storage=True): sim_name=name, version="mf6", exe_name="mf6", sim_ws=ws ) # create tdis package - flopy.mf6.ModflowTdis( + tdis = flopy.mf6.ModflowTdis( sim, time_units="DAYS", nper=nper, perioddata=tdis_rc ) - - # create gwf model - gwf = flopy.mf6.ModflowGwf( - sim, - modelname=name, - model_nam_file=f"{name}.nam", - save_flows=True, - newtonoptions="NEWTON", - ) - # create iterative model solution and register the gwf model with it ims = flopy.mf6.ModflowIms( sim, print_option="SUMMARY", - outer_dvclose=1e-3, - outer_maximum=1500, - under_relaxation="dbd", - under_relaxation_theta=0.9, - under_relaxation_kappa=0.0, - under_relaxation_gamma=0.0, - under_relaxation_momentum=0.0, - backtracking_number=20, - backtracking_tolerance=2.0, - backtracking_reduction_factor=0.6, - backtracking_residual_limit=1.0, - inner_maximum=200, - inner_dvclose=1e-6, - rcloserecord="0. RELATIVE_RCLOSE", - linear_acceleration="BICGSTAB", + outer_dvclose=hclose, + outer_maximum=nouter, + under_relaxation="NONE", + inner_maximum=ninner, + inner_dvclose=hclose, + rcloserecord=rclose, + linear_acceleration=imsla, scaling_method="NONE", reordering_method="NONE", - preconditioner_levels=5, - number_orthogonalizations=7, - preconditioner_drop_tolerance=1e-4, + relaxation_factor=relax, + ) + + # create gwf model + gwf = flopy.mf6.ModflowGwf( + sim, modelname=name, newtonoptions=newtonoptions, save_flows=True ) - sim.register_ims_package(ims, [gwf.name]) - flopy.mf6.ModflowGwfdis( + dis = flopy.mf6.ModflowGwfdis( gwf, nlay=nlay, nrow=nrow, @@ -96,15 +97,13 @@ def build_mf6(idx, ws, storage=True): delc=delc, top=top, botm=botm, - idomain=1, - filename=f"{name}.dis", ) # initial conditions - flopy.mf6.ModflowGwfic(gwf, strt=strt, filename=f"{name}.ic") + ic = flopy.mf6.ModflowGwfic(gwf, strt=strt) # node property flow - flopy.mf6.ModflowGwfnpf(gwf, icelltype=1, k=hk) + npf = flopy.mf6.ModflowGwfnpf(gwf, save_flows=False, icelltype=1, k=hk) # storage if storage: @@ -112,22 +111,24 @@ def build_mf6(idx, ws, storage=True): gwf, iconvert=1, ss=ss, sy=sy, steady_state={0: True} ) - # chd files - flopy.mf6.modflow.ModflowGwfchd(gwf, stress_period_data=cd6) + # recharge + rch = flopy.mf6.ModflowGwfrcha(gwf, readasarrays=True, recharge=rech) - # rch files - flopy.mf6.modflow.ModflowGwfrcha(gwf, recharge={0: rch}) + # chd files + chd = flopy.mf6.modflow.mfgwfchd.ModflowGwfchd( + gwf, maxbound=maxchd, stress_period_data=cd6, save_flows=False + ) # output control - flopy.mf6.ModflowGwfoc( + oc = flopy.mf6.ModflowGwfoc( gwf, budget_filerecord=f"{name}.cbc", head_filerecord=f"{name}.hds", headprintrecord=[("COLUMNS", 10, "WIDTH", 15, "DIGITS", 6, "GENERAL")], - saverecord=[("HEAD", "LAST")], - printrecord=[("HEAD", "LAST"), ("BUDGET", "LAST")], + saverecord=[("HEAD", "ALL"), ("BUDGET", "ALL")], + printrecord=[("HEAD", "LAST"), ("BUDGET", "ALL")], ) - + return sim @@ -143,7 +144,6 @@ def build_model(idx, dir): return sim, mc -@pytest.mark.slow @pytest.mark.parametrize( "idx, name", list(enumerate(ex)), diff --git a/autotest/test_z01_testmodels_mf6.py b/autotest/test_z01_testmodels_mf6.py index b349598c027..aa73294349e 100644 --- a/autotest/test_z01_testmodels_mf6.py +++ b/autotest/test_z01_testmodels_mf6.py @@ -5,6 +5,7 @@ excluded_models = ["alt_model", "test205_gwtbuy-henrytidal"] excluded_comparisons = { "test001e_noUZF_3lay": ["6.2.1",], + "test001f_hfb-xt3d-nwt": ["6.4.1",], "test005_advgw_tidal": ["6.2.1",], "test017_Crinkle": ["6.2.1",], "test028_sfr": ["6.2.1",], diff --git a/src/Model/GroundWaterFlow/gwf3.f90 b/src/Model/GroundWaterFlow/gwf3.f90 index 9e37a1c7575..afdde28cbc7 100644 --- a/src/Model/GroundWaterFlow/gwf3.f90 +++ b/src/Model/GroundWaterFlow/gwf3.f90 @@ -635,6 +635,7 @@ subroutine gwf_ptc(this, matrix, & vec_x, vec_rhs, iptc, ptcf) ! modules use ConstantsModule, only: DONE, DP9 + use TdisModule, only: DELT ! -- dummy class(GwfModelType) :: this class(MatrixBaseType), pointer :: matrix @@ -657,11 +658,8 @@ subroutine gwf_ptc(this, matrix, & real(DP), contiguous, dimension(:), pointer :: x real(DP), contiguous, dimension(:), pointer :: rhs real(DP) :: ptcdelem1 - real(DP) :: diag - real(DP) :: diagcnt - real(DP) :: diagmin - real(DP) :: diagmax - integer(I4B) :: first_col, last_col + integer(I4B) :: first_col + integer(I4B) :: last_col ! ------------------------------------------------------------------------------ ! ! set pointers to vec_x and vec_rhs @@ -699,7 +697,7 @@ subroutine gwf_ptc(this, matrix, & jcol = matrix%get_column(j) jcol_loc = jcol - matrix_offset if (jcol_loc < 1 .or. jcol_loc > size(x)) cycle ! temporary protection for parallel case - resid = resid + matrix%get_value_pos(j) * x(jrow_loc) + resid = resid + matrix%get_value_pos(j) * x(jcol_loc) end do ! subtract the right-hand side @@ -708,11 +706,8 @@ subroutine gwf_ptc(this, matrix, & resid_vec(n) = resid end do ! - ! calculate the pseudo-time step with constraints - ! using the calculated residual - diagmin = DEP20 - diagmax = DZERO - diagcnt = DZERO + ! calculate the pseudo-time step using the + ! calculated residual do n = 1, this%dis%nodes if (this%npf%ibound(n) < 1) cycle ! @@ -730,26 +725,11 @@ subroutine gwf_ptc(this, matrix, & ! exceeds the current value (equivalent to using the ! smallest pseudo-time step) if (ptcdelem1 > ptcf) ptcf = ptcdelem1 - ! - ! -- determine minimum and maximum diagonal entries - jrow = n + this%moffset - diag = abs(matrix%get_diag_value(jrow)) - diagcnt = diagcnt + DONE - if (diag > DZERO) then - if (diag < diagmin) diagmin = diag - if (diag > diagmax) diagmax = diag - end if end do ! - ! -- set the reciprocal of the pseudo-time step - ! to a fraction of the minimum or maximum - ! diagonal entry to prevent excessively small - ! or large values - if (diagcnt > DZERO) then - diagmin = diagmin * DEM1 - diagmax = diagmax * DEM1 - if (ptcf < diagmin) ptcf = diagmin - if (ptcf > diagmax) ptcf = diagmax + ! protection for the case where the residuals are zero + if (ptcf == DZERO) then + ptcf = DONE / (DELT * DTEN) end if end if diff --git a/src/Solution/NumericalSolution.f90 b/src/Solution/NumericalSolution.f90 index db581aa2861..d4258dd5c78 100644 --- a/src/Solution/NumericalSolution.f90 +++ b/src/Solution/NumericalSolution.f90 @@ -124,8 +124,6 @@ module NumericalSolutionModule real(DP), pointer :: ptcdel => null() !< PTC delta value real(DP), pointer :: ptcdel0 => null() !< initial PTC delta value real(DP), pointer :: ptcexp => null() !< PTC exponent - real(DP), pointer :: ptcthresh => null() !< PTC threshold value (0.001) - real(DP), pointer :: ptcrat => null() !< ratio of the PTC value and the minimum of the diagonal of AMAT used to determine if the PTC effect has decayed ! ! -- adaptive time step real(DP), pointer :: atsfrac => null() !< adaptive time step faction @@ -312,8 +310,6 @@ subroutine allocate_scalars(this) call mem_allocate(this%ptcdel, 'PTCDEL', this%memoryPath) call mem_allocate(this%ptcdel0, 'PTCDEL0', this%memoryPath) call mem_allocate(this%ptcexp, 'PTCEXP', this%memoryPath) - call mem_allocate(this%ptcthresh, 'PTCTHRESH', this%memoryPath) - call mem_allocate(this%ptcrat, 'PTCRAT', this%memoryPath) call mem_allocate(this%atsfrac, 'ATSFRAC', this%memoryPath) ! ! -- initialize scalars @@ -356,8 +352,6 @@ subroutine allocate_scalars(this) this%ptcdel = DZERO this%ptcdel0 = DZERO this%ptcexp = done - this%ptcthresh = DEM3 - this%ptcrat = DZERO this%atsfrac = DONETHIRD ! ! -- return @@ -726,18 +720,6 @@ subroutine sln_ar(this) write (IOUT, '(1x,A,1x,g15.7)') & 'PSEUDO-TRANSIENT CONTINUATION EXPONENT', this%ptcexp end if - case ('DEV_PTC_THRESHOLD') - call this%parser%DevOpt() - rval = this%parser%GetDouble() - if (rval < DZERO) then - write (errmsg, '(a)') 'PTC_THRESHOLD MUST BE > 0.' - call store_error(errmsg) - else - this%iallowptc = 1 - this%ptcthresh = rval - write (IOUT, '(1x,A,1x,g15.7)') & - 'PSEUDO-TRANSIENT CONTINUATION THRESHOLD', this%ptcthresh - end if case ('DEV_PTC_DEL0') call this%parser%DevOpt() rval = this%parser%GetDouble() @@ -1301,8 +1283,6 @@ subroutine sln_da(this) call mem_deallocate(this%ptcdel) call mem_deallocate(this%ptcdel0) call mem_deallocate(this%ptcexp) - call mem_deallocate(this%ptcthresh) - call mem_deallocate(this%ptcrat) call mem_deallocate(this%atsfrac) ! ! -- return @@ -1668,23 +1648,6 @@ subroutine solve(this, kiter) iend = 1 end if ! - ! -- Additional convergence check for pseudo-transient continuation - ! term. Evaluate if the ptc value added to the diagonal has - ! decayed sufficiently. - if (iptc > 0) then - if (this%icnvg /= 0) then - if (this%ptcrat > this%ptcthresh) then - this%icnvg = 0 - cmsg = trim(cmsg)//'PTC' - if (iend /= 0) then - write (line, '(a)') & - 'PSEUDO-TRANSIENT CONTINUATION CAUSED CONVERGENCE FAILURE' - call sim_message(line) - end if - end if - end if - end if - ! ! -- write maximum dependent-variable change from linear solver to list file if (this%iprims > 0) then cval = 'Model' @@ -2435,7 +2398,6 @@ subroutine sln_ls(this, kiter, kstp, kper, in_iter, iptc, ptcf) real(DP) :: diagval real(DP) :: l2norm real(DP) :: ptcval - real(DP) :: diagmin real(DP) :: bnorm character(len=50) :: fname character(len=*), parameter :: fmtfname = "('mf6mat_', i0, '_', i0, & @@ -2533,10 +2495,9 @@ subroutine sln_ls(this, kiter, kstp, kper, in_iter, iptc, ptcf) if (iptct /= 0) then if (kiter == 1) then if (this%iptcout > 0) then - write (this%iptcout, '(A10,6(1x,A15),2(1x,A15))') 'OUTER ITER', & + write (this%iptcout, '(A10,6(1x,A15))') 'OUTER ITER', & ' PTCDEL', ' L2NORM0', ' L2NORM', & - ' RHSNORM', ' 1/PTCDEL', ' DIAGONAL MIN.', & - ' RHSNORM/L2NORM', ' STOPPING CRIT.' + ' RHSNORM', ' 1/PTCDEL', ' RHSNORM/L2NORM' end if if (this%ptcdel0 > DZERO) then this%ptcdel = this%ptcdel0 @@ -2568,23 +2529,21 @@ subroutine sln_ls(this, kiter, kstp, kper, in_iter, iptc, ptcf) else ptcval = DONE end if - diagmin = DEP20 bnorm = DZERO do ieq = 1, this%neq irow = ieq + this%matrix_offset if (this%active(ieq) > 0) then diagval = abs(this%system_matrix%get_diag_value(irow)) bnorm = bnorm + this%rhs(ieq) * this%rhs(ieq) - if (diagval < diagmin) diagmin = diagval call this%system_matrix%add_diag_value(irow, -ptcval) this%rhs(ieq) = this%rhs(ieq) - ptcval * this%x(ieq) end if end do bnorm = sqrt(bnorm) if (this%iptcout > 0) then - write (this%iptcout, '(i10,6(1x,e15.7),2(1x,f15.6))') & + write (this%iptcout, '(i10,5(1x,e15.7),1(1x,f15.6))') & kiter, this%ptcdel, this%l2norm0, l2norm, bnorm, & - ptcval, diagmin, bnorm / l2norm, ptcval / diagmin + ptcval, bnorm / l2norm end if this%l2norm0 = l2norm end if @@ -2633,14 +2592,6 @@ subroutine sln_ls(this, kiter, kstp, kper, in_iter, iptc, ptcf) this%icnvg = this%linear_solver%is_converged end if ! - ! -- ptc finalize - set ratio of ptc value added to the diagonal and the - ! minimum value on the diagonal. This value will be used - ! to determine if the make sure the ptc value has decayed - ! sufficiently - if (iptct /= 0) then - this%ptcrat = ptcval / diagmin - end if - ! ! -- return return end subroutine sln_ls From c79943ac05a75dde20e844e9728049d97f28d4c6 Mon Sep 17 00:00:00 2001 From: mjr-deltares <45555666+mjr-deltares@users.noreply.github.com> Date: Tue, 18 Apr 2023 18:44:15 +0200 Subject: [PATCH 072/123] rfct(par): add parallel ptc (#1206) * - add calculation of (global) residual to solution instead of model ptc - extend matrix and vector functionalities to do so * - implement l2norm through matrix and vector operations * - implement multiply... * - restyled interface model rewet test to explicitly test drying and wetting at the interface * - add idomain tests for interface model and parallel * - implementation of parallel PTC * - add tests for newton + PTC for interface model + parallel --- autotest/test_gwf_ifmod_idomain.py | 390 +++++++++++++++++ autotest/test_gwf_ifmod_newton.py | 395 ++++++++++++++++++ autotest/test_gwf_ifmod_rewet.py | 74 ++-- autotest/test_gwf_ifmod_vert.py | 2 +- autotest/test_par_gwf_idomain.py | 56 +++ autotest/test_par_gwf_newton.py | 58 +++ src/Model/GroundWaterFlow/gwf3.f90 | 68 +-- src/Model/NumericalModel.f90 | 6 +- .../LinearMethods/ImsLinearSolver.f90 | 10 - src/Solution/LinearSolverBase.f90 | 9 - src/Solution/NumericalSolution.f90 | 129 +++--- src/Solution/PETSc/PetscSolver.F90 | 54 --- src/Solution/ParallelSolution.f90 | 37 +- src/Utilities/Matrix/MatrixBase.f90 | 20 +- src/Utilities/Matrix/PetscMatrix.F90 | 50 ++- src/Utilities/Matrix/SparseMatrix.f90 | 46 +- src/Utilities/Vector/PetscVector.F90 | 98 ++++- src/Utilities/Vector/SeqVector.f90 | 88 +++- src/Utilities/Vector/VectorBase.f90 | 40 +- 19 files changed, 1380 insertions(+), 250 deletions(-) create mode 100644 autotest/test_gwf_ifmod_idomain.py create mode 100644 autotest/test_gwf_ifmod_newton.py create mode 100644 autotest/test_par_gwf_idomain.py create mode 100644 autotest/test_par_gwf_newton.py diff --git a/autotest/test_gwf_ifmod_idomain.py b/autotest/test_gwf_ifmod_idomain.py new file mode 100644 index 00000000000..28c6eb3c113 --- /dev/null +++ b/autotest/test_gwf_ifmod_idomain.py @@ -0,0 +1,390 @@ +import os + +import flopy +import numpy as np +import pytest +from framework import TestFramework +from simulation import TestSimulation + +# General test for the interface model approach. +# It compares the result of a single reference model +# to the equivalent case where the domain is decomposed +# and joined by a GWF-GWF exchange. +# +# In this case we test the use of idomain at the interface +# +# 'refmodel' 'leftmodel' 'rightmodel' +# +# layer 1: 1 1 1 1 0 0 1 1 1 1 1 1 1 1 0 0 1 1 1 1 +# layer 2: 1 1 1 1 1 1 1 1 1 1 VS 1 1 1 1 1 + 1 1 1 1 1 +# layer 3: 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 +# +# We assert equality on the head values. All models are part of a single +# solution for convenience. Finally, the budget error is checked. + +ex = ["ifmod_ibound"] + +# some global convenience...: +# model names +mname_ref = "refmodel" +mname_left = "leftmodel" +mname_right = "rightmodel" + +# solver criterion +hclose_check = 1e-9 +max_inner_it = 300 +nper = 1 + +# model spatial discretization +nlay = 3 +ncol = 10 +ncol_left = 5 +ncol_right = 5 +nrow = 1 + +# idomain +idomain = np.ones((nlay, nrow, ncol)) +idomain[0, 0, 4] = 0 +idomain[0, 0, 5] = 0 +idomain_left = np.ones((nlay, nrow, ncol_left)) +idomain_left[0, 0, 4] = 0 +idomain_right = np.ones((nlay, nrow, ncol_right)) +idomain_right[0, 0, 0] = 0 + +lenx = 15.0 * 500.0 +leny = 10.0 * 500.0 +delr = lenx / float(ncol) +delc = leny / float(nrow) +area = delr * delc + +# shift (hor. and vert.) +shift_some_x = -25 * delr # avoids overlap +shift_x = ncol_left * delr +shift_y = 0.0 + +# top/bot of the aquifer +tops = [150.0, 50.0, 0.0, -50.0] + +# hydraulic conductivity +hk = 10.0 + +# boundary stress period data for period 1 and 2 +h_left = 125.0 +h_right = 75.0 + +# initial head +h_start = 0.0 + + +# head boundaries +lchd = [ + [(ilay, irow, 0), h_left] + for ilay in range(nlay) + for irow in range(nrow) + if h_left > tops[ilay + 1] +] +rchd = [ + [(ilay, irow, ncol - 1), h_right] + for ilay in range(nlay) + for irow in range(nrow) + if h_right > tops[ilay + 1] +] +rchd_right = [ + [(ilay, irow, ncol_right - 1), h_right] + for ilay in range(nlay) + for irow in range(nrow) + if h_right > tops[ilay + 1] +] +chd = lchd + rchd + +chd_spd = {0: chd} +chd_spd_left = {0: lchd} +chd_spd_right = {0: rchd_right} + + +def get_model(idx, dir): + name = ex[idx] + + # parameters and spd + # tdis + tdis_rc = [] + for i in range(nper): + tdis_rc.append((1.0, 1, 1)) + + # solver data + nouter, ninner = 100, max_inner_it + hclose, rclose, relax = hclose_check, 1e-3, 0.97 + + sim = flopy.mf6.MFSimulation( + sim_name=name, version="mf6", exe_name="mf6", sim_ws=dir + ) + + tdis = flopy.mf6.ModflowTdis( + sim, time_units="DAYS", nper=nper, perioddata=tdis_rc + ) + + ims = flopy.mf6.ModflowIms( + sim, + print_option="SUMMARY", + outer_dvclose=hclose, + outer_maximum=nouter, + under_relaxation="NONE", + inner_maximum=ninner, + inner_dvclose=hclose, + rcloserecord=rclose, + linear_acceleration="CG", + scaling_method="NONE", + reordering_method="NONE", + relaxation_factor=relax, + filename="gwf.ims", + ) + + # the full gwf model as a reference + add_refmodel(sim) + + # now add two coupled models with the interface model enabled, + # to be stored in the same solution as the reference model + add_leftmodel(sim) + add_rightmodel(sim) + add_gwfexchange(sim) + + return sim + + +def add_refmodel(sim): + global mname_ref + global nlay, nrow, ncol + global idomain + global delr, delc + global shift_some_x + global h_start + global chd_spd + global tops + + gwf = flopy.mf6.ModflowGwf(sim, modelname=mname_ref, save_flows=True) + + dis = flopy.mf6.ModflowGwfdis( + gwf, + nlay=nlay, + nrow=nrow, + ncol=ncol, + delr=delr, + delc=delc, + xorigin=shift_some_x, + yorigin=0.0, + top=tops[0], + botm=tops[1:], + idomain=idomain, + ) + + # initial conditions + ic = flopy.mf6.ModflowGwfic(gwf, strt=h_start) + + # node property flow + npf = flopy.mf6.ModflowGwfnpf( + gwf, + save_specific_discharge=True, + icelltype=0, + k=hk, + ) + + # chd file + chd = flopy.mf6.ModflowGwfchd(gwf, stress_period_data=chd_spd) + + # output control + oc = flopy.mf6.ModflowGwfoc( + gwf, + head_filerecord=f"{mname_ref}.hds", + budget_filerecord=f"{mname_ref}.cbc", + headprintrecord=[("COLUMNS", 10, "WIDTH", 15, "DIGITS", 6, "GENERAL")], + saverecord=[("HEAD", "LAST"), ("BUDGET", "LAST")], + ) + + return gwf + + +def add_leftmodel(sim): + global mname_left + global nlay, nrow, ncol_left + global idomain_left + global delr, delc + global tops + global h_start + global h_left + global chd_spd_left + + gwf = flopy.mf6.ModflowGwf(sim, modelname=mname_left, save_flows=True) + dis = flopy.mf6.ModflowGwfdis( + gwf, + nlay=nlay, + nrow=nrow, + ncol=ncol_left, + delr=delr, + delc=delc, + top=tops[0], + botm=tops[1:], + idomain=idomain_left, + ) + ic = flopy.mf6.ModflowGwfic(gwf, strt=h_start) + npf = flopy.mf6.ModflowGwfnpf( + gwf, + save_specific_discharge=True, + save_flows=True, + icelltype=0, + k=hk, + ) + chd = flopy.mf6.ModflowGwfchd(gwf, stress_period_data=chd_spd_left) + oc = flopy.mf6.ModflowGwfoc( + gwf, + head_filerecord=f"{mname_left}.hds", + budget_filerecord=f"{mname_left}.cbc", + headprintrecord=[("COLUMNS", 10, "WIDTH", 15, "DIGITS", 6, "GENERAL")], + saverecord=[("HEAD", "LAST"), ("BUDGET", "LAST")], + ) + + return gwf + + +def add_rightmodel(sim): + global mname_right + global nlay, nrow, ncol_right + global idomain_right + global h_right + global delr, delc + global tops + global h_start + global shift_x, shift_y + global chd_spd_right + + gwf = flopy.mf6.ModflowGwf(sim, modelname=mname_right, save_flows=True) + dis = flopy.mf6.ModflowGwfdis( + gwf, + nlay=nlay, + nrow=nrow, + ncol=ncol_right, + delr=delr, + delc=delc, + xorigin=shift_x, + yorigin=shift_y, + top=tops[0], + botm=tops[1:], + idomain=idomain_right, + ) + ic = flopy.mf6.ModflowGwfic(gwf, strt=h_start) + npf = flopy.mf6.ModflowGwfnpf( + gwf, + save_specific_discharge=True, + save_flows=True, + icelltype=0, + k=hk + ) + chd = flopy.mf6.ModflowGwfchd(gwf, stress_period_data=chd_spd_right) + oc = flopy.mf6.ModflowGwfoc( + gwf, + head_filerecord=f"{mname_right}.hds", + budget_filerecord=f"{mname_right}.cbc", + headprintrecord=[("COLUMNS", 10, "WIDTH", 15, "DIGITS", 6, "GENERAL")], + saverecord=[("HEAD", "LAST"), ("BUDGET", "LAST")], + ) + + return gwf + + +def add_gwfexchange(sim): + global mname_left, mname_right + global nrow + global delc, delr + global ncol_left + global idomain_left, idomain_right + + angldegx = 0.0 + cdist = delr + gwfgwf_data = [ + [ + (ilay, irow, ncol_left - 1), + (ilay, irow, 0), + 1, + delr / 2.0, + delr / 2.0, + delc, + angldegx, + cdist, + ] + for ilay in range(nlay) + for irow in range(nrow) + if idomain_left[ilay, irow, ncol_left - 1] > 0 and + idomain_right[ilay, irow, 0] > 0 + ] + gwfgwf = flopy.mf6.ModflowGwfgwf( + sim, + exgtype="GWF6-GWF6", + nexg=len(gwfgwf_data), + exgmnamea=mname_left, + exgmnameb=mname_right, + exchangedata=gwfgwf_data, + auxiliary=["ANGLDEGX", "CDIST"], + dev_interfacemodel_on=True, + ) + + +def build_model(idx, exdir): + sim = get_model(idx, exdir) + return sim, None + + +def compare_to_ref(sim): + print("comparing heads to single model reference...") + + fpth = os.path.join(sim.simpath, f"{mname_ref}.hds") + hds = flopy.utils.HeadFile(fpth) + fpth = os.path.join(sim.simpath, f"{mname_left}.hds") + hds_l = flopy.utils.HeadFile(fpth) + fpth = os.path.join(sim.simpath, f"{mname_right}.hds") + hds_r = flopy.utils.HeadFile(fpth) + + times = hds.get_times() + for t in times: + heads = hds.get_data(totim=t) + heads_left = hds_l.get_data(totim=t) + heads_right = hds_r.get_data(totim=t) + heads_2models = np.append(heads_left, heads_right, axis=2) + + # check idomain was used + assert heads[0, 0, 4] == 1.0e+30, "idomain was set to 0 for this cell" + assert heads[0, 0, 5] == 1.0e+30, "idomain was set to 0 for this cell" + + # compare heads + maxdiff = np.amax(abs(heads - heads_2models)) + assert ( + maxdiff < 10 * hclose_check + ), "Max. head diff. {} should \ + be within solver tolerance (x10): {}".format( + maxdiff, 10 * hclose_check + ) + + # check budget error from .lst file + for mname in [mname_ref, mname_left, mname_right]: + fpth = os.path.join(sim.simpath, f"{mname}.lst") + for line in open(fpth): + if line.lstrip().startswith("PERCENT"): + cumul_balance_error = float(line.split()[3]) + assert ( + abs(cumul_balance_error) < 0.00001 + ), "Cumulative balance error = {} for {}, should equal 0.0".format( + cumul_balance_error, mname + ) + + +@pytest.mark.parametrize( + "idx, name", + list(enumerate(ex)), +) +def test_mf6model(idx, name, function_tmpdir, targets): + test = TestFramework() + test.build(build_model, idx, str(function_tmpdir)) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=compare_to_ref, idxsim=idx + ), + str(function_tmpdir), + ) diff --git a/autotest/test_gwf_ifmod_newton.py b/autotest/test_gwf_ifmod_newton.py new file mode 100644 index 00000000000..7cead3912d0 --- /dev/null +++ b/autotest/test_gwf_ifmod_newton.py @@ -0,0 +1,395 @@ +import os + +import flopy +import numpy as np +import pytest +from framework import TestFramework +from simulation import TestSimulation + +# General test for the interface model approach. +# It compares the result of a single reference model +# to the equivalent case where the domain is decomposed +# and joined by a GWF-GWF exchange. +# +# In this case we test newton option, which is also enabled in +# the interface model and should give identical results. +# +# period 1: The first stress period we start almost dry and have the +# model fill up. +# period 2: The BC on the left is lowered such that a part of the top +# layer is drained. +# +# 'refmodel' 'leftmodel' 'rightmodel' +# +# layer 1: 1 . . . . . . . 1 1 . . . . 1 1 . . 1 +# layer 2: 1 . . . . . . . 1 VS 1 . . . . 1 + 1 . . 1 +# layer 3: 1 . . . . . . . 1 1 . . . . 1 1 . . 1 +# +# We assert equality on the head values. All models are part of the same +# solution for convenience. Finally, the budget error is checked. + +ex = ["ifmod_newton01"] + +# some global convenience...: +# model names +mname_ref = "refmodel" +mname_left = "leftmodel" +mname_right = "rightmodel" + +# solver criterion +hclose_check = 1e-9 +max_inner_it = 300 +nper = 2 + +# model spatial discretization +nlay = 3 +ncol = 15 +ncol_left = 10 +ncol_right = 5 +nrow = 1 + +lenx = 15.0 * 500.0 +leny = 10.0 * 500.0 +delr = lenx / float(ncol) +delc = leny / float(nrow) +area = delr * delc + +# shift (hor. and vert.) +shift_some_x = -25 * delr # avoids overlap +shift_x = ncol_left * delr +shift_y = 0.0 + +# top/bot of the aquifer +tops = [150.0, 50.0, 0.0, -50.0] + +# hydraulic conductivity +hk = 10.0 + +# boundary stress period data for period 1 and 2 +h_left = [150.0, 20.0] +h_right = 60.0 + +# initial head +h_start = -40.0 + + +# head boundaries +lchd1 = [ + [(ilay, irow, 0), h_left[0]] + for ilay in range(nlay) + for irow in range(nrow) + if h_left[0] > tops[ilay + 1] +] +rchd = [ + [(ilay, irow, ncol - 1), h_right] + for ilay in range(nlay) + for irow in range(nrow) + if h_right > tops[ilay + 1] +] +rchd_right = [ + [(ilay, irow, ncol_right - 1), h_right] + for ilay in range(nlay) + for irow in range(nrow) + if h_right > tops[ilay + 1] +] +chd1 = lchd1 + rchd + +chd_spd = {0: chd1} +chd_spd_left = {0: lchd1} +chd_spd_right = {0: rchd_right} + +lchd2 = [ + [(ilay, irow, 0), h_left[1]] + for ilay in range(nlay) + for irow in range(nrow) + if h_left[1] > tops[ilay + 1] +] +chd_spd[1] = lchd2 + rchd +chd_spd_left[1] = lchd2 +chd_spd_right[1] = rchd_right + + +def get_model(idx, dir): + name = ex[idx] + + # parameters and spd + # tdis + tdis_rc = [] + for i in range(nper): + tdis_rc.append((1.0, 1, 1)) + + # solver data + nouter, ninner = 100, max_inner_it + hclose, rclose, relax = hclose_check, 1e-3, 0.97 + + sim = flopy.mf6.MFSimulation( + sim_name=name, version="mf6", exe_name="mf6", sim_ws=dir + ) + + tdis = flopy.mf6.ModflowTdis( + sim, time_units="DAYS", nper=nper, perioddata=tdis_rc + ) + + ims = flopy.mf6.ModflowIms( + sim, + print_option="SUMMARY", + complexity="COMPLEX", + inner_dvclose=hclose, + outer_dvclose=hclose, + ) + + # ims = flopy.mf6.ModflowIms( + # sim, + # print_option="SUMMARY", + # outer_dvclose=hclose, + # outer_maximum=nouter, + # under_relaxation="NONE", + # inner_maximum=ninner, + # inner_dvclose=hclose, + # rcloserecord=rclose, + # linear_acceleration="BICGSTAB", + # scaling_method="NONE", + # reordering_method="NONE", + # relaxation_factor=relax, + # filename="gwf.ims", + # ) + + # the full gwf model as a reference + add_refmodel(sim) + + # now add two coupled models with the interface model enabled, + # to be stored in the same solution as the reference model + add_leftmodel(sim) + add_rightmodel(sim) + add_gwfexchange(sim) + + return sim + + +def add_refmodel(sim): + global mname_ref + global nlay, nrow, ncol + global delr, delc + global shift_some_x + global h_start + global chd_spd + global tops + + gwf = flopy.mf6.ModflowGwf(sim, modelname=mname_ref, newtonoptions="NEWTON", + save_flows=True) + + dis = flopy.mf6.ModflowGwfdis( + gwf, + nlay=nlay, + nrow=nrow, + ncol=ncol, + delr=delr, + delc=delc, + xorigin=shift_some_x, + yorigin=0.0, + top=tops[0], + botm=tops[1:], + ) + + # initial conditions + ic = flopy.mf6.ModflowGwfic(gwf, strt=h_start) + + # node property flow + npf = flopy.mf6.ModflowGwfnpf( + gwf, + save_specific_discharge=True, + icelltype=1, + k=hk, + ) + + # chd file + chd = flopy.mf6.ModflowGwfchd(gwf, stress_period_data=chd_spd) + + # output control + oc = flopy.mf6.ModflowGwfoc( + gwf, + head_filerecord=f"{mname_ref}.hds", + budget_filerecord=f"{mname_ref}.cbc", + headprintrecord=[("COLUMNS", 10, "WIDTH", 15, "DIGITS", 6, "GENERAL")], + saverecord=[("HEAD", "LAST"), ("BUDGET", "LAST")], + ) + + return gwf + + +def add_leftmodel(sim): + global mname_left + global nlay, nrow, ncol_left + global delr, delc + global tops + global h_start + global h_left + global chd_spd_left + + gwf = flopy.mf6.ModflowGwf(sim, modelname=mname_left, newtonoptions="NEWTON", + save_flows=True) + dis = flopy.mf6.ModflowGwfdis( + gwf, + nlay=nlay, + nrow=nrow, + ncol=ncol_left, + delr=delr, + delc=delc, + top=tops[0], + botm=tops[1:], + ) + ic = flopy.mf6.ModflowGwfic(gwf, strt=h_start) + npf = flopy.mf6.ModflowGwfnpf( + gwf, + save_specific_discharge=True, + save_flows=True, + icelltype=1, + k=hk, + ) + chd = flopy.mf6.ModflowGwfchd(gwf, stress_period_data=chd_spd_left) + oc = flopy.mf6.ModflowGwfoc( + gwf, + head_filerecord=f"{mname_left}.hds", + budget_filerecord=f"{mname_left}.cbc", + headprintrecord=[("COLUMNS", 10, "WIDTH", 15, "DIGITS", 6, "GENERAL")], + saverecord=[("HEAD", "LAST"), ("BUDGET", "LAST")], + ) + + return gwf + + +def add_rightmodel(sim): + global mname_right + global nlay, nrow, ncol_right + global h_right + global delr, delc + global tops + global h_start + global shift_x, shift_y + global chd_spd_right + + gwf = flopy.mf6.ModflowGwf(sim, modelname=mname_right, newtonoptions="NEWTON", + save_flows=True) + dis = flopy.mf6.ModflowGwfdis( + gwf, + nlay=nlay, + nrow=nrow, + ncol=ncol_right, + delr=delr, + delc=delc, + xorigin=shift_x, + yorigin=shift_y, + top=tops[0], + botm=tops[1:], + ) + ic = flopy.mf6.ModflowGwfic(gwf, strt=h_start) + npf = flopy.mf6.ModflowGwfnpf( + gwf, + save_specific_discharge=True, + save_flows=True, + icelltype=1, + k=hk, + ) + chd = flopy.mf6.ModflowGwfchd(gwf, stress_period_data=chd_spd_right) + oc = flopy.mf6.ModflowGwfoc( + gwf, + head_filerecord=f"{mname_right}.hds", + budget_filerecord=f"{mname_right}.cbc", + headprintrecord=[("COLUMNS", 10, "WIDTH", 15, "DIGITS", 6, "GENERAL")], + saverecord=[("HEAD", "LAST"), ("BUDGET", "LAST")], + ) + + return gwf + + +def add_gwfexchange(sim): + global mname_left, mname_right + global nrow + global delc, delr + global ncol_left + + angldegx = 0.0 + cdist = delr + gwfgwf_data = [ + [ + (ilay, irow, ncol_left - 1), + (ilay, irow, 0), + 1, + delr / 2.0, + delr / 2.0, + delc, + angldegx, + cdist, + ] + for ilay in range(nlay) + for irow in range(nrow) + ] + gwfgwf = flopy.mf6.ModflowGwfgwf( + sim, + exgtype="GWF6-GWF6", + nexg=len(gwfgwf_data), + exgmnamea=mname_left, + exgmnameb=mname_right, + exchangedata=gwfgwf_data, + auxiliary=["ANGLDEGX", "CDIST"], + dev_interfacemodel_on=True, + ) + + +def build_model(idx, exdir): + sim = get_model(idx, exdir) + return sim, None + + +def compare_to_ref(sim): + print("comparing heads to single model reference...") + + fpth = os.path.join(sim.simpath, f"{mname_ref}.hds") + hds = flopy.utils.HeadFile(fpth) + fpth = os.path.join(sim.simpath, f"{mname_left}.hds") + hds_l = flopy.utils.HeadFile(fpth) + fpth = os.path.join(sim.simpath, f"{mname_right}.hds") + hds_r = flopy.utils.HeadFile(fpth) + + times = hds.get_times() + for t in times: + heads = hds.get_data(totim=t) + heads_left = hds_l.get_data(totim=t) + heads_right = hds_r.get_data(totim=t) + heads_2models = np.append(heads_left, heads_right, axis=2) + + # compare heads + maxdiff = np.amax(abs(heads - heads_2models)) + assert ( + maxdiff < 10 * hclose_check + ), "Max. head diff. {} should \ + be within solver tolerance (x10): {}".format( + maxdiff, 10 * hclose_check + ) + + # check budget error from .lst file + for mname in [mname_ref, mname_left, mname_right]: + fpth = os.path.join(sim.simpath, f"{mname}.lst") + for line in open(fpth): + if line.lstrip().startswith("PERCENT"): + cumul_balance_error = float(line.split()[3]) + assert ( + abs(cumul_balance_error) < 0.00001 + ), "Cumulative balance error = {} for {}, should equal 0.0".format( + cumul_balance_error, mname + ) + + +@pytest.mark.parametrize( + "idx, name", + list(enumerate(ex)), +) +def test_mf6model(idx, name, function_tmpdir, targets): + test = TestFramework() + test.build(build_model, idx, str(function_tmpdir)) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=compare_to_ref, idxsim=idx + ), + str(function_tmpdir), + ) diff --git a/autotest/test_gwf_ifmod_rewet.py b/autotest/test_gwf_ifmod_rewet.py index 61c0d53a582..bf099d4f672 100644 --- a/autotest/test_gwf_ifmod_rewet.py +++ b/autotest/test_gwf_ifmod_rewet.py @@ -14,18 +14,18 @@ # In this case we test rewetting, which is also enabled in # the interface model and should give identical results. # -# 'refmodel' 'leftmodel' 'rightmodel' +# period 1: The first stress period we start almost dry and have the +# model fill up. +# period 2: The BC on the left is lowered such that a part of the top +# layer dries. To test the interface, the value is chosen such +# that the boundary cell on the left is DRY and the one on the +# right isn't. # -# 1 . . . . . . . 1 1 . . . . 1 1 . . 1 -# 1 . . . . . . . 1 1 . . . . 1 1 . . 1 -# 1 . . . . . . . 1 1 . . . . 1 1 . . 1 -# 1 . . . . . . . 1 1 . . . . 1 1 . . 1 -# 1 . . . . . . . 1 VS 1 . . . . 1 + 1 . . 1 -# 1 . . . . . . . 1 1 . . . . 1 1 . . 1 -# 1 . . . . . . . 1 1 . . . . 1 1 . . 1 -# 1 . . . . . . . 1 1 . . . . 1 1 . . 1 -# 1 . . . . . . . 1 1 . . . . 1 1 . . 1 -# 1 . . . . . . . 1 1 . . . . 1 1 . . 1 +# 'refmodel' 'leftmodel' 'rightmodel' +# +# layer 1: 1 . . . . . . . 1 1 . . . . 1 1 . . 1 +# layer 2: 1 . . . . . . . 1 VS 1 . . . . 1 + 1 . . 1 +# layer 3: 1 . . . . . . . 1 1 . . . . 1 1 . . 1 # # We assert equality on the head values. All models are part of the same # solution for convenience. Finally, the budget error is checked. @@ -48,7 +48,7 @@ ncol = 15 ncol_left = 10 ncol_right = 5 -nrow = 10 +nrow = 1 lenx = 15.0 * 500.0 leny = 10.0 * 500.0 @@ -67,12 +67,14 @@ # hydraulic conductivity hk = 10.0 -# boundary stress period data -h_left = [100.0, 25.0] +# boundary stress period data for period 1 and 2 +h_left = [150.0, 20.0] +h_right = 60.0 # initial head h_start = -40.0 + # head boundaries lchd1 = [ [(ilay, irow, 0), h_left[0]] @@ -81,16 +83,16 @@ if h_left[0] > tops[ilay + 1] ] rchd = [ - [(ilay, irow, ncol - 1), h_start] + [(ilay, irow, ncol - 1), h_right] for ilay in range(nlay) for irow in range(nrow) - if h_start > tops[ilay + 1] + if h_right > tops[ilay + 1] ] rchd_right = [ - [(ilay, irow, ncol_right - 1), h_start] + [(ilay, irow, ncol_right - 1), h_right] for ilay in range(nlay) for irow in range(nrow) - if h_start > tops[ilay + 1] + if h_right > tops[ilay + 1] ] chd1 = lchd1 + rchd @@ -104,8 +106,9 @@ for irow in range(nrow) if h_left[1] > tops[ilay + 1] ] -chd_spd[1] = lchd2 +chd_spd[1] = lchd2 + rchd chd_spd_left[1] = lchd2 +chd_spd_right[1] = rchd_right # rewetting rewet_record = [("WETFCT", 1.0, "IWETIT", 1, "IHDWET", 1)] @@ -353,21 +356,28 @@ def build_model(idx, exdir): def compare_to_ref(sim): print("comparing heads to single model reference...") - for iper in range(nper): - fpth = os.path.join(sim.simpath, f"{mname_ref}.hds") - hds = flopy.utils.HeadFile(fpth) - heads = hds.get_data(idx=iper) - - fpth = os.path.join(sim.simpath, f"{mname_left}.hds") - hds = flopy.utils.HeadFile(fpth) - heads_left = hds.get_data(idx=iper) - - fpth = os.path.join(sim.simpath, f"{mname_right}.hds") - hds = flopy.utils.HeadFile(fpth) - heads_right = hds.get_data(idx=iper) - + fpth = os.path.join(sim.simpath, f"{mname_ref}.hds") + hds = flopy.utils.HeadFile(fpth) + fpth = os.path.join(sim.simpath, f"{mname_left}.hds") + hds_l = flopy.utils.HeadFile(fpth) + fpth = os.path.join(sim.simpath, f"{mname_right}.hds") + hds_r = flopy.utils.HeadFile(fpth) + + times = hds.get_times() + for iper, t in enumerate(times): + heads = hds.get_data(totim=t) + heads_left = hds_l.get_data(totim=t) + heads_right = hds_r.get_data(totim=t) heads_2models = np.append(heads_left, heads_right, axis=2) + # in this test we want to have the top layer in the left model + # dry in period 2, but the cells in the right model should remain + # active. This tests the interface model for dealing with drying + # and wetting, and handling inactive cells, explicitly + if (iper == 1): + assert np.all(heads_left[0,0,:] == -1.0e+30), "left model, top layer should be DRY in period 2" + assert np.all(heads_right[0,0,:] > -1.0e+30), "right model, top layer should be WET in period 2" + # compare heads maxdiff = np.amax(abs(heads - heads_2models)) assert ( diff --git a/autotest/test_gwf_ifmod_vert.py b/autotest/test_gwf_ifmod_vert.py index 16fd838da27..b70c97e1b5d 100644 --- a/autotest/test_gwf_ifmod_vert.py +++ b/autotest/test_gwf_ifmod_vert.py @@ -27,7 +27,7 @@ The exchange will have XT3D enabled so the head values in the child model should match the theory. In this case we just assert that they are equal for each column, something -that is clearly not through when simulating without XT3D. +that is clearly not true when simulating without XT3D. """ import os diff --git a/autotest/test_par_gwf_idomain.py b/autotest/test_par_gwf_idomain.py new file mode 100644 index 00000000000..f471d0bf2f0 --- /dev/null +++ b/autotest/test_par_gwf_idomain.py @@ -0,0 +1,56 @@ +import os +from decimal import Decimal +import pytest +from framework import TestFramework +from simulation import TestSimulation + +# This tests reuses the simulation data in test_gwf_ifmod_idomain.py +# and runs it in parallel on three cpus with +# +# cpu 0: 'refmodel' +# cpu 1: 'leftmodel' +# cpu 2: 'rightmodel' +# +# so we can compare the parallel coupling of 'leftmodel' + 'rightmodel' +# with a serial 'refmodel' + +ex = ["par_idomain"] + +def build_petsc_db(idx, exdir): + from test_gwf_ifmod_idomain import hclose_check, max_inner_it + petsc_db_file = os.path.join(exdir, ".petscrc") + with open(petsc_db_file, 'w') as petsc_file: + petsc_file.write("-ksp_type bicg\n") + petsc_file.write("-pc_type bjacobi\n") + petsc_file.write("-sub_pc_type ilu\n") + petsc_file.write("-sub_pc_factor_levels 2\n") + petsc_file.write(f"-dvclose {Decimal(hclose_check):.2E}\n") + petsc_file.write(f"-nitermax {max_inner_it}\n") + petsc_file.write("-options_left no\n") + +def build_model(idx, exdir): + from test_gwf_ifmod_idomain import build_model as build_model_ext + build_petsc_db(idx, exdir) + sim, dummy = build_model_ext(idx, exdir) + return sim, dummy + +def eval_model(test_sim): + from test_gwf_ifmod_idomain import compare_to_ref + compare_to_ref(test_sim) + +@pytest.mark.parallel +@pytest.mark.parametrize( + "idx, name", + list(enumerate(ex)), +) +def test_mf6model(idx, name, function_tmpdir, targets): + test = TestFramework() + test.build(build_model, idx, str(function_tmpdir)) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_model, + idxsim=0, make_comparison=False, + parallel=True, ncpus=3, + ), + str(function_tmpdir), + ) diff --git a/autotest/test_par_gwf_newton.py b/autotest/test_par_gwf_newton.py new file mode 100644 index 00000000000..29881e3cccb --- /dev/null +++ b/autotest/test_par_gwf_newton.py @@ -0,0 +1,58 @@ +import os +from decimal import Decimal +import pytest +from framework import TestFramework +from simulation import TestSimulation + +# This tests reuses the simulation data in test_gwf_ifmod_newton.py +# and runs it in parallel on three cpus with +# +# cpu 0: 'refmodel' +# cpu 1: 'leftmodel' +# cpu 2: 'rightmodel' +# +# so we can compare the parallel coupling of 'leftmodel' + 'rightmodel' +# with a serial 'refmodel'. +# +# This test also checks that PTC works in parallel. + +ex = ["par_newton"] + +def build_petsc_db(idx, exdir): + from test_gwf_ifmod_newton import hclose_check, max_inner_it + petsc_db_file = os.path.join(exdir, ".petscrc") + with open(petsc_db_file, 'w') as petsc_file: + petsc_file.write("-ksp_type bicg\n") + petsc_file.write("-pc_type bjacobi\n") + petsc_file.write("-sub_pc_type ilu\n") + petsc_file.write("-sub_pc_factor_levels 2\n") + petsc_file.write(f"-dvclose {Decimal(hclose_check):.2E}\n") + petsc_file.write(f"-nitermax {max_inner_it}\n") + petsc_file.write("-options_left no\n") + +def build_model(idx, exdir): + from test_gwf_ifmod_newton import build_model as build_model_ext + build_petsc_db(idx, exdir) + sim, dummy = build_model_ext(idx, exdir) + return sim, dummy + +def eval_model(test_sim): + from test_gwf_ifmod_newton import compare_to_ref + compare_to_ref(test_sim) + +@pytest.mark.parallel +@pytest.mark.parametrize( + "idx, name", + list(enumerate(ex)), +) +def test_mf6model(idx, name, function_tmpdir, targets): + test = TestFramework() + test.build(build_model, idx, str(function_tmpdir)) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_model, + idxsim=0, make_comparison=False, + parallel=True, ncpus=3, + ), + str(function_tmpdir), + ) diff --git a/src/Model/GroundWaterFlow/gwf3.f90 b/src/Model/GroundWaterFlow/gwf3.f90 index afdde28cbc7..7dbe2491252 100644 --- a/src/Model/GroundWaterFlow/gwf3.f90 +++ b/src/Model/GroundWaterFlow/gwf3.f90 @@ -631,40 +631,21 @@ end subroutine gwf_ptcchk !! for the current outer iteration !! !< - subroutine gwf_ptc(this, matrix, & - vec_x, vec_rhs, iptc, ptcf) - ! modules - use ConstantsModule, only: DONE, DP9 + subroutine gwf_ptc(this, vec_residual, iptc, ptcf) + ! -- modules + use ConstantsModule, only: DONE use TdisModule, only: DELT ! -- dummy class(GwfModelType) :: this - class(MatrixBaseType), pointer :: matrix - class(VectorBaseType), pointer :: vec_x - class(VectorBaseType), pointer :: vec_rhs + class(VectorBaseType), pointer :: vec_residual integer(I4B), intent(inout) :: iptc real(DP), intent(inout) :: ptcf ! -- local - integer(I4B) :: iptct integer(I4B) :: n - integer(I4B) :: jrow - integer(I4B) :: jrow_loc - integer(I4B) :: matrix_offset - integer(I4B) :: j - integer(I4B) :: jcol - integer(I4B) :: jcol_loc + integer(I4B) :: iptct real(DP) :: v real(DP) :: resid - real(DP), dimension(this%dis%nodes) :: resid_vec - real(DP), contiguous, dimension(:), pointer :: x - real(DP), contiguous, dimension(:), pointer :: rhs real(DP) :: ptcdelem1 - integer(I4B) :: first_col - integer(I4B) :: last_col -! ------------------------------------------------------------------------------ - ! - ! set pointers to vec_x and vec_rhs - x => vec_x%get_array() - rhs => vec_rhs%get_array() ! ! -- set temporary flag indicating if pseudo-transient continuation should ! be used for this model and time step @@ -681,41 +662,16 @@ subroutine gwf_ptc(this, matrix, & ! ! -- calculate pseudo-transient continuation factor for model if (iptct > 0) then - matrix_offset = matrix%get_row_offset() ! - ! calculate the residual - do n = 1, this%dis%nodes - resid = DZERO - if (this%npf%ibound(n) > 0) then - jrow = n + this%moffset - jrow_loc = jrow - matrix_offset - - ! diagonal and off-diagonal elements - first_col = matrix%get_first_col_pos(jrow) - last_col = matrix%get_last_col_pos(jrow) - do j = first_col, last_col - jcol = matrix%get_column(j) - jcol_loc = jcol - matrix_offset - if (jcol_loc < 1 .or. jcol_loc > size(x)) cycle ! temporary protection for parallel case - resid = resid + matrix%get_value_pos(j) * x(jcol_loc) - end do - - ! subtract the right-hand side - resid = resid - rhs(jrow_loc) - end if - resid_vec(n) = resid - end do - ! - ! calculate the pseudo-time step using the - ! calculated residual + ! -- calculate the pseudo-time step using the residual do n = 1, this%dis%nodes if (this%npf%ibound(n) < 1) cycle ! - ! get the maximum volume of the cell (head at top of cell) + ! -- get the maximum volume of the cell (head at top of cell) v = this%dis%get_cell_volume(n, this%dis%top(n)) ! - ! set the residual - resid = resid_vec(n) + ! -- set the residual + resid = vec_residual%get_value_local(n) ! ! -- calculate the reciprocal of the pseudo-time step ! resid [L3/T] / volume [L3] = [1/T] @@ -727,13 +683,13 @@ subroutine gwf_ptc(this, matrix, & if (ptcdelem1 > ptcf) ptcf = ptcdelem1 end do ! - ! protection for the case where the residuals are zero + ! -- protection for the case where the residuals are zero if (ptcf == DZERO) then ptcf = DONE / (DELT * DTEN) end if end if - - ! reset ipc if needed + ! + ! -- reset ipc if needed if (iptc == 0) then if (iptct > 0) iptc = 1 end if diff --git a/src/Model/NumericalModel.f90 b/src/Model/NumericalModel.f90 index 188f2f1dcb2..59ccf46ec76 100644 --- a/src/Model/NumericalModel.f90 +++ b/src/Model/NumericalModel.f90 @@ -124,11 +124,9 @@ subroutine model_ptcchk(this, iptc) iptc = 0 end subroutine model_ptcchk - subroutine model_ptc(this, matrix, vec_x, vec_rhs, iptc, ptcf) + subroutine model_ptc(this, vec_residual, iptc, ptcf) class(NumericalModelType) :: this - class(MatrixBaseType), pointer :: matrix - class(VectorBaseType), pointer :: vec_x - class(VectorBaseType), pointer :: vec_rhs + class(VectorBaseType), pointer :: vec_residual integer(I4B), intent(inout) :: iptc real(DP), intent(inout) :: ptcf end subroutine model_ptc diff --git a/src/Solution/LinearMethods/ImsLinearSolver.f90 b/src/Solution/LinearMethods/ImsLinearSolver.f90 index a5305ee36e2..de63b9c8a38 100644 --- a/src/Solution/LinearMethods/ImsLinearSolver.f90 +++ b/src/Solution/LinearMethods/ImsLinearSolver.f90 @@ -14,7 +14,6 @@ module ImsLinearSolverModule procedure :: initialize => ims_initialize procedure :: solve => ims_solve procedure :: get_result => ims_get_result - procedure :: get_l2_norm => ims_get_l2_norm procedure :: destroy => ims_destroy procedure :: create_matrix => ims_create_matrix @@ -48,15 +47,6 @@ subroutine ims_get_result(this) class(ImsLinearSolverType) :: this end subroutine ims_get_result - function ims_get_l2_norm(this, x, rhs, active) result(l2norm) - class(ImsLinearSolverType) :: this - class(VectorBaseType), pointer :: x - class(VectorBaseType), pointer :: rhs - integer(I4B), dimension(:), pointer, contiguous :: active - real(DP) :: l2norm - l2norm = 0.0_DP - end function ims_get_l2_norm - subroutine ims_destroy(this) class(ImsLinearSolverType) :: this end subroutine ims_destroy diff --git a/src/Solution/LinearSolverBase.f90 b/src/Solution/LinearSolverBase.f90 index 3dd2fa00ba6..3bb8566d9c3 100644 --- a/src/Solution/LinearSolverBase.f90 +++ b/src/Solution/LinearSolverBase.f90 @@ -18,7 +18,6 @@ module LinearSolverBaseModule procedure(initialize_if), deferred :: initialize procedure(solve_if), deferred :: solve procedure(get_result_if), deferred :: get_result - procedure(get_l2_norm_if), deferred :: get_l2_norm procedure(destroy_if), deferred :: destroy procedure(create_matrix_if), deferred :: create_matrix @@ -41,14 +40,6 @@ subroutine get_result_if(this) import LinearSolverBaseType class(LinearSolverBaseType) :: this end subroutine - function get_l2_norm_if(this, x, rhs, active) result(l2norm) - import LinearSolverBaseType, VectorBaseType, I4B, DP - class(LinearSolverBaseType) :: this - class(VectorBaseType), pointer :: x - class(VectorBaseType), pointer :: rhs - integer(I4B), dimension(:), pointer, contiguous :: active - real(DP) :: l2norm - end function get_l2_norm_if subroutine destroy_if(this) import LinearSolverBaseType class(LinearSolverBaseType) :: this diff --git a/src/Solution/NumericalSolution.f90 b/src/Solution/NumericalSolution.f90 index d4258dd5c78..363bad7af2a 100644 --- a/src/Solution/NumericalSolution.f90 +++ b/src/Solution/NumericalSolution.f90 @@ -160,6 +160,8 @@ module NumericalSolutionModule ! 'protected' (this can be overridden) procedure :: sln_has_converged procedure :: sln_l2norm + procedure :: sln_calc_ptc + procedure :: sln_calc_residual ! private procedure, private :: sln_connect @@ -465,9 +467,9 @@ subroutine sln_df(this) ! -- create linear system matrix and compatible vectors this%linear_solver => create_linear_solver(this%solver_mode) this%system_matrix => this%linear_solver%create_matrix() - this%vec_x => this%system_matrix%create_vector(this%neq, 'X', this%memoryPath) + this%vec_x => this%system_matrix%create_vec_mm(this%neq, 'X', this%memoryPath) this%x => this%vec_x%get_array() - this%vec_rhs => this%system_matrix%create_vector(this%neq, 'RHS', & + this%vec_rhs => this%system_matrix%create_vec_mm(this%neq, 'RHS', & this%memoryPath) this%rhs => this%vec_rhs%get_array() ! @@ -1580,21 +1582,14 @@ subroutine solve(this, kiter) if (this%numtrack > 0) then call this%sln_backtracking(mp, cp, kiter) end if - + ! call code_timer(0, ttform, this%ttform) - - ! (re)build the solution matrix + ! + ! -- (re)build the solution matrix call this%sln_buildsystem(kiter, inewton=1) - ! ! -- Calculate pseudo-transient continuation factor for each model - iptc = 0 - ptcf = DZERO - do im = 1, this%modellist%Count() - mp => GetNumericalModelFromList(this%modellist, im) - call mp%model_ptc(this%system_matrix, & - this%vec_x, this%vec_rhs, iptc, ptcf) - end do + call this%sln_calc_ptc(iptc, ptcf) ! ! -- Add model Newton-Raphson terms to solution do im = 1, this%modellist%Count() @@ -1734,7 +1729,7 @@ subroutine solve(this, kiter) ! -- under-relaxation - only done if convergence not achieved if (this%icnvg /= 1) then if (this%nonmeth > 0) then - call this%sln_underrelax(kiter, this%hncg(kiter), this%neq, & + call this%sln_underrelax(kiter, this%hncg(kiter), this%neq, & ! TODO_MJR: this is not equiv. serial/parallel this%active, this%x, this%xtemp) else call this%sln_calcdx(this%neq, this%active, & @@ -2811,46 +2806,28 @@ end subroutine sln_backtracking_xupdate !! A = the linear system matrix !! x = the dependendent variable vector !! b = the right-hand side vector - !! m = the mask array for inactive cells, - !! where inactive == 0, active > 0 !! - !! r = A * x - b - !! M = diag(if m_i > 0: 1 else: 0) - !! L2norm = ||M * r||_2 with + !! r = A * x - b + !! r_i = 0 if cell i is inactive + !! L2norm = || r ||_2 !< subroutine sln_l2norm(this, l2norm) - ! -- dummy variables - class(NumericalSolutionType), intent(inout) :: this !< NumericalSolutionType instance - real(DP), intent(inout) :: l2norm !< calculated L-2 norm - ! -- local variables - integer(I4B) :: n - integer(I4B) :: ipos, icol_s, icol_e - integer(I4B) :: jcol - real(DP) :: rowsum - real(DP) :: residual - ! - ! -- initialize local variables - residual = DZERO - ! - ! -- calculate the L-2 norm - do n = 1, this%neq - if (this%active(n) > 0) then - rowsum = DZERO - icol_s = this%system_matrix%get_first_col_pos(n) - icol_e = this%system_matrix%get_last_col_pos(n) - do ipos = icol_s, icol_e - jcol = this%system_matrix%get_column(ipos) - rowsum = rowsum + & - (this%system_matrix%get_value_pos(ipos) * this%x(jcol)) - end do - ! compute mean square residual from q of each node - residual = residual + (rowsum - this%rhs(n))**2 - end if - end do - ! -- The L-2 norm is the square root of the sum of the square of the residuals - l2norm = sqrt(residual) - ! - ! -- return + class(NumericalSolutionType) :: this !< NumericalSolutionType instance + real(DP) :: l2norm !< calculated L-2 norm + ! local + class(VectorBaseType), pointer :: vec_resid + + ! calc. residual vector + vec_resid => this%system_matrix%create_vec(this%neq) + call this%sln_calc_residual(vec_resid) + + ! 2-norm + l2norm = vec_resid%norm2() + + ! clean up temp. vector + call vec_resid%destroy() + deallocate (vec_resid) + return end subroutine sln_l2norm @@ -2921,6 +2898,56 @@ subroutine sln_calcdx(this, neq, active, x, xtemp, dx) return end subroutine sln_calcdx + !> @brief Calculate pseudo-transient continuation factor + !< from the models in the solution + subroutine sln_calc_ptc(this, iptc, ptcf) + class(NumericalSolutionType) :: this !< NumericalSolutionType instance + integer(I4B) :: iptc !< PTC (1) or not (0) + real(DP) :: ptcf !< the PTC factor calculated + ! local + integer(I4B) :: im + class(NumericalModelType), pointer :: mp + class(VectorBaseType), pointer :: vec_resid + + iptc = 0 + ptcf = DZERO + + ! calc. residual vector + vec_resid => this%system_matrix%create_vec(this%neq) + call this%sln_calc_residual(vec_resid) + + ! determine ptc + do im = 1, this%modellist%Count() + mp => GetNumericalModelFromList(this%modellist, im) + call mp%model_ptc(vec_resid, iptc, ptcf) + end do + + ! clean up temp. vector + call vec_resid%destroy() + deallocate (vec_resid) + + end subroutine sln_calc_ptc + + !> @brief Calculate the current residual vector r = A*x - b, + !< zeroes out for inactive cells + subroutine sln_calc_residual(this, vec_resid) + class(NumericalSolutionType) :: this !< NumericalSolutionType instance + class(VectorBaseType), pointer :: vec_resid !< the residual vector + ! local + integer(I4B) :: n + + call this%system_matrix%multiply(this%vec_x, vec_resid) ! r = A*x + + call vec_resid%axpy(-1.0_DP, this%vec_rhs) ! r = r - b + + do n = 1, this%neq + if (this%active(n) < 1) then + call vec_resid%set_value_local(n, 0.0_DP) ! r_i = 0 if inactive + end if + end do + + end subroutine sln_calc_residual + !> @ brief Under-relaxation !! !! Under relax using the simple, cooley, or delta-bar-delta methods. diff --git a/src/Solution/PETSc/PetscSolver.F90 b/src/Solution/PETSc/PetscSolver.F90 index d7c0306ae68..bec64d17b9c 100644 --- a/src/Solution/PETSc/PetscSolver.F90 +++ b/src/Solution/PETSc/PetscSolver.F90 @@ -28,7 +28,6 @@ module PetscSolverModule procedure :: initialize => petsc_initialize procedure :: solve => petsc_solve procedure :: get_result => petsc_get_result - procedure :: get_l2_norm => petsc_get_l2_norm procedure :: destroy => petsc_destroy procedure :: create_matrix => petsc_create_matrix @@ -189,59 +188,6 @@ subroutine petsc_get_result(this) class(PetscSolverType) :: this end subroutine petsc_get_result - !> @brief Gets the global L2 norm for active cells using - !< the petsc linear system and solution's active cells array - function petsc_get_l2_norm(this, x, rhs, active) result(l2norm) - use ConstantsModule, only: DONE - class(PetscSolverType) :: this - class(VectorBaseType), pointer :: x - class(VectorBaseType), pointer :: rhs - integer(I4B), dimension(:), pointer, contiguous :: active - real(DP) :: l2norm - ! local - integer(I4B) :: i - class(PetscVectorType), pointer :: x_petsc, rhs_petsc - PetscScalar, parameter :: min_one = -1.0 - PetscScalar, parameter :: zero = 0.0 - PetscScalar :: norm - PetscErrorCode :: ierr - - x_petsc => null() - select type (x) - class is (PetscVectorType) - x_petsc => x - end select - rhs_petsc => null() - select type (rhs) - class is (PetscVectorType) - rhs_petsc => rhs - end select - - ! set up vector with residual elements - call MatMult(this%mat_petsc, x_petsc%vec_impl, this%vec_residual, ierr) ! r = A * x - CHKERRQ(ierr) - call VecAXPY(this%vec_residual, min_one, rhs_petsc%vec_impl, ierr) ! r = r - rhs - CHKERRQ(ierr) - - ! zero out inactive cell contributions - do i = 1, size(active) - if (active(i) == 0) then - call VecSetValueLocal(this%vec_residual, i - 1, zero, INSERT_VALUES, ierr) ! r_i = 0 if active_i == 0 - end if - end do - call VecAssemblyBegin(this%vec_residual, ierr) - CHKERRQ(ierr) - call VecAssemblyEnd(this%vec_residual, ierr) - CHKERRQ(ierr) - - ! collective norm - call VecNorm(this%vec_residual, NORM_2, norm, ierr) ! 2-norm - CHKERRQ(ierr) - - l2norm = norm - - end function petsc_get_l2_norm - subroutine petsc_destroy(this) class(PetscSolverType) :: this ! local diff --git a/src/Solution/ParallelSolution.f90 b/src/Solution/ParallelSolution.f90 index 2d1da38f41c..6c1c5278fc6 100644 --- a/src/Solution/ParallelSolution.f90 +++ b/src/Solution/ParallelSolution.f90 @@ -1,5 +1,6 @@ module ParallelSolutionModule - use KindModule, only: DP, LGP + use KindModule, only: DP, LGP, I4B + use ConstantsModule, only: DONE, DZERO use NumericalSolutionModule, only: NumericalSolutionType use mpi use MpiWorldModule @@ -12,7 +13,7 @@ module ParallelSolutionModule contains ! override procedure :: sln_has_converged => par_has_converged - procedure :: sln_l2norm => par_l2norm + procedure :: sln_calc_ptc => par_calc_ptc end type ParallelSolutionType contains @@ -43,13 +44,33 @@ function par_has_converged(this, max_dvc) result(has_converged) end function par_has_converged - subroutine par_l2norm(this, l2norm) - class(ParallelSolutionType), intent(inout) :: this !< parallel solution - real(DP), intent(inout) :: l2norm !< calculated L-2 norm + !> @brief Calculate pseudo-transient continuation factor + !< for the parallel case + subroutine par_calc_ptc(this, iptc, ptcf) + class(ParallelSolutionType) :: this !< parallel solution + integer(I4B) :: iptc !< PTC (1) or not (0) + real(DP) :: ptcf !< the (global) PTC factor calculated + ! local + integer(I4B) :: iptc_loc + real(DP) :: ptcf_loc, ptcf_glo_max + integer :: ierr + type(MpiWorldType), pointer :: mpi_world - l2norm = this%linear_solver%get_l2_norm(this%vec_x, this%vec_rhs, & - this%active) + mpi_world => get_mpi_world() + call this%NumericalSolutionType%sln_calc_ptc(iptc_loc, ptcf_loc) + if (iptc_loc == 0) ptcf_loc = DZERO + + ! now reduce + call MPI_Allreduce(ptcf_loc, ptcf_glo_max, 1, MPI_DOUBLE_PRECISION, & + MPI_MAX, mpi_world%comm, ierr) + + iptc = 0 + ptcf = DZERO + if (ptcf_glo_max > DZERO) then + iptc = 1 + ptcf = ptcf_glo_max + end if - end subroutine par_l2norm + end subroutine par_calc_ptc end module ParallelSolutionModule diff --git a/src/Utilities/Matrix/MatrixBase.f90 b/src/Utilities/Matrix/MatrixBase.f90 index 001ef8e5b19..e225e3fd2e6 100644 --- a/src/Utilities/Matrix/MatrixBase.f90 +++ b/src/Utilities/Matrix/MatrixBase.f90 @@ -11,7 +11,8 @@ module MatrixBaseModule contains procedure(init_if), deferred :: init procedure(destroy_if), deferred :: destroy - procedure(create_vector_if), deferred :: create_vector + procedure(create_vec_mm_if), deferred :: create_vec_mm + procedure(create_vec_if), deferred :: create_vec procedure(get_value_pos_if), deferred :: get_value_pos procedure(get_diag_value_if), deferred :: get_diag_value @@ -32,6 +33,8 @@ module MatrixBaseModule procedure(get_aij_if), deferred :: get_aij procedure(get_row_offset_if), deferred :: get_row_offset + procedure(multiply_if), deferred :: multiply + end type MatrixBaseType abstract interface @@ -45,7 +48,7 @@ subroutine destroy_if(this) import MatrixBaseType class(MatrixBaseType) :: this end subroutine - function create_vector_if(this, n, name, mem_path) result(vec) + function create_vec_mm_if(this, n, name, mem_path) result(vec) import MatrixBaseType, VectorBaseType, I4B class(MatrixBaseType) :: this integer(I4B) :: n @@ -53,6 +56,13 @@ function create_vector_if(this, n, name, mem_path) result(vec) character(len=*) :: mem_path class(VectorBaseType), pointer :: vec end function + function create_vec_if(this, n) result(vec) + import MatrixBaseType, VectorBaseType, I4B + class(MatrixBaseType) :: this + integer(I4B) :: n + class(VectorBaseType), pointer :: vec + end function + function get_value_pos_if(this, ipos) result(value) import MatrixBaseType, I4B, DP class(MatrixBaseType) :: this @@ -142,6 +152,12 @@ function get_row_offset_if(this) result(offset) class(MatrixBaseType) :: this integer(I4B) :: offset end function + subroutine multiply_if(this, vec_x, vec_y) + import MatrixBaseType, VectorBaseType + class(MatrixBaseType) :: this + class(VectorBaseType) :: vec_x + class(VectorBaseType) :: vec_y + end subroutine end interface end module MatrixBaseModule diff --git a/src/Utilities/Matrix/PetscMatrix.F90 b/src/Utilities/Matrix/PetscMatrix.F90 index 7b2ed516db4..a65bcad0823 100644 --- a/src/Utilities/Matrix/PetscMatrix.F90 +++ b/src/Utilities/Matrix/PetscMatrix.F90 @@ -26,7 +26,8 @@ module PetscMatrixModule ! override procedure :: init => pm_init procedure :: destroy => pm_destroy - procedure :: create_vector => pm_create_vector + procedure :: create_vec_mm => pm_create_vec_mm + procedure :: create_vec => pm_create_vec procedure :: get_value_pos => pm_get_value_pos procedure :: get_diag_value => pm_get_diag_value procedure :: set_diag_value => pm_set_diag_value @@ -43,6 +44,8 @@ module PetscMatrixModule procedure :: get_aij => pm_get_aij procedure :: get_row_offset => pm_get_row_offset + procedure :: multiply => pm_multiply + ! public procedure :: update => pm_update @@ -137,7 +140,7 @@ subroutine pm_destroy(this) end subroutine pm_destroy - function pm_create_vector(this, n, name, mem_path) result(vec) + function pm_create_vec_mm(this, n, name, mem_path) result(vec) class(PetscMatrixType) :: this integer(I4B) :: n !< the nr. of elements in the vector character(len=*) :: name !< the variable name (for access through memory manager) @@ -147,10 +150,23 @@ function pm_create_vector(this, n, name, mem_path) result(vec) class(PetscVectorType), pointer :: petsc_vec allocate (petsc_vec) - call petsc_vec%create(n, name, mem_path) + call petsc_vec%create_mm(n, name, mem_path) + vec => petsc_vec + + end function pm_create_vec_mm + + function pm_create_vec(this, n) result(vec) + class(PetscMatrixType) :: this + integer(I4B) :: n !< the nr. of elements in the vector + class(VectorBaseType), pointer :: vec !< the vector object to return + ! local + class(PetscVectorType), pointer :: petsc_vec + + allocate (petsc_vec) + call petsc_vec%create(n) vec => petsc_vec - end function pm_create_vector + end function pm_create_vec function pm_get_value_pos(this, ipos) result(value) class(PetscMatrixType) :: this @@ -341,4 +357,30 @@ function pm_get_row_offset(this) result(offset) end function pm_get_row_offset + !> @brief Calculates global matrix vector product: y = A * x + !< + subroutine pm_multiply(this, vec_x, vec_y) + class(PetscMatrixType) :: this + class(VectorBaseType), pointer :: vec_x + class(VectorBaseType), pointer :: vec_y + ! local + PetscErrorCode :: ierr + class(PetscVectorType), pointer :: x, y + + x => null() + select type (vec_x) + class is (PetscVectorType) + x => vec_x + end select + y => null() + select type (vec_y) + class is (PetscVectorType) + y => vec_y + end select + + call MatMult(this%mat, x%vec_impl, y%vec_impl, ierr) + CHKERRQ(ierr) + + end subroutine pm_multiply + end module PetscMatrixModule diff --git a/src/Utilities/Matrix/SparseMatrix.f90 b/src/Utilities/Matrix/SparseMatrix.f90 index 701a719d52f..83bd3bb225b 100644 --- a/src/Utilities/Matrix/SparseMatrix.f90 +++ b/src/Utilities/Matrix/SparseMatrix.f90 @@ -19,7 +19,8 @@ module SparseMatrixModule contains procedure :: init => spm_init procedure :: destroy => spm_destroy - procedure :: create_vector => spm_create_vector + procedure :: create_vec_mm => spm_create_vec_mm + procedure :: create_vec => spm_create_vec procedure :: get_value_pos => spm_get_value_pos procedure :: get_diag_value => spm_get_diag_value @@ -39,6 +40,8 @@ module SparseMatrixModule procedure :: get_aij => spm_get_aij procedure :: get_row_offset => spm_get_row_offset + procedure :: multiply => spm_multiply + procedure :: allocate_scalars procedure :: allocate_arrays @@ -81,7 +84,7 @@ subroutine spm_destroy(this) end subroutine spm_destroy - function spm_create_vector(this, n, name, mem_path) result(vec) + function spm_create_vec_mm(this, n, name, mem_path) result(vec) class(SparseMatrixType) :: this ! this sparse matrix integer(I4B) :: n !< the nr. of elements in the vector character(len=*) :: name !< the variable name (for access through memory manager) @@ -91,10 +94,23 @@ function spm_create_vector(this, n, name, mem_path) result(vec) class(SeqVectorType), pointer :: seq_vec allocate (seq_vec) - call seq_vec%create(n, name, mem_path) + call seq_vec%create_mm(n, name, mem_path) vec => seq_vec - end function spm_create_vector + end function spm_create_vec_mm + + function spm_create_vec(this, n) result(vec) + class(SparseMatrixType) :: this ! this sparse matrix + integer(I4B) :: n !< the nr. of elements in the vector + class(VectorBaseType), pointer :: vec ! the vector to create + ! local + class(SeqVectorType), pointer :: seq_vec + + allocate (seq_vec) + call seq_vec%create(n) + vec => seq_vec + + end function spm_create_vec function spm_get_value_pos(this, ipos) result(value) class(SparseMatrixType) :: this @@ -275,4 +291,26 @@ function spm_get_row_offset(this) result(offset) end function spm_get_row_offset + !> @brief Calculates the matrix vector product y = A*x + !< + subroutine spm_multiply(this, vec_x, vec_y) + class(SparseMatrixType) :: this + class(VectorBaseType) :: vec_x + class(VectorBaseType) :: vec_y + ! local + integer(I4B) :: irow, icol, ipos + real(DP), dimension(:), pointer, contiguous :: x, y + + x => vec_x%get_array() + y => vec_y%get_array() + do irow = 1, this%nrow + y(irow) = DZERO + do ipos = this%ia(irow), this%ia(irow + 1) - 1 + icol = this%ja(ipos) + y(irow) = y(irow) + this%amat(ipos) * x(icol) + end do + end do + + end subroutine spm_multiply + end module SparseMatrixModule diff --git a/src/Utilities/Vector/PetscVector.F90 b/src/Utilities/Vector/PetscVector.F90 index 404b77d31b0..7c58d1cf79d 100644 --- a/src/Utilities/Vector/PetscVector.F90 +++ b/src/Utilities/Vector/PetscVector.F90 @@ -14,20 +14,25 @@ module PetscVectorModule Vec :: vec_impl contains ! override + procedure :: create_mm => petsc_vec_create_mm procedure :: create => petsc_vec_create procedure :: destroy => petsc_vec_destroy procedure :: get_array => petsc_vec_get_array procedure :: get_ownership_range => petsc_vec_get_ownership_range procedure :: get_size => petsc_vec_get_size + procedure :: get_value_local => petsc_vec_get_value_local procedure :: zero_entries => petsc_vec_zero_entries + procedure :: set_value_local => petsc_vec_set_value_local + procedure :: axpy => petsc_vec_axpy + procedure :: norm2 => petsc_vec_norm2 procedure :: print => petsc_vec_print end type PetscVectorType contains - !> @brief Create a PETSc vector - !< - subroutine petsc_vec_create(this, n, name, mem_path) + !> @brief Create a PETSc vector, with memory + !< in the memory manager + subroutine petsc_vec_create_mm(this, n, name, mem_path) class(PetscVectorType) :: this !< this vector integer(I4B) :: n !< the nr. of elements in the vector character(len=*) :: name !< the variable name (for access through memory manager) @@ -35,6 +40,8 @@ subroutine petsc_vec_create(this, n, name, mem_path) ! local PetscErrorCode :: ierr + this%is_mem_managed = .true. + call mem_allocate(this%array, n, name, mem_path) if (simulation_mode == 'PARALLEL' .and. nr_procs > 1) then call VecCreateMPIWithArray(PETSC_COMM_WORLD, 1, n, PETSC_DECIDE, & @@ -47,6 +54,30 @@ subroutine petsc_vec_create(this, n, name, mem_path) call this%zero_entries() + end subroutine petsc_vec_create_mm + + !> @brief Create a PETSc vector, with memory + !< in the memory manager + subroutine petsc_vec_create(this, n) + class(PetscVectorType) :: this !< this vector + integer(I4B) :: n !< the nr. of elements in the vector + ! local + PetscErrorCode :: ierr + + this%is_mem_managed = .false. + + allocate (this%array(n)) + if (simulation_mode == 'PARALLEL' .and. nr_procs > 1) then + call VecCreateMPIWithArray(PETSC_COMM_WORLD, 1, n, PETSC_DECIDE, & + this%array, this%vec_impl, ierr) + else + call VecCreateSeqWithArray(PETSC_COMM_WORLD, 1, n, this%array, & + this%vec_impl, ierr) + end if + CHKERRQ(ierr) + + call this%zero_entries() + end subroutine petsc_vec_create !> @brief Clean up @@ -58,7 +89,11 @@ subroutine petsc_vec_destroy(this) call VecDestroy(this%vec_impl, ierr) CHKERRQ(ierr) - call mem_deallocate(this%array) + if (this%is_mem_managed) then + call mem_deallocate(this%array) + else + deallocate (this%array) + end if end subroutine petsc_vec_destroy @@ -100,6 +135,17 @@ function petsc_vec_get_size(this) result(size) end function petsc_vec_get_size + !> @brief Gets a value from the vector at the local index + !< + function petsc_vec_get_value_local(this, idx) result(val) + class(PetscVectorType) :: this !< this vector + integer(I4B) :: idx !< the index in local numbering + real(DP) :: val !< the value + + val = this%array(idx) + + end function petsc_vec_get_value_local + !> @brief set all elements to zero !< subroutine petsc_vec_zero_entries(this) @@ -116,6 +162,50 @@ subroutine petsc_vec_zero_entries(this) end subroutine petsc_vec_zero_entries + !> @brief Set vector value at local index + !< + subroutine petsc_vec_set_value_local(this, idx, val) + class(PetscVectorType) :: this !< this vector + integer(I4B) :: idx !< the index in local numbering + real(DP) :: val !< the value to set + + this%array(idx) = val + + end subroutine petsc_vec_set_value_local + + !> @brief Calculate AXPY: y = a*x + y + !< + subroutine petsc_vec_axpy(this, alpha, vec_x) + class(PetscVectorType) :: this !< this vector + real(DP) :: alpha !< the factor + class(VectorBaseType), pointer :: vec_x !< the vector to add + ! local + PetscErrorCode :: ierr + class(PetscVectorType), pointer :: x + + x => null() + select type (vec_x) + class is (PetscVectorType) + x => vec_x + end select + call VecAxpy(this%vec_impl, alpha, x%vec_impl, ierr) + CHKERRQ(ierr) + + end subroutine petsc_vec_axpy + + !> @brief Calculate this vector's (global) 2-norm + !< + function petsc_vec_norm2(this) result(n2) + class(PetscVectorType) :: this !< this vector + real(DP) :: n2 !< the calculated 2-norm + ! local + PetscErrorCode :: ierr + + call VecNorm(this%vec_impl, NORM_2, n2, ierr) + CHKERRQ(ierr) + + end function petsc_vec_norm2 + subroutine petsc_vec_print(this) class(PetscVectorType) :: this !< this vector ! local diff --git a/src/Utilities/Vector/SeqVector.f90 b/src/Utilities/Vector/SeqVector.f90 index 701118adb51..a5552c83f3e 100644 --- a/src/Utilities/Vector/SeqVector.f90 +++ b/src/Utilities/Vector/SeqVector.f90 @@ -7,32 +7,51 @@ module SeqVectorModule private type, public, extends(VectorBaseType) :: SeqVectorType - integer(I4B) :: size + integer(I4B), private :: size real(DP), dimension(:), pointer, contiguous :: array contains + procedure :: create_mm => sqv_create_mm procedure :: create => sqv_create procedure :: destroy => sqv_destroy procedure :: get_array => sqv_get_array procedure :: get_ownership_range => sqv_get_ownership_range procedure :: get_size => sqv_get_size + procedure :: get_value_local => sqv_get_value_local procedure :: zero_entries => sqv_zero_entries + procedure :: set_value_local => sqv_set_value_local + procedure :: axpy => sqv_axpy + procedure :: norm2 => sqv_norm2 procedure :: print => sqv_print end type SeqVectorType contains - !> @brief Create a sequential vector: the classic MF6 verion - !< - subroutine sqv_create(this, n, name, mem_path) + !> @brief Create a sequential vector: the classic MF6 verion, + !< storing in the memory manager. + subroutine sqv_create_mm(this, n, name, mem_path) class(SeqVectorType) :: this !< this vector integer(I4B) :: n !< the nr. of elements in the vector character(len=*) :: name !< the variable name (for access through memory manager) character(len=*) :: mem_path !< memory path for storing the underlying memory items this%size = n + this%is_mem_managed = .true. call mem_allocate(this%array, n, name, mem_path) call this%zero_entries() + end subroutine sqv_create_mm + + !> @brief Create a sequential vector: the classic MF6 verion + !< + subroutine sqv_create(this, n) + class(SeqVectorType) :: this !< this vector + integer(I4B) :: n !< the nr. of elements in the vector + + this%size = n + this%is_mem_managed = .false. + allocate (this%array(n)) + call this%zero_entries() + end subroutine sqv_create !> @brief Clean up @@ -40,7 +59,11 @@ end subroutine sqv_create subroutine sqv_destroy(this) class(SeqVectorType) :: this !< this vector - call mem_deallocate(this%array) + if (this%is_mem_managed) then + call mem_deallocate(this%array) + else + deallocate (this%array) + end if end subroutine sqv_destroy @@ -72,6 +95,17 @@ function sqv_get_size(this) result(size) end function sqv_get_size + !> @brief Get value at local index + !< + function sqv_get_value_local(this, idx) result(val) + class(SeqVectorType) :: this !< this vector + integer(I4B) :: idx !< the index in local numbering + real(DP) :: val !< the value + + val = this%array(idx) + + end function sqv_get_value_local + !> @brief set all elements to zero !< subroutine sqv_zero_entries(this) @@ -85,6 +119,50 @@ subroutine sqv_zero_entries(this) end subroutine sqv_zero_entries + !> @brief Set vector value at local index + !< + subroutine sqv_set_value_local(this, idx, val) + class(SeqVectorType) :: this !< this vector + integer(I4B) :: idx !< the index in local numbering + real(DP) :: val !< the value to set + + this%array(idx) = val + + end subroutine sqv_set_value_local + + !> @brief Caculcates AXPY: y = a*x + y + !< + subroutine sqv_axpy(this, alpha, vec_x) + class(SeqVectorType) :: this !< this vector + real(DP) :: alpha !< the factor + class(VectorBaseType), pointer :: vec_x !< the vector to add + ! local + integer(I4B) :: i + real(DP), dimension(:), pointer, contiguous :: x_array + + x_array => vec_x%get_array() + do i = 1, this%size + this%array(i) = alpha * x_array(i) + this%array(i) + end do + + end subroutine sqv_axpy + + !> @brief Calculate the 2-norm + !< + function sqv_norm2(this) result(n2) + class(SeqVectorType) :: this !< this vector + real(DP) :: n2 + ! local + integer(I4B) :: i + + n2 = DZERO + do i = 1, this%size + n2 = n2 + this%array(i)**2 + end do + n2 = sqrt(n2) + + end function sqv_norm2 + subroutine sqv_print(this) class(SeqVectorType) :: this !< this vector diff --git a/src/Utilities/Vector/VectorBase.f90 b/src/Utilities/Vector/VectorBase.f90 index 07b604667ef..633d851f8a9 100644 --- a/src/Utilities/Vector/VectorBase.f90 +++ b/src/Utilities/Vector/VectorBase.f90 @@ -1,27 +1,32 @@ module VectorBaseModule - use KindModule, only: I4B, DP + use KindModule, only: I4B, DP, LGP implicit none private type, public, abstract :: VectorBaseType + logical(LGP) :: is_mem_managed contains - procedure(create_if), deferred :: create + procedure(create_mm_if), deferred :: create_mm procedure(destroy_if), deferred :: destroy procedure(get_array_if), deferred :: get_array procedure(get_ownership_range_if), deferred :: get_ownership_range procedure(get_size_if), deferred :: get_size + procedure(get_value_local_if), deferred :: get_value_local procedure(zero_entries_if), deferred :: zero_entries + procedure(set_value_local_if), deferred :: set_value_local + procedure(axpy_if), deferred :: axpy + procedure(norm2_if), deferred :: norm2 procedure(print_if), deferred :: print end type VectorBaseType abstract interface - subroutine create_if(this, n, name, mem_path) + subroutine create_mm_if(this, n, name, mem_path) import VectorBaseType, I4B class(VectorBaseType) :: this !< this vector integer(I4B) :: n !< the nr. of elements in the (local) vector character(len=*) :: name !< the variable name (for access through memory manager) character(len=*) :: mem_path !< memory path for storing the underlying memory items - end subroutine create_if + end subroutine create_mm_if subroutine destroy_if(this) import VectorBaseType class(VectorBaseType) :: this !< this vector @@ -34,17 +39,40 @@ end function get_array_if subroutine get_ownership_range_if(this, start, end) import VectorBaseType, I4B class(VectorBaseType) :: this !< this vector - integer(I4B) :: start, end + integer(I4B) :: start, end !< local range in global numbering end subroutine function get_size_if(this) result(size) import VectorBaseType, I4B class(VectorBaseType) :: this !< this vector - integer(I4B) :: size + integer(I4B) :: size !< the global vector size + end function + function get_value_local_if(this, idx) result(val) + import VectorBaseType, I4B, DP + class(VectorBaseType) :: this !< this vector + integer(I4B) :: idx !< index in local numbering + real(DP) :: val !< the value at the index end function subroutine zero_entries_if(this) import VectorBaseType class(VectorBaseType) :: this end subroutine + subroutine set_value_local_if(this, idx, val) + import VectorBaseType, I4B, DP + class(VectorBaseType) :: this !< this vector + integer(I4B) :: idx !< index in local numbering + real(DP) :: val !< the value to set + end subroutine + subroutine axpy_if(this, alpha, vec_x) + import VectorBaseType, DP + class(VectorBaseType) :: this !< this vector y => alpha*x + y + real(DP) :: alpha !< the factor + class(VectorBaseType), pointer :: vec_x !< the vector to add + end subroutine + function norm2_if(this) result(n2) + import VectorBaseType, DP + class(VectorBaseType) :: this !< this vector + real(DP) :: n2 !< the calculated 2-norm + end function subroutine print_if(this) import VectorBaseType class(VectorBaseType) :: this From 604369991c4dea6db2c7316c9676b45f9f561450 Mon Sep 17 00:00:00 2001 From: jdhughes-usgs Date: Tue, 18 Apr 2023 18:01:31 -0500 Subject: [PATCH 073/123] test(test001f_hfb_xt3d): remove exclusion for version 6.4.1 (#1207) --- autotest/test_z01_testmodels_mf6.py | 1 - 1 file changed, 1 deletion(-) diff --git a/autotest/test_z01_testmodels_mf6.py b/autotest/test_z01_testmodels_mf6.py index aa73294349e..b349598c027 100644 --- a/autotest/test_z01_testmodels_mf6.py +++ b/autotest/test_z01_testmodels_mf6.py @@ -5,7 +5,6 @@ excluded_models = ["alt_model", "test205_gwtbuy-henrytidal"] excluded_comparisons = { "test001e_noUZF_3lay": ["6.2.1",], - "test001f_hfb-xt3d-nwt": ["6.4.1",], "test005_advgw_tidal": ["6.2.1",], "test017_Crinkle": ["6.2.1",], "test028_sfr": ["6.2.1",], From a91b73110a8893969f0c94718d990064999288ec Mon Sep 17 00:00:00 2001 From: mjr-deltares <45555666+mjr-deltares@users.noreply.github.com> Date: Fri, 21 Apr 2023 15:02:25 +0200 Subject: [PATCH 074/123] fix(mat): base and derived interface should be consistent (for ifort) (#1208) --- src/Utilities/Matrix/MatrixBase.f90 | 4 ++-- src/Utilities/Matrix/SparseMatrix.f90 | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Utilities/Matrix/MatrixBase.f90 b/src/Utilities/Matrix/MatrixBase.f90 index e225e3fd2e6..6af0613199b 100644 --- a/src/Utilities/Matrix/MatrixBase.f90 +++ b/src/Utilities/Matrix/MatrixBase.f90 @@ -155,8 +155,8 @@ function get_row_offset_if(this) result(offset) subroutine multiply_if(this, vec_x, vec_y) import MatrixBaseType, VectorBaseType class(MatrixBaseType) :: this - class(VectorBaseType) :: vec_x - class(VectorBaseType) :: vec_y + class(VectorBaseType), pointer :: vec_x + class(VectorBaseType), pointer :: vec_y end subroutine end interface diff --git a/src/Utilities/Matrix/SparseMatrix.f90 b/src/Utilities/Matrix/SparseMatrix.f90 index 83bd3bb225b..3ba96a64f5c 100644 --- a/src/Utilities/Matrix/SparseMatrix.f90 +++ b/src/Utilities/Matrix/SparseMatrix.f90 @@ -295,8 +295,8 @@ end function spm_get_row_offset !< subroutine spm_multiply(this, vec_x, vec_y) class(SparseMatrixType) :: this - class(VectorBaseType) :: vec_x - class(VectorBaseType) :: vec_y + class(VectorBaseType), pointer :: vec_x + class(VectorBaseType), pointer :: vec_y ! local integer(I4B) :: irow, icol, ipos real(DP), dimension(:), pointer, contiguous :: x, y From ad92ee143a2a4164960edf7d28b647788712aa12 Mon Sep 17 00:00:00 2001 From: w-bonelli Date: Tue, 25 Apr 2023 07:57:27 -0400 Subject: [PATCH 075/123] ci: use actions/deploy-pages (allows removing gh-pages branch) (#1210) --- .github/workflows/docs.yml | 37 +++++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index fe278c93186..2b8be6d97e0 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -158,8 +158,7 @@ jobs: - uses: actions/checkout@v3 - name: Print branch name - run: | - echo ${{env.branch-name}} + run: echo ${{env.branch-name}} - name: Install doxygen and graphviz run: | @@ -173,12 +172,10 @@ jobs: cache-env: true - name: Print python package versions - run: | - pip list + run: pip list - name: update MODFLOW 6 version - run: | - python update_version.py + run: python update_version.py working-directory: ${{env.distribution-directory}} - name: update MODFLOW 6 version in Doxyfile @@ -201,13 +198,25 @@ jobs: working-directory: ${{env.working-directory}} - name: run doxygen - run: | - doxygen + run: doxygen working-directory: ${{env.working-directory}} - - - name: Deploy doxygen html to gh-pages - uses: peaceiris/actions-gh-pages@v3.7.3 - if: github.ref == 'refs/heads/develop' && github.event_name == 'push' + + - name: upload pages artifact + uses: actions/upload-pages-artifact@v1 with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ${{env.working-directory}}/html + path: ${{env.working-directory}}/html + + pages_deploy: + if: github.ref == 'refs/heads/develop' && github.event_name == 'push' + needs: doxygen_build + permissions: + pages: write + id-token: write + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v2 From 372e3ba6a50c2e79eea9d774ae0a7d21db111129 Mon Sep 17 00:00:00 2001 From: Eric Morway Date: Wed, 3 May 2023 12:58:31 -0700 Subject: [PATCH 076/123] fix(gwf3lak8): error msg should be tripped when input variable connlen equals zero (#1213) * fix(gwf3lak8): error msg should be tripped when input variable connlen equals zero * setting ctypenm = keyword caused a character truncation error * fprettify * responding to review comments --- doc/ReleaseNotes/v6.5.0.tex | 1 + src/Model/GroundWaterFlow/gwf3lak8.f90 | 59 ++++++++++++++------------ 2 files changed, 32 insertions(+), 28 deletions(-) diff --git a/doc/ReleaseNotes/v6.5.0.tex b/doc/ReleaseNotes/v6.5.0.tex index 6992d5ad49c..f58e55e1612 100644 --- a/doc/ReleaseNotes/v6.5.0.tex +++ b/doc/ReleaseNotes/v6.5.0.tex @@ -25,6 +25,7 @@ \item When n-point cross-sections are active in SFR, the evaporation calculation uses the variable rwid (see MF6io.pdf) to calculate the total amount of evaporation even though the wetted topwidth is less than rwid. For example, using a trapezoidal cross-section geometry with an rwid of 10, an rlen of 100, and prescribed evaporation rate of 0.1, the calculated evaporative losses would equal 100 even when the wetted top width was only 5.0 units wide. With this bug fix, the evaporation in this example results in only 50 units of evaporation loss. A new autotests confirms the evaporation calculation using an n-point cross-section and common rectangular geometries in the same simulation. It is also worth mentioning that the precipitation calculation currently uses rwid. Since the precipitation falling outside the margins of the wetted top width but within rwid would likely be accumulated in a channel, it makes sense to leave this calculation as is. \item The input for some stress packages is read in a list format consisting of a cellid, the form of which depends on the type of discretization package, and stress information on each line. The cellid is checked upon reading to ensure that the cell is within the model grid. If the cell is outside the model grid, the program issues an error message and terminates. This cellid check was not implemented when the list was provided from an OPEN/CLOSE binary input file. The program was modified to include this check for both text and binary input. \item In some cases, unrecognized keywords and invalid auxiliary input did not terminate with a useful error message. The program was corrected to provide error handling for these cases. + \item Based on the LAK package input-output instructions (mf6io.pdf), the variable ``connlen must be greater than zero for a HORIZONTAL, EMBEDDEDH, or EMBEDDEDV lake-GWF connection.'' However, a value of zero could be specified and the model would run with no LAK-groundwater exchange. A minor fix was made to enforce connlen to be strictly greater than zero per the input instructions. The error message thrown when connlen is specified as zero was augmented with additional information for assisting the user. % \item xxx \end{itemize} diff --git a/src/Model/GroundWaterFlow/gwf3lak8.f90 b/src/Model/GroundWaterFlow/gwf3lak8.f90 index c540c5baced..3a04d06cf99 100644 --- a/src/Model/GroundWaterFlow/gwf3lak8.f90 +++ b/src/Model/GroundWaterFlow/gwf3lak8.f90 @@ -609,7 +609,7 @@ subroutine lak_read_lakes(this) n = this%parser%GetInteger() if (n < 1 .or. n > this%nlakes) then - write (errmsg, '(a,1x,i6)') 'lakeno MUST BE > 0 and <= ', this%nlakes + write (errmsg, '(a,1x,i0)') 'lakeno MUST BE > 0 and <= ', this%nlakes call store_error(errmsg) cycle end if @@ -624,7 +624,7 @@ subroutine lak_read_lakes(this) ival = this%parser%GetInteger() if (ival < 0) then - write (errmsg, '(a,1x,i6)') 'nlakeconn MUST BE >= 0 for lake ', n + write (errmsg, '(a,1x,i0)') 'nlakeconn MUST BE >= 0 for lake ', n call store_error(errmsg) end if @@ -716,7 +716,7 @@ subroutine lak_read_lake_connections(this) ! ! SPECIFICATIONS: ! ------------------------------------------------------------------------------ - use ConstantsModule, only: LINELENGTH + use ConstantsModule, only: LINELENGTH, LENVARNAME use SimModule, only: store_error, count_errors ! -- dummy class(LakType), intent(inout) :: this @@ -731,6 +731,7 @@ subroutine lak_read_lake_connections(this) integer(I4B) :: icellid, icellid0 real(DP) :: top, bot integer(I4B), dimension(:), pointer, contiguous :: nboundchk + character(len=LENVARNAME) :: ctypenm ! -- format ! @@ -777,7 +778,7 @@ subroutine lak_read_lake_connections(this) n = this%parser%GetInteger() if (n < 1 .or. n > this%nlakes) then - write (errmsg, '(a,1x,i6)') 'lakeno MUST BE > 0 and <= ', this%nlakes + write (errmsg, '(a,1x,i0)') 'lakeno MUST BE > 0 and <= ', this%nlakes call store_error(errmsg) cycle end if @@ -785,7 +786,7 @@ subroutine lak_read_lake_connections(this) ! -- read connection number ival = this%parser%GetInteger() if (ival < 1 .or. ival > this%nlakeconn(n)) then - write (errmsg, '(a,1x,i4,1x,a,1x,i6)') & + write (errmsg, '(a,1x,i0,1x,a,1x,i0)') & 'iconn FOR LAKE ', n, 'MUST BE > 1 and <= ', this%nlakeconn(n) call store_error(errmsg) cycle @@ -808,7 +809,7 @@ subroutine lak_read_lake_connections(this) ! ! -- determine if a valid cell location was provided if (nn < 1) then - write (errmsg, '(a,1x,i4,1x,a,1x,i4)') & + write (errmsg, '(a,1x,i0,1x,a,1x,i0)') & 'INVALID cellid FOR LAKE ', n, 'connection', j call store_error(errmsg) end if @@ -829,11 +830,12 @@ subroutine lak_read_lake_connections(this) case ('EMBEDDEDV') this%ictype(ipos) = 3 case default - write (errmsg, '(a,1x,i4,1x,a,1x,i4,1x,a,a,a)') & + write (errmsg, '(a,1x,i0,1x,a,1x,i0,1x,a,a,a)') & 'UNKNOWN ctype FOR LAKE ', n, 'connection', j, & '(', trim(keyword), ')' call store_error(errmsg) end select + write (ctypenm, '(a16)') keyword ! -- bed leakance !this%bedleak(ipos) = this%parser%GetDouble() @@ -846,7 +848,7 @@ subroutine lak_read_lake_connections(this) end select if (keyword /= 'NONE' .and. this%bedleak(ipos) < dzero) then - write (errmsg, '(a,1x,i4,1x,a)') 'bedleak FOR LAKE ', n, 'MUST BE >= 0' + write (errmsg, '(a,1x,i0,1x,a)') 'bedleak FOR LAKE ', n, 'MUST BE >= 0' call store_error(errmsg) end if @@ -858,12 +860,13 @@ subroutine lak_read_lake_connections(this) ! -- connection length rval = this%parser%GetDouble() - if (rval < dzero) then + if (rval <= DZERO) then if (this%ictype(ipos) == 1 .or. this%ictype(ipos) == 2 .or. & this%ictype(ipos) == 3) then - write (errmsg, '(a,1x,i4,1x,a,1x,i4,1x,a)') & - 'connection length (connlength) FOR LAKE ', n, & - ' HORIZONTAL CONNECTION ', j, 'MUST BE >= 0' + write (errmsg, '(a,1x,i0,1x,a,1x,i0,1x,a,a,1x,a)') & + 'connection length (connlen) FOR LAKE ', n, & + ', CONNECTION NO.', j, ', MUST BE > 0 FOR SPECIFIED ', & + 'connection type (ctype)', ctypenm call store_error(errmsg) else rval = DZERO @@ -875,7 +878,7 @@ subroutine lak_read_lake_connections(this) rval = this%parser%GetDouble() if (rval < dzero) then if (this%ictype(ipos) == 1) then - write (errmsg, '(a,1x,i4,1x,a,1x,i4,1x,a)') & + write (errmsg, '(a,1x,i0,1x,a,1x,i0,1x,a)') & 'cell width (connwidth) FOR LAKE ', n, & ' HORIZONTAL CONNECTION ', j, 'MUST BE >= 0' call store_error(errmsg) @@ -903,7 +906,7 @@ subroutine lak_read_lake_connections(this) if (this%ictype(ipos) /= 2 .and. this%ictype(ipos) /= 3) cycle j = j + 1 if (j > 1) then - write (errmsg, '(a,1x,i4,1x,a,1x,i4,1x,a)') & + write (errmsg, '(a,1x,i0,1x,a,1x,i0,1x,a)') & 'nlakeconn FOR LAKE', n, 'EMBEDDED CONNECTION', j, ' EXCEEDS 1.' call store_error(errmsg) end if @@ -923,7 +926,7 @@ subroutine lak_read_lake_connections(this) icellid = this%cellid(ipos) if (icellid == icellid0) then if (this%ictype(ipos) == 0) then - write (errmsg, '(a,1x,i4,1x,a,1x,i4,1x,a,1x,i4,1x,a)') & + write (errmsg, '(a,1x,i0,1x,a,1x,i0,1x,a,1x,i0,1x,a)') & 'EMBEDDED LAKE', n, & 'CANNOT COINCIDE WITH VERTICAL CONNECTION', j, & 'IN LAKE', nn, '.' @@ -954,17 +957,17 @@ subroutine lak_read_lake_connections(this) this%belev(ipos) = bot else if (this%belev(ipos) >= this%telev(ipos)) then - write (errmsg, '(a,1x,i4,1x,a,1x,i4,1x,a)') & + write (errmsg, '(a,1x,i0,1x,a,1x,i0,1x,a)') & 'telev FOR LAKE ', n, ' HORIZONTAL CONNECTION ', j, & 'MUST BE >= belev' call store_error(errmsg) else if (this%belev(ipos) < bot) then - write (errmsg, '(a,1x,i4,1x,a,1x,i4,1x,a,1x,g15.7,1x,a)') & + write (errmsg, '(a,1x,i0,1x,a,1x,i0,1x,a,1x,g15.7,1x,a)') & 'belev FOR LAKE ', n, ' HORIZONTAL CONNECTION ', j, & 'MUST BE >= cell bottom (', bot, ')' call store_error(errmsg) else if (this%telev(ipos) > top) then - write (errmsg, '(a,1x,i4,1x,a,1x,i4,1x,a,1x,g15.7,1x,a)') & + write (errmsg, '(a,1x,i0,1x,a,1x,i0,1x,a,1x,g15.7,1x,a)') & 'telev FOR LAKE ', n, ' HORIZONTAL CONNECTION ', j, & 'MUST BE <= cell top (', top, ')' call store_error(errmsg) @@ -1065,7 +1068,7 @@ subroutine lak_read_tables(this) n = this%parser%GetInteger() if (n < 1 .or. n > this%nlakes) then - write (errmsg, '(a,1x,i6)') 'lakeno MUST BE > 0 and <= ', this%nlakes + write (errmsg, '(a,1x,i0)') 'lakeno MUST BE > 0 and <= ', this%nlakes call store_error(errmsg) cycle readtable end if @@ -1088,7 +1091,7 @@ subroutine lak_read_tables(this) call this%parser%GetString(line) call this%lak_read_table(n, line, laketables(n)) case default - write (errmsg, '(a,1x,i4,1x,a)') & + write (errmsg, '(a,1x,i0,1x,a)') & 'LAKE TABLE ENTRY for LAKE ', n, 'MUST INCLUDE TAB6 KEYWORD' call store_error(errmsg) cycle readtable @@ -1235,7 +1238,7 @@ subroutine lak_read_table(this, ilak, filename, laketable) type(BlockParserType) :: parser ! -- formats character(len=*), parameter :: fmttaberr = & - &'(a,1x,i4,1x,a,1x,g15.6,1x,a,1x,i6,1x,a,1x,i4,1x,a,1x,g15.6,1x,a)' + &'(a,1x,i0,1x,a,1x,g15.6,1x,a,1x,i0,1x,a,1x,i0,1x,a,1x,g15.6,1x,a)' ! ------------------------------------------------------------------------------ ! -- format @@ -1520,7 +1523,7 @@ subroutine lak_read_outlets(this) n = this%parser%GetInteger() if (n < 1 .or. n > this%noutlets) then - write (errmsg, '(a,1x,i6)') & + write (errmsg, '(a,1x,i0)') & 'outletno MUST BE > 0 and <= ', this%noutlets call store_error(errmsg) cycle readoutlet @@ -1532,7 +1535,7 @@ subroutine lak_read_outlets(this) ! -- read outlet lakein ival = this%parser%GetInteger() if (ival < 1 .or. ival > this%nlakes) then - write (errmsg, '(a,1x,i4,1x,a,1x,i6)') & + write (errmsg, '(a,1x,i0,1x,a,1x,i0)') & 'lakein FOR OUTLET ', n, 'MUST BE > 0 and <= ', this%nlakes call store_error(errmsg) cycle readoutlet @@ -1542,7 +1545,7 @@ subroutine lak_read_outlets(this) ! -- read outlet lakeout ival = this%parser%GetInteger() if (ival < 0 .or. ival > this%nlakes) then - write (errmsg, '(a,1x,i4,1x,a,1x,i6)') & + write (errmsg, '(a,1x,i0,1x,a,1x,i0)') & 'lakeout FOR OUTLET ', n, 'MUST BE >= 0 and <= ', this%nlakes call store_error(errmsg) cycle readoutlet @@ -1559,7 +1562,7 @@ subroutine lak_read_outlets(this) case ('WEIR') this%iouttype(n) = 2 case default - write (errmsg, '(a,1x,i4,1x,a,a,a)') & + write (errmsg, '(a,1x,i0,1x,a,a,a)') & 'UNKNOWN couttype FOR OUTLET ', n, '(', trim(keyword), ')' call store_error(errmsg) cycle readoutlet @@ -3103,7 +3106,7 @@ subroutine lak_vol2stage(this, ilak, vol, stage) end do secantbisection stage = sm if (ABS(ds) >= DEM6) then - write (this%iout, '(1x,a,1x,i5,4(1x,a,1x,g15.6))') & + write (this%iout, '(1x,a,1x,i0,4(1x,a,1x,g15.6))') & & 'LAK_VOL2STAGE failed for lake', ilak, 'volume error =', fm, & & 'finding stage (', stage, ') for volume =', vol, & & 'final change in stage =', ds @@ -3392,10 +3395,10 @@ subroutine lak_set_attribute_error(this, ilak, keyword, msg) ! -- formats ! ------------------------------------------------------------------------------ if (len(msg) == 0) then - write (errmsg, '(a,1x,a,1x,i6,1x,a)') & + write (errmsg, '(a,1x,a,1x,i0,1x,a)') & keyword, ' for LAKE', ilak, 'has already been set.' else - write (errmsg, '(a,1x,a,1x,i6,1x,a)') keyword, ' for LAKE', ilak, msg + write (errmsg, '(a,1x,a,1x,i0,1x,a)') keyword, ' for LAKE', ilak, msg end if call store_error(errmsg) ! -- return From e81061d64c02d191bc1e29c994aaf293111a52c5 Mon Sep 17 00:00:00 2001 From: Eric Morway Date: Wed, 3 May 2023 13:06:58 -0700 Subject: [PATCH 077/123] fix(gwf3lak8): wetted lakebed area in lak cbc output not set (#1212) * fix(gwf3lak8): wetted lakebed area in lak cbc output not set * -Added another test for checking wetted lakebed area that tracks an initially dry lake through a brief wet-up period. -Added a check in gwf3lak8 that determines if lake stage is below bottom elevation of the lake and sets wetted area to 0 if so * Further refinement of fortran and autotest --- autotest/test_gwf_lak_wetlakbedarea01.py | 426 +++++++++++++++++++++++ autotest/test_gwf_lak_wetlakbedarea02.py | 412 ++++++++++++++++++++++ src/Model/GroundWaterFlow/gwf3lak8.f90 | 8 + 3 files changed, 846 insertions(+) create mode 100644 autotest/test_gwf_lak_wetlakbedarea01.py create mode 100644 autotest/test_gwf_lak_wetlakbedarea02.py diff --git a/autotest/test_gwf_lak_wetlakbedarea01.py b/autotest/test_gwf_lak_wetlakbedarea01.py new file mode 100644 index 00000000000..cba89465af3 --- /dev/null +++ b/autotest/test_gwf_lak_wetlakbedarea01.py @@ -0,0 +1,426 @@ +# A simple 2 layer by 1 row by 2 column model. Upper-right cell is the only +# active LAK cell. Lake starts out initially dry and then is wetted by a +# rising water table. A constant head boundary in the lower left corner cell +# is used to raise water table. This autotest checks to ensure that the wetted +# areas between the lake and the 2 connected cells (1 vertical, 1 horizontal) +# is correct. + +import os + +import flopy +import numpy as np +import pytest +from framework import TestFramework +from simulation import TestSimulation + +ex = ["lak-1cellkbd"] + +# Model units +length_units = "feet" +time_units = "days" + +# Model Parameters + +nlay = 2 +nrow = 1 +ncol = 2 +strt = 9.96 +k11 = 130.0 +k33 = [1179.0, 1179.0] +ss = 3e-4 +sy = 0.2 +chd_lr = 9.0 +lak_strt = 9.0 # Starting lake stage +lak_bedleak = 10.0 # Lakebed leakance + +idomain = np.full((nlay, nrow, ncol), 1) +idomain[0, 0, 1] = 0 # deactivate upper-right corner of 2x1x2 model + +top = 20. +botm = [10., 0.] + +# define delr and delc +delr = 10.0 +delc = 10.0 + +# Timing +perlen = [1] * 10 +nper = len(perlen) +nstp = 1 +tsmult = 1.0 + +tdis_rc = [] +for i in range(nper): + tdis_rc.append((perlen[i], nstp, tsmult)) + +# set dt0, dtmin, dtmax, dtadj, dtfailadj +dt0 = 5 +dtmin = 1.001e-5 +dtmax = 10.0 +dtadj = 2.0 +dtfailadj = 5.0 + +# Prepare constant head boundary data information +chd_spd = {} +chd_inc = [9.999999, 10.0, 10.000001, 10.00001, 10.0001, 10.001, 10.01, 10.1, 10.11, 10.12] +for i, t in enumerate(range(len(perlen))): + chd_spd.update({i: [nlay - 1, nrow - 1, 0, chd_inc[i]]}) + +# Prepare LAK package input +use_embedded_lak = False +if use_embedded_lak: + idomain[0, 0, 1] = 1 + +lak_spd = [ + [0, "rainfall", 0.0], + [0, "evaporation", 0.0], +] +lak_tab = [ + [10.1, 0.0, 0.0, 0.0], + [10.11, 0.0002272, 0.001, 0.001], + [10.12, 0.0006799, 0.01, 0.01], + [10.13, 0.0014466, 0.1, 0.1], + [10.15, 0.0036611, 0.2, 0.2], + [10.2, 0.011438787, 0.3, 0.3], + [10.25, 0.0280716, 0.4, 0.4], + [10.3, 0.068889924, 0.5, 0.5], + [10.35, 0.1690610, 0.6, 0.6], + [10.4, 0.4148885490, 0.7, 0.7] +] + +# Set solver parameters +nouter = 500 +ninner = 100 +hclose = 1e-6 +rclose = 1e-6 +relax = 0.97 + +def resolve_lvl(stg, hd, toplay): + ss = min(stg, toplay) + hh = min(hd, toplay) + thk = max(ss, hh) + return thk + +def calc_qSat(top, bot, thk): + teps = 1e-6 + tbmin = 0.0 + b = top - bot + if b > 0.0: + if thk < bot: + br = 0.0 + elif thk > top: + br = 1.0 + else: + br = (thk - bot) / b + + av = 1.0 / (1.0 - teps) + bri = 1.0 - br + if br < tbmin: + br = tbmin + + if br < teps: + y = av * 0.5 * (br * br) / teps + elif br < (1.0 - teps): + y = av * br + 0.5 * (1.0 - av) + elif br < 1.0: + y = 1.0 - ((av * 0.5 * (bri * bri)) / teps) + else: + y = 1.0 + + else: + if x < bot: + y = 0.0 + else: + y = 1.0 + + return y + +# +# MODFLOW 6 flopy GWF object +# +def build_model(idx, dir): + # Base simulation and model name and workspace + ws = dir + name = ex[idx] + + print("Building model...{}".format(name)) + + # generate names for each model + gwfname = "gwf-" + name + + sim = flopy.mf6.MFSimulation( + sim_name=name, sim_ws=ws, exe_name="mf6", version="mf6" + ) + + # Instantiating time discretization + ats_filerecord = None + tdis = flopy.mf6.ModflowTdis( + sim, + ats_filerecord=ats_filerecord, + nper=nper, + perioddata=tdis_rc, + time_units=time_units + ) + + if True: + ats_filerecord = gwfname + ".ats" + atsperiod = [ + (1, dt0, dtmin, dtmax, dtadj, dtfailadj), + (2, dt0, dtmin, dtmax, dtadj, dtfailadj), + (3, dt0, dtmin, dtmax, dtadj, dtfailadj), + (4, dt0, dtmin, dtmax, dtadj, dtfailadj), + (5, dt0, dtmin, dtmax, dtadj, dtfailadj), + (6, dt0, dtmin, dtmax, dtadj, dtfailadj), + (7, dt0, dtmin, dtmax, dtadj, dtfailadj), + ] + tdis.ats.initialize( + maxats=len(atsperiod), + perioddata=atsperiod, + filename=ats_filerecord, + ) + + gwf = flopy.mf6.ModflowGwf( + sim, + modelname=gwfname, + save_flows=True, + newtonoptions="NEWTON UNDER_RELAXATION", + ) + + # Instantiating solver + ims = flopy.mf6.ModflowIms( + sim, + print_option="ALL", + linear_acceleration="bicgstab", + outer_maximum=nouter, + outer_dvclose=hclose, + inner_maximum=ninner, + inner_dvclose=hclose, + rcloserecord="{} strict".format(rclose), + filename="{}.ims".format(gwfname), + ) + sim.register_ims_package(ims, [gwfname]) + + # Instantiate discretization package + flopy.mf6.ModflowGwfdis( + gwf, + length_units=length_units, + nlay=nlay, + nrow=nrow, + ncol=ncol, + delr=delr, + delc=delc, + top=top, + botm=botm, + idomain=idomain, + ) + + # Instantiate node property flow package + flopy.mf6.ModflowGwfnpf( + gwf, + save_specific_discharge=True, + icelltype=1, # >0 means saturated thickness varies with computed head + k=k11, + k33=k33, + ) + + # Instantiate gw storage package + flopy.mf6.ModflowGwfsto(gwf, iconvert=1, sy=sy, ss=ss, transient={0: True}) + + # Instantiate initial conditions package + flopy.mf6.ModflowGwfic(gwf, strt=strt) + + # Instantiate constant head boundary package + flopy.mf6.ModflowGwfchd(gwf, stress_period_data=chd_spd) + + # Instantiate LAK package + lak_conn = [] + if use_embedded_lak: + lak_conn.append([0, 0, (0, 0, 1), 'embeddedv', lak_bedleak, 0.0, 0.0, 1.0, 0.0]) + else: + lak_conn.append([0, 0, (0, 0, 0), 'horizontal', lak_bedleak, 10.0, 20.0, 10.0, 10.0]) + lak_conn.append([0, 1, (1, 0, 1), 'vertical', lak_bedleak, 0.0, 0.0, 0.0, 0.0]) + + lak_packagedata = [0, lak_strt, len(lak_conn)] + budpth = f"{gwfname}.lak.cbc" + tab6_filename = '{}.laktab'.format(gwfname) + if use_embedded_lak: + # LAK package input requires tables option when using embedded lakes. + lak = flopy.mf6.ModflowGwflak( + gwf, + save_flows=True, + print_stage=True, + nlakes=1, + noutlets=0, + packagedata=lak_packagedata, + connectiondata=lak_conn, + perioddata=lak_spd, + ntables=1, + tables=[0, tab6_filename], + budget_filerecord=budpth, + time_conversion=86400, + length_conversion=3.28081, + surfdep=0.05, + pname="LAK-1", + filename="{}.lak".format(gwfname), + ) + else: + # Don't need to use the "TABLES" option for non-embedded lakes + lak = flopy.mf6.ModflowGwflak( + gwf, + save_flows=True, + print_stage=True, + nlakes=1, + noutlets=0, + packagedata=lak_packagedata, + connectiondata=lak_conn, + perioddata=lak_spd, + budget_filerecord=budpth, + time_conversion=86400, + length_conversion=3.28081, + #surfdep=0.05, + pname="LAK-1", + filename="{}.lak".format(gwfname), + ) + obs_file = "{}.lak.obs".format(gwfname) + csv_file = obs_file + ".csv" + obs_dict = { + csv_file: [ + ("stage", "stage", (0,)), + ] + } + lak.obs.initialize( + filename=obs_file, + digits=10, + print_input=True, + continuous=obs_dict + ) + if use_embedded_lak: + tabinput = [] + for itm in lak_tab: + tabinput.append([itm[0], itm[1], itm[2], itm[3]]) + + laktab = flopy.mf6.ModflowUtllaktab(gwf, + nrow=len(tabinput), + ncol=len(tabinput[0]), + table=tabinput, + filename=tab6_filename, + pname='LAK_tab', + parent_file=lak) + + # Instantiate output control package + head_filerecord = "{}.hds".format(gwfname) + budget_filerecord = "{}.cbc".format(gwfname) + flopy.mf6.ModflowGwfoc( + gwf, + head_filerecord=head_filerecord, + budget_filerecord=budget_filerecord, + saverecord=[("HEAD", "LAST"), ("BUDGET", "LAST")], + printrecord=[("HEAD", "ALL")] + ) + + return sim, None + + +def eval_results(sim): + print("evaluating results...") + + # read flow results from model + name = ex[sim.idxsim] + gwfname = "gwf-" + name + + # read flow results from model + sim1 = flopy.mf6.MFSimulation.load(sim_ws=sim.simpath, load_only=["dis"]) + gwf = sim1.get_model(gwfname) + + # get final lake stage + lk_pth0 = os.path.join(sim.simpath, f"{gwfname}.lak.obs.csv") + lkstg = np.genfromtxt(lk_pth0, names=True, delimiter=",") + lkstg_time = lkstg["time"].tolist() + lkstg_val = lkstg["STAGE"].tolist() + + # Store only the values at the end of the time step + idx = [i for i, val in enumerate(lkstg_time) if not val.is_integer()] + for i in idx[::-1]: + lkstg_time.pop(i) + lkstg_val.pop(i) + + # Get heads + fname = gwfname + ".hds" + fname = os.path.join(sim.simpath, fname) + assert os.path.isfile(fname) + + hdobj = flopy.utils.binaryfile.HeadFile(fname, precision="double") + hds = hdobj.get_alldata() + + # Get lake/gwf exchange information + fname = gwfname + ".lak.cbc" + fname = os.path.join(sim.simpath, fname) + assert os.path.isfile(fname) + + lakobj = flopy.utils.binaryfile.CellBudgetFile(fname, precision="double") + wetted_lakebed_area = lakobj.get_data(text="gwf") + + wetted_out = [] + for i in np.arange(len(wetted_lakebed_area)): + vals = [] + for j in np.arange(len(wetted_lakebed_area[i])): + val = wetted_lakebed_area[i][j][-1] + vals.append(val) + wetted_out.append(vals) + wetted_out = np.array(wetted_out) + + # Compare MF6 output to answer calculated here + msg = 'Compare value written by MF6 to a value calculated here based on ' \ + 'either lake stage or gw head' + for tm in np.arange(wetted_out.shape[0]): + for conn in np.arange(wetted_out.shape[1]): + stg = lkstg_val[tm] + # horizontal connections are stored first + if conn == 0: + gwh = hds[tm, 0, 0, 0] + thk = resolve_lvl(stg, gwh, top) + sat = calc_qSat(top, botm[0], thk) + wa = sat * delc * (top - botm[0]) + mf6_wa = wetted_out[tm, conn] + assert np.isclose(mf6_wa, wa, atol=1e-6), msg + + # vertical connection analysis + elif conn == 1: + gwh = hds[tm, 1, 0, 1] + if gwh >= botm[0] or stg >= botm[0]: + # For a wetted vertical connection, it doesn't matter + # which direction the gradient is in, the wetted area + # is always delr * delc + wa = delc * delr + assert wetted_out[tm, conn] == wa, msg + + # Lake is dry in the first two stress periods. Both horiz. and vert. + # connections + msg = ( + "Lake starts out dry and wets-up after stress period 2. Horizontal " + "wetted lakebed area should be equal to 0.0 in the first two stress " + "periods" + ) + assert np.all(wetted_out[0:2, 0] == 0.0), msg + + msg = ( + "With stage rising in the lake continuously, so too should the wetted area " + "of the horizontal connection" + ) + monotonicIncrease = np.diff(wetted_out[2:, 0]) + assert np.all(monotonicIncrease > 0), msg + +@pytest.mark.parametrize( + "idx, name", + list(enumerate(ex)), +) +def test_mf6model(idx, name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, idx, ws) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_results, idxsim=idx + ), + ws, + ) diff --git a/autotest/test_gwf_lak_wetlakbedarea02.py b/autotest/test_gwf_lak_wetlakbedarea02.py new file mode 100644 index 00000000000..91e83afc917 --- /dev/null +++ b/autotest/test_gwf_lak_wetlakbedarea02.py @@ -0,0 +1,412 @@ +# An adaptation of the LAK package problem 1 supplemented with an additional +# layer that has variable thinkness to help test that the shared wetted area +# between a lakebed and groundwater cells in contact with the lake are written +# to the LAK cbc output file correctly. + +import os + +import flopy +import numpy as np +import pytest +from framework import TestFramework +from simulation import TestSimulation + +ex = ["lak-wetlkbd"] + +# Model units +length_units = "feet" +time_units = "days" + +# Model Parameters + +nper = 1 +nlay = 6 +nrow = 17 +ncol = 17 +strt = 115.0 +k11 = 130.0 +k33 = [1179.0, 1179.0, 30.0, 30.0, 30.0, 30.0] +ss = 3e-4 +sy = 0.2 +H1 = 160.0 # Constant head on left side of model +H2 = 140.0 # Constant head on right side of model +recharge = 0.0116 +etvrate = 0.0141 +etvdepth = 15.0 +lak_strt = 110.0 # Starting lake stage +lak_etrate = 0.0103 # Lake evaporation rate +lak_bedleak = 0.1 # Lakebed leakance + +left_new_lay_elv = 140 +right_new_lay_elv = 120 +new_lay_bot_elv = np.linspace(left_new_lay_elv, right_new_lay_elv, ncol) + +top = np.ones((nrow, ncol)) * 500 + +botm = [] +botm1 = [] +for i in np.arange(nrow): + botm1.append(new_lay_bot_elv) + +botm1 = np.array(botm1) +botm.append(botm1) +# Account for remaining bottom layers +botm_elevs = [107.0, 97.0, 87.0, 77.0, 67.0] +for i in np.arange(1, nlay): + botm_lay = np.ones((nrow, ncol)) * botm_elevs[i - 1] + botm.append(botm_lay) + +botm = np.array(botm) + +# define delr and delc +delr = np.array( + [ + 250.0, + 1000.0, + 1000.0, + 1000.0, + 1000.0, + 1000.0, + 500.00, + 500.00, + 500.00, + 500.0, + 500.00, + 1000.0, + 1000.0, + 1000.0, + 1000.0, + 1000.0, + 250.0, + ] +) +delc = np.array( + [ + 250.0, + 1000.0, + 1000.0, + 1000.0, + 1000.0, + 1000.0, + 500.00, + 500.00, + 500.00, + 500.0, + 500.00, + 1000.0, + 1000.0, + 1000.0, + 1000.0, + 1000.0, + 250.0, + ] +) + +# Timing +tdis_ds = ((5000.0, 1, 1.0),) + +# Define dimensions +extents = (0.0, delr.sum(), 0.0, delc.sum()) +shape3d = (nlay, nrow, ncol) + +# Create the array defining the lake location +lake_map = np.ones(shape3d, dtype=np.int32) * -1 +lake_map[0, 6:11, 6:11] = 0 +lake_map[1, 6:11, 6:11] = 0 +lake_map[2, 7:10, 7:10] = 0 +lake_map = np.ma.masked_where(lake_map < 0, lake_map) + +# Prepare linearly varying evapotranspiration surface +xlen = delr.sum() - 0.5 * (delr[0] + delr[-1]) +x = 0.0 +s1d = H1 * np.ones(ncol, dtype=float) +for idx in range(1, ncol): + x += 0.5 * (delr[idx - 1] + delr[idx]) + frac = x / xlen + s1d[idx] = H1 + (H2 - H1) * frac + +surf = np.tile(s1d, (nrow, 1)) +surf[lake_map[0] == 0] = 0 +surf[lake_map[1] == 0] = 0 +surf[lake_map[2] == 0] = 0 + +# Prepare constant head boundary data information +chd_spd = [] +for k in range(nlay): + chd_spd += [[k, i, 0, H1] for i in range(nrow)] + chd_spd += [[k, i, ncol - 1, H2] for i in range(nrow)] + +# Prepare LAK package input +lak_spd = [ + [0, "rainfall", recharge], + [0, "evaporation", lak_etrate], +] + +# Prepare Rch array +rech = np.ones((nrow, ncol)) * recharge +rech[lake_map[0] == 0] = 0 + +# Set solver parameters +nouter = 500 +ninner = 100 +hclose = 1e-9 +rclose = 1e-6 +relax = 0.97 + + +def resolve_lvl(stg, hd, toplay): + ss = min(stg, toplay) + hh = min(hd, toplay) + thk = max(ss, hh) + return thk + + +def calc_qSat(top, bot, thk): + teps = 1e-6 + tbmin = 0.0 + b = top - bot + if b > 0.0: + if thk < bot: + br = 0.0 + elif thk > top: + br = 1.0 + else: + br = (thk - bot) / b + + av = 1.0 / (1.0 - teps) + bri = 1.0 - br + if br < tbmin: + br = tbmin + + if br < teps: + y = av * 0.5 * (br * br) / teps + elif br < (1.0 - teps): + y = av * br + 0.5 * (1.0 - av) + elif br < 1.0: + y = 1.0 - ((av * 0.5 * (bri * bri)) / teps) + else: + y = 1.0 + + else: + if x < bot: + y = 0.0 + else: + y = 1.0 + + return y + + +# +# MODFLOW 6 flopy GWF object +# + + +def build_model(idx, dir): + # Base simulation and model name and workspace + ws = dir + name = ex[idx] + + print("Building model...{}".format(name)) + + # generate names for each model + gwfname = "gwf-" + name + + sim = flopy.mf6.MFSimulation( + sim_name=name, sim_ws=ws, exe_name="mf6", version="mf6" + ) + + # Instantiating time discretization + flopy.mf6.ModflowTdis( + sim, nper=len(tdis_ds), perioddata=tdis_ds, time_units=time_units + ) + + gwf = flopy.mf6.ModflowGwf( + sim, + modelname=gwfname, + save_flows=True, + newtonoptions="newton", + ) + + # Instantiating solver + ims = flopy.mf6.ModflowIms( + sim, + print_option="ALL", + linear_acceleration="bicgstab", + outer_maximum=nouter, + outer_dvclose=hclose, + inner_maximum=ninner, + inner_dvclose=hclose, + rcloserecord="{} strict".format(rclose), + filename="{}.ims".format(gwfname), + ) + sim.register_ims_package(ims, [gwfname]) + + # Instantiate discretization package + flopy.mf6.ModflowGwfdis( + gwf, + length_units=length_units, + nlay=nlay, + nrow=nrow, + ncol=ncol, + delr=delr, + delc=delc, + top=top, + botm=botm, + ) + + # Instantiate node property flow package + flopy.mf6.ModflowGwfnpf( + gwf, + save_specific_discharge=True, + icelltype=1, # >0 means saturated thickness varies with computed head + k=k11, + k33=k33, + ) + + # Instantiate gw storage package + flopy.mf6.ModflowGwfsto(gwf, iconvert=1, sy=sy, ss=ss, steady_state=True) + + # Instantiate initial conditions package + flopy.mf6.ModflowGwfic(gwf, strt=strt) + + # Instantiate constant head boundary package + flopy.mf6.ModflowGwfchd(gwf, stress_period_data=chd_spd) + + # Instantiate recharge package + flopy.mf6.ModflowGwfrcha(gwf, recharge=recharge) + + # Instantiate ET package + flopy.mf6.ModflowGwfevta(gwf, surface=surf, rate=etvrate, depth=etvdepth) + + # Instantiate LAK package + ( + idomain_wlakes, + pakdata_dict, + lak_conn, + ) = flopy.mf6.utils.get_lak_connections( + gwf.modelgrid, + lake_map, + bedleak=lak_bedleak, + ) + global lak_con + lak_con = lak_conn + lak_packagedata = [[0, lak_strt, pakdata_dict[0]]] + budpth = f"{gwfname}.lak.cbc" + lak = flopy.mf6.ModflowGwflak( + gwf, + save_flows=True, + print_stage=True, + nlakes=1, + noutlets=0, + packagedata=lak_packagedata, + connectiondata=lak_conn, + perioddata=lak_spd, + budget_filerecord=budpth, + pname="LAK-1", + filename="{}.lak".format(gwfname), + ) + obs_file = "{}.lak.obs".format(gwfname) + csv_file = obs_file + ".csv" + obs_dict = { + csv_file: [ + ("stage", "stage", (0,)), + ] + } + lak.obs.initialize( + filename=obs_file, digits=10, print_input=True, continuous=obs_dict + ) + gwf.dis.idomain = idomain_wlakes + + # Instantiate output control package + head_filerecord = "{}.hds".format(gwfname) + budget_filerecord = "{}.cbc".format(gwfname) + flopy.mf6.ModflowGwfoc( + gwf, + head_filerecord=head_filerecord, + budget_filerecord=budget_filerecord, + saverecord=[("HEAD", "LAST"), ("BUDGET", "LAST")], + ) + + return sim, None + + +def eval_results(sim): + print("evaluating results...") + + # read flow results from model + name = ex[sim.idxsim] + gwfname = "gwf-" + name + + # read flow results from model + sim1 = flopy.mf6.MFSimulation.load(sim_ws=sim.simpath, load_only=["dis"]) + gwf = sim1.get_model(gwfname) + + # get final lake stage + lk_pth0 = os.path.join(sim.simpath, f"{gwfname}.lak.obs.csv") + lkstg = np.genfromtxt(lk_pth0, names=True, delimiter=",") + lkstg_val = lkstg["STAGE"] + + # Get heads + fname = gwfname + ".hds" + fname = os.path.join(sim.simpath, fname) + assert os.path.isfile(fname) + + hdobj = flopy.utils.binaryfile.HeadFile(fname, precision="double") + hds = hdobj.get_alldata() + + # Get lake/gwf exchange information + fname = gwfname + ".lak.cbc" + fname = os.path.join(sim.simpath, fname) + assert os.path.isfile(fname) + + lakobj = flopy.utils.binaryfile.CellBudgetFile(fname, precision="double") + lak_wetted_interface_area = lakobj.get_data(text="gwf") + + checks_out = [] + for i in np.arange(len(lak_wetted_interface_area[0])): + dat = lak_wetted_interface_area[0][i] + checks_out.append(dat[3]) + + # Assumes that the order in which connection data is passed to the LAK instantiation + # is the same order that values are written to the binary output file (*.lak.cbc) + msg = ( + "The wetted interfacial areas saved in the binary output file " + "(.cbc) do not match the values calculated in the autotest script" + ) + for idx, itm in enumerate(lak_con): + k, i, j = itm[2] + ctype = itm[3] + if ctype[0] == "h": + botelv = botm[k, i, j] + if k == 0: + topelv = top[i, j] + else: + topelv = botm[k - 1, i, j] + + gwhd = hds[0, k, i, j] + thk = resolve_lvl(lkstg_val, gwhd, topelv) + sat = calc_qSat(topelv, botelv, thk) + width = itm[-1] + warea = sat * ((topelv - botelv) * width) + elif ctype[0] == "v": + length = delr[j] + width = delc[i] + warea = length * width + + assert np.isclose(warea, checks_out[idx], atol=1e-5), msg + + +@pytest.mark.parametrize( + "idx, name", + list(enumerate(ex)), +) +def test_mf6model(idx, name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, idx, ws) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_results, idxsim=idx + ), + ws, + ) diff --git a/src/Model/GroundWaterFlow/gwf3lak8.f90 b/src/Model/GroundWaterFlow/gwf3lak8.f90 index 3a04d06cf99..189bd1cb9f5 100644 --- a/src/Model/GroundWaterFlow/gwf3lak8.f90 +++ b/src/Model/GroundWaterFlow/gwf3lak8.f90 @@ -6288,6 +6288,7 @@ subroutine lak_fill_budobj(this) integer(I4B) :: nlen real(DP) :: v, v1 real(DP) :: q + real(DP) :: lkstg, gwhead, wa ! -- formats ! ----------------------------------------------------------------------------- ! @@ -6325,6 +6326,13 @@ subroutine lak_fill_budobj(this) do j = this%idxlakeconn(n), this%idxlakeconn(n + 1) - 1 n2 = this%cellid(j) q = this%qleak(j) + lkstg = this%xnewpak(n) + ! -- For the case when the lak stage is exactly equal + ! to the lake bottom, the wetted area is not returned + ! equal to 0.0 + gwhead = this%xnew(n2) + call this%lak_calculate_conn_warea(n, j, lkstg, gwhead, wa) + this%qauxcbc(1) = wa call this%budobj%budterm(idx)%update_term(n, n2, q, this%qauxcbc) end do end do From 07c86ed67aefdf619952a9d3fcd92041b0030877 Mon Sep 17 00:00:00 2001 From: Eric Morway Date: Wed, 3 May 2023 13:10:42 -0700 Subject: [PATCH 078/123] refactor(gwf3sfr8): adapt flow-area in sfr cbc output to wetted streambed area (#1211) * refactor(gwf3sfr8): adapt flow-area in sfr cbc output to wetted streambed area * restore original text string 'flow-area' to sfr cbc; tweak new autotest to ensure that no flow in channel results in wetted streambed area of 0.0 * comment cleanup in script * renaming new autotest * Modify how the new autotest determines whether it passes --- autotest/test_gwf_sfr_wetstrmbedarea.py | 394 ++++++++++++++++++++++++ src/Model/GroundWaterFlow/gwf3sfr8.f90 | 11 +- 2 files changed, 404 insertions(+), 1 deletion(-) create mode 100644 autotest/test_gwf_sfr_wetstrmbedarea.py diff --git a/autotest/test_gwf_sfr_wetstrmbedarea.py b/autotest/test_gwf_sfr_wetstrmbedarea.py new file mode 100644 index 00000000000..6b46250e5ca --- /dev/null +++ b/autotest/test_gwf_sfr_wetstrmbedarea.py @@ -0,0 +1,394 @@ +# Test evap in SFR reaches (no interaction with gwf) + +import os + +import flopy +import numpy as np +import pytest +import math +from framework import TestFramework +from simulation import TestSimulation + +ex = ["sfr-wetperim"] + + +def get_x_frac(x_coord1, rwid): + x_xsec1 = [val / rwid for val in x_coord1] + return x_xsec1 + + +def get_xy_pts(x, y, rwid): + x_xsec1 = get_x_frac(x, rwid) + x_sec_tab = [[xx, hh] for xx, hh, in zip(x_xsec1, y)] + return x_sec_tab + + +# Model units +length_units = "m" +time_units = "days" + +# model domain and grid definition +Lx = 600.0 +Ly = 300.0 +nrow = 3 +ncol = 6 +nlay = 1 +delr = Lx / ncol +delc = Ly / nrow +xmax = ncol * delr +ymax = nrow * delc +X, Y = np.meshgrid( + np.linspace(delr / 2, xmax - delr / 2, ncol), + np.linspace(ymax - delc / 2, 0 + delc / 2, nrow), +) +ibound = np.ones((nlay, nrow, ncol)) +# Because eqn uses negative values in the Y direction, need to do a little manipulation +Y_m = -1 * np.flipud(Y) +top = np.array( + [ + [101.50, 101.25, 101.00, 100.75, 100.50, 100.25], + [101.25, 101.00, 100.75, 100.50, 100.25, 100.00], + [101.50, 101.25, 101.00, 100.75, 100.50, 100.25], + ] +) + +botm = np.zeros(top.shape) +strthd = top - 1.0 + +# NPF parameters +k11 = 1 +ss = 0.00001 +sy = 0.20 +hani = 1 +laytyp = 1 + +# Package boundary conditions +surf_Q_in = [86400, 0] # 86400 m^3/d = 1 m^3/s +sfr_evaprate = 0.1 +streambed_K = 0.0 +rwid = [9.0, 10.0, 20] +# Channel geometry: trapezoidal +x_sec_tab1 = get_xy_pts( + [0.0, 2.0, 4.0, 5.0, 7.0, 9.0], + [0.66666667, 0.33333333, 0.0, 0.0, 0.33333333, 0.66666667], + rwid[0], +) + +x_sec_tab2 = get_xy_pts( + [0.0, 2.0, 4.0, 6.0, 8.0, 10.0], + [0.5, 0.25, 0.0, 0.0, 0.25, 0.5], + rwid[1], +) + +x_sec_tab3 = get_xy_pts( + [0.0, 4.0, 8.0, 12.0, 16.0, 20.0], + [0.33333333, 0.16666667, 0.0, 0.0, 0.16666667, 0.33333333], + rwid[2], +) +x_sec_tab = [x_sec_tab1, x_sec_tab2, x_sec_tab3] + +def calc_wp(j, stg): + if j < 2: + rise = 1 / 3 + run = 2 + bot_wid = 1. + elif j < 4: + rise = 1 / 4 + run = 2 + bot_wid = 2. + else: + rise = 1 / 6 + run = 4 + bot_wid = 4. + + ang = math.atan2(rise, run) + hyp_len = stg / math.sin(ang) + wp = hyp_len * 2 + bot_wid + + return wp + +# time params +steady = {0: True, 1: False} +transient = {0: False, 1: True} +nstp = [1, 1] +tsmult = [1, 1] +perlen = [1, 1] + +nouter, ninner = 1000, 300 +hclose, rclose, relax = 1e-3, 1e-4, 0.97 + +# +# MODFLOW 6 flopy GWF object +# + + +def build_model(idx, dir): + # Base simulation and model name and workspace + ws = dir + name = ex[idx] + + print("Building model...{}".format(name)) + + # generate names for each model + gwfname_trapezoidal = "gwf-" + name + + sim = flopy.mf6.MFSimulation( + sim_name=name, sim_ws=ws, exe_name="mf6", version="mf6" + ) + + # Instantiating time discretization + tdis_rc = [] + for i in range(len(nstp)): + tdis_rc.append((perlen[i], nstp[i], tsmult[i])) + + flopy.mf6.ModflowTdis( + sim, nper=len(nstp), perioddata=tdis_rc, time_units=time_units + ) + + gwf = flopy.mf6.ModflowGwf( + sim, + modelname=gwfname_trapezoidal, + save_flows=True, + newtonoptions="newton", + ) + + # Instantiating solver + ims = flopy.mf6.ModflowIms( + sim, + print_option="ALL", + outer_dvclose=hclose, + outer_maximum=nouter, + under_relaxation="cooley", + inner_maximum=ninner, + inner_dvclose=hclose, + rcloserecord=rclose, + linear_acceleration="BICGSTAB", + scaling_method="NONE", + reordering_method="NONE", + relaxation_factor=relax, + filename="{}.ims".format(gwfname_trapezoidal), + ) + sim.register_ims_package(ims, [gwfname_trapezoidal]) + + # Instantiate discretization package + flopy.mf6.ModflowGwfdis( + gwf, + length_units=length_units, + nlay=nlay, + nrow=nrow, + ncol=ncol, + delr=delr, + delc=delc, + top=top, + botm=botm, + ) + + # Instantiate node property flow package + flopy.mf6.ModflowGwfnpf( + gwf, + save_specific_discharge=True, + icelltype=1, # >0 means saturated thickness varies with computed head + k=k11, + ) + + # Instantiate storage package + flopy.mf6.ModflowGwfsto( + gwf, + save_flows=False, + iconvert=laytyp, + ss=ss, + sy=sy, + steady_state=steady, + transient=transient, + ) + + # Instantiate initial conditions package + flopy.mf6.ModflowGwfic(gwf, strt=strthd) + + # Instantiate output control package + flopy.mf6.ModflowGwfoc( + gwf, + budget_filerecord=f"{gwfname_trapezoidal}.cbc", + head_filerecord=f"{gwfname_trapezoidal}.hds", + headprintrecord=[("COLUMNS", 10, "WIDTH", 15, "DIGITS", 6, "GENERAL")], + saverecord=[("HEAD", "ALL"), ("BUDGET", "ALL")], + printrecord=[("HEAD", "ALL"), ("BUDGET", "ALL")], + ) + + # Instantiate streamflow routing package + # Determine the middle row and store in rMid (account for 0-base) + rMid = 1 + # sfr data + nreaches = ncol + rlen = delr + roughness = 0.035 + rbth = 1.0 + rhk = streambed_K + strm_up = 100.25 + strm_dn = 99 + slope = (strm_up - strm_dn) / ((ncol - 1) * delr) + ustrf = 1.0 + ndv = 0 + strm_incision = 1.0 + + # use trapezoidal cross-section for channel geometry + sfr_xsec_tab_nm1 = "{}.xsec.tab1".format(gwfname_trapezoidal) + sfr_xsec_tab_nm2 = "{}.xsec.tab2".format(gwfname_trapezoidal) + sfr_xsec_tab_nm3 = "{}.xsec.tab3".format(gwfname_trapezoidal) + sfr_xsec_tab_nm = [sfr_xsec_tab_nm1, sfr_xsec_tab_nm2, sfr_xsec_tab_nm3] + crosssections = [] + for n in range(nreaches): + # 6 reaches, 3 cross section types + crosssections.append([n, sfr_xsec_tab_nm[n // 2]]) + + # Setup the tables + for n in range(len(x_sec_tab)): + flopy.mf6.ModflowUtlsfrtab( + gwf, + nrow=len(x_sec_tab[n]), + ncol=2, + table=x_sec_tab[n], + filename=sfr_xsec_tab_nm[n], + pname=f"sfrxsectable" + str(n + 1), + ) + + packagedata = [] + for irch in range(nreaches): + nconn = 1 + if 0 < irch < nreaches - 1: + nconn += 1 + rp = [ + irch, + (0, rMid, irch), + rlen, + rwid[irch // 2], + slope, + top[rMid, irch] - strm_incision, + rbth, + rhk, + roughness, + nconn, + ustrf, + ndv, + ] + packagedata.append(rp) + + connectiondata = [] + for irch in range(nreaches): + rc = [irch] + if irch > 0: + rc.append(irch - 1) + if irch < nreaches - 1: + rc.append(-(irch + 1)) + connectiondata.append(rc) + + sfr_perioddata = {} + for t in np.arange(len(surf_Q_in)): + sfrbndx = [] + for i in np.arange(nreaches): + if i == 0: + sfrbndx.append([i, "INFLOW", surf_Q_in[t]]) + sfrbndx.append([i, "EVAPORATION", sfr_evaprate]) + + sfr_perioddata.update({t: sfrbndx}) + + # Instantiate SFR observation points + sfr_obs = { + "{}.sfr.obs.csv".format(gwfname_trapezoidal): [ + ("rch1_depth", "depth", 1), + ("rch2_depth", "depth", 2), + ("rch3_depth", "depth", 3), + ("rch4_depth", "depth", 4), + ("rch5_depth", "depth", 5), + ("rch6_depth", "depth", 6), + ], + "digits": 8, + "print_input": True, + "filename": name + ".sfr.obs", + } + + budpth = f"{gwfname_trapezoidal}.sfr.cbc" + flopy.mf6.ModflowGwfsfr( + gwf, + save_flows=True, + print_stage=True, + print_flows=True, + print_input=True, + length_conversion=1.0, + time_conversion=86400, + budget_filerecord=budpth, + mover=False, + nreaches=nreaches, + packagedata=packagedata, + connectiondata=connectiondata, + crosssections=crosssections, + perioddata=sfr_perioddata, + observations=sfr_obs, + pname="SFR-1", + filename="{}.sfr".format(gwfname_trapezoidal), + ) + + return sim, None + + +def eval_results(sim): + print("evaluating results...") + + # read flow results from model + name = ex[sim.idxsim] + gwfname = "gwf-" + name + + fname = gwfname + ".sfr.cbc" + fname = os.path.join(sim.simpath, fname) + assert os.path.isfile(fname) + + sfrobj = flopy.utils.binaryfile.CellBudgetFile(fname, precision="double") + sfr_wetted_interface_area = sfrobj.get_data(text="gwf") + + # Retrieve simulated stage of each reach + sfr_pth0 = os.path.join(sim.simpath, f"{gwfname}.sfr.obs.csv") + sfrstg = np.genfromtxt(sfr_pth0, names=True, delimiter=",") + + # Extract shared wetted interfacial areas + shared_area = [] + for t in range(len(sfr_wetted_interface_area)): + sp_area = [] + for i in range(ncol): + sp_area.append(sfr_wetted_interface_area[t][i][3]) + + shared_area.append(sp_area) + + shared_area = np.array(shared_area) + + # Calculate wetted streambed area for comparison + for j, stg in enumerate(list(sfrstg[0])[1:]): + wp = calc_wp(j, stg) + wa = wp * delr + msg = ( + "Wetted streambed area for reach " + str(j) + + "in stress period 1 does not match explicitly-calculated answer" + ) + assert np.isclose(wa, shared_area[0, j], atol=1e-4), msg + + msg = ( + "Wetted streambed area of all reaches should be zero in stess " + "period 2" + ) + for val in list(sfrstg[1])[1:]: + assert val == 0.0, msg + + +@pytest.mark.parametrize( + "idx, name", + list(enumerate(ex)), +) +def test_mf6model(idx, name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, idx, ws) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_results, idxsim=idx + ), + ws, + ) diff --git a/src/Model/GroundWaterFlow/gwf3sfr8.f90 b/src/Model/GroundWaterFlow/gwf3sfr8.f90 index 93b97110547..7c7d4c6b20e 100644 --- a/src/Model/GroundWaterFlow/gwf3sfr8.f90 +++ b/src/Model/GroundWaterFlow/gwf3sfr8.f90 @@ -5293,6 +5293,8 @@ subroutine sfr_fill_budobj(this) real(DP) :: d real(DP) :: ca real(DP) :: a + real(DP) :: wp + real(DP) :: l ! ! -- initialize counter idx = 0 @@ -5332,7 +5334,14 @@ subroutine sfr_fill_budobj(this) do n = 1, this%maxbound n2 = this%igwfnode(n) if (n2 > 0) then - a = this%calc_surface_area(n) + ! -- calc_perimeter_wet() does not enforce depth dependence + if (this%depth(n) > DZERO) then + wp = this%calc_perimeter_wet(n, this%depth(n)) + else + wp = DZERO + end if + l = this%length(n) + a = wp * l this%qauxcbc(1) = a q = -this%gwflow(n) call this%budobj%budterm(idx)%update_term(n, n2, q, this%qauxcbc) From 187cf48797d6efd6e1ea831c9fa78a16fb257d70 Mon Sep 17 00:00:00 2001 From: Eric Morway Date: Tue, 9 May 2023 14:34:43 -0700 Subject: [PATCH 079/123] fix(SfrCrossSectionUtils): fix for n-point x-section area calculation (#1172) * fix(SfrCrossSectionUtils): fix for n-point x-section area calculation * Changes that accounted for the vertically wetted sides in the wide rectangular channel approach should not have been made. * updates I originally forgot to transfer when redoing the PR (originally #1134) * now that the areas are being calculated correctly, really small widths are being multiplied by really small depths on the intial guess at channel depth. As a result, the iterative stage calculation blows up leading to a temporary fix. * one more fix I should've included earlier * goes with e579d10 * More work to do on this PR after conversation with @jdhughes-usgs * clean-up python to go with reworked fortran changes. * Bolster discussion in 'Streamflow Routing Package Cross-SectionTable Input File' section as discussed. * Update doc/mf6io/gwf/sfr.tex Co-authored-by: langevin-usgs * remove errant "(" * more changes after checking answers for a more complex x-section; reran black --------- Co-authored-by: langevin-usgs --- autotest/cross_section_functions.py | 30 +++++++++++------- autotest/test_gwf_sfr_evap.py | 10 +++--- autotest/test_gwf_sfr_npoint02.py | 23 +++++++++++--- autotest/test_gwf_sfr_npoint03.py | 10 +++++- doc/ReleaseNotes/v6.5.0.tex | 1 + ...n-point-cross-section-wetted-perimeter.pdf | Bin 0 -> 27489 bytes ...-point-cross-section-wetted-perimeter.pptx | Bin 0 -> 56632 bytes doc/mf6io/Figures/n-point-cross-section.pdf | Bin 14980 -> 10673 bytes doc/mf6io/Figures/n-point-cross-section.pptx | Bin 40382 -> 40384 bytes doc/mf6io/gwf/sfr.tex | 10 ++++++ .../ModelUtilities/SfrCrossSectionUtils.f90 | 6 +++- 11 files changed, 67 insertions(+), 23 deletions(-) create mode 100644 doc/mf6io/Figures/n-point-cross-section-wetted-perimeter.pdf create mode 100644 doc/mf6io/Figures/n-point-cross-section-wetted-perimeter.pptx diff --git a/autotest/cross_section_functions.py b/autotest/cross_section_functions.py index 57a9759073d..e315427fe0e 100644 --- a/autotest/cross_section_functions.py +++ b/autotest/cross_section_functions.py @@ -12,7 +12,7 @@ def calculate_rectchan_mannings_discharge( """ area = width * depth - return conversion_factor * area * depth**mpow * slope**0.5 / roughness + return conversion_factor * area * depth ** mpow * slope ** 0.5 / roughness # n-point cross-section functions @@ -69,17 +69,20 @@ def get_wetted_perimeter( # -- calculate the wetted perimeter for the segment xlen = x1 - x0 + dlen = 0.0 if xlen > 0.0: - if depth > hmax: + if depth >= hmax: dlen = hmax - hmin else: dlen = depth - hmin + xlen = x1 - x0 else: if depth > hmin: dlen = min(depth, hmax) - hmin else: dlen = 0.0 - return np.sqrt(xlen**2.0 + dlen**2.0) + + return np.sqrt(xlen ** 2.0 + dlen ** 2.0) def get_wetted_area(x0, x1, h0, h1, depth): @@ -95,8 +98,12 @@ def get_wetted_area(x0, x1, h0, h1, depth): if depth > hmax: area = xlen * (depth - hmax) # -- add the area below zmax - if hmax != hmin and depth > hmin: - area += 0.5 * (depth - hmin) + if hmax != hmin: + if depth >= hmax: + area += 0.5 * (hmax - hmin) * xlen + elif depth > hmin: + x0, x1 = get_wetted_station(x0, x1, h0, h1, depth) + area += 0.5 * (depth - hmin) * (x1 - x0) return area @@ -114,9 +121,6 @@ def wetted_area( x0, x1 = x[idx], x[idx + 1] h0, h1 = h[idx], h[idx + 1] - # get station data - x0, x1 = get_wetted_station(x0, x1, h0, h1, depth) - # get wetted area a = get_wetted_area(x0, x1, h0, h1, depth) area += a @@ -172,12 +176,16 @@ def manningsq( q = 0.0 for i0 in range(x.shape[0] - 1): i1 = i0 + 1 - perimeter = get_wetted_perimeter(x[i0], x[i1], h[i0], h[i1], depth) + + # get station data + x0, x1 = get_wetted_station(x[i0], x[i1], h[i0], h[i1], depth) + + perimeter = get_wetted_perimeter(x0, x1, h[i0], h[i1], depth) area = get_wetted_area(x[i0], x[i1], h[i0], h[i1], depth) if perimeter > 0.0: radius = area / perimeter q += ( - conv * area * radius**mpow * slope**0.5 / roughness[i0] + conv * area * radius ** mpow * slope ** 0.5 / roughness[i0] ) else: perimeter = wetted_perimeter(x, h, depth) @@ -185,7 +193,7 @@ def manningsq( radius = 0.0 if perimeter > 0.0: radius = area / perimeter - q = conv * area * radius**mpow * slope**0.5 / roughness[0] + q = conv * area * radius ** mpow * slope ** 0.5 / roughness[0] return q diff --git a/autotest/test_gwf_sfr_evap.py b/autotest/test_gwf_sfr_evap.py index 8684476c33b..2e9646680bc 100644 --- a/autotest/test_gwf_sfr_evap.py +++ b/autotest/test_gwf_sfr_evap.py @@ -401,11 +401,11 @@ def eval_results(sim): # Establish known answer: stored_strm_evap = np.array( [ - -66.01388942706727, - -65.99953139524196, - -65.98517173673817, - -65.97081044940869, - -65.95644753110243, + -62.17272623, + -62.15731943, + -62.14191043, + -62.12649925, + -62.11108587, ] ) diff --git a/autotest/test_gwf_sfr_npoint02.py b/autotest/test_gwf_sfr_npoint02.py index dd49ecc1a29..db51e9238da 100644 --- a/autotest/test_gwf_sfr_npoint02.py +++ b/autotest/test_gwf_sfr_npoint02.py @@ -5,6 +5,7 @@ import pytest from framework import TestFramework from simulation import TestSimulation +from cross_section_functions import get_depths paktest = "sfr" @@ -46,6 +47,7 @@ "h": np.array([0.0, 0.0], dtype=float), } + # depth as a function of flow for a wide cross-section def flow_to_depth_wide(rwid, q): return ((q * roughness) / (conversion_fact * rwid * np.sqrt(slope))) ** 0.6 @@ -53,7 +55,6 @@ def flow_to_depth_wide(rwid, q): # def build_model(idx, ws): - # build MODFLOW 6 files name = ex[idx] sim = flopy.mf6.MFSimulation( @@ -219,10 +220,22 @@ def eval_npointdepth(sim): obs["INFLOW"], np.abs(obs["OUTFLOW"]) ), "inflow not equal to outflow" - d = flow_to_depth_wide( - obs["WIDTH"], - inflow, - ) + d = [] + for n in range(nper): + x0 = 0.0 + x1 = rwid * (n + 1) # generates absolute widths generated above + x = np.array([x0, x1]) + cdepth = get_depths( + inflow, + x=x, + h=np_data[n]["h"], + roughness=roughness, + slope=slope, + conv=1.0, + dd=1e-4, + verbose=False, + ) + d.append(cdepth[0]) assert np.allclose( obs["DEPTH"], d diff --git a/autotest/test_gwf_sfr_npoint03.py b/autotest/test_gwf_sfr_npoint03.py index cecbdc67cbf..d7ae482e611 100644 --- a/autotest/test_gwf_sfr_npoint03.py +++ b/autotest/test_gwf_sfr_npoint03.py @@ -59,8 +59,16 @@ "r": np.array([10.0, 1.0, 10.0, 10.0], dtype=float), } - +# Cross section depiction +# +# | | | | +# | (left) | (channel) | (right) | +# | 10.0 | 1.0 | 10.0 | <- "MANFRACTION" +# | | | | +# +-----------+-----------+-----------+ y: 0, 0, 0, 0 +# x: 0 1/3 2/3 1 # + def build_model(idx, ws, base=False): if base: diff --git a/doc/ReleaseNotes/v6.5.0.tex b/doc/ReleaseNotes/v6.5.0.tex index f58e55e1612..c3908439ff8 100644 --- a/doc/ReleaseNotes/v6.5.0.tex +++ b/doc/ReleaseNotes/v6.5.0.tex @@ -26,6 +26,7 @@ \item The input for some stress packages is read in a list format consisting of a cellid, the form of which depends on the type of discretization package, and stress information on each line. The cellid is checked upon reading to ensure that the cell is within the model grid. If the cell is outside the model grid, the program issues an error message and terminates. This cellid check was not implemented when the list was provided from an OPEN/CLOSE binary input file. The program was modified to include this check for both text and binary input. \item In some cases, unrecognized keywords and invalid auxiliary input did not terminate with a useful error message. The program was corrected to provide error handling for these cases. \item Based on the LAK package input-output instructions (mf6io.pdf), the variable ``connlen must be greater than zero for a HORIZONTAL, EMBEDDEDH, or EMBEDDEDV lake-GWF connection.'' However, a value of zero could be specified and the model would run with no LAK-groundwater exchange. A minor fix was made to enforce connlen to be strictly greater than zero per the input instructions. The error message thrown when connlen is specified as zero was augmented with additional information for assisting the user. + \item An SFR channel defined with the n-point cross-section option was calculating the wetted cross-sectional area incorrectly. The cross sectional area for the area of a triangle was being calculated as one-half multiplied by the depth of the channel, as opposed to one-half multiplied by the base width multiplied by the height. As a result, the units in the mannings equation were not correct owing to the missing dimension in the area calculation. The change in the area calculation will slightly alter the solution found using Manning's equation since the cross-sectional area term appears in it. As a result, existing models may reflect slightly different answers in groundwater\/surface-water exchange amounts owing to slight differences in the calculated stream stage. In addition to the fix, some clarifying text, including a new figure, was added to mf6io.pdf. % \item xxx \end{itemize} diff --git a/doc/mf6io/Figures/n-point-cross-section-wetted-perimeter.pdf b/doc/mf6io/Figures/n-point-cross-section-wetted-perimeter.pdf new file mode 100644 index 0000000000000000000000000000000000000000..46f86e628715a1d9ddc18962ca478bc93a231187 GIT binary patch literal 27489 zcmeHw2|Sf+*S9&DMdsM%*`DpajhSZ|O9&g=keO`=$vkBiGDR{}A|xs#Q=yW~4N4)T zWUMIqZaST()85bX*886KeCPXqXS;vD?Oyk~*1i7MwAQt*VGA3nYf3|9Q1mpySAh>b z4HN_cdD$POr%_M+v$0lHXtf#XW&QsdlR2&30#ryg@ z;LUt-I1?`~JP0AX!(xE-b<^>5@&d$c|BG`30c`zSKYuC z2kejH9Pj|Ajt7u8Xb^gbdFKWS0Rh)`R(-vNfuLW(e!WG2pos6D%7UO@#eaQ@20_0Q z1%m+j@h@f=3#U^840{%e+~e*IGW z`lYj7YCzR+bj7NA1z7<}0r`M(FsSSi5KzjVc%Z!eKws6<^7Zof2C{vpdhXnSO~Gcq zSWiFi?Q(TE239i#tK$M(9dIUEs(@TGti2z=^0lIVEqgx@q z10W0>8A0h+Ip56RdK!(6{k&Q5PQmQ;+{H5sOBWSSo=q)XNyFo9OpbpXyxh9EGQ(GN z!s_P6*8H^c0nx^5!w+qHuVxE()lk@G+nxxP+nl^IZF``C@%dI-PU@HAa_iHRvX5ud zK0TT(QVx;Zd^=bFIk#JeZ|g?rQdiu&)~$v7uH);|%fl#JuzCe#Uz%9q*4k8#Z9@5F z?3$}9RvIH+bjI*(J<>TFvc&<)^LZH#Yiz!s!`o+ImxME}iQGK5Hz6!5YXckS z^fW+BvNn~87ZDmb7JYy`#nU5QlqCSm`K29BjSxP1bX=PvHe{5)KEkTC_;a(Mt;u?* zfK8!jW-_mIaXXm%4J$A8$bP!eH&59fzdjv*VEnvX+W>RD$3mXPxAwU*OPQ z&fjg%6Q$lz2X-tlnjmp6;<%+fEkZ954?3fB{ZZPTtgu6p_sHsAXX}V+ksrR*C*|w( zh=n8I(>h(_M3=TMAJf1sczn;YohxX)?ju|Ao;3lpb@tohYHyhjv(D)cxjOTPGBkZM z^-{k2>`Cn!tEchj=&BFzM0mQJlB6JZZV|5BO+aHCHzG{c`RB$f4R^-rP+gdlW7qbuA{>3u?XJdQ_>@7 z+NN-2kU#Ib%L((3E)@gPwPh6(*VAm?*OubX)B5ryOG!$a$w-8hS)@$JadAykh*uZ# zuyv<1-eo;|C`C&2<>)|qsG7^1%MAQ&VXFI3^9!hFsE!rT|qqRUQebXOhI&lKT+@e9xXXdsfN`q+P&BPX@;`p;rxBImS;r z44nfR=e?(=Ium)N3lF}G@6gI+yDmAvkRoa4G1b^7k)q@{<611Ty6bYf>ffcUhu1Kz96cm5QpllrZDSckfA?L(Q6H7#%pFJ3DL4 zmtLLNp3J}d7KLztP)%{3z^mLEqjx&0Mq#>B)HY-%SrZX@LA+~91rts3K3XM~VsaH8 zF(1@T3TQ*?FJGBa(2sbj$gffH6y+Yu^>purr}t(dM^A1AmUaj!3vVnm;L$0cvl^rE zVYJs=mX6Cw#do4{H$CLWv_p?SEeSs|g(xg+GY{_-$-%dqMsi)ZkG^3v@Vq2ECByOU zq(BsQciO>0{rg8$+1d{aNz-R{g5GZ*{u~KYm^L+|`I8*}9EYT>Zllm!QaW zY&Vl_=6gN$GH4zrmlMfoD9u4SU8n^@*8TUJVtIREvBhbQ;9A1J>Q z^|Bpa3>TbCaMaV(Cpi=FkSSIEFm*@1Ml)NM(ktr#)csJF+Z7F*s+3*lBN)XKPRlF4 z>|j%EN6Wd&)zT@%425!@&o)@hGF%}US7)$R-X&}Fg1QC z^GqZ+#rW0;o%r+g)t2g!Pw^rBeIG^#)8**0SN0v_8&!UAa%~C~tlawG{TBnLGtQpj zI69P-Na>%j5@hx){n+*BdMZ?>jPCjBoCx0yvq?EqilH}io!89{(L7zhPlnQ-Mk}5H zU2)5C$J?v9$1ARjC+ahTPr0a8^9qx$m*{U+!^bn4QDUeTRCl0t+mVfGG4`3p_3Ed6 zEECtwTEy#%_9_TT%|*!f9uII*%oWH-&1X8jd%=!+En8M~@X0~*EY&wllBbs!+F47i zsP*Op-cLl2-I>yckw#Qz@74=j%(3z$b%hvMuf7m~_vZ z0ZO}3liXf;d+(=4zJ@IMjQEo|9${f)*xLlGYi*9ttTBjd9w`F?Rn=Pjrjl<-Fd?oPpC>fl;GzVy?O2v#*|YAOT;Gag zj*%>aMU#cR^%|8Z!m)BG-ho58p}w6^ZZDDoi$@Si8dHM#g8`}XP3Xv~x-g^ArA_-xe}XNZbXF1&S5Jga)} zfU4!CGas?|G&1W%tq8-lLz{H5l-un)v_EnBAg{z~flUIp=-ef4lfv2D z2f;i(frrK;<7dZi9;$dhIMLljJ$ALv+;@Eh#I{bqaONGf^kiQsH<}Dxg`|$Z3J5PnWxNB%{RQ`a;Gmc`f)HI_GNRwoow{l6X7J7S~yiSst;v88i1* z%)k+Azwz|QW9xm|MwFeK@a(=Nb6YLV`_)i!63TYBnX1g95A@AP`M@|%3GZSTY3bPR zRL`V~E#1)U0r7UR$3nBArM!o>T8_I0vT|`ts9P<8@u?)&S`%*!Vm-$fx+iUwX{q3> zs@_Fmx8E8)vI^SQSq>ZKs=~6epY3d-_q(26Ws&0l(Luv$7oX z(g%XaY!j^uXPA1f&h^Nly4fuZ1jIyjjvC&@R7&DH-#c-pDev_gLx)vY?$LPxe`TCJ^dZBD_o1{*4O;KT{;ZnWMMHx$NYop4- z^Y6B{K*sk@Kf&QXd^D|7qLUG}v1j*dq%*!Ju#aTQjnc?wwaM}G(3H{5^%stixCkuM z;NVk;UfbYI*CHA1Q3U0{XQt1W@-?9Mboz91c`d>QBfgMhT)8QBA_?EdZQp_l+oSt$ zqrRQNkf9nf}r0I6}QL4Ka&U|gM>gavfn4&jvV}#N!3*T_`dBy z_a74d7Iy%f`rG5T12j891MneNM__~s18d`4on3&?1~6!L_wqIM#ya4vz&f6exFDS4 zzXru1C`bXa^U>V-C;_8vRad;95zbf5%fs8tb2~f&1=0Z{VQ;VPkub;!>+XjGn;KyK z+&~yWCm;&+byN)den?D0LNuqh6Z|v?1oC$nXvlXsep6q|7kiADvj2YEE(=3_uVYZb z+mW`EdC#dwX2E)kOI@@6HqW;$5<^ zy9MZ772wlO{87tPs9O=Q9;;rkra3`k0Eg;(Ya7MV_0C4ez0Z-H2RZDrJv~_j@{+uM zuK;7+Q;B+v{CFt2!Fa&{N5)1cbhhJI{^GkZF?uhPs4yC;NSYFKc{gckmw&8_b@bN z7{Wv~QERP_L5+FSC7S#0O{u{p4&T-02^V~k;$~4YwkPnK#iP#-EAJJ_yl^zp`+Y8= zR>W&rKAliM*Fk?biWjd{mi1D_*3ygWf@m;G8;8pJk{!I3>!Nx!%^MLMd9u**ftF+@ z%fkzMhV^e=)Stcfz}z(?;Hng|_zquPmJ0ikizSBE=cFjhD!49W8$O&&?dmPgSMpu- zDW5xjDvq-Z%yYy>PElt<;j-U3Nu0ijK7J;nb9b3@PAIE~-&LDD-f~Sr;T(?S+zg6l z@*GmKz&xH2A!9E3gG_r`Dw3=&Jj@#2b#7#`e}Uv;A#7Hnx7hW7&)k7iHf2-fRTNij zW?RJAs9x7X4mJgxl3~5{(C)xBgURQ`W0HdP_EsJpL&MnDE3?);H}8P1rYh^k*^a#H zZ&FqnHRgJ-ifg^ua@CeQ>q*6g>f0GHgMfze0DJE7qA|C`OT`*9VO+`Oe7+N^(kkkx zt9aMd0-3~_!#AuSvIxfCya>9&^)^?-`n>bVUX4Vb<0jQFJTa%H6{~7iW`GYxhup=SUq&k}ZWxvqM7b;NCvrrQ4 zWvBv428@os17!t^d8*2rrVewR$cqlYu8-F^DPuxg}1j!5;!L6oPB{xt#<>Wt6kVc%DNi( z4r8Sa30bOpCTDwj_4pjos<1QN`_p;&7qw8>FZ+c%DSjFT`ga@mXH^B*4^?3ts0!f_ zI*R>T;wCV%bC5hZ*EtB8utxORTki!qAC|>e&M$wVNw+ECKdq5xdeP5sfstD}&c`>~ z=^pfe!|PtO;qKirPG0wVL*j-xz1SEjFRQ@1B{etfkiNUNIwP9Jr6`gQ2ZsAUE4n)~ zn5rG1wnElhnRJYJ5&s?C7}zhnF*_-Kx`_Y&o$$_5il4^wSGBxfc3xor-#RZli(CG7 z1@z0#3+$Ji7bG0=%}o9|LuOg@PFV6^R`Ts=ZA!sTJdCGDT;RGv%n3)zR*eV-y>+OT zcy%*h{GLG9@vbf!XD5hM&B%g}q_A=10n6Hs#r-x4ljK2o1S9P(AEsSCsYc?Dx&p5# zNedi5*riu7yH8coaP|;$z~1LfPFbHtN`lF2IV;9HKkZraWM8UP4;|B}7k-InYAsr{ zzqK}@Wq+E@MVaMs1lm;FGNB@=LMqWP`twU<^;MDAtP(~&Ju?vlnTNz*ey~82ijmhh zh>+TuiReAd4O$X8HoHp%zq}l?Y)-@Su&7O_xx&*u^O82XrlN-P%cjd^?6pqqp)|{t zOieEA;eks}~o#;k@cb2Pc!sG@J$Y?;m&7Q>PW3o4arz2@-K4B24Bj zg#cH*q6*^rHOLEb6*+(Y&e;Ck8o{$^F5+@~#E-Ce8klL6SngR(tnW=x@9c3d>1cXD zLA7W>{z^b2SA#4_j@(S}p?xnsnM`!diA0x|3}mBY$OzgO4Cw>83DI}U!OTvK5qBs{ zYCpWPSDBEIVj7n$XIZum_Z1dc;IEfv#ez?LdoiKrHM*mV;@>C6 zH&%}1_0L~ilM`T9DsDd{R8xOhA}At0m)cC8-h5G-f3hwW;xi{G$gHIbdN`KH=d>1C zsXcz_?s!tdh5TXtq;b0Q`L0i_-Z(wMX;xLoihM8=si}ZrKc;hI_8g$$etDp>wxT*A z%q8M+;KCbyJ7;HAxJpY=wQGjyTwIrQXTwy1)urRbD_fDfpV~ssp62+#{!xFodT>L1 z>uKsOis|b!%N(FoUN(oP(2WvMwuKCu)O0r2^ zC*^3Nly~eT6I2|&H|7&ofRG4QC0i0WY~XQX-N)kr>jbIR64U4Nd~3>=u3MVz>WV z&kfBpoTRino6y>2dMM~Y%bm+wQdx1CAF8Pq8PKv%<}Ho=hAbZ14fj-@zurm`Xk{rD z=WOgSy}NROcHdQq!R0k1{hn)WY}Lnx?s-SC$8gb@i-n~YKJp*ur{l8jw}x~3?^XpP z^!+1R)GrI)V$`S^)e4mej3z-nF;Iw%>oivqaf=#u^TP zn1*N?#LJw^Mm*(f@IQOeph^~>aO3H~JDn$$ma~)RpL2%hl|Ct6i+y&kde{92xt{Ct z18>dm8=wYpr`1cBQZgn79W756`*5X0e21>R9H|#d9`~jbGV7Dw3Z02^8lks5AEU&W zC!#b_l}9@Vw#lS&LtWkHU&i^m8nkJyz6p2b9C0gOQYcG!}X)+6?LQ^)b6dW~JGC%9+0?l)M|DjvOF^0w-+3=%%cm5I&C zcRPa^)m+F9wtgO9kZ;V_YdxrM_&B7s-@Ro1`DnxF8~4}8t7EtV9AhnKxTrP{d$~pN zJxWO$v>W;qT+Jv}IiEK`_2{zK5UzZli994(c#3&YJ38X}^V|u;oH*+o2a4P6**6R~ znjW7OLA{1HEHM31Jr!Wk(%G)VQ*5=xz)r^1`ZB;P9Sc zkqrtm|JI^iExXvCXCZEjdm~V$w>8`-5?R%Z2V=yt$IiPxaFBIi%?R@OxMAzdJnu|K(xePKuv~@lT)jPoMTr zpZ5Q6pZ0f*d%%A=?y-~Nr~9-&KgtRD!B3kWILmwsM7RIps)U5RsumW(k!;LCdbo}G zsLRb4)nj|s)XkPPwv?NW#vt1{WyUo^k$j8gP7m$g;}J~}mk~27Cs*2epE|zpTb&I= zeCgxu-<^-uTX_4{^<69r&%NWgIoFZQSOel#>hCz;1OMfa$xe!&E>RTv+kpH}3RWV1 z*jM6U7f$9B%inFk_2tW#UG^3yCgft_w6o85b=FWlD5}z4db7%?>}MRf5N{1G3>ju=lG`^)L+#v{PJ)D@k^`0WHEoX^ZCPQ z0&qS_0%BDj*ljzXq^e2Ekgkp>($z!c{I@|quft&+9I+5`k`XP_1fqWD&d%?@U7`H) zhyd|Rlc<0G@&))G*3|=R)(4;r;oD<8S%xH@wsYPHG?&&AbqZZHjft)(%QynoLb|7z z&rT{DH8`yzM5C|9nw#z7T>VJ6{P`tF-o4TqYOHvnc6gM=l?BYBJ_T&Xq0>d|?tIf~ z&NsETjv}S*%UOur<7}j+I(~}R4C3ij`qHFlm}&jB+=0W-CrRZwoGOTxHj<$39e?DrF;FF=C6_%} z8DgCFZQ?M>uuQjB(a8NDt}scl(u}>W`$}@b=CwCewR~m(DkGq*xnjuvfZg8b^JA!P&xKGd!$rD^(_jmkq*7C zO!`GL(i7n^l*=WQ`>feCcF%fdvP<$q6Iw>a`^-)rpLyFvmByN6Q<8T<+pfvc~^pTr+08XHCKo>ZxltLs)QDF zEc0sV5=(zZI1^QOnuZabNSBljEu>4Jn#G$l0d^}pNu85UhfFffK+2fvVn&wOq?T{I zsA$9~3I-=pu#2vscFb+^Bg;Ay?EFzn7-3QCw1T?kh$%WR9a)ltvt+qBiJh#}_iiUc zSgxt5m7ZWja)R!$<+krX+^`4z87JC9m06eL7=@>1Gb4+R&^rB+pH^N-i(gabl~4rv zhHr-0x%>j*-dj{p;gMVx{_PCaWLPN|q_+@xrbgY9l2(Do{GMIGI=UA$x~4dR*-8W%lHa3HC<1cEODwlmtpFM0w8Qi@Tniau%|59M- z1I8xheAS>tZW3#!qzi1P%9)hiRIJN_Pn#(gRmh=K;_A>9A&1LClzJ4&*K98Nfyb^V zyq{vfkvn|f;EgD1<`sf-O4zfS>pj=FmG8B~ekO-!tDIj~IVI>E>$7@vX?es^*SX2) z1E1LkC(K=i%1;;N4kF*sTU-g44zdY;;*P zPcx?n>YVU!8lUojV_e>&wq=FwiIs-?l|(75J6noylEF}Rng9H7Sje$!kH!ZV0_Ch3 z%|tPIA_mdYY@0>-sc~)_YxyTyfxrW&XvxntItlzymV=F>V@Og zQ&-0igM1CddVRx2k_MxmByC)DeD|g`UP0qj@lrrFJ`pF zei$Jgwc2HUg#l2cB($fbCUAH@q@w+1?yWk9t2f8iol?L#iih8Za+50*H6OfNc*K0c zY_pu~{k`CsC!qS3wZ7`gzyO1ml@`z{a2YW5GRz*U_Ea?qP@A!SJ2EUUUnt=@r~6W4 zSQ#N1<-Xkay7j2^S=|PCn;VkLCTm`dZ#@HS)f=i0&7BB-sBq=shCfvr=IPtpgzBC>=Xx~Cd+T)MXrbxi z#i+y~+V<_s#(b)4_rUm^&Iyc&Gcgw^X2Ya5oK*&h>tu}XgpQ*OgMD@#B zP9AbPuy8DRChV~HXu)`nNXqBQr-9cL5TlCzr|E9Wo|UbY_2;#!s5KKk8SJnA&UV(E z>f$+}xzkp0)`}-`XfqtnoiewOzG^0!&5P?bzf*f*UzS9fnRuB=w(jAJ_qZ4ovoO4F zNja=;+*gO!uNljYTp*QHl=o<9=zXyB=auz9qR?Ef5`S>6;Qgdo_29dOk1bphC~~*T zRhzk%fsnEfx_qaU&OhT)IFij%u{L?6<*`KJ?WA)98pBpm-Nv61dM}eYCWG9l4?CRi znDepV0uK%!oVzmMW_=Zr()j9V+_DF3-` zQ++rvAN6F@TRE)kb=Qrxz*8SSt|9H#R~Ff86T>JEe`xBcIVSzZ=GlqQ&r<~>wbVD! z?#c|GHl!5p%oHtnc`d}9oUd{b1D_dJYd`p^ZzJTywe@|Oa~tzTO`Cjt0Zq!X;D8*) zz<$@zmGxi_hLS1l%H8+Ats%>w){$Gv>zgl~LYkU_mtVeo|7vnEy5T)}UFGI`!@Kd1 zH#S}@tb_zjUCZ?jZE4|FYQ3x+^15?S&oP)^@13l{sgwO;?EZ#jhtRxtHG(Z=UdIP( zUjNwY@Objr41V11O0{R`F+*PkuhZ3hhI56zfj-zxqhP)RLyYpq$~VUv(bZo*&3EVM z)l8Z=-8eH_T)Z+ixTY@H+I;2|c)n-Uc9Z>{i@!cmX9xz_jtPA8ebsZmAi2x$N@@K(dQxM~Oc z;!J}L%^ZT=959Xo%1R1BazP&69>BR>pdb%-Pd~XJMFA{uEEi4=_KI0YhM57)%=A zkoG&~iN^*>d-@4}lc0|CbMSTb-p)wSwnVJGmp@)nKmhccT<`DXdiu%iC?@0Jn9e!8Y+uzrHM`1??FwPz4v3(vDARhWZ5f9|*@AChk zdJxtdkPX?94KT^=)Fj+?Em7L8MbZ$YGz4Y_LCL{kaywN?4gygC|G=rReGgPFSEpk? zlkz*x|88~xFElXt&xkxczJW3@kW=?^@ZZiz9d#vte^*C2m?{LViiX3a)!`77Gy<-I zl14++P|`3Jw1$QTN<&=}A-e7o0u&ujKRgyVKkmOH?Yp!@z=QsR56xfj4$wa@ z`K4U`C%gX1u3yT5Ut<2JcKwrGzmx;N#Qaa~`nBx(YW4wlRSEDgzWKXK|MHgs2V7Ag z5b%cojpP4)7*yTW&)Xe)3~+DET=DL>-%h^$-iaq%G-HBu0-L%9!{5iiV&8uL-3N_(}-)wzKrTucY)}a{zDn_i!R5bNrDNeF2#dH2&AchLhuA=CS&?qopdY`*n;tdk3pE zQujTPZP=uITtDYk^I>s*rSEXTsNb2Hf-cNShorqvo`fB2;9N=F`%#iCQhhAD27_g~ zd2-Q)87d+m8GYP9J()(UFutLqJbqo=zCy#gD&Cf9ca=c;l2EEPLj-+#orH;&`fYGJ zMzXFop}_D358Jw@@x+-E zaq&J+RZ;wYc6^8QY!vTo4C!p#69}~$gf4JG=4sR?2#w$(32!?~5MAnocv2fYh;(w1 z$EHIvXg3RFx9K9)vo=I?3}+A{pB>w)33zB^D#UA(%r@C3!OH+~kn;+8*IqAMq7^s@ zIDVO)X8WjNB=Ggy2RO=E7KsFwMr}X(?E?iC@N9oTp1;$evcT?(A84@ct#m)oP$*zZ z<&QK3aHK8)4Y7SN^bdRpS)khgKtpaHqWl95wS5}NA8AM=pzV(|Sq#B@W#QW!Z~vH9 z7P-Bl`3D+mdr8s{G|cu{$$zAwfcXys8hm@c{`0{ze!6oEVp6h$Bp1IG~LDF%u8Q9mdIvAyi%57?j(+3giIf22WW3F-k93T!?3 zkq?F;C`(`-Y5UNeKgxr{Q9t5^!jY&S@j~G!G$9`bO~8kcg%I!|&=3MX;CL?rosqEZ zlRf^ZpDaZ7$NCJFg%OlFR2D%{Zctg^^MgD^YQ( zJ>T}d39u2d=TN|ZCgdYh9)Mp=$VUVR5ql0LV$Y#O>^YQ(J%^T%eq+SuR=TIW{97@EV11J9y>P*C*Ly6dPU`muAEsTgg z2Sx%v@Bt_Qf9;3y2L?_s7J|WmeKJ4r0W={0eou?{#k#uVe0P2a>bJNQ@_#=V^)=jt z^~HaUM*tJ%2sAy7u&~AfP2lHtev45+=xP4%Xb1e35iS6G)= literal 0 HcmV?d00001 diff --git a/doc/mf6io/Figures/n-point-cross-section-wetted-perimeter.pptx b/doc/mf6io/Figures/n-point-cross-section-wetted-perimeter.pptx new file mode 100644 index 0000000000000000000000000000000000000000..2bfacb559a1655f081b3c0a32fd352008af17ec8 GIT binary patch literal 56632 zcmeFYWmH_vx~`2o!QI`Rpb75o1cJM}YjAgWcY-^?J-7$A;2xlx01aQ~U2E<0uC@0b zGAt4~7P#_?%AP}HFi#s~F znLD@{YIr-DyXv!g+1rs9!$8p&LO_Az|KI2T;wLbb@mYD09eZp?@#USoAu3ZLB_SM| zLH@VINU}l)_(~7^Mch!w>owTm&>W~nHu;&E-*2<^w(TBYXQ*)o@M90_uNUM6Z%Ua#<- zINUR?1KIC%Ec`>%fWZliH!#7hTvu6TpCns7 zG>951CvAtOm6%jC`({mLG(O0wVzOqJ?MvgA$4;)1uMB(&;w{K@=X;0|y__>c-WKFY z;~v}lA(d#|C1vd!`+Fj;9V3?Ck0i{<;agn)o*>GI`(8I4`cPOU(c{dm-7wjA* zPV&Z|AWS*CiMEJk$`^Wjigt~SS%Q0}ri96MsC66x4%pYJO{fL~P1T}_s{@~Qn7kaw zaNEf_OL+OJ0P~Uzbk#kVkYPCHbAYQSkqCQRs=S z+-acetUf2o3g9fvVjo$dm-*}pTl`VtiR`n03HLa?yG%=Dl>%LxIo`amO~CZ_LpnAOOixKI;0~ybZoWvmzlKgYD+C>yTOZvghXq!iaeKF zo(j34<=IQ{yGR?yt7OtnGZ#Lq?3VIn)v&V|y}y#gy&0;r`+lCBHae;Qq*wHk%-rRc z_N|_Kcsb9|$!Sey{QiF4=XLQ<*LHZjS3xr6=dQDSoaL}x zT}YCjiAph_=kcjpYvs2FD5Zt6q0vUF4JE7@1GRv1hPiz1X(bTSvZ>*+5UJ)CN+Q8Y zyanEG9D#j8d{K)*?oMr_XLSLBhpf`hsY;Y&#c96m3dQJkmMH77$ckypa$>|pqV@0d z9v@3J=ii@Z29yf+Eci+tbD7Mf%Uf%s$)B>D*o3(Nt(WPzG>kuof1b0B{KlJ0JZMlj zgYP18ob;hS;Fvx-MW}13R=enDKHE>**^O_S4(;UYBJ}im=40~qKSP+Yy#}jp@8rrD z%nugF(~R6Tmtr4?)DiB}Wi92&v$si346PniNqQXjo@lT^?cv=DM2g9(E)<17$?2EF z3N0y@Vhv%vlGPcX(OPbm#fB}N$C3@`Var}`MU90r?4Z%6f~L(X=D zyJY(Ms!lG%uud<=_*Q3XNHi-?)!1=)jNi1rsjfwLkuf_t;lsXT;rZ#}2UzpImM;7) z{6CIo8Ug%iTm=Y-lg}^^IR6~au6EXD=B{jieR2FXp#5zfH)YZffv^B!>U(R?Agi%w zReazVv*bl+?sB5bGtuiYW(IpiD*0>;t=1a&yB#`Gv*@IR-Lmo2#kv!y^`?=?lnK{a za|!nS&A_K)wt{QCzFOW{&jhsV`tj?VWfPDS$jcRYa|&8E475w^)&1b8jmBH^JQnfr zTw}O8tNr%!{08cKJw1&$e$ENA2i?33oq?7wJ)ZA}x;iN77}t0 zs&50-=O|x0@YM8e7zpq5mEcc$7HE5-Y1==emVyq(5v($qYIM84W@PBBxrasjdfl)k<9DCX%YL9&`|682@Zc`r*NJGufb2fjyTk=$}x`cZ&BMk8%LjcP-`_+C>{GV8^0HG7<_j@LG@=FJ_C zQ-XBv{`Zy7-Q&xn^K6M*zwIAbP{L)G7-*nd^;`gnJ;kAWvwG4Bh9~VJMYtR5Dw^VH+c@c#`U@dh80R7 z+d*9o9KQ7hc9tgZja&xf+pw?DWxvGTXSejA9o!Q^vRjdbpfe*vP}!8)VSPjy_Ob66 zmZJZNvg9a%pc&h2Oua2#yu?E_Bm>iME*TI$zmKP)(HAhiOUSiroa~> z8(h9K&cq$Z&5va@-9S8=MT1pJ@st7Wp3ODDn}mqnSd_&jIs?Ox^;6+y%N$AxL6wRt zi0lOV%3ua2g%ffw`H6wjbbb3*vpMw6Gy6=bolz$*MC|F>7a~RoUi)ynLYh(I@I;Ah z3L>W>gk8buMAT4~4`vOdWco0Yg3GYp2g_M1m$IJ@&)OBbJ^4V((52I<2qbMAiIU8|HoMKRkvNG+#}z{M z(2;!lE3u^wNnU6=4*@Np2Fbj%?`hh%_XTcDX9d**2XF|wI2J;)gs zmW~;!n6^Hl8L%z7&wP{6sEUMX%Jzb0BoWt;nj6+WAzRubWBZ#7{FB4LW8q2k;E}99 z(nNtMNHX$>S_+$+-D1CiSR&FR-UX#Y=Xq%(CauuYG+$0G+?aNew2N#wOQZ`|&-Z;f z#?#9H!kXoWt-t~yxj5e;fpn)Cf^hsB}o|x-m-k8QCN6X zeEU^BhIR@w2x=Q{gvPP9b1!^KlMPSd$i<2T<0}9e>KBPnb(Sb1hEma|3(?0&u0eLP zq(P9s+OW_sQP)s(MNIi0bBb%mnSdr4af!Idn+=FXc8r6fJLAkxJ9W>Zxk7=dL|1qA zgfzvQj0w+=ip9gtTgH*%K@~m;Fz1VfgQ=P7t7Mq0C@cN&9gTOaWX;x*R>Kw{%XI71 z3JM+9e)zPMo8a5QQbve!%aMUo*;gf^a(OJ5%!XBQ5%FpQ3I$CCLXII{@O?s{YY)Sd z;lrZ-@R*Dal8S~QO{RC21Nx(t3`2Qtq3sVNcguxAYGTp^L?XuxIZ=l#9HR}67xk<6 zSoU)5sPu+ebW;pg<&`ZmNOyB=$i|+AK3~B*mt$VR*#~?`mdFz0i%e$7#Ui!fdlf0D|0XTPQIVf#y+{pfec7gEE%kTo?`ZhmtAg7-U6)FJhJoHzZ_}?WpZE`8FVMDx zKMHl19Dz^S$~EkOzCJu6Lm5r^a#@h8GBg2Y$?m*#K&@$X~4%kOk_z zJ)W>_cA*Uou6nw)-LPqI3g6e;(L9(;-&`F=hcH3k*9)HfJY?-38WC~;vi83IuIN5@ z0VPx-;xON{bx;(ONQduGj6g9h1;EX-66QWNIUlbA6K2nEkUfb!g<(v?ek||)zJ0wa z@9gda2Cn%%Rdww!2k7Xev$Hy5;TvuVZ`BRmakYmphBbwQcQ$_;jY4k@F4dn!NK%+Rzcu9U#OO-(>i$z`n8T?S8J?3gmQm zylnA+_nhNffAj$TbRJPNawmKN8tCfIQ+{=IyUA~Ep+YPYj7ghpr``)?&{7RSszat$ z5KT&#qofMcHCfTfE?7(sB^YHEvy`P|*l``3M>Qyb1En>lT+0P2kn?zK5)hEzVr zamG30gr8bRM#Z`cCL)ClaZgEnhm2J)$-Qn76;;1ojmq`lWenm}{mDHwl7gPXP{F4# zy}*ve5TU|}e4L)4j`ntt>$y!@!lztB?2;cG$yb5oDNiLEnd!O;BA;QHQD>*)Qyw5b z_yFsidWYSQI3vpSYa%y<_HdCeLUEkV^<7Sq@;)&)Rp7_t(JxxdbT0WKSuuAo!@0S1 zMo0S}Ad~oKa|_a312ZZyc9&OfL$R>7`Ds+kU9bKui!+^+dvVg37D&fKsS=?F04e9u zz4P-}({~nnp6Zp@!yKb2^v|(e2Y?i`LSoAgQCK~q&Cu5KO8n~VzHEy2*CF8L3tj0Q z{f>LVv>4kA^Agrtc9=kVd{&t53f1#$s%B-HhSPboTo9j+998GL_KYsx+e;{s=g(nP zb4B8E_?PMM0=z2qUsuFhLlR$2I zZ3#^WK$h-!i`DtNuK0Dgo1*umUpEurw@X;>#k*b`wra10wjA)%rda@O9~?F_FU0I+ z7)okYK2b7p1hX?Cl_F+Dn%K+eu(mGK&4s&p8v6Zuxw#)3JAdxo;Shp`G=p6v%#md;W5Y>eF#bV_OMG9IvWR4aO+!HKRE}swReuFlmo-gF zBMqB@A^m-tyjtYpYTCQ@1aF-DRBZX>p7!(9kPwR&Ot&tJ%}Qy`d8zeJx3vKs`t=TH z5ji7(RXQU0jp@AUdEhzI>gDkRc%2KdPx%e1$dhVXFeu?pLC@}0$s-tX3i^$SQ--tA zbo72<-RL=TUqQP#8K#mmNRNquUvTsCIZI(DR7MSF2w66Ny3Yh0R^gXMk6@o@azpq-{Ot%M-f z1?sO0;haJuK~n5d4)<$uLf!M0yx<)%FVC_W-W^allSOVBoQvo}JMM7L zk7YKrX2+KUssP9TISBokw3VaT&Z-o5esjNRy|K z8$N9kDcya~iGy3oC?B*ngVY$RtN%rr?@4^{tv666(`6zg{IaLrwZ6yo_i%sMsVaTa8}yo;MmOJ`?d{O;LO|0hwqI0s^%V-_gO z9>-w%T|aM)X`+-z8+5c}jcKKoN1J4^3_e}{SE5VFA2=iel9IC$`C^%6ku_QY+gkcX zB+?U(fHv&&!4#)IR~BX~SE=G;YNJCSMf~!-H(GgdENKau(rmR9ZAYq6epS z;isxk^E@kRB8_SU<;=wAiclkRBhk;l-b2fPb2KkIx2YHM)%4$nz?Wre%%z>w3j?G= z%MNzR6eDtnx>0(eMU;N^T8(mi$Q$hU$@co~kl=QEpN$K>HbCT~KnC?nA3zJL zy0S(FUUj}#1-8n^+ij0RpLaYcVoO-3*wy(m6)F$+H1Jx=O9++uGR`Uw!_Rwc@*j6A z;|WDey4YZyG{&C$zNnIDZ$n5u3;-l#G^C$`q})r%tTcdS5bM~rzE&a6{3I$@>q>F!Uop{laaB)s1CIKFjiltfxC9VbB$wKi-sFbcBE2Z?hHx=qs z;1a@kAU2oED;jTjAL82&kf+6K3cfE9V%n0i0#|V0&Kvte?h9Rz#lCP<((i&{;iTlM z#ur=rOt${a-?J}<U^R{6~I37G^p_1p(hznl8t;#>TMp9WC`ONXrDP zrD3aSkSn;iHd}PvY>zqae1Zr*lcD2J6Qw={+MEofI$5%m(-R6x7UUM#BLT9$5}cD$ zeic*r(YICNIb0ts)p};1b7d)}lcxEwE?sHES}c(z*pcgPn=Q7#WMO1yjm8?Iw7lL4 zSbzg36_BhAEYxIDvgV~)Bd*8LOJGaxiN9GXSvf7sP%2o^xPg^H9@cbPJEP3ty(3%2 z0)b~d&9Kf;F5!SOWvJ};5J^!aV*!d@@cI2TlwCBqLL7+}>z75S6~!iU&FT%u5ruFz z+6h+xBiS6?hr|R>o|oyEmm+wv`%EQJ`#p(rCD+jzuJG~T1@U=Z*zt6^>_XYv z71e7iU)Sfq-N_i=^iE1IYJ6mh%b0Ak9P_+fD-xKECtEdaJ3PpMP0O%Z6O~cd4I_Vs zuuinlBd3Cu(p1Gu+tpyP{V>>Mx_6bFz?$hjcI14{{G}3k8(rgT2)v0L1Ll-3HHL2c zXn`O++EjyYSNq}jORR4x9>;sPEr^r`9b|T+jq~`M9=gJ4(ssVGb`^KU6Csh$&$N}V z-~*%oa*Qz9l=8^z=MC8nPbwQ z85l30%)1bFOFY7(1^S_^wgDCb=_R^A&D8N5DQ2RXPP3nigSm18p@;#Bp=vhkos#i) z^(4n0_-{q158pu@WS2;u^RLF;I=|r~L7&h7ufh(v9?@P|L9^-Q#pYY&4sQ~$M`0vS zsY_=NyfpJ+wHZ+OkKNt-6(7j*Umv|dWU~}aPknKRbI-?E(Yq4(z+K=w3x_w&?y*kw z(3~KzF$vNZ7hPnaC)#-`Zg7s#hp?!z!wt5r4!?U8IQB_Cp^uPL?Aty*zg&po6_km$ z@-*Pyi(_W1?hP5{Tcm3$GKJWd#xzO60@09b=hZUgJ5$U0`n(O2PGJE|DFH6)f)fVgj*q+z$o67ouJXVl zk^rS}z|-sN)hU10m3ov=smxmg|2?ngwa!Iq1?uhH2i9McX^wY?NNaI0A)9RO7wyz) zTD2|B)YF75+}|2gXw^%)m?iA3%H9XnaeR}y0;oz|I@K)-wz?M70&9Ve%GWj(KnLO) zR;^ze8H4eJNnsiu#5HomG89R*w))6=OWD8L3C7e|hH{l`qyVJ%*vK>MBW~YmP&bTv zhd7v>DLtMQ#y3#RV`nExB0J&pNTy8;jOs!ewrN*d zIT@v&bEJLwSBXlOkW1ya-Z!Tf&$o(2v&S(C=8JpDB57C;^6A(VY5ZQ^A>(NOaS7*H z*R&r1n|lRH!o>Dm>h)1|T)(k@7`pUvR~O!V#Bas3zp}I2`MmeTLAc(r4i{+46!fgz zE&1&mHlG7SBJssHij1zi(fHJhmJ#&f%UGCODPs+-Vu@7z@F*KXJ=Ps71{LcvcY2Jm z&_rXHjT~X;8MEz_8=Q2gh)fEwFW&I+R+n4sND8 zl||6i`~?iXz3XIvvl@)&q*$xfiLxTIK;LriOX$(%UG(2y=iBdAtlD{vfh#9!ywpFA z#l*3>BGq&Y2hqdNf*H&#H#^WL!!_mav~svPkeA^u?}JljUU;t!y6?Aa?SS$3<0--V zXXk1I=QJ#YL8&HCEKCLz17RQpjg3UZl}zN5tDnun416@z{wgZr1m(WIE(oh~`yK>7 zzg#JmC0L(N2jj7udzNl94S2Gb-Qz-WG1pF8o)MZvM&td5)l}BF=lEZ3w|746$&lED zV+k&q~HnJCttkz(1u++R?Oj2I@(&Ic3d08eLS{7zFNmz~HCKBJnC+KXTQj~E|c`4Nz+=``CPA{UcY4aYlP*auk z*aJ#G1T93}!y%t}0Sd}o15Up{`_zvkFc$(#p^;C(5Vi22>z+yy8s$U=wm~IkCk_-2 zO-O91N@bOMDap+|Q!F*d7jsUQ5NmFfD>;A|^QCg){GhhSlS+=u7>g$YT28X%UzD}A z>_T0}H)-XU@Rt_t!ifjr*d?^=wiYQQDpqJS7+i|%6nxdW{!D(gbipw6OX5R(r{;HZ zRWd|V6-Z>tmjA(8>(nO6#R`P+DV}31p05|BtDGG_jbYAgF&s5xIHaQBWU0|F{sw^I zp~7Cy+FSUwcELw{GI&3_=K_jO+xV!Uw0UpTy}_O}BC)I>r$2CmdXPV@=w2PkI6OBW zF7oWRyE$$nRGLEZlKMc$ZXskz&25>6_5l+l=6k}>AEq1bA5btF|Ipmi#WD74n_?Of z%E7$WgnGhEnA2wXJ*pXmAwLNuAW#XPMB2gEEAj+Zvu61N321Wpp5h1O~ z2R9)Utn&>p?_E4ja2fLNRV-5jS+(MG9|97$N?(r(4h#)SWJ;{Z?mw`U&w{TV0;HYbBC_N@^crPwt10 z&emT%03sj=e&^1ZYo|uEjh>kLz}M5OF1 z?_|mpl`@AbV+)|B=SF;LE^l+*>CoI(;Dx$*$Ku@&B+NK%1CNaaz6q3%o>+=pD3%Lo z8e_R;;SR`WY_h(|e7k(xIBc0vGk3k`a@SiM@7Jyy6tDFxm2 zj2n@=LMcV0`HVY?yGAKR=D!k)?q}U(aA&+~V$e_@Cb=iMg-~i~U}40*-OjQzYzo#e zFY=}^sA2=oLL09hA@=g9etc0w2cznuSB_Akx)8xQkO;J<6u%bJ9LY1T)L>A!2% z`(iAxW~HJAYu0%jux157i~nz$bxfc2-!I+3pH{_wl_(sU=Tfd~R-Fnu4bZEChc1m6d#%Cud9ymZ{bWeyd>3`$xN){Gy@# zYM~|~9W@WL*suy99tMldlt04rk7%U|UppP*8^NFZyd|R01cc$J;84Oyk&r`oB|OFE zkS$#t&V>_@Q+TvFR#WH#^5dj{SqX=A=^8dw9Ob^j2p7W;xS zo27AmCuFH6<7&n=Bk@PMUUmjxObwo|*Jkk=S%g}YbC}{oe|`HF|0j?W{G2nguouiM zTGj{*W67nErB3<1=5yzavQs27r{qoo&@Bb%vLl9ovYD@!hZ+Kg+l`@o#}(>SkFufc zvfd5g_7&)c!S9K<^3Z8zZcbze%*3{1(OD#J9K3PJb|-$?K0-{>Snfh|r}(`mW1H3g z@nKETiC>Lw1o4=m?{bkw0&D|s#0R%lmxT<4mZX9c^XIad{aWBo^tN>^ z(5aqH`MNwQ46{Q-4Uc9p&HjC1Kjc9wx{9hVD?wWbU zclQ?R!GP^n%_JVXjSP}V9}Iq=SjwyjrJVG6C%Mk@){eW8QW&1SJmH{pu5_+;3OBv! zfMoV3=?R9oJdk?j`#8q#6hvXA3jivOa*@(%K1z3%Eap#CNFT*+kv%i~+~Ssd(Nh!I z-&ZJDEnY6KS(Dj{r&?dKX5kh$zZ0SeXxB?tdyP4`h(SOTySUC_w|(<&)iHd4nvuR% zzr9=WSOAddeleXj?BvJw4rtvya%g@y=jauee;P7*K3{w~I9r!mHYB?W&~ePi(_^U~ zi^zExx_h#?9qU$Z->4Z`+yXqc>G=0{dwMQx*KI5xdD~x0S2Eqrgmfvj_c`*^*y1V1 zj3vZeUiuh^oIiQo0=siAFZbVGzW$b11wGaWKDED{ufyz%3lXrvJCnl)RAK3f$s43X zO$1|6BVr*isjs7%!n7w)$B*F8uC7i7$<;~Po=$aa+B07K>}MYcZ%q)hue@48?c;qf z(@qJ*wdwEY>wEom^OrLBLceT^0ibP6fA>cLHj-X|0ymj!v00ZcGEb^^6{x0aFg43L zW!N<-)HTCQByOdUjdriEq7A>j{_90T((;=ZliY)p6J;NXnZsYBimm+lh9WwO*IMA0U!Er_afvb;@NOCL z+8&89h9iy@m-=+Ofd?F(`+J%Q@7~*OjT(essZB(mh<|36vqsT zEYu*Xe?Bg*&?TQCAn{O4im-vE*?a)&1i$8<)`aCMbJqvY|{4 z_i8MXntpM=7xzp2EkH$+vw3_M8qtX!pJXmvRa+iV1)XIUmC7p@%-S(%6Hxk+)>5}| zbOg!^8=xK`EFfN2bokf0r7Lzz z@@v;mu!+&LKJ-7`H#n4|B54b6)@k!T48Cx04LB51 z*B43lbY(Y{4r$rU%+~Y)zaqA$BUt;w3rQ@iy6BloPt+*X@SxHF-r-0r+zbeZ^6L4{ zh{KFy)nzP{m6w~fcco`I7^PyZB_9Q8OiThvQ)3X0D1$pAjaVr&|CX#tIRxhl=%ibx zTmO5>--7-f~1&2%=tn?P+6;gf2udWWqcA-s|hQ+g;(w`nqkQ zZde^AT!R#2@To&iX4p)VLNiT%*hycrRZVKq75CIw!D$c0Pki~E;E%2L^+}-|o$H<8 zZBKKg;BB^6DN#we@^l<2U6UX=u}-GhWpV}vhv6oAKI7x%Zmm2F!9%F)4BBy@S)VuB z`(CCc^!tFSGJ;TUbDAuP@)k9=8XBo!u8fYHw zaT&g$mvO#u?sA%1|E*U8TD472?U}g}y()voq*#Yiv{EV&InHOVUwqpIB?otE06k3^%QAF}&F77%WhqpLqM!tqa4BbpS zGMU-@$j0!KCd|=6cHn~X)?J<6wQA&1&mKp&5)Q|GL^X^+T(U&o3`uv{Jxrc?DMz6w zasU%+E1FCm!yzzV%pb&{K{kdf*kd+QtHx=;GaK_1&X$H`C$4Hy#raHO^aCvVrk@ts8}#A=KwTyYcSg)CgoI}nzmu! zRn(lN)-08cbek+MxLAQxjZ}@UDzCZHBZEP-4 zIgyDx2IA2^Mi5ftzf!BOK) zl!+?(Ml#JYYVDN&EEje_-RgSktjXP9*1A6=TEb|BAl5()5nGATU?g@C5*4Siy_pt6C2iJtEkp`Ehy6Zyh3wSV z6RS8*^$VsuNJ>G+<}}@&#P_iIkcRF>jfI;%C7ah><}nau$NmxJ5)k!wh%N0rQ=5sh+$fX+x3T? zor#o!Q4^dP{WdO_GQ~eB&noy7oQGBgXj}h7c}NKVo$`cr;Qx#AY^3~+@?axF z+j#;^-s3DDuT|VE@ZZw_&oCo}fr^DXBa82C-nE3Io?}|Al!jewLvgsgJ-_a4FN!46&zJqx~T_&-bb}n?x&(p{_Fxya;^Adr}iBuoPjOd5$|^fP>we1#)~0cvuU$b?*0UdLu~g zKFV|xdaxwd>)`bjr{_Yy`s34+;H%(F?USLHN?croP(>vP+ZZ&>x{)U*9vvh06V&bZ z+h8je!3vGM1{+R#Q|LK%zQ-p*(t}4Z9}py(OXXLA9)Fi7!8hZH^z+NL*&ElbKjYKU z;WPF3DZhf@thnnG1aMZ<+X-TwSLNf+7ua2#0;!uzLDKNd@4ToR2=FQvdxBByNsjnr zh9TD}W!{#D&)na?`4x<2#au5U+}-$hy-fjulb&_>?S>o<4bD!}r&}A|lhd%2{+*ru z3ym(W@c^vDBYc zfV;GDRC*JOA`LrLi#`QXUu!{Ldp@(;TBAHudRqkdr{Hr3Wx2;V)vtSb zoO*2mo^%e+q*Gv;b4L-3-6^Qw|Ly>jm*QRvsN7ydrIft)%M>oNaa9e_{1Ojhj#A(2 z6(23rEnyl+H`C!6-(MPMj4fV>MnqJ|D6T!%oiYT@Y)rj82TjKg6n|}~YTX}bXu3`d zib`&BZ{bc3+q_Bp#wqn=<^(1>*I0Wz1ar+|^N**8H&~lYLu6)9c092L5+XXC+ zMFy8GS)q}#D-bA@2X+qrF(CYLBK*4n;g1uc{Cvq%80rKTnr#fIU?7}%>kj@sFq~{)33fX9zX}x;oYeA$0%{zIV~D{tzNG3aA8eUp5Obk z|17F~9eNpyEFPx%cy}04;$xCh(OHxmlBTkui_wEb+?TT`YDuo zY7o-yeqSK)m*+brtu)AT9fKtPG9dg#bqtaIcoiT^1k|9os}GvvONz7EYc~#Yr>Lzg zM<4&234#C5${J}M_uf8ZOYM*KfLgcxyn&&Vz(m(*C2v%LLNUR52gI%tf65zY4W>88 zlR-%a3$%u7xqJ1`4?u0tc_kvq(ojzGJ}2vdNtiBK8{`WaorUR{}7BS zE`4X3jq&n^=DWH=)EQ^-CO4qmjV9sMhcX9!60Jkt4cW#O3eZ?bg?e;=PL@hOb@#x>HXjD9E)cx&kLmFCIZpvG2TRDr`{-EF^(ms4h`ib6MaWA zz($7p_&{nHI=|6*=JC*27n}y^fuYufak^kn7Mwvo#aEbxNI&&A?gi^#bcuey_$Bfz z#6;+#k`VTVWUYNn23sqrX=789GuhPyOd<`0rqpAjeQM4l5m!jbk3Nj@n*_^1n#@q; zQPD=a{$i&eGQO~$2QRA!#QXp{I8ANxw5@$+B-r|1X4Kb8J z*vk}1s6iie5wm+p;hRLHK;c(!Kv$ssIgOAMn}w%kI5d_k8o)!=X!@ciWFq}2BQ#2P zdo&YvPyMtxVrtsZVTn&NI0Ud#MmRh{q?TaZ-iS@)I}p=8cLNQH4U_I8kP~nDULT_` z1Hdno_H*47d(3<{s!kY38Zq+4b0U|X`o8$&RReUtZ`FZvBgYG1i?%iHB z*Y3dhCkWJ9$Ek%MI#8kt)DKhMT|^v#_u2jk7za_V>~$O-oJWWRspb1Q=f}$0Y;SH3 ztd{<+>urRYi{A{=^__a{@rYNtcV9X{O>70dcCj2Y0{Tu?s#>)w2rUvwg=d)1kTGyh z%%3Tos0gUJ^qhFg^X&X2fA68_IVCP|O3gsX%MN()nx5!8AxNks&A>=AUg){N(elj- z(rv{37WLE=c+EA{w2U;ZykNa$sWQ;?!Zr=O$eju7-nOrY!>}9zc0StIGo7A!&=iNv z8gvTc_~f2M_V(;t+LKu`E#(S300M;^1ABYtRUd$3&pYki8K^ipXQ#Iw&yP(#TYiCW zlX+LA1=m1c&&!8;AlfLa0`Kb9!#*ogfzon>R*>j)RongG{s-~Z0>D_^hB~zT+ zD1}LxjfkpjSx_SktQ7W5vQ~$KvFwix2>~80mnNb-^ie2x`4r-_Ymze`z+Gny;QNag z@l7$>_*5kt)r?(E& zi2)tVr)Hi&#-_IgWl0O=bxK;T3QIGLlj;Gu)_h zbBL8_XdO)&qn0Mzpx{C^j*%MAbW{x#k7nAf_~?oGmWk65xr%~0i#0#|waP-fm9yHl z6N<2Qp8!p5O-OXQC){kYplDN#78&Gfn{92LP_a76&53Y&hHhx_gLyd2B=cs@HJBAD z1k!rxB{@x~G*S(lx**t}5b1GHVz~I+#kGvgRVu-K6F#9QVQAW0_`&yfDsiY*6x!&A z4>Da32)e!ZQk7^AWG#ME@Xbtdt}{%dRxCkw8mJPQoaE%SkkUS)+f7n9GNk|=04}aT z@tSc7e$@5|2z?8|)%2S~$p(lx_UdwI2y`dkFpKs8s4Hl>o{8^Sn107aR~z$dGM2#m z^Y0J`CaMo=-)t{&u6RyH1WjR~6_(Jk8(4zS#e#xOf^6l@~J-*`3av%MAadzyDci%|(iUa&Fiz-vr1)wXZ&;jnAEm6GG?jVaOmvoktx;K~h zo?d%WM|-q-w)L%ZiapT;-mZ1S_IB-@h_9slqyhZ&H0PH!_ZBmzaQAI1d5>L7$PmkI zBJ@a*baD{YX>ct+`0B`ulE#} z>7&&hR@i5qFK%QhJ4lh@pEprz$;4tgl8E_xGUPKjm#f1ZY|Z?SlK(k=-X#7s*NTHq zcPa|kwRq%Da(yxi!1hCFJr}9fJ{+}9`(_VBPityMy1aYG#>*#2Vnd*7Cl<9m0?aDKpdp@%i$Dv8bR`?>W(j&;P-ee zJ+=8JUY+4kq(J(*!5wKhBX8V)XZ)w750csgV3 z%JZorZW>}&L9d{|AeP`Hu}Ao$hhAEjhJB7FAJ)octpq;Z-QM0vtRH3Xb+zANi-OkZE59D6?_K_^7TUlFQJV*HL>HZlo zM(4Y?y%}rU)jMMF=5x294CFloyoIk~ruc7p*CUNkoO+~hk?}|9_tHwt52P=HreS;J zkIYOv1J%%e?l~i@NH#EWqolqzse4A^2jk0sj-;>r#G7;dD#v#){1JkeEW>?%9n|W` z4$>wnqH|dAVmb$WeA%_L_g7vM{~o*TnVcd9U#PXrWI zZdv&*CLOhp3FTe_Y9Dl}_CHfN%csI9PT-3u!cO}mK){M)<2KN*(cmaQ?8@R?H$AJk zZjv(zF}+eTz9u3yslVvniLBsj`HZqaVB$cPrDIz$QbRZrEX?85NZ4H!*MGkyhb^b2 z3nw~&rPjICxg}G(eW4$t@tC~9IqIOcS3eVhYp{T&$1f_ZizI!3W&7S`BeONmJK-!# zVcD|5GN+8<{f2RG+ooNWI02c`+*2;+vUXMHsK95RAFzFK-KJl?DtzS#^vnr--U0N* zoQ=Ib?)AO;d#`BwO9C$F;71Sz46 zsxuH>EOy_ssBOYv#fJ1z+HEnvq_#d;?Y$CYdUJ1`5VAS@`MFK=!L1mDo(Q7Eq6;~Z z1_Uevg7R!{sjJPWD*W&OrR z&GeAgVXw_j5HX-aIokf~=jGf1F|A^lVks7S#*r^p#*l?824P;X)&zp< zrPv!smOVUApUG_Nn@m+a>OH6D$G02L8=y+moPMwj;9iTG?S1t>-fs}!zG~YjC~*5x z3t+}puTF9wf=rfxiYI`l)qsqURCHRVpD=}QJ(-S4p)8IR?fK$j*~svoZC>hHhN2=E za4o~&PIm_{Mo6l2HNwRgMwS$U%H+Dub%cc&&!3fr)luV5IMyzZorac40mC2Ne}g1h z9{AAwlv#4ZBPxnNK6@4dTZHldk@t?_wRBszXjW|7wr$(CZQHhO+qPD0Co6VV>}18s z$$Iy`_nvdU{XF0I|IQyZt7=x&Q&n^HHhOQpk2Wy$KQ2Pl(VAX>2E=tsD_4!z2cdah z1z~j}SLhSddgxj2>(l6$HgO~J$ElFBWIWO%;0Y>{>oJB0#0Le1=?_krsqpG`lMPIp zxI-Mer-~CW>Xx~4VCmwPP9vsvJr(he!XoN@qceB{zHi4&K`x)lwGqOI`Wgb9#fBRq zIiGBff7Lxh2A|{v0-W(wj5Go90a%?8MPTtRS3jMSH@LZ-jQ;ef>-~}-o0vxc-O$~m zSk_7IhQJfB;8%;z-ODD@VJ`j?a0l6Qs3%|~yyNNcz84_Ze4KgDCKB}-1pHC^uchqrgsF z+VJFjw*JT_+BbJ_3k$osk{oCOXgV=SOZYT6{7$m)C5tNL5(sR0k+%90PCw1DnzzvHpU<3Ijx`OZ( z7q0Nb4=K1*i**YE#Dm0(uJC35y((lP7QS>1t}B8$bd!ujhF2$^{XOWHFI>E+GOiBLU0`^29bD-THSAvW-zelj)2amFV zF#lU&1hlyvh(p2eKCCx1*pn0Xd&uFDHS)rP{V6JBEVIGg=x`>!RHB0^sF`-~n@De; zY!@3gq7EwMtFGt@bgv^nz(Fctj+r6(m>b@Q)mIRMGAOhJ5 zO*QX*3<@s?2^@6;-$&jNK+dx(FdIW4|8CH7(HTcv;ZA|q@B-esP{N&{3aYb3J}Vuw?3!@-#bqoEC&F??lhEOW&tPg=Ct(cKAwICqEiT*sMBRQ3B2qSHo#qr8NoT5gj;e~3L-A? znQ?&VvB&>L-G`2lTXIKFs@>#hAvjE+H`iWDxZTn26h|z$jRbdDdJr)xyxmz13sgct zSedLVIlSDL73p?9B0jyqX#)l-qFzNVh?;~@-Gh=a5TAh1ab)=cLye2odPL>dTp436 z=2bRU(MjI>W1<4=QtwCrf-3n(MPhWs%$%&aaM$c>Cvc50!vW$)*?0wM{zGD(tA}dX z-X2hPAkqlgnz58gbR?~97uM;upx}WL*HokD`vXyaSQ*;EEqapWqc^4sxYTMVdb_WC zZx`>L-UTh6N!P`jx+&U`-MYw2y!r03k9Ex&h_Hy2<|Lvr$}vAw1#q@xG`p2nXtoIl(;%0=-mlyl|;#+(M+c6jxBl&zn!f&nh_~~Z+qVx>slP2sjL%zX3 zPAz!7RB>$tO*`RY-lGheEDw1Tv-Jc~EYml#FyxZ#jbLY{tMS9Wi%i^drV@n+zxB5X znpdxrL&=CK%pxbjo#?-e1cggvT=@>p2}~!-!E?I>6Y`Q?N`EAfdX@EIrdxmuf^&L# zVCIApAfA1@Uh-uI`N^CxL?ufW=oBTggWnL2DXLkmr1rrqkCynC9B&uF5OHfW!K}(dWHVY} zM&4om_^ihuS~>cZ6RtcvKibW3cI%8;g)3LqaE2jCtI4BVApYnc8Kn|;tYz_94`WN|OO{Lcdt)^e_|VUZ_!$VvGhNDza7f z?;R@S@jf)BhIhOy)^3Qex%caVcQ6J~ycbB?t!z`!uhvhC;^cR*0K{9op|N+meLM%* zA??o+W#A4q?~Jdhud`<{LHhwS?CC)1G4erDuK`Z&w{UMl_g{(x(Tj(2_%UN_-|s$$ z)DLi+EKg|3f)v;}5j_GQ_4wT#6$z=9hlIhPIC&^IVF5wt;m#n}PG1KR!TcA%Vhp$GJ-e5e$J-KkiWDbK@yal_&$h)se>uXP*mT%t=Y`kG$X?< z86$*%+sr*xKl_`(bj^k!C^?xXH`roF62#PSB@*BKclBGSX(~ezctk67=ImcebSn6p zmO~QMoQhWNDcQm{Bql=F3q+GZaE)yf2p+C(KO=KUbt0^&Z&emZH@H`v%2}J_7L2xW zO5XUXEeJlkv=$Z$pNeEy^$qs2;09waHKF>91a_j~##65}J|K8bpf79QL$S9~FX1?Q zL@(%hLs?vC3rQzVXmLDh2e}5$;BXs7JOs%_WbyD7?i=cY``iUOHH(jMPzYY(ME>^9 z*F6nF${S1*z!6f4`e!+nJ5;-8xuWB8@p<@sKP$-CS67C(e<&l5;Fz*L$}V5?1=M(9 zn!E!`!p+RX@skKSK+!2(3SVwtc4d|^#M>%*xr>L?z?sI*-!7_Y7qSgPsf$l@Cx?x;(B*g zl6r*yV)xFZyKO3nf&6i<}!?mi8P2j8=wA;{&P*ZfkvHQFb!r zc3Fv?kh4oLuOfOt1m&UcrcqIU(Sz~knd>Ioigh1!Rp9*2;ng?8JSD%Tv;X$gQ+Y!^ z^0~^9^X1yJp$kJe6MF4*yP>PLuzV>g43^j9=tlX_eyiX8muEdT|DW=#S^pc)s-y7# z@~jxhe*Xv0njPWHgXnz~*u%(&JIZx-@Nw_!W^*;T`KyR@*P@NVQr+xiu~fdqQY!yp zO3BC(_yGlcEcDb?OI%Sj?6W}UqW$IN<*h8sue*H}^K%((X!qAKa{+izCOcasgv1*# zb`SZKxUn?{g-Q?AF$M?sCZDa4qkU=Wr~L1y**7|$FK#d5D0CZHI3)z4X)QRVx@mjE zbVlzd%ZyYQ*ohIA^;cu1lL)r}3n=7$Y<)_Cxf}-2cEe^xVAK$AyR~$ld0TP@pqD)o zCpxS$G^!lm5$z)^^XaSn$(_Yk^mPqT z&JBD!91=6-d^Xu3D2BH-_6Hvpthv{91BqOB8Q@az^vjXOxZE^F&ygb?X6lG30uUns zdAQH!8+XB%6u*>~B_*bIjE`dpe9Ux8;dp2nOGRl?4rBD9C}r`N4tFkd;+_gv6_?NxJ;hD2T9)e&u1u>9TGHODhZU&}>h&DEK%Y z-CEh!qyv0YB0FkS##U(kQrd&X$;3M*+Hu;!jr{LrJXxQT6 z+v2;5$uuZ4-N+V(FxMUHtXu7EdRqDNioZAx)2dnx+w#-p`?~2$DFNNEb4&SHZaO8~ zEU8ONH~V$;iT9s`@vvxFz@WdyBl0 zpXww5*+F`EktgxZpP4)kiur=&vTWcLP{hD{0HhhjsIfL4(gbazmIcHrC@u*{Uw_^` zG8@#+uVtHPJQY{K#}bjzGZ$1Rm;tvVN2|E>k&7#eTR}k{#Or71=v3*-O~OT%D8;Bt z(GllDcdt0g8i!>S4RJEv6}5K8;Y;k=la9iyyOOBNVCm<=7@RzVP!Q6d4K`^0Y(*J(Jv=N{W}=HhVmV3bzF}vD{#y-%sml# z4zJ*1dOk*?`Y2mnpx4S9rf1}5B$o?-v;$>Wx9X0tN%}*?w=MFJ=+60Ko1XgqjQ5Za7nr> zAe`A5j?W&!+n=Dlp`R73jr{heU9XQ%4R8AZUpILbq|Shb3jR>0e#Q;+s%_B{ zERe}FO6aE8fG||3Nqb2RlRk~;O2=3Mc^30E`#_h1l_zG5QTWbeA(YvW*5piC_`W}O zskE|{(g$m9O4^|nb78GkV6#;GYqh{8r@)AQHo^N7p}^ffm6kokjF!4Iwh@$~@2#mM zh8iJ1Q*(OThWGoy{;SC3W|aI^$n4=Kt-{Wel90O&tF%&G)a3e+l$yUfXW4 z!hiAVU*ntOf&D5J{%BAr3(sk%if3!)W@goNO`U#p=1YihAUbTkO5i#Fq=Fm0**ScoSTo zy_oAGBK5ZXv#zh3x|2j|J5f>5AOJ?!Rbn<*Qp0)Uk}-E7I+^Y+&@mnBn2-q3cDOhP z{0_p}too8uc5X&|!sfoaef<8#J$vMVJkAW`sF9J7oCxuquFOJVWhF98oeG%dXSA>9 za0!sQp7#i%3yw@>9^T2SWRg^P;sO2~zK%@>Ym(@Hh}$z(}<- zGp+HHlIDF$bh?t;hY!##ch7EtE}g#oE%ypdm7~qaqxMI~8Kv=ZmkDv{G7c$!Do$a| zwm&`8vUksg&F-*PN(JSqopa?Yb+yAwiE1F}DHGN6Riuylr>i67?grtU%Z>=J*nTv; z><`+bEZ_X~sLMynaFQ*73aVvI5ijKz1?M9ZKDpZD-{7_I zMB?o3zL`<8t;5?ljb?7}BR7^6cJ+9ae8Ntq&5T5A_-2^I14i|WO-2;Wbl4|8>`5=| zEVwFl%1YJAW_OVaJ!gU+bA~@)84B-m?p8*dD9P{i`xl-E5<5halr>2gU3Lrb{ZUM` z&e+VUlF7=12T4$Jwo06L!BYbFUJxTg zHXL}#`75-MDiq-7otw4X{3%xav6xM%CayAX@I2HP2zfR$m^`ys`hn zjKaot;83_B3B8& z?yxUf3k!lYbBa#TLl9y)!?w}7N`c{JN-=OslQQzJ%}@}}Aq-M7u<0eA3yH^p`Ys1< zXC6~47*xx;YQ}GaDE59)FibhAtY=CTI6c=&Fn|uRMEgyL`?IAF%|W?&?_~qyK z_UEzBt9rY~3eRO=Wsm+wO9&60^+YTT==wRz z5)6Mc24p!WkWFICi2qqLjX=sMA?-!IH~fC8nQC3+Ga`~u&Seykqx7l zmUUOlXL-A_n<*zeJ)*6+UK9!Vty36&M+%(;jUH1*o5U$)N`(l0C!trM=!oKRb{w%W3ZRY0hm60T%5|0=iG^ZrRG9Zg9dRh^KUV>PJCMGlM{ z0h9+oK4mdP^2*LaMaO}+eZi+$Ww`h(t@Q6izxd0c^N3N!?-mnZ)uH82)Vz^WQP*sf z7;DN06)-y$7X?YQrlPg3Y_*+%7{i$6BsAsn)XMYCYDcU^8JZqq9pTY>t*48fCO$3c z%$oRW8F@oZ_oH=TZOjd`IQIW|J#CIN8Q(XNx0j}-Fq`8C1lNIr7w+(6eE%P{bql|&t`9-)Lc6PE~-ul}9 zbKXxxyF+&02WDAouYNnQcEL3OTvh-vh2owapwCSJHFB)}BG6sk@W79ROAf$Vc~*Zn zO#e4t)tX(N-C0=s5XVO{qe)ol`RSwNND)5JT=p7s15~#jm|kTKn{94zv0U|Vwl)s= zLs7~S-`5-KE662==hMNa7ozo1g&TU1XAN(sP~5wedp;j`R=>j2Eib&>8a!rMGUulj zkGB`Rz@R_F9&Zj9V7X0oGt-ME_xu%Kx`m%C){k22XL62kpWi+1f9;E>jM33pfdK#n zVgIkbSlYnD&c*rPeeu6G{^^Um)6T#9V)W22xK$qTbvqHVGvg&%LM~j;VhQ;&G=3>( z>SRQgMAu6g5Kpa*qX)^?ghXl%i6-pmj|THd@8=$GzT2CtkJ{pqRC|SbOY2UvSc`Z{ zBM*hGm}$8-Cn{DL0*4f%2}(xRC*kyKvhUGFwYU(Odiy*Z;y~yr=z9$vsg1a6tz#?2 zL;KOo#er^ABU03RbbU=uv3&qt#jVHe1v>kMj2oZt93N|zD#pemjWQCHYy9j5>Vofv zI!lE`%d-W`+2H*r+oJ^IJ}{Oh1QZM~ThVp}11x0u+*)&+<@OLLZIvMn`699pR0=ze zg31-^pEKtoK7%fCRhKYqk=h9BxusjZ=2@qG%c^J>UR&3lgt(Wg-O6(5;6`k$KkIs4 zTU2n?B#5jin|fKb^!OIO7u*Z$SD^J^L6j;o_Hak;IoYO!n?aSdx(a%Y7ftk(pq|O^ zqT2-`MH)QkE?nmshnXv^HrwZdc(X-G2qOX)%#;X`NXXc7P>wpqJ5(JK@a!i6zd^O* z4rxtZbX{}@kec@?wV1}{OoEC9X%BM|b+#4TRAo+dN`lx_Y-CmO3KZW#oPx7$^IL6{ zaOo`{i%1+!gd1L8#Z7!PnFq(mlTp{qgcySkLzsE>2_V>E^Yxzod_8aGo()oWiLdHJDKLLaNERto)z}lN;^jCP>B2xO4~L|!>9Al#UkDg+EYHyST%cQ1O&J8wWPYti=^9R3m=SYZ51-vwXxmI2 z^?1x{ZDzHZW&*v=SIldDMKrMnmiT6hI#Iz0jdp@7SmwO-nuRFwrI*x4d69D(*T_#$ zg)HuGUum2qo zH^&?^TlP7u=3-($Pktb*C-6DeH5nC!tF1RdcpzP$!ab?JwjFel=|j+M8qy2dA?xL_ zW(Zj}Gv^?W+-|V24&2}(hf~AoTktTLbO+boyM0DX@wm>ltirOnypDaEMSi)pWq0=- zF8^21={njg2>Jd+2kO5E9mc;3-OZ|hK?nJhFaMgqyboo|X1H8O*pV4pJS$%o&p)9& zoJPcvxMm#-@}cFg^PgNy%++SHz5j_o$NBC)^GA%p5%f%sC@b=##u$dU4;4HE z_tF=|d$=xG=c%^~5kL*pO#Rw@Fz)9Rtc4lZcF<9q5!wuEi@8kII6ziMky9&+-t1IC z44?_0#+BLR?>mT%Mh?G$bu6}ut{lws*d1Qy zX+(>nf>8ue0ZJ{=D_Xb>q&Y-ZO>ZB9J3AtIi@6HEUO!-FXu2NpczW&QUK#TE`KF;z zX7GjryM$x=o4mM`2p36TFHUVzzcxzOAhdpJ7J_qJWam93$g{c`(a5< zvX9m8BnvBX9YcBJ%@cWT<#nG`;r+BsBYsq1zjTk$k%$j=tN8}#>AMmJY>`=3Znm)Q zfJpvS9>rE6!0s2IPVF?w7dr`mDZoked+ITwh0G}jOgg<@36+b&(;q2CJ{RbP3MQj30Xx0+EjuH z7fd4kY-vcA3~8?yZv@j5b)=wMqS!biR3LkF8P~ET636m34&}=A&ZpT&2wj zqPX4znj#EOJ*qlbqDfg2y&Mx8)c!zBTA9*Qbvm(ag0Mrj*{KF@jvq^jGW@J$fVIaT zEf!Q)f%o!eD+h{n;}%W|4X&kdTr<7+UFZ!)hMTZ~+MwS85(D^|vOZoG9xE*kEi;+j zb<)qBB>OmrX*tW{R(f@tvJN^Uu!w6}>2srv?zjKZhV7C&=v==Y2Xuu0-iDd}+VGV6 zhV20>ybs-+AO4b=2-=xtbyN67yAFO9u*)AH^oZDy65*8M9@i+4KW*!?YjmgaiPU6S zGCdUthr`|GrtULS@j|cPW;{HX38``CY3)H)8)BYwUs!!TvJ#e+k)tk7sfZSQn;Ba} zld`h0nCIuY7)?w?JC<6942ac&ozT#tT#3OEJC)Wjg5;EGol3B>}@V0!=Q&1=Q!Y1XNRO{T2Q$Y#4dk&H89`iv99R&M6{qgXDV#X+v73Zx|iEe zc>NU3{txPMwWj^f!mSr4TZtjE2bU_3?wa<^dyy~;7;mPplpWc;3DI zIVVuM7*D_ffRQ!1D1_|7O|M)a`djUe!((^9lU9V!NX?OHF)IdLtL7W3roQBv_c(!s zomQ4~WydLPHBnZC;iC0+&?qJHUVApsi@5~^AI`zIv;*rY zzoHMF$1x7M?&1)vFAbnj*P-`kBRkS|>1A^vJr6r=z3r#^(9D$)91K`xG|=q*Pip5i zbp|Lbn*ZG4$E?>SHZ`WFL}vu>rkn&rG?PLv_!PmRhMQAYS9Z_MkDo}zW; znUtQx@%IeLB?8TpUQHG(!*eye^G6lY$HEF@z0rlK3SG>B059?zwYJ)3fewwUa!hfuP=Cxro9iNp@aQ2)bkw z?mlAc`Lw&0w4Lw2Vwp^;76V<4cDJ`iffcPBxI3 zXE5*W&FhD=Wsz|L5E=Yz?XoH;?61FUpd&ILq_oQY;#xMV?IpdGlqielh z->>8es_GM(UV%iNv)Gw5bdI%#lB9)R31N;X(R|BxXq7pBj9D zJ^IN|mbxp~k)JYGAUoX?Rg7rbcOjhP9?Lw}IiY@qg?l9)dtSCrq-C}VjzrfBSddrEje`dX zQN_d@cH4dC?OIPFMdX*;>}xKuLyvpcE&X~y?WN>sn(L_+074ELOGmC&UTRZU!yg0$ z6!@jUF*PuVD;wjpd_4w40Z~%z1yT^mpa?|lt&|jNV-CW)1)z~k6YvT)`3*Kw^gj(>i^vM&x3d_sv5FN1)lxOx=`vzg?-?m1`y0%9=LHx=R)2vMS6h*txNioJ{Qr>7&I zs;pqo1c{uf{z8@vQ=PF1=gjuqMlY1uc_Le7@ChF|^mB3SupVFW~M|fjGwPbNql4 z0hN~!yMqJ?VW&PRFHj;$g)uduAqj_B4sV`#=;fERw~OL2qP*_2bNlR?&>yH>3xI1G zrW8aR3Z`!LMk}7b>Ad{~MXnmRq&|U*0C=eVRjk$qB(Pe%OOPx)VBHHhfy3bfERcC$ z@|H0%suAWss_vNOG^+9#CcsEA$kz3XYeRAm&oqt+3JL&Gs^B<&=0u*D_Hs*KZv7kX z-Duj5>R3_eIR5I%Apb8N++ew*fW2K~4}D@!1>9!`>@YsWJu$*}xCr>i2>K92o-sh9 zD=WEGLCv_Ckd>9B?DmpQTnoBS$E2l6LApjHKg`tttXEe?Cmt_$8QN}kh@?G_n~pvk zlnE6LHgnY-T8>W5GpJ=F34e3rPz2(Q;`pSo+vTHaIM;o0sNDEtE)k4}!YT6QaTh(* zq|>^TlH~5?qgj$e%P`$cvzd!DaVJgD{oDkOS}5Rcx~wc7UC^>!krYhGSA3S)_mMs- zOC!3wCg$9=Q~lj_v>^JH*`WS5Fb#MarCF#C`fbsgxm+ZZt%jekkCQun-XC|f5QfXl zJ{<+V4{3s~JoSeyj>IxyKzve)T#o}8II>|4KD=1Mf2h9`8d6F3)a}pJlc;pqN7xyh zCvAmw`Q0}PFw09EWr3-8)MYYi(Hzm+1T*F~XAHBKl;tQ(U#e`bTVsz0fAE=GA8Q&^ zq4<7%R-xXz#p~2a$20$ao|uAH0x?1(>Y`YG>IKMo{|++#YX~xpb*g^*?Y_jK|M%_- z%RfkvvE^9AiadJFwdkgQ!UBc3KiM=RYdxAXW1J*?=>q_Am`;mNIUc(;Gjn0p64xZB zDB<6LGb)9Bt>R<^EY6qX^tr$x?D1ugVm6XUpSw_K7wGm)eGryPXmUSH%PlsbWP+l# zi`$DrEwKFx&cdVh{XF$qJJ4{9`Q=OiJx=Zp_cYxBZ!+8J!A-{T``TOr^2JtEBQ~Vl z1kQEfrpWj>0|sZb%_$qzlqc5(L(Z&;nUn~V&QUQOazH#Zq&aVPF_Epg5vs;GSSlK~ z$0|`hZ9(f+ysYY!sHf*^klER+H90Q8gWw~MOv4bFJq<3DnT|n%pdB3C7FrS!i*nXF2-ljIzj}`n439?ppk8`7#)5@j9bL2f4ymRo7XSojZCU6wz0cbOx39SZWNagKeC zc8f)K8ak66NS+~;Zz^;yMJ&Jdi8t}XvE{nJL`gPY#N06?F3;+Obn$mm#{W5$Z2_N5ZVMB!%K#Y zCL`6XUy+?2fx*9M#;Vet2=20%FHD9csJWaJrUgW~%bLh`)Wyd*clJ#AW1AkP>ztS5 zp+-mpeGmVN!pb;*C=Z@W#(l5r#dv8v@HsdHRlL`q1R{VQjGdeF08}Z)JZqKz^#o|b zk7k~)`b?{wU`cLSfD7rfgiy(KC5TcH>Pf?FA5aF(j!-GivSe3)z%L$hCR|RqJf|;3 zP6ny3Rst)JQdt_Rr5pjQQ^LuwQl5!vqmXdTSvMzHQ2A$tcMd~(nt;pNa$u4vbG?SM z;5H}PRkBu4rWSKSP@KTN_(LbhGEIF{#GCnx)`v{q8Pk zcr8x^?Bl3$#)@3*!TKag1q8%j87McwtI`EIk%DRa`lFbnDtDrW-Pt}1P$>*>9;I~o9_U*Si zs=Y1(PV}aODCI2#B8C|4E-?P+J3hgFWFw68dQJ*$(I8SKL_q?Fg9hyN zpa3y6%#ncSju&1lt4;`*umh6?UXLO<+(1iF81tiReI(NY?EG@E1NY3P$%`58Xp7yv zN^MfF;_4DgKGm?3=@PZm>I!_7 zONWJeMAvC2o_>FUqkVlRr2i{khQ^>$kbQejZQnEK{vSEp|G*Qo{>95`)l1n8dgKp2 zi)($8J|W(X&`8vTWZ5MDLKNJ7V%ivc>seQ+E{KR<>$qWLYb{7XK&%B7euksHPp{cd zbxuz9?H1&kNEMbdqPop!p5gV0YAjv29%*PweE0DS=_=`tqN3ZWF?EkzcqaQz!61O4stE^PXTL`}NXd23Q9*RWcT?w(=J`*Gm8xt8iJfA*9jadFnb z&J$6EsYM{^Zj%qO-!MFAdB&mAm@!2jIqh+Exh~4)_B$J%%FI;Na2!I4c7=y2pLT33 z8tMgmsG}p^C|aLo(tkN_Tjq~~)J$R(a}cW3%u}dliW|>{4TO=4!rPcMD1cYZGz{tK z0Sl?nJTgIqh+OP~$oBPBrL&`usUtbdI!L5utNN2{hvcV(dcEUZ0hdC-!3GzL(X8Zv zcl%H3tG|OcaZ@goADi70-zL$=d{|?Oetr^j#e!;NUgD9sU3seTtNen=SouXIVM=w` zlR{REsUsh_X0&(aKo`E(Ix?>02=J3{;XY47LPxPQJFC$>H`N%;Lc@IR_t6HV6#LJT zk(VocmM)dzW8{p*PQnxGW$m6hlC2QhIW7E$tcBI{`h!Xw;hy#}FeSe0C!w$Qyx*S+ zi9^19;+{#|kqhiN+^RKw4Ktr{q{Uw==(Y4M$_`R}ZZ?H?;sk+R*Ohu=aw!2x!uIkB4* zz35LYD&)^ZcX0#|Ewm;;98YYvY6FkAY8f4u;4BsL{nkSpAGJ(@IBft{%5E+|fRkHPjL$R4fcd`=)w;X>Vs#CejJL38V*OTQ-qSfP|7hE@m8smmyw+xA_-W35O zdAYo(!rGmrI*$=WX4TN^IoPJ2*SB|>3AAYzMyaghP`3vRI)g1?O@pbt%VHxrMe_wP zmrT0~LUR=?Le8%w?JN$5i{stW=pyGsQE=-U7+zjs%?>~D*d-X#w9UP|c2Dj`LGyGr zr={M45Q9aK=D4H(F|bqw2!ax+rGf^ z=}&o%di-cPc)KjRPKgF4nh&!Z_H(F1LvX8R3Nv~vz>y)VUxI1VCrF*QJE52B7fuDBKxVCHtN zkspIle%dzm@qYd|z1^Ed@Km8Qr5%Xxgm2u@z|sYZXU2gEq?X88igP5(j0>Hi#v78w z4O2)i8K;JtyvYc#p9;hTh<)Jv&+DEG`>ZdShvKV_^@-9@Rbz|D4lO*>9Pu+{{RbR zM)RsBvPTQwKD-{{e{U`9f30OmUFR zgGStEfKpR%C;2uI{CN`ZA~{K{pVo21CA*(iTs8C3ecOyXOSUOkx87KHPLUZu`?qla z>$!&NGG@(Lkg9_wP&DR;w}hdJK=Z|CTvv$&Ia{wk*cf-vSdv68CJDvv&+rsL_~=pT zsc3`>;>5d(DlXZI=856D?o8!2ZI~Y2O-CXr5@jfZbsMqs($7>VlU1xiyk-f|NeVh+ zC=7 zI}lmwKo{&WLTtOB?U><5@g-Co&$yc4$;so<0y*1k@g6j!hAB!WbdF24WF?C`KRZ4l zlRB&(T7sInfT&)IMjX(SkveY8EqrpBox+=0lklW1!)B}KiOMSAELF6=Wha9z!&gYaQrUzxX^2`qF z3bmjeuy5wC!wkZY+Gr5j_xsw_#^I}HT%qb6x_Y+&rEg2^u%EP_`O@D?{Te&{yYj(P z+le=&A6In+#nmJ5g@>7Kyrf?TG_)7!)Dc z|6C+c>Q@S8CwN=3B9^p!o*N){*mhOvaYV;o?*klOh=(vj4%97KSn=3uD>T`{bUQ_lXmg0;Y1Uj zGpj5TzEfY87>1e_kV(`tQth!Zw| z6&J{##AqswxLqPLe%jD14Pxj1;v~yHm0z)TKj8_^a-EeRZ)TimM!`~hJp3W58~8LP zl~V@L!@}YfJMoz)Szk81&5L_MJ>$J4O@PMc~*Q)00 z{rZ@UyZ-7Hs;Lz9`wV*4UM_5j=9tBg-=l}!RA6on1&^cgMhIJZVZW(qxHjO%6g?)o zzRT(s<#&&_ue>7jm3o{89iY2njilbIHdS&+tS{^#ZlL-j?lH{hA{Utb$J9vR(8?M) z#P%duyoYMd(xy|TDrHE{_`vGC21NAH9jFJ`;MZ#NB37eB0pn4T0BOXf3?O<$9EhS25{o#V zVF0I^KkQD4zQx2^GiU6gdeoAne($WG6t{z;Dk_B}M}_8RN`o8?Fv91}8EGQ$RO?rMW{i20awBwx`;=s|G(PI?}QsbhjBn#Q9!r9ExD>pmc zdngI^lh-xTNp=0Tl9Mf${gvVhSX=JBv(Kiybc&o73nC&`Yl7@F5x{REy7cpltGvu3 z6#)P68QM%YsVJwH6a1Z6dILbC>_D@N8W{zd$~U{E;zxl>4=wQ=m`J7KopJhY*^0}R zX?;$tMr_(}J(}D0%Epk(1khD)Mdzh<=Owo4aG^U-GK`wj9woK#2+f=<_zqT&RuyK> zBns=i(rt2!@2?TP7QVN_c2#-(n9`NqmN8xzu^U}ZT4RZXW(O*Eb^2*CuOj)KKm3y4 zYP_;=Yi7?T?r7f?3p#x-^Ew+?evga08=!CWy3R*zl6%4Lhl{7c@PhIi2TCXCObKnoa9#_Rk6 zJ2Yj4rh79h-|RZa;aWTO0-ZQ(xM_%RUU+;m5e}w$27m&8MMI|G`9-LGAvOAdysACN z80}*2A6&~aJ=ywSWbJe>S&7BWb`#K34tjEI%_AJ>!m}Vn6`OT{`adw~yCgTV&ify^ z(Y$O3+!z`UscZQq9?R7rMcB4z zDyx2fxW{tsQizY42Vnf?U&hOoz9v^_GF8a2Q;qD?A4Zdh;pK$bgfy*** zYS#}B!-lr_0{mTP9}B?=qcpKQ{=PyKD7Qby8>vxP89ojj?v4HK8kmWY04z!cp^asU zGVvt5mC3%JB3bvE@6sKvWRhESj(Cly&?qI@QFjtCjc1sxOv1UV)mcUjL?xc%$#s?Z zXaYfte_XlXr##CxouGSp!ZjK}378mi0nX-|h&h-WFv9vCjzF-hyBK@{&h|ZF@yVRQ z{p;%=HnNuWhp430`BEX}(Q6em!NV&k1m4gy^AYaC^$UlQS143&g928C`H}R z8?lIH(KtKNn-OM$6WZVWODu+5Arx7(J#wDc&YN8!w04S!NZByY*xRGvSvKz!0AdPu zTJlns@Ho?s;~vuFW2*rel?UL&FU4Z10t8;E$mFTlB4b+j^rRltfI%oPOI>$i zFA2+NjQFaUu_`Kt$oCXbR|49BMnfXW82Ne7(^OhV0xDR6%yCKfmZSpuS!h3Gr<~l6 z*n%34_pk>(B+c<{n_JDqu<$0eP3U+8$Ng+apNmSmqTM)Ea&W)J?1ljW==Jc=>@eMf9l zc{Mg(2zG!Q{dLX{Uymu@ zUd{Q$DfbRr@hUEO3G|iSzRC)DpiFkqZ-xS3HC{GY`h6HZxT@ z9EUv7t_X1%3s5~qL%rbl^|sf?G&MMK604tg**(8u5vP z%O=J`s|ew#Psa#pEGeTvqF*rV$5s{RMFPcQ1))3EY(zW@e<;9w z=)?1!fn#Y();>$-rF5TaNy**B2}8=-xM4i|@U{OeeJ3->EN-}t^UUaPGict%+(RSv zMbGdK8Galgl`!(t3(-NG`~1VG88gjUeZzFex{vR0mKeejOY8$p2 z?C?H%`Ct6yvyoSg-`QJgE?U&@vN6}EoRpJva*{^k%k?huxt;ZoY+s46Rf`ACqM;NA+ep$-R&FTkdKCBR{B;<<3&)nK z=xcIZd$>(_^k^*vO1st7idSI5iJ`4-^lHlGaWdIK{;~4f-t&K3IaDPE#;+s8b9^g0 zwi^WGTB~Hr0z_SDpe7z^051ffsQf4 zmz5(+yqnMN$@%4Yr3HDbb3yEnNkFxM_)*^XTlVmx2AffdLZq1H409XVXW)>`${ zt06^HiX`l!G<|r?dD51pLyu|6dDUn?kZ@Le;=)4r!LTs1oD4sfx9xp=y4g3TqLO@W7M!i#gWrG8zJZXPUc)VK07JL3sGJc>OpBab z<$C+7YHmi~HXZb$k!{nsNPb?6U&|z9*Y=KR@RO#FMH@rgtg`Cy_NVCUyVC@yRP z5EcDp@}1;|f=DWH%_ZtL3eqa_?w5hFl2`_&G#^4fN1m)MnOBIp-F83p8nc(i{GxIj zeX|GfYrHkvKM=%D->*<)WXXdOKiHD)<~Gs%iqpMjdCJ00PWIbV@vUoUdwhWwMV3OJ zzhLr6=j|asrRh(~>U(7pv9`MRT~iQN*d7QlBb;WnULEl)>Xj+#I1*AR+LJ!JB&Unt z5_->R!%a;6D%*SzQ+9akdQxecCm7_QT@}o%tx~4=WYRa%_*}$H^m_;Crt6wXA5)hO zDcqcikvx!@PKfnY4@OyeO?E_4k>Svo5m@*QIJvCo)sTAhTVmC!%b3#1BW;g=diXQ? zE%KlNVCxTG#lNukPxb0I_WtH7@X!1fi+ucf?D1M!q>d3nm#0vs>6bIsr<1;U`z*O% z!(Ns{6f>PvUJ*C=YN_A-lBA!q$NRmwYm^AdyUal>Pc^Y_ql&&gU`BNW{&BcWtbacOwWz_bmX`;I}^eeXY zz1<>w@9k0R$L_8;LaNky@e%UPN4959fmP=JJ_7rnbK&RR<^Omt z{Jj17AJ2uK*UA3lx$yHU&3`->ejbqj$8+K50p@=^7k(a0{l|0Rr#1TjV=l1&u9W_3 zt?8#}>VMhrbMyZH85?XrAt4h1mp*dIKp=vDj0QVXV{>E9pI?8Hy!}Ekh@79KiTHxL z{X1pY4k?7~0LAq!qoPAp_)OL^)4-PpfxMX#k&ff<#O0ng5@48ZWDjcNWb+J)KjOUR z!bCT}pv(W}v>m~2BDWMRZs;R3B)nH3v&Skj1@7|>Mu00NrYoO3{{Gz9m>fbC2QXGDz$S3bGL}z>Pk=mH(QC)A2Q+zgZI&edR}Il z6SY*t>OhlS-+A4Ut&81e|B&0Y{zTJLoMSnp*tj6<`AqXm9WuPqjw&O5m9+OWt#{+c zVkj}Y6drY1r@Xe@&_nQ_XvfxWTm4jfoC_K}TXC}Gs2fn9u_c3vUr-Vo(580k}1zpVujNZX+OJs6p|B6I4|0kx2`pwtx|pqGPrT6LD2OBo@n626Me*eX#())NAhxM+)MEmM(#10_Z1{< z*WRjk&Uo#&k5pI3t}L*{s8S}qbio@koxR7sa{ntNT>zs;_Ls#2E!uim_#%aNvDwbt3FX3JwW2 zc+&F)k%{(Bcc#EmrKc^;14>%Smiyw#NpCGmYo*V8qe*S2aJq%ZBA(3Wp*w?^+38U2 zHl^-A4N11fk10(%$V<+^{hre*>%GZJU(hT3*$7;>XONYBxmfA0(G%|MZRGg+YXv>- z==f6dle;SY1>icWrDe$oy(q`C=7_Vid)xJK$5d^dypZjVfmLA2~yJdlcPT1U$Ua1@LXXZ`Y_ebD)w28)fD*4X?C4 zBI{nvP{k(Z{@CNGd>}ydO&Ljb@`d6F?&15baGz+KTBVP#Gc79zzAio@TP~{9I%r_Q z{-9ae>z-p8S0DLBJE>^YXL6`9|NSOc-QDoXChPze6Ttvqug3+Z4W94FCZB5BVX;jm zTo$p77)KD+Vjz5l?OUZJMtDhyCtdWf%iAQVaEMn`4{8)t-y$g175AV2DHaT^U4wzQSzlWU^(8>IKzhTpk~m1yB8m1?-s_ zNfqyXA=cJXf9}lb_^RPDbiPp1B=T69AlaKMp;YJ@N{{(y83-jl$e0K5t9+CzVFz!m z7Ms)#q+%5RJ$=ahD;ee>=i3@93@Jko35HLUH%>bAI}Kv99B1u0`Lyozv6IVIffG-b zuFVe_)LCc7HCKiji|tb+x2ksV_nK&;KNblNe>4up9;w=)6cWabuG4Pgi^J08!uw^>9#-&U+1xl-$jTTmmf+(6}|K^|3br^uV`%?9ud&fbPr* zi^DXfda|#f#*{^g@dmg<5bvmY0+E_UR*z^AM}Ipbu(D5=xC!Bj8i zAA6$Y6u*zcyO`vG<#)aOC)y`jFU2(DOI@gB?yeC+X04C1FOWJRtOB> z^^b=JM26EO3Z@5veiUNhH%69*&ulEMY=LFO-u#t?f!SM*H&(_b@Co=l2v=TOP8x)O zfB@15K0xqk&?6Ayty@1nfd>irL`FkKMnXcyKt)AC!@|JA!oi+vmKHYO%6 z5iZ^x0zyJUEF5AIA_5Y80z!fxAP9&+9VBFQWMp&#Y)ou||M3I=48lVLc_M%i5okfT z@DLF35a8`VTs05^GSJ!|4gSZ2a0_T73Mv{p1}5+amAIf=2#AQckPv^g8u)8>;C&Dh z9x^@+mjuckWdl@NYXWYsh;%eM$4_?`Lqhk;elaP`z++$>7X5r!G<9{e1 z_~@~ew2Z8rysFxBbq&oIT82i(uT4zN-q_mNJ2*NyyZF3&@9XCu5EvO19TOWDpOBc5 znU$TB`!O%Sth}PKs=B7OuC=YbqqFNvchAu9$mrPk#N^b%;?nZU>e~9o=E337@yY2K z^!(xnT|emj^ZA3Y8+73TblpNiLPSFSK^MX;N1!0$AtBRnq2Nm>qZ(M>q2=~MBan

i-%(tc9*_XzX;f1>Qa2>V6X6bK6u0q8tLJdhaZTB>=R z=bGc}X?a6~@6?t`Lecg@#Ak2-5nn_Mstq1xQ{bBun^ZQaZ7#G;Z?H0`zF^w5kw}Sq zc)KkBqahnNm%iGXm0z#ew%pzMDj5xNWWog(;2thG4>nN;2erIE?Ad^kRFA%EZs!Qj z>Lza9HTHsoP)h7R+zMp+Rm#>5L}ScL9%b}Guv`kilu#D%_qV)zZ+(K>Bfd)q*-acN zR(J*l=WOcXu?ZV8^et}kS#@EEhi0IlRkNUzJDLzj!a>Bw#{!7yW8ZWsFQ0h~5v7&a zyYXMjhD5c`GFN^p#!5Tv9Bp$RzP`f+v)| zNQWs*WD~K-%uU`r%@`>DapJnsVmG7wM6A&PKh+Ut7#r znIbv_9$8L3TFk$~gWg#RBlq@Rk{Mb_{ANU|UQ1_$A(I{x?!nu*pVl=tN62oZ1)x_YeJV>_`*oE`Bb*g(aa#e?J} zk}gPF5qKN{D{kw+##q})tW0bAD>$tjw5VS%PyCP}(9;H;T$}{P5;qSA1u`CJ_)@th zWYr}y6Yb?qOejrPPTtyeT!dl22VWEx1iM1(WFnRSxI7mN4 z2o93b9aj`bCQ$xMA<<=ogTg{EVXe$?P{wl6RO^A*m7A&FDW1|bF@wb*f)v5C-wW!4 z10k4PNs!n!o+D5}x9-gm^%P^*SQ9eT?4t~CMYW^Wh|Pw2gtTNBpRAEkO4!V^{PyHK zpFtF3d#%j@vDAbOEWAnN&V^F~sbi~SHkglCJmr_IB-GWB91Oy5i}y|qpQiS}PD(6k z1(y1{-Jf;_G8HLcFlBiDS%j;`rAC*adytQC&}+r(*tg>R{?oKEfu023Pc_ZENGO&M zkThbT)}nOtU$qD3OdnrjkIhXAcJg6O*Snd`uvg z)dSUy0Z~q0y0IF<(s^4Tt&8K=>|%T0;2^RsQ9(WJ7f?Y2Yij}&?R6C{*7viMVoU?7 zJG%UOFJcTnj(u2BVXlXRBF^`44Xq!(RWp#bV2v@~>|cTr6E*)qcxT+N*U ziGOSqO~~l|bk_MZ(((K)u;jpeXy!f9(LC+XVjF=xekUEabNJ21&cbSSI;#;SX!Z&; zY*LmPU-4z`waz)Ns-y7SJw*G`bvG1!trs`hs`l+z*lf}}jqkb4?i*Bv+`&D8r!m%n zith2XLgzXioE@oWxtm0FmG2u*Lca3HYH$?}e%G#XT9GDISEs<0K*CdJje{D_+d(bo z6&c%t9L86>$=!Q72DoJ$BEGy#DX5*sjPmk|3LaP1Ag@UJej(-h#!8Q(fic^ue!-)e zDi3@b_GwBH0;a8}YZ0~Nt!d$q~~eixr}bkZ#@slWT7dIOtemTv3M2xpt?-s;qG*Z>_G*X{)WjH>bbu zB=E3Im+ke^x&so0Lu-^*cb`q17*T_wA(!v&82Jl7wb+?xSWaVA!K~ToRIupt(FP4J zILJW_OHAuv2CElG<+)Y-d-|DkmKJ%@K;`Ac!}^$dLiJQ?JVLU-XYLF4()vVAnX@_I)MzP!CI7$ZKc8l(B?Up^4q^Q&m_!A7DGRox>5~5)^ht`6b35r{2i! znb~3wNw4yo8r>%Jd;UCPPT+@<3oI!($a4)?c1vDnl)<|EUMfPan2O+_?j$Y0Ys4WyHBy6v&hK=KEl7WN z95aN2KHCEFShaaUx^GTP?+OJD+AbI~&AcRw(>j5J_A{E{AU2%uaL_;?1j>>I2hlo< zT`_fwtv?joCx(N{OGpohxAPVQL|N&_1hc~{8f!wYc|XB)X2Sh?RLi)@IkHxTDH~EI zV4NlSg^A`3Qz4BDte0r>R=`2etDQfZH&BK zK7C^{ow@sjH0NI6(^;8!P6MZA2f2YwZ0j(#3h3UFBBnxVnf432Ad($|mo*V{uCHj& zN%TccDQhlgKxeE)+}cGb<`3t-qi0otA=TIge2evN1JNLI;eF76ILo!TWl`+D7P0XEB;() zK*bRRA!(amd3JhX(xE3|>N{StN%~^N)|d8`hr&k5TOB=@1@-3UQRU|!GA{!o^RVZ1 zU|Tzf!bK31OQvEn%?8zyyNt9CD1GuTu}^bP`oNP{tTn?2rdTIyy$Y%qFEm`G zg!xqC=1CywE z!2JOn6ut#qH8+f3f`b-N$C}sEouYtAemnqsRM<@DCN$2=iM2UqeIwJ`8lfl zc5(r^AfY;gIU*LmAg((a?|O`e%CPxK5NYA=(vkTpxem9qjBbXfu(hYERrKkTg2@?N}{?Mo-V(}9rCZXH0uaVA1&JF@RY6^*b)#SOJz zq1uWJ4MK}s15ulDso@|r zQ9xUf!fa22;^aUMDXI5;w)BplHOK*z>jKa_XvPhsZasv9ZZZ&97jN*m9)k%nIO(%s zWxF{=ljs3fY4vkg^ZPzfa>1j$0KM{%&gKP5T}syW#F+j99`K5UQ&qUcy-C7UzC-iD zkoCfZeo+sJ61$8VE={a^d;@s$++ejS#oQT@;bYzqt?-~=qaha|k%2fE?iJEO;uXWe zy;wJmJSPkhiz(UT9We#Ia)yLR{3d51iUR!4JHAp22;WHqsG=^#u1F53Tbgf&2(^n5 zTqz#hb5C;HaWIe%EQ+Q_dkY+j+mJ*-q7)?HB|{Mq2;&$7#FP>Ej}m_*L*5y@Swhpw zii?TMEq*e8M;xcvh8n#Wr=5Hbzi9ukxMJE|Wcw|`lBF&T;Y@&rZ&sLlR?(A@UZI&?EVQ*Y)+mdTeilWAuK69wVWfLt^FpbxzTQaAvP(BT1hYTOd>RNOrGJ+x4F`Q_41*!%LIJH5 zT{;8~l5nNILRUJ-oK%BpoS}{}!9jwT-}0o40i8fO6Edg^EakUUugvzLVRAJwbU|w6 zkuo&T@fXlvxqu$X{QM6%mU8PkO~nks_E+Qcq~Z=M`wv50?fml`0|g&rA2= zpiXr?JYmd*J%Wu~KC-bi%a1Klk@%XUpGD{SaE8h~xYk`P{b2;qsy6lE9eV{*o?bs! z?Fn_$gjnuP`A>JldkRI+P%HHY_paC%W~c@fhl+XJA3UuLmO)NQ>LB1^TF(#i%$&gZ z*r@`2{&hu#0LB;bP5A}nC%=jR3j4ZRt-=Dx8nch6pm3eu86*+Iz9h?@=ioBkMb%OMK&of64`uH{e z6M7{+zCbQgC3Y>eP=Vz`xtYPWQ0D!OQ?dPOUhQ~$p_V*M@#e|>uCn`lhz&%F#c~Gy z+eZ4KED-OlSHfa0Z_^vS5?7vdyR&>*_{tU0lOMb@o%$eoEsVx>*nM7%ahs*#i_$|~ zF17b}!>rxkd^N1&tIs}Ho8pxF8n)@}aGOfd0TXKvC4#F^t9ka@q?kMPU^$6X% zdqgjRk8C_0#ujUsjQW%nv+bh#^wcM3Q&&OTy=+P-jG_OD>mc9o?t{Qn%PGvZI`zhd zMy^T`?g3>}PKkFj(^)y(%4Hf>fR?Ewp{uH!i~TD9dB@WI!?=2-$e!Mvdvj@zj$kZp zmjcR#Gv~wS^rx3It-vO@SrqMuz+{a+=nAV8LzpowH$BiIKQG3V;k`1aXI=5UlxL-R zE#kZI*>D=QAYZE~%CKmnvN+S&67p8o5PL*{79R%{e2aAr>PIMiTREs>*uie2Uk{3< zy)`=~@-ioE%=(xKBAn64v7OdyJJ*B-y;xf`X-na3Cyp(hoarBa#AbzW$VpIwXE#8j znIlfW^su{A$x_WuNWm4GsC8-#ZKo*Rnz(Pofsv z20Qk-?YbwnF@9FUJxMy#k3CGT2-G+lJ(lA;esXFPT$f`O>`O%s#08YHm^&#bn@JJd z?mBSPfQIC5UOcTddmZOO;gD|0M(31~K#z~C61@AUBr56%_fg5M#=0_L&IJ+T7Of{P zuYIy7H;B+2F!oj9C0p4@GE zD1Xr9j--CNWz9Br6u;o!(~}{4aCA%UXrwGsP(8P0&d(8Y+fGD!=yVY?V2?9(|2nz! z`S*OTFBDgeo>ZfgU0D-t?hM-w4hOTc_s=l#14vwmIu&0-iwE=?!W7AnuPfh0%+k$N zj(EK4wm`HHuj!`WSywwa^K&1X_|`bU0_}i<=m1OZcs_)$_}(XWQK~D21IDiD^-4L* zgp8U**ZK{eaV0R(kyK3xCU&Fzoa)o?@>iS#6oz zVblK_X70Ftkj~%C)^EDcOsW) zf(w}X7#K{e<{HtZFS0Y5a;McaJfVA!uCwT&rCBeXggU5Dm5|s2i!&_?&1}7g&r51L z1Ly?IlLWZ)FYOtp?J;#E*V@kb_Gh|K3K$Bci~*OTUNw zi~yIw5iAmlc-8N=@2`{Q!iK@-{0$6y-wLNcVjHs z5Y!8O9C01&k`+?nP2?O@!}w($wqE_p(QR~F3}dIk6}%v12MvKNT*5*4kX|`q zelQQwXG2(G{R3(@rqf&rUU~FWy@=C>@3501o4xjub4FbjIOx;!aah2c+C?!OiNz2U z1$5~54_)`Tl|L^u54-}WK-;LQfh`Rej23y!A8(vgedc zY*KaFIh4o0J6@>qMqEm{2|(kD+{C>%$H=dB1$O9sdzRV)Ppz_A;;R#L)G_89&mvRa zTM6A4O*h*buPzrYw~uhY zR;R>eOr`3uUX+-!EsQcaPZeni@P`|6&~W`YDZ=UMpe_TL&jE3f&0BJQ{RvQn(tx?2 z2_wh>s1~cDJ{txG4KJ`2Esln)V>H7Ms9{flJ-upa^U*~b4C#mY$19XS@o^}R8G{3c ze!KB@A%mj{k!3!6%fcDGN4m5O$4PA$EnY2uK_Iqmx_>zOT9l{)&=m_41tA zFy`hx1>-4JrGPZ;CB6eSkwHbhRmj50jL3~ zLpyKD6e-8wLah<#zn6KX_%Ud@`1HSl4P01ux{I}Sh~D24y)$Zm|HI>1vhqZ}0bAC3 zWX4H4W!-?y)Dqwj2<3UTpF!mT!){qMF}BDE*XO}<9A-&;2}IHX#=I^@lJ5Q@f@i1^ zPG@aRjMXE&LMe1z`|IYeHpStyxV^XD2Hek=*yXnE+CyI|vh^UXAI(8k^IKevgJg8L zuRZJ6NAvupPbat@;^~1JX_{kB)7Ne-Olw*Bm0o{)yCYuoMYZfs zO{7E%>fKi(a1inpgZ-%?@4KPK#`I4Cvxl#G5!6+w9FM2An;!)h-Kjgg5-W+%tV}z( zHnnX=5v$~^B;DHurt#uBtPP_NyTDhxpKviAh}7QO{9%pmGLtIJIoCb^qIGQ`Qmm|= zKekY^yB1@?*SS;TiZyLttQ8KzaTBZ=F&<>6>b4xC;gMFi`|A2hdQq0qdEnvFR%)52 ze2Wx;3TfCPCsx~{^w1PIDakN*w#zN;wN?G(gLiH|F#^k2CSz4h8EN4Z8tsax8l$g= zEh^IR!*CQV$awK_4O6BxNl;YJnq*gW??aqhD8>_}x)!zj#5LOv3>5^IH&!}8DLTF@ zF-x?xne?pb8%O@q)(tx+dz4qAgB}c~K5_@x5ly8p9&Z*rXz#v@#9EM!MPl*r?!J{v zVCygp5z45LIICdJu*i2rf517F%@Q0+X~l1d;A=e`rCK!psw92wU~U$yx}gkgxW_8% ze5Xi@Uh}>4VHk;cS@@&_)bi|+eyUrB72j%*(9Y}G)^l2Dp3%OpH1U%fjMundo*UC7 zm-OHAwU8AuS_Tn}+k!7ep|oQYQr8J=WfpSS?QtsSO|P>2>D96L_!w*tmm<-Yv2-e- zjPrm4kTFW^f#!AkZc|US%DJRQaQjzc2LWl;ZNcZ=Gcypq@nt2JGM%u8`wdMrm5f2<*JfiQ3n0O{Df`H9K zX2;J^h=Y$q6kZPY6~}l?na`z=2MkI2z4=6MggAm_Si`$amJ@fNMX9)x0}V=_CTfr{ zBq;A7uX_*5@@MyQV5gVC5GVJ%yGyvEOFw9SrTpOynbdS2W#cwBVzul-ZZEpYtKp5%16VYQj^5(&s~Rnpq?VIOBX1CR{RYm}ROtAzN)2IJE) z4@M`C{*w|8q{HiCGzM95NjlXP&aZ)Tj2IGDlb@%|+VY*s6~-%*I-#WtQ5m{@jttTK z8lSk{#UqM;i0`~r82+Uxo(;6x@FQLg;SS*dU%9Op^v}L{UG)%g-B?^&K2#1 zIkRR&1#N8A)E93BgZ~o=1yT39Xv|8T-*)}{j^q&9ixUE{t3zdV_)Dw~av>qknR72k zhx77Y7i7$>;|t`%*6FFm|sSpjA;eDawBA`$@z z1q4I}1nwtg8qtA6fcrb#fDaWA8vbWqyB{eMe_m4h;dx&;q-;5T7Z)^tCU=QLw1;$` zFSO`k3?+rx5LHh8HNuxp^h7Q++3CDZ2(|4H%M4Fm;;G$TyDmqoq$EZW2Am;h<5jY7 zLX?1H*9RZZpZQ$Yi@fw{N9DqK#a&+5g0s=mcY2f?QdAhF+b{7h{-Teeba0zh*~bvA zO7hE@Ox}AvQr0ymKC)aXe{Dx`pF3d(w?xtj41Ft}J*Zi(4_i*MR%=|&5!*m}NNjDR zMGyN}2q}6krm|BP=9p#vRz>uEiRer9?(RG?VMpX?d*d;2-;LX^=@ksL9oUVM2X7gA zCsjC|$0(M)dDcX10nO-;aPYx+iF6Q1H%TpAH)px;>ib`$wv3&Prul$%C z$8P6@KHtJcYca-`CI9|t(O$wFzhiP8*!PL?36{25R9;`481fJM<+Du?0nUcpq7?1P zUJ>eMQ6^W0=Qg$jq$mc_Ood*-d!X~S?Ta^?Lq)!6jsssl>$7k--EPTNerJQ7{n^W2 z|6^5I6M>gxnoLg+H8)4lI$vniM}n#S;yJw`OAS``g~=X8^m`Tes4(!*sK%!jTKaSr zSB39R^2{*GKG5nPXnAfrh@+LGuqASBt{@XhQt`kF8S z1TkphC*h_#`pBuHHO|DCX~dnc@NJ_(E9kxYK5>s!x*!QpEBj8tcs}25YjufbID(lA zB7@LQ?1yZo`S32mctxo|!pu2${(H~LNIS7u^zLgGSk8QmqS2wI755t`0Qv7lW(JQ|Gcsqj(Ka(b5XX@DQQW?EI8pb{B>vfuO2*}iY^kp7Kg zOh$SU)q8o)$%FOtPj+vo6)9Yvnl%5@E}Xay2`dENsRGI!p!`THX=m{2t+A?|^IK!v z9}^JktK4nx`y}EEjRB6PFn9Jx%Jc-ScUc`b5m=&WHpERc@t05lpGg+u8VXi)g;$zk`)@Bv@pD-|;tLCzWt(BZ@3kWo}26If4tWYv-Z6*E9 zWzuUz;$N)qoV-^4ZeEL(r44l*j-mHjqYPM#LKz82ZQ;-NzME0;Ik$Jk-%J+qr3+7| zwc6NdZQeBtn}>MWKX>!3nCf5+0>2~jtD zhE6AJ|Pp;o(qf*^^i#phjiSQjaaiCk|dCSEa76XdXG+& zB_=s~w}anDT<#Wjh5oC%cGBGeA@kGwE3Nkh=(H(uZZ|#dT6o-yEaIgz-dATMuIeEV zd+PxsD^4^Uc4R;}l9wa0f9pA3g{Y@(PMa_CE?To)%>g*6GRa28D_T3_tQUEwaIBA5 zBcg>p@o-zg^09x?vFHHVNEqUr=3GD<`tnhO-obwU<=Kjz=-pxG<5AIU#Ezf_ zrt&M-Sl~``nOpz*qFH3w>67rLKpU?Y0# zZ7{hBDnt03Q*1cL#B=M{6v%F9*9WeL5TG0WZwY&dC%?b|j9x=z5D5485o};(^}hoc zVA(&OEHy<-GA`UkjZMg>Qz|TG20ZT2j1XME!MmkdCMF`iS|?Mi)9LBWP35|pA0&>0 zFfCuF&W5I%_|7ywPkj5?*|?9M^!NfDAxb;%t4m~I*~X~3q#Ld@hB@|=NOtIQDCFuh zQUBPy_bsJotx!1?-)CXWC(h_G`yy=Qg#=^IY4z8~Mu^_`EVQha5XOs_Twde7uLenb zH{=VoWId;_Y;6vdPvXBmUwf;aNo7K+R!M{aRW*vjTJikG(Z@~xu3rt=&F%;tQ|C*Q z7NZDU_*I3}c2jX>kCb>9^yNywi#Af(9o>n&c_Q_M&+A-x*SEs1J50%@*ER}ps$LyG z31EMB2h6xRbhJgDMIl)=BJaJ1DDCL)UWx#}2(I$%i;mWJc>W}<{jI1{Wxk;S&LlT6 znMy@4v81crYPkLb+`flw?`*z&e|La#s84yYiF&)8iH5WFk>N4@BwFq}bU6jEkH7tn z&yp(Gn~@bK@*4-~0@oQy0S&eEE$^Uqb?{jsq)-HkxnoM*tu{Chhu`7y7o`=oueokl z^|GY$Fmc*WudFv;qdGaUcBPbmug>{YCqF`_lB4ZKXV1q~`S1m7IriOx7MTWdF`e=U z@7hwsmkuB%+p`&ZPvp#>W0rSF`ccK4uWH!8rcQi68**S4$*!;O^CmyDE}Sff%+d1e^79+`0L`hx-|F;3nM7#23Hd5N-ZF+)v;An{YSd!vBJM z;P~(1emcS5gu594`xjh-%fE;F=}mqU?q-DGUvORS{~qqATlP)3oAEV&!A*God$^ze z%{Sq0My>k=_s#F$!~GG%?k3*N09n8As)GI-yq{C|Cf?1cI=}F~h5d&2=dArr68tuI z|EYAj8A|0B^8JY4kpG;(zajrNjsJ(pe35?#`B%u6o80)_g> z{42D`-$#~>`8&wJ!gc(8vg~0gx$ZBza2l-c6iNBAm1(>-1`!@dy z)$sR`^%MRM@~`j*{|@;_5P@IM>%_l<{D-9YZ>P#%q0N*27WBVf?{7N0|7x{e@*mJQ zUElwp`(L{`{z7QT_?J$PH<@?S7WoV0W!E3g_={!oX7!uquwT_ly8o#D7vtE?>Nia> zzp797{DTR9HO$-u_^Y|&S9K7mruR1<{Av8SS^ckj&EKoL^#4}OIbJETL literal 0 HcmV?d00001 diff --git a/doc/mf6io/Figures/n-point-cross-section.pdf b/doc/mf6io/Figures/n-point-cross-section.pdf index 8a59b175ed34e58051d5fdd9b92e6f2bf6875597..ba7b6ded19cc377822618bd911036d9914e1cb95 100644 GIT binary patch literal 10673 zcmeHNc|276-&dlNEeT1Sreqy+W}gw0VQiPl9yiNv%p8oE(aczjQqm@C+m#Ypt|&s5 zsI)09ZVRO(%9=`Aq^=v!nL#%>zvuVd=XpKP^Us}LneX|`XM2CnocX@LpYK=KnPG#* znvfObbkD*s1vxAL0(}2q1vxV_jC*t#gt20YSRwpCj58|`5&}5*V^@qLix;R5@z5S_ z1^~uQBnW4V+yxNi%IAv!EGSW7vIJ}Gc>#Rbjd%!g09Xy8(J)L%#Nxm{0SZ;3xQYu! zLIP(26aWbz9vhB9m3W}h6yzWtN0N&;>ZGJ3$zZJb;XF7X#(~QbdINZ|J73^?JF#er z9gGAHBpQd}3JLk)0yr$3sCAUc)=k6`LGaktjR+7W3EH}m0Mht~43Nbm7={g>CxX>% zumELT;w<2^-5`-S1}-Va9f}fR>_g$2S&qF~jlJ266NGEP;j%3GQQojA2oUf%086F# z0x%$+2u@815X3$)wgP^57#u`$A0!tTH;lW0#S?~!A+Vz{R&E#u6v1UfuC|u2Uw4+j z5LOwxBV#ay0Qh}H#EvH_V#5s)K?00T2#lBkvH2Vb;|TErML_@#OTmk+gdzdN3RRGc z%4+@Do9{zbjMG^&vPJuM)#Aco8GSiI1y8M)N5-a3zeB5Rppa6p=0}!)86HmC(?I>C z75JoaxP9rRPG^*X>rwr5(~*&w$mqD3a<`PY4TW*uMM=0P?M~aNdh=`C%GJWp`xg1d z=Ftj0udk>{>U*>Cp;5uJmCdAcUi>xL8%zDZu08MSbZOP1(W4i>e)@d<`tRBLA6g^( z?qys*va4@isl)At-*g@pc>H9l4xMj$m_PJN@%qtFm$wfK_RrA@?Fib@W~g}xr=MhU zn6l@H+o_wGzd0;bdnluTJ@o(!$~X3Tl%Mmm2|FG%Z}0C<0uoCfEh}&?J1g&55T9MF z`1|_QpF6_Ci8m(Va4vv ziSt(wy}}h1E)A01>hrKUCg}B|r3z-wCueDhbQhe8uqH_DIwD9&jB~%2FSQfLi$8`A zi``}M>!FBcL*@yQUTe!B3^w_Brpin-6Yq!=%15F1vZpD9^7L&p31@ z!&z^;LEhsZ2X-@;7 z?6nh!(#oW@M8wg{ni)&pW^-6Ut2KwKW;V#gUrDf6RNiKv)O)?E`j{O#FMh_f`!k<) zv1)b{SwtpmGAo&VIN8SX?xtHW_AAax_G43;hDi?Z}?q zI{Oe|uV~Sl;|Ydf^K-j!)51MgY{Ry|8g5OlJb{(apLjH|BcEISp?Ln!CEnZ!qr1g+ zNddbeD}y{e=toy>by~bxdGn@qi==v{tKaUh$k+O1AxrvzQ4Z;VwCKbYmeL`Ejr07y zUAi-kEs5$P+}*y#M#Y>1Pvko+pfCEXT?^u;IkwMN^ijr0w;ioqAPR14e|02zK~LD@ z7XuNv_yc-uIgNF?^P76Zb{ubvx=Ci;jEP;jbS(!@4Gf5`e1TbJY91*t=#X`rbv%O5Th)`Aw7s1_duU z(?KU2j&jcCr$qU_Xt0`;X>H~m9P>hJY- zyEdfUvf3MWZ<+b?AytE})hcJ};u!%$s;lt7B;45QQtPaCxpqO$GT#&Rm#b_}tWD($ zlB6r;xS0lPb!9ZYr)lhc^g4r?WkcQ{5s=AY599@4Ix$_O4NALz4gPLfqjF6+k z_@2Z+GzmYKc@%XPo)TeJzdD=oxpgM0f{}Gh)RuePdW3!qAHCT^mFJs#GBV5L^~pnJ z^xMCtwMac0J)|1vcqg;&XTWCP))tZaPj>tpW*2u|P=VCY4-5Q)E7lF)KH6T2oAajJ zR_|5QJA8uA&9_bsRkoWih8c8@sw&sy6%2I7zCwq|e7!XgJTLJjKLVfw!GrY=YCB4R# z3**{O?%s8!|0mXPQN@Wsbqm~;BG2m9reQ0&ru74=DMrU+Z)V=P&|6f#T*cL=OX2BJ zMV)%+a@d!+<#&8^lpA-4`o387Qp>p}$z9-DxcdgEqGZ>TJQF4Bx)4waKImWj=pkq+ z<*1cYm1eLTF#V;`yOXzQaq1a;lSovG*G&hRb0e!G3(@nBu25^S;G$cW$GGu=jji3D-!EEFE7@=JjI0W=Pb=8e8yl$7KFa6tXm0azihz~C;mdq`6 zt!July0he9#FEa$PRBzmk)gKLS9+nIVfKl$FXTJ$CaU~TFk{4u6)ts zePlSPHTLQJ#=K4QSO%p!f!QG|Mz9-Ss!t2QmC@CGc>g>bZ@)iktGQD156O$t*GUa; z{V8W~j>E>jd{-~EoVglli&0Nr$$Dh(#8Xqg|OD1Mf zU9I-)yj86<6w3(MmAONkFkPML5czo@6Q9!XXY8O$DDKdw6&xzRzeBD5FeTN5YU47n3Z6%KWdHuPs!ccKRR#esJo*}}>;5Bm<`ThG=Rcu>zaj>@OSzoUz6BDWZ zv}>C1rR;!tfAZTkBxaHSK%|h&O%Z%_b-(=D`p!$|uRU#z8&bkj)18{^-sI~Au5em( zJM`MzlUq9a91h$4A@%-#jGp>(!Ott9NHbf3$ssu41Q(4Jy@ylnS-{j^4@2M zGajrn{h)Bye&N-m1yu?&PAV+V?|Bs8@OY?V%Z0FmJlyOqpZ2dq=Zomsyt>h8)9dyK zB!qB+lT2_!F-ej&;45?EBa(3G4=bmMsOt1 zga~4(r0?U2IpQQ!F@40ih5L)d6kD7&7PFES3c-95W62Uil8-%j+z3b@M8m9*1M!3q z$B`QtB*IwpV5-LD1!6q8JUUOv{dO%jVn9N+fEy;_3xF|tZNn7^MOHy90f5KBynTF( z#ZqB&jwnb7P{#S|Uu;`!I3?M?980LLB-wwFAWSF&h2n7b@XCS9rt<h~SJS%_8T2P>7A#ppeg3oFFyBJSu`LffLXIp;3udI^tNGzU422tCz*fl&*esx#7k%r^d zbCx|v*DTl)#-CKySJX7qq-P$pP^fEnbH3S9SX*@I#F-tk zpzNQT@0pkE=GV?RXj!Je>7=Zb{2$YO>H(jHwbIH*(mTrm>B<=i3oQWiOd0*K_d(sx z_iJa$-PEu+_FP{&PKoHaSN4d;jf$kSZFemfNRLdvu~2DI)*2R0T7Q+)>VO+RIfo>p z*J$g_DUvfT*Ir$@ll^S{LA=GznM0}Ax2hJe4(Za*rl>iGjC=}>c=M|UvxFOQA?nFP zlj<*3>mHZoSCn>NxqqtU)^h(}^sD1gFV!!sANr%onMO-@O6ts5^5uQnhTx^y+Vz1p z?;odaxaM^+Me)FyxmT_y%Ihq|ZwR?Nz-hd(s6*s-dLw)JA!3Ulx+9LDR{?bdXYyVZ zb=^Lnv&tWL_-K+!QHJd`&ouj@*>Tm|`fSsezkOk|Z$ZG!y0%*ZE~oMo>YHttFH0|J zLt9?HusNm?aK*MFDwFJ9t!wa062bAmI2=c64C)ArP9-*SGHTx{H%UFN8` z8~2FNpGG?ESEx1JBs1M{)^FXh$3=-Z0k_UwPtBpFza|P=w_V~zN9-`F!yNVpco`4B zZp3IGjNat9beepIc6%J3{z7g0`H%&g5lXRtUR=up7D1dhqW^Lv}MJa#YxmyxZE-bMR1{7Kw# zvZ(R4Z_S#ro`ilUTDH^5wl<$X!_P*#;hGLUQwdErym$P=?O$^q3wp9SC8YDe-q+e| z!0cI!wKAfmoYQ-?^(FdDIo?$ErJq*Xsm^9K_vAP9uI6R%o8^ZyzCnlkEr%Z0-N_WO zA9Wk?q-T?Xxi>er$8Wdb0MS6aQ?JG4Q?@eQGRt__n+OD z5NTNG&bk;jIG}TFwaRBpR7ySAy({XYzv^(23wrk3kW{X8wt`XU^=MOzSH~lGt{zNM z{=R;r`T+Y_ao^t)PDNsQ@%119Pz{cA#-XWly8Czzi{coK8g z*SyVT`0MGd5ZU+i(+|EHBT6id&z5-Y<&T;N-&_G_&532cHXd2HryJCd+@D{()CYPI zidxm_RpvjElK;`k^5bB^Mk&K3IwHh5n)3Z`trgr;5OMiD2HbYgXPDwZ93CWsIFO77 z@mMrS)&oI3xUIzGbN+LUN%QNs3Dz6H;l%Bt@BfJ;u;E1^$}HC7Di|$R~Pl-CTQ>%cgQrD`;j>rct3`p>T^2hzbqi2~DHSQ7pKJ z2bscSu^58_Br2k{<|w=g7EK1RD4Lv^fD>Tq%CH$Xg0IX`K_XF@DFzc68EF!UH{lBc zF<2^tw zYOAn}t!6F*sKkcb9x?jYF|51L9IFH;aS!~CFWCccIrIBr1n zf5q}U&Hrw>;DAi#e~u_LbOIEUY0BWU!^MTPXVAjKxg1kV2ANL8Gq7kn1Is`YtZ^hX z9i-6FI4T1NS`vvw(3&KHIoVbW6qeZYgd!H)YyR(e`_2{tJn(<;lKVf10sIr>Z&mr9 za{W`Tzg2<1rTovg>z{J{tqS}t<$u0if3I9)(>_dBX)wc>;JdWH_%hs?G)G0kY#4DZ zB-!1bVC4+1Ff4=>4YM0}t_bdujqQ>DMd9J@)epTmFIOl4hqYZX;%#?8vgCrt3~w_AmWU_QEn%WWXW&Q#YcWy!o+r`%y#^fMU-!XqAYpPF zjCkL6rIm}k%-ta?C(C-LUfqwHF>lYmmF_m)xfNv1+fD?n{8i$fO;g&ysgLArOZ7N^ zm~&p$a*O8%oAe^P5rwuiONHOl{B;w2lGgbZHwieCWln+!{W*ONcUBn3BeNF-AM=z!8N!i5FiA%0!>F!mls#mRAO+Qbq8d@cBNfsb07>%}P``h+@(Mj%jPahfwfF0mq zY>mdx4`7owv$t@y1aQJcDgZV~D_d7HXV}pe>S`u#X5wIK1`rfPb8&SxgW92az|UmK zIgs_^^dFrtTsMQ>I(1R>i@V{H4!p;OM~QHWARbl#d~{0bF%;@5^&4T8iKcOm6c^^Z z$vO0LRo7J4Jc{2zKPjl`I~3HhG2s9I3* zlUo84F$a$of3}Sv!g|eVAoZ{xTg7z7_lzI1NmZFEM{X;d%IO4YO`@(N%93L$)H?E2 zaii4OcL8YO3E3UphpkR#QVcYYthx)r?+NWR$&r zM46bK&Wy0XZ!;j-m#!6T^p%Ae!`Pc~Hc(vyxXA1?b=YgT!kVcrn5yl2OPy@1e!_dg zqY(0Cu%NcQZ$DkJ=4xHaE&ko?vuB4uGDZ}QzM>SDrCt%zomGh}Tqg~J?n~^)I?**R zKa^bcv?;0LTDr{am(z+p6HRKV{f8VshWT8neb6;V2v*ujhKzy!K?Pie) z@hD0fy@Xb2=^8D(eNu_zBSSkZ*pg}R)}^VHundKxVW6#L@_bwtugsm9xIE-XYltws zS+Z3A4bgYL-J!kk5o^3RT3Au@k4Kt$+4C|`GzjS4i8tz&q6)oKb7+c#-hdB{YzM-t zPgf#uPBw#-23t_v_0xBlO$grin-^9w6`D*W|hpsSM^isTu?E| zEs>CBp9O>-Dzuk-meaM>uNkv`3rXL9d>&=%N`-c8cxt{VGta}W7!jq-VOOF-+-rXf zc_zTw=epHLb8bgfUa3r4U_;XxfFWxky&I1Bgvdu$`Y;B!7Mr9)OLi9D4-vXTIn#qV zNdE>g^LlyUqyK5j2QP@bWknk&PD3R5j$56r=GZ6=YXyqUj`sp7O_6kQ8BtP;m#yJC%mT0tLK2+>rc zT}`s5i5ktamnEwxQahEdr_&3#qi|beYo~5X6mmCQb=+NgOcf(@h0%AR6dB=N`hHZv>n$Iq=$KBPgFN5b$L}q$Fgm zsxEVSY91zekJ&iA8jMDbH&4Kn$`uI^rFg7%J_DMI3 z+v7wl#^k;Nr0!`VUAR+7W%En(U;V*>m^I#WgXRU~5mpBuBA2 zXu18Cbt=t}_PKLtypHW<^@p>HtIO*L1sVHXJBmA}+vtT;MtpanD4-&qyJ!?0^2N_D zB5&)ccT5e4Rx+mc{a3xu%;U6O?S&EScs|+sL*{Z`C{{I4#4Aw=+)=BCr7auSqJ}jx z;*;&_ag6Wa#zxv~!1r5S6%D=g70Ik%5?kO(xjL=*8nWqsS^kNpmD@(afBzC|wTt}- zrx*PygWHWmA#(#9AqvW?xQd`L(Z-VTd%xk7hTk`8U*vGpbaaiPavar*H=(7M*xV)J z_fAVJbE%&J)kXBpYa0fKz9qr6aKURzuVCy#qr)}9KHs|_$y%P+!R$%ojzvD3=j-0C zdkoz?ZiX^f`(tyA13KBB%bTnb-`*d+eA=OZJWu7;2!yd{&Ka7NlnxDTRo$cl#|5@${z>nEf&}wPe?z?2>Z<)K z^rV4HUfk-2k2*c=!$;Gz-p&tL=`f-_r&;`DWOb5(l%B*vHQ1bpRB=VjEUWr>mSZ;b zQ4_6^+G5<}n7I84od?8CI7DPksG*yjSZeCdsiG6C2f`gK8AP$?p^-t8I(ZgdJDBnk z-ctoJ9AigPPZ+7u?wI76dwocC(|o&Q!#@h1y``q;XEh7$XY~!WdPsFi_d_ma4RLkL zAH!E`pVh=FO&X_;2&-tnW@EwNBKb0oXC10(HmDV+K zQ7Hdfvq*EJnLVj_82_CDXrAH*QfBDMYk=;^HrM5MN ziKA-_H_|j}H}til;{;Q=*gzuIMt&jAah#nw$V-hym2H?7IToWsexs-RIc=pvfABC3 zj)nqo$ytdhI5h68gms(=s%=(Tj+-5yc9s>^%k)8(>m_#gXSDa076mv&&0de?D;3E; zus`Xy#T0d&E=783m8cvF^j?BY@q{bx*z0a5l3FqhujOyHhH_*Lgr%>2$}jq!<|pZ} zIidGl!fXHW`$_a@*TENr`u%FsY!OMZeO6NwB9g@7n<|M(?WzA3#85oTcj9V zaDL2~gX#6Q5|6`1xRQnnuTt2A&iFyKvN;@*U0m!)LB%`COf3+1Pw~QC8WGf}j_<9y z!gjbR*GwA)vxbI>a8FpH8r3_jjE}0~_yzscbIs<8yAilPq<-i7{;d1m*ma|4(AJ{f zd6j}#spgz&me5G}aITyC*aj`_f+Wj8HqUmiUgI!;*SaiGNZ~xR<@-%3En5`sGL%d@ zxBpIlCfb&{Su+vA$ z-Sx;``)#O@FpV3Ynijx?%@EIj&p=fyv3Gsz6R7wJZZM5eJf|eC47Apw*;7D?*S+4( zYa+wv`RsF#?a*et*zUpU9--2NHXHDbpbzB1onPnG6J7DyU7TSRVHB$g1D=mgQ`qjc zn^TWSE&0BZ5|lu4Stmy44Tc$b__s zgo+cy&uw+^=4Pg>r`qa|mm5<_ge-H7YI%U{f>ma0TcC*u)%c z9h_Aip(bX4dsZgq0t9gX(F}l1!oyWc)fL9bewInOfY5;VHp2Lt6ig5J%OF8P7;S@@ z@KZ<41Ox!G|I$%20Rnh_pMn8E;O}<-wD4zhzfJtrePCI1C!5YH#7C!l_Io@X&*UjOS3lVJr2$Qx=!l zUXHOrJ*wb5S;@NV*Ir%NZJJxKIP#rqg;Tl>Lh5$af)C)x)51I4f2W80c(~^Rfe0HO zQw#p>y16-$)TKDW^lgVgW@fQ;U)}j3Yk+CnB6F7#oy+VG|3oS-F`5T(Xc99Jem^?G zZG;rnx?WlBH$vS8j;3c z$P2-DUl$vQHqhB_S&>-f5)bnFzI#o9t`}v$E-yl*x^s>ZZ|Y2&&rDpf8e;nn0XG4~ zH5ZK|kd+;Q+2|2j0N4?61ppTkAdQWfhR7<7U?q%HD&m51K(l&_}!(t{t;(tO1=hmSvyGFv-x6C)|OUV}k>) zg5DU<)D&vbmkYU}LU3Nhvh-*e#T)5Xr`I6PL%;BmVfMcz-XL@IYbMeI*>hGRHDS%g zv<2+Hf3!|_?9tA+f_D^z_0Hw1u@7Y@nm?I8`D#2xUl)A>st%G(5Ef+`fX0?a4V&cQ z?!&tfoNf-~z+y#LsxWNN9zA1*nz(t9b{R4{g=9RLFd9M?8ivgJKs6a_Dt$Tux(>?Z zL0(fSXCiH31I0?39DpG$R+5lX%2PT#MOiWl(FFw_*%o0>VNbC)Dvyfor?pBmTC;T& zJH&luec7av!RaUIn}Zd}Wr;Sa`{~}aiYasH?&*fCIrN5<{<7Z-Z<`U6-wD4{4J@eE z$dg>J?-mG^k;uwVHI6XOGLAG3-r($2rfp9OOlrbaED`=(JxTk9l0HpzC~kjjH@cB4zM3h|qmmp2D7%ab~*a% zEp^ZvkaV_nb{h=XQ!@`o)XZt?qIvQvDi4ZSCInwEmkCsu6xV546e!e7sb?Z9t<;&8qUM6T$Xd64nJg2&$y8)sIgnNW{U|!6-x;2P-n9B+ z>ZZ>A^~kfi*j=|CyPoBFfrY!~8zw9{tVF5k{**bTVue;ZCH;PEyNad)X&|V`+B59xqqJvOzk)I>I ze+U1rEr^0`(vU`PngBucR?IDwAC(hBAOG5gh?SY6obHQfv$}_$hdUCBv4pr-Pn&f7 zz~q2bbS!}!fxG0ORIkL0)H4ZhiF66MbS-A>x-H2Voftr&009LlzBi4VbIHZzvRwG) zmkqSdLsqa}THTkA&JEio!(){RhVQhU7|BT~o~&a3h#S>6ETqhGhLlHec22jlw(N(W44w?$_;H$Zx<9w*bo(B5`;PO_2D2h`wo-4gMZsPF zUF-T;=YG5$z)pJM>#*c-tQqO5ebmada;|c9r;%md(N3Rei>UH?2g|4~rfsh+zU{%X zd0mC|Vmg(<#F_E%M%0EP?NZYIs#})|JKx|`K40>s_SHgWfgB4Uk@s-?U1dqmYONrd%YJ(5}&oxvFF`%ko~cL zajt9C&VLF%7O@vakQ|S!-GAUh$)-ZYt{1wrUjBY;KYy>a%fjE#U;IG&IB`rWRO(HC zcl`4izUeuxVy+ZOcWhE$&8Abo!`<4MnD^td&r0BemP;gVN}l75{!5CZR~Y;Yo>9J~ zpXu*%&Kt*dqIB|Rmu4dyUqh_C<-J!w!yxsO(I+fIFMjx(#++wn)eTw5jU|razkI=y zuOv@i5HI+8G)SJPFnwSH?dyRy1+WjHXs%HX)gxn!~9=2ktu%%YMb4Fuz5f<@b&0=gn zQm?8M-y@Hej4?^pOYTWkNR7o@#VmW(oc4Ylj!B=|4DDk0-guEZQI_N9e$!!LJlONq zW@0~6NOq;TljnN&+-}$O)7F%`)lp$l)Jj>W(0Slp-i^s=f8ysaikFJ1Ik7_SKB%|f zJm~DqcUCV#PHz4Q|9`^JpO_y41pUVA_rUvJ2E*twVq&6D7c)};@E54p|E+V6x&MxV z?>T}CfbE%?sTEYz!2_Us55GCUFdWXq{S)9nhhcLdfKATI)CC6re*t*?zry6d@`PU? zTny?8wRN!gML=ABGY<_jXBR66djK1dmE$ks^-go?0iH?S>0*j0U_n1jbDy>|yC6D|?vr&!8qM4t7xcKP8e@&MvNEmQZH^%t37O&_6DK zKpp^_rj@Cy!i@05|x4 zjQ@Rum7?8ifVp`9_s9Dk#C7lApJ)HF4Fb9M!fy@sKXmRj?`7zc8@hqj%5# zehvP;@Beh`FE=Yd?O-J7zm112)B+YPAhe%+Q1oXovH&4q01Jqd9Yzab9p&K!!(vhf z7F|{*BK8)xW-yc3L|jbn`7NyMo`?Oq_*sQb4C*Ltc0br?T(BOpsk)lkX#gOwDq6q9 zFz0dp5=#R(xM3~1S^ONupPhvbzsIjDAP59gyYCk)&u9il1OBGCzn`j~g5RgnetM?dz*xX(?bs9`Ghm(V^cTiYtgd_TPhnb?HiC>)MM-L9#lKv+=g_lLDGh3N?_SSJ3_^z zG}L<9&ZbAWvSp225D@AL?HFf3>Q$+cnx+O(wPF-JQV<)@uv#*2xA$SVQ{auo9Y?4~ zKU>pU8e#mf9{uRicU{{@L;z5;0fW&wK67X-Fr@Z1W|~J-$!m0$U?_fqJY}X5(F)o* zM4E=5n+(dl9dk|U0t4wNbuyFIHxML7>J>r*A}#~X@c@ts@CNQn4~FF4l;L&H z3wZ89&xSRvZStT6DVKTYSWg>*8lST!OTSKpWb71wRJ!81+!|c@axF7V(?as*onVa| zqK9wk;l}9IED~Y?u2z=i^gM3na9!Am2~L|C!f*^PM^+o+MHO(kZ<&T>$=7~ zJevwf+Di2|&l*CT5GLM1@ay}Ujbr<1Co&6%B$~C7tORpYkJLw`Y|OcG5eM!fm04k? zZ1kt)TG-W062RTYVghUGd7yf1}&*-d%qv%WJ7kn<_AcS`2sOd$b z2((6@J29dpDgC|#I>A$2$hT&!iW?@}=u6p`2~7lF9Xf@yJ&rkxOz4mOusMp4IW#~1Z^a}i-NZFR8 z5k<9ZyP!*unF?K!R1FYu0{6#9@|ZL_fJ=7MG~#oEjI{<=Bm$J&0f%J1b-s-9k>{o# zH0p-8Lav%-T{MCU&Avx$UUGGCZJ#F5c{U@K(6Wp`ikPlu++Z^Pxmf zHDJ{&S*`Y%9~IdW5U-}*Dcz;H`8qAhEkQ>y1(wlySki|5iq~TFu^+Xi>gEJiC?bog zg1}ZI)9g{iYIG{n?FEAk3XQsnZLCl(-rIMBrO`{H>wHFdMD^iqP!c)SlTY9FW#946>&ZgW-Qz%ndK^{-a&qDFiP)+1Q>P*pdYWY^?tRODm!SKrKt=Iwq<^ua68Z3Ro;w12j;?vvx<2 z;T6=5J2UmkQ2WOFgtEZM??^#txMs&T^-Vz1B*T=Vp$2U2Qa#~EHzePs#}bT?=7#y_ zKPPGAJUz!#?)H1O@EY&gp>g5C2&T-Q#7C#*RPWs+ZVEhaXq@KhbzYyt_-39#ARZdf zwsq+B7mT#mHFH4HyUihr8UOLaDnVrMpm-&ZS?JVLV)c_xVQvWCu|`Y&XEoOz{tv*` z=8#OUQw+BD%`)p6{%vA@FY7bvj)y-Ud)7R;3y7bnBb=U+KI94BmTEZUy`X58bh*0} zJ>c>HG4^!im5_6Yv~PtQ+dJ3e+Z!c}Hrz~;h7)XAf8jZ&9(TIvlVX28K-7E(g=7XI+~F%)BUC z$==zHlAc(POv5Ifpza0JI`iLAkA#&J!b7FC4)^VmQe#!qr-Js>qa*8-NtpM_R?eEr073|>1T)^%!4l#%DM zRNmP;6F9iqTi#|QbzhHFS&yHT^1Y36VSEi;7bGGJOTwmQ-YPFzuX>sK*~9E=deGW> z>O_Eq{}HD*wjwX&EYs^%je-Kb)7y08byZNeMI@3^O(C9amD#j z2Bvr^-TTUQ4;i)Bb6fr&7Rm6pS4r?Ic02QS8GpbZXr93R2Ppj;R|2`Yx&HM+zsFC% zU+90r8W>jiAHk-woS2BX>i-sOg6?nd|0l5d6K0~_gUMg;?;a?^fF%rk{uQ`FV6*VQ z0qLK*zrg0N>tE;We*oa$K=R*V=WqG%w(pPk(DJwZzLj6_6!r~k>%RU^==m4k@Y99= z9HajXJ^$P2{J#P{LBF#gza#ah;I|k4fSy0mJS@2W9<2Wx=&50&gr`YxXkK`9kQoY; zUj(Nm*$az^aM*JsTE^f~tRd3y2ZaPE!+&5+LdIi|D2s?^H+YbLTrm)F`ILfIYnvLg zl23%pHQ%xpJ_(!2X*gCLw?O;s)FZFf-Q4)R|NFcU-;Daj(&5SFsQYM(yN3cr&oO^h zJEyx>cg*QTX75otVM8!fzQxOxQJa$?M1cg(~*MvNWTi4jU ziL-)bK55J$PN~7&&Cz+;&@fgoVb95XDt{L|L8$TNQD^tvsNYR@4aFESH#Om4>{@yMD82i`u1bq5Yx3|0fg@MT!?5fr zmkhbJSx5iP@NacjxSf#>+d0T@9yB~S+Y8&Xe%`g`a!M|OLx8S0QT9oo9_=o|#0gn2 z&L1~Cs%0a+etOIQ6ZTQnEifJvV};Wh600L?g8GCBmtu z|3JA2pkmtRCzrs_%T>oOK^;HwR8$HyoPkSHmw{=KwkvEED2*^N;`~J=;zQWe2_&zL z00vf;!`adYC#(kv>U)8)E25bi7vK$d7QddL+`!3RlCI#57oPwUb z%l*#%Zn|-HyP&ggVD`*u=R1SFnCHiDiiwk{BZn9+28fuu+p z%b%a!u69o(NAvwdUi~}?NS1h=+GolSwNo@tRpfh+E%e7fZM5;XvOciM#eV-}tGgcO zoi@gE!5T~v>aY%Z4zp_0*1PL$a{Z#-;9RWtXgQN;U6YbI^Kl5;;NIa!x9Q}T<{{zQ zKBXTb&Y@V}2sTSCxCA+TSKH{!XbK4lyPdjt|?S z2=qb8DlE3bX~^Y#N9R=?qJ*1t5Ho(QZGw2T-|pCSpXKE!@{Qh(0+#L9*C2Tk+dxB8 zB^e%do2Z_wTOE@2H+o-$8>wH!B~X{sy4fxfA7CG7c`dmc#lKj>7tuLqP{+^O6dNkO zMSf4{Rh?fsgRLuGjI*JxEk9k$eRx$lasdwxSpEX}O_(!B<(N zvRXYF4+Hg`FBGcJ8FI^F^CVMA$~KD?^7sP>9MnrDCEW*AY)~2t3f;%j^eL%gmL53e zsvaE%S#cQ1P|FQPJByRJKHpxBV`=QuHGVi$YmeI?j%^MP%`9~9btkRa;r+q)wp!~T6t;4 zXJkUY=f!I$=o$ZnVs092rBe*4;8*&(a*0>Tb4VqOM1zWN835QMoqBiFg~1?~DZCO~ z3}k1%(iKhLAU(Ve5e&BHM!K;|uDoN3!#kI>qK47xZ#1lLLnKA&okQz6o|r7nT%tYj zRFHQ4rog4v{9K3IHi0rt_^GdIo~SHttj1^bjJTG%FLB=z^n~j0g))BX_}gpni@;EIAc+%(GYKfjjiBV?ey_X(-Fc{GG}bJuaLtDuz^v`-oC0W%@CIpbJ0gs zGY&<0*l)Na>%Na`&+@vS&U#MgO4CmcdeiTJ)D9KY89xPOXmiDIduDS6Ly(ijIG?!{ zW$e9pDGa^DEc(7h)p6*-*q*i~USWOxjR#nv|C6+3m)Ya~hXes9Sa%S@? z^7S^Cuz1r|Syhrxd$o&HU0v0kZB+mASIpElCxoo)oVv1fiOA~eDV8<3p<8SsCVQ@t zGlAK6P!%?$_jBLQ3YM44E6+Z2mLEUx5$^2!ai)OYH`9T>NROLHtEZR6QrVf|%`x1h ztD@GJ0W~|+saltxHXP z*DojLF!HkW&+B#-HCtsm9d7n7NP93}imn(K3>BtxPSPyW+SMl?x$Ixp>t!k@`#4%v z)#h+aPcQb$d1Tui?5K;6EL2~F4IRb_M+YH~vEhW>yqIv8N_Dp}AsJcZ3`9OK2JQkP z4w}%p7V;NDn-We5d5eWajMlXxE3JYyJrKK=`Q%Z6X{@K>FV@>Up5KAzYfu@%Mw<+$ zMeNT@=Tt(F@W|&M+Jfh#w2i37f^_C>&s8l_Uwx-|pz0-@NH;V&wW$0h2LWFgKWSc6 zy%Aioe895wY|b{EhcAJk%loZTIMccJ$rIN*LFsv>?sdDb#vcNlxpK-n~0)UBhO1UY*AHgLLG3zSBQp^-m0Pk4r#6_CI0uJ$m^KR{nReT0~t%Sw{VT z4XgiH_?|OfQ&;Ror?k_0( z3!>lGzqjD8lkuQ`PVoQF^RfTUxBt(8;$KOQKWC*u_k!PX_8S!cv2y4C9u%wGDB@}2 zb-q7bYEg1y;Cs>-%Pm$DjT|C9of})2|9m1X9cxl$Q-_5)qj53l(VGeDNXysh;aZ_8 z6wDl{4;B$1_`_>hHZLm8OM{->ikLi$C3#FTm9dw(-fSghe2ebolI*Q;c-490dqEz^ zy%_;`Xf1M;HB&ZJH!P;RSGL!aD`~S)nsy@5@;Nw@60f_l&1@etsVxSJqqA_H*?ZAVkum(qwu> z#G591no?>@8c_PxM#Uu4=n^{NM&c#dZMlPJnQ=AHi#GL~Vx8v5T%J&H+nZ#2du`Uu zc6A4=2YA@LPzmg`9@)qk<~GMLxqet`8)F})-Hd(Hg?=X*VO-;87jz=|{>-^{*}i@Kp~Ma6JyN?@C{UR}gw3 z702<~XN9LEI{00>c_awL$S7Om%h_0c&<$iEU4hy=ev9D2$6+OPoFvHAc1hjGyS;mJ z*%;>aeqXDSHLog_sX9XRlecsL-6O%WU?hy<=CqHU2ZC~5AxvSLQTS@cB7?aaO3pt< z-aE1&gj2tYe!xL&7N!ZwLp)^2J@%T`SutZtwab`S9vhw9`7UtRzUr4ex~xwOon;*S zaXWKkma=LLn7a+hirW9o0bEL6OM2XX`Ykeykkb`;uXaXz+D@?l{n^}!Lsn6h-}EMr zr9}HT2PJ=ftHKPZXOAe_u(8N7V=9(#VJcOl%GX{M@kO&de7xvleiRoI)``ke*7AY9Uaf}2J}b~DFLzS5yzVVi9Ej!9?~ zh4@Rlu&3WD=^lS)y|p3%*wS@tp7_k#Ay;^jX0V`B|C3O+6Y>i#4QDxl{{Bwh$~uib zgSp}t$gS+Q!cNoh?(2EAM}7Wk`qx$W$@JbM`$cRY48Pbh}CPC44=x!TtuF>;Ow{{-#1NW7@nG_+7889|#Y$tg3+ zcu;m^{HqnJi~aPH(vO{8|0OJY4RKwx;Qe%GNYVOeh@0qS_8Px4R3)3g7B@RfKyQcS ziL7@>o$|x^AbkdQ%0LhEr}+w}6--lKI|VF^WOv<{-kCPM)a)dLt3S?&maLy4+G+>_CVf13>vX z)Y;Vw09%pya}lS9E5pC01%Bt{o{IB=*g-%bkew3*1cNv@b=cYIVSoS9{Oc1_nd^Q5VedPZT=D*7z z_bK;(mvM6aL&nAaPhW9?IsS299`HYH;o*Y(qaQGz{(FC6eftkG2>ZYL83^KrC1?L` zGmwKFmPq}F4D#<|35x>`*fVhdxsL-^XDBSQ>iqMUovKz|uvmj-V^tg+U@H&q9|~9` z%GjGbz?LEWIv0m6MBs;lL0nKUhpCwvCl?e7HUV>j*p0zF<|bgM8N?U@7DW5+D!;Ik Yi!1Cg`Wd`H4iE%{MoTN9D2eue0M#gJhyVZp diff --git a/doc/mf6io/Figures/n-point-cross-section.pptx b/doc/mf6io/Figures/n-point-cross-section.pptx index 27307dba2c241e93f9b2216fc50930eb1ccc0f7a..b406650e64a6c8a8a2f2be65337848fe7d64475d 100644 GIT binary patch delta 25825 zcmXVXV|XS_*KKUuwkEdiiEZ1~9Zqc9wrx9^*tYHD%=3Qd=c?-Nt7=`lcdd=;=>+NP z1gWEfL?A$1Hp>M80+NLX0zv~y+)2a$T&8Z>Wiq1q-%wuR)9uKkO#uqyDlA7?%M%Mq ze1R~tAeL6g*e(B--|*vR2`bB-gF2vZoTSF0I?sGs=_}Wf3ae$ABI$Y{A*tOq7|mk) z_>5(vfs`}SG95*k+zOI)Y5DxH+nYrbB`}60vxBe0C$ePqS1V0Vxti9Kv!!4Hx`<^b z*>x5*&Z<4!WCHY?KQcOcPKNEGX(6h+UnBT-Y$^gb?+dD3U$?`FnkGf;D#Z!9+4S$& z{UTzirMgHjo~2NdolA;JK!NShd-s}|5-KYcs`X6ff?%gb{@x>|M1<`eWviG|=Fza6 z7BWIF5Xp-`4NYZPKnGqMU4M@PJ`XzFQOHTH0d)iyH9La!vj%7-j;CktT6j^!Fvi`E z(Fg|-l9lMCis$&v42oD8a;2&X@OI^1MBdG6kkS~=HZy9phUzc0z*Owb!f6j}*9q8_ zbNFJ>%jNZLELlJ$vmG;3U|J`@0@XXrSmNRK1+Zc~=z%w@N?k5p)7qv0w@NW>eCgDg z3eyngdqoT-Z{hO02jO69(}RUE@>11w^&~f3WvIhHFm;368S5{PvrQdkg7`RJuOSTY zglx`b%o!fyx9R@7dJ1DKLd1}GFA$;L`-bzcpc$mW;jsZmZ(yLgJ#>`(d}x!Dc&~Xx zLdUawz~^Neg7Z)c=cT%UXH+7QVUP$&!JgcaEp-DBlYdj=Pzopz3;4bTM!?0OL?Y%L zMZZ<(@+Ohs=W}cNlnH4xpM|Artpx(O?=pFRxh8fq?v{6X~Jw0owKlWA%Olg5RL|rH4S> z*B_=x_I(#-0dj8jESZ;{i(^gnc$$CnIdjs%-fl|mi=;G3%Ih!l-MRWSl2(_*+*Z`L zRF`zWPI`Shw839I>$UBcQ&}G2v%ufH>f5zy>>12^yC(g!z&o9KdNOL+FFu`~xpt<% z@2?2IFJ?D50G&&!dF3me91oLgwwiW3Iy#pat#4aCFVkk3wNKp_a*iCoo>umc_bbu> zi_P2B42;2_`*uA#v%J1c!G6xZ-8zTkW_NoG`l&SuB>K}bUx%F=M{ep%E%-_sEo1H- zI5JyaMfOjb^xHKxQzv#GHDk`s%{@Ogt36xaDytg+K<`gEREl~>w^_fJUumYP+n)Wa z(euUoww}&q{EkZM14nL6B+oPfF#r3Ye5Ju zQ-$`JehtAIpYRTr*u~qq>d|w~zr&HgFDfF90nY6^!*J@lpDlF9lbIXLVH_8(JyaZG zeRGl@Jn+R+BzU0m@!zBt5Cx(JNVLU8k}OFj9tMDwYG!xar$Mv3UY?b+=}ucii_b1= zNnwX>ExRAXBq0$BGISV{jlw9w{97RfRI+-c>X@wFHH^;tP9JB?w8+UdtM(b&=?CvG z07Z?3>dWgaV?3q)t$0OTD9ewLHo{+YZx-521`v@n4MklLuu&3{_=`i`>n+h?^5yK#rp}vN zPy4-Y{al81E8T-WTkAzp&9}hxeweY8i36?y1oodx0MvAC znG|R-O4c)r6-s)7(i6*ooH!hlysAJUMZA$@IIz7a4l9-u!=gWj6$)`a(nwn_sx1<- zEOkj)u5#ko%Fb*!8*Y}=1mn%gBH3TkDiqO2=Y2W(2$AaZ4=%QFBy~y9*4#u3Bqr(N zMY-+Ca;f0B5j;X7eq#aLrroz=~jbb0L{#{^B}{a z8QSH(A?l$5IUP@}Kgg5p^}OL4&EcwvtG-@_wD)4zRSf2pnhJU|6PgM-GZC7%-%iil z;m>Y*IM~$;L9rt(ZUuy!dNVZb>@PR$FK?aE^qTyyVOwcOguD4;R$FxE5Y+11(R|I` zCkl=Ni>vo`U}tpKkZ|hV09;7!N$+O+$<_exwl4MUny!WMt)pz1=V@a$Yje+9NnOuU z!p=z^*V8USwaF`VeIJir9nrVm3{gBG;WBG7x+2jO313VR$s$lDqXg$0(r0(taEsUP zOx~zj1=!!ddtJP?gp*#zyg@C0m)66u2SB{kHy=DOvo9{ga%ZpH&dE;mmnzr_@iDm@w-7;|bdTXCzgF--S5e4A-^ zaD*vApJPkXYVDSzh2P%KOFfx>GRLJ7q{+*%+*%Ndo_ z^F?K_!yG9v8Om-Dh|>>>Uq}f{36z#Z3o|X7mEkcdl$9R3xw^e|PR6ioS$$;H^g&IfHOI;Z80-O;ky#ygQ!9TdqioQ?q$G zex%Yw3J>%QK_dE$M2a-l{vv1!x!cSIFf?M50fJHs5T1F$Ij?_@bWr3evhIfCe<0^paSZfO71nlYcabx2vf^9{uM*t~?<5_TX{fDx& zBKo|VwrbI9pMJ7~k`}x-fHBL3O+} zp#{V*1OIxIM0RPkpbm7qQr>iq@a{QpGbewixYitZmDp^gJk(qo8M>I zVpOfOi#N8ELq*jyv!Tu5p;cQB;)ka{xJMui!B{9W9+sZn{D?eGBW@(Rxqxr~w6rENur(8g;qFjfl^h!EtziY(#pJTL~X;4A)|sb@gALv?K&XGc9oI zW$c{Y8jGpM-%w@6KIuOM$f9O|4940} zi@-jId{nkhF3DE}eJ>YdodYbn@xj3@afJ7*R!cs@9ckP>>^5Z%V>_--pGI+BnYqYk zxjA!F%6D>Qqn2)E&4e%5$^RZHuk6X4*K~*=-ycoU%Y`n@ru#P`FMcv)Nm9{_AR>gUB=%SY8=Hvz7mbd+%;|=r#oPjMsmkb)xkd1CS9LiVh<^%|1XN|8I!S=ddSnfj~*_>nm$r@<T<0g~tE7Ib0sCLKEV& zUYe+kTi%g5=I>`knhX{A&)G*x_9n&(x&8atlG@H}nG~yF2cl?4yxR7lgfdzDMm;ND z;TGLGmgy>@Q?^fxK~tt8Y@x8irE#Ec)PHE6K>TSTeQTR9>p!H7Tlhai{-*;F?+6l@kM3cXV+4K0)`B|MWv5f#n#qzplS`clk35DUe+&lMAH@A zFPos$f=>o#*JmLG7oxaWG=J6^5Ihs$j~qei)2WtFd%%r6wGE!v5N1_ zDAGT!zw%u`ov=_29hYRwDjC^OUq{b&^zocgqk4#1Su30ye#$E0<}@1X8p9~oAEg5d z%E2@xp~D=cg|Pn6$i)q0m6_`VjgiIk&m$Wv>~#mzKpD8vZ>;crpdiLJ`Sej0?{5RFPyvDin+XtP~!n z>9G2NZeULvOCK09_oQU#ffE^6AI7{L3zuSTbm4te|mB1wy~Up}0&0Y3PLM zu+6MalU`+RW72~a0IzhR>+7aYH@ivDH>lhC;Ma z;;_sO2SfwoJ;b|E3$V%um{6t3p3sN+K2W1Es zz=aajz0eF9m&py+B{rB3)nY@(|eG>NdzqO9P8%Ww_EnhY_h0 zslF3yj3CM7u)M}hDgHjj^758N`?E@akn`WJlJ~mZW_uS3aBca)J*o)2C_JU((11vU z0wkkC`QHSKXKtVz(KKOm&S(AhL_(rgI#0h@ScqWo$vxl9Vq&(g;;c6EhdhVw^QX7h?XZd zAsw?TZUJXcKKUW%sZaO(zubV)&Cm~3d$y0%`w`7l59wAJCWmd=d$nt1dCrdi=`YMq zoz~FFGcUvR_2%83Z)d$@vD;@6Ln}6Sog40A$BwBLhI6gs(evVE<4A%zBXCYBEXm+; z=hRb)T{xNGLb9Q8YQ=I?Q+_J;Vm|hHDw~!5C709;BvoJ80wjmo>|)>njS%1=s%rML zuVqGq7r|~Yh`*|S_Qe-x_xhbS0P8ox%9Rx2B(XVB)AGf0y$h{8&LAR$1;*u+97&~D zwnIXNK(V83F0!96+OTkeSv!zEE}+|$&6<*5PRc*N1WCBQeEORZHyozz?cU6EQxx-j z8c|DY`A__V5mHDL%)qi4#iNG8)8#Qyr4a_ncbMrg$k1G7{8jnzBzoB*0NvS;DJhRr z*O`zJ#=G%JB~-fyBhSfB~e?C{bkbSx#kZ%@Z8u zZ!lolcqMPrgxgm5V<@x&wGMqw5?A8OuW91Y^_!)LpS77~eumnK4bX$tGQzJ|fuDzu z!{6pOzLr0yGNat@BTyybCOW^_n1Hh^Mk1Wy$YfL_3OMIUw^z0j0MT5?n^nS#*%f(Q z!3VcFHlkEw$)i3vj-`B?a=~ty2EifV>@my#kL&SxWKL8ST z*TmF!J=Lz(lf!D5x31j<3ut+9Mj-R#6I7qb=+NWh#Hc zZcF3Hlk7fR1-*& zT!G;&(~@c3X}KXxy~gpx)LRA%kBySfs|RZkh2yhMMssGD`|}^AHVH|L$ME>Q*aZ$* zHJ!hQ3+H#^0X$ecU#yG0tH6uvjT)e?RWYJv1yJM>{wn0e{;PDmsw5DsaGqs<gP+R!FyG;5-`XbWF>F<>NRU5UU_As3*30M?w%qpZEbRfDCIp4qGwDG&3<(b9uq z^Hji5WYD|$YwkG4P$(5;7<|~27G=uUmQFEgaZ{Y^fU7_-go%yKTJ?s+hTYg+Gl9^%&Ktnz2lk_1wo3WPpvD7@BYD%VO!XC4KF|ib zFG-Yt0k#Y?y>k|vT*cQAbcRY6cenj-C9ft<t|BDeK5p_1VPU?tT4$ZG4K zb#G!00I&o)15Q3?Yn@Ko<@qh4`ArHYcYeQTc6ycjwHkZFyeC^AK+n7?oeU(mvm zB1BUEjtXcqH#jTvX9{@`@X5n0PFgmsU?`yCZ=Q7E=z%kqm{b9%pjZEK`teUaFinw zEL^~ya{yHgc^F!fV@eS<%x`G-tjkMb5YohjA)B&=W0;JFYdK3#@r)D+z$ThDsL#h> zCrK-!W`4h(r>sQ$K4*$o%^wR0ivlnxm1&aHC}ima$v;d|@FlHb{3A`@j#wqFnX#Vr zTXZYM0c?+TE$}j7*fNZFGM874s<~`#J2f?QF6zTO#~Xw<^!#j%%)g927M7{t)F1Q1 zJl*A9eiHL}&>MB}6hRWHAU_bMvFZ-2A%TD}nSp?aff6!E0hd}ob_eVz-@Sd^L3tk! zyUmj4rSOF1V~`pmQj;7s84(Vx3ZvARlxu81Au+znw%P8jgmZDYoe1#<_nD&xzUdt{9TgH|k7Roa-7myToq1l{nH>`hs z9}@0#S3|JP0{jY^js61T77)zkcPdoiJbt)#7=TyrB%!0;+Nezg(zGQ%_)(nQ7mk=8| zsu+sS_6E;|1LYXcxC1eE^=#UB|s$>fRCr_RmMM0VAbdTtp2Ypb$!`fdPE6i?% zSxv|#xRxESo}qRf9<3ssJ8p9~E*0iWaM#~14m{bFI*!LW4jBz%<1D)oj`8!hqk8Hm zre3-{fP&z5OLbOdU2E2dl@+VKvJIFi8r$V_HbgmNmbz>`V#VMDvyQMhPU)7f0|_wi z)tQZ0k=dObUVN1XHG#SY2N&a0=@gnl&<^fe)x!0PG-S4eGKh0C6|+_?XBhQcWH7w0 zUaBL)n!%k&uTzS#&}%$XyHBFO2XR}J3)PenfLIwj!2%L+9uW@Jzfod9-M>>SvX0zD z+MHPo!Z!#x|hi$m&*XsMP( z9DzYy15bWTnS)MMDmfgbOj!Y{7KoqVe2gTtiHqn0F~g7PkdM4z6d%M;U}p)G%hbKz*NLV!_r z!lKmNXkGW92`UatcjpE!9)N``ke!Uev|L%6gGv?-V5u+#*{r6~Ef|ECKrC?^3ZNu|0qrKCXnv)JxLuo;gWP;6!w08K@(>7CeD8L@rvQ&g6iE{GTqEqpb zE>=KlNdq|+`ZE%$akh^f)-emSSW*UeOT zIXnM4)mq8IMm#Nuv3tbU-JFjWD3syupztJ^d+O9kqvFf!0@vwu+Jw~GN?TOQY^kBS z-0J0y%dRD})<Ehxy?rfLo*v{Cu;;p91lBoyt|Oo z4dCZ2?@%Pq4^QHe1jtEiU~trMC3A%csjDQhZF;TZdUhO_%CJup&h<>FcBIXboYc(| z5lLdIEX!77R-QS*p^y@ENV|n}!i0z4is_3=*oV*N7f z<-WG1kO4@w=pA4uw9Nb&<9ibT0s{K^0R>W!0R{V4sjuOIf*$WFvgZW@0uoLn{zaOo zK!OfHb8TdLCTch+5Tt-Up8~N6Zwbj4O)lbdiv%{)zH%}n=PM%9ZkNr7itf>174zd2 z_#xHX?%qkBT2~^5)r+=q*;~V?X3oJ}vN8d5<#L7FN{LluG3w37cbES46Zn2Bb$?Q5 z_{_L`6W8o3JB*9EY*u=jb2V)#A&h_A|~uU zB3B+yCQak#sb|^At5raelaRshCS4ULW7K;8sKL?z8+!@BNnKehRKg6G*`}Kr!J>~5 z1l`-rI8~C6t889iEN7u}vDKRR{-Rncw)&_nJ9)}xbr_DZ_>QCh0vJzBg)cZo!bH~KAKxGl4g!&Td^cX5Om^UlhPCk2PE zl))jvF!^!PD`2L*XxX8J3R&PhvT*=*y~^CEXJ0xVKyvbuRn{^@{xIN>C0f-yM`hfn z_079kTD5sDQRe2OPTBu*Jn!lM6z+8+(0lrO6{4<$*iyi|Jmx;w~ zVh_TagkdTw8Wenc4`5*sG(l`=T1W1pNccj8W=A0ke6`l39jMliy*3=zmpK4?jOo*F zF1*xCu>(%H1|GLJ;uGZOZq6|MI{tg3U_IL}_b-OA3k+ z2r(M;5c1p7Vwz_GgtAlh_($g;&@MU?MXZ`foVnweF4SRa#r0e(m3CulEu+q~b(^i; z&%K|eu&0e7UBE0R)q!=M=KDSYO%4Q&J6vEG;TdE{%8bAXV&M>G7$6k~$8eW=otGG- zqMGEQR>dB;ZI{IKQLmapw-l_XgfEY5h1bEKmFeTd3Q6M8pF_w>ekq&oSi@3iFXMoq zof5Y@k}AM1+6R@*_EV49J)H3{Bk8gl@wB(lv6d7m@qm(JYHh}qmz#~#_>{|KZH)Gw z$eP1NZNoLAidHm34jAb{m2=>fGsJuedW~yVqOB$BSfRLRtp)ttUtI{8kEuSK#=JXB zw53$gYvL;U$Fbszev?UMqk*$frXd)eIAMg$&!ck$F1!mBW*?YQs z3iT&m$Y`1b%JisUV*lFkj?;daRaxv}R3|=cPEDmZbU4R*rFR&RgSeWpHM+>5(6as1 zxd^31C6x5-WRp_LXm&TMuxhJp=5n$rXm)q+z3cr?;==%@5n2C$0s-m4CL)WHG>%gM zKmhfaryS%^h~x7FU2!=-X-OWk(*0>%Ixh=Gs*meUi_(vtZ?>Qz+lI2!(Dq3S%n4CZ zRnRp^9=(?Kgm);pftb3$mZ#?B^_WxPsyt5zM6)F!ndIX`-I4`Ue29a6a`%jSragq< z^DebomL#R?oyNoOR~NYgz8~)^&`nmE^#HKHm|w*zB8WC%%K;{9bkO&#&MX2nn-*-D z9VcQaonm<6iaEt6o*s9@NlJdKzrY)g)_yH%tiH`B{;e40*xS*Z^aM}VTB zc&=yA^ZUPE6JB4nU!MP*EH?6g1SLk(praLiuUROGx^=-zCK|{S`p2JYl#wO2(2xNt zP^#NiLds(CeGYlTR?^2J-=9}K0esG=kkr%XS;+;*qcbVACs*UM0^OD)6;rcoYE`Tt zxXn0?#^&X&EYG~j^q#|u@fIHNI~c-hvV#iSUhO7?{cfsWLocMYMq*bk@ApW)^%`i{ zxe+$1cUrCDWWUlOKCLuaD_WQFjH&_gIsw>~zL*Ky?7P+bYxY@32n~Tw@XD)Pz&nke z>4h1pj7%r#iW1^+G9RjJD+$gEl`FjyX4^Val`i!?31wr^@$zeT9f;X!_{-KCch}`R z&${f}bS9=+2B5Yv_)zQ1pI&A$_N)`@<2Gs2DXEBKW|NJOMcsbD_V$Vgs<41-Yj??; z8P|N7nA@33f?BH?v1g%tEq{TLujTG+(ThgQOjC(&_7d*)=fW#E0|Jx45#Zv*4y<^K zLr8ZnXd`K#zi$y=MEBa^zOmYq(;D_l@%GWr@J+kam5s&zWZjmwDblk~Er%_=433ag zCy@ez8eiZ|g8Z(_?jR+-rK^B6!b(b^auc(ayLDdy`;|G$#D+0`B6_4**ywGh;&#y# z+|?9=A*{vas^)=<&j=|qq)S{8PvR#%58MmOn|#;zJE#TSVEANp^2t}Fq_~?fUWq#R zK?J%c?4evVXFPJUyE9TDM3L2lLJ{NdYQe&Yapz9U2<}kOA(!gqa}a>MRAqB2q|{$O z)q;Qde#I0w19@_OYI3bcl)t{l2pD0eiWhDa5JkRSZUa{qbq7zdThq)NqT(kEJSjn3QifD3{iTqwEi1Guqa%Oh4S;Y zfFDAzWzCbb5Z+fG1N`@L^ov{iO(^L*%s3ksPun-}5U-YTv7;aol+APtT9j>2!6u|vJrqW5h9J=<)YuHWD^pL(A%X}|2$ zd0t4qf-#wo?KcQs2Xwkp>N~JwVtVY>Jb;yJ&=G~KQ2D*QxA$bXf`Nvg z)=oV^zorTP^$Utf^-OA9vl}*vADRzj7FZZ^9k`#Aql$97a)6(Tg;=)lWzfjJL;xox zB*j+eTZajn_A&o0*LwLS_}CLW+)Mnp?trsHLzUyj13EVd)1xc!46?kQW8Hja9V^r% zVT6Sv1OOZ9jR9WnElurM2n}CKJpv59ue(fh;0b?ORW}BXWaQ=hS9Ie4(k#%#6~y1O|9tT1KOIK{(6-y)MEfE% zcowwiLD#e&FVhuu(t(R(?_bpgB2}y}K^sqKGH#`svvo~bNQRS=awD+YXU%y#kCjwB z-0MEslsWJ5>b7IlAZDeg{amN!>QY>VckAjWa>CI- zt<;`H)cjpp!7vw$F$saZ7U&S1uksuzHZ2-Ef*@p!2Munlw!o998x2yC-jy}LUwdtpBaD3}J&^il1-fn7b@VH%m^2ER0IEs!xYlNLkQ zK4Z~7pQ%&63^zawz;L4Az)Hs~Asm6Lxg1uIU?X|{(3}jwJ85I!2a*D&v4-9jCPs!d z1E4>{h@q0e(?fvA-=jFauJAD`EbD9+#9>EzKV;?f5;mT`(7EM@!eQ*Ho)k&Hzl~>x0fI1tBSDMlE6=pn~{I$Dy}^<51A|-=Okm36sP9 zVg0~iuFaUYy^}$^^}LLDc=U)-qO%jAzhVw{)k!|3gMWyT2l$aG6bg7W4W@k0;I*YM z`n%`Wy^?SNtxCNT>yfiTWX=zL@K-~buPp*lygca$Iy~8A=5|?NQeNkPv!52}qlzkA z*6P|+JYBjc#3d0ae+!csWU`H-xP!U7#o5f0n;}IEUXt105hzauveZlCuG%TLSPd!( ztsOo(!ZmKE#h&{!ele`0rexRH>3bcbyIT$I9ix!|E>iu8NE$Y@M?I??&Jh5Ol`+TT zJzdVaCK~)@Bh}cY^3Ka8)ytsNaYq2=&_)^hx^qDl535Em;OlwxrS=DpSWRvr756+( z0JGsDfV?)DR5L|fFUQ2~7($sRt*G9YJ6hNq=UphyeBAOn*LU7D5*H3OMi&3kd`(@! z?lw>MkJWjgvJkDO(p(Ac2%fk-l6e#RQyR}HO&R*4c_l95U$QWtM1q+5YWwJBt=IHt z{pPgZkV_-ez#^sdE^MfoI>=msxb@x)$*%(Yxz!&oD2l;60 zLp23QW@0k<$QM+t<@Fxo!d&*7 z5YVvx#)5OhCSRQCo0!Mw;}r?7jcKsw znWBlxiMEsLzL9K85+y7DX!YoBQ^&!A-^KAPXR%JbPWB6h_nNwqo~T|S6(6d-~)&9$7jc%M^yFglMzR(<=;IG;qd+{8txDMlpA)Jq&8 z@AKjg*r7>f_#)012SpjmItUA7?puN(lwW=w5De`JO(%Ip4m3 z%!Z&bdCIAq(xW}s_ZQZLGB3**H%rM1oKr^rV!YynEA25IiB{Ua$OGQ){X_FxbAN>q zIpFCW!E)V~WE6~;zNo9@sqJK+HkfMw@LsEBmavkC$~c4}8hvMugNB^|U8pW9UkFD)8`!uS;NxEIl= z$icgpL4k%DnpX&`US<2{q03Ga^VnDoOrpzOsnJ^;4`3Ck;xdnHAd)xeIeh_^&n%(M#V>$QRnwT&d!{XF*|@WO$t^cPn#kasRayC{KJKz zGv2+c05A9~h?up&NKb1LJ0V#!i2#8bv(BBWwe`)F@--`w&vqgmh9s}wr>8v@zXf+* z95sAZ2x@nsj1GHLZ(Cu$)%t%B(KmTi`?nrwUwaHJ*)HicMk`-q47yeCT50^m(O)vV z8A$@KZNUKDMKjg=^<`GCGZOS=Hebho<{DdcS{hlG^**n_KpBVnnn5^)F`O8i0_#Dm z9ME(?1^R?MLijl(%<5fK$*}P$V`1fQFoY{N4W*-)Q3sO}d>W~TimgR(xshxWN$EM` zb7G{RGm4_Hl{2k5=(S*H`c}hutNlgPT#K~+(n57caa9SfT_lOw!t%r(b zjl=nIGHyQUmo1mx`$7f-1X>{OttUF3Z~HnD*6ln+4r3sI48p#qE9jF?+M8;wjK1EF za`k)s-d@m`D|%Q@hLuk3r4?JFz<(E!1__<^8#2~G0L9Yt!uVcTUA8Z;ieVu__Aepn z0@U|kg@ohmh+@T)zc9ndBQeUTd#hY~az&{m|j1yk4HCz?=*ndYlyb=B{vV40* zx25{0YFeTGb9(@G1`k`CDzz*54Mwyt0m~Z$(_WE(&GafwVMQ((U})91KopLa!N%S# z+Us(XUCYm$yyG?5SXgz?a$l%C&)q90S>D;1L4$H4^BIz=&g}OC-8rF9w4SPs7xC_% zOiwI`)X|zD)aE9Kw&C{(ql5x@Vfh71(A)RP;z&W|!~_6Ond)IiD3x|Hcr|YwBnWR< z2HdJWRXxb$p;n~Me4)jQryz)xsf*7e@6R_Q#Z;4lNmY?FyC6y7`u`#<*rS5*7g5v% zDk$UbpWvcakkxZ7WoBcS3u0@Ul{zdhV*~ve%MdI(gS`O_Vrw(Eq_()=G;D1^iV?$1 zu`8!`E_okH;B6nsnE+)RIMT|N-=dc`gxG&96z3Q8+)dMqf(cAACJ+N4l%jUnZC z<&o3#AKP|bzd>#XNU&K1wkj=$+B4A@`AU)3v57s^{oqs%7$yWCxUZD$Lb1uUD5E!> z*O{8FrbJ4{Yz?o(2P0Ww2~z zodohybc1j=VvFKE0zfEFz}Y|$DDjzLq$?%JuU%#=3;{1q0t~6NcTLvtueMZCKlZDT zZ@@Ph4+5lS!WH4L`?Z&I`tk~`lo{<+cQR!Wj;=!Bn(^MbBaWzpcHt{Em;Wx| z+1qRp83VQQjGQL#Z<(&hCb}k@hkM%)@~mB@10VOej4hf3Zn5L08YpgUHuYaFQf~RM zj~Gy2C9Kcfmu*&%hB)YN2InVVezOAClz?^s+GzaFuYH$&FiS$i` z#p8LCD1y#~;MVv29FK-Y_h^x8>&ueHy`3StD7Qg#$zzG(`PY7&BfY@?yXCUBad>+D z?{1bQ!oMb~>8pervqX6*lXA+4@C=M!mvYm-LfFcR)L>3dc z5^pLw?;pBilxl_@4vju&H5^=TFw2-pNgHgQ$u0F=#MSCS<4*{nY z1chHyfBQFOKEC>RZbr*Fq6_9O%oXiZNYFy>cITRvNJ|yA0N$BTcY80TqoD#4r>c#s z?NB1;!WDKWHlpBye_d7)8_AxO&}O?m*zr6TJbO>3y%q7yEt#ha9=!SV6?rGXnG)Y+ z49$5X^yS`4CcqP`R4BQuJ1z!JE+LV#RF6jlg*Xmc+x~nD4l+V}2aO?MmQB}`XwiQv z9fUSx=ri@MV`NYS3<@4jrE%(5>c;Z6TUUphJ;a*}P+_YV(h6X;IE2 zWbf&UN3Oa&E|2GKdswW+HwT%hAw=J-AV{V_+J)`}*ga*nxa$o6}IB>@{+BT>ff*UjBhBXHwo)d;@ zR?R19Q_8(j4x2b|VM!3P9nSp|C6I>c<&ZM{DlEwFP}2o?oxcNA++o?IV93i!k)A3h zCQ996css{7pN8j#|CjNDPIjbRnf}L6RwxUutREsw-n|i9=D|=7lejKdT_WG1T%H`-axHiXOFix2D z0pzdRlb-rqGfie-m$OvZe(m*YlEYB!DQx;~!i-+y6u1(L|rvxT~^jOpE4Ary*OrT8Zo~pRcIiyh>|HeK4=}$Q`fzZjw93{N3c%3B0+TH}Nm< z9KC+c=4nlzY7b%8bA4uiGEMfetZD!OB>js#Gcl@XE2>V+-iw9P-L7&w8rY7<0qK27 zBLwpd1#y4+gSdoPqrxS$^`+4Mwg~O)Vdx+pT4@wpFTEj1J0vBKcLnlxTgQ8nmO5oS z>*^nmttQ^D<<7OeK?D&;!22!;E_vfyKNWsJpC++1-+G++(gm!@=g~$ zg|j8V%KV0}lJ%#`Thx%-GxZt-11MuW8meVLuSMEb^Ih>S%Je8z2eTTjh*>F#}=b!53E6b#o#5M129ZhWm@`knY)$ye9o6VwZ}wKpUyI zyDO26n>7nmWppgZR8=b>Vy>tFj-*vk+PX*H%juR21k*QFkMwd~pv@_iYD3uyCew7_< zrBYn2d%Ke~pWE`OJEzP>0mv-pd0eau$oj4_yv(eHIZMc_Mps4RV@O44z+=mV!Jbeq zzDUq)TFwi@kPy6Qdh~hL{vguDp<^%UIfwbL|m2LB%KvHL2}UX`OYKP^9UbRW_D@C0iJiQnDFydf6 zYfe;GiaEX*=soto0J#2Svy}IU7xIHZ?lp8})WLuKWAyYN1k=^Wg zpAgeEeP0IiJkBv57nEl5!9!k^-5(2vwz-hDt%mtjc|$a1;IDu8Do-q$OW^)ndtTMW z!PQ_};B!+GKJ%Rjtb`7O@~tje{7-i#!zTX7k~Z>5W&T&u=&PlILfrR<{5|#%SuoU1 zg?)1Dw3RY!Hh(kGu-ve8v;ZN)nV)*Bem;p<#6?d!m=jN42rgvLz7*=+HB`C_jzj71 zmxQBjAfMGtDu8f&M&-P)=dh~>SGf3JO#^>s&?Elc4^6btcbNv&6Mh>%vudaJ>+AEQ z6(Ap132-~brJ#CDm3C)oibn&HTDW6Oqb$#SS3DMvyc^@2{ZpSgY=}0!aOgvz=ff7A zN+{$CZ|$4%P+?8a*Drud&Dp$FEO7eX50QCK7(A$W89Qqpt0%USn&O@Lm1e zJFx}cN_^K=+5fXdyVCcF$=hKcO0F9LCcJ}=gL zs``G8I-SgRz?$PZLD83^JT>F-5C%^5m;1xWvW`3_Ek6+{5`DUmeHgn6F(#V|NuI{9 zsE6VFxsERKh)+MDKhh;rpy0*M9nQ;dKV4yW%E@q0%40&><=3Gm1sJ&VdO;fw|x=vt@>;H%Mnk)&mN_80q=keApQPPwZwjp)5XK4+3WLtlKL-Y&nHsT zk#7yQCjzLJ;}Q9Pm3;?T6ic)AkVJA^;vynZfdrn+jTy2q*e{1X}0{cN@5 zV@XZBU=f!6Q&dUH_bNy&SIdayMon?gX|LY7&9l`wz4W$hskk0HmR#LRSKk!#1y}J)s=Ph{8uN*d zc3L$O0%(~14_#OG}7ASiW2svIr8;8#)VFyJDzt+)_w&xsNKmL~{pwn^wH zX_cTpAr3-NpBtg2H(6zH=lU69hF7tXe#`*i$BZa&3#0u} z2@W1q2A_b4fPjdWjD(E#e+kt8K3E^pi)OuC4)O9zqjLwBZ@DzJ_sg6#$oR7=U1iaL4WV6E<-=vH z3e0YQY+T65^Km3fX5i=tgmqO5`nE(Q^~S>eZvZ^mQU8c4mPKk;@Deo?UOCc%v$dH{ zL3c&w-!ELNQWCq%cR`4>ME3FdX`h&V=}a15d9$%EH56ach7>ykt_!{~cYU9mD6X7h zXXrhyG#!pJKS~&&OskFa zS2W#?N%W&vHF3}-32y^+;E5_`x0)M-ZiI-)R`Lc=(3v>OaZXm>F5S6yzGBiS8E;zLjUTIhHf{e(1sojSB2L)R0LlA4?56dE^^KE(VTbC1F3v#77v&OL$(9+#cg z!JARd8#mPIEz?8lxA?d|ce66;trFzL$l)N1(Hd_u?iFGIBAFiB&cx?2v$ze;id` z7Wm(~pm7?ps`Z_54GVNQf~Z?fue#h_@4|fyFYK(SjtIr5Hf8RFc6(&bnoDAVET1kc zfR@k10#Aw-8^Bd{i!)}6w;jBFz;hf?>>U2iK3vyrEj>0$O4v7^TGA2Yu*;BN6_w-g zRq53qE#I|94!7u#15IkC2yDGx)z$obIh_!F&>5bXdouSp5W+%o-h8H%5>>0lI zB+L1xWxP-hWo1_580Oh7xFM)T>O=2eBEpXX&`)_AlSl)rLi@UKCGq{0%$-DhgBLf+ zL?)dmXq^uv48yOiS)chPlI!}Sd5=EbyMR9n3!KHYU;!n4h`0oVSb#%8*S%^p}laW?`BAVGO72(W2i=sjU?{BTOeF4P5mm!8? zDq8xJ5sH&gK9L|sj5D|(4msaVw&|9fyAOQn(vTLQbDt&_0! z25s92UwRT@M!G?G%j#n>qpYeZknu8$`LZ3E+!PbNN~`O^ zVrac`R>mQeiAfW3eS|Ej?UB{k7x^H$X*&VWvBaobjB&JOUIdXq6yXq#6h z-s8q*_QmHCe3)O`ezge^ZD8=yEz70GS&wlyo{i{IcZD1GLsFr!yVNffT%WM9Gm%#M z%&Ou*cyiz@)WO3}YLK#A9^tt}cEXPwWU|fmP8fMLH$POznU}PxxF>ziC~`}UJI`vs zaCY%?;+$7o;fq%z0Uy2hb(HVf)9NS#KcZi7m>A(&auZE2Lo-!{8nuDI#u1$#X$#?% z9?7*UAGoVf@+X1z76%}e)+s&beRI|+w)i!!hCDxia28)fSjafngO~O}?C_aQexoYH z$@#T}xVkrxoI+)R@08{DO_PM*xq3vP8(+0!C{f|B&aJ$3lyVtujiE>$_aMQ!FH41{ z_9k>f9rwr;-||u?m=c?7;=Uj0PIGPWB{d!{g+-|^U189w#aYmiB6p-{F}odjRI*!> zgMoWYu&&HDwAN@|nY}HwxK-@MC?AoQ%W5|@@0wFnSLtj0ieHY-w?H)w;aR7C)Ix7Q z_a%rzIdBOgXj~H(P>9|=#w@L=o^VO+o}LkG+dT$jgM1D+Ek3CBVu4aI`44j^6uCU* zX;@%Icn16k3I|~UaPGvhCjO1Ax}Iu)Je8JrLPP8 z*?jw7W&c~$9^%4YGc543`|uPCxT127sms4+fq%8d+nRN`yEW58paggG(*?VzGeIG$ zK$7meEB)=mnV&E8iA>X zhMi1Tq}-G2R!9&%8u&@o-TzP(LnGh=b++wCXH)(`6xQoH7SJI5tIofunEaC;mVWZX zj{*vq>d`;z2cgUMs{vV0aM1ej=T(^Y5{c9eE!54{xv|}nXi}N=X0k|4kv4a3%-OH5 zd%On zeGRTpZXtT&L9dovzs*?*(d$d1r0b;MJ_F?GT*CUupT^<4ET;Va^U2Fi4}IK4WBM<> z8|UEfMeM1(46s>jYD5pVmI&)7i=?FpTuasc*l6V0IP+C*Sf5idH%G!}#w2e&q#-k0 zeorHi8w*%QYn@UjO&AIz`$JB|kk7gCjxS6guz;y<%T+1=ix=&IABrv%LA+rxbq$ta zF{eZAxdpij4ifkb*+rpjer?L4moAL+Qi(@|5AvLf{wpqiN(=W!vh|4*Zxp@@Sa_DM z8|HbGCx6!vNA$uaAso`?5CzUgN@Z_zs2B0EB$&>NSRi}7Fs>^gt)@TlCUQH!Y9+0y z@GE?pO7@KhFPLlzQ-`&$hY%`ko4Ih_UlV%jKFJwTR?ZlN(U6Ok)iZuoB=$;IKmQdA z7AWudCP#(L?2c{;(2pAaptRRIfv4qqpq`i1sw+}n&jS~d#E)6`r&%(ZLq{0g58inh%=LbSR!=9I zT!q0+HS3k(HiPLcxYt?h*L$0f#vRHEx1OV0{6Uz|l&xa53r}t8mr)&M*t8{V$N|~Y zMpe^~%i}%%MWp>c7~r#nWKHs{8GeTa`W2_!zR+7?ft+$)&PGfllahmfaKXod?5HZC z|FRUWboAqG%S4A4HsO~q?eya4lO}@1c}TFE$+cUHNhv$8^jl1@i5X$DvVuD?NY!LF z&8aom#uw&H?Y@_8UmBB|8*K|~o6W61eHU?W`TcT;bksM&(9EixP-M1Vq0&KGsonLP zxa!g22`)JtG;qsHPF68j_%#_}X`cxpX=y)==ga|OR*wc2&Mwy)ksD0iItSZHxQ%BDa|gI ze>q+;qTj$B(BSM6n%h(H*n0vI!E-%G)O^5PsbFrTFpvb9oOZ8HQGI|+R%Xsb?UFc< zkp5bkQ?=L|813=b3bT_qM?bdVkh+pv6&5q7Ao!Hfy_ZqeceTbzv`fwvJYo0O%CHj+ zN)*^!A--;6IlRG<?17Gyk8E)>{FV;AG2tbJ6x zz`e7|e@D8GJO__~*f7K0>@K9zudOL!f!9&s#|FNLahW4IDaS}xj?rAVqdMu*x?T~6 zv0HSy_s`nSyZ0gw`gdeNEy%iIqCb74hXs`T^;p7(W_hv;6#i?g5k3wyN2Y#cdN{qT zolMX1{P^rrp}bQ6$!YP3*n+`;Q4PIR&_>I*2F`2K<0X*^leTJ%x_J=auI`qgho z&rqU}>7BiKQEW!>V(r}AV(kl8+q|Kui#Q{i4o=@1FBh&}>?viusA8P|V!~{~Br{3> zm6WLG7DUSlaIE}^A#;K8qPcIAMR=zb!Ptc7c-f%7rKuSmBao1Uf0yNRWtn#`<_292 zBvzX!PFegkol+tF%~*IMunwI5*frq^vVx9RXJ$8|jTD3yUw6_Dr{872r^?9Ue<~8N z#ZwgH=UOP5DO;^R3R&Wnxb+KqtH~*OzLC|10uocg#v%>n$uXLB5pOFQu?VU}{1 z@5>`s`B(F)^7p$0;hPud zR?XOQqV=CC7mM+`R2Qc`U=qF{v_R`DnlK^R>j>;S@+CvhO%foBdL+LM(jayq#jS_u zf5^fs%x-W+8!>@eC*PeQ>q&Eq^~5?J%J_TwfmG`My}sPf9qWdISb&6G4N@{RAjO7g z0n*!IbpD;Cea1DamFCmzSU@Z&6Dy9~Q;c7bJTza@Ibzz=@aam858sh_q4++lzw@nR zEGE``?%!4D(`QJJdXf0lHBQtFJT|q;L3H^)B*X&e4I{PiEcgpPuTRM$?(LA7Kg~=R z+i*R4xqAH7Sw)m0d~l^dKH_q1;<(d?DZd+^!J2r&KiJzmWwn$-&Fqi~*4nn^yotm> zG>oeV-Isq6n6=!-ur+q#kD{vya4PJ7`FXlF*?`XetLDKjWxYVO<#U3NjU*NAhT;2f zg1jlBdHMrnh7no1(o2h>W1orgiuN2u%x=}lOZU1>IrC>uZ5FA3#;UpHUODlqpS(Vd zerJj*hE1)vGn~k-Dl%uuv9{u2fosz>$?-m3p#mAjcJiOf!U}}3K(EV?gJG;lU#l@? zF=YRFKi$Rx4{r#^Lyz}WAAOaI`l=Ix*P9Cj_7mT`>K)uY^zVdAVZ3ux~iupMiO%Q!+G=#cVo$Pi*@}Z?~b_;2CxM z)R4-`CvV>x%v%-%UGv1tDX_KAVz1u^W@j2B9tC@c7A)mWox~et687+ZtI%n)@6ke~ z2riWmuC|ps4y|W9s5=j4HZF9)c8Qji#|-5d+fuFq zR?dn?({NJYJcpm7ESvqFmSh?Iih0EH$RqFX55}!fW*bvS@!JxO;h}-!jrqF*LdkEl z3)jH@;#_LD$QD1rpWxg=y@_Wz5Y(>xgZr0 z{~1=Nq7!Qqmaq1$tWKpB))cZ)JM1F1UF0}d@CB{i0uES_m13Dk`!kTj|6)=e-Ti0U z|4c8Nd&!BZ$TI;k4aS_?;3}EN{ceUn8-piBk!Rbj+x=mfOVv+4%QNCXQh$kdE2^~0 zdESH68tOpAP?T#PwSW2Z6#q<9#)WD))} zXe~?!UFLVrixxzv2-X}>b zF-g~(KW45B-QE#zH01WWj{RB5K6>w&s-)d%xFwpU%`t0i&ZvF|V7}5JE2`Ip5kY5` zI#jmg2GdUN3QVylN}D`c3no`jditfm7w-yq^qLvZjj&qB=o~G=Bt_|Avl1-VxYlII zgz}o1=2?Pgal-S{EqFg=<_8(e!_nGD&SE)*ncLT|&Dm0&W?W#9>7B8cmELTWy7&@8 zJt3ggx%!`In7CzuCz!EAB$mnK*m${ZZnL$arY_klZf#JKNAYHwiaNZE2pp*ql68zi zjSvUoO`V9I#{$vO<@0kTqRK37FD_(@MX4b3b%{)q83_|LwHPkPzu#HYuNxC) zRqR>a+Ac3$)Zs`Rj7qDt9fs*Ytglt*ki8$}VBVk6YucKE?CMMvjJnsXYW4E=>}Ma& zn3RO>@cEM_oH3)e$Oi;Xn_&H^$8u|1kxBOKiOP)UQQ$)oHF)Kd>rrBhgsDRJV@^J7 z273&DG+Z_)Jr_5veWEy1%9-q2##LXJp*`Hku@Ev|firN;BqOi{q{>NBgA7KCakh*W zW#8b?V{tQk{}gE^aYfr5VOp62bL8~28)4N}j2Q1&f95xtZ;D>=ceXL0Fb zwv)-5ir9NAQDX1m=JqmSSF<$Fh-XL^%aNRB=ye3kM8Bz>-7jO>+1v|~x`JZ-MajsV$CREv)KrFsEVr1%3av1P!mfvsZM@d>7qTTzjI|vrQ;|S3=71| zL5pl2xbV);)vOjgyTh_!ZaA+gVj?Qk&;1~$##3MC6tqzF^$YvOF@JH+|NlR073}dK z4tPghH?RN)08F8mtyGX*>*nSu=CUm)zEWW3VrxhUKK6|EI3jh7r%eKc%&TCTZO z>Y0M3qcyc9f=9AhRWvYxb9NLHlUZddJ7=*(Yo2adtW)<|jOcOrK2CVu^e(4!k8Wn@ zFLN)~b`99UyWI!f$|Od4#8`1+H{`9u=RR^IS!7oM$mP( zbSlf2aoFSBVzZhT=Dqkwyui`vLE$u=nD*g$D zOe}jkt+ZW=$9ft6=}oSu<3lJZ6+>$3btYpfyGS7iv||t-Xf5>}=C0Tp344-0t~aho zd*hmdzttue`{Ko<^rt%u5}{YsaLKAIt6G6V)QegmnStGs{YiJ_`omk4x^XzgPB@93louif9JEIy{B{}g0?xgeU%;`UPH5LM%WvBCObvjqy7b$qxH zL*D0kE~rN9(^XXZrm~8bNUGp__vasMb((LEQx$o`T=gC*7N*`eS-;}Pd#OBGqW-WV zrXtt0vMP~l>61E`)pF`;)f9c&hEnYe*%$`->d@b$qtI%7rlJ8)%YHu5Y5YqgPK|Kysj8iBd2thUxF^V{bY_M=H$?J^eQ zGVX>iwoXSmxBEHlb1?3IC&DpfVEg8uShDwNN<%Sl+1Sli>}H|>dObPlhE__%Co=d#pP0ls36yB((9@ z9;e8=pI0IB+dRjV!`&Y)-S2hX*qHfRNeM-qMl5+oiOktl@BXv#4ZXL?-wRPwnsU?{ zH{Ct8+3v8gS3SOdDHIor&3vq|PlYP%+U7o|9{3e0-DN{jy5J)1Q&1i^_gT9qDbRaQ z7t-zfykNs32FBusA*5|j2A5kM1K*JBPh6H(y7Glc@*LKuXGg%_=7yc)z&AP)7XK&T zn=IbX&r6WBFt1H;m>JAJY&RK;^-FmvDmGado_Wi(T;Yr4F3Av)ucIp$bBuq`Nw>?= zht#A+R?!2Jzm`kD!4-f$ZEyxS3jhfF#31-TKY0MjWL{@ZZz4{{(kl`UU>@6bAo2UHMO7gvc+TL=p`C zYtrGrnwOXO1q_FPzo#$#S)+#ZFQB$40b(|Z68_ui|4*>r)&GJi5f_sw;lG`m{{${8 z{i@X?83zC5)=T{-kWBR#urnFP{L8QPUqDerLJBK_Dh0;;+cyjVF8rP3fIMO)h4v@O zEmP>=zx<=1%lc0o$E{zDzD}V-L_KH1y=sJzGq?mX0vvJ#!AA%^BgTa+AYpw@gp&mk zA}Ey?H_rr0avXZ|xem&SxR|BMN@#o}l3 z%ri0EF3X?0me06;HP&zSGt2Gyf50Qw2)3tkP&raW_%m|cDH{apnflM&`8yDh6LLuT z(G5z3!JWTz?V2ruDxC}d+erml?du;D;&VonI5Hxf(|K{(pD!p^8>^QZ5q%$ZZ<32`+q&4g*u2=hTx|2rbH5k=zO_5kRnyh|nW^cm z{#JEwZzpI^CukiNR8k%BD^NT@B?}5200`&@05AXmfTN=;ql>MTshP|F6wC~scD85w zrw;oPiJ$(x??}M{e-Mw_w&VH~Fn!IzOgZ}|O*tQxN#{*=O{FEB3_4dn-zz(%Gn4HS zTg;H~nS>;o78b=DPBr!8E=~mBpC=Y6o|32KZ<$Fk z28p(BNGR*wF&y6D`kuXZDx%R zJB^ff`d)Bh2NW!A^-Cbk$Ccb{FCp9+({SMY`*2$a)I4bKUh{vcU-`B9sneGC`Ea~U zqLnxBssq0M2cs)>Nc&!Naq_+W{eWRQc2sa7Z9?-qLnQ4h%nYWlS9d_#=i-Nt`LBsz z$$jI%_KpvCx916ZJI%CX`kyn_kQVf%i)J&T8HW}AbU=5)$H2a*6Muh!#B*yHfG6$ogs9>mQ#P@vRcAu?xX1U?RyC6EKY;(TgTc*)XIrKRdlq9B1y!d?Fi9 zA{3cz;ejsF#kfc!5h&HX0id!Bb=P2`75%@}=rjAeio#Bfx~t-NLp|@!bdj8F8RNpV z!o9<{Q-_@2cN^gMHr=yo$MsR51?H|XDR%W7gBn%_!-w^VNMpo;RWU*-iZjC#_BXpy zN`RVzml~fwS3kBsnrY#G)*{hMLo;xkzHk3zu>_8RQ-aePZ^XlfrWCXmW}?WaoJ1eo zEcS3E3m$e>^%`^;*egZvD9~OQ@-|E_NZtl*&{JUd=Hyx2|M8dbC0XQiOQ7=mA+L6vlS>@009`o#@rg-mvEu)1Q<%-7L-au_uJC6X{H#TZ+sC)v8Np#G>N zJY1k4`Hc*?V4_?kLCv;7sP&Ck%?d~E&Bh}}>6n5!Kx|rMD&v^b!sevUtb8?+up{A{ z+|O_e3aUpZ72Dz9XE_blrkv#21rGA;R+}-?G1`!V%5|Ucw!x-ys2$l&zh4-}q_w;} zttlh*$mZh+%XJJk-*d;NyGP`FGG(T_MVv}aa2aor*Gx4y4HnAk#r1I+6AD=y?FkH4 z%If*3?}xjkX>CWXbb8TS*`mD8$gIFRYJ*;haYO;|I-OC_YrhSVcz$|c1Gl0c{Izwk ze=`1-h-&xZ|j$fZy)UgYsV2wBByM1buvXNV$19?^d{QxKww%_s%vyxBE`5vb}XW^6_AR z@2xmu)xLE&<=GOn(Gzli3N)I{g1d}xd(S_g_P$im;LxQd2&U0%pHM^txw<&V?`eBI zcJ|7Bt%V5vw3c8OSBy6lbKPzK0iIe`-c4v2qrWseHZKR_b?91gTBQf=pccF?6d?4-raGe2X#KLFiP@IH=bwSP@_ zcgAIQ<2D8=LHxq5G(CP%wtk3$Y2_$C8Ecw zJ&&eP1CfDTu*Rq)!a=v?baM()3dxsPzZ->a6;m0jP7sA>$Ku^&J4jLMM!K~^i`kib zpa&<`N<`t~5rdVgeg<;)mY?f7M#LyBz2O-yI7ta9vP_3^H~YaUP{(VBnFf4~Dym6v z!s{EJo{&B!=@`#a4&}v9rm|QEBZs&ctWrc2a!}P%Qv}D87tA#*GyFu5ud!NcXgS7I z)MC>|R@Bc^4(7`v01rWpLyV|DV;nC&;)5MFkDD2wOi`Q_41@$jU*M^KoCoiDG!lsW z4L(~RCNCH_VgovS&P!nG3MvnXrouh^Dtn1yJfgv!32+k-32v5tp-T-sN^GdM^C37!r|u%o=|O_CroRIz|Z=%MWy z{Z}|cI^u{eTiJdLGF4 ze)I~n1Knb#i;SKfHOq7O^Xt{9dI~)Y`Nd{=9)XZuwJLoKS0u_3GxKfI5ay-b3%WR# zZ0SAVwl8ym!yJKAxVbWp#Zy^8I4sook=5WA`z_`W?8(v z=EfyKakM6egh;_uCx2n>kCXk-$VII1ik*X8_8zSeG4HQXHfSYSrn=XZH&5-fHgi13 zb-#(k$^)ZNL2*+Pl1SNa#TfNfn>Gjhs-8G1<~sl5s*?TS%tNiKz;~!<1vRHU(-aEeS0GRr;HW`V90FyL1LpwXhuLPYz2cEhIP*7i>i^?!LMk zV;Z7L1ZMGXXg}W$siEvAGvoK@FD6o>VQHj1N-dQ7_PE19LciE}sD1N}71&!5r6!@C zQhWNxHCS!^YhW{|l&DNrwHqkt7h6ZDr#PSNFNo7Dk9(faPkgCjQE(V%cSxBV1kQx|#tEl`|3oV;@4C~VrlHLAC@VjcV~Q`hsob{E$d(|K zTIYhe;PvqKBBDK5a7+QlI<2`;otmat<+xdcT-H>lszxj>VU-<2Hkd2at83Gg)rKZI zYw$m}?l(<42^{ag-qVbKu9E*Yr1akH_LrQhb(|GI5PBire?4&9x3mSNusQKJwwef*Gi!`Pr$;qMA5?Imx zVk)u9WK`ml!NiN1r6TbTN*ZPQQ8Fwvr}SQ}=u}l`x}o7VG4DIhJod}vXh<9Ct-fv< zj5G7+UP}I(Kgg1YaKP=77;Rg_s>JKYgo!u_-_VjL?hiZ8-Ju>icz*J0wsb%QRYCZHN`4M9H8d27gPZ2ECGCz zx!o|mP}T_v0@$!q`1!?eDW~M(maS3_tU&c?S2|9v24QI1BV{Kek@Dn1gO!6CpMrP?fK8YI+g-%3Z`nLJ&P{CB+(K-;;fyZy zq*gSN-c5=v)~1*vnSrdM6?4!QhEWpQ#VGqceK3d2MJ6`Y0^sN11M}>s`7j$|7`eIE z0{idcyWK$QUwhXAi@#};tx?VSM75wH^Z&wzpu!a+s1q*+!Llxq`9p72%wo~zkrMBm z!}_UFc@4itKYj~*JQMQUH(u1_~NB2g2G@D}M}H3zR^ zRmfx}uY8+|e#E|HguQHt^2n3OCEAKeW{v`9$ut8})q5hfODp4qkg&5Y?Z@GH#q-y; zBX3Z4MF1!Z4eHtV{f1wo`hAkLZanj>86t2{jT0rP5&+(@+!98ZWl|O@$5i`d(}iW( z+vKhFu5lhU)cdz@U(ZIqr|)rHLlNoWn2|(RXgH)g6c0IBfINb=+AzvT#*A20cDJ2j z!~`tSq+Nh!Z8_}wcZ0P{fQ!9NIgCqYbCv^i7zO5jEUd74Oe%Hawt4D_Has8G1JSGL zki@1+n||!DG5^q4_{VS1kM=h?3LMWh;-&0=!%@f1c~Zc|wQ!Pg+F%l3`jg7!deD3} ze(*Oh;$sLile>8O55LL|`k>_kp$c(<=RM%A4PoFm87zeu{bEkVsJI1+Xc1DG!Foeh zWY>Nq?ty-?3LO_YSw&hj=PoX=tPHUt9+}F`+ zz<^$KSa0SJ64)893F4b+4SVBzTCIuH1AldE@0d*0JdN>Fyy{>`8zK&SoWpz5dFrGC zK8-X?M(v!B^gmbwuZvDohTqM4V3avo1Q)5;C+V3Ah9IR=p_8d}`Dp`n4G1eiVCf{P*1 z=0RSzE5E@UAl$QLwz977jlwhK zZ^Q8g_<(tk9maND`R@lUpNd{e7dzhV9q?P1%j_IBzLLOEiZdOJH_|@8n0!Sx8kt- zPR`FK>gQTCZ3^ENmR;3mIpd&tF*q1=E;A_>L)+qFsi=iwvPiT#$ewd|H=2wc{y(!> zB1hv|t@bSvhwtZ{C~KP1L5l40Vx~c?1MH|W!Bj&#%weIZKJbB&qJ1!LK%gyw%u^8z zff~#0(bPq22dW$}fK}N6SxF4dDOg_yre3#(P zUmymfIw~`Gf9jfDT|m(SRj^ZJQu;Mn^uS-{+Yn^=G4^3SBS;dlLRFH2p;bHcSIe=knUV{n zMKQOlK6*)2UL;UDohL8|b`9|fDCwE&{}#Up6>z8lkxc2o(<$W92gDLG{*_1hDKSl{ zD@lt$+E)Y)lS(EP>P~1c$1KyfOXSU?FuEeNv44;a-RVB0D3welem#fkFB9&LvG|u< zuPpJdTC1j-3!*bBziYDUUU!-buIH8C2W|M$|K*PZ)jG6In#D6;y*s zt$W`c?=>ug6X{s>3>oD5b0w$ShnLDTAl_HObF!%{>0YgECdM$Ahv{OPP^H2m#86#% zen+kqJ~)Z=u|oyQmAXWO4{1sJp)=JiL0C=$r?NGCt)K+i#)j5~SQq(7W##0G*hKLJ zl*7Hk*`lblq`9KP5zR8AH_smLBn1Ob4U@b4=%=DH(!w)(ZX*}eOuD7Bf3NJIQ)E&> zoQ}7nY88n|Ml_k#I^M+GCW>^js$B4O_Wk(7M#(vu?qxp7)-!6Co>8eA)I4TPfP)?a zWE0=9m+Zx_fkt9Ytc0I;h{3*!hSoA=wfrzF%Vz6|uB~EIzZ9pSArX;6r;SW-TTn(H z!4Zc%<&yoK9Q+uuytS@7((ibj)<9)#>R0_o&%&&xk)@f3_UgJ|MR%u@aSNlpa|{RJ zqO#u=9b4K2Aqio3XP-=>VAlMYwT~-KffRq3t~q@R%^z+4>JOLPrAOYa@h@EmSvVWL zzO`_!h8lQ|zjCq^qhwR5oiZK)6=#PaxTUpSi%U(`5cg`2dS2`2Z$b^nzT zu=+_f8S=SPi`uXC$2iG|5psz#hkOGxDR(39Fq)wQ^Wz7af9FNw;4u-;Sy9;96jWgu z-iUIlSWkP|oh{f8D&|lNc0Efag%ne#!|!Tw!syyX>KJvT2=lsJ$1wt|T9Xl*y0s;` z-PPpAgVV5AW2dEd=2S+KmKAhRJn1Utu1g4IYcEjxR7upMwz(eh?#Xrvr}N#1tVu^wa_oL5 zN81%^l~Q>&F3$__?{Sfe=`AuWV9tL5zq!jhT$C2~J?pu;XpzDw&gS%pqJ8Pm4M!~2 zZjXnZ59WoPd<%0GndHG<9peIYXb_e70LdZR>+jH`Doz231WH)u(yy3UAlhFwJb+}0 z`RnMOuZjkO_!3rv-$lIjF^-lM3NMr43QK{gA82Q9A5b`8XEZ*FXC|6lnItDFq8BbuVc~>v$$4yL* z(;M~*M6><~%sJ@alGTU5wZ}6{r+nltsY?*lW?;L)#xU1Wb_Nl#8r`89P9%ULA3=C& z0vaV+7wHJABXeIUjM40%8&_%8qCp6?jVjFH#6#@4S~0g7808Y@N7~xdMf_!cdv}kG zQC4wFpG^1HLc7w>R9>Kd9iiK{t2p0Qtye)V6Uwl^anN_Ddz&hrhBS#awNgL3j&8rw zuakFa^Cp5J5~O-%=1%Uo6EF9aSbk9@gMqSs<6RPy45WARo5WVVc#$@Ll^ySC#X;s0 zhuzkx2;od)63Vdf85@mXFzAdA7roNo(?=P5JP7}MTzGcQ9iTPcBg_P1ChIdqI{8ltblObTu$c=9+R6i(B)>+))MHBfWv=z<&X+kK@I9^~qyzW=D#LA%0%B%>A~4~j@_%SWgGxAgC)wUmsk zT4Ju(Mu^oXxLR!NhVh6_Z)CWxbLRzA(fvf8tlZWtJ;bznvJc)3e<29GeI|{#!nJpG z8n%Esybtj3+>$fPK_?dc=pT@ zO{z%XyQk~1-!UQ&wp5CjFy?dV;XzC4+S!N(c40iLjH4Hy_$alAyJ2VfBP6J2Uk>3N z*%6;MQz@#+Q6vyib18m%EmhEZ(kOU3<8vi)Yj4uaf{gEC@Z`*DV`1oIt_$2^HNy(> z$f8W*^36hF4I1KtE&@{nlsDE5<0ZoF*sBJJG2LaD+e>lrGRJHcqGBN8T06Dq@cr}S z&-yZOI=<>Fm*7E_3|XD zgP91J?Of6XIFXo>4s4nPNr9pt#zyR@i+=0L`FYZ#y`CeoNmyy}!WapdW;e zlb{*5HTI{^iDk;QYzbj*GO%~?T!B)$AZ;t7rgEop=}>B0%<8hne;x?(->e0zeJs)v z0tNu+!~OFk+sP1(AdKFiz@Ez?gB?QWB79F`-cM%A>-3}`W|x)=i=oJ)X6=fUqxqY4 zNZ9Uy^b9lusxmV&Y;0{bJ>o0>`7MDxasdd&4#4l(85NVuG_)x9lM&%uc{onl#0Z}R zu?!!A(7>z%qpopZLD<4$gQnkN3e6rP*7xhng20!D`&u;1MGg}PVDaU%Y(*%+3UoEt zSc5j|fz_e8zgpXzHHX_w6q$PzS9Bqt?9A=eUQ|W#o23D)X;tI*%KDnyBFYr~DA_); zszvRwwF0p_uGeUAixrKC57Y8f9?PvHri|%5PafP$%=7gX9S8ZnWeWdI)N90>s_Dq$ z=2}hrvNi@2s2DssD7iBe?XEY(e>OTz5+v~a^0inf|7_I9f`MN6v1+Lz=H7)Mm1L+u z7?5zJMMa);!a@e5iCvPbR9P576u7}3Jkdip^Yc0D`jy*W@gup^O}M>$wZ748+{@?Y zR?#7|es73-GcrZOut>wi`;4yHemJ-6hPJKcbfu>4+mrv9 zdQ)|d!gqo~M$9M{3)O^L8jaJXaMNt--r7{NB5#{1;w2p57gTZK zE)gx&GE+zn+7*A^E$CuXPz3ix3=d0?+*)mAdunusLHCC`$L*nvPD#{#ikO*EwCi~6 zvft6*S)IXptcT)z%jNHDb7IvX&)&CH8q&4hLq zfLkIoG8h=*r6I0xNq^6@BqriOLf0N+!RU7lP6701&2mgwo-J_8cU(ZDKa##G9OlhJjkC&Gr_kLTe*7lpC>-yavB! zH|ZKT4FQq+WU++<*xcxCG$sk|`KN zmxRt{CBW9WuJ-}I1ezd?!#D_LOUV!tHAjB}16K;84$;?bVrh=3HXednpO1kWu6+x@S;&0XJd10p+Vh1Q4_v@?Yy< z+%spu80~cvV)!t>&y~|P*$&+Njg-+Uh@1S}Xy~D2nVFE}Vg6y~FG3 zUB{3Ul#1z|6>#}Y!39g9&Emq^Rts#ia7u443kJ96Bc5X$;DYZrO=}wWGyv-mb-ADT z3-o`!dET}dRbRhz(B$%&PPy>5B$st0n~-o~l4j)`*quZuD;ng_8A{-dlEPAGxN{Ym z7RcaaLL6x~eK30WnG+mydRi>%qH&>4);cQEQ`#6cvmB z0}Z}chP3swnak{*iTRv$evaixv&kY$;a8?~ucSU*XOM(M+4uR38y*8D3DID{V!Uy8 zPJMad;eTij$o4Nx(A>0N=Rofvtn(mhvJo%+Fk4_C;U<7AmS^fj>XmY>P9fn=bU#Cc zc+pwkzmt|wQOPFcbi#4a>_(xEeDj=s%n?25^6IpL)-oZsk&r~>!{!3aC#th|6L1qA zs&cvosD*2UGet^HW6J(LfBJ%UR|RB?+-nUfpg^1--%k!HUC-DbsJ!)`EU%#pOk%j<=?vwMBx9k)`@_e~hlMRQ> z&YZayTb-UWD^8`&u7{%Yv*_bdm8@)aU0~x_iv2ke4r+zDwQQZ=?xE2X@|r=AoSMmr ze|j@sxs&k84XG0RPNl{Uej+TK;p4JVtuj3E_-|unrk@N6Lcat7VCwV>_^|EGZJaZw z&#xR&!ZXI-WOfqsQ5mr-nz%nsxl;06X^0ve?-a5&4;+v?DF=hd0=02@ow6aceSv~2 zZ$tqR+Yl(x4OEkB{1?G0QNDG?8&{nat@SU?5HU^~LSBk}8d+hTS}u88W}&hYv_e-n zNnpr7O%+L})0(;Pm^=v_$2COgktE=egsQ*!fP8lHLFbKwP4vy(^vc{7@?9m7!2rqn z ze+=kYCFB1gjZ?a6b=Cg4UfEPtX@Aw!V*uk@&myQ6M-I}U4AB?9MG=A{dL*8UxqoYd365c0%emD+{SxNFVT3-Q_;u zn~zve>NkBW%cJfEK9@+K<}3x%O%z{7hZi2wMkCkc<~-5WW*c;&G@{W_*qGg{%s%(x z5OfcMa8h2y}SD6eW3mSc+AVoaE|bYv#B{Tsp2QJ8noQeUN5} zL(7mcBH011y5n8#^_L|bo8Ua`PdC{?-a*DC4k;WT9}M^KQ}6nygiZ8)pYOofPl$g& zLZnF=PED6r!v-6GF2Z!ArS6TxNw;Ulh#JoSDo|yvm`at z9n~&_phnH97!;!SJN)8L$z+d9O)qmgn0;#c7m7AVxgY4v^1t`$9sILnf6lVC^@<0v z>csc7Q*8JGpKlc}#|dTvCX(KZ&LZ! zU{KI$7438I{3u zx@X!Fi23Uw`7(TaO4@yH4(el}@<-il!^LA`ILC^H#W)~FGp4(%tKgc_9$oFFXrp#x z{jS_sW7&Bt*r4FVe^GXG1;>RAl?j?;WS#*OdVpvx4hA;=6xh~WOLd2TH@OR|_b_NJ zl+=v#MpVl~+?M9VjB*-%l%?`+Ntzxl8oC%u9CGiZGDXc0ri}w+0cV#=gxv=_3}O;} zC2vhGF82z_)mN2D*f0qN3IGxj#fVj0 zr8^fcw)m8Ny`WynnqPU0-#U7olu{UvMzgFVF`ro+QD!O@lEYwEixpbau;T}!@fvZv z@8(GoSn@M*>e^Ct$|T86OK!L?1|WCHg5t5pK_NGafCdf)r)apTa}>s zp>=dlUT5gO#K;z<87I z9%Im^yn1;i&V`aN!0m6WT1aKZ93lqE4oNGkdO_vGw7@KE>0vxR2m9`y$Wr$zOtzjf zOZ_HTb{+QOka+)#F$_M%8a2~HuB=7llA+ovEpei|jh$dEkR$2(-_Gt5ZqI$~+}YwI zm%uwZG}L(#W|jsrZMZ_j8>>Cn+=d6CZb${N5!b%KE_Re=qSD5qUP8^rZigjXyNlBW z+h*bqJwu9bGToG}uHTy|H;AB>)cn*4YIkPEXML)695CK~27MCJv;ixFeEw=)`Sty_ zUeT_L)x5^<^RC)+P=AZ2AzoXlEktyC(?Z4U+rDv|Z6992FHjPv;ruVA=urJh-5TmxTP~w3h{Nv@})L=z-AP za{$0Wp<$zC`%PE;QnUVlKJWHFpV!l`vzT_+rgid$@Xc*Pt4@uF1xu`l7anQ}kY67n zp=_uh<2QrjgIusn)P>W3I&<=U1v}qhGF)KdgWgJ^55(IYF}IAlD0_NPT<-QoXp`X> z&4^t7K7+^cMD>bX!}#2b2#_xz*W7G3rLJ3rDyaN1VKXeSb7NoeO}eIPSxJy)ey`qu zbe&=R@h}fM?ekxZ3Bzj-LPYh2Zd#J!g=m5H^GxXPaf7^CyB?`LgmxNp3bGv}#eEbV z`~higHf$ptE$huhWCm~q7K;7NXbvXuYy}??Ij5(mF4Nu9Q|m=`6SJW-o6rI8TjTZS_tdKq&BDk(PMI^nX-y8eB3*&a zh4gb!A}WYBvIRQEJL>w9%_ObJoo32>Snx%uYI$5DJom8YpBj$VJ~C2LITU1AHJW1V&fajvljlfaV;^lLU`-z$a~2)P%To~p6* zDxhj+?eQu*5Xq%&-Q7RbD=2LNd5HlEFEHNOU!*u}ka;(}uPKgscB=iv7lv!f0grFU z@sn7RXoXbIsrRnasA{zEty&yo-AWK7@^&pZTMT?nQaC$y<=OGkAgE0(E9{sHhubkZ z_PD#?=b3g-t{Ulo^xrJZ;tMz)Vp$hi;Pn~Bh@O@jmdgrRWj-+|_T$2WykL?vI8RVgf3-H5)o=9X!qRND=3`BML z12vhiX{N&qjvew%@ptJMqUQMMLI;FBS>&RwxTNAWxNO-tPU?qoeFf8uu4A(9?2f`4XHeS;?95n z7Zl`6>-(R7ttD7O{f8DBal}?ZfWo7f&&0pkXhk{D4TFn@K;hob#UnM4?S*v2derwn zGsQHLxJ_V_J!@n<;niixb+mgN>H^TJSvVe`$MMnC=_%&0v}HH2QC%k zkbtqXRp#|3DCP?xYMaxm;jjV4$906}(kCTUxjxsnTvw&H{xeyRgr_)_E!!MzW#rG4^j}$B+jN3mna0c50jJAnN51_K7hpRY4`*YJSXnJ98OQ$n**nhcX|>ubEV8k*5~icaLY^c#UsP3EGV+N67} zM+SG0B=N~sIPo$P__!(m-BCSN`AaS2KtGk6b5kyjOR|lRFi_794?5C;*j3;L^BthU z)c>)FOgwYne_#C5?2uf{<4EuQOgFhSFd5ehBi0qlP1rZnxT8)hV^E zu$#1=_95dY((EJ?ocnxeIB1lW5|3wR2Sv(I>W%?;m7ED&G>IrO<2>oj(^$7_Me@S?Bt+;>tAsu*2pE)89wy6P6AbA zp(z`+G4EHTDa};&U!+N=R+i9Y<3TmMyhc!_$dXuS%Pl#=>~1p0^1Sb{m&YdPtCs?y z#lmpY!Q-|QaS6~UaZA;FESvtj9)Zg%B&B24R8H%DQk~%_ko*2J5oS&ld6rmn8Uh}v z909`bb_i7A0GmNO&Q4LwUO=c?aA?d-Q>(0f+nV5duUs+Ph3FylBA=oiiT(Q;U2D zkcoj2aWPR$B+5@j6!Bs(57MW!v82D=+0iLVZ{9h$u09{|+EG6pu#5OGi_u!ZJ0mG$rCwI;Vf? zp(xGcduFuF&K9ExZyRa&EaD|JNz(vt5cK&~?l)q2O?-}#<<`9H zJ8M=b%K9StuyVu;H4C{v7lBlKp(my{35UJz5PY;e=1bq2`+S)TvMYehYf-G9 zi3jcRq7$Otm>l;bkTy;6zL)<{#-jXt<1|Bw;!cSbvTKQz7dJcP;a3J25FXEHuYMvut>HF&Iw~xhR5gf_^`=n#-O#A^Zu>$q;nW`_AkxqDV2wpTu9ORH6Z@I9lUW~}I{#+xt6v-lspiAYfNZd&q%HWS=qe$&!ZE_kRm5Yuw8=Nj)4$WV{*VpGq zhtF&x&2!-FqpZ?j#*_4wPTm%!&r~#_Xvax5rt|#jk z2EojO#Ff&EnL-Ds_ak9ZtvNmCq2(94zv|1{-a$YL6-VnVS>SB*4+M@yLFlle6~dU2 z9Jw7`K^=#fD_JzbXt|!@y~=uNmDOL%8w@Z<{++OR-0A5nulC*#|7HTj!j=lK^)EBI zeC<{EA2THxsEdKUO+P1EYAeCRJO?Ls*j9Q#RSvPib z8{|1cgeJhV?zSG~ccVPn;D(0X>Rd)yzR^J^WUGYWGT)zlYHzj7ZY|Pl&cuTILtKDG zwJf!8VJ)|Lkh?zz82VBEEu_K)f5d5tkS`WvSQ~HckpmJX)(PtYiF8hAqG0u~aAS4z z(kK{OOM)~G_0$(^qB#SYg2GHZGMD~q$`(79jx8U#Dgatz*Ew5b4= zyS4xoWT=5DHcGU>%}W;j`5geCB9{RLD^KO46mbK)_~*_!g9Ru=*P?OgokmM&X?_RzqL&5`VKuS zDZ1ao__cy6pim^emG|sb`|U-uo9uDK3Gex9W08VObP^Tt_%caDcgDuh!@9=*`E7>r zyTGSUsGghXGE~3EhfbDz=u%`Lsh7Z!coN;COZMcODbLaCzBc2%g;h3L5+APOUX@}? z-r!iB(gOR>Un%;XQAv~t4nwfyO_GNNNwEk^K>7L4o(mfIC0ZRQ#!jS?4?Cqwo6C5Biy=Q8pTbdKj z6pb`MJVrv)+5Z=IVo}%oN2%#_WMpai)2z4y-*N$8)lJY$*hQ^olEEHN3>hC~^`7g| z*6-o>j|0N(?w?Qy6F?GROmRO5ZWnfZL1%me!QHETOFp^PmKyt?D_f0-M5#86TYrYp zOT$i6zwO&Xe`(l@wZ|dRty=R&k|GQB7CP#*;qd%uDJ)huJ{TvSG6qpgj(!=6Pg`!ziwZ;>=OH? zwqDP9@0Fs@iCAk0v>(SXue3p61-kwG7Tfi+uUgIgXRcQ{ZdTy=9c-*ga^^8fbQ0I* zgCerY_U(Ds53-w}^RXmhdYoslpE8o{p*8s#3w%UXw-Pvu8sc%I(M1HKS;{HfG*i@i zeUiwIS09C_z&1pmnXH1WLO_R0aSu$tR*a}?o?L6L6h}=u5HjKl{tSLo4wfGi@lHU0 z+*)G=C4*s+bZJ!9Yoa{Y?Wcc}R&M~7^0k83<+AKPx)+PNxcm9&c!3u&KJM|_bv$x! zWCOh$(i-faX9$#oG~VItF64%RA|2jRxA2EKBbr_I6m$iBif6?>vOoCCq4#OXG?6OV z5>U_~{cP|m!1<9z^MxX5HQG_l7kzqPzKY}lwz5om73_|UbMekAN5(X^D$fh1j>0n# zO!fps-7X#Uy^y#B+w5)fuP^c^k+kH#fAeuN=4HAD7~XS$PbXW$>G{AI|0( zu!!sfLb?Y92fhH1KVybxd0H;{rBXJyw0GpEv#tza&Q$rgH1l`Zm)qY&PMk#9rO74| zFyG6_fL=jLf@iOmXxOaH?d!O>Kvt4|4iXCW5*A%f3Rh^LmE0u2F=o#}mLwkv_Z!a^ z0me5fVdhvQ2np0eD(}9)Is5cneM>%i@}ktlG$zawU#--6-a+`hpmawp@cGWR3a`($ z?3_Vi-}o#>ek(y$^O_|0Xb~V^BxN^BarjmzkP7Qwf%(B_4lB%pG#FGcd|A3vd2nAS zHtg_3k?`|%=Iitb-mdd~t%$P@s<)Nif*Lk`)G}G(gA4m!f-3zksFCd=N1?Ee6DH7T zU)h;w`0_(#5r7WQBJ5VMd zIJxDD1nsr%m=07c>V`~^25M7cT}tpzGHdd9W6bKEZhP(k|jIP7!AJz3O9_G(>m&@L(olxS^gU_?_j(eg~=y(m> zPfZq=c}NNk%#~cf6P%$cq#-7Nd&BHyg+xq8nOBv8x-*XwxirrekulF;Ys(>%m}n}P z@EkZ;;HyF5q2u?)?E107g+ro`^CO>FR%vj#gg|_TD?h!R*S20H7h%qKgf0JYqQYMg zy~@UjLNg#&WY@X;Td<~4oLf8i`RKb2B)xfPTg)Fa?H@4L8brEXKM=NnMyNy9gpPx+ z%$}#2qpTykfwtF(oSq}Zc$xF0TZ~8!5Di1TYdbkF0xAMrd+)!m{%g07ur(_Cn~M+f z2@YJZz!pT2C*aixFW)5imryUpIObIonCDJdE=@Ec2+%5kHhD@g+76cKK0gtTX6X-> z=YIcezruw-hHp`WekV{lk1#izbUQuDLz7zxU9zCk>X-ZD83SySfam?is!Qm@8(U=T z7jqD8%y_dHR^&m+a3(nGX%rcEx7ztB$&9%8ZA$YpkAK|^k4RAId;9lJ z;8N$rMX1VWr`RjTl5a8lXS~l1@M{n5=O-9INfrzo9RLY{PUsNH zxc`@z0HFeW;pngHU)j1aE5KKF<)4niSNR_qejWd{`JWOjALM^)e_hK5{Xg0ufPAq3 zc(H$~ZeQwv&vO8mlBI*IgNvnuBM~bD6M#!ZS|0qr?!V|CPyUZZUcirDjRQ1*9|yps zpFN5AH-6X-f&kY6q5#l;1O^HU1_1^J1_252MUc>t{}D7S^gn|A-vs}U5dR~j|4W}= zoq&Ax0S*oh_4P%DgMmZ-|0Ll5)BD`~dNkzEH2^%=myuAw{#R+=0Tk8Jw0!^x0*WF* z;!2XRk_1GufFwzhB(owQNrD8)9tFusSaObnM9CsKM{sUkm_Pz%=ON?-F~bsEO(;&!rG9?U)1rmoYgL5DjSYZGH{=To!O}<15s@jd#_h(JNn)LEE!EmtpZd|#GdNHAa~=y71;WQg#_z9u zej;0sqbj=unv-^fw8{|44TW2Abgs2s*H-h2*Q_ua5$2zPjjDFFx+bO1f_`5(vSxlv z6PTJ3`HoJ?GEH7xv?&)7>k2#EM)kPv6|NQf)$MWrL<2`~BpL``MS8V-nXKHxvAH>M z-6UUr=6K`zwFN?Q{}-)Nz*+ltCmx&2lS{NxPqTtSF|)-Zzc_|7-v0Eh&`3Cqy7L?1 z4^miNYBmQ_CKEl1bAtgZN-K-)P$$X1I{6Mx=c9pRj!=k9{!JoJ5wpza1h2%7cx_Np z>HnfWV4?wI_lIa8HM|WCU|XSq4)x?8uF5SXfOhIG+?7>0AlDCLv%X0!~P#y z=NBCh(E#i_8d@B{vdJ$FR73G^MhV0)L<942V&$TAXrQ;u1PyrIgBTY7Wtc(?9yHKC zh{0EjoNNBm26{+G!eo7)PTFf zRuIp3cAz4kqsx>zfK|&fzZnEw{}ey-oN8MYT0*DJb_wsGaG$ zEYq{8TTEYoN@nxZ)u5SdtiWxjrkK6_CzBwCGfbg&imHR>TPx=z4izaVy^n?byd5HR zjb*FiO&l7~i&7?chA9s`Oa5AUxBiEiS+t>Ti?LNpAoNY%-MfOr6Uo6S__#uOg(#lx zD*^`IDT^xN$vHIOhaCI1I?dYLr`G~VIt1>>X2!iJ&Sptw zp|ex66}Xu~q*q#6{;3S~Y3<~`QuF1`%d{<0^`{tX*mfr&k#jk3s-m+(kugnP(=^TRZo!7UTxU&K7u!3sw_t&C#$T5-9p?ZZPTtk5Zh zYv<|;E-%K;W~;_g*VgE&%637~w%n#WPmbT-so2$qlpmXJv9~}3#~(<4v2eKyr+o&;CA^G6oxR!&3Rli{7iTmdRl3TPXAYid z(73dEITFHP`!%=ab@^h~P`%-jm>FXfh7{qd1VTmAZ=^{$Z#ERl9C@RGTjva@CP4%$ zQsx0QZi|~x-Q^>j7Muo=a`Kl`;*?Ql)(dabLsmduy>RMvxi_#0WRR?K4IN=> z#QZfp`doz8=*km&Ed$6t^JZCb^FrJ@s%tu_^D0xz>Uas)k&7xv+5_!k?{=FwCB$g= z=Z3zfOLC?9wwy*-oaTI3;iEbYUO;S>^*&%qkJb(sNrq0gK#HIsL^cmnXB(}HF zz`AnHUAFwaoiS}aJ>8b2E)wgXH}7+FUM5I#O@hxTm+6B&kaDdJhM8BQfutTZ;6Vwl zNTPw{I^Q!$Tl;;4o!~<1zV1|QG7rVKl23Rg+nGw+N880Xg!q)CNIV>DxGP7|c4%uq zTERyYK$-8oKAcobGRR@7H7x&0$3X!aDgoZTz+(OVPVBmzIlP~ zcJ_SjKD9YVmeIK%)`-Yczy^ zfJ^DGpNIYjF9q##Dqyb|N)Zi=Cc9Z9Ko#jd)K;OV`wy^oDmG)65q0_-Es3H}Ljx(` zSv(pj6FriEHEr8XY8_DCElI*246TDyB(q#@vP+X5C~Sii<7gMs2MDqR4q1m zv0uy~kYo~^`6g51Te?c!y^%=-kyK&(9?=_uIc5JkBKD;tZUj1qmba+;T{ovcZj%FHxJ7IznjXVf&T1O zufF|Yi@CXjvds9dx0#Y3QcvOAHxo#X2d$dmaH4o8y%1Z8!{mR}S;ndqmQT;G^=s7T%j1Kf^(g#cQtOHS!sNX$d>;&4jc=|a-%w;ne6yS01`L%pBvR&PB|qA1 z@U`0erS1psE)uH>TeS|V5&&(?nXgkvNauR+WoEx#l~)H0K=D}islNKtj1zvzBif+_ zBd+5p>?Yn+zmI*HAylw>0ElU7`ef`ZlkdvUvnEYl$i=p^8p1!%$z(EvjMq}2>`Y*4;V`WklS zupPnGJ5o6lI(~2FW>sIV@>;&Hp_``7F*72)S5qBuEkB^~p7OFQ#gZ~y`GwI~zC-_ZXA)ifZ!$j-2 zde_%c!-uJ9+}EK%PoD|YH=cxpLhwlbRLmJZBhq$I8^1JZ(SuR3yK&q6AvZT+Ut@ls zoJJRQCH~#-vvt_pM8cVIb4Fpk?!B2T-%4~@rBxCIt@b1P6Vk1SH7$OMjaBo_bc0G1 zGW>CJ@^RcX8v{yr!}ZIGtyc4qBdr8J6-V%y zVQhNbQ!(PIcj|^@ngVl4GVTe4gd2#|f3M8krQ|>rgarccj4wq?%E4eqh?OvEcvE!f ztq~JZ6v6GSagdA6mPo`5>t~gO&M+CXfRq3Y5$%s$Ql0J+od?}`OeF7{%)beRw2kKV zVn~rZ#zhbO$96csA?FX8(b#Kh=Nq?pw!9VWaL9Y8(2!0XC52_pFO7rOu#_zMZ(G2C zCyiqM{DWi63qJW2f8!Fp`bjW)z$h3cug6;y|nL6&Mg}p&ear#)=?uFlae05vsz=~J0CwgQoRog`-w0* z)7nyOYqs&#S@O*1i27!w3iva4_HD?3ngq9~W5x?cuo5#yT183DlV8)h3kfFG9n+s)kxzcOu=WAPZ}#E%_BwNB z%5kQfh$v5pvrhR-K}W7n=X*xaH=+d*_`{PsnsU$;Ztqaq`)jVMBYU*^v4-0bAf10B z6rjN5$Wl6>KcK^Nj0PyY@_lpJ_@?LXFAqGi9O5bzsVy5UbQhn6ThY(0RVbTezCgZI zz~Jk%i4|vH!<%+0lqh)8gp$#>M^%?jonPJf^1RUG{6&kz6V=y<9D&>`%(KY{@9wU| zEu@xtK7J&HhY%FKi}xdJ++)*q4?JPX!m|r|Qj+wOQ|f7209&_bH8jSvue^+Ad1jhy z?BO!PulmQ+n19U1DW<@QXTA}zJl9~WoZou6Y$r!8Yw!dw{UbA067KC9@_JZFvgf{s zl9*o-b4l#RQk+osy8Dv6sdG`WxgjoLb4~&*Vww!6j(wXv&|)S_hOJ)dI~dTZ^pm+m z5iYQr=KhsMU#KfxuIvPcu@zLblf;pymZ{i!XvKODMZ@Q+U!xJNuW>$APm5i#6&amu z@9!w|M)-%|8f;u`Stw~~t57m6;D|b&IrVK*+ZA6Oc==eB)yl%{eLLh(MeYmKo90=Q zCwl#Ra%SPWj9(Z_Wg(5R4-yh`l0Zk(qFgjElGug{vQb6fc= zkh1RFWgJ{C&#s2dGWsZgNGsi&I$<@#O8a}C8~kfsVD>aoawO;L9|iQ2%b!k@nG+Us zJ*^Rye&$8j#vjQp<87*Xii@MgK zE|+nlWYYhjQ?4%lvnlZZ)E40;=^1x#wM^~C;1$uc`QxI{$k0>EdNCC*#C_+`M%PX6 ztet%`F$X1LhvPJxwxWakeEYa(cSHzK?)%``4GY8>82WhJd9%sMwx(t{Ij4)ni!e0J z;!cL^HLKi3)_KP*(uJV*TsQo1rwA)o4cOt&33%O-SI}vBDKl@w3=ica9~0$7c9QZ0 zu!&=8B64m%^nJ~0{EZ=O+rDc$sKN= zoKz>#QLU9RQv5BuT10kZKT-fLV#r&2)FCZjqLUnCI!QU)*m)$FlgFIls&r_u8t8WX zVW9lyvWJ(HIMG4#j1DJ}rIxjKY2X6NRuhynnOT>f!&#X;=>^L&ER?3g$F84@NW)w8 zayX+29+jD%ck&TK$H^QP$gIE3-RLYIKd718p$NKWfGm~rTU@!Ey2B1=fXLj5vNe}3 zWb`{`i+UAtGh0sfLTt<%U`5e&J^7vcJuXM88uRmqf>%?Lb017~mhOvPa;F{M1Kome zX!n<`ezsP}{QAMdZ>3Wbo+|Iy1P40ZuEE=r16=aWnY=s4Z>&DaW3Um`c|D9u=seZ8 zuJsIk$D3B@*ZSd9g4k)Nx{`g>bh(G^NH`9+)k=D!W!?A`C%&j~y^#KKcerVnYNd$@ z($enoY2H=57LRAOc(uI|FwB*Hb1;lqp6b2znk_DVb~lrsE8e_1Y+MBXwf3eN;>znA zI#W_JU5c+~=Twa?1LJGpkHbx`V=^e@=dk5izra@MBDPbq9QuFO!EtpRmWF12XsdEu z&GUrhhvB7$w0!|JU&_avUYwJ&H?{2dgUihw3^QOCK8WR37gRhujdwbFjr>AO##Dj+s{2TmU(Ev+cy>LNrG%}Pb+TW|NpV!3wWBRuJQ7NTa$T#rnx5h%vgf=(r~#e>}>)vEnQ~M5i^B8o3Ck# z8=S*NdHu7+88KyzLWwBk9t&>X0_v{{;F4ghscXJfp0x=WwQ3dCv`<+Mjl4Q4xXyg{l&9Klz7pfH~P;Pl*uiui{U}JbNNEuvOYIoX2Oy;Uh&Pi&2z)t6NEoOEH1 zo1i(%1-%%^Ol)avD;cL>CeVJrMSFxN>=pN*Qwf6t7Z$3sl}I!l6vy7EkaTBoUe?gr zDD=);Sly`HYu7;7I595;UBmPn%V0Q*0MgimYI>h2Cps&2Hiv4;OQG70rJc^*WP zs^(jnW!`1gwtBrH&GXROGZ=pFk~c8KS!7jT0LTu#E}80W)X_`=x(345Ks=Y^{4}M&e6H35`WH_t z4)2JJ1@T^%8(cxuSz79wTy^jAI1}CA2sF`nRE)Ly?g=-|Rf$IrUM60SY};e-aEVcw z6;`7_>WC{4O)uZAtkaD#V}6RZzD&$QUKB$%8P9@8&6#3sopOaG&#II?wyk$j{P_=B z&|jV+sL_@(LV?A#aZGp1wd6W=65mu*io5(X@w=gm`gB37-@Dmgw0cU$;dmIbHq59` zu!xOaC$I`t?k*@I*L+<3C}I{*d%%y;K)3zLr|o8%m%hlp7ZP5t*@r%`er+2#{>*_L zjpIzMV3 z!~CG1O_2_^t+BGYVFXe+F-krRkFI{_rFATKlQ<@$DNGO}{pO=v-t{u#U|KcX-KG&_ zC(<<1r(>PiqZy_*WVZ@lFvpYz%qcqOW^+jmB!k@*WcxS2rj`fbJ|zlMw2Q!s zdu1LMU$4Po`E(>%)F_qUr_)iaPkG;N#hW%Mc&*nt!I{LH zO!NM3X4OfIR2z&Vbmrz(*NTco@89DSbS~bxWj4yhAiPLxBw*Ym`@lBrJ7}@4bdX$z zi%z9kXQ(8^KRXq>?zPM?tL8hVEF9syIAi0}Mypdb_R3lXK~pi38U#O{pUY{=Pi!BP<7s-%Y6h+5%95u+w=lq<@IH%1E)t(jTsPnF)iU!H2jWru`1YGrc=j*WI5K3e zSR&s)%(ba29nI+u)b~LPA(TI0L(hllm)tW28th(Y2+!2r8I*=j#rf;m)s~@a9h))QMv~|0p z*~hW=Gm%t8Ys68VNM~?Ud23{B)y-|_ar+1ORTgCer9@k%d3z17SU8;gXDs$*qnl}FulPc_ggP54OwF#SI;C`xCpHs);2^U@n7ayc}NGYWFC1q*= zK-$1y7wA8eqWwmv&?2#rw~=5hCFV6cWP>gvl-mmbqR#L9RscZvy8`qZ8T~JhZYbjw zDfRE z_J#lFKw`>WfHb4YsQ>K={!h@7!Uf1c0|%KOeeGhRFjX!l>KuWO?1*K<#8bT>?c;7E z72~gBUR6VG8!`RzhDA2V(p|7f)gcxqGAN!JDHeAXlUn1V!q+%TWKA?BCjAqno;JtD zod4}8d-u;5daH@7q-Q`%NBs2&Y>10argbh~JObBKs6`HBRSX;EJ;RH}31WFMg`XkC zW3@0Pj4n8Jv6PU<4<)9oF%oS;hGb2+M*Z9G2LQ diff --git a/doc/mf6io/gwf/sfr.tex b/doc/mf6io/gwf/sfr.tex index b1163f680e0..2cad3ae2a97 100644 --- a/doc/mf6io/gwf/sfr.tex +++ b/doc/mf6io/gwf/sfr.tex @@ -94,6 +94,16 @@ \subsection{Streamflow Routing Package Cross-Section Table Input File} \label{se \label{fig:sfr-n-point} \end{figure} +Where irregular cross sections are used to define cross-sectional stream geometries, the wetted perimeter used in Manning’s equation [Equation 7-7, \cite{modflow6gwf}] depends on the number of points defining the cross section and the simulated stage. Using only the minimum number of points (i.e., 2-point cross section), \mf does not include perimeter lengths above the uppermost defined points in the wetted perimeter calculations. For example, the 2-point cross sections shown in fig.~\ref{fig:sfr-n-point-wp}A-C depict the cross-sectional areas (light blue) and wetted perimeters (orange) calculated by the SFR package and used in \cite{modflow6gwf} (Equation 7-7). In applications where the intent is for the wetted perimeter to include the entire lengths of wetted sides, additional points above the maximum anticipated stage should be defined (fig.~\ref{fig:sfr-n-point-wp}D). Note that when the simulated stream stage rises above the points representing the top of the channel, the additional cross-sectional flow area above the defined points will be accounted for but the corresponding wetted perimeter will not extend above the defined points (fig.~\ref{fig:sfr-n-point-wp}E,F). + + +\begin{figure}[ht] + \centering + \includegraphics[scale=1.0]{../Figures/n-point-cross-section-wetted-perimeter} + \caption[Illustrations of variously defined n-point cross-sections that show how wetted perimeter will vary depending on the stage and the number of points used to define the cross-section]{Example irregular cross-section geometries showing the corresponding wetted perimeter based on the number of points that define a cross-section and the simulated stage. (A-C) Wetted perimeters (orange lines) for variously configured 2-point cross-sections. (D-F) Wetted perimeters for variously configurated 4-point cross-sections} + \label{fig:sfr-n-point-wp} +\end{figure} + Cross-Section tables are specified by including file names in the CROSSSECTIONS or PERIOD blocks of the SFR Package for specific reaches. These file names correspond to a Streamflow Routing cross-section table input file. The format of the Streamflow Routing cross-section table input file is described here. \vspace{5mm} diff --git a/src/Model/ModelUtilities/SfrCrossSectionUtils.f90 b/src/Model/ModelUtilities/SfrCrossSectionUtils.f90 index d4bd43250ca..1048c83cfea 100644 --- a/src/Model/ModelUtilities/SfrCrossSectionUtils.f90 +++ b/src/Model/ModelUtilities/SfrCrossSectionUtils.f90 @@ -382,7 +382,11 @@ subroutine get_cross_section_areas(npts, stations, heights, d, a) ! ! -- add the area below dmax if (dmax /= dmin .and. d > dmin) then - a(n) = a(n) + DHALF * (d - dmin) + if (d < dmax) then + a(n) = a(n) + DHALF * (d - dmin) * xlen + else + a(n) = a(n) + DHALF * (dmax - dmin) * xlen + end if end if end if end do From a068ba6e385ec04e2039404cf2aa7c58884b5c36 Mon Sep 17 00:00:00 2001 From: langevin-usgs Date: Mon, 15 May 2023 19:11:13 -0500 Subject: [PATCH 080/123] fix(gwtssmspc): readarrays did not work for transient models (#1219) * fix(gwtssmspc): readarrays did not work for transient models * Updated test to work for transient conditions * Corrected bug in SPC spc_rp_array routine * updated release notes * Close #1218 * fprettify --- autotest/test_gwt_ssm04.py | 48 +++++++++++++------ doc/ReleaseNotes/v6.5.0.tex | 2 +- src/Model/ModelUtilities/GwtSpc.f90 | 71 +++++++++++++++-------------- 3 files changed, 70 insertions(+), 51 deletions(-) diff --git a/autotest/test_gwt_ssm04.py b/autotest/test_gwt_ssm04.py index af29c01539c..7dee055bb8c 100644 --- a/autotest/test_gwt_ssm04.py +++ b/autotest/test_gwt_ssm04.py @@ -34,9 +34,9 @@ def build_model(idx, dir): - perlen = [5.0] - nstp = [5] - tsmult = [1.0] + perlen = [5.0, 5.0, 5.0] + nstp = [5, 5, 5] + tsmult = [1.0, 1.0, 1.0] nper = len(perlen) delr = 1.0 delc = 1.0 @@ -127,9 +127,12 @@ def build_model(idx, dir): # list based recharge idxrow, idxcol = np.where(idomain[0] == 1) recharge_rate = np.arange(nrow * ncol).reshape((nrow, ncol)) - spd = [] - for i, j in zip(idxrow, idxcol): - spd.append([(0, i, j), recharge_rate[i, j]]) + spd = {} + for kper in range(nper): + rlist = [] + for i, j in zip(idxrow, idxcol): + rlist.append([(0, i, j), recharge_rate[i, j]]) + spd[kper] = rlist rch1 = flopy.mf6.modflow.ModflowGwfrch( gwf, print_flows=True, @@ -140,10 +143,13 @@ def build_model(idx, dir): ) # array-based rch files + rspd = {} + for kper in range(nper): + rspd[kper] = recharge_rate rch2 = flopy.mf6.ModflowGwfrcha( gwf, print_flows=True, - recharge=recharge_rate, + recharge=rspd, pname="RCH-2", filename=f"{gwfname}.rch2", ) @@ -160,7 +166,10 @@ def build_model(idx, dir): for j in range(ncol): tsnames.append(f"rch-{i + 1}-{j + 1}") ts_data = [] - for t in [0, perlen[0]]: + totim = 0. + for kper in range(nper): + totim += perlen[kper] + for t in [0, totim]: ts = tuple([float(t)] + list(range(0, nrow * ncol))) ts_data.append(ts) ts_dict = { @@ -193,7 +202,9 @@ def build_model(idx, dir): # is a bug in flopy that will not correctly write this array as internal tas_array = { 0.0: f"{gwfname}.rch4.tas.dat", - perlen[0]: f"{gwfname}.rch4.tas.dat", + 5.0: f"{gwfname}.rch4.tas.dat", + 10.0: f"{gwfname}.rch4.tas.dat", + 15.0: f"{gwfname}.rch4.tas.dat", } time_series_namerecord = "rcharray" interpolation_methodrecord = "linear" @@ -315,7 +326,7 @@ def build_model(idx, dir): for j in range(ncol): tsnames.append(f"rch-{i + 1}-{j + 1}") ts_data = [tuple([0.0] + nrow * ncol * [0.0])] - for t in perlen: + for t in [5., 10., 15.]: ts = tuple([float(t)] + list(range(0, nrow * ncol))) ts_data.append(ts) ts_dict = { @@ -344,7 +355,12 @@ def build_model(idx, dir): filename = f"{gwtname}.rch4.spc.tas" # for now write the recharge concentration to a dat file because there # is a bug in flopy that will not correctly write this array as internal - tas_array = {0.0: 0.0, perlen[0]: f"{gwtname}.rch4.spc.tas.dat"} + tas_array = { + 0.0: 0.0, + 5.0: f"{gwtname}.rch4.spc.tas.dat", + 10.0: f"{gwtname}.rch4.spc.tas.dat", + 15.0: f"{gwtname}.rch4.spc.tas.dat", + } time_series_namerecord = "carray" interpolation_methodrecord = "linear" spc4.tas.initialize( @@ -421,7 +437,7 @@ def eval_transport(sim): times = cobj.get_times() print(times) - for itime, totim in enumerate(times): + for itime, totim in enumerate(times[:5]): print(f"Checking records for time {totim}") # Check records for each of the four recharge packages @@ -468,16 +484,18 @@ def eval_transport(sim): id2a = np.arange(23) + 1 elif irchpak in [1, 3]: id2a = id1a - assert np.allclose(id2, id2a), f"{id2} /= {id2a}" + assert np.allclose(id2, id2a), f"q: {id2} /= {id2a}" - print(" Checking q") + print(f" Checking q for irchpak {irchpak + 1}") q = ssmbud[istart:istop]["q"] + print("q", q) if irchpak in [2, 3]: frac = (totim - 0.5) / 5.0 qa = [float(a - 1) * frac * (a - 1) for a in id1a] else: qa = [float(a - 1) ** 2 for a in id1a] - assert np.allclose(q, qa), f"{q} /=\n {qa}" + print("qa", qa) + assert np.allclose(q, qa), f"q: {q} /=\n {qa}" istart = istop diff --git a/doc/ReleaseNotes/v6.5.0.tex b/doc/ReleaseNotes/v6.5.0.tex index c3908439ff8..ad9a459e025 100644 --- a/doc/ReleaseNotes/v6.5.0.tex +++ b/doc/ReleaseNotes/v6.5.0.tex @@ -27,7 +27,7 @@ \item In some cases, unrecognized keywords and invalid auxiliary input did not terminate with a useful error message. The program was corrected to provide error handling for these cases. \item Based on the LAK package input-output instructions (mf6io.pdf), the variable ``connlen must be greater than zero for a HORIZONTAL, EMBEDDEDH, or EMBEDDEDV lake-GWF connection.'' However, a value of zero could be specified and the model would run with no LAK-groundwater exchange. A minor fix was made to enforce connlen to be strictly greater than zero per the input instructions. The error message thrown when connlen is specified as zero was augmented with additional information for assisting the user. \item An SFR channel defined with the n-point cross-section option was calculating the wetted cross-sectional area incorrectly. The cross sectional area for the area of a triangle was being calculated as one-half multiplied by the depth of the channel, as opposed to one-half multiplied by the base width multiplied by the height. As a result, the units in the mannings equation were not correct owing to the missing dimension in the area calculation. The change in the area calculation will slightly alter the solution found using Manning's equation since the cross-sectional area term appears in it. As a result, existing models may reflect slightly different answers in groundwater\/surface-water exchange amounts owing to slight differences in the calculated stream stage. In addition to the fix, some clarifying text, including a new figure, was added to mf6io.pdf. - % \item xxx + \item The SSM Package for the GWT Model did not work properly with Stress Package Concentration (SPC) input with the READARRAY option for transient models. Under these conditions, the program would prematurely terminate looking for the next BEGIN PERIOD block. The program was corrected so that SPC input can be read for transient conditions. \end{itemize} \underline{INTERNAL FLOW PACKAGES} diff --git a/src/Model/ModelUtilities/GwtSpc.f90 b/src/Model/ModelUtilities/GwtSpc.f90 index d28ac59ad3e..e2d1f1ed026 100644 --- a/src/Model/ModelUtilities/GwtSpc.f90 +++ b/src/Model/ModelUtilities/GwtSpc.f90 @@ -541,43 +541,44 @@ subroutine spc_rp_array(this, line) end do ! ! -- Read CONCENTRATION variables as arrays - call this%parser%GetNextLine(endOfBlock) - if (endOfBlock) then - call store_error('LOOKING FOR CONCENTRATION. FOUND: '//trim(line)) - call this%parser%StoreErrorUnit() - end if - call this%parser%GetStringCaps(keyword) - ! - ! -- Parse the keywords - select case (keyword) - case ('CONCENTRATION') - ! - ! -- Look for keyword TIMEARRAYSERIES and time-array series - ! name on line, following RECHARGE + do + call this%parser%GetNextLine(endOfBlock) + if (endOfBlock) exit call this%parser%GetStringCaps(keyword) - if (keyword == 'TIMEARRAYSERIES') then - ! -- Get time-array series name - call this%parser%GetStringCaps(tasName) - bndArrayPtr => this%dblvec(:) - ! Make a time-array-series link and add it to the list of links - ! contained in the TimeArraySeriesManagerType object. - convertflux = .false. - call this%TasManager%MakeTasLink(this%packName, bndArrayPtr, & - this%iprpak, tasName, 'CONCENTRATION', & - convertFlux, nodelist, & - this%parser%iuactive) - else - ! - ! -- Read the concentration array - call this%dis%read_layer_array(nodelist, this%dblvec, ncolbnd, & - this%maxbound, 1, aname(1), & - this%parser%iuactive, this%iout) - end if ! - case default - call store_error('LOOKING FOR CONCENTRATION. FOUND: '//trim(line)) - call this%parser%StoreErrorUnit() - end select + ! -- Parse the keywords + select case (keyword) + case ('CONCENTRATION') + ! + ! -- Look for keyword TIMEARRAYSERIES and time-array series + ! name on line, following RECHARGE + call this%parser%GetStringCaps(keyword) + if (keyword == 'TIMEARRAYSERIES') then + ! -- Get time-array series name + call this%parser%GetStringCaps(tasName) + bndArrayPtr => this%dblvec(:) + ! Make a time-array-series link and add it to the list of links + ! contained in the TimeArraySeriesManagerType object. + convertflux = .false. + call this%TasManager%MakeTasLink(this%packName, bndArrayPtr, & + this%iprpak, tasName, & + 'CONCENTRATION', & + convertFlux, nodelist, & + this%parser%iuactive) + else + ! + ! -- Read the concentration array + call this%dis%read_layer_array(nodelist, this%dblvec, ncolbnd, & + this%maxbound, 1, aname(1), & + this%parser%iuactive, this%iout) + end if + ! + case default + call store_error('LOOKING FOR CONCENTRATION. FOUND: '//trim(line)) + call this%parser%StoreErrorUnit() + end select + + end do ! return end subroutine spc_rp_array From edb4f705d2ffcd5f9b9e1824bb69deaa37c76693 Mon Sep 17 00:00:00 2001 From: langevin-usgs Date: Wed, 17 May 2023 06:24:13 -0500 Subject: [PATCH 081/123] fix(gwtssmspc): another fix for readasarrays with SPC (#1220) * improve testing * fix for list mismatch when flow/transport are in separate simulations --- autotest/test_gwt_ssm04.py | 61 ++- autotest/test_gwt_ssm04fmi.py | 556 ++++++++++++++++++++ src/Model/GroundWaterTransport/gwt1ssm1.f90 | 10 +- src/Model/ModelUtilities/GwtSpc.f90 | 35 +- 4 files changed, 629 insertions(+), 33 deletions(-) create mode 100644 autotest/test_gwt_ssm04fmi.py diff --git a/autotest/test_gwt_ssm04.py b/autotest/test_gwt_ssm04.py index 7dee055bb8c..df3b02ebdda 100644 --- a/autotest/test_gwt_ssm04.py +++ b/autotest/test_gwt_ssm04.py @@ -124,9 +124,9 @@ def build_model(idx, dir): pname="CHD-1", ) - # list based recharge + # list based recharge, recharge is equal to one-based user node number idxrow, idxcol = np.where(idomain[0] == 1) - recharge_rate = np.arange(nrow * ncol).reshape((nrow, ncol)) + recharge_rate = np.arange(nrow * ncol).reshape((nrow, ncol)) + 1 spd = {} for kper in range(nper): rlist = [] @@ -158,19 +158,21 @@ def build_model(idx, dir): idxrow, idxcol = np.where(idomain[0] == 1) spd = [] for i, j in zip(idxrow, idxcol): - tsname = f"rch-{i + 1}-{j + 1}" + nodeu = i * ncol + j + tsname = f"rch-{nodeu + 1}" spd.append([(0, i, j), tsname]) tsnames = [] for i in range(nrow): for j in range(ncol): - tsnames.append(f"rch-{i + 1}-{j + 1}") + nodeu = i * nrow + j + tsnames.append(f"rch-{nodeu + 1}") ts_data = [] totim = 0. for kper in range(nper): totim += perlen[kper] for t in [0, totim]: - ts = tuple([float(t)] + list(range(0, nrow * ncol))) + ts = tuple([float(t)] + list(range(1, nrow * ncol + 1))) ts_data.append(ts) ts_dict = { "timeseries": ts_data, @@ -291,7 +293,7 @@ def build_model(idx, dir): # spc package for RCH-1 idxrow, idxcol = np.where(idomain[0] == 1) - recharge_concentration = np.arange(nrow * ncol).reshape((nrow, ncol)) + recharge_concentration = np.arange(nrow * ncol).reshape((nrow, ncol)) + 1 pd = [] for ipos, (i, j) in enumerate(zip(idxrow, idxcol)): pd.append([ipos, "CONCENTRATION", recharge_concentration[i, j]]) @@ -304,13 +306,13 @@ def build_model(idx, dir): # spc package for RCH-2 idxrow, idxcol = np.where(idomain[0] == 1) - recharge_concentration = np.arange(nrow * ncol).reshape((nrow, ncol)) - pd = [] - for ipos, (i, j) in enumerate(zip(idxrow, idxcol)): - pd.append([ipos, "CONCENTRATION", recharge_concentration[i, j]]) + recharge_concentration = np.arange(nrow * ncol).reshape((nrow, ncol)) + 1 + crchspd = {} + for kper in range(nper): + crchspd[kper] = recharge_concentration spc2 = flopy.mf6.ModflowUtlspca( gwt, - concentration=recharge_concentration, + concentration=crchspd, filename=f"{gwtname}.rch2.spc", ) @@ -318,16 +320,18 @@ def build_model(idx, dir): idxrow, idxcol = np.where(idomain[0] == 1) pd = [] for ipos, (i, j) in enumerate(zip(idxrow, idxcol)): - tsname = f"rch-{i + 1}-{j + 1}" + nodeu = i * ncol + j + tsname = f"crch-{nodeu + 1}" pd.append([ipos, "CONCENTRATION", tsname]) tsnames = [] for i in range(nrow): for j in range(ncol): - tsnames.append(f"rch-{i + 1}-{j + 1}") - ts_data = [tuple([0.0] + nrow * ncol * [0.0])] + nodeu = i * ncol + j + tsnames.append(f"crch-{nodeu + 1}") + ts_data = [tuple([0.0] + list(range(1, nrow * ncol + 1)))] for t in [5., 10., 15.]: - ts = tuple([float(t)] + list(range(0, nrow * ncol))) + ts = tuple([float(t)] + list(range(1, nrow * ncol + 1))) ts_data.append(ts) ts_dict = { "timeseries": ts_data, @@ -356,7 +360,7 @@ def build_model(idx, dir): # for now write the recharge concentration to a dat file because there # is a bug in flopy that will not correctly write this array as internal tas_array = { - 0.0: 0.0, + 0.0: f"{gwtname}.rch4.spc.tas.dat", 5.0: f"{gwtname}.rch4.spc.tas.dat", 10.0: f"{gwtname}.rch4.spc.tas.dat", 15.0: f"{gwtname}.rch4.spc.tas.dat", @@ -369,7 +373,7 @@ def build_model(idx, dir): time_series_namerecord=time_series_namerecord, interpolation_methodrecord=interpolation_methodrecord, ) - recharge_concentration = np.arange(nrow * ncol).reshape((nrow, ncol)) + recharge_concentration = np.arange(nrow * ncol).reshape((nrow, ncol)) + 1 np.savetxt( os.path.join(ws, f"{gwtname}.rch4.spc.tas.dat"), recharge_concentration, @@ -437,14 +441,14 @@ def eval_transport(sim): times = cobj.get_times() print(times) - for itime, totim in enumerate(times[:5]): + for itime, totim in enumerate(times): print(f"Checking records for time {totim}") # Check records for each of the four recharge packages ssmbud = ssmbudall[itime] istart = 0 - for irchpak in [0, 1, 2, 3]: - print(f" Checking records for recharge package {irchpak + 1}") + for irchpak in [1, 2, 3, 4]: + print(f" Checking records for recharge package {irchpak}") istop = istart + 23 print(ssmbud[istart:istop]) @@ -480,21 +484,22 @@ def eval_transport(sim): print(" Checking id2") id2 = ssmbud[istart:istop]["node2"] - if irchpak in [0, 2]: + if irchpak in [1, 3]: + # recharge packages 1 and 3 are list-based with 23 entries id2a = np.arange(23) + 1 - elif irchpak in [1, 3]: + elif irchpak in [2, 4]: + # recharge packages 2 and 4 are array-based with 25 entries id2a = id1a assert np.allclose(id2, id2a), f"q: {id2} /= {id2a}" - print(f" Checking q for irchpak {irchpak + 1}") + print(f" Checking q for irchpak {irchpak}") q = ssmbud[istart:istop]["q"] - print("q", q) if irchpak in [2, 3]: - frac = (totim - 0.5) / 5.0 - qa = [float(a - 1) * frac * (a - 1) for a in id1a] + qa = [float(a) ** 2 for a in id1] else: - qa = [float(a - 1) ** 2 for a in id1a] - print("qa", qa) + qa = [float(a) ** 2 for a in id1a] + for i in range(23): + print(f"{i + 1} {id1[i]} {id2[i]} {q[i]} {qa[i]}") assert np.allclose(q, qa), f"q: {q} /=\n {qa}" istart = istop diff --git a/autotest/test_gwt_ssm04fmi.py b/autotest/test_gwt_ssm04fmi.py new file mode 100644 index 00000000000..5950d5b3a64 --- /dev/null +++ b/autotest/test_gwt_ssm04fmi.py @@ -0,0 +1,556 @@ +""" +MODFLOW 6 Autotest +Test the SSM FILEINPUT option for specifying source and sink +concentrations. + +Four different recharge packages are tested with the SSM FILEINPUT +1. list-based recharge no time series +2. array-based recharge, no time array series +3. list-based recharge with time series +4. array-based recharge with time array series + +""" + +import os +from os.path import join + +import flopy +import numpy as np +import pytest +from framework import TestFramework +from simulation import TestSimulation + +testgroup = "ssm04fmi" + +nlay, nrow, ncol = 3, 5, 5 +idomain_lay0 = [ + [1, 1, 1, 1, 1], + [1, 1, 1, 1, 1], + [1, 1, 0, 1, 1], + [1, 1, 0, 1, 1], + [1, 1, 1, 1, 1], +] +idomain = np.ones((nlay, nrow, ncol), dtype=int) +idomain[0, :, :] = np.array(idomain_lay0) + +perlen = [5.0, 5.0, 5.0] +nstp = [5, 5, 5] +tsmult = [1.0, 1.0, 1.0] +nper = len(perlen) +delr = 1.0 +delc = 1.0 +top = 4.0 +botm = [3.0, 2.0, 1.0] + +nouter, ninner = 100, 300 +hclose, rclose, relax = 1e-6, 1e-6, 1.0 + +recharge_package_1 = False +recharge_package_2 = True +recharge_package_3 = False +recharge_package_4 = False + +def run_flow_model(dir, exe): + + name = "flow" + gwfname = name + wsf = join(dir, testgroup, name) + sim = flopy.mf6.MFSimulation(sim_name=name, sim_ws=wsf, exe_name=exe) + + strt = 4.0 + hk = 1.0 + laytyp = 0 + + tdis_rc = [] + for i in range(nper): + tdis_rc.append((perlen[i], nstp[i], tsmult[i])) + + # create tdis package + tdis = flopy.mf6.ModflowTdis( + sim, time_units="DAYS", nper=nper, perioddata=tdis_rc + ) + + # create gwf model + gwf = flopy.mf6.ModflowGwf( + sim, + modelname=gwfname, + save_flows=True, + ) + + # create iterative model solution and register the gwf model with it + imsgwf = flopy.mf6.ModflowIms( + sim, + print_option="SUMMARY", + outer_dvclose=hclose, + outer_maximum=nouter, + under_relaxation="NONE", + inner_maximum=ninner, + inner_dvclose=hclose, + rcloserecord=rclose, + linear_acceleration="CG", + scaling_method="NONE", + reordering_method="NONE", + relaxation_factor=relax, + filename=f"{gwfname}.ims", + ) + sim.register_ims_package(imsgwf, [gwf.name]) + + dis = flopy.mf6.ModflowGwfdis( + gwf, + nlay=nlay, + nrow=nrow, + ncol=ncol, + delr=delr, + delc=delc, + top=top, + botm=botm, + idomain=idomain, + ) + + # initial conditions + ic = flopy.mf6.ModflowGwfic(gwf, strt=strt) + + # node property flow + npf = flopy.mf6.ModflowGwfnpf( + gwf, + icelltype=laytyp, + k=hk, + save_specific_discharge=True, + save_saturation=True, + ) + + # chd files + spd = [[(nlay - 1, nrow - 1, ncol - 1), 4.0]] + chd = flopy.mf6.modflow.ModflowGwfchd( + gwf, + print_flows=True, + maxbound=len(spd), + stress_period_data=spd, + pname="CHD-1", + ) + + # list based recharge, recharge is equal to one-based user node number + idxrow, idxcol = np.where(idomain[0] == 1) + recharge_rate = np.arange(nrow * ncol).reshape((nrow, ncol)) + 1 + spd = {} + for kper in range(nper): + rlist = [] + for i, j in zip(idxrow, idxcol): + rlist.append([(0, i, j), recharge_rate[i, j]]) + spd[kper] = rlist + if recharge_package_1: + rch1 = flopy.mf6.modflow.ModflowGwfrch( + gwf, + print_flows=True, + maxbound=len(spd), + stress_period_data=spd, + pname="RCH-1", + filename=f"{gwfname}.rch1", + ) + + # array-based rch files + rspd = {} + for kper in range(nper): + rspd[kper] = recharge_rate + if recharge_package_2: + rch2 = flopy.mf6.ModflowGwfrcha( + gwf, + print_flows=True, + recharge=rspd, + pname="RCH-2", + filename=f"{gwfname}.rch2", + ) + + # list-based recharge with time series + idxrow, idxcol = np.where(idomain[0] == 1) + spd = [] + for i, j in zip(idxrow, idxcol): + nodeu = i * ncol + j + tsname = f"rch-{nodeu + 1}" + spd.append([(0, i, j), tsname]) + + tsnames = [] + for i in range(nrow): + for j in range(ncol): + nodeu = i * nrow + j + tsnames.append(f"rch-{nodeu + 1}") + ts_data = [] + totim = 0. + for kper in range(nper): + totim += perlen[kper] + for t in [0, totim]: + ts = tuple([float(t)] + list(range(1, nrow * ncol + 1))) + ts_data.append(ts) + ts_dict = { + "timeseries": ts_data, + "time_series_namerecord": tsnames, + "interpolation_methodrecord": [nrow * ncol * ("linear",)], + "filename": f"{gwfname}.rch3.ts", + } + + if recharge_package_3: + rch3 = flopy.mf6.modflow.ModflowGwfrch( + gwf, + print_flows=True, + maxbound=len(spd), + stress_period_data=spd, + pname="RCH-3", + filename=f"{gwfname}.rch3", + timeseries=ts_dict, + ) + + # array-based rch files + if recharge_package_4: + rch4 = flopy.mf6.ModflowGwfrcha( + gwf, + print_flows=True, + recharge="TIMEARRAYSERIES rcharray", + pname="RCH-4", + filename=f"{gwfname}.rch4", + ) + filename = f"{gwfname}.rch4.tas" + # for now write the recharge concentration to a dat file because there + # is a bug in flopy that will not correctly write this array as internal + tas_array = { + 0.0: f"{gwfname}.rch4.tas.dat", + 5.0: f"{gwfname}.rch4.tas.dat", + 10.0: f"{gwfname}.rch4.tas.dat", + 15.0: f"{gwfname}.rch4.tas.dat", + } + time_series_namerecord = "rcharray" + interpolation_methodrecord = "linear" + rch4.tas.initialize( + filename=filename, + tas_array=tas_array, + time_series_namerecord=time_series_namerecord, + interpolation_methodrecord=interpolation_methodrecord, + ) + np.savetxt( + os.path.join(wsf, f"{gwfname}.rch4.tas.dat"), recharge_rate, fmt="%7.1f" + ) + + # output control + oc = flopy.mf6.ModflowGwfoc( + gwf, + budget_filerecord=f"{gwfname}.bud", + head_filerecord=f"{gwfname}.hds", + headprintrecord=[("COLUMNS", 10, "WIDTH", 15, "DIGITS", 6, "GENERAL")], + saverecord=[("HEAD", "ALL"), ("BUDGET", "ALL")], + printrecord=[("HEAD", "LAST"), ("BUDGET", "LAST")], + ) + + sim.write_simulation() + success, buff = sim.run_simulation(silent=False) + errmsg = f"flow model did not terminate successfully\n{buff}" + assert success, errmsg + + +def run_transport_model(dir, exe): + + name = "transport" + gwtname = name + wst = join(dir, testgroup, name) + sim = flopy.mf6.MFSimulation( + sim_name=name, + version="mf6", + exe_name=exe, + sim_ws=wst, + continue_=False, + ) + + tdis_rc = [] + for i in range(nper): + tdis_rc.append((perlen[i], nstp[i], tsmult[i])) + + # create tdis package + tdis = flopy.mf6.ModflowTdis( + sim, time_units="DAYS", nper=nper, perioddata=tdis_rc + ) + + # create gwt model + gwt = flopy.mf6.ModflowGwt(sim, modelname=gwtname) + gwt.name_file.save_flows = True + + # create iterative model solution and register the gwt model with it + imsgwt = flopy.mf6.ModflowIms( + sim, + print_option="SUMMARY", + outer_dvclose=hclose, + outer_maximum=nouter, + under_relaxation="NONE", + inner_maximum=ninner, + inner_dvclose=hclose, + rcloserecord=rclose, + linear_acceleration="BICGSTAB", + scaling_method="NONE", + reordering_method="NONE", + relaxation_factor=relax, + filename=f"{gwtname}.ims", + ) + sim.register_ims_package(imsgwt, [gwt.name]) + + dis = flopy.mf6.ModflowGwtdis( + gwt, + nlay=nlay, + nrow=nrow, + ncol=ncol, + delr=delr, + delc=delc, + top=top, + botm=botm, + idomain=idomain, + ) + + # initial conditions + ic = flopy.mf6.ModflowGwtic(gwt, strt=0.0) + + # advection + adv = flopy.mf6.ModflowGwtadv(gwt) + + # mass storage and transfer + mst = flopy.mf6.ModflowGwtmst(gwt, porosity=0.1) + + # ssm package + sourcerecarray = [()] + fileinput = [] + if recharge_package_1: + fileinput.append(("RCH-1", f"{gwtname}.rch1.spc")) + if recharge_package_2: + fileinput.append(("RCH-2", f"{gwtname}.rch2.spc")) + if recharge_package_3: + fileinput.append(("RCH-3", f"{gwtname}.rch3.spc")) + if recharge_package_4: + fileinput.append(("RCH-4", f"{gwtname}.rch4.spc")) + + ssm = flopy.mf6.ModflowGwtssm( + gwt, print_flows=True, sources=sourcerecarray, fileinput=fileinput + ) + + # spc package for RCH-1 + idxrow, idxcol = np.where(idomain[0] == 1) + recharge_concentration = np.arange(nrow * ncol).reshape((nrow, ncol)) + 1 + pd = [] + for ipos, (i, j) in enumerate(zip(idxrow, idxcol)): + pd.append([ipos, "CONCENTRATION", recharge_concentration[i, j]]) + + if recharge_package_1: + spc1 = flopy.mf6.ModflowUtlspc( + gwt, + perioddata=pd, + maxbound=len(pd), + filename=f"{gwtname}.rch1.spc", + ) + + # spc package for RCH-2 + idxrow, idxcol = np.where(idomain[0] == 1) + recharge_concentration = np.arange(nrow * ncol).reshape((nrow, ncol)) + 1 + crchspd = {} + for kper in range(nper): + crchspd[kper] = recharge_concentration + if recharge_package_2: + spc2 = flopy.mf6.ModflowUtlspca( + gwt, + concentration=crchspd, + filename=f"{gwtname}.rch2.spc", + ) + + # spc package for RCH-3 + idxrow, idxcol = np.where(idomain[0] == 1) + pd = [] + for ipos, (i, j) in enumerate(zip(idxrow, idxcol)): + nodeu = i * ncol + j + tsname = f"crch-{nodeu + 1}" + pd.append([ipos, "CONCENTRATION", tsname]) + + tsnames = [] + for i in range(nrow): + for j in range(ncol): + nodeu = i * ncol + j + tsnames.append(f"crch-{nodeu + 1}") + ts_data = [tuple([0.0] + list(range(1, nrow * ncol + 1)))] + for t in [5., 10., 15.]: + ts = tuple([float(t)] + list(range(1, nrow * ncol + 1))) + ts_data.append(ts) + ts_dict = { + "timeseries": ts_data, + "time_series_namerecord": tsnames, + "interpolation_methodrecord": [nrow * ncol * ("linear",)], + "sfacrecord": [nrow * ncol * (1.0,)], + "filename": f"{gwtname}.rch3.spc.ts", + } + if recharge_package_3: + spc3 = flopy.mf6.ModflowUtlspc( + gwt, + perioddata=pd, + maxbound=len(pd), + filename=f"{gwtname}.rch3.spc", + timeseries=ts_dict, + print_input=True, + ) + + # spc package for RCH-4 + if recharge_package_4: + spc4 = flopy.mf6.ModflowUtlspca( + gwt, + concentration="TIMEARRAYSERIES carray", + filename=f"{gwtname}.rch4.spc", + print_input=True, + ) + filename = f"{gwtname}.rch4.spc.tas" + # for now write the recharge concentration to a dat file because there + # is a bug in flopy that will not correctly write this array as internal + tas_array = { + 0.0: f"{gwtname}.rch4.spc.tas.dat", + 5.0: f"{gwtname}.rch4.spc.tas.dat", + 10.0: f"{gwtname}.rch4.spc.tas.dat", + 15.0: f"{gwtname}.rch4.spc.tas.dat", + } + time_series_namerecord = "carray" + interpolation_methodrecord = "linear" + spc4.tas.initialize( + filename=filename, + tas_array=tas_array, + time_series_namerecord=time_series_namerecord, + interpolation_methodrecord=interpolation_methodrecord, + ) + recharge_concentration = np.arange(nrow * ncol).reshape((nrow, ncol)) + 1 + np.savetxt( + os.path.join(wst, f"{gwtname}.rch4.spc.tas.dat"), + recharge_concentration, + fmt="%7.1f", + ) + + # output control + oc = flopy.mf6.ModflowGwtoc( + gwt, + budget_filerecord=f"{gwtname}.cbc", + concentration_filerecord=f"{gwtname}.ucn", + concentrationprintrecord=[ + ("COLUMNS", 10, "WIDTH", 15, "DIGITS", 6, "GENERAL") + ], + saverecord=[("CONCENTRATION", "ALL"), ("BUDGET", "ALL")], + printrecord=[("CONCENTRATION", "LAST"), ("BUDGET", "LAST")], + ) + + obs_data = { + f"{gwtname}.obs.csv": [ + ("(1-1-1)", "CONCENTRATION", (0, 0, 0)), + ("(1-5-5)", "CONCENTRATION", (nlay - 1, nrow - 1, ncol - 1)), + ], + } + + obs_package = flopy.mf6.ModflowUtlobs( + gwt, + pname=f"{gwtname}.obs", + digits=10, + print_input=True, + continuous=obs_data, + ) + + pd = [ + ("GWFHEAD", "../flow/flow.hds", None), + ("GWFBUDGET", "../flow/flow.bud", None), + ] + fmi = flopy.mf6.ModflowGwtfmi( + gwt, packagedata=pd, flow_imbalance_correction=True + ) + + sim.write_simulation() + success, buff = sim.run_simulation(silent=False) + errmsg = f"transport model did not terminate successfully\n{buff}" + assert success, errmsg + + # check results + eval_transport(wst) + return + +def eval_transport(wst): + print("evaluating transport...") + gwtname = "transport" + + # load concentration file + fpth = os.path.join(wst, f"{gwtname}.ucn") + cobj = flopy.utils.HeadFile(fpth, precision="double", text="CONCENTRATION") + conc = cobj.get_data() + + # load transport budget file + fpth = os.path.join(wst, f"{gwtname}.cbc") + bobj = flopy.utils.CellBudgetFile( + fpth, + precision="double", + ) + + ssmbudall = bobj.get_data(text="SOURCE-SINK MIX") + times = cobj.get_times() + + print(times) + for itime, totim in enumerate(times): + print(f"Checking records for time {totim}") + + # Check records for each of the four recharge packages + ssmbud = ssmbudall[itime] + istart = 0 + for irchpak in [2]: # [1, 2, 3, 4]: + print(f" Checking records for recharge package {irchpak}") + istop = istart + 23 + + print(ssmbud[istart:istop]) + + print(" Checking id1") + id1 = ssmbud[istart:istop]["node"] + id1a = [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 14, + 15, + 16, + 17, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + ] + assert np.allclose(id1, id1a), f"id1 {id1} /= {id1a}" + + print(" Checking id2") + id2 = ssmbud[istart:istop]["node2"] + if irchpak in [1, 3]: + # recharge packages 1 and 3 are list-based with 23 entries + id2a = np.arange(23) + 1 + elif irchpak in [2, 4]: + # recharge packages 2 and 4 are array-based, but unlike + # with flow and transport in the same simulation the number + # of entries is only 23, instead of 25. + id2a = np.arange(23) + 1 + assert np.allclose(id2, id2a), f"id2: {id2} /= {id2a}" + + print(f" Checking q for irchpak {irchpak}") + q = ssmbud[istart:istop]["q"] + if irchpak in [2, 3]: + qa = [float(a) ** 2 for a in id1] + else: + qa = [float(a) ** 2 for a in id1a] + for i in range(23): + print(f"{i + 1} {id1[i]} {id2[i]} {q[i]} {qa[i]}") + assert np.allclose(q, qa), f"q: {q} /=\n {qa}" + + istart = istop + + +def test_ssm04fmi(function_tmpdir, targets): + mf6 = targets.mf6 + run_flow_model(str(function_tmpdir), mf6) + run_transport_model(str(function_tmpdir), mf6) diff --git a/src/Model/GroundWaterTransport/gwt1ssm1.f90 b/src/Model/GroundWaterTransport/gwt1ssm1.f90 index 0238f826bb7..d671a7cbc6a 100644 --- a/src/Model/GroundWaterTransport/gwt1ssm1.f90 +++ b/src/Model/GroundWaterTransport/gwt1ssm1.f90 @@ -283,6 +283,7 @@ subroutine ssm_term(this, ipackage, ientry, rrate, rhsval, hcofval, & ! -- local logical(LGP) :: lauxmixed integer(I4B) :: n + integer(I4B) :: nbound_flow real(DP) :: qbnd real(DP) :: ctmp real(DP) :: omega @@ -294,6 +295,7 @@ subroutine ssm_term(this, ipackage, ientry, rrate, rhsval, hcofval, & rhstmp = DZERO ctmp = DZERO qbnd = DZERO + nbound_flow = this%fmi%gwfpackages(ipackage)%nbound n = this%fmi%gwfpackages(ipackage)%nodelist(ientry) ! ! -- If cell is active (ibound > 0) then calculate values @@ -301,7 +303,7 @@ subroutine ssm_term(this, ipackage, ientry, rrate, rhsval, hcofval, & ! ! -- retrieve qbnd and iauxpos qbnd = this%fmi%gwfpackages(ipackage)%get_flow(ientry) - call this%get_ssm_conc(ipackage, ientry, ctmp, lauxmixed) + call this%get_ssm_conc(ipackage, ientry, nbound_flow, ctmp, lauxmixed) ! ! -- assign values for hcoftmp, rhstmp, and ctmp for subsequent assigment ! of hcof, rhs, and rate @@ -368,11 +370,13 @@ end subroutine ssm_term !! The mixed flag indicates whether or not !! !< - subroutine get_ssm_conc(this, ipackage, ientry, conc, lauxmixed) + subroutine get_ssm_conc(this, ipackage, ientry, nbound_flow, conc, & + lauxmixed) ! -- dummy class(GwtSsmType) :: this !< GwtSsmType integer(I4B), intent(in) :: ipackage !< package number integer(I4B), intent(in) :: ientry !< bound number + integer(I4B), intent(in) :: nbound_flow !< size of flow package bound list real(DP), intent(out) :: conc !< user-specified concentration for this bound logical(LGP), intent(out) :: lauxmixed !< user-specified flag for marking this as a mixed boundary ! -- local @@ -389,7 +393,7 @@ subroutine get_ssm_conc(this, ipackage, ientry, conc, lauxmixed) conc = this%fmi%gwfpackages(ipackage)%auxvar(iauxpos, ientry) if (isrctype == 2) lauxmixed = .true. case (3, 4) - conc = this%ssmivec(ipackage)%get_value(ientry) + conc = this%ssmivec(ipackage)%get_value(ientry, nbound_flow) if (isrctype == 4) lauxmixed = .true. end select diff --git a/src/Model/ModelUtilities/GwtSpc.f90 b/src/Model/ModelUtilities/GwtSpc.f90 index e2d1f1ed026..8c2656caaf2 100644 --- a/src/Model/ModelUtilities/GwtSpc.f90 +++ b/src/Model/ModelUtilities/GwtSpc.f90 @@ -343,11 +343,42 @@ end subroutine allocate_arrays !! Get the floating point value from the dblvec array. !! !< - function get_value(this, ientry) result(value) + function get_value(this, ientry, nbound_flow) result(value) class(GwtSpcType) :: this !< GwtSpcType object integer(I4B), intent(in) :: ientry !< index of the data to return + integer(I4B), intent(in) :: nbound_flow !< size of bound list in flow package real(DP) :: value - value = this%dblvec(ientry) + integer(I4B) :: nu + if (this%readasarrays) then + ! Special handling for reduced grids and readasarrays + ! if flow and transport are in the same simulation, then + ! ientry is a user node number and it corresponds to the + ! correct position in the dblvec array. But if flow and + ! transport are not in the same simulation, then ientry is + ! a reduced node number, because the list of flows in the + ! budget file do not include idomain < 1 entries. In this + ! case, ientry must be converted to a user node number so + ! that it corresponds to a user array, which includes + ! idomain < 1 values. + if (nbound_flow == this%maxbound) then + ! flow and transport are in the same simulation or there + ! are no idomain < 1 cells. + value = this%dblvec(ientry) + else + ! This identifies case where flow and transport must be + ! in a separate simulation, because nbound_flow is not + ! the same as this%maxbound. Under these conditions, we + ! must assume that ientry corresponds to a flow list that + ! would be of size ncpl if flow and transport were in the + ! same simulation, but because boundary cells with + ! idomain < 1 are not written to binary budget file, the + ! list size is smaller. + nu = this%dis%get_nodeuser(ientry) + value = this%dblvec(nu) + end if + else + value = this%dblvec(ientry) + end if return end function get_value From f8315197080575c24fa88f5cde9f423b463a13dd Mon Sep 17 00:00:00 2001 From: langevin-usgs Date: Thu, 18 May 2023 07:34:47 -0500 Subject: [PATCH 082/123] fix(gwfuzf): fix array assignment to prevent stack overflow (#1221) * implicit array assignment can result in stack overflow for large uzf packages * change array assignment to explicit loop --- src/Model/GroundWaterFlow/gwf3uzf8.f90 | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Model/GroundWaterFlow/gwf3uzf8.f90 b/src/Model/GroundWaterFlow/gwf3uzf8.f90 index 5417d68d33e..51204499414 100644 --- a/src/Model/GroundWaterFlow/gwf3uzf8.f90 +++ b/src/Model/GroundWaterFlow/gwf3uzf8.f90 @@ -1764,7 +1764,9 @@ subroutine uzf_solve(this, reset_state) ! ! -- Initialize ierr = 0 - this%uzfobj%pet = this%uzfobj%petmax + do i = 1, this%nodes + this%uzfobj%pet(i) = this%uzfobj%petmax(i) + end do ! ! -- Calculate hcof and rhs for each UZF entry do i = 1, this%nodes From 1049dffd715b096ebd8e76ec3cee6f946098bd91 Mon Sep 17 00:00:00 2001 From: w-bonelli Date: Wed, 24 May 2023 07:48:29 -0400 Subject: [PATCH 083/123] ci: switch provision-with-micromamba to setup-micromamba (#1224) --- .github/workflows/ci.yml | 48 +++++++++++++++++++++-------------- .github/workflows/docs.yml | 9 ++++--- .github/workflows/large.yml | 13 ++++------ .github/workflows/release.yml | 24 +++++++++--------- 4 files changed, 51 insertions(+), 43 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 88f0420b902..b3410fde337 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,10 +28,11 @@ jobs: uses: actions/checkout@v3 - name: Setup Micromamba - uses: mamba-org/provision-with-micromamba@main + uses: mamba-org/setup-micromamba@v1 with: + environment-file: environment.yml + cache-environment: true cache-downloads: true - cache-env: true - name: Check Fortran source formatting run: python .github/common/fortran_format_check.py @@ -57,10 +58,11 @@ jobs: version: ${{ env.GCC_V }} - name: Setup Micromamba - uses: mamba-org/provision-with-micromamba@main + uses: mamba-org/setup-micromamba@v1 with: + environment-file: environment.yml + cache-environment: true cache-downloads: true - cache-env: true - name: Meson setup run: meson setup builddir -Ddebug=false -Dwerror=true @@ -93,11 +95,11 @@ jobs: version: ${{ env.GCC_V }} - name: Setup Micromamba - uses: mamba-org/provision-with-micromamba@main + uses: mamba-org/setup-micromamba@v1 with: environment-file: modflow6/environment.yml + cache-environment: true cache-downloads: true - cache-env: true - name: Build modflow6 working-directory: modflow6 @@ -161,11 +163,14 @@ jobs: version: ${{ env.GCC_V }} - name: Setup Micromamba - uses: mamba-org/provision-with-micromamba@main + uses: mamba-org/setup-micromamba@v1 with: environment-file: modflow6/environment.yml + init-shell: >- + bash + powershell + cache-environment: true cache-downloads: true - cache-env: true - name: Build modflow6 working-directory: modflow6 @@ -238,11 +243,11 @@ jobs: version: ${{ matrix.GCC_V }} - name: Setup Micromamba - uses: mamba-org/provision-with-micromamba@main + uses: mamba-org/setup-micromamba@v1 with: environment-file: modflow6/environment.yml cache-downloads: true - cache-env: true + cache-environment: true - name: Update flopy working-directory: modflow6/autotest @@ -300,11 +305,14 @@ jobs: path: modflow6-testmodels - name: Setup Micromamba - uses: mamba-org/provision-with-micromamba@main + uses: mamba-org/setup-micromamba@v1 with: environment-file: modflow6/environment.yml + init-shell: >- + bash + powershell + cache-environment: true cache-downloads: true - cache-env: true - name: Setup Intel Fortran uses: modflowpy/install-intelfortran-action@v1 @@ -504,11 +512,14 @@ jobs: make all - name: Setup Micromamba - uses: mamba-org/provision-with-micromamba@main + uses: mamba-org/setup-micromamba@v1 with: environment-file: modflow6/environment.yml + init-shell: >- + bash + powershell + cache-environment: true cache-downloads: true - cache-env: true - name: Build modflow6 working-directory: modflow6 @@ -537,9 +548,8 @@ jobs: env: REPOS_PATH: ${{ github.workspace }} run: | - if [ "${{ github.ref_name }}" == "master" ]; then - pytest -v -n auto --parallel --durations 0 -m "not large and not developmode" - else - pytest -v -n auto --parallel --durations 0 -m "not large" - fi + branch="${{ github.ref_name }}" + marker="not large" + markers=$([ "$branch" == "master" ] && echo "$marker and not developmode" || echo "$marker") + pytest -v -n auto --parallel --durations 0 -m "$markers" diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 2b8be6d97e0..afe452ec6dd 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -42,11 +42,11 @@ jobs: path: usgslatex - name: Install Conda environment from environment.yml - uses: mamba-org/provision-with-micromamba@main + uses: mamba-org/setup-micromamba@v1 with: environment-file: modflow6/environment.yml + cache-environment: true cache-downloads: true - cache-env: true - name: Install additional packages for Sphinx using pip working-directory: modflow6/.build_rtd_docs @@ -166,10 +166,11 @@ jobs: sudo apt-get install doxygen graphviz - name: Install Conda environment from environment.yml - uses: mamba-org/provision-with-micromamba@main + uses: mamba-org/setup-micromamba@v1 with: + environment-file: environment.yml + cache-environment: true cache-downloads: true - cache-env: true - name: Print python package versions run: pip list diff --git a/.github/workflows/large.yml b/.github/workflows/large.yml index b18cff74882..291dcc16673 100644 --- a/.github/workflows/large.yml +++ b/.github/workflows/large.yml @@ -40,11 +40,11 @@ jobs: path: modflow6-${{ matrix.repo }} - name: Setup Micromamba - uses: mamba-org/provision-with-micromamba@main + uses: mamba-org/setup-micromamba@v1 with: environment-file: modflow6/environment.yml cache-downloads: true - cache-env: true + cache-environment: true - name: Setup gfortran ${{ env.GCC_V }} if: matrix.FC == 'gfortran' @@ -95,15 +95,12 @@ jobs: working-directory: modflow6/autotest env: GITHUB_TOKEN: ${{ github.token }} - run: | - pytest -v --durations 0 get_exes.py + run: pytest -v --durations 0 get_exes.py - name: Update flopy working-directory: modflow6/autotest - run: | - python update_flopy.py + run: python update_flopy.py - name: Run tests working-directory: modflow6/autotest - run: | - pytest -v -n auto --durations 0 test_z03_${{ matrix.repo }}.py \ No newline at end of file + run: pytest -v -n auto --durations 0 test_z03_${{ matrix.repo }}.py \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 66e6fc98310..731bcf48a32 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -43,11 +43,11 @@ jobs: - name: Setup Micromamba if: ${{ steps.cache-bin.outputs.cache-hit != 'true' }} - uses: mamba-org/provision-with-micromamba@main + uses: mamba-org/setup-micromamba@v1 with: environment-file: modflow6/environment.yml cache-downloads: true - cache-env: true + cache-environment: true - name: Setup Intel Fortran if: ${{ steps.cache-bin.outputs.cache-hit != 'true' }} @@ -187,11 +187,11 @@ jobs: run: sudo ./install.sh --all-users - name: Setup Micromamba - uses: mamba-org/provision-with-micromamba@main + uses: mamba-org/setup-micromamba@v1 with: environment-file: modflow6/environment.yml cache-downloads: true - cache-env: true + cache-environment: true - name: Setup Intel Fortran uses: modflowpy/install-intelfortran-action@v1 @@ -299,11 +299,11 @@ jobs: path: modflow6-examples - name: Setup Micromamba - uses: mamba-org/provision-with-micromamba@main + uses: mamba-org/setup-micromamba@v1 with: environment-file: modflow6/environment.yml cache-downloads: true - cache-env: true + cache-environment: true - name: Setup Intel Fortran uses: modflowpy/install-intelfortran-action@v1 @@ -443,10 +443,10 @@ jobs: uses: actions/checkout@v3 - name: Setup Micromamba - uses: mamba-org/provision-with-micromamba@main + uses: mamba-org/setup-micromamba@v1 with: cache-downloads: true - cache-env: true + cache-environment: true - name: Update version working-directory: distribution @@ -505,11 +505,11 @@ jobs: path: modflow6 - name: Setup Micromamba - uses: mamba-org/provision-with-micromamba@main + uses: mamba-org/setup-micromamba@v1 with: environment-file: modflow6/environment.yml cache-downloads: true - cache-env: true + cache-environment: true - name: Download artifacts uses: dawidd6/action-download-artifact@v2 @@ -549,11 +549,11 @@ jobs: path: modflow6 - name: Setup Micromamba - uses: mamba-org/provision-with-micromamba@main + uses: mamba-org/setup-micromamba@v1 with: environment-file: modflow6/environment.yml cache-downloads: true - cache-env: true + cache-environment: true - name: Get release tag uses: oprypin/find-latest-tag@v1 From 4fb0d4fe5ddbdf5120fec0dd096be9733ac0a0be Mon Sep 17 00:00:00 2001 From: Hofer-Julian <30049909+Hofer-Julian@users.noreply.github.com> Date: Thu, 25 May 2023 05:04:18 +0200 Subject: [PATCH 084/123] ci: use dependabot to update github actions (#1222) --- .github/dependabot.yml | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000000..82618bb3f92 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,7 @@ +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" # Location of package manifests + schedule: + interval: "daily" From c4c26161ed9b29c618dfe73a56437c48e6417bf9 Mon Sep 17 00:00:00 2001 From: mjreno Date: Thu, 25 May 2023 10:09:41 -0400 Subject: [PATCH 085/123] fix(idm): address compiler issue impacting ifort up to 19.1.1.217 (#1225) * workaround for compiler issues with empty derived type list syntax * fix IdmLogger edit descriptor compiler warning --------- Co-authored-by: mjreno --- src/Model/GroundWaterFlow/gwf3dis8idm.f90 | 15 +++++++- src/Model/GroundWaterFlow/gwf3npf8idm.f90 | 15 +++++++- .../GroundWaterTransport/gwt1dis1idm.f90 | 15 +++++++- src/Model/GroundWaterTransport/gwt1dspidm.f90 | 15 +++++++- src/Utilities/Idm/IdmLogger.f90 | 2 +- utils/idmloader/scripts/dfn2f90.py | 38 +++++++++++++++++-- 6 files changed, 92 insertions(+), 8 deletions(-) diff --git a/src/Model/GroundWaterFlow/gwf3dis8idm.f90 b/src/Model/GroundWaterFlow/gwf3dis8idm.f90 index ff33cf24725..0c8d8cd90b1 100644 --- a/src/Model/GroundWaterFlow/gwf3dis8idm.f90 +++ b/src/Model/GroundWaterFlow/gwf3dis8idm.f90 @@ -256,7 +256,20 @@ module GwfDisInputModule type(InputParamDefinitionType), parameter :: & gwf_dis_aggregate_definitions(*) = & [ & - InputParamDefinitionType :: & + InputParamDefinitionType & + ( & + '', & ! component + '', & ! subcomponent + '', & ! block + '', & ! tag name + '', & ! fortran variable + '', & ! type + '', & ! shape + .false., & ! required + .false., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) & ] type(InputBlockDefinitionType), parameter :: & diff --git a/src/Model/GroundWaterFlow/gwf3npf8idm.f90 b/src/Model/GroundWaterFlow/gwf3npf8idm.f90 index 87d2825bed2..79fe5bee186 100644 --- a/src/Model/GroundWaterFlow/gwf3npf8idm.f90 +++ b/src/Model/GroundWaterFlow/gwf3npf8idm.f90 @@ -688,7 +688,20 @@ module GwfNpfInputModule type(InputParamDefinitionType), parameter :: & gwf_npf_aggregate_definitions(*) = & [ & - InputParamDefinitionType :: & + InputParamDefinitionType & + ( & + '', & ! component + '', & ! subcomponent + '', & ! block + '', & ! tag name + '', & ! fortran variable + '', & ! type + '', & ! shape + .false., & ! required + .false., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) & ] type(InputBlockDefinitionType), parameter :: & diff --git a/src/Model/GroundWaterTransport/gwt1dis1idm.f90 b/src/Model/GroundWaterTransport/gwt1dis1idm.f90 index b80694384aa..dda32a6b3a1 100644 --- a/src/Model/GroundWaterTransport/gwt1dis1idm.f90 +++ b/src/Model/GroundWaterTransport/gwt1dis1idm.f90 @@ -256,7 +256,20 @@ module GwtDisInputModule type(InputParamDefinitionType), parameter :: & gwt_dis_aggregate_definitions(*) = & [ & - InputParamDefinitionType :: & + InputParamDefinitionType & + ( & + '', & ! component + '', & ! subcomponent + '', & ! block + '', & ! tag name + '', & ! fortran variable + '', & ! type + '', & ! shape + .false., & ! required + .false., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) & ] type(InputBlockDefinitionType), parameter :: & diff --git a/src/Model/GroundWaterTransport/gwt1dspidm.f90 b/src/Model/GroundWaterTransport/gwt1dspidm.f90 index 63b8cfeeb46..0f9e3c29e1d 100644 --- a/src/Model/GroundWaterTransport/gwt1dspidm.f90 +++ b/src/Model/GroundWaterTransport/gwt1dspidm.f90 @@ -166,7 +166,20 @@ module GwtDspInputModule type(InputParamDefinitionType), parameter :: & gwt_dsp_aggregate_definitions(*) = & [ & - InputParamDefinitionType :: & + InputParamDefinitionType & + ( & + '', & ! component + '', & ! subcomponent + '', & ! block + '', & ! tag name + '', & ! fortran variable + '', & ! type + '', & ! shape + .false., & ! required + .false., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) & ] type(InputBlockDefinitionType), parameter :: & diff --git a/src/Utilities/Idm/IdmLogger.f90 b/src/Utilities/Idm/IdmLogger.f90 index 80820652bf8..43f5ce53ee4 100644 --- a/src/Utilities/Idm/IdmLogger.f90 +++ b/src/Utilities/Idm/IdmLogger.f90 @@ -55,7 +55,7 @@ subroutine idm_log_var_logical(p_mem, varname, mempath, iout) character(len=*), intent(in) :: mempath !< variable memory path integer(I4B) :: iout - write (iout, '(3x,a, " = ", l)') trim(varname), p_mem + write (iout, '(3x,a, " = ", l1)') trim(varname), p_mem end subroutine idm_log_var_logical !> @brief Log type specific information integer diff --git a/utils/idmloader/scripts/dfn2f90.py b/utils/idmloader/scripts/dfn2f90.py index f96ffe0f52d..1921a3cc5d0 100644 --- a/utils/idmloader/scripts/dfn2f90.py +++ b/utils/idmloader/scripts/dfn2f90.py @@ -244,13 +244,45 @@ def _set_param_strs(self): self._set_blk_param_strs(b, self.component, self.subcomponent) if not self._param_str: - self._param_str += " InputParamDefinitionType ::, &" + self._param_str += " InputParamDefinitionType &\n" + self._param_str += " ( &\n" + self._param_str += " '', & ! component\n" + self._param_str += " '', & ! subcomponent\n" + self._param_str += " '', & ! block\n" + self._param_str += " '', & ! tag name\n" + self._param_str += " '', & ! fortran variable\n" + self._param_str += " '', & ! type\n" + self._param_str += " '', & ! shape\n" + self._param_str += " .false., & ! required\n" + self._param_str += " .false., & ! multi-record\n" + self._param_str += " .false., & ! preserve case\n" + self._param_str += " .false. & ! layered\n" + self._param_str += " ), &\n" if not self._aggregate_str: - self._aggregate_str += " InputParamDefinitionType ::, &" + self._aggregate_str += " InputParamDefinitionType &\n" + self._aggregate_str += " ( &\n" + self._aggregate_str += " '', & ! component\n" + self._aggregate_str += " '', & ! subcomponent\n" + self._aggregate_str += " '', & ! block\n" + self._aggregate_str += " '', & ! tag name\n" + self._aggregate_str += " '', & ! fortran variable\n" + self._aggregate_str += " '', & ! type\n" + self._aggregate_str += " '', & ! shape\n" + self._aggregate_str += " .false., & ! required\n" + self._aggregate_str += " .false., & ! multi-record\n" + self._aggregate_str += " .false., & ! preserve case\n" + self._aggregate_str += " .false. & ! layered\n" + self._aggregate_str += " ), &\n" if not self._block_str: - self._aggregate_str += " InputBlockDefinitionType ::, &" + self._block_str += " InputBlockDefinitionType &\n" + self._block_str += " ( &\n" + self._block_str += " '', & ! blockname\n" + self._block_str += " .false., & ! required\n" + self._block_str += " .false., & ! aggregate\n" + self._block_str += " .false. & ! block_varaible\n" + self._block_str += " ), &\n" def _set_blk_param_strs(self, blockname, component, subcomponent): print(" processing block params => ", blockname) From 2d8303005814a66af50687d4aa1d0eb08d88020b Mon Sep 17 00:00:00 2001 From: jdhughes-usgs Date: Fri, 26 May 2023 08:59:44 -0500 Subject: [PATCH 086/123] =?UTF-8?q?refactor(obs):=20refactor=20observation?= =?UTF-8?q?s=20to=20flush=20after=20writing=20formatted=E2=80=A6=20(#1226)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Refactor required for compiling on Denali. Added doxygen comments to all observation source files. Refactored some observation subroutines to clarify what they actually do. Update ReleaseNotes to include description of change to observation functionality. --- autotest/test_gwf_wel01.py | 52 +- doc/ReleaseNotes/v6.5.0.tex | 1 + src/Utilities/Observation/Obs3.f90 | 759 ++++++++++---------- src/Utilities/Observation/ObsContainer.f90 | 13 +- src/Utilities/Observation/ObsOutput.f90 | 144 ++-- src/Utilities/Observation/ObsOutputList.f90 | 151 ++-- src/Utilities/Observation/ObsUtility.f90 | 117 ++- src/Utilities/Observation/Observe.f90 | 245 ++++--- 8 files changed, 743 insertions(+), 739 deletions(-) diff --git a/autotest/test_gwf_wel01.py b/autotest/test_gwf_wel01.py index 719504765b5..6217184b4de 100644 --- a/autotest/test_gwf_wel01.py +++ b/autotest/test_gwf_wel01.py @@ -32,7 +32,6 @@ def build_model(idx, ws): - name = ex[idx] # build MODFLOW 6 files @@ -114,7 +113,11 @@ def build_model(idx, ws): "wel.obs.csv": [ ["q", "wel", (0, 0, 0)], ["qred", "wel-reduction", (0, 0, 0)], - ] + ], + "wel.obs.dup.csv": [ + ["qred", "wel-reduction", (0, 0, 0)], + ["q", "wel", (0, 0, 0)], + ], } wel_spd = {0: [[0, 0, 0, -wellq]]} wel = flopy.mf6.ModflowGwfwel( @@ -126,7 +129,6 @@ def build_model(idx, ws): afrcsv_filerecord=f"{name}.afr.csv", ) welobs = wel.obs.initialize( - digits=25, print_input=True, continuous=obs, ) @@ -144,27 +146,31 @@ def eval_obs(sim): print("evaluating well observations...") # MODFLOW 6 observations - fpth = os.path.join(sim.simpath, "wel.obs.csv") - try: - tc = np.genfromtxt(fpth, names=True, delimiter=",") - except: - assert False, f'could not load data from "{fpth}"' - - qtot = tc["Q"] + tc["QRED"] - - # calculate maximum absolute error - diff = qtot + wellq - diffmax = np.abs(diff).max() dtol = 1e-9 - msg = f"maximum absolute well rates ({diffmax}) " - - if diffmax > dtol: - sim.success = False - msg += f"exceeds {dtol}" - assert diffmax < dtol, msg - else: - sim.success = True - print(" " + msg) + for file_name in ( + "wel.obs.csv", + "wel.obs.dup.csv", + ): + fpth = os.path.join(sim.simpath, file_name) + try: + tc = np.genfromtxt(fpth, names=True, delimiter=",") + except: + assert False, f'could not load data from "{fpth}"' + + qtot = tc["Q"] + tc["QRED"] + + # calculate maximum absolute error + diff = qtot + wellq + diffmax = np.abs(diff).max() + msg = f"maximum absolute well rates ({diffmax}) " + + if diffmax > dtol: + sim.success = False + msg += f"exceeds {dtol}" + assert diffmax < dtol, msg + else: + sim.success = True + print(" " + msg) # MODFLOW 6 AFR CSV output file fpth = os.path.join(sim.simpath, "wel01.afr.csv") diff --git a/doc/ReleaseNotes/v6.5.0.tex b/doc/ReleaseNotes/v6.5.0.tex index ad9a459e025..6135c3aa169 100644 --- a/doc/ReleaseNotes/v6.5.0.tex +++ b/doc/ReleaseNotes/v6.5.0.tex @@ -28,6 +28,7 @@ \item Based on the LAK package input-output instructions (mf6io.pdf), the variable ``connlen must be greater than zero for a HORIZONTAL, EMBEDDEDH, or EMBEDDEDV lake-GWF connection.'' However, a value of zero could be specified and the model would run with no LAK-groundwater exchange. A minor fix was made to enforce connlen to be strictly greater than zero per the input instructions. The error message thrown when connlen is specified as zero was augmented with additional information for assisting the user. \item An SFR channel defined with the n-point cross-section option was calculating the wetted cross-sectional area incorrectly. The cross sectional area for the area of a triangle was being calculated as one-half multiplied by the depth of the channel, as opposed to one-half multiplied by the base width multiplied by the height. As a result, the units in the mannings equation were not correct owing to the missing dimension in the area calculation. The change in the area calculation will slightly alter the solution found using Manning's equation since the cross-sectional area term appears in it. As a result, existing models may reflect slightly different answers in groundwater\/surface-water exchange amounts owing to slight differences in the calculated stream stage. In addition to the fix, some clarifying text, including a new figure, was added to mf6io.pdf. \item The SSM Package for the GWT Model did not work properly with Stress Package Concentration (SPC) input with the READARRAY option for transient models. Under these conditions, the program would prematurely terminate looking for the next BEGIN PERIOD block. The program was corrected so that SPC input can be read for transient conditions. + \item For some Linux systems, observations were not being correctly written to formatted observation output files when the source code was compiled with the Intel IFORT 19.1.0.166 20191121 compiler. This issue has been addressed by adding a flush statement to ObsUtilityModule::write\_unfmtd\_obs after writing each observation for a time step. This change will not affect simulated observations and should not affect simulation run times. \end{itemize} \underline{INTERNAL FLOW PACKAGES} diff --git a/src/Utilities/Observation/Obs3.f90 b/src/Utilities/Observation/Obs3.f90 index 54c4cc8e4a2..b1d1867ad38 100644 --- a/src/Utilities/Observation/Obs3.f90 +++ b/src/Utilities/Observation/Obs3.f90 @@ -1,129 +1,129 @@ -! This module defines type ObsType, which is the highest-level -! derived type for implementing observations. All objects derived from -! NumericalModelType or BndType already contain an ObsType member. -! -! Examples: -! NumericalModelType.obs -! BndType.obs -! -! Similarly, an ObsType member could be added to, say, -! NumericalExchangeType or any other type that has DF, AR, RP, AD, BD, and OT -! routines. -! -! ------------------------------------------------------------------------------ -! IMPLEMENTATION OF OBSERVATIONS IN A MODEL OR PACKAGE -! -! For simple boundary packages like RIV and DRN, only steps 1-6 are -! needed. For models and advanced packages like MAW and SFR, additional -! steps are needed. -! -! 1. (package only) Override BndType.bnd_obs_supported to return true. -! bnd_obs_supported is called from various places in code. -! -! 2. (optional) Write a subroutine that implements abstract interface -! ObserveModule.ProcessIdSub. (Not needed if IDstring, which identifies -! location in model to be observed, is either a single node number or -! a single {lay, row, col} set of indices). -! -! Examples: -! gwf_process_head_drawdown_obs_id, gwf_process_intercell_obs_id -! -! A package can allow IDstring to be a boundary name. -! Example: ObsModule.DefaultObsIdProcessor -! -! 3. Override BndType.bnd_df_obs() to define string(s) to be -! recognized as observation type(s) and (optional) assign ProcessIdPtr -! (not needed if IDstring is either a node number or a {lay, row, col} -! set of indices). -! -! Examples: gwf_df_obs, drn_df_obs -! -! When boundary names are allowed and developer wants simulated value -! to be cumulative (flow, for example) if user specifies multiple -! boundaries with the same BOUNDNAME, in bnd_df_obs call to -! ObsPackage.StoreObsType, provide cumulative argument as true. -! Otherwise, simulated values are not cumulative. -! -! 4. In DF routine: Call bnd_df_obs -! -! 5. In AR routine: Call ObsType.obs_ar. This reads the OBS input -! file. -! Example (gwf_ar): call this%obs%obs_ar() -! Example (lak_ar): call this%obs%obs_ar() -! -! 6. Override BndType.bnd_rp_obs for any package that needs to -! check user input or process observation input in any special way. -! If no special processing is needed, BndType.bnd_rp_obs can -! be used. This routine also expands the ObserveType%indxbnds array for -! each observation in a package. ObserveType%indxbnds is used to sum -! simulated values from multiple boundaries when BOUNDNAMES is used. -! Equivalent routine may or may not be needed for model observations. -! If needed, call it from bottom of RP routine. -! -! Examples: -! BndType.bnd_rp_obs, which is called from gwf_rp -! -! 7. In AD routine: Call ObsType.obs_ad -! Example: gwf_ad -! -! 8. Write a *_bd_obs routine. This is the routine that actually -! calculates the simulated value for each observation type supported -! by the model/package. Call *_bd_obs from the bottom of the -! _bd routine. -! *_bd_obs needs to: -! Call ObsType.obs_bd_clear -! For each observation: -! Calculate the simulated value -! Call ObsType.SaveOneSimval -! Examples: gwf_bd_obs, maw_bd_obs, lak_bd_obs -! -! 9. In BD routine: -! Call BndType.bnd_bd_obs -! Examples: BndType.bnd_bd calls bnd_bd_obs -! GwfModelType.gwf_bd calls gwf_bd_obs -! MawType.maw_bd calls maw_bd_obs -! LakType.lak_bd calls lak_bd_obs -! -! 10. Ensure that ObsType.obs_ot is called. For packages, obs_ot is called -! from the model _ot procedure. The model _ot procedure should also call -! obs_ot for its own observations. Do not call obs_ot from a package _ot -! procedure because the package _ot procedure may not be called, depending -! on Output Control settings (ibudfl). -! -! Note: BndType.bnd_ot_obs calls: -! ObsType.obs_ot -! -! Note: ObsType.obs_ot calls: -! store_all_simvals -! write_continuous_simvals -! obsOutputList.WriteOutputLines -! -! BINARY OUTPUT: -! -! When observation-output files are written, the user has the option to have -! output written to a binary file. Binary obs output files start with a -! 100-byte header structured as follows: -! -! bytes 1-4 (ascii): Observation type contained in file; options are: -! "sngl" -- Single observations -! "cont" -- Continuous observations -! byte 5: blank -! bytes 6-11 (ascii): Precision of all floating-point values; options are: -! "single" -- Single precision -! "double" -- Double precision -! bytes 12-15 (ascii): LENOBSNAME (integer; length of observation names, -! in bytes) -! bytes 16-100: blank -! -! IN A FILE OF CONTINUOUS OBSERVATIONS: -! -! The 100-byte header is followed by: -! NOBS (4-byte integer) -- Number of observations. -! NOBS repetitions of OBSNAME (ascii, LENOBSNAME bytes each). -! Any number of repetitions of: -! TIME SIMVAL-1 SIMVAL-2 ... SIMVAL-NOBS (floating point) -! -!------------------------------------------------------------------------------- +!> @brief This module contains the derived type ObsType +!! +!! This module defines type ObsType, which is the highest-level +!! derived type for implementing observations. All objects derived from +!! NumericalModelType or BndType already contain an ObsType member. +!! +!! Examples: +!! NumericalModelType.obs +!! BndType.obs +!! +!! Similarly, an ObsType member could be added to, say, +!! NumericalExchangeType or any other type that has DF, AR, RP, AD, BD, and OT +!! routines. +!! +!! IMPLEMENTATION OF OBSERVATIONS IN A MODEL OR PACKAGE +!! +!! For simple boundary packages like RIV and DRN, only steps 1-6 are +!! needed. For models and advanced packages like MAW and SFR, additional +!! steps are needed. +!! +!! 1. (package only) Override BndType.bnd_obs_supported to return true. +!! bnd_obs_supported is called from various places in code. +!! +!! 2. (optional) Write a subroutine that implements abstract interface +!! ObserveModule.ProcessIdSub. (Not needed if IDstring, which identifies +!! location in model to be observed, is either a single node number or +!! a single {lay, row, col} set of indices). +!! +!! Examples: +!! gwf_process_head_drawdown_obs_id, gwf_process_intercell_obs_id +!! +!! A package can allow IDstring to be a boundary name. +!! Example: ObsModule.DefaultObsIdProcessor +!! +!! 3. Override BndType.bnd_df_obs() to define string(s) to be +!! recognized as observation type(s) and (optional) assign ProcessIdPtr +!! (not needed if IDstring is either a node number or a {lay, row, col} +!! set of indices). +!! +!! Examples: gwf_df_obs, drn_df_obs +!! +!! When boundary names are allowed and developer wants simulated value +!! to be cumulative (flow, for example) if user specifies multiple +!! boundaries with the same BOUNDNAME, in bnd_df_obs call to +!! ObsPackage.StoreObsType, provide cumulative argument as true. +!! Otherwise, simulated values are not cumulative. +!! +!! 4. In DF routine: Call bnd_df_obs +!! +!! 5. In AR routine: Call ObsType.obs_ar. This reads the OBS input +!! file. +!! Example (gwf_ar): call this%obs%obs_ar() +!! Example (lak_ar): call this%obs%obs_ar() +!! +!! 6. Override BndType.bnd_rp_obs for any package that needs to +!! check user input or process observation input in any special way. +!! If no special processing is needed, BndType.bnd_rp_obs can +!! be used. This routine also expands the ObserveType%indxbnds array for +!! each observation in a package. ObserveType%indxbnds is used to sum +!! simulated values from multiple boundaries when BOUNDNAMES is used. +!! Equivalent routine may or may not be needed for model observations. +!! If needed, call it from bottom of RP routine. +!! +!! Examples: +!! BndType.bnd_rp_obs, which is called from gwf_rp +!! +!! 7. In AD routine: Call ObsType.obs_ad +!! Example: gwf_ad +!! +!! 8. Write a *_bd_obs routine. This is the routine that actually +!! calculates the simulated value for each observation type supported +!! by the model/package. Call *_bd_obs from the bottom of the +!! _bd routine. +!! *_bd_obs needs to: +!! Call ObsType.obs_bd_clear +!! For each observation: +!! Calculate the simulated value +!! Call ObsType.SaveOneSimval +!! Examples: gwf_bd_obs, maw_bd_obs, lak_bd_obs +!! +!! 9. In BD routine: +!! Call BndType.bnd_bd_obs +!! Examples: BndType.bnd_bd calls bnd_bd_obs +!! GwfModelType.gwf_bd calls gwf_bd_obs +!! MawType.maw_bd calls maw_bd_obs +!! LakType.lak_bd calls lak_bd_obs +!! +!! 10. Ensure that ObsType.obs_ot is called. For packages, obs_ot is called +!! from the model _ot procedure. The model _ot procedure should also call +!! obs_ot for its own observations. Do not call obs_ot from a package _ot +!! procedure because the package _ot procedure may not be called, depending +!! on Output Control settings (ibudfl). +!! +!! Note: BndType.bnd_ot_obs calls: +!! ObsType.obs_ot +!! +!! Note: ObsType.obs_ot calls: +!! store_all_simvals +!! write_continuous_simvals +!! obsOutputList.WriteOutputLines +!! +!! BINARY OUTPUT: +!! +!! When observation-output files are written, the user has the option to have +!! output written to a binary file. Binary obs output files start with a +!! 100-byte header structured as follows: +!! +!! bytes 1-4 (ascii): Observation type contained in file; options are: +!! "cont" -- Continuous observations +!! byte 5: blank +!! bytes 6-11 (ascii): Precision of all floating-point values; options are: +!! "single" -- Single precision +!! "double" -- Double precision +!! bytes 12-15 (ascii): LENOBSNAME (integer; length of observation names, +!! in bytes) +!! bytes 16-100: blank +!! +!! IN A FILE OF CONTINUOUS OBSERVATIONS: +!! +!! The 100-byte header is followed by: +!! NOBS (4-byte integer) -- Number of observations. +!! NOBS repetitions of OBSNAME (ascii, LENOBSNAME bytes each). +!! Any number of repetitions of: +!! TIME SIMVAL-1 SIMVAL-2 ... SIMVAL-NOBS (floating point) +!! +!< module ObsModule use KindModule, only: DP, I4B @@ -144,7 +144,7 @@ module ObsModule AddObsToList use ObsOutputListModule, only: ObsOutputListType use ObsOutputModule, only: ObsOutputType - use ObsUtilityModule, only: write_fmtd_cont, write_unfmtd_cont + use ObsUtilityModule, only: write_fmtd_obs, write_unfmtd_obs use OpenSpecModule, only: ACCESS, FORM use SimVariablesModule, only: errmsg use SimModule, only: count_errors, store_error, store_error_unit @@ -157,15 +157,15 @@ module ObsModule type :: ObsType ! -- Public members - integer(I4B), public :: iout = 0 - integer(I4B), public :: npakobs = 0 - integer(I4B), pointer, public :: inUnitObs => null() - character(len=LINELENGTH), pointer, public :: inputFilename => null() - character(len=2*LENPACKAGENAME + 4), public :: pkgName = '' - character(len=LENFTYPE), public :: filtyp = '' - logical, pointer, public :: active => null() - type(ObsContainerType), dimension(:), pointer, public :: pakobs => null() - type(ObsDataType), dimension(:), pointer, public :: obsData => null() + integer(I4B), public :: iout = 0 !< model list file unit + integer(I4B), public :: npakobs = 0 !< number of observations + integer(I4B), pointer, public :: inUnitObs => null() !< observation input file unit + character(len=LINELENGTH), pointer, public :: inputFilename => null() !< observation input file name + character(len=2*LENPACKAGENAME + 4), public :: pkgName = '' !< package name + character(len=LENFTYPE), public :: filtyp = '' !< package file type + logical, pointer, public :: active => null() !> logical indicating if a observation is active + type(ObsContainerType), dimension(:), pointer, public :: pakobs => null() !< package observations + type(ObsDataType), dimension(:), pointer, public :: obsData => null() !< observation data ! -- Private members integer(I4B), private :: iprecision = 2 ! 2=double; 1=single integer(I4B), private :: idigits = 0 @@ -201,62 +201,61 @@ module ObsModule procedure, private :: get_obs_datum procedure, private :: obs_ar1 procedure, private :: obs_ar2 - procedure, private :: populate_obs_array + procedure, private :: set_obs_array procedure, private :: read_observations procedure, private :: read_obs_blocks procedure, private :: read_obs_options - procedure, private :: write_continuous_simvals + procedure, private :: write_obs_simvals end type ObsType contains ! Non-type-bound procedures + !> @ brief Create a new ObsType object + !! + !! Subroutine to create a new ObsType object. Soubroutine + !! + !! - creates object + !! - allocates pointer + !! - initilizes values + !! + !< subroutine obs_cr(obs, inobs) -! ****************************************************************************** -! obs_cr -- Create a new ObsType object -! Subroutine: (1) creates object -! (2) allocates pointers -! (3) initializes values -! ****************************************************************************** -! -! SPECIFICATIONS: -! ------------------------------------------------------------------------------ ! -- dummy - type(ObsType), pointer, intent(out) :: obs - integer(I4B), pointer, intent(in) :: inobs -! ------------------------------------------------------------------------------ + type(ObsType), pointer, intent(out) :: obs !< observation ObsType + integer(I4B), pointer, intent(in) :: inobs !< observation input file unit ! allocate (obs) call obs%allocate_scalars() obs%inUnitObs => inobs ! + ! -- return return end subroutine obs_cr + !> @ brief Process IDstring provided for each observation + !! + !! Subroutine to process the IDstring provided for each observation. The + !! IDstring identifies the location in the model of the node(s) or feature(s) + !! where the simulated value is to be extracted and recorded. Subroutine + !! + !! - interprets the IDstring + !! - stores the location of interest in the ObserveType object that + !! contains information about the observation + !! + !< subroutine DefaultObsIdProcessor(obsrv, dis, inunitobs, iout) -! ****************************************************************************** -! DefaultObsIdProcessor -- Process IDstring provided for each observation. The -! IDstring identifies the location in the model of the node(s) or feature(s) -! where the simulated value is to be extracted and recorded. -! Subroutine: (1) interprets the IDstring -! (2) stores the location of interest in the ObserveType object that -! contains information about the observation -! ****************************************************************************** -! -! SPECIFICATIONS: -! ------------------------------------------------------------------------------ ! -- dummy - type(ObserveType), intent(inout) :: obsrv - class(DisBaseType), intent(in) :: dis - integer(I4B), intent(in) :: inunitobs - integer(I4B), intent(in) :: iout + type(ObserveType), intent(inout) :: obsrv !< observation ObserveType + class(DisBaseType), intent(in) :: dis !< discretization object + integer(I4B), intent(in) :: inunitobs !< observation input file unit + integer(I4B), intent(in) :: iout !< model list file ! -- local integer(I4B) :: n integer(I4B) :: icol, istart, istop character(len=LINELENGTH) :: strng logical :: flag_string -! ------------------------------------------------------------------------------ ! ! -- Initialize variables strng = obsrv%IDstring @@ -282,25 +281,24 @@ subroutine DefaultObsIdProcessor(obsrv, dis, inunitobs, iout) call store_error_unit(inunitobs) end if ! + ! -- return return end subroutine DefaultObsIdProcessor ! Type-bound public procedures + !> @ brief Define some members of an ObsType object + !! + !! Subroutine to define some members of an ObsType object. + !! + !< subroutine obs_df(this, iout, pkgname, filtyp, dis) -! ****************************************************************************** -! obs_df -- Define some members of an ObsType object -! ****************************************************************************** -! -! SPECIFICATIONS: -! ------------------------------------------------------------------------------ ! -- dummy class(ObsType), intent(inout) :: this - integer(I4B), intent(in) :: iout - character(len=*), intent(in) :: pkgname - character(len=*), intent(in) :: filtyp - class(DisBaseType), pointer :: dis -! ------------------------------------------------------------------------------ + integer(I4B), intent(in) :: iout !< model list file unit + character(len=*), intent(in) :: pkgname !< package name + character(len=*), intent(in) :: filtyp !< package file type + class(DisBaseType), pointer :: dis !< discretization object ! this%iout = iout this%pkgName = pkgname @@ -310,116 +308,112 @@ subroutine obs_df(this, iout, pkgname, filtyp, dis) ! -- Initialize block parser call this%parser%Initialize(this%inUnitObs, this%iout) ! + ! -- return return end subroutine obs_df + !> @ brief Allocate and read package observations + !! + !! Subroutine to allocate and read observations for a package. Subroutine + !! + !! - reads OPTIONS block of OBS input file + !! - reads CONTINUOUS blocks of OBS input file + !! + !< subroutine obs_ar(this) -! ****************************************************************************** -! obs_ar -- ObsType Allocate and Read -! Subroutine: (1) reads OPTIONS block of OBS input file -! (2) reads CONTINUOUS blocks of OBS input file -! ****************************************************************************** -! -! SPECIFICATIONS: -! ------------------------------------------------------------------------------ ! -- dummy class(ObsType) :: this -! ------------------------------------------------------------------------------ ! call this%obs_ar1(this%pkgName) if (this%active) then call this%obs_ar2(this%dis) end if ! + ! -- return return end subroutine obs_ar + !> @ brief Advance package observations + !! + !! Subroutine to advance each package observations by resetting the + !! "current" value. + !! + !< subroutine obs_ad(this) -! ****************************************************************************** -! obs_ad -- Observation Time Step Advance -! Subroutine: (1) For each observation, resets "current" value -! ****************************************************************************** -! -! SPECIFICATIONS: -! ------------------------------------------------------------------------------ ! -- dummy class(ObsType) :: this ! -- local integer(I4B) :: i, n class(ObserveType), pointer :: obsrv => null() -! ------------------------------------------------------------------------------ ! n = this%get_num() do i = 1, n obsrv => this%get_obs(i) - call obsrv%ResetCurrent() + call obsrv%ResetCurrentValue() end do ! + ! -- return return end subroutine obs_ad + !> @ brief Clear observation output lines + !! + !! Subroutine to clear output lines in preparation for new rows of + !! continuous observations. + !! + !< subroutine obs_bd_clear(this) -! ****************************************************************************** -! obs_bd_clear -- Clear output lines in preparation for new rows of -! continuous observations -! Subroutine: (1) Clears contents of all lineout members of obsOutputList -! at start of a new time step -! ****************************************************************************** -! -! SPECIFICATIONS: -! ------------------------------------------------------------------------------ ! -- dummy class(ObsType), target :: this -! ------------------------------------------------------------------------------ ! - call this%obsOutputList%ClearOutputLines() + call this%obsOutputList%ResetAllObsEmptyLines() ! + ! -- return return end subroutine obs_bd_clear + !> @ brief Output observation data + !! + !! Subroutine to output observation data. Subroutine + !! + !! - stores each simulated value into its ObserveType object + !! - writes each simulated value to it ObsOutputList object + !! _ writes contents of ObsOutputList to output file + !! + !! This procedure should NOT be called from a package's _ot procedure + !! because the package _ot procedure may not be called every time step. + !! + !< subroutine obs_ot(this) -! ****************************************************************************** -! obs_ot -- Observation Output -! Subroutine: (1) stores each simulated value into its ObserveType object -! (2) writes each simulated value to it ObsOutputList object -! (3) writes contents of ObsOutputList to output file -! Note: This procedure should NOT be called from a package's _ot procedure -! because the package _ot procedure may not be called every time step. -! ****************************************************************************** -! -! SPECIFICATIONS: -! ------------------------------------------------------------------------------ ! -- dummy class(ObsType), intent(inout) :: this -! ------------------------------------------------------------------------------ ! if (this%npakobs > 0) then - call this%write_continuous_simvals() - call this%obsOutputList%WriteOutputLines() + call this%write_obs_simvals() + call this%obsOutputList%WriteAllObsLineReturns() end if ! + ! -- return return end subroutine obs_ot + !> @ brief Deallocate observation data + !! + !! Subroutine to deallocate observation data. + !! + !< subroutine obs_da(this) -! ****************************************************************************** -! obs_da -- Observation Output -! ****************************************************************************** -! -! SPECIFICATIONS: -! ------------------------------------------------------------------------------ ! -- dummy class(ObsType), intent(inout) :: this ! -- local integer(I4B) :: i class(ObserveType), pointer :: obsrv => null() -! ------------------------------------------------------------------------------ ! deallocate (this%active) deallocate (this%inputFilename) deallocate (this%obsData) ! - ! -- obs table object + ! -- observation table object if (associated(this%obstab)) then call this%obstab%table_da() deallocate (this%obstab) @@ -447,26 +441,24 @@ subroutine obs_da(this) ! -- nullify nullify (this%inUnitObs) ! + ! -- return return end subroutine obs_da + !> @ brief Save a simulated value + !! + !! Subroutine to save or accumulate a simulated value to its ObserveType + !! object. + !! + !< subroutine SaveOneSimval(this, obsrv, simval) -! ****************************************************************************** -! SaveOneSimval -! Subroutine: (1) saves or accumulates a simulated value to its ObserveType -! object -! ****************************************************************************** -! -! SPECIFICATIONS: -! ------------------------------------------------------------------------------ ! -- dummy class(ObsType) :: this - class(ObserveType), intent(inout) :: obsrv - real(DP), intent(in) :: simval + class(ObserveType), intent(inout) :: obsrv !< observation ObserveType + real(DP), intent(in) :: simval !< simulated value ! -- local character(len=LENOBSTYPE) :: obsTypeID type(ObsDataType), pointer :: obsDatum => null() -! ------------------------------------------------------------------------------ ! ! -- initialize variables obsTypeID = obsrv%ObsTypeId @@ -482,30 +474,28 @@ subroutine SaveOneSimval(this, obsrv, simval) obsrv%CurrentTimeStepEndValue = simval end if ! + ! -- return return end subroutine SaveOneSimval + !> @ brief Store observation type + !! + !! Subroutine to store type name and related information for an + !! observation type that belongs to a package or model in the + !! obsData array. + !! + !< subroutine StoreObsType(this, obsrvType, cumulative, indx) -! ****************************************************************************** -! StoreObsType -! Subroutine: (1) stores type name and related information for an -! observation type that belongs to a package or model in -! the obsData array -! ****************************************************************************** -! -! SPECIFICATIONS: -! ------------------------------------------------------------------------------ ! -- dummy class(ObsType), intent(inout) :: this - character(len=*), intent(in) :: obsrvType + character(len=*), intent(in) :: obsrvType !< observation type ! cumulative: Accumulate simulated values for multiple boundaries - logical, intent(in) :: cumulative - integer(I4B), intent(out) :: indx + logical, intent(in) :: cumulative !< logical indicating if the observation should be accumulated + integer(I4B), intent(out) :: indx !< observation index ! -- local integer(I4B) :: i character(len=LENOBSTYPE) :: obsTypeUpper character(len=100) :: msg -! ------------------------------------------------------------------------------ ! ! -- Ensure that obsrvType is not blank if (obsrvType == '') then @@ -537,21 +527,21 @@ subroutine StoreObsType(this, obsrvType, cumulative, indx) this%obsData(indx)%ObsTypeID = obsTypeUpper this%obsData(indx)%Cumulative = cumulative ! + ! -- return return end subroutine StoreObsType ! Type-bound private procedures + !> @ brief Allocate observation scalars + !! + !! Subroutine to allocate and initialize memory for non-allocatable + ! members (scalars). + !! + !< subroutine allocate_scalars(this) -! ****************************************************************************** -! allocate_scalars -- Allocate memory for non-allocatable members -! ****************************************************************************** -! -! SPECIFICATIONS: -! ------------------------------------------------------------------------------ ! -- dummy class(ObsType) :: this -! ------------------------------------------------------------------------------ ! allocate (this%active) allocate (this%inputFilename) @@ -562,24 +552,22 @@ subroutine allocate_scalars(this) this%active = .false. this%inputFilename = '' ! - ! -- Return + ! -- return return end subroutine allocate_scalars + !> @ brief Read observation options and output formats + !! + !! Subroutine to read the options block in the observation input file and + !! define output formats. + !! + !< subroutine obs_ar1(this, pkgname) -! ****************************************************************************** -! obs_ar1 -! -- read OPTIONS block of OBS input file and define output formats. -! ****************************************************************************** -! -! SPECIFICATIONS: -! ------------------------------------------------------------------------------ ! -- dummy class(ObsType), intent(inout) :: this - character(len=*), intent(in) :: pkgname + character(len=*), intent(in) :: pkgname !< package name ! -- formats 10 format(/, 'The observation utility is active for "', a, '"') -! ------------------------------------------------------------------------------ ! if (this%inUnitObs > 0) then this%active = .true. @@ -594,30 +582,28 @@ subroutine obs_ar1(this, pkgname) call this%define_fmts() end if ! + ! -- return return end subroutine obs_ar1 + !> @ brief Call procedure provided by package + !! + !! Subroutine to call procedure provided by package to interpret IDstring + !! and store required data. + !! + !< subroutine obs_ar2(this, dis) -! ****************************************************************************** -! obs_ar2 -! -- Call procedure provided by package to interpret IDstring and -! store required data. -! ****************************************************************************** -! -! SPECIFICATIONS: -! ------------------------------------------------------------------------------ ! -- dummy class(ObsType), intent(inout) :: this - class(DisBaseType) :: dis + class(DisBaseType) :: dis !< discretization object ! -- local integer(I4B) :: i type(ObsDataType), pointer :: obsDat => null() character(len=LENOBSTYPE) :: obsTypeID class(ObserveType), pointer :: obsrv => null() -! ------------------------------------------------------------------------------ ! call this%read_observations() - ! -- allocate and populate observations array + ! -- allocate and set observation array call this%get_obs_array(this%npakobs, this%pakobs) ! do i = 1, this%npakobs @@ -638,17 +624,16 @@ subroutine obs_ar2(this, dis) call store_error_unit(this%inunitobs) end if ! + ! -- return return end subroutine obs_ar2 + !> @ brief Read observation options block + !! + !! Subroutine to read the options block in the observation input file. + !! + !< subroutine read_obs_options(this) -! ****************************************************************************** -! read_obs_options -! -- read OPTIONS block of OBS input file -! ****************************************************************************** -! -! SPECIFICATIONS: -! ------------------------------------------------------------------------------ ! -- dummy class(ObsType) :: this ! -- local @@ -665,7 +650,6 @@ subroutine read_obs_options(this) 40 format('Text output number of digits of precision set to: ', i2) 50 format('Text output number of digits set to internal representation (G0).') 60 format(/, 'Processing observation options:',/) -! ------------------------------------------------------------------------------ ! localprecision = 0 localdigits = -1 @@ -752,43 +736,41 @@ subroutine read_obs_options(this) if (localprecision > 0) this%iprecision = localprecision if (localdigits >= 0) this%idigits = localdigits ! + ! -- return return end subroutine read_obs_options + !> @ brief Define observation output formats + !! + !! Subroutine to define observation output formats. + !! + !< subroutine define_fmts(this) -! ****************************************************************************** -! define_fmts -! -- define output formats for single and continuous observations -! ****************************************************************************** -! -! SPECIFICATIONS: -! ------------------------------------------------------------------------------ ! -- dummy class(ObsType) :: this ! formats 50 format('(g', i2.2, '.', i2.2, ')') -! ------------------------------------------------------------------------------ ! if (this%idigits == 0) then this%obsfmtcont = '(G0)' else write (this%obsfmtcont, 50) this%idigits + 7, this%idigits end if + ! + ! -- return return end subroutine define_fmts + !> @ brief Read observations + !! + !! Subroutine to read the observations from the observation input file + !! and build headers for the observation output files. + !! + !< subroutine read_observations(this) -! ****************************************************************************** -! read_observations -! -- read CONTINUOUS blocks from OBS input file -! ****************************************************************************** -! -! SPECIFICATIONS: -! ------------------------------------------------------------------------------ ! -- dummy class(ObsType) :: this ! -- local -! ------------------------------------------------------------------------------ ! ! -- Read CONTINUOUS blocks and store observations call this%read_obs_blocks(this%outputFilename) @@ -796,37 +778,36 @@ subroutine read_observations(this) ! -- build headers call this%build_headers() ! + ! -- return return end subroutine read_observations + !> @ brief Get the number of observations + !! + !! Function to get the number of observationns in this ObsType object. + !! + !< function get_num(this) -! ****************************************************************************** -! get_num -! -- Return the number of observations contained in this ObsType object -! ****************************************************************************** -! -! SPECIFICATIONS: -! ------------------------------------------------------------------------------ ! -- return - integer(I4B) :: get_num + integer(I4B) :: get_num !< number of observations ! -- dummy class(ObsType) :: this -! ------------------------------------------------------------------------------ ! get_num = this%obsList%Count() + ! + ! -- return return end function get_num + !> @ brief Build observation headers + !! + !! Subroutine to build headers for CSV-formatted and unformatted + !! continuous-observation output files and write them to those files. + !! + !! Each formatted header will have the form: "time,obsname-1,obsname-2, ..." + !! + !< subroutine build_headers(this) -! ****************************************************************************** -! build_headers -! -- Build headers for CSV-formatted and unformatted continuous-observation -! output files and write them to those files. -! Each formatted header will have the form: "time,obsname-1,obsname-2, ..." -! ****************************************************************************** -! -! SPECIFICATIONS: -! ------------------------------------------------------------------------------ ! -- module use iso_fortran_env, only: int32 ! -- dummy @@ -841,7 +822,6 @@ subroutine build_headers(this) character(len=4) :: clenobsname type(ObserveType), pointer :: obsrv => null() type(ObsOutputType), pointer :: obsOutput => null() -! ------------------------------------------------------------------------------ ! ! -- Cycle through ObsOutputList to write headers ! to formatted and unformatted file(s). @@ -897,53 +877,49 @@ subroutine build_headers(this) return end subroutine build_headers + !> @ brief Get an array of observations + !! + !! Subroutine to get an array containing all observations in this + !! ObsType object. + !! + !< subroutine get_obs_array(this, nObs, obsArray) -! ****************************************************************************** -! get_obs_array -! -- Get an array containing all observations in this ObsType object -! ****************************************************************************** -! -! SPECIFICATIONS: -! ------------------------------------------------------------------------------ ! -- dummy class(ObsType), intent(inout) :: this - integer(I4B), intent(out) :: nObs - type(ObsContainerType), dimension(:), pointer, intent(inout) :: obsArray - ! -- local -! ------------------------------------------------------------------------------ + integer(I4B), intent(out) :: nObs !< number of observations + type(ObsContainerType), dimension(:), pointer, intent(inout) :: obsArray !< observation array ! nObs = this%get_num() if (associated(obsArray)) deallocate (obsArray) allocate (obsArray(nObs)) ! - ! Get observations + ! set observations in obsArray if (nObs > 0) then - call this%populate_obs_array(nObs, obsArray) + call this%set_obs_array(nObs, obsArray) end if ! + ! -- return return end subroutine get_obs_array + !> @ brief Get an ObsDataType object + !! + !! Function to get an ObsDataType object for the specified observation type. + !! + !< function get_obs_datum(this, obsTypeID) result(obsDatum) -! ****************************************************************************** -! get_obs_datum -! -- Return an ObsDataType object for the specified observation type -! ****************************************************************************** -! -! SPECIFICATIONS: -! ------------------------------------------------------------------------------ ! -- dummy class(ObsType) :: this - character(len=*), intent(in) :: obsTypeID - type(ObsDataType), pointer :: obsDatum + character(len=*), intent(in) :: obsTypeID !< observation type + ! -- return + type(ObsDataType), pointer :: obsDatum !< observation ObsDataType ! -- local integer(I4B) :: i -! ------------------------------------------------------------------------------ ! obsDatum => null() do i = 1, MAXOBSTYPES if (this%obsData(i)%ObsTypeID == obsTypeID) then - obsDatum => this%obsData(I) + obsDatum => this%obsData(i) exit end if end do @@ -954,25 +930,24 @@ function get_obs_datum(this, obsTypeID) result(obsDatum) call store_error_unit(this%inUnitObs) end if ! + ! -- return return end function get_obs_datum - subroutine populate_obs_array(this, nObs, obsArray) -! ****************************************************************************** -! populate_obs_array -! -- Populate obsArray with observations for specified package -! ****************************************************************************** -! -! SPECIFICATIONS: -! ------------------------------------------------------------------------------ + !> @ brief Set observation array values + !! + !! Subroutine to set values in an observation array. + !! + !< + subroutine set_obs_array(this, nObs, obsArray) ! -- dummy class(ObsType), intent(inout) :: this - integer(I4B), intent(in) :: nObs - type(ObsContainerType), dimension(nObs), intent(inout) :: obsArray -! ------------------------------------------------------------------------------ + integer(I4B), intent(in) :: nObs !< number of observations + type(ObsContainerType), dimension(nObs), intent(inout) :: obsArray !< observation array ! ! -- local - integer(I4B) :: i, n + integer(I4B) :: i + integer(I4B) :: n type(ObserveType), pointer :: obsrv => null() ! n = this%get_num() @@ -981,37 +956,34 @@ subroutine populate_obs_array(this, nObs, obsArray) obsArray(i)%obsrv => obsrv end do ! + ! -- return return - end subroutine populate_obs_array + end subroutine set_obs_array + !> @ brief Get an ObserveType object + !! + !! Subroutine to get an ObserveType object from the list of observations + !! using an list index. + !! + !< function get_obs(this, indx) result(obsrv) -! ****************************************************************************** -! get_obs -! -- Return the specified ObserveType object from the list of observations -! ****************************************************************************** -! -! SPECIFICATIONS: -! ------------------------------------------------------------------------------ ! -- dummy class(ObsType) :: this - integer(I4B), intent(in) :: indx - class(ObserveType), pointer :: obsrv - ! -- local -! ------------------------------------------------------------------------------ + integer(I4B), intent(in) :: indx !< observation list index + class(ObserveType), pointer :: obsrv !< observation ObserveType ! obsrv => GetObsFromList(this%obsList, indx) ! + ! -- return return end function get_obs + !> @ brief Read observation blocks + !! + !! Subroutine to read CONTIGUIUS block from the observation input file. + !! + !< subroutine read_obs_blocks(this, fname) -! ****************************************************************************** -! read_obs_blocks -! -- read CONTINUOUS blocks from the OBS input file -! ****************************************************************************** -! -! SPECIFICATIONS: -! ------------------------------------------------------------------------------ ! -- dummy class(ObsType), intent(inout) :: this character(len=*), intent(inout) :: fname @@ -1028,8 +1000,6 @@ subroutine read_obs_blocks(this, fname) type(ObsOutputType), pointer :: obsOutput => null() integer(I4B) :: ntabrows integer(I4B) :: ntabcols - ! -- formats -! ------------------------------------------------------------------------------ ! ! -- initialize local variables numspec = -1 @@ -1164,22 +1134,22 @@ subroutine read_obs_blocks(this, fname) return end subroutine read_obs_blocks - subroutine write_continuous_simvals(this) -! ****************************************************************************** -! write_continuous_simvals -! Subroutine: (1) for each continuous observation, writes value to output -! ****************************************************************************** -! -! SPECIFICATIONS: -! ------------------------------------------------------------------------------ + !> @ brief Write observation data + !! + !! Subroutine to write observation data for a time step for each observation + !! to the observation output file. + !! + !< + subroutine write_obs_simvals(this) ! -- dummy class(ObsType), intent(inout) :: this ! -- local - integer(I4B) :: i, iprec, numobs + integer(I4B) :: i + integer(I4B) :: iprec + integer(I4B) :: numobs character(len=20) :: fmtc real(DP) :: simval class(ObserveType), pointer :: obsrv => null() -! ------------------------------------------------------------------------------ ! ! Write simulated values for observations iprec = this%iprecision @@ -1191,17 +1161,14 @@ subroutine write_continuous_simvals(this) ! -- continuous observation simval = obsrv%CurrentTimeStepEndValue if (obsrv%FormattedOutput) then - call write_fmtd_cont(fmtc, obsrv, this%obsOutputList, simval) + call write_fmtd_obs(fmtc, obsrv, this%obsOutputList, simval) else - call write_unfmtd_cont(obsrv, iprec, this%obsOutputList, simval) + call write_unfmtd_obs(obsrv, iprec, this%obsOutputList, simval) end if end do ! - ! -- flush file - flush (obsrv%UnitNumber) - ! ! --return return - end subroutine write_continuous_simvals + end subroutine write_obs_simvals end module ObsModule diff --git a/src/Utilities/Observation/ObsContainer.f90 b/src/Utilities/Observation/ObsContainer.f90 index b59dac00617..45b91fab51b 100644 --- a/src/Utilities/Observation/ObsContainer.f90 +++ b/src/Utilities/Observation/ObsContainer.f90 @@ -1,9 +1,10 @@ -! This module defines derived type ObsContainerType: -! -! ObsContainerType -- contains a pointer to an object of type -! ObserveType. Its purpose is to allow ObserveType objects to be -! stored in an array. -!----------------------------------------------------------------------- +!> @brief This module contains the derived type ObsContainerType +!! +!! This module contains the derived type ObsContainerType, which +!! contains a pointer to an object of type ObserveType. Its purpose is +!! to allow ObserveType objects to be stored in an array. +!! +!< module ObsContainerModule use KindModule, only: DP, I4B diff --git a/src/Utilities/Observation/ObsOutput.f90 b/src/Utilities/Observation/ObsOutput.f90 index a467031acbe..f40581bae3b 100644 --- a/src/Utilities/Observation/ObsOutput.f90 +++ b/src/Utilities/Observation/ObsOutput.f90 @@ -1,14 +1,15 @@ -! This module defines derived type ObsOutputType. -! -! ObsOutputType -- contains information and methods needed for writing -! a line of simulated values for observations to an output file. Each -! block of type continuous in an observation file is -! associated with an ObsOutputType object. However, the methods are -! needed only for continuous observations. -!----------------------------------------------------------------------- +!> @brief This module defines the derived type ObsOutputType +!! +!! This module contains information and methods needed for writing +!! a line of simulated values for observations to an output file. Each +!! block of type continuous in an observation file is +!! associated with an ObsOutputType object. However, the methods are +!! needed only for continuous observations. +!! +!< module ObsOutputModule - use KindModule, only: DP, I4B + use KindModule, only: DP, I4B, LGP use ConstantsModule, only: LENBIGLINE, LENOBSNAME use ListModule, only: ListType @@ -21,74 +22,65 @@ module ObsOutputModule type :: ObsOutputType ! -- Public members ! kind specified to ensure consistent binary output - integer(kind=4), public :: nobs = 0 - integer(I4B), public :: nunit = 0 - character(len=500), public :: filename = '' - !character(len=LENOBSNAME), allocatable, dimension(:), public :: obsnames - character(len=LENOBSNAME), public :: header = '' - character(len=LENOBSNAME), public :: lineout = '' - logical, public :: FormattedOutput = .true. + integer(kind=4), public :: nobs = 0 !< number of observations + integer(I4B), public :: nunit = 0 !< observation output unit + character(len=500), public :: filename = '' !< observation output filename + logical(LGP), public :: empty_line = .TRUE. !< logical indicating if the line for a time step is empty + character(len=LENOBSNAME), public :: header = '' !< observation header string + logical, public :: FormattedOutput = .true. !< logical indicating if writing formatted output contains ! -- Public procedures - procedure, public :: ClearLineout - procedure, public :: WriteLineout - !procedure, public :: DeallocObsOutput + procedure, public :: ResetObsEmptyLine + procedure, public :: WriteObsLineReturn end type ObsOutputType contains ! Procedures bound to ObsOutputType - subroutine ClearLineout(this) -! ************************************************************************** -! ClearLineout -- clear the lineout member -! ************************************************************************** -! -! SPECIFICATIONS: -! -------------------------------------------------------------------------- - implicit none + !> @ brief Reset empty line logical + !! + !! Subroutine to reset the empty line logical. + !! + !< + subroutine ResetObsEmptyLine(this) ! -- dummy class(ObsOutputType), intent(inout) :: this ! - this%lineout = '' + this%empty_line = .TRUE. + ! + ! -- return return - end subroutine ClearLineout + end subroutine ResetObsEmptyLine - subroutine WriteLineout(this) -! ************************************************************************** -! WriteLineout -- write the lineout member to the output file -! ************************************************************************** -! -! SPECIFICATIONS: -! -------------------------------------------------------------------------- - implicit none + !> @ brief Write line return for observation + !! + !! Subroutine to write a line return for a time step in an observation + !! output file. + !! + !< + subroutine WriteObsLineReturn(this) ! -- dummy class(ObsOutputType), intent(inout) :: this ! -- write a line return to end of observation output line ! for this totim write (this%nunit, '(a)', advance='YES') '' ! + ! --return return - end subroutine WriteLineout - ! - !subroutine DeallocObsOutput(this) - ! implicit none - ! ! -- dummy - ! class(ObsOutputType), intent(inout) :: this - ! ! - ! if (allocated(this%obsnames)) then - ! deallocate(this%obsnames) - ! endif - ! ! - ! return - !end subroutine DeallocObsOutput + end subroutine WriteObsLineReturn ! Non-type-bound procedures + !> @ brief Cast as ObsOutputType + !! + !! Cast an object as an ObsOutputType. + !! + !< function CastAsObsOutputType(obj) result(res) - implicit none - class(*), pointer, intent(inout) :: obj - type(ObsOutputType), pointer :: res + ! -- dummy + class(*), pointer, intent(inout) :: obj !< input object + type(ObsOutputType), pointer :: res !< ObsOutputType ! res => null() if (.not. associated(obj)) return @@ -99,54 +91,68 @@ function CastAsObsOutputType(obj) result(res) class default continue end select + ! + ! -- return return end function CastAsObsOutputType + !> @ brief Construct and assign ObsOutputType object + !! + !! Subroutine to construct an ObsOutputType object and assign + !! the observation output file name and unit number. + !! + !< subroutine ConstructObsOutput(newObsOutput, fname, nunit) -! ************************************************************************** -! ConstructObsOutput -- construct an ObsOutputType object and assign -! arguments to its members -! ************************************************************************** -! -! SPECIFICATIONS: -! -------------------------------------------------------------------------- - implicit none + ! -- dummy type(ObsOutputType), pointer, intent(out) :: newObsOutput - character(len=*), intent(in) :: fname - integer(I4B), intent(in) :: nunit + character(len=*), intent(in) :: fname !< observation output file name + integer(I4B), intent(in) :: nunit !< observation output unit number ! allocate (newObsOutput) newObsOutput%filename = fname newObsOutput%nunit = nunit + ! + ! -- return return end subroutine ConstructObsOutput + !> @ brief Add observation output to a list + !! + !! Subroutine to add observation output to a observation list. + !! + !< subroutine AddObsOutputToList(list, obsOutput) - implicit none ! -- dummy - type(ListType), intent(inout) :: list - type(ObsOutputType), pointer, intent(inout) :: obsOutput + type(ListType), intent(inout) :: list !< observation list + type(ObsOutputType), pointer, intent(inout) :: obsOutput !< observation output ! -- local class(*), pointer :: obj ! obj => obsOutput call list%Add(obj) ! + ! -- return return end subroutine AddObsOutputToList + !> @ brief Get observation output from a list + !! + !! Subroutine to get observation output from a observation list. + !! + !< function GetObsOutputFromList(list, idx) result(res) implicit none ! -- dummy - type(ListType), intent(inout) :: list - integer(I4B), intent(in) :: idx - type(ObsOutputType), pointer :: res + type(ListType), intent(inout) :: list !< observation list + integer(I4B), intent(in) :: idx !< observation index + type(ObsOutputType), pointer :: res !< observation output ! -- local class(*), pointer :: obj ! obj => list%GetItem(idx) res => CastAsObsOutputType(obj) ! + ! --return return end function GetObsOutputFromList diff --git a/src/Utilities/Observation/ObsOutputList.f90 b/src/Utilities/Observation/ObsOutputList.f90 index 55de0b7665f..cedda58309c 100644 --- a/src/Utilities/Observation/ObsOutputList.f90 +++ b/src/Utilities/Observation/ObsOutputList.f90 @@ -1,10 +1,11 @@ -! This module defines derived type ObsOutputListType. -! -! ObsOutputListType -- contains a list of ObsOutputType objects and -! methods needed for coordinating between an ObsType object and its -! ObsOutputType objects. Like ObsOutputType, ObsOutputListType is -! needed only for processing continuous observations. -!----------------------------------------------------------------------- +!> @brief This module defines the derived type ObsOutputListType +!! +!! This module contains a list of ObsOutputType objects and +!! methods needed for coordinating between an ObsType object and its +!! ObsOutputType objects. Like ObsOutputType, ObsOutputListType is +!! needed only for processing continuous observations. +!! +!< module ObsOutputListModule use KindModule, only: DP, I4B @@ -24,26 +25,24 @@ module ObsOutputListModule contains ! -- Public procedures procedure, public :: Add - procedure, public :: ClearOutputLines + procedure, public :: ResetAllObsEmptyLines procedure, public :: ContainsFile procedure, public :: Count procedure, public :: Get - procedure, public :: WriteOutputLines + procedure, public :: WriteAllObsLineReturns procedure, public :: Clear procedure, public :: DeallocObsOutputList end type ObsOutputListType contains - subroutine ClearOutputLines(this) -! ************************************************************************** -! ClearOutputLines -- clear the lineout member of all ObsOutputType objects -! in the list -! ************************************************************************** -! -! SPECIFICATIONS: -! -------------------------------------------------------------------------- - implicit none + !> @ brief Reset empty line logical for all observations + !! + !! Subroutine to reset the empty line logical for all ObsOutputType + !! objects in the list. + !! + !< + subroutine ResetAllObsEmptyLines(this) ! -- dummy class(ObsOutputListType), intent(inout) :: this ! -- local @@ -53,41 +52,40 @@ subroutine ClearOutputLines(this) num = this%Count() do i = 1, num obsOutput => this%Get(i) - call obsOutput%ClearLineout() + call obsOutput%ResetObsEmptyLine() end do ! + ! -- return return - end subroutine ClearOutputLines + end subroutine ResetAllObsEmptyLines + !> @ brief Count the number of ObsOutputType objects + !! + !! Subroutine to return the number of ObsOutputType objects in the list. + !! + !< function Count(this) -! ************************************************************************** -! Count -- return the number of ObsOutputType objects in the list -! ************************************************************************** -! -! SPECIFICATIONS: -! -------------------------------------------------------------------------- - implicit none ! -- return - integer(I4B) :: count + integer(I4B) :: count !< number of ObsOutputType objects ! -- dummy class(ObsOutputListType), intent(inout) :: this ! Count = this%ObsOutputs%Count() + ! + ! -- return return end function Count + !> @ brief Determine if a file name is in the list of ObsOutputType objects + !! + !! Function to determine if a file name is in the list of + !! ObsOutptType objects. + !! + !< logical function ContainsFile(this, fname) -! ************************************************************************** -! ContainsFile -- return true if filename fname is included in list of -! ObsOutputType objects -! ************************************************************************** -! -! SPECIFICATIONS: -! -------------------------------------------------------------------------- - implicit none ! -- dummy class(ObsOutputListType), intent(inout) :: this - character(len=*), intent(in) :: fname + character(len=*), intent(in) :: fname !< observation output file name ! -- local type(ObsOutputType), pointer :: obsOutput => null() integer(I4B) :: i, n @@ -101,41 +99,39 @@ logical function ContainsFile(this, fname) exit loop1 end if end do loop1 + ! + ! -- return return end function ContainsFile + !> @ brief Add a ObsOutputType object to the list + !! + !! Subroutine to add a new ObsOutputType object to the ObsOutputList and + !! assign ObsOutputType members. + !! + !< subroutine Add(this, fname, nunit) -! ************************************************************************** -! Add -- construct a new ObsOutputType object with arguments assigned to -! its members, and add the new object to the list -! ************************************************************************** -! -! SPECIFICATIONS: -! -------------------------------------------------------------------------- - implicit none ! -- dummy class(ObsOutputListType), intent(inout) :: this - character(len=*), intent(in) :: fname - integer(I4B), intent(in) :: nunit + character(len=*), intent(in) :: fname !< observation output file name + integer(I4B), intent(in) :: nunit !< observation output unit number ! -- local type(ObsOutputType), pointer :: obsOutput => null() ! call ConstructObsOutput(obsOutput, fname, nunit) call AddObsOutputToList(this%ObsOutputs, obsOutput) ! + ! -- return return end subroutine Add - subroutine WriteOutputLines(this) -! ************************************************************************** -! WriteOutputLines -- iterate through list of ObsOutputType objects and, -! for each continuous observation, write the lineout member to the output -! file -! ************************************************************************** -! -! SPECIFICATIONS: -! -------------------------------------------------------------------------- - implicit none + !> @ brief Write line returns for all ObsOutputListType + !! + !! Subroutine to write line returns for a time step for all observation + !! output files in a ObsOutputListType. + !! + !< + subroutine WriteAllObsLineReturns(this) ! -- dummy class(ObsOutputListType), intent(inout) :: this ! -- local @@ -146,49 +142,53 @@ subroutine WriteOutputLines(this) do i = 1, num obsOutput => this%Get(i) if (obsOutput%FormattedOutput) then - call obsOutput%WriteLineout() + call obsOutput%WriteObsLineReturn() end if end do ! + ! -- return return - end subroutine WriteOutputLines + end subroutine WriteAllObsLineReturns + !> @ brief Get an item from a ObsOutputListType + !! + !! Function to get a ObsOutputType from a ObsOutputListType list. + !! + !< function Get(this, indx) result(obsOutput) -! ************************************************************************** -! Get -- return the specified ObsOutputType object from the list -! ************************************************************************** -! -! SPECIFICATIONS: -! -------------------------------------------------------------------------- - implicit none ! -- dummy class(ObsOutputListType), intent(inout) :: this - integer(I4B), intent(in) :: indx + integer(I4B), intent(in) :: indx !< index for ObsOutputType object ! result type(ObsOutputType), pointer :: obsOutput ! obsOutput => GetObsOutputFromList(this%ObsOutputs, indx) + ! + ! -- return return end function Get + !> @ brief Clear a ObsOutputListType + !! + !! Subroutine to clear a ObsOutputListType list. + !! + !< subroutine Clear(this) -! ************************************************************************** -! Clear -! ************************************************************************** -! -! SPECIFICATIONS: -! -------------------------------------------------------------------------- - implicit none ! -- dummy class(ObsOutputListType), intent(inout) :: this ! call this%ObsOutputs%Clear() ! + ! -- return return end subroutine Clear + !> @ brief Deallocate a ObsOutputListType + !! + !! Subroutine to deallocate a ObsOutputListType list. + !! + !< subroutine DeallocObsOutputList(this) - implicit none ! -- dummy class(ObsOutputListType), intent(inout) :: this ! -- local @@ -203,6 +203,7 @@ subroutine DeallocObsOutputList(this) ! call this%ObsOutputs%Clear(.true.) ! + ! -- return return end subroutine DeallocObsOutputList diff --git a/src/Utilities/Observation/ObsUtility.f90 b/src/Utilities/Observation/ObsUtility.f90 index 1b04c70a9c4..3a2ae187daa 100644 --- a/src/Utilities/Observation/ObsUtility.f90 +++ b/src/Utilities/Observation/ObsUtility.f90 @@ -1,8 +1,11 @@ -! This module contains subroutines for writing simulated values stored -! in objects of ObserveType to output files. The subroutines handle -! continuous observations, and can write values to either formatted or -! unformatted files. -!----------------------------------------------------------------------- +!> @brief This module contains the ObsUtilityModule module +!! +!! This module contains subroutines for writing simulated values stored +!! in objects of ObserveType to output files. The subroutines handle +!! continuous observations, and can write values to either formatted or +!! unformatted files. +!! +!< module ObsUtilityModule use KindModule, only: DP, I4B @@ -15,93 +18,87 @@ module ObsUtilityModule implicit none private - public :: write_fmtd_cont, write_unfmtd_cont + public :: write_fmtd_obs, write_unfmtd_obs contains - subroutine write_fmtd_cont(fmtc, obsrv, obsOutputList, value) -! ************************************************************************** -! For a continuous observation, store a simulated value for the end of a -! time step into the lineout member of a specified ObserveType object -! for later writing. If the simulation time has not been written to -! the lineout member for the current time step, totim is written. The -! simulated value is written in the format specified in the fmtc argument. -! ************************************************************************** -! -! SPECIFICATIONS: -! -------------------------------------------------------------------------- - implicit none + !> @ brief Write formatted observation + !! + !! Subroutine to write observation data for the end of a time step to + !! a formatted file. If the simulation time has not been written to + !! for the current time step, totim is written. The simulated value is + !! written in the format specified in the fmtc argument. + !! + !< + subroutine write_fmtd_obs(fmtc, obsrv, obsOutputList, value) ! -- dummy - character(len=*), intent(in) :: fmtc - type(ObserveType), intent(inout) :: obsrv - type(ObsOutputListType), pointer, intent(inout) :: obsOutputList - real(DP), intent(in) :: value + character(len=*), intent(in) :: fmtc !< observation format + type(ObserveType), intent(inout) :: obsrv !< observation type + type(ObsOutputListType), pointer, intent(inout) :: obsOutputList !< observation list + real(DP), intent(in) :: value !< observation ! -- local integer(I4B) :: indx integer(I4B) :: nunit + character(len=20) :: ctotim character(len=50) :: cval - character(len=LENOBSNAME), pointer :: linout => null() type(ObsOutputType), pointer :: ObsOutput => null() - !--------------------------------------------------------------------------- - ! -- format for totim -10 format(G20.13) ! -- output unit nunit = obsrv%UnitNumber ! indx = obsrv%indxObsOutput ObsOutput => obsOutputList%Get(indx) - linout => obsOutput%lineout - if (linout == '') then - write (linout, 10) totim - write (cval, 10) totim - write (nunit, '(a)', advance='NO') trim(adjustl(cval)) + if (obsOutput%empty_line) then + ObsOutput%empty_line = .FALSE. + write (ctotim, '(G20.13)') totim + else + ctotim = '' end if ! -- append value to output line write (cval, fmtc) value - write (nunit, '(a,a)', advance='NO') ',', trim(adjustl(cval)) + write (nunit, '(3a)', advance='NO') & + trim(adjustl(ctotim)), ',', trim(adjustl(cval)) + ! + ! -- flush the file + ! Added flush after each non-advancing write to resolve + ! issue with ifort (IFORT) 19.1.0.166 20191121 for Linux + ! that occured on some Linux systems. + flush (nunit) ! ! -- return return - end subroutine write_fmtd_cont + end subroutine write_fmtd_obs - subroutine write_unfmtd_cont(obsrv, iprec, obsOutputList, value) -! ************************************************************************** -! For a continuous observation, write a simulated value for the end of a -! time step to the output unit number stored in the specified -! ObserveType object. If the simulation time has not been written to -! the output file for the current time step, totim is written. Totim and -! the simulated value are written unformatted using the precision specified -! in the iprec argument. -! ************************************************************************** -! -! SPECIFICATIONS: -! -------------------------------------------------------------------------- -! real32 specifies 32-bit real = 4 bytes = single precision. -! real64 specifies 64-bit real = 8 bytes = double precision. + !> @ brief Write unformatted observation + !! + !! Subroutine to write observation data for the end of a time step to + !! a unformatted file. If the simulation time has not been written for + !! the current time step, totim is written. The simulated value is + !! written using the precision specified in the iprec argument. + !! + !! iprec = 1: real32 specifies 32-bit real = 4 bytes = single precision. + !! iprec = 2: real64 specifies 64-bit real = 8 bytes = double precision. + !! + !< + subroutine write_unfmtd_obs(obsrv, iprec, obsOutputList, value) use iso_fortran_env, only: real32, real64 - implicit none ! -- dummy - type(ObserveType), intent(inout) :: obsrv - integer(I4B), intent(in) :: iprec - type(ObsOutputListType), pointer, intent(inout) :: obsOutputList - real(DP), intent(in) :: value + type(ObserveType), intent(inout) :: obsrv !< observation type + integer(I4B), intent(in) :: iprec !< observation precision + type(ObsOutputListType), pointer, intent(inout) :: obsOutputList !< observation list + real(DP), intent(in) :: value !< observation ! -- local integer(I4B) :: indx, nunit - character(len=LENOBSNAME), pointer :: linout => null() real(real32) :: totimsngl, valsngl real(real64) :: totimdbl, valdbl type(ObsOutputType), pointer :: obsOutput => null() - !--------------------------------------------------------------------------- - ! -- formats -10 format(G20.13) + ! ! -- output unit nunit = obsrv%UnitNumber ! -- continuous observation indx = obsrv%indxObsOutput obsOutput => obsOutputList%Get(indx) - linout => obsOutput%lineout - if (linout == '') then - write (linout, 10) totim + if (obsOutput%empty_line) then + obsOutput%empty_line = .FALSE. if (iprec == 1) then totimsngl = real(totim, real32) write (nunit) totimsngl @@ -121,6 +118,6 @@ subroutine write_unfmtd_cont(obsrv, iprec, obsOutputList, value) ! ! -- return return - end subroutine write_unfmtd_cont + end subroutine write_unfmtd_obs end module ObsUtilityModule diff --git a/src/Utilities/Observation/Observe.f90 b/src/Utilities/Observation/Observe.f90 index cd34726e074..8cc02be1161 100644 --- a/src/Utilities/Observation/Observe.f90 +++ b/src/Utilities/Observation/Observe.f90 @@ -1,14 +1,17 @@ -! This module defines two derived types: -! -! ObserveType -- is designed to contain all information and -! functionality needed for one observation. ObserveType contains a -! pointer to an ObsDataType object. -! -! ObsDataType -- is for storing package ID, observation type, and a -! pointer to a subroutine that will be called to process the IDstring -! provided in Obs input. The ProcessIdPtr member of ObsDataType -! requires a pointer to an ObserveType object. -!----------------------------------------------------------------------- +!> @brief This module contains the derived types ObserveType and ObsDataType +!! +!! This module contains the derived types ObserveType and ObsDataType. +!! +!! - ObserveType -- is designed to contain all information and +!! functionality needed for one observation. ObserveType contains a +!! pointer to an ObsDataType object. +!! +!! - ObsDataType -- is for storing package ID, observation type, and a +!! pointer to a subroutine that will be called to process the IDstring +!! provided in Obs input. The ProcessIdPtr member of ObsDataType +!! requires a pointer to an ObserveType object. +!! +!< module ObserveModule use KindModule, only: DP, I4B @@ -33,42 +36,42 @@ module ObserveModule ! -- Public members ! ! -- For all observations - integer(I4B), public :: NodeNumber = 0 - integer(I4B), public :: UnitNumber = 0 - character(len=LENOBSNAME), public :: Name = '' - character(len=LENOBSTYPE), public :: ObsTypeId = '' - character(len=200), public :: IDstring = '' - character(len=LENBOUNDNAME), public :: FeatureName = '' - character(len=LENBOUNDNAME), public :: FeatureName2 = '' + integer(I4B), public :: NodeNumber = 0 !< observation node number + integer(I4B), public :: UnitNumber = 0 !< observation output unit number + character(len=LENOBSNAME), public :: Name = '' !< observation name + character(len=LENOBSTYPE), public :: ObsTypeId = '' !< observation type id + character(len=200), public :: IDstring = '' !< observation id string + character(len=LENBOUNDNAME), public :: FeatureName = '' !< observation feature name + character(len=LENBOUNDNAME), public :: FeatureName2 = '' !< observation feature name 2 ! ! -- members specific to NPF intercell-flow observations - integer(I4B), public :: NodeNumber2 = 0 - integer(I4B), public :: JaIndex = -2 + integer(I4B), public :: NodeNumber2 = 0 !< observation second nod number + integer(I4B), public :: JaIndex = -2 !< observation JA index ! ! -- members that can be used as needed by packages or models - integer(I4B), public :: intPak1 = 0 - real(DP), public :: Obsdepth = DZERO - real(DP), public :: dblPak1 = DZERO + integer(I4B), public :: intPak1 = 0 !< + real(DP), public :: Obsdepth = DZERO !< + real(DP), public :: dblPak1 = DZERO !< ! ! -- indxbnds is intended to hold indices of position(s) in bound ! array of boundaries included in the observation. - integer(I4B), public :: indxbnds_count = 0 - integer(I4B), allocatable, dimension(:), public :: indxbnds + integer(I4B), public :: indxbnds_count = 0 !< number of observations indexes when using boundname + integer(I4B), allocatable, dimension(:), public :: indxbnds !< node numbers for observations when using boundname ! ! -- Set FormattedOutput false if output unit is opened for unformatted i/o - logical, public :: FormattedOutput = .true. - logical, public :: BndFound = .false. - real(DP), public :: CurrentTimeStepEndValue = DZERO - real(DP), public :: CurrentTimeStepEndTime = DZERO + logical, public :: FormattedOutput = .true. !< logical indicating if obervation output is formatted + logical, public :: BndFound = .false. !< logical indicating if a boundname was found + real(DP), public :: CurrentTimeStepEndValue = DZERO !< observation value + real(DP), public :: CurrentTimeStepEndTime = DZERO !< observation time ! ! -- Members specific to continuous observations - integer(I4B), public :: indxObsOutput = -1 + integer(I4B), public :: indxObsOutput = -1 !< index for observation output ! ! -- Private members - type(ObsDataType), pointer, private :: obsDatum => null() + type(ObsDataType), pointer, private :: obsDatum => null() !< observation Datum contains ! -- Public procedures - procedure, public :: ResetCurrent + procedure, public :: ResetCurrentValue procedure, public :: WriteTo procedure, public :: AddObsIndex procedure, public :: ResetObsIndex @@ -77,29 +80,28 @@ module ObserveModule type :: ObsDataType ! -- Public members - character(len=LENOBSTYPE), public :: ObsTypeID = '' - logical, public :: Cumulative = .false. - procedure(ProcessIdSub), nopass, pointer, public :: ProcessIdPtr => null() + character(len=LENOBSTYPE), public :: ObsTypeID = '' !< observation type id + logical, public :: Cumulative = .false. !< logical indicating if observations should be summed + procedure(ProcessIdSub), nopass, pointer, public :: ProcessIdPtr => null() !< process id pointer end type ObsDataType abstract interface + + !> @ brief Process user-provided IDstring + !! + !! Subroutine that processes the user-provided IDstring, which identifies + !! the grid location or model feature to be observed. + !! + !< subroutine ProcessIdSub(obsrv, dis, inunitobs, iout) -! ************************************************************************** -! ProcessIdSub -- A procedure that implements this subroutine processes the -! user-provided IDstring, which identifies the grid location or model -! feature to be observed. -! ************************************************************************** -! -! SPECIFICATIONS: -! -------------------------------------------------------------------------- use KindModule, only: DP, I4B import :: ObserveType import :: DisBaseType ! -- dummy - type(ObserveType), intent(inout) :: obsrv - class(DisBaseType), intent(in) :: dis - integer(I4B), intent(in) :: inunitobs - integer(I4B), intent(in) :: iout + type(ObserveType), intent(inout) :: obsrv !< observation type + class(DisBaseType), intent(in) :: dis !< discretization object + integer(I4B), intent(in) :: inunitobs !< observation input file unit + integer(I4B), intent(in) :: iout !< model list file unit end subroutine ProcessIdSub end interface @@ -107,37 +109,37 @@ end subroutine ProcessIdSub ! Procedures bound to ObserveType - subroutine ResetCurrent(this) -! ************************************************************************** -! ResetCurrent -- Reset "current" value. -! ************************************************************************** -! -! SPECIFICATIONS: -! -------------------------------------------------------------------------- + !> @ brief Reset current observation value + !! + !! Subroutine to reset the current observation value. + !! + !< + subroutine ResetCurrentValue(this) ! -- dummy class(ObserveType), intent(inout) :: this ! ! -- Reset current value to zero. this%CurrentTimeStepEndValue = DZERO + ! + ! -- return return - end subroutine ResetCurrent + end subroutine ResetCurrentValue + !> @ brief Write observation input data + !! + !! Subroutine to write observation input data to a table in the model + !! list file. + !! + !< subroutine WriteTo(this, obstab, btagfound, fnamein) -! ************************************************************************** -! WriteTo -- Write information about this observation to table in list file. -! ************************************************************************** -! -! SPECIFICATIONS: -! -------------------------------------------------------------------------- ! -- dummy class(ObserveType), intent(inout) :: this - type(TableType), intent(inout) :: obstab - character(len=*), intent(in) :: btagfound - character(len=*), intent(in) :: fnamein + type(TableType), intent(inout) :: obstab !< observation table + character(len=*), intent(in) :: btagfound !< logical indicating if boundname was found + character(len=*), intent(in) :: fnamein !< observation input file name ! -- local character(len=12) :: tag character(len=80) :: fnameout - ! -- formats ! ! -- write btagfound to tag if (len_trim(btagfound) > 12) then @@ -164,13 +166,12 @@ subroutine WriteTo(this, obstab, btagfound, fnamein) return end subroutine WriteTo + !> @ brief Reset a observation index + !! + !! Subroutine to reset the observation index count and array. + !! + !< subroutine ResetObsIndex(this) -! ************************************************************************** -! ResetObsIndex -- Reset the observation index count and array. -! ************************************************************************** -! -! SPECIFICATIONS: -! -------------------------------------------------------------------------- ! -- dummy class(ObserveType), intent(inout) :: this ! @@ -189,19 +190,18 @@ subroutine ResetObsIndex(this) return end subroutine ResetObsIndex + !> @ brief Add a observation index + !! + !! Subroutine to add the observation index to the observation index + !! array (indxbnds). The observation index count (indxbnds_count) is + !! also incremented by one and the observation index array is + !! expanded, if necessary. + !! + !< subroutine AddObsIndex(this, indx) -! ************************************************************************** -! AddObsIndex -- Add the observation index to the observation index array -! (indbnds). The observation index count (indxbnds_count) -! is also incremented by one and the observation index array -! is expanded, if necessary. -! ************************************************************************** -! -! SPECIFICATIONS: -! -------------------------------------------------------------------------- ! -- dummy class(ObserveType), intent(inout) :: this - integer(I4B), intent(in) :: indx + integer(I4B), intent(in) :: indx !< observation index ! ! -- Increment the index count this%indxbnds_count = this%indxbnds_count + 1 @@ -216,13 +216,12 @@ subroutine AddObsIndex(this, indx) return end subroutine AddObsIndex + !> @ brief Deallocate a observation + !! + !! Subroutine to deallocated a observation (ObserveType). + !! + !< subroutine da(this) -! ************************************************************************** -! da -- destroy observation -! ************************************************************************** -! -! SPECIFICATIONS: -! -------------------------------------------------------------------------- ! -- dummy class(ObserveType), intent(inout) :: this if (allocated(this%indxbnds)) then @@ -235,27 +234,30 @@ end subroutine da ! Non-type-bound procedures + !> @ brief Construct a new ObserveType + !! + !! Subroutine to construct and return an ObserveType object based + !! on the contents of defLine. + !! + !< subroutine ConstructObservation(newObservation, defLine, numunit, & formatted, indx, obsData, inunit) -! ************************************************************************** -! ConstructObservation -- Construct and return an ObserveType object based -! on the contents of defLine. -! ************************************************************************** -! -! SPECIFICATIONS: -! -------------------------------------------------------------------------- ! -- dummy variables - type(ObserveType), pointer :: newObservation - character(len=*), intent(in) :: defLine - integer(I4B), intent(in) :: numunit ! Output unit number - logical, intent(in) :: formatted ! Formatted output? - integer(I4B), intent(in) :: indx ! Index in ObsOutput array - type(ObsDataType), dimension(:), pointer, intent(in) :: obsData - integer(I4B), intent(in) :: inunit + type(ObserveType), pointer :: newObservation !< new ObserveType + character(len=*), intent(in) :: defLine !< string with observation data + integer(I4B), intent(in) :: numunit !< Output unit number + logical, intent(in) :: formatted !< logical indicating if formatted output will be written + integer(I4B), intent(in) :: indx !< Index in ObsOutput array + type(ObsDataType), dimension(:), pointer, intent(in) :: obsData !< obsData type + integer(I4B), intent(in) :: inunit !< observation input file unit ! -- local real(DP) :: r - integer(I4B) :: i, icol, iout, istart, istop, n - ! -------------------------------------------------------------------------- + integer(I4B) :: i + integer(I4B) :: icol + integer(I4B) :: iout + integer(I4B) :: istart + integer(I4B) :: istop + integer(I4B) :: n ! ! -- initialize iout = 0 @@ -302,12 +304,20 @@ subroutine ConstructObservation(newObservation, defLine, numunit, & newObservation%FormattedOutput = formatted newObservation%IndxObsOutput = indx ! + ! -- return return end subroutine ConstructObservation + !> @ brief Cast a object as a ObserveType + !! + !! Function to cast an object as a ObserveType object. + !! + !< function CastAsObserveType(obj) result(res) - class(*), pointer, intent(inout) :: obj - type(ObserveType), pointer :: res + ! -- dummy + class(*), pointer, intent(inout) :: obj !< object + ! -- return + type(ObserveType), pointer :: res !< returned ObserveType object ! res => null() if (.not. associated(obj)) return @@ -316,33 +326,48 @@ function CastAsObserveType(obj) result(res) type is (ObserveType) res => obj end select + ! + ! -- return return end function CastAsObserveType + !> @ brief Add a ObserveType to a list + !! + !! Subroutine to add a ObserveType to a list. + !! + !< subroutine AddObsToList(list, obs) ! -- dummy - type(ListType), intent(inout) :: list - type(ObserveType), pointer, intent(inout) :: obs + type(ListType), intent(inout) :: list !< ObserveType list + type(ObserveType), pointer, intent(inout) :: obs !< ObserveType ! -- local class(*), pointer :: obj ! obj => obs call list%Add(obj) ! + ! -- return return end subroutine AddObsToList + !> @ brief Get an ObserveType from a list + !! + !! Function to get an ObserveType from a list. + !! + !< function GetObsFromList(list, idx) result(res) ! -- dummy - type(ListType), intent(inout) :: list - integer(I4B), intent(in) :: idx - type(ObserveType), pointer :: res + type(ListType), intent(inout) :: list !< ObserveType list + integer(I4B), intent(in) :: idx !< ObserveType list index + ! -- return + type(ObserveType), pointer :: res !< returned ObserveType ! -- local class(*), pointer :: obj ! obj => list%GetItem(idx) res => CastAsObserveType(obj) ! + ! -- return return end function GetObsFromList From dd345e8b1794dcb57d74f64244cd44074e656f41 Mon Sep 17 00:00:00 2001 From: Hofer-Julian <30049909+Hofer-Julian@users.noreply.github.com> Date: Tue, 30 May 2023 16:05:55 +0200 Subject: [PATCH 087/123] Add `get_value_ptr` (#1209) * Add `get_value_ptr` This way the modflow API will be one step closer to BMI conformity * Add xmipy to dependencies * Add error message for unsupported type * Start using current develop of xmipy again * Add get_value * Add set_value * Run fprettify --- environment.yml | 1 + srcbmi/mf6bmi.f90 | 136 +++++++++++++++++++++++++++++++++++++++++ srcbmi/mf6bmiError.f90 | 3 + 3 files changed, 140 insertions(+) diff --git a/environment.yml b/environment.yml index 7a2294d3884..12633dc18a5 100644 --- a/environment.yml +++ b/environment.yml @@ -17,6 +17,7 @@ dependencies: - pip: - git+https://github.com/modflowpy/flopy.git - git+https://github.com/modflowpy/pymake.git + - git+https://github.com/Deltares/xmipy.git - git+https://github.com/MODFLOW-USGS/modflowapi.git - modflow-devtools - pytest diff --git a/srcbmi/mf6bmi.f90 b/srcbmi/mf6bmi.f90 index e963f96cc4a..c191ba937e5 100644 --- a/srcbmi/mf6bmi.f90 +++ b/srcbmi/mf6bmi.f90 @@ -593,6 +593,98 @@ function get_value_string(c_var_address, c_arr_ptr) result(bmi_status) & end function get_value_string + !> @brief Copy the value of a variable into the array + !! + !! The copied variable is located at @p c_var_address. The caller should + !! provide @p c_arr_ptr pointing to an array of the proper shape (the + !! BMI function get_var_shape() can be used to create it). Multi-dimensional + !! arrays are supported. + !< + function get_value(c_var_address, c_arr_ptr) result(bmi_status) & + bind(C, name="get_value") + !DIR$ ATTRIBUTES DLLEXPORT :: get_value + ! -- modules + use ConstantsModule, only: LENMEMTYPE + ! -- dummy variables + character(kind=c_char), intent(in) :: c_var_address(*) !< memory address string of the variable + type(c_ptr), intent(inout) :: c_arr_ptr !< pointer to the array + integer(kind=c_int) :: bmi_status !< BMI status code + ! -- local variables + character(len=LENMEMPATH) :: mem_path + character(len=LENMEMTYPE) :: mem_type + character(len=LENVARNAME) :: var_name + logical(LGP) :: valid + + bmi_status = BMI_SUCCESS + + call split_address(c_var_address, mem_path, var_name, valid) + if (.not. valid) then + bmi_status = BMI_FAILURE + return + end if + + call get_mem_type(var_name, mem_path, mem_type) + + if (index(mem_type, "DOUBLE") /= 0) then + bmi_status = get_value_double(c_var_address, c_arr_ptr) + else if (index(mem_type, "INTEGER") /= 0) then + bmi_status = get_value_int(c_var_address, c_arr_ptr) + else if (index(mem_type, "STRING") /= 0) then + bmi_status = get_value_string(c_var_address, c_arr_ptr) + else + write (bmi_last_error, fmt_unsupported_type) trim(var_name) + call report_bmi_error(bmi_last_error) + bmi_status = BMI_FAILURE + return + end if + + end function get_value + + !> @brief Get a pointer to an array + !! + !! The array is located at @p c_var_address. There is no copying of data involved. + !! Multi-dimensional arrays are supported and the get_var_rank() function + !! can be used to get the variable's dimensionality, and get_var_shape() for + !! its shape. + !< + function get_value_ptr(c_var_address, c_arr_ptr) result(bmi_status) & + bind(C, name="get_value_ptr") + !DIR$ ATTRIBUTES DLLEXPORT :: get_value_ptr + ! -- modules + use ConstantsModule, only: LENMEMTYPE + ! -- dummy variables + character(kind=c_char), intent(in) :: c_var_address(*) !< memory address string of the variable + type(c_ptr), intent(inout) :: c_arr_ptr !< pointer to the array + integer(kind=c_int) :: bmi_status !< BMI status code + ! -- local variables + character(len=LENMEMPATH) :: mem_path + character(len=LENMEMTYPE) :: mem_type + character(len=LENVARNAME) :: var_name + logical(LGP) :: valid + + bmi_status = BMI_SUCCESS + + call split_address(c_var_address, mem_path, var_name, valid) + if (.not. valid) then + bmi_status = BMI_FAILURE + return + end if + + call get_mem_type(var_name, mem_path, mem_type) + + if (index(mem_type, "DOUBLE") /= 0) then + bmi_status = get_value_ptr_double(c_var_address, c_arr_ptr) + else if (index(mem_type, "INTEGER") /= 0) then + bmi_status = get_value_ptr_int(c_var_address, c_arr_ptr) + else + write (bmi_last_error, fmt_unsupported_type) trim(var_name) + call report_bmi_error(bmi_last_error) + bmi_status = BMI_FAILURE + return + end if + + end function get_value_ptr + !> @brief Get a pointer to the array of double precision numbers !! !! The array is located at @p c_var_address. There is no copying of data involved. @@ -703,6 +795,50 @@ function get_value_ptr_int(c_var_address, c_arr_ptr) result(bmi_status) & end function get_value_ptr_int + !> @brief Set new values for a given variable + !! + !! The array pointed to by @p c_arr_ptr can have rank equal to 0, 1, or 2 + !! and should have a C-style layout, which is particularly important for + !! rank > 1. + !< + function set_value(c_var_address, c_arr_ptr) result(bmi_status) & + bind(C, name="set_value") + !DIR$ ATTRIBUTES DLLEXPORT :: set_value + ! -- modules + use ConstantsModule, only: LENMEMTYPE + ! -- dummy variables + character(kind=c_char), intent(in) :: c_var_address(*) !< memory address string of the variable + type(c_ptr), intent(inout) :: c_arr_ptr !< pointer to the array + integer(kind=c_int) :: bmi_status !< BMI status code + ! -- local variables + character(len=LENMEMPATH) :: mem_path + character(len=LENMEMTYPE) :: mem_type + character(len=LENVARNAME) :: var_name + logical(LGP) :: valid + + bmi_status = BMI_SUCCESS + + call split_address(c_var_address, mem_path, var_name, valid) + if (.not. valid) then + bmi_status = BMI_FAILURE + return + end if + + call get_mem_type(var_name, mem_path, mem_type) + + if (index(mem_type, "DOUBLE") /= 0) then + bmi_status = set_value_double(c_var_address, c_arr_ptr) + else if (index(mem_type, "INTEGER") /= 0) then + bmi_status = set_value_int(c_var_address, c_arr_ptr) + else + write (bmi_last_error, fmt_unsupported_type) trim(var_name) + call report_bmi_error(bmi_last_error) + bmi_status = BMI_FAILURE + return + end if + + end function set_value + !> @brief Set new values for a variable of type double !! !! The array pointed to by @p c_arr_ptr can have rank equal to 0, 1, or 2 diff --git a/srcbmi/mf6bmiError.f90 b/srcbmi/mf6bmiError.f90 index c337b68f2e7..f1e7121b05a 100644 --- a/srcbmi/mf6bmiError.f90 +++ b/srcbmi/mf6bmiError.f90 @@ -28,6 +28,9 @@ module mf6bmiError character(len=*), parameter :: fmt_unsupported_rank = & !< Unsupported rank, args: variable name "('BMI Error, unsupported rank for variable: & &', a)" + character(len=*), parameter :: fmt_unsupported_type = & !< Unsupported type, args: variable name + "('BMI Error, unsupported type for variable: & + &', a)" character(len=*), parameter :: fmt_invalid_mem_access = & !< Invalid memory access, args: variable name "('Fatal BMI Error, invalid access of memory & &for variable: ', a)" From 7f7bdb2310e9934db6f5ed94242bc6562db95fc4 Mon Sep 17 00:00:00 2001 From: mjreno Date: Wed, 31 May 2023 09:14:52 -0400 Subject: [PATCH 088/123] fix(idm) : optionally log input variables to simulation list file (#1214) (#1227) * log input parameters with tag names instead of internal variable names * add mfsim.nam option to log input variables in sim list file * fprettify * change option to print_input and add more descripting logging * Update doc/mf6io/mf6ivar/dfn/sim-nam.dfn Co-authored-by: langevin-usgs --------- Co-authored-by: mjreno Co-authored-by: langevin-usgs --- doc/mf6io/mf6ivar/dfn/sim-nam.dfn | 8 + src/Utilities/Idm/IdmLogger.f90 | 204 +++++++++++------- src/Utilities/Idm/IdmSimulation.f90 | 27 ++- .../Idm/mf6blockfile/LoadMf6File.f90 | 26 +-- .../Idm/mf6blockfile/StructArray.f90 | 39 ++-- .../Idm/mf6blockfile/StructVector.f90 | 1 + src/Utilities/SimVariables.f90 | 1 + src/mf6core.f90 | 4 +- src/simnamidm.f90 | 18 ++ 9 files changed, 225 insertions(+), 103 deletions(-) diff --git a/doc/mf6io/mf6ivar/dfn/sim-nam.dfn b/doc/mf6io/mf6ivar/dfn/sim-nam.dfn index 5a2e1693c4a..293a092b0d9 100644 --- a/doc/mf6io/mf6ivar/dfn/sim-nam.dfn +++ b/doc/mf6io/mf6ivar/dfn/sim-nam.dfn @@ -33,6 +33,14 @@ optional true longname maximum number of errors description maximum number of errors that will be stored and printed. +block options +name print_input +type keyword +reader urword +optional true +longname print input to listing file +description keyword to activate printing of simulation input summaries to the simulation list file (mfsim.lst). With this keyword, input summaries will be written for those packages that support newer input data model routines. Not all packages are supported yet by the newer input data model routines. + # --------------------- sim nam timing --------------------- block timing diff --git a/src/Utilities/Idm/IdmLogger.f90 b/src/Utilities/Idm/IdmLogger.f90 index 43f5ce53ee4..de88f8cba14 100644 --- a/src/Utilities/Idm/IdmLogger.f90 +++ b/src/Utilities/Idm/IdmLogger.f90 @@ -7,6 +7,8 @@ module IdmLoggerModule use KindModule, only: DP, LGP, I4B + use SimVariablesModule, only: iparamlog + use ConstantsModule, only: LINELENGTH implicit none private @@ -31,7 +33,7 @@ subroutine idm_log_header(component, subcomponent, iout) character(len=*), intent(in) :: subcomponent !< subcomponent name integer(I4B), intent(in) :: iout - if (iout > 0) then + if (iparamlog > 0 .and. iout > 0) then write (iout, '(1x,a)') 'Loading input for '//trim(component)//& &'/'//trim(subcomponent) end if @@ -42,9 +44,11 @@ end subroutine idm_log_header subroutine idm_log_close(component, subcomponent, iout) character(len=*), intent(in) :: component !< component name character(len=*), intent(in) :: subcomponent !< subcomponent name - integer(I4B) :: iout + integer(I4B), intent(in) :: iout - write (iout, '(1x,a,/)') 'Loading input complete...' + if (iparamlog > 0 .and. iout > 0) then + write (iout, '(1x,a,/)') 'Loading input complete...' + end if end subroutine idm_log_close !> @brief Log type specific information logical @@ -53,20 +57,36 @@ subroutine idm_log_var_logical(p_mem, varname, mempath, iout) logical(LGP), intent(in) :: p_mem !< logical scalar character(len=*), intent(in) :: varname !< variable name character(len=*), intent(in) :: mempath !< variable memory path - integer(I4B) :: iout + integer(I4B), intent(in) :: iout + character(len=LINELENGTH) :: description - write (iout, '(3x,a, " = ", l1)') trim(varname), p_mem + if (iparamlog > 0 .and. iout > 0) then + description = 'Logical detected' + write (iout, '(3x, a, ": ", a, " = ", l1)') & + trim(description), trim(varname), p_mem + end if end subroutine idm_log_var_logical !> @brief Log type specific information integer !< - subroutine idm_log_var_int(p_mem, varname, mempath, iout) + subroutine idm_log_var_int(p_mem, varname, mempath, datatype, iout) integer(I4B), intent(in) :: p_mem !< int scalar character(len=*), intent(in) :: varname !< variable name character(len=*), intent(in) :: mempath !< variable memory path - integer(I4B) :: iout + character(len=*), intent(in) :: datatype !< variable data type + integer(I4B), intent(in) :: iout + character(len=LINELENGTH) :: description - write (iout, '(3x,a, " = ", i0)') trim(varname), p_mem + if (iparamlog > 0 .and. iout > 0) then + if (datatype == 'KEYWORD') then + description = 'Keyword detected' + write (iout, '(3x, a, ": ", a)') trim(description), trim(varname) + else + description = 'Integer detected' + write (iout, '(3x, a, ": ", a, " = ", i0)') & + trim(description), trim(varname), p_mem + end if + end if end subroutine idm_log_var_int !> @brief Log type specific information int1d @@ -75,18 +95,23 @@ subroutine idm_log_var_int1d(p_mem, varname, mempath, iout) integer(I4B), dimension(:), contiguous, intent(in) :: p_mem !< 1d int array character(len=*), intent(in) :: varname !< variable name character(len=*), intent(in) :: mempath !< variable memory path - integer(I4B) :: iout + integer(I4B), intent(in) :: iout integer(I4B) :: min_val, max_val + character(len=LINELENGTH) :: description - min_val = minval(p_mem) - max_val = maxval(p_mem) - if (min_val == max_val) then - write (iout, '(3x,a, " = ", i0)') trim(varname), min_val - else - write (iout, '(3x, a, a, i0, a, i0)') & - trim(varname), & - ' = variable 1D integer array ranging from ', & - min_val, ' to ', max_val + if (iparamlog > 0 .and. iout > 0) then + min_val = minval(p_mem) + max_val = maxval(p_mem) + if (min_val == max_val) then + description = 'Integer 1D constant array detected' + write (iout, '(3x, a, ": ", a, " = ", i0)') & + trim(description), trim(varname), min_val + else + description = 'Integer 1D array detected' + write (iout, '(3x, a, ": ", a, a, i0, a, i0)') & + trim(description), trim(varname), & + ' ranges from ', min_val, ' to ', max_val + end if end if end subroutine idm_log_var_int1d @@ -96,18 +121,23 @@ subroutine idm_log_var_int2d(p_mem, varname, mempath, iout) integer(I4B), dimension(:, :), contiguous, intent(in) :: p_mem !< 2d int array character(len=*), intent(in) :: varname !< variable name character(len=*), intent(in) :: mempath !< variable memory path - integer(I4B) :: iout + integer(I4B), intent(in) :: iout integer(I4B) :: min_val, max_val + character(len=LINELENGTH) :: description - min_val = minval(p_mem) - max_val = maxval(p_mem) - if (min_val == max_val) then - write (iout, '(3x,a, " = ", i0)') trim(varname), min_val - else - write (iout, '(3x, a, a, i0, a, i0)') & - trim(varname), & - ' = variable 2D integer array ranging from ', & - min_val, ' to ', max_val + if (iparamlog > 0 .and. iout > 0) then + min_val = minval(p_mem) + max_val = maxval(p_mem) + if (min_val == max_val) then + description = 'Integer 2D constant array detected' + write (iout, '(3x, a, ": ", a, " = ", i0)') & + trim(description), trim(varname), min_val + else + description = 'Integer 2D array detected' + write (iout, '(3x, a, ": ", a, a, i0, a, i0)') & + trim(description), trim(varname), & + ' ranges from ', min_val, ' to ', max_val + end if end if end subroutine idm_log_var_int2d @@ -117,18 +147,23 @@ subroutine idm_log_var_int3d(p_mem, varname, mempath, iout) integer(I4B), dimension(:, :, :), contiguous, intent(in) :: p_mem !< 3d int array character(len=*), intent(in) :: varname !< variable name character(len=*), intent(in) :: mempath !< variable memory path - integer(I4B) :: iout + integer(I4B), intent(in) :: iout integer(I4B) :: min_val, max_val + character(len=LINELENGTH) :: description - min_val = minval(p_mem) - max_val = maxval(p_mem) - if (min_val == max_val) then - write (iout, '(3x,a, " = ", i0)') trim(varname), min_val - else - write (iout, '(3x, a, a, i0, a, i0)') & - trim(varname), & - ' = variable 3D integer array ranging from ', & - min_val, ' to ', max_val + if (iparamlog > 0 .and. iout > 0) then + min_val = minval(p_mem) + max_val = maxval(p_mem) + if (min_val == max_val) then + description = 'Integer 3D constant array detected' + write (iout, '(3x, a, ": ", a, " = ", i0)') & + trim(description), trim(varname), min_val + else + description = 'Integer 3D array detected' + write (iout, '(3x, a, ": ", a, a, i0, a, i0)') & + trim(description), trim(varname), & + ' ranges from ', min_val, ' to ', max_val + end if end if end subroutine idm_log_var_int3d @@ -138,9 +173,14 @@ subroutine idm_log_var_dbl(p_mem, varname, mempath, iout) real(DP), intent(in) :: p_mem !< dbl scalar character(len=*), intent(in) :: varname !< variable name character(len=*), intent(in) :: mempath !< variable memory path - integer(I4B) :: iout + integer(I4B), intent(in) :: iout + character(len=LINELENGTH) :: description - write (iout, '(3x,a, " = ", G0)') trim(varname), p_mem + if (iparamlog > 0 .and. iout > 0) then + description = 'Double detected' + write (iout, '(3x, a, ": ", a, " = ", G0)') & + trim(description), trim(varname), p_mem + end if end subroutine idm_log_var_dbl !> @brief Log type specific information dbl1d @@ -149,18 +189,23 @@ subroutine idm_log_var_dbl1d(p_mem, varname, mempath, iout) real(DP), dimension(:), contiguous, intent(in) :: p_mem !< 1d real array character(len=*), intent(in) :: varname !< variable name character(len=*), intent(in) :: mempath !< variable memory path - integer(I4B) :: iout + integer(I4B), intent(in) :: iout real(DP) :: min_val, max_val + character(len=LINELENGTH) :: description - min_val = minval(p_mem) - max_val = maxval(p_mem) - if (min_val == max_val) then - write (iout, '(3x,a, " = ", G0)') trim(varname), min_val - else - write (iout, '(3x, a, a, G0, a, G0)') & - trim(varname), & - ' = variable 1D double precision array ranging from ', & - min_val, ' to ', max_val + if (iparamlog > 0 .and. iout > 0) then + min_val = minval(p_mem) + max_val = maxval(p_mem) + if (min_val == max_val) then + description = 'Double precision 1D constant array detected' + write (iout, '(3x, a, ": ", a, " = ", G0)') & + trim(description), trim(varname), min_val + else + description = 'Double precision 1D array detected' + write (iout, '(3x, a, ": ", a, a, G0, a, G0)') & + trim(description), trim(varname), & + ' ranges from ', min_val, ' to ', max_val + end if end if end subroutine idm_log_var_dbl1d @@ -170,18 +215,23 @@ subroutine idm_log_var_dbl2d(p_mem, varname, mempath, iout) real(DP), dimension(:, :), contiguous, intent(in) :: p_mem !< 2d dbl array character(len=*), intent(in) :: varname !< variable name character(len=*), intent(in) :: mempath !< variable memory path - integer(I4B) :: iout + integer(I4B), intent(in) :: iout real(DP) :: min_val, max_val + character(len=LINELENGTH) :: description - min_val = minval(p_mem) - max_val = maxval(p_mem) - if (min_val == max_val) then - write (iout, '(3x,a, " = ", G0)') trim(varname), min_val - else - write (iout, '(3x, a, a, G0, a, G0)') & - trim(varname), & - ' = variable 2D double precision array ranging from ', & - min_val, ' to ', max_val + if (iparamlog > 0 .and. iout > 0) then + min_val = minval(p_mem) + max_val = maxval(p_mem) + if (min_val == max_val) then + description = 'Double precision 2D constant array detected' + write (iout, '(3x, a, ": ", a, " = ", G0)') & + trim(description), trim(varname), min_val + else + description = 'Double precision 2D array detected' + write (iout, '(3x, a, ": ", a, a, G0, a, G0)') & + trim(description), trim(varname), & + ' ranges from ', min_val, ' to ', max_val + end if end if end subroutine idm_log_var_dbl2d @@ -191,18 +241,23 @@ subroutine idm_log_var_dbl3d(p_mem, varname, mempath, iout) real(DP), dimension(:, :, :), contiguous, intent(in) :: p_mem !< 3d dbl array character(len=*), intent(in) :: varname !< variable name character(len=*), intent(in) :: mempath !< variable memory path - integer(I4B) :: iout + integer(I4B), intent(in) :: iout real(DP) :: min_val, max_val + character(len=LINELENGTH) :: description - min_val = minval(p_mem) - max_val = maxval(p_mem) - if (min_val == max_val) then - write (iout, '(3x,a, " = ", G0)') trim(varname), min_val - else - write (iout, '(3x, a, a, G0, a, G0)') & - trim(varname), & - ' = variable 3D double precision array ranging from ', & - min_val, ' to ', max_val + if (iparamlog > 0 .and. iout > 0) then + min_val = minval(p_mem) + max_val = maxval(p_mem) + if (min_val == max_val) then + description = 'Double precision 3D constant array detected' + write (iout, '(3x, a, ": ", a, " = ", G0)') & + trim(description), trim(varname), min_val + else + description = 'Double precision 3D array detected' + write (iout, '(3x, a, ": ", a, a, G0, a, G0)') & + trim(description), trim(varname), & + ' ranges from ', min_val, ' to ', max_val + end if end if end subroutine idm_log_var_dbl3d @@ -212,9 +267,14 @@ subroutine idm_log_var_str(p_mem, varname, mempath, iout) character(len=*), intent(in) :: p_mem !< pointer to str scalar character(len=*), intent(in) :: varname !< variable name character(len=*), intent(in) :: mempath !< variable memory path - integer(I4B) :: iout + integer(I4B), intent(in) :: iout + character(len=LINELENGTH) :: description - write (iout, '(3x,a, " = ", a)') trim(varname), trim(p_mem) + if (iparamlog > 0 .and. iout > 0) then + description = 'String detected' + write (iout, '(3x, a, ": ", a, " = ", a)') & + trim(description), trim(varname), trim(p_mem) + end if end subroutine idm_log_var_str end module IdmLoggerModule diff --git a/src/Utilities/Idm/IdmSimulation.f90 b/src/Utilities/Idm/IdmSimulation.f90 index 523029695ac..ecb8da877a7 100644 --- a/src/Utilities/Idm/IdmSimulation.f90 +++ b/src/Utilities/Idm/IdmSimulation.f90 @@ -81,6 +81,9 @@ subroutine set_default_value(intvar, mf6varname) case ('MXITER') intvar = 1 ! + case ('PRINT_INPUT') + intvar = 0 + ! case default write (errmsg, '(a,a)') & 'IdmSimulation set_default_value unhandled variable: ', & @@ -182,11 +185,30 @@ subroutine load_models(model_loadmask, iout) return end subroutine load_models + function input_param_log() result(paramlog) + use MemoryHelperModule, only: create_mem_path + use MemoryManagerModule, only: mem_setptr + use SimVariablesModule, only: idm_context + character(len=LENMEMPATH) :: simnam_mempath + integer(I4B) :: paramlog + integer(I4B), pointer :: p + ! + ! -- read and set input value of PRINT_INPUT + simnam_mempath = create_mem_path('SIM', 'NAM', idm_context) + call mem_setptr(p, 'PRINT_INPUT', simnam_mempath) + ! + paramlog = p + ! + ! -- return + return + end function input_param_log + !> @brief MODFLOW 6 mfsim.nam input load routine !< - subroutine simnam_load() + subroutine simnam_load(paramlog) use SimVariablesModule, only: simfile use GenericUtilitiesModule, only: sim_message + integer(I4B), intent(inout) :: paramlog integer(I4B) :: inunit logical :: lexist character(len=LINELENGTH) :: line @@ -211,6 +233,9 @@ subroutine simnam_load() ! -- allocate any unallocated simnam params call simnam_allocate() ! + ! -- read and set input parameter logging keyword + paramlog = input_param_log() + ! ! -- memload summary info call simnam_load_dim() ! diff --git a/src/Utilities/Idm/mf6blockfile/LoadMf6File.f90 b/src/Utilities/Idm/mf6blockfile/LoadMf6File.f90 index 9bce437132e..91be0a0c4e3 100644 --- a/src/Utilities/Idm/mf6blockfile/LoadMf6File.f90 +++ b/src/Utilities/Idm/mf6blockfile/LoadMf6File.f90 @@ -405,7 +405,7 @@ subroutine parse_structarray_block(parser, mf6_input, iblock, mshape, & end if ! call struct_array%mem_create_vector(icol, 'INTEGER', & - varname, & + varname, varname, & mf6_input%mempath, '', & .false.) ! @@ -430,8 +430,8 @@ subroutine parse_structarray_block(parser, mf6_input, iblock, mshape, & ! ! -- allocate variable in memory manager call struct_array%mem_create_vector(icol, idt%datatype, idt%mf6varname, & - mf6_input%mempath, idt%shape, & - idt%preserve_case) + idt%tagname, mf6_input%mempath, & + idt%shape, idt%preserve_case) end do ! ! -- read the structured array @@ -454,7 +454,7 @@ subroutine load_keyword_type(parser, idt, memoryPath, iout) integer(I4B), pointer :: intvar call mem_allocate(intvar, idt%mf6varname, memoryPath) intvar = 1 - call idm_log_var(intvar, idt%mf6varname, memoryPath, iout) + call idm_log_var(intvar, idt%tagname, memoryPath, idt%datatype, iout) return end subroutine load_keyword_type @@ -470,7 +470,7 @@ subroutine load_string_type(parser, idt, memoryPath, iout) ilen = LINELENGTH call mem_allocate(cstr, ilen, idt%mf6varname, memoryPath) call parser%GetString(cstr, (.not. idt%preserve_case)) - call idm_log_var(cstr, idt%mf6varname, memoryPath, iout) + call idm_log_var(cstr, idt%tagname, memoryPath, iout) return end subroutine load_string_type @@ -484,7 +484,7 @@ subroutine load_integer_type(parser, idt, memoryPath, iout) integer(I4B), pointer :: intvar call mem_allocate(intvar, idt%mf6varname, memoryPath) intvar = parser%GetInteger() - call idm_log_var(intvar, idt%mf6varname, memoryPath, iout) + call idm_log_var(intvar, idt%tagname, memoryPath, idt%datatype, iout) return end subroutine load_integer_type @@ -531,7 +531,7 @@ subroutine load_integer1d_type(parser, idt, memoryPath, mshape, iout) end if ! log information on the loaded array to the list file - call idm_log_var(int1d, idt%mf6varname, memoryPath, iout) + call idm_log_var(int1d, idt%tagname, memoryPath, iout) return end subroutine load_integer1d_type @@ -574,7 +574,7 @@ subroutine load_integer2d_type(parser, idt, memoryPath, mshape, iout) end if ! log information on the loaded array to the list file - call idm_log_var(int2d, idt%mf6varname, memoryPath, iout) + call idm_log_var(int2d, idt%tagname, memoryPath, iout) return end subroutine load_integer2d_type @@ -622,7 +622,7 @@ subroutine load_integer3d_type(parser, idt, memoryPath, mshape, iout) end if ! log information on the loaded array to the list file - call idm_log_var(int3d, idt%mf6varname, memoryPath, iout) + call idm_log_var(int3d, idt%tagname, memoryPath, iout) return end subroutine load_integer3d_type @@ -637,7 +637,7 @@ subroutine load_double_type(parser, idt, memoryPath, iout) real(DP), pointer :: dblvar call mem_allocate(dblvar, idt%mf6varname, memoryPath) dblvar = parser%GetDouble() - call idm_log_var(dblvar, idt%mf6varname, memoryPath, iout) + call idm_log_var(dblvar, idt%tagname, memoryPath, iout) return end subroutine load_double_type @@ -683,7 +683,7 @@ subroutine load_double1d_type(parser, idt, memoryPath, mshape, iout) end if ! log information on the loaded array to the list file - call idm_log_var(dbl1d, idt%mf6varname, memoryPath, iout) + call idm_log_var(dbl1d, idt%tagname, memoryPath, iout) return end subroutine load_double1d_type @@ -726,7 +726,7 @@ subroutine load_double2d_type(parser, idt, memoryPath, mshape, iout) end if ! log information on the loaded array to the list file - call idm_log_var(dbl2d, idt%mf6varname, memoryPath, iout) + call idm_log_var(dbl2d, idt%tagname, memoryPath, iout) return end subroutine load_double2d_type @@ -774,7 +774,7 @@ subroutine load_double3d_type(parser, idt, memoryPath, mshape, iout) end if ! log information on the loaded array to the list file - call idm_log_var(dbl3d, idt%mf6varname, memoryPath, iout) + call idm_log_var(dbl3d, idt%tagname, memoryPath, iout) return end subroutine load_double3d_type diff --git a/src/Utilities/Idm/mf6blockfile/StructArray.f90 b/src/Utilities/Idm/mf6blockfile/StructArray.f90 index 05823f9bb4a..5dca7d8becd 100644 --- a/src/Utilities/Idm/mf6blockfile/StructArray.f90 +++ b/src/Utilities/Idm/mf6blockfile/StructArray.f90 @@ -99,12 +99,13 @@ end subroutine destructStructArray !> @brief create new vector in StructArrayType !< - subroutine mem_create_vector(this, icol, vartype, name, memoryPath, & + subroutine mem_create_vector(this, icol, vartype, name, tagname, memoryPath, & varname_shape, preserve_case) class(StructArrayType) :: this !< StructArrayType integer(I4B), intent(in) :: icol !< column to create character(len=*), intent(in) :: vartype !< type of column to create character(len=*), intent(in) :: name !< name of the column to create + character(len=*), intent(in) :: tagname character(len=*), intent(in) :: memoryPath !< memory path for storing the vector character(len=*), intent(in) :: varname_shape !< shape logical(LGP), optional, intent(in) :: preserve_case !< flag indicating whether or not to preserve case @@ -124,8 +125,8 @@ subroutine mem_create_vector(this, icol, vartype, name, memoryPath, & allocate (intvector) ! ! -- initialize StructVector and add to StructArray - call this%add_vector_intvector(name, memoryPath, varname_shape, icol, & - intvector) + call this%add_vector_intvector(name, tagname, memoryPath, varname_shape, & + icol, intvector) ! case ('INTEGER') ! @@ -143,7 +144,7 @@ subroutine mem_create_vector(this, icol, vartype, name, memoryPath, & end do ! ! -- initialize StructVector and add to StructArray - call this%add_vector_int1d(name, memoryPath, icol, int1d) + call this%add_vector_int1d(name, tagname, memoryPath, icol, int1d) ! case ('DOUBLE') ! @@ -153,7 +154,7 @@ subroutine mem_create_vector(this, icol, vartype, name, memoryPath, & dbl1d(j) = DNODATA end do ! - call this%add_vector_dbl1d(name, memoryPath, icol, dbl1d) + call this%add_vector_dbl1d(name, tagname, memoryPath, icol, dbl1d) ! case ('STRING', 'KEYWORD') ! @@ -167,7 +168,7 @@ subroutine mem_create_vector(this, icol, vartype, name, memoryPath, & charstr1d(j) = '' end do ! - call this%add_vector_charstr1d(name, memoryPath, icol, charstr1d, & + call this%add_vector_charstr1d(name, tagname, memoryPath, icol, charstr1d, & varname_shape, preserve_case) end select @@ -176,9 +177,10 @@ end subroutine mem_create_vector !> @brief add int1d to StructArrayType !< - subroutine add_vector_int1d(this, varname, memoryPath, icol, int1d) + subroutine add_vector_int1d(this, varname, tagname, memoryPath, icol, int1d) class(StructArrayType) :: this !< StructArrayType character(len=*), intent(in) :: varname !< name of the variable + character(len=*), intent(in) :: tagname character(len=*), intent(in) :: memoryPath !< memory path to vector integer(I4B), intent(in) :: icol !< column of the vector integer(I4B), dimension(:), pointer, contiguous, intent(in) :: int1d !< vector to add @@ -186,6 +188,7 @@ subroutine add_vector_int1d(this, varname, memoryPath, icol, int1d) ! ! -- initialize StructVectorType sv%varname = varname + sv%tagname = tagname sv%shapevar = '' sv%mempath = memoryPath sv%memtype = 1 @@ -207,9 +210,10 @@ end subroutine add_vector_int1d !> @brief add dbl1d to StructArrayType !< - subroutine add_vector_dbl1d(this, varname, memoryPath, icol, dbl1d) + subroutine add_vector_dbl1d(this, varname, tagname, memoryPath, icol, dbl1d) class(StructArrayType) :: this !< StructArrayType character(len=*), intent(in) :: varname !< name of the variable + character(len=*), intent(in) :: tagname character(len=*), intent(in) :: memoryPath !< memory path to vector integer(I4B), intent(in) :: icol !< column of the vector real(DP), dimension(:), pointer, contiguous, intent(in) :: dbl1d !< vector to add @@ -217,6 +221,7 @@ subroutine add_vector_dbl1d(this, varname, memoryPath, icol, dbl1d) ! ! -- initialize StructVectorType sv%varname = varname + sv%tagname = tagname sv%shapevar = '' sv%mempath = memoryPath sv%memtype = 2 @@ -232,11 +237,12 @@ end subroutine add_vector_dbl1d !> @brief add charstr1d to StructArrayType !< - subroutine add_vector_charstr1d(this, varname, memoryPath, icol, charstr1d, & - varname_shape, preserve_case) + subroutine add_vector_charstr1d(this, varname, tagname, memoryPath, icol, & + charstr1d, varname_shape, preserve_case) class(StructArrayType) :: this !< StructArrayType integer(I4B), intent(in) :: icol !< column of the vector character(len=*), intent(in) :: varname !< name of the variable + character(len=*), intent(in) :: tagname character(len=*), intent(in) :: memoryPath !< memory path to vector type(CharacterStringType), dimension(:), pointer, contiguous, intent(in) :: & charstr1d !< vector to add @@ -246,6 +252,7 @@ subroutine add_vector_charstr1d(this, varname, memoryPath, icol, charstr1d, & ! ! -- initialize StructVectorType sv%varname = varname + sv%tagname = tagname sv%shapevar = varname_shape sv%mempath = memoryPath sv%memtype = 3 @@ -268,10 +275,11 @@ end subroutine add_vector_charstr1d !> @brief add STLVecInt to StructArrayType !< - subroutine add_vector_intvector(this, varname, memoryPath, varname_shape, & - icol, intvector) + subroutine add_vector_intvector(this, varname, tagname, memoryPath, & + varname_shape, icol, intvector) class(StructArrayType) :: this !< StructArrayType character(len=*), intent(in) :: varname !< name of the variable + character(len=*), intent(in) :: tagname character(len=*), intent(in) :: memoryPath !< memory path to vector character(len=*), intent(in) :: varname_shape !< shape of variable integer(I4B), intent(in) :: icol !< column of the vector @@ -286,6 +294,7 @@ subroutine add_vector_intvector(this, varname, memoryPath, varname_shape, & ! ! -- initialize StructVectorType sv%varname = varname + sv%tagname = tagname sv%shapevar = varname_shape sv%mempath = memoryPath sv%memtype = 4 @@ -459,13 +468,13 @@ subroutine log_structarray_vars(this, iout) case (1) ! -- memtype integer ! call idm_log_var(this%struct_vector_1d(j)%int1d, & - this%struct_vector_1d(j)%varname, & + this%struct_vector_1d(j)%tagname, & this%struct_vector_1d(j)%mempath, iout) ! case (2) ! -- memtype real ! call idm_log_var(this%struct_vector_1d(j)%dbl1d, & - this%struct_vector_1d(j)%varname, & + this%struct_vector_1d(j)%tagname, & this%struct_vector_1d(j)%mempath, iout) ! case (4) ! -- memtype intvector @@ -473,7 +482,7 @@ subroutine log_structarray_vars(this, iout) call mem_setptr(int1d, this%struct_vector_1d(j)%varname, & this%struct_vector_1d(j)%mempath) ! - call idm_log_var(int1d, this%struct_vector_1d(j)%varname, & + call idm_log_var(int1d, this%struct_vector_1d(j)%tagname, & this%struct_vector_1d(j)%mempath, iout) ! end select diff --git a/src/Utilities/Idm/mf6blockfile/StructVector.f90 b/src/Utilities/Idm/mf6blockfile/StructVector.f90 index abc2bd07345..a6c0223b2b7 100644 --- a/src/Utilities/Idm/mf6blockfile/StructVector.f90 +++ b/src/Utilities/Idm/mf6blockfile/StructVector.f90 @@ -23,6 +23,7 @@ module StructVectorModule !< type StructVectorType character(len=LENVARNAME) :: varname + character(len=100) :: tagname character(len=LENVARNAME) :: shapevar character(len=LENMEMPATH) :: mempath integer(I4B) :: memtype = 0 diff --git a/src/Utilities/SimVariables.f90 b/src/Utilities/SimVariables.f90 index 87c6c5824ef..28cd21f97a8 100644 --- a/src/Utilities/SimVariables.f90 +++ b/src/Utilities/SimVariables.f90 @@ -40,4 +40,5 @@ module SimVariablesModule integer(I4B) :: iunext = IUSTART !< next file unit number to assign integer(I4B) :: lastStepFailed = 0 !< flag indicating if the last step failed (1) if last step failed; (0) otherwise (set in converge_check) integer(I4B) :: iFailedStepRetry = 0 !< current retry for this time step + integer(I4B) :: iparamlog = 0 !< input (idm) parameter logging to simulation listing file end module SimVariablesModule diff --git a/src/mf6core.f90 b/src/mf6core.f90 index f972bfdf63d..63c62b4f9b9 100644 --- a/src/mf6core.f90 +++ b/src/mf6core.f90 @@ -266,7 +266,7 @@ subroutine static_input_load() use IdmSimulationModule, only: simnam_load, load_models use MemoryHelperModule, only: create_mem_path use MemoryManagerModule, only: mem_setptr, mem_allocate - use SimVariablesModule, only: idm_context + use SimVariablesModule, only: idm_context, iparamlog use SimulationCreateModule, only: create_load_mask ! -- dummy ! -- locals @@ -275,7 +275,7 @@ subroutine static_input_load() integer(I4B), pointer :: nummodels => null() ! ! -- load simnam input context - call simnam_load() + call simnam_load(iparamlog) ! ! -- allocate model load mask input_mempath = create_mem_path(component='SIM', context=idm_context) diff --git a/src/simnamidm.f90 b/src/simnamidm.f90 index 84b59621420..86c6f058f37 100644 --- a/src/simnamidm.f90 +++ b/src/simnamidm.f90 @@ -14,6 +14,7 @@ module SimNamInputModule logical :: nocheck = .false. logical :: prmem = .false. logical :: maxerrors = .false. + logical :: print_input = .false. logical :: tdis6 = .false. logical :: mtype = .false. logical :: mfname = .false. @@ -94,6 +95,22 @@ module SimNamInputModule .false. & ! layered ) + type(InputParamDefinitionType), parameter :: & + simnam_print_input = InputParamDefinitionType & + ( & + 'SIM', & ! component + 'NAM', & ! subcomponent + 'OPTIONS', & ! block + 'PRINT_INPUT', & ! tag name + 'PRINT_INPUT', & ! fortran variable + 'KEYWORD', & ! type + '', & ! shape + .false., & ! required + .false., & ! multi-record + .false., & ! preserve case + .false. & ! layered + ) + type(InputParamDefinitionType), parameter :: & simnam_tdis6 = InputParamDefinitionType & ( & @@ -293,6 +310,7 @@ module SimNamInputModule simnam_nocheck, & simnam_prmem, & simnam_maxerrors, & + simnam_print_input, & simnam_tdis6, & simnam_mtype, & simnam_mfname, & From 335a85b17c36962ebb0cfc6e574b4f2d14650f94 Mon Sep 17 00:00:00 2001 From: Eric Morway Date: Wed, 7 Jun 2023 04:13:26 -0700 Subject: [PATCH 089/123] fix(SfrCrossSectionUtils): vertical channel walls not accounted for in wetted perimeter calc (#1228) --- autotest/cross_section_functions.py | 72 ++++++++++++++ autotest/test_gwf_sfr_npoint01.py | 14 +++ .../ModelUtilities/SfrCrossSectionUtils.f90 | 97 +++++++++++++++++++ 3 files changed, 183 insertions(+) diff --git a/autotest/cross_section_functions.py b/autotest/cross_section_functions.py index e315427fe0e..1b22e137e0d 100644 --- a/autotest/cross_section_functions.py +++ b/autotest/cross_section_functions.py @@ -135,6 +135,31 @@ def wetted_area( return area +def add_wetted_vert(x, h, depth, vert_neighbs, idx): + left_wet_len = 0 + right_wet_len = 0 + + # left side + if vert_neighbs[0]: + idxm1 = idx - 1 + if h[idxm1] > depth: + left_wet_len = depth - h[idx] + else: + left_wet_len = h[idxm1] - h[idx] + + # right side + if vert_neighbs[1]: + idxp1 = idx + 1 + idxp2 = idxp1 + 1 + if h[idxp2] > depth: + right_wet_len = depth - h[idxp1] + else: + right_wet_len = h[idxp2] - h[idxp1] + + vert_len = left_wet_len + right_wet_len + return vert_len + + def wetted_perimeter( x, h, @@ -155,6 +180,13 @@ def wetted_perimeter( # get wetted perimeter perimeter += get_wetted_perimeter(x0, x1, h0, h1, depth) + # set neighbor status + vert_neighbs = is_neighb_vert(x, h, idx) + + # add wetted vertical neighbors if necessary + if np.any(vert_neighbs): + perimeter += add_wetted_vert(x, h, depth, vert_neighbs, idx) + # write to screen if verbose: print(f"{idx}->{idx + 1} ({x0},{x1}) - perimeter={x1 - x0}") @@ -162,6 +194,38 @@ def wetted_perimeter( return perimeter +def is_neighb_vert(x, h, idx): + cnt = len(x) + + # Assess left neighbor first + if idx > 0: + if ( + cnt > 2 + ): # only x-sections w/ 3 or more pts may host a vertical side + idxm1 = idx - 1 + if x[idxm1] == x[idx] and h[idxm1] != h[idx]: + leftvert = True + else: + leftvert = False + else: + leftvert = False + else: + leftvert = False + + # Assess right neighbor + idxp1 = idx + 1 + idxp2 = idxp1 + 1 + if cnt > idxp2: + if x[idxp1] == x[idxp2] and h[idxp1] != idxp2: + rightvert = True + else: + rightvert = False + else: + rightvert = False + + return (leftvert, rightvert) + + def manningsq( x, h, @@ -181,6 +245,14 @@ def manningsq( x0, x1 = get_wetted_station(x[i0], x[i1], h[i0], h[i1], depth) perimeter = get_wetted_perimeter(x0, x1, h[i0], h[i1], depth) + + # set neighbor status + vert_neighbs = is_neighb_vert(x, h, i0) + + # add wetted vertical neighbors if necessary + if np.any(vert_neighbs): + perimeter += add_wetted_vert(x, h, depth, vert_neighbs, i0) + area = get_wetted_area(x[i0], x[i1], h[i0], h[i1], depth) if perimeter > 0.0: radius = area / perimeter diff --git a/autotest/test_gwf_sfr_npoint01.py b/autotest/test_gwf_sfr_npoint01.py index 4106805eac5..7579e1b2025 100644 --- a/autotest/test_gwf_sfr_npoint01.py +++ b/autotest/test_gwf_sfr_npoint01.py @@ -18,6 +18,8 @@ "sfr_npt01g", "sfr_npt01h", "sfr_npt01i", + "sfr_npt01j", + "sfr_npt01k", ] xsect_types = ( @@ -30,6 +32,8 @@ "v", "w", "v_invalid", + "|/", + "\|", ) # spatial discretization data @@ -100,6 +104,16 @@ "h": np.array([1.0, 1.0, 0.0, 1.0, 1.0], dtype=float), "n": np.array([roughness] * 5, dtype=float), }, + xsect_types[9]: { + "x": np.array([0.0, 0.0, rwid], dtype=float), + "h": np.array([1.0, 0.0, 1.0], dtype=float), + "n": np.array([roughness] * 3, dtype=float), + }, + xsect_types[10]: { + "x": np.array([0.0, rwid, rwid], dtype=float), + "h": np.array([1.0, 0.0, 1.0], dtype=float), + "n": np.array([roughness] * 3, dtype=float), + }, } diff --git a/src/Model/ModelUtilities/SfrCrossSectionUtils.f90 b/src/Model/ModelUtilities/SfrCrossSectionUtils.f90 index 1048c83cfea..e5e9d0aba66 100644 --- a/src/Model/ModelUtilities/SfrCrossSectionUtils.f90 +++ b/src/Model/ModelUtilities/SfrCrossSectionUtils.f90 @@ -82,6 +82,41 @@ function get_wetted_topwidth(npts, stations, heights, d) result(w) return end function get_wetted_topwidth + !> @brief Calculate wetted vertical height + !! + !! For segments flanked by vertically-oriented neighboring segments, + !! return the length of the submerged, vertically-oriented, neighboring face + !! + !< + function get_wet_vert_face(n, npts, heights, d, leftface) result(vwf) + ! -- dummy + integer(I4B), intent(in) :: n !< index to be evaluated + integer(I4B), intent(in) :: npts !< length of heights vector + real(DP), dimension(npts), intent(in) :: heights !< cross-section height data + real(DP), intent(in) :: d + logical, intent(in) :: leftface + ! -- local + real(DP) :: vwf !< vertically wetted face length + ! + ! -- calculate the vertically-oriented wetted face length + if (leftface) then + if (heights(n - 1) > d) then + vwf = d - heights(n) + else if (heights(n - 1) > heights(n)) then + vwf = heights(n - 1) - heights(n) + end if + else + if (heights(n + 2) > d) then + vwf = d - heights(n + 1) + else if (heights(n + 2) > heights(n + 1)) then + vwf = heights(n + 2) - heights(n + 1) + end if + end if + ! + ! -- return + return + end function get_wet_vert_face + !> @brief Calculate the wetted perimeter for a reach !! !! Function to calculate the wetted perimeter for a reach using the @@ -269,6 +304,47 @@ end function get_mannings_section ! -- private functions and subroutines + !> @brief Determine vertical segments + !! + !! Subroutine to cycle through each segment (npts - 1) and determine + !! whether neighboring segments are vertically-oriented. + !! + !< + subroutine determine_vert_neighbors(npts, stations, heights, leftv, rightv) + ! -- dummy + integer(I4B), intent(in) :: npts !< number of station-height data for a reach + real(DP), dimension(npts), intent(in) :: stations !< cross-section station distances (x-distance) + real(DP), dimension(npts), intent(in) :: heights !< cross-section height data + logical, dimension(npts - 1), intent(inout) :: leftv + logical, dimension(npts - 1), intent(inout) :: rightv + ! -- local + integer(I4B) :: n + ! + ! -- default neighboring segments to false unless determined otherwise + ! o 2 pt x-section has 1 segment (no neighbors to eval) + ! o 3+ pt x-section has at the very least one neighbor to eval + do n = 1, npts - 1 + leftv(n) = .false. + rightv(n) = .false. + ! -- left neighbor + if (n > 1) then + if (stations(n - 1) == stations(n) .and. heights(n - 1) > heights(n)) then + leftv(n) = .true. + end if + end if + ! -- right neighbor + if (n < npts - 1) then + if (stations(n + 2) == stations(n + 1) .and. & + heights(n + 2) > heights(n + 1)) then + rightv(n) = .true. + end if + end if + end do + ! + ! -- return + return + end subroutine determine_vert_neighbors + !> @brief Calculate the wetted perimeters for each line segment !! !! Subroutine to calculate the wetted perimeter for each line segment @@ -293,6 +369,10 @@ subroutine get_wetted_perimeters(npts, stations, heights, d, p) real(DP) :: dmin real(DP) :: xlen real(DP) :: dlen + logical, dimension(npts - 1) :: leftv, rightv + ! + ! -- set neighbor status + call determine_vert_neighbors(npts, stations, heights, leftv, rightv) ! ! -- iterate over the station-height data do n = 1, npts - 1 @@ -310,6 +390,7 @@ subroutine get_wetted_perimeters(npts, stations, heights, d, p) call get_wetted_station(x0, x1, d0, d1, dmax, dmin, d) ! ! -- calculate the wetted perimeter for the segment + ! - bottom wetted length xlen = x1 - x0 dlen = DZERO if (xlen > DZERO) then @@ -326,6 +407,22 @@ subroutine get_wetted_perimeters(npts, stations, heights, d, p) end if end if p(n) = sqrt(xlen**DTWO + dlen**DTWO) + ! + ! -- if neighboring segments are vertical, account for their + ! contribution to wetted perimeter + ! + ! left neighbor (if applicable) + if (n > 1) then + if (leftv(n)) then + p(n) = p(n) + get_wet_vert_face(n, npts, heights, d, .true.) + end if + end if + ! right neighbor (if applicable) + if (n < npts - 1) then + if (rightv(n)) then + p(n) = p(n) + get_wet_vert_face(n, npts, heights, d, .false.) + end if + end if end do ! ! -- return From 0d24460aa615ddfaad2b92c8b7c2049d92dfc77b Mon Sep 17 00:00:00 2001 From: mjreno Date: Wed, 7 Jun 2023 07:14:10 -0400 Subject: [PATCH 090/123] docs(mf6io): upates for idm including mf6io and release pdfs (#1233) Co-authored-by: mjreno --- doc/ReleaseNotes/v6.5.0.tex | 2 +- doc/mf6io/body.tex | 4 ++ doc/mf6io/processing_of_input.tex | 63 +++++++++++++++++++++++ utils/idmloader/IDM.md | 84 +++---------------------------- 4 files changed, 75 insertions(+), 78 deletions(-) create mode 100644 doc/mf6io/processing_of_input.tex diff --git a/doc/ReleaseNotes/v6.5.0.tex b/doc/ReleaseNotes/v6.5.0.tex index 6135c3aa169..061eca49068 100644 --- a/doc/ReleaseNotes/v6.5.0.tex +++ b/doc/ReleaseNotes/v6.5.0.tex @@ -8,7 +8,7 @@ \item The sorption formulation for the Groundwater Transport (GWT) model was modified for simulations involving a mobile domain and one or more immobile domains. The modifications do not affect GWT Models without the Immobile Storage and Transfer (IST) Package or GWT models without sorption. Prior to these changes, the multi-domain sorption formulation required the bulk density to be specified in both the Mobile Storage and Transfer (MST) Package and the IST Package. To generalize the formulation, the definition for bulk density in the MST Package was changed to be the mass of aquifer solid material in the mobile domain per unit volume of aquifer. The bulk density specified in the IST Package was changed to be the mass of aquifer solid material in the immobile domain per unit volume of aquifer. For multi-domain GWT Models that include sorption (and prepared for MODFLOW version 6.4.1 or earlier), it will be necessary to change the bulk density values specified in the MST and IST Packages according to the new definitions. A full description of the revised sorption formulation for multi-domain GWT Models is included in the MODFLOW 6 Supplemental Technical Information document included with the distribution. \item Add LENGTH\_CONVERSION and TIME\_CONVERSION variables to replace the UNIT\_CONVERSION variable in the SFR Package input file. The LENGTH\_CONVERSION and TIME\_CONVERSION variables are used to convert user-specified Manning's roughness coefficients from SI units (sec/m$^{1/3}$) to model length and time units. LENGTH\_CONVERSION does not need to be specified if LENGTH\_UNITS are meters. TIME\_CONVERSION does not need to be specified if TIME\_UNITS are seconds. Warning messages will be issued if UNIT\_CONVERSION variable is specified. The model will terminate with an error if UNIT\_CONVERSION and LENGTH\_CONVERSION and TIME\_CONVERSION variables are specified. The UNIT\_CONVERSION variable in the SFR Package input file will eventually be deprecated. \item Add MAXIMUM\_ITERATIONS and MAXIMUM\_STAGE\_CHANGE variables in the LAK Package input file. The MAXIMUM\_ITERATIONS variable is used to change the maximum number of iterations and would only need to be increased from the default value if one or more lakes in a simulation has a large water budget error. The MAXIMUM\_STAGE\_CHANGE variable defines the stage closure tolerance for each lake. The MAXIMUM\_STAGE\_CHANGE variable would only need to be increased or decreased from the default value if the water budget error for one or more lakes is too small or too large, respectively. - % \item xxx + \item The Input Data Processor (IDP) is introduced to read ASCII simulation input files and write variable input data to structured locations in the memory manager. Simulation components that have been integrated with IDP no longer handle input files directly but rather retrieve all input data from named locations, called memory paths, allocated in managed memory. The collection of all simulation input data in managed memory is called the input context. IDP uses existing descriptions of input varibles, called variable definitions, to interpret and store input. The program variable definition set and its representation in the input context is described as the Input Data Model (IDM). Input variables can be recognized in a memory dump (e.g., with the MEMORY\_PRINT\_OPTION) by their memory path prefix string "\_\_INPUT\_\_". The downstream context (model, package, etc.) that later accesses input typically copies data from the input context to their own memory managed context, therefore IDP results in an increased memory footprint for the program. Among its advantages include the consolidation of all input processing early in program runtime, and outside of any particular component. enabling the support of alternative types of input data sources. Input file types that are currently processed by IDP include DIS6, DISU6, DIV6, NPF6, DSP6, and Name File inputs for the Simulation (mfsim.nam) and GWF and GWT models. % \item xxx \end{itemize} diff --git a/doc/mf6io/body.tex b/doc/mf6io/body.tex index 4bcf8ca3f73..b34c0b6f72b 100644 --- a/doc/mf6io/body.tex +++ b/doc/mf6io/body.tex @@ -13,6 +13,10 @@ \SECTION{Form of Input Instructions} \input{form_of_input.tex} +%Processing of program input +\SECTION{Processing of Program Input} +\input{processing_of_input.tex} + %Simulation name file \newpage \SECTION{Simulation Name File} diff --git a/doc/mf6io/processing_of_input.tex b/doc/mf6io/processing_of_input.tex new file mode 100644 index 00000000000..3a5766cf204 --- /dev/null +++ b/doc/mf6io/processing_of_input.tex @@ -0,0 +1,63 @@ +An effort is underway to process program input early in program runtime, before the simulation is created, in a general way that is not dependent on any given component. This capability is called the \mf Input Data Processor (IDP). Components that have been updated to use IDP no longer directly read or process file inputs but instead access input data from internally managed memory locations. + +\subsection{Supported components} + +A specific set of \mf components has been updated in the current version to use the Input Data Processor, as shown in Table~\ref{table:idmsupported}. Two integration steps have been taken for each file type listed in the table. First, IDP has been updated to support the reading and loading of variable input data for the component. File types listed in the table, each previously read and processed by the component, are now processed by IDP. Second, the component itself has been refactored to retrieve input from managed memory locations in a predictable way. Components and associated file types shown in table~\ref{table:idmsupported} are described in more detail in later sections of this document. + +\begin{table}[H] +\caption{IDP integrated components} +\small +\begin{center} +\begin{tabular*}{\columnwidth}{l l l l} +\hline +\hline +\textbf{Component Type} & \textbf{Subcomponent Type} & \textbf{Component} & \textbf{File Type} \\ +\hline +SIM & NAM & SIM/NAM & mfsim.nam \\ +GWF & NAM & GWF/NAM & GWF name file \\ +GWT & NAM & GWT/NAM & GWT name file \\ +GWF & DIS & GWF/DIS & DIS6 \\ +GWF & DISU & GWF/DISU & DISU6 \\ +GWF & DISV & GWF/DISV & DISV6 \\ +GWF & NPF & GWF/NPF & NPF6 \\ +GWT & DIS & GWT/DIS & DIS6 \\ +GWT & DISU & GWT/DISU & DISU6 \\ +GWT & DISV & GWT/DISV & DISV6 \\ +GWT & DSP & GWT/DSP & DSP6 \\ +\hline +\end{tabular*} +\label{table:idmsupported} +\end{center} +\normalsize +\end{table} + +\subsection{Scope of change} + +The Input Data Processor introduces transparent changes that are beyond the scope of this document. Input logging differences, however, are readily apparent when comparing to earlier versions of \mf. These differences are primarily related to timing as input files processed by IDP are read before the simulation has been created. Logging appears in the simulation log (mfsim.lst) in part because simulation models and their associated listing files do not exist at the time when input is read. In addition, input logging reflects only what was read and loaded to memory as further processing and use is deferred to the simulation components that the input is intended for. Summaries of memory managed variables, including input data variables loaded by IDP, are possible to view in the simulation listing files with a Simulation Name File option described later. + +\subsection{Example logging blocks} + +Below is example simulation logging (mfsim.lst) for two model package input files read and loaded by the Input Data Processor. The first logging block results from processing a DIS6 input file and the second logging block results from processing an NPF6 input file. Variable names in the blocks are described in later sections of this document. + +\small +\begin{lstlisting}[style=modeloutput] + + Loading input for GWF-NO-VSC-SFR01/DIS + # File generated by Flopy version 3.3.7 on 05/31/2023 at 12:56:15. + String detected: LENGTH_UNITS = M + Integer detected: NLAY = 1 + Integer detected: NROW = 60 + Integer detected: NCOL = 200 + Double precision 1D constant array detected: DELR = 50.000000000000000 + Double precision 1D constant array detected: DELC = 50.000000000000000 + Double precision 2D array detected: TOP ranges from 230.07503124999999 to 303.32871875000001 + Double precision 3D constant array detected: BOTM = 0.0000000000000000 + Loading input complete... + + Loading input for GWF-NO-VSC-SFR01/NPF + # File generated by Flopy version 3.3.7 on 05/31/2023 at 12:56:15. + Keyword detected: SAVE_SPECIFIC_DISCHARGE + Integer 1D constant array detected: ICELLTYPE = 1 + Double precision 1D constant array detected: K = 1.0000000000000000 + Loading input complete... +\end{lstlisting} diff --git a/utils/idmloader/IDM.md b/utils/idmloader/IDM.md index c699541f0d6..37ff976aa77 100644 --- a/utils/idmloader/IDM.md +++ b/utils/idmloader/IDM.md @@ -6,8 +6,6 @@ This document intends to describe, from a development perspective, the MODFLOW 6 * [Overview](#overview) * [Terminology](#terminology) -* [IDM roadmap](#idm-roadmap) -* [Package Update Status](#package-update-status) * [Package Update Process](#package-update-process) ## Overview @@ -55,90 +53,22 @@ type GwtDspParamFoundType logical :: grid_atv = .false. end type GwtDspParamFoundType` ``` - -## IDM roadmap - -Implementation of the new IDM concepts and approach in the MODFLOW 6 code is a large effort. For this reason, the effort is divided into phases as shown here. - -Phase | Scope | Action | Status ---- | --- | --- | --- -1.0 | idm | tool to generate input definitions from dfn files | complete -1.0 | idm | support generic reading of static mf6 input | complete -1.0 | idm | evaluate and prototype support for time-varying inputs | ongoing -1.0 | model subcomponents | update packages requiring static loader | ongoing -1.1 | idm | support for dynamic loading | not started -1.1 | model subcomponents | update packages requiring dynamic loader | not started -1.2 | model subcomponents | remove blockparser from all packages | not started -1.2 | doc | plan and prioritize required documentation | not started -1.2 | test | plan and prioritize testing | not started -2.0 | idm | preparation for 2nd supported input source | future -2.1 | idm | reader/writer support for 2nd supported input source | future - -## Package Update Status -FTYPE | Status | Comment ---- | --- | --- -DIS6 | complete | -DISV6 | complete | -DISU6 | complete | -IC6 | candidate | stage next set -OC6 | IDM time-varying support needed | -NPF6 | complete | -STO6 | candidate | simplified period block, good candidate for early tv support -CSUB6 | IDM time-varying support needed | -BUY6 | candidate | in progress -HFB6 | candidate | period block but no timeseries data -CHD6 | IDM time-varying support needed | BndType -WEL6 | IDM time-varying support needed | BndType -DRN6 | IDM time-varying support needed | BndType -RIV6 | IDM time-varying support needed | BndType -GHB6 | IDM time-varying support needed | BndType -RCH6 | IDM time-varying support needed | BndType -EVT6 | IDM time-varying support needed | BndType -MAW6 | IDM time-varying support needed | BndType -SFR6 | IDM time-varying support needed | BndType -LAK6 | IDM time-varying support needed | BndType -UZF6 | IDM time-varying support needed | BndType -MVR6 | IDM time-varying support needed | -GNC6 | candidate | stage next set -OBS6 | candidate | stage next set -FMI6 | candidate | in progress -ADV6 | candidate | stage next set -DSP6 | complete | -SSM6 | candidate | stage next set -MST6 | candidate | stage next set -IST6 | IDM time-varying support needed | BndType -CNC6 | IDM time-varying support needed | BndType -SRC6 | IDM time-varying support needed | BndType -LKT6 | IDM time-varying support needed | GwtAptType -SFT6 | IDM time-varying support needed | GwtAptType -MWT6 | IDM time-varying support needed | GwtAptType -UZT6 | IDM time-varying support needed | GwtAptType -MVT6 | candidate | stage next set -API6 | IDM time-varying support needed | BndType - ## Package Update process ### Update [dfn2f90.py](scripts/dfn2f90.py) -Add the package dfn file path to the `gwf_dfns` or `gwt_dfns` list structure +Add a new dfns entry in main, designating paths and names for the input dfn and output f90 files. ### Run the dfn2f90.py script ```shell cd utils/idmloader/scripts python dfn2f90.py ``` -This will create the new IDM Fortran definition file (with a .f90 suffix) in the appropriate directory, either `src/Model/GroundWaterFlow` or `src/Model/GroundWaterTransport` +This will create the new IDM Fortran definition file at the location designated. This will also automatically update IDM selector modules so that the newly generated definitions can be used. If a new component (e.g. a new model) has been introduced, a new selector file will be generated in the src/Utitilites/Idm/selector directory. ### Update [meson.build](../../src/meson.build) and [mf6core.vfproj](../../msvs/mf6core.vfproj) -Add newly created f90 definition file -### Update [InputDefinitionSelector.f90](../../src/Utilities/Idm/InputDefinitionSelector.f90) -Expose the newly generated definitions to Idm core. This involves adding “use” statements for the new module definition types and updating 3 select statements to make the new definition lists available. -### Update the package file -#### Create and load subcomponent input context -In create, or after parser initialization, invoke IdmMf6FileLoaderModule `input_load()` interface to create and load package input data to the subcomponent input context. - -```fortran -call input_load(dspobj%parser, 'DSP6', 'GWT', 'DSP', dspobj%name_model, 'DSP', iout) -``` +Add any newly generated fortran files to relevant build scripts to compile new definitions and definition select routines into MODFLOW 6 binaries. +Note: To simplify the update process, all necessary internal modifications are performed when dfn2f90.py is run. Compiling with these changes immediately updates IDM to treat any newly added package as integrated. This may not be the case if, for example, model package code has not been updated to source input from the input context. GWF and GWT model code is fully integrated with IDM and as such either a valid unit number (for packages that are not IDM integrated) or a valid mempath (for packages that are IDM integrated) is passed into a package but not both. Once a new fortran idm definition file has been compiled in, a valid unit number will no longer be provided to the package and parser operations will fail if attempted. +### Update the package file #### Source package input data -To convert a package to use the new IDM approach, the read routine for the package must be replaced by a source routine. The implementation of the source routine is dependent on the data itself but a common pattern is to use the MemoryManagerExtModule `mem_set_value()` interface to copy data from the input context to package paths. A parameter found type, in the generated IDM definition file, should be used to pass a corresponding logical to `mem_set_value()`, which sets the logical to True if the input path was found and data was copied. When sourcing has been completed, the found type parameter logicals can be checked to determine what other actions need to be taken in response to both found or not found input data. +To convert a package to use the new IDM approach, the read routine for the package must be replaced by a sourcing routine that accesses data from the input context. The implementation of the source routine is dependent on the data itself but a common pattern is to use the MemoryManagerExtModule `mem_set_value()` interface to copy data from the input context to package paths. A parameter found type, in the generated IDM definition file, should be used to pass a corresponding logical to `mem_set_value()`, which sets the logical to True if the input path was found and data was copied. When sourcing has been completed, the found type parameter logicals can be checked to determine what other actions need to be taken in response to both found or not found input data. ```fortran character(len=LENMEMPATH) :: idmMemoryPath @@ -176,6 +106,6 @@ cd utils/idmloader/scripts python dfn2f90.py ``` -This will update the existing idm package f90 file to add the new parameter definition +This will update the existing idm package f90 file to add the new parameter definition. Compile to update the binaries. #### Source package input data Update the package source routine for the relevant block to copy the data from the input path to the package path. Take any necessary action depending on whether the data was found or not found. From 4a99cbc017d29d453518c57c4bf7433d8128d551 Mon Sep 17 00:00:00 2001 From: Eric Morway Date: Wed, 7 Jun 2023 04:16:35 -0700 Subject: [PATCH 091/123] fix(gwf3lak8): wetted area should be zero when lak stg < cell bottom (#1230) --- doc/ReleaseNotes/v6.5.0.tex | 1 + src/Model/GroundWaterFlow/gwf3lak8.f90 | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/doc/ReleaseNotes/v6.5.0.tex b/doc/ReleaseNotes/v6.5.0.tex index 061eca49068..7cd3925f81d 100644 --- a/doc/ReleaseNotes/v6.5.0.tex +++ b/doc/ReleaseNotes/v6.5.0.tex @@ -29,6 +29,7 @@ \item An SFR channel defined with the n-point cross-section option was calculating the wetted cross-sectional area incorrectly. The cross sectional area for the area of a triangle was being calculated as one-half multiplied by the depth of the channel, as opposed to one-half multiplied by the base width multiplied by the height. As a result, the units in the mannings equation were not correct owing to the missing dimension in the area calculation. The change in the area calculation will slightly alter the solution found using Manning's equation since the cross-sectional area term appears in it. As a result, existing models may reflect slightly different answers in groundwater\/surface-water exchange amounts owing to slight differences in the calculated stream stage. In addition to the fix, some clarifying text, including a new figure, was added to mf6io.pdf. \item The SSM Package for the GWT Model did not work properly with Stress Package Concentration (SPC) input with the READARRAY option for transient models. Under these conditions, the program would prematurely terminate looking for the next BEGIN PERIOD block. The program was corrected so that SPC input can be read for transient conditions. \item For some Linux systems, observations were not being correctly written to formatted observation output files when the source code was compiled with the Intel IFORT 19.1.0.166 20191121 compiler. This issue has been addressed by adding a flush statement to ObsUtilityModule::write\_unfmtd\_obs after writing each observation for a time step. This change will not affect simulated observations and should not affect simulation run times. + \item The wetted area stored in the binary LAK package output needs to be zero when the lake stage is below the bottom of a connected groundwater cell. The code uses the lak_calculate_conn_warea() function to determine the wetted area, which makes sense for calculating the flow conductance; however, for thermal conduction the shared wetted area should be 0.0 when the lake stage falls below the bottom of a connected cell. \end{itemize} \underline{INTERNAL FLOW PACKAGES} diff --git a/src/Model/GroundWaterFlow/gwf3lak8.f90 b/src/Model/GroundWaterFlow/gwf3lak8.f90 index 189bd1cb9f5..366ebc54e23 100644 --- a/src/Model/GroundWaterFlow/gwf3lak8.f90 +++ b/src/Model/GroundWaterFlow/gwf3lak8.f90 @@ -6332,6 +6332,10 @@ subroutine lak_fill_budobj(this) ! equal to 0.0 gwhead = this%xnew(n2) call this%lak_calculate_conn_warea(n, j, lkstg, gwhead, wa) + ! -- For thermal conduction between a lake and a gw cell, + ! the shared wetted area should be reset to zero when the lake + ! stage is below the cell bottom + if (this%belev(j) > lkstg) wa = DZERO this%qauxcbc(1) = wa call this%budobj%budterm(idx)%update_term(n, n2, q, this%qauxcbc) end do From 323af71d8429f271dae75da65a278fec3d1d56d3 Mon Sep 17 00:00:00 2001 From: langevin-usgs Date: Thu, 8 Jun 2023 09:28:36 -0500 Subject: [PATCH 092/123] fix(benchmark): correct benchmark script (#1238) * benchmark table may incorrectly attribute run to wrong version * continue with table even if a run failed --- distribution/benchmark.py | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/distribution/benchmark.py b/distribution/benchmark.py index ab19213a947..1c890ce7b0c 100644 --- a/distribution/benchmark.py +++ b/distribution/benchmark.py @@ -158,13 +158,13 @@ def elapsed_real_to_string(elt): return elt_str + f"{time_sec:.3f} Seconds" -def run_function(app, example): - return flopy.run_model( +def run_function(id, app, example): + return (id, flopy.run_model( app, None, model_ws=example, silent=True, - report=True, + report=True,) ) @@ -200,8 +200,8 @@ def run_model(current_app: PathLike, previous_app: PathLike, model_path: PathLik # processing options args = ( - (current_app, model_path), - (previous_app, prev_dir), + (0, current_app, model_path), + (1, previous_app, prev_dir), ) # Multi-processing using Pool @@ -215,8 +215,16 @@ def run_model(current_app: PathLike, previous_app: PathLike, model_path: PathLik pool.close() # set variables for processing - success, buff = results[0].get() - success0, buff0 = results[1].get() + id, (s, b) = results[0].get() + if id == 0: + success, buff = s, b + elif id == 1: + success0, buff0 = s, b + id, (s, b) = results[1].get() + if id == 0: + success, buff = s, b + elif id == 1: + success0, buff0 = s, b if success: elt = get_elapsed_time(buff) @@ -356,7 +364,8 @@ def run_benchmarks( previous_exe, example, ) - assert success, f"{example} run failed" + if not success: + print(f"{example} run failed") current_total += t previous_total += t0 lines.append(line) From c2e56e3257b1888d1c18ccd4d3c2f96bc602d59c Mon Sep 17 00:00:00 2001 From: langevin-usgs Date: Thu, 8 Jun 2023 14:55:02 -0500 Subject: [PATCH 093/123] refactor(mst-ist): implement new parameterization for MST/IST (#1231) * IST now requires immobile domain volume fraction * porosities are entered as void volume per domain volume, rather than void volume per aquifer volume * not backward compatible --- autotest/test_gwt_ist01.py | 6 +- autotest/test_gwt_moc3d01_zod.py | 51 ++-- autotest/test_gwt_mt3dms_p01.py | 16 +- doc/ReleaseNotes/v6.5.0.tex | 2 +- doc/SuppTechInfo/Tables/sorption_params1.tex | 38 --- doc/SuppTechInfo/Tables/sorption_params2.tex | 36 --- .../Tables/transport_params_original.tex | 35 +++ .../Tables/transport_params_revised.tex | 38 +++ doc/SuppTechInfo/body.tex | 2 +- doc/SuppTechInfo/sorption.tex | 175 ++++++------ doc/mf6io/gwt/ist.tex | 4 +- doc/mf6io/mf6io.bbl | 248 ++++++++++++++++++ doc/mf6io/mf6ivar/dfn/gwt-ist.dfn | 29 +- doc/mf6io/mf6ivar/dfn/gwt-mst.dfn | 4 +- doc/mf6io/mf6ivar/md/mf6ivar.md | 18 +- doc/mf6io/mf6ivar/tex/gwf-sfr-period.dat | 1 - doc/mf6io/mf6ivar/tex/gwt-disv-griddata.dat | 4 +- doc/mf6io/mf6ivar/tex/gwt-ist-desc.tex | 10 +- doc/mf6io/mf6ivar/tex/gwt-ist-griddata.dat | 18 +- doc/mf6io/mf6ivar/tex/gwt-mst-desc.tex | 4 +- doc/mf6io/mf6ivar/tex/sim-nam-desc.tex | 2 + doc/mf6io/mf6ivar/tex/sim-nam-options.dat | 1 + src/Distributed/VirtualGwtModel.f90 | 10 +- src/Model/Connection/GwtGwtConnection.f90 | 4 +- src/Model/Connection/GwtInterfaceModel.f90 | 8 +- src/Model/GroundWaterTransport/gwt1.f90 | 5 +- src/Model/GroundWaterTransport/gwt1dsp.f90 | 15 +- src/Model/GroundWaterTransport/gwt1ist1.f90 | 165 +++++++----- src/Model/GroundWaterTransport/gwt1mst1.f90 | 108 ++++++-- 29 files changed, 746 insertions(+), 311 deletions(-) delete mode 100644 doc/SuppTechInfo/Tables/sorption_params1.tex delete mode 100644 doc/SuppTechInfo/Tables/sorption_params2.tex create mode 100644 doc/SuppTechInfo/Tables/transport_params_original.tex create mode 100644 doc/SuppTechInfo/Tables/transport_params_revised.tex diff --git a/autotest/test_gwt_ist01.py b/autotest/test_gwt_ist01.py index a87d6be5de0..6587941a2d3 100644 --- a/autotest/test_gwt_ist01.py +++ b/autotest/test_gwt_ist01.py @@ -16,7 +16,8 @@ laytyp = [1] ss = [1.0e-10] sy = [0.1] -thetaim = [0.05] +porosity_im = [0.05] +volfrac_im = [0.5] zetaim = [0.1] nlay, nrow, ncol = 1, 1, 1 @@ -188,7 +189,8 @@ def build_model(idx, dir): save_flows=True, cim_filerecord=cim_filerecord, cim=0.0, - thetaim=thetaim[idx], + porosity=porosity_im[idx], + volfrac=volfrac_im[idx], zetaim=zetaim[idx], ) diff --git a/autotest/test_gwt_moc3d01_zod.py b/autotest/test_gwt_moc3d01_zod.py index 9ec0e22f3e7..1597ff67bc8 100644 --- a/autotest/test_gwt_moc3d01_zod.py +++ b/autotest/test_gwt_moc3d01_zod.py @@ -1,7 +1,7 @@ # This autotest is based on the MOC3D problem 1 autotest except that it # tests the zero-order decay for a simple one-dimensional flow problem. # The test ensures that concentrations do not go below zero (they do go -# slightly negative but, it does ensure that the decay rate shuts off as +# slightly negative but, it does ensure that the decay rate shuts off # where concentrations are zero. import os @@ -214,20 +214,25 @@ def build_model(idx, dir): ) # storage - porosity = 0.1 + theta_mobile = 0.1 # vol mobile voids per cell volume + volfrac_immobile = 0. + theta_immobile = 0. + if ist_package[idx]: + # if dual domain, then assume half of cell is mobile and other half is immobile + volfrac_immobile = 0.5 + theta_immobile = theta_mobile + porosity_immobile = theta_immobile / volfrac_immobile + volfrac_mobile = 1. - volfrac_immobile + porosity_mobile = theta_mobile / volfrac_mobile rtd = retardation[idx] sorption = None kd = None - rhobm = None - rhobim = None + rhob = None if rtd is not None: rhob = 1.0 - kd = (rtd - 1.0) * porosity / rhob + kd = (rtd - 1.0) * theta_mobile / rhob rhobm = rhob - if ist_package[idx]: - rhobm = .5 * rhob - rhobim = .5 * rhob sorption = "linear" decay_rate = decay[idx] @@ -238,13 +243,13 @@ def build_model(idx, dir): # mass storage and transfer mst = flopy.mf6.ModflowGwtmst( gwt, - porosity=porosity, + porosity=porosity_mobile, zero_order_decay=zero_order_decay, decay=decay_rate, decay_sorbed=decay_rate, sorption=sorption, distcoef=kd, - bulk_density=rhobm, + bulk_density=rhob, ) if ist_package[idx]: @@ -254,10 +259,11 @@ def build_model(idx, dir): sorption=sorption, zero_order_decay=True, cim=0.0, - thetaim=porosity, + volfrac=volfrac_immobile, + porosity=porosity_immobile, zetaim=1.0, decay=decay_rate, - bulk_density=rhobim, + bulk_density=rhob, distcoef=kd, decay_sorbed=decay_rate, ) @@ -373,6 +379,16 @@ def eval_transport(sim): except: assert False, f'could not load data from "{fpth}"' + makeplot = False + if makeplot: + fname = "fig-ct.pdf" + fname = os.path.join(sim.simpath, fname) + make_plot_ct(tssim, fname) + + fname = "fig-cd.pdf" + fname = os.path.join(sim.simpath, fname) + make_plot_cd(cobj, fname) + # get mobile domain budget object fpth = os.path.join(sim.simpath, f"{gwtname}.cbc") bobj = flopy.utils.CellBudgetFile(fpth, precision="double") @@ -422,16 +438,7 @@ def eval_transport(sim): ) np.allclose(qim_budfile, qim_calculated), errmsg - makeplot = False - if makeplot: - fname = "fig-ct.pdf" - fname = os.path.join(ex[sim.idxsim], fname) - make_plot_ct(tssim, fname) - - fname = "fig-cd.pdf" - fname = os.path.join(ex[sim.idxsim], fname) - make_plot_cd(cobj, fname) - + # compare every tenth time tssim = tssim[::10] # print(tssim) diff --git a/autotest/test_gwt_mt3dms_p01.py b/autotest/test_gwt_mt3dms_p01.py index 1a7d0998c9b..bd163a76202 100644 --- a/autotest/test_gwt_mt3dms_p01.py +++ b/autotest/test_gwt_mt3dms_p01.py @@ -374,12 +374,23 @@ def p01mf6( else: decay_rate_sorbed = decay_rate + porosity_mobile = prsity + porosity_immobile = None + if prsity2 is not None: + # immobile domain is active + volfrac_immobile = 0.5 + volfrac_mobile = 1.0 - volfrac_immobile + theta_immobile = prsity2 + porosity_immobile = theta_immobile / volfrac_immobile + porosity_mobile = prsity / volfrac_mobile + + first_order_decay = True if zero_order_decay: first_order_decay = False mst = flopy.mf6.ModflowGwtmst( gwt, - porosity=prsity, + porosity=porosity_mobile, first_order_decay=first_order_decay, zero_order_decay=zero_order_decay, decay=decay_rate, @@ -413,7 +424,8 @@ def p01mf6( decay=decay_rate, decay_sorbed=decay_rate_sorbed, zetaim=zeta, - thetaim=prsity2, + porosity=porosity_immobile, + volfrac=volfrac_immobile, filename=f"{gwtname}.ist", pname="IST-1", ) diff --git a/doc/ReleaseNotes/v6.5.0.tex b/doc/ReleaseNotes/v6.5.0.tex index 7cd3925f81d..46dc6f028e7 100644 --- a/doc/ReleaseNotes/v6.5.0.tex +++ b/doc/ReleaseNotes/v6.5.0.tex @@ -5,7 +5,7 @@ \underline{NEW FUNCTIONALITY} \begin{itemize} - \item The sorption formulation for the Groundwater Transport (GWT) model was modified for simulations involving a mobile domain and one or more immobile domains. The modifications do not affect GWT Models without the Immobile Storage and Transfer (IST) Package or GWT models without sorption. Prior to these changes, the multi-domain sorption formulation required the bulk density to be specified in both the Mobile Storage and Transfer (MST) Package and the IST Package. To generalize the formulation, the definition for bulk density in the MST Package was changed to be the mass of aquifer solid material in the mobile domain per unit volume of aquifer. The bulk density specified in the IST Package was changed to be the mass of aquifer solid material in the immobile domain per unit volume of aquifer. For multi-domain GWT Models that include sorption (and prepared for MODFLOW version 6.4.1 or earlier), it will be necessary to change the bulk density values specified in the MST and IST Packages according to the new definitions. A full description of the revised sorption formulation for multi-domain GWT Models is included in the MODFLOW 6 Supplemental Technical Information document included with the distribution. + \item The Groundwater Transport (GWT) model was modified for simulations involving a mobile domain and one or more immobile domains. The modifications do not affect GWT Models without the Immobile Storage and Transfer (IST) Package. The original IST Package formulation described by \cite{modflow6gwt} was based on a limiting assumption about how the mobile and immobile domains are apportioned within a model cell. The changes introduced here require the user to explicitly specify in the IST Package the volume fraction of each cell that is immobile. This change also redefines the meaning of several input parameters. As described in a new chapter in the Supplemental Technical Information document, porosity and bulk density values must now be entered per domain volume rather than per cell volume. Consequently, for simulations that include one or more IST Packages, these changes are not backward compatible, and will require updates to IST and MST input. Suggestions for updating existing parameter values is included in the Supplemental Technical Information document, which is included with the distribution. \item Add LENGTH\_CONVERSION and TIME\_CONVERSION variables to replace the UNIT\_CONVERSION variable in the SFR Package input file. The LENGTH\_CONVERSION and TIME\_CONVERSION variables are used to convert user-specified Manning's roughness coefficients from SI units (sec/m$^{1/3}$) to model length and time units. LENGTH\_CONVERSION does not need to be specified if LENGTH\_UNITS are meters. TIME\_CONVERSION does not need to be specified if TIME\_UNITS are seconds. Warning messages will be issued if UNIT\_CONVERSION variable is specified. The model will terminate with an error if UNIT\_CONVERSION and LENGTH\_CONVERSION and TIME\_CONVERSION variables are specified. The UNIT\_CONVERSION variable in the SFR Package input file will eventually be deprecated. \item Add MAXIMUM\_ITERATIONS and MAXIMUM\_STAGE\_CHANGE variables in the LAK Package input file. The MAXIMUM\_ITERATIONS variable is used to change the maximum number of iterations and would only need to be increased from the default value if one or more lakes in a simulation has a large water budget error. The MAXIMUM\_STAGE\_CHANGE variable defines the stage closure tolerance for each lake. The MAXIMUM\_STAGE\_CHANGE variable would only need to be increased or decreased from the default value if the water budget error for one or more lakes is too small or too large, respectively. \item The Input Data Processor (IDP) is introduced to read ASCII simulation input files and write variable input data to structured locations in the memory manager. Simulation components that have been integrated with IDP no longer handle input files directly but rather retrieve all input data from named locations, called memory paths, allocated in managed memory. The collection of all simulation input data in managed memory is called the input context. IDP uses existing descriptions of input varibles, called variable definitions, to interpret and store input. The program variable definition set and its representation in the input context is described as the Input Data Model (IDM). Input variables can be recognized in a memory dump (e.g., with the MEMORY\_PRINT\_OPTION) by their memory path prefix string "\_\_INPUT\_\_". The downstream context (model, package, etc.) that later accesses input typically copies data from the input context to their own memory managed context, therefore IDP results in an increased memory footprint for the program. Among its advantages include the consolidation of all input processing early in program runtime, and outside of any particular component. enabling the support of alternative types of input data sources. Input file types that are currently processed by IDP include DIS6, DISU6, DIV6, NPF6, DSP6, and Name File inputs for the Simulation (mfsim.nam) and GWF and GWT models. diff --git a/doc/SuppTechInfo/Tables/sorption_params1.tex b/doc/SuppTechInfo/Tables/sorption_params1.tex deleted file mode 100644 index 6b6d61e79ac..00000000000 --- a/doc/SuppTechInfo/Tables/sorption_params1.tex +++ /dev/null @@ -1,38 +0,0 @@ -\begin{table}[!ht] - \small - \centering - \caption{Symbols, descriptions, and definitions of mobile and immobile domain input parameters and related variables relevant to the new, generalized formulation of sorption in \mf. Division of the aquifer into domains is conceptualized in terms of solid mass fractions, and input parameters are defined on a per-aquifer-volume basis} \tabularnewline - - \begin{tabular}{z{1.50cm} - z{3.50cm} - z{5.00cm} - z{3.50cm} - } - % header - \hline - \rowcolor{Gray} - \multicolumn{1}{ z{1.50cm} }{\textbf{Symbol}} & - \multicolumn{1}{ z{3.50cm} }{\textbf{Description}} & - \multicolumn{1}{ z{5.00cm} }{\textbf{Definition}} & - \multicolumn{1}{ z{3.50cm} }{\textbf{Type}} \\ - \hline - - $f_m$ & solid mass fraction (mobile domain) & $\frac{mobile \; domain \; solid \; mass}{aquifer \; solid \; mass}$ & sorption-related variable in equation~\ref{eqn:gwtpde} (not input) \\ - -\rowcolor{Gray} - $f_{im}$ & solid mass fraction (immobile domain $im$) & $\frac{immobile \; domain \; solid \; mass}{aquifer \; solid \; mass}$ & sorption-related variable in equation~\ref{eqn:gwtistpde} (not input) \\ - - $\theta_m$ & porosity (mobile domain) & $\frac{mobile \; domain \; pore \; volume}{aquifer \; volume}$ & input parameter (definition unchanged, but no longer directly related to sorption) \\ - -\rowcolor{Gray} - $\theta_{im}$ & porosity (immobile domain $im$) & $\frac{immobile \; domain \; pore \; volume}{aquifer \; volume}$ & input parameter (definition unchanged, but no longer directly related to sorption) \\ - - $\rho_{b,m}$ & bulk density (mobile domain) & $\frac{mobile \; domain \; solid \; mass}{aquifer \; volume}$ & sorption-related input parameter \\ - -\rowcolor{Gray} - $\rho_{b,im}$ & bulk density (immobile domain) & $\frac{immobile \; domain \; solid \; mass}{aquifer \; volume}$ & sorption-related input parameter \\ - - \hline - \end{tabular} - \label{table:sorptionparam1} -\end{table} diff --git a/doc/SuppTechInfo/Tables/sorption_params2.tex b/doc/SuppTechInfo/Tables/sorption_params2.tex deleted file mode 100644 index 9bea3339a8f..00000000000 --- a/doc/SuppTechInfo/Tables/sorption_params2.tex +++ /dev/null @@ -1,36 +0,0 @@ -\begin{table}[!ht] - \small - \centering - \caption{Symbols, descriptions, and definitions of an alternative set of sorption-related variables. Division of the aquifer into domains is conceptualized in terms of volume fractions, and porosities and bulk densities are defined on a localized, per-domain-volume basis. If used, these variables must be converted to the parameters in table~\ref{table:sorptionparam1} for input into \mf, as described in this section} \tabularnewline - - \begin{tabular}{z{1.50cm} - z{3.50cm} - z{5.00cm} - } - % header - \hline - \rowcolor{Gray} - \multicolumn{1}{ z{1.50cm} }{\textbf{Symbol}} & - \multicolumn{1}{ z{3.50cm} }{\textbf{Description}} & - \multicolumn{1}{ z{5.00cm} }{\textbf{Definition}} \\ - \hline - - $\hat{f}_m$ & volume fraction (mobile domain) & $\frac{mobile \; domain \; volume}{aquifer \; volume}$ \\ - -\rowcolor{Gray} - $\hat{f}_{im}$ & volume fraction (immobile domain $im$) & $\frac{immobile \; domain \; volume}{aquifer \; volume}$ \\ - - $\phi_m$ & local porosity (mobile domain) & $\frac{mobile \; domain \; pore \; volume}{mobile \; domain \; volume}$ \\ - -\rowcolor{Gray} - $\phi_{im}$ & local porosity (immobile domain $im$) & $\frac{immobile \; domain \; pore \; volume}{immobile \; domain \; volume}$ \\ - - $\tilde{\rho}_{b,m}$ & local bulk density (mobile domain) & $\frac{mobile \; domain \; solid \; mass}{mobile \; domain \; volume}$ \\ - -\rowcolor{Gray} - $\tilde{\rho}_{b,im}$ & local bulk density (immobile domain) & $\frac{immobile \; domain \; solid \; mass}{immobile \; domain \; volume}$ \\ - - \hline - \end{tabular} - \label{table:sorptionparam2} -\end{table} diff --git a/doc/SuppTechInfo/Tables/transport_params_original.tex b/doc/SuppTechInfo/Tables/transport_params_original.tex new file mode 100644 index 00000000000..e10b5bf9fb1 --- /dev/null +++ b/doc/SuppTechInfo/Tables/transport_params_original.tex @@ -0,0 +1,35 @@ +\begin{table}[!ht] + \small + \centering + \caption{Symbols, descriptions, and definitions of original mobile and immobile domain model parameters (used in versions of \mf up to and including version 6.4.1) that are affected by the revised parameterization. In the original parameterization, division of the aquifer into domains is conceptualized in terms of solid mass fractions, and domain properties are defined on a per-aquifer-volume basis} \tabularnewline + + \begin{tabular}{z{1.50cm} + z{3.50cm} + z{5.00cm} + z{3.50cm} + } + % header + \hline + \rowcolor{Gray} + \multicolumn{1}{ z{1.50cm} }{\textbf{Symbol}} & + \multicolumn{1}{ z{3.50cm} }{\textbf{Description}} & + \multicolumn{1}{ z{5.00cm} }{\textbf{Definition}} & + \multicolumn{1}{ z{3.50cm} }{\textbf{Notes}} \\ + \hline + + $\theta_m$ & porosity (mobile domain) & $\frac{mobile \; domain \; pore \; volume}{aquifer \; volume}$ & sorption-related input parameter \\ + +\rowcolor{Gray} + $\theta_{im}$ & porosity (immobile domain $im$) & $\frac{immobile \; domain \; pore \; volume}{aquifer \; volume}$ & sorption-related input parameter \\ + + $\rho_{b}$ & overall bulk density (mobile and immobile domains combined) & $\frac{aquifer \; solid \; mass}{aquifer \; volume}$ & sorption-related input parameter \\ + +\rowcolor{Gray} + $f_{m}$ & solid mass fraction (mobile domain) & $\frac{mobile \; domain \; solid \; mass}{aquifer \; solid \; mass}$ & sorption-related parameter (not input; calculated by \mf from immobile-domain solid mass fractions) \\ + + $f_{im}$ & solid mass fraction (immobile domain $im$) & $\frac{immobile \; domain \; solid \; mass}{aquifer \; solid \; mass}$ & sorption-related parameter (not input; calculated by \mf from user-specified domain porosities) \\ + + \hline + \end{tabular} + \label{table:origparam} +\end{table} diff --git a/doc/SuppTechInfo/Tables/transport_params_revised.tex b/doc/SuppTechInfo/Tables/transport_params_revised.tex new file mode 100644 index 00000000000..b90fda88a26 --- /dev/null +++ b/doc/SuppTechInfo/Tables/transport_params_revised.tex @@ -0,0 +1,38 @@ +\begin{table}[!ht] + \small + \centering + \caption{Symbols, descriptions, and definitions of revised mobile and immobile domain model parameters introduced in \mf version 6.5.0. In the revised parameterization, division of the aquifer into domains is conceptualized in terms of volume fractions, and domain properties are defined on a per-domain-volume basis} \tabularnewline + + \begin{tabular}{z{1.50cm} + z{3.50cm} + z{5.00cm} + z{3.50cm} + } + % header + \hline + \rowcolor{Gray} + \multicolumn{1}{ z{1.50cm} }{\textbf{Symbol}} & + \multicolumn{1}{ z{3.50cm} }{\textbf{Description}} & + \multicolumn{1}{ z{5.00cm} }{\textbf{Definition}} & + \multicolumn{1}{ z{3.50cm} }{\textbf{Notes}} \\ + \hline + + $\phi_m$ & porosity (mobile domain) & $\frac{mobile \; domain \; pore \; volume}{mobile \; domain \; volume}$ & transport input parameter (not directly related to sorption) \\ + +\rowcolor{Gray} + $\phi_{im}$ & porosity (immobile domain $im$) & $\frac{immobile \; domain \; pore \; volume}{immobile \; domain \; volume}$ & transport input parameter (not directly related to sorption) \\ + + $\rho_{b,m}$ & bulk density (mobile domain) & $\frac{mobile \; domain \; solid \; mass}{mobile \; domain \; volume}$ & sorption-related input parameter \\ + +\rowcolor{Gray} + $\rho_{b,im}$ & bulk density (immobile domain $im$) & $\frac{immobile \; domain \; solid \; mass}{immobile \; domain \; volume}$ & sorption-related input parameter \\ + + $\hat{f}_{m}$ & volume fraction (mobile domain) & $\frac{mobile \; domain \; volume}{aquifer \; volume}$ & transport and sorption-related parameter (not input; calculated by \mf from immobile-domain volume fractions) \\ + +\rowcolor{Gray} + $\hat{f}_{im}$ & volume fraction (immobile domain $im$) & $\frac{immobile \; domain \; volume}{aquifer \; volume}$ & transport and sorption-related input parameter \\ + + \hline + \end{tabular} + \label{table:revparam} +\end{table} diff --git a/doc/SuppTechInfo/body.tex b/doc/SuppTechInfo/body.tex index 7ca521b6cdd..6d3795d7b64 100644 --- a/doc/SuppTechInfo/body.tex +++ b/doc/SuppTechInfo/body.tex @@ -49,7 +49,7 @@ \newpage \incchap -\SECTION{Chapter \thechapno. Revised Sorption Formulation for Combined Mobile and Immobile Domain Simulations} +\SECTION{Chapter \thechapno. Revised Parameterization of Transport for Combined Mobile and Immobile Domain Simulations} \customlabel{ch:sorption}{\thechapno} \input{sorption.tex} diff --git a/doc/SuppTechInfo/sorption.tex b/doc/SuppTechInfo/sorption.tex index 5b7831d43a2..8c0dc993295 100644 --- a/doc/SuppTechInfo/sorption.tex +++ b/doc/SuppTechInfo/sorption.tex @@ -1,16 +1,18 @@ -The Groundwater Transport (GWT) Model \citep{modflow6gwt} in \mf can simulate solute transport in aquifer material that includes ``mobile" and ``immobile" domains. In the mobile domain, which is always simulated by the GWT Model, the evolution of solute concentration is governed by the interplay between storage, advection, dispersion (including molecular diffusion), sorption, decay or production, sources and sinks, and exchange with any immobile domains that may exist. In an immobile domain, which may be optionally defined by the user, the evolution of solute concentration is governed by storage, sorption, decay or production, and exchange with the mobile domain. Advection and dispersion (including molecular diffusion) are considered negligible, and, aside from exchange with the mobile domain and optional zero- or first-order decay or production, there are no sources or sinks that add or remove solute mass directly to or from an immobile domain. Mobile and immobile domains are conceptualized as coexisting within a model cell, and the exchange of solute between domains represents a process occurring at the sub-grid scale. +The Groundwater Transport (GWT) Model \citep{modflow6gwt} in \mf can simulate a variety of solute-transport processes in aquifer material that includes ``mobile" and ``immobile" domains. The mobile and immobile domains are conceptualized as coexisting within a model cell and can exchange solute with each other. The mobile domain is always simulated by the GWT Model. Transport in an immobile domain, which may be optionally defined by the user, is simulated by the Immobile Storage and Transfer (IST) Package \citep{modflow6gwt} for the GWT Model. Multiple instances of the IST Package may be invoked to represent multiple immobile domains. -The Immobile Storage and Transfer (IST) Package \citep{modflow6gwt} for the GWT Model is designed to model the solute-transport processes occurring within a user-defined immobile domain and the exchange of solute between the immobile and mobile domains. Multiple instances of the IST Package may be invoked to represent multiple immobile domains. +In \mf version 6.5.0, the parameterization of the equations that govern transport in the mobile and immobile domains has been revised, and corresponding changes have been made to the input requirements for porosity, domain fraction, and bulk density. The revised parameterization is expected to be more intutive for users in many mobile-immobile transport applications. It will also allow users to model a wider variety of solute transport scenarios involving immobile-domain sorption than the original parameterization used in versions of \mf up to and including version 6.4.1. -The GWT model allows sorption, as well as decay of sorbed solute, to occur in the mobile and immobile domains. Since sorption involves interaction of solute with aquifer solid material, the rates at which sorption-related processes occur in a domain depend on the amount of solid mass that is in that domain. This chapter summarizes and clarifies sorption parameters for the mobile and immobile domains, and presents a generalized formulation that replaces the formulation presented in \cite{modflow6gwt}. The modified formulation should not have an effect on most existing GWT Models, except for those models that include one or more IST Packages with active sorption processes. +Input for existing \mf models that include one or more immobile domains can be converted from the original parameterization to the revised parameterization such that the simulated transport behavior remains the same. No changes to the model input are needed for existing \mf models that do not include an immobile domain. -\subsection{Governing Equations} \label{sec:goveqn1} +The remainder of this chapter begins with a review of the original parameterization of transport described in \citep{modflow6gwt}. The revised parameterization is then presented, and guidance is provided for converting existing model input from the original parameterization to the revised parameterization. -The GWT Model \citep{modflow6gwt} solves a discretized form of the following partial differential equation, which represents the conservation of solute mass at each point in the mobile domain: +\subsection{Review of the Original Parameterization} \label{sec:origparamreview} + +With the original parameterization used in versions of \mf up to and including version 6.4.1, the partial differential equation that represents the conservation of solute mass at points in the mobile domain is equation 2--1 of \cite{modflow6gwt}, which is reproduced here as \begin{equation} -\label{eqn:gwtpde} +\label{eqn:gwtpdeorig} \begin{aligned} \frac {\partial \left ( S_w \theta_m C \right )}{\partial t} = - \nabla \cdot \left ( \matr{q} C \right ) @@ -19,16 +21,16 @@ \subsection{Governing Equations} \label{sec:goveqn1} - \lambda_1 \theta_m S_w C - \gamma_1 \theta_m S_w \\ - f_m \rho_b \frac {\partial \left ( S_w \overline{C} \right ) }{\partial t} - \lambda_2 f_m \rho_b S_w \overline{C} - \gamma_2 f_m \rho_b S_w -- \sum \limits_{im=1}^{nim} \zeta_{im} S_w \left ( C - C_{im} \right ), +- \sum \limits_{im=1}^{nim} \zeta_{im} S_w \left ( C - C_{im} \right ) \end{aligned} \end{equation} -\noindent where $S_w$ is the water saturation (dimensionless) defined as the volume of water per volume of voids, $\theta_m$ is the effective porosity of the mobile domain (dimensionless), defined as volume of voids participating in mobile transport per unit volume of aquifer, $C$ is volumetric concentration of the mobile domain expressed as mass of dissolved solute per unit volume of fluid ($M/L^3$), $t$ is time ($T$), $\matr{q}$ is the vector of specific discharge ($L/T$), $\matr{D}$ is the second-order tensor of hydrodynamic dispersion coefficients ($L^2/T$), $q'_s$ is the volumetric flow rate per unit volume of aquifer (defined as positive for flow into the aquifer) for mass sources and sinks ($1/T$), $C_s$ is the volumetric solute concentration of the source or sink fluid ($M/L^3$), $M_s$ is rate of solute mass loading per unit volume of aquifer ($M/L^3T$), $\lambda_1$ is the first-order decay rate coefficient for the liquid phase ($1/T$), $\gamma_1$ is the zero-order decay rate coefficient for the liquid phase ($M/L^3T$), $\rho_b$ is the bulk density of the aquifer material ($M/L^3$), $\overline{C}$ is the sorbed concentration of solute mass in the mobile domain ($M/M$), $\lambda_2$ is the first-order decay rate coefficient ($1/T$) for the sorbed phase of the mobile domain, $\gamma_2$ is the zero-order decay rate coefficient for the sorbed phase of the mobile domain ($M/MT$), $nim$ is the number of immobile domains, $\zeta_{im}$ is the rate coefficient for the transfer of mass between the mobile domain and immobile domain $im$ ($1/T$), and $C_{im}$ is the solute concentration for immobile domain $im$ ($M/L^3$). In this chapter, the mobile porosity is intentionally given the $\theta_m$ symbol, which is different than the $\theta$ symbol used by \cite{modflow6gwt}. The definition of $f_m$, which appears in the three terms in equation~\ref{eqn:gwtpde} that relate to sorbed solute, is discussed and clarified later in this chapter. +\noindent where $S_w$ is the water saturation defined as the volume of water per volume of voids ($L^3/L^3$), $C$ is the mobile-domain volumetric concentration of solute expressed as mass of dissolved solute per unit volume of mobile-domain fluid ($M/L^3$), $t$ is time ($T$), $\matr{q}$ is the vector of specific discharge ($L/T$), $\matr{D}$ is the second-order tensor of hydrodynamic dispersion coefficients ($L^2/T$), $q'_s$ is the volumetric flow rate per unit volume of aquifer (defined as positive for flow into the aquifer) for mass sources and sinks ($1/T$), $C_s$ is the volumetric solute concentration of the source or sink fluid ($M/L^3$), $M_s$ is rate of solute mass loading per unit volume of aquifer ($M/L^3T$), $\lambda_1$ is the first-order decay rate coefficient for dissolved solute in the mobile domain ($1/T$), $\gamma_1$ is the zero-order decay rate coefficient for dissolved solute in the mobile domain ($M/L^3T$), $\overline{C}$ is the mass-fraction concentration of sorbate (sorbed solute) expressed as mass of sorbate per unit mass of solid aquifer material in the mobile domain ($M/M$), $\lambda_2$ is the first-order decay rate coefficient ($1/T$) for sorbate in the mobile domain, $\gamma_2$ is the zero-order decay rate coefficient ($M/MT$) for sorbate in the mobile domain, $nim$ is the number of immobile domains, $\zeta_{im}$ is the rate coefficient for the transfer of mass between the mobile domain and immobile domain $im$ ($1/T$), $C_{im}$ is an immobile-domain volumetric concentration of solute expressed as mass of dissolved solute per unit volume of fluid in immobile domain $im$ ($M/L^3$), $\rho_{b}$ is the bulk density of the aquifer material defined as the mass of solid aquifer material per unit volume of aquifer ($M/L^3$), $\theta_m$ is the mobile-domain effective porosity defined as defined as volume of voids participating in mobile-domain transport per unit volume of aquifer ($L^3/L^3$), and $f_m$ is the fraction of the mass of aquifer solid material that is in the mobile domain ($M/M$). To avoid potential confusion with the total porosity, in equation~\ref{eqn:gwtpdeorig} the mobile porosity is intentionally given the symbol $\theta_m$, which is different than the symbol $\theta$ used by \cite{modflow6gwt}. Also, note that \cite{modflow6gwt} define $f_m$ as ``the fraction of aquifer solid material available for sorptive exchange with the mobile phase under fully saturated conditions," which is correct only if all the aquifer solid material in the mobile domain is available for sorptive exchange and the fraction is understood to be a mass fraction. -The Immobile Storage and Transfer (IST) Package for the GWT Model allows users to designate a fraction of a model cell as immobile. Solute transport in an immobile domain is represented by a discretized form of +The Immobile Storage and Transfer (IST) Package for the GWT Model allows users to designate a fraction of a model cell as immobile. With the original parameterization used in versions of \mf up to and including version 6.4.1, the partial differential equation that represents the conservation of solute mass at points in an immobile domain is equation 7--2 of \cite{modflow6gwt}, which is reproduced here as \begin{equation} -\label{eqn:gwtistpde} +\label{eqn:gwtistpdeorig} \begin{split} \theta_{im} \frac{\partial C_{im} }{\partial t} + f_{im} \rho_b \frac{\partial \overline{C}_{im}}{\partial t} = - \lambda_{1,im} \theta_{im} C_{im} - \lambda_{2,im} f_{im} \rho_b \overline{C}_{im} \\ @@ -37,35 +39,27 @@ \subsection{Governing Equations} \label{sec:goveqn1} \end{split} \end{equation} -\noindent where $\theta_{im}$ is the volume of the immobile pores per volume of aquifer, $\overline{C}_{im}$ is the sorbed concentration of the immobile domain, expressed as the mass of the sorbed chemical per mass of solid, $\lambda_{1,im}$ is the first-order reaction rate coefficient for the liquid phase of the immobile domain ($1/T$), $\lambda_{2,im}$ is the first-order reaction rate coefficient for the sorbed phase of the immobile domain ($1/T$), $\gamma_{1,im}$ is the zero-order reaction rate coefficient for the liquid phase of the immobile domain ($ML^{-3}T^{-1}$), and $\gamma_{2,im}$ is the zero-order reaction rate coefficient for the sorbed phase of the immobile domain ($M M^{-1}T^{-1}$). The definition of $f_{im}$, which appears in the three terms in equation~\ref{eqn:gwtistpde} that relate to sorbed solute, is discussed and clarified below along with the definition of $f_m$. +\noindent where $\overline{C}_{im}$ is the mass-fraction concentration of sorbate (sorbed solute) expressed as mass of sorbate per unit mass of solid aquifer material in the immobile domain ($M/M$), $\lambda_{1,im}$ is the first-order reaction rate coefficient for dissolved solute in the immobile domain ($1/T$), $\lambda_{2,im}$ is the first-order reaction rate coefficient for sorbate in the immobile domain ($1/T$), $\gamma_{1,im}$ is the zero-order reaction rate coefficient for dissolved solute in the immobile domain ($ML^{-3}T^{-1}$), $\gamma_{2,im}$ is the zero-order reaction rate coefficient for sorbate in the immobile domain ($M M^{-1}T^{-1}$), $\theta_{im}$ is the effective porosity in the immobile domain defined as defined as volume of voids participating in immobile-domain transport per unit volume of immobile domain $im$ ($L^3/L^3$), and $f_{im}$ is the fraction of the mass of aquifer solid material that is in immobile domain $im$ ($M/M$). Note that \cite{modflow6gwt} define $f_{im}$ as ``the fraction of aquifer solid material available for sorptive exchange with the immobile domain under fully saturated conditions," which is correct only if all the aquifer solid material in the immobile domain is available for sorptive exchange and the fraction is understood to be a mass fraction. -\subsection{Definition of Solid Mass Fractions} \label{sec:solidmassfrac0} +The original model parameters that are affected by the revised parameterization are listed in table~\ref{table:origparam}. Note that in the original parameterization, division of the aquifer into mobile and immobile domains is conceptualized in terms solid mass fractions, and porosities and bulk densities are defined on a per-aquifer-volume basis. Note also that the user is required (in versions of \mf up to and including version 6.4.1) to provide the value of the overall bulk density, $\rho_b$, in the input for each package that represents a domain in which sorption is active (the MST Package for the mobile domain, and the IST Package for immobile domains). -\cite{modflow6gwt} define $f_m$ in equation~\ref{eqn:gwtpde} as ``the fraction of aquifer solid material available for sorptive exchange with the mobile phase under fully saturated conditions." This is correct assuming all of the aquifer solid material in the mobile domain is available for sorptive exchange with the mobile phase, and the fraction considered is the mass fraction. A more generally correct and complete definition of $f_m$ is the fraction of the mass of aquifer solid material that is in the mobile domain. The product $f_m \rho_b$ is then the mass of aquifer solid material in the mobile domain per unit volume of aquifer. This product forms the basis for expressing the three terms in equation~\ref{eqn:gwtpde} that relate to sorbed solute, each of which has units of (sorbed) solute mass per volume of aquifer per time. Note that concentration $\overline{C}$ in equation~\ref{eqn:gwtpde} has units of (sorbed) solute mass per mass of aquifer solid material in the mobile domain. +\input{./Tables/transport_params_original.tex} -Similarly, $f_{im}$ is most correctly and completely defined as the fraction of the mass of aquifer solid material that is in immobile domain $im$. The product $f_{im} \rho_b$ is then the mass of aquifer solid material in immobile domain $im$ per unit volume of aquifer. This product forms the basis for expressing the three terms in equation~\ref{eqn:gwtistpde} that relate to sorbed solute, each of which has units of (sorbed) solute mass per volume of aquifer per time. Note that concentration $\overline{C}_{im}$ in equation~\ref{eqn:gwtistpde} has units of (sorbed) solute mass per mass of aquifer solid material in immobile domain $im$. - -The solid mass fractions in the mobile and immobile domains sum to one. Therefore, the mobile solid mass fraction can be calculated from the immobile solid mass fractions according to +Immobile-domain solid mass fractions, $f_{im}$, are not input by the user; rather, they are calculated by \mf (in versions up to and including 6.4.1) from user-input porosities using \begin{equation} -\label{eqn:fm0} -f_m = 1 - \sum_{im}f_{im}, +\label{eqn:fim1} +f_{im} = \frac{\theta_{im}}{\theta_t}. \end{equation} -\noindent where the summation is over all immobile domains specified by the user. - -\subsection{Previous Approximation of Solid Mass Fractions} \label{sec:solidmassfrac1} - -Up to and including \mf version 6.4.1, the GWT Model automatically set immobile solid mass fractions to +\noindent where $\theta_t$ is the total porosity [which is erroneously represented by the symbol $\theta$ in the related text on p. 7--2 of \cite{modflow6gwt}]. The mobile-domain solid mass fraction is then calculated by \mf (in versions up to and including 6.4.1) using \begin{equation} -\label{eqn:fim1} -f_{im} = \frac{\theta_{im}}{\theta_t}, +\label{eqn:fm0} +f_m = 1 - \sum_{im}f_{im}, \end{equation} -\noindent where $\theta_{im}$ is the immobile domain porosity and $\theta_t$ is the total porosity. Note that the text on p. 7--2 of \cite{modflow6gwt} erroneously states that \mf sets ``$f_{im} = \theta_{im} / \theta$''. This statement is incorrect because the symbol in the denominator, ``$\theta$'', represents the mobile domain porosity rather than the total porosity, $\theta_t$, in \cite{modflow6gwt}. In actual fact, \mf used equation~\ref{eqn:fim1} to set $f_{im}$ values from user-supplied porosities. - -Equations~\ref{eqn:fm0} and~\ref{eqn:fim1}, together with the definition of total porosity, +\noindent where the summation is over all immobile domains specified by the user. Equations~\ref{eqn:fm0} and~\ref{eqn:fim1}, together with the definition of total porosity, \begin{equation} \label{eqn:thetat1} @@ -79,109 +73,136 @@ \subsection{Previous Approximation of Solid Mass Fractions} \label{sec:solidmass f_m = 1 - \sum_{im}f_{im} = 1 - \frac{\sum_{im}\theta_{im}}{\theta_t} = \frac{\theta_m}{\theta_t}. \end{equation} -\noindent If there are no immobile domains, the total porosity is the same as the mobile porosity and $f_m$ is one. +\noindent If there are no immobile domains, the total porosity is the same as the mobile porosity and $f_m$ is 1. -When considering the validity of equations~\ref{eqn:fim1} and~\ref{eqn:fm1}, it is important to note that the domain porosities are defined in terms of pore volume in a domain per volume of aquifer. Specifically, $\theta_m$ is the pore volume in the mobile domain per volume of aquifer (not per volume of mobile domain), and $\theta_{im}$ is the pore volume in immobile domain $im$ per volume of aquifer (not per volume of immobile domain $im$). When porosities are defined in this way, the ratios $\theta_m / \theta_t$ and $\theta_{im} / \theta_t$ can be thought of as ``pore volume fractions" in the same sense that $f_m$ and $f_{im}$ are solid mass fractions. Thus, underlying equations~\ref{eqn:fim1} and~\ref{eqn:fm1} is the assumption that the mass of aquifer solid material is distributed among the various domains in the same proportions as the pore volume is distributed among the domains. This is true, for example, when a volume of aquifer consists of a mobile domain and one immobile domain, and the local porosities (pore volume in the domain per volume of domain) and aquifer solid material densities are the same for both domains, which is a generalization of the example given by \cite{modflow6gwt}. In general, however, the aquifer solid mass need not be distributed between domains in the same proportions as pore volume, and equations~\ref{eqn:fim1} and~\ref{eqn:fm1} may not be good approximations in many cases of practical interest. As described below, the model input has been reformulated in terms of domain bulk densities to give the user the flexibility to specify how solid aquifer mass is distributed between domains. +\subsection{Revised Parameterization} \label{sec:revisedparam} -\subsection{Generalized Formulation Based on Domain Bulk Densities} \label{sec:solidmassfrac2b} +The revised parameterization described in this chapter differs from the original parameterization, which is described in \cite{modflow6gwt} and summarized above, in the following ways: -Up to and including \mf version 6.4.1 the user was required to provide a value for the overall bulk density, $\rho_b$, in the input for the MST Package. The user was also required to again provide the value for the overall bulk density in the input for each IST Package. A value of $f_{im}$ for each immobile domain was internally calculated by the program using equation~\ref{eqn:fim1}, and the value of $f_{m}$ for the mobile domain was internally calculated using equation~\ref{eqn:fm1}. The generalized formulation presented in this chapter redefines the bulk densities for which the user provides values in the MST and IST Packages. The generalized formulation is more flexible than the previous formulation and no longer requires that the aquifer solid mass be distributed between domains in the same proportions as pore volume as implied by equations~\ref{eqn:fim1} and ~\ref{eqn:fm1}. +\begin{itemize} +\item Two sets of parameter substitutions are implemented in governing equations~\ref{eqn:gwtpdeorig} and~\ref{eqn:gwtistpdeorig}. These substitutions recast the model input in terms of parameters that are expected be more intuitive for users in many applications. + \begin{itemize} + \item Domain porosities $\theta_{m}$ and $\theta_{im}$, which are defined on a per-aquifer-volume basis, are replaced by the mathematically equivalent expressions $\hat{f}_{m} \phi_{m}$ and $\hat{f}_{im} \phi_{im}$, respectively, where $\hat{f}_{m}$ and $\hat{f}_{im}$ are domain volume fractions (instead of mass fractions) and $\phi_{m}$ and $\phi_{im}$ are domain porosities defined on a per-domain-volume basis. + \item The parameter products $f_{m} \rho_{b}$ and $f_{im} \rho_{b}$ are replaced by the mathematically equivalent products $\hat{f}_{m} \rho_{b, m}$ and $\hat{f}_{im} \rho_{b, im}$, respectively, where $\rho_{b, m}$ and $\rho_{b, im}$ are domain bulk densities defined on a per-domain-volume basis. + \end{itemize} +\item Unlike the domain mass fractions $f_{m}$ and $f_{im}$, which are set automatically by \mf (in versions up to and including 6.4.1) based on domain porosities in the original parameterization, the domain volume fractions $\hat{f}_{m}$ and $\hat{f}_{im}$ are specified directly by the user in the revised parameterization, offering the flexibility to accurately characterize sorption in a wider variety of mobile-immobile systems. +\end{itemize} -In equations~\ref{eqn:gwtpde} and~\ref{eqn:gwtistpde}, $f_m$ and $f_{im}$ always appear multiplied by the bulk density, $\rho_b$. As noted earlier in this chapter, the products $f_m \rho_b$ and $f_{im} \rho_b$ are the masses of aquifer solid material in the mobile domain and immobile domain $im$, respectively, per unit volume of aquifer. Thus, $f_m \rho_b$ and $f_{im} \rho_b$ are bulk densities defined on a per-aquifer-volume basis: +With the changes to the parameterization summarized above, the partial differential equations that represent the conservation of solute mass at points in the mobile and immobile domains can be written as \begin{equation} -\label{eqn:rho_b_m_1} -f_m \rho_b = \frac{mobile \; domain \; solid \; mass}{aquifer \: volume} \equiv \rho_{b,m}, +\label{eqn:gwtpde} +\begin{aligned} +\frac {\partial \left ( S_w \theta_m C \right )}{\partial t} = +- \nabla \cdot \left ( \matr{q} C \right ) ++ \nabla \cdot \left ( S_w \theta_m \matr{D} \nabla C \right ) ++ q'_s C_s + M_s +- \lambda_1 \theta_m S_w C - \gamma_1 \theta_m S_w \\ +- \hat{f}_m \rho_{b,m} \frac {\partial \left ( S_w \overline{C} \right ) }{\partial t} +- \lambda_2 \hat{f}_m \rho_{b,m} S_w \overline{C} - \gamma_2 \hat{f}_m \rho_{b,m} S_w +- \sum \limits_{im=1}^{nim} \zeta_{im} S_w \left ( C - C_{im} \right ) +\end{aligned} \end{equation} -\noindent and +\noindent where \begin{equation} -\label{eqn:rho_b_im_1} -f_{im} \rho_b = \frac{immobile \; domain \; im \; solid \; mass}{aquifer \: volume} \equiv \rho_{b,im}, +\label{eqn:thetam_from_revparams} +\theta_{m} = \hat{f}_{m} \phi_{m} , \end{equation} -\noindent where $\rho_{b,m}$ and $\rho_{b,im}$ are the bulk densities for the mobile domain and immobile domain $im$, respectively. Note that the domain bulk densities are defined on a per-aquifer-volume basis and sum to the overall bulk density: +\noindent and \begin{equation} -\label{eqn:rho_b_1} -\rho_{b} = \rho_{b, m} + \sum_{im}{\rho_{b, im}}. +\label{eqn:gwtistpde} +\begin{split} +\theta_{im} \frac{\partial C_{im} }{\partial t} + \hat{f}_{im} \rho_{b,im} \frac{\partial \overline{C}_{im}}{\partial t} = +- \lambda_{1,im} \theta_{im} C_{im} - \lambda_{2,im} \hat{f}_{im} \rho_{b,im} \overline{C}_{im} \\ +- \gamma_{1,im} \theta_{im} - \gamma_{2,im} \hat{f}_{im} \rho_{b,im} ++ \zeta_{im} S_w \left ( C - C_{im} \right ), +\end{split} \end{equation} -With this new, generalized formulation users are required to specify $\rho_{b,m} \left ( \equiv f_m \rho_b \right )$ for the mobile domain in the MST Package and $\rho_{b,im} \left ( \equiv f_{im} \rho_b \right )$ for each immobile domain in the IST Package. There are no additional input parameters required for this generalized formulation; users continue to specify a value of bulk density in the MST Packages and each IST Package. Subsequent to these changes, however, the bulk densities entered into the MST and IST Packages are defined as domain bulk densities (according to equations~\ref{eqn:rho_b_m_1} and ~\ref{eqn:rho_b_im_1}) rather than multiple instances of the overall bulk density (which were required up through \mf version 6.4.1). +\noindent where -\subsection{Parameter Relations} \label{sec:solidmassfrac3} +\begin{equation} +\label{eqn:thetaim_from_revparams} +\theta_{im} = \hat{f}_{im} \phi_{im} . +\end{equation} -Successful application of the generalized formulation presented here for mobile and immobile domain sorption depends on proper interpretation and assignment of the different input parameters. Input parameters and related variables relevant to the generalized formulation are presented in table~\ref{table:sorptionparam1}. (As shown in equations~\ref{eqn:fm0},~\ref{eqn:thetat1} and~\ref{eqn:rho_b_1}, the domain solid mass fractions sum to 1, the domain porosities sum to the total porosity, $\theta_t$, and the domain bulk densities sum to the overall bulk density for the aquifer, $\rho_b$.) Note that division of the aquifer into mobile and immobile domains is conceptualized in terms solid mass fractions, and porosities and bulk densities are defined on a per-aquifer-volume basis. +\noindent respectively. Model parameters and variables in equations~\ref{eqn:gwtpde} and ~\ref{eqn:gwtistpde} are defined as in equations~\ref{eqn:gwtpdeorig} and ~\ref{eqn:gwtistpdeorig}, except for the new parameters introduced in the revised parameterization: $\hat{f}_m$ is the volume fraction of the mobile domain defined as the volume of mobile domain per volume of aquifer ($L^3/L^3$), $\hat{f}_{im}$ is the volume fraction of the immobile domain defined as the volume of mobile domain $im$ per volume of aquifer ($L^3/L^3$), $\rho_{b,m}$ is the bulk density of aquifer material in the mobile domain defined as mass of solid aquifer material per unit volume of mobile domain ($M/L^3$), $\rho_{b,im}$ is the bulk density of aquifer material in the immobile domain defined as mass of solid aquifer material per unit volume of immobile domain $im$ ($M/L^3$), $\phi_m$ is the mobile-domain effective porosity defined as defined as volume of voids participating in mobile-domain transport per unit volume of mobile domain ($L^3/L^3$), and $\phi_{im}$ is the effective porosity in the immobile domain defined as defined as volume of voids participating in immobile-domain transport per unit volume of mobile domain $im$ ($L^3/L^3$). In the \mf code, porosities $\theta_{m}$ and $\theta_{im}$ are calculated from the new input parameters using equations~\ref{eqn:thetam_from_revparams} and ~\ref{eqn:thetaim_from_revparams} before being incorporated into discretized representations of equations~\ref{eqn:gwtpde} and~\ref{eqn:gwtistpde}. The new parameters are discussed in more detail below. -\input{./Tables/sorption_params1.tex} +\subsubsection{Revised Parameters} -When an aquifer can be divided into multiple domains that occupy distinct, well-defined volumes, as when the mobile and immobile domains represent different lithologies, it may be intuitive to think in terms of the domain volume fractions and local domain properties (properties defined on a per-domain-volume basis) presented in table~\ref{table:sorptionparam2}. In that case, domain volume fractions and local domain properties must be converted by the user into domain mass fractions and per-aquifer-volume domain properties for input into \mf. This section presents the mathematical relations between the input parameters read by \mf and domain volume fractions and local domain properties. +The new parameters are summarized in table~\ref{table:revparam}. Note that in the revised parameterization, division of the aquifer into mobile and immobile domains is conceptualized in terms of volume fractions, and porosities and bulk densities are defined on a per-domain-volume basis. When an aquifer can be divided into multiple domains that occupy distinct, well-defined volumes, as when the mobile and immobile domains represent different lithologies, it may be intuitive to think in terms of the domain volume fractions and local domain properties (properties defined on a per-domain-volume basis). -\input{./Tables/sorption_params2.tex} +\input{./Tables/transport_params_revised.tex} -As with solid mass fractions, volume fractions for the mobile and immobile domains sum to one by definition, which implies that +\noindent All parameters in table~\ref{table:revparam} are model input parameters except the mobile-domain volume fraction, $\hat{f}_m$, which is calculated by \mf from the user-specified immobile-domain volume fractions using \begin{equation} \label{eqn:fm5} -\hat{f}_m = 1 - \sum_{im}{\hat{f}_{im}}. +\hat{f}_m = 1 - \sum_{im}{\hat{f}_{im}}, \end{equation} -\noindent The total porosity, $\theta_t$, is the volume-weighted average of the local domain porosities: +\noindent where the summation is over all immobile domains specified by the user. If there are no immobile domains, $\hat{f}_m$ is set to 1. + +Porosities and bulk densities in table~\ref{table:revparam} are defined on a per-domain-volume basis, i.e., as an amount per unit volume of domain. The total porosity, $\theta_t$, can be calculated as the volume-weighted average of the domain porosities, $\phi_m$ and $\phi_{im}$: \begin{equation} \label{eqn:thetat} -\theta_{t} = \hat{f}_{m} \phi_{m} + \sum_{im}{ \hat{f}_{im} \phi_{im}}, +\theta_{t} = \theta_{m} + \sum_{im}{\theta_{im}} = \hat{f}_{m} \phi_{m} + \sum_{im}{\hat{f}_{im} \phi_{im}} , \end{equation} -\noindent The overall bulk density for the aquifer, $\rho_b$, is the volume-weighted average of the local domain bulk densities: +\noindent Similarly, the overall bulk density, $\rho_b$, can be calculated as the volume-weighted average of the domain bulk densities, $\rho_{b, m}$ and $\rho_{b, im}$: \begin{equation} \label{eqn:rhob2} -\rho_{b} = \hat{f}_m \tilde{\rho}_{b, m} + \sum_{im}{\hat{f}_{im} \tilde{\rho}_{b, im}}. +\rho_{b} = \hat{f}_m \rho_{b, m} + \sum_{im}{\hat{f}_{im} \rho_{b, im}}. \end{equation} -% here present relations -Conversion between the alternative variables in table~\ref{table:sorptionparam2} to those used in \mf (table~\ref{table:sorptionparam1}) is straightforward. Local porosities for the mobile and immobile domains, denoted by $\phi_m$ and $\phi_{im}$, respectively, can be converted to mobile and immobile domain porosities defined on a per-aquifer-volume basis, denoted by $\theta_m$ and $\theta_{im}$, respectively, using +\subsection{Conversion of Existing Model Input to the Revised Parameterization} \label{sec:inputconversion} + +Conversion of existing \mf model input to the revised parameterization such that the simulation gives the same numerical results requires that the revised mobile-domain solute conservation equation, equation~\ref{eqn:gwtpde}, be numerically equivalent to the original mobile-domain solute conservation equation, equation~\ref{eqn:gwtpdeorig}. This is achieved when $\hat{f}_{m}$ and $\phi_{m}$ satisfy \begin{equation} -\label{eqn:theta1} -\theta_{m} = \hat{f}_{m} \phi_{m} +\label{eqn:equiv_porm} +\hat{f}_{m} \phi_{m} = \theta_{m} \end{equation} -\noindent and +\noindent and, if sorption is active in the mobile domain, $\hat{f}_{m}$ and $\rho_{b, m}$ satisfy \begin{equation} -\label{eqn:theta2} -\theta_{im} = \hat{f}_{im} \phi_{im}. +\label{eqn:equiv_rhobm} +\hat{f}_{m} \rho_{b, m} = f_{m} \rho_{b} . \end{equation} -\noindent Local bulk densities for the mobile and immobile domains, denoted by $\tilde{\rho}_{b, m}$ and $\tilde{\rho}_{b, im}$, respectively, can be converted to mobile and immobile domain bulk densities defined on a per-aquifer-volume basis, denoted by $\rho_{b, m}$ and $\rho_{b, im}$, respectively, using +\noindent For a model that includes at least one immobile domain, the revised immobile-domain solute conservation equation, equation~\ref{eqn:gwtistpde}, must also be numerically equivalent to the original immobile-domain solute conservation equation, equation~\ref{eqn:gwtistpdeorig}. This is achieved when $\hat{f}_{im}$ and $\phi_{im}$ satisfy \begin{equation} -\label{eqn:rhobm} -\rho_{b,m} = \hat{f}_m \tilde{\rho}_{b, m} +\label{eqn:equiv_porim} +\hat{f}_{im} \phi_{im} = \theta_{im} \end{equation} -\noindent and +\noindent and, if sorption is active in immobile domain $im$, $\hat{f}_{im}$ and $\rho_{b, im}$ satisfy \begin{equation} -\label{eqn:rhobim} -\rho_{b, im} = \hat{f}_{im} \tilde{\rho}_{b, im}. +\label{eqn:equiv_rhobim} +\hat{f}_{im} \rho_{b, im} = f_{im} \rho_{b} . \end{equation} -\noindent Volume fractions for the mobile and immobile domains, denoted by $\hat{f}_{m}$ and $\hat{f}_{im}$, respectively, can be converted to mobile and immobile domain solid mass fractions, denoted by $f_{m}$ and $f_{im}$, respectively, using +\subsubsection{Simulation Without Immobile Domains} -\begin{equation} -\label{eqn:fmfm} -f_{m} = \hat{f}_m \frac{\tilde{\rho}_{b, m}}{\rho_{b}} -\end{equation} +For an existing simulation without immobile domains, which is the most common use case, no conversion of input parameters is needed to obtain the same simulation results. In this case, \mf sets $f_{m} = 1$ in the original parameterization and $\hat{f}_{m} = 1$ in the revised parameterization. Thus, equations~\ref{eqn:equiv_porm} and~\ref{eqn:equiv_rhobm} reduce to $\phi_{m} = \theta_{m}$ and $\rho_{b, m} = \rho_{b}$, respectively, and are satisfied by default by the original input values. -\noindent and +\subsubsection{Simulation With At Least One Immobile Domain} -\begin{equation} -\label{eqn:fimfim} -f_{im} = \hat{f}_{im} \frac{\tilde{\rho}_{b, im}}{\rho_{b}}. -\end{equation} +For an existing simulation that includes at least one immobile domain, the values of the revised input parameters $\phi_{m}$, $\phi_{im}$, and $\hat{f}_{im}$ must be set such that they satisfy equations~\ref{eqn:equiv_porm} and~\ref{eqn:equiv_porim} in order to obtain the same simulation results. The value of $\hat{f}_{m}$ is calculated by \mf using equation~\ref{eqn:fm5}. Note that the immobile-domain fractions $\hat{f}_{im}$ must sum to less than one so that the resulting mobile-domain fraction, $\hat{f}_{m}$, is greater than zero. + +If the existing simulation also includes sorption, the value of the domain bulk density ($\rho_{b, m}$ or $\rho_{b, im}$) must be specified for each domain in which sorption is active. For a mobile domain with sorption, $\rho_{b, m}$ must satisfy equation~\ref{eqn:equiv_rhobm}. For an immobile domain with sorption, $\rho_{b, im}$ must satisfy equation~\ref{eqn:equiv_rhobim}. + +Because the revised parameterization involves more input parameters than the original parameterization, the choice of new parameter values that produce the same simulation results is not unique. One approach, for example, is to specify the values of the immobile domain fractions $\hat{f}_{im}$. The required values of $\hat{f}_{m}$, $\phi_{m}$, $\rho_{b, m}$, $\phi_{im}$, and $\rho_{b, im}$ are then determined by equations~\ref{eqn:fm5} and~\ref{eqn:equiv_porm}~--~\ref{eqn:equiv_rhobim}, respectively. If the only goal is to obtain the same simulation results, without regard for whether the revised parameter values are realistic or representative of the domains being modeled, one may, for example, set each immobile domain fraction to $\hat{f}_{im} = 1 / N_{dom}$, where $N_{dom}$ is the total number of domains including the mobile domain. + +\subsubsection{Unrealistic Parameter Values} -\noindent Note that although $f_{m}$ and $f_{im}$ appear in equations~\ref{eqn:gwtpde} and~\ref{eqn:gwtistpde}, they are not (and never have been) input parameters for \mf. Rather, it is the domain bulk densities, $\rho_{b, m} \left ( \equiv f_{m} \rho_b \right )$ and $\rho_{b, im} \left ( \equiv f_{im} \rho_b \right )$, that are input parameters in the new, generalized formulation of sorption. +It is possible that in some cases the process of converting parameters will produce revised parameter values that are unrealistic or inconsistent with independently measured or estimated values. For example, selection of apparently reasonable values of $\hat{f}_{im}$ may require setting $\hat{f}_{m}$, $\phi_{m}$, $\rho_{b, m}$, $\phi_{im}$, and/or $\rho_{b, im}$ to unreasonable values. Assuming the conversion equations have been applied correctly and the independently measured or estimated values are reasonably accurate, other possible sources of inconsistency are errors in setting the original parameter values; selection of unrealistic values for revised parameters ($\hat{f}_{im}$ values, for example) as a starting point for the conversion; and the setting of the original immobile domain fractions $f_{im}$ according to equation~\ref{eqn:fim1}, which may be a poor approxiation for some of the domains being modeled. \ No newline at end of file diff --git a/doc/mf6io/gwt/ist.tex b/doc/mf6io/gwt/ist.tex index 762b15a2c43..97e23397f12 100644 --- a/doc/mf6io/gwt/ist.tex +++ b/doc/mf6io/gwt/ist.tex @@ -1,4 +1,6 @@ -Immobile Storage and Transfer (IMD) Package information is read from the file that is specified by ``IST6'' as the file type. Any number of IST Packages can be specified for a single GWT model. This allows the user to specify triple porosity systems, or systems with as many immobile domains as necessary. +Immobile Storage and Transfer (IST) Package information is read from the file that is specified by ``IST6'' as the file type. Any number of IST Packages can be specified for a single GWT model. This allows the user to specify triple porosity systems, or systems with as many immobile domains as necessary. + +Subsequent to MODFLOW Version 6.4.1, substantial changes were made to the input parameter definitions and conceptualization of the IST Package. These changes are described in Chapter 9 of the MODFLOW 6 Supplemental Technical Information document that is included with the distribution. \vspace{5mm} \subsubsection{Structure of Blocks} diff --git a/doc/mf6io/mf6io.bbl b/doc/mf6io/mf6io.bbl index e69de29bb2d..240c6f51337 100644 --- a/doc/mf6io/mf6io.bbl +++ b/doc/mf6io/mf6io.bbl @@ -0,0 +1,248 @@ +\begin{thebibliography}{35} +\providecommand{\natexlab}[1]{#1} +\expandafter\ifx\csname urlstyle\endcsname\relax + \providecommand{\doiagency}[1]{doi:\discretionary{}{}{}#1}\else + \providecommand{\doiagency}{doi:\discretionary{}{}{}\begingroup + \urlstyle{rm}\Url}\fi + +\bibitem[{Anderman and Hill(2000)}]{anderman2000modflow} +Anderman, E.R., and Hill, M.C., 2000, MODFLOW-2000, the U.S. Geological Survey + modular ground-water model-documentation of the Hydrogeologic-Unit Flow (HUF) + Package: {U.S. Geological Survey Open-File Report 2000--342, 89 p.} + +\bibitem[{Anderman and Hill(2003)}]{anderman2003modflow} +Anderman, E.R., and Hill, M.C., 2003, MODFLOW-2000, the U.S. Geological Survey + modular ground-water model---Three additions to the Hydrogeologic-Unit Flow + (HUF) Package: Alternative storage for the uppermost active cells, flows in + hydrogeologic units, and the hydraulic-conductivity depth-dependence (KDEP) + capability: {U.S. Geological Survey Open-File Report 2003--347, 36 p.} + +\bibitem[{Bakker and others(2013)Bakker, Schaars, Hughes, Langevin, and + Dausman}]{bakker2013documentation} +Bakker, M., Schaars, F., Hughes, J.D., Langevin, C.D., and Dausman, A.M., 2013, + Documentation of the seawater intrusion (SWI2) package for MODFLOW: {U.S. + Geological Survey Techniques and Methods, book 6, chap. A46, 47 p.}, accessed + June 27, 2017, at \url{https://pubs.er.usgs.gov/publication/tm6A46}. + +\bibitem[{Banta(2000)}]{modflowdrtpack} +Banta, E.R., 2000, MODFLOW-2000, the U.S. Geological Survey Modular + Ground-Water Model; documentation of packages for simulating + evapotranspiration with a segmented function (ETS1) and drains with return + flow (DRT1): {U.S. Geological Survey Open File Report 2000--466, 127 p}. + +\bibitem[{Banta(2011)}]{banta2011modflow} +Banta, E.R., 2011, MODFLOW-CDSS, a version of MODFLOW-2005 with modifications + for Colorado Decision Support Systems: {U.S. Geological Survey Open-File + Report 2011--1213, 19 p.}, accessed June 27, 2017, at + \url{https://pubs.er.usgs.gov/publication/ofr20111213}. + +\bibitem[{Fenske and others(1996)Fenske, Leake, and + Prudic}]{fenske1996documentation} +Fenske, J.P., Leake, S.A., and Prudic, D.E., 1996, Documentation of a computer + program (RES1) to simulate leakage from reservoirs using the modular + finite-difference ground-water flow model (MODFLOW): {U.S. Geological Survey + Open-File Report 96--364, 51 p.}, accessed June 27, 2017, at + \url{https://pubs.er.usgs.gov/publication/ofr96364}. + +\bibitem[{Halford and Hanson(2002)}]{halford2002} +Halford, K.J., and Hanson, R.T., 2002, User guide for the drawdown-limited, + multi-node well (MNW) package for the U.S. Geological Survey's modular + three-dimensional finite-difference ground-water flow model, versions + MODFLOW-96 and MODFLOW-2000: {U.S. Geological Survey Open-File Report + 02--293, 33 p.} + +\bibitem[{Hanson and Leake(1999)}]{hanson1999documentation} +Hanson, R.T., and Leake, S.A., 1999, Documentation for HYDMOD---A program for + extracting and processing time-series data from the U.S. Geological Survey's + modular three-dimensional finite-difference ground-water flow model: {U.S. + Geological Survey Open-File Report 98--564, 57 p.}, accessed June 27, 2017, + at \url{https://pubs.er.usgs.gov/publication/ofr98564}. + +\bibitem[{Harbaugh(2005)}]{modflow2005} +Harbaugh, A.W., 2005, MODFLOW-2005, the U.S. Geological Survey modular + ground-water model---the Ground-Water Flow Process: {U.S. Geological Survey + Techniques and Methods, book 6, chap. A16, variously paged}, accessed June + 27, 2017, at \url{https://pubs.usgs.gov/tm/2005/tm6A16/}. + +\bibitem[{Hill(1990)}]{hill1990preconditioned} +Hill, M.C., 1990, Preconditioned Conjugate-Gradient 2 (PCG2), a computer + program for solving ground-water flow equations: {U.S. Geological Survey + Water-Resources Investigations Report 90--4048, 25 p.}, accessed June 27, + 2017, at \url{https://pubs.usgs.gov/wri/wrir_90-4048}. + +\bibitem[{Hill and others(2000)Hill, Banta, Harbaugh, and + Anderman}]{hill2000modflow} +Hill, M.C., Banta, E.R., Harbaugh, A.W., and Anderman, E.R., 2000, + MODFLOW-2000, the U.S. Geological Survey modular ground-water model---User + guide to the observation, sensitivity, and parameter-estimation processes and + three post-processing programs: {U.S. Geological Survey Open-File Report + 00--184, 210 p.} + +\bibitem[{Hoffmann and others(2003)Hoffmann, Leake, Galloway, and + Wilson}]{hoffmann2003modflow} +Hoffmann, J., Leake, S.A., Galloway, D.L., and Wilson, A.M., 2003, MODFLOW-2000 + Ground-Water Model---User Guide to the Subsidence and Aquifer-System + Compaction (SUB) Package: {U.S. Geological Survey Open-File Report 03--233, + 44 p.}, accessed June 27, 2017, at + \url{https://pubs.usgs.gov/of/2003/ofr03-233/}. + +\bibitem[{Hsieh and Freckleton(1993)}]{hsieh1993hfb} +Hsieh, P.A., and Freckleton, J.R., 1993, Documentation of a computer program to + simulate horizontal-flow barriers using the U.S. Geological Survey's modular + three-dimensional finite-difference ground-water flow model: {U.S. Geological + Survey Open-File Report 92--477, 32 p.}, accessed June 27, 2017, at + \url{https://pubs.er.usgs.gov/publication/ofr92477}. + +\bibitem[{Hughes and others(2012)Hughes, Langevin, Chartier, and + White}]{hughes2012documentation} +Hughes, J.D., Langevin, C.D., Chartier, K.L., and White, J.T., 2012, + Documentation of the Surface-Water Routing (SWR1) Process for modeling + surface-water flow with the U.S. Geological Survey modular groundwater model + (MODFLOW-2005): {U.S. Geological Survey Techniques and Methods, book 6, chap. + A40 (Version 1.0), 113 p.}, accessed June 27, 2017, at + \url{https://pubs.usgs.gov/tm/6a40/}. + +\bibitem[{Hughes and others(2017)Hughes, Langevin, and + Banta}]{modflow6framework} +Hughes, J.D., Langevin, C.D., and Banta, E.R., 2017, Documentation for the + MODFLOW 6 framework: {U.S. Geological Survey Techniques and Methods, book 6, + chap. A57, 36 p.}, \url{https://doi.org/10.3133/tm6A57}. + +\bibitem[{Hughes and others(2022{\natexlab{a}})Hughes, Russcher, Langevin, + Morway, and McDonald}]{modflow6api} +Hughes, J.D., Russcher, M.J., Langevin, C.D., Morway, E.D., and McDonald, R.R., + 2022{\natexlab{a}}, The {MODFLOW Application Programming Interface} for + simulation control and software interoperability: Environmental Modelling \& + Software, v. 148, article 105257, + \url{https://doi.org/10.1016/j.envsoft.2021.105257}. + +\bibitem[{Hughes and others(2022{\natexlab{b}})Hughes, Leake, Galloway, and + White}]{modflow6csub} +Hughes, J.D., Leake, S.A., Galloway, D.L., and White, J.T., 2022{\natexlab{b}}, + Documentation for the Skeletal Storage, Compaction, and Subsidence (CSUB) + Package of MODFLOW 6: {U.S. Geological Survey Techniques and Methods, book 6, + chap. A62, 57 p.}, \url{https://doi.org/10.3133/tm6A62}. + +\bibitem[{Konikow and others(2009)Konikow, Hornberger, Halford, and + Hanson}]{konikow2009} +Konikow, L.F., Hornberger, G.Z., Halford, K.J., and Hanson, R.T., 2009, Revised + multi-node well (MNW2) package for MODFLOW ground-water flow model: {U.S. + Geological Survey Techniques and Methods, book 6, chap. A30, 67 p.}, accessed + June 27, 2017, at \url{https://pubs.usgs.gov/tm/tm6a30/}. + +\bibitem[{Langevin and others(2008)Langevin, Thorne~Jr, Dausman, Sukop, and + Guo}]{langevin2008seawat} +Langevin, C.D., Thorne~Jr, D.T., Dausman, A.M., Sukop, M.C., and Guo, W., 2008, + {SEAWAT} Version 4---A computer program for simulation of multi-species + solute and heat transport: {U.S. Geological Survey Techniques and Methods, + book 6, chap. A22, 39 p.}, accessed June 27, 2017, at + \url{https://pubs.er.usgs.gov/publication/tm6A22}. + +\bibitem[{Langevin and others(2017)Langevin, Hughes, Provost, Banta, Niswonger, + and Panday}]{modflow6gwf} +Langevin, C.D., Hughes, J.D., Provost, A.M., Banta, E.R., Niswonger, R.G., and + Panday, S., 2017, Documentation for the MODFLOW 6 Groundwater Flow (GWF) + Model: {U.S. Geological Survey Techniques and Methods, book 6, chap. A55, 197 + p.}, \url{https://doi.org/10.3133/tm6A55}. + +\bibitem[{Langevin and others(2020)Langevin, Panday, and + Provost}]{langevin2020hydraulic} +Langevin, C.D., Panday, S., and Provost, A.M., 2020, Hydraulic-head formulation + for density-dependent flow and transport: Groundwater, v.~58, no.~3, + p.~349--362. + +\bibitem[{Langevin and others(2022)Langevin, Provost, Panday, and + Hughes}]{modflow6gwt} +Langevin, C.D., Provost, A.M., Panday, S., and Hughes, J.D., 2022, + Documentation for the MODFLOW 6 Groundwater Transport (GWT) Model: {U.S. + Geological Survey Techniques and Methods, book 6, chap. A61, 56 p.}, + \url{https://doi.org/10.3133/tm6A61}. + +\bibitem[{Leake and Galloway(2007)}]{leake2007modflow} +Leake, S.A., and Galloway, D.L., 2007, MODFLOW Ground-water model---User guide + to the Subsidence and Aquifer-System Compaction Package (SUB-WT) for + Water-Table Aquifers: {U.S. Geological Survey Techniques and Methods, book 6, + chap. A23, 42 p.}, accessed June 27, 2017, at + \url{https://pubs.er.usgs.gov/publication/tm6A23}. + +\bibitem[{Leake and Lilly(1997)}]{leake1997documentation} +Leake, S.A., and Lilly, M.R., 1997, Documentation of computer program (FHB1) + for assignment of transient specified-flow and specified-head boundaries in + applications of the modular finite-diference ground-water flow model + (MODFLOW): {U.S. Geological Survey Open-File Report 97--571, 50 p.}, accessed + June 27, 2017, at \url{https://pubs.er.usgs.gov/publication/ofr97571}. + +\bibitem[{Maddock and others(2012)Maddock, Baird, Hanson, Schmid, and + Ajami}]{modflowripetpack} +Maddock, Thomas, I., Baird, K.J., Hanson, R.T., Schmid, W., and Ajami, H., + 2012, RIP-ET---A Riparian Evapotranspiration Package for MODFLOW-2005: {U.S. + Geological Survey Techniques and Methods, book 6, chap. A39, 76 p.}, accessed + June 27, 2017, at \url{https://pubs.usgs.gov/tm/tm6a39/}. + +\bibitem[{Merritt and Konikow(2000)}]{modflowlak3pack} +Merritt, M.L., and Konikow, L.F., 2000, Documentation of a computer program to + simulate lake-aquifer interaction using the MODFLOW ground-water flow model + and the MOC3D solute-transport model: {U.S. Geological Survey Water-Resources + Investigations Report 00--4167, 146 p.}, accessed June 27, 2017, at + \url{https://pubs.er.usgs.gov/publication/wri004167}. + +\bibitem[{Niswonger and Prudic(2005)}]{modflowsfr2pack} +Niswonger, R.G., and Prudic, D.E., 2005, Documentation of the + Streamflow-Routing (SFR2) Package to include unsaturated flow beneath + streams---A modification to SFR1: {U.S. Geological Survey Techniques and + Methods, book 6, chap. A13, 50 p.}, accessed June 27, 2017, at + \url{https://pubs.er.usgs.gov/publication/tm6A13}. + +\bibitem[{Niswonger and others(2006)Niswonger, Prudic, and Regan}]{UZF} +Niswonger, R.G., Prudic, D.E., and Regan, R.S., 2006, Documentation of the + Unsaturated-Zone Flow (UZF1) Package for modeling unsaturated flow between + the land surface and the water table with {MODFLOW}-2005: {U.S. Geological + Survey Techniques and Methods, book 6, chap. A19, 62 p.}, accessed June 27, + 2017, at \url{https://pubs.usgs.gov/tm/2006/tm6a19/}. + +\bibitem[{Panday and others(2013)Panday, Langevin, Niswonger, Ibaraki, and + Hughes}]{modflowusg} +Panday, S., Langevin, C.D., Niswonger, R.G., Ibaraki, M., and Hughes, J.D., + 2013, MODFLOW-USG version 1---An unstructured grid version of MODFLOW for + simulating groundwater flow and tightly coupled processes using a control + volume finite-difference formulation: {U.S. Geological Survey Techniques and + Methods, book 6, chap. A45, 66 p.}, accessed June 27, 2017, at + \url{https://pubs.usgs.gov/tm/06/a45/}. + +\bibitem[{Provost and others(2017)Provost, Langevin, and Hughes}]{modflow6xt3d} +Provost, A.M., Langevin, C.D., and Hughes, J.D., 2017, Documentation for the + ``XT3D'' Option in the Node Property Flow (NPF) Package of MODFLOW 6: {U.S. + Geological Survey Techniques and Methods, book 6, chap. A56, 46 p.}, + \url{https://doi.org/10.3133/tm6A56}. + +\bibitem[{Prudic(1989)}]{prudic1989str} +Prudic, D.E., 1989, Documentation of a computer program to simulate + stream-aquifer relations using a modular, finite-difference, ground-water + flow model: {U.S. Geological Survey Open-File Report 88--729, 113 p.}, + accessed June 27, 2017, at + \url{https://pubs.er.usgs.gov/publication/ofr88729}. + +\bibitem[{Prudic and others(2004)Prudic, Konikow, and Banta}]{modflowsfr1pack} +Prudic, D.E., Konikow, L.F., and Banta, E.R., 2004, A New Streamflow-Routing + (SFR1) Package to simulate stream-aquifer interaction with MODFLOW-2000: + {U.S. Geological Survey Open File Report 2004--1042, 104 p.}, accessed June + 27, 2017, at \url{https://pubs.er.usgs.gov/publication/ofr20041042}. + +\bibitem[{Voss(1984)}]{Voss1984sutra} +Voss, C.I., 1984, SUTRA---A finite-element simulation model for + saturated-unsaturated fluid-density-dependent ground-water flow with energy + transport or chemically-reactive single-species solute transport: {U.S. + Geological Survey Water-Resources Investigations Report 84--4369, 409 p.} + +\bibitem[{Zheng(2010)}]{zheng2010supplemental} +Zheng, C., 2010, MT3DMS v5.3, Supplemental User's Guide: {Technical Report + Prepared for the U.S. Army Corps of Engineers, 51 p.} + +\bibitem[{Zheng and others(2001)Zheng, Hill, and Hsieh}]{zheng2001modflow} +Zheng, C., Hill, M.C., and Hsieh, P.A., 2001, MODFLOW-2000, the U.S. Geological + Survey Modular Ground-Water Model---User guide to the LMT6 package, the + linkage with MT3DMS for multi-species mass transport modeling: {U.S. + Geological Survey Open-File Report 01--82, 43 p.}, accessed June 27, 2017, at + \url{https://pubs.er.usgs.gov/publication/ofr0182}. + +\end{thebibliography} diff --git a/doc/mf6io/mf6ivar/dfn/gwt-ist.dfn b/doc/mf6io/mf6ivar/dfn/gwt-ist.dfn index 9af818288e6..7026755d810 100644 --- a/doc/mf6io/mf6ivar/dfn/gwt-ist.dfn +++ b/doc/mf6io/mf6ivar/dfn/gwt-ist.dfn @@ -231,23 +231,22 @@ description write format can be EXPONENTIAL, FIXED, GENERAL, or SCIENTIFIC. # --------------------- gwt ist griddata --------------------- block griddata -name cim +name porosity type double precision shape (nodes) reader readarray -optional true layered true -longname initial concentration of the immobile domain -description initial concentration of the immobile domain in mass per length cubed. If CIM is not specified, then it is assumed to be zero. +longname porosity of the immobile domain +description porosity of the immobile domain specified as the immobile domain pore volume per immobile domain volume. block griddata -name thetaim +name volfrac type double precision shape (nodes) reader readarray layered true -longname porosity of the immobile domain -description porosity of the immobile domain specified as the volume of immobile pore space per total volume (dimensionless). +longname volume fraction of this immobile domain +description fraction of the cell volume that consists of this immobile domain. The sum of all immobile domain volume fractions must be less than one. block griddata name zetaim @@ -258,6 +257,16 @@ layered true longname mass transfer rate coefficient between the mobile and immobile domains description mass transfer rate coefficient between the mobile and immobile domains, in dimensions of per time. +block griddata +name cim +type double precision +shape (nodes) +reader readarray +optional true +layered true +longname initial concentration of the immobile domain +description initial concentration of the immobile domain in mass per length cubed. If CIM is not specified, then it is assumed to be zero. + block griddata name decay type double precision @@ -283,16 +292,18 @@ name bulk_density type double precision shape (nodes) reader readarray +optional true layered true longname bulk density -description is the bulk density of the aquifer in mass per length cubed. bulk\_density will have no effect on simulation results unless the SORPTION keyword is specified in the options block. Bulk density is defined as the immobile domain solid mass per aquifer volume. The definition for this input variable changed after version 6.4.1 was released. The bulk density specified in this package is defined differently from the bulk density defined in the Mobile Storage and Transfer (MST) Package. Additional information on bulk density is included in the MODFLOW 6 Supplemental Technical Information document. +description is the bulk density of this immobile domain in mass per length cubed. Bulk density is defined as the immobile domain solid mass per volume of the immobile domain. bulk\_density is not required unless the SORPTION keyword is specified in the options block. If the SORPTION keyword is not specified in the options block, bulk\_density will have no effect on simulation results. block griddata name distcoef type double precision shape (nodes) reader readarray +optional true layered true longname distribution coefficient -description is the distribution coefficient for the equilibrium-controlled linear sorption isotherm in dimensions of length cubed per mass. distcoef will have no effect on simulation results unless the SORPTION keyword is specified in the options block. +description is the distribution coefficient for the equilibrium-controlled linear sorption isotherm in dimensions of length cubed per mass. distcoef is not required unless the SORPTION keyword is specified in the options block. If the SORPTION keyword is not specified in the options block, distcoef will have no effect on simulation results. diff --git a/doc/mf6io/mf6ivar/dfn/gwt-mst.dfn b/doc/mf6io/mf6ivar/dfn/gwt-mst.dfn index d3d9bee0689..06e5169f816 100644 --- a/doc/mf6io/mf6ivar/dfn/gwt-mst.dfn +++ b/doc/mf6io/mf6ivar/dfn/gwt-mst.dfn @@ -42,7 +42,7 @@ shape (nodes) reader readarray layered true longname porosity -description is the aquifer porosity. +description is the mobile domain porosity, defined as the mobile domain pore volume per mobile domain volume. Additional information on porosity within the context of mobile and immobile domain transport simulations is included in the MODFLOW 6 Supplemental Technical Information document. block griddata name decay @@ -72,7 +72,7 @@ reader readarray optional true layered true longname bulk density -description is the bulk density of the aquifer in mass per length cubed. bulk\_density is not required unless the SORPTION keyword is specified. Bulk density is defined as the mobile domain solid mass per aquifer volume. The definition for this input variable changed after version 6.4.1 was released. The bulk density specified here is defined differently from the bulk density defined in the Immobile Storage and Transfer (IST) Package. Additional information on bulk density is included in the MODFLOW 6 Supplemental Technical Information document. +description is the bulk density of the aquifer in mass per length cubed. bulk\_density is not required unless the SORPTION keyword is specified. Bulk density is defined as the mobile domain solid mass per mobile domain volume. Additional information on bulk density is included in the MODFLOW 6 Supplemental Technical Information document. block griddata name distcoef diff --git a/doc/mf6io/mf6ivar/md/mf6ivar.md b/doc/mf6io/mf6ivar/md/mf6ivar.md index 1603d491f61..bf50dddc82e 100644 --- a/doc/mf6io/mf6ivar/md/mf6ivar.md +++ b/doc/mf6io/mf6ivar/md/mf6ivar.md @@ -6,6 +6,7 @@ | SIM | NAM | OPTIONS | NOCHECK | KEYWORD | keyword flag to indicate that the model input check routines should not be called prior to each time step. Checks are performed by default. | | SIM | NAM | OPTIONS | MEMORY_PRINT_OPTION | STRING | is a flag that controls printing of detailed memory manager usage to the end of the simulation list file. NONE means do not print detailed information. SUMMARY means print only the total memory for each simulation component. ALL means print information for each variable stored in the memory manager. NONE is default if MEMORY\_PRINT\_OPTION is not specified. | | SIM | NAM | OPTIONS | MAXERRORS | INTEGER | maximum number of errors that will be stored and printed. | +| SIM | NAM | OPTIONS | PRINT_INPUT | KEYWORD | keyword to activate printing of simulation input summaries to the simulation list file (mfsim.lst). With this keyword, input summaries will be written for those packages that support newer input data model routines. Not all packages are supported yet by the newer input data model routines. | | SIM | NAM | TIMING | TDIS6 | STRING | is the name of the Temporal Discretization (TDIS) Input File. | | SIM | NAM | MODELS | MTYPE | STRING | is the type of model to add to simulation. | | SIM | NAM | MODELS | MFNAME | STRING | is the file name of the model name file. | @@ -867,8 +868,8 @@ | GWT | DISV | DIMENSIONS | NCPL | INTEGER | is the number of cells per layer. This is a constant value for the grid and it applies to all layers. | | GWT | DISV | DIMENSIONS | NVERT | INTEGER | is the total number of (x, y) vertex pairs used to characterize the horizontal configuration of the model grid. | | GWT | DISV | GRIDDATA | TOP | DOUBLE PRECISION (NCPL) | is the top elevation for each cell in the top model layer. | -| GWT | DISV | GRIDDATA | BOTM | DOUBLE PRECISION (NLAY, NCPL) | is the bottom elevation for each cell. | -| GWT | DISV | GRIDDATA | IDOMAIN | INTEGER (NLAY, NCPL) | is an optional array that characterizes the existence status of a cell. If the IDOMAIN array is not specified, then all model cells exist within the solution. If the IDOMAIN value for a cell is 0, the cell does not exist in the simulation. Input and output values will be read and written for the cell, but internal to the program, the cell is excluded from the solution. If the IDOMAIN value for a cell is 1, the cell exists in the simulation. If the IDOMAIN value for a cell is -1, the cell does not exist in the simulation. Furthermore, the first existing cell above will be connected to the first existing cell below. This type of cell is referred to as a ``vertical pass through'' cell. | +| GWT | DISV | GRIDDATA | BOTM | DOUBLE PRECISION (NCPL, NLAY) | is the bottom elevation for each cell. | +| GWT | DISV | GRIDDATA | IDOMAIN | INTEGER (NCPL, NLAY) | is an optional array that characterizes the existence status of a cell. If the IDOMAIN array is not specified, then all model cells exist within the solution. If the IDOMAIN value for a cell is 0, the cell does not exist in the simulation. Input and output values will be read and written for the cell, but internal to the program, the cell is excluded from the solution. If the IDOMAIN value for a cell is 1, the cell exists in the simulation. If the IDOMAIN value for a cell is -1, the cell does not exist in the simulation. Furthermore, the first existing cell above will be connected to the first existing cell below. This type of cell is referred to as a ``vertical pass through'' cell. | | GWT | DISV | VERTICES | IV | INTEGER | is the vertex number. Records in the VERTICES block must be listed in consecutive order from 1 to NVERT. | | GWT | DISV | VERTICES | XV | DOUBLE PRECISION | is the x-coordinate for the vertex. | | GWT | DISV | VERTICES | YV | DOUBLE PRECISION | is the y-coordinate for the vertex. | @@ -965,10 +966,10 @@ | GWT | MST | OPTIONS | FIRST_ORDER_DECAY | KEYWORD | is a text keyword to indicate that first-order decay will occur. Use of this keyword requires that DECAY and DECAY\_SORBED (if sorption is active) are specified in the GRIDDATA block. | | GWT | MST | OPTIONS | ZERO_ORDER_DECAY | KEYWORD | is a text keyword to indicate that zero-order decay will occur. Use of this keyword requires that DECAY and DECAY\_SORBED (if sorption is active) are specified in the GRIDDATA block. | | GWT | MST | OPTIONS | SORPTION | STRING | is a text keyword to indicate that sorption will be activated. Valid sorption options include LINEAR, FREUNDLICH, and LANGMUIR. Use of this keyword requires that BULK\_DENSITY and DISTCOEF are specified in the GRIDDATA block. If sorption is specified as FREUNDLICH or LANGMUIR then SP2 is also required in the GRIDDATA block. | -| GWT | MST | GRIDDATA | POROSITY | DOUBLE PRECISION (NODES) | is the aquifer porosity. | +| GWT | MST | GRIDDATA | POROSITY | DOUBLE PRECISION (NODES) | is the mobile domain porosity, defined as the mobile domain pore volume per mobile domain volume. Additional information on porosity within the context of mobile and immobile domain transport simulations is included in the MODFLOW 6 Supplemental Technical Information document. | | GWT | MST | GRIDDATA | DECAY | DOUBLE PRECISION (NODES) | is the rate coefficient for first or zero-order decay for the aqueous phase of the mobile domain. A negative value indicates solute production. The dimensions of decay for first-order decay is one over time. The dimensions of decay for zero-order decay is mass per length cubed per time. decay will have no effect on simulation results unless either first- or zero-order decay is specified in the options block. | | GWT | MST | GRIDDATA | DECAY_SORBED | DOUBLE PRECISION (NODES) | is the rate coefficient for first or zero-order decay for the sorbed phase of the mobile domain. A negative value indicates solute production. The dimensions of decay\_sorbed for first-order decay is one over time. The dimensions of decay\_sorbed for zero-order decay is mass of solute per mass of aquifer per time. If decay\_sorbed is not specified and both decay and sorption are active, then the program will terminate with an error. decay\_sorbed will have no effect on simulation results unless the SORPTION keyword and either first- or zero-order decay are specified in the options block. | -| GWT | MST | GRIDDATA | BULK_DENSITY | DOUBLE PRECISION (NODES) | is the bulk density of the aquifer in mass per length cubed. bulk\_density is not required unless the SORPTION keyword is specified. Bulk density is defined as the mobile domain solid mass per aquifer volume. The definition for this input variable changed after version 6.4.1 was released. The bulk density specified here is defined differently from the bulk density defined in the Immobile Storage and Transfer (IST) Package. Additional information on bulk density is included in the MODFLOW 6 Supplemental Technical Information document. | +| GWT | MST | GRIDDATA | BULK_DENSITY | DOUBLE PRECISION (NODES) | is the bulk density of the aquifer in mass per length cubed. bulk\_density is not required unless the SORPTION keyword is specified. Bulk density is defined as the mobile domain solid mass per mobile domain volume. Additional information on bulk density is included in the MODFLOW 6 Supplemental Technical Information document. | | GWT | MST | GRIDDATA | DISTCOEF | DOUBLE PRECISION (NODES) | is the distribution coefficient for the equilibrium-controlled linear sorption isotherm in dimensions of length cubed per mass. distcoef is not required unless the SORPTION keyword is specified. | | GWT | MST | GRIDDATA | SP2 | DOUBLE PRECISION (NODES) | is the exponent for the Freundlich isotherm and the sorption capacity for the Langmuir isotherm. | | GWT | IST | OPTIONS | SAVE_FLOWS | KEYWORD | keyword to indicate that IST flow terms will be written to the file specified with ``BUDGET FILEOUT'' in Output Control. | @@ -987,13 +988,14 @@ | GWT | IST | OPTIONS | WIDTH | INTEGER | width for writing each number. | | GWT | IST | OPTIONS | DIGITS | INTEGER | number of digits to use for writing a number. | | GWT | IST | OPTIONS | FORMAT | STRING | write format can be EXPONENTIAL, FIXED, GENERAL, or SCIENTIFIC. | -| GWT | IST | GRIDDATA | CIM | DOUBLE PRECISION (NODES) | initial concentration of the immobile domain in mass per length cubed. If CIM is not specified, then it is assumed to be zero. | -| GWT | IST | GRIDDATA | THETAIM | DOUBLE PRECISION (NODES) | porosity of the immobile domain specified as the volume of immobile pore space per total volume (dimensionless). | +| GWT | IST | GRIDDATA | POROSITY | DOUBLE PRECISION (NODES) | porosity of the immobile domain specified as the immobile domain pore volume per immobile domain volume. | +| GWT | IST | GRIDDATA | VOLFRAC | DOUBLE PRECISION (NODES) | fraction of the cell volume that consists of this immobile domain. The sum of all immobile domain volume fractions must be less than one. | | GWT | IST | GRIDDATA | ZETAIM | DOUBLE PRECISION (NODES) | mass transfer rate coefficient between the mobile and immobile domains, in dimensions of per time. | +| GWT | IST | GRIDDATA | CIM | DOUBLE PRECISION (NODES) | initial concentration of the immobile domain in mass per length cubed. If CIM is not specified, then it is assumed to be zero. | | GWT | IST | GRIDDATA | DECAY | DOUBLE PRECISION (NODES) | is the rate coefficient for first or zero-order decay for the aqueous phase of the immobile domain. A negative value indicates solute production. The dimensions of decay for first-order decay is one over time. The dimensions of decay for zero-order decay is mass per length cubed per time. Decay will have no effect on simulation results unless either first- or zero-order decay is specified in the options block. | | GWT | IST | GRIDDATA | DECAY_SORBED | DOUBLE PRECISION (NODES) | is the rate coefficient for first or zero-order decay for the sorbed phase of the immobile domain. A negative value indicates solute production. The dimensions of decay\_sorbed for first-order decay is one over time. The dimensions of decay\_sorbed for zero-order decay is mass of solute per mass of aquifer per time. If decay\_sorbed is not specified and both decay and sorption are active, then the program will terminate with an error. decay\_sorbed will have no effect on simulation results unless the SORPTION keyword and either first- or zero-order decay are specified in the options block. | -| GWT | IST | GRIDDATA | BULK_DENSITY | DOUBLE PRECISION (NODES) | is the bulk density of the aquifer in mass per length cubed. bulk\_density will have no effect on simulation results unless the SORPTION keyword is specified in the options block. Bulk density is defined as the immobile domain solid mass per aquifer volume. The definition for this input variable changed after version 6.4.1 was released. The bulk density specified in this package is defined differently from the bulk density defined in the Mobile Storage and Transfer (MST) Package. Additional information on bulk density is included in the MODFLOW 6 Supplemental Technical Information document. | -| GWT | IST | GRIDDATA | DISTCOEF | DOUBLE PRECISION (NODES) | is the distribution coefficient for the equilibrium-controlled linear sorption isotherm in dimensions of length cubed per mass. distcoef will have no effect on simulation results unless the SORPTION keyword is specified in the options block. | +| GWT | IST | GRIDDATA | BULK_DENSITY | DOUBLE PRECISION (NODES) | is the bulk density of this immobile domain in mass per length cubed. Bulk density is defined as the immobile domain solid mass per volume of the immobile domain. bulk\_density is not required unless the SORPTION keyword is specified in the options block. If the SORPTION keyword is not specified in the options block, bulk\_density will have no effect on simulation results. | +| GWT | IST | GRIDDATA | DISTCOEF | DOUBLE PRECISION (NODES) | is the distribution coefficient for the equilibrium-controlled linear sorption isotherm in dimensions of length cubed per mass. distcoef is not required unless the SORPTION keyword is specified in the options block. If the SORPTION keyword is not specified in the options block, distcoef will have no effect on simulation results. | | GWT | SFT | OPTIONS | FLOW_PACKAGE_NAME | STRING | keyword to specify the name of the corresponding flow package. If not specified, then the corresponding flow package must have the same name as this advanced transport package (the name associated with this package in the GWT name file). | | GWT | SFT | OPTIONS | AUXILIARY | STRING (NAUX) | defines an array of one or more auxiliary variable names. There is no limit on the number of auxiliary variables that can be provided on this line; however, lists of information provided in subsequent blocks must have a column of data for each auxiliary variable name defined here. The number of auxiliary variables detected on this line determines the value for naux. Comments cannot be provided anywhere on this line as they will be interpreted as auxiliary variable names. Auxiliary variables may not be used by the package, but they will be available for use by other parts of the program. The program will terminate with an error if auxiliary variables are specified on more than one line in the options block. | | GWT | SFT | OPTIONS | FLOW_PACKAGE_AUXILIARY_NAME | STRING | keyword to specify the name of an auxiliary variable in the corresponding flow package. If specified, then the simulated concentrations from this advanced transport package will be copied into the auxiliary variable specified with this name. Note that the flow package must have an auxiliary variable with this name or the program will terminate with an error. If the flows for this advanced transport package are read from a file, then this option will have no effect. | diff --git a/doc/mf6io/mf6ivar/tex/gwf-sfr-period.dat b/doc/mf6io/mf6ivar/tex/gwf-sfr-period.dat index 06390501c16..40fc6597787 100644 --- a/doc/mf6io/mf6ivar/tex/gwf-sfr-period.dat +++ b/doc/mf6io/mf6ivar/tex/gwf-sfr-period.dat @@ -2,5 +2,4 @@ BEGIN PERIOD ... - CROSS_SECTION TAB6 FILEIN END PERIOD diff --git a/doc/mf6io/mf6ivar/tex/gwt-disv-griddata.dat b/doc/mf6io/mf6ivar/tex/gwt-disv-griddata.dat index a9db9563a42..e263cb1d7bb 100644 --- a/doc/mf6io/mf6ivar/tex/gwt-disv-griddata.dat +++ b/doc/mf6io/mf6ivar/tex/gwt-disv-griddata.dat @@ -2,7 +2,7 @@ BEGIN GRIDDATA TOP -- READARRAY BOTM [LAYERED] - -- READARRAY + -- READARRAY [IDOMAIN [LAYERED] - -- READARRAY] + -- READARRAY] END GRIDDATA diff --git a/doc/mf6io/mf6ivar/tex/gwt-ist-desc.tex b/doc/mf6io/mf6ivar/tex/gwt-ist-desc.tex index 8959d526f4f..dca064cfe94 100644 --- a/doc/mf6io/mf6ivar/tex/gwt-ist-desc.tex +++ b/doc/mf6io/mf6ivar/tex/gwt-ist-desc.tex @@ -39,19 +39,21 @@ \item \textbf{Block: GRIDDATA} \begin{description} -\item \texttt{cim}---initial concentration of the immobile domain in mass per length cubed. If CIM is not specified, then it is assumed to be zero. +\item \texttt{porosity}---porosity of the immobile domain specified as the immobile domain pore volume per immobile domain volume. -\item \texttt{thetaim}---porosity of the immobile domain specified as the volume of immobile pore space per total volume (dimensionless). +\item \texttt{volfrac}---fraction of the cell volume that consists of this immobile domain. The sum of all immobile domain volume fractions must be less than one. \item \texttt{zetaim}---mass transfer rate coefficient between the mobile and immobile domains, in dimensions of per time. +\item \texttt{cim}---initial concentration of the immobile domain in mass per length cubed. If CIM is not specified, then it is assumed to be zero. + \item \texttt{decay}---is the rate coefficient for first or zero-order decay for the aqueous phase of the immobile domain. A negative value indicates solute production. The dimensions of decay for first-order decay is one over time. The dimensions of decay for zero-order decay is mass per length cubed per time. Decay will have no effect on simulation results unless either first- or zero-order decay is specified in the options block. \item \texttt{decay\_sorbed}---is the rate coefficient for first or zero-order decay for the sorbed phase of the immobile domain. A negative value indicates solute production. The dimensions of decay\_sorbed for first-order decay is one over time. The dimensions of decay\_sorbed for zero-order decay is mass of solute per mass of aquifer per time. If decay\_sorbed is not specified and both decay and sorption are active, then the program will terminate with an error. decay\_sorbed will have no effect on simulation results unless the SORPTION keyword and either first- or zero-order decay are specified in the options block. -\item \texttt{bulk\_density}---is the bulk density of the aquifer in mass per length cubed. bulk\_density will have no effect on simulation results unless the SORPTION keyword is specified in the options block. Bulk density is defined as the immobile domain solid mass per aquifer volume. The definition for this input variable changed after version 6.4.1 was released. The bulk density specified in this package is defined differently from the bulk density defined in the Mobile Storage and Transfer (MST) Package. Additional information on bulk density is included in the MODFLOW 6 Supplemental Technical Information document. +\item \texttt{bulk\_density}---is the bulk density of this immobile domain in mass per length cubed. Bulk density is defined as the immobile domain solid mass per volume of the immobile domain. bulk\_density is not required unless the SORPTION keyword is specified in the options block. If the SORPTION keyword is not specified in the options block, bulk\_density will have no effect on simulation results. -\item \texttt{distcoef}---is the distribution coefficient for the equilibrium-controlled linear sorption isotherm in dimensions of length cubed per mass. distcoef will have no effect on simulation results unless the SORPTION keyword is specified in the options block. +\item \texttt{distcoef}---is the distribution coefficient for the equilibrium-controlled linear sorption isotherm in dimensions of length cubed per mass. distcoef is not required unless the SORPTION keyword is specified in the options block. If the SORPTION keyword is not specified in the options block, distcoef will have no effect on simulation results. \end{description} diff --git a/doc/mf6io/mf6ivar/tex/gwt-ist-griddata.dat b/doc/mf6io/mf6ivar/tex/gwt-ist-griddata.dat index dc8d556ff14..401d32bc400 100644 --- a/doc/mf6io/mf6ivar/tex/gwt-ist-griddata.dat +++ b/doc/mf6io/mf6ivar/tex/gwt-ist-griddata.dat @@ -1,16 +1,18 @@ BEGIN GRIDDATA - [CIM [LAYERED] - -- READARRAY] - THETAIM [LAYERED] - -- READARRAY + POROSITY [LAYERED] + -- READARRAY + VOLFRAC [LAYERED] + -- READARRAY ZETAIM [LAYERED] -- READARRAY + [CIM [LAYERED] + -- READARRAY] [DECAY [LAYERED] -- READARRAY] [DECAY_SORBED [LAYERED] -- READARRAY] - BULK_DENSITY [LAYERED] - -- READARRAY - DISTCOEF [LAYERED] - -- READARRAY + [BULK_DENSITY [LAYERED] + -- READARRAY] + [DISTCOEF [LAYERED] + -- READARRAY] END GRIDDATA diff --git a/doc/mf6io/mf6ivar/tex/gwt-mst-desc.tex b/doc/mf6io/mf6ivar/tex/gwt-mst-desc.tex index 90c6e7d5713..754752c1f3c 100644 --- a/doc/mf6io/mf6ivar/tex/gwt-mst-desc.tex +++ b/doc/mf6io/mf6ivar/tex/gwt-mst-desc.tex @@ -15,13 +15,13 @@ \item \textbf{Block: GRIDDATA} \begin{description} -\item \texttt{porosity}---is the aquifer porosity. +\item \texttt{porosity}---is the mobile domain porosity, defined as the mobile domain pore volume per mobile domain volume. Additional information on porosity within the context of mobile and immobile domain transport simulations is included in the MODFLOW 6 Supplemental Technical Information document. \item \texttt{decay}---is the rate coefficient for first or zero-order decay for the aqueous phase of the mobile domain. A negative value indicates solute production. The dimensions of decay for first-order decay is one over time. The dimensions of decay for zero-order decay is mass per length cubed per time. decay will have no effect on simulation results unless either first- or zero-order decay is specified in the options block. \item \texttt{decay\_sorbed}---is the rate coefficient for first or zero-order decay for the sorbed phase of the mobile domain. A negative value indicates solute production. The dimensions of decay\_sorbed for first-order decay is one over time. The dimensions of decay\_sorbed for zero-order decay is mass of solute per mass of aquifer per time. If decay\_sorbed is not specified and both decay and sorption are active, then the program will terminate with an error. decay\_sorbed will have no effect on simulation results unless the SORPTION keyword and either first- or zero-order decay are specified in the options block. -\item \texttt{bulk\_density}---is the bulk density of the aquifer in mass per length cubed. bulk\_density is not required unless the SORPTION keyword is specified. Bulk density is defined as the mobile domain solid mass per aquifer volume. The definition for this input variable changed after version 6.4.1 was released. The bulk density specified here is defined differently from the bulk density defined in the Immobile Storage and Transfer (IST) Package. Additional information on bulk density is included in the MODFLOW 6 Supplemental Technical Information document. +\item \texttt{bulk\_density}---is the bulk density of the aquifer in mass per length cubed. bulk\_density is not required unless the SORPTION keyword is specified. Bulk density is defined as the mobile domain solid mass per mobile domain volume. Additional information on bulk density is included in the MODFLOW 6 Supplemental Technical Information document. \item \texttt{distcoef}---is the distribution coefficient for the equilibrium-controlled linear sorption isotherm in dimensions of length cubed per mass. distcoef is not required unless the SORPTION keyword is specified. diff --git a/doc/mf6io/mf6ivar/tex/sim-nam-desc.tex b/doc/mf6io/mf6ivar/tex/sim-nam-desc.tex index 3efe21201c4..727842d12da 100644 --- a/doc/mf6io/mf6ivar/tex/sim-nam-desc.tex +++ b/doc/mf6io/mf6ivar/tex/sim-nam-desc.tex @@ -11,6 +11,8 @@ \item \texttt{maxerrors}---maximum number of errors that will be stored and printed. +\item \texttt{PRINT\_INPUT}---keyword to activate printing of simulation input summaries to the simulation list file (mfsim.lst). With this keyword, input summaries will be written for those packages that support newer input data model routines. Not all packages are supported yet by the newer input data model routines. + \end{description} \item \textbf{Block: TIMING} diff --git a/doc/mf6io/mf6ivar/tex/sim-nam-options.dat b/doc/mf6io/mf6ivar/tex/sim-nam-options.dat index 5f007b32f0d..c09288ec1ea 100644 --- a/doc/mf6io/mf6ivar/tex/sim-nam-options.dat +++ b/doc/mf6io/mf6ivar/tex/sim-nam-options.dat @@ -3,4 +3,5 @@ BEGIN OPTIONS [NOCHECK] [MEMORY_PRINT_OPTION ] [MAXERRORS ] + [PRINT_INPUT] END OPTIONS diff --git a/src/Distributed/VirtualGwtModel.f90 b/src/Distributed/VirtualGwtModel.f90 index f4b3c8d5d00..4d0b526da89 100644 --- a/src/Distributed/VirtualGwtModel.f90 +++ b/src/Distributed/VirtualGwtModel.f90 @@ -26,7 +26,7 @@ module VirtualGwtModelModule type(VirtualDbl2dType), pointer :: fmi_gwfspdis => null() type(VirtualDbl1dType), pointer :: fmi_gwfflowja => null() ! MST - type(VirtualDbl1dType), pointer :: mst_porosity => null() + type(VirtualDbl1dType), pointer :: mst_thetam => null() ! GWT Model fields type(VirtualIntType), pointer :: indsp => null() type(VirtualIntType), pointer :: inmst => null() @@ -90,7 +90,7 @@ subroutine init_virtual_data(this) call this%set(this%fmi_gwfsat%base(), 'GWFSAT', 'FMI', MAP_NODE_TYPE) call this%set(this%fmi_gwfspdis%base(), 'GWFSPDIS', 'FMI', MAP_NODE_TYPE) call this%set(this%fmi_gwfflowja%base(), 'GWFFLOWJA', 'FMI', MAP_NODE_TYPE) - call this%set(this%mst_porosity%base(), 'POROSITY', 'MST', MAP_NODE_TYPE) + call this%set(this%mst_thetam%base(), 'THETAM', 'MST', MAP_NODE_TYPE) call this%set(this%indsp%base(), 'INDSP', '', MAP_ALL_TYPE) call this%set(this%inmst%base(), 'INMST', '', MAP_ALL_TYPE) @@ -139,7 +139,7 @@ subroutine vgwt_prepare_stage(this, stage) call this%map(this%fmi_gwfflowja%base(), nr_conns, (/STG_BFR_EXG_AD/)) if (this%indsp%get() > 0 .and. this%inmst%get() > 0) then - call this%map(this%mst_porosity%base(), nr_nodes, (/STG_AFT_CON_AR/)) + call this%map(this%mst_thetam%base(), nr_nodes, (/STG_AFT_CON_AR/)) end if end if @@ -161,7 +161,7 @@ subroutine allocate_data(this) allocate (this%fmi_gwfsat) allocate (this%fmi_gwfspdis) allocate (this%fmi_gwfflowja) - allocate (this%mst_porosity) + allocate (this%mst_thetam) allocate (this%indsp) allocate (this%inmst) @@ -182,7 +182,7 @@ subroutine deallocate_data(this) deallocate (this%fmi_gwfsat) deallocate (this%fmi_gwfspdis) deallocate (this%fmi_gwfflowja) - deallocate (this%mst_porosity) + deallocate (this%mst_thetam) deallocate (this%indsp) deallocate (this%inmst) diff --git a/src/Model/Connection/GwtGwtConnection.f90 b/src/Model/Connection/GwtGwtConnection.f90 index bb49ab9afb9..cda088c77d2 100644 --- a/src/Model/Connection/GwtGwtConnection.f90 +++ b/src/Model/Connection/GwtGwtConnection.f90 @@ -223,9 +223,9 @@ subroutine cfg_dist_vars(this) call this%cfg_dv('GWFFLOWJA', 'FMI', SYNC_CON, (/STG_BFR_EXG_AD/)) call this%cfg_dv('GWFFLOWJA', 'FMI', SYNC_EXG, (/STG_BFR_EXG_AD/), & exg_var_name='GWFSIMVALS') - ! fill porosity from mst packages, needed for dsp + ! fill thetam from mst packages, needed for dsp if (this%gwtModel%indsp > 0 .and. this%gwtModel%inmst > 0) then - call this%cfg_dv('POROSITY', 'MST', SYNC_NDS, (/STG_AFT_CON_AR/)) + call this%cfg_dv('THETAM', 'MST', SYNC_NDS, (/STG_AFT_CON_AR/)) end if end subroutine cfg_dist_vars diff --git a/src/Model/Connection/GwtInterfaceModel.f90 b/src/Model/Connection/GwtInterfaceModel.f90 index 72d6ce0a304..43c1d25865c 100644 --- a/src/Model/Connection/GwtInterfaceModel.f90 +++ b/src/Model/Connection/GwtInterfaceModel.f90 @@ -153,8 +153,8 @@ subroutine gwtifmod_df(this) trim(this%dsp%memoryPath)) end if allocate (this%mst) - call mem_allocate(this%mst%porosity, this%dis%nodes, & - 'POROSITY', create_mem_path(this%name, 'MST')) + call mem_allocate(this%mst%thetam, this%dis%nodes, & + 'THETAM', create_mem_path(this%name, 'MST')) end if ! assign or point model members to dis members @@ -179,7 +179,7 @@ subroutine gwtifmod_ar(this) call this%adv%adv_ar(this%dis, this%ibound) end if if (this%indsp > 0) then - call this%dsp%dsp_ar(this%ibound, this%mst%porosity) + call this%dsp%dsp_ar(this%ibound, this%mst%thetam) end if end subroutine gwtifmod_ar @@ -205,7 +205,7 @@ subroutine gwtifmod_da(this) deallocate (this%dsp) if (associated(this%mst)) then - call mem_deallocate(this%mst%porosity) + call mem_deallocate(this%mst%thetam) deallocate (this%mst) end if diff --git a/src/Model/GroundWaterTransport/gwt1.f90 b/src/Model/GroundWaterTransport/gwt1.f90 index 8ae69ce8c81..281fd2b4931 100644 --- a/src/Model/GroundWaterTransport/gwt1.f90 +++ b/src/Model/GroundWaterTransport/gwt1.f90 @@ -307,7 +307,7 @@ subroutine gwt_ar(this) if (this%inic > 0) call this%ic%ic_ar(this%x) if (this%inmst > 0) call this%mst%mst_ar(this%dis, this%ibound) if (this%inadv > 0) call this%adv%adv_ar(this%dis, this%ibound) - if (this%indsp > 0) call this%dsp%dsp_ar(this%ibound, this%mst%porosity) + if (this%indsp > 0) call this%dsp%dsp_ar(this%ibound, this%mst%thetam) if (this%inssm > 0) call this%ssm%ssm_ar(this%dis, this%ibound, this%x) if (this%inobs > 0) call this%obs%gwt_obs_ar(this%ic, this%x, this%flowja) ! @@ -1328,7 +1328,8 @@ subroutine create_packages(this) call fmi_cr(this%fmi, this%name, this%infmi, this%iout) call mst_cr(this%mst, this%name, this%inmst, this%iout, this%fmi) call adv_cr(this%adv, this%name, this%inadv, this%iout, this%fmi) - call dsp_cr(this%dsp, this%name, mempathdsp, this%indsp, this%iout, this%fmi) + call dsp_cr(this%dsp, this%name, mempathdsp, this%indsp, this%iout, & + this%fmi) call ssm_cr(this%ssm, this%name, this%inssm, this%iout, this%fmi) call mvt_cr(this%mvt, this%name, this%inmvt, this%iout, this%fmi) call oc_cr(this%oc, this%name, this%inoc, this%iout) diff --git a/src/Model/GroundWaterTransport/gwt1dsp.f90 b/src/Model/GroundWaterTransport/gwt1dsp.f90 index 60c385cefbf..d9604cc5ad1 100644 --- a/src/Model/GroundWaterTransport/gwt1dsp.f90 +++ b/src/Model/GroundWaterTransport/gwt1dsp.f90 @@ -18,7 +18,7 @@ module GwtDspModule integer(I4B), dimension(:), pointer, contiguous :: ibound => null() ! pointer to GWT model ibound type(GwtFmiType), pointer :: fmi => null() ! pointer to GWT fmi object - real(DP), dimension(:), pointer, contiguous :: porosity => null() ! pointer to GWT storage porosity + real(DP), dimension(:), pointer, contiguous :: thetam => null() ! pointer to GWT storage porosity (voids per aquifer volume) real(DP), dimension(:), pointer, contiguous :: diffc => null() ! molecular diffusion coefficient for each cell real(DP), dimension(:), pointer, contiguous :: alh => null() ! longitudinal horizontal dispersivity real(DP), dimension(:), pointer, contiguous :: alv => null() ! longitudinal vertical dispersivity @@ -226,7 +226,7 @@ subroutine dsp_mc(this, moffset, matrix_sln) return end subroutine dsp_mc - subroutine dsp_ar(this, ibound, porosity) + subroutine dsp_ar(this, ibound, thetam) ! ****************************************************************************** ! dsp_ar -- Allocate and Read ! ****************************************************************************** @@ -237,7 +237,7 @@ subroutine dsp_ar(this, ibound, porosity) ! -- dummy class(GwtDspType) :: this integer(I4B), dimension(:), pointer, contiguous :: ibound - real(DP), dimension(:), pointer, contiguous :: porosity + real(DP), dimension(:), pointer, contiguous :: thetam ! -- local ! -- formats character(len=*), parameter :: fmtdsp = & @@ -247,7 +247,7 @@ subroutine dsp_ar(this, ibound, porosity) ! ! -- dsp pointers to arguments that were passed in this%ibound => ibound - this%porosity => porosity + this%thetam => thetam ! ! -- Return return @@ -543,6 +543,9 @@ subroutine dsp_da(this) ! -- deallocate variables in NumericalPackageType call this%NumericalPackageType%da() ! + ! -- nullify pointers + this%thetam => null() + ! ! -- Return return end subroutine dsp_da @@ -780,7 +783,9 @@ subroutine calcdispellipse(this) end if dstar = DZERO if (this%idiffc > 0) then - dstar = this%diffc(n) * this%porosity(n) + ! -- Multiply diffusion coefficient by mobile porosity, defined + ! as volume of voids in the mobile domain per aquifer volume + dstar = this%diffc(n) * this%thetam(n) end if ! ! -- Calculate the longitudal and transverse dispersivities diff --git a/src/Model/GroundWaterTransport/gwt1ist1.f90 b/src/Model/GroundWaterTransport/gwt1ist1.f90 index cfef7874976..5e6c3358090 100644 --- a/src/Model/GroundWaterTransport/gwt1ist1.f90 +++ b/src/Model/GroundWaterTransport/gwt1ist1.f90 @@ -62,8 +62,9 @@ module GwtIstModule real(DP), dimension(:), pointer, contiguous :: cimnew => null() !< immobile concentration at end of current time step real(DP), dimension(:), pointer, contiguous :: cimold => null() !< immobile concentration at end of last time step real(DP), dimension(:), pointer, contiguous :: zetaim => null() !< mass transfer rate to immobile domain - real(DP), dimension(:), pointer, contiguous :: thetaim => null() !< porosity of the immobile domain - real(DP), dimension(:), pointer, contiguous :: bulk_density => null() !< bulk density + real(DP), dimension(:), pointer, contiguous :: porosity => null() !< immobile domain porosity defined as volume of immobile voids per volume of immobile domain + real(DP), dimension(:), pointer, contiguous :: volfrac => null() !< volume fraction of the immobile domain defined as volume of immobile domain per aquifer volume + real(DP), dimension(:), pointer, contiguous :: bulk_density => null() !< bulk density of immobile domain defined as mass of solids in immobile domain per volume of immobile domain real(DP), dimension(:), pointer, contiguous :: distcoef => null() !< distribution coefficient real(DP), dimension(:), pointer, contiguous :: decay => null() !< first or zero order rate constant for liquid real(DP), dimension(:), pointer, contiguous :: decaylast => null() !< decay rate used for last iteration (needed for zero order decay) @@ -90,6 +91,7 @@ module GwtIstModule procedure :: allocate_scalars procedure :: read_dimensions => ist_read_dimensions procedure :: read_options + procedure :: get_thetaim procedure, private :: ist_allocate_arrays procedure, private :: read_data @@ -160,16 +162,6 @@ subroutine ist_ar(this) class(GwtIstType), intent(inout) :: this !< GwtIstType object ! -- local integer(I4B) :: n - ! -- formats - character(len=*), parameter :: fmtist = & - "(1x,/1x,'IST -- IMMOBILE DOMAIN STORAGE AND TRANSFER PACKAGE, ', & - &'VERSION 1, 12/24/2018 INPUT READ FROM UNIT ', i0, //)" - ! - ! --print a message identifying the immobile domain package. - write (this%iout, fmtist) this%inunit - ! - ! -- Read immobile domain options - call this%read_options() ! ! -- Allocate arrays call this%ist_allocate_arrays() @@ -188,6 +180,9 @@ subroutine ist_ar(this) this%cimnew(n) = this%cim(n) end do ! + ! -- add volfrac to the volfracim accumulator in mst package + call this%mst%addto_volfracim(this%volfrac) + ! ! -- setup the immobile domain budget call budget_cr(this%budget, this%memoryPath) call this%budget%budget_df(NBDITEMS, 'MASS', 'M', bdzone=this%packName) @@ -287,6 +282,7 @@ subroutine ist_fc(this, rhs, ia, idxglo, matrix_sln) real(DP) :: thetaim real(DP) :: zetaim real(DP) :: kd + real(DP) :: volfracim real(DP) :: rhobim real(DP) :: lambda1im real(DP) :: lambda2im @@ -312,7 +308,7 @@ subroutine ist_fc(this, rhs, ia, idxglo, matrix_sln) vcell = this%dis%area(n) * (this%dis%top(n) - this%dis%bot(n)) swtpdt = this%fmi%gwfsat(n) swt = this%fmi%gwfsatold(n, delt) - thetaim = this%thetaim(n) + thetaim = this%get_thetaim(n) idiag = ia(n) ! ! -- set exchange coefficient @@ -320,6 +316,7 @@ subroutine ist_fc(this, rhs, ia, idxglo, matrix_sln) ! ! -- Add dual domain mass transfer contributions to rhs and hcof kd = DZERO + volfracim = DZERO rhobim = DZERO lambda1im = DZERO lambda2im = DZERO @@ -338,6 +335,7 @@ subroutine ist_fc(this, rhs, ia, idxglo, matrix_sln) ! -- setup sorption variables if (this%isrb > 0) then kd = this%distcoef(n) + volfracim = this%volfrac(n) rhobim = this%bulk_density(n) if (this%idcy == 1) lambda2im = this%decay_sorbed(n) if (this%idcy == 2) then @@ -353,7 +351,7 @@ subroutine ist_fc(this, rhs, ia, idxglo, matrix_sln) ! ! -- calculate the terms and then get the hcof and rhs contributions call get_ddterm(thetaim, vcell, delt, swtpdt, & - rhobim, kd, lambda1im, lambda2im, & + volfracim, rhobim, kd, lambda1im, lambda2im, & gamma1im, gamma2im, zetaim, ddterm, f) cimold = this%cimold(n) call get_hcofrhs(ddterm, f, cimold, hhcof, rrhs) @@ -392,6 +390,7 @@ subroutine ist_cq(this, x, flowja, iadv) real(DP) :: thetaim real(DP) :: zetaim real(DP) :: kd + real(DP) :: volfracim real(DP) :: rhobim real(DP) :: lambda1im real(DP) :: lambda2im @@ -420,7 +419,7 @@ subroutine ist_cq(this, x, flowja, iadv) vcell = this%dis%area(n) * (this%dis%top(n) - this%dis%bot(n)) swtpdt = this%fmi%gwfsat(n) swt = this%fmi%gwfsatold(n, delt) - thetaim = this%thetaim(n) + thetaim = this%get_thetaim(n) ! ! -- set exchange coefficient zetaim = this%zetaim(n) @@ -430,6 +429,7 @@ subroutine ist_cq(this, x, flowja, iadv) hhcof = DZERO rrhs = DZERO kd = DZERO + volfracim = DZERO rhobim = DZERO lambda1im = DZERO lambda2im = DZERO @@ -442,6 +442,7 @@ subroutine ist_cq(this, x, flowja, iadv) end if if (this%isrb > 0) then kd = this%distcoef(n) + volfracim = this%volfrac(n) rhobim = this%bulk_density(n) if (this%idcy == 1) lambda2im = this%decay_sorbed(n) if (this%idcy == 2) then @@ -456,7 +457,7 @@ subroutine ist_cq(this, x, flowja, iadv) ! ! -- calculate the terms and then get the hcof and rhs contributions call get_ddterm(thetaim, vcell, delt, swtpdt, & - rhobim, kd, lambda1im, lambda2im, & + volfracim, rhobim, kd, lambda1im, lambda2im, & gamma1im, gamma2im, zetaim, ddterm, f) cimold = this%cimold(n) call get_hcofrhs(ddterm, f, cimold, hhcof, rrhs) @@ -665,7 +666,8 @@ subroutine ist_da(this) call mem_deallocate(this%cimnew) call mem_deallocate(this%cimold) call mem_deallocate(this%zetaim) - call mem_deallocate(this%thetaim) + call mem_deallocate(this%porosity) + call mem_deallocate(this%volfrac) call mem_deallocate(this%bulk_density) call mem_deallocate(this%distcoef) call mem_deallocate(this%decay) @@ -754,8 +756,9 @@ subroutine ist_allocate_arrays(this) call mem_allocate(this%cim, this%dis%nodes, 'CIM', this%memoryPath) call mem_allocate(this%cimnew, this%dis%nodes, 'CIMNEW', this%memoryPath) call mem_allocate(this%cimold, this%dis%nodes, 'CIMOLD', this%memoryPath) + call mem_allocate(this%porosity, this%dis%nodes, 'POROSITY', this%memoryPath) call mem_allocate(this%zetaim, this%dis%nodes, 'ZETAIM', this%memoryPath) - call mem_allocate(this%thetaim, this%dis%nodes, 'THETAIM', this%memoryPath) + call mem_allocate(this%volfrac, this%dis%nodes, 'VOLFRAC', this%memoryPath) if (this%isrb == 0) then call mem_allocate(this%bulk_density, 1, 'BULK_DENSITY', this%memoryPath) call mem_allocate(this%distcoef, 1, 'DISTCOEF', this%memoryPath) @@ -787,8 +790,9 @@ subroutine ist_allocate_arrays(this) this%cim(n) = DZERO this%cimnew(n) = DZERO this%cimold(n) = DZERO + this%porosity(n) = DZERO this%zetaim(n) = DZERO - this%thetaim(n) = DZERO + this%volfrac(n) = DZERO end do do n = 1, size(this%decay) this%decay(n) = DZERO @@ -941,8 +945,8 @@ subroutine read_data(this) character(len=:), allocatable :: line integer(I4B) :: istart, istop, lloc, ierr logical :: isfound, endOfBlock - logical, dimension(7) :: lname - character(len=24), dimension(7) :: aname + logical, dimension(8) :: lname + character(len=24), dimension(8) :: aname ! -- formats ! -- data data aname(1)/' BULK DENSITY'/ @@ -952,6 +956,7 @@ subroutine read_data(this) data aname(5)/' INITIAL IMMOBILE CONC'/ data aname(6)/' FIRST ORDER TRANS RATE'/ data aname(7)/'IMMOBILE DOMAIN POROSITY'/ + data aname(8)/'IMMOBILE VOLUME FRACTION'/ ! ! -- initialize isfound = .false. @@ -1009,11 +1014,23 @@ subroutine read_data(this) this%parser%iuactive, this%zetaim, & aname(6)) lname(6) = .true. - case ('THETAIM') + case ('POROSITY') call this%dis%read_grid_array(line, lloc, istart, istop, this%iout, & - this%parser%iuactive, this%thetaim, & + this%parser%iuactive, this%porosity, & aname(7)) lname(7) = .true. + case ('VOLFRAC') + call this%dis%read_grid_array(line, lloc, istart, istop, this%iout, & + this%parser%iuactive, this%volfrac, & + aname(8)) + lname(8) = .true. + case ('THETAIM') + write (errmsg, '(a)') & + 'THETAIM is no longer supported. See Chapter 9 in & + &mf6suptechinfo.pdf for revised parameterization of mobile and & + &immobile domain simulations.' + call store_error(errmsg) + call this%parser%StoreErrorUnit() case default write (errmsg, '(4x,a,a)') 'Unknown GRIDDATA tag: ', trim(keyword) call store_error(errmsg) @@ -1030,35 +1047,35 @@ subroutine read_data(this) ! -- Check for required sorption variables if (this%isrb > 0) then if (.not. lname(1)) then - write (errmsg, '(1x,a)') 'ERROR. SORPTION IS ACTIVE BUT BULK_DENSITY & - &NOT SPECIFIED. BULK_DENSITY MUST BE SPECIFIED IN GRIDDATA BLOCK.' + write (errmsg, '(1x,a)') 'Sorption is active but BULK_DENSITY & + ¬ specified. BULK_DENSITY must be specified in griddata block.' call store_error(errmsg) end if if (.not. lname(2)) then - write (errmsg, '(1x,a)') 'ERROR. SORPTION IS ACTIVE BUT DISTRIBUTION & - &COEFFICIENT NOT SPECIFIED. DISTCOEF MUST BE SPECIFIED IN & - &GRIDDATA BLOCK.' + write (errmsg, '(1x,a)') 'Sorption is active but distribution & + &coefficient not specified. DISTCOEF must be specified in & + &GRIDDATA block.' call store_error(errmsg) end if else if (lname(1)) then - write (this%iout, '(1x,a)') 'WARNING. SORPTION IS NOT ACTIVE BUT & - &BULK_DENSITY WAS SPECIFIED. BULK_DENSITY WILL HAVE NO AFFECT ON & - &SIMULATION RESULTS.' + write (this%iout, '(1x,a)') 'Warning. Sorption is not active but & + &BULK_DENSITY was specified. BULK_DENSITY will have no affect on & + &simulation results.' end if if (lname(2)) then - write (this%iout, '(1x,a)') 'WARNING. SORPTION IS NOT ACTIVE BUT & - &DISTRIBUTION COEFFICIENT WAS SPECIFIED. DISTCOEF WILL HAVE & - &NO AFFECT ON SIMULATION RESULTS.' + write (this%iout, '(1x,a)') 'Warning. Sorption is not active but & + &distribution coefficient was specified. DISTCOEF will have & + &no affect on simulation results.' end if end if ! ! -- Check for required decay/production rate coefficients if (this%idcy > 0) then if (.not. lname(3)) then - write (errmsg, '(1x,a)') 'ERROR. FIRST OR ZERO ORDER DECAY IS & - &ACTIVE BUT THE FIRST RATE COEFFICIENT IS NOT SPECIFIED. & - &DECAY MUST BE SPECIFIED IN GRIDDATA BLOCK.' + write (errmsg, '(1x,a)') 'First- or zero-order decay is & + &active but the first rate coefficient was not specified. & + &Decay must be specified in GRIDDATA block.' call store_error(errmsg) end if if (.not. lname(4)) then @@ -1074,35 +1091,43 @@ subroutine read_data(this) end if else if (lname(3)) then - write (this%iout, '(1x,a)') 'WARNING. FIRST OR ZERO ORER DECAY & - &IS NOT ACTIVE BUT DECAY WAS SPECIFIED. DECAY WILL & - &HAVE NO AFFECT ON SIMULATION RESULTS.' + write (this%iout, '(1x,a)') 'Warning. First- or zero-orer decay & + &is not active but DECAY was specified. DECAY will & + &have no affect on simulation results.' end if if (lname(4)) then - write (this%iout, '(1x,a)') 'WARNING. FIRST OR ZERO ORER DECAY & - &IS NOT ACTIVE BUT DECAY_SORBED MUST WAS SPECIFIED. & - &DECAY_SORBED MUST WILL HAVE NO AFFECT ON SIMULATION & - &RESULTS.' + write (this%iout, '(1x,a)') 'Warning. First- or zero-orer decay & + &is not active but DECAY_SORBED was specified. & + &DECAY_SORBED will have no affect on simulation & + &results.' end if end if ! ! -- Check for required dual domain arrays or warn if they are specified ! but won't be used. if (.not. lname(5)) then - write (this%iout, '(1x,a)') 'WARNING. DUAL DOMAIN IS ACTIVE BUT & - &INITIAL IMMOBILE DOMAIN CONCENTRATION WAS NOT SPECIFIED. & - &SETTING CIM TO ZERO.' + write (this%iout, '(1x,a)') 'Warning. Dual domain is active but & + &initial immobile domain concentration was not specified. & + &Setting CIM to zero.' end if if (.not. lname(6)) then - write (errmsg, '(1x,a)') 'DUAL DOMAIN IS ACTIVE BUT DUAL & - &DOMAIN MASS TRANSFER RATE (ZETAIM) WAS NOT SPECIFIED. ZETAIM & - &MUST BE SPECIFIED IN GRIDDATA BLOCK.' + write (errmsg, '(1x,a)') 'Dual domain is active but dual & + &domain mass transfer rate (ZETAIM) was not specified. ZETAIM & + &must be specified in GRIDDATA block.' call store_error(errmsg) end if if (.not. lname(7)) then - write (errmsg, '(1x,a)') 'DUAL DOMAIN IS ACTIVE BUT & - &IMMOBILE DOMAIN POROSITY (THETAIM) WAS NOT SPECIFIED. THETAIM & - &MUST BE SPECIFIED IN GRIDDATA BLOCK.' + write (errmsg, '(1x,a)') 'Dual domain is active but & + &immobile domain POROSITY was not specified. POROSITY & + &must be specified in GRIDDATA block.' + call store_error(errmsg) + end if + if (.not. lname(8)) then + write (errmsg, '(1x,a)') 'Dual domain is active but & + &immobile domain VOLFRAC was not specified. VOLFRAC & + &must be specified in GRIDDATA block. This is a new & + &requirement for MODFLOW versions later than version & + &6.4.1.' call store_error(errmsg) end if ! @@ -1115,6 +1140,25 @@ subroutine read_data(this) return end subroutine read_data + !> @ brief Return thetaim + !! + !! Calculate and return thetaim, volume of immobile voids per aquifer volume + !! + !< + function get_thetaim(this, node) result(thetaim) + ! -- modules + ! -- dummy + class(GwtIstType) :: this !< GwtIstType object + integer(I4B), intent(in) :: node !< node number + ! -- return + real(DP) :: thetaim + ! + thetaim = this%volfrac(node) * this%porosity(node) + ! + ! -- Return + return + end function get_thetaim + !> @ brief Calculate immobile domain equation terms !! !! This subroutine calculates the immobile domain (or dual domain) terms. @@ -1124,13 +1168,14 @@ end subroutine read_data !! !< subroutine get_ddterm(thetaim, vcell, delt, swtpdt, & - rhobim, kd, lambda1im, lambda2im, & + volfracim, rhobim, kd, lambda1im, lambda2im, & gamma1im, gamma2im, zetaim, ddterm, f) ! -- dummy real(DP), intent(in) :: thetaim !< immobile domain porosity real(DP), intent(in) :: vcell !< volume of cell real(DP), intent(in) :: delt !< length of time step real(DP), intent(in) :: swtpdt !< cell saturation at end of time step + real(DP), intent(in) :: volfracim !< volume fraction of immobile domain real(DP), intent(in) :: rhobim !< bulk density for the immobile domain (fim * rhob) real(DP), intent(in) :: kd !< distribution coefficient for linear isotherm real(DP), intent(in) :: lambda1im !< first-order decay rate in aqueous phase @@ -1147,15 +1192,17 @@ subroutine get_ddterm(thetaim, vcell, delt, swtpdt, & tled = DONE / delt ! ! -- Calculate terms. These terms correspond to the concentration - ! coefficients in equation 7-4 of the GWT model report + ! coefficients in equation 7-4 of the GWT model report. However, + ! an updated equation is presented as 9-9 in the supplemental technical + ! information guide (mf6suptechinfo.pdf) ddterm(1) = thetaim * vcell * tled ddterm(2) = thetaim * vcell * tled - ddterm(3) = rhobim * vcell * kd * tled - ddterm(4) = rhobim * vcell * kd * tled + ddterm(3) = volfracim * rhobim * vcell * kd * tled + ddterm(4) = volfracim * rhobim * vcell * kd * tled ddterm(5) = thetaim * lambda1im * vcell - ddterm(6) = lambda2im * rhobim * kd * vcell + ddterm(6) = lambda2im * volfracim * rhobim * kd * vcell ddterm(7) = thetaim * gamma1im * vcell - ddterm(8) = gamma2im * rhobim * vcell + ddterm(8) = gamma2im * volfracim * rhobim * vcell ddterm(9) = vcell * swtpdt * zetaim ! ! -- calculate denominator term, f diff --git a/src/Model/GroundWaterTransport/gwt1mst1.f90 b/src/Model/GroundWaterTransport/gwt1mst1.f90 index 94160af7c38..c1343f0bfa4 100644 --- a/src/Model/GroundWaterTransport/gwt1mst1.f90 +++ b/src/Model/GroundWaterTransport/gwt1mst1.f90 @@ -37,7 +37,9 @@ module GwtMstModule type, extends(NumericalPackageType) :: GwtMstType ! ! -- storage - real(DP), dimension(:), pointer, contiguous :: porosity => null() !< porosity + real(DP), dimension(:), pointer, contiguous :: porosity => null() !< mobile porosity defined as volume mobile voids per volume of mobile domain + real(DP), dimension(:), pointer, contiguous :: thetam => null() !< mobile porosity defined as volume mobile voids per volume of aquifer + real(DP), dimension(:), pointer, contiguous :: volfracim => null() !< sum of all immobile domain volume fractions real(DP), dimension(:), pointer, contiguous :: ratesto => null() !< rate of mobile storage ! ! -- decay @@ -77,6 +79,8 @@ module GwtMstModule procedure :: mst_ot_flow procedure :: mst_da procedure :: allocate_scalars + procedure :: addto_volfracim + procedure :: get_volfracm procedure, private :: allocate_arrays procedure, private :: read_options procedure, private :: read_data @@ -233,7 +237,7 @@ subroutine mst_fc_sto(this, nodes, cold, nja, matrix_sln, idxglo, rhs) ! ! -- calculate new and old water volumes vnew = this%dis%area(n) * (this%dis%top(n) - this%dis%bot(n)) * & - this%fmi%gwfsat(n) * this%porosity(n) + this%fmi%gwfsat(n) * this%thetam(n) vold = vnew if (this%fmi%igwfstrgss /= 0) vold = vold + this%fmi%gwfstrgss(n) * delt if (this%fmi%igwfstrgsy /= 0) vold = vold + this%fmi%gwfstrgsy(n) * delt @@ -292,7 +296,7 @@ subroutine mst_fc_dcy(this, nodes, cold, cnew, nja, matrix_sln, & ! ! -- first order decay rate is a function of concentration, so add ! to left hand side - hhcof = -this%decay(n) * vcell * swtpdt * this%porosity(n) + hhcof = -this%decay(n) * vcell * swtpdt * this%thetam(n) call matrix_sln%add_value_pos(idxglo(idiag), hhcof) elseif (this%idcy == 2) then ! @@ -301,7 +305,7 @@ subroutine mst_fc_dcy(this, nodes, cold, cnew, nja, matrix_sln, & decay_rate = get_zero_order_decay(this%decay(n), this%decaylast(n), & kiter, cold(n), cnew(n), delt) this%decaylast(n) = decay_rate - rrhs = decay_rate * vcell * swtpdt * this%porosity(n) + rrhs = decay_rate * vcell * swtpdt * this%thetam(n) rhs(n) = rhs(n) + rrhs end if ! @@ -337,6 +341,7 @@ subroutine mst_fc_srb(this, nodes, cold, nja, matrix_sln, idxglo, rhs, & real(DP) :: vcell real(DP) :: const1 real(DP) :: const2 + real(DP) :: volfracm real(DP) :: rhobm ! ! -- set variables @@ -356,8 +361,9 @@ subroutine mst_fc_srb(this, nodes, cold, nja, matrix_sln, idxglo, rhs, & const1 = this%distcoef(n) const2 = 0. if (this%isrb > 1) const2 = this%sp2(n) + volfracm = this%get_volfracm(n) rhobm = this%bulk_density(n) - call mst_srb_term(this%isrb, rhobm, vcell, tled, cnew(n), & + call mst_srb_term(this%isrb, volfracm, rhobm, vcell, tled, cnew(n), & cold(n), swtpdt, swt, const1, const2, & hcofval=hhcof, rhsval=rrhs) ! @@ -376,12 +382,13 @@ end subroutine mst_fc_srb !! Subroutine to calculate sorption terms !! !< - subroutine mst_srb_term(isrb, rhobm, vcell, tled, cnew, cold, & + subroutine mst_srb_term(isrb, volfracm, rhobm, vcell, tled, cnew, cold, & swnew, swold, const1, const2, rate, hcofval, rhsval) ! -- modules ! -- dummy integer(I4B), intent(in) :: isrb !< sorption flag 1, 2, 3 are linear, freundlich, and langmuir - real(DP), intent(in) :: rhobm !< bulk density of mobile domain (fm * rhob) + real(DP), intent(in) :: volfracm !< volume fraction of mobile domain (fhat_m) + real(DP), intent(in) :: rhobm !< bulk density of mobile domain (rhob_m) real(DP), intent(in) :: vcell !< volume of cell real(DP), intent(in) :: tled !< one over time step length real(DP), intent(in) :: cnew !< concentration at end of this time step @@ -405,7 +412,7 @@ subroutine mst_srb_term(isrb, rhobm, vcell, tled, cnew, cold, & ! -- Calculate based on type of sorption if (isrb == 1) then ! -- linear - term = -rhobm * vcell * tled * const1 + term = -volfracm * rhobm * vcell * tled * const1 if (present(hcofval)) hcofval = term * swnew if (present(rhsval)) rhsval = term * swold * cold if (present(rate)) rate = term * swnew * cnew - term * swold * cold @@ -428,7 +435,7 @@ subroutine mst_srb_term(isrb, rhobm, vcell, tled, cnew, cold, & end if ! ! -- calculate hcof, rhs, and rate for freundlich and langmuir - term = -rhobm * vcell * tled + term = -volfracm * rhobm * vcell * tled cbaravg = (cbarold + cbarnew) * DHALF swavg = (swnew + swold) * DHALF if (present(hcofval)) then @@ -470,6 +477,7 @@ subroutine mst_fc_dcy_srb(this, nodes, cold, nja, matrix_sln, idxglo, & real(DP) :: vcell real(DP) :: swnew real(DP) :: distcoef + real(DP) :: volfracm real(DP) :: rhobm real(DP) :: term real(DP) :: csrb @@ -490,8 +498,9 @@ subroutine mst_fc_dcy_srb(this, nodes, cold, nja, matrix_sln, idxglo, & swnew = this%fmi%gwfsat(n) distcoef = this%distcoef(n) idiag = this%dis%con%ia(n) + volfracm = this%get_volfracm(n) rhobm = this%bulk_density(n) - term = this%decay_sorbed(n) * rhobm * swnew * vcell + term = this%decay_sorbed(n) * volfracm * rhobm * swnew * vcell ! ! -- add sorbed mass decay rate terms to accumulators if (this%idcy == 1) then @@ -533,7 +542,7 @@ subroutine mst_fc_dcy_srb(this, nodes, cold, nja, matrix_sln, idxglo, & this%decayslast(n), & kiter, csrbold, csrbnew, delt) this%decayslast(n) = decay_rate - rrhs = decay_rate * rhobm * swnew * vcell + rrhs = decay_rate * volfracm * rhobm * swnew * vcell end if end if @@ -619,7 +628,7 @@ subroutine mst_cq_sto(this, nodes, cnew, cold, flowja) ! ! -- calculate new and old water volumes vnew = this%dis%area(n) * (this%dis%top(n) - this%dis%bot(n)) * & - this%fmi%gwfsat(n) * this%porosity(n) + this%fmi%gwfsat(n) * this%thetam(n) vold = vnew if (this%fmi%igwfstrgss /= 0) vold = vold + this%fmi%gwfstrgss(n) * delt if (this%fmi%igwfstrgsy /= 0) vold = vold + this%fmi%gwfstrgsy(n) * delt @@ -678,11 +687,11 @@ subroutine mst_cq_dcy(this, nodes, cnew, cold, flowja) hhcof = DZERO rrhs = DZERO if (this%idcy == 1) then - hhcof = -this%decay(n) * vcell * swtpdt * this%porosity(n) + hhcof = -this%decay(n) * vcell * swtpdt * this%thetam(n) elseif (this%idcy == 2) then decay_rate = get_zero_order_decay(this%decay(n), this%decaylast(n), & 0, cold(n), cnew(n), delt) - rrhs = decay_rate * vcell * swtpdt * this%porosity(n) + rrhs = decay_rate * vcell * swtpdt * this%thetam(n) end if rate = hhcof * cnew(n) - rrhs this%ratedcy(n) = rate @@ -716,6 +725,7 @@ subroutine mst_cq_srb(this, nodes, cnew, cold, flowja) real(DP) :: tled real(DP) :: swt, swtpdt real(DP) :: vcell + real(DP) :: volfracm real(DP) :: rhobm real(DP) :: const1 real(DP) :: const2 @@ -736,11 +746,12 @@ subroutine mst_cq_srb(this, nodes, cnew, cold, flowja) vcell = this%dis%area(n) * (this%dis%top(n) - this%dis%bot(n)) swtpdt = this%fmi%gwfsat(n) swt = this%fmi%gwfsatold(n, delt) + volfracm = this%get_volfracm(n) rhobm = this%bulk_density(n) const1 = this%distcoef(n) const2 = 0. if (this%isrb > 1) const2 = this%sp2(n) - call mst_srb_term(this%isrb, rhobm, vcell, tled, cnew(n), & + call mst_srb_term(this%isrb, volfracm, rhobm, vcell, tled, cnew(n), & cold(n), swtpdt, swt, const1, const2, & rate=rate) this%ratesrb(n) = rate @@ -775,6 +786,7 @@ subroutine mst_cq_dcy_srb(this, nodes, cnew, cold, flowja) real(DP) :: vcell real(DP) :: swnew real(DP) :: distcoef + real(DP) :: volfracm real(DP) :: rhobm real(DP) :: term real(DP) :: csrb @@ -798,8 +810,9 @@ subroutine mst_cq_dcy_srb(this, nodes, cnew, cold, flowja) vcell = this%dis%area(n) * (this%dis%top(n) - this%dis%bot(n)) swnew = this%fmi%gwfsat(n) distcoef = this%distcoef(n) + volfracm = this%get_volfracm(n) rhobm = this%bulk_density(n) - term = this%decay_sorbed(n) * rhobm * swnew * vcell + term = this%decay_sorbed(n) * volfracm * rhobm * swnew * vcell ! ! -- add sorbed mass decay rate terms to accumulators if (this%idcy == 1) then @@ -838,7 +851,7 @@ subroutine mst_cq_dcy_srb(this, nodes, cnew, cold, flowja) decay_rate = get_zero_order_decay(this%decay_sorbed(n), & this%decayslast(n), & 0, csrbold, csrbnew, delt) - rrhs = decay_rate * rhobm * swnew * vcell + rrhs = decay_rate * volfracm * rhobm * swnew * vcell end if end if ! @@ -975,6 +988,8 @@ subroutine mst_da(this) ! -- Deallocate arrays if package was active if (this%inunit > 0) then call mem_deallocate(this%porosity) + call mem_deallocate(this%thetam) + call mem_deallocate(this%volfracim) call mem_deallocate(this%ratesto) call mem_deallocate(this%idcy) call mem_deallocate(this%decay) @@ -1046,6 +1061,8 @@ subroutine allocate_arrays(this, nodes) ! -- Allocate ! -- sto call mem_allocate(this%porosity, nodes, 'POROSITY', this%memoryPath) + call mem_allocate(this%thetam, nodes, 'THETAM', this%memoryPath) + call mem_allocate(this%volfracim, nodes, 'VOLFRACIM', this%memoryPath) call mem_allocate(this%ratesto, nodes, 'RATESTO', this%memoryPath) ! ! -- dcy @@ -1090,6 +1107,8 @@ subroutine allocate_arrays(this, nodes) ! -- Initialize do n = 1, nodes this%porosity(n) = DZERO + this%thetam(n) = DZERO + this%volfracim(n) = DZERO this%ratesto(n) = DZERO end do do n = 1, size(this%decay) @@ -1205,7 +1224,7 @@ subroutine read_data(this) ! -- local character(len=LINELENGTH) :: keyword character(len=:), allocatable :: line - integer(I4B) :: istart, istop, lloc, ierr + integer(I4B) :: istart, istop, lloc, ierr, n logical :: isfound, endOfBlock logical, dimension(6) :: lname character(len=24), dimension(6) :: aname @@ -1382,10 +1401,63 @@ subroutine read_data(this) call this%parser%StoreErrorUnit() end if ! + ! -- initialize thetam from porosity + do n = 1, size(this%porosity) + this%thetam(n) = this%porosity(n) + end do + ! ! -- Return return end subroutine read_data + !> @ brief Add volfrac values to volfracim + !! + !! Method to add immobile domain volume fracions, which are stored as a + !! cumulative value in volfracim. + !! + !< + subroutine addto_volfracim(this, volfracim) + ! -- modules + ! -- dummy + class(GwtMstType) :: this !< GwtMstType object + real(DP), dimension(:), intent(in) :: volfracim !< immobile domain volume fraction that contributes to total immobile volume fraction + ! -- local + integer(I4B) :: n + ! + ! -- Add to volfracim + do n = 1, this%dis%nodes + this%volfracim(n) = this%volfracim(n) + volfracim(n) + end do + ! + ! -- An immobile domain is adding a volume fraction, so update thetam + ! accordingly. + do n = 1, this%dis%nodes + this%thetam(n) = this%get_volfracm(n) * this%porosity(n) + end do + ! + ! -- Return + return + end subroutine addto_volfracim + + !> @ brief Return mobile domain volume fraction + !! + !! Calculate and return the volume fraction of the aquifer that is mobile + !! + !< + function get_volfracm(this, node) result(volfracm) + ! -- modules + ! -- dummy + class(GwtMstType) :: this !< GwtMstType object + integer(I4B), intent(in) :: node !< node number + ! -- return + real(DP) :: volfracm + ! + volfracm = DONE - this%volfracim(node) + ! + ! -- Return + return + end function get_volfracm + !> @ brief Calculate sorption concentration using Freundlich !! !! Function to calculate sorption concentration using Freundlich From 3d3e0bbdc9a7abf09523ae0d32fc69d69992130b Mon Sep 17 00:00:00 2001 From: jdhughes-usgs Date: Fri, 9 Jun 2023 08:56:06 -0500 Subject: [PATCH 094/123] docs: fix issue with ReleaseNotes build (#1241) --- doc/ReleaseNotes/v6.5.0.tex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/ReleaseNotes/v6.5.0.tex b/doc/ReleaseNotes/v6.5.0.tex index 46dc6f028e7..281fab53c02 100644 --- a/doc/ReleaseNotes/v6.5.0.tex +++ b/doc/ReleaseNotes/v6.5.0.tex @@ -29,7 +29,7 @@ \item An SFR channel defined with the n-point cross-section option was calculating the wetted cross-sectional area incorrectly. The cross sectional area for the area of a triangle was being calculated as one-half multiplied by the depth of the channel, as opposed to one-half multiplied by the base width multiplied by the height. As a result, the units in the mannings equation were not correct owing to the missing dimension in the area calculation. The change in the area calculation will slightly alter the solution found using Manning's equation since the cross-sectional area term appears in it. As a result, existing models may reflect slightly different answers in groundwater\/surface-water exchange amounts owing to slight differences in the calculated stream stage. In addition to the fix, some clarifying text, including a new figure, was added to mf6io.pdf. \item The SSM Package for the GWT Model did not work properly with Stress Package Concentration (SPC) input with the READARRAY option for transient models. Under these conditions, the program would prematurely terminate looking for the next BEGIN PERIOD block. The program was corrected so that SPC input can be read for transient conditions. \item For some Linux systems, observations were not being correctly written to formatted observation output files when the source code was compiled with the Intel IFORT 19.1.0.166 20191121 compiler. This issue has been addressed by adding a flush statement to ObsUtilityModule::write\_unfmtd\_obs after writing each observation for a time step. This change will not affect simulated observations and should not affect simulation run times. - \item The wetted area stored in the binary LAK package output needs to be zero when the lake stage is below the bottom of a connected groundwater cell. The code uses the lak_calculate_conn_warea() function to determine the wetted area, which makes sense for calculating the flow conductance; however, for thermal conduction the shared wetted area should be 0.0 when the lake stage falls below the bottom of a connected cell. + \item The wetted area stored in the binary LAK package output needs to be zero when the lake stage is below the bottom of a connected groundwater cell. The code uses the lak\_calculate\_conn\_warea() function to determine the wetted area, which makes sense for calculating the flow conductance; however, for thermal conduction the shared wetted area should be 0.0 when the lake stage falls below the bottom of a connected cell. \end{itemize} \underline{INTERNAL FLOW PACKAGES} From 5e989a0cb8c8be7f73b1e5c3b6f491028b3b01a7 Mon Sep 17 00:00:00 2001 From: langevin-usgs Date: Fri, 9 Jun 2023 20:36:31 -0500 Subject: [PATCH 095/123] test(ist): add another test of GWT IST Package (#1244) --- autotest/test_gwt_ist02.py | 396 +++++++++++++++++++++++++++++++++++++ 1 file changed, 396 insertions(+) create mode 100644 autotest/test_gwt_ist02.py diff --git a/autotest/test_gwt_ist02.py b/autotest/test_gwt_ist02.py new file mode 100644 index 00000000000..927ee082f39 --- /dev/null +++ b/autotest/test_gwt_ist02.py @@ -0,0 +1,396 @@ +""" +Test the IST Package with a one-dimensional flow problem +with dual-domain transport. Flow is from left to right with +constant concentration equal to 1.0 for first 20 days and then +set to 0.0 for next 30 days. The results are compared to +the results of an MT3D simulation sent by Sorab Panday. The MT3D +results had many transport time steps, but they were interpolated +onto an even 1-day interval. The mf6 results are also +interpolated onto the 1-day interval for comparison. The test +passes if the difference in simulated concentration in column 300 +between mf6 and mt3d is less than 0.05. +""" + +import os + +import flopy +import numpy as np +import pytest +from framework import TestFramework +from simulation import TestSimulation + +ex = ["ist02"] +nlay, nrow, ncol = 1, 1, 300 + +mt3d_times = np.arange(1.0, 51.0, 1.0) +mt3d_conc = np.array( + [ + 0.000000e00, + 2.060000e-25, + 7.220000e-10, + 9.541440e-04, + 8.276250e-02, + 2.328700e-01, + 3.536184e-01, + 4.644795e-01, + 5.631907e-01, + 6.484800e-01, + 7.204300e-01, + 7.799700e-01, + 8.284300e-01, + 8.673400e-01, + 8.982000e-01, + 9.224300e-01, + 9.412600e-01, + 9.557800e-01, + 9.668900e-01, + 9.753300e-01, + 9.817000e-01, + 9.864900e-01, + 9.900600e-01, + 9.917600e-01, + 9.119200e-01, + 7.632500e-01, + 6.435600e-01, + 5.334700e-01, + 4.353300e-01, + 3.504500e-01, + 2.788000e-01, + 2.194700e-01, + 1.711600e-01, + 1.323600e-01, + 1.015800e-01, + 7.741230e-02, + 5.860264e-02, + 4.411496e-02, + 3.302846e-02, + 2.460321e-02, + 1.824100e-02, + 1.346449e-02, + 9.894626e-03, + 7.245270e-03, + 5.285890e-03, + 3.843070e-03, + 2.784940e-03, + 2.011888e-03, + 1.448659e-03, + 1.040850e-03, + ] +) + + +def build_model(idx, dir): + perlen = [20.0, 30.0] + nper = len(perlen) + nstp = [100, 100] + tsmult = [1.0, 1.0] + delr = 0.5 + delc = 1.0 + top = 1.0 + botm = [0.0] + strt = 9.5 + hk = 1000.0 + + nouter, ninner = 100, 300 + hclose, rclose, relax = 1e-6, 1e-6, 0.97 + + tdis_rc = [] + for id in range(nper): + tdis_rc.append((perlen[id], nstp[id], tsmult[id])) + + name = ex[idx] + + # build MODFLOW 6 files + ws = dir + sim = flopy.mf6.MFSimulation( + sim_name=name, version="mf6", exe_name="mf6", sim_ws=ws + ) + # create tdis package + tdis = flopy.mf6.ModflowTdis( + sim, time_units="DAYS", nper=nper, perioddata=tdis_rc + ) + + # create gwf model + gwfname = "gwf_" + name + newtonoptions = None + gwf = flopy.mf6.ModflowGwf( + sim, + modelname=gwfname, + newtonoptions=newtonoptions, + ) + + # create iterative model solution and register the gwf model with it + imsgwf = flopy.mf6.ModflowIms( + sim, + print_option="SUMMARY", + outer_dvclose=hclose, + outer_maximum=nouter, + under_relaxation="DBD", + under_relaxation_theta=0.7, + inner_maximum=ninner, + inner_dvclose=hclose, + rcloserecord=rclose, + linear_acceleration="BICGSTAB", + scaling_method="NONE", + reordering_method="NONE", + relaxation_factor=relax, + filename=f"{gwfname}.ims", + ) + sim.register_ims_package(imsgwf, [gwf.name]) + + dis = flopy.mf6.ModflowGwfdis( + gwf, + nlay=nlay, + nrow=nrow, + ncol=ncol, + delr=delr, + delc=delc, + top=top, + botm=botm, + ) + + # initial conditions + ic = flopy.mf6.ModflowGwfic(gwf, strt=strt) + + # node property flow + npf = flopy.mf6.ModflowGwfnpf( + gwf, save_flows=False, icelltype=0, k=hk, k33=hk + ) + + # chd files + chdspdict = { + 0: [[(0, 0, 0), 10.0], [(0, 0, ncol - 1), 9.0]], + } + chd = flopy.mf6.ModflowGwfchd( + gwf, + print_input=True, + print_flows=True, + stress_period_data=chdspdict, + save_flows=False, + pname="CHD-1", + ) + + # output control + oc = flopy.mf6.ModflowGwfoc( + gwf, + budget_filerecord=f"{gwfname}.cbc", + head_filerecord=f"{gwfname}.hds", + headprintrecord=[("COLUMNS", 10, "WIDTH", 15, "DIGITS", 6, "GENERAL")], + saverecord=[("HEAD", "ALL")], + printrecord=[("HEAD", "ALL"), ("BUDGET", "ALL")], + ) + + # create gwt model + gwtname = "gwt_" + name + gwt = flopy.mf6.ModflowGwt(sim, modelname=gwtname, save_flows=True) + + # create iterative model solution and register the gwt model with it + imsgwt = flopy.mf6.ModflowIms( + sim, + print_option="SUMMARY", + outer_dvclose=hclose, + outer_maximum=nouter, + under_relaxation="NONE", + inner_maximum=ninner, + inner_dvclose=hclose, + rcloserecord=rclose, + linear_acceleration="BICGSTAB", + scaling_method="NONE", + reordering_method="NONE", + relaxation_factor=relax, + filename=f"{gwtname}.ims", + ) + sim.register_ims_package(imsgwt, [gwt.name]) + + dis = flopy.mf6.ModflowGwtdis( + gwt, + nlay=nlay, + nrow=nrow, + ncol=ncol, + delr=delr, + delc=delc, + top=top, + botm=botm, + filename=f"{gwtname}.dis", + ) + + # initial conditions + ic = flopy.mf6.ModflowGwtic(gwt, strt=0.0) + + # advection + adv = flopy.mf6.ModflowGwtadv(gwt, scheme="UPSTREAM") + + # dispersion + xt3d_off = True + dsp = flopy.mf6.ModflowGwtdsp( + gwt, + xt3d_off=xt3d_off, + diffc=0.0, + alh=0.5, + ath1=0.0, + ) + + # mass storage and transfer + bulk_density = 1.6 + thetam = 0.14 + thetaim = 0.12 + volfracm = thetam / (thetam + thetaim) + volfracim = 1.0 - volfracm + porositym = thetam / volfracm + porosityim = thetaim / volfracim + mst = flopy.mf6.ModflowGwtmst( + gwt, + sorption="LINEAR", + porosity=porositym, + bulk_density=bulk_density, + distcoef=0.1, + ) + + # immobile storage and transfer + cim_filerecord = f"{gwtname}.ist.ucn" + ist = flopy.mf6.ModflowGwtist( + gwt, + sorption=True, + save_flows=True, + cim_filerecord=cim_filerecord, + cim=0.0, + porosity=porosityim, + volfrac=volfracim, + bulk_density=bulk_density, + zetaim=0.1, + distcoef=0.1, + ) + + # cnc + cncspdict = { + 0: [[(0, 0, 0), 1.0]], + 1: [[(0, 0, 0), 0.0]], + } + cnc = flopy.mf6.ModflowGwtcnc( + gwt, + print_input=True, + print_flows=True, + maxbound=1, + stress_period_data=cncspdict, + save_flows=False, + pname="CNC-1", + ) + + # sources + sourcerecarray = [()] + ssm = flopy.mf6.ModflowGwtssm(gwt, sources=sourcerecarray) + + # output control + oc = flopy.mf6.ModflowGwtoc( + gwt, + budget_filerecord=f"{gwtname}.cbc", + concentration_filerecord=f"{gwtname}.ucn", + concentrationprintrecord=[ + ("COLUMNS", 10, "WIDTH", 15, "DIGITS", 6, "GENERAL") + ], + saverecord=[("CONCENTRATION", "ALL"), ("BUDGET", "ALL")], + printrecord=[("CONCENTRATION", "ALL"), ("BUDGET", "ALL")], + ) + + obs_data = { + f"{gwtname}.obs.csv": [ + ("(1-1-300)", "CONCENTRATION", (0, 0, ncol - 1)), + ], + } + obs_package = flopy.mf6.ModflowUtlobs( + gwt, + # pname="conc_obs", + filename=f"{gwtname}.obs", + digits=10, + print_input=True, + continuous=obs_data, + ) + + # GWF GWT exchange + gwfgwt = flopy.mf6.ModflowGwfgwt( + sim, + exgtype="GWF6-GWT6", + exgmnamea=gwfname, + exgmnameb=gwtname, + filename=f"{name}.gwfgwt", + ) + + return sim, None + + +def make_plot(sim): + print("making plots...") + name = sim.name + ws = sim.simpath + sim = flopy.mf6.MFSimulation.load(sim_ws=ws) + gwfname = "gwf_" + name + gwtname = "gwt_" + name + gwf = sim.get_model(gwfname) + gwt = sim.get_model(gwtname) + + output = gwt.obs.output + obs_names = output.obs_names + data = output.obs(f=obs_names[0]).data + + import matplotlib.pyplot as plt + + fig = plt.figure(figsize=(10, 10)) + ax = fig.add_subplot(1, 1, 1) + ax.plot(data["totim"], data["(1-1-300)"], "k-", label="mf6") + ax.plot(mt3d_times, mt3d_conc, "ko", label="mt3d") + plt.xlabel("time, in days") + plt.ylabel("concentration, dimensionless") + plt.legend() + fname = os.path.join(ws, gwtname + ".png") + plt.savefig(fname) + + return + + +def eval_transport(sim): + print("evaluating transport...") + + makeplot = False + if makeplot: + make_plot(sim) + + name = sim.name + gwtname = "gwt_" + name + gwfname = "gwf_" + name + + # load the observed concentrations in column 300 + fname = os.path.join(sim.simpath, gwtname + ".obs.csv") + assert os.path.isfile(fname), f"file not found: {fname}" + simvals = np.genfromtxt(fname, names=True, delimiter=",", deletechars="") + + # interpolate mf6 results to same times as mt3d + mf6conc_interp = np.interp( + mt3d_times, simvals["time"], simvals["(1-1-300)"] + ) + + # calculate difference between mf6 and mt3d + atol = 0.05 + diff = mf6conc_interp - mt3d_conc + success = True + print("index mf6 mt3d diff") + for i in range(mf6conc_interp.shape[0]): + print(f"{i} {mf6conc_interp[i]:.3f} {mt3d_conc[i]:.3f} {diff[i]:.3f}") + if abs(diff[i]) > atol: + success = False + assert success, "Conc difference between mf6 and mt3d > 0.05" + + +@pytest.mark.parametrize( + "name", + ex, +) +def test_mf6model(name, function_tmpdir, targets): + ws = str(function_tmpdir) + test = TestFramework() + test.build(build_model, 0, ws) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_transport, idxsim=0 + ), + ws, + ) From 50ee8eaf44346765fa7b7d0cb64b04e7c7bd3d79 Mon Sep 17 00:00:00 2001 From: w-bonelli Date: Sat, 10 Jun 2023 09:44:26 -0400 Subject: [PATCH 096/123] ci: test release docs pdf build in linux CI (#1240) --- .github/workflows/ci.yml | 55 +++++++++++++++++++++++++++++++++++--- distribution/build_docs.py | 31 ++++++++++++++------- 2 files changed, 72 insertions(+), 14 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b3410fde337..8825cafe9a6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -155,7 +155,13 @@ jobs: with: repository: MODFLOW-USGS/modflow6-testmodels path: modflow6-testmodels - + + - name: Checkout modflow6-examples + uses: actions/checkout@v3 + with: + repository: MODFLOW-USGS/modflow6-examples + path: modflow6-examples + - name: Setup GNU Fortran ${{ env.GCC_V }} uses: awvwgk/setup-fortran@main with: @@ -187,9 +193,10 @@ jobs: working-directory: modflow6/autotest env: GITHUB_TOKEN: ${{ github.token }} - run: pytest -v --durations 0 get_exes.py + run: | + pytest -v --durations 0 get_exes.py - - name: Test programs + - name: Test modflow6 working-directory: modflow6/autotest env: REPOS_PATH: ${{ github.workspace }} @@ -199,8 +206,48 @@ jobs: else pytest -v -n auto --durations 0 -m "not large" fi + + # steps below run only on Linux to test distribution procedures, e.g. + # compiling binaries, building documentation + - name: Checkout usgslatex + if: runner.os == 'Linux' + uses: actions/checkout@v3 + with: + repository: MODFLOW-USGS/usgslatex + path: usgslatex - - name: Test scripts + - name: Install TeX Live + if: runner.os == 'Linux' + run: | + sudo apt-get update + sudo apt install texlive-science \ + texlive-latex-extra \ + texlive-font-utils \ + texlive-fonts-recommended \ + texlive-fonts-extra + + - name: Install USGS LaTeX style files and Univers font + if: runner.os == 'Linux' + working-directory: usgslatex/usgsLaTeX + run: sudo ./install.sh --all-users + + - name: Install dependencies for ex-gwf-twri example model + if: runner.os == 'Linux' + working-directory: modflow6-examples/etc + run: | + # install extra Python packages + pip install -r requirements.pip.txt + + # the example model needs executables to be on the path + echo "${{ github.workspace }}/modflow6/bin" >> $GITHUB_PATH + echo "${{ github.workspace }}/modflow6/bin/downloaded" >> $GITHUB_PATH + + - name: Build ex-gwf-twri example model + if: runner.os == 'Linux' + working-directory: modflow6-examples/scripts + run: python ex-gwf-twri.py + + - name: Test distribution scripts working-directory: modflow6/distribution env: GITHUB_TOKEN: ${{ github.token }} diff --git a/distribution/build_docs.py b/distribution/build_docs.py index c1d4ce1913b..f57d56c9ac0 100644 --- a/distribution/build_docs.py +++ b/distribution/build_docs.py @@ -18,14 +18,14 @@ from modflow_devtools.build import meson_build from modflow_devtools.download import list_artifacts, download_artifact, get_release, download_and_unzip from modflow_devtools.markers import requires_exe, requires_github -from modflow_devtools.misc import set_dir, run_cmd +from modflow_devtools.misc import set_dir, run_cmd, is_in_ci from benchmark import run_benchmarks from utils import convert_line_endings from utils import get_project_root_path _project_root_path = get_project_root_path() -_version_texf_path = _project_root_path / "doc" / "version.tex" +_bin_path = _project_root_path / "bin" _examples_repo_path = _project_root_path.parent / "modflow6-examples" _release_notes_path = _project_root_path / "doc" / "ReleaseNotes" _distribution_path = _project_root_path / "distribution" @@ -157,7 +157,6 @@ def build_benchmark_tex(output_path: PathLike, overwrite: bool = False): @flaky @requires_github -@pytest.mark.skipif(not (_benchmarks_path / "run-time-comparison.md").is_file(), reason="needs benchmarks") def test_build_benchmark_tex(tmp_path): benchmarks_path = _benchmarks_path / "run-time-comparison.md" tex_path = _distribution_path / f"{benchmarks_path.stem}.tex" @@ -284,7 +283,6 @@ def build_mf6io_tex_example(workspace_path: PathLike, bin_path: PathLike, exampl shutil.copytree(example_model_path, workspace_path) # run example model - with set_dir(workspace_path): out, err, ret = run_cmd(cmd) buff = out + err @@ -328,6 +326,7 @@ def build_mf6io_tex_example(workspace_path: PathLike, bin_path: PathLike, exampl f.write("}\n") +@pytest.mark.skip(reason="todo") def test_build_mf6io_tex_example(): pass @@ -385,7 +384,6 @@ def test_build_pdfs_from_tex(tmp_path): _docs_path / "zonebudget" / "zonebudget.tex", _docs_path / "ConverterGuide" / "converter_mf5to6.tex", _docs_path / "SuppTechInfo" / "mf6suptechinfo.tex", - _examples_repo_path / "doc" / "mf6examples.tex", ] bbl_paths = [ _docs_path / "ConverterGuide" / "converter_mf5to6.bbl", @@ -403,6 +401,9 @@ def test_build_pdfs_from_tex(tmp_path): def build_documentation(bin_path: PathLike, output_path: PathLike, examples_repo_path: PathLike, + # Example to use to render sample mf6 output in the docs. + # Must be a valid directory in modflow6-examples/examples + example_for_sample: str = "ex-gwf-twri01", development: bool = False, overwrite: bool = False): print(f"Building {'development' if development else 'full'} documentation") @@ -423,7 +424,7 @@ def build_documentation(bin_path: PathLike, build_mf6io_tex_example( workspace_path=temp_path, bin_path=bin_path, - example_model_path=examples_repo_path / "examples" / "ex-gwf-twri01", + example_model_path=examples_repo_path / "examples" / example_for_sample, ) # build LaTeX file describing distribution folder structure @@ -474,13 +475,14 @@ def build_documentation(bin_path: PathLike, @requires_exe("pdflatex") -@pytest.mark.skip(reason="manual testing") -@pytest.mark.skipif(not (_benchmarks_path / "run-time-comparison.md").is_file(), reason="needs benchmarks") +# skip if in CI so we don't have to build/process example models, +# example model docs can be tested in the modflow6-examples repo +@pytest.mark.skipif(is_in_ci(), reason="needs built/processed example models") def test_build_documentation(tmp_path): bin_path = tmp_path / "bin" dist_path = tmp_path / "dist" meson_build(_project_root_path, tmp_path / "builddir", bin_path) - build_documentation(bin_path, dist_path, _examples_repo_path) #, _benchmarks_path / "run-time-comparison.md") + build_documentation(bin_path, dist_path, _examples_repo_path) if __name__ == "__main__": @@ -502,7 +504,7 @@ def test_build_documentation(tmp_path): "-b", "--bin-path", required=False, - default=os.getcwd(), + default=str(_bin_path), help="Location of modflow6 executables", ) parser.add_argument( @@ -512,6 +514,13 @@ def test_build_documentation(tmp_path): default=str(_examples_repo_path), help="Path to directory containing modflow6 example models" ) + parser.add_argument( + "-s", + "--example-for-sample", + required=False, + default=str(_examples_repo_path), + help="Name of example model to use for sample mf6 output" + ) parser.add_argument( "-o", "--output-path", @@ -541,10 +550,12 @@ def test_build_documentation(tmp_path): output_path.mkdir(parents=True, exist_ok=True) bin_path = Path(args.bin_path).expanduser().absolute() examples_repo_path = Path(args.examples_repo_path).expanduser().absolute() + example_for_sample = args.example_for_sample build_documentation( bin_path=bin_path, output_path=output_path, examples_repo_path=examples_repo_path, + example_for_sample=example_for_sample, development=args.development, overwrite=args.force) From 6a492b9e0e2960ec33de69e381cc6764dad60c69 Mon Sep 17 00:00:00 2001 From: Mike Taves Date: Mon, 12 Jun 2023 20:02:45 +1200 Subject: [PATCH 097/123] feat(BMI): Add get/set for logical (bool) scalar values (#1236) --- srcbmi/mf6bmi.f90 | 148 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 148 insertions(+) diff --git a/srcbmi/mf6bmi.f90 b/srcbmi/mf6bmi.f90 index c191ba937e5..2d09ecbfb3e 100644 --- a/srcbmi/mf6bmi.f90 +++ b/srcbmi/mf6bmi.f90 @@ -515,6 +515,52 @@ function get_value_int(c_var_address, c_arr_ptr) result(bmi_status) & end function get_value_int + !> @brief Copy the logical scalar value into the array + !! + !! The copied variable us located at @p c_var_address. The caller should + !! provide @p c_arr_ptr pointing to a scalar array with rank=0. + !< + function get_value_bool(c_var_address, c_arr_ptr) result(bmi_status) & + bind(C, name="get_value_bool") + !DIR$ ATTRIBUTES DLLEXPORT :: get_value_bool + ! -- modules + use MemorySetHandlerModule, only: on_memory_set + ! -- dummy variables + character(kind=c_char), intent(in) :: c_var_address(*) !< memory address string of the variable + type(c_ptr), intent(in) :: c_arr_ptr !< pointer to the logical array + integer(kind=c_int) :: bmi_status !< BMI status code + ! -- local variables + character(len=LENMEMPATH) :: mem_path + character(len=LENVARNAME) :: var_name + logical(LGP) :: valid + integer(I4B) :: rank + logical(LGP), pointer :: src_ptr, tgt_ptr + + bmi_status = BMI_SUCCESS + + call split_address(c_var_address, mem_path, var_name, valid) + if (.not. valid) then + bmi_status = BMI_FAILURE + return + end if + + rank = -1 + call get_mem_rank(var_name, mem_path, rank) + + ! convert pointer + if (rank == 0) then + call mem_setptr(src_ptr, var_name, mem_path) + call c_f_pointer(c_arr_ptr, tgt_ptr) + tgt_ptr = src_ptr + else + write (bmi_last_error, fmt_unsupported_rank) trim(var_name) + call report_bmi_error(bmi_last_error) + bmi_status = BMI_FAILURE + return + end if + + end function get_value_bool + !> @brief Copy the string(s) of a variable into the array !! !! The copied variable is located at @p c_var_address. The caller should @@ -629,6 +675,8 @@ function get_value(c_var_address, c_arr_ptr) result(bmi_status) & bmi_status = get_value_double(c_var_address, c_arr_ptr) else if (index(mem_type, "INTEGER") /= 0) then bmi_status = get_value_int(c_var_address, c_arr_ptr) + else if (index(mem_type, "LOGICAL") /= 0) then + bmi_status = get_value_bool(c_var_address, c_arr_ptr) else if (index(mem_type, "STRING") /= 0) then bmi_status = get_value_string(c_var_address, c_arr_ptr) else @@ -676,6 +724,8 @@ function get_value_ptr(c_var_address, c_arr_ptr) result(bmi_status) & bmi_status = get_value_ptr_double(c_var_address, c_arr_ptr) else if (index(mem_type, "INTEGER") /= 0) then bmi_status = get_value_ptr_int(c_var_address, c_arr_ptr) + else if (index(mem_type, "LOGICAL") /= 0) then + bmi_status = get_value_ptr_bool(c_var_address, c_arr_ptr) else write (bmi_last_error, fmt_unsupported_type) trim(var_name) call report_bmi_error(bmi_last_error) @@ -795,6 +845,46 @@ function get_value_ptr_int(c_var_address, c_arr_ptr) result(bmi_status) & end function get_value_ptr_int + !> @brief Get a pointer to the logical scalar value + !! + !! Only scalar values (with rank=0) are supported. + !< + function get_value_ptr_bool(c_var_address, c_arr_ptr) result(bmi_status) & + bind(C, name="get_value_ptr_bool") + !DIR$ ATTRIBUTES DLLEXPORT :: get_value_ptr_bool + ! -- dummy variables + character(kind=c_char), intent(in) :: c_var_address(*) !< memory address string of the variable + type(c_ptr), intent(inout) :: c_arr_ptr !< pointer to the array + integer(kind=c_int) :: bmi_status !< BMI status code + ! -- local variables + character(len=LENMEMPATH) :: mem_path + character(len=LENVARNAME) :: var_name + logical(LGP) :: valid + logical(LGP), pointer :: scalar_ptr + integer(I4B) :: rank + + bmi_status = BMI_SUCCESS + + call split_address(c_var_address, mem_path, var_name, valid) + if (.not. valid) then + bmi_status = BMI_FAILURE + return + end if + + rank = -1 + call get_mem_rank(var_name, mem_path, rank) + if (rank == 0) then + call mem_setptr(scalar_ptr, var_name, mem_path) + c_arr_ptr = c_loc(scalar_ptr) + else + write (bmi_last_error, fmt_unsupported_rank) trim(var_name) + call report_bmi_error(bmi_last_error) + bmi_status = BMI_FAILURE + return + end if + + end function get_value_ptr_bool + !> @brief Set new values for a given variable !! !! The array pointed to by @p c_arr_ptr can have rank equal to 0, 1, or 2 @@ -830,6 +920,8 @@ function set_value(c_var_address, c_arr_ptr) result(bmi_status) & bmi_status = set_value_double(c_var_address, c_arr_ptr) else if (index(mem_type, "INTEGER") /= 0) then bmi_status = set_value_int(c_var_address, c_arr_ptr) + else if (index(mem_type, "LOGICAL") /= 0) then + bmi_status = set_value_bool(c_var_address, c_arr_ptr) else write (bmi_last_error, fmt_unsupported_type) trim(var_name) call report_bmi_error(bmi_last_error) @@ -987,6 +1079,62 @@ function set_value_int(c_var_address, c_arr_ptr) result(bmi_status) & end function set_value_int + !> @brief Set new value for a logical scalar variable + !! + !! The array pointed to by @p c_arr_ptr must have a rank equal to 0. + !< + function set_value_bool(c_var_address, c_arr_ptr) result(bmi_status) & + bind(C, name="set_value_bool") + !DIR$ ATTRIBUTES DLLEXPORT :: set_value_bool + ! -- modules + use MemorySetHandlerModule, only: on_memory_set + ! -- dummy variables + character(kind=c_char), intent(in) :: c_var_address(*) !< memory address string of the variable + type(c_ptr), intent(in) :: c_arr_ptr !< pointer to the logical array + integer(kind=c_int) :: bmi_status !< BMI status code + ! -- local variables + character(len=LENMEMPATH) :: mem_path + character(len=LENVARNAME) :: var_name + logical(LGP) :: valid + integer(I4B) :: rank + logical(LGP), pointer :: src_ptr, tgt_ptr + integer(I4B) :: status + + bmi_status = BMI_SUCCESS + + call split_address(c_var_address, mem_path, var_name, valid) + if (.not. valid) then + bmi_status = BMI_FAILURE + return + end if + + rank = -1 + call get_mem_rank(var_name, mem_path, rank) + + ! convert pointer + if (rank == 0) then + call mem_setptr(tgt_ptr, var_name, mem_path) + call c_f_pointer(c_arr_ptr, src_ptr) + tgt_ptr = src_ptr + else + write (bmi_last_error, fmt_unsupported_rank) trim(var_name) + call report_bmi_error(bmi_last_error) + bmi_status = BMI_FAILURE + return + end if + + ! trigger event: + call on_memory_set(var_name, mem_path, status) + if (status /= 0) then + ! something went terribly wrong here, aborting + write (bmi_last_error, fmt_invalid_mem_access) trim(var_name) + call report_bmi_error(bmi_last_error) + bmi_status = BMI_FAILURE + return + end if + + end function set_value_bool + !> @brief Get the variable type as a string !! !! The type returned is that of a single element. From 36736bdc774934aa37bea4772371e22f03c462bc Mon Sep 17 00:00:00 2001 From: Mike Taves Date: Tue, 13 Jun 2023 01:58:55 +1200 Subject: [PATCH 098/123] refactor(tdis): Calculate exact last delt to avoid accumulation errors with totim (#1235) --- autotest/conftest.py | 12 ++++++ autotest/test_gwf_tdis.py | 86 +++++++++++++++++++++++++++++++++++++++ src/Timing/tdis.f90 | 19 +++++++-- 3 files changed, 114 insertions(+), 3 deletions(-) create mode 100644 autotest/test_gwf_tdis.py diff --git a/autotest/conftest.py b/autotest/conftest.py index 21fb8d1ba88..a1a7e954456 100644 --- a/autotest/conftest.py +++ b/autotest/conftest.py @@ -1,3 +1,4 @@ +import platform from pathlib import Path import pytest @@ -31,6 +32,17 @@ def bin_path() -> Path: return project_root_path / "bin" +@pytest.fixture(scope="session") +def libmf6_path(bin_path) -> Path: + ext = { + "Darwin": ".dylib", + "Linux": ".so", + "Windows": ".dll", + }[platform.system()] + lib_name = bin_path / f"libmf6{ext}" + return lib_name + + @pytest.fixture(scope="session") def targets(bin_path) -> Executables: return Executables(**build_default_exe_dict(bin_path)) diff --git a/autotest/test_gwf_tdis.py b/autotest/test_gwf_tdis.py new file mode 100644 index 00000000000..c7f62c7e9f2 --- /dev/null +++ b/autotest/test_gwf_tdis.py @@ -0,0 +1,86 @@ +"""Test TDIS package.""" +import flopy +import numpy as np +import pytest +from xmipy import XmiWrapper + + +@pytest.fixture +def simple_sim(tmp_path): + """Create a simple and bogus GWF simulation without TDIS package.""" + sim = flopy.mf6.MFSimulation(sim_ws=str(tmp_path)) + _ = flopy.mf6.ModflowTdis(sim) # placeholder + _ = flopy.mf6.ModflowIms(sim) + gwf = flopy.mf6.ModflowGwf(sim) + _ = flopy.mf6.ModflowGwfdis(gwf) + _ = flopy.mf6.ModflowGwfic(gwf) + _ = flopy.mf6.ModflowGwfnpf(gwf) + sim.write_simulation() + + try: + sim.remove_package("TDIS") + except AttributeError: + pass + + return sim + + +@pytest.mark.parametrize("tsmult", [1.0, 1.2]) +def test_tdis_tsmult(tsmult, libmf6_path, simple_sim): + """Check totim values to ensure they avoid accumulation errors.""" + sim = simple_sim + + # Add TDIS package using time variables + nper = 4 + nstp = 3 + perlen = 7.0 + tdis = flopy.mf6.ModflowTdis( + sim, + time_units="DAYS", + nper=nper, + perioddata=[(perlen, nstp, tsmult)] * nper, + ) + tdis.write() + + # Run within libmf6 + mf6 = XmiWrapper(lib_path=libmf6_path, working_directory=sim.sim_path) + + mf6.initialize() + dt_list = [] + totim = mf6.get_current_time() + all_times = [totim] + sp_times = [totim] + for kper in range(nper): + for kstep in range(nstp): + mf6.update() + delt = mf6.get_time_step() + totim = mf6.get_current_time() + all_times.append(totim) + dt_list.append(delt) + sp_times.append(totim) + mf6.finalize() + + # Stress period times should be exact + np.testing.assert_equal(sp_times, np.arange(nper + 1) * perlen) + + if tsmult == 1.0: + # Note that delt may not always be exactly unique, but should be close + assert max(dt_list) - min(dt_list) < 1e-14 + assert pytest.approx(dt_list[0]) == perlen / nstp + + # Check all times + np.testing.assert_allclose( + all_times, + np.linspace(0.0, perlen * nper, nper * nstp + 1), + ) + else: + # Recreate geometric progression to check delt + dt0 = perlen * (1.0 - tsmult) / (1.0 - tsmult**nstp) + expected_dt = np.tile(dt0 * tsmult ** np.arange(nstp), nper) + np.testing.assert_allclose(dt_list, expected_dt) + + # Check all times + np.testing.assert_allclose( + all_times, + np.cumsum(np.insert(expected_dt, 0, 0.0)), + ) diff --git a/src/Timing/tdis.f90 b/src/Timing/tdis.f90 index 01c631ac8fd..2cd72aa9b4b 100644 --- a/src/Timing/tdis.f90 +++ b/src/Timing/tdis.f90 @@ -29,6 +29,7 @@ module TdisModule logical(LGP), public, pointer :: endofsimulation => null() !< flag indicating end of simulation real(DP), public, pointer :: delt => null() !< length of the current time step real(DP), public, pointer :: pertim => null() !< time relative to start of stress period + real(DP), public, pointer :: topertim => null() !< simulation time at start of stress period real(DP), public, pointer :: totim => null() !< time relative to start of simulation real(DP), public, pointer :: totimc => null() !< simulation time at start of time step real(DP), public, pointer :: deltsav => null() !< saved value for delt, used for subtiming @@ -194,6 +195,7 @@ subroutine tdis_set_timestep() adaptivePeriod = isAdaptivePeriod(kper) if (kstp == 1) then pertim = DZERO + topertim = DZERO end if ! ! -- Set delt @@ -229,7 +231,6 @@ subroutine tdis_set_timestep() ! -- Set end of simulation indicator if (endofperiod .and. kper == nper) then endofsimulation = .true. - totim = totalsimtime end if ! ! -- return @@ -295,10 +296,19 @@ subroutine tdis_set_delt() ! ------------------------------------------------------------------------------ ! if (kstp == 1) then - delt = perlen(kper) / float(nstp(kper)) - if (tsmult(kper) /= DONE) & + ! -- Calculate the first value of delt for this stress period + topertim = totim + if (tsmult(kper) /= DONE) then + ! -- Timestep length has a geometric progression delt = perlen(kper) * (DONE - tsmult(kper)) / & (DONE - tsmult(kper)**nstp(kper)) + else + ! -- Timestep length is constant + delt = perlen(kper) / float(nstp(kper)) + end if + elseif (kstp == nstp(kper)) then + ! -- Calculate exact last delt to avoid accumulation errors + delt = topertim + perlen(kper) - totim else delt = tsmult(kper) * delt end if @@ -467,6 +477,7 @@ subroutine tdis_da() call mem_deallocate(endofsimulation) call mem_deallocate(delt) call mem_deallocate(pertim) + call mem_deallocate(topertim) call mem_deallocate(totim) call mem_deallocate(totimc) call mem_deallocate(deltsav) @@ -611,6 +622,7 @@ subroutine tdis_allocate_scalars() call mem_allocate(endofsimulation, 'ENDOFSIMULATION', 'TDIS') call mem_allocate(delt, 'DELT', 'TDIS') call mem_allocate(pertim, 'PERTIM', 'TDIS') + call mem_allocate(topertim, 'TOPERTIM', 'TDIS') call mem_allocate(totim, 'TOTIM', 'TDIS') call mem_allocate(totimc, 'TOTIMC', 'TDIS') call mem_allocate(deltsav, 'DELTSAV', 'TDIS') @@ -632,6 +644,7 @@ subroutine tdis_allocate_scalars() endofsimulation = .false. delt = DZERO pertim = DZERO + topertim = DZERO totim = DZERO totimc = DZERO deltsav = DZERO From 642f02f7fc0ebe7c22a66fa40b962caa70f1cdf1 Mon Sep 17 00:00:00 2001 From: Eric Morway Date: Mon, 12 Jun 2023 07:00:19 -0700 Subject: [PATCH 099/123] misc(gwt1dsp.f90): Rename for consistency with GWE ( -> gwt1dsp1.f90) (#1242) --- make/makefile | 4 ++-- msvs/mf6core.vfproj | 4 ++-- pymake/makefile | 4 ++-- src/Model/GroundWaterTransport/{gwt1dsp.f90 => gwt1dsp1.f90} | 0 .../GroundWaterTransport/{gwt1dspidm.f90 => gwt1dsp1idm.f90} | 0 src/meson.build | 4 ++-- utils/idmloader/scripts/dfn2f90.py | 2 +- 7 files changed, 9 insertions(+), 9 deletions(-) rename src/Model/GroundWaterTransport/{gwt1dsp.f90 => gwt1dsp1.f90} (100%) rename src/Model/GroundWaterTransport/{gwt1dspidm.f90 => gwt1dsp1idm.f90} (100%) diff --git a/make/makefile b/make/makefile index 90d440be5b6..3c34055f856 100644 --- a/make/makefile +++ b/make/makefile @@ -146,7 +146,7 @@ $(OBJDIR)/VirtualDataContainer.o \ $(OBJDIR)/SimStages.o \ $(OBJDIR)/simnamidm.o \ $(OBJDIR)/gwt1idm.o \ -$(OBJDIR)/gwt1dspidm.o \ +$(OBJDIR)/gwt1dsp1idm.o \ $(OBJDIR)/gwt1disv1idm.o \ $(OBJDIR)/gwt1disu1idm.o \ $(OBJDIR)/gwt1dis1idm.o \ @@ -225,7 +225,7 @@ $(OBJDIR)/gwt1mwt1.o \ $(OBJDIR)/gwt1mvt1.o \ $(OBJDIR)/gwt1lkt1.o \ $(OBJDIR)/gwt1ist1.o \ -$(OBJDIR)/gwt1dsp.o \ +$(OBJDIR)/gwt1dsp1.o \ $(OBJDIR)/gwt1cnc1.o \ $(OBJDIR)/gwt1adv1.o \ $(OBJDIR)/gwf3disv8.o \ diff --git a/msvs/mf6core.vfproj b/msvs/mf6core.vfproj index 7ce104b72bb..e2be9f2e7b6 100644 --- a/msvs/mf6core.vfproj +++ b/msvs/mf6core.vfproj @@ -160,8 +160,8 @@ - - + + diff --git a/pymake/makefile b/pymake/makefile index 62617b14b8d..484e7798cf0 100644 --- a/pymake/makefile +++ b/pymake/makefile @@ -90,7 +90,7 @@ $(OBJDIR)/CsrUtils.o \ $(OBJDIR)/gwt1idm.o \ $(OBJDIR)/gwf3disv8idm.o \ $(OBJDIR)/blas1_d.o \ -$(OBJDIR)/gwt1dspidm.o \ +$(OBJDIR)/gwt1dsp1idm.o \ $(OBJDIR)/CharString.o \ $(OBJDIR)/OpenSpec.o \ $(OBJDIR)/dag_module.o \ @@ -269,7 +269,7 @@ $(OBJDIR)/DisConnExchange.o \ $(OBJDIR)/GhostNode.o \ $(OBJDIR)/gwt1mvt1.o \ $(OBJDIR)/VirtualExchange.o \ -$(OBJDIR)/gwt1dsp.o \ +$(OBJDIR)/gwt1dsp1.o \ $(OBJDIR)/gwf3buy8.o \ $(OBJDIR)/VirtualGwfExchange.o \ $(OBJDIR)/gwf3hfb8.o \ diff --git a/src/Model/GroundWaterTransport/gwt1dsp.f90 b/src/Model/GroundWaterTransport/gwt1dsp1.f90 similarity index 100% rename from src/Model/GroundWaterTransport/gwt1dsp.f90 rename to src/Model/GroundWaterTransport/gwt1dsp1.f90 diff --git a/src/Model/GroundWaterTransport/gwt1dspidm.f90 b/src/Model/GroundWaterTransport/gwt1dsp1idm.f90 similarity index 100% rename from src/Model/GroundWaterTransport/gwt1dspidm.f90 rename to src/Model/GroundWaterTransport/gwt1dsp1idm.f90 diff --git a/src/meson.build b/src/meson.build index d84924ee05e..0f1156ff9e3 100644 --- a/src/meson.build +++ b/src/meson.build @@ -87,8 +87,8 @@ modflow_sources = files( 'Model' / 'GroundWaterTransport' / 'gwt1dis1idm.f90', 'Model' / 'GroundWaterTransport' / 'gwt1disu1idm.f90', 'Model' / 'GroundWaterTransport' / 'gwt1disv1idm.f90', - 'Model' / 'GroundWaterTransport' / 'gwt1dsp.f90', - 'Model' / 'GroundWaterTransport' / 'gwt1dspidm.f90', + 'Model' / 'GroundWaterTransport' / 'gwt1dsp1.f90', + 'Model' / 'GroundWaterTransport' / 'gwt1dsp1idm.f90', 'Model' / 'GroundWaterTransport' / 'gwt1fmi1.f90', 'Model' / 'GroundWaterTransport' / 'gwt1ic1.f90', 'Model' / 'GroundWaterTransport' / 'gwt1idm.f90', diff --git a/utils/idmloader/scripts/dfn2f90.py b/utils/idmloader/scripts/dfn2f90.py index 1921a3cc5d0..6f59df4b38b 100644 --- a/utils/idmloader/scripts/dfn2f90.py +++ b/utils/idmloader/scripts/dfn2f90.py @@ -859,7 +859,7 @@ def _write_master_integration(self, fh=None): ], [ Path("../../../doc/mf6io/mf6ivar/dfn", "gwt-dsp.dfn"), - Path("../../../src/Model/GroundWaterTransport", "gwt1dspidm.f90"), + Path("../../../src/Model/GroundWaterTransport", "gwt1dsp1idm.f90"), ], [ Path("../../../doc/mf6io/mf6ivar/dfn", "gwf-nam.dfn"), From 49d48c153b45367b921afb960764139bc3bd6295 Mon Sep 17 00:00:00 2001 From: langevin-usgs Date: Mon, 12 Jun 2023 12:02:26 -0500 Subject: [PATCH 100/123] refactor(io): remove u8rdcom (#1250) The u9rdcom line reader was introduced to replace the u8rdcom line reader, however, u8rdcom was not removed completely. This PR removes u8rdcom entirely. This simplification may help with ongoing efforts to remove calls to the Fortran backspace intrinsic so that mf6 will compile with latest versions of ifort. --- src/Utilities/ArrayReaders.f90 | 12 ++- src/Utilities/InputOutput.f90 | 123 +------------------------ utils/mf5to6/src/ArrayReadersMF5.f90 | 18 ++-- utils/mf5to6/src/Preproc/Utilities.f90 | 4 +- 4 files changed, 20 insertions(+), 137 deletions(-) diff --git a/src/Utilities/ArrayReaders.f90 b/src/Utilities/ArrayReaders.f90 index c3a98961285..537e0d47f50 100644 --- a/src/Utilities/ArrayReaders.f90 +++ b/src/Utilities/ArrayReaders.f90 @@ -3,7 +3,7 @@ module ArrayReadersModule use ConstantsModule, only: DONE, LINELENGTH, LENBIGLINE, LENBOUNDNAME, & NAMEDBOUNDFLAG, LINELENGTH, DZERO, MAXCHARLEN, & DZERO - use InputOutputModule, only: openfile, u8rdcom, urword, ucolno, ulaprw, & + use InputOutputModule, only: openfile, u9rdcom, urword, ucolno, ulaprw, & BuildFixedFormat, BuildFloatFormat, & BuildIntFormat use KindModule, only: DP, I4B @@ -578,7 +578,8 @@ subroutine read_control_int(iu, iout, aname, locat, iconst, & ! -- local integer(I4B) :: icol, icol1, istart, istop, n real(DP) :: r - character(len=MAXCHARLEN) :: fname, line + character(len=MAXCHARLEN) :: fname + character(len=:), allocatable :: line ! ! -- Read CONSTANT, INTERNAL, or OPEN/CLOSE from array control record. call read_control_1(iu, iout, aname, locat, iclose, line, icol, fname) @@ -627,7 +628,8 @@ subroutine read_control_dbl(iu, iout, aname, locat, cnstnt, & ! -- local integer(I4B) :: icol, icol1, istart, istop, n real(DP) :: r - character(len=MAXCHARLEN) :: fname, line + character(len=MAXCHARLEN) :: fname + character(len=:), allocatable :: line ! ! -- Read CONSTANT, INTERNAL, or OPEN/CLOSE from array control record. call read_control_1(iu, iout, aname, locat, iclose, line, icol, fname) @@ -664,7 +666,7 @@ subroutine read_control_1(iu, iout, aname, locat, iclose, line, icol, fname) character(len=*), intent(in) :: aname integer(I4B), intent(out) :: locat integer(I4B), intent(out) :: iclose - character(len=*), intent(inout) :: line + character(len=:), allocatable, intent(inout) :: line integer(I4B), intent(inout) :: icol character(len=*), intent(inout) :: fname @@ -675,7 +677,7 @@ subroutine read_control_1(iu, iout, aname, locat, iclose, line, icol, fname) character(len=MAXCHARLEN) :: ermsg ! ! -- Read array control record. - call u8rdcom(iu, iout, line, ierr) + call u9rdcom(iu, iout, line, ierr) ! iclose = 0 icol = 1 diff --git a/src/Utilities/InputOutput.f90 b/src/Utilities/InputOutput.f90 index 33340df950a..8d12c1bd21b 100644 --- a/src/Utilities/InputOutput.f90 +++ b/src/Utilities/InputOutput.f90 @@ -13,7 +13,7 @@ module InputOutputModule DZERO use GenericUtilitiesModule, only: is_same, sim_message private - public :: GetUnit, u8rdcom, uget_block, & + public :: GetUnit, uget_block, & uterminate_block, UPCASE, URWORD, ULSTLB, UBDSV4, & ubdsv06, UBDSVB, UCOLNO, ULAPRW, & ULASAV, ubdsv1, ubdsvc, ubdsvd, UWWORD, & @@ -22,7 +22,7 @@ module InputOutputModule linear_interpolate, lowcase, & read_line, uget_any_block, & GetFileFromPath, extract_idnum_or_bndname, urdaux, & - get_jk, uget_block_line, print_format, BuildFixedFormat, & + get_jk, print_format, BuildFixedFormat, & BuildFloatFormat, BuildIntFormat, fseek_stream, & get_nwords, u9rdcom @@ -201,125 +201,6 @@ function getunit() return end function getunit - !> @brief Get the next non-comment line - !! - !! Subroutine to get the next non-comment line from a file - !! - !< - subroutine u8rdcom(iin, iout, line, ierr) - use, intrinsic :: iso_fortran_env, only: IOSTAT_END - implicit none - ! -- dummy variables - integer(I4B), intent(in) :: iin !< file unit number - integer(I4B), intent(in) :: iout !< output listing file - character (len=*), intent(inout) :: line !< next non-comment line - integer(I4B), intent(out) :: ierr !< error code - ! -- local variables - character (len=2), parameter :: comment = '//' - character(len=1), parameter :: tab = CHAR(9) - logical :: iscomment - integer(I4B) :: i, l - ! - ! -- code - line = comment - pcomments: do - read (iin,'(a)', iostat=ierr) line - if (ierr == IOSTAT_END) then - ! -- End of file reached. - ! -- Backspace is needed for gfortran. - backspace(iin) - line = ' ' - exit pcomments - elseif (ierr /= 0) then - ! -- Other error...report it - call unitinquire(iin) - write(errmsg, *) 'u8rdcom: Could not read from unit: ',iin - call store_error(errmsg, terminate=.TRUE.) - endif - if (len_trim(line).lt.1) then - line = comment - cycle - end if - ! - ! -- Ensure that any initial tab characters are treated as spaces - cleartabs: do - line = trim(adjustl(line)) - iscomment = .false. - select case (line(1:1)) - case ('#') - iscomment = .true. - exit cleartabs - case ('!') - iscomment = .true. - exit cleartabs - case (tab) - line(1:1) = ' ' - cycle cleartabs - case default - if (line(1:2).eq.comment) iscomment = .true. - if (len_trim(line) < 1) iscomment = .true. - exit cleartabs - end select - end do cleartabs - ! - if (.not.iscomment) then - exit pcomments - else - if (iout > 0) then - !find the last non-blank character. - l=len(line) - do i = l, 1, -1 - if(line(i:i).ne.' ') then - exit - end if - end do - !print the line up to the last non-blank character. - write(iout,'(1x,a)') line(1:i) - end if - end if - end do pcomments - return - end subroutine u8rdcom - - !> @brief Get line from a block - !! - !! Subroutine to read and return a line from an external file or from - !! within a block. The line is read from an external file if iu is - !! not equal to iuext. - !! - !< - subroutine uget_block_line(iu, iuext, iout, line, lloc, istart, istop) - implicit none - ! -- dummy variables - integer(I4B), intent(in) :: iu !< file unit number - integer(I4B), intent(in) :: iuext !< external file unit number - integer(I4B), intent(in) :: iout !< output listing file - character (len=*), intent(inout) :: line !< line - integer(I4B), intent(inout) :: lloc !< position in line - integer(I4B), intent(inout) :: istart !< starting position of a word - integer(I4B), intent(inout) :: istop !< ending position of a word - ! -- local variables - integer(I4B) :: ierr - integer(I4B) :: ival - real(DP) :: rval -! ------------------------------------------------------------------------------ - lloc = 1 - call u8rdcom(iuext, iout, line, ierr) - call urword(line, lloc, istart, istop, 1, ival, rval, iout, iuext) - ! -- determine if an empty string is returned - ! condition occurs if the end of the file has been read - if (len_trim(line) < 1) then - ! -- if external file, read line from package unit (iu) - if (iuext /= iu) then - lloc = 1 - call u8rdcom(iu, iout, line, ierr) - call urword(line, lloc, istart, istop, 1, ival, rval, iout, iu) - end if - end if - return - end subroutine uget_block_line - - !> @brief Find a block in a file !! !! Subroutine to read from a file until the tag (ctag) for a block is diff --git a/utils/mf5to6/src/ArrayReadersMF5.f90 b/utils/mf5to6/src/ArrayReadersMF5.f90 index 9a33f0a7a95..96404b9e1d4 100644 --- a/utils/mf5to6/src/ArrayReadersMF5.f90 +++ b/utils/mf5to6/src/ArrayReadersMF5.f90 @@ -2,7 +2,7 @@ module ArrayReadersMF5Module use ConstantsModule, only: LINELENGTH, LENBIGLINE, LENBOUNDNAME, & NAMEDBOUNDFLAG, LINELENGTH, DZERO - use InputOutputModule, only: openfile, u8rdcom, urword, ucolno, ulaprw + use InputOutputModule, only: openfile, u9rdcom, urword, ucolno, ulaprw use KindModule, only: DP, I4B use OpenSpecModule, only: ACCESS, FORM use SimModule, only: store_error, ustop, store_error_unit @@ -71,7 +71,7 @@ subroutine u1ddbl(a,aname,jj,in,iout) integer :: iclose, icol, ierr, ifree, iprn, istart, istop, j, locat, n real(DP) :: r, cnstnt character(len=20) :: fmtin - character(len=200) :: cntrl + character (len=:), allocatable :: cntrl character(len=200) :: fname character(len=linelength) :: ermsg integer, parameter :: nunopn=99 @@ -86,7 +86,7 @@ subroutine u1ddbl(a,aname,jj,in,iout) ! ------------------------------------------------------------------ ! !C1------READ ARRAY CONTROL RECORD AS CHARACTER DATA. - call u8rdcom(in,iout,cntrl,ierr) + call u9rdcom(in,iout,cntrl,ierr) ! !C2------LOOK FOR ALPHABETIC WORD THAT INDICATES THAT THE RECORD IS FREE !C2------FORMAT. SET A FLAG SPECIFYING IF FREE FORMAT OR FIXED FORMAT. @@ -222,7 +222,7 @@ subroutine u1dint(ia,aname,jj,in,iout) locat real(DP) :: r character(len=20) :: fmtin - character(len=200) :: cntrl + character (len=:), allocatable :: cntrl character(len=200) :: fname character(len=linelength) :: ermsg integer, parameter :: nunopn=99 @@ -237,7 +237,7 @@ subroutine u1dint(ia,aname,jj,in,iout) ! ------------------------------------------------------------------ ! !C1------READ ARRAY CONTROL RECORD AS CHARACTER DATA. - call u8rdcom(in,iout,cntrl,ierr) + call u9rdcom(in,iout,cntrl,ierr) !C !C2------LOOK FOR ALPHABETIC WORD THAT INDICATES THAT THE RECORD IS FREE !C2------FORMAT. SET A FLAG SPECIFYING IF FREE FORMAT OR FIXED FORMAT. @@ -373,7 +373,7 @@ subroutine u2ddbl(a,aname,ii,jj,k,in,iout) kper, kstp, locat, n, ncol, nrow real(DP) :: r, cnstnt, pertim, totim character(len=20) :: fmtin - character(len=200) :: cntrl + character (len=:), allocatable :: cntrl character(len=16) :: text character(len=200) :: fname character(len=20) :: ftype @@ -400,7 +400,7 @@ subroutine u2ddbl(a,aname,ii,jj,k,in,iout) ! ------------------------------------------------------------------ ! !C1------READ ARRAY CONTROL RECORD AS CHARACTER DATA. - call u8rdcom(in,iout,cntrl,ierr) + call u9rdcom(in,iout,cntrl,ierr) ! !C2------LOOK FOR ALPHABETIC WORD THAT INDICATES THAT THE RECORD IS FREE !C2------FORMAT. SET A FLAG SPECIFYING IF FREE FORMAT OR FIXED FORMAT. @@ -569,7 +569,7 @@ subroutine u2dint(ia,aname,ii,jj,k,in,iout) istop, locat, n real(DP) :: r character(len=20) :: fmtin - character(len=200) :: cntrl + character (len=:), allocatable :: cntrl character(len=200) :: fname character(len=20) :: ftype character(len=LINELENGTH) :: ermsg @@ -605,7 +605,7 @@ subroutine u2dint(ia,aname,ii,jj,k,in,iout) ! ------------------------------------------------------------------ ! !C1------READ ARRAY CONTROL RECORD AS CHARACTER DATA. - call u8rdcom(in,iout,cntrl,ierr) + call u9rdcom(in,iout,cntrl,ierr) ! !C2------LOOK FOR ALPHABETIC WORD THAT INDICATES THAT THE RECORD IS FREE !C2------FORMAT. SET A FLAG SPECIFYING IF FREE FORMAT OR FIXED FORMAT. diff --git a/utils/mf5to6/src/Preproc/Utilities.f90 b/utils/mf5to6/src/Preproc/Utilities.f90 index 3ed0673ae3a..3d97b4c0618 100644 --- a/utils/mf5to6/src/Preproc/Utilities.f90 +++ b/utils/mf5to6/src/Preproc/Utilities.f90 @@ -4,7 +4,7 @@ module UtilitiesModule use GlobalVariablesModule, only: optfile, PathToPostObsMf, ScriptType, & verbose, echo use InputOutputModule, only: GetUnit, openfile, UPCASE, URWORD, & - uget_block, uterminate_block, u8rdcom + uget_block, uterminate_block, u9rdcom use SimModule, onlY: store_error, store_note, store_warning, ustop private @@ -895,7 +895,7 @@ subroutine ReadMf5to6Options() if (found) then do icol = 1 - call u8rdcom(iu, 0, line, ierr) + call u9rdcom(iu, 0, line, ierr) call urword(line, icol, istart, istop, 1, idum, rdum, 0, iu) select case (line(istart:istop)) case ('PATHTOPOSTOBSMF') From 2f60b3129a22cbd9d191137b2d44e670846175c6 Mon Sep 17 00:00:00 2001 From: mjr-deltares <45555666+mjr-deltares@users.noreply.github.com> Date: Tue, 13 Jun 2023 08:28:27 +0200 Subject: [PATCH 101/123] - add under-relaxation synchronized over processes for the parallel case (#1217) --- src/Distributed/MpiMessageBuilder.f90 | 3 +- src/Distributed/VirtualDataManager.f90 | 2 +- src/Model/Connection/GridConnection.f90 | 2 +- src/Model/Connection/GwfGwfConnection.f90 | 1 + .../Connection/SpatialModelConnection.f90 | 11 +++--- src/SimulationCreate.f90 | 2 +- src/Solution/NumericalSolution.f90 | 8 ++--- src/Solution/ParallelSolution.f90 | 35 ++++++++++++++++++- src/mf6core.f90 | 2 +- 9 files changed, 49 insertions(+), 17 deletions(-) diff --git a/src/Distributed/MpiMessageBuilder.f90 b/src/Distributed/MpiMessageBuilder.f90 index d5ae8f4a0eb..4c5c0f3c507 100644 --- a/src/Distributed/MpiMessageBuilder.f90 +++ b/src/Distributed/MpiMessageBuilder.f90 @@ -215,7 +215,8 @@ subroutine create_map_snd(this, rank, stage, map_snd_type) call model_idxs%init() call exg_idxs%init() - ! determine which containers to include TODO_MJR: avoid repetition + ! determine which containers to include, + ! currently models + exchanges do i = 1, size(this%vdc_models) vdc => this%vdc_models(i)%ptr if (vdc%is_active .and. vdc%orig_rank == rank) then diff --git a/src/Distributed/VirtualDataManager.f90 b/src/Distributed/VirtualDataManager.f90 index 93f8e4e0dbd..d1bb0688be4 100644 --- a/src/Distributed/VirtualDataManager.f90 +++ b/src/Distributed/VirtualDataManager.f90 @@ -199,7 +199,7 @@ subroutine vds_reduce_halo(this) ! assign reduced maps to virtual data containers do isol = 1, this%nr_solutions virt_sol => this%virtual_solutions(isol) - do ivm = 1, size(virt_sol%models) ! TODO_MJR: other containers, exchanges? + do ivm = 1, size(virt_sol%models) vdc => virt_sol%models(ivm)%ptr if (vdc%is_local) cycle nmap => virt_sol%interface_map%get_node_map(vdc%id) diff --git a/src/Model/Connection/GridConnection.f90 b/src/Model/Connection/GridConnection.f90 index 150cb0d9280..4530aee1df5 100644 --- a/src/Model/Connection/GridConnection.f90 +++ b/src/Model/Connection/GridConnection.f90 @@ -785,7 +785,7 @@ end subroutine fillConnectionDataFromExchanges !! The level indicates the nr of connections away from !! the remote neighbor, the diagonal term holds the negated !! value of their nearest connection. We end with setting - !< a normalized mask: 0 or 1 + !< a normalized mask to the connections object: 0 or 1 subroutine createConnectionMask(this) class(GridConnectionType), intent(inout) :: this !< instance of this grid connection ! local diff --git a/src/Model/Connection/GwfGwfConnection.f90 b/src/Model/Connection/GwfGwfConnection.f90 index d452d3dcfb5..6291d9d2e0b 100644 --- a/src/Model/Connection/GwfGwfConnection.f90 +++ b/src/Model/Connection/GwfGwfConnection.f90 @@ -394,6 +394,7 @@ subroutine validateConnection(this) ! local ! base validation (geometry/spatial) + ! TODO_MJR: uncomment this... !call this%SpatialModelConnectionType%validateConnection() !call this%validateGwfExchange() diff --git a/src/Model/Connection/SpatialModelConnection.f90 b/src/Model/Connection/SpatialModelConnection.f90 index 60c2a72e27b..2581d67f441 100644 --- a/src/Model/Connection/SpatialModelConnection.f90 +++ b/src/Model/Connection/SpatialModelConnection.f90 @@ -458,13 +458,10 @@ subroutine spatialcon_da(this) end subroutine spatialcon_da - !> @brief Set up the grid connection - !! - !! This works in three steps: - !! 1. set the primary connections - !! 2. create the topology of connected models, finding - !! neighbors of neighboring models when required - !! 3. extend the interface grid, using that information + !> @brief Creates the connection structure for the + !! interface grid, starting from primary exchanges, + !! then extending inward and outward, possibly across + !! model boundaries. !< subroutine setupGridConnection(this) class(SpatialModelConnectionType) :: this !< this connection diff --git a/src/SimulationCreate.f90 b/src/SimulationCreate.f90 index 0e7f1d209d8..73efab00db9 100644 --- a/src/SimulationCreate.f90 +++ b/src/SimulationCreate.f90 @@ -28,7 +28,7 @@ module SimulationCreateModule private public :: simulation_cr public :: simulation_da - public :: create_load_mask ! TODO_MJR: this should go somewhere else + public :: create_load_mask contains diff --git a/src/Solution/NumericalSolution.f90 b/src/Solution/NumericalSolution.f90 index 363bad7af2a..83682cfdc4c 100644 --- a/src/Solution/NumericalSolution.f90 +++ b/src/Solution/NumericalSolution.f90 @@ -159,9 +159,8 @@ module NumericalSolutionModule ! 'protected' (this can be overridden) procedure :: sln_has_converged - procedure :: sln_l2norm procedure :: sln_calc_ptc - procedure :: sln_calc_residual + procedure :: sln_underrelax ! private procedure, private :: sln_connect @@ -172,7 +171,8 @@ module NumericalSolutionModule procedure, private :: sln_backtracking_xupdate procedure, private :: sln_maxval procedure, private :: sln_calcdx - procedure, private :: sln_underrelax + procedure, private :: sln_calc_residual + procedure, private :: sln_l2norm procedure, private :: sln_outer_check procedure, private :: sln_get_loc procedure, private :: sln_get_nodeu @@ -2955,7 +2955,7 @@ end subroutine sln_calc_residual !< subroutine sln_underrelax(this, kiter, bigch, neq, active, x, xtemp) ! -- dummy variables - class(NumericalSolutionType), intent(inout) :: this !< NumericalSolutionType instance + class(NumericalSolutionType) :: this !< NumericalSolutionType instance integer(I4B), intent(in) :: kiter !< Picard iteration number real(DP), intent(in) :: bigch !< maximum dependent-variable change integer(I4B), intent(in) :: neq !< number of equations diff --git a/src/Solution/ParallelSolution.f90 b/src/Solution/ParallelSolution.f90 index 6c1c5278fc6..b97bbf45132 100644 --- a/src/Solution/ParallelSolution.f90 +++ b/src/Solution/ParallelSolution.f90 @@ -14,6 +14,7 @@ module ParallelSolutionModule ! override procedure :: sln_has_converged => par_has_converged procedure :: sln_calc_ptc => par_calc_ptc + procedure :: sln_underrelax => par_underrelax end type ParallelSolutionType contains @@ -34,7 +35,6 @@ function par_has_converged(this, max_dvc) result(has_converged) mpi_world => get_mpi_world() has_converged = .false. - global_max_dvc = huge(0.0) abs_max_dvc = abs(max_dvc) call MPI_Allreduce(abs_max_dvc, global_max_dvc, 1, MPI_DOUBLE_PRECISION, & MPI_MAX, mpi_world%comm, ierr) @@ -73,4 +73,37 @@ subroutine par_calc_ptc(this, iptc, ptcf) end subroutine par_calc_ptc + !> @brief apply under-relaxation in sync over all processes + !< + subroutine par_underrelax(this, kiter, bigch, neq, active, x, xtemp) + class(ParallelSolutionType) :: this !< parallel instance + integer(I4B), intent(in) :: kiter !< Picard iteration number + real(DP), intent(in) :: bigch !< maximum dependent-variable change + integer(I4B), intent(in) :: neq !< number of equations + integer(I4B), dimension(neq), intent(in) :: active !< active cell flag (1) + real(DP), dimension(neq), intent(inout) :: x !< current dependent-variable + real(DP), dimension(neq), intent(in) :: xtemp !< previous dependent-variable + ! local + real(DP) :: dvc_global_max, dvc_global_min + integer :: ierr + type(MpiWorldType), pointer :: mpi_world + + mpi_world => get_mpi_world() + + ! first reduce largest change over all processes + call MPI_Allreduce(bigch, dvc_global_max, 1, MPI_DOUBLE_PRECISION, & + MPI_MAX, mpi_world%comm, ierr) + call MPI_Allreduce(bigch, dvc_global_min, 1, MPI_DOUBLE_PRECISION, & + MPI_MIN, mpi_world%comm, ierr) + + if (abs(dvc_global_min) > abs(dvc_global_max)) then + dvc_global_max = dvc_global_min + end if + + ! call local underrelax routine + call this%NumericalSolutionType%sln_underrelax(kiter, dvc_global_max, & + neq, active, x, xtemp) + + end subroutine par_underrelax + end module ParallelSolutionModule diff --git a/src/mf6core.f90 b/src/mf6core.f90 index 63c62b4f9b9..f173596e33f 100644 --- a/src/mf6core.f90 +++ b/src/mf6core.f90 @@ -339,7 +339,7 @@ subroutine simulation_df() ! -- synchronize call run_ctrl%at_stage(STG_AFT_CON_CR) ! - ! -- synchronize TODO_MJR: this could be merged with the above, in general + ! -- synchronize call run_ctrl%at_stage(STG_BFR_CON_DF) ! ! -- Define each connection From cdf6d3583306419f80b9048353b51326c402bba5 Mon Sep 17 00:00:00 2001 From: w-bonelli Date: Wed, 14 Jun 2023 15:14:14 -0400 Subject: [PATCH 102/123] test: add markers/mechanism to skip developmode tests in release mode (#1252) * add developmode marker to programmatically defined tests that use dev options * skip modflow6-testmodels with dev in name if markers include not developmode * add modflow-devtools link to readme CI section --- README.md | 1 + autotest/conftest.py | 8 ++++++-- autotest/test_gwf_ifmod_buy.py | 1 + autotest/test_gwf_ifmod_idomain.py | 1 + autotest/test_gwf_ifmod_mult_exg.py | 1 + autotest/test_gwf_ifmod_newton.py | 1 + autotest/test_gwf_ifmod_rewet.py | 1 + autotest/test_gwf_ifmod_vert.py | 1 + autotest/test_gwf_libmf6_ifmod02.py | 1 + autotest/test_gwfgwf_lgr.py | 1 + autotest/test_gwt_adv01_gwtgwt.py | 1 + autotest/test_gwt_dsp01_gwtgwt.py | 1 + autotest/test_z01_testmodels_mf6.py | 7 +++++-- autotest/test_z03_largetestmodels.py | 7 +++++-- 14 files changed, 27 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 4da440d2e02..b35ab693c22 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,7 @@ This repository contains an `./autotest` folder with python scripts for building * [MODFLOW-USGS/modflow6-testmodels](https://github.com/MODFLOW-USGS/modflow6-testmodels) * [MODFLOW-USGS/modflow6-largetestmodels](https://github.com/MODFLOW-USGS/modflow6-largetestmodels) * [MODFLOW-USGS/executables](https://github.com/MODFLOW-USGS/executables) +* [MODFLOW-USGS/modflow-devtools](https://github.com/MODFLOW-USGS/modflow-devtools) * [Deltares/xmipy](https://github.com/Deltares/xmipy) ## Code Documentation diff --git a/autotest/conftest.py b/autotest/conftest.py index a1a7e954456..5f0073b18c2 100644 --- a/autotest/conftest.py +++ b/autotest/conftest.py @@ -50,8 +50,12 @@ def targets(bin_path) -> Executables: @pytest.fixture def original_regression(request) -> bool: - oreg = request.config.getoption("--original-regression") - return oreg + return request.config.getoption("--original-regression") + + +@pytest.fixture(scope="session") +def markers(pytestconfig) -> str: + return pytestconfig.getoption('-m') def pytest_addoption(parser): diff --git a/autotest/test_gwf_ifmod_buy.py b/autotest/test_gwf_ifmod_buy.py index 79be47060a0..428b025c230 100644 --- a/autotest/test_gwf_ifmod_buy.py +++ b/autotest/test_gwf_ifmod_buy.py @@ -649,6 +649,7 @@ def compare_to_ref(sim): "idx, name", list(enumerate(ex)), ) +@pytest.mark.developmode def test_mf6model(idx, name, function_tmpdir, targets): test = TestFramework() test.build(build_model, idx, str(function_tmpdir)) diff --git a/autotest/test_gwf_ifmod_idomain.py b/autotest/test_gwf_ifmod_idomain.py index 28c6eb3c113..4f89039dd14 100644 --- a/autotest/test_gwf_ifmod_idomain.py +++ b/autotest/test_gwf_ifmod_idomain.py @@ -379,6 +379,7 @@ def compare_to_ref(sim): "idx, name", list(enumerate(ex)), ) +@pytest.mark.developmode def test_mf6model(idx, name, function_tmpdir, targets): test = TestFramework() test.build(build_model, idx, str(function_tmpdir)) diff --git a/autotest/test_gwf_ifmod_mult_exg.py b/autotest/test_gwf_ifmod_mult_exg.py index f56269b32e4..dc179b804db 100644 --- a/autotest/test_gwf_ifmod_mult_exg.py +++ b/autotest/test_gwf_ifmod_mult_exg.py @@ -334,6 +334,7 @@ def exact(x): "name", ex, ) +@pytest.mark.developmode def test_mf6model(name, function_tmpdir, targets): test = TestFramework() test.build(build_model, 0, str(function_tmpdir)) diff --git a/autotest/test_gwf_ifmod_newton.py b/autotest/test_gwf_ifmod_newton.py index 7cead3912d0..abfc8e2a3c8 100644 --- a/autotest/test_gwf_ifmod_newton.py +++ b/autotest/test_gwf_ifmod_newton.py @@ -384,6 +384,7 @@ def compare_to_ref(sim): "idx, name", list(enumerate(ex)), ) +@pytest.mark.developmode def test_mf6model(idx, name, function_tmpdir, targets): test = TestFramework() test.build(build_model, idx, str(function_tmpdir)) diff --git a/autotest/test_gwf_ifmod_rewet.py b/autotest/test_gwf_ifmod_rewet.py index bf099d4f672..feb98489108 100644 --- a/autotest/test_gwf_ifmod_rewet.py +++ b/autotest/test_gwf_ifmod_rewet.py @@ -404,6 +404,7 @@ def compare_to_ref(sim): "idx, name", list(enumerate(ex)), ) +@pytest.mark.developmode def test_mf6model(idx, name, function_tmpdir, targets): test = TestFramework() test.build(build_model, idx, str(function_tmpdir)) diff --git a/autotest/test_gwf_ifmod_vert.py b/autotest/test_gwf_ifmod_vert.py index b70c97e1b5d..5e5240726a7 100644 --- a/autotest/test_gwf_ifmod_vert.py +++ b/autotest/test_gwf_ifmod_vert.py @@ -285,6 +285,7 @@ def eval_heads(sim): "idx, name", list(enumerate(ex)), ) +@pytest.mark.developmode def test_mf6model(idx, name, function_tmpdir, targets): test = TestFramework() test.build(build_model, idx, str(function_tmpdir)) diff --git a/autotest/test_gwf_libmf6_ifmod02.py b/autotest/test_gwf_libmf6_ifmod02.py index 63dec777796..5faa7d76512 100644 --- a/autotest/test_gwf_libmf6_ifmod02.py +++ b/autotest/test_gwf_libmf6_ifmod02.py @@ -407,6 +407,7 @@ def check_interface_models(mf6): "idx, name", list(enumerate(ex)), ) +@pytest.mark.developmode def test_mf6model(idx, name, function_tmpdir, targets): test = TestFramework() test.build(build_model, idx, str(function_tmpdir)) diff --git a/autotest/test_gwfgwf_lgr.py b/autotest/test_gwfgwf_lgr.py index 7404f10805b..2ac991d04e5 100644 --- a/autotest/test_gwfgwf_lgr.py +++ b/autotest/test_gwfgwf_lgr.py @@ -280,6 +280,7 @@ def eval_heads(sim): "idx, name", list(enumerate(ex)), ) +@pytest.mark.developmode def test_mf6model(idx, name, function_tmpdir, targets): ws = str(function_tmpdir) test = TestFramework() diff --git a/autotest/test_gwt_adv01_gwtgwt.py b/autotest/test_gwt_adv01_gwtgwt.py index ed325110506..29624b7054a 100644 --- a/autotest/test_gwt_adv01_gwtgwt.py +++ b/autotest/test_gwt_adv01_gwtgwt.py @@ -730,6 +730,7 @@ def eval_transport(sim): "idx, name", list(enumerate(ex)), ) +@pytest.mark.developmode def test_mf6model(idx, name, function_tmpdir, targets): ws = str(function_tmpdir) test = TestFramework() diff --git a/autotest/test_gwt_dsp01_gwtgwt.py b/autotest/test_gwt_dsp01_gwtgwt.py index 156269c0e05..ab29408fa09 100644 --- a/autotest/test_gwt_dsp01_gwtgwt.py +++ b/autotest/test_gwt_dsp01_gwtgwt.py @@ -302,6 +302,7 @@ def eval_transport(sim): "idx, name", list(enumerate(ex)), ) +@pytest.mark.developmode def test_mf6model(idx, name, function_tmpdir, targets): ws = str(function_tmpdir) test = TestFramework() diff --git a/autotest/test_z01_testmodels_mf6.py b/autotest/test_z01_testmodels_mf6.py index b349598c027..48187b4197d 100644 --- a/autotest/test_z01_testmodels_mf6.py +++ b/autotest/test_z01_testmodels_mf6.py @@ -37,12 +37,15 @@ @pytest.mark.repo @pytest.mark.regression -def test_model(function_tmpdir, test_model_mf6, targets, original_regression): +def test_model(function_tmpdir, test_model_mf6, targets, original_regression, markers): exdir = test_model_mf6.parent name = exdir.name if name in excluded_models: - pytest.skip(f"Excluding mf6 model: {name}") + pytest.skip(f"Excluding mf6 model '{name}'") + + if "dev" in name and "not developmode" in markers: + pytest.skip(f"Skipping mf6 model '{name}' (develop mode only)") sim = TestSimulation( name=name, diff --git a/autotest/test_z03_largetestmodels.py b/autotest/test_z03_largetestmodels.py index a90475cb1a4..c5f9175579a 100644 --- a/autotest/test_z03_largetestmodels.py +++ b/autotest/test_z03_largetestmodels.py @@ -16,13 +16,16 @@ @pytest.mark.regression @pytest.mark.slow def test_model( - function_tmpdir, large_test_model, targets, original_regression + function_tmpdir, large_test_model, targets, original_regression, markers ): exdir = large_test_model.parent name = exdir.name if name in excluded_models: - pytest.skip(f"Excluding large mf6 model: {name}") + pytest.skip(f"Excluding large mf6 model '{name}'") + + if "dev" in name and "not developmode" in markers: + pytest.skip(f"Skipping large mf6 model '{name}' (develop mode only)") sim = TestSimulation( name=name, From 2e3dfe3694bd3574d15c15c76078d4095e35e236 Mon Sep 17 00:00:00 2001 From: w-bonelli Date: Wed, 14 Jun 2023 19:17:13 -0400 Subject: [PATCH 103/123] ci: refactor release workflow (#1246) * introduce dispatch_release.yml to trigger/orchestrate release * refactor release.yml to only build the release distribution * make release.yml reusable with workflow_call trigger * make release options configurable: version, branch, testing, reset, approval, minimal vs. full build * refactor distribution scripts to support an optional version label * update release procedure guide --- .github/workflows/release.yml | 590 ++++++++++--------------- .github/workflows/release_dispatch.yml | 283 ++++++++++++ autotest/test_cli.py | 18 +- distribution/README.md | 48 +- distribution/build_docs.py | 4 +- distribution/check_dist.py | 31 +- distribution/conftest.py | 2 + distribution/update_version.py | 189 ++++---- distribution/utils.py | 8 + 9 files changed, 713 insertions(+), 460 deletions(-) create mode 100644 .github/workflows/release_dispatch.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 731bcf48a32..ae92fd1c9e2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,18 +1,45 @@ name: MODFLOW 6 release on: - push: - branches: - - master - - v[0-9]+.[0-9]+.[0-9]+* - release: - types: - - published + # workflow_call event lets this workflow be called either + # from this repo or the nightly build repo for dev builds + workflow_call: + inputs: + approve: + description: 'Whether to approve the release. Default is false for preliminary/provisional releases. Approval is only relevant for full builds: if development is true, approve is not considered.' + required: false + type: boolean + default: false + branch: + description: 'Branch to use for release. Default is the develop branch.' + required: false + type: string + default: 'develop' + development: + description: 'Toggle a minimal development build. Default is false, i.e. to perform a full release build. If true, approve is not considered.' + required: false + type: boolean + default: false + run_tests: + description: 'Whether to run tests after building binaries. Default is true. Can be set to false for rapid testing/debugging, or for a faster nightly/development build.' + required: false + type: boolean + default: true + version: + description: 'Version number to use for release.' + required: true + type: string + outputs: + version: + description: 'Version number used for release' + value: ${{ jobs.build.outputs.version }} + distname: + description: 'Distribution name used for release' + value: ${{ jobs.build.outputs.distname }} env: FC: ifort jobs: build: name: Build binaries (${{ matrix.os }}) - if: ${{ github.event_name == 'push' && github.ref_name != 'master' }} runs-on: ${{ matrix.os }} strategy: fail-fast: false @@ -27,57 +54,61 @@ jobs: defaults: run: shell: bash -l {0} + outputs: + version: ${{ steps.set_version.outputs.version }} + distname: ${{ steps.set_version.outputs.distname }} steps: - name: Checkout modflow6 uses: actions/checkout@v3 with: + repository: ${{ github.repository_owner }}/modflow6 path: modflow6 - - - name: Cache binaries - if: ${{ contains(github.ref_name, 'rc') }} - id: cache-bin - uses: actions/cache@v3 - with: - key: bin-${{ runner.os }} - path: modflow6/bin + ref: ${{ inputs.branch }} - name: Setup Micromamba - if: ${{ steps.cache-bin.outputs.cache-hit != 'true' }} uses: mamba-org/setup-micromamba@v1 with: + micromamba-root-path: ${{ github.workspace }}/micromamba-root environment-file: modflow6/environment.yml cache-downloads: true cache-environment: true - name: Setup Intel Fortran - if: ${{ steps.cache-bin.outputs.cache-hit != 'true' }} uses: modflowpy/install-intelfortran-action@v1 - name: Fix Micromamba path (Windows) - if: ${{ runner.os == 'Windows' && steps.cache-bin.outputs.cache-hit != 'true' }} + if: runner.os == 'Windows' shell: pwsh run: | # https://github.com/modflowpy/install-intelfortran-action#conda-scripts - $mamba_bin = "C:\Users\runneradmin\micromamba-root\envs\modflow6\Scripts" + $mamba_bin = "${{ github.workspace }}\micromamba-root\envs\modflow6\Scripts" echo $mamba_bin | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + - name: Set version number + id: set_version + run: | + # distribution name format is 'mf' + distname="mf${{ inputs.version }}" + + # set step outputs and environment variables + echo "version=${{ inputs.version }}" >> "$GITHUB_OUTPUT" + echo "VERSION=${{ inputs.version }}" >> "$GITHUB_ENV" + echo "distname=$distname" >> "$GITHUB_OUTPUT" + echo "DISTNAME=$distname" >> "$GITHUB_ENV" + - name: Update version - if: ${{ steps.cache-bin.outputs.cache-hit != 'true' }} + id: update_version working-directory: modflow6/distribution run: | - # extract version from ref name - ref="${{ github.ref_name }}" - ver="${ref%"rc"}" - - # update version files - if [[ "$ver" == *"rc"* ]]; then - python update_version.py -v "${ver#"v"}" + ver="${{ steps.set_version.outputs.version }}" + if [[ ("${{ inputs.development }}" == "false") && ("${{ inputs.approve }}" == "true") ]]; then + python update_version.py -v "$ver" --approve else - python update_version.py -v "${ver#"v"}" --approve + python update_version.py -v "$ver" fi - name: Build binaries - if: ${{ runner.os != 'Windows' && steps.cache-bin.outputs.cache-hit != 'true' }} + if: runner.os != 'Windows' working-directory: modflow6 run: | meson setup builddir -Ddebug=false --prefix=$(pwd) --libdir=bin @@ -85,7 +116,7 @@ jobs: meson test --verbose --no-rebuild -C builddir - name: Build binaries (Windows) - if: ${{ runner.os == 'Windows' && steps.cache-bin.outputs.cache-hit != 'true' }} + if: runner.os == 'Windows' working-directory: modflow6 shell: pwsh run: | @@ -99,56 +130,95 @@ jobs: name: bin-${{ runner.os }} path: modflow6/bin + # only run steps below if inputs.run_tests is true - name: Checkout modflow6-testmodels + if: inputs.run_tests == true uses: actions/checkout@v3 with: repository: MODFLOW-USGS/modflow6-testmodels path: modflow6-testmodels - name: Checkout modflow6-examples + if: inputs.run_tests == true uses: actions/checkout@v3 with: repository: MODFLOW-USGS/modflow6-examples path: modflow6-examples - - name: Cache modflow6 examples - id: cache-examples - uses: actions/cache@v3 - with: - path: modflow6-examples/examples - key: modflow6-examples-${{ hashFiles('modflow6-examples/scripts/**') }} - - - name: Install extra Python packages - if: steps.cache-examples.outputs.cache-hit != 'true' - working-directory: modflow6-examples/etc - run: pip install -r requirements.pip.txt - - - name: Build example models - if: steps.cache-examples.outputs.cache-hit != 'true' - working-directory: modflow6-examples/etc - run: python ci_build_files.py - - name: Update flopy + if: inputs.run_tests == true working-directory: modflow6/autotest run: python update_flopy.py - name: Get executables + if: inputs.run_tests == true working-directory: modflow6/autotest + env: + GITHUB_TOKEN: ${{ github.token }} run: pytest -v --durations 0 get_exes.py - - name: Test programs + - name: Test modflow6 + if: inputs.run_tests == true working-directory: modflow6/autotest - run: pytest -v -n auto -m "not developmode" --durations 0 + env: + REPOS_PATH: ${{ github.workspace }} + run: | + marker="not large" + if [[ "${{ inputs.development }}" == "false" ]]; then + marker="$marker and not developmode" + fi + + pytest -v -n auto --durations 0 -m "$marker" + + # steps below run only on Linux to test distribution procedures, e.g. + # compiling binaries, building documentation + - name: Checkout usgslatex + if: ${{ runner.os == 'Linux' && inputs.run_tests == true }} + uses: actions/checkout@v3 + with: + repository: MODFLOW-USGS/usgslatex + path: usgslatex + + - name: Install TeX Live + if: ${{ runner.os == 'Linux' && inputs.run_tests == true }} + run: | + sudo apt-get update + sudo apt install texlive-science \ + texlive-latex-extra \ + texlive-font-utils \ + texlive-fonts-recommended \ + texlive-fonts-extra + + - name: Install USGS LaTeX style files and Univers font + if: ${{ runner.os == 'Linux' && inputs.run_tests == true }} + working-directory: usgslatex/usgsLaTeX + run: sudo ./install.sh --all-users + + - name: Install dependencies for ex-gwf-twri example model + if: ${{ runner.os == 'Linux' && inputs.run_tests == true }} + working-directory: modflow6-examples/etc + run: | + # install extra Python packages + pip install -r requirements.pip.txt + + # the example model needs executables to be on the path + echo "${{ github.workspace }}/modflow6/bin" >> $GITHUB_PATH + echo "${{ github.workspace }}/modflow6/bin/downloaded" >> $GITHUB_PATH - - name: Test scripts + - name: Build ex-gwf-twri example model + if: ${{ runner.os == 'Linux' && inputs.run_tests == true }} + working-directory: modflow6-examples/scripts + run: python ex-gwf-twri.py + + - name: Test distribution scripts + if: ${{ inputs.run_tests == true }} working-directory: modflow6/distribution - run: pytest -v --durations 0 env: GITHUB_TOKEN: ${{ github.token }} + run: pytest -v --durations 0 docs: name: Build docs - if: ${{ github.event_name == 'push' && github.ref_name != 'master' }} needs: build runs-on: ubuntu-22.04 defaults: @@ -159,7 +229,9 @@ jobs: - name: Checkout modflow6 uses: actions/checkout@v3 with: + repository: ${{ github.repository_owner }}/modflow6 path: modflow6 + ref: ${{ inputs.branch }} - name: Checkout modflow6-examples uses: actions/checkout@v3 @@ -167,6 +239,12 @@ jobs: repository: MODFLOW-USGS/modflow6-examples path: modflow6-examples + - name: Checkout usgslatex + uses: actions/checkout@v3 + with: + repository: MODFLOW-USGS/usgslatex + path: usgslatex + - name: Install TeX Live run: | sudo apt-get update @@ -176,12 +254,6 @@ jobs: texlive-fonts-recommended \ texlive-fonts-extra - - name: Checkout usgslatex - uses: actions/checkout@v3 - with: - repository: MODFLOW-USGS/usgslatex - path: usgslatex - - name: Install USGS LaTeX style files and Univers font working-directory: usgslatex/usgsLaTeX run: sudo ./install.sh --all-users @@ -196,73 +268,70 @@ jobs: - name: Setup Intel Fortran uses: modflowpy/install-intelfortran-action@v1 - - name: Fix Micromamba path (Windows) - if: runner.os == 'Windows' - shell: pwsh - run: | - # https://github.com/modflowpy/install-intelfortran-action#conda-scripts - $mamba_bin = "C:\Users\runneradmin\micromamba-root\envs\modflow6\Scripts" - echo $mamba_bin | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append - - - name: Install extra Python packages - working-directory: modflow6-examples/etc - run: pip install -r requirements.pip.txt - - - name: Build example models - working-directory: modflow6-examples/etc - run: pytest -v -n auto ci_build_files.py - - name: Update version + id: update_version working-directory: modflow6/distribution run: | - # extract version from ref name - ref="${{ github.ref_name }}" - ver="${ref%"rc"}" - - # update version files - if [[ "$ver" == *"rc"* ]]; then - python update_version.py -v "${ver#"v"}" + ver="${{ needs.build.outputs.version }}" + if [[ ("${{ inputs.development }}" == "false") && ("${{ inputs.approve }}" == "true") ]]; then + python update_version.py -v "$ver" --approve else - python update_version.py -v "${ver#"v"}" --approve + python update_version.py -v "$ver" fi - - # set dist name, format is 'mf' for docs (no os tag) - distname="mf${ref#"v"}" - echo "DISTNAME=$distname" >> $GITHUB_ENV + - name: Download pre-built binaries + uses: actions/download-artifact@v3 + with: + name: bin-${{ runner.os }} + path: bin + + - name: Install dependencies for ex-gwf-twri example model + working-directory: modflow6-examples/etc + run: | + # install extra Python packages + pip install -r requirements.pip.txt + + # the example model needs executables to be on the path + echo "${{ github.workspace }}/bin" >> $GITHUB_PATH + + # execute permissions may not have survived artifact upload/download + chmod +x "${{ github.workspace }}/bin/mf6" + chmod +x "${{ github.workspace }}/bin/mf5to6" + chmod +x "${{ github.workspace }}/bin/zbud6" + + # the example model also needs mf2005 + get-modflow "${{ github.workspace }}/bin" --subset mf2005 + + - name: Build ex-gwf-twri example model + working-directory: modflow6-examples/scripts + run: python ex-gwf-twri.py + - name: Create directory structure run: | + distname=${{ needs.build.outputs.distname }} + # Create a skeleton of the distribution's folder structure to include in the docs - mkdir -p "$DISTNAME/doc" - mkdir "$DISTNAME/make" - mkdir "$DISTNAME/msvs" - mkdir "$DISTNAME/srcbmi" - cp modflow6/code.json "$DISTNAME/code.json" - cp modflow6/meson.build "$DISTNAME/meson.build" - cp -r modflow6-examples/examples "$DISTNAME" - cp -r modflow6/src "$DISTNAME" - cp -r modflow6/utils "$DISTNAME" + mkdir -p "$distname/doc" + mkdir "$distname/make" + mkdir "$distname/msvs" + mkdir "$distname/srcbmi" + cp modflow6/code.json "$distname/code.json" + cp modflow6/meson.build "$distname/meson.build" + cp -r modflow6-examples/examples "$distname" + cp -r modflow6/src "$distname" + cp -r modflow6/utils "$distname" # create LaTeX file describing the folder structure cd modflow6/doc/ReleaseNotes - python mk_folder_struct.py -dp "${{ github.workspace }}/$DISTNAME" - - - name: Download artifacts - uses: actions/download-artifact@v3 - with: - name: bin-${{ runner.os }} - path: bin + python mk_folder_struct.py -dp "${{ github.workspace }}/$distname" - name: Build documentation env: + # need a GITHUB_TOKEN to download example doc PDF asset from modflow6-examples repo GITHUB_TOKEN: ${{ github.token }} - run: | - chmod +x bin/mf6 - chmod +x bin/mf5to6 - chmod +x bin/zbud6 - python modflow6/distribution/build_docs.py -b bin -o doc + run: python modflow6/distribution/build_docs.py -b bin -o doc - - name: Upload documentation + - name: Upload documentation artifact uses: actions/upload-artifact@v3 with: name: doc @@ -270,8 +339,9 @@ jobs: dist: name: Build distribution (${{ matrix.os }}) - if: ${{ github.event_name == 'push' && github.ref_name != 'master' }} - needs: docs + needs: + - build + - docs runs-on: ${{ matrix.os }} strategy: fail-fast: false @@ -290,7 +360,9 @@ jobs: - name: Checkout modflow6 uses: actions/checkout@v3 with: + repository: ${{ github.repository_owner }}/modflow6 path: modflow6 + ref: ${{ inputs.branch }} - name: Checkout modflow6-examples uses: actions/checkout@v3 @@ -301,6 +373,7 @@ jobs: - name: Setup Micromamba uses: mamba-org/setup-micromamba@v1 with: + micromamba-root-path: ${{ github.workspace }}/micromamba-root environment-file: modflow6/environment.yml cache-downloads: true cache-environment: true @@ -313,75 +386,65 @@ jobs: shell: pwsh run: | # https://github.com/modflowpy/install-intelfortran-action#conda-scripts - $mamba_bin = "C:\Users\runneradmin\micromamba-root\envs\modflow6\Scripts" + $mamba_bin = "${{ github.workspace }}\micromamba-root\envs\modflow6\Scripts" echo $mamba_bin | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append - - - name: Install extra Python packages - working-directory: modflow6-examples/etc - run: pip install -r requirements.pip.txt - - - name: Build example models - working-directory: modflow6-examples/etc - run: pytest -v -n auto ci_build_files.py - + - name: Update version + id: update_version working-directory: modflow6/distribution run: | - # extract version from ref name - ref="${{ github.ref_name }}" - ver="${ref%"rc"}" - - # update version files - if [[ "$ver" == *"rc"* ]]; then - python update_version.py -v "${ver#"v"}" - else - python update_version.py -v "${ver#"v"}" --approve - fi - - # set dist name, format is 'mf_' - if [ "${{ runner.os }}" == "Windows" ]; then - distname="mf${ref#"v"}" + ver="${{ needs.build.outputs.version }}" + if [[ ("${{ inputs.development }}" == "false") && ("${{ inputs.approve }}" == "true") ]]; then + python update_version.py -v "$ver" --approve else - distname="mf${ref#"v"}_${{ matrix.ostag }}" + python update_version.py -v "$ver" fi - echo "DISTNAME=$distname" >> $GITHUB_ENV - name: Download artifacts uses: actions/download-artifact@v3 with: - path: ${{ env.DISTNAME }} + # download artifacts to OS-specific directory + path: ${{ needs.build.outputs.distname }}_${{ matrix.ostag }} - name: Select artifacts for OS run: | - # move binaries for current OS to top level bin dir - mv "$DISTNAME/bin-${{ runner.os }}" "$DISTNAME/bin" - rm -rf "$DISTNAME/bin-*" + # create directory for OS-specific artifacts + distname="${{ needs.build.outputs.distname }}" + mkdir -p "$distname_${{ matrix.ostag }}/bin" + + # move binaries for current OS to distribution bin dir + mv "$distname/bin-${{ runner.os }}" "$distname_${{ matrix.ostag }}/bin" + + # remove binaries for other OSes + rm -rf "$distname/bin-*" - name: Build distribution env: GITHUB_TOKEN: ${{ github.token }} run: | - # build dist folder - python modflow6/distribution/build_dist.py -o "$DISTNAME" -e modflow6-examples + # build distribution + distname="${{ needs.build.outputs.distname }}_${{ matrix.ostag }}" + python modflow6/distribution/build_dist.py -o "$distname" -e modflow6-examples - # rename PDF docs - mv "$DISTNAME/doc/ReleaseNotes.pdf" "$DISTNAME/doc/release.pdf" - mv "$DISTNAME/doc/converter_mf5to6.pdf" "$DISTNAME/doc/mf5to6.pdf" + # move & rename PDF documents to dist folder + mv "$distname/doc/ReleaseNotes.pdf" "$distname/doc/release.pdf" + mv "$distname/doc/converter_mf5to6.pdf" "$distname/doc/mf5to6.pdf" - name: Zip distribution if: runner.os != 'Windows' run: | - zip -r ${{ env.DISTNAME }}.zip \ - ${{ env.DISTNAME }}/bin \ - ${{ env.DISTNAME }}/src \ - ${{ env.DISTNAME }}/srcbmi \ - ${{ env.DISTNAME }}/doc \ - ${{ env.DISTNAME }}/examples \ - ${{ env.DISTNAME }}/make \ - ${{ env.DISTNAME }}/msvs \ - ${{ env.DISTNAME }}/utils \ - ${{ env.DISTNAME }}/code.json \ - ${{ env.DISTNAME }}/meson.build \ + distname="${{ needs.build.outputs.distname }}_${{ matrix.ostag }}" + zip -r $distname.zip \ + $distname/bin \ + $distname/src \ + $distname/srcbmi \ + $distname/doc \ + $distname/examples \ + $distname/make \ + $distname/msvs \ + $distname/utils \ + $distname/code.json \ + $distname/meson.build \ -x '*.DS_Store' \ -x '*libmf6.lib' \ -x '*idmloader*' \ @@ -392,205 +455,42 @@ jobs: - name: Zip distribution (Windows) if: runner.os == 'Windows' run: | - 7z a -tzip ${{ env.DISTNAME }}.zip \ - ${{ env.DISTNAME }}/bin \ - ${{ env.DISTNAME }}/src \ - ${{ env.DISTNAME }}/srcbmi \ - ${{ env.DISTNAME }}/doc \ - ${{ env.DISTNAME }}/examples \ - ${{ env.DISTNAME }}/make \ - ${{ env.DISTNAME }}/msvs \ - ${{ env.DISTNAME }}/utils \ - ${{ env.DISTNAME }}/code.json \ - ${{ env.DISTNAME }}/meson.build \ + distname="${{ needs.build.outputs.distname }}_${{ matrix.ostag }}" + 7z a -tzip $distname.zip \ + $distname/bin \ + $distname/src \ + $distname/srcbmi \ + $distname/doc \ + $distname/examples \ + $distname/make \ + $distname/msvs \ + $distname/utils \ + $distname/code.json \ + $distname/meson.build \ -xr!libmf6.lib \ -xr!idmloader \ -xr!pymake \ -xr!obj_temp \ -xr!mod_temp - # validate after zipping to avoid accidentally changing the distribution files - - name: Check distribution - run: pytest -v -s modflow6/distribution/check_dist.py -P ${{ env.DISTNAME }} + # validate only after zipping distribution to avoid accidentally changing any files + - name: Validate distribution + run: | + if [[ "${{ inputs.approve }}" == "true" ]]; then + pytest -v -s modflow6/distribution/check_dist.py --path "${{ needs.build.outputs.distname }}_${{ matrix.ostag }}" --approved + else + pytest -v -s modflow6/distribution/check_dist.py --path "${{ needs.build.outputs.distname }}_${{ matrix.ostag }}" + fi - - name: Upload distribution + - name: Upload distribution zipfile artifact uses: actions/upload-artifact@v3 with: - name: ${{ env.DISTNAME }} - path: ${{ env.DISTNAME }}.zip + name: "${{ needs.build.outputs.distname }}_${{ matrix.ostag }}" + path: "${{ needs.build.outputs.distname }}_${{ matrix.ostag }}.zip" - - name: Upload release notes + - name: Upload release notes artifact if: runner.os == 'Linux' uses: actions/upload-artifact@v3 with: name: release_notes - path: ${{ env.DISTNAME }}/doc/release.pdf - - pr: - name: Draft release PR - # only runs if branch name doesn't end with 'rc' (i.e. release is approved) - if: ${{ github.event_name == 'push' && github.ref_name != 'master' && !(contains(github.ref_name, 'rc')) }} - needs: dist - runs-on: ubuntu-22.04 - permissions: - contents: write - pull-requests: write - defaults: - run: - shell: bash -l {0} - steps: - - name: Checkout modflow6 - uses: actions/checkout@v3 - - - name: Setup Micromamba - uses: mamba-org/setup-micromamba@v1 - with: - cache-downloads: true - cache-environment: true - - - name: Update version - working-directory: distribution - run: | - # extract version from branch name - ref="${{ github.ref_name }}" - ver="${ref#"v"}" - - # update version files - if [[ "$ver" == *"rc"* ]]; then - python update_version.py -v "$ver" - else - python update_version.py -v "$ver" --approve - fi - - # lint version.f90 - fprettify -c ../.fprettify.yaml ../src/Utilities/version.f90 - - # commit and push - git config core.sharedRepository true - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git add -A - git commit -m "ci(release): update version to $ver release mode" - git push origin "$ref" - - - name: Create pull request - env: - GITHUB_TOKEN: ${{ github.token }} - run: | - ref="${{ github.ref_name }}" - ver="${ref#"v"}" - body=' - # MODFLOW '$ver' release - - The release can be approved by merging this PR into `master`. Merging rather than squashing is necessary to preserve the commit history. - - When this PR is merged, a final job will be triggered to: - 1) create and tag a draft GitHub release, then upload assets (OS distributions and release notes) - 2) open a PR to update `develop` from `master`, resetting version files and setting `IDEVELOPMODE=1` - ' - gh pr create -B "master" -H "$ref" --title "Release $ver" --draft --body "$body" - - release: - name: Draft release - # runs only after release PR is merged to master - if: ${{ github.event_name == 'push' && github.ref_name == 'master' }} - runs-on: ubuntu-22.04 - defaults: - run: - shell: bash -l {0} - steps: - - name: Checkout modflow6 - uses: actions/checkout@v3 - with: - path: modflow6 - - - name: Setup Micromamba - uses: mamba-org/setup-micromamba@v1 - with: - environment-file: modflow6/environment.yml - cache-downloads: true - cache-environment: true - - - name: Download artifacts - uses: dawidd6/action-download-artifact@v2 - - - name: Draft release - working-directory: modflow6 - env: - GITHUB_TOKEN: ${{ github.token }} - run: | - # detect release version - version=$(python distribution/update_version.py --get) - - # create draft release - title="MODFLOW $version" - notes=' - This is the approved USGS MODFLOW '$version' release. - - *Insert citation here* - - Visit the USGS "MODFLOW and Related Programs" site for information on MODFLOW 6 and related software: https://doi.org/10.5066/F76Q1VQV - ' - gh release create "$version" ../mf*/mf*.zip ../release_notes/release.pdf --target master --title "$title" --notes "$notes" --draft --latest - - reset: - name: Draft reset PR - # runs only after release is published (manually promoted from draft to public) - if: ${{ github.event_name == 'release' }} - runs-on: ubuntu-22.04 - defaults: - run: - shell: bash -l {0} - steps: - - - name: Checkout modflow6 - uses: actions/checkout@v3 - with: - path: modflow6 - - - name: Setup Micromamba - uses: mamba-org/setup-micromamba@v1 - with: - environment-file: modflow6/environment.yml - cache-downloads: true - cache-environment: true - - - name: Get release tag - uses: oprypin/find-latest-tag@v1 - id: latest_tag - with: - repository: ${{ github.repository }} - releases-only: true - - - name: Create pull request - working-directory: modflow6 - env: - GITHUB_TOKEN: ${{ github.token }} - run: | - # create reset branch from master - reset_branch="post-release-${{ steps.latest_tag.outputs.tag }}-reset" - git fetch origin - git checkout master - git switch -c $reset_branch - - # increment minor version and reset IDEVELOPMODE to 1 - major_version=$(echo "${{ steps.latest_tag.outputs.tag }}" | cut -d. -f1) - minor_version=$(echo "${{ steps.latest_tag.outputs.tag }}" | cut -d. -f2) - version="$major_version.$((minor_version + 1)).0" - python distribution/update_version.py -v "$version" - - # commit and push reset branch - git config core.sharedRepository true - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git add -A - git commit -m "ci(release): update version to $version development mode" - git push -u origin $reset_branch - - # create PR into develop - body=' - # Reinitialize for development - - Updates the `develop` branch from `master` following a successful release. Increments the minor version number and resets `IDEVELOPMODE` back to `1`. - ' - gh pr create -B "develop" -H "$reset_branch" --title "Reinitialize develop branch" --draft --body "$body" \ No newline at end of file + path: "${{ needs.build.outputs.distname }}_${{ matrix.ostag }}/doc/release.pdf" diff --git a/.github/workflows/release_dispatch.yml b/.github/workflows/release_dispatch.yml new file mode 100644 index 00000000000..6dff66b60d6 --- /dev/null +++ b/.github/workflows/release_dispatch.yml @@ -0,0 +1,283 @@ +name: MODFLOW 6 release dispatch +on: + push: + branches: + # initial phase of the release procedure is triggered by pushing a release branch + - v[0-9]+.[0-9]+.[0-9]+* + # intermediate phase is triggered by merging the release branch to master + - master + # final phase is triggered by promoting/publishing a GitHub release draft to a full release + release: + types: + - published + # workflow_dispatch trigger to start release via GitHub UI or CLI, + # as an alternative to triggering when a release branch is pushed. + # see https://docs.github.com/en/actions/managing-workflow-runs/manually-running-a-workflow + workflow_dispatch: + inputs: + approve: + description: 'Whether to approve the release. Default is false, i.e. a preliminary/provisional release.' + required: false + type: boolean + default: false + branch: + description: 'Branch to use for release. Defaults to the current branch (for releases triggered by pushing a release branch).' + required: false + type: string + default: '' + commit_version: + description: 'Whether to commit version numbers back to the develop branch. Default is false. Not considered if approve is false.' + required: false + type: boolean + default: false + reset: + description: 'Whether to reset the develop branch to the state of the master branch. Default is false. Not considered if approve is false.' + required: false + type: boolean + default: false + run_tests: + description: 'Whether to run tests after building binaries. Default is true.' + required: true + type: boolean + default: true + version: + description: 'Version number to use for release.' + required: false + type: string + default: '' +jobs: + set_options: + name: Set release options + runs-on: ubuntu-22.04 + defaults: + run: + shell: bash -l {0} + outputs: + branch: ${{ steps.set_branch.outputs.branch }} + version: ${{ steps.set_version.outputs.version }} + steps: + - name: Set branch + id: set_branch + run: | + # if branch was provided explicitly via workflow_dispatch, use it + if [[ ("${{ github.event_name }}" == "workflow_dispatch") && (-n "${{ inputs.branch }}") ]]; then + branch="${{ inputs.branch }}" + # prevent releases from develop or master + if [[ ("$branch" == "develop") || ("$branch" == "master") ]]; then + echo "error: releases may not be triggered from branch $branch" + exit 1 + fi + echo "using branch $branch from workflow_dispatch" + elif [[ ("${{ github.event_name }}" == "push") && ("${{ github.ref_name }}" != "master") ]]; then + # if release was triggered by pushing a release branch, use that branch + branch="${{ github.ref_name }}" + echo "using branch $branch from ref ${{ github.ref }}" + else + # otherwise exit with an error + echo "error: this workflow should not have triggered for event ${{ github.event_name }} on branch ${{ github.ref_name }}" + exit 1 + fi + echo "branch=$branch" >> $GITHUB_OUTPUT + - name: Set version + id: set_version + run: | + # if version number was provided explicitly via workflow_dispatch, use it + if [[ ("${{ github.event_name }}" == "workflow_dispatch") && (-n "${{ inputs.version }}") ]]; then + ver="${{ inputs.version }}" + echo "using version number $ver from workflow_dispatch" + elif [[ ("${{ github.event_name }}" == "push") && ("${{ github.ref_name }}" != "master") ]]; then + # if release was triggered by pushing a release branch, parse version number from branch name (sans leading 'v') + ref="${{ github.ref_name }}" + ver="${ref#"v"}" + echo "parsed version number $ver from branch name $ref" + else + # otherwise exit with an error + echo "error: version number not provided explicitly (via workflow_dispatch input) or implicitly (via branch name)" + exit 1 + fi + echo "version=$ver" >> $GITHUB_OUTPUT + make_dist: + name: Make distribution + needs: set_options + uses: MODFLOW-USGS/modflow6/.github/workflows/release.yml@develop + with: + # If the workflow is manually triggered, the maintainer must manually set approve=true to approve a release. + # If triggered by pushing a release branch, the release is approved if the branch name doesn't contain "rc". + approve: ${{ (github.event_name == 'workflow_dispatch' && inputs.approve == 'true') || (github.event_name != 'workflow_dispatch' && !contains(github.ref_name, 'rc')) }} + branch: ${{ needs.set_options.outputs.branch }} + development: false + run_tests: ${{ inputs.run_tests == '' || inputs.run_tests == 'true' }} + version: ${{ needs.set_options.outputs.version }} + pr: + name: Draft release PR + if: ${{ github.event_name == 'push' && github.ref_name != 'master' && (github.event_name == 'workflow_dispatch' && inputs.approve == 'true') || (github.event_name != 'workflow_dispatch' && !contains(github.ref_name, 'rc')) }} + needs: make_dist + runs-on: ubuntu-22.04 + permissions: + contents: write + pull-requests: write + defaults: + run: + shell: bash -l {0} + steps: + - name: Checkout modflow6 + uses: actions/checkout@v3 + with: + repository: ${{ github.repository_owner }}/modflow6 + ref: ${{ github.ref }} + + - name: Setup Micromamba + uses: mamba-org/setup-micromamba@v1 + with: + environment-file: environment.yml + cache-downloads: true + cache-environment: true + + - name: Update version + working-directory: distribution + run: | + # update version files + ver="${{ needs.make_dist.outputs.version }}" + if [[ "${{ inputs.approve }}" == "true" ]]; then + python update_version.py -v "$ver" --approve + else + python update_version.py -v "$ver" + fi + + # lint version.f90 + fprettify -c ../.fprettify.yaml ../src/Utilities/version.f90 + + # commit and push + git config core.sharedRepository true + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add -A + git commit -m "ci(release): update version to $ver" + git push origin "${{ github.ref }}" + + - name: Create pull request + env: + GITHUB_TOKEN: ${{ github.token }} + run: | + ver="${{ needs.make_dist.outputs.version }}" + body=' + # MODFLOW '$ver' release + + The release can be approved by merging this PR into `master`. Merging rather than squashing is necessary to preserve the commit history. + + When this PR is merged, a final job will be triggered to: + 1) create and tag a draft GitHub release, then upload assets (OS distributions and release notes) + 2) open a PR to update `develop` from `master`, resetting version files and setting `IDEVELOPMODE=1` + ' + gh pr create -B "master" -H "${{ github.ref }}" --title "Release $ver" --draft --body "$body" + + release: + name: Draft release + # runs only after release PR is merged to master + if: github.event_name == 'push' && github.ref_name == 'master' + runs-on: ubuntu-22.04 + defaults: + run: + shell: bash -l {0} + steps: + - name: Checkout modflow6 + uses: actions/checkout@v3 + with: + repository: ${{ github.repository_owner }}/modflow6 + path: modflow6 + + - name: Setup Micromamba + uses: mamba-org/setup-micromamba@v1 + with: + environment-file: modflow6/environment.yml + cache-downloads: true + cache-environment: true + + - name: Download artifacts + uses: dawidd6/action-download-artifact@v2 + + - name: Draft release + working-directory: modflow6 + env: + GITHUB_TOKEN: ${{ github.token }} + run: | + # create draft release + ver=$(python distribution/update_version.py -g) + title="MODFLOW $ver" + notes=' + This is the approved USGS MODFLOW '$ver' release. + + *Insert citation here* + + Visit the USGS "MODFLOW and Related Programs" site for information on MODFLOW 6 and related software: https://doi.org/10.5066/F76Q1VQV + ' + gh release create "$ver" ../mf*/mf*.zip ../release_notes/release.pdf --target master --title "$title" --notes "$notes" --draft --latest + + reset: + name: Draft reset PR + # runs only after GitHub release post is published (manually promoted from draft to public) + # to bring the release commits from master back into develop + if: github.event_name == 'release' && inputs.reset == 'true' + runs-on: ubuntu-22.04 + defaults: + run: + shell: bash -l {0} + steps: + + - name: Checkout modflow6 + uses: actions/checkout@v3 + with: + repository: ${{ github.repository_owner }}/modflow6 + path: modflow6 + + - name: Setup Micromamba + uses: mamba-org/setup-micromamba@v1 + with: + environment-file: modflow6/environment.yml + cache-downloads: true + cache-environment: true + + - name: Get release tag + uses: oprypin/find-latest-tag@v1 + id: latest_tag + with: + repository: ${{ github.repository }} + releases-only: true + + - name: Create pull request + working-directory: modflow6 + env: + GITHUB_TOKEN: ${{ github.token }} + run: | + # create reset branch from master + reset_branch="post-release-${{ steps.latest_tag.outputs.tag }}-reset" + git fetch origin + git checkout master + git switch -c $reset_branch + + # configure git + git config core.sharedRepository true + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + + # updating version numbers if enabled + if [[ "${{ inputs.commit_version }}" == "true" ]]; then + ver="${{ steps.latest_tag.outputs.tag }}" + python distribution/update_version.py -v "$ver" + git add -A + git commit -m "ci(release): update version to $ver, reset IDEVELOPMODE to 1" + else + # in either case we at least need to reset IDEVELOPMODE to 1 + python distribution/update_version.py + git add -A + git commit -m "ci(release): reset IDEVELOPMODE to 1" + fi + + # push reset branch + git push -u origin $reset_branch + + # create PR into develop + body=' + Reinitialize the `develop` branch following a successful release. + ' + gh pr create -B "develop" -H "$reset_branch" --title "Reinitialize develop branch" --draft --body "$body \ No newline at end of file diff --git a/autotest/test_cli.py b/autotest/test_cli.py index 366963df3f3..aa9185ceb46 100644 --- a/autotest/test_cli.py +++ b/autotest/test_cli.py @@ -1,3 +1,4 @@ +import re import subprocess from conftest import project_root_path @@ -5,19 +6,24 @@ bin_path = project_root_path / "bin" +def split_nonnumeric(s): + match = re.compile("[^0-9]").search(s) + return [s[:match.start()], s[match.start():]] if match else s + + def test_cli_version(): output = " ".join( subprocess.check_output([str(bin_path / "mf6"), "-v"]).decode().split() ) - assert output.startswith("mf6:") - assert output.lower().count("release") == 1 - # assert output.lower().count("candidate") <= 1 - print(output) + assert output.startswith("mf6:") version = ( - output.lower().rpartition(":")[2].rpartition("release")[0].strip() + output.lower().split(' ')[1] ) + print(version) v_split = version.split(".") assert len(v_split) == 3 - assert all(s.isdigit() for s in v_split) + assert all(s.isdigit() for s in v_split[:2]) + sol = split_nonnumeric(v_split[2]) + assert sol[0].isdigit() diff --git a/distribution/README.md b/distribution/README.md index dc497d5d22e..7ab0eeb8b92 100644 --- a/distribution/README.md +++ b/distribution/README.md @@ -86,14 +86,14 @@ Full distributions, on the other hand, contain the items listed above, as well a - docs for `mf5to6` and `zbud6` -### Preparing a nightly release +### Preparing a minimal development release Development releases are built and [posted nightly on the `MODFLOW-USGS/modflow6-nightly-build` repository](https://github.com/MODFLOW-USGS/modflow6-nightly-build/releases). Release assets include: - platform-specific distributions containing only executables `mf6`, `zbud6`, `mf5to6` and library `libmf6` - MODFLOW 6 input/output documentation -The `build_dist.py` script is used to create both development and full distributions. To create a development distribution zipfile in the default location (`/distribution/`), run the script with the `--development` (short `-d`) flag: +The `build_dist.py` script can be used to create both development and full distributions. To create a development distribution, run the script with the `--development` (short `-d`) flag: ```shell python build_dist.py -d @@ -115,7 +115,7 @@ To prepare an official release for distribution, the steps are as follows: Version information is stored primarily in `version.txt` in the project root, as well as in several other files in the repository. -The `update_version.py` script updates files containing version information. First a file lock is acquired, then `version.txt` is updated, and changes are propagated to other files in the repository, then the lock is released. +The `update_version.py` script updates files containing version information. First a file lock is acquired, then repository files are updated, then the lock is released. The version can be specified with the `--version` (short `-v`) option. For instance, to set version to `6.4.1`, run from the `scripts/` folder: @@ -125,6 +125,16 @@ python update_version.py -v 6.4.1 If no `--version` is provided, the version is not changed, only the build timestamp. +An optional label may be appended to the release number, e.g. + +```shell +python update_version.py -v 6.4.2rc +``` + +The label must start immediately following the patch version number, with no space in between. The label may contain numeric characters or symbols, but *must not* start with a number (otherwise there is no way to distinguish it from the patch version number). + +The `--approve` (short `-a`) option can be used to approve an official release. If the `--approve` option is provided, `IDEVELOPMODE` is set to 0. If `--approve` is not provided, `IDEVELOPMODE = 1` and `(preliminary)` is appended to version numbers. + #### Building makefiles The `build_makefiles.py` script is used to rewrite makefiles after Fortran source files have been added, removed, or renamed. Up-to-date makefiles must be generated for inclusion in a distribution. To build makefiles, run: @@ -160,12 +170,16 @@ The above will write results to a markdown file `.benchmarks/run-time-comparison Extensive documentation is bundled with official MODFLOW 6 releases. MODFLOW 6 documentation is written in LaTeX. Some LaTeX files (in particular for MODFLOW 6 input/output documentation) is automatically generated from DFN files. The `release.yml` workflow first runs `update_version.py` to update version strings to be substituted into the docs, then runs `build_docs.py` to regenerate LaTeX files where necessary, download benchmark results (and convert the Markdown results file to LaTeX), download publications hosted on the USGS website, and finally convert LaTeX to PDFs. -Manually building MODFLOW 6 documentation requires additional Python dependencies specified in `build_rtd_docs/requirements.rtd.txt`. Styles defined in the [`MODFLOW-USGS/usgslatex`](https://github.com/MODFLOW-USGS/usgslatex) are also required. (See that repository's `README` for installation instructions or this repo's [`../.github/workflows/docs.yml](../.github/workflows/docs.yml) CI workflow for an example.) A Docker image with documentation dependencies pre-installed is also available on Docker Hub: [`wbonelli/usgslatex`](https://hub.docker.com/r/wbonelli/usgslatex). This can be useful for building documentation on a system without a LaTeX installation. +Manually building MODFLOW 6 documentation requires additional Python dependencies specified in `build_rtd_docs/requirements.rtd.txt`. Styles defined in the [`MODFLOW-USGS/usgslatex`](https://github.com/MODFLOW-USGS/usgslatex) are also required. (See that repository's `README` for installation instructions or this repo's [`../.github/workflows/docs.yml](../.github/workflows/docs.yml) CI workflow for an example.) #### Building the distribution archive After each step above is complete, the `build_dist.py` script can be used (without the `--development` flag) to bundle MODFLOW 6 official release artifacts for distribution. See [the `release.yml` workflow](../.github/workflows/release.yml) for a complete example of how to build a distribution archive. +#### Verifying the distribution archive + +The `check_dist.py` script can be used to check the release distribution folder. The `--path` argument is the path to the dist folder. The `--approved` flag can be used to signal that the release is approved/official. By default the release is assumed preliminary. The script checks the version string emitted by `mf6 -v` for the presence or absence of "preliminary" depending on this flag. + ## Release automation Both nightly builds and official distributions are built automatically with GitHub Actions. @@ -176,13 +190,31 @@ As mentioned, development releases are automatically built and posted nightly on ### Official releases -The procedure above to prepare an official release is reproduced in `.github/workflows/release.yml`. This workflow is configured to run whenever tags are pushed to the `MODFLOW-USGS/modflow6` repository. +The procedure above to prepare an official release is reproduced in `.github/workflows/release.yml`. This workflow has no triggers of its own, and must be dispatched by `.github/workflows/release_dispatch.yml`. A release can be dispatched in two ways: + +- Push a release branch to the `MODFLOW-USGS/modflow6` repository. +- Manually trigger the workflow via GitHub CLI or web UI. + +#### Triggering with a release branch To release a new version of MODFLOW 6: -1. Create a release candidate branch from the tip of `develop`. The branch's name must begin with `v` followed by the version number, and ending with `rc`. The branch name must must end with `rc` for release candidate branches, e.g. `v6.4.0rc`. -2. Push the branch to the `MODFLOW-USGS/modflow6` repository. This will trigger a dry run build, in which the distribution archive is constructed with development mode binaries (`IDEVELOPMODE` set to 1) and no release is posted to the repository. Release are cached for easier debugging on dry runs. -3. If the release candidate build passes inspection, rename the branch (or create another) without the trailing `rc`, and push it. This will trigger another build, in which the distribution archive is constructed with production mode binaries. After binaries and docs are rebuilt, a draft PR will automatically be created on the `MODFLOW-USGS/modflow6` repository's `master` branch. To approve and finalize the release, This PR can be merged into `master`. This will trigger a final CI job to tag the revision to `master`, post a draft release to the `MODFLOW-USGS/modflow6` repository, and create another PR updating the `develop` branch from `master`, resetting version files, and setting `IDEVELOPMODE` back to 1. +1. Create a release candidate branch from the tip of `develop` or `master`. The branch's name must begin with `v` followed by the version number. For an officially approved release, include *only* the version number. For a preliminary release candidate, append `rc` after the version number, e.g. `v6.4.0rc`. If the branch name does not end in `rc`, the release is assumed to be approved. +2. Push the branch to the `MODFLOW-USGS/modflow6` repository. This triggers the release workflow. If the release is still an unapproved candidate (i.e. the branch name ends with `rc`) binaries are built with `IDEVELOPMODE` set to 1, and the workflow ends after uploading binaries and documentation artifacts for inspection. If the release is approved/official, the workflow drafts a pull request against the `master` branch. +3. To continue with the release, merge the PR into `master`. This triggers another job to tag the new tip of `master` with the release number, draft a release post, upload binaries and documentation as release assets, and create another PR updating the `develop` branch from `master`, resetting version files, and setting `IDEVELOPMODE` back to 1. +4. To finalize the release, publish the release post and merge the PR into `develop`. + +#### Triggering a release manually + +The `workflow_dispatch` event is GitHub's mechanism for manually triggering workflows. This can be accomplished from the Actions tab in the GitHub UI. + +Navigate to the Actions tab of this repository. Select the release dispatch workflow. A `Run workflow` button should be visible in an alert at the top of the list of workflow runs. Click the `Run workflow` button, selecting values for the various inputs: + +- `approve`: whether the release is officially approved, or just a release candidate +- `branch`: the branch to release from +- `development`: whether to build a minimal development distribution or a full distribution +- `run_tests`: whether to run autotests after building binaries +- `version`: the version number of the release ### Release versioning diff --git a/distribution/build_docs.py b/distribution/build_docs.py index f57d56c9ac0..6161a79837b 100644 --- a/distribution/build_docs.py +++ b/distribution/build_docs.py @@ -100,8 +100,8 @@ def clean_tex_files(): def download_benchmarks(output_path: PathLike, verbose: bool = False) -> Optional[Path]: output_path = Path(output_path).expanduser().absolute() - name = "run-time-comparison" - repo = "w-bonelli/modflow6" + name = "run-time-comparison" # todo make configurable + repo = "MODFLOW-USGS/modflow6" # todo make configurable, add pytest/cli args artifacts = list_artifacts(repo, name=name, verbose=verbose) artifacts = sorted(artifacts, key=lambda a: datetime.strptime(a['created_at'], '%Y-%m-%dT%H:%M:%SZ'), reverse=True) most_recent = next(iter(artifacts), None) diff --git a/distribution/check_dist.py b/distribution/check_dist.py index 2af3083103c..c2dd3a43798 100644 --- a/distribution/check_dist.py +++ b/distribution/check_dist.py @@ -1,11 +1,12 @@ import platform +import re import subprocess from os import environ from pathlib import Path import pytest -from utils import get_branch +from utils import get_branch, split_nonnumeric _system = platform.system() _eext = ".exe" if _system == "Windows" else "" @@ -14,6 +15,11 @@ _fc = environ.get("FC", None) +@pytest.fixture +def approved(request): + return request.config.getoption("--approved") + + @pytest.fixture def dist_dir_path(request): def skip(): @@ -92,14 +98,18 @@ def test_docs(dist_dir_path): def test_examples(dist_dir_path): + # make sure examples dir exists examples_path = dist_dir_path / "examples" + assert examples_path.is_dir() + + # test run an example model with the provided script example_path = next(examples_path.iterdir(), None) assert example_path output = ' '.join(subprocess.check_output([str(example_path / f"run{_scext}")], cwd=example_path).decode().split()) print(output) -def test_binaries(dist_dir_path): +def test_binaries(dist_dir_path, approved): bin_path = dist_dir_path / "bin" assert (bin_path / f"mf6{_eext}").is_file() assert (bin_path / f"zbud6{_eext}").is_file() @@ -108,17 +118,20 @@ def test_binaries(dist_dir_path): output = ' '.join(subprocess.check_output([str(bin_path / f"mf6{_eext}"), "-v"]).decode().split()).lower() assert output.startswith("mf6") - assert output.count("release") == 1 # make sure binaries were built in correct mode - branch = get_branch() - if "rc" in branch or "candidate" in branch: - assert output.count("candidate") == 1, "Binaries were not built in development mode" + if approved: + assert "preliminary" not in output, "Binaries were not built in release mode" else: - assert "candidate" not in output, "Binaries were not built in release mode" + assert "preliminary" in output, "Binaries were not built in development mode" # check version string - version = output.lower().rpartition(":")[2].rpartition("release")[0].strip() + version = ( + output.lower().split(' ')[1] + ) + print(version) v_split = version.split(".") assert len(v_split) == 3 - assert all(s.isdigit() for s in v_split) + assert all(s.isdigit() for s in v_split[:2]) + sol = split_nonnumeric(v_split[2]) + assert sol[0].isdigit() diff --git a/distribution/conftest.py b/distribution/conftest.py index 5cfa5120e10..f493d33c7a9 100644 --- a/distribution/conftest.py +++ b/distribution/conftest.py @@ -7,4 +7,6 @@ def pytest_addoption(parser): + # both options below are for check_dist.py parser.addoption("-P", "--path", action="store", default=str(_dist_dir_path)) + parser.addoption("-A", "--approved", action="store_true", default=False) diff --git a/distribution/update_version.py b/distribution/update_version.py index 2b278a09d6c..1acb393a625 100755 --- a/distribution/update_version.py +++ b/distribution/update_version.py @@ -13,28 +13,26 @@ ../code.json ../src/Utilities/version.f90 -Information in these files include version number (major.minor.patch), build date, whether or not -the release is a release candidate or an actual release, whether the source code should be compiled -in develop mode or in release mode, and the approval status. +Information in these files include version number major.minor.patch[-label], build timestamp, +whether or not the release is a prerelease candidate or an official distribution, whether the +source code should be compiled in develop mode or in release mode, and other version metadata. -The version number is read in from ../../version.txt, which contains major, minor, and patch version -numbers. These numbers will be propagated through the source code, latex files, markdown files, -etc. The version numbers can be overridden using the command line argument --version major.minor.macro. +The version number is read from ../version.txt, which contains major, minor, and patch version +numbers, and an optional label. Version numbers are substituted into source code, latex files, +markdown files, etc. The version number can be overridden using argument --version, short -v. -Develop mode is set to 0 if the distribution is approved. - -Once this script is run, these updated files will be used in compilation, latex documents, and -other parts of the repo to mark the overall status. +Develop mode is set to 0 if the distribution is approved with --approved, short -a, otherwise, +it is set to 1. """ import argparse import json import os +import re import shutil import textwrap from collections import OrderedDict from datetime import datetime -from enum import Enum from os import PathLike from pathlib import Path from typing import NamedTuple, Optional @@ -43,7 +41,7 @@ from filelock import FileLock import yaml -from utils import get_modified_time +from utils import get_modified_time, split_nonnumeric project_name = "MODFLOW 6" project_root_path = Path(__file__).resolve().parent.parent @@ -62,31 +60,36 @@ class Version(NamedTuple): - """Semantic version number, not including extensions (e.g., 'Release Candidate')""" + """Semantic version number, optionally with a short label (e.g, 'rc'). + The label may contain numbers but must begin with a letter.""" major: int = 0 minor: int = 0 patch: int = 0 + label: Optional[str] = None def __repr__(self): - return f"{self.major}.{self.minor}.{self.patch}" + s = f"{self.major}.{self.minor}.{self.patch}" + if self.label is not None and self.label != "": + s += self.label + return s @classmethod def from_string(cls, version: str) -> "Version": t = version.split(".") - + assert len(t) > 2 vmajor = int(t[0]) vminor = int(t[1]) - vpatch = int(t[2]) - - return cls(major=vmajor, minor=vminor, patch=vpatch) + tt = split_nonnumeric(t[2]) + vpatch = int(tt[0]) + vlabel = tt[1] if len(tt) > 1 else None + return cls(major=vmajor, minor=vminor, patch=vpatch, label=vlabel) @classmethod def from_file(cls, path: PathLike) -> "Version": path = Path(path).expanduser().absolute() lines = [line.rstrip("\n") for line in open(Path(path), "r")] - - vmajor = vminor = vpatch = None + vmajor = vminor = vpatch = vlabel = None for line in lines: t = line.split() if "major =" in line: @@ -95,18 +98,15 @@ def from_file(cls, path: PathLike) -> "Version": vminor = int(t[2]) elif "micro =" in line: vpatch = int(t[2]) + elif "label =" in line: + vlabel = t[2].replace("'", "") - msg = "version string must follow semantic version format: major.minor.patch" - assert vmajor is not None, f"Missing major number, {msg}" - assert vminor is not None, f"Missing minor number, {msg}" - assert vpatch is not None, f"Missing patch number, {msg}" - - return cls(major=vmajor, minor=vminor, patch=vpatch) - - -class ReleaseType(Enum): - CANDIDATE = "Release Candidate" - APPROVED = "Release" + msg = "version string must follow semantic version format, with optional label: major.minor.patch[label]" + missing = lambda v: f"Missing {v} number, {msg}" + assert vmajor is not None, missing("major") + assert vminor is not None, missing("minor") + assert vpatch is not None, missing("patch") + return cls(major=vmajor, minor=vminor, patch=vpatch, label=vlabel) @@ -166,18 +166,19 @@ class ReleaseType(Enum): """ -def get_disclaimer(release_type: ReleaseType, formatted: bool = False) -> str: - approved = _approved_fmtdisclaimer if formatted else _approved_disclaimer - preliminary = _preliminary_fmtdisclaimer if formatted else _preliminary_disclaimer - return approved if release_type == ReleaseType.APPROVED else preliminary +def get_disclaimer(approved: bool = False, formatted: bool = False) -> str: + if approved: + return _approved_fmtdisclaimer if formatted else _approved_disclaimer + else: + return _preliminary_fmtdisclaimer if formatted else _preliminary_disclaimer -def log_update(path, release_type: ReleaseType, version: Version): - print(f"Updated {path} with version {version}" + (f" {release_type.value}" if release_type != ReleaseType.APPROVED else "")) +def log_update(path, version: Version): + print(f"Updated {path} with version {version}") def update_version_txt_and_py( - release_type: ReleaseType, timestamp: datetime, version: Version + version: Version, timestamp: datetime ): with open(version_file_path, "w") as f: f.write( @@ -189,38 +190,34 @@ def update_version_txt_and_py( f.write(f"major = {version.major}\n") f.write(f"minor = {version.minor}\n") f.write(f"micro = {version.patch}\n") + f.write("label = " + (("'" + version.label + "'") if version.label else "''") + "\n") f.write("__version__ = '{:d}.{:d}.{:d}'.format(major, minor, micro)\n") + f.write("if label:\n") + f.write("\t__version__ += '{}{}'.format(__version__, label)") f.close() - log_update(version_file_path, release_type, version) - + log_update(version_file_path, version) py_path = project_root_path / "doc" / version_file_path.name.replace(".txt", ".py") shutil.copyfile(version_file_path, py_path) - log_update(py_path, release_type, version) + log_update(py_path, version) -def update_meson_build(release_type: ReleaseType, timestamp: datetime, version: Version): +def update_meson_build(version: Version): path = project_root_path / "meson.build" lines = open(path, "r").read().splitlines() with open(path, "w") as f: for line in lines: if "version:" in line and "meson_version:" not in line: - line = f" version: '{version.major}.{version.minor}.{version.patch}'," + line = f" version: '{version.major}.{version.minor}.{version.patch}{version.label if version.label else ''}'," f.write(f"{line}\n") - - log_update(path, release_type, version) + log_update(path, version) def update_version_tex( - release_type: ReleaseType, timestamp: datetime, version: Version + version: Version, timestamp: datetime ): path = project_root_path / "doc" / "version.tex" - - version_str = str(version) - if release_type != ReleaseType.APPROVED: - version_str += "rc" - with open(path, "w") as f: - line = "\\newcommand{\\modflowversion}{mf" + f"{version_str}" + "}" + line = "\\newcommand{\\modflowversion}{mf" + f"{str(version)}" + "}" f.write(f"{line}\n") line = ( "\\newcommand{\\modflowdate}{" + f"{timestamp.strftime('%B %d, %Y')}" + "}" @@ -231,17 +228,24 @@ def update_version_tex( + "{Version \\modflowversion---\\modflowdate}" ) f.write(f"{line}\n") - - log_update(path, release_type, version) + log_update(path, version) def update_version_f90( - release_type: ReleaseType, timestamp: datetime, version: Optional[Version] + version: Optional[Version], timestamp: datetime, approved: bool = False ): path = project_root_path / "src" / "Utilities" / "version.f90" lines = open(path, "r").read().splitlines() with open(path, "w") as f: skip = False + version_spl = str(version).rpartition("-") + if version_spl[1]: + version_num = version_spl[0] + version_label = version_spl[2] + else: + version_num = str(version_spl[2]) + version_label = "" + for line in lines: # skip all of the disclaimer text if skip: @@ -251,52 +255,51 @@ def update_version_f90( elif ":: IDEVELOPMODE =" in line: line = ( " integer(I4B), parameter :: " - + f"IDEVELOPMODE = {1 if release_type == ReleaseType.CANDIDATE else 0}" + + f"IDEVELOPMODE = {0 if approved else 1}" ) elif ":: VERSIONNUMBER =" in line: - line = line.rpartition("::")[0] + f":: VERSIONNUMBER = '{version}'" + line = line.rpartition("::")[0] + f":: VERSIONNUMBER = '{version_num}'" elif ":: VERSIONTAG =" in line: fmat_tstmp = timestamp.strftime("%m/%d/%Y") - line = line.rpartition("::")[0] + f":: VERSIONTAG = ' {release_type.value} {fmat_tstmp}'" + label_clause = version_label if version_label else "" + label_clause += " (preliminary)" if not approved else "" + line = line.rpartition("::")[0] + f":: VERSIONTAG = '{label_clause} {fmat_tstmp}'" elif ":: FMTDISCLAIMER =" in line: - line = get_disclaimer(release_type, formatted=True) + line = get_disclaimer(approved, formatted=True) skip = True f.write(f"{line}\n") - - log_update(path, release_type, version) + log_update(path, version) def update_readme_and_disclaimer( - release_type: ReleaseType, timestamp: datetime, version: Version + version: Version, approved: bool = False ): - disclaimer = get_disclaimer(release_type, formatted=False) + disclaimer = get_disclaimer(approved, formatted=False) readme_path = str(project_root_path / "README.md") readme_lines = open(readme_path, "r").read().splitlines() with open(readme_path, "w") as f: for line in readme_lines: if "## Version " in line: version_line = f"### Version {version}" - if release_type != ReleaseType.APPROVED: - version_line += f" {release_type.value}" + if not approved: + version_line += " (preliminary)" f.write(f"{version_line}\n") elif "Disclaimer" in line: f.write(f"{disclaimer}\n") break else: f.write(f"{line}\n") - log_update(readme_path, release_type, version) + log_update(readme_path, version) disclaimer_path = project_root_path / "DISCLAIMER.md" with open(disclaimer_path, "w") as f: f.write(disclaimer) - log_update(disclaimer_path, release_type, version) + log_update(disclaimer_path, version) -def update_citation_cff(release_type: ReleaseType, timestamp: datetime, version: Version): +def update_citation_cff(version: Version, timestamp: datetime): path = project_root_path / "CITATION.cff" citation = yaml.safe_load(path.read_text()) - - # update version and date-released citation["version"] = str(version) citation["date-released"] = timestamp.strftime("%Y-%m-%d") @@ -308,28 +311,28 @@ def update_citation_cff(release_type: ReleaseType, timestamp: datetime, version: default_flow_style=False, sort_keys=False, ) - log_update(path, release_type, version) + log_update(path, version) -def update_codejson(release_type: ReleaseType, timestamp: datetime, version: Version): +def update_codejson(version: Version, timestamp: datetime, approved: bool = False): path = project_root_path / "code.json" with open(path, "r") as f: data = json.load(f, object_pairs_hook=OrderedDict) data[0]["date"]["metadataLastUpdated"] = timestamp.strftime("%Y-%m-%d") data[0]["version"] = str(version) - data[0]["status"] = release_type.value + data[0]["status"] = "Release" if approved else "Preliminary" with open(path, "w") as f: json.dump(data, f, indent=4) f.write("\n") - log_update(path, release_type, version) + log_update(path, version) def update_version( - release_type: ReleaseType = ReleaseType.CANDIDATE, - timestamp: datetime = datetime.now(), version: Version = None, + timestamp: datetime = datetime.now(), + approved: bool = False ): """ Update version information stored in version.txt in the project root, @@ -351,13 +354,13 @@ def update_version( ) with lock: - update_version_txt_and_py(release_type, timestamp, version) - update_meson_build(release_type, timestamp, version) - update_version_tex(release_type, timestamp, version) - update_version_f90(release_type, timestamp, version) - update_readme_and_disclaimer(release_type, timestamp, version) - update_citation_cff(release_type, timestamp, version) - update_codejson(release_type, timestamp, version) + update_version_txt_and_py(version, timestamp) + update_meson_build(version) + update_version_tex(version, timestamp) + update_version_f90(version, timestamp, approved) + update_readme_and_disclaimer(version, approved) + update_citation_cff(version, timestamp) + update_codejson(version, timestamp, approved) finally: lock_path.unlink(missing_ok=True) @@ -367,14 +370,13 @@ def update_version( @pytest.mark.skip(reason="reverts repo files on cleanup, tread carefully") -@pytest.mark.parametrize( - "release_type", [ReleaseType.CANDIDATE, ReleaseType.APPROVED] -) @pytest.mark.parametrize( "version", - [None, Version(major=_initial_version.major, minor=_initial_version.minor, patch=_initial_version.patch)], + [None, + Version(major=_initial_version.major, minor=_initial_version.minor, patch=_initial_version.patch), + Version(major=_initial_version.major, minor=_initial_version.minor, patch=_initial_version.patch, label="rc")], ) -def test_update_version(tmp_path, release_type, version): +def test_update_version(release_type, version): m_times = [get_modified_time(file) for file in touched_file_paths] timestamp = datetime.now() @@ -391,11 +393,18 @@ def test_update_version(tmp_path, release_type, version): assert updated.major == _initial_version.major assert updated.minor == _initial_version.minor assert updated.patch == _initial_version.patch + if version.label is not None: + assert updated.label == version.label else: # version should not have changed assert updated.major == _current_version.major assert updated.minor == _current_version.minor assert updated.patch == _current_version.patch + if version.label is not None: + assert updated.label == version.label + + if version.label is not None: + assert updated.label == _initial_version finally: for p in touched_file_paths: os.system(f"git restore {p}") @@ -489,7 +498,7 @@ def test_update_version(tmp_path, release_type, version): version = Version(current.major, current.minor, current.patch + 1) update_version( - release_type=ReleaseType.APPROVED if args.approve else ReleaseType.CANDIDATE, - timestamp=datetime.now(), version=version, + timestamp=datetime.now(), + approved=args.approve, ) diff --git a/distribution/utils.py b/distribution/utils.py index 06b66e6bbfd..ca0a634e744 100644 --- a/distribution/utils.py +++ b/distribution/utils.py @@ -1,5 +1,6 @@ import os import platform +import re import shutil import subprocess import sys @@ -9,6 +10,7 @@ from warnings import warn from modflow_devtools.markers import requires_exe +import pytest _project_root_path = Path(__file__).resolve().parent.parent @@ -134,5 +136,11 @@ def convert_line_endings(folder, windows=True): @requires_exe("dos2unix", "unix2dos") +@pytest.mark.skip(reason="todo") def test_convert_line_endings(): pass + + +def split_nonnumeric(s): + match = re.compile("[^0-9]").search(s) + return [s[:match.start()], s[match.start():]] if match else s From c24daa85e32f22c6f6df76592b7d1b3193a98b5e Mon Sep 17 00:00:00 2001 From: w-bonelli Date: Wed, 14 Jun 2023 21:14:10 -0400 Subject: [PATCH 104/123] ci: fix release refactor (#1253) * remove default branch input in release.yml * fix check_dist.py test for IDEVELOPMODE --- .github/workflows/release.yml | 5 ++--- distribution/check_dist.py | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ae92fd1c9e2..14fdbbdf208 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -10,10 +10,9 @@ on: type: boolean default: false branch: - description: 'Branch to use for release. Default is the develop branch.' - required: false + description: 'Branch to use for release.' + required: true type: string - default: 'develop' development: description: 'Toggle a minimal development build. Default is false, i.e. to perform a full release build. If true, approve is not considered.' required: false diff --git a/distribution/check_dist.py b/distribution/check_dist.py index c2dd3a43798..4b2ad34a2b9 100644 --- a/distribution/check_dist.py +++ b/distribution/check_dist.py @@ -36,7 +36,7 @@ def skip(): return path -def test_sources(dist_dir_path): +def test_sources(dist_dir_path, approved): assert (dist_dir_path / "src").is_dir() assert (dist_dir_path / "src" / "mf6.f90").is_file() @@ -51,7 +51,7 @@ def test_sources(dist_dir_path): # make sure IDEVELOPMODE was set correctly branch = get_branch() - idevelopmode = 1 if ("rc" in branch or "candidate" in branch) else 0 + idevelopmode = 0 if approved else 1 assert f"IDEVELOPMODE = {idevelopmode}" in line From 705b2b223b44ff816d3eca21f510a01329ff0bbb Mon Sep 17 00:00:00 2001 From: mjr-deltares <45555666+mjr-deltares@users.noreply.github.com> Date: Thu, 15 Jun 2023 18:33:43 +0200 Subject: [PATCH 105/123] - change petsc matrix format for running 1 core (#1256) - petsc initialize with backwards compatability --- autotest/test_par_gwf03.py | 22 +++++++--------------- src/Distributed/MpiRunControl.F90 | 2 +- src/Utilities/Matrix/PetscMatrix.F90 | 4 ++-- 3 files changed, 10 insertions(+), 18 deletions(-) diff --git a/autotest/test_par_gwf03.py b/autotest/test_par_gwf03.py index 2b8e3c76fe5..2d329446a14 100644 --- a/autotest/test_par_gwf03.py +++ b/autotest/test_par_gwf03.py @@ -217,21 +217,13 @@ def build_petsc_db(idx, exdir): np = ncpus[idx] petsc_db_file = os.path.join(exdir, ".petscrc") with open(petsc_db_file, 'w') as petsc_file: - if np == 1: - petsc_file.write("-ksp_type cg\n") - petsc_file.write("-pc_type ilu\n") - petsc_file.write("-pc_factor_levels 2\n") - petsc_file.write(f"-dvclose {Decimal(hclose):.2E}\n") - petsc_file.write(f"-nitermax {500}\n") - petsc_file.write("-options_left no\n") - else: - petsc_file.write("-ksp_type cg\n") - petsc_file.write("-pc_type bjacobi\n") - petsc_file.write("-sub_pc_type ilu\n") - petsc_file.write("-sub_pc_factor_levels 2\n") - petsc_file.write(f"-dvclose {Decimal(hclose):.2E}\n") - petsc_file.write(f"-nitermax {500}\n") - petsc_file.write("-options_left no\n") + petsc_file.write("-ksp_type cg\n") + petsc_file.write("-pc_type bjacobi\n") + petsc_file.write("-sub_pc_type ilu\n") + petsc_file.write("-sub_pc_factor_levels 2\n") + petsc_file.write(f"-dvclose {Decimal(hclose):.2E}\n") + petsc_file.write(f"-nitermax {500}\n") + petsc_file.write("-options_left no\n") def build_model(idx, exdir): sim = get_simulation(idx, exdir) diff --git a/src/Distributed/MpiRunControl.F90 b/src/Distributed/MpiRunControl.F90 index c66897327f1..32aa468a577 100644 --- a/src/Distributed/MpiRunControl.F90 +++ b/src/Distributed/MpiRunControl.F90 @@ -60,7 +60,7 @@ subroutine mpi_ctrl_start(this) inquire (file=petsc_db_file, exist=petsc_db_exists) if (.not. petsc_db_exists) then write (*, *) 'WARNING. PETSc database file not found: '//petsc_db_file - call PetscInitialize(ierr) + call PetscInitialize(PETSC_NULL_CHARACTER, ierr) CHKERRQ(ierr) else call PetscInitialize(petsc_db_file, ierr) diff --git a/src/Utilities/Matrix/PetscMatrix.F90 b/src/Utilities/Matrix/PetscMatrix.F90 index a65bcad0823..92d6955fbb5 100644 --- a/src/Utilities/Matrix/PetscMatrix.F90 +++ b/src/Utilities/Matrix/PetscMatrix.F90 @@ -57,7 +57,7 @@ module PetscMatrixModule contains subroutine pm_init(this, sparse, mem_path) - use SimVariablesModule, only: simulation_mode, nr_procs + use SimVariablesModule, only: simulation_mode class(PetscMatrixType) :: this type(sparsematrix) :: sparse character(len=*) :: mem_path @@ -89,7 +89,7 @@ subroutine pm_init(this, sparse, mem_path) end do ! create PETSc matrix object - if (simulation_mode == 'PARALLEL' .and. nr_procs > 1) then + if (simulation_mode == 'PARALLEL') then this%is_parallel = .true. call MatCreateMPIAIJWithArrays(PETSC_COMM_WORLD, & sparse%nrow, sparse%nrow, & From b0c158cdd4eb7e44004c145aa70fca2a2736f18d Mon Sep 17 00:00:00 2001 From: jdhughes-usgs Date: Fri, 16 Jun 2023 15:40:41 -0500 Subject: [PATCH 106/123] build: refactored meson files for cray build (#1258) * Refactor meson files to build a CRAY parallel version of MODFLOW using cray-mpich (7.7.19) and cray-petsc (3.14.5) * Bumped minimum version of meson to 1.1.0. * Updated `PARALLEL.md` with information on installing OpenMPI and PETSc on MacOS using Homebrew. * Added license for PETSc (this is only added to the license when MODFLOW is built with PETSc) * Added functionality to use MODFLOW 6 PROFESSIONAL in the header written to the screen and listing files when MODFLOW 6 is built with PETSC * Added minimal MODFLOW simulation files in `.mf6minsim` directory that can be used for testing serial and parallel builds * Update makefiles --- .hpc/BUILD.md | 20 ++++ .hpc/cray-meson-build.slurm.batch | 37 ++++++ .mf6minsim/.petscrc | 6 + .mf6minsim/leftmodel.chd | 12 ++ .mf6minsim/leftmodel.dis | 21 ++++ .mf6minsim/leftmodel.ic | 6 + .mf6minsim/leftmodel.nam | 11 ++ .mf6minsim/leftmodel.npf | 13 +++ .mf6minsim/mfsim.nam | 21 ++++ .mf6minsim/par_gwf01-1d.gwfgwf | 13 +++ .mf6minsim/par_gwf01-1d.ims | 20 ++++ .mf6minsim/par_gwf01-1d.tdis | 13 +++ .mf6minsim/rightmodel.chd | 12 ++ .mf6minsim/rightmodel.dis | 23 ++++ .mf6minsim/rightmodel.ic | 6 + .mf6minsim/rightmodel.nam | 11 ++ .mf6minsim/rightmodel.npf | 13 +++ PARALLEL.md | 66 ++++++++--- doc/ReleaseNotes/ReleaseNotes.tex | 4 +- doc/ReleaseNotes/{v6.5.0.tex => v6.4.2.tex} | 0 doc/version.py | 4 +- doc/version.tex | 2 +- environment.yml | 2 +- make/makefile | 58 +++++----- meson.build | 122 +++++++++++++------- meson.options | 4 + meson_options.txt | 1 - src/Utilities/comarg.f90 | 4 +- src/Utilities/defmacro.F90 | 45 +++++++- src/Utilities/version.f90 | 73 +++++++++--- src/meson.build | 6 +- srcbmi/meson.build | 4 +- utils/meson.build | 7 +- utils/mf5to6/make/makefile | 9 +- utils/mf5to6/meson.build | 5 +- utils/mf5to6/pymake/extrafiles.txt | 1 + utils/zonebudget/make/makefile | 2 +- utils/zonebudget/meson.build | 6 +- version.txt | 4 +- 39 files changed, 560 insertions(+), 127 deletions(-) create mode 100644 .hpc/BUILD.md create mode 100644 .hpc/cray-meson-build.slurm.batch create mode 100644 .mf6minsim/.petscrc create mode 100644 .mf6minsim/leftmodel.chd create mode 100644 .mf6minsim/leftmodel.dis create mode 100644 .mf6minsim/leftmodel.ic create mode 100644 .mf6minsim/leftmodel.nam create mode 100644 .mf6minsim/leftmodel.npf create mode 100644 .mf6minsim/mfsim.nam create mode 100644 .mf6minsim/par_gwf01-1d.gwfgwf create mode 100644 .mf6minsim/par_gwf01-1d.ims create mode 100644 .mf6minsim/par_gwf01-1d.tdis create mode 100644 .mf6minsim/rightmodel.chd create mode 100644 .mf6minsim/rightmodel.dis create mode 100644 .mf6minsim/rightmodel.ic create mode 100644 .mf6minsim/rightmodel.nam create mode 100644 .mf6minsim/rightmodel.npf rename doc/ReleaseNotes/{v6.5.0.tex => v6.4.2.tex} (100%) create mode 100644 meson.options delete mode 100644 meson_options.txt diff --git a/.hpc/BUILD.md b/.hpc/BUILD.md new file mode 100644 index 00000000000..dd8257e2b6d --- /dev/null +++ b/.hpc/BUILD.md @@ -0,0 +1,20 @@ +# Building MODFLOW 6 on HPC systems + +## SLURM job + +``` +sbatch --reservation=dev cray-meson-build.slurm.batch +``` + +## Interactive job + +``` +module switch PrgEnv-${PE_ENV,,} PrgEnv-intel +module load cray-petsc meson ninja +export PKG_CONFIG_PATH=/opt/cray/pe/mpt/7.7.19/gni/mpich-intel/16.0/lib/pkgconfig:/opt/cray/pe/petsc/3.14.5.0/real/INTEL/19.1/x86_skylake/lib/pkgconfig:$PKG_CONFIG_PATH + +srun --reservation=dev --account=impd --pty bash + +meson setup builddir -Ddebug=false --prefix=$(pwd) --libdir=bin -Dcray=true -Ddebug=false --wipe +meson install -C builddir +``` \ No newline at end of file diff --git a/.hpc/cray-meson-build.slurm.batch b/.hpc/cray-meson-build.slurm.batch new file mode 100644 index 00000000000..c49de516198 --- /dev/null +++ b/.hpc/cray-meson-build.slurm.batch @@ -0,0 +1,37 @@ +#!/bin/bash + +#SBATCH --job-name=meson-MODFLOW-build +#SBATCH --nodes=1 +#SBATCH --ntasks=2 +#SBATCH --account=impd +#SBATCH --time=00:10:00 +#SBATCH --output=slurm-%j.out + +# load appropriate modules +module switch PrgEnv-${PE_ENV,,} PrgEnv-intel +module load cray-petsc meson ninja +export PKG_CONFIG_PATH=/opt/cray/pe/mpt/7.7.19/gni/mpich-intel/16.0/lib/pkgconfig:/opt/cray/pe/petsc/3.14.5.0/real/INTEL/19.1/x86_skylake/lib/pkgconfig:$PKG_CONFIG_PATH + +# list loaded modules +module list + +# move to root directory +cd .. + +# define paths relative to the root directory +MODFLOW6ROOT=$(pwd) +BINDIR=$MODFLOW6ROOT/bin +TESTDIR=$MODFLOW6ROOT/.mf6minsim + +# build MODFLOW 6 +CC=cc CXX=CC F77=ftn F90=ftn FC=ftn meson setup builddir --prefix=$(pwd) --libdir=bin -Dcray=true -Ddebug=false --wipe +meson install -C builddir + +# test MODFLOW 6 build +cd $TESTDIR + +# serial run +$BINDIR/mf6 + +# parallel run +srun $BINDIR/mf6 -p diff --git a/.mf6minsim/.petscrc b/.mf6minsim/.petscrc new file mode 100644 index 00000000000..45020662fe1 --- /dev/null +++ b/.mf6minsim/.petscrc @@ -0,0 +1,6 @@ +-ksp_type cg +-pc_type bjacobi +-sub_pc_type ilu +-sub_pc_factor_levels 2 +-dvclose 1.0e-6 +-options_left no diff --git a/.mf6minsim/leftmodel.chd b/.mf6minsim/leftmodel.chd new file mode 100644 index 00000000000..3b6347a5d63 --- /dev/null +++ b/.mf6minsim/leftmodel.chd @@ -0,0 +1,12 @@ +# File generated by Flopy version 3.3.7 on 06/01/2023 at 12:15:38. +BEGIN options +END options + +BEGIN dimensions + MAXBOUND 1 +END dimensions + +BEGIN period 1 + 1 1 1 1.00000000 +END period 1 + diff --git a/.mf6minsim/leftmodel.dis b/.mf6minsim/leftmodel.dis new file mode 100644 index 00000000000..16662c7a5c7 --- /dev/null +++ b/.mf6minsim/leftmodel.dis @@ -0,0 +1,21 @@ +# File generated by Flopy version 3.3.7 on 06/01/2023 at 12:15:38. +BEGIN options +END options + +BEGIN dimensions + NLAY 1 + NROW 1 + NCOL 5 +END dimensions + +BEGIN griddata + delr + CONSTANT 100.00000000 + delc + CONSTANT 100.00000000 + top + CONSTANT 10.00000000 + botm + CONSTANT -100.00000000 +END griddata + diff --git a/.mf6minsim/leftmodel.ic b/.mf6minsim/leftmodel.ic new file mode 100644 index 00000000000..c06c499afbf --- /dev/null +++ b/.mf6minsim/leftmodel.ic @@ -0,0 +1,6 @@ +# File generated by Flopy version 3.3.7 on 06/01/2023 at 12:15:38. +BEGIN griddata + strt + CONSTANT 0.00000000 +END griddata + diff --git a/.mf6minsim/leftmodel.nam b/.mf6minsim/leftmodel.nam new file mode 100644 index 00000000000..3cba0577fea --- /dev/null +++ b/.mf6minsim/leftmodel.nam @@ -0,0 +1,11 @@ +# File generated by Flopy version 3.3.7 on 06/01/2023 at 12:15:38. +BEGIN options +END options + +BEGIN packages + DIS6 leftmodel.dis dis + IC6 leftmodel.ic ic + NPF6 leftmodel.npf npf + CHD6 leftmodel.chd chd_0 +END packages + diff --git a/.mf6minsim/leftmodel.npf b/.mf6minsim/leftmodel.npf new file mode 100644 index 00000000000..9a4023a8378 --- /dev/null +++ b/.mf6minsim/leftmodel.npf @@ -0,0 +1,13 @@ +# File generated by Flopy version 3.3.7 on 06/01/2023 at 12:15:38. +BEGIN options + SAVE_FLOWS + SAVE_SPECIFIC_DISCHARGE +END options + +BEGIN griddata + icelltype + CONSTANT 1 + k + CONSTANT 1.00000000 +END griddata + diff --git a/.mf6minsim/mfsim.nam b/.mf6minsim/mfsim.nam new file mode 100644 index 00000000000..b7a8c040960 --- /dev/null +++ b/.mf6minsim/mfsim.nam @@ -0,0 +1,21 @@ +# File generated by Flopy version 3.3.7 on 06/01/2023 at 12:15:38. +BEGIN options +END options + +BEGIN timing + TDIS6 par_gwf01-1d.tdis +END timing + +BEGIN models + gwf6 leftmodel.nam leftmodel + gwf6 rightmodel.nam rightmodel +END models + +BEGIN exchanges + GWF6-GWF6 par_gwf01-1d.gwfgwf leftmodel rightmodel +END exchanges + +BEGIN solutiongroup 1 + ims6 par_gwf01-1d.ims leftmodel rightmodel +END solutiongroup 1 + diff --git a/.mf6minsim/par_gwf01-1d.gwfgwf b/.mf6minsim/par_gwf01-1d.gwfgwf new file mode 100644 index 00000000000..b57e7c6bce9 --- /dev/null +++ b/.mf6minsim/par_gwf01-1d.gwfgwf @@ -0,0 +1,13 @@ +# File generated by Flopy version 3.3.7 on 06/01/2023 at 12:15:38. +BEGIN options + auxiliary ANGLDEGX CDIST +END options + +BEGIN dimensions + NEXG 1 +END dimensions + +BEGIN exchangedata + 1 1 5 1 1 1 1 50.00000000 50.00000000 100.00000000 0.00000000 100.00000000 +END exchangedata + diff --git a/.mf6minsim/par_gwf01-1d.ims b/.mf6minsim/par_gwf01-1d.ims new file mode 100644 index 00000000000..00598e4d05d --- /dev/null +++ b/.mf6minsim/par_gwf01-1d.ims @@ -0,0 +1,20 @@ +# File generated by Flopy version 3.3.7 on 06/01/2023 at 12:15:38. +BEGIN options + PRINT_OPTION all +END options + +BEGIN nonlinear + OUTER_DVCLOSE 1.00000000E-08 + OUTER_MAXIMUM 100 + UNDER_RELAXATION dbd +END nonlinear + +BEGIN linear + INNER_MAXIMUM 300 + INNER_DVCLOSE 1.00000000E-08 + inner_rclose 0.00100000 + LINEAR_ACCELERATION cg + RELAXATION_FACTOR 0.0000000 + preconditioner_levels 2 +END linear + diff --git a/.mf6minsim/par_gwf01-1d.tdis b/.mf6minsim/par_gwf01-1d.tdis new file mode 100644 index 00000000000..9966abaa0e6 --- /dev/null +++ b/.mf6minsim/par_gwf01-1d.tdis @@ -0,0 +1,13 @@ +# File generated by Flopy version 3.3.7 on 06/01/2023 at 12:15:38. +BEGIN options + TIME_UNITS days +END options + +BEGIN dimensions + NPER 1 +END dimensions + +BEGIN perioddata + 1.00000000 1 1.00000000 +END perioddata + diff --git a/.mf6minsim/rightmodel.chd b/.mf6minsim/rightmodel.chd new file mode 100644 index 00000000000..d6c952c6c57 --- /dev/null +++ b/.mf6minsim/rightmodel.chd @@ -0,0 +1,12 @@ +# File generated by Flopy version 3.3.7 on 06/01/2023 at 12:15:38. +BEGIN options +END options + +BEGIN dimensions + MAXBOUND 1 +END dimensions + +BEGIN period 1 + 1 1 5 10.00000000 +END period 1 + diff --git a/.mf6minsim/rightmodel.dis b/.mf6minsim/rightmodel.dis new file mode 100644 index 00000000000..15d3436a7c2 --- /dev/null +++ b/.mf6minsim/rightmodel.dis @@ -0,0 +1,23 @@ +# File generated by Flopy version 3.3.7 on 06/01/2023 at 12:15:38. +BEGIN options + XORIGIN 500.00000000 + YORIGIN 0.00000000 +END options + +BEGIN dimensions + NLAY 1 + NROW 1 + NCOL 5 +END dimensions + +BEGIN griddata + delr + CONSTANT 100.00000000 + delc + CONSTANT 100.00000000 + top + CONSTANT 10.00000000 + botm + CONSTANT -100.00000000 +END griddata + diff --git a/.mf6minsim/rightmodel.ic b/.mf6minsim/rightmodel.ic new file mode 100644 index 00000000000..c06c499afbf --- /dev/null +++ b/.mf6minsim/rightmodel.ic @@ -0,0 +1,6 @@ +# File generated by Flopy version 3.3.7 on 06/01/2023 at 12:15:38. +BEGIN griddata + strt + CONSTANT 0.00000000 +END griddata + diff --git a/.mf6minsim/rightmodel.nam b/.mf6minsim/rightmodel.nam new file mode 100644 index 00000000000..be4b2ebe877 --- /dev/null +++ b/.mf6minsim/rightmodel.nam @@ -0,0 +1,11 @@ +# File generated by Flopy version 3.3.7 on 06/01/2023 at 12:15:38. +BEGIN options +END options + +BEGIN packages + DIS6 rightmodel.dis dis + IC6 rightmodel.ic ic + NPF6 rightmodel.npf npf + CHD6 rightmodel.chd chd_0 +END packages + diff --git a/.mf6minsim/rightmodel.npf b/.mf6minsim/rightmodel.npf new file mode 100644 index 00000000000..9a4023a8378 --- /dev/null +++ b/.mf6minsim/rightmodel.npf @@ -0,0 +1,13 @@ +# File generated by Flopy version 3.3.7 on 06/01/2023 at 12:15:38. +BEGIN options + SAVE_FLOWS + SAVE_SPECIFIC_DISCHARGE +END options + +BEGIN griddata + icelltype + CONSTANT 1 + k + CONSTANT 1.00000000 +END griddata + diff --git a/PARALLEL.md b/PARALLEL.md index 6b1372b3cbc..8229ae1cbbb 100644 --- a/PARALLEL.md +++ b/PARALLEL.md @@ -19,43 +19,73 @@ The design philosophy has been to maintain MODFLOW as a single codebase and have ## Prerequisites +The parallel version of MODFLOW 6 requires the the Message Passing Interface (MPI) and the Portable, Extensible Toolkit for Scientific Computation (PETSc - pronounced PET-see (/ˈpɛt-siː/)) libraries. + ### MPI -The parallel software uses the Message Passing Interface (MPI) to synchronize data between processes. There are a couple of implementations of the MPI standard available. Their applicability usually depends on the platform that is used: +The parallel version of MODFLOW 6 uses MPI to synchronize data between processes. There are a couple of implementations of the MPI standard available. Their applicability usually depends on the platform that is used: - Open MPI: https://www.open-mpi.org/ - MPICH: https://www.mpich.org/ - Intel MPI: https://www.intel.com/content/www/us/en/developer/tools/oneapi/mpi-library.html - Microsoft MPI: https://learn.microsoft.com/en-us/message-passing-interface/microsoft-mpi -On Linux and macOS, if you haven't installed any MPI framework yet, your best bet is to automatically download the MPI implementation upon configuring PETSc with the option flag `--download-openmpi` or `--download-mpich` (see below). Alternatively you can install OpenMPI or MPICH using the package manager of your OS. It general it would be good to check the table below for tested configurations. +On Linux and macOS, if you haven't installed a MPI framework yet, your best bet is to automatically download the MPI implementation if building PETSc from source using the option flag `--download-openmpi` or `--download-mpich` and the `configure` approach described below. Alternatively, you can install OpenMPI or MPICH using a package manager for your OS. Tested configurations are listed in the table below. In addition to compiling, the MPI toolset is also required to run a parallel simulation. The implementations above all come with `mpiexec` (or `mpiexec.exe`) to start an executable in parallel. ### PETSc -PETSc, the Portable, Extensible Toolkit for Scientific Computation, pronounced PET-see (/ˈpɛt-siː/), is a suite of data structures and routines for the scalable (parallel) solution of scientific applications modeled by partial differential equations: https://petsc.org/release/ +The PETSc library is a suite of data structures and routines for the scalable (parallel) solution of scientific applications modeled by partial differential equations: + +https://petsc.org/release/ -The PETSc library is used by MODFLOW for its parallel linear solver capabilities and the distributed data formats (vectors and matrices) that go along with it. Parallel PETSc uses MPI internally as well, so setting up this library should typically be coordinated with the installation of the MPI library. A lot of obscure things can happen if the binaries are not compatible, so in general it is a good strategy to compile MPI, PETSc, and MODFLOW with the same compiler toolchain. +The PETSc library (version 3.16 or higher) is used by MODFLOW for its parallel linear solver capabilities and the distributed data formats (vectors and matrices) that go along with it. Parallel PETSc uses MPI internally as well, so setting up this library should typically be coordinated with the installation of the MPI library. A lot of obscure things can happen if the binaries are not compatible, so in general it is a good strategy to compile MPI, PETSc, and MODFLOW with the same compiler toolchain or install MPI and PETSc using a package manager and built with the same compiler being used to compile MODFLOW. -The PETSc website gives details on a large number of configurations, depending on the target platform/OS, and many different ways to configure/make/install the library: https://petsc.org/release/install/. Building on Windows is notoriously challenging and discouraged by the PETSc development team. On Linux, however, it could be as easy as +## Compiling MPI and PETSC from source +The PETSc website gives details on a large number of configurations, depending on the target platform/OS, and many different ways to configure/make/install the library: https://petsc.org/release/install/. Building on Windows is notoriously challenging and discouraged by the PETSc development team. On Linux, however, PETSc can be installed (configure/make/install) by executing the following command ``` $ ./configure --download-openmpi --download-fblaslapack $ make all ``` +in a terminal open in the root directory of your PETSc download + + + +## Using a package manager to install MPI and PETSc + +Use of a package manager can simplify the process of building the parallel version of MODFLOW 6. + +### MacOS +[OpenMPI](https://formulae.brew.sh/formula/open-mpi) and [PETSc](https://formulae.brew.sh/formula/petsc) are available on Homebrew for Intel and Apple Silicon (M1). Both of these depend on [gcc 13.1.0](https://formulae.brew.sh/formula/gcc). [pkg-config](https://formulae.brew.sh/formula/pkg-config) should also be installed from Homebrew, if not already installed, so that Meson will be able to resolve the installation location of MPI and PETSc. + +### Ubuntu +OpenMPI and PETSc are available for a variety of Ubuntu versions using the Advanced Packaging Tool (apt). -### pkg-config and setting the `PKG_CONFIG_PATH` variable +### Windows -Eventually, the MODFLOW build process has to resolve the installation location of all external dependencies. The pkg-config tool (https://en.wikipedia.org/wiki/Pkg-config) can be used to take care of that. For PETSc, you can check the contents of the folder +??? + +## Using pkg-config to check your PETSc installation + +Eventually, the MODFLOW build process has to resolve the installation location of all external dependencies. The pkg-config tool (https://en.wikipedia.org/wiki/Pkg-config) can be used to take care of that. + +``` +pkg-config --libs petsc +``` + +If PETSc was build from source, you can check the contents of the folder ``` $PETSC_DIR/$PETSC_ARCH/lib/pkgconfig/ ``` + and confirm that there are one or more `*.pc` files in there. A similar `pkgconfig/` folder has to present for the MPI installation that was used. For example, for Open MPI on WSL2 this folder is `/lib/x86_64-linux-gnu/pkgconfig/`. To connect everything, both of these folder paths have to be added to the `PKG_CONFIG_PATH` variable so that the `pkg-config` executable can resolve the installed libraries. -## Building + +## Building the parallel version of MODFLOW 6 The primary build system for MODFLOW is Meson (https://mesonbuild.com/). The `meson.build` script takes an additional argument to activate a parallel build of the software. E.g for building and installing a parallel release version: @@ -65,9 +95,9 @@ meson setup builddir -Ddebug=false -Dparallel=true \ meson install -C builddir meson test --verbose --no-rebuild -C builddir ``` -Note that changing the option flags in the `meson setup` command requires the flag `--reconfigure` to reconfigure the build directory. If the `PKG_CONFIG_PATH` was set as desribed above, the linking to PETSc and MPI is done automatically. +Note that changing the option flags in the `meson setup` command requires the flag `--reconfigure` to reconfigure the build directory. If the `PKG_CONFIG_PATH` was set as described above, the linking to PETSc and MPI is done automatically. -It's always a good idea to check your `mf6` executable to confirm that it is successfully linked against the external dependencies. You can use the command line tools `ldd` (Linux), `otool` (macOS), or `Dependencies.exe` (Windows, https://github.com/lucasg/Dependencies) to do that. In the list of dependencies, you should be able to identify `libpetsc` and `libmpi` for parallel builds. +It's always a good idea to check your `mf6pro` executable to confirm that it is successfully linked against the external dependencies. You can use the command line tools `ldd` (Linux), `otool` (macOS), or `Dependencies.exe` (Windows, https://github.com/lucasg/Dependencies) to do that. In the list of dependencies, you should be able to identify `libpetsc` and `libmpi` for parallel builds. The other build systems in the MODFLOW project (MS Visual Studio, `pymake`, `Makefile`) continue to be supported for *serial* builds only. `pymake` uses the `excludefiles.txt` to ignore those files that can only be build when MPI and PETSc are present on the system. In MS Visual Studio these same files are included in the solution but not in the build process. @@ -82,7 +112,7 @@ Parallel MODFLOW was designed to have all third party functionality (MPI and PET --- -## Testing parallel +## Testing the parallel of MODFLOW 6 Parallel MODFLOW can be tested using the same test framework as the serial program, with just a few modifications. To run a test inside the `autotest` folder in parallel mode, make sure to add a marker `@pytest.mark.parallel` so that the test is only executed in the Continuous Integration when running a configuration with a parallel build of MODFLOW. @@ -149,12 +179,14 @@ Make sure that you work with gdb versions >= 10. We have found that earlier vers Parallel MODFLOW has been built successfully with the following configurations: -| Operating System | Toolchain | MPI | PETSc | -|-----------------------|-------------|---------------|--------| -| MS Windows | ? | ? | ? | -| WSL2 (Ubuntu 20.04.5) | gcc 9.4.0 | OpenMPI 4.0.3 | 3.18.2 | -| Ubuntu 22.04 | gcc 9.5.0 | OpenMPI 4.1.4 | 3.18.5 | -| macOS 12.6.3 | gcc 9.5.0 | OpenMPI 4.1.4 | 3.18.5 | +| Operating System | Toolchain | MPI | PETSc | Package Manager | +|-----------------------|-------------|---------------|--------|-----------------| +| MS Windows | ? | ? | ? | NA | +| WSL2 (Ubuntu 20.04.5) | gcc 9.4.0 | OpenMPI 4.0.3 | 3.18.2 | NA | +| Ubuntu 22.04 | gcc 9.5.0 | OpenMPI 4.1.4 | 3.18.5 | NA | +| Ubuntu 23.04 | gcc 13 | OpenMPI 4.1.4 | 3.18.1 | apt | +| macOS 12.6.3 | gcc 9.5.0 | OpenMPI 4.1.4 | 3.18.5 | NA | +| macOS 12.6.6 | gcc 13.1.0 | OpenMPI 4.1.5 | 3.19.1 | Homebrew | The most up-to-date configurations are available in the GitHub CI script: `.github/workflows/ci.yml` under the task `parallel_test`. These are being tested upon every change to the `develop` branch of MODFLOW. diff --git a/doc/ReleaseNotes/ReleaseNotes.tex b/doc/ReleaseNotes/ReleaseNotes.tex index 9ac94960124..da515b57d5a 100644 --- a/doc/ReleaseNotes/ReleaseNotes.tex +++ b/doc/ReleaseNotes/ReleaseNotes.tex @@ -175,7 +175,7 @@ \section{Release History} 6.3.0 & March 4, 2022 & \url{https://doi.org/10.5066/P97FFF9M} \\ 6.4.0 & November 30, 2022 & \url{https://doi.org/10.5066/P9FL1JCC} \\ 6.4.1 & December 9, 2022 & \url{https://doi.org/10.5066/P9FL1JCC} \\ -6.5.0 & Month x, 202x & assigned at release \\ +6.4.2 & Month x, 202x & assigned at release \\ \hline \label{tab:releases} \end{tabular*} @@ -189,7 +189,7 @@ \section{Changes Introduced in this Release} This section describes changes introduced into MODFLOW~6 for the current release. These changes may substantially affect users. \begin{itemize} -\input{v6.5.0.tex} +\input{v6.4.2.tex} \end{itemize} % ------------------------------------------------- diff --git a/doc/ReleaseNotes/v6.5.0.tex b/doc/ReleaseNotes/v6.4.2.tex similarity index 100% rename from doc/ReleaseNotes/v6.5.0.tex rename to doc/ReleaseNotes/v6.4.2.tex diff --git a/doc/version.py b/doc/version.py index a4e12af98ec..bcb5e4e4fb1 100644 --- a/doc/version.py +++ b/doc/version.py @@ -2,6 +2,6 @@ # created on...December 09, 2022 20:57:08 major = 6 -minor = 5 -micro = 0 +minor = 4 +micro = 2 __version__ = '{:d}.{:d}.{:d}'.format(major, minor, micro) diff --git a/doc/version.tex b/doc/version.tex index ea81231afb8..61fea70fabc 100644 --- a/doc/version.tex +++ b/doc/version.tex @@ -1,3 +1,3 @@ -\newcommand{\modflowversion}{mf6.5.0rc} +\newcommand{\modflowversion}{mf6.4.2rc} \newcommand{\modflowdate}{December 09, 2022} \newcommand{\currentmodflowversion}{Version \modflowversion---\modflowdate} diff --git a/environment.yml b/environment.yml index 12633dc18a5..c989e3bc197 100644 --- a/environment.yml +++ b/environment.yml @@ -10,7 +10,7 @@ dependencies: - fprettify - jupytext - matplotlib - - meson!=0.63.0 + - meson>=1.1.0 - ninja - numpy - pip diff --git a/make/makefile b/make/makefile index 3c34055f856..552d7c70a1d 100644 --- a/make/makefile +++ b/make/makefile @@ -5,35 +5,35 @@ include ./makedefaults # Define the source file directories SOURCEDIR1=../src -SOURCEDIR2=../src/Distributed -SOURCEDIR3=../src/Exchange -SOURCEDIR4=../src/Model -SOURCEDIR5=../src/Model/Connection -SOURCEDIR6=../src/Model/Geometry -SOURCEDIR7=../src/Model/GroundWaterFlow -SOURCEDIR8=../src/Model/GroundWaterTransport -SOURCEDIR9=../src/Model/ModelUtilities -SOURCEDIR10=../src/Solution -SOURCEDIR11=../src/Solution/LinearMethods -SOURCEDIR12=../src/Solution/PETSc -SOURCEDIR13=../src/Timing -SOURCEDIR14=../src/Utilities +SOURCEDIR2=../src/Exchange +SOURCEDIR3=../src/Distributed +SOURCEDIR4=../src/Solution +SOURCEDIR5=../src/Solution/LinearMethods +SOURCEDIR6=../src/Solution/PETSc +SOURCEDIR7=../src/Timing +SOURCEDIR8=../src/Utilities +SOURCEDIR9=../src/Utilities/Idm +SOURCEDIR10=../src/Utilities/Idm/selector +SOURCEDIR11=../src/Utilities/Idm/mf6blockfile +SOURCEDIR12=../src/Utilities/TimeSeries +SOURCEDIR13=../src/Utilities/Memory +SOURCEDIR14=../src/Utilities/OutputControl SOURCEDIR15=../src/Utilities/ArrayRead -SOURCEDIR16=../src/Utilities/Idm -SOURCEDIR17=../src/Utilities/Idm/mf6blockfile -SOURCEDIR18=../src/Utilities/Idm/selector -SOURCEDIR19=../src/Utilities/Libraries -SOURCEDIR20=../src/Utilities/Libraries/blas -SOURCEDIR21=../src/Utilities/Libraries/daglib -SOURCEDIR22=../src/Utilities/Libraries/rcm -SOURCEDIR23=../src/Utilities/Libraries/sparsekit -SOURCEDIR24=../src/Utilities/Libraries/sparskit2 -SOURCEDIR25=../src/Utilities/Matrix -SOURCEDIR26=../src/Utilities/Memory -SOURCEDIR27=../src/Utilities/Observation -SOURCEDIR28=../src/Utilities/OutputControl -SOURCEDIR29=../src/Utilities/TimeSeries -SOURCEDIR30=../src/Utilities/Vector +SOURCEDIR16=../src/Utilities/Libraries +SOURCEDIR17=../src/Utilities/Libraries/rcm +SOURCEDIR18=../src/Utilities/Libraries/blas +SOURCEDIR19=../src/Utilities/Libraries/sparskit2 +SOURCEDIR20=../src/Utilities/Libraries/daglib +SOURCEDIR21=../src/Utilities/Libraries/sparsekit +SOURCEDIR22=../src/Utilities/Vector +SOURCEDIR23=../src/Utilities/Matrix +SOURCEDIR24=../src/Utilities/Observation +SOURCEDIR25=../src/Model +SOURCEDIR26=../src/Model/Connection +SOURCEDIR27=../src/Model/GroundWaterTransport +SOURCEDIR28=../src/Model/ModelUtilities +SOURCEDIR29=../src/Model/GroundWaterFlow +SOURCEDIR30=../src/Model/Geometry VPATH = \ ${SOURCEDIR1} \ @@ -74,11 +74,11 @@ $(OBJDIR)/kind.o \ $(OBJDIR)/Constants.o \ $(OBJDIR)/SimVariables.o \ $(OBJDIR)/genericutils.o \ +$(OBJDIR)/defmacro.o \ $(OBJDIR)/compilerversion.o \ $(OBJDIR)/ArrayHandlers.o \ $(OBJDIR)/version.o \ $(OBJDIR)/Message.o \ -$(OBJDIR)/defmacro.o \ $(OBJDIR)/Sim.o \ $(OBJDIR)/OpenSpec.o \ $(OBJDIR)/InputOutput.o \ diff --git a/meson.build b/meson.build index fb4bf0bdb35..faf8bd2185e 100644 --- a/meson.build +++ b/meson.build @@ -1,11 +1,13 @@ project( 'MODFLOW 6', 'fortran', - version: '6.5.0', - meson_version: '>= 0.59.0', + version: '6.4.2', + license: 'CC0', + meson_version: '>= 1.1.0', default_options : [ 'b_vscrt=static_from_buildtype', # Link runtime libraries statically on Windows 'optimization=2', + 'debug=false', 'fortran_std=f2008', ]) @@ -18,15 +20,9 @@ if get_option('optimization') == '2' else profile = 'develop' endif - -do_parallel_build = get_option('parallel') message('The used profile is:', profile) -message('Parallel build:', do_parallel_build) - -# windows options: -petsc_dir = meson.project_source_root() / '..' /'petsc-3.18.5' -petsc_arch = 'arch-ci-mswin-intel-modflow6' +# parse compiler options fc = meson.get_compiler('fortran') fc_id = fc.get_id() compile_args = [] @@ -50,7 +46,7 @@ if fc_id == 'gcc' if profile == 'release' compile_args += ['-ffpe-summary=overflow', '-ffpe-trap=overflow,zero,invalid'] elif profile == 'develop' - compile_args += ['-fcheck=all', '-ffpe-trap=overflow,zero,invalid,denormal'] + compile_args += ['-fcheck=all', '-ffpe-trap=overflow,zero,invalid'] endif # Define OS with gfortran for OS specific code @@ -91,45 +87,70 @@ elif fc_id == 'intel' link_args += '-static-intel' endif -if build_machine.system() == 'windows' - # directly look for petsc - petsc_compiled = petsc_dir / petsc_arch - petsc = fc.find_library('libpetsc', dirs: petsc_compiled / 'lib', required : false) -else - petsc = dependency('PETSc', required : false) + +# parallel build options +is_parallel_build = get_option('parallel') +is_cray = get_option('cray') +is_mpich = get_option('mpich') +if is_cray and build_machine.system() != 'linux' + error('cray=true only supported on linux systems') endif -mpi = dependency('mpi', language : 'fortran', required : petsc.found()) +if is_cray and not is_parallel_build + is_parallel_build = true + is_mpich = true +endif +if is_mpich + if is_cray + mpifort_name = 'mpichf90' + else + mpifort_name = 'mpichfort' + endif +endif +message('Parallel build:', is_parallel_build) + +# windows options for petsc +petsc_dir = meson.project_source_root() / '..' /'petsc-3.18.5' +petsc_arch = 'arch-ci-mswin-intel-modflow6' # on windows only with intel -enable_mpi = do_parallel_build -if build_machine.system() == 'windows' and do_parallel_build - if fc_id == 'intel-cl' - enable_mpi = true - else - message('Parallel build on Windows only with intel compiler, disabling...') - enable_mpi +if build_machine.system() == 'windows' and is_parallel_build + if fc_id != 'intel-cl' + error('Parallel build on Windows only with intel compiler. Terminating...') endif endif -# compile with mpi or petsc+mpi when found, -# (not allowing petsc without mpi) +# lists for parallel dependencies and compiler arguments dependencies = [ ] extra_cmp_args = [ ] -if mpi.found() and enable_mpi - extra_cmp_args = [ '-D__WITH_MPI__' ] - with_mpi = true - dependencies = [ mpi ] -else - with_mpi = false -endif - -if petsc.found() and with_mpi - extra_cmp_args = [ '-D__WITH_MPI__', '-D__WITH_PETSC__' ] +# load petsc and mpi dependencies/libraries +if is_parallel_build + # find petsc + if build_machine.system() != 'windows' + petsc = dependency('PETSc', required : true) + else + # directly look for petsc + petsc_compiled = petsc_dir / petsc_arch + petsc = fc.find_library('libpetsc', dirs: petsc_compiled / 'lib', required : true) + endif + extra_cmp_args += [ '-D__WITH_PETSC__' ] + dependencies += petsc with_petsc = true - dependencies = [ mpi, petsc ] + + # find mpi + if is_mpich + mpi = dependency('mpich', required : true) + mpifort = dependency(mpifort_name, required : mpi.found()) + dependencies += [ mpi, mpifort ] + else + mpi = dependency('mpi', language : 'fortran', required : true) + dependencies += mpi + endif + extra_cmp_args += [ '-D__WITH_MPI__'] + with_mpi = true else with_petsc = false + with_mpi = false endif compile_args += extra_cmp_args @@ -137,8 +158,8 @@ compile_args += extra_cmp_args add_project_arguments(fc.get_supported_arguments(compile_args), language: 'fortran') add_project_link_arguments(fc.get_supported_arguments(link_args), language: 'fortran') -if with_petsc and build_machine.system() == 'windows' - message('Compiling PETSc Fortran modudules') +if is_parallel_build and build_machine.system() == 'windows' + message('Compiling PETSc Fortran modules') petsc_incdir = include_directories([petsc_dir / 'include', petsc_compiled / 'include']) petsc_src = petsc_dir / 'src' sources_petsc = [petsc_src / 'dm/f90-mod/petscdmdamod.F90', @@ -161,14 +182,31 @@ if with_petsc and build_machine.system() == 'windows' include_directories: petsc_incdir) endif +# build mf6 and libmf6 +buildname = get_option('buildname') subdir('src') subdir('srcbmi') + +# build zbud6 and mf5to6 utility programs subdir('utils') # add unit test directory # subdir('unittests') # meson tests to evaluate installation success -test('Test installation version', mf6exe, args : ['-v',]) -test('Test installation compiler', mf6exe, args : ['-c',]) -test('Test installation help', mf6exe, args : ['-h',]) +testdir = meson.project_source_root() / '.mf6minsim' +if with_mpi + mpiexec = find_program('mpiexec', required : false) + if mpiexec.found() + test('Parallel version command line test', mpiexec, args : ['-n', '2', mf6exe, '-v', '-p']) + test('Parallel compiler command line test', mpiexec, args : ['-n', '2', mf6exe, '-c', '-p']) + test('Serial simulation test', mf6exe, workdir : testdir) + test('Parallel simulation test - 1 core', mpiexec, workdir : testdir, args : ['-n', '1', mf6exe, '-p']) + test('Parallel simulation test - 2 cores', mpiexec, workdir : testdir, args : ['-n', '2', mf6exe, '-p']) + endif +else + test('Version command line test', mf6exe, args : ['-v',]) + test('Compiler command line test', mf6exe, args : ['-c',]) + test('Test installation help', mf6exe, args : ['-h',]) + test('Serial simulation test', mf6exe, workdir : testdir) +endif diff --git a/meson.options b/meson.options new file mode 100644 index 00000000000..1ac66580078 --- /dev/null +++ b/meson.options @@ -0,0 +1,4 @@ +option('parallel', type : 'boolean', value : false, description : 'Parallel build') +option('mpich', type : 'boolean', value : false, description : 'Use MPICH version of MPI') +option('cray', type : 'boolean', value : false, description : 'Parallel build on CRAY with MPICH') +option('buildname', type : 'string', value : 'mf6', description : 'Optional build name') diff --git a/meson_options.txt b/meson_options.txt deleted file mode 100644 index 2fd9c44ad78..00000000000 --- a/meson_options.txt +++ /dev/null @@ -1 +0,0 @@ -option('parallel', type : 'boolean', value : false) \ No newline at end of file diff --git a/src/Utilities/comarg.f90 b/src/Utilities/comarg.f90 index 1fc3eb20801..d8379d8888c 100644 --- a/src/Utilities/comarg.f90 +++ b/src/Utilities/comarg.f90 @@ -4,7 +4,7 @@ module CommandArguments VSUMMARY, VALL, VDEBUG, & MVALIDATE use VersionModule, only: VERSION, MFVNAM, IDEVELOPMODE, & - FMTDISCLAIMER, FMTLICENSE + FMTDISCLAIMER, write_license use CompilerVersion use SimVariablesModule, only: istdout, isim_level, & simfile, simlstfile, simstdout, & @@ -167,7 +167,7 @@ subroutine GetCommandLineArguments() simulation_mode = 'PARALLEL' case ('-LIC', '--LICENSE') lstop = .TRUE. - call sim_message('', fmt=FMTLICENSE) + call write_license() case ('-CO', '--COMPILER-OPT') lstop = .TRUE. call get_compile_options(coptions) diff --git a/src/Utilities/defmacro.F90 b/src/Utilities/defmacro.F90 index 6e532cbd188..eef048079ec 100644 --- a/src/Utilities/defmacro.F90 +++ b/src/Utilities/defmacro.F90 @@ -1,10 +1,10 @@ module DefinedMacros ! -- modules - use KindModule, only: I4B + use KindModule, only: LGP, I4B use ConstantsModule, only: OSUNDEF, OSLINUX, OSMAC, OSWIN implicit none private - public :: get_os + public :: get_os, is_pro, using_petsc contains !> @brief Get operating system @@ -48,4 +48,45 @@ function get_os() result(ios) return end function get_os + !> @brief Determine if this is the professional version + !! + !! Function to get a logical indicating if this is the + !! professional version of MODFLOW. + !! + !! @return ispro pro version logical + !< + function is_pro() result(ispro) + ! -- local variables + logical(LGP) :: ispro !< pro version logical + ! + ! -- check if using petsc + ispro = using_petsc() + ! + ! return + return + end function is_pro + + !> @brief Determine if using petsc + !! + !! Function to get a logical indicating if petsc is + !! being used. + !! + !! @return petscavail petsc used logical + !< + function using_petsc() result(petscused) + ! -- local variables + logical(LGP) :: petscused !< petsc used logical + ! + ! -- initialize petscavail + petscused = .FALSE. + ! + ! -- set operating system variables +#ifdef __WITH_PETSC__ + petscused = .TRUE. +#endif + ! + ! return + return + end function using_petsc + end module DefinedMacros diff --git a/src/Utilities/version.f90 b/src/Utilities/version.f90 index 08b08afb624..082fb43e53b 100644 --- a/src/Utilities/version.f90 +++ b/src/Utilities/version.f90 @@ -7,25 +7,21 @@ module VersionModule ! -- module imports use KindModule + use DefinedMacros, only: is_pro, using_petsc use ConstantsModule, only: LENBIGLINE, LENHUGELINE, DZERO use SimVariablesModule, only: istdout - use GenericUtilitiesModule, only: write_centered, write_message + use GenericUtilitiesModule, only: write_centered, write_message, sim_message use CompilerVersion, only: get_compiler, get_compile_options implicit none public ! -- modflow 6 version integer(I4B), parameter :: IDEVELOPMODE = 1 - character(len=*), parameter :: VERSIONNUMBER = '6.5.0' + character(len=*), parameter :: VERSIONNUMBER = '6.4.2' character(len=*), parameter :: VERSIONTAG = ' Release Candidate 12/09/2022' character(len=40), parameter :: VERSION = VERSIONNUMBER//VERSIONTAG - character(len=10), parameter :: MFVNAM = ' 6' + character(len=2), parameter :: MFVNAM = ' 6' character(len=*), parameter :: MFTITLE = & &'U.S. GEOLOGICAL SURVEY MODULAR HYDROLOGIC MODEL' - character(len=*), parameter :: FMTTITLE = & - "(/,34X,'MODFLOW',A,/,& - &16X,'U.S. GEOLOGICAL SURVEY MODULAR HYDROLOGIC MODEL',& - &/,23X,'Version ',A/)" - ! -- license for MODFLOW and libraries character(len=*), parameter :: FMTLICENSE = & "(/,& &'As a work of the United States Government, this USGS product is ',/,& @@ -55,6 +51,16 @@ module VersionModule &' All rights reserved.',/,& &' (https://github.com/jacobwilliams/daglib)',/& &)" + character(len=*), parameter :: PETSCLICENSE = & + "(& + &'The following 2-clause BSD License library is used in this',/,& + &'USGS product:',//,& + &' PETSc, the Portable, Extensible Toolkit for Scientific',/,& + &' Computation Library',/,& + &' Copyright (c) 1991-2021, UChicago Argonne, LLC',/,& + &' and the PETSc Development Team All rights reserved.',/,& + &' (https://petsc.org/release/)',/& + &)" ! -- disclaimer must be appropriate for version (release or release candidate) character(len=*), parameter :: FMTDISCLAIMER = & "(/,& @@ -72,9 +78,9 @@ module VersionModule contains !> @ brief Write program header - !! - !! Write header for program to the program listing file. - !! + !! + !! Write header for program to the program listing file. + !! !< subroutine write_listfile_header(iout, cmodel_type, write_sys_command, & write_kind_info) @@ -84,15 +90,23 @@ subroutine write_listfile_header(iout, cmodel_type, write_sys_command, & logical(LGP), intent(in), optional :: write_sys_command !< boolean indicating if the system command should be written logical(LGP), intent(in), optional :: write_kind_info !< boolean indicating in program data types should be written ! -- local variables + integer(I4B), parameter :: iheader_width = 80 + character(len=22) :: cheader character(len=LENBIGLINE) :: syscmd character(len=LENBIGLINE) :: compiler character(len=LENBIGLINE) :: compiler_options - integer(I4B) :: iheader_width = 80 logical(LGP) :: wki logical(LGP) :: wsc ! - ! -- Write title to list file - call write_centered('MODFLOW'//MFVNAM, iheader_width, iunit=iout) + ! -- set pro string + if (is_pro()) then + write (cheader, '(3a)') 'MODFLOW', MFVNAM, ' PROFESSIONAL' + else + write (cheader, '(2a)') 'MODFLOW', MFVNAM + end if + ! + ! -- Write title to iout + call write_centered(cheader, iheader_width, iunit=iout) call write_centered(MFTITLE, iheader_width, iunit=iout) ! ! -- Write model type to list file @@ -118,7 +132,7 @@ subroutine write_listfile_header(iout, cmodel_type, write_sys_command, & ! ! -- Write license information if (iout /= istdout) then - write (iout, FMTLICENSE) + call write_license(iout) end if ! ! -- write compiler options @@ -149,5 +163,34 @@ subroutine write_listfile_header(iout, cmodel_type, write_sys_command, & return end subroutine write_listfile_header + !> @ brief Write program license + !! + !! Write license for program to the program listing file. + !! + !< + subroutine write_license(iout) + ! -- dummy variables + integer(I4B), intent(in), optional :: iout !< program listing file + ! + ! - write standard license + if (present(iout)) then + write (iout, FMTLICENSE) + else + call sim_message('', fmt=FMTLICENSE) + end if + ! + ! -- write PETSc license + if (using_petsc()) then + if (present(iout)) then + write (iout, PETSCLICENSE) + else + call sim_message('', fmt=PETSCLICENSE) + end if + end if + ! + ! -- return + return + end subroutine write_license + end module VersionModule diff --git a/src/meson.build b/src/meson.build index 0f1156ff9e3..5f57e065d7b 100644 --- a/src/meson.build +++ b/src/meson.build @@ -257,13 +257,15 @@ endif mf6_external = static_library('mf6_external', external_libraries) +message('MODFLOW 6 executable name: ' + buildname) + if build_machine.system() == 'windows' and with_petsc mf6core = static_library('mf6core', modflow_sources, dependencies: dependencies, link_with: [mf6_external], include_directories: petsc_incdir) - mf6exe = executable('mf6', + mf6exe = executable(buildname, 'mf6.f90', link_with: [mf6core], dependencies: dependencies, @@ -273,7 +275,7 @@ else modflow_sources, dependencies: dependencies, link_with: [mf6_external]) - mf6exe = executable('mf6', + mf6exe = executable(buildname, 'mf6.f90', link_with: [mf6core], dependencies: dependencies, diff --git a/srcbmi/meson.build b/srcbmi/meson.build index 1e2f6d5d381..7f2de62df36 100644 --- a/srcbmi/meson.build +++ b/srcbmi/meson.build @@ -7,4 +7,6 @@ bmi_sources = files( 'mf6xmi.F90', ) -library('mf6', bmi_sources, link_with: mf6core, name_prefix: 'lib', install: true) +message('MODFLOW 6 shared library name:', 'lib' + buildname) + +library(buildname, bmi_sources, link_with: mf6core, name_prefix: 'lib', install: true) diff --git a/utils/meson.build b/utils/meson.build index 4ed5f4a58b7..7a6eb67c7ba 100644 --- a/utils/meson.build +++ b/utils/meson.build @@ -6,5 +6,10 @@ elif fc_id == 'intel-cl' util_args += '/w' endif -subdir('mf5to6') +# MODFLOW 5 to 6 converter - DO NOT BUILD ON CRAY +if not is_cray + subdir('mf5to6') +endif + +# ZONEBUDGET 6 subdir('zonebudget') diff --git a/utils/mf5to6/make/makefile b/utils/mf5to6/make/makefile index e1c0b88e1e3..d8eb7e6963d 100644 --- a/utils/mf5to6/make/makefile +++ b/utils/mf5to6/make/makefile @@ -5,10 +5,10 @@ include ./makedefaults # Define the source file directories SOURCEDIR1=../src -SOURCEDIR2=../src/LGR -SOURCEDIR3=../src/MF2005 -SOURCEDIR4=../src/NWT -SOURCEDIR5=../src/Preproc +SOURCEDIR2=../src/NWT +SOURCEDIR3=../src/LGR +SOURCEDIR4=../src/Preproc +SOURCEDIR5=../src/MF2005 SOURCEDIR6=../../../src/Utilities/Memory SOURCEDIR7=../../../src/Utilities/TimeSeries SOURCEDIR8=../../../src/Utilities @@ -30,6 +30,7 @@ $(OBJDIR)/kind.o \ $(OBJDIR)/Constants.o \ $(OBJDIR)/SimVariables.o \ $(OBJDIR)/genericutils.o \ +$(OBJDIR)/defmacro.o \ $(OBJDIR)/compilerversion.o \ $(OBJDIR)/version.o \ $(OBJDIR)/OpenSpec.o \ diff --git a/utils/mf5to6/meson.build b/utils/mf5to6/meson.build index 427258008e8..0a937a0271d 100644 --- a/utils/mf5to6/meson.build +++ b/utils/mf5to6/meson.build @@ -136,7 +136,10 @@ mf5to6_sources = files( 'src' / 'WelPackageWriter.f90', ) -executable('mf5to6', +mf6convname = 'mf5to6' +message('MODFLOW 5 to 6 converter name:', mf6convname) + +executable(mf6convname, mf5to6_sources, link_with: mf6core, install: true, diff --git a/utils/mf5to6/pymake/extrafiles.txt b/utils/mf5to6/pymake/extrafiles.txt index 658c95a6c60..1699e51ed46 100644 --- a/utils/mf5to6/pymake/extrafiles.txt +++ b/utils/mf5to6/pymake/extrafiles.txt @@ -14,6 +14,7 @@ ../../../src/Utilities/kind.f90 ../../../src/Utilities/List.f90 ../../../src/Utilities/OpenSpec.f90 +../../../src/Utilities/defmacro.F90 ../../../src/Utilities/version.f90 ../../../src/Utilities/Table.f90 ../../../src/Utilities/TableTerm.f90 diff --git a/utils/zonebudget/make/makefile b/utils/zonebudget/make/makefile index 5ba239e2b62..092dc3b0294 100644 --- a/utils/zonebudget/make/makefile +++ b/utils/zonebudget/make/makefile @@ -18,11 +18,11 @@ $(OBJDIR)/kind.o \ $(OBJDIR)/Constants.o \ $(OBJDIR)/SimVariables.o \ $(OBJDIR)/genericutils.o \ +$(OBJDIR)/defmacro.o \ $(OBJDIR)/compilerversion.o \ $(OBJDIR)/ArrayHandlers.o \ $(OBJDIR)/version.o \ $(OBJDIR)/Message.o \ -$(OBJDIR)/defmacro.o \ $(OBJDIR)/Sim.o \ $(OBJDIR)/OpenSpec.o \ $(OBJDIR)/InputOutput.o \ diff --git a/utils/zonebudget/meson.build b/utils/zonebudget/meson.build index b12534ad90a..1c9d4fb3e43 100644 --- a/utils/zonebudget/meson.build +++ b/utils/zonebudget/meson.build @@ -6,7 +6,11 @@ zonebudget_sources = files( 'src' / 'zoneoutput.f90', ) -executable('zbud6', + +zbud6name = 'zbud6' +message('ZONEBUDGET 6 executable name: ' + zbud6name + '\n') + +executable(zbud6name, zonebudget_sources, link_with: mf6core, install: true, diff --git a/version.txt b/version.txt index a4e12af98ec..bcb5e4e4fb1 100644 --- a/version.txt +++ b/version.txt @@ -2,6 +2,6 @@ # created on...December 09, 2022 20:57:08 major = 6 -minor = 5 -micro = 0 +minor = 4 +micro = 2 __version__ = '{:d}.{:d}.{:d}'.format(major, minor, micro) From 01954755e68a5aa3ea6be2e54647ac7ae4d3500c Mon Sep 17 00:00:00 2001 From: w-bonelli Date: Mon, 19 Jun 2023 12:55:03 -0400 Subject: [PATCH 107/123] ci: refactor release workflow inputs (#1257) * refactor release.yml inputs: developmode, approve, full * use full: true in release_dispatch.yml * corresponding updates to distribution scripts * remove path fix for ifort/mamba in workflows * update release docs --- .github/workflows/ci.yml | 8 - .github/workflows/release.yml | 225 ++++++++++++++++--------- .github/workflows/release_dispatch.yml | 11 +- distribution/README.md | 26 +-- distribution/build_dist.py | 40 +++-- distribution/build_docs.py | 23 ++- distribution/check_dist.py | 77 ++++++--- distribution/conftest.py | 4 +- distribution/update_version.py | 70 ++++++-- 9 files changed, 306 insertions(+), 178 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8825cafe9a6..75c7b43d4b6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -364,14 +364,6 @@ jobs: - name: Setup Intel Fortran uses: modflowpy/install-intelfortran-action@v1 - - name: Fix Micromamba path (Windows) - if: runner.os == 'Windows' - shell: pwsh - run: | - # https://github.com/modflowpy/install-intelfortran-action#conda-scripts - $mamba_bin = "C:\Users\runneradmin\micromamba-root\envs\modflow6\Scripts" - echo $mamba_bin | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append - - name: Update version files working-directory: modflow6/distribution run: python update_version.py diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 14fdbbdf208..69e5d579e84 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -5,21 +5,26 @@ on: workflow_call: inputs: approve: - description: 'Whether to approve the release. Default is false for preliminary/provisional releases. Approval is only relevant for full builds: if development is true, approve is not considered.' + description: 'Approve the release, modifying disclaimer language to indicate the distribution has been reviewed. If false, disclaimers & version strings indicate preliminary/provisional status.' required: false type: boolean default: false branch: - description: 'Branch to use for release.' + description: 'Branch to release from.' required: true type: string - development: - description: 'Toggle a minimal development build. Default is false, i.e. to perform a full release build. If true, approve is not considered.' + developmode: + description: 'Build binaries in develop mode. If false, IDEVELOPMODE is set to 0.' + required: false + type: boolean + default: true + full: + description: 'Build a full distribution containing sources, examples, and all documentation. If false, the distribution contains only binaries, mf6io, release notes, and code.json.' required: false type: boolean default: false run_tests: - description: 'Whether to run tests after building binaries. Default is true. Can be set to false for rapid testing/debugging, or for a faster nightly/development build.' + description: Run tests after building binaries.' required: false type: boolean default: true @@ -67,22 +72,16 @@ jobs: - name: Setup Micromamba uses: mamba-org/setup-micromamba@v1 with: - micromamba-root-path: ${{ github.workspace }}/micromamba-root environment-file: modflow6/environment.yml cache-downloads: true cache-environment: true + init-shell: >- + bash + powershell - name: Setup Intel Fortran uses: modflowpy/install-intelfortran-action@v1 - - name: Fix Micromamba path (Windows) - if: runner.os == 'Windows' - shell: pwsh - run: | - # https://github.com/modflowpy/install-intelfortran-action#conda-scripts - $mamba_bin = "${{ github.workspace }}\micromamba-root\envs\modflow6\Scripts" - echo $mamba_bin | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append - - name: Set version number id: set_version run: | @@ -100,11 +99,14 @@ jobs: working-directory: modflow6/distribution run: | ver="${{ steps.set_version.outputs.version }}" - if [[ ("${{ inputs.development }}" == "false") && ("${{ inputs.approve }}" == "true") ]]; then - python update_version.py -v "$ver" --approve - else - python update_version.py -v "$ver" + cmd="python update_version.py -v $ver" + if [[ "${{ inputs.approve }}" == "true" ]]; then + cmd="$cmd --approve" + fi + if [[ "${{ inputs.developmode }}" == "false" ]]; then + cmd="$cmd --releasemode" fi + eval "$cmd" - name: Build binaries if: runner.os != 'Windows' @@ -157,17 +159,29 @@ jobs: run: pytest -v --durations 0 get_exes.py - name: Test modflow6 - if: inputs.run_tests == true + if: inputs.run_tests == true && runner.os != 'Windows' working-directory: modflow6/autotest env: REPOS_PATH: ${{ github.workspace }} run: | - marker="not large" - if [[ "${{ inputs.development }}" == "false" ]]; then - marker="$marker and not developmode" + markers="not large" + if [[ "${{ inputs.developmode }}" == "false" ]]; then + markers="$markers and not developmode" fi - - pytest -v -n auto --durations 0 -m "$marker" + pytest -v -n auto --durations 0 -m "$markers" + + - name: Test modflow6 (Windows) + if: inputs.run_tests == true && runner.os == 'Windows' + working-directory: modflow6/autotest + shell: pwsh + env: + REPOS_PATH: ${{ github.workspace }} + run: | + $markers="not large" + if ("${{ inputs.developmode }}" -eq "false") { + $markers="$markers and not developmode" + } + pytest -v -n auto --durations 0 -m "$markers" # steps below run only on Linux to test distribution procedures, e.g. # compiling binaries, building documentation @@ -272,11 +286,14 @@ jobs: working-directory: modflow6/distribution run: | ver="${{ needs.build.outputs.version }}" - if [[ ("${{ inputs.development }}" == "false") && ("${{ inputs.approve }}" == "true") ]]; then - python update_version.py -v "$ver" --approve - else - python update_version.py -v "$ver" + cmd="python update_version.py -v $ver" + if [[ "${{ inputs.approve }}" == "true" ]]; then + cmd="$cmd --approve" fi + if [[ "${{ inputs.developmode }}" == "false" ]]; then + cmd="$cmd --releasemode" + fi + eval "$cmd" - name: Download pre-built binaries uses: actions/download-artifact@v3 @@ -305,7 +322,8 @@ jobs: working-directory: modflow6-examples/scripts run: python ex-gwf-twri.py - - name: Create directory structure + - name: Create full docs folder structure + if: inputs.full == true run: | distname=${{ needs.build.outputs.distname }} @@ -328,7 +346,14 @@ jobs: env: # need a GITHUB_TOKEN to download example doc PDF asset from modflow6-examples repo GITHUB_TOKEN: ${{ github.token }} - run: python modflow6/distribution/build_docs.py -b bin -o doc + run: | + mkdir -p "${{ needs.build.outputs.distname }}/doc" + cmd="python modflow6/distribution/build_docs.py -b bin -o doc" + if [[ "${{ inputs.full }}" == "true" ]]; then + cmd="$cmd --full" + fi + eval "$cmd" + mv "doc/ReleaseNotes.pdf" "doc/release.pdf" - name: Upload documentation artifact uses: actions/upload-artifact@v3 @@ -372,32 +397,29 @@ jobs: - name: Setup Micromamba uses: mamba-org/setup-micromamba@v1 with: - micromamba-root-path: ${{ github.workspace }}/micromamba-root environment-file: modflow6/environment.yml cache-downloads: true cache-environment: true + init-shell: >- + bash + powershell - name: Setup Intel Fortran uses: modflowpy/install-intelfortran-action@v1 - - - name: Fix Micromamba path (Windows) - if: runner.os == 'Windows' - shell: pwsh - run: | - # https://github.com/modflowpy/install-intelfortran-action#conda-scripts - $mamba_bin = "${{ github.workspace }}\micromamba-root\envs\modflow6\Scripts" - echo $mamba_bin | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append - name: Update version id: update_version working-directory: modflow6/distribution run: | ver="${{ needs.build.outputs.version }}" - if [[ ("${{ inputs.development }}" == "false") && ("${{ inputs.approve }}" == "true") ]]; then - python update_version.py -v "$ver" --approve - else - python update_version.py -v "$ver" + cmd="python update_version.py -v $ver" + if [[ "${{ inputs.approve }}" == "true" ]]; then + cmd="$cmd --approve" + fi + if [[ "${{ inputs.developmode }}" == "false" ]]; then + cmd="$cmd --releasemode" fi + eval "$cmd" - name: Download artifacts uses: actions/download-artifact@v3 @@ -423,63 +445,102 @@ jobs: run: | # build distribution distname="${{ needs.build.outputs.distname }}_${{ matrix.ostag }}" - python modflow6/distribution/build_dist.py -o "$distname" -e modflow6-examples + cmd="python modflow6/distribution/build_dist.py -o $distname -e modflow6-examples" + if [[ "${{ inputs.full }}" == "true" ]]; then + cmd="$cmd --full" + fi + eval "$cmd" # move & rename PDF documents to dist folder - mv "$distname/doc/ReleaseNotes.pdf" "$distname/doc/release.pdf" - mv "$distname/doc/converter_mf5to6.pdf" "$distname/doc/mf5to6.pdf" + + if [[ "${{ inputs.full }}" == "true" ]]; then + mv "$distname/doc/converter_mf5to6.pdf" "$distname/doc/mf5to6.pdf" + fi - name: Zip distribution if: runner.os != 'Windows' run: | distname="${{ needs.build.outputs.distname }}_${{ matrix.ostag }}" - zip -r $distname.zip \ - $distname/bin \ - $distname/src \ - $distname/srcbmi \ - $distname/doc \ - $distname/examples \ - $distname/make \ - $distname/msvs \ - $distname/utils \ - $distname/code.json \ - $distname/meson.build \ - -x '*.DS_Store' \ - -x '*libmf6.lib' \ - -x '*idmloader*' \ - -x '*pymake*' \ - -x '*obj_temp*' \ - -x '*mod_temp*' + if [[ "${{ inputs.full }}" == "true" ]]; then + zip -r $distname.zip \ + $distname/bin \ + $distname/src \ + $distname/srcbmi \ + $distname/doc \ + $distname/examples \ + $distname/make \ + $distname/msvs \ + $distname/utils \ + $distname/code.json \ + $distname/meson.build \ + -x '*.DS_Store' \ + -x '*libmf6.lib' \ + -x '*idmloader*' \ + -x '*pymake*' \ + -x '*obj_temp*' \ + -x '*mod_temp*' + else + zip -r $distname.zip \ + $distname/bin/* \ + $distname/doc/mf6io.pdf \ + $distname/doc/release.pdf \ + $distname/code.json \ + -x '*.DS_Store' \ + -x '*libmf6.lib' \ + -x '*idmloader*' \ + -x '*pymake*' \ + -x '*obj_temp*' \ + -x '*mod_temp*' + fi - name: Zip distribution (Windows) if: runner.os == 'Windows' run: | distname="${{ needs.build.outputs.distname }}_${{ matrix.ostag }}" - 7z a -tzip $distname.zip \ - $distname/bin \ - $distname/src \ - $distname/srcbmi \ - $distname/doc \ - $distname/examples \ - $distname/make \ - $distname/msvs \ - $distname/utils \ - $distname/code.json \ - $distname/meson.build \ - -xr!libmf6.lib \ - -xr!idmloader \ - -xr!pymake \ - -xr!obj_temp \ - -xr!mod_temp + if [[ "${{ inputs.full }}" == "true" ]]; then + 7z a -tzip $distname.zip \ + $distname/bin \ + $distname/src \ + $distname/srcbmi \ + $distname/doc \ + $distname/examples \ + $distname/make \ + $distname/msvs \ + $distname/utils \ + $distname/code.json \ + $distname/meson.build \ + -xr!libmf6.lib \ + -xr!idmloader \ + -xr!pymake \ + -xr!obj_temp \ + -xr!mod_temp + else + 7z a -tzip $distname.zip \ + $distname/bin/* \ + $distname/doc/mf6io.pdf \ + $distname/doc/release.pdf \ + $distname/code.json \ + -xr!libmf6.lib \ + -xr!idmloader \ + -xr!pymake \ + -xr!obj_temp \ + -xr!mod_temp + fi # validate only after zipping distribution to avoid accidentally changing any files - name: Validate distribution run: | + cmd="pytest -v -s modflow6/distribution/check_dist.py --path ${{ needs.build.outputs.distname }}_${{ matrix.ostag }}" if [[ "${{ inputs.approve }}" == "true" ]]; then - pytest -v -s modflow6/distribution/check_dist.py --path "${{ needs.build.outputs.distname }}_${{ matrix.ostag }}" --approved - else - pytest -v -s modflow6/distribution/check_dist.py --path "${{ needs.build.outputs.distname }}_${{ matrix.ostag }}" + cmd="$cmd --approved" + fi + if [[ "${{ inputs.developmode }}" == "false" ]]; then + cmd="$cmd --releasemode" + fi + if [[ "${{ inputs.full }}" == "true" ]]; then + cmd="$cmd --full" fi + eval "$cmd" - name: Upload distribution zipfile artifact uses: actions/upload-artifact@v3 diff --git a/.github/workflows/release_dispatch.yml b/.github/workflows/release_dispatch.yml index 6dff66b60d6..55704c0e6cd 100644 --- a/.github/workflows/release_dispatch.yml +++ b/.github/workflows/release_dispatch.yml @@ -105,13 +105,16 @@ jobs: # If triggered by pushing a release branch, the release is approved if the branch name doesn't contain "rc". approve: ${{ (github.event_name == 'workflow_dispatch' && inputs.approve == 'true') || (github.event_name != 'workflow_dispatch' && !contains(github.ref_name, 'rc')) }} branch: ${{ needs.set_options.outputs.branch }} - development: false + developmode: false + full: true run_tests: ${{ inputs.run_tests == '' || inputs.run_tests == 'true' }} version: ${{ needs.set_options.outputs.version }} pr: name: Draft release PR if: ${{ github.event_name == 'push' && github.ref_name != 'master' && (github.event_name == 'workflow_dispatch' && inputs.approve == 'true') || (github.event_name != 'workflow_dispatch' && !contains(github.ref_name, 'rc')) }} - needs: make_dist + needs: + - set_options + - make_dist runs-on: ubuntu-22.04 permissions: contents: write @@ -137,7 +140,7 @@ jobs: working-directory: distribution run: | # update version files - ver="${{ needs.make_dist.outputs.version }}" + ver="${{ needs.set_options.outputs.version }}" if [[ "${{ inputs.approve }}" == "true" ]]; then python update_version.py -v "$ver" --approve else @@ -159,7 +162,7 @@ jobs: env: GITHUB_TOKEN: ${{ github.token }} run: | - ver="${{ needs.make_dist.outputs.version }}" + ver="${{ needs.set_options.outputs.version }}" body=' # MODFLOW '$ver' release diff --git a/distribution/README.md b/distribution/README.md index 7ab0eeb8b92..f0ca205f37d 100644 --- a/distribution/README.md +++ b/distribution/README.md @@ -5,11 +5,12 @@ This folder contains scripts to automate MODFLOW 6 distribution tasks. + - [Overview](#overview) - [Requirements](#requirements) - [Testing](#testing) - [Release procedures](#release-procedures) - - [Preparing a nightly release](#preparing-a-nightly-release) + - [Preparing a minimal development release](#preparing-a-minimal-development-release) - [Preparing an official release](#preparing-an-official-release) - [Updating version info](#updating-version-info) - [Building makefiles](#building-makefiles) @@ -17,9 +18,13 @@ This folder contains scripts to automate MODFLOW 6 distribution tasks. - [Benchmarking example models](#benchmarking-example-models) - [Building documentation](#building-documentation) - [Building the distribution archive](#building-the-distribution-archive) + - [Verifying the distribution archive](#verifying-the-distribution-archive) - [Release automation](#release-automation) - [Nightly builds](#nightly-builds) - [Official releases](#official-releases) + - [Triggering with a release branch](#triggering-with-a-release-branch) + - [Triggering a release manually](#triggering-a-release-manually) + - [Release versioning](#release-versioning) @@ -88,22 +93,21 @@ Full distributions, on the other hand, contain the items listed above, as well a ### Preparing a minimal development release -Development releases are built and [posted nightly on the `MODFLOW-USGS/modflow6-nightly-build` repository](https://github.com/MODFLOW-USGS/modflow6-nightly-build/releases). Release assets include: +Development releases are built and [posted nightly on the `MODFLOW-USGS/modflow6-nightly-build` repository](https://github.com/MODFLOW-USGS/modflow6-nightly-build/releases). For a minimal release, distribution contents include: - platform-specific distributions containing only executables `mf6`, `zbud6`, `mf5to6` and library `libmf6` - MODFLOW 6 input/output documentation +- release notes +- `code.json` metadata -The `build_dist.py` script can be used to create both development and full distributions. To create a development distribution, run the script with the `--development` (short `-d`) flag: +The `build_dist.py` script can be used to create both minimal and full distributions. By default, a minimal distribution is created. To create a full distribution, run the script with the `--full` flag: -```shell -python build_dist.py -d -``` - -The script has several optional command line arguments: +The script has several other arguments: - `--build-path`: path to the build workspace, defaults to `/builddir` - `--output-path (-o)`: path to create a distribution zipfile, defaults to `/distribution/` - `--examples-repo-path (-e)`: path to the [`MODFLOW-USGS/modflow6-examples`](https://github.com/MODFLOW-USGS/modflow6-examples) repository, defaults to `modflow6-examples` side-by-side with project root +- `--force (-f)`: whether to recreate and overwrite preexisting components of the distribution, if they already exist Default paths are resolved relative to the script's location on the filesystem, *not* the current working directory, so the script can be run from `distribution/`, from the project root, or from anywhere else. (This is true of all scripts in the `distribution/` directory.) @@ -133,7 +137,9 @@ python update_version.py -v 6.4.2rc The label must start immediately following the patch version number, with no space in between. The label may contain numeric characters or symbols, but *must not* start with a number (otherwise there is no way to distinguish it from the patch version number). -The `--approve` (short `-a`) option can be used to approve an official release. If the `--approve` option is provided, `IDEVELOPMODE` is set to 0. If `--approve` is not provided, `IDEVELOPMODE = 1` and `(preliminary)` is appended to version numbers. +The `--approved` (short `-a`) flag can be used to approve an official release. If the `--approved` flag is provided, disclaimer language is altered to reflect approval. If the flag is not provided, the language reflects preliminary/provisional status and `(preliminary)` is appended to version numbers. + +The `--releasemode` flag can be used to control whether binaries are built in development or release mode by editing the contents of `src/Utilities/version.f90`. If the `--releasemode` flag is provided, `IDEVELOPMODE` is set to 0. If `--releasemode` is not provided, `IDEVELOPMODE` is set to 1. #### Building makefiles @@ -174,7 +180,7 @@ Manually building MODFLOW 6 documentation requires additional Python dependencie #### Building the distribution archive -After each step above is complete, the `build_dist.py` script can be used (without the `--development` flag) to bundle MODFLOW 6 official release artifacts for distribution. See [the `release.yml` workflow](../.github/workflows/release.yml) for a complete example of how to build a distribution archive. +After each step above is complete, the `build_dist.py` script can be used to construct the MODFLOW 6 distribution. See [the `release.yml` workflow](../.github/workflows/release.yml) for a complete example of how to build a distribution archive. #### Verifying the distribution archive diff --git a/distribution/build_dist.py b/distribution/build_dist.py index 8ebf8014daa..b1ac5db73a6 100644 --- a/distribution/build_dist.py +++ b/distribution/build_dist.py @@ -78,9 +78,6 @@ def copy_sources(output_path: PathLike): # make sure output directory exists output_path.mkdir(exist_ok=True) - # copy code.json - shutil.copy(_project_root_path / "code.json", output_path) - # Copy Visual Studio sln and project files print("Copying msvs files to output directory") (output_path / "msvs").mkdir(exist_ok=True) @@ -302,9 +299,9 @@ def build_distribution( build_path: PathLike, output_path: PathLike, examples_repo_path: PathLike, - development: bool = False, + full: bool = False, overwrite: bool = False): - print(f"Building {'development' if development else 'full'} distribution") + print(f"Building {'full' if full else 'minimal'} distribution") build_path = Path(build_path).expanduser().absolute() output_path = Path(output_path).expanduser().absolute() @@ -315,9 +312,12 @@ def build_distribution( build_path=build_path, bin_path=output_path / "bin", overwrite=overwrite) + + # code.json metadata + shutil.copy(_project_root_path / "code.json", output_path) # full releases include examples, source code, makefiles and docs - if not development: + if full: # examples setup_examples( bin_path=output_path / "bin", @@ -336,24 +336,27 @@ def build_distribution( output_path=output_path / "doc", examples_repo_path=examples_repo_path, # benchmarks_path=_benchmarks_path / "run-time-comparison.md", - development=development, + full=full, overwrite=overwrite) @requires_exe("pdflatex") @pytest.mark.skip(reason="manual testing") -@pytest.mark.parametrize("dev", [True, False]) -def test_build_distribution(tmp_path, dev): +@pytest.mark.parametrize("full", [True, False]) +def test_build_distribution(tmp_path, full): output_path = tmp_path / "dist" build_distribution( build_path=tmp_path / "builddir", output_path=output_path, examples_repo_path=_examples_repo_path, - development=dev, + full=full, overwrite=True ) - if dev: + if full: + # todo + pass + else: # check binaries and libs system = platform.system() ext = ".exe" if system == "Windows" else "" @@ -369,8 +372,6 @@ def test_build_distribution(tmp_path, dev): # check mf6io docs assert (output_path / "mf6io.pdf").is_file() - else: - pass if __name__ == "__main__": @@ -381,6 +382,10 @@ def test_build_distribution(tmp_path, dev): """\ Create a distribution folder. If no output path is provided distribution files are written to the distribution/ folder. + By default a minimal distribution containing only binaries, + mf6io documentation, release notes and metadata (code.json) + is created. To create a full distribution including sources + and examples, use the --full flag. """ ), ) @@ -412,12 +417,11 @@ def test_build_distribution(tmp_path, dev): # help="Path to directory containing benchmark results" # ) parser.add_argument( - "-d", - "--development", + "--full", required=False, default=False, action="store_true", - help="Whether to build a development (e.g., nightly) rather than a full distribution" + help="Build a full rather than minimal distribution" ) parser.add_argument( "-f", @@ -425,7 +429,7 @@ def test_build_distribution(tmp_path, dev): required=False, default=False, action="store_true", - help="Whether to recreate and overwrite existing artifacts" + help="Recreate and overwrite existing artifacts" ) args = parser.parse_args() @@ -445,6 +449,6 @@ def test_build_distribution(tmp_path, dev): build_path=build_path, output_path=out_path, examples_repo_path=examples_repo_path, - development=args.development, + full=args.full, overwrite=args.force, ) diff --git a/distribution/build_docs.py b/distribution/build_docs.py index 6161a79837b..926a3a1c577 100644 --- a/distribution/build_docs.py +++ b/distribution/build_docs.py @@ -404,9 +404,9 @@ def build_documentation(bin_path: PathLike, # Example to use to render sample mf6 output in the docs. # Must be a valid directory in modflow6-examples/examples example_for_sample: str = "ex-gwf-twri01", - development: bool = False, + full: bool = False, overwrite: bool = False): - print(f"Building {'development' if development else 'full'} documentation") + print(f"Building {'full' if full else 'full'} documentation") bin_path = Path(bin_path).expanduser().absolute() output_path = Path(output_path).expanduser().absolute() @@ -430,7 +430,7 @@ def build_documentation(bin_path: PathLike, # build LaTeX file describing distribution folder structure # build_tex_folder_structure(overwrite=True) - if development: + if not full: # convert LaTeX to PDF build_pdfs_from_tex(tex_paths=_dev_dist_tex_paths, output_path=output_path) else: @@ -463,9 +463,8 @@ def build_documentation(bin_path: PathLike, convert_line_endings(output_path, windows_line_endings) # make sure we have expected PDFs - if development: - assert (output_path / "mf6io.pdf").is_file() - else: + assert (output_path / "mf6io.pdf").is_file() + if full: assert (output_path / "mf6io.pdf").is_file() assert (output_path / "ReleaseNotes.pdf").is_file() assert (output_path / "zonebudget.pdf").is_file() @@ -491,7 +490,8 @@ def test_build_documentation(tmp_path): formatter_class=argparse.RawDescriptionHelpFormatter, epilog=textwrap.dedent( """\ - Create documentation for a distribution. This includes benchmarks, release notes, the + Create documentation for a distribution. By default, this only includes the mf6io PDF + document. If the --full flag is provided this includes benchmarks, release notes, the MODFLOW 6 input/output specification, example model documentation, supplemental info, documentation for the MODFLOW 5 to 6 converter and Zonebudget 6, and several articles downloaded from the USGS website. These are all written to a specified --output-path. @@ -529,12 +529,11 @@ def test_build_documentation(tmp_path): help="Location to create documentation artifacts", ) parser.add_argument( - "-d", - "--development", + "--full", required=False, default=False, action="store_true", - help="Whether to build a development (e.g., nightly) rather than a full distribution" + help="Build docs for a full rather than minimal distribution" ) parser.add_argument( "-f", @@ -542,7 +541,7 @@ def test_build_documentation(tmp_path): required=False, default=False, action="store_true", - help="Whether to recreate and overwrite existing artifacts" + help="Recreate and overwrite existing artifacts" ) args = parser.parse_args() tex_paths = _full_dist_tex_paths + ([Path(p) for p in args.tex_path] if args.tex_path else []) @@ -557,5 +556,5 @@ def test_build_documentation(tmp_path): output_path=output_path, examples_repo_path=examples_repo_path, example_for_sample=example_for_sample, - development=args.development, + full=args.full, overwrite=args.force) diff --git a/distribution/check_dist.py b/distribution/check_dist.py index 4b2ad34a2b9..df7e5ed1069 100644 --- a/distribution/check_dist.py +++ b/distribution/check_dist.py @@ -20,6 +20,16 @@ def approved(request): return request.config.getoption("--approved") +@pytest.fixture +def releasemode(request): + return request.config.getoption("--releasemode") + + +@pytest.fixture +def full(request): + return request.config.getoption("--full") + + @pytest.fixture def dist_dir_path(request): def skip(): @@ -36,7 +46,10 @@ def skip(): return path -def test_sources(dist_dir_path, approved): +def test_sources(dist_dir_path, approved, releasemode, full): + if not full: + pytest.skip(reason="sources not included in minimal distribution") + assert (dist_dir_path / "src").is_dir() assert (dist_dir_path / "src" / "mf6.f90").is_file() @@ -50,13 +63,15 @@ def test_sources(dist_dir_path, approved): assert line # make sure IDEVELOPMODE was set correctly - branch = get_branch() - idevelopmode = 0 if approved else 1 + idevelopmode = 0 if releasemode else 1 assert f"IDEVELOPMODE = {idevelopmode}" in line @pytest.mark.skipif(not _fc, reason="needs Fortran compiler") -def test_makefiles(dist_dir_path): +def test_makefiles(dist_dir_path, full): + if not full: + pytest.skip(reason="makefiles not included in minimal distribution") + assert (dist_dir_path / "make" / "makefile").is_file() assert (dist_dir_path / "make" / "makedefaults").is_file() assert (dist_dir_path / "utils" / "zonebudget" / "make" / "makefile").is_file() @@ -71,7 +86,10 @@ def test_makefiles(dist_dir_path): print(subprocess.check_output("make", cwd=dist_dir_path / "utils" / "mf5to6" / "make", shell=True)) -def test_msvs(dist_dir_path): +def test_msvs(dist_dir_path, full): + if not full: + pytest.skip(reason="MSVS files not included in minimal distribution") + assert (dist_dir_path / "msvs" / "mf6.sln").is_file() assert (dist_dir_path / "msvs" / "mf6.vfproj").is_file() assert (dist_dir_path / "msvs" / "mf6bmi.sln").is_file() @@ -79,25 +97,33 @@ def test_msvs(dist_dir_path): assert (dist_dir_path / "msvs" / "mf6core.vfproj").is_file() -def test_docs(dist_dir_path): +def test_docs(dist_dir_path, full): + # mf6io should always be included assert (dist_dir_path / "doc" / "mf6io.pdf").is_file() - assert (dist_dir_path / "doc" / "release.pdf").is_file() - assert (dist_dir_path / "doc" / "mf5to6.pdf").is_file() - assert (dist_dir_path / "doc" / "zonebudget.pdf").is_file() - assert (dist_dir_path / "doc" / "mf6suptechinfo.pdf").is_file() - assert (dist_dir_path / "doc" / "mf6examples.pdf").is_file() - - for pub in [ - "tm6a55", - "tm6a56", - "tm6a57", - "tm6a61", - "tm6a62", - ]: - assert (dist_dir_path / "doc" / f"{pub}.pdf").is_file() - - -def test_examples(dist_dir_path): + + if full: + # check other custom-built documentation + assert (dist_dir_path / "doc" / "release.pdf").is_file() + assert (dist_dir_path / "doc" / "mf5to6.pdf").is_file() + assert (dist_dir_path / "doc" / "zonebudget.pdf").is_file() + assert (dist_dir_path / "doc" / "mf6suptechinfo.pdf").is_file() + assert (dist_dir_path / "doc" / "mf6examples.pdf").is_file() + + # check publications downloaded from USGS site + for pub in [ + "tm6a55", + "tm6a56", + "tm6a57", + "tm6a61", + "tm6a62", + ]: + assert (dist_dir_path / "doc" / f"{pub}.pdf").is_file() + + +def test_examples(dist_dir_path, full): + if not full: + pytest.skip(reason="examples not included in minimal distribution") + # make sure examples dir exists examples_path = dist_dir_path / "examples" assert examples_path.is_dir() @@ -120,10 +146,7 @@ def test_binaries(dist_dir_path, approved): assert output.startswith("mf6") # make sure binaries were built in correct mode - if approved: - assert "preliminary" not in output, "Binaries were not built in release mode" - else: - assert "preliminary" in output, "Binaries were not built in development mode" + assert ("preliminary" in output) != approved # check version string version = ( diff --git a/distribution/conftest.py b/distribution/conftest.py index f493d33c7a9..66c79609533 100644 --- a/distribution/conftest.py +++ b/distribution/conftest.py @@ -7,6 +7,8 @@ def pytest_addoption(parser): - # both options below are for check_dist.py + # all options below are for check_dist.py parser.addoption("-P", "--path", action="store", default=str(_dist_dir_path)) parser.addoption("-A", "--approved", action="store_true", default=False) + parser.addoption("-R", "--releasemode", action="store_true", default=False) + parser.addoption("-F", "--full", action="store_true", default=False) diff --git a/distribution/update_version.py b/distribution/update_version.py index 1acb393a625..17308d4771e 100755 --- a/distribution/update_version.py +++ b/distribution/update_version.py @@ -13,17 +13,20 @@ ../code.json ../src/Utilities/version.f90 -Information in these files include version number major.minor.patch[-label], build timestamp, -whether or not the release is a prerelease candidate or an official distribution, whether the -source code should be compiled in develop mode or in release mode, and other version metadata. +Information in these files include version number (major.minor.patch[label]), build timestamp, +whether or not the release is preliminary/provisional or official/approved, whether the source +code should be compiled in develop mode (IDEVELOPMODE = 1) or for release, and other metadata. The version number is read from ../version.txt, which contains major, minor, and patch version numbers, and an optional label. Version numbers are substituted into source code, latex files, -markdown files, etc. The version number can be overridden using argument --version, short -v. +markdown files, etc. The version number can be provided explicitly using --version, short -v. -Develop mode is set to 0 if the distribution is approved with --approved, short -a, otherwise, -it is set to 1. +If the --releasemode flag is provided, IDEVELOPMODE is set to 0 in src/Utilities/version.f90. +Otherwise, IDEVELOPMODE is set to 1. +if the --approved flag (short -a) is provided, the disclaimer in src/Utilities/version.f90 and +the README/DISCLAIMER markdown files is modified to reflect review and approval. Otherwise the +language reflects preliminary/provisional status, and version strings contain "(preliminary)". """ import argparse import json @@ -232,7 +235,10 @@ def update_version_tex( def update_version_f90( - version: Optional[Version], timestamp: datetime, approved: bool = False + version: Optional[Version], + timestamp: datetime, + approved: bool = False, + developmode: bool = True ): path = project_root_path / "src" / "Utilities" / "version.f90" lines = open(path, "r").read().splitlines() @@ -255,7 +261,7 @@ def update_version_f90( elif ":: IDEVELOPMODE =" in line: line = ( " integer(I4B), parameter :: " - + f"IDEVELOPMODE = {0 if approved else 1}" + + f"IDEVELOPMODE = {1 if developmode else 0}" ) elif ":: VERSIONNUMBER =" in line: line = line.rpartition("::")[0] + f":: VERSIONNUMBER = '{version_num}'" @@ -332,7 +338,8 @@ def update_codejson(version: Version, timestamp: datetime, approved: bool = Fals def update_version( version: Version = None, timestamp: datetime = datetime.now(), - approved: bool = False + approved: bool = False, + developmode: bool = True ): """ Update version information stored in version.txt in the project root, @@ -357,7 +364,7 @@ def update_version( update_version_txt_and_py(version, timestamp) update_meson_build(version) update_version_tex(version, timestamp) - update_version_f90(version, timestamp, approved) + update_version_f90(version, timestamp, approved, developmode) update_readme_and_disclaimer(version, approved) update_citation_cff(version, timestamp) update_codejson(version, timestamp, approved) @@ -376,18 +383,26 @@ def update_version( Version(major=_initial_version.major, minor=_initial_version.minor, patch=_initial_version.patch), Version(major=_initial_version.major, minor=_initial_version.minor, patch=_initial_version.patch, label="rc")], ) -def test_update_version(release_type, version): +@pytest.mark.parametrize("approved", [True, False]) +@pytest.mark.parametrize("developmode", [True, False]) +def test_update_version(version, approved, developmode): m_times = [get_modified_time(file) for file in touched_file_paths] timestamp = datetime.now() try: - update_version(release_type=release_type, timestamp=timestamp, version=version) + update_version( + timestamp=timestamp, + version=version, + approved=approved, + developmode=developmode + ) updated = Version.from_file(version_file_path) # check files containing version info were modified for p, t in zip(touched_file_paths, m_times): assert p.stat().st_mtime > t + # check version number and optional label are correct if version: # version should be auto-incremented assert updated.major == _initial_version.major @@ -402,9 +417,24 @@ def test_update_version(release_type, version): assert updated.patch == _current_version.patch if version.label is not None: assert updated.label == version.label - if version.label is not None: assert updated.label == _initial_version + + # check IDEVELOPMODE was set correctly + version_f90_path = project_root_path / "src" / "Utilities" / "version.f90" + lines = version_f90_path.read_text().splitlines() + assert any(f"IDEVELOPMODE = {1 if developmode else 0}" in line for line in lines) + + # check disclaimer has appropriate language + disclaimer_path = project_root_path / "DISCLAIMER.md" + disclaimer = disclaimer_path.read_text().splitlines() + assert any(("approved for release") in line for line in lines) == approved + assert any(("preliminary or provisional") in line for line in lines) != approved + + # check readme has appropriate language + readme_path = project_root_path / "README.md" + readme = readme_path.read_text().splitlines() + assert any(("(preliminary)") in line for line in lines) != approved finally: for p in touched_file_paths: os.system(f"git restore {p}") @@ -439,10 +469,17 @@ def test_update_version(release_type, version): ) parser.add_argument( "-a", - "--approve", + "--approved", + required=False, + action="store_true", + help="Approve the release version (defaults to false for preliminary/development distributions)", + ) + parser.add_argument( + "-r", + "--releasemode", required=False, action="store_true", - help="Indicate release is approved (defaults to false for preliminary/development distributions)", + help="Set IDEVELOPMODE to 0 for release mode (defaults to false for development distributions)", ) parser.add_argument( "--bump-major", @@ -500,5 +537,6 @@ def test_update_version(release_type, version): update_version( version=version, timestamp=datetime.now(), - approved=args.approve, + approved=args.approved, + developmode=not args.releasemode ) From 9b6b8a6500974570717efa40e53ce671b5c1ae74 Mon Sep 17 00:00:00 2001 From: Mike Taves Date: Wed, 21 Jun 2023 04:19:33 +1200 Subject: [PATCH 108/123] refactor(print_final_message): flush console buffer output to stdout (#1259) --- src/Utilities/Sim.f90 | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Utilities/Sim.f90 b/src/Utilities/Sim.f90 index e56ea5fc9be..2f19b90e0fa 100644 --- a/src/Utilities/Sim.f90 +++ b/src/Utilities/Sim.f90 @@ -414,6 +414,9 @@ subroutine print_final_message(stopmess, ioutlocal) end if end if ! + ! -- write console buffer output to stdout + flush (istdout) + ! ! -- determine if an error condition has occurred if (sim_errors%count_message() > 0) then ireturnerr = 2 From ea82a5d25e5ee396151ad012be05d40c1971e19a Mon Sep 17 00:00:00 2001 From: mjreno Date: Tue, 20 Jun 2023 16:35:19 -0400 Subject: [PATCH 109/123] fix(sim): ensure local string can hold entire solution group block slnmnames input string (#1260) Co-authored-by: mjreno --- src/SimulationCreate.f90 | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/SimulationCreate.f90 b/src/SimulationCreate.f90 index 73efab00db9..9fad6a2f806 100644 --- a/src/SimulationCreate.f90 +++ b/src/SimulationCreate.f90 @@ -498,7 +498,8 @@ subroutine solution_groups_create() type(CharacterStringType), dimension(:), contiguous, & pointer :: slnmnames integer(I4B), dimension(:), contiguous, pointer :: blocknum - character(len=LINELENGTH) :: stype, fname, mnames + character(len=LINELENGTH) :: stype, fname + character(len=:), allocatable :: mnames type(SolutionGroupType), pointer :: sgp class(BaseSolutionType), pointer :: sp class(BaseModelType), pointer :: mp @@ -533,6 +534,9 @@ subroutine solution_groups_create() ! ! -- create solution groups do i = 1, size(blocknum) + ! + ! -- allocate slnmnames string + allocate (character(slnmnames(i)%strlen()) :: mnames) ! ! -- attributes for this solution stype = slntype(i) @@ -651,6 +655,9 @@ subroutine solution_groups_create() end do case default end select + ! + ! -- clean up + deallocate (mnames) end do ! ! -- error check final group From 9ed141213c55eb2ed375eb1dc4997b9063976659 Mon Sep 17 00:00:00 2001 From: langevin-usgs Date: Tue, 20 Jun 2023 15:36:16 -0500 Subject: [PATCH 110/123] docs(6.4.2): cleanup and revision for upcoming release (#1261) --- doc/MODFLOW6References.bib | 11 +++++++++ doc/ReleaseNotes/ReleaseNotes.bbl | 9 ++++++- doc/ReleaseNotes/ReleaseNotes.tex | 2 +- doc/ReleaseNotes/v6.4.2.tex | 26 +++++++++++---------- doc/SuppTechInfo/Tables/mf6enhancements.tex | 2 +- 5 files changed, 35 insertions(+), 15 deletions(-) diff --git a/doc/MODFLOW6References.bib b/doc/MODFLOW6References.bib index ebd845d7ab0..0623f871f57 100644 --- a/doc/MODFLOW6References.bib +++ b/doc/MODFLOW6References.bib @@ -2918,3 +2918,14 @@ @book{maidment1993 Address = {New York, USA}, Title = {Handbook of Hydrology}, Year = {1993}} + +@article{hughes2023flopy, + author = {Hughes, Joseph D. and Langevin, Christian D. and Paulinski, Scott R. and Larsen, Joshua D. and Brakenhoff, David}, + title = {{FloPy} Workflows for Creating Structured and Unstructured {MODFLOW} Models}, + journal = {Groundwater}, + Year = {2023}, + volume = {}, + number = {}, + pages = {}, + doi = {https://doi.org/10.1111/gwat.13327}} + diff --git a/doc/ReleaseNotes/ReleaseNotes.bbl b/doc/ReleaseNotes/ReleaseNotes.bbl index d262d6abb29..da65c463d1d 100644 --- a/doc/ReleaseNotes/ReleaseNotes.bbl +++ b/doc/ReleaseNotes/ReleaseNotes.bbl @@ -1,4 +1,4 @@ -\begin{thebibliography}{13} +\begin{thebibliography}{14} \providecommand{\natexlab}[1]{#1} \expandafter\ifx\csname urlstyle\endcsname\relax \providecommand{\doiagency}[1]{doi:\discretionary{}{}{}#1}\else @@ -34,6 +34,13 @@ Hughes, J.D., Leake, S.A., Galloway, D.L., and White, J.T., 2022{\natexlab{b}}, Package of MODFLOW 6: {U.S. Geological Survey Techniques and Methods, book 6, chap. A62, 57 p.}, \url{https://doi.org/10.3133/tm6A62}. +\bibitem[{Hughes and others(2023)Hughes, Langevin, Paulinski, Larsen, and + Brakenhoff}]{hughes2023flopy} +Hughes, J.D., Langevin, C.D., Paulinski, S.R., Larsen, J.D., and Brakenhoff, + D., 2023, {FloPy} workflows for creating structured and unstructured + {MODFLOW} models: Groundwater, + \url{https://doi.org/https://doi.org/10.1111/gwat.13327}. + \bibitem[{Langevin and others(2017)Langevin, Hughes, Provost, Banta, Niswonger, and Panday}]{modflow6gwf} Langevin, C.D., Hughes, J.D., Provost, A.M., Banta, E.R., Niswonger, R.G., and diff --git a/doc/ReleaseNotes/ReleaseNotes.tex b/doc/ReleaseNotes/ReleaseNotes.tex index da515b57d5a..c43f37ec1c0 100644 --- a/doc/ReleaseNotes/ReleaseNotes.tex +++ b/doc/ReleaseNotes/ReleaseNotes.tex @@ -175,7 +175,7 @@ \section{Release History} 6.3.0 & March 4, 2022 & \url{https://doi.org/10.5066/P97FFF9M} \\ 6.4.0 & November 30, 2022 & \url{https://doi.org/10.5066/P9FL1JCC} \\ 6.4.1 & December 9, 2022 & \url{https://doi.org/10.5066/P9FL1JCC} \\ -6.4.2 & Month x, 202x & assigned at release \\ +6.4.2 & June 28, 2023 & \url{https://doi.org/10.5066/P9FL1JCC} \\ \hline \label{tab:releases} \end{tabular*} diff --git a/doc/ReleaseNotes/v6.4.2.tex b/doc/ReleaseNotes/v6.4.2.tex index 281fab53c02..6a72217ce98 100644 --- a/doc/ReleaseNotes/v6.4.2.tex +++ b/doc/ReleaseNotes/v6.4.2.tex @@ -1,35 +1,34 @@ % Use this template for starting initializing the release notes % after a release has just been made. - \item \currentmodflowversion + %\item \currentmodflowversion + \item Version mf6.4.2--June 28, 2023 \underline{NEW FUNCTIONALITY} \begin{itemize} - \item The Groundwater Transport (GWT) model was modified for simulations involving a mobile domain and one or more immobile domains. The modifications do not affect GWT Models without the Immobile Storage and Transfer (IST) Package. The original IST Package formulation described by \cite{modflow6gwt} was based on a limiting assumption about how the mobile and immobile domains are apportioned within a model cell. The changes introduced here require the user to explicitly specify in the IST Package the volume fraction of each cell that is immobile. This change also redefines the meaning of several input parameters. As described in a new chapter in the Supplemental Technical Information document, porosity and bulk density values must now be entered per domain volume rather than per cell volume. Consequently, for simulations that include one or more IST Packages, these changes are not backward compatible, and will require updates to IST and MST input. Suggestions for updating existing parameter values is included in the Supplemental Technical Information document, which is included with the distribution. + \item The source code was refactored to support compilation of a parallel version of MODFLOW 6 based on the Message Passing Interface (MPI) and the Portable, Extensible Toolkit for Scientific Computation (PETSc) libraries. The parallel version of MODFLOW is considered preliminary (alpha release). Limited testing of the parallel version has been performed on laptops, desktops, and supercomputers, but significant changes are expected in future releases. User support for the parallel version of MODFLOW 6 may be provided in the future. + \item The Groundwater Transport (GWT) model was modified for simulations involving a mobile domain and one or more immobile domains. The modifications do not affect GWT Models without the Immobile Storage and Transfer (IST) Package. The original IST Package formulation described by \cite{modflow6gwt} was based on a limiting assumption about how the mobile and immobile domains are apportioned within a model cell. The changes introduced here require the user to explicitly specify in the IST Package the volume fraction of each cell that is immobile. This change also redefines the meaning of several input parameters. As described in a new chapter (Chapter 9) in the Supplemental Technical Information document, porosity and bulk density values must now be entered per domain volume rather than per cell volume. Consequently, for simulations that include one or more IST Packages, these changes are not backward compatible, and will require updates to IST and MST input. Suggestions for updating existing parameter values is included in Chapter 9 of the Supplemental Technical Information document, which is included with the distribution. \item Add LENGTH\_CONVERSION and TIME\_CONVERSION variables to replace the UNIT\_CONVERSION variable in the SFR Package input file. The LENGTH\_CONVERSION and TIME\_CONVERSION variables are used to convert user-specified Manning's roughness coefficients from SI units (sec/m$^{1/3}$) to model length and time units. LENGTH\_CONVERSION does not need to be specified if LENGTH\_UNITS are meters. TIME\_CONVERSION does not need to be specified if TIME\_UNITS are seconds. Warning messages will be issued if UNIT\_CONVERSION variable is specified. The model will terminate with an error if UNIT\_CONVERSION and LENGTH\_CONVERSION and TIME\_CONVERSION variables are specified. The UNIT\_CONVERSION variable in the SFR Package input file will eventually be deprecated. \item Add MAXIMUM\_ITERATIONS and MAXIMUM\_STAGE\_CHANGE variables in the LAK Package input file. The MAXIMUM\_ITERATIONS variable is used to change the maximum number of iterations and would only need to be increased from the default value if one or more lakes in a simulation has a large water budget error. The MAXIMUM\_STAGE\_CHANGE variable defines the stage closure tolerance for each lake. The MAXIMUM\_STAGE\_CHANGE variable would only need to be increased or decreased from the default value if the water budget error for one or more lakes is too small or too large, respectively. - \item The Input Data Processor (IDP) is introduced to read ASCII simulation input files and write variable input data to structured locations in the memory manager. Simulation components that have been integrated with IDP no longer handle input files directly but rather retrieve all input data from named locations, called memory paths, allocated in managed memory. The collection of all simulation input data in managed memory is called the input context. IDP uses existing descriptions of input varibles, called variable definitions, to interpret and store input. The program variable definition set and its representation in the input context is described as the Input Data Model (IDM). Input variables can be recognized in a memory dump (e.g., with the MEMORY\_PRINT\_OPTION) by their memory path prefix string "\_\_INPUT\_\_". The downstream context (model, package, etc.) that later accesses input typically copies data from the input context to their own memory managed context, therefore IDP results in an increased memory footprint for the program. Among its advantages include the consolidation of all input processing early in program runtime, and outside of any particular component. enabling the support of alternative types of input data sources. Input file types that are currently processed by IDP include DIS6, DISU6, DIV6, NPF6, DSP6, and Name File inputs for the Simulation (mfsim.nam) and GWF and GWT models. + \item The Input Data Processor (IDP) is introduced in this release to read user-provided input files and store user-provided input data in memory for subsequent use by simulation, model, and package components. Components that have been integrated with IDP no longer handle input files directly but rather retrieve all input data from named locations, called memory paths, allocated in managed memory. The collection of all simulation input data in managed memory is called the input context. IDP uses existing descriptions of input variables, called variable definitions, to interpret and store input. The program variable definition set and its representation in the input context is described as the Input Data Model (IDM). Input variables can be recognized in a memory dump (e.g., with the MEMORY\_PRINT\_OPTION activated in the options block of mfsim.nam) by their memory path prefix string "\_\_INPUT\_\_". Components that later access input typically copy data from the input context to their own memory space; therefore, the present implemention of the IDP results in an increased memory footprint for the program. Among its advantages include the consolidation of all input processing early in program runtime, and outside of any particular component, enabling the support of alternative types of input data sources. Input file types that are currently processed by IDP include DIS6, DISU6, DIV6, NPF6, DSP6, and Name File inputs for the Simulation (mfsim.nam) and GWF and GWT models. % \item xxx \end{itemize} - %\underline{EXAMPLES} - %\begin{itemize} - % \item xxx + \underline{EXAMPLES} + \begin{itemize} + \item A new example called ex-gwt-synthetic-valley was added. This new problem simulates groundwater flow and solute transport using a Voronoi model grid. Additional details for this example problem are described by \cite{hughes2023flopy}. % \item xxx % \item xxx - %\end{itemize} + \end{itemize} \textbf{\underline{BUG FIXES AND OTHER CHANGES TO EXISTING FUNCTIONALITY}} \\ \underline{BASIC FUNCTIONALITY} \begin{itemize} - \item When n-point cross-sections are active in SFR, the evaporation calculation uses the variable rwid (see MF6io.pdf) to calculate the total amount of evaporation even though the wetted topwidth is less than rwid. For example, using a trapezoidal cross-section geometry with an rwid of 10, an rlen of 100, and prescribed evaporation rate of 0.1, the calculated evaporative losses would equal 100 even when the wetted top width was only 5.0 units wide. With this bug fix, the evaporation in this example results in only 50 units of evaporation loss. A new autotests confirms the evaporation calculation using an n-point cross-section and common rectangular geometries in the same simulation. It is also worth mentioning that the precipitation calculation currently uses rwid. Since the precipitation falling outside the margins of the wetted top width but within rwid would likely be accumulated in a channel, it makes sense to leave this calculation as is. \item The input for some stress packages is read in a list format consisting of a cellid, the form of which depends on the type of discretization package, and stress information on each line. The cellid is checked upon reading to ensure that the cell is within the model grid. If the cell is outside the model grid, the program issues an error message and terminates. This cellid check was not implemented when the list was provided from an OPEN/CLOSE binary input file. The program was modified to include this check for both text and binary input. \item In some cases, unrecognized keywords and invalid auxiliary input did not terminate with a useful error message. The program was corrected to provide error handling for these cases. - \item Based on the LAK package input-output instructions (mf6io.pdf), the variable ``connlen must be greater than zero for a HORIZONTAL, EMBEDDEDH, or EMBEDDEDV lake-GWF connection.'' However, a value of zero could be specified and the model would run with no LAK-groundwater exchange. A minor fix was made to enforce connlen to be strictly greater than zero per the input instructions. The error message thrown when connlen is specified as zero was augmented with additional information for assisting the user. - \item An SFR channel defined with the n-point cross-section option was calculating the wetted cross-sectional area incorrectly. The cross sectional area for the area of a triangle was being calculated as one-half multiplied by the depth of the channel, as opposed to one-half multiplied by the base width multiplied by the height. As a result, the units in the mannings equation were not correct owing to the missing dimension in the area calculation. The change in the area calculation will slightly alter the solution found using Manning's equation since the cross-sectional area term appears in it. As a result, existing models may reflect slightly different answers in groundwater\/surface-water exchange amounts owing to slight differences in the calculated stream stage. In addition to the fix, some clarifying text, including a new figure, was added to mf6io.pdf. \item The SSM Package for the GWT Model did not work properly with Stress Package Concentration (SPC) input with the READARRAY option for transient models. Under these conditions, the program would prematurely terminate looking for the next BEGIN PERIOD block. The program was corrected so that SPC input can be read for transient conditions. \item For some Linux systems, observations were not being correctly written to formatted observation output files when the source code was compiled with the Intel IFORT 19.1.0.166 20191121 compiler. This issue has been addressed by adding a flush statement to ObsUtilityModule::write\_unfmtd\_obs after writing each observation for a time step. This change will not affect simulated observations and should not affect simulation run times. - \item The wetted area stored in the binary LAK package output needs to be zero when the lake stage is below the bottom of a connected groundwater cell. The code uses the lak\_calculate\_conn\_warea() function to determine the wetted area, which makes sense for calculating the flow conductance; however, for thermal conduction the shared wetted area should be 0.0 when the lake stage falls below the bottom of a connected cell. + \item The wetted area written to the binary LAK package output was modified to be zero when the lake stage is below the bottom of a connected groundwater cell. The code uses the lak\_calculate\_conn\_warea() function to determine the wetted area, which makes sense for calculating the flow conductance. \end{itemize} \underline{INTERNAL FLOW PACKAGES} @@ -49,8 +48,11 @@ \underline{ADVANCED STRESS PACKAGES} \begin{itemize} \item Added additional convergence checks to the Streamflow Routing (SFR), Lake (LAK) and Unsaturated Zone Flow (UZF) Packages to ensure that flows from the Water Mover (MVR) Package meet solver tolerance. Mover flows are converted into depths using the time step length and area, and the depths are compared to the Iterative Model Solution (IMS) DVCLOSE input parameter. If a depth is greater than DVCLOSE, then the iteration is marked as not converged. The maximum depth change between iterations and the advanced package feature number is written for each outer iteration to a comma-separated value file, provided the PACKAGE\_CONVERGENCE option is specified in the options block. - \item Added an additional convergence check to the Lake (LAK) Package to ensure that the lake residual meets solver tolerance. In previous versions, it was possible for a time step to converge even if there was a relatively large lake residual. The lake residual is converted to a depth, using the time step length and lake surface area. This depth is compared to the Iterative Model Solution (IMS) DVCLOSE input parameter. If the residual depth is greater than DVCLOSE, then the iteration is marked as not converged. The maximum residual depth and the advanced package feature number (lake number) is written for each outer iteration to a comma-separated value file, provided the PACKAGE\_CONVERGENCE option is specified in the options block. This fix may increase the number of iterations required to reach convergence for existing models that use the LAK Package. + \item Added an additional convergence check to the Lake (LAK) Package to ensure that the lake residual meets solver tolerance. In previous versions, it was possible for a time step to converge even if there was a relatively large lake residual. The lake residual is converted to a depth, using the time step length and lake surface area. This depth is compared to the Iterative Model Solution (IMS) DVCLOSE input parameter. If the residual depth is greater than DVCLOSE, then the iteration is marked as not converged. The maximum residual depth and the advanced package feature number (lake number) is written for each outer iteration to a comma-separated value file, provided the PACKAGE\_CONVERGENCE option is specified in the options block. This fix may increase the number of iterations required to reach convergence for existing models that use the LAK Package. \item The secant method used in the LAK Package when Newton-Raphson iterations result in lake stages below the lake bottom or when Newton-Raphson iterations stall has been updated. The updates to the secant method improve convergence under drying and wetting conditions in a lake. + \item Based on the LAK package input-output instructions (mf6io.pdf), the variable ``connlen must be greater than zero for a HORIZONTAL, EMBEDDEDH, or EMBEDDEDV lake-GWF connection.'' However, a value of zero could be specified and the model would run with no LAK-groundwater exchange. A minor fix was made to enforce connlen to be strictly greater than zero per the input instructions. The error message issued when connlen is specified as zero was augmented with additional information for assisting the user. + \item When n-point cross-sections are active in SFR, the evaporation calculation uses the variable rwid (see mf6io.pdf) to calculate the total amount of evaporation even though the wetted topwidth is less than rwid. For example, using a trapezoidal cross-section geometry with an rwid of 10, an rlen of 100, and prescribed evaporation rate of 0.1, the calculated evaporative losses would equal 100 even when the wetted top width was only 5.0 units wide. With this bug fix, the evaporation in this example results in only 50 units of evaporation loss. It is also worth mentioning that the precipitation calculation currently uses rwid. Since the precipitation falling outside the margins of the wetted top width but within rwid would likely be accumulated in a channel, it makes sense to leave this calculation as is. + \item An SFR channel defined with the n-point cross-section option was calculating the wetted cross-sectional area incorrectly for rectangular cross sections and for some other cross-section configurations. The cross sectional area of a triangular section was being calculated as one-half multiplied by the depth of the channel, as opposed to one-half multiplied by the base width multiplied by the height. As a result, the units in the Manning's equation were not correct owing to the missing dimension in the area calculation. The change in the area calculation will slightly alter the solution found using Manning's equation since the cross-sectional area term appears in it. As a result, existing models may reflect slightly different answers in groundwater and surface-water exchange amounts owing to slight differences in the calculated stream stage. In addition to the fix, some clarifying text, including a new figure, was added to mf6io.pdf. % \item xxx % \item xxx \end{itemize} diff --git a/doc/SuppTechInfo/Tables/mf6enhancements.tex b/doc/SuppTechInfo/Tables/mf6enhancements.tex index 5f191a686bf..40c9e0fcdc9 100644 --- a/doc/SuppTechInfo/Tables/mf6enhancements.tex +++ b/doc/SuppTechInfo/Tables/mf6enhancements.tex @@ -28,7 +28,7 @@ Generalized Coupling of Numerical Models & \ref{ch:gencouple} & 6.3.0 & -- \\ VSC Package & \ref{ch:vscpackage} & 6.4.0 & -- \\ \rowcolor{Gray} - Sorption in Mobile and Immobile Domains & \ref{ch:sorption} & 6.5.0 & -- \\ + Revised Parameterization of Transport for Combined Mobile and Immobile Domain Simulations & \ref{ch:sorption} & 6.4.2 & -- \\ \hline \end{tabular} \label{table:mf6enhance} From ba436360a2e2900c591287c4ef7ce2469dd1d16e Mon Sep 17 00:00:00 2001 From: Mike Taves Date: Thu, 22 Jun 2023 23:22:46 +1200 Subject: [PATCH 111/123] fix(dfn): use "double precision" instead of "double", remove duplicates (#1264) --- doc/mf6io/mf6ivar/dfn/gwf-buy.dfn | 2 +- doc/mf6io/mf6ivar/dfn/gwf-csub.dfn | 11 ----------- doc/mf6io/mf6ivar/dfn/gwf-vsc.dfn | 6 +++--- doc/mf6io/mf6ivar/dfn/gwt-ist.dfn | 11 ----------- doc/mf6io/mf6ivar/md/mf6ivar.md | 8 ++++---- doc/mf6io/mf6ivar/mf6ivar.py | 26 ++++++++++++++++++++++++-- 6 files changed, 32 insertions(+), 32 deletions(-) diff --git a/doc/mf6io/mf6ivar/dfn/gwf-buy.dfn b/doc/mf6io/mf6ivar/dfn/gwf-buy.dfn index b131433b9b1..54a5d1b8f1d 100644 --- a/doc/mf6io/mf6ivar/dfn/gwf-buy.dfn +++ b/doc/mf6io/mf6ivar/dfn/gwf-buy.dfn @@ -10,7 +10,7 @@ description use the variable-density hydraulic head formulation and add off-diag block options name denseref -type double +type double precision reader urword optional true longname reference density diff --git a/doc/mf6io/mf6ivar/dfn/gwf-csub.dfn b/doc/mf6io/mf6ivar/dfn/gwf-csub.dfn index d4f126410fa..954240daaf3 100644 --- a/doc/mf6io/mf6ivar/dfn/gwf-csub.dfn +++ b/doc/mf6io/mf6ivar/dfn/gwf-csub.dfn @@ -223,17 +223,6 @@ optional false longname compaction keyword description keyword to specify that record corresponds to the compaction. -block options -name fileout -type keyword -shape -in_record true -reader urword -tagged true -optional false -longname file keyword -description keyword to specify that an output filename is expected next. - block options name compaction_filename type string diff --git a/doc/mf6io/mf6ivar/dfn/gwf-vsc.dfn b/doc/mf6io/mf6ivar/dfn/gwf-vsc.dfn index 9e3ab6d0083..0a6e74b9d18 100644 --- a/doc/mf6io/mf6ivar/dfn/gwf-vsc.dfn +++ b/doc/mf6io/mf6ivar/dfn/gwf-vsc.dfn @@ -2,7 +2,7 @@ block options name viscref -type double +type double precision reader urword optional true longname reference viscosity @@ -30,7 +30,7 @@ description may be used for specifying which viscosity formulation to use for th block options name thermal_a2 -type double +type double precision reader urword optional true longname coefficient used in nonlinear viscosity function @@ -39,7 +39,7 @@ default_value 10. block options name thermal_a3 -type double +type double precision reader urword optional true longname coefficient used in nonlinear viscosity function diff --git a/doc/mf6io/mf6ivar/dfn/gwt-ist.dfn b/doc/mf6io/mf6ivar/dfn/gwt-ist.dfn index 7026755d810..c39d95cc26e 100644 --- a/doc/mf6io/mf6ivar/dfn/gwt-ist.dfn +++ b/doc/mf6io/mf6ivar/dfn/gwt-ist.dfn @@ -130,17 +130,6 @@ optional false longname cim keyword description keyword to specify that record corresponds to immobile concentration. -block options -name fileout -type keyword -shape -in_record true -reader urword -tagged true -optional false -longname file keyword -description keyword to specify that an output filename is expected next. - block options name cimfile type string diff --git a/doc/mf6io/mf6ivar/md/mf6ivar.md b/doc/mf6io/mf6ivar/md/mf6ivar.md index bf50dddc82e..3402680d544 100644 --- a/doc/mf6io/mf6ivar/md/mf6ivar.md +++ b/doc/mf6io/mf6ivar/md/mf6ivar.md @@ -221,7 +221,7 @@ | GWF | NPF | GRIDDATA | ANGLE3 | DOUBLE PRECISION (NODES) | is a rotation angle of the hydraulic conductivity tensor in degrees. The angle represents the third of three sequential rotations of the hydraulic conductivity ellipsoid. Following the rotations by ANGLE1 and ANGLE2 described above, ANGLE3 rotates the ellipsoid about its K11 axis. An array can be specified for ANGLE3 only if ANGLE1 and ANGLE2 are also specified. An array must be specified for ANGLE3 if ANGLE2 is specified. A positive value of ANGLE3 represents clockwise rotation when viewed from any point on the positive K11 axis, looking toward the center of the ellipsoid. A value of zero indicates that the K22 axis lies within the x - y plane. | | GWF | NPF | GRIDDATA | WETDRY | DOUBLE PRECISION (NODES) | is a combination of the wetting threshold and a flag to indicate which neighboring cells can cause a cell to become wet. If WETDRY $<$ 0, only a cell below a dry cell can cause the cell to become wet. If WETDRY $>$ 0, the cell below a dry cell and horizontally adjacent cells can cause a cell to become wet. If WETDRY is 0, the cell cannot be wetted. The absolute value of WETDRY is the wetting threshold. When the sum of BOT and the absolute value of WETDRY at a dry cell is equaled or exceeded by the head at an adjacent cell, the cell is wetted. WETDRY must be specified if ``REWET'' is specified in the OPTIONS block. If ``REWET'' is not specified in the options block, then WETDRY can be entered, and memory will be allocated for it, even though it is not used. | | GWF | BUY | OPTIONS | HHFORMULATION_RHS | KEYWORD | use the variable-density hydraulic head formulation and add off-diagonal terms to the right-hand. This option will prevent the BUY Package from adding asymmetric terms to the flow matrix. | -| GWF | BUY | OPTIONS | DENSEREF | DOUBLE | fluid reference density used in the equation of state. This value is set to 1000. if not specified as an option. | +| GWF | BUY | OPTIONS | DENSEREF | DOUBLE PRECISION | fluid reference density used in the equation of state. This value is set to 1000. if not specified as an option. | | GWF | BUY | OPTIONS | DENSITY | KEYWORD | keyword to specify that record corresponds to density. | | GWF | BUY | OPTIONS | FILEOUT | KEYWORD | keyword to specify that an output filename is expected next. | | GWF | BUY | OPTIONS | DENSITYFILE | STRING | name of the binary output file to write density information. The density file has the same format as the head file. Density values will be written to the density file whenever heads are written to the binary head file. The settings for controlling head output are contained in the Output Control option. | @@ -796,11 +796,11 @@ | GWF | OC | PERIOD | LAST | KEYWORD | keyword to indicate save for last step in period. This keyword may be used in conjunction with other keywords to print or save results for multiple time steps. | | GWF | OC | PERIOD | FREQUENCY | INTEGER | save at the specified time step frequency. This keyword may be used in conjunction with other keywords to print or save results for multiple time steps. | | GWF | OC | PERIOD | STEPS | INTEGER ( Date: Thu, 22 Jun 2023 09:59:48 -0400 Subject: [PATCH 112/123] ci: fix release scripts and workflow (#1263) * update release_dispatch.yml input descriptions * fix sample output substituted into mf6io * prevent double zipping on Windows * fix steps to build examples * update check_dist.py * format dist scripts --- .github/workflows/release.yml | 62 ++++++++++-- .github/workflows/release_dispatch.yml | 16 ++- distribution/benchmark.py | 55 ++++++---- distribution/build_dist.py | 135 ++++++++++++++++--------- distribution/build_docs.py | 113 ++++++++++++++------- distribution/build_makefiles.py | 4 +- distribution/check_dist.py | 49 ++++++--- distribution/conftest.py | 5 +- distribution/update_version.py | 55 ++++++---- distribution/utils.py | 2 +- 10 files changed, 332 insertions(+), 164 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 69e5d579e84..0ee6121c112 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -348,7 +348,7 @@ jobs: GITHUB_TOKEN: ${{ github.token }} run: | mkdir -p "${{ needs.build.outputs.distname }}/doc" - cmd="python modflow6/distribution/build_docs.py -b bin -o doc" + cmd="python modflow6/distribution/build_docs.py -b bin -o doc -e modflow6-examples" if [[ "${{ inputs.full }}" == "true" ]]; then cmd="$cmd --full" fi @@ -429,15 +429,43 @@ jobs: - name: Select artifacts for OS run: | - # create directory for OS-specific artifacts - distname="${{ needs.build.outputs.distname }}" - mkdir -p "$distname_${{ matrix.ostag }}/bin" - - # move binaries for current OS to distribution bin dir - mv "$distname/bin-${{ runner.os }}" "$distname_${{ matrix.ostag }}/bin" + # rename bin dir for current OS + distname="${{ needs.build.outputs.distname }}_${{ matrix.ostag }}" + mv "$distname/bin-${{ runner.os }}" "$distname/bin" # remove binaries for other OSes - rm -rf "$distname/bin-*" + rm -rf $distname/bin-* + ls "$distname" + + - name: Install dependencies for example models + env: + GITHUB_TOKEN: ${{ github.token }} + run: | + # install extra Python packages + pip install -r modflow6-examples/etc/requirements.pip.txt + + # example models need executables to be on the path + distname="${{ needs.build.outputs.distname }}_${{ matrix.ostag }}" + echo "$distname/bin" >> $GITHUB_PATH + + # execute permissions may not have survived artifact upload/download + chmod +x "$distname/bin/mf6" + chmod +x "$distname/bin/mf5to6" + chmod +x "$distname/bin/zbud6" + + # the example models also need mf2005, triangle and gridgen + - name: Install extra executables + uses: modflowpy/install-modflow-action@v1 + with: + subset: mf2005,triangle,gridgen + + - name: Update Flopy + working-directory: modflow6/autotest + run: python update_flopy.py + + - name: Build example models + working-directory: modflow6-examples/etc + run: python ci_build_files.py - name: Build distribution env: @@ -451,8 +479,7 @@ jobs: fi eval "$cmd" - # move & rename PDF documents to dist folder - + # rename PDF documents if [[ "${{ inputs.full }}" == "true" ]]; then mv "$distname/doc/converter_mf5to6.pdf" "$distname/doc/mf5to6.pdf" fi @@ -541,13 +568,28 @@ jobs: cmd="$cmd --full" fi eval "$cmd" + + - name: Unzip (Windows) + if: runner.os == 'Windows' + run: | + distname="${{ needs.build.outputs.distname }}_${{ matrix.ostag }}" + 7z x $distname.zip -o$distname + rm -rf "$distname/$distname" - name: Upload distribution zipfile artifact + if: runner.os != 'Windows' uses: actions/upload-artifact@v3 with: name: "${{ needs.build.outputs.distname }}_${{ matrix.ostag }}" path: "${{ needs.build.outputs.distname }}_${{ matrix.ostag }}.zip" + - name: Upload distribution zipfile artifact + if: runner.os == 'Windows' + uses: actions/upload-artifact@v3 + with: + name: "${{ needs.build.outputs.distname }}_${{ matrix.ostag }}" + path: "${{ needs.build.outputs.distname }}_${{ matrix.ostag }}" + - name: Upload release notes artifact if: runner.os == 'Linux' uses: actions/upload-artifact@v3 diff --git a/.github/workflows/release_dispatch.yml b/.github/workflows/release_dispatch.yml index 55704c0e6cd..57b02cdf5d1 100644 --- a/.github/workflows/release_dispatch.yml +++ b/.github/workflows/release_dispatch.yml @@ -16,35 +16,33 @@ on: workflow_dispatch: inputs: approve: - description: 'Whether to approve the release. Default is false, i.e. a preliminary/provisional release.' + description: 'Approve the release. Otherwise it is preliminary/provisional.' required: false type: boolean default: false branch: - description: 'Branch to use for release. Defaults to the current branch (for releases triggered by pushing a release branch).' - required: false + description: 'Branch to release from.' + required: true type: string - default: '' commit_version: - description: 'Whether to commit version numbers back to the develop branch. Default is false. Not considered if approve is false.' + description: 'Commit version numbers back to the develop branch. Not considered if reset is false.' required: false type: boolean default: false reset: - description: 'Whether to reset the develop branch to the state of the master branch. Default is false. Not considered if approve is false.' + description: 'Reset the develop branch from the master branch. Not considered if approve is false.' required: false type: boolean default: false run_tests: - description: 'Whether to run tests after building binaries. Default is true.' + description: 'Run tests after building binaries.' required: true type: boolean default: true version: description: 'Version number to use for release.' - required: false + required: true type: string - default: '' jobs: set_options: name: Set release options diff --git a/distribution/benchmark.py b/distribution/benchmark.py index 1c890ce7b0c..f5c319804e7 100644 --- a/distribution/benchmark.py +++ b/distribution/benchmark.py @@ -159,12 +159,15 @@ def elapsed_real_to_string(elt): def run_function(id, app, example): - return (id, flopy.run_model( - app, - None, - model_ws=example, - silent=True, - report=True,) + return ( + id, + flopy.run_model( + app, + None, + model_ws=example, + silent=True, + report=True, + ), ) @@ -177,7 +180,11 @@ def run_model(current_app: PathLike, previous_app: PathLike, model_path: PathLik previous_time = 0.0 generic_names = ["mf6gwf", "mf6gwt"] - name = f"{model_path.parent.name}/{model_path.name}" if model_path.name in generic_names else model_path.name + name = ( + f"{model_path.parent.name}/{model_path.name}" + if model_path.name in generic_names + else model_path.name + ) print(f"Running scenario: {name}") line = f"| {name} |" @@ -315,12 +322,13 @@ def write_results( def run_benchmarks( - build_path: PathLike, - current_bin_path: PathLike, - previous_bin_path: PathLike, - examples_path: PathLike, - output_path: PathLike, - excluded: List[str]=[]): + build_path: PathLike, + current_bin_path: PathLike, + previous_bin_path: PathLike, + examples_path: PathLike, + output_path: PathLike, + excluded: List[str] = [], +): """Benchmark current development version against previous release with example models.""" build_path = Path(build_path).expanduser().absolute() @@ -343,12 +351,20 @@ def run_benchmarks( if not current_exe.is_file(): print(f"Building current MODFLOW 6 development version") - meson_build(project_path=_project_root_path, build_path=build_path, bin_path=current_bin_path) + meson_build( + project_path=_project_root_path, + build_path=build_path, + bin_path=current_bin_path, + ) if not previous_exe.is_file(): version, download_path = download_previous_version(output_path) print(f"Rebuilding latest MODFLOW 6 release {version} in development mode") - meson_build(project_path=download_path, build_path=build_path, bin_path=previous_bin_path) + meson_build( + project_path=download_path, + build_path=build_path, + bin_path=previous_bin_path, + ) print(f"Benchmarking MODFLOW 6 versions:") print(f" current: {current_exe}") @@ -389,7 +405,8 @@ def test_run_benchmarks(tmp_path): previous_bin_path=_bin_path / "rebuilt", examples_path=_examples_repo_path / "examples", output_path=tmp_path, - excluded=["previous"]) + excluded=["previous"], + ) assert (tmp_path / _markdown_file_name).is_file() @@ -448,9 +465,7 @@ def test_run_benchmarks(tmp_path): ) output_path.mkdir(parents=True, exist_ok=True) - assert ( - examples_repo_path.is_dir() - ), f"Examples repo not found: {examples_repo_path}" + assert examples_repo_path.is_dir(), f"Examples repo not found: {examples_repo_path}" run_benchmarks( build_path=build_path, @@ -458,5 +473,5 @@ def test_run_benchmarks(tmp_path): previous_bin_path=previous_bin_path, examples_path=examples_repo_path / "examples", output_path=output_path, - excluded=["previous"] + excluded=["previous"], ) diff --git a/distribution/build_dist.py b/distribution/build_dist.py index b1ac5db73a6..f60b76302c6 100644 --- a/distribution/build_dist.py +++ b/distribution/build_dist.py @@ -17,8 +17,12 @@ from modflow_devtools.misc import get_model_paths from build_docs import build_documentation -from build_makefiles import build_mf6_makefile, build_mf5to6_makefile, build_zbud6_makefile -from utils import (get_project_root_path, run_command) +from build_makefiles import ( + build_mf6_makefile, + build_mf5to6_makefile, + build_zbud6_makefile, +) +from utils import get_project_root_path, run_command _project_name = "MODFLOW 6" @@ -44,20 +48,22 @@ "utils", ] -Makefile = namedtuple('Makefile', ['app', 'src_path', 'out_path']) +Makefile = namedtuple("Makefile", ["app", "src_path", "out_path"]) # makefiles included in distribution _makefiles = [ - Makefile(app="mf6", - src_path=_project_root_path / "src", - out_path=Path("make")), - Makefile(app="zbud6", - src_path=_project_root_path / "utils" / "zonebudget" / "src", - out_path=Path("utils") / "zonebudget" / "make"), - Makefile(app="mf5to6", - src_path=_project_root_path / "utils" / "mf5to6" / "src", - out_path=Path("utils") / "mf5to6" / "make") + Makefile(app="mf6", src_path=_project_root_path / "src", out_path=Path("make")), + Makefile( + app="zbud6", + src_path=_project_root_path / "utils" / "zonebudget" / "src", + out_path=Path("utils") / "zonebudget" / "make", + ), + Makefile( + app="mf5to6", + src_path=_project_root_path / "utils" / "mf5to6" / "src", + out_path=Path("utils") / "mf5to6" / "make", + ), ] # system-specific filenames, extensions, etc @@ -145,8 +151,8 @@ def build_examples(examples_repo_path: PathLike, overwrite: bool = False): fname for fname in scripts_folder.glob("*") if fname.suffix == ".py" - and fname.stem.startswith("ex-") - and fname.stem not in exclude_list + and fname.stem.startswith("ex-") + and fname.stem not in exclude_list ] for script in scripts: argv = [ @@ -161,13 +167,17 @@ def build_examples(examples_repo_path: PathLike, overwrite: bool = False): run_command(argv, scripts_folder) -def setup_examples(bin_path: PathLike, examples_path: PathLike, overwrite: bool = False): +def setup_examples( + bin_path: PathLike, examples_path: PathLike, overwrite: bool = False +): examples_path = Path(examples_path).expanduser().absolute() # download example models zip asset latest = get_release("MODFLOW-USGS/modflow6-examples", "latest") assets = latest["assets"] - asset = next(iter([a for a in assets if a["name"] == "modflow6-examples.zip"]), None) + asset = next( + iter([a for a in assets if a["name"] == "modflow6-examples.zip"]), None + ) download_and_unzip(asset["browser_download_url"], examples_path, verbose=True) # list folders with mfsim.nam (recursively) @@ -226,27 +236,33 @@ def test_setup_examples(): pass -def build_programs_meson(build_path: PathLike, bin_path: PathLike, overwrite: bool = False): +def build_programs_meson( + build_path: PathLike, bin_path: PathLike, overwrite: bool = False +): build_path = Path(build_path).expanduser().absolute() bin_path = Path(bin_path).expanduser().absolute() exe_paths = [ bin_path / f"mf6{_eext}", bin_path / f"zbud6{_eext}", - bin_path / f"mf5to6{_eext}" - ] - lib_paths = [ - bin_path / f"libmf6{_soext}" + bin_path / f"mf5to6{_eext}", ] + lib_paths = [bin_path / f"libmf6{_soext}"] - if not overwrite and all(p.is_file() for p in exe_paths) and all(p.is_file() for p in lib_paths): + if ( + not overwrite + and all(p.is_file() for p in exe_paths) + and all(p.is_file() for p in lib_paths) + ): print(f"Binaries already exist:") pprint(exe_paths + lib_paths) else: print(f"Building binaries in {build_path}, installing to {bin_path}") - meson_build(project_path=_project_root_path, build_path=build_path, bin_path=bin_path) + meson_build( + project_path=_project_root_path, build_path=build_path, bin_path=bin_path + ) - for target in (exe_paths + lib_paths): + for target in exe_paths + lib_paths: assert target.is_file(), f"Failed to build {target}" target.chmod(target.stat().st_mode | 0o111) print(f"Execute permission set for {target}") @@ -262,22 +278,37 @@ def build_makefiles(output_path: PathLike): # create and copy mf6 makefile build_mf6_makefile() (output_path / "make").mkdir(parents=True, exist_ok=True) - shutil.copyfile(_project_root_path / "make" / "makefile", output_path / "make" / "makefile") - shutil.copyfile(_project_root_path / "make" / "makedefaults", output_path / "make" / "makedefaults") + shutil.copyfile( + _project_root_path / "make" / "makefile", output_path / "make" / "makefile" + ) + shutil.copyfile( + _project_root_path / "make" / "makedefaults", + output_path / "make" / "makedefaults", + ) # create and copy zbud6 makefile build_zbud6_makefile() rel_path = Path("utils") / "zonebudget" / "make" (output_path / rel_path).mkdir(parents=True, exist_ok=True) - shutil.copyfile(_project_root_path / rel_path / "makefile", output_path / rel_path / "makefile") - shutil.copyfile(_project_root_path / rel_path / "makedefaults", output_path / rel_path / "makedefaults") + shutil.copyfile( + _project_root_path / rel_path / "makefile", output_path / rel_path / "makefile" + ) + shutil.copyfile( + _project_root_path / rel_path / "makedefaults", + output_path / rel_path / "makedefaults", + ) # create and copy mf5to6 makefile build_mf5to6_makefile() rel_path = Path("utils") / "mf5to6" / "make" (output_path / rel_path).mkdir(parents=True, exist_ok=True) - shutil.copyfile(_project_root_path / rel_path / "makefile", output_path / rel_path / "makefile") - shutil.copyfile(_project_root_path / rel_path / "makedefaults", output_path / rel_path / "makedefaults") + shutil.copyfile( + _project_root_path / rel_path / "makefile", output_path / rel_path / "makefile" + ) + shutil.copyfile( + _project_root_path / rel_path / "makedefaults", + output_path / rel_path / "makedefaults", + ) def test_build_makefiles(tmp_path): @@ -296,11 +327,12 @@ def test_build_makefiles(tmp_path): def build_distribution( - build_path: PathLike, - output_path: PathLike, - examples_repo_path: PathLike, - full: bool = False, - overwrite: bool = False): + build_path: PathLike, + output_path: PathLike, + examples_repo_path: PathLike, + full: bool = False, + overwrite: bool = False, +): print(f"Building {'full' if full else 'minimal'} distribution") build_path = Path(build_path).expanduser().absolute() @@ -309,10 +341,9 @@ def build_distribution( # binaries build_programs_meson( - build_path=build_path, - bin_path=output_path / "bin", - overwrite=overwrite) - + build_path=build_path, bin_path=output_path / "bin", overwrite=overwrite + ) + # code.json metadata shutil.copy(_project_root_path / "code.json", output_path) @@ -322,7 +353,8 @@ def build_distribution( setup_examples( bin_path=output_path / "bin", examples_path=output_path / "examples", - overwrite=overwrite) + overwrite=overwrite, + ) # copy source code files copy_sources(output_path=output_path) @@ -337,7 +369,8 @@ def build_distribution( examples_repo_path=examples_repo_path, # benchmarks_path=_benchmarks_path / "run-time-comparison.md", full=full, - overwrite=overwrite) + overwrite=overwrite, + ) @requires_exe("pdflatex") @@ -350,7 +383,7 @@ def test_build_distribution(tmp_path, full): output_path=output_path, examples_repo_path=_examples_repo_path, full=full, - overwrite=True + overwrite=True, ) if full: @@ -363,11 +396,15 @@ def test_build_distribution(tmp_path, full): for exe in ["mf6", "mf5to6", "zbud6"]: assert (output_path / f"{exe}{ext}").is_file() assert ( - output_path - / ( - "libmf6" - + (".so" if system == "Linux" else (".dylib" if system == "Darwin" else ".dll")) + output_path + / ( + "libmf6" + + ( + ".so" + if system == "Linux" + else (".dylib" if system == "Darwin" else ".dll") ) + ) ).is_file() # check mf6io docs @@ -407,7 +444,7 @@ def test_build_distribution(tmp_path, full): "--examples-repo-path", required=False, default=str(_examples_repo_path), - help="Path to directory containing modflow6 example models" + help="Path to directory containing modflow6 example models", ) # parser.add_argument( # "-b", @@ -421,7 +458,7 @@ def test_build_distribution(tmp_path, full): required=False, default=False, action="store_true", - help="Build a full rather than minimal distribution" + help="Build a full rather than minimal distribution", ) parser.add_argument( "-f", @@ -429,7 +466,7 @@ def test_build_distribution(tmp_path, full): required=False, default=False, action="store_true", - help="Recreate and overwrite existing artifacts" + help="Recreate and overwrite existing artifacts", ) args = parser.parse_args() diff --git a/distribution/build_docs.py b/distribution/build_docs.py index 926a3a1c577..efc4c8bbc0c 100644 --- a/distribution/build_docs.py +++ b/distribution/build_docs.py @@ -16,7 +16,12 @@ import pytest from flaky import flaky from modflow_devtools.build import meson_build -from modflow_devtools.download import list_artifacts, download_artifact, get_release, download_and_unzip +from modflow_devtools.download import ( + list_artifacts, + download_artifact, + get_release, + download_and_unzip, +) from modflow_devtools.markers import requires_exe, requires_github from modflow_devtools.misc import set_dir, run_cmd, is_in_ci @@ -103,12 +108,16 @@ def download_benchmarks(output_path: PathLike, verbose: bool = False) -> Optiona name = "run-time-comparison" # todo make configurable repo = "MODFLOW-USGS/modflow6" # todo make configurable, add pytest/cli args artifacts = list_artifacts(repo, name=name, verbose=verbose) - artifacts = sorted(artifacts, key=lambda a: datetime.strptime(a['created_at'], '%Y-%m-%dT%H:%M:%SZ'), reverse=True) + artifacts = sorted( + artifacts, + key=lambda a: datetime.strptime(a["created_at"], "%Y-%m-%dT%H:%M:%SZ"), + reverse=True, + ) most_recent = next(iter(artifacts), None) print(f"Found most recent benchmarks (artifact {most_recent['id']})") if most_recent: print(f"Downloading benchmarks (artifact {most_recent['id']})") - download_artifact(repo, id=most_recent['id'], path=output_path, verbose=verbose) + download_artifact(repo, id=most_recent["id"], path=output_path, verbose=verbose) print(f"Downloaded benchmarks to {output_path}") path = output_path / f"{name}.md" assert path.is_file() @@ -141,13 +150,16 @@ def build_benchmark_tex(output_path: PathLike, overwrite: bool = False): current_bin_path=_project_root_path / "bin", previous_bin_path=_project_root_path / "bin" / "rebuilt", examples_path=_examples_repo_path / "examples", - output_path=output_path) + output_path=output_path, + ) # convert markdown benchmark results to LaTeX with set_dir(_release_notes_path): tex_path = Path("run-time-comparison.tex") tex_path.unlink(missing_ok=True) - out, err, ret = run_cmd(sys.executable, "mk_runtimecomp.py", benchmarks_path, verbose=True) + out, err, ret = run_cmd( + sys.executable, "mk_runtimecomp.py", benchmarks_path, verbose=True + ) assert not ret, out + err assert tex_path.is_file() @@ -177,15 +189,15 @@ def files_match(tex_path, dfn_path, ignored): f.stem for f in dfn_path.glob("*") if f.is_file() - and "dfn" in f.suffix - and not any(pattern in f.name for pattern in ignored) + and "dfn" in f.suffix + and not any(pattern in f.name for pattern in ignored) ] tex_names = [ f.stem.replace("-desc", "") for f in tex_path.glob("*") if f.is_file() - and "tex" in f.suffix - and not any(pattern in f.name for pattern in ignored) + and "tex" in f.suffix + and not any(pattern in f.name for pattern in ignored) ] return set(tex_names) == set(dfn_names) @@ -197,7 +209,12 @@ def files_match(tex_path, dfn_path, ignored): tex_files = [f for f in tex_pth.glob("*") if f.is_file()] dfn_files = [f for f in dfn_pth.glob("*") if f.is_file()] - if not overwrite and any(tex_files) and any(dfn_files) and files_match(tex_pth, dfn_pth, ignored): + if ( + not overwrite + and any(tex_files) + and any(dfn_files) + and files_match(tex_pth, dfn_pth, ignored) + ): print(f"DFN files already exist:") pprint(dfn_files) else: @@ -229,13 +246,13 @@ def test_build_mf6io_tex_from_dfn(overwrite): for p, t in zip(file_paths, file_mtimes): assert overwrite == (p.stat().st_mtime > t) finally: - for p in (file_paths + [ + for p in file_paths + [ # should these be under version control, since they're cleaned in fn above? _project_root_path / "doc" / "ConverterGuide" / "converter_mf5to6.bbl", _project_root_path / "doc" / "ReleaseNotes" / "ReleaseNotes.bbl", _project_root_path / "doc" / "mf6io" / "mf6io.bbl", - _project_root_path / "doc" / "zonebudget" / "zonebudget.bbl" - ]): + _project_root_path / "doc" / "zonebudget" / "zonebudget.bbl", + ]: os.system(f"git restore {p}") @@ -249,7 +266,9 @@ def build_tex_folder_structure(overwrite: bool = False): return with set_dir(_release_notes_path): - out, err, ret = run_cmd(sys.executable, "mk_folder_struct.py", "-dp", _project_root_path) + out, err, ret = run_cmd( + sys.executable, "mk_folder_struct.py", "-dp", _project_root_path + ) assert not ret, out + err assert path.is_file(), f"Failed to create {path}" @@ -263,7 +282,9 @@ def test_build_tex_folder_structure(): os.system(f"git restore {path}") -def build_mf6io_tex_example(workspace_path: PathLike, bin_path: PathLike, example_model_path: PathLike): +def build_mf6io_tex_example( + workspace_path: PathLike, bin_path: PathLike, example_model_path: PathLike +): workspace_path = Path(workspace_path) / "workspace" bin_path = Path(bin_path).expanduser().absolute() mf6_exe_path = bin_path / f"mf6{_eext}" @@ -284,7 +305,7 @@ def build_mf6io_tex_example(workspace_path: PathLike, bin_path: PathLike, exampl # run example model with set_dir(workspace_path): - out, err, ret = run_cmd(cmd) + out, err, ret = run_cmd(cmd, verbose=True) buff = out + err lines = buff.split("\r\n") with open(fname1, "w") as f: @@ -301,7 +322,7 @@ def build_mf6io_tex_example(workspace_path: PathLike, bin_path: PathLike, exampl # run model without a namefile present with set_dir(workspace_path): - out, err, ret = run_cmd(cmd) + out, err, ret = run_cmd(cmd, verbose=True) buff = out + err lines = buff.split("\r\n") with open(fname2, "w") as f: @@ -314,7 +335,7 @@ def build_mf6io_tex_example(workspace_path: PathLike, bin_path: PathLike, exampl with set_dir(workspace_path): # run mf6 command with -h to show help - out, err, ret = run_cmd(str(mf6_exe_path), "-h") + out, err, ret = run_cmd(str(mf6_exe_path), "-h", verbose=True) buff = out + err lines = buff.split("\r\n") with open(fname3, "w") as f: @@ -331,7 +352,12 @@ def test_build_mf6io_tex_example(): pass -def build_pdfs_from_tex(tex_paths: List[PathLike], output_path: PathLike, passes: int = 3, overwrite: bool = False): +def build_pdfs_from_tex( + tex_paths: List[PathLike], + output_path: PathLike, + passes: int = 3, + overwrite: bool = False, +): print(f"Building PDFs from LaTex:") pprint(tex_paths) @@ -394,18 +420,20 @@ def test_build_pdfs_from_tex(tmp_path): try: build_pdfs_from_tex(tex_paths, tmp_path) finally: - for p in (tex_paths[:-1] + bbl_paths): + for p in tex_paths[:-1] + bbl_paths: os.system(f"git restore {p}") -def build_documentation(bin_path: PathLike, - output_path: PathLike, - examples_repo_path: PathLike, - # Example to use to render sample mf6 output in the docs. - # Must be a valid directory in modflow6-examples/examples - example_for_sample: str = "ex-gwf-twri01", - full: bool = False, - overwrite: bool = False): +def build_documentation( + bin_path: PathLike, + output_path: PathLike, + examples_repo_path: PathLike, + # Example to use to render sample mf6 output in the docs. + # Must be a valid directory in modflow6-examples/examples + example_for_sample: str = "ex-gwf-twri01", + full: bool = False, + overwrite: bool = False, +): print(f"Building {'full' if full else 'full'} documentation") bin_path = Path(bin_path).expanduser().absolute() @@ -456,7 +484,9 @@ def build_documentation(bin_path: PathLike, raise # convert LaTex to PDF - build_pdfs_from_tex(tex_paths=_full_dist_tex_paths, output_path=output_path, overwrite=overwrite) + build_pdfs_from_tex( + tex_paths=_full_dist_tex_paths, output_path=output_path, overwrite=overwrite + ) # enforce os line endings on all text files windows_line_endings = True @@ -499,7 +529,13 @@ def test_build_documentation(tmp_path): """ ), ) - parser.add_argument("-t", "--tex-path", action="append", required=False, help="Extra LaTeX files to include") + parser.add_argument( + "-t", + "--tex-path", + action="append", + required=False, + help="Extra LaTeX files to include", + ) parser.add_argument( "-b", "--bin-path", @@ -512,14 +548,14 @@ def test_build_documentation(tmp_path): "--examples-repo-path", required=False, default=str(_examples_repo_path), - help="Path to directory containing modflow6 example models" + help="Path to directory containing modflow6 example models", ) parser.add_argument( "-s", "--example-for-sample", required=False, - default=str(_examples_repo_path), - help="Name of example model to use for sample mf6 output" + default="ex-gwf-twri01", + help="Name of example model to use for sample mf6 output", ) parser.add_argument( "-o", @@ -533,7 +569,7 @@ def test_build_documentation(tmp_path): required=False, default=False, action="store_true", - help="Build docs for a full rather than minimal distribution" + help="Build docs for a full rather than minimal distribution", ) parser.add_argument( "-f", @@ -541,10 +577,12 @@ def test_build_documentation(tmp_path): required=False, default=False, action="store_true", - help="Recreate and overwrite existing artifacts" + help="Recreate and overwrite existing artifacts", ) args = parser.parse_args() - tex_paths = _full_dist_tex_paths + ([Path(p) for p in args.tex_path] if args.tex_path else []) + tex_paths = _full_dist_tex_paths + ( + [Path(p) for p in args.tex_path] if args.tex_path else [] + ) output_path = Path(args.output_path).expanduser().absolute() output_path.mkdir(parents=True, exist_ok=True) bin_path = Path(args.bin_path).expanduser().absolute() @@ -557,4 +595,5 @@ def test_build_documentation(tmp_path): examples_repo_path=examples_repo_path, example_for_sample=example_for_sample, full=args.full, - overwrite=args.force) + overwrite=args.force, + ) diff --git a/distribution/build_makefiles.py b/distribution/build_makefiles.py index ab06a902b14..ce441200775 100644 --- a/distribution/build_makefiles.py +++ b/distribution/build_makefiles.py @@ -108,7 +108,7 @@ def build_mf5to6_makefile(): def test_build_mf6_makefile(): makefile_paths = [ _project_root_path / "make" / "makefile", - _project_root_path / "make" / "makedefaults" + _project_root_path / "make" / "makedefaults", ] makefile_mtimes = [p.stat().st_mtime for p in makefile_paths] @@ -150,7 +150,7 @@ def test_build_mf5to6_makefile(): util_path = _project_root_path / "utils" / "mf5to6" makefile_paths = [ util_path / "make" / "makefile", - util_path / "make" / "makedefaults" + util_path / "make" / "makedefaults", ] makefile_mtimes = [p.stat().st_mtime for p in makefile_paths] diff --git a/distribution/check_dist.py b/distribution/check_dist.py index df7e5ed1069..03a0468a367 100644 --- a/distribution/check_dist.py +++ b/distribution/check_dist.py @@ -3,10 +3,11 @@ import subprocess from os import environ from pathlib import Path +from pprint import pprint import pytest -from utils import get_branch, split_nonnumeric +from utils import split_nonnumeric _system = platform.system() _eext = ".exe" if _system == "Windows" else "" @@ -80,10 +81,18 @@ def test_makefiles(dist_dir_path, full): assert (dist_dir_path / "utils" / "mf5to6" / "make" / "makedefaults").is_file() # makefiles can't be used on Windows with ifort compiler - if _system != 'Windows' or _fc != 'ifort': + if _system != "Windows" or _fc != "ifort": print(subprocess.check_output("make", cwd=dist_dir_path / "make", shell=True)) - print(subprocess.check_output("make", cwd=dist_dir_path / "utils" / "zonebudget" / "make", shell=True)) - print(subprocess.check_output("make", cwd=dist_dir_path / "utils" / "mf5to6" / "make", shell=True)) + print( + subprocess.check_output( + "make", cwd=dist_dir_path / "utils" / "zonebudget" / "make", shell=True + ) + ) + print( + subprocess.check_output( + "make", cwd=dist_dir_path / "utils" / "mf5to6" / "make", shell=True + ) + ) def test_msvs(dist_dir_path, full): @@ -128,11 +137,25 @@ def test_examples(dist_dir_path, full): examples_path = dist_dir_path / "examples" assert examples_path.is_dir() - # test run an example model with the provided script - example_path = next(examples_path.iterdir(), None) - assert example_path - output = ' '.join(subprocess.check_output([str(example_path / f"run{_scext}")], cwd=example_path).decode().split()) - print(output) + # test run all example models with provided script + pprint( + subprocess.check_output( + [str(examples_path / f"runall{_scext}")], cwd=examples_path + ) + ) + + # test run example models individually with provided scripts + example_paths = [ + p for p in examples_path.glob("*") if p.is_dir() and p.stem.startswith("ex") + ] + print(f"{len(example_paths)} example models found:") + pprint(example_paths) + for p in example_paths: + pprint( + subprocess.check_output([str(p / f"run{_scext}")], cwd=p).decode().split() + ) + break + def test_binaries(dist_dir_path, approved): @@ -142,16 +165,16 @@ def test_binaries(dist_dir_path, approved): assert (bin_path / f"mf5to6{_eext}").is_file() assert (bin_path / f"libmf6{_soext}").is_file() - output = ' '.join(subprocess.check_output([str(bin_path / f"mf6{_eext}"), "-v"]).decode().split()).lower() + output = " ".join( + subprocess.check_output([str(bin_path / f"mf6{_eext}"), "-v"]).decode().split() + ).lower() assert output.startswith("mf6") # make sure binaries were built in correct mode assert ("preliminary" in output) != approved # check version string - version = ( - output.lower().split(' ')[1] - ) + version = output.lower().split(" ")[1] print(version) v_split = version.split(".") assert len(v_split) == 3 diff --git a/distribution/conftest.py b/distribution/conftest.py index 66c79609533..860ef1d585b 100644 --- a/distribution/conftest.py +++ b/distribution/conftest.py @@ -3,7 +3,10 @@ from update_version import Version _project_root_path = Path(__file__).resolve().parent.parent -_dist_dir_path = _project_root_path.parent / f"mf{str(Version.from_file(_project_root_path / 'version.txt'))}" +_dist_dir_path = ( + _project_root_path.parent + / f"mf{str(Version.from_file(_project_root_path / 'version.txt'))}" +) def pytest_addoption(parser): diff --git a/distribution/update_version.py b/distribution/update_version.py index 17308d4771e..f1112c58761 100755 --- a/distribution/update_version.py +++ b/distribution/update_version.py @@ -112,7 +112,6 @@ def from_file(cls, path: PathLike) -> "Version": return cls(major=vmajor, minor=vminor, patch=vpatch, label=vlabel) - _approved_fmtdisclaimer = ''' character(len=*), parameter :: FMTDISCLAIMER = & "(/,& &'This software has been approved for release by the U.S. Geological ',/,& @@ -180,9 +179,7 @@ def log_update(path, version: Version): print(f"Updated {path} with version {version}") -def update_version_txt_and_py( - version: Version, timestamp: datetime -): +def update_version_txt_and_py(version: Version, timestamp: datetime): with open(version_file_path, "w") as f: f.write( f"# {project_name} version file automatically " @@ -193,7 +190,9 @@ def update_version_txt_and_py( f.write(f"major = {version.major}\n") f.write(f"minor = {version.minor}\n") f.write(f"micro = {version.patch}\n") - f.write("label = " + (("'" + version.label + "'") if version.label else "''") + "\n") + f.write( + "label = " + (("'" + version.label + "'") if version.label else "''") + "\n" + ) f.write("__version__ = '{:d}.{:d}.{:d}'.format(major, minor, micro)\n") f.write("if label:\n") f.write("\t__version__ += '{}{}'.format(__version__, label)") @@ -215,9 +214,7 @@ def update_meson_build(version: Version): log_update(path, version) -def update_version_tex( - version: Version, timestamp: datetime -): +def update_version_tex(version: Version, timestamp: datetime): path = project_root_path / "doc" / "version.tex" with open(path, "w") as f: line = "\\newcommand{\\modflowversion}{mf" + f"{str(version)}" + "}" @@ -238,7 +235,7 @@ def update_version_f90( version: Optional[Version], timestamp: datetime, approved: bool = False, - developmode: bool = True + developmode: bool = True, ): path = project_root_path / "src" / "Utilities" / "version.f90" lines = open(path, "r").read().splitlines() @@ -269,7 +266,10 @@ def update_version_f90( fmat_tstmp = timestamp.strftime("%m/%d/%Y") label_clause = version_label if version_label else "" label_clause += " (preliminary)" if not approved else "" - line = line.rpartition("::")[0] + f":: VERSIONTAG = '{label_clause} {fmat_tstmp}'" + line = ( + line.rpartition("::")[0] + + f":: VERSIONTAG = '{label_clause} {fmat_tstmp}'" + ) elif ":: FMTDISCLAIMER =" in line: line = get_disclaimer(approved, formatted=True) skip = True @@ -277,9 +277,7 @@ def update_version_f90( log_update(path, version) -def update_readme_and_disclaimer( - version: Version, approved: bool = False -): +def update_readme_and_disclaimer(version: Version, approved: bool = False): disclaimer = get_disclaimer(approved, formatted=False) readme_path = str(project_root_path / "README.md") readme_lines = open(readme_path, "r").read().splitlines() @@ -339,7 +337,7 @@ def update_version( version: Version = None, timestamp: datetime = datetime.now(), approved: bool = False, - developmode: bool = True + developmode: bool = True, ): """ Update version information stored in version.txt in the project root, @@ -379,9 +377,20 @@ def update_version( @pytest.mark.skip(reason="reverts repo files on cleanup, tread carefully") @pytest.mark.parametrize( "version", - [None, - Version(major=_initial_version.major, minor=_initial_version.minor, patch=_initial_version.patch), - Version(major=_initial_version.major, minor=_initial_version.minor, patch=_initial_version.patch, label="rc")], + [ + None, + Version( + major=_initial_version.major, + minor=_initial_version.minor, + patch=_initial_version.patch, + ), + Version( + major=_initial_version.major, + minor=_initial_version.minor, + patch=_initial_version.patch, + label="rc", + ), + ], ) @pytest.mark.parametrize("approved", [True, False]) @pytest.mark.parametrize("developmode", [True, False]) @@ -394,7 +403,7 @@ def test_update_version(version, approved, developmode): timestamp=timestamp, version=version, approved=approved, - developmode=developmode + developmode=developmode, ) updated = Version.from_file(version_file_path) @@ -419,11 +428,13 @@ def test_update_version(version, approved, developmode): assert updated.label == version.label if version.label is not None: assert updated.label == _initial_version - + # check IDEVELOPMODE was set correctly version_f90_path = project_root_path / "src" / "Utilities" / "version.f90" lines = version_f90_path.read_text().splitlines() - assert any(f"IDEVELOPMODE = {1 if developmode else 0}" in line for line in lines) + assert any( + f"IDEVELOPMODE = {1 if developmode else 0}" in line for line in lines + ) # check disclaimer has appropriate language disclaimer_path = project_root_path / "DISCLAIMER.md" @@ -459,7 +470,7 @@ def test_update_version(version, approved, developmode): may not be provided. Likewise, if any of the latter are provided, the version number must not be specified. """ - ) + ), ) parser.add_argument( "-v", @@ -538,5 +549,5 @@ def test_update_version(version, approved, developmode): version=version, timestamp=datetime.now(), approved=args.approved, - developmode=not args.releasemode + developmode=not args.releasemode, ) diff --git a/distribution/utils.py b/distribution/utils.py index ca0a634e744..a301987e51c 100644 --- a/distribution/utils.py +++ b/distribution/utils.py @@ -143,4 +143,4 @@ def test_convert_line_endings(): def split_nonnumeric(s): match = re.compile("[^0-9]").search(s) - return [s[:match.start()], s[match.start():]] if match else s + return [s[: match.start()], s[match.start() :]] if match else s From 1f962c1b2c42cef2739dfa7b50793c35e54b7fed Mon Sep 17 00:00:00 2001 From: langevin-usgs Date: Thu, 22 Jun 2023 10:54:44 -0500 Subject: [PATCH 113/123] feat(release): minor updates for release (#1266) * add information about drain discharge scaling * add warning if ATS used for steady state period * Close #769 --- doc/mf6io/mf6ivar/dfn/gwf-drn.dfn | 2 +- doc/mf6io/mf6ivar/dfn/gwf-uzf.dfn | 2 +- doc/mf6io/mf6ivar/md/mf6ivar.md | 4 ++-- doc/mf6io/mf6ivar/tex/gwf-drn-desc.tex | 2 +- doc/mf6io/mf6ivar/tex/gwf-uzf-desc.tex | 2 +- src/Model/GroundWaterFlow/gwf3.f90 | 31 ++++++++++++++++++++++++++ src/Timing/ats.f90 | 1 - 7 files changed, 37 insertions(+), 7 deletions(-) diff --git a/doc/mf6io/mf6ivar/dfn/gwf-drn.dfn b/doc/mf6io/mf6ivar/dfn/gwf-drn.dfn index 6116ff13cf1..c03a1078000 100644 --- a/doc/mf6io/mf6ivar/dfn/gwf-drn.dfn +++ b/doc/mf6io/mf6ivar/dfn/gwf-drn.dfn @@ -26,7 +26,7 @@ shape reader urword optional true longname name of auxiliary variable for drainage depth -description name of a variable listed in AUXILIARY that defines the depth at which drainage discharge will be scaled. If a positive value is specified for the AUXDEPTHNAME AUXILIARY variable, then ELEV is the elevation at which the drain starts to discharge and ELEV + DDRN (assuming DDRN is the AUXDEPTHNAME variable) is the elevation when the drain conductance (COND) scaling factor is 1. If a negative drainage depth value is specified for DDRN, then ELEV + DDRN is the elevation at which the drain starts to discharge and ELEV is the elevation when the conductance (COND) scaling factor is 1. A linear- or cubic-scaling is used to scale the drain conductance (COND) when the Standard or Newton-Raphson Formulation is used, respectively. +description name of a variable listed in AUXILIARY that defines the depth at which drainage discharge will be scaled. If a positive value is specified for the AUXDEPTHNAME AUXILIARY variable, then ELEV is the elevation at which the drain starts to discharge and ELEV + DDRN (assuming DDRN is the AUXDEPTHNAME variable) is the elevation when the drain conductance (COND) scaling factor is 1. If a negative drainage depth value is specified for DDRN, then ELEV + DDRN is the elevation at which the drain starts to discharge and ELEV is the elevation when the conductance (COND) scaling factor is 1. A linear- or cubic-scaling is used to scale the drain conductance (COND) when the Standard or Newton-Raphson Formulation is used, respectively. This discharge scaling option is described in more detail in Chapter 3 of the Supplemental Technical Information. block options name boundnames diff --git a/doc/mf6io/mf6ivar/dfn/gwf-uzf.dfn b/doc/mf6io/mf6ivar/dfn/gwf-uzf.dfn index a199cf880e8..02833c08967 100644 --- a/doc/mf6io/mf6ivar/dfn/gwf-uzf.dfn +++ b/doc/mf6io/mf6ivar/dfn/gwf-uzf.dfn @@ -312,7 +312,7 @@ tagged true reader urword optional true longname activate seepage -description keyword specifying that groundwater discharge (GWSEEP) to land surface will be simulated. Groundwater discharge is nonzero when groundwater head is greater than land surface. +description keyword specifying that groundwater discharge (GWSEEP) to land surface will be simulated. Groundwater discharge is nonzero when groundwater head is greater than land surface. This option is no longer recommended; a better approach is to use the Drain Package with discharge scaling as a way to handle seepage to land surface. The Drain Package with discharge scaling is described in Chapter 3 of the Supplemental Technical Information. block options name unsat_etwc diff --git a/doc/mf6io/mf6ivar/md/mf6ivar.md b/doc/mf6io/mf6ivar/md/mf6ivar.md index 3402680d544..796bc5bcfeb 100644 --- a/doc/mf6io/mf6ivar/md/mf6ivar.md +++ b/doc/mf6io/mf6ivar/md/mf6ivar.md @@ -351,7 +351,7 @@ | GWF | WEL | PERIOD | BOUNDNAME | STRING | name of the well cell. BOUNDNAME is an ASCII character variable that can contain as many as 40 characters. If BOUNDNAME contains spaces in it, then the entire name must be enclosed within single quotes. | | GWF | DRN | OPTIONS | AUXILIARY | STRING (NAUX) | defines an array of one or more auxiliary variable names. There is no limit on the number of auxiliary variables that can be provided on this line; however, lists of information provided in subsequent blocks must have a column of data for each auxiliary variable name defined here. The number of auxiliary variables detected on this line determines the value for naux. Comments cannot be provided anywhere on this line as they will be interpreted as auxiliary variable names. Auxiliary variables may not be used by the package, but they will be available for use by other parts of the program. The program will terminate with an error if auxiliary variables are specified on more than one line in the options block. | | GWF | DRN | OPTIONS | AUXMULTNAME | STRING | name of auxiliary variable to be used as multiplier of drain conductance. | -| GWF | DRN | OPTIONS | AUXDEPTHNAME | STRING | name of a variable listed in AUXILIARY that defines the depth at which drainage discharge will be scaled. If a positive value is specified for the AUXDEPTHNAME AUXILIARY variable, then ELEV is the elevation at which the drain starts to discharge and ELEV + DDRN (assuming DDRN is the AUXDEPTHNAME variable) is the elevation when the drain conductance (COND) scaling factor is 1. If a negative drainage depth value is specified for DDRN, then ELEV + DDRN is the elevation at which the drain starts to discharge and ELEV is the elevation when the conductance (COND) scaling factor is 1. A linear- or cubic-scaling is used to scale the drain conductance (COND) when the Standard or Newton-Raphson Formulation is used, respectively. | +| GWF | DRN | OPTIONS | AUXDEPTHNAME | STRING | name of a variable listed in AUXILIARY that defines the depth at which drainage discharge will be scaled. If a positive value is specified for the AUXDEPTHNAME AUXILIARY variable, then ELEV is the elevation at which the drain starts to discharge and ELEV + DDRN (assuming DDRN is the AUXDEPTHNAME variable) is the elevation when the drain conductance (COND) scaling factor is 1. If a negative drainage depth value is specified for DDRN, then ELEV + DDRN is the elevation at which the drain starts to discharge and ELEV is the elevation when the conductance (COND) scaling factor is 1. A linear- or cubic-scaling is used to scale the drain conductance (COND) when the Standard or Newton-Raphson Formulation is used, respectively. This discharge scaling option is described in more detail in Chapter 3 of the Supplemental Technical Information. | | GWF | DRN | OPTIONS | BOUNDNAMES | KEYWORD | keyword to indicate that boundary names may be provided with the list of drain cells. | | GWF | DRN | OPTIONS | PRINT_INPUT | KEYWORD | keyword to indicate that the list of drain information will be written to the listing file immediately after it is read. | | GWF | DRN | OPTIONS | PRINT_FLOWS | KEYWORD | keyword to indicate that the list of drain flow rates will be printed to the listing file for every stress period time step in which ``BUDGET PRINT'' is specified in Output Control. If there is no Output Control option and ``PRINT\_FLOWS'' is specified, then flow rates are printed for the last time step of each stress period. | @@ -717,7 +717,7 @@ | GWF | UZF | OPTIONS | SIMULATE_ET | KEYWORD | keyword specifying that ET in the unsaturated (UZF) and saturated zones (GWF) will be simulated. ET can be simulated in the UZF cell and not the GWF cell by omitting keywords LINEAR\_GWET and SQUARE\_GWET. | | GWF | UZF | OPTIONS | LINEAR_GWET | KEYWORD | keyword specifying that groundwater ET will be simulated using the original ET formulation of MODFLOW-2005. | | GWF | UZF | OPTIONS | SQUARE_GWET | KEYWORD | keyword specifying that groundwater ET will be simulated by assuming a constant ET rate for groundwater levels between land surface (TOP) and land surface minus the ET extinction depth (TOP-EXTDP). Groundwater ET is smoothly reduced from the PET rate to zero over a nominal interval at TOP-EXTDP. | -| GWF | UZF | OPTIONS | SIMULATE_GWSEEP | KEYWORD | keyword specifying that groundwater discharge (GWSEEP) to land surface will be simulated. Groundwater discharge is nonzero when groundwater head is greater than land surface. | +| GWF | UZF | OPTIONS | SIMULATE_GWSEEP | KEYWORD | keyword specifying that groundwater discharge (GWSEEP) to land surface will be simulated. Groundwater discharge is nonzero when groundwater head is greater than land surface. This option is no longer recommended; a better approach is to use the Drain Package with discharge scaling as a way to handle seepage to land surface. The Drain Package with discharge scaling is described in Chapter 3 of the Supplemental Technical Information. | | GWF | UZF | OPTIONS | UNSAT_ETWC | KEYWORD | keyword specifying that ET in the unsaturated zone will be simulated as a function of the specified PET rate while the water content (THETA) is greater than the ET extinction water content (EXTWC). | | GWF | UZF | OPTIONS | UNSAT_ETAE | KEYWORD | keyword specifying that ET in the unsaturated zone will be simulated using a capillary pressure based formulation. Capillary pressure is calculated using the Brooks-Corey retention function. | | GWF | UZF | DIMENSIONS | NUZFCELLS | INTEGER | is the number of UZF cells. More than one UZF cell can be assigned to a GWF cell; however, only one GWF cell can be assigned to a single UZF cell. If more than one UZF cell is assigned to a GWF cell, then an auxiliary variable should be used to reduce the surface area of the UZF cell with the AUXMULTNAME option. | diff --git a/doc/mf6io/mf6ivar/tex/gwf-drn-desc.tex b/doc/mf6io/mf6ivar/tex/gwf-drn-desc.tex index d8234a8be3a..a73dab42ee5 100644 --- a/doc/mf6io/mf6ivar/tex/gwf-drn-desc.tex +++ b/doc/mf6io/mf6ivar/tex/gwf-drn-desc.tex @@ -7,7 +7,7 @@ \item \texttt{auxmultname}---name of auxiliary variable to be used as multiplier of drain conductance. -\item \texttt{auxdepthname}---name of a variable listed in AUXILIARY that defines the depth at which drainage discharge will be scaled. If a positive value is specified for the AUXDEPTHNAME AUXILIARY variable, then ELEV is the elevation at which the drain starts to discharge and ELEV + DDRN (assuming DDRN is the AUXDEPTHNAME variable) is the elevation when the drain conductance (COND) scaling factor is 1. If a negative drainage depth value is specified for DDRN, then ELEV + DDRN is the elevation at which the drain starts to discharge and ELEV is the elevation when the conductance (COND) scaling factor is 1. A linear- or cubic-scaling is used to scale the drain conductance (COND) when the Standard or Newton-Raphson Formulation is used, respectively. +\item \texttt{auxdepthname}---name of a variable listed in AUXILIARY that defines the depth at which drainage discharge will be scaled. If a positive value is specified for the AUXDEPTHNAME AUXILIARY variable, then ELEV is the elevation at which the drain starts to discharge and ELEV + DDRN (assuming DDRN is the AUXDEPTHNAME variable) is the elevation when the drain conductance (COND) scaling factor is 1. If a negative drainage depth value is specified for DDRN, then ELEV + DDRN is the elevation at which the drain starts to discharge and ELEV is the elevation when the conductance (COND) scaling factor is 1. A linear- or cubic-scaling is used to scale the drain conductance (COND) when the Standard or Newton-Raphson Formulation is used, respectively. This discharge scaling option is described in more detail in Chapter 3 of the Supplemental Technical Information. \item \texttt{BOUNDNAMES}---keyword to indicate that boundary names may be provided with the list of drain cells. diff --git a/doc/mf6io/mf6ivar/tex/gwf-uzf-desc.tex b/doc/mf6io/mf6ivar/tex/gwf-uzf-desc.tex index 6bbd3b03034..88f4401eddc 100644 --- a/doc/mf6io/mf6ivar/tex/gwf-uzf-desc.tex +++ b/doc/mf6io/mf6ivar/tex/gwf-uzf-desc.tex @@ -51,7 +51,7 @@ \item \texttt{SQUARE\_GWET}---keyword specifying that groundwater ET will be simulated by assuming a constant ET rate for groundwater levels between land surface (TOP) and land surface minus the ET extinction depth (TOP-EXTDP). Groundwater ET is smoothly reduced from the PET rate to zero over a nominal interval at TOP-EXTDP. -\item \texttt{SIMULATE\_GWSEEP}---keyword specifying that groundwater discharge (GWSEEP) to land surface will be simulated. Groundwater discharge is nonzero when groundwater head is greater than land surface. +\item \texttt{SIMULATE\_GWSEEP}---keyword specifying that groundwater discharge (GWSEEP) to land surface will be simulated. Groundwater discharge is nonzero when groundwater head is greater than land surface. This option is no longer recommended; a better approach is to use the Drain Package with discharge scaling as a way to handle seepage to land surface. The Drain Package with discharge scaling is described in Chapter 3 of the Supplemental Technical Information. \item \texttt{UNSAT\_ETWC}---keyword specifying that ET in the unsaturated zone will be simulated as a function of the specified PET rate while the water content (THETA) is greater than the ET extinction water content (EXTWC). diff --git a/src/Model/GroundWaterFlow/gwf3.f90 b/src/Model/GroundWaterFlow/gwf3.f90 index 7dbe2491252..de101564f53 100644 --- a/src/Model/GroundWaterFlow/gwf3.f90 +++ b/src/Model/GroundWaterFlow/gwf3.f90 @@ -95,6 +95,7 @@ module GwfModule procedure, private :: create_bndpkgs procedure, private :: create_lstfile procedure, private :: log_namfile_options + procedure, private :: steady_period_check ! end type GwfModelType @@ -376,6 +377,9 @@ subroutine gwf_rp(this) call packobj%bnd_rp_obs() end do ! + ! -- Check for steady state period + call this%steady_period_check() + ! ! -- Return return end subroutine gwf_rp @@ -1655,4 +1659,31 @@ subroutine log_namfile_options(this, found) write (this%iout, '(1x,a)') 'END NAMEFILE OPTIONS:' end subroutine log_namfile_options + !> @brief Check for steady state period + !! + !! Write warning message if steady state + !! period and adaptive time stepping is + !! active for the period + !! + !< + subroutine steady_period_check(this) + ! -- modules + use TdisModule, only: kper + use AdaptiveTimeStepModule, only: isAdaptivePeriod + use SimVariablesModule, only: warnmsg + use SimModule, only: store_warning + ! -- dummy + class(GwfModelType) :: this + if (this%iss == 1) then + if (isAdaptivePeriod(kper)) then + write (warnmsg, '(a,a,a,i0,a)') & + 'GWF Model (', trim(this%name), ') is steady state for period ', & + kper, ' and adaptive time stepping is active. Adaptive time & + &stepping may not work properly for steady-state conditions.' + call store_warning(warnmsg) + end if + end if + return + end subroutine steady_period_check + end module GwfModule diff --git a/src/Timing/ats.f90 b/src/Timing/ats.f90 index 1fca4d34010..30f44f3c548 100644 --- a/src/Timing/ats.f90 +++ b/src/Timing/ats.f90 @@ -1,6 +1,5 @@ ! Outstanding issues for future work: ! CSUB state advance/restore -! Ensure ATS not specified for steady state period ! Add courant time step constraint and other stability controls for GWT model module AdaptiveTimeStepModule From dd3e9b9ab11cf3e39f3be48d3f27d4a67b83c659 Mon Sep 17 00:00:00 2001 From: w-bonelli Date: Thu, 22 Jun 2023 22:16:37 -0400 Subject: [PATCH 114/123] ci: debug release workflow (#1268) * use github token to avoid rate limit * only run one example in check_dist.py * upload zipped versions on all platforms * fix release_dispatch.yml set_options condition --- .github/workflows/release.yml | 47 +++++++++++--------------- .github/workflows/release_dispatch.yml | 1 + distribution/check_dist.py | 11 +----- 3 files changed, 22 insertions(+), 37 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0ee6121c112..50ce55846f8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -303,6 +303,8 @@ jobs: - name: Install dependencies for ex-gwf-twri example model working-directory: modflow6-examples/etc + env: + GITHUB_TOKEN: ${{ github.token }} run: | # install extra Python packages pip install -r requirements.pip.txt @@ -424,18 +426,23 @@ jobs: - name: Download artifacts uses: actions/download-artifact@v3 with: - # download artifacts to OS-specific directory path: ${{ needs.build.outputs.distname }}_${{ matrix.ostag }} - - - name: Select artifacts for OS + + - name: Select artifacts + working-directory: ${{ needs.build.outputs.distname }}_${{ matrix.ostag }} run: | - # rename bin dir for current OS - distname="${{ needs.build.outputs.distname }}_${{ matrix.ostag }}" - mv "$distname/bin-${{ runner.os }}" "$distname/bin" - - # remove binaries for other OSes - rm -rf $distname/bin-* - ls "$distname" + echo "selecting ${{ matrix.ostag }} artifacts" + # remove dists for other systems + rm -rf ${{ needs.build.outputs.distname }}_* + # remove release notes + rm -rf release_notes + # rename dist bin directory + mv bin-${{ runner.os }} bin + # remove binaries for other systems + rm -rf bin-* + + echo "dist directory contains:" + ls - name: Install dependencies for example models env: @@ -464,6 +471,7 @@ jobs: run: python update_flopy.py - name: Build example models + if: inputs.full == true working-directory: modflow6-examples/etc run: python ci_build_files.py @@ -568,29 +576,14 @@ jobs: cmd="$cmd --full" fi eval "$cmd" - - - name: Unzip (Windows) - if: runner.os == 'Windows' - run: | - distname="${{ needs.build.outputs.distname }}_${{ matrix.ostag }}" - 7z x $distname.zip -o$distname - rm -rf "$distname/$distname" - - name: Upload distribution zipfile artifact - if: runner.os != 'Windows' + - name: Upload distribution uses: actions/upload-artifact@v3 with: name: "${{ needs.build.outputs.distname }}_${{ matrix.ostag }}" path: "${{ needs.build.outputs.distname }}_${{ matrix.ostag }}.zip" - - name: Upload distribution zipfile artifact - if: runner.os == 'Windows' - uses: actions/upload-artifact@v3 - with: - name: "${{ needs.build.outputs.distname }}_${{ matrix.ostag }}" - path: "${{ needs.build.outputs.distname }}_${{ matrix.ostag }}" - - - name: Upload release notes artifact + - name: Upload release notes if: runner.os == 'Linux' uses: actions/upload-artifact@v3 with: diff --git a/.github/workflows/release_dispatch.yml b/.github/workflows/release_dispatch.yml index 57b02cdf5d1..bcfce4c3d3e 100644 --- a/.github/workflows/release_dispatch.yml +++ b/.github/workflows/release_dispatch.yml @@ -46,6 +46,7 @@ on: jobs: set_options: name: Set release options + if: github.ref_name != 'master' runs-on: ubuntu-22.04 defaults: run: diff --git a/distribution/check_dist.py b/distribution/check_dist.py index 03a0468a367..27be7d087bd 100644 --- a/distribution/check_dist.py +++ b/distribution/check_dist.py @@ -133,18 +133,9 @@ def test_examples(dist_dir_path, full): if not full: pytest.skip(reason="examples not included in minimal distribution") - # make sure examples dir exists examples_path = dist_dir_path / "examples" assert examples_path.is_dir() - - # test run all example models with provided script - pprint( - subprocess.check_output( - [str(examples_path / f"runall{_scext}")], cwd=examples_path - ) - ) - - # test run example models individually with provided scripts + assert (examples_path / f"runall{_scext}").is_file() example_paths = [ p for p in examples_path.glob("*") if p.is_dir() and p.stem.startswith("ex") ] From cb11acfb388dd53bd7e115f796e004f436da6588 Mon Sep 17 00:00:00 2001 From: langevin-usgs Date: Fri, 23 Jun 2023 11:45:31 -0500 Subject: [PATCH 115/123] refactor(errmsg): refactor error messages (#1267) * Use lower case for all error messages * Remove leading spaces from error messages * Remove ***ERROR prefix from error messages * Close #758 --- src/Exchange/GhostNode.f90 | 23 +++-- src/Exchange/GwfGwfExchange.f90 | 32 +++--- src/Exchange/GwtGwtExchange.f90 | 28 +++--- src/Model/Connection/GwfGwfConnection.f90 | 10 +- src/Model/Connection/GwtGwtConnection.f90 | 6 +- .../Connection/SpatialModelConnection.f90 | 4 +- src/Model/Geometry/CircularGeometry.f90 | 2 +- src/Model/Geometry/RectangularGeometry.f90 | 2 +- src/Model/GroundWaterFlow/gwf3.f90 | 8 +- src/Model/GroundWaterFlow/gwf3buy8.f90 | 4 +- src/Model/GroundWaterFlow/gwf3disv8.f90 | 7 +- src/Model/GroundWaterFlow/gwf3drn8.f90 | 8 +- src/Model/GroundWaterFlow/gwf3evt8.f90 | 26 ++--- src/Model/GroundWaterFlow/gwf3hfb8.f90 | 6 +- src/Model/GroundWaterFlow/gwf3ic8.f90 | 4 +- src/Model/GroundWaterFlow/gwf3mvr8.f90 | 34 +++---- src/Model/GroundWaterFlow/gwf3npf8.f90 | 8 +- src/Model/GroundWaterFlow/gwf3rch8.f90 | 4 +- src/Model/GroundWaterFlow/gwf3sto8.f90 | 8 +- src/Model/GroundWaterFlow/gwf3vsc8.f90 | 6 +- src/Model/GroundWaterTransport/gwt1.f90 | 14 +-- src/Model/GroundWaterTransport/gwt1adv1.f90 | 10 +- src/Model/GroundWaterTransport/gwt1apt1.f90 | 30 +++--- src/Model/GroundWaterTransport/gwt1cnc1.f90 | 4 +- src/Model/GroundWaterTransport/gwt1dsp1.f90 | 2 +- src/Model/GroundWaterTransport/gwt1fmi1.f90 | 98 +++++++++---------- src/Model/GroundWaterTransport/gwt1ic1.f90 | 4 +- src/Model/GroundWaterTransport/gwt1ist1.f90 | 24 ++--- src/Model/GroundWaterTransport/gwt1lkt1.f90 | 2 +- src/Model/GroundWaterTransport/gwt1mst1.f90 | 60 ++++++------ src/Model/GroundWaterTransport/gwt1mvt1.f90 | 8 +- src/Model/GroundWaterTransport/gwt1mwt1.f90 | 2 +- src/Model/GroundWaterTransport/gwt1obs1.f90 | 2 +- src/Model/GroundWaterTransport/gwt1sft1.f90 | 2 +- src/Model/GroundWaterTransport/gwt1ssm1.f90 | 46 ++++----- src/Model/GroundWaterTransport/gwt1uzt1.f90 | 2 +- src/Model/ModelUtilities/BoundaryPackage.f90 | 14 +-- src/Model/ModelUtilities/Connections.f90 | 10 +- src/Model/ModelUtilities/GwfMvrPeriodData.f90 | 4 +- src/Model/ModelUtilities/GwtSpc.f90 | 22 ++--- src/Model/ModelUtilities/Mover.f90 | 22 ++--- .../ModelUtilities/SfrCrossSectionManager.f90 | 16 +-- src/Model/NumericalPackage.f90 | 14 +-- src/RunControlFactory.F90 | 4 +- src/SimulationCreate.f90 | 74 +++++++------- src/Solution/LinearMethods/ims8base.f90 | 4 +- src/Solution/LinearMethods/ims8linear.f90 | 16 +-- src/Solution/NumericalSolution.f90 | 42 ++++---- src/Timing/ats.f90 | 40 ++++---- src/Timing/tdis.f90 | 26 ++--- src/Utilities/Idm/DefinitionSelect.f90 | 4 +- .../Idm/mf6blockfile/LoadMf6File.f90 | 3 +- src/Utilities/ListReader.f90 | 13 ++- src/Utilities/Sim.f90 | 2 +- src/Utilities/sort.f90 | 4 +- 55 files changed, 431 insertions(+), 443 deletions(-) diff --git a/src/Exchange/GhostNode.f90 b/src/Exchange/GhostNode.f90 index 82af8a4617e..d6e124cf1f7 100644 --- a/src/Exchange/GhostNode.f90 +++ b/src/Exchange/GhostNode.f90 @@ -96,12 +96,12 @@ subroutine gnc_df(this, m1, m2) ! -- modules use NumericalModelModule, only: NumericalModelType use SimModule, only: store_error, store_error_unit + use SimVariablesModule, only: errmsg ! -- dummy class(GhostNodeType) :: this class(NumericalModelType), target :: m1 class(NumericalModelType), target, optional :: m2 ! -- local - character(len=LINELENGTH) :: errmsg ! ------------------------------------------------------------------------------ ! ! -- Point or set attributes @@ -132,7 +132,7 @@ subroutine gnc_df(this, m1, m2) ! -- Trap for implicit gnc but models are in different solutions if (this%m1%idsoln /= this%m2%idsoln) then if (this%implicit) then - write (errmsg, '(a)') 'Error. GNC is implicit but models are in '// & + write (errmsg, '(a)') 'GNC is implicit but models are in '// & 'different solutions.' call store_error(errmsg) call store_error_unit(this%inunit) @@ -198,11 +198,11 @@ subroutine gnc_mc(this, matrix_sln) ! ------------------------------------------------------------------------------ ! -- modules use SimModule, only: store_error, store_error_unit, count_errors + use SimVariablesModule, only: errmsg ! -- dummy class(GhostNodeType) :: this class(MatrixBaseType), pointer :: matrix_sln ! -- local - character(len=LINELENGTH) :: errmsg integer(I4B) :: noden, nodem, ipos, ignc, jidx, nodej ! -- formats character(len=*), parameter :: fmterr = & @@ -740,10 +740,11 @@ subroutine read_options(this) ! ------------------------------------------------------------------------------ ! -- modules use SimModule, only: store_error + use SimVariablesModule, only: errmsg ! -- dummy class(GhostNodeType) :: this ! -- local - character(len=LINELENGTH) :: errmsg, keyword + character(len=LINELENGTH) :: keyword integer(I4B) :: ierr logical :: isfound, endOfBlock ! ------------------------------------------------------------------------------ @@ -776,7 +777,7 @@ subroutine read_options(this) this%implicit = .false. write (this%iout, '(4x,a)') 'GHOST NODE CORRECTION IS EXPLICIT.' case default - write (errmsg, '(4x,a,a)') '****ERROR. UNKNOWN GNC OPTION: ', & + write (errmsg, '(a,a)') 'Unknown GNC option: ', & trim(keyword) call store_error(errmsg) call this%parser%StoreErrorUnit() @@ -802,10 +803,11 @@ subroutine read_dimensions(this) ! ------------------------------------------------------------------------------ ! -- modules use SimModule, only: store_error + use SimVariablesModule, only: errmsg ! -- dummy class(GhostNodeType) :: this ! -- local - character(len=LINELENGTH) :: errmsg, keyword + character(len=LINELENGTH) :: keyword integer(I4B) :: ierr logical :: isfound, endOfBlock ! ------------------------------------------------------------------------------ @@ -829,7 +831,7 @@ subroutine read_dimensions(this) this%numjs = this%parser%GetInteger() write (this%iout, '(4x,a,i7)') 'NUMAPHAJ = ', this%numjs case default - write (errmsg, '(4x,a,a)') '****ERROR. UNKNOWN GNC DIMENSION: ', & + write (errmsg, '(a,a)') 'Unknown GNC dimension: ', & trim(keyword) call store_error(errmsg) call this%parser%StoreErrorUnit() @@ -854,10 +856,11 @@ subroutine read_data(this) ! ------------------------------------------------------------------------------ ! -- modules use SimModule, only: store_error, count_errors + use SimVariablesModule, only: errmsg ! -- dummy class(GhostNodeType) :: this ! -- local - character(len=LINELENGTH) :: line, errmsg, nodestr, fmtgnc, cellid, & + character(len=LINELENGTH) :: line, nodestr, fmtgnc, cellid, & cellidm, cellidn integer(I4B) :: lloc, ierr, ival integer(I4B) :: ignc, jidx, nodeun, nodeum, nerr @@ -970,7 +973,7 @@ subroutine read_data(this) ! write (this%iout, '(1x,a)') 'END OF GNCDATA' else - write (errmsg, '(1x,a)') 'ERROR. REQUIRED GNCDATA BLOCK NOT FOUND.' + write (errmsg, '(a)') 'Required GNCDATA block not found.' call store_error(errmsg) call this%parser%StoreErrorUnit() end if @@ -992,13 +995,13 @@ subroutine nodeu_to_noder(this, nodeu, noder, model) ! -- modules use NumericalModelModule, only: NumericalModelType use SimModule, only: store_error + use SimVariablesModule, only: errmsg ! -- dummy class(GhostNodeType) :: this integer(I4B), intent(in) :: nodeu integer(I4B), intent(inout) :: noder class(NumericalModelType), intent(in) :: model ! -- local - character(len=LINELENGTH) :: errmsg ! ------------------------------------------------------------------------------ ! if (nodeu < 1 .or. nodeu > model%dis%nodesuser) then diff --git a/src/Exchange/GwfGwfExchange.f90 b/src/Exchange/GwfGwfExchange.f90 index b8ea6954c45..ad89b404457 100644 --- a/src/Exchange/GwfGwfExchange.f90 +++ b/src/Exchange/GwfGwfExchange.f90 @@ -228,9 +228,9 @@ subroutine gwf_gwf_df(this) ! -- Ensure models are in same solution if (associated(this%gwfmodel1) .and. associated(this%gwfmodel2)) then if (this%gwfmodel1%idsoln /= this%gwfmodel2%idsoln) then - call store_error('ERROR. TWO MODELS ARE CONNECTED IN A GWF '// & - 'EXCHANGE BUT THEY ARE IN DIFFERENT SOLUTIONS. '// & - 'GWF MODELS MUST BE IN SAME SOLUTION: '// & + call store_error('Two models are connected in a GWF '// & + 'exchange but they are in different solutions. '// & + 'GWF models must be in same solution: '// & trim(this%gwfmodel1%name)//' '// & trim(this%gwfmodel2%name)) call this%parser%StoreErrorUnit() @@ -1337,13 +1337,13 @@ function parse_option(this, keyword, iout) result(parsed) case ('GNC6') call this%parser%GetStringCaps(subkey) if (subkey /= 'FILEIN') then - call store_error('GNC6 KEYWORD MUST BE FOLLOWED BY '// & + call store_error('GNC6 keyword must be followed by '// & '"FILEIN" then by filename.') call this%parser%StoreErrorUnit() end if call this%parser%GetString(fname) if (fname == '') then - call store_error('NO GNC6 FILE SPECIFIED.') + call store_error('No GNC6 file specified.') call this%parser%StoreErrorUnit() end if this%ingnc = getunit() @@ -1357,13 +1357,13 @@ function parse_option(this, keyword, iout) result(parsed) end if call this%parser%GetStringCaps(subkey) if (subkey /= 'FILEIN') then - call store_error('MVR6 KEYWORD MUST BE FOLLOWED BY '// & + call store_error('MVR6 keyword must be followed by '// & '"FILEIN" then by filename.') call this%parser%StoreErrorUnit() end if call this%parser%GetString(fname) if (fname == '') then - call store_error('NO MVR6 FILE SPECIFIED.') + call store_error('No MVR6 file specified.') call this%parser%StoreErrorUnit() end if this%inmvr = getunit() @@ -1377,7 +1377,7 @@ function parse_option(this, keyword, iout) result(parsed) end if call this%parser%GetStringCaps(subkey) if (subkey /= 'FILEIN') then - call store_error('OBS8 KEYWORD MUST BE FOLLOWED BY '// & + call store_error('OBS8 keyword must be followed by '// & '"FILEIN" then by filename.') call this%parser%StoreErrorUnit() end if @@ -1416,15 +1416,15 @@ subroutine read_gnc(this) ! ! -- Verify gnc is implicit if exchange has Newton Terms if (.not. this%gnc%implicit .and. this%inewton /= 0) then - call store_error('GNC IS EXPLICIT, BUT GWF EXCHANGE HAS ACTIVE NEWTON.') - call store_error('ADD IMPLICIT OPTION TO GNC OR REMOVE NEWTON FROM '// & - 'GWF EXCHANGE.') + call store_error('GNC is explicit, but GWF exchange has active newton.') + call store_error('Add implicit option to GNC or remove NEWTON from '// & + 'GWF exchange.') call store_error_unit(this%ingnc) end if ! ! -- Perform checks to ensure GNCs match with GWF-GWF nodes if (this%nexg /= this%gnc%nexg) then - call store_error('NUMBER OF EXCHANGES DOES NOT MATCH NUMBER OF GNCs') + call store_error('Number of exchanges does not match number of GNCs') call store_error_unit(this%ingnc) end if ! @@ -2092,6 +2092,7 @@ function use_interface_model(this) result(use_im) subroutine gwf_gwf_save_simvals(this) ! -- dummy use SimModule, only: store_error, store_error_unit + use SimVariablesModule, only: errmsg use ConstantsModule, only: DZERO use ObserveModule, only: ObserveType class(GwfExchangeType), intent(inout) :: this @@ -2102,7 +2103,6 @@ subroutine gwf_gwf_save_simvals(this) integer(I4B) :: n2 integer(I4B) :: iexg real(DP) :: v - character(len=100) :: msg type(ObserveType), pointer :: obsrv => null() ! ! -- Write simulated values for all gwf-gwf observations @@ -2119,9 +2119,9 @@ subroutine gwf_gwf_save_simvals(this) n2 = this%nodem2(iexg) v = this%simvals(iexg) case default - msg = 'Error: Unrecognized observation type: '// & - trim(obsrv%ObsTypeId) - call store_error(msg) + errmsg = 'Unrecognized observation type: '// & + trim(obsrv%ObsTypeId) + call store_error(errmsg) call store_error_unit(this%inobs) end select call this%obs%SaveOneSimval(obsrv, v) diff --git a/src/Exchange/GwtGwtExchange.f90 b/src/Exchange/GwtGwtExchange.f90 index f3e52f6d560..faf7d1345ba 100644 --- a/src/Exchange/GwtGwtExchange.f90 +++ b/src/Exchange/GwtGwtExchange.f90 @@ -215,9 +215,9 @@ subroutine gwt_gwt_df(this) ! ! -- Ensure models are in same solution if (this%gwtmodel1%idsoln /= this%gwtmodel2%idsoln) then - call store_error('ERROR. TWO MODELS ARE CONNECTED IN A GWT '// & - 'EXCHANGE BUT THEY ARE IN DIFFERENT SOLUTIONS. '// & - 'GWT MODELS MUST BE IN SAME SOLUTION: '// & + call store_error('Two models are connected in a GWT '// & + 'exchange but they are in different solutions. '// & + 'GWT models must be in same solution: '// & trim(this%gwtmodel1%name)//' '//trim(this%gwtmodel2%name)) call this%parser%StoreErrorUnit() end if @@ -819,8 +819,8 @@ function parse_option(this, keyword, iout) result(parsed) call this%parser%GetStringCaps(subkey) ilen = len_trim(subkey) if (ilen > LENMODELNAME) then - write (errmsg, '(4x,a,a)') & - 'INVALID MODEL NAME: ', trim(subkey) + write (errmsg, '(a,a)') & + 'Invalid model name: ', trim(subkey) call store_error(errmsg) call this%parser%StoreErrorUnit() end if @@ -837,8 +837,8 @@ function parse_option(this, keyword, iout) result(parsed) call this%parser%GetStringCaps(subkey) ilen = len_trim(subkey) if (ilen > LENMODELNAME) then - write (errmsg, '(4x,a,a)') & - 'INVALID MODEL NAME: ', trim(subkey) + write (errmsg, '(a,a)') & + 'Invalid model name: ', trim(subkey) call store_error(errmsg) call this%parser%StoreErrorUnit() end if @@ -862,13 +862,13 @@ function parse_option(this, keyword, iout) result(parsed) case ('MVT6') call this%parser%GetStringCaps(subkey) if (subkey /= 'FILEIN') then - call store_error('MVT6 KEYWORD MUST BE FOLLOWED BY '// & + call store_error('MVT6 keyword must be followed by '// & '"FILEIN" then by filename.') call this%parser%StoreErrorUnit() end if call this%parser%GetString(fname) if (fname == '') then - call store_error('NO MVT6 FILE SPECIFIED.') + call store_error('No MVT6 file specified.') call this%parser%StoreErrorUnit() end if this%inmvt = getunit() @@ -878,7 +878,7 @@ function parse_option(this, keyword, iout) result(parsed) case ('OBS6') call this%parser%GetStringCaps(subkey) if (subkey /= 'FILEIN') then - call store_error('OBS8 KEYWORD MUST BE FOLLOWED BY '// & + call store_error('OBS8 keyword must be followed by '// & '"FILEIN" then by filename.') call this%parser%StoreErrorUnit() end if @@ -1264,6 +1264,7 @@ function use_interface_model(this) result(use_im) subroutine gwt_gwt_save_simvals(this) ! -- dummy use SimModule, only: store_error, store_error_unit + use SimVariablesModule, only: errmsg use ConstantsModule, only: DZERO use ObserveModule, only: ObserveType class(GwtExchangeType), intent(inout) :: this @@ -1274,7 +1275,6 @@ subroutine gwt_gwt_save_simvals(this) integer(I4B) :: n2 integer(I4B) :: iexg real(DP) :: v - character(len=100) :: msg type(ObserveType), pointer :: obsrv => null() ! ! -- Write simulated values for all gwt-gwt observations @@ -1291,9 +1291,9 @@ subroutine gwt_gwt_save_simvals(this) n2 = this%nodem2(iexg) v = this%simvals(iexg) case default - msg = 'Error: Unrecognized observation type: '// & - trim(obsrv%ObsTypeId) - call store_error(msg) + errmsg = 'Unrecognized observation type: '// & + trim(obsrv%ObsTypeId) + call store_error(errmsg) call store_error_unit(this%inobs) end select call this%obs%SaveOneSimval(obsrv, v) diff --git a/src/Model/Connection/GwfGwfConnection.f90 b/src/Model/Connection/GwfGwfConnection.f90 index 6291d9d2e0b..52e089e6aee 100644 --- a/src/Model/Connection/GwfGwfConnection.f90 +++ b/src/Model/Connection/GwfGwfConnection.f90 @@ -400,7 +400,7 @@ subroutine validateConnection(this) ! abort on errors if (count_errors() > 0) then - write (errmsg, '(1x,a)') 'Errors occurred while processing exchange(s)' + write (errmsg, '(a)') 'Errors occurred while processing exchange(s)' call ustop() end if @@ -433,7 +433,7 @@ subroutine validateGwfExchange(this) ! GNC not allowed if (gwfEx%ingnc /= 0) then - write (errmsg, '(1x,2a)') 'Ghost node correction not supported '// & + write (errmsg, '(2a)') 'Ghost node correction not supported '// & 'with interface model for exchange', & trim(gwfEx%name) call store_error(errmsg) @@ -441,7 +441,7 @@ subroutine validateGwfExchange(this) if ((gwfModel1%inbuy > 0 .and. gwfModel2%inbuy == 0) .or. & (gwfModel1%inbuy == 0 .and. gwfModel2%inbuy > 0)) then - write (errmsg, '(1x,2a)') 'Buoyancy package should be enabled/disabled '// & + write (errmsg, '(2a)') 'Buoyancy package should be enabled/disabled '// & 'simultaneously in models connected with the '// & 'interface model for exchange ', & trim(gwfEx%name) @@ -452,7 +452,7 @@ subroutine validateGwfExchange(this) if (gwfModel1%inbuy > 0 .and. gwfModel2%inbuy > 0) then ! does not work with XT3D if (this%iXt3dOnExchange > 0) then - write (errmsg, '(1x,2a)') 'Connecting models with BUY package not '// & + write (errmsg, '(2a)') 'Connecting models with BUY package not '// & 'allowed with XT3D enabled on exchange ', & trim(gwfEx%name) call store_error(errmsg) @@ -472,7 +472,7 @@ subroutine validateGwfExchange(this) end if if (.not. compatible) then - write (errmsg, '(1x,6a)') 'Buoyancy packages in model ', & + write (errmsg, '(6a)') 'Buoyancy packages in model ', & trim(gwfEx%model1%name), ' and ', & trim(gwfEx%model2%name), & ' should be equivalent to construct an '// & diff --git a/src/Model/Connection/GwtGwtConnection.f90 b/src/Model/Connection/GwtGwtConnection.f90 index cda088c77d2..8c7df985c50 100644 --- a/src/Model/Connection/GwtGwtConnection.f90 +++ b/src/Model/Connection/GwtGwtConnection.f90 @@ -314,7 +314,7 @@ subroutine validateConnection(this) this%gwtExchange%gwtmodel2%inadv == 0) .or. & (this%gwtExchange%gwtmodel2%inadv > 0 .and. & this%gwtExchange%gwtmodel1%inadv == 0)) then - write (errmsg, '(1x,a,a,a)') 'Cannot connect GWT models in exchange ', & + write (errmsg, '(a,a,a)') 'Cannot connect GWT models in exchange ', & trim(this%gwtExchange%name), ' because one model is configured with ADV & &and the other one is not' call store_error(errmsg) @@ -324,7 +324,7 @@ subroutine validateConnection(this) this%gwtExchange%gwtmodel2%indsp == 0) .or. & (this%gwtExchange%gwtmodel2%indsp > 0 .and. & this%gwtExchange%gwtmodel1%indsp == 0)) then - write (errmsg, '(1x,a,a,a)') 'Cannot connect GWT models in exchange ', & + write (errmsg, '(a,a,a)') 'Cannot connect GWT models in exchange ', & trim(this%gwtExchange%name), ' because one model is configured with DSP & &and the other one is not' call store_error(errmsg) @@ -332,7 +332,7 @@ subroutine validateConnection(this) ! abort on errors if (count_errors() > 0) then - write (errmsg, '(1x,a)') 'Errors occurred while processing exchange(s)' + write (errmsg, '(a)') 'Errors occurred while processing exchange(s)' call ustop() end if diff --git a/src/Model/Connection/SpatialModelConnection.f90 b/src/Model/Connection/SpatialModelConnection.f90 index 2581d67f441..a383d608414 100644 --- a/src/Model/Connection/SpatialModelConnection.f90 +++ b/src/Model/Connection/SpatialModelConnection.f90 @@ -550,13 +550,13 @@ subroutine validateConnection(this) if (conEx%ixt3d > 0) then ! if XT3D, we need these angles: if (conEx%model1%dis%con%ianglex == 0) then - write (errmsg, '(1x,a,a,a,a,a)') 'XT3D configured on the exchange ', & + write (errmsg, '(a,a,a,a,a)') 'XT3D configured on the exchange ', & trim(conEx%name), ' but the discretization in model ', & trim(conEx%model1%name), ' has no ANGLDEGX specified' call store_error(errmsg) end if if (conEx%model2%dis%con%ianglex == 0) then - write (errmsg, '(1x,a,a,a,a,a)') 'XT3D configured on the exchange ', & + write (errmsg, '(a,a,a,a,a)') 'XT3D configured on the exchange ', & trim(conEx%name), ' but the discretization in model ', & trim(conEx%model2%name), ' has no ANGLDEGX specified' call store_error(errmsg) diff --git a/src/Model/Geometry/CircularGeometry.f90 b/src/Model/Geometry/CircularGeometry.f90 index c49f4179e14..18e71a6491d 100644 --- a/src/Model/Geometry/CircularGeometry.f90 +++ b/src/Model/Geometry/CircularGeometry.f90 @@ -170,7 +170,7 @@ subroutine set_attribute(this, line) call urword(line, lloc, istart, istop, 3, ival, rval, 0, 0) this%radius = rval case default - write (errmsg, '(4x,a,a)') & + write (errmsg, '(a,a)') & 'Unknown circular geometry attribute: ', line(istart:istop) call store_error(errmsg, terminate=.TRUE.) end select diff --git a/src/Model/Geometry/RectangularGeometry.f90 b/src/Model/Geometry/RectangularGeometry.f90 index dfe89ac50b4..1df035584de 100644 --- a/src/Model/Geometry/RectangularGeometry.f90 +++ b/src/Model/Geometry/RectangularGeometry.f90 @@ -160,7 +160,7 @@ subroutine set_attribute(this, line) call urword(line, lloc, istart, istop, 3, ival, rval, 0, 0) this%width = rval case default - write (errmsg, '(4x,a,a)') & + write (errmsg, '(a,a)') & 'Unknown rectangular geometry attribute: ', line(istart:istop) call store_error(errmsg, terminate=.TRUE.) end select diff --git a/src/Model/GroundWaterFlow/gwf3.f90 b/src/Model/GroundWaterFlow/gwf3.f90 index de101564f53..9a8057246be 100644 --- a/src/Model/GroundWaterFlow/gwf3.f90 +++ b/src/Model/GroundWaterFlow/gwf3.f90 @@ -1347,23 +1347,23 @@ subroutine ftype_check(this, indis) ! ! -- Check for IC8, DIS(u), and NPF. Stop if not present. if (this%inic == 0) then - write (errmsg, '(1x,a)') & + write (errmsg, '(a)') & 'Initial Conditions (IC6) package not specified.' call store_error(errmsg) end if if (indis == 0) then - write (errmsg, '(1x,a)') & + write (errmsg, '(a)') & 'Discretization (DIS6, DISV6, or DISU6) Package not specified.' call store_error(errmsg) end if if (this%innpf == 0) then - write (errmsg, '(1x,a)') & + write (errmsg, '(a)') & 'Node Property Flow (NPF6) Package not specified.' call store_error(errmsg) end if ! if (count_errors() > 0) then - write (errmsg, '(1x,a)') 'One or more required package(s) not specified.' + write (errmsg, '(a)') 'One or more required package(s) not specified.' call store_error(errmsg) call store_error_filename(this%filename) end if diff --git a/src/Model/GroundWaterFlow/gwf3buy8.f90 b/src/Model/GroundWaterFlow/gwf3buy8.f90 index c23097e22b1..281df9f962b 100644 --- a/src/Model/GroundWaterFlow/gwf3buy8.f90 +++ b/src/Model/GroundWaterFlow/gwf3buy8.f90 @@ -1146,7 +1146,7 @@ subroutine read_dimensions(this) this%nrhospecies = this%parser%GetInteger() write (this%iout, '(4x,a,i0)') 'NRHOSPECIES = ', this%nrhospecies case default - write (errmsg, '(4x,a,a)') & + write (errmsg, '(a,a)') & 'Unknown BUY dimension: ', trim(keyword) call store_error(errmsg) call this%parser%StoreErrorUnit() @@ -1688,7 +1688,7 @@ subroutine read_options(this) call store_error(errmsg) end if case default - write (errmsg, '(4x,a,a)') '****Error. Unknown BUY option: ', & + write (errmsg, '(a,a)') 'Unknown BUY option: ', & trim(keyword) call store_error(errmsg) call this%parser%StoreErrorUnit() diff --git a/src/Model/GroundWaterFlow/gwf3disv8.f90 b/src/Model/GroundWaterFlow/gwf3disv8.f90 index 28c5b24951d..63630cc0190 100644 --- a/src/Model/GroundWaterFlow/gwf3disv8.f90 +++ b/src/Model/GroundWaterFlow/gwf3disv8.f90 @@ -1928,7 +1928,7 @@ subroutine nlarray_to_nodelist(this, nodelist, maxbnd, nbound, aname, & nodeu = get_node(1, ir, ic, nlay, nrow, ncol) il = this%ibuff(nodeu) if (il < 1 .or. il > nlay) then - write (errmsg, *) 'ERROR. INVALID LAYER NUMBER: ', il + write (errmsg, *) 'Invalid layer number: ', il call store_error(errmsg, terminate=.TRUE.) end if nodeu = get_node(il, ir, ic, nlay, nrow, ncol) @@ -1945,9 +1945,8 @@ subroutine nlarray_to_nodelist(this, nodelist, maxbnd, nbound, aname, & ! -- Check for errors nbound = ipos - 1 if (ierr > 0) then - write (errmsg, *) 'ERROR. MAXBOUND DIMENSION IS TOO SMALL.' - call store_error(errmsg) - write (errmsg, *) 'INCREASE MAXBOUND TO: ', ierr + write (errmsg, '(a, i0)') & + 'MAXBOUND dimension is too small. Increase MAXBOUND to ', ierr call store_error(errmsg, terminate=.TRUE.) end if ! diff --git a/src/Model/GroundWaterFlow/gwf3drn8.f90 b/src/Model/GroundWaterFlow/gwf3drn8.f90 index 53f74bf3bea..5b8d7cd1a9b 100644 --- a/src/Model/GroundWaterFlow/gwf3drn8.f90 +++ b/src/Model/GroundWaterFlow/gwf3drn8.f90 @@ -204,8 +204,8 @@ subroutine drn_options(this, option, found) ! -- Error if no aux variable specified if (this%naux == 0) then write (errmsg, '(a,2(1x,a))') & - 'AUXDEPTHNAME WAS SPECIFIED AS', trim(adjustl(ddrnauxname)), & - 'BUT NO AUX VARIABLES SPECIFIED.' + 'AUXDEPTHNAME was specified as', trim(adjustl(ddrnauxname)), & + 'but no AUX variables specified.' call store_error(errmsg) end if ! @@ -221,8 +221,8 @@ subroutine drn_options(this, option, found) ! -- Error if aux variable cannot be found if (this%iauxddrncol == 0) then write (errmsg, '(a,2(1x,a))') & - 'AUXDEPTHNAME WAS SPECIFIED AS', trim(adjustl(ddrnauxname)), & - 'BUT NO AUX VARIABLE FOUND WITH THIS NAME.' + 'AUXDEPTHNAME was specified as', trim(adjustl(ddrnauxname)), & + 'but no AUX variable found with this name.' call store_error(errmsg) end if end if diff --git a/src/Model/GroundWaterFlow/gwf3evt8.f90 b/src/Model/GroundWaterFlow/gwf3evt8.f90 index 1be0ecbf2d6..34d687cfbdf 100644 --- a/src/Model/GroundWaterFlow/gwf3evt8.f90 +++ b/src/Model/GroundWaterFlow/gwf3evt8.f90 @@ -303,7 +303,7 @@ subroutine evt_read_dimensions(this) end if end if case default - write (errmsg, '(4x,a,a)') & + write (errmsg, '(a,a)') & 'Unknown '//trim(this%text)//' DIMENSION: ', trim(keyword) call store_error(errmsg) call this%parser%StoreErrorUnit() @@ -320,7 +320,7 @@ subroutine evt_read_dimensions(this) ! ! -- verify dimensions were set if (this%maxbound <= 0) then - write (errmsg, '(1x,a)') & + write (errmsg, '(a)') & 'MAXBOUND must be an integer greater than zero.' call store_error(errmsg) call this%parser%StoreErrorUnit() @@ -854,8 +854,8 @@ subroutine evt_rp_array(this, line, inrate, insurf, indepth, & ! -- Check to see if other variables have already been read. If so, ! then terminate with an error that IEVT must be read first. if (ivarsread > 0) then - call store_error('****ERROR. IEVT IS NOT FIRST VARIABLE IN & - &PERIOD BLOCK OR IT IS SPECIFIED MORE THAN ONCE.') + call store_error('IEVT is not first variable in & + &period block or it is specified more than once.') call this%parser%StoreErrorUnit() end if ! @@ -874,8 +874,8 @@ subroutine evt_rp_array(this, line, inrate, insurf, indepth, & case ('SURFACE') ! if (this%inievt == 0) then - call store_error('Error. IEVT must be read at least once ') - call store_error('prior to reading the SURFACE array.') + call store_error('IEVT must be read at least once & + &prior to reading the SURFACE array.') call this%parser%StoreErrorUnit() end if ! @@ -922,8 +922,8 @@ subroutine evt_rp_array(this, line, inrate, insurf, indepth, & case ('DEPTH') ! if (this%inievt == 0) then - call store_error('IEVT must be read at least once ') - call store_error('prior to reading the DEPTH array.') + call store_error('IEVT must be read at least once & + &prior to reading the DEPTH array.') call this%parser%StoreErrorUnit() end if ! @@ -942,8 +942,8 @@ subroutine evt_rp_array(this, line, inrate, insurf, indepth, & end if ! if (this%inievt == 0) then - call store_error('IEVT must be read at least once ') - call store_error('prior to reading any PXDP array.') + call store_error('IEVT must be read at least once & + &prior to reading any PXDP array.') call this%parser%StoreErrorUnit() end if ! @@ -970,8 +970,8 @@ subroutine evt_rp_array(this, line, inrate, insurf, indepth, & end if ! if (this%inievt == 0) then - call store_error('IEVT must be read at least once ') - call store_error('prior to reading any PETM array.') + call store_error('IEVT must be read at least once & + &prior to reading any PETM array.') call this%parser%StoreErrorUnit() end if ! @@ -1027,7 +1027,7 @@ subroutine evt_rp_array(this, line, inrate, insurf, indepth, & ! -- Nothing found if (.not. found) then call this%parser%GetCurrentLine(line) - call store_error('LOOKING FOR VALID VARIABLE NAME. FOUND: ') + call store_error('Looking for valid variable name. Found: ') call store_error(trim(line)) call this%parser%StoreErrorUnit() end if diff --git a/src/Model/GroundWaterFlow/gwf3hfb8.f90 b/src/Model/GroundWaterFlow/gwf3hfb8.f90 index 72bbac6d6c6..8fc3199f551 100644 --- a/src/Model/GroundWaterFlow/gwf3hfb8.f90 +++ b/src/Model/GroundWaterFlow/gwf3hfb8.f90 @@ -637,7 +637,7 @@ subroutine read_options(this) write (this%iout, '(4x,a)') & 'THE LIST OF HFBS WILL BE PRINTED.' case default - write (errmsg, '(4x,a,a)') 'Unknown HFB option: ', & + write (errmsg, '(a,a)') 'Unknown HFB option: ', & trim(keyword) call store_error(errmsg) call this%parser%StoreErrorUnit() @@ -684,7 +684,7 @@ subroutine read_dimensions(this) this%maxhfb = this%parser%GetInteger() write (this%iout, '(4x,a,i7)') 'MAXHFB = ', this%maxhfb case default - write (errmsg, '(4x,a,a)') & + write (errmsg, '(a,a)') & 'Unknown HFB dimension: ', trim(keyword) call store_error(errmsg) call this%parser%StoreErrorUnit() @@ -699,7 +699,7 @@ subroutine read_dimensions(this) ! ! -- verify dimensions were set if (this%maxhfb <= 0) then - write (errmsg, '(1x,a)') & + write (errmsg, '(a)') & 'MAXHFB must be specified with value greater than zero.' call store_error(errmsg) call this%parser%StoreErrorUnit() diff --git a/src/Model/GroundWaterFlow/gwf3ic8.f90 b/src/Model/GroundWaterFlow/gwf3ic8.f90 index 91991d170b2..033f565819b 100644 --- a/src/Model/GroundWaterFlow/gwf3ic8.f90 +++ b/src/Model/GroundWaterFlow/gwf3ic8.f90 @@ -178,7 +178,7 @@ subroutine read_options(this) call this%parser%GetStringCaps(keyword) select case (keyword) case default - write (errmsg, '(4x,a,a)') 'Unknown IC option: ', trim(keyword) + write (errmsg, '(a,a)') 'Unknown IC option: ', trim(keyword) call store_error(errmsg) call this%parser%StoreErrorUnit() end select @@ -230,7 +230,7 @@ subroutine read_data(this) this%parser%iuactive, this%strt, & aname(1)) case default - write (errmsg, '(4x,a,a)') 'Unknown GRIDDATA tag: ', trim(keyword) + write (errmsg, '(a,a)') 'Unknown GRIDDATA tag: ', trim(keyword) call store_error(errmsg) call this%parser%StoreErrorUnit() end select diff --git a/src/Model/GroundWaterFlow/gwf3mvr8.f90 b/src/Model/GroundWaterFlow/gwf3mvr8.f90 index 0fdd6460b92..842a13dd1b0 100644 --- a/src/Model/GroundWaterFlow/gwf3mvr8.f90 +++ b/src/Model/GroundWaterFlow/gwf3mvr8.f90 @@ -376,14 +376,14 @@ subroutine mvr_rp(this) do i = 1, this%nmvr ipos = ifind(this%pckMemPaths, this%mvr(i)%pckNameSrc) if (ipos < 1) then - write (errmsg, '(4x,a,a,a)') 'PROVIDER ', & - trim(this%mvr(i)%pckNameSrc), ' NOT LISTED IN PACKAGES BLOCK.' + write (errmsg, '(a,a,a)') 'Provider ', & + trim(this%mvr(i)%pckNameSrc), ' not listed in packages block.' call store_error(errmsg) end if ipos = ifind(this%pckMemPaths, this%mvr(i)%pckNameTgt) if (ipos < 1) then - write (errmsg, '(4x,a,a,a)') 'RECEIVER ', & - trim(this%mvr(i)%pckNameTgt), ' NOT LISTED IN PACKAGES BLOCK.' + write (errmsg, '(a,a,a)') 'Receiver ', & + trim(this%mvr(i)%pckNameTgt), ' not listed in packages block.' call store_error(errmsg) end if end do @@ -819,14 +819,14 @@ subroutine read_options(this) write (this%iout, '(4x,a)') 'ALL PACKAGE NAMES ARE PRECEDED '// & 'BY THE NAME OF THE MODEL CONTAINING THE PACKAGE.' if (this%iexgmvr == 0) then - write (errmsg, '(4x,a,a)') & - 'MODELNAMES CANNOT BE SPECIFIED UNLESS THE '// & - 'MOVER PACKAGE IS FOR AN EXCHANGE.' + write (errmsg, '(a,a)') & + 'MODELNAMES cannot be specified unless the '// & + 'mover package is for an exchange.' call store_error(errmsg) call this%parser%StoreErrorUnit() end if case default - write (errmsg, '(4x,a,a)') 'Unknown MVR option: ', trim(keyword) + write (errmsg, '(a,a)') 'Unknown MVR option: ', trim(keyword) call store_error(errmsg) call this%parser%StoreErrorUnit() end select @@ -857,18 +857,18 @@ subroutine check_options(this) ! ! -- Check if not exchange mover but model names are specified if (this%iexgmvr == 0 .and. this%imodelnames == 1) then - write (errmsg, '(4x,a,a)') & - '****ERROR. MODELNAMES CANNOT BE SPECIFIED UNLESS THE '// & - 'MOVER PACKAGE IS FOR AN EXCHANGE.' + write (errmsg, '(a,a)') & + 'MODELNAMES cannot be specified unless the '// & + 'mover package is for an exchange.' call store_error(errmsg) call this%parser%StoreErrorUnit() end if ! ! -- Check if exchange mover but model names not specified if (this%iexgmvr /= 0 .and. this%imodelnames == 0) then - write (errmsg, '(4x,a,a)') & - '****ERROR. MODELNAMES OPTION MUST BE SPECIFIED BECAUSE '// & - 'MOVER PACKAGE IS FOR AN EXCHANGE.' + write (errmsg, '(a,a)') & + 'MODELNAMES option must be specified because '// & + 'mover package is for an exchange.' call store_error(errmsg) call this%parser%StoreErrorUnit() end if @@ -917,7 +917,7 @@ subroutine read_dimensions(this) this%maxpackages = this%parser%GetInteger() write (this%iout, '(4x,a,i0)') 'MAXPACKAGES = ', this%maxpackages case default - write (errmsg, '(4x,a,a)') & + write (errmsg, '(a,a)') & 'Unknown MVR dimension: ', trim(keyword) call store_error(errmsg) call this%parser%StoreErrorUnit() @@ -939,13 +939,13 @@ subroutine read_dimensions(this) ! ! -- verify dimensions were set if (this%maxmvr < 0) then - write (errmsg, '(1x,a)') & + write (errmsg, '(a)') & 'MAXMVR was not specified or was specified incorrectly.' call store_error(errmsg) call this%parser%StoreErrorUnit() end if if (this%maxpackages < 0) then - write (errmsg, '(1x,a)') & + write (errmsg, '(a)') & 'MAXPACKAGES was not specified or was specified incorrectly.' call store_error(errmsg) call this%parser%StoreErrorUnit() diff --git a/src/Model/GroundWaterFlow/gwf3npf8.f90 b/src/Model/GroundWaterFlow/gwf3npf8.f90 index 070d825e091..fd60dd91cc2 100644 --- a/src/Model/GroundWaterFlow/gwf3npf8.f90 +++ b/src/Model/GroundWaterFlow/gwf3npf8.f90 @@ -1609,8 +1609,8 @@ subroutine check_options(this) ! -- check that the transmissivity weighting functions are not specified with ! with the this%inwtupw option if (this%inwtupw /= 0 .and. this%icellavg < 2) then - write (errmsg, '(4x,a,2(1x,a))') & - '****ERROR. THE DEV_MODFLOWNWT_UPSTREAM_WEIGHTING OPTION CAN', & + write (errmsg, '(a,2(1x,a))') & + 'THE DEV_MODFLOWNWT_UPSTREAM_WEIGHTING OPTION CAN', & 'ONLY BE SPECIFIED WITH THE AMT-LMK AND AMT-HMK', & 'ALTERNATIVE_CELL_AVERAGING OPTIONS IN THE NPF PACKAGE.' call store_error(errmsg) @@ -1618,8 +1618,8 @@ subroutine check_options(this) ! ! -- check that this%iusgnrhc and this%inwtupw have not both been enabled if (this%iusgnrhc /= 0 .and. this%inwtupw /= 0) then - write (errmsg, '(4x,a,2(1x,a))') & - '****ERROR. THE DEV_MODFLOWUSG_UPSTREAM_WEIGHTED_SATURATION', & + write (errmsg, '(a,2(1x,a))') & + 'THE DEV_MODFLOWUSG_UPSTREAM_WEIGHTED_SATURATION', & 'AND DEV_MODFLOWNWT_UPSTREAM_WEIGHTING OPTIONS CANNOT BE', & 'SPECIFIED IN THE SAME NPF PACKAGE.' call store_error(errmsg) diff --git a/src/Model/GroundWaterFlow/gwf3rch8.f90 b/src/Model/GroundWaterFlow/gwf3rch8.f90 index f0fa647a798..e50ad4f279b 100644 --- a/src/Model/GroundWaterFlow/gwf3rch8.f90 +++ b/src/Model/GroundWaterFlow/gwf3rch8.f90 @@ -227,7 +227,7 @@ subroutine rch_read_dimensions(this) this%maxbound = this%parser%GetInteger() write (this%iout, '(4x,a,i7)') 'MAXBOUND = ', this%maxbound case default - write (errmsg, '(4x,a,a)') & + write (errmsg, '(a,a)') & 'Unknown '//trim(this%text)//' DIMENSION: ', trim(keyword) call store_error(errmsg) call this%parser%StoreErrorUnit() @@ -244,7 +244,7 @@ subroutine rch_read_dimensions(this) ! ! -- verify dimensions were set if (this%maxbound <= 0) then - write (errmsg, '(1x,a)') & + write (errmsg, '(a)') & 'MAXBOUND must be an integer greater than zero.' call store_error(errmsg) call this%parser%StoreErrorUnit() diff --git a/src/Model/GroundWaterFlow/gwf3sto8.f90 b/src/Model/GroundWaterFlow/gwf3sto8.f90 index 63b4051fbca..df9ee6195b2 100644 --- a/src/Model/GroundWaterFlow/gwf3sto8.f90 +++ b/src/Model/GroundWaterFlow/gwf3sto8.f90 @@ -225,7 +225,7 @@ subroutine sto_rp(this) case ('TRANSIENT') this%iss = 0 case default - write (errmsg, '(4x,a,a)') 'Unknown STORAGE data tag: ', & + write (errmsg, '(a,a)') 'Unknown STORAGE data tag: ', & trim(keyword) call store_error(errmsg) call this%parser%StoreErrorUnit() @@ -919,7 +919,7 @@ subroutine read_options(this) this%iorig_ss = 0 write (this%iout, fmtconfss) case default - write (errmsg, '(4x,a,a)') 'Unknown STO option: ', & + write (errmsg, '(a,a)') 'Unknown STO option: ', & trim(keyword) call store_error(errmsg, terminate=.TRUE.) end select @@ -998,7 +998,7 @@ subroutine read_data(this) aname(3)) readsy = .true. case default - write (errmsg, '(4x,a,a)') 'Unknown GRIDDATA tag: ', & + write (errmsg, '(a,a)') 'Unknown GRIDDATA tag: ', & trim(keyword) call store_error(errmsg) call this%parser%StoreErrorUnit() @@ -1006,7 +1006,7 @@ subroutine read_data(this) end do write (this%iout, '(1x,a)') 'END PROCESSING GRIDDATA' else - write (errmsg, '(1x,a)') 'Required GRIDDATA block not found.' + write (errmsg, '(a)') 'Required GRIDDATA block not found.' call store_error(errmsg) call this%parser%StoreErrorUnit() end if diff --git a/src/Model/GroundWaterFlow/gwf3vsc8.f90 b/src/Model/GroundWaterFlow/gwf3vsc8.f90 index e5f3a6fbf20..d803f5937ee 100644 --- a/src/Model/GroundWaterFlow/gwf3vsc8.f90 +++ b/src/Model/GroundWaterFlow/gwf3vsc8.f90 @@ -1063,8 +1063,8 @@ subroutine read_dimensions(this) this%nviscspecies = this%parser%GetInteger() write (this%iout, '(4x,a,i0)') 'NVISCSPECIES = ', this%nviscspecies case default - write (errmsg, '(4x,a,a)') & - 'unknown VSC dimension: ', trim(keyword) + write (errmsg, '(a,a)') & + 'Unknown VSC dimension: ', trim(keyword) call store_error(errmsg) call this%parser%StoreErrorUnit() end select @@ -1452,7 +1452,7 @@ subroutine read_options(this) &specified. THERMAL_A4 will not affect ', 'viscosity calculations.' end if case default - write (errmsg, '(4x,a,a)') '**Error. Unknown VSC option: ', & + write (errmsg, '(a,a)') 'Unknown VSC option: ', & trim(keyword) call store_error(errmsg) call this%parser%StoreErrorUnit() diff --git a/src/Model/GroundWaterTransport/gwt1.f90 b/src/Model/GroundWaterTransport/gwt1.f90 index 281fd2b4931..0e391664630 100644 --- a/src/Model/GroundWaterTransport/gwt1.f90 +++ b/src/Model/GroundWaterTransport/gwt1.f90 @@ -1128,23 +1128,23 @@ subroutine ftype_check(this, indis) ! ! -- Check for IC6, DIS(u), and MST. Stop if not present. if (this%inic == 0) then - write (errmsg, '(1x,a)') & - 'ERROR. INITIAL CONDITIONS (IC6) PACKAGE NOT SPECIFIED.' + write (errmsg, '(a)') & + 'Initial conditions (IC6) package not specified.' call store_error(errmsg) end if if (indis == 0) then - write (errmsg, '(1x,a)') & - 'ERROR. DISCRETIZATION (DIS6 or DISU6) PACKAGE NOT SPECIFIED.' + write (errmsg, '(a)') & + 'Discretization (DIS6 or DISU6) package not specified.' call store_error(errmsg) end if if (this%inmst == 0) then - write (errmsg, '(1x,a)') 'ERROR. MASS STORAGE AND TRANSFER (MST6) & - &PACKAGE NOT SPECIFIED.' + write (errmsg, '(a)') 'Mass storage and transfer (MST6) & + &package not specified.' call store_error(errmsg) end if ! if (count_errors() > 0) then - write (errmsg, '(1x,a)') 'ERROR. REQUIRED PACKAGE(S) NOT SPECIFIED.' + write (errmsg, '(a)') 'Required package(s) not specified.' call store_error(errmsg) call store_error_filename(this%filename) end if diff --git a/src/Model/GroundWaterTransport/gwt1adv1.f90 b/src/Model/GroundWaterTransport/gwt1adv1.f90 index ee3888ee328..0e9f4bdb487 100644 --- a/src/Model/GroundWaterTransport/gwt1adv1.f90 +++ b/src/Model/GroundWaterTransport/gwt1adv1.f90 @@ -457,16 +457,16 @@ subroutine read_options(this) this%iadvwt = 2 write (this%iout, fmtiadvwt) 'TVD' case default - write (errmsg, '(4x, a, a)') & - 'ERROR. UNKNOWN SCHEME: ', trim(keyword) + write (errmsg, '(a, a)') & + 'Unknown scheme: ', trim(keyword) call store_error(errmsg) - write (errmsg, '(4x, a, a)') & - 'SCHEME MUST BE "UPSTREAM", "CENTRAL" OR "TVD"' + write (errmsg, '(a, a)') & + 'Scheme must be "UPSTREAM", "CENTRAL" or "TVD"' call store_error(errmsg) call this%parser%StoreErrorUnit() end select case default - write (errmsg, '(4x,a,a)') 'Unknown ADVECTION option: ', & + write (errmsg, '(a,a)') 'Unknown ADVECTION option: ', & trim(keyword) call store_error(errmsg, terminate=.TRUE.) end select diff --git a/src/Model/GroundWaterTransport/gwt1apt1.f90 b/src/Model/GroundWaterTransport/gwt1apt1.f90 index b338404b770..6f50995ac4c 100644 --- a/src/Model/GroundWaterTransport/gwt1apt1.f90 +++ b/src/Model/GroundWaterTransport/gwt1apt1.f90 @@ -359,8 +359,8 @@ subroutine apt_ar(this) end if end do if (this%iauxfpconc == 0) then - errmsg = 'COULD NOT FIND AUXILIARY VARIABLE '// & - trim(adjustl(this%cauxfpconc))//' IN FLOW PACKAGE '// & + errmsg = 'Could not find auxiliary variable '// & + trim(adjustl(this%cauxfpconc))//' in flow package '// & trim(adjustl(this%flowpackagename)) call store_error(errmsg) call this%parser%StoreErrorUnit() @@ -647,8 +647,8 @@ function apt_check_valid(this, itemno) result(ierr) ! ------------------------------------------------------------------------------ ierr = 0 if (itemno < 1 .or. itemno > this%ncv) then - write (errmsg, '(4x,a,1x,i6,1x,a,1x,i6)') & - '****ERROR. FEATURENO ', itemno, 'MUST BE > 0 and <= ', this%ncv + write (errmsg, '(a,1x,i6,1x,a,1x,i6)') & + 'Featureno ', itemno, 'must be > 0 and <= ', this%ncv call store_error(errmsg) ierr = 1 end if @@ -1453,8 +1453,8 @@ subroutine apt_options(this, option, found) write (this%iout, fmtaptbin) & trim(adjustl(this%text)), 'CONCENTRATION', trim(fname), this%iconcout else - call store_error('OPTIONAL CONCENTRATION KEYWORD MUST & - &BE FOLLOWED BY FILEOUT') + call store_error('Optional CONCENTRATION keyword must & + &be followed by FILEOUT') end if case ('BUDGET') call this%parser%GetStringCaps(keyword) @@ -1466,7 +1466,7 @@ subroutine apt_options(this, option, found) write (this%iout, fmtaptbin) trim(adjustl(this%text)), 'BUDGET', & trim(fname), this%ibudgetout else - call store_error('OPTIONAL BUDGET KEYWORD MUST BE FOLLOWED BY FILEOUT') + call store_error('Optional BUDGET keyword must be followed by FILEOUT') end if case ('BUDGETCSV') call this%parser%GetStringCaps(keyword) @@ -1478,7 +1478,7 @@ subroutine apt_options(this, option, found) write (this%iout, fmtaptbin) trim(adjustl(this%text)), 'BUDGET CSV', & trim(fname), this%ibudcsv else - call store_error('OPTIONAL BUDGETCSV KEYWORD MUST BE FOLLOWED BY & + call store_error('Optional BUDGETCSV keyword must be followed by & &FILEOUT') end if case default @@ -1537,8 +1537,8 @@ subroutine apt_read_dimensions(this) ! ! -- Check for errors if (this%ncv < 0) then - write (errmsg, '(1x,a)') & - 'ERROR: NUMBER OF CONTROL VOLUMES COULD NOT BE DETERMINED CORRECTLY.' + write (errmsg, '(a)') & + 'Number of control volumes could not be determined correctly.' call store_error(errmsg) end if ! @@ -1650,8 +1650,8 @@ subroutine apt_read_cvs(this) n = this%parser%GetInteger() if (n < 1 .or. n > this%ncv) then - write (errmsg, '(4x,a,1x,i6)') & - '****ERROR. itemno MUST BE > 0 and <= ', this%ncv + write (errmsg, '(a,1x,i6)') & + 'Itemno must be > 0 and <= ', this%ncv call store_error(errmsg) cycle end if @@ -1698,11 +1698,11 @@ subroutine apt_read_cvs(this) ! -- check for duplicate or missing lakes do n = 1, this%ncv if (nboundchk(n) == 0) then - write (errmsg, '(a,1x,i0)') 'ERROR. NO DATA SPECIFIED FOR FEATURE', n + write (errmsg, '(a,1x,i0)') 'No data specified for feature', n call store_error(errmsg) else if (nboundchk(n) > 1) then write (errmsg, '(a,1x,i0,1x,a,1x,i0,1x,a)') & - 'ERROR. DATA FOR FEATURE', n, 'SPECIFIED', nboundchk(n), 'TIMES' + 'Data for feature', n, 'specified', nboundchk(n), 'times' call store_error(errmsg) end if end do @@ -1710,7 +1710,7 @@ subroutine apt_read_cvs(this) write (this%iout, '(1x,a)') & 'END OF '//trim(adjustl(this%text))//' PACKAGEDATA' else - call store_error('ERROR. REQUIRED PACKAGEDATA BLOCK NOT FOUND.') + call store_error('Required packagedata block not found.') end if ! ! -- terminate if any errors were detected diff --git a/src/Model/GroundWaterTransport/gwt1cnc1.f90 b/src/Model/GroundWaterTransport/gwt1cnc1.f90 index 821ba58398a..5fc5378f078 100644 --- a/src/Model/GroundWaterTransport/gwt1cnc1.f90 +++ b/src/Model/GroundWaterTransport/gwt1cnc1.f90 @@ -150,7 +150,7 @@ subroutine cnc_rp(this) ibd = this%ibound(node) if (ibd < 0) then call this%dis%noder_to_string(node, nodestr) - call store_error('Error. Cell is already a constant concentration: ' & + call store_error('Cell is already a constant concentration: ' & //trim(adjustl(nodestr))) ierr = ierr + 1 else @@ -222,7 +222,7 @@ subroutine cnc_ck(this) integer(I4B) :: node ! -- formats character(len=*), parameter :: fmtcncerr = & - &"('CNC BOUNDARY ',i0,' CONC (',g0,') IS LESS THAN ZERO FOR CELL', a)" + &"('CNC boundary ',i0,' conc (',g0,') is less than zero for cell', a)" ! ------------------------------------------------------------------------------ ! ! -- check stress period data diff --git a/src/Model/GroundWaterTransport/gwt1dsp1.f90 b/src/Model/GroundWaterTransport/gwt1dsp1.f90 index d9604cc5ad1..50881f867c7 100644 --- a/src/Model/GroundWaterTransport/gwt1dsp1.f90 +++ b/src/Model/GroundWaterTransport/gwt1dsp1.f90 @@ -692,7 +692,7 @@ subroutine source_griddata(this) ! -- manage dispersion arrays if (this%idisp > 0) then if (.not. (found%alh .and. found%ath1)) then - write (errmsg, '(1x,a)') & + write (errmsg, '(a)') & 'if dispersivities are specified then ALH and ATH1 are required.' call store_error(errmsg) end if diff --git a/src/Model/GroundWaterTransport/gwt1fmi1.f90 b/src/Model/GroundWaterTransport/gwt1fmi1.f90 index 7dba0f0d801..cf5680e1868 100644 --- a/src/Model/GroundWaterTransport/gwt1fmi1.f90 +++ b/src/Model/GroundWaterTransport/gwt1fmi1.f90 @@ -193,8 +193,8 @@ subroutine fmi_df(this, dis, inssm) ! -- Make sure that ssm is on if there are any boundary packages if (inssm == 0) then if (this%nflowpack > 0) then - call store_error('FLOW MODEL HAS BOUNDARY PACKAGES, BUT THERE & - &IS NO SSM PACKAGE. THE SSM PACKAGE MUST BE ACTIVATED.', & + call store_error('Flow model has boundary packages, but there & + &is no SSM package. The SSM package must be activated.', & terminate=.TRUE.) end if end if @@ -250,14 +250,14 @@ subroutine fmi_rp(this, inmvr) ! to mvrbudobj until exg_ar(). if (kper * kstp == 1) then if (associated(this%mvrbudobj) .and. inmvr == 0) then - write (errmsg, '(4x,a)') 'GWF WATER MOVER IS ACTIVE BUT THE GWT MVT & - &PACKAGE HAS NOT BEEN SPECIFIED. ACTIVATE GWT MVT PACKAGE.' + write (errmsg, '(a)') 'GWF water mover is active but the GWT MVT & + &package has not been specified. activate GWT MVT package.' call store_error(errmsg, terminate=.TRUE.) end if if (.not. associated(this%mvrbudobj) .and. inmvr > 0) then - write (errmsg, '(4x,a)') 'GWF WATER MOVER TERMS ARE NOT AVAILABLE & - &BUT THE GWT MVT PACKAGE HAS BEEN ACTIVATED. GWF-GWT EXCHANGE & - &OR SPECIFY GWFMOVER IN FMI PACKAGEDATA.' + write (errmsg, '(a)') 'GWF water mover terms are not available & + &but the GWT MVT package has been activated. Activate GWF-GWT & + &exchange or specify GWFMOVER in FMI PACKAGEDATA.' call store_error(errmsg, terminate=.TRUE.) end if end if @@ -786,7 +786,7 @@ subroutine read_options(this) write (this%iout, fmtifc) this%iflowerr = 1 case default - write (errmsg, '(4x,a,a)') '***ERROR. UNKNOWN FMI OPTION: ', & + write (errmsg, '(a,a)') 'Unknown FMI option: ', & trim(keyword) call store_error(errmsg) call this%parser%StoreErrorUnit() @@ -848,7 +848,7 @@ subroutine read_packagedata(this) case ('GWFBUDGET') call this%parser%GetStringCaps(keyword) if (keyword /= 'FILEIN') then - call store_error('GWFBUDGET KEYWORD MUST BE FOLLOWED BY '// & + call store_error('GWFBUDGET keyword must be followed by '// & '"FILEIN" then by filename.') call this%parser%StoreErrorUnit() end if @@ -866,7 +866,7 @@ subroutine read_packagedata(this) case ('GWFHEAD') call this%parser%GetStringCaps(keyword) if (keyword /= 'FILEIN') then - call store_error('GWFHEAD KEYWORD MUST BE FOLLOWED BY '// & + call store_error('GWFHEAD keyword must be followed by '// & '"FILEIN" then by filename.') call this%parser%StoreErrorUnit() end if @@ -884,7 +884,7 @@ subroutine read_packagedata(this) case ('GWFMOVER') call this%parser%GetStringCaps(keyword) if (keyword /= 'FILEIN') then - call store_error('GWFMOVER KEYWORD MUST BE FOLLOWED BY '// & + call store_error('GWFMOVER keyword must be followed by '// & '"FILEIN" then by filename.') call this%parser%StoreErrorUnit() end if @@ -915,7 +915,7 @@ subroutine read_packagedata(this) pname = keyword(1:LENPACKAGENAME) call this%parser%GetStringCaps(keyword) if (keyword /= 'FILEIN') then - call store_error('PACKAGE NAME MUST BE FOLLOWED BY '// & + call store_error('Package name must be followed by '// & '"FILEIN" then by filename.') call this%parser%StoreErrorUnit() end if @@ -1027,9 +1027,9 @@ subroutine advance_bfr(this) readnext = .false. end if else if (this%bfr%endoffile) then - write (errmsg, '(4x,a)') 'REACHED END OF GWF BUDGET & - &FILE BEFORE READING SUFFICIENT BUDGET INFORMATION FOR THIS & - &GWT SIMULATION.' + write (errmsg, '(a)') 'Reached end of GWF budget & + &file before reading sufficient budget information for this & + &GWT simulation.' call store_error(errmsg) call store_error_unit(this%iubud) end if @@ -1047,29 +1047,29 @@ subroutine advance_bfr(this) do n = 1, this%bfr%nbudterms call this%bfr%read_record(success, this%iout) if (.not. success) then - write (errmsg, '(4x,a)') 'GWF BUDGET READ NOT SUCCESSFUL' + write (errmsg, '(a)') 'GWF budget read not successful' call store_error(errmsg) call store_error_unit(this%iubud) end if ! ! -- Ensure kper is same between model and budget file if (kper /= this%bfr%kper) then - write (errmsg, '(4x,a)') 'PERIOD NUMBER IN BUDGET FILE & - &DOES NOT MATCH PERIOD NUMBER IN TRANSPORT MODEL. IF THERE & - &IS MORE THAN ONE TIME STEP IN THE BUDGET FILE FOR A GIVEN STRESS & - &PERIOD, BUDGET FILE TIME STEPS MUST MATCH GWT MODEL TIME STEPS & - &ONE-FOR-ONE IN THAT STRESS PERIOD.' + write (errmsg, '(a)') 'Period number in budget file & + &does not match period number in transport model. If there & + &is more than one time step in the budget file for a given stress & + &period, budget file time steps must match GWT model time steps & + &one-for-one in that stress period.' call store_error(errmsg) call store_error_unit(this%iubud) end if ! ! -- if budget file kstp > 1, then kstp must match if (this%bfr%kstp > 1 .and. (kstp /= this%bfr%kstp)) then - write (errmsg, '(4x,a)') 'TIME STEP NUMBER IN BUDGET FILE & - &DOES NOT MATCH TIME STEP NUMBER IN TRANSPORT MODEL. IF THERE & - &IS MORE THAN ONE TIME STEP IN THE BUDGET FILE FOR A GIVEN STRESS & - &PERIOD, BUDGET FILE TIME STEPS MUST MATCH GWT MODEL TIME STEPS & - &ONE-FOR-ONE IN THAT STRESS PERIOD.' + write (errmsg, '(a)') 'Time step number in budget file & + &does not match time step number in transport model. If there & + &is more than one time step in the budget file for a given stress & + &period, budget file time steps must match gwt model time steps & + &one-for-one in that stress period.' call store_error(errmsg) call store_error_unit(this%iubud) end if @@ -1207,9 +1207,9 @@ subroutine advance_hfr(this) readnext = .false. end if else if (this%hfr%endoffile) then - write (errmsg, '(4x,a)') 'REACHED END OF GWF HEAD & - &FILE BEFORE READING SUFFICIENT HEAD INFORMATION FOR THIS & - &GWT SIMULATION.' + write (errmsg, '(a)') 'Reached end of GWF head & + &file before reading sufficient head information for this & + &GWT simulation.' call store_error(errmsg) call store_error_unit(this%iuhds) end if @@ -1227,29 +1227,29 @@ subroutine advance_hfr(this) ! -- read next head chunk call this%hfr%read_record(success, this%iout) if (.not. success) then - write (errmsg, '(4x,a)') 'GWF HEAD READ NOT SUCCESSFUL' + write (errmsg, '(a)') 'GWF head read not successful' call store_error(errmsg) call store_error_unit(this%iuhds) end if ! ! -- Ensure kper is same between model and head file if (kper /= this%hfr%kper) then - write (errmsg, '(4x,a)') 'PERIOD NUMBER IN HEAD FILE & - &DOES NOT MATCH PERIOD NUMBER IN TRANSPORT MODEL. IF THERE & - &IS MORE THAN ONE TIME STEP IN THE HEAD FILE FOR A GIVEN STRESS & - &PERIOD, HEAD FILE TIME STEPS MUST MATCH GWT MODEL TIME STEPS & - &ONE-FOR-ONE IN THAT STRESS PERIOD.' + write (errmsg, '(a)') 'Period number in head file & + &does not match period number in transport model. If there & + &is more than one time step in the head file for a given stress & + &period, head file time steps must match gwt model time steps & + &one-for-one in that stress period.' call store_error(errmsg) call store_error_unit(this%iuhds) end if ! ! -- if head file kstp > 1, then kstp must match if (this%hfr%kstp > 1 .and. (kstp /= this%hfr%kstp)) then - write (errmsg, '(4x,a)') 'TIME STEP NUMBER IN HEAD FILE & - &DOES NOT MATCH TIME STEP NUMBER IN TRANSPORT MODEL. IF THERE & - &IS MORE THAN ONE TIME STEP IN THE HEAD FILE FOR A GIVEN STRESS & - &PERIOD, HEAD FILE TIME STEPS MUST MATCH GWT MODEL TIME STEPS & - &ONE-FOR-ONE IN THAT STRESS PERIOD.' + write (errmsg, '(a)') 'Time step number in head file & + &does not match time step number in transport model. If there & + &is more than one time step in the head file for a given stress & + &period, head file time steps must match gwt model time steps & + &one-for-one in that stress period.' call store_error(errmsg) call store_error_unit(this%iuhds) end if @@ -1367,21 +1367,21 @@ subroutine initialize_gwfterms_from_bfr(this) ! ! -- Error if specific discharge, saturation or flowja not found if (.not. found_dataspdis) then - write (errmsg, '(4x,a)') 'SPECIFIC DISCHARGE NOT FOUND IN & - &BUDGET FILE. SAVE_SPECIFIC_DISCHARGE AND & - &SAVE_FLOWS MUST BE ACTIVATED IN THE NPF PACKAGE.' + write (errmsg, '(a)') 'Specific discharge not found in & + &budget file. SAVE_SPECIFIC_DISCHARGE and & + &SAVE_FLOWS must be activated in the NPF package.' call store_error(errmsg) end if if (.not. found_datasat) then - write (errmsg, '(4x,a)') 'SATURATION NOT FOUND IN & - &BUDGET FILE. SAVE_SATURATION AND & - &SAVE_FLOWS MUST BE ACTIVATED IN THE NPF PACKAGE.' + write (errmsg, '(a)') 'Saturation not found in & + &budget file. SAVE_SATURATION and & + &SAVE_FLOWS must be activated in the NPF package.' call store_error(errmsg) end if if (.not. found_flowja) then - write (errmsg, '(4x,a)') 'FLOWJA NOT FOUND IN & - &BUDGET FILE. SAVE_FLOWS MUST & - &BE ACTIVATED IN THE NPF PACKAGE.' + write (errmsg, '(a)') 'FLOWJA not found in & + &budget file. SAVE_FLOWS must & + &be activated in the NPF package.' call store_error(errmsg) end if if (count_errors() > 0) then diff --git a/src/Model/GroundWaterTransport/gwt1ic1.f90 b/src/Model/GroundWaterTransport/gwt1ic1.f90 index b89d932e5bd..e9d872a7137 100644 --- a/src/Model/GroundWaterTransport/gwt1ic1.f90 +++ b/src/Model/GroundWaterTransport/gwt1ic1.f90 @@ -95,7 +95,7 @@ subroutine read_data(this) this%parser%iuactive, this%strt, & aname(1)) case default - write (errmsg, '(4x,a,a)') 'ERROR. UNKNOWN GRIDDATA TAG: ', & + write (errmsg, '(a,a)') 'Unknown GRIDDATA tag: ', & trim(keyword) call store_error(errmsg) call this%parser%StoreErrorUnit() @@ -103,7 +103,7 @@ subroutine read_data(this) end do write (this%iout, '(1x,a)') 'END PROCESSING GRIDDATA' else - call store_error('ERROR. REQUIRED GRIDDATA BLOCK NOT FOUND.') + call store_error('Required GRIDDATA block not found.') call this%parser%StoreErrorUnit() end if ! diff --git a/src/Model/GroundWaterTransport/gwt1ist1.f90 b/src/Model/GroundWaterTransport/gwt1ist1.f90 index 5e6c3358090..875a89ddfd7 100644 --- a/src/Model/GroundWaterTransport/gwt1ist1.f90 +++ b/src/Model/GroundWaterTransport/gwt1ist1.f90 @@ -873,8 +873,8 @@ subroutine read_options(this) this%ibudgetout found = .true. else - call store_error('OPTIONAL BUDGET KEYWORD MUST & - &BE FOLLOWED BY FILEOUT') + call store_error('Optional BUDGET keyword must & + &be followed by FILEOUT') end if case ('BUDGETCSV') call this%parser%GetStringCaps(keyword) @@ -886,7 +886,7 @@ subroutine read_options(this) write (this%iout, fmtistbin) 'BUDGET CSV', trim(adjustl(fname)), & this%ibudcsv else - call store_error('OPTIONAL BUDGETCSV KEYWORD MUST BE FOLLOWED BY & + call store_error('Optional BUDGETCSV keyword must be followed by & &FILEOUT') end if case ('SORBTION', 'SORPTION') @@ -899,7 +899,7 @@ subroutine read_options(this) this%idcy = 2 write (this%iout, fmtidcy2) case default - write (errmsg, '(4x,a,a)') '****ERROR. UNKNOWN IST OPTION: ', & + write (errmsg, '(a,a)') 'Unknown IST option: ', & trim(keyword) call store_error(errmsg) call this%parser%StoreErrorUnit() @@ -1032,14 +1032,14 @@ subroutine read_data(this) call store_error(errmsg) call this%parser%StoreErrorUnit() case default - write (errmsg, '(4x,a,a)') 'Unknown GRIDDATA tag: ', trim(keyword) + write (errmsg, '(a,a)') 'Unknown GRIDDATA tag: ', trim(keyword) call store_error(errmsg) call this%parser%StoreErrorUnit() end select end do write (this%iout, '(1x,a)') 'END PROCESSING GRIDDATA' else - write (errmsg, '(1x,a)') 'Required GRIDDATA block not found.' + write (errmsg, '(a)') 'Required GRIDDATA block not found.' call store_error(errmsg) call this%parser%StoreErrorUnit() end if @@ -1047,12 +1047,12 @@ subroutine read_data(this) ! -- Check for required sorption variables if (this%isrb > 0) then if (.not. lname(1)) then - write (errmsg, '(1x,a)') 'Sorption is active but BULK_DENSITY & + write (errmsg, '(a)') 'Sorption is active but BULK_DENSITY & ¬ specified. BULK_DENSITY must be specified in griddata block.' call store_error(errmsg) end if if (.not. lname(2)) then - write (errmsg, '(1x,a)') 'Sorption is active but distribution & + write (errmsg, '(a)') 'Sorption is active but distribution & &coefficient not specified. DISTCOEF must be specified in & &GRIDDATA block.' call store_error(errmsg) @@ -1073,7 +1073,7 @@ subroutine read_data(this) ! -- Check for required decay/production rate coefficients if (this%idcy > 0) then if (.not. lname(3)) then - write (errmsg, '(1x,a)') 'First- or zero-order decay is & + write (errmsg, '(a)') 'First- or zero-order decay is & &active but the first rate coefficient was not specified. & &Decay must be specified in GRIDDATA block.' call store_error(errmsg) @@ -1111,19 +1111,19 @@ subroutine read_data(this) &Setting CIM to zero.' end if if (.not. lname(6)) then - write (errmsg, '(1x,a)') 'Dual domain is active but dual & + write (errmsg, '(a)') 'Dual domain is active but dual & &domain mass transfer rate (ZETAIM) was not specified. ZETAIM & &must be specified in GRIDDATA block.' call store_error(errmsg) end if if (.not. lname(7)) then - write (errmsg, '(1x,a)') 'Dual domain is active but & + write (errmsg, '(a)') 'Dual domain is active but & &immobile domain POROSITY was not specified. POROSITY & &must be specified in GRIDDATA block.' call store_error(errmsg) end if if (.not. lname(8)) then - write (errmsg, '(1x,a)') 'Dual domain is active but & + write (errmsg, '(a)') 'Dual domain is active but & &immobile domain VOLFRAC was not specified. VOLFRAC & &must be specified in GRIDDATA block. This is a new & &requirement for MODFLOW versions later than version & diff --git a/src/Model/GroundWaterTransport/gwt1lkt1.f90 b/src/Model/GroundWaterTransport/gwt1lkt1.f90 index 338f0675b30..98ef40abcd0 100644 --- a/src/Model/GroundWaterTransport/gwt1lkt1.f90 +++ b/src/Model/GroundWaterTransport/gwt1lkt1.f90 @@ -197,7 +197,7 @@ subroutine find_lkt_package(this) ! ! -- error if flow package not found if (.not. found) then - write (errmsg, '(a)') 'COULD NOT FIND FLOW PACKAGE WITH NAME '& + write (errmsg, '(a)') 'Could not find flow package with name '& &//trim(adjustl(this%flowpackagename))//'.' call store_error(errmsg) call this%parser%StoreErrorUnit() diff --git a/src/Model/GroundWaterTransport/gwt1mst1.f90 b/src/Model/GroundWaterTransport/gwt1mst1.f90 index c1343f0bfa4..415053cc195 100644 --- a/src/Model/GroundWaterTransport/gwt1mst1.f90 +++ b/src/Model/GroundWaterTransport/gwt1mst1.f90 @@ -1198,7 +1198,7 @@ subroutine read_options(this) this%idcy = 2 write (this%iout, fmtidcy2) case default - write (errmsg, '(a,a)') 'UNKNOWN MST OPTION: ', trim(keyword) + write (errmsg, '(a,a)') 'Unknown MST option: ', trim(keyword) call store_error(errmsg) call this%parser%StoreErrorUnit() end select @@ -1297,64 +1297,64 @@ subroutine read_data(this) aname(6)) lname(6) = .true. case default - write (errmsg, '(a,a)') 'UNKNOWN GRIDDATA TAG: ', trim(keyword) + write (errmsg, '(a,a)') 'Unknown GRIDDATA tag: ', trim(keyword) call store_error(errmsg) call this%parser%StoreErrorUnit() end select end do write (this%iout, '(1x,a)') 'END PROCESSING GRIDDATA' else - write (errmsg, '(a)') 'REQUIRED GRIDDATA BLOCK NOT FOUND.' + write (errmsg, '(a)') 'Required GRIDDATA block not found.' call store_error(errmsg) call this%parser%StoreErrorUnit() end if ! ! -- Check for rquired porosity if (.not. lname(1)) then - write (errmsg, '(a)') 'POROSITY NOT SPECIFIED IN GRIDDATA BLOCK.' + write (errmsg, '(a)') 'POROSITY not specified in GRIDDATA block.' call store_error(errmsg) end if ! ! -- Check for required sorption variables if (this%isrb > 0) then if (.not. lname(2)) then - write (errmsg, '(a)') 'SORPTION IS ACTIVE BUT BULK_DENSITY & - &NOT SPECIFIED. BULK_DENSITY MUST BE SPECIFIED IN GRIDDATA BLOCK.' + write (errmsg, '(a)') 'Sorption is active but BULK_DENSITY & + ¬ specified. BULK_DENSITY must be specified in GRIDDATA block.' call store_error(errmsg) end if if (.not. lname(3)) then - write (errmsg, '(a)') 'SORPTION IS ACTIVE BUT DISTRIBUTION & - &COEFFICIENT NOT SPECIFIED. DISTCOEF MUST BE SPECIFIED IN & - &GRIDDATA BLOCK.' + write (errmsg, '(a)') 'Sorption is active but distribution & + &coefficient not specified. DISTCOEF must be specified in & + &GRIDDATA block.' call store_error(errmsg) end if if (this%isrb > 1) then if (.not. lname(6)) then - write (errmsg, '(a)') 'FREUNDLICH OR LANGMUIR SORPTION IS ACTIVE & - &BUT SP2 NOT SPECIFIED. SP2 MUST BE SPECIFIED IN & - &GRIDDATA BLOCK.' + write (errmsg, '(a)') 'Freundlich or langmuir sorption is active & + &but SP2 not specified. SP2 must be specified in & + &GRIDDATA block.' call store_error(errmsg) end if end if else if (lname(2)) then - write (warnmsg, '(a)') 'SORPTION IS NOT ACTIVE BUT & - &BULK_DENSITY WAS SPECIFIED. BULK_DENSITY WILL HAVE NO AFFECT ON & - &SIMULATION RESULTS.' + write (warnmsg, '(a)') 'Sorption is not active but & + &BULK_DENSITY was specified. BULK_DENSITY will have no affect on & + &simulation results.' call store_warning(warnmsg) write (this%iout, '(1x,a)') 'WARNING. '//warnmsg end if if (lname(3)) then - write (warnmsg, '(a)') 'SORPTION IS NOT ACTIVE BUT & - &DISTRIBUTION COEFFICIENT WAS SPECIFIED. DISTCOEF WILL HAVE & - &NO AFFECT ON SIMULATION RESULTS.' + write (warnmsg, '(a)') 'Sorption is not active but & + &distribution coefficient was specified. DISTCOEF will have & + &no affect on simulation results.' call store_warning(warnmsg) write (this%iout, '(1x,a)') 'WARNING. '//warnmsg end if if (lname(6)) then - write (warnmsg, '(a)') 'SORPTION IS NOT ACTIVE BUT & - &SP2 WAS SPECIFIED. SP2 WILL HAVE & - &NO AFFECT ON SIMULATION RESULTS.' + write (warnmsg, '(a)') 'Sorption is not active but & + &SP2 was specified. SP2 will have & + &no affect on simulation results.' call store_warning(warnmsg) write (this%iout, '(1x,a)') 'WARNING. '//warnmsg end if @@ -1363,9 +1363,9 @@ subroutine read_data(this) ! -- Check for required decay/production rate coefficients if (this%idcy > 0) then if (.not. lname(4)) then - write (errmsg, '(a)') 'FIRST OR ZERO ORDER DECAY IS & - &ACTIVE BUT THE FIRST RATE COEFFICIENT IS NOT SPECIFIED. DECAY & - &MUST BE SPECIFIED IN GRIDDATA BLOCK.' + write (errmsg, '(a)') 'First or zero order decay is & + &active but the first rate coefficient is not specified. DECAY & + &must be specified in GRIDDATA block.' call store_error(errmsg) end if if (.not. lname(5)) then @@ -1381,16 +1381,16 @@ subroutine read_data(this) end if else if (lname(4)) then - write (warnmsg, '(a)') 'FIRST OR ZERO ORER DECAY & - &IS NOT ACTIVE BUT DECAY WAS SPECIFIED. DECAY WILL & - &HAVE NO AFFECT ON SIMULATION RESULTS.' + write (warnmsg, '(a)') 'First or zero orer decay & + &is not active but decay was specified. DECAY will & + &have no affect on simulation results.' call store_warning(warnmsg) write (this%iout, '(1x,a)') 'WARNING. '//warnmsg end if if (lname(5)) then - write (warnmsg, '(a)') 'FIRST OR ZERO ORER DECAY & - &IS NOT ACTIVE BUT DECAY_SORBED WAS SPECIFIED. & - &DECAY_SORBED WILL HAVE NO AFFECT ON SIMULATION RESULTS.' + write (warnmsg, '(a)') 'First or zero orer decay & + &is not active but DECAY_SORBED was specified. & + &DECAY_SORBED will have no affect on simulation results.' call store_warning(warnmsg) write (this%iout, '(1x,a)') 'WARNING. '//warnmsg end if diff --git a/src/Model/GroundWaterTransport/gwt1mvt1.f90 b/src/Model/GroundWaterTransport/gwt1mvt1.f90 index 5a5bee11e1c..732b2e59ac3 100644 --- a/src/Model/GroundWaterTransport/gwt1mvt1.f90 +++ b/src/Model/GroundWaterTransport/gwt1mvt1.f90 @@ -721,8 +721,8 @@ subroutine read_options(this) write (this%iout, fmtflow) 'MVT', 'BUDGET', trim(adjustl(fname)), & this%ibudgetout else - call store_error('OPTIONAL BUDGET KEYWORD MUST & - &BE FOLLOWED BY FILEOUT') + call store_error('Optional BUDGET keyword must & + &be followed by FILEOUT') end if case ('BUDGETCSV') call this%parser%GetStringCaps(keyword) @@ -734,11 +734,11 @@ subroutine read_options(this) write (this%iout, fmtflow) 'MVT', 'BUDGET CSV', & trim(adjustl(fname)), this%ibudcsv else - call store_error('OPTIONAL BUDGETCSV KEYWORD MUST BE FOLLOWED BY & + call store_error('Optional BUDGETCSV keyword must be followed by & &FILEOUT') end if case default - write (errmsg, '(4x,a,a)') '***ERROR. UNKNOWN MVT OPTION: ', & + write (errmsg, '(a,a)') 'Unknown MVT option: ', & trim(keyword) call store_error(errmsg) call this%parser%StoreErrorUnit() diff --git a/src/Model/GroundWaterTransport/gwt1mwt1.f90 b/src/Model/GroundWaterTransport/gwt1mwt1.f90 index 1dc65492678..15137d3a5c6 100644 --- a/src/Model/GroundWaterTransport/gwt1mwt1.f90 +++ b/src/Model/GroundWaterTransport/gwt1mwt1.f90 @@ -190,7 +190,7 @@ subroutine find_mwt_package(this) ! ! -- error if flow package not found if (.not. found) then - write (errmsg, '(a)') 'COULD NOT FIND FLOW PACKAGE WITH NAME '& + write (errmsg, '(a)') 'Could not find flow package with name '& &//trim(adjustl(this%flowpackagename))//'.' call store_error(errmsg) call this%parser%StoreErrorUnit() diff --git a/src/Model/GroundWaterTransport/gwt1obs1.f90 b/src/Model/GroundWaterTransport/gwt1obs1.f90 index b16c0658f12..48dd58f0e7c 100644 --- a/src/Model/GroundWaterTransport/gwt1obs1.f90 +++ b/src/Model/GroundWaterTransport/gwt1obs1.f90 @@ -141,7 +141,7 @@ subroutine gwt_obs_bd(this) case ('FLOW-JA-FACE') call this%SaveOneSimval(obsrv, this%flowja(jaindex)) case default - msg = 'Error: Unrecognized observation type: '//trim(obsrv%ObsTypeId) + msg = ' Unrecognized observation type: '//trim(obsrv%ObsTypeId) call store_error(msg) call store_error_unit(this%inUnitObs) end select diff --git a/src/Model/GroundWaterTransport/gwt1sft1.f90 b/src/Model/GroundWaterTransport/gwt1sft1.f90 index 368ce1c2e13..fe310f5eb42 100644 --- a/src/Model/GroundWaterTransport/gwt1sft1.f90 +++ b/src/Model/GroundWaterTransport/gwt1sft1.f90 @@ -194,7 +194,7 @@ subroutine find_sft_package(this) ! ! -- error if flow package not found if (.not. found) then - write (errmsg, '(a)') 'COULD NOT FIND FLOW PACKAGE WITH NAME '& + write (errmsg, '(a)') 'Could not find flow package with name '& &//trim(adjustl(this%flowpackagename))//'.' call store_error(errmsg) call this%parser%StoreErrorUnit() diff --git a/src/Model/GroundWaterTransport/gwt1ssm1.f90 b/src/Model/GroundWaterTransport/gwt1ssm1.f90 index d671a7cbc6a..e8684820918 100644 --- a/src/Model/GroundWaterTransport/gwt1ssm1.f90 +++ b/src/Model/GroundWaterTransport/gwt1ssm1.f90 @@ -156,12 +156,12 @@ subroutine ssm_ar(this, dis, ibound, cnew) ! ! -- Check to make sure that there are flow packages if (this%fmi%nflowpack == 0) then - write (errmsg, '(a)') 'SSM PACKAGE DOES NOT DETECT ANY BOUNDARY FLOWS & - &THAT REQUIRE SSM TERMS. ACTIVATE GWF-GWT & - &EXCHANGE OR ACTIVATE FMI PACKAGE AND PROVIDE A & - &BUDGET FILE THAT CONTAINS BOUNDARY FLOWS. IF NO & - &BOUNDARY FLOWS ARE PRESENT IN CORRESPONDING GWF & - &MODEL THEN THIS SSM PACKAGE SHOULD BE REMOVED.' + write (errmsg, '(a)') 'SSM package does not detect any boundary flows & + &that require SSM terms. Activate GWF-GWT & + &exchange or activate FMI package and provide a & + &budget file that contains boundary flows. If no & + &boundary flows are present in corresponding GWF & + &model then this SSM package should be removed.' call store_error(errmsg) call this%parser%StoreErrorUnit() end if @@ -823,7 +823,7 @@ subroutine read_options(this) this%ipakcb = -1 write (this%iout, fmtisvflow) case default - write (errmsg, '(4x,a,a)') 'UNKNOWN SSM OPTION: ', trim(keyword) + write (errmsg, '(a,a)') 'Unknown SSM option: ', trim(keyword) call store_error(errmsg) call this%parser%StoreErrorUnit() end select @@ -899,7 +899,7 @@ subroutine read_sources_aux(this) end if end do if (.not. pakfound) then - write (errmsg, '(1x, a, a)') 'FLOW PACKAGE CANNOT BE FOUND: ', & + write (errmsg, '(a,a)') 'Flow package cannot be found: ', & trim(keyword) call store_error(errmsg) call this%parser%StoreErrorUnit() @@ -907,9 +907,9 @@ subroutine read_sources_aux(this) ! ! -- Ensure package was not specified more than once in SOURCES block if (this%isrctype(ip) /= 0) then - write (errmsg, '(1x, a, a)') & - 'A PACKAGE CANNOT BE SPECIFIED MORE THAN ONCE IN THE SSM SOURCES & - &BLOCK. THE FOLLOWING PACKAGE WAS SPECIFIED MORE THAN ONCE: ', & + write (errmsg, '(a, a)') & + 'A package cannot be specified more than once in the SSM SOURCES & + &block. The following package was specified more than once: ', & trim(keyword) call store_error(errmsg) call this%parser%StoreErrorUnit() @@ -926,8 +926,8 @@ subroutine read_sources_aux(this) lauxmixed = .true. isrctype = 2 case default - write (errmsg, '(1x, a, a)') & - 'SRCTYPE MUST BE AUX OR AUXMIXED. FOUND: ', trim(srctype) + write (errmsg, '(a, a)') & + 'SRCTYPE must be AUX or AUXMIXED. Found: ', trim(srctype) call store_error(errmsg) call this%parser%StoreErrorUnit() end select @@ -941,7 +941,7 @@ subroutine read_sources_aux(this) end do write (this%iout, '(1x,a)') 'END PROCESSING SOURCES' else - write (errmsg, '(1x,a)') 'ERROR. REQUIRED SOURCES BLOCK NOT FOUND.' + write (errmsg, '(a)') 'Required SOURCES block not found.' call store_error(errmsg) call this%parser%StoreErrorUnit() end if @@ -1003,7 +1003,7 @@ subroutine read_sources_fileinput(this) end if end do if (.not. pakfound) then - write (errmsg, '(1x, a, a)') 'FLOW PACKAGE CANNOT BE FOUND: ', & + write (errmsg, '(a,a)') 'Flow package cannot be found: ', & trim(keyword) call store_error(errmsg) call this%parser%StoreErrorUnit() @@ -1011,10 +1011,10 @@ subroutine read_sources_fileinput(this) ! ! -- Ensure package was not specified more than once in SOURCES block if (this%isrctype(ip) /= 0) then - write (errmsg, '(1x, a, a)') & - 'A PACKAGE CANNOT BE SPECIFIED MORE THAN ONCE IN THE SSM SOURCES & - &AND SOURCES_FILES BLOCKS. THE FOLLOWING PACKAGE WAS SPECIFIED & - &MORE THAN ONCE: ', & + write (errmsg, '(a, a)') & + 'A package cannot be specified more than once in the SSM SOURCES & + &and SOURCES_FILES blocks. The following package was specified & + &more than once: ', & trim(keyword) call store_error(errmsg) call this%parser%StoreErrorUnit() @@ -1048,8 +1048,8 @@ subroutine read_sources_fileinput(this) trim(keyword) end if case default - write (errmsg, '(1x, a, a)') & - 'SRCTYPE MUST BE SPC6. FOUND: ', trim(srctype) + write (errmsg, '(a,a)') & + 'SRCTYPE must be SPC6. Found: ', trim(srctype) call store_error(errmsg) call this%parser%StoreErrorUnit() end select @@ -1103,8 +1103,8 @@ subroutine set_iauxpak(this, ip, packname) end if end do if (.not. auxfound) then - write (errmsg, '(1x, a, a)') & - 'AUXILIARY NAME CANNOT BE FOUND: ', trim(auxname) + write (errmsg, '(a, a)') & + 'Auxiliary name cannot be found: ', trim(auxname) call store_error(errmsg) call this%parser%StoreErrorUnit() end if diff --git a/src/Model/GroundWaterTransport/gwt1uzt1.f90 b/src/Model/GroundWaterTransport/gwt1uzt1.f90 index 939ee3706d0..c6be55aec38 100644 --- a/src/Model/GroundWaterTransport/gwt1uzt1.f90 +++ b/src/Model/GroundWaterTransport/gwt1uzt1.f90 @@ -182,7 +182,7 @@ subroutine find_uzt_package(this) ! ! -- error if flow package not found if (.not. found) then - write (errmsg, '(a)') 'COULD NOT FIND FLOW PACKAGE WITH NAME '& + write (errmsg, '(a)') 'Could not find flow package with name '& &//trim(adjustl(this%flowpackagename))//'.' call store_error(errmsg) call this%parser%StoreErrorUnit() diff --git a/src/Model/ModelUtilities/BoundaryPackage.f90 b/src/Model/ModelUtilities/BoundaryPackage.f90 index a7f44c9f025..1ec95293e16 100644 --- a/src/Model/ModelUtilities/BoundaryPackage.f90 +++ b/src/Model/ModelUtilities/BoundaryPackage.f90 @@ -1400,8 +1400,8 @@ subroutine bnd_read_options(this) ! -- Error if no aux variable specified if (this%naux == 0) then write (errmsg, '(a,2(1x,a))') & - 'AUXMULTNAME WAS SPECIFIED AS', trim(adjustl(sfacauxname)), & - 'BUT NO AUX VARIABLES SPECIFIED.' + 'AUXMULTNAME was specified as', trim(adjustl(sfacauxname)), & + 'but no AUX variables specified.' call store_error(errmsg) end if ! @@ -1417,8 +1417,8 @@ subroutine bnd_read_options(this) ! -- Error if aux variable cannot be found if (this%iauxmultcol == 0) then write (errmsg, '(a,2(1x,a))') & - 'AUXMULTNAME WAS SPECIFIED AS', trim(adjustl(sfacauxname)), & - 'BUT NO AUX VARIABLE FOUND WITH THIS NAME.' + 'AUXMULTNAME was specified as', trim(adjustl(sfacauxname)), & + 'but no AUX variable found with this name.' call store_error(errmsg) end if end if @@ -1466,7 +1466,7 @@ subroutine bnd_read_dimensions(this) write (this%iout, '(4x,a,i7)') 'MAXBOUND = ', this%maxbound case default write (errmsg, '(a,3(1x,a))') & - 'UNKNOWN', trim(this%text), 'DIMENSION:', trim(keyword) + 'Unknown', trim(this%text), 'dimension:', trim(keyword) call store_error(errmsg) end select end do @@ -1474,13 +1474,13 @@ subroutine bnd_read_dimensions(this) write (this%iout, '(1x,a)') & 'END OF '//trim(adjustl(this%text))//' DIMENSIONS' else - call store_error('REQUIRED DIMENSIONS BLOCK NOT FOUND.') + call store_error('Required DIMENSIONS block not found.') call this%parser%StoreErrorUnit() end if ! ! -- verify dimensions were set if (this%maxbound <= 0) then - write (errmsg, '(a)') 'MAXBOUND MUST BE AN INTEGER GREATER THAN ZERO.' + write (errmsg, '(a)') 'MAXBOUND must be an integer greater than zero.' call store_error(errmsg) end if ! diff --git a/src/Model/ModelUtilities/Connections.f90 b/src/Model/ModelUtilities/Connections.f90 index de138daabcb..9dfaec39fae 100644 --- a/src/Model/ModelUtilities/Connections.f90 +++ b/src/Model/ModelUtilities/Connections.f90 @@ -407,23 +407,23 @@ subroutine read_connectivity_from_block(this, name_model, nodes, nja, iout) this%nja, iout, 0) lname(2) = .true. case default - write (errmsg, '(4x,a,a)') & - 'UNKNOWN CONNECTIONDATA TAG: ', trim(keyword) + write (errmsg, '(a,a)') & + 'Unknown CONNECTIONDATA tag: ', trim(keyword) call store_error(errmsg) call this%parser%StoreErrorUnit() end select end do write (iout, '(1x,a)') 'END PROCESSING CONNECTIONDATA' else - call store_error('REQUIRED CONNECTIONDATA BLOCK NOT FOUND.') + call store_error('Required CONNECTIONDATA block not found.') call this%parser%StoreErrorUnit() end if ! ! -- verify all items were read do n = 1, nname if (.not. lname(n)) then - write (errmsg, '(1x,a,a)') & - 'REQUIRED INPUT WAS NOT SPECIFIED: ', aname(n) + write (errmsg, '(a,a)') & + 'Required input was not specified: ', aname(n) call store_error(errmsg) end if end do diff --git a/src/Model/ModelUtilities/GwfMvrPeriodData.f90 b/src/Model/ModelUtilities/GwfMvrPeriodData.f90 index c65b6b55cb6..de72b63d822 100644 --- a/src/Model/ModelUtilities/GwfMvrPeriodData.f90 +++ b/src/Model/ModelUtilities/GwfMvrPeriodData.f90 @@ -101,7 +101,7 @@ subroutine read_from_parser(this, parser, nmvr, modelname) ! -- Raise error if movers exceeds maxmvr if (i > maxmvr) then call parser%GetCurrentLine(line) - write (errmsg, '(4x,a,a)') 'MOVERS EXCEED MAXMVR ON LINE: ', & + write (errmsg, '(a,a)') 'Movers exceed MAXMVR on line: ', & trim(adjustl(line)) call store_error(errmsg) call parser%StoreErrorUnit() @@ -137,7 +137,7 @@ subroutine read_from_parser(this, parser, nmvr, modelname) case ('UPTO') this%imvrtype(i) = 4 case default - call store_error('INVALID MOVER TYPE: '//trim(mvrtype_char)) + call store_error('Invalid mover type: '//trim(mvrtype_char)) call parser%StoreErrorUnit() end select this%value(i) = parser%GetDouble() diff --git a/src/Model/ModelUtilities/GwtSpc.f90 b/src/Model/ModelUtilities/GwtSpc.f90 index 8c2656caaf2..9b33f196e2d 100644 --- a/src/Model/ModelUtilities/GwtSpc.f90 +++ b/src/Model/ModelUtilities/GwtSpc.f90 @@ -243,7 +243,7 @@ subroutine read_options(this) write (this%iout, fmttas) trim(fname) call this%TasManager%add_tasfile(fname) case default - write (errmsg, '(4x,a,a)') 'UNKNOWN SPC OPTION: ', trim(keyword) + write (errmsg, '(a,a)') 'Unknown SPC option: ', trim(keyword) call store_error(errmsg) call this%parser%StoreErrorUnit() end select @@ -287,20 +287,20 @@ subroutine read_dimensions(this) write (this%iout, '(4x,a,i7)') 'MAXBOUND = ', this%maxbound case default write (errmsg, '(a,3(1x,a))') & - 'UNKNOWN', trim(text), 'DIMENSION:', trim(keyword) + 'Unknown', trim(text), 'dimension:', trim(keyword) call store_error(errmsg) end select end do ! write (this%iout, '(1x,a)') 'END OF '//trim(adjustl(text))//' DIMENSIONS' else - call store_error('REQUIRED DIMENSIONS BLOCK NOT FOUND.') + call store_error('Required DIMENSIONS block not found.') call this%parser%StoreErrorUnit() end if ! ! -- verify dimensions were set if (this%maxbound <= 0) then - write (errmsg, '(a)') 'MAXBOUND MUST BE AN INTEGER GREATER THAN ZERO.' + write (errmsg, '(a)') 'MAXBOUND must be an integer greater than zero.' call store_error(errmsg) end if ! @@ -605,7 +605,7 @@ subroutine spc_rp_array(this, line) end if ! case default - call store_error('LOOKING FOR CONCENTRATION. FOUND: '//trim(line)) + call store_error('Looking for CONCENTRATION. Found: '//trim(line)) call this%parser%StoreErrorUnit() end select @@ -692,14 +692,10 @@ subroutine read_check_ionper(this) ! ! -- make check if (this%ionper <= this%lastonper) then - write (errmsg, '(a, i0)') & - 'ERROR IN STRESS PERIOD ', kper - call store_error(errmsg) - write (errmsg, '(a, i0)') & - 'PERIOD NUMBERS NOT INCREASING. FOUND ', this%ionper - call store_error(errmsg) - write (errmsg, '(a, i0)') & - 'BUT LAST PERIOD BLOCK WAS ASSIGNED ', this%lastonper + write (errmsg, '(a, i0, a, i0, a, i0, a)') & + 'Error in stress period ', kper, & + '. Period numbers not increasing. Found ', this%ionper, & + ' but last period block was assigned ', this%lastonper, '.' call store_error(errmsg) call this%parser%StoreErrorUnit() end if diff --git a/src/Model/ModelUtilities/Mover.f90 b/src/Model/ModelUtilities/Mover.f90 index 0ff698fde01..64d52ce1a8c 100644 --- a/src/Model/ModelUtilities/Mover.f90 +++ b/src/Model/ModelUtilities/Mover.f90 @@ -104,7 +104,7 @@ subroutine prepare(this, inunit, pckMemPaths, pakmovers) ! -- Check to make sure provider and receiver are not the same if (this%pckNameSrc == this%pckNameTgt .and. & this%iRchNrSrc == this%iRchNrTgt) then - call store_error('PROVIDER AND RECEIVER ARE THE SAME: '// & + call store_error('Provider and receiver are the same: '// & trim(this%pckNameSrc)//' : '//trim(this%pckNameTgt)) call store_error_unit(inunit) end if @@ -121,8 +121,8 @@ subroutine prepare(this, inunit, pckMemPaths, pakmovers) end if end do if (.not. found) then - call store_error('MOVER CAPABILITY NOT ACTIVATED IN '//this%pckNameSrc) - call store_error('ADD "MOVER" KEYWORD TO PACKAGE OPTIONS BLOCK.') + call store_error('Mover capability not activated in '//this%pckNameSrc) + call store_error('Add "MOVER" keyword to package options block.') end if found = .false. ipakloc2 = 0 @@ -134,8 +134,8 @@ subroutine prepare(this, inunit, pckMemPaths, pakmovers) end if end do if (.not. found) then - call store_error('MOVER CAPABILITY NOT ACTIVATED IN '//this%pckNameTgt) - call store_error('ADD "MOVER" KEYWORD TO PACKAGE OPTIONS BLOCK.') + call store_error('Mover capability not activated in '//this%pckNameTgt) + call store_error('Add "MOVER" keyword to package options block.') end if if (count_errors() > 0) then call store_error_unit(inunit) @@ -144,9 +144,9 @@ subroutine prepare(this, inunit, pckMemPaths, pakmovers) ! -- Set pointer to QTOMVR array in the provider boundary package temp_ptr => pakmovers(ipakloc1)%qtomvr if (this%iRchNrSrc < 1 .or. this%iRchNrSrc > size(temp_ptr)) then - call store_error('PROVIDER ID < 1 OR GREATER THAN PACKAGE SIZE ') - write (errmsg, '(4x,a,i0,a,i0)') 'PROVIDER ID = ', this%iRchNrSrc, & - '; PACKAGE SIZE = ', size(temp_ptr) + call store_error('Provider ID < 1 or greater than package size ') + write (errmsg, '(a,i0,a,i0)') 'Provider ID = ', this%iRchNrSrc, & + '; Package size = ', size(temp_ptr) call store_error(trim(errmsg)) call store_error_unit(inunit) end if @@ -163,9 +163,9 @@ subroutine prepare(this, inunit, pckMemPaths, pakmovers) ! -- Set pointer to QFROMMVR array in the receiver boundary package temp_ptr => pakmovers(ipakloc2)%qfrommvr if (this%iRchNrTgt < 1 .or. this%iRchNrTgt > size(temp_ptr)) then - call store_error('RECEIVER ID < 1 OR GREATER THAN PACKAGE SIZE ') - write (errmsg, '(4x,a,i0,a,i0)') 'RECEIVER ID = ', this%iRchNrTgt, & - '; PACKAGE SIZE = ', size(temp_ptr) + call store_error('Receiver ID < 1 or greater than package size ') + write (errmsg, '(a,i0,a,i0)') 'Receiver ID = ', this%iRchNrTgt, & + '; package size = ', size(temp_ptr) call store_error(trim(errmsg)) call store_error_unit(inunit) end if diff --git a/src/Model/ModelUtilities/SfrCrossSectionManager.f90 b/src/Model/ModelUtilities/SfrCrossSectionManager.f90 index 87fdfc940a0..bee9e5fab66 100644 --- a/src/Model/ModelUtilities/SfrCrossSectionManager.f90 +++ b/src/Model/ModelUtilities/SfrCrossSectionManager.f90 @@ -205,19 +205,19 @@ subroutine read_table(this, irch, width, filename) case ('NROW') n = parser%GetInteger() if (n < 1) then - write (errmsg, '(a)') 'TABLE NROW MUST BE > 0' + write (errmsg, '(a)') 'Table NROW must be > 0' call store_error(errmsg) end if case ('NCOL') j = parser%GetInteger() jmin = 2 if (j < jmin) then - write (errmsg, '(a,1x,i0)') 'TABLE NCOL MUST BE >= ', jmin + write (errmsg, '(a,1x,i0)') 'Table NCOL must be >= ', jmin call store_error(errmsg) end if case default write (errmsg, '(a,a)') & - 'UNKNOWN '//trim(adjustl(tag))//' DIMENSIONS KEYWORD: ', & + 'UNKNOWN '//trim(adjustl(tag))//' DIMENSIONS keyword: ', & trim(keyword) call store_error(errmsg) end select @@ -227,18 +227,18 @@ subroutine read_table(this, irch, width, filename) 'END OF '//trim(adjustl(tag))//' DIMENSIONS' end if else - call store_error('REQUIRED DIMENSIONS BLOCK NOT FOUND.') + call store_error('Required DIMENSIONS block not found.') end if ! ! -- check that ncol and nrow have been specified if (n < 1) then write (errmsg, '(a)') & - 'NROW NOT SPECIFIED IN THE TABLE DIMENSIONS BLOCK' + 'NROW not specified in the table DIMENSIONS block' call store_error(errmsg) end if if (j < 1) then write (errmsg, '(a)') & - 'NCOL NOT SPECIFIED IN THE TABLE DIMENSIONS BLOCK' + 'NCOL not specified in the table DIMENSIONS block' call store_error(errmsg) end if ! @@ -301,13 +301,13 @@ subroutine read_table(this, irch, width, filename) 'END OF '//trim(adjustl(tag))//' TABLE' end if else - call store_error('REQUIRED TABLE BLOCK NOT FOUND.') + call store_error('Required TABLE block not found.') end if ! ! -- error condition if number of rows read are not equal to nrow if (ipos /= this%npoints(irch)) then write (errmsg, '(a,1x,i0,1x,a,1x,i0,1x,a)') & - 'NROW SET TO', this%npoints(irch), 'BUT', ipos, 'ROWS WERE READ' + 'NROW set to', this%npoints(irch), 'but', ipos, 'rows were read' call store_error(errmsg) end if end if diff --git a/src/Model/NumericalPackage.f90 b/src/Model/NumericalPackage.f90 index 07cd2882e6f..114256a4778 100644 --- a/src/Model/NumericalPackage.f90 +++ b/src/Model/NumericalPackage.f90 @@ -214,14 +214,10 @@ subroutine read_check_ionper(this) ! ! -- make check if (this%ionper <= this%lastonper) then - write (errmsg, '(a, i0)') & - 'ERROR IN STRESS PERIOD ', kper - call store_error(errmsg) - write (errmsg, '(a, i0)') & - 'PERIOD NUMBERS NOT INCREASING. FOUND ', this%ionper - call store_error(errmsg) - write (errmsg, '(a, i0)') & - 'BUT LAST PERIOD BLOCK WAS ASSIGNED ', this%lastonper + write (errmsg, '(a, i0, a, i0, a, i0, a)') & + 'Error in stress period ', kper, & + '. Period numbers not increasing. Found ', this%ionper, & + ' but last period block was assigned ', this%lastonper, '.' call store_error(errmsg) call this%parser%StoreErrorUnit() end if @@ -288,7 +284,7 @@ subroutine get_block_data(this, tags, lfound, varinames) end if end do tag_iter if (.not. lkeyword) then - write (errmsg, '(4x,a,a)') 'ERROR. UNKNOWN GRIDDATA TAG: ', & + write (errmsg, '(a,a)') 'Unknown GRIDDATA tag: ', & trim(keyword) call store_error(errmsg) call this%parser%StoreErrorUnit() diff --git a/src/RunControlFactory.F90 b/src/RunControlFactory.F90 index 3f452101c22..a6ca7215672 100644 --- a/src/RunControlFactory.F90 +++ b/src/RunControlFactory.F90 @@ -27,8 +27,8 @@ function create_run_control() result(controller) end if #else if (simulation_mode == 'PARALLEL') then - write (errmsg, '(1x,a)') & - 'ERROR. Can not run parallel mode with this executable: no MPI' + write (errmsg, '(a)') & + 'Can not run parallel mode with this executable: no MPI' call store_error(errmsg, terminate=.true.) end if controller => create_seq_run_control() diff --git a/src/SimulationCreate.f90 b/src/SimulationCreate.f90 index 9fad6a2f806..6407097b7b2 100644 --- a/src/SimulationCreate.f90 +++ b/src/SimulationCreate.f90 @@ -199,7 +199,7 @@ subroutine timing_create() if (tdis6 /= '') then call tdis_cr(tdis6) else - call store_error('****ERROR. TIMING block variable TDIS6 is unset'// & + call store_error('TIMING block variable TDIS6 is unset'// & ' in simulation control input.', terminate) end if ! @@ -280,8 +280,8 @@ subroutine models_create() case ('GWF6') if (model_ranks(n) == proc_id) then im = im + 1 - write (iout, '(4x,2a,i0,a)') trim(model_type), " model ", & - n, " will be created" + write (iout, '(4x,2a,i0,a)') trim(model_type), ' model ', & + n, ' will be created' call gwf_cr(fname, n, model_names(n)) num_model => GetNumericalModelFromList(basemodellist, im) model_loc_idx(n) = im @@ -290,17 +290,16 @@ subroutine models_create() case ('GWT6') if (model_ranks(n) == proc_id) then im = im + 1 - write (iout, '(4x,2a,i0,a)') trim(model_type), " model ", & - n, " will be created" + write (iout, '(4x,2a,i0,a)') trim(model_type), ' model ', & + n, ' will be created' call gwt_cr(fname, n, model_names(n)) num_model => GetNumericalModelFromList(basemodellist, im) model_loc_idx(n) = im end if call add_virtual_gwt_model(n, model_names(n), num_model) case default - write (errmsg, '(4x,a,a)') & - '****ERROR. UNKNOWN SIMULATION MODEL: ', & - trim(model_type) + write (errmsg, '(a,a)') & + 'Unknown simulation model type: ', trim(model_type) call store_error(errmsg, terminate) end select end do @@ -310,8 +309,8 @@ subroutine models_create() ! ! -- sanity check if (simulation_mode == 'PARALLEL' .and. im == 0) then - write (errmsg, '(4x,a, i0)') & - '****ERROR. No MODELS assigned to process ', proc_id + write (errmsg, '(a, i0)') & + 'No MODELS assigned to process ', proc_id call store_error(errmsg, terminate) end if ! @@ -422,9 +421,8 @@ subroutine exchanges_create() end if call add_virtual_gwt_exchange(exg_name, exg_id, m1_id, m2_id) case default - write (errmsg, '(4x,a,a)') & - '****ERROR. UNKNOWN SIMULATION EXCHANGES: ', & - trim(exgtype) + write (errmsg, '(a,a)') & + 'Unknown simulation exchange type: ', trim(exgtype) call store_error(errmsg, terminate) end select end do @@ -449,17 +447,17 @@ subroutine solution_group_check(sgp, sgid, isgpsoln) logical :: terminate = .true. ! -- formats character(len=*), parameter :: fmterrmxiter = & - "('ERROR. MXITER IS SET TO ', i0, ' BUT THERE IS ONLY ONE SOLUTION', & - &' IN SOLUTION GROUP ', i0, '. SET MXITER TO 1 IN SIMULATION CONTROL', & - &' FILE.')" + "('MXITER is set to ', i0, ' but there is only one solution', & + &' in SOLUTION GROUP ', i0, '. Set MXITER to 1 in simulation control', & + &' file.')" ! ! -- error check completed group if (sgid > 0) then ! ! -- Make sure there is a solution in this solution group if (isgpsoln == 0) then - write (errmsg, '(4x,a,i0)') & - 'ERROR. THERE ARE NO SOLUTIONS FOR SOLUTION GROUP ', sgid + write (errmsg, '(a,i0)') & + 'There are no solutions for solution group ', sgid call store_error(errmsg, terminate) end if ! @@ -596,8 +594,7 @@ subroutine solution_groups_create() call upcase(words(j)) glo_mid = ifind(model_names, words(j)) if (glo_mid == -1) then - write (errmsg, '(a,a)') 'Error. Invalid model name: ', & - trim(words(j)) + write (errmsg, '(a,a)') 'Invalid model name: ', trim(words(j)) call store_error(errmsg, terminate) end if ! @@ -634,8 +631,7 @@ subroutine solution_groups_create() call upcase(words(j)) glo_mid = ifind(model_names, words(j)) if (glo_mid == -1) then - write (errmsg, '(a,a)') 'Error. Invalid model name: ', & - trim(words(j)) + write (errmsg, '(a,a)') 'Invalid model name: ', trim(words(j)) call store_error(errmsg, terminate) end if ! @@ -668,7 +664,7 @@ subroutine solution_groups_create() ! ! -- Check and make sure at least one solution group was found if (solutiongrouplist%Count() == 0) then - call store_error('ERROR. THERE ARE NO SOLUTION GROUPS.', terminate) + call store_error('There are no solution groups.', terminate) end if ! ! -- return @@ -687,7 +683,7 @@ subroutine check_model_assignment() mp => GetBaseModelFromList(basemodellist, im) if (mp%idsoln == 0) then write (errmsg, '(a,a)') & - '****ERROR. Model was not assigned to a solution: ', mp%name + 'Model was not assigned to a solution: ', mp%name call store_error(errmsg) end if end do @@ -748,21 +744,19 @@ subroutine check_model_name(mtype, mname) ! ------------------------------------------------------------------------------ ilen = len_trim(mname) if (ilen > LENMODELNAME) then - write (errmsg, '(4x,a,a)') & - 'ERROR. INVALID MODEL NAME: ', trim(mname) + write (errmsg, '(a,a)') 'Invalid model name: ', trim(mname) call store_error(errmsg) - write (errmsg, '(4x,a,i0,a,i0)') & - 'NAME LENGTH OF ', ilen, ' EXCEEDS MAXIMUM LENGTH OF ', & + write (errmsg, '(a,i0,a,i0)') & + 'Name length of ', ilen, ' exceeds maximum length of ', & LENMODELNAME call store_error(errmsg, terminate) end if do i = 1, ilen if (mname(i:i) == ' ') then - write (errmsg, '(4x,a,a)') & - 'ERROR. INVALID MODEL NAME: ', trim(mname) + write (errmsg, '(a,a)') 'Invalid model name: ', trim(mname) call store_error(errmsg) - write (errmsg, '(4x,a)') & - 'MODEL NAME CANNOT HAVE SPACES WITHIN IT.' + write (errmsg, '(a)') & + 'Model name cannot have spaces within it.' call store_error(errmsg, terminate) end if end do @@ -826,7 +820,7 @@ subroutine create_load_balance(mranks) pointer :: emnames_b !< model b names mranks = 0 - if (simulation_mode /= "PARALLEL") return + if (simulation_mode /= 'PARALLEL') return ! load IDM data input_mempath = create_mem_path('SIM', 'NAM', idm_context) @@ -841,14 +835,14 @@ subroutine create_load_balance(mranks) nr_gwf_models = 0 nr_gwt_models = 0 do im = 1, nr_models - if (mtypes(im) == "GWF6") then + if (mtypes(im) == 'GWF6') then nr_gwf_models = nr_gwf_models + 1 - else if (mtypes(im) == "GWT6") then + else if (mtypes(im) == 'GWT6') then nr_gwt_models = nr_gwt_models + 1 else model_type_str = mtypes(im) - write (errmsg, *) "Error. Model type ", model_type_str, & - " not supported in parallel mode" + write (errmsg, *) 'Model type ', model_type_str, & + ' not supported in parallel mode.' call store_error(errmsg, terminate=.true.) end if end do @@ -869,7 +863,7 @@ subroutine create_load_balance(mranks) ! assign ranks for flow models rank = 0 do im = 1, nr_models - if (mtypes(im) == "GWF6") then + if (mtypes(im) == 'GWF6') then if (nr_models_proc(rank + 1) == 0) then rank = rank + 1 end if @@ -882,11 +876,11 @@ subroutine create_load_balance(mranks) nr_exchanges = size(etypes) do im = 1, nr_models - if (.not. mtypes(im) == "GWT6") cycle + if (.not. mtypes(im) == 'GWT6') cycle ! find match do ie = 1, nr_exchanges - if (etypes(ie) == "GWF6-GWT6" .and. mnames(im) == emnames_b(ie)) then + if (etypes(ie) == 'GWF6-GWT6' .and. mnames(im) == emnames_b(ie)) then ! this is the exchange, now find the flow model's rank rank = 0 do imm = 1, nr_models diff --git a/src/Solution/LinearMethods/ims8base.f90 b/src/Solution/LinearMethods/ims8base.f90 index a78ebccaf5e..7e927e6d0b7 100644 --- a/src/Solution/LinearMethods/ims8base.f90 +++ b/src/Solution/LinearMethods/ims8base.f90 @@ -612,8 +612,8 @@ SUBROUTINE ims_base_calc_order(IORD, NEQ, NJA, IA, JA, LORDER, IORDER) iwork1, iflag) IF (iflag .NE. 0) THEN write (errmsg, '(A,1X,A)') & - 'IMSLINEARSUB_CALC_ORDER ERROR CREATING MINIMUM DEGREE ', & - 'ORDER PERMUTATION ' + 'IMSLINEARSUB_CALC_ORDER error creating minimum degree ', & + 'order permutation ' call store_error(errmsg) END IF ! diff --git a/src/Solution/LinearMethods/ims8linear.f90 b/src/Solution/LinearMethods/ims8linear.f90 index 65a14098452..ecb7c2f16d7 100644 --- a/src/Solution/LinearMethods/ims8linear.f90 +++ b/src/Solution/LinearMethods/ims8linear.f90 @@ -244,7 +244,7 @@ SUBROUTINE imslinear_ar(this, NAME, parser, IOUT, IPRIMS, MXITER, IFDPARAM, & else this%ILINMETH = 0 write (errmsg, '(3a)') & - 'UNKNOWN IMSLINEAR LINEAR_ACCELERATION METHOD (', & + 'Unknown IMSLINEAR LINEAR_ACCELERATION method (', & trim(keyword), ').' call store_error(errmsg) end if @@ -259,7 +259,7 @@ SUBROUTINE imslinear_ar(this, NAME, parser, IOUT, IPRIMS, MXITER, IFDPARAM, & i = 2 else write (errmsg, '(3a)') & - 'UNKNOWN IMSLINEAR SCALING_METHOD (', trim(keyword), ').' + 'Unknown IMSLINEAR SCALING_METHOD (', trim(keyword), ').' call store_error(errmsg) end if this%ISCL = i @@ -276,7 +276,7 @@ SUBROUTINE imslinear_ar(this, NAME, parser, IOUT, IPRIMS, MXITER, IFDPARAM, & i = 2 else write (errmsg, '(3a)') & - 'UNKNOWN IMSLINEAR REORDERING_METHOD (', trim(keyword), ').' + 'Unknown IMSLINEAR REORDERING_METHOD (', trim(keyword), ').' call store_error(errmsg) end if this%IORD = i @@ -289,8 +289,8 @@ SUBROUTINE imslinear_ar(this, NAME, parser, IOUT, IPRIMS, MXITER, IFDPARAM, & this%level = i if (i < 0) then write (errmsg, '(a,1x,a)') & - 'IMSLINEAR PRECONDITIONER_LEVELS MUST BE GREATER THAN', & - 'OR EQUAL TO ZERO' + 'IMSLINEAR PRECONDITIONER_LEVELS must be greater than', & + 'or equal to zero' call store_error(errmsg) end if case ('PRECONDITIONER_DROP_TOLERANCE') @@ -299,7 +299,7 @@ SUBROUTINE imslinear_ar(this, NAME, parser, IOUT, IPRIMS, MXITER, IFDPARAM, & if (r < DZERO) then write (errmsg, '(a,1x,a)') & 'IMSLINEAR PRECONDITIONER_DROP_TOLERANCE', & - 'MUST BE GREATER THAN OR EQUAL TO ZERO' + 'must be greater than or equal to zero' call store_error(errmsg) end if ! @@ -318,14 +318,14 @@ SUBROUTINE imslinear_ar(this, NAME, parser, IOUT, IPRIMS, MXITER, IFDPARAM, & ! -- default case default write (errmsg, '(3a)') & - 'UNKNOWN IMSLINEAR KEYWORD (', trim(keyword), ').' + 'Unknown IMSLINEAR keyword (', trim(keyword), ').' call store_error(errmsg) end select end do write (iout, '(1x,a)') 'END OF LINEAR DATA' else if (IFDPARAM == 0) THEN - write (errmsg, '(a)') 'NO LINEAR BLOCK DETECTED.' + write (errmsg, '(a)') 'NO LINEAR block detected.' call store_error(errmsg) end if end if diff --git a/src/Solution/NumericalSolution.f90 b/src/Solution/NumericalSolution.f90 index 83682cfdc4c..1641c57b48e 100644 --- a/src/Solution/NumericalSolution.f90 +++ b/src/Solution/NumericalSolution.f90 @@ -584,7 +584,7 @@ subroutine sln_ar(this) this%iprims = 2 else write (errmsg, '(3a)') & - 'UNKNOWN IMS PRINT OPTION (', trim(keyword), ').' + 'Unknown IMS print option (', trim(keyword), ').' call store_error(errmsg) end if case ('COMPLEXITY') @@ -600,7 +600,7 @@ subroutine sln_ar(this) WRITE (IOUT, 25) else write (errmsg, '(3a)') & - 'UNKNOWN IMS COMPLEXITY OPTION (', trim(keyword), ').' + 'Unknown IMS COMPLEXITY option (', trim(keyword), ').' call store_error(errmsg) end if case ('CSV_OUTER_OUTPUT') @@ -612,8 +612,8 @@ subroutine sln_ar(this) filstat_opt='REPLACE') write (iout, fmtcsvout) trim(fname), this%icsvouterout else - write (errmsg, '(a)') 'OPTIONAL CSV_OUTER_OUTPUT '// & - 'KEYWORD MUST BE FOLLOWED BY FILEOUT' + write (errmsg, '(a)') 'Optional CSV_OUTER_OUTPUT '// & + 'keyword must be followed by FILEOUT' call store_error(errmsg) end if case ('CSV_INNER_OUTPUT') @@ -625,8 +625,8 @@ subroutine sln_ar(this) filstat_opt='REPLACE') write (iout, fmtcsvout) trim(fname), this%icsvinnerout else - write (errmsg, '(a)') 'OPTIONAL CSV_INNER_OUTPUT '// & - 'KEYWORD MUST BE FOLLOWED BY FILEOUT' + write (errmsg, '(a)') 'Optional CSV_INNER_OUTPUT '// & + 'keyword must be followed by FILEOUT' call store_error(errmsg) end if case ('NO_PTC') @@ -648,8 +648,8 @@ subroutine sln_ar(this) case ('ATS_OUTER_MAXIMUM_FRACTION') rval = this%parser%GetDouble() if (rval < DZERO .or. rval > DHALF) then - write (errmsg, '(a,G0)') 'VALUE FOR ATS_OUTER_MAXIMUM_FRAC MUST BE & - &BETWEEN 0 AND 0.5. FOUND ', rval + write (errmsg, '(a,G0)') 'Value for ATS_OUTER_MAXIMUM_FRAC must be & + &between 0 and 0.5. Found ', rval call store_error(errmsg) end if this%atsfrac = rval @@ -675,8 +675,8 @@ subroutine sln_ar(this) call deprecation_warning('OPTIONS', 'CSV_OUTPUT', '6.1.1', & warnmsg, this%parser%GetUnit()) else - write (errmsg, '(a)') 'OPTIONAL CSV_OUTPUT '// & - 'KEYWORD MUST BE FOLLOWED BY FILEOUT' + write (errmsg, '(a)') 'Optional CSV_OUTPUT '// & + 'keyword must be followed by FILEOUT' call store_error(errmsg) end if ! @@ -700,7 +700,7 @@ subroutine sln_ar(this) write (iout, fmtptcout) trim(fname), this%iptcout else write (errmsg, '(a)') & - 'OPTIONAL PTC_OUTPUT KEYWORD MUST BE FOLLOWED BY FILEOUT' + 'Optional PTC_OUTPUT keyword must be followed by FILEOUT' call store_error(errmsg) end if case ('DEV_PTC_OPTION') @@ -714,7 +714,7 @@ subroutine sln_ar(this) call this%parser%DevOpt() rval = this%parser%GetDouble() if (rval < DZERO) then - write (errmsg, '(a)') 'PTC_EXPONENT MUST BE > 0.' + write (errmsg, '(a)') 'PTC_EXPONENT must be > 0.' call store_error(errmsg) else this%iallowptc = 1 @@ -726,7 +726,7 @@ subroutine sln_ar(this) call this%parser%DevOpt() rval = this%parser%GetDouble() if (rval < DZERO) then - write (errmsg, '(a)') 'IMS sln_ar: PTC_DEL0 MUST BE > 0.' + write (errmsg, '(a)') 'IMS sln_ar: PTC_DEL0 must be > 0.' call store_error(errmsg) else this%iallowptc = 1 @@ -736,7 +736,7 @@ subroutine sln_ar(this) end if case default write (errmsg, '(a,2(1x,a))') & - 'UNKNOWN IMS OPTION (', trim(keyword), ').' + 'Unknown IMS option (', trim(keyword), ').' call store_error(errmsg) end select end do @@ -786,7 +786,7 @@ subroutine sln_ar(this) ival = 3 else write (errmsg, '(3a)') & - 'UNKNOWN UNDER_RELAXATION SPECIFIED (', trim(keyword), ').' + 'Unknown UNDER_RELAXATION specified (', trim(keyword), ').' call store_error(errmsg) end if this%nonmeth = ival @@ -798,7 +798,7 @@ subroutine sln_ar(this) ival = 1 else write (errmsg, '(3a)') & - 'UNKNOWN LINEAR_SOLVER SPECIFIED (', trim(keyword), ').' + 'Unknown LINEAR_SOLVER specified (', trim(keyword), ').' call store_error(errmsg) end if this%linmeth = ival @@ -842,14 +842,14 @@ subroutine sln_ar(this) warnmsg, this%parser%GetUnit()) case default write (errmsg, '(3a)') & - 'UNKNOWN IMS NONLINEAR KEYWORD (', trim(keyword), ').' + 'Unknown IMS NONLINEAR keyword (', trim(keyword), ').' call store_error(errmsg) end select end do write (iout, '(1x,a)') 'END OF IMS NONLINEAR DATA' else if (IFDPARAM .EQ. 0) then - write (errmsg, '(a)') 'NO IMS NONLINEAR BLOCK DETECTED.' + write (errmsg, '(a)') 'NO IMS NONLINEAR block detected.' call store_error(errmsg) end if end if @@ -865,7 +865,7 @@ subroutine sln_ar(this) ! ! -- check that MXITER is greater than zero if (this%mxiter <= 0) then - write (errmsg, '(a)') 'OUTER ITERATION NUMBER MUST BE > 0.' + write (errmsg, '(a)') 'Outer iteration number must be > 0.' call store_error(errmsg) END IF ! @@ -878,7 +878,7 @@ subroutine sln_ar(this) WRITE (IOUT, *) else WRITE (errmsg, '(a)') & - 'INCORRECT VALUE FOR VARIABLE NONMETH WAS SPECIFIED.' + 'Incorrect value for variable NONMETH was specified.' call store_error(errmsg) end if ! @@ -917,7 +917,7 @@ subroutine sln_ar(this) this%isymmetric = 0 ELSE WRITE (errmsg, '(a)') & - 'INCORRECT VALUE FOR LINEAR SOLUTION METHOD SPECIFIED.' + 'Incorrect value for linear solution method specified.' call store_error(errmsg) END IF diff --git a/src/Timing/ats.f90 b/src/Timing/ats.f90 index 30f44f3c548..73133d3f220 100644 --- a/src/Timing/ats.f90 +++ b/src/Timing/ats.f90 @@ -220,7 +220,7 @@ subroutine ats_read_options() call parser%GetStringCaps(keyword) select case (keyword) case default - write (errmsg, '(4x,a,a)') '****ERROR. UNKNOWN ATS OPTION: ', & + write (errmsg, '(a,a)') 'Unknown ATS option: ', & trim(keyword) call store_error(errmsg) call parser%StoreErrorUnit() @@ -264,7 +264,7 @@ subroutine ats_read_dimensions() maxats = parser%GetInteger() write (iout, fmtmaxats) maxats case default - write (errmsg, '(4x,a,a)') '****ERROR. UNKNOWN ATS DIMENSION: ', & + write (errmsg, '(a,a)') 'Unknown ATS dimension: ', & trim(keyword) call store_error(errmsg) call parser%StoreErrorUnit() @@ -272,7 +272,7 @@ subroutine ats_read_dimensions() end do write (iout, '(1x,a)') 'END OF ATS DIMENSIONS' else - write (errmsg, '(1x,a)') 'ERROR. REQUIRED DIMENSIONS BLOCK NOT FOUND.' + write (errmsg, '(a)') 'Required DIMENSIONS block not found.' call store_error(errmsg) call parser%StoreErrorUnit() end if @@ -324,7 +324,7 @@ subroutine ats_read_timing() end if write (iout, '(1x,a)') 'END READING ATS PERIODDATA' else - write (errmsg, '(1x,a)') 'ERROR. REQUIRED PERIODDATA BLOCK NOT FOUND.' + write (errmsg, '(a)') 'Required PERIODDATA block not found.' call store_error(errmsg) call parser%StoreErrorUnit() end if @@ -414,62 +414,62 @@ subroutine ats_check_timing() ! -- check iperats if (iperats(n) < 1) then write (errmsg, '(a, i0, a, i0)') & - 'IPERATS MUST BE GREATER THAN ZERO. FOUND ', iperats(n), & - ' FOR ATS PERIODDATA RECORD ', n + 'IPERATS must be greater than zero. Found ', iperats(n), & + ' for ATS PERIODDATA record ', n call store_error(errmsg) end if if (iperats(n) > nper) then write (warnmsg, '(a, i0, a, i0)') & - 'IPERATS GREATER THAN NPER. FOUND ', iperats(n), & - ' FOR ATS PERIODDATA RECORD ', n + 'IPERATS greater than NPER. Found ', iperats(n), & + ' for ATS PERIODDATA record ', n call store_warning(warnmsg) end if ! ! -- check dt0 if (dt0(n) < DZERO) then write (errmsg, '(a, g15.7, a, i0)') & - 'DT0 MUST BE >= ZERO. FOUND ', dt0(n), & - ' FOR ATS PERIODDATA RECORD ', n + 'DT0 must be >= zero. Found ', dt0(n), & + ' for ATS PERIODDATA record ', n call store_error(errmsg) end if ! ! -- check dtmin if (dtmin(n) <= DZERO) then write (errmsg, '(a, g15.7, a, i0)') & - 'DTMIN MUST BE > ZERO. FOUND ', dtmin(n), & - ' FOR ATS PERIODDATA RECORD ', n + 'DTMIN must be > zero. Found ', dtmin(n), & + ' for ATS PERIODDATA record ', n call store_error(errmsg) end if ! ! -- check dtmax if (dtmax(n) <= DZERO) then write (errmsg, '(a, g15.7, a, i0)') & - 'DTMAX MUST BE > ZERO. FOUND ', dtmax(n), & - ' FOR ATS PERIODDATA RECORD ', n + 'DTMAX must be > zero. Found ', dtmax(n), & + ' for ATS PERIODDATA record ', n call store_error(errmsg) end if ! ! -- check dtmin <= dtmax if (dtmin(n) > dtmax(n)) then write (errmsg, '(a, 2g15.7, a, i0)') & - 'DTMIN MUST BE < DTMAX. FOUND ', dtmin(n), dtmax(n), & - ' FOR ATS PERIODDATA RECORD ', n + 'DTMIN must be < dtmax. Found ', dtmin(n), dtmax(n), & + ' for ATS PERIODDATA record ', n call store_error(errmsg) end if ! ! -- check dtadj if (dtadj(n) .ne. DZERO .and. dtadj(n) < DONE) then write (errmsg, '(a, g15.7, a, i0)') & - 'DTADJ MUST BE 0 or >= 1.0. FOUND ', dtadj(n), & - ' FOR ATS PERIODDATA RECORD ', n + 'DTADJ must be 0 or >= 1.0. Found ', dtadj(n), & + ' for ATS PERIODDATA record ', n call store_error(errmsg) end if ! ! -- check dtfailadj if (dtfailadj(n) .ne. DZERO .and. dtfailadj(n) < DONE) then write (errmsg, '(a, g15.7, a, i0)') & - 'DTFAILADJ MUST BE 0 or >= 1.0. FOUND ', dtfailadj(n), & - ' FOR ATS PERIODDATA RECORD ', n + 'DTFAILADJ must be 0 or >= 1.0. Found ', dtfailadj(n), & + ' for ATS PERIODDATA record ', n call store_error(errmsg) end if diff --git a/src/Timing/tdis.f90 b/src/Timing/tdis.f90 index 2cd72aa9b4b..b1dee473e7a 100644 --- a/src/Timing/tdis.f90 +++ b/src/Timing/tdis.f90 @@ -560,7 +560,7 @@ subroutine tdis_read_options() itmuni = 5 write (iout, fmtitmuni) 'YEARS' case default - write (errmsg, '(4x,a,a)') '****ERROR. UNKNOWN TIME_UNITS: ', & + write (errmsg, '(a,a)') 'Unknown TIME_UNITS: ', & trim(keyword) call store_error(errmsg) call parser%StoreErrorUnit() @@ -579,7 +579,7 @@ subroutine tdis_read_options() inats = GetUnit() call openfile(inats, iout, fname, 'ATS') case default - write (errmsg, '(4x,a,a)') '****ERROR. UNKNOWN TDIS OPTION: ', & + write (errmsg, '(a,a)') 'Unknown TDIS option: ', & trim(keyword) call store_error(errmsg) call parser%StoreErrorUnit() @@ -712,7 +712,7 @@ subroutine tdis_read_dimensions() nper = parser%GetInteger() write (iout, fmtnper) nper case default - write (errmsg, '(4x,a,a)') '****ERROR. UNKNOWN TDIS DIMENSION: ', & + write (errmsg, '(a,a)') 'Unknown TDIS dimension: ', & trim(keyword) call store_error(errmsg) call parser%StoreErrorUnit() @@ -720,7 +720,7 @@ subroutine tdis_read_dimensions() end do write (iout, '(1x,a)') 'END OF TDIS DIMENSIONS' else - write (errmsg, '(1x,a)') 'ERROR. REQUIRED DIMENSIONS BLOCK NOT FOUND.' + write (errmsg, '(a)') 'Required DIMENSIONS block not found.' call store_error(errmsg) call parser%StoreErrorUnit() end if @@ -779,7 +779,7 @@ subroutine tdis_read_timing() end if write (iout, '(1x,a)') 'END OF TDIS PERIODDATA' else - write (errmsg, '(1x,a)') 'ERROR. REQUIRED PERIODDATA BLOCK NOT FOUND.' + write (errmsg, '(a)') 'Required PERIODDATA block not found.' call store_error(errmsg) call parser%StoreErrorUnit() end if @@ -812,13 +812,13 @@ subroutine check_tdis_timing(nper, perlen, nstp, tsmult) character(len=LINELENGTH) :: errmsg ! -- formats character(len=*), parameter :: fmtpwarn = & - "(1X,/1X,'PERLEN IS ZERO FOR STRESS PERIOD ', I0, & - &'. PERLEN MUST NOT BE ZERO FOR TRANSIENT PERIODS.')" + "(1X,/1X,'PERLEN is zero for stress period ', I0, & + &'. PERLEN must not be zero for transient periods.')" character(len=*), parameter :: fmtsperror = & - &"(A,' FOR STRESS PERIOD ', I0)" + &"(A,' for stress period ', I0)" character(len=*), parameter :: fmtdterror = & - "('TIME STEP LENGTH OF ', G0, ' IS TOO SMALL IN PERIOD ', I0, & - &' AND TIME STEP ', I0)" + "('Time step length of ', G0, ' is too small in period ', I0, & + &' and time step ', I0)" ! ------------------------------------------------------------------------------ ! ! -- Initialize @@ -829,7 +829,7 @@ subroutine check_tdis_timing(nper, perlen, nstp, tsmult) ! ! -- Error if nstp less than or equal to zero if (nstp(kper) <= 0) then - write (errmsg, fmtsperror) 'NUMBER OF TIME STEPS LESS THAN ONE ', kper + write (errmsg, fmtsperror) 'Number of time steps less than one ', kper call store_error(errmsg) return end if @@ -842,14 +842,14 @@ subroutine check_tdis_timing(nper, perlen, nstp, tsmult) ! ! -- Error if tsmult is less than zero if (tsmult(kper) <= DZERO) then - write (errmsg, fmtsperror) 'TSMULT MUST BE GREATER THAN 0.0 ', kper + write (errmsg, fmtsperror) 'TSMULT must be greater than 0.0 ', kper call store_error(errmsg) return end if ! ! -- Error if negative period length if (perlen(kper) < DZERO) then - write (errmsg, fmtsperror) 'PERLEN CANNOT BE LESS THAN 0.0 ', kper + write (errmsg, fmtsperror) 'PERLEN cannot be less than 0.0 ', kper call store_error(errmsg) return end if diff --git a/src/Utilities/Idm/DefinitionSelect.f90 b/src/Utilities/Idm/DefinitionSelect.f90 index 088d75edd40..656312e9cf8 100644 --- a/src/Utilities/Idm/DefinitionSelect.f90 +++ b/src/Utilities/Idm/DefinitionSelect.f90 @@ -51,7 +51,7 @@ function get_param_definition_type(input_definition_types, & end do ! if (.not. associated(idt)) then - write (errmsg, '(1x,a,a,a,a,a)') & + write (errmsg, '(a,a,a,a,a)') & 'Input file tag not found: "', trim(tagname), & '" in block "', trim(blockname), & '".' @@ -88,7 +88,7 @@ function get_aggregate_definition_type(input_definition_types, component_type, & end do ! if (.not. associated(idt)) then - write (errmsg, '(1x,a,a,a,a,a,a,a)') & + write (errmsg, '(a,a,a,a,a,a,a)') & 'Idm aggregate definition not found: ', trim(blockname), & '. Component="', trim(component_type), & '", subcomponent="', trim(subcomponent_type), '".' diff --git a/src/Utilities/Idm/mf6blockfile/LoadMf6File.f90 b/src/Utilities/Idm/mf6blockfile/LoadMf6File.f90 index 91be0a0c4e3..41c7d84b622 100644 --- a/src/Utilities/Idm/mf6blockfile/LoadMf6File.f90 +++ b/src/Utilities/Idm/mf6blockfile/LoadMf6File.f90 @@ -311,13 +311,14 @@ recursive subroutine parse_tag(parser, mf6_input, iblock, mshape, filename, & case ('DOUBLE3D') call load_double3d_type(parser, idt, mf6_input%mempath, mshape, iout) case default - write (errmsg, '(4x,a,a)') 'Failure reading data for tag: ', trim(tag) + write (errmsg, '(a,a)') 'Failure reading data for tag: ', trim(tag) call store_error(errmsg) call parser%StoreErrorUnit() end select ! ! -- continue line if in same record if (idt%in_record) then + ! recursively call parse tag again to read rest of line call parse_tag(parser, mf6_input, iblock, mshape, filename, iout, .true.) end if diff --git a/src/Utilities/ListReader.f90 b/src/Utilities/ListReader.f90 index 7ae360a1f3b..e4fd7d4092f 100644 --- a/src/Utilities/ListReader.f90 +++ b/src/Utilities/ListReader.f90 @@ -407,10 +407,10 @@ subroutine read_ascii(this) character(len=LINELENGTH) :: fname ! -- formats character(len=*), parameter :: fmtmxlsterronly = & - "('***ERROR READING LIST. & - &THE NUMBER OF RECORDS ENCOUNTERED EXCEEDS THE MAXIMUM NUMBER "// & - "OF RECORDS. TRY INCREASING MAXBOUND FOR THIS LIST."// & - " NUMBER OF RECORDS: ',I0,' MAXBOUND: ',I0)" + "('Error reading list. The number of records encountered exceeds & + &the maximum number of records. Number of records found is ',I0,& + &' but MAXBOUND is ', I0, '. Try increasing MAXBOUND for this list. & + &Error occurred reading the following line: ', a, 5x, '>>> ', a)" ! ------------------------------------------------------------------------------ ! ! -- determine array sizes @@ -449,9 +449,8 @@ subroutine read_ascii(this) ! -- Check range if (ii > mxlist) then inquire (unit=this%inlist, name=fname) - write (errmsg, fmtmxlsterronly) ii, mxlist - call store_error(errmsg) - errmsg = 'Error occurred reading line: '//trim(this%line) + write (errmsg, fmtmxlsterronly) & + ii, mxlist, new_line("A"), trim(this%line) call store_error(errmsg) call store_error_unit(this%inlist) end if diff --git a/src/Utilities/Sim.f90 b/src/Utilities/Sim.f90 index 2f19b90e0fa..298f0c544a5 100644 --- a/src/Utilities/Sim.f90 +++ b/src/Utilities/Sim.f90 @@ -224,7 +224,7 @@ subroutine store_error_unit(iunit, terminate) ! -- store error unit inquire (unit=iunit, name=fname) write (errmsg, '(3a)') & - "ERROR OCCURRED WHILE READING FILE '", trim(adjustl(fname)), "'" + "Error occurred while reading file '", trim(adjustl(fname)), "'" call sim_uniterrors%store_message(errmsg) ! ! -- terminate the simulation diff --git a/src/Utilities/sort.f90 b/src/Utilities/sort.f90 index 4cc637a3511..bdb5422b17e 100644 --- a/src/Utilities/sort.f90 +++ b/src/Utilities/sort.f90 @@ -122,7 +122,7 @@ subroutine qsort_int1d(indx, v, reverse) indx(j) = ia jstack = jstack + 2 if (jstack > nstack) then - write (errmsg, '(4x,a,3(1x,a))') & + write (errmsg, '(a,3(1x,a))') & 'JSTACK > NSTACK IN SortModule::qsort' call store_error(errmsg, terminate=.TRUE.) end if @@ -257,7 +257,7 @@ subroutine qsort_dbl1d(indx, v, reverse) indx(j) = ia jstack = jstack + 2 if (jstack > nstack) then - write (errmsg, '(4x,a,3(1x,a))') & + write (errmsg, '(a,3(1x,a))') & 'JSTACK > NSTACK IN SortModule::qsort' call store_error(errmsg, terminate=.TRUE.) end if From 753fdf78e550a6496f4f5f598d752f69f8cc94ae Mon Sep 17 00:00:00 2001 From: w-bonelli Date: Fri, 23 Jun 2023 18:56:43 -0400 Subject: [PATCH 116/123] ci(release): make linux version configurable (#1271) --- .github/workflows/release.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 50ce55846f8..a0a154946bc 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -23,6 +23,11 @@ on: required: false type: boolean default: false + linux_version: + description: 'Linux runner image to build binaries on.' + required: false + type: string + default: 'ubuntu-20.04' run_tests: description: Run tests after building binaries.' required: false @@ -49,7 +54,7 @@ jobs: fail-fast: false matrix: include: - - os: ubuntu-22.04 + - os: ${{ inputs.linux_version }} ostag: linux - os: macos-12 ostag: mac From cfde81b2f62b93f1452e118d966da6ed0a4ffcb2 Mon Sep 17 00:00:00 2001 From: jdhughes-usgs Date: Sat, 24 Jun 2023 09:09:53 -0500 Subject: [PATCH 117/123] fix(ListReader): fix issue with reading binary stress period data containing AUX variables (#1270) - Added autotest to confirm that binary stress period data with auxiliary data can be run. - Updated the release notes. --- autotest/test_gwf_chd02.py | 104 +++++++++++++++++++++++++++++++++++ doc/ReleaseNotes/v6.4.2.tex | 1 + src/Utilities/ListReader.f90 | 2 +- 3 files changed, 106 insertions(+), 1 deletion(-) create mode 100644 autotest/test_gwf_chd02.py diff --git a/autotest/test_gwf_chd02.py b/autotest/test_gwf_chd02.py new file mode 100644 index 00000000000..019a00917d4 --- /dev/null +++ b/autotest/test_gwf_chd02.py @@ -0,0 +1,104 @@ +# test use of binary stress period data with auxilliary data +import os + +import flopy +import numpy as np +import pytest +from framework import TestFramework +from simulation import TestSimulation + +ex = [ + "chd02", +] + + +def build_model(idx, workspace): + name = ex[idx] + nlay, nrow, ncol = 1, 1, 10 + sim = flopy.mf6.MFSimulation(sim_ws=workspace, sim_name=name) + flopy.mf6.ModflowTdis(sim) + flopy.mf6.ModflowIms(sim, complexity="simple") + gwf = flopy.mf6.ModflowGwf(sim, modelname=name, print_input=True) + flopy.mf6.ModflowGwfdis( + gwf, + nlay=nlay, + nrow=nrow, + ncol=ncol, + delr=1.0, + delc=1.0, + top=10.0, + botm=0.0, + ) + flopy.mf6.ModflowGwfnpf( + gwf, + icelltype=1, + ) + flopy.mf6.ModflowGwfic( + gwf, + strt=10.0, + ) + chd_data = [ + (0, 0, 0, 10.0, 1.0, 100.), + (0, 0, ncol - 1, 5.0, 0.0, 100.), + ] + chd_data = { + 0: { + "filename": "chd.bin", + "binary": True, + "data": chd_data, + }, + } + flopy.mf6.ModflowGwfchd( + gwf, + auxiliary=["conc", "something"], + stress_period_data=chd_data, + ) + flopy.mf6.ModflowGwfoc( + gwf, + head_filerecord=f"{name}.hds", + saverecord=[("HEAD", "LAST")], + printrecord=[("HEAD", "LAST"), ("BUDGET", "LAST")], + ) + + return sim, None + + +def eval_model(sim): + print("evaluating model...") + + name = sim.name + + fpth = os.path.join(sim.simpath, f"{name}.hds") + hobj = flopy.utils.HeadFile(fpth, precision="double") + head = hobj.get_data().flatten() + + # This is the answer to this problem. + hres = np.array( + [ + 10.00, + 9.57481441, + 9.1298034, + 8.66189866, + 8.16714512, + 7.64029169, + 7.07409994, + 6.45808724, + 5.77600498, + 5.000, + ] + ) + assert np.allclose( + hres, head + ), "simulated head does not match with known solution." + + +@pytest.mark.parametrize("name", ex) +def test_mf6model(name, function_tmpdir, targets): + test = TestFramework() + test.build(build_model, 0, str(function_tmpdir)) + test.run( + TestSimulation( + name=name, exe_dict=targets, exfunc=eval_model, idxsim=0 + ), + str(function_tmpdir), + ) diff --git a/doc/ReleaseNotes/v6.4.2.tex b/doc/ReleaseNotes/v6.4.2.tex index 6a72217ce98..cede8448342 100644 --- a/doc/ReleaseNotes/v6.4.2.tex +++ b/doc/ReleaseNotes/v6.4.2.tex @@ -29,6 +29,7 @@ \item The SSM Package for the GWT Model did not work properly with Stress Package Concentration (SPC) input with the READARRAY option for transient models. Under these conditions, the program would prematurely terminate looking for the next BEGIN PERIOD block. The program was corrected so that SPC input can be read for transient conditions. \item For some Linux systems, observations were not being correctly written to formatted observation output files when the source code was compiled with the Intel IFORT 19.1.0.166 20191121 compiler. This issue has been addressed by adding a flush statement to ObsUtilityModule::write\_unfmtd\_obs after writing each observation for a time step. This change will not affect simulated observations and should not affect simulation run times. \item The wetted area written to the binary LAK package output was modified to be zero when the lake stage is below the bottom of a connected groundwater cell. The code uses the lak\_calculate\_conn\_warea() function to determine the wetted area, which makes sense for calculating the flow conductance. + \item Fix issue with reading boundary package stress period data from binary files when auxiliary data is included. \end{itemize} \underline{INTERNAL FLOW PACKAGES} diff --git a/src/Utilities/ListReader.f90 b/src/Utilities/ListReader.f90 index e4fd7d4092f..99177280d38 100644 --- a/src/Utilities/ListReader.f90 +++ b/src/Utilities/ListReader.f90 @@ -344,7 +344,7 @@ subroutine read_binary(this) ! -- Read remainder of record read (this%inlist, iostat=this%ierr) (this%rlist(jj, ii), jj=1, ldim), & - (this%auxvar(ii, jj), jj=1, naux) + (this%auxvar(jj, ii), jj=1, naux) if (this%ierr /= 0) then inquire (unit=this%inlist, name=fname) write (errmsg, fmtlsterronly) trim(adjustl(fname)), this%inlist From 83c5e215240b04fd0189c3dab5a3c8a7b591a204 Mon Sep 17 00:00:00 2001 From: langevin-usgs Date: Mon, 26 Jun 2023 12:40:48 -0500 Subject: [PATCH 118/123] fix(release): updates for 6.4.2 release (#1272) * fix(deprecation message): deprecation message changed from 6.5.0 to 6.4.2 * deprecation message referred to wrong version * * correct typos in GWT MST error message * improve release note wording in response to Richard Winston suggestions * Correct several references to 6.5.0 to 6.4.2 * update some version information --- README.md | 2 +- code.json | 6 +++--- doc/ReleaseNotes/ReleaseNotes.tex | 2 +- doc/ReleaseNotes/v6.4.2.tex | 2 +- doc/SuppTechInfo/Tables/transport_params_revised.tex | 2 +- doc/SuppTechInfo/sorption.tex | 2 +- doc/mf6io/mf6ivar/dfn/gwf-sfr.dfn | 2 +- doc/version.tex | 4 ++-- src/Model/GroundWaterFlow/gwf3sfr8.f90 | 2 +- src/Model/GroundWaterTransport/gwt1ist1.f90 | 4 ++-- src/Model/GroundWaterTransport/gwt1mst1.f90 | 4 ++-- src/Utilities/version.f90 | 2 +- 12 files changed, 17 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index b35ab693c22..e048a5c11d2 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ This is the development repository for the USGS MODFLOW 6 Hydrologic Model. The official USGS distribution is available at [USGS Release Page](https://water.usgs.gov/ogw/modflow/MODFLOW.html). -### Version 6.5.0 Release Candidate +### Version 6.4.2 (preliminary) [![GitHub release](https://img.shields.io/github/release/MODFLOW-USGS/modflow6.svg)](https://github.com/MODFLOW-USGS/modflow6/releases/latest) [![MODFLOW 6 continuous integration](https://github.com/MODFLOW-USGS/modflow6/actions/workflows/ci.yml/badge.svg)](https://github.com/MODFLOW-USGS/modflow6/actions/workflows/ci.yml) diff --git a/code.json b/code.json index 54ba297ac4d..5e7d5fa9e38 100644 --- a/code.json +++ b/code.json @@ -1,6 +1,6 @@ [ { - "status": "Release Candidate", + "status": "Preliminary", "languages": [ "Fortran2008" ], @@ -18,9 +18,9 @@ "email": "langevin@usgs.gov" }, "laborHours": -1, - "version": "6.5.0", + "version": "6.4.2", "date": { - "metadataLastUpdated": "2022-12-09" + "metadataLastUpdated": "2023-06-26" }, "organization": "U.S. Geological Survey", "permissions": { diff --git a/doc/ReleaseNotes/ReleaseNotes.tex b/doc/ReleaseNotes/ReleaseNotes.tex index c43f37ec1c0..26fbf93e8d8 100644 --- a/doc/ReleaseNotes/ReleaseNotes.tex +++ b/doc/ReleaseNotes/ReleaseNotes.tex @@ -251,7 +251,7 @@ \section{Installation and Execution} % ------------------------------------------------- \section{Compiling MODFLOW~6} -MODFLOW~6 has been compiled using Intel Fortran and GNU Fortran on the Windows, macOS, and Linux operating systems. Because the program uses relatively new Fortran extensions, recent versions of the compilers may be required for successful compilation. MODFLOW~6 is currently tested with gfortran 7-12 on Linux and gfortran 12 on macOS and Windows. If you have gfortran installed on your computer, you can tell which version it is by entering ``\verb|gfortran --version|'' at a terminal window. MODFLOW~6 is currently not compatible with the next-generation Intel Fortran Compiler; a recent version of the Intel Fortran Compiler Classic (e.g. 2021.8.0) must be used. +MODFLOW~6 has been compiled using Intel Fortran and GNU Fortran on the Windows, macOS, and Linux operating systems. Because the program uses relatively new Fortran functionality, recent versions of the compilers may be required for successful compilation. MODFLOW~6 is currently tested with gfortran 7-12 on Linux and gfortran 12 on macOS and Windows. If you have gfortran installed on your computer, you can tell which version it is by entering ``\verb|gfortran --version|'' at a terminal window. MODFLOW~6 is currently not compatible with the next-generation Intel Fortran Compiler; a recent version of the Intel Fortran Compiler Classic (e.g. 2021.8.0) must be used. Meson is the recommended build tool for MODFLOW~6. For more detailed compilation instructions, please refer to \url{https://github.com/MODFLOW-USGS/modflow6/blob/develop/DEVELOPER.md#building}. diff --git a/doc/ReleaseNotes/v6.4.2.tex b/doc/ReleaseNotes/v6.4.2.tex index cede8448342..8d718485fc1 100644 --- a/doc/ReleaseNotes/v6.4.2.tex +++ b/doc/ReleaseNotes/v6.4.2.tex @@ -9,7 +9,7 @@ \item The source code was refactored to support compilation of a parallel version of MODFLOW 6 based on the Message Passing Interface (MPI) and the Portable, Extensible Toolkit for Scientific Computation (PETSc) libraries. The parallel version of MODFLOW is considered preliminary (alpha release). Limited testing of the parallel version has been performed on laptops, desktops, and supercomputers, but significant changes are expected in future releases. User support for the parallel version of MODFLOW 6 may be provided in the future. \item The Groundwater Transport (GWT) model was modified for simulations involving a mobile domain and one or more immobile domains. The modifications do not affect GWT Models without the Immobile Storage and Transfer (IST) Package. The original IST Package formulation described by \cite{modflow6gwt} was based on a limiting assumption about how the mobile and immobile domains are apportioned within a model cell. The changes introduced here require the user to explicitly specify in the IST Package the volume fraction of each cell that is immobile. This change also redefines the meaning of several input parameters. As described in a new chapter (Chapter 9) in the Supplemental Technical Information document, porosity and bulk density values must now be entered per domain volume rather than per cell volume. Consequently, for simulations that include one or more IST Packages, these changes are not backward compatible, and will require updates to IST and MST input. Suggestions for updating existing parameter values is included in Chapter 9 of the Supplemental Technical Information document, which is included with the distribution. \item Add LENGTH\_CONVERSION and TIME\_CONVERSION variables to replace the UNIT\_CONVERSION variable in the SFR Package input file. The LENGTH\_CONVERSION and TIME\_CONVERSION variables are used to convert user-specified Manning's roughness coefficients from SI units (sec/m$^{1/3}$) to model length and time units. LENGTH\_CONVERSION does not need to be specified if LENGTH\_UNITS are meters. TIME\_CONVERSION does not need to be specified if TIME\_UNITS are seconds. Warning messages will be issued if UNIT\_CONVERSION variable is specified. The model will terminate with an error if UNIT\_CONVERSION and LENGTH\_CONVERSION and TIME\_CONVERSION variables are specified. The UNIT\_CONVERSION variable in the SFR Package input file will eventually be deprecated. - \item Add MAXIMUM\_ITERATIONS and MAXIMUM\_STAGE\_CHANGE variables in the LAK Package input file. The MAXIMUM\_ITERATIONS variable is used to change the maximum number of iterations and would only need to be increased from the default value if one or more lakes in a simulation has a large water budget error. The MAXIMUM\_STAGE\_CHANGE variable defines the stage closure tolerance for each lake. The MAXIMUM\_STAGE\_CHANGE variable would only need to be increased or decreased from the default value if the water budget error for one or more lakes is too small or too large, respectively. + \item Add MAXIMUM\_ITERATIONS and MAXIMUM\_STAGE\_CHANGE variables in the LAK Package input file. The MAXIMUM\_ITERATIONS variable is used to change the maximum number of iterations and would only need to be increased from the default value if one or more lakes in a simulation has a large water budget error. The MAXIMUM\_STAGE\_CHANGE variable defines the stage closure tolerance for each lake. The MAXIMUM\_STAGE\_CHANGE variable would only need to be increased or decreased from the default value if the water budget error for one or more lakes is too small or too large. \item The Input Data Processor (IDP) is introduced in this release to read user-provided input files and store user-provided input data in memory for subsequent use by simulation, model, and package components. Components that have been integrated with IDP no longer handle input files directly but rather retrieve all input data from named locations, called memory paths, allocated in managed memory. The collection of all simulation input data in managed memory is called the input context. IDP uses existing descriptions of input variables, called variable definitions, to interpret and store input. The program variable definition set and its representation in the input context is described as the Input Data Model (IDM). Input variables can be recognized in a memory dump (e.g., with the MEMORY\_PRINT\_OPTION activated in the options block of mfsim.nam) by their memory path prefix string "\_\_INPUT\_\_". Components that later access input typically copy data from the input context to their own memory space; therefore, the present implemention of the IDP results in an increased memory footprint for the program. Among its advantages include the consolidation of all input processing early in program runtime, and outside of any particular component, enabling the support of alternative types of input data sources. Input file types that are currently processed by IDP include DIS6, DISU6, DIV6, NPF6, DSP6, and Name File inputs for the Simulation (mfsim.nam) and GWF and GWT models. % \item xxx \end{itemize} diff --git a/doc/SuppTechInfo/Tables/transport_params_revised.tex b/doc/SuppTechInfo/Tables/transport_params_revised.tex index b90fda88a26..30822292377 100644 --- a/doc/SuppTechInfo/Tables/transport_params_revised.tex +++ b/doc/SuppTechInfo/Tables/transport_params_revised.tex @@ -1,7 +1,7 @@ \begin{table}[!ht] \small \centering - \caption{Symbols, descriptions, and definitions of revised mobile and immobile domain model parameters introduced in \mf version 6.5.0. In the revised parameterization, division of the aquifer into domains is conceptualized in terms of volume fractions, and domain properties are defined on a per-domain-volume basis} \tabularnewline + \caption{Symbols, descriptions, and definitions of revised mobile and immobile domain model parameters introduced in \mf version 6.4.2. In the revised parameterization, division of the aquifer into domains is conceptualized in terms of volume fractions, and domain properties are defined on a per-domain-volume basis} \tabularnewline \begin{tabular}{z{1.50cm} z{3.50cm} diff --git a/doc/SuppTechInfo/sorption.tex b/doc/SuppTechInfo/sorption.tex index 8c0dc993295..41f17747b14 100644 --- a/doc/SuppTechInfo/sorption.tex +++ b/doc/SuppTechInfo/sorption.tex @@ -1,7 +1,7 @@ The Groundwater Transport (GWT) Model \citep{modflow6gwt} in \mf can simulate a variety of solute-transport processes in aquifer material that includes ``mobile" and ``immobile" domains. The mobile and immobile domains are conceptualized as coexisting within a model cell and can exchange solute with each other. The mobile domain is always simulated by the GWT Model. Transport in an immobile domain, which may be optionally defined by the user, is simulated by the Immobile Storage and Transfer (IST) Package \citep{modflow6gwt} for the GWT Model. Multiple instances of the IST Package may be invoked to represent multiple immobile domains. -In \mf version 6.5.0, the parameterization of the equations that govern transport in the mobile and immobile domains has been revised, and corresponding changes have been made to the input requirements for porosity, domain fraction, and bulk density. The revised parameterization is expected to be more intutive for users in many mobile-immobile transport applications. It will also allow users to model a wider variety of solute transport scenarios involving immobile-domain sorption than the original parameterization used in versions of \mf up to and including version 6.4.1. +In \mf version 6.4.2, the parameterization of the equations that govern transport in the mobile and immobile domains has been revised, and corresponding changes have been made to the input requirements for porosity, domain fraction, and bulk density. The revised parameterization is expected to be more intutive for users in many mobile-immobile transport applications. It will also allow users to model a wider variety of solute transport scenarios involving immobile-domain sorption than the original parameterization used in versions of \mf up to and including version 6.4.1. Input for existing \mf models that include one or more immobile domains can be converted from the original parameterization to the revised parameterization such that the simulated transport behavior remains the same. No changes to the model input are needed for existing \mf models that do not include an immobile domain. diff --git a/doc/mf6io/mf6ivar/dfn/gwf-sfr.dfn b/doc/mf6io/mf6ivar/dfn/gwf-sfr.dfn index 1aa6a218a4c..5a23b919e45 100644 --- a/doc/mf6io/mf6ivar/dfn/gwf-sfr.dfn +++ b/doc/mf6io/mf6ivar/dfn/gwf-sfr.dfn @@ -306,7 +306,7 @@ name unit_conversion type double precision reader urword optional true -deprecated 6.5.0 +deprecated 6.4.2 longname conversion factor description real value that is used to convert user-specified Manning's roughness coefficients from seconds per meters$^{1/3}$ to model length and time units. A constant of 1.486 is used for flow units of cubic feet per second, and a constant of 1.0 is used for units of cubic meters per second. The constant must be multiplied by 86,400 when using time units of days in the simulation. diff --git a/doc/version.tex b/doc/version.tex index 61fea70fabc..3aebcf5655d 100644 --- a/doc/version.tex +++ b/doc/version.tex @@ -1,3 +1,3 @@ -\newcommand{\modflowversion}{mf6.4.2rc} -\newcommand{\modflowdate}{December 09, 2022} +\newcommand{\modflowversion}{mf6.4.2} +\newcommand{\modflowdate}{June 26, 2023} \newcommand{\currentmodflowversion}{Version \modflowversion---\modflowdate} diff --git a/src/Model/GroundWaterFlow/gwf3sfr8.f90 b/src/Model/GroundWaterFlow/gwf3sfr8.f90 index 7c7d4c6b20e..2a043350ec7 100644 --- a/src/Model/GroundWaterFlow/gwf3sfr8.f90 +++ b/src/Model/GroundWaterFlow/gwf3sfr8.f90 @@ -716,7 +716,7 @@ subroutine sfr_options(this, option, found) 'SETTING UNIT_CONVERSION DIRECTLY' ! ! -- create deprecation warning - call deprecation_warning('OPTIONS', 'UNIT_CONVERSION', '6.5.0', & + call deprecation_warning('OPTIONS', 'UNIT_CONVERSION', '6.4.2', & warnmsg, this%parser%GetUnit()) case ('LENGTH_CONVERSION') this%lengthconv = this%parser%GetDouble() diff --git a/src/Model/GroundWaterTransport/gwt1ist1.f90 b/src/Model/GroundWaterTransport/gwt1ist1.f90 index 875a89ddfd7..99ed8128e4d 100644 --- a/src/Model/GroundWaterTransport/gwt1ist1.f90 +++ b/src/Model/GroundWaterTransport/gwt1ist1.f90 @@ -1091,12 +1091,12 @@ subroutine read_data(this) end if else if (lname(3)) then - write (this%iout, '(1x,a)') 'Warning. First- or zero-orer decay & + write (this%iout, '(1x,a)') 'Warning. First- or zero-order decay & &is not active but DECAY was specified. DECAY will & &have no affect on simulation results.' end if if (lname(4)) then - write (this%iout, '(1x,a)') 'Warning. First- or zero-orer decay & + write (this%iout, '(1x,a)') 'Warning. First- or zero-order decay & &is not active but DECAY_SORBED was specified. & &DECAY_SORBED will have no affect on simulation & &results.' diff --git a/src/Model/GroundWaterTransport/gwt1mst1.f90 b/src/Model/GroundWaterTransport/gwt1mst1.f90 index 415053cc195..148f937d385 100644 --- a/src/Model/GroundWaterTransport/gwt1mst1.f90 +++ b/src/Model/GroundWaterTransport/gwt1mst1.f90 @@ -1381,14 +1381,14 @@ subroutine read_data(this) end if else if (lname(4)) then - write (warnmsg, '(a)') 'First or zero orer decay & + write (warnmsg, '(a)') 'First- or zero-order decay & &is not active but decay was specified. DECAY will & &have no affect on simulation results.' call store_warning(warnmsg) write (this%iout, '(1x,a)') 'WARNING. '//warnmsg end if if (lname(5)) then - write (warnmsg, '(a)') 'First or zero orer decay & + write (warnmsg, '(a)') 'First- or zero-order decay & &is not active but DECAY_SORBED was specified. & &DECAY_SORBED will have no affect on simulation results.' call store_warning(warnmsg) diff --git a/src/Utilities/version.f90 b/src/Utilities/version.f90 index 082fb43e53b..2fe6f7f7fb7 100644 --- a/src/Utilities/version.f90 +++ b/src/Utilities/version.f90 @@ -17,7 +17,7 @@ module VersionModule ! -- modflow 6 version integer(I4B), parameter :: IDEVELOPMODE = 1 character(len=*), parameter :: VERSIONNUMBER = '6.4.2' - character(len=*), parameter :: VERSIONTAG = ' Release Candidate 12/09/2022' + character(len=*), parameter :: VERSIONTAG = ' (preliminary) 06/26/2023' character(len=40), parameter :: VERSION = VERSIONNUMBER//VERSIONTAG character(len=2), parameter :: MFVNAM = ' 6' character(len=*), parameter :: MFTITLE = & From cf91c96ef065729f3bf3410dec82402613dc24f4 Mon Sep 17 00:00:00 2001 From: Eric Morway Date: Tue, 27 Jun 2023 08:10:21 -0700 Subject: [PATCH 119/123] fix(typos): various minutiae (#1273) --- doc/mf6io/gwf/mvr.tex | 2 +- doc/mf6io/gwt/gwt-gwt.tex | 2 +- doc/mf6io/mf6ivar/dfn/gwt-disv.dfn | 2 +- doc/mf6io/mf6ivar/dfn/gwt-mwt.dfn | 2 +- doc/mf6io/mf6ivar/dfn/gwt-ssm.dfn | 2 +- doc/mf6io/mf6ivar/examples/gwt-fmi-example.dat | 2 +- doc/mf6io/mf6ivar/examples/gwt-sft-example-obs.dat | 2 +- doc/mf6io/mf6ivar/readme.md | 2 +- src/Model/GroundWaterFlow/gwf3buy8.f90 | 4 ++-- src/Model/GroundWaterFlow/gwf3drn8.f90 | 4 +--- src/Model/GroundWaterTransport/gwt1mst1.f90 | 6 +++--- 11 files changed, 14 insertions(+), 16 deletions(-) diff --git a/doc/mf6io/gwf/mvr.tex b/doc/mf6io/gwf/mvr.tex index cb6e6d52c5c..7e2adac4213 100644 --- a/doc/mf6io/gwf/mvr.tex +++ b/doc/mf6io/gwf/mvr.tex @@ -68,7 +68,7 @@ \noindent In the UPTO case, all of the available water will be taken from the provider up to the $Q_S$ value specified by the user. Once $Q_S$ is exceeded, the receiver will continue to get the $Q_S$ value specified by the user. \end{enumerate} -\noindent In the MVR PERIOD block (as shown below), the user assigns the equation to used for each individual entry by specifying FACTOR, EXCESS, THRESHOLD, or UPTO to the input variable \texttt{mvrtype}. +\noindent In the MVR PERIOD block (as shown below), the user assigns the equation used for each individual entry by specifying FACTOR, EXCESS, THRESHOLD, or UPTO to the input variable \texttt{mvrtype}. Input to the Water Mover (MVR) Package is read from the file that has type ``MVR6'' in the Name File. Only one MVR Package can be used per GWF Model. diff --git a/doc/mf6io/gwt/gwt-gwt.tex b/doc/mf6io/gwt/gwt-gwt.tex index dff4b0a70f6..42b2e5131b2 100644 --- a/doc/mf6io/gwt/gwt-gwt.tex +++ b/doc/mf6io/gwt/gwt-gwt.tex @@ -1,4 +1,4 @@ -Input to the Groundwater Flow (GWT-GWT) Exchange is read from the file that has type ``GWT6-GWT6'' in the Simulation Name File. +Input to the Groundwater Transport (GWT-GWT) Exchange is read from the file that has type ``GWT6-GWT6'' in the Simulation Name File. The list of exchanges entered into the EXCHANGEDATA block must be identical to the list of exchanges entered for the GWF-GWF input file. One way to ensure that this information is identical is to put this list into an external file and refer to this same list using the OPEN/CLOSE functionality in both this EXCHANGEDATA input block and the EXCHANGEDATA input block in the GWF-GWF input file. diff --git a/doc/mf6io/mf6ivar/dfn/gwt-disv.dfn b/doc/mf6io/mf6ivar/dfn/gwt-disv.dfn index 98077265a72..376c0e2d532 100644 --- a/doc/mf6io/mf6ivar/dfn/gwt-disv.dfn +++ b/doc/mf6io/mf6ivar/dfn/gwt-disv.dfn @@ -1,4 +1,4 @@ -# --------------------- gwf disv options --------------------- +# --------------------- gwt disv options --------------------- block options name length_units diff --git a/doc/mf6io/mf6ivar/dfn/gwt-mwt.dfn b/doc/mf6io/mf6ivar/dfn/gwt-mwt.dfn index f4600d695e3..67529c4b8a9 100644 --- a/doc/mf6io/mf6ivar/dfn/gwt-mwt.dfn +++ b/doc/mf6io/mf6ivar/dfn/gwt-mwt.dfn @@ -352,7 +352,7 @@ tagged false in_record true reader urword longname -description line of information that is parsed into a keyword and values. Keyword values that can be used to start the MWTSETTING string include: STATUS, CONCENTRATION, RAINFALL, EVAPORATION, RUNOFF, and AUXILIARY. These settings are used to assign the concentration of associated with the corresponding flow terms. Concentrations cannot be specified for all flow terms. For example, the Multi-Aquifer Well Package supports a ``WITHDRAWAL'' flow term. If this withdrawal term is active, then water will be withdrawn from the well at the calculated concentration of the well. +description line of information that is parsed into a keyword and values. Keyword values that can be used to start the MWTSETTING string include: STATUS, CONCENTRATION, RAINFALL, EVAPORATION, RUNOFF, and AUXILIARY. These settings are used to assign the concentration associated with the corresponding flow terms. Concentrations cannot be specified for all flow terms. For example, the Multi-Aquifer Well Package supports a ``WITHDRAWAL'' flow term. If this withdrawal term is active, then water will be withdrawn from the well at the calculated concentration of the well. block period name status diff --git a/doc/mf6io/mf6ivar/dfn/gwt-ssm.dfn b/doc/mf6io/mf6ivar/dfn/gwt-ssm.dfn index 085398bc4c9..71a83e772c2 100644 --- a/doc/mf6io/mf6ivar/dfn/gwt-ssm.dfn +++ b/doc/mf6io/mf6ivar/dfn/gwt-ssm.dfn @@ -44,7 +44,7 @@ tagged false optional false reader urword longname source type -description keyword indicating how concentration will be assigned for sources and sinks. Keyword must be specified as either AUX or AUXMIXED. For both options the user must provide an auxiliary variable in the corresponding flow package. The auxiliary variable must have the same name as the AUXNAME value that follows. If the AUX keyword is specified, then the auxiliary variable specified by the user will be assigned as the concenration value for groundwater sources (flows with a positive sign). For negative flow rates (sinks), groundwater will be withdrawn from the cell at the simulated concentration of the cell. The AUXMIXED option provides an alternative method for how to determine the concentration of sinks. If the cell concentration is larger than the user-specified auxiliary concentration, then the concentration of groundwater withdrawn from the cell will be assigned as the user-specified concentration. Alternatively, if the user-specified auxiliary concentration is larger than the cell concentration, then groundwater will be withdrawn at the cell concentration. Thus, the AUXMIXED option is designed to work with the Evapotranspiration (EVT) and Recharge (RCH) Packages where water may be withdrawn at a concentration that is less than the cell concentration. +description keyword indicating how concentration will be assigned for sources and sinks. Keyword must be specified as either AUX or AUXMIXED. For both options the user must provide an auxiliary variable in the corresponding flow package. The auxiliary variable must have the same name as the AUXNAME value that follows. If the AUX keyword is specified, then the auxiliary variable specified by the user will be assigned as the concentration value for groundwater sources (flows with a positive sign). For negative flow rates (sinks), groundwater will be withdrawn from the cell at the simulated concentration of the cell. The AUXMIXED option provides an alternative method for how to determine the concentration of sinks. If the cell concentration is larger than the user-specified auxiliary concentration, then the concentration of groundwater withdrawn from the cell will be assigned as the user-specified concentration. Alternatively, if the user-specified auxiliary concentration is larger than the cell concentration, then groundwater will be withdrawn at the cell concentration. Thus, the AUXMIXED option is designed to work with the Evapotranspiration (EVT) and Recharge (RCH) Packages where water may be withdrawn at a concentration that is less than the cell concentration. block sources name auxname diff --git a/doc/mf6io/mf6ivar/examples/gwt-fmi-example.dat b/doc/mf6io/mf6ivar/examples/gwt-fmi-example.dat index 67dee32f24e..b7aaace26cb 100644 --- a/doc/mf6io/mf6ivar/examples/gwt-fmi-example.dat +++ b/doc/mf6io/mf6ivar/examples/gwt-fmi-example.dat @@ -5,7 +5,7 @@ END OPTIONS BEGIN PACKAGEDATA GWFBUDGET FILEIN ../flow/mygwfmodel.bud GWFHEAD FILEIN ../flow/mygwfmodel.hds - GWFMOVER FILEIN ../flow/mygwfmodel.hds + GWFMOVER FILEIN ../flow/mygwfmodel.mvr.bud LAK-1 FILEIN ../flow/mygwfmodel.lak.bud SFR-1 FILEIN ../flow/mygwfmodel.sfr.bud MAW-1 FILEIN ../flow/mygwfmodel.maw.bud diff --git a/doc/mf6io/mf6ivar/examples/gwt-sft-example-obs.dat b/doc/mf6io/mf6ivar/examples/gwt-sft-example-obs.dat index 38eae5d215b..2e6a2c2bf45 100644 --- a/doc/mf6io/mf6ivar/examples/gwt-sft-example-obs.dat +++ b/doc/mf6io/mf6ivar/examples/gwt-sft-example-obs.dat @@ -3,7 +3,7 @@ BEGIN options PRINT_INPUT END options -BEGIN continuous FILEOUT gwt_lkt02.lkt.obs.csv +BEGIN continuous FILEOUT gwt_sft02.lkt.obs.csv sft-1-conc CONCENTRATION 1 sft-1-extinflow EXT-INFLOW 1 sft-1-rain RAINFALL 1 diff --git a/doc/mf6io/mf6ivar/readme.md b/doc/mf6io/mf6ivar/readme.md index 479b58c9ee5..3a78c3273fa 100644 --- a/doc/mf6io/mf6ivar/readme.md +++ b/doc/mf6io/mf6ivar/readme.md @@ -257,7 +257,7 @@ Note that in this case, both of the variables are required (optional false) and The recarray type is pattered after the recarray type that is available in the numpy package for Python. -An example of a recarray record is shown below for the drain package. First you'll note that the recarray has a shape. This shape is of maxbound, which is the maximum number of records that the user can enter. Also note that following the ``recarray'' identifier is cellid, elev, cond, aux, and boundname. These are all additional variables that are described after the recarray. Because these are listed next to recarray, the protocol as that they will all be listed on one line. You'll also note that the cellid, elev, cond, aux, and boundname variables have the in_record attribute set to true. This is required so that the variables are not written again after the recarray; they are only written inside the recarray. These variables all have the tagged attribute set to false so that they are not preceded by a keyword. +An example of a recarray record is shown below for the drain package. First you'll note that the recarray has a shape. This shape is of maxbound, which is the maximum number of records that the user can enter. Also note that following the ``recarray'' identifier is cellid, elev, cond, aux, and boundname. These are all additional variables that are described after the recarray. Because these are listed next to recarray, the protocol is that they will all be listed on one line. You'll also note that the cellid, elev, cond, aux, and boundname variables have the in_record attribute set to true. This is required so that the variables are not written again after the recarray; they are only written inside the recarray. These variables all have the tagged attribute set to false so that they are not preceded by a keyword. ``` block period diff --git a/src/Model/GroundWaterFlow/gwf3buy8.f90 b/src/Model/GroundWaterFlow/gwf3buy8.f90 index 281df9f962b..39fbdf5629c 100644 --- a/src/Model/GroundWaterFlow/gwf3buy8.f90 +++ b/src/Model/GroundWaterFlow/gwf3buy8.f90 @@ -36,7 +36,7 @@ module GwfBuyModule real(DP), pointer :: denseref => null() ! reference fluid density real(DP), dimension(:), pointer, contiguous :: dense => null() ! density real(DP), dimension(:), pointer, contiguous :: concbuy => null() ! concentration array if specified in buy package - real(DP), dimension(:), pointer, contiguous :: elev => null() ! cell center elevation (optional; if not specified, hten use (top+bot)/2) + real(DP), dimension(:), pointer, contiguous :: elev => null() ! cell center elevation (optional; if not specified, then use (top+bot)/2) integer(I4B), dimension(:), pointer :: ibound => null() ! store pointer to ibound integer(I4B), pointer :: nrhospecies => null() ! number of species used in equation of state to calculate density @@ -300,7 +300,7 @@ subroutine buy_rp(this) integer(I4B) :: i ! -- formats character(len=*), parameter :: fmtc = & - "('Buoyancy Package does not have have a concentration set & + "('Buoyancy Package does not have a concentration set & &for species ',i0,'. One or more model names may be specified & &incorrectly in the PACKAGEDATA block or a gwf-gwt exchange may need & &to be activated.')" diff --git a/src/Model/GroundWaterFlow/gwf3drn8.f90 b/src/Model/GroundWaterFlow/gwf3drn8.f90 index 5b8d7cd1a9b..c0b5562c555 100644 --- a/src/Model/GroundWaterFlow/gwf3drn8.f90 +++ b/src/Model/GroundWaterFlow/gwf3drn8.f90 @@ -169,17 +169,16 @@ subroutine drn_options(this, option, found) integer(I4B) :: n ! ------------------------------------------------------------------------------ ! + found = .true. select case (option) case ('MOVER') this%imover = 1 write (this%iout, '(4x,A)') 'MOVER OPTION ENABLED' - found = .true. case ('AUXDEPTHNAME') call this%parser%GetStringCaps(ddrnauxname) this%iauxddrncol = -1 write (this%iout, '(4x,a,a)') & 'AUXILIARY DRAIN DEPTH NAME: ', trim(ddrnauxname) - found = .true. ! ! -- right now these are options that are only available in the ! development version and are not included in the documentation. @@ -191,7 +190,6 @@ subroutine drn_options(this, option, found) write (this%iout, '(4x,a,1x,a)') & 'CUBIC SCALING will be used for drains with non-zero DDRN values', & 'even if the NEWTON-RAPHSON method is not being used.' - found = .true. case default ! ! -- No options found diff --git a/src/Model/GroundWaterTransport/gwt1mst1.f90 b/src/Model/GroundWaterTransport/gwt1mst1.f90 index 148f937d385..f962a5f2bef 100644 --- a/src/Model/GroundWaterTransport/gwt1mst1.f90 +++ b/src/Model/GroundWaterTransport/gwt1mst1.f90 @@ -1,6 +1,6 @@ !> -- @ brief Mobile Storage and Transfer (MST) Module !! -!! The GwtMstModule is contains the GwtMstType, which is the +!! The GwtMstModule contains the GwtMstType, which is the !! derived type responsible for adding the effects of !! 1. Changes in dissolved solute mass !! 2. Decay of dissolved solute mass @@ -140,7 +140,7 @@ subroutine mst_ar(this, dis, ibound) "(1x,/1x,'MST -- MOBILE STORAGE AND TRANSFER PACKAGE, VERSION 1, & &7/29/2020 INPUT READ FROM UNIT ', i0, //)" ! - ! --print a message identifying the immobile domain package. + ! --print a message identifying the mobile storage and transfer package. write (this%iout, fmtmst) this%inunit ! ! -- Read options @@ -1309,7 +1309,7 @@ subroutine read_data(this) call this%parser%StoreErrorUnit() end if ! - ! -- Check for rquired porosity + ! -- Check for required porosity if (.not. lname(1)) then write (errmsg, '(a)') 'POROSITY not specified in GRIDDATA block.' call store_error(errmsg) From 19d26c1d4a9fd419030c6df706791a04f408db0f Mon Sep 17 00:00:00 2001 From: w-bonelli Date: Wed, 28 Jun 2023 12:28:50 -0400 Subject: [PATCH 120/123] ci: update flopy before benchmarking in docs.yml (#1278) --- .github/workflows/docs.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index afe452ec6dd..1da492fc9c5 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -88,6 +88,11 @@ jobs: pip install -r requirements.pip.txt pip install -r requirements.usgs.txt + - name: Update FloPy + if: steps.cache-examples.outputs.cache-hit != 'true' + working-directory: modflow6/autotest + run: python update_flopy.py + - name: Build example models if: steps.cache-examples.outputs.cache-hit != 'true' working-directory: modflow6-examples/etc From 26d02877666f071114db797cb8eb9a0c8e60f2c2 Mon Sep 17 00:00:00 2001 From: w-bonelli Date: Wed, 28 Jun 2023 13:40:33 -0400 Subject: [PATCH 121/123] ci(release): miscellaneous fixes (#1276) * fix input usages in release_dispatch.yml * install triangle/gridgen before ci_build_files.py --- .github/workflows/release.yml | 14 ++++++++++++-- .github/workflows/release_dispatch.yml | 12 +++++++----- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a0a154946bc..1973766a474 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -306,7 +306,7 @@ jobs: name: bin-${{ runner.os }} path: bin - - name: Install dependencies for ex-gwf-twri example model + - name: Install dependencies for building models working-directory: modflow6-examples/etc env: GITHUB_TOKEN: ${{ github.token }} @@ -323,11 +323,21 @@ jobs: chmod +x "${{ github.workspace }}/bin/zbud6" # the example model also needs mf2005 - get-modflow "${{ github.workspace }}/bin" --subset mf2005 + get-modflow "${{ github.workspace }}/bin" --subset mf2005,triangle,gridgen + - name: Update FloPy + working-directory: modflow6/autotest + run: python update_flopy.py + - name: Build ex-gwf-twri example model + if: inputs.full != true working-directory: modflow6-examples/scripts run: python ex-gwf-twri.py + + - name: Build example models + if: inputs.full == true + working-directory: modflow6-examples/etc + run: python ci_build_files.py - name: Create full docs folder structure if: inputs.full == true diff --git a/.github/workflows/release_dispatch.yml b/.github/workflows/release_dispatch.yml index bcfce4c3d3e..567bab3eae3 100644 --- a/.github/workflows/release_dispatch.yml +++ b/.github/workflows/release_dispatch.yml @@ -140,7 +140,9 @@ jobs: run: | # update version files ver="${{ needs.set_options.outputs.version }}" - if [[ "${{ inputs.approve }}" == "true" ]]; then + # approve will be empty if workflow was triggered by pushing a release branch + # todo: pull approve into set_options job/output? + if [[ ("${{ inputs.approve }}" == "true") || ("${{ inputs.approve }}" == "") ]]; then python update_version.py -v "$ver" --approve else python update_version.py -v "$ver" @@ -217,9 +219,9 @@ jobs: reset: name: Draft reset PR - # runs only after GitHub release post is published (manually promoted from draft to public) - # to bring the release commits from master back into develop - if: github.event_name == 'release' && inputs.reset == 'true' + # runs only after GitHub release post is published (promoted from draft to public) + # to bring release commits from master back into develop and reset IDEVELOPMODE=1. + if: github.event_name == 'release' && (inputs.reset == 'true' || inputs.reset == '') runs-on: ubuntu-22.04 defaults: run: @@ -263,7 +265,7 @@ jobs: git config user.email "41898282+github-actions[bot]@users.noreply.github.com" # updating version numbers if enabled - if [[ "${{ inputs.commit_version }}" == "true" ]]; then + if [[ ("${{ inputs.commit_version }}" == "true") || ("${{ inputs.commit_version }}" == "") ]]; then ver="${{ steps.latest_tag.outputs.tag }}" python distribution/update_version.py -v "$ver" git add -A From d0bb1ffbe141d9aa8e326846a044f1f07a07564c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 28 Jun 2023 19:45:13 +0000 Subject: [PATCH 122/123] ci(release): update version to 6.4.2 --- CITATION.cff | 4 ++-- DISCLAIMER.md | 17 +++++++++-------- README.md | 19 ++++++++++--------- code.json | 4 ++-- doc/version.py | 5 ++++- doc/version.tex | 2 +- src/Utilities/version.f90 | 24 +++++++++++++----------- version.txt | 5 ++++- 8 files changed, 45 insertions(+), 35 deletions(-) diff --git a/CITATION.cff b/CITATION.cff index 6986280242e..b378cfa4c99 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -2,8 +2,8 @@ cff-version: 1.2.0 message: If you use this software, please cite the software itself. type: software title: MODFLOW 6 Modular Hydrologic Model -version: 6.4.1 -date-released: '2022-12-09' +version: 6.4.2 +date-released: '2023-06-28' doi: 10.5066/F76Q1VQV abstract: MODFLOW 6 is an object-oriented program and framework developed to provide a platform for supporting multiple models and multiple types of models within the diff --git a/DISCLAIMER.md b/DISCLAIMER.md index f32778cb5e4..9226475a939 100644 --- a/DISCLAIMER.md +++ b/DISCLAIMER.md @@ -1,11 +1,12 @@ Disclaimer ---------- -This software is preliminary or provisional and is subject to revision. It is -being provided to meet the need for timely best science. The software has not -received final approval by the U.S. Geological Survey (USGS). No warranty, -expressed or implied, is made by the USGS or the U.S. Government as to the -functionality of the software and related material nor shall the fact of release -constitute any such warranty. The software is provided on the condition that -neither the USGS nor the U.S. Government shall be held liable for any damages -resulting from the authorized or unauthorized use of the software. +This software has been approved for release by the U.S. Geological Survey +(USGS). Although the software has been subjected to rigorous review, the USGS +reserves the right to update the software as needed pursuant to further analysis +and review. No warranty, expressed or implied, is made by the USGS or the U.S. +Government as to the functionality of the software and related material nor +shall the fact of release constitute any such warranty. Furthermore, the +software is released on condition that neither the USGS nor the U.S. Government +shall be held liable for any damages resulting from its authorized or +unauthorized use. diff --git a/README.md b/README.md index e048a5c11d2..306731b232e 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ This is the development repository for the USGS MODFLOW 6 Hydrologic Model. The official USGS distribution is available at [USGS Release Page](https://water.usgs.gov/ogw/modflow/MODFLOW.html). -### Version 6.4.2 (preliminary) +### Version 6.4.2 [![GitHub release](https://img.shields.io/github/release/MODFLOW-USGS/modflow6.svg)](https://github.com/MODFLOW-USGS/modflow6/releases/latest) [![MODFLOW 6 continuous integration](https://github.com/MODFLOW-USGS/modflow6/actions/workflows/ci.yml/badge.svg)](https://github.com/MODFLOW-USGS/modflow6/actions/workflows/ci.yml) @@ -106,12 +106,13 @@ Citations for specific versions are included with the [releases](https://github. Disclaimer ---------- -This software is preliminary or provisional and is subject to revision. It is -being provided to meet the need for timely best science. The software has not -received final approval by the U.S. Geological Survey (USGS). No warranty, -expressed or implied, is made by the USGS or the U.S. Government as to the -functionality of the software and related material nor shall the fact of release -constitute any such warranty. The software is provided on the condition that -neither the USGS nor the U.S. Government shall be held liable for any damages -resulting from the authorized or unauthorized use of the software. +This software has been approved for release by the U.S. Geological Survey +(USGS). Although the software has been subjected to rigorous review, the USGS +reserves the right to update the software as needed pursuant to further analysis +and review. No warranty, expressed or implied, is made by the USGS or the U.S. +Government as to the functionality of the software and related material nor +shall the fact of release constitute any such warranty. Furthermore, the +software is released on condition that neither the USGS nor the U.S. Government +shall be held liable for any damages resulting from its authorized or +unauthorized use. diff --git a/code.json b/code.json index 5e7d5fa9e38..99dbdf41b3c 100644 --- a/code.json +++ b/code.json @@ -1,6 +1,6 @@ [ { - "status": "Preliminary", + "status": "Release", "languages": [ "Fortran2008" ], @@ -20,7 +20,7 @@ "laborHours": -1, "version": "6.4.2", "date": { - "metadataLastUpdated": "2023-06-26" + "metadataLastUpdated": "2023-06-28" }, "organization": "U.S. Geological Survey", "permissions": { diff --git a/doc/version.py b/doc/version.py index bcb5e4e4fb1..b063daa0c02 100644 --- a/doc/version.py +++ b/doc/version.py @@ -1,7 +1,10 @@ # MODFLOW 6 version file automatically created using...update_version.py -# created on...December 09, 2022 20:57:08 +# created on...June 28, 2023 19:45:12 major = 6 minor = 4 micro = 2 +label = '' __version__ = '{:d}.{:d}.{:d}'.format(major, minor, micro) +if label: + __version__ += '{}{}'.format(__version__, label) \ No newline at end of file diff --git a/doc/version.tex b/doc/version.tex index 3aebcf5655d..6beb425516a 100644 --- a/doc/version.tex +++ b/doc/version.tex @@ -1,3 +1,3 @@ \newcommand{\modflowversion}{mf6.4.2} -\newcommand{\modflowdate}{June 26, 2023} +\newcommand{\modflowdate}{June 28, 2023} \newcommand{\currentmodflowversion}{Version \modflowversion---\modflowdate} diff --git a/src/Utilities/version.f90 b/src/Utilities/version.f90 index 2fe6f7f7fb7..208b488ed1e 100644 --- a/src/Utilities/version.f90 +++ b/src/Utilities/version.f90 @@ -17,7 +17,7 @@ module VersionModule ! -- modflow 6 version integer(I4B), parameter :: IDEVELOPMODE = 1 character(len=*), parameter :: VERSIONNUMBER = '6.4.2' - character(len=*), parameter :: VERSIONTAG = ' (preliminary) 06/26/2023' + character(len=*), parameter :: VERSIONTAG = ' 06/28/2023' character(len=40), parameter :: VERSION = VERSIONNUMBER//VERSIONTAG character(len=2), parameter :: MFVNAM = ' 6' character(len=*), parameter :: MFTITLE = & @@ -64,16 +64,18 @@ module VersionModule ! -- disclaimer must be appropriate for version (release or release candidate) character(len=*), parameter :: FMTDISCLAIMER = & "(/,& - &'This software is preliminary or provisional and is subject to ',/,& - &'revision. It is being provided to meet the need for timely best ',/,& - &'science. The software has not received final approval by the U.S. ',/,& - &'Geological Survey (USGS). No warranty, expressed or implied, is made ',/,& - &'by the USGS or the U.S. Government as to the functionality of the ',/,& - &'software and related material nor shall the fact of release ',/,& - &'constitute any such warranty. The software is provided on the ',/,& - &'condition that neither the USGS nor the U.S. Government shall be held ',/,& - &'liable for any damages resulting from the authorized or unauthorized ',/,& - &'use of the software.',/)" + &'This software has been approved for release by the U.S. Geological ',/,& + &'Survey (USGS). Although the software has been subjected to rigorous ',/,& + &'review, the USGS reserves the right to update the software as needed ',/,& + &'pursuant to further analysis and review. No warranty, expressed or ',/,& + &'implied, is made by the USGS or the U.S. Government as to the ',/,& + &'functionality of the software and related material nor shall the ',/,& + &'fact of release constitute any such warranty. Furthermore, the ',/,& + &'software is released on condition that neither the USGS nor the U.S. ',/,& + &'Government shall be held liable for any damages resulting from its ',/,& + &'authorized or unauthorized use. Also refer to the USGS Water ',/,& + &'Resources Software User Rights Notice for complete use, copyright, ',/,& + &'and distribution information.',/)" contains diff --git a/version.txt b/version.txt index bcb5e4e4fb1..b063daa0c02 100644 --- a/version.txt +++ b/version.txt @@ -1,7 +1,10 @@ # MODFLOW 6 version file automatically created using...update_version.py -# created on...December 09, 2022 20:57:08 +# created on...June 28, 2023 19:45:12 major = 6 minor = 4 micro = 2 +label = '' __version__ = '{:d}.{:d}.{:d}'.format(major, minor, micro) +if label: + __version__ += '{}{}'.format(__version__, label) \ No newline at end of file From 8d91f823b29ab5d06beb12cb1a7d285a1a1f8aba Mon Sep 17 00:00:00 2001 From: w-bonelli Date: Wed, 28 Jun 2023 16:14:50 -0400 Subject: [PATCH 123/123] ci(release): set IDEVELOPMODE = 0 --- src/Utilities/version.f90 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Utilities/version.f90 b/src/Utilities/version.f90 index 208b488ed1e..e9c4a685450 100644 --- a/src/Utilities/version.f90 +++ b/src/Utilities/version.f90 @@ -15,7 +15,7 @@ module VersionModule implicit none public ! -- modflow 6 version - integer(I4B), parameter :: IDEVELOPMODE = 1 + integer(I4B), parameter :: IDEVELOPMODE = 0 character(len=*), parameter :: VERSIONNUMBER = '6.4.2' character(len=*), parameter :: VERSIONTAG = ' 06/28/2023' character(len=40), parameter :: VERSION = VERSIONNUMBER//VERSIONTAG