├── torch_np ├── tests │ ├── __init__.py │ ├── test_random.py │ ├── numpy_tests │ │ ├── core │ │ │ ├── test_scalarinherit.py │ │ │ ├── test_scalar_ctors.py │ │ │ ├── test_dlpack.py │ │ │ ├── test_numerictypes.py │ │ │ ├── test_getlimits.py │ │ │ └── test_scalar_methods.py │ │ ├── fft │ │ │ └── test_helper.py │ │ └── lib │ │ │ └── test_arraypad.py │ ├── test_dtype.py │ ├── conftest.py │ ├── test_scalars_0D_arrays.py │ ├── test_function_base.py │ ├── test_unary_ufuncs.py │ ├── test_xps.py │ ├── test_binary_ufuncs.py │ └── test_nep50_examples.py ├── _getlimits.py ├── testing │ └── __init__.py ├── __init__.py ├── _unary_ufuncs_impl.py ├── _funcs.py ├── _binary_ufuncs_impl.py ├── fft.py ├── random.py ├── _dtypes_impl.py └── linalg.py ├── .gitattributes ├── setup.py ├── .git-blame-ignore-revs ├── e2e ├── maze │ ├── maze_tnp.png │ ├── maze.output.np.txt │ ├── maze.output.tnp.txt │ └── maze_numpy.py ├── smoke │ ├── smoke-1.mp4 │ ├── eager │ │ ├── smoke_nompl.py │ │ ├── smoke_1.py │ │ └── smoke_solver.py │ ├── smoke_nompl.py │ ├── smoke_1.py │ ├── bench_smoke.py │ └── smoke_solver.py ├── mandelbrot │ ├── mandelbrot.png │ └── mandelbrot.py ├── mojo_mandelbrot │ ├── mandelbrot_torch.py │ ├── num.py │ └── compiled.py ├── kmeans │ └── kmeans.py └── nn_from_scratch │ ├── eager │ ├── nn.py │ └── outp.original.txt │ ├── nn.py │ └── outp.original.txt ├── pyproject.toml ├── .github └── workflows │ ├── lint.yml │ └── basic.yml ├── .pre-commit-config.yaml ├── autogen ├── gen_mapping.py ├── gen_dtypes.py ├── dump_namespace.py ├── test_unary_ufuncs.py ├── gen_ufuncs_2.py ├── gen_ufuncs.py └── test_binary_ufuncs.py ├── .gitignore └── Readme.md /torch_np/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | e2e/smole/*mp4 binary 2 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup() 4 | -------------------------------------------------------------------------------- /.git-blame-ignore-revs: -------------------------------------------------------------------------------- 1 | 3833f26594cd838213ee6602142a12003073ae7f 2 | 6621cacf0feed4a25f37ccddb9cc9f1b0d27157f 3 | -------------------------------------------------------------------------------- /e2e/maze/maze_tnp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Quansight-Labs/numpy_pytorch_interop/HEAD/e2e/maze/maze_tnp.png -------------------------------------------------------------------------------- /e2e/smoke/smoke-1.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Quansight-Labs/numpy_pytorch_interop/HEAD/e2e/smoke/smoke-1.mp4 -------------------------------------------------------------------------------- /e2e/mandelbrot/mandelbrot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Quansight-Labs/numpy_pytorch_interop/HEAD/e2e/mandelbrot/mandelbrot.png -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [project] 6 | name = "numpy_pytorch_interop" 7 | dependencies = [ 8 | "numpy", 9 | "torch", 10 | ] 11 | version = "0.1.0" 12 | [tool.setuptools.packages.find] 13 | include = ["torch_np", "torch_np.*"] # ["*"] by default 14 | -------------------------------------------------------------------------------- /torch_np/_getlimits.py: -------------------------------------------------------------------------------- 1 | import contextlib 2 | 3 | import torch 4 | 5 | from . import _dtypes 6 | 7 | 8 | def finfo(dtyp): 9 | torch_dtype = _dtypes.dtype(dtyp).torch_dtype 10 | return torch.finfo(torch_dtype) 11 | 12 | 13 | def iinfo(dtyp): 14 | torch_dtype = _dtypes.dtype(dtyp).torch_dtype 15 | return torch.iinfo(torch_dtype) 16 | 17 | 18 | @contextlib.contextmanager 19 | def errstate(*args, **kwds): 20 | yield 21 | -------------------------------------------------------------------------------- /torch_np/testing/__init__.py: -------------------------------------------------------------------------------- 1 | from .utils import ( 2 | HAS_REFCOUNT, 3 | IS_WASM, 4 | _gen_alignment_data, 5 | assert_, 6 | assert_allclose, 7 | assert_almost_equal, 8 | assert_array_almost_equal, 9 | assert_array_equal, 10 | assert_array_less, 11 | assert_equal, 12 | assert_raises_regex, 13 | assert_warns, 14 | suppress_warnings, 15 | ) 16 | 17 | # from .testing import assert_allclose # FIXME 18 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Linting 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | strategy: 10 | matrix: 11 | python-version: [3.8] 12 | 13 | steps: 14 | - uses: actions/checkout@v1 15 | - name: Set up Python ${{ matrix.python-version }} 16 | uses: actions/setup-python@v1 17 | with: 18 | python-version: ${{ matrix.python-version }} 19 | - name: Run pre-commit hook 20 | uses: pre-commit/action@v2.0.3 21 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/myint/autoflake 3 | rev: v2.0.0 4 | hooks: 5 | - id: autoflake 6 | args: ["--in-place"] 7 | exclude: '^torch_np/tests/numpy_tests/|^e2e' 8 | - repo: https://github.com/timothycrosley/isort 9 | rev: 5.12.0 10 | hooks: 11 | - id: isort 12 | args: ["--profile", "black", "--filter-files"] 13 | exclude: '^torch_np/tests/numpy_tests/|^e2e' 14 | - repo: https://github.com/psf/black 15 | rev: 22.12.0 16 | hooks: 17 | - id: black 18 | exclude: '^torch_np/tests/numpy_tests/|^e2e' 19 | -------------------------------------------------------------------------------- /torch_np/__init__.py: -------------------------------------------------------------------------------- 1 | from . import fft, linalg, random 2 | from ._dtypes import * 3 | from ._funcs import * 4 | from ._getlimits import errstate, finfo, iinfo 5 | from ._ndarray import ( 6 | array, 7 | asarray, 8 | ascontiguousarray, 9 | can_cast, 10 | from_dlpack, 11 | ndarray, 12 | newaxis, 13 | result_type, 14 | ) 15 | from ._ufuncs import * 16 | from ._util import AxisError, UFuncTypeError 17 | 18 | # from . import testing 19 | 20 | alltrue = all 21 | sometrue = any 22 | 23 | inf = float("inf") 24 | nan = float("nan") 25 | from math import pi, e # isort: skip 26 | 27 | False_ = False 28 | True_ = True 29 | -------------------------------------------------------------------------------- /autogen/gen_mapping.py: -------------------------------------------------------------------------------- 1 | # Generate the mapping between numpy names and replacement names from the wrapper module. 2 | # 3 | # The default is autopopulated, apply manual tweaks on the result, if needed. 4 | 5 | import numpy as np 6 | import wrapper 7 | 8 | _all = set(name for name in dir(wrapper) if not name.startswith("_")) 9 | 10 | _all.remove("ndarray") 11 | _all.remove("NoValue") 12 | # _all.remove("mapping") 13 | 14 | pieces = [ 15 | " np.{np_name}: {wrapper_name}, ".format(np_name=name, wrapper_name=name) 16 | for name in sorted(_all) 17 | ] 18 | 19 | # XXX: apply additional manual surgery here, if neeeded. 20 | 21 | with open("_mapping.py", "w") as f: 22 | f.write("import numpy as np\n\n") 23 | f.write("mapping = {\n") 24 | f.write("\n".join(pieces)) 25 | f.write("\n}\n") 26 | -------------------------------------------------------------------------------- /e2e/mojo_mandelbrot/mandelbrot_torch.py: -------------------------------------------------------------------------------- 1 | import torch 2 | 3 | def mandelbrot_pytorch(max_iter=200, device='cpu'): 4 | 5 | # Define the boundaries of the complex plane 6 | xn = 450 7 | yn = 375 8 | xmin = -2.25 9 | xmax = 0.75 10 | ymin = -1.25 11 | ymax = 1.25 12 | 13 | # Create the grid of complex numbers 14 | x_values = torch.linspace(xmin, xmax, xn, device=device, dtype=torch.float64) 15 | y_values = torch.linspace(ymin, ymax, yn, device=device, dtype=torch.float64) 16 | 17 | rx, iy = torch.meshgrid(x_values, y_values, indexing='xy') 18 | 19 | x = rx.clone() 20 | y = iy.clone() 21 | mask = torch.zeros_like(x, device=device) 22 | for i in range(max_iter): 23 | x_prev = x 24 | y_prev = y 25 | x = x_prev**2 - y_prev**2 + rx 26 | y = 2*x_prev*y_prev + iy 27 | inside = torch.sqrt(x**2 + y**2) <= 2 28 | mask+=inside 29 | return mask 30 | 31 | mandelbrot_compiled = torch.compile(mandelbrot_pytorch) 32 | print(mandelbrot_compiled(3)) 33 | 34 | -------------------------------------------------------------------------------- /e2e/mojo_mandelbrot/num.py: -------------------------------------------------------------------------------- 1 | import numpy as np # otherwise @compile is ImportError 2 | import torch._dynamo.config as cfg 3 | import torch 4 | import torch_np 5 | 6 | cfg.numpy_ndarray_as_tensor = True 7 | device="cpu" 8 | 9 | @torch.compile 10 | def mandelbrot_numpy(max_iter=200): 11 | # Define the boundaries of the complex plane 12 | xn = 450 13 | yn = 375 14 | xmin = -2.25 15 | xmax = 0.75 16 | ymin = -1.25 17 | ymax = 1.25 18 | 19 | # Create the grid of complex numbers 20 | x_values = np.linspace(xmin, xmax, xn, dtype='float64') 21 | y_values = np.linspace(ymin, ymax, yn, dtype='float64') 22 | 23 | 24 | rx, iy = np.meshgrid(x_values, y_values, indexing='xy') 25 | 26 | x = rx.copy() 27 | y = iy.copy() 28 | mask = np.zeros_like(x) 29 | for _ in range(max_iter): 30 | x_prev = x 31 | y_prev = y 32 | x = x_prev**2 - y_prev**2 + rx 33 | y = 2*x_prev*y_prev + iy 34 | inside = np.sqrt(x**2 + y**2) <= 2 # sqrt is from the OP 35 | mask += inside 36 | return mask 37 | 38 | print(mandelbrot_numpy(3)) 39 | -------------------------------------------------------------------------------- /torch_np/tests/test_random.py: -------------------------------------------------------------------------------- 1 | """Light smoke test switching between numpy to pytorch random streams. 2 | """ 3 | import pytest 4 | 5 | import torch_np as tnp 6 | from torch_np.testing import assert_equal 7 | 8 | 9 | def test_uniform(): 10 | r = tnp.random.uniform(0, 1, size=10) 11 | 12 | 13 | def test_shuffle(): 14 | x = tnp.arange(10) 15 | tnp.random.shuffle(x) 16 | 17 | 18 | def test_numpy_global(): 19 | tnp.random.USE_NUMPY_RANDOM = True 20 | tnp.random.seed(12345) 21 | x = tnp.random.uniform(0, 1, size=11) 22 | 23 | # check that the stream is identical to numpy's 24 | import numpy as _np 25 | 26 | _np.random.seed(12345) 27 | x_np = _np.random.uniform(0, 1, size=11) 28 | 29 | assert_equal(x, tnp.asarray(x_np)) 30 | 31 | # switch to the pytorch stream, variates differ 32 | tnp.random.USE_NUMPY_RANDOM = False 33 | tnp.random.seed(12345) 34 | 35 | x_1 = tnp.random.uniform(0, 1, size=11) 36 | assert not (x_1 == x).all() 37 | 38 | 39 | def test_wrong_global(): 40 | try: 41 | oldstate = tnp.random.USE_NUMPY_RANDOM 42 | 43 | tnp.random.USE_NUMPY_RANDOM = "oops" 44 | with pytest.raises(ValueError): 45 | tnp.random.rand() 46 | 47 | finally: 48 | tnp.random.USE_NUMPY_RANDOM = oldstate 49 | -------------------------------------------------------------------------------- /torch_np/tests/numpy_tests/core/test_scalarinherit.py: -------------------------------------------------------------------------------- 1 | """ Test printing of scalar types. 2 | 3 | """ 4 | import pytest 5 | 6 | import torch_np as np 7 | from torch_np.testing import assert_ 8 | from pytest import raises as assert_raises 9 | 10 | 11 | class A: 12 | pass 13 | class B(A, np.float64): 14 | pass 15 | 16 | class C(B): 17 | pass 18 | class D(C, B): 19 | pass 20 | 21 | class B0(np.float64, A): 22 | pass 23 | class C0(B0): 24 | pass 25 | 26 | class HasNew: 27 | def __new__(cls, *args, **kwargs): 28 | return cls, args, kwargs 29 | 30 | class B1(np.float64, HasNew): 31 | pass 32 | 33 | 34 | @pytest.mark.skip(reason='scalar repr: numpy plans to make it more explicit') 35 | class TestInherit: 36 | def test_init(self): 37 | x = B(1.0) 38 | assert_(str(x) == '1.0') 39 | y = C(2.0) 40 | assert_(str(y) == '2.0') 41 | z = D(3.0) 42 | assert_(str(z) == '3.0') 43 | 44 | def test_init2(self): 45 | x = B0(1.0) 46 | assert_(str(x) == '1.0') 47 | y = C0(2.0) 48 | assert_(str(y) == '2.0') 49 | 50 | def test_gh_15395(self): 51 | # HasNew is the second base, so `np.float64` should have priority 52 | x = B1(1.0) 53 | assert_(str(x) == '1.0') 54 | 55 | # previously caused RecursionError!? 56 | with pytest.raises(TypeError): 57 | B1(1.0, 2.0) 58 | 59 | -------------------------------------------------------------------------------- /torch_np/_unary_ufuncs_impl.py: -------------------------------------------------------------------------------- 1 | """Export torch work functions for unary ufuncs, rename/tweak to match numpy. 2 | This listing is further exported to public symbols in the `torch_np/_ufuncs.py` module. 3 | """ 4 | 5 | import torch 6 | 7 | # renames 8 | from torch import absolute as fabs 9 | from torch import arccos, arccosh, arcsin, arcsinh, arctan, arctanh 10 | from torch import bitwise_not 11 | from torch import bitwise_not as invert 12 | from torch import ceil 13 | from torch import conj_physical as conjugate 14 | from torch import cos, cosh 15 | from torch import deg2rad 16 | from torch import deg2rad as radians 17 | from torch import ( 18 | exp, 19 | exp2, 20 | expm1, 21 | floor, 22 | isfinite, 23 | isinf, 24 | isnan, 25 | log, 26 | log1p, 27 | log2, 28 | log10, 29 | logical_not, 30 | negative, 31 | ) 32 | from torch import rad2deg 33 | from torch import rad2deg as degrees 34 | from torch import reciprocal 35 | from torch import round as fix 36 | from torch import round as rint 37 | from torch import sign, signbit, sin, sinh, sqrt, square, tan, tanh, trunc 38 | 39 | 40 | # special cases: torch does not export these names 41 | def cbrt(x): 42 | return torch.pow(x, 1 / 3) 43 | 44 | 45 | def positive(x): 46 | return +x 47 | 48 | 49 | def absolute(x): 50 | # work around torch.absolute not impl for bools 51 | if x.dtype == torch.bool: 52 | return x 53 | return torch.absolute(x) 54 | 55 | 56 | abs = absolute 57 | conj = conjugate 58 | -------------------------------------------------------------------------------- /torch_np/tests/test_dtype.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pytest 3 | 4 | import torch_np as tnp 5 | 6 | dtype_names = [ 7 | "bool_", 8 | *[f"int{w}" for w in [8, 16, 32, 64]], 9 | "uint8", 10 | *[f"float{w}" for w in [16, 32, 64]], 11 | *[f"complex{w}" for w in [64, 128]], 12 | ] 13 | np_dtype_params = [] 14 | np_dtype_params.append(pytest.param("bool", "bool", id="'bool'")) 15 | np_dtype_params.append( 16 | pytest.param( 17 | "bool", 18 | np.dtype("bool"), 19 | id=f"np.dtype('bool')", 20 | marks=pytest.mark.xfail(reason="XXX: np.dtype() objects not supported"), 21 | ) 22 | ) 23 | for name in dtype_names: 24 | np_dtype_params.append(pytest.param(name, name, id=repr(name))) 25 | np_dtype_params.append( 26 | pytest.param( 27 | name, 28 | getattr(np, name), 29 | id=f"np.{name}", 30 | marks=pytest.mark.xfail(reason="XXX: namespaced dtypes not supported"), 31 | ) 32 | ) 33 | np_dtype_params.append( 34 | pytest.param( 35 | name, 36 | np.dtype(name), 37 | id=f"np.dtype({name!r})", 38 | marks=pytest.mark.xfail(reason="XXX: np.dtype() objects not supported"), 39 | ) 40 | ) 41 | 42 | 43 | @pytest.mark.parametrize("name, np_dtype", np_dtype_params) 44 | def test_convert_np_dtypes(name, np_dtype): 45 | tnp_dtype = tnp.dtype(np_dtype) 46 | if name == "bool_": 47 | assert tnp_dtype == tnp.bool_ 48 | elif tnp_dtype.name == "bool_": 49 | assert name.startswith("bool") 50 | else: 51 | assert tnp_dtype.name == name 52 | -------------------------------------------------------------------------------- /.github/workflows/basic.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies, run tests and lint with a variety of Python versions 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions 3 | 4 | name: dev 5 | 6 | on: [push, pull_request] 7 | jobs: 8 | example-4: 9 | name: test 10 | runs-on: "ubuntu-latest" 11 | defaults: 12 | run: 13 | shell: bash -el {0} 14 | steps: 15 | - uses: actions/checkout@v2 16 | - uses: conda-incubator/setup-miniconda@v2 17 | 18 | # cache 19 | - uses: actions/cache@v2 20 | env: 21 | # Increase this value to reset cache if etc/example-environment.yml has not changed 22 | CACHE_NUMBER: 0 23 | with: 24 | path: ~/conda_pkgs_dir 25 | key: 26 | ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-${{ 27 | hashFiles('etc/example-environment.yml') }} 28 | activate-environment: foo 29 | python-version: 3.8 30 | channels: conda-forge 31 | allow-softlinks: true 32 | channel-priority: strict 33 | show-channel-urls: true 34 | use-only-tar-bz2: true 35 | 36 | - run: | 37 | conda config --add channels conda-forge 38 | conda config --set channel_priority strict 39 | conda info 40 | conda install python==3.8 pytorch pytest pytest-xdist numpy hypothesis -y 41 | conda list 42 | conda config --show-sources 43 | - run: | 44 | echo $PWD 45 | ls -l 46 | - name: Run tests 47 | run: | 48 | pytest torch_np/ -s -n 2 --runslow 49 | 50 | -------------------------------------------------------------------------------- /e2e/kmeans/kmeans.py: -------------------------------------------------------------------------------- 1 | # k-means step, given data, `X` and centroids 2 | # https://realpython.com/numpy-array-programming/#clustering-algorithms 3 | import numpy as np 4 | import torch 5 | torch.set_default_device("cpu") 6 | import torch._dynamo.config as cfg 7 | cfg.numpy_ndarray_as_tensor = True 8 | 9 | 10 | # np.linalg.norm replacement (2-norm only), https://github.com/pytorch/pytorch/issues/105269 11 | def norm(a, axis): 12 | s = (a.conj() * a).real 13 | return np.sqrt(s.sum(axis=axis)) 14 | 15 | 16 | #@torch.compile 17 | def get_labels(X, centroids) -> np.ndarray: 18 | return np.argmin(norm(X - centroids[:, None], axis=2), 19 | axis=0) 20 | 21 | 22 | def init(npts): 23 | np.random.seed(12345) 24 | X = np.repeat([[5, 5], [10, 10]], [npts, npts], axis=0) 25 | X = X + np.random.randn(*X.shape) # 2 distinct "blobs" 26 | centroids = np.array([[5, 5], [10, 10]]) 27 | return X, centroids 28 | 29 | 30 | ################ benchmark ##################### 31 | import time 32 | 33 | # ### numpy ### 34 | npts = int(2e7) 35 | X, centroids = init(npts) 36 | 37 | start_time = time.time() 38 | labels = get_labels(X, centroids) 39 | end_time = time.time() 40 | numpy_time = end_time - start_time 41 | print("\n\nnumpy: elapsed=", numpy_time) 42 | 43 | 44 | # ### compile ### 45 | get_labels_c = torch.compile(get_labels) 46 | 47 | # ### warm up ### 48 | for _ in range(5): 49 | get_labels_c(X, centroids) 50 | 51 | 52 | # ### measure ### 53 | start_time = time.time() 54 | labels = get_labels_c(X, centroids) 55 | end_time = time.time() 56 | compiled_time = end_time - start_time 57 | print("compiled: elapsed=", compiled_time, ' speedup = ', numpy_time / compiled_time) 58 | 59 | -------------------------------------------------------------------------------- /e2e/smoke/eager/smoke_nompl.py: -------------------------------------------------------------------------------- 1 | # ----------------------------------------------------------------------------- 2 | # From Numpy to Python 3 | # Copyright (2017) Nicolas P. Rougier - BSD license 4 | # More information at https://github.com/rougier/numpy-book 5 | # ----------------------------------------------------------------------------- 6 | import torch_np as np 7 | from smoke_solver import vel_step, dens_step 8 | 9 | N = 128 10 | size = N + 2 11 | dt = 0.1 12 | diff = 0.0 13 | visc = 0.0 14 | force = 5.0 15 | source = 100.0 16 | 17 | u = np.zeros((size, size), np.float32) # velocity 18 | u_prev = np.zeros((size, size), np.float32) 19 | 20 | v = np.zeros((size, size), np.float32) # velocity 21 | v_prev = np.zeros((size, size), np.float32) 22 | 23 | dens = np.zeros((size, size), np.float32) # density 24 | dens_prev = np.zeros((size, size), np.float32) 25 | 26 | # initialization 27 | 28 | u[:, :] = 0.0 29 | v[:, :] = 0.0 30 | u_prev[:, :] = 0.0 31 | v_prev[:, :] = 0.0 32 | dens[:, :] = 0.0 33 | dens_prev[:, :] = 0.0 34 | 35 | def disc(shape=(size, size), center=(size/2, size/2), radius=10): 36 | def distance(x, y): 37 | return np.sqrt((x-center[0])**2+(y-center[1])**2) 38 | 39 | args = np.indices(shape, dtype=float) 40 | D = distance(*args) 41 | return np.where(D <= radius, True, False) 42 | 43 | D = disc(radius=32) ^ disc(radius=16) 44 | dens[...] = D*source/10 45 | 46 | np.random.seed(1234) 47 | u[:, :] = force * 0.1 * np.random.uniform(-1, 1, u.shape) 48 | v[:, :] = force * 0.1 * np.random.uniform(-1, 1, u.shape) 49 | 50 | 51 | # update 52 | def update_single(N, dens, u, v, u_prev, v_prev, visc, diff, dt): 53 | vel_step(N, u, v, u_prev, v_prev, visc, dt) 54 | dens_step(N, dens, dens_prev, u, v, diff, dt) 55 | 56 | n_steps = 28 57 | for _ in range(n_steps): 58 | update_single(N, dens, u, v, u_prev, v_prev, visc, diff, dt) 59 | 60 | # dump the density field 61 | import numpy as _np 62 | _np.savez_compressed(f'smoke_density_eager_{n_steps}.npy', dens.tensor.numpy()) 63 | -------------------------------------------------------------------------------- /e2e/mojo_mandelbrot/compiled.py: -------------------------------------------------------------------------------- 1 | import numpy as np # otherwise @compile is ImportError 2 | import torch._dynamo.config as cfg 3 | import torch 4 | 5 | cfg.numpy_ndarray_as_tensor = True 6 | device="cpu" 7 | 8 | 9 | def init(): 10 | # Define the boundaries of the complex plane 11 | xn = 450 12 | yn = 375 13 | xmin = -2.25 14 | xmax = 0.75 15 | ymin = -1.25 16 | ymax = 1.25 17 | 18 | # Create the grid of complex numbers 19 | x_values = np.linspace(xmin, xmax, xn, dtype='float32') 20 | y_values = np.linspace(ymin, ymax, yn, dtype='float32') 21 | 22 | rx, iy = np.meshgrid(x_values, y_values, indexing='xy') 23 | 24 | return rx, iy 25 | 26 | 27 | def simulate(rx, iy, step, max_iter=200): 28 | x = rx.copy() 29 | y = iy.copy() 30 | mask = np.zeros_like(x) 31 | for _ in range(max_iter): 32 | mask = step(x, y, rx, iy, mask) 33 | return mask 34 | 35 | 36 | def step(x, y, rx, iy, mask): 37 | x_prev = x 38 | y_prev = y 39 | x = x_prev**2 - y_prev**2 + rx 40 | y = 2*x_prev*y_prev + iy 41 | inside = np.sqrt(x**2 + y**2) <= 2 # sqrt is from the OP 42 | mask += inside 43 | return mask 44 | 45 | 46 | 47 | rx, iy = init() 48 | mask = simulate(rx, iy, step, max_iter=3) 49 | 50 | print(np.count_nonzero(mask)) 51 | 52 | 53 | ################ benchmark ##################### 54 | import time 55 | 56 | # ### numpy ### 57 | 58 | 59 | rx, iy = init() 60 | start_time = time.time() 61 | mask = simulate(rx, iy, step, max_iter=1000) 62 | end_time = time.time() 63 | numpy_time = end_time - start_time 64 | print("\n\nnumpy: elapsed=", numpy_time) 65 | 66 | 67 | # ### compile ### 68 | 69 | step_c = torch.compile(step) 70 | 71 | # ### warm up ### 72 | rx, iy = init() 73 | for _ in range(50): 74 | simulate(rx, iy, step_c, max_iter=3) 75 | 76 | 77 | rx, iy = init() 78 | start_time = time.time() 79 | mask = simulate(rx, iy, step_c, max_iter=1000) 80 | end_time = time.time() 81 | 82 | 83 | compiled_time = end_time - start_time 84 | print("compiled: elapsed=", compiled_time, ' speedup = ', numpy_time / compiled_time) 85 | 86 | -------------------------------------------------------------------------------- /torch_np/_funcs.py: -------------------------------------------------------------------------------- 1 | import inspect 2 | 3 | from . import _funcs_impl 4 | from ._normalizations import normalizer 5 | 6 | # _funcs_impl.py contains functions which mimic NumPy's eponimous equivalents, 7 | # and consume/return PyTorch tensors/dtypes. 8 | # They are also type annotated. 9 | # Pull these functions from _funcs_impl and decorate them with @normalizer, which 10 | # - Converts any input `np.ndarray`, `torch_np.ndarray`, list of lists, Python scalars, etc into a `torch.Tensor`. 11 | # - Maps NumPy dtypes to PyTorch dtypes 12 | # - If the input to the `axis` kwarg is an ndarray, it maps it into a tuple 13 | # - Implements the semantics for the `out=` arg 14 | # - Wraps back the outputs into `torch_np.ndarrays` 15 | 16 | __all__ = [ 17 | x 18 | for x in dir(_funcs_impl) 19 | if inspect.isfunction(getattr(_funcs_impl, x)) and not x.startswith("_") 20 | ] 21 | 22 | # decorate implementer functions with argument normalizers and export to the top namespace 23 | for name in __all__: 24 | func = getattr(_funcs_impl, name) 25 | if name in ["percentile", "quantile", "median"]: 26 | decorated = normalizer(func, promote_scalar_result=True) 27 | elif name == "einsum": 28 | # normalized manually 29 | decorated = func 30 | else: 31 | decorated = normalizer(func) 32 | 33 | decorated.__qualname__ = name 34 | decorated.__name__ = name 35 | vars()[name] = decorated 36 | 37 | 38 | """ 39 | Vendored objects from numpy.lib.index_tricks 40 | """ 41 | 42 | 43 | class IndexExpression: 44 | """ 45 | Written by Konrad Hinsen 46 | last revision: 1999-7-23 47 | 48 | Cosmetic changes by T. Oliphant 2001 49 | """ 50 | 51 | def __init__(self, maketuple): 52 | self.maketuple = maketuple 53 | 54 | def __getitem__(self, item): 55 | if self.maketuple and not isinstance(item, tuple): 56 | return (item,) 57 | else: 58 | return item 59 | 60 | 61 | index_exp = IndexExpression(maketuple=True) 62 | s_ = IndexExpression(maketuple=False) 63 | 64 | 65 | __all__ += ["index_exp", "s_"] 66 | -------------------------------------------------------------------------------- /e2e/smoke/smoke_nompl.py: -------------------------------------------------------------------------------- 1 | # ----------------------------------------------------------------------------- 2 | # From Numpy to Python 3 | # Copyright (2017) Nicolas P. Rougier - BSD license 4 | # More information at https://github.com/rougier/numpy-book 5 | # ----------------------------------------------------------------------------- 6 | import numpy as np 7 | from smoke_solver import vel_step, dens_step 8 | 9 | N = 128 10 | size = N + 2 11 | dt = 0.1 12 | diff = 0.0 13 | visc = 0.0 14 | force = 5.0 15 | source = 100.0 16 | 17 | u = np.zeros((size, size), np.float32) # velocity 18 | u_prev = np.zeros((size, size), np.float32) 19 | 20 | v = np.zeros((size, size), np.float32) # velocity 21 | v_prev = np.zeros((size, size), np.float32) 22 | 23 | dens = np.zeros((size, size), np.float32) # density 24 | dens_prev = np.zeros((size, size), np.float32) 25 | 26 | # initialization 27 | 28 | u[:, :] = 0.0 29 | v[:, :] = 0.0 30 | u_prev[:, :] = 0.0 31 | v_prev[:, :] = 0.0 32 | dens[:, :] = 0.0 33 | dens_prev[:, :] = 0.0 34 | 35 | def disc(shape=(size, size), center=(size/2, size/2), radius=10): 36 | def distance(x, y): 37 | return np.sqrt((x-center[0])**2+(y-center[1])**2) 38 | 39 | args = np.indices(shape, dtype=float) 40 | D = distance(*args) 41 | return np.where(D <= radius, True, False) 42 | 43 | D = disc(radius=32) ^ disc(radius=16) 44 | dens[...] = D*source/10 45 | 46 | np.random.seed(1234) 47 | u[:, :] = force * 0.1 * np.random.uniform(-1, 1, u.shape) 48 | v[:, :] = force * 0.1 * np.random.uniform(-1, 1, u.shape) 49 | 50 | 51 | # update 52 | import torch 53 | torch.set_default_device("cpu") 54 | 55 | import torch._dynamo.config as cfg 56 | cfg.numpy_ndarray_as_tensor = True 57 | 58 | @torch.compile 59 | def update_single(N, dens, u, v, u_prev, v_prev, visc, diff, dt): 60 | vel_step(N, u, v, u_prev, v_prev, visc, dt) 61 | dens_step(N, dens, dens_prev, u, v, diff, dt) 62 | 63 | n_steps = 28 64 | for _ in range(n_steps): 65 | update_single(N, dens, u, v, u_prev, v_prev, visc, diff, dt) 66 | 67 | # dump the density field 68 | np.savez_compressed(f'smoke_density_compiled_{n_steps}.npy', dens) 69 | -------------------------------------------------------------------------------- /torch_np/_binary_ufuncs_impl.py: -------------------------------------------------------------------------------- 1 | """Export torch work functions for binary ufuncs, rename/tweak to match numpy. 2 | This listing is further exported to public symbols in the `torch_np/_ufuncs.py` module. 3 | """ 4 | 5 | import torch 6 | 7 | # renames 8 | from torch import add, arctan2, bitwise_and 9 | from torch import bitwise_left_shift as left_shift 10 | from torch import bitwise_or 11 | from torch import bitwise_right_shift as right_shift 12 | from torch import bitwise_xor, copysign, divide 13 | from torch import eq as equal 14 | from torch import ( 15 | float_power, 16 | floor_divide, 17 | fmax, 18 | fmin, 19 | fmod, 20 | gcd, 21 | greater, 22 | greater_equal, 23 | heaviside, 24 | hypot, 25 | lcm, 26 | ldexp, 27 | less, 28 | less_equal, 29 | logaddexp, 30 | logaddexp2, 31 | logical_and, 32 | logical_or, 33 | logical_xor, 34 | maximum, 35 | minimum, 36 | multiply, 37 | nextafter, 38 | not_equal, 39 | ) 40 | from torch import pow as power 41 | from torch import remainder 42 | from torch import remainder as mod 43 | from torch import subtract, true_divide 44 | 45 | from . import _dtypes_impl, _util 46 | 47 | 48 | # work around torch limitations w.r.t. numpy 49 | def matmul(x, y): 50 | # work around: 51 | # - RuntimeError: expected scalar type Int but found Double 52 | # - RuntimeError: "addmm_impl_cpu_" not implemented for 'Bool' 53 | # - RuntimeError: "addmm_impl_cpu_" not implemented for 'Half' 54 | dtype = _dtypes_impl.result_type_impl(x, y) 55 | is_bool = dtype == torch.bool 56 | is_half = dtype == torch.float16 57 | 58 | work_dtype = dtype 59 | if is_bool: 60 | work_dtype = torch.uint8 61 | if is_half: 62 | work_dtype = torch.float32 63 | 64 | x = _util.cast_if_needed(x, work_dtype) 65 | y = _util.cast_if_needed(y, work_dtype) 66 | 67 | result = torch.matmul(x, y) 68 | 69 | if work_dtype != dtype: 70 | result = result.to(dtype) 71 | 72 | return result 73 | 74 | 75 | # a stub implementation of divmod, should be improved after 76 | # https://github.com/pytorch/pytorch/issues/90820 is fixed in pytorch 77 | def divmod(x, y): 78 | return x // y, x % y 79 | -------------------------------------------------------------------------------- /torch_np/tests/conftest.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | import pytest 4 | 5 | import torch_np as tnp 6 | 7 | 8 | def pytest_configure(config): 9 | config.addinivalue_line("markers", "slow: very slow tests") 10 | 11 | 12 | def pytest_addoption(parser): 13 | parser.addoption("--runslow", action="store_true", help="run slow tests") 14 | parser.addoption("--nonp", action="store_true", help="error when NumPy is accessed") 15 | 16 | 17 | class Inaccessible: 18 | def __getattribute__(self, attr): 19 | raise RuntimeError(f"Using --nonp but accessed np.{attr}") 20 | 21 | 22 | def pytest_sessionstart(session): 23 | if session.config.getoption("--nonp"): 24 | sys.modules["numpy"] = Inaccessible() 25 | 26 | 27 | def pytest_generate_tests(metafunc): 28 | """ 29 | Hook to parametrize test cases 30 | See https://docs.pytest.org/en/6.2.x/parametrize.html#pytest-generate-tests 31 | 32 | The logic here allows us to test with both NumPy-proper and torch_np. 33 | Normally we'd just test torch_np, e.g. 34 | 35 | import torch_np as np 36 | ... 37 | def test_foo(): 38 | np.array([42]) 39 | ... 40 | 41 | but this hook allows us to test NumPy-proper as well, e.g. 42 | 43 | def test_foo(np): 44 | np.array([42]) 45 | ... 46 | 47 | np is a pytest parameter, which is either NumPy-proper or torch_np. This 48 | allows us to sanity check our own tests, so that tested behaviour is 49 | consistent with NumPy-proper. 50 | 51 | pytest will have test names respective to the library being tested, e.g. 52 | 53 | $ pytest --collect-only 54 | test_foo[torch_np] 55 | test_foo[numpy] 56 | 57 | """ 58 | np_params = [tnp] 59 | 60 | try: 61 | import numpy as np 62 | except ImportError: 63 | pass 64 | else: 65 | if not isinstance(np, Inaccessible): # i.e. --nonp was used 66 | np_params.append(np) 67 | 68 | if "np" in metafunc.fixturenames: 69 | metafunc.parametrize("np", np_params) 70 | 71 | 72 | def pytest_collection_modifyitems(config, items): 73 | if not config.getoption("--runslow"): 74 | skip_slow = pytest.mark.skip(reason="slow test, use --runslow to run") 75 | for item in items: 76 | if "slow" in item.keywords: 77 | item.add_marker(skip_slow) 78 | -------------------------------------------------------------------------------- /autogen/gen_dtypes.py: -------------------------------------------------------------------------------- 1 | """ Define the supported dtypes and numpy <--> torch dtype mapping, define casting rules. 2 | """ 3 | 4 | # TODO: 1. define torch_np dtypes, make this work without numpy. 5 | # 2. mimic numpy's various aliases (np.half == np.float16, dtype='i8' etc) 6 | # 3. convert from python types: np.ones(3, dtype=float) etc 7 | 8 | import numpy as np 9 | import torch 10 | 11 | np._set_promotion_state("weak") 12 | 13 | 14 | class dtype: 15 | def __init__(self, name): 16 | self._name = name 17 | 18 | 19 | dt_names = [ 20 | "float16", 21 | "float32", 22 | "float64", 23 | "complex64", 24 | "complex128", 25 | "uint8", 26 | "int8", 27 | "int16", 28 | "int32", 29 | "int64", 30 | "bool", 31 | ] 32 | 33 | 34 | tmap = {dt: torch.as_tensor(np.ones(1, dtype=dt)).dtype for dt in dt_names} 35 | 36 | 37 | templ = """\ 38 | {name} = dtype("{name}") 39 | """ 40 | 41 | 42 | ############### Output the dtypes ############# 43 | 44 | src_lines = [templ.format(name=name) for name in dt_names] 45 | src = "".join(src_lines) 46 | print(src) 47 | 48 | 49 | ############### Output the casting dict ############3 50 | 51 | _casting_modes = ["no", "equiv", "safe", "same_kind", "unsafe"] 52 | 53 | # The structure is 54 | # _can_cast_dict["safe"]["dtyp1"]["dtyp2"] 55 | 56 | 57 | def generate_can_cast(casting): 58 | """Dump the np casting table""" 59 | dct = {} 60 | for dtyp1 in dt_names: 61 | dct_dtyp1 = {} 62 | for dtyp2 in dt_names: 63 | can_cast = np.can_cast(np.dtype(dtyp1), np.dtype(dtyp2), casting=casting) 64 | dct_dtyp1[tmap[dtyp2]] = can_cast 65 | dct[tmap[dtyp1]] = dct_dtyp1 66 | return dct 67 | 68 | 69 | def generate_result_type(): 70 | """Dump the np.result_type table.""" 71 | dct = {} 72 | for dtyp1 in dt_names: 73 | dct_dtyp1 = {} 74 | for dtyp2 in dt_names: 75 | result_type = np.result_type(np.dtype(dtyp1), np.dtype(dtyp2)) 76 | dct_dtyp1[tmap[dtyp2]] = tmap[result_type.name] 77 | dct[tmap[dtyp1]] = dct_dtyp1 78 | return dct 79 | 80 | 81 | # pprint compact=True doesn't quite work :-) 82 | # import pprint 83 | # pprint.pprint(_can_cast_dict['no']['int32'], compact=True, width=100) 84 | 85 | 86 | preamble = f""" 87 | # These two dicts are autogenerated with autogen/gen_dtypes.py, 88 | # using numpy version {np.__version__}. 89 | """ 90 | print(preamble) 91 | 92 | _can_cast_dict = {} 93 | for casting in _casting_modes: 94 | _can_cast_dict[casting] = generate_can_cast(casting) 95 | 96 | 97 | print("_can_cast_dict = ", _can_cast_dict) 98 | print("\n") 99 | print("_result_type_dict = ", generate_result_type()) 100 | -------------------------------------------------------------------------------- /e2e/maze/maze.output.np.txt: -------------------------------------------------------------------------------- 1 | Z = [[ True True True ... True True True] 2 | [ True False False ... True False True] 3 | [ True False True ... True False True] 4 | ... 5 | [ True False True ... True False True] 6 | [ True False False ... True False True] 7 | [ True True True ... True True True]] 8 | P = [[79 39] 9 | [79 38] 10 | [79 37] 11 | [79 36] 12 | [79 35] 13 | [78 35] 14 | [77 35] 15 | [76 35] 16 | [75 35] 17 | [75 36] 18 | [75 37] 19 | [74 37] 20 | [73 37] 21 | [72 37] 22 | [71 37] 23 | [71 36] 24 | [71 35] 25 | [70 35] 26 | [69 35] 27 | [69 34] 28 | [69 33] 29 | [68 33] 30 | [67 33] 31 | [67 34] 32 | [67 35] 33 | [67 36] 34 | [67 37] 35 | [66 37] 36 | [65 37] 37 | [64 37] 38 | [63 37] 39 | [62 37] 40 | [61 37] 41 | [61 38] 42 | [61 39] 43 | [60 39] 44 | [59 39] 45 | [58 39] 46 | [57 39] 47 | [56 39] 48 | [55 39] 49 | [54 39] 50 | [53 39] 51 | [52 39] 52 | [51 39] 53 | [50 39] 54 | [49 39] 55 | [48 39] 56 | [47 39] 57 | [46 39] 58 | [45 39] 59 | [45 38] 60 | [45 37] 61 | [45 36] 62 | [45 35] 63 | [44 35] 64 | [43 35] 65 | [43 34] 66 | [43 33] 67 | [43 32] 68 | [43 31] 69 | [44 31] 70 | [45 31] 71 | [45 30] 72 | [45 29] 73 | [46 29] 74 | [47 29] 75 | [47 28] 76 | [47 27] 77 | [48 27] 78 | [49 27] 79 | [50 27] 80 | [51 27] 81 | [51 26] 82 | [51 25] 83 | [51 24] 84 | [51 23] 85 | [50 23] 86 | [49 23] 87 | [48 23] 88 | [47 23] 89 | [46 23] 90 | [45 23] 91 | [44 23] 92 | [43 23] 93 | [43 22] 94 | [43 21] 95 | [42 21] 96 | [41 21] 97 | [40 21] 98 | [39 21] 99 | [38 21] 100 | [37 21] 101 | [36 21] 102 | [35 21] 103 | [34 21] 104 | [33 21] 105 | [32 21] 106 | [31 21] 107 | [31 20] 108 | [31 19] 109 | [32 19] 110 | [33 19] 111 | [33 18] 112 | [33 17] 113 | [33 16] 114 | [33 15] 115 | [33 14] 116 | [33 13] 117 | [32 13] 118 | [31 13] 119 | [30 13] 120 | [29 13] 121 | [29 12] 122 | [29 11] 123 | [28 11] 124 | [27 11] 125 | [26 11] 126 | [25 11] 127 | [24 11] 128 | [23 11] 129 | [22 11] 130 | [21 11] 131 | [20 11] 132 | [19 11] 133 | [19 12] 134 | [19 13] 135 | [19 14] 136 | [19 15] 137 | [19 16] 138 | [19 17] 139 | [19 18] 140 | [19 19] 141 | [18 19] 142 | [17 19] 143 | [16 19] 144 | [15 19] 145 | [15 18] 146 | [15 17] 147 | [14 17] 148 | [13 17] 149 | [12 17] 150 | [11 17] 151 | [11 16] 152 | [11 15] 153 | [11 14] 154 | [11 13] 155 | [11 12] 156 | [11 11] 157 | [11 10] 158 | [11 9] 159 | [10 9] 160 | [ 9 9] 161 | [ 8 9] 162 | [ 7 9] 163 | [ 6 9] 164 | [ 5 9] 165 | [ 4 9] 166 | [ 3 9] 167 | [ 3 10] 168 | [ 3 11] 169 | [ 3 12] 170 | [ 3 13] 171 | [ 2 13] 172 | [ 1 13] 173 | [ 1 12] 174 | [ 1 11] 175 | [ 1 10] 176 | [ 1 9] 177 | [ 1 8] 178 | [ 1 7] 179 | [ 1 6] 180 | [ 1 5] 181 | [ 1 4] 182 | [ 1 3] 183 | [ 1 2] 184 | [ 1 1]] 185 | -------------------------------------------------------------------------------- /e2e/mandelbrot/mandelbrot.py: -------------------------------------------------------------------------------- 1 | # ----------------------------------------------------------------------------- 2 | # From Numpy to Python 3 | # Copyright (2017) Nicolas P. Rougier - BSD license 4 | # More information at https://github.com/rougier/numpy-book 5 | # ----------------------------------------------------------------------------- 6 | #import numpy as np 7 | import torch_np as np 8 | 9 | 10 | # To run on CUDA, change "cpu" to "cuda" below. 11 | import torch 12 | torch.set_default_device("cpu") 13 | 14 | 15 | # from mandelbrot_numpy_1 import mandelbrot # copy-paste below 16 | 17 | def mandelbrot(xmin, xmax, ymin, ymax, xn, yn, maxiter, horizon=2.0): 18 | # Adapted from https://www.ibm.com/developerworks/community/blogs/jfp/... 19 | # .../entry/How_To_Compute_Mandelbrodt_Set_Quickly?lang=en 20 | X = np.linspace(xmin, xmax, xn, dtype=np.float32) 21 | Y = np.linspace(ymin, ymax, yn, dtype=np.float32) 22 | C = X + Y[:,None]*1j 23 | N = np.zeros(C.shape, dtype=int) 24 | Z = np.zeros(C.shape, np.complex64) 25 | for n in range(maxiter): 26 | I = np.less(abs(Z), horizon) 27 | N[I] = n 28 | Z[I] = Z[I]**2 + C[I] 29 | N[N == maxiter-1] = 0 30 | return Z, N 31 | 32 | 33 | if __name__ == '__main__': 34 | from matplotlib import colors 35 | import matplotlib.pyplot as plt 36 | ## from timeit import timeit 37 | 38 | # Benchmark 39 | xmin, xmax, xn = -2.25, +0.75, int(3000/3) 40 | ymin, ymax, yn = -1.25, +1.25, int(2500/3) 41 | maxiter = 200 42 | ## timeit("mandelbrot_1(xmin, xmax, ymin, ymax, xn, yn, maxiter)", globals()) 43 | ## timeit("mandelbrot_2(xmin, xmax, ymin, ymax, xn, yn, maxiter)", globals()) 44 | ## timeit("mandelbrot_3(xmin, xmax, ymin, ymax, xn, yn, maxiter)", globals()) 45 | 46 | # Visualization 47 | xmin, xmax, xn = -2.25, +0.75, int(3000/2) 48 | ymin, ymax, yn = -1.25, +1.25, int(2500/2) 49 | maxiter = 200 50 | horizon = 2.0 ** 40 51 | log_horizon = np.log(np.log(horizon))/np.log(2) 52 | Z, N = mandelbrot(xmin, xmax, ymin, ymax, xn, yn, maxiter, horizon) 53 | 54 | # Normalized recount as explained in: 55 | # http://linas.org/art-gallery/escape/smooth.html 56 | M = np.nan_to_num(N + 1 - np.log(np.log(abs(Z)))/np.log(2) + log_horizon) 57 | 58 | dpi = 72 59 | width = 10 60 | height = 10*yn/xn 61 | 62 | fig = plt.figure(figsize=(width, height), dpi=dpi) 63 | ax = fig.add_axes([0.0, 0.0, 1.0, 1.0], frameon=False, aspect=1) 64 | 65 | light = colors.LightSource(azdeg=315, altdeg=10) 66 | 67 | plt.imshow(light.shade(M.tensor.cpu().numpy(), cmap=plt.cm.hot, vert_exag=1.5, 68 | norm = colors.PowerNorm(0.3), blend_mode='hsv'), 69 | extent=[xmin, xmax, ymin, ymax], interpolation="bicubic") 70 | ax.set_xticks([]) 71 | ax.set_yticks([]) 72 | plt.savefig("mandelbrot.png") 73 | plt.show() 74 | 75 | -------------------------------------------------------------------------------- /autogen/dump_namespace.py: -------------------------------------------------------------------------------- 1 | import inspect 2 | from collections import defaultdict 3 | 4 | import numpy as np 5 | 6 | 7 | def grab_namespace(np=np): 8 | """grab the numpy namespace content""" 9 | dct = defaultdict(list) 10 | for name in dir(np): 11 | obj = getattr(np, name) 12 | dct[type(obj).__name__].append(obj) 13 | return dct 14 | 15 | 16 | def get_signature(obj): 17 | """Get a text signature of an object.""" 18 | try: 19 | return obj.__name__ + str(inspect.signature(obj)) 20 | except Exception: 21 | # builtins don't have it, try the first line of the docstring 22 | d = obj.__doc__.split("\n") 23 | if d[0]: 24 | return d[0].strip() 25 | else: 26 | # empty line, maybe the is lines 2-3 (np.vectorize) 27 | return "\n".join((d[1], d[2])).strip() 28 | 29 | 30 | def dump_signatures(keys, namespace=None, replace=None): 31 | """Dump a pseudo-python source for a subset of signatures.""" 32 | if namespace is None: 33 | namespace = np 34 | dct = grab_namespace(namespace) 35 | 36 | out = "\n" 37 | for key in keys: 38 | lst = dct[key] 39 | for obj in lst: 40 | sig = get_signature(obj) 41 | 42 | if replace: 43 | for old in replace: 44 | sig = sig.replace(old, replace[old]) 45 | 46 | out += f"def {sig}:\n raise NotImplementedError\n\n" 47 | return out 48 | 49 | 50 | def dump_difference(namespace): 51 | import torch_np 52 | 53 | dct_wrapper = grab_namespace(torch_np) 54 | wrapper_funcs = set([obj.__name__ for obj in dct_wrapper["function"]]) 55 | 56 | dct_api = grab_namespace(namespace) 57 | namespace_funcs = set(obj.__name__ for obj in dct_api["function"]) 58 | 59 | missing_names = namespace_funcs.difference(wrapper_funcs) 60 | 61 | for name in sorted(missing_names): 62 | print("- [ ]", name) 63 | 64 | breakpoint() 65 | 66 | extras = wrapper_funcs.difference(namespace_funcs) 67 | print("\n\n") 68 | for name in sorted(extras): 69 | print("- [ ]", name) 70 | 71 | 72 | if __name__ == "__main__": 73 | 74 | # dct = grab_namespace(np) 75 | # print(dct.keys()) 76 | 77 | # for obj in dct['function']: 78 | # print( get_signature(obj) ) 79 | 80 | # dump array_api, full_signatures 81 | # from numpy import array_api 82 | 83 | # keys = ["builtin_function_or_method", "function"] 84 | # replace = {"": "NoValue"} 85 | 86 | # print(dump_signatures(keys, namespace=array_api, replace=replace)) 87 | 88 | # dump the difference 89 | from numpy import array_api 90 | 91 | dump_difference(array_api) 92 | 93 | # keys = ["builtin_function_or_method", "function"] 94 | # replace = {"": "NoValue"} 95 | # print(dump_signatures(keys, namespace=array_api, replace=replace)) 96 | -------------------------------------------------------------------------------- /e2e/smoke/smoke_1.py: -------------------------------------------------------------------------------- 1 | # ----------------------------------------------------------------------------- 2 | # From Numpy to Python 3 | # Copyright (2017) Nicolas P. Rougier - BSD license 4 | # More information at https://github.com/rougier/numpy-book 5 | # ----------------------------------------------------------------------------- 6 | import numpy as np 7 | from smoke_solver import vel_step, dens_step 8 | 9 | N = 128 10 | size = N + 2 11 | dt = 0.1 12 | diff = 0.0 13 | visc = 0.0 14 | force = 5.0 15 | source = 100.0 16 | 17 | u = np.zeros((size, size), np.float32) # velocity 18 | u_prev = np.zeros((size, size), np.float32) 19 | 20 | v = np.zeros((size, size), np.float32) # velocity 21 | v_prev = np.zeros((size, size), np.float32) 22 | 23 | dens = np.zeros((size, size), np.float32) # density 24 | dens_prev = np.zeros((size, size), np.float32) 25 | 26 | 27 | def initialization(): 28 | global u, v, u_prev, v_prev, dens, dens_prev, size 29 | 30 | u[:, :] = 0.0 31 | v[:, :] = 0.0 32 | u_prev[:, :] = 0.0 33 | v_prev[:, :] = 0.0 34 | dens[:, :] = 0.0 35 | dens_prev[:, :] = 0.0 36 | 37 | def disc(shape=(size, size), center=(size/2, size/2), radius=10): 38 | def distance(x, y): 39 | return np.sqrt((x-center[0])**2+(y-center[1])**2) 40 | 41 | 42 | # inline np.fromfunction: https://github.com/numpy/numpy/blob/v1.24.0/numpy/core/numeric.py#L1798-L1866 43 | # D = np.fromfunction(distance, shape) 44 | 45 | args = np.indices(shape, dtype=float) 46 | D = distance(*args) 47 | return np.where(D <= radius, True, False) 48 | 49 | D = disc(radius=32) ^ disc(radius=16) 50 | dens[...] = D*source/10 51 | 52 | u[:, :] = force * 0.1 * np.random.uniform(-1, 1, u.shape) 53 | v[:, :] = force * 0.1 * np.random.uniform(-1, 1, u.shape) 54 | 55 | 56 | def update(*args): 57 | global im, dens, dens_prev, u, u_prev, v, v_prev, N, visc, dt, diff 58 | 59 | vel_step(N, u, v, u_prev, v_prev, visc, dt) 60 | dens_step(N, dens, dens_prev, u, v, diff, dt) 61 | im.set_data(dens) 62 | im.set_clim(vmin=dens.min(), vmax=dens.max()) 63 | 64 | 65 | if __name__ == '__main__': 66 | import matplotlib.pyplot as plt 67 | from matplotlib.animation import FuncAnimation 68 | 69 | fig = plt.figure(figsize=(5, 5)) 70 | ax = fig.add_axes([0, 0, 1, 1], frameon=False) 71 | ax.set_xlim(0, 1) 72 | ax.set_xticks([]) 73 | ax.set_ylim(0, 1) 74 | ax.set_yticks([]) 75 | 76 | initialization() 77 | im = ax.imshow(dens[1:-1, 1:-1], 78 | interpolation='bicubic', extent=[0, 1, 0, 1], 79 | cmap=plt.cm.magma, origin="lower", vmin=0, vmax=1) 80 | animation = FuncAnimation(fig, update, interval=10, frames=800) 81 | animation.save('smoke-1.mp4', fps=40, dpi=80, bitrate=-1, 82 | codec="libx264", extra_args=['-pix_fmt', 'yuv420p'], 83 | metadata={'artist':'Nicolas P. Rougier'}) 84 | plt.show() 85 | -------------------------------------------------------------------------------- /e2e/smoke/eager/smoke_1.py: -------------------------------------------------------------------------------- 1 | # ----------------------------------------------------------------------------- 2 | # From Numpy to Python 3 | # Copyright (2017) Nicolas P. Rougier - BSD license 4 | # More information at https://github.com/rougier/numpy-book 5 | # ----------------------------------------------------------------------------- 6 | import torch_np as np 7 | from smoke_solver import vel_step, dens_step 8 | 9 | N = 128 10 | size = N + 2 11 | dt = 0.1 12 | diff = 0.0 13 | visc = 0.0 14 | force = 5.0 15 | source = 100.0 16 | 17 | u = np.zeros((size, size), np.float32) # velocity 18 | u_prev = np.zeros((size, size), np.float32) 19 | 20 | v = np.zeros((size, size), np.float32) # velocity 21 | v_prev = np.zeros((size, size), np.float32) 22 | 23 | dens = np.zeros((size, size), np.float32) # density 24 | dens_prev = np.zeros((size, size), np.float32) 25 | 26 | 27 | def initialization(): 28 | global u, v, u_prev, v_prev, dens, dens_prev, size 29 | 30 | u[:, :] = 0.0 31 | v[:, :] = 0.0 32 | u_prev[:, :] = 0.0 33 | v_prev[:, :] = 0.0 34 | dens[:, :] = 0.0 35 | dens_prev[:, :] = 0.0 36 | 37 | def disc(shape=(size, size), center=(size/2, size/2), radius=10): 38 | def distance(x, y): 39 | return np.sqrt((x-center[0])**2+(y-center[1])**2) 40 | 41 | 42 | # inline np.fromfunction: https://github.com/numpy/numpy/blob/v1.24.0/numpy/core/numeric.py#L1798-L1866 43 | # D = np.fromfunction(distance, shape) 44 | 45 | args = np.indices(shape, dtype=float) 46 | D = distance(*args) 47 | return np.where(D <= radius, True, False) 48 | 49 | D = disc(radius=32) ^ disc(radius=16) 50 | dens[...] = D*source/10 51 | 52 | u[:, :] = force * 0.1 * np.random.uniform(-1, 1, u.shape) 53 | v[:, :] = force * 0.1 * np.random.uniform(-1, 1, u.shape) 54 | 55 | 56 | def update(*args): 57 | global im, dens, dens_prev, u, u_prev, v, v_prev, N, visc, dt, diff 58 | 59 | vel_step(N, u, v, u_prev, v_prev, visc, dt) 60 | dens_step(N, dens, dens_prev, u, v, diff, dt) 61 | im.set_data(dens.tensor.cpu().numpy()) 62 | im.set_clim(vmin=dens.min(), vmax=dens.max()) 63 | 64 | 65 | if __name__ == '__main__': 66 | import matplotlib.pyplot as plt 67 | from matplotlib.animation import FuncAnimation 68 | 69 | fig = plt.figure(figsize=(5, 5)) 70 | ax = fig.add_axes([0, 0, 1, 1], frameon=False) 71 | ax.set_xlim(0, 1) 72 | ax.set_xticks([]) 73 | ax.set_ylim(0, 1) 74 | ax.set_yticks([]) 75 | 76 | initialization() 77 | im = ax.imshow(dens[1:-1, 1:-1].tensor.cpu().numpy(), 78 | interpolation='bicubic', extent=[0, 1, 0, 1], 79 | cmap=plt.cm.magma, origin="lower", vmin=0, vmax=1) 80 | animation = FuncAnimation(fig, update, interval=10, frames=800) 81 | animation.save('smoke-1.mp4', fps=40, dpi=80, bitrate=-1, 82 | codec="libx264", extra_args=['-pix_fmt', 'yuv420p'], 83 | metadata={'artist':'Nicolas P. Rougier'}) 84 | plt.show() 85 | -------------------------------------------------------------------------------- /torch_np/tests/numpy_tests/core/test_scalar_ctors.py: -------------------------------------------------------------------------------- 1 | """ 2 | Test the scalar constructors, which also do type-coercion 3 | """ 4 | import pytest 5 | 6 | import torch_np as np 7 | from torch_np.testing import ( 8 | assert_equal, assert_almost_equal, assert_warns, 9 | ) 10 | 11 | 12 | class TestFromString: 13 | @pytest.mark.xfail(reason='XXX: floats from strings') 14 | def test_floating(self): 15 | # Ticket #640, floats from string 16 | fsingle = np.single('1.234') 17 | fdouble = np.double('1.234') 18 | assert_almost_equal(fsingle, 1.234) 19 | assert_almost_equal(fdouble, 1.234) 20 | 21 | @pytest.mark.xfail(reason='XXX: floats from strings') 22 | def test_floating_overflow(self): 23 | """ Strings containing an unrepresentable float overflow """ 24 | fhalf = np.half('1e10000') 25 | assert_equal(fhalf, np.inf) 26 | fsingle = np.single('1e10000') 27 | assert_equal(fsingle, np.inf) 28 | fdouble = np.double('1e10000') 29 | assert_equal(fdouble, np.inf) 30 | 31 | fhalf = np.half('-1e10000') 32 | assert_equal(fhalf, -np.inf) 33 | fsingle = np.single('-1e10000') 34 | assert_equal(fsingle, -np.inf) 35 | fdouble = np.double('-1e10000') 36 | assert_equal(fdouble, -np.inf) 37 | 38 | def test_bool(self): 39 | with pytest.raises(TypeError): 40 | np.bool_(False, garbage=True) 41 | 42 | 43 | class TestFromInt: 44 | def test_intp(self): 45 | # Ticket #99 46 | assert_equal(1024, np.intp(1024)) 47 | 48 | def test_uint64_from_negative(self): 49 | # NumPy test was asserting a DeprecationWarning 50 | assert_equal(np.uint8(-2), np.uint8(254)) 51 | 52 | 53 | int_types = [np.byte, np.short, np.intc, np.int_, np.longlong] 54 | uint_types = [np.ubyte] 55 | float_types = [np.half, np.single, np.double] 56 | cfloat_types = [np.csingle, np.cdouble] 57 | 58 | 59 | class TestArrayFromScalar: 60 | """ gh-15467 """ 61 | 62 | def _do_test(self, t1, t2): 63 | x = t1(2) 64 | arr = np.array(x, dtype=t2) 65 | # type should be preserved exactly 66 | if t2 is None: 67 | assert arr.dtype.type is t1 68 | else: 69 | assert arr.dtype.type is t2 70 | 71 | arr1 = np.asarray(x, dtype=t2) 72 | if t2 is None: 73 | assert arr1.dtype.type is t1 74 | else: 75 | assert arr1.dtype.type is t2 76 | 77 | @pytest.mark.parametrize('t1', int_types + uint_types) 78 | @pytest.mark.parametrize('t2', int_types + uint_types + [None]) 79 | def test_integers(self, t1, t2): 80 | return self._do_test(t1, t2) 81 | 82 | @pytest.mark.parametrize('t1', float_types) 83 | @pytest.mark.parametrize('t2', float_types + [None]) 84 | def test_reals(self, t1, t2): 85 | return self._do_test(t1, t2) 86 | 87 | @pytest.mark.parametrize('t1', cfloat_types) 88 | @pytest.mark.parametrize('t2', cfloat_types + [None]) 89 | def test_complex(self, t1, t2): 90 | return self._do_test(t1, t2) 91 | 92 | -------------------------------------------------------------------------------- /torch_np/tests/test_scalars_0D_arrays.py: -------------------------------------------------------------------------------- 1 | """ 2 | Basic tests to assert and illustrate the behavior around the decision to use 0D 3 | arrays in place of array scalars. 4 | 5 | Extensive tests of this sort of functionality is in numpy_tests/core/*scalar* 6 | 7 | Also test the isscalar function (which is deliberately a bit more lax). 8 | """ 9 | import pytest 10 | 11 | import torch_np as np 12 | from torch_np.testing import assert_equal 13 | 14 | 15 | @pytest.mark.parametrize( 16 | "value", [np.int64(42), np.array(42), np.asarray(42), np.asarray(np.int64(42))] 17 | ) 18 | class TestArrayScalars: 19 | def test_array_scalar_basic(self, value): 20 | assert value.ndim == 0 21 | assert value.shape == () 22 | assert value.size == 1 23 | assert value.dtype == np.dtype("int64") 24 | 25 | def test_conversion_to_int(self, value): 26 | py_scalar = int(value) 27 | assert py_scalar == 42 28 | assert isinstance(py_scalar, int) 29 | assert not isinstance(value, int) 30 | 31 | def test_decay_to_py_scalar(self, value): 32 | # NumPy distinguishes array scalars and 0D arrays. For instance 33 | # `scalar * list` is equivalent to `int(scalar) * list`, but 34 | # `0D array * list` is equivalent to `0D array * np.asarray(list)`. 35 | # Our scalars follow 0D array behavior (because they are 0D arrays) 36 | lst = [1, 2, 3] 37 | 38 | product = value * lst 39 | assert isinstance(product, np.ndarray) 40 | assert product.shape == (3,) 41 | assert_equal(product, [42, 42 * 2, 42 * 3]) 42 | 43 | # repeat with right-mulitply 44 | product = lst * value 45 | assert isinstance(product, np.ndarray) 46 | assert product.shape == (3,) 47 | assert_equal(product, [42, 42 * 2, 42 * 3]) 48 | 49 | 50 | def test_scalar_comparisons(): 51 | scalar = np.int64(42) 52 | arr = np.array(42) 53 | 54 | assert arr == scalar 55 | assert arr >= scalar 56 | assert arr <= scalar 57 | 58 | assert scalar == 42 59 | assert arr == 42 60 | 61 | 62 | class TestIsScalar: 63 | # 64 | # np.isscalar(...) checks that its argument is a numeric object with exactly one element. 65 | # 66 | # This differs from NumPy which also requires that shape == (). 67 | # 68 | scalars = [ 69 | 42, 70 | int(42.0), 71 | np.float32(42), 72 | np.array(42), 73 | [42], 74 | [[42]], 75 | np.array([42]), 76 | np.array([[42]]), 77 | ] 78 | 79 | import math 80 | 81 | not_scalars = [ 82 | int, 83 | np.float32, 84 | "s", 85 | "string", 86 | (), 87 | [], 88 | math.sin, 89 | np, 90 | np.transpose, 91 | [1, 2], 92 | np.asarray([1, 2]), 93 | np.float32([1, 2]), 94 | ] 95 | 96 | @pytest.mark.parametrize("value", scalars) 97 | def test_is_scalar(self, value): 98 | assert np.isscalar(value) 99 | 100 | @pytest.mark.parametrize("value", not_scalars) 101 | def test_is_not_scalar(self, value): 102 | assert not np.isscalar(value) 103 | -------------------------------------------------------------------------------- /torch_np/fft.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import functools 4 | 5 | import torch 6 | 7 | from . import _dtypes_impl, _util 8 | from ._normalizations import ArrayLike, normalizer 9 | 10 | 11 | def upcast(func): 12 | """NumPy fft casts inputs to 64 bit and *returns 64-bit results*.""" 13 | 14 | @functools.wraps(func) 15 | def wrapped(tensor, *args, **kwds): 16 | target_dtype = ( 17 | _dtypes_impl.default_dtypes.complex_dtype 18 | if tensor.is_complex() 19 | else _dtypes_impl.default_dtypes.float_dtype 20 | ) 21 | tensor = _util.cast_if_needed(tensor, target_dtype) 22 | return func(tensor, *args, **kwds) 23 | 24 | return wrapped 25 | 26 | 27 | @normalizer 28 | @upcast 29 | def fft(a: ArrayLike, n=None, axis=-1, norm=None): 30 | return torch.fft.fft(a, n, dim=axis, norm=norm) 31 | 32 | 33 | @normalizer 34 | @upcast 35 | def ifft(a: ArrayLike, n=None, axis=-1, norm=None): 36 | return torch.fft.ifft(a, n, dim=axis, norm=norm) 37 | 38 | 39 | @normalizer 40 | @upcast 41 | def rfft(a: ArrayLike, n=None, axis=-1, norm=None): 42 | return torch.fft.rfft(a, n, dim=axis, norm=norm) 43 | 44 | 45 | @normalizer 46 | @upcast 47 | def irfft(a: ArrayLike, n=None, axis=-1, norm=None): 48 | return torch.fft.irfft(a, n, dim=axis, norm=norm) 49 | 50 | 51 | @normalizer 52 | @upcast 53 | def fftn(a: ArrayLike, s=None, axes=None, norm=None): 54 | return torch.fft.fftn(a, s, dim=axes, norm=norm) 55 | 56 | 57 | @normalizer 58 | @upcast 59 | def ifftn(a: ArrayLike, s=None, axes=None, norm=None): 60 | return torch.fft.ifftn(a, s, dim=axes, norm=norm) 61 | 62 | 63 | @normalizer 64 | @upcast 65 | def rfftn(a: ArrayLike, s=None, axes=None, norm=None): 66 | return torch.fft.rfftn(a, s, dim=axes, norm=norm) 67 | 68 | 69 | @normalizer 70 | @upcast 71 | def irfftn(a: ArrayLike, s=None, axes=None, norm=None): 72 | return torch.fft.irfftn(a, s, dim=axes, norm=norm) 73 | 74 | 75 | @normalizer 76 | @upcast 77 | def fft2(a: ArrayLike, s=None, axes=(-2, -1), norm=None): 78 | return torch.fft.fft2(a, s, dim=axes, norm=norm) 79 | 80 | 81 | @normalizer 82 | @upcast 83 | def ifft2(a: ArrayLike, s=None, axes=(-2, -1), norm=None): 84 | return torch.fft.ifft2(a, s, dim=axes, norm=norm) 85 | 86 | 87 | @normalizer 88 | @upcast 89 | def rfft2(a: ArrayLike, s=None, axes=(-2, -1), norm=None): 90 | return torch.fft.rfft2(a, s, dim=axes, norm=norm) 91 | 92 | 93 | @normalizer 94 | @upcast 95 | def irfft2(a: ArrayLike, s=None, axes=(-2, -1), norm=None): 96 | return torch.fft.irfft2(a, s, dim=axes, norm=norm) 97 | 98 | 99 | @normalizer 100 | @upcast 101 | def hfft(a: ArrayLike, n=None, axis=-1, norm=None): 102 | return torch.fft.hfft(a, n, dim=axis, norm=norm) 103 | 104 | 105 | @normalizer 106 | @upcast 107 | def ihfft(a: ArrayLike, n=None, axis=-1, norm=None): 108 | return torch.fft.ihfft(a, n, dim=axis, norm=norm) 109 | 110 | 111 | @normalizer 112 | def fftfreq(n, d=1.0): 113 | return torch.fft.fftfreq(n, d) 114 | 115 | 116 | @normalizer 117 | def rfftfreq(n, d=1.0): 118 | return torch.fft.rfftfreq(n, d) 119 | 120 | 121 | @normalizer 122 | def fftshift(x: ArrayLike, axes=None): 123 | return torch.fft.fftshift(x, axes) 124 | 125 | 126 | @normalizer 127 | def ifftshift(x: ArrayLike, axes=None): 128 | return torch.fft.ifftshift(x, axes) 129 | -------------------------------------------------------------------------------- /e2e/smoke/bench_smoke.py: -------------------------------------------------------------------------------- 1 | # ----------------------------------------------------------------------------- 2 | # From Numpy to Python 3 | # Copyright (2017) Nicolas P. Rougier - BSD license 4 | # More information at https://github.com/rougier/numpy-book 5 | # ----------------------------------------------------------------------------- 6 | import numpy as np 7 | from smoke_solver import vel_step, dens_step 8 | 9 | import time 10 | 11 | import matplotlib.pyplot as plt 12 | plot = True 13 | 14 | 15 | N = 128 16 | size = N + 2 17 | dt = 0.1 18 | diff = 0.0 19 | visc = 0.0 20 | force = 5.0 21 | source = 100.0 22 | 23 | 24 | def init(): 25 | 26 | u = np.zeros((size, size), np.float32) # velocity 27 | u_prev = np.zeros((size, size), np.float32) 28 | 29 | v = np.zeros((size, size), np.float32) # velocity 30 | v_prev = np.zeros((size, size), np.float32) 31 | 32 | dens = np.zeros((size, size), np.float32) # density 33 | dens_prev = np.zeros((size, size), np.float32) 34 | 35 | """ 36 | ## initialization 37 | u[:, :] = 0.0 38 | v[:, :] = 0.0 39 | u_prev[:, :] = 0.0 40 | v_prev[:, :] = 0.0 41 | dens[:, :] = 0.0 42 | dens_prev[:, :] = 0.0 43 | """ 44 | 45 | def disc(shape=(size, size), center=(size/2, size/2), radius=10): 46 | def distance(x, y): 47 | return np.sqrt((x-center[0])**2+(y-center[1])**2) 48 | 49 | args = np.indices(shape, dtype=float) 50 | D = distance(*args) 51 | return np.where(D <= radius, True, False) 52 | 53 | D = disc(radius=32) ^ disc(radius=16) 54 | dens[...] = D*source/10 55 | 56 | np.random.seed(1234) 57 | u[:, :] = force * 0.1 * np.random.uniform(-1, 1, u.shape) 58 | v[:, :] = force * 0.1 * np.random.uniform(-1, 1, u.shape) 59 | 60 | return u, u_prev, v, v_prev, dens, dens_prev 61 | 62 | 63 | 64 | # update 65 | import torch 66 | torch.set_default_device("cpu") 67 | 68 | import torch._dynamo.config as cfg 69 | cfg.numpy_ndarray_as_tensor = True 70 | 71 | 72 | def update_single(N, dens, u, v, u_prev, v_prev, visc, diff, dt): 73 | vel_step(N, u, v, u_prev, v_prev, visc, dt) 74 | dens_step(N, dens, dens_prev, u, v, diff, dt) 75 | 76 | def simulate(n_steps): 77 | for _ in range(n_steps): 78 | update_single(N, dens, u, v, u_prev, v_prev, visc, diff, dt) 79 | 80 | 81 | if plot: 82 | fig, (ax1, ax2) = plt.subplots(1, 2) 83 | 84 | # ### simulation: numpy ### 85 | n_steps = 500 86 | u, u_prev, v, v_prev, dens, dens_prev = init() 87 | 88 | time_start = time.time() 89 | simulate(n_steps) 90 | time_end = time.time() 91 | 92 | print("\n\n=== numpy: ", time_end - time_start) 93 | #################### 94 | 95 | if plot: 96 | ax1.imshow(dens) 97 | 98 | 99 | # ### simulation: compiled ### 100 | update_single = torch.compile(update_single) 101 | 102 | # warm up 103 | for _ in range(5): 104 | u, u_prev, v, v_prev, dens, dens_prev = init() 105 | simulate(n_steps) 106 | 107 | 108 | # simulate and measure 109 | u, u_prev, v, v_prev, dens, dens_prev = init() 110 | 111 | time_start = time.time() 112 | simulate(n_steps) 113 | time_end = time.time() 114 | 115 | print("=== compiled: ", time_end - time_start) 116 | #################### 117 | 118 | if plot: 119 | ax2.imshow(dens) 120 | plt.show() 121 | 122 | # dump the density field 123 | #np.savez_compressed(f'smoke_density_compiled_{n_steps}.npy', dens) 124 | 125 | 126 | 127 | -------------------------------------------------------------------------------- /torch_np/tests/test_function_base.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from pytest import raises as assert_raises 3 | 4 | import torch_np as np 5 | from torch_np.testing import assert_array_equal, assert_equal 6 | 7 | 8 | class TestArange: 9 | def test_infinite(self): 10 | assert_raises( 11 | (RuntimeError, ValueError), np.arange, 0, np.inf 12 | ) # "size exceeded", 13 | 14 | def test_nan_step(self): 15 | assert_raises( 16 | (RuntimeError, ValueError), np.arange, 0, 1, np.nan 17 | ) # "cannot compute length", 18 | 19 | def test_zero_step(self): 20 | assert_raises(ZeroDivisionError, np.arange, 0, 10, 0) 21 | assert_raises(ZeroDivisionError, np.arange, 0.0, 10.0, 0.0) 22 | 23 | # empty range 24 | assert_raises(ZeroDivisionError, np.arange, 0, 0, 0) 25 | assert_raises(ZeroDivisionError, np.arange, 0.0, 0.0, 0.0) 26 | 27 | def test_require_range(self): 28 | assert_raises(TypeError, np.arange) 29 | assert_raises(TypeError, np.arange, step=3) 30 | assert_raises(TypeError, np.arange, dtype="int64") 31 | 32 | @pytest.mark.xfail(reason="XXX: arange(start=0, stop, step=1)") 33 | def test_require_range_2(self): 34 | assert_raises(TypeError, np.arange, start=4) 35 | 36 | def test_start_stop_kwarg(self): 37 | keyword_stop = np.arange(stop=3) 38 | keyword_zerotostop = np.arange(start=0, stop=3) 39 | keyword_start_stop = np.arange(start=3, stop=9) 40 | 41 | assert len(keyword_stop) == 3 42 | assert len(keyword_zerotostop) == 3 43 | assert len(keyword_start_stop) == 6 44 | assert_array_equal(keyword_stop, keyword_zerotostop) 45 | 46 | @pytest.mark.xfail(reason="XXX: arange(..., dtype=bool)") 47 | def test_arange_booleans(self): 48 | # Arange makes some sense for booleans and works up to length 2. 49 | # But it is weird since `arange(2, 4, dtype=bool)` works. 50 | # Arguably, much or all of this could be deprecated/removed. 51 | res = np.arange(False, dtype=bool) 52 | assert_array_equal(res, np.array([], dtype="bool")) 53 | 54 | res = np.arange(True, dtype="bool") 55 | assert_array_equal(res, [False]) 56 | 57 | res = np.arange(2, dtype="bool") 58 | assert_array_equal(res, [False, True]) 59 | 60 | # This case is especially weird, but drops out without special case: 61 | res = np.arange(6, 8, dtype="bool") 62 | assert_array_equal(res, [True, True]) 63 | 64 | with pytest.raises(TypeError): 65 | np.arange(3, dtype="bool") 66 | 67 | @pytest.mark.parametrize("which", [0, 1, 2]) 68 | def test_error_paths_and_promotion(self, which): 69 | args = [0, 10, 2] # start, stop, and step 70 | args[which] = np.float64(2.0) # should ensure float64 output 71 | assert np.arange(*args).dtype == np.float64 72 | 73 | # Cover stranger error path, test only to achieve code coverage! 74 | args[which] = [None, []] 75 | with pytest.raises((ValueError, RuntimeError)): 76 | # Fails discovering start dtype 77 | np.arange(*args) 78 | 79 | 80 | class TestAppend: 81 | # tests taken from np.append docstring 82 | def test_basic(self): 83 | result = np.append([1, 2, 3], [[4, 5, 6], [7, 8, 9]]) 84 | assert_equal(result, np.arange(1, 10, dtype=int)) 85 | 86 | # When `axis` is specified, `values` must have the correct shape. 87 | result = np.append([[1, 2, 3], [4, 5, 6]], [[7, 8, 9]], axis=0) 88 | assert_equal(result, np.arange(1, 10, dtype=int).reshape((3, 3))) 89 | 90 | with pytest.raises((RuntimeError, ValueError)): 91 | np.append([[1, 2, 3], [4, 5, 6]], [7, 8, 9], axis=0) 92 | -------------------------------------------------------------------------------- /torch_np/tests/numpy_tests/core/test_dlpack.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import pytest 3 | 4 | import torch 5 | 6 | import torch_np as np 7 | from torch_np.testing import assert_array_equal 8 | 9 | IS_PYPY = False 10 | 11 | 12 | class TestDLPack: 13 | @pytest.mark.xfail(reason="pytorch seems to handle refcounts differently") 14 | @pytest.mark.skipif(IS_PYPY, reason="PyPy can't get refcounts.") 15 | def test_dunder_dlpack_refcount(self): 16 | x = np.arange(5) 17 | y = x.__dlpack__() 18 | assert sys.getrefcount(x) == 3 19 | del y 20 | assert sys.getrefcount(x) == 2 21 | 22 | @pytest.mark.xfail(reason="pytorch does not raise") 23 | def test_dunder_dlpack_stream(self): 24 | x = np.arange(5) 25 | x.__dlpack__(stream=None) 26 | 27 | with pytest.raises(RuntimeError): 28 | x.__dlpack__(stream=1) 29 | 30 | @pytest.mark.xfail(reason="pytorch seems to handle refcounts differently") 31 | @pytest.mark.skipif(IS_PYPY, reason="PyPy can't get refcounts.") 32 | def test_from_dlpack_refcount(self): 33 | x = np.arange(5) 34 | y = np.from_dlpack(x) 35 | assert sys.getrefcount(x) == 3 36 | del y 37 | assert sys.getrefcount(x) == 2 38 | 39 | @pytest.mark.parametrize("dtype", [ 40 | np.int8, np.int16, np.int32, np.int64, 41 | np.uint8, 42 | np.float16, np.float32, np.float64, 43 | np.complex64, np.complex128 44 | ]) 45 | def test_dtype_passthrough(self, dtype): 46 | x = np.arange(5, dtype=dtype) 47 | y = np.from_dlpack(x) 48 | 49 | assert y.dtype == x.dtype 50 | assert_array_equal(x, y) 51 | 52 | def test_non_contiguous(self): 53 | x = np.arange(25).reshape((5, 5)) 54 | 55 | y1 = x[0] 56 | assert_array_equal(y1, np.from_dlpack(y1)) 57 | 58 | y2 = x[:, 0] 59 | assert_array_equal(y2, np.from_dlpack(y2)) 60 | 61 | y3 = x[1, :] 62 | assert_array_equal(y3, np.from_dlpack(y3)) 63 | 64 | y4 = x[1] 65 | assert_array_equal(y4, np.from_dlpack(y4)) 66 | 67 | y5 = np.diagonal(x).copy() 68 | assert_array_equal(y5, np.from_dlpack(y5)) 69 | 70 | @pytest.mark.parametrize("ndim", range(33)) 71 | def test_higher_dims(self, ndim): 72 | shape = (1,) * ndim 73 | x = np.zeros(shape, dtype=np.float64) 74 | 75 | assert shape == np.from_dlpack(x).shape 76 | 77 | def test_dlpack_device(self): 78 | x = np.arange(5) 79 | assert x.__dlpack_device__() == (1, 0) 80 | y = np.from_dlpack(x) 81 | assert y.__dlpack_device__() == (1, 0) 82 | z = y[::2] 83 | assert z.__dlpack_device__() == (1, 0) 84 | 85 | def dlpack_deleter_exception(self): 86 | x = np.arange(5) 87 | _ = x.__dlpack__() 88 | raise RuntimeError 89 | 90 | def test_dlpack_destructor_exception(self): 91 | with pytest.raises(RuntimeError): 92 | self.dlpack_deleter_exception() 93 | 94 | @pytest.mark.skip(reason='no readonly arrays in pytorch') 95 | def test_readonly(self): 96 | x = np.arange(5) 97 | x.flags.writeable = False 98 | with pytest.raises(BufferError): 99 | x.__dlpack__() 100 | 101 | def test_ndim0(self): 102 | x = np.array(1.0) 103 | y = np.from_dlpack(x) 104 | assert_array_equal(x, y) 105 | 106 | def test_from_torch(self): 107 | t = torch.arange(4) 108 | a = np.from_dlpack(t) 109 | assert_array_equal(a, np.asarray(t)) 110 | 111 | def test_to_torch(self): 112 | a = np.arange(4) 113 | t = torch.from_dlpack(a) 114 | assert_array_equal(np.asarray(t), a) 115 | 116 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib64/ 18 | parts/ 19 | sdist/ 20 | var/ 21 | wheels/ 22 | share/python-wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .nox/ 42 | .coverage 43 | .coverage.* 44 | .cache 45 | nosetests.xml 46 | coverage.xml 47 | *.cover 48 | *.py,cover 49 | .hypothesis/ 50 | .pytest_cache/ 51 | cover/ 52 | 53 | # Translations 54 | *.mo 55 | *.pot 56 | 57 | # Django stuff: 58 | *.log 59 | local_settings.py 60 | db.sqlite3 61 | db.sqlite3-journal 62 | 63 | # Flask stuff: 64 | instance/ 65 | .webassets-cache 66 | 67 | # Scrapy stuff: 68 | .scrapy 69 | 70 | # Sphinx documentation 71 | docs/_build/ 72 | 73 | # PyBuilder 74 | .pybuilder/ 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | # For a library or package, you might want to ignore these files since the code is 86 | # intended to run in multiple environments; otherwise, check them in: 87 | # .python-version 88 | 89 | # pipenv 90 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 91 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 92 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 93 | # install all needed dependencies. 94 | #Pipfile.lock 95 | 96 | # poetry 97 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 98 | # This is especially recommended for binary packages to ensure reproducibility, and is more 99 | # commonly ignored for libraries. 100 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 101 | #poetry.lock 102 | 103 | # pdm 104 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 105 | #pdm.lock 106 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 107 | # in version control. 108 | # https://pdm.fming.dev/#use-with-ide 109 | .pdm.toml 110 | 111 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 112 | __pypackages__/ 113 | 114 | # Celery stuff 115 | celerybeat-schedule 116 | celerybeat.pid 117 | 118 | # SageMath parsed files 119 | *.sage.py 120 | 121 | # Environments 122 | .env 123 | .venv 124 | env/ 125 | venv/ 126 | ENV/ 127 | env.bak/ 128 | venv.bak/ 129 | 130 | # Spyder project settings 131 | .spyderproject 132 | .spyproject 133 | 134 | # Rope project settings 135 | .ropeproject 136 | 137 | # mkdocs documentation 138 | /site 139 | 140 | # mypy 141 | .mypy_cache/ 142 | .dmypy.json 143 | dmypy.json 144 | 145 | # Pyre type checker 146 | .pyre/ 147 | 148 | # pytype static type analyzer 149 | .pytype/ 150 | 151 | # Cython debug symbols 152 | cython_debug/ 153 | 154 | # PyCharm 155 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 156 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 157 | # and can be added to the global gitignore or merged into this file. For a more nuclear 158 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 159 | #.idea/ 160 | -------------------------------------------------------------------------------- /torch_np/random.py: -------------------------------------------------------------------------------- 1 | """Wrapper to mimic (parts of) np.random API surface. 2 | 3 | NumPy has strict guarantees on reproducibility etc; here we don't give any. 4 | 5 | Q: default dtype is float64 in numpy 6 | 7 | """ 8 | from __future__ import annotations 9 | 10 | import functools 11 | from math import sqrt 12 | from typing import Optional 13 | 14 | import torch 15 | 16 | from . import _dtypes_impl, _util 17 | from ._normalizations import ArrayLike, array_or_scalar, normalizer 18 | 19 | _default_dtype = _dtypes_impl.default_dtypes.float_dtype 20 | 21 | __all__ = [ 22 | "seed", 23 | "random_sample", 24 | "sample", 25 | "random", 26 | "rand", 27 | "randn", 28 | "normal", 29 | "choice", 30 | "randint", 31 | "shuffle", 32 | "uniform", 33 | "USE_NUMPY_RANDOM", 34 | ] 35 | 36 | 37 | USE_NUMPY_RANDOM = False 38 | 39 | 40 | def deco_stream(func): 41 | @functools.wraps(func) 42 | def inner(*args, **kwds): 43 | if USE_NUMPY_RANDOM is False: 44 | return func(*args, **kwds) 45 | elif USE_NUMPY_RANDOM is True: 46 | from numpy import random as nr 47 | 48 | f = getattr(nr, func.__name__) 49 | return f(*args, **kwds) 50 | else: 51 | raise ValueError(f"USE_NUMPY_RANDOM={USE_NUMPY_RANDOM} not understood.") 52 | 53 | return inner 54 | 55 | 56 | @deco_stream 57 | def seed(seed=None): 58 | if seed is not None: 59 | torch.random.manual_seed(seed) 60 | 61 | 62 | @deco_stream 63 | def random_sample(size=None): 64 | if size is None: 65 | size = () 66 | values = torch.empty(size, dtype=_default_dtype).uniform_() 67 | return array_or_scalar(values, return_scalar=size is None) 68 | 69 | 70 | @deco_stream 71 | def rand(*size): 72 | return random_sample(size) 73 | 74 | 75 | sample = random_sample 76 | random = random_sample 77 | 78 | 79 | @deco_stream 80 | def uniform(low=0.0, high=1.0, size=None): 81 | if size is None: 82 | size = () 83 | values = torch.empty(size, dtype=_default_dtype).uniform_(low, high) 84 | return array_or_scalar(values, return_scalar=size is None) 85 | 86 | 87 | @deco_stream 88 | def randn(*size): 89 | values = torch.randn(size, dtype=_default_dtype) 90 | return array_or_scalar(values, return_scalar=size is None) 91 | 92 | 93 | @deco_stream 94 | def normal(loc=0.0, scale=1.0, size=None): 95 | if size is None: 96 | size = () 97 | values = torch.empty(size, dtype=_default_dtype).normal_(loc, scale) 98 | return array_or_scalar(values, return_scalar=size is None) 99 | 100 | 101 | @deco_stream 102 | @normalizer 103 | def shuffle(x: ArrayLike): 104 | perm = torch.randperm(x.shape[0]) 105 | xp = x[perm] 106 | x.copy_(xp) 107 | 108 | 109 | @deco_stream 110 | def randint(low, high=None, size=None): 111 | if size is None: 112 | size = () 113 | if not isinstance(size, (tuple, list)): 114 | size = (size,) 115 | if high is None: 116 | low, high = 0, low 117 | values = torch.randint(low, high, size=size) 118 | return array_or_scalar(values, int, return_scalar=size is None) 119 | 120 | 121 | @deco_stream 122 | @normalizer 123 | def choice(a: ArrayLike, size=None, replace=True, p: Optional[ArrayLike] = None): 124 | 125 | # https://stackoverflow.com/questions/59461811/random-choice-with-pytorch 126 | if a.numel() == 1: 127 | a = torch.arange(a) 128 | 129 | # TODO: check a.dtype is integer -- cf np.random.choice(3.4) which raises 130 | 131 | # number of draws 132 | if size is None: 133 | num_el = 1 134 | elif _util.is_sequence(size): 135 | num_el = 1 136 | for el in size: 137 | num_el *= el 138 | else: 139 | num_el = size 140 | 141 | # prepare the probabilities 142 | if p is None: 143 | p = torch.ones_like(a) / a.shape[0] 144 | 145 | # cf https://github.com/numpy/numpy/blob/main/numpy/random/mtrand.pyx#L973 146 | atol = sqrt(torch.finfo(p.dtype).eps) 147 | if abs(p.sum() - 1.0) > atol: 148 | raise ValueError("probabilities do not sum to 1.") 149 | 150 | # actually sample 151 | indices = torch.multinomial(p, num_el, replacement=replace) 152 | 153 | if _util.is_sequence(size): 154 | indices = indices.reshape(size) 155 | 156 | samples = a[indices] 157 | 158 | return samples 159 | -------------------------------------------------------------------------------- /e2e/maze/maze.output.tnp.txt: -------------------------------------------------------------------------------- 1 | $ python maze_numpy.py 2 | Z = array_w([[ True, True, True, ..., True, True, True], 3 | [ True, False, False, ..., True, False, True], 4 | [ True, False, True, ..., True, False, True], 5 | ..., 6 | [ True, False, True, ..., True, False, True], 7 | [ True, False, False, ..., True, False, True], 8 | [ True, True, True, ..., True, True, True]]) 9 | P = array_w([[79, 39], 10 | [79, 38], 11 | [79, 37], 12 | [79, 36], 13 | [79, 35], 14 | [78, 35], 15 | [77, 35], 16 | [76, 35], 17 | [75, 35], 18 | [75, 36], 19 | [75, 37], 20 | [74, 37], 21 | [73, 37], 22 | [72, 37], 23 | [71, 37], 24 | [71, 36], 25 | [71, 35], 26 | [70, 35], 27 | [69, 35], 28 | [69, 34], 29 | [69, 33], 30 | [68, 33], 31 | [67, 33], 32 | [67, 34], 33 | [67, 35], 34 | [67, 36], 35 | [67, 37], 36 | [66, 37], 37 | [65, 37], 38 | [64, 37], 39 | [63, 37], 40 | [62, 37], 41 | [61, 37], 42 | [61, 38], 43 | [61, 39], 44 | [60, 39], 45 | [59, 39], 46 | [58, 39], 47 | [57, 39], 48 | [56, 39], 49 | [55, 39], 50 | [54, 39], 51 | [53, 39], 52 | [52, 39], 53 | [51, 39], 54 | [50, 39], 55 | [49, 39], 56 | [48, 39], 57 | [47, 39], 58 | [46, 39], 59 | [45, 39], 60 | [45, 38], 61 | [45, 37], 62 | [45, 36], 63 | [45, 35], 64 | [44, 35], 65 | [43, 35], 66 | [43, 34], 67 | [43, 33], 68 | [43, 32], 69 | [43, 31], 70 | [44, 31], 71 | [45, 31], 72 | [45, 30], 73 | [45, 29], 74 | [46, 29], 75 | [47, 29], 76 | [47, 28], 77 | [47, 27], 78 | [48, 27], 79 | [49, 27], 80 | [50, 27], 81 | [51, 27], 82 | [51, 26], 83 | [51, 25], 84 | [51, 24], 85 | [51, 23], 86 | [50, 23], 87 | [49, 23], 88 | [48, 23], 89 | [47, 23], 90 | [46, 23], 91 | [45, 23], 92 | [44, 23], 93 | [43, 23], 94 | [43, 22], 95 | [43, 21], 96 | [42, 21], 97 | [41, 21], 98 | [40, 21], 99 | [39, 21], 100 | [38, 21], 101 | [37, 21], 102 | [36, 21], 103 | [35, 21], 104 | [34, 21], 105 | [33, 21], 106 | [32, 21], 107 | [31, 21], 108 | [31, 20], 109 | [31, 19], 110 | [32, 19], 111 | [33, 19], 112 | [33, 18], 113 | [33, 17], 114 | [33, 16], 115 | [33, 15], 116 | [33, 14], 117 | [33, 13], 118 | [32, 13], 119 | [31, 13], 120 | [30, 13], 121 | [29, 13], 122 | [29, 12], 123 | [29, 11], 124 | [28, 11], 125 | [27, 11], 126 | [26, 11], 127 | [25, 11], 128 | [24, 11], 129 | [23, 11], 130 | [22, 11], 131 | [21, 11], 132 | [20, 11], 133 | [19, 11], 134 | [19, 12], 135 | [19, 13], 136 | [19, 14], 137 | [19, 15], 138 | [19, 16], 139 | [19, 17], 140 | [19, 18], 141 | [19, 19], 142 | [18, 19], 143 | [17, 19], 144 | [16, 19], 145 | [15, 19], 146 | [15, 18], 147 | [15, 17], 148 | [14, 17], 149 | [13, 17], 150 | [12, 17], 151 | [11, 17], 152 | [11, 16], 153 | [11, 15], 154 | [11, 14], 155 | [11, 13], 156 | [11, 12], 157 | [11, 11], 158 | [11, 10], 159 | [11, 9], 160 | [10, 9], 161 | [ 9, 9], 162 | [ 8, 9], 163 | [ 7, 9], 164 | [ 6, 9], 165 | [ 5, 9], 166 | [ 4, 9], 167 | [ 3, 9], 168 | [ 3, 10], 169 | [ 3, 11], 170 | [ 3, 12], 171 | [ 3, 13], 172 | [ 2, 13], 173 | [ 1, 13], 174 | [ 1, 12], 175 | [ 1, 11], 176 | [ 1, 10], 177 | [ 1, 9], 178 | [ 1, 8], 179 | [ 1, 7], 180 | [ 1, 6], 181 | [ 1, 5], 182 | [ 1, 4], 183 | [ 1, 3], 184 | [ 1, 2], 185 | [ 1, 1]]) 186 | 187 | -------------------------------------------------------------------------------- /e2e/nn_from_scratch/eager/nn.py: -------------------------------------------------------------------------------- 1 | # from https://www.geeksforgeeks.org/implementation-of-neural-network-from-scratch-using-numpy/ 2 | 3 | 4 | import numpy as _np 5 | import torch_np as np 6 | 7 | # To run on CUDA, change "cpu" to "cuda" below. 8 | import torch 9 | torch.set_default_device("cpu") 10 | 11 | 12 | # Creating data set 13 | 14 | # A 15 | a =[0, 0, 1, 1, 0, 0, 16 | 0, 1, 0, 0, 1, 0, 17 | 1, 1, 1, 1, 1, 1, 18 | 1, 0, 0, 0, 0, 1, 19 | 1, 0, 0, 0, 0, 1] 20 | # B 21 | b =[0, 1, 1, 1, 1, 0, 22 | 0, 1, 0, 0, 1, 0, 23 | 0, 1, 1, 1, 1, 0, 24 | 0, 1, 0, 0, 1, 0, 25 | 0, 1, 1, 1, 1, 0] 26 | # C 27 | c =[0, 1, 1, 1, 1, 0, 28 | 0, 1, 0, 0, 0, 0, 29 | 0, 1, 0, 0, 0, 0, 30 | 0, 1, 0, 0, 0, 0, 31 | 0, 1, 1, 1, 1, 0] 32 | 33 | # Creating labels 34 | y =[[1, 0, 0], 35 | [0, 1, 0], 36 | [0, 0, 1]] 37 | 38 | 39 | # converting data and labels into numpy array 40 | 41 | """ 42 | Convert the matrix of 0 and 1 into one hot vector 43 | so that we can directly feed it to the neural network, 44 | these vectors are then stored in a list x. 45 | """ 46 | 47 | x =[np.array(a).reshape(1, 30), np.array(b).reshape(1, 30), 48 | np.array(c).reshape(1, 30)] 49 | 50 | 51 | # Labels are also converted into NumPy array 52 | y = np.array(y) 53 | 54 | 55 | print(x, "\n\n", y) 56 | 57 | 58 | # activation function 59 | 60 | def sigmoid(x): 61 | return(1/(1 + np.exp(-x))) 62 | 63 | # Creating the Feed forward neural network 64 | # 1 Input layer(1, 30) 65 | # 1 hidden layer (1, 5) 66 | # 1 output layer(3, 3) 67 | 68 | def f_forward(x, w1, w2): 69 | # hidden 70 | z1 = x.dot(w1)# input from layer 1 71 | a1 = sigmoid(z1)# out put of layer 2 72 | 73 | # Output layer 74 | z2 = a1.dot(w2)# input of out layer 75 | a2 = sigmoid(z2)# output of out layer 76 | return(a2) 77 | 78 | # initializing the weights randomly 79 | def generate_wt(x, y): 80 | 81 | _np.random.seed(1234) 82 | 83 | l =[] 84 | for i in range(x * y): 85 | l.append(_np.random.randn()) 86 | return(np.array(l).reshape(x, y)) 87 | 88 | # for loss we will be using mean square error(MSE) 89 | def loss(out, Y): 90 | s =(np.square(out-Y)) 91 | s = np.sum(s)/len(y) 92 | return(s) 93 | 94 | # Back propagation of error 95 | def back_prop(x, y, w1, w2, alpha): 96 | 97 | # hidden layer 98 | z1 = x.dot(w1)# input from layer 1 99 | a1 = sigmoid(z1)# output of layer 2 100 | 101 | # Output layer 102 | z2 = a1.dot(w2)# input of out layer 103 | a2 = sigmoid(z2)# output of out layer 104 | # error in output layer 105 | d2 =(a2-y) 106 | d1 = np.multiply((w2.dot((d2.transpose()))).transpose(), 107 | (np.multiply(a1, 1-a1))) 108 | 109 | # Gradient for w1 and w2 110 | w1_adj = x.transpose().dot(d1) 111 | w2_adj = a1.transpose().dot(d2) 112 | 113 | # Updating parameters 114 | w1 = w1-(alpha*(w1_adj)) 115 | w2 = w2-(alpha*(w2_adj)) 116 | 117 | return(w1, w2) 118 | 119 | def train(x, Y, w1, w2, alpha = 0.01, epoch = 10): 120 | acc =[] 121 | losss =[] 122 | for j in range(epoch): 123 | l =[] 124 | for i in range(len(x)): 125 | out = f_forward(x[i], w1, w2) 126 | l.append((loss(out, Y[i]))) 127 | w1, w2 = back_prop(x[i], y[i], w1, w2, alpha) 128 | print("epochs:", j + 1, "======== acc:", (1-(sum(l)/len(x)))*100) 129 | acc.append((1-(sum(l)/len(x)))*100) 130 | losss.append(sum(l)/len(x)) 131 | return(acc, losss, w1, w2) 132 | 133 | def predict(x, w1, w2): 134 | Out = f_forward(x, w1, w2) 135 | maxm = 0 136 | k = 0 137 | for i in range(len(Out[0])): 138 | if(maxm