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.
Files changed (46) hide show
  1. pynumerics/__init__.py +73 -0
  2. pynumerics/_bin/Microsoft.CodeAnalysis.CSharp.dll +0 -0
  3. pynumerics/_bin/Microsoft.CodeAnalysis.LICENSE.txt +27 -0
  4. pynumerics/_bin/Microsoft.CodeAnalysis.dll +0 -0
  5. pynumerics/_bin/Numerics.dll +0 -0
  6. pynumerics/_bin/pynumerics.runtimeconfig.json +15 -0
  7. pynumerics/_coverage.py +3825 -0
  8. pynumerics/_util.py +237 -0
  9. pynumerics/_version.py +40 -0
  10. pynumerics/bootstrap.py +593 -0
  11. pynumerics/callbacks.py +791 -0
  12. pynumerics/copulas.py +910 -0
  13. pynumerics/data.py +2233 -0
  14. pynumerics/differentiation.py +348 -0
  15. pynumerics/distributions.py +4043 -0
  16. pynumerics/download.py +623 -0
  17. pynumerics/enums.py +119 -0
  18. pynumerics/errors.py +245 -0
  19. pynumerics/functions.py +1142 -0
  20. pynumerics/goodness_of_fit.py +699 -0
  21. pynumerics/graphs.py +556 -0
  22. pynumerics/hypothesis_tests.py +445 -0
  23. pynumerics/identity.py +144 -0
  24. pynumerics/integration.py +677 -0
  25. pynumerics/linalg.py +1408 -0
  26. pynumerics/machine_learning.py +1335 -0
  27. pynumerics/marshal.py +733 -0
  28. pynumerics/mathematics.py +1053 -0
  29. pynumerics/mcmc.py +1406 -0
  30. pynumerics/multivariate.py +1539 -0
  31. pynumerics/ode.py +156 -0
  32. pynumerics/optimization.py +1736 -0
  33. pynumerics/probability.py +966 -0
  34. pynumerics/py.typed +0 -0
  35. pynumerics/regional.py +522 -0
  36. pynumerics/roots.py +274 -0
  37. pynumerics/runtime.py +384 -0
  38. pynumerics/sampling.py +1758 -0
  39. pynumerics/special.py +1079 -0
  40. pynumerics/statistics.py +1958 -0
  41. pynumerics/timeseries.py +1326 -0
  42. pynumerics-2.3.0.dist-info/METADATA +142 -0
  43. pynumerics-2.3.0.dist-info/RECORD +46 -0
  44. pynumerics-2.3.0.dist-info/WHEEL +5 -0
  45. pynumerics-2.3.0.dist-info/licenses/LICENSE +10 -0
  46. 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())