pythonic-fp-numpy 0.1.2__tar.gz → 1.1.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.
@@ -0,0 +1,51 @@
1
+ Metadata-Version: 2.4
2
+ Name: pythonic-fp-numpy
3
+ Version: 1.1.0
4
+ Summary: FP tools for Numpy
5
+ Keywords: numpy,ndarray,hashable,wrap
6
+ Author-email: "Geoffrey R. Scheller" <geoffrey@scheller.com>
7
+ Requires-Python: >=3.14
8
+ Description-Content-Type: text/markdown
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Framework :: Pytest
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: Apache Software License
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3.14
15
+ Classifier: Programming Language :: Python :: 3.15
16
+ Classifier: Typing :: Typed
17
+ License-File: LICENSE
18
+ Requires-Dist: numpy>=2.3.4
19
+ Project-URL: Changelog, https://github.com/grscheller/pythonic-fp-numpy/blob/main/CHANGELOG.md
20
+ Project-URL: Documentation, https://grscheller.github.io/pythonic-fp-numpy/release/html/
21
+ Project-URL: Homepage, https://grscheller.github.io/pythonic-fp/
22
+ Project-URL: Source, https://github.com/grscheller/pythonic-fp-numpy
23
+
24
+ # Pythonic FP - FP Numpy
25
+
26
+ Python package implementing functional programming tools for NumPy.
27
+
28
+ PyPI project
29
+ [pythonic-fp-numpy](https://pypi.org/project/pythonic-fp-numpy).
30
+ Part of the
31
+ [pythonic-fp](https://grscheller.github.io/pythonic-fp/)
32
+ PyPI projects.
33
+
34
+ ## Documentation
35
+
36
+ Documentation and other links for this project are hosted on
37
+ [GitHub Pages](https://grscheller.github.io/pythonic-fp/projects/numpy.html).
38
+
39
+ ### Modules
40
+
41
+ #### Module hashable_wrapped_ndarray
42
+
43
+ - wrap readonly NDArrays to make them hashable
44
+ - prioritize performance over safety
45
+ - see PyPI project boring-math-abstract-algebra for a use case
46
+
47
+ ## Copyright and License
48
+
49
+ Copyright (c) 2025-2026 Geoffrey R. Scheller. Licensed under the Apache
50
+ License, Version 2.0. See the LICENSE file for details.
51
+
@@ -0,0 +1,27 @@
1
+ # Pythonic FP - FP Numpy
2
+
3
+ Python package implementing functional programming tools for NumPy.
4
+
5
+ PyPI project
6
+ [pythonic-fp-numpy](https://pypi.org/project/pythonic-fp-numpy).
7
+ Part of the
8
+ [pythonic-fp](https://grscheller.github.io/pythonic-fp/)
9
+ PyPI projects.
10
+
11
+ ## Documentation
12
+
13
+ Documentation and other links for this project are hosted on
14
+ [GitHub Pages](https://grscheller.github.io/pythonic-fp/projects/numpy.html).
15
+
16
+ ### Modules
17
+
18
+ #### Module hashable_wrapped_ndarray
19
+
20
+ - wrap readonly NDArrays to make them hashable
21
+ - prioritize performance over safety
22
+ - see PyPI project boring-math-abstract-algebra for a use case
23
+
24
+ ## Copyright and License
25
+
26
+ Copyright (c) 2025-2026 Geoffrey R. Scheller. Licensed under the Apache
27
+ License, Version 2.0. See the LICENSE file for details.
@@ -1,10 +1,16 @@
1
+ [build-system]
2
+ requires = ["flit_core>=3.12,<4"]
3
+ build-backend = "flit_core.buildapi"
4
+
1
5
  [project]
2
6
  name = "pythonic-fp-numpy"
3
- version = "0.1.2"
4
- readme = "README.rst"
5
- requires-python = ">=3.13"
6
- license = { file = "LICENSE" }
7
- authors = [{ name = "Geoffrey R. Scheller", email = "geoffrey@scheller.com" }]
7
+ version = "1.1.0"
8
+ description = "FP tools for Numpy"
9
+ readme = "README.md"
10
+ requires-python = ">=3.14" # floor: PEP 649/749 deferred annotations — class-level self-reference possible NameErrors on <3.14
11
+ authors = [
12
+ { name = "Geoffrey R. Scheller", email = "geoffrey@scheller.com" },
13
+ ]
8
14
  keywords = [
9
15
  "numpy",
10
16
  "ndarray",
@@ -12,69 +18,76 @@ keywords = [
12
18
  "wrap",
13
19
  ]
14
20
  classifiers = [
15
- "Development Status :: 4 - Beta",
21
+ "Development Status :: 3 - Alpha",
16
22
  "Framework :: Pytest",
17
23
  "Intended Audience :: Developers",
18
24
  "License :: OSI Approved :: Apache Software License",
19
25
  "Operating System :: OS Independent",
20
- "Programming Language :: Python :: 3.13",
26
+ "Programming Language :: Python :: 3.14",
27
+ "Programming Language :: Python :: 3.15",
21
28
  "Typing :: Typed",
22
29
  ]
23
30
  dependencies = [
24
31
  "numpy>=2.3.4",
25
32
  ]
26
- dynamic = ["description"]
27
33
 
28
34
  [project.urls]
29
- Changelog = "https://github.com/grscheller/pythonic-fp-numpy/blob/main/CHANGELOG.rst"
30
- Documentation = "https://grscheller.github.io/pythonic-fp/numpy/development/build/html/"
31
- Homepage = "https://grscheller.github.io/pythonic-fp/homepage/build/html/"
35
+ Changelog = "https://github.com/grscheller/pythonic-fp-numpy/blob/main/CHANGELOG.md"
36
+ Documentation ="https://grscheller.github.io/pythonic-fp-numpy/release/html/"
37
+ Homepage = "https://grscheller.github.io/pythonic-fp/"
32
38
  Source = "https://github.com/grscheller/pythonic-fp-numpy"
33
39
 
34
- [project.optional-dependencies]
40
+ [dependency-groups]
41
+ docs = [
42
+ "furo>=2025.12.19",
43
+ "sphinx>=9.1",
44
+ ]
35
45
  test = [
36
- "boring-math-abstract-algebra>=1.1.0",
37
46
  "pytest>=8.4.1",
47
+ "boring-math-abstract-algebra",
38
48
  ]
39
49
 
40
- [build-system]
41
- requires = ["flit_core>=3.12,<4"]
42
- build-backend = "flit_core.buildapi"
43
-
44
50
  [tool.flit.module]
45
51
  name = "pythonic_fp.numpy"
46
52
 
53
+ [tool.flit.sdist]
54
+ exclude = [
55
+ "docs/",
56
+ "tests/",
57
+ "**/.mypy_cache/",
58
+ "**/.ruff_cache/",
59
+ "**/.pytest_cache/",
60
+ "**/__pycache__/",
61
+ "**/.dmypy.json",
62
+ ]
63
+
64
+ [tool.pytest]
65
+ addopts = ["-ra"]
66
+ consider_namespace_packages = true
67
+ testpaths = ["tests/"]
68
+
47
69
  [tool.mypy]
48
- enable_incomplete_feature = "NewGenericSyntax"
49
70
  explicit_package_bases = true
50
- implicit_reexport = false
51
71
  local_partial_types = true
52
72
  namespace_packages = true
53
73
  warn_redundant_casts = true
54
74
  warn_return_any = true
55
75
  warn_unused_configs = true
56
76
 
57
- [tool.pylsp-mypy]
58
- enabled = true
59
- live-mode = true
60
- dmypy = false
61
- strict = true
62
- report_progress = true
63
-
64
- [tool.pytest.ini_options]
65
- addopts = "-ra"
66
- consider_namespace_packages = true
67
- testpaths = ["tests/"]
68
-
69
77
  [tool.ruff]
70
- target-version = "py313"
78
+ target-version = "py314"
79
+
80
+ [tool.ruff.lint]
81
+ ignore = ["RUF022", "E741"]
71
82
 
72
83
  [tool.ruff.lint.flake8-quotes]
73
84
  docstring-quotes = "double"
74
85
 
75
86
  [tool.ruff.lint.per-file-ignores]
76
- "tests/*" = ["C901"]
77
- "**/*.py" = ["E741"]
87
+ "tests/**/*.py" = [
88
+ "E731",
89
+ "C901",
90
+ ]
78
91
 
79
92
  [tool.ruff.format]
80
93
  quote-style = "single"
@@ -1,4 +1,4 @@
1
- # Copyright 2024-2025 Geoffrey R. Scheller
1
+ # Copyright 2024-2026 Geoffrey R. Scheller
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -13,19 +13,14 @@
13
13
  # limitations under the License.
14
14
 
15
15
  """
16
- FP NumPy Tools
17
- ==============
16
+ NumPy Tools
17
+ -----------
18
18
 
19
- Package implementing functional programming tools for NumPy.
19
+ .. admonition:: Functional Tools for Numpy
20
20
 
21
- Modules
22
- -------
21
+ Package implementing functional programming tools for NumPy.
23
22
 
24
- +-------------------------------+--------------------------------------+
25
- | Module | Description |
26
- +===============================+======================================+
27
- | **hashable_wrapped_ndarray** | Make non-writable NDArrays hashable. |
28
- +-------------------------------+--------------------------------------+
23
+ - module **hashable_wrapped_ndarray:** Wrap a non-writable NDArray making result hashable.
29
24
 
30
25
  """
31
26
 
@@ -0,0 +1,411 @@
1
+ # Copyright 2025-2026 Geoffrey R. Scheller
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from abc import ABC, abstractmethod
16
+ from enum import auto, Enum
17
+ import numpy as np
18
+ import numpy.typing as npt
19
+
20
+ __all__ = [
21
+ 'DTypes',
22
+ 'HWrapNDArray',
23
+ 'HWrapNDArrayNumber',
24
+ 'HWrapNDArrayString',
25
+ 'HWrapNDArrayBytes',
26
+ 'HWrapNDArrayVoid',
27
+ 'HWrapNDArrayObject',
28
+ 'HWrapNDArrayDateTime',
29
+ 'HWrapNDArrayTimeDelta',
30
+ 'HWrapNDArrayBool',
31
+ ]
32
+
33
+
34
+ class DTypes(Enum):
35
+ """
36
+ .. admonition:: Enumeration of closed NumPy datatypes
37
+
38
+ Groups (Unions) of NumPy datatypes closed to NumPy operations.
39
+
40
+ - number
41
+ - str\\_
42
+ - bytes
43
+ - datetime64
44
+ - timedelta64
45
+ - bool\\_
46
+ - void
47
+ - object\\_
48
+
49
+ While NumPy types are extensively covariant, the NumPy
50
+ C internals are somewhat invariant. NumPy also suffers
51
+ from what I call "Fortran Disease", types get "auto-promoted"
52
+ to compatible "wider types" when necessary. That is fine when
53
+ dealing with operators on mixed types, but NumPy will
54
+ auto-promote with operations on the same type.
55
+
56
+ """
57
+
58
+ number = auto()
59
+ str_ = auto()
60
+ bytes = auto()
61
+ void = auto()
62
+ object_ = auto()
63
+ datetime64 = auto()
64
+ timedelta64 = auto()
65
+ bool_ = auto()
66
+
67
+
68
+ class HWrapNDArray(ABC):
69
+ """
70
+ Make NumPy NDArrays hashable.
71
+
72
+ Just making an NDArray (np.array) readonly is not enough. This class
73
+ stores a read-only copy of the NDArray given to the constructor and
74
+ is hashable.
75
+
76
+ """
77
+
78
+ __slots__ = '_ndarray', '_type', '_shape', '_hash'
79
+
80
+ def __init__(self, ndarray: npt.NDArray[np.generic]) -> None:
81
+ """
82
+ .. admonition:: init
83
+
84
+ Base class for a hashable wrapped ndarray.
85
+
86
+ """
87
+ self._ndarray = np.array(ndarray, copy=True)
88
+ self._ndarray.setflags(write=False)
89
+
90
+ dtype = self._ndarray.dtype.type
91
+ if issubclass(dtype, np.number):
92
+ self._type = DTypes.number
93
+ elif isinstance(dtype, np.str_):
94
+ self._type = DTypes.str_
95
+ elif issubclass(dtype, np.bytes_):
96
+ self._type = DTypes.bytes
97
+ elif issubclass(dtype, np.datetime64):
98
+ self._type = DTypes.datetime64
99
+ elif issubclass(dtype, np.timedelta64):
100
+ self._type = DTypes.timedelta64
101
+ elif issubclass(dtype, np.bool_):
102
+ self._type = DTypes.bool_
103
+ elif issubclass(dtype, np.void):
104
+ self._type = DTypes.void
105
+ elif issubclass(dtype, np.object_):
106
+ self._type = DTypes.object_
107
+ else:
108
+ msg = f"HWrapNDArray: Unknow np.dtype '{dtype}'"
109
+ raise TypeError(msg)
110
+
111
+ self._shape = self._ndarray.shape
112
+ self._hash = hash((self._ndarray.tobytes(), hash((self._shape, self._type))))
113
+
114
+ def __call__(self) -> npt.NDArray[np.number]:
115
+ """
116
+ .. admonition:: call
117
+
118
+ Return a reference to the stored NDArray.
119
+
120
+ .. warning::
121
+
122
+ For efficiency this method returns a reference to the
123
+ wrapped NDArray, not a copy.
124
+
125
+ - Allows for faster slicing and faster operations.
126
+ - Use the copy method if you want a read-write copy.
127
+ - Never make the underlying NDArray writable!!!
128
+
129
+ """
130
+ return np.array(self._ndarray)
131
+
132
+ def __hash__(self) -> int:
133
+ """
134
+ .. admonition:: hash
135
+
136
+ Return hash created during initialization. Hash only
137
+ remains valid if NDArray's flag remains off.
138
+
139
+ """
140
+ return self._hash
141
+
142
+ def __eq__(self, other: object) -> bool:
143
+ """
144
+ .. admonition:: equality comparison
145
+
146
+ :param other: The ``object`` being compared to ``self``.
147
+ :returns: ``True`` if ``object`` is another ``HWrapNDArray``
148
+ all whose corresponding components are equal to
149
+ the ones in ``self``.
150
+
151
+ """
152
+ if not isinstance(other, type(self)):
153
+ return False
154
+ if self._shape != other._shape or self._type != other._type:
155
+ return False
156
+ return np.array_equal(self._ndarray, other._ndarray)
157
+
158
+ def _ndarray_repr(self) -> str:
159
+ ndarray = self._ndarray
160
+ repr_str = repr(ndarray)
161
+ repr_str = 'np.' + repr_str.replace('\n', '')
162
+ repr_str = repr_str.replace(' ', '')
163
+ repr_str = repr_str.replace('=', "=np.")
164
+ return repr_str
165
+
166
+ @abstractmethod
167
+ def __repr__(self) -> str: ...
168
+
169
+ def __str__(self) -> str:
170
+ """
171
+ .. admonition:: user string
172
+
173
+ :returns: String meaningful to an end user.
174
+
175
+ """
176
+ user_str = str(self._ndarray)
177
+ user_str = user_str.replace('\n ', '\n ')
178
+ user_str = '<< ' + user_str + ' >>'
179
+ return user_str
180
+
181
+ def copy(self) -> npt.NDArray[np.number]:
182
+ """
183
+ .. admonition:: copy
184
+
185
+ Return a copy of the wrapped NDArray.
186
+
187
+ """
188
+ return np.array(self._ndarray, copy=True)
189
+
190
+
191
+ class HWrapNDArrayNumber(HWrapNDArray):
192
+ """
193
+ .. admonition:: wrapped numeric ndarray
194
+
195
+ Class for a hashable wrapped numeric ndarray.
196
+
197
+ """
198
+ def __init__(self, ndarray: npt.NDArray[np.number]) -> None:
199
+ """
200
+ .. admonition:: init
201
+
202
+ Wrap NDArray of arbitrary NumPy numeric types.
203
+
204
+ """
205
+ super().__init__(ndarray)
206
+
207
+ def __repr__(self) -> str:
208
+ """
209
+ .. admonition:: repr string
210
+
211
+ :returns: String to reproduce the wrapped numeric ndarray.
212
+
213
+ """
214
+ return 'HWrapNDArrayNumber(' + self._ndarray_repr() + ')'
215
+
216
+
217
+ class HWrapNDArrayString(HWrapNDArray):
218
+ """
219
+ .. admonition:: wrapped string ndarray
220
+
221
+ Class for a hashable wrapped string ndarray.
222
+
223
+ """
224
+ def __init__(self, ndarray: npt.NDArray[np.str_]) -> None:
225
+ """
226
+ .. admonition:: init
227
+
228
+ Wrap NDArray of Unicode strings.
229
+
230
+ """
231
+ super().__init__(ndarray)
232
+
233
+ def __repr__(self) -> str:
234
+ """
235
+ .. admonition:: repr string
236
+
237
+ :returns: String to reproduce the wrapped string ndarray.
238
+
239
+ """
240
+ return 'HWrapNDArrayStr(' + self._ndarray_repr() + ')'
241
+
242
+
243
+ class HWrapNDArrayBytes(HWrapNDArray):
244
+ """
245
+ .. admonition:: wrapped byte ndarray
246
+
247
+ Class for a hashable wrapped null-terminated
248
+ byte sequence ndarray.
249
+
250
+ """
251
+ def __init__(self, ndarray: npt.NDArray[np.bytes_]) -> None:
252
+ """
253
+ .. admonition:: init
254
+
255
+ Wrap NDArray of null-terminated byte sequences.
256
+
257
+ """
258
+ super().__init__(ndarray)
259
+
260
+ def __repr__(self) -> str:
261
+ """
262
+ .. admonition:: repr string
263
+
264
+ :returns: String to reproduce the wrapped null-terminated
265
+ byte sequence ndarray.
266
+
267
+ """
268
+ return 'HWrapNDArrayBytes(' + self._ndarray_repr() + ')'
269
+
270
+
271
+ class HWrapNDArrayVoid(HWrapNDArray):
272
+ """
273
+ .. admonition:: wrapped void ndarray
274
+
275
+ Class for a hashable wrapped arbitrary byte
276
+ sequence ndarray.
277
+
278
+ """
279
+ def __init__(self, ndarray: npt.NDArray[np.void]) -> None:
280
+ """
281
+ .. admonition:: init
282
+
283
+ Wrap NDArray of arbitrary byte sequences.
284
+
285
+ """
286
+ super().__init__(ndarray)
287
+
288
+ def __repr__(self) -> str:
289
+ """
290
+ .. admonition:: repr string
291
+
292
+ :returns: String to reproduce the wrapped arbitrary
293
+ byte sequence ndarray.
294
+
295
+ """
296
+ return 'HWrapNDArrayVoid(' + self._ndarray_repr() + ')'
297
+
298
+
299
+ class HWrapNDArrayObject(HWrapNDArray):
300
+ """
301
+ .. admonition:: wrapped object reference ndarray
302
+
303
+ Class for a hashable wrapped object reference ndarray.
304
+
305
+ """
306
+ def __init__(self, ndarray: npt.NDArray[np.object_]) -> None:
307
+ """
308
+ .. admonition:: init
309
+
310
+ Wrap NDArray of references to arbitrary Python objects.
311
+
312
+ """
313
+ super().__init__(ndarray)
314
+
315
+ def __repr__(self) -> str:
316
+ """
317
+ .. admonition:: repr string
318
+
319
+ :returns: String to reproduce the wrapped object
320
+ reference ndarray.
321
+
322
+ """
323
+ return 'HWrapNDArrayObject(' + self._ndarray_repr() + ')'
324
+
325
+
326
+ class HWrapNDArrayDateTime(HWrapNDArray):
327
+ """
328
+ .. admonition:: wrapped datetime ndarray
329
+
330
+ Class for a hashable wrapped datatime ndarray.
331
+
332
+ """
333
+ def __init__(self, ndarray: npt.NDArray[np.datetime64]) -> None:
334
+ """
335
+ .. admonition:: init
336
+
337
+ Wrap NDArray of datetimes.
338
+
339
+ """
340
+ super().__init__(ndarray)
341
+
342
+ def __repr__(self) -> str:
343
+ """
344
+ .. admonition:: repr string
345
+
346
+ :returns: String to reproduce the wrapped datetime ndarray.
347
+
348
+ """
349
+ return 'HWrapNDArrayTimeDelta(' + self._ndarray_repr() + ')'
350
+
351
+
352
+ class HWrapNDArrayTimeDelta(HWrapNDArray):
353
+ """
354
+ .. admonition:: wrapped timedelta ndarray
355
+
356
+ Class for a hashable wrapped timedelta ndarray.
357
+
358
+ """
359
+ def __init__(self, ndarray: npt.NDArray[np.timedelta64]) -> None:
360
+ """
361
+ .. admonition:: init
362
+
363
+ Wrap NDArray of timedeltas.
364
+
365
+ """
366
+ super().__init__(ndarray)
367
+
368
+ def __repr__(self) -> str:
369
+ """
370
+ .. admonition:: repr string
371
+
372
+ :returns: String to reproduce the wrapped timedelta ndarray.
373
+
374
+ """
375
+ return 'HWrapNDArrayTimeDelta(' + self._ndarray_repr() + ')'
376
+
377
+
378
+ class HWrapNDArrayBool(HWrapNDArray):
379
+ """
380
+ .. admonition:: wrapped Boolean ndarray
381
+
382
+ Class for a hashable wrapped Boolean ndarray.
383
+
384
+ .. note::
385
+
386
+ NumPy Booleans are actual Booleans, unlike Python bools
387
+ which are subtypes of int.
388
+
389
+ - ``*`` uses component-wise Boolean **and**
390
+ - ``+`` uses component-wise Boolean **or**
391
+ - ``@`` matrix multiplication using **and** then **or**
392
+
393
+ """
394
+
395
+ def __init__(self, ndarray: npt.NDArray[np.bool_]) -> None:
396
+ """
397
+ .. admonition:: init
398
+
399
+ Wrap NDArray of Booleans.
400
+
401
+ """
402
+ super().__init__(ndarray)
403
+
404
+ def __repr__(self) -> str:
405
+ """
406
+ .. admonition:: repr string
407
+
408
+ :returns: String to reproduce the wrapped Boolean ndarray.
409
+
410
+ """
411
+ return 'HWrapNDArrayBool(' + self._ndarray_repr() + ')'
@@ -1,7 +0,0 @@
1
- # Minimal version - only add to when necessary
2
- **/__pycache__/
3
- dist/
4
- **/.dmypy.json
5
- **/.mypy_cache/
6
- **/.pytest_cache/
7
- **/.ruff_cache
@@ -1,54 +0,0 @@
1
- CHANGELOG
2
- =========
3
-
4
- PyPI pythonic-fp-numpy project.
5
-
6
- Semantic Versioning
7
- -------------------
8
-
9
- Strict 3 digit semantic versioning
10
-
11
- - **MAJOR** version incremented for incompatible API changes
12
- - **MINOR** version incremented for backward compatible added functionality
13
- - **PATCH** version incremented for backward compatible bug fixes
14
-
15
- See `Semantic Versioning 2.0.0 <https://semver.org>`_.
16
-
17
- Releases and Important Milestones
18
- ---------------------------------
19
-
20
- PyPI v0.1.2 - 2026-01-16
21
- ~~~~~~~~~~~~~~~~~~~~~~~~
22
-
23
- Fixed some documentation rough edges. Added *.pyi stub files. Updated __repr__
24
- behavior for HWrapNDArray derived classes. Eliminated spaces.
25
-
26
- Future directions.
27
-
28
- - define ``+``, ``*``, and ``@`` on wrapped arrays.
29
- - more tests
30
-
31
-
32
- PyPI v0.1.1 - 2025-12-01
33
- ~~~~~~~~~~~~~~~~~~~~~~~~
34
-
35
- Fixed problem with the extra test dependencies.
36
-
37
-
38
- PyPI v0.1.0 - 2025-11-30
39
- ~~~~~~~~~~~~~~~~~~~~~~~~
40
-
41
- Initial PyPI release.
42
-
43
- Update - 2025-11-26
44
- ~~~~~~~~~~~~~~~~~~~
45
-
46
- Created GitHub repo for this new effort.
47
-
48
- Previously wrote a hashable wrapper for NumPy NDArray for the test suite
49
- of my boring-math-abstract-algebra PyPI project.
50
-
51
- Makes an NDArray readonly and hashable.
52
-
53
- Will need to drop the "only requires the Python std library" requirement
54
- for pythonic-fp projects.
@@ -1,62 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: pythonic-fp-numpy
3
- Version: 0.1.2
4
- Summary: FP NumPy Tools
5
- Keywords: numpy,ndarray,hashable,wrap
6
- Author-email: "Geoffrey R. Scheller" <geoffrey@scheller.com>
7
- Requires-Python: >=3.13
8
- Description-Content-Type: text/x-rst
9
- Classifier: Development Status :: 4 - Beta
10
- Classifier: Framework :: Pytest
11
- Classifier: Intended Audience :: Developers
12
- Classifier: License :: OSI Approved :: Apache Software License
13
- Classifier: Operating System :: OS Independent
14
- Classifier: Programming Language :: Python :: 3.13
15
- Classifier: Typing :: Typed
16
- License-File: LICENSE
17
- Requires-Dist: numpy>=2.3.4
18
- Requires-Dist: boring-math-abstract-algebra>=1.1.0 ; extra == "test"
19
- Requires-Dist: pytest>=8.4.1 ; extra == "test"
20
- Project-URL: Changelog, https://github.com/grscheller/pythonic-fp-numpy/blob/main/CHANGELOG.rst
21
- Project-URL: Documentation, https://grscheller.github.io/pythonic-fp/numpy/development/build/html/
22
- Project-URL: Homepage, https://grscheller.github.io/pythonic-fp/homepage/build/html/
23
- Project-URL: Source, https://github.com/grscheller/pythonic-fp-numpy
24
- Provides-Extra: test
25
-
26
- Pythonic FP - FP NumPy
27
- ======================
28
-
29
- PyPI project
30
- `pythonic-fp-numpy
31
- <https://pypi.org/project/pythonic-fp-numpy>`_.
32
-
33
- Python package implementing functional programming for NumPy.
34
-
35
- Part of the
36
- `pythonic-fp
37
- <https://grscheller.github.io/pythonic-fp>`_
38
- PyPI projects.
39
-
40
- Modules
41
- -------
42
-
43
- Module hashable_wrapped_ndarray
44
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
45
-
46
- - wrap readonly NDArrays to make them hashable
47
- - prioritize performance over safety
48
- - see PyPI project boring-math-abstract-algebra for a use case
49
-
50
- Documentation
51
- -------------
52
-
53
- Documentation for this project is hosted on
54
- `GitHub Pages
55
- <https://grscheller.github.io/pythonic-fp/numpy>`_.
56
-
57
- Copyright and License
58
- ---------------------
59
-
60
- Copyright (c) 2025-2026 Geoffrey R. Scheller. Licensed under the Apache
61
- License, Version 2.0. See the LICENSE file for details.
62
-
@@ -1,36 +0,0 @@
1
- Pythonic FP - FP NumPy
2
- ======================
3
-
4
- PyPI project
5
- `pythonic-fp-numpy
6
- <https://pypi.org/project/pythonic-fp-numpy>`_.
7
-
8
- Python package implementing functional programming for NumPy.
9
-
10
- Part of the
11
- `pythonic-fp
12
- <https://grscheller.github.io/pythonic-fp>`_
13
- PyPI projects.
14
-
15
- Modules
16
- -------
17
-
18
- Module hashable_wrapped_ndarray
19
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
20
-
21
- - wrap readonly NDArrays to make them hashable
22
- - prioritize performance over safety
23
- - see PyPI project boring-math-abstract-algebra for a use case
24
-
25
- Documentation
26
- -------------
27
-
28
- Documentation for this project is hosted on
29
- `GitHub Pages
30
- <https://grscheller.github.io/pythonic-fp/numpy>`_.
31
-
32
- Copyright and License
33
- ---------------------
34
-
35
- Copyright (c) 2025-2026 Geoffrey R. Scheller. Licensed under the Apache
36
- License, Version 2.0. See the LICENSE file for details.
@@ -1,235 +0,0 @@
1
- # Copyright 2025-2026 Geoffrey R. Scheller
2
- #
3
- # Licensed under the Apache License, Version 2.0 (the "License");
4
- # you may not use this file except in compliance with the License.
5
- # You may obtain a copy of the License at
6
- #
7
- # http://www.apache.org/licenses/LICENSE-2.0
8
- #
9
- # Unless required by applicable law or agreed to in writing, software
10
- # distributed under the License is distributed on an "AS IS" BASIS,
11
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- # See the License for the specific language governing permissions and
13
- # limitations under the License.
14
-
15
- from abc import ABC, abstractmethod
16
- from enum import auto, Enum
17
- import numpy as np
18
- import numpy.typing as npt
19
-
20
- __all__ = [
21
- 'DTypes',
22
- 'HWrapNDArray',
23
- 'HWrapNDArrayNumber',
24
- 'HWrapNDArrayString',
25
- 'HWrapNDArrayBytes',
26
- 'HWrapNDArrayVoid',
27
- 'HWrapNDArrayObject',
28
- 'HWrapNDArrayDateTime',
29
- 'HWrapNDArrayTimeDelta',
30
- 'HWrapNDArrayBool',
31
- ]
32
-
33
-
34
- class DTypes(Enum):
35
- """Enumeration of closed NumPy datatypes.
36
-
37
- .. admonition:: Groups (Unions) of NumPy datatypes closed to NumPy operations.
38
-
39
- - number
40
- - str\_
41
- - bytes
42
- - datetime64
43
- - timedelta64
44
- - bool\_
45
- - void
46
- - object\_
47
-
48
- While NumPy types are extensively covariant, the NumPy C internals
49
- are somewhat invariant. NumPy also suffers from what I call "Fortran
50
- Disease", types get "auto-promoted" to compatible "wider types" when
51
- necessary. That is fine when dealing with operators on mixed types,
52
- but NumPy will auto-promote with operations on the same type.
53
-
54
- """
55
-
56
- number = auto()
57
- str_ = auto()
58
- bytes = auto()
59
- void = auto()
60
- object_ = auto()
61
- datetime64 = auto()
62
- timedelta64 = auto()
63
- bool_ = auto()
64
-
65
-
66
- class HWrapNDArray(ABC):
67
- """
68
- Make NumPy NDArrays hashable.
69
-
70
- Just making an NDArray (np.array) readonly is not enough. This class
71
- stores a read-only copy of the NDArray given to the constructor and
72
- is hashable.
73
-
74
- """
75
-
76
- __slots__ = '_ndarray', '_type', '_shape', '_hash'
77
-
78
- def __init__(self, ndarray: npt.NDArray[np.generic]) -> None:
79
- self._ndarray = np.array(ndarray, copy=True)
80
- self._ndarray.setflags(write=False)
81
-
82
- dtype = self._ndarray.dtype.type
83
- if issubclass(dtype, np.number):
84
- self._type = DTypes.number
85
- elif isinstance(dtype, np.str_):
86
- self._type = DTypes.str_
87
- elif issubclass(dtype, np.bytes_):
88
- self._type = DTypes.bytes
89
- elif issubclass(dtype, np.datetime64):
90
- self._type = DTypes.datetime64
91
- elif issubclass(dtype, np.timedelta64):
92
- self._type = DTypes.timedelta64
93
- elif issubclass(dtype, np.bool_):
94
- self._type = DTypes.bool_
95
- elif issubclass(dtype, np.void):
96
- self._type = DTypes.void
97
- elif issubclass(dtype, np.object_):
98
- self._type = DTypes.object_
99
- else:
100
- msg = f"HWrapNDArray: Unknow np.dtype '{dtype}'"
101
- raise TypeError(msg)
102
-
103
- self._shape = self._ndarray.shape
104
- self._hash = hash((self._ndarray.tobytes(), hash((self._shape, self._type))))
105
-
106
- @abstractmethod
107
- def __repr__(self) -> str: ...
108
-
109
- def __call__(self) -> npt.NDArray[np.number]:
110
- """Return a reference to the stored NDArray.
111
-
112
- .. warning::
113
-
114
- For efficiency this method returns a reference to the
115
- wrapped NDArray.
116
-
117
- - Allows for faster slicing and faster operations.
118
- - Use the copy method if you want a read-write copy.
119
- - Never make the underlying NDArray writable!!!
120
-
121
- """
122
- return np.array(self._ndarray)
123
-
124
- def __hash__(self) -> int:
125
- return self._hash
126
-
127
- def __eq__(self, other: object) -> bool:
128
- if not isinstance(other, HWrapNDArray):
129
- return False
130
- if self._shape != other._shape or self._type != other._type:
131
- return False
132
- return np.array_equal(self._ndarray, other._ndarray)
133
-
134
- def __str__(self) -> str:
135
- np_array_str = ' ' + str(self._ndarray).replace('\n', '\n ')
136
- return f'hwrap<\n{np_array_str}\n>'
137
-
138
- def _repr(self) -> str:
139
- stripped_repr = ''.join(repr(self._ndarray).split())
140
- return f'np.{stripped_repr}'
141
-
142
- def copy(self) -> npt.NDArray[np.number]:
143
- """Return a copy of the wrapped NDArray."""
144
- return np.array(self._ndarray, copy=True)
145
-
146
-
147
- class HWrapNDArrayNumber(HWrapNDArray):
148
- """Wrap NDArrays of arbitrary NumPy numeric types."""
149
-
150
- def __init__(self, ndarray: npt.NDArray[np.number]) -> None:
151
- super().__init__(ndarray)
152
-
153
- def __repr__(self) -> str:
154
- return f'HWrapNDArrayNumber({self._repr()})'
155
-
156
-
157
- class HWrapNDArrayString(HWrapNDArray):
158
- """Wrap NDArrays of Unicode strings."""
159
-
160
- def __init__(self, ndarray: npt.NDArray[np.str_]) -> None:
161
- super().__init__(ndarray)
162
-
163
- def __repr__(self) -> str:
164
- return f'HWrapNDArrayStr({self._repr()})'
165
-
166
-
167
- class HWrapNDArrayBytes(HWrapNDArray):
168
- """Wrap NDArrays of null-terminated byte sequences."""
169
-
170
- def __init__(self, ndarray: npt.NDArray[np.bytes_]) -> None:
171
- super().__init__(ndarray)
172
-
173
- def __repr__(self) -> str:
174
- return f'HWrapNDArrayBytes({self._repr()})'
175
-
176
-
177
- class HWrapNDArrayDateTime(HWrapNDArray):
178
- """Wrap NDArrays of references to arbitrary Python objects."""
179
-
180
- def __init__(self, ndarray: npt.NDArray[np.datetime64]) -> None:
181
- super().__init__(ndarray)
182
-
183
- def __repr__(self) -> str:
184
- return f'HWrapNDArrayTimeDelta({self._repr()})'
185
-
186
-
187
- class HWrapNDArrayTimeDelta(HWrapNDArray):
188
- """Wrap NDArrays of references to arbitrary Python objects."""
189
-
190
- def __init__(self, ndarray: npt.NDArray[np.timedelta64]) -> None:
191
- super().__init__(ndarray)
192
-
193
- def __repr__(self) -> str:
194
- return f'HWrapNDArrayTimeDelta({self._repr()})'
195
-
196
-
197
- class HWrapNDArrayVoid(HWrapNDArray):
198
- """Wrap NDArrays of arbitrary byte sequences."""
199
-
200
- def __init__(self, ndarray: npt.NDArray[np.void]) -> None:
201
- super().__init__(ndarray)
202
-
203
- def __repr__(self) -> str:
204
- return f'HWrapNDArrayVoid({self._repr()})'
205
-
206
-
207
- class HWrapNDArrayObject(HWrapNDArray):
208
- """Wrap NDArrays of references to arbitrary Python objects."""
209
-
210
- def __init__(self, ndarray: npt.NDArray[np.object_]) -> None:
211
- super().__init__(ndarray)
212
-
213
- def __repr__(self) -> str:
214
- return f'HWrapNDArrayObject({self._repr()})'
215
-
216
-
217
- class HWrapNDArrayBool(HWrapNDArray):
218
- """Wrap NDArrays of Booleans.
219
-
220
- NumPy Booleans are actual Booleans, unlike Python bools which are
221
- subtypes of int.
222
-
223
- .. note::
224
-
225
- - ``*`` uses component-wise Boolean **and**
226
- - ``+`` uses component-wise Boolean **or**
227
- - ``@`` does matrix multiplication using **and** then **or**
228
-
229
- """
230
-
231
- def __init__(self, ndarray: npt.NDArray[np.bool_]) -> None:
232
- super().__init__(ndarray)
233
-
234
- def __repr__(self) -> str:
235
- return f'HWrapNDArrayBool({self._repr()})'
@@ -1,48 +0,0 @@
1
- import abc
2
- import numpy as np
3
- import numpy.typing as npt
4
- from abc import ABC
5
- from enum import Enum
6
-
7
- __all__ = ['DTypes', 'HWrapNDArray', 'HWrapNDArrayNumber', 'HWrapNDArrayString', 'HWrapNDArrayBytes', 'HWrapNDArrayVoid', 'HWrapNDArrayObject', 'HWrapNDArrayDateTime', 'HWrapNDArrayTimeDelta', 'HWrapNDArrayBool']
8
-
9
- class DTypes(Enum):
10
- number = ...
11
- str_ = ...
12
- bytes = ...
13
- void = ...
14
- object_ = ...
15
- datetime64 = ...
16
- timedelta64 = ...
17
- bool_ = ...
18
-
19
- class HWrapNDArray(ABC, metaclass=abc.ABCMeta):
20
- def __init__(self, ndarray: npt.NDArray[np.generic]) -> None: ...
21
- def __call__(self) -> npt.NDArray[np.number]: ...
22
- def __hash__(self) -> int: ...
23
- def __eq__(self, other: object) -> bool: ...
24
- def copy(self) -> npt.NDArray[np.number]: ...
25
-
26
- class HWrapNDArrayNumber(HWrapNDArray):
27
- def __init__(self, ndarray: npt.NDArray[np.number]) -> None: ...
28
-
29
- class HWrapNDArrayString(HWrapNDArray):
30
- def __init__(self, ndarray: npt.NDArray[np.str_]) -> None: ...
31
-
32
- class HWrapNDArrayBytes(HWrapNDArray):
33
- def __init__(self, ndarray: npt.NDArray[np.bytes_]) -> None: ...
34
-
35
- class HWrapNDArrayVoid(HWrapNDArray):
36
- def __init__(self, ndarray: npt.NDArray[np.void]) -> None: ...
37
-
38
- class HWrapNDArrayObject(HWrapNDArray):
39
- def __init__(self, ndarray: npt.NDArray[np.object_]) -> None: ...
40
-
41
- class HWrapNDArrayDateTime(HWrapNDArray):
42
- def __init__(self, ndarray: npt.NDArray[np.datetime64]) -> None: ...
43
-
44
- class HWrapNDArrayTimeDelta(HWrapNDArray):
45
- def __init__(self, ndarray: npt.NDArray[np.timedelta64]) -> None: ...
46
-
47
- class HWrapNDArrayBool(HWrapNDArray):
48
- def __init__(self, ndarray: npt.NDArray[np.bool_]) -> None: ...
@@ -1,86 +0,0 @@
1
- # Copyright 2025 Geoffrey R. Scheller
2
- #
3
- # Licensed under the Apache License, Version 2.0 (the "License");
4
- # you may not use this file except in compliance with the License.
5
- # You may obtain a copy of the License at
6
- #
7
- # http://www.apache.org/licenses/LICENSE-2.0
8
- #
9
- # Unless required by applicable law or agreed to in writing, software
10
- # distributed under the License is distributed on an "AS IS" BASIS,
11
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- # See the License for the specific language governing permissions and
13
- # limitations under the License.
14
-
15
- import numpy as np
16
- import numpy.typing as npt
17
- from boring_math.abstract_algebra.algebras.semigroup import Semigroup
18
- from pythonic_fp.numpy.hashable_wrapped_ndarray import HWrapNDArrayNumber
19
-
20
- type I32_2x2 = HWrapNDArrayNumber[npt.NDArray[np.int32]]
21
-
22
-
23
- def matrix_mult(left: I32_2x2, right: I32_2x2) -> I32_2x2:
24
- return HWrapNDArrayNumber(left() @ right())
25
-
26
-
27
- m2x2 = Semigroup[I32_2x2](mult=matrix_mult)
28
-
29
- np_eye = HWrapNDArrayNumber(np.eye(2, dtype=np.int32))
30
- np_zero = HWrapNDArrayNumber(np.zeros((2, 2), dtype=np.int32))
31
- np_A = HWrapNDArrayNumber(np.array([[5, -1], [0, 2]], dtype=np.int32))
32
- np_B = HWrapNDArrayNumber(np.array([[2, -1], [-1, 2]], dtype=np.int32))
33
- np_C = HWrapNDArrayNumber(np.array([[1, 1], [1, 1]], dtype=np.int32))
34
- np_D = HWrapNDArrayNumber(np.array([[0, 1], [1, 0]], dtype=np.int32))
35
- np_E = HWrapNDArrayNumber(np.array([[11, -7], [-2, 4]], dtype=np.int32))
36
-
37
-
38
- Eye = m2x2(np_eye)
39
- Zero = m2x2(np_zero)
40
- A = m2x2(np_A)
41
- B = m2x2(np_B)
42
- C = m2x2(np_C)
43
- D = m2x2(np_D)
44
- E = m2x2(np_E)
45
-
46
-
47
- class Test_number_with_int32:
48
- def test_equality(self) -> None:
49
- assert Eye * Eye == Eye
50
- assert Eye * A == A
51
- assert B * Eye == B
52
- assert E * Zero == Zero
53
- assert Zero * E == Zero
54
- assert (A * B) * C == A * (B * C)
55
- assert D * D == Eye
56
- assert A * B == E
57
-
58
- def test_identity(self) -> None:
59
- assert Eye * Eye is Eye
60
- assert Eye * A is A
61
- assert B * Eye is B
62
- assert E * Zero is Zero
63
- assert Zero * E is Zero
64
- assert (A * B) * C is A * (B * C)
65
- assert D * D is Eye
66
- assert A * B is E
67
-
68
- def test_pow(self) -> None:
69
- Eye**5 == Eye
70
- Eye**5 is Eye
71
-
72
- def test_str(self) -> None:
73
- assert str(A()) == 'hwrap<\n [[ 5 -1]\n [ 0 2]]\n>'
74
-
75
- def test_repr(self) -> None:
76
- assert repr(np_eye) == 'HWrapNDArrayNumber(np.array([[1,0],[0,1]],dtype=int32))'
77
- assert repr(np_A) == 'HWrapNDArrayNumber(np.array([[5,-1],[0,2]],dtype=int32))'
78
- assert repr(np_B) == 'HWrapNDArrayNumber(np.array([[2,-1],[-1,2]],dtype=int32))'
79
- assert repr(HWrapNDArrayNumber(np_A() @ np_B())) == 'HWrapNDArrayNumber(np.array([[11,-7],[-2,4]],dtype=int32))'
80
-
81
- def test_create(self) -> None:
82
- np_see = HWrapNDArrayNumber(np.array([[1, 1], [1, 1]], dtype=np.int32))
83
- See = m2x2(np_see)
84
- assert See == C
85
- assert See is C
86
- assert repr(np_see) == 'HWrapNDArrayNumber(np.array([[1,1],[1,1]],dtype=int32))'