Skip to content
Snippets Groups Projects
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
Test_SubImpl.cpp 14.88 KiB
/********************************************************************************
 * Copyright (c) 2023 CEA-List
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 ********************************************************************************/

#include <chrono>      // std::micro, std::chrono::time_point,
                       // std::chrono::system_clock
#include <cstddef>     // std::size_t
#include <cstdint>     // std::uint16_t
#include <functional>  // std::multiplies
#include <memory>
#include <numeric>     // std::accumulate
#include <random>      // std::random_device, std::mt19937
                       // std::uniform_int_distribution, std::uniform_real_distribution
#include <vector>

#include <catch2/catch_test_macros.hpp>
#include <fmt/core.h>

#include "aidge/backend/cpu/data/TensorImpl.hpp"
#include "aidge/backend/cpu/operator/SubImpl.hpp"
#include "aidge/data/Data.hpp"
#include "aidge/data/Tensor.hpp"
#include "aidge/operator/Sub.hpp"
#include "aidge/operator/OperatorTensor.hpp"
#include "aidge/utils/TensorUtils.hpp"

namespace Aidge {

TEST_CASE("[cpu/operator] Sub", "[Sub][CPU]") {
    constexpr std::uint16_t NBTRIALS = 10;
    // Create a random number generator
    std::random_device rd;
    std::mt19937 gen(rd());
    std::uniform_real_distribution<float> valueDist(0.1f, 1.1f); // Random float distribution between 0 and 1
    std::uniform_int_distribution<std::size_t> dimSizeDist(std::size_t(2), std::size_t(10));
    std::uniform_int_distribution<std::size_t> nbDimsDist(std::size_t(1), std::size_t(5));
    std::uniform_int_distribution<int> boolDist(0,1);

    // Create MatMul Operator
    std::shared_ptr<Node> mySub = Sub();
    auto op = std::static_pointer_cast<OperatorTensor>(mySub-> getOperator());
    op->setDataType(DataType::Float32);
    op->setBackend("cpu");

    // Create 2 input Tensors
    std::shared_ptr<Tensor> T0 = std::make_shared<Tensor>();
    op->associateInput(0,T0);
    T0->setDataType(DataType::Float32);
    T0->setBackend("cpu");
    std::shared_ptr<Tensor> T1 = std::make_shared<Tensor>();
    op -> associateInput(1,T1);
    T1->setDataType(DataType::Float32);
    T1->setBackend("cpu");

    // Create results Tensor
    std::shared_ptr<Tensor> Tres = std::make_shared<Tensor>();
    Tres->setDataType(DataType::Float32);
    Tres->setBackend("cpu");

    // To measure execution time of 'MatMul_Op::forward()' member function call
    std::chrono::time_point<std::chrono::system_clock> start;
    std::chrono::time_point<std::chrono::system_clock> end;
    std::chrono::duration<double, std::micro> duration{};

    SECTION("SubImpl_cpu::forward()") {
        SECTION("Scalar / Scalar") {

        }
        SECTION("Scalar / +1-D Tensor") {

        }
        SECTION("+1-D Tensor / +1-D Tensor - same dimensions") {
            std::size_t number_of_operation = 0;

            for (std::uint16_t trial = 0; trial < NBTRIALS; ++trial) {
                // generate 2 random Tensors
                const std::size_t nbDims = nbDimsDist(gen);
                std::vector<std::size_t> dims;
                for (std::size_t i = 0; i < nbDims; ++i) {
                    dims.push_back(dimSizeDist(gen));
                }
                const std::size_t nb_elements = std::accumulate(dims.cbegin(), dims.cend(), std::size_t(1), std::multiplies<std::size_t>());
                number_of_operation += nb_elements;

                // without broadcasting
                float* array0 = new float[nb_elements];
                float* array1 = new float[nb_elements];
                float* result = new float[nb_elements];

                for (std::size_t i = 0; i < nb_elements; ++i) {
                    array0[i] = valueDist(gen);
                    array1[i] = valueDist(gen);
                    result[i] = array0[i] - array1[i];
                }

                // input0
                T0->resize(dims);
                T0 -> getImpl() -> setRawPtr(array0, nb_elements);

                // input1
                T1->resize(dims);
                T1 -> getImpl() -> setRawPtr(array1, nb_elements);

                // results
                Tres->resize(dims);
                Tres -> getImpl() -> setRawPtr(result, nb_elements);

                op->forwardDims();
                start = std::chrono::system_clock::now();
                mySub->forward();
                end = std::chrono::system_clock::now();
                duration += std::chrono::duration_cast<std::chrono::microseconds>(end - start);

                REQUIRE(approxEq<float>(*(op->getOutput(0)), *Tres));

                delete[] array0;
                delete[] array1;
                delete[] result;

                // with broadcasting
            }
            Log::info("number of elements over time spent: {}\n", (number_of_operation / duration.count()));
            Log::info("total time: {}μs\n", duration.count());
        }

        SECTION("+1-D Tensor / +1-D Tensor - broadcasting") {
            std::size_t number_of_operation = 0;

            for (std::uint16_t trial = 0; trial < NBTRIALS; ++trial) {
                // generate 2 random Tensors
                // handle dimensions, replace some dimensions with '1' to get broadcasting
                constexpr std::size_t nbDims = 4;
                std::vector<std::size_t> dims;
                for (std::size_t i = 0; i < nbDims; ++i) {
                    dims.push_back(dimSizeDist(gen));
                }
                std::vector<std::size_t> dims0 = dims;
                std::vector<std::size_t> dims1 = dims;
                std::vector<std::size_t> dimsOut = dims;
                for (std::size_t i = 0; i < nbDims; ++i) {
                    if (boolDist(gen)) {
                        dims0[i] = 1;
                    }
                    if (boolDist(gen)) {
                        dims1[i] = 1;
                    }
                    dimsOut[i] = (dims0[i] == 1) ? dims1[i] : dims0[i];
                }

                // create arrays and fill them with random values
                float* array0 = new float[dims0[0]*dims0[1]*dims0[2]*dims0[3]];
                float* array1 = new float[dims1[0]*dims1[1]*dims1[2]*dims1[3]];
                float* result = new float[dimsOut[0]*dimsOut[1]*dimsOut[2]*dimsOut[3]];

                for (std::size_t i = 0; i < dims0[0]*dims0[1]*dims0[2]*dims0[3]; ++i) {
                    array0[i] = valueDist(gen);
                }
                for (std::size_t i = 0; i < dims1[0]*dims1[1]*dims1[2]*dims1[3]; ++i) {
                    array1[i] = valueDist(gen);
                }

                // compute true result
                const std::size_t strides0[nbDims] = {dims0[1]*dims0[2]*dims0[3], dims0[2]*dims0[3], dims0[3], 1};
                const std::size_t strides1[nbDims] = {dims1[1]*dims1[2]*dims1[3], dims1[2]*dims1[3], dims1[3], 1};
                for (std::size_t a = 0; a < dimsOut[0]; ++a) {
                    for (std::size_t b = 0; b < dimsOut[1]; ++b) {
                        const std::size_t idx0_0 = strides0[0] * ((dims0[0] > 1) ? a : 0)
                                                    + strides0[1] * ((dims0[1] > 1) ? b : 0);
                        const std::size_t idx1_0 = strides1[0] * ((dims1[0] > 1) ? a : 0)
                                                    + strides1[1] * ((dims1[1] > 1) ? b : 0);
                        for (std::size_t c = 0; c < dimsOut[2]; ++c) {
                            const std::size_t idx_out = dimsOut[3] * (c + dimsOut[2] * (b + dimsOut[1] * a));
                            for (std::size_t d = 0; d < dimsOut[3]; ++d) {
                                std::size_t idx0 = idx0_0
                                                    + strides0[2] * ((dims0[2] > 1) ? c : 0)
                                                    + ((dims0[3] > 1) ? d : 0);
                                std::size_t idx1 = idx1_0
                                                    + strides1[2] * ((dims1[2] > 1) ? c : 0)
                                                    + ((dims1[3] > 1) ? d : 0);
                                result[idx_out + d] = array0[idx0] - array1[idx1];
                                // std::cout << "(" << idx0 << ", " << idx1 << ") -> " << array0[idx0] << " - " << array1[idx1] << " -> " << idx_out + d << std::endl;
                            }
                        }
                    }
                }

                // conversion to Aidge::Tensors
                // input0
                T0->resize(dims0);
                T0 -> getImpl() -> setRawPtr(array0, dims0[0]*dims0[1]*dims0[2]*dims0[3]);

                // input1
                T1->resize(dims1);
                T1 -> getImpl() -> setRawPtr(array1, dims1[0]*dims1[1]*dims1[2]*dims1[3]);

                // results
                Tres->resize(dimsOut);
                Tres -> getImpl() -> setRawPtr(result, dimsOut[0]*dimsOut[1]*dimsOut[2]*dimsOut[3]);

                // compute result
                op->forwardDims();
                start = std::chrono::system_clock::now();
                mySub->forward();
                end = std::chrono::system_clock::now();
                duration += std::chrono::duration_cast<std::chrono::microseconds>(end - start);

                // comparison between truth and computed result
                REQUIRE(approxEq<float>(*(op->getOutput(0)), *Tres));

                delete[] array0;
                delete[] array1;
                delete[] result;

                const std::size_t nb_elements = std::accumulate(dimsOut.cbegin(), dimsOut.cend(), std::size_t(1), std::multiplies<std::size_t>());
                number_of_operation += nb_elements;
            }
            Log::info("number of elements over time spent: {}\n", (number_of_operation / duration.count()));
            Log::info("total time: {}μs\n", duration.count());
        }
        SECTION("+1-D Tensor / 1-D Tensor") {
            std::size_t number_of_operation = 0;
            std::uniform_int_distribution<std::size_t> nbRemovedDimsDist(std::size_t(1), std::size_t(3));

            for (std::uint16_t trial = 0; trial < NBTRIALS; ++trial) {
                // generate 2 random Tensors
                // handle dimensions
                constexpr std::size_t nbDims = 4;
                std::vector<std::size_t> dims0(4);
                for (std::size_t i = 0; i < nbDims; ++i) {
                    dims0[i] = dimSizeDist(gen);
                }
                std::vector<std::size_t> dimsOut = dims0;
                std::vector<std::size_t> dims1 = dims0;
                for (std::size_t i = 0; i < nbDims; ++i) {
                    if (boolDist(gen)) {
                        dims1[i] = 1;
                    }
                }
                dims1.erase(dims1.cbegin(), dims1.cbegin() + nbRemovedDimsDist(gen));

                // create arrays and fill them with random values
                float* array0 = new float[dims0[0]*dims0[1]*dims0[2]*dims0[3]];
                std::size_t array1_size = std::accumulate(dims1.cbegin(), dims1.cend(), std::size_t(1), std::multiplies<std::size_t>());
                float* array1 = new float[array1_size];
                float* result = new float[dimsOut[0]*dimsOut[1]*dimsOut[2]*dimsOut[3]];

                for (std::size_t i = 0; i < (dims0[0]*dims0[1]*dims0[2]*dims0[3]); ++i) {
                    array0[i] = valueDist(gen);
                }
                for (std::size_t i = 0; i < array1_size; ++i) {
                    array1[i] = valueDist(gen);
                }

                // compute true result
                auto dims1_tmp = dims1;
                dims1_tmp.insert(dims1_tmp.cbegin(), 4 - dims1_tmp.size(), std::size_t(1));

                const std::size_t strides0[nbDims] = {dims0[1]*dims0[2]*dims0[3], dims0[2]*dims0[3], dims0[3], 1};
                const std::size_t strides1[nbDims] = {dims1_tmp[1]*dims1_tmp[2]*dims1_tmp[3], dims1_tmp[2]*dims1_tmp[3], dims1_tmp[3], 1};
                for (std::size_t a = 0; a < dimsOut[0]; ++a) {
                    for (std::size_t b = 0; b < dimsOut[1]; ++b) {
                        const std::size_t idx0_0 = strides0[0] * ((dims0[0] > 1) ? a : 0)
                                                    + strides0[1] * ((dims0[1] > 1) ? b : 0);
                        const std::size_t idx1_0 = strides1[0] * ((dims1_tmp[0] > 1) ? a : 0)
                                                    + strides1[1] * ((dims1_tmp[1] > 1) ? b : 0);
                        for (std::size_t c = 0; c < dimsOut[2]; ++c) {
                            const std::size_t idx_out = dimsOut[3] * (c + dimsOut[2] * (b + dimsOut[1] * a));
                            for (std::size_t d = 0; d < dimsOut[3]; ++d) {
                                std::size_t idx0 = idx0_0
                                                    + strides0[2] * ((dims0[2] > 1) ? c : 0)
                                                    + ((dims0[3] > 1) ? d : 0);
                                std::size_t idx1 = idx1_0
                                                    + strides1[2] * ((dims1_tmp[2] > 1) ? c : 0)
                                                    + ((dims1_tmp[3] > 1) ? d : 0);
                                result[idx_out + d] = array0[idx0] - array1[idx1];
                                // std::cout << "(" << idx0 << ", " << idx1 << ") -> " << array0[idx0] << " - " << array1[idx1] << " -> " << idx_out + d << std::endl;
                            }
                        }
                    }
                }

                // conversion to Aidge::Tensors
                // input0
                T0->resize(dims0);
                T0 -> getImpl() -> setRawPtr(array0, dims0[0]*dims0[1]*dims0[2]*dims0[3]);

                // input1
                T1->resize(dims1);
                T1 -> getImpl() -> setRawPtr(array1, array1_size);

                // results
                Tres->resize(dimsOut);
                Tres -> getImpl() -> setRawPtr(result, dimsOut[0]*dimsOut[1]*dimsOut[2]*dimsOut[3]);

                // compute result
                op->forwardDims();
                start = std::chrono::system_clock::now();
                mySub->forward();
                end = std::chrono::system_clock::now();
                duration += std::chrono::duration_cast<std::chrono::microseconds>(end - start);

                // comparison between truth and computed result
                REQUIRE(approxEq<float>(*(op->getOutput(0)), *Tres));

                delete[] array0;
                delete[] array1;
                delete[] result;

                const std::size_t nb_elements = std::accumulate(dimsOut.cbegin(), dimsOut.cend(), std::size_t(1), std::multiplies<std::size_t>());
                number_of_operation += nb_elements;
            }

            Log::info("number of elements over time spent: {}\n", (number_of_operation / duration.count()));
            Log::info("total time: {}μs\n", duration.count());
        }
    }
}
} // namespace Aidge