# Arrays and indexing


<!-- WARNING: THIS FILE WAS AUTOGENERATED! DO NOT EDIT! -->

``` python
import rustymath as rm
```

Python operations run eagerly. Basic slices, reversals and transposes
share storage without writable aliases: assigning to an array never
changes another array’s values. A small view keeps its whole base
allocation alive. Use `a.compact()` to keep just its logical items, even
for a contiguous slice of a larger base. Float display rounding depends
on the dtype and does not change the values returned by `.tolist()`. For
expression fusion and richer array notation, use
[BasedPL](https://answerdotai.github.io/basedpl/).

## Construction and dtypes

`array` copies Python values or a buffer exporter into an owned array.
`dtype` selects its item type. Fill constructors take a shape; their
`_like` forms retain another array’s shape, keys and names:

``` python
temperatures = rm.array([[18., 20, 22], [16, 19, 23]], dtype='float32')
temperatures, rm.zeros_like(temperatures), rm.full((2, 3), 1)
```

    (Array([[18.0, 20.0, 22.0], [16.0, 19.0, 23.0]], dtype=float32),
     Array([[0.0, 0.0, 0.0], [0.0, 0.0, 0.0]], dtype=float32),
     Array([[1, 1, 1], [1, 1, 1]], dtype=int64))

`arange` generates values before its stop. `linspace` includes both
endpoints by default. `logspace` generates evenly spaced exponents in
the chosen base:

``` python
rm.arange(0, 6, 2), rm.linspace(0, 1, 5), rm.logspace(0, 3, 4)
```

    (Array([0, 2, 4], dtype=int64),
     Array([0.0, 0.25, 0.5, 0.75, 1.0], dtype=float64),
     Array([1.0, 10.0, 100.0, 1000.0], dtype=float64))

Dtype helpers use the same supported item types as arrays. `finfo`
reports float limits and spacing at 1; `iinfo` reports integer limits.
`result_type` gives the shared promotion rule:

``` python
rm.finfo('float32').eps, rm.iinfo('int16').max, rm.result_type('int16', 'float16'), rm.issubdtype('float32', 'floating')
```

    (1.1920928955078125e-07, 32767, 'float16', True)

## Broadcasting and conversion

Each function takes numbers, nested lists, rustymath arrays, NumPy
arrays, or any other object that exports a buffer. It returns a
`rustymath.Array`, or a Python number when every argument is a Python
number. Arguments of different shapes broadcast by aligning their
leading axes, where NumPy aligns trailing axes. An axis of length 1
stretches to match. `broadcast_to` makes this explicit and returns a
compact copy. Here a vector of two items adds one number to each row of
a 2 by 3 matrix:

``` python
rm.add([[1, 2, 3], [4, 5, 6]], [10, 20])
```

    Array([[11, 12, 13], [24, 25, 26]], dtype=int64)

Arrays of different dtypes promote to the higher kind, wide enough to
hold both. An integer array beside a float or complex array takes that
dtype, as in JAX. So an `int16` array beside a `float16` array gives
`float16`, where NumPy gives `float32`. Characters don’t mix with
numbers. An integer sum, difference, product, magnitude or power that
doesn’t fit its dtype wraps, as in NumPy. A Python number or list
follows NEP 50, as in NumPy: it takes the dtype of the array beside it,
unless its kind is higher, so a float beside an integer array gives
`float64`. A function that computes only on floats, such as `exp`,
converts Booleans and integers to the narrowest float that holds them
exactly, but at least `float32`, which computes faster than `float16`.
So Booleans, `uint8` and `int16` give `float32`, and wider integers give
`float64`:

``` python
i16 = rm.array([1, 2]).astype('int16')
rm.add(i16, 3), rm.add(i16, 0.5), rm.add(i16, rm.array([0.5, 1]).astype('float16')), rm.exp(i16), rm.exp([1, 2])
```

    (Array([4, 5], dtype=int16),
     Array([1.5, 2.5], dtype=float64),
     Array([1.5, 3.0], dtype=float16),
     Array([2.71828, 7.38906], dtype=float32),
     Array([2.7182818, 7.3890561], dtype=float64))

A `rustymath.Array` has arithmetic, bitwise and comparison operators,
`@`, and the methods `.sum()`, `.prod()`, `.max()`, `.min()`, `.all()`,
`.any()`, `.cumsum()`, `.cumprod()`, `.reshape()`, `.astype()` and
`.tolist()`. With no `axis`, a reduction reduces the whole array to a
number, and a scan runs over the flattened array:

``` python
m = rm.array([[1, 2], [3, 4]])
(m * 10).sum(), m.sum(axis=0), m.cumsum()
```

    (100, Array([4, 6], dtype=int64), Array([1, 3, 6, 10], dtype=int64))

## Selecting and reshaping

Brackets take one selector for each axis. Index arrays pair by
broadcasting from the front, as function arguments do:
`grid[[1, 0], [2, 0]]` reads two items. Length-1 axes expand, so index
arrays shaped as an open grid read every combination, as `np.ix_`’s do.
The paired axes take the first array’s place, where NumPy moves them to
the front when a slice separates the arrays. Integers remove an axis;
lists and one-dimensional Boolean masks keep it. Negative positions
count from the end, and slices take Python’s start, stop and step
syntax:

``` python
grid = rm.array([[10, 20, 30], [40, 50, 60]])
grid[[1, 0], [2, 0]], grid[[[1], [0]], [[2, 0]]], grid[:, ::-1], grid[:, [True, False, True]]
```

    (Array([60, 10], dtype=int64),
     Array([[60, 40], [30, 10]], dtype=int64),
     Array([[30, 20, 10], [60, 50, 40]], dtype=int64),
     Array([[10, 30], [40, 60]], dtype=int64))

`...` fills in omitted axes and `None` inserts an axis of length one.
Transpose and reshape of compact inputs share storage. Joining arrays
creates a compact result:

``` python
grid[None, ...].shape, grid.T, rm.concatenate([grid, grid], axis=0).shape
```

    ((1, 2, 3), Array([[10, 40], [20, 50], [30, 60]], dtype=int64), (4, 3))

`eye` constructs an identity or rectangular diagonal matrix. `diag`
constructs a matrix from a vector, or extracts a matrix’s diagonal.
`diagonal` works on any two axes, moving the diagonal to the last axis.
Its axes default to the last two, the axes that the matrix functions
take, where NumPy defaults to the first two. `tril` and `triu` keep one
triangle of each matrix. All return compact copies.

``` python
rm.eye(3), rm.diagonal(grid), rm.tril(grid)
```

    (Array([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]], dtype=float64),
     Array([10, 50], dtype=int64),
     Array([[10, 0, 0], [40, 50, 0]], dtype=int64))

A selection returns a Python scalar when every axis is removed, and an
independent array otherwise. Assignment uses the same selectors and
leading-axis broadcasting. It preserves the array’s dtype and shape, and
leaves earlier selections unchanged. For repeated unkeyed targets, the
last assigned value wins:

``` python
selected = grid[:, 1]
grid[:, 1] = [200, 500]
selected, grid
```

    (Array([20, 50], dtype=int64),
     Array([[10, 200, 30], [40, 500, 60]], dtype=int64))

`rm.at(rm.add, a, indices, b)` adds `b` at the selection, as NumPy’s
`np.add.at` does, but returns a new array. A target selected more than
once adds every item that selects it. Here row 0’s last item is selected
twice:

``` python
rm.at(rm.add, grid, ([0, 0, 1], [2, 2, 0]), [1, 2, 3])
```

    Array([[10, 200, 33], [43, 500, 60]], dtype=int64)

## Names and keys

A dictionary becomes a vector whose axis has the dictionary’s keys.
Functions align keyed axes by key:

``` python
rm.add({'a': 1.5, 'b': 2.5}, {'b': 10.0, 'a': 20.0})
```

    Array([21.5, 12.5], dtype=float64, keys=(('a', 'b'),))

Keys select positions too. An axis holds each key at most once.
Selecting a key twice raises an error that names the key, such as
`duplicate key: a`. Reordering a keyed axis keeps its keys:

``` python
keyed = rm.array({'a': [1, 2], 'b': [3, 4]})
keyed[['b', 'a']]
```

    Array([[3, 4], [1, 2]], dtype=int64, keys=(('b', 'a'), None))

Assigning a tuple to `.names` names the axes, with one name or `None`
for each axis. `axis` then takes a name wherever it takes a position:

``` python
s = rm.array([[10, 20, 30], [40, 50, 60]])
s.names = ('city', 'month')
s.sum(axis='month')
```

    Array([60, 150], dtype=int64, names=('city',))

Index arrays with axis names follow arithmetic’s rule for names. Two
with the same name read one position along both, and two with different
names read every combination. A named index array selects along the axis
with its name, wherever it stands in the brackets:

``` python
m = rm.array([[0, 1, 2], [3, 4, 5], [6, 7, 8]])
i, j = rm.arange(3), rm.arange(3)
i.names, j.names = ('i',), ('j',)
month = rm.array([2, 0])
month.names = ('month',)
m[i, i], m[i, j].shape, s[month]
```

    (Array([0, 4, 8], dtype=int64, names=('i',)),
     (3, 3),
     Array([[30, 10], [60, 40]], dtype=int64, names=('city', 'month')))

## Interoperability and threads

Rustymath arrays export Python’s buffer protocol. NumPy can read them
through `np.asarray` without copying. Rustymath accepts NumPy arrays and
other buffer exporters too. Its Python names follow familiar NumPy or
SciPy spellings where they fit the shared core. Broadcasting and
indexing follow rustymath’s rules.

Here `exp` keeps its argument’s 32-bit width:

``` python
import numpy as np
```

``` python
x = np.linspace(0, 2, 5, dtype=np.float32)
rm.exp(x)
```

    Array([1.0, 1.64872, 2.71828, 4.48169, 7.38906], dtype=float32)

Buffer exports are read-only snapshots. They retain their values after
assignment to the Rustymath array. Transposes and slices export their
actual strides without copying. Use ordinary Rustymath bracket
assignment to change an array.

`rm.set_threads(n)` limits parallel calls on the calling thread and
returns its previous setting. Use 0 for the default or 1 for serial
calls. Results need not agree in their last bits across thread counts.
