Skip to main content

Arrays and tensors

A multi-dimensional array is a collection that represents a tensor whose elements are numeric values arranged as a cube in a number of dimensions. For example, a numerical vector is a 1-dimensional (1D) array, while a matrix is a 2D array.

Multi-dimensional arrays are represented as objects of type Array. The difference between objects of type Array and type Vector is that type Vector represent 1D sequences of objects of any kind, while 1D arrays very efficiently represent dense numerical vectors.

All elements in an array have the same data representation or format. The format specifies the internal binary representation of the elements. There is a built-in subtype of Array for each format. The format is specified by strings:

FormatInternal representationType signature
f64/F6464-bit float (Real)Array of F64
f32/F3232-bit floatArray of F32
i64/I6464-bit integer (Integer)Array of I64
i32/I3232-bit integerArray of I32
u32/U3232-bit unsigned integerArray of U32
i16/I1616-bit integerArray of I16
u16/U1616-bit unsigned integerArray of U16
i8/I88-bit integerArray of I8
u8/U88-bit unsigned integerArray of U8

Creating arrays

An array can be created with the array(Vector elements)->Array constructor function, where elements is a vector holding the elements of the new array. The default type for arrays is 64-bit integers (format 'I64') for integer elements, and 64-bit floats (format 'F64') for floating point elements.

Example: The following statement constructs a numerical vector of 64-bits integers:

set :a1 = array([10,20,30])
typesig(:a1)

Example: The following statement constructs a numerical vector of 64-bits real numbers:

set :a2 = array([10.0, 20, 30, 40])
typesig(:a2)

The elements of array :a2 are real numbers since one of the array elements in the array() call is a real number.

The function array(Charstring format, Vector elements)->Array takes an extra first argument being the format of the elements.

For example:

set :a2 = array('I8',[1,2,3])
typesig(:a2)  -- a numerical vector of 8-bit integers

You can create arrays of higher rank than one by nesting the elements vector.

Example: The following expression creates a matrix :m having three columns and two rows.

set :m = array([[1,2,3],
[4,5,6]])
typesig(:m)

To copy an array you pass the array you want to copy into the array(Array a)->Array or array(Charstring fmt, Array a)->Arrayfunction.

set :a3 = array([1,2,3])
set :a4 = array("U8", :a3)
set :a5 = array(format(:a4), :a4)

Array metadata

An array object contains metadata describing its shape, number of elements, number of dimensions, and datatype.

The array metadata functions:

shape(Array a)->Vector of Integer    - Shape of array (e.g., [3,2] for a 3x2 array)
dim(Array a)->Integer - Number of elements in array
rank(Array a)->Integer - Number of dimensions
format(Array a)->Charstring - Format of array elements (e.g., 'F64', `I8`)
length(Array a,Integer ax)->Integer - The length of dimension ax in array a

Example:

set :d = array("I16", [[1,2,3],
[4,5,6]])
typesig(:d)
shape(:d)
dim(:d)
rank(:d)
format(:d)
length(:d, 2)

Indexing arrays

Arrays are indexed with square brackets [].

set :arr = array([1,2,3,4,5,6,7,8,9,10])
:arr[3]
set :arr = array([[1,2,3],
[4,5,6]])
:arr[1,2]
:arr[2,3]

OSQL also supports linear indexing of multidimensional arrays.

set :arr = array([[1,2,3],
[4,5,6]])
:arr[5]

The character * can be used to omit dimensions to form subarrays, called array slicing.

set :arr = array([[ 1,  2,  3,  4,  5],
[ 6, 7, 8, 9, 10],
[11, 12, 13, 14, 15],
[16, 17, 18, 19, 20]])
:arr[1,*]
:arr[*,3]

Arithmetics

The following infix operators and functions over arrays a and b, and numbers lambda are defined:

operationdescriptionimplemented as function
a + belement-wise additionplus
a .+ b-"-plus
lambda + aadd number to elementsplus
a + lambda-"-plus
a - belement-wise subtractionsminus
a .- b-"-minus
a - lambdasubtract number from elementsminus
lambda - asubtract elements from numberminus
-anegate elementsuminus
a .* belement-wise multiplicationselemtimes
lambda * amultiply elements with numbertimes
a * lambda-"-times
dot(a,b)dot produtdot
matmul(a,b)matrix multiplicationmatmul
a ./ belement-wise divisionelemdiv
a / lambdadivide elements with numberdiv
lambda / adivide number with elementsdiv
a .^ lambdaelement-wise exponentelempower

To exemplify the array operators and function we use the following two arrays:

set :a = array("f32", [[1,2],[2,4]])
typesig(:a)
set :b = array("f32", [[-1,-2],[-4,-8]])

Addition

Example: Element-wise addition.

:a + :b

is the same as

:a .+ :b

Example: Add number to elements

5 + :a

is the same as

:a + 5

Subtraction

Example: Element-wise subtraction

:a - :b

is the same as

:a .- :b

Example: Subtract number from elements

:a - 5

Example: Subtract elements from number

5 - :a

Example: Negate elements

- :a

Multiplication

Example: Element-wise multiplication

:a .* :b

Example: Multiply elements with number

:a * 2

is the same as

2 * :a

Example: Dot product

dot(:a, :b)

Example: Matrix multiplication

matmul(:a, :b)

Division

Example: Element-wise division

:a ./ :b

Example: Divide elements with number

:a / 2

Example: Divide number with elements

2 / :a

Exponent

Example: Element-wise exponent

:a .^ 2

Aggregation

Arrays can be used as arguments to aggregate functions. If an aggregate function is called with an array as argument it will aggregate over all array elements, independent of the array's shape.

We exemplify aggregation with the following array:

set :a8 = array('i8',[[1,2,3],
[4,5,6]])
typesig(:a8)

Example: The largest element in :a8.

max(:a8)

Example: The smallest element in :a8.

min(:a8)

Example: The sum of the elements in :a8.

sum(:a8)

Example: The sum of the first column elements of :a8.

sum(:a8[*,1])

Example: The mean of the elements in the second row of :a8.

avg(:a8[2,*])

Select array queries

You can specify queries that construct new arrays as array comprehension queries, where you specify both shape and format of the result along with index variables that are bound when specifying the values of array elements.

Example: Constructs a 1D array named out with shape [5] indexed by the variable i

select Array out[i..5] of U8 e
where e = i

The array name is optional and can be omitted:

select Array[i..5] of U8 e
where e = i + 3

Example: Construct a 5x5 unity matrix of integers with format ´U8´

select Array[i..5, j..5] of U8 e
where e = case when i = j then 1
else 0 end

You can use arrays in regular queries just like with all other data types.

Example:

select a
from Array a
where a = array('U8',[1,2,3])

Defining array functions

Derived functions can be defined to create and transform arrays.

Example: This function create a new array

create function my_arr() -> Array
as array("U8", [1,2,3])
my_arr()

Variables can be used for defining the shape in a select array query.

Example:

create function inc_2d(Integer sz) -> Array
as select Array[y..sz, x..sz] of U8 val
where val = (y-1) + (x-1)
inc_2d(2)
inc_2d(5)

The array size does not have to be a scalar. It can be an expression.

Example:

create function inc(Integer sz) -> Array
as select Array[i..(sz + 3)] of U8 val
where val = i
inc(5)

Arrays as parameters

Functions can take arrays as arguments.

Examples:

create function my_fun(Array a) -> Integer
as a[2]*10
my_fun(array([1,2,3]))
create function my_fun2(Array a) -> Integer
as select a[i]*10
from Integer i
where i = shape(a)[1]/2
my_fun2(array([1,2,3,4,5,6]))

Functions are type-aware, so if you use the generic type Array as result type (without the data type format) the system will deduce the exact result type automatically.

Example:

create function my_fun(Array a) -> Array
as a + 2
my_fun(array("U8",[1,2,3]))

You can also specify the format and declare array shape variables directly in the function signature.

Example: The following takes an Array of U8 array as argument and creates a nes Array of U8 object with the same shape.

create function transpose(Array arr[ys,xs] of U8) -> Array
as select Array[x..xs, y..ys] of U8 val
where val = arr[y,x]
set :a2 = array("U8", [[1,2,3,4],
[5,6,7,8],
[9,10,11,12]])
transpose(:a2)

Overloaded array functions

OSQL supports overloading of array functions based on array formats (but not on shapes).

Example:

create function overload(Array of F64 a, Array of F64 b) -> Real
as sum(a .* b)
create function overload(Array of F32 a, Array of F32 b) -> Real
as sum(a ./ b)
overload(array("F64", [1.0,2,3]), array("F64", [4.0,5,6]))
roundto(overload(array("F32", [1.0,2,3]), array("F32", [2.0,4.0,5])), 2)

It is possible to use the same shape variable for multiple arguments. This will put constraints on the arguments requiring matching shapes.

Example: The following Boolean function tests that arrays u and v are numerical vectors of the same size.

create function sameshape(Array u[dim], Array v[dim])
-> Boolean
as true
sameshape(array([1,2,3,4]), array([5,6,7,8]))
sameshape(array([1,2,3,4]), array([5,6,7,8,9]))
sameshape(array([1,2,3,4]), array([[5,6],[7,8]]))

Example: The following Boolean function tests that an array a is a square matrix.

create function is_square(Array a[dim,dim]) -> Boolean
as true
is_square(array([[1,2],
[3,4]]))
is_square(array([[1,2],
[3,4],
[5,6]]))

Reshaping arrays

Arrays can be reshaped with the function reshape(Array a,Vector shape)->Array. You can pass a desired shape, but the number of elements have to be the same as the original.

Example:

set :arr = array("u8", [[1,  2,  3,  4],
[5, 6, 7, 8],
[9, 10, 11, 12],
[13, 14, 15, 16]])
reshape(:arr, [8,2])

You can reshape a multi-dimenasional array to a 1D numerical vector by the function flatten(Array)->Array:

set :f = flatten(:arr)
:f[10]

Plotting arrays

The default plot for arrays is Text. But when the arrays represent images you can plot the images by using the Bitmap plot. It works for both gray level images (arrays with shape [Y,X]) and color images (arrays with shape [Y,X,3]). Almost all array data types are supported. Negative element values will be displayed as black.

Let's define two functions that generate images, one gray level and one color image:

create function inc_2d(Integer sz) -> Array
as select Array[y..sz, x..sz] of U8 val
where val = (y-1) + (x-1)
create function rgb_arr(Integer sz) -> Array
as select Array[y..sz,x..sz,c..3] of U8 val
where val = case when c = 1 then mod(x+y,256)
when c = 2 then mod(abs(x-y),256)
else mod((x + y)/2,256) end

Now we can plot the generated arrays with the //plot: Bitmap directive.

//plot: Bitmap
inc_2d(200)
//plot: Bitmap
rgb_arr(500)

Note that the channel order is RGB (unlike, e.g., OpenCV, which uses BGR).

To illustrate this we can create a function that returns a "red" image:

create function red(Integer sz) -> Array
as select Array[y..sz,x..sz,c..3] of U8 val
where val = case when c = 1 then mod(x + y,256)
else 0 end

Now we can plot the red image with the //plot: Bitmap directive.

//plot: Bitmap
red(200)

Functions

There is documentation of the system functions over arrays in Array functions.