makepst 0.2.0__tar.gz → 0.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. {makepst-0.2.0 → makepst-0.4.0}/CHANGELOG.md +50 -0
  2. {makepst-0.2.0 → makepst-0.4.0}/CITATION.cff +2 -2
  3. {makepst-0.2.0 → makepst-0.4.0}/PKG-INFO +242 -47
  4. {makepst-0.2.0 → makepst-0.4.0}/README.md +241 -46
  5. {makepst-0.2.0 → makepst-0.4.0}/makepst/__init__.py +7 -4
  6. {makepst-0.2.0 → makepst-0.4.0}/makepst/checks.py +8 -5
  7. {makepst-0.2.0 → makepst-0.4.0}/makepst/cli.py +414 -22
  8. {makepst-0.2.0 → makepst-0.4.0}/makepst/diff.py +49 -2
  9. {makepst-0.2.0 → makepst-0.4.0}/makepst/excel.py +43 -10
  10. makepst-0.4.0/makepst/hpstart.py +66 -0
  11. makepst-0.4.0/makepst/pestpp.py +241 -0
  12. makepst-0.4.0/makepst/provenance.py +251 -0
  13. makepst-0.4.0/makepst/pst.py +910 -0
  14. {makepst-0.2.0 → makepst-0.4.0}/makepst/reader.py +56 -20
  15. makepst-0.4.0/makepst/reweight.py +344 -0
  16. {makepst-0.2.0 → makepst-0.4.0}/makepst/rules.py +18 -0
  17. {makepst-0.2.0 → makepst-0.4.0}/makepst/sections.py +14 -0
  18. {makepst-0.2.0 → makepst-0.4.0}/makepst/writer.py +137 -20
  19. {makepst-0.2.0 → makepst-0.4.0}/makepst.egg-info/PKG-INFO +242 -47
  20. {makepst-0.2.0 → makepst-0.4.0}/makepst.egg-info/SOURCES.txt +12 -0
  21. {makepst-0.2.0 → makepst-0.4.0}/pyproject.toml +1 -1
  22. {makepst-0.2.0 → makepst-0.4.0}/tests/data/demo.par +1 -1
  23. {makepst-0.2.0 → makepst-0.4.0}/tests/make_fixture.py +2 -1
  24. {makepst-0.2.0 → makepst-0.4.0}/tests/test_checks.py +10 -2
  25. makepst-0.4.0/tests/test_convenience.py +60 -0
  26. {makepst-0.2.0 → makepst-0.4.0}/tests/test_diff.py +22 -3
  27. {makepst-0.2.0 → makepst-0.4.0}/tests/test_examples.py +31 -0
  28. {makepst-0.2.0 → makepst-0.4.0}/tests/test_fixture.py +50 -2
  29. makepst-0.4.0/tests/test_hpstart.py +68 -0
  30. makepst-0.4.0/tests/test_log_bundle.py +98 -0
  31. {makepst-0.2.0 → makepst-0.4.0}/tests/test_makepst.py +34 -10
  32. makepst-0.4.0/tests/test_optional_positions.py +93 -0
  33. makepst-0.4.0/tests/test_optional_positions_variations.py +19 -0
  34. makepst-0.4.0/tests/test_pestpp.py +141 -0
  35. {makepst-0.2.0 → makepst-0.4.0}/tests/test_provenance.py +1 -1
  36. makepst-0.4.0/tests/test_rescale.py +193 -0
  37. makepst-0.4.0/tests/test_reweight.py +240 -0
  38. {makepst-0.2.0 → makepst-0.4.0}/tests/test_rules.py +18 -0
  39. makepst-0.4.0/tests/test_set_par.py +166 -0
  40. makepst-0.2.0/makepst/provenance.py +0 -89
  41. makepst-0.2.0/makepst/pst.py +0 -449
  42. {makepst-0.2.0 → makepst-0.4.0}/LICENSE +0 -0
  43. {makepst-0.2.0 → makepst-0.4.0}/MANIFEST.in +0 -0
  44. {makepst-0.2.0 → makepst-0.4.0}/examples/minimal/control.csv +0 -0
  45. {makepst-0.2.0 → makepst-0.4.0}/examples/minimal/flow.ins +0 -0
  46. {makepst-0.2.0 → makepst-0.4.0}/examples/minimal/flow.out +0 -0
  47. {makepst-0.2.0 → makepst-0.4.0}/examples/minimal/heads.ins +0 -0
  48. {makepst-0.2.0 → makepst-0.4.0}/examples/minimal/heads.out +0 -0
  49. {makepst-0.2.0 → makepst-0.4.0}/examples/minimal/io.csv +0 -0
  50. {makepst-0.2.0 → makepst-0.4.0}/examples/minimal/model.in +0 -0
  51. {makepst-0.2.0 → makepst-0.4.0}/examples/minimal/model.par +0 -0
  52. {makepst-0.2.0 → makepst-0.4.0}/examples/minimal/model.py +0 -0
  53. {makepst-0.2.0 → makepst-0.4.0}/examples/minimal/model.res +0 -0
  54. {makepst-0.2.0 → makepst-0.4.0}/examples/minimal/model.tpl +0 -0
  55. {makepst-0.2.0 → makepst-0.4.0}/examples/minimal/obs.csv +0 -0
  56. {makepst-0.2.0 → makepst-0.4.0}/examples/minimal/par.csv +0 -0
  57. {makepst-0.2.0 → makepst-0.4.0}/examples/minimal/pargp.csv +0 -0
  58. {makepst-0.2.0 → makepst-0.4.0}/makepst/__main__.py +0 -0
  59. {makepst-0.2.0 → makepst-0.4.0}/makepst/model_files.py +0 -0
  60. {makepst-0.2.0 → makepst-0.4.0}/makepst/phi.py +0 -0
  61. {makepst-0.2.0 → makepst-0.4.0}/makepst/pyemu_bridge.py +0 -0
  62. {makepst-0.2.0 → makepst-0.4.0}/makepst/starter.py +0 -0
  63. {makepst-0.2.0 → makepst-0.4.0}/makepst.egg-info/dependency_links.txt +0 -0
  64. {makepst-0.2.0 → makepst-0.4.0}/makepst.egg-info/entry_points.txt +0 -0
  65. {makepst-0.2.0 → makepst-0.4.0}/makepst.egg-info/requires.txt +0 -0
  66. {makepst-0.2.0 → makepst-0.4.0}/makepst.egg-info/top_level.txt +0 -0
  67. {makepst-0.2.0 → makepst-0.4.0}/setup.cfg +0 -0
  68. {makepst-0.2.0 → makepst-0.4.0}/tests/data/demo.3.obs.csv +0 -0
  69. {makepst-0.2.0 → makepst-0.4.0}/tests/data/demo.3.par.csv +0 -0
  70. {makepst-0.2.0 → makepst-0.4.0}/tests/data/demo.phi.actual.csv +0 -0
  71. {makepst-0.2.0 → makepst-0.4.0}/tests/data/demo.pst +0 -0
  72. {makepst-0.2.0 → makepst-0.4.0}/tests/data/demo.res +0 -0
  73. {makepst-0.2.0 → makepst-0.4.0}/tests/data/demo.xlsx +0 -0
  74. {makepst-0.2.0 → makepst-0.4.0}/tests/test_model_files.py +0 -0
  75. {makepst-0.2.0 → makepst-0.4.0}/tests/test_phi.py +0 -0
  76. {makepst-0.2.0 → makepst-0.4.0}/tests/test_pyemu.py +0 -0
  77. {makepst-0.2.0 → makepst-0.4.0}/tests/test_starter.py +0 -0
  78. {makepst-0.2.0 → makepst-0.4.0}/tests/test_v2.py +0 -0
  79. {makepst-0.2.0 → makepst-0.4.0}/tests/test_version.py +0 -0
@@ -1,5 +1,55 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.4.0 (2026-09-27)
4
+
5
+ - `rescale` / `Pst.rescale_par()`: move parameter values into `SCALE` so each selected parameter
6
+ starts at 1 (bounds divided by the value), or back with `--undo`; the model sees the same values.
7
+ Selection by `--par` globs and `--pargp` (fixed parameters included unless `--exclude_fixed`;
8
+ tied children follow their parent). Prior-information equations are rewritten exactly (every
9
+ residual is unchanged), and settings in parameter units that cannot be converted (absolute
10
+ derivative increments, `absolute(n)` limits, `++parcov` / `++base_jacobian` / `++ies_par_en`)
11
+ are reported.
12
+ - `fill_parval` (`build --fill_parval`, `parrep`), `tempchek --par` and `hpstart --par` convert a
13
+ `.par` value written under a different `SCALE` / `OFFSET` than the control file's through the
14
+ model value, instead of copying `PARVAL1` across. The test fixture's `demo.par` now records the
15
+ parameters' own offsets, as PEST writes them.
16
+ - Writing a control file no longer fails on an adjustable `PARVAL1` outside its bounds (a `parrep`
17
+ from an IES realization, say): it is a warning, as documented, and the file is written.
18
+ `makepst validate` still reports it as an error. `diff` normalizes a workbook side, as `build`
19
+ would, so a workbook compares equal to the control file built from it.
20
+
21
+ - `reweight`: balance observation-group contributions equally or to relative target shares,
22
+ multiply selected group weights directly, match weights to the current misfit with
23
+ `--discrepancy group|obs` (group phi = weighted-observation count, or `w = min(w, 1/|r|)`), consume `.res`/IES observation results, enforce
24
+ optional weight bounds with `--clip`, and write an auditable before/after report.
25
+
26
+ - `++` options are checked against PEST++ 5.2.29's option list (`makepst/pestpp.py`): a known option
27
+ with a value PEST++ would not accept, or the same option twice (also through an alias), is an error;
28
+ an unknown option is a warning with the closest known name (`did you mean "ies_num_reals"?`) and is
29
+ kept as written. Findings name the workbook cell, csv line or control-file line the option came from
30
+ (`PP!A17`). `validate` reports them; `build`, `parrep` and `set` print them but still write.
31
+
32
+ - Unknown control-file sections are retained verbatim, including their original header,
33
+ body lines and position, instead of being dropped during a read/write round trip.
34
+ - `Pst.validate()` now returns a read-only report; `apply_fixes(report)` and `normalize()` make
35
+ the previous tied-parameter, unused-group and prior-information cleanups explicit.
36
+
37
+ ## 0.3.0 (2026-09-21)
38
+
39
+ - `log`: ledger of every manifest under a folder — when, which command, which output from
40
+ which sources, dimensions, version; `--file` filters by file name, path or sha256 prefix
41
+ ("which runs used that version of the workbook?"), `--command`, `--last`, `--check`
42
+ verifies every recorded hash
43
+ - `bundle`: zip a run for reproduction or review — control file, manifest, templates,
44
+ instruction files, model inputs, command-line files; `--sources`, `--outputs`, `--extra`,
45
+ `--list`, `--strict`; the zip gets its own manifest
46
+ - `provenance`: compare the hashes recorded in a manifest with the files on disk
47
+ (`unchanged` / `changed` / `missing`; exit 1 unless all unchanged)
48
+ - `update --dry_run`: preview matched names, target columns and skipped cells without writing
49
+ - `build` warns when a referenced sheet has formulas without cached values (a workbook saved
50
+ by openpyxl and not yet recalculated in Excel)
51
+ - `hpstart`: a PEST_HP `.hp` file with observation values from residuals or an IES realization
52
+
3
53
  ## 0.2.0 (2026-09-19)
4
54
 
5
55
  - `update`: the xlwings backend reads only headers, name and target columns, writes contiguous
@@ -6,8 +6,8 @@ authors:
6
6
  - family-names: Ou
7
7
  given-names: Michael
8
8
  email: ougengxin@gmail.com
9
- version: 0.2.0
10
- date-released: 2026-09-19
9
+ version: 0.4.0
10
+ date-released: 2026-09-27
11
11
  repository-code: "https://github.com/ougx/makePst"
12
12
  license: MIT
13
13
  keywords:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: makepst
3
- Version: 0.2.0
3
+ Version: 0.4.0
4
4
  Summary: Build PEST / PEST++ / PEST_HP control files from spreadsheet tables, read them back, and push results into the workbook
5
5
  Author-email: Michael Ou <ougengxin@gmail.com>
6
6
  License: MIT
@@ -46,7 +46,12 @@ Excel / CSV tables -> .pst makepst build
46
46
  .pst -> editable workbook makepst dump
47
47
  .par / .res / .rei / IES ensemble -> existing workbook makepst update
48
48
  .par or IES realization (+ tweaks) -> new .pst makepst parrep
49
+ .pst -> values moved to SCALE makepst rescale
50
+ .res/.rei or IES obs + group targets -> reweighted .pst makepst reweight
51
+ .res/.rei or IES obs + existing .hp -> new .hp makepst hpstart
49
52
  two .pst / workbooks -> what changed makepst diff
53
+ manifests in a folder -> run ledger makepst log
54
+ .pst + everything it references -> zip for review makepst bundle
50
55
  ```
51
56
 
52
57
  See [What round-tripping preserves](#what-round-tripping-preserves) for the exact guarantees
@@ -155,7 +160,7 @@ without `--force`.
155
160
 
156
161
  ```
157
162
  makepst validate model.pst --outputs
158
- makepst validate book.xlsm --strict
163
+ makepst validate book.xlsx --strict
159
164
  ```
160
165
 
161
166
  See [Validation](#validation). Template and instruction paths are resolved the way PEST
@@ -164,27 +169,43 @@ the current directory and the control file's parent, uses the one where the file
164
169
  says which; `--base_dir` overrides that. `--strict` also fails on warnings,
165
170
  `--quiet` hides the informational lines.
166
171
 
172
+ A workbook is accepted in place of a control file when it carries a `BUILD` sheet (one that
173
+ `dump` or `init` wrote), which is built first and then checked. For tables that have no
174
+ `BUILD` sheet — a hand-made workbook, or a set of CSV files — name them on a
175
+ [`build`](#build--tables--pst) command line and add `--dry_run`, which runs this same report
176
+ and writes nothing.
177
+
167
178
  ### build — tables → .pst
168
179
 
169
180
  ```
170
181
  makepst build out.pst [estimation|regularisation|prediction|pareto]
171
- --set_ctl_xls book.xlsm,CONTROL (or --set_ctl_csv file.csv)
172
- --add_pargp_xls book.xlsm,PARGP
173
- --add_par_xls book.xlsm,PAR_* (repeatable; sheet names may be globs)
174
- --add_obs_xls book.xlsm,OBS_*
175
- --add_io_xls book.xlsm,IO --add_pp_xls book.xlsm,PPcntl
182
+ --set_ctl_xls book.xlsx,CONTROL (or --set_ctl_csv file.csv)
183
+ --add_pargp_xls book.xlsx,PARGP
184
+ --add_par_xls book.xlsx,PAR_* (repeatable; sheet names may be globs)
185
+ --add_obs_xls book.xlsx,OBS_*
186
+ --add_io_xls book.xlsx,IO --add_pp_xls book.xlsx,PPcntl
176
187
  [--add_tied_xls ...] [--add_prior_xls ...] [--add_comment "text"] [--add_comment_xls ...]
177
188
  [--fill_parval run.par [--real NAME]] [--ss] [--no_dump_tpl] [--v2]
178
189
  ```
179
190
 
180
191
  Each `--add_*` takes `book,SHEET` (`_xls`) or a csv path (`_csv`) and may be repeated;
181
192
  tables split across sheets are concatenated. The sheet name may be a glob:
182
- `--add_par_xls tr13.xlsm,PAR_*` adds every `PAR_…` sheet in workbook order. Headers are
183
- case-insensitive. `makepst build book.xlsm [--out x.pst]` runs the command stored in the
193
+ `--add_par_xls tr13.xlsx,PAR_*` adds every `PAR_…` sheet in workbook order. Headers are
194
+ case-insensitive. `makepst build book.xlsx [--out x.pst]` runs the command stored in the
184
195
  workbook's `BUILD` sheet instead. The mode argument
185
196
  overrides `pestmode` in the CONTROL sheet; adding prior information switches to regularisation.
186
197
  `--ss` drops `ss*`/`sy*` parameters and groups (steady-state runs of a transient setup).
187
198
 
199
+ `--dry_run` builds the control file in memory, runs the full [validation](#validation) report
200
+ on it — including the template, instruction and model files it references — and writes
201
+ nothing (no `.pst`, no `dump.tpl`, no manifest), exiting 1 when there are errors. This is how
202
+ to check a workbook or a set of CSV tables that has no `BUILD` sheet; for one that has a
203
+ `BUILD` sheet, `makepst validate book.xlsx` does the same in one argument.
204
+
205
+ ```bash
206
+ makepst build case.pst --add_par_xls book.xlsx,PAR_* --add_obs_xls book.xlsx,OBS_* ... --dry_run
207
+ ```
208
+
188
209
  | table | columns |
189
210
  |---------|---------|
190
211
  | CONTROL | `NAME`, `VALUE` in the first four columns (`LINE`, `NAME`, `DEFAULT`, `VALUE`); blank values use the built-in default. Any variable of the control data, SVD, LSQR, AUI, SVD-assist or regularisation sections: `noptmax`, `jacupdate`, `lamforgive`, `win_mrun_hours`, `absparmax(1)=0.1 absparmax(2)=20`, `svdmode`, `phimlim`, … Counts are ignored (computed). |
@@ -197,6 +218,15 @@ overrides `pestmode` in the CONTROL sheet; adding prior information switches to
197
218
  | IO | `TYPE` (`cmd` / `tpl` / `ins`), `IN`, `OUT` |
198
219
  | PP | first two columns: PEST++ option name, value → `++name(value)` |
199
220
 
221
+ Classic PEST numeric control settings are positional. If a later setting is supplied,
222
+ all earlier numeric settings on that line must have values after built-in defaults
223
+ are applied. For example, `upvecbend` requires `iboundstick`, and `regsingthresh`
224
+ requires both `noptregadj` and `regweightrat`. Export raises an error naming any
225
+ missing predecessors instead of shifting settings into the wrong slots. Supply
226
+ those values explicitly; trailing optional fields may remain blank. Text flags
227
+ and named tokens such as `absparmax(1)=0.1` do not require numeric fillers. This
228
+ positional requirement does not apply to the version-2 keyword format (`--v2`).
229
+
200
230
  **Regularisation from the parameter table.** In regularisation mode each adjustable
201
231
  parameter with a `PRIOR` and a positive `WEIGHT` gets an equation: a numeric `PRIOR` gives
202
232
  `1.0 * log(p) = log10(v)` (or `1.0 * p = v` for untransformed parameters); a `PRIOR` naming
@@ -218,12 +248,13 @@ to recover a workbook that was lost.
218
248
  ### update — results → existing workbook
219
249
 
220
250
  ```
221
- makepst update book.xlsm --par run.par # PARVAL1
222
- makepst update book.xlsm --res run.res # adds MODELLED / RESIDUAL
223
- makepst update book.xlsm --pst run.pst --par_cols PARVAL1,PARTRANS,PARLBND,PARUBND
224
- makepst update book.xlsm --par run.3.par.csv --real best # PESTPP-IES ensemble
225
- makepst update book.xlsm --obs_csv run.3.obs.csv --pst run.pst # IES simulated values
226
- [--sheet "PAR_*"] [--group hk,sy] [--overwrite_formulas] [--out copy.xlsm] [--backend openpyxl|xlwings]
251
+ makepst update book.xlsx --par run.par # PARVAL1
252
+ makepst update book.xlsx --res run.res # adds MODELLED / RESIDUAL
253
+ makepst update book.xlsx --pst run.pst --par_cols PARVAL1,PARTRANS,PARLBND,PARUBND
254
+ makepst update book.xlsx --par run.3.par.csv --real best # PESTPP-IES ensemble
255
+ makepst update book.xlsx --obs_csv run.3.obs.csv --pst run.pst # IES simulated values
256
+ makepst update book.xlsx --par run.par --dry_run # preview; write nothing
257
+ [--sheet "PAR_*"] [--group hk,sy] [--overwrite_formulas] [--out copy.xlsx] [--backend openpyxl|xlwings]
227
258
  ```
228
259
 
229
260
  > **`update` saves in place unless `--out` is given.** Use `--out` the first time, and keep
@@ -240,6 +271,8 @@ refuse), only counted. `--sheet` (glob, case-insensitive) restricts the workshee
240
271
  add `--pst` to supply them). `--pst` on its own writes the control file's parameter and
241
272
  observation columns; next to `--par`, `--res` or `--obs_csv` it only supplies groups and
242
273
  measured values, and the observation sheets are not touched by a parameter update.
274
+ Likewise, `--res` or `--obs_csv` with `--pst` leaves parameter cells unchanged unless
275
+ `--par` is also supplied.
243
276
 
244
277
  The xlwings backend reads only each sheet's header, name column and target columns, writes
245
278
  contiguous runs of cells in one call each and recalculates once before saving: the
@@ -260,21 +293,75 @@ Both sheets are rewritten on each run (`--no_phi` to skip).
260
293
 
261
294
  Two backends: **xlwings** (used when installed; drives Excel invisibly, so everything in the
262
295
  workbook is preserved and formulas recalculate; Windows/macOS with Excel) or **openpyxl**
263
- (no Excel needed; keeps VBA macros; drops charts and images; formula results are stale until
264
- Excel next opens the file). The backend used is printed.
296
+ (no Excel needed; keeps VBA macros; drops charts and images; does not recalculate formulas).
297
+ Before building from a workbook updated with openpyxl, recalculate and save it in Excel;
298
+ otherwise the build may read missing or stale cached formula values. The backend used is printed.
299
+ `--dry_run` uses openpyxl to preview matched names, target columns, skipped formulas and
300
+ unmatched names without saving a workbook or manifest. It does not test whether Excel will
301
+ refuse individual writes. During `build`, missing cached formula results in referenced
302
+ tables produce a warning; existing cached values may still be stale and cannot be verified.
303
+
304
+ ### reweight — balance observation-group weights
305
+
306
+ ```
307
+ makepst reweight run.pst balanced.pst --res run.res --equal
308
+ makepst reweight run.pst balanced.pst --res run.res --targets group_shares.csv
309
+ makepst reweight run.pst scaled.pst --factor heads=0.5 --factor flow=2
310
+ makepst reweight run.pst balanced.pst --obs_csv run.3.obs.csv --real base --equal
311
+ makepst reweight run.pst adjusted.pst --res run.res --discrepancy group
312
+ makepst reweight run.pst adjusted.pst --obs_csv run.0.obs.csv --discrepancy obs
313
+ ```
314
+
315
+ `reweight` writes a new control file; it never overwrites the input. `--equal` gives each
316
+ eligible measurement group the same initial objective-function contribution; a group whose
317
+ current phi is zero cannot be scaled to a share, so it is skipped with an INFO line and its
318
+ weights are left unchanged. `--targets`
319
+ reads a CSV or `BOOK,SHEET` table with `OBGNME,TARGET_SHARE` columns. Shares are relative, so
320
+ `4,3,2` means 44.44%, 33.33% and 22.22%; the selected groups' total phi is preserved. Direct
321
+ `--factor GROUP=NUMBER` specifications multiply the existing positive weights in named groups.
322
+
323
+ `--discrepancy` makes weights consistent with the current misfit, so that each weighted
324
+ observation contributes about 1 to phi. `--discrepancy group` scales each measurement group by
325
+ one factor so its phi equals its number of weighted observations (as PEST's PWTADJ2); relative
326
+ weights inside a group are kept and zero-phi groups are skipped. It raises the weights of groups
327
+ that already fit better than that, so pair it with `--clip` when some groups fit very closely.
328
+ `--discrepancy obs` sets `w = min(w, 1/|residual|)` for each observation (pyEMU's
329
+ `adjust_weights_discrepancy` with its original ceiling): observations the model cannot fit are
330
+ lowered to a contribution of 1, weights are never raised, and zero residuals keep their weight.
331
+ It is commonly applied to the prior mean or base realization before a PESTPP-IES run so that
332
+ observation noise is not smaller than the misfit the prior can reach. Total phi is not preserved
333
+ in either variant.
334
+
335
+ For equal, target and discrepancy modes, residuals are joined by observation name while weights and group
336
+ membership come from the input control file. Groups backed by covariance matrices, `predict`,
337
+ and `regul*` groups are not measurement groups and are rejected or skipped as appropriate.
338
+
339
+ Use `--clip MIN MAX` to constrain final positive weights, for example `--clip 0 10`. Original
340
+ zero weights remain zero. Clipping is included in the balancing calculation; if a requested
341
+ target cannot be reached under the bounds, the command fails instead of reporting a misleading
342
+ result. `--report FILE` writes the before/after group table, including phi, percentages,
343
+ factors, clipping counts and the number of weights lowered (`N_REDUCED`). `--dry_run` prints that table without writing a control file,
344
+ report or manifest.
345
+
346
+ The workbook remains the source of truth. If the control file is rebuilt from a workbook later,
347
+ formula-generated workbook weights will replace weights changed by `reweight` unless the change
348
+ is deliberately transferred back to the workbook.
265
349
 
266
350
  ### parrep — .par values → new .pst
267
351
 
268
352
  ```
269
353
  makepst parrep run.pst run.par next.pst [--set noptmax=0] [--set NAME=VALUE ...] [--v1|--v2]
270
354
  makepst parrep run.pst run.3.par.csv next.pst --real best
271
- makepst parrep book.xlsm run.par next.pst # base is the workbook
355
+ makepst parrep book.xlsx run.par next.pst # base is the workbook
272
356
  ```
273
357
 
274
358
  Like PEST's PARREP: `PARVAL1` is replaced from a `.par` file or an IES realization and a new
275
359
  control file is written; `--set` changes control values on the way. All supported content of
276
360
  the input control file is preserved semantically (see below); adjustable parameters that end
277
- up outside their bounds are reported.
361
+ up outside their bounds are reported. A `.par` file records the `SCALE` and `OFFSET` each value
362
+ was written with; where those differ from the control file's (a run from before a
363
+ [`rescale`](#rescale--parameter-values--scale)), the value is converted so the model sees the
364
+ same number (`PARVAL1 × SCALE + OFFSET`). An IES ensemble carries no scales and is copied as is.
278
365
 
279
366
  With a **workbook** as the base, the control file is first built from the command in the
280
367
  workbook's `BUILD` sheet (`dump` writes one; for a hand-made workbook paste the build command
@@ -283,6 +370,57 @@ to that workbook; other relative names resolve against the workbook's folder. No
283
370
  route rebuilds from the workbook's *current* state, so any edits made since the original
284
371
  control file was written are picked up too.
285
372
 
373
+ ### rescale — parameter values → SCALE
374
+
375
+ ```
376
+ makepst rescale run.pst run_unit.pst # every parameter starts at 1
377
+ makepst rescale run.pst run_unit.pst --par "hk*,sy*" --exclude_fixed
378
+ makepst rescale run.pst run_unit.pst --pargp kh --pargp kv
379
+ makepst rescale run_unit.pst run_back.pst --undo # SCALE folded back into PARVAL1
380
+ ```
381
+
382
+ PEST applies `SCALE` and `OFFSET` only when it writes a model input file, so a parameter at
383
+ value *v* can start at 1 without the model seeing any difference: `PARVAL1 = 1`,
384
+ `SCALE = SCALE × v`, bounds divided by *v* (swapped when *v* < 0), `OFFSET` unchanged. `--undo`
385
+ does the reverse (`PARVAL1 × SCALE`, bounds likewise, `SCALE = 1`).
386
+
387
+ - **Selection:** all parameters by default, fixed ones included; `--par` (names or globs) and
388
+ `--pargp` narrow it, and both must match when both are given; `--exclude_fixed` leaves fixed
389
+ parameters alone. Tied children follow a selected parent. Parameters at zero are skipped and
390
+ listed.
391
+ - **Prior information** is written in PEST's own units, so every equation naming a rescaled
392
+ parameter is rewritten exactly: a linear term takes *v* into its coefficient, a log term
393
+ moves `log10(v)` to the right-hand side. Each equation's residual is the same number as
394
+ before, so its weight is unchanged.
395
+ - **Warned, not converted:** settings in parameter units that one factor per parameter can't
396
+ convert — `INCTYP absolute` and `DERINCLB` of groups holding rescaled parameters, `absolute(n)`
397
+ change limits — and files named in the control file that were written for the old values
398
+ (`++parcov`, `++base_jacobian`, `++ies_par_en`, SVD-assist `basepestfile` / `basejacfile`).
399
+ A Jacobian given to PEST another way (a restart, a command-line switch) can't be seen from
400
+ the control file; don't reuse one across a rescale.
401
+
402
+ `--set`, `--eol`, `--v1|--v2` and `--no_manifest` work as for `parrep`.
403
+
404
+ ### hpstart — modeled observations → new PEST_HP .hp file
405
+
406
+ ```
407
+ makepst hpstart case.pst start.hp --template case.hp --res case.res
408
+ makepst hpstart case.pst start.hp --template case.hp --obs_csv case.3.obs.csv --real best
409
+ makepst hpstart case.pst start.hp --template case.hp --res case.rei --par case.par
410
+ ```
411
+
412
+ By default, parameter values are copied unchanged from the template `.hp`, **not** from
413
+ `PARVAL1` in the `.pst`. Use `--par` to replace them with values from a PEST `.par` file
414
+ or a selected IES parameter realization. The `.pst` supplies parameter and observation
415
+ names and order; the template supplies PEST_HP instruction metadata that a residual or
416
+ IES observation file cannot provide. `hpstart` replaces the template's reference
417
+ modeled-observation array in `.pst` observation order. Every required observation must
418
+ be present with a finite value; extra result rows are ignored. The template must come
419
+ from the same control-file layout and
420
+ observation/parameter order. The binary format stores counts but not names, so matching
421
+ counts alone cannot establish that the template has the right order. The original `.hp`
422
+ is never modified, and the new file receives a provenance manifest by default.
423
+
286
424
  ### tempchek — templates → model input files
287
425
 
288
426
  ```
@@ -321,8 +459,8 @@ read and observations the control file lists but no file produced are reported.
321
459
  ### diff — what changed
322
460
 
323
461
  ```
324
- makepst diff tr12.pst tr13.pst [--xlsx changes.xlsx] [--rtol 1e-9] [--max_rows 50]
325
- makepst diff tr13.xlsm tr13.pst # is the control file still what the workbook says?
462
+ makepst diff tr12.pst tr13.pst [--xlsx changes.xlsx] [--file changes.txt] [--rtol 1e-9] [--max_rows 50]
463
+ makepst diff tr13.xlsx tr13.pst # is the control file still what the workbook says?
326
464
  ```
327
465
 
328
466
  Compares the tables, not the text: parameters and observations added, removed or changed
@@ -332,6 +470,40 @@ instruction pairs, command lines and header comments. Numbers are compared with
332
470
  tolerance, so `49.7377` vs `49.73775` shows up at the default `1e-9` and disappears at
333
471
  `--rtol 1e-5`. Prints a table per section and a one-line summary; `--xlsx` writes the same
334
472
  tables to a workbook for review. Exits 1 when there are differences, like `diff`.
473
+ `--file` writes the complete text report, including rows beyond the console's `--max_rows` limit.
474
+
475
+ ### log — the run ledger
476
+
477
+ ```
478
+ makepst log [FOLDER ...] [--file tr13.xlsx] [--command build] [--last 10] [--check] [--no_recursive]
479
+ ```
480
+
481
+ Every manifest under the folders (default: the current one, recursively), oldest first, one
482
+ line each: when, which command, which output, from which sources, the dimensions, the
483
+ makePst version. `--file` keeps the entries whose output or sources include a file — by name,
484
+ path or **sha256 prefix**, so `--file b43fc509` answers "which runs used *that* version of
485
+ the workbook?" even after the file was renamed or overwritten. `--check` adds a column from
486
+ `provenance`: `unchanged` / `changed` / `missing`.
487
+
488
+ ```
489
+ 2026-09-19 21:14 build pest/tr13.pst <- tr13.xlsx [1068 par 41747 obs 159 prior] makepst 0.4.0
490
+ 2026-09-21 08:10 update tr13-xlwings.xlsx <- tr13.xlsx, tr13.par [1068 rows in 8 sheets] makepst 0.4.0
491
+ ```
492
+
493
+ ### bundle — a run, zipped for review
494
+
495
+ ```
496
+ makepst bundle model.pst [run.zip] [--sources] [--outputs] [--extra "bin/**"] [--list] [--strict]
497
+ ```
498
+
499
+ Zips the control file with its manifest, every template and instruction file, the model
500
+ input files the templates write, and any file named on the model command line, with paths
501
+ relative to the run directory so the archive unzips runnable. `--sources` adds the manifest's
502
+ sources (the workbook, the `.par` ...), `--outputs` the model output files, `--extra` any
503
+ glob relative to the run directory (the model binaries, say). Files the control file
504
+ references but that don't exist are listed as `MISSING` (exit 1 with `--strict`); `--list`
505
+ shows what would be bundled without writing. The zip gets its own manifest, so `log` shows
506
+ who bundled what and when.
335
507
 
336
508
  ## What round-tripping preserves
337
509
 
@@ -343,11 +515,13 @@ Preserved: every parameter, group, observation, prior-information equation, tied
343
515
  template/instruction pair, command line, `++` option, header comment, control-data value
344
516
  (including PEST_HP keyed tokens), SVD / LSQR / AUI / SVD-assist / regularisation values, and
345
517
  the verbatim text of `* sensitivity reuse`, `* derivatives command line`, `* predictive
346
- analysis` and `* pareto`.
518
+ analysis` and `* pareto`. Any section unknown to this version of makePst is also retained
519
+ verbatim at its original position, so newer or site-specific PEST content survives a round trip.
347
520
 
348
521
  Normalised: parameter, observation and group names are lower-cased (PEST is
349
522
  case-insensitive); numbers are written with 11 significant digits; whitespace and column
350
- alignment are makePst's own; comments *inside* sections are dropped; observation groups are
523
+ alignment in understood sections is makePst's own; comments *inside understood sections* are
524
+ dropped; observation groups are
351
525
  listed in first-use order (or the order of the source file when reading a `.pst`).
352
526
 
353
527
  Also preserved: covariance-file references in `* observation groups` (an `OBSGP` sheet on
@@ -357,27 +531,27 @@ written back as version 2 unless `--v1` is given; `--v2` writes any control file
357
531
  format, with `case.par_data.csv` / `obs_data` / `pargp_data` / `prior_data` beside it. Both
358
532
  versions of the same content are identical after reading (`makepst diff` says so).
359
533
 
360
- Not preserved: any unrecognised section (dropped with a warning naming it).
361
-
362
534
  ## Compatibility
363
535
 
364
536
  | | |
365
537
  |---|---|
366
538
  | PEST dialects | PEST, PEST_HP (`win_mrun_hours=`, `uptestmin=`, `uptestlim=`, `absparmax(n)=`), PEST++ (`++name(value)` options) |
367
- | Control-file sections | control data, singular value decomposition, lsqr, automatic user intervention, svd assist, parameter groups, parameter data (incl. tied pairs), observation groups, observation data, model command line, model input/output, prior information (with `&` continuation lines), regularisation. Kept verbatim: sensitivity reuse, derivatives command line, predictive analysis, pareto. |
539
+ | Control-file sections | control data, singular value decomposition, lsqr, automatic user intervention, svd assist, parameter groups, parameter data (incl. tied pairs), observation groups, observation data, model command line, model input/output, prior information (with `&` continuation lines), regularisation. Kept verbatim: sensitivity reuse, derivatives command line, predictive analysis, pareto, and all unknown sections. |
368
540
  | File formats | classic (version 1) and PEST++ version 2 (`pcf version=2`, `* control data keyword` with control variables and `++` options, `* … external` csv tables with `sep=` / `missing_values=`, `partied` column); observation covariance files in `* observation groups` |
369
- | Not supported | unknown sections — dropped with a warning |
370
541
  | Result files | `.par`, `.res` / `.rei`, PESTPP-IES `case.N.par.csv` / `case.N.obs.csv` / `case.phi.actual.csv` |
371
- | Table inputs | `.xlsx` / `.xlsm` sheets (openpyxl), `.csv` |
542
+ | Table inputs | `.xlsx` / `.xlsx` sheets (openpyxl), `.csv` |
372
543
  | Workbook update | openpyxl backend on any platform (macros kept, charts/images dropped, no recalculation); xlwings backend on Windows/macOS with Excel (everything kept, recalculated) |
373
544
  | Platforms | CI runs the suite on Ubuntu 24.04 and Windows Server 2022 for Python 3.9, 3.11 and 3.13. macOS is expected to work but is not exercised in CI. |
374
545
  | Python | ≥ 3.9; pandas ≥ 2.0 (tested with 2.3 and 3.0), numpy, openpyxl ≥ 3.1; optional xlwings, pyemu, pytest |
375
546
 
376
547
  ## Validation
377
548
 
378
- Two layers. `Pst.validate()` runs before every write (`build`, `dump`, `parrep`, `write_pst`)
379
- and enforces **internal consistency** of the tables, changing what it can and refusing what it
380
- cannot:
549
+ `Pst.validate()` is a read-only audit of the in-memory tables. It returns a report with
550
+ `errors`, `warnings`, and `fixes_available`; inspecting it never changes the `Pst`. Apply
551
+ reviewed fixes explicitly with `pst.apply_fixes(report)`, or use `pst.normalize()` to apply
552
+ all currently available normalizations. Writers refuse validation errors and serialize the
553
+ current tables without modifying them. The CLI's file-building commands call `normalize()`
554
+ explicitly before writing.
381
555
 
382
556
  | check | outcome |
383
557
  |---|---|
@@ -385,19 +559,19 @@ cannot:
385
559
  | duplicate parameter, observation or prior-information names | error |
386
560
  | parameter group used but not defined | error |
387
561
  | tied parameter whose target is missing, or tied to a tied parameter | error |
388
- | tied to a fixed parameter | parameter becomes fixed (message) |
389
- | parameter group defined but unused | dropped from the file |
390
- | prior equation referencing a fixed, tied or missing parameter | dropped (message) |
562
+ | tied to a fixed parameter | error and fix available: convert it to fixed |
563
+ | parameter group defined but unused | fix available: remove the group |
564
+ | prior equation referencing a fixed, tied or missing parameter | error and fix available: rebuild prior information, dropping those equations |
391
565
  | adjustable `PARVAL1` outside `[PARLBND, PARUBND]` | warning |
392
566
  | control value that is computed (`npar` …) or unknown | ignored (warning) |
393
567
 
394
- `makepst validate` is the **pestchek-style report**: it changes nothing, looks beyond the
395
- tables at the files the control file points to, and exits 1 on errors so a batch file can
396
- stop:
568
+ `makepst validate` uses the same table checks and adds the **pestchek-style report**: it
569
+ changes nothing, looks beyond the tables at the files the control file points to, and exits 1
570
+ on errors so a batch file can stop:
397
571
 
398
572
  ```
399
573
  makepst validate model.pst [--outputs] [--strict] [--quiet] [--base_dir DIR]
400
- makepst validate tr13.xlsm # a workbook with a BUILD sheet is built first, in memory
574
+ makepst validate tr13.xlsx # a workbook with a BUILD sheet is built first, in memory
401
575
  ```
402
576
 
403
577
  | check | severity |
@@ -414,13 +588,23 @@ makepst validate tr13.xlsm # a workbook with a BUILD sheet is built f
414
588
  | template: parameter space narrower than 3 characters or containing a tab | error |
415
589
  | template: a value that cannot be written into its space as PEST writes numbers (`PRECIS` / `DPOINT` honoured), e.g. `1e-10` in five characters | error |
416
590
  | instruction file syntax: `l` / `t` integers, marker delimiters, `!` balance, `[name]n1:n2` order, `dum` in a fixed field, continuation `&` placement, `l` only at the start of a line, `t` and fixed columns moving left to right, unknown instructions — checked statically, without model output | error |
417
- | section in the `.pst` that makePst drops on read | warning |
591
+ | `++` option with a value PEST++ does not accept (integer given `2.5`, boolean given `t` or `yes`, number list with a non-number, `svd_pack` / `glm_normal_form` / `ies_subset_how` outside their choices, `ies_num_reals` < 1, …); the same option twice, directly or through an alias (`ies_par_en` and `ies_parameter_ensemble`) | error |
592
+ | `++` option not in PEST++'s option list — kept, with the closest known name (`unknown PEST++ option "ies_num_real"; did you mean "ies_num_reals"?`); a control variable such as `noptmax` given as a `++` option | warning (note with `++forgive_unknown_args(true)`) |
593
+ | deprecated `++` option that PEST++ ignores (`mat_inv`, `upgrade_augment`, `svd_pack(propack)`, …) | note |
594
+ | unknown section in the `.pst` that makePst preserves verbatim | note |
418
595
  | template or instruction file missing; bad `ptf` / `pif` line | error |
419
596
  | parameter cited in no template; template citing an unknown parameter | error |
420
597
  | observation read by no instruction file, by two files, or twice in one; unknown observation in an instruction file | error |
421
598
  | model command / model-input folder not found next to the control file | warning |
422
599
  | `--outputs`: each instruction file run against its model output file when present | note (success) / warning (failure) |
423
600
 
601
+ The `++` checks use the option list in `makepst/pestpp.py`, read off PEST++ 5.2.29's option parser
602
+ (250 options and 34 aliases, with their types). They point at where each option came from —
603
+ `PP!A17` for a workbook cell, `pp.csv:17`, `line 42` of a control file — and `build` prints them
604
+ too, without refusing to write: plain PEST ignores `++` lines, and a newer PEST++ may know an
605
+ option this list does not. PEST++ itself stops on an option it does not accept, so fix or remove
606
+ what is flagged before a run.
607
+
424
608
  The `--outputs` interpreter follows the PEST manual (primary and secondary markers, `l`, `w`,
425
609
  `t`, `!name!`, `[name]c1:c2`, `(name)c1:c2`, `dum`, `&` continuation) and catches the classic
426
610
  mistakes — a `w` too few so a label is read instead of a number, a marker that never appears
@@ -439,19 +623,19 @@ from?" after the fact:
439
623
 
440
624
  ```json
441
625
  {
442
- "makepst": "0.2.0",
626
+ "makepst": "0.4.0",
443
627
  "command": "build",
444
- "argv": ["build", "tr13.pst", "regul", "--set_ctl_xls", "tr13.xlsm,CONTROL", "..."],
628
+ "argv": ["build", "tr13.pst", "regul", "--set_ctl_xls", "tr13.xlsx,CONTROL", "..."],
445
629
  "created": "2026-09-19T13:51:39-04:00",
446
630
  "user": "hydro", "host": "OU13700", "cwd": "D:\\...\\0023-makePst",
447
631
  "python": "3.12.12", "pandas": "3.0.5", "platform": "Windows-11-10.0.26200-SP0",
448
632
  "sources": [
449
- {"path": "D:\\...\\tr13.xlsm", "sha256": "9d641a2f...", "size": 3089317,
633
+ {"path": "D:\\...\\tr13.xlsx", "sha256": "9d641a2f...", "size": 3089317,
450
634
  "modified": "2026-07-12T03:32:10-04:00", "role": "control",
451
635
  "sheets": ["CONTROL", "PARGP", "PAR_HK", "PAR_VK", "...", "PPglm"]}
452
636
  ],
453
637
  "output": {"path": "D:\\...\\tr13.pst", "sha256": "d93f317d...", "size": 2163331,
454
- "npar": 1068, "nobs": 41747, "npargp": 12, "nprior": 185, "nobsgp": 12,
638
+ "npar": 1068, "nobs": 41747, "npargp": 12, "nprior": 159, "nobsgp": 12,
455
639
  "ntplfle": 18, "ninsfle": 7, "pestmode": "regularisation"}
456
640
  }
457
641
  ```
@@ -463,6 +647,15 @@ realization and `--set` values (and, from a workbook, the resolved build argumen
463
647
  left alone, and the backend. A hash mismatch between a manifest and the workbook on disk is
464
648
  the signal that the control file no longer corresponds to the spreadsheet.
465
649
 
650
+ Run `makepst provenance model.pst` (or pass `model.pst.manifest.json`) to compare every
651
+ recorded input and output hash with the files at their recorded paths. It reports
652
+ `unchanged`, `changed`, `missing`, or `not recorded` and exits with status 1 unless all
653
+ records are unchanged. This checks file identity, not the validity of PEST tables; use
654
+ `makepst validate` for the latter. Moving files requires updating their recorded paths.
655
+ `makepst log` lists the manifests of a whole project folder as a ledger (with `--check`,
656
+ each one verified), and `makepst bundle` zips a run with everything its control file
657
+ references — see the two commands above.
658
+
466
659
  ## Relationship to pyEMU
467
660
 
468
661
  [pyEMU](https://github.com/pypest/pyemu) is the broader toolkit: it constructs PEST++
@@ -497,10 +690,11 @@ from makepst import Pst, read_pst, write_pst, to_workbook, update_workbook, load
497
690
  pst = read_pst('run.pst') # Pst: .par/.obs/.pargp/.prior DataFrames, .control dict, ...
498
691
  pst.fill_parval('run.3.par.csv', real='best')
499
692
  pst.set_control({'noptmax': 0})
693
+ pst.rescale_par(patterns=['hk*'], include_fixed=False) # values -> SCALE; returns a report dict
500
694
  write_pst(pst, 'run_final.pst', dump_tpl=False)
501
695
 
502
696
  to_workbook(pst, 'run.xlsx', split=True)
503
- update_workbook('book.xlsm', par='run.par', res='run.res', out='book_results.xlsm',
697
+ update_workbook('book.xlsx', par='run.par', res='run.res', out='book_results.xlsx',
504
698
  sheets=['PAR_*'], groups=['hk'])
505
699
  ```
506
700
 
@@ -532,14 +726,15 @@ makepst/
532
726
  writer.py Pst -> .pst text (+ dump.tpl)
533
727
  reader.py .pst text -> Pst
534
728
  excel.py sheets -> Pst; Pst -> workbook; results -> existing workbook
535
- cli.py init / validate / build / dump / update / parrep / diff / tempchek / inschek
729
+ cli.py init / validate / build / dump / update / parrep / set / rescale / reweight / diff / tempchek / inschek / log / bundle
536
730
  diff.py semantic comparison of two Pst objects
537
731
  phi.py objective function by group from residuals; IES realization phis
732
+ reweight.py observation-group balancing, scaling and bounded weight adjustment
538
733
  pyemu_bridge.py to_pyemu / from_pyemu
539
734
  checks.py the validate report: table checks, template / instruction cross-checks, instruction interpreter
540
735
  rules.py pestchek's rules (parameters, groups, observations, prior information, control variables)
541
736
  model_files.py PEST's number writer, template filling, .obf files (tempchek / inschek)
542
- provenance.py the <output>.manifest.json sidecar
737
+ provenance.py the <output>.manifest.json sidecar; provenance / log / bundle
543
738
  starter.py the `init` workbook: control-variable descriptions, example rows, drop-downs
544
739
  examples/minimal/ the tutorial inputs
545
740
  tests/ suite + fixture generator
@@ -552,7 +747,7 @@ stored anywhere. To release: bump the version in `pyproject.toml` and the fallba
552
747
  `makepst/__init__.py` (a test keeps them equal), add a `CHANGELOG.md` entry, commit, then
553
748
 
554
749
  ```
555
- git tag v0.2.0 && git push origin main v0.2.0
750
+ git tag v0.4.0 && git push origin main v0.4.0
556
751
  ```
557
752
 
558
753
  The `publish` workflow checks that the tag matches the package version, runs the tests, builds