pax_global_header00006660000000000000000000000064145567517000014524gustar00rootroot0000000000000052 comment=ae3768721819adf144d14175ceb833c96d281e7b npx-0.1.2/000077500000000000000000000000001455675170000123315ustar00rootroot00000000000000npx-0.1.2/.codecov.yml000066400000000000000000000000141455675170000145470ustar00rootroot00000000000000comment: no npx-0.1.2/.flake8000066400000000000000000000001531455675170000135030ustar00rootroot00000000000000[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/000077500000000000000000000000001455675170000136715ustar00rootroot00000000000000npx-0.1.2/.github/workflows/000077500000000000000000000000001455675170000157265ustar00rootroot00000000000000npx-0.1.2/.github/workflows/release.yml000066400000000000000000000012621455675170000200720ustar00rootroot00000000000000name: 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.yml000066400000000000000000000014671455675170000176230ustar00rootroot00000000000000name: 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/.gitignore000066400000000000000000000001341455675170000143170ustar00rootroot00000000000000*.pyc *.swp *.prof MANIFEST dist/ build/ .coverage .cache/ *.egg-info/ .pytest_cache/ .tox/ npx-0.1.2/.pre-commit-config.yaml000066400000000000000000000010211455675170000166040ustar00rootroot00000000000000repos: - 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.md000066400000000000000000000064321455675170000151350ustar00rootroot00000000000000# 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.md000066400000000000000000000014161455675170000145640ustar00rootroot00000000000000# 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/LICENSE000066400000000000000000000027121455675170000133400ustar00rootroot00000000000000Copyright 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.in000066400000000000000000000000711455675170000140650ustar00rootroot00000000000000include tox.ini include tests/* include src/npx/py.typed npx-0.1.2/README.md000066400000000000000000000103041455675170000136060ustar00rootroot00000000000000# npx [![PyPi Version](https://img.shields.io/pypi/v/npx.svg?style=flat-square)](https://pypi.org/project/npx/) [![PyPI pyversions](https://img.shields.io/pypi/pyversions/npx.svg?style=flat-square)](https://pypi.org/project/npx/) [![GitHub stars](https://img.shields.io/github/stars/nschloe/npx.svg?style=flat-square&logo=github&label=Stars&logoColor=white)](https://github.com/nschloe/npx) [![Downloads](https://pepy.tech/badge/npx/month?style=flat-square)](https://pepy.tech/project/npx) [![gh-actions](https://img.shields.io/github/workflow/status/nschloe/npx/ci?style=flat-square)](https://github.com/nschloe/npx/actions?query=workflow%3Aci) [![codecov](https://img.shields.io/codecov/c/github/nschloe/npx.svg?style=flat-square)](https://app.codecov.io/gh/nschloe/npx) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg?style=flat-square)](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: memory usage 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/justfile000066400000000000000000000007641455675170000141100ustar00rootroot00000000000000version := `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.toml000066400000000000000000000025571455675170000152560ustar00rootroot00000000000000[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/000077500000000000000000000000001455675170000131205ustar00rootroot00000000000000npx-0.1.2/src/npx/000077500000000000000000000000001455675170000137255ustar00rootroot00000000000000npx-0.1.2/src/npx/__init__.py000066400000000000000000000005241455675170000160370ustar00rootroot00000000000000from ._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.py000066400000000000000000000015341455675170000154030ustar00rootroot00000000000000import 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.py000066400000000000000000000055671455675170000153770ustar00rootroot00000000000000from 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.py000066400000000000000000000025531455675170000153630ustar00rootroot00000000000000import 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.py000066400000000000000000000032401455675170000157430ustar00rootroot00000000000000from __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.typed000066400000000000000000000000331455675170000154200ustar00rootroot00000000000000# Marker file for PEP 561. npx-0.1.2/tests/000077500000000000000000000000001455675170000134735ustar00rootroot00000000000000npx-0.1.2/tests/speedtest.py000066400000000000000000000011511455675170000160430ustar00rootroot00000000000000import 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.py000066400000000000000000000013301455675170000155050ustar00rootroot00000000000000import 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.py000066400000000000000000000007161455675170000171060ustar00rootroot00000000000000import 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.py000066400000000000000000000005051455675170000160460ustar00rootroot00000000000000import 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.py000066400000000000000000000026021455675170000160240ustar00rootroot00000000000000import 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.py000066400000000000000000000007201455675170000164110ustar00rootroot00000000000000import 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.py000066400000000000000000000025511455675170000174670ustar00rootroot00000000000000import 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.ini000066400000000000000000000002461455675170000136460ustar00rootroot00000000000000[tox] envlist = py3 isolated_build = True [testenv] deps = pytest pytest-codeblocks pytest-cov extras = all commands = pytest {posargs} --codeblocks