Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
a07d342
fix(baseflow): quiet the test run and draw the version it reports
mgcooper Sep 18, 2026
5964528
fix(baseflow): close the last window, label the demo values, draw 1.2.0
mgcooper Sep 18, 2026
0ff9665
docs: refresh workflow and local style guidance
mgcooper Oct 14, 2026
810248d
test(baseflow): split TestBaseflow into per-function suites
mgcooper Oct 14, 2026
8fd3cdf
fix(baseflow): return Inf for a divergent mean tau and NaN expected flow
mgcooper Oct 14, 2026
db4dc14
fix(baseflow): correct the RS05 late-time constant, tail mask, and fits
mgcooper Oct 14, 2026
2cb51bd
test(baseflow): make TestBaseflow an end-to-end workflow suite
mgcooper Oct 14, 2026
cedc762
fix(baseflow): keep a given streamlength and accept leap-day calendars
mgcooper Oct 14, 2026
1619d9f
fix(baseflow): repair cloudphi userab, phi domain, fitab order, rounding
mgcooper Oct 14, 2026
56e6395
fix(baseflow): correct the RS05 early-time constant fR1
mgcooper Oct 14, 2026
778d890
fix(baseflow): fit slope 1 by default for fitab 'median' and 'envelope'
mgcooper Oct 14, 2026
6e7fde9
fix(baseflow): round b in aquiferstorage only to choose the formula
mgcooper Oct 14, 2026
d1c1253
fix(internal): build the doc search database after the pages
mgcooper Oct 14, 2026
5db8719
docs: regenerate the doc pages, function pages, and search database
mgcooper Oct 14, 2026
15cacd7
test(baseflow): describe the Kuparuk workflow checks as they run
mgcooper Oct 14, 2026
38d9ad3
fix(docs): stop tracking the generated doc search metadata
mgcooper Oct 17, 2026
de505c4
Merge branch 'feat/recession-analysis-fixes' into dev
mgcooper Oct 17, 2026
07bba31
refactor(baseflow): share one plotrain between the point-cloud figures
mgcooper Oct 23, 2026
c9f90b4
docs(baseflow): document the hyetograph tags the code sets
mgcooper Oct 23, 2026
b483605
fix(wrapevents): preserve annual leap-day alignment
mgcooper Oct 23, 2026
788c10f
docs: correct conditional MPI installation route
mgcooper Oct 23, 2026
1a5264a
chore: ignore MATLAB Project UUID state
mgcooper Oct 23, 2026
a849d3d
docs: refresh static function references and search
mgcooper Oct 23, 2026
944fff7
style(agents): adopt shared MATLAB formatting
mgcooper Oct 23, 2026
c61ac21
fix(deps): return no peak when no extremum passes the selectivity
mgcooper Oct 23, 2026
5c298df
fix(baseflow): treat rounding-level slopes and curvatures as zero
mgcooper Oct 23, 2026
62febf8
fix(baseflow): reject unsupported sloped porosity requests
mgcooper Oct 24, 2026
bda74bc
feat(baseflow): add descriptive bootstrap percentile summaries
mgcooper Oct 24, 2026
9c0eaf8
merge fix/bfra-contract-percentiles-20261024 into dev
mgcooper Oct 24, 2026
ba668ee
fix: stabilize event-scale nonlinear power-law fits
mgcooper Oct 28, 2026
7c27e44
Merge branch 'fix/bfra-kuparuk-conditioning-20261028' into dev
mgcooper Oct 28, 2026
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
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@ codegen/
*.slxc
.MATLABDriveTag

# Written by the Help browser search-database build beside the docs; not a source file.
custom_toolbox.json

# -----------------
# ---- PYTHON -----
# -----------------
Expand Down Expand Up @@ -621,3 +624,5 @@ TSWLatexianTemp*
# project's state is machine-local, not source.
**/resources/*
!**/resources/project/

**/resources/project/uuid-*.xml
195 changes: 195 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,201 @@ This file lists notable changes to the baseflow toolbox. The format follows
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/); the project uses
semantic versioning.

## [Unreleased]

### Changed

- The unit tests of `tests/TestBaseflow.m` live in per-function suites,
`tests/test_<function_name>.m`. `TestBaseflow` is the integration suite
of the documented workflow. It runs the chain of `baseflow_demo_kuparuk`
once, with the demo options but without figures or the bootstrap. It
checks each stage against the stage before it. It also checks the
Kuparuk recession exponent, the BS04 and RS05 aquifer
properties at b = 3/2, and a synthetic divergent tail. `TestBaseflow`
holds the workflow checks of the removed `tests/test_corechain.m`, and
the per-function suites hold its function checks.
- One private function, `expectedtau`, computes the mean tau for
`gpfitb`, `plfitb`, `plplotb`, and `baseflow.getfunction`. It alone
raises the pole warning, `baseflow:expectedtau:tauPole`, with the count
of values of b that give no finite mean tau. No function raises
`baseflow:plfitb:tauPoleReplicates`. Suppress
`baseflow:expectedtau:tauPole` to silence the pole warning.
- Git does not track `toolbox/docs/html/custom_toolbox.json`.
`builddocsearchdb`, which `baseflow.internal.makedocs` calls for the
'docsearch' option, writes it again on each build, with the absolute
path of the build folder. A copy names a folder that exists only on the
machine that built it. The search database in `helpsearch-v4_en` still
ships, and `toolbox/toolbox.ignore` leaves the file out of a toolbox
package.

### Fixed

- The `eventfinder` filters treat a slope or curvature as zero when its
magnitude does not exceed the rounding error of its own computation.
Exact arithmetic gives a zero curvature on a straight stretch of flow,
but the computed values carry rounding, and the smoothing that
`getevents` applies makes it depend on the MATLAB release. So the
filters selected different events on different releases. The bound
scales with the flow and, after smoothing, with the smoothing window;
the `eventfinder` help states it. `getevents` passes the bound of its
smoothing through the new `eventfinder` option `'qerror'`, and
unsmoothed input counts one rounding per value. The Kuparuk annual
workflow selects the same 231 events on R2024b, R2025b, and R2026a,
where it selected 229 or 230. Against the earlier R2025b result, the
`rmnochange` filter excludes 46 more late-season days on straight
stretches, which shortens or splits some events. The workflow's
recession exponent b is 1.3465 on R2025b and R2026a, against 1.3541
in the 1.1.0 release notes, and `TestBaseflow` checks that value.
R2024b selects the same events but gives 1.3577, because its
per-event nonlinear fits differ.
- `baseflow.deps.peakfinder` returns no peak when no extremum passes the
selectivity with the endpoints excluded. It read an unset candidate and
errored for a positive selectivity on a series that ends below its
extrema.
- `wrapevents` removes February 29 before annual grouping and keeps the
remaining timestamps, flow and rainfall rows aligned. Leap-year input
previously failed the complete-year check.
- The `hyetograph` help names the tags the code sets. The figure has the
Name 'Hyetograph' and the Tag 'HyetographFigure'. The streamflow axes is
'HyetographAxis', and the series are 'StreamflowPlot' and
'PrecipitationPlot'. The help searched for 'StreamflowAxis' and
'PrecipitationAxis', which no object has. A search of all of H returns
the streamflow axes twice, so the examples search from the figure H(1).
The precipitation axes has no tag; `ancestor(H(4), 'axes')` finds it.
- The late-time Rupp and Selker (2005) constant in `specialfunctions`
raises the Beta function to the power n+3. At n = 0 it gives 4.805, the
Brutsaert and Nieber (1977) constant 4.804; it gave 1.857. `globalfit`
uses the RS05 solutions by default, so its phi changes for every b
other than 1 and 3/2. `aquiferprops` 'RS05' gives the 'BS04' k at
b = 3/2. fR2/2 matches the Phi2 column of Rupp and Selker (2005)
Table 1 for n from 0 to 64.
- The early-time Rupp and Selker (2005) constant fR1 in
`specialfunctions` divides the root mu by 4-2*gamma, as their equation
33 prints. It divided by 4*(1-2*gamma), which gave 0.869 at n = 0, a
pole at n = 1, and negative values above n = 2.4.
fR1/(n+1) matches the Phi1 column (m = 1) of their Table 1 for n from 0
to 64, with 1.108 at n = 0. So `globalfit` phi changes again for every
b other than 1 and 3/2. A result that divides by phi, such as the
thickness from `aquiferthickness` and `aquifertrend`, changes with it.
A storage trend does not depend on phi.
- `plfitb` puts the xmin sample in the tail mask, as `plfit` fits the
tail x >= xmin. So the `globalfit` a and expected flows change.
- `baseflowtrend` requires `pctl` in (0,1). A NaN `pctl`, which a
divergent mean tau gives through `pQexp`, made `trendplot` fit the mean
flow, so the baseflow equalled the mean flow.
- `gpfitb` and `plfitb` return Inf for the mean tau at or below the tau
pole at alpha 2. The mean of a Pareto tail diverges there. The formula
tau0*(2-b)/(3-2*b) is negative below the pole, so `gpfitb` reported a
tauExp of -10.01 for a sample with alpha 1.5. The mean is NaN where no
Pareto tail exists, at alpha 1 or below.
- `gpfitb` and `plplotb` draw no mean-tau label where the mean or its
bounds diverge, so no label names a negative or infinite tau.
- A `plfitb` bootstrap replicate at or below alpha 2 has an infinite tau,
so `tau_sig`, `tau_L`, and `tau_H` are NaN.
- `expectedQ` returns NaN for `Qexp`, `Q0`, `pQexp`, and `pQ0` when tau
is not finite. So `globalfit` reports no expected flow on data with b
at or above 3/2. It returned 0 at b = 3/2 and complex values between
3/2 and 2, where the negative tau entered a fractional power.
- `fdcurve` returns a reference value for each reference point, and NaN
for a point that is NaN or above every flow. It sized its output from
the first point only. Missing flows stay out of the plotting
positions.
- `baseflow.getfunction('expectedTime')` shares the tau pole. The
`'boftautau0'` and `'aofa0'` handles apply elementwise, so array input
gives one value per element instead of a matrix division.
- `fitab` 'nls' reports its interval at the confidence level `alpha`.
`nlparci` and `regress` take a significance level, so the default 0.68
gave a 32% interval, and the linear fallback a 95% one. The Octave path
used one degree of freedom.
- `prepfits` drops masked and zero-weight points, so every `fitab` method
fits the same points. 'nls' honors the mask, and the OLS interval and
`Fit.N` count the fitted points only.
- The CTS stencils B2, F2, C2, and C4 pair each derivative with the flow
at the same time. B2 and F2 paired it with a flow half a step away.
That biased a by about 5% at a step of a tenth of tau.
- The ETS method leaves a rejected window empty. It interpolated over
the rejected windows, so a rise inside a recession took the slope of
the windows around it. The example data has no such window, so its
results are unchanged.
- The VTS method widens its window across a flat step, so a record in
whole units gives no zero slopes (Rupp and Selker, 2006).
- `pointcloudintercept` orders its a bounds for flows below 1. Its 'mean'
method pivots through the geometric means of q and -dq/dt, so its
bounds are in flow units.
- `dndtuncertainty` correlates the event variables over the events where
all three exist, so a nan phi from `phifitensemble` leaves the
uncertainty finite.
- `aquiferstorage` uses one path for scalar and array input. The scalar
path with a `Ktime` input returned negative storage for 1 < b < 2.
- `Qnonlin` gives the exponential recession at b = 1, and for b < 1 a
flow that stays 0 once the reservoir empties. `generateTestData` uses
it for every b.
- `printtrend` scales the trend by the years the record spans to report
the total change. It used the sample count.
- `conversions` accepts 'Nstar' as an input, and its help gives k and
alpha from the pdf of tau.
- `gpfitb` returns the bootstrap replicates of tau0 = sigma/k in
`tau0reps`. It returned sigma.
- `quantreg` flags a significant slope when its bootstrap interval
excludes zero. A z test on the replicates divided their spread by the
square root of the replicate count.
- The `fdcurve` x label names the non-exceedance probability it plots,
and the `plfitb` help says that `limit` bounds xmin.
- `gpfitb`, `plplotb` and `fitphidist` print nothing when they label a
figure. The private `drawarrow` assigned its output whether or not the
caller asked for it, and no call site ended with a semicolon, so each
label printed a 1x2 graphics array.
- `baseflow.internal.version` draws the version it reports. The two
slots beside and below the v carried the digits of 1.1.0 and now come
from the version string. The large letters, which are drawn art, read
1.2.0.
- `baseflow.internal.runtests` draws test figures off screen, so a run on
the desktop opens no window for a test that closes its own figure. It
puts the figure default back on both exits.
- Three tests made an axes current with `axes()`, which shows and raises
its figure whatever the root default asks for, so a window still opened
during a run. They name the current axes through the figure instead.
- The demos print each value with its name. `baseflow_demo_1`,
`baseflow_demo_2` and `baseflow_demo_3` displayed bare results, which
reach the screen as `ans`.
- `fitphidist` draws its figure off screen when the root asks for
invisible figures, through the new private `figurevisibility`. A figure
created with an explicit `'Visible'` of `'on'` ignores that default, so
`showfit` true opened a window during a test run.
- The `fitphi` warnings carry identifiers,
`baseflow:fitphi:incompatibleLateTimeSolution` and
`baseflow:fitphi:lateTimeSolutionImpliesB`, so a caller can suppress or
catch one by name. The tests that ask for a solution pair `fitphi`
falls back from now suppress the warning they expect, as do the tests
that run a fit to the iteration limit of `nlinfit`.
- `cloudphi` uses the `userab` line as the late-time line and still fits
the early-time line to the point cloud. A call with `userab` failed
with an undefined variable.
- `fitphidist` leaves a phi outside [0, 1] out of the beta fit and warns
`baseflow:fitphidist:phiOutsideUnitInterval` with the count. `betafit`
errored on such a value, so `globalfit` 'distfit' failed when one event
gave a phi above 1. 'phicombo' set those values to NaN without a
warning, and it shares the `fitphidist` check.
- `globalfit` keeps a `streamlength` given without `drainagedensity`.
`setopts` and `globalfit` gave `drainagedensity` a default of 0.8,
which replaced that `streamlength` with A*0.8/1000 and warned. The
default is NaN. A `streamlength` within rounding of A*Dd/1000 raises no
warning, and the warning has the identifier
`baseflow:globalfit:inconsistentStreamlength`. The flat-aquifer phi
solutions do not use L, so no result changes.
- `aquiferstorage` runs on Octave. It rounded b with `round(b,2)`, and
the Octave `round` takes one argument. It rounds b to two decimals
only to choose the formula, and the power-law formulas use the
unrounded b. At b = 1.567 the rounded b changed the storage by +2.1% at
q = 0.01 and -3.4% at q = 1e6.
- `dndtuncertainty` accepts a daily calendar with leap days. It reshaped
the record into 365-day rows, which a leap day breaks. It takes the
years that T spans directly, so the result on a no-leap calendar is
unchanged.
- `fitab` 'mean', 'median', and 'envelope' fit a line of slope 1 when
`order` is not given, and return b = 1. 'mean' returned b = NaN,
'median' failed in `signrank`, and 'envelope' returned NaN.

## [1.2.0] - 2026-09-17

### Added
Expand Down
18 changes: 15 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,7 @@ and `tau` conventions.

## Install

- Clone this repo or use [`mpm`](http://mobeets.github.io/mpm/) in your Matlab terminal:
- `mpm install baseflow`
- Clone this repository.
- cd into the `toolbox` directory.
- Run `Setup.m`:
- Type `msg = Setup('install')` at the command window then press enter.
Expand All @@ -68,6 +67,19 @@ and `tau` conventions.
- To debug, try `runtests('tests', 'Debug', true)`.
- In new Matlab sessions, cd to the toolbox directory and try `Setup('addpath')` or just `Setup` to add the toolbox to your search path (or manage the search path however you normally do).

The optional [Matlab Package Installer (MPI)](https://github.com/mobeets/mpm)
route uses this repository's explicit URL and a numeric release tag:

```matlab
mpi install -u https://github.com/mgcooper/baseflow.git -t <numeric-tag>
```

The selected release must include a root `pathList.m` containing `toolbox`.
It must also pass an installation check from a clean checkout.
No existing release has been qualified for this command.
Use the clone route until a qualified release is published.
Do not install this project by the bare name `baseflow`.

For more options, see [Configuration](#configuration).
For Octave compatibility, see [Octave](#octave).

Expand Down Expand Up @@ -234,4 +246,4 @@ cd baseflow
Baseflow recession analysis is a set of methods in hydrologic science used to estimate aquifer properties from streamflow measurements. Properties that cannot be observed, such as hydraulic conductivity, are inferred from something that can be observed, such as streamflow. The underlying theory is based on solutions to the one-dimensional (lateral) groundwater flow equation ("Boussinesq equation").

The theory makes various strong assumptions, including that the aquifer extent is much larger in the lateral direction than the vertical, and that capillarity can be ignored or parameterized by the concept of 'drainable porosity'. This reduces the three-dimensional Richard's equation to one dimension and eliminates unsaturated flow considerations. The theory has been validated with laboratory experiments and may work better than you expect. When applied to entire catchments ("watersheds"), the theory becomes an effective one, meaning the inferred properties are those that give the same solution to the Boussinesq equation that one would obtain for a single hillslope with those same properties and geometric similarity.
-->
-->
11 changes: 8 additions & 3 deletions STYLE.local.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,14 @@ merged into it).
- Give a class suite the same name as its file (`test_fitab`).
- `TestBaseflow.m` is the integration suite for the toolbox workflow, so
it keeps its PascalCase name.
- A suite that tests several functions together keeps a descriptive
name: `test_eqstrings` and `test_internal`.
- Variables use short lowercase or camelCase names (`q`, `dqdt`, `gotopath`).

## Formatting

- Indent with 3 spaces; no tabs.
- Wrap at roughly 80 columns; continue long lines with `...`.
- Close every function with an explicit `end`.
Use the shared MATLAB formatting rules in `STYLE.md`.

- Function help uses an uppercase H1 (`%FITAB fit event-scale ...`) followed
by `Syntax`, `Description`, input, and output sections for public
functions. See `toolbox/+baseflow/fitab.m` for the reference format.
Expand Down Expand Up @@ -78,3 +79,7 @@ as this:

> This method fails when nmin is 0, and possibly when nmin is 1. runlength
> returns 1 for consecutive nan values. See isminlength.

## Other project conventions

The New-test convention section defines this repository's additional test requirements.
Loading
Loading