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/copulas.py ADDED
@@ -0,0 +1,910 @@
1
+ """Bivariate copulas from ``Numerics.Distributions.Copulas``.
2
+
3
+ Every class wraps one .NET copula (``.net``). Paired (u, v) arrays cross once into the
4
+ ``(IList<double>, IList<double>)`` overloads of ``BivariateCopula`` where Numerics has them, and
5
+ are evaluated pair by pair with the scalar member otherwise. Parameter estimation
6
+ (``BivariateCopulaEstimation.Estimate``), the factory (``CopulaFactory``) and the XML round trip
7
+ (``ToXElement`` and ``CopulaFactory.CreateCopula(XElement)``) are Numerics calls.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from typing import Any
13
+
14
+ import numpy as np
15
+
16
+ from pynumerics._util import bind, loop, namespace, net_enum, unwrap
17
+ from pynumerics.distributions import FrozenDistribution, UnivariateDistribution
18
+ from pynumerics.distributions import wrap as wrap_univariate
19
+ from pynumerics.enums import NetEnum
20
+ from pynumerics.errors import translate_public
21
+ from pynumerics.marshal import to_net_doubles, to_numpy_doubles, to_numpy_matrix
22
+ from pynumerics.multivariate import _draw, _message
23
+
24
+
25
+ def _nc() -> Any:
26
+ """Return the ``Numerics.Distributions.Copulas`` namespace after starting the runtime."""
27
+ return namespace("Numerics.Distributions.Copulas")
28
+
29
+
30
+ class CopulaType(NetEnum):
31
+ """Copula families (``Numerics.Distributions.Copulas.CopulaType``)."""
32
+
33
+ ALI_MIKHAIL_HAQ = "AliMikhailHaq"
34
+ CLAYTON = "Clayton"
35
+ FRANK = "Frank"
36
+ GUMBEL = "Gumbel"
37
+ JOE = "Joe"
38
+ NORMAL = "Normal"
39
+ STUDENT_T = "StudentT"
40
+ INDEPENDENCE = "Independence"
41
+
42
+
43
+ class CopulaEstimationMethod(NetEnum):
44
+ """Copula estimation methods (``Numerics.Distributions.Copulas.CopulaEstimationMethod``)."""
45
+
46
+ FULL_LIKELIHOOD = "FullLikelihood"
47
+ PSEUDO_LIKELIHOOD = "PseudoLikelihood"
48
+ INFERENCE_FROM_MARGINS = "InferenceFromMargins"
49
+
50
+
51
+ def _pairwise(u: Any, v: Any, copula: Any, name: str) -> Any:
52
+ """Evaluate a (u, v) copula member over broadcast arrays.
53
+
54
+ A scalar pair calls the ``(double, double)`` overload. Arrays cross once into the
55
+ ``(IList<double>, IList<double>)`` overload of ``BivariateCopula`` when the member has one, or
56
+ pair by pair otherwise; every value is the scalar member's. Both overloads are bound once per
57
+ copula object.
58
+ """
59
+ import System # noqa: PLC0415 - the runtime is running (the copula exists)
60
+ from System.Collections.Generic import IList # noqa: PLC0415
61
+
62
+ cache = copula.__dict__.setdefault("_bound", {})
63
+ entry = cache.get(name)
64
+ if entry is None or entry[0] is not copula.net:
65
+ group = getattr(copula.net, name)
66
+ scalar = group.Overloads[System.Double, System.Double]
67
+ try:
68
+ vector = group.Overloads[IList[System.Double], IList[System.Double]]
69
+ except (KeyError, TypeError):
70
+ vector = None
71
+ entry = cache[name] = (copula.net, scalar, vector)
72
+ _, scalar, vector = entry
73
+ uu, vv = np.broadcast_arrays(np.asarray(u, dtype=np.float64), np.asarray(v, dtype=np.float64))
74
+ if uu.ndim == 0:
75
+ return float(scalar(float(uu), float(vv)))
76
+ if vector is not None:
77
+ return to_numpy_doubles(vector(to_net_doubles(uu.ravel()), to_net_doubles(vv.ravel()))).reshape(uu.shape)
78
+ out = np.empty(uu.size, dtype=np.float64)
79
+ for index, (a, b) in enumerate(zip(uu.ravel(), vv.ravel(), strict=True)):
80
+ out[index] = scalar(float(a), float(b))
81
+ return out.reshape(uu.shape)
82
+
83
+
84
+ def _marginal_net(distribution: UnivariateDistribution | None) -> Any:
85
+ """Return the .NET marginal to attach: None, the distribution's object, or a clone of a frozen one.
86
+
87
+ A frozen distribution is read-only, and estimation with the full likelihood refits attached
88
+ marginals in place, so a frozen marginal is attached as a clone.
89
+ """
90
+ if distribution is None:
91
+ return None
92
+ if isinstance(distribution, FrozenDistribution):
93
+ return distribution.net.Clone()
94
+ return distribution.net
95
+
96
+
97
+ class BivariateCopula:
98
+ """A wrapped ``Numerics.Distributions.Copulas.BivariateCopula``.
99
+
100
+ Parameters
101
+ ----------
102
+ net : Numerics.Distributions.Copulas.BivariateCopula
103
+ The .NET copula.
104
+
105
+ Attributes
106
+ ----------
107
+ net : Numerics.Distributions.Copulas.BivariateCopula
108
+ The wrapped .NET object.
109
+ """
110
+
111
+ def __init__(self, net: Any) -> None:
112
+ self.net = unwrap(net)
113
+
114
+ def _attach(self, marginal_x: UnivariateDistribution | None, marginal_y: UnivariateDistribution | None) -> None:
115
+ """Attach the marginals given to a constructor, leaving unset ones untouched."""
116
+ if marginal_x is not None or marginal_y is not None:
117
+ self.set_marginals(marginal_x, marginal_y)
118
+
119
+ @property
120
+ def type(self) -> CopulaType:
121
+ """The copula family (``Type``)."""
122
+ return CopulaType(str(self.net.Type))
123
+
124
+ @property
125
+ def display_name(self) -> str:
126
+ """The Numerics display name."""
127
+ return str(self.net.DisplayName)
128
+
129
+ @property
130
+ def short_display_name(self) -> str:
131
+ """The Numerics short display name."""
132
+ return str(self.net.ShortDisplayName)
133
+
134
+ @property
135
+ def theta(self) -> float:
136
+ """The dependence parameter θ (ρ for the normal and Student-t copulas); settable."""
137
+ return float(self.net.Theta)
138
+
139
+ @theta.setter
140
+ def theta(self, value: float) -> None:
141
+ self.net.Theta = float(value)
142
+
143
+ @property
144
+ def theta_minimum(self) -> float:
145
+ """The smallest admissible θ."""
146
+ return float(self.net.ThetaMinimum)
147
+
148
+ @property
149
+ def theta_maximum(self) -> float:
150
+ """The largest admissible θ."""
151
+ return float(self.net.ThetaMaximum)
152
+
153
+ @property
154
+ def parameter_to_string(self) -> tuple[tuple[str, ...], ...]:
155
+ """The parameter names and values as Numerics formats them, one (name, value) row per parameter."""
156
+ table = self.net.ParameterToString
157
+ return tuple(tuple(str(table[i, j]) for j in range(table.GetLength(1))) for i in range(table.GetLength(0)))
158
+
159
+ @property
160
+ def parameter_name_short_form(self) -> str:
161
+ """The short parameter name (for example θ or ρ)."""
162
+ return str(self.net.ParameterNameShortForm)
163
+
164
+ @property
165
+ def parameter_property_name(self) -> str:
166
+ """The name of the parameter property (``GetParameterPropertyName``)."""
167
+ return str(self.net.GetParameterPropertyName)
168
+
169
+ @property
170
+ def parameters_valid(self) -> bool:
171
+ """Whether the current parameters are valid (``ParametersValid``)."""
172
+ return bool(self.net.ParametersValid)
173
+
174
+ @property
175
+ def upper_tail_dependence(self) -> float:
176
+ """The upper tail dependence coefficient λ_U."""
177
+ return float(self.net.UpperTailDependence)
178
+
179
+ @property
180
+ def lower_tail_dependence(self) -> float:
181
+ """The lower tail dependence coefficient λ_L."""
182
+ return float(self.net.LowerTailDependence)
183
+
184
+ @property
185
+ def number_of_copula_parameters(self) -> int:
186
+ """The number of copula parameters (0, 1, or 2 for the Student-t copula)."""
187
+ return int(self.net.NumberOfCopulaParameters)
188
+
189
+ @property
190
+ def copula_parameters(self) -> np.ndarray:
191
+ """The copula parameters in Numerics order (``GetCopulaParameters``)."""
192
+ return to_numpy_doubles(self.net.GetCopulaParameters)
193
+
194
+ def set_copula_parameters(self, parameters: Any) -> None:
195
+ """Set the copula parameters in Numerics order (``SetCopulaParameters``).
196
+
197
+ Parameters
198
+ ----------
199
+ parameters : array_like
200
+ One value per copula parameter.
201
+ """
202
+ self.net.SetCopulaParameters(to_net_doubles(parameters))
203
+
204
+ def validate_parameter(self, parameter: float) -> str | None:
205
+ """Return why a dependence parameter is invalid, or None when it is valid.
206
+
207
+ Parameters
208
+ ----------
209
+ parameter : float
210
+ Candidate θ.
211
+
212
+ Returns
213
+ -------
214
+ str or None
215
+ The Numerics message of ``ValidateParameter(parameter, false)``, or None.
216
+ """
217
+ return _message(self.net.ValidateParameter(float(parameter), False))
218
+
219
+ def parameter_constraints(self, x: Any, y: Any) -> np.ndarray:
220
+ """Return the search bounds of each copula parameter for a sample (``ParameterConstraints``).
221
+
222
+ Parameters
223
+ ----------
224
+ x : array_like
225
+ Sample of the first variable.
226
+ y : array_like
227
+ Sample of the second variable.
228
+
229
+ Returns
230
+ -------
231
+ numpy.ndarray
232
+ Array of shape ``(number_of_copula_parameters, 2)`` of lower and upper bounds.
233
+ """
234
+ return to_numpy_matrix(self.net.ParameterConstraints(to_net_doubles(x), to_net_doubles(y)))
235
+
236
+ @property
237
+ def marginal_distribution_x(self) -> UnivariateDistribution | None:
238
+ """The marginal of the first variable, or None; settable (a frozen marginal is attached as a clone)."""
239
+ net = self.net.MarginalDistributionX
240
+ return None if net is None else wrap_univariate(net)
241
+
242
+ @marginal_distribution_x.setter
243
+ def marginal_distribution_x(self, value: UnivariateDistribution | None) -> None:
244
+ self.net.MarginalDistributionX = _marginal_net(value)
245
+
246
+ @property
247
+ def marginal_distribution_y(self) -> UnivariateDistribution | None:
248
+ """The marginal of the second variable, or None; settable (a frozen marginal is attached as a clone)."""
249
+ net = self.net.MarginalDistributionY
250
+ return None if net is None else wrap_univariate(net)
251
+
252
+ @marginal_distribution_y.setter
253
+ def marginal_distribution_y(self, value: UnivariateDistribution | None) -> None:
254
+ self.net.MarginalDistributionY = _marginal_net(value)
255
+
256
+ def set_marginals(self, x: UnivariateDistribution | None, y: UnivariateDistribution | None) -> None:
257
+ """Set the marginal distributions used by sampling and the likelihoods.
258
+
259
+ Parameters
260
+ ----------
261
+ x : UnivariateDistribution or None
262
+ Marginal of the first variable.
263
+ y : UnivariateDistribution or None
264
+ Marginal of the second variable.
265
+
266
+ Notes
267
+ -----
268
+ The copula holds the distribution's own .NET object, so estimation with the full
269
+ likelihood refits it in place; a frozen distribution is attached as a clone.
270
+ """
271
+ self.net.MarginalDistributionX = _marginal_net(x)
272
+ self.net.MarginalDistributionY = _marginal_net(y)
273
+
274
+ def pdf(self, u: Any, v: Any) -> Any:
275
+ """Return the copula density c(u, v).
276
+
277
+ Parameters
278
+ ----------
279
+ u : float or array_like
280
+ First uniform margin.
281
+ v : float or array_like
282
+ Second uniform margin, broadcast with ``u``.
283
+
284
+ Returns
285
+ -------
286
+ float or numpy.ndarray
287
+ Densities.
288
+ """
289
+ return _pairwise(u, v, self, "PDF")
290
+
291
+ def log_pdf(self, u: Any, v: Any) -> Any:
292
+ """Return the log copula density log c(u, v) (``LogPDF``).
293
+
294
+ Parameters
295
+ ----------
296
+ u : float or array_like
297
+ First uniform margin.
298
+ v : float or array_like
299
+ Second uniform margin, broadcast with ``u``.
300
+
301
+ Returns
302
+ -------
303
+ float or numpy.ndarray
304
+ Log-densities.
305
+ """
306
+ return _pairwise(u, v, self, "LogPDF")
307
+
308
+ def cdf(self, u: Any, v: Any) -> Any:
309
+ """Return the copula C(u, v).
310
+
311
+ Parameters
312
+ ----------
313
+ u : float or array_like
314
+ First uniform margin.
315
+ v : float or array_like
316
+ Second uniform margin, broadcast with ``u``.
317
+
318
+ Returns
319
+ -------
320
+ float or numpy.ndarray
321
+ Joint non-exceedance probabilities.
322
+ """
323
+ return _pairwise(u, v, self, "CDF")
324
+
325
+ def or_joint_exceedance_probability(self, u: Any, v: Any) -> Any:
326
+ """Return P(U > u or V > v) = 1 − C(u, v) (``ORJointExceedanceProbability``).
327
+
328
+ Parameters
329
+ ----------
330
+ u : float or array_like
331
+ First uniform margin.
332
+ v : float or array_like
333
+ Second uniform margin, broadcast with ``u``.
334
+
335
+ Returns
336
+ -------
337
+ float or numpy.ndarray
338
+ Probabilities that either variable exceeds its threshold.
339
+ """
340
+ return _pairwise(u, v, self, "ORJointExceedanceProbability")
341
+
342
+ def and_joint_exceedance_probability(self, u: Any, v: Any) -> Any:
343
+ """Return P(U > u and V > v) = 1 − u − v + C(u, v) (``ANDJointExceedanceProbability``).
344
+
345
+ Parameters
346
+ ----------
347
+ u : float or array_like
348
+ First uniform margin.
349
+ v : float or array_like
350
+ Second uniform margin, broadcast with ``u``.
351
+
352
+ Returns
353
+ -------
354
+ float or numpy.ndarray
355
+ Probabilities that both variables exceed their thresholds.
356
+ """
357
+ return _pairwise(u, v, self, "ANDJointExceedanceProbability")
358
+
359
+ def conditional_cdf(self, u: Any, v: Any) -> Any:
360
+ """Return the conditional CDF ``ConditionalCDF(u, v)``.
361
+
362
+ Parameters
363
+ ----------
364
+ u : float or array_like
365
+ First uniform margin.
366
+ v : float or array_like
367
+ Second uniform margin, broadcast with ``u``.
368
+
369
+ Returns
370
+ -------
371
+ float or numpy.ndarray
372
+ Conditional probabilities.
373
+ """
374
+ return _pairwise(u, v, self, "ConditionalCDF")
375
+
376
+ def inverse_conditional_cdf(self, u: Any, t: Any) -> Any:
377
+ """Return the inverse conditional CDF ``InverseConditionalCDF(u, t)``.
378
+
379
+ Parameters
380
+ ----------
381
+ u : float or array_like
382
+ Conditioning margin.
383
+ t : float or array_like
384
+ Conditional probability.
385
+
386
+ Returns
387
+ -------
388
+ float or numpy.ndarray
389
+ The second margin v.
390
+ """
391
+ return _pairwise(u, t, self, "InverseConditionalCDF")
392
+
393
+ def inverse_cdf(self, u: float, v: float) -> np.ndarray:
394
+ """Map two independent uniforms to a dependent pair (``InverseCDF(u, v)``).
395
+
396
+ Parameters
397
+ ----------
398
+ u : float
399
+ First independent uniform.
400
+ v : float
401
+ Second independent uniform.
402
+
403
+ Returns
404
+ -------
405
+ numpy.ndarray
406
+ The dependent uniform pair.
407
+ """
408
+ return to_numpy_doubles(self.net.InverseCDF(float(u), float(v)))
409
+
410
+ def generate_random_values(self, sample_size: int, seed: Any = -1) -> np.ndarray:
411
+ """Draw a sample (``GenerateRandomValues``; marginal values when both marginals are set).
412
+
413
+ Parameters
414
+ ----------
415
+ sample_size : int
416
+ Number of pairs.
417
+ seed : int or MersenneTwister, optional
418
+ Seed (zero or negative seeds from the clock, not reproducible) or a generator.
419
+
420
+ Returns
421
+ -------
422
+ numpy.ndarray
423
+ Array of shape ``(sample_size, 2)``.
424
+ """
425
+ return _draw(self.net.GenerateRandomValues, sample_size, seed)
426
+
427
+ def pseudo_log_likelihood(self, x: Any, y: Any) -> float:
428
+ """Return the pseudo log-likelihood Σ log c(xᵢ, yᵢ) (``PseudoLogLikelihood``).
429
+
430
+ Parameters
431
+ ----------
432
+ x : array_like
433
+ Plotting positions of the first variable.
434
+ y : array_like
435
+ Plotting positions of the second variable.
436
+
437
+ Returns
438
+ -------
439
+ float
440
+ The log-likelihood; negative infinity when it is not finite.
441
+ """
442
+ return float(self.net.PseudoLogLikelihood(to_net_doubles(x), to_net_doubles(y)))
443
+
444
+ def ifm_log_likelihood(self, x: Any, y: Any) -> float:
445
+ """Return the inference-from-margins log-likelihood Σ log c(F(xᵢ), G(yᵢ)) (``IFMLogLikelihood``).
446
+
447
+ Parameters
448
+ ----------
449
+ x : array_like
450
+ Sample of the first variable.
451
+ y : array_like
452
+ Sample of the second variable.
453
+
454
+ Returns
455
+ -------
456
+ float
457
+ The log-likelihood with the attached marginals; negative infinity when it is not finite.
458
+ """
459
+ return float(self.net.IFMLogLikelihood(to_net_doubles(x), to_net_doubles(y)))
460
+
461
+ def log_likelihood(self, x: Any, y: Any) -> float:
462
+ """Return the full log-likelihood of copula and marginals (``LogLikelihood``).
463
+
464
+ Parameters
465
+ ----------
466
+ x : array_like
467
+ Sample of the first variable.
468
+ y : array_like
469
+ Sample of the second variable.
470
+
471
+ Returns
472
+ -------
473
+ float
474
+ Σ [log c(F(xᵢ), G(yᵢ)) + log f(xᵢ) + log g(yᵢ)]; negative infinity when it is not finite.
475
+ """
476
+ return float(self.net.LogLikelihood(to_net_doubles(x), to_net_doubles(y)))
477
+
478
+ def estimate(self, x: Any, y: Any,
479
+ method: CopulaEstimationMethod | str = CopulaEstimationMethod.PSEUDO_LIKELIHOOD) -> None:
480
+ """Fit the copula in place (``BivariateCopulaEstimation.Estimate``).
481
+
482
+ Parameters
483
+ ----------
484
+ x : array_like
485
+ Sample of the first variable (plotting positions for the pseudo likelihood).
486
+ y : array_like
487
+ Sample of the second variable (plotting positions for the pseudo likelihood).
488
+ method : CopulaEstimationMethod or str, optional
489
+ Default the pseudo likelihood. Inference from margins needs both marginals attached;
490
+ the full likelihood also refits them, so they must support maximum likelihood.
491
+
492
+ Notes
493
+ -----
494
+ One-parameter copulas are fitted with Brent's method over the parameter constraints and
495
+ the Student-t copula with Nelder-Mead; the full likelihood fits the copula and both
496
+ marginals jointly with Nelder-Mead, restarted once. A zero-parameter copula is unchanged.
497
+ """
498
+ nc = _nc()
499
+ self.net = unwrap(nc.BivariateCopulaEstimation.Estimate(
500
+ self.net, to_net_doubles(x), to_net_doubles(y), net_enum(nc.CopulaEstimationMethod, method)))
501
+
502
+ def clone(self) -> BivariateCopula:
503
+ """Return a deep copy, marginals included (``Clone``).
504
+
505
+ Returns
506
+ -------
507
+ BivariateCopula
508
+ A wrapper of the cloned .NET copula, of the matching Python class.
509
+ """
510
+ return wrap(self.net.Clone())
511
+
512
+ def to_xml(self, include_marginals: bool = False) -> str:
513
+ """Return the copula's ``ToXElement`` as XML text.
514
+
515
+ Parameters
516
+ ----------
517
+ include_marginals : bool, optional
518
+ Also write the attached marginals (``ToXElement(true)``).
519
+
520
+ Returns
521
+ -------
522
+ str
523
+ The element as .NET writes it; :func:`copula_from_xml` reads it back.
524
+ """
525
+ if not include_marginals:
526
+ return str(self.net.ToXElement().ToString())
527
+ import System # noqa: PLC0415 - the runtime is running (self.net exists)
528
+
529
+ return str(self.net.ToXElement.Overloads[System.Boolean](True).ToString())
530
+
531
+
532
+ class ArchimedeanCopula(BivariateCopula):
533
+ """A wrapped ``Numerics.Distributions.Copulas.ArchimedeanCopula`` with its generator φ.
534
+
535
+ Parameters
536
+ ----------
537
+ net : Numerics.Distributions.Copulas.ArchimedeanCopula
538
+ The .NET copula.
539
+
540
+ Notes
541
+ -----
542
+ C(u, v) = φ⁻¹(φ(u) + φ(v)). The generator members accept a scalar or an array, evaluated
543
+ element by element with the bound scalar member.
544
+ """
545
+
546
+ def _generator_member(self, name: str, t: Any) -> Any:
547
+ """Evaluate a scalar generator member, bound once per copula object."""
548
+ cache = self.__dict__.setdefault("_generators", {})
549
+ entry = cache.get(name)
550
+ if entry is None or entry[0] is not self.net:
551
+ entry = cache[name] = (self.net, bind(getattr(self.net, name)))
552
+ return loop(t, entry[1])
553
+
554
+ def generator(self, t: Any) -> Any:
555
+ """Return the generator φ(t) (``Generator``).
556
+
557
+ Parameters
558
+ ----------
559
+ t : float or array_like
560
+ Arguments in (0, 1].
561
+
562
+ Returns
563
+ -------
564
+ float or numpy.ndarray
565
+ The value φ(t).
566
+ """
567
+ return self._generator_member("Generator", t)
568
+
569
+ def generator_inverse(self, t: Any) -> Any:
570
+ """Return the inverse generator φ⁻¹(t) (``GeneratorInverse``).
571
+
572
+ Parameters
573
+ ----------
574
+ t : float or array_like
575
+ Arguments in [0, ∞).
576
+
577
+ Returns
578
+ -------
579
+ float or numpy.ndarray
580
+ The value φ⁻¹(t).
581
+ """
582
+ return self._generator_member("GeneratorInverse", t)
583
+
584
+ def generator_prime(self, t: Any) -> Any:
585
+ """Return the first derivative φ′(t) (``GeneratorPrime``).
586
+
587
+ Parameters
588
+ ----------
589
+ t : float or array_like
590
+ Arguments in (0, 1].
591
+
592
+ Returns
593
+ -------
594
+ float or numpy.ndarray
595
+ The value φ′(t).
596
+ """
597
+ return self._generator_member("GeneratorPrime", t)
598
+
599
+ def generator_prime2(self, t: Any) -> Any:
600
+ """Return the second derivative φ″(t) (``GeneratorPrime2``).
601
+
602
+ Parameters
603
+ ----------
604
+ t : float or array_like
605
+ Arguments in (0, 1].
606
+
607
+ Returns
608
+ -------
609
+ float or numpy.ndarray
610
+ The value φ″(t).
611
+ """
612
+ return self._generator_member("GeneratorPrime2", t)
613
+
614
+ def generator_prime_inverse(self, t: Any) -> Any:
615
+ """Return the inverse of the first derivative, (φ′)⁻¹(t) (``GeneratorPrimeInverse``).
616
+
617
+ Parameters
618
+ ----------
619
+ t : float or array_like
620
+ Values of φ′.
621
+
622
+ Returns
623
+ -------
624
+ float or numpy.ndarray
625
+ The argument whose φ′ is ``t``.
626
+ """
627
+ return self._generator_member("GeneratorPrimeInverse", t)
628
+
629
+ def set_theta_from_tau(self, x: Any, y: Any) -> None:
630
+ """Set θ by inverting Kendall's tau of a sample (``SetThetaFromTau``).
631
+
632
+ Parameters
633
+ ----------
634
+ x : array_like
635
+ Sample of the first variable.
636
+ y : array_like
637
+ Sample of the second variable.
638
+ """
639
+ self.net.SetThetaFromTau(to_net_doubles(x), to_net_doubles(y))
640
+
641
+
642
+ def _build(name: str, parameters: tuple[Any, ...]) -> Any:
643
+ """Construct a .NET copula with its parameters, or with the Numerics defaults when none are given."""
644
+ net_type = getattr(_nc(), name)
645
+ return net_type(*(float(p) for p in parameters)) if parameters else net_type()
646
+
647
+
648
+ class NormalCopula(BivariateCopula):
649
+ """Gaussian copula.
650
+
651
+ Parameters
652
+ ----------
653
+ rho : float, optional
654
+ Correlation ρ; default the Numerics default.
655
+ marginal_x : UnivariateDistribution, optional
656
+ Marginal of the first variable.
657
+ marginal_y : UnivariateDistribution, optional
658
+ Marginal of the second variable.
659
+ """
660
+
661
+ def __init__(self, rho: float | None = None, marginal_x: UnivariateDistribution | None = None,
662
+ marginal_y: UnivariateDistribution | None = None) -> None:
663
+ super().__init__(_build("NormalCopula", () if rho is None else (rho,)))
664
+ self._attach(marginal_x, marginal_y)
665
+
666
+
667
+ class StudentTCopula(BivariateCopula):
668
+ """Student-t copula.
669
+
670
+ Parameters
671
+ ----------
672
+ rho : float, optional
673
+ Correlation ρ. Give both ``rho`` and ``degrees_of_freedom``, or neither for the Numerics defaults.
674
+ degrees_of_freedom : float, optional
675
+ Degrees of freedom ν.
676
+ marginal_x : UnivariateDistribution, optional
677
+ Marginal of the first variable.
678
+ marginal_y : UnivariateDistribution, optional
679
+ Marginal of the second variable.
680
+ """
681
+
682
+ def __init__(self, rho: float | None = None, degrees_of_freedom: float | None = None,
683
+ marginal_x: UnivariateDistribution | None = None,
684
+ marginal_y: UnivariateDistribution | None = None) -> None:
685
+ if (rho is None) != (degrees_of_freedom is None):
686
+ raise TypeError("Give both rho and degrees_of_freedom, or neither.")
687
+ parameters = () if rho is None else (rho, degrees_of_freedom)
688
+ super().__init__(_build("StudentTCopula", parameters))
689
+ self._attach(marginal_x, marginal_y)
690
+
691
+ @property
692
+ def degrees_of_freedom(self) -> float:
693
+ """The degrees of freedom ν; settable."""
694
+ return float(self.net.DegreesOfFreedom)
695
+
696
+ @degrees_of_freedom.setter
697
+ def degrees_of_freedom(self, value: float) -> None:
698
+ self.net.DegreesOfFreedom = float(value)
699
+
700
+ def validate_parameters(self, rho: float, degrees_of_freedom: float) -> str | None:
701
+ """Return why a correlation and degrees of freedom are invalid, or None when they are valid.
702
+
703
+ Parameters
704
+ ----------
705
+ rho : float
706
+ Candidate correlation.
707
+ degrees_of_freedom : float
708
+ Candidate degrees of freedom.
709
+
710
+ Returns
711
+ -------
712
+ str or None
713
+ The Numerics message of ``ValidateParameters(rho, nu, false)``, or None.
714
+ """
715
+ return _message(self.net.ValidateParameters(float(rho), float(degrees_of_freedom), False))
716
+
717
+
718
+ class ClaytonCopula(ArchimedeanCopula):
719
+ """Clayton copula.
720
+
721
+ Parameters
722
+ ----------
723
+ theta : float, optional
724
+ Dependence parameter θ; default the Numerics default.
725
+ marginal_x : UnivariateDistribution, optional
726
+ Marginal of the first variable.
727
+ marginal_y : UnivariateDistribution, optional
728
+ Marginal of the second variable.
729
+ """
730
+
731
+ def __init__(self, theta: float | None = None, marginal_x: UnivariateDistribution | None = None,
732
+ marginal_y: UnivariateDistribution | None = None) -> None:
733
+ super().__init__(_build("ClaytonCopula", () if theta is None else (theta,)))
734
+ self._attach(marginal_x, marginal_y)
735
+
736
+
737
+ class GumbelCopula(ArchimedeanCopula):
738
+ """Gumbel copula.
739
+
740
+ Parameters
741
+ ----------
742
+ theta : float, optional
743
+ Dependence parameter θ ≥ 1; default the Numerics default.
744
+ marginal_x : UnivariateDistribution, optional
745
+ Marginal of the first variable.
746
+ marginal_y : UnivariateDistribution, optional
747
+ Marginal of the second variable.
748
+ """
749
+
750
+ def __init__(self, theta: float | None = None, marginal_x: UnivariateDistribution | None = None,
751
+ marginal_y: UnivariateDistribution | None = None) -> None:
752
+ super().__init__(_build("GumbelCopula", () if theta is None else (theta,)))
753
+ self._attach(marginal_x, marginal_y)
754
+
755
+
756
+ class FrankCopula(ArchimedeanCopula):
757
+ """Frank copula.
758
+
759
+ Parameters
760
+ ----------
761
+ theta : float, optional
762
+ Dependence parameter θ ≠ 0; default the Numerics default.
763
+ marginal_x : UnivariateDistribution, optional
764
+ Marginal of the first variable.
765
+ marginal_y : UnivariateDistribution, optional
766
+ Marginal of the second variable.
767
+ """
768
+
769
+ def __init__(self, theta: float | None = None, marginal_x: UnivariateDistribution | None = None,
770
+ marginal_y: UnivariateDistribution | None = None) -> None:
771
+ super().__init__(_build("FrankCopula", () if theta is None else (theta,)))
772
+ self._attach(marginal_x, marginal_y)
773
+
774
+
775
+ class JoeCopula(ArchimedeanCopula):
776
+ """Joe copula.
777
+
778
+ Parameters
779
+ ----------
780
+ theta : float, optional
781
+ Dependence parameter θ ≥ 1; default the Numerics default.
782
+ marginal_x : UnivariateDistribution, optional
783
+ Marginal of the first variable.
784
+ marginal_y : UnivariateDistribution, optional
785
+ Marginal of the second variable.
786
+ """
787
+
788
+ def __init__(self, theta: float | None = None, marginal_x: UnivariateDistribution | None = None,
789
+ marginal_y: UnivariateDistribution | None = None) -> None:
790
+ super().__init__(_build("JoeCopula", () if theta is None else (theta,)))
791
+ self._attach(marginal_x, marginal_y)
792
+
793
+
794
+ class AMHCopula(ArchimedeanCopula):
795
+ """Ali-Mikhail-Haq copula.
796
+
797
+ Parameters
798
+ ----------
799
+ theta : float, optional
800
+ Dependence parameter θ in [−1, 1); default the Numerics default.
801
+ marginal_x : UnivariateDistribution, optional
802
+ Marginal of the first variable.
803
+ marginal_y : UnivariateDistribution, optional
804
+ Marginal of the second variable.
805
+ """
806
+
807
+ def __init__(self, theta: float | None = None, marginal_x: UnivariateDistribution | None = None,
808
+ marginal_y: UnivariateDistribution | None = None) -> None:
809
+ super().__init__(_build("AMHCopula", () if theta is None else (theta,)))
810
+ self._attach(marginal_x, marginal_y)
811
+
812
+
813
+ class IndependenceCopula(BivariateCopula):
814
+ """Independence copula C(u, v) = u·v.
815
+
816
+ Parameters
817
+ ----------
818
+ marginal_x : UnivariateDistribution, optional
819
+ Marginal of the first variable.
820
+ marginal_y : UnivariateDistribution, optional
821
+ Marginal of the second variable.
822
+ """
823
+
824
+ def __init__(self, marginal_x: UnivariateDistribution | None = None,
825
+ marginal_y: UnivariateDistribution | None = None) -> None:
826
+ super().__init__(_build("IndependenceCopula", ()))
827
+ self._attach(marginal_x, marginal_y)
828
+
829
+
830
+ _CLASSES: dict[str, type[BivariateCopula]] = {
831
+ "NormalCopula": NormalCopula, "StudentTCopula": StudentTCopula, "ClaytonCopula": ClaytonCopula,
832
+ "GumbelCopula": GumbelCopula, "FrankCopula": FrankCopula, "JoeCopula": JoeCopula, "AMHCopula": AMHCopula,
833
+ "IndependenceCopula": IndependenceCopula,
834
+ }
835
+
836
+
837
+ def wrap(net: Any) -> BivariateCopula:
838
+ """Wrap a .NET bivariate copula in the matching Python class.
839
+
840
+ Parameters
841
+ ----------
842
+ net : Numerics.Distributions.Copulas.BivariateCopula or IBivariateCopula
843
+ The .NET object.
844
+
845
+ Returns
846
+ -------
847
+ BivariateCopula
848
+ The specific wrapper class when one exists, otherwise the generic wrapper; the .NET object
849
+ is shared, not copied.
850
+ """
851
+ concrete = unwrap(net)
852
+ cls = _CLASSES.get(str(concrete.GetType().Name), BivariateCopula)
853
+ instance = cls.__new__(cls)
854
+ BivariateCopula.__init__(instance, concrete)
855
+ return instance
856
+
857
+
858
+ def create_copula(copula_type: CopulaType | str) -> BivariateCopula:
859
+ """Create a copula of a family with its default parameters (``CopulaFactory.CreateCopula``).
860
+
861
+ Parameters
862
+ ----------
863
+ copula_type : CopulaType or str
864
+ The family.
865
+
866
+ Returns
867
+ -------
868
+ BivariateCopula
869
+ The typed copula.
870
+ """
871
+ nc = _nc()
872
+ return wrap(nc.CopulaFactory.CreateCopula(net_enum(nc.CopulaType, copula_type)))
873
+
874
+
875
+ def copula_from_xml(xml: str, include_marginals: bool = False) -> BivariateCopula:
876
+ """Rebuild a copula from :meth:`BivariateCopula.to_xml` text (``CopulaFactory.CreateCopula(XElement)``).
877
+
878
+ Parameters
879
+ ----------
880
+ xml : str
881
+ The serialized ``Copula`` element.
882
+ include_marginals : bool, optional
883
+ Also restore the serialized marginals (``CreateCopula(XElement, bool)``).
884
+
885
+ Returns
886
+ -------
887
+ BivariateCopula
888
+ The validated typed copula.
889
+
890
+ Raises
891
+ ------
892
+ ValueError
893
+ If the element is malformed or its parameters do not define a valid copula.
894
+ """
895
+ nc = _nc()
896
+ element = namespace("System.Xml.Linq").XElement.Parse(str(xml))
897
+ if not include_marginals:
898
+ return wrap(nc.CopulaFactory.CreateCopula(element))
899
+ import System # noqa: PLC0415 - the runtime is running
900
+
901
+ return wrap(nc.CopulaFactory.CreateCopula.Overloads[type(element), System.Boolean](element, True))
902
+
903
+
904
+ __all__ = [
905
+ "AMHCopula", "ArchimedeanCopula", "BivariateCopula", "ClaytonCopula", "CopulaEstimationMethod", "CopulaType",
906
+ "FrankCopula", "GumbelCopula", "IndependenceCopula", "JoeCopula", "NormalCopula", "StudentTCopula",
907
+ "copula_from_xml", "create_copula", "wrap",
908
+ ]
909
+
910
+ translate_public(globals())