Compute

Marrow provides SIMD-vectorized compute kernels for arithmetic, comparisons, aggregation, selection, and strings. All kernels are null-aware: the exact null handling behaviour depends on the operation.

Arithmetic

Element-wise binary operations: add, sub, mul, div.

a = ma.array([1, 2, 3, 4, 5], type=ma.int64())
b = ma.array([10, 20, 30, 40, 50], type=ma.int64())

print("add:", ma.add(a, b, None))
print("sub:", ma.sub(b, a, None))
print("mul:", ma.mul(a, b, None))
print("div:", ma.div(b, a, None))
add: PrimitiveArray[int64]([11, 22, 33, 44, 55])
sub: PrimitiveArray[int64]([9, 18, 27, 36, 45])
mul: PrimitiveArray[int64]([10, 40, 90, 160, 250])
div: PrimitiveArray[int64]([10, 10, 10, 10, 10])

Floats work the same way:

x = ma.array([1.0, 2.0, 3.0])
y = ma.array([0.5, 1.5, 2.5])
print("add:", ma.add(x, y, None))
print("div:", ma.div(x, y, None))
add: PrimitiveArray[float64]([1.5, 3.5, 5.5])
div: PrimitiveArray[float64]([2.0, 1.3333333333333333, 1.2])

Null propagation

If either operand at a position is null, the result at that position is null. This mirrors SQL’s three-valued logic.

a = ma.array([1, None, 3, None])
b = ma.array([10, 20, 30, 40])

result = ma.add(a, b, None)
print(result)    # index 1 and 3 are null
print("null count:", result.null_count())
PrimitiveArray[int64]([11, NULL, 33, NULL])
null count: 2
# Both inputs can contribute nulls
a = ma.array([None, 2, 3, None])
b = ma.array([10, None, 30, None])

print(ma.add(a, b, None))   # null at 0, 1, and 3
PrimitiveArray[int64]([NULL, NULL, 33, NULL])

Aggregates

Aggregates reduce an array to a single scalar. They skip null values by default — an all-null or empty array returns the identity value.

Numeric aggregates

arr = ma.array([1, None, 3, None, 5])

print("sum:    ", ma.sum_(arr, None))     # 1+3+5 = 9
print("product:", ma.product(arr, None))  # 1*3*5 = 15
print("min:    ", ma.min_(arr, None))
print("max:    ", ma.max_(arr, None))
sum:     9
product: 15
min:     1
max:     5

Aggregates always return float64 regardless of input type:

ints = ma.array([10, 20, 30], type=ma.int32())
result = ma.sum_(ints, None)
print(result, type(result))
60 <class 'Scalar'>

Identity values for empty / all-null input

empty    = ma.array([], type=ma.int64())
all_null = ma.array([None, None], type=ma.int64())

print("sum of empty:      ", ma.sum_(empty, None))      # 0.0
print("sum of all-null:   ", ma.sum_(all_null, None))    # 0.0
print("product of empty:  ", ma.product(empty, None))    # 1.0
print("min of empty:      ", ma.min_(empty, None))       # inf
print("max of empty:      ", ma.max_(empty, None))       # -inf
sum of empty:       0
sum of all-null:    0
product of empty:   1
min of empty:       9223372036854775807
max of empty:       -9223372036854775808

Boolean aggregates

any_ and all_ operate on boolean arrays. Nulls are skipped.

flags = ma.array([True, False, None, True])
print("any_:", ma.any_(flags))   # True  — at least one True
print("all_:", ma.all_(flags))   # False — False is present
any_: True
all_: False
all_true = ma.array([True, True, None])
print("all_ with nulls:", ma.all_(all_true))   # True — only True values

all_false = ma.array([None, None], type=ma.bool_())
print("all_ empty/null:", ma.all_(all_false))   # True (identity)
print("any_ empty/null:", ma.any_(all_false))   # False (identity)
all_ with nulls: True
all_ empty/null: True
any_ empty/null: False

Selection

filter_

filter_(array, mask) keeps elements where the boolean mask is True. The mask must be a BoolArray of the same length.

arr  = ma.array([10, 20, 30, 40, 50])
mask = ma.array([True, False, True, False, True])

print(ma.filter_(arr, mask))   # [10, 30, 50]
PrimitiveArray[int64]([10, 30, 50])

Nulls in the source array are preserved through the filter:

arr  = ma.array([1, None, 3, 4, None])
mask = ma.array([True, True, False, True, True])

print(ma.filter_(arr, mask))   # [1, NULL, 4, NULL]
PrimitiveArray[int64]([1, NULL, 4, NULL])

Nulls in the mask are treated as False (the element is excluded):

arr  = ma.array([1, 2, 3, 4])
mask = ma.array([True, None, True, None])

print(ma.filter_(arr, mask))   # [1, 3]
PrimitiveArray[int64]([1, 3])

drop_nulls

drop_nulls(array) removes all null positions. Equivalent to filter_(array, validity_bitmap).

arr = ma.array([1, None, 3, None, 5])
print(ma.drop_nulls(arr))   # [1, 3, 5]
PrimitiveArray[int64]([1, 3, 5])

Works on numeric array types:

f = ma.array([1.0, None, 3.0, None, 5.0])
print(ma.drop_nulls(f))
PrimitiveArray[float64]([1.0, 3.0, 5.0])

Comparisons

Element-wise comparison kernels return a boolean array. Nulls propagate: if either input at a position is null, the output is null.

a = ma.array([1, 2, 3, None, 5])
b = ma.array([1, 3, 2, 4,    5])

print("equal:        ", ma.equal(a, b, None))
print("not_equal:    ", ma.not_equal(a, b, None))
print("less:         ", ma.less(a, b, None))
print("less_equal:   ", ma.less_equal(a, b, None))
print("greater:      ", ma.greater(a, b, None))
print("greater_equal:", ma.greater_equal(a, b, None))
equal:         BoolArray([True, False, False, NULL, True])
not_equal:     BoolArray([False, True, True, NULL, False])
less:          BoolArray([False, True, False, NULL, False])
less_equal:    BoolArray([True, True, False, NULL, True])
greater:       BoolArray([False, False, True, NULL, False])
greater_equal: BoolArray([True, False, True, NULL, True])

The null at index 3 propagates to all output arrays:

result = ma.equal(a, b, None)
print("null count:", result.null_count())   # 1
print("is_valid(3):", result.is_valid(3))   # False
null count: 1
is_valid(3): False

Null behaviour summary

Operation Null input Null output
add, sub, mul, div either operand null null propagates
equal, less, greater, … either operand null null propagates
sum_, product, min_, max_ element null element skipped
any_, all_ element null element skipped
filter_ (source null) source element null null preserved in output
filter_ (mask null) mask element null element excluded
drop_nulls element null element removed