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
|
@@ -0,0 +1,1539 @@
|
|
|
1
|
+
"""Multivariate distributions from ``Numerics.Distributions``.
|
|
2
|
+
|
|
3
|
+
Every class wraps one .NET distribution object (``.net``) and derives from
|
|
4
|
+
:class:`MultivariateDistribution`, which carries the members every Numerics multivariate
|
|
5
|
+
distribution declares (``PDF``, ``CDF``, ``CCDF`` and their logarithms, ``Dimension``,
|
|
6
|
+
``Clone``). Numerics evaluates those members at one point (``double[]``); a two-dimensional
|
|
7
|
+
array is evaluated row by row with the bound member, one crossing per row. Samples cross back
|
|
8
|
+
as one ``double[,]`` block copy.
|
|
9
|
+
|
|
10
|
+
Random generators: members that take a ``System.Random`` accept a
|
|
11
|
+
:class:`pynumerics.sampling.MersenneTwister` or any .NET ``System.Random``; the ``seed`` argument
|
|
12
|
+
of ``generate_random_values`` (and of ``MultivariateNormal.latin_hypercube_random_values``)
|
|
13
|
+
accepts an integer seed or such a generator.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from typing import Any
|
|
19
|
+
|
|
20
|
+
import numpy as np
|
|
21
|
+
|
|
22
|
+
from pynumerics._util import _nd, cached_overload, namespace, net_enum, overload, unwrap
|
|
23
|
+
from pynumerics.enums import NetEnum, Transform
|
|
24
|
+
from pynumerics.errors import translate_public
|
|
25
|
+
from pynumerics.marshal import (
|
|
26
|
+
_flat_doubles,
|
|
27
|
+
to_net_doubles,
|
|
28
|
+
to_net_ints,
|
|
29
|
+
to_net_matrix,
|
|
30
|
+
to_numpy_doubles,
|
|
31
|
+
to_numpy_ints,
|
|
32
|
+
to_numpy_matrix,
|
|
33
|
+
)
|
|
34
|
+
from pynumerics.sampling import _bins
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class MultivariateDistributionType(NetEnum):
|
|
38
|
+
"""Multivariate distribution types (``Numerics.Distributions.MultivariateDistributionType``)."""
|
|
39
|
+
|
|
40
|
+
BIVARIATE_EMPIRICAL_DISTRIBUTION = "BivariateEmpiricalDistribution"
|
|
41
|
+
MULTIVARIATE_NORMAL = "MultivariateNormal"
|
|
42
|
+
DIRICHLET = "Dirichlet"
|
|
43
|
+
MULTINOMIAL = "Multinomial"
|
|
44
|
+
MULTIVARIATE_STUDENT_T = "MultivariateStudentT"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _categorical_indices() -> Any:
|
|
48
|
+
"""Return ``Multinomial.CategoricalIndices(double[], double[], bool)``.
|
|
49
|
+
|
|
50
|
+
The two-argument overload calls this one with ``inclusive`` false, so binding the
|
|
51
|
+
three-argument overload once serves both forms without a per-call overload resolution.
|
|
52
|
+
"""
|
|
53
|
+
return overload(_nd().Multinomial.CategoricalIndices, "Double[]", "Double[]", "Boolean")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _generator(random: Any) -> Any:
|
|
57
|
+
"""Return the .NET ``System.Random`` behind a :class:`pynumerics.sampling.MersenneTwister` or a .NET generator."""
|
|
58
|
+
return getattr(random, "net", random)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _wrap_generator(net: Any) -> Any:
|
|
62
|
+
"""Wrap a .NET generator as :class:`pynumerics.sampling.MersenneTwister` when it is one, else return it."""
|
|
63
|
+
from pynumerics.sampling import MersenneTwister # noqa: PLC0415 - imported on use
|
|
64
|
+
|
|
65
|
+
concrete = unwrap(net)
|
|
66
|
+
if str(concrete.GetType().FullName) != "Numerics.Sampling.MersenneTwister":
|
|
67
|
+
return concrete
|
|
68
|
+
instance = MersenneTwister.__new__(MersenneTwister)
|
|
69
|
+
instance.net = concrete
|
|
70
|
+
return instance
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _seeded_generator(value: Any) -> Any:
|
|
74
|
+
"""Return the .NET generator for a generator argument, or a Numerics ``MersenneTwister`` for an integer seed."""
|
|
75
|
+
if isinstance(value, (int, np.integer)):
|
|
76
|
+
return namespace("Numerics.Sampling").MersenneTwister(int(value))
|
|
77
|
+
return _generator(value)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _draw(member: Any, sample_size: int, seed: Any) -> np.ndarray:
|
|
81
|
+
"""Call a ``(int, int seed)`` or ``(int, Random)`` sampling overload and return the ``double[,]`` as NumPy.
|
|
82
|
+
|
|
83
|
+
An integer ``seed`` calls the seed overload; any other value is taken as a generator for the
|
|
84
|
+
``System.Random`` overload.
|
|
85
|
+
"""
|
|
86
|
+
import System # noqa: PLC0415 - the runtime is running (the owner exists)
|
|
87
|
+
|
|
88
|
+
if isinstance(seed, (int, np.integer)):
|
|
89
|
+
return to_numpy_matrix(member.Overloads[System.Int32, System.Int32](int(sample_size), int(seed)))
|
|
90
|
+
return to_numpy_matrix(member.Overloads[System.Int32, System.Random](int(sample_size), _generator(seed)))
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _message(error: Any) -> str | None:
|
|
94
|
+
"""Return the message of an exception Numerics built without throwing, or None."""
|
|
95
|
+
return None if error is None else str(error.Message)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class MultivariateDistribution:
|
|
99
|
+
"""A wrapped ``Numerics.Distributions.MultivariateDistribution``.
|
|
100
|
+
|
|
101
|
+
Parameters
|
|
102
|
+
----------
|
|
103
|
+
net : Numerics.Distributions.MultivariateDistribution
|
|
104
|
+
The .NET distribution; interface and base-class wrappers are unwrapped.
|
|
105
|
+
|
|
106
|
+
Attributes
|
|
107
|
+
----------
|
|
108
|
+
net : Numerics.Distributions.MultivariateDistribution
|
|
109
|
+
The wrapped .NET object.
|
|
110
|
+
|
|
111
|
+
Notes
|
|
112
|
+
-----
|
|
113
|
+
The point members accept one point of length ``dimension`` and return a float, or a
|
|
114
|
+
two-dimensional array of points (one per row) and return one value per row.
|
|
115
|
+
"""
|
|
116
|
+
|
|
117
|
+
def __init__(self, net: Any) -> None:
|
|
118
|
+
self.net = unwrap(net)
|
|
119
|
+
self._bind()
|
|
120
|
+
|
|
121
|
+
def _bind(self) -> None:
|
|
122
|
+
"""Bind members once after construction; subclasses override it."""
|
|
123
|
+
|
|
124
|
+
@classmethod
|
|
125
|
+
def from_net(cls, net: Any) -> MultivariateDistribution:
|
|
126
|
+
"""Wrap an existing .NET distribution without copying it.
|
|
127
|
+
|
|
128
|
+
Parameters
|
|
129
|
+
----------
|
|
130
|
+
net : Numerics.Distributions.MultivariateDistribution
|
|
131
|
+
The .NET object; interface and base-class wrappers are unwrapped.
|
|
132
|
+
|
|
133
|
+
Returns
|
|
134
|
+
-------
|
|
135
|
+
MultivariateDistribution
|
|
136
|
+
A wrapper of class ``cls`` sharing ``net``.
|
|
137
|
+
"""
|
|
138
|
+
instance = cls.__new__(cls)
|
|
139
|
+
MultivariateDistribution.__init__(instance, net)
|
|
140
|
+
return instance
|
|
141
|
+
|
|
142
|
+
def _point(self, name: str, x: Any) -> Any:
|
|
143
|
+
"""Evaluate a ``(double[])`` member at one point, or row by row for a 2-D array."""
|
|
144
|
+
cache = self.__dict__.setdefault("_points", {})
|
|
145
|
+
member = cache.get(name)
|
|
146
|
+
if member is None:
|
|
147
|
+
import System # noqa: PLC0415 - the runtime is running (self.net exists)
|
|
148
|
+
|
|
149
|
+
group = getattr(self.net, name)
|
|
150
|
+
try:
|
|
151
|
+
member = group.Overloads[System.Array[System.Double]]
|
|
152
|
+
except (KeyError, TypeError):
|
|
153
|
+
member = group
|
|
154
|
+
cache[name] = member
|
|
155
|
+
array = np.asarray(x, dtype=np.float64)
|
|
156
|
+
if array.ndim <= 1:
|
|
157
|
+
return float(member(to_net_doubles(array)))
|
|
158
|
+
if array.ndim != 2:
|
|
159
|
+
raise ValueError(f"Expected one point or a 2-D array of points; got shape {array.shape}.")
|
|
160
|
+
return np.array([float(member(to_net_doubles(row))) for row in array], dtype=np.float64)
|
|
161
|
+
|
|
162
|
+
@property
|
|
163
|
+
def dimension(self) -> int:
|
|
164
|
+
"""The number of variables."""
|
|
165
|
+
return int(self.net.Dimension)
|
|
166
|
+
|
|
167
|
+
@property
|
|
168
|
+
def type(self) -> MultivariateDistributionType:
|
|
169
|
+
"""The distribution type (``Type``)."""
|
|
170
|
+
return MultivariateDistributionType(str(self.net.Type))
|
|
171
|
+
|
|
172
|
+
@property
|
|
173
|
+
def display_name(self) -> str:
|
|
174
|
+
"""The Numerics display name."""
|
|
175
|
+
return str(self.net.DisplayName)
|
|
176
|
+
|
|
177
|
+
@property
|
|
178
|
+
def short_display_name(self) -> str:
|
|
179
|
+
"""The Numerics short display name."""
|
|
180
|
+
return str(self.net.ShortDisplayName)
|
|
181
|
+
|
|
182
|
+
@property
|
|
183
|
+
def parameters_valid(self) -> bool:
|
|
184
|
+
"""Whether the current parameters are valid (``ParametersValid``)."""
|
|
185
|
+
return bool(self.net.ParametersValid)
|
|
186
|
+
|
|
187
|
+
def pdf(self, x: Any) -> Any:
|
|
188
|
+
"""Return the density (``PDF(double[])``).
|
|
189
|
+
|
|
190
|
+
Parameters
|
|
191
|
+
----------
|
|
192
|
+
x : array_like
|
|
193
|
+
One point of length d, or an array of shape (n, d).
|
|
194
|
+
|
|
195
|
+
Returns
|
|
196
|
+
-------
|
|
197
|
+
float or numpy.ndarray
|
|
198
|
+
The density at the point, or one per row.
|
|
199
|
+
"""
|
|
200
|
+
return self._point("PDF", x)
|
|
201
|
+
|
|
202
|
+
def log_pdf(self, x: Any) -> Any:
|
|
203
|
+
"""Return the log-density (``LogPDF(double[])``).
|
|
204
|
+
|
|
205
|
+
Parameters
|
|
206
|
+
----------
|
|
207
|
+
x : array_like
|
|
208
|
+
One point of length d, or an array of shape (n, d).
|
|
209
|
+
|
|
210
|
+
Returns
|
|
211
|
+
-------
|
|
212
|
+
float or numpy.ndarray
|
|
213
|
+
The log-density at the point, or one per row.
|
|
214
|
+
"""
|
|
215
|
+
return self._point("LogPDF", x)
|
|
216
|
+
|
|
217
|
+
def cdf(self, x: Any) -> Any:
|
|
218
|
+
"""Return the joint CDF P(X ≤ x) (``CDF(double[])``).
|
|
219
|
+
|
|
220
|
+
Parameters
|
|
221
|
+
----------
|
|
222
|
+
x : array_like
|
|
223
|
+
One point of length d, or an array of shape (n, d).
|
|
224
|
+
|
|
225
|
+
Returns
|
|
226
|
+
-------
|
|
227
|
+
float or numpy.ndarray
|
|
228
|
+
The joint non-exceedance probability at the point, or one per row.
|
|
229
|
+
"""
|
|
230
|
+
return self._point("CDF", x)
|
|
231
|
+
|
|
232
|
+
def log_cdf(self, x: Any) -> Any:
|
|
233
|
+
"""Return the log of the joint CDF (``LogCDF(double[])``).
|
|
234
|
+
|
|
235
|
+
Parameters
|
|
236
|
+
----------
|
|
237
|
+
x : array_like
|
|
238
|
+
One point of length d, or an array of shape (n, d).
|
|
239
|
+
|
|
240
|
+
Returns
|
|
241
|
+
-------
|
|
242
|
+
float or numpy.ndarray
|
|
243
|
+
The log joint non-exceedance probability.
|
|
244
|
+
"""
|
|
245
|
+
return self._point("LogCDF", x)
|
|
246
|
+
|
|
247
|
+
def ccdf(self, x: Any) -> Any:
|
|
248
|
+
"""Return the complement of the joint CDF, 1 − P(X ≤ x) (``CCDF(double[])``).
|
|
249
|
+
|
|
250
|
+
Parameters
|
|
251
|
+
----------
|
|
252
|
+
x : array_like
|
|
253
|
+
One point of length d, or an array of shape (n, d).
|
|
254
|
+
|
|
255
|
+
Returns
|
|
256
|
+
-------
|
|
257
|
+
float or numpy.ndarray
|
|
258
|
+
One minus the joint non-exceedance probability.
|
|
259
|
+
"""
|
|
260
|
+
return self._point("CCDF", x)
|
|
261
|
+
|
|
262
|
+
def log_ccdf(self, x: Any) -> Any:
|
|
263
|
+
"""Return the log of the complement of the joint CDF (``LogCCDF(double[])``).
|
|
264
|
+
|
|
265
|
+
Parameters
|
|
266
|
+
----------
|
|
267
|
+
x : array_like
|
|
268
|
+
One point of length d, or an array of shape (n, d).
|
|
269
|
+
|
|
270
|
+
Returns
|
|
271
|
+
-------
|
|
272
|
+
float or numpy.ndarray
|
|
273
|
+
The log of one minus the joint non-exceedance probability.
|
|
274
|
+
"""
|
|
275
|
+
return self._point("LogCCDF", x)
|
|
276
|
+
|
|
277
|
+
def clone(self) -> MultivariateDistribution:
|
|
278
|
+
"""Return a deep copy through ``Clone``.
|
|
279
|
+
|
|
280
|
+
Returns
|
|
281
|
+
-------
|
|
282
|
+
MultivariateDistribution
|
|
283
|
+
A wrapper of the cloned .NET object, of the matching Python class.
|
|
284
|
+
"""
|
|
285
|
+
return wrap(self.net.Clone())
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
class MultivariateNormal(MultivariateDistribution):
|
|
289
|
+
"""Multivariate normal distribution (``Numerics.Distributions.MultivariateNormal``).
|
|
290
|
+
|
|
291
|
+
Parameters
|
|
292
|
+
----------
|
|
293
|
+
mean : array_like or int
|
|
294
|
+
Mean vector μ of length d, or the dimension d for a zero mean.
|
|
295
|
+
covariance : array_like, optional
|
|
296
|
+
Covariance matrix Σ of shape (d, d); default the identity.
|
|
297
|
+
decomposition : str, optional
|
|
298
|
+
Factorization of Σ (``DecompositionMethod`` member name, for example ``"Cholesky"``);
|
|
299
|
+
default the Numerics default.
|
|
300
|
+
|
|
301
|
+
Attributes
|
|
302
|
+
----------
|
|
303
|
+
net : Numerics.Distributions.MultivariateNormal
|
|
304
|
+
The wrapped .NET object.
|
|
305
|
+
"""
|
|
306
|
+
|
|
307
|
+
def __init__(self, mean: Any, covariance: Any = None, decomposition: str | None = None) -> None:
|
|
308
|
+
nd = _nd()
|
|
309
|
+
is_dimension = isinstance(mean, (int, np.integer))
|
|
310
|
+
args: list[Any] = [int(mean) if is_dimension else to_net_doubles(mean)]
|
|
311
|
+
if covariance is not None:
|
|
312
|
+
if is_dimension:
|
|
313
|
+
raise ValueError("A covariance matrix needs a mean vector, not a dimension.")
|
|
314
|
+
args.append(to_net_matrix(covariance))
|
|
315
|
+
if decomposition is not None:
|
|
316
|
+
linear_algebra = namespace("Numerics.Mathematics.LinearAlgebra")
|
|
317
|
+
args.append(net_enum(linear_algebra.DecompositionMethod, decomposition))
|
|
318
|
+
MultivariateDistribution.__init__(self, nd.MultivariateNormal(*args))
|
|
319
|
+
|
|
320
|
+
@classmethod
|
|
321
|
+
def univariate(cls, mu: float, sigma: float) -> MultivariateNormal:
|
|
322
|
+
"""Build the one-dimensional normal N(μ, σ²) (``MultivariateNormal.Univariate``).
|
|
323
|
+
|
|
324
|
+
Parameters
|
|
325
|
+
----------
|
|
326
|
+
mu : float
|
|
327
|
+
Mean.
|
|
328
|
+
sigma : float
|
|
329
|
+
Standard deviation.
|
|
330
|
+
|
|
331
|
+
Returns
|
|
332
|
+
-------
|
|
333
|
+
MultivariateNormal
|
|
334
|
+
The distribution.
|
|
335
|
+
"""
|
|
336
|
+
return cls.from_net(_nd().MultivariateNormal.Univariate(float(mu), float(sigma)))
|
|
337
|
+
|
|
338
|
+
@classmethod
|
|
339
|
+
def bivariate(cls, mu1: float, mu2: float, sigma1: float, sigma2: float, rho: float) -> MultivariateNormal:
|
|
340
|
+
"""Build a bivariate normal from means, standard deviations and correlation (``Bivariate``).
|
|
341
|
+
|
|
342
|
+
Parameters
|
|
343
|
+
----------
|
|
344
|
+
mu1 : float
|
|
345
|
+
Mean of the first variable.
|
|
346
|
+
mu2 : float
|
|
347
|
+
Mean of the second variable.
|
|
348
|
+
sigma1 : float
|
|
349
|
+
Standard deviation of the first variable.
|
|
350
|
+
sigma2 : float
|
|
351
|
+
Standard deviation of the second variable.
|
|
352
|
+
rho : float
|
|
353
|
+
Correlation.
|
|
354
|
+
|
|
355
|
+
Returns
|
|
356
|
+
-------
|
|
357
|
+
MultivariateNormal
|
|
358
|
+
The distribution.
|
|
359
|
+
"""
|
|
360
|
+
return cls.from_net(_nd().MultivariateNormal.Bivariate(float(mu1), float(mu2), float(sigma1), float(sigma2),
|
|
361
|
+
float(rho)))
|
|
362
|
+
|
|
363
|
+
@staticmethod
|
|
364
|
+
def default_mvnuni_seed() -> int:
|
|
365
|
+
"""Return the seed of the default CDF generator (``DefaultMVNUNISeed``).
|
|
366
|
+
|
|
367
|
+
Returns
|
|
368
|
+
-------
|
|
369
|
+
int
|
|
370
|
+
The seed every new instance's ``mvnuni`` starts from.
|
|
371
|
+
"""
|
|
372
|
+
return int(_nd().MultivariateNormal.DefaultMVNUNISeed)
|
|
373
|
+
|
|
374
|
+
@property
|
|
375
|
+
def mvnuni(self) -> Any:
|
|
376
|
+
"""The generator of the randomized lattice rule behind the CDF above two dimensions (``MVNUNI``).
|
|
377
|
+
|
|
378
|
+
Reads as a :class:`pynumerics.sampling.MersenneTwister` when it is one. Accepts a generator or
|
|
379
|
+
an integer seed (a new Numerics ``MersenneTwister``).
|
|
380
|
+
"""
|
|
381
|
+
return _wrap_generator(self.net.MVNUNI)
|
|
382
|
+
|
|
383
|
+
@mvnuni.setter
|
|
384
|
+
def mvnuni(self, value: Any) -> None:
|
|
385
|
+
self.net.MVNUNI = _seeded_generator(value)
|
|
386
|
+
|
|
387
|
+
@property
|
|
388
|
+
def max_evaluations(self) -> int:
|
|
389
|
+
"""The maximum number of integrand evaluations of the CDF above two dimensions; settable."""
|
|
390
|
+
return int(self.net.MaxEvaluations)
|
|
391
|
+
|
|
392
|
+
@max_evaluations.setter
|
|
393
|
+
def max_evaluations(self, value: int) -> None:
|
|
394
|
+
self.net.MaxEvaluations = int(value)
|
|
395
|
+
|
|
396
|
+
@property
|
|
397
|
+
def absolute_error(self) -> float:
|
|
398
|
+
"""The absolute error tolerance of the CDF above two dimensions; settable."""
|
|
399
|
+
return float(self.net.AbsoluteError)
|
|
400
|
+
|
|
401
|
+
@absolute_error.setter
|
|
402
|
+
def absolute_error(self, value: float) -> None:
|
|
403
|
+
self.net.AbsoluteError = float(value)
|
|
404
|
+
|
|
405
|
+
@property
|
|
406
|
+
def relative_error(self) -> float:
|
|
407
|
+
"""The relative error tolerance of the CDF above two dimensions; settable."""
|
|
408
|
+
return float(self.net.RelativeError)
|
|
409
|
+
|
|
410
|
+
@relative_error.setter
|
|
411
|
+
def relative_error(self, value: float) -> None:
|
|
412
|
+
self.net.RelativeError = float(value)
|
|
413
|
+
|
|
414
|
+
@property
|
|
415
|
+
def mean(self) -> np.ndarray:
|
|
416
|
+
"""The mean vector."""
|
|
417
|
+
return to_numpy_doubles(self.net.Mean)
|
|
418
|
+
|
|
419
|
+
@property
|
|
420
|
+
def median(self) -> np.ndarray:
|
|
421
|
+
"""The median vector (equal to the mean)."""
|
|
422
|
+
return to_numpy_doubles(self.net.Median)
|
|
423
|
+
|
|
424
|
+
@property
|
|
425
|
+
def mode(self) -> np.ndarray:
|
|
426
|
+
"""The mode vector (equal to the mean)."""
|
|
427
|
+
return to_numpy_doubles(self.net.Mode)
|
|
428
|
+
|
|
429
|
+
@property
|
|
430
|
+
def variance(self) -> np.ndarray:
|
|
431
|
+
"""The marginal variances (the diagonal of Σ)."""
|
|
432
|
+
return to_numpy_doubles(self.net.Variance)
|
|
433
|
+
|
|
434
|
+
@property
|
|
435
|
+
def standard_deviation(self) -> np.ndarray:
|
|
436
|
+
"""The marginal standard deviations."""
|
|
437
|
+
return to_numpy_doubles(self.net.StandardDeviation)
|
|
438
|
+
|
|
439
|
+
@property
|
|
440
|
+
def covariance(self) -> np.ndarray:
|
|
441
|
+
"""The covariance matrix."""
|
|
442
|
+
return to_numpy_matrix(self.net.Covariance)
|
|
443
|
+
|
|
444
|
+
@property
|
|
445
|
+
def decomposition(self) -> str:
|
|
446
|
+
"""The factorization of Σ in use (``DecompositionMethod`` member name)."""
|
|
447
|
+
return str(self.net.Decomposition)
|
|
448
|
+
|
|
449
|
+
@property
|
|
450
|
+
def is_positive_definite(self) -> bool:
|
|
451
|
+
"""Whether Σ is positive definite."""
|
|
452
|
+
return bool(self.net.IsPositiveDefinite)
|
|
453
|
+
|
|
454
|
+
@property
|
|
455
|
+
def is_density_valid(self) -> bool:
|
|
456
|
+
"""Whether the density is defined (Σ is non-singular)."""
|
|
457
|
+
return bool(self.net.IsDensityValid)
|
|
458
|
+
|
|
459
|
+
def set_parameters(self, mean: Any, covariance: Any) -> None:
|
|
460
|
+
"""Replace the mean and covariance (``SetParameters``).
|
|
461
|
+
|
|
462
|
+
Parameters
|
|
463
|
+
----------
|
|
464
|
+
mean : array_like
|
|
465
|
+
Mean vector of length d.
|
|
466
|
+
covariance : array_like
|
|
467
|
+
Covariance matrix of shape (d, d).
|
|
468
|
+
"""
|
|
469
|
+
self.net.SetParameters(to_net_doubles(mean), to_net_matrix(covariance))
|
|
470
|
+
|
|
471
|
+
def set_mean(self, mean: Any) -> None:
|
|
472
|
+
"""Replace the mean and keep the covariance and its factorization (``SetMean``).
|
|
473
|
+
|
|
474
|
+
Parameters
|
|
475
|
+
----------
|
|
476
|
+
mean : array_like
|
|
477
|
+
Mean vector of length d.
|
|
478
|
+
"""
|
|
479
|
+
self.net.SetMean(to_net_doubles(mean))
|
|
480
|
+
|
|
481
|
+
def validate_parameters(self, mean: Any, covariance: Any) -> str | None:
|
|
482
|
+
"""Return why a mean and covariance are invalid, or None when they are valid.
|
|
483
|
+
|
|
484
|
+
Parameters
|
|
485
|
+
----------
|
|
486
|
+
mean : array_like
|
|
487
|
+
Mean vector.
|
|
488
|
+
covariance : array_like
|
|
489
|
+
Covariance matrix.
|
|
490
|
+
|
|
491
|
+
Returns
|
|
492
|
+
-------
|
|
493
|
+
str or None
|
|
494
|
+
The Numerics message of ``ValidateParameters(mean, covariance, false)``, or None.
|
|
495
|
+
"""
|
|
496
|
+
return _message(self.net.ValidateParameters(to_net_doubles(mean), to_net_matrix(covariance), False))
|
|
497
|
+
|
|
498
|
+
def try_set_parameters(self, mean: Any, covariance: Any) -> bool:
|
|
499
|
+
"""Set the parameters when they are valid (``TrySetParameters``).
|
|
500
|
+
|
|
501
|
+
Parameters
|
|
502
|
+
----------
|
|
503
|
+
mean : array_like
|
|
504
|
+
Mean vector.
|
|
505
|
+
covariance : array_like
|
|
506
|
+
Covariance matrix.
|
|
507
|
+
|
|
508
|
+
Returns
|
|
509
|
+
-------
|
|
510
|
+
bool
|
|
511
|
+
True when the parameters were set; the distribution is unchanged otherwise.
|
|
512
|
+
"""
|
|
513
|
+
return bool(self.net.TrySetParameters(to_net_doubles(mean), to_net_matrix(covariance)))
|
|
514
|
+
|
|
515
|
+
def try_set_covariance(self, covariance: Any) -> bool:
|
|
516
|
+
"""Set the covariance when it is valid, keeping the mean (``TrySetCovariance``).
|
|
517
|
+
|
|
518
|
+
Parameters
|
|
519
|
+
----------
|
|
520
|
+
covariance : array_like
|
|
521
|
+
Covariance matrix of shape (d, d).
|
|
522
|
+
|
|
523
|
+
Returns
|
|
524
|
+
-------
|
|
525
|
+
bool
|
|
526
|
+
True when the covariance was set.
|
|
527
|
+
"""
|
|
528
|
+
return bool(self.net.TrySetCovariance(to_net_matrix(covariance)))
|
|
529
|
+
|
|
530
|
+
def marginal(self, *indices: int) -> MultivariateNormal:
|
|
531
|
+
"""Return the marginal distribution of a subset of the variables (``Marginal``).
|
|
532
|
+
|
|
533
|
+
Parameters
|
|
534
|
+
----------
|
|
535
|
+
*indices : int
|
|
536
|
+
Zero-based variable indices to keep, in the order wanted.
|
|
537
|
+
|
|
538
|
+
Returns
|
|
539
|
+
-------
|
|
540
|
+
MultivariateNormal
|
|
541
|
+
The marginal normal.
|
|
542
|
+
"""
|
|
543
|
+
return MultivariateNormal.from_net(self.net.Marginal(to_net_ints(list(indices))))
|
|
544
|
+
|
|
545
|
+
def conditional(self, observed_indices: Any, observed_values: Any) -> MultivariateNormal:
|
|
546
|
+
"""Return the distribution of the other variables given observed values (``Conditional``).
|
|
547
|
+
|
|
548
|
+
Parameters
|
|
549
|
+
----------
|
|
550
|
+
observed_indices : array_like of int
|
|
551
|
+
Zero-based indices of the observed variables.
|
|
552
|
+
observed_values : array_like
|
|
553
|
+
Their observed values.
|
|
554
|
+
|
|
555
|
+
Returns
|
|
556
|
+
-------
|
|
557
|
+
MultivariateNormal
|
|
558
|
+
The conditional normal of the unobserved variables, in index order.
|
|
559
|
+
"""
|
|
560
|
+
return MultivariateNormal.from_net(self.net.Conditional(to_net_ints(observed_indices),
|
|
561
|
+
to_net_doubles(observed_values)))
|
|
562
|
+
|
|
563
|
+
def independent_normal(self, index: int) -> Any:
|
|
564
|
+
"""Return the univariate normal marginal of one variable (``IndependentNormal``).
|
|
565
|
+
|
|
566
|
+
Parameters
|
|
567
|
+
----------
|
|
568
|
+
index : int
|
|
569
|
+
Zero-based variable index.
|
|
570
|
+
|
|
571
|
+
Returns
|
|
572
|
+
-------
|
|
573
|
+
pynumerics.distributions.Normal
|
|
574
|
+
The marginal.
|
|
575
|
+
"""
|
|
576
|
+
from pynumerics.distributions import wrap as wrap_univariate # noqa: PLC0415 - imported on use
|
|
577
|
+
|
|
578
|
+
return wrap_univariate(self.net.IndependentNormal(int(index)))
|
|
579
|
+
|
|
580
|
+
def mahalanobis(self, x: Any) -> float:
|
|
581
|
+
"""Return the squared Mahalanobis distance (x − μ)ᵀ Σ⁻¹ (x − μ) (``Mahalanobis``).
|
|
582
|
+
|
|
583
|
+
Parameters
|
|
584
|
+
----------
|
|
585
|
+
x : array_like
|
|
586
|
+
Point of length d.
|
|
587
|
+
|
|
588
|
+
Returns
|
|
589
|
+
-------
|
|
590
|
+
float
|
|
591
|
+
The squared distance.
|
|
592
|
+
"""
|
|
593
|
+
return float(self.net.Mahalanobis(to_net_doubles(x)))
|
|
594
|
+
|
|
595
|
+
def interval(self, lower: Any, upper: Any) -> float:
|
|
596
|
+
"""Return the probability of the box lower ≤ X ≤ upper (``Interval``).
|
|
597
|
+
|
|
598
|
+
Parameters
|
|
599
|
+
----------
|
|
600
|
+
lower : array_like
|
|
601
|
+
Lower limits of length d.
|
|
602
|
+
upper : array_like
|
|
603
|
+
Upper limits of length d.
|
|
604
|
+
|
|
605
|
+
Returns
|
|
606
|
+
-------
|
|
607
|
+
float
|
|
608
|
+
The box probability.
|
|
609
|
+
"""
|
|
610
|
+
return float(self.net.Interval(to_net_doubles(lower), to_net_doubles(upper)))
|
|
611
|
+
|
|
612
|
+
def inverse_cdf(self, probabilities: Any) -> np.ndarray:
|
|
613
|
+
"""Map d marginal probabilities to a correlated point (``InverseCDF(double[])``).
|
|
614
|
+
|
|
615
|
+
Parameters
|
|
616
|
+
----------
|
|
617
|
+
probabilities : array_like
|
|
618
|
+
Probabilities of length d; each maps to a standard normal deviate, and the
|
|
619
|
+
deviates are correlated as A·z + μ.
|
|
620
|
+
|
|
621
|
+
Returns
|
|
622
|
+
-------
|
|
623
|
+
numpy.ndarray
|
|
624
|
+
The point of length d.
|
|
625
|
+
"""
|
|
626
|
+
return to_numpy_doubles(self.net.InverseCDF(to_net_doubles(probabilities)))
|
|
627
|
+
|
|
628
|
+
def mvndst(self, lower: Any, upper: Any, infin: Any, correl: Any, max_points: int, absolute_error: float,
|
|
629
|
+
relative_error: float) -> tuple[float, float, int]:
|
|
630
|
+
"""Integrate a standard multivariate normal over a box with Genz's MVNDST (``MVNDST``).
|
|
631
|
+
|
|
632
|
+
Parameters
|
|
633
|
+
----------
|
|
634
|
+
lower : array_like
|
|
635
|
+
Lower limits of length N.
|
|
636
|
+
upper : array_like
|
|
637
|
+
Upper limits of length N.
|
|
638
|
+
infin : array_like of int
|
|
639
|
+
Limit flags: negative for (−∞, ∞), 0 for (−∞, upper], 1 for [lower, ∞), 2 for [lower, upper].
|
|
640
|
+
correl : array_like
|
|
641
|
+
Strict lower triangle of the correlation matrix by rows, N(N − 1)/2 values.
|
|
642
|
+
max_points : int
|
|
643
|
+
Maximum number of integrand evaluations.
|
|
644
|
+
absolute_error : float
|
|
645
|
+
Absolute error tolerance.
|
|
646
|
+
relative_error : float
|
|
647
|
+
Relative error tolerance.
|
|
648
|
+
|
|
649
|
+
Returns
|
|
650
|
+
-------
|
|
651
|
+
error : float
|
|
652
|
+
Estimated absolute error (99% confidence).
|
|
653
|
+
value : float
|
|
654
|
+
Estimated probability.
|
|
655
|
+
inform : int
|
|
656
|
+
0 for normal completion, 1 when ``max_points`` was reached first, 2 for N outside 1 to 500.
|
|
657
|
+
|
|
658
|
+
Notes
|
|
659
|
+
-----
|
|
660
|
+
The lattice shifts come from this instance's :attr:`mvnuni`, which the call advances.
|
|
661
|
+
"""
|
|
662
|
+
low = to_net_doubles(lower)
|
|
663
|
+
error, value, inform = self.net.MVNDST(len(low), low, to_net_doubles(upper), to_net_ints(infin),
|
|
664
|
+
to_net_doubles(correl), int(max_points), float(absolute_error),
|
|
665
|
+
float(relative_error), 0.0, 0.0, 0)
|
|
666
|
+
return float(error), float(value), int(inform)
|
|
667
|
+
|
|
668
|
+
@staticmethod
|
|
669
|
+
def bivariate_cdf(z1: float, z2: float, r: float) -> float:
|
|
670
|
+
"""Return the standard bivariate normal upper orthant P(X > z1, Y > z2) (``BivariateCDF``).
|
|
671
|
+
|
|
672
|
+
Parameters
|
|
673
|
+
----------
|
|
674
|
+
z1 : float
|
|
675
|
+
Lower limit of the first variable.
|
|
676
|
+
z2 : float
|
|
677
|
+
Lower limit of the second variable.
|
|
678
|
+
r : float
|
|
679
|
+
Correlation.
|
|
680
|
+
|
|
681
|
+
Returns
|
|
682
|
+
-------
|
|
683
|
+
float
|
|
684
|
+
The probability (Drezner and Wesolowsky, as modified by Genz).
|
|
685
|
+
|
|
686
|
+
Notes
|
|
687
|
+
-----
|
|
688
|
+
Numerics follows Genz's BVN convention, an upper orthant despite the name; the joint CDF
|
|
689
|
+
P(X ≤ a, Y ≤ b) is ``bivariate_cdf(-a, -b, r)``, which is how Numerics calls it.
|
|
690
|
+
"""
|
|
691
|
+
return float(_nd().MultivariateNormal.BivariateCDF(float(z1), float(z2), float(r)))
|
|
692
|
+
|
|
693
|
+
@staticmethod
|
|
694
|
+
def bivnor(ah: float, ak: float, r: float) -> float:
|
|
695
|
+
"""Return the standard bivariate normal upper orthant P(X ≥ ah, Y ≥ ak) (``bivnor``).
|
|
696
|
+
|
|
697
|
+
Parameters
|
|
698
|
+
----------
|
|
699
|
+
ah : float
|
|
700
|
+
Lower limit of the first variable.
|
|
701
|
+
ak : float
|
|
702
|
+
Lower limit of the second variable.
|
|
703
|
+
r : float
|
|
704
|
+
Correlation.
|
|
705
|
+
|
|
706
|
+
Returns
|
|
707
|
+
-------
|
|
708
|
+
float
|
|
709
|
+
The probability (Donnelly, ACM Algorithm 462).
|
|
710
|
+
"""
|
|
711
|
+
return float(_nd().MultivariateNormal.bivnor(float(ah), float(ak), float(r)))
|
|
712
|
+
|
|
713
|
+
@staticmethod
|
|
714
|
+
def bvu(sh: float, sk: float, r: float) -> float:
|
|
715
|
+
"""Return the standard bivariate normal upper orthant P(X > sh, Y > sk) (``BVU``).
|
|
716
|
+
|
|
717
|
+
Parameters
|
|
718
|
+
----------
|
|
719
|
+
sh : float
|
|
720
|
+
Lower limit of the first variable.
|
|
721
|
+
sk : float
|
|
722
|
+
Lower limit of the second variable.
|
|
723
|
+
r : float
|
|
724
|
+
Correlation.
|
|
725
|
+
|
|
726
|
+
Returns
|
|
727
|
+
-------
|
|
728
|
+
float
|
|
729
|
+
The probability (Genz).
|
|
730
|
+
"""
|
|
731
|
+
return float(_nd().MultivariateNormal.BVU(float(sh), float(sk), float(r)))
|
|
732
|
+
|
|
733
|
+
@staticmethod
|
|
734
|
+
def mvnphi(z: float) -> float:
|
|
735
|
+
"""Return the standard normal CDF used by MVNDST (``MVNPHI``).
|
|
736
|
+
|
|
737
|
+
Parameters
|
|
738
|
+
----------
|
|
739
|
+
z : float
|
|
740
|
+
The z-score.
|
|
741
|
+
|
|
742
|
+
Returns
|
|
743
|
+
-------
|
|
744
|
+
float
|
|
745
|
+
Φ(z).
|
|
746
|
+
"""
|
|
747
|
+
return float(_nd().MultivariateNormal.MVNPHI(float(z)))
|
|
748
|
+
|
|
749
|
+
@staticmethod
|
|
750
|
+
def phinvs(p: float) -> float:
|
|
751
|
+
"""Return the standard normal quantile used by MVNDST (``PHINVS``, algorithm AS 241).
|
|
752
|
+
|
|
753
|
+
Parameters
|
|
754
|
+
----------
|
|
755
|
+
p : float
|
|
756
|
+
Lower tail probability.
|
|
757
|
+
|
|
758
|
+
Returns
|
|
759
|
+
-------
|
|
760
|
+
float
|
|
761
|
+
Φ⁻¹(p).
|
|
762
|
+
"""
|
|
763
|
+
return float(_nd().MultivariateNormal.PHINVS(float(p)))
|
|
764
|
+
|
|
765
|
+
def generate_random_values(self, sample_size: int, seed: Any = -1) -> np.ndarray:
|
|
766
|
+
"""Draw a Monte Carlo sample (``GenerateRandomValues``).
|
|
767
|
+
|
|
768
|
+
Parameters
|
|
769
|
+
----------
|
|
770
|
+
sample_size : int
|
|
771
|
+
Number of points.
|
|
772
|
+
seed : int or MersenneTwister, optional
|
|
773
|
+
Seed (zero or negative seeds from the clock, not reproducible) or a generator.
|
|
774
|
+
|
|
775
|
+
Returns
|
|
776
|
+
-------
|
|
777
|
+
numpy.ndarray
|
|
778
|
+
Array of shape ``(sample_size, d)``.
|
|
779
|
+
"""
|
|
780
|
+
return _draw(self.net.GenerateRandomValues, sample_size, seed)
|
|
781
|
+
|
|
782
|
+
def latin_hypercube_random_values(self, sample_size: int, seed: Any) -> np.ndarray:
|
|
783
|
+
"""Draw a Latin hypercube sample (``LatinHypercubeRandomValues``).
|
|
784
|
+
|
|
785
|
+
Parameters
|
|
786
|
+
----------
|
|
787
|
+
sample_size : int
|
|
788
|
+
Number of points.
|
|
789
|
+
seed : int or MersenneTwister
|
|
790
|
+
Seed or a generator.
|
|
791
|
+
|
|
792
|
+
Returns
|
|
793
|
+
-------
|
|
794
|
+
numpy.ndarray
|
|
795
|
+
Array of shape ``(sample_size, d)``.
|
|
796
|
+
"""
|
|
797
|
+
return _draw(self.net.LatinHypercubeRandomValues, sample_size, seed)
|
|
798
|
+
|
|
799
|
+
def stratified_random_values(self, stratification_bins: Any, seed: int) -> np.ndarray:
|
|
800
|
+
"""Draw a sample stratified in the first variable (``StratifiedRandomValues``).
|
|
801
|
+
|
|
802
|
+
Parameters
|
|
803
|
+
----------
|
|
804
|
+
stratification_bins : pynumerics.sampling.StratificationBins
|
|
805
|
+
Probability bins; one point is drawn per bin at its midpoint.
|
|
806
|
+
seed : int
|
|
807
|
+
Seed of the other variables.
|
|
808
|
+
|
|
809
|
+
Returns
|
|
810
|
+
-------
|
|
811
|
+
numpy.ndarray
|
|
812
|
+
Array of shape ``(len(stratification_bins), d)``.
|
|
813
|
+
"""
|
|
814
|
+
return to_numpy_matrix(self.net.StratifiedRandomValues(_bins(stratification_bins), int(seed)))
|
|
815
|
+
|
|
816
|
+
|
|
817
|
+
class MultivariateStudentT(MultivariateDistribution):
|
|
818
|
+
"""Multivariate Student-t distribution (``Numerics.Distributions.MultivariateStudentT``).
|
|
819
|
+
|
|
820
|
+
Parameters
|
|
821
|
+
----------
|
|
822
|
+
degrees_of_freedom : float
|
|
823
|
+
Degrees of freedom ν > 0.
|
|
824
|
+
location : array_like or int
|
|
825
|
+
Location vector μ of length d, or the dimension d for a zero location.
|
|
826
|
+
scale_matrix : array_like, optional
|
|
827
|
+
Scale matrix Σ of shape (d, d), positive definite; default the identity. The covariance
|
|
828
|
+
is ν/(ν − 2)·Σ for ν > 2.
|
|
829
|
+
|
|
830
|
+
Attributes
|
|
831
|
+
----------
|
|
832
|
+
net : Numerics.Distributions.MultivariateStudentT
|
|
833
|
+
The wrapped .NET object.
|
|
834
|
+
"""
|
|
835
|
+
|
|
836
|
+
def __init__(self, degrees_of_freedom: float, location: Any, scale_matrix: Any = None) -> None:
|
|
837
|
+
nd = _nd()
|
|
838
|
+
if isinstance(location, (int, np.integer)):
|
|
839
|
+
if scale_matrix is not None:
|
|
840
|
+
raise ValueError("A scale matrix needs a location vector, not a dimension.")
|
|
841
|
+
net = nd.MultivariateStudentT(int(location), float(degrees_of_freedom))
|
|
842
|
+
elif scale_matrix is None:
|
|
843
|
+
net = nd.MultivariateStudentT(float(degrees_of_freedom), to_net_doubles(location))
|
|
844
|
+
else:
|
|
845
|
+
net = nd.MultivariateStudentT(float(degrees_of_freedom), to_net_doubles(location),
|
|
846
|
+
to_net_matrix(scale_matrix))
|
|
847
|
+
MultivariateDistribution.__init__(self, net)
|
|
848
|
+
|
|
849
|
+
@property
|
|
850
|
+
def mvnuni(self) -> Any:
|
|
851
|
+
"""The generator of the Genz-Bretz lattice rule of :meth:`cdf` above two dimensions (``MVNUNI``).
|
|
852
|
+
|
|
853
|
+
Reads as a :class:`pynumerics.sampling.MersenneTwister` when it is one. Accepts a generator or
|
|
854
|
+
an integer seed (a new Numerics ``MersenneTwister``).
|
|
855
|
+
"""
|
|
856
|
+
return _wrap_generator(self.net.MVNUNI)
|
|
857
|
+
|
|
858
|
+
@mvnuni.setter
|
|
859
|
+
def mvnuni(self, value: Any) -> None:
|
|
860
|
+
self.net.MVNUNI = _seeded_generator(value)
|
|
861
|
+
|
|
862
|
+
@property
|
|
863
|
+
def max_evaluations(self) -> int:
|
|
864
|
+
"""The maximum number of lattice-rule evaluations of the CDF above two dimensions; default 25,000."""
|
|
865
|
+
return int(self.net.MaxEvaluations)
|
|
866
|
+
|
|
867
|
+
@max_evaluations.setter
|
|
868
|
+
def max_evaluations(self, value: int) -> None:
|
|
869
|
+
self.net.MaxEvaluations = int(value)
|
|
870
|
+
|
|
871
|
+
@property
|
|
872
|
+
def absolute_error(self) -> float:
|
|
873
|
+
"""The absolute error tolerance of the CDF above two dimensions; default 1e-5."""
|
|
874
|
+
return float(self.net.AbsoluteError)
|
|
875
|
+
|
|
876
|
+
@absolute_error.setter
|
|
877
|
+
def absolute_error(self, value: float) -> None:
|
|
878
|
+
self.net.AbsoluteError = float(value)
|
|
879
|
+
|
|
880
|
+
@property
|
|
881
|
+
def relative_error(self) -> float:
|
|
882
|
+
"""The relative error tolerance of the CDF above two dimensions; default 0."""
|
|
883
|
+
return float(self.net.RelativeError)
|
|
884
|
+
|
|
885
|
+
@relative_error.setter
|
|
886
|
+
def relative_error(self, value: float) -> None:
|
|
887
|
+
self.net.RelativeError = float(value)
|
|
888
|
+
|
|
889
|
+
@property
|
|
890
|
+
def degrees_of_freedom(self) -> float:
|
|
891
|
+
"""The degrees of freedom ν."""
|
|
892
|
+
return float(self.net.DegreesOfFreedom)
|
|
893
|
+
|
|
894
|
+
@property
|
|
895
|
+
def location(self) -> np.ndarray:
|
|
896
|
+
"""The location vector μ."""
|
|
897
|
+
return to_numpy_doubles(self.net.Location)
|
|
898
|
+
|
|
899
|
+
@property
|
|
900
|
+
def mean(self) -> np.ndarray:
|
|
901
|
+
"""The mean vector (μ for ν > 1)."""
|
|
902
|
+
return to_numpy_doubles(self.net.Mean)
|
|
903
|
+
|
|
904
|
+
@property
|
|
905
|
+
def median(self) -> np.ndarray:
|
|
906
|
+
"""The median vector μ."""
|
|
907
|
+
return to_numpy_doubles(self.net.Median)
|
|
908
|
+
|
|
909
|
+
@property
|
|
910
|
+
def mode(self) -> np.ndarray:
|
|
911
|
+
"""The mode vector μ."""
|
|
912
|
+
return to_numpy_doubles(self.net.Mode)
|
|
913
|
+
|
|
914
|
+
@property
|
|
915
|
+
def variance(self) -> np.ndarray:
|
|
916
|
+
"""The marginal variances ν/(ν − 2)·Σᵢᵢ for ν > 2."""
|
|
917
|
+
return to_numpy_doubles(self.net.Variance)
|
|
918
|
+
|
|
919
|
+
@property
|
|
920
|
+
def standard_deviation(self) -> np.ndarray:
|
|
921
|
+
"""The marginal standard deviations."""
|
|
922
|
+
return to_numpy_doubles(self.net.StandardDeviation)
|
|
923
|
+
|
|
924
|
+
@property
|
|
925
|
+
def scale_matrix(self) -> np.ndarray:
|
|
926
|
+
"""The scale matrix Σ."""
|
|
927
|
+
return to_numpy_matrix(self.net.ScaleMatrix)
|
|
928
|
+
|
|
929
|
+
@property
|
|
930
|
+
def covariance(self) -> np.ndarray:
|
|
931
|
+
"""The covariance matrix ν/(ν − 2)·Σ for ν > 2."""
|
|
932
|
+
return to_numpy_matrix(self.net.Covariance)
|
|
933
|
+
|
|
934
|
+
@property
|
|
935
|
+
def is_positive_definite(self) -> bool:
|
|
936
|
+
"""Whether Σ is positive definite."""
|
|
937
|
+
return bool(self.net.IsPositiveDefinite)
|
|
938
|
+
|
|
939
|
+
def set_parameters(self, degrees_of_freedom: float, location: Any, scale_matrix: Any) -> None:
|
|
940
|
+
"""Replace the parameters (``SetParameters``).
|
|
941
|
+
|
|
942
|
+
Parameters
|
|
943
|
+
----------
|
|
944
|
+
degrees_of_freedom : float
|
|
945
|
+
Degrees of freedom ν.
|
|
946
|
+
location : array_like
|
|
947
|
+
Location vector μ.
|
|
948
|
+
scale_matrix : array_like
|
|
949
|
+
Scale matrix Σ.
|
|
950
|
+
"""
|
|
951
|
+
self.net.SetParameters(float(degrees_of_freedom), to_net_doubles(location), to_net_matrix(scale_matrix))
|
|
952
|
+
|
|
953
|
+
def validate_parameters(self, degrees_of_freedom: float, location: Any, scale_matrix: Any) -> str | None:
|
|
954
|
+
"""Return why a parameter set is invalid, or None when it is valid.
|
|
955
|
+
|
|
956
|
+
Parameters
|
|
957
|
+
----------
|
|
958
|
+
degrees_of_freedom : float
|
|
959
|
+
Degrees of freedom ν.
|
|
960
|
+
location : array_like
|
|
961
|
+
Location vector μ.
|
|
962
|
+
scale_matrix : array_like
|
|
963
|
+
Scale matrix Σ.
|
|
964
|
+
|
|
965
|
+
Returns
|
|
966
|
+
-------
|
|
967
|
+
str or None
|
|
968
|
+
The Numerics message of ``ValidateParameters(..., false)``, or None.
|
|
969
|
+
"""
|
|
970
|
+
return _message(self.net.ValidateParameters(float(degrees_of_freedom), to_net_doubles(location),
|
|
971
|
+
to_net_matrix(scale_matrix), False))
|
|
972
|
+
|
|
973
|
+
def mahalanobis(self, x: Any) -> float:
|
|
974
|
+
"""Return the squared Mahalanobis distance (x − μ)ᵀ Σ⁻¹ (x − μ) (``Mahalanobis``).
|
|
975
|
+
|
|
976
|
+
Parameters
|
|
977
|
+
----------
|
|
978
|
+
x : array_like
|
|
979
|
+
Point of length d.
|
|
980
|
+
|
|
981
|
+
Returns
|
|
982
|
+
-------
|
|
983
|
+
float
|
|
984
|
+
The squared distance.
|
|
985
|
+
"""
|
|
986
|
+
return float(self.net.Mahalanobis(to_net_doubles(x)))
|
|
987
|
+
|
|
988
|
+
def inverse_cdf(self, probabilities: Any) -> np.ndarray:
|
|
989
|
+
"""Map d + 1 probabilities to a point (``InverseCDF(double[])``).
|
|
990
|
+
|
|
991
|
+
Parameters
|
|
992
|
+
----------
|
|
993
|
+
probabilities : array_like
|
|
994
|
+
Length d + 1: the first d map to standard normal deviates, the last to the χ²(ν) mixing
|
|
995
|
+
variate W, and the point is μ + L·z·√(ν/W).
|
|
996
|
+
|
|
997
|
+
Returns
|
|
998
|
+
-------
|
|
999
|
+
numpy.ndarray
|
|
1000
|
+
The point of length d.
|
|
1001
|
+
"""
|
|
1002
|
+
return to_numpy_doubles(self.net.InverseCDF(to_net_doubles(probabilities)))
|
|
1003
|
+
|
|
1004
|
+
def generate_random_values(self, sample_size: int, seed: Any = -1) -> np.ndarray:
|
|
1005
|
+
"""Draw a Monte Carlo sample (``GenerateRandomValues``).
|
|
1006
|
+
|
|
1007
|
+
Parameters
|
|
1008
|
+
----------
|
|
1009
|
+
sample_size : int
|
|
1010
|
+
Number of points.
|
|
1011
|
+
seed : int or MersenneTwister, optional
|
|
1012
|
+
Seed (zero or negative seeds from the clock, not reproducible) or a generator.
|
|
1013
|
+
|
|
1014
|
+
Returns
|
|
1015
|
+
-------
|
|
1016
|
+
numpy.ndarray
|
|
1017
|
+
Array of shape ``(sample_size, d)``.
|
|
1018
|
+
"""
|
|
1019
|
+
return _draw(self.net.GenerateRandomValues, sample_size, seed)
|
|
1020
|
+
|
|
1021
|
+
def latin_hypercube_random_values(self, sample_size: int, seed: int) -> np.ndarray:
|
|
1022
|
+
"""Draw a Latin hypercube sample of the normal part (``LatinHypercubeRandomValues``).
|
|
1023
|
+
|
|
1024
|
+
Parameters
|
|
1025
|
+
----------
|
|
1026
|
+
sample_size : int
|
|
1027
|
+
Number of points.
|
|
1028
|
+
seed : int
|
|
1029
|
+
Seed; the χ² mixing variates use ``seed + 1``.
|
|
1030
|
+
|
|
1031
|
+
Returns
|
|
1032
|
+
-------
|
|
1033
|
+
numpy.ndarray
|
|
1034
|
+
Array of shape ``(sample_size, d)``.
|
|
1035
|
+
"""
|
|
1036
|
+
return to_numpy_matrix(self.net.LatinHypercubeRandomValues(int(sample_size), int(seed)))
|
|
1037
|
+
|
|
1038
|
+
def stratified_random_values(self, stratification_bins: Any, seed: int) -> np.ndarray:
|
|
1039
|
+
"""Draw a sample stratified in the first variable (``StratifiedRandomValues``).
|
|
1040
|
+
|
|
1041
|
+
Parameters
|
|
1042
|
+
----------
|
|
1043
|
+
stratification_bins : pynumerics.sampling.StratificationBins
|
|
1044
|
+
Probability bins; one point is drawn per bin at its midpoint.
|
|
1045
|
+
seed : int
|
|
1046
|
+
Seed of the other variables; the χ² mixing variates use ``seed + 1``.
|
|
1047
|
+
|
|
1048
|
+
Returns
|
|
1049
|
+
-------
|
|
1050
|
+
numpy.ndarray
|
|
1051
|
+
Array of shape ``(len(stratification_bins), d)``.
|
|
1052
|
+
"""
|
|
1053
|
+
return to_numpy_matrix(self.net.StratifiedRandomValues(_bins(stratification_bins), int(seed)))
|
|
1054
|
+
|
|
1055
|
+
|
|
1056
|
+
class Multinomial(MultivariateDistribution):
|
|
1057
|
+
"""Multinomial distribution of category counts (``Numerics.Distributions.Multinomial``).
|
|
1058
|
+
|
|
1059
|
+
Parameters
|
|
1060
|
+
----------
|
|
1061
|
+
number_of_trials : int
|
|
1062
|
+
Number of trials N ≥ 1.
|
|
1063
|
+
probabilities : array_like
|
|
1064
|
+
Category probabilities p of length k ≥ 2, each in [0, 1], summing to one.
|
|
1065
|
+
|
|
1066
|
+
Attributes
|
|
1067
|
+
----------
|
|
1068
|
+
net : Numerics.Distributions.Multinomial
|
|
1069
|
+
The wrapped .NET object.
|
|
1070
|
+
|
|
1071
|
+
Notes
|
|
1072
|
+
-----
|
|
1073
|
+
Points are count vectors of length k; ``pdf`` is the probability mass function.
|
|
1074
|
+
"""
|
|
1075
|
+
|
|
1076
|
+
def __init__(self, number_of_trials: int, probabilities: Any) -> None:
|
|
1077
|
+
MultivariateDistribution.__init__(self, _nd().Multinomial(int(number_of_trials), to_net_doubles(probabilities)))
|
|
1078
|
+
|
|
1079
|
+
@property
|
|
1080
|
+
def number_of_trials(self) -> int:
|
|
1081
|
+
"""The number of trials N."""
|
|
1082
|
+
return int(self.net.NumberOfTrials)
|
|
1083
|
+
|
|
1084
|
+
@property
|
|
1085
|
+
def probabilities(self) -> np.ndarray:
|
|
1086
|
+
"""The category probabilities p."""
|
|
1087
|
+
return to_numpy_doubles(self.net.Probabilities)
|
|
1088
|
+
|
|
1089
|
+
@property
|
|
1090
|
+
def mean(self) -> np.ndarray:
|
|
1091
|
+
"""The mean counts N·pᵢ."""
|
|
1092
|
+
return to_numpy_doubles(self.net.Mean)
|
|
1093
|
+
|
|
1094
|
+
@property
|
|
1095
|
+
def variance(self) -> np.ndarray:
|
|
1096
|
+
"""The count variances N·pᵢ(1 − pᵢ)."""
|
|
1097
|
+
return to_numpy_doubles(self.net.Variance)
|
|
1098
|
+
|
|
1099
|
+
def covariance(self, i: int, j: int) -> float:
|
|
1100
|
+
"""Return one covariance entry (``Covariance(int, int)``).
|
|
1101
|
+
|
|
1102
|
+
Parameters
|
|
1103
|
+
----------
|
|
1104
|
+
i : int
|
|
1105
|
+
Row category index.
|
|
1106
|
+
j : int
|
|
1107
|
+
Column category index.
|
|
1108
|
+
|
|
1109
|
+
Returns
|
|
1110
|
+
-------
|
|
1111
|
+
float
|
|
1112
|
+
Cov(Xᵢ, Xⱼ): N·pᵢ(1 − pᵢ) on the diagonal, −N·pᵢ·pⱼ off it.
|
|
1113
|
+
"""
|
|
1114
|
+
return float(self.net.Covariance(int(i), int(j)))
|
|
1115
|
+
|
|
1116
|
+
def log_pmf(self, x: Any) -> Any:
|
|
1117
|
+
"""Return the log probability mass (``LogPMF``).
|
|
1118
|
+
|
|
1119
|
+
Parameters
|
|
1120
|
+
----------
|
|
1121
|
+
x : array_like
|
|
1122
|
+
One count vector of length k, or an array of shape (n, k).
|
|
1123
|
+
|
|
1124
|
+
Returns
|
|
1125
|
+
-------
|
|
1126
|
+
float or numpy.ndarray
|
|
1127
|
+
The log mass; negative infinity off the support.
|
|
1128
|
+
"""
|
|
1129
|
+
return self._point("LogPMF", x)
|
|
1130
|
+
|
|
1131
|
+
def generate_random_values(self, sample_size: int, seed: int = -1) -> np.ndarray:
|
|
1132
|
+
"""Draw count vectors by sequential binomial sampling (``GenerateRandomValues``).
|
|
1133
|
+
|
|
1134
|
+
Parameters
|
|
1135
|
+
----------
|
|
1136
|
+
sample_size : int
|
|
1137
|
+
Number of draws.
|
|
1138
|
+
seed : int, optional
|
|
1139
|
+
Seed; zero or negative seeds from the clock (not reproducible).
|
|
1140
|
+
|
|
1141
|
+
Returns
|
|
1142
|
+
-------
|
|
1143
|
+
numpy.ndarray
|
|
1144
|
+
Array of shape ``(sample_size, k)``; each row sums to N.
|
|
1145
|
+
"""
|
|
1146
|
+
return to_numpy_matrix(self.net.GenerateRandomValues(int(sample_size), int(seed)))
|
|
1147
|
+
|
|
1148
|
+
@staticmethod
|
|
1149
|
+
def sample(weights: Any, random: Any) -> int:
|
|
1150
|
+
"""Draw one category index with probability proportional to its weight (``Multinomial.Sample``).
|
|
1151
|
+
|
|
1152
|
+
Parameters
|
|
1153
|
+
----------
|
|
1154
|
+
weights : array_like
|
|
1155
|
+
Non-negative weights, at least one positive; normalization is not required.
|
|
1156
|
+
random : MersenneTwister
|
|
1157
|
+
The generator (a :class:`pynumerics.sampling.MersenneTwister` or a .NET ``System.Random``);
|
|
1158
|
+
one draw is consumed.
|
|
1159
|
+
|
|
1160
|
+
Returns
|
|
1161
|
+
-------
|
|
1162
|
+
int
|
|
1163
|
+
The zero-based category index; a draw on a boundary selects the lower category.
|
|
1164
|
+
"""
|
|
1165
|
+
return int(_nd().Multinomial.Sample(to_net_doubles(weights), _generator(random)))
|
|
1166
|
+
|
|
1167
|
+
@staticmethod
|
|
1168
|
+
def categorical_indices(cumulative_weights: Any, uniforms: Any, inclusive: bool = False) -> Any:
|
|
1169
|
+
"""Map uniforms to category indices through a cumulative weight table (``CategoricalIndices``).
|
|
1170
|
+
|
|
1171
|
+
Parameters
|
|
1172
|
+
----------
|
|
1173
|
+
cumulative_weights : array_like
|
|
1174
|
+
Non-decreasing finite cumulative weights c of length n ≥ 1; normalization is not required.
|
|
1175
|
+
uniforms : array_like
|
|
1176
|
+
Variates on the scale of ``cumulative_weights``, any shape, no NaN.
|
|
1177
|
+
inclusive : bool, optional
|
|
1178
|
+
False (the default) selects min{k : c[k] > u}; True selects min{k : c[k] ≥ u}, the
|
|
1179
|
+
inverse-transform count min{k : F(k) ≥ u} of a discrete CDF table F. Both clip to n - 1.
|
|
1180
|
+
|
|
1181
|
+
Returns
|
|
1182
|
+
-------
|
|
1183
|
+
int or numpy.ndarray
|
|
1184
|
+
One zero-based index per variate (int32, the shape of ``uniforms``), equal to
|
|
1185
|
+
``numpy.clip(numpy.searchsorted(c, u, side="right"), 0, n - 1)``, or with ``side="left"``
|
|
1186
|
+
when ``inclusive`` is True.
|
|
1187
|
+
|
|
1188
|
+
Notes
|
|
1189
|
+
-----
|
|
1190
|
+
The two forms differ only for a variate equal to a table entry. The whole array crosses into
|
|
1191
|
+
Numerics once, and the indices cross back once.
|
|
1192
|
+
"""
|
|
1193
|
+
member = cached_overload("Multinomial.CategoricalIndices(double[], double[], bool)", _categorical_indices)
|
|
1194
|
+
array = np.asarray(uniforms, dtype=np.float64)
|
|
1195
|
+
indices = to_numpy_ints(member(to_net_doubles(cumulative_weights), _flat_doubles(array), bool(inclusive)))
|
|
1196
|
+
return int(indices[0]) if array.ndim == 0 else indices.reshape(array.shape)
|
|
1197
|
+
|
|
1198
|
+
|
|
1199
|
+
class BivariateEmpirical(MultivariateDistribution):
|
|
1200
|
+
"""Bivariate distribution interpolated from a table of joint non-exceedance probabilities.
|
|
1201
|
+
|
|
1202
|
+
Wraps ``Numerics.Distributions.BivariateEmpirical``: the CDF is bilinear interpolation of
|
|
1203
|
+
``probability_values`` on the grid ``x1_values`` by ``x2_values``, in the chosen transforms.
|
|
1204
|
+
|
|
1205
|
+
Parameters
|
|
1206
|
+
----------
|
|
1207
|
+
x1_values : array_like, optional
|
|
1208
|
+
Ascending grid of the first variable. Omit all three tables for an empty distribution
|
|
1209
|
+
to fill later with :meth:`set_parameters`.
|
|
1210
|
+
x2_values : array_like, optional
|
|
1211
|
+
Ascending grid of the second variable.
|
|
1212
|
+
probability_values : array_like, optional
|
|
1213
|
+
Joint probabilities of shape ``(len(x1_values), len(x2_values))``.
|
|
1214
|
+
x1_transform : Transform or str, optional
|
|
1215
|
+
Interpolation transform of the first variable; default none.
|
|
1216
|
+
x2_transform : Transform or str, optional
|
|
1217
|
+
Interpolation transform of the second variable; default none.
|
|
1218
|
+
probability_transform : Transform or str, optional
|
|
1219
|
+
Interpolation transform of the probabilities; default none.
|
|
1220
|
+
|
|
1221
|
+
Attributes
|
|
1222
|
+
----------
|
|
1223
|
+
net : Numerics.Distributions.BivariateEmpirical
|
|
1224
|
+
The wrapped .NET object.
|
|
1225
|
+
"""
|
|
1226
|
+
|
|
1227
|
+
def __init__(self, x1_values: Any = None, x2_values: Any = None, probability_values: Any = None,
|
|
1228
|
+
x1_transform: Transform | str = Transform.NONE, x2_transform: Transform | str = Transform.NONE,
|
|
1229
|
+
probability_transform: Transform | str = Transform.NONE) -> None:
|
|
1230
|
+
nd = _nd()
|
|
1231
|
+
kind = namespace("Numerics.Data").Transform
|
|
1232
|
+
transforms = (net_enum(kind, x1_transform), net_enum(kind, x2_transform), net_enum(kind, probability_transform))
|
|
1233
|
+
if x1_values is None and x2_values is None and probability_values is None:
|
|
1234
|
+
net = nd.BivariateEmpirical(*transforms)
|
|
1235
|
+
else:
|
|
1236
|
+
net = nd.BivariateEmpirical(to_net_doubles(x1_values), to_net_doubles(x2_values),
|
|
1237
|
+
to_net_matrix(probability_values), *transforms)
|
|
1238
|
+
MultivariateDistribution.__init__(self, net)
|
|
1239
|
+
|
|
1240
|
+
def _bind(self) -> None:
|
|
1241
|
+
"""Bind the ``(double, double)`` overloads of ``PDF`` and ``CDF`` once."""
|
|
1242
|
+
import System # noqa: PLC0415 - the runtime is running (self.net exists)
|
|
1243
|
+
|
|
1244
|
+
self._pair = {name: getattr(self.net, name).Overloads[System.Double, System.Double] for name in ("PDF", "CDF")}
|
|
1245
|
+
|
|
1246
|
+
def _pairs(self, name: str, x1: Any, x2: Any) -> Any:
|
|
1247
|
+
"""Evaluate a point member at a point, or at broadcast (x1, x2) pairs with the scalar overload."""
|
|
1248
|
+
if x2 is None:
|
|
1249
|
+
return self._point(name, x1)
|
|
1250
|
+
member = self._pair[name]
|
|
1251
|
+
a, b = np.broadcast_arrays(np.asarray(x1, dtype=np.float64), np.asarray(x2, dtype=np.float64))
|
|
1252
|
+
if a.ndim == 0:
|
|
1253
|
+
return float(member(float(a), float(b)))
|
|
1254
|
+
out = np.array([float(member(float(p), float(q))) for p, q in zip(a.ravel(), b.ravel(), strict=True)])
|
|
1255
|
+
return out.reshape(a.shape)
|
|
1256
|
+
|
|
1257
|
+
@property
|
|
1258
|
+
def x1_values(self) -> np.ndarray:
|
|
1259
|
+
"""The grid of the first variable."""
|
|
1260
|
+
return to_numpy_doubles(self.net.X1Values)
|
|
1261
|
+
|
|
1262
|
+
@property
|
|
1263
|
+
def x2_values(self) -> np.ndarray:
|
|
1264
|
+
"""The grid of the second variable."""
|
|
1265
|
+
return to_numpy_doubles(self.net.X2Values)
|
|
1266
|
+
|
|
1267
|
+
@property
|
|
1268
|
+
def probability_values(self) -> np.ndarray:
|
|
1269
|
+
"""The table of joint non-exceedance probabilities."""
|
|
1270
|
+
return to_numpy_matrix(self.net.ProbabilityValues)
|
|
1271
|
+
|
|
1272
|
+
@property
|
|
1273
|
+
def x1_transform(self) -> Transform:
|
|
1274
|
+
"""The interpolation transform of the first variable; settable."""
|
|
1275
|
+
return Transform(str(self.net.X1Transform))
|
|
1276
|
+
|
|
1277
|
+
@x1_transform.setter
|
|
1278
|
+
def x1_transform(self, value: Transform | str) -> None:
|
|
1279
|
+
self.net.X1Transform = net_enum(namespace("Numerics.Data").Transform, value)
|
|
1280
|
+
|
|
1281
|
+
@property
|
|
1282
|
+
def x2_transform(self) -> Transform:
|
|
1283
|
+
"""The interpolation transform of the second variable; settable."""
|
|
1284
|
+
return Transform(str(self.net.X2Transform))
|
|
1285
|
+
|
|
1286
|
+
@x2_transform.setter
|
|
1287
|
+
def x2_transform(self, value: Transform | str) -> None:
|
|
1288
|
+
self.net.X2Transform = net_enum(namespace("Numerics.Data").Transform, value)
|
|
1289
|
+
|
|
1290
|
+
@property
|
|
1291
|
+
def probability_transform(self) -> Transform:
|
|
1292
|
+
"""The interpolation transform of the probabilities; settable."""
|
|
1293
|
+
return Transform(str(self.net.ProbabilityTransform))
|
|
1294
|
+
|
|
1295
|
+
@probability_transform.setter
|
|
1296
|
+
def probability_transform(self, value: Transform | str) -> None:
|
|
1297
|
+
self.net.ProbabilityTransform = net_enum(namespace("Numerics.Data").Transform, value)
|
|
1298
|
+
|
|
1299
|
+
def set_parameters(self, x1_values: Any, x2_values: Any, probability_values: Any) -> None:
|
|
1300
|
+
"""Replace the grids and the probability table (``SetParameters``).
|
|
1301
|
+
|
|
1302
|
+
Parameters
|
|
1303
|
+
----------
|
|
1304
|
+
x1_values : array_like
|
|
1305
|
+
Grid of the first variable.
|
|
1306
|
+
x2_values : array_like
|
|
1307
|
+
Grid of the second variable.
|
|
1308
|
+
probability_values : array_like
|
|
1309
|
+
Joint probabilities of shape ``(len(x1_values), len(x2_values))``.
|
|
1310
|
+
"""
|
|
1311
|
+
self.net.SetParameters(to_net_doubles(x1_values), to_net_doubles(x2_values), to_net_matrix(probability_values))
|
|
1312
|
+
|
|
1313
|
+
def validate_parameters(self, x1_values: Any, x2_values: Any, probability_values: Any) -> str | None:
|
|
1314
|
+
"""Return why a grid and table are invalid, or None when they are valid.
|
|
1315
|
+
|
|
1316
|
+
Parameters
|
|
1317
|
+
----------
|
|
1318
|
+
x1_values : array_like
|
|
1319
|
+
Grid of the first variable.
|
|
1320
|
+
x2_values : array_like
|
|
1321
|
+
Grid of the second variable.
|
|
1322
|
+
probability_values : array_like
|
|
1323
|
+
Joint probabilities.
|
|
1324
|
+
|
|
1325
|
+
Returns
|
|
1326
|
+
-------
|
|
1327
|
+
str or None
|
|
1328
|
+
The Numerics message of ``ValidateParameters(..., false)``, or None.
|
|
1329
|
+
"""
|
|
1330
|
+
return _message(self.net.ValidateParameters(to_net_doubles(x1_values), to_net_doubles(x2_values),
|
|
1331
|
+
to_net_matrix(probability_values), False))
|
|
1332
|
+
|
|
1333
|
+
def pdf(self, x1: Any, x2: Any = None) -> Any:
|
|
1334
|
+
"""Return the density, which Numerics does not define for the interpolated surface (NaN).
|
|
1335
|
+
|
|
1336
|
+
Parameters
|
|
1337
|
+
----------
|
|
1338
|
+
x1 : float or array_like
|
|
1339
|
+
The first variable, or one point (x1, x2) when ``x2`` is omitted.
|
|
1340
|
+
x2 : float or array_like, optional
|
|
1341
|
+
The second variable, broadcast with ``x1``.
|
|
1342
|
+
|
|
1343
|
+
Returns
|
|
1344
|
+
-------
|
|
1345
|
+
float or numpy.ndarray
|
|
1346
|
+
NaN for every point.
|
|
1347
|
+
"""
|
|
1348
|
+
return self._pairs("PDF", x1, x2)
|
|
1349
|
+
|
|
1350
|
+
def cdf(self, x1: Any, x2: Any = None) -> Any:
|
|
1351
|
+
"""Return the interpolated joint CDF P(X1 ≤ x1, X2 ≤ x2) (``CDF(double, double)``).
|
|
1352
|
+
|
|
1353
|
+
Parameters
|
|
1354
|
+
----------
|
|
1355
|
+
x1 : float or array_like
|
|
1356
|
+
The first variable, or one point (x1, x2) when ``x2`` is omitted.
|
|
1357
|
+
x2 : float or array_like, optional
|
|
1358
|
+
The second variable, broadcast with ``x1``.
|
|
1359
|
+
|
|
1360
|
+
Returns
|
|
1361
|
+
-------
|
|
1362
|
+
float or numpy.ndarray
|
|
1363
|
+
Joint non-exceedance probabilities.
|
|
1364
|
+
"""
|
|
1365
|
+
return self._pairs("CDF", x1, x2)
|
|
1366
|
+
|
|
1367
|
+
|
|
1368
|
+
class Dirichlet(MultivariateDistribution):
|
|
1369
|
+
"""Dirichlet distribution on the probability simplex (``Numerics.Distributions.Dirichlet``).
|
|
1370
|
+
|
|
1371
|
+
Parameters
|
|
1372
|
+
----------
|
|
1373
|
+
alpha : array_like
|
|
1374
|
+
Concentration parameters α of length K ≥ 2, each positive and finite. Numerics refuses
|
|
1375
|
+
anything else with ``ArgumentOutOfRangeException`` (``ValueError`` inside
|
|
1376
|
+
:func:`pynumerics.errors.net_errors`).
|
|
1377
|
+
|
|
1378
|
+
Attributes
|
|
1379
|
+
----------
|
|
1380
|
+
net : Numerics.Distributions.Dirichlet
|
|
1381
|
+
The wrapped .NET object.
|
|
1382
|
+
|
|
1383
|
+
Notes
|
|
1384
|
+
-----
|
|
1385
|
+
Every value comes from Numerics; nothing is computed in Python. Draws use the gamma
|
|
1386
|
+
representation inside ``Dirichlet.GenerateRandomValues``: K independent
|
|
1387
|
+
Gamma(αᵢ, 1) inverse-CDF variates from one seeded Mersenne Twister, normalized to sum
|
|
1388
|
+
to one. The sample crosses back as one ``double[,]`` block copy, so a draw is bit for bit
|
|
1389
|
+
the ``double[,]`` the .NET call returns. The density is zero (log-density negative
|
|
1390
|
+
infinity) off the simplex.
|
|
1391
|
+
"""
|
|
1392
|
+
|
|
1393
|
+
def __init__(self, alpha: Any) -> None:
|
|
1394
|
+
MultivariateDistribution.__init__(self, _nd().Dirichlet(to_net_doubles(alpha)))
|
|
1395
|
+
|
|
1396
|
+
@classmethod
|
|
1397
|
+
def symmetric(cls, dimension: int, alpha: float) -> Dirichlet:
|
|
1398
|
+
"""Build the symmetric Dirichlet with every αᵢ = ``alpha`` (``Dirichlet(int, double)``).
|
|
1399
|
+
|
|
1400
|
+
Parameters
|
|
1401
|
+
----------
|
|
1402
|
+
dimension : int
|
|
1403
|
+
Number of components K ≥ 2.
|
|
1404
|
+
alpha : float
|
|
1405
|
+
Common positive concentration parameter.
|
|
1406
|
+
|
|
1407
|
+
Returns
|
|
1408
|
+
-------
|
|
1409
|
+
Dirichlet
|
|
1410
|
+
The wrapped distribution.
|
|
1411
|
+
"""
|
|
1412
|
+
return cls.from_net(_nd().Dirichlet(int(dimension), float(alpha)))
|
|
1413
|
+
|
|
1414
|
+
def _bind(self) -> None:
|
|
1415
|
+
"""Bind ``GenerateRandomValues(int, int)`` once, so calls skip overload resolution."""
|
|
1416
|
+
import System # noqa: PLC0415 - the runtime is started by the constructor
|
|
1417
|
+
|
|
1418
|
+
self._generate = self.net.GenerateRandomValues.Overloads[System.Int32, System.Int32]
|
|
1419
|
+
|
|
1420
|
+
@property
|
|
1421
|
+
def alpha(self) -> np.ndarray:
|
|
1422
|
+
"""The concentration parameters α (a copy)."""
|
|
1423
|
+
return to_numpy_doubles(self.net.Alpha)
|
|
1424
|
+
|
|
1425
|
+
@property
|
|
1426
|
+
def alpha_sum(self) -> float:
|
|
1427
|
+
"""The concentration sum α₀ = Σαᵢ."""
|
|
1428
|
+
return float(self.net.AlphaSum)
|
|
1429
|
+
|
|
1430
|
+
@property
|
|
1431
|
+
def mean(self) -> np.ndarray:
|
|
1432
|
+
"""The mean vector αᵢ/α₀."""
|
|
1433
|
+
return to_numpy_doubles(self.net.Mean)
|
|
1434
|
+
|
|
1435
|
+
@property
|
|
1436
|
+
def variance(self) -> np.ndarray:
|
|
1437
|
+
"""The marginal variances αᵢ(α₀ − αᵢ)/(α₀²(α₀ + 1))."""
|
|
1438
|
+
return to_numpy_doubles(self.net.Variance)
|
|
1439
|
+
|
|
1440
|
+
@property
|
|
1441
|
+
def mode(self) -> np.ndarray:
|
|
1442
|
+
"""The mode (αᵢ − 1)/(α₀ − K); Numerics refuses it unless every αᵢ > 1."""
|
|
1443
|
+
return to_numpy_doubles(self.net.Mode)
|
|
1444
|
+
|
|
1445
|
+
def covariance(self, i: int, j: int) -> float:
|
|
1446
|
+
"""Return one covariance entry (``Covariance(int, int)``).
|
|
1447
|
+
|
|
1448
|
+
Parameters
|
|
1449
|
+
----------
|
|
1450
|
+
i : int
|
|
1451
|
+
Row component index.
|
|
1452
|
+
j : int
|
|
1453
|
+
Column component index.
|
|
1454
|
+
|
|
1455
|
+
Returns
|
|
1456
|
+
-------
|
|
1457
|
+
float
|
|
1458
|
+
Cov(Xᵢ, Xⱼ).
|
|
1459
|
+
"""
|
|
1460
|
+
return float(self.net.Covariance(int(i), int(j)))
|
|
1461
|
+
|
|
1462
|
+
def covariance_matrix(self) -> np.ndarray:
|
|
1463
|
+
"""Return the full covariance matrix (``CovarianceMatrix()``).
|
|
1464
|
+
|
|
1465
|
+
Returns
|
|
1466
|
+
-------
|
|
1467
|
+
numpy.ndarray
|
|
1468
|
+
Array of shape ``(K, K)``.
|
|
1469
|
+
"""
|
|
1470
|
+
return to_numpy_matrix(self.net.CovarianceMatrix())
|
|
1471
|
+
|
|
1472
|
+
def generate_random_values(self, sample_size: int, seed: int = -1) -> np.ndarray:
|
|
1473
|
+
"""Draw a Monte Carlo sample (``GenerateRandomValues(int, int)``).
|
|
1474
|
+
|
|
1475
|
+
Parameters
|
|
1476
|
+
----------
|
|
1477
|
+
sample_size : int
|
|
1478
|
+
Number of points; zero returns an empty ``(0, K)`` array.
|
|
1479
|
+
seed : int, optional
|
|
1480
|
+
Mersenne Twister seed; zero or negative seeds from the clock (not reproducible).
|
|
1481
|
+
|
|
1482
|
+
Returns
|
|
1483
|
+
-------
|
|
1484
|
+
numpy.ndarray
|
|
1485
|
+
Array of shape ``(sample_size, K)``; each row lies on the simplex.
|
|
1486
|
+
"""
|
|
1487
|
+
return to_numpy_matrix(self._generate(int(sample_size), int(seed)))
|
|
1488
|
+
|
|
1489
|
+
@staticmethod
|
|
1490
|
+
def log_multivariate_beta(alpha: Any) -> float:
|
|
1491
|
+
"""Return log B(α) = Σ log Γ(αᵢ) − log Γ(Σαᵢ) (``Dirichlet.LogMultivariateBeta``).
|
|
1492
|
+
|
|
1493
|
+
Parameters
|
|
1494
|
+
----------
|
|
1495
|
+
alpha : array_like
|
|
1496
|
+
Concentration parameters.
|
|
1497
|
+
|
|
1498
|
+
Returns
|
|
1499
|
+
-------
|
|
1500
|
+
float
|
|
1501
|
+
The log of the multivariate beta function.
|
|
1502
|
+
"""
|
|
1503
|
+
return float(_nd().Dirichlet.LogMultivariateBeta(to_net_doubles(alpha)))
|
|
1504
|
+
|
|
1505
|
+
|
|
1506
|
+
_CLASSES: dict[str, type[MultivariateDistribution]] = {
|
|
1507
|
+
"MultivariateNormal": MultivariateNormal,
|
|
1508
|
+
"MultivariateStudentT": MultivariateStudentT,
|
|
1509
|
+
"Multinomial": Multinomial,
|
|
1510
|
+
"BivariateEmpirical": BivariateEmpirical,
|
|
1511
|
+
"Dirichlet": Dirichlet,
|
|
1512
|
+
}
|
|
1513
|
+
|
|
1514
|
+
|
|
1515
|
+
def wrap(net: Any) -> MultivariateDistribution:
|
|
1516
|
+
"""Wrap a .NET multivariate distribution in the matching Python class.
|
|
1517
|
+
|
|
1518
|
+
Parameters
|
|
1519
|
+
----------
|
|
1520
|
+
net : Numerics.Distributions.MultivariateDistribution or IMultivariateDistribution
|
|
1521
|
+
The .NET object.
|
|
1522
|
+
|
|
1523
|
+
Returns
|
|
1524
|
+
-------
|
|
1525
|
+
MultivariateDistribution
|
|
1526
|
+
The specific wrapper class when one exists, otherwise the generic wrapper; the .NET object
|
|
1527
|
+
is shared, not copied.
|
|
1528
|
+
"""
|
|
1529
|
+
concrete = unwrap(net)
|
|
1530
|
+
cls = _CLASSES.get(str(concrete.GetType().Name), MultivariateDistribution)
|
|
1531
|
+
return cls.from_net(concrete)
|
|
1532
|
+
|
|
1533
|
+
|
|
1534
|
+
__all__ = [
|
|
1535
|
+
"BivariateEmpirical", "Dirichlet", "Multinomial", "MultivariateDistribution", "MultivariateDistributionType",
|
|
1536
|
+
"MultivariateNormal", "MultivariateStudentT", "wrap",
|
|
1537
|
+
]
|
|
1538
|
+
|
|
1539
|
+
translate_public(globals())
|