//
// This confidential and proprietary software may be used only as
// authorised by a licensing agreement from ARM Limited
// (C) COPYRIGHT 2020-2021 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.

*Arguments:*

|===
|Argument|Type|Name|Shape|Description

|Input|in_t*|input1|shapes1[]|List of input tensors. All inputs must have the same rank and data type
|Attribute|int|axis|-|Axis along which concatenation is to occur, in range from 0 to rank(shape)-1
|Output|in_t*|output|shape|Output tensor
|===

*Operation Function:*

[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) {
    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_t value = tensor_read<in_t>(input1[t], shapes1[t], index2);
            tensor_write<in_t>(output, shape, index1, value);
        }
        index2[axis] = index2[axis] - shapes1[t][axis];
    }
}

----

*Supported Data Types:*

|===
|Profile|Mode|in_t

|Any|Boolean|bool_t
|Any|signed 8|int8_t
|Any|signed 16|int16_t
|Any|signed 32|int32_t
|MI, MT|floating-point|float_t
|===

==== 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.

*Arguments:*

|===
|Argument|Type|Name|Shape|Description

|Input|in_t*|input1|shape1|Input tensor
|Attribute|int|padding|[rank(input1),2]|Amount of padding to be done
|Attribute|in_t|pad_const|-|Constant value to be used as padding
|Output|in_t*|output|shape|Output tensor of same type as the input tensor
|===

*Operation Function:*

[source,c++]
----
// Padding sizes must be >= 0.
for_each(pad_size in padding) {
    ERROR_IF(pad_size < 0);
}
for_each(index in shape) {
    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_t value = is_pad ? pad_const : tensor_read<in_t>(input1, shape1, index1);
    tensor_write<in_t>(output, shape, index, value);
}
----

*Supported Data Types:*

|===
|Profile|Mode|in_t

|Any|Boolean|bool_t
|Any|signed 8|int8_t
|Any|signed 16|int16_t
|Any|signed 32|int32_t
|MI, MT|floating-point|float_t
|===

==== 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.

*Arguments:*

|===
|Argument|Type|Name|Shape|Description

|Input|in_t*|input1|shape1|Input tensor
|Attribute|int|new_shape|[rank(output)]|List of values, with each element giving the size of the result tensor for the given dimension. At most one dimension may be given as -1 to automatically calculate the dimension size.
|Output|in_t*|output|shape|Output tensor of same type, size as the input tensor
|===

*Operation Function:*

[source,c++]
----
ERROR_IF(tensor_size(shape1) != tensor_size(shape));
for(i = 0; i < tensor_size(shape); i++) {
    output[i] = input[i];
}
----

*Supported Data Types:*

|===
|Profile|Mode|in_t

|Any|Boolean|bool_t
|Any|signed 8|int8_t
|Any|signed 16|int16_t
|Any|signed 32|int32_t
|MI, MT|floating-point|float_t
|===

==== 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.

*Arguments:*

|===
|Argument|Type|Name|Shape|Description

|Input|in_t*|input|shape|Input tensor from 1 to 4 dims
|Attribute|int|axis|-|Axis to reverse, in range from 0 to rank(shape)-1
|Output|in_t*|output|shape|Output tensor. Same shape as input tensor.
|===

*Operation Function:*

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

*Supported Data Types:*

|===
|Profile|Mode|in_t

|Any|Boolean|bool_t
|Any|signed 8|int8_t
|Any|signed 16|int16_t
|Any|signed 32|int32_t
|MI, MT|floating-point|float_t
|===

==== SLICE

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

*Arguments:*
|===
|Argument|Type|Name|Shape|Description

|Input|in_t*|input1|shape1|Input tensor with rank from 1 to 4
|Attribute|int|start|[rank(input1)]|List of integer coordinates, of length equal to the rank of input1. Start coordinate for slicing.
|Attribute|int|size|[rank(input1)]|List of integer size values, of length equal to the rank of input1. Size of the input to be used.
|Output|in_t*|output|shape|Output tensor of same type as the input tensor
|===

*Operation Function:*

[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) {
    tmp_index = index;
    for(i = 0; i < rank(shape); i++) {
       tmp_index[i] = index[i] + start[i];
    }
    in_t value = tensor_read<in_t>(input, shape1, tmp_index);
    tensor_write<in_t>(output, shape, index, value);
}
----

*Supported Data Types:*

|===
|Profile|Mode|in_t

|Any|Boolean|bool_t
|Any|signed 8|int8_t
|Any|signed 16|int16_t
|Any|signed 32|int32_t
|MI, MT|floating-point|float_t
|===

==== TILE

Replicates input1 multiplies times along each dimension.

*Arguments:*

|===
|Argument|Type|Name|Shape|Description

|Input|in_t*|input1|shape1|Input tensor with rank from 1 to 4
|Attribute|int32_t|multiplies|[rank(shape1)]|Number of times to replicate input1 in each dimension
|Output|in_t*|output|shape|Output tensor of same type, rank as the input tensor
|===

*Operation Function:*

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

*Supported Data Types:*

|===
|Profile|Mode|in_t

|Any|Boolean|bool_t
|Any|signed 8|int8_t
|Any|signed 16|int16_t
|Any|signed 32|int32_t
|MI, MT|floating-point|float_t
|===

==== TRANSPOSE

Permutes the dimensions based on perm.

*Arguments:*

|===
|Argument|Type|Name|Shape|Description

|Input|in_t*|input1|shape1|Input tensor with minimum rank of one.
|Attribute|int32_t|perms|[rank(input1)]|List of integers of length equal to the rank of input1. Values must be valid dimensions within shape1, and may not be repeated.
|Output|in_t*|output|shape|Output tensor of same type, rank as the input tensor
|===

*Operation Function:*

[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;
}
for_each(index in shape) {
    tmp_index = index;
    for(i = 0; i < rank(shape); i++) {
        REQUIRE(shape1[perm[i]] == shape[i])
        tmp_index[perm[i]] = index[i]
    }
    in_t value = tensor_read<in_t>(input, shape1, tmp_index);
    tensor_write<in_t>(output, shape, index, value);
}
----

*Supported Data Types:*

|===
|Profile|Mode|in_t

|Any|Boolean|bool_t
|Any|signed 8|int8_t
|Any|signed 16|int16_t
|Any|signed 32|int32_t
|MI, MT|floating-point|float_t
|===
