bfflearn 0.2.0__tar.gz → 0.3.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.
- {bfflearn-0.2.0 → bfflearn-0.3.0}/CHANGELOG.md +48 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/CITATION.cff +2 -2
- {bfflearn-0.2.0 → bfflearn-0.3.0}/PKG-INFO +12 -2
- {bfflearn-0.2.0 → bfflearn-0.3.0}/README.md +10 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/bff/__init__.py +1 -1
- bfflearn-0.3.0/bff/bayes/__init__.py +21 -0
- bfflearn-0.3.0/bff/bayes/effective_observations.py +31 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/bff/bayes/gaussian_process.py +27 -9
- {bfflearn-0.2.0 → bfflearn-0.3.0}/bff/bayes/learning.py +21 -38
- {bfflearn-0.2.0 → bfflearn-0.3.0}/bff/bayes/likelihoods.py +62 -39
- {bfflearn-0.2.0 → bfflearn-0.3.0}/bff/cli.py +1 -118
- {bfflearn-0.2.0 → bfflearn-0.3.0}/bff/plotting.py +233 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/bff/qoi/data.py +10 -3
- {bfflearn-0.2.0 → bfflearn-0.3.0}/bff/qoi/rdf.py +3 -3
- {bfflearn-0.2.0 → bfflearn-0.3.0}/bff/qoi/routines.py +5 -3
- {bfflearn-0.2.0 → bfflearn-0.3.0}/bff/topology.py +38 -11
- {bfflearn-0.2.0 → bfflearn-0.3.0}/bff/workflows/_shared/config.py +1 -22
- {bfflearn-0.2.0 → bfflearn-0.3.0}/bff/workflows/build/config.py +4 -4
- {bfflearn-0.2.0 → bfflearn-0.3.0}/bff/workflows/fit/config.py +6 -7
- {bfflearn-0.2.0 → bfflearn-0.3.0}/bff/workflows/fit/main.py +0 -4
- bfflearn-0.3.0/bff/workflows/learn/config.py +210 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/bff/workflows/learn/main.py +90 -9
- {bfflearn-0.2.0 → bfflearn-0.3.0}/bfflearn.egg-info/PKG-INFO +12 -2
- {bfflearn-0.2.0 → bfflearn-0.3.0}/bfflearn.egg-info/SOURCES.txt +2 -1
- {bfflearn-0.2.0 → bfflearn-0.3.0}/bfflearn.egg-info/requires.txt +1 -1
- {bfflearn-0.2.0 → bfflearn-0.3.0}/docs/architecture.md +3 -2
- {bfflearn-0.2.0 → bfflearn-0.3.0}/docs/changelog.md +31 -1
- {bfflearn-0.2.0 → bfflearn-0.3.0}/docs/cli.md +11 -9
- {bfflearn-0.2.0 → bfflearn-0.3.0}/docs/configuration/analyze.md +3 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/docs/configuration/build.md +3 -1
- {bfflearn-0.2.0 → bfflearn-0.3.0}/docs/configuration/fit.md +7 -4
- bfflearn-0.3.0/docs/configuration/learn.md +127 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/docs/development.md +1 -1
- {bfflearn-0.2.0 → bfflearn-0.3.0}/docs/examples/acetate.md +1 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/docs/examples/arbitrary-data.md +9 -1
- {bfflearn-0.2.0 → bfflearn-0.3.0}/docs/examples/neon-mie-lgpmd.md +12 -3
- {bfflearn-0.2.0 → bfflearn-0.3.0}/docs/installation.md +16 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/pyproject.toml +2 -2
- bfflearn-0.3.0/tests/bayes/test_effective_observations.py +28 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/tests/bayes/test_gaussian_process.py +61 -3
- {bfflearn-0.2.0 → bfflearn-0.3.0}/tests/bayes/test_learning_problem.py +9 -14
- {bfflearn-0.2.0 → bfflearn-0.3.0}/tests/bayes/test_likelihoods.py +73 -3
- {bfflearn-0.2.0 → bfflearn-0.3.0}/tests/qoi/test_data.py +34 -1
- {bfflearn-0.2.0 → bfflearn-0.3.0}/tests/test_examples.py +26 -0
- bfflearn-0.3.0/tests/test_plotting.py +128 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/tests/test_topology.py +29 -1
- {bfflearn-0.2.0 → bfflearn-0.3.0}/tests/workflows/test_configs.py +206 -4
- {bfflearn-0.2.0 → bfflearn-0.3.0}/tests/workflows/test_regressions.py +48 -7
- bfflearn-0.2.0/bff/bayes/__init__.py +0 -11
- bfflearn-0.2.0/bff/data.py +0 -19
- bfflearn-0.2.0/bff/workflows/learn/config.py +0 -114
- bfflearn-0.2.0/docs/configuration/learn.md +0 -78
- bfflearn-0.2.0/tests/test_plotting.py +0 -53
- {bfflearn-0.2.0 → bfflearn-0.3.0}/CODE_OF_CONDUCT.md +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/CONTRIBUTING.md +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/LICENSE +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/MANIFEST.in +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/SECURITY.md +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/SUPPORT.md +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/bff/bayes/kernels.py +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/bff/bayes/means.py +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/bff/bayes/posterior.py +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/bff/bayes/priors.py +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/bff/bayes/results.py +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/bff/bayes/utils.py +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/bff/domain/__init__.py +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/bff/domain/bias.py +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/bff/domain/sample.py +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/bff/domain/specs.py +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/bff/io/__init__.py +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/bff/io/colvars.py +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/bff/io/commands.py +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/bff/io/cp2k.py +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/bff/io/extxyz.py +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/bff/io/logs.py +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/bff/io/mdp.py +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/bff/io/plumed.py +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/bff/io/progress.py +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/bff/io/schedulers.py +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/bff/io/utils.py +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/bff/mcmc/__init__.py +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/bff/mcmc/convergence.py +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/bff/mcmc/proposal.py +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/bff/mcmc/sampler.py +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/bff/qoi/__init__.py +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/bff/qoi/analysis.py +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/bff/qoi/hbonds.py +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/bff/tools.py +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/bff/workflows/__init__.py +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/bff/workflows/_shared/__init__.py +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/bff/workflows/_shared/campaign.py +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/bff/workflows/_shared/preparation.py +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/bff/workflows/_shared/scheduler.py +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/bff/workflows/analyze/__init__.py +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/bff/workflows/analyze/__main__.py +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/bff/workflows/analyze/config.py +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/bff/workflows/analyze/main.py +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/bff/workflows/build/__init__.py +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/bff/workflows/build/__main__.py +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/bff/workflows/build/main.py +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/bff/workflows/evaluate_snapshots/__init__.py +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/bff/workflows/evaluate_snapshots/__main__.py +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/bff/workflows/evaluate_snapshots/config.py +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/bff/workflows/evaluate_snapshots/main.py +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/bff/workflows/examples.py +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/bff/workflows/fit/__init__.py +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/bff/workflows/fit/__main__.py +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/bff/workflows/learn/__init__.py +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/bff/workflows/learn/__main__.py +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/bff/workflows/md/__init__.py +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/bff/workflows/md/__main__.py +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/bff/workflows/md/config.py +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/bff/workflows/md/main.py +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/bff/workflows/prepare_assets/__init__.py +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/bff/workflows/prepare_assets/__main__.py +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/bff/workflows/prepare_assets/config.py +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/bff/workflows/prepare_assets/main.py +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/bff/workflows/sample/__init__.py +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/bff/workflows/sample/__main__.py +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/bff/workflows/sample/config.py +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/bff/workflows/sample/main.py +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/bff/workflows/validate/__init__.py +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/bff/workflows/validate/__main__.py +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/bff/workflows/validate/config.py +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/bff/workflows/validate/main.py +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/bfflearn.egg-info/dependency_links.txt +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/bfflearn.egg-info/entry_points.txt +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/bfflearn.egg-info/top_level.txt +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/docs/assets/bff-logo.pdf +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/docs/assets/bff-logo.svg +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/docs/configuration/evaluate-snapshots.md +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/docs/configuration/prepare-assets.md +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/docs/configuration/sample.md +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/docs/configuration/validate.md +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/docs/examples/index.md +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/docs/index.md +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/mkdocs.yml +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/setup.cfg +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/tests/bayes/test_priors_posterior.py +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/tests/bayes/test_results.py +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/tests/bayes/test_utils.py +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/tests/domain/test_specs.py +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/tests/io/_fake_mdanalysis.py +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/tests/io/test_cp2k.py +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/tests/io/test_cp2k_collect.py +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/tests/io/test_logs.py +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/tests/io/test_mdp.py +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/tests/mcmc/test_convergence.py +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/tests/mcmc/test_proposal.py +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/tests/mcmc/test_sampler.py +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/tests/workflows/test_charge_constraints.py +0 -0
- {bfflearn-0.2.0 → bfflearn-0.3.0}/tests/workflows/test_scheduler.py +0 -0
|
@@ -2,6 +2,54 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## `0.3.0` - 2026-06-11
|
|
6
|
+
|
|
7
|
+
### Breaking Changes
|
|
8
|
+
|
|
9
|
+
- Moved effective-observation configuration from `bff fit` to each model under
|
|
10
|
+
`bff learn`. Model entries now require `model_path` and exactly one strategy:
|
|
11
|
+
explicit `n_eff`, `independent_observations: true`, or curve inference with
|
|
12
|
+
`tolerance`.
|
|
13
|
+
- Changed `.lgp` serialization to store reference-curve metadata. Models
|
|
14
|
+
written by earlier releases must be refitted before use with 0.3.0.
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
|
|
18
|
+
- Added prominence-based effective-observation estimates for smooth,
|
|
19
|
+
curve-valued QoIs while retaining direct counts for independent scalar data.
|
|
20
|
+
- Added `qoi-marginals.pdf`, which colors posterior parameter marginals by the
|
|
21
|
+
local contribution of each QoI and shows priors and parameter bounds.
|
|
22
|
+
- Added per-QoI Gaussian log-likelihood evaluation for post-processing thinned
|
|
23
|
+
posterior samples.
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
|
|
27
|
+
- Made the Gaussian likelihood insensitive to curve binning by applying
|
|
28
|
+
`n_eff` to a mean squared residual rather than treating every bin as an
|
|
29
|
+
independent observation.
|
|
30
|
+
- Allowed surrogate models for different QoIs to use different numbers of
|
|
31
|
+
training rows, provided their parameter dimensions agree.
|
|
32
|
+
- Simplified the `bff` command entry point and shell completion while retaining
|
|
33
|
+
subcommands such as `bff build`.
|
|
34
|
+
- Made build templates optional for systems that do not require molecule
|
|
35
|
+
templates.
|
|
36
|
+
- Updated the packaged examples and documentation for effective observations
|
|
37
|
+
and QoI-attributed posterior plots.
|
|
38
|
+
- Included `pytest` in the standard installation so packaged test and example
|
|
39
|
+
checks are available without installing the developer extra.
|
|
40
|
+
|
|
41
|
+
### Fixed
|
|
42
|
+
|
|
43
|
+
- Fixed multiprocessing imports for analysis routines loaded from user Python
|
|
44
|
+
files.
|
|
45
|
+
|
|
46
|
+
## `0.2.1` - 2026-06-01
|
|
47
|
+
|
|
48
|
+
### Fixed
|
|
49
|
+
|
|
50
|
+
- Restored Python 3.10 compatibility by replacing the Python 3.11-only
|
|
51
|
+
`typing.Self` annotation in Gaussian-process model loading.
|
|
52
|
+
|
|
5
53
|
## `0.2.0` - 2026-06-01
|
|
6
54
|
|
|
7
55
|
The public reproduction snapshot for the published study is archived as
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
cff-version: 1.2.0
|
|
2
2
|
message: "If you use BFF, please cite the repository and the associated paper."
|
|
3
3
|
title: "Bayesic Force Fields"
|
|
4
|
-
version: "0.
|
|
5
|
-
date-released: "2026-06-
|
|
4
|
+
version: "0.3.0"
|
|
5
|
+
date-released: "2026-06-11"
|
|
6
6
|
authors:
|
|
7
7
|
- family-names: Kostal
|
|
8
8
|
given-names: Vojtech
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: bfflearn
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Bayesian workflows for fixed-charge molecular force fields
|
|
5
5
|
Author-email: Vojtech Kostal <vojtakostal8@gmail.com>
|
|
6
6
|
License-Expression: GPL-3.0-only
|
|
@@ -28,6 +28,7 @@ Requires-Dist: gmxtopology>=0.1.1
|
|
|
28
28
|
Requires-Dist: matplotlib>=3.8
|
|
29
29
|
Requires-Dist: MDAnalysis>=2.7
|
|
30
30
|
Requires-Dist: numpy>=1.26
|
|
31
|
+
Requires-Dist: pytest>=8
|
|
31
32
|
Requires-Dist: PyYAML>=6.0
|
|
32
33
|
Requires-Dist: scipy>=1.11
|
|
33
34
|
Requires-Dist: typer>=0.12
|
|
@@ -39,7 +40,6 @@ Requires-Dist: jupyterlab; extra == "notebook"
|
|
|
39
40
|
Requires-Dist: notebook; extra == "notebook"
|
|
40
41
|
Provides-Extra: dev
|
|
41
42
|
Requires-Dist: build; extra == "dev"
|
|
42
|
-
Requires-Dist: pytest; extra == "dev"
|
|
43
43
|
Requires-Dist: ruff; extra == "dev"
|
|
44
44
|
Requires-Dist: twine; extra == "dev"
|
|
45
45
|
Provides-Extra: docs
|
|
@@ -64,6 +64,10 @@ molecular force-field parameters from molecular-dynamics observables. It
|
|
|
64
64
|
coordinates simulation campaigns, trajectory analysis, surrogate fitting, and
|
|
65
65
|
Bayesian posterior learning.
|
|
66
66
|
|
|
67
|
+
Posterior learning supports explicit or data-derived effective observation
|
|
68
|
+
counts for scalar and curve-valued observables. It also writes QoI-attributed
|
|
69
|
+
marginals that show which observables support different posterior regions.
|
|
70
|
+
|
|
67
71
|
Full documentation:
|
|
68
72
|
[vojtechkostal.github.io/BayesicForceFields](https://vojtechkostal.github.io/BayesicForceFields/)
|
|
69
73
|
|
|
@@ -91,6 +95,12 @@ mamba activate bfflearn
|
|
|
91
95
|
pip install bfflearn
|
|
92
96
|
```
|
|
93
97
|
|
|
98
|
+
For Jupyter notebooks, install the optional notebook tools:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
pip install "bfflearn[notebook]"
|
|
102
|
+
```
|
|
103
|
+
|
|
94
104
|
> **WARNING:** GPU-enabled PyTorch must be installed separately. Use the
|
|
95
105
|
> [official PyTorch installation selector](https://pytorch.org/get-started/locally/)
|
|
96
106
|
> to choose the command matching your CUDA version before running fitting,
|
|
@@ -14,6 +14,10 @@ molecular force-field parameters from molecular-dynamics observables. It
|
|
|
14
14
|
coordinates simulation campaigns, trajectory analysis, surrogate fitting, and
|
|
15
15
|
Bayesian posterior learning.
|
|
16
16
|
|
|
17
|
+
Posterior learning supports explicit or data-derived effective observation
|
|
18
|
+
counts for scalar and curve-valued observables. It also writes QoI-attributed
|
|
19
|
+
marginals that show which observables support different posterior regions.
|
|
20
|
+
|
|
17
21
|
Full documentation:
|
|
18
22
|
[vojtechkostal.github.io/BayesicForceFields](https://vojtechkostal.github.io/BayesicForceFields/)
|
|
19
23
|
|
|
@@ -41,6 +45,12 @@ mamba activate bfflearn
|
|
|
41
45
|
pip install bfflearn
|
|
42
46
|
```
|
|
43
47
|
|
|
48
|
+
For Jupyter notebooks, install the optional notebook tools:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
pip install "bfflearn[notebook]"
|
|
52
|
+
```
|
|
53
|
+
|
|
44
54
|
> **WARNING:** GPU-enabled PyTorch must be installed separately. Use the
|
|
45
55
|
> [official PyTorch installation selector](https://pytorch.org/get-started/locally/)
|
|
46
56
|
> to choose the command matching your CUDA version before running fitting,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from . import (
|
|
2
|
+
effective_observations,
|
|
3
|
+
gaussian_process,
|
|
4
|
+
kernels,
|
|
5
|
+
learning,
|
|
6
|
+
likelihoods,
|
|
7
|
+
priors,
|
|
8
|
+
results,
|
|
9
|
+
utils,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
'effective_observations',
|
|
14
|
+
'gaussian_process',
|
|
15
|
+
'kernels',
|
|
16
|
+
'learning',
|
|
17
|
+
'likelihoods',
|
|
18
|
+
'priors',
|
|
19
|
+
'results',
|
|
20
|
+
'utils',
|
|
21
|
+
]
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
from scipy.signal import find_peaks, savgol_filter
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def estimate_curve_n_eff(y: np.ndarray, *, tolerance: float) -> float:
|
|
8
|
+
"""Estimate an effective number of resolved features in one smooth curve."""
|
|
9
|
+
y = np.asarray(y, dtype=float)
|
|
10
|
+
tolerance = float(tolerance)
|
|
11
|
+
|
|
12
|
+
if y.ndim != 1 or y.size < 11:
|
|
13
|
+
raise ValueError("curve must be one-dimensional with at least 11 values.")
|
|
14
|
+
if not np.all(np.isfinite(y)):
|
|
15
|
+
raise ValueError("curve must contain only finite values.")
|
|
16
|
+
if not np.isfinite(tolerance) or tolerance <= 0.0:
|
|
17
|
+
raise ValueError("tolerance must be positive and finite.")
|
|
18
|
+
|
|
19
|
+
window_length = min(15, y.size if y.size % 2 else y.size - 1)
|
|
20
|
+
y_smooth = savgol_filter(y, window_length=window_length, polyorder=3)
|
|
21
|
+
|
|
22
|
+
noise = np.median(np.abs(y - y_smooth))
|
|
23
|
+
prominence_threshold = max(5.0 * noise, np.finfo(float).eps)
|
|
24
|
+
|
|
25
|
+
n_eff = 0.0
|
|
26
|
+
for sign in (1.0, -1.0):
|
|
27
|
+
_, properties = find_peaks(sign * y_smooth, prominence=prominence_threshold)
|
|
28
|
+
for prominence in properties["prominences"]:
|
|
29
|
+
n_eff += max(1.0, np.log(float(prominence) / tolerance))
|
|
30
|
+
|
|
31
|
+
return max(1.0, float(n_eff))
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from pathlib import Path
|
|
2
|
-
from typing import Callable,
|
|
2
|
+
from typing import Callable, TypeVar, Union
|
|
3
3
|
|
|
4
4
|
import numpy as np
|
|
5
5
|
import torch
|
|
@@ -14,6 +14,7 @@ MeanFunction = Union[
|
|
|
14
14
|
float,
|
|
15
15
|
Callable[[torch.Tensor], torch.Tensor],
|
|
16
16
|
]
|
|
17
|
+
LGPCommitteeT = TypeVar("LGPCommitteeT", bound="LGPCommittee")
|
|
17
18
|
|
|
18
19
|
|
|
19
20
|
def evaluate_mean(
|
|
@@ -165,10 +166,10 @@ class LGPCommittee:
|
|
|
165
166
|
----------
|
|
166
167
|
lgps : list of LocalGaussianProcess
|
|
167
168
|
List of LGP models.
|
|
168
|
-
n_observations : int
|
|
169
|
-
Effective number of observations used in the likelihood term.
|
|
170
169
|
reference_values : np.ndarray
|
|
171
170
|
Reference observation vector matched by the surrogate outputs.
|
|
171
|
+
n_curves : int
|
|
172
|
+
Number of curves represented by the flattened reference vector.
|
|
172
173
|
nuisance : float, optional
|
|
173
174
|
Nuisance parameter for model selection (default is None).
|
|
174
175
|
stochastic : bool, optional
|
|
@@ -194,15 +195,16 @@ class LGPCommittee:
|
|
|
194
195
|
def __init__(
|
|
195
196
|
self,
|
|
196
197
|
lgps: list[LocalGaussianProcess],
|
|
197
|
-
n_observations: int,
|
|
198
198
|
reference_values: np.ndarray,
|
|
199
|
+
n_curves: int,
|
|
199
200
|
nuisance: float | None = None,
|
|
200
201
|
stochastic: bool = False
|
|
201
202
|
) -> None:
|
|
202
203
|
self.lgps = lgps
|
|
203
204
|
self.error: float | None = None
|
|
204
|
-
self.n_observations = int(n_observations)
|
|
205
205
|
self.reference_values = np.asarray(reference_values, dtype=float).reshape(-1)
|
|
206
|
+
self.n_curves = int(n_curves)
|
|
207
|
+
self.n_eff = float(self.reference_values.size)
|
|
206
208
|
self.nuisance = nuisance
|
|
207
209
|
self.stochastic = stochastic
|
|
208
210
|
|
|
@@ -210,6 +212,12 @@ class LGPCommittee:
|
|
|
210
212
|
raise ValueError(
|
|
211
213
|
"Reference observation size does not match surrogate output size."
|
|
212
214
|
)
|
|
215
|
+
if self.n_curves <= 0:
|
|
216
|
+
raise ValueError("'n_curves' must be a positive integer.")
|
|
217
|
+
if self.reference_values.size % self.n_curves != 0:
|
|
218
|
+
raise ValueError(
|
|
219
|
+
"Reference observation size must be divisible by 'n_curves'."
|
|
220
|
+
)
|
|
213
221
|
|
|
214
222
|
@property
|
|
215
223
|
def size(self) -> int:
|
|
@@ -223,6 +231,10 @@ class LGPCommittee:
|
|
|
223
231
|
def y_size(self) -> int:
|
|
224
232
|
return self.lgps[0].y_size
|
|
225
233
|
|
|
234
|
+
@property
|
|
235
|
+
def curve_length(self) -> int:
|
|
236
|
+
return int(self.reference_values.size // self.n_curves)
|
|
237
|
+
|
|
226
238
|
def predict(self, X: torch.Tensor) -> torch.Tensor:
|
|
227
239
|
"""
|
|
228
240
|
Predict outputs by averaging predictions from all LGP models.
|
|
@@ -269,13 +281,19 @@ class LGPCommittee:
|
|
|
269
281
|
return self.error
|
|
270
282
|
|
|
271
283
|
@classmethod
|
|
272
|
-
def load(cls, fn: PathLike) ->
|
|
284
|
+
def load(cls: type[LGPCommitteeT], fn: PathLike) -> LGPCommitteeT:
|
|
273
285
|
state = torch.load(fn, weights_only=False)
|
|
286
|
+
if "n_curves" not in state:
|
|
287
|
+
raise ValueError(
|
|
288
|
+
"This surrogate model predates BFF 0.3.0 and does not contain "
|
|
289
|
+
"reference-curve metadata. Refit the model before learning."
|
|
290
|
+
)
|
|
274
291
|
lgps = [LocalGaussianProcess(**lgp_state) for lgp_state in state["lgps"]]
|
|
292
|
+
reference_values = np.asarray(state["reference_values"], dtype=float)
|
|
275
293
|
committee = cls(
|
|
276
294
|
lgps=lgps,
|
|
277
|
-
|
|
278
|
-
|
|
295
|
+
reference_values=reference_values,
|
|
296
|
+
n_curves=int(state["n_curves"]),
|
|
279
297
|
nuisance=state["nuisance"],
|
|
280
298
|
stochastic=state["stochastic"],
|
|
281
299
|
)
|
|
@@ -285,8 +303,8 @@ class LGPCommittee:
|
|
|
285
303
|
def write(self, fn_out: PathLike) -> None:
|
|
286
304
|
|
|
287
305
|
state = {
|
|
288
|
-
"n_observations": self.n_observations,
|
|
289
306
|
"reference_values": self.reference_values.tolist(),
|
|
307
|
+
"n_curves": self.n_curves,
|
|
290
308
|
"nuisance": self.nuisance,
|
|
291
309
|
"stochastic": self.stochastic,
|
|
292
310
|
"error": self.error,
|
|
@@ -78,20 +78,6 @@ class LearningProblem:
|
|
|
78
78
|
+ ", ".join(sorted(inconsistent_inputs))
|
|
79
79
|
)
|
|
80
80
|
|
|
81
|
-
model_input_shapes = {
|
|
82
|
-
qoi: tuple(int(dim) for dim in model.lgps[0].X_train.shape)
|
|
83
|
-
for qoi, model in self.models.items()
|
|
84
|
-
}
|
|
85
|
-
if len(set(model_input_shapes.values())) != 1:
|
|
86
|
-
shape_summary = ", ".join(
|
|
87
|
-
f"{qoi}={shape}"
|
|
88
|
-
for qoi, shape in sorted(model_input_shapes.items())
|
|
89
|
-
)
|
|
90
|
-
raise ValueError(
|
|
91
|
-
"All surrogate models must expect the same training input shape. "
|
|
92
|
-
f"Found: {shape_summary}"
|
|
93
|
-
)
|
|
94
|
-
|
|
95
81
|
n_params = {model.n_params for model in self.models.values()}
|
|
96
82
|
if len(n_params) != 1:
|
|
97
83
|
raise ValueError("All surrogate models must have the same input dimension.")
|
|
@@ -114,6 +100,17 @@ class LearningProblem:
|
|
|
114
100
|
+ ", ".join(sorted(invalid))
|
|
115
101
|
)
|
|
116
102
|
|
|
103
|
+
invalid_curve_schema = []
|
|
104
|
+
for qoi, model in self.models.items():
|
|
105
|
+
n_curves = int(getattr(model, "n_curves", 0))
|
|
106
|
+
if n_curves <= 0 or model.reference_values.size % n_curves != 0:
|
|
107
|
+
invalid_curve_schema.append(qoi)
|
|
108
|
+
if invalid_curve_schema:
|
|
109
|
+
raise ValueError(
|
|
110
|
+
"Models with incompatible n_curves metadata: "
|
|
111
|
+
+ ", ".join(sorted(invalid_curve_schema))
|
|
112
|
+
)
|
|
113
|
+
|
|
117
114
|
@property
|
|
118
115
|
def qoi_names(self) -> list[str]:
|
|
119
116
|
return list(self.models)
|
|
@@ -306,8 +303,8 @@ def fit_lgp_committee(
|
|
|
306
303
|
test_fraction: float,
|
|
307
304
|
n_hyper: int,
|
|
308
305
|
committee: int,
|
|
309
|
-
n_observations: int,
|
|
310
306
|
reference_values: np.ndarray,
|
|
307
|
+
n_curves: int,
|
|
311
308
|
nuisance: float | None,
|
|
312
309
|
fn_out: PathLike | None,
|
|
313
310
|
device: str,
|
|
@@ -395,10 +392,10 @@ def fit_lgp_committee(
|
|
|
395
392
|
logger.status("Committee", f"{i}/{committee}", level=2, overwrite=True)
|
|
396
393
|
|
|
397
394
|
lgp_committee = LGPCommittee(
|
|
398
|
-
lgps,
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
nuisance,
|
|
395
|
+
lgps=lgps,
|
|
396
|
+
reference_values=reference_values,
|
|
397
|
+
n_curves=n_curves,
|
|
398
|
+
nuisance=nuisance,
|
|
402
399
|
)
|
|
403
400
|
lgp_committee.validate(X_test, y_test)
|
|
404
401
|
logger.done(
|
|
@@ -413,20 +410,11 @@ def fit_lgp_committee(
|
|
|
413
410
|
return lgp_committee
|
|
414
411
|
|
|
415
412
|
|
|
416
|
-
def _effective_observations(
|
|
417
|
-
dataset: QoIDataset,
|
|
418
|
-
observation_scale: float = 1.0,
|
|
419
|
-
) -> int:
|
|
420
|
-
"""Return the effective observation count used in the likelihood term."""
|
|
421
|
-
return max(1, int(round(dataset.n_observations * float(observation_scale))))
|
|
422
|
-
|
|
423
|
-
|
|
424
413
|
def fit_surrogates(
|
|
425
414
|
datasets: Sequence[QoIDataset],
|
|
426
415
|
*,
|
|
427
416
|
y_means: Optional[Mapping[str, MeanFunction | str]] = None,
|
|
428
417
|
hyperpriors: Optional[Mapping[str, Priors | Sequence]] = None,
|
|
429
|
-
observation_scales: Optional[Mapping[str, float]] = None,
|
|
430
418
|
model_paths: Optional[Mapping[str, PathLike | None]] = None,
|
|
431
419
|
reuse_models: bool = True,
|
|
432
420
|
n_hyper_max: int = 200,
|
|
@@ -441,7 +429,6 @@ def fit_surrogates(
|
|
|
441
429
|
logger = logger or Logger("fit")
|
|
442
430
|
y_means = dict(y_means or {})
|
|
443
431
|
hyperpriors = dict(hyperpriors or {})
|
|
444
|
-
observation_scales = dict(observation_scales or {})
|
|
445
432
|
model_paths = dict(model_paths or {})
|
|
446
433
|
|
|
447
434
|
if owns_logger:
|
|
@@ -456,10 +443,6 @@ def fit_surrogates(
|
|
|
456
443
|
)
|
|
457
444
|
|
|
458
445
|
qoi = dataset.name
|
|
459
|
-
n_observations = _effective_observations(
|
|
460
|
-
dataset,
|
|
461
|
-
observation_scales.get(qoi, 1.0),
|
|
462
|
-
)
|
|
463
446
|
logger.info(f"QoI {qoi}", level=1)
|
|
464
447
|
|
|
465
448
|
fn_model_raw = model_paths.get(qoi)
|
|
@@ -469,19 +452,20 @@ def fit_surrogates(
|
|
|
469
452
|
|
|
470
453
|
if reuse_models and fn_model is not None and fn_model.exists():
|
|
471
454
|
models[qoi] = LGPCommittee.load(fn_model)
|
|
472
|
-
models[qoi].n_observations = n_observations
|
|
473
455
|
models[qoi].reference_values = np.asarray(
|
|
474
456
|
dataset.outputs_ref,
|
|
475
457
|
dtype=float,
|
|
476
458
|
).reshape(-1)
|
|
459
|
+
models[qoi].n_eff = float(models[qoi].reference_values.size)
|
|
460
|
+
models[qoi].n_curves = dataset.n_curves
|
|
477
461
|
if models[qoi].reference_values.size != models[qoi].y_size:
|
|
478
462
|
raise ValueError(
|
|
479
463
|
f"Cached surrogate for {qoi!r} is incompatible with the "
|
|
480
464
|
"current reference observation size."
|
|
481
465
|
)
|
|
466
|
+
models[qoi].write(fn_model)
|
|
482
467
|
logger.info(
|
|
483
|
-
f"Using cached surrogate model. |
|
|
484
|
-
f"| MAPE = {models[qoi].error:.2f}",
|
|
468
|
+
f"Using cached surrogate model. | MAPE = {models[qoi].error:.2f}",
|
|
485
469
|
level=2,
|
|
486
470
|
)
|
|
487
471
|
logger.blank()
|
|
@@ -494,8 +478,8 @@ def fit_surrogates(
|
|
|
494
478
|
test_fraction=test_fraction,
|
|
495
479
|
n_hyper=n_hyper_max,
|
|
496
480
|
committee=committee_size,
|
|
497
|
-
n_observations=n_observations,
|
|
498
481
|
reference_values=dataset.outputs_ref,
|
|
482
|
+
n_curves=dataset.n_curves,
|
|
499
483
|
nuisance=dataset.nuisance,
|
|
500
484
|
fn_out=fn_model,
|
|
501
485
|
device=device,
|
|
@@ -503,7 +487,6 @@ def fit_surrogates(
|
|
|
503
487
|
opt_kwargs=opt_kwargs,
|
|
504
488
|
hyperpriors=hyperpriors.get(qoi),
|
|
505
489
|
)
|
|
506
|
-
logger.kv("Effective observations", n_observations, level=2)
|
|
507
490
|
logger.blank()
|
|
508
491
|
|
|
509
492
|
return models
|
|
@@ -10,6 +10,42 @@ if TYPE_CHECKING:
|
|
|
10
10
|
from .learning import LearningProblem
|
|
11
11
|
|
|
12
12
|
|
|
13
|
+
def _split_parameters_and_sigmas(
|
|
14
|
+
theta: torch.Tensor,
|
|
15
|
+
problem: LearningProblem,
|
|
16
|
+
) -> tuple[torch.Tensor, torch.Tensor]:
|
|
17
|
+
"""Return explicit parameters and one scalar nuisance per QoI."""
|
|
18
|
+
n_params = problem.n_params
|
|
19
|
+
params = theta[:, :n_params]
|
|
20
|
+
nuisance_free = theta[:, n_params:]
|
|
21
|
+
|
|
22
|
+
sigma_columns = []
|
|
23
|
+
j = 0
|
|
24
|
+
for model in problem.models.values():
|
|
25
|
+
if model.nuisance is None:
|
|
26
|
+
sigma_columns.append(nuisance_free[:, j].exp())
|
|
27
|
+
j += 1
|
|
28
|
+
else:
|
|
29
|
+
sigma_columns.append(
|
|
30
|
+
torch.full(
|
|
31
|
+
(len(theta),),
|
|
32
|
+
float(model.nuisance),
|
|
33
|
+
device=theta.device,
|
|
34
|
+
dtype=theta.dtype,
|
|
35
|
+
)
|
|
36
|
+
)
|
|
37
|
+
return params, torch.stack(sigma_columns, dim=1)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _valid_parameter_mask(
|
|
41
|
+
params: torch.Tensor,
|
|
42
|
+
problem: LearningProblem,
|
|
43
|
+
) -> torch.Tensor:
|
|
44
|
+
if problem.constraint is None:
|
|
45
|
+
return torch.ones(len(params), dtype=bool, device=params.device)
|
|
46
|
+
return problem.constraint(params)
|
|
47
|
+
|
|
48
|
+
|
|
13
49
|
def loo_log_likelihood(
|
|
14
50
|
theta: torch.Tensor, X: torch.Tensor, y: torch.Tensor
|
|
15
51
|
) -> torch.Tensor:
|
|
@@ -103,50 +139,37 @@ def gaussian_log_likelihood(
|
|
|
103
139
|
Log likelihood for each sample in `theta`, shape (n_samples,).
|
|
104
140
|
"""
|
|
105
141
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
# Split the theta into parameters and nuisances
|
|
110
|
-
n_params = problem.n_params
|
|
111
|
-
params = theta[:, :n_params]
|
|
112
|
-
nuisance_free = theta[:, n_params:]
|
|
142
|
+
contributions = gaussian_log_likelihood_by_qoi(theta, problem)
|
|
143
|
+
return torch.stack(tuple(contributions.values())).sum(dim=0)
|
|
113
144
|
|
|
114
|
-
sigma_columns = []
|
|
115
|
-
j = 0
|
|
116
|
-
for model in problem.models.values():
|
|
117
|
-
if model.nuisance is None:
|
|
118
|
-
sigma_columns.append(nuisance_free[:, j].exp())
|
|
119
|
-
j += 1
|
|
120
|
-
else:
|
|
121
|
-
sigma_columns.append(
|
|
122
|
-
torch.full(
|
|
123
|
-
(len(theta),),
|
|
124
|
-
float(model.nuisance),
|
|
125
|
-
device=device,
|
|
126
|
-
dtype=theta.dtype,
|
|
127
|
-
)
|
|
128
|
-
)
|
|
129
|
-
sigmas = torch.stack(sigma_columns, dim=1)
|
|
130
145
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
146
|
+
def gaussian_log_likelihood_by_qoi(
|
|
147
|
+
theta: torch.Tensor,
|
|
148
|
+
problem: LearningProblem,
|
|
149
|
+
) -> dict[str, torch.Tensor]:
|
|
150
|
+
"""Compute one Gaussian log-likelihood contribution per QoI."""
|
|
151
|
+
params, sigmas = _split_parameters_and_sigmas(theta, problem)
|
|
152
|
+
mask = _valid_parameter_mask(params, problem)
|
|
136
153
|
params = params[mask]
|
|
137
154
|
sigmas = sigmas[mask]
|
|
138
155
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
156
|
+
contributions = {}
|
|
157
|
+
for (qoi, model), sigma in zip(problem.models.items(), sigmas.T):
|
|
158
|
+
contribution = torch.full(
|
|
159
|
+
(len(theta),),
|
|
160
|
+
-torch.inf,
|
|
161
|
+
device=theta.device,
|
|
162
|
+
dtype=theta.dtype,
|
|
163
|
+
)
|
|
164
|
+
if mask.any():
|
|
144
165
|
y_trial = model.predict(params)
|
|
145
|
-
N = model.n_observations
|
|
146
166
|
diff = problem.observations[qoi] - y_trial
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
167
|
+
mse = torch.mean(diff**2, dim=1)
|
|
168
|
+
n_eff = float(model.n_eff)
|
|
169
|
+
contribution[mask] = (
|
|
170
|
+
-0.5 * n_eff * mse / sigma**2
|
|
171
|
+
- n_eff * torch.log(sigma)
|
|
172
|
+
)
|
|
173
|
+
contributions[qoi] = contribution
|
|
151
174
|
|
|
152
|
-
return
|
|
175
|
+
return contributions
|