text:
// Copyright (c) 2010-2022, Lawrence Livermore National Security, LLC. Produced
// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
// LICENSE and NOTICE for details. LLNL-CODE-806117.
//
// This file is part of the MFEM library. For more information and source code
// availability visit https://mfem.org.
//
// MFEM is free software; you can redistribute it and/or modify it under the
// terms of the BSD-3 license. We welcome feedback and contributions, see file
// CONTRIBUTING.md for details.

/**
 * @file tensor.hpp
 *
 * @brief Implementation of the tensor class
 */

#ifndef MFEM_INTERNAL_TENSOR_HPP
#define MFEM_INTERNAL_TENSOR_HPP

#include "dual.hpp"
#include <type_traits> // for std::false_type

namespace mfem
{
namespace internal
{

#if defined(__CUDACC__)
#if __CUDAVER__ >= 75000
#define MFEM_SUPPRESS_NVCC_HOSTDEVICE_WARNING #pragma nv_exec_check_disable
#else
#define MFEM_SUPPRESS_NVCC_HOSTDEVICE_WARNING #pragma hd_warning_disable
#endif
#else  //__CUDACC__
#define MFEM_SUPPRESS_NVCC_HOSTDEVICE_WARNING
#endif

template <typename T, int... n>
struct tensor;

/// The implementation can be drastically generalized by using concepts of the
/// c++17 standard.

template < typename T >
struct tensor<T>
{
   using type = T;
   static constexpr int ndim      = 1;
   static constexpr int first_dim = 0;
   MFEM_HOST_DEVICE T& operator[](int /*unused*/) { return values; }
   MFEM_HOST_DEVICE const T& operator[](int /*unused*/) const { return values; }
   MFEM_HOST_DEVICE T& operator()(int /*unused*/) { return values; }
   MFEM_HOST_DEVICE const T& operator()(int /*unused*/) const { return values; }
   MFEM_HOST_DEVICE operator T() const { return values; }
   T values;
};

template < typename T, int n0 >
struct tensor<T, n0>
{
   using type = T;
   static constexpr int ndim      = 1;
   static constexpr int first_dim = n0;
   MFEM_HOST_DEVICE T& operator[](int i) { return values[i]; }
   MFEM_HOST_DEVICE const T& operator[](int i) const { return values[i]; }
   MFEM_HOST_DEVICE T& operator()(int i) { return values[i]; }
   MFEM_HOST_DEVICE const T& operator()(int i) const { return values[i]; }
   T values[n0];
};

template < typename T, int n0, int n1 >
struct tensor<T, n0, n1>
{
   using type = T;
   static constexpr int ndim      = 2;
   static constexpr int first_dim = n0;
   MFEM_HOST_DEVICE tensor< T, n1 >& operator[](int i) { return values[i]; }
   MFEM_HOST_DEVICE const tensor< T, n1 >& operator[](int i) const { return values[i]; }
   MFEM_HOST_DEVICE tensor< T, n1 >& operator()(int i) { return values[i]; }
   MFEM_HOST_DEVICE const tensor< T, n1 >& operator()(int i) const { return values[i]; }
   MFEM_HOST_DEVICE T& operator()(int i, int j) { return values[i][j]; }
   MFEM_HOST_DEVICE const T& operator()(int i, int j) const { return values[i][j]; }
   tensor < T, n1 > values[n0];
};

template < typename T, int n0, int n1, int n2 >
struct tensor<T, n0, n1, n2>
{
   using type = T;
   static constexpr int ndim      = 3;
   static constexpr int first_dim = n0;
   MFEM_HOST_DEVICE tensor< T, n1, n2 >& operator[](int i) { return values[i]; }
   MFEM_HOST_DEVICE const tensor< T, n1, n2 >& operator[](int i) const { return values[i]; }
   MFEM_HOST_DEVICE tensor< T, n1, n2 >& operator()(int i) { return values[i]; }
   MFEM_HOST_DEVICE const tensor< T, n1, n2 >& operator()(int i) const { return values[i]; }
   MFEM_HOST_DEVICE tensor< T, n2 >& operator()(int i, int j) { return values[i][j]; }
   MFEM_HOST_DEVICE const tensor< T, n2 >& operator()(int i, int j) const { return values[i][j]; }
   MFEM_HOST_DEVICE T& operator()(int i, int j, int k) { return values[i][j][k]; }
   MFEM_HOST_DEVICE const T& operator()(int i, int j, int k) const { return values[i][j][k]; }
   tensor < T, n1, n2 > values[n0];
};

template < typename T, int n0, int n1, int n2, int n3 >
struct tensor<T, n0, n1, n2, n3>
{
   using type = T;
   static constexpr int ndim      = 4;
   static constexpr int first_dim = n0;
   MFEM_HOST_DEVICE tensor< T, n1, n2, n3 >& operator[](int i) { return values[i]; }
   MFEM_HOST_DEVICE const tensor< T, n1, n2, n3 >& operator[](int i) const { return values[i]; }
   MFEM_HOST_DEVICE tensor< T, n1, n2, n3 >& operator()(int i) { return values[i]; }
   MFEM_HOST_DEVICE const tensor< T, n1, n2, n3 >& operator()(int i) const { return values[i]; }
   MFEM_HOST_DEVICE tensor< T, n2, n3 >& operator()(int i, int j) { return values[i][j]; }
   MFEM_HOST_DEVICE const tensor< T, n2, n3 >& operator()(int i, int j) const { return values[i][j]; }
   MFEM_HOST_DEVICE tensor< T, n3 >& operator()(int i, int j, int k) { return values[i][j][k]; }
   MFEM_HOST_DEVICE const tensor< T, n3 >& operator()(int i, int j, int k) const { return values[i][j][k]; }
   MFEM_HOST_DEVICE T& operator()(int i, int j, int k, int l) { return values[i][j][k][l]; }
   MFEM_HOST_DEVICE const T&  operator()(int i, int j, int k, int l) const { return values[i][j][k][l]; }
   tensor < T, n1, n2, n3 > values[n0];
};

template < typename T, int n0, int n1, int n2, int n3, int n4 >
struct tensor<T, n0, n1, n2, n3, n4>
{
   using type = T;
   static constexpr int ndim      = 5;
   static constexpr int first_dim = n0;
   MFEM_HOST_DEVICE tensor< T, n1, n2, n3, n4 >& operator[](int i) { return values[i]; }
   MFEM_HOST_DEVICE const tensor< T, n1, n2, n3, n4 >& operator[](int i) const { return values[i]; }
   MFEM_HOST_DEVICE tensor< T, n1, n2, n3, n4 >& operator()(int i) { return values[i]; }
   MFEM_HOST_DEVICE const tensor< T, n1, n2, n3, n4 >& operator()(int i) const { return values[i]; }
   MFEM_HOST_DEVICE tensor< T, n2, n3, n4 >& operator()(int i, int j) { return values[i][j]; }
   MFEM_HOST_DEVICE const tensor< T, n2, n3, n4 >& operator()(int i,
                                                              int j) const { return values[i][j]; }
   MFEM_HOST_DEVICE tensor< T, n3, n4>& operator()(int i, int j, int k) { return values[i][j][k]; }
   MFEM_HOST_DEVICE const tensor< T, n3, n4>& operator()(int i, int j,
                                                         int k) const { return values[i][j][k]; }
   MFEM_HOST_DEVICE tensor< T, n4 >& operator()(int i, int j, int k, int l) { return values[i][j][k][l]; }
   MFEM_HOST_DEVICE const tensor< T, n4 >& operator()(int i, int j, int k,
                                                      int l) const { return values[i][j][k][l]; }
   MFEM_HOST_DEVICE T& operator()(int i, int j, int k, int l, int m) { return values[i][j][k][l][m]; }
   MFEM_HOST_DEVICE const T& operator()(int i, int j, int k, int l, int m) const { return values[i][j][k][l][m]; }
   tensor < T, n1, n2, n3, n4 > values[n0];
};

/**
 * @brief A sentinel struct for eliding no-op tensor operations
 */
struct zero
{
   /** @brief `zero` is implicitly convertible to double with value 0.0 */
   MFEM_HOST_DEVICE operator double() { return 0.0; }

   /** @brief `zero` is implicitly convertible to a tensor of any shape */
   template <typename T, int... n>
   MFEM_HOST_DEVICE operator tensor<T, n...>()
   {
      return tensor<T, n...> {};
   }

   /** @brief `zero` can be accessed like a multidimensional array */
   template <typename... T>
   MFEM_HOST_DEVICE zero operator()(T...)
   {
      return zero{};
   }

   /** @brief anything assigned to `zero` does not change its value and returns `zero` */
   template <typename T>
   MFEM_HOST_DEVICE zero operator=(T)
   {
      return zero{};
   }
};

/** @brief checks if a type is `zero` */
template <typename T>
struct is_zero : std::false_type
{
};

/** @overload */
template <>
struct is_zero<zero> : std::true_type
{
};

/** @brief the sum of two `zero`s is `zero` */
MFEM_HOST_DEVICE constexpr zero operator+(zero, zero) { return zero{}; }

/** @brief the sum of `zero` with something non-`zero` just returns the other value */
template <typename T>
MFEM_HOST_DEVICE constexpr T operator+(zero, T other)
{
   return other;
}

/** @brief the sum of `zero` with something non-`zero` just returns the other value */
template <typename T>
MFEM_HOST_DEVICE constexpr T operator+(T other, zero)
{
   return other;
}

/////////////////////////////////////////////////

/** @brief the unary negation of `zero` is `zero` */
MFEM_HOST_DEVICE constexpr zero operator-(zero) { return zero{}; }

/** @brief the difference of two `zero`s is `zero` */
MFEM_HOST_DEVICE constexpr zero operator-(zero, zero) { return zero{}; }

/** @brief the difference of `zero` with something else is the unary negation of the other thing */
template <typename T>
MFEM_HOST_DEVICE constexpr T operator-(zero, T other)
{
   return -other;
}

/** @brief the difference of something else with `zero` is the other thing itself */
template <typename T>
MFEM_HOST_DEVICE constexpr T operator-(T other, zero)
{
   return other;
}

/////////////////////////////////////////////////

/** @brief the product of two `zero`s is `zero` */
MFEM_HOST_DEVICE constexpr zero operator*(zero, zero) { return zero{}; }

/** @brief the product `zero` with something else is also `zero` */
template <typename T>
MFEM_HOST_DEVICE constexpr zero operator*(zero, T /*other*/)
{
   return zero{};
}

/** @brief the product `zero` with something else is also `zero` */
template <typename T>
MFEM_HOST_DEVICE constexpr zero operator*(T /*other*/, zero)
{
   return zero{};
}

/** @brief `zero` divided by something is `zero` */
template <typename T>
MFEM_HOST_DEVICE constexpr zero operator/(zero, T /*other*/)
{
   return zero{};
}

/** @brief `zero` plus `zero` is `zero */
MFEM_HOST_DEVICE constexpr zero operator+=(zero, zero) { return zero{}; }

/** @brief `zero` minus `zero` is `zero */
MFEM_HOST_DEVICE constexpr zero operator-=(zero, zero) { return zero{}; }

/** @brief let `zero` be accessed like a tuple */
template <int i>
MFEM_HOST_DEVICE zero& get(zero& x)
{
   return x;
}

/** @brief the dot product of anything with `zero` is `zero` */
template <typename T>
MFEM_HOST_DEVICE zero dot(const T&, zero)
{
   return zero{};
}

/** @brief the dot product of anything with `zero` is `zero` */
template <typename T>
MFEM_HOST_DEVICE zero dot(zero, const T&)
{
   return zero{};
}

/**
 * @brief Removes 1s from tensor dimensions
 * For example, a tensor<T, 1, 10> is equivalent to a tensor<T, 10>
 * @tparam T The scalar type of the tensor
 * @tparam n1 The first dimension
 * @tparam n2 The second dimension
 */
template <typename T, int n1, int n2 = 1>
using reduced_tensor = typename std::conditional<
                       (n1 == 1 && n2 == 1), T,
                       typename std::conditional<n1 == 1, tensor<T, n2>,
                       typename std::conditional<n2 == 1, tensor<T, n1>, tensor<T, n1, n2>
                       >::type
                       >::type
                       >::type;

/**
 * @brief Creates a tensor of requested dimension by subsequent calls to a functor
 * Can be thought of as analogous to @p std::transform in that the set of possible
 * indices for dimensions @p n are transformed into the values of the tensor by @a f
 * @tparam lambda_type The type of the functor
 * @param[in] f The functor to generate the tensor values from
 *
 * @note the different cases of 0D, 1D, 2D, 3D, and 4D are implemented separately
 *       to work around a limitation in nvcc involving __host__ __device__ lambdas with `auto` parameters.
 */
MFEM_SUPPRESS_NVCC_HOSTDEVICE_WARNING
template <typename lambda_type>
MFEM_HOST_DEVICE constexpr auto make_tensor(lambda_type f) ->
tensor<decltype(f())>
{
   return {f()};
}

/**
 * @brief Creates a tensor of requested dimension by subsequent calls to a functor
 *
 * @tparam n1 The dimension of the tensor
 * @tparam lambda_type The type of the functor
 * @param[in] f The functor to generate the tensor values from
 * @pre @a f must accept @p n1 arguments of type @p int
 *
 * @note the different cases of 0D, 1D, 2D, 3D, and 4D are implemented separately
 *       to work around a limitation in nvcc involving __host__ __device__ lambdas with `auto` parameters.
 */
MFEM_SUPPRESS_NVCC_HOSTDEVICE_WARNING
template <int n1, typename lambda_type>
MFEM_HOST_DEVICE auto make_tensor(lambda_type f) ->
tensor<decltype(f(n1)), n1>
{
   using T = decltype(f(n1));
   tensor<T, n1> A{};
   for (int i = 0; i < n1; i++)
   {
      A(i) = f(i);
   }
   return A;
}

/**
 * @brief Creates a tensor of requested dimension by subsequent calls to a functor
 *
 * @tparam n1 The first dimension of the tensor
 * @tparam n2 The second dimension of the tensor
 * @tparam lambda_type The type of the functor
 * @param[in] f The functor to generate the tensor values from
 * @pre @a f must accept @p n1 x @p n2 arguments of type @p int
 *
 * @note the different cases of 0D, 1D, 2D, 3D, and 4D are implemented separately
 *       to work around a limitation in nvcc involving __host__ __device__ lambdas with `auto` parameters.
 */
MFEM_SUPPRESS_NVCC_HOSTDEVICE_WARNING
template <int n1, int n2, typename lambda_type>
MFEM_HOST_DEVICE auto make_tensor(lambda_type f) ->
tensor<decltype(f(n1, n2)), n1, n2>
{
   using T = decltype(f(n1, n2));
   tensor<T, n1, n2> A{};
   for (int i = 0; i < n1; i++)
   {
      for (int j = 0; j < n2; j++)
      {
         A(i, j) = f(i, j);
      }
   }
   return A;
}

/**
 * @brief Creates a tensor of requested dimension by subsequent calls to a functor
 *
 * @tparam n1 The first dimension of the tensor
 * @tparam n2 The second dimension of the tensor
 * @tparam n3 The third dimension of the tensor
 * @tparam lambda_type The type of the functor
 * @param[in] f The functor to generate the tensor values from
 * @pre @a f must accept @p n1 x @p n2 x @p n3 arguments of type @p int
 *
 * @note the different cases of 0D, 1D, 2D, 3D, and 4D are implemented separately
 *       to work around a limitation in nvcc involving __host__ __device__ lambdas with `auto` parameters.
 */
MFEM_SUPPRESS_NVCC_HOSTDEVICE_WARNING
template <int n1, int n2, int n3, typename lambda_type>
MFEM_HOST_DEVICE auto make_tensor(lambda_type f) ->
tensor<decltype(f(n1, n2, n3)), n1, n2, n3>
{
   using T = decltype(f(n1, n2, n3));
   tensor<T, n1, n2, n3> A{};
   for (int i = 0; i < n1; i++)
   {
      for (int j = 0; j < n2; j++)
      {
         for (int k = 0; k < n3; k++)
         {
            A(i, j, k) = f(i, j, k);
         }
      }
   }
   return A;
}

/**
 * @brief Creates a tensor of requested dimension by subsequent calls to a functor
 *
 * @tparam n1 The first dimension of the tensor
 * @tparam n2 The second dimension of the tensor
 * @tparam n3 The third dimension of the tensor
 * @tparam n4 The fourth dimension of the tensor
 * @tparam lambda_type The type of the functor
 * @param[in] f The functor to generate the tensor values from
 * @pre @a f must accept @p n1 x @p n2 x @p n3 x @p n4 arguments of type @p int
 *
 * @note the different cases of 0D, 1D, 2D, 3D, and 4D are implemented separately
 *       to work around a limitation in nvcc involving __host__ __device__ lambdas with `auto` parameters.
 */
MFEM_SUPPRESS_NVCC_HOSTDEVICE_WARNING
template <int n1, int n2, int n3, int n4, typename lambda_type>
MFEM_HOST_DEVICE auto make_tensor(lambda_type f) ->
tensor<decltype(f(n1, n2, n3, n4)), n1, n2, n3, n4>
{
   using T = decltype(f(n1, n2, n3, n4));
   tensor<T, n1, n2, n3, n4> A{};
   for (int i = 0; i < n1; i++)
   {
      for (int j = 0; j < n2; j++)
      {
         for (int k = 0; k < n3; k++)
         {
            for (int l = 0; l < n4; l++)
            {
               A(i, j, k, l) = f(i, j, k, l);
            }
         }
      }
   }
   return A;
}

/**
 * @brief return the sum of two tensors
 * @tparam S the underlying type of the lefthand argument
 * @tparam T the underlying type of the righthand argument
 * @tparam n integers describing the tensor shape
 * @param[in] A The lefthand operand
 * @param[in] B The righthand operand
 */
template <typename S, typename T, int... n>
MFEM_HOST_DEVICE auto operator+(const tensor<S, n...>& A,
                                const tensor<T, n...>& B) ->
tensor<decltype(S {} + T{}), n...>
{
   tensor<decltype(S{} + T{}), n...> C{};
   for (int i = 0; i < tensor<T, n...>::first_dim; i++)
   {
      C[i] = A[i] + B[i];
   }
   return C;
}

/**
 * @brief return the unary negation of a tensor
 * @tparam T the underlying type of the righthand argument
 * @tparam n integers describing the tensor shape
 * @param[in] A The tensor to negate
 */
template <typename T, int... n>
MFEM_HOST_DEVICE tensor<T, n...> operator-(const tensor<T, n...>& A)
{
   tensor<T, n...> B{};
   for (int i = 0; i < tensor<T, n...>::first_dim; i++)
   {
      B[i] = -A[i];
   }
   return B;
}

/**
 * @brief return the difference of two tensors
 * @tparam S the underlying type of the lefthand argument
 * @tparam T the underlying type of the righthand argument
 * @tparam n integers describing the tensor shape
 * @param[in] A The lefthand operand
 * @param[in] B The righthand operand
 */
template <typename S, typename T, int... n>
MFEM_HOST_DEVICE auto operator-(const tensor<S, n...>& A,
                                const tensor<T, n...>& B) ->
tensor<decltype(S {} + T{}), n...>
{
   tensor<decltype(S{} + T{}), n...> C{};
   for (int i = 0; i < tensor<T, n...>::first_dim; i++)
   {
      C[i] = A[i] - B[i];
   }
   return C;
}

/**
 * @brief multiply a tensor by a scalar value
 * @tparam S the scalar value type. Must be arithmetic (e.g. float, double, int) or a dual number
 * @tparam T the underlying type of the tensor (righthand) argument
 * @tparam n integers describing the tensor shape
 * @param[in] scale The scaling factor
 * @param[in] A The tensor to be scaled
 */
template <typename S, typename T, int... n,
          typename = typename std::enable_if<std::is_arithmetic<S>::value ||
                                             is_dual_number<S>::value>::type>
MFEM_HOST_DEVICE auto operator*(S scale, const tensor<T, n...>& A) ->
tensor<decltype(S {} * T{}), n...>
{
   tensor<decltype(S{} * T{}), n...> C{};
   for (int i = 0; i < tensor<T, n...>::first_dim; i++)
   {
      C[i] = scale * A[i];
   }
   return C;
}

/**
 * @brief multiply a tensor by a scalar value
 * @tparam S the scalar value type. Must be arithmetic (e.g. float, double, int) or a dual number
 * @tparam T the underlying type of the tensor (righthand) argument
 * @tparam n integers describing the tensor shape
 * @param[in] A The tensor to be scaled
 * @param[in] scale The scaling factor
 */
template <typename S, typename T, int... n,
          typename = typename std::enable_if<std::is_arithmetic<S>::value ||
                                             is_dual_number<S>::value>::type>
MFEM_HOST_DEVICE auto operator*(const tensor<T, n...>& A, S scale) ->
tensor<decltype(T {} * S{}), n...>
{
   tensor<decltype(T{} * S{}), n...> C{};
   for (int i = 0; i < tensor<T, n...>::first_dim; i++)
   {
      C[i] = A[i] * scale;
   }
   return C;
}

/**
 * @brief divide a scalar by each element in a tensor
 * @tparam S the scalar value type. Must be arithmetic (e.g. float, double, int) or a dual number
 * @tparam T the underlying type of the tensor (righthand) argument
 * @tparam n integers describing the tensor shape
 * @param[in] scale The numerator
 * @param[in] A The tensor of denominators
 */
template <typename S, typename T, int... n,
          typename = typename std::enable_if<std::is_arithmetic<S>::value ||
                                             is_dual_number<S>::value>::type>
MFEM_HOST_DEVICE auto operator/(S scale, const tensor<T, n...>& A) ->
tensor<decltype(S {} * T{}), n...>
{
   tensor<decltype(S{} * T{}), n...> C{};
   for (int i = 0; i < tensor<T, n...>::first_dim; i++)
   {
      C[i] = scale / A[i];
   }
   return C;
}

/**
 * @brief divide a tensor by a scalar
 * @tparam S the scalar value type. Must be arithmetic (e.g. float, double, int) or a dual number
 * @tparam T the underlying type of the tensor (righthand) argument
 * @tparam n integers describing the tensor shape
 * @param[in] A The tensor of numerators
 * @param[in] scale The denominator
 */
template <typename S, typename T, int... n,
          typename = typename std::enable_if<std::is_arithmetic<S>::value ||
                                             is_dual_number<S>::value>::type>
MFEM_HOST_DEVICE auto operator/(const tensor<T, n...>& A, S scale) ->
tensor<decltype(T {} * S{}), n...>
{
   tensor<decltype(T{} * S{}), n...> C{};
   for (int i = 0; i < tensor<T, n...>::first_dim; i++)
   {
      C[i] = A[i] / scale;
   }
   return C;
}

/**
 * @brief compound assignment (+) on tensors
 * @tparam S the underlying type of the tensor (lefthand) argument
 * @tparam T the underlying type of the tensor (righthand) argument
 * @tparam n integers describing the tensor shape
 * @param[in] A The lefthand tensor
 * @param[in] B The righthand tensor
 */
template <typename S, typename T, int... n> MFEM_HOST_DEVICE
tensor<S, n...>& operator+=(tensor<S, n...>& A,
                            const tensor<T, n...>& B)
{
   for (int i = 0; i < tensor<S, n...>::first_dim; i++)
   {
      A[i] += B[i];
   }
   return A;
}

/**
 * @brief compound assignment (+) on tensors
 * @tparam T the underlying type of the tensor argument
 * @param[in] A The lefthand tensor
 * @param[in] B The righthand tensor
 */
template <typename T> MFEM_HOST_DEVICE
tensor<T>& operator+=(tensor<T>& A, const T& B)
{
   return A.values += B;
}

/**
 * @brief compound assignment (+) on tensors
 * @tparam T the underlying type of the tensor argument
 * @param[in] A The lefthand tensor
 * @param[in] B The righthand tensor
 */
template <typename T> MFEM_HOST_DEVICE
tensor<T, 1>& operator+=(tensor<T, 1>& A, const T& B)
{
   return A.values += B;
}

/**
 * @brief compound assignment (+) on tensors
 * @tparam T the underlying type of the tensor argument
 * @param[in] A The lefthand tensor
 * @param[in] B The righthand tensor
 */
template <typename T> MFEM_HOST_DEVICE
tensor<T, 1, 1>& operator+=(tensor<T, 1, 1>& A, const T& B)
{
   return A.values += B;
}

/**
 * @brief compound assignment (+) between a tensor and zero (no-op)
 * @tparam T the underlying type of the tensor (righthand) argument
 * @tparam n integers describing the tensor shape
 * @param[in] A The lefthand tensor
 */
template <typename T, int... n> MFEM_HOST_DEVICE
tensor<T, n...>& operator+=(tensor<T, n...>& A, zero)
{
   return A;
}

/**
 * @brief compound assignment (-) on tensors
 * @tparam S the underlying type of the tensor (lefthand) argument
 * @tparam T the underlying type of the tensor (righthand) argument
 * @tparam n integers describing the tensor shape
 * @param[in] A The lefthand tensor
 * @param[in] B The righthand tensor
 */
template <typename S, typename T, int... n> MFEM_HOST_DEVICE
tensor<S, n...>& operator-=(tensor<S, n...>& A, const tensor<T, n...>& B)
{
   for (int i = 0; i < tensor<S, n...>::first_dim; i++)
   {
      A[i] -= B[i];
   }
   return A;
}

/**
 * @brief compound assignment (-) between a tensor and zero (no-op)
 * @tparam T the underlying type of the tensor (righthand) argument
 * @tparam n integers describing the tensor shape
 * @param[in] A The lefthand tensor
 */
template <typename T, int... n> MFEM_HOST_DEVICE
constexpr tensor<T, n...>& operator-=(tensor<T, n...>& A, zero)
{
   return A;
}

/**
 * @brief compute the outer product of two tensors
 * @tparam S the type of the lefthand argument
 * @tparam T the type of the righthand argument
 * @param[in] A The lefthand argument
 * @param[in] B The righthand argument
 *
 * @note this overload implements the special case where both arguments are scalars
 */
template <typename S, typename T> MFEM_HOST_DEVICE
auto outer(S A, T B) -> decltype(A * B)
{
   static_assert(std::is_arithmetic<S>::value && std::is_arithmetic<T>::value,
                 "outer product types must be tensor or arithmetic_type");
   return A * B;
}

/**
 * @overload
 * @note this overload implements the case where the left argument is a scalar, and the right argument is a tensor
 */
template <typename S, typename T, int n> MFEM_HOST_DEVICE
tensor<decltype(S{} * T{}), n> outer(S A, tensor<T, n> B)
{
   static_assert(std::is_arithmetic<S>::value,
                 "outer product types must be tensor or arithmetic_type");
   tensor<decltype(S{} * T{}), n> AB{};
   for (int i = 0; i < n; i++)
   {
      AB[i] = A * B[i];
   }
   return AB;
}

/**
 * @overload
 * @note this overload implements the case where the left argument is a tensor, and the right argument is a scalar
 */
template <typename S, typename T, int m> MFEM_HOST_DEVICE
tensor<decltype(S{} * T{}), m> outer(const tensor<S, m>& A, T B)
{
   static_assert(std::is_arithmetic<T>::value,
                 "outer product types must be tensor or arithmetic_type");
   tensor<decltype(S{} * T{}), m> AB{};
   for (int i = 0; i < m; i++)
   {
      AB[i] = A[i] * B;
   }
   return AB;
}

/**
 * @overload
 * @note this overload implements the case where the left argument is `zero`, and the right argument is a tensor
 */
template <typename T, int n> MFEM_HOST_DEVICE
zero outer(zero, const tensor<T, n>&)
{
   return zero{};
}

/**
 * @overload
 * @note this overload implements the case where the left argument is a tensor, and the right argument is `zero`
 */
template <typename T, int n> MFEM_HOST_DEVICE
zero outer(const tensor<T, n>&, zero)
{
   return zero{};
}

/**
 * @overload
 * @note this overload implements the case where the left argument is a scalar,
 * and the right argument is a tensor
 */
template <typename S, typename T, int m, int n> MFEM_HOST_DEVICE
tensor<decltype(S{} * T{}), m, n> outer(S A, const tensor<T, m, n>& B)
{
   static_assert(std::is_arithmetic<S>::value,
                 "outer product types must be tensor or arithmetic_type");
   tensor<decltype(S{} * T{}), m, n> AB{};
   for (int i = 0; i < m; i++)
   {
      for (int j = 0; j < n; j++)
      {
         AB[i][j] = A * B[i][j];
      }
   }
   return AB;
}

/**
 * @overload
 * @note this overload implements the case where both arguments are vectors
 */
template <typename S, typename T, int m, int n> MFEM_HOST_DEVICE
tensor<decltype(S{} * T{}), m, n> outer(const tensor<S, m>& A,
                                        const tensor<T, n>& B)
{
   tensor<decltype(S{} * T{}), m, n> AB{};
   for (int i = 0; i < m; i++)
   {
      for (int j = 0; j < n; j++)
      {
         AB[i][j] = A[i] * B[j];
      }
   }
   return AB;
}

/**
 * @overload
 * @note this overload implements the case where the left argument is a 2nd order tensor, and the right argument is a
 * scalar
 */
template <typename S, typename T, int m, int n> MFEM_HOST_DEVICE
tensor<decltype(S{} * T{}), m, n> outer(const tensor<S, m, n>& A, T B)
{
   static_assert(std::is_arithmetic<T>::value,
                 "outer product types must be tensor or arithmetic_type");
   tensor<decltype(S{} * T{}), m, n> AB{};
   for (int i = 0; i < m; i++)
   {
      for (int j = 0; j < n; j++)
      {
         AB[i][j] = A[i][j] * B;
      }
   }
   return AB;
}

/**
 * @overload
 * @note this overload implements the case where the left argument is a 2nd order tensor, and the right argument is a
 * first order tensor
 */
template <typename S, typename T, int m, int n, int p> MFEM_HOST_DEVICE
tensor<decltype(S{} * T{}), m, n, p> outer(const tensor<S, m, n>& A,
                                           const tensor<T, p>& B)
{
   tensor<decltype(S{} * T{}), m, n, p> AB{};
   for (int i = 0; i < m; i++)
   {
      for (int j = 0; j < n; j++)
      {
         for (int k = 0; k < p; k++)
         {
            AB[i][j][k] = A[i][j] * B[k];
         }
      }
   }
   return AB;
}

/**
 * @overload
 * @note this overload implements the case where the left argument is a 1st order tensor, and the right argument is a
 * 2nd order tensor
 */
template <typename S, typename T, int m, int n, int p> MFEM_HOST_DEVICE
tensor<decltype(S{} * T{}), m, n, p> outer(const tensor<S, m>& A,
                                           const tensor<T, n, p>& B)
{
   tensor<decltype(S{} * T{}), m, n, p> AB{};
   for (int i = 0; i < m; i++)
   {
      for (int j = 0; j < n; j++)
      {
         for (int k = 0; k < p; k++)
         {
            AB[i][j][k] = A[i] * B[j][k];
         }
      }
   }
   return AB;
}

/**
 * @overload
 * @note this overload implements the case where both arguments are second order tensors
 */
template <typename S, typename T, int m, int n, int p, int q> MFEM_HOST_DEVICE
tensor<decltype(S{} * T{}), m, n, p, q> outer(const tensor<S, m, n>& A,
                                              const tensor<T, p, q>& B)
{
   tensor<decltype(S{} * T{}), m, n, p, q> AB{};
   for (int i = 0; i < m; i++)
   {
      for (int j = 0; j < n; j++)
      {
         for (int k = 0; k < p; k++)
         {
            for (int l = 0; l < q; l++)
            {
               AB[i][j][k][l] = A[i][j] * B[k][l];
            }
         }
      }
   }
   return AB;
}

/**
 * @brief this function contracts over all indices of the two tensor arguments
 * @tparam S the underlying type of the tensor (lefthand) argument
 * @tparam T the underlying type of the tensor (righthand) argument
 * @tparam m the number of rows
 * @tparam n the number of columns
 * @param[in] A The lefthand tensor
 * @param[in] B The righthand tensor
 */
template <typename S, typename T, int m, int n> MFEM_HOST_DEVICE
auto inner(const tensor<S, m, n>& A, const tensor<T, m, n>& B) ->
decltype(S {} * T{})
{
   decltype(S{} * T{}) sum{};
   for (int i = 0; i < m; i++)
   {
      for (int j = 0; j < n; j++)
      {
         sum += A[i][j] * B[i][j];
      }
   }
   return sum;
}

/**
 * @brief this function contracts over the "middle" index of the two tensor
 * arguments. E.g. returns tensor C, such that C_ij = sum_kl A_ijkl B_kl.
 * @tparam S the underlying type of the tensor (lefthand) argument
 * @tparam T the underlying type of the tensor (righthand) argument
 * @tparam n integers describing the tensor shape
 * @param[in] A The lefthand tensor
 * @param[in] B The righthand tensor
 */
template <typename S, typename T, int m, int n, int p> MFEM_HOST_DEVICE
auto dot(const tensor<S, m, n>& A,
         const tensor<T, n, p>& B) ->
tensor<decltype(S {} * T{}), m, p>
{
   tensor<decltype(S{} * T{}), m, p> AB{};
   for (int i = 0; i < m; i++)
   {
      for (int j = 0; j < p; j++)
      {
         for (int k = 0; k < n; k++)
         {
            AB[i][j] = AB[i][j] + A[i][k] * B[k][j];
         }
      }
   }
   return AB;
}

/**
 * @overload
 * @note vector . matrix
 */
template <typename S, typename T, int m, int n> MFEM_HOST_DEVICE
auto dot(const tensor<S, m>& A, const tensor<T, m, n>& B) ->
tensor<decltype(S {} * T{}), n>
{
   tensor<decltype(S{} * T{}), n> AB{};
   for (int i = 0; i < n; i++)
   {
      for (int j = 0; j < m; j++)
      {
         AB[i] = AB[i] + A[j] * B[j][i];
      }
   }
   return AB;
}

/**
 * @overload
 * @note matrix . vector
 */
template <typename S, typename T, int m, int n> MFEM_HOST_DEVICE
auto dot(const tensor<S, m, n>& A, const tensor<T, n>& B) ->
tensor<decltype(S {} * T{}), m>
{
   tensor<decltype(S{} * T{}), m> AB{};
   for (int i = 0; i < m; i++)
   {
      for (int j = 0; j < n; j++)
      {
         AB[i] = AB[i] + A[i][j] * B[j];
      }
   }
   return AB;
}

/**
 * @overload
 * @note 3rd-order-tensor . vector
 */
template <typename S, typename T, int m, int n, int p> MFEM_HOST_DEVICE
auto dot(const tensor<S, m, n, p>& A, const tensor<T, p>& B) ->
tensor<decltype(S {} * T{}), m, n>
{
   tensor<decltype(S{} * T{}), m, n> AB{};
   for (int i = 0; i < m; i++)
   {
      for (int j = 0; j < n; j++)
      {
         for (int k = 0; k < p; k++)
         {
            AB[i][j] += A[i][j][k] * B[k];
         }
      }
   }
   return AB;
}

// /**
//  * @brief Dot product of a vector . vector and vector . tensor
//  *
//  * @tparam S the underlying type of the tensor (lefthand) argument
//  * @tparam T the underlying type of the tensor (righthand) argument
//  * @tparam m the dimension of the first tensor
//  * @tparam n the parameter pack of dimensions of the second tensor
//  * @param A The lefthand tensor
//  * @param B The righthand tensor
//  * @return The computed dot product
//  */
// template <typename S, typename T, int m, int... n>
// auto dot(const tensor<S, m>& A, const tensor<T, m, n...>& B)
// {
//    // this dot product function includes the vector * vector implementation and
//    // the vector * tensor one, since clang emits an error about ambiguous
//    // overloads if they are separate functions. The `if constexpr` expression avoids
//    // using an `else` because that confuses nvcc (11.2) into thinking there's not
//    // a return statement
//    if constexpr (sizeof...(n) == 0)
//    {
//       decltype(S{} * T{}) AB{};
//       for (int i = 0; i < m; i++)
//       {
//          AB += A[i] * B[i];
//       }
//       return AB;
//    }

//    if constexpr (sizeof...(n) > 0)
//    {
//       constexpr int                     dimensions[] = {n...};
//       tensor<decltype(S{} * T{}), n...> AB{};
//       for (int i = 0; i < dimensions[0]; i++)
//       {
//          for (int j = 0; j < m; j++)
//          {
//             AB[i] = AB[i] + A[j] * B[j][i];
//          }
//       }
//       return AB;
//    }
// }

template <typename S, typename T, int m> MFEM_HOST_DEVICE
auto dot(const tensor<S, m>& A, const tensor<T, m>& B) ->
decltype(S {} * T{})
{
   decltype(S{} * T{}) AB{};
   for (int i = 0; i < m; i++)
   {
      AB += A[i] * B[i];
   }
   return AB;
}

template <typename S, typename T, int m, int... n> MFEM_HOST_DEVICE
auto dot(const tensor<S, m>& A, const tensor<T, m, n...>& B) ->
tensor<decltype(S {} * T{}), n...>
{
   constexpr int dimensions[] = {n...};
   tensor<decltype(S{} * T{}), n...> AB{};
   for (int i = 0; i < dimensions[0]; i++)
   {
      for (int j = 0; j < m; j++)
      {
         AB[i] = AB[i] + A[j] * B[j][i];
      }
   }
   return AB;
}

/**
 * @overload
 * @note vector . matrix . vector
 */
template <typename S, typename T, typename U, int m, int n> MFEM_HOST_DEVICE
auto dot(const tensor<S, m>& u, const tensor<T, m, n>& A,
         const tensor<U, n>& v) ->
decltype(S {} * T{} * U{})
{
   decltype(S{} * T{} * U{}) uAv{};
   for (int i = 0; i < m; i++)
   {
      for (int j = 0; j < n; j++)
      {
         uAv += u[i] * A[i][j] * v[j];
      }
   }
   return uAv;
}

/**
 * @brief double dot product, contracting over the two "middle" indices
 * @tparam S the underlying type of the tensor (lefthand) argument
 * @tparam T the underlying type of the tensor (righthand) argument
 * @tparam m first dimension of A
 * @tparam n second dimension of A
 * @tparam p third dimension of A, first dimensions of B
 * @tparam q fourth dimension of A, second dimensions of B
 * @param[in] A The lefthand tensor
 * @param[in] B The righthand tensor
 */
template <typename S, typename T, int m, int n, int p, int q> MFEM_HOST_DEVICE
auto ddot(const tensor<S, m, n, p, q>& A, const tensor<T, p, q>& B) ->
tensor<decltype(S {} * T{}), m, n>
{
   tensor<decltype(S{} * T{}), m, n> AB{};
   for (int i = 0; i < m; i++)
   {
      for (int j = 0; j < n; j++)
      {
         for (int k = 0; k < p; k++)
         {
            for (int l = 0; l < q; l++)
            {
               AB[i][j] += A[i][j][k][l] * B[k][l];
            }
         }
      }
   }
   return AB;
}

/**
 * @overload
 * @note 3rd-order-tensor : 2nd-order-tensor. Returns vector C, such that C_i =
 * sum_jk A_ijk B_jk.
 */
template <typename S, typename T, int m, int n, int p> MFEM_HOST_DEVICE
auto ddot(const tensor<S, m, n, p>& A, const tensor<T, n, p>& B) ->
tensor<decltype(S {} * T{}), m>
{
   tensor<decltype(S{} * T{}), m> AB{};
   for (int i = 0; i < m; i++)
   {
      for (int j = 0; j < n; j++)
      {
         for (int k = 0; k < p; k++)
         {
            AB[i] += A[i][j][k] * B[j][k];
         }
      }
   }
   return AB;
}

/**
 * @overload
 * @note 2nd-order-tensor : 2nd-order-tensor, like inner()
 */
template <typename S, typename T, int m, int n> MFEM_HOST_DEVICE
auto ddot(const tensor<S, m, n>& A, const tensor<T, m, n>& B) ->
decltype(S {} * T{})
{
   decltype(S{} * T{}) AB{};
   for (int i = 0; i < m; i++)
   {
      for (int j = 0; j < n; j++)
      {
         AB += A[i][j] * B[i][j];
      }
   }
   return AB;
}

/**
 * @brief this is a shorthand for dot(A, B)
 */
template <typename S, typename T, int... m, int... n> MFEM_HOST_DEVICE
auto operator*(const tensor<S, m...>& A, const tensor<T, n...>& B) ->
decltype(dot(A, B))
{
   return dot(A, B);
}

/**
 * @brief Returns the squared Frobenius norm of the tensor
 * @param[in] A The tensor to obtain the squared norm from
 */
template <typename T, int m> MFEM_HOST_DEVICE
T sqnorm(const tensor<T, m>& A)
{
   T total{};
   for (int i = 0; i < m; i++)
   {
      total += A[i] * A[i];
   }
   return total;
}

/**
 * @overload
 * @brief Returns the squared Frobenius norm of the tensor
 */
template <typename T, int m, int n> MFEM_HOST_DEVICE
T sqnorm(const tensor<T, m, n>& A)
{
   T total{};
   for (int i = 0; i < m; i++)
   {
      for (int j = 0; j < n; j++)
      {
         total += A[i][j] * A[i][j];
      }
   }
   return total;
}

/**
 * @brief Returns the Frobenius norm of the tensor
 * @param[in] A The tensor to obtain the norm from
 */
template <typename T, int... n> MFEM_HOST_DEVICE
T norm(const tensor<T, n...>& A)
{
   return std::sqrt(sqnorm(A));
}

/**
 * @brief Normalizes the tensor
 * Each element is divided by the Frobenius norm of the tensor, @see norm
 * @param[in] A The tensor to normalize
 */
template <typename T, int... n> MFEM_HOST_DEVICE
auto normalize(const tensor<T, n...>& A) ->
decltype(A / norm(A))
{
   return A / norm(A);
}

/**
 * @brief Returns the trace of a square matrix
 * @param[in] A The matrix to compute the trace of
 * @return The sum of the elements on the main diagonal
 */
template <typename T, int n> MFEM_HOST_DEVICE
T tr(const tensor<T, n, n>& A)
{
   T trA{};
   for (int i = 0; i < n; i++)
   {
      trA = trA + A[i][i];
   }
   return trA;
}

/**
 * @brief Returns the symmetric part of a square matrix
 * @param[in] A The matrix to obtain the symmetric part of
 * @return (1/2) * (A + A^T)
 */
template <typename T, int n> MFEM_HOST_DEVICE
tensor<T, n, n> sym(const tensor<T, n, n>& A)
{
   tensor<T, n, n> symA{};
   for (int i = 0; i < n; i++)
   {
      for (int j = 0; j < n; j++)
      {
         symA[i][j] = 0.5 * (A[i][j] + A[j][i]);
      }
   }
   return symA;
}

/**
 * @brief Calculates the deviator of a matrix (rank-2 tensor)
 * @param[in] A The matrix to calculate the deviator of
 * In the context of stress tensors, the deviator is obtained by
 * subtracting the mean stress (average of main diagonal elements)
 * from each element on the main diagonal
 */
template <typename T, int n> MFEM_HOST_DEVICE
tensor<T, n, n> dev(const tensor<T, n, n>& A)
{
   auto devA = A;
   auto trA  = tr(A);
   for (int i = 0; i < n; i++)
   {
      devA[i][i] -= trA / n;
   }
   return devA;
}

/**
 * @brief Obtains the identity matrix of the specified dimension
 * @return I_dim
 */
template <int dim>
MFEM_HOST_DEVICE tensor<double, dim, dim> Identity()
{
   tensor<double, dim, dim> I{};
   for (int i = 0; i < dim; i++)
   {
      for (int j = 0; j < dim; j++)
      {
         I[i][j] = (i == j);
      }
   }
   return I;
}

/**
 * @brief Returns the transpose of the matrix
 * @param[in] A The matrix to obtain the transpose of
 */
template <typename T, int m, int n> MFEM_HOST_DEVICE
tensor<T, n, m> transpose(const tensor<T, m, n>& A)
{
   tensor<T, n, m> AT{};
   for (int i = 0; i < n; i++)
   {
      for (int j = 0; j < m; j++)
      {
         AT[i][j] = A[j][i];
      }
   }
   return AT;
}

/**
 * @brief Returns the determinant of a matrix
 * @param[in] A The matrix to obtain the determinant of
 */
template <typename T> MFEM_HOST_DEVICE
T det(const tensor<T, 2, 2>& A)
{
   return A[0][0] * A[1][1] - A[0][1] * A[1][0];
}
/// @overload
template <typename T> MFEM_HOST_DEVICE
T det(const tensor<T, 3, 3>& A)
{
   return A[0][0] * A[1][1] * A[2][2] + A[0][1] * A[1][2] * A[2][0] + A[0][2] *
          A[1][0] * A[2][1] -
          A[0][0] * A[1][2] * A[2][1] - A[0][1] * A[1][0] * A[2][2] - A[0][2] * A[1][1] *
          A[2][0];
}

/**
 * @brief Return whether a square rank 2 tensor is symmetric
 *
 * @tparam n The height of the tensor
 * @param A The square rank 2 tensor
 * @param abs_tolerance The absolute tolerance to check for symmetry
 * @return Whether the square rank 2 tensor (matrix) is symmetric
 */
template <int n> MFEM_HOST_DEVICE
bool is_symmetric(tensor<double, n, n> A, double abs_tolerance = 1.0e-8)
{
   for (int i = 0; i < n; ++i)
   {
      for (int j = i + 1; j < n; ++j)
      {
         if (std::abs(A(i, j) - A(j, i)) > abs_tolerance)
         {
            return false;
         }
      }
   }
   return true;
}

/**
 * @brief Return whether a matrix is symmetric and positive definite
 * This check uses Sylvester's criterion, checking that each upper left subtensor has a
 * determinant greater than zero.
 *
 * @param A The matrix to test for positive definiteness
 * @return Whether the matrix is positive definite
 */
inline MFEM_HOST_DEVICE
bool is_symmetric_and_positive_definite(tensor<double, 2, 2> A)
{
   if (!is_symmetric(A))
   {
      return false;
   }
   if (A(0, 0) < 0.0)
   {
      return false;
   }
   if (det(A) < 0.0)
   {
      return false;
   }
   return true;
}
/// @overload
inline MFEM_HOST_DEVICE
bool is_symmetric_and_positive_definite(tensor<double, 3, 3> A)
{
   if (!is_symmetric(A))
   {
      return false;
   }
   if (det(A) < 0.0)
   {
      return false;
   }
   auto subtensor = make_tensor<2, 2>([A](int i, int j) { return A(i, j); });
   if (!is_symmetric_and_positive_definite(subtensor))
   {
      return false;
   }
   return true;
}

/**
 * @brief Solves Ax = b for x using Gaussian elimination with partial pivoting
 * @param[in] A The coefficient matrix A
 * @param[in] b The righthand side vector b
 * @note @a A and @a b are by-value as they are mutated as part of the elimination
 */
template <typename T, int n> MFEM_HOST_DEVICE
tensor<T, n> linear_solve(tensor<T, n, n> A, const tensor<T, n> b)
{
   auto abs  = [](double x) { return (x < 0) ? -x : x; };
   auto swap_vector = [](tensor<T, n>& x, tensor<T, n>& y)
   {
      auto tmp = x;
      x        = y;
      y        = tmp;
   };
   auto swap_scalar = [](T& x, T& y)
   {
      auto tmp = x;
      x        = y;
      y        = tmp;
   };

   tensor<double, n> x{};

   for (int i = 0; i < n; i++)
   {
      // Search for maximum in this column
      double max_val = abs(A[i][i]);

      int max_row = i;
      for (int j = i + 1; j < n; j++)
      {
         if (abs(A[j][i]) > max_val)
         {
            max_val = abs(A[j][i]);
            max_row = j;
         }
      }

      swap_scalar(b[max_row], b[i]);
      swap_vector(A[max_row], A[i]);

      // zero entries below in this column
      for (int j = i + 1; j < n; j++)
      {
         double c = -A[j][i] / A[i][i];
         A[j] += c * A[i];
         b[j] += c * b[i];
         A[j][i] = 0;
      }
   }

   // Solve equation Ax=b for an upper triangular matrix A
   for (int i = n - 1; i >= 0; i--)
   {
      x[i] = b[i] / A[i][i];
      for (int j = i - 1; j >= 0; j--)
      {
         b[j] -= A[j][i] * x[i];
      }
   }

   return x;
}

/**
 * @brief Inverts a matrix
 * @param[in] A The matrix to invert
 * @note Uses a shortcut for inverting a 2-by-2 matrix
 */
inline MFEM_HOST_DEVICE tensor<double, 2, 2> inv(const tensor<double, 2, 2>& A)
{
   double inv_detA(1.0 / det(A));

   tensor<double, 2, 2> invA{};

   invA[0][0] = A[1][1] * inv_detA;
   invA[0][1] = -A[0][1] * inv_detA;
   invA[1][0] = -A[1][0] * inv_detA;
   invA[1][1] = A[0][0] * inv_detA;

   return invA;
}

/**
 * @overload
 * @note Uses a shortcut for inverting a 3-by-3 matrix
 */
inline MFEM_HOST_DEVICE tensor<double, 3, 3> inv(const tensor<double, 3, 3>& A)
{
   double inv_detA(1.0 / det(A));

   tensor<double, 3, 3> invA{};

   invA[0][0] = (A[1][1] * A[2][2] - A[1][2] * A[2][1]) * inv_detA;
   invA[0][1] = (A[0][2] * A[2][1] - A[0][1] * A[2][2]) * inv_detA;
   invA[0][2] = (A[0][1] * A[1][2] - A[0][2] * A[1][1]) * inv_detA;
   invA[1][0] = (A[1][2] * A[2][0] - A[1][0] * A[2][2]) * inv_detA;
   invA[1][1] = (A[0][0] * A[2][2] - A[0][2] * A[2][0]) * inv_detA;
   invA[1][2] = (A[0][2] * A[1][0] - A[0][0] * A[1][2]) * inv_detA;
   invA[2][0] = (A[1][0] * A[2][1] - A[1][1] * A[2][0]) * inv_detA;
   invA[2][1] = (A[0][1] * A[2][0] - A[0][0] * A[2][1]) * inv_detA;
   invA[2][2] = (A[0][0] * A[1][1] - A[0][1] * A[1][0]) * inv_detA;

   return invA;
}
/**
 * @overload
 * @note For N-by-N matrices with N > 3, requires Gaussian elimination
 * with partial pivoting
 */
template <typename T, int n> MFEM_HOST_DEVICE
tensor<T, n, n> inv(const tensor<T, n, n>& A)
{
   auto abs  = [](double x) { return (x < 0) ? -x : x; };
   auto swap = [](tensor<T, n>& x, tensor<T, n>& y)
   {
      auto tmp = x;
      x        = y;
      y        = tmp;
   };

   tensor<double, n, n> B = Identity<n>();

   for (int i = 0; i < n; i++)
   {
      // Search for maximum in this column
      double max_val = abs(A[i][i]);

      int max_row = i;
      for (int j = i + 1; j < n; j++)
      {
         if (abs(A[j][i]) > max_val)
         {
            max_val = abs(A[j][i]);
            max_row = j;
         }
      }

      swap(B[max_row], B[i]);
      swap(A[max_row], A[i]);

      // zero entries below in this column
      for (int j = i + 1; j < n; j++)
      {
         if (A[j][i] != 0.0)
         {
            double c = -A[j][i] / A[i][i];
            A[j] += c * A[i];
            B[j] += c * B[i];
            A[j][i] = 0;
         }
      }
   }

   // upper triangular solve
   for (int i = n - 1; i >= 0; i--)
   {
      B[i] = B[i] / A[i][i];
      for (int j = i - 1; j >= 0; j--)
      {
         if (A[j][i] != 0.0)
         {
            B[j] -= A[j][i] * B[i];
         }
      }
   }

   return B;
}

/**
 * @overload
 * @note when inverting a tensor of dual numbers,
 * hardcode the analytic derivative of the
 * inverse of a square matrix, rather than
 * apply Gauss elimination directly on the dual number types
 *
 * TODO: compare performance of this hardcoded implementation to just using inv() directly
 */
template <typename value_type, typename gradient_type, int n> MFEM_HOST_DEVICE
dual<value_type, gradient_type> inv(
   tensor<dual<value_type, gradient_type>, n, n> A)
{
   auto invA = inv(get_value(A));
   return make_tensor<n, n>([&](int i, int j)
   {
      auto          value = invA[i][j];
      gradient_type gradient{};
      for (int k = 0; k < n; k++)
      {
         for (int l = 0; l < n; l++)
         {
            gradient -= invA[i][k] * A[k][l].gradient * invA[l][j];
         }
      }
      return dual<value_type, gradient_type> {value, gradient};
   });
}

/**
 * @brief recursively serialize the entries in a tensor to an output stream.
 * Output format uses braces and comma separators to mimic C syntax for multidimensional array
 * initialization.
 *
 * @param[in] os The stream to work with standard output streams
 * @param[in] A The tensor to write out
 */
template <typename T, int... n>
std::ostream& operator<<(std::ostream& os, const tensor<T, n...>& A)
{
   os << '{' << A[0];
   for (int i = 1; i < tensor<T, n...>::first_dim; i++)
   {
      os << ", " << A[i];
   }
   os << '}';
   return os;
}

/**
 * @brief replace all entries in a tensor satisfying |x| < 1.0e-10 by literal zero
 * @param[in] A The tensor to "chop"
 */
template <int n> MFEM_HOST_DEVICE
tensor<double, n> chop(const tensor<double, n>& A)
{
   auto copy = A;
   for (int i = 0; i < n; i++)
   {
      if (copy[i] * copy[i] < 1.0e-20)
      {
         copy[i] = 0.0;
      }
   }
   return copy;
}

/// @overload
template <int m, int n> MFEM_HOST_DEVICE
tensor<double, m, n> chop(const tensor<double, m, n>& A)
{
   auto copy = A;
   for (int i = 0; i < m; i++)
   {
      for (int j = 0; j < n; j++)
      {
         if (copy[i][j] * copy[i][j] < 1.0e-20)
         {
            copy[i][j] = 0.0;
         }
      }
   }
   return copy;
}

/// @cond
namespace detail
{
template <typename T1, typename T2>
struct outer_prod;

template <int... m, int... n>
struct outer_prod<tensor<double, m...>, tensor<double, n...>>
{
   using type = tensor<double, m..., n...>;
};

template <int... n>
struct outer_prod<double, tensor<double, n...>>
{
   using type = tensor<double, n...>;
};

template <int... n>
struct outer_prod<tensor<double, n...>, double>
{
   using type = tensor<double, n...>;
};

template <>
struct outer_prod<double, double>
{
   using type = tensor<double>;
};

template <typename T>
struct outer_prod<zero, T>
{
   using type = zero;
};

template <typename T>
struct outer_prod<T, zero>
{
   using type = zero;
};

}  // namespace detail
/// @endcond

/**
 * @brief a type function that returns the tensor type of an outer product of two tensors
 * @tparam T1 the first argument to the outer product
 * @tparam T2 the second argument to the outer product
 */
template <typename T1, typename T2>
using outer_product_t = typename detail::outer_prod<T1, T2>::type;

/**
 * @brief Retrieves the gradient component of a double (which is nothing)
 * @return The sentinel, @see zero
 */
inline MFEM_HOST_DEVICE zero get_gradient(double /* arg */) { return zero{}; }

/**
 * @brief get the gradient of type `tensor` (note: since its stored type is not a dual
 * number, the derivative term is identically zero)
 * @return The sentinel, @see zero
 */
template <int... n>
MFEM_HOST_DEVICE zero get_gradient(const tensor<double, n...>& /* arg */)
{
   return zero{};
}

/**
 * @brief evaluate the change (to first order) in a function, f, given a small change in the input argument, dx.
 */
inline MFEM_HOST_DEVICE zero chain_rule(const zero /* df_dx */,
                                        const zero /* dx */) { return zero{}; }

/**
 * @overload
 * @note this overload implements a no-op for the case where the gradient w.r.t. an input argument is identically zero
 */
template <typename T>
MFEM_HOST_DEVICE zero chain_rule(const zero /* df_dx */,
                                 const T /* dx */)
{
   return zero{};
}

/**
 * @overload
 * @note this overload implements a no-op for the case where the small change is identically zero
 */
template <typename T>
MFEM_HOST_DEVICE zero chain_rule(const T /* df_dx */,
                                 const zero /* dx */)
{
   return zero{};
}

/**
 * @overload
 * @note for a scalar-valued function of a scalar, the chain rule is just multiplication
 */
inline MFEM_HOST_DEVICE double chain_rule(const double df_dx,
                                          const double dx) { return df_dx * dx; }

/**
 * @overload
 * @note for a tensor-valued function of a scalar, the chain rule is just scalar multiplication
 */
template <int... n>
MFEM_HOST_DEVICE auto chain_rule(const tensor<double, n...>& df_dx,
                                 const double dx) ->
decltype(df_dx * dx)
{
   return df_dx * dx;
}

template <int n> struct always_false : std::false_type { };

template <typename T, int... n> struct isotropic_tensor;

template <typename T, int n>
struct isotropic_tensor<T, n>
{
   static_assert(always_false<n> {},
                 "error: there is no such thing as a rank-1 isotropic tensor!");
};

// rank-2 isotropic tensors are just identity matrices
template <typename T, int m>
struct isotropic_tensor<T, m, m>
{
   MFEM_HOST_DEVICE constexpr T operator()(int i, int j) const
   {
      return (i == j) * value;
   }
   T value;
};

template <int m>
MFEM_HOST_DEVICE constexpr isotropic_tensor<double, m, m> IsotropicIdentity()
{
   return isotropic_tensor<double, m, m> {1.0};
}

template <typename S, typename T, int m> MFEM_HOST_DEVICE constexpr
auto operator*(S scale,
               const isotropic_tensor<T, m, m> & I)
-> isotropic_tensor<decltype(S {} * T{}), m, m>
{
   return {I.value * scale};
}

template <typename S, typename T, int m> MFEM_HOST_DEVICE constexpr
auto operator*(const isotropic_tensor<T, m, m> & I,
               const S scale)
-> isotropic_tensor<decltype(S {}, T{}), m, m>
{
   return {I.value * scale};
}

template <typename S, typename T, int m> MFEM_HOST_DEVICE constexpr
auto operator+(const isotropic_tensor<S, m, m>& I1,
               const isotropic_tensor<T, m, m>& I2)
-> isotropic_tensor<decltype(S {} + T{}), m, m>
{
   return {I1.value + I2.value};
}

template <typename S, typename T, int m> MFEM_HOST_DEVICE constexpr
auto operator-(const isotropic_tensor<S, m, m>& I1,
               const isotropic_tensor<T, m, m>& I2)
-> isotropic_tensor<decltype(S {} - T{}), m, m>
{
   return {I1.value - I2.value};
}

template <typename S, typename T, int m> MFEM_HOST_DEVICE //constexpr
auto operator+(const isotropic_tensor<S, m, m>& I,
               const tensor<T, m, m>& A)
-> tensor<decltype(S {} + T{}), m, m>
{
   tensor<decltype(S{} + T{}), m, m> output{};
   for (int i = 0; i < m; i++)
   {
      for (int j = 0; j < m; j++)
      {
         output[i][j] = I.value * (i == j) + A[i][j];
      }
   }
   return output;
}

template <typename S, typename T, int m> MFEM_HOST_DEVICE //constexpr
auto operator+(const tensor<S, m, m>& A,
               const isotropic_tensor<T, m, m>& I)
-> tensor<decltype(S {} + T{}), m, m>
{
   tensor<decltype(S{} + T{}), m, m> output{};
   for (int i = 0; i < m; i++)
   {
      for (int j = 0; j < m; j++)
      {
         output[i][j] = A[i][j] + I.value * (i == j);
      }
   }
   return output;
}

template <typename S, typename T, int m> MFEM_HOST_DEVICE //constexpr
auto operator-(const isotropic_tensor<S, m, m>& I,
               const tensor<T, m, m>& A)
-> tensor<decltype(S {} - T{}), m, m>
{
   tensor<decltype(S{} - T{}), m, m> output{};
   for (int i = 0; i < m; i++)
   {
      for (int j = 0; j < m; j++)
      {
         output[i][j] = I.value * (i == j) - A[i][j];
      }
   }
   return output;
}

template <typename S, typename T, int m> MFEM_HOST_DEVICE // constexpr
auto operator-(const tensor<S, m, m>& A,
               const isotropic_tensor<T, m, m>& I)
-> tensor<decltype(S {} - T{}), m, m>
{
   tensor<decltype(S{} - T{}), m, m> output{};
   for (int i = 0; i < m; i++)
   {
      for (int j = 0; j < m; j++)
      {
         output[i][j] = A[i][j] - I.value * (i == j);
      }
   }
   return output;
}

template <typename S, typename T, int m, int... n> MFEM_HOST_DEVICE constexpr
auto dot(const isotropic_tensor<S, m, m>& I,
         const tensor<T, m, n...>& A)
-> tensor<decltype(S {} * T{}), m, n...>
{
   return I.value * A;
}

template <typename S, typename T, int m, int... n> MFEM_HOST_DEVICE //constexpr
auto dot(const tensor<S, n...>& A,
         const isotropic_tensor<T, m, m> & I)
-> tensor<decltype(S {} * T{}), n...>
{
   constexpr int dimensions[sizeof...(n)] = {n...};
   static_assert(dimensions[sizeof...(n) - 1] == m, "n-1 != m");
   return A * I.value;
}

template <typename S, typename T, int m, int... n> MFEM_HOST_DEVICE constexpr
auto ddot(const isotropic_tensor<S, m, m>& I,
          const tensor<T, m, m>& A)
-> decltype(S {} * T{})
{
   return I.value * tr(A);
}

template <typename T, int m> MFEM_HOST_DEVICE constexpr
auto sym(const isotropic_tensor<T, m, m>& I) -> isotropic_tensor<T, m, m>
{
   return I;
}

template <typename T, int m> MFEM_HOST_DEVICE constexpr
auto antisym(const isotropic_tensor<T, m, m>&) -> zero
{
   return zero{};
}

template <typename T, int m> MFEM_HOST_DEVICE constexpr
auto tr(const isotropic_tensor<T, m, m>& I) -> decltype(T {} * m)
{
   return I.value * m;
}

template <typename T, int m> MFEM_HOST_DEVICE constexpr
auto transpose(const isotropic_tensor<T, m, m>& I) -> isotropic_tensor<T, m, m>
{
   return I;
}

template <typename T, int m> MFEM_HOST_DEVICE constexpr
auto det(const isotropic_tensor<T, m, m>& I) -> T
{
   return std::pow(I.value, m);
}

template <typename T, int m> MFEM_HOST_DEVICE constexpr
auto norm(const isotropic_tensor<T, m, m>& I) -> T
{
   return sqrt(I.value * I.value * m);
}

template <typename T, int m> MFEM_HOST_DEVICE constexpr
auto sqnorm(const isotropic_tensor<T, m, m>& I) -> T
{
   return I.value * I.value * m;
}

// rank-3 isotropic tensors are just the alternating symbol
template <typename T>
struct isotropic_tensor<T, 3, 3, 3>
{
   MFEM_HOST_DEVICE constexpr T operator()(int i, int j, int k) const
   {
      return 0.5 * (i - j) * (j - k) * (k - i) * value;
   }
   T value;
};

// there are 3 linearly-independent rank-4 isotropic tensors,
// so the general one will be some linear combination of them
template <typename T, int m>
struct isotropic_tensor<T, m, m, m, m>
{
   T c1, c2, c3;

   MFEM_HOST_DEVICE constexpr T operator()(int i, int j, int k, int l) const
   {
      return c1 * (i == j) * (k == l)
             + c2 * ((i == k) * (j == l) + (i == l) * (j == k)) * 0.5
             + c3 * ((i == k) * (j == l) - (i == l) * (j == k)) * 0.5;
   }
};

template <int m> MFEM_HOST_DEVICE constexpr
auto SymmetricIdentity() -> isotropic_tensor<double, m, m, m, m>
{
   return {0.0, 1.0, 0.0};
}

template <int m>MFEM_HOST_DEVICE constexpr
auto AntisymmetricIdentity() -> isotropic_tensor<double, m, m, m, m>
{
   return {0.0, 0.0, 1.0};
}

template <typename S, typename T, int m> MFEM_HOST_DEVICE constexpr
auto operator*(S scale,
               isotropic_tensor<T, m, m, m, m> I)
-> isotropic_tensor<decltype(S {} * T{}), m, m, m, m>
{
   return {I.c1 * scale, I.c2 * scale, I.c3 * scale};
}

template <typename S, typename T, int m> MFEM_HOST_DEVICE constexpr
auto operator*(isotropic_tensor<S, m, m, m, m> I,
               T scale)
-> isotropic_tensor<decltype(S {} * T{}), m, m, m, m>
{
   return {I.c1 * scale, I.c2 * scale, I.c3 * scale};
}

template <typename S, typename T, int m> MFEM_HOST_DEVICE constexpr
auto operator+(isotropic_tensor<S, m, m, m, m> I1,
               isotropic_tensor<T, m, m, m, m> I2)
-> isotropic_tensor<decltype(S {} + T{}), m, m, m, m>
{
   return {I1.c1 + I2.c1, I1.c2 + I2.c2, I1.c3 + I2.c3};
}

template <typename S, typename T, int m> MFEM_HOST_DEVICE constexpr
auto operator-(isotropic_tensor<S, m, m, m, m> I1,
               isotropic_tensor<T, m, m, m, m> I2)
-> isotropic_tensor<decltype(S {} - T{}), m, m, m, m>
{
   return {I1.c1 - I2.c1, I1.c2 - I2.c2, I1.c3 - I2.c3};
}

template <typename S, typename T, int m, int... n> MFEM_HOST_DEVICE constexpr
auto ddot(const isotropic_tensor<S, m, m, m, m>& I,
          const tensor<T, m, m>& A)
-> tensor<decltype(S {} * T{}), m, m>
{
   return I.c1 * tr(A) * Identity<m>() + I.c2 * sym(A) + I.c3 * antisym(A);
}

} // namespace internal

} // namespace mfem

#endif


Download Gapps for Android 8.0 / 8.1 Oreo: Gapps for Android devices running Android 8.1 Oreo is now available to download and ready to install. Gapps are quite important when it comes to flashing custom ROMS like Lineage OS 15. Though the custom ROMS are independent of the Google Apps and services and it is entirely optional for Android users to use Gapps or not, yet they play an important role in evolving the essence of the Android mega system. Following the official release of Android 8.1 Oreo by Google, the latest Gapps (Google Apps packages) for Android 8.1 Oreo have also come out while flashing custom ROMS. Proceed to the given guide on how to download and install Gapps on Android 8.0 / 8.1 Oreo. You can download Gapps for Android 8.0 / 8.1 Oreo from the given links.
The latest 8.1 Oreo ROM comes with enhanced features and upgrades for the Android system with almost every tech geek and fanatic upgrading/updating the ROM on most of their devices. As of now Gapps are also available to flash on the updated custom ROM and if you wish to download and install Gapps on devices running Android 8.1 Oreo, read on the following detailed guide. Proceed to the given links to Download Gapps for Android 8.0 / 8.1 Oreo.
What are Gapps (Google Apps) and why should you Download?
Google Apps is basically a Web-based Software solution that customizes the Google platform essentially for businesses of all sizes. The primary sectors that Google Apps facilitates are the provisioning of Google applications and tools, like Gmail, Google Talk, Google Calendar, Google Docs, Google Videos and Google Cloud Connect.
As new features and innovative tools continue to evolve, there is an increase in trend of installing the latest firmware update by most the Android users. Gapps is directly related to such updates. In a more simpler way, we can describe Gapps as a package of Android programs that you have to flash or install every time you install a new custom ROM. This is because until and unless you do so, you will not be able to use the applications on your device.
Once you install the Gapps package, automatically all the Google-based applications will be installed on your device so that you can use Gmail, Gtalk, Google Voice, Calendar, Google Play Store, Google Maps, Goggles, Google Contacts and any other application that you need to operate on your phone. So Gapps is in a way a solution when these apps can't be integrated into custom ROM packages( as it breaks the licensing restrictions ) and you download them as a complete package on your device.
So If you have ever done rooting on your phone and installed a new ROM, you might have noticed that the Google apps present before have been erased. Although these apps come pre-installed on most of the Android devices, like the Play Store, Gmail, Maps, etc, owing to the licensing restrictions stated earlier. These apps cannot come pre-installed with LineageOS and so they must be installed separately in order to take full advantage of the Android system. The direct links to download Gapps for Android 8.0 / 8.1 Oreo are here below.
You can choose the Gapps package depending upon your requirements and opt for the one best suits you. While most of Android 8.0 Gapps package will be available in a wide range of sizes; it can be of 50 MB or 400 MB according to the criteria and features of the package.
1. For installing Android 8.0 Gapps on your device, it is recommended that you install the Android 8.0 based AOSP custom ROM, like LineageOS 15 or SlimROM on your device before starting the procedure.
2. You must also install a custom recovery such as TWRP or CWM in case you lose any data or just as a safety measure.
3. Your battery should be At least 30% charged, though, it won't take more than 5 minutes maximum for the download, but you should always be prepared in case things go wrong or you make a mistake and get stuck.
4. A very important requirement is, however, that there should be enough empty space remaining on your device, which you can also precalculate depending on which Gapp you want to install.
1. First of all Download the Latest Gapps For Android Oreo ROMs from here and save it on your device.
2. Boot your device into Recovery Mode.
• First of all switch off your android smartphone.
Simultaneously press these buttons in the above mentioned order for a few seconds. Once you keep pressing in the same exact order you may release to see that you are given a list of options to choose from.
3. Under TWRP Recovery, click 'Install' and select Gapps packaged apps options.
4. Once you have selected and flashed the Gapps, flash the zip file. clear cache and Dalvik Cache. Also, clear cache and Dalvik Cache after flashing the Gapps.
5. You have successfully installed Gapps on Android 8.0 Oreo.
So above is the step by step guide to download Gapps for Android 8.0 / 8.1 Oreo. If You have any questions or queries regarding This guide Then you can Freely contact us Below In Comment Section.
Hey guys, my question is what would be the result if I flashed oreo 8.0 gapps on oreo 8.1 based custom ROM (more specifically AOSP Extended ROM based on Android Oreo 8.1)?


text:
The FBI Has Secretly Gathered Millions Of "Faceprints" For Biometric Database For Years
A representative of the FBI was grilled by the House Committee on Oversight and Government Reform regarding the agency's growing biometric database.
On Wednesday the House Committee on Oversight and Government Reform questioned Kimberly Del Greco, Deputy Assistant Director at the FBI's Criminal Justice Information Services Division, about why the bureau broke the law by failing to file a privacy impact statement acknowledging the collection of millions of Americans' faces for the agency's new biometric identification system.
The FBI's Next Generation Identification (NGI) system is made up of fingerprints, iris scans, faceprints, and other facial recognition data.
The NGI organizes Americans' biometric data into a single file that includes personal and biographic data like name, home address, ID number, immigration status, age, race, etc.
The Committee reports that nearly half of all adult Americans' photographs are in the database.
The 2013 U.S. Census Bureau estimated that there are over 242 million adults living in the U.S. If the Committee's numbers are correct, over 121 million adults are in the FBI's database.
Other revelations include that 18 states have a memorandum of understanding (MOU) with the FBI to share photos with the federal government, including from state departments of motor vehicles (DMV).
According to the Electronic Privacy Information Center:
With NGI, the FBI will expand the number of uploaded photographs and provide investigators with 'automated facial recognition search capability.'
The FBI intends to do this by eliminating restrictions on the number of submitted photographs (including photographs that are not accompanied by tenprint fingerprints) and allowing the submission of non-facial photographs (e.g. scars or tattoos).
The FBI also widely disseminates this NGI data. According to the FBI's latest NGI fact sheet, 24,510 local, state, tribal, federal and international partners submitted queries to NGI in September 2016.
Committee Chairman Congressman Jason Chaffetz (R- Utah) scolded Ms. Del Greco for the FBI's failures. "The failure here is years after you were supposed to make it public," Chaffetz stated.
"You were using it in real world circumstances, you were actually using it and didn't issue the statement." Chaffetz also asked Del Greco whether the FBI had plans to gather faceprints via social media.
"Are you collecting that information that is available on social media?," the chairman demanded. "We do not have any other photos in our repository."
Alvaro Bedoya, Executive Director, Center on Privacy and Technology Georgetown Law, questioned the FBI's claim, stating that the bureau has access to driver's license photos. "We have access to that data but we do not use it," Del Greco answered.
Jennifer Lynch, Senior Staff attorney with the Electronic Frontier Foundation also stated that the FBI has access to "civil photos" in the NGI database.
Rep. Paul Mitchell (R-MI) also questioned Del Greco regarding the FBI's activities. "I think the issue goes beyond the First Amendment concerns that were expressed. . .and is broader," Mitchell stated during the panel.
"I don't want to just protect someone if they're in a political protest from being identified, the reality is we should protect everybody unless there is a valid documented criminal justice action.
Why should my photo. . .be subject because I get a driver's license, to access?" Rep. John Duncan (R-TN) expressed similar fear regarding the possibility that the expectation of privacy is quickly fading.
Duncan worried that Americans looking at the information, "would wonder if were ending up in a federal police state that's gotten totally out of control, and has far too much power."
Duncan is not far off. The databases include photos those of people who aren't suspected of any criminal activity that come from driver's license and passport and visa photos. Other issues with the FBI database include misidentifying females and blacks at a higher rate. The FBI is currently facing a lawsuit from EPIC regarding the database.
EPIC is asking a judge to force the FBI to release records about its plan to share the biometric data with the U.S. Department of Defense. EPIC filed a Freedom of Information Act request in 2015, but the FBI has so far refused to release the 35 pages of responsive records.
EPIC and privacy advocates are concerned about the potential for cases of mistaken identity and abuse of the collected data. EPIC also argues "the FBI stated that '[i]ncreased collection and retention of personally identifiable information presents a correspondingly increased risk that the FBI will then be maintaining more information that might potentially be subject to loss or unauthorized use."
In 2014, the EFF received documents from the FBI related to the NGI system. At the time the FBI estimated the facial recognition component of NGI would include as many as 52 million face images by 2015.
The Committee's hearing on Wednesday indicated that number was more than double as of 2017. The danger of abuse from facial recognition programs is on the rise. The report, "The Perpetual Line-Up: Unregulated Police Face Recognition in America," examines several cases of misuse or abuse of facial recognition technology.
How can we protect our privacy in a world that is quickly becoming a digitally interconnected panopticon of audio recording devices, faceprint collection machines, and cameras from every direction?
The answer is not simple. Most of us live our lives with these devices and use them for several hours each day. When we are staring into our phones to send text messages, the camera is potentially stealing our faceprint and sending it to your cell phone provider and/or law enforcement.
We are giving away our privacy for convenience, luxury, and entertainment. How can we stop the fast march to a total surveillance state? Perhaps abandoning all technology and living off the land. Short of that, we need to take measures to encrypt our communication and practice a culture of security.
SOURCEactivistpost
The Soros reign: a Romanian example
Mystery Stingray devices discovered in Washington
This Eradicated Disease Could Come Back as a Terrifying Biological Weapon
The worrying personal information Facebook has on you that you didn't know about
Biodiversity Scientist Warns: "We're Sleepwalking into a Mass Extinction"
Here's how Stephen Hawking predicted the world will end
Nothing to See Here, Just Freedom
New Alien Species Invasions Still Rising Globally
Facebook and Google Promise Congress They'll Brainwash You With Their Own Counter Propaganda
Jimmy Carter Speaks Out Against US 'Oligarchy' and Trump's Diplomatic Failures
US Bombings in Afghanistan Hit Five-Year High in August
Terrorism a lower threat 'even if 9/11 happened every year' – Snowden
What is Awareness?
Ask Your Neighbors The Building 7 Challenge" This Year On 9/11
Scientists Discover Black Hole 12 Billion Times Larger Than the Sun

redpajama_set_name: RedPajamaCommonCrawl


A physician assistant career description can assist you in putting out an 22, In the event you wish to hire a health and fitness assistant afterward. Learning to be a wellness assistant will open tons of windows into opportunities to advance and continue on with your education. With lots of job openings in the area, medical assistants or PAs are still certainly one of the careers.
You will find many sorts of health assistants, all which can be extremely crucial to the healthcare teams they are a part of. Apart from duties, an health care Assistant might be asked to do clinical and clerical work also. They need to employ a huge range of skill sets because urges have been predicted to conduct a wide range of tasks on a basis. They have to display their interest in employed in the field and also their capacity to carry out a wide array of tasks. They have a vast range of responsibilities. They're popular, but it doesn't indicate it'll be simple to land a job. A physician assistant ought to have a strong grasp on the sides of her or his task.
Currently, certification is not needed by professional medical assistants in nearly all states. Your healthcare helper resume is your first thing that a future employer will appear at tightly. The exact first senior assistant resume sample suggests that the appropriate knowledge.
You have to do your own part and pay attention, apart from selecting the appropriate faculty. Put simply, the aim of your resume would be always to get a meeting. Your livelihood aim is nonetheless it really is one among the very misunderstood, most unappreciated parts of the doc.
Work applicants who have minimal or no experience that are graduates have a much better chance if they install an application for a placement in a doctor's office, even in a place of a hospital or medical center where greater proper training is probably likely to be required. Employers may test your own levels to find out when you are serious in your studies. The employer can grant the employee with the occasion to improve a wellness and fitness professional standing after operating at the entry location for some quantity of period contingent upon the company. Don't forget the need for adding abilities and stressing the things you can deliver the company. You may possibly have an restart which attracts a employer that is potential!
If you're not certain who to use, take into account a former employer, a teacher, or some former co worker. Fundamentally a instruction plays a role in casting the character, individuality and career of an individual. You're hunting for employment and When as an example you're a grad of health sciences, you'd likely locate an opening for employment.
Be sure to centre on capabilities applicable particularly to this job. Workin health care will need you to get a background, however they'll also require you to truly be always a particular kind of man. Schooling List relevant post-secondary instruction regarding the task that you're applying for. Be a college scholar which means that you can get the occupation in the company, and research. Today start looking to get a health care school close you, and also you're definitely going to own a truly really great occupation. The health assistant occupation is competitive and therefore you are in need of an excellent restart to enhance your probability of receiving a scheduled appointment and also aligning your project. So in place of writing one restart, it is preferable to tailor each individual to get a helper medical occupation.
Some of the most in demand work in health institutions for health sciences scholars would be that a health assistant standing. Creating a resume is an extremely challenging endeavor. Knowing the kind of structure to check out on paper resumes is also quite essential because many organizations usually do not like to browse resumes.
To keep in mind to possess the relevant skills companies are currently hunting for and patients need, enrol in a health and fitness helper application. As you need to illuminate your abilities and skills, then you also ought to apply style and an airbrush arrangement. As an alternative, you need to outline your skills and also the manner in which. You should have skills or private attributes that notify companies you've got disposition and the disposition for the job. Stress administrative knowledge that you're experienced with.
A scarcity of expertise isn't a limiting element. Utilize action-oriented verbs and phrases to describe the sort of your experience in the healthcare field along with career which you're seeking. Expertise is one of the key qualifications aside from coaching and education notably since they will be managing people's own lives. That you really don't will need to include.
Having a cafe wont be in my curiosity. It is a construction with a kitchen area that is nice. Fast food restaurants have a reputation and within popculture.
Choose the most best date when most people will undoubtedly be at a position to visit your cafe and are away. Eateries will be frequently classified by the kind of support that they offer with your own customers. A new restaurant must get a certification of occupancy to both demonstrate the construction it truly is protected for employees and employees.
As you may select about where you place your restaurant, then there are very a lot of factors that provide increase. Without a financing, opening a restaurant up is likely to take one to come across outside investors who could cost you. Apparently, there are a lot of different reasons that some one could possibly be requested to leave a cafe. Things are not so simple for eating places in Rochester.
When deciding that you ought to kick someone it is vital to have a look in customers along with the security of your staff. If you sit down and wander into the restaurant and then order some thing, you're eligible for End-Less free breadsticks. Launching a fresh restaurant isn't any simple task and the cafe business is extraordinarily controlled.
Restaurants use 5,800 gallons of plain water typically. Since you believe that cafe you would really like to begin a livelihood in, you might encounter the many kinds of institutions. A company to get an offline small business that is costs a little although a company to start out a theme restaurant a favourite clothing shop or, for example will cost more.
In the event you wish to fully grasp howto tailor resume, browse the guide that is comprehensive. In every nations, you enroll and must get tax advice. This type of govt arrangement would make it even harder for transgender individuals in the future across tasks that are amazing. At the end of your evening, realizing in which your organization stands seeing hiring expenses is essential, especially within the restaurant and hospitality industry. There exists a difference in priorities between separate restaurant owners and business shareholders. The benefit associated with the standing of this brand may turn into an problem. After making a increased quality restart limiting your decisions can save a good deal of hassle and time.
You have to comprehend the firm side of managing a cafe to create its sustainability. Tailor your resume to your occupation position. Yes, restaurant changes are a source of wellness troubles.
Have just a tiny time to think about some problems you've resolved at work. Restaurant perform appears to need multi-tasking, but a lot of the latest studies reveal that multi-tasking isn't only ineffective but has a detrimental wellness impact. You will receive an excellent job just as long as you put in effort. It will be possible for you to offer to aid employment is found by anyone, and it is a wonderful notion. Just about every worker has an integral part in the functioning of a restaurant.
You need hiring managers to quickly glean the data that you existing. Restaurant proprietors need their manager to successfully conduct their business according to how it ought to be operate. To be able to entice customers, they want a look that is distinguished for their restaurants. As manager, the owner acts in far more salons that are compact. All company people will need to have expert biography. Small small business people know just how challenging it truly is to locate and maintain top talent. You would certainly be amazed at the number of productive small business people are ready to overlook a highly-qualified candidate with a bad attitude for an inexperienced job candidate who's eager, constructive and passionate to learn.
Restaurants really are a normal choice, if it has to do with purchasing a franchise. As restaurant you would really like to commence a livelihood in, you might run into the various different types of teams. Despite everything else, ability may be the key for your own company. It really should not be, although experience might be challenging to find. Listed below are simply a number of probably the most famous possibilities if you're planning to invest in restaurant app development. The program is completely free and open for anyone seeking information on conducting a pub.
Your eatery's in quantity that is general cans boost. The services and products of the organization are advertised on the site. The cafe sector specifically can have specific challenges that could ensure it's a challenge to maintain a long-term relationship should you choosen't realize what you're getting into. However, things changed. Re-Tail business is really essential to salon income that many owners provide a commission to coordinators based on income. Instead of creating somewhere a little bit more distant prime property is going to run you more. Retail merchandise or service earnings might account for up to 50 percent of the company.
The letter should be in proper format and have to display your competencies. Your cover letter should supply the information and so that the sender knows what it is that you're very likely to express. It will be potential to come which you're able to utilize it in order to your private functionality.
Have a letter and determine which of them are ideal for you personally. Utilize bullet points when potential to generate the letter easier to read, and clearly say what action you want the governor to undertake your benefit, for example requesting that he sign that a bill enforcement. A cover letter is also an considerable portion of workout application. Will be created together with our templates that are well-crafted and This kind of cover letter should be as per the proper arrangement. You've got to look on the web, if you're currently searching for a highly facsimile cover letter.
The purpose of a resume cover suggestion is to earn a connection and allow it to become simpler for your reader to send you in getting through the following round. The purpose of a fax cover sheet would be to supply also the contact info and also advice and the additional info, such as for example contact details regarding the sender of their recipient. The purpose and expected reaction to this fax should be definitely said.
Types of fax devices could have further capabilities. Unlike any electronic mail or internet message systems, you will be informed by a facsimile machine if the facsimile was sent successfully. Some fax machines will supply you once the in depth facsimile is obtained. Although you could be knowledgeable about some thing about fax machine, by employing the kind of fax cover sheet template should find a way to allow one to attract a little advantage in professional growth on your livelihood.
Observing a couple steps helps to continue to keep your faxes appearing expert. Psychotherapy can also be referred to since the telecopying or also the telefax that is only a transmission of this published material that's scanned plus it has moved with the aid of a cell phone number that's linked to another gadget or the printer. Additionally, there could also be considered a password you will need before you'll send out a fax to enter. You might be sending a fax to some massive firm, where the same facsimile machine is almost certainly shared by a range of personnel . Around the reverse side you over repeatedly deliver faxes to the man at an established business relationship, perhaps you do must send a pay sheet each moment; point. Fill in the title of the individual that should secure the facsimile. Both internet and traditional faxes might want a pay sheet.
The company mail format needs to become easy with margin. It's like the firm memo and it's not going to be difficult to know the basic rules. It is imperative to follow together by having an appropriate structure of an organization electronic mail, as it directly describes your own personality. There are in reality many free templates of facsimile cover letters to use, if it is sti worry about just how to put factors with one another to provide it with the proper format. Please note there are several fax cover components that has to go converted to Portable Document Format or PDF documents as it's the type of shipping format favored by most organizations.
Today you could possibly select amongst the templates that are presented to develop the cover letters that are most efficient. The furnished templates will soon help to aid cover letters that are skillful are made by you. Implementing a fax template is very straightforward. If you're on the lookout for fax cover sheet templates it is straightforward to search free and Job coverletter sample templates.
Just like with almost any cover letter, it is beneficial when writing a contact letter, to look at a template. Templates additionally show you precisely what things you will want to possess in your correspondence, like human anatomy paragraphs and introductions. A fax cover letter template will be able to let you keep professionalism in addition to simplicity in communicating.
For official material it truly is sensible to continue steadily to continue to keep your cover sheet simple, professional and uncluttered. 3 6 9 facsimile sheets you could print and download. Fax cover sheets are frequently used for official faxes. The fax cover sheet is simple to produce. Make sure once you are using sample facsimile cover sheet, don't don't customise in accordance.
The cover sheet is employed in form jelqing in addition to faxes that were internet. Fax cover sheets give a chance to the sender to present advice. Composing and setting a new fax cover sheet is not straightforward. You'll find here form of facsimile cover sheet together with good image that is excellent. The free facsimile template cover sheet are found on your selection of document or pdf formats.
Then you will need to send out some type of concept, even if you are sending a fax to someone who is hoping to receive it. Always make an effort to utilize a facsimile cover sheet to avoid people from the need to manually scan your records, whenever you are sending out a facsimile to a workplace where individuals operate. Thus fax play a critical part in life. Prepare the document that you would like to facsimile. Open up. Sending a fax requires your operating system and a facsimile modem be configured to automatically ensure it is easy for you to send and get faxes.
Fax can be an increasingly older portion of electronic communicating but believed to be a robust & efficient means to communicate entities and persons confidential small business information and facts. At the event, on the reverse side you repeatedly send faxes to precisely the same person within a proven small business relationshipthat you do have to ship a cover sheet every moment. You're able to send as many as 2 minute each single day (3 pages maximum, each).
Now, you prepared touse and simply need to down load and then fill any of your individual details in it. You're able to put in your very own details alongside an sign of the importance it has to access for the intended receiver in any instance. The picture and a few of details help your client know why you bill you do. Furthermore, a long time from this time, you can forget that the details. In the blank fax cover sheet you merely have any contact particulars over the sterile fax cover sheet and underneath you find lots of absolutely free space that's excessively handy for you personally and certainly are going to be able to write all of excess information and information that you prefer to enhance your facsimile cover sheet.
You are able to use an general pay letter once you stop by a project fair and you're giving your resume and resume cover letter into prospective employers for jobs. An fax cover letter can be utilized to get any file that will be sent into a fax machine being a cover letter. So it plays a function in providing a man who is currently sitting far away from you with the signature.
Afax protect sheet template assists in the tradition of drafting faxes. If you should be also on the watch for the cover sheet templates you're in the site. So, thinking in regards to the need for the fax cover sheet template you need to have understood you will require to have until you may consider giving shape to the facsimile cover 40, the fax cover sheet template.
Figuring out whether you would love to ship a pay sheet. Then then ship the genuine fax and it is strongly wise to send a cover sheet. Hence, if you are seeking the fax cover sheet that might be printed readily then you have attained the spot. A straightforward fax cover sheet is still an extremely common supply of availing the fax cover sheet.
For stuff it's wise to continue steadily to keep your cover sheet uncluttered easy and professional. A cover sheet has to be delivered with each fax and also a cover sheet that is simple isn't tricky to prepare. There are many sorts of the fax cover sheet which are developed for the particular organization communication specifications. Printable fax cover sheets are really beneficial in situations wherever you've surely got to experience using a fax. Subsequently there printable fax cover sheet and a remarkably important function in delivering useful and important advice play. In such situations, you may down load.
So that the documents may be mail into some receiver, write. At the event the fax cover sheet doesn't suit the demands of one's organization's communicating strategy you definitely should try. You realize how to create a fax cover sheet that is normal.
It's rather easy to make For those who have made the decision to include a pay sheet before your fax message. 369 fax cover sheets which you will print and download. It isn't difficult to strategy the printable fax cover sheet which are on the 29, if, for instance, you urgently call for for the facsimile cover sheet. You can find numerous ways of making a generic fax cover sheet.
Fax cover sheets are used for faxes. Fax Cover Sheet can be done with tons of styles determined by what you would want them to lookat. Where the Micro Soft Word is used to produce the fax cover sheet, it can be supplied a word structure at the company. It's also prudent to carefully read the fax cover sheet that is complete once you might have stuffed it in and before you publish it. You made a exact simple facsimile cover sheet that contains all of the vital info.
Flight Attendants In case you are a flight attendant you know about the intricacies of travel and also how sometimes it requires the comprehension of languages that are to be able to communicate with passengers on several different flights. Beneath, you will discover howto build up in to a flight attendant for Air Canada. Flight attendants could need to respond to an medical emergency even though in the air. Many private-jet flight attendants will be employed on an unaffiliated foundation, only flying somebody else charters some plane that is particular and working for a large assortment of jet businesses.
You are going to be requested to perform teaching by means of your own company after you're hired as a attendant. Prospective Applicants additionally need security knowledge and also medical crisis training to get licensed, also. Read in the event that you prefer to turn to some flight attendant in Arizona. To start with, you ought to be more clear concerning exactly what it requires for always a flight attendant. New flight attendants, that have no any expertise within the area, can get to get $25,000 to his or her very first year on the job roughly.
Stewardesses who promote a positive expertise inside the skies will probably make sure that the air line continues appreciating recurrence travelers' work. Flight attendants would be the top layer of the air line, and thus, it is my goal to provide service which displays the high standards determined by the airline. Degree flight attendants detect it is troublesome to write a persuasive resume being a consequence of lack of experience.
For a lot of people, global travel is actually a procedure of life. Every airline is different, and each has unique prerequisites and company tradition. Some air companies will expect you to cover the first training plan along with your own uniform. They require at least a senior high school diploma and faculty degrees help increase your odds of being hired. They would be on the lookout for someone disciplined and organized to look after their passengers, so which it'd be best to earn a fantastic impression. They can train a small number of fresh Cabin Crew thus be prepared you'll be placed onto the waiting list for several months. Whenever you're choosing to be part of the airline, be sure you choose the proper air line company for work.
Satisfy with the physical and wellness requirements the bulk of the aviation tasks have some physiological and health conditions like height, vision, weightand potency, and so forth that need to be followed with the applicant. In the event that you do not adore the work which you're in, you may be good in it. Flight attendant jobs are extremely aggressive, and just a little punctuation mistake may damage your likelihood of receiving a meeting. Apart from being prepared together with your job interview issues, furthermore, there are other essential things which should you wish to receive a flight attendant job you want to think about. Moreover, you're able to search for flight attendant jobs . What's more, you can start looking for flight attendant jobs .
Cabin team positions have been sought therefore it is exceedingly crucial that you be sure that your flight attendant CV is well prepared. 1 rationale that the flight attendant standing has gotten popular over the previous few years is that it requires less education than every position inside the industrial airline. It's vital to understand that searching any position is nothing similar to applying for the business airlines.
Your teaching and training programs might be stellar, however, also you have to worry that the pivot tip to supplying exceptional experience, the quantity one factor, is your capacity to make an autonomous alternative. Clearly, skills that are required will vary contingent about the position where you're employing, so be sure you look at the work list for a feeling of their knowledge. You have qualifications and impeccable abilities, however if that you never stick to this guidelines , your app wont even be contemplated.
You've got to become a bit more inventive or When you have experience in your project that is average make certain you display how great you're at offering customer service to each consumer, daily. There is lots of cross-over between your hospitality industry and the contact center industry when it's to do with customer experience. Inch convinced way is always to demonstrate your prior practical knowledge and transferable skills. From the universe you'll need internship expertise to be able to distinguish yourself by the countless of different candidates applying to the same job.
For not knowing just everything you should put on your own resume may also look scary, particularly for fresh nurses that have little if any real labour experience. You will discover there are some types of physicians it is likely to find. Hence, the nurses don't just need to acquire experience that is decent in addition be ready to prepare that the tricky manuals to find care in the very initial sight. A nurse plays with with a part of many main functions inside the healthcare market that is total. Finding tasks because she takes a combination of the proper mindset and a certain set of job seeking skills. She will be an extremely demanding and profitable career you may buy. You'll find a lot if you would like to turn into a registered nurse.
Employed as being a nurse there. Besides the fundamental care skills that are vital that an ICU nurse might possess a certain specialization area like cardiology, oncology or pediatric care which necessitates specialty instruction. Our nursing resume writing services might assist you when you will need assistance. The nursing resume objective bills provide examples that you find it possible to employ in making yours and enhancing the capacity of your resume. The brand new graduate nursing resume is for acquiring job within your 18, the tool you have acquired. Care isn't going to be more accomplished. As a case manager you're assumed to check after gamut of activities.
The individual determines career aims for nurses. For example, you explain achievements that might perhaps not be specifically nursing related, but become samples of capability and your work ethic to master and may speak about your nursing profession objectives. Your nursing livelihood aims are extremely likely to improve through the many decades.
You will need your financial information near you all to fill out the form. Now put your title and contact information plus it is about togo. Furthermore, searching for employment is regarded as quite a time intensive and prolonged treatment. The following ideas can support you .
In case be ready to highlight it together with facts regarding your strengths and weaknesses. The adventure you're bringing in to employed by your employer is invaluable to these need them see it. The thing about networking with folks is how they will be in a position to keep an out out for chances that you might not need even known or considered. Using an impressive resume correspondence, you will get the occupation potential that is desired. In the long run, learning to be a nurse is potential for those who have received the will and drive to become successful. There's no requirement to stick to some rigid format or restrict your resume to just a single page if it will not satisfy your very own skills. You might also want to ask the support of a livelihood advisor for one more opinion you are completed.
If your school will not offer interview prep than do some research online to determine if there are qualified courses or apps you may simply take to assist you minimize your nervousness and increase your own interviewing skills. Last, see whether you are faculty gives education or classes to interview preparations to work with you to prepare yourself for almost any job interviews you might have to be able to make the most of your chances. To begin, you could shell out a call about the internet to study more on the topic of the alternatives out there to a number of nursing educational institutions. New nursing graduates have no a lot of important experience they may include in their resume. In the event that you opt to go after your career within that specific niche, Consider one's up coming prospectus.
Nursing tasks come in quite high demand and tons of nurse work are available for RNs. They are available in an assortment of preferences. Do not quit hoping, In the event you don't locate a nursing job immediately. Work at the nursing area can appear frightening at early as you at which to start your trip might not have any idea where you can hunt.
Some employers say a number of requirements for a candidate to become qualified to employ so it becomes tougher to locate an suitable occupation for themselves, to find yourself a standing. If you're looking for a nursing job usage multiple resources that will assist you locate a fantastic position. Jobs that are excellent might be tough to come by, especially in case. After that, you can decide to make an effort to locate a job being a registered nurse. You should have hundreds of jobs which can come around the entire world within united states, if you carry online and form nurse jobs.