Construction and Data Types

The SimpleArray family is constructed in two styles. The 13 typed classes fix the element type in the class name and take a shape; the dtype-erased SimpleArray takes a shape plus a dtype string and wraps the matching typed class behind a single Python type. Both styles either allocate a ConcreteBuffer or wrap the memory of an existing numpy array. This page defines the constructor forms, the element types, the fill and clone operations, and the alignment extension.

Element Types

Each typed class binds one C++ element type. The dtype string in the table names the numpy dtype of the array and selects the typed class in the SimpleArray constructor. The dtype naming matches numpy: the strings are the numpy dtype names, so an array round-trips to numpy without translation.

| Class | C++ value type | dtype string | | ———————– | —————– | ———— | | SimpleArrayBool | bool | bool | | SimpleArrayInt8 | int8_t | int8 | | SimpleArrayInt16 | int16_t | int16 | | SimpleArrayInt32 | int32_t | int32 | | SimpleArrayInt64 | int64_t | int64 | | SimpleArrayUint8 | uint8_t | uint8 | | SimpleArrayUint16 | uint16_t | uint16 | | SimpleArrayUint32 | uint32_t | uint32 | | SimpleArrayUint64 | uint64_t | uint64 | | SimpleArrayFloat32 | float | float32 | | SimpleArrayFloat64 | double | float64 | | SimpleArrayComplex64 | Complex<float> | complex64 | | SimpleArrayComplex128 | Complex<double> | complex128 |

The complex classes use solvcon’s own Complex value type in C++ but expose the standard numpy complex dtypes through the buffer protocol. The item size follows the element type, and the numpy view of an array carries the matching dtype:

import numpy as np
assert solvcon.SimpleArrayInt8((2, 3)).nbytes == 6
assert solvcon.SimpleArrayInt32(7).nbytes == 28
assert solvcon.SimpleArrayFloat64((2, 3, 4)).nbytes == 192
assert solvcon.SimpleArrayFloat64((2, 3)).ndarray.dtype == np.float64

Typed Constructors

Every typed class offers the same five constructor forms, shown here on SimpleArrayFloat64:

solvcon.SimpleArrayFloat64(shape)
solvcon.SimpleArrayFloat64(shape, alignment=64)
solvcon.SimpleArrayFloat64(shape, value=1.0)
solvcon.SimpleArrayFloat64(shape, value=1.0, alignment=64)
solvcon.SimpleArrayFloat64(array=ndarr)

The first four allocate; the last wraps existing numpy memory. The sections below define each form.

Construction from a Shape

A typed array is constructed from a shape given as a single integer (a one-dimensional array) or a tuple of integers:

sarr = solvcon.SimpleArrayInt32(7)          # shape (7,)
sarr = solvcon.SimpleArrayFloat64((2, 3, 4))

The element storage is allocated immediately and is not initialized; the allocation semantics match numpy.empty. The shape, stride, size, itemsize, and nbytes properties describe the layout of the row-major result, and len() returns the element count:

sarr = solvcon.SimpleArrayFloat64((2, 3, 4))
assert sarr.shape == (2, 3, 4)
assert sarr.stride == (12, 4, 1)  # counted in elements, not bytes
assert sarr.size == 24
assert sarr.itemsize == 8
assert sarr.nbytes == 24 * 8
assert len(sarr) == 24

len() diverges from numpy: it returns the total element count, where numpy returns the length of the first dimension (24 versus 2 for the shape above).

A negative shape dimension raises IndexError:

solvcon.SimpleArrayFloat64((-1, 2))
# IndexError: SimpleArray: shape dimension must be non-negative ...

Fill Value

The second constructor form takes an initial value and fills every element with it, in the spirit of numpy.full:

sarr = solvcon.SimpleArrayFloat64((4, 4), value=3.14159)
assert sarr[0, 0] == 3.14159

Pass the value with the value= keyword. An integer given positionally in the second slot resolves to the alignment overload instead of the fill value, because the alignment constructor is matched first; the keyword removes the ambiguity.

Wrapping a Numpy Array

The array= keyword form wraps the memory of an existing numpy array instead of allocating:

ndarr = np.arange(24, dtype='float64').reshape((2, 3, 4))
sarr = solvcon.SimpleArrayFloat64(array=ndarr)

The array and the wrapping SimpleArray share memory zero-copy, and the wrapper keeps the source alive by holding a reference. The dtype of the source must equal the element type of the class; a mismatch raises RuntimeError. As on ConcreteBuffer, the is_from_python property reports the provenance: True for an array wrapping numpy memory and False for an array that allocated its own buffer.

Unlike the buffer-level wrap, the source does not need to be contiguous. Strided views are supported, including negative strides from reversed slices; the stride of the view is recorded in elements, and writes made through the wrapper land in the viewed region of the original array:

ndarr = np.arange(6, dtype='float64').reshape((2, 3))
view = ndarr[::-1, ::-1]
sarr = solvcon.SimpleArrayFloat64(array=view)
assert sarr.stride == (-3, -1)
sarr[0, 0] = 200.0
assert ndarr[1, 2] == 200.0

A source whose byte stride is not divisible by the item size, or whose data pointer is not aligned for the element type, raises RuntimeError. A Fortran-ordered source is likewise wrapped with its own stride; the is_c_contiguous and is_f_contiguous properties report the layout, and a degenerate shape (a single row, column, or element) reports both as True.

The Dtype-Erased SimpleArray

The Python class SimpleArray erases the element type from the class name and moves it into a dtype string argument, closer to the numpy calling convention:

sarr = solvcon.SimpleArray((2, 3, 4), dtype='float64')
sarr = solvcon.SimpleArray((2, 3, 4), value=3.0, dtype='float64')
sarr = solvcon.SimpleArray(np.arange(6, dtype='int32'))

The dtype string selects the typed class per the table above; a string outside the table raises ValueError. The third form infers the dtype from the numpy array and shares its memory.

The value= form diverges from numpy: where numpy.full casts the fill value to the array dtype, SimpleArray validates the Python type of the value strictly and raises TypeError on mismatch. A bool dtype requires a Python bool, the integer dtypes require a Python int, and the floating-point dtypes require a Python float:

solvcon.SimpleArray((2, 3), dtype='bool', value=3.3)
# TypeError: Data type mismatch, expected Python bool
solvcon.SimpleArray((2, 3), dtype='int32', value=3.3)
# TypeError: Data type mismatch, expected Python int
solvcon.SimpleArray((2, 3), dtype='float64', value=3)
# TypeError: Data type mismatch, expected Python float

The erased wrapper carries the typed interface, as the family overview states, so every operation of this document reads the same on both. The typed property converts to the concrete class: it returns an independent copy of the wrapped array as its typed class, and the plex property on a typed array returns an erased copy the same way. Neither direction shares memory with the original, so the bridge serves read-style workflows; a write made through the bridged object stays in the copy. The content survives the round trip in both directions:

plex = solvcon.SimpleArray((2, 3, 4), dtype='float64', value=1.5)
typed = plex.typed
assert type(typed) is solvcon.SimpleArrayFloat64
assert typed[0, 0, 0] == 1.5
assert type(typed.plex) is solvcon.SimpleArray

Filling

fill(value) assigns the value to every element in place, matching numpy ndarray.fill:

sarr = solvcon.SimpleArrayFloat64((2, 3, 4))
sarr.fill(2.0)

On the dtype-erased SimpleArray, fill applies the same strict value typing as the value= constructor and raises TypeError when the Python type of the value does not match the dtype.

Cloning

clone() returns a deep copy: a new array with the same shape, stride, alignment, and a copy of the content, on a freshly allocated buffer. The copy never shares memory with the source, regardless of how the source was constructed; cloning an array that wraps numpy memory yields an independent array whose is_from_python is False:

sarr = solvcon.SimpleArrayFloat64((2, 3, 4), value=2.0)
clone = sarr.clone()
sarr[0, 0, 3] = 3.0
assert clone[0, 0, 3] == 2.0

The dtype-erased SimpleArray clones the same way and returns another erased wrapper.

The spelling diverges from numpy: numpy calls this operation copy(). The target behavior is that clone() remains the canonical spelling for a deep copy across the family, and no copy() alias is provided.

Alignment

Alignment is a solvcon-specific extension with no numpy counterpart. Every allocating constructor form accepts an optional alignment argument that aligns the start of the buffer for SIMD kernels, which require 16-, 32-, or 64-byte alignment depending on the vector width:

sarr = solvcon.SimpleArrayFloat64((4, 4), alignment=16)
sarr = solvcon.SimpleArrayFloat64((4, 4), value=2.7, alignment=16)
sarr = solvcon.SimpleArray((4, 4), dtype='float64', alignment=16)

Valid alignment values are 0 (the default, no specific alignment), 16, 32, and 64 bytes; any other value raises ValueError. When a non-zero alignment is requested, the total byte count of the array must be a multiple of the alignment, or the allocation raises ValueError:

solvcon.SimpleArrayFloat64((4, 4), alignment=17)
# ValueError: ... alignment must be 0, 16, 32, or 64, but got 17
solvcon.SimpleArrayFloat64((5, 1), alignment=16)
# ValueError: ConcreteBuffer::allocate: size ... must be a multiple ...

The read-only alignment property returns the requested alignment, and clone() preserves it. Arrays that wrap numpy memory do not take the argument, because nothing is allocated. An aligned array behaves like any other array elsewhere: it fills, computes, converts to numpy, and clones the same way, and additionally satisfies the memory precondition of the _simd operation variants.