Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 24 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,18 @@ on:
push:
pull_request:
branches: [main, develop]
# the release workflow calls this to test a release branch, which a bot
# pushes and so does not raise a push or pull_request event of its own
workflow_call:
inputs:
ref:
description: 'Ref to test. Defaults to the ref that triggered the workflow.'
required: false
type: string
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
# distinct from the calling workflow's group, so a called run cannot cancel
# the release that started it
group: ${{ github.workflow }}-${{ inputs.ref || github.ref }}-tests
cancel-in-progress: true
jobs:

Expand All @@ -23,6 +33,8 @@ jobs:

- name: Checkout repo
uses: actions/checkout@v7
with:
ref: ${{ inputs.ref }}

- name: Setup uv
uses: astral-sh/setup-uv@v7
Expand All @@ -44,6 +56,8 @@ jobs:
steps:
- name: Checkout repo
uses: actions/checkout@v7
with:
ref: ${{ inputs.ref }}

- name: Setup uv
uses: astral-sh/setup-uv@v7
Expand All @@ -67,13 +81,15 @@ jobs:
fail-fast: false
matrix:
os: [ ubuntu-latest, macos-latest, windows-latest ]
python-version: [ "3.11", "3.12", "3.13", "3.14" ]
python-version: [ "3.12", "3.13", "3.14" ]
defaults:
run:
shell: bash
steps:
- name: Checkout repo
uses: actions/checkout@v7
with:
ref: ${{ inputs.ref }}

- name: Setup uv
uses: astral-sh/setup-uv@v7
Expand Down Expand Up @@ -118,13 +134,15 @@ jobs:
fail-fast: false
matrix:
os: [ ubuntu-latest, windows-latest ]
python-version: [ "3.11", "3.12", "3.13", "3.14" ]
python-version: [ "3.12", "3.13", "3.14" ]
defaults:
run:
shell: bash
steps:
- name: Checkout repo
uses: actions/checkout@v7
with:
ref: ${{ inputs.ref }}

- name: Setup uv
uses: astral-sh/setup-uv@v7
Expand Down Expand Up @@ -171,13 +189,15 @@ jobs:
fail-fast: false
matrix:
os: [ ubuntu-latest, macos-latest, windows-latest ]
python-version: [ "3.11", "3.12", "3.13", "3.14" ]
python-version: [ "3.12", "3.13", "3.14" ]
defaults:
run:
shell: bash
steps:
- name: Checkout repo
uses: actions/checkout@v7
with:
ref: ${{ inputs.ref }}

- name: Setup uv
uses: astral-sh/setup-uv@v7
Expand Down
79 changes: 45 additions & 34 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,11 @@ jobs:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
defaults:
run:
shell: bash
outputs:
version: ${{ steps.version.outputs.version }}
steps:

- name: Check release branch
Expand Down Expand Up @@ -110,34 +111,6 @@ jobs:
uvx ruff format --check .
uvx codespell

- name: Install modflow executables
if: ${{ github.event_name != 'workflow_dispatch' || inputs.run_tests }}
id: install_modflow
continue-on-error: true
uses: modflowpy/install-modflow-action@v1
with:
path: ${{ github.workspace }}/autotest
repo: modflow6-nightly-build

# the download reaches out to github and occasionally has its
# connection reset, failing the job before any test runs
- name: Install modflow executables (retry)
if: ${{ (github.event_name != 'workflow_dispatch' || inputs.run_tests) && steps.install_modflow.outcome == 'failure' }}
uses: modflowpy/install-modflow-action@v1
with:
path: ${{ github.workspace }}/autotest
repo: modflow6-nightly-build

# temporary. devtools 2.x will autosync, but 1.x needs opt-in.
- name: Sync devtools
if: ${{ github.event_name != 'workflow_dispatch' || inputs.run_tests }}
run: uv run mf sync

- name: Run tests
if: ${{ github.event_name != 'workflow_dispatch' || inputs.run_tests }}
working-directory: ./autotest
run: uv run pytest -v -n auto -m "not mf6"

- name: Generate changelog
uses: orhun/git-cliff-action@v4
with:
Expand Down Expand Up @@ -165,14 +138,11 @@ jobs:
sed -i '1i # Changelog' $clog

- name: Push release branch
env:
GITHUB_TOKEN: ${{ github.token }}
run: |
ver="${{ steps.version.outputs.version }}"

# the release notes have already been prepended to HISTORY.md,
# keep a copy for the PR body then drop the standalone changelog
notes=$(grep -v "### Version $ver" CHANGELOG.md)
# the notes are already prepended to HISTORY.md; the pr job reads
# them back from there rather than from this standalone file
rm -f CHANGELOG.md

git config core.sharedRepository true
Expand All @@ -182,6 +152,47 @@ jobs:
git commit -m "ci(release): set version to $ver, update changelog"
git push origin "v$ver"

test:
name: Test release branch
# a bot pushes the release branch, so no push or pull_request event fires
# for it and the pull request would otherwise carry no checks at all
needs: prep
if: ${{ github.event_name != 'workflow_dispatch' || inputs.run_tests }}
permissions:
contents: read
uses: ./.github/workflows/ci.yml
with:
ref: v${{ needs.prep.outputs.version }}

pr:
name: Draft release pull request
needs: [prep, test]
# the tests are skipped when a release is dispatched with run_tests off
if: ${{ always() && needs.prep.result == 'success' && (needs.test.result == 'success' || needs.test.result == 'skipped') }}
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
defaults:
run:
shell: bash
steps:

- name: Checkout release branch
uses: actions/checkout@v7
with:
ref: v${{ needs.prep.outputs.version }}

- name: Draft pull request
env:
GITHUB_TOKEN: ${{ github.token }}
run: |
ver="${{ needs.prep.outputs.version }}"
notes=$(awk -v hdr="### Version $ver" '
index($0, hdr) == 1 { flag = 1; next }
/^### Version / { flag = 0 }
flag' HISTORY.md)

body='
# Release '$ver'

Expand Down
4 changes: 2 additions & 2 deletions CITATION.cff
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ message: If you use this software, please cite both the article from preferred-c
and the software itself.
type: software
title: MODFLOW API
version: 1.0.1
date-released: '2026-09-06'
version: 1.0.2
date-released: '2026-11-02'
abstract: An extension to xmipy for the MODFLOW API.
repository-artifact: https://pypi.org/project/modflowapi
repository-code: https://github.com/MODFLOW-ORG/modflowapi
Expand Down
6 changes: 6 additions & 0 deletions HISTORY.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
# Changelog
### Version 1.0.2

#### Bug fixes

* [fix(sfr)](https://github.com/MODFLOW-ORG/modflowapi/commit/3b93eb7346936cc4d5d1cee1d8791f7b5ba106d3): Read bedk, manning, and upstream_frac from the input context (#128). Committed by Joseph Hughes on 2026-11-02.

### Version 1.0.1

#### Bug fixes
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ and additional functionality specific to the MODFLOW API. Currently it is a join

## Installation

`modflowapi` requires Python 3.11+, with:
`modflowapi` requires Python 3.12+, with:

```shell
numpy
Expand Down
152 changes: 152 additions & 0 deletions autotest/test_interface.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import shutil
from pathlib import Path
from platform import system
from textwrap import dedent

import numpy as np
import pytest
Expand Down Expand Up @@ -341,6 +342,157 @@ def callback(sim, step):
raise Exception(e)


def test_sfr_input_context_names(function_tmpdir):
def callback(sim, step):
if step != Callbacks.timestep_start or sim.kstp != 0:
return
model = sim.get_model()
sfr = model.get_package("sfr_0")
if sim.kper == 1:
# the write persists into the next time step
np.testing.assert_allclose(sfr.packagedata.values["hk"], written["hk"], err_msg="hk write did not persist")
written["persisted"] = True
return
if f"__INPUT__/{model.name}/SFR_0/BEDK" in sim.mf6.get_input_var_names():
path = f"__INPUT__/{model.name}/SFR_0"
names = ("BEDK", "MANNING", "UPSTREAM_FRAC")
else:
path = f"{model.name}/SFR_0"
names = ("HK", "ROUGH", "USTRF")
addrs = [f"{path}/{name}" for name in names]
for addr in addrs:
assert addr in sfr._package_var_addrs, f"{addr} not used"

pd = sfr.packagedata.values
for column, values in expected.items():
np.testing.assert_allclose(pd[column], values, err_msg=f"{column} does not match the input")

pd["hk"] *= 10.0
sfr.packagedata = pd
np.testing.assert_allclose(sim.mf6.get_value(addrs[0]), pd["hk"], err_msg="hk is not being properly set")
written["hk"] = pd["hk"].copy()

written = {}

# three reaches in a row of three cells; reach 1 flows to reach 2 and
# diverts to reach 3
expected = {"hk": [0.1, 0.2, 0.3], "rough": [0.03, 0.035, 0.04], "ustrf": [1.0, 1.0, 0.0]}
packagedata = "\n".join(
f" {i + 1} 1 1 {i + 1} 100.0 {width} 0.001 {rtp} 1.0 {hk} {rough} {ncon} {ustrf} {ndiv}"
for i, (width, rtp, hk, rough, ncon, ustrf, ndiv) in enumerate(
zip(
[10.0, 10.0, 5.0],
[9.0, 8.9, 8.9],
expected["hk"],
expected["rough"],
[2, 1, 1],
expected["ustrf"],
[1, 0, 0],
)
)
)
files = {
"mfsim.nam": """
BEGIN timing
TDIS6 sim.tdis
END timing
BEGIN models
gwf6 sfr.nam sfr
END models
BEGIN exchanges
END exchanges
BEGIN solutiongroup 1
ims6 sim.ims sfr
END solutiongroup
""",
"sim.tdis": """
BEGIN dimensions
NPER 2
END dimensions
BEGIN perioddata
1.0 1 1.0
1.0 1 1.0
END perioddata
""",
"sim.ims": "",
"sfr.nam": """
BEGIN packages
DIS6 sfr.dis dis
NPF6 sfr.npf npf
IC6 sfr.ic ic
CHD6 sfr.chd chd_0
SFR6 sfr.sfr sfr_0
END packages
""",
"sfr.dis": """
BEGIN dimensions
NLAY 1
NROW 1
NCOL 3
END dimensions
BEGIN griddata
delr
CONSTANT 100.0
delc
CONSTANT 100.0
top
CONSTANT 10.0
botm
CONSTANT 0.0
END griddata
""",
"sfr.npf": """
BEGIN griddata
icelltype
CONSTANT 0
k
CONSTANT 10.0
END griddata
""",
"sfr.ic": """
BEGIN griddata
strt
CONSTANT 8.0
END griddata
""",
"sfr.chd": """
BEGIN dimensions
MAXBOUND 1
END dimensions
BEGIN period 1
1 1 1 8.0
END period
""",
"sfr.sfr": """
BEGIN dimensions
NREACHES 3
END dimensions
BEGIN packagedata
{packagedata}
END packagedata
BEGIN connectiondata
1 -2 -3
2 1
3 1
END connectiondata
BEGIN diversions
1 1 3 UPTO
END diversions
BEGIN period 1
1 INFLOW 10.0
1 DIVERSION 1 2.0
END period
""",
}
test_pth = function_tmpdir / "sfr_model"
test_pth.mkdir()
for name, text in files.items():
(test_pth / name).write_text(dedent(text).format(packagedata=packagedata))

run_simulation(so, test_pth, callback)
assert written.get("persisted"), "hk was not checked in the second stress period"


def test_rhs_hcof_advanced(function_tmpdir):
def callback(sim, step):
model = sim.test_model
Expand Down
Loading