From 684f7bc6b6ad413edbb49d80698a01559ba0702c Mon Sep 17 00:00:00 2001
From: cmoineau <cyril.moineau@cea.fr>
Date: Tue, 24 Sep 2024 12:15:14 +0000
Subject: [PATCH] Add AtanImpl for forward & backward, add unit test for
 forward.

---
 include/aidge/backend/cpu.hpp                 |  2 +
 .../aidge/backend/cpu/operator/AtanImpl.hpp   | 33 ++++++++
 .../backend/cpu/operator/AtanImpl_kernels.hpp | 60 +++++++++++++++
 src/operator/AtanImpl.cpp                     | 54 +++++++++++++
 unit_tests/operator/Test_Atan.cpp             | 77 +++++++++++++++++++
 5 files changed, 226 insertions(+)
 create mode 100644 include/aidge/backend/cpu/operator/AtanImpl.hpp
 create mode 100644 include/aidge/backend/cpu/operator/AtanImpl_kernels.hpp
 create mode 100644 src/operator/AtanImpl.cpp
 create mode 100644 unit_tests/operator/Test_Atan.cpp

diff --git a/include/aidge/backend/cpu.hpp b/include/aidge/backend/cpu.hpp
index 963895c1..712d5203 100644
--- a/include/aidge/backend/cpu.hpp
+++ b/include/aidge/backend/cpu.hpp
@@ -15,6 +15,8 @@
 #include "aidge/backend/cpu/operator/AbsImpl.hpp"
 #include "aidge/backend/cpu/operator/AddImpl.hpp"
 #include "aidge/backend/cpu/operator/AndImpl.hpp"
+#include "aidge/backend/cpu/operator/AtanImpl.hpp"
+
 #include "aidge/backend/cpu/operator/ArgMaxImpl.hpp"
 #include "aidge/backend/cpu/operator/AvgPoolingImpl.hpp"
 #include "aidge/backend/cpu/operator/MaxPoolingImpl.hpp"
diff --git a/include/aidge/backend/cpu/operator/AtanImpl.hpp b/include/aidge/backend/cpu/operator/AtanImpl.hpp
new file mode 100644
index 00000000..2f1b4bf0
--- /dev/null
+++ b/include/aidge/backend/cpu/operator/AtanImpl.hpp
@@ -0,0 +1,33 @@
+/********************************************************************************
+ * 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
+ *
+ ********************************************************************************/
+
+#ifndef AIDGE_CPU_OPERATOR_ATAN_H_
+#define AIDGE_CPU_OPERATOR_ATAN_H_
+
+#include "aidge/backend/cpu/operator/OperatorImpl.hpp"
+#include "aidge/operator/Atan.hpp"
+#include "aidge/utils/Registrar.hpp"
+#include "aidge/utils/Types.h"
+#include "aidge/backend/cpu/data/GetCPUPtr.h"
+#include <memory>
+#include <vector>
+
+namespace Aidge {
+// Operator implementation entry point for the backend
+using AtanImpl_cpu = OperatorImpl_cpu<Atan_Op,
+    void(const std::size_t, const void*, void*),
+    void(const std::size_t, const void*, const void*, void*)>;
+
+// Implementation entry point registration to Operator
+REGISTRAR(Atan_Op, "cpu", Aidge::AtanImpl_cpu::create);
+}  // namespace Aidge
+
+#endif /* AIDGE_CPU_OPERATOR_ATAN_H_ */
diff --git a/include/aidge/backend/cpu/operator/AtanImpl_kernels.hpp b/include/aidge/backend/cpu/operator/AtanImpl_kernels.hpp
new file mode 100644
index 00000000..2a786339
--- /dev/null
+++ b/include/aidge/backend/cpu/operator/AtanImpl_kernels.hpp
@@ -0,0 +1,60 @@
+/********************************************************************************
+ * 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
+ *
+ ********************************************************************************/
+
+#ifndef AIDGE_CPU_OPERATOR_ATANIMPL_KERNELS_H_
+#define AIDGE_CPU_OPERATOR_ATANIMPL_KERNELS_H_
+
+#include "aidge/utils/Registrar.hpp"
+
+#include "aidge/backend/cpu/operator/AtanImpl.hpp"
+#include <cmath>  // For atan()
+
+
+namespace Aidge {
+template <class I, class O>
+void AtanImpl_cpu_forward_kernel(std::size_t inputLenght,
+                                    const void* input_,
+                                    void* output_) {
+    const I* input = static_cast<const I*>(input_);
+    O* output = static_cast<O*>(output_);
+
+    for (size_t i = 0; i < inputLenght; ++i) {
+        output[i] = static_cast<O>(atan(input[i]));
+    }
+
+}
+
+template <class O, class GI, class GO>
+void AtanImpl_cpu_backward_kernel(const std::size_t inputLenght,
+                                     const void* output_, const void* grad_output_,
+				     void* grad_input_) {
+    const O* output = static_cast<const O*>(output_);
+    const GO* grad_output = static_cast<const GO*>(grad_output_);
+    GI* grad_input = static_cast<GI*>(grad_input_);
+
+    // Apply the derivative of atan for each element in the input array
+    for (size_t i = 0; i < inputLenght; ++i) {
+        // dx = dy * (1 / (1 + x^2))
+        grad_input[i] = grad_output[i] * static_cast<O>(1.0 / (1.0 + output[i] * output[i]));
+    }
+}
+
+
+// Kernels registration to implementation entry point
+REGISTRAR(AtanImpl_cpu,
+    {DataType::Float32},
+    {ProdConso::inPlaceModel, Aidge::AtanImpl_cpu_forward_kernel<float, float>, Aidge::AtanImpl_cpu_backward_kernel<float, float, float>});
+REGISTRAR(AtanImpl_cpu,
+    {DataType::Float64},
+    {ProdConso::inPlaceModel, Aidge::AtanImpl_cpu_forward_kernel<double, double>, Aidge::AtanImpl_cpu_backward_kernel<double, double, double>});
+}  // namespace Aidge
+
+#endif /* AIDGE_CPU_OPERATOR_ATANIMPL_KERNELS_H_ */
diff --git a/src/operator/AtanImpl.cpp b/src/operator/AtanImpl.cpp
new file mode 100644
index 00000000..af3393e7
--- /dev/null
+++ b/src/operator/AtanImpl.cpp
@@ -0,0 +1,54 @@
+/********************************************************************************
+ * 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 <cassert>
+#include <chrono>  // std::chrono::milliseconds
+#include <numeric> // std::accumulate
+#include <thread>  // std::this_thread::sleep_for
+#include <vector>
+
+#include "aidge/operator/Atan.hpp"
+#include "aidge/utils/Types.h"
+#include "aidge/backend/cpu/data/GetCPUPtr.h"
+
+#include "aidge/backend/cpu/operator/AtanImpl.hpp"
+#include "aidge/backend/cpu/operator/AtanImpl_kernels.hpp"
+
+template <>
+void Aidge::AtanImpl_cpu::forward() {
+	const Atan_Op& op_ = dynamic_cast<const Atan_Op&>(mOp);
+    std::shared_ptr<Tensor> in0 = op_.getInput(0);
+    std::shared_ptr<Tensor> out0 = op_.getOutput(0);
+    AIDGE_ASSERT(in0, "missing input #0");
+
+    // Find the correct kernel type
+    const auto impl = Registrar<AtanImpl_cpu>::create(getBestMatch(getRequiredSpec()));
+
+    // Call kernel
+    impl.forward(in0->size(),
+        getCPUPtr(mOp.getRawInput(0)),
+        getCPUPtr(mOp.getRawOutput(0)));
+}
+
+template <>
+void Aidge::AtanImpl_cpu::backward() {
+    const Atan_Op& op_ = dynamic_cast<const Atan_Op&>(mOp);
+    std::shared_ptr<Tensor> out0  = op_.getOutput(0);
+    std::shared_ptr<Tensor> gra_int0 = op_.getInput(0)->grad();
+    std::shared_ptr<Tensor> gra_out0 = op_.getOutput(0)->grad();
+    AIDGE_ASSERT(out0, "missing output #0 for current {} operator", op_.type());
+
+    // Find the correct kernel type
+    const auto impl = Registrar<AtanImpl_cpu>::create(getBestMatch(getRequiredSpec()));
+
+    // Call kernel
+    impl.backward(gra_int0->size(), getCPUPtr(out0), getCPUPtr(gra_out0), getCPUPtr(gra_int0));
+}
diff --git a/unit_tests/operator/Test_Atan.cpp b/unit_tests/operator/Test_Atan.cpp
new file mode 100644
index 00000000..9548e35d
--- /dev/null
+++ b/unit_tests/operator/Test_Atan.cpp
@@ -0,0 +1,77 @@
+/********************************************************************************
+ * 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 <catch2/catch_test_macros.hpp>
+
+#include "aidge/data/Tensor.hpp"
+#include "aidge/operator/Atan.hpp"
+
+#include "aidge/backend/cpu.hpp"
+
+#include <memory>
+
+using namespace Aidge;
+
+TEST_CASE("[cpu/operator] Atan(forward)") {
+  SECTION("1D Tensor") {
+    std::shared_ptr<Tensor> input0 =
+        std::make_shared<Tensor>(Array1D<float, 10>{
+            {0.41384590, 0.43120754, 0.93762982, 0.31049860, 0.77547199,
+             0.09514862, 0.16145366, 0.42776686, 0.43487436, 0.41170865}});
+    std::shared_ptr<Tensor> expectedOutput =
+        std::make_shared<Tensor>(Array1D<float, 10>{
+            {0.39238522, 0.40711672, 0.75322037, 0.30106049, 0.65960488,
+             0.09486303, 0.16007232, 0.40421187, 0.4102045, 0.39055911}});
+
+    std::shared_ptr<Node> myAtan = Atan();
+    auto op = std::static_pointer_cast<OperatorTensor>(myAtan->getOperator());
+    op->associateInput(0, input0);
+    op->setDataType(DataType::Float32);
+    op->setBackend("cpu");
+    myAtan->forward();
+
+    float* resPtr = static_cast<float*>(op->getOutput(0)->getImpl()->rawPtr());
+    float* expectedPtr =
+        static_cast<float*>(expectedOutput->getImpl()->rawPtr());
+    for (std::size_t i = 0; i < expectedOutput->size(); ++i) {
+      REQUIRE(std::abs(resPtr[i] - expectedPtr[i]) < 0.00001);
+    }
+  }
+
+  SECTION("3D Tensor") {
+    std::shared_ptr<Tensor> input0 = std::make_shared<Tensor>(
+        Array3D<float, 2, 2, 3>{{{
+                                     {0.97037154, 0.86208081, 0.77767169},
+                                     {0.38160080, 0.11422747, 0.77284443},
+                                 },
+                                 {{0.51592529, 0.72543722, 0.54641193},
+                                  {0.93866944, 0.97767913, 0.34172094}}}});
+    std::shared_ptr<Tensor> expectedOutput = std::make_shared<Tensor>(
+        Array3D<float, 2, 2, 3>{{{{0.77036231, 0.71146592, 0.66097706},
+                                  {0.36454508, 0.11373451, 0.65796196}},
+                                 {{0.47630652, 0.62759472, 0.50008428},
+                                  {0.75377332, 0.77411225, 0.32928031}}}});
+
+    std::shared_ptr<Node> myAtan = Atan();
+    auto op = std::static_pointer_cast<OperatorTensor>(myAtan->getOperator());
+    op->associateInput(0, input0);
+    op->setDataType(DataType::Float32);
+    op->setBackend("cpu");
+    myAtan->forward();
+
+    float* resPtr = static_cast<float*>(op->getOutput(0)->getImpl()->rawPtr());
+    float* expectedPtr =
+        static_cast<float*>(expectedOutput->getImpl()->rawPtr());
+    for (std::size_t i = 0; i < expectedOutput->size(); ++i) {
+      REQUIRE(std::abs(resPtr[i] - expectedPtr[i]) < 0.00001);
+    }
+  }
+}
-- 
GitLab