oprattr 0.6.0__tar.gz → 0.7.0__tar.gz
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.
- {oprattr-0.6.0 → oprattr-0.7.0}/CHANGELOG.md +7 -0
- {oprattr-0.6.0 → oprattr-0.7.0}/PKG-INFO +1 -1
- {oprattr-0.6.0 → oprattr-0.7.0}/pyproject.toml +1 -1
- {oprattr-0.6.0 → oprattr-0.7.0}/src/oprattr/__init__.py +75 -35
- {oprattr-0.6.0 → oprattr-0.7.0}/src/oprattr/__init__.pyi +20 -20
- oprattr-0.6.0/src/oprattr/abstract.py → oprattr-0.7.0/src/oprattr/_abstract.py +6 -6
- {oprattr-0.6.0 → oprattr-0.7.0}/src/oprattr/_operations.py +1 -1
- {oprattr-0.6.0 → oprattr-0.7.0}/src/oprattr/mixins.py +8 -10
- {oprattr-0.6.0 → oprattr-0.7.0}/tests/test_object.py +9 -11
- {oprattr-0.6.0 → oprattr-0.7.0}/.gitignore +0 -0
- {oprattr-0.6.0 → oprattr-0.7.0}/.python-version +0 -0
- {oprattr-0.6.0 → oprattr-0.7.0}/DEVELOPERS.md +0 -0
- {oprattr-0.6.0 → oprattr-0.7.0}/LICENSE +0 -0
- {oprattr-0.6.0 → oprattr-0.7.0}/README.md +0 -0
- /oprattr-0.6.0/src/oprattr/typeface.py → /oprattr-0.7.0/src/oprattr/_typeface.py +0 -0
- /oprattr-0.6.0/src/oprattr/typeface.pyi → /oprattr-0.7.0/src/oprattr/_typeface.pyi +0 -0
- {oprattr-0.6.0 → oprattr-0.7.0}/src/oprattr/methods.py +0 -0
- {oprattr-0.6.0 → oprattr-0.7.0}/src/oprattr/py.typed +0 -0
- {oprattr-0.6.0 → oprattr-0.7.0}/tests/print-exceptions.py +0 -0
- {oprattr-0.6.0 → oprattr-0.7.0}/uv.lock +0 -0
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
## NEXT
|
|
2
2
|
|
|
3
|
+
## v0.7.0
|
|
4
|
+
|
|
5
|
+
- Add `oprattr.Object` and `oprattr.Quantity` to the public namespace
|
|
6
|
+
- Rename modules that should not be part of the public namespace
|
|
7
|
+
- Define the `OperationError` exception class
|
|
8
|
+
- Change default behavior when a metadata attribute does not implement an operation
|
|
9
|
+
|
|
3
10
|
## v0.6.0
|
|
4
11
|
|
|
5
12
|
- Add `methods` module
|
|
@@ -3,17 +3,27 @@ import functools
|
|
|
3
3
|
|
|
4
4
|
import numpy
|
|
5
5
|
|
|
6
|
-
from . import
|
|
6
|
+
from ._abstract import (
|
|
7
|
+
Quantity,
|
|
8
|
+
Object,
|
|
9
|
+
)
|
|
7
10
|
from . import methods
|
|
8
11
|
from . import mixins
|
|
9
|
-
from . import
|
|
10
|
-
from ._operations import
|
|
11
|
-
|
|
12
|
+
from . import _typeface
|
|
13
|
+
from ._operations import (
|
|
14
|
+
unary,
|
|
15
|
+
equality,
|
|
16
|
+
ordering,
|
|
17
|
+
additive,
|
|
18
|
+
multiplicative,
|
|
19
|
+
MetadataTypeError,
|
|
20
|
+
MetadataValueError,
|
|
21
|
+
)
|
|
12
22
|
|
|
13
|
-
T = typeface.TypeVar('T')
|
|
14
23
|
|
|
24
|
+
T = _typeface.TypeVar('T')
|
|
15
25
|
|
|
16
|
-
class Operand(
|
|
26
|
+
class Operand(Object[T], mixins.Numpy):
|
|
17
27
|
"""A concrete implementation of a real-valued object."""
|
|
18
28
|
|
|
19
29
|
__abs__ = methods.__abs__
|
|
@@ -66,22 +76,22 @@ def array_equal(
|
|
|
66
76
|
return numpy.array_equal(numpy.array(x), numpy.array(y), **kwargs)
|
|
67
77
|
|
|
68
78
|
|
|
79
|
+
class OperationError(NotImplementedError):
|
|
80
|
+
"""A metadata attribute does not support this operation.
|
|
81
|
+
|
|
82
|
+
The default behavior when applying an operator to a metadata attribute of
|
|
83
|
+
`~Operand` is to copy the current value if the attribute does not define the
|
|
84
|
+
operation. Custom metadata attributes may raise this exception to indicate
|
|
85
|
+
that attempting to apply the operator is truly an error.
|
|
86
|
+
"""
|
|
87
|
+
|
|
88
|
+
|
|
69
89
|
@Operand.implementation(numpy.gradient)
|
|
70
90
|
def gradient(x: Operand[T], *args, **kwargs):
|
|
71
91
|
"""Called for numpy.gradient(x)."""
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
try:
|
|
76
|
-
v = numpy.gradient(value, **kwargs)
|
|
77
|
-
except TypeError as exc:
|
|
78
|
-
raise TypeError(
|
|
79
|
-
"Cannot compute numpy.gradient(x)"
|
|
80
|
-
f" because metadata attribute {key!r}"
|
|
81
|
-
" does not support this operation"
|
|
82
|
-
) from exc
|
|
83
|
-
else:
|
|
84
|
-
meta[key] = v
|
|
92
|
+
f = numpy.gradient
|
|
93
|
+
data = f(x._data, *args, **kwargs)
|
|
94
|
+
meta = _apply_to_metadata(f, x, **kwargs)
|
|
85
95
|
if isinstance(data, (list, tuple)):
|
|
86
96
|
r = [type(x)(array, **meta) for array in data]
|
|
87
97
|
if isinstance(data, tuple):
|
|
@@ -96,22 +106,34 @@ def wrapnumpy(f: collections.abc.Callable):
|
|
|
96
106
|
def method(x: Operand[T], **kwargs):
|
|
97
107
|
"""Apply a numpy function to x."""
|
|
98
108
|
data = f(x._data, **kwargs)
|
|
99
|
-
meta =
|
|
100
|
-
for key, value in x._meta.items():
|
|
101
|
-
try:
|
|
102
|
-
v = f(value, **kwargs)
|
|
103
|
-
except TypeError as exc:
|
|
104
|
-
raise TypeError(
|
|
105
|
-
f"Cannot compute numpy.{f.__qualname__}(x)"
|
|
106
|
-
f" because metadata attribute {key!r}"
|
|
107
|
-
" does not support this operation"
|
|
108
|
-
) from exc
|
|
109
|
-
else:
|
|
110
|
-
meta[key] = v
|
|
109
|
+
meta = _apply_to_metadata(f, x, **kwargs)
|
|
111
110
|
return type(x)(data, **meta)
|
|
112
111
|
return method
|
|
113
112
|
|
|
114
113
|
|
|
114
|
+
def _apply_to_metadata(
|
|
115
|
+
f: collections.abc.Callable,
|
|
116
|
+
x: Operand,
|
|
117
|
+
**kwargs,
|
|
118
|
+
) -> dict[str, _typeface.Any]:
|
|
119
|
+
"""Apply `f` to metadata attributes."""
|
|
120
|
+
processed = {}
|
|
121
|
+
for key, value in x._meta.items():
|
|
122
|
+
try:
|
|
123
|
+
v = f(value, **kwargs)
|
|
124
|
+
except TypeError:
|
|
125
|
+
processed[key] = value
|
|
126
|
+
except OperationError as exc:
|
|
127
|
+
raise TypeError(
|
|
128
|
+
f"Cannot compute numpy.{f.__qualname__}(x)"
|
|
129
|
+
f" because metadata attribute {key!r} of {type(x)}"
|
|
130
|
+
" does not support this operation"
|
|
131
|
+
) from exc
|
|
132
|
+
else:
|
|
133
|
+
processed[key] = v
|
|
134
|
+
return processed.copy()
|
|
135
|
+
|
|
136
|
+
|
|
115
137
|
_OPERAND_UFUNCS = (
|
|
116
138
|
numpy.sqrt,
|
|
117
139
|
numpy.sin,
|
|
@@ -133,7 +155,25 @@ _OPERAND_FUNCTIONS = (
|
|
|
133
155
|
)
|
|
134
156
|
|
|
135
157
|
|
|
136
|
-
for
|
|
137
|
-
Operand.implement(
|
|
138
|
-
|
|
139
|
-
|
|
158
|
+
for _func in _OPERAND_UFUNCS + _OPERAND_FUNCTIONS:
|
|
159
|
+
Operand.implement(_func, wrapnumpy(_func))
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
__all__ = [
|
|
163
|
+
# Modules
|
|
164
|
+
methods,
|
|
165
|
+
mixins,
|
|
166
|
+
# Object classes
|
|
167
|
+
Quantity,
|
|
168
|
+
Object,
|
|
169
|
+
# Functions
|
|
170
|
+
unary,
|
|
171
|
+
equality,
|
|
172
|
+
ordering,
|
|
173
|
+
additive,
|
|
174
|
+
multiplicative,
|
|
175
|
+
# Error classes
|
|
176
|
+
MetadataTypeError,
|
|
177
|
+
MetadataValueError,
|
|
178
|
+
OperationError,
|
|
179
|
+
]
|
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
import numpy.typing
|
|
2
2
|
|
|
3
|
-
from . import
|
|
3
|
+
from . import _abstract
|
|
4
4
|
from . import mixins
|
|
5
|
-
from . import
|
|
5
|
+
from . import _typeface
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
T =
|
|
8
|
+
T = _typeface.TypeVar('T')
|
|
9
9
|
|
|
10
|
-
class Operand(
|
|
10
|
+
class Operand(_abstract.Object[T], mixins.Numpy):
|
|
11
11
|
"""A concrete implementation of a real-valued object."""
|
|
12
12
|
|
|
13
|
-
def __abs__(self) ->
|
|
13
|
+
def __abs__(self) -> _typeface.Self:
|
|
14
14
|
"""Called for abs(self)."""
|
|
15
15
|
|
|
16
|
-
def __pos__(self) ->
|
|
16
|
+
def __pos__(self) -> _typeface.Self:
|
|
17
17
|
"""Called for +self."""
|
|
18
18
|
|
|
19
|
-
def __neg__(self) ->
|
|
19
|
+
def __neg__(self) -> _typeface.Self:
|
|
20
20
|
"""Called for -self."""
|
|
21
21
|
|
|
22
22
|
def __eq__(self, other) -> bool:
|
|
@@ -37,43 +37,43 @@ class Operand(abstract.Object[T], mixins.Numpy):
|
|
|
37
37
|
def __ge__(self, other) -> bool | numpy.typing.NDArray[numpy.bool]:
|
|
38
38
|
"""Called for self >= other."""
|
|
39
39
|
|
|
40
|
-
def __add__(self, other) ->
|
|
40
|
+
def __add__(self, other) -> _typeface.Self:
|
|
41
41
|
"""Called for self + other."""
|
|
42
42
|
|
|
43
|
-
def __radd__(self, other) ->
|
|
43
|
+
def __radd__(self, other) -> _typeface.Self:
|
|
44
44
|
"""Called for other + self."""
|
|
45
45
|
|
|
46
|
-
def __sub__(self, other) ->
|
|
46
|
+
def __sub__(self, other) -> _typeface.Self:
|
|
47
47
|
"""Called for self - other."""
|
|
48
48
|
|
|
49
|
-
def __rsub__(self, other) ->
|
|
49
|
+
def __rsub__(self, other) -> _typeface.Self:
|
|
50
50
|
"""Called for other - self."""
|
|
51
51
|
|
|
52
|
-
def __mul__(self, other) ->
|
|
52
|
+
def __mul__(self, other) -> _typeface.Self:
|
|
53
53
|
"""Called for self * other."""
|
|
54
54
|
|
|
55
|
-
def __rmul__(self, other) ->
|
|
55
|
+
def __rmul__(self, other) -> _typeface.Self:
|
|
56
56
|
"""Called for other * self."""
|
|
57
57
|
|
|
58
|
-
def __truediv__(self, other) ->
|
|
58
|
+
def __truediv__(self, other) -> _typeface.Self:
|
|
59
59
|
"""Called for self / other."""
|
|
60
60
|
|
|
61
|
-
def __rtruediv__(self, other) ->
|
|
61
|
+
def __rtruediv__(self, other) -> _typeface.Self:
|
|
62
62
|
"""Called for other / self."""
|
|
63
63
|
|
|
64
|
-
def __floordiv__(self, other) ->
|
|
64
|
+
def __floordiv__(self, other) -> _typeface.Self:
|
|
65
65
|
"""Called for self // other."""
|
|
66
66
|
|
|
67
|
-
def __rfloordiv__(self, other) ->
|
|
67
|
+
def __rfloordiv__(self, other) -> _typeface.Self:
|
|
68
68
|
"""Called for other // self."""
|
|
69
69
|
|
|
70
|
-
def __mod__(self, other) ->
|
|
70
|
+
def __mod__(self, other) -> _typeface.Self:
|
|
71
71
|
"""Called for self % other."""
|
|
72
72
|
|
|
73
|
-
def __rmod__(self, other) ->
|
|
73
|
+
def __rmod__(self, other) -> _typeface.Self:
|
|
74
74
|
"""Called for other % self."""
|
|
75
75
|
|
|
76
|
-
def __pow__(self, other) ->
|
|
76
|
+
def __pow__(self, other) -> _typeface.Self:
|
|
77
77
|
"""Called for self ** other."""
|
|
78
78
|
|
|
79
79
|
def __rpow__(self, other):
|
|
@@ -4,10 +4,10 @@ import numbers
|
|
|
4
4
|
import numerical
|
|
5
5
|
import numpy.typing
|
|
6
6
|
|
|
7
|
-
from . import
|
|
7
|
+
from . import _typeface
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
DataType =
|
|
10
|
+
DataType = _typeface.TypeVar(
|
|
11
11
|
'DataType',
|
|
12
12
|
int,
|
|
13
13
|
float,
|
|
@@ -18,14 +18,14 @@ DataType = typeface.TypeVar(
|
|
|
18
18
|
)
|
|
19
19
|
|
|
20
20
|
|
|
21
|
-
@
|
|
22
|
-
class Quantity(numerical.Quantity[DataType],
|
|
21
|
+
@_typeface.runtime_checkable
|
|
22
|
+
class Quantity(numerical.Quantity[DataType], _typeface.Protocol):
|
|
23
23
|
"""Protocol for numerical objects with metadata."""
|
|
24
24
|
|
|
25
|
-
_meta: collections.abc.Mapping[str,
|
|
25
|
+
_meta: collections.abc.Mapping[str, _typeface.Any]
|
|
26
26
|
|
|
27
27
|
|
|
28
|
-
class Object(numerical.Real,
|
|
28
|
+
class Object(numerical.Real, _typeface.Generic[DataType]):
|
|
29
29
|
"""A real-valued object with metadata attributes."""
|
|
30
30
|
|
|
31
31
|
def __init__(
|
|
@@ -3,12 +3,11 @@ import numbers
|
|
|
3
3
|
|
|
4
4
|
import numpy
|
|
5
5
|
|
|
6
|
-
from . import
|
|
7
|
-
from . import
|
|
6
|
+
from . import _abstract
|
|
7
|
+
from . import _typeface
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
T =
|
|
11
|
-
|
|
10
|
+
T = _typeface.TypeVar('T')
|
|
12
11
|
|
|
13
12
|
class Real:
|
|
14
13
|
"""Mixin for adding basic real-valued operator support."""
|
|
@@ -85,7 +84,6 @@ class Real:
|
|
|
85
84
|
|
|
86
85
|
UserFunction = collections.abc.Callable[..., T]
|
|
87
86
|
|
|
88
|
-
|
|
89
87
|
class Numpy:
|
|
90
88
|
"""Mixin for adding support for `numpy` functions to numeric objects.
|
|
91
89
|
|
|
@@ -128,7 +126,7 @@ class Numpy:
|
|
|
128
126
|
numpy.ndarray,
|
|
129
127
|
numbers.Number,
|
|
130
128
|
list,
|
|
131
|
-
|
|
129
|
+
_abstract.Quantity,
|
|
132
130
|
}
|
|
133
131
|
|
|
134
132
|
def __array_ufunc__(self, ufunc, method, *args, **kwargs):
|
|
@@ -163,7 +161,7 @@ class Numpy:
|
|
|
163
161
|
return NotImplemented
|
|
164
162
|
if out:
|
|
165
163
|
kwargs['out'] = tuple(
|
|
166
|
-
x._data if isinstance(x,
|
|
164
|
+
x._data if isinstance(x, _abstract.Quantity)
|
|
167
165
|
else x for x in out
|
|
168
166
|
)
|
|
169
167
|
if self._implements(ufunc):
|
|
@@ -208,7 +206,7 @@ class Numpy:
|
|
|
208
206
|
|
|
209
207
|
_FUNCTION_TYPES = {
|
|
210
208
|
numpy.ndarray,
|
|
211
|
-
|
|
209
|
+
_abstract.Object,
|
|
212
210
|
} | set(numpy.ScalarType)
|
|
213
211
|
|
|
214
212
|
def __array_function__(self, func, types, args, kwargs):
|
|
@@ -325,7 +323,7 @@ class Numpy:
|
|
|
325
323
|
`arg` if `arg` is an instance of the base object class; otherwise, it
|
|
326
324
|
will return the unmodified argument.
|
|
327
325
|
"""
|
|
328
|
-
if isinstance(arg,
|
|
326
|
+
if isinstance(arg, _abstract.Quantity):
|
|
329
327
|
return arg._data
|
|
330
328
|
return arg
|
|
331
329
|
|
|
@@ -341,7 +339,7 @@ class Numpy:
|
|
|
341
339
|
"""
|
|
342
340
|
return tuple(
|
|
343
341
|
ti for ti in types
|
|
344
|
-
if not issubclass(ti,
|
|
342
|
+
if not issubclass(ti, _abstract.Object)
|
|
345
343
|
)
|
|
346
344
|
|
|
347
345
|
@classmethod
|
|
@@ -157,11 +157,6 @@ def symbol_gradient(x: Symbol, **kwargs):
|
|
|
157
157
|
return f"numpy.gradient({x})"
|
|
158
158
|
|
|
159
159
|
|
|
160
|
-
@Symbol.implementation(numpy.trapezoid)
|
|
161
|
-
def symbol_trapezoid(x: Symbol, **kwargs):
|
|
162
|
-
return f"numpy.trapezoid({x})"
|
|
163
|
-
|
|
164
|
-
|
|
165
160
|
def symbolic_binary(a, op, b):
|
|
166
161
|
if isinstance(a, (Symbol, str)) and isinstance(b, (Symbol, str)):
|
|
167
162
|
return f"{a} {op} {b}"
|
|
@@ -502,26 +497,29 @@ def test_gradient():
|
|
|
502
497
|
grad = numpy.gradient(v)
|
|
503
498
|
for t, g in zip(that, grad):
|
|
504
499
|
assert isinstance(t, oprattr.Operand)
|
|
505
|
-
assert numpy.array_equal(t,
|
|
500
|
+
assert numpy.array_equal(t, g)
|
|
501
|
+
assert t._meta['name'] == nR
|
|
506
502
|
for axis in range(v.ndim):
|
|
507
503
|
that = numpy.gradient(this, axis=axis)
|
|
508
504
|
assert isinstance(that, oprattr.Operand)
|
|
509
505
|
grad = numpy.gradient(v, axis=axis)
|
|
510
|
-
assert numpy.array_equal(that,
|
|
506
|
+
assert numpy.array_equal(that, grad)
|
|
507
|
+
assert t._meta['name'] == nR
|
|
511
508
|
|
|
512
509
|
|
|
513
510
|
def test_trapezoid():
|
|
514
|
-
"""Test `numpy.trapezoid
|
|
511
|
+
"""Test `numpy.trapezoid`, which `Symbol` does not implement."""
|
|
515
512
|
nA = Symbol('A')
|
|
516
|
-
nR = Symbol('numpy.trapezoid(A)')
|
|
517
513
|
v = numpy.arange(3*4*5).reshape(3, 4, 5)
|
|
518
514
|
this = x(v, name=nA)
|
|
519
515
|
that = numpy.trapezoid(this)
|
|
520
516
|
assert isinstance(that, oprattr.Operand)
|
|
521
|
-
assert numpy.array_equal(that,
|
|
517
|
+
assert numpy.array_equal(that, numpy.trapezoid(v))
|
|
518
|
+
assert that._meta['name'] == nA
|
|
522
519
|
for axis in range(v.ndim):
|
|
523
520
|
that = numpy.trapezoid(this, axis=axis)
|
|
524
521
|
assert isinstance(that, oprattr.Operand)
|
|
525
522
|
trap = numpy.trapezoid(v, axis=axis)
|
|
526
|
-
assert numpy.array_equal(that,
|
|
523
|
+
assert numpy.array_equal(that, trap)
|
|
524
|
+
assert that._meta['name'] == nA
|
|
527
525
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|