// SPDX-FileCopyrightText: 2021-2024 The Ikarus Developers mueller@ibb.uni-stuttgart.de
// SPDX-License-Identifier: LGPL-3.0-or-later

/**
 * \file linearalgebrahelper.hh
 * \brief Helper for the autodiff library
 */

#pragma once
#include "concepts.hh"
#include "traits.hh"

#include <iosfwd>
#include <random>
#include <ranges>

#include <dune/common/tuplevector.hh>
#include <dune/functions/functionspacebases/lagrangebasis.hh>
#include <dune/istl/bvector.hh>

#include <Eigen/Core>

#include <autodiff/forward/dual/dual.hpp>

namespace Ikarus {

  /**
   * @brief Orthonormalizes all Matrix columns using Gram-Schmidt Orthogonalization.
   * @tparam Derived Type of the input matrix.
   * \ingroup utils
   * @param A The input matrix.
   * @return Eigen Matrix with orthonormalized columns.
   */
  template <typename Derived>
  auto orthonormalizeMatrixColumns(const Eigen::MatrixBase<Derived>& A) {
    // Gram Schmidt Ortho
    auto Q = A.eval();

    Q.col(0).normalize();

    for (int colIndex = 1; colIndex < Q.cols(); colIndex++) {
      Q.col(colIndex) -= Q.leftCols(colIndex) * (Q.leftCols(colIndex).transpose() * A.col(colIndex));
      Q.col(colIndex).normalize();
    }

    return Q;
  }

  /**
   * @brief View Dune::BlockVector as an Eigen::Vector.
   * \ingroup utils
   * @tparam ValueType Type of the elements in the BlockVector.
   * @param blockedVector Input Dune::BlockVector.
   * @return Eigen::Map of the BlockVector as a flat Eigen::Vector.
   */
  template <typename ValueType>
  auto viewAsFlatEigenVector(Dune::BlockVector<ValueType>& blockedVector) {
    Eigen::Map<Eigen::VectorX<typename ValueType::field_type>> vec(&blockedVector.begin()->begin().operator*(),
                                                                   blockedVector.size() * blockedVector[0].size());

    return vec;
  }

  /**
   * @brief View const Dune::BlockVector as an Eigen::Vector.
   *  \ingroup utils
   * @tparam ValueType Type of the elements in the BlockVector.
   * @param blockedVector Input Dune::BlockVector.
   * @return Eigen::Map of the BlockVector as a flat Eigen::Vector (const version).
   */
  template <typename ValueType>
  auto viewAsFlatEigenVector(const Dune::BlockVector<ValueType>& blockedVector) {
    Eigen::Map<const Eigen::VectorX<typename ValueType::field_type>> vec(
        &blockedVector.begin()->begin().operator*(), blockedVector.size() * blockedVector[0].size());

    return vec;
  }

  /**
   * @brief View Dune::BlockVector as an Eigen::Matrix with dynamic rows and fixed columns depending on the size of the
   * ValueType.
   * \ingroup utils
   * @tparam ValueType Type of the elements in the BlockVector.
   * @param blockedVector Input Dune::BlockVector.
   * @return Eigen::Map of the BlockVector as a dynamic Eigen::Matrix.
   */
  template <typename ValueType>
  auto viewAsEigenMatrixAsDynFixed(Dune::BlockVector<ValueType>& blockedVector) {
    Eigen::Map<Eigen::Matrix<typename ValueType::field_type, Eigen::Dynamic, ValueType::valueSize, Eigen::RowMajor>>
        vec(&blockedVector.begin()->begin().operator*(), blockedVector.size(), blockedVector[0].size());

    return vec;
  }

  /**
   * @brief Const view Dune::BlockVector as an Eigen::Matrix with dynamic rows and fixed columns depending on the size
   * of the ValueType.
   * @ingroup utils
   * @tparam ValueType Type of the elements in the BlockVector.
   * @param blockedVector Input Dune::BlockVector.
   * @return Eigen::Map of the BlockVector as a dynamic Eigen::Matrix (const version).
   */
  template <typename ValueType>
  auto viewAsEigenMatrixAsDynFixed(const Dune::BlockVector<ValueType>& blockedVector) {
    Eigen::Map<
        const Eigen::Matrix<typename ValueType::field_type, Eigen::Dynamic, ValueType::valueSize, Eigen::RowMajor>>
        vec(&blockedVector.begin()->begin().operator*(), blockedVector.size(), blockedVector[0].size());

    return vec;
  }

  /**
   * @brief View Dune::BlockVector as an Eigen::Matrix with fixed rows depending on the size of the ValueType and
   * dynamic columns.
   * @ingroup utils
   * @tparam ValueType Type of the elements in the BlockVector.
   * @param blockedVector Input Dune::BlockVector.
   * @return Eigen::Map of the BlockVector as a fixed-size Eigen::Matrix with dynamic columns.
   */
  template <typename ValueType>
  auto viewAsEigenMatrixFixedDyn(Dune::BlockVector<ValueType>& blockedVector) {
    Eigen::Map<Eigen::Matrix<typename ValueType::field_type, ValueType::valueSize, Eigen::Dynamic>> vec(
        &blockedVector.begin()->begin().operator*(), blockedVector[0].size(), blockedVector.size());

    return vec;
  }

  /**
   * @brief Const view Dune::BlockVector as an Eigen::Matrix with fixed rows depending on the size of the ValueType and
   * dynamic columns. \ingroup utils
   * @tparam ValueType Type of the elements in the BlockVector.
   * @param blockedVector Input Dune::BlockVector.
   * @return Eigen::Map of the BlockVector as a fixed-size Eigen::Matrix with dynamic columns (const version).
   */
  template <typename ValueType>
  auto viewAsEigenMatrixFixedDyn(const Dune::BlockVector<ValueType>& blockedVector) {
    Eigen::Map<const Eigen::Matrix<typename ValueType::field_type, ValueType::valueSize, Eigen::Dynamic>> vec(
        &blockedVector.begin()->begin().operator*(), blockedVector[0].size(), blockedVector.size());

    return vec;
  }

  /**
   * @brief Returns the total correction size of a block vector with a Manifold as the underlying type.
   *  \ingroup utils
   * @tparam Type Manifold type.
   * @param a Input Dune::BlockVector.
   * @return Total correction size.
   */
  template <typename Type>
  size_t correctionSize(const Dune::BlockVector<Type>& a) requires requires {
    Type::correctionSize;
  }
  { return a.size() * Type::correctionSize; }

  /**
   * @brief Returns the total value size of a block vector with a Manifold as the underlying type.
   *  \ingroup utils
   * @tparam Type Manifold type.
   * @param a Input Dune::BlockVector.
   * @return Total value size.
   */
  template <typename Type>
  size_t valueSize(const Dune::BlockVector<Type>& a) requires requires {
    Type::valueSize;
  }
  { return a.size() * Type::valueSize; }

  /**
   * @brief Enables the += operator for Dune::BlockVector += Eigen::Vector.
   *  \ingroup utils
   * @tparam Type Manifold type.
   * @tparam Derived Type of the input Eigen matrix.
   * @param a Input Dune::BlockVector.
   * @param b Input Eigen::Matrix.
   * @return Reference to the modified Dune::BlockVector.
   */
  template <typename Type, typename Derived>
  Dune::BlockVector<Type>& operator+=(Dune::BlockVector<Type>& a, const Eigen::MatrixBase<Derived>& b) requires(
      Ikarus::Concepts::AddAssignAble<Type, decltype(b.template segment<Type::correctionSize>(0))>and requires() {
        Type::correctionSize;
      }) {
    assert(correctionSize(a) == static_cast<size_t>(b.size()) && " The passed vector has wrong size");
    for (auto i = 0U; i < a.size(); ++i)
      a[i] += b.template segment<Type::correctionSize>(i * Type::correctionSize);
    return a;
  }

  /**
   * @brief Enables the -= operator for Dune::BlockVector += Eigen::Vector.
   *  \ingroup utils
   * @tparam Type Manifold type.
   * @tparam Derived Type of the input Eigen matrix.
   * @param a Input Dune::BlockVector.
   * @param b Input Eigen::Matrix.
   * @return Reference to the modified Dune::BlockVector.
   */
  template <typename Type, typename Derived>
  Dune::BlockVector<Type>& operator-=(Dune::BlockVector<Type>& a, const Eigen::MatrixBase<Derived>& b) requires(
      Ikarus::Concepts::AddAssignAble<Type, decltype(b.template segment<Type::correctionSize>(0))>and requires() {
        Type::correctionSize;
      }) {
    return a += (-b);
  }

  /**
   * @brief Enables the += operator for Dune::TupleVector += Eigen::Vector.
   * \ingroup utils
   * @tparam Types Types of the elements in the TupleVector.
   * @tparam Derived Type of the input Eigen matrix.
   * @param a Input Dune::TupleVector.
   * @param b Input Eigen::Matrix.
   * @return Reference to the modified Dune::TupleVector.
   */
  template <typename... Types, typename Derived>
  Dune::TupleVector<Types...>& operator+=(Dune::TupleVector<Types...>& a, const Eigen::MatrixBase<Derived>& b) {
    using namespace Dune::Indices;
    size_t posStart = 0;
    Dune::Hybrid::forEach(Dune::Hybrid::integralRange(Dune::index_constant<sizeof...(Types)>()), [&](const auto i) {
      const size_t size = correctionSize(a[i]);
      a[i] += b(Eigen::seqN(posStart, size));
      posStart += size;
    });

    return a;
  }

  /**
   * @brief Enables the addition in the embedding space of a vector in the space M^n, where M is a manifold with the
   * points of type ManifoldPoint \ingroup utils
   * @tparam ManifoldPoint Manifold type.
   * @tparam Derived ManifoldPoint of the input Eigen matrix.
   * @param a Input Dune::BlockVector.
   * @param b Input Eigen::Matrix.
   * @return Reference to the modified Dune::BlockVector.
   */
  template <typename ManifoldPoint, typename Derived>
  Dune::BlockVector<ManifoldPoint>&
  addInEmbedding(Dune::BlockVector<ManifoldPoint>& a, const Eigen::MatrixBase<Derived>& b) requires(
      Ikarus::Concepts::AddAssignAble<ManifoldPoint,
                                      decltype(b.template segment<ManifoldPoint::valueSize>(0))>and requires() {
        ManifoldPoint::valueSize;
      }) {
    assert(valueSize(a) == static_cast<size_t>(b.size()) && " The passed vector has wrong size");
    for (auto i = 0U; i < a.size(); ++i)
      a[i].addInEmbedding(b.template segment<ManifoldPoint::valueSize>(i * ManifoldPoint::valueSize));
    return a;
  }

  /**
   * @brief Adding free norm function to Eigen types.
   *  \ingroup utils
   * @tparam Derived Type of the input Eigen matrix.
   * @param v Input Eigen matrix.
   * @return Norm of the matrix.
   */
  template <typename Derived>
  requires(!std::floating_point<Derived>) auto norm(const Eigen::MatrixBase<Derived>& v) { return v.norm(); }

  /**
   * @brief Helper Free Function to have the same interface as for Eigen Vector Types.
   *  \ingroup utils
   * @param v Input scalar.
   * @return Absolute value of the scalar.
   */
  auto norm(const std::floating_point auto& v) { return std::abs(v); }

  /**
   * @brief Eigen::DiagonalMatrix Product Missing in Eigen.
   *  \ingroup utils
   * @tparam Scalar Scalar type.
   * @tparam size Size of the diagonal matrix.
   * @param a Input DiagonalMatrix.
   * @param b Input DiagonalMatrix.
   * @return Product of the two DiagonalMatrices.
   */
  template <typename Scalar, int size>
  auto operator*(const Eigen::DiagonalMatrix<Scalar, size>& a, const Eigen::DiagonalMatrix<Scalar, size>& b) {
    return (a.diagonal().cwiseProduct(b.diagonal())).asDiagonal();
  }

  /**
   * @brief In-place addition for Eigen::DiagonalMatrix.
   *  \ingroup utils
   * @tparam Scalar Scalar type.
   * @tparam size Size of the diagonal matrix.
   * @param a Input DiagonalMatrix.
   * @param b Input DiagonalMatrix.
   * @return Reference to the modified DiagonalMatrix.
   */
  template <typename Scalar, int size>
  auto operator+=(Eigen::DiagonalMatrix<Scalar, size>& a, const Eigen::DiagonalMatrix<Scalar, size>& b) {
    a.diagonal() += b.diagonal();
    return a;
  }

  /**
   * @brief Eigen::Matrix + Eigen::DiagonalMatrix addition missing in Eigen.
   *  \ingroup utils
   * @tparam Derived Type of the input Eigen matrix.
   * @tparam Scalar Scalar type.
   * @tparam size Size of the diagonal matrix.
   * @param a Input Eigen matrix.
   * @param b Input DiagonalMatrix.
   * @return Sum of the Eigen matrix and DiagonalMatrix.
   */
  template <typename Derived, typename Scalar, int size>
  auto operator+(const Eigen::MatrixBase<Derived>& a, const Eigen::DiagonalMatrix<Scalar, size>& b) {
    auto c = a.derived().eval();
    c.diagonal() += b.diagonal();
    return c;
  }

  /**
   * @brief Eigen::DiagonalMatrix + Eigen::Matrix addition missing in Eigen.
   *  \ingroup utils
   * @tparam Derived Type of the input Eigen matrix.
   * @tparam Scalar Scalar type.
   * @tparam size Size of the diagonal matrix.
   * @param a Input DiagonalMatrix.
   * @param b Input Eigen matrix.
   * @return Sum of the DiagonalMatrix and Eigen matrix.
   */
  template <typename Derived, typename Scalar, int size>
  auto operator+(const Eigen::DiagonalMatrix<Scalar, size>& a, const Eigen::MatrixBase<Derived>& b) {
    return b + a;
  }

  /**
   * @brief Unary minus for Eigen::DiagonalMatrix.
   *  \ingroup utils
   * @tparam Scalar Scalar type.
   * @tparam size Size of the diagonal matrix.
   * @param a Input DiagonalMatrix.
   * @return Negation of the DiagonalMatrix.
   */
  template <typename Scalar, int size>
  auto operator-(const Eigen::DiagonalMatrix<Scalar, size>& a) {
    return (-a.diagonal()).asDiagonal();
  }

  /**
   * @brief Addition of Eigen::Matrix and Eigen::DiagonalWrapper.
   *  \ingroup utils
   * @tparam Derived Type of the input Eigen matrix.
   * @tparam Derived2 Type of the input Eigen DiagonalWrapper.
   * @param a Input Eigen matrix.
   * @param b Input Eigen DiagonalWrapper.
   * @return Sum of Eigen matrix and DiagonalWrapper.
   */
  template <typename Derived, typename Derived2>
  auto operator+(const Eigen::MatrixBase<Derived>& a, const Eigen::DiagonalWrapper<Derived2>& b) {
    auto c = a.derived().eval();
    c.diagonal() += b.diagonal();
    return c;
  }

  /**
   * @brief Addition of Eigen::DiagonalWrapper and Eigen::Matrix.
   *  \ingroup utils
   * @tparam Derived Type of the input Eigen DiagonalWrapper.
   * @tparam Derived2 Type of the input Eigen matrix.
   * @param a Input Eigen DiagonalWrapper.
   * @param b Input Eigen matrix.
   * @return Sum of DiagonalWrapper and Eigen matrix.
   */
  template <typename Derived, typename Derived2>
  auto operator+(const Eigen::DiagonalWrapper<Derived>& a, const Eigen::MatrixBase<Derived2>& b) {
    return b + a;
  }

  /**
   * @brief Output stream operator for Eigen::DiagonalMatrix.
   *  \ingroup utils
   * @tparam Scalar Scalar type.
   * @tparam size Size of the diagonal matrix.
   * @param os Output stream.
   * @param a Input DiagonalMatrix.
   * @return Reference to the output stream.
   */
  template <typename Scalar, int size>
  std::ostream& operator<<(std::ostream& os, const Eigen::DiagonalMatrix<Scalar, size>& a) {
    os << Eigen::Matrix<Scalar, size, size>(a);
    return os;
  }

  /**
   * @brief Returns the symmetric part of a matrix.
   *  \ingroup utils
   * @tparam Derived Type of the input Eigen matrix.
   * @param A Input Eigen matrix.
   * @return Symmetric part of the matrix.
   */
  template <typename Derived>
  Derived sym(const Eigen::MatrixBase<Derived>& A) {
    return 0.5 * (A + A.transpose());
  }

  /**
   * @brief Returns the skew part of a matrix.
   *  \ingroup utils
   * @tparam Derived Type of the input Eigen matrix.
   * @param A Input Eigen matrix.
   * @return Skew part of the matrix.
   */
  template <typename Derived>
  Derived skew(const Eigen::MatrixBase<Derived>& A) {
    return 0.5 * (A - A.transpose());
  }

  /**
   * \brief Method to print the matrix in a format that can directly be copied to Maple.
   *  \ingroup utils
   * \tparam Derived The derived type of the matrix.
   * \param A The input matrix.
   */
  template <typename Derived>
  void printForMaple(const Eigen::EigenBase<Derived>& A) {
    Eigen::IOFormat mapleFmt(Eigen::FullPrecision, 0, ", ", "|\n", "<", ">", "<", ">");
    if constexpr (std::convertible_to<Derived, const Eigen::MatrixBase<Derived>&>) {
      std::cout << "\n" << A.derived().format(mapleFmt) << std::endl;
    } else {  // branch for Eigen::DiagonalMatrix
      using Scalar = typename Derived::Scalar;
      using namespace Eigen;
      constexpr int diag_size = EIGEN_SIZE_MIN_PREFER_DYNAMIC(Derived::RowsAtCompileTime, Derived::ColsAtCompileTime);
      std::cout << "\n"
                << Eigen::Matrix<Scalar, diag_size, diag_size>(A.derived().diagonal().asDiagonal()).format(mapleFmt)
                << std::endl;
    }
  }

  /**
   * \brief Creates a random vector of the specified type within a given range.
   *  \ingroup utils
   * \tparam FieldVectorT The type of the vector.
   * \param lower The lower bound of the random values (default is -1).
   * \param upper The upper bound of the random values (default is 1).
   * \return A random vector within the specified range.
   */
  template <typename FieldVectorT>
  auto createRandomVector(typename FieldVectorT::value_type lower = -1, typename FieldVectorT::value_type upper = 1) {
    std::random_device rd;
    std::mt19937 mt(rd());
    std::uniform_real_distribution<typename FieldVectorT::value_type> dist(lower, upper);
    auto rand = [&dist, &mt]() { return dist(mt); };
    FieldVectorT vec;
    std::generate(vec.begin(), vec.end(), rand);
    return vec;
  }

  namespace Impl {
    constexpr std::tuple<std::array<std::array<int, 2>, 1>, std::array<std::array<int, 2>, 3>,
                         std::array<std::array<int, 2>, 6>>
        voigtIndices = {{{{0, 0}}}, {{{0, 0}, {1, 1}, {0, 1}}}, {{{0, 0}, {1, 1}, {2, 2}, {1, 2}, {0, 2}, {0, 1}}}};
  }

  /**
   * \brief Container for Voigt notation indices based on dimension.
   *  \ingroup utils
   * 1D: 0,0
   * 2D: 0,0 1,1 0,1
   * 3D: 0,0 1,1 2,2 1,2 0,2 0,1
   * \tparam dim The dimension for which Voigt indices are needed.
   */
  template <int dim>
  constexpr auto voigtNotationContainer = std::get<dim - 1>(Impl::voigtIndices);

  /**
   * @brief Performs static condensation on a square matrix.
   *  \ingroup utils
   * @tparam Derived Type of the matrix.
   * @tparam sizeOfCondensedIndices Size of the condensed indices.
   * @param E Input matrix.
   * @param indices Array of indices to be condensed.
   * @return Resulting matrix after static condensation.
   *  \details
   * This function performs static condensation on a square matrix. It removes the specified indices from the matrix,
   * computes the remaining submatrices (K11, K12, K22), and returns the result of the static condensation.
   */
  template <typename Derived, size_t sizeOfCondensedIndices>
  auto staticCondensation(const Eigen::MatrixBase<Derived>& E,
                          const std::array<size_t, sizeOfCondensedIndices>& indices) {
    constexpr size_t colsFull = std::remove_cvref_t<Derived>::ColsAtCompileTime;
    constexpr size_t rowsFull = std::remove_cvref_t<Derived>::RowsAtCompileTime;
    static_assert(colsFull == rowsFull, "staticCondensation only allowed for square matrices");
    std::array<size_t, rowsFull - sizeOfCondensedIndices> remainingIndices{};
    std::ranges::set_difference(std::ranges::iota_view(size_t(0), size_t(colsFull)), indices, remainingIndices.begin());

    auto K11 = E(remainingIndices, remainingIndices);
    auto K12 = E(indices, remainingIndices);
    auto K22 = E(indices, indices);

    return (K11 - K12.transpose() * K22.inverse() * K12).eval();
  }

  /**
   * @brief Removes specified columns from a matrix.
   *  \ingroup utils
   * @tparam Derived Type of the matrix.
   * @tparam sizeOfRemovedCols Size of the columns to be removed.
   * @param E Input matrix.
   * @param indices Array of column indices to be removed.
   * @return Resulting matrix after removing specified columns.
   *  \details
   * This function removes specified columns from a matrix. It computes the remaining columns after removing the
   * specified indices and returns the resulting matrix.
   */
  template <typename Derived, size_t sizeOfRemovedCols>
  auto removeCol(const Eigen::MatrixBase<Derived>& E, const std::array<size_t, sizeOfRemovedCols>& indices) {
    constexpr size_t colsFull = std::remove_cvref_t<Derived>::ColsAtCompileTime;
    constexpr size_t rowsFull = std::remove_cvref_t<Derived>::RowsAtCompileTime;
    static_assert(colsFull == 1);

    std::array<size_t, rowsFull - sizeOfRemovedCols> remainingIndices{};
    std::ranges::set_difference(std::ranges::iota_view(size_t(0), size_t(rowsFull)), indices, remainingIndices.begin());

    return (E(remainingIndices)).eval();
  }

  /**
   * @brief Converts a 3x3 matrix to Voigt notation, possibly reducing it based on material properties.
   *  \ingroup utils
   * @tparam ST Scalar type of the matrix.
   * @tparam MaterialImpl Type of the material implementation.
   * @param E Input 3x3 matrix.
   * @param material Reference to the material implementation.
   * @param isStrain Flag indicating if the matrix represents strain (default is true).
   * @return Resulting matrix in Voigt notation.
   *
   * This function converts a 3x3 matrix to its Voigt notation. If the material is not reduced, the full Voigt notation
   * is returned. Otherwise, the specified columns (based on material properties, such as VanishingStress) are removed,
   * and the reduced Voigt notation is returned.
   */
  template <typename ST, typename MaterialImpl>
  auto toVoigtAndMaybeReduce(const Eigen::Matrix<ST, 3, 3>& E, [[maybe_unused]] const MaterialImpl& material,
                             bool isStrain = true) {
    if constexpr (!MaterialImpl::isReduced)
      return toVoigt(E, isStrain);
    else {
      auto ev = toVoigt(E, isStrain);
      static_assert(decltype(ev)::RowsAtCompileTime == 6 and decltype(ev)::ColsAtCompileTime == 1);

      auto evRed = removeCol(ev, MaterialImpl::fixedVoigtIndices);
      static_assert(decltype(evRed)::RowsAtCompileTime == 6 - MaterialImpl::fixedVoigtIndices.size()
                    and decltype(evRed)::ColsAtCompileTime == 1);
      return evRed;
    }
  }

  /**
   * @brief Enlarges a matrix if it reduced in the context of material laws, i.e., VanishingStress
   * If the material is not reduced the untouched matrix is returned and rendering the function as a NoOp.
   *  \ingroup utils
   * @tparam Material Type of the material.
   * @tparam Derived Type of the input matrix.
   * @param E Input matrix.
   * @return auto Resulting matrix based on material properties.
   * \details
   * This function takes an input matrix and, based on the material properties, either returns the original matrix
   * (if it is not reduced) or enlarges the matrix by filling in the specified columns with zeros (if it is reduced).
   */
  template <typename Material, typename Derived>
  decltype(auto) enlargeIfReduced(const Eigen::MatrixBase<Derived>& E) {
    if constexpr (!Concepts::EigenVector6<Derived> and Concepts::EigenVector<Derived>) {
      static_assert(Material::isReduced, "You should only end up here, if your material is reduced");

      auto freeindices = Material::MaterialImplType::freeVoigtIndices;
      auto p_E         = Eigen::Vector<typename Material::MaterialImplType::ScalarType, 6>::Zero().eval();
      for (int ri = 0; auto i : freeindices)
        p_E(i) = E(ri++);
      return p_E;

    } else if constexpr (Concepts::EigenMatrix66<
                             Derived> or Concepts::EigenMatrix33<Derived> or Concepts::EigenVector6<Derived>) {
      return E.derived();
    } else {
      static_assert(Material::isReduced, "You should only end up here, if your material is reduced");

      auto freeindices = Material::MaterialImpl::freeVoigtIndices;
      auto p_E         = Eigen::Matrix<typename Material::MaterialImpl::ScalarType, 6, 6>::Zero().eval();
      for (int ri = 0; auto i : freeindices) {
        for (int rj = 0; auto j : freeindices)
          p_E(i, j) = E(ri, rj++);
        ++ri;
      }
      return p_E;
    }
  }

}  // namespace Ikarus
