diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..ca8486e --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,270 @@ +name: Release +on: + push: + branches: + - main + - v[0-9]+.[0-9]+.[0-9]+* + release: + types: + - published +jobs: + prep: + name: Prepare release + runs-on: ubuntu-latest + if: ${{ github.event_name == 'push' && github.ref_name != 'main' }} + permissions: + contents: write + pull-requests: write + defaults: + run: + shell: bash + steps: + + - name: Checkout release branch + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Setup Python + uses: actions/setup-python@v4 + with: + python-version: 3.8 + cache: 'pip' + cache-dependency-path: pyproject.toml + + - name: Install Python dependencies + run: | + pip install --upgrade pip + pip install build twine + pip install . + pip install ".[lint, test]" + + - name: Update version + id: version + run: | + ref="${{ github.ref_name }}" + version="${ref#"v"}" + python scripts/update_version.py -v "$version" + python -c "import modflowapi; print('Version: ', modflowapi.__version__)" + echo "version=$version" >> $GITHUB_OUTPUT + + - name: Touch changelog + run: touch HISTORY.md + + - name: Generate changelog + id: cliff + uses: orhun/git-cliff-action@v1 + with: + config: cliff.toml + args: --verbose --unreleased --tag ${{ steps.version.outputs.version }} + env: + OUTPUT: CHANGELOG.md + + - name: Update changelog + id: update-changelog + run: | + # move changelog + clog="CHANGELOG_${{ steps.version.outputs.version }}.md" + echo "changelog=$clog" >> $GITHUB_OUTPUT + sudo cp "${{ steps.cliff.outputs.changelog }}" "$clog" + + # show current release changelog + cat "$clog" + + # substitute full group names + sed -i 's/#### Ci/#### Continuous integration/' "$clog" + sed -i 's/#### Feat/#### New features/' "$clog" + sed -i 's/#### Fix/#### Bug fixes/' "$clog" + sed -i 's/#### Refactor/#### Refactoring/' "$clog" + sed -i 's/#### Test/#### Testing/' "$clog" + + cat "$clog" HISTORY.md > temp_history.md + sudo mv temp_history.md HISTORY.md + + # show full changelog + cat HISTORY.md + + - name: Upload changelog + uses: actions/upload-artifact@v3 + with: + name: changelog + path: ${{ steps.update-changelog.outputs.changelog }} + + - name: Push release branch + env: + GITHUB_TOKEN: ${{ github.token }} + run: | + ver="${{ steps.version.outputs.version }}" + changelog=$(cat ${{ steps.update-changelog.outputs.changelog }} | grep -v "### Version $ver") + + # remove this release's changelog so we don't commit it + # the changes have already been prepended to HISTORY.md + rm ${{ steps.update-changelog.outputs.changelog }} + rm -f CHANGELOG.md + + # commit and push changes + 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): set version to ${{ steps.version.outputs.version }}, update changelog" + git push origin "${{ github.ref_name }}" + + title="Release $ver" + body=' + # Release '$ver' + + The release can be approved by merging this pull request into `main`. This will trigger jobs to publish the release to PyPI and reset `develop` from `main`, incrementing the minor version number. + + ## Changelog + + '$changelog' + ' + gh pr create -B "main" -H "${{ github.ref_name }}" --title "$title" --draft --body "$body" + + release: + name: Draft release + # runs only when changes are merged to main + if: ${{ github.event_name == 'push' && github.ref_name == 'main' }} + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + steps: + + - name: Checkout repo + uses: actions/checkout@v3 + with: + ref: main + + - name: Setup Python + uses: actions/setup-python@v4 + with: + python-version: 3.8 + + - name: Install Python dependencies + run: | + pip install --upgrade pip + pip install . + pip install ".[test]" + + # actions/download-artifact won't look at previous workflow runs but we need to in order to get changelog + - name: Download artifacts + uses: dawidd6/action-download-artifact@v2 + + - name: Draft release + env: + GITHUB_TOKEN: ${{ github.token }} + run: | + version=$(python scripts/update_version.py -g) + title="modflowapi $version" + notes=$(cat "changelog/CHANGELOG_$version.md" | grep -v "### Version $version") + gh release create "$version" \ + --target main \ + --title "$title" \ + --notes "$notes" \ + --draft \ + --latest + + publish: + name: Publish package + # runs only after release is published (manually promoted from draft) + if: github.event_name == 'release' && github.repository_owner == 'MODFLOW-USGS' + runs-on: ubuntu-22.04 + permissions: + contents: write + pull-requests: write + steps: + + - name: Checkout main branch + uses: actions/checkout@v3 + with: + ref: main + + - name: Setup Python + uses: actions/setup-python@v4 + with: + python-version: 3.8 + + - name: Install Python dependencies + run: | + pip install --upgrade pip + pip install build twine + pip install . + + - name: Build package + run: python -m build + + - name: Check package + run: twine check --strict dist/* + + - name: Publish package + run: twine upload dist/* + + reset: + name: Draft reset PR + if: ${{ github.event_name == 'release' }} + runs-on: ubuntu-22.04 + permissions: + contents: write + pull-requests: write + steps: + + - name: Checkout main branch + uses: actions/checkout@v3 + with: + ref: main + + - name: Setup Python + uses: actions/setup-python@v4 + with: + python-version: 3.8 + cache: 'pip' + cache-dependency-path: pyproject.toml + + - name: Install Python dependencies + run: | + pip install --upgrade pip + pip install . + pip install ".[lint, test]" + + - name: Get release tag + uses: oprypin/find-latest-tag@v1 + id: latest_tag + with: + repository: ${{ github.repository }} + releases-only: true + + - name: Draft pull request + env: + GITHUB_TOKEN: ${{ github.token }} + run: | + # create reset branch from main + reset_branch="post-release-${{ steps.latest_tag.outputs.tag }}-reset" + git switch -c $reset_branch + + # increment minor version + major_version=$(echo "${{ steps.latest_tag.outputs.tag }}" | cut -d. -f1) + minor_version=$(echo "${{ steps.latest_tag.outputs.tag }}" | cut -d. -f2) + patch_version=0 + version="$major_version.$((minor_version + 1)).$patch_version" + python scripts/update_version.py -v "$version" + + # format Python files + black -v ./modflowapi + + # 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 to development version $version" + git push -u origin $reset_branch + + # create PR into develop + body=' + # Reinitialize for development + + Updates the `develop` branch from `main` following a successful release. Increments the minor version number. + ' + gh pr create -B "develop" -H "$reset_branch" --title "Reinitialize develop branch" --draft --body "$body" \ No newline at end of file diff --git a/CITATION.cff b/CITATION.cff index 8e9e2ea..b7fab24 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -3,7 +3,7 @@ message: If you use this software, please cite both the article from preferred-c and the software itself. type: software title: MODFLOW API -version: 0.1.0 +version: 0.1.1 date-released: '2023-04-19' abstract: An extension to xmipy for the MODFLOW API. repository-artifact: https://pypi.org/project/modflowapi diff --git a/HISTORY.md b/HISTORY.md new file mode 100644 index 0000000..f187443 --- /dev/null +++ b/HISTORY.md @@ -0,0 +1,16 @@ +### Version 0.1.1 + +#### New features + +* [feat(modflowapi)](https://github.com/MODFLOW-USGS/modflowapi/commit/3cc77dad6eae6c0fdb8ea856bc3ea3e7285ca658): Base files for modflowapi package. Committed by Joseph D Hughes on 2021-05-11. + +#### Bug fixes + +* [fix(get value)](https://github.com/MODFLOW-USGS/modflowapi/commit/defd2ee2bfc840ee2b7b3df76fcea4af651f1936): Fixed error handling when modflowapi fails to get a pointer to a value from the API (#9). Committed by spaulins-usgs on 2023-02-24. + +#### Refactoring + +* [refactor(rhs, hcof, AdvancedInput)](https://github.com/MODFLOW-USGS/modflowapi/commit/2c4d893eaa96457be099313a220c7c7d8fca888a): Bug fixes for setting variable values for advanced inputs. Committed by Joshua Larsen on 2023-02-25. +* [refactor(EOL)](https://github.com/MODFLOW-USGS/modflowapi/commit/e0ca9e80a60ae6c85933a69ec322a5bc861a32ab): Change CRLF to LF line endings for source files (#12). Committed by Mike Taves on 2023-03-24. +* [refactor(test_rhs_hcof_advanced)](https://github.com/MODFLOW-USGS/modflowapi/commit/a8e241df1def5899ccbf22368eddc76da0d7a60c): Add additional test (#13). Committed by Joshua Larsen on 2023-03-29. + diff --git a/cliff.toml b/cliff.toml new file mode 100644 index 0000000..d8089d6 --- /dev/null +++ b/cliff.toml @@ -0,0 +1,46 @@ +# configuration file for git-cliff (0.1.0) + +[changelog] +body = """ +{% if version %}\ + ### Version {{ version | trim_start_matches(pat="v") }} +{% else %}\ + ### [unreleased] +{% endif %}\ +{% for group, commits in commits | group_by(attribute="group") %} + #### {{ group | upper_first }} + {% for commit in commits %} + * [{{ commit.group }}{% if commit.scope %}({{ commit.scope }}){% endif %}](https://github.com/MODFLOW-USGS/modflowapi/commit/{{ commit.id }}): {% if commit.breaking %}[**breaking**] {% endif %}{{ commit.message | upper_first }}. Committed by {{ commit.author.name }} on {{ commit.author.timestamp | date(format="%Y-%m-%d") }}.\ + {% endfor %} +{% endfor %}\n +""" +trim = true + +[git] +conventional_commits = true +filter_unconventional = true +split_commits = false +commit_parsers = [ + { message = "^[fF]eat", group = "feat"}, + { message = "^[fF]ix", group = "fix"}, + { message = "^[bB]ug", group = "fix"}, + { message = "^[pP]erf", group = "perf"}, + { message = "^[rR]efactor", group = "refactor"}, + { message = "^[uU]pdate", group = "refactor"}, + { message = "^[aA]dd", group = "refactor"}, + { message = "^[dD]oc.*", group = "docs", skip = true}, + { message = "^[bB]inder", group = "docs", skip = true}, + { message = "^[nN]otebook.*", group = "docs", skip = true}, + { message = "^[rR][eE][aA][dD].*", group = "docs", skip = true}, + { message = "^[sS]tyl.*", group = "style", skip = true}, + { message = "^[tT]est.*", group = "test", skip = true}, + { message = "^[cC][iI]", skip = true}, + { message = "^[cC][iI]\\(release\\):", skip = true}, + { message = "^[rR]elease", skip = true}, + { message = "^[cC]hore", group = "chore", skip = true}, +] +protect_breaking_commits = false +filter_commits = false +tag_pattern = "[0-9].[0-9].[0-9]" +ignore_tags = "" +sort_commits = "oldest" diff --git a/examples/notebooks/Head Monitor Example.ipynb b/examples/notebooks/Head Monitor Example.ipynb new file mode 100644 index 0000000..4e800ea --- /dev/null +++ b/examples/notebooks/Head Monitor Example.ipynb @@ -0,0 +1,255 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "ee31d799", + "metadata": {}, + "source": [ + "# MODFLOW-API Head monitor example\n", + "\n", + "In this example the modflow-api is used in a more complex callback function to create a Head Monitor that updates at the timestep level. This example reverses `CHD` boundary conditions each stress period on a simple 10 x 10 model and displays the head results for each timestep.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "4526a124", + "metadata": {}, + "outputs": [], + "source": [ + "from IPython.display import clear_output, display # remove this import if adapted to python script\n", + "\n", + "from modflowapi import run_simulation, Callbacks\n", + "from flopy.discretization import StructuredGrid\n", + "from flopy.plot import PlotMapView, styles\n", + "from pathlib import Path\n", + "import numpy as np\n", + "import matplotlib.pyplot as plt" + ] + }, + { + "cell_type": "markdown", + "id": "3f7ac8a2", + "metadata": {}, + "source": [ + "### Create a class that includes a callback function\n", + "\n", + "This class handles changing the `CHD` boundary condition as well as updating the matplotlib plot." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "cdd38b59", + "metadata": {}, + "outputs": [], + "source": [ + "class StructuredHeadMonitor:\n", + " \"\"\"\n", + " An example class that reverses the model gradient by\n", + " swapping CHD boundary conditions each stress period, \n", + " and monitors the head at each timestep by updating\n", + " a matplotlib plot. This class could be adapted to \n", + " be used as a head monitor to observe other changes\n", + " in the model by modifying the callback class.\n", + " \n", + " Parameters\n", + " ----------\n", + " layer: int\n", + " zero based model layer to plot\n", + " vmin : float\n", + " minimum head value for color scaling on the plot\n", + " vmax : float\n", + " maximum head value for color scaling on the plot\n", + " \"\"\"\n", + "\n", + " def __init__(self, layer, vmin, vmax):\n", + " self.modelgrid = None\n", + " self.ax = None\n", + " self.pmv = None\n", + " self.pc = None\n", + " self.ax = None\n", + " self.layer = layer\n", + " self.vmin = vmin\n", + " self.vmax = vmax\n", + " self.kperold = None\n", + "\n", + " def build_modelgrid(self, ml):\n", + " \"\"\"\n", + " Method to update the matplotlib plot\n", + " \n", + " Parameters\n", + " ----------\n", + " ml : ApiModel\n", + " modflow-api ApiModel object\n", + " \"\"\"\n", + " delc = ml.dis.get_advanced_var(\"delc\")\n", + " delr = ml.dis.get_advanced_var(\"delr\")\n", + " top = ml.dis.top.values[0]\n", + " botm = ml.dis.bot.values\n", + " idomain = ml.dis.idomain.values\n", + " self.modelgrid = StructuredGrid(\n", + " delc=delc,\n", + " delr=delr,\n", + " top=top,\n", + " botm=botm,\n", + " idomain=idomain\n", + " )\n", + "\n", + " def initialize_plot(self):\n", + " \"\"\"\n", + " Method to initalize a matplotlib plot using flopy\n", + " \"\"\"\n", + " fig, ax = plt.subplots(figsize=(8, 8))\n", + " self.fig = fig\n", + " self.ax = ax\n", + " self.pmv = PlotMapView(modelgrid=self.modelgrid, ax=ax, layer=self.layer)\n", + " grid = self.pmv.plot_grid()\n", + " idm = self.pmv.plot_inactive()\n", + " initial = np.full(self.modelgrid.shape, np.nan)\n", + " self.pc = self.pmv.plot_array(initial, vmin=self.vmin, vmax=self.vmax)\n", + " plt.colorbar(self.pc)\n", + "\n", + " def update_plot(self, ml):\n", + " \"\"\"\n", + " Method to update the matplotlib plot\n", + " \n", + " Parameters\n", + " ----------\n", + " ml : ApiModel\n", + " modflow-api ApiModel object\n", + " \"\"\"\n", + " heads = ml.X\n", + " self.ax.cla()\n", + " grid = self.pmv.plot_grid()\n", + " idm = self.pmv.plot_inactive()\n", + " self.pc = self.pmv.plot_array(heads, vmin=self.vmin, vmax=self.vmax)\n", + " \n", + " # only applicable to jupyter notebooks, remove these two lines in python scipt\n", + " display(self.fig) \n", + " if ml.kper == (ml.nper - 1) and ml.kstp == (ml.nstp - 1):\n", + " pass\n", + " else:\n", + " clear_output(wait = True) \n", + " \n", + " # the pause time can be reduced if adapted in python script \n", + " plt.pause(0.1) \n", + "\n", + " def callback(self, sim, callback_step):\n", + " \"\"\"\n", + " A demonstration function that dynamically adjusts the CHD\n", + " boundary conditions each stress period in a modflow-6 model\n", + " through the MODFLOW-API and then updates heads on a matplotlib\n", + " plot for each timestep.\n", + "\n", + " Parameters\n", + " ----------\n", + " sim : modflowapi.Simulation\n", + " A simulation object for the solution group that is \n", + " currently being solved\n", + " callback_step : enumeration\n", + " modflowapi.Callbacks enumeration object that indicates\n", + " the part of the solution modflow is currently in.\n", + " \"\"\"\n", + " if callback_step == Callbacks.initialize:\n", + " ml = sim.get_model()\n", + " self.build_modelgrid(ml)\n", + " self.initialize_plot()\n", + "\n", + " if callback_step == Callbacks.timestep_start:\n", + " ml = sim.get_model()\n", + " if ml.kper == 0:\n", + " self.kperold = ml.kper\n", + " head = ml.chd.stress_period_data.dataframe[\"head\"].values\n", + " self.head = head\n", + " else:\n", + " df = ml.chd.stress_period_data.dataframe\n", + " if self.kperold != ml.kper:\n", + " self.kperold = ml.kper\n", + " self.head = self.head[::-1]\n", + "\n", + " df[\"head\"] = self.head\n", + " ml.chd.stress_period_data.dataframe = df\n", + "\n", + " if callback_step == Callbacks.timestep_end:\n", + " ml = sim.get_model()\n", + " self.update_plot(ml)\n" + ] + }, + { + "cell_type": "markdown", + "id": "ef5cf1d9", + "metadata": {}, + "source": [ + "Run the model using the and supply the `StructuredHeadMonitor`'s `callback` function" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "f2902aff", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Solving: Stress Period 12; Timestep 31\n" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "NORMAL TERMINATION OF SIMULATION\n" + ] + } + ], + "source": [ + "hdmon = StructuredHeadMonitor(layer=0, vmin=70, vmax=95)\n", + "dll = \"libmf6\"\n", + "sim_ws = Path(\"../data/dis_model\")\n", + "run_simulation(dll, sim_ws, hdmon.callback, verbose=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6ba6ce60", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.12" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/notebooks/MODFLOW-API extensions objects.ipynb b/examples/notebooks/MODFLOW-API extensions objects.ipynb new file mode 100644 index 0000000..d6637e1 --- /dev/null +++ b/examples/notebooks/MODFLOW-API extensions objects.ipynb @@ -0,0 +1,2047 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "5acdb8ec", + "metadata": {}, + "source": [ + "# Interacting with MODFLOW-API Interface objects\n", + "\n", + "The purpose of this notebook is to show the MODFLOW-API interface objects and introduce the user to the data types and how to interact with the objects. \n", + "\n", + "**Note**: This notebook shows how to run a model using the modflow-api at the end of the notebook. However, the majority of the notebook is an illustration of how to access and work with the data types that are returned to a user defined callback function. " + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "4b0e6a93", + "metadata": {}, + "outputs": [], + "source": [ + "import modflowapi\n", + "from modflowapi.extensions import ApiSimulation\n", + "from pathlib import Path\n", + "import platform" + ] + }, + { + "cell_type": "markdown", + "id": "9e654316", + "metadata": {}, + "source": [ + "Define the paths to the model and the Modflow shared library" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "66c0f32c", + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "sim_ws = Path(\"../data/dis_model\")\n", + "dll = \"./libmf6\"\n", + "if platform.system().lower() == \"windows\":\n", + " ext = \".dll\"\n", + "elif platform.system().lower() == \"linux\":\n", + " ext = \".so\"\n", + "else:\n", + " ext = \".dylib\"\n", + " \n", + "dll = Path(dll + ext)" + ] + }, + { + "cell_type": "markdown", + "id": "d258b430", + "metadata": {}, + "source": [ + "#### Initializing the API model object\n", + "\n", + "The modflow api allows users to initialize an object that can be used to interact with the model. This processes is done automatically with the `modflowapi.run_model` function call. We're going to initialize an object outside of that call as a demonstration of the interface data objects" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "9c18c3bf", + "metadata": {}, + "outputs": [], + "source": [ + "mf6 = modflowapi.ModflowApi(dll, working_directory=sim_ws)\n", + "mf6.initialize()\n", + "\n", + "# let's advance the model to the first timestep\n", + "dt = mf6.get_time_step()\n", + "mf6.prepare_time_step(dt)" + ] + }, + { + "cell_type": "markdown", + "id": "424925fc", + "metadata": {}, + "source": [ + "## The `ApiSimulation` object \n", + "\n", + "The `ApiSimulation` object is the top level container for the modflowapi interface classes. This container holds methods and other objects that allow the user to access boundary condition pointer data without assembling the specific memory addresses of the modflow data. \n", + "\n", + "Let's take a look at the `ApiSimulation` object" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "b0e83b86", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "\n", + " ApiSimulation object that holds a modflow simulation info and loads\n", + " supported models.\n", + "\n", + " Parameters\n", + " ----------\n", + " mf6 : ModflowApi\n", + " initialized ModflowApi object\n", + " models : dict\n", + " dictionary of model_name: modflowapi.extensions.ApiModel objects\n", + " solutions : dict\n", + " dictionary of solution_id: solution_name\n", + " exchanges : dict\n", + " dictoinary of exchange_name: modflowapi.extensions.ApiExchange objects\n", + " tdis : ApiTdisPackage\n", + " time discretization (TDIS) ScalarPackage\n", + " ats : None or ApiAtsPackage\n", + " adaptive time step ScalarPackage object\n", + " Number of models: 1:\n", + "\ttest_model : \n", + "Simulation level packages include:\n", + "\tSLN: SLN Package: SLN_1 \n", + " Accessible variables include:\n", + " akappa \n", + " amomentum \n", + " breduc \n", + " btol \n", + " droptol \n", + " dvclose \n", + " gamma \n", + " ims_dvclose \n", + " iord \n", + " ipc \n", + " iscl \n", + " mxiter \n", + " niterc \n", + " north \n", + " numtrack \n", + " rclose \n", + " relax \n", + " res_lim \n", + " theta \n", + "\n", + "\tTDIS: TDIS Package: TDIS \n", + " Accessible variables include:\n", + " delt \n", + " itmuni \n", + " kper \n", + " kstp \n", + " nper \n", + " nstp \n", + " perlen \n", + " pertim \n", + " tsmult \n" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "sim = ApiSimulation.load(mf6)\n", + "sim" + ] + }, + { + "cell_type": "markdown", + "id": "65030e7d", + "metadata": {}, + "source": [ + "The simulation object allows the user to access models by name and has a number of handy properties and contains simulation level packages such as `sln`, `tdis`, `ats`, and `exchanges`" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "c6930030", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['test_model']" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "mnames = sim.model_names\n", + "mnames" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "327a1c6e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(0, 0)" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "kstp, kper = sim.kstp, sim.kper\n", + "kstp, kper" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "93c4c417", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "31" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "nstp = sim.nstp\n", + "nstp" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "77c77460", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "SLN Package: SLN_1 \n", + " Accessible variables include:\n", + " akappa \n", + " amomentum \n", + " breduc \n", + " btol \n", + " droptol \n", + " dvclose \n", + " gamma \n", + " ims_dvclose \n", + " iord \n", + " ipc \n", + " iscl \n", + " mxiter \n", + " niterc \n", + " north \n", + " numtrack \n", + " rclose \n", + " relax \n", + " res_lim \n", + " theta " + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "ims = sim.sln\n", + "ims" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "3805e031", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "0.1" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "ims.dvclose" + ] + }, + { + "cell_type": "markdown", + "id": "236d4a25", + "metadata": {}, + "source": [ + "## The `ApiModel` object\n", + "\n", + "`ApiModel` objects are accessed from the `ApiSimulation` object and are a container for packages. These objects allow the user to view which packages are available and access those packages. \n", + "\n", + "The following cells show the main attributes and functions available on the `ApiModel` object" + ] + }, + { + "cell_type": "markdown", + "id": "7e65a3f3", + "metadata": {}, + "source": [ + "Model objects are accessible through the `get_model` function and as attributes on the sim object" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "232c0660", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "TEST_MODEL, 1 Layer, 10 Row, 10 Column model\n", + "Packages accessible include: \n", + " ArrayPackage objects:\n", + " dis: \n", + " npf: \n", + " sto: \n", + " ic: \n", + " ListPackage objects:\n", + " wel_0: \n", + " drn_0: \n", + " rch_0: \n", + " rcha_0: \n", + " evt_0: \n", + " chd_0: \n", + " AdvancedPackage objects:\n", + " buy: \n", + " vsc: \n", + " gnc: \n", + " hfb: \n", + " csub: \n", + " mvr: " + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "model = sim.get_model('test_model')\n", + "model" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "bb9328af", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "TEST_MODEL, 1 Layer, 10 Row, 10 Column model\n", + "Packages accessible include: \n", + " ArrayPackage objects:\n", + " dis: \n", + " npf: \n", + " sto: \n", + " ic: \n", + " ListPackage objects:\n", + " wel_0: \n", + " drn_0: \n", + " rch_0: \n", + " rcha_0: \n", + " evt_0: \n", + " chd_0: \n", + " AdvancedPackage objects:\n", + " buy: \n", + " vsc: \n", + " gnc: \n", + " hfb: \n", + " csub: \n", + " mvr: " + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# approach 2\n", + "model = sim.test_model\n", + "model" + ] + }, + { + "cell_type": "markdown", + "id": "0ddc33e1", + "metadata": {}, + "source": [ + "There are also a number of other functions available including the following:" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "43aaed16", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(1, 10, 10)" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "model.shape" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "07b0b3e2", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "100" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "model.size" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "088578c0", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "1" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "model.solution_id" + ] + }, + { + "cell_type": "markdown", + "id": "4eafd365", + "metadata": {}, + "source": [ + "A list of all package names that are accesible is also available" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "eee1f0a5", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['dis',\n", + " 'npf',\n", + " 'buy',\n", + " 'vsc',\n", + " 'gnc',\n", + " 'hfb',\n", + " 'sto',\n", + " 'csub',\n", + " 'ic',\n", + " 'mvr',\n", + " 'wel_0',\n", + " 'drn_0',\n", + " 'rch_0',\n", + " 'rcha_0',\n", + " 'evt_0',\n", + " 'chd_0']" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "model.package_names" + ] + }, + { + "cell_type": "markdown", + "id": "073bb268", + "metadata": {}, + "source": [ + "## The `ApiPackage` object(s)\n", + "\n", + "Each package is contained in `ApiPackage` container. There are three types depending on the input data. We'll access and take a look at each of the types of `ApiPackage` containers." + ] + }, + { + "cell_type": "markdown", + "id": "79ac8066", + "metadata": {}, + "source": [ + "Packages can be accessed from the `Model` object using `get_package()` or by attribute" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "4914c509", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "RCH Package: RCHA_0" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# example 1: get a package using get_package\n", + "rch = model.get_package(\"rcha_0\")\n", + "rch" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "cbc0aadf", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "WEL Package: WEL_0" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# example 2: get a package by package name attribute\n", + "wel = model.wel_0\n", + "wel" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "1a705679", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[RCH Package: RCH_0, RCH Package: RCHA_0]" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# example 3: get all packages based on a package type\n", + "rch_pkgs = model.rch\n", + "rch_pkgs" + ] + }, + { + "cell_type": "markdown", + "id": "8d59405f", + "metadata": {}, + "source": [ + "### `ListPackage` objects\n", + "\n", + "`ListPackage` objects are the primary object type of stress period data. The exception to this rule is the advanced packages which will be discussed later. \n", + "\n", + "`ListPackage` objects allow users to access stress period data as a numpy recarray or as a pandas dataframe." + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "fbc7ed8a", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "rec.array([((0, 9, 7), 1.00000e-03), ((0, 9, 7), 1.00000e-03),\n", + " ((0, 0, 2), 4.04496e+00), ((0, 0, 3), 4.04496e+00),\n", + " ((0, 0, 4), 4.04496e+00), ((0, 0, 5), 4.04496e+00),\n", + " ((0, 0, 6), 4.04496e+00), ((0, 0, 7), 4.04496e+00),\n", + " ((0, 9, 7), 1.00000e-03), ((0, 9, 7), 1.00000e-03)],\n", + " dtype=[('nodelist', 'O'), ('recharge', '\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
nodelistrecharge
0(0, 9, 7)0.00100
1(0, 9, 7)0.00100
2(0, 0, 2)4.04496
3(0, 0, 3)4.04496
4(0, 0, 4)4.04496
\n", + "" + ], + "text/plain": [ + " nodelist recharge\n", + "0 (0, 9, 7) 0.00100\n", + "1 (0, 9, 7) 0.00100\n", + "2 (0, 0, 2) 4.04496\n", + "3 (0, 0, 3) 4.04496\n", + "4 (0, 0, 4) 4.04496" + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df = rch.stress_period_data.dataframe\n", + "df.head()" + ] + }, + { + "cell_type": "markdown", + "id": "62faee8b", + "metadata": {}, + "source": [ + "### Updating values for `ListPackage` based data\n", + "\n", + "There are multiple ways to update values for `ListPackage` based data. The `.values` and `.dataframe` attributes can be used, or the object can be directly indexed if the user knows the underlying data. Here are some examples" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "0fecf116", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "rec.array([((0, 9, 7), 1.00000e-01), ((0, 9, 7), 1.00000e-03),\n", + " ((0, 0, 2), 4.04496e+00), ((0, 0, 3), 4.04496e+00),\n", + " ((0, 0, 4), 4.04496e+00)],\n", + " dtype=[('nodelist', 'O'), ('recharge', '\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
nodelistrecharge
0(0, 9, 7)0.10000
1(0, 9, 7)10000.00000
2(0, 0, 2)4.04496
3(0, 0, 3)4.04496
4(0, 0, 4)4.04496
\n", + "" + ], + "text/plain": [ + " nodelist recharge\n", + "0 (0, 9, 7) 0.10000\n", + "1 (0, 9, 7) 10000.00000\n", + "2 (0, 0, 2) 4.04496\n", + "3 (0, 0, 3) 4.04496\n", + "4 (0, 0, 4) 4.04496" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df = rch.stress_period_data.dataframe\n", + "df.loc[1, \"recharge\"] = 10000\n", + "rch.stress_period_data.dataframe = df\n", + "\n", + "# show that values have been updated\n", + "df = rch.stress_period_data.dataframe\n", + "df.head()" + ] + }, + { + "cell_type": "markdown", + "id": "fd85083e", + "metadata": {}, + "source": [ + "#### Interfacing directly with the `.stress_period_data` attribute\n", + "\n", + "The `.stress_period_data` attribute returns a container class that interacts with the internal modflow pointers. The data can be adjusted by interacting with `.stress_period_data` in the same fashion as changing data in a numpy recarray. " + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "c1d21b67", + "metadata": {}, + "outputs": [], + "source": [ + "rch.stress_period_data[\"recharge\"] *= 100" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "0a127471", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
nodelistrecharge
0(0, 9, 7)10.000
1(0, 9, 7)1000000.000
2(0, 0, 2)404.496
3(0, 0, 3)404.496
4(0, 0, 4)404.496
\n", + "
" + ], + "text/plain": [ + " nodelist recharge\n", + "0 (0, 9, 7) 10.000\n", + "1 (0, 9, 7) 1000000.000\n", + "2 (0, 0, 2) 404.496\n", + "3 (0, 0, 3) 404.496\n", + "4 (0, 0, 4) 404.496" + ] + }, + "execution_count": 24, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df = rch.stress_period_data.dataframe\n", + "df.head()" + ] + }, + { + "cell_type": "markdown", + "id": "85f6922c", + "metadata": {}, + "source": [ + "#### Adding or removing a boundary condition\n", + "In list packages the user can add and remove specific boundary conditions. Note: if a user adds a boundary condition, such as another well during a stress period, the total number of wells cannot be greater than the wel package's `maxbound` variable. Here's an example" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "4921081e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "nbound=3 maxbound=10\n" + ] + } + ], + "source": [ + "wel = model.wel\n", + "maxbound = wel.maxbound\n", + "nbound = wel.nbound\n", + "print(f\"{nbound=}\", f\"{maxbound=}\")" + ] + }, + { + "cell_type": "markdown", + "id": "9639edaf", + "metadata": {}, + "source": [ + "For the current stress period there are two active wells `nbound=2`, but there can be up to ten `maxbound=10`." + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "a9d7ba04", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "rec.array([((0, 5, 4), -150., 1., 2.), ((0, 1, 2), -100., 1., 2.),\n", + " ((0, 3, 5), -50., 1., 2.)],\n", + " dtype=[('nodelist', 'O'), ('flux', '\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
nodelistfluxTEST1TEST2
0(0, 5, 4)-150.01.02.0
1(0, 1, 2)-100.01.02.0
2(0, 3, 5)-50.01.02.0
3(0, 1, 5)-20.00.01.0
\n", + "" + ], + "text/plain": [ + " nodelist flux TEST1 TEST2\n", + "0 (0, 5, 4) -150.0 1.0 2.0\n", + "1 (0, 1, 2) -100.0 1.0 2.0\n", + "2 (0, 3, 5) -50.0 1.0 2.0\n", + "3 (0, 1, 5) -20.0 0.0 1.0" + ] + }, + "execution_count": 29, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "wel.stress_period_data.dataframe" + ] + }, + { + "cell_type": "markdown", + "id": "42b72baa", + "metadata": {}, + "source": [ + "### `ArrayPackage` objects\n", + "\n", + "The `ArrayPackage` class is used as a container for packages such as `DIS`, `NPF`, and `IC` that do not contain any sort of stress period data. These packages are used primarily to define model connectivity, initial conditions, and hydraulic parameters of the basin. " + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "e9939cae", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "NPF Package: NPF \n", + " Accessible variables include:\n", + " angle1 \n", + " angle2 \n", + " angle3 \n", + " icelltype \n", + " k11 \n", + " k22 \n", + " k33 " + ] + }, + "execution_count": 30, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "npf = model.npf\n", + "npf" + ] + }, + { + "cell_type": "markdown", + "id": "f55215ba", + "metadata": {}, + "source": [ + "For an `ArrayPackage` type object, variable names can be viewed by calling the `.variable_names` property" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "id": "df4e7242", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['angle1', 'angle2', 'angle3', 'icelltype', 'k11', 'k22', 'k33']" + ] + }, + "execution_count": 31, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "npf.variable_names" + ] + }, + { + "cell_type": "markdown", + "id": "93c2006e", + "metadata": {}, + "source": [ + "### Updating values for `ArrayPackage` objects\n", + "\n", + "Two methods are available for accessing and updating data in `ArrayPackage` objects. `get_array()` and `set_array()` methods can be used to get and set data. Arrays can also be accessed as attributes on the object." + ] + }, + { + "cell_type": "markdown", + "id": "0f408dff", + "metadata": {}, + "source": [ + "Using `get_array()` and `set_array()`" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "id": "0d202974", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[[nan, nan, 1., 1., 1., 1., 1., 1., nan, nan],\n", + " [nan, 1., 1., 1., 1., 1., 1., 1., 1., nan],\n", + " [ 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.],\n", + " [ 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.],\n", + " [ 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.],\n", + " [ 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.],\n", + " [ 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.],\n", + " [ 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.],\n", + " [nan, 1., 1., 1., 1., 1., 1., 1., 1., nan],\n", + " [nan, nan, 1., 1., 1., 1., 1., 1., nan, nan]]])" + ] + }, + "execution_count": 32, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "hk = npf.get_array(\"k11\")\n", + "hk" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "17ce597b", + "metadata": {}, + "outputs": [], + "source": [ + "hk[0, 0:5, 0:5] = 50\n", + "npf.set_array(\"k11\", hk)" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "id": "2ed869a9", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[[nan, nan, 50., 50., 50., 1., 1., 1., nan, nan],\n", + " [nan, 50., 50., 50., 50., 1., 1., 1., 1., nan],\n", + " [50., 50., 50., 50., 50., 1., 1., 1., 1., 1.],\n", + " [50., 50., 50., 50., 50., 1., 1., 1., 1., 1.],\n", + " [50., 50., 50., 50., 50., 1., 1., 1., 1., 1.],\n", + " [ 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.],\n", + " [ 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.],\n", + " [ 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.],\n", + " [nan, 1., 1., 1., 1., 1., 1., 1., 1., nan],\n", + " [nan, nan, 1., 1., 1., 1., 1., 1., nan, nan]]])" + ] + }, + "execution_count": 34, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# confirm that the data has been updated\n", + "hk = npf.get_array(\"k11\")\n", + "hk" + ] + }, + { + "cell_type": "markdown", + "id": "7bdbdb1e", + "metadata": {}, + "source": [ + "Getting and setting data by attribute" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "id": "2b589b5f", + "metadata": {}, + "outputs": [], + "source": [ + "# needs an update for inplace operations....\n", + "npf.k33[0, 0:5, 0:5] = 5" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "id": "38e79b05", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[[nan, nan, 5., 5., 5., 1., 1., 1., nan, nan],\n", + " [nan, 5., 5., 5., 5., 1., 1., 1., 1., nan],\n", + " [ 5., 5., 5., 5., 5., 1., 1., 1., 1., 1.],\n", + " [ 5., 5., 5., 5., 5., 1., 1., 1., 1., 1.],\n", + " [ 5., 5., 5., 5., 5., 1., 1., 1., 1., 1.],\n", + " [ 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.],\n", + " [ 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.],\n", + " [ 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.],\n", + " [nan, 1., 1., 1., 1., 1., 1., 1., 1., nan],\n", + " [nan, nan, 1., 1., 1., 1., 1., 1., nan, nan]]])" + ] + }, + "execution_count": 36, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# confirm that the data has been updated\n", + "npf.k33.values" + ] + }, + { + "cell_type": "markdown", + "id": "f245911d", + "metadata": {}, + "source": [ + "## Accessing \"advanced variables\"\n", + "\n", + "Advanced variables in this context are variables that would not normally need to be accessed by the user, and in many cases changes to these variables would cause the Modflow simulation to do unexpected things. " + ] + }, + { + "cell_type": "markdown", + "id": "ff9a706b", + "metadata": {}, + "source": [ + "For each package object a list of avanced variables can be returned by calling the `advanced_vars` attribute" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "id": "4c943166", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['package_type',\n", + " 'id',\n", + " 'inunit',\n", + " 'iout',\n", + " 'inewton',\n", + " 'iasym',\n", + " 'iprpak',\n", + " 'iprflow',\n", + " 'ipakcb',\n", + " 'ionper',\n", + " 'lastonper',\n", + " 'listlabel',\n", + " 'isadvpak',\n", + " 'ibcnum',\n", + " 'ncolbnd',\n", + " 'iscloc',\n", + " 'inamedbound',\n", + " 'iauxmultcol',\n", + " 'inobspkg',\n", + " 'imover',\n", + " 'ivsc',\n", + " 'npakeq',\n", + " 'ioffset',\n", + " 'auxname',\n", + " 'iflowred',\n", + " 'flowred',\n", + " 'ioutafrcsv',\n", + " 'noupdateauxvar',\n", + " 'bound',\n", + " 'condinput',\n", + " 'hcof',\n", + " 'rhs',\n", + " 'simvals',\n", + " 'simtomvr',\n", + " 'boundname',\n", + " 'boundname_cst']" + ] + }, + "execution_count": 37, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "wel = model.wel_0\n", + "wel.advanced_vars" + ] + }, + { + "cell_type": "markdown", + "id": "dcd0e792", + "metadata": {}, + "source": [ + "The user can access and change these values, _at their own risk_, using the `.get_advanced_var()` and `.set_advanced_var()` methods. Data is returned to the user in the internal modflowapi structure. " + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "id": "5a8ee821", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([1])" + ] + }, + "execution_count": 38, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "wel.get_advanced_var(\"ibcnum\")" + ] + }, + { + "cell_type": "markdown", + "id": "b034954d", + "metadata": {}, + "source": [ + "### Advanced Packages\n", + "\n", + "Certain packages only support accessing data through the `.get_advanced_var()` and `.set_advanced_var()` methods. These packages, are sometimes refered to as \"advanced packages\" and include: BUY, CSUB, GNC, HFB, MAW, MVR, SFR, and UZF. " + ] + }, + { + "cell_type": "markdown", + "id": "2e2d960a", + "metadata": {}, + "source": [ + "-------" + ] + }, + { + "cell_type": "markdown", + "id": "853b6390", + "metadata": {}, + "source": [ + "Let's close the existing modflowapi shared library object and look at an example of how this is all used in practice" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "id": "6896d260", + "metadata": {}, + "outputs": [], + "source": [ + "mf6.finalize()" + ] + }, + { + "cell_type": "markdown", + "id": "27d3baa1", + "metadata": {}, + "source": [ + "# Putting it all together and running a modflowapi simulation\n", + "\n", + "To run a simulation using the built in modflowapi runner the user needs to create a function that will receive callbacks at different steps in the simulation run. For the remainder of this notebook, we'll show how to create a callback function and use it with the `modflowapi.run_simulation()` method." + ] + }, + { + "cell_type": "markdown", + "id": "4404b2c2", + "metadata": {}, + "source": [ + "## Create a callback function for adjusting model data\n", + "\n", + "The callback function allows users to wrap function that updates the modflow model at different steps. The `modflowapi.Callbacks` object allows users to find the particular solution step that they are currently in. `modflowapi.Callbacks` includes:\n", + "\n", + " - `Callbacks.initalize`: the initialize callback sends loaded simulation data back to the user to make adjustments before the model begins solving. This callback only occurs once at the beginning of the MODFLOW6 simulation\n", + " - `Callbacks.stress_period_start`: the stress_period_start callback sends simulation data for each solution group to the user to make adjustments to stress packages at the beginning of each stress period.\n", + " - `Callbacks.stress_period_end`: the stress_period_end callback sends simulation data for each solution group to the user at the end of each stress period. This can be useful for writing custom output and coupling models\n", + " - `Callbacks.timestep_start`: the timestep_start callback sends simulation data for each solution group to the user to make adjustments to stress packages at the beginning of each timestep.\n", + " - `Callbacks.timestep_end`: the timestep_end callback sends simulation data for each solution group to the user at the end of each timestep. This can be useful for writing custom output and coupling models\n", + " - `Callbacks.iteration_start`: the iteration_start callback sends simulation data for each solution group to the user to make adjustments to stress packages at the beginning of each outer solution iteration.\n", + " - `Callbacks.iteration_end`: the iteration_end callback sends simulation data for each solution group to the user to make adjustments to stress packages and check values of stress packages at the end of each outer solution iteration.\n", + " - `Callbacks.finalize`: the finalize callback is useful for finalizing models coupled with the modflowapi.\n", + " \n", + "The user can use any or all of these callbacks within their callback function" + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "id": "c2783828", + "metadata": {}, + "outputs": [], + "source": [ + "from modflowapi import Callbacks" + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "id": "395327da", + "metadata": {}, + "outputs": [], + "source": [ + "def callback_function(sim, callback_step):\n", + " \"\"\"\n", + " A demonstration function that dynamically adjusts recharge\n", + " and pumping in a modflow-6 model through the MODFLOW-API\n", + " \n", + " Parameters\n", + " ----------\n", + " sim : modflowapi.Simulation\n", + " A simulation object for the solution group that is \n", + " currently being solved\n", + " callback_step : enumeration\n", + " modflowapi.Callbacks enumeration object that indicates\n", + " the part of the solution modflow is currently in.\n", + " \"\"\"\n", + " ml = sim.test_model\n", + " if callback_step == Callbacks.initialize:\n", + " print(sim.models)\n", + " \n", + " if callback_step == Callbacks.stress_period_start:\n", + " # adjust recharge for stress periods 1 through 7\n", + " if sim.kper <= 6:\n", + " rcha = ml.rcha_0\n", + " spd = rcha.stress_period_data\n", + " print(f\"updating recharge: stress_period={ml.kper}\")\n", + " spd[\"recharge\"] += 0.40 * sim.kper\n", + " \n", + " \n", + " if callback_step == Callbacks.timestep_start:\n", + " print(f\"updating wel flux: stress_period={ml.kper}, timestep={ml.kstp}\")\n", + " ml.wel.stress_period_data[\"flux\"] -= ml.kstp * 1.5\n", + " \n", + " if callback_step == Callbacks.iteration_start:\n", + " # we can implement complex solutions to boundary conditions here!\n", + " pass\n", + " " + ] + }, + { + "cell_type": "markdown", + "id": "b751eb41", + "metadata": {}, + "source": [ + "The callback function is then passed to `modflowapi.run_simulation`" + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "id": "0878e5b6", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[TEST_MODEL, 1 Layer, 10 Row, 10 Column model\n", + "Packages accessible include: \n", + " ArrayPackage objects:\n", + " dis: \n", + " npf: \n", + " sto: \n", + " ic: \n", + " ListPackage objects:\n", + " wel_0: \n", + " drn_0: \n", + " rch_0: \n", + " rcha_0: \n", + " evt_0: \n", + " chd_0: \n", + " AdvancedPackage objects:\n", + " buy: \n", + " vsc: \n", + " gnc: \n", + " hfb: \n", + " csub: \n", + " mvr: \n", + "]\n", + "updating recharge: stress_period=0\n", + "updating wel flux: stress_period=0, timestep=0\n", + "updating wel flux: stress_period=0, timestep=1\n", + "updating wel flux: stress_period=0, timestep=2\n", + "updating wel flux: stress_period=0, timestep=3\n", + "updating wel flux: stress_period=0, timestep=4\n", + "updating wel flux: stress_period=0, timestep=5\n", + "updating wel flux: stress_period=0, timestep=6\n", + "updating wel flux: stress_period=0, timestep=7\n", + "updating wel flux: stress_period=0, timestep=8\n", + "updating wel flux: stress_period=0, timestep=9\n", + "updating wel flux: stress_period=0, timestep=10\n", + "updating wel flux: stress_period=0, timestep=11\n", + "updating wel flux: stress_period=0, timestep=12\n", + "updating wel flux: stress_period=0, timestep=13\n", + "updating wel flux: stress_period=0, timestep=14\n", + "updating wel flux: stress_period=0, timestep=15\n", + "updating wel flux: stress_period=0, timestep=16\n", + "updating wel flux: stress_period=0, timestep=17\n", + "updating wel flux: stress_period=0, timestep=18\n", + "updating wel flux: stress_period=0, timestep=19\n", + "updating wel flux: stress_period=0, timestep=20\n", + "updating wel flux: stress_period=0, timestep=21\n", + "updating wel flux: stress_period=0, timestep=22\n", + "updating wel flux: stress_period=0, timestep=23\n", + "updating wel flux: stress_period=0, timestep=24\n", + "updating wel flux: stress_period=0, timestep=25\n", + "updating wel flux: stress_period=0, timestep=26\n", + "updating wel flux: stress_period=0, timestep=27\n", + "updating wel flux: stress_period=0, timestep=28\n", + "updating wel flux: stress_period=0, timestep=29\n", + "updating wel flux: stress_period=0, timestep=30\n", + "updating recharge: stress_period=1\n", + "updating wel flux: stress_period=1, timestep=0\n", + "updating wel flux: stress_period=1, timestep=1\n", + "updating wel flux: stress_period=1, timestep=2\n", + "updating wel flux: stress_period=1, timestep=3\n", + "updating wel flux: stress_period=1, timestep=4\n", + "updating wel flux: stress_period=1, timestep=5\n", + "updating wel flux: stress_period=1, timestep=6\n", + "updating wel flux: stress_period=1, timestep=7\n", + "updating wel flux: stress_period=1, timestep=8\n", + "updating wel flux: stress_period=1, timestep=9\n", + "updating wel flux: stress_period=1, timestep=10\n", + "updating wel flux: stress_period=1, timestep=11\n", + "updating wel flux: stress_period=1, timestep=12\n", + "updating wel flux: stress_period=1, timestep=13\n", + "updating wel flux: stress_period=1, timestep=14\n", + "updating wel flux: stress_period=1, timestep=15\n", + "updating wel flux: stress_period=1, timestep=16\n", + "updating wel flux: stress_period=1, timestep=17\n", + "updating wel flux: stress_period=1, timestep=18\n", + "updating wel flux: stress_period=1, timestep=19\n", + "updating wel flux: stress_period=1, timestep=20\n", + "updating wel flux: stress_period=1, timestep=21\n", + "updating wel flux: stress_period=1, timestep=22\n", + "updating wel flux: stress_period=1, timestep=23\n", + "updating wel flux: stress_period=1, timestep=24\n", + "updating wel flux: stress_period=1, timestep=25\n", + "updating wel flux: stress_period=1, timestep=26\n", + "updating wel flux: stress_period=1, timestep=27\n", + "updating recharge: stress_period=2\n", + "updating wel flux: stress_period=2, timestep=0\n", + "updating wel flux: stress_period=2, timestep=1\n", + "updating wel flux: stress_period=2, timestep=2\n", + "updating wel flux: stress_period=2, timestep=3\n", + "updating wel flux: stress_period=2, timestep=4\n", + "updating wel flux: stress_period=2, timestep=5\n", + "updating wel flux: stress_period=2, timestep=6\n", + "updating wel flux: stress_period=2, timestep=7\n", + "updating wel flux: stress_period=2, timestep=8\n", + "updating wel flux: stress_period=2, timestep=9\n", + "updating wel flux: stress_period=2, timestep=10\n", + "updating wel flux: stress_period=2, timestep=11\n", + "updating wel flux: stress_period=2, timestep=12\n", + "updating wel flux: stress_period=2, timestep=13\n", + "updating wel flux: stress_period=2, timestep=14\n", + "updating wel flux: stress_period=2, timestep=15\n", + "updating wel flux: stress_period=2, timestep=16\n", + "updating wel flux: stress_period=2, timestep=17\n", + "updating wel flux: stress_period=2, timestep=18\n", + "updating wel flux: stress_period=2, timestep=19\n", + "updating wel flux: stress_period=2, timestep=20\n", + "updating wel flux: stress_period=2, timestep=21\n", + "updating wel flux: stress_period=2, timestep=22\n", + "updating wel flux: stress_period=2, timestep=23\n", + "updating wel flux: stress_period=2, timestep=24\n", + "updating wel flux: stress_period=2, timestep=25\n", + "updating wel flux: stress_period=2, timestep=26\n", + "updating wel flux: stress_period=2, timestep=27\n", + "updating wel flux: stress_period=2, timestep=28\n", + "updating wel flux: stress_period=2, timestep=29\n", + "updating wel flux: stress_period=2, timestep=30\n", + "updating recharge: stress_period=3\n", + "updating wel flux: stress_period=3, timestep=0\n", + "updating wel flux: stress_period=3, timestep=1\n", + "updating wel flux: stress_period=3, timestep=2\n", + "updating wel flux: stress_period=3, timestep=3\n", + "updating wel flux: stress_period=3, timestep=4\n", + "updating wel flux: stress_period=3, timestep=5\n", + "updating wel flux: stress_period=3, timestep=6\n", + "updating wel flux: stress_period=3, timestep=7\n", + "updating wel flux: stress_period=3, timestep=8\n", + "updating wel flux: stress_period=3, timestep=9\n", + "updating wel flux: stress_period=3, timestep=10\n", + "updating wel flux: stress_period=3, timestep=11\n", + "updating wel flux: stress_period=3, timestep=12\n", + "updating wel flux: stress_period=3, timestep=13\n", + "updating wel flux: stress_period=3, timestep=14\n", + "updating wel flux: stress_period=3, timestep=15\n", + "updating wel flux: stress_period=3, timestep=16\n", + "updating wel flux: stress_period=3, timestep=17\n", + "updating wel flux: stress_period=3, timestep=18\n", + "updating wel flux: stress_period=3, timestep=19\n", + "updating wel flux: stress_period=3, timestep=20\n", + "updating wel flux: stress_period=3, timestep=21\n", + "updating wel flux: stress_period=3, timestep=22\n", + "updating wel flux: stress_period=3, timestep=23\n", + "updating wel flux: stress_period=3, timestep=24\n", + "updating wel flux: stress_period=3, timestep=25\n", + "updating wel flux: stress_period=3, timestep=26\n", + "updating wel flux: stress_period=3, timestep=27\n", + "updating wel flux: stress_period=3, timestep=28\n", + "updating wel flux: stress_period=3, timestep=29\n", + "updating recharge: stress_period=4\n", + "updating wel flux: stress_period=4, timestep=0\n", + "updating wel flux: stress_period=4, timestep=1\n", + "updating wel flux: stress_period=4, timestep=2\n", + "updating wel flux: stress_period=4, timestep=3\n", + "updating wel flux: stress_period=4, timestep=4\n", + "updating wel flux: stress_period=4, timestep=5\n", + "updating wel flux: stress_period=4, timestep=6\n", + "updating wel flux: stress_period=4, timestep=7\n", + "updating wel flux: stress_period=4, timestep=8\n", + "updating wel flux: stress_period=4, timestep=9\n", + "updating wel flux: stress_period=4, timestep=10\n", + "updating wel flux: stress_period=4, timestep=11\n", + "updating wel flux: stress_period=4, timestep=12\n", + "updating wel flux: stress_period=4, timestep=13\n", + "updating wel flux: stress_period=4, timestep=14\n", + "updating wel flux: stress_period=4, timestep=15\n", + "updating wel flux: stress_period=4, timestep=16\n", + "updating wel flux: stress_period=4, timestep=17\n", + "updating wel flux: stress_period=4, timestep=18\n", + "updating wel flux: stress_period=4, timestep=19\n", + "updating wel flux: stress_period=4, timestep=20\n", + "updating wel flux: stress_period=4, timestep=21\n", + "updating wel flux: stress_period=4, timestep=22\n", + "updating wel flux: stress_period=4, timestep=23\n", + "updating wel flux: stress_period=4, timestep=24\n", + "updating wel flux: stress_period=4, timestep=25\n", + "updating wel flux: stress_period=4, timestep=26\n", + "updating wel flux: stress_period=4, timestep=27\n", + "updating wel flux: stress_period=4, timestep=28\n", + "updating wel flux: stress_period=4, timestep=29\n", + "updating wel flux: stress_period=4, timestep=30\n", + "updating recharge: stress_period=5\n", + "updating wel flux: stress_period=5, timestep=0\n", + "updating wel flux: stress_period=5, timestep=1\n", + "updating wel flux: stress_period=5, timestep=2\n", + "updating wel flux: stress_period=5, timestep=3\n", + "updating wel flux: stress_period=5, timestep=4\n", + "updating wel flux: stress_period=5, timestep=5\n", + "updating wel flux: stress_period=5, timestep=6\n", + "updating wel flux: stress_period=5, timestep=7\n", + "updating wel flux: stress_period=5, timestep=8\n", + "updating wel flux: stress_period=5, timestep=9\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "updating wel flux: stress_period=5, timestep=10\n", + "updating wel flux: stress_period=5, timestep=11\n", + "updating wel flux: stress_period=5, timestep=12\n", + "updating wel flux: stress_period=5, timestep=13\n", + "updating wel flux: stress_period=5, timestep=14\n", + "updating wel flux: stress_period=5, timestep=15\n", + "updating wel flux: stress_period=5, timestep=16\n", + "updating wel flux: stress_period=5, timestep=17\n", + "updating wel flux: stress_period=5, timestep=18\n", + "updating wel flux: stress_period=5, timestep=19\n", + "updating wel flux: stress_period=5, timestep=20\n", + "updating wel flux: stress_period=5, timestep=21\n", + "updating wel flux: stress_period=5, timestep=22\n", + "updating wel flux: stress_period=5, timestep=23\n", + "updating wel flux: stress_period=5, timestep=24\n", + "updating wel flux: stress_period=5, timestep=25\n", + "updating wel flux: stress_period=5, timestep=26\n", + "updating wel flux: stress_period=5, timestep=27\n", + "updating wel flux: stress_period=5, timestep=28\n", + "updating wel flux: stress_period=5, timestep=29\n", + "updating recharge: stress_period=6\n", + "updating wel flux: stress_period=6, timestep=0\n", + "updating wel flux: stress_period=6, timestep=1\n", + "updating wel flux: stress_period=6, timestep=2\n", + "updating wel flux: stress_period=6, timestep=3\n", + "updating wel flux: stress_period=6, timestep=4\n", + "updating wel flux: stress_period=6, timestep=5\n", + "updating wel flux: stress_period=6, timestep=6\n", + "updating wel flux: stress_period=6, timestep=7\n", + "updating wel flux: stress_period=6, timestep=8\n", + "updating wel flux: stress_period=6, timestep=9\n", + "updating wel flux: stress_period=6, timestep=10\n", + "updating wel flux: stress_period=6, timestep=11\n", + "updating wel flux: stress_period=6, timestep=12\n", + "updating wel flux: stress_period=6, timestep=13\n", + "updating wel flux: stress_period=6, timestep=14\n", + "updating wel flux: stress_period=6, timestep=15\n", + "updating wel flux: stress_period=6, timestep=16\n", + "updating wel flux: stress_period=6, timestep=17\n", + "updating wel flux: stress_period=6, timestep=18\n", + "updating wel flux: stress_period=6, timestep=19\n", + "updating wel flux: stress_period=6, timestep=20\n", + "updating wel flux: stress_period=6, timestep=21\n", + "updating wel flux: stress_period=6, timestep=22\n", + "updating wel flux: stress_period=6, timestep=23\n", + "updating wel flux: stress_period=6, timestep=24\n", + "updating wel flux: stress_period=6, timestep=25\n", + "updating wel flux: stress_period=6, timestep=26\n", + "updating wel flux: stress_period=6, timestep=27\n", + "updating wel flux: stress_period=6, timestep=28\n", + "updating wel flux: stress_period=6, timestep=29\n", + "updating wel flux: stress_period=6, timestep=30\n", + "updating wel flux: stress_period=7, timestep=0\n", + "updating wel flux: stress_period=7, timestep=1\n", + "updating wel flux: stress_period=7, timestep=2\n", + "updating wel flux: stress_period=7, timestep=3\n", + "updating wel flux: stress_period=7, timestep=4\n", + "updating wel flux: stress_period=7, timestep=5\n", + "updating wel flux: stress_period=7, timestep=6\n", + "updating wel flux: stress_period=7, timestep=7\n", + "updating wel flux: stress_period=7, timestep=8\n", + "updating wel flux: stress_period=7, timestep=9\n", + "updating wel flux: stress_period=7, timestep=10\n", + "updating wel flux: stress_period=7, timestep=11\n", + "updating wel flux: stress_period=7, timestep=12\n", + "updating wel flux: stress_period=7, timestep=13\n", + "updating wel flux: stress_period=7, timestep=14\n", + "updating wel flux: stress_period=7, timestep=15\n", + "updating wel flux: stress_period=7, timestep=16\n", + "updating wel flux: stress_period=7, timestep=17\n", + "updating wel flux: stress_period=7, timestep=18\n", + "updating wel flux: stress_period=7, timestep=19\n", + "updating wel flux: stress_period=7, timestep=20\n", + "updating wel flux: stress_period=7, timestep=21\n", + "updating wel flux: stress_period=7, timestep=22\n", + "updating wel flux: stress_period=7, timestep=23\n", + "updating wel flux: stress_period=7, timestep=24\n", + "updating wel flux: stress_period=7, timestep=25\n", + "updating wel flux: stress_period=7, timestep=26\n", + "updating wel flux: stress_period=7, timestep=27\n", + "updating wel flux: stress_period=7, timestep=28\n", + "updating wel flux: stress_period=7, timestep=29\n", + "updating wel flux: stress_period=7, timestep=30\n", + "updating wel flux: stress_period=8, timestep=0\n", + "updating wel flux: stress_period=8, timestep=1\n", + "updating wel flux: stress_period=8, timestep=2\n", + "updating wel flux: stress_period=8, timestep=3\n", + "updating wel flux: stress_period=8, timestep=4\n", + "updating wel flux: stress_period=8, timestep=5\n", + "updating wel flux: stress_period=8, timestep=6\n", + "updating wel flux: stress_period=8, timestep=7\n", + "updating wel flux: stress_period=8, timestep=8\n", + "updating wel flux: stress_period=8, timestep=9\n", + "updating wel flux: stress_period=8, timestep=10\n", + "updating wel flux: stress_period=8, timestep=11\n", + "updating wel flux: stress_period=8, timestep=12\n", + "updating wel flux: stress_period=8, timestep=13\n", + "updating wel flux: stress_period=8, timestep=14\n", + "updating wel flux: stress_period=8, timestep=15\n", + "updating wel flux: stress_period=8, timestep=16\n", + "updating wel flux: stress_period=8, timestep=17\n", + "updating wel flux: stress_period=8, timestep=18\n", + "updating wel flux: stress_period=8, timestep=19\n", + "updating wel flux: stress_period=8, timestep=20\n", + "updating wel flux: stress_period=8, timestep=21\n", + "updating wel flux: stress_period=8, timestep=22\n", + "updating wel flux: stress_period=8, timestep=23\n", + "updating wel flux: stress_period=8, timestep=24\n", + "updating wel flux: stress_period=8, timestep=25\n", + "updating wel flux: stress_period=8, timestep=26\n", + "updating wel flux: stress_period=8, timestep=27\n", + "updating wel flux: stress_period=8, timestep=28\n", + "updating wel flux: stress_period=8, timestep=29\n", + "updating wel flux: stress_period=9, timestep=0\n", + "updating wel flux: stress_period=9, timestep=1\n", + "updating wel flux: stress_period=9, timestep=2\n", + "updating wel flux: stress_period=9, timestep=3\n", + "updating wel flux: stress_period=9, timestep=4\n", + "updating wel flux: stress_period=9, timestep=5\n", + "updating wel flux: stress_period=9, timestep=6\n", + "updating wel flux: stress_period=9, timestep=7\n", + "updating wel flux: stress_period=9, timestep=8\n", + "updating wel flux: stress_period=9, timestep=9\n", + "updating wel flux: stress_period=9, timestep=10\n", + "updating wel flux: stress_period=9, timestep=11\n", + "updating wel flux: stress_period=9, timestep=12\n", + "updating wel flux: stress_period=9, timestep=13\n", + "updating wel flux: stress_period=9, timestep=14\n", + "updating wel flux: stress_period=9, timestep=15\n", + "updating wel flux: stress_period=9, timestep=16\n", + "updating wel flux: stress_period=9, timestep=17\n", + "updating wel flux: stress_period=9, timestep=18\n", + "updating wel flux: stress_period=9, timestep=19\n", + "updating wel flux: stress_period=9, timestep=20\n", + "updating wel flux: stress_period=9, timestep=21\n", + "updating wel flux: stress_period=9, timestep=22\n", + "updating wel flux: stress_period=9, timestep=23\n", + "updating wel flux: stress_period=9, timestep=24\n", + "updating wel flux: stress_period=9, timestep=25\n", + "updating wel flux: stress_period=9, timestep=26\n", + "updating wel flux: stress_period=9, timestep=27\n", + "updating wel flux: stress_period=9, timestep=28\n", + "updating wel flux: stress_period=9, timestep=29\n", + "updating wel flux: stress_period=9, timestep=30\n", + "updating wel flux: stress_period=10, timestep=0\n", + "updating wel flux: stress_period=10, timestep=1\n", + "updating wel flux: stress_period=10, timestep=2\n", + "updating wel flux: stress_period=10, timestep=3\n", + "updating wel flux: stress_period=10, timestep=4\n", + "updating wel flux: stress_period=10, timestep=5\n", + "updating wel flux: stress_period=10, timestep=6\n", + "updating wel flux: stress_period=10, timestep=7\n", + "updating wel flux: stress_period=10, timestep=8\n", + "updating wel flux: stress_period=10, timestep=9\n", + "updating wel flux: stress_period=10, timestep=10\n", + "updating wel flux: stress_period=10, timestep=11\n", + "updating wel flux: stress_period=10, timestep=12\n", + "updating wel flux: stress_period=10, timestep=13\n", + "updating wel flux: stress_period=10, timestep=14\n", + "updating wel flux: stress_period=10, timestep=15\n", + "updating wel flux: stress_period=10, timestep=16\n", + "updating wel flux: stress_period=10, timestep=17\n", + "updating wel flux: stress_period=10, timestep=18\n", + "updating wel flux: stress_period=10, timestep=19\n", + "updating wel flux: stress_period=10, timestep=20\n", + "updating wel flux: stress_period=10, timestep=21\n", + "updating wel flux: stress_period=10, timestep=22\n", + "updating wel flux: stress_period=10, timestep=23\n", + "updating wel flux: stress_period=10, timestep=24\n", + "updating wel flux: stress_period=10, timestep=25\n", + "updating wel flux: stress_period=10, timestep=26\n", + "updating wel flux: stress_period=10, timestep=27\n", + "updating wel flux: stress_period=10, timestep=28\n", + "updating wel flux: stress_period=10, timestep=29\n", + "updating wel flux: stress_period=11, timestep=0\n", + "updating wel flux: stress_period=11, timestep=1\n", + "updating wel flux: stress_period=11, timestep=2\n", + "updating wel flux: stress_period=11, timestep=3\n", + "updating wel flux: stress_period=11, timestep=4\n", + "updating wel flux: stress_period=11, timestep=5\n", + "updating wel flux: stress_period=11, timestep=6\n", + "updating wel flux: stress_period=11, timestep=7\n", + "updating wel flux: stress_period=11, timestep=8\n", + "updating wel flux: stress_period=11, timestep=9\n", + "updating wel flux: stress_period=11, timestep=10\n", + "updating wel flux: stress_period=11, timestep=11\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "updating wel flux: stress_period=11, timestep=12\n", + "updating wel flux: stress_period=11, timestep=13\n", + "updating wel flux: stress_period=11, timestep=14\n", + "updating wel flux: stress_period=11, timestep=15\n", + "updating wel flux: stress_period=11, timestep=16\n", + "updating wel flux: stress_period=11, timestep=17\n", + "updating wel flux: stress_period=11, timestep=18\n", + "updating wel flux: stress_period=11, timestep=19\n", + "updating wel flux: stress_period=11, timestep=20\n", + "updating wel flux: stress_period=11, timestep=21\n", + "updating wel flux: stress_period=11, timestep=22\n", + "updating wel flux: stress_period=11, timestep=23\n", + "updating wel flux: stress_period=11, timestep=24\n", + "updating wel flux: stress_period=11, timestep=25\n", + "updating wel flux: stress_period=11, timestep=26\n", + "updating wel flux: stress_period=11, timestep=27\n", + "updating wel flux: stress_period=11, timestep=28\n", + "updating wel flux: stress_period=11, timestep=29\n", + "updating wel flux: stress_period=11, timestep=30\n", + "NORMAL TERMINATION OF SIMULATION\n" + ] + } + ], + "source": [ + "modflowapi.run_simulation(dll, sim_ws, callback_function, verbose=False)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bc1e31af", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.12" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/modflowapi/version.py b/modflowapi/version.py index 2f94b78..fd729ae 100644 --- a/modflowapi/version.py +++ b/modflowapi/version.py @@ -1,6 +1,6 @@ -# version file for modflowapi +# modflowapi version file automatically created using update_version.py on April 24, 2023 15:45:45 major = 0 minor = 1 -micro = 0 -__version__ = f"{major}.{minor}.{micro}" +micro = 1 +__version__ = f'{major}.{minor}.{micro}' diff --git a/scripts/update_version.py b/scripts/update_version.py new file mode 100644 index 0000000..e3aa0fe --- /dev/null +++ b/scripts/update_version.py @@ -0,0 +1,152 @@ +import argparse +import textwrap +from datetime import datetime +from enum import Enum +from os import PathLike +from pathlib import Path +from typing import NamedTuple + +from filelock import FileLock + +_project_name = "modflowapi" +_project_root_path = Path(__file__).parent.parent +_version_py_path = _project_root_path / "modflowapi" / "version.py" +_citation_cff_path = _project_root_path / "CITATION.cff" + + +class Version(NamedTuple): + """Semantic version number""" + + major: int = 0 + minor: int = 0 + patch: int = 0 + + def __repr__(self): + return f"{self.major}.{self.minor}.{self.patch}" + + @classmethod + def from_string(cls, version: str) -> "Version": + t = version.split(".") + + vmajor = int(t[0]) + vminor = int(t[1]) + vpatch = int(t[2]) + + return cls(major=vmajor, minor=vminor, patch=vpatch) + + @classmethod + def from_file(cls, path: PathLike) -> "Version": + lines = [ + line.rstrip("\n") + for line in open(Path(path).expanduser().absolute(), "r") + ] + vmajor = vminor = vpatch = None + for line in lines: + line = line.strip() + if not any(line): + continue + + def get_ver(l): + return l.split("=")[1] + + if "__version__" not in line: + if "major" in line: + vmajor = int(get_ver(line)) + elif "minor" in line: + vminor = int(get_ver(line)) + elif "patch" in line or "micro" in line: + vpatch = int(get_ver(line)) + + assert ( + vmajor is not None and vminor is not None and vpatch is not None + ), "version string must follow semantic version format: major.minor.patch" + return cls(major=vmajor, minor=vminor, patch=vpatch) + + +_initial_version = Version(0, 0, 1) +_current_version = Version.from_file(_version_py_path) + + +def update_version_py(timestamp: datetime, version: Version): + with open(_version_py_path, "w") as f: + f.write( + f"# {_project_name} version file automatically created using " + f"{Path(__file__).name} on {timestamp:%B %d, %Y %H:%M:%S}\n\n" + ) + f.write(f"major = {version.major}\n") + f.write(f"minor = {version.minor}\n") + f.write(f"micro = {version.patch}\n") + f.write("__version__ = f'{major}.{minor}.{micro}'\n") + print(f"Updated {_version_py_path} to version {version}") + +def update_citation_cff(timestamp: datetime, version: Version): + lines = open(_citation_cff_path, "r").readlines() + with open(_citation_cff_path, "w") as f: + for line in lines: + if line.startswith("version:"): + line = f"version: {version}\n" + f.write(line) + print(f"Updated {_citation_cff_path} to version {version}") + +def update_version( + timestamp: datetime = datetime.now(), + version: Version = None, +): + lock_path = Path(_version_py_path.name + ".lock") + try: + lock = FileLock(lock_path) + previous = Version.from_file(_version_py_path) + version = ( + version + if version + else Version(previous.major, previous.minor, previous.patch) + ) + + with lock: + update_version_py(timestamp, version) + update_citation_cff(timestamp, version) + finally: + try: + lock_path.unlink() + except: + pass + + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + prog=f"Update {_project_name} version", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=textwrap.dedent( + """\ + Update version information stored in version.txt in the project root, + as well as several other files in the repository. If --version is not + provided, the version number will not be changed. A file lock is held + to synchronize file access. The version tag must comply with standard + '..' format conventions for semantic versioning. + """ + ), + ) + parser.add_argument( + "-v", + "--version", + required=False, + help="Specify the release version", + ) + parser.add_argument( + "-g", + "--get", + required=False, + action="store_true", + help="Just get the current version number, don't update anything (defaults to false)", + ) + args = parser.parse_args() + + if args.get: + print(_current_version) + else: + update_version( + timestamp=datetime.now(), + version=Version.from_string(args.version) + if args.version + else _current_version, + )