makepst 0.4.0__tar.gz → 0.5.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.
- {makepst-0.4.0 → makepst-0.5.0}/CHANGELOG.md +36 -0
- {makepst-0.4.0 → makepst-0.5.0}/CITATION.cff +2 -2
- {makepst-0.4.0 → makepst-0.5.0}/MANIFEST.in +1 -0
- {makepst-0.4.0 → makepst-0.5.0}/PKG-INFO +28 -7
- {makepst-0.4.0 → makepst-0.5.0}/README.md +27 -6
- {makepst-0.4.0 → makepst-0.5.0}/makepst/__init__.py +1 -1
- {makepst-0.4.0 → makepst-0.5.0}/makepst/cli.py +42 -15
- {makepst-0.4.0 → makepst-0.5.0}/makepst/excel.py +3 -0
- {makepst-0.4.0 → makepst-0.5.0}/makepst/pestpp.py +4 -1
- {makepst-0.4.0 → makepst-0.5.0}/makepst/provenance.py +9 -0
- {makepst-0.4.0 → makepst-0.5.0}/makepst/pst.py +39 -5
- {makepst-0.4.0 → makepst-0.5.0}/makepst/reader.py +9 -1
- {makepst-0.4.0 → makepst-0.5.0}/makepst/rules.py +84 -4
- {makepst-0.4.0 → makepst-0.5.0}/makepst/sections.py +63 -19
- {makepst-0.4.0 → makepst-0.5.0}/makepst/starter.py +9 -0
- {makepst-0.4.0 → makepst-0.5.0}/makepst/writer.py +17 -7
- {makepst-0.4.0 → makepst-0.5.0}/makepst.egg-info/PKG-INFO +28 -7
- {makepst-0.4.0 → makepst-0.5.0}/makepst.egg-info/SOURCES.txt +6 -1
- {makepst-0.4.0 → makepst-0.5.0}/pyproject.toml +1 -1
- makepst-0.5.0/tests/test_convenience.py +113 -0
- {makepst-0.4.0 → makepst-0.5.0}/tests/test_makepst.py +12 -0
- makepst-0.5.0/tests/test_pest_hp.py +172 -0
- makepst-0.5.0/tests/test_rule_sources.py +152 -0
- makepst-0.5.0/tools/rule_sources.py +353 -0
- makepst-0.5.0/tools/snapshots/pestchek.json +719 -0
- makepst-0.5.0/tools/snapshots/pestpp.json +322 -0
- makepst-0.4.0/tests/test_convenience.py +0 -60
- {makepst-0.4.0 → makepst-0.5.0}/LICENSE +0 -0
- {makepst-0.4.0 → makepst-0.5.0}/examples/minimal/control.csv +0 -0
- {makepst-0.4.0 → makepst-0.5.0}/examples/minimal/flow.ins +0 -0
- {makepst-0.4.0 → makepst-0.5.0}/examples/minimal/flow.out +0 -0
- {makepst-0.4.0 → makepst-0.5.0}/examples/minimal/heads.ins +0 -0
- {makepst-0.4.0 → makepst-0.5.0}/examples/minimal/heads.out +0 -0
- {makepst-0.4.0 → makepst-0.5.0}/examples/minimal/io.csv +0 -0
- {makepst-0.4.0 → makepst-0.5.0}/examples/minimal/model.in +0 -0
- {makepst-0.4.0 → makepst-0.5.0}/examples/minimal/model.par +0 -0
- {makepst-0.4.0 → makepst-0.5.0}/examples/minimal/model.py +0 -0
- {makepst-0.4.0 → makepst-0.5.0}/examples/minimal/model.res +0 -0
- {makepst-0.4.0 → makepst-0.5.0}/examples/minimal/model.tpl +0 -0
- {makepst-0.4.0 → makepst-0.5.0}/examples/minimal/obs.csv +0 -0
- {makepst-0.4.0 → makepst-0.5.0}/examples/minimal/par.csv +0 -0
- {makepst-0.4.0 → makepst-0.5.0}/examples/minimal/pargp.csv +0 -0
- {makepst-0.4.0 → makepst-0.5.0}/makepst/__main__.py +0 -0
- {makepst-0.4.0 → makepst-0.5.0}/makepst/checks.py +0 -0
- {makepst-0.4.0 → makepst-0.5.0}/makepst/diff.py +0 -0
- {makepst-0.4.0 → makepst-0.5.0}/makepst/hpstart.py +0 -0
- {makepst-0.4.0 → makepst-0.5.0}/makepst/model_files.py +0 -0
- {makepst-0.4.0 → makepst-0.5.0}/makepst/phi.py +0 -0
- {makepst-0.4.0 → makepst-0.5.0}/makepst/pyemu_bridge.py +0 -0
- {makepst-0.4.0 → makepst-0.5.0}/makepst/reweight.py +0 -0
- {makepst-0.4.0 → makepst-0.5.0}/makepst.egg-info/dependency_links.txt +0 -0
- {makepst-0.4.0 → makepst-0.5.0}/makepst.egg-info/entry_points.txt +0 -0
- {makepst-0.4.0 → makepst-0.5.0}/makepst.egg-info/requires.txt +0 -0
- {makepst-0.4.0 → makepst-0.5.0}/makepst.egg-info/top_level.txt +0 -0
- {makepst-0.4.0 → makepst-0.5.0}/setup.cfg +0 -0
- {makepst-0.4.0 → makepst-0.5.0}/tests/data/demo.3.obs.csv +0 -0
- {makepst-0.4.0 → makepst-0.5.0}/tests/data/demo.3.par.csv +0 -0
- {makepst-0.4.0 → makepst-0.5.0}/tests/data/demo.par +0 -0
- {makepst-0.4.0 → makepst-0.5.0}/tests/data/demo.phi.actual.csv +0 -0
- {makepst-0.4.0 → makepst-0.5.0}/tests/data/demo.pst +0 -0
- {makepst-0.4.0 → makepst-0.5.0}/tests/data/demo.res +0 -0
- {makepst-0.4.0 → makepst-0.5.0}/tests/data/demo.xlsx +0 -0
- {makepst-0.4.0 → makepst-0.5.0}/tests/make_fixture.py +0 -0
- {makepst-0.4.0 → makepst-0.5.0}/tests/test_checks.py +0 -0
- {makepst-0.4.0 → makepst-0.5.0}/tests/test_diff.py +0 -0
- {makepst-0.4.0 → makepst-0.5.0}/tests/test_examples.py +0 -0
- {makepst-0.4.0 → makepst-0.5.0}/tests/test_fixture.py +0 -0
- {makepst-0.4.0 → makepst-0.5.0}/tests/test_hpstart.py +0 -0
- {makepst-0.4.0 → makepst-0.5.0}/tests/test_log_bundle.py +0 -0
- {makepst-0.4.0 → makepst-0.5.0}/tests/test_model_files.py +0 -0
- {makepst-0.4.0 → makepst-0.5.0}/tests/test_optional_positions.py +0 -0
- {makepst-0.4.0 → makepst-0.5.0}/tests/test_optional_positions_variations.py +0 -0
- {makepst-0.4.0 → makepst-0.5.0}/tests/test_pestpp.py +0 -0
- {makepst-0.4.0 → makepst-0.5.0}/tests/test_phi.py +0 -0
- {makepst-0.4.0 → makepst-0.5.0}/tests/test_provenance.py +0 -0
- {makepst-0.4.0 → makepst-0.5.0}/tests/test_pyemu.py +0 -0
- {makepst-0.4.0 → makepst-0.5.0}/tests/test_rescale.py +0 -0
- {makepst-0.4.0 → makepst-0.5.0}/tests/test_reweight.py +0 -0
- {makepst-0.4.0 → makepst-0.5.0}/tests/test_rules.py +0 -0
- {makepst-0.4.0 → makepst-0.5.0}/tests/test_set_par.py +0 -0
- {makepst-0.4.0 → makepst-0.5.0}/tests/test_starter.py +0 -0
- {makepst-0.4.0 → makepst-0.5.0}/tests/test_v2.py +0 -0
- {makepst-0.4.0 → makepst-0.5.0}/tests/test_version.py +0 -0
|
@@ -1,5 +1,41 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.5.0 (2026-09-29)
|
|
4
|
+
|
|
5
|
+
- `tools/rule_sources.py`: are `validate`'s rules still current? `pestpp` re-reads PEST++'s option
|
|
6
|
+
parser from GitHub (names, aliases, types, choices, deprecations); `pestchek --source DIR` reads a
|
|
7
|
+
PEST download's `pestchek.F` / `cheksub.F` (control-data keywords and every message). Each is
|
|
8
|
+
compared with the reviewed snapshot in `tools/snapshots/` and with makePst's own tables; exit 1 on
|
|
9
|
+
a change, 2 when the source cannot be read; `--write` records a reviewed source. Tests hold
|
|
10
|
+
`makepst/pestpp.py` and the control-data schema to the snapshots, and a weekly GitHub workflow
|
|
11
|
+
(`.github/workflows/rule-sources.yml`) opens an issue when PEST++'s parser changes. The PEST++
|
|
12
|
+
registry is unchanged; its provenance now names commit 02e1455 (5.2.29) - the commit named before,
|
|
13
|
+
ad8428d, has identical option parsing but is labelled 5.2.28 in the source.
|
|
14
|
+
|
|
15
|
+
- `build --dry_run`: the `validate` report on the control file a build would write, from a workbook's
|
|
16
|
+
BUILD sheet or from tables named on the command line; nothing is written, no manifest; exit 1 on
|
|
17
|
+
errors. A workbook with no BUILD sheet now says how to name its tables and check them this way.
|
|
18
|
+
- `validate` on a workbook judges it as `build` would write it, after the same normalization
|
|
19
|
+
(regularisation equations from `PRIOR`/`WEIGHT`, unused groups removed, ties to fixed parameters
|
|
20
|
+
fixed), listing those as notes. It used to judge the tables before that and report errors building
|
|
21
|
+
removes: a regularisation file with no "regul" group, prior information not generated.
|
|
22
|
+
|
|
23
|
+
- PEST_HP control variables are treated as `++` options are: a control-data token makePst does not
|
|
24
|
+
know (`newhpvar=7`, a new flag) is kept with the line it came from and written back there, instead
|
|
25
|
+
of being dropped, and `validate` warns about it with the closest known name. `dump` writes it to
|
|
26
|
+
the CONTROL sheet with its line (`control data line 4`), so `build` puts it back.
|
|
27
|
+
- Every PEST_HP variable PEST 17.2's pestchek reads is now known: `run_slow_fac`, `run_abandon_fac`,
|
|
28
|
+
`jcowarnthresh`, `jcozerothresh`, `zerosenval`, `hardstophours`, `softstophours`, `reg2measrat`,
|
|
29
|
+
`orr_not_first`, and `obsreref_N`; `name = value` with spaces around `=` is read, as PEST_HP does.
|
|
30
|
+
- `validate` checks that PEST can read each control value: an integer or a number where one is
|
|
31
|
+
expected (Fortran `1d-3` included), a known word for `precis`, `dpoint` and the other flags,
|
|
32
|
+
`absparmax(n)=value` with n 1-10; and the PEST_HP ranges and combinations (`RUN_ABANDON_FAC` 0 or
|
|
33
|
+
at least 1.2, not both stop-hours variables, `JCOZEROTHRESH` above `JCOWARNTHRESH`, `REG2MEASRAT`
|
|
34
|
+
below 1, `orr_not_first` and `ZEROSENVAL` only with what they need).
|
|
35
|
+
- The rule sources are pinned and reported: `PESTCHEK_SOURCE_VERSION` (17.2) beside
|
|
36
|
+
`PESTPP_REGISTRY_VERSION` (5.2.29); `validate` prints both and every manifest records them under
|
|
37
|
+
`checked_against`.
|
|
38
|
+
|
|
3
39
|
## 0.4.0 (2026-09-27)
|
|
4
40
|
|
|
5
41
|
- `rescale` / `Pst.rescale_par()`: move parameter values into `SCALE` so each selected parameter
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
include LICENSE README.md CHANGELOG.md CITATION.cff
|
|
2
2
|
recursive-include tests *.py *.xlsx *.pst *.par *.res *.csv
|
|
3
|
+
recursive-include tools *.py *.json
|
|
3
4
|
recursive-include examples/minimal *.csv *.py *.tpl *.ins *.in *.out *.par *.res
|
|
4
5
|
prune examples/mnnrd
|
|
5
6
|
global-exclude __pycache__ *.py[cod] *.manifest.json
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: makepst
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.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
|
|
@@ -486,8 +486,8 @@ the workbook?" even after the file was renamed or overwritten. `--check` adds a
|
|
|
486
486
|
`provenance`: `unchanged` / `changed` / `missing`.
|
|
487
487
|
|
|
488
488
|
```
|
|
489
|
-
2026-09-19 21:14 build pest/tr13.pst <- tr13.xlsx [1068 par 41747 obs 159 prior] makepst 0.
|
|
490
|
-
2026-09-21 08:10 update tr13-xlwings.xlsx <- tr13.xlsx, tr13.par [1068 rows in 8 sheets] makepst 0.
|
|
489
|
+
2026-09-19 21:14 build pest/tr13.pst <- tr13.xlsx [1068 par 41747 obs 159 prior] makepst 0.5.0
|
|
490
|
+
2026-09-21 08:10 update tr13-xlwings.xlsx <- tr13.xlsx, tr13.par [1068 rows in 8 sheets] makepst 0.5.0
|
|
491
491
|
```
|
|
492
492
|
|
|
493
493
|
### bundle — a run, zipped for review
|
|
@@ -563,7 +563,8 @@ explicitly before writing.
|
|
|
563
563
|
| parameter group defined but unused | fix available: remove the group |
|
|
564
564
|
| prior equation referencing a fixed, tied or missing parameter | error and fix available: rebuild prior information, dropping those equations |
|
|
565
565
|
| adjustable `PARVAL1` outside `[PARLBND, PARUBND]` | warning |
|
|
566
|
-
| control value that is computed (`npar` …)
|
|
566
|
+
| control value that is computed (`npar` …) | ignored (warning) |
|
|
567
|
+
| control variable makePst does not know (a newer PEST_HP keyword): kept on the line it came from; a CONTROL-sheet row gives that line in `LINE` (`control data line 4`) or is ignored | warning |
|
|
567
568
|
|
|
568
569
|
`makepst validate` uses the same table checks and adds the **pestchek-style report**: it
|
|
569
570
|
changes nothing, looks beyond the tables at the files the control file points to, and exits 1
|
|
@@ -582,7 +583,9 @@ makepst validate tr13.xlsx # a workbook with a BUILD sheet is built f
|
|
|
582
583
|
| malformed prior equation; non-numeric right-hand side; `log()` used on a non-log parameter or vice versa | error |
|
|
583
584
|
| `DERCOM` beyond the number of model command lines | error |
|
|
584
585
|
| name longer than PEST's limit (12 parameter / 20 observation / 12 group; PEST++ allows 200) | warning |
|
|
585
|
-
| **pestchek's rules** (from PEST 17's `pestchek.F` / `cheksub.F`): `PARCHGLIM` vocabulary and `absolute(n)` needing `absparmax(n)=`; log parameters factor-limited with positive values; factor-limited bounds of one sign and non-zero; `SCALE ≠ 0`; tied to itself, tied or parent with initial value 0; group `none` reserved; all parameters fixed/tied; `INCTYP` / `FORCEN` / `DERMTHD` vocabularies and their compatibility; split-column ranges; duplicate groups; `dum` as an observation name; prior weights, labels (≤ 20, not an observation name), duplicated parameters, all-zero factors, group `predict`; regularisation needs a `regul*` group, prediction exactly one `predict` observation; ~60 control-variable range and consistency rules (`RLAMFAC`, `PHIRATSUF`, `FACPARMAX`, `NPHISTP`, `RELPARSTP < RELPARMAX`, `EIGTHRESH`, `PHIMLIM < PHIMACCEPT < 1.2·PHIMLIM`, `WFMIN ≤ WFINIT ≤ WFMAX`, `UPTESTMIN`/`UPTESTLIM`, SVD vs AUI vs LSQR exclusivity, …) | error |
|
|
586
|
+
| **pestchek's rules** (from PEST 17.2's `pestchek.F` / `cheksub.F`): `PARCHGLIM` vocabulary and `absolute(n)` needing `absparmax(n)=`; log parameters factor-limited with positive values; factor-limited bounds of one sign and non-zero; `SCALE ≠ 0`; tied to itself, tied or parent with initial value 0; group `none` reserved; all parameters fixed/tied; `INCTYP` / `FORCEN` / `DERMTHD` vocabularies and their compatibility; split-column ranges; duplicate groups; `dum` as an observation name; prior weights, labels (≤ 20, not an observation name), duplicated parameters, all-zero factors, group `predict`; regularisation needs a `regul*` group, prediction exactly one `predict` observation; ~60 control-variable range and consistency rules (`RLAMFAC`, `PHIRATSUF`, `FACPARMAX`, `NPHISTP`, `RELPARSTP < RELPARMAX`, `EIGTHRESH`, `PHIMLIM < PHIMACCEPT < 1.2·PHIMLIM`, `WFMIN ≤ WFINIT ≤ WFMAX`, `UPTESTMIN`/`UPTESTLIM`, SVD vs AUI vs LSQR exclusivity, …) | error |
|
|
587
|
+
| control value PEST cannot read: not an integer or a number where one is expected (Fortran `1d-3` is fine), a word outside its field's vocabulary (`precis`, `dpoint`, `obsreref`, …), `absparmax(n)=` malformed or n outside 1-10; the PEST_HP limits and combinations (`RUN_SLOW_FAC`, `RUN_ABANDON_FAC`, `HARDSTOPHOURS` / `SOFTSTOPHOURS` not both, `JCOZEROTHRESH` above `JCOWARNTHRESH`, `ZEROSENVAL`, `REG2MEASRAT` below 1, `orr_not_first` only with `obsreref`) | error |
|
|
588
|
+
| control-data token not in PEST 17.2's pestchek — kept as written, with the closest known name (`"win_mrun_hour=2" … did you mean "win_mrun_hours"?`) | warning |
|
|
586
589
|
| pestchek's warnings: derivative increment larger than a third of the range; initial value 0 with a relative increment; group with only fixed/tied parameters; log parameters with a non-relative increment; observation group listed but empty (dropped on write); `MAXSING` above the adjustable-parameter count; more than 300 adjustable parameters with `ICOV`/`ICOR`/`IEIG` on; `NOPTMAX 0` | warning |
|
|
587
590
|
| PEST_HP-only control variables present (`WIN_MRUN_HOURS`, `UPTESTMIN`, `RRFSAVE`, …; plain PEST needs `/hpstart`); observation group whose weights are all zero | note |
|
|
588
591
|
| template: parameter space narrower than 3 characters or containing a tab | error |
|
|
@@ -605,6 +608,22 @@ too, without refusing to write: plain PEST ignores `++` lines, and a newer PEST+
|
|
|
605
608
|
option this list does not. PEST++ itself stops on an option it does not accept, so fix or remove
|
|
606
609
|
what is flagged before a run.
|
|
607
610
|
|
|
611
|
+
`validate` prints the two releases its rules were read from, and every manifest records them
|
|
612
|
+
(`checked_against`). Whether they are still current is checked by `tools/rule_sources.py`, which
|
|
613
|
+
re-reads the upstream source and compares it with the snapshot the rules were written against
|
|
614
|
+
(`tools/snapshots/`), exiting 1 on any change:
|
|
615
|
+
|
|
616
|
+
```
|
|
617
|
+
python tools/rule_sources.py pestpp # usgs/pestpp develop, from GitHub
|
|
618
|
+
python tools/rule_sources.py pestchek --source pest_source # the source folder of a PEST download
|
|
619
|
+
```
|
|
620
|
+
|
|
621
|
+
It reports added and removed `++` options, aliases, value types and choices; for PEST, the
|
|
622
|
+
control-data keywords pestchek reads and its messages (each message is a rule); and anything
|
|
623
|
+
makePst's own tables no longer agree with. It edits nothing: port what changed, then run it
|
|
624
|
+
again with `--write` to record the new source as reviewed, and the tests hold the code to that
|
|
625
|
+
snapshot. The PEST++ check runs weekly on GitHub and opens an issue when the parser changes.
|
|
626
|
+
|
|
608
627
|
The `--outputs` interpreter follows the PEST manual (primary and secondary markers, `l`, `w`,
|
|
609
628
|
`t`, `!name!`, `[name]c1:c2`, `(name)c1:c2`, `dum`, `&` continuation) and catches the classic
|
|
610
629
|
mistakes — a `w` too few so a label is read instead of a number, a marker that never appears
|
|
@@ -623,12 +642,13 @@ from?" after the fact:
|
|
|
623
642
|
|
|
624
643
|
```json
|
|
625
644
|
{
|
|
626
|
-
"makepst": "0.
|
|
645
|
+
"makepst": "0.5.0",
|
|
627
646
|
"command": "build",
|
|
628
647
|
"argv": ["build", "tr13.pst", "regul", "--set_ctl_xls", "tr13.xlsx,CONTROL", "..."],
|
|
629
648
|
"created": "2026-09-19T13:51:39-04:00",
|
|
630
649
|
"user": "hydro", "host": "OU13700", "cwd": "D:\\...\\0023-makePst",
|
|
631
650
|
"python": "3.12.12", "pandas": "3.0.5", "platform": "Windows-11-10.0.26200-SP0",
|
|
651
|
+
"checked_against": {"pestchek": "17.2", "pestpp": "5.2.29"},
|
|
632
652
|
"sources": [
|
|
633
653
|
{"path": "D:\\...\\tr13.xlsx", "sha256": "9d641a2f...", "size": 3089317,
|
|
634
654
|
"modified": "2026-07-12T03:32:10-04:00", "role": "control",
|
|
@@ -738,6 +758,7 @@ makepst/
|
|
|
738
758
|
starter.py the `init` workbook: control-variable descriptions, example rows, drop-downs
|
|
739
759
|
examples/minimal/ the tutorial inputs
|
|
740
760
|
tests/ suite + fixture generator
|
|
761
|
+
tools/ rule_sources.py (are the rules current with PEST / PEST++?) and its snapshots
|
|
741
762
|
```
|
|
742
763
|
|
|
743
764
|
## Releasing
|
|
@@ -747,7 +768,7 @@ stored anywhere. To release: bump the version in `pyproject.toml` and the fallba
|
|
|
747
768
|
`makepst/__init__.py` (a test keeps them equal), add a `CHANGELOG.md` entry, commit, then
|
|
748
769
|
|
|
749
770
|
```
|
|
750
|
-
git tag v0.
|
|
771
|
+
git tag v0.5.0 && git push origin main v0.5.0
|
|
751
772
|
```
|
|
752
773
|
|
|
753
774
|
The `publish` workflow checks that the tag matches the package version, runs the tests, builds
|
|
@@ -455,8 +455,8 @@ the workbook?" even after the file was renamed or overwritten. `--check` adds a
|
|
|
455
455
|
`provenance`: `unchanged` / `changed` / `missing`.
|
|
456
456
|
|
|
457
457
|
```
|
|
458
|
-
2026-09-19 21:14 build pest/tr13.pst <- tr13.xlsx [1068 par 41747 obs 159 prior] makepst 0.
|
|
459
|
-
2026-09-21 08:10 update tr13-xlwings.xlsx <- tr13.xlsx, tr13.par [1068 rows in 8 sheets] makepst 0.
|
|
458
|
+
2026-09-19 21:14 build pest/tr13.pst <- tr13.xlsx [1068 par 41747 obs 159 prior] makepst 0.5.0
|
|
459
|
+
2026-09-21 08:10 update tr13-xlwings.xlsx <- tr13.xlsx, tr13.par [1068 rows in 8 sheets] makepst 0.5.0
|
|
460
460
|
```
|
|
461
461
|
|
|
462
462
|
### bundle — a run, zipped for review
|
|
@@ -532,7 +532,8 @@ explicitly before writing.
|
|
|
532
532
|
| parameter group defined but unused | fix available: remove the group |
|
|
533
533
|
| prior equation referencing a fixed, tied or missing parameter | error and fix available: rebuild prior information, dropping those equations |
|
|
534
534
|
| adjustable `PARVAL1` outside `[PARLBND, PARUBND]` | warning |
|
|
535
|
-
| control value that is computed (`npar` …)
|
|
535
|
+
| control value that is computed (`npar` …) | ignored (warning) |
|
|
536
|
+
| control variable makePst does not know (a newer PEST_HP keyword): kept on the line it came from; a CONTROL-sheet row gives that line in `LINE` (`control data line 4`) or is ignored | warning |
|
|
536
537
|
|
|
537
538
|
`makepst validate` uses the same table checks and adds the **pestchek-style report**: it
|
|
538
539
|
changes nothing, looks beyond the tables at the files the control file points to, and exits 1
|
|
@@ -551,7 +552,9 @@ makepst validate tr13.xlsx # a workbook with a BUILD sheet is built f
|
|
|
551
552
|
| malformed prior equation; non-numeric right-hand side; `log()` used on a non-log parameter or vice versa | error |
|
|
552
553
|
| `DERCOM` beyond the number of model command lines | error |
|
|
553
554
|
| name longer than PEST's limit (12 parameter / 20 observation / 12 group; PEST++ allows 200) | warning |
|
|
554
|
-
| **pestchek's rules** (from PEST 17's `pestchek.F` / `cheksub.F`): `PARCHGLIM` vocabulary and `absolute(n)` needing `absparmax(n)=`; log parameters factor-limited with positive values; factor-limited bounds of one sign and non-zero; `SCALE ≠ 0`; tied to itself, tied or parent with initial value 0; group `none` reserved; all parameters fixed/tied; `INCTYP` / `FORCEN` / `DERMTHD` vocabularies and their compatibility; split-column ranges; duplicate groups; `dum` as an observation name; prior weights, labels (≤ 20, not an observation name), duplicated parameters, all-zero factors, group `predict`; regularisation needs a `regul*` group, prediction exactly one `predict` observation; ~60 control-variable range and consistency rules (`RLAMFAC`, `PHIRATSUF`, `FACPARMAX`, `NPHISTP`, `RELPARSTP < RELPARMAX`, `EIGTHRESH`, `PHIMLIM < PHIMACCEPT < 1.2·PHIMLIM`, `WFMIN ≤ WFINIT ≤ WFMAX`, `UPTESTMIN`/`UPTESTLIM`, SVD vs AUI vs LSQR exclusivity, …) | error |
|
|
555
|
+
| **pestchek's rules** (from PEST 17.2's `pestchek.F` / `cheksub.F`): `PARCHGLIM` vocabulary and `absolute(n)` needing `absparmax(n)=`; log parameters factor-limited with positive values; factor-limited bounds of one sign and non-zero; `SCALE ≠ 0`; tied to itself, tied or parent with initial value 0; group `none` reserved; all parameters fixed/tied; `INCTYP` / `FORCEN` / `DERMTHD` vocabularies and their compatibility; split-column ranges; duplicate groups; `dum` as an observation name; prior weights, labels (≤ 20, not an observation name), duplicated parameters, all-zero factors, group `predict`; regularisation needs a `regul*` group, prediction exactly one `predict` observation; ~60 control-variable range and consistency rules (`RLAMFAC`, `PHIRATSUF`, `FACPARMAX`, `NPHISTP`, `RELPARSTP < RELPARMAX`, `EIGTHRESH`, `PHIMLIM < PHIMACCEPT < 1.2·PHIMLIM`, `WFMIN ≤ WFINIT ≤ WFMAX`, `UPTESTMIN`/`UPTESTLIM`, SVD vs AUI vs LSQR exclusivity, …) | error |
|
|
556
|
+
| control value PEST cannot read: not an integer or a number where one is expected (Fortran `1d-3` is fine), a word outside its field's vocabulary (`precis`, `dpoint`, `obsreref`, …), `absparmax(n)=` malformed or n outside 1-10; the PEST_HP limits and combinations (`RUN_SLOW_FAC`, `RUN_ABANDON_FAC`, `HARDSTOPHOURS` / `SOFTSTOPHOURS` not both, `JCOZEROTHRESH` above `JCOWARNTHRESH`, `ZEROSENVAL`, `REG2MEASRAT` below 1, `orr_not_first` only with `obsreref`) | error |
|
|
557
|
+
| control-data token not in PEST 17.2's pestchek — kept as written, with the closest known name (`"win_mrun_hour=2" … did you mean "win_mrun_hours"?`) | warning |
|
|
555
558
|
| pestchek's warnings: derivative increment larger than a third of the range; initial value 0 with a relative increment; group with only fixed/tied parameters; log parameters with a non-relative increment; observation group listed but empty (dropped on write); `MAXSING` above the adjustable-parameter count; more than 300 adjustable parameters with `ICOV`/`ICOR`/`IEIG` on; `NOPTMAX 0` | warning |
|
|
556
559
|
| PEST_HP-only control variables present (`WIN_MRUN_HOURS`, `UPTESTMIN`, `RRFSAVE`, …; plain PEST needs `/hpstart`); observation group whose weights are all zero | note |
|
|
557
560
|
| template: parameter space narrower than 3 characters or containing a tab | error |
|
|
@@ -574,6 +577,22 @@ too, without refusing to write: plain PEST ignores `++` lines, and a newer PEST+
|
|
|
574
577
|
option this list does not. PEST++ itself stops on an option it does not accept, so fix or remove
|
|
575
578
|
what is flagged before a run.
|
|
576
579
|
|
|
580
|
+
`validate` prints the two releases its rules were read from, and every manifest records them
|
|
581
|
+
(`checked_against`). Whether they are still current is checked by `tools/rule_sources.py`, which
|
|
582
|
+
re-reads the upstream source and compares it with the snapshot the rules were written against
|
|
583
|
+
(`tools/snapshots/`), exiting 1 on any change:
|
|
584
|
+
|
|
585
|
+
```
|
|
586
|
+
python tools/rule_sources.py pestpp # usgs/pestpp develop, from GitHub
|
|
587
|
+
python tools/rule_sources.py pestchek --source pest_source # the source folder of a PEST download
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
It reports added and removed `++` options, aliases, value types and choices; for PEST, the
|
|
591
|
+
control-data keywords pestchek reads and its messages (each message is a rule); and anything
|
|
592
|
+
makePst's own tables no longer agree with. It edits nothing: port what changed, then run it
|
|
593
|
+
again with `--write` to record the new source as reviewed, and the tests hold the code to that
|
|
594
|
+
snapshot. The PEST++ check runs weekly on GitHub and opens an issue when the parser changes.
|
|
595
|
+
|
|
577
596
|
The `--outputs` interpreter follows the PEST manual (primary and secondary markers, `l`, `w`,
|
|
578
597
|
`t`, `!name!`, `[name]c1:c2`, `(name)c1:c2`, `dum`, `&` continuation) and catches the classic
|
|
579
598
|
mistakes — a `w` too few so a label is read instead of a number, a marker that never appears
|
|
@@ -592,12 +611,13 @@ from?" after the fact:
|
|
|
592
611
|
|
|
593
612
|
```json
|
|
594
613
|
{
|
|
595
|
-
"makepst": "0.
|
|
614
|
+
"makepst": "0.5.0",
|
|
596
615
|
"command": "build",
|
|
597
616
|
"argv": ["build", "tr13.pst", "regul", "--set_ctl_xls", "tr13.xlsx,CONTROL", "..."],
|
|
598
617
|
"created": "2026-09-19T13:51:39-04:00",
|
|
599
618
|
"user": "hydro", "host": "OU13700", "cwd": "D:\\...\\0023-makePst",
|
|
600
619
|
"python": "3.12.12", "pandas": "3.0.5", "platform": "Windows-11-10.0.26200-SP0",
|
|
620
|
+
"checked_against": {"pestchek": "17.2", "pestpp": "5.2.29"},
|
|
601
621
|
"sources": [
|
|
602
622
|
{"path": "D:\\...\\tr13.xlsx", "sha256": "9d641a2f...", "size": 3089317,
|
|
603
623
|
"modified": "2026-07-12T03:32:10-04:00", "role": "control",
|
|
@@ -707,6 +727,7 @@ makepst/
|
|
|
707
727
|
starter.py the `init` workbook: control-variable descriptions, example rows, drop-downs
|
|
708
728
|
examples/minimal/ the tutorial inputs
|
|
709
729
|
tests/ suite + fixture generator
|
|
730
|
+
tools/ rule_sources.py (are the rules current with PEST / PEST++?) and its snapshots
|
|
710
731
|
```
|
|
711
732
|
|
|
712
733
|
## Releasing
|
|
@@ -716,7 +737,7 @@ stored anywhere. To release: bump the version in `pyproject.toml` and the fallba
|
|
|
716
737
|
`makepst/__init__.py` (a test keeps them equal), add a `CHANGELOG.md` entry, commit, then
|
|
717
738
|
|
|
718
739
|
```
|
|
719
|
-
git tag v0.
|
|
740
|
+
git tag v0.5.0 && git push origin main v0.5.0
|
|
720
741
|
```
|
|
721
742
|
|
|
722
743
|
The `publish` workflow checks that the tag matches the package version, runs the tests, builds
|
|
@@ -3,7 +3,7 @@ try: # the installed package's metadata (
|
|
|
3
3
|
from importlib.metadata import version as _dist_version
|
|
4
4
|
__version__ = _dist_version('makepst')
|
|
5
5
|
except Exception: # a checkout run without installing
|
|
6
|
-
__version__ = '0.
|
|
6
|
+
__version__ = '0.5.0'
|
|
7
7
|
|
|
8
8
|
from .excel import load_table, to_workbook, update_workbook
|
|
9
9
|
from .hpstart import write_hpstart
|
|
@@ -49,6 +49,8 @@ def _build_parser(p):
|
|
|
49
49
|
p.add_argument('--no_dump_tpl', action='store_true', help="don't write dump.tpl next to the pst")
|
|
50
50
|
p.add_argument('--no_manifest', action='store_true', help="don't write <pst>.manifest.json")
|
|
51
51
|
p.add_argument('--out', metavar='PST', help='with a workbook: where to write (default: the name in the BUILD sheet)')
|
|
52
|
+
p.add_argument('--dry_run', action='store_true',
|
|
53
|
+
help="report as `makepst validate` would on the control file this builds; write nothing")
|
|
52
54
|
fmt_ = p.add_mutually_exclusive_group()
|
|
53
55
|
fmt_.add_argument('--v2', action='store_true', help='write a PEST++ version-2 file (external csv tables beside it)')
|
|
54
56
|
fmt_.add_argument('--v1', action='store_true', help='write the classic format (default for tables)')
|
|
@@ -111,15 +113,19 @@ def _finish_pst(pst, path, manifest, dump_tpl, version=None, eol=None):
|
|
|
111
113
|
|
|
112
114
|
|
|
113
115
|
def build(args):
|
|
114
|
-
manifest = None if args.no_manifest else Manifest('build', args._argv)
|
|
116
|
+
manifest = None if args.no_manifest or args.dry_run else Manifest('build', args._argv)
|
|
115
117
|
if args.pstfile.lower().endswith(WORKBOOK_EXT):
|
|
116
118
|
# `makepst build book.xlsx [--out x.pst]`: run the command in the workbook's BUILD sheet
|
|
117
119
|
pst, ns = build_from_workbook(args.pstfile, manifest)
|
|
120
|
+
if args.dry_run:
|
|
121
|
+
return check_report(pst, args.pstfile, built=True, dry_run=True)
|
|
118
122
|
out = args.out or os.path.join(os.path.dirname(os.path.abspath(args.pstfile)), ns.pstfile)
|
|
119
123
|
_finish_pst(pst, out, manifest, dump_tpl=not (args.no_dump_tpl or ns.no_dump_tpl),
|
|
120
124
|
version=_version(args) or _version(ns))
|
|
121
125
|
return
|
|
122
126
|
pst = build_pst(args, manifest)
|
|
127
|
+
if args.dry_run:
|
|
128
|
+
return check_report(pst, args.pstfile, built=True, dry_run=True)
|
|
123
129
|
_finish_pst(pst, args.pstfile, manifest, dump_tpl=not args.no_dump_tpl, version=_version(args))
|
|
124
130
|
|
|
125
131
|
|
|
@@ -130,8 +136,10 @@ def build_from_workbook(book, manifest=None):
|
|
|
130
136
|
try:
|
|
131
137
|
rows = pd.read_excel(book, 'BUILD', engine='openpyxl').iloc[:, 0].dropna()
|
|
132
138
|
except ValueError:
|
|
133
|
-
raise SystemExit(f'{book} has no BUILD sheet; add one holding the build command '
|
|
134
|
-
f'
|
|
139
|
+
raise SystemExit(f'{book} has no BUILD sheet; add one holding the build command (as `makepst dump` '
|
|
140
|
+
f'does), or name the tables on the command line: `makepst build OUT.pst --set_ctl_xls '
|
|
141
|
+
f'"{os.path.basename(book)},CONTROL" --add_par_xls ...`, with --dry_run to check them '
|
|
142
|
+
f'without writing') from None
|
|
135
143
|
argv = [a.strip('"') for a in shlex.split(' '.join(str(r) for r in rows), posix=False)]
|
|
136
144
|
while argv and (argv[0].lower() in ('python', 'makepst', 'build') or argv[0].lower().endswith('makepst.py')):
|
|
137
145
|
argv.pop(0)
|
|
@@ -403,27 +411,46 @@ def guess_base_dir(pst, target):
|
|
|
403
411
|
|
|
404
412
|
def validate(args):
|
|
405
413
|
"""pestchek-style report for a control file or a workbook (built via its BUILD sheet)."""
|
|
406
|
-
from .checks import Finding, summary, validate as run_checks
|
|
407
414
|
target = args.target
|
|
408
415
|
if target.lower().endswith(WORKBOOK_EXT):
|
|
409
|
-
|
|
410
|
-
|
|
416
|
+
check_report(build_from_workbook(target)[0], target, built=True, base_dir=args.base_dir,
|
|
417
|
+
outputs=args.outputs, quiet=args.quiet, strict=args.strict)
|
|
418
|
+
else:
|
|
419
|
+
check_report(read_pst(target), target, pst_path=target, base_dir=args.base_dir,
|
|
420
|
+
outputs=args.outputs, quiet=args.quiet, strict=args.strict)
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
def check_report(pst, target, built=False, pst_path=None, base_dir=None, outputs=False, quiet=False,
|
|
424
|
+
strict=False, dry_run=False):
|
|
425
|
+
"""Print the validate report for `pst`; exit 1 on errors (or warnings with `strict`).
|
|
426
|
+
|
|
427
|
+
`built`: the Pst comes from tables, not from a control file. It is then judged as `build` would write it,
|
|
428
|
+
after the same normalization (regularisation equations from PRIOR/WEIGHT, unused groups removed, ...);
|
|
429
|
+
judging the tables before it would report errors that building removes. The fixes are listed as notes.
|
|
430
|
+
"""
|
|
431
|
+
from .checks import Finding, summary, validate as run_checks
|
|
432
|
+
from .provenance import checked_against
|
|
433
|
+
fixes = pst.validate().fixes_available
|
|
434
|
+
if built:
|
|
435
|
+
pst.normalize()
|
|
436
|
+
notes = [Finding('info', 'normalization', f'{fix} (done by build)') for fix in fixes]
|
|
411
437
|
else:
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
findings += [Finding('info', 'normalization', str(fix)) for fix in pst.validate().fixes_available]
|
|
417
|
-
if not args.base_dir and not args.quiet:
|
|
438
|
+
notes = [Finding('info', 'normalization', str(fix)) for fix in fixes]
|
|
439
|
+
base = base_dir or guess_base_dir(pst, target)
|
|
440
|
+
findings = run_checks(pst, base_dir=base, pst_path=pst_path, outputs=outputs) + notes
|
|
441
|
+
if not base_dir and not quiet:
|
|
418
442
|
print(f'INFO file paths resolved relative to {base} (override with --base_dir)')
|
|
443
|
+
if not quiet:
|
|
444
|
+
v = checked_against()
|
|
445
|
+
print(f"INFO rules from PEST {v['pestchek']}'s pestchek and PEST++ {v['pestpp']}'s options")
|
|
419
446
|
order = {'error': 0, 'warning': 1, 'info': 2}
|
|
420
447
|
for f in sorted(findings, key=lambda f: order[f.severity]):
|
|
421
|
-
if f.severity == 'info' and
|
|
448
|
+
if f.severity == 'info' and quiet:
|
|
422
449
|
continue
|
|
423
450
|
print(f)
|
|
424
451
|
n, text = summary(findings)
|
|
425
|
-
print(f'{target}: {text}')
|
|
426
|
-
if n['error'] or (
|
|
452
|
+
print(f'{target}: {text}' + (' (dry run: nothing written)' if dry_run else ''))
|
|
453
|
+
if n['error'] or (strict and n['warning']):
|
|
427
454
|
raise SystemExit(1)
|
|
428
455
|
|
|
429
456
|
|
|
@@ -86,6 +86,9 @@ def control_table(pst: Pst):
|
|
|
86
86
|
val = pst.control.get(k)
|
|
87
87
|
default = pst.counts.get(k, sec.defaults.get(k))
|
|
88
88
|
rows.append((sec.name, k, default, val))
|
|
89
|
+
# tokens this makePst does not know, kept with the line PEST reads them on so build puts them back there
|
|
90
|
+
for k, (section, n) in getattr(pst, 'control_line', {}).items():
|
|
91
|
+
rows.append((f'{section} line {n}', k, None, pst.control.get(k)))
|
|
89
92
|
return pd.DataFrame(rows, columns=['LINE', 'NAME', 'DEFAULT', 'VALUE'])
|
|
90
93
|
|
|
91
94
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""PEST++ `++` options: what PEST++ accepts, and a check of a control file's options against it.
|
|
2
2
|
|
|
3
|
-
The registry is read off PEST++ 5.2.29 (usgs/pestpp develop, commit
|
|
3
|
+
The registry is read off PEST++ 5.2.29 (usgs/pestpp develop, commit 02e1455, 2026-09-03), from
|
|
4
4
|
PestppOptions::assign_value_by_key and the ies / da / mou / sqp functions it falls through to in
|
|
5
5
|
src/libs/pestpp_common/pest_data_structs.cpp. What that code does on start-up decides the severities:
|
|
6
6
|
|
|
@@ -14,6 +14,9 @@ src/libs/pestpp_common/pest_data_structs.cpp. What that code does on start-up de
|
|
|
14
14
|
|
|
15
15
|
`++` lines go to PestppOptions only. Control variables (noptmax, maxsing, ...) are accepted in a
|
|
16
16
|
version-2 file's `* control data keyword` section, not as `++` options.
|
|
17
|
+
|
|
18
|
+
`python tools/rule_sources.py pestpp` re-reads that source and reports what has changed since, and whether
|
|
19
|
+
the names, aliases, types and choices below still match what was extracted (tools/snapshots/pestpp.json).
|
|
17
20
|
"""
|
|
18
21
|
import difflib
|
|
19
22
|
import re
|
|
@@ -31,6 +31,14 @@ def _file_record(path):
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
|
|
34
|
+
def checked_against():
|
|
35
|
+
"""The releases makePst's checks were read from: PEST's pestchek (control data, PEST_HP variables) and
|
|
36
|
+
PEST++'s option list. A check is only as current as these; the manifest records them with each output."""
|
|
37
|
+
from .pestpp import PESTPP_REGISTRY_VERSION
|
|
38
|
+
from .sections import PESTCHEK_SOURCE_VERSION
|
|
39
|
+
return {'pestchek': PESTCHEK_SOURCE_VERSION, 'pestpp': PESTPP_REGISTRY_VERSION}
|
|
40
|
+
|
|
41
|
+
|
|
34
42
|
class Manifest:
|
|
35
43
|
def __init__(self, command, argv=None):
|
|
36
44
|
from . import __version__
|
|
@@ -47,6 +55,7 @@ class Manifest:
|
|
|
47
55
|
'python': platform.python_version(),
|
|
48
56
|
'pandas': pandas.__version__,
|
|
49
57
|
'openpyxl': openpyxl.__version__,
|
|
58
|
+
'checked_against': checked_against(),
|
|
50
59
|
'platform': platform.platform(),
|
|
51
60
|
'sources': [],
|
|
52
61
|
'output': None,
|
|
@@ -13,7 +13,7 @@ from dataclasses import dataclass, field
|
|
|
13
13
|
import numpy as np
|
|
14
14
|
import pandas as pd
|
|
15
15
|
|
|
16
|
-
from .sections import COMPUTED, FIELD_SECTION, _num, fmt, is_number, normalize_mode
|
|
16
|
+
from .sections import COMPUTED, FIELD_SECTION, SECTIONS, _num, fmt, is_number, normalize_mode
|
|
17
17
|
|
|
18
18
|
PAR_COLS = 'PARNME PARTRANS PARCHGLIM PARVAL1 PARLBND PARUBND PARGP SCALE OFFSET DERCOM'.split()
|
|
19
19
|
PAR_EXTRA = ['TIETO', 'PRIOR', 'WEIGHT'] # optional columns carried on self.par
|
|
@@ -31,6 +31,14 @@ def _clean(s):
|
|
|
31
31
|
return s.astype(str).str.strip().str.lower()
|
|
32
32
|
|
|
33
33
|
|
|
34
|
+
def _control_place(text):
|
|
35
|
+
"""('control data', 4) from a CONTROL sheet's LINE cell such as 'control data line 4', else None."""
|
|
36
|
+
m = re.fullmatch(r'\*?\s*(.*?)[\s,]+(?:line\s*)?(\d+)', str(text or '').strip().lower())
|
|
37
|
+
if m and m.group(1) in SECTIONS and int(m.group(2)) > 0:
|
|
38
|
+
return SECTIONS[m.group(1)].name, int(m.group(2))
|
|
39
|
+
return None
|
|
40
|
+
|
|
41
|
+
|
|
34
42
|
def _is_storage(names):
|
|
35
43
|
return names.str.startswith('ss') | names.str.startswith('sy')
|
|
36
44
|
|
|
@@ -185,6 +193,7 @@ class Pst:
|
|
|
185
193
|
self.ss = ss # steady state: drop storage (ss*/sy*) parameters and groups
|
|
186
194
|
self.comments = [] # '# ...' lines written after 'pcf'
|
|
187
195
|
self.control = {} # user values for every control-style section, flat
|
|
196
|
+
self.control_line = {} # control tokens the schema does not know: name -> (section, line number)
|
|
188
197
|
self.use_svd = True
|
|
189
198
|
self.par = _empty(PAR_COLS)
|
|
190
199
|
self.pargp = _empty(PARGP_COLS)
|
|
@@ -246,11 +255,16 @@ class Pst:
|
|
|
246
255
|
# ------------------------------------------------------------------ control
|
|
247
256
|
def set_control(self, values):
|
|
248
257
|
"""Set control-style values from a dict or a CONTROL sheet (NAME/VALUE columns)."""
|
|
258
|
+
lines = {}
|
|
249
259
|
if isinstance(values, pd.DataFrame):
|
|
250
260
|
df = values.iloc[:, :4].copy()
|
|
251
261
|
df.columns = ['LINE', 'NAME', 'DEFAULT', 'VALUE'][:df.shape[1]]
|
|
252
|
-
|
|
253
|
-
|
|
262
|
+
rows = df.dropna(subset=['NAME', 'VALUE']).itertuples()
|
|
263
|
+
values = {}
|
|
264
|
+
for r in rows:
|
|
265
|
+
name = str(r.NAME).strip().lower()
|
|
266
|
+
values[name] = r.VALUE
|
|
267
|
+
lines[name] = getattr(r, 'LINE', None)
|
|
254
268
|
for k, v in values.items():
|
|
255
269
|
k = str(k).strip().lower()
|
|
256
270
|
if k == 'pestmode':
|
|
@@ -258,7 +272,16 @@ class Pst:
|
|
|
258
272
|
elif k in COMPUTED:
|
|
259
273
|
warnings.warn(f'control value {k}={v!r} ignored; it is computed from the tables')
|
|
260
274
|
elif k not in FIELD_SECTION:
|
|
261
|
-
|
|
275
|
+
# A token this makePst does not know (a newer PEST_HP variable, say) is kept, like an unknown
|
|
276
|
+
# `++` option - but PEST reads it only on its own line, so it needs one: a control file gives
|
|
277
|
+
# it, a CONTROL sheet says it in the LINE column ('control data line 4').
|
|
278
|
+
place = _control_place(lines.get(k)) or self.control_line.get(k)
|
|
279
|
+
if place is None:
|
|
280
|
+
warnings.warn(f'unknown control variable {k!r} ignored: give the line it belongs on, '
|
|
281
|
+
f"as 'control data line 4' in the CONTROL sheet's LINE column")
|
|
282
|
+
elif fmt(v) != '':
|
|
283
|
+
self.control[k] = _num(v) if isinstance(v, str) else v
|
|
284
|
+
self.control_line[k] = place
|
|
262
285
|
elif fmt(v) != '':
|
|
263
286
|
self.control[k] = _num(v) if isinstance(v, str) else v # csv gives strings
|
|
264
287
|
|
|
@@ -632,8 +655,8 @@ class Pst:
|
|
|
632
655
|
'tied_to_fixed', 'Convert parameters tied to fixed parameters to fixed', fixed))
|
|
633
656
|
|
|
634
657
|
prior_rows = []
|
|
658
|
+
adjustable = set(self.par.loc[self.par['PARTRANS'].isin(ADJUSTABLE), 'PARNME'])
|
|
635
659
|
if self.nprior:
|
|
636
|
-
adjustable = set(self.par.loc[self.par['PARTRANS'].isin(ADJUSTABLE), 'PARNME'])
|
|
637
660
|
for row in self.prior.itertuples():
|
|
638
661
|
refs = equation_params(str(row.EQ))
|
|
639
662
|
if any(name not in adjustable for name in refs):
|
|
@@ -644,6 +667,17 @@ class Pst:
|
|
|
644
667
|
warnings.simplefilter('always')
|
|
645
668
|
expected = self._auto_prior()
|
|
646
669
|
report.warnings.extend(Finding('warning', 'prior information', str(w.message)) for w in caught)
|
|
670
|
+
# _build_prior drops a PRIOR that names a fixed, tied or missing parameter, so
|
|
671
|
+
# compare against what it would keep, or normalize() could never satisfy this check
|
|
672
|
+
buildable = expected['EQ'].map(
|
|
673
|
+
lambda eq: all(p in adjustable for p in equation_params(eq))).astype(bool)
|
|
674
|
+
if (~buildable).any():
|
|
675
|
+
skipped = expected.loc[~buildable, 'PINME'].tolist()
|
|
676
|
+
report.warnings.append(Finding(
|
|
677
|
+
'warning', 'prior information',
|
|
678
|
+
f'{len(skipped)} PRIOR entries name a fixed, tied or missing parameter and '
|
|
679
|
+
f'get no equation: {skipped[:5]}'))
|
|
680
|
+
expected = expected[buildable]
|
|
647
681
|
current = self.prior[self.prior['PINME'].isin(self._auto_labels)]
|
|
648
682
|
manual = self.prior[~self.prior['PINME'].isin(self._auto_labels)]
|
|
649
683
|
collisions = tuple(sorted(set(expected['PINME']) & set(manual['PINME'])))
|
|
@@ -161,11 +161,19 @@ def from_text(text, base_dir='.'):
|
|
|
161
161
|
counts = {k: ctl.get(k) for k in COMPUTED}
|
|
162
162
|
pst.use_svd = 'singular value decomposition' in sections or any(
|
|
163
163
|
k in ctl for k in ('svdmode', 'maxsing', 'eigthresh'))
|
|
164
|
+
extras = [(CONTROL.name, ctl.pop('_extra', {}))]
|
|
164
165
|
for name in list(sections):
|
|
165
166
|
sec = SECTIONS.get(name)
|
|
166
167
|
if sec is not None:
|
|
167
|
-
|
|
168
|
+
values = sec.parse(sections.pop(name))
|
|
169
|
+
extras.append((sec.name, values.pop('_extra', {})))
|
|
170
|
+
ctl.update(values)
|
|
168
171
|
pst.control = {k: v for k, v in ctl.items() if k not in COMPUTED and not k.startswith('_')}
|
|
172
|
+
for section, lines in extras:
|
|
173
|
+
for n, tokens in lines.items():
|
|
174
|
+
for key, value in tokens:
|
|
175
|
+
pst.control[key] = value
|
|
176
|
+
pst.control_line[key] = (section, n)
|
|
169
177
|
|
|
170
178
|
def take(section):
|
|
171
179
|
"""Body of a classic section, or the table of its external variant (None if absent)."""
|