//
// This confidential and proprietary software may be used only as
// authorised by a licensing agreement from ARM Limited
// (C) COPYRIGHT 2020-2022 ARM Limited
// ALL RIGHTS RESERVED
// The entire notice above must be reproduced on all authorised
// copies and copies may only be made to the extent permitted
// by a licensing agreement from ARM Limited.

=== Data Layout

==== CONCAT
Concatenate a list of tensors along a given axis.
No data conversion happens during a concat operation.

include::{generated}/operators/CONCAT.adoc[]

[source,c]
----
ERROR_IF(axis < 0 || axis >= rank(shapes1[0]));
ERROR_IF(shape[axis] != sum(shape1[k][axis] for all k))
// The following checks ensure all inputs are compatible for concatenation
for_each(input_shape in shapes1) {
    ERROR_IF(rank(input_shape) != rank(shapes1[0]));
    for_each(index in input_shape) {
        ERROR_IF(input_shape[index] != shapes1[0][index] && index != axis);
    }
}
for_each(index1 in shape) {
    dim_t index2 = index1;
    for (tensor t = 0; t < length(input1); t++) {
        // Continue to concatenate along axis from each tensor
        // For each output location, we are looking for the
        // appropriate input tensor
        if (index2[axis] >= 0 && index2[axis] < shapes1[t][axis]) {
            in_out_t value = tensor_read<in_out_t>(input1[t], shapes1[t], index2);
            tensor_write<in_out_t>(output, shape, index1, value);
        }
        index2[axis] = index2[axis] - shapes1[t][axis];
    }
}

----

==== PAD

Pads a tensor along the borders of each dimension with a supplied value.
Returns a new tensor with the padding included.
The pad_const value includes the zero point if the tensor uses a zero point.

include::{generated}/operators/PAD.adoc[]

[source,c++]
----
// Check output shape matches the padded input shape
ERROR_IF(rank(shape) != rank(shape1));
for (i = 0; i < rank(shape); i++) {
    ERROR_IF(padding[i,0] < 0 || padding[i,1] < 0);
    ERROR_IF(shape[i] != padding[i, 0] + shape1[i] + padding[i, 1]);
}
for_each(index in shape) {
    dim_t index1 = index;
    bool_t is_pad = false;
    for(i = 0; i < rank(shape); i++) {
        index1[i] = index1[i] - padding[i,0];
        if (index1[i] < 0 || index[i] >= length(shape[i])) {
            is_pad = true;
        }
    }
    in_out_t value = is_pad ? pad_const : tensor_read<in_out_t>(input1, shape1, index1);
    tensor_write<in_out_t>(output, shape, index, value);
}
----

==== RESHAPE

Returns a tensor with the same type/values as the input, with a new shape specified by the shape argument. Reshape may operate on tensors of any rank. No data conversion happens during a reshape operation.

include::{generated}/operators/RESHAPE.adoc[]

[source,c++]
----
ERROR_IF(tensor_size(shape1) != tensor_size(shape));

for_each(index in shape) {
    // Calculate flattened index for the output location (index)
    size_t offset = tensor_index_to_offset(shape, index);
    // Now convert to the location in the input
    dim_t tmp_index = tensor_offset_to_index(shape1, offset);

    // Now read/write the value
    in_out_t val = tensor_read<in_out_t>(input, shape1, tmp_index);
    tensor_write<in_out_t>(output, shape, index, val);
}
----

==== REVERSE

Returns a tensor with the same type/values as the input, with the data reversed along the given axis. No data conversion happens during a reverse operation.

include::{generated}/operators/REVERSE.adoc[]

[source,c++]
----
ERROR_IF(axis < 0 || axis >= rank(shape));
for_each(index in shape) {
    dim_t tmp_index = index;
    tmp_index[axis] = shape[axis] - 1 - index[axis];
    in_out_t value = tensor_read<in_out_t>(input, shape, tmp_index);
    tensor_write<in_out_t>(output, shape, index, value);
}
----

==== SLICE

Extracts a slice of input1, beginning at the start coordinates, and extending for size elements in each direction.
No data conversion happens during a slice operation.

include::{generated}/operators/SLICE.adoc[]

[source,c++]
----
ERROR_IF(rank(input1) != length(start) || rank(input1) != length(size));
ERROR_IF(rank(input1) != rank(output))
// Sanity check the given coordinates, ensure start and end are
// within tensor bounds
for_each(index in rank(input1)) {
    ERROR_IF(start[index] < 0);
    ERROR_IF(size[index] <= 0); //Output must be positive size
    ERROR_IF(start[index] + size[index] > shape1[index]);
    ERROR_IF(shape[index] != size[index]);
}

for_each(index in shape) {
    dim_t tmp_index = index;
    for(i = 0; i < rank(shape); i++) {
       tmp_index[i] = index[i] + start[i];
    }
    in_out_t value = tensor_read<in_out_t>(input, shape1, tmp_index);
    tensor_write<in_out_t>(output, shape, index, value);
}
----

==== TILE

Replicates input1 multiples times along each dimension.

include::{generated}/operators/TILE.adoc[]

[source,c++]
----
for_each(index in shape) {
    dim_t tmp_index = index;
    for(i = 0; i < rank(shape); i++) {
        ERROR_IF(shape1[i] * multiples[i] != shape[i]);
        tmp_index[i] = index[i] % shape1[i];
    }
    in_out_t value = tensor_read<in_out_t>(input, shape1, tmp_index);
    tensor_write<in_out_t>(output, shape, index, value);
}
----

==== TRANSPOSE

Permutes the dimensions of the input tensor input1 based on the perms argument.
Each value in the perms list must be a valid dimension of the input tensor and may not be repeated.

include::{generated}/operators/TRANSPOSE.adoc[]

[source,c++]
----
for_each(index in perms) {
    // Ensure each perms value is a valid value
    ERROR_IF(index >= rank(shape1));
    ERROR_IF(index < 0);
    // Ensure ranks aren't repeated
    ERROR_IF(indexes_used[index] == true);
    indexes_used[index] = true;
}

// Ensure that the output shapes have the properly
// permuted shapes
for(i = 0; i < rank(shape); i++) {
    ERROR_IF(shape1[perms[i]] != shape[i])
}

for_each(index in shape) {
    dim_t tmp_index = index;
    for(i = 0; i < rank(shape); i++) {
        tmp_index[perms[i]] = index[i]
    }
    in_out_t value = tensor_read<in_out_t>(input, shape1, tmp_index);
    tensor_write<in_out_t>(output, shape, index, value);
}
----
