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
@@ -0,0 +1,1142 @@
1
+ """Univariate functions and link functions from ``Numerics.Functions``.
2
+
3
+ :class:`LinearFunction`, :class:`PowerFunction`, :class:`SegmentedPowerFunction`,
4
+ :class:`TabularFunction` and :class:`CompositeFunction` wrap the ``IUnivariateFunction``
5
+ implementations (rating curves and other deterministic or stochastic transfer functions);
6
+ :class:`EnsembleFunction` holds an ensemble of parameter sets for one template.
7
+ :class:`IdentityLink`, :class:`LogLink`, :class:`LogitLink`, :class:`ProbitLink`,
8
+ :class:`ComplementaryLogLogLink`, :class:`FisherZLink` and :class:`YeoJohnsonLink` wrap the
9
+ ``ILinkFunction`` implementations, and :class:`LinkController` holds one link per parameter.
10
+
11
+ An array argument crosses once, through the list overloads ``UnivariateFunctionExtensions`` and
12
+ ``LinkFunctionExtensions``, which give the scalar members' values bit for bit.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ from collections.abc import Sequence
18
+ from typing import Any
19
+
20
+ import numpy as np
21
+
22
+ from pynumerics._util import evaluate, namespace, net_enum, unwrap
23
+ from pynumerics.data import UncertainOrderedPairedData, parse_xml
24
+ from pynumerics.enums import Extrapolation, NetEnum, Transform
25
+ from pynumerics.errors import translate_public
26
+ from pynumerics.marshal import to_net_doubles, to_numpy_doubles, to_numpy_matrix
27
+
28
+
29
+ def _functions() -> Any:
30
+ """Return the ``Numerics.Functions`` namespace after starting the runtime."""
31
+ return namespace("Numerics.Functions")
32
+
33
+
34
+ _LIST_OVERLOADS: dict[tuple[str, str], Any] = {}
35
+
36
+
37
+ def _apply(net: Any, member: str, extensions: str, values: Any) -> Any:
38
+ """Evaluate a scalar member of ``net`` on a scalar or an array, the array through its list overload.
39
+
40
+ The list overload is the static ``extensions.member(net, IList<double>)``, which calls the scalar
41
+ member once per element, so both give the scalar member's values bit for bit.
42
+ """
43
+ key = (extensions, member)
44
+ vector = _LIST_OVERLOADS.get(key)
45
+ if vector is None:
46
+ vector = _LIST_OVERLOADS[key] = getattr(getattr(_functions(), extensions), member)
47
+ return evaluate(values, getattr(net, member), lambda array: vector(net, array))
48
+
49
+
50
+ class FunctionType(NetEnum):
51
+ """Univariate function families (``Numerics.Functions.UnivariateFunctionType``)."""
52
+
53
+ LINEAR = "Linear"
54
+ POWER = "Power"
55
+ TABULAR = "Tabular"
56
+ SEGMENTED_POWER = "SegmentedPower"
57
+ COMPOSITE = "Composite"
58
+
59
+
60
+ class CompositeMode(NetEnum):
61
+ """How a :class:`CompositeFunction` combines its children (``Numerics.Functions.CompositeFunctionMode``)."""
62
+
63
+ WEIGHTED_AVERAGE = "WeightedAverage"
64
+ MIXTURE = "Mixture"
65
+
66
+
67
+ class LinkFunction:
68
+ """A link function ``eta = g(x)`` (``Numerics.Functions.ILinkFunction``).
69
+
70
+ Parameters
71
+ ----------
72
+ net : Numerics.Functions.ILinkFunction
73
+ The .NET link to wrap; the subclasses construct their own.
74
+
75
+ Attributes
76
+ ----------
77
+ net : Numerics.Functions.ILinkFunction
78
+ The wrapped .NET object.
79
+ """
80
+
81
+ def __init__(self, net: Any) -> None:
82
+ self.net = unwrap(net)
83
+
84
+ def link(self, x: Any) -> Any:
85
+ """Map values to the link space (``Link``).
86
+
87
+ Parameters
88
+ ----------
89
+ x : float or array_like
90
+ Values in the natural space.
91
+
92
+ Returns
93
+ -------
94
+ float or numpy.ndarray
95
+ ``g(x)``, shaped like ``x``.
96
+ """
97
+ return _apply(self.net, "Link", "LinkFunctionExtensions", x)
98
+
99
+ def inverse_link(self, eta: Any) -> Any:
100
+ """Map link-space values back to the natural space (``InverseLink``).
101
+
102
+ Parameters
103
+ ----------
104
+ eta : float or array_like
105
+ Values in the link space.
106
+
107
+ Returns
108
+ -------
109
+ float or numpy.ndarray
110
+ ``g^-1(eta)``, shaped like ``eta``.
111
+ """
112
+ return _apply(self.net, "InverseLink", "LinkFunctionExtensions", eta)
113
+
114
+ def d_link(self, x: Any) -> Any:
115
+ """Return the derivative ``dg/dx`` (``DLink``).
116
+
117
+ Parameters
118
+ ----------
119
+ x : float or array_like
120
+ Values in the natural space.
121
+
122
+ Returns
123
+ -------
124
+ float or numpy.ndarray
125
+ The derivative, shaped like ``x``.
126
+ """
127
+ return _apply(self.net, "DLink", "LinkFunctionExtensions", x)
128
+
129
+ def to_xml(self) -> str:
130
+ """Return the link's ``ToXElement()`` as XML text.
131
+
132
+ Returns
133
+ -------
134
+ str
135
+ The element as .NET writes it.
136
+ """
137
+ return str(self.net.ToXElement().ToString())
138
+
139
+
140
+ class IdentityLink(LinkFunction):
141
+ """The identity link ``g(x) = x`` (``Numerics.Functions.IdentityLink``)."""
142
+
143
+ def __init__(self) -> None:
144
+ super().__init__(_functions().IdentityLink())
145
+
146
+
147
+ class LogLink(LinkFunction):
148
+ """The log link ``g(x) = ln x`` for positive parameters (``Numerics.Functions.LogLink``)."""
149
+
150
+ def __init__(self) -> None:
151
+ super().__init__(_functions().LogLink())
152
+
153
+
154
+ class LogitLink(LinkFunction):
155
+ """The logit link ``g(x) = ln(x / (1 - x))`` for parameters in (0, 1) (``Numerics.Functions.LogitLink``)."""
156
+
157
+ def __init__(self) -> None:
158
+ super().__init__(_functions().LogitLink())
159
+
160
+
161
+ class ProbitLink(LinkFunction):
162
+ """The probit link ``g(x) = Phi^-1(x)`` for parameters in (0, 1) (``Numerics.Functions.ProbitLink``)."""
163
+
164
+ def __init__(self) -> None:
165
+ super().__init__(_functions().ProbitLink())
166
+
167
+
168
+ class ComplementaryLogLogLink(LinkFunction):
169
+ """The complementary log-log link ``g(x) = ln(-ln(1 - x))`` (``ComplementaryLogLogLink``)."""
170
+
171
+ def __init__(self) -> None:
172
+ super().__init__(_functions().ComplementaryLogLogLink())
173
+
174
+
175
+ class FisherZLink(LinkFunction):
176
+ """The Fisher z link ``g(x) = atanh(x)`` for correlations in (-1, 1) (``Numerics.Functions.FisherZLink``)."""
177
+
178
+ def __init__(self) -> None:
179
+ super().__init__(_functions().FisherZLink())
180
+
181
+
182
+ class YeoJohnsonLink(LinkFunction):
183
+ """The Yeo-Johnson power link (``Numerics.Functions.YeoJohnsonLink``).
184
+
185
+ Parameters
186
+ ----------
187
+ lambda_ : float, optional
188
+ The transformation parameter; default 1 (the identity).
189
+ values : array_like, optional
190
+ Representative values (at least two, finite) to fit lambda to by maximum likelihood
191
+ (``YeoJohnson.FitLambda``) instead of giving ``lambda_``.
192
+ """
193
+
194
+ def __init__(self, lambda_: float = 1.0, values: Any = None) -> None:
195
+ functions = _functions()
196
+ if values is not None:
197
+ super().__init__(functions.YeoJohnsonLink(to_net_doubles(values)))
198
+ else:
199
+ super().__init__(functions.YeoJohnsonLink(float(lambda_)))
200
+
201
+ @property
202
+ def lambda_(self) -> float:
203
+ """The transformation parameter."""
204
+ return float(self.net.Lambda)
205
+
206
+
207
+ _LINK_CLASSES: dict[str, type[LinkFunction]] = {
208
+ cls.__name__: cls for cls in (IdentityLink, LogLink, LogitLink, ProbitLink, ComplementaryLogLogLink,
209
+ FisherZLink, YeoJohnsonLink)}
210
+
211
+
212
+ def wrap_link(net: Any) -> LinkFunction | None:
213
+ """Wrap a .NET link function in the matching Python class.
214
+
215
+ Parameters
216
+ ----------
217
+ net : Numerics.Functions.ILinkFunction or None
218
+ The .NET link.
219
+
220
+ Returns
221
+ -------
222
+ LinkFunction or None
223
+ The specific wrapper class (the generic :class:`LinkFunction` for an unknown type), or
224
+ None for a null link.
225
+ """
226
+ if net is None:
227
+ return None
228
+ concrete = unwrap(net)
229
+ cls = _LINK_CLASSES.get(str(concrete.GetType().Name), LinkFunction)
230
+ instance = cls.__new__(cls)
231
+ LinkFunction.__init__(instance, concrete)
232
+ return instance
233
+
234
+
235
+ def create_link(link_type: str) -> LinkFunction:
236
+ """Create a link function by type name (``LinkFunctionFactory.Create``).
237
+
238
+ Parameters
239
+ ----------
240
+ link_type : str
241
+ A ``LinkFunctionType`` member name: ``"identity"``, ``"log"``, ``"logit"``, ``"probit"``,
242
+ ``"complementary_log_log"``, ``"yeo_johnson"`` or ``"fisher_z"``.
243
+
244
+ Returns
245
+ -------
246
+ LinkFunction
247
+ The wrapped link (a Yeo-Johnson link has lambda 1).
248
+ """
249
+ functions = _functions()
250
+ return wrap_link(functions.LinkFunctionFactory.Create(net_enum(functions.LinkFunctionType, link_type)))
251
+
252
+
253
+ def link_from_xml(xml: str) -> LinkFunction:
254
+ """Restore a link function from its XML (``LinkFunctionFactory.CreateFromXElement``).
255
+
256
+ Parameters
257
+ ----------
258
+ xml : str
259
+ The text :meth:`LinkFunction.to_xml` returns.
260
+
261
+ Returns
262
+ -------
263
+ LinkFunction
264
+ The wrapped link.
265
+ """
266
+ return wrap_link(_functions().LinkFunctionFactory.CreateFromXElement(parse_xml(xml)))
267
+
268
+
269
+ def _net_link(link: Any) -> Any:
270
+ """Return the .NET link for a wrapper, a ``LinkFunctionType`` name, a .NET link or None."""
271
+ if link is None:
272
+ return None
273
+ if isinstance(link, LinkFunction):
274
+ return link.net
275
+ if isinstance(link, str):
276
+ return create_link(link).net
277
+ return link
278
+
279
+
280
+ class LinkController:
281
+ """Per-parameter link functions (``Numerics.Functions.LinkController``).
282
+
283
+ Maps parameters to and from the link space, with the Jacobian, the log-determinant of the
284
+ inverse-link Jacobian, indexing and the XML round trip.
285
+
286
+ Parameters
287
+ ----------
288
+ links : sequence of LinkFunction, str or None
289
+ One link per parameter: a :class:`LinkFunction`, a ``LinkFunctionType`` member name, or
290
+ None for a parameter without a link.
291
+
292
+ Attributes
293
+ ----------
294
+ net : Numerics.Functions.LinkController
295
+ The wrapped .NET object.
296
+ """
297
+
298
+ def __init__(self, links: Sequence[Any]) -> None:
299
+ functions = _functions()
300
+ import System # noqa: PLC0415 - the runtime is running
301
+
302
+ self.net = functions.LinkController(System.Array[functions.ILinkFunction]([_net_link(v) for v in links]))
303
+
304
+ @classmethod
305
+ def _from_net(cls, net: Any) -> LinkController:
306
+ """Wrap an existing .NET controller."""
307
+ instance = cls.__new__(cls)
308
+ instance.net = net
309
+ return instance
310
+
311
+ @classmethod
312
+ def for_location_scale_shape(cls, location: Any = None, scale: Any = None, shape: Any = None) -> LinkController:
313
+ """Return a three-parameter controller (``ForLocationScaleShape``).
314
+
315
+ Parameters
316
+ ----------
317
+ location, scale, shape : LinkFunction, str or None, optional
318
+ The link of each parameter; None leaves the parameter unlinked.
319
+
320
+ Returns
321
+ -------
322
+ LinkController
323
+ The controller.
324
+ """
325
+ return cls._from_net(_functions().LinkController.ForLocationScaleShape(
326
+ _net_link(location), _net_link(scale), _net_link(shape)))
327
+
328
+ @classmethod
329
+ def from_xml(cls, xml: str) -> LinkController:
330
+ """Restore a controller from its XML (the ``XElement`` constructor).
331
+
332
+ Parameters
333
+ ----------
334
+ xml : str
335
+ The text :meth:`to_xml` returns.
336
+
337
+ Returns
338
+ -------
339
+ LinkController
340
+ The controller.
341
+ """
342
+ return cls._from_net(_functions().LinkController(parse_xml(xml)))
343
+
344
+ def __len__(self) -> int:
345
+ return int(self.net.Count)
346
+
347
+ @property
348
+ def count(self) -> int:
349
+ """The number of parameter slots."""
350
+ return int(self.net.Count)
351
+
352
+ def __getitem__(self, index: int) -> LinkFunction | None:
353
+ return wrap_link(self.net[int(index)])
354
+
355
+ @property
356
+ def links(self) -> list[LinkFunction | None]:
357
+ """The link of each parameter (None where unlinked)."""
358
+ return [wrap_link(link) for link in self.net.Links]
359
+
360
+ def link(self, x: Any) -> np.ndarray:
361
+ """Map parameters to the link space (``Link``).
362
+
363
+ Parameters
364
+ ----------
365
+ x : array_like
366
+ Parameters, one per link.
367
+
368
+ Returns
369
+ -------
370
+ numpy.ndarray
371
+ The linked values.
372
+ """
373
+ return to_numpy_doubles(self.net.Link(to_net_doubles(x)))
374
+
375
+ def inverse_link(self, eta: Any) -> np.ndarray:
376
+ """Map link-space values back to parameters (``InverseLink``).
377
+
378
+ Parameters
379
+ ----------
380
+ eta : array_like
381
+ Link-space values, one per link.
382
+
383
+ Returns
384
+ -------
385
+ numpy.ndarray
386
+ The parameters.
387
+ """
388
+ return to_numpy_doubles(self.net.InverseLink(to_net_doubles(eta)))
389
+
390
+ def link_jacobian(self, x: Any) -> np.ndarray:
391
+ """Return the diagonal Jacobian d(eta)/d(x) at the parameters (``LinkJacobian``).
392
+
393
+ Parameters
394
+ ----------
395
+ x : array_like
396
+ Parameters, one per link.
397
+
398
+ Returns
399
+ -------
400
+ numpy.ndarray
401
+ The square Jacobian matrix.
402
+ """
403
+ return to_numpy_matrix(self.net.LinkJacobian(to_net_doubles(x)).ToArray())
404
+
405
+ def log_det_jacobian(self, phi: Any) -> float:
406
+ """Return ``log |d theta / d phi|`` of the inverse link at link-space values (``LogDetJacobian``).
407
+
408
+ Parameters
409
+ ----------
410
+ phi : array_like
411
+ Link-space values, one per parameter.
412
+
413
+ Returns
414
+ -------
415
+ float
416
+ The log-determinant, the change-of-variables term of a prior in the link space.
417
+ """
418
+ return float(self.net.LogDetJacobian(to_net_doubles(phi)))
419
+
420
+ def to_xml(self) -> str:
421
+ """Return the controller's ``ToXElement()`` as XML text.
422
+
423
+ Returns
424
+ -------
425
+ str
426
+ The element as .NET writes it.
427
+ """
428
+ return str(self.net.ToXElement().ToString())
429
+
430
+
431
+ class UnivariateFunction:
432
+ """A deterministic or stochastic function ``y = f(x)`` (``Numerics.Functions.IUnivariateFunction``).
433
+
434
+ A stochastic function evaluates at :attr:`confidence_level` of its residual; with the level
435
+ outside [0, 1] or :attr:`is_deterministic` set, it evaluates the mean curve.
436
+
437
+ Parameters
438
+ ----------
439
+ net : Numerics.Functions.IUnivariateFunction
440
+ The .NET function to wrap; the subclasses construct their own.
441
+
442
+ Attributes
443
+ ----------
444
+ net : Numerics.Functions.IUnivariateFunction
445
+ The wrapped .NET object.
446
+ """
447
+
448
+ def __init__(self, net: Any) -> None:
449
+ self.net = unwrap(net)
450
+
451
+ @property
452
+ def number_of_parameters(self) -> int:
453
+ """The length of the parameter vector."""
454
+ return int(self.net.NumberOfParameters)
455
+
456
+ @property
457
+ def parameters_valid(self) -> bool:
458
+ """Whether the current parameters are valid."""
459
+ return bool(self.net.ParametersValid)
460
+
461
+ @property
462
+ def minimum(self) -> float:
463
+ """The lower bound of the support (settable where the function allows it)."""
464
+ return float(self.net.Minimum)
465
+
466
+ @minimum.setter
467
+ def minimum(self, value: float) -> None:
468
+ self.net.Minimum = float(value)
469
+
470
+ @property
471
+ def maximum(self) -> float:
472
+ """The upper bound of the support."""
473
+ return float(self.net.Maximum)
474
+
475
+ @maximum.setter
476
+ def maximum(self, value: float) -> None:
477
+ self.net.Maximum = float(value)
478
+
479
+ @property
480
+ def minimum_of_parameters(self) -> np.ndarray:
481
+ """The lower bound of each parameter."""
482
+ return to_numpy_doubles(self.net.MinimumOfParameters)
483
+
484
+ @property
485
+ def maximum_of_parameters(self) -> np.ndarray:
486
+ """The upper bound of each parameter."""
487
+ return to_numpy_doubles(self.net.MaximumOfParameters)
488
+
489
+ @property
490
+ def is_deterministic(self) -> bool:
491
+ """Whether the function ignores its residual."""
492
+ return bool(self.net.IsDeterministic)
493
+
494
+ @is_deterministic.setter
495
+ def is_deterministic(self, value: bool) -> None:
496
+ self.net.IsDeterministic = bool(value)
497
+
498
+ @property
499
+ def confidence_level(self) -> float:
500
+ """The residual quantile evaluated; outside [0, 1] (default -1) the mean curve."""
501
+ return float(self.net.ConfidenceLevel)
502
+
503
+ @confidence_level.setter
504
+ def confidence_level(self, value: float) -> None:
505
+ self.net.ConfidenceLevel = float(value)
506
+
507
+ def set_parameters(self, parameters: Any) -> None:
508
+ """Set the parameter vector (``SetParameters``); invalid values are recorded, not refused.
509
+
510
+ Parameters
511
+ ----------
512
+ parameters : array_like
513
+ The parameters in Numerics order.
514
+ """
515
+ self.net.SetParameters(to_net_doubles(parameters))
516
+
517
+ def validate_parameters(self, parameters: Any, throw: bool = False) -> str | None:
518
+ """Check a parameter vector (``ValidateParameters``).
519
+
520
+ Parameters
521
+ ----------
522
+ parameters : array_like
523
+ The parameters in Numerics order.
524
+ throw : bool, optional
525
+ Whether to raise instead of returning the message; default False.
526
+
527
+ Returns
528
+ -------
529
+ str or None
530
+ The validation message, or None when the parameters are valid.
531
+
532
+ Raises
533
+ ------
534
+ ValueError
535
+ If ``throw`` is True and the parameters are invalid.
536
+ """
537
+ error = self.net.ValidateParameters(to_net_doubles(parameters), bool(throw))
538
+ return None if error is None else str(error.Message)
539
+
540
+ def function(self, x: Any) -> Any:
541
+ """Evaluate ``y = f(x)`` (``Function``).
542
+
543
+ Parameters
544
+ ----------
545
+ x : float or array_like
546
+ Arguments.
547
+
548
+ Returns
549
+ -------
550
+ float or numpy.ndarray
551
+ The function values, shaped like ``x``.
552
+ """
553
+ return _apply(self.net, "Function", "UnivariateFunctionExtensions", x)
554
+
555
+ def inverse_function(self, y: Any) -> Any:
556
+ """Evaluate ``x = f^-1(y)`` (``InverseFunction``).
557
+
558
+ Parameters
559
+ ----------
560
+ y : float or array_like
561
+ Function values.
562
+
563
+ Returns
564
+ -------
565
+ float or numpy.ndarray
566
+ The arguments, shaped like ``y``.
567
+ """
568
+ return _apply(self.net, "InverseFunction", "UnivariateFunctionExtensions", y)
569
+
570
+ def __call__(self, x: Any) -> Any:
571
+ return self.function(x)
572
+
573
+ def to_xml(self) -> str:
574
+ """Return the function's ``ToXElement()`` as XML text.
575
+
576
+ Returns
577
+ -------
578
+ str
579
+ The element as .NET writes it.
580
+ """
581
+ return str(self.net.ToXElement().ToString())
582
+
583
+ @property
584
+ def function_type(self) -> FunctionType:
585
+ """The family (``UnivariateFunctionFactory.GetFunctionType``)."""
586
+ return FunctionType(str(_functions().UnivariateFunctionFactory.GetFunctionType(self.net)))
587
+
588
+
589
+ class LinearFunction(UnivariateFunction):
590
+ """The linear function ``y = alpha + beta x`` with a Normal residual (``Numerics.Functions.LinearFunction``).
591
+
592
+ Parameters
593
+ ----------
594
+ alpha, beta : float, optional
595
+ Intercept and slope; default 0 and 1.
596
+ sigma : float, optional
597
+ Residual standard error; when given the function is stochastic, otherwise deterministic.
598
+ """
599
+
600
+ def __init__(self, alpha: float = 0.0, beta: float = 1.0, sigma: float | None = None) -> None:
601
+ functions = _functions()
602
+ net = (functions.LinearFunction(float(alpha), float(beta)) if sigma is None
603
+ else functions.LinearFunction(float(alpha), float(beta), float(sigma)))
604
+ super().__init__(net)
605
+
606
+ @classmethod
607
+ def from_xml(cls, xml: str) -> LinearFunction:
608
+ """Restore the function from its XML (``FromXElement``).
609
+
610
+ Parameters
611
+ ----------
612
+ xml : str
613
+ The text :meth:`to_xml` returns.
614
+
615
+ Returns
616
+ -------
617
+ LinearFunction
618
+ The function.
619
+ """
620
+ return wrap_function(_functions().LinearFunction.FromXElement(parse_xml(xml)))
621
+
622
+ @property
623
+ def alpha(self) -> float:
624
+ """The intercept."""
625
+ return float(self.net.Alpha)
626
+
627
+ @alpha.setter
628
+ def alpha(self, value: float) -> None:
629
+ self.net.Alpha = float(value)
630
+
631
+ @property
632
+ def beta(self) -> float:
633
+ """The slope."""
634
+ return float(self.net.Beta)
635
+
636
+ @beta.setter
637
+ def beta(self, value: float) -> None:
638
+ self.net.Beta = float(value)
639
+
640
+ @property
641
+ def sigma(self) -> float:
642
+ """The residual standard error."""
643
+ return float(self.net.Sigma)
644
+
645
+ @sigma.setter
646
+ def sigma(self, value: float) -> None:
647
+ self.net.Sigma = float(value)
648
+
649
+
650
+ class PowerFunction(UnivariateFunction):
651
+ """The power function ``y = alpha (x - xi)^beta`` with a log-space Normal residual (``PowerFunction``).
652
+
653
+ Parameters
654
+ ----------
655
+ alpha, beta : float, optional
656
+ Coefficient and exponent; default 1 and 1.5.
657
+ xi : float, optional
658
+ Location (the lower bound of the support); default 0.
659
+ sigma : float, optional
660
+ Residual standard error in natural-log space; when given the function is stochastic.
661
+ """
662
+
663
+ def __init__(self, alpha: float = 1.0, beta: float = 1.5, xi: float = 0.0, sigma: float | None = None) -> None:
664
+ functions = _functions()
665
+ net = (functions.PowerFunction(float(alpha), float(beta), float(xi)) if sigma is None
666
+ else functions.PowerFunction(float(alpha), float(beta), float(xi), float(sigma)))
667
+ super().__init__(net)
668
+
669
+ @classmethod
670
+ def from_xml(cls, xml: str) -> PowerFunction:
671
+ """Restore the function from its XML (``FromXElement``).
672
+
673
+ Parameters
674
+ ----------
675
+ xml : str
676
+ The text :meth:`to_xml` returns.
677
+
678
+ Returns
679
+ -------
680
+ PowerFunction
681
+ The function.
682
+ """
683
+ return wrap_function(_functions().PowerFunction.FromXElement(parse_xml(xml)))
684
+
685
+ @property
686
+ def alpha(self) -> float:
687
+ """The coefficient."""
688
+ return float(self.net.Alpha)
689
+
690
+ @alpha.setter
691
+ def alpha(self, value: float) -> None:
692
+ self.net.Alpha = float(value)
693
+
694
+ @property
695
+ def beta(self) -> float:
696
+ """The exponent."""
697
+ return float(self.net.Beta)
698
+
699
+ @beta.setter
700
+ def beta(self, value: float) -> None:
701
+ self.net.Beta = float(value)
702
+
703
+ @property
704
+ def xi(self) -> float:
705
+ """The location (lower bound of the support)."""
706
+ return float(self.net.Xi)
707
+
708
+ @xi.setter
709
+ def xi(self, value: float) -> None:
710
+ self.net.Xi = float(value)
711
+
712
+ @property
713
+ def sigma(self) -> float:
714
+ """The residual standard error in natural-log space."""
715
+ return float(self.net.Sigma)
716
+
717
+ @sigma.setter
718
+ def sigma(self, value: float) -> None:
719
+ self.net.Sigma = float(value)
720
+
721
+ @property
722
+ def is_inverse(self) -> bool:
723
+ """Whether :meth:`function` evaluates the inverse power law (x and y swapped)."""
724
+ return bool(self.net.IsInverse)
725
+
726
+ @is_inverse.setter
727
+ def is_inverse(self, value: bool) -> None:
728
+ self.net.IsInverse = bool(value)
729
+
730
+
731
+ class SegmentedPowerFunction(UnivariateFunction):
732
+ """A sum of power-law controls ``sum_k alpha_k (x - h_k)^beta_k [x > h_k]`` (``SegmentedPowerFunction``).
733
+
734
+ The parameter layout is ``[h_1, log10 alpha_1, beta_1, ..., h_K, log10 alpha_K, beta_K, sigma]``
735
+ with a Gaussian residual in log10 space (Le Coz et al., 2014, J. Hydrol. 509:573-587).
736
+
737
+ Parameters
738
+ ----------
739
+ number_of_segments : int, optional
740
+ Segments of a deterministic function at the default parameters; default 1.
741
+ parameters : array_like, optional
742
+ A full parameter vector (length ``3 K + 1``) instead; the function is then stochastic.
743
+ """
744
+
745
+ def __init__(self, number_of_segments: int = 1, parameters: Any = None) -> None:
746
+ functions = _functions()
747
+ net = (functions.SegmentedPowerFunction(int(number_of_segments)) if parameters is None
748
+ else functions.SegmentedPowerFunction(to_net_doubles(parameters)))
749
+ super().__init__(net)
750
+
751
+ @classmethod
752
+ def from_xml(cls, xml: str) -> SegmentedPowerFunction:
753
+ """Restore the function from its XML (``FromXElement``).
754
+
755
+ Parameters
756
+ ----------
757
+ xml : str
758
+ The text :meth:`to_xml` returns.
759
+
760
+ Returns
761
+ -------
762
+ SegmentedPowerFunction
763
+ The function.
764
+ """
765
+ return wrap_function(_functions().SegmentedPowerFunction.FromXElement(parse_xml(xml)))
766
+
767
+ @property
768
+ def number_of_segments(self) -> int:
769
+ """The number of controls."""
770
+ return int(self.net.NumberOfSegments)
771
+
772
+ @property
773
+ def sigma(self) -> float:
774
+ """The residual standard error in log10 space."""
775
+ return float(self.net.Sigma)
776
+
777
+ def get_breakpoint(self, segment: int) -> float:
778
+ """Return the activation stage ``h_k`` of a segment (``GetBreakpoint``).
779
+
780
+ Parameters
781
+ ----------
782
+ segment : int
783
+ The one-based segment number.
784
+
785
+ Returns
786
+ -------
787
+ float
788
+ The breakpoint.
789
+ """
790
+ return float(self.net.GetBreakpoint(int(segment)))
791
+
792
+ def get_log10_alpha(self, segment: int) -> float:
793
+ """Return ``log10 alpha_k`` of a segment (``GetLog10Alpha``).
794
+
795
+ Parameters
796
+ ----------
797
+ segment : int
798
+ The one-based segment number.
799
+
800
+ Returns
801
+ -------
802
+ float
803
+ The log10 coefficient.
804
+ """
805
+ return float(self.net.GetLog10Alpha(int(segment)))
806
+
807
+ def get_beta(self, segment: int) -> float:
808
+ """Return the exponent ``beta_k`` of a segment (``GetBeta``).
809
+
810
+ Parameters
811
+ ----------
812
+ segment : int
813
+ The one-based segment number.
814
+
815
+ Returns
816
+ -------
817
+ float
818
+ The exponent.
819
+ """
820
+ return float(self.net.GetBeta(int(segment)))
821
+
822
+
823
+ class TabularFunction(UnivariateFunction):
824
+ """A function tabulated by uncertain paired data (``Numerics.Functions.TabularFunction``).
825
+
826
+ The curve at :attr:`confidence_level` is ``paired_data.curve_sample(level)`` (the means
827
+ when the level is negative), interpolated in the transformed spaces.
828
+
829
+ Parameters
830
+ ----------
831
+ paired_data : UncertainOrderedPairedData
832
+ The x values and the distribution of y at each.
833
+ """
834
+
835
+ def __init__(self, paired_data: UncertainOrderedPairedData) -> None:
836
+ super().__init__(_functions().TabularFunction(paired_data.net))
837
+
838
+ @classmethod
839
+ def from_xml(cls, xml: str) -> TabularFunction:
840
+ """Restore the function from its XML (``FromXElement``).
841
+
842
+ Parameters
843
+ ----------
844
+ xml : str
845
+ The text :meth:`to_xml` returns.
846
+
847
+ Returns
848
+ -------
849
+ TabularFunction
850
+ The function.
851
+ """
852
+ return wrap_function(_functions().TabularFunction.FromXElement(parse_xml(xml)))
853
+
854
+ @property
855
+ def paired_data(self) -> UncertainOrderedPairedData:
856
+ """The uncertain paired data."""
857
+ return UncertainOrderedPairedData._from_net(self.net.PairedData)
858
+
859
+ @property
860
+ def x_transform(self) -> Transform:
861
+ """The interpolation space of x."""
862
+ return Transform(str(self.net.XTransform))
863
+
864
+ @x_transform.setter
865
+ def x_transform(self, value: Transform | str) -> None:
866
+ self.net.XTransform = net_enum(namespace("Numerics.Data").Transform, value)
867
+
868
+ @property
869
+ def y_transform(self) -> Transform:
870
+ """The interpolation space of y."""
871
+ return Transform(str(self.net.YTransform))
872
+
873
+ @y_transform.setter
874
+ def y_transform(self, value: Transform | str) -> None:
875
+ self.net.YTransform = net_enum(namespace("Numerics.Data").Transform, value)
876
+
877
+ @property
878
+ def extrapolation(self) -> Extrapolation:
879
+ """The sides on which the table extends linearly; default none (end values held)."""
880
+ return Extrapolation(str(self.net.Extrapolation))
881
+
882
+ @extrapolation.setter
883
+ def extrapolation(self, value: Extrapolation | str) -> None:
884
+ self.net.Extrapolation = net_enum(namespace("Numerics.Data").ExtrapolationSides, value)
885
+
886
+ @property
887
+ def allow_negative_y_values(self) -> bool:
888
+ """Whether negative or NaN values are kept; when False they become 0."""
889
+ return bool(self.net.AllowNegativeYValues)
890
+
891
+ @allow_negative_y_values.setter
892
+ def allow_negative_y_values(self, value: bool) -> None:
893
+ self.net.AllowNegativeYValues = bool(value)
894
+
895
+
896
+ class CompositeFunction(UnivariateFunction):
897
+ """A weighted combination of univariate functions (``Numerics.Functions.CompositeFunction``).
898
+
899
+ With :attr:`mode` weighted average, a stochastic evaluation draws every child at the same
900
+ confidence level; with mixture, the level selects one child by its weight.
901
+
902
+ Parameters
903
+ ----------
904
+ functions : sequence of UnivariateFunction
905
+ The children (at least one).
906
+ weights : array_like, optional
907
+ Non-negative weights summing to one, one per child; default equal weights.
908
+ """
909
+
910
+ def __init__(self, functions: Sequence[UnivariateFunction], weights: Any = None) -> None:
911
+ net_functions = _functions()
912
+ import System # noqa: PLC0415 - the runtime is running
913
+
914
+ array = System.Array[net_functions.IUnivariateFunction]([f.net for f in functions])
915
+ net = (net_functions.CompositeFunction(array) if weights is None
916
+ else net_functions.CompositeFunction(array, to_net_doubles(weights)))
917
+ super().__init__(net)
918
+
919
+ @classmethod
920
+ def from_xml(cls, xml: str) -> CompositeFunction:
921
+ """Restore the function from its XML (``FromXElement``).
922
+
923
+ Parameters
924
+ ----------
925
+ xml : str
926
+ The text :meth:`to_xml` returns.
927
+
928
+ Returns
929
+ -------
930
+ CompositeFunction
931
+ The function.
932
+ """
933
+ return wrap_function(_functions().CompositeFunction.FromXElement(parse_xml(xml)))
934
+
935
+ @property
936
+ def mode(self) -> CompositeMode:
937
+ """How the children combine."""
938
+ return CompositeMode(str(self.net.Mode))
939
+
940
+ @mode.setter
941
+ def mode(self, value: CompositeMode | str) -> None:
942
+ self.net.Mode = net_enum(_functions().CompositeFunctionMode, value)
943
+
944
+ @property
945
+ def functions(self) -> list[UnivariateFunction]:
946
+ """The children."""
947
+ return [wrap_function(f) for f in self.net.Functions]
948
+
949
+ @property
950
+ def weights(self) -> np.ndarray:
951
+ """The child weights (the parameter vector)."""
952
+ return to_numpy_doubles(unwrap(self.net.Weights))
953
+
954
+
955
+ _FUNCTION_CLASSES: dict[str, type[UnivariateFunction]] = {
956
+ cls.__name__: cls for cls in (LinearFunction, PowerFunction, SegmentedPowerFunction, TabularFunction,
957
+ CompositeFunction)}
958
+
959
+
960
+ def wrap_function(net: Any) -> UnivariateFunction:
961
+ """Wrap a .NET univariate function in the matching Python class.
962
+
963
+ Parameters
964
+ ----------
965
+ net : Numerics.Functions.IUnivariateFunction
966
+ The .NET function.
967
+
968
+ Returns
969
+ -------
970
+ UnivariateFunction
971
+ The specific wrapper class (the generic :class:`UnivariateFunction` for an unknown type).
972
+ """
973
+ concrete = unwrap(net)
974
+ cls = _FUNCTION_CLASSES.get(str(concrete.GetType().Name), UnivariateFunction)
975
+ instance = cls.__new__(cls)
976
+ UnivariateFunction.__init__(instance, concrete)
977
+ return instance
978
+
979
+
980
+ def create_function(function_type: FunctionType | str) -> UnivariateFunction:
981
+ """Create a function at its default parameters (``UnivariateFunctionFactory.CreateFunction``).
982
+
983
+ Parameters
984
+ ----------
985
+ function_type : FunctionType or str
986
+ The family: ``"linear"``, ``"power"`` or ``"segmented_power"`` (a tabular or composite
987
+ function needs its data, so the factory refuses them).
988
+
989
+ Returns
990
+ -------
991
+ UnivariateFunction
992
+ The wrapped function.
993
+ """
994
+ functions = _functions()
995
+ return wrap_function(functions.UnivariateFunctionFactory.CreateFunction(
996
+ net_enum(functions.UnivariateFunctionType, function_type)))
997
+
998
+
999
+ def function_from_xml(xml: str) -> UnivariateFunction:
1000
+ """Restore any univariate function from its XML (``UnivariateFunctionFactory.CreateFromXElement``).
1001
+
1002
+ Parameters
1003
+ ----------
1004
+ xml : str
1005
+ The text :meth:`UnivariateFunction.to_xml` returns.
1006
+
1007
+ Returns
1008
+ -------
1009
+ UnivariateFunction
1010
+ The wrapped function.
1011
+ """
1012
+ return wrap_function(_functions().UnivariateFunctionFactory.CreateFromXElement(parse_xml(xml)))
1013
+
1014
+
1015
+ class EnsembleFunction:
1016
+ """An ensemble of parameter sets for one function template (``Numerics.Functions.EnsembleFunction``).
1017
+
1018
+ Each member is the template with one parameter set (for example a posterior sample of a
1019
+ rating curve); :meth:`sample` picks the member at a percentile of the ensemble order.
1020
+
1021
+ Parameters
1022
+ ----------
1023
+ template : UnivariateFunction
1024
+ The function whose configuration every member copies (not a :class:`TabularFunction`).
1025
+ parameter_sets : array_like
1026
+ Shape ``(members, template.number_of_parameters)``: one finite, valid parameter vector per row.
1027
+ fitness : array_like, optional
1028
+ One fitness value per member (for example the log-posterior); default 0.
1029
+ weights : array_like, optional
1030
+ One weight per member; default 0.
1031
+
1032
+ Attributes
1033
+ ----------
1034
+ net : Numerics.Functions.EnsembleFunction
1035
+ The wrapped .NET object.
1036
+ """
1037
+
1038
+ def __init__(self, template: UnivariateFunction, parameter_sets: Any, fitness: Any = None,
1039
+ weights: Any = None) -> None:
1040
+ rows = np.atleast_2d(np.asarray(parameter_sets, dtype=np.float64))
1041
+ count = rows.shape[0]
1042
+ fit = np.zeros(count) if fitness is None else np.asarray(fitness, dtype=np.float64).reshape(count)
1043
+ weight = np.zeros(count) if weights is None else np.asarray(weights, dtype=np.float64).reshape(count)
1044
+ optimization = namespace("Numerics.Mathematics.Optimization")
1045
+ import System # noqa: PLC0415 - the runtime is running
1046
+
1047
+ sets = System.Array[optimization.ParameterSet]([
1048
+ optimization.ParameterSet(to_net_doubles(row), float(f), float(w))
1049
+ for row, f, w in zip(rows, fit, weight, strict=True)])
1050
+ self.net = _functions().EnsembleFunction(template.net, sets)
1051
+
1052
+ @classmethod
1053
+ def from_xml(cls, xml: str) -> EnsembleFunction:
1054
+ """Restore the ensemble from its XML (``FromXElement``).
1055
+
1056
+ Parameters
1057
+ ----------
1058
+ xml : str
1059
+ The text :meth:`to_xml` returns.
1060
+
1061
+ Returns
1062
+ -------
1063
+ EnsembleFunction
1064
+ The ensemble.
1065
+ """
1066
+ instance = cls.__new__(cls)
1067
+ instance.net = _functions().EnsembleFunction.FromXElement(parse_xml(xml))
1068
+ return instance
1069
+
1070
+ def __len__(self) -> int:
1071
+ return int(self.net.Count)
1072
+
1073
+ @property
1074
+ def count(self) -> int:
1075
+ """The number of members."""
1076
+ return int(self.net.Count)
1077
+
1078
+ @property
1079
+ def parameter_sets(self) -> np.ndarray:
1080
+ """The parameter vectors, shape ``(members, parameters)`` (copies)."""
1081
+ return np.array([to_numpy_doubles(s.Values) for s in self.net.ParameterSets], dtype=np.float64)
1082
+
1083
+ @property
1084
+ def fitness(self) -> np.ndarray:
1085
+ """The fitness of each member."""
1086
+ return np.array([float(s.Fitness) for s in self.net.ParameterSets], dtype=np.float64)
1087
+
1088
+ @property
1089
+ def weights(self) -> np.ndarray:
1090
+ """The weight of each member."""
1091
+ return np.array([float(s.Weight) for s in self.net.ParameterSets], dtype=np.float64)
1092
+
1093
+ def sample_at(self, index: int) -> UnivariateFunction:
1094
+ """Return the member at an index as an independent function (``SampleAt``).
1095
+
1096
+ Parameters
1097
+ ----------
1098
+ index : int
1099
+ The member, in [0, count).
1100
+
1101
+ Returns
1102
+ -------
1103
+ UnivariateFunction
1104
+ A copy of the template with that member's parameters.
1105
+ """
1106
+ return wrap_function(self.net.SampleAt(int(index)))
1107
+
1108
+ def sample(self, percentile: float) -> UnivariateFunction:
1109
+ """Return the member at a percentile of the ensemble order (``Sample``).
1110
+
1111
+ Parameters
1112
+ ----------
1113
+ percentile : float
1114
+ In [0, 1]; the member index is ``floor(percentile * count)``, capped at the last.
1115
+
1116
+ Returns
1117
+ -------
1118
+ UnivariateFunction
1119
+ A copy of the template with that member's parameters.
1120
+ """
1121
+ return wrap_function(self.net.Sample(float(percentile)))
1122
+
1123
+ def to_xml(self) -> str:
1124
+ """Return the ensemble's ``ToXElement()`` as XML text.
1125
+
1126
+ Returns
1127
+ -------
1128
+ str
1129
+ The element as .NET writes it.
1130
+ """
1131
+ return str(self.net.ToXElement().ToString())
1132
+
1133
+
1134
+ __all__ = [
1135
+ "ComplementaryLogLogLink", "CompositeFunction", "CompositeMode", "EnsembleFunction", "FisherZLink",
1136
+ "FunctionType", "IdentityLink", "LinearFunction", "LinkController", "LinkFunction", "LogLink", "LogitLink",
1137
+ "PowerFunction", "ProbitLink", "SegmentedPowerFunction", "TabularFunction", "UnivariateFunction",
1138
+ "YeoJohnsonLink", "create_function", "create_link", "function_from_xml", "link_from_xml", "wrap_function",
1139
+ "wrap_link",
1140
+ ]
1141
+
1142
+ translate_public(globals())