pynumerics 2.3.0__py3-none-any.whl
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.
- pynumerics/__init__.py +73 -0
- pynumerics/_bin/Microsoft.CodeAnalysis.CSharp.dll +0 -0
- pynumerics/_bin/Microsoft.CodeAnalysis.LICENSE.txt +27 -0
- pynumerics/_bin/Microsoft.CodeAnalysis.dll +0 -0
- pynumerics/_bin/Numerics.dll +0 -0
- pynumerics/_bin/pynumerics.runtimeconfig.json +15 -0
- pynumerics/_coverage.py +3825 -0
- pynumerics/_util.py +237 -0
- pynumerics/_version.py +40 -0
- pynumerics/bootstrap.py +593 -0
- pynumerics/callbacks.py +791 -0
- pynumerics/copulas.py +910 -0
- pynumerics/data.py +2233 -0
- pynumerics/differentiation.py +348 -0
- pynumerics/distributions.py +4043 -0
- pynumerics/download.py +623 -0
- pynumerics/enums.py +119 -0
- pynumerics/errors.py +245 -0
- pynumerics/functions.py +1142 -0
- pynumerics/goodness_of_fit.py +699 -0
- pynumerics/graphs.py +556 -0
- pynumerics/hypothesis_tests.py +445 -0
- pynumerics/identity.py +144 -0
- pynumerics/integration.py +677 -0
- pynumerics/linalg.py +1408 -0
- pynumerics/machine_learning.py +1335 -0
- pynumerics/marshal.py +733 -0
- pynumerics/mathematics.py +1053 -0
- pynumerics/mcmc.py +1406 -0
- pynumerics/multivariate.py +1539 -0
- pynumerics/ode.py +156 -0
- pynumerics/optimization.py +1736 -0
- pynumerics/probability.py +966 -0
- pynumerics/py.typed +0 -0
- pynumerics/regional.py +522 -0
- pynumerics/roots.py +274 -0
- pynumerics/runtime.py +384 -0
- pynumerics/sampling.py +1758 -0
- pynumerics/special.py +1079 -0
- pynumerics/statistics.py +1958 -0
- pynumerics/timeseries.py +1326 -0
- pynumerics-2.3.0.dist-info/METADATA +142 -0
- pynumerics-2.3.0.dist-info/RECORD +46 -0
- pynumerics-2.3.0.dist-info/WHEEL +5 -0
- pynumerics-2.3.0.dist-info/licenses/LICENSE +10 -0
- pynumerics-2.3.0.dist-info/top_level.txt +1 -0
pynumerics/mcmc.py
ADDED
|
@@ -0,0 +1,1406 @@
|
|
|
1
|
+
"""Bayesian sampling from ``Numerics.Sampling.MCMC``: the MCMC samplers, their results and diagnostics.
|
|
2
|
+
|
|
3
|
+
Every sampler takes a list of prior distributions and a log-likelihood. The priors bound the
|
|
4
|
+
parameters and seed the chains; the log-likelihood is the full log-posterior up to a constant
|
|
5
|
+
(Numerics adds no prior density), so uniform priors need no prior term and other priors need
|
|
6
|
+
their log-densities added (:func:`distribution_log_likelihood` does this with
|
|
7
|
+
``add_log_prior=True``). The log-likelihood is any of:
|
|
8
|
+
|
|
9
|
+
- a Python callable of the parameter array, called once per evaluation (about 1 microsecond of
|
|
10
|
+
crossing each); the wrapper then sets ``ParallelizeChains = False``, because the chains would
|
|
11
|
+
only contend for the GIL;
|
|
12
|
+
- C# source compiled with Roslyn (:func:`pynumerics.callbacks.compile_csharp`), or the in-.NET
|
|
13
|
+
model of :func:`distribution_log_likelihood`: no Python runs per evaluation and the chains
|
|
14
|
+
keep running in parallel;
|
|
15
|
+
- a ``Numerics.Sampling.MCMC.LogLikelihood`` delegate.
|
|
16
|
+
|
|
17
|
+
Runs are reproducible: every sampler draws from Mersenne Twister streams seeded by ``seed``
|
|
18
|
+
(``PRNGSeed``, default 12345), one per chain, so a seeded run returns the same chains whether
|
|
19
|
+
or not the chains run in parallel.
|
|
20
|
+
|
|
21
|
+
Chains and outputs come back as NumPy arrays: ``markov_chains`` has shape
|
|
22
|
+
``(chains, iterations, parameters)``. The chains are read from .NET in one pass per run (about
|
|
23
|
+
3 microseconds per recorded state) and cached, so later reads cost only a NumPy copy.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
from collections.abc import Sequence
|
|
29
|
+
from typing import Any
|
|
30
|
+
|
|
31
|
+
import numpy as np
|
|
32
|
+
|
|
33
|
+
from pynumerics import callbacks
|
|
34
|
+
from pynumerics._util import (
|
|
35
|
+
_opt,
|
|
36
|
+
bind_list,
|
|
37
|
+
cached_overload,
|
|
38
|
+
namespace,
|
|
39
|
+
net_enum,
|
|
40
|
+
unwrap,
|
|
41
|
+
)
|
|
42
|
+
from pynumerics.enums import NetEnum
|
|
43
|
+
from pynumerics.errors import translate_public
|
|
44
|
+
from pynumerics.marshal import (
|
|
45
|
+
_linear_algebra,
|
|
46
|
+
_numerics_matrix,
|
|
47
|
+
_numerics_vector,
|
|
48
|
+
to_net_doubles,
|
|
49
|
+
to_net_ints,
|
|
50
|
+
to_net_matrix,
|
|
51
|
+
to_numpy_doubles,
|
|
52
|
+
to_numpy_ints,
|
|
53
|
+
to_numpy_matrix,
|
|
54
|
+
)
|
|
55
|
+
from pynumerics.sampling import _parameter_set
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _mcmc() -> Any:
|
|
59
|
+
"""Return the ``Numerics.Sampling.MCMC`` namespace after starting the runtime."""
|
|
60
|
+
return namespace("Numerics.Sampling.MCMC")
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class InitializationType(NetEnum):
|
|
64
|
+
"""How the chains start (``MCMCSampler.InitializationType``)."""
|
|
65
|
+
|
|
66
|
+
MAP = "MAP"
|
|
67
|
+
RANDOMIZE = "Randomize"
|
|
68
|
+
USER_DEFINED = "UserDefined"
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _priors(priors: Sequence[Any]) -> Any:
|
|
72
|
+
"""Build the ``List<IUnivariateDistribution>`` of priors from pynumerics or .NET distributions."""
|
|
73
|
+
nd = namespace("Numerics.Distributions")
|
|
74
|
+
from System.Collections.Generic import List # noqa: PLC0415 - the runtime is running
|
|
75
|
+
|
|
76
|
+
if isinstance(priors, List[nd.IUnivariateDistribution]):
|
|
77
|
+
return priors
|
|
78
|
+
items = List[nd.IUnivariateDistribution]()
|
|
79
|
+
for prior in priors:
|
|
80
|
+
items.Add(getattr(prior, "net", prior))
|
|
81
|
+
return items
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _point(parameter_set: Any) -> tuple[np.ndarray, float]:
|
|
85
|
+
"""Return a ``ParameterSet`` as ``(values, fitness)``; an empty set gives an empty array."""
|
|
86
|
+
values = parameter_set.Values
|
|
87
|
+
return (np.empty(0) if values is None else to_numpy_doubles(values)), float(parameter_set.Fitness)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _read_sets(sets: Any, width: int, *, fitness: bool = True,
|
|
91
|
+
weights: bool = True) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
|
|
92
|
+
"""Read a .NET list of ``ParameterSet`` into ``(values (n, p), fitness (n,), weight (n,))``.
|
|
93
|
+
|
|
94
|
+
The list is enumerated once and the value arrays are joined by one ``numpy.concatenate`` over
|
|
95
|
+
their buffers, so each set costs one crossing for the set and one for its values. The fitness
|
|
96
|
+
or weight array is empty, and not read, when ``fitness`` or ``weights`` is False.
|
|
97
|
+
"""
|
|
98
|
+
items = list(sets)
|
|
99
|
+
count = len(items)
|
|
100
|
+
values = np.concatenate([item.Values for item in items]) if count else np.empty(0)
|
|
101
|
+
values = values.astype(np.float64, copy=False).reshape(count, width)
|
|
102
|
+
scores = (np.fromiter((item.Fitness for item in items), dtype=np.float64, count=count) if fitness
|
|
103
|
+
else np.empty(0))
|
|
104
|
+
weight = np.fromiter((item.Weight for item in items), dtype=np.float64, count=count) if weights else np.empty(0)
|
|
105
|
+
return values, scores, weight
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _chain_values(chains: Any, width: int) -> tuple[np.ndarray, np.ndarray]:
|
|
109
|
+
"""Read ``List<ParameterSet>[]`` into ``(values (c, n, p), fitness (c, n))`` over the common length."""
|
|
110
|
+
read = [_read_sets(chain, width, weights=False) for chain in chains]
|
|
111
|
+
if not read:
|
|
112
|
+
return np.empty((0, 0, width)), np.empty((0, 0))
|
|
113
|
+
length = min(r[0].shape[0] for r in read)
|
|
114
|
+
return np.stack([r[0][:length] for r in read]), np.stack([r[1][:length] for r in read])
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _cached_chain_values(owner: Any, chains: Any, width: int) -> tuple[np.ndarray, np.ndarray]:
|
|
118
|
+
"""Return :func:`_chain_values` of ``chains``, read once and kept on ``owner`` until the chains change.
|
|
119
|
+
|
|
120
|
+
The cache holds while ``owner.net`` is the same object, ``chains`` is the same .NET array and
|
|
121
|
+
every chain has the same count; :meth:`MCMCSampler.sample` also clears it. Callers return copies,
|
|
122
|
+
so the cached arrays are never handed out.
|
|
123
|
+
"""
|
|
124
|
+
import System # noqa: PLC0415 - the runtime is running
|
|
125
|
+
|
|
126
|
+
counts = tuple(int(chain.Count) for chain in chains)
|
|
127
|
+
cache = getattr(owner, "_chain_cache", None)
|
|
128
|
+
if (cache is not None and cache[0] is owner.net and System.Object.ReferenceEquals(cache[1], chains)
|
|
129
|
+
and cache[2] == counts):
|
|
130
|
+
return cache[3], cache[4]
|
|
131
|
+
values, fitness = _chain_values(chains, width)
|
|
132
|
+
owner._chain_cache = (owner.net, chains, counts, values, fitness)
|
|
133
|
+
return values, fitness
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _chains_to_net(chains: Any) -> Any:
|
|
137
|
+
"""Return Markov chains as ``List<ParameterSet>[]``.
|
|
138
|
+
|
|
139
|
+
Accepts a sampler or results wrapper, a .NET chain array, or an array of shape
|
|
140
|
+
``(chains, iterations, parameters)`` (a 2-D array is one parameter per chain).
|
|
141
|
+
"""
|
|
142
|
+
net = getattr(chains, "net", None)
|
|
143
|
+
if net is not None:
|
|
144
|
+
return net.MarkovChains
|
|
145
|
+
ps = _opt().ParameterSet
|
|
146
|
+
import System # noqa: PLC0415 - the runtime is running
|
|
147
|
+
from System.Collections.Generic import List # noqa: PLC0415
|
|
148
|
+
|
|
149
|
+
if isinstance(chains, System.Array):
|
|
150
|
+
return chains
|
|
151
|
+
array = np.asarray(chains, dtype=np.float64)
|
|
152
|
+
if array.ndim == 2:
|
|
153
|
+
array = array[:, :, None]
|
|
154
|
+
if array.ndim != 3:
|
|
155
|
+
raise ValueError(f"Expected chains of shape (chains, iterations, parameters); got {array.shape}.")
|
|
156
|
+
result = System.Array.CreateInstance(List[ps], array.shape[0])
|
|
157
|
+
nan = float("nan")
|
|
158
|
+
for c in range(array.shape[0]):
|
|
159
|
+
chain = List[ps](array.shape[1])
|
|
160
|
+
for row in array[c]:
|
|
161
|
+
chain.Add(ps(to_net_doubles(row), nan))
|
|
162
|
+
result[c] = chain
|
|
163
|
+
return result
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
_LIKELIHOOD_FACTORY = """
|
|
167
|
+
static LogLikelihood Create(UnivariateDistributionBase template, double[] data, IUnivariateDistribution[] priors,
|
|
168
|
+
int[] indices)
|
|
169
|
+
{
|
|
170
|
+
var baseline = template.GetParameters;
|
|
171
|
+
return parameters =>
|
|
172
|
+
{
|
|
173
|
+
double logPrior = 0d;
|
|
174
|
+
if (priors != null)
|
|
175
|
+
{
|
|
176
|
+
for (int i = 0; i < priors.Length; i++)
|
|
177
|
+
logPrior += priors[i].LogPDF(parameters[i]);
|
|
178
|
+
if (double.IsNaN(logPrior) || double.IsNegativeInfinity(logPrior))
|
|
179
|
+
return double.NegativeInfinity;
|
|
180
|
+
}
|
|
181
|
+
var values = (double[])baseline.Clone();
|
|
182
|
+
for (int i = 0; i < indices.Length; i++)
|
|
183
|
+
values[indices[i]] = parameters[i];
|
|
184
|
+
var model = template.Clone();
|
|
185
|
+
model.SetParameters(values);
|
|
186
|
+
if (!model.ParametersValid)
|
|
187
|
+
return double.NegativeInfinity;
|
|
188
|
+
return logPrior + model.LogLikelihood(data);
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
"""
|
|
192
|
+
"""C# factory of the in-.NET log-likelihood: a fresh clone per evaluation, so parallel chains share nothing."""
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def distribution_log_likelihood(distribution: Any, data: Any, priors: Sequence[Any] | None = None,
|
|
196
|
+
parameter_indices: Sequence[int] | None = None, add_log_prior: bool = False) -> Any:
|
|
197
|
+
"""Return the log-likelihood of data under a Numerics distribution as a delegate that runs in .NET.
|
|
198
|
+
|
|
199
|
+
The delegate sets the sampled parameters on a clone of ``distribution`` and returns
|
|
200
|
+
``LogLikelihood(data)`` (``UnivariateDistributionBase.LogLikelihood``), plus the priors'
|
|
201
|
+
log-densities when ``add_log_prior`` is True. No Python runs per evaluation, so samplers
|
|
202
|
+
given it keep ``ParallelizeChains`` on. The factory is compiled once per process with Roslyn
|
|
203
|
+
(:func:`pynumerics.callbacks.compile_csharp`).
|
|
204
|
+
|
|
205
|
+
Parameters
|
|
206
|
+
----------
|
|
207
|
+
distribution : pynumerics.distributions.UnivariateDistribution or UnivariateDistributionBase
|
|
208
|
+
The model; its current parameters are the values of the parameters not sampled.
|
|
209
|
+
data : array_like
|
|
210
|
+
The observations.
|
|
211
|
+
priors : sequence of distributions, optional
|
|
212
|
+
The priors of the sampled parameters, in order; used only with ``add_log_prior``.
|
|
213
|
+
parameter_indices : sequence of int, optional
|
|
214
|
+
The positions in ``GetParameters`` of the sampled parameters, in sampling order; default
|
|
215
|
+
all parameters in their order. Parameters not listed keep the template's values (for
|
|
216
|
+
example a known scale).
|
|
217
|
+
add_log_prior : bool, optional
|
|
218
|
+
Add ``sum(prior.LogPDF(theta_i))`` so the delegate is the full log-posterior; default
|
|
219
|
+
False, which is right for uniform priors.
|
|
220
|
+
|
|
221
|
+
Returns
|
|
222
|
+
-------
|
|
223
|
+
Numerics.Sampling.MCMC.LogLikelihood
|
|
224
|
+
The delegate. Parameters the distribution rejects (``ParametersValid`` false) and draws of
|
|
225
|
+
zero prior density return negative infinity.
|
|
226
|
+
|
|
227
|
+
Raises
|
|
228
|
+
------
|
|
229
|
+
ValueError
|
|
230
|
+
If ``add_log_prior`` is True without priors, or the priors and indices differ in length.
|
|
231
|
+
pynumerics.RuntimeLoadError
|
|
232
|
+
If the bundled Roslyn assemblies are missing.
|
|
233
|
+
"""
|
|
234
|
+
mc = _mcmc()
|
|
235
|
+
nd = namespace("Numerics.Distributions")
|
|
236
|
+
import System # noqa: PLC0415 - the runtime is running
|
|
237
|
+
|
|
238
|
+
template = unwrap(getattr(distribution, "net", distribution))
|
|
239
|
+
count = int(template.NumberOfParameters)
|
|
240
|
+
indices = list(range(count)) if parameter_indices is None else [int(i) for i in parameter_indices]
|
|
241
|
+
prior_array = None
|
|
242
|
+
if add_log_prior:
|
|
243
|
+
if priors is None:
|
|
244
|
+
raise ValueError("add_log_prior=True needs the priors.")
|
|
245
|
+
prior_list = [getattr(p, "net", p) for p in priors]
|
|
246
|
+
if len(prior_list) != len(indices):
|
|
247
|
+
raise ValueError(f"{len(prior_list)} priors for {len(indices)} sampled parameters.")
|
|
248
|
+
prior_array = System.Array[nd.IUnivariateDistribution](prior_list)
|
|
249
|
+
factory_type = System.Func[nd.UnivariateDistributionBase, System.Array[System.Double],
|
|
250
|
+
System.Array[nd.IUnivariateDistribution], System.Array[System.Int32], mc.LogLikelihood]
|
|
251
|
+
factory = callbacks.compile_csharp(_LIKELIHOOD_FACTORY, factory_type, usings=("Numerics.Sampling.MCMC",))
|
|
252
|
+
flat = np.ravel(np.asarray(data, dtype=np.float64))
|
|
253
|
+
return factory(template.Clone(), to_net_doubles(flat), prior_array, to_net_ints(indices))
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def _likelihood(log_likelihood: Any) -> Any:
|
|
257
|
+
"""Return the ``LogLikelihood`` delegate of a callable, C# source or delegate."""
|
|
258
|
+
return callbacks.log_likelihood(log_likelihood)
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
class MCMCSampler:
|
|
262
|
+
"""Base of the Numerics MCMC samplers (``Numerics.Sampling.MCMC.MCMCSampler``).
|
|
263
|
+
|
|
264
|
+
Use a concrete sampler (:class:`RWMH`, :class:`ARWMH`, :class:`DEMCz`, :class:`DEMCzs`,
|
|
265
|
+
:class:`HMC`, :class:`NUTS`, :class:`Gibbs`, :class:`SNIS`). The keyword settings are applied
|
|
266
|
+
after the .NET sampler is built; None keeps the Numerics default. Setting the seed or a run
|
|
267
|
+
length resets the sampler, as in Numerics.
|
|
268
|
+
|
|
269
|
+
Parameters
|
|
270
|
+
----------
|
|
271
|
+
net : Numerics.Sampling.MCMC.MCMCSampler
|
|
272
|
+
The .NET sampler.
|
|
273
|
+
functions : sequence
|
|
274
|
+
The functions given to it, for the parallel rule of :mod:`pynumerics.callbacks`.
|
|
275
|
+
seed : int, optional
|
|
276
|
+
``PRNGSeed``; Numerics default 12345.
|
|
277
|
+
number_of_chains : int, optional
|
|
278
|
+
``NumberOfChains``.
|
|
279
|
+
iterations : int, optional
|
|
280
|
+
``Iterations``, the recorded iterations per chain including warmup.
|
|
281
|
+
warmup_iterations : int, optional
|
|
282
|
+
``WarmupIterations``.
|
|
283
|
+
thinning_interval : int, optional
|
|
284
|
+
``ThinningInterval``.
|
|
285
|
+
output_length : int, optional
|
|
286
|
+
``OutputLength``, the posterior draws output after the iterations.
|
|
287
|
+
initial_iterations : int, optional
|
|
288
|
+
``InitialIterations``, the initial population size.
|
|
289
|
+
initialize : InitializationType or str, optional
|
|
290
|
+
``Initialize``; Numerics default ``Randomize``.
|
|
291
|
+
parallelize_chains : bool, optional
|
|
292
|
+
``ParallelizeChains``; Numerics default True. A Python callback always turns it off.
|
|
293
|
+
|
|
294
|
+
Attributes
|
|
295
|
+
----------
|
|
296
|
+
net : Numerics.Sampling.MCMC.MCMCSampler
|
|
297
|
+
The wrapped .NET sampler.
|
|
298
|
+
"""
|
|
299
|
+
|
|
300
|
+
def __init__(self, net: Any, functions: Sequence[Any] = (), *, seed: int | None = None,
|
|
301
|
+
number_of_chains: int | None = None, iterations: int | None = None,
|
|
302
|
+
warmup_iterations: int | None = None, thinning_interval: int | None = None,
|
|
303
|
+
output_length: int | None = None, initial_iterations: int | None = None,
|
|
304
|
+
initialize: InitializationType | str | None = None, parallelize_chains: bool | None = None) -> None:
|
|
305
|
+
self.net = net
|
|
306
|
+
self._handlers: list[Any] = []
|
|
307
|
+
settings = (("PRNGSeed", seed), ("NumberOfChains", number_of_chains), ("Iterations", iterations),
|
|
308
|
+
("WarmupIterations", warmup_iterations), ("ThinningInterval", thinning_interval),
|
|
309
|
+
("OutputLength", output_length), ("InitialIterations", initial_iterations))
|
|
310
|
+
for name, value in settings:
|
|
311
|
+
if value is not None:
|
|
312
|
+
setattr(net, name, int(value))
|
|
313
|
+
if initialize is not None:
|
|
314
|
+
self.initialize = initialize
|
|
315
|
+
if parallelize_chains is not None:
|
|
316
|
+
net.ParallelizeChains = bool(parallelize_chains)
|
|
317
|
+
callbacks.apply_parallel_policy(net, *functions)
|
|
318
|
+
|
|
319
|
+
@property
|
|
320
|
+
def seed(self) -> int:
|
|
321
|
+
"""The PRNG seed (``PRNGSeed``); setting it resets the sampler."""
|
|
322
|
+
return int(self.net.PRNGSeed)
|
|
323
|
+
|
|
324
|
+
@seed.setter
|
|
325
|
+
def seed(self, value: int) -> None:
|
|
326
|
+
self.net.PRNGSeed = int(value)
|
|
327
|
+
|
|
328
|
+
@property
|
|
329
|
+
def initial_iterations(self) -> int:
|
|
330
|
+
"""The size of the initial population (``InitialIterations``); setting it resets the sampler."""
|
|
331
|
+
return int(self.net.InitialIterations)
|
|
332
|
+
|
|
333
|
+
@initial_iterations.setter
|
|
334
|
+
def initial_iterations(self, value: int) -> None:
|
|
335
|
+
self.net.InitialIterations = int(value)
|
|
336
|
+
|
|
337
|
+
@property
|
|
338
|
+
def warmup_iterations(self) -> int:
|
|
339
|
+
"""The warmup iterations, part of ``iterations`` (``WarmupIterations``); setting it resets the sampler."""
|
|
340
|
+
return int(self.net.WarmupIterations)
|
|
341
|
+
|
|
342
|
+
@warmup_iterations.setter
|
|
343
|
+
def warmup_iterations(self, value: int) -> None:
|
|
344
|
+
self.net.WarmupIterations = int(value)
|
|
345
|
+
|
|
346
|
+
@property
|
|
347
|
+
def iterations(self) -> int:
|
|
348
|
+
"""The recorded iterations per chain (``Iterations``); setting it resets the sampler."""
|
|
349
|
+
return int(self.net.Iterations)
|
|
350
|
+
|
|
351
|
+
@iterations.setter
|
|
352
|
+
def iterations(self, value: int) -> None:
|
|
353
|
+
self.net.Iterations = int(value)
|
|
354
|
+
|
|
355
|
+
@property
|
|
356
|
+
def number_of_chains(self) -> int:
|
|
357
|
+
"""The number of chains (``NumberOfChains``); setting it resets the sampler."""
|
|
358
|
+
return int(self.net.NumberOfChains)
|
|
359
|
+
|
|
360
|
+
@number_of_chains.setter
|
|
361
|
+
def number_of_chains(self, value: int) -> None:
|
|
362
|
+
self.net.NumberOfChains = int(value)
|
|
363
|
+
|
|
364
|
+
@property
|
|
365
|
+
def thinning_interval(self) -> int:
|
|
366
|
+
"""Transitions per recorded iteration (``ThinningInterval``); setting it resets the sampler."""
|
|
367
|
+
return int(self.net.ThinningInterval)
|
|
368
|
+
|
|
369
|
+
@thinning_interval.setter
|
|
370
|
+
def thinning_interval(self, value: int) -> None:
|
|
371
|
+
self.net.ThinningInterval = int(value)
|
|
372
|
+
|
|
373
|
+
@property
|
|
374
|
+
def output_length(self) -> int:
|
|
375
|
+
"""The number of posterior draws output after the iterations, over all chains (``OutputLength``)."""
|
|
376
|
+
return int(self.net.OutputLength)
|
|
377
|
+
|
|
378
|
+
@output_length.setter
|
|
379
|
+
def output_length(self, value: int) -> None:
|
|
380
|
+
self.net.OutputLength = int(value)
|
|
381
|
+
|
|
382
|
+
@property
|
|
383
|
+
def initialize(self) -> InitializationType:
|
|
384
|
+
"""How the chains start (``Initialize``)."""
|
|
385
|
+
return InitializationType(str(self.net.Initialize))
|
|
386
|
+
|
|
387
|
+
@initialize.setter
|
|
388
|
+
def initialize(self, value: InitializationType | str) -> None:
|
|
389
|
+
self.net.Initialize = net_enum(_mcmc().MCMCSampler.InitializationType, value)
|
|
390
|
+
|
|
391
|
+
@property
|
|
392
|
+
def parallelize_chains(self) -> bool:
|
|
393
|
+
"""Whether chains advance on several threads (``ParallelizeChains``)."""
|
|
394
|
+
return bool(self.net.ParallelizeChains)
|
|
395
|
+
|
|
396
|
+
@parallelize_chains.setter
|
|
397
|
+
def parallelize_chains(self, value: bool) -> None:
|
|
398
|
+
self.net.ParallelizeChains = bool(value)
|
|
399
|
+
|
|
400
|
+
@property
|
|
401
|
+
def resume_simulation(self) -> bool:
|
|
402
|
+
"""Whether another :meth:`sample` continues the chains instead of restarting (``ResumeSimulation``)."""
|
|
403
|
+
return bool(self.net.ResumeSimulation)
|
|
404
|
+
|
|
405
|
+
@resume_simulation.setter
|
|
406
|
+
def resume_simulation(self, value: bool) -> None:
|
|
407
|
+
self.net.ResumeSimulation = bool(value)
|
|
408
|
+
|
|
409
|
+
@property
|
|
410
|
+
def progress_changed_rate(self) -> float:
|
|
411
|
+
"""The fraction of the run between progress reports (``ProgressChangedRate``)."""
|
|
412
|
+
return float(self.net.ProgressChangedRate)
|
|
413
|
+
|
|
414
|
+
@progress_changed_rate.setter
|
|
415
|
+
def progress_changed_rate(self, value: float) -> None:
|
|
416
|
+
self.net.ProgressChangedRate = float(value)
|
|
417
|
+
|
|
418
|
+
@property
|
|
419
|
+
def cancellation_token_source(self) -> Any:
|
|
420
|
+
"""The ``System.Threading.CancellationTokenSource`` of the current run (``CancellationTokenSource``)."""
|
|
421
|
+
return self.net.CancellationTokenSource
|
|
422
|
+
|
|
423
|
+
@cancellation_token_source.setter
|
|
424
|
+
def cancellation_token_source(self, value: Any) -> None:
|
|
425
|
+
self.net.CancellationTokenSource = value
|
|
426
|
+
|
|
427
|
+
@property
|
|
428
|
+
def cancellation_token(self) -> Any:
|
|
429
|
+
"""An external ``System.Threading.CancellationToken`` that stops the run (``CancellationToken``)."""
|
|
430
|
+
return self.net.CancellationToken
|
|
431
|
+
|
|
432
|
+
@cancellation_token.setter
|
|
433
|
+
def cancellation_token(self, value: Any) -> None:
|
|
434
|
+
self.net.CancellationToken = value
|
|
435
|
+
|
|
436
|
+
@property
|
|
437
|
+
def log_likelihood_function(self) -> Any:
|
|
438
|
+
"""The ``LogLikelihood`` delegate the sampler evaluates (``LogLikelihoodFunction``)."""
|
|
439
|
+
return self.net.LogLikelihoodFunction
|
|
440
|
+
|
|
441
|
+
@property
|
|
442
|
+
def prior_distributions(self) -> list[Any]:
|
|
443
|
+
"""The prior distributions as pynumerics wrappers (``PriorDistributions``)."""
|
|
444
|
+
from pynumerics.distributions import wrap # noqa: PLC0415 - keeps the import of this module light
|
|
445
|
+
|
|
446
|
+
return [wrap(p) for p in self.net.PriorDistributions]
|
|
447
|
+
|
|
448
|
+
@property
|
|
449
|
+
def number_of_parameters(self) -> int:
|
|
450
|
+
"""The number of model parameters (``NumberOfParameters``)."""
|
|
451
|
+
return int(self.net.NumberOfParameters)
|
|
452
|
+
|
|
453
|
+
@property
|
|
454
|
+
def is_population_sampler(self) -> bool:
|
|
455
|
+
"""Whether proposals draw on the population of past states (``IsPopulationSampler``)."""
|
|
456
|
+
return bool(self.net.IsPopulationSampler)
|
|
457
|
+
|
|
458
|
+
@property
|
|
459
|
+
def transition_count(self) -> int:
|
|
460
|
+
"""Chain transitions one chain performs per :meth:`sample` (``TransitionCount``)."""
|
|
461
|
+
return int(self.net.TransitionCount)
|
|
462
|
+
|
|
463
|
+
@property
|
|
464
|
+
def total_transition_count(self) -> int:
|
|
465
|
+
"""Chain transitions over all chains per :meth:`sample` (``TotalTransitionCount``)."""
|
|
466
|
+
return int(self.net.TotalTransitionCount)
|
|
467
|
+
|
|
468
|
+
@property
|
|
469
|
+
def markov_chains(self) -> np.ndarray:
|
|
470
|
+
"""The recorded chains, shape ``(chains, iterations, parameters)`` (``MarkovChains``).
|
|
471
|
+
|
|
472
|
+
The chains are read from .NET once per run and cached; each access returns a new copy.
|
|
473
|
+
"""
|
|
474
|
+
return _cached_chain_values(self, self.net.MarkovChains, self.number_of_parameters)[0].copy()
|
|
475
|
+
|
|
476
|
+
@property
|
|
477
|
+
def markov_chain_log_likelihoods(self) -> np.ndarray:
|
|
478
|
+
"""The log-likelihood of each recorded state, shape ``(chains, iterations)`` (``MarkovChains``).
|
|
479
|
+
|
|
480
|
+
Read with :attr:`markov_chains` and cached the same way; each access returns a new copy.
|
|
481
|
+
"""
|
|
482
|
+
return _cached_chain_values(self, self.net.MarkovChains, self.number_of_parameters)[1].copy()
|
|
483
|
+
|
|
484
|
+
@property
|
|
485
|
+
def output(self) -> list[np.ndarray]:
|
|
486
|
+
"""The posterior draws of each chain, one ``(draws, parameters)`` array per chain (``Output``)."""
|
|
487
|
+
width = self.number_of_parameters
|
|
488
|
+
return [_read_sets(chain, width, fitness=False, weights=False)[0] for chain in self.net.Output]
|
|
489
|
+
|
|
490
|
+
@property
|
|
491
|
+
def population_matrix(self) -> np.ndarray:
|
|
492
|
+
"""The population of a population sampler, shape ``(states, parameters)`` (``PopulationMatrix``)."""
|
|
493
|
+
return _read_sets(self.net.PopulationMatrix, self.number_of_parameters, fitness=False, weights=False)[0]
|
|
494
|
+
|
|
495
|
+
@property
|
|
496
|
+
def accept_count(self) -> np.ndarray:
|
|
497
|
+
"""Accepted proposals per chain (``AcceptCount``)."""
|
|
498
|
+
return to_numpy_ints(self.net.AcceptCount).astype(np.int64)
|
|
499
|
+
|
|
500
|
+
@property
|
|
501
|
+
def sample_count(self) -> np.ndarray:
|
|
502
|
+
"""Proposals per chain (``SampleCount``)."""
|
|
503
|
+
return to_numpy_ints(self.net.SampleCount).astype(np.int64)
|
|
504
|
+
|
|
505
|
+
@property
|
|
506
|
+
def acceptance_rates(self) -> np.ndarray:
|
|
507
|
+
"""Accepted over proposed per chain (``AcceptanceRates``)."""
|
|
508
|
+
return to_numpy_doubles(self.net.AcceptanceRates)
|
|
509
|
+
|
|
510
|
+
@property
|
|
511
|
+
def mean_log_likelihood(self) -> np.ndarray:
|
|
512
|
+
"""The log-likelihood averaged over chains at each iteration (``MeanLogLikelihood``)."""
|
|
513
|
+
return to_numpy_doubles(self.net.MeanLogLikelihood)
|
|
514
|
+
|
|
515
|
+
@property
|
|
516
|
+
def map(self) -> tuple[np.ndarray, float]:
|
|
517
|
+
"""The output draw of largest log-likelihood as ``(values, log_likelihood)`` (``MAP``)."""
|
|
518
|
+
return _point(self.net.MAP)
|
|
519
|
+
|
|
520
|
+
@property
|
|
521
|
+
def map_initialization_failed(self) -> bool:
|
|
522
|
+
"""Whether MAP initialization failed and the run started from the priors (``MAPInitializationFailed``)."""
|
|
523
|
+
return bool(self.net.MAPInitializationFailed)
|
|
524
|
+
|
|
525
|
+
def sample(self) -> MCMCResults:
|
|
526
|
+
"""Run the sampler (``Sample``) and post-process the run (``MCMCResults(sampler)``).
|
|
527
|
+
|
|
528
|
+
Returns
|
|
529
|
+
-------
|
|
530
|
+
MCMCResults
|
|
531
|
+
The results at the default 90 percent intervals; :meth:`results` gives another level.
|
|
532
|
+
"""
|
|
533
|
+
self._chain_cache = None
|
|
534
|
+
self.net.Sample()
|
|
535
|
+
return MCMCResults(self)
|
|
536
|
+
|
|
537
|
+
def results(self, alpha: float = 0.1) -> MCMCResults:
|
|
538
|
+
"""Return the post-processed results of the last run (``MCMCResults(sampler, alpha)``).
|
|
539
|
+
|
|
540
|
+
Parameters
|
|
541
|
+
----------
|
|
542
|
+
alpha : float, optional
|
|
543
|
+
Two-sided interval level; default 0.1 (90 percent intervals).
|
|
544
|
+
|
|
545
|
+
Returns
|
|
546
|
+
-------
|
|
547
|
+
MCMCResults
|
|
548
|
+
The results.
|
|
549
|
+
"""
|
|
550
|
+
return MCMCResults(self, alpha)
|
|
551
|
+
|
|
552
|
+
def reset(self) -> None:
|
|
553
|
+
"""Clear the chains and results and reseed the generators (``Reset``)."""
|
|
554
|
+
self.net.Reset()
|
|
555
|
+
|
|
556
|
+
def cancel_simulation(self) -> None:
|
|
557
|
+
"""Stop the running :meth:`sample` after the current iteration (``CancelSimulation``)."""
|
|
558
|
+
self.net.CancelSimulation()
|
|
559
|
+
|
|
560
|
+
def report_progress(self, percent_complete: float) -> None:
|
|
561
|
+
"""Raise the progress event (``ReportProgress``).
|
|
562
|
+
|
|
563
|
+
Parameters
|
|
564
|
+
----------
|
|
565
|
+
percent_complete : float
|
|
566
|
+
Fraction complete in [0, 1].
|
|
567
|
+
"""
|
|
568
|
+
self.net.ReportProgress(float(percent_complete))
|
|
569
|
+
|
|
570
|
+
def add_progress_handler(self, handler: Any) -> Any:
|
|
571
|
+
"""Subscribe to progress reports (the ``ProgressChanged`` event).
|
|
572
|
+
|
|
573
|
+
Parameters
|
|
574
|
+
----------
|
|
575
|
+
handler : callable, str or System.Delegate
|
|
576
|
+
``handler(percent_complete, progress_text)``; called on the thread running :meth:`sample`.
|
|
577
|
+
|
|
578
|
+
Returns
|
|
579
|
+
-------
|
|
580
|
+
Numerics.Sampling.MCMC.MCMCSampler.ProgressChangedEventHandler
|
|
581
|
+
The subscribed delegate, for :meth:`remove_progress_handler`.
|
|
582
|
+
"""
|
|
583
|
+
delegate = callbacks.to_delegate(handler, "MCMCSampler.ProgressChangedEventHandler")
|
|
584
|
+
self.net.ProgressChanged += delegate
|
|
585
|
+
self._handlers.append(delegate)
|
|
586
|
+
return delegate
|
|
587
|
+
|
|
588
|
+
def remove_progress_handler(self, delegate: Any) -> None:
|
|
589
|
+
"""Unsubscribe a delegate returned by :meth:`add_progress_handler`.
|
|
590
|
+
|
|
591
|
+
Parameters
|
|
592
|
+
----------
|
|
593
|
+
delegate : Numerics.Sampling.MCMC.MCMCSampler.ProgressChangedEventHandler
|
|
594
|
+
The delegate.
|
|
595
|
+
"""
|
|
596
|
+
self.net.ProgressChanged -= delegate
|
|
597
|
+
if delegate in self._handlers:
|
|
598
|
+
self._handlers.remove(delegate)
|
|
599
|
+
|
|
600
|
+
@staticmethod
|
|
601
|
+
def current_chain_index() -> int:
|
|
602
|
+
"""Return the chain the calling thread is advancing, or -1 outside one (``CurrentChainIndex``).
|
|
603
|
+
|
|
604
|
+
Returns
|
|
605
|
+
-------
|
|
606
|
+
int
|
|
607
|
+
The zero-based chain index.
|
|
608
|
+
"""
|
|
609
|
+
return int(_mcmc().MCMCSampler.CurrentChainIndex)
|
|
610
|
+
|
|
611
|
+
|
|
612
|
+
class RWMH(MCMCSampler):
|
|
613
|
+
"""Random-walk Metropolis-Hastings (``Numerics.Sampling.MCMC.RWMH``).
|
|
614
|
+
|
|
615
|
+
Parameters
|
|
616
|
+
----------
|
|
617
|
+
priors : sequence of distributions
|
|
618
|
+
Prior of each parameter (pynumerics or .NET distributions).
|
|
619
|
+
log_likelihood : callable, str or System.Delegate
|
|
620
|
+
Log-posterior of the parameter array.
|
|
621
|
+
proposal_sigma : array_like, optional
|
|
622
|
+
Proposal covariance, shape ``(p, p)``; default a zero matrix, which suits only
|
|
623
|
+
``initialize="MAP"`` (the MAP covariance replaces it).
|
|
624
|
+
**settings
|
|
625
|
+
Keyword settings of :class:`MCMCSampler`.
|
|
626
|
+
"""
|
|
627
|
+
|
|
628
|
+
def __init__(self, priors: Sequence[Any], log_likelihood: Any, proposal_sigma: Any = None, **settings: Any) -> None:
|
|
629
|
+
net_priors = _priors(priors)
|
|
630
|
+
sigma = (_linear_algebra().Matrix(int(net_priors.Count)) if proposal_sigma is None
|
|
631
|
+
else _numerics_matrix(proposal_sigma, pass_through=True))
|
|
632
|
+
super().__init__(_mcmc().RWMH(net_priors, _likelihood(log_likelihood), sigma), (log_likelihood,), **settings)
|
|
633
|
+
|
|
634
|
+
@property
|
|
635
|
+
def proposal_sigma(self) -> np.ndarray:
|
|
636
|
+
"""The proposal covariance (``ProposalSigma``)."""
|
|
637
|
+
return to_numpy_matrix(self.net.ProposalSigma.ToArray())
|
|
638
|
+
|
|
639
|
+
|
|
640
|
+
class ARWMH(MCMCSampler):
|
|
641
|
+
"""Adaptive random-walk Metropolis-Hastings (``Numerics.Sampling.MCMC.ARWMH``).
|
|
642
|
+
|
|
643
|
+
Parameters
|
|
644
|
+
----------
|
|
645
|
+
priors : sequence of distributions
|
|
646
|
+
Prior of each parameter.
|
|
647
|
+
log_likelihood : callable, str or System.Delegate
|
|
648
|
+
Log-posterior of the parameter array.
|
|
649
|
+
**settings
|
|
650
|
+
Keyword settings of :class:`MCMCSampler`.
|
|
651
|
+
"""
|
|
652
|
+
|
|
653
|
+
def __init__(self, priors: Sequence[Any], log_likelihood: Any, **settings: Any) -> None:
|
|
654
|
+
super().__init__(_mcmc().ARWMH(_priors(priors), _likelihood(log_likelihood)), (log_likelihood,), **settings)
|
|
655
|
+
|
|
656
|
+
@property
|
|
657
|
+
def scale(self) -> float:
|
|
658
|
+
"""The scale of the adapted covariance (``Scale``)."""
|
|
659
|
+
return float(self.net.Scale)
|
|
660
|
+
|
|
661
|
+
@scale.setter
|
|
662
|
+
def scale(self, value: float) -> None:
|
|
663
|
+
self.net.Scale = float(value)
|
|
664
|
+
|
|
665
|
+
@property
|
|
666
|
+
def beta(self) -> float:
|
|
667
|
+
"""The fraction of proposals drawn from the small identity covariance (``Beta``)."""
|
|
668
|
+
return float(self.net.Beta)
|
|
669
|
+
|
|
670
|
+
@beta.setter
|
|
671
|
+
def beta(self, value: float) -> None:
|
|
672
|
+
self.net.Beta = float(value)
|
|
673
|
+
|
|
674
|
+
@property
|
|
675
|
+
def proposal_sigma(self) -> list[np.ndarray]:
|
|
676
|
+
"""The current proposal covariance of each chain (``ProposalSigma``)."""
|
|
677
|
+
return [to_numpy_matrix(m.ToArray()) for m in self.net.ProposalSigma]
|
|
678
|
+
|
|
679
|
+
|
|
680
|
+
class DEMCz(MCMCSampler):
|
|
681
|
+
"""Differential evolution Markov chain with past-state sampling (``Numerics.Sampling.MCMC.DEMCz``).
|
|
682
|
+
|
|
683
|
+
Parameters
|
|
684
|
+
----------
|
|
685
|
+
priors : sequence of distributions
|
|
686
|
+
Prior of each parameter.
|
|
687
|
+
log_likelihood : callable, str or System.Delegate
|
|
688
|
+
Log-posterior of the parameter array.
|
|
689
|
+
**settings
|
|
690
|
+
Keyword settings of :class:`MCMCSampler`; at least three chains are needed.
|
|
691
|
+
"""
|
|
692
|
+
|
|
693
|
+
_NET_TYPE = "DEMCz"
|
|
694
|
+
|
|
695
|
+
def __init__(self, priors: Sequence[Any], log_likelihood: Any, **settings: Any) -> None:
|
|
696
|
+
net_type = getattr(_mcmc(), self._NET_TYPE)
|
|
697
|
+
super().__init__(net_type(_priors(priors), _likelihood(log_likelihood)), (log_likelihood,), **settings)
|
|
698
|
+
|
|
699
|
+
@property
|
|
700
|
+
def jump(self) -> float:
|
|
701
|
+
"""The jump scale gamma (``Jump``)."""
|
|
702
|
+
return float(self.net.Jump)
|
|
703
|
+
|
|
704
|
+
@jump.setter
|
|
705
|
+
def jump(self, value: float) -> None:
|
|
706
|
+
self.net.Jump = float(value)
|
|
707
|
+
|
|
708
|
+
@property
|
|
709
|
+
def jump_threshold(self) -> float:
|
|
710
|
+
"""The fraction of iterations with a jump of 1 (``JumpThreshold``)."""
|
|
711
|
+
return float(self.net.JumpThreshold)
|
|
712
|
+
|
|
713
|
+
@jump_threshold.setter
|
|
714
|
+
def jump_threshold(self, value: float) -> None:
|
|
715
|
+
self.net.JumpThreshold = float(value)
|
|
716
|
+
|
|
717
|
+
@property
|
|
718
|
+
def noise(self) -> float:
|
|
719
|
+
"""The standard deviation b of the proposal noise (``Noise``)."""
|
|
720
|
+
return float(self.net.Noise)
|
|
721
|
+
|
|
722
|
+
@noise.setter
|
|
723
|
+
def noise(self, value: float) -> None:
|
|
724
|
+
self.net.Noise = float(value)
|
|
725
|
+
|
|
726
|
+
|
|
727
|
+
class DEMCzs(DEMCz):
|
|
728
|
+
"""DEMCz with snooker updates (``Numerics.Sampling.MCMC.DEMCzs``).
|
|
729
|
+
|
|
730
|
+
Parameters
|
|
731
|
+
----------
|
|
732
|
+
priors : sequence of distributions
|
|
733
|
+
Prior of each parameter.
|
|
734
|
+
log_likelihood : callable, str or System.Delegate
|
|
735
|
+
Log-posterior of the parameter array.
|
|
736
|
+
**settings
|
|
737
|
+
Keyword settings of :class:`MCMCSampler`; at least three chains are needed.
|
|
738
|
+
"""
|
|
739
|
+
|
|
740
|
+
_NET_TYPE = "DEMCzs"
|
|
741
|
+
|
|
742
|
+
@property
|
|
743
|
+
def snooker_threshold(self) -> float:
|
|
744
|
+
"""The fraction of snooker updates (``SnookerThreshold``)."""
|
|
745
|
+
return float(self.net.SnookerThreshold)
|
|
746
|
+
|
|
747
|
+
@snooker_threshold.setter
|
|
748
|
+
def snooker_threshold(self, value: float) -> None:
|
|
749
|
+
self.net.SnookerThreshold = float(value)
|
|
750
|
+
|
|
751
|
+
|
|
752
|
+
class HMC(MCMCSampler):
|
|
753
|
+
"""Hamiltonian Monte Carlo (``Numerics.Sampling.MCMC.HMC``).
|
|
754
|
+
|
|
755
|
+
Parameters
|
|
756
|
+
----------
|
|
757
|
+
priors : sequence of distributions
|
|
758
|
+
Prior of each parameter.
|
|
759
|
+
log_likelihood : callable, str or System.Delegate
|
|
760
|
+
Log-posterior of the parameter array.
|
|
761
|
+
mass : array_like, optional
|
|
762
|
+
Mass of each parameter; default ones.
|
|
763
|
+
step_size : float, optional
|
|
764
|
+
Leapfrog step size; default 0.1.
|
|
765
|
+
steps : int, optional
|
|
766
|
+
Leapfrog steps; default 50.
|
|
767
|
+
gradient : callable, str or System.Delegate, optional
|
|
768
|
+
Gradient of the log-posterior (``HMC.Gradient``), returning an array; default Numerics
|
|
769
|
+
finite differences.
|
|
770
|
+
**settings
|
|
771
|
+
Keyword settings of :class:`MCMCSampler`.
|
|
772
|
+
"""
|
|
773
|
+
|
|
774
|
+
def __init__(self, priors: Sequence[Any], log_likelihood: Any, mass: Any = None, step_size: float = 0.1,
|
|
775
|
+
steps: int = 50, gradient: Any = None, **settings: Any) -> None:
|
|
776
|
+
grad = None if gradient is None else callbacks.to_delegate(gradient, "HMC.Gradient")
|
|
777
|
+
net = _mcmc().HMC(_priors(priors), _likelihood(log_likelihood), _numerics_vector(mass, pass_through=True),
|
|
778
|
+
float(step_size), int(steps), grad)
|
|
779
|
+
super().__init__(net, (log_likelihood, gradient), **settings)
|
|
780
|
+
|
|
781
|
+
@property
|
|
782
|
+
def mass(self) -> np.ndarray:
|
|
783
|
+
"""The mass vector (``Mass``)."""
|
|
784
|
+
return to_numpy_doubles(self.net.Mass.Array)
|
|
785
|
+
|
|
786
|
+
@property
|
|
787
|
+
def step_size(self) -> float:
|
|
788
|
+
"""The leapfrog step size (``StepSize``)."""
|
|
789
|
+
return float(self.net.StepSize)
|
|
790
|
+
|
|
791
|
+
@step_size.setter
|
|
792
|
+
def step_size(self, value: float) -> None:
|
|
793
|
+
self.net.StepSize = float(value)
|
|
794
|
+
|
|
795
|
+
@property
|
|
796
|
+
def steps(self) -> int:
|
|
797
|
+
"""The number of leapfrog steps (``Steps``)."""
|
|
798
|
+
return int(self.net.Steps)
|
|
799
|
+
|
|
800
|
+
@steps.setter
|
|
801
|
+
def steps(self, value: int) -> None:
|
|
802
|
+
self.net.Steps = int(value)
|
|
803
|
+
|
|
804
|
+
@property
|
|
805
|
+
def gradient_function(self) -> Any:
|
|
806
|
+
"""The ``HMC.Gradient`` delegate in use (``GradientFunction``)."""
|
|
807
|
+
return self.net.GradientFunction
|
|
808
|
+
|
|
809
|
+
@staticmethod
|
|
810
|
+
def quadratic_form(phi: Any, inverse_mass: Any) -> float:
|
|
811
|
+
"""Return the kinetic quadratic form ``sum(inverse_mass * phi**2)`` (``HMC.QuadraticForm``).
|
|
812
|
+
|
|
813
|
+
Parameters
|
|
814
|
+
----------
|
|
815
|
+
phi : array_like
|
|
816
|
+
Momentum.
|
|
817
|
+
inverse_mass : array_like
|
|
818
|
+
Inverse mass of each parameter.
|
|
819
|
+
|
|
820
|
+
Returns
|
|
821
|
+
-------
|
|
822
|
+
float
|
|
823
|
+
The quadratic form.
|
|
824
|
+
"""
|
|
825
|
+
return float(_mcmc().HMC.QuadraticForm(_numerics_vector(phi, pass_through=True),
|
|
826
|
+
_numerics_vector(inverse_mass, pass_through=True)))
|
|
827
|
+
|
|
828
|
+
|
|
829
|
+
class NUTS(MCMCSampler):
|
|
830
|
+
"""The No-U-Turn sampler (``Numerics.Sampling.MCMC.NUTS``).
|
|
831
|
+
|
|
832
|
+
Parameters
|
|
833
|
+
----------
|
|
834
|
+
priors : sequence of distributions
|
|
835
|
+
Prior of each parameter.
|
|
836
|
+
log_likelihood : callable, str or System.Delegate
|
|
837
|
+
Log-posterior of the parameter array.
|
|
838
|
+
mass : array_like, optional
|
|
839
|
+
Initial mass vector, adapted during warmup; default ones.
|
|
840
|
+
step_size : float, optional
|
|
841
|
+
Initial step size, adapted during warmup; default 0.1.
|
|
842
|
+
max_tree_depth : int, optional
|
|
843
|
+
Maximum tree depth; default 10.
|
|
844
|
+
gradient : callable, str or System.Delegate, optional
|
|
845
|
+
Gradient of the log-posterior (``HMC.Gradient``); default finite differences.
|
|
846
|
+
log_likelihood_and_gradient : callable, str or System.Delegate, optional
|
|
847
|
+
``(value, gradient)`` in one call (``NUTS.LogLikelihoodAndGradient``).
|
|
848
|
+
**settings
|
|
849
|
+
Keyword settings of :class:`MCMCSampler`.
|
|
850
|
+
"""
|
|
851
|
+
|
|
852
|
+
def __init__(self, priors: Sequence[Any], log_likelihood: Any, mass: Any = None, step_size: float = 0.1,
|
|
853
|
+
max_tree_depth: int = 10, gradient: Any = None, log_likelihood_and_gradient: Any = None,
|
|
854
|
+
**settings: Any) -> None:
|
|
855
|
+
grad = None if gradient is None else callbacks.to_delegate(gradient, "HMC.Gradient")
|
|
856
|
+
net = _mcmc().NUTS(_priors(priors), _likelihood(log_likelihood), _numerics_vector(mass, pass_through=True),
|
|
857
|
+
float(step_size), int(max_tree_depth), grad)
|
|
858
|
+
super().__init__(net, (log_likelihood, gradient, log_likelihood_and_gradient), **settings)
|
|
859
|
+
if log_likelihood_and_gradient is not None:
|
|
860
|
+
self.log_likelihood_and_gradient_function = log_likelihood_and_gradient
|
|
861
|
+
|
|
862
|
+
@property
|
|
863
|
+
def mass(self) -> np.ndarray:
|
|
864
|
+
"""The initial mass vector (``Mass``)."""
|
|
865
|
+
return to_numpy_doubles(self.net.Mass.Array)
|
|
866
|
+
|
|
867
|
+
@property
|
|
868
|
+
def max_tree_depth(self) -> int:
|
|
869
|
+
"""The maximum tree depth (``MaxTreeDepth``)."""
|
|
870
|
+
return int(self.net.MaxTreeDepth)
|
|
871
|
+
|
|
872
|
+
@max_tree_depth.setter
|
|
873
|
+
def max_tree_depth(self, value: int) -> None:
|
|
874
|
+
self.net.MaxTreeDepth = int(value)
|
|
875
|
+
|
|
876
|
+
@property
|
|
877
|
+
def gradient_function(self) -> Any:
|
|
878
|
+
"""The ``HMC.Gradient`` delegate in use (``GradientFunction``)."""
|
|
879
|
+
return self.net.GradientFunction
|
|
880
|
+
|
|
881
|
+
@property
|
|
882
|
+
def log_likelihood_and_gradient_function(self) -> Any:
|
|
883
|
+
"""The joint value-and-gradient delegate, or None (``LogLikelihoodAndGradientFunction``)."""
|
|
884
|
+
return self.net.LogLikelihoodAndGradientFunction
|
|
885
|
+
|
|
886
|
+
@log_likelihood_and_gradient_function.setter
|
|
887
|
+
def log_likelihood_and_gradient_function(self, value: Any) -> None:
|
|
888
|
+
delegate = None if value is None else callbacks.to_delegate(value, "NUTS.LogLikelihoodAndGradient")
|
|
889
|
+
self.net.LogLikelihoodAndGradientFunction = delegate
|
|
890
|
+
callbacks.apply_parallel_policy(self.net, value)
|
|
891
|
+
|
|
892
|
+
@property
|
|
893
|
+
def target_acceptance_rate(self) -> float:
|
|
894
|
+
"""The acceptance probability step-size adaptation targets (``TargetAcceptanceRate``)."""
|
|
895
|
+
return float(self.net.TargetAcceptanceRate)
|
|
896
|
+
|
|
897
|
+
@target_acceptance_rate.setter
|
|
898
|
+
def target_acceptance_rate(self, value: float) -> None:
|
|
899
|
+
self.net.TargetAcceptanceRate = float(value)
|
|
900
|
+
|
|
901
|
+
@property
|
|
902
|
+
def adapt_mass_matrix(self) -> bool:
|
|
903
|
+
"""Whether warmup adapts the diagonal mass (``AdaptMassMatrix``)."""
|
|
904
|
+
return bool(self.net.AdaptMassMatrix)
|
|
905
|
+
|
|
906
|
+
@adapt_mass_matrix.setter
|
|
907
|
+
def adapt_mass_matrix(self, value: bool) -> None:
|
|
908
|
+
self.net.AdaptMassMatrix = bool(value)
|
|
909
|
+
|
|
910
|
+
@property
|
|
911
|
+
def hamiltonian_acceptance_rates(self) -> np.ndarray:
|
|
912
|
+
"""Mean Hamiltonian acceptance statistic per chain (``HamiltonianAcceptanceRates``)."""
|
|
913
|
+
return to_numpy_doubles(self.net.HamiltonianAcceptanceRates)
|
|
914
|
+
|
|
915
|
+
@property
|
|
916
|
+
def diagnostic_sample_counts(self) -> np.ndarray:
|
|
917
|
+
"""Post-warmup transitions per chain behind the diagnostics (``DiagnosticSampleCounts``)."""
|
|
918
|
+
return to_numpy_ints(self.net.DiagnosticSampleCounts).astype(np.int64)
|
|
919
|
+
|
|
920
|
+
@property
|
|
921
|
+
def divergence_counts(self) -> np.ndarray:
|
|
922
|
+
"""Divergent transitions per chain (``DivergenceCounts``)."""
|
|
923
|
+
return to_numpy_ints(self.net.DivergenceCounts).astype(np.int64)
|
|
924
|
+
|
|
925
|
+
@property
|
|
926
|
+
def max_tree_depth_hit_counts(self) -> np.ndarray:
|
|
927
|
+
"""Transitions per chain that reached the maximum depth (``MaxTreeDepthHitCounts``)."""
|
|
928
|
+
return to_numpy_ints(self.net.MaxTreeDepthHitCounts).astype(np.int64)
|
|
929
|
+
|
|
930
|
+
@property
|
|
931
|
+
def mean_tree_depths(self) -> np.ndarray:
|
|
932
|
+
"""Mean tree depth per chain (``MeanTreeDepths``)."""
|
|
933
|
+
return to_numpy_doubles(self.net.MeanTreeDepths)
|
|
934
|
+
|
|
935
|
+
@property
|
|
936
|
+
def mean_leapfrog_steps(self) -> np.ndarray:
|
|
937
|
+
"""Mean leapfrog steps per transition per chain (``MeanLeapfrogSteps``)."""
|
|
938
|
+
return to_numpy_doubles(self.net.MeanLeapfrogSteps)
|
|
939
|
+
|
|
940
|
+
@property
|
|
941
|
+
def step_sizes(self) -> np.ndarray:
|
|
942
|
+
"""The adapted step size of each chain (``StepSizes``)."""
|
|
943
|
+
return to_numpy_doubles(self.net.StepSizes)
|
|
944
|
+
|
|
945
|
+
@property
|
|
946
|
+
def energy_bayesian_fraction_of_missing_information(self) -> np.ndarray:
|
|
947
|
+
"""E-BFMI per chain (``EnergyBayesianFractionOfMissingInformation``)."""
|
|
948
|
+
return to_numpy_doubles(self.net.EnergyBayesianFractionOfMissingInformation)
|
|
949
|
+
|
|
950
|
+
|
|
951
|
+
class Gibbs(MCMCSampler):
|
|
952
|
+
"""Gibbs sampling from a user proposal of the full conditionals (``Numerics.Sampling.MCMC.Gibbs``).
|
|
953
|
+
|
|
954
|
+
Every proposal is accepted, so the proposal must draw from the full conditional distributions.
|
|
955
|
+
|
|
956
|
+
Parameters
|
|
957
|
+
----------
|
|
958
|
+
priors : sequence of distributions
|
|
959
|
+
Prior of each parameter.
|
|
960
|
+
log_likelihood : callable, str or System.Delegate
|
|
961
|
+
Log-posterior of the parameter array (recorded with each state).
|
|
962
|
+
proposal : callable, str or System.Delegate
|
|
963
|
+
``proposal(values, prng)`` returning the next state (``Gibbs.Proposal``); ``prng`` is the
|
|
964
|
+
chain's ``System.Random`` (a Numerics ``MersenneTwister``), so draws from it are reproducible.
|
|
965
|
+
**settings
|
|
966
|
+
Keyword settings of :class:`MCMCSampler`.
|
|
967
|
+
"""
|
|
968
|
+
|
|
969
|
+
def __init__(self, priors: Sequence[Any], log_likelihood: Any, proposal: Any, **settings: Any) -> None:
|
|
970
|
+
net = _mcmc().Gibbs(_priors(priors), _likelihood(log_likelihood),
|
|
971
|
+
callbacks.to_delegate(proposal, "Gibbs.Proposal"))
|
|
972
|
+
super().__init__(net, (log_likelihood, proposal), **settings)
|
|
973
|
+
|
|
974
|
+
@property
|
|
975
|
+
def proposal_function(self) -> Any:
|
|
976
|
+
"""The ``Gibbs.Proposal`` delegate (``ProposalFunction``)."""
|
|
977
|
+
return self.net.ProposalFunction
|
|
978
|
+
|
|
979
|
+
|
|
980
|
+
class SNIS(MCMCSampler):
|
|
981
|
+
"""Self-normalized importance sampling (``Numerics.Sampling.MCMC.SNIS``).
|
|
982
|
+
|
|
983
|
+
Draws ``iterations`` independent points from the priors, or from ``importance`` when given
|
|
984
|
+
(or from the inflated MAP normal with ``initialize="MAP"``), weights them and resamples
|
|
985
|
+
``output_length`` draws. Numerics evaluates the draws with ``Parallel.For`` while
|
|
986
|
+
``parallelize_chains`` is True; a Python log-likelihood turns it off, so the draws are then
|
|
987
|
+
evaluated in order on the calling thread, with the same bits. C# source or
|
|
988
|
+
:func:`distribution_log_likelihood` keep the draws parallel.
|
|
989
|
+
|
|
990
|
+
Parameters
|
|
991
|
+
----------
|
|
992
|
+
priors : sequence of distributions
|
|
993
|
+
Prior of each parameter.
|
|
994
|
+
log_likelihood : callable, str or System.Delegate
|
|
995
|
+
Log-posterior of the parameter array.
|
|
996
|
+
importance : pynumerics.multivariate.MultivariateNormal or Numerics MultivariateNormal, optional
|
|
997
|
+
Importance distribution; default the priors (naive Monte Carlo).
|
|
998
|
+
**settings
|
|
999
|
+
Keyword settings of :class:`MCMCSampler`.
|
|
1000
|
+
"""
|
|
1001
|
+
|
|
1002
|
+
def __init__(self, priors: Sequence[Any], log_likelihood: Any, importance: Any = None, **settings: Any) -> None:
|
|
1003
|
+
mvn = None if importance is None else getattr(importance, "net", importance)
|
|
1004
|
+
super().__init__(_mcmc().SNIS(_priors(priors), _likelihood(log_likelihood), mvn), (log_likelihood,),
|
|
1005
|
+
**settings)
|
|
1006
|
+
|
|
1007
|
+
@property
|
|
1008
|
+
def weights(self) -> np.ndarray:
|
|
1009
|
+
"""Normalized importance weight of each draw, in the order of ``markov_chains`` (``Weight``)."""
|
|
1010
|
+
return _read_sets(self.net.MarkovChains[0], self.number_of_parameters)[2]
|
|
1011
|
+
|
|
1012
|
+
|
|
1013
|
+
class ParameterStatistics:
|
|
1014
|
+
"""Posterior summary of one parameter (``Numerics.Sampling.MCMC.ParameterStatistics``).
|
|
1015
|
+
|
|
1016
|
+
Parameters
|
|
1017
|
+
----------
|
|
1018
|
+
net : Numerics.Sampling.MCMC.ParameterStatistics, optional
|
|
1019
|
+
The .NET object to wrap; default a new empty one.
|
|
1020
|
+
|
|
1021
|
+
Attributes
|
|
1022
|
+
----------
|
|
1023
|
+
net : Numerics.Sampling.MCMC.ParameterStatistics
|
|
1024
|
+
The wrapped .NET object.
|
|
1025
|
+
"""
|
|
1026
|
+
|
|
1027
|
+
def __init__(self, net: Any = None) -> None:
|
|
1028
|
+
self.net = _mcmc().ParameterStatistics() if net is None else net
|
|
1029
|
+
|
|
1030
|
+
def __repr__(self) -> str:
|
|
1031
|
+
return (f"ParameterStatistics(mean={self.mean!r}, standard_deviation={self.standard_deviation!r}, "
|
|
1032
|
+
f"lower_ci={self.lower_ci!r}, upper_ci={self.upper_ci!r}, rhat={self.rhat!r}, ess={self.ess!r})")
|
|
1033
|
+
|
|
1034
|
+
@property
|
|
1035
|
+
def rhat(self) -> float:
|
|
1036
|
+
"""Rank-normalized split R-hat (``Rhat``)."""
|
|
1037
|
+
return float(self.net.Rhat)
|
|
1038
|
+
|
|
1039
|
+
@rhat.setter
|
|
1040
|
+
def rhat(self, value: float) -> None:
|
|
1041
|
+
self.net.Rhat = float(value)
|
|
1042
|
+
|
|
1043
|
+
@property
|
|
1044
|
+
def ess(self) -> float:
|
|
1045
|
+
"""Conservative effective sample size (``ESS``)."""
|
|
1046
|
+
return float(self.net.ESS)
|
|
1047
|
+
|
|
1048
|
+
@ess.setter
|
|
1049
|
+
def ess(self, value: float) -> None:
|
|
1050
|
+
self.net.ESS = float(value)
|
|
1051
|
+
|
|
1052
|
+
@property
|
|
1053
|
+
def n(self) -> int:
|
|
1054
|
+
"""Number of draws (``N``)."""
|
|
1055
|
+
return int(self.net.N)
|
|
1056
|
+
|
|
1057
|
+
@n.setter
|
|
1058
|
+
def n(self, value: int) -> None:
|
|
1059
|
+
self.net.N = int(value)
|
|
1060
|
+
|
|
1061
|
+
@property
|
|
1062
|
+
def mean(self) -> float:
|
|
1063
|
+
"""Posterior mean (``Mean``)."""
|
|
1064
|
+
return float(self.net.Mean)
|
|
1065
|
+
|
|
1066
|
+
@mean.setter
|
|
1067
|
+
def mean(self, value: float) -> None:
|
|
1068
|
+
self.net.Mean = float(value)
|
|
1069
|
+
|
|
1070
|
+
@property
|
|
1071
|
+
def median(self) -> float:
|
|
1072
|
+
"""Posterior median (``Median``)."""
|
|
1073
|
+
return float(self.net.Median)
|
|
1074
|
+
|
|
1075
|
+
@median.setter
|
|
1076
|
+
def median(self, value: float) -> None:
|
|
1077
|
+
self.net.Median = float(value)
|
|
1078
|
+
|
|
1079
|
+
@property
|
|
1080
|
+
def standard_deviation(self) -> float:
|
|
1081
|
+
"""Posterior standard deviation (``StandardDeviation``)."""
|
|
1082
|
+
return float(self.net.StandardDeviation)
|
|
1083
|
+
|
|
1084
|
+
@standard_deviation.setter
|
|
1085
|
+
def standard_deviation(self, value: float) -> None:
|
|
1086
|
+
self.net.StandardDeviation = float(value)
|
|
1087
|
+
|
|
1088
|
+
@property
|
|
1089
|
+
def lower_ci(self) -> float:
|
|
1090
|
+
"""Lower credible bound, the alpha/2 percentile (``LowerCI``)."""
|
|
1091
|
+
return float(self.net.LowerCI)
|
|
1092
|
+
|
|
1093
|
+
@lower_ci.setter
|
|
1094
|
+
def lower_ci(self, value: float) -> None:
|
|
1095
|
+
self.net.LowerCI = float(value)
|
|
1096
|
+
|
|
1097
|
+
@property
|
|
1098
|
+
def upper_ci(self) -> float:
|
|
1099
|
+
"""Upper credible bound, the 1 - alpha/2 percentile (``UpperCI``)."""
|
|
1100
|
+
return float(self.net.UpperCI)
|
|
1101
|
+
|
|
1102
|
+
@upper_ci.setter
|
|
1103
|
+
def upper_ci(self, value: float) -> None:
|
|
1104
|
+
self.net.UpperCI = float(value)
|
|
1105
|
+
|
|
1106
|
+
|
|
1107
|
+
class ParameterResults:
|
|
1108
|
+
"""Posterior summary, density and histogram of one parameter (``Numerics.Sampling.MCMC.ParameterResults``).
|
|
1109
|
+
|
|
1110
|
+
Parameters
|
|
1111
|
+
----------
|
|
1112
|
+
values : array_like or Numerics.Sampling.MCMC.ParameterResults
|
|
1113
|
+
Posterior draws of the parameter, or a .NET object to wrap.
|
|
1114
|
+
alpha : float, optional
|
|
1115
|
+
Two-sided interval level; default 0.1.
|
|
1116
|
+
is_sorted : bool, optional
|
|
1117
|
+
Whether ``values`` is already ascending (``sorted``); default False.
|
|
1118
|
+
|
|
1119
|
+
Attributes
|
|
1120
|
+
----------
|
|
1121
|
+
net : Numerics.Sampling.MCMC.ParameterResults
|
|
1122
|
+
The wrapped .NET object.
|
|
1123
|
+
"""
|
|
1124
|
+
|
|
1125
|
+
def __init__(self, values: Any, alpha: float = 0.1, is_sorted: bool = False) -> None:
|
|
1126
|
+
mc = _mcmc()
|
|
1127
|
+
if isinstance(values, mc.ParameterResults):
|
|
1128
|
+
self.net = values
|
|
1129
|
+
else:
|
|
1130
|
+
draws = np.ravel(np.asarray(values, dtype=np.float64))
|
|
1131
|
+
self.net = mc.ParameterResults(to_net_doubles(draws), float(alpha), bool(is_sorted))
|
|
1132
|
+
|
|
1133
|
+
@property
|
|
1134
|
+
def summary_statistics(self) -> ParameterStatistics:
|
|
1135
|
+
"""The summary statistics (``SummaryStatistics``)."""
|
|
1136
|
+
return ParameterStatistics(self.net.SummaryStatistics)
|
|
1137
|
+
|
|
1138
|
+
@property
|
|
1139
|
+
def kernel_density(self) -> np.ndarray:
|
|
1140
|
+
"""The kernel density graph, columns x and density (``KernelDensity``)."""
|
|
1141
|
+
return to_numpy_matrix(self.net.KernelDensity)
|
|
1142
|
+
|
|
1143
|
+
@property
|
|
1144
|
+
def histogram(self) -> Any:
|
|
1145
|
+
"""The ``Numerics.Data.Statistics.Histogram`` of the draws (``Histogram``)."""
|
|
1146
|
+
return self.net.Histogram
|
|
1147
|
+
|
|
1148
|
+
@property
|
|
1149
|
+
def autocorrelation(self) -> np.ndarray:
|
|
1150
|
+
"""The chain-averaged autocorrelation at lags 0 to 50 in column 1 (``Autocorrelation``); column 0 is zero."""
|
|
1151
|
+
return to_numpy_matrix(self.net.Autocorrelation)
|
|
1152
|
+
|
|
1153
|
+
@autocorrelation.setter
|
|
1154
|
+
def autocorrelation(self, value: Any) -> None:
|
|
1155
|
+
self.net.Autocorrelation = to_net_matrix(value)
|
|
1156
|
+
|
|
1157
|
+
|
|
1158
|
+
class MCMCResults:
|
|
1159
|
+
"""Post-processed MCMC output (``Numerics.Sampling.MCMC.MCMCResults``).
|
|
1160
|
+
|
|
1161
|
+
Built from a sampler after a run: R-hat over the post-warmup chains, ESS and autocorrelation
|
|
1162
|
+
over the output, and per-parameter summaries.
|
|
1163
|
+
|
|
1164
|
+
Parameters
|
|
1165
|
+
----------
|
|
1166
|
+
sampler : MCMCSampler or Numerics.Sampling.MCMC.MCMCSampler or Numerics.Sampling.MCMC.MCMCResults
|
|
1167
|
+
The sampler to post-process, or .NET results to wrap.
|
|
1168
|
+
alpha : float, optional
|
|
1169
|
+
Two-sided interval level; default 0.1 (90 percent intervals).
|
|
1170
|
+
|
|
1171
|
+
Attributes
|
|
1172
|
+
----------
|
|
1173
|
+
net : Numerics.Sampling.MCMC.MCMCResults
|
|
1174
|
+
The wrapped .NET object.
|
|
1175
|
+
"""
|
|
1176
|
+
|
|
1177
|
+
def __init__(self, sampler: Any, alpha: float = 0.1) -> None:
|
|
1178
|
+
mc = _mcmc()
|
|
1179
|
+
net = getattr(sampler, "net", sampler)
|
|
1180
|
+
self.net = net if isinstance(net, mc.MCMCResults) else mc.MCMCResults(net, float(alpha))
|
|
1181
|
+
|
|
1182
|
+
@classmethod
|
|
1183
|
+
def from_output(cls, values: Any, map_values: Any, map_log_likelihood: float = float("nan"),
|
|
1184
|
+
alpha: float = 0.1) -> MCMCResults:
|
|
1185
|
+
"""Summarize posterior draws from elsewhere (``MCMCResults(ParameterSet, IList<ParameterSet>, alpha)``).
|
|
1186
|
+
|
|
1187
|
+
Parameters
|
|
1188
|
+
----------
|
|
1189
|
+
values : array_like
|
|
1190
|
+
Draws, shape ``(draws, parameters)``.
|
|
1191
|
+
map_values : array_like
|
|
1192
|
+
The MAP parameters.
|
|
1193
|
+
map_log_likelihood : float, optional
|
|
1194
|
+
The MAP log-likelihood; default NaN.
|
|
1195
|
+
alpha : float, optional
|
|
1196
|
+
Two-sided interval level; default 0.1.
|
|
1197
|
+
|
|
1198
|
+
Returns
|
|
1199
|
+
-------
|
|
1200
|
+
MCMCResults
|
|
1201
|
+
Results with output, MAP and parameter summaries (no chains or diagnostics).
|
|
1202
|
+
"""
|
|
1203
|
+
ps = _opt().ParameterSet
|
|
1204
|
+
from System.Collections.Generic import List # noqa: PLC0415 - the runtime is running
|
|
1205
|
+
|
|
1206
|
+
draws = np.atleast_2d(np.asarray(values, dtype=np.float64))
|
|
1207
|
+
sets = List[ps](draws.shape[0])
|
|
1208
|
+
nan = float("nan")
|
|
1209
|
+
for row in draws:
|
|
1210
|
+
sets.Add(ps(to_net_doubles(row), nan))
|
|
1211
|
+
return cls(_mcmc().MCMCResults(_parameter_set(map_values, map_log_likelihood), sets, float(alpha)))
|
|
1212
|
+
|
|
1213
|
+
@classmethod
|
|
1214
|
+
def from_bytes(cls, data: bytes) -> MCMCResults:
|
|
1215
|
+
"""Read results written by :meth:`to_bytes` (``MCMCResults.FromByteArray``).
|
|
1216
|
+
|
|
1217
|
+
Parameters
|
|
1218
|
+
----------
|
|
1219
|
+
data : bytes
|
|
1220
|
+
The serialized results.
|
|
1221
|
+
|
|
1222
|
+
Returns
|
|
1223
|
+
-------
|
|
1224
|
+
MCMCResults
|
|
1225
|
+
The results.
|
|
1226
|
+
|
|
1227
|
+
Raises
|
|
1228
|
+
------
|
|
1229
|
+
ValueError
|
|
1230
|
+
If the bytes hold no results.
|
|
1231
|
+
"""
|
|
1232
|
+
mc = _mcmc()
|
|
1233
|
+
import System # noqa: PLC0415 - the runtime is running
|
|
1234
|
+
|
|
1235
|
+
net = mc.MCMCResults.FromByteArray(System.Array[System.Byte](bytes(data)))
|
|
1236
|
+
if net is None:
|
|
1237
|
+
raise ValueError("The bytes hold no MCMC results.")
|
|
1238
|
+
return cls(net)
|
|
1239
|
+
|
|
1240
|
+
def to_bytes(self) -> bytes:
|
|
1241
|
+
"""Serialize the results (``MCMCResults.ToByteArray``).
|
|
1242
|
+
|
|
1243
|
+
Returns
|
|
1244
|
+
-------
|
|
1245
|
+
bytes
|
|
1246
|
+
The serialized results.
|
|
1247
|
+
"""
|
|
1248
|
+
return bytes(_mcmc().MCMCResults.ToByteArray(self.net))
|
|
1249
|
+
|
|
1250
|
+
def _width(self) -> int:
|
|
1251
|
+
"""Return the number of parameters."""
|
|
1252
|
+
return int(self.net.ParameterResults.Length)
|
|
1253
|
+
|
|
1254
|
+
@property
|
|
1255
|
+
def markov_chains(self) -> np.ndarray:
|
|
1256
|
+
"""The recorded chains, shape ``(chains, iterations, parameters)`` (``MarkovChains``).
|
|
1257
|
+
|
|
1258
|
+
The chains are read from .NET once and cached; each access returns a new copy.
|
|
1259
|
+
"""
|
|
1260
|
+
chains = self.net.MarkovChains
|
|
1261
|
+
if chains is None:
|
|
1262
|
+
return np.empty((0, 0, self._width()))
|
|
1263
|
+
return _cached_chain_values(self, chains, self._width())[0].copy()
|
|
1264
|
+
|
|
1265
|
+
@property
|
|
1266
|
+
def output(self) -> np.ndarray:
|
|
1267
|
+
"""All posterior draws, chain after chain, shape ``(draws, parameters)`` (``Output``)."""
|
|
1268
|
+
return _read_sets(self.net.Output, self._width(), fitness=False, weights=False)[0]
|
|
1269
|
+
|
|
1270
|
+
@property
|
|
1271
|
+
def mean_log_likelihood(self) -> np.ndarray:
|
|
1272
|
+
"""The log-likelihood averaged over chains at each iteration (``MeanLogLikelihood``)."""
|
|
1273
|
+
values = self.net.MeanLogLikelihood
|
|
1274
|
+
return np.empty(0) if values is None else to_numpy_doubles(values)
|
|
1275
|
+
|
|
1276
|
+
@property
|
|
1277
|
+
def acceptance_rates(self) -> np.ndarray:
|
|
1278
|
+
"""Acceptance rate per chain; the mean Hamiltonian acceptance statistic for NUTS (``AcceptanceRates``)."""
|
|
1279
|
+
values = self.net.AcceptanceRates
|
|
1280
|
+
return np.empty(0) if values is None else to_numpy_doubles(values)
|
|
1281
|
+
|
|
1282
|
+
@property
|
|
1283
|
+
def parameter_results(self) -> list[ParameterResults]:
|
|
1284
|
+
"""The per-parameter results (``ParameterResults``)."""
|
|
1285
|
+
return [ParameterResults(r) for r in self.net.ParameterResults]
|
|
1286
|
+
|
|
1287
|
+
@property
|
|
1288
|
+
def map(self) -> tuple[np.ndarray, float]:
|
|
1289
|
+
"""The MAP draw as ``(values, log_likelihood)`` (``MAP``)."""
|
|
1290
|
+
return _point(self.net.MAP)
|
|
1291
|
+
|
|
1292
|
+
@property
|
|
1293
|
+
def posterior_mean(self) -> tuple[np.ndarray, float]:
|
|
1294
|
+
"""The posterior mean and its log-likelihood as ``(values, log_likelihood)`` (``PosteriorMean``)."""
|
|
1295
|
+
return _point(self.net.PosteriorMean)
|
|
1296
|
+
|
|
1297
|
+
def recompute_parameter_results(self, alpha: float) -> None:
|
|
1298
|
+
"""Recompute the summaries at another interval level, keeping R-hat, ESS and autocorrelation.
|
|
1299
|
+
|
|
1300
|
+
Calls ``RecomputeParameterResults``.
|
|
1301
|
+
|
|
1302
|
+
Parameters
|
|
1303
|
+
----------
|
|
1304
|
+
alpha : float
|
|
1305
|
+
The new two-sided level.
|
|
1306
|
+
"""
|
|
1307
|
+
self.net.RecomputeParameterResults(float(alpha))
|
|
1308
|
+
|
|
1309
|
+
|
|
1310
|
+
def _diagnostics() -> Any:
|
|
1311
|
+
"""Return ``MCMCDiagnostics``."""
|
|
1312
|
+
return _mcmc().MCMCDiagnostics
|
|
1313
|
+
|
|
1314
|
+
|
|
1315
|
+
def effective_sample_size(series: Any) -> float:
|
|
1316
|
+
"""Return the conservative rank-normalized ESS of one series (``MCMCDiagnostics.EffectiveSampleSize``).
|
|
1317
|
+
|
|
1318
|
+
The minimum of bulk and tail ESS of the series split in two halves (Vehtari et al. 2021). This is
|
|
1319
|
+
the effective size of a correlated series; for the Kish effective size of a set of weights use
|
|
1320
|
+
:func:`pynumerics.statistics.effective_sample_size`.
|
|
1321
|
+
|
|
1322
|
+
Parameters
|
|
1323
|
+
----------
|
|
1324
|
+
series : array_like
|
|
1325
|
+
The draws.
|
|
1326
|
+
|
|
1327
|
+
Returns
|
|
1328
|
+
-------
|
|
1329
|
+
float
|
|
1330
|
+
The ESS, or NaN for an insufficient or constant series.
|
|
1331
|
+
"""
|
|
1332
|
+
member = cached_overload("MCMCDiagnostics.EffectiveSampleSize(IList<double>)",
|
|
1333
|
+
lambda: bind_list(_diagnostics().EffectiveSampleSize))
|
|
1334
|
+
return float(member(to_net_doubles(np.ravel(np.asarray(series, dtype=np.float64)))))
|
|
1335
|
+
|
|
1336
|
+
|
|
1337
|
+
def effective_sample_sizes(chains: Any) -> tuple[np.ndarray, list[np.ndarray]]:
|
|
1338
|
+
"""Return the conservative ESS of each parameter over several chains, and the averaged autocorrelation.
|
|
1339
|
+
|
|
1340
|
+
Calls ``MCMCDiagnostics.EffectiveSampleSize(IList<List<ParameterSet>>, out double[][,])``.
|
|
1341
|
+
|
|
1342
|
+
Parameters
|
|
1343
|
+
----------
|
|
1344
|
+
chains : array_like or MCMCSampler or MCMCResults
|
|
1345
|
+
Chains of shape ``(chains, iterations, parameters)``, or a sampler or results whose
|
|
1346
|
+
``MarkovChains`` are used.
|
|
1347
|
+
|
|
1348
|
+
Returns
|
|
1349
|
+
-------
|
|
1350
|
+
ess : numpy.ndarray
|
|
1351
|
+
ESS per parameter.
|
|
1352
|
+
autocorrelation : list of numpy.ndarray
|
|
1353
|
+
The chain-averaged autocorrelation of each parameter at lags 0 to 50, shape ``(51, 2)``:
|
|
1354
|
+
column 1 holds the values; Numerics leaves column 0 at zero.
|
|
1355
|
+
"""
|
|
1356
|
+
ess, acf = _diagnostics().EffectiveSampleSize(_chains_to_net(chains), None)
|
|
1357
|
+
return to_numpy_doubles(ess), [to_numpy_matrix(a) for a in acf]
|
|
1358
|
+
|
|
1359
|
+
|
|
1360
|
+
def gelman_rubin(chains: Any, warmup_iterations: int = 0) -> np.ndarray:
|
|
1361
|
+
"""Return rank-normalized split R-hat of each parameter (``MCMCDiagnostics.GelmanRubin``).
|
|
1362
|
+
|
|
1363
|
+
The maximum of the rank-normalized and the folded rank-normalized split R-hat (Vehtari et al. 2021).
|
|
1364
|
+
|
|
1365
|
+
Parameters
|
|
1366
|
+
----------
|
|
1367
|
+
chains : array_like or MCMCSampler or MCMCResults
|
|
1368
|
+
Equal-length chains of shape ``(chains, iterations, parameters)``, or a sampler or results.
|
|
1369
|
+
warmup_iterations : int, optional
|
|
1370
|
+
Leading iterations of each chain to discard; default 0.
|
|
1371
|
+
|
|
1372
|
+
Returns
|
|
1373
|
+
-------
|
|
1374
|
+
numpy.ndarray
|
|
1375
|
+
R-hat per parameter; NaN for a single chain.
|
|
1376
|
+
"""
|
|
1377
|
+
return to_numpy_doubles(_diagnostics().GelmanRubin(_chains_to_net(chains), int(warmup_iterations)))
|
|
1378
|
+
|
|
1379
|
+
|
|
1380
|
+
def minimum_sample_size(quantile: float, tolerance: float, probability: float) -> int:
|
|
1381
|
+
"""Return the Raftery-Lewis minimum sample size, rounded to hundreds (``MCMCDiagnostics.MinimumSampleSize``).
|
|
1382
|
+
|
|
1383
|
+
Parameters
|
|
1384
|
+
----------
|
|
1385
|
+
quantile : float
|
|
1386
|
+
Posterior quantile of interest, for example 0.975.
|
|
1387
|
+
tolerance : float
|
|
1388
|
+
Acceptable error of that quantile's probability, for example 0.005.
|
|
1389
|
+
probability : float
|
|
1390
|
+
Probability of being within the tolerance, for example 0.95.
|
|
1391
|
+
|
|
1392
|
+
Returns
|
|
1393
|
+
-------
|
|
1394
|
+
int
|
|
1395
|
+
``q (1 - q) z^2 / r^2`` rounded to hundreds, with z the standard normal ``(1 + s) / 2`` quantile.
|
|
1396
|
+
"""
|
|
1397
|
+
return int(_diagnostics().MinimumSampleSize(float(quantile), float(tolerance), float(probability)))
|
|
1398
|
+
|
|
1399
|
+
|
|
1400
|
+
__all__ = [
|
|
1401
|
+
"ARWMH", "DEMCz", "DEMCzs", "Gibbs", "HMC", "InitializationType", "MCMCResults", "MCMCSampler", "NUTS",
|
|
1402
|
+
"ParameterResults", "ParameterStatistics", "RWMH", "SNIS", "distribution_log_likelihood",
|
|
1403
|
+
"effective_sample_size", "effective_sample_sizes", "gelman_rubin", "minimum_sample_size",
|
|
1404
|
+
]
|
|
1405
|
+
|
|
1406
|
+
translate_public(globals())
|