pynumerics 2.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pynumerics/__init__.py +73 -0
- pynumerics/_bin/Microsoft.CodeAnalysis.CSharp.dll +0 -0
- pynumerics/_bin/Microsoft.CodeAnalysis.LICENSE.txt +27 -0
- pynumerics/_bin/Microsoft.CodeAnalysis.dll +0 -0
- pynumerics/_bin/Numerics.dll +0 -0
- pynumerics/_bin/pynumerics.runtimeconfig.json +15 -0
- pynumerics/_coverage.py +3825 -0
- pynumerics/_util.py +237 -0
- pynumerics/_version.py +40 -0
- pynumerics/bootstrap.py +593 -0
- pynumerics/callbacks.py +791 -0
- pynumerics/copulas.py +910 -0
- pynumerics/data.py +2233 -0
- pynumerics/differentiation.py +348 -0
- pynumerics/distributions.py +4043 -0
- pynumerics/download.py +623 -0
- pynumerics/enums.py +119 -0
- pynumerics/errors.py +245 -0
- pynumerics/functions.py +1142 -0
- pynumerics/goodness_of_fit.py +699 -0
- pynumerics/graphs.py +556 -0
- pynumerics/hypothesis_tests.py +445 -0
- pynumerics/identity.py +144 -0
- pynumerics/integration.py +677 -0
- pynumerics/linalg.py +1408 -0
- pynumerics/machine_learning.py +1335 -0
- pynumerics/marshal.py +733 -0
- pynumerics/mathematics.py +1053 -0
- pynumerics/mcmc.py +1406 -0
- pynumerics/multivariate.py +1539 -0
- pynumerics/ode.py +156 -0
- pynumerics/optimization.py +1736 -0
- pynumerics/probability.py +966 -0
- pynumerics/py.typed +0 -0
- pynumerics/regional.py +522 -0
- pynumerics/roots.py +274 -0
- pynumerics/runtime.py +384 -0
- pynumerics/sampling.py +1758 -0
- pynumerics/special.py +1079 -0
- pynumerics/statistics.py +1958 -0
- pynumerics/timeseries.py +1326 -0
- pynumerics-2.3.0.dist-info/METADATA +142 -0
- pynumerics-2.3.0.dist-info/RECORD +46 -0
- pynumerics-2.3.0.dist-info/WHEEL +5 -0
- pynumerics-2.3.0.dist-info/licenses/LICENSE +10 -0
- pynumerics-2.3.0.dist-info/top_level.txt +1 -0
pynumerics/functions.py
ADDED
|
@@ -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())
|