From 2900ed6e3fbaa90d4b8cfcd8f5c1a232122c7f3b Mon Sep 17 00:00:00 2001 From: tannevaled Date: Fri, 9 Oct 2026 15:26:24 +0200 Subject: [PATCH 1/3] docs: dtype design (runtime dtype on Array, NumPy 2.x promotion) + numpy promotion fixture The design for Phase 2: why the dtype is a runtime property of the one Array type rather than a generic Dense[T] (promotion picks the result type from both operands, which a generic method cannot express; the consumers are dynamic; nothing existing breaks), the storage layout that leaves the float64 hot path untouched, the NumPy semantics and the deliberate differences, the two breaking changes, and the delivery order. testdata/promotion.json is generated from NumPy 2.5.3 by testdata/promotion.py: the 13x13 result_type table, weak-scalar results, unary ufunc and reduction dtypes. The implementation will be tested against every cell. Co-Authored-By: Claude Opus 5.5 --- docs/design-dtypes.md | 142 ++++++++++++ docs/plan-ndarray.md | 4 +- testdata/promotion.json | 479 ++++++++++++++++++++++++++++++++++++++++ testdata/promotion.py | 22 ++ 4 files changed, 646 insertions(+), 1 deletion(-) create mode 100644 docs/design-dtypes.md create mode 100644 testdata/promotion.json create mode 100644 testdata/promotion.py diff --git a/docs/design-dtypes.md b/docs/design-dtypes.md new file mode 100644 index 0000000..90f747d --- /dev/null +++ b/docs/design-dtypes.md @@ -0,0 +1,142 @@ +# Design — dtypes + +> Status: accepted, implementation in progress. Supersedes decision 4 of +> [plan-ndarray.md](plan-ndarray.md) ("`float64` first"). + +## Why now + +`Array` holds `float64` only. Three things the library is meant to do cannot be +done on that alone: + +- **Spectra.** `go-fft` returns `[]complex128`. Without a complex dtype an FFT + result cannot be an array, so the two libraries do not compose. +- **Masks.** Comparisons return `0/1` floats. They work for `MaskSelect` and + `Where`, but a mask cannot be told apart from data, and it costs 8 bytes per + element instead of 1. +- **Memory-bound work.** `float32` halves the bytes moved, which is the whole + cost of the elementwise ops; images arrive as `uint8`; counts and indices are + integers. + +The type at the centre of the library is the one thing that gets more +expensive to change every release, and the module is pre-1.0. That is why it +goes first. + +## Decision: the dtype is a runtime property of the one `Array` type + +```go +type DType uint8 // Bool, Int8…Int64, Uint8…Uint64, Float32, Float64, Complex64, Complex128 + +a, _ := ndarray.Zeros(3, 4) // Float64, as before +b := a.AsType(ndarray.Complex128) +c, _ := a.Add(b) // Complex128, by NumPy promotion +m, _ := a.Greater(ndarray.Scalar(0)) // Bool +``` + +The alternative was a generic `Dense[T]`. It was rejected for four reasons: + +1. **Promotion cannot be written as a generic method.** `int32 / int32` is + `float64` and `int8 + float32` is `float32`: the result type depends on both + operands. A method on `Dense[T]` cannot return `Dense[U]` for a `U` chosen + from its argument, and methods cannot have their own type parameters. The + arithmetic would have to move to package functions, one per pair of types + in the general case, and NumPy's semantics would be lost or re-encoded. +2. **The consumers are dynamic.** The notebook evaluates cells in an + interpreter, and the Ruby binding (`go-embedded-ruby`) is a dynamic language. + Both hold "an array" and ask it what it is; neither can carry a type + parameter. +3. **Nothing that exists breaks.** `*Array`, its methods and the `float64` hot + path keep their names, signatures and speed. The Ruby binding uses method + expressions such as `(*nd.Array).Sqrt`; those would stop compiling on an + alias of an instantiated generic type. +4. **Static typing is still available where it pays**: generic accessors + `Data[T]`, `FromSlice[T]` and `Item[T]` check the dtype once at the boundary + and hand back a `[]T`. + +## Storage + +```go +type Array struct { + data []float64 // Float64 storage — the existing hot path, unchanged + ext any // []T for every other dtype, nil for Float64 + dtype DType + shape []int + strides []int + offset int +} +``` + +Keeping `data []float64` as a concrete field means every existing `float64` +kernel, SIMD path and benchmark is untouched. The other dtypes are reached +through one type assertion per operation, not per element. Shape, strides and +views do not depend on the dtype, so slicing, reshape, transpose, broadcast and +the manipulation functions are written once over a generic helper. + +Inner loops for the other dtypes are generic Go (`func add[T number](dst, x, +y []T)`). The gc compiler stencils one copy per GC shape, and every numeric type +has its own shape, so these are fully specialised loops. SIMD kernels come +dtype by dtype after correctness, `float32` first. + +## Semantics: NumPy 2.x, checked against NumPy + +- **Array–array promotion** follows `numpy.result_type`. The full 13×13 table + is generated from NumPy 2.5.3 and committed as a test fixture + (`testdata/promotion.json`, embedded so the emulated CI arches can read it); + the implementation is tested against every cell. +- **Go scalars are weak**, like Python scalars under NEP 50: `AddScalar(1.5)` on + a `float32` array stays `float32`, on an `int8` array becomes `float64`. + `ScalarOf(v, dtype)` makes a strong one. +- **True division** of integers or bools gives `float64`. +- **Reductions:** `Sum`/`Prod`/`CumSum` of bool and signed ints accumulate in + `int64`, of unsigned ints in `uint64`; `Mean` of integers is `float64`; + `Max`/`Min` keep the dtype; arg-reductions are `int64`. +- **Float ufuncs on integers** (`Sqrt`, `Exp`, `Sin` …) give `float64` for + 32- and 64-bit ints, `float32` for 16-bit ints. +- **Comparisons return `Bool`.** `MaskSelect` and `Where` accept a `Bool` mask + and, as NumPy does, any other dtype by truthiness, so existing `0/1` masks + keep working. +- **Integer overflow wraps**, as it does in both Go and NumPy. + +### Deliberate differences + +| NumPy | here | why | +|---|---|---| +| `float16` exists; `sqrt(int8)` is `float16` | no `float16`; `sqrt(int8)`, `sqrt(uint8)` and `sqrt(bool)` give `float32` | Go has no half-precision arithmetic; `float32` is the smallest type that holds the result exactly | +| `round` is half-to-even | half-away-from-zero (unchanged) | documented since v0.1.0 | +| `datetime64`, `str_`, `object`, structured dtypes | absent | out of scope for a numeric library | + +## Element access + +- `At(idx...) float64` keeps working for every real dtype, converting the + element. On a complex array it panics with a message that names `AtComplex`. + NumPy instead discards the imaginary part with a `ComplexWarning`; Go has no + warnings, and a silent loss is the defect that warning exists to flag. +- `AtComplex(idx...) complex128`, and `Item[T](a, idx...) T` for exact typed + access. +- `Set(v float64, idx...)` converts into the array's dtype. + +## Breaking changes + +| before | after | migration | +|---|---|---| +| comparisons return a `Float64` 0/1 array | they return `Bool` | `.AsType(Float64)` to get the old values; `MaskSelect` and `Where` need no change | +| `MaskSelect` takes a 0/1 float mask | it takes any dtype (truthiness) | none | + +The only consumer in the fleet, `go-embedded-ruby/ruby` (`internal/vm/ndarray.go`), +does not use comparisons; it is pinned to a June pseudo-version and will be +moved forward in its own pull request. + +## Delivery + +1. **Core** — `DType`, storage, `AsType`, creation with a dtype + (`ZerosOf`/`OnesOf`/`FullOf`/`FromSlice[T]`), element access, and every + shape-only operation (reshape, transpose, slice, copy, flatten, expand, + squeeze, concatenate, stack, string form) for every dtype. +2. **Arithmetic** — promotion, the four operators and the scalar forms, + comparisons returning `Bool`, `Where`/`MaskSelect`, reductions with their + accumulator types, unary ufuncs. +3. **Complex** — `Real`/`Imag`/`Conj`/`Angle`, `Abs` of complex to float, and an + `fft` subpackage bridging `go-fft` on arrays (`fft.FFTN(a, axes...)`). +4. **Speed** — `float32` SIMD kernels, then `MatMul` for `float32` and complex. + +Each step keeps the 100% coverage gate and the per-arch CI, and is checked +against NumPy before it ships. diff --git a/docs/plan-ndarray.md b/docs/plan-ndarray.md index 2bc146e..7e36d9d 100644 --- a/docs/plan-ndarray.md +++ b/docs/plan-ndarray.md @@ -172,6 +172,8 @@ suite (plus the per-arch CI jobs already wired in `.github/workflows/ci.yml`). 3. **Errors, not panics, for shape/broadcast problems**; panics reserved for programmer index errors (matching Go slice semantics). *Settled.* 4. **`float64` first**, dtype abstraction deferred to Phase 2 but kept in mind - so the kernel interface generalises cleanly. + so the kernel interface generalises cleanly. *Superseded:* Phase 2 is now + designed in [design-dtypes.md](design-dtypes.md) — the dtype is a runtime + property of the one `Array` type, with NumPy 2.x promotion. BSD-3-Clause. diff --git a/testdata/promotion.json b/testdata/promotion.json new file mode 100644 index 0000000..876380e --- /dev/null +++ b/testdata/promotion.json @@ -0,0 +1,479 @@ +{ +"numpy": "2.5.3", +"pair": { +"bool,bool": "bool", +"bool,int8": "int8", +"bool,int16": "int16", +"bool,int32": "int32", +"bool,int64": "int64", +"bool,uint8": "uint8", +"bool,uint16": "uint16", +"bool,uint32": "uint32", +"bool,uint64": "uint64", +"bool,float32": "float32", +"bool,float64": "float64", +"bool,complex64": "complex64", +"bool,complex128": "complex128", +"int8,bool": "int8", +"int8,int8": "int8", +"int8,int16": "int16", +"int8,int32": "int32", +"int8,int64": "int64", +"int8,uint8": "int16", +"int8,uint16": "int32", +"int8,uint32": "int64", +"int8,uint64": "float64", +"int8,float32": "float32", +"int8,float64": "float64", +"int8,complex64": "complex64", +"int8,complex128": "complex128", +"int16,bool": "int16", +"int16,int8": "int16", +"int16,int16": "int16", +"int16,int32": "int32", +"int16,int64": "int64", +"int16,uint8": "int16", +"int16,uint16": "int32", +"int16,uint32": "int64", +"int16,uint64": "float64", +"int16,float32": "float32", +"int16,float64": "float64", +"int16,complex64": "complex64", +"int16,complex128": "complex128", +"int32,bool": "int32", +"int32,int8": "int32", +"int32,int16": "int32", +"int32,int32": "int32", +"int32,int64": "int64", +"int32,uint8": "int32", +"int32,uint16": "int32", +"int32,uint32": "int64", +"int32,uint64": "float64", +"int32,float32": "float64", +"int32,float64": "float64", +"int32,complex64": "complex128", +"int32,complex128": "complex128", +"int64,bool": "int64", +"int64,int8": "int64", +"int64,int16": "int64", +"int64,int32": "int64", +"int64,int64": "int64", +"int64,uint8": "int64", +"int64,uint16": "int64", +"int64,uint32": "int64", +"int64,uint64": "float64", +"int64,float32": "float64", +"int64,float64": "float64", +"int64,complex64": "complex128", +"int64,complex128": "complex128", +"uint8,bool": "uint8", +"uint8,int8": "int16", +"uint8,int16": "int16", +"uint8,int32": "int32", +"uint8,int64": "int64", +"uint8,uint8": "uint8", +"uint8,uint16": "uint16", +"uint8,uint32": "uint32", +"uint8,uint64": "uint64", +"uint8,float32": "float32", +"uint8,float64": "float64", +"uint8,complex64": "complex64", +"uint8,complex128": "complex128", +"uint16,bool": "uint16", +"uint16,int8": "int32", +"uint16,int16": "int32", +"uint16,int32": "int32", +"uint16,int64": "int64", +"uint16,uint8": "uint16", +"uint16,uint16": "uint16", +"uint16,uint32": "uint32", +"uint16,uint64": "uint64", +"uint16,float32": "float32", +"uint16,float64": "float64", +"uint16,complex64": "complex64", +"uint16,complex128": "complex128", +"uint32,bool": "uint32", +"uint32,int8": "int64", +"uint32,int16": "int64", +"uint32,int32": "int64", +"uint32,int64": "int64", +"uint32,uint8": "uint32", +"uint32,uint16": "uint32", +"uint32,uint32": "uint32", +"uint32,uint64": "uint64", +"uint32,float32": "float64", +"uint32,float64": "float64", +"uint32,complex64": "complex128", +"uint32,complex128": "complex128", +"uint64,bool": "uint64", +"uint64,int8": "float64", +"uint64,int16": "float64", +"uint64,int32": "float64", +"uint64,int64": "float64", +"uint64,uint8": "uint64", +"uint64,uint16": "uint64", +"uint64,uint32": "uint64", +"uint64,uint64": "uint64", +"uint64,float32": "float64", +"uint64,float64": "float64", +"uint64,complex64": "complex128", +"uint64,complex128": "complex128", +"float32,bool": "float32", +"float32,int8": "float32", +"float32,int16": "float32", +"float32,int32": "float64", +"float32,int64": "float64", +"float32,uint8": "float32", +"float32,uint16": "float32", +"float32,uint32": "float64", +"float32,uint64": "float64", +"float32,float32": "float32", +"float32,float64": "float64", +"float32,complex64": "complex64", +"float32,complex128": "complex128", +"float64,bool": "float64", +"float64,int8": "float64", +"float64,int16": "float64", +"float64,int32": "float64", +"float64,int64": "float64", +"float64,uint8": "float64", +"float64,uint16": "float64", +"float64,uint32": "float64", +"float64,uint64": "float64", +"float64,float32": "float64", +"float64,float64": "float64", +"float64,complex64": "complex128", +"float64,complex128": "complex128", +"complex64,bool": "complex64", +"complex64,int8": "complex64", +"complex64,int16": "complex64", +"complex64,int32": "complex128", +"complex64,int64": "complex128", +"complex64,uint8": "complex64", +"complex64,uint16": "complex64", +"complex64,uint32": "complex128", +"complex64,uint64": "complex128", +"complex64,float32": "complex64", +"complex64,float64": "complex128", +"complex64,complex64": "complex64", +"complex64,complex128": "complex128", +"complex128,bool": "complex128", +"complex128,int8": "complex128", +"complex128,int16": "complex128", +"complex128,int32": "complex128", +"complex128,int64": "complex128", +"complex128,uint8": "complex128", +"complex128,uint16": "complex128", +"complex128,uint32": "complex128", +"complex128,uint64": "complex128", +"complex128,float32": "complex128", +"complex128,float64": "complex128", +"complex128,complex64": "complex128", +"complex128,complex128": "complex128" +}, +"weak": { +"bool": { +"int": "int64", +"float": "float64", +"complex": "complex128", +"truediv": "float64" +}, +"int8": { +"int": "int8", +"float": "float64", +"complex": "complex128", +"truediv": "float64" +}, +"int16": { +"int": "int16", +"float": "float64", +"complex": "complex128", +"truediv": "float64" +}, +"int32": { +"int": "int32", +"float": "float64", +"complex": "complex128", +"truediv": "float64" +}, +"int64": { +"int": "int64", +"float": "float64", +"complex": "complex128", +"truediv": "float64" +}, +"uint8": { +"int": "uint8", +"float": "float64", +"complex": "complex128", +"truediv": "float64" +}, +"uint16": { +"int": "uint16", +"float": "float64", +"complex": "complex128", +"truediv": "float64" +}, +"uint32": { +"int": "uint32", +"float": "float64", +"complex": "complex128", +"truediv": "float64" +}, +"uint64": { +"int": "uint64", +"float": "float64", +"complex": "complex128", +"truediv": "float64" +}, +"float32": { +"int": "float32", +"float": "float32", +"complex": "complex64", +"truediv": "float32" +}, +"float64": { +"int": "float64", +"float": "float64", +"complex": "complex128", +"truediv": "float64" +}, +"complex64": { +"int": "complex64", +"float": "complex64", +"complex": "complex64", +"truediv": "complex64" +}, +"complex128": { +"int": "complex128", +"float": "complex128", +"complex": "complex128", +"truediv": "complex128" +} +}, +"unary": { +"bool": { +"sqrt": "float16", +"exp": "float16", +"sin": "float16", +"abs": "bool", +"negative": "ERR:TypeError", +"floor": "bool", +"square": "int8" +}, +"int8": { +"sqrt": "float16", +"exp": "float16", +"sin": "float16", +"abs": "int8", +"negative": "int8", +"floor": "int8", +"square": "int8" +}, +"int16": { +"sqrt": "float32", +"exp": "float32", +"sin": "float32", +"abs": "int16", +"negative": "int16", +"floor": "int16", +"square": "int16" +}, +"int32": { +"sqrt": "float64", +"exp": "float64", +"sin": "float64", +"abs": "int32", +"negative": "int32", +"floor": "int32", +"square": "int32" +}, +"int64": { +"sqrt": "float64", +"exp": "float64", +"sin": "float64", +"abs": "int64", +"negative": "int64", +"floor": "int64", +"square": "int64" +}, +"uint8": { +"sqrt": "float16", +"exp": "float16", +"sin": "float16", +"abs": "uint8", +"negative": "uint8", +"floor": "uint8", +"square": "uint8" +}, +"uint16": { +"sqrt": "float32", +"exp": "float32", +"sin": "float32", +"abs": "uint16", +"negative": "uint16", +"floor": "uint16", +"square": "uint16" +}, +"uint32": { +"sqrt": "float64", +"exp": "float64", +"sin": "float64", +"abs": "uint32", +"negative": "uint32", +"floor": "uint32", +"square": "uint32" +}, +"uint64": { +"sqrt": "float64", +"exp": "float64", +"sin": "float64", +"abs": "uint64", +"negative": "uint64", +"floor": "uint64", +"square": "uint64" +}, +"float32": { +"sqrt": "float32", +"exp": "float32", +"sin": "float32", +"abs": "float32", +"negative": "float32", +"floor": "float32", +"square": "float32" +}, +"float64": { +"sqrt": "float64", +"exp": "float64", +"sin": "float64", +"abs": "float64", +"negative": "float64", +"floor": "float64", +"square": "float64" +}, +"complex64": { +"sqrt": "complex64", +"exp": "complex64", +"sin": "complex64", +"abs": "float32", +"negative": "complex64", +"floor": "ERR:TypeError", +"square": "complex64" +}, +"complex128": { +"sqrt": "complex128", +"exp": "complex128", +"sin": "complex128", +"abs": "float64", +"negative": "complex128", +"floor": "ERR:TypeError", +"square": "complex128" +} +}, +"reduce": { +"bool": { +"sum": "int64", +"prod": "int64", +"mean": "float64", +"max": "bool", +"cumsum": "int64", +"argmax": "int64" +}, +"int8": { +"sum": "int64", +"prod": "int64", +"mean": "float64", +"max": "int8", +"cumsum": "int64", +"argmax": "int64" +}, +"int16": { +"sum": "int64", +"prod": "int64", +"mean": "float64", +"max": "int16", +"cumsum": "int64", +"argmax": "int64" +}, +"int32": { +"sum": "int64", +"prod": "int64", +"mean": "float64", +"max": "int32", +"cumsum": "int64", +"argmax": "int64" +}, +"int64": { +"sum": "int64", +"prod": "int64", +"mean": "float64", +"max": "int64", +"cumsum": "int64", +"argmax": "int64" +}, +"uint8": { +"sum": "uint64", +"prod": "uint64", +"mean": "float64", +"max": "uint8", +"cumsum": "uint64", +"argmax": "int64" +}, +"uint16": { +"sum": "uint64", +"prod": "uint64", +"mean": "float64", +"max": "uint16", +"cumsum": "uint64", +"argmax": "int64" +}, +"uint32": { +"sum": "uint64", +"prod": "uint64", +"mean": "float64", +"max": "uint32", +"cumsum": "uint64", +"argmax": "int64" +}, +"uint64": { +"sum": "uint64", +"prod": "uint64", +"mean": "float64", +"max": "uint64", +"cumsum": "uint64", +"argmax": "int64" +}, +"float32": { +"sum": "float32", +"prod": "float32", +"mean": "float32", +"max": "float32", +"cumsum": "float32", +"argmax": "int64" +}, +"float64": { +"sum": "float64", +"prod": "float64", +"mean": "float64", +"max": "float64", +"cumsum": "float64", +"argmax": "int64" +}, +"complex64": { +"sum": "complex64", +"prod": "complex64", +"mean": "complex64", +"max": "complex64", +"cumsum": "complex64", +"argmax": "int64" +}, +"complex128": { +"sum": "complex128", +"prod": "complex128", +"mean": "complex128", +"max": "complex128", +"cumsum": "complex128", +"argmax": "int64" +} +} +} diff --git a/testdata/promotion.py b/testdata/promotion.py new file mode 100644 index 0000000..6cc679e --- /dev/null +++ b/testdata/promotion.py @@ -0,0 +1,22 @@ +# Generates testdata/promotion.json: `uv run --with numpy python testdata/promotion.py > testdata/promotion.json` +import numpy as np, json +dts=['bool','int8','int16','int32','int64','uint8','uint16','uint32','uint64','float32','float64','complex64','complex128'] +out={"numpy":np.__version__,"pair":{},"weak":{},"unary":{},"reduce":{}} +for a in dts: + for b in dts: + out["pair"][a+","+b]=str(np.result_type(np.dtype(a),np.dtype(b))) +for a in dts: + x=np.zeros(2,dtype=a) + out["weak"][a]={"int":str((x+1).dtype) if a!='bool' else str((x+1).dtype),"float":str((x+1.5).dtype),"complex":str((x+1j).dtype), + "truediv":str(np.true_divide(x,x).dtype),} + u={} + for f in ['sqrt','exp','sin','abs','negative','floor','square']: + try: u[f]=str(getattr(np,f)(x).dtype) + except Exception as e: u[f]="ERR:"+type(e).__name__ + out["unary"][a]=u + r={} + for f in ['sum','prod','mean','max','cumsum','argmax']: + try: r[f]=str(np.asarray(getattr(np,f)(x)).dtype) + except Exception as e: r[f]="ERR:"+type(e).__name__ + out["reduce"][a]=r +print(json.dumps(out,indent=0)) From 2ea7be47b7ea7913d8870cc4cfd6749ab55c5d46 Mon Sep 17 00:00:00 2001 From: tannevaled Date: Fri, 9 Oct 2026 20:51:20 +0200 Subject: [PATCH 2/3] feat: thirteen dtypes with NumPy 2.x promotion Array now holds bool, the eight integer types, float32/64 and complex64/128, chosen at run time as in NumPy. Float64 storage and every float64 kernel are untouched; the other dtypes live in a typed slice and run generic loops, one per element type, picked by type switches that gen_dtypes.go generates. Promotion is numpy.result_type; Go scalars are weak as Python scalars are under NEP 50 (Scalar, and the *Scalar methods' float64). Integer true division gives float64; sums and products accumulate in int64/uint64; float ufuncs on integers give float32 where NumPy gives float16. New: DType, ZerosOf/OnesOf/FullOf, FromSlice/Data/Item, AsType, AtComplex/SetComplex, Scalar, SumAll/ProdAll, LogicalNot/And/Or/Xor, Any/All, Real/Imag/Conj/Angle. Breaking: comparisons return Bool; Nonzero and ArgMax/ArgMinAxis return Int64 indices. MaskSelect and Where accept any dtype by truthiness, so the old 0/1 float masks keep working. The float64 comparison and arg-axis kernels they used are removed. Checked against NumPy 2.5.3: all 169 result_type cells, weak scalars, and the result dtype of unary ufuncs and reductions; 3,147 values over 47 operations on every dtype with edge values (bit-exact where both sides round correctly; a sabotaged integer max fails 48). Coverage 100% under the CI gate. Float64 Add/Sum/Max/AddInto A/B against main, interleaved: within noise (0.93-1.02). Co-Authored-By: Claude Opus 5.5 --- arith.go | 489 ++ docs/design-dtypes.md | 33 +- docs/plan-ndarray.md | 6 +- dtype.go | 233 + dtype_api_test.go | 600 ++ dtype_test.go | 208 + gen_dtypes.go | 356 ++ indexing.go | 54 +- indexing_test.go | 9 +- internal/kernels/kernels.go | 87 - internal/kernels/kernels_test.go | 7 +- linalg.go | 54 + manipulation.go | 57 +- ndarray.go | 178 +- reductions.go | 43 +- slicing.go | 2 +- storage.go | 329 + testdata/values.json | 9590 ++++++++++++++++++++++++++++++ testdata/values.py | 70 + typed.go | 533 ++ ufunc.go | 114 +- values_test.go | 267 + zz_dtypes_gen.go | 1549 +++++ 23 files changed, 14677 insertions(+), 191 deletions(-) create mode 100644 arith.go create mode 100644 dtype.go create mode 100644 dtype_api_test.go create mode 100644 dtype_test.go create mode 100644 gen_dtypes.go create mode 100644 storage.go create mode 100644 testdata/values.json create mode 100644 testdata/values.py create mode 100644 typed.go create mode 100644 values_test.go create mode 100644 zz_dtypes_gen.go diff --git a/arith.go b/arith.go new file mode 100644 index 0000000..6a253b7 --- /dev/null +++ b/arith.go @@ -0,0 +1,489 @@ +package ndarray + +import ( + "fmt" + "math/cmplx" + "reflect" +) + +// The operations on dtypes other than Float64. Each public method keeps its +// Float64 path as it was and comes here when an operand has another dtype; +// when the result is Float64 anyway the operands are converted and the +// Float64 path runs, so its kernels and their speed are kept. + +// Scalar returns a 0-d array holding v, to combine with arrays. A Go int, +// float or complex (of any size) is "weak", as a Python scalar is in NumPy 2: +// it takes the other operand's dtype when that can hold it, so +// +// int8Array.Add(Scalar(1)) // int8 +// float32Array.Mul(Scalar(0.5)) // float32 +// int8Array.Add(Scalar(0.5)) // float64 +// +// A bool gives a Bool array. Scalar panics on any other type. +func Scalar(v any) *Array { + rv := reflect.ValueOf(v) + var a *Array + switch rv.Kind() { + case reflect.Bool: + a = fromStore([]bool{rv.Bool()}, []int{}) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + a = fromStore([]int64{rv.Int()}, []int{}) + a.weak = weakInt + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + a = fromStore([]uint64{rv.Uint()}, []int{}) + a.weak = weakInt + case reflect.Float32, reflect.Float64: + a = scalarArray(rv.Float()) + case reflect.Complex64, reflect.Complex128: + a = fromStore([]complex128{rv.Complex()}, []int{}) + a.weak = weakComplex + default: + panic(fmt.Sprintf("ndarray: Scalar of a %T", v)) + } + return a +} + +// toFloat64 returns a as a Float64 array: a itself, or a converted copy. +func (a *Array) toFloat64() *Array { + if a.dtype == Float64 { + return a + } + r := a.AsType(Float64) + r.weak = a.weak + return r +} + +// arith is the binary arithmetic driver for operands of any dtype. k is the +// Float64 kernel, used whenever the result is Float64. +func (a *Array) arith(b *Array, op binCode, k func(dst, x, y []float64)) (*Array, error) { + if a.dtype == Float64 && b.dtype == Float64 { + return a.binOp(b, k) + } + dt := promote(a, b) + switch { + case op == opDiv && (dt == Bool || dt.IsInteger()): + dt = Float64 // true division + case op == opSub && dt == Bool: + return nil, fmt.Errorf("%w: subtracting bool arrays; use LogicalXor", ErrDType) + } + if dt == Float64 { + return a.toFloat64().binOp(b.toFloat64(), k) + } + shape, err := broadcastShape(a.shape, b.shape) + if err != nil { + return nil, err + } + d := makeStore(dt, prod(shape)) + arithStore(op, d, a.operandStore(dt, shape), b.operandStore(dt, shape)) + return a.result(d, append([]int(nil), shape...)), nil +} + +// arithInto is arith writing into out, which must be contiguous and have the +// broadcast shape; the result is converted to out's dtype. +func (a *Array) arithInto(out, b *Array, op binCode, k func(dst, x, y []float64)) error { + if a.dtype == Float64 && b.dtype == Float64 && out.dtype == Float64 { + return a.binOpInto(out, b, k) + } + r, err := a.arith(b, op, k) + if err != nil { + return err + } + if !out.isContiguous() { + return fmt.Errorf("%w: out must be contiguous", ErrBroadcast) + } + if !sameShape(out.shape, r.shape) { + return fmt.Errorf("%w: out shape %v != result shape %v", ErrBroadcast, out.shape, r.shape) + } + convertStore(out.store(), r.store()) + return nil +} + +// compare is the comparison driver: the operands are promoted to their common +// dtype and compared into a Bool array. +func (a *Array) compare(b *Array, op cmpCode) (*Array, error) { + shape, err := broadcastShape(a.shape, b.shape) + if err != nil { + return nil, err + } + dt := promote(a, b) + d := make([]bool, prod(shape)) + cmpStore(op, d, a.operandStore(dt, shape), b.operandStore(dt, shape)) + return a.result(d, append([]int(nil), shape...)), nil +} + +// LogicalNot returns the elementwise "not zero is false" of a, as a Bool array. +func (a *Array) LogicalNot() *Array { + b := a.operandStore(Bool, a.shape).([]bool) + d := make([]bool, len(b)) + for i, v := range b { + d[i] = !v + } + return a.result(d, append([]int(nil), a.shape...)) +} + +// logical is the driver for LogicalAnd/Or/Xor: both operands are read by +// truthiness. +func (a *Array) logical(b *Array, f func(x, y bool) bool) (*Array, error) { + shape, err := broadcastShape(a.shape, b.shape) + if err != nil { + return nil, err + } + x := a.operandStore(Bool, shape).([]bool) + y := b.operandStore(Bool, shape).([]bool) + d := make([]bool, len(x)) + for i := range d { + d[i] = f(x[i], y[i]) + } + return a.result(d, append([]int(nil), shape...)), nil +} + +// LogicalAnd returns the elementwise "a and b" of the operands' truthiness, +// with broadcasting, as a Bool array. +func (a *Array) LogicalAnd(b *Array) (*Array, error) { + return a.logical(b, func(x, y bool) bool { return x && y }) +} + +// LogicalOr returns the elementwise "a or b" of the operands' truthiness. +func (a *Array) LogicalOr(b *Array) (*Array, error) { + return a.logical(b, func(x, y bool) bool { return x || y }) +} + +// LogicalXor returns the elementwise "a xor b" of the operands' truthiness. +func (a *Array) LogicalXor(b *Array) (*Array, error) { + return a.logical(b, func(x, y bool) bool { return x != y }) +} + +// Any reports whether any element is non-zero. +func (a *Array) Any() bool { + for _, v := range a.operandStore(Bool, a.shape).([]bool) { + if v { + return true + } + } + return false +} + +// All reports whether every element is non-zero (true for an empty array). +func (a *Array) All() bool { + for _, v := range a.operandStore(Bool, a.shape).([]bool) { + if !v { + return false + } + } + return true +} + +// floatDType is the dtype NumPy's float ufuncs (sqrt, exp, sin ...) give for an +// input of dtype dt: inexact types are kept; integers become the smallest float +// that holds them exactly. NumPy gives float16 for 8-bit integers and bool; +// there is no float16 here, so they get float32. +func floatDType(dt DType) DType { + switch { + case dt.IsFloat() || dt.IsComplex(): + return dt + case dt.Size() <= 2: + return Float32 + } + return Float64 +} + +// mathUnary applies a float ufunc to a non-Float64 array. f64 is the Float64 +// method, used when the result is Float64. +func (a *Array) mathUnary(code unaryCode, f64 func(*Array) *Array) *Array { + dt := floatDType(a.dtype) + if dt == Float64 { + return f64(a.AsType(Float64)) + } + src := a.operandStore(dt, a.shape) + d := makeStore(dt, a.Size()) + switch s := src.(type) { + case []float32: + unaryFloat32(realFuncs[code], d.([]float32), s) + case []complex64: + unaryComplex(a.complexFunc(code), d.([]complex64), s) + case []complex128: + unaryComplex(a.complexFunc(code), d.([]complex128), s) + } + return a.result(d, append([]int(nil), a.shape...)) +} + +// complexFunc returns the complex function for a unary code, panicking for an +// operation NumPy does not define on complex numbers. +func (a *Array) complexFunc(code unaryCode) func(complex128) complex128 { + f := complexFuncs[code] + if f == nil { + panic(fmt.Sprintf("ndarray: operation not defined for %v", a.dtype)) + } + return f +} + +// exactUnary is the driver of Floor, Ceil and Round: integers and bools are +// returned unchanged (as a copy), as in NumPy. +func (a *Array) exactUnary(code unaryCode, f64 func(*Array) *Array) *Array { + if a.dtype == Bool || a.dtype.IsInteger() { + return a.Copy() + } + return a.mathUnary(code, f64) +} + +// intUnary applies negation, absolute value or squaring, which keep the dtype +// of an integer array; f64 is the Float64 method. +func (a *Array) intUnary(code unaryCode, f64 func(*Array) *Array) *Array { + switch { + case a.dtype == Bool && code == uNeg: + panic("ndarray: negating a bool array; use LogicalNot") + case a.dtype == Bool && code == uAbs: + return a.Copy() + case a.dtype == Bool: // square of bool is int8, as in NumPy + return a.AsType(Int8).intUnary(code, f64) + case a.dtype.IsInteger(): + src := a.contiguousStore() + d := makeStore(a.dtype, a.Size()) + intUnaryStore(code, d, src) + return a.result(d, append([]int(nil), a.shape...)) + case a.dtype.IsComplex() && code == uAbs: + return a.absComplex() + } + return a.mathUnary(code, f64) +} + +// absComplex is the modulus of a complex array, as a float array of the same +// precision. +func (a *Array) absComplex() *Array { + n := a.Size() + var d any + switch s := a.contiguousStore().(type) { + case []complex64: + r := make([]float32, n) + absComplex(r, s) + d = r + case []complex128: + r := make([]float64, n) + absComplex(r, s) + d = r + } + return a.result(d, append([]int(nil), a.shape...)) +} + +// power is Power for a non-Float64 array: the exponent is a weak float. +func (a *Array) power(p float64) *Array { + src := a.contiguousStore() + switch s := src.(type) { + case []float32: + d := make([]float32, len(s)) + powFloat32(p, d, s) + return a.result(d, append([]int(nil), a.shape...)) + case []complex64: + d := make([]complex64, len(s)) + powComplex(complex(p, 0), d, s) + return a.result(d, append([]int(nil), a.shape...)) + case []complex128: + d := make([]complex128, len(s)) + powComplex(complex(p, 0), d, s) + return a.result(d, append([]int(nil), a.shape...)) + } + return a.AsType(Float64).Power(p) +} + +// realOnly panics for a complex array in an operation whose result is a +// float64, naming what to use instead. +func (a *Array) realOnly(op, instead string) { + if a.dtype.IsComplex() { + panic(fmt.Sprintf("ndarray: %s of a %v array; use %s", op, a.dtype, instead)) + } +} + +// accDType is the dtype NumPy accumulates sums and products of dt in: Int64 +// for bools and signed integers, Uint64 for unsigned, dt itself otherwise. +func accDType(dt DType) DType { + switch { + case dt == Bool || dt.IsSigned(): + return Int64 + case dt.IsInteger(): + return Uint64 + } + return dt +} + +// reduceTyped is the axis-reduction driver for non-Float64 arrays. +func (a *Array) reduceTyped(axis int, keepdims bool, op reduceCode) (*Array, error) { + ax, err := a.normalizeAxis(axis) + if err != nil { + return nil, err + } + outer, axisLen, inner := a.reduceLayout(ax) + dt := a.dtype + if op == rSum || op == rProd { + dt = accDType(dt) + } + shape := a.reduceShape(ax, keepdims) + dst := makeStore(dt, outer*inner) + if axisLen == 0 { + if op == rMax || op == rMin { + return nil, fmt.Errorf("%w: reduction along zero-length axis %d", ErrShapeMismatch, ax) + } + if op == rProd { + one := makeStore(dt, 1) + setComplex(one, 0, 1) + fillStore(dst, one) + } + return a.result(dst, shape), nil + } + if outer*inner > 0 { + src := a.operandStore(dt, a.shape) + if op == rSum || op == rProd { + accumulateStore(op, dst, src, outer, axisLen, inner) + } else { + extremeStore(op, dst, src, outer, axisLen, inner) + } + } + return a.result(dst, shape), nil +} + +// reduceAll reduces every element of a non-Float64 array into a 0-d array. +func (a *Array) reduceAll(op reduceCode) (*Array, error) { + return a.Ravel().reduceTyped(0, false, op) +} + +// SumAll returns the sum of all elements as a 0-d array of the dtype NumPy +// gives it: Int64 for bools and signed integers, Uint64 for unsigned, the +// array's own dtype otherwise. Unlike Sum, it holds every dtype exactly, +// including the complex ones and integers beyond 2^53. +func (a *Array) SumAll() *Array { + if a.dtype == Float64 { + return scalarOf(a.Sum(), a.ws) + } + r, _ := a.reduceAll(rSum) // a 1-D reduction over axis 0 cannot fail + return r +} + +// ProdAll returns the product of all elements as a 0-d array; see SumAll. +func (a *Array) ProdAll() *Array { + if a.dtype == Float64 { + return scalarOf(a.Prod(), a.ws) + } + r, _ := a.reduceAll(rProd) + return r +} + +// scalarOf is a strong 0-d Float64 array. +func scalarOf(v float64, ws *Workspace) *Array { + return &Array{data: []float64{v}, shape: []int{}, strides: []int{}, ws: ws} +} + +// argTyped is the arg-reduction driver for every dtype: it returns Int64 +// indices, as NumPy does. +func (a *Array) argTyped(axis int, keepdims, larger bool) (*Array, error) { + ax, err := a.normalizeAxis(axis) + if err != nil { + return nil, err + } + outer, axisLen, inner := a.reduceLayout(ax) + if axisLen == 0 { + return nil, fmt.Errorf("%w: reduction along zero-length axis %d", ErrShapeMismatch, ax) + } + dst := make([]int64, outer*inner) + if len(dst) > 0 { + argStore(larger, dst, a.contiguousStore(), outer, axisLen, inner) + } + return a.result(dst, a.reduceShape(ax, keepdims)), nil +} + +// scanTyped is the cumulative-scan driver for non-Float64 arrays. +func (a *Array) scanTyped(axis int, op reduceCode) (*Array, error) { + ax, err := a.normalizeAxis(axis) + if err != nil { + return nil, err + } + outer, axisLen, inner := a.reduceLayout(ax) + dt := accDType(a.dtype) + dst := makeStore(dt, a.Size()) + if storeLen(dst) > 0 { + scanStore(op, dst, a.operandStore(dt, a.shape), outer, axisLen, inner) + } + return a.result(dst, append([]int(nil), a.shape...)), nil +} + +// clipTyped is Clip for a non-Float64 array. The bounds are weak floats: an +// integer array is clipped as float64, a float32 array stays float32. +func (a *Array) clipTyped(lo, hi float64) (*Array, error) { + switch { + case a.dtype.IsComplex(): + return nil, fmt.Errorf("%w: clip of a %v array", ErrDType, a.dtype) + case a.dtype != Float32: + return a.AsType(Float64).Clip(lo, hi) + } + d := make([]float32, a.Size()) + clipOrdered(d, a.contiguousStore().([]float32), float32(lo), float32(hi)) + return a.result(d, append([]int(nil), a.shape...)), nil +} + +// whereTyped is Where for operands of any dtype. +func whereTyped(cond, t, f *Array, shape []int) *Array { + dt := promote(t, f) + c := cond.operandStore(Bool, shape).([]bool) + d := makeStore(dt, len(c)) + whereStore(d, c, t.operandStore(dt, shape), f.operandStore(dt, shape)) + r := fromStore(d, append([]int(nil), shape...)) + r.ws = wsOf(cond, t, f) + return r +} + +// Real returns the real part of the elements: a float array of the same +// precision for a complex array, a copy otherwise (as numpy.real). +func (a *Array) Real() *Array { + if !a.dtype.IsComplex() { + return a.Copy() + } + return a.AsType(complexPart(a.dtype)) +} + +// Imag returns the imaginary part of the elements: a float array of the same +// precision for a complex array, zeros of a's dtype otherwise (as numpy.imag). +func (a *Array) Imag() *Array { + if !a.dtype.IsComplex() { + z, _ := ZerosOf(a.dtype, a.shape...) // a's shape is valid + z.ws = a.ws + return z + } + r := a.complexMap(func(z complex128) complex128 { return complex(imag(z), 0) }) + return r.AsType(complexPart(a.dtype)) +} + +// Conj returns the complex conjugate (a copy for a real array). +func (a *Array) Conj() *Array { + if !a.dtype.IsComplex() { + return a.Copy() + } + return a.complexMap(cmplx.Conj) +} + +// Angle returns the argument of each element in radians, as numpy.angle: a +// float array (float32 for complex64, float64 otherwise). +func (a *Array) Angle() *Array { + if !a.dtype.IsComplex() { + return a.AsType(Complex128).Angle() + } + r := a.complexMap(func(z complex128) complex128 { return complex(cmplx.Phase(z), 0) }) + return r.AsType(complexPart(a.dtype)) +} + +// complexMap applies f to the elements of a complex array. +func (a *Array) complexMap(f func(complex128) complex128) *Array { + d := makeStore(a.dtype, a.Size()) + switch s := a.contiguousStore().(type) { + case []complex64: + unaryComplex(f, d.([]complex64), s) + case []complex128: + unaryComplex(f, d.([]complex128), s) + } + return a.result(d, append([]int(nil), a.shape...)) +} + +// complexPart is the float dtype of one component of a complex dtype. +func complexPart(dt DType) DType { + if dt == Complex64 { + return Float32 + } + return Float64 +} diff --git a/docs/design-dtypes.md b/docs/design-dtypes.md index 90f747d..5afd98a 100644 --- a/docs/design-dtypes.md +++ b/docs/design-dtypes.md @@ -1,6 +1,6 @@ # Design — dtypes -> Status: accepted, implementation in progress. Supersedes decision 4 of +> Status: implemented (Phase 2). Supersedes decision 4 of > [plan-ndarray.md](plan-ndarray.md) ("`float64` first"). ## Why now @@ -119,12 +119,43 @@ dtype by dtype after correctness, `float32` first. | before | after | migration | |---|---|---| | comparisons return a `Float64` 0/1 array | they return `Bool` | `.AsType(Float64)` to get the old values; `MaskSelect` and `Where` need no change | +| `Nonzero` returns `Float64` indices | `Int64`, as `np.flatnonzero` | `.AsType(Float64)`, or `Data[int64]` | +| `ArgMaxAxis`/`ArgMinAxis` return `Float64` indices | `Int64`, as NumPy | same | | `MaskSelect` takes a 0/1 float mask | it takes any dtype (truthiness) | none | The only consumer in the fleet, `go-embedded-ruby/ruby` (`internal/vm/ndarray.go`), does not use comparisons; it is pinned to a June pseudo-version and will be moved forward in its own pull request. +## What the implementation added + +- `Scalar(v)` makes the weak operand from any Go number; the `*Scalar` + methods' `float64` arguments are weak floats. +- `SumAll`/`ProdAll` return a 0-d array in the accumulation dtype, exact for + every dtype; `Sum`/`Prod`/`Mean`/`Max`/`Min` keep returning `float64` and + panic on complex arrays (naming the method to use instead). +- `LogicalNot`/`LogicalAnd`/`LogicalOr`/`LogicalXor`, `Any`, `All`, and + `Real`/`Imag`/`Conj`/`Angle`. +- Float-to-integer casts saturate on every platform. NumPy leaves them + undefined: on arm64 it saturates to 64 bits and then wraps (`+Inf` to `int8` + is `-1`), on x86 `NaN` gives the minimum. +- A `Workspace` serves `Float64` results only; the other dtypes allocate from + the heap for now. +- Only `Float64` has SIMD kernels and multicore fan-out; the other dtypes run + tight generic loops (one per element type, generated by `gen_dtypes.go`). + `MatMul` on them is a plain triple loop. + +### Verification + +- `testdata/promotion.json`: every one of the 169 `result_type` cells, the + weak-scalar results, and the result dtype of the unary ufuncs and the + reductions, from NumPy 2.5.3. All match, with `float16` read as `float32`. +- `testdata/values.json`: 47 operations on every dtype with edge values + (integer limits, NaN, ±Inf, −0) — 3,147 values. Correctly rounded + operations and every integer or bool result must match to the bit; the + others within 8 machine epsilons of NumPy's result dtype. A deliberately + broken integer `max` fails 48 of them. + ## Delivery 1. **Core** — `DType`, storage, `AsType`, creation with a dtype diff --git a/docs/plan-ndarray.md b/docs/plan-ndarray.md index 7e36d9d..51c907e 100644 --- a/docs/plan-ndarray.md +++ b/docs/plan-ndarray.md @@ -151,8 +151,10 @@ suite (plus the per-arch CI jobs already wired in `.github/workflows/ci.yml`). into the flattened array, negative indices from the end). These give NumPy boolean indexing today using the 0/1 masks the comparison ufuncs return; a first-class bool dtype (so masks are real booleans) remains Phase 2. -- **Phase 2 — dtypes.** Generalise beyond `float64` (float32, int64/int32, - complex128, bool) with a dtype abstraction; typed kernels. +- **Phase 2 — dtypes — DONE.** Thirteen dtypes (bool, signed and unsigned + integers, float32/64, complex64/128) as a runtime property of `Array`, + with NumPy 2.x promotion; see [design-dtypes.md](design-dtypes.md). Next: + SIMD kernels for `float32`, and the `fft` bridge. - **Phase 3 — broadcasting ufuncs.** A general ufunc framework over views (the axis-reduction primitives above are the first step); more elementwise and reduction ufuncs. diff --git a/dtype.go b/dtype.go new file mode 100644 index 0000000..6f55800 --- /dev/null +++ b/dtype.go @@ -0,0 +1,233 @@ +package ndarray + +import "fmt" + +// DType is the element type of an Array. It is a runtime property, as in +// NumPy: one Array type holds any of them, and operations on two arrays pick +// the result type from both operands (see ResultType). The zero value is +// Float64, so every array built before dtypes existed keeps its meaning. +type DType uint8 + +// The element types. Their names, sizes and promotion follow NumPy. +const ( + Float64 DType = iota + Bool + Int8 + Int16 + Int32 + Int64 + Uint8 + Uint16 + Uint32 + Uint64 + Float32 + Complex64 + Complex128 + + numDTypes = iota +) + +var dtypeNames = [numDTypes]string{ + Float64: "float64", Bool: "bool", + Int8: "int8", Int16: "int16", Int32: "int32", Int64: "int64", + Uint8: "uint8", Uint16: "uint16", Uint32: "uint32", Uint64: "uint64", + Float32: "float32", Complex64: "complex64", Complex128: "complex128", +} + +// String returns the NumPy name of the dtype ("float64", "int8", ...). +func (d DType) String() string { + if int(d) < numDTypes { + return dtypeNames[d] + } + return fmt.Sprintf("DType(%d)", uint8(d)) +} + +// ParseDType returns the dtype with the given NumPy name. +func ParseDType(name string) (DType, error) { + for d, n := range dtypeNames { + if n == name { + return DType(d), nil + } + } + return 0, fmt.Errorf("%w: unknown dtype %q", ErrDType, name) +} + +// ErrDType is returned when an operation is not defined for an array's dtype, +// or a dtype name is not known. +var ErrDType = fmt.Errorf("ndarray: unsupported dtype") + +// Size returns the size of one element in bytes. +func (d DType) Size() int { + switch d { + case Bool, Int8, Uint8: + return 1 + case Int16, Uint16: + return 2 + case Int32, Uint32, Float32: + return 4 + case Complex128: + return 16 + } + return 8 // Float64, Int64, Uint64, Complex64 +} + +// IsBool reports whether d is Bool. +func (d DType) IsBool() bool { return d == Bool } + +// IsInteger reports whether d is a signed or unsigned integer type. +func (d DType) IsInteger() bool { return d >= Int8 && d <= Uint64 } + +// IsSigned reports whether d is a signed integer type. +func (d DType) IsSigned() bool { return d >= Int8 && d <= Int64 } + +// IsFloat reports whether d is Float32 or Float64. +func (d DType) IsFloat() bool { return d == Float32 || d == Float64 } + +// IsComplex reports whether d is Complex64 or Complex128. +func (d DType) IsComplex() bool { return d == Complex64 || d == Complex128 } + +// signedOf returns the signed integer dtype of the given size in bytes. +// It is asked only for 2, 4 and 8: the type holding an unsigned integer of +// half that size. +func signedOf(size int) DType { + switch size { + case 2: + return Int16 + case 4: + return Int32 + } + return Int64 +} + +// ResultType returns the dtype NumPy gives the result of combining arrays of +// dtypes a and b, numpy.result_type(a, b): +// +// - Bool gives way to anything. +// - Two integers of the same signedness give the larger; a signed and an +// unsigned give the smallest signed type holding both, and Float64 when +// there is none (Int64 with Uint64). +// - An integer with a float or complex type gives that type when the +// integer fits its mantissa exactly (8- and 16-bit integers with Float32 +// and Complex64), else the 64-bit float or complex type. +// - Floats and complexes give the wider, complex when either is. +// +// The table this produces is checked against NumPy 2.5.3 cell by cell. +func ResultType(a, b DType) DType { + if a == b { + return a + } + switch { + case a == Bool: + return b + case b == Bool: + return a + } + if a.IsInteger() && b.IsInteger() { + if a.IsSigned() == b.IsSigned() { + return maxSize2(a, b) + } + s, u := a, b + if u.IsSigned() { + s, u = u, s + } + if s.Size() > u.Size() { + return s + } + if u.Size() < 8 { + return signedOf(2 * u.Size()) + } + return Float64 + } + // At least one inexact type. An integer becomes the float that holds it. + a, b = inexactFor(a, b), inexactFor(b, a) + complexOut := a.IsComplex() || b.IsComplex() + prec := max(precision(a), precision(b)) + switch { + case complexOut && prec <= 4: + return Complex64 + case complexOut: + return Complex128 + case prec <= 4: + return Float32 + } + return Float64 +} + +// inexactFor maps an integer d, combined with the inexact type other, to the +// float type that represents it: Float32 for 8- and 16-bit integers next to a +// single-precision type, else Float64. Inexact types are returned unchanged. +func inexactFor(d, other DType) DType { + if !d.IsInteger() { + return d + } + if d.Size() <= 2 && precision(other) <= 4 { + return Float32 + } + return Float64 +} + +// precision is the size in bytes of one real component of an inexact dtype. +func precision(d DType) int { + if d.IsComplex() { + return d.Size() / 2 + } + return d.Size() +} + +// maxSize2 returns the larger of two integer dtypes of the same signedness. +func maxSize2(a, b DType) DType { + if a.Size() >= b.Size() { + return a + } + return b +} + +// scalarKind is the kind of a Go scalar operand: under NumPy 2's rules +// (NEP 50) a Python int, float or complex is "weak" — it takes the array's +// dtype when the array's kind can hold it, so float32_array + 1.5 stays +// float32. Go scalars passed to this package behave the same way. +type scalarKind uint8 + +const ( + strong scalarKind = iota // not a scalar: an ordinary array + weakInt + weakFloat + weakComplex +) + +// weakResult returns the dtype of an operation between an array of dtype d +// and a weak scalar of kind k. +func weakResult(d DType, k scalarKind) DType { + switch k { + case weakInt: + if d == Bool { + return Int64 + } + case weakFloat: + if d == Bool || d.IsInteger() { + return Float64 + } + case weakComplex: + switch { + case d == Float32 || d == Complex64: + return Complex64 + case !d.IsComplex(): + return Complex128 + } + } + return d +} + +// promote returns the result dtype of combining a and b, honouring a weak +// scalar operand on either side. +func promote(a, b *Array) DType { + switch { + case a.weak != strong && b.weak != strong: + return weakResult(weakResult(Bool, a.weak), b.weak) + case b.weak != strong: + return weakResult(a.dtype, b.weak) + case a.weak != strong: + return weakResult(b.dtype, a.weak) + } + return ResultType(a.dtype, b.dtype) +} diff --git a/dtype_api_test.go b/dtype_api_test.go new file mode 100644 index 0000000..765281d --- /dev/null +++ b/dtype_api_test.go @@ -0,0 +1,600 @@ +package ndarray + +import ( + "errors" + "math" + "math/cmplx" + "reflect" + "strings" + "testing" +) + +// mustPanic fails the test unless f panics with a message containing want. +func mustPanic(t *testing.T, want string, f func()) { + t.Helper() + defer func() { + t.Helper() + p := recover() + if p == nil { + t.Errorf("no panic, want %q", want) + return + } + if s, _ := p.(string); !strings.Contains(s, want) { + t.Errorf("panic %v, want %q", p, want) + } + }() + f() +} + +func data[T elemT](t *testing.T, a *Array) []T { + t.Helper() + d, ok := Data[T](a) + if !ok { + t.Fatalf("Data[%v] of a %v array", dtypeOf[T](), a.DType()) + } + return d +} + +func TestTypedConstructionAndAccess(t *testing.T) { + a, err := FromSlice([]int16{1, -2, 3, 4, 5, 6}, 2, 3) + if err != nil || a.DType() != Int16 { + t.Fatal(a, err) + } + if got := Item[int16](a, 0, 1); got != -2 { + t.Errorf("Item = %d", got) + } + if got := a.At(1, 2); got != 6 { + t.Errorf("At = %g", got) + } + a.Set(-7.9, 1, 0) // truncates toward zero + if got := Item[int16](a, 1, 0); got != -7 { + t.Errorf("Set(-7.9) stored %d", got) + } + a.SetComplex(9+5i, 0, 0) + if got := a.AtComplex(0, 0); got != 9 { + t.Errorf("SetComplex/AtComplex = %v", got) + } + if _, ok := Data[float32](a); ok { + t.Error("Data[float32] of an int16 array succeeded") + } + mustPanic(t, "Item[int8] of a int16", func() { Item[int8](a, 0, 0) }) + + // Data of a strided view is a contiguous copy. + col, _ := a.Slice(All(), A(1)) + if got := data[int16](t, col); !reflect.DeepEqual(got, []int16{-2, 5}) { + t.Errorf("column = %v", got) + } + + if _, err := FromSlice([]bool{true}, 2); !errors.Is(err, ErrShapeMismatch) { + t.Errorf("FromSlice size mismatch err = %v", err) + } + if _, err := FromSlice([]bool{true}, -1); !errors.Is(err, ErrShapeMismatch) { + t.Errorf("FromSlice negative shape err = %v", err) + } + if _, err := ZerosOf(DType(99), 2); !errors.Is(err, ErrDType) { + t.Errorf("ZerosOf(99) err = %v", err) + } + if _, err := ZerosOf(Int8, -1); !errors.Is(err, ErrShapeMismatch) { + t.Errorf("ZerosOf negative err = %v", err) + } + if _, err := FullOf(Int8, 1, -1); !errors.Is(err, ErrShapeMismatch) { + t.Errorf("FullOf negative err = %v", err) + } + f, _ := ZerosOf(Float64, 2) + if f.DType() != Float64 || f.Size() != 2 { + t.Errorf("ZerosOf(Float64) = %v", f) + } + c, _ := FullOf(Complex64, 1-2i, 2) + if got := data[complex64](t, c); got[1] != 1-2i { + t.Errorf("FullOf complex = %v", got) + } + mustPanic(t, "use AtComplex", func() { c.At(0) }) + for _, typ := range []any{[]bool{}, []int8{}, []uint32{}, []complex128{}} { + r := fromStore(typ, []int{0}) + if r.dtype != dtypeOfStore(typ) { + t.Errorf("fromStore(%T) dtype %v", typ, r.dtype) + } + } + if dtypeOf[float64]() != Float64 || dtypeOf[uint16]() != Uint16 { + t.Error("dtypeOf") + } +} + +func TestTypedString(t *testing.T) { + cases := []struct { + a *Array + want string + }{ + {must(FromSlice([]bool{true, false}, 2)), "Array(shape=[2], dtype=bool, data=[true false])"}, + {must(FromSlice([]int8{-1, 2}, 2)), "Array(shape=[2], dtype=int8, data=[-1 2])"}, + {must(FromSlice([]uint64{math.MaxUint64}, 1)), "Array(shape=[1], dtype=uint64, data=[18446744073709551615])"}, + {must(FromSlice([]float32{0.1}, 1)), "Array(shape=[1], dtype=float32, data=[0.1])"}, + {must(FromSlice([]complex64{1 - 2i, complex(0, float32(math.Copysign(0, -1)))}, 2)), + "Array(shape=[2], dtype=complex64, data=[(1-2j) (0-0j)])"}, + {must(FromSlice([]complex128{0.5 + 1i}, 1)), "Array(shape=[1], dtype=complex128, data=[(0.5+1j)])"}, + } + for _, c := range cases { + if got := c.a.String(); got != c.want { + t.Errorf("String = %s, want %s", got, c.want) + } + } +} + +func must(a *Array, err error) *Array { + if err != nil { + panic(err) + } + return a +} + +func TestTypedStructural(t *testing.T) { + a := must(FromSlice([]int32{1, 2, 3, 4, 5, 6}, 2, 3)) + tr := a.Transpose() + if tr.DType() != Int32 || Item[int32](tr, 2, 1) != 6 { + t.Errorf("Transpose = %v", tr) + } + // Reshape of a non-contiguous view copies. + r := must(tr.Reshape(6)) + if got := data[int32](t, r); !reflect.DeepEqual(got, []int32{1, 4, 2, 5, 3, 6}) { + t.Errorf("Reshape of transpose = %v", got) + } + if got := data[int32](t, tr.Ravel()); !reflect.DeepEqual(got, []int32{1, 4, 2, 5, 3, 6}) { + t.Errorf("Ravel = %v", got) + } + fl := a.Flatten() + fl.Set(0, 0) + if Item[int32](a, 0, 0) != 1 { + t.Error("Flatten shares storage") + } + cp := tr.Copy() + if cp.DType() != Int32 || !cp.isContiguous() { + t.Errorf("Copy = %v", cp) + } + row := must(a.Slice(A(1))) + ex := must(row.ExpandDims(0)) + sq := must(ex.Squeeze()) + if got := data[int32](t, sq); !reflect.DeepEqual(got, []int32{4, 5, 6}) { + t.Errorf("slice/expand/squeeze = %v", got) + } + + // Concatenation promotes, like numpy.concatenate. + b := must(FromSlice([]float32{0.5, 1.5, 2.5}, 1, 3)) + cat := must(Concatenate([]*Array{a, b}, 0)) + if cat.DType() != Float64 || cat.At(2, 2) != 2.5 || cat.At(1, 0) != 4 { + t.Errorf("Concatenate int32+float32 = %v", cat) + } + u8 := must(FromSlice([]uint8{1, 2, 3}, 1, 3)) + cat = must(VStack([]*Array{u8, must(FromSlice([]int8{-1, -2, -3}, 3))})) + if cat.DType() != Int16 || Item[int16](cat, 1, 2) != -3 { + t.Errorf("VStack uint8+int8 = %v", cat) + } + if e := must(Concatenate([]*Array{must(ZerosOf(Int8, 0, 2)), must(ZerosOf(Int8, 0, 2))}, 0)); e.Size() != 0 || e.DType() != Int8 { + t.Errorf("empty concat = %v", e) + } + st := must(Stack([]*Array{must(FromSlice([]bool{true}, 1)), must(FromSlice([]bool{false}, 1))}, 1)) + if st.DType() != Bool || !reflect.DeepEqual(st.Shape(), []int{1, 2}) { + t.Errorf("Stack bool = %v", st) + } + + tk := must(a.Take(-1, 0)) + if got := data[int32](t, tk); !reflect.DeepEqual(got, []int32{6, 1}) { + t.Errorf("Take = %v", got) + } + if _, err := a.Take(6); !errors.Is(err, ErrShapeMismatch) { + t.Errorf("Take out of range err = %v", err) + } +} + +func TestMasks(t *testing.T) { + x := must(FromSlice([]int64{5, -1, 7, 0}, 4)) + m := must(x.Greater(Scalar(0))) + if m.DType() != Bool || !reflect.DeepEqual(data[bool](t, m), []bool{true, false, true, false}) { + t.Fatalf("x > 0 = %v", m) + } + sel := must(x.MaskSelect(m)) + if got := data[int64](t, sel); !reflect.DeepEqual(got, []int64{5, 7}) { + t.Errorf("x[x>0] = %v", got) + } + // A float array and a bool mask, and the old 0/1 float mask. + f := must(FromData([]float64{1, 2, 3, 4}, 4)) + if got := must(f.MaskSelect(m)).materialize(); !reflect.DeepEqual(got, []float64{1, 3}) { + t.Errorf("float[boolmask] = %v", got) + } + if got := data[int64](t, must(x.MaskSelect(must(FromData([]float64{0, 1, 0, 1}, 4))))); !reflect.DeepEqual(got, []int64{-1, 0}) { + t.Errorf("int[floatmask] = %v", got) + } + nz := m.Nonzero() + if got := data[int64](t, nz); !reflect.DeepEqual(got, []int64{0, 2}) { + t.Errorf("Nonzero = %v", got) + } + not := m.LogicalNot() + if got := data[bool](t, not); !reflect.DeepEqual(got, []bool{false, true, false, true}) { + t.Errorf("LogicalNot = %v", got) + } + y := must(FromSlice([]float64{1, 1, 0, 0}, 4)) + for name, c := range map[string]struct { + f func(*Array) (*Array, error) + want []bool + }{ + "and": {m.LogicalAnd, []bool{true, false, false, false}}, + "or": {m.LogicalOr, []bool{true, true, true, false}}, + "xor": {m.LogicalXor, []bool{false, true, true, false}}, + } { + if got := data[bool](t, must(c.f(y))); !reflect.DeepEqual(got, c.want) { + t.Errorf("%s = %v, want %v", name, got, c.want) + } + } + if _, err := m.LogicalAnd(must(Zeros(3))); !errors.Is(err, ErrBroadcast) { + t.Errorf("LogicalAnd broadcast err = %v", err) + } + if !m.Any() || m.All() || must(Zeros(2)).Any() || !must(ZerosOf(Bool, 0)).All() || !must(OnesOf(Int8, 2)).All() { + t.Error("Any/All") + } + if _, err := x.Equal(must(Zeros(3))); !errors.Is(err, ErrBroadcast) { + t.Errorf("compare broadcast err = %v", err) + } +} + +func TestTypedArithmeticEdges(t *testing.T) { + b := must(FromSlice([]bool{true, false}, 2)) + if _, err := b.Sub(b); !errors.Is(err, ErrDType) { + t.Errorf("bool - bool err = %v", err) + } + i := must(FromSlice([]int8{1, 2}, 2)) + if _, err := i.Add(must(ZerosOf(Int8, 3))); !errors.Is(err, ErrBroadcast) { + t.Errorf("broadcast err = %v", err) + } + // Into: converted into out's dtype, with the shape and contiguity checks. + out := must(ZerosOf(Int16, 2)) + if err := i.AddInto(out, i); err != nil || !reflect.DeepEqual(data[int16](t, out), []int16{2, 4}) { + t.Errorf("AddInto = %v, %v", out, err) + } + for _, f := range []func(out, b *Array) error{i.SubInto, i.MulInto, i.DivInto} { + if err := f(out, i); err != nil { + t.Error(err) + } + } + if err := i.AddInto(out, must(ZerosOf(Int8, 3))); !errors.Is(err, ErrBroadcast) { + t.Errorf("AddInto broadcast err = %v", err) + } + if err := i.AddInto(must(ZerosOf(Int16, 3)), i); !errors.Is(err, ErrBroadcast) { + t.Errorf("AddInto shape err = %v", err) + } + strided := must(must(ZerosOf(Int16, 4)).Slice(Step(2))) + if err := i.AddInto(strided, i); !errors.Is(err, ErrBroadcast) { + t.Errorf("AddInto strided err = %v", err) + } + if err := i.SqrtInto(must(Zeros(2))); !errors.Is(err, ErrDType) { + t.Errorf("SqrtInto int err = %v", err) + } + // Float64 result from an int: the float path runs. + f := must(FromData([]float64{0.5, 0.25}, 2)) + if got := must(i.Mul(f)).materialize(); !reflect.DeepEqual(got, []float64{0.5, 0.5}) { + t.Errorf("int8 * float64 = %v", got) + } +} + +func TestTypedUnary(t *testing.T) { + i := must(FromSlice([]int32{1, 4, -9}, 3)) + for name, f := range map[string]func() *Array{ + "log2": i.Log2, "log10": i.Log10, "cos": i.Cos, "tan": i.Tan, + } { + if r := f(); r.DType() != Float64 || r.Size() != 3 { + t.Errorf("%s = %v", name, r) + } + } + for name, f := range map[string]func() *Array{"ceil": i.Ceil, "round": i.Round} { + if got := data[int32](t, f()); !reflect.DeepEqual(got, []int32{1, 4, -9}) { + t.Errorf("%s = %v", name, got) + } + } + h := must(FromSlice([]float32{1.5, -2.5}, 2)) + if got := data[float32](t, h.Ceil()); !reflect.DeepEqual(got, []float32{2, -2}) { + t.Errorf("float32 ceil = %v", got) + } + if got := data[float32](t, h.Round()); !reflect.DeepEqual(got, []float32{2, -3}) { + t.Errorf("float32 round = %v", got) + } + if got := data[float32](t, h.Power(2)); !reflect.DeepEqual(got, []float32{2.25, 6.25}) { + t.Errorf("float32 power = %v", got) + } + if r := i.Power(0.5); r.DType() != Float64 || r.At(1) != 2 { + t.Errorf("int32 power = %v", r) + } + c := must(FromSlice([]complex128{3 + 4i}, 1)) + if got := c.Round(); got.AtComplex(0) != 3+4i { + t.Errorf("complex round = %v", got) + } + mustPanic(t, "not defined for complex128", func() { c.Ceil() }) + if r := i.Map(func(x float64) float64 { return x + 0.5 }); r.DType() != Float64 || r.At(0) != 1.5 { + t.Errorf("int Map = %v", r) + } + mustPanic(t, "Map of a complex128", func() { c.Map(math.Abs) }) + mustPanic(t, "negating a bool", func() { must(OnesOf(Bool, 1)).Neg() }) + if got := data[uint8](t, must(FromSlice([]uint8{200}, 1)).Neg()); got[0] != 56 { + t.Errorf("-uint8(200) = %v", got) + } +} + +func TestTypedReductions(t *testing.T) { + big := must(FromSlice([]int64{1 << 60, 1, 1}, 3)) + if s := big.SumAll(); Item[int64](s) != 1<<60+2 || s.Ndim() != 0 { + t.Errorf("SumAll int64 = %v", s) + } + if p := must(FromSlice([]uint8{200, 2}, 2)).ProdAll(); Item[uint64](p) != 400 { + t.Errorf("ProdAll uint8 = %v", p) + } + f := must(FromData([]float64{1, 2, 3}, 3)) + if f.SumAll().At() != 6 || f.ProdAll().At() != 6 { + t.Error("SumAll/ProdAll float64") + } + i := must(FromSlice([]int16{3, -1, 4}, 3)) + if i.Sum() != 6 || i.Prod() != -12 { + t.Errorf("Sum/Prod = %g %g", i.Sum(), i.Prod()) + } + if m, err := i.Mean(); err != nil || m != 2 { + t.Errorf("Mean = %g %v", m, err) + } + if m, err := i.Max(); err != nil || m != 4 { + t.Errorf("Max = %g %v", m, err) + } + if m, err := i.Min(); err != nil || m != -1 { + t.Errorf("Min = %g %v", m, err) + } + if k, err := i.ArgMax(); err != nil || k != 2 { + t.Errorf("ArgMax = %d %v", k, err) + } + if k, err := i.ArgMin(); err != nil || k != 1 { + t.Errorf("ArgMin = %d %v", k, err) + } + if got := data[int64](t, i.CumSumFlat()); !reflect.DeepEqual(got, []int64{3, 2, 6}) { + t.Errorf("CumSumFlat = %v", got) + } + if got := data[int64](t, i.CumProdFlat()); !reflect.DeepEqual(got, []int64{3, -3, -12}) { + t.Errorf("CumProdFlat = %v", got) + } + c := must(FromSlice([]complex64{1 + 1i, 2}, 2)) + if s := c.SumAll(); Item[complex64](s) != 3+1i { + t.Errorf("complex SumAll = %v", s) + } + for name, f := range map[string]func(){ + "Sum": func() { c.Sum() }, + "Prod": func() { c.Prod() }, + "Mean": func() { _, _ = c.Mean() }, + "Max": func() { _, _ = c.Max() }, + "Min": func() { _, _ = c.Min() }, + } { + mustPanic(t, name+" of a complex64", f) + } + + // Axis errors and empty axes. + if _, err := i.SumAxis(1, false); !errors.Is(err, ErrAxis) { + t.Errorf("SumAxis(1) err = %v", err) + } + if _, err := i.ArgMaxAxis(1, false); !errors.Is(err, ErrAxis) { + t.Errorf("ArgMaxAxis(1) err = %v", err) + } + if _, err := i.CumSum(1); !errors.Is(err, ErrAxis) { + t.Errorf("CumSum(1) err = %v", err) + } + e := must(ZerosOf(Int8, 2, 0)) + if s := must(e.SumAxis(1, false)); !reflect.DeepEqual(data[int64](t, s), []int64{0, 0}) { + t.Errorf("empty SumAxis = %v", s) + } + if p := must(e.ProdAxis(1, true)); !reflect.DeepEqual(data[int64](t, p), []int64{1, 1}) { + t.Errorf("empty ProdAxis = %v", p) + } + if _, err := e.MaxAxis(1, false); !errors.Is(err, ErrShapeMismatch) { + t.Errorf("empty MaxAxis err = %v", err) + } + if _, err := e.ArgMinAxis(1, false); !errors.Is(err, ErrShapeMismatch) { + t.Errorf("empty ArgMinAxis err = %v", err) + } + if s := must(e.CumSum(1)); s.Size() != 0 || s.DType() != Int64 { + t.Errorf("empty CumSum = %v", s) + } + if s := must(must(ZerosOf(Int8, 0, 3)).MinAxis(1, false)); s.Size() != 0 { + t.Errorf("MinAxis of (0,3) = %v", s) + } + if s := must(must(ZerosOf(Int8, 0, 3)).ArgMaxAxis(1, false)); s.Size() != 0 { + t.Errorf("ArgMaxAxis of (0,3) = %v", s) + } +} + +func TestTypedClipWhere(t *testing.T) { + i := must(FromSlice([]int8{-5, 0, 5}, 3)) + if r := must(i.Clip(-1, 1)); r.DType() != Float64 || !reflect.DeepEqual(r.materialize(), []float64{-1, 0, 1}) { + t.Errorf("int8 Clip = %v", r) + } + h := must(FromSlice([]float32{-5, float32(math.NaN()), 5}, 3)) + r := must(h.Clip(-1, 1)) + if got := data[float32](t, r); got[0] != -1 || got[2] != 1 || got[1] == got[1] { + t.Errorf("float32 Clip = %v", got) + } + if _, err := must(OnesOf(Complex64, 1)).Clip(0, 1); !errors.Is(err, ErrDType) { + t.Errorf("complex Clip err = %v", err) + } + w := must(Where(must(FromSlice([]bool{true, false, true}, 3)), i, Scalar(9))) + if got := data[int8](t, w); !reflect.DeepEqual(got, []int8{-5, 9, 5}) { + t.Errorf("Where = %v", got) + } +} + +func TestComplexParts(t *testing.T) { + c := must(FromSlice([]complex64{3 + 4i, -1 - 1i}, 2)) + if got := data[float32](t, c.Real()); !reflect.DeepEqual(got, []float32{3, -1}) { + t.Errorf("Real = %v", got) + } + if got := data[float32](t, c.Imag()); !reflect.DeepEqual(got, []float32{4, -1}) { + t.Errorf("Imag = %v", got) + } + if got := data[complex64](t, c.Conj()); !reflect.DeepEqual(got, []complex64{3 - 4i, -1 + 1i}) { + t.Errorf("Conj = %v", got) + } + if got := data[float32](t, c.Abs()); !reflect.DeepEqual(got, []float32{5, float32(math.Sqrt2)}) { + t.Errorf("Abs = %v", got) + } + an := data[float32](t, c.Angle()) + if math.Abs(float64(an[1])+3*math.Pi/4) > 1e-6 { + t.Errorf("Angle = %v", an) + } + z := must(FromSlice([]complex128{1i}, 1)) + if got := data[float64](t, z.Angle()); got[0] != math.Pi/2 { + t.Errorf("Angle complex128 = %v", got) + } + if got := data[float64](t, z.Abs()); got[0] != 1 { + t.Errorf("Abs complex128 = %v", got) + } + if got := data[complex128](t, z.Conj()); got[0] != cmplx.Conj(1i) { + t.Errorf("Conj complex128 = %v", got) + } + r := must(FromSlice([]int8{-2}, 1)) + if got := data[int8](t, r.Real()); got[0] != -2 { + t.Errorf("Real of int8 = %v", got) + } + if got := data[int8](t, r.Imag()); got[0] != 0 { + t.Errorf("Imag of int8 = %v", got) + } + if got := data[int8](t, r.Conj()); got[0] != -2 { + t.Errorf("Conj of int8 = %v", got) + } + if got := data[float64](t, r.Angle()); got[0] != math.Pi { + t.Errorf("Angle of int8 = %v", got) + } +} + +func TestTypedLinalg(t *testing.T) { + a := must(FromSlice([]int32{1, 2, 3, 4}, 2, 2)) + v := must(FromSlice([]int32{1, -1}, 2)) + cases := []struct { + x, y *Array + shape []int + want []int32 + }{ + {v, v, []int{}, []int32{2}}, + {a, v, []int{2}, []int32{-1, -1}}, + {v, a, []int{2}, []int32{-2, -2}}, + {a, a, []int{2, 2}, []int32{7, 10, 15, 22}}, + } + for _, c := range cases { + r, err := c.x.Dot(c.y) + if err != nil || !sameShape(r.Shape(), c.shape) || !reflect.DeepEqual(data[int32](t, r), c.want) { + t.Errorf("Dot %v·%v = %v, %v", c.x.Shape(), c.y.Shape(), r, err) + } + } + if _, err := v.Dot(must(ZerosOf(Int32, 3))); !errors.Is(err, ErrLinalg) { + t.Errorf("Dot mismatch err = %v", err) + } + f := must(FromData([]float64{0.5, 0.5}, 2, 1)) + if r := must(a.MatMul(f)); r.DType() != Float64 || !reflect.DeepEqual(r.materialize(), []float64{1.5, 3.5}) { + t.Errorf("int32 @ float64 = %v", r) + } + b := must(FromSlice([]bool{true, false, false, true}, 2, 2)) + if r := must(b.MatMul(b)); !reflect.DeepEqual(data[bool](t, r), []bool{true, false, false, true}) { + t.Errorf("bool matmul = %v", r) + } + o := v.Outer(must(FromSlice([]int8{2, 3}, 2))) + if o.DType() != Int32 || !reflect.DeepEqual(data[int32](t, o), []int32{2, 3, -2, -3}) { + t.Errorf("Outer = %v", o) + } +} + +// TestGeneratedDispatchRejectsForeignValues covers the guards at the end of +// the generated type switches: they are only reached by a programming error. +func TestGeneratedDispatchRejectsForeignValues(t *testing.T) { + bad := []string{"x"} + for name, f := range map[string]func(){ + "dtypeOfStore": func() { dtypeOfStore(bad) }, + "makeStore": func() { makeStore(DType(99), 1) }, + "storeLen": func() { storeLen(bad) }, + "cloneStore": func() { cloneStore(bad) }, + "takeStore": func() { takeStore(bad, nil) }, + "gatherStore": func() { gatherStore(&Array{ext: bad, dtype: Int8}) }, + "broadcastStore": func() { broadcastStore(&Array{ext: bad, dtype: Int8}, nil) }, + "elemComplex": func() { elemComplex(bad, 0) }, + "elemString": func() { elemString(bad, 0) }, + } { + func() { + defer func() { + if recover() == nil { + t.Errorf("%s accepted a []string", name) + } + }() + f() + }() + } +} + +// TestEveryDTypeThroughTheGenericPaths runs the shape-only operations, +// element access and formatting on an array of every dtype, so each case of +// the generated type switches is taken. +func TestEveryDTypeThroughTheGenericPaths(t *testing.T) { + for _, dt := range allDTypes() { + a := must(ZerosOf(dt, 2, 3)) + a.Set(1, 0, 1) + a.SetComplex(1, 1, 2) + if !dt.IsComplex() && a.At(0, 1) != 1 { + t.Errorf("%v: Set/At", dt) + } + if a.AtComplex(1, 2) != 1 { + t.Errorf("%v: SetComplex/AtComplex = %v", dt, a.AtComplex(1, 2)) + } + tr := a.Transpose() + if got := tr.Copy(); got.DType() != dt || got.AtComplex(2, 1) != 1 { + t.Errorf("%v: transposed copy = %v", dt, got) + } + if got := must(Concatenate([]*Array{a, a}, 1)); got.DType() != dt || got.AtComplex(1, 5) != 1 { + t.Errorf("%v: concatenate = %v", dt, got) + } + if got := must(a.Take(1, 5)); got.AtComplex(1) != 1 { + t.Errorf("%v: take = %v", dt, got) + } + if !strings.Contains(a.String(), "shape=[2 3]") { + t.Errorf("%v: String = %s", dt, a) + } + empty := must(ZerosOf(dt, 0, 3)).Transpose().Copy() + if empty.Size() != 0 { + t.Errorf("%v: empty copy = %v", dt, empty) + } + for _, to := range allDTypes() { + src, dst := makeStore(dt, 1), makeStore(to, 1) + setComplex(src, 0, 1) + convertStore(dst, src) + if elemComplex(dst, 0) != 1 { + t.Errorf("convert %v -> %v of 1 = %v", dt, to, elemComplex(dst, 0)) + } + } + } + for _, d := range []any{[]int8{1}, []int16{1}, []int32{1}, []int64{1}, []uint8{1}, []uint16{1}, + []uint32{1}, []uint64{1}, []float32{1}, []float64{1}, []complex64{1}, []complex128{1}, []bool{true}} { + a := fromStore(d, []int{1}) + switch a.dtype { + case Int8: + _ = data[int8](t, a) + case Int16: + _ = data[int16](t, a) + case Int32: + _ = data[int32](t, a) + case Int64: + _ = data[int64](t, a) + case Uint8: + _ = data[uint8](t, a) + case Uint16: + _ = data[uint16](t, a) + case Uint32: + _ = data[uint32](t, a) + case Uint64: + _ = data[uint64](t, a) + case Float32: + _ = data[float32](t, a) + case Float64: + _ = data[float64](t, a) + case Complex64: + _ = data[complex64](t, a) + case Complex128: + _ = data[complex128](t, a) + case Bool: + _ = data[bool](t, a) + } + } +} diff --git a/dtype_test.go b/dtype_test.go new file mode 100644 index 0000000..da1541c --- /dev/null +++ b/dtype_test.go @@ -0,0 +1,208 @@ +package ndarray + +import ( + _ "embed" + "encoding/json" + "errors" + "strings" + "testing" +) + +// promotion.json is generated from NumPy by testdata/promotion.py. It is +// embedded so the emulated CI arches, which run the test binary from another +// directory, can read it. +// +//go:embed testdata/promotion.json +var promotionJSON []byte + +type promotionTable struct { + NumPy string `json:"numpy"` + Pair map[string]string `json:"pair"` + Weak map[string]map[string]string `json:"weak"` + Unary map[string]map[string]string `json:"unary"` + Reduce map[string]map[string]string `json:"reduce"` +} + +func loadPromotion(t *testing.T) promotionTable { + t.Helper() + var p promotionTable + if err := json.Unmarshal(promotionJSON, &p); err != nil { + t.Fatal(err) + } + if len(p.Pair) != numDTypes*numDTypes { + t.Fatalf("fixture has %d pairs, want %d", len(p.Pair), numDTypes*numDTypes) + } + return p +} + +func allDTypes() []DType { + out := make([]DType, numDTypes) + for i := range out { + out[i] = DType(i) + } + return out +} + +// noFloat16 maps NumPy's float16 results to float32, the documented +// difference. +func noFloat16(s string) string { + if s == "float16" { + return "float32" + } + return s +} + +func TestDTypeNames(t *testing.T) { + for _, d := range allDTypes() { + got, err := ParseDType(d.String()) + if err != nil || got != d { + t.Errorf("ParseDType(%q) = %v, %v", d.String(), got, err) + } + } + if _, err := ParseDType("float16"); !errors.Is(err, ErrDType) { + t.Errorf("ParseDType(float16) err = %v", err) + } + if s := DType(200).String(); s != "DType(200)" { + t.Errorf("String = %q", s) + } +} + +func TestDTypeKinds(t *testing.T) { + sizes := map[DType]int{Bool: 1, Int8: 1, Uint8: 1, Int16: 2, Uint16: 2, Int32: 4, Uint32: 4, + Float32: 4, Int64: 8, Uint64: 8, Float64: 8, Complex64: 8, Complex128: 16} + for d, want := range sizes { + if d.Size() != want { + t.Errorf("%v.Size() = %d, want %d", d, d.Size(), want) + } + } + if !Bool.IsBool() || Int8.IsBool() || !Int8.IsSigned() || Uint8.IsSigned() || + !Uint64.IsInteger() || Float32.IsInteger() || !Float32.IsFloat() || !Complex64.IsComplex() { + t.Error("kind predicates disagree") + } +} + +// TestResultTypeMatchesNumPy checks ResultType, and the dtype the arithmetic +// actually produces, against every cell of numpy.result_type. +func TestResultTypeMatchesNumPy(t *testing.T) { + p := loadPromotion(t) + for _, a := range allDTypes() { + for _, b := range allDTypes() { + want := p.Pair[a.String()+","+b.String()] + if got := ResultType(a, b).String(); got != want { + t.Errorf("ResultType(%v, %v) = %v, NumPy %s", a, b, got, want) + } + x, _ := OnesOf(a, 2) + y, _ := OnesOf(b, 2) + op := x.Add + if a == Bool && b == Bool { + op = x.Mul // bool - bool is an error; bool * bool is bool + } + r, err := op(y) + if err != nil || r.DType().String() != want { + t.Errorf("%v + %v gives %v (%v), NumPy %s", a, b, r.DType(), err, want) + } + } + } +} + +// TestWeakScalarsMatchNumPy checks the NEP 50 results of an array with a Go +// int, float and complex scalar, and of true division. +func TestWeakScalarsMatchNumPy(t *testing.T) { + p := loadPromotion(t) + for _, d := range allDTypes() { + w := p.Weak[d.String()] + x, _ := OnesOf(d, 2) + for kind, s := range map[string]*Array{"int": Scalar(1), "float": Scalar(1.5), "complex": Scalar(1i)} { + r, err := x.Add(s) + if err != nil || r.DType().String() != w[kind] { + t.Errorf("%v + %s scalar = %v (%v), NumPy %s", d, kind, r.DType(), err, w[kind]) + } + r, err = s.Add(x) + if err != nil || r.DType().String() != w[kind] { + t.Errorf("%s scalar + %v = %v (%v), NumPy %s", kind, d, r.DType(), err, w[kind]) + } + } + r, err := x.Div(x) + if err != nil || r.DType().String() != w["truediv"] { + t.Errorf("%v / %v = %v (%v), NumPy %s", d, d, r.DType(), err, w["truediv"]) + } + } + if r, _ := Scalar(1).Add(Scalar(2.5)); r.DType() != Float64 { + t.Errorf("int scalar + float scalar = %v", r.DType()) + } + if r, _ := Scalar(int8(1)).Add(Scalar(uint(1))); r.DType() != Int64 { + t.Errorf("int scalar + uint scalar = %v", r.DType()) + } + if r, _ := Scalar(true).Add(Scalar(float32(1))); r.DType() != Float64 { + t.Errorf("bool + float scalar = %v", r.DType()) + } + if r, _ := Scalar(complex64(1)).Add(Scalar(1)); r.DType() != Complex128 { + t.Errorf("complex scalar + int scalar = %v", r.DType()) + } +} + +func TestScalarPanicsOnOtherTypes(t *testing.T) { + defer func() { + if recover() == nil { + t.Error("Scalar(string) did not panic") + } + }() + Scalar("1") +} + +// TestUnaryDTypesMatchNumPy checks the dtype of every unary ufunc result +// against NumPy, with float16 read as float32 and NumPy's TypeErrors as +// panics. +func TestUnaryDTypesMatchNumPy(t *testing.T) { + p := loadPromotion(t) + ops := map[string]func(*Array) *Array{ + "sqrt": (*Array).Sqrt, "exp": (*Array).Exp, "sin": (*Array).Sin, "abs": (*Array).Abs, + "negative": (*Array).Neg, "floor": (*Array).Floor, "square": (*Array).Square, + } + for _, d := range allDTypes() { + for name, op := range ops { + want := noFloat16(p.Unary[d.String()][name]) + x, _ := OnesOf(d, 3) + got, panicked := func() (s string, panicked bool) { + defer func() { + if recover() != nil { + panicked = true + } + }() + return op(x).DType().String(), false + }() + if strings.HasPrefix(want, "ERR") { + if !panicked { + t.Errorf("%s(%v) = %s, NumPy raises", name, d, got) + } + continue + } + if panicked || got != want { + t.Errorf("%s(%v) = %s (panic %v), NumPy %s", name, d, got, panicked, want) + } + } + } +} + +// TestReductionDTypesMatchNumPy checks the dtype of the axis reductions. +func TestReductionDTypesMatchNumPy(t *testing.T) { + p := loadPromotion(t) + ops := map[string]func(*Array) (*Array, error){ + "sum": func(a *Array) (*Array, error) { return a.SumAxis(0, false) }, + "prod": func(a *Array) (*Array, error) { return a.ProdAxis(0, false) }, + "mean": func(a *Array) (*Array, error) { return a.MeanAxis(0, false) }, + "max": func(a *Array) (*Array, error) { return a.MaxAxis(0, false) }, + "cumsum": func(a *Array) (*Array, error) { return a.CumSum(0) }, + "argmax": func(a *Array) (*Array, error) { return a.ArgMaxAxis(0, false) }, + } + for _, d := range allDTypes() { + for name, op := range ops { + want := p.Reduce[d.String()][name] + x, _ := OnesOf(d, 3) + r, err := op(x) + if err != nil || r.DType().String() != want { + t.Errorf("%s(%v) = %v (%v), NumPy %s", name, d, r.DType(), err, want) + } + } + } +} diff --git a/gen_dtypes.go b/gen_dtypes.go new file mode 100644 index 0000000..35728b6 --- /dev/null +++ b/gen_dtypes.go @@ -0,0 +1,356 @@ +//go:build ignore + +// gen_dtypes writes zz_dtypes_gen.go: the type switches that turn a dtype or +// an untyped storage slice into a call on the concrete element type. Run it +// with `go generate`. +package main + +import ( + "bytes" + "fmt" + "go/format" + "os" + "strings" +) + +type typ struct { + Go, DType string + kind byte // b(ool) i(nt) u(int) f(loat) c(omplex) + bits int +} + +var types = []typ{ + {"bool", "Bool", 'b', 8}, + {"int8", "Int8", 'i', 8}, {"int16", "Int16", 'i', 16}, {"int32", "Int32", 'i', 32}, {"int64", "Int64", 'i', 64}, + {"uint8", "Uint8", 'u', 8}, {"uint16", "Uint16", 'u', 16}, {"uint32", "Uint32", 'u', 32}, {"uint64", "Uint64", 'u', 64}, + {"float32", "Float32", 'f', 32}, {"float64", "Float64", 'f', 64}, + {"complex64", "Complex64", 'c', 64}, {"complex128", "Complex128", 'c', 128}, +} + +func (t typ) isInt() bool { return t.kind == 'i' || t.kind == 'u' } + +// fbits is the precision of a float or complex component, for formatting. +func (t typ) fbits() int { + if t.kind == 'c' { + return t.bits / 2 + } + return t.bits +} + +// narrow lists the types sums and products never run in: they accumulate in +// int64, uint64 or the input's own inexact type. +const narrow = "int8,int16,int32,uint8,uint16,uint32,float64" + +var b bytes.Buffer + +func p(format string, args ...any) { fmt.Fprintf(&b, format+"\n", args...) } + +// conv returns the Go expression converting the value expression x of type +// s to type d. +func conv(s, d typ, x string) string { + switch { + case s == d: + return x + case d.kind == 'b': + return x + " != 0" + case s.kind == 'b': + return "boolTo[" + d.Go + "](" + x + ")" + case s.kind == 'c' && d.kind == 'c': + return d.Go + "(" + x + ")" + case s.kind == 'c': + return conv(typ{"float64", "Float64", 'f', 64}, d, "float64(real("+x+"))") + case d.kind == 'c': + return d.Go + "(complex(float64(" + x + "), 0))" + case s.kind == 'f' && d.isInt(): + return "satInt[" + d.Go + "](float64(" + x + "))" + } + return d.Go + "(" + x + ")" +} + +// each emits one case per type whose kind letter is in set, substituting $T +// for the Go type and $D for the dtype constant. A set may end in +// "-name,name" to leave those Go types out: the Float64 cases of the +// operations whose Float64 path never reaches the generic code, and the +// narrow integers of the accumulations, which always run in 64 bits. +func each(set string, body string) { + set, skip, _ := strings.Cut(set, "-") + for _, t := range types { + if strings.IndexByte(set, t.kind) < 0 || strings.Contains(","+skip+",", ","+t.Go+",") { + continue + } + p(strings.ReplaceAll(strings.ReplaceAll(body, "$T", t.Go), "$D", t.DType)) + } +} + +func main() { + p("// Code generated by gen_dtypes.go; DO NOT EDIT.") + p("") + p("package ndarray") + p("") + p(`import "strconv"`) + p("") + + p("// dtypeOfStore returns the dtype of a storage slice.") + p("func dtypeOfStore(s any) DType {") + p("switch s.(type) {") + each("biucf-float64", "case []$T: return $D") // fromStore handles []float64 itself + p("}") + p(`panic("ndarray: not a storage slice")`) + p("}") + p("") + + p("// makeStore returns a zeroed storage slice of n elements of dtype dt.") + p("func makeStore(dt DType, n int) any {") + p("switch dt {") + each("biucf", "case $D: return make([]$T, n)") + p("}") + p(`panic("ndarray: unknown dtype " + dt.String())`) + p("}") + p("") + + p("// storeLen returns the length of a storage slice.") + p("func storeLen(s any) int {") + p("switch s := s.(type) {") + each("biucf", "case []$T: return len(s)") + p("}") + p(`panic("ndarray: not a storage slice")`) + p("}") + p("") + + p("// cloneStore returns a copy of a storage slice.") + p("func cloneStore(s any) any {") + p("switch s := s.(type) {") + each("biucf", "case []$T: return append([]$T(nil), s...)") + p("}") + p(`panic("ndarray: not a storage slice")`) + p("}") + p("") + + p("// fillStore sets every element of dst to the single element of one, a") + p("// storage slice of the same dtype.") + p("func fillStore(dst, one any) {") + p("switch d := dst.(type) {") + each("biucf", "case []$T: v := one.([]$T)[0]; for i := range d { d[i] = v }") + p("}") + p("}") + p("") + + p("// copyStore copies n elements from src[so:] to dst[do:], two storage slices") + p("// of the same dtype.") + p("func copyStore(dst any, do int, src any, so, n int) {") + p("switch d := dst.(type) {") + each("biucf", "case []$T: copy(d[do:do+n], src.([]$T)[so:so+n])") + p("}") + p("}") + p("") + + p("// takeStore returns the elements of a storage slice at the given positions.") + p("func takeStore(s any, idx []int) any {") + p("switch s := s.(type) {") + each("biucf", "case []$T: out := make([]$T, len(idx)); for k, i := range idx { out[k] = s[i] }; return out") + p("}") + p(`panic("ndarray: not a storage slice")`) + p("}") + p("") + + p("// gatherStore returns a contiguous copy of the elements of the view a.") + p("func gatherStore(a *Array) any {") + p("switch s := a.store().(type) {") + each("biucf-float64", "case []$T: return gather(s, a.shape, a.strides, a.offset)") // Float64 uses materialize + p("}") + p(`panic("ndarray: not a storage slice")`) + p("}") + p("") + + p("// broadcastStore returns a contiguous copy of a's elements broadcast to shape.") + p("func broadcastStore(a *Array, shape []int) any {") + p("es := a.broadcastStrides(shape)") + p("switch s := a.store().(type) {") + each("biucf", "case []$T: return gather(s, shape, es, a.offset)") + p("}") + p(`panic("ndarray: not a storage slice")`) + p("}") + p("") + + p("// convertStore converts every element of src into dst, two storage slices") + p("// of the same length, as AsType documents.") + p("func convertStore(dst, src any) {") + p("switch s := src.(type) {") + for _, st := range types { + p("case []%s:", st.Go) + p("switch d := dst.(type) {") + for _, dt := range types { + if st == dt { + p("case []%s: copy(d, s)", dt.Go) + continue + } + p("case []%s: for i, v := range s { d[i] = %s }", dt.Go, conv(st, dt, "v")) + } + p("}") + } + p("}") + p("}") + p("") + + c128 := typ{"complex128", "Complex128", 'c', 128} + f64 := typ{"float64", "Float64", 'f', 64} + p("// elemComplex returns element i of a storage slice as a complex128.") + p("func elemComplex(s any, i int) complex128 {") + p("switch s := s.(type) {") + for _, t := range types { + p("case []%s: return %s", t.Go, conv(t, c128, "s[i]")) + } + p("}") + p(`panic("ndarray: not a storage slice")`) + p("}") + p("") + + p("// setComplex stores v, converted, as element i of a storage slice.") + p("func setComplex(s any, i int, v complex128) {") + p("switch s := s.(type) {") + for _, t := range types { + p("case []%s: s[i] = %s", t.Go, conv(c128, t, "v")) + } + p("}") + p("}") + p("") + + p("// elemFloat returns element i of a storage slice of a real dtype as a float64.") + p("func elemFloat(s any, i int) float64 {") + p("switch s := s.(type) {") + for _, t := range types { + if t.kind != 'c' && t != f64 { // At reads Float64 storage itself + p("case []%s: return %s", t.Go, conv(t, f64, "s[i]")) + } + } + p("}") + p(`panic("ndarray: complex element read as float64; use AtComplex")`) + p("}") + p("") + + p("// setFloat stores v, converted, as element i of a storage slice.") + p("func setFloat(s any, i int, v float64) {") + p("switch s := s.(type) {") + for _, t := range types { + if t != f64 { // Set writes Float64 storage itself + p("case []%s: s[i] = %s", t.Go, conv(f64, t, "v")) + } + } + p("}") + p("}") + p("") + + p("// elemString formats element i of a storage slice as NumPy prints it.") + p("func elemString(s any, i int) string {") + p("switch s := s.(type) {") + for _, t := range types { + switch t.kind { + case 'b': + p(`case []bool: if s[i] { return "true" }; return "false"`) + case 'i': + p("case []%s: return strconv.FormatInt(int64(s[i]), 10)", t.Go) + case 'u': + p("case []%s: return strconv.FormatUint(uint64(s[i]), 10)", t.Go) + case 'f': + p("case []%s: return floatString(float64(s[i]), %d)", t.Go, t.fbits()) + case 'c': + p("case []%s: return complexString(complex128(s[i]), %d)", t.Go, t.fbits()) + } + } + p("}") + p(`panic("ndarray: not a storage slice")`) + p("}") + p("") + + p("// arithStore applies a binary operation to storage slices of one dtype.") + p("func arithStore(op binCode, d, x, y any) {") + p("switch d := d.(type) {") + each("iu", "case []$T: binInt(op, d, x.([]$T), y.([]$T))") + each("f-float64", "case []$T: binFloat(op, d, x.([]$T), y.([]$T))") + each("c", "case []$T: binComplex(op, d, x.([]$T), y.([]$T))") + each("b", "case []$T: binBool(op, d, x.([]$T), y.([]$T))") + p("}") + p("}") + p("") + + p("// cmpStore compares storage slices of one dtype into d.") + p("func cmpStore(op cmpCode, d []bool, x, y any) {") + p("switch x := x.(type) {") + each("iuf", "case []$T: cmpOrdered(op, d, x, y.([]$T))") + each("c", "case []$T: cmpComplex(op, d, x, y.([]$T))") + each("b", "case []$T: cmpBool(op, d, x, y.([]$T))") + p("}") + p("}") + p("") + + p("// accumulateStore sums or multiplies src along the middle axis of") + p("// [outer][axisLen][inner] into dst; both have an accumulation dtype.") + p("func accumulateStore(op reduceCode, dst, src any, outer, axisLen, inner int) {") + p("switch d := dst.(type) {") + each("iufc-"+narrow, "case []$T: reduceNum(op, d, src.([]$T), outer, axisLen, inner)") + p("}") + p("}") + p("") + + p("// extremeStore takes the maximum or minimum of src along the middle axis.") + p("func extremeStore(op reduceCode, dst, src any, outer, axisLen, inner int) {") + p("switch d := dst.(type) {") + each("iuf-float64", "case []$T: reduceOrdered(op, d, src.([]$T), outer, axisLen, inner)") + each("c", "case []$T: reduceComplex(op, d, src.([]$T), outer, axisLen, inner)") + each("b", "case []$T: reduceBool(op, d, src.([]$T), outer, axisLen, inner)") + p("}") + p("}") + p("") + + p("// argStore writes the arg-max (or arg-min) along the middle axis into dst.") + p("func argStore(larger bool, dst []int64, src any, outer, axisLen, inner int) {") + p("switch s := src.(type) {") + each("iuf", "case []$T: argOrdered(larger, dst, s, outer, axisLen, inner)") + each("c", "case []$T: argComplex(larger, dst, s, outer, axisLen, inner)") + each("b", "case []$T: argBool(larger, dst, s, outer, axisLen, inner)") + p("}") + p("}") + p("") + + p("// scanStore is the cumulative sum or product along the middle axis.") + p("func scanStore(op reduceCode, dst, src any, outer, axisLen, inner int) {") + p("switch d := dst.(type) {") + each("iufc-"+narrow, "case []$T: scanNum(op, d, src.([]$T), outer, axisLen, inner)") + p("}") + p("}") + p("") + + p("// whereStore picks from t where c is true and from f elsewhere.") + p("func whereStore(d any, c []bool, t, f any) {") + p("switch d := d.(type) {") + each("biucf", "case []$T: whereT(d, c, t.([]$T), f.([]$T))") + p("}") + p("}") + p("") + + p("// intUnaryStore negates, takes the absolute value of, or squares integers.") + p("func intUnaryStore(code unaryCode, d, x any) {") + p("switch d := d.(type) {") + each("iu", "case []$T: s := x.([]$T); switch code { case uNeg: negInt(d, s); case uAbs: absInt(d, s); default: squareInt(d, s) }") + p("}") + p("}") + p("") + + p("// matmulStore multiplies (m x k) by (k x n) storage slices of one dtype into d.") + p("func matmulStore(d, x, y any, m, k, n int) {") + p("switch d := d.(type) {") + each("iufc-float64", "case []$T: matmulNum(d, x.([]$T), y.([]$T), m, k, n)") + each("b", "case []$T: matmulBool(d, x.([]$T), y.([]$T), m, k, n)") + p("}") + p("}") + p("") + + src, err := format.Source(b.Bytes()) + if err != nil { + os.Stdout.Write(b.Bytes()) + panic(err) + } + if err := os.WriteFile("zz_dtypes_gen.go", src, 0o644); err != nil { + panic(err) + } +} diff --git a/indexing.go b/indexing.go index 52aa880..2e29df6 100644 --- a/indexing.go +++ b/indexing.go @@ -4,11 +4,11 @@ import "fmt" // Boolean / fancy indexing. // -// A dedicated bool dtype is a later phase; until then a boolean mask is a 0/1 -// float array (exactly what the comparison ufuncs Greater/Equal/… return), and -// "truthy" means != 0. These operations bring NumPy boolean indexing to that -// representation: MaskSelect is a[mask], Nonzero is np.flatnonzero(mask), and -// Take is integer fancy indexing into the flat array. +// A mask is normally a Bool array (what the comparison ufuncs Greater/Equal/… +// return), but as in NumPy any dtype is accepted and "truthy" means non-zero, +// so the 0/1 float masks of earlier versions keep working. MaskSelect is +// a[mask], Nonzero is np.flatnonzero(mask), and Take is integer fancy indexing +// into the flat array. // MaskSelect returns a 1-D array of the elements of a where mask is truthy // (non-zero), in row-major order: NumPy's a[mask] for a mask of a's shape, the @@ -29,6 +29,16 @@ func (a *Array) MaskSelect(mask *Array) (*Array, error) { return nil, fmt.Errorf("%w: mask shape %v does not match array shape %v", ErrShapeMismatch, mask.shape, a.shape) } + if a.dtype != Float64 || mask.dtype != Float64 { + mb := mask.operandStore(Bool, shape).([]bool) + var idx []int + for i, m := range mb { + if m { + idx = append(idx, i) + } + } + return a.result(takeStore(a.operandStore(a.dtype, shape), idx), []int{len(idx)}), nil + } av := a.operandFor(shape) mv := mask.operandFor(shape) out := make([]float64, 0) @@ -40,24 +50,40 @@ func (a *Array) MaskSelect(mask *Array) (*Array, error) { return &Array{data: out, shape: []int{len(out)}, strides: []int{1}, ws: a.ws}, nil } -// Nonzero returns a 1-D array of the flat (row-major) indices at which a is -// non-zero — NumPy's np.flatnonzero(a). For a 0/1 mask this is the positions of -// the truthy elements. +// Nonzero returns a 1-D Int64 array of the flat (row-major) indices at which +// a is non-zero — NumPy's np.flatnonzero(a). For a mask this is the positions +// of the true elements. func (a *Array) Nonzero() *Array { - av := a.contiguousData() - out := make([]float64, 0) - for i, v := range av { - if v != 0 { - out = append(out, float64(i)) + b := a.operandStore(Bool, a.shape).([]bool) + out := make([]int64, 0) + for i, v := range b { + if v { + out = append(out, int64(i)) } } - return &Array{data: out, shape: []int{len(out)}, strides: []int{1}, ws: a.ws} + return a.result(out, []int{len(out)}) } // Take returns a 1-D array gathering a's flattened (row-major) elements at the // given integer indices — NumPy's a.take(indices) / fancy indexing a[idx]. // Negative indices count from the end; an out-of-range index errors. func (a *Array) Take(indices ...int) (*Array, error) { + if a.dtype != Float64 { + n := a.Size() + idx := make([]int, len(indices)) + for k, i := range indices { + j := i + if j < 0 { + j += n + } + if j < 0 || j >= n { + return nil, fmt.Errorf("%w: take index %d out of range for size %d", + ErrShapeMismatch, i, n) + } + idx[k] = j + } + return a.result(takeStore(a.contiguousStore(), idx), []int{len(idx)}), nil + } flat := a.contiguousData() n := len(flat) out := a.alloc(len(indices), false) diff --git a/indexing_test.go b/indexing_test.go index e3743a8..f32a7c2 100644 --- a/indexing_test.go +++ b/indexing_test.go @@ -38,11 +38,14 @@ func TestMaskSelect(t *testing.T) { func TestNonzero(t *testing.T) { m, _ := FromData([]float64{0, 1, 0, 1, 0, 1}, 2, 3) nz := m.Nonzero() - // np.flatnonzero -> [1, 3, 5]. - wantData(t, nz, []int{3}, []float64{1, 3, 5}) + // np.flatnonzero -> [1, 3, 5], dtype int64. + if nz.DType() != Int64 { + t.Fatalf("dtype = %v, want int64", nz.DType()) + } + wantData(t, nz.AsType(Float64), []int{3}, []float64{1, 3, 5}) all0, _ := Zeros(3) - wantData(t, all0.Nonzero(), []int{0}, []float64{}) + wantData(t, all0.Nonzero().AsType(Float64), []int{0}, []float64{}) } func TestTake(t *testing.T) { diff --git a/internal/kernels/kernels.go b/internal/kernels/kernels.go index 91e3c1e..d84134d 100644 --- a/internal/kernels/kernels.go +++ b/internal/kernels/kernels.go @@ -62,56 +62,6 @@ func sqrtScalar(dst, src []float64) { } } -// b2f maps a boolean comparison result to a 0/1 float mask value. -func b2f(b bool) float64 { - if b { - return 1 - } - return 0 -} - -// Equal writes the a[i]==b[i] mask (1/0) into dst[i]. -func Equal(dst, a, b []float64) { - for i := range dst { - dst[i] = b2f(a[i] == b[i]) - } -} - -// NotEqual writes the a[i]!=b[i] mask (1/0) into dst[i]. -func NotEqual(dst, a, b []float64) { - for i := range dst { - dst[i] = b2f(a[i] != b[i]) - } -} - -// Greater writes the a[i]>b[i] mask (1/0) into dst[i]. -func Greater(dst, a, b []float64) { - for i := range dst { - dst[i] = b2f(a[i] > b[i]) - } -} - -// GreaterEqual writes the a[i]>=b[i] mask (1/0) into dst[i]. -func GreaterEqual(dst, a, b []float64) { - for i := range dst { - dst[i] = b2f(a[i] >= b[i]) - } -} - -// Less writes the a[i] best || math.IsNaN(v) { - best, bi = v, k - } - } - dst[base+i] = float64(bi) - } - } -} -// ArgMinAxis writes into dst the index (along the middle axis) of the first -// minimum for each [outer][inner] position, or of the first NaN. -func ArgMinAxis(dst []float64, src []float64, outer, axisLen, inner, lo, hi int) { - for o := 0; o < outer; o++ { - base := o * inner - block := o * axisLen * inner - for i := lo; i < hi; i++ { - best := src[block+i] - bi := 0 - for k := 1; k < axisLen && !math.IsNaN(best); k++ { - if v := src[block+k*inner+i]; v < best || math.IsNaN(v) { - best, bi = v, k - } - } - dst[base+i] = float64(bi) - } - } -} // CumSumAxis writes the cumulative sum along the middle axis into dst (same // shape as src), matching numpy.cumsum along an axis. Layout is diff --git a/internal/kernels/kernels_test.go b/internal/kernels/kernels_test.go index aa69519..ef7993e 100644 --- a/internal/kernels/kernels_test.go +++ b/internal/kernels/kernels_test.go @@ -130,13 +130,8 @@ func TestArgAndScan(t *testing.T) { t.Errorf("ArgMin desc = %d, want 2", got) } - // Axis variants over [outer=1][axisLen=3][inner=2]: columns {3,1},{9,5} etc. + // Scans over [outer=1][axisLen=3][inner=2]. src := []float64{3, 1, 9, 5, 0, 7} - dst := make([]float64, 2) - ArgMaxAxis(dst, src, 1, 3, 2, 0, 2) - eqSlice(t, dst, []float64{1, 2}) // col0 max at k=1 (9); col1 max at k=2 (7) - ArgMinAxis(dst, src, 1, 3, 2, 0, 2) - eqSlice(t, dst, []float64{2, 0}) // col0 min at k=2 (0); col1 min at k=0 (1) cs := make([]float64, 6) CumSumAxis(cs, src, 1, 3, 2) diff --git a/linalg.go b/linalg.go index 10d5e67..d9ebe60 100644 --- a/linalg.go +++ b/linalg.go @@ -44,9 +44,30 @@ func (a *Array) MatMul(b *Array) (*Array, error) { if err := validateShape([]int{a.shape[0], b.shape[1]}); err != nil { return nil, err } + if a.dtype != Float64 || b.dtype != Float64 { + return matmulTyped(a, b), nil + } return matmul2D(a, b, a.shape[0], a.shape[1], b.shape[1]), nil } +// matmulTyped is MatMul for operands of other dtypes, in their common dtype. +// It is a plain triple loop; only Float64 has the blocked SIMD GEMM so far. +func matmulTyped(a, b *Array) *Array { + dt := ResultType(a.dtype, b.dtype) + if dt == Float64 { + return a.toFloat64().matmulFloat64(b.toFloat64()) + } + m, k, n := a.shape[0], a.shape[1], b.shape[1] + d := makeStore(dt, m*n) + matmulStore(d, a.operandStore(dt, a.shape), b.operandStore(dt, b.shape), m, k, n) + return a.result(d, []int{m, n}) +} + +// matmulFloat64 is matmul2D on two validated 2-D Float64 arrays. +func (a *Array) matmulFloat64(b *Array) *Array { + return matmul2D(a, b, a.shape[0], a.shape[1], b.shape[1]) +} + // Dot returns the dot product following numpy.dot for 1-D and 2-D operands: // // - 1-D · 1-D -> a 0-d (scalar) array, the inner product. @@ -57,6 +78,9 @@ func (a *Array) MatMul(b *Array) (*Array, error) { // Higher-rank operands are rejected (the general tensordot is a later phase). func (a *Array) Dot(b *Array) (*Array, error) { na, nb := len(a.shape), len(b.shape) + if (a.dtype != Float64 || b.dtype != Float64) && na >= 1 && na <= 2 && nb >= 1 && nb <= 2 { + return a.dotTyped(b) + } switch { case na == 1 && nb == 1: if a.shape[0] != b.shape[0] { @@ -127,6 +151,12 @@ func (a *Array) Inner(b *Array) (*Array, error) { // operands are flattened to 1-D vectors u (length m) and v (length n), and the // result is the (m x n) array out[i,j] = u[i]*v[j]. func (a *Array) Outer(b *Array) *Array { + if a.dtype != Float64 || b.dtype != Float64 { + u, _ := a.Ravel().Reshape(-1, 1) // a column and a row: these reshapes + v, _ := b.Ravel().Reshape(1, -1) // and their product cannot fail + r, _ := u.Mul(v) + return r + } u := a.contiguousData() v := b.contiguousData() m, n := len(u), len(v) @@ -141,3 +171,27 @@ func (a *Array) Outer(b *Array) *Array { shape := []int{m, n} return &Array{data: dst, shape: shape, strides: rowMajorStrides(shape), ws: a.ws} } + +// dotTyped is Dot for 1-D and 2-D operands of other dtypes: vectors are made +// matrices, multiplied, and the added axes dropped again. +func (a *Array) dotTyped(b *Array) (*Array, error) { + x, y := a, b + if len(a.shape) == 1 { + x = a.view([]int{1, a.shape[0]}, []int{0, a.strides[0]}, a.offset) + } + if len(b.shape) == 1 { + y = b.view([]int{b.shape[0], 1}, []int{b.strides[0], 0}, b.offset) + } + r, err := x.MatMul(y) + if err != nil { + return nil, fmt.Errorf("%w: Dot %v x %v inner dims differ", ErrLinalg, a.shape, b.shape) + } + var shape []int + if len(a.shape) == 2 { + shape = append(shape, a.shape[0]) + } + if len(b.shape) == 2 { + shape = append(shape, b.shape[1]) + } + return r.Reshape(shape...) +} diff --git a/manipulation.go b/manipulation.go index cfd7639..6529e49 100644 --- a/manipulation.go +++ b/manipulation.go @@ -7,6 +7,9 @@ import "fmt" // flatten semantics; here Ravel is also a copy, but Flatten documents the // always-copy contract). func (a *Array) Flatten() *Array { + if a.dtype != Float64 { + return a.Ravel() + } data := a.materialize() return &Array{data: data, shape: []int{len(data)}, strides: []int{1}, ws: a.ws} } @@ -31,7 +34,7 @@ func (a *Array) ExpandDims(axis int) (*Array, error) { // The inserted axis has length 1; its stride is irrelevant, use 0. strides = append(strides, 0) strides = append(strides, a.strides[axis:]...) - return &Array{data: a.data, shape: shape, strides: strides, offset: a.offset, ws: a.ws}, nil + return a.view(shape, strides, a.offset), nil } // Squeeze returns a view with length-1 axes removed. With no axes given, every @@ -70,7 +73,7 @@ func (a *Array) Squeeze(axes ...int) (*Array, error) { strides = append(strides, a.strides[i]) } } - return &Array{data: a.data, shape: shape, strides: strides, offset: a.offset, ws: a.ws}, nil + return a.view(shape, strides, a.offset), nil } // Concatenate joins the given arrays along an existing axis, matching @@ -116,9 +119,51 @@ func Concatenate(arrays []*Array, axis int) (*Array, error) { if err := validateShape(out); err != nil { return nil, err } + dt := arrays[0].dtype + for _, arr := range arrays[1:] { + dt = ResultType(dt, arr.dtype) + } + if !allFloat64(arrays) { + return concatStore(arrays, dt, ax, out), nil + } return concatInto(arrays, ax, out), nil } +// allFloat64 reports whether every array has dtype Float64. +func allFloat64(arrays []*Array) bool { + for _, a := range arrays { + if a.dtype != Float64 { + return false + } + } + return true +} + +// concatStore is concatInto for any dtype: the arrays are converted to the +// common dtype dt and joined along ax into a result of shape out. It runs on +// the heap; concatInto keeps the float64 workspace path. +func concatStore(arrays []*Array, dt DType, ax int, out []int) *Array { + inner := prod(out[ax+1:]) + outer := prod(out[:ax]) + axisTotal := out[ax] + data := makeStore(dt, outer*axisTotal*inner) + r := fromStore(data, out) + r.ws = wsOf(arrays...) + if storeLen(data) == 0 { + return r + } + colBase := 0 + for _, arr := range arrays { + src := arr.operandStore(dt, arr.shape) + aLen := arr.shape[ax] + for o := 0; o < outer; o++ { + copyStore(data, (o*axisTotal+colBase)*inner, src, o*aLen*inner, aLen*inner) + } + colBase += aLen + } + return r +} + // concatInto materialises arrays joined along ax into a contiguous result of // the given shape. The result is laid out as [outer][axisTotal][inner]; each // source contributes its axis slab into the matching outer rows. @@ -201,13 +246,7 @@ func sameShape(a, b []int) bool { // reshape that could fail). func atLeast2D(a *Array) *Array { if len(a.shape) == 1 { - return &Array{ - data: a.data, - shape: []int{1, a.shape[0]}, - strides: []int{a.shape[0] * a.strides[0], a.strides[0]}, - offset: a.offset, - ws: a.ws, - } + return a.view([]int{1, a.shape[0]}, []int{a.shape[0] * a.strides[0], a.strides[0]}, a.offset) } return a } diff --git a/ndarray.go b/ndarray.go index ea1a020..6023627 100644 --- a/ndarray.go +++ b/ndarray.go @@ -1,6 +1,8 @@ // Package ndarray is a pure-Go (CGO=0) NumPy-style N-dimensional array library. // -// The element type is float64. The numeric kernels live in internal/kernels +// An array holds one of thirteen element types (DType), chosen at run time as +// in NumPy and combined by NumPy's promotion rules; Float64 is the default +// and the most optimised. The numeric kernels live in internal/kernels // behind a contiguous-slice API: go-asmgen SIMD kernels on amd64, arm64, // ppc64le, loong64, riscv64 and s390x, pure Go on the 32-bit targets. See docs/plan-ndarray.md for the // roadmap. @@ -26,9 +28,13 @@ import ( "github.com/go-ndarray/ndarray/internal/kernels" ) -// Array is an N-dimensional, row-major array of float64. +// Array is an N-dimensional, row-major array. Its element type is a runtime +// property, DType (Float64 unless made otherwise). type Array struct { - data []float64 + data []float64 // storage when dtype is Float64: the hot path, untouched by dtypes + ext any // storage for every other dtype: a []T, see storage.go + dtype DType + weak scalarKind // set only on the 0-d operands made from Go scalars shape []int strides []int offset int @@ -232,12 +238,22 @@ func (a *Array) flatIndex(idx []int) int { } // At returns the element at the given multi-dimensional index. +// For a dtype other than Float64 the element is converted to float64; on a +// complex array At panics (the imaginary part would be lost): use AtComplex. func (a *Array) At(idx ...int) float64 { + if a.dtype != Float64 { + return elemFloat(a.ext, a.flatIndex(idx)) + } return a.data[a.flatIndex(idx)] } -// Set stores v at the given multi-dimensional index. +// Set stores v at the given multi-dimensional index, converted to the array's +// dtype as AsType converts. func (a *Array) Set(v float64, idx ...int) { + if a.dtype != Float64 { + setFloat(a.ext, a.flatIndex(idx), v) + return + } a.data[a.flatIndex(idx)] = v } @@ -279,7 +295,14 @@ func (a *Array) forEach(f func(linear, pos int)) { // O(n) per-element gather into O(outer) memmoves of length inner. // - Otherwise (e.g. a transposed view, where the last axis is strided): the // general per-element gather. +// +// For a dtype other than Float64 it returns the elements converted to float64 +// (the real part of complex ones), so a float64 kernel never reads storage of +// another type. func (a *Array) materialize() []float64 { + if a.dtype != Float64 { + return a.AsType(Float64).data + } n := a.Size() out := a.alloc(n, false) // every element is written below if n == 0 { @@ -332,12 +355,18 @@ func (a *Array) isContiguous() bool { return false } } + if a.dtype != Float64 { + return a.Size() == storeLen(a.ext) + } return a.Size() == len(a.data) } // Copy returns a deep, contiguous copy of the array. func (a *Array) Copy() *Array { cp := append([]int(nil), a.shape...) + if a.dtype != Float64 { + return a.result(cloneStore(a.contiguousStore()), cp) + } return &Array{ data: a.materialize(), shape: cp, @@ -395,12 +424,10 @@ func (a *Array) Reshape(shape ...int) (*Array, error) { } cp := append([]int(nil), shape...) if a.isContiguous() { - return &Array{ - data: a.data, - shape: cp, - strides: rowMajorStrides(cp), - ws: a.ws, - }, nil + return a.view(cp, rowMajorStrides(cp), 0), nil + } + if a.dtype != Float64 { + return a.result(gatherStore(a), cp), nil } return &Array{ data: a.materialize(), @@ -413,6 +440,9 @@ func (a *Array) Reshape(shape ...int) (*Array, error) { // Ravel returns a contiguous 1-D array containing the elements in row-major // order. func (a *Array) Ravel() *Array { + if a.dtype != Float64 { + return a.result(cloneStore(a.contiguousStore()), []int{a.Size()}) + } data := a.materialize() return &Array{ data: data, @@ -432,16 +462,11 @@ func (a *Array) Transpose() *Array { shape[i] = a.shape[n-1-i] strides[i] = a.strides[n-1-i] } - return &Array{ - data: a.data, - shape: shape, - strides: strides, - offset: a.offset, - ws: a.ws, - } + return a.view(shape, strides, a.offset) } -// String renders the array's shape and its elements in row-major order. +// String renders the array's shape and its elements in row-major order, and +// its dtype when that is not Float64. func (a *Array) String() string { var b strings.Builder b.WriteString("Array(shape=[") @@ -451,13 +476,17 @@ func (a *Array) String() string { } b.WriteString(strconv.Itoa(d)) } - b.WriteString("], data=[") - flat := a.materialize() - for i, v := range flat { + b.WriteString("], ") + if a.dtype != Float64 { + b.WriteString("dtype=" + a.dtype.String() + ", ") + } + b.WriteString("data=[") + flat := a.contiguousStore() + for i := range a.Size() { if i > 0 { b.WriteByte(' ') } - b.WriteString(strconv.FormatFloat(v, 'g', -1, 64)) + b.WriteString(elemString(flat, i)) } b.WriteString("])") return b.String() @@ -675,16 +704,16 @@ func repeatOperands(a, b *Array, shape []int) (full, rep []float64, repFirst, ok } // Add returns the elementwise sum a+b with broadcasting. -func (a *Array) Add(b *Array) (*Array, error) { return a.binOp(b, kernels.AddP) } +func (a *Array) Add(b *Array) (*Array, error) { return a.arith(b, opAdd, kernels.AddP) } // Sub returns the elementwise difference a-b with broadcasting. -func (a *Array) Sub(b *Array) (*Array, error) { return a.binOp(b, kernels.SubP) } +func (a *Array) Sub(b *Array) (*Array, error) { return a.arith(b, opSub, kernels.SubP) } // Mul returns the elementwise product a*b with broadcasting. -func (a *Array) Mul(b *Array) (*Array, error) { return a.binOp(b, kernels.MulP) } +func (a *Array) Mul(b *Array) (*Array, error) { return a.arith(b, opMul, kernels.MulP) } // Div returns the elementwise quotient a/b with broadcasting. -func (a *Array) Div(b *Array) (*Array, error) { return a.binOp(b, kernels.DivP) } +func (a *Array) Div(b *Array) (*Array, error) { return a.arith(b, opDiv, kernels.DivP) } // binOpInto writes a OP b into the caller-provided contiguous out array, the // no-allocation analogue of binOp and of NumPy's `np.add(a, b, out=z)`. It is @@ -717,20 +746,22 @@ func (a *Array) binOpInto(out, b *Array, kernel func(dst, x, y []float64)) error // AddInto writes a+b into out (no allocation), the parity-path analogue of // np.add(a, b, out=out). out must be contiguous and the broadcast result shape; // it may alias a or b. See binOpInto. -func (a *Array) AddInto(out, b *Array) error { return a.binOpInto(out, b, kernels.AddP) } +func (a *Array) AddInto(out, b *Array) error { return a.arithInto(out, b, opAdd, kernels.AddP) } // SubInto writes a-b into out (no allocation). See AddInto. -func (a *Array) SubInto(out, b *Array) error { return a.binOpInto(out, b, kernels.SubP) } +func (a *Array) SubInto(out, b *Array) error { return a.arithInto(out, b, opSub, kernels.SubP) } // MulInto writes a*b into out (no allocation). See AddInto. -func (a *Array) MulInto(out, b *Array) error { return a.binOpInto(out, b, kernels.MulP) } +func (a *Array) MulInto(out, b *Array) error { return a.arithInto(out, b, opMul, kernels.MulP) } // DivInto writes a/b into out (no allocation). See AddInto. -func (a *Array) DivInto(out, b *Array) error { return a.binOpInto(out, b, kernels.DivP) } +func (a *Array) DivInto(out, b *Array) error { return a.arithInto(out, b, opDiv, kernels.DivP) } -// scalarArray wraps a scalar as a 0-d array so it broadcasts against anything. +// scalarArray wraps a scalar as a weak 0-d array so it broadcasts against +// anything and takes the other operand's dtype when that is inexact (see +// Scalar). func scalarArray(v float64) *Array { - return &Array{data: []float64{v}, shape: []int{}, strides: []int{}} + return &Array{data: []float64{v}, shape: []int{}, strides: []int{}, weak: weakFloat} } // AddScalar returns the array with v added to every element. @@ -754,7 +785,14 @@ func (a *Array) DivScalar(v float64) *Array { r, _ := a.Div(scalarArray(v)); ret // not panic: on a large array a panic may happen on a worker goroutine, which // ends the program as any unrecovered goroutine panic does. A panic on the // calling goroutine propagates once the workers have stopped writing. +// +// On an array of another dtype, f is applied to the elements converted to +// float64 and the result is Float64; on a complex array Map panics. func (a *Array) Map(f func(float64) float64) *Array { + if a.dtype != Float64 { + a.realOnly("Map", "Real, Imag or Abs first") + return a.AsType(Float64).Map(f) + } src := a.contiguousData() dst := a.alloc(len(src), false) kernels.MapP(dst, src, f) @@ -773,10 +811,25 @@ func (a *Array) contiguousData() []float64 { } // Neg returns the elementwise negation. -func (a *Array) Neg() *Array { return a.Map(func(x float64) float64 { return -x }) } +// +// Integer arrays wrap, as in NumPy; negating a Bool array panics (use +// LogicalNot). +func (a *Array) Neg() *Array { + if a.dtype != Float64 { + return a.intUnary(uNeg, (*Array).Neg) + } + return a.Map(func(x float64) float64 { return -x }) +} // Abs returns the elementwise absolute value. -func (a *Array) Abs() *Array { return a.Map(kernels.Abs) } +// For a complex array it is the modulus, as a float array of the same +// precision; other dtypes are kept. +func (a *Array) Abs() *Array { + if a.dtype != Float64 { + return a.intUnary(uAbs, (*Array).Abs) + } + return a.Map(kernels.Abs) +} // --- reductions ------------------------------------------------------------ @@ -784,14 +837,36 @@ func (a *Array) Abs() *Array { return a.Map(kernels.Abs) } // computed as a tree of per-core partials, so the result can differ from a // strictly left-to-right sum by a few ULP (floating-point addition is not // associative) — the same trade-off NumPy's pairwise summation makes. -func (a *Array) Sum() float64 { return kernels.SumP(a.contiguousData()) } +// +// For another dtype the sum is accumulated as NumPy accumulates it (exactly, +// in int64 or uint64, for integers) and converted to float64; on a complex +// array Sum panics: SumAll keeps the dtype. +func (a *Array) Sum() float64 { + if a.dtype != Float64 { + a.realOnly("Sum", "SumAll") + return elemFloat(a.SumAll().ext, 0) + } + return kernels.SumP(a.contiguousData()) +} // Prod returns the product of all elements (1 for an empty array). -func (a *Array) Prod() float64 { return kernels.Prod(a.contiguousData()) } +// See Sum for other dtypes; ProdAll keeps the dtype. +func (a *Array) Prod() float64 { + if a.dtype != Float64 { + a.realOnly("Prod", "ProdAll") + return elemFloat(a.ProdAll().ext, 0) + } + return kernels.Prod(a.contiguousData()) +} // Mean returns the arithmetic mean of all elements. It returns an error for an // empty array. +// On a complex array it panics; use MeanAxis. func (a *Array) Mean() (float64, error) { + if a.dtype != Float64 { + a.realOnly("Mean", "MeanAxis") + return a.AsType(Float64).Mean() + } n := a.Size() if n == 0 { return 0, fmt.Errorf("%w: mean of empty array", ErrShapeMismatch) @@ -800,7 +875,12 @@ func (a *Array) Mean() (float64, error) { } // Max returns the maximum element. It returns an error for an empty array. +// On a complex array it panics; use MaxAxis. func (a *Array) Max() (float64, error) { + if a.dtype != Float64 { + a.realOnly("Max", "MaxAxis") + return a.AsType(Float64).Max() + } if a.Size() == 0 { return 0, fmt.Errorf("%w: max of empty array", ErrShapeMismatch) } @@ -808,7 +888,12 @@ func (a *Array) Max() (float64, error) { } // Min returns the minimum element. It returns an error for an empty array. +// On a complex array it panics; use MinAxis. func (a *Array) Min() (float64, error) { + if a.dtype != Float64 { + a.realOnly("Min", "MinAxis") + return a.AsType(Float64).Min() + } if a.Size() == 0 { return 0, fmt.Errorf("%w: min of empty array", ErrShapeMismatch) } @@ -894,6 +979,9 @@ func (a *Array) reduceAxis( // is kept with length 1 (e.g. (2,3) summed over axis 0 -> (1,3)); otherwise it // is removed (-> (3,)). A negative axis counts from the end. func (a *Array) SumAxis(axis int, keepdims bool) (*Array, error) { + if a.dtype != Float64 { + return a.reduceTyped(axis, keepdims, rSum) + } return a.reduceAxisOr(axis, keepdims, kernels.SumAxis, 0) } @@ -924,24 +1012,39 @@ func (a *Array) reduceAxisOr( // ProdAxis returns the product along the given axis. See SumAxis for the // axis/keepdims semantics. func (a *Array) ProdAxis(axis int, keepdims bool) (*Array, error) { + if a.dtype != Float64 { + return a.reduceTyped(axis, keepdims, rProd) + } return a.reduceAxisOr(axis, keepdims, kernels.ProdAxis, 1) } // MaxAxis returns the maximum along the given axis. See SumAxis for the // axis/keepdims semantics. func (a *Array) MaxAxis(axis int, keepdims bool) (*Array, error) { + if a.dtype != Float64 { + return a.reduceTyped(axis, keepdims, rMax) + } return a.reduceAxis(axis, keepdims, kernels.MaxAxis) } // MinAxis returns the minimum along the given axis. See SumAxis for the // axis/keepdims semantics. func (a *Array) MinAxis(axis int, keepdims bool) (*Array, error) { + if a.dtype != Float64 { + return a.reduceTyped(axis, keepdims, rMin) + } return a.reduceAxis(axis, keepdims, kernels.MinAxis) } // MeanAxis returns the arithmetic mean along the given axis. See SumAxis for the // axis/keepdims semantics. +// +// The mean of an integer or Bool array is Float64; Float32 and complex arrays +// keep their dtype. func (a *Array) MeanAxis(axis int, keepdims bool) (*Array, error) { + if a.dtype == Bool || a.dtype.IsInteger() { + return a.AsType(Float64).MeanAxis(axis, keepdims) + } ax, err := a.normalizeAxis(axis) if err != nil { return nil, err @@ -954,6 +1057,9 @@ func (a *Array) MeanAxis(axis int, keepdims bool) (*Array, error) { } r, _ := a.SumAxis(ax, keepdims) // ax is valid and non-empty: cannot fail n := float64(a.shape[ax]) + if a.dtype != Float64 { + return r.arith(Scalar(n), opDiv, kernels.DivP) + } for i := range r.data { r.data[i] /= n } diff --git a/reductions.go b/reductions.go index 1400a69..f65d6c9 100644 --- a/reductions.go +++ b/reductions.go @@ -13,6 +13,10 @@ func (a *Array) ArgMax() (int, error) { if a.Size() == 0 { return 0, fmt.Errorf("%w: argmax of empty array", ErrShapeMismatch) } + if a.dtype != Float64 { + r, _ := a.Ravel().argTyped(0, false, true) // non-empty 1-D: cannot fail + return int(r.ext.([]int64)[0]), nil + } return kernels.ArgMax(a.contiguousData()), nil } @@ -23,21 +27,25 @@ func (a *Array) ArgMin() (int, error) { if a.Size() == 0 { return 0, fmt.Errorf("%w: argmin of empty array", ErrShapeMismatch) } + if a.dtype != Float64 { + r, _ := a.Ravel().argTyped(0, false, false) // non-empty 1-D: cannot fail + return int(r.ext.([]int64)[0]), nil + } return kernels.ArgMin(a.contiguousData()), nil } // ArgMaxAxis returns the indices of the first maxima along the given axis, -// matching numpy.argmax(axis=...). The result has the reduced shape (the axis -// removed, or kept as length 1 with keepdims) and integer index values stored -// as float64. A negative axis counts from the end. +// matching numpy.argmax(axis=...). The result is an Int64 array of the +// reduced shape (the axis removed, or kept as length 1 with keepdims). A +// negative axis counts from the end. func (a *Array) ArgMaxAxis(axis int, keepdims bool) (*Array, error) { - return a.reduceAxis(axis, keepdims, kernels.ArgMaxAxis) + return a.argTyped(axis, keepdims, true) } // ArgMinAxis returns the indices of the first minima along the given axis, // matching numpy.argmin(axis=...). See ArgMaxAxis for the shape semantics. func (a *Array) ArgMinAxis(axis int, keepdims bool) (*Array, error) { - return a.reduceAxis(axis, keepdims, kernels.ArgMinAxis) + return a.argTyped(axis, keepdims, false) } // scanAxis is the shared driver for the cumulative scans (CumSum/CumProd along @@ -69,18 +77,28 @@ func (a *Array) scanAxis( // numpy.cumsum(axis=...). The result has the same shape as the input. A // negative axis counts from the end. func (a *Array) CumSum(axis int) (*Array, error) { + if a.dtype != Float64 { + return a.scanTyped(axis, rSum) + } return a.scanAxis(axis, kernels.CumSumAxis) } // CumProd returns the cumulative product along the given axis, matching // numpy.cumprod(axis=...). The result has the same shape as the input. func (a *Array) CumProd(axis int) (*Array, error) { + if a.dtype != Float64 { + return a.scanTyped(axis, rProd) + } return a.scanAxis(axis, kernels.CumProdAxis) } // CumSumFlat returns the cumulative sum over the flattened array (1-D, row-major // order), matching numpy.cumsum with no axis. func (a *Array) CumSumFlat() *Array { + if a.dtype != Float64 { + r, _ := a.Ravel().scanTyped(0, rSum) // 1-D over axis 0: cannot fail + return r + } src := a.materialize() dst := a.alloc(len(src), false) kernels.CumSumAxis(dst, src, 1, len(src), 1) @@ -90,6 +108,10 @@ func (a *Array) CumSumFlat() *Array { // CumProdFlat returns the cumulative product over the flattened array (1-D, // row-major order), matching numpy.cumprod with no axis. func (a *Array) CumProdFlat() *Array { + if a.dtype != Float64 { + r, _ := a.Ravel().scanTyped(0, rProd) // 1-D over axis 0: cannot fail + return r + } src := a.materialize() dst := a.alloc(len(src), false) kernels.CumProdAxis(dst, src, 1, len(src), 1) @@ -102,6 +124,9 @@ func (a *Array) Clip(lo, hi float64) (*Array, error) { if lo > hi { return nil, fmt.Errorf("%w: clip bounds lo=%g > hi=%g", ErrShapeMismatch, lo, hi) } + if a.dtype != Float64 { + return a.clipTyped(lo, hi) + } src := a.contiguousData() dst := a.alloc(len(src), false) kernels.Clip(dst, src, lo, hi) @@ -111,8 +136,9 @@ func (a *Array) Clip(lo, hi float64) (*Array, error) { // Where returns an array selecting from t where cond is truthy (non-zero) and // from f otherwise, elementwise with full NumPy broadcasting across all three -// operands — matching numpy.where(cond, t, f). cond is typically a 0/1 mask -// from the comparison ufuncs. +// operands — matching numpy.where(cond, t, f). cond is typically a Bool mask +// from the comparison ufuncs; any dtype is read by truthiness. The result has +// the common dtype of t and f. func Where(cond, t, f *Array) (*Array, error) { shape, err := broadcastShape(cond.shape, t.shape) if err != nil { @@ -122,6 +148,9 @@ func Where(cond, t, f *Array) (*Array, error) { if err != nil { return nil, err } + if cond.dtype != Float64 || t.dtype != Float64 || f.dtype != Float64 { + return whereTyped(cond, t, f, shape), nil + } c := cond.operandFor(shape) tv := t.operandFor(shape) fv := f.operandFor(shape) diff --git a/slicing.go b/slicing.go index f650d79..b42deae 100644 --- a/slicing.go +++ b/slicing.go @@ -179,5 +179,5 @@ func (a *Array) Slice(idx ...Index) (*Array, error) { shape = append(shape, count) strides = append(strides, step*a.strides[axis]) } - return &Array{data: a.data, shape: shape, strides: strides, offset: offset, ws: a.ws}, nil + return a.view(shape, strides, offset), nil } diff --git a/storage.go b/storage.go new file mode 100644 index 0000000..3871a36 --- /dev/null +++ b/storage.go @@ -0,0 +1,329 @@ +package ndarray + +import ( + "fmt" + "math" + "strconv" +) + +//go:generate go run gen_dtypes.go + +// Element type sets. They list exact types, not ~T: storage is always one of +// these slices, and the type switches that dispatch on it name them exactly. +type ( + intT interface{ int8 | int16 | int32 | int64 | uint8 | uint16 | uint32 | uint64 } + floatT interface{ float32 | float64 } + complexT interface{ complex64 | complex128 } + realT interface{ intT | floatT } + numT interface{ realT | complexT } + elemT interface{ numT | bool } +) + +// dtypeOf returns the DType whose storage is []T. +func dtypeOf[T elemT]() DType { + var z T + switch any(z).(type) { + case bool: + return Bool + case int8: + return Int8 + case int16: + return Int16 + case int32: + return Int32 + case int64: + return Int64 + case uint8: + return Uint8 + case uint16: + return Uint16 + case uint32: + return Uint32 + case uint64: + return Uint64 + case float32: + return Float32 + case complex64: + return Complex64 + case complex128: + return Complex128 + } + return Float64 +} + +// store returns a's backing storage as an untyped slice: a.data for Float64, +// a.ext for every other dtype. +func (a *Array) store() any { + if a.dtype == Float64 { + return a.data + } + return a.ext +} + +// typed returns a's backing storage as a []T. T must be a's element type. +func typed[T elemT](a *Array) []T { return a.store().([]T) } + +// fromStore wraps a contiguous storage slice (one of the element slices) in a +// row-major array of the given shape, which it takes ownership of. +func fromStore(s any, shape []int) *Array { + a := &Array{shape: shape, strides: rowMajorStrides(shape)} + if d, ok := s.([]float64); ok { + a.data = d + return a + } + a.ext = s + a.dtype = dtypeOfStore(s) + return a +} + +// view returns an array sharing a's storage, dtype and workspace with the +// given layout. +func (a *Array) view(shape, strides []int, offset int) *Array { + return &Array{data: a.data, ext: a.ext, dtype: a.dtype, + shape: shape, strides: strides, offset: offset, ws: a.ws} +} + +// result wraps a fresh contiguous storage slice computed from a: it is +// shaped like shape and bound to a's workspace. +func (a *Array) result(s any, shape []int) *Array { + r := fromStore(s, shape) + r.ws = a.ws + return r +} + +// DType returns the array's element type. +func (a *Array) DType() DType { return a.dtype } + +// ZerosOf returns a zero-filled array of the given dtype and shape. +func ZerosOf(dt DType, shape ...int) (*Array, error) { + if dt == Float64 { + return New(shape...) + } + if int(dt) >= numDTypes { + return nil, fmt.Errorf("%w: %v", ErrDType, dt) + } + if err := validateShape(shape); err != nil { + return nil, err + } + cp := append([]int(nil), shape...) + return fromStore(makeStore(dt, prod(cp)), cp), nil +} + +// OnesOf returns an array of the given dtype and shape filled with one. +func OnesOf(dt DType, shape ...int) (*Array, error) { return FullOf(dt, 1, shape...) } + +// FullOf returns an array of the given dtype and shape filled with v, +// converted to the dtype as AsType converts. +func FullOf(dt DType, v complex128, shape ...int) (*Array, error) { + a, err := ZerosOf(dt, shape...) + if err != nil { + return nil, err + } + src := []complex128{v} + one := makeStore(dt, 1) + convertStore(one, src) + fillStore(a.store(), one) + return a, nil +} + +// FromSlice wraps a copy of data in an array of the given shape. The dtype is +// the one whose element type is T. It is the typed counterpart of FromData. +func FromSlice[T elemT](data []T, shape ...int) (*Array, error) { + if err := validateShape(shape); err != nil { + return nil, err + } + if prod(shape) != len(data) { + return nil, fmt.Errorf("%w: shape %v implies %d elements, got %d", + ErrShapeMismatch, shape, prod(shape), len(data)) + } + cp := append([]int(nil), shape...) + return fromStore(append([]T(nil), data...), cp), nil +} + +// Data returns the array's elements as a contiguous row-major []T. T must be +// the element type of the array's dtype (int8 for Int8, complex128 for +// Complex128 ...); otherwise ok is false. The slice is the array's own storage +// when the array is contiguous and a copy otherwise, so write through it only +// to an array you made. +func Data[T elemT](a *Array) (data []T, ok bool) { + if a.dtype != dtypeOf[T]() { + return nil, false + } + return a.contiguousStore().([]T), true +} + +// Item returns the element at the given index as a T, which must be the +// element type of the array's dtype. It panics like At on a bad index, and on +// a T of another dtype. +func Item[T elemT](a *Array, idx ...int) T { + if a.dtype != dtypeOf[T]() { + panic(fmt.Sprintf("ndarray: Item[%v] of a %v array", dtypeOf[T](), a.dtype)) + } + return typed[T](a)[a.flatIndex(idx)] +} + +// AtComplex returns the element at the given index as a complex128, for an +// array of any dtype. +func (a *Array) AtComplex(idx ...int) complex128 { + return elemComplex(a.store(), a.flatIndex(idx)) +} + +// SetComplex stores v at the given index, converted to the array's dtype as +// AsType converts (a real dtype keeps the real part). +func (a *Array) SetComplex(v complex128, idx ...int) { + setComplex(a.store(), a.flatIndex(idx), v) +} + +// AsType returns a contiguous copy of the array converted to dtype dt, like +// NumPy's astype. Conversions follow Go's, with these definitions where Go +// leaves the result to the platform or NumPy to the hardware: +// +// - float to integer truncates toward zero and saturates: values beyond the +// type's range, and infinities, give its minimum or maximum, and NaN +// gives 0. NumPy leaves these casts undefined and they differ by +// platform (on arm64 it saturates to 64 bits and then wraps, so +Inf to +// int8 is -1; on x86 NaN gives the minimum); here they are the same +// everywhere; +// - complex to real keeps the real part (NumPy warns, Go cannot); +// - anything to Bool is "not zero", so NaN is true. +func (a *Array) AsType(dt DType) *Array { + src := a.contiguousStore() + if dt == a.dtype { + return a.result(cloneStore(src), append([]int(nil), a.shape...)) + } + dst := makeStore(dt, a.Size()) + convertStore(dst, src) + return a.result(dst, append([]int(nil), a.shape...)) +} + +// contiguousStore returns a's elements as a contiguous row-major storage +// slice, sharing the backing storage when a is contiguous. +func (a *Array) contiguousStore() any { + if a.dtype == Float64 { + return a.contiguousData() + } + if a.isContiguous() { + return a.ext + } + return gatherStore(a) +} + +// gather returns a fresh contiguous row-major copy of the elements of the +// strided view (data, shape, strides, offset). +func gather[T any](data []T, shape, strides []int, offset int) []T { + n := prod(shape) + out := make([]T, n) + if n == 0 { + return out + } + idx := make([]int, len(shape)) + for linear := range out { + pos := offset + for axis, i := range idx { + pos += i * strides[axis] + } + out[linear] = data[pos] + for axis := len(idx) - 1; axis >= 0; axis-- { + idx[axis]++ + if idx[axis] < shape[axis] { + break + } + idx[axis] = 0 + } + } + return out +} + +// broadcastStrides returns a's strides aligned to the trailing dimensions of +// shape, with 0 for every axis a lacks or has length 1 on, so that indexing +// with them repeats a across shape. +func (a *Array) broadcastStrides(shape []int) []int { + n := len(shape) + es := make([]int, n) + for i := range es { + if j := len(a.shape) - n + i; j >= 0 && a.shape[j] != 1 { + es[i] = a.strides[j] + } + } + return es +} + +// operandStore returns a's elements, converted to dtype dt, as a contiguous +// storage slice of the broadcast shape. It shares a's storage when nothing +// needs to change. +func (a *Array) operandStore(dt DType, shape []int) any { + var s any + if a.isContiguous() && sameShape(a.shape, shape) { + s = a.store() + } else { + s = broadcastStore(a, shape) + } + if a.dtype == dt { + return s + } + out := makeStore(dt, prod(shape)) + convertStore(out, s) + return out +} + +// Conversions. The generated convertStore calls these for the cases Go does +// not define or NumPy defines otherwise. + +// satInt converts f to the integer type I, truncating toward zero and +// saturating at I's range; NaN gives 0. +func satInt[I intT](f float64) I { + if f != f { + return 0 + } + var lo, hi I + hi = ^hi + if hi < 0 { // signed: ^0 is -1 + bits := uint(8 * sizeOf[I]()) + hi = I(uint64(1)<<(bits-1) - 1) + lo = -hi - 1 + } + if f <= float64(lo) { + return lo + } + // float64(hi) can round up to 2^bits, one past hi: compare with >=. + if f >= float64(hi) { + return hi + } + return I(f) +} + +// sizeOf returns the size of I in bytes. +func sizeOf[I intT]() int { + var z I + switch any(z).(type) { + case int8, uint8: + return 1 + case int16, uint16: + return 2 + case int32, uint32: + return 4 + } + return 8 +} + +// boolTo converts a bool to 1 or 0 of a numeric type. +func boolTo[T numT](b bool) T { + if b { + return 1 + } + return 0 +} + +// complexString formats a complex number as NumPy does: (1+2j). +func complexString(c complex128, bits int) string { + re, im := real(c), imag(c) + sign := "+" + if im < 0 || (im == 0 && math.Signbit(im)) { + sign, im = "-", -im + } + return "(" + floatString(re, bits) + sign + floatString(im, bits) + "j)" +} + +// floatString formats a float as the package always has: the shortest +// representation that reads back to the same value at the given precision. +func floatString(f float64, bits int) string { return strconv.FormatFloat(f, 'g', -1, bits) } diff --git a/testdata/values.json b/testdata/values.json new file mode 100644 index 0000000..473d871 --- /dev/null +++ b/testdata/values.json @@ -0,0 +1,9590 @@ +{ +"numpy": "2.5.3", +"machine": "arm64", +"cases": { +"bool": { +"x": [ +"true", +"false", +"true", +"false", +"true", +"true" +], +"y": [ +"true", +"true", +"false", +"false", +"false", +"true" +], +"add": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"true", +"true", +"true", +"false", +"true", +"true" +] +}, +"sub": { +"error": "TypeError" +}, +"mul": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"true", +"false", +"false", +"false", +"false", +"true" +] +}, +"div": { +"dtype": "float64", +"shape": [ +6 +], +"data": [ +"1.0", +"0.0", +"inf", +"nan", +"inf", +"1.0" +] +}, +"maximum": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"true", +"true", +"true", +"false", +"true", +"true" +] +}, +"minimum": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"true", +"false", +"false", +"false", +"false", +"true" +] +}, +"eq": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"true", +"false", +"false", +"true", +"false", +"true" +] +}, +"ne": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"false", +"true", +"true", +"false", +"true", +"false" +] +}, +"lt": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"false", +"true", +"false", +"false", +"false", +"false" +] +}, +"le": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"true", +"true", +"false", +"true", +"false", +"true" +] +}, +"gt": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"false", +"false", +"true", +"false", +"true", +"false" +] +}, +"ge": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"true", +"false", +"true", +"true", +"true", +"true" +] +}, +"sqrt": { +"dtype": "float16", +"shape": [ +6 +], +"data": [ +"1.0", +"0.0", +"1.0", +"0.0", +"1.0", +"1.0" +] +}, +"exp": { +"dtype": "float16", +"shape": [ +6 +], +"data": [ +"2.71875", +"1.0", +"2.71875", +"1.0", +"2.71875", +"2.71875" +] +}, +"log": { +"dtype": "float16", +"shape": [ +6 +], +"data": [ +"0.0", +"-inf", +"0.0", +"-inf", +"0.0", +"0.0" +] +}, +"sin": { +"dtype": "float16", +"shape": [ +6 +], +"data": [ +"0.84130859375", +"0.0", +"0.84130859375", +"0.0", +"0.84130859375", +"0.84130859375" +] +}, +"abs": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"true", +"false", +"true", +"false", +"true", +"true" +] +}, +"neg": { +"error": "TypeError" +}, +"floor": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"true", +"false", +"true", +"false", +"true", +"true" +] +}, +"square": { +"dtype": "int8", +"shape": [ +6 +], +"data": [ +"1", +"0", +"1", +"0", +"1", +"1" +] +}, +"power": { +"dtype": "float64", +"shape": [ +6 +], +"data": [ +"1.0", +"0.0", +"1.0", +"0.0", +"1.0", +"1.0" +] +}, +"sum0": { +"dtype": "int64", +"shape": [ +3 +], +"data": [ +"1", +"1", +"2" +] +}, +"sum1": { +"dtype": "int64", +"shape": [ +2 +], +"data": [ +"2", +"2" +] +}, +"prod1": { +"dtype": "int64", +"shape": [ +2 +], +"data": [ +"0", +"0" +] +}, +"max0": { +"dtype": "bool", +"shape": [ +3 +], +"data": [ +"true", +"true", +"true" +] +}, +"min1": { +"dtype": "bool", +"shape": [ +2 +], +"data": [ +"false", +"false" +] +}, +"argmax1": { +"dtype": "int64", +"shape": [ +2 +], +"data": [ +"0", +"1" +] +}, +"argmin0": { +"dtype": "int64", +"shape": [ +3 +], +"data": [ +"1", +"0", +"0" +] +}, +"cumsum1": { +"dtype": "int64", +"shape": [ +2, +3 +], +"data": [ +"1", +"1", +"2", +"0", +"1", +"2" +] +}, +"cumprod0": { +"dtype": "int64", +"shape": [ +2, +3 +], +"data": [ +"1", +"0", +"1", +"0", +"0", +"1" +] +}, +"mean1": { +"dtype": "float64", +"shape": [ +2 +], +"data": [ +"0.6666666666666666", +"0.6666666666666666" +] +}, +"matmul": { +"dtype": "bool", +"shape": [ +2, +2 +], +"data": [ +"true", +"true", +"true", +"true" +] +}, +"nonzero": { +"dtype": "int64", +"shape": [ +4 +], +"data": [ +"0", +"2", +"4", +"5" +] +}, +"where": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"true", +"true", +"true", +"false", +"true", +"true" +] +}, +"astype_bool": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"true", +"false", +"true", +"false", +"true", +"true" +] +}, +"astype_int8": { +"dtype": "int8", +"shape": [ +6 +], +"data": [ +"1", +"0", +"1", +"0", +"1", +"1" +] +}, +"astype_int16": { +"dtype": "int16", +"shape": [ +6 +], +"data": [ +"1", +"0", +"1", +"0", +"1", +"1" +] +}, +"astype_int32": { +"dtype": "int32", +"shape": [ +6 +], +"data": [ +"1", +"0", +"1", +"0", +"1", +"1" +] +}, +"astype_int64": { +"dtype": "int64", +"shape": [ +6 +], +"data": [ +"1", +"0", +"1", +"0", +"1", +"1" +] +}, +"astype_uint8": { +"dtype": "uint8", +"shape": [ +6 +], +"data": [ +"1", +"0", +"1", +"0", +"1", +"1" +] +}, +"astype_uint16": { +"dtype": "uint16", +"shape": [ +6 +], +"data": [ +"1", +"0", +"1", +"0", +"1", +"1" +] +}, +"astype_uint32": { +"dtype": "uint32", +"shape": [ +6 +], +"data": [ +"1", +"0", +"1", +"0", +"1", +"1" +] +}, +"astype_uint64": { +"dtype": "uint64", +"shape": [ +6 +], +"data": [ +"1", +"0", +"1", +"0", +"1", +"1" +] +}, +"astype_float32": { +"dtype": "float32", +"shape": [ +6 +], +"data": [ +"1.0", +"0.0", +"1.0", +"0.0", +"1.0", +"1.0" +] +}, +"astype_float64": { +"dtype": "float64", +"shape": [ +6 +], +"data": [ +"1.0", +"0.0", +"1.0", +"0.0", +"1.0", +"1.0" +] +}, +"astype_complex64": { +"dtype": "complex64", +"shape": [ +6 +], +"data": [ +[ +"1.0", +"0.0" +], +[ +"0.0", +"0.0" +], +[ +"1.0", +"0.0" +], +[ +"0.0", +"0.0" +], +[ +"1.0", +"0.0" +], +[ +"1.0", +"0.0" +] +] +}, +"astype_complex128": { +"dtype": "complex128", +"shape": [ +6 +], +"data": [ +[ +"1.0", +"0.0" +], +[ +"0.0", +"0.0" +], +[ +"1.0", +"0.0" +], +[ +"0.0", +"0.0" +], +[ +"1.0", +"0.0" +], +[ +"1.0", +"0.0" +] +] +} +}, +"int8": { +"x": [ +"0", +"1", +"7", +"127", +"-128", +"-3" +], +"y": [ +"1", +"3", +"2", +"2", +"5", +"4" +], +"add": { +"dtype": "int8", +"shape": [ +6 +], +"data": [ +"1", +"4", +"9", +"-127", +"-123", +"1" +] +}, +"sub": { +"dtype": "int8", +"shape": [ +6 +], +"data": [ +"-1", +"-2", +"5", +"125", +"123", +"-7" +] +}, +"mul": { +"dtype": "int8", +"shape": [ +6 +], +"data": [ +"0", +"3", +"14", +"-2", +"-128", +"-12" +] +}, +"div": { +"dtype": "float64", +"shape": [ +6 +], +"data": [ +"0.0", +"0.3333333333333333", +"3.5", +"63.5", +"-25.6", +"-0.75" +] +}, +"maximum": { +"dtype": "int8", +"shape": [ +6 +], +"data": [ +"1", +"3", +"7", +"127", +"5", +"4" +] +}, +"minimum": { +"dtype": "int8", +"shape": [ +6 +], +"data": [ +"0", +"1", +"2", +"2", +"-128", +"-3" +] +}, +"eq": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"false", +"false", +"false", +"false", +"false", +"false" +] +}, +"ne": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"true", +"true", +"true", +"true", +"true", +"true" +] +}, +"lt": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"true", +"true", +"false", +"false", +"true", +"true" +] +}, +"le": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"true", +"true", +"false", +"false", +"true", +"true" +] +}, +"gt": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"false", +"false", +"true", +"true", +"false", +"false" +] +}, +"ge": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"false", +"false", +"true", +"true", +"false", +"false" +] +}, +"sqrt": { +"dtype": "float16", +"shape": [ +6 +], +"data": [ +"0.0", +"1.0", +"2.646484375", +"11.265625", +"nan", +"nan" +] +}, +"exp": { +"dtype": "float16", +"shape": [ +6 +], +"data": [ +"1.0", +"2.71875", +"1097.0", +"inf", +"0.0", +"0.049774169921875" +] +}, +"log": { +"dtype": "float16", +"shape": [ +6 +], +"data": [ +"-inf", +"0.0", +"1.9462890625", +"4.84375", +"nan", +"nan" +] +}, +"sin": { +"dtype": "float16", +"shape": [ +6 +], +"data": [ +"0.0", +"0.84130859375", +"0.6572265625", +"0.97265625", +"-0.72119140625", +"-0.14111328125" +] +}, +"abs": { +"dtype": "int8", +"shape": [ +6 +], +"data": [ +"0", +"1", +"7", +"127", +"-128", +"3" +] +}, +"neg": { +"dtype": "int8", +"shape": [ +6 +], +"data": [ +"0", +"-1", +"-7", +"-127", +"-128", +"3" +] +}, +"floor": { +"dtype": "int8", +"shape": [ +6 +], +"data": [ +"0", +"1", +"7", +"127", +"-128", +"-3" +] +}, +"square": { +"dtype": "int8", +"shape": [ +6 +], +"data": [ +"0", +"1", +"49", +"1", +"0", +"9" +] +}, +"power": { +"dtype": "float64", +"shape": [ +6 +], +"data": [ +"0.0", +"1.0", +"129.64181424216494", +"181764.59888273073", +"nan", +"nan" +] +}, +"sum0": { +"dtype": "int64", +"shape": [ +3 +], +"data": [ +"127", +"-127", +"4" +] +}, +"sum1": { +"dtype": "int64", +"shape": [ +2 +], +"data": [ +"8", +"-4" +] +}, +"prod1": { +"dtype": "int64", +"shape": [ +2 +], +"data": [ +"0", +"48768" +] +}, +"max0": { +"dtype": "int8", +"shape": [ +3 +], +"data": [ +"127", +"1", +"7" +] +}, +"min1": { +"dtype": "int8", +"shape": [ +2 +], +"data": [ +"0", +"-128" +] +}, +"argmax1": { +"dtype": "int64", +"shape": [ +2 +], +"data": [ +"2", +"0" +] +}, +"argmin0": { +"dtype": "int64", +"shape": [ +3 +], +"data": [ +"0", +"1", +"1" +] +}, +"cumsum1": { +"dtype": "int64", +"shape": [ +2, +3 +], +"data": [ +"0", +"1", +"8", +"127", +"-1", +"-4" +] +}, +"cumprod0": { +"dtype": "int64", +"shape": [ +2, +3 +], +"data": [ +"0", +"1", +"7", +"0", +"-128", +"-21" +] +}, +"mean1": { +"dtype": "float64", +"shape": [ +2 +], +"data": [ +"2.6666666666666665", +"-1.3333333333333333" +] +}, +"matmul": { +"dtype": "int8", +"shape": [ +2, +2 +], +"data": [ +"50", +"107", +"107", +"10" +] +}, +"nonzero": { +"dtype": "int64", +"shape": [ +5 +], +"data": [ +"1", +"2", +"3", +"4", +"5" +] +}, +"where": { +"dtype": "int8", +"shape": [ +6 +], +"data": [ +"1", +"3", +"7", +"127", +"5", +"4" +] +}, +"astype_bool": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"false", +"true", +"true", +"true", +"true", +"true" +] +}, +"astype_int8": { +"dtype": "int8", +"shape": [ +6 +], +"data": [ +"0", +"1", +"7", +"127", +"-128", +"-3" +] +}, +"astype_int16": { +"dtype": "int16", +"shape": [ +6 +], +"data": [ +"0", +"1", +"7", +"127", +"-128", +"-3" +] +}, +"astype_int32": { +"dtype": "int32", +"shape": [ +6 +], +"data": [ +"0", +"1", +"7", +"127", +"-128", +"-3" +] +}, +"astype_int64": { +"dtype": "int64", +"shape": [ +6 +], +"data": [ +"0", +"1", +"7", +"127", +"-128", +"-3" +] +}, +"astype_uint8": { +"dtype": "uint8", +"shape": [ +6 +], +"data": [ +"0", +"1", +"7", +"127", +"128", +"253" +] +}, +"astype_uint16": { +"dtype": "uint16", +"shape": [ +6 +], +"data": [ +"0", +"1", +"7", +"127", +"65408", +"65533" +] +}, +"astype_uint32": { +"dtype": "uint32", +"shape": [ +6 +], +"data": [ +"0", +"1", +"7", +"127", +"4294967168", +"4294967293" +] +}, +"astype_uint64": { +"dtype": "uint64", +"shape": [ +6 +], +"data": [ +"0", +"1", +"7", +"127", +"18446744073709551488", +"18446744073709551613" +] +}, +"astype_float32": { +"dtype": "float32", +"shape": [ +6 +], +"data": [ +"0.0", +"1.0", +"7.0", +"127.0", +"-128.0", +"-3.0" +] +}, +"astype_float64": { +"dtype": "float64", +"shape": [ +6 +], +"data": [ +"0.0", +"1.0", +"7.0", +"127.0", +"-128.0", +"-3.0" +] +}, +"astype_complex64": { +"dtype": "complex64", +"shape": [ +6 +], +"data": [ +[ +"0.0", +"0.0" +], +[ +"1.0", +"0.0" +], +[ +"7.0", +"0.0" +], +[ +"127.0", +"0.0" +], +[ +"-128.0", +"0.0" +], +[ +"-3.0", +"0.0" +] +] +}, +"astype_complex128": { +"dtype": "complex128", +"shape": [ +6 +], +"data": [ +[ +"0.0", +"0.0" +], +[ +"1.0", +"0.0" +], +[ +"7.0", +"0.0" +], +[ +"127.0", +"0.0" +], +[ +"-128.0", +"0.0" +], +[ +"-3.0", +"0.0" +] +] +} +}, +"int16": { +"x": [ +"0", +"1", +"7", +"32767", +"-32768", +"-3" +], +"y": [ +"1", +"3", +"2", +"2", +"5", +"4" +], +"add": { +"dtype": "int16", +"shape": [ +6 +], +"data": [ +"1", +"4", +"9", +"-32767", +"-32763", +"1" +] +}, +"sub": { +"dtype": "int16", +"shape": [ +6 +], +"data": [ +"-1", +"-2", +"5", +"32765", +"32763", +"-7" +] +}, +"mul": { +"dtype": "int16", +"shape": [ +6 +], +"data": [ +"0", +"3", +"14", +"-2", +"-32768", +"-12" +] +}, +"div": { +"dtype": "float64", +"shape": [ +6 +], +"data": [ +"0.0", +"0.3333333333333333", +"3.5", +"16383.5", +"-6553.6", +"-0.75" +] +}, +"maximum": { +"dtype": "int16", +"shape": [ +6 +], +"data": [ +"1", +"3", +"7", +"32767", +"5", +"4" +] +}, +"minimum": { +"dtype": "int16", +"shape": [ +6 +], +"data": [ +"0", +"1", +"2", +"2", +"-32768", +"-3" +] +}, +"eq": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"false", +"false", +"false", +"false", +"false", +"false" +] +}, +"ne": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"true", +"true", +"true", +"true", +"true", +"true" +] +}, +"lt": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"true", +"true", +"false", +"false", +"true", +"true" +] +}, +"le": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"true", +"true", +"false", +"false", +"true", +"true" +] +}, +"gt": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"false", +"false", +"true", +"true", +"false", +"false" +] +}, +"ge": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"false", +"false", +"true", +"true", +"false", +"false" +] +}, +"sqrt": { +"dtype": "float32", +"shape": [ +6 +], +"data": [ +"0.0", +"1.0", +"2.6457512378692627", +"181.01657104492188", +"nan", +"nan" +] +}, +"exp": { +"dtype": "float32", +"shape": [ +6 +], +"data": [ +"1.0", +"2.7182817459106445", +"1096.6331787109375", +"inf", +"0.0", +"0.049787066876888275" +] +}, +"log": { +"dtype": "float32", +"shape": [ +6 +], +"data": [ +"-inf", +"0.0", +"1.945910096168518", +"10.397176742553711", +"nan", +"nan" +] +}, +"sin": { +"dtype": "float32", +"shape": [ +6 +], +"data": [ +"0.0", +"0.8414710164070129", +"0.6569865942001343", +"0.1875065565109253", +"-0.9278563261032104", +"-0.14112000167369843" +] +}, +"abs": { +"dtype": "int16", +"shape": [ +6 +], +"data": [ +"0", +"1", +"7", +"32767", +"-32768", +"3" +] +}, +"neg": { +"dtype": "int16", +"shape": [ +6 +], +"data": [ +"0", +"-1", +"-7", +"-32767", +"-32768", +"3" +] +}, +"floor": { +"dtype": "int16", +"shape": [ +6 +], +"data": [ +"0", +"1", +"7", +"32767", +"-32768", +"-3" +] +}, +"square": { +"dtype": "int16", +"shape": [ +6 +], +"data": [ +"0", +"1", +"49", +"1", +"0", +"9" +] +}, +"power": { +"dtype": "float64", +"shape": [ +6 +], +"data": [ +"0.0", +"1.0", +"129.64181424216494", +"194353203233.87292", +"nan", +"nan" +] +}, +"sum0": { +"dtype": "int64", +"shape": [ +3 +], +"data": [ +"32767", +"-32767", +"4" +] +}, +"sum1": { +"dtype": "int64", +"shape": [ +2 +], +"data": [ +"8", +"-4" +] +}, +"prod1": { +"dtype": "int64", +"shape": [ +2 +], +"data": [ +"0", +"3221127168" +] +}, +"max0": { +"dtype": "int16", +"shape": [ +3 +], +"data": [ +"32767", +"1", +"7" +] +}, +"min1": { +"dtype": "int16", +"shape": [ +2 +], +"data": [ +"0", +"-32768" +] +}, +"argmax1": { +"dtype": "int64", +"shape": [ +2 +], +"data": [ +"2", +"0" +] +}, +"argmin0": { +"dtype": "int64", +"shape": [ +3 +], +"data": [ +"0", +"1", +"1" +] +}, +"cumsum1": { +"dtype": "int64", +"shape": [ +2, +3 +], +"data": [ +"0", +"1", +"8", +"32767", +"-1", +"-4" +] +}, +"cumprod0": { +"dtype": "int64", +"shape": [ +2, +3 +], +"data": [ +"0", +"1", +"7", +"0", +"-32768", +"-21" +] +}, +"mean1": { +"dtype": "float64", +"shape": [ +2 +], +"data": [ +"2.6666666666666665", +"-1.3333333333333333" +] +}, +"matmul": { +"dtype": "int16", +"shape": [ +2, +2 +], +"data": [ +"50", +"32747", +"32747", +"10" +] +}, +"nonzero": { +"dtype": "int64", +"shape": [ +5 +], +"data": [ +"1", +"2", +"3", +"4", +"5" +] +}, +"where": { +"dtype": "int16", +"shape": [ +6 +], +"data": [ +"1", +"3", +"7", +"32767", +"5", +"4" +] +}, +"astype_bool": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"false", +"true", +"true", +"true", +"true", +"true" +] +}, +"astype_int8": { +"dtype": "int8", +"shape": [ +6 +], +"data": [ +"0", +"1", +"7", +"-1", +"0", +"-3" +] +}, +"astype_int16": { +"dtype": "int16", +"shape": [ +6 +], +"data": [ +"0", +"1", +"7", +"32767", +"-32768", +"-3" +] +}, +"astype_int32": { +"dtype": "int32", +"shape": [ +6 +], +"data": [ +"0", +"1", +"7", +"32767", +"-32768", +"-3" +] +}, +"astype_int64": { +"dtype": "int64", +"shape": [ +6 +], +"data": [ +"0", +"1", +"7", +"32767", +"-32768", +"-3" +] +}, +"astype_uint8": { +"dtype": "uint8", +"shape": [ +6 +], +"data": [ +"0", +"1", +"7", +"255", +"0", +"253" +] +}, +"astype_uint16": { +"dtype": "uint16", +"shape": [ +6 +], +"data": [ +"0", +"1", +"7", +"32767", +"32768", +"65533" +] +}, +"astype_uint32": { +"dtype": "uint32", +"shape": [ +6 +], +"data": [ +"0", +"1", +"7", +"32767", +"4294934528", +"4294967293" +] +}, +"astype_uint64": { +"dtype": "uint64", +"shape": [ +6 +], +"data": [ +"0", +"1", +"7", +"32767", +"18446744073709518848", +"18446744073709551613" +] +}, +"astype_float32": { +"dtype": "float32", +"shape": [ +6 +], +"data": [ +"0.0", +"1.0", +"7.0", +"32767.0", +"-32768.0", +"-3.0" +] +}, +"astype_float64": { +"dtype": "float64", +"shape": [ +6 +], +"data": [ +"0.0", +"1.0", +"7.0", +"32767.0", +"-32768.0", +"-3.0" +] +}, +"astype_complex64": { +"dtype": "complex64", +"shape": [ +6 +], +"data": [ +[ +"0.0", +"0.0" +], +[ +"1.0", +"0.0" +], +[ +"7.0", +"0.0" +], +[ +"32767.0", +"0.0" +], +[ +"-32768.0", +"0.0" +], +[ +"-3.0", +"0.0" +] +] +}, +"astype_complex128": { +"dtype": "complex128", +"shape": [ +6 +], +"data": [ +[ +"0.0", +"0.0" +], +[ +"1.0", +"0.0" +], +[ +"7.0", +"0.0" +], +[ +"32767.0", +"0.0" +], +[ +"-32768.0", +"0.0" +], +[ +"-3.0", +"0.0" +] +] +} +}, +"int32": { +"x": [ +"0", +"1", +"7", +"2147483647", +"-2147483648", +"-3" +], +"y": [ +"1", +"3", +"2", +"2", +"5", +"4" +], +"add": { +"dtype": "int32", +"shape": [ +6 +], +"data": [ +"1", +"4", +"9", +"-2147483647", +"-2147483643", +"1" +] +}, +"sub": { +"dtype": "int32", +"shape": [ +6 +], +"data": [ +"-1", +"-2", +"5", +"2147483645", +"2147483643", +"-7" +] +}, +"mul": { +"dtype": "int32", +"shape": [ +6 +], +"data": [ +"0", +"3", +"14", +"-2", +"-2147483648", +"-12" +] +}, +"div": { +"dtype": "float64", +"shape": [ +6 +], +"data": [ +"0.0", +"0.3333333333333333", +"3.5", +"1073741823.5", +"-429496729.6", +"-0.75" +] +}, +"maximum": { +"dtype": "int32", +"shape": [ +6 +], +"data": [ +"1", +"3", +"7", +"2147483647", +"5", +"4" +] +}, +"minimum": { +"dtype": "int32", +"shape": [ +6 +], +"data": [ +"0", +"1", +"2", +"2", +"-2147483648", +"-3" +] +}, +"eq": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"false", +"false", +"false", +"false", +"false", +"false" +] +}, +"ne": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"true", +"true", +"true", +"true", +"true", +"true" +] +}, +"lt": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"true", +"true", +"false", +"false", +"true", +"true" +] +}, +"le": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"true", +"true", +"false", +"false", +"true", +"true" +] +}, +"gt": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"false", +"false", +"true", +"true", +"false", +"false" +] +}, +"ge": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"false", +"false", +"true", +"true", +"false", +"false" +] +}, +"sqrt": { +"dtype": "float64", +"shape": [ +6 +], +"data": [ +"0.0", +"1.0", +"2.6457513110645907", +"46340.950001051984", +"nan", +"nan" +] +}, +"exp": { +"dtype": "float64", +"shape": [ +6 +], +"data": [ +"1.0", +"2.718281828459045", +"1096.6331584284585", +"inf", +"0.0", +"0.049787068367863944" +] +}, +"log": { +"dtype": "float64", +"shape": [ +6 +], +"data": [ +"-inf", +"0.0", +"1.9459101490553132", +"21.487562596892644", +"nan", +"nan" +] +}, +"sin": { +"dtype": "float64", +"shape": [ +6 +], +"data": [ +"0.0", +"0.8414709848078965", +"0.6569865987187891", +"-0.7249165551445563", +"0.9713101757929392", +"-0.1411200080598672" +] +}, +"abs": { +"dtype": "int32", +"shape": [ +6 +], +"data": [ +"0", +"1", +"7", +"2147483647", +"-2147483648", +"3" +] +}, +"neg": { +"dtype": "int32", +"shape": [ +6 +], +"data": [ +"0", +"-1", +"-7", +"-2147483647", +"-2147483648", +"3" +] +}, +"floor": { +"dtype": "int32", +"shape": [ +6 +], +"data": [ +"0", +"1", +"7", +"2147483647", +"-2147483648", +"-3" +] +}, +"square": { +"dtype": "int32", +"shape": [ +6 +], +"data": [ +"0", +"1", +"49", +"1", +"0", +"9" +] +}, +"power": { +"dtype": "float64", +"shape": [ +6 +], +"data": [ +"0.0", +"1.0", +"129.64181424216494", +"2.1370991100146124e+23", +"nan", +"nan" +] +}, +"sum0": { +"dtype": "int64", +"shape": [ +3 +], +"data": [ +"2147483647", +"-2147483647", +"4" +] +}, +"sum1": { +"dtype": "int64", +"shape": [ +2 +], +"data": [ +"8", +"-4" +] +}, +"prod1": { +"dtype": "int64", +"shape": [ +2 +], +"data": [ +"0", +"-4611686024869838848" +] +}, +"max0": { +"dtype": "int32", +"shape": [ +3 +], +"data": [ +"2147483647", +"1", +"7" +] +}, +"min1": { +"dtype": "int32", +"shape": [ +2 +], +"data": [ +"0", +"-2147483648" +] +}, +"argmax1": { +"dtype": "int64", +"shape": [ +2 +], +"data": [ +"2", +"0" +] +}, +"argmin0": { +"dtype": "int64", +"shape": [ +3 +], +"data": [ +"0", +"1", +"1" +] +}, +"cumsum1": { +"dtype": "int64", +"shape": [ +2, +3 +], +"data": [ +"0", +"1", +"8", +"2147483647", +"-1", +"-4" +] +}, +"cumprod0": { +"dtype": "int64", +"shape": [ +2, +3 +], +"data": [ +"0", +"1", +"7", +"0", +"-2147483648", +"-21" +] +}, +"mean1": { +"dtype": "float64", +"shape": [ +2 +], +"data": [ +"2.6666666666666665", +"-1.3333333333333333" +] +}, +"matmul": { +"dtype": "int32", +"shape": [ +2, +2 +], +"data": [ +"50", +"2147483627", +"2147483627", +"10" +] +}, +"nonzero": { +"dtype": "int64", +"shape": [ +5 +], +"data": [ +"1", +"2", +"3", +"4", +"5" +] +}, +"where": { +"dtype": "int32", +"shape": [ +6 +], +"data": [ +"1", +"3", +"7", +"2147483647", +"5", +"4" +] +}, +"astype_bool": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"false", +"true", +"true", +"true", +"true", +"true" +] +}, +"astype_int8": { +"dtype": "int8", +"shape": [ +6 +], +"data": [ +"0", +"1", +"7", +"-1", +"0", +"-3" +] +}, +"astype_int16": { +"dtype": "int16", +"shape": [ +6 +], +"data": [ +"0", +"1", +"7", +"-1", +"0", +"-3" +] +}, +"astype_int32": { +"dtype": "int32", +"shape": [ +6 +], +"data": [ +"0", +"1", +"7", +"2147483647", +"-2147483648", +"-3" +] +}, +"astype_int64": { +"dtype": "int64", +"shape": [ +6 +], +"data": [ +"0", +"1", +"7", +"2147483647", +"-2147483648", +"-3" +] +}, +"astype_uint8": { +"dtype": "uint8", +"shape": [ +6 +], +"data": [ +"0", +"1", +"7", +"255", +"0", +"253" +] +}, +"astype_uint16": { +"dtype": "uint16", +"shape": [ +6 +], +"data": [ +"0", +"1", +"7", +"65535", +"0", +"65533" +] +}, +"astype_uint32": { +"dtype": "uint32", +"shape": [ +6 +], +"data": [ +"0", +"1", +"7", +"2147483647", +"2147483648", +"4294967293" +] +}, +"astype_uint64": { +"dtype": "uint64", +"shape": [ +6 +], +"data": [ +"0", +"1", +"7", +"2147483647", +"18446744071562067968", +"18446744073709551613" +] +}, +"astype_float32": { +"dtype": "float32", +"shape": [ +6 +], +"data": [ +"0.0", +"1.0", +"7.0", +"2147483648.0", +"-2147483648.0", +"-3.0" +] +}, +"astype_float64": { +"dtype": "float64", +"shape": [ +6 +], +"data": [ +"0.0", +"1.0", +"7.0", +"2147483647.0", +"-2147483648.0", +"-3.0" +] +}, +"astype_complex64": { +"dtype": "complex64", +"shape": [ +6 +], +"data": [ +[ +"0.0", +"0.0" +], +[ +"1.0", +"0.0" +], +[ +"7.0", +"0.0" +], +[ +"2147483648.0", +"0.0" +], +[ +"-2147483648.0", +"0.0" +], +[ +"-3.0", +"0.0" +] +] +}, +"astype_complex128": { +"dtype": "complex128", +"shape": [ +6 +], +"data": [ +[ +"0.0", +"0.0" +], +[ +"1.0", +"0.0" +], +[ +"7.0", +"0.0" +], +[ +"2147483647.0", +"0.0" +], +[ +"-2147483648.0", +"0.0" +], +[ +"-3.0", +"0.0" +] +] +} +}, +"int64": { +"x": [ +"0", +"1", +"7", +"9223372036854775807", +"-9223372036854775808", +"-3" +], +"y": [ +"1", +"3", +"2", +"2", +"5", +"4" +], +"add": { +"dtype": "int64", +"shape": [ +6 +], +"data": [ +"1", +"4", +"9", +"-9223372036854775807", +"-9223372036854775803", +"1" +] +}, +"sub": { +"dtype": "int64", +"shape": [ +6 +], +"data": [ +"-1", +"-2", +"5", +"9223372036854775805", +"9223372036854775803", +"-7" +] +}, +"mul": { +"dtype": "int64", +"shape": [ +6 +], +"data": [ +"0", +"3", +"14", +"-2", +"-9223372036854775808", +"-12" +] +}, +"div": { +"dtype": "float64", +"shape": [ +6 +], +"data": [ +"0.0", +"0.3333333333333333", +"3.5", +"4.611686018427388e+18", +"-1.8446744073709553e+18", +"-0.75" +] +}, +"maximum": { +"dtype": "int64", +"shape": [ +6 +], +"data": [ +"1", +"3", +"7", +"9223372036854775807", +"5", +"4" +] +}, +"minimum": { +"dtype": "int64", +"shape": [ +6 +], +"data": [ +"0", +"1", +"2", +"2", +"-9223372036854775808", +"-3" +] +}, +"eq": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"false", +"false", +"false", +"false", +"false", +"false" +] +}, +"ne": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"true", +"true", +"true", +"true", +"true", +"true" +] +}, +"lt": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"true", +"true", +"false", +"false", +"true", +"true" +] +}, +"le": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"true", +"true", +"false", +"false", +"true", +"true" +] +}, +"gt": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"false", +"false", +"true", +"true", +"false", +"false" +] +}, +"ge": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"false", +"false", +"true", +"true", +"false", +"false" +] +}, +"sqrt": { +"dtype": "float64", +"shape": [ +6 +], +"data": [ +"0.0", +"1.0", +"2.6457513110645907", +"3037000499.97605", +"nan", +"nan" +] +}, +"exp": { +"dtype": "float64", +"shape": [ +6 +], +"data": [ +"1.0", +"2.718281828459045", +"1096.6331584284585", +"inf", +"0.0", +"0.049787068367863944" +] +}, +"log": { +"dtype": "float64", +"shape": [ +6 +], +"data": [ +"-inf", +"0.0", +"1.9459101490553132", +"43.66827237527655", +"nan", +"nan" +] +}, +"sin": { +"dtype": "float64", +"shape": [ +6 +], +"data": [ +"0.0", +"0.8414709848078965", +"0.6569865987187891", +"0.9999303766734422", +"-0.9999303766734422", +"-0.1411200080598672" +] +}, +"abs": { +"dtype": "int64", +"shape": [ +6 +], +"data": [ +"0", +"1", +"7", +"9223372036854775807", +"-9223372036854775808", +"3" +] +}, +"neg": { +"dtype": "int64", +"shape": [ +6 +], +"data": [ +"0", +"-1", +"-7", +"-9223372036854775807", +"-9223372036854775808", +"3" +] +}, +"floor": { +"dtype": "int64", +"shape": [ +6 +], +"data": [ +"0", +"1", +"7", +"9223372036854775807", +"-9223372036854775808", +"-3" +] +}, +"square": { +"dtype": "int64", +"shape": [ +6 +], +"data": [ +"0", +"1", +"49", +"1", +"0", +"9" +] +}, +"power": { +"dtype": "float64", +"shape": [ +6 +], +"data": [ +"0.0", +"1.0", +"129.64181424216494", +"2.5835942961798094e+47", +"nan", +"nan" +] +}, +"sum0": { +"dtype": "int64", +"shape": [ +3 +], +"data": [ +"9223372036854775807", +"-9223372036854775807", +"4" +] +}, +"sum1": { +"dtype": "int64", +"shape": [ +2 +], +"data": [ +"8", +"-4" +] +}, +"prod1": { +"dtype": "int64", +"shape": [ +2 +], +"data": [ +"0", +"-9223372036854775808" +] +}, +"max0": { +"dtype": "int64", +"shape": [ +3 +], +"data": [ +"9223372036854775807", +"1", +"7" +] +}, +"min1": { +"dtype": "int64", +"shape": [ +2 +], +"data": [ +"0", +"-9223372036854775808" +] +}, +"argmax1": { +"dtype": "int64", +"shape": [ +2 +], +"data": [ +"2", +"0" +] +}, +"argmin0": { +"dtype": "int64", +"shape": [ +3 +], +"data": [ +"0", +"1", +"1" +] +}, +"cumsum1": { +"dtype": "int64", +"shape": [ +2, +3 +], +"data": [ +"0", +"1", +"8", +"9223372036854775807", +"-1", +"-4" +] +}, +"cumprod0": { +"dtype": "int64", +"shape": [ +2, +3 +], +"data": [ +"0", +"1", +"7", +"0", +"-9223372036854775808", +"-21" +] +}, +"mean1": { +"dtype": "float64", +"shape": [ +2 +], +"data": [ +"2.6666666666666665", +"-1.0" +] +}, +"matmul": { +"dtype": "int64", +"shape": [ +2, +2 +], +"data": [ +"50", +"9223372036854775787", +"9223372036854775787", +"10" +] +}, +"nonzero": { +"dtype": "int64", +"shape": [ +5 +], +"data": [ +"1", +"2", +"3", +"4", +"5" +] +}, +"where": { +"dtype": "int64", +"shape": [ +6 +], +"data": [ +"1", +"3", +"7", +"9223372036854775807", +"5", +"4" +] +}, +"astype_bool": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"false", +"true", +"true", +"true", +"true", +"true" +] +}, +"astype_int8": { +"dtype": "int8", +"shape": [ +6 +], +"data": [ +"0", +"1", +"7", +"-1", +"0", +"-3" +] +}, +"astype_int16": { +"dtype": "int16", +"shape": [ +6 +], +"data": [ +"0", +"1", +"7", +"-1", +"0", +"-3" +] +}, +"astype_int32": { +"dtype": "int32", +"shape": [ +6 +], +"data": [ +"0", +"1", +"7", +"-1", +"0", +"-3" +] +}, +"astype_int64": { +"dtype": "int64", +"shape": [ +6 +], +"data": [ +"0", +"1", +"7", +"9223372036854775807", +"-9223372036854775808", +"-3" +] +}, +"astype_uint8": { +"dtype": "uint8", +"shape": [ +6 +], +"data": [ +"0", +"1", +"7", +"255", +"0", +"253" +] +}, +"astype_uint16": { +"dtype": "uint16", +"shape": [ +6 +], +"data": [ +"0", +"1", +"7", +"65535", +"0", +"65533" +] +}, +"astype_uint32": { +"dtype": "uint32", +"shape": [ +6 +], +"data": [ +"0", +"1", +"7", +"4294967295", +"0", +"4294967293" +] +}, +"astype_uint64": { +"dtype": "uint64", +"shape": [ +6 +], +"data": [ +"0", +"1", +"7", +"9223372036854775807", +"9223372036854775808", +"18446744073709551613" +] +}, +"astype_float32": { +"dtype": "float32", +"shape": [ +6 +], +"data": [ +"0.0", +"1.0", +"7.0", +"9.223372036854776e+18", +"-9.223372036854776e+18", +"-3.0" +] +}, +"astype_float64": { +"dtype": "float64", +"shape": [ +6 +], +"data": [ +"0.0", +"1.0", +"7.0", +"9.223372036854776e+18", +"-9.223372036854776e+18", +"-3.0" +] +}, +"astype_complex64": { +"dtype": "complex64", +"shape": [ +6 +], +"data": [ +[ +"0.0", +"0.0" +], +[ +"1.0", +"0.0" +], +[ +"7.0", +"0.0" +], +[ +"9.223372036854776e+18", +"0.0" +], +[ +"-9.223372036854776e+18", +"0.0" +], +[ +"-3.0", +"0.0" +] +] +}, +"astype_complex128": { +"dtype": "complex128", +"shape": [ +6 +], +"data": [ +[ +"0.0", +"0.0" +], +[ +"1.0", +"0.0" +], +[ +"7.0", +"0.0" +], +[ +"9.223372036854776e+18", +"0.0" +], +[ +"-9.223372036854776e+18", +"0.0" +], +[ +"-3.0", +"0.0" +] +] +} +}, +"uint8": { +"x": [ +"0", +"1", +"7", +"255", +"0", +"3" +], +"y": [ +"1", +"3", +"2", +"2", +"5", +"4" +], +"add": { +"dtype": "uint8", +"shape": [ +6 +], +"data": [ +"1", +"4", +"9", +"1", +"5", +"7" +] +}, +"sub": { +"dtype": "uint8", +"shape": [ +6 +], +"data": [ +"255", +"254", +"5", +"253", +"251", +"255" +] +}, +"mul": { +"dtype": "uint8", +"shape": [ +6 +], +"data": [ +"0", +"3", +"14", +"254", +"0", +"12" +] +}, +"div": { +"dtype": "float64", +"shape": [ +6 +], +"data": [ +"0.0", +"0.3333333333333333", +"3.5", +"127.5", +"0.0", +"0.75" +] +}, +"maximum": { +"dtype": "uint8", +"shape": [ +6 +], +"data": [ +"1", +"3", +"7", +"255", +"5", +"4" +] +}, +"minimum": { +"dtype": "uint8", +"shape": [ +6 +], +"data": [ +"0", +"1", +"2", +"2", +"0", +"3" +] +}, +"eq": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"false", +"false", +"false", +"false", +"false", +"false" +] +}, +"ne": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"true", +"true", +"true", +"true", +"true", +"true" +] +}, +"lt": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"true", +"true", +"false", +"false", +"true", +"true" +] +}, +"le": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"true", +"true", +"false", +"false", +"true", +"true" +] +}, +"gt": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"false", +"false", +"true", +"true", +"false", +"false" +] +}, +"ge": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"false", +"false", +"true", +"true", +"false", +"false" +] +}, +"sqrt": { +"dtype": "float16", +"shape": [ +6 +], +"data": [ +"0.0", +"1.0", +"2.646484375", +"15.96875", +"0.0", +"1.732421875" +] +}, +"exp": { +"dtype": "float16", +"shape": [ +6 +], +"data": [ +"1.0", +"2.71875", +"1097.0", +"inf", +"1.0", +"20.078125" +] +}, +"log": { +"dtype": "float16", +"shape": [ +6 +], +"data": [ +"-inf", +"0.0", +"1.9462890625", +"5.54296875", +"-inf", +"1.0986328125" +] +}, +"sin": { +"dtype": "float16", +"shape": [ +6 +], +"data": [ +"0.0", +"0.84130859375", +"0.6572265625", +"-0.50634765625", +"0.0", +"0.14111328125" +] +}, +"abs": { +"dtype": "uint8", +"shape": [ +6 +], +"data": [ +"0", +"1", +"7", +"255", +"0", +"3" +] +}, +"neg": { +"dtype": "uint8", +"shape": [ +6 +], +"data": [ +"0", +"255", +"249", +"1", +"0", +"253" +] +}, +"floor": { +"dtype": "uint8", +"shape": [ +6 +], +"data": [ +"0", +"1", +"7", +"255", +"0", +"3" +] +}, +"square": { +"dtype": "uint8", +"shape": [ +6 +], +"data": [ +"0", +"1", +"49", +"1", +"0", +"9" +] +}, +"power": { +"dtype": "float64", +"shape": [ +6 +], +"data": [ +"0.0", +"1.0", +"129.64181424216494", +"1038365.9804592021", +"0.0", +"15.588457268119896" +] +}, +"sum0": { +"dtype": "uint64", +"shape": [ +3 +], +"data": [ +"255", +"1", +"10" +] +}, +"sum1": { +"dtype": "uint64", +"shape": [ +2 +], +"data": [ +"8", +"258" +] +}, +"prod1": { +"dtype": "uint64", +"shape": [ +2 +], +"data": [ +"0", +"0" +] +}, +"max0": { +"dtype": "uint8", +"shape": [ +3 +], +"data": [ +"255", +"1", +"7" +] +}, +"min1": { +"dtype": "uint8", +"shape": [ +2 +], +"data": [ +"0", +"0" +] +}, +"argmax1": { +"dtype": "int64", +"shape": [ +2 +], +"data": [ +"2", +"0" +] +}, +"argmin0": { +"dtype": "int64", +"shape": [ +3 +], +"data": [ +"0", +"1", +"1" +] +}, +"cumsum1": { +"dtype": "uint64", +"shape": [ +2, +3 +], +"data": [ +"0", +"1", +"8", +"255", +"255", +"258" +] +}, +"cumprod0": { +"dtype": "uint64", +"shape": [ +2, +3 +], +"data": [ +"0", +"1", +"7", +"0", +"0", +"21" +] +}, +"mean1": { +"dtype": "float64", +"shape": [ +2 +], +"data": [ +"2.6666666666666665", +"86.0" +] +}, +"matmul": { +"dtype": "uint8", +"shape": [ +2, +2 +], +"data": [ +"50", +"21", +"21", +"10" +] +}, +"nonzero": { +"dtype": "int64", +"shape": [ +4 +], +"data": [ +"1", +"2", +"3", +"5" +] +}, +"where": { +"dtype": "uint8", +"shape": [ +6 +], +"data": [ +"1", +"3", +"7", +"255", +"5", +"4" +] +}, +"astype_bool": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"false", +"true", +"true", +"true", +"false", +"true" +] +}, +"astype_int8": { +"dtype": "int8", +"shape": [ +6 +], +"data": [ +"0", +"1", +"7", +"-1", +"0", +"3" +] +}, +"astype_int16": { +"dtype": "int16", +"shape": [ +6 +], +"data": [ +"0", +"1", +"7", +"255", +"0", +"3" +] +}, +"astype_int32": { +"dtype": "int32", +"shape": [ +6 +], +"data": [ +"0", +"1", +"7", +"255", +"0", +"3" +] +}, +"astype_int64": { +"dtype": "int64", +"shape": [ +6 +], +"data": [ +"0", +"1", +"7", +"255", +"0", +"3" +] +}, +"astype_uint8": { +"dtype": "uint8", +"shape": [ +6 +], +"data": [ +"0", +"1", +"7", +"255", +"0", +"3" +] +}, +"astype_uint16": { +"dtype": "uint16", +"shape": [ +6 +], +"data": [ +"0", +"1", +"7", +"255", +"0", +"3" +] +}, +"astype_uint32": { +"dtype": "uint32", +"shape": [ +6 +], +"data": [ +"0", +"1", +"7", +"255", +"0", +"3" +] +}, +"astype_uint64": { +"dtype": "uint64", +"shape": [ +6 +], +"data": [ +"0", +"1", +"7", +"255", +"0", +"3" +] +}, +"astype_float32": { +"dtype": "float32", +"shape": [ +6 +], +"data": [ +"0.0", +"1.0", +"7.0", +"255.0", +"0.0", +"3.0" +] +}, +"astype_float64": { +"dtype": "float64", +"shape": [ +6 +], +"data": [ +"0.0", +"1.0", +"7.0", +"255.0", +"0.0", +"3.0" +] +}, +"astype_complex64": { +"dtype": "complex64", +"shape": [ +6 +], +"data": [ +[ +"0.0", +"0.0" +], +[ +"1.0", +"0.0" +], +[ +"7.0", +"0.0" +], +[ +"255.0", +"0.0" +], +[ +"0.0", +"0.0" +], +[ +"3.0", +"0.0" +] +] +}, +"astype_complex128": { +"dtype": "complex128", +"shape": [ +6 +], +"data": [ +[ +"0.0", +"0.0" +], +[ +"1.0", +"0.0" +], +[ +"7.0", +"0.0" +], +[ +"255.0", +"0.0" +], +[ +"0.0", +"0.0" +], +[ +"3.0", +"0.0" +] +] +} +}, +"uint16": { +"x": [ +"0", +"1", +"7", +"65535", +"0", +"3" +], +"y": [ +"1", +"3", +"2", +"2", +"5", +"4" +], +"add": { +"dtype": "uint16", +"shape": [ +6 +], +"data": [ +"1", +"4", +"9", +"1", +"5", +"7" +] +}, +"sub": { +"dtype": "uint16", +"shape": [ +6 +], +"data": [ +"65535", +"65534", +"5", +"65533", +"65531", +"65535" +] +}, +"mul": { +"dtype": "uint16", +"shape": [ +6 +], +"data": [ +"0", +"3", +"14", +"65534", +"0", +"12" +] +}, +"div": { +"dtype": "float64", +"shape": [ +6 +], +"data": [ +"0.0", +"0.3333333333333333", +"3.5", +"32767.5", +"0.0", +"0.75" +] +}, +"maximum": { +"dtype": "uint16", +"shape": [ +6 +], +"data": [ +"1", +"3", +"7", +"65535", +"5", +"4" +] +}, +"minimum": { +"dtype": "uint16", +"shape": [ +6 +], +"data": [ +"0", +"1", +"2", +"2", +"0", +"3" +] +}, +"eq": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"false", +"false", +"false", +"false", +"false", +"false" +] +}, +"ne": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"true", +"true", +"true", +"true", +"true", +"true" +] +}, +"lt": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"true", +"true", +"false", +"false", +"true", +"true" +] +}, +"le": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"true", +"true", +"false", +"false", +"true", +"true" +] +}, +"gt": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"false", +"false", +"true", +"true", +"false", +"false" +] +}, +"ge": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"false", +"false", +"true", +"true", +"false", +"false" +] +}, +"sqrt": { +"dtype": "float32", +"shape": [ +6 +], +"data": [ +"0.0", +"1.0", +"2.6457512378692627", +"255.998046875", +"0.0", +"1.7320507764816284" +] +}, +"exp": { +"dtype": "float32", +"shape": [ +6 +], +"data": [ +"1.0", +"2.7182817459106445", +"1096.6331787109375", +"inf", +"1.0", +"20.08553695678711" +] +}, +"log": { +"dtype": "float32", +"shape": [ +6 +], +"data": [ +"-inf", +"0.0", +"1.945910096168518", +"11.090339660644531", +"-inf", +"1.0986123085021973" +] +}, +"sin": { +"dtype": "float32", +"shape": [ +6 +], +"data": [ +"0.0", +"0.8414710164070129", +"0.6569865942001343", +"0.9813275337219238", +"0.0", +"0.14112000167369843" +] +}, +"abs": { +"dtype": "uint16", +"shape": [ +6 +], +"data": [ +"0", +"1", +"7", +"65535", +"0", +"3" +] +}, +"neg": { +"dtype": "uint16", +"shape": [ +6 +], +"data": [ +"0", +"65535", +"65529", +"1", +"0", +"65533" +] +}, +"floor": { +"dtype": "uint16", +"shape": [ +6 +], +"data": [ +"0", +"1", +"7", +"65535", +"0", +"3" +] +}, +"square": { +"dtype": "uint16", +"shape": [ +6 +], +"data": [ +"0", +"1", +"49", +"1", +"0", +"9" +] +}, +"power": { +"dtype": "float64", +"shape": [ +6 +], +"data": [ +"0.0", +"1.0", +"129.64181424216494", +"1099469685215.9988", +"0.0", +"15.588457268119896" +] +}, +"sum0": { +"dtype": "uint64", +"shape": [ +3 +], +"data": [ +"65535", +"1", +"10" +] +}, +"sum1": { +"dtype": "uint64", +"shape": [ +2 +], +"data": [ +"8", +"65538" +] +}, +"prod1": { +"dtype": "uint64", +"shape": [ +2 +], +"data": [ +"0", +"0" +] +}, +"max0": { +"dtype": "uint16", +"shape": [ +3 +], +"data": [ +"65535", +"1", +"7" +] +}, +"min1": { +"dtype": "uint16", +"shape": [ +2 +], +"data": [ +"0", +"0" +] +}, +"argmax1": { +"dtype": "int64", +"shape": [ +2 +], +"data": [ +"2", +"0" +] +}, +"argmin0": { +"dtype": "int64", +"shape": [ +3 +], +"data": [ +"0", +"1", +"1" +] +}, +"cumsum1": { +"dtype": "uint64", +"shape": [ +2, +3 +], +"data": [ +"0", +"1", +"8", +"65535", +"65535", +"65538" +] +}, +"cumprod0": { +"dtype": "uint64", +"shape": [ +2, +3 +], +"data": [ +"0", +"1", +"7", +"0", +"0", +"21" +] +}, +"mean1": { +"dtype": "float64", +"shape": [ +2 +], +"data": [ +"2.6666666666666665", +"21846.0" +] +}, +"matmul": { +"dtype": "uint16", +"shape": [ +2, +2 +], +"data": [ +"50", +"21", +"21", +"10" +] +}, +"nonzero": { +"dtype": "int64", +"shape": [ +4 +], +"data": [ +"1", +"2", +"3", +"5" +] +}, +"where": { +"dtype": "uint16", +"shape": [ +6 +], +"data": [ +"1", +"3", +"7", +"65535", +"5", +"4" +] +}, +"astype_bool": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"false", +"true", +"true", +"true", +"false", +"true" +] +}, +"astype_int8": { +"dtype": "int8", +"shape": [ +6 +], +"data": [ +"0", +"1", +"7", +"-1", +"0", +"3" +] +}, +"astype_int16": { +"dtype": "int16", +"shape": [ +6 +], +"data": [ +"0", +"1", +"7", +"-1", +"0", +"3" +] +}, +"astype_int32": { +"dtype": "int32", +"shape": [ +6 +], +"data": [ +"0", +"1", +"7", +"65535", +"0", +"3" +] +}, +"astype_int64": { +"dtype": "int64", +"shape": [ +6 +], +"data": [ +"0", +"1", +"7", +"65535", +"0", +"3" +] +}, +"astype_uint8": { +"dtype": "uint8", +"shape": [ +6 +], +"data": [ +"0", +"1", +"7", +"255", +"0", +"3" +] +}, +"astype_uint16": { +"dtype": "uint16", +"shape": [ +6 +], +"data": [ +"0", +"1", +"7", +"65535", +"0", +"3" +] +}, +"astype_uint32": { +"dtype": "uint32", +"shape": [ +6 +], +"data": [ +"0", +"1", +"7", +"65535", +"0", +"3" +] +}, +"astype_uint64": { +"dtype": "uint64", +"shape": [ +6 +], +"data": [ +"0", +"1", +"7", +"65535", +"0", +"3" +] +}, +"astype_float32": { +"dtype": "float32", +"shape": [ +6 +], +"data": [ +"0.0", +"1.0", +"7.0", +"65535.0", +"0.0", +"3.0" +] +}, +"astype_float64": { +"dtype": "float64", +"shape": [ +6 +], +"data": [ +"0.0", +"1.0", +"7.0", +"65535.0", +"0.0", +"3.0" +] +}, +"astype_complex64": { +"dtype": "complex64", +"shape": [ +6 +], +"data": [ +[ +"0.0", +"0.0" +], +[ +"1.0", +"0.0" +], +[ +"7.0", +"0.0" +], +[ +"65535.0", +"0.0" +], +[ +"0.0", +"0.0" +], +[ +"3.0", +"0.0" +] +] +}, +"astype_complex128": { +"dtype": "complex128", +"shape": [ +6 +], +"data": [ +[ +"0.0", +"0.0" +], +[ +"1.0", +"0.0" +], +[ +"7.0", +"0.0" +], +[ +"65535.0", +"0.0" +], +[ +"0.0", +"0.0" +], +[ +"3.0", +"0.0" +] +] +} +}, +"uint32": { +"x": [ +"0", +"1", +"7", +"4294967295", +"0", +"3" +], +"y": [ +"1", +"3", +"2", +"2", +"5", +"4" +], +"add": { +"dtype": "uint32", +"shape": [ +6 +], +"data": [ +"1", +"4", +"9", +"1", +"5", +"7" +] +}, +"sub": { +"dtype": "uint32", +"shape": [ +6 +], +"data": [ +"4294967295", +"4294967294", +"5", +"4294967293", +"4294967291", +"4294967295" +] +}, +"mul": { +"dtype": "uint32", +"shape": [ +6 +], +"data": [ +"0", +"3", +"14", +"4294967294", +"0", +"12" +] +}, +"div": { +"dtype": "float64", +"shape": [ +6 +], +"data": [ +"0.0", +"0.3333333333333333", +"3.5", +"2147483647.5", +"0.0", +"0.75" +] +}, +"maximum": { +"dtype": "uint32", +"shape": [ +6 +], +"data": [ +"1", +"3", +"7", +"4294967295", +"5", +"4" +] +}, +"minimum": { +"dtype": "uint32", +"shape": [ +6 +], +"data": [ +"0", +"1", +"2", +"2", +"0", +"3" +] +}, +"eq": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"false", +"false", +"false", +"false", +"false", +"false" +] +}, +"ne": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"true", +"true", +"true", +"true", +"true", +"true" +] +}, +"lt": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"true", +"true", +"false", +"false", +"true", +"true" +] +}, +"le": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"true", +"true", +"false", +"false", +"true", +"true" +] +}, +"gt": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"false", +"false", +"true", +"true", +"false", +"false" +] +}, +"ge": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"false", +"false", +"true", +"true", +"false", +"false" +] +}, +"sqrt": { +"dtype": "float64", +"shape": [ +6 +], +"data": [ +"0.0", +"1.0", +"2.6457513110645907", +"65535.999992370605", +"0.0", +"1.7320508075688772" +] +}, +"exp": { +"dtype": "float64", +"shape": [ +6 +], +"data": [ +"1.0", +"2.718281828459045", +"1096.6331584284585", +"inf", +"1.0", +"20.085536923187668" +] +}, +"log": { +"dtype": "float64", +"shape": [ +6 +], +"data": [ +"-inf", +"0.0", +"1.9459101490553132", +"22.18070977768542", +"-inf", +"1.0986122886681098" +] +}, +"sin": { +"dtype": "float64", +"shape": [ +6 +], +"data": [ +"0.0", +"0.8414709848078965", +"0.6569865987187891", +"0.49667719175328817", +"0.0", +"0.1411200080598672" +] +}, +"abs": { +"dtype": "uint32", +"shape": [ +6 +], +"data": [ +"0", +"1", +"7", +"4294967295", +"0", +"3" +] +}, +"neg": { +"dtype": "uint32", +"shape": [ +6 +], +"data": [ +"0", +"4294967295", +"4294967289", +"1", +"0", +"4294967293" +] +}, +"floor": { +"dtype": "uint32", +"shape": [ +6 +], +"data": [ +"0", +"1", +"7", +"4294967295", +"0", +"3" +] +}, +"square": { +"dtype": "uint32", +"shape": [ +6 +], +"data": [ +"0", +"1", +"49", +"1", +"0", +"9" +] +}, +"power": { +"dtype": "float64", +"shape": [ +6 +], +"data": [ +"0.0", +"1.0", +"129.64181424216494", +"1.2089258189109417e+24", +"0.0", +"15.588457268119896" +] +}, +"sum0": { +"dtype": "uint64", +"shape": [ +3 +], +"data": [ +"4294967295", +"1", +"10" +] +}, +"sum1": { +"dtype": "uint64", +"shape": [ +2 +], +"data": [ +"8", +"4294967298" +] +}, +"prod1": { +"dtype": "uint64", +"shape": [ +2 +], +"data": [ +"0", +"0" +] +}, +"max0": { +"dtype": "uint32", +"shape": [ +3 +], +"data": [ +"4294967295", +"1", +"7" +] +}, +"min1": { +"dtype": "uint32", +"shape": [ +2 +], +"data": [ +"0", +"0" +] +}, +"argmax1": { +"dtype": "int64", +"shape": [ +2 +], +"data": [ +"2", +"0" +] +}, +"argmin0": { +"dtype": "int64", +"shape": [ +3 +], +"data": [ +"0", +"1", +"1" +] +}, +"cumsum1": { +"dtype": "uint64", +"shape": [ +2, +3 +], +"data": [ +"0", +"1", +"8", +"4294967295", +"4294967295", +"4294967298" +] +}, +"cumprod0": { +"dtype": "uint64", +"shape": [ +2, +3 +], +"data": [ +"0", +"1", +"7", +"0", +"0", +"21" +] +}, +"mean1": { +"dtype": "float64", +"shape": [ +2 +], +"data": [ +"2.6666666666666665", +"1431655766.0" +] +}, +"matmul": { +"dtype": "uint32", +"shape": [ +2, +2 +], +"data": [ +"50", +"21", +"21", +"10" +] +}, +"nonzero": { +"dtype": "int64", +"shape": [ +4 +], +"data": [ +"1", +"2", +"3", +"5" +] +}, +"where": { +"dtype": "uint32", +"shape": [ +6 +], +"data": [ +"1", +"3", +"7", +"4294967295", +"5", +"4" +] +}, +"astype_bool": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"false", +"true", +"true", +"true", +"false", +"true" +] +}, +"astype_int8": { +"dtype": "int8", +"shape": [ +6 +], +"data": [ +"0", +"1", +"7", +"-1", +"0", +"3" +] +}, +"astype_int16": { +"dtype": "int16", +"shape": [ +6 +], +"data": [ +"0", +"1", +"7", +"-1", +"0", +"3" +] +}, +"astype_int32": { +"dtype": "int32", +"shape": [ +6 +], +"data": [ +"0", +"1", +"7", +"-1", +"0", +"3" +] +}, +"astype_int64": { +"dtype": "int64", +"shape": [ +6 +], +"data": [ +"0", +"1", +"7", +"4294967295", +"0", +"3" +] +}, +"astype_uint8": { +"dtype": "uint8", +"shape": [ +6 +], +"data": [ +"0", +"1", +"7", +"255", +"0", +"3" +] +}, +"astype_uint16": { +"dtype": "uint16", +"shape": [ +6 +], +"data": [ +"0", +"1", +"7", +"65535", +"0", +"3" +] +}, +"astype_uint32": { +"dtype": "uint32", +"shape": [ +6 +], +"data": [ +"0", +"1", +"7", +"4294967295", +"0", +"3" +] +}, +"astype_uint64": { +"dtype": "uint64", +"shape": [ +6 +], +"data": [ +"0", +"1", +"7", +"4294967295", +"0", +"3" +] +}, +"astype_float32": { +"dtype": "float32", +"shape": [ +6 +], +"data": [ +"0.0", +"1.0", +"7.0", +"4294967296.0", +"0.0", +"3.0" +] +}, +"astype_float64": { +"dtype": "float64", +"shape": [ +6 +], +"data": [ +"0.0", +"1.0", +"7.0", +"4294967295.0", +"0.0", +"3.0" +] +}, +"astype_complex64": { +"dtype": "complex64", +"shape": [ +6 +], +"data": [ +[ +"0.0", +"0.0" +], +[ +"1.0", +"0.0" +], +[ +"7.0", +"0.0" +], +[ +"4294967296.0", +"0.0" +], +[ +"0.0", +"0.0" +], +[ +"3.0", +"0.0" +] +] +}, +"astype_complex128": { +"dtype": "complex128", +"shape": [ +6 +], +"data": [ +[ +"0.0", +"0.0" +], +[ +"1.0", +"0.0" +], +[ +"7.0", +"0.0" +], +[ +"4294967295.0", +"0.0" +], +[ +"0.0", +"0.0" +], +[ +"3.0", +"0.0" +] +] +} +}, +"uint64": { +"x": [ +"0", +"1", +"7", +"18446744073709551615", +"0", +"3" +], +"y": [ +"1", +"3", +"2", +"2", +"5", +"4" +], +"add": { +"dtype": "uint64", +"shape": [ +6 +], +"data": [ +"1", +"4", +"9", +"1", +"5", +"7" +] +}, +"sub": { +"dtype": "uint64", +"shape": [ +6 +], +"data": [ +"18446744073709551615", +"18446744073709551614", +"5", +"18446744073709551613", +"18446744073709551611", +"18446744073709551615" +] +}, +"mul": { +"dtype": "uint64", +"shape": [ +6 +], +"data": [ +"0", +"3", +"14", +"18446744073709551614", +"0", +"12" +] +}, +"div": { +"dtype": "float64", +"shape": [ +6 +], +"data": [ +"0.0", +"0.3333333333333333", +"3.5", +"9.223372036854776e+18", +"0.0", +"0.75" +] +}, +"maximum": { +"dtype": "uint64", +"shape": [ +6 +], +"data": [ +"1", +"3", +"7", +"18446744073709551615", +"5", +"4" +] +}, +"minimum": { +"dtype": "uint64", +"shape": [ +6 +], +"data": [ +"0", +"1", +"2", +"2", +"0", +"3" +] +}, +"eq": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"false", +"false", +"false", +"false", +"false", +"false" +] +}, +"ne": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"true", +"true", +"true", +"true", +"true", +"true" +] +}, +"lt": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"true", +"true", +"false", +"false", +"true", +"true" +] +}, +"le": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"true", +"true", +"false", +"false", +"true", +"true" +] +}, +"gt": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"false", +"false", +"true", +"true", +"false", +"false" +] +}, +"ge": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"false", +"false", +"true", +"true", +"false", +"false" +] +}, +"sqrt": { +"dtype": "float64", +"shape": [ +6 +], +"data": [ +"0.0", +"1.0", +"2.6457513110645907", +"4294967296.0", +"0.0", +"1.7320508075688772" +] +}, +"exp": { +"dtype": "float64", +"shape": [ +6 +], +"data": [ +"1.0", +"2.718281828459045", +"1096.6331584284585", +"inf", +"1.0", +"20.085536923187668" +] +}, +"log": { +"dtype": "float64", +"shape": [ +6 +], +"data": [ +"-inf", +"0.0", +"1.9459101490553132", +"44.3614195558365", +"-inf", +"1.0986122886681098" +] +}, +"sin": { +"dtype": "float64", +"shape": [ +6 +], +"data": [ +"0.0", +"0.8414709848078965", +"0.6569865987187891", +"0.023598509904439558", +"0.0", +"0.1411200080598672" +] +}, +"abs": { +"dtype": "uint64", +"shape": [ +6 +], +"data": [ +"0", +"1", +"7", +"18446744073709551615", +"0", +"3" +] +}, +"neg": { +"dtype": "uint64", +"shape": [ +6 +], +"data": [ +"0", +"18446744073709551615", +"18446744073709551609", +"1", +"0", +"18446744073709551613" +] +}, +"floor": { +"dtype": "uint64", +"shape": [ +6 +], +"data": [ +"0", +"1", +"7", +"18446744073709551615", +"0", +"3" +] +}, +"square": { +"dtype": "uint64", +"shape": [ +6 +], +"data": [ +"0", +"1", +"49", +"1", +"0", +"9" +] +}, +"power": { +"dtype": "float64", +"shape": [ +6 +], +"data": [ +"0.0", +"1.0", +"129.64181424216494", +"1.461501637330903e+48", +"0.0", +"15.588457268119896" +] +}, +"sum0": { +"dtype": "uint64", +"shape": [ +3 +], +"data": [ +"18446744073709551615", +"1", +"10" +] +}, +"sum1": { +"dtype": "uint64", +"shape": [ +2 +], +"data": [ +"8", +"2" +] +}, +"prod1": { +"dtype": "uint64", +"shape": [ +2 +], +"data": [ +"0", +"0" +] +}, +"max0": { +"dtype": "uint64", +"shape": [ +3 +], +"data": [ +"18446744073709551615", +"1", +"7" +] +}, +"min1": { +"dtype": "uint64", +"shape": [ +2 +], +"data": [ +"0", +"0" +] +}, +"argmax1": { +"dtype": "int64", +"shape": [ +2 +], +"data": [ +"2", +"0" +] +}, +"argmin0": { +"dtype": "int64", +"shape": [ +3 +], +"data": [ +"0", +"1", +"1" +] +}, +"cumsum1": { +"dtype": "uint64", +"shape": [ +2, +3 +], +"data": [ +"0", +"1", +"8", +"18446744073709551615", +"18446744073709551615", +"2" +] +}, +"cumprod0": { +"dtype": "uint64", +"shape": [ +2, +3 +], +"data": [ +"0", +"1", +"7", +"0", +"0", +"21" +] +}, +"mean1": { +"dtype": "float64", +"shape": [ +2 +], +"data": [ +"2.6666666666666665", +"6.148914691236517e+18" +] +}, +"matmul": { +"dtype": "uint64", +"shape": [ +2, +2 +], +"data": [ +"50", +"21", +"21", +"10" +] +}, +"nonzero": { +"dtype": "int64", +"shape": [ +4 +], +"data": [ +"1", +"2", +"3", +"5" +] +}, +"where": { +"dtype": "uint64", +"shape": [ +6 +], +"data": [ +"1", +"3", +"7", +"18446744073709551615", +"5", +"4" +] +}, +"astype_bool": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"false", +"true", +"true", +"true", +"false", +"true" +] +}, +"astype_int8": { +"dtype": "int8", +"shape": [ +6 +], +"data": [ +"0", +"1", +"7", +"-1", +"0", +"3" +] +}, +"astype_int16": { +"dtype": "int16", +"shape": [ +6 +], +"data": [ +"0", +"1", +"7", +"-1", +"0", +"3" +] +}, +"astype_int32": { +"dtype": "int32", +"shape": [ +6 +], +"data": [ +"0", +"1", +"7", +"-1", +"0", +"3" +] +}, +"astype_int64": { +"dtype": "int64", +"shape": [ +6 +], +"data": [ +"0", +"1", +"7", +"-1", +"0", +"3" +] +}, +"astype_uint8": { +"dtype": "uint8", +"shape": [ +6 +], +"data": [ +"0", +"1", +"7", +"255", +"0", +"3" +] +}, +"astype_uint16": { +"dtype": "uint16", +"shape": [ +6 +], +"data": [ +"0", +"1", +"7", +"65535", +"0", +"3" +] +}, +"astype_uint32": { +"dtype": "uint32", +"shape": [ +6 +], +"data": [ +"0", +"1", +"7", +"4294967295", +"0", +"3" +] +}, +"astype_uint64": { +"dtype": "uint64", +"shape": [ +6 +], +"data": [ +"0", +"1", +"7", +"18446744073709551615", +"0", +"3" +] +}, +"astype_float32": { +"dtype": "float32", +"shape": [ +6 +], +"data": [ +"0.0", +"1.0", +"7.0", +"1.8446744073709552e+19", +"0.0", +"3.0" +] +}, +"astype_float64": { +"dtype": "float64", +"shape": [ +6 +], +"data": [ +"0.0", +"1.0", +"7.0", +"1.8446744073709552e+19", +"0.0", +"3.0" +] +}, +"astype_complex64": { +"dtype": "complex64", +"shape": [ +6 +], +"data": [ +[ +"0.0", +"0.0" +], +[ +"1.0", +"0.0" +], +[ +"7.0", +"0.0" +], +[ +"1.8446744073709552e+19", +"0.0" +], +[ +"0.0", +"0.0" +], +[ +"3.0", +"0.0" +] +] +}, +"astype_complex128": { +"dtype": "complex128", +"shape": [ +6 +], +"data": [ +[ +"0.0", +"0.0" +], +[ +"1.0", +"0.0" +], +[ +"7.0", +"0.0" +], +[ +"1.8446744073709552e+19", +"0.0" +], +[ +"0.0", +"0.0" +], +[ +"3.0", +"0.0" +] +] +} +}, +"float32": { +"x": [ +"0.0", +"-1.5", +"2.25", +"nan", +"inf", +"-0.0" +], +"y": [ +"1.0", +"2.0", +"-0.5", +"1.0", +"3.0", +"4.0" +], +"add": { +"dtype": "float32", +"shape": [ +6 +], +"data": [ +"1.0", +"0.5", +"1.75", +"nan", +"inf", +"4.0" +] +}, +"sub": { +"dtype": "float32", +"shape": [ +6 +], +"data": [ +"-1.0", +"-3.5", +"2.75", +"nan", +"inf", +"-4.0" +] +}, +"mul": { +"dtype": "float32", +"shape": [ +6 +], +"data": [ +"0.0", +"-3.0", +"-1.125", +"nan", +"inf", +"-0.0" +] +}, +"div": { +"dtype": "float32", +"shape": [ +6 +], +"data": [ +"0.0", +"-0.75", +"-4.5", +"nan", +"inf", +"-0.0" +] +}, +"maximum": { +"dtype": "float32", +"shape": [ +6 +], +"data": [ +"1.0", +"2.0", +"2.25", +"nan", +"inf", +"4.0" +] +}, +"minimum": { +"dtype": "float32", +"shape": [ +6 +], +"data": [ +"0.0", +"-1.5", +"-0.5", +"nan", +"3.0", +"-0.0" +] +}, +"eq": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"false", +"false", +"false", +"false", +"false", +"false" +] +}, +"ne": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"true", +"true", +"true", +"true", +"true", +"true" +] +}, +"lt": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"true", +"true", +"false", +"false", +"false", +"true" +] +}, +"le": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"true", +"true", +"false", +"false", +"false", +"true" +] +}, +"gt": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"false", +"false", +"true", +"false", +"true", +"false" +] +}, +"ge": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"false", +"false", +"true", +"false", +"true", +"false" +] +}, +"sqrt": { +"dtype": "float32", +"shape": [ +6 +], +"data": [ +"0.0", +"nan", +"1.5", +"nan", +"inf", +"-0.0" +] +}, +"exp": { +"dtype": "float32", +"shape": [ +6 +], +"data": [ +"1.0", +"0.22313016653060913", +"9.487735748291016", +"nan", +"inf", +"1.0" +] +}, +"log": { +"dtype": "float32", +"shape": [ +6 +], +"data": [ +"-inf", +"nan", +"0.8109301924705505", +"nan", +"inf", +"-inf" +] +}, +"sin": { +"dtype": "float32", +"shape": [ +6 +], +"data": [ +"0.0", +"-0.9974949955940247", +"0.7780731916427612", +"nan", +"nan", +"-0.0" +] +}, +"abs": { +"dtype": "float32", +"shape": [ +6 +], +"data": [ +"0.0", +"1.5", +"2.25", +"nan", +"inf", +"0.0" +] +}, +"neg": { +"dtype": "float32", +"shape": [ +6 +], +"data": [ +"-0.0", +"1.5", +"-2.25", +"nan", +"-inf", +"0.0" +] +}, +"floor": { +"dtype": "float32", +"shape": [ +6 +], +"data": [ +"0.0", +"-2.0", +"2.0", +"nan", +"inf", +"-0.0" +] +}, +"square": { +"dtype": "float32", +"shape": [ +6 +], +"data": [ +"0.0", +"2.25", +"5.0625", +"nan", +"inf", +"0.0" +] +}, +"power": { +"dtype": "float32", +"shape": [ +6 +], +"data": [ +"0.0", +"nan", +"7.59375", +"nan", +"inf", +"0.0" +] +}, +"sum0": { +"dtype": "float32", +"shape": [ +3 +], +"data": [ +"nan", +"inf", +"2.25" +] +}, +"sum1": { +"dtype": "float32", +"shape": [ +2 +], +"data": [ +"0.75", +"nan" +] +}, +"prod1": { +"dtype": "float32", +"shape": [ +2 +], +"data": [ +"-0.0", +"nan" +] +}, +"max0": { +"dtype": "float32", +"shape": [ +3 +], +"data": [ +"nan", +"inf", +"2.25" +] +}, +"min1": { +"dtype": "float32", +"shape": [ +2 +], +"data": [ +"-1.5", +"nan" +] +}, +"argmax1": { +"dtype": "int64", +"shape": [ +2 +], +"data": [ +"2", +"0" +] +}, +"argmin0": { +"dtype": "int64", +"shape": [ +3 +], +"data": [ +"1", +"0", +"1" +] +}, +"cumsum1": { +"dtype": "float32", +"shape": [ +2, +3 +], +"data": [ +"0.0", +"-1.5", +"0.75", +"nan", +"nan", +"nan" +] +}, +"cumprod0": { +"dtype": "float32", +"shape": [ +2, +3 +], +"data": [ +"0.0", +"-1.5", +"2.25", +"nan", +"-inf", +"-0.0" +] +}, +"mean1": { +"dtype": "float32", +"shape": [ +2 +], +"data": [ +"0.25", +"nan" +] +}, +"matmul": { +"dtype": "float32", +"shape": [ +2, +2 +], +"data": [ +"7.3125", +"nan", +"nan", +"nan" +] +}, +"nonzero": { +"dtype": "int64", +"shape": [ +4 +], +"data": [ +"1", +"2", +"3", +"4" +] +}, +"where": { +"dtype": "float32", +"shape": [ +6 +], +"data": [ +"1.0", +"2.0", +"2.25", +"1.0", +"inf", +"4.0" +] +}, +"astype_bool": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"false", +"true", +"true", +"true", +"true", +"false" +] +}, +"astype_int8": { +"dtype": "int8", +"shape": [ +6 +], +"data": [ +"0", +"-1", +"2", +"0", +"-1", +"0" +] +}, +"astype_int16": { +"dtype": "int16", +"shape": [ +6 +], +"data": [ +"0", +"-1", +"2", +"0", +"-1", +"0" +] +}, +"astype_int32": { +"dtype": "int32", +"shape": [ +6 +], +"data": [ +"0", +"-1", +"2", +"0", +"2147483647", +"0" +] +}, +"astype_int64": { +"dtype": "int64", +"shape": [ +6 +], +"data": [ +"0", +"-1", +"2", +"0", +"9223372036854775807", +"0" +] +}, +"astype_uint8": { +"dtype": "uint8", +"shape": [ +6 +], +"data": [ +"0", +"255", +"2", +"0", +"255", +"0" +] +}, +"astype_uint16": { +"dtype": "uint16", +"shape": [ +6 +], +"data": [ +"0", +"65535", +"2", +"0", +"65535", +"0" +] +}, +"astype_uint32": { +"dtype": "uint32", +"shape": [ +6 +], +"data": [ +"0", +"0", +"2", +"0", +"4294967295", +"0" +] +}, +"astype_uint64": { +"dtype": "uint64", +"shape": [ +6 +], +"data": [ +"0", +"0", +"2", +"0", +"18446744073709551615", +"0" +] +}, +"astype_float32": { +"dtype": "float32", +"shape": [ +6 +], +"data": [ +"0.0", +"-1.5", +"2.25", +"nan", +"inf", +"-0.0" +] +}, +"astype_float64": { +"dtype": "float64", +"shape": [ +6 +], +"data": [ +"0.0", +"-1.5", +"2.25", +"nan", +"inf", +"-0.0" +] +}, +"astype_complex64": { +"dtype": "complex64", +"shape": [ +6 +], +"data": [ +[ +"0.0", +"0.0" +], +[ +"-1.5", +"0.0" +], +[ +"2.25", +"0.0" +], +[ +"nan", +"0.0" +], +[ +"inf", +"0.0" +], +[ +"-0.0", +"0.0" +] +] +}, +"astype_complex128": { +"dtype": "complex128", +"shape": [ +6 +], +"data": [ +[ +"0.0", +"0.0" +], +[ +"-1.5", +"0.0" +], +[ +"2.25", +"0.0" +], +[ +"nan", +"0.0" +], +[ +"inf", +"0.0" +], +[ +"-0.0", +"0.0" +] +] +} +}, +"float64": { +"x": [ +"0.0", +"-1.5", +"2.25", +"nan", +"inf", +"-0.0" +], +"y": [ +"1.0", +"2.0", +"-0.5", +"1.0", +"3.0", +"4.0" +], +"add": { +"dtype": "float64", +"shape": [ +6 +], +"data": [ +"1.0", +"0.5", +"1.75", +"nan", +"inf", +"4.0" +] +}, +"sub": { +"dtype": "float64", +"shape": [ +6 +], +"data": [ +"-1.0", +"-3.5", +"2.75", +"nan", +"inf", +"-4.0" +] +}, +"mul": { +"dtype": "float64", +"shape": [ +6 +], +"data": [ +"0.0", +"-3.0", +"-1.125", +"nan", +"inf", +"-0.0" +] +}, +"div": { +"dtype": "float64", +"shape": [ +6 +], +"data": [ +"0.0", +"-0.75", +"-4.5", +"nan", +"inf", +"-0.0" +] +}, +"maximum": { +"dtype": "float64", +"shape": [ +6 +], +"data": [ +"1.0", +"2.0", +"2.25", +"nan", +"inf", +"4.0" +] +}, +"minimum": { +"dtype": "float64", +"shape": [ +6 +], +"data": [ +"0.0", +"-1.5", +"-0.5", +"nan", +"3.0", +"-0.0" +] +}, +"eq": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"false", +"false", +"false", +"false", +"false", +"false" +] +}, +"ne": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"true", +"true", +"true", +"true", +"true", +"true" +] +}, +"lt": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"true", +"true", +"false", +"false", +"false", +"true" +] +}, +"le": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"true", +"true", +"false", +"false", +"false", +"true" +] +}, +"gt": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"false", +"false", +"true", +"false", +"true", +"false" +] +}, +"ge": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"false", +"false", +"true", +"false", +"true", +"false" +] +}, +"sqrt": { +"dtype": "float64", +"shape": [ +6 +], +"data": [ +"0.0", +"nan", +"1.5", +"nan", +"inf", +"-0.0" +] +}, +"exp": { +"dtype": "float64", +"shape": [ +6 +], +"data": [ +"1.0", +"0.22313016014842982", +"9.487735836358526", +"nan", +"inf", +"1.0" +] +}, +"log": { +"dtype": "float64", +"shape": [ +6 +], +"data": [ +"-inf", +"nan", +"0.8109302162163288", +"nan", +"inf", +"-inf" +] +}, +"sin": { +"dtype": "float64", +"shape": [ +6 +], +"data": [ +"0.0", +"-0.9974949866040544", +"0.7780731968879212", +"nan", +"nan", +"-0.0" +] +}, +"abs": { +"dtype": "float64", +"shape": [ +6 +], +"data": [ +"0.0", +"1.5", +"2.25", +"nan", +"inf", +"0.0" +] +}, +"neg": { +"dtype": "float64", +"shape": [ +6 +], +"data": [ +"-0.0", +"1.5", +"-2.25", +"nan", +"-inf", +"0.0" +] +}, +"floor": { +"dtype": "float64", +"shape": [ +6 +], +"data": [ +"0.0", +"-2.0", +"2.0", +"nan", +"inf", +"-0.0" +] +}, +"square": { +"dtype": "float64", +"shape": [ +6 +], +"data": [ +"0.0", +"2.25", +"5.0625", +"nan", +"inf", +"0.0" +] +}, +"power": { +"dtype": "float64", +"shape": [ +6 +], +"data": [ +"0.0", +"nan", +"7.59375", +"nan", +"inf", +"0.0" +] +}, +"sum0": { +"dtype": "float64", +"shape": [ +3 +], +"data": [ +"nan", +"inf", +"2.25" +] +}, +"sum1": { +"dtype": "float64", +"shape": [ +2 +], +"data": [ +"0.75", +"nan" +] +}, +"prod1": { +"dtype": "float64", +"shape": [ +2 +], +"data": [ +"-0.0", +"nan" +] +}, +"max0": { +"dtype": "float64", +"shape": [ +3 +], +"data": [ +"nan", +"inf", +"2.25" +] +}, +"min1": { +"dtype": "float64", +"shape": [ +2 +], +"data": [ +"-1.5", +"nan" +] +}, +"argmax1": { +"dtype": "int64", +"shape": [ +2 +], +"data": [ +"2", +"0" +] +}, +"argmin0": { +"dtype": "int64", +"shape": [ +3 +], +"data": [ +"1", +"0", +"1" +] +}, +"cumsum1": { +"dtype": "float64", +"shape": [ +2, +3 +], +"data": [ +"0.0", +"-1.5", +"0.75", +"nan", +"nan", +"nan" +] +}, +"cumprod0": { +"dtype": "float64", +"shape": [ +2, +3 +], +"data": [ +"0.0", +"-1.5", +"2.25", +"nan", +"-inf", +"-0.0" +] +}, +"mean1": { +"dtype": "float64", +"shape": [ +2 +], +"data": [ +"0.25", +"nan" +] +}, +"matmul": { +"dtype": "float64", +"shape": [ +2, +2 +], +"data": [ +"7.3125", +"nan", +"nan", +"nan" +] +}, +"nonzero": { +"dtype": "int64", +"shape": [ +4 +], +"data": [ +"1", +"2", +"3", +"4" +] +}, +"where": { +"dtype": "float64", +"shape": [ +6 +], +"data": [ +"1.0", +"2.0", +"2.25", +"1.0", +"inf", +"4.0" +] +}, +"astype_bool": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"false", +"true", +"true", +"true", +"true", +"false" +] +}, +"astype_int8": { +"dtype": "int8", +"shape": [ +6 +], +"data": [ +"0", +"-1", +"2", +"0", +"-1", +"0" +] +}, +"astype_int16": { +"dtype": "int16", +"shape": [ +6 +], +"data": [ +"0", +"-1", +"2", +"0", +"-1", +"0" +] +}, +"astype_int32": { +"dtype": "int32", +"shape": [ +6 +], +"data": [ +"0", +"-1", +"2", +"0", +"2147483647", +"0" +] +}, +"astype_int64": { +"dtype": "int64", +"shape": [ +6 +], +"data": [ +"0", +"-1", +"2", +"0", +"9223372036854775807", +"0" +] +}, +"astype_uint8": { +"dtype": "uint8", +"shape": [ +6 +], +"data": [ +"0", +"255", +"2", +"0", +"255", +"0" +] +}, +"astype_uint16": { +"dtype": "uint16", +"shape": [ +6 +], +"data": [ +"0", +"65535", +"2", +"0", +"65535", +"0" +] +}, +"astype_uint32": { +"dtype": "uint32", +"shape": [ +6 +], +"data": [ +"0", +"0", +"2", +"0", +"4294967295", +"0" +] +}, +"astype_uint64": { +"dtype": "uint64", +"shape": [ +6 +], +"data": [ +"0", +"0", +"2", +"0", +"18446744073709551615", +"0" +] +}, +"astype_float32": { +"dtype": "float32", +"shape": [ +6 +], +"data": [ +"0.0", +"-1.5", +"2.25", +"nan", +"inf", +"-0.0" +] +}, +"astype_float64": { +"dtype": "float64", +"shape": [ +6 +], +"data": [ +"0.0", +"-1.5", +"2.25", +"nan", +"inf", +"-0.0" +] +}, +"astype_complex64": { +"dtype": "complex64", +"shape": [ +6 +], +"data": [ +[ +"0.0", +"0.0" +], +[ +"-1.5", +"0.0" +], +[ +"2.25", +"0.0" +], +[ +"nan", +"0.0" +], +[ +"inf", +"0.0" +], +[ +"-0.0", +"0.0" +] +] +}, +"astype_complex128": { +"dtype": "complex128", +"shape": [ +6 +], +"data": [ +[ +"0.0", +"0.0" +], +[ +"-1.5", +"0.0" +], +[ +"2.25", +"0.0" +], +[ +"nan", +"0.0" +], +[ +"inf", +"0.0" +], +[ +"-0.0", +"0.0" +] +] +} +}, +"complex64": { +"x": [ +[ +"0.0", +"0.0" +], +[ +"1.5", +"-2.0" +], +[ +"-0.5", +"0.25" +], +[ +"nan", +"1.0" +], +[ +"3.0", +"4.0" +], +[ +"-2.0", +"0.0" +] +], +"y": [ +[ +"1.0", +"1.0" +], +[ +"2.0", +"0.0" +], +[ +"-0.0", +"-0.5" +], +[ +"1.0", +"0.0" +], +[ +"3.0", +"-4.0" +], +[ +"0.5", +"0.5" +] +], +"add": { +"dtype": "complex64", +"shape": [ +6 +], +"data": [ +[ +"1.0", +"1.0" +], +[ +"3.5", +"-2.0" +], +[ +"-0.5", +"-0.25" +], +[ +"nan", +"1.0" +], +[ +"6.0", +"0.0" +], +[ +"-1.5", +"0.5" +] +] +}, +"sub": { +"dtype": "complex64", +"shape": [ +6 +], +"data": [ +[ +"-1.0", +"-1.0" +], +[ +"-0.5", +"-2.0" +], +[ +"-0.5", +"0.75" +], +[ +"nan", +"1.0" +], +[ +"0.0", +"8.0" +], +[ +"-2.5", +"-0.5" +] +] +}, +"mul": { +"dtype": "complex64", +"shape": [ +6 +], +"data": [ +[ +"0.0", +"0.0" +], +[ +"3.0", +"-4.0" +], +[ +"0.125", +"0.25" +], +[ +"nan", +"nan" +], +[ +"25.0", +"0.0" +], +[ +"-1.0", +"-1.0" +] +] +}, +"div": { +"dtype": "complex64", +"shape": [ +6 +], +"data": [ +[ +"0.0", +"0.0" +], +[ +"0.75", +"-1.0" +], +[ +"-0.5", +"-1.0" +], +[ +"nan", +"nan" +], +[ +"-0.2800000011920929", +"0.9599999785423279" +], +[ +"-2.0", +"2.0" +] +] +}, +"maximum": { +"dtype": "complex64", +"shape": [ +6 +], +"data": [ +[ +"1.0", +"1.0" +], +[ +"2.0", +"0.0" +], +[ +"-0.0", +"-0.5" +], +[ +"nan", +"1.0" +], +[ +"3.0", +"4.0" +], +[ +"0.5", +"0.5" +] +] +}, +"minimum": { +"dtype": "complex64", +"shape": [ +6 +], +"data": [ +[ +"0.0", +"0.0" +], +[ +"1.5", +"-2.0" +], +[ +"-0.5", +"0.25" +], +[ +"nan", +"1.0" +], +[ +"3.0", +"-4.0" +], +[ +"-2.0", +"0.0" +] +] +}, +"eq": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"false", +"false", +"false", +"false", +"false", +"false" +] +}, +"ne": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"true", +"true", +"true", +"true", +"true", +"true" +] +}, +"lt": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"true", +"true", +"true", +"false", +"false", +"true" +] +}, +"le": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"true", +"true", +"true", +"false", +"false", +"true" +] +}, +"gt": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"false", +"false", +"false", +"false", +"true", +"false" +] +}, +"ge": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"false", +"false", +"false", +"false", +"true", +"false" +] +}, +"sqrt": { +"dtype": "complex64", +"shape": [ +6 +], +"data": [ +[ +"0.0", +"0.0" +], +[ +"1.4142135381698608", +"-0.7071067690849304" +], +[ +"0.17178037762641907", +"0.727673351764679" +], +[ +"nan", +"nan" +], +[ +"2.0", +"1.0" +], +[ +"0.0", +"1.4142135381698608" +] +] +}, +"exp": { +"dtype": "complex64", +"shape": [ +6 +], +"data": [ +[ +"1.0", +"0.0" +], +[ +"-1.8650407791137695", +"-4.075188159942627" +], +[ +"0.5876750946044922", +"0.1500580906867981" +], +[ +"nan", +"nan" +], +[ +"-13.128783226013184", +"-15.200784683227539" +], +[ +"0.1353352814912796", +"0.0" +] +] +}, +"log": { +"dtype": "complex64", +"shape": [ +6 +], +"data": [ +[ +"-inf", +"0.0" +], +[ +"0.9162907600402832", +"-0.9272952079772949" +], +[ +"-0.5815753936767578", +"2.677945137023926" +], +[ +"nan", +"nan" +], +[ +"1.6094379425048828", +"0.9272952079772949" +], +[ +"0.6931471824645996", +"3.141592502593994" +] +] +}, +"sin": { +"dtype": "complex64", +"shape": [ +6 +], +"data": [ +[ +"0.0", +"0.0" +], +[ +"3.7527713775634766", +"-0.2565539479255676" +], +[ +"-0.4944857954978943", +"0.2216881662607193" +], +[ +"nan", +"nan" +], +[ +"3.8537380695343018", +"-27.016813278198242" +], +[ +"-0.9092974066734314", +"0.0" +] +] +}, +"abs": { +"dtype": "float32", +"shape": [ +6 +], +"data": [ +"0.0", +"2.5", +"0.55901700258255", +"nan", +"5.0", +"2.0" +] +}, +"neg": { +"dtype": "complex64", +"shape": [ +6 +], +"data": [ +[ +"-0.0", +"-0.0" +], +[ +"-1.5", +"2.0" +], +[ +"0.5", +"-0.25" +], +[ +"nan", +"-1.0" +], +[ +"-3.0", +"-4.0" +], +[ +"2.0", +"-0.0" +] +] +}, +"floor": { +"error": "TypeError" +}, +"square": { +"dtype": "complex64", +"shape": [ +6 +], +"data": [ +[ +"0.0", +"0.0" +], +[ +"-1.75", +"-6.0" +], +[ +"0.1875", +"-0.25" +], +[ +"nan", +"nan" +], +[ +"-7.0", +"24.0" +], +[ +"4.0", +"-0.0" +] +] +}, +"power": { +"dtype": "complex64", +"shape": [ +6 +], +"data": [ +[ +"0.0", +"0.0" +], +[ +"-6.717514514923096", +"-7.24784517288208" +], +[ +"0.21412713825702667", +"0.09349370747804642" +], +[ +"nan", +"nan" +], +[ +"-38.0", +"41.000003814697266" +], +[ +"3.484102307993453e-06", +"5.656854152679443" +] +] +}, +"sum0": { +"dtype": "complex64", +"shape": [ +3 +], +"data": [ +[ +"nan", +"1.0" +], +[ +"4.5", +"2.0" +], +[ +"-2.5", +"0.25" +] +] +}, +"sum1": { +"dtype": "complex64", +"shape": [ +2 +], +"data": [ +[ +"1.0", +"-1.75" +], +[ +"nan", +"5.0" +] +] +}, +"prod1": { +"dtype": "complex64", +"shape": [ +2 +], +"data": [ +[ +"-0.0", +"0.0" +], +[ +"nan", +"nan" +] +] +}, +"max0": { +"dtype": "complex64", +"shape": [ +3 +], +"data": [ +[ +"nan", +"1.0" +], +[ +"3.0", +"4.0" +], +[ +"-0.5", +"0.25" +] +] +}, +"min1": { +"dtype": "complex64", +"shape": [ +2 +], +"data": [ +[ +"-0.5", +"0.25" +], +[ +"nan", +"1.0" +] +] +}, +"argmax1": { +"dtype": "int64", +"shape": [ +2 +], +"data": [ +"1", +"0" +] +}, +"argmin0": { +"dtype": "int64", +"shape": [ +3 +], +"data": [ +"1", +"0", +"1" +] +}, +"cumsum1": { +"dtype": "complex64", +"shape": [ +2, +3 +], +"data": [ +[ +"0.0", +"0.0" +], +[ +"1.5", +"-2.0" +], +[ +"1.0", +"-1.75" +], +[ +"nan", +"1.0" +], +[ +"nan", +"5.0" +], +[ +"nan", +"5.0" +] +] +}, +"cumprod0": { +"dtype": "complex64", +"shape": [ +2, +3 +], +"data": [ +[ +"0.0", +"0.0" +], +[ +"1.5", +"-2.0" +], +[ +"-0.5", +"0.25" +], +[ +"nan", +"nan" +], +[ +"12.5", +"0.0" +], +[ +"1.0", +"-0.5" +] +] +}, +"mean1": { +"dtype": "complex64", +"shape": [ +2 +], +"data": [ +[ +"0.3333333432674408", +"-0.5833333134651184" +], +[ +"nan", +"nan" +] +] +}, +"matmul": { +"dtype": "complex64", +"shape": [ +2, +2 +], +"data": [ +[ +"-1.5625", +"-6.25" +], +[ +"nan", +"nan" +], +[ +"nan", +"nan" +], +[ +"nan", +"nan" +] +] +}, +"nonzero": { +"dtype": "int64", +"shape": [ +5 +], +"data": [ +"1", +"2", +"3", +"4", +"5" +] +}, +"where": { +"dtype": "complex64", +"shape": [ +6 +], +"data": [ +[ +"1.0", +"1.0" +], +[ +"2.0", +"0.0" +], +[ +"-0.0", +"-0.5" +], +[ +"1.0", +"0.0" +], +[ +"3.0", +"4.0" +], +[ +"0.5", +"0.5" +] +] +}, +"astype_bool": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"false", +"true", +"true", +"true", +"true", +"true" +] +}, +"astype_int8": { +"dtype": "int8", +"shape": [ +6 +], +"data": [ +"0", +"1", +"0", +"0", +"3", +"-2" +] +}, +"astype_int16": { +"dtype": "int16", +"shape": [ +6 +], +"data": [ +"0", +"1", +"0", +"0", +"3", +"-2" +] +}, +"astype_int32": { +"dtype": "int32", +"shape": [ +6 +], +"data": [ +"0", +"1", +"0", +"0", +"3", +"-2" +] +}, +"astype_int64": { +"dtype": "int64", +"shape": [ +6 +], +"data": [ +"0", +"1", +"0", +"0", +"3", +"-2" +] +}, +"astype_uint8": { +"dtype": "uint8", +"shape": [ +6 +], +"data": [ +"0", +"1", +"0", +"0", +"3", +"254" +] +}, +"astype_uint16": { +"dtype": "uint16", +"shape": [ +6 +], +"data": [ +"0", +"1", +"0", +"0", +"3", +"65534" +] +}, +"astype_uint32": { +"dtype": "uint32", +"shape": [ +6 +], +"data": [ +"0", +"1", +"0", +"0", +"3", +"0" +] +}, +"astype_uint64": { +"dtype": "uint64", +"shape": [ +6 +], +"data": [ +"0", +"1", +"0", +"0", +"3", +"0" +] +}, +"astype_float32": { +"dtype": "float32", +"shape": [ +6 +], +"data": [ +"0.0", +"1.5", +"-0.5", +"nan", +"3.0", +"-2.0" +] +}, +"astype_float64": { +"dtype": "float64", +"shape": [ +6 +], +"data": [ +"0.0", +"1.5", +"-0.5", +"nan", +"3.0", +"-2.0" +] +}, +"astype_complex64": { +"dtype": "complex64", +"shape": [ +6 +], +"data": [ +[ +"0.0", +"0.0" +], +[ +"1.5", +"-2.0" +], +[ +"-0.5", +"0.25" +], +[ +"nan", +"1.0" +], +[ +"3.0", +"4.0" +], +[ +"-2.0", +"0.0" +] +] +}, +"astype_complex128": { +"dtype": "complex128", +"shape": [ +6 +], +"data": [ +[ +"0.0", +"0.0" +], +[ +"1.5", +"-2.0" +], +[ +"-0.5", +"0.25" +], +[ +"nan", +"1.0" +], +[ +"3.0", +"4.0" +], +[ +"-2.0", +"0.0" +] +] +} +}, +"complex128": { +"x": [ +[ +"0.0", +"0.0" +], +[ +"1.5", +"-2.0" +], +[ +"-0.5", +"0.25" +], +[ +"nan", +"1.0" +], +[ +"3.0", +"4.0" +], +[ +"-2.0", +"0.0" +] +], +"y": [ +[ +"1.0", +"1.0" +], +[ +"2.0", +"0.0" +], +[ +"-0.0", +"-0.5" +], +[ +"1.0", +"0.0" +], +[ +"3.0", +"-4.0" +], +[ +"0.5", +"0.5" +] +], +"add": { +"dtype": "complex128", +"shape": [ +6 +], +"data": [ +[ +"1.0", +"1.0" +], +[ +"3.5", +"-2.0" +], +[ +"-0.5", +"-0.25" +], +[ +"nan", +"1.0" +], +[ +"6.0", +"0.0" +], +[ +"-1.5", +"0.5" +] +] +}, +"sub": { +"dtype": "complex128", +"shape": [ +6 +], +"data": [ +[ +"-1.0", +"-1.0" +], +[ +"-0.5", +"-2.0" +], +[ +"-0.5", +"0.75" +], +[ +"nan", +"1.0" +], +[ +"0.0", +"8.0" +], +[ +"-2.5", +"-0.5" +] +] +}, +"mul": { +"dtype": "complex128", +"shape": [ +6 +], +"data": [ +[ +"0.0", +"0.0" +], +[ +"3.0", +"-4.0" +], +[ +"0.125", +"0.25" +], +[ +"nan", +"nan" +], +[ +"25.0", +"0.0" +], +[ +"-1.0", +"-1.0" +] +] +}, +"div": { +"dtype": "complex128", +"shape": [ +6 +], +"data": [ +[ +"0.0", +"0.0" +], +[ +"0.75", +"-1.0" +], +[ +"-0.5", +"-1.0" +], +[ +"nan", +"nan" +], +[ +"-0.28", +"0.96" +], +[ +"-2.0", +"2.0" +] +] +}, +"maximum": { +"dtype": "complex128", +"shape": [ +6 +], +"data": [ +[ +"1.0", +"1.0" +], +[ +"2.0", +"0.0" +], +[ +"-0.0", +"-0.5" +], +[ +"nan", +"1.0" +], +[ +"3.0", +"4.0" +], +[ +"0.5", +"0.5" +] +] +}, +"minimum": { +"dtype": "complex128", +"shape": [ +6 +], +"data": [ +[ +"0.0", +"0.0" +], +[ +"1.5", +"-2.0" +], +[ +"-0.5", +"0.25" +], +[ +"nan", +"1.0" +], +[ +"3.0", +"-4.0" +], +[ +"-2.0", +"0.0" +] +] +}, +"eq": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"false", +"false", +"false", +"false", +"false", +"false" +] +}, +"ne": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"true", +"true", +"true", +"true", +"true", +"true" +] +}, +"lt": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"true", +"true", +"true", +"false", +"false", +"true" +] +}, +"le": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"true", +"true", +"true", +"false", +"false", +"true" +] +}, +"gt": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"false", +"false", +"false", +"false", +"true", +"false" +] +}, +"ge": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"false", +"false", +"false", +"false", +"true", +"false" +] +}, +"sqrt": { +"dtype": "complex128", +"shape": [ +6 +], +"data": [ +[ +"0.0", +"0.0" +], +[ +"1.4142135623730951", +"-0.7071067811865475" +], +[ +"0.17178037486125622", +"0.7276733451126774" +], +[ +"nan", +"nan" +], +[ +"2.0", +"1.0" +], +[ +"0.0", +"1.4142135623730951" +] +] +}, +"exp": { +"dtype": "complex128", +"shape": [ +6 +], +"data": [ +[ +"1.0", +"0.0" +], +[ +"-1.8650407290090891", +"-4.075188339491183" +], +[ +"0.5876750903439226", +"0.15005808662216327" +], +[ +"nan", +"nan" +], +[ +"-13.128783081462158", +"-15.200784463067956" +], +[ +"0.1353352832366127", +"0.0" +] +] +}, +"log": { +"dtype": "complex128", +"shape": [ +6 +], +"data": [ +[ +"-inf", +"0.0" +], +[ +"0.9162907318741551", +"-0.9272952180016122" +], +[ +"-0.5815754049028404", +"2.677945044588987" +], +[ +"nan", +"nan" +], +[ +"1.6094379124341003", +"0.9272952180016122" +], +[ +"0.6931471805599453", +"3.141592653589793" +] +] +}, +"sin": { +"dtype": "complex128", +"shape": [ +6 +], +"data": [ +[ +"0.0", +"0.0" +], +[ +"3.752771340479298", +"-0.2565539560904818" +], +[ +"-0.494485780933195", +"0.22168816414957482" +], +[ +"nan", +"nan" +], +[ +"3.853738037919377", +"-27.016813258003932" +], +[ +"-0.9092974268256817", +"0.0" +] +] +}, +"abs": { +"dtype": "float64", +"shape": [ +6 +], +"data": [ +"0.0", +"2.5", +"0.5590169943749475", +"nan", +"5.0", +"2.0" +] +}, +"neg": { +"dtype": "complex128", +"shape": [ +6 +], +"data": [ +[ +"-0.0", +"-0.0" +], +[ +"-1.5", +"2.0" +], +[ +"0.5", +"-0.25" +], +[ +"nan", +"-1.0" +], +[ +"-3.0", +"-4.0" +], +[ +"2.0", +"-0.0" +] +] +}, +"floor": { +"error": "TypeError" +}, +"square": { +"dtype": "complex128", +"shape": [ +6 +], +"data": [ +[ +"0.0", +"0.0" +], +[ +"-1.75", +"-6.0" +], +[ +"0.1875", +"-0.25" +], +[ +"nan", +"nan" +], +[ +"-7.0", +"24.0" +], +[ +"4.0", +"-0.0" +] +] +}, +"power": { +"dtype": "complex128", +"shape": [ +6 +], +"data": [ +[ +"0.0", +"0.0" +], +[ +"-6.7175144212722", +"-7.247844507162115" +], +[ +"0.21412715656465486", +"0.09349365849331295" +], +[ +"nan", +"nan" +], +[ +"-37.99999999999998", +"41.00000000000001" +], +[ +"1.7319121124709869e-15", +"5.656854249492381" +] +] +}, +"sum0": { +"dtype": "complex128", +"shape": [ +3 +], +"data": [ +[ +"nan", +"1.0" +], +[ +"4.5", +"2.0" +], +[ +"-2.5", +"0.25" +] +] +}, +"sum1": { +"dtype": "complex128", +"shape": [ +2 +], +"data": [ +[ +"1.0", +"-1.75" +], +[ +"nan", +"5.0" +] +] +}, +"prod1": { +"dtype": "complex128", +"shape": [ +2 +], +"data": [ +[ +"-0.0", +"0.0" +], +[ +"nan", +"nan" +] +] +}, +"max0": { +"dtype": "complex128", +"shape": [ +3 +], +"data": [ +[ +"nan", +"1.0" +], +[ +"3.0", +"4.0" +], +[ +"-0.5", +"0.25" +] +] +}, +"min1": { +"dtype": "complex128", +"shape": [ +2 +], +"data": [ +[ +"-0.5", +"0.25" +], +[ +"nan", +"1.0" +] +] +}, +"argmax1": { +"dtype": "int64", +"shape": [ +2 +], +"data": [ +"1", +"0" +] +}, +"argmin0": { +"dtype": "int64", +"shape": [ +3 +], +"data": [ +"1", +"0", +"1" +] +}, +"cumsum1": { +"dtype": "complex128", +"shape": [ +2, +3 +], +"data": [ +[ +"0.0", +"0.0" +], +[ +"1.5", +"-2.0" +], +[ +"1.0", +"-1.75" +], +[ +"nan", +"1.0" +], +[ +"nan", +"5.0" +], +[ +"nan", +"5.0" +] +] +}, +"cumprod0": { +"dtype": "complex128", +"shape": [ +2, +3 +], +"data": [ +[ +"0.0", +"0.0" +], +[ +"1.5", +"-2.0" +], +[ +"-0.5", +"0.25" +], +[ +"nan", +"nan" +], +[ +"12.5", +"0.0" +], +[ +"1.0", +"-0.5" +] +] +}, +"mean1": { +"dtype": "complex128", +"shape": [ +2 +], +"data": [ +[ +"0.3333333333333333", +"-0.5833333333333333" +], +[ +"nan", +"nan" +] +] +}, +"matmul": { +"dtype": "complex128", +"shape": [ +2, +2 +], +"data": [ +[ +"-1.5625", +"-6.25" +], +[ +"nan", +"nan" +], +[ +"nan", +"nan" +], +[ +"nan", +"nan" +] +] +}, +"nonzero": { +"dtype": "int64", +"shape": [ +5 +], +"data": [ +"1", +"2", +"3", +"4", +"5" +] +}, +"where": { +"dtype": "complex128", +"shape": [ +6 +], +"data": [ +[ +"1.0", +"1.0" +], +[ +"2.0", +"0.0" +], +[ +"-0.0", +"-0.5" +], +[ +"1.0", +"0.0" +], +[ +"3.0", +"4.0" +], +[ +"0.5", +"0.5" +] +] +}, +"astype_bool": { +"dtype": "bool", +"shape": [ +6 +], +"data": [ +"false", +"true", +"true", +"true", +"true", +"true" +] +}, +"astype_int8": { +"dtype": "int8", +"shape": [ +6 +], +"data": [ +"0", +"1", +"0", +"0", +"3", +"-2" +] +}, +"astype_int16": { +"dtype": "int16", +"shape": [ +6 +], +"data": [ +"0", +"1", +"0", +"0", +"3", +"-2" +] +}, +"astype_int32": { +"dtype": "int32", +"shape": [ +6 +], +"data": [ +"0", +"1", +"0", +"0", +"3", +"-2" +] +}, +"astype_int64": { +"dtype": "int64", +"shape": [ +6 +], +"data": [ +"0", +"1", +"0", +"0", +"3", +"-2" +] +}, +"astype_uint8": { +"dtype": "uint8", +"shape": [ +6 +], +"data": [ +"0", +"1", +"0", +"0", +"3", +"254" +] +}, +"astype_uint16": { +"dtype": "uint16", +"shape": [ +6 +], +"data": [ +"0", +"1", +"0", +"0", +"3", +"65534" +] +}, +"astype_uint32": { +"dtype": "uint32", +"shape": [ +6 +], +"data": [ +"0", +"1", +"0", +"0", +"3", +"0" +] +}, +"astype_uint64": { +"dtype": "uint64", +"shape": [ +6 +], +"data": [ +"0", +"1", +"0", +"0", +"3", +"0" +] +}, +"astype_float32": { +"dtype": "float32", +"shape": [ +6 +], +"data": [ +"0.0", +"1.5", +"-0.5", +"nan", +"3.0", +"-2.0" +] +}, +"astype_float64": { +"dtype": "float64", +"shape": [ +6 +], +"data": [ +"0.0", +"1.5", +"-0.5", +"nan", +"3.0", +"-2.0" +] +}, +"astype_complex64": { +"dtype": "complex64", +"shape": [ +6 +], +"data": [ +[ +"0.0", +"0.0" +], +[ +"1.5", +"-2.0" +], +[ +"-0.5", +"0.25" +], +[ +"nan", +"1.0" +], +[ +"3.0", +"4.0" +], +[ +"-2.0", +"0.0" +] +] +}, +"astype_complex128": { +"dtype": "complex128", +"shape": [ +6 +], +"data": [ +[ +"0.0", +"0.0" +], +[ +"1.5", +"-2.0" +], +[ +"-0.5", +"0.25" +], +[ +"nan", +"1.0" +], +[ +"3.0", +"4.0" +], +[ +"-2.0", +"0.0" +] +] +} +} +} +} diff --git a/testdata/values.py b/testdata/values.py new file mode 100644 index 0000000..f9e657d --- /dev/null +++ b/testdata/values.py @@ -0,0 +1,70 @@ +# Generates testdata/values.json: `uv run --with numpy python testdata/values.py > testdata/values.json` +# Every number is a string (repr), so NaN, infinities and 64-bit integers +# survive JSON; complex numbers are [re, im]. +import json, platform, warnings +import numpy as np + +warnings.simplefilter("ignore") +DT = ['bool','int8','int16','int32','int64','uint8','uint16','uint32','uint64', + 'float32','float64','complex64','complex128'] + +def inputs(dt): + d = np.dtype(dt) + if d.kind == 'b': + return [True, False, True, False, True, True], [True, True, False, False, False, True] + if d.kind in 'iu': + info = np.iinfo(d) + x = [0, 1, 7, info.max, info.min, 3 if d.kind == 'u' else -3] + y = [1, 3, 2, 2, 5, 4] + return x, y + if d.kind == 'f': + return [0.0, -1.5, 2.25, float('nan'), float('inf'), -0.0], [1.0, 2.0, -0.5, 1.0, 3.0, 4.0] + return [0j, 1.5-2j, -0.5+0.25j, complex('nan+1j'), 3+4j, -2-0j], [1+1j, 2+0j, -0.5j, 1+0j, 3-4j, 0.5+0.5j] + +def enc(v): + v = np.asarray(v) + if v.dtype.kind == 'c': + return [[repr(float(z.real)), repr(float(z.imag))] for z in v.ravel()] + if v.dtype.kind == 'b': + return [str(bool(z)).lower() for z in v.ravel()] + if v.dtype.kind in 'iu': + return [str(int(z)) for z in v.ravel()] + return [repr(float(z)) for z in v.ravel()] + +def rec(f): + try: + r = np.asarray(f()) + return {"dtype": str(r.dtype), "shape": list(r.shape), "data": enc(r)} + except Exception as e: + return {"error": type(e).__name__} + +out = {"numpy": np.__version__, "machine": platform.machine(), "cases": {}} +for dt in DT: + xs, ys = inputs(dt) + x = np.array(xs, dtype=dt); y = np.array(ys, dtype=dt) + c = {"x": enc(x), "y": enc(y)} + m = np.array(xs, dtype=dt).reshape(2, 3) + ops = { + "add": lambda: x + y, "sub": lambda: x - y, "mul": lambda: x * y, "div": lambda: x / y, + "maximum": lambda: np.maximum(x, y), "minimum": lambda: np.minimum(x, y), + "eq": lambda: x == y, "ne": lambda: x != y, "lt": lambda: x < y, "le": lambda: x <= y, + "gt": lambda: x > y, "ge": lambda: x >= y, + "sqrt": lambda: np.sqrt(x), "exp": lambda: np.exp(x), "log": lambda: np.log(x), + "sin": lambda: np.sin(x), "abs": lambda: np.abs(x), "neg": lambda: -x, + "floor": lambda: np.floor(x), "square": lambda: np.square(x), + "power": lambda: np.power(x, 2.5), + "sum0": lambda: m.sum(axis=0), "sum1": lambda: m.sum(axis=1), "prod1": lambda: m.prod(axis=1), + "max0": lambda: m.max(axis=0), "min1": lambda: m.min(axis=1), + "argmax1": lambda: m.argmax(axis=1), "argmin0": lambda: m.argmin(axis=0), + "cumsum1": lambda: np.cumsum(m, axis=1), "cumprod0": lambda: np.cumprod(m, axis=0), + "mean1": lambda: m.mean(axis=1), + "matmul": lambda: m @ m.T, + "nonzero": lambda: np.flatnonzero(x), + "where": lambda: np.where(x > y, x, y) if dt != 'bool' else np.where(x, x, y), + } + for to in DT: + ops["astype_" + to] = (lambda to=to: x.astype(to)) + for k, f in ops.items(): + c[k] = rec(f) + out["cases"][dt] = c +print(json.dumps(out, indent=0)) diff --git a/typed.go b/typed.go new file mode 100644 index 0000000..33d9e4e --- /dev/null +++ b/typed.go @@ -0,0 +1,533 @@ +package ndarray + +import ( + "math" + "math/cmplx" +) + +// Inner loops for the dtypes other than Float64. Each takes contiguous storage +// slices of one element type, already converted to the result dtype and +// broadcast to the result shape; the operation code is switched on once, so +// every loop is a tight loop over one element type. The generated +// zz_dtypes_gen.go picks the instantiation from the storage type. + +// binCode selects a binary elementwise operation. +type binCode uint8 + +const ( + opAdd binCode = iota + opSub + opMul + opDiv + opMax + opMin +) + +// cmpCode selects a comparison. +type cmpCode uint8 + +const ( + opEq cmpCode = iota + opNe + opLt + opLe + opGt + opGe +) + +// binNum applies an arithmetic operation. opDiv is only used for inexact +// types: integer division is true division, done in a float type. +func binNum[T numT](op binCode, d, x, y []T) { + switch op { + case opAdd: + for i := range d { + d[i] = x[i] + y[i] + } + case opSub: + for i := range d { + d[i] = x[i] - y[i] + } + case opMul: + for i := range d { + d[i] = x[i] * y[i] + } + case opDiv: + for i := range d { + d[i] = x[i] / y[i] + } + } +} + +// binInt applies an arithmetic or max/min operation to integers. +func binInt[T intT](op binCode, d, x, y []T) { + switch op { + case opMax: + for i := range d { + d[i] = max(x[i], y[i]) + } + case opMin: + for i := range d { + d[i] = min(x[i], y[i]) + } + default: + binNum(op, d, x, y) + } +} + +// binFloat applies an arithmetic or max/min operation to floats. Max and min +// propagate NaN, as NumPy's maximum and minimum do (and Go's builtins). +func binFloat[T floatT](op binCode, d, x, y []T) { + switch op { + case opMax: + for i := range d { + d[i] = max(x[i], y[i]) + } + case opMin: + for i := range d { + d[i] = min(x[i], y[i]) + } + default: + binNum(op, d, x, y) + } +} + +// binComplex applies an arithmetic or max/min operation to complex numbers. +// Max and min order lexicographically, real part first, and propagate NaN, as +// NumPy does. +func binComplex[T complexT](op binCode, d, x, y []T) { + switch op { + case opMax: + for i := range d { + d[i] = pickComplex(x[i], y[i], true) + } + case opMin: + for i := range d { + d[i] = pickComplex(x[i], y[i], false) + } + default: + binNum(op, d, x, y) + } +} + +// pickComplex returns the larger (or smaller) of x and y in NumPy's +// lexicographic order; a NaN in either gives that operand, x first. +func pickComplex[T complexT](x, y T, larger bool) T { + xc, yc := complex128(x), complex128(y) + if cmplx.IsNaN(xc) { + return x + } + if cmplx.IsNaN(yc) { + return y + } + if lessComplex(xc, yc) == larger { + return y + } + return x +} + +// lessComplex is NumPy's lexicographic order on complex numbers. +func lessComplex(x, y complex128) bool { + return real(x) < real(y) || (real(x) == real(y) && imag(x) < imag(y)) +} + +// binBool applies an operation to booleans: NumPy's add and maximum are "or", +// multiply and minimum are "and". Subtraction is an error before this is +// reached, and division converts to a float type first. +func binBool(op binCode, d, x, y []bool) { + switch op { + case opAdd, opMax: + for i := range d { + d[i] = x[i] || y[i] + } + case opMul, opMin: + for i := range d { + d[i] = x[i] && y[i] + } + } +} + +// cmpOrdered compares real values. +func cmpOrdered[T realT](op cmpCode, d []bool, x, y []T) { + switch op { + case opEq: + for i := range d { + d[i] = x[i] == y[i] + } + case opNe: + for i := range d { + d[i] = x[i] != y[i] + } + case opLt: + for i := range d { + d[i] = x[i] < y[i] + } + case opLe: + for i := range d { + d[i] = x[i] <= y[i] + } + case opGt: + for i := range d { + d[i] = x[i] > y[i] + } + case opGe: + for i := range d { + d[i] = x[i] >= y[i] + } + } +} + +// cmpComplex compares complex values: equality componentwise, order +// lexicographic as NumPy defines it (false when either side has a NaN). +func cmpComplex[T complexT](op cmpCode, d []bool, x, y []T) { + for i := range d { + a, b := complex128(x[i]), complex128(y[i]) + nan := cmplx.IsNaN(a) || cmplx.IsNaN(b) + switch op { + case opEq: + d[i] = a == b + case opNe: + d[i] = a != b + case opLt: + d[i] = !nan && lessComplex(a, b) + case opLe: + d[i] = !nan && !lessComplex(b, a) + case opGt: + d[i] = !nan && lessComplex(b, a) + case opGe: + d[i] = !nan && !lessComplex(a, b) + } + } +} + +// cmpBool compares booleans, with false < true. +func cmpBool(op cmpCode, d []bool, x, y []bool) { + for i := range d { + a, b := x[i], y[i] + switch op { + case opEq: + d[i] = a == b + case opNe: + d[i] = a != b + case opLt: + d[i] = !a && b + case opLe: + d[i] = !a || b + case opGt: + d[i] = a && !b + case opGe: + d[i] = a || !b + } + } +} + +// unaryCode selects a unary operation on an inexact type. +type unaryCode uint8 + +const ( + uSqrt unaryCode = iota + uExp + uLog + uLog2 + uLog10 + uSin + uCos + uTan + uFloor + uCeil + uRound + uSquare + uNeg + uAbs + uPow +) + +// realFuncs are the float64 functions behind the unary codes. +var realFuncs = [...]func(float64) float64{ + uSqrt: math.Sqrt, uExp: math.Exp, uLog: math.Log, uLog2: log2, + uLog10: math.Log10, uSin: math.Sin, uCos: math.Cos, uTan: math.Tan, + uFloor: math.Floor, uCeil: math.Ceil, uRound: math.Round, + uSquare: func(x float64) float64 { return x * x }, + uNeg: func(x float64) float64 { return -x }, + uAbs: math.Abs, +} + +// complexFuncs are the complex128 functions behind the unary codes; nil where +// NumPy does not define the operation on complex numbers. +var complexFuncs = [...]func(complex128) complex128{ + uSqrt: cmplx.Sqrt, uExp: cmplx.Exp, uLog: cmplx.Log, + uLog2: func(z complex128) complex128 { return cmplx.Log(z) / math.Ln2 }, + uLog10: cmplx.Log10, uSin: cmplx.Sin, uCos: cmplx.Cos, uTan: cmplx.Tan, + uFloor: nil, uCeil: nil, + uRound: func(z complex128) complex128 { return complex(math.Round(real(z)), math.Round(imag(z))) }, + uSquare: func(z complex128) complex128 { return z * z }, + uNeg: func(z complex128) complex128 { return -z }, + uAbs: nil, // complex abs is real-valued: see absStore +} + +// unaryFloat32 applies f, computed in float64 and rounded, to float32 +// elements. For the correctly rounded operations (sqrt, floor, ceil, round, +// negation, abs, square) this is exactly the float32 result: float64 carries +// more than twice float32's precision, so the second rounding cannot change it. +func unaryFloat32(f func(float64) float64, d, x []float32) { + for i := range d { + d[i] = float32(f(float64(x[i]))) + } +} + +// unaryComplex applies f to complex elements, computing complex64 in +// complex128. +func unaryComplex[T complexT](f func(complex128) complex128, d, x []T) { + for i := range d { + d[i] = T(f(complex128(x[i]))) + } +} + +// powFloat32 raises float32 elements to p, computed in float64. +func powFloat32(p float64, d, x []float32) { + for i := range d { + d[i] = float32(math.Pow(float64(x[i]), p)) + } +} + +// powComplex raises complex elements to p. +func powComplex[T complexT](p complex128, d, x []T) { + for i := range d { + d[i] = T(cmplx.Pow(complex128(x[i]), p)) + } +} + +// negInt negates integers, wrapping as Go and NumPy do. +func negInt[T intT](d, x []T) { + for i := range d { + d[i] = -x[i] + } +} + +// absInt is the absolute value of integers; the minimum of a signed type is +// its own absolute value, as in NumPy. +func absInt[T intT](d, x []T) { + for i := range d { + v := x[i] + if v < 0 { + v = -v + } + d[i] = v + } +} + +// squareInt squares integers, wrapping. +func squareInt[T intT](d, x []T) { + for i := range d { + d[i] = x[i] * x[i] + } +} + +// absComplex is the modulus of complex elements, into the real type of the +// same precision. +func absComplex[T complexT, R floatT](d []R, x []T) { + for i := range d { + d[i] = R(cmplx.Abs(complex128(x[i]))) + } +} + +// Reductions over the [outer][axisLen][inner] layout the axis reductions use: +// dst[o*inner+j] combines src[(o*axisLen+k)*inner+j] over k. axisLen > 0. + +// reduceCode selects a reduction. +type reduceCode uint8 + +const ( + rSum reduceCode = iota + rProd + rMax + rMin +) + +// reduceNum sums or multiplies along the axis. +func reduceNum[T numT](op reduceCode, dst, src []T, outer, axisLen, inner int) { + for o := 0; o < outer; o++ { + row := dst[o*inner : (o+1)*inner] + copy(row, src[o*axisLen*inner:(o*axisLen+1)*inner]) + for k := 1; k < axisLen; k++ { + s := src[(o*axisLen+k)*inner : (o*axisLen+k+1)*inner] + if op == rSum { + for j := range row { + row[j] += s[j] + } + } else { + for j := range row { + row[j] *= s[j] + } + } + } + } +} + +// reduceOrdered takes the maximum or minimum along the axis; for floats a NaN +// wins, as in NumPy. +func reduceOrdered[T realT](op reduceCode, dst, src []T, outer, axisLen, inner int) { + for o := 0; o < outer; o++ { + row := dst[o*inner : (o+1)*inner] + copy(row, src[o*axisLen*inner:(o*axisLen+1)*inner]) + for k := 1; k < axisLen; k++ { + s := src[(o*axisLen+k)*inner : (o*axisLen+k+1)*inner] + if op == rMax { + for j := range row { + row[j] = max(row[j], s[j]) + } + } else { + for j := range row { + row[j] = min(row[j], s[j]) + } + } + } + } +} + +// reduceComplex takes the lexicographic maximum or minimum along the axis. +func reduceComplex[T complexT](op reduceCode, dst, src []T, outer, axisLen, inner int) { + for o := 0; o < outer; o++ { + row := dst[o*inner : (o+1)*inner] + copy(row, src[o*axisLen*inner:(o*axisLen+1)*inner]) + for k := 1; k < axisLen; k++ { + s := src[(o*axisLen+k)*inner : (o*axisLen+k+1)*inner] + for j := range row { + row[j] = pickComplex(row[j], s[j], op == rMax) + } + } + } +} + +// reduceBool is "any" for the maximum and "all" for the minimum. +func reduceBool(op reduceCode, dst, src []bool, outer, axisLen, inner int) { + for o := 0; o < outer; o++ { + row := dst[o*inner : (o+1)*inner] + copy(row, src[o*axisLen*inner:(o*axisLen+1)*inner]) + for k := 1; k < axisLen; k++ { + s := src[(o*axisLen+k)*inner : (o*axisLen+k+1)*inner] + for j := range row { + if op == rMax { + row[j] = row[j] || s[j] + } else { + row[j] = row[j] && s[j] + } + } + } + } +} + +// argOrdered writes the index of the first maximum (or minimum) along the +// axis; for floats the first NaN wins, as in NumPy. +func argOrdered[T realT](larger bool, dst []int64, src []T, outer, axisLen, inner int) { + for o := 0; o < outer; o++ { + for j := 0; j < inner; j++ { + best, bi := src[o*axisLen*inner+j], 0 + for k := 1; k < axisLen && best == best; k++ { + v := src[(o*axisLen+k)*inner+j] + if v != v || (larger && v > best) || (!larger && v < best) { + best, bi = v, k + } + } + dst[o*inner+j] = int64(bi) + } + } +} + +// argComplex is argOrdered in NumPy's lexicographic complex order. +func argComplex[T complexT](larger bool, dst []int64, src []T, outer, axisLen, inner int) { + for o := 0; o < outer; o++ { + for j := 0; j < inner; j++ { + best, bi := complex128(src[o*axisLen*inner+j]), 0 + for k := 1; k < axisLen && !cmplx.IsNaN(best); k++ { + v := complex128(src[(o*axisLen+k)*inner+j]) + if cmplx.IsNaN(v) || (larger && lessComplex(best, v)) || (!larger && lessComplex(v, best)) { + best, bi = v, k + } + } + dst[o*inner+j] = int64(bi) + } + } +} + +// argBool is argOrdered with false < true. +func argBool(larger bool, dst []int64, src []bool, outer, axisLen, inner int) { + for o := 0; o < outer; o++ { + for j := 0; j < inner; j++ { + bi := 0 + for k := 0; k < axisLen; k++ { + if src[(o*axisLen+k)*inner+j] == larger { + bi = k + break + } + } + dst[o*inner+j] = int64(bi) + } + } +} + +// scanNum is the cumulative sum or product along the axis; dst has src's +// layout. +func scanNum[T numT](op reduceCode, dst, src []T, outer, axisLen, inner int) { + for o := 0; o < outer; o++ { + base := o * axisLen * inner + copy(dst[base:base+inner], src[base:base+inner]) + for k := 1; k < axisLen; k++ { + prev := dst[base+(k-1)*inner : base+k*inner] + cur := dst[base+k*inner : base+(k+1)*inner] + s := src[base+k*inner : base+(k+1)*inner] + for j := range cur { + if op == rSum { + cur[j] = prev[j] + s[j] + } else { + cur[j] = prev[j] * s[j] + } + } + } + } +} + +// clipOrdered limits real elements to [lo, hi]; NaN stays NaN, as in NumPy. +func clipOrdered[T realT](d, x []T, lo, hi T) { + for i := range d { + d[i] = min(max(x[i], lo), hi) + } +} + +// whereStore picks t where c is true and f otherwise. +func whereT[T elemT](d []T, c []bool, t, f []T) { + for i := range d { + if c[i] { + d[i] = t[i] + } else { + d[i] = f[i] + } + } +} + +// matmulNum multiplies an (m x k) by a (k x n) row-major matrix into d. +func matmulNum[T numT](d, x, y []T, m, k, n int) { + for i := 0; i < m; i++ { + row := d[i*n : (i+1)*n] + for p := 0; p < k; p++ { + xv := x[i*k+p] + yr := y[p*n : (p+1)*n] + for j := range row { + row[j] += xv * yr[j] + } + } + } +} + +// matmulBool is the matrix product over the booleans: "or" of "and"s. +func matmulBool(d, x, y []bool, m, k, n int) { + for i := 0; i < m; i++ { + for p := 0; p < k; p++ { + if !x[i*k+p] { + continue + } + for j := 0; j < n; j++ { + d[i*n+j] = d[i*n+j] || y[p*n+j] + } + } + } +} diff --git a/ufunc.go b/ufunc.go index f04e935..c17d1fc 100644 --- a/ufunc.go +++ b/ufunc.go @@ -20,6 +20,9 @@ import ( // intrinsic and the packed kernel. The result is bit-identical to a scalar // math.Sqrt loop (sqrt(-x)=NaN, sqrt(+Inf)=+Inf, matching NumPy). func (a *Array) Sqrt() *Array { + if a.dtype != Float64 { + return a.mathUnary(uSqrt, (*Array).Sqrt) + } src := a.contiguousData() dst := a.alloc(len(src), false) kernels.SqrtP(dst, src) @@ -35,6 +38,9 @@ func (a *Array) Sqrt() *Array { // routes through the same SIMD sqrt kernel as Sqrt (packed SQRTPD on amd64, the // FSQRTD-intrinsic scalar loop on arm64/others), bit-identical to math.Sqrt. func (a *Array) SqrtInto(out *Array) error { + if a.dtype != Float64 || out.dtype != Float64 { + return fmt.Errorf("%w: SqrtInto needs float64 arrays; use Sqrt", ErrDType) + } if !out.isContiguous() { return fmt.Errorf("%w: out must be contiguous", ErrBroadcast) } @@ -53,6 +59,9 @@ func (a *Array) SqrtInto(out *Array) error { // element, and finite up to ln(MaxFloat64) on amd64, where math.Exp returns // +Inf from x ~ 709.436 (golang/go#81995; Go 1.26.8 and 1.27.1). func (a *Array) Exp() *Array { + if a.dtype != Float64 { + return a.mathUnary(uExp, (*Array).Exp) + } src := a.contiguousData() dst := a.alloc(len(src), false) kernels.ExpP(dst, src) @@ -66,10 +75,20 @@ func (a *Array) Exp() *Array { // case, measured), split across cores: about twice as fast as math.Log, and // correct for subnormal inputs on amd64, where math.Log is not (log(5e-324) // gives -709.09 instead of -744.44; golang/go#56600). -func (a *Array) Log() *Array { return a.unary(kernels.LogP) } +func (a *Array) Log() *Array { + if a.dtype != Float64 { + return a.mathUnary(uLog, (*Array).Log) + } + return a.unary(kernels.LogP) +} // Log2 returns the elementwise base-2 logarithm, within 2 ULP of numpy's. -func (a *Array) Log2() *Array { return a.Map(log2) } +func (a *Array) Log2() *Array { + if a.dtype != Float64 { + return a.mathUnary(uLog2, (*Array).Log2) + } + return a.Map(log2) +} // log2 is math.Log2 without its cancellation near 1. math.Log2 computes // log(frac)/ln2 + exp with frac in [0.5, 1), so for x just above 1 it adds @@ -88,7 +107,12 @@ func log2(x float64) float64 { // Log10 returns the elementwise base-10 logarithm, log(x)*(1/ln10) as // math.Log10 computes it, on the same log as Log (so subnormals are right). -func (a *Array) Log10() *Array { return a.unary(kernels.Log10P) } +func (a *Array) Log10() *Array { + if a.dtype != Float64 { + return a.mathUnary(uLog10, (*Array).Log10) + } + return a.unary(kernels.Log10P) +} // unary applies a contiguous-slice kernel elementwise into a new array. func (a *Array) unary(kernel func(dst, src []float64)) *Array { @@ -100,59 +124,99 @@ func (a *Array) unary(kernel func(dst, src []float64)) *Array { } // Sin returns the elementwise sine (radians). -func (a *Array) Sin() *Array { return a.Map(math.Sin) } +func (a *Array) Sin() *Array { + if a.dtype != Float64 { + return a.mathUnary(uSin, (*Array).Sin) + } + return a.Map(math.Sin) +} // Cos returns the elementwise cosine (radians). -func (a *Array) Cos() *Array { return a.Map(math.Cos) } +func (a *Array) Cos() *Array { + if a.dtype != Float64 { + return a.mathUnary(uCos, (*Array).Cos) + } + return a.Map(math.Cos) +} // Tan returns the elementwise tangent (radians). -func (a *Array) Tan() *Array { return a.Map(math.Tan) } +func (a *Array) Tan() *Array { + if a.dtype != Float64 { + return a.mathUnary(uTan, (*Array).Tan) + } + return a.Map(math.Tan) +} // Floor returns the elementwise floor (greatest integer <= x). -func (a *Array) Floor() *Array { return a.Map(math.Floor) } +func (a *Array) Floor() *Array { + if a.dtype != Float64 { + return a.exactUnary(uFloor, (*Array).Floor) + } + return a.Map(math.Floor) +} // Ceil returns the elementwise ceiling (least integer >= x). -func (a *Array) Ceil() *Array { return a.Map(math.Ceil) } +func (a *Array) Ceil() *Array { + if a.dtype != Float64 { + return a.exactUnary(uCeil, (*Array).Ceil) + } + return a.Map(math.Ceil) +} // Round returns the elementwise round-half-away-from-zero, matching math.Round. -func (a *Array) Round() *Array { return a.Map(math.Round) } +func (a *Array) Round() *Array { + if a.dtype != Float64 { + return a.exactUnary(uRound, (*Array).Round) + } + return a.Map(math.Round) +} // Square returns the elementwise square x*x. -func (a *Array) Square() *Array { return a.Map(func(x float64) float64 { return x * x }) } +// Integers wrap and keep their dtype; a Bool array gives Int8, as in NumPy. +func (a *Array) Square() *Array { + if a.dtype != Float64 { + return a.intUnary(uSquare, (*Array).Square) + } + return a.Map(func(x float64) float64 { return x * x }) +} // Power returns the elementwise a**p (every element raised to p). +// +// p is a weak float: Float32 and complex arrays keep their dtype, integer +// and Bool arrays give Float64. func (a *Array) Power(p float64) *Array { + if a.dtype != Float64 { + return a.power(p) + } return a.Map(func(x float64) float64 { return math.Pow(x, p) }) } -// Comparison ufuncs. Each compares two arrays elementwise with NumPy -// broadcasting and returns a new array whose elements are 1.0 where the -// relation holds and 0.0 otherwise. (A dedicated bool dtype is a later phase; -// until then masks are float 0/1, which compose directly with the arithmetic -// ufuncs — e.g. mask.Mul(x) zeroes the unselected elements.) +// Comparison ufuncs. Each promotes both arrays to their common dtype, +// compares them elementwise with NumPy broadcasting, and returns a Bool array. +// Complex numbers order lexicographically, as in NumPy. A mask can be used +// in arithmetic directly (true is 1): mask.Mul(x) zeroes the unselected +// elements. // Equal returns the elementwise a == b mask. -func (a *Array) Equal(b *Array) (*Array, error) { return a.binOp(b, kernels.Equal) } +func (a *Array) Equal(b *Array) (*Array, error) { return a.compare(b, opEq) } // NotEqual returns the elementwise a != b mask. -func (a *Array) NotEqual(b *Array) (*Array, error) { return a.binOp(b, kernels.NotEqual) } +func (a *Array) NotEqual(b *Array) (*Array, error) { return a.compare(b, opNe) } // Greater returns the elementwise a > b mask. -func (a *Array) Greater(b *Array) (*Array, error) { return a.binOp(b, kernels.Greater) } +func (a *Array) Greater(b *Array) (*Array, error) { return a.compare(b, opGt) } // GreaterEqual returns the elementwise a >= b mask. -func (a *Array) GreaterEqual(b *Array) (*Array, error) { - return a.binOp(b, kernels.GreaterEqual) -} +func (a *Array) GreaterEqual(b *Array) (*Array, error) { return a.compare(b, opGe) } // Less returns the elementwise a < b mask. -func (a *Array) Less(b *Array) (*Array, error) { return a.binOp(b, kernels.Less) } +func (a *Array) Less(b *Array) (*Array, error) { return a.compare(b, opLt) } // LessEqual returns the elementwise a <= b mask. -func (a *Array) LessEqual(b *Array) (*Array, error) { return a.binOp(b, kernels.LessEqual) } +func (a *Array) LessEqual(b *Array) (*Array, error) { return a.compare(b, opLe) } // Maximum returns the elementwise pairwise maximum of a and b (broadcasting). -func (a *Array) Maximum(b *Array) (*Array, error) { return a.binOp(b, kernels.Maximum) } +func (a *Array) Maximum(b *Array) (*Array, error) { return a.arith(b, opMax, kernels.Maximum) } // Minimum returns the elementwise pairwise minimum of a and b (broadcasting). -func (a *Array) Minimum(b *Array) (*Array, error) { return a.binOp(b, kernels.Minimum) } +func (a *Array) Minimum(b *Array) (*Array, error) { return a.arith(b, opMin, kernels.Minimum) } diff --git a/values_test.go b/values_test.go new file mode 100644 index 0000000..54fd1e1 --- /dev/null +++ b/values_test.go @@ -0,0 +1,267 @@ +package ndarray + +import ( + _ "embed" + "encoding/json" + "fmt" + "math" + "strconv" + "strings" + "testing" +) + +// values.json holds NumPy's results for every operation below on every dtype, +// generated by testdata/values.py. Numbers are strings so that NaN, the +// infinities and 64-bit integers survive JSON; complex numbers are [re, im]. +// +//go:embed testdata/values.json +var valuesJSON []byte + +type npResult struct { + DType string `json:"dtype"` + Shape []int `json:"shape"` + Data []json.RawMessage `json:"data"` + Error string `json:"error"` +} + +type npValues struct { + NumPy, Machine string + Cases map[string]map[string]json.RawMessage +} + +// parseStore builds a storage slice of dtype dt from NumPy's encoded values. +func parseStore(t *testing.T, dt DType, raw []json.RawMessage) any { + t.Helper() + cs := make([]complex128, len(raw)) + for i, r := range raw { + var pair []string + if json.Unmarshal(r, &pair) == nil { + cs[i] = complex(parseNum(t, pair[0]), parseNum(t, pair[1])) + continue + } + var s string + if err := json.Unmarshal(r, &s); err != nil { + t.Fatal(err) + } + switch { + case s == "true" || s == "false": + cs[i] = complex(map[bool]float64{true: 1}[s == "true"], 0) + case dt.IsInteger(): + // 64-bit integers do not fit a float64: store them directly. + st := makeStore(dt, len(raw)) + for j, r := range raw { + var v string + _ = json.Unmarshal(r, &v) + if dt.IsSigned() { + n, _ := strconv.ParseInt(v, 10, 64) + convertStore(sliceAt(st, j), []int64{n}) + } else { + n, _ := strconv.ParseUint(v, 10, 64) + convertStore(sliceAt(st, j), []uint64{n}) + } + } + return st + default: + cs[i] = complex(parseNum(t, s), 0) + } + } + st := makeStore(dt, len(raw)) + convertStore(st, cs) + return st +} + +// sliceAt returns element j of a storage slice as a one-element subslice. +func sliceAt(s any, j int) any { + switch s := s.(type) { + case []int8: + return s[j : j+1] + case []int16: + return s[j : j+1] + case []int32: + return s[j : j+1] + case []int64: + return s[j : j+1] + case []uint8: + return s[j : j+1] + case []uint16: + return s[j : j+1] + case []uint32: + return s[j : j+1] + case []uint64: + return s[j : j+1] + } + panic("sliceAt: not an integer slice") +} + +func parseNum(t *testing.T, s string) float64 { + t.Helper() + f, err := strconv.ParseFloat(s, 64) + if err != nil { + t.Fatalf("parse %q: %v", s, err) + } + return f +} + +// exactOps are correctly rounded in both libraries, so they must agree to the +// bit; the others may differ by a few units in the last place. +var exactOps = map[string]bool{ + "add": true, "sub": true, "maximum": true, "minimum": true, "eq": true, "ne": true, + "lt": true, "le": true, "gt": true, "ge": true, "abs": true, "neg": true, "floor": true, + "max0": true, "min1": true, "argmax1": true, "argmin0": true, "nonzero": true, "where": true, +} + +// operations maps each fixture key to the operation under test. +func operations(x, y, m *Array) map[string]func() (*Array, error) { + ok := func(f func() *Array) func() (*Array, error) { + return func() (*Array, error) { return f(), nil } + } + ops := map[string]func() (*Array, error){ + "add": func() (*Array, error) { return x.Add(y) }, "sub": func() (*Array, error) { return x.Sub(y) }, + "mul": func() (*Array, error) { return x.Mul(y) }, "div": func() (*Array, error) { return x.Div(y) }, + "maximum": func() (*Array, error) { return x.Maximum(y) }, "minimum": func() (*Array, error) { return x.Minimum(y) }, + "eq": func() (*Array, error) { return x.Equal(y) }, "ne": func() (*Array, error) { return x.NotEqual(y) }, + "lt": func() (*Array, error) { return x.Less(y) }, "le": func() (*Array, error) { return x.LessEqual(y) }, + "gt": func() (*Array, error) { return x.Greater(y) }, "ge": func() (*Array, error) { return x.GreaterEqual(y) }, + "sqrt": ok(x.Sqrt), "exp": ok(x.Exp), "log": ok(x.Log), "sin": ok(x.Sin), "abs": ok(x.Abs), + "neg": ok(x.Neg), "floor": ok(x.Floor), "square": ok(x.Square), + "power": ok(func() *Array { return x.Power(2.5) }), + "sum0": func() (*Array, error) { return m.SumAxis(0, false) }, + "sum1": func() (*Array, error) { return m.SumAxis(1, false) }, + "prod1": func() (*Array, error) { return m.ProdAxis(1, false) }, + "max0": func() (*Array, error) { return m.MaxAxis(0, false) }, + "min1": func() (*Array, error) { return m.MinAxis(1, false) }, + "argmax1": func() (*Array, error) { return m.ArgMaxAxis(1, false) }, + "argmin0": func() (*Array, error) { return m.ArgMinAxis(0, false) }, + "cumsum1": func() (*Array, error) { return m.CumSum(1) }, + "cumprod0": func() (*Array, error) { return m.CumProd(0) }, + "mean1": func() (*Array, error) { return m.MeanAxis(1, false) }, + "matmul": func() (*Array, error) { return m.MatMul(m.Transpose()) }, + "nonzero": ok(x.Nonzero), + "where": func() (*Array, error) { + if x.dtype == Bool { + return Where(x, x, y) + } + c, _ := x.Greater(y) + return Where(c, x, y) + }, + } + for _, to := range allDTypes() { + ops["astype_"+to.String()] = ok(func() *Array { return x.AsType(to) }) + } + return ops +} + +// run calls f, turning a panic into an error. +func run(f func() (*Array, error)) (r *Array, err error) { + defer func() { + if p := recover(); p != nil { + err = fmt.Errorf("panic: %v", p) + } + }() + return f() +} + +// TestValuesMatchNumPy runs every operation on every dtype and compares the +// dtype, shape and values with NumPy's. +func TestValuesMatchNumPy(t *testing.T) { + var v npValues + if err := json.Unmarshal(valuesJSON, &v); err != nil { + t.Fatal(err) + } + checked := 0 + for _, dt := range allDTypes() { + c := v.Cases[dt.String()] + var xr, yr []json.RawMessage + _ = json.Unmarshal(c["x"], &xr) + _ = json.Unmarshal(c["y"], &yr) + x := fromStore(parseStore(t, dt, xr), []int{len(xr)}) + y := fromStore(parseStore(t, dt, yr), []int{len(yr)}) + m, _ := x.Reshape(2, 3) + for name, op := range operations(x, y, m) { + var want npResult + if err := json.Unmarshal(c[name], &want); err != nil { + t.Fatalf("%v %s: %v", dt, name, err) + } + got, err := run(op) + if want.Error != "" { + if err == nil { + t.Errorf("%v %s: got %v, NumPy raises %s", dt, name, got, want.Error) + } + continue + } + if err != nil { + t.Errorf("%v %s: %v", dt, name, err) + continue + } + wdt, _ := ParseDType(noFloat16(want.DType)) + if got.DType() != wdt || !sameShape(got.Shape(), want.Shape) { + t.Errorf("%v %s: %v %v, NumPy %s %v", dt, name, got.DType(), got.Shape(), want.DType, want.Shape) + continue + } + ws := parseStore(t, wdt, want.Data) + gs := got.contiguousStore() + for i := range storeLen(ws) { + if strings.HasPrefix(name, "astype_") && outOfRange(x, i, wdt) { + continue // NumPy leaves it undefined; see AsType + } + g, w := elemComplex(gs, i), elemComplex(ws, i) + if !closeTo(g, w, exactOps[name] || wdt.IsInteger() || wdt == Bool, want.DType) { + t.Errorf("%v %s[%d] = %v, NumPy %v", dt, name, i, elemString(gs, i), elemString(ws, i)) + } + checked++ + } + } + } + t.Logf("%d values checked against NumPy %s (%s)", checked, v.NumPy, v.Machine) +} + +// outOfRange reports whether element i of x, converted to the integer dtype +// to, is a NaN, an infinity or beyond to's range: a cast NumPy does not +// define. +func outOfRange(x *Array, i int, to DType) bool { + if !to.IsInteger() || !(x.dtype.IsFloat() || x.dtype.IsComplex()) { + return false + } + f := real(elemComplex(x.store(), i)) + if math.IsNaN(f) || math.IsInf(f, 0) { + return true + } + lo, hi := 0.0, math.Ldexp(1, 8*to.Size()) + if to.IsSigned() { + lo, hi = -hi/2, hi/2 + } + return f < lo || f >= hi +} + +// closeTo compares two values: to the bit when exact is set (NaN equal to +// NaN, and the sign of a zero counted); otherwise within 8 machine epsilons of +// the result's magnitude, eps being that of NumPy's result dtype (float16 where +// NumPy computes in half precision and this package in float32, which is the +// documented difference). The magnitude is the complex modulus, so a component +// near zero is judged against the size of the number, not its own. +func closeTo(g, w complex128, exact bool, npDType string) bool { + if cmplxIsNaN(g) || cmplxIsNaN(w) { + return cmplxIsNaN(g) == cmplxIsNaN(w) && + math.IsNaN(real(g)) == math.IsNaN(real(w)) && math.IsNaN(imag(g)) == math.IsNaN(imag(w)) + } + if exact { + return g == w && math.Signbit(real(g)) == math.Signbit(real(w)) && + math.Signbit(imag(g)) == math.Signbit(imag(w)) + } + if g == w { + return true + } + eps := 0x1p-52 + switch npDType { + case "float16": + eps = 0x1p-10 + case "float32", "complex64": + eps = 0x1p-23 + } + mag := math.Max(cabs(g), cabs(w)) + return cabs(g-w) <= 8*eps*mag +} + +func cmplxIsNaN(z complex128) bool { return math.IsNaN(real(z)) || math.IsNaN(imag(z)) } + +func cabs(z complex128) float64 { return math.Hypot(real(z), imag(z)) } diff --git a/zz_dtypes_gen.go b/zz_dtypes_gen.go new file mode 100644 index 0000000..72b5be9 --- /dev/null +++ b/zz_dtypes_gen.go @@ -0,0 +1,1549 @@ +// Code generated by gen_dtypes.go; DO NOT EDIT. + +package ndarray + +import "strconv" + +// dtypeOfStore returns the dtype of a storage slice. +func dtypeOfStore(s any) DType { + switch s.(type) { + case []bool: + return Bool + case []int8: + return Int8 + case []int16: + return Int16 + case []int32: + return Int32 + case []int64: + return Int64 + case []uint8: + return Uint8 + case []uint16: + return Uint16 + case []uint32: + return Uint32 + case []uint64: + return Uint64 + case []float32: + return Float32 + case []complex64: + return Complex64 + case []complex128: + return Complex128 + } + panic("ndarray: not a storage slice") +} + +// makeStore returns a zeroed storage slice of n elements of dtype dt. +func makeStore(dt DType, n int) any { + switch dt { + case Bool: + return make([]bool, n) + case Int8: + return make([]int8, n) + case Int16: + return make([]int16, n) + case Int32: + return make([]int32, n) + case Int64: + return make([]int64, n) + case Uint8: + return make([]uint8, n) + case Uint16: + return make([]uint16, n) + case Uint32: + return make([]uint32, n) + case Uint64: + return make([]uint64, n) + case Float32: + return make([]float32, n) + case Float64: + return make([]float64, n) + case Complex64: + return make([]complex64, n) + case Complex128: + return make([]complex128, n) + } + panic("ndarray: unknown dtype " + dt.String()) +} + +// storeLen returns the length of a storage slice. +func storeLen(s any) int { + switch s := s.(type) { + case []bool: + return len(s) + case []int8: + return len(s) + case []int16: + return len(s) + case []int32: + return len(s) + case []int64: + return len(s) + case []uint8: + return len(s) + case []uint16: + return len(s) + case []uint32: + return len(s) + case []uint64: + return len(s) + case []float32: + return len(s) + case []float64: + return len(s) + case []complex64: + return len(s) + case []complex128: + return len(s) + } + panic("ndarray: not a storage slice") +} + +// cloneStore returns a copy of a storage slice. +func cloneStore(s any) any { + switch s := s.(type) { + case []bool: + return append([]bool(nil), s...) + case []int8: + return append([]int8(nil), s...) + case []int16: + return append([]int16(nil), s...) + case []int32: + return append([]int32(nil), s...) + case []int64: + return append([]int64(nil), s...) + case []uint8: + return append([]uint8(nil), s...) + case []uint16: + return append([]uint16(nil), s...) + case []uint32: + return append([]uint32(nil), s...) + case []uint64: + return append([]uint64(nil), s...) + case []float32: + return append([]float32(nil), s...) + case []float64: + return append([]float64(nil), s...) + case []complex64: + return append([]complex64(nil), s...) + case []complex128: + return append([]complex128(nil), s...) + } + panic("ndarray: not a storage slice") +} + +// fillStore sets every element of dst to the single element of one, a +// storage slice of the same dtype. +func fillStore(dst, one any) { + switch d := dst.(type) { + case []bool: + v := one.([]bool)[0] + for i := range d { + d[i] = v + } + case []int8: + v := one.([]int8)[0] + for i := range d { + d[i] = v + } + case []int16: + v := one.([]int16)[0] + for i := range d { + d[i] = v + } + case []int32: + v := one.([]int32)[0] + for i := range d { + d[i] = v + } + case []int64: + v := one.([]int64)[0] + for i := range d { + d[i] = v + } + case []uint8: + v := one.([]uint8)[0] + for i := range d { + d[i] = v + } + case []uint16: + v := one.([]uint16)[0] + for i := range d { + d[i] = v + } + case []uint32: + v := one.([]uint32)[0] + for i := range d { + d[i] = v + } + case []uint64: + v := one.([]uint64)[0] + for i := range d { + d[i] = v + } + case []float32: + v := one.([]float32)[0] + for i := range d { + d[i] = v + } + case []float64: + v := one.([]float64)[0] + for i := range d { + d[i] = v + } + case []complex64: + v := one.([]complex64)[0] + for i := range d { + d[i] = v + } + case []complex128: + v := one.([]complex128)[0] + for i := range d { + d[i] = v + } + } +} + +// copyStore copies n elements from src[so:] to dst[do:], two storage slices +// of the same dtype. +func copyStore(dst any, do int, src any, so, n int) { + switch d := dst.(type) { + case []bool: + copy(d[do:do+n], src.([]bool)[so:so+n]) + case []int8: + copy(d[do:do+n], src.([]int8)[so:so+n]) + case []int16: + copy(d[do:do+n], src.([]int16)[so:so+n]) + case []int32: + copy(d[do:do+n], src.([]int32)[so:so+n]) + case []int64: + copy(d[do:do+n], src.([]int64)[so:so+n]) + case []uint8: + copy(d[do:do+n], src.([]uint8)[so:so+n]) + case []uint16: + copy(d[do:do+n], src.([]uint16)[so:so+n]) + case []uint32: + copy(d[do:do+n], src.([]uint32)[so:so+n]) + case []uint64: + copy(d[do:do+n], src.([]uint64)[so:so+n]) + case []float32: + copy(d[do:do+n], src.([]float32)[so:so+n]) + case []float64: + copy(d[do:do+n], src.([]float64)[so:so+n]) + case []complex64: + copy(d[do:do+n], src.([]complex64)[so:so+n]) + case []complex128: + copy(d[do:do+n], src.([]complex128)[so:so+n]) + } +} + +// takeStore returns the elements of a storage slice at the given positions. +func takeStore(s any, idx []int) any { + switch s := s.(type) { + case []bool: + out := make([]bool, len(idx)) + for k, i := range idx { + out[k] = s[i] + } + return out + case []int8: + out := make([]int8, len(idx)) + for k, i := range idx { + out[k] = s[i] + } + return out + case []int16: + out := make([]int16, len(idx)) + for k, i := range idx { + out[k] = s[i] + } + return out + case []int32: + out := make([]int32, len(idx)) + for k, i := range idx { + out[k] = s[i] + } + return out + case []int64: + out := make([]int64, len(idx)) + for k, i := range idx { + out[k] = s[i] + } + return out + case []uint8: + out := make([]uint8, len(idx)) + for k, i := range idx { + out[k] = s[i] + } + return out + case []uint16: + out := make([]uint16, len(idx)) + for k, i := range idx { + out[k] = s[i] + } + return out + case []uint32: + out := make([]uint32, len(idx)) + for k, i := range idx { + out[k] = s[i] + } + return out + case []uint64: + out := make([]uint64, len(idx)) + for k, i := range idx { + out[k] = s[i] + } + return out + case []float32: + out := make([]float32, len(idx)) + for k, i := range idx { + out[k] = s[i] + } + return out + case []float64: + out := make([]float64, len(idx)) + for k, i := range idx { + out[k] = s[i] + } + return out + case []complex64: + out := make([]complex64, len(idx)) + for k, i := range idx { + out[k] = s[i] + } + return out + case []complex128: + out := make([]complex128, len(idx)) + for k, i := range idx { + out[k] = s[i] + } + return out + } + panic("ndarray: not a storage slice") +} + +// gatherStore returns a contiguous copy of the elements of the view a. +func gatherStore(a *Array) any { + switch s := a.store().(type) { + case []bool: + return gather(s, a.shape, a.strides, a.offset) + case []int8: + return gather(s, a.shape, a.strides, a.offset) + case []int16: + return gather(s, a.shape, a.strides, a.offset) + case []int32: + return gather(s, a.shape, a.strides, a.offset) + case []int64: + return gather(s, a.shape, a.strides, a.offset) + case []uint8: + return gather(s, a.shape, a.strides, a.offset) + case []uint16: + return gather(s, a.shape, a.strides, a.offset) + case []uint32: + return gather(s, a.shape, a.strides, a.offset) + case []uint64: + return gather(s, a.shape, a.strides, a.offset) + case []float32: + return gather(s, a.shape, a.strides, a.offset) + case []complex64: + return gather(s, a.shape, a.strides, a.offset) + case []complex128: + return gather(s, a.shape, a.strides, a.offset) + } + panic("ndarray: not a storage slice") +} + +// broadcastStore returns a contiguous copy of a's elements broadcast to shape. +func broadcastStore(a *Array, shape []int) any { + es := a.broadcastStrides(shape) + switch s := a.store().(type) { + case []bool: + return gather(s, shape, es, a.offset) + case []int8: + return gather(s, shape, es, a.offset) + case []int16: + return gather(s, shape, es, a.offset) + case []int32: + return gather(s, shape, es, a.offset) + case []int64: + return gather(s, shape, es, a.offset) + case []uint8: + return gather(s, shape, es, a.offset) + case []uint16: + return gather(s, shape, es, a.offset) + case []uint32: + return gather(s, shape, es, a.offset) + case []uint64: + return gather(s, shape, es, a.offset) + case []float32: + return gather(s, shape, es, a.offset) + case []float64: + return gather(s, shape, es, a.offset) + case []complex64: + return gather(s, shape, es, a.offset) + case []complex128: + return gather(s, shape, es, a.offset) + } + panic("ndarray: not a storage slice") +} + +// convertStore converts every element of src into dst, two storage slices +// of the same length, as AsType documents. +func convertStore(dst, src any) { + switch s := src.(type) { + case []bool: + switch d := dst.(type) { + case []bool: + copy(d, s) + case []int8: + for i, v := range s { + d[i] = boolTo[int8](v) + } + case []int16: + for i, v := range s { + d[i] = boolTo[int16](v) + } + case []int32: + for i, v := range s { + d[i] = boolTo[int32](v) + } + case []int64: + for i, v := range s { + d[i] = boolTo[int64](v) + } + case []uint8: + for i, v := range s { + d[i] = boolTo[uint8](v) + } + case []uint16: + for i, v := range s { + d[i] = boolTo[uint16](v) + } + case []uint32: + for i, v := range s { + d[i] = boolTo[uint32](v) + } + case []uint64: + for i, v := range s { + d[i] = boolTo[uint64](v) + } + case []float32: + for i, v := range s { + d[i] = boolTo[float32](v) + } + case []float64: + for i, v := range s { + d[i] = boolTo[float64](v) + } + case []complex64: + for i, v := range s { + d[i] = boolTo[complex64](v) + } + case []complex128: + for i, v := range s { + d[i] = boolTo[complex128](v) + } + } + case []int8: + switch d := dst.(type) { + case []bool: + for i, v := range s { + d[i] = v != 0 + } + case []int8: + copy(d, s) + case []int16: + for i, v := range s { + d[i] = int16(v) + } + case []int32: + for i, v := range s { + d[i] = int32(v) + } + case []int64: + for i, v := range s { + d[i] = int64(v) + } + case []uint8: + for i, v := range s { + d[i] = uint8(v) + } + case []uint16: + for i, v := range s { + d[i] = uint16(v) + } + case []uint32: + for i, v := range s { + d[i] = uint32(v) + } + case []uint64: + for i, v := range s { + d[i] = uint64(v) + } + case []float32: + for i, v := range s { + d[i] = float32(v) + } + case []float64: + for i, v := range s { + d[i] = float64(v) + } + case []complex64: + for i, v := range s { + d[i] = complex64(complex(float64(v), 0)) + } + case []complex128: + for i, v := range s { + d[i] = complex128(complex(float64(v), 0)) + } + } + case []int16: + switch d := dst.(type) { + case []bool: + for i, v := range s { + d[i] = v != 0 + } + case []int8: + for i, v := range s { + d[i] = int8(v) + } + case []int16: + copy(d, s) + case []int32: + for i, v := range s { + d[i] = int32(v) + } + case []int64: + for i, v := range s { + d[i] = int64(v) + } + case []uint8: + for i, v := range s { + d[i] = uint8(v) + } + case []uint16: + for i, v := range s { + d[i] = uint16(v) + } + case []uint32: + for i, v := range s { + d[i] = uint32(v) + } + case []uint64: + for i, v := range s { + d[i] = uint64(v) + } + case []float32: + for i, v := range s { + d[i] = float32(v) + } + case []float64: + for i, v := range s { + d[i] = float64(v) + } + case []complex64: + for i, v := range s { + d[i] = complex64(complex(float64(v), 0)) + } + case []complex128: + for i, v := range s { + d[i] = complex128(complex(float64(v), 0)) + } + } + case []int32: + switch d := dst.(type) { + case []bool: + for i, v := range s { + d[i] = v != 0 + } + case []int8: + for i, v := range s { + d[i] = int8(v) + } + case []int16: + for i, v := range s { + d[i] = int16(v) + } + case []int32: + copy(d, s) + case []int64: + for i, v := range s { + d[i] = int64(v) + } + case []uint8: + for i, v := range s { + d[i] = uint8(v) + } + case []uint16: + for i, v := range s { + d[i] = uint16(v) + } + case []uint32: + for i, v := range s { + d[i] = uint32(v) + } + case []uint64: + for i, v := range s { + d[i] = uint64(v) + } + case []float32: + for i, v := range s { + d[i] = float32(v) + } + case []float64: + for i, v := range s { + d[i] = float64(v) + } + case []complex64: + for i, v := range s { + d[i] = complex64(complex(float64(v), 0)) + } + case []complex128: + for i, v := range s { + d[i] = complex128(complex(float64(v), 0)) + } + } + case []int64: + switch d := dst.(type) { + case []bool: + for i, v := range s { + d[i] = v != 0 + } + case []int8: + for i, v := range s { + d[i] = int8(v) + } + case []int16: + for i, v := range s { + d[i] = int16(v) + } + case []int32: + for i, v := range s { + d[i] = int32(v) + } + case []int64: + copy(d, s) + case []uint8: + for i, v := range s { + d[i] = uint8(v) + } + case []uint16: + for i, v := range s { + d[i] = uint16(v) + } + case []uint32: + for i, v := range s { + d[i] = uint32(v) + } + case []uint64: + for i, v := range s { + d[i] = uint64(v) + } + case []float32: + for i, v := range s { + d[i] = float32(v) + } + case []float64: + for i, v := range s { + d[i] = float64(v) + } + case []complex64: + for i, v := range s { + d[i] = complex64(complex(float64(v), 0)) + } + case []complex128: + for i, v := range s { + d[i] = complex128(complex(float64(v), 0)) + } + } + case []uint8: + switch d := dst.(type) { + case []bool: + for i, v := range s { + d[i] = v != 0 + } + case []int8: + for i, v := range s { + d[i] = int8(v) + } + case []int16: + for i, v := range s { + d[i] = int16(v) + } + case []int32: + for i, v := range s { + d[i] = int32(v) + } + case []int64: + for i, v := range s { + d[i] = int64(v) + } + case []uint8: + copy(d, s) + case []uint16: + for i, v := range s { + d[i] = uint16(v) + } + case []uint32: + for i, v := range s { + d[i] = uint32(v) + } + case []uint64: + for i, v := range s { + d[i] = uint64(v) + } + case []float32: + for i, v := range s { + d[i] = float32(v) + } + case []float64: + for i, v := range s { + d[i] = float64(v) + } + case []complex64: + for i, v := range s { + d[i] = complex64(complex(float64(v), 0)) + } + case []complex128: + for i, v := range s { + d[i] = complex128(complex(float64(v), 0)) + } + } + case []uint16: + switch d := dst.(type) { + case []bool: + for i, v := range s { + d[i] = v != 0 + } + case []int8: + for i, v := range s { + d[i] = int8(v) + } + case []int16: + for i, v := range s { + d[i] = int16(v) + } + case []int32: + for i, v := range s { + d[i] = int32(v) + } + case []int64: + for i, v := range s { + d[i] = int64(v) + } + case []uint8: + for i, v := range s { + d[i] = uint8(v) + } + case []uint16: + copy(d, s) + case []uint32: + for i, v := range s { + d[i] = uint32(v) + } + case []uint64: + for i, v := range s { + d[i] = uint64(v) + } + case []float32: + for i, v := range s { + d[i] = float32(v) + } + case []float64: + for i, v := range s { + d[i] = float64(v) + } + case []complex64: + for i, v := range s { + d[i] = complex64(complex(float64(v), 0)) + } + case []complex128: + for i, v := range s { + d[i] = complex128(complex(float64(v), 0)) + } + } + case []uint32: + switch d := dst.(type) { + case []bool: + for i, v := range s { + d[i] = v != 0 + } + case []int8: + for i, v := range s { + d[i] = int8(v) + } + case []int16: + for i, v := range s { + d[i] = int16(v) + } + case []int32: + for i, v := range s { + d[i] = int32(v) + } + case []int64: + for i, v := range s { + d[i] = int64(v) + } + case []uint8: + for i, v := range s { + d[i] = uint8(v) + } + case []uint16: + for i, v := range s { + d[i] = uint16(v) + } + case []uint32: + copy(d, s) + case []uint64: + for i, v := range s { + d[i] = uint64(v) + } + case []float32: + for i, v := range s { + d[i] = float32(v) + } + case []float64: + for i, v := range s { + d[i] = float64(v) + } + case []complex64: + for i, v := range s { + d[i] = complex64(complex(float64(v), 0)) + } + case []complex128: + for i, v := range s { + d[i] = complex128(complex(float64(v), 0)) + } + } + case []uint64: + switch d := dst.(type) { + case []bool: + for i, v := range s { + d[i] = v != 0 + } + case []int8: + for i, v := range s { + d[i] = int8(v) + } + case []int16: + for i, v := range s { + d[i] = int16(v) + } + case []int32: + for i, v := range s { + d[i] = int32(v) + } + case []int64: + for i, v := range s { + d[i] = int64(v) + } + case []uint8: + for i, v := range s { + d[i] = uint8(v) + } + case []uint16: + for i, v := range s { + d[i] = uint16(v) + } + case []uint32: + for i, v := range s { + d[i] = uint32(v) + } + case []uint64: + copy(d, s) + case []float32: + for i, v := range s { + d[i] = float32(v) + } + case []float64: + for i, v := range s { + d[i] = float64(v) + } + case []complex64: + for i, v := range s { + d[i] = complex64(complex(float64(v), 0)) + } + case []complex128: + for i, v := range s { + d[i] = complex128(complex(float64(v), 0)) + } + } + case []float32: + switch d := dst.(type) { + case []bool: + for i, v := range s { + d[i] = v != 0 + } + case []int8: + for i, v := range s { + d[i] = satInt[int8](float64(v)) + } + case []int16: + for i, v := range s { + d[i] = satInt[int16](float64(v)) + } + case []int32: + for i, v := range s { + d[i] = satInt[int32](float64(v)) + } + case []int64: + for i, v := range s { + d[i] = satInt[int64](float64(v)) + } + case []uint8: + for i, v := range s { + d[i] = satInt[uint8](float64(v)) + } + case []uint16: + for i, v := range s { + d[i] = satInt[uint16](float64(v)) + } + case []uint32: + for i, v := range s { + d[i] = satInt[uint32](float64(v)) + } + case []uint64: + for i, v := range s { + d[i] = satInt[uint64](float64(v)) + } + case []float32: + copy(d, s) + case []float64: + for i, v := range s { + d[i] = float64(v) + } + case []complex64: + for i, v := range s { + d[i] = complex64(complex(float64(v), 0)) + } + case []complex128: + for i, v := range s { + d[i] = complex128(complex(float64(v), 0)) + } + } + case []float64: + switch d := dst.(type) { + case []bool: + for i, v := range s { + d[i] = v != 0 + } + case []int8: + for i, v := range s { + d[i] = satInt[int8](float64(v)) + } + case []int16: + for i, v := range s { + d[i] = satInt[int16](float64(v)) + } + case []int32: + for i, v := range s { + d[i] = satInt[int32](float64(v)) + } + case []int64: + for i, v := range s { + d[i] = satInt[int64](float64(v)) + } + case []uint8: + for i, v := range s { + d[i] = satInt[uint8](float64(v)) + } + case []uint16: + for i, v := range s { + d[i] = satInt[uint16](float64(v)) + } + case []uint32: + for i, v := range s { + d[i] = satInt[uint32](float64(v)) + } + case []uint64: + for i, v := range s { + d[i] = satInt[uint64](float64(v)) + } + case []float32: + for i, v := range s { + d[i] = float32(v) + } + case []float64: + copy(d, s) + case []complex64: + for i, v := range s { + d[i] = complex64(complex(float64(v), 0)) + } + case []complex128: + for i, v := range s { + d[i] = complex128(complex(float64(v), 0)) + } + } + case []complex64: + switch d := dst.(type) { + case []bool: + for i, v := range s { + d[i] = v != 0 + } + case []int8: + for i, v := range s { + d[i] = satInt[int8](float64(float64(real(v)))) + } + case []int16: + for i, v := range s { + d[i] = satInt[int16](float64(float64(real(v)))) + } + case []int32: + for i, v := range s { + d[i] = satInt[int32](float64(float64(real(v)))) + } + case []int64: + for i, v := range s { + d[i] = satInt[int64](float64(float64(real(v)))) + } + case []uint8: + for i, v := range s { + d[i] = satInt[uint8](float64(float64(real(v)))) + } + case []uint16: + for i, v := range s { + d[i] = satInt[uint16](float64(float64(real(v)))) + } + case []uint32: + for i, v := range s { + d[i] = satInt[uint32](float64(float64(real(v)))) + } + case []uint64: + for i, v := range s { + d[i] = satInt[uint64](float64(float64(real(v)))) + } + case []float32: + for i, v := range s { + d[i] = float32(float64(real(v))) + } + case []float64: + for i, v := range s { + d[i] = float64(real(v)) + } + case []complex64: + copy(d, s) + case []complex128: + for i, v := range s { + d[i] = complex128(v) + } + } + case []complex128: + switch d := dst.(type) { + case []bool: + for i, v := range s { + d[i] = v != 0 + } + case []int8: + for i, v := range s { + d[i] = satInt[int8](float64(float64(real(v)))) + } + case []int16: + for i, v := range s { + d[i] = satInt[int16](float64(float64(real(v)))) + } + case []int32: + for i, v := range s { + d[i] = satInt[int32](float64(float64(real(v)))) + } + case []int64: + for i, v := range s { + d[i] = satInt[int64](float64(float64(real(v)))) + } + case []uint8: + for i, v := range s { + d[i] = satInt[uint8](float64(float64(real(v)))) + } + case []uint16: + for i, v := range s { + d[i] = satInt[uint16](float64(float64(real(v)))) + } + case []uint32: + for i, v := range s { + d[i] = satInt[uint32](float64(float64(real(v)))) + } + case []uint64: + for i, v := range s { + d[i] = satInt[uint64](float64(float64(real(v)))) + } + case []float32: + for i, v := range s { + d[i] = float32(float64(real(v))) + } + case []float64: + for i, v := range s { + d[i] = float64(real(v)) + } + case []complex64: + for i, v := range s { + d[i] = complex64(v) + } + case []complex128: + copy(d, s) + } + } +} + +// elemComplex returns element i of a storage slice as a complex128. +func elemComplex(s any, i int) complex128 { + switch s := s.(type) { + case []bool: + return boolTo[complex128](s[i]) + case []int8: + return complex128(complex(float64(s[i]), 0)) + case []int16: + return complex128(complex(float64(s[i]), 0)) + case []int32: + return complex128(complex(float64(s[i]), 0)) + case []int64: + return complex128(complex(float64(s[i]), 0)) + case []uint8: + return complex128(complex(float64(s[i]), 0)) + case []uint16: + return complex128(complex(float64(s[i]), 0)) + case []uint32: + return complex128(complex(float64(s[i]), 0)) + case []uint64: + return complex128(complex(float64(s[i]), 0)) + case []float32: + return complex128(complex(float64(s[i]), 0)) + case []float64: + return complex128(complex(float64(s[i]), 0)) + case []complex64: + return complex128(s[i]) + case []complex128: + return s[i] + } + panic("ndarray: not a storage slice") +} + +// setComplex stores v, converted, as element i of a storage slice. +func setComplex(s any, i int, v complex128) { + switch s := s.(type) { + case []bool: + s[i] = v != 0 + case []int8: + s[i] = satInt[int8](float64(float64(real(v)))) + case []int16: + s[i] = satInt[int16](float64(float64(real(v)))) + case []int32: + s[i] = satInt[int32](float64(float64(real(v)))) + case []int64: + s[i] = satInt[int64](float64(float64(real(v)))) + case []uint8: + s[i] = satInt[uint8](float64(float64(real(v)))) + case []uint16: + s[i] = satInt[uint16](float64(float64(real(v)))) + case []uint32: + s[i] = satInt[uint32](float64(float64(real(v)))) + case []uint64: + s[i] = satInt[uint64](float64(float64(real(v)))) + case []float32: + s[i] = float32(float64(real(v))) + case []float64: + s[i] = float64(real(v)) + case []complex64: + s[i] = complex64(v) + case []complex128: + s[i] = v + } +} + +// elemFloat returns element i of a storage slice of a real dtype as a float64. +func elemFloat(s any, i int) float64 { + switch s := s.(type) { + case []bool: + return boolTo[float64](s[i]) + case []int8: + return float64(s[i]) + case []int16: + return float64(s[i]) + case []int32: + return float64(s[i]) + case []int64: + return float64(s[i]) + case []uint8: + return float64(s[i]) + case []uint16: + return float64(s[i]) + case []uint32: + return float64(s[i]) + case []uint64: + return float64(s[i]) + case []float32: + return float64(s[i]) + } + panic("ndarray: complex element read as float64; use AtComplex") +} + +// setFloat stores v, converted, as element i of a storage slice. +func setFloat(s any, i int, v float64) { + switch s := s.(type) { + case []bool: + s[i] = v != 0 + case []int8: + s[i] = satInt[int8](float64(v)) + case []int16: + s[i] = satInt[int16](float64(v)) + case []int32: + s[i] = satInt[int32](float64(v)) + case []int64: + s[i] = satInt[int64](float64(v)) + case []uint8: + s[i] = satInt[uint8](float64(v)) + case []uint16: + s[i] = satInt[uint16](float64(v)) + case []uint32: + s[i] = satInt[uint32](float64(v)) + case []uint64: + s[i] = satInt[uint64](float64(v)) + case []float32: + s[i] = float32(v) + case []complex64: + s[i] = complex64(complex(float64(v), 0)) + case []complex128: + s[i] = complex128(complex(float64(v), 0)) + } +} + +// elemString formats element i of a storage slice as NumPy prints it. +func elemString(s any, i int) string { + switch s := s.(type) { + case []bool: + if s[i] { + return "true" + } + return "false" + case []int8: + return strconv.FormatInt(int64(s[i]), 10) + case []int16: + return strconv.FormatInt(int64(s[i]), 10) + case []int32: + return strconv.FormatInt(int64(s[i]), 10) + case []int64: + return strconv.FormatInt(int64(s[i]), 10) + case []uint8: + return strconv.FormatUint(uint64(s[i]), 10) + case []uint16: + return strconv.FormatUint(uint64(s[i]), 10) + case []uint32: + return strconv.FormatUint(uint64(s[i]), 10) + case []uint64: + return strconv.FormatUint(uint64(s[i]), 10) + case []float32: + return floatString(float64(s[i]), 32) + case []float64: + return floatString(float64(s[i]), 64) + case []complex64: + return complexString(complex128(s[i]), 32) + case []complex128: + return complexString(complex128(s[i]), 64) + } + panic("ndarray: not a storage slice") +} + +// arithStore applies a binary operation to storage slices of one dtype. +func arithStore(op binCode, d, x, y any) { + switch d := d.(type) { + case []int8: + binInt(op, d, x.([]int8), y.([]int8)) + case []int16: + binInt(op, d, x.([]int16), y.([]int16)) + case []int32: + binInt(op, d, x.([]int32), y.([]int32)) + case []int64: + binInt(op, d, x.([]int64), y.([]int64)) + case []uint8: + binInt(op, d, x.([]uint8), y.([]uint8)) + case []uint16: + binInt(op, d, x.([]uint16), y.([]uint16)) + case []uint32: + binInt(op, d, x.([]uint32), y.([]uint32)) + case []uint64: + binInt(op, d, x.([]uint64), y.([]uint64)) + case []float32: + binFloat(op, d, x.([]float32), y.([]float32)) + case []complex64: + binComplex(op, d, x.([]complex64), y.([]complex64)) + case []complex128: + binComplex(op, d, x.([]complex128), y.([]complex128)) + case []bool: + binBool(op, d, x.([]bool), y.([]bool)) + } +} + +// cmpStore compares storage slices of one dtype into d. +func cmpStore(op cmpCode, d []bool, x, y any) { + switch x := x.(type) { + case []int8: + cmpOrdered(op, d, x, y.([]int8)) + case []int16: + cmpOrdered(op, d, x, y.([]int16)) + case []int32: + cmpOrdered(op, d, x, y.([]int32)) + case []int64: + cmpOrdered(op, d, x, y.([]int64)) + case []uint8: + cmpOrdered(op, d, x, y.([]uint8)) + case []uint16: + cmpOrdered(op, d, x, y.([]uint16)) + case []uint32: + cmpOrdered(op, d, x, y.([]uint32)) + case []uint64: + cmpOrdered(op, d, x, y.([]uint64)) + case []float32: + cmpOrdered(op, d, x, y.([]float32)) + case []float64: + cmpOrdered(op, d, x, y.([]float64)) + case []complex64: + cmpComplex(op, d, x, y.([]complex64)) + case []complex128: + cmpComplex(op, d, x, y.([]complex128)) + case []bool: + cmpBool(op, d, x, y.([]bool)) + } +} + +// accumulateStore sums or multiplies src along the middle axis of +// [outer][axisLen][inner] into dst; both have an accumulation dtype. +func accumulateStore(op reduceCode, dst, src any, outer, axisLen, inner int) { + switch d := dst.(type) { + case []int64: + reduceNum(op, d, src.([]int64), outer, axisLen, inner) + case []uint64: + reduceNum(op, d, src.([]uint64), outer, axisLen, inner) + case []float32: + reduceNum(op, d, src.([]float32), outer, axisLen, inner) + case []complex64: + reduceNum(op, d, src.([]complex64), outer, axisLen, inner) + case []complex128: + reduceNum(op, d, src.([]complex128), outer, axisLen, inner) + } +} + +// extremeStore takes the maximum or minimum of src along the middle axis. +func extremeStore(op reduceCode, dst, src any, outer, axisLen, inner int) { + switch d := dst.(type) { + case []int8: + reduceOrdered(op, d, src.([]int8), outer, axisLen, inner) + case []int16: + reduceOrdered(op, d, src.([]int16), outer, axisLen, inner) + case []int32: + reduceOrdered(op, d, src.([]int32), outer, axisLen, inner) + case []int64: + reduceOrdered(op, d, src.([]int64), outer, axisLen, inner) + case []uint8: + reduceOrdered(op, d, src.([]uint8), outer, axisLen, inner) + case []uint16: + reduceOrdered(op, d, src.([]uint16), outer, axisLen, inner) + case []uint32: + reduceOrdered(op, d, src.([]uint32), outer, axisLen, inner) + case []uint64: + reduceOrdered(op, d, src.([]uint64), outer, axisLen, inner) + case []float32: + reduceOrdered(op, d, src.([]float32), outer, axisLen, inner) + case []complex64: + reduceComplex(op, d, src.([]complex64), outer, axisLen, inner) + case []complex128: + reduceComplex(op, d, src.([]complex128), outer, axisLen, inner) + case []bool: + reduceBool(op, d, src.([]bool), outer, axisLen, inner) + } +} + +// argStore writes the arg-max (or arg-min) along the middle axis into dst. +func argStore(larger bool, dst []int64, src any, outer, axisLen, inner int) { + switch s := src.(type) { + case []int8: + argOrdered(larger, dst, s, outer, axisLen, inner) + case []int16: + argOrdered(larger, dst, s, outer, axisLen, inner) + case []int32: + argOrdered(larger, dst, s, outer, axisLen, inner) + case []int64: + argOrdered(larger, dst, s, outer, axisLen, inner) + case []uint8: + argOrdered(larger, dst, s, outer, axisLen, inner) + case []uint16: + argOrdered(larger, dst, s, outer, axisLen, inner) + case []uint32: + argOrdered(larger, dst, s, outer, axisLen, inner) + case []uint64: + argOrdered(larger, dst, s, outer, axisLen, inner) + case []float32: + argOrdered(larger, dst, s, outer, axisLen, inner) + case []float64: + argOrdered(larger, dst, s, outer, axisLen, inner) + case []complex64: + argComplex(larger, dst, s, outer, axisLen, inner) + case []complex128: + argComplex(larger, dst, s, outer, axisLen, inner) + case []bool: + argBool(larger, dst, s, outer, axisLen, inner) + } +} + +// scanStore is the cumulative sum or product along the middle axis. +func scanStore(op reduceCode, dst, src any, outer, axisLen, inner int) { + switch d := dst.(type) { + case []int64: + scanNum(op, d, src.([]int64), outer, axisLen, inner) + case []uint64: + scanNum(op, d, src.([]uint64), outer, axisLen, inner) + case []float32: + scanNum(op, d, src.([]float32), outer, axisLen, inner) + case []complex64: + scanNum(op, d, src.([]complex64), outer, axisLen, inner) + case []complex128: + scanNum(op, d, src.([]complex128), outer, axisLen, inner) + } +} + +// whereStore picks from t where c is true and from f elsewhere. +func whereStore(d any, c []bool, t, f any) { + switch d := d.(type) { + case []bool: + whereT(d, c, t.([]bool), f.([]bool)) + case []int8: + whereT(d, c, t.([]int8), f.([]int8)) + case []int16: + whereT(d, c, t.([]int16), f.([]int16)) + case []int32: + whereT(d, c, t.([]int32), f.([]int32)) + case []int64: + whereT(d, c, t.([]int64), f.([]int64)) + case []uint8: + whereT(d, c, t.([]uint8), f.([]uint8)) + case []uint16: + whereT(d, c, t.([]uint16), f.([]uint16)) + case []uint32: + whereT(d, c, t.([]uint32), f.([]uint32)) + case []uint64: + whereT(d, c, t.([]uint64), f.([]uint64)) + case []float32: + whereT(d, c, t.([]float32), f.([]float32)) + case []float64: + whereT(d, c, t.([]float64), f.([]float64)) + case []complex64: + whereT(d, c, t.([]complex64), f.([]complex64)) + case []complex128: + whereT(d, c, t.([]complex128), f.([]complex128)) + } +} + +// intUnaryStore negates, takes the absolute value of, or squares integers. +func intUnaryStore(code unaryCode, d, x any) { + switch d := d.(type) { + case []int8: + s := x.([]int8) + switch code { + case uNeg: + negInt(d, s) + case uAbs: + absInt(d, s) + default: + squareInt(d, s) + } + case []int16: + s := x.([]int16) + switch code { + case uNeg: + negInt(d, s) + case uAbs: + absInt(d, s) + default: + squareInt(d, s) + } + case []int32: + s := x.([]int32) + switch code { + case uNeg: + negInt(d, s) + case uAbs: + absInt(d, s) + default: + squareInt(d, s) + } + case []int64: + s := x.([]int64) + switch code { + case uNeg: + negInt(d, s) + case uAbs: + absInt(d, s) + default: + squareInt(d, s) + } + case []uint8: + s := x.([]uint8) + switch code { + case uNeg: + negInt(d, s) + case uAbs: + absInt(d, s) + default: + squareInt(d, s) + } + case []uint16: + s := x.([]uint16) + switch code { + case uNeg: + negInt(d, s) + case uAbs: + absInt(d, s) + default: + squareInt(d, s) + } + case []uint32: + s := x.([]uint32) + switch code { + case uNeg: + negInt(d, s) + case uAbs: + absInt(d, s) + default: + squareInt(d, s) + } + case []uint64: + s := x.([]uint64) + switch code { + case uNeg: + negInt(d, s) + case uAbs: + absInt(d, s) + default: + squareInt(d, s) + } + } +} + +// matmulStore multiplies (m x k) by (k x n) storage slices of one dtype into d. +func matmulStore(d, x, y any, m, k, n int) { + switch d := d.(type) { + case []int8: + matmulNum(d, x.([]int8), y.([]int8), m, k, n) + case []int16: + matmulNum(d, x.([]int16), y.([]int16), m, k, n) + case []int32: + matmulNum(d, x.([]int32), y.([]int32), m, k, n) + case []int64: + matmulNum(d, x.([]int64), y.([]int64), m, k, n) + case []uint8: + matmulNum(d, x.([]uint8), y.([]uint8), m, k, n) + case []uint16: + matmulNum(d, x.([]uint16), y.([]uint16), m, k, n) + case []uint32: + matmulNum(d, x.([]uint32), y.([]uint32), m, k, n) + case []uint64: + matmulNum(d, x.([]uint64), y.([]uint64), m, k, n) + case []float32: + matmulNum(d, x.([]float32), y.([]float32), m, k, n) + case []complex64: + matmulNum(d, x.([]complex64), y.([]complex64), m, k, n) + case []complex128: + matmulNum(d, x.([]complex128), y.([]complex128), m, k, n) + case []bool: + matmulBool(d, x.([]bool), y.([]bool), m, k, n) + } +} From da372883134ab5666ff5615b32a203aeeee6afff Mon Sep 17 00:00:00 2001 From: tannevaled Date: Fri, 9 Oct 2026 20:56:02 +0200 Subject: [PATCH 3/3] style: gofmt Co-Authored-By: Claude Opus 5.5 --- internal/kernels/kernels.go | 2 -- storage.go | 4 +++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/kernels/kernels.go b/internal/kernels/kernels.go index d84134d..cb54fee 100644 --- a/internal/kernels/kernels.go +++ b/internal/kernels/kernels.go @@ -290,8 +290,6 @@ func ArgMin(a []float64) int { return bi } - - // CumSumAxis writes the cumulative sum along the middle axis into dst (same // shape as src), matching numpy.cumsum along an axis. Layout is // [outer][axisLen][inner] as for the reductions. diff --git a/storage.go b/storage.go index 3871a36..f8ecbf7 100644 --- a/storage.go +++ b/storage.go @@ -11,7 +11,9 @@ import ( // Element type sets. They list exact types, not ~T: storage is always one of // these slices, and the type switches that dispatch on it name them exactly. type ( - intT interface{ int8 | int16 | int32 | int64 | uint8 | uint16 | uint32 | uint64 } + intT interface { + int8 | int16 | int32 | int64 | uint8 | uint16 | uint32 | uint64 + } floatT interface{ float32 | float64 } complexT interface{ complex64 | complex128 } realT interface{ intT | floatT }