pax_global_header 0000666 0000000 0000000 00000000064 14556751700 0014524 g ustar 00root root 0000000 0000000 52 comment=ae3768721819adf144d14175ceb833c96d281e7b
npx-0.1.2/ 0000775 0000000 0000000 00000000000 14556751700 0012331 5 ustar 00root root 0000000 0000000 npx-0.1.2/.codecov.yml 0000664 0000000 0000000 00000000014 14556751700 0014547 0 ustar 00root root 0000000 0000000 comment: no
npx-0.1.2/.flake8 0000664 0000000 0000000 00000000153 14556751700 0013503 0 ustar 00root root 0000000 0000000 [flake8]
ignore = E203, E266, E501, W503
max-line-length = 80
max-complexity = 18
select = B,C,E,F,W,T4,B9
npx-0.1.2/.github/ 0000775 0000000 0000000 00000000000 14556751700 0013671 5 ustar 00root root 0000000 0000000 npx-0.1.2/.github/workflows/ 0000775 0000000 0000000 00000000000 14556751700 0015726 5 ustar 00root root 0000000 0000000 npx-0.1.2/.github/workflows/release.yml 0000664 0000000 0000000 00000001262 14556751700 0020072 0 ustar 00root root 0000000 0000000 name: Release
on:
release:
types: [released]
jobs:
build-upload:
name: Build and upload to PyPI
runs-on: ubuntu-latest
permissions:
id-token: write # required for pypi upload
contents: read # required for checkout
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Build stubs
run: |
pip install mypy
stubgen --include-docstrings src/ -o src/
- name: Build wheels
run: |
pip install build
python3 -m build --wheel
- name: Upload to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
npx-0.1.2/.github/workflows/tests.yml 0000664 0000000 0000000 00000001467 14556751700 0017623 0 ustar 00root root 0000000 0000000 name: ci
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
lint:
runs-on: ubuntu-latest
steps:
- name: Check out repo
uses: actions/checkout@v4
- name: Run pre-commit
uses: pre-commit/action@v3.0.0
build:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.7", "3.12"]
steps:
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
allow-prereleases: true
- uses: actions/checkout@v4
- name: Test with tox
run: |
pip install tox
tox -- --cov npx --cov-report xml --cov-report term
- name: Submit to codecov
uses: codecov/codecov-action@v4
if: ${{ matrix.python-version == '3.9' }}
npx-0.1.2/.gitignore 0000664 0000000 0000000 00000000134 14556751700 0014317 0 ustar 00root root 0000000 0000000 *.pyc
*.swp
*.prof
MANIFEST
dist/
build/
.coverage
.cache/
*.egg-info/
.pytest_cache/
.tox/
npx-0.1.2/.pre-commit-config.yaml 0000664 0000000 0000000 00000001021 14556751700 0016604 0 ustar 00root root 0000000 0000000 repos:
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.1.15
hooks:
- id: ruff
- repo: https://github.com/pre-commit/mirrors-prettier
rev: v3.1.0
hooks:
- id: prettier
- repo: https://github.com/codespell-project/codespell
rev: v2.2.6
hooks:
- id: codespell
# args: ["-L", "sur,nd"]
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.8.0
hooks:
- id: mypy
files: src
args: ["--install-types", "--non-interactive"]
npx-0.1.2/CODE_OF_CONDUCT.md 0000664 0000000 0000000 00000006432 14556751700 0015135 0 ustar 00root root 0000000 0000000 # npx Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, sex characteristics, gender identity and expression,
level of experience, education, socio-economic status, nationality, personal
appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
- Using welcoming and inclusive language
- Being respectful of differing viewpoints and experiences
- Gracefully accepting constructive criticism
- Focusing on what is best for the community
- Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
- The use of sexualized language or imagery and unwelcome sexual attention or
advances
- Trolling, insulting/derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or electronic
address, without explicit permission
- Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies within all project spaces, and it also applies when
an individual is representing the project or its community in public spaces.
Examples of representing a project or community include using an official
project e-mail address, posting via an official social media account, or acting
as an appointed representative at an online or offline event. Representation of
a project may be further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at [INSERT EMAIL ADDRESS]. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct/
[homepage]: https://www.contributor-covenant.org/
For answers to common questions about this code of conduct, see
https://www.contributor-covenant.org/faq/
npx-0.1.2/CONTRIBUTING.md 0000664 0000000 0000000 00000001416 14556751700 0014564 0 ustar 00root root 0000000 0000000 # npx contributing guidelines
The npx community appreciates your contributions via issues and
pull requests. Note that the [code of conduct](CODE_OF_CONDUCT.md)
applies to all interactions with the npx project, including
issues and pull requests.
When submitting pull requests, please follow the style guidelines of
the project, ensure that your code is tested and documented, and write
good commit messages, e.g., following [these
guidelines](https://chris.beams.io/posts/git-commit/).
By submitting a pull request, you are licensing your code under the
project [license](LICENSE) and affirming that you either own copyright
(automatic for most individuals) or are authorized to distribute under
the project license (e.g., in case your employer retains copyright on
your work).
npx-0.1.2/LICENSE 0000664 0000000 0000000 00000002712 14556751700 0013340 0 ustar 00root root 0000000 0000000 Copyright 2021-present Nico Schlömer
Redistribution and use in source and binary forms, with or without modification, are
permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of
conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list
of conditions and the following disclaimer in the documentation and/or other
materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may be
used to endorse or promote products derived from this software without specific prior
written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
npx-0.1.2/MANIFEST.in 0000664 0000000 0000000 00000000071 14556751700 0014065 0 ustar 00root root 0000000 0000000 include tox.ini
include tests/*
include src/npx/py.typed
npx-0.1.2/README.md 0000664 0000000 0000000 00000010304 14556751700 0013606 0 ustar 00root root 0000000 0000000 # npx
[](https://pypi.org/project/npx/)
[](https://pypi.org/project/npx/)
[](https://github.com/nschloe/npx)
[](https://pepy.tech/project/npx)
[](https://github.com/nschloe/npx/actions?query=workflow%3Aci)
[](https://app.codecov.io/gh/nschloe/npx)
[](https://github.com/psf/black)
[NumPy](https://numpy.org/) is a large library used everywhere in scientific computing.
That's why breaking backwards-compatibility comes at a significant cost and is almost
always avoided, even if the API of some methods is arguably lacking. This package
provides drop-in wrappers "fixing" those.
[scipyx](https://github.com/nschloe/scipyx) does the same for
[SciPy](https://www.scipy.org/).
If you have a fix for a NumPy method that can't go upstream for some reason, feel free
to PR here.
#### `dot`
```python
import npx
import numpy as np
a = np.random.rand(3, 4, 5)
b = np.random.rand(5, 2, 2)
out = npx.dot(a, b)
# out.shape == (3, 4, 2, 2)
```
Forms the dot product between the last axis of `a` and the _first_ axis of `b`.
(Not the second-last axis of `b` as `numpy.dot(a, b)`.)
#### `np.solve`
```python
import npx
import numpy as np
A = np.random.rand(3, 3)
b = np.random.rand(3, 10, 4)
out = npx.solve(A, b)
# out.shape == (3, 10, 4)
```
Solves a linear equation system with a matrix of shape `(n, n)` and an array of shape
`(n, ...)`. The output has the same shape as the second argument.
#### `sum_at`/`add_at`
```python
npx.sum_at(a, idx, minlength=0)
npx.add_at(out, idx, a)
```
Returns an array with entries of `a` summed up at indices `idx` with a minimum length of
`minlength`. `idx` can have any shape as long as it's matching `a`. The output shape is
`(minlength,...)`.
The numpy equivalent `numpy.add.at` is _much_
slower:
Relevant issue reports:
- [ufunc.at (and possibly other methods)
slow](https://github.com/numpy/numpy/issues/11156)
#### `unique`
```python
import npx
a = [0.1, 0.15, 0.7]
a_unique = npx.unique(a, tol=2.0e-1)
assert all(a_unique == [0.1, 0.7])
```
npx's `unique()` works just like NumPy's, except that it provides a parameter
`tol` (default `0.0`) which allows the user to set a tolerance. The real line
is essentially partitioned into bins of size `tol` and at most one
representative of each bin is returned.
#### `unique_rows`
```python
import npx
import numpy as np
a = np.random.randint(0, 5, size=(100, 2))
npx.unique_rows(a, return_inverse=False, return_counts=False)
```
Returns the unique rows of the integer array `a`. The numpy alternative `np.unique(a, axis=0)` is slow.
Relevant issue reports:
- [unique() needlessly slow](https://github.com/numpy/numpy/issues/11136)
#### `isin_rows`
```python
import npx
import numpy as np
a = [[0, 1], [0, 2]]
b = np.random.randint(0, 5, size=(100, 2))
npx.isin_rows(a, b)
```
Returns a boolean array of length `len(a)` specifying if the rows `a[k]` appear in `b`.
Similar to NumPy's own `np.isin` which only works for scalars.
#### `mean`
```python
import npx
a = [1.0, 2.0, 5.0]
npx.mean(a, p=3)
```
Returns the [generalized mean](https://en.wikipedia.org/wiki/Generalized_mean) of a
given list. Handles the cases `+-np.inf` (max/min) and`0` (geometric mean) correctly.
Also does well for large `p`.
Relevant NumPy issues:
- [generalized mean](https://github.com/numpy/numpy/issues/19341)
### License
This software is published under the [BSD-3-Clause
license](https://spdx.org/licenses/BSD-3-Clause.html).
npx-0.1.2/justfile 0000664 0000000 0000000 00000000764 14556751700 0014110 0 ustar 00root root 0000000 0000000 version := `python -c "import tomllib; print(tomllib.load(open('pyproject.toml', 'rb'))['project']['version'])"`
default:
@echo "\"just publish\"?"
publish:
@if [ "$(git rev-parse --abbrev-ref HEAD)" != "main" ]; then exit 1; fi
gh release create "v{{version}}"
clean:
@find . | grep -E "(__pycache__|\.pyc|\.pyo$)" | xargs rm -rf
@rm -rf src/*.egg-info/ build/ dist/ .tox/ .mypy_cache/
format:
ruff --fix src/ tests/
black src/ tests/
blacken-docs README.md
lint:
pre-commit run --all
npx-0.1.2/pyproject.toml 0000664 0000000 0000000 00000002557 14556751700 0015256 0 ustar 00root root 0000000 0000000 [build-system]
requires = ["setuptools>=61"]
build-backend = "setuptools.build_meta"
[project]
name = "npx"
version = "0.1.2"
authors = [{name = "Nico Schlömer", email = "nico.schloemer@gmail.com"}]
description = "Some useful extensions for NumPy"
readme = "README.md"
license = {file = "LICENSE"}
classifiers = [
"Development Status :: 4 - Beta",
"Intended Audience :: Science/Research",
"License :: OSI Approved :: BSD License",
"Operating System :: OS Independent",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Topic :: Scientific/Engineering",
"Topic :: Utilities",
]
requires-python = ">=3.7"
dependencies = [
"numpy >= 1.20.0",
# "scipy >= 1.8",
]
[project.urls]
Code = "https://github.com/sigma-py/npx"
Issues = "https://github.com/sigma-py/npx/issues"
[tool.ruff]
src = ["src", "tests"]
line-length = 88
select = ["ALL"]
ignore = [
"ANN", "S101", "D", "T201", "ERA", "N803", "PLR2004"
# "ANN", "C901", "D", "E741", "ERA", "FBT", "INP001",
# "N", "PLR", "S101", "T201", "TID252", "TD", "FIX002"
]
target-version = "py38"
[tool.mypy]
ignore_missing_imports = true
npx-0.1.2/src/ 0000775 0000000 0000000 00000000000 14556751700 0013120 5 ustar 00root root 0000000 0000000 npx-0.1.2/src/npx/ 0000775 0000000 0000000 00000000000 14556751700 0013725 5 ustar 00root root 0000000 0000000 npx-0.1.2/src/npx/__init__.py 0000664 0000000 0000000 00000000524 14556751700 0016037 0 ustar 00root root 0000000 0000000 from ._isin import isin_rows
from ._main import add_at, dot, outer, solve, subtract_at, sum_at
from ._mean import mean
from ._unique import unique, unique_rows
__all__ = [
"dot",
"outer",
"solve",
"sum_at",
"add_at",
"subtract_at",
"unique_rows",
"isin_rows",
"mean",
"unique",
"unique_rows",
]
npx-0.1.2/src/npx/_isin.py 0000664 0000000 0000000 00000001534 14556751700 0015403 0 ustar 00root root 0000000 0000000 import numpy as np
from numpy.typing import ArrayLike
def isin_rows(a: ArrayLike, b: ArrayLike) -> np.ndarray:
a = np.asarray(a)
b = np.asarray(b)
if not np.issubdtype(a.dtype, np.integer):
msg = f"Input array must be integer type, got {a.dtype}."
raise ValueError(msg)
if not np.issubdtype(b.dtype, np.integer):
msg = f"Input array must be integer type, got {b.dtype}."
raise ValueError(msg)
a = a.reshape(a.shape[0], np.prod(a.shape[1:], dtype=int))
b = b.reshape(b.shape[0], np.prod(b.shape[1:], dtype=int))
a_view = np.ascontiguousarray(a).view(
np.dtype((np.void, a.dtype.itemsize * a.shape[1])),
)
b_view = np.ascontiguousarray(b).view(
np.dtype((np.void, b.dtype.itemsize * b.shape[1])),
)
out = np.isin(a_view, b_view)
return out.reshape(a.shape[0])
npx-0.1.2/src/npx/_main.py 0000664 0000000 0000000 00000005567 14556751700 0015377 0 ustar 00root root 0000000 0000000 from functools import reduce
from operator import mul
import numpy as np
from numpy.typing import ArrayLike
# math.prod in 3.8
# https://docs.python.org/3/library/math.html#math.prod
def _prod(a):
return reduce(mul, a, 1)
def dot(a: ArrayLike, b: np.ndarray) -> np.ndarray:
"""Take arrays `a` and `b` and form the dot product between the last axis
of `a` and the first of `b`.
"""
return np.tensordot(a, b, 1)
def outer(a: ArrayLike, b: ArrayLike) -> np.ndarray:
"""Compute the outer product of two arrays `a` and `b` such that the shape
of the resulting array is `(*a.shape, *b.shape)`.
"""
a = np.asarray(a)
b = np.asarray(b)
return np.outer(a, b).reshape(*a.shape, *b.shape)
def solve(A: np.ndarray, x: np.ndarray) -> np.ndarray:
"""Solves a linear equation system with a matrix of shape (n, n) and an array of
shape (n, ...). The output has the same shape as the second argument.
"""
# https://stackoverflow.com/a/48387507/353337
x = np.asarray(x)
return np.linalg.solve(A, x.reshape(x.shape[0], -1)).reshape(x.shape)
def sum_at(a: ArrayLike, indices: ArrayLike, minlength: int):
"""Sums up values `a` with `indices` into an output array of at least
length `minlength` while treating dimensionality correctly. It's a lot
faster than numpy's own np.add.at (see
https://github.com/numpy/numpy/issues/5922#issuecomment-511477435).
Typically, `indices` will be a one-dimensional array; `a` can have any
dimensionality. In this case, the output array will have shape (minlength,
a.shape[1:]).
`indices` may have arbitrary shape, too, but then `a` has to start out the
same. (Those dimensions are flattened out in the computation.)
"""
a = np.asarray(a)
indices = np.asarray(indices)
if len(a.shape) < len(indices.shape):
msg = (
f"a.shape = {a.shape}, indices.shape = {indices.shape}, "
"but len(a.shape) >= len(indices.shape) is required."
)
raise RuntimeError(msg)
m = len(indices.shape)
assert indices.shape == a.shape[:m]
out_shape = (minlength, *a.shape[m:])
indices = indices.reshape(-1)
a = a.reshape(_prod(a.shape[:m]), _prod(a.shape[m:]))
# Cast to int; bincount doesn't work for uint64 yet
# https://github.com/numpy/numpy/issues/17760
indices = indices.astype(int)
return np.array(
[
np.bincount(indices, weights=a[:, k], minlength=minlength)
for k in range(a.shape[1])
],
).T.reshape(out_shape)
def add_at(a: ArrayLike, indices: ArrayLike, b: ArrayLike):
a = np.asarray(a)
indices = np.asarray(indices)
b = np.asarray(b)
m = len(indices.shape)
assert a.shape[1:] == b.shape[m:]
a += sum_at(b, indices, a.shape[0])
def subtract_at(a: ArrayLike, indices: ArrayLike, b: ArrayLike):
b = np.asarray(b)
add_at(a, indices, -b)
npx-0.1.2/src/npx/_mean.py 0000664 0000000 0000000 00000002553 14556751700 0015363 0 ustar 00root root 0000000 0000000 import numpy as np
from numpy.typing import ArrayLike
# There also is
# ,
# but implementation is easy enough
def _logsumexp(x: ArrayLike):
c = np.max(x)
return c + np.log(np.sum(np.exp(x - c)))
def mean(x: ArrayLike, p: float = 1) -> np.ndarray:
"""Generalized mean.
See for the numpy issue.
"""
x = np.asarray(x)
n = len(x)
if p == 1:
return np.mean(x)
if p == -np.inf:
return np.min(np.abs(x))
if p == 0:
# first compute the root, then the product, to avoid numerical
# difficulties with too small values of prod(x)
if np.any(x < 0.0):
msg = "p=0 only works with nonnegative x."
raise ValueError(msg)
return np.prod(np.power(x, 1 / n))
# alternative:
# return np.exp(np.mean(np.log(x)))
if p == np.inf:
return np.max(np.abs(x))
if np.all(x > 0.0):
# logsumexp trick to avoid overflow for large p
# only works for positive x though
return np.exp((_logsumexp(p * np.log(x)) - np.log(n)) / p)
if not isinstance(p, (int, np.integer)):
msg = f"Non-integer p (={p}) only work with nonnegative x."
raise TypeError(msg)
return (np.sum(x**p) / n) ** (1.0 / p)
npx-0.1.2/src/npx/_unique.py 0000664 0000000 0000000 00000003240 14556751700 0015743 0 ustar 00root root 0000000 0000000 from __future__ import annotations
from typing import TYPE_CHECKING, Callable
import numpy as np
if TYPE_CHECKING:
from numpy.typing import ArrayLike
def _unique_tol(
unique_fun: Callable,
a: ArrayLike,
tol: float,
**kwargs,
) -> np.ndarray | tuple[np.ndarray, ...]:
a = np.asarray(a)
# compute 1/tol first. Difference:
#
# int(3.3 / 0.1) = int(32.99999999999999) = 32
# int(3.3 * (1.0 / 0.1)) = int(33.0) = 33
#
aint = (a * (1.0 / tol)).astype(int)
return_index = kwargs.pop("return_index", False)
_, idx, *out = unique_fun(aint, return_index=True, **kwargs)
unique_a = a[idx]
if return_index:
out = [idx, *out]
if len(out) == 0:
return unique_a
return (unique_a, *out)
def unique(
a: ArrayLike,
tol: float = 0.0,
**kwargs,
) -> np.ndarray | tuple[np.ndarray, ...]:
assert tol >= 0.0
if tol > 0.0:
return _unique_tol(np.unique, a, tol, **kwargs)
return np.unique(a, **kwargs)
def unique_rows(a: ArrayLike, **kwargs) -> np.ndarray | tuple[np.ndarray, ...]:
# The numpy alternative `np.unique(a, axis=0)` is slow; cf.
# .
a = np.asarray(a)
a_shape = a.shape
a = a.reshape(a.shape[0], np.prod(a.shape[1:], dtype=int))
b = np.ascontiguousarray(a).view(np.dtype((np.void, a.dtype.itemsize * a.shape[1])))
out = np.unique(b, **kwargs)
# out[0] are the sorted, unique rows
if isinstance(out, tuple):
out = (out[0].view(a.dtype).reshape(out[0].shape[0], *a_shape[1:]), *out[1:])
else:
out = out.view(a.dtype).reshape(out.shape[0], *a_shape[1:])
return out
npx-0.1.2/src/npx/py.typed 0000664 0000000 0000000 00000000033 14556751700 0015420 0 ustar 00root root 0000000 0000000 # Marker file for PEP 561.
npx-0.1.2/tests/ 0000775 0000000 0000000 00000000000 14556751700 0013473 5 ustar 00root root 0000000 0000000 npx-0.1.2/tests/speedtest.py 0000664 0000000 0000000 00000001151 14556751700 0016043 0 ustar 00root root 0000000 0000000 import numpy as np
import perfplot
import npx
rng = np.random.default_rng(0)
m = 100
def setup(n):
idx = rng.randomint(0, m, n)
b = rng.random(n)
return idx, b
def np_add_at(data):
a = np.zeros(m)
idx, b = data
np.add.at(a, idx, b)
return a
def npx_add_at(data):
a = np.zeros(m)
idx, b = data
npx.add_at(a, idx, b)
return a
def npx_sum_at(data):
idx, b = data
return npx.sum_at(b, idx, minlength=m)
b = perfplot.bench(
setup=setup,
kernels=[np_add_at, npx_add_at, npx_sum_at],
n_range=[2**k for k in range(23)],
)
b.save("perf-add-at.svg")
npx-0.1.2/tests/test_at.py 0000664 0000000 0000000 00000001330 14556751700 0015505 0 ustar 00root root 0000000 0000000 import numpy as np
import npx
def test_sum_at():
a = [1.0, 2.0, 3.0]
idx = [0, 1, 0]
out = npx.sum_at(a, idx, minlength=4)
tol = 1.0e-13
ref = np.array([4.0, 2.0, 0.0, 0.0])
assert np.all(np.abs(out - ref) < (1 + np.abs(ref)) * tol)
def test_add_at():
a = [1.0, 2.0, 3.0]
idx = [0, 1, 0]
out = np.zeros(2)
npx.add_at(out, idx, a)
tol = 1.0e-13
ref = np.array([4.0, 2.0])
assert np.all(np.abs(out - ref) < (1 + np.abs(ref)) * tol)
def test_subtract_at():
a = [1.0, 2.0, 3.0]
idx = [0, 1, 0]
out = np.ones(2)
npx.subtract_at(out, idx, a)
tol = 1.0e-13
ref = np.array([-3.0, -1.0])
assert np.all(np.abs(out - ref) < (1 + np.abs(ref)) * tol)
npx-0.1.2/tests/test_dot_solve.py 0000664 0000000 0000000 00000000716 14556751700 0017106 0 ustar 00root root 0000000 0000000 import numpy as np
import npx
rng = np.random.default_rng(0)
def test_dot():
a = rng.random((1, 2, 3))
b = rng.random((3, 4, 5))
c = npx.dot(a, b)
assert c.shape == (1, 2, 4, 5)
def test_solve():
a = rng.random((3, 3))
b = rng.random((3, 4, 5))
c = npx.solve(a, b)
assert c.shape == b.shape
def test_outer():
a = rng.random((1, 2))
b = rng.random((3, 4))
c = npx.outer(a, b)
assert c.shape == (1, 2, 3, 4)
npx-0.1.2/tests/test_isin.py 0000664 0000000 0000000 00000000505 14556751700 0016046 0 ustar 00root root 0000000 0000000 import numpy as np
import npx
def test_isin():
a = [[0, 3], [1, 0]]
b = [[1, 0], [7, 12], [-1, 5]]
out = npx.isin_rows(a, b)
assert np.all(out == [False, True])
def test_scalar():
a = [0, 3, 5]
b = [-1, 6, 5, 0, 0, 0]
out = npx.isin_rows(a, b)
assert np.all(out == [True, False, True])
npx-0.1.2/tests/test_mean.py 0000664 0000000 0000000 00000002602 14556751700 0016024 0 ustar 00root root 0000000 0000000 import numpy as np
import pytest
import npx
@pytest.mark.parametrize(
("p", "ref"),
[
(-np.inf, 1.0), # min
(-20000, 1.0000693171203765),
(-1, 1.9672131147540985), # harmonic mean
(-0.1, 2.3000150292740735),
(0, 2.3403473193207156), # geometric mean
(0.1, 2.3810581190184337),
(1, 2.75), # arithmetic mean
(2, np.sqrt(9.75)), # root mean square
(10000, 4.999306900862521),
(np.inf, 5.0), # max
],
)
def test_mean_pos(p, ref):
a = [1.0, 2.0, 3.0, 5.0]
val = npx.mean(a, p)
# val = pmean(a, p)
# print(p, val)
assert abs(val - ref) < 1.0e-13 * abs(ref)
@pytest.mark.parametrize(
("p", "ref"),
[
(-np.inf, 1.0), # absmin
(-1, -1.9672131147540985), # harmonic mean
# (0, 2.3403473193207156), # geometric mean
(1, -2.75), # arithmetic mean
(2, np.sqrt(9.75)), # root mean square
(np.inf, 5.0), # absmax
],
)
def test_mean_neg(p, ref):
a = [-1.0, -2.0, -3.0, -5.0]
val = npx.mean(a, p)
# val = pmean(a, p)
# print(p, val)
assert abs(val - ref) < 1.0e-13 * abs(ref)
def test_errors():
a = [-1.0, -2.0, -3.0, -5.0]
with pytest.raises(TypeError, match="Non-integer p.*"):
npx.mean(a, 0.5)
with pytest.raises(ValueError, match="p=0 only works with nonnegative x."):
npx.mean(a, 0)
npx-0.1.2/tests/test_unique.py 0000664 0000000 0000000 00000000720 14556751700 0016411 0 ustar 00root root 0000000 0000000 import numpy as np
import npx
def test_unique_tol():
a = [0.1, 0.15, 0.7]
a_unique = npx.unique(a, 2.0e-1)
print(a_unique)
assert np.all(a_unique == [0.1, 0.7])
a_unique, inv = npx.unique(a, 2.0e-1, return_inverse=True)
assert np.all(a_unique == [0.1, 0.7])
assert np.all(inv == [0, 0, 1])
def test_unique_edge_case():
# 1.1 + 2.2 = 3.3000000000000003
out = npx.unique([1.1 + 2.2, 3.3], tol=0.1)
assert len(out) == 1
npx-0.1.2/tests/test_unique_rows.py 0000664 0000000 0000000 00000002551 14556751700 0017467 0 ustar 00root root 0000000 0000000 import numpy as np
import npx
def test_1d():
a = [1, 2, 1]
a_unique = npx.unique_rows(a)
assert np.all(a_unique == [1, 2])
def test_2d():
a = [[1, 2], [1, 4], [1, 2]]
a_unique = npx.unique_rows(a)
assert np.all(a_unique == [[1, 2], [1, 4]])
def test_3d():
# entries are matrices
# fails for some reason. keep an eye on
#
a = [[[3, 4], [-1, 2]], [[3, 4], [-1, 2]]]
a_unique = npx.unique_rows(a)
assert np.all(a_unique == [[[3, 4], [-1, 2]]])
def test_return_all():
a = [[1, 2], [1, 4], [1, 2]]
a_unique, inv, count = npx.unique_rows(a, return_inverse=True, return_counts=True)
assert np.all(a_unique == [[1, 2], [1, 4]])
assert np.all(inv == [0, 1, 0])
assert np.all(count == [2, 1])
def test_empty():
# empty mesh
a = np.empty((1, 0), dtype=int)
a_unique = npx.unique_rows(a)
assert np.all(a_unique == [[]])
a = np.empty((0, 2), dtype=int)
a_unique = npx.unique_rows(a)
assert np.all(a_unique == a)
def test_float():
a = [1.1, 1.2, 1.1]
out = npx.unique_rows(a)
ref = np.array([1.2, 1.1])
assert np.all(np.abs(out - ref) < 1.0e-14)
def test_float_rows():
a = [[1.1, 0.7], [1.0, 1.2], [1.1, 0.7]]
out = npx.unique_rows(a)
ref = [[1.0, 1.2], [1.1, 0.7]]
assert np.all(np.abs(out - ref) < 1.0e-14)
npx-0.1.2/tox.ini 0000664 0000000 0000000 00000000246 14556751700 0013646 0 ustar 00root root 0000000 0000000 [tox]
envlist = py3
isolated_build = True
[testenv]
deps =
pytest
pytest-codeblocks
pytest-cov
extras = all
commands =
pytest {posargs} --codeblocks