{"id":16874026,"url":"https://github.com/nstarman/overload_numpy","last_synced_at":"2025-09-19T00:32:53.179Z","repository":{"id":58885641,"uuid":"534337108","full_name":"nstarman/overload_numpy","owner":"nstarman","description":"Utilities for NumPy overrides with __array_(u)func(tion)__","archived":false,"fork":false,"pushed_at":"2024-11-04T19:44:06.000Z","size":239,"stargazers_count":3,"open_issues_count":7,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2024-12-16T08:26:14.910Z","etag":null,"topics":["interoperability","mypyc","numpy","python"],"latest_commit_sha":null,"homepage":"https://overload-numpy.readthedocs.io/en/latest/","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/nstarman.png","metadata":{"files":{"readme":"README.rst","changelog":"CHANGES.rst","contributing":"docs/contributing.rst","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":"AUTHORS.rst","dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-09-08T18:09:59.000Z","updated_at":"2024-10-08T03:26:32.000Z","dependencies_parsed_at":"2023-02-01T04:01:09.455Z","dependency_job_id":"6b47ec62-6c7e-47af-999e-61f1e8036336","html_url":"https://github.com/nstarman/overload_numpy","commit_stats":{"total_commits":84,"total_committers":3,"mean_commits":28.0,"dds":0.4285714285714286,"last_synced_commit":"8a26ec49dc51c3c67ad35c1721cf25242a16c31f"},"previous_names":[],"tags_count":8,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nstarman%2Foverload_numpy","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nstarman%2Foverload_numpy/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nstarman%2Foverload_numpy/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nstarman%2Foverload_numpy/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/nstarman","download_url":"https://codeload.github.com/nstarman/overload_numpy/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":231823565,"owners_count":18431948,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["interoperability","mypyc","numpy","python"],"created_at":"2024-10-13T15:28:59.539Z","updated_at":"2025-09-19T00:32:47.788Z","avatar_url":"https://github.com/nstarman.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"Overload ``NumPy`` ufuncs and functions\n#######################################\n\n.. container::\n\n    |PyPI status| |coverage status| |RTD status| |black status| |pre-commit status|\n\n\n``overload_numpy`` provides easy-to-use tools for working with ``NumPy``'s\n``__array_(u)func(tion)__``. The library is fully typed and wheels are compiled\nwith mypyc.\n\n\nImplementing an Overload\n------------------------\n\nFirst, some imports:\n\n    \u003e\u003e\u003e from dataclasses import dataclass, fields\n    \u003e\u003e\u003e from typing import ClassVar\n    \u003e\u003e\u003e import numpy as np\n    \u003e\u003e\u003e from overload_numpy import NumPyOverloader, NPArrayOverloadMixin\n\nNow we can define a ``NumPyOverloader`` instance:\n\n    \u003e\u003e\u003e W_FUNCS = NumPyOverloader()\n\nThe overloads apply to an array wrapping class. Let's define one:\n\n    \u003e\u003e\u003e @dataclass\n    ... class Wrap1D(NPArrayOverloadMixin):\n    ...     '''A simple array wrapper.'''\n    ...     x: np.ndarray\n    ...     NP_OVERLOADS: ClassVar[NumPyOverloader] = W_FUNCS\n\n    \u003e\u003e\u003e w1d = Wrap1D(np.arange(3))\n\nNow both ``numpy.ufunc`` (e.g. ``numpy.add``) and ``numpy`` functions (e.g.\n``numpy.concatenate``) can be overloaded and registered for ``Wrap1D``.\n\n    \u003e\u003e\u003e @W_FUNCS.implements(np.add, Wrap1D)\n    ... def add(w1, w2):\n    ...     return Wrap1D(np.add(w1.x, w2.x))\n\n    \u003e\u003e\u003e @W_FUNCS.implements(np.concatenate, Wrap1D)\n    ... def concatenate(w1ds):\n    ...     return Wrap1D(np.concatenate(tuple(w.x for w in w1ds)))\n\nTime to check these work:\n\n    \u003e\u003e\u003e np.add(w1d, w1d)\n    Wrap1D(x=array([0, 2, 4]))\n\n    \u003e\u003e\u003e np.concatenate((w1d, w1d))\n    Wrap1D(x=array([0, 1, 2, 0, 1, 2]))\n\n``ufunc`` also have a number of methods: 'at', 'accumulate', etc. The function\ndispatch mechanism in `NEP13\n\u003chttps://numpy.org/neps/nep-0013-ufunc-overrides.html\u003e`_ says that  \"If one of\nthe input or output arguments implements __array_ufunc__, it is executed instead\nof the ufunc.\" Currently the overloaded ``numpy.add`` does not work for any of\nthe ``ufunc`` methods.\n\n    \u003e\u003e\u003e try: np.add.accumulate(w1d)\n    ... except Exception: print(\"failed\")\n    failed\n\n``ufunc`` method overloads can be registered on the wrapped ``add``\nimplementation:\n\n    \u003e\u003e\u003e @add.register('accumulate')\n    ... def add_accumulate(w1):\n    ...     return Wrap1D(np.add.accumulate(w1.x))\n\n    \u003e\u003e\u003e np.add.accumulate(w1d)\n    Wrap1D(x=array([0, 1, 3]))\n\n\nDispatching Overloads for Subclasses\n------------------------------------\nWhat if we defined a subclass of ``Wrap1D``?\n\n    \u003e\u003e\u003e @dataclass\n    ... class Wrap2D(Wrap1D):\n    ...     '''A simple 2-array wrapper.'''\n    ...     y: np.ndarray\n\nThe overload for ``numpy.concatenate`` registered on ``Wrap1D`` will not work\ncorrectly for ``Wrap2D``. However, ``NumPyOverloader`` supports single-dispatch\non the calling type for the overload, so overloads can be customized for\nsubclasses.\n\n    \u003e\u003e\u003e @W_FUNCS.implements(np.add, Wrap2D)\n    ... def add(w1, w2):\n    ...     print(\"using Wrap2D implementation...\")\n    ...     return Wrap2D(np.add(w1.x, w2.x),\n    ...                   np.add(w1.y, w2.y))\n\n    \u003e\u003e\u003e @W_FUNCS.implements(np.concatenate, Wrap2D)\n    ... def concatenate2(w2ds):\n    ...     print(\"using Wrap2D implementation...\")\n    ...     return Wrap2D(np.concatenate(tuple(w.x for w in w2ds)),\n    ...                   np.concatenate(tuple(w.y for w in w2ds)))\n\nChecking these work:\n\n    \u003e\u003e\u003e w2d = Wrap2D(np.arange(3), np.arange(3, 6))\n    \u003e\u003e\u003e np.add(w2d, w2d)\n    using Wrap2D implementation...\n    Wrap2D(x=array([0, 2, 4]), y=array([ 6, 8, 10]))\n\n    \u003e\u003e\u003e np.concatenate((w2d, w2d))\n    using Wrap2D implementation...\n    Wrap2D(x=array([0, 1, 2, 0, 1, 2]), y=array([3, 4, 5, 3, 4, 5]))\n\nGreat! But rather than defining a new implementation for each subclass,\nlet's see how we could write a more broadly applicable overload:\n\n    \u003e\u003e\u003e @W_FUNCS.implements(np.add, Wrap1D)  # overriding both\n    ... @W_FUNCS.implements(np.add, Wrap2D)  # overriding both\n    ... def add_general(w1, w2):\n    ...     WT = type(w1)\n    ...     return WT(*(np.add(getattr(w1, f.name), getattr(w2, f.name))\n    ...                 for f in fields(WT)))\n\n    \u003e\u003e\u003e @W_FUNCS.implements(np.concatenate, Wrap1D)  # overriding both\n    ... @W_FUNCS.implements(np.concatenate, Wrap2D)  # overriding both\n    ... def concatenate_general(ws):\n    ...     WT = type(ws[0])\n    ...     return WT(*(np.concatenate(tuple(getattr(w, f.name) for w in ws))\n    ...                 for f in fields(WT)))\n\nChecking these work:\n\n    \u003e\u003e\u003e np.add(w2d, w2d)\n    Wrap2D(x=array([0, 2, 4]), y=array([ 6, 8, 10]))\n\n    \u003e\u003e\u003e np.concatenate((w2d, w2d))\n    Wrap2D(x=array([0, 1, 2, 0, 1, 2]), y=array([3, 4, 5, 3, 4, 5]))\n\n    \u003e\u003e\u003e @dataclass\n    ... class Wrap3D(Wrap2D):\n    ...     '''A simple 3-array wrapper.'''\n    ...     z: np.ndarray\n\n    \u003e\u003e\u003e w3d = Wrap3D(np.arange(2), np.arange(3, 5), np.arange(6, 8))\n    \u003e\u003e\u003e np.add(w3d, w3d)\n    Wrap3D(x=array([0, 2]), y=array([6, 8]), z=array([12, 14]))\n    \u003e\u003e\u003e np.concatenate((w3d, w3d))\n    Wrap3D(x=array([0, 1, 0, 1]), y=array([3, 4, 3, 4]), z=array([6, 7, 6, 7]))\n\n\nAssisting Groups of Overloads\n-----------------------------\n\nIn the previous examples we wrote implementations for a single NumPy\nfunction. Overloading the full set of NumPy functions this way would take a\nlong time.\n\nWouldn't it be better if we could write many fewer, based on groups of NumPy\nfunctions?\n\n    \u003e\u003e\u003e add_funcs = {np.add, np.subtract}\n    \u003e\u003e\u003e @W_FUNCS.assists(add_funcs, types=Wrap1D, dispatch_on=Wrap1D)\n    ... def add_assists(cls, func, w1, w2, *args, **kwargs):\n    ...     return cls(*(func(getattr(w1, f.name), getattr(w2, f.name), *args, **kwargs)\n    ...                     for f in fields(cls)))\n\n    \u003e\u003e\u003e stack_funcs = {np.vstack, np.hstack, np.dstack, np.column_stack, np.row_stack}\n    \u003e\u003e\u003e @W_FUNCS.assists(stack_funcs, types=Wrap1D, dispatch_on=Wrap1D)\n    ... def stack_assists(cls, func, ws, *args, **kwargs):\n    ...     return cls(*(func(tuple(getattr(v, f.name) for v in ws), *args, **kwargs)\n    ...                     for f in fields(cls)))\n\nChecking these work:\n\n    \u003e\u003e\u003e np.subtract(w2d, w2d)\n    Wrap2D(x=array([0, 0, 0]), y=array([0, 0, 0]))\n\n    \u003e\u003e\u003e np.vstack((w1d, w1d))\n    Wrap1D(x=array([[0, 1, 2],\n                        [0, 1, 2]]))\n\n    \u003e\u003e\u003e np.hstack((w1d, w1d))\n    Wrap1D(x=array([0, 1, 2, 0, 1, 2]))\n\nWe would also like to implement the ``accumulate`` method for all the\n``add_funcs`` overloads:\n\n    \u003e\u003e\u003e @add_assists.register(\"accumulate\")\n    ... def add_accumulate_assists(cls, func, w1, *args, **kwargs):\n    ...     return cls(*(func(getattr(w1, f.name), *args, **kwargs)\n    ...                  for f in fields(cls)))\n\n    \u003e\u003e\u003e np.subtract.accumulate(w2d)\n    Wrap2D(x=array([ 0, -1, -3]), y=array([ 3, -1, -6]))\n\n\nDetails\n-------\n\nWant to see about type constraints and the API? Check out the docs!\n\n\n\n.. |black status| image:: https://img.shields.io/badge/code%20style-black-000000.svg\n   :target: https://github.com/psf/black\n   :alt: Codestyle Black\n\n.. |coverage status| image:: https://codecov.io/gh/nstarman/overload_numpy/branch/main/graph/badge.svg\n    :target: https://codecov.io/gh/nstarman/overload_numpy\n    :alt: overload_numpy's Coverage Status\n\n.. |pre-commit status| image:: https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit\u0026logoColor=white\n   :target: https://github.com/pre-commit/pre-commit\n   :alt: pre-commit\n\n.. |PyPI status| image:: https://img.shields.io/pypi/v/overload_numpy.svg\n    :target: https://pypi.org/project/overload_numpy\n    :alt: overload_numpy's PyPI Status\n\n.. |RTD status| image:: https://readthedocs.org/projects/overload-numpy/badge/?version=latest\n    :target: https://overload-numpy.readthedocs.io/en/latest/?badge=latest\n    :alt: overload_numpy's Documentation Status\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnstarman%2Foverload_numpy","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnstarman%2Foverload_numpy","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnstarman%2Foverload_numpy/lists"}