326 lines
10 KiB
Go
326 lines
10 KiB
Go
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package core
|
|
|
|
// The narrow element types: bool and the small integers. They exist so
|
|
// byte payloads, masks and the file formats' short integers ride in the
|
|
// width they were born in instead of widening to int64 in transit. The
|
|
// constructors here mirror the established pair: From…s copies its
|
|
// values, …FromArray takes ownership of the caller's slice. The byte
|
|
// serialisation stays deliberately Int-only: Bytes answers
|
|
// only for an Int array and FromBytes builds Int, so a narrow payload
|
|
// converts through Astype first rather than riding Bytes natively.
|
|
|
|
// FromBools builds a bool array from vals, copying them.
|
|
func FromBools(vals []bool, shape ...int) (*Array, error) {
|
|
sh, err := shapeFor(shape, len(vals))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
bools := make([]bool, len(vals))
|
|
copy(bools, vals)
|
|
return &Array{shape: sh, dt: Bool, bools: bools}, nil
|
|
}
|
|
|
|
// FromInt8s builds an int8 array from vals, copying them.
|
|
func FromInt8s(vals []int8, shape ...int) (*Array, error) {
|
|
sh, err := shapeFor(shape, len(vals))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
out := make([]int8, len(vals))
|
|
copy(out, vals)
|
|
return &Array{shape: sh, dt: Int8, i8s: out}, nil
|
|
}
|
|
|
|
// FromUint8s builds a uint8 array from vals, copying them.
|
|
func FromUint8s(vals []uint8, shape ...int) (*Array, error) {
|
|
sh, err := shapeFor(shape, len(vals))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
out := make([]uint8, len(vals))
|
|
copy(out, vals)
|
|
return &Array{shape: sh, dt: Uint8, u8s: out}, nil
|
|
}
|
|
|
|
// FromInt16s builds an int16 array from vals, copying them.
|
|
func FromInt16s(vals []int16, shape ...int) (*Array, error) {
|
|
sh, err := shapeFor(shape, len(vals))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
out := make([]int16, len(vals))
|
|
copy(out, vals)
|
|
return &Array{shape: sh, dt: Int16, i16s: out}, nil
|
|
}
|
|
|
|
// FromUint16s builds a uint16 array from vals, copying them.
|
|
func FromUint16s(vals []uint16, shape ...int) (*Array, error) {
|
|
sh, err := shapeFor(shape, len(vals))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
out := make([]uint16, len(vals))
|
|
copy(out, vals)
|
|
return &Array{shape: sh, dt: Uint16, u16s: out}, nil
|
|
}
|
|
|
|
// FromInt32s builds an int32 array from vals, copying them.
|
|
func FromInt32s(vals []int32, shape ...int) (*Array, error) {
|
|
sh, err := shapeFor(shape, len(vals))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
out := make([]int32, len(vals))
|
|
copy(out, vals)
|
|
return &Array{shape: sh, dt: Int32, i32s: out}, nil
|
|
}
|
|
|
|
// FromUint32s builds a uint32 array from vals, copying them.
|
|
func FromUint32s(vals []uint32, shape ...int) (*Array, error) {
|
|
sh, err := shapeFor(shape, len(vals))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
out := make([]uint32, len(vals))
|
|
copy(out, vals)
|
|
return &Array{shape: sh, dt: Uint32, u32s: out}, nil
|
|
}
|
|
|
|
// BoolsFromArray builds a bool array that takes ownership of vals, with
|
|
// the FloatsFromArray contract.
|
|
func BoolsFromArray(vals []bool, shape ...int) (*Array, error) {
|
|
sh, err := shapeFor(shape, len(vals))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &Array{shape: sh, dt: Bool, bools: vals}, nil
|
|
}
|
|
|
|
// Int8sFromArray builds an int8 array that takes ownership of vals,
|
|
// with the FloatsFromArray contract.
|
|
func Int8sFromArray(vals []int8, shape ...int) (*Array, error) {
|
|
sh, err := shapeFor(shape, len(vals))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &Array{shape: sh, dt: Int8, i8s: vals}, nil
|
|
}
|
|
|
|
// Uint8sFromArray builds a uint8 array that takes ownership of vals,
|
|
// with the FloatsFromArray contract.
|
|
func Uint8sFromArray(vals []uint8, shape ...int) (*Array, error) {
|
|
sh, err := shapeFor(shape, len(vals))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &Array{shape: sh, dt: Uint8, u8s: vals}, nil
|
|
}
|
|
|
|
// Int16sFromArray builds an int16 array that takes ownership of vals,
|
|
// with the FloatsFromArray contract.
|
|
func Int16sFromArray(vals []int16, shape ...int) (*Array, error) {
|
|
sh, err := shapeFor(shape, len(vals))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &Array{shape: sh, dt: Int16, i16s: vals}, nil
|
|
}
|
|
|
|
// Uint16sFromArray builds a uint16 array that takes ownership of vals,
|
|
// with the FloatsFromArray contract.
|
|
func Uint16sFromArray(vals []uint16, shape ...int) (*Array, error) {
|
|
sh, err := shapeFor(shape, len(vals))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &Array{shape: sh, dt: Uint16, u16s: vals}, nil
|
|
}
|
|
|
|
// Int32sFromArray builds an int32 array that takes ownership of vals,
|
|
// with the FloatsFromArray contract.
|
|
func Int32sFromArray(vals []int32, shape ...int) (*Array, error) {
|
|
sh, err := shapeFor(shape, len(vals))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &Array{shape: sh, dt: Int32, i32s: vals}, nil
|
|
}
|
|
|
|
// Uint32sFromArray builds a uint32 array that takes ownership of vals,
|
|
// with the FloatsFromArray contract.
|
|
func Uint32sFromArray(vals []uint32, shape ...int) (*Array, error) {
|
|
sh, err := shapeFor(shape, len(vals))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &Array{shape: sh, dt: Uint32, u32s: vals}, nil
|
|
}
|
|
|
|
// RawBools returns the bool payload with the RawFloats contract.
|
|
func (a *Array) RawBools() []bool { return a.bools }
|
|
|
|
// RawInt8s returns the int8 payload with the RawFloats contract.
|
|
func (a *Array) RawInt8s() []int8 { return a.i8s }
|
|
|
|
// RawUint8s returns the uint8 payload with the RawFloats contract.
|
|
func (a *Array) RawUint8s() []uint8 { return a.u8s }
|
|
|
|
// RawInt16s returns the int16 payload with the RawFloats contract.
|
|
func (a *Array) RawInt16s() []int16 { return a.i16s }
|
|
|
|
// RawUint16s returns the uint16 payload with the RawFloats contract.
|
|
func (a *Array) RawUint16s() []uint16 { return a.u16s }
|
|
|
|
// RawInt32s returns the int32 payload with the RawFloats contract.
|
|
func (a *Array) RawInt32s() []int32 { return a.i32s }
|
|
|
|
// RawUint32s returns the uint32 payload with the RawFloats contract.
|
|
func (a *Array) RawUint32s() []uint32 { return a.u32s }
|
|
|
|
// boolAt returns element i as a bool: the boolean payload's own value,
|
|
// and everywhere else the test against zero. A NaN compares unequal to
|
|
// zero, so a NaN element reads true, the same masked-read decision the
|
|
// int mask path makes on it.
|
|
func (a *Array) boolAt(i int) bool {
|
|
if a.strides != nil {
|
|
i = a.physIndex(i)
|
|
}
|
|
switch a.dt {
|
|
case Bool:
|
|
return a.bools[i]
|
|
case Int:
|
|
return a.ints[i] != 0
|
|
case Float16:
|
|
return a.halves[i]&0x7FFF != 0
|
|
case Float32:
|
|
return a.floats32[i] != 0
|
|
case Float:
|
|
return a.floats[i] != 0
|
|
case Complex:
|
|
return a.complexes[i] != 0
|
|
case Int8:
|
|
return a.i8s[i] != 0
|
|
case Uint8:
|
|
return a.u8s[i] != 0
|
|
case Int16:
|
|
return a.i16s[i] != 0
|
|
case Uint16:
|
|
return a.u16s[i] != 0
|
|
case Int32:
|
|
return a.i32s[i] != 0
|
|
default:
|
|
return a.u32s[i] != 0
|
|
}
|
|
}
|
|
|
|
// BoolAt returns the element at the given index as a bool: the boolean
|
|
// payload's own value, and every other dtype read against zero. It is
|
|
// the widening reader the small integer types share with the internal
|
|
// accessors; unlike IntAt and FloatAt it takes any dtype, because the
|
|
// mask question makes sense over all of them.
|
|
func BoolAt(a *Array, index ...int) (bool, error) {
|
|
off, err := flatIndex(a.shape, index)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
return a.boolAt(off), nil
|
|
}
|
|
|
|
// intClassOrder lists the integer-class dtypes in containment order,
|
|
// the axis the promotion table below is indexed by.
|
|
var intClassOrder = [...]Dtype{Bool, Int8, Uint8, Int16, Uint16, Int32, Uint32, Int}
|
|
|
|
// intClassIndex maps an integer-class dtype to its row in the promotion
|
|
// table; an unlisted ordinal lands on Int, the recorded default.
|
|
func intClassIndex(d Dtype) int {
|
|
for i, c := range intClassOrder {
|
|
if c == d {
|
|
return i
|
|
}
|
|
}
|
|
return len(intClassOrder) - 1
|
|
}
|
|
|
|
// intClass reports whether d belongs to the integer class: Bool or one
|
|
// of the integer dtypes, everything promote resolves by containment.
|
|
func intClass(d Dtype) bool {
|
|
switch d {
|
|
case Bool, Int8, Uint8, Int16, Uint16, Int32, Uint32, Int:
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// cloneArray returns a deep copy of the array: same shape, same dtype,
|
|
// own payload. The narrow element types clone here, where their payload
|
|
// fields live; cloneData keeps serving the five legacy slices to the
|
|
// callers that consume them individually.
|
|
func (a *Array) cloneArray() *Array {
|
|
out := &Array{shape: a.Shape(), dt: a.dt}
|
|
n := a.Len()
|
|
out.alloc(n)
|
|
if a.strides != nil {
|
|
for i := range n {
|
|
out.setFrom(i, a, i)
|
|
}
|
|
return out
|
|
}
|
|
switch a.dt {
|
|
case Int:
|
|
copy(out.ints, a.ints[:n])
|
|
case Float16:
|
|
copy(out.halves, a.halves[:n])
|
|
case Float32:
|
|
copy(out.floats32, a.floats32[:n])
|
|
case Float:
|
|
copy(out.floats, a.floats[:n])
|
|
case Complex:
|
|
copy(out.complexes, a.complexes[:n])
|
|
case Bool:
|
|
copy(out.bools, a.bools[:n])
|
|
case Int8:
|
|
copy(out.i8s, a.i8s[:n])
|
|
case Uint8:
|
|
copy(out.u8s, a.u8s[:n])
|
|
case Int16:
|
|
copy(out.i16s, a.i16s[:n])
|
|
case Uint16:
|
|
copy(out.u16s, a.u16s[:n])
|
|
case Int32:
|
|
copy(out.i32s, a.i32s[:n])
|
|
default:
|
|
copy(out.u32s, a.u32s[:n])
|
|
}
|
|
return out
|
|
}
|
|
|
|
// intPromote answers the smallest dtype of the integer class whose
|
|
// value range contains both operands' ranges. Mixed signedness pairs
|
|
// therefore widen instead of losing negative values: int8 with uint8
|
|
// answers int16, int16 with uint16 answers int32, int32 with uint32
|
|
// answers int. promote answers a same-dtype pair before the table is
|
|
// ever consulted, and the signed rows widen their own kind to the next
|
|
// width as the containment rule dictates; the table itself is
|
|
// symmetric.
|
|
var intPromote = [len(intClassOrder)][len(intClassOrder)]Dtype{
|
|
// Bool row: every integer dtype contains {0, 1}.
|
|
{Bool, Int8, Uint8, Int16, Uint16, Int32, Uint32, Int},
|
|
// Int8 [-128, 127]: with Uint8 needs the 16-bit signed width.
|
|
{Int8, Int16, Int16, Int16, Int32, Int32, Int, Int},
|
|
// Uint8 [0, 255]: contained by every wider dtype of either sign.
|
|
{Uint8, Int16, Uint8, Int16, Uint16, Int32, Uint32, Int},
|
|
// Int16: with Uint16 needs 32-bit signed; with Uint32 needs Int.
|
|
{Int16, Int16, Int16, Int32, Int32, Int32, Int, Int},
|
|
// Uint16 [0, 65535]: contained by Int32 and everything wider.
|
|
{Uint16, Int32, Uint16, Int32, Uint16, Int32, Uint32, Int},
|
|
// Int32: with Uint32 needs the 64-bit signed width.
|
|
{Int32, Int32, Int32, Int32, Int32, Int, Int, Int},
|
|
// Uint32 [0, 2^32-1]: contained by Int.
|
|
{Uint32, Int, Uint32, Int, Uint32, Int, Uint32, Int},
|
|
// Int contains the whole class.
|
|
{Int, Int, Int, Int, Int, Int, Int, Int},
|
|
}
|