From b56a1d1d57c977e645e6cd65ce7bd21c02bc0920 Mon Sep 17 00:00:00 2001
From: Manuel Weberndorfer <manuel.weberndorfer@id.ethz.ch>
Date: Tue, 26 Oct 2021 11:01:14 +0000
Subject: [PATCH] add cpppetsc::LeastSquaresSolver

---
 cpppetsc/src/CMakeLists.txt                   |   1 +
 cpppetsc/src/LeastSquaresSolver.cc            |  32 +++
 .../ae108/cpppetsc/LeastSquaresSolver.h       | 186 ++++++++++++++++++
 cpppetsc/test/CMakeLists.txt                  |   1 +
 cpppetsc/test/LeastSquaresSolver_Test.cc      |  88 +++++++++
 5 files changed, 308 insertions(+)
 create mode 100644 cpppetsc/src/LeastSquaresSolver.cc
 create mode 100644 cpppetsc/src/include/ae108/cpppetsc/LeastSquaresSolver.h
 create mode 100644 cpppetsc/test/LeastSquaresSolver_Test.cc

diff --git a/cpppetsc/src/CMakeLists.txt b/cpppetsc/src/CMakeLists.txt
index 8249b2b2..b0761fa4 100644
--- a/cpppetsc/src/CMakeLists.txt
+++ b/cpppetsc/src/CMakeLists.txt
@@ -20,6 +20,7 @@ add_library(${PROJECT_NAME}-cpppetsc
             ./Exception.cc
             ./GeneralizedMeshBoundaryConditition.cc
             ./InvalidParametersException.cc
+            ./LeastSquaresSolver.cc
             ./LinearSolver.cc
             ./LinearSolverDivergedException.cc
             ./LocalVertexView.cc
diff --git a/cpppetsc/src/LeastSquaresSolver.cc b/cpppetsc/src/LeastSquaresSolver.cc
new file mode 100644
index 00000000..cc9f275a
--- /dev/null
+++ b/cpppetsc/src/LeastSquaresSolver.cc
@@ -0,0 +1,32 @@
+// © 2020, 2021 ETH Zurich, Mechanics and Materials Lab
+// © 2020 California Institute of Technology
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "ae108/cpppetsc/LeastSquaresSolver.h"
+
+#ifndef AE108_PETSC_COMPLEX
+
+#include "ae108/cpppetsc/ParallelComputePolicy.h"
+#include "ae108/cpppetsc/SequentialComputePolicy.h"
+
+namespace ae108 {
+namespace cpppetsc {
+
+template class LeastSquaresSolver<SequentialComputePolicy>;
+template class LeastSquaresSolver<ParallelComputePolicy>;
+
+} // namespace cpppetsc
+} // namespace ae108
+
+#endif
\ No newline at end of file
diff --git a/cpppetsc/src/include/ae108/cpppetsc/LeastSquaresSolver.h b/cpppetsc/src/include/ae108/cpppetsc/LeastSquaresSolver.h
new file mode 100644
index 00000000..1be860cb
--- /dev/null
+++ b/cpppetsc/src/include/ae108/cpppetsc/LeastSquaresSolver.h
@@ -0,0 +1,186 @@
+// © 2020, 2021 ETH Zurich, Mechanics and Materials Lab
+// © 2020 California Institute of Technology
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma once
+
+#ifndef AE108_PETSC_COMPLEX
+
+#include "ae108/cpppetsc/Matrix.h"
+#include "ae108/cpppetsc/ParallelComputePolicy_fwd.h"
+#include "ae108/cpppetsc/SequentialComputePolicy_fwd.h"
+#include "ae108/cpppetsc/TAOSolverDivergedException.h"
+#include "ae108/cpppetsc/TaggedVector.h"
+#include "ae108/cpppetsc/UniqueEntity.h"
+#include "ae108/cpppetsc/Vector.h"
+#include <functional>
+#include <petscmat.h>
+#include <petscmath.h>
+#include <petscsys.h>
+#include <petsctao.h>
+#include <petscvec.h>
+
+namespace ae108 {
+namespace cpppetsc {
+
+template <class Policy> class LeastSquaresSolver {
+public:
+  using vector_type = Vector<Policy>;
+  using matrix_type = Matrix<Policy>;
+
+  using size_type = typename Vector<Policy>::size_type;
+  using value_type = typename Vector<Policy>::value_type;
+  using real_type = typename Vector<Policy>::real_type;
+
+  /**
+   * @brief Initializes the nonlinear solver with the problem dimension.
+   *
+   * @param bufferMatrix The matrix to use as an internal buffer.
+   * @param bufferVector The matrix to use as an internal buffer.
+   */
+  explicit LeastSquaresSolver(matrix_type bufferMatrix,
+                              distributed<vector_type> bufferVector);
+
+  /**
+   * @remark The first parameter is the input, the second parameter is the
+   * output.
+   */
+  using ObjectiveFunctor = std::function<void(const distributed<vector_type> &,
+                                              distributed<vector_type> *)>;
+
+  /**
+   * @remark The first parameter is the input, the second parameter is the
+   * output.
+   */
+  using GradientFunctor =
+      std::function<void(const distributed<vector_type> &, matrix_type *)>;
+
+  /**
+   * @brief Minimizes the sum of the squared elements of the objective function.
+   *
+   * @param objective The function to minimize.
+   * @param gradient The gradient of the objective.
+   * @param guess The starting point for the optimization.
+   *
+   * @throw TAOSolverDivergedException if solve did not converge.
+   *
+   * @return The minimizer.
+   */
+  distributed<vector_type> solve(ObjectiveFunctor objective,
+                                 GradientFunctor gradient,
+                                 distributed<vector_type> guess) const;
+
+private:
+  static PetscErrorCode objectiveAdapter(Tao, Vec, Vec, void *);
+  static PetscErrorCode gradientAdapter(Tao, Vec, Mat, Mat, void *);
+
+  static Tao createTao();
+
+  /**
+   * @throw TAOSolverDivergedException if solve did not converge.
+   */
+  void checkConvergence() const;
+
+  UniqueEntity<Tao> _tao;
+
+  matrix_type _buffer_matrix;
+  distributed<vector_type> _buffer_vector;
+};
+
+extern template class LeastSquaresSolver<SequentialComputePolicy>;
+extern template class LeastSquaresSolver<ParallelComputePolicy>;
+} // namespace cpppetsc
+} // namespace ae108
+
+/********************************************************************
+ *  implementations
+ *******************************************************************/
+
+namespace ae108 {
+namespace cpppetsc {
+template <class Policy> Tao LeastSquaresSolver<Policy>::createTao() {
+  Tao tao;
+  Policy::handleError(TaoCreate(Policy::communicator(), &tao));
+  return tao;
+}
+
+template <class Policy>
+LeastSquaresSolver<Policy>::LeastSquaresSolver(
+    matrix_type bufferMatrix, distributed<vector_type> bufferVector)
+    : _tao(makeUniqueEntity<Policy>(createTao())),
+      _buffer_matrix(std::move(bufferMatrix)),
+      _buffer_vector(std::move(bufferVector)) {
+  Policy::handleError(TaoSetFromOptions(_tao.get()));
+  Policy::handleError(TaoSetType(_tao.get(), TAOBRGN));
+  Policy::handleError(TaoBRGNSetRegularizerWeight(_tao.get(), 0.));
+}
+
+template <class Policy>
+void LeastSquaresSolver<Policy>::checkConvergence() const {
+  const auto errorCode = [this]() {
+    auto reason = TaoConvergedReason{};
+    Policy::handleError(TaoGetConvergedReason(_tao.get(), &reason));
+    return reason;
+  }();
+
+  if (errorCode < 0) {
+    throw TAOSolverDivergedException{};
+  }
+}
+
+template <class Policy>
+distributed<typename LeastSquaresSolver<Policy>::vector_type>
+LeastSquaresSolver<Policy>::solve(ObjectiveFunctor objective,
+                                  GradientFunctor gradient,
+                                  distributed<vector_type> guess) const {
+  Policy::handleError(
+      TaoSetResidualRoutine(_tao.get(), _buffer_vector.unwrap().data(),
+                            &LeastSquaresSolver::objectiveAdapter, &objective));
+  Policy::handleError(TaoSetJacobianResidualRoutine(
+      _tao.get(), _buffer_matrix.data(), _buffer_matrix.data(),
+      &LeastSquaresSolver::gradientAdapter, &gradient));
+  Policy::handleError(TaoSetInitialVector(_tao.get(), guess.unwrap().data()));
+  Policy::handleError(TaoSolve(_tao.get()));
+
+  checkConvergence();
+
+  return guess;
+}
+
+template <class Policy>
+PetscErrorCode LeastSquaresSolver<Policy>::objectiveAdapter(Tao, Vec input,
+                                                            Vec output,
+                                                            void *context) {
+  const auto functionPtr = static_cast<ObjectiveFunctor *>(context);
+  distributed<vector_type> wrappedOutput(UniqueEntity<Vec>(output, [](Vec) {}));
+  (*functionPtr)(distributed<vector_type>(UniqueEntity<Vec>(input, [](Vec) {})),
+                 &wrappedOutput);
+  return 0;
+}
+
+template <class Policy>
+PetscErrorCode LeastSquaresSolver<Policy>::gradientAdapter(Tao, Vec input,
+                                                           Mat output, Mat,
+                                                           void *context) {
+  const auto functionPtr = static_cast<GradientFunctor *>(context);
+  matrix_type wrappedOutput(UniqueEntity<Mat>(output, [](Mat) {}));
+  (*functionPtr)(distributed<vector_type>(UniqueEntity<Vec>(input, [](Vec) {})),
+                 &wrappedOutput);
+  return 0;
+}
+
+} // namespace cpppetsc
+} // namespace ae108
+
+#endif
\ No newline at end of file
diff --git a/cpppetsc/test/CMakeLists.txt b/cpppetsc/test/CMakeLists.txt
index b7cf6dc4..2167eeab 100644
--- a/cpppetsc/test/CMakeLists.txt
+++ b/cpppetsc/test/CMakeLists.txt
@@ -17,6 +17,7 @@ cmake_minimum_required(VERSION 3.12 FATAL_ERROR)
 
 add_executable(${PROJECT_NAME}-CppPetscTests
                ./InvalidParametersException_Test.cc
+               ./LeastSquaresSolver_Test.cc
                ./LinearSolverDivergedException_Test.cc
                ./LinearSolver_Test.cc
                ./Matrix_Test.cc
diff --git a/cpppetsc/test/LeastSquaresSolver_Test.cc b/cpppetsc/test/LeastSquaresSolver_Test.cc
new file mode 100644
index 00000000..a5a7d4e1
--- /dev/null
+++ b/cpppetsc/test/LeastSquaresSolver_Test.cc
@@ -0,0 +1,88 @@
+// © 2020, 2021 ETH Zurich, Mechanics and Materials Lab
+// © 2020 California Institute of Technology
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "ae108/cpppetsc/LeastSquaresSolver.h"
+#include "ae108/cpppetsc/ParallelComputePolicy.h"
+#include "ae108/cpppetsc/SequentialComputePolicy.h"
+#include "ae108/cpppetsc/TaggedVector.h"
+#include "ae108/cppptest/Matchers.h"
+#include "ae108/cppptest/isLocal.h"
+#include <gmock/gmock.h>
+
+using ae108::cppptest::isLocal;
+using ae108::cppptest::ScalarNear;
+using testing::Test;
+using testing::Types;
+
+namespace ae108 {
+namespace cpppetsc {
+namespace {
+
+template <class Policy> struct LeastSquaresSolver_Test : Test {
+  using solver_type = LeastSquaresSolver<Policy>;
+  using vector_type = typename solver_type::vector_type;
+  using matrix_type = typename solver_type::matrix_type;
+
+  /**
+   * @brief Solves {x == 1, 2x == 4} by minimizing the squared distance.
+   *
+   * @return The solution.
+   */
+  distributed<vector_type>
+  solveConflictingEquations(const solver_type &solver) const {
+    return solver.solve(
+        [](const distributed<vector_type> &input,
+           distributed<vector_type> *const output) {
+          const auto full = vector_type::fromDistributed(input);
+          const auto replacer = output->unwrap().replace();
+          if (isLocal(output->unwrap(), 0)) {
+            replacer(0) = full(0) - 1.;
+          }
+          if (isLocal(output->unwrap(), 1)) {
+            replacer(1) = 2. * full(0) - 4.;
+          }
+        },
+        [](const distributed<vector_type> &, matrix_type *const output) {
+          const auto replacer = output->assemblyView().replace();
+          if (isLocal(*output, 0)) {
+            replacer(0, 0) = 1.;
+          }
+          if (isLocal(*output, 1)) {
+            replacer(1, 0) = 2.;
+          }
+        },
+        tag<DistributedTag>(vector_type::fromList({7.})));
+  }
+};
+
+using Policies = Types<SequentialComputePolicy, ParallelComputePolicy>;
+TYPED_TEST_CASE(LeastSquaresSolver_Test, Policies);
+
+TYPED_TEST(LeastSquaresSolver_Test,
+           finds_optimal_solution_for_conflicting_equations) {
+  using matrix_type = typename TestFixture::solver_type::matrix_type;
+  using vector_type = typename TestFixture::solver_type::vector_type;
+
+  typename TestFixture::solver_type solver(matrix_type(2, 1),
+                                           distributed<vector_type>(2));
+  const auto solution =
+      vector_type::fromDistributed(this->solveConflictingEquations(solver));
+
+  EXPECT_THAT(solution(0), ScalarNear(18. / 10., 1e-6));
+}
+
+} // namespace
+} // namespace cpppetsc
+} // namespace ae108
-- 
GitLab