// SPDX-License-Identifier: Apache-2.0
// 
// Copyright 2008-2016 Conrad Sanderson (https://conradsanderson.id.au)
// Copyright 2008-2016 National ICT Australia (NICTA)
// 
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// https://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ------------------------------------------------------------------------


//! \addtogroup eop_core
//! @{


#undef arma_applier_1u
#undef arma_applier_1a
#undef arma_applier_2
#undef arma_applier_3
#undef operatorA

#undef arma_applier_1_mp
#undef arma_applier_2_mp
#undef arma_applier_3_mp


#if defined(ARMA_SIMPLE_LOOPS)
  #define arma_applier_1u(operatorA) \
    {\
    for(uword i=0; i<n_elem; ++i)\
      {\
      out_mem[i] operatorA eop_core<eop_type>::process(P[i], k);\
      }\
    }
#else
  #define arma_applier_1u(operatorA) \
    {\
    uword i,j;\
    \
    for(i=0, j=1; j<n_elem; i+=2, j+=2)\
      {\
      eT tmp_i = P[i];\
      eT tmp_j = P[j];\
      \
      tmp_i = eop_core<eop_type>::process(tmp_i, k);\
      tmp_j = eop_core<eop_type>::process(tmp_j, k);\
      \
      out_mem[i] operatorA tmp_i;\
      out_mem[j] operatorA tmp_j;\
      }\
    \
    if(i < n_elem)\
      {\
      out_mem[i] operatorA eop_core<eop_type>::process(P[i], k);\
      }\
    }
#endif



#if defined(ARMA_SIMPLE_LOOPS)
  #define arma_applier_1a(operatorA) \
    {\
    for(uword i=0; i<n_elem; ++i)\
      {\
      out_mem[i] operatorA eop_core<eop_type>::process(P.at_alt(i), k);\
      }\
    }
#else
  #define arma_applier_1a(operatorA) \
    {\
    uword i,j;\
    \
    for(i=0, j=1; j<n_elem; i+=2, j+=2)\
      {\
      eT tmp_i = P.at_alt(i);\
      eT tmp_j = P.at_alt(j);\
      \
      tmp_i = eop_core<eop_type>::process(tmp_i, k);\
      tmp_j = eop_core<eop_type>::process(tmp_j, k);\
      \
      out_mem[i] operatorA tmp_i;\
      out_mem[j] operatorA tmp_j;\
      }\
    \
    if(i < n_elem)\
      {\
      out_mem[i] operatorA eop_core<eop_type>::process(P.at_alt(i), k);\
      }\
    }
#endif


#define arma_applier_2(operatorA) \
  {\
  if(n_rows != 1)\
    {\
    for(uword col=0; col<n_cols; ++col)\
      {\
      uword i,j;\
      \
      for(i=0, j=1; j<n_rows; i+=2, j+=2)\
        {\
        eT tmp_i = P.at(i,col);\
        eT tmp_j = P.at(j,col);\
        \
        tmp_i = eop_core<eop_type>::process(tmp_i, k);\
        tmp_j = eop_core<eop_type>::process(tmp_j, k);\
        \
        *out_mem operatorA tmp_i;  out_mem++;\
        *out_mem operatorA tmp_j;  out_mem++;\
        }\
      \
      if(i < n_rows)\
        {\
        *out_mem operatorA eop_core<eop_type>::process(P.at(i,col), k);  out_mem++;\
        }\
      }\
    }\
  else\
    {\
    for(uword count=0; count < n_cols; ++count)\
      {\
      out_mem[count] operatorA eop_core<eop_type>::process(P.at(0,count), k);\
      }\
    }\
  }



#define arma_applier_3(operatorA) \
  {\
  for(uword slice=0; slice<n_slices; ++slice)\
    {\
    for(uword col=0; col<n_cols; ++col)\
      {\
      uword i,j;\
      \
      for(i=0, j=1; j<n_rows; i+=2, j+=2)\
        {\
        eT tmp_i = P.at(i,col,slice);\
        eT tmp_j = P.at(j,col,slice);\
        \
        tmp_i = eop_core<eop_type>::process(tmp_i, k);\
        tmp_j = eop_core<eop_type>::process(tmp_j, k);\
        \
        *out_mem operatorA tmp_i; out_mem++; \
        *out_mem operatorA tmp_j; out_mem++; \
        }\
      \
      if(i < n_rows)\
        {\
        *out_mem operatorA eop_core<eop_type>::process(P.at(i,col,slice), k); out_mem++; \
        }\
      }\
    }\
  }



#if defined(ARMA_USE_OPENMP)
  
  #define arma_applier_1_mp(operatorA) \
    {\
    const int n_threads = mp_thread_limit::get();\
    _Pragma("omp parallel for schedule(static) num_threads(n_threads)")\
    for(uword i=0; i<n_elem; ++i)\
      {\
      out_mem[i] operatorA eop_core<eop_type>::process(P[i], k);\
      }\
    }
  
  #define arma_applier_2_mp(operatorA) \
    {\
    const int n_threads = mp_thread_limit::get();\
    if(n_cols == 1)\
      {\
      _Pragma("omp parallel for schedule(static) num_threads(n_threads)")\
      for(uword count=0; count < n_rows; ++count)\
        {\
        out_mem[count] operatorA eop_core<eop_type>::process(P.at(count,0), k);\
        }\
      }\
    else\
    if(n_rows == 1)\
      {\
      _Pragma("omp parallel for schedule(static) num_threads(n_threads)")\
      for(uword count=0; count < n_cols; ++count)\
        {\
        out_mem[count] operatorA eop_core<eop_type>::process(P.at(0,count), k);\
        }\
      }\
    else\
      {\
      _Pragma("omp parallel for schedule(static) num_threads(n_threads)")\
      for(uword col=0; col < n_cols; ++col)\
        {\
        for(uword row=0; row < n_rows; ++row)\
          {\
          out.at(row,col) operatorA eop_core<eop_type>::process(P.at(row,col), k);\
          }\
        }\
      }\
    }
  
  #define arma_applier_3_mp(operatorA) \
    {\
    const int n_threads = mp_thread_limit::get();\
    _Pragma("omp parallel for schedule(static) num_threads(n_threads)")\
    for(uword slice=0; slice<n_slices; ++slice)\
      {\
      for(uword col=0; col<n_cols; ++col)\
      for(uword row=0; row<n_rows; ++row)\
        {\
        out.at(row,col,slice) operatorA eop_core<eop_type>::process(P.at(row,col,slice), k);\
        }\
      }\
    }

#else
  
  #define arma_applier_1_mp(operatorA)  arma_applier_1u(operatorA)
  #define arma_applier_2_mp(operatorA)  arma_applier_2(operatorA)
  #define arma_applier_3_mp(operatorA)  arma_applier_3(operatorA)

#endif



//
// matrices



template<typename eop_type>
template<typename outT, typename T1>
inline
void
eop_core<eop_type>::apply(outT& out, const eOp<T1, eop_type>& x)
  {
  arma_debug_sigprint();
  
  typedef typename T1::elem_type eT;
  
  // NOTE: we're assuming that the matrix has already been set to the correct size and there is no aliasing;
  // size setting and alias checking is done by either the Mat constructor or operator=()
  
  const eT  k       = x.aux;
        eT* out_mem = out.memptr();
  
  const bool use_mp = (arma_config::openmp) && (eOp<T1, eop_type>::use_mp || (is_same_type<eop_type, eop_pow>::value && (is_cx<eT>::yes || x.aux != eT(2))));
  
  if(Proxy<T1>::use_at == false)
    {
    const uword n_elem = x.get_n_elem();
    
    if(use_mp && mp_gate<eT>::eval(n_elem))
      {
      typename Proxy<T1>::ea_type P = x.P.get_ea();
      
      arma_applier_1_mp(=);
      }
    else
      {
      if(memory::is_aligned(out_mem))
        {
        memory::mark_as_aligned(out_mem);
        
        if(x.P.is_aligned())
          {
          typename Proxy<T1>::aligned_ea_type P = x.P.get_aligned_ea();
          
          arma_applier_1a(=);
          }
        else
          {
          typename Proxy<T1>::ea_type P = x.P.get_ea();
          
          arma_applier_1u(=);
          }
        }
      else
        {
        typename Proxy<T1>::ea_type P = x.P.get_ea();
        
        arma_applier_1u(=);
        }
      }
    }
  else
    {
    const uword n_rows = x.get_n_rows();
    const uword n_cols = x.get_n_cols();
    
    const Proxy<T1>& P = x.P;
    
    if(use_mp && mp_gate<eT>::eval(x.get_n_elem()))
      {
      arma_applier_2_mp(=);
      }
    else
      {
      arma_applier_2(=);
      }
    }
  }



template<typename eop_type>
template<typename T1>
inline
void
eop_core<eop_type>::apply_inplace_plus(Mat<typename T1::elem_type>& out, const eOp<T1, eop_type>& x)
  {
  arma_debug_sigprint();
  
  typedef typename T1::elem_type eT;
  
  const uword n_rows = x.get_n_rows();
  const uword n_cols = x.get_n_cols();
  
  arma_conform_assert_same_size(out.n_rows, out.n_cols, n_rows, n_cols, "addition");
  
  const eT  k       = x.aux;
        eT* out_mem = out.memptr();
  
  const bool use_mp = (arma_config::openmp) && (eOp<T1, eop_type>::use_mp || (is_same_type<eop_type, eop_pow>::value && (is_cx<eT>::yes || x.aux != eT(2))));
  
  if(Proxy<T1>::use_at == false)
    {
    const uword n_elem = x.get_n_elem();
    
    if(use_mp && mp_gate<eT>::eval(n_elem))
      {
      typename Proxy<T1>::ea_type P = x.P.get_ea();
      
      arma_applier_1_mp(+=);
      }
    else
      {
      if(memory::is_aligned(out_mem))
        {
        memory::mark_as_aligned(out_mem);
        
        if(x.P.is_aligned())
          {
          typename Proxy<T1>::aligned_ea_type P = x.P.get_aligned_ea();
          
          arma_applier_1a(+=);
          }
        else
          {
          typename Proxy<T1>::ea_type P = x.P.get_ea();
          
          arma_applier_1u(+=);
          }
        }
      else
        {
        typename Proxy<T1>::ea_type P = x.P.get_ea();
        
        arma_applier_1u(+=);
        }
      }
    }
  else
    {
    const Proxy<T1>& P = x.P;
    
    if(use_mp && mp_gate<eT>::eval(x.get_n_elem()))
      {
      arma_applier_2_mp(+=);
      }
    else
      {
      arma_applier_2(+=);
      }
    }
  }



template<typename eop_type>
template<typename T1>
inline
void
eop_core<eop_type>::apply_inplace_minus(Mat<typename T1::elem_type>& out, const eOp<T1, eop_type>& x)
  {
  arma_debug_sigprint();
  
  typedef typename T1::elem_type eT;
  
  const uword n_rows = x.get_n_rows();
  const uword n_cols = x.get_n_cols();
  
  arma_conform_assert_same_size(out.n_rows, out.n_cols, n_rows, n_cols, "subtraction");
  
  const eT  k       = x.aux;
        eT* out_mem = out.memptr();
  
  const bool use_mp = (arma_config::openmp) && (eOp<T1, eop_type>::use_mp || (is_same_type<eop_type, eop_pow>::value && (is_cx<eT>::yes || x.aux != eT(2))));
  
  if(Proxy<T1>::use_at == false)
    {
    const uword n_elem = x.get_n_elem();
    
    if(use_mp && mp_gate<eT>::eval(n_elem))
      {
      typename Proxy<T1>::ea_type P = x.P.get_ea();
      
      arma_applier_1_mp(-=);
      }
    else
      {
      if(memory::is_aligned(out_mem))
        {
        memory::mark_as_aligned(out_mem);
        
        if(x.P.is_aligned())
          {
          typename Proxy<T1>::aligned_ea_type P = x.P.get_aligned_ea();
          
          arma_applier_1a(-=);
          }
        else
          {
          typename Proxy<T1>::ea_type P = x.P.get_ea();
          
          arma_applier_1u(-=);
          }
        }
      else
        {
        typename Proxy<T1>::ea_type P = x.P.get_ea();
        
        arma_applier_1u(-=);
        }
      }
    }
  else
    {
    const Proxy<T1>& P = x.P;
    
    if(use_mp && mp_gate<eT>::eval(x.get_n_elem()))
      {
      arma_applier_2_mp(-=);
      }
    else
      {
      arma_applier_2(-=);
      }
    }
  }



template<typename eop_type>
template<typename T1>
inline
void
eop_core<eop_type>::apply_inplace_schur(Mat<typename T1::elem_type>& out, const eOp<T1, eop_type>& x)
  {
  arma_debug_sigprint();
  
  typedef typename T1::elem_type eT;
  
  const uword n_rows = x.get_n_rows();
  const uword n_cols = x.get_n_cols();
  
  arma_conform_assert_same_size(out.n_rows, out.n_cols, n_rows, n_cols, "element-wise multiplication");
  
  const eT  k       = x.aux;
        eT* out_mem = out.memptr();
  
  const bool use_mp = (arma_config::openmp) && (eOp<T1, eop_type>::use_mp || (is_same_type<eop_type, eop_pow>::value && (is_cx<eT>::yes || x.aux != eT(2))));
  
  if(Proxy<T1>::use_at == false)
    {
    const uword n_elem = x.get_n_elem();
    
    if(use_mp && mp_gate<eT>::eval(n_elem))
      {
      typename Proxy<T1>::ea_type P = x.P.get_ea();
      
      arma_applier_1_mp(*=);
      }
    else
      {
      if(memory::is_aligned(out_mem))
        {
        memory::mark_as_aligned(out_mem);
        
        if(x.P.is_aligned())
          {
          typename Proxy<T1>::aligned_ea_type P = x.P.get_aligned_ea();
          
          arma_applier_1a(*=);
          }
        else
          {
          typename Proxy<T1>::ea_type P = x.P.get_ea();
          
          arma_applier_1u(*=);
          }
        }
      else
        {
        typename Proxy<T1>::ea_type P = x.P.get_ea();
        
        arma_applier_1u(*=);
        }
      }
    }
  else
    {
    const Proxy<T1>& P = x.P;
    
    if(use_mp && mp_gate<eT>::eval(x.get_n_elem()))
      {
      arma_applier_2_mp(*=);
      }
    else
      {
      arma_applier_2(*=);
      }
    }
  }



template<typename eop_type>
template<typename T1>
inline
void
eop_core<eop_type>::apply_inplace_div(Mat<typename T1::elem_type>& out, const eOp<T1, eop_type>& x)
  {
  arma_debug_sigprint();
  
  typedef typename T1::elem_type eT;
  
  const uword n_rows = x.get_n_rows();
  const uword n_cols = x.get_n_cols();
  
  arma_conform_assert_same_size(out.n_rows, out.n_cols, n_rows, n_cols, "element-wise division");
  
  const eT  k       = x.aux;
        eT* out_mem = out.memptr();
  
  const bool use_mp = (arma_config::openmp) && (eOp<T1, eop_type>::use_mp || (is_same_type<eop_type, eop_pow>::value && (is_cx<eT>::yes || x.aux != eT(2))));
  
  if(Proxy<T1>::use_at == false)
    {
    const uword n_elem = x.get_n_elem();
    
    if(use_mp && mp_gate<eT>::eval(n_elem))
      {
      typename Proxy<T1>::ea_type P = x.P.get_ea();
      
      arma_applier_1_mp(/=);
      }
    else
      {
      if(memory::is_aligned(out_mem))
        {
        memory::mark_as_aligned(out_mem);
        
        if(x.P.is_aligned())
          {
          typename Proxy<T1>::aligned_ea_type P = x.P.get_aligned_ea();
          
          arma_applier_1a(/=);
          }
        else
          {
          typename Proxy<T1>::ea_type P = x.P.get_ea();
          
          arma_applier_1u(/=);
          }
        }
      else
        {
        typename Proxy<T1>::ea_type P = x.P.get_ea();
        
        arma_applier_1u(/=);
        }
      }
    }
  else
    {
    const Proxy<T1>& P = x.P;
    
    if(use_mp && mp_gate<eT>::eval(x.get_n_elem()))
      {
      arma_applier_2_mp(/=);
      }
    else
      {
      arma_applier_2(/=);
      }
    }
  }



//
// cubes



template<typename eop_type>
template<typename T1>
inline
void
eop_core<eop_type>::apply(Cube<typename T1::elem_type>& out, const eOpCube<T1, eop_type>& x)
  {
  arma_debug_sigprint();
  
  typedef typename T1::elem_type eT;
  
  // NOTE: we're assuming that the matrix has already been set to the correct size and there is no aliasing;
  // size setting and alias checking is done by either the Mat constructor or operator=()
  
  const eT  k       = x.aux;
        eT* out_mem = out.memptr();
  
  const bool use_mp = (arma_config::openmp) && (eOpCube<T1, eop_type>::use_mp || (is_same_type<eop_type, eop_pow>::value && (is_cx<eT>::yes || x.aux != eT(2))));
  
  if(ProxyCube<T1>::use_at == false)
    {
    const uword n_elem = out.n_elem;
    
    if(use_mp && mp_gate<eT>::eval(n_elem))
      {
      typename ProxyCube<T1>::ea_type P = x.P.get_ea();
      
      arma_applier_1_mp(=);
      }
    else
      {
      if(memory::is_aligned(out_mem))
        {
        memory::mark_as_aligned(out_mem);
        
        if(x.P.is_aligned())
          {
          typename ProxyCube<T1>::aligned_ea_type P = x.P.get_aligned_ea();
          
          arma_applier_1a(=);
          }
        else
          {
          typename ProxyCube<T1>::ea_type P = x.P.get_ea();
          
          arma_applier_1u(=);
          }
        }
      else
        {
        typename ProxyCube<T1>::ea_type P = x.P.get_ea();
        
        arma_applier_1u(=);
        }
      }
    }
  else
    {
    const uword n_rows   = x.get_n_rows();
    const uword n_cols   = x.get_n_cols();
    const uword n_slices = x.get_n_slices();
    
    const ProxyCube<T1>& P = x.P;
    
    if(use_mp && mp_gate<eT>::eval(x.get_n_elem()))
      {
      arma_applier_3_mp(=);
      }
    else
      {
      arma_applier_3(=);
      }
    }
  }



template<typename eop_type>
template<typename T1>
inline
void
eop_core<eop_type>::apply_inplace_plus(Cube<typename T1::elem_type>& out, const eOpCube<T1, eop_type>& x)
  {
  arma_debug_sigprint();
  
  typedef typename T1::elem_type eT;
  
  const uword n_rows   = x.get_n_rows();
  const uword n_cols   = x.get_n_cols();
  const uword n_slices = x.get_n_slices();
  
  arma_conform_assert_same_size(out.n_rows, out.n_cols, out.n_slices, n_rows, n_cols, n_slices, "addition");
  
  const eT  k       = x.aux;
        eT* out_mem = out.memptr();
  
  const bool use_mp = (arma_config::openmp) && (eOpCube<T1, eop_type>::use_mp || (is_same_type<eop_type, eop_pow>::value && (is_cx<eT>::yes || x.aux != eT(2))));
  
  if(ProxyCube<T1>::use_at == false)
    {
    const uword n_elem = out.n_elem;
    
    if(use_mp && mp_gate<eT>::eval(n_elem))
      {
      typename ProxyCube<T1>::ea_type P = x.P.get_ea();
      
      arma_applier_1_mp(+=);
      }
    else
      {
      if(memory::is_aligned(out_mem))
        {
        memory::mark_as_aligned(out_mem);
        
        if(x.P.is_aligned())
          {
          typename ProxyCube<T1>::aligned_ea_type P = x.P.get_aligned_ea();
          
          arma_applier_1a(+=);
          }
        else
          {
          typename ProxyCube<T1>::ea_type P = x.P.get_ea();
          
          arma_applier_1u(+=);
          }
        }
      else
        {
        typename ProxyCube<T1>::ea_type P = x.P.get_ea();
        
        arma_applier_1u(+=);
        }
      }
    }
  else
    {
    const ProxyCube<T1>& P = x.P;
    
    if(use_mp && mp_gate<eT>::eval(x.get_n_elem()))
      {
      arma_applier_3_mp(+=);
      }
    else
      {
      arma_applier_3(+=);
      }
    }
  }



template<typename eop_type>
template<typename T1>
inline
void
eop_core<eop_type>::apply_inplace_minus(Cube<typename T1::elem_type>& out, const eOpCube<T1, eop_type>& x)
  {
  arma_debug_sigprint();
  
  typedef typename T1::elem_type eT;
  
  const uword n_rows   = x.get_n_rows();
  const uword n_cols   = x.get_n_cols();
  const uword n_slices = x.get_n_slices();
  
  arma_conform_assert_same_size(out.n_rows, out.n_cols, out.n_slices, n_rows, n_cols, n_slices, "subtraction");
  
  const eT  k       = x.aux;
        eT* out_mem = out.memptr();
  
  const bool use_mp = (arma_config::openmp) && (eOpCube<T1, eop_type>::use_mp || (is_same_type<eop_type, eop_pow>::value && (is_cx<eT>::yes || x.aux != eT(2))));
  
  if(ProxyCube<T1>::use_at == false)
    {
    const uword n_elem = out.n_elem;
      
    if(use_mp && mp_gate<eT>::eval(n_elem))
      {
      typename ProxyCube<T1>::ea_type P = x.P.get_ea();
      
      arma_applier_1_mp(-=);
      }
    else
      {
      if(memory::is_aligned(out_mem))
        {
        memory::mark_as_aligned(out_mem);
        
        if(x.P.is_aligned())
          {
          typename ProxyCube<T1>::aligned_ea_type P = x.P.get_aligned_ea();
          
          arma_applier_1a(-=);
          }
        else
          {
          typename ProxyCube<T1>::ea_type P = x.P.get_ea();
          
          arma_applier_1u(-=);
          }
        }
      else
        {
        typename ProxyCube<T1>::ea_type P = x.P.get_ea();
        
        arma_applier_1u(-=);
        }
      }
    }
  else
    {
    const ProxyCube<T1>& P = x.P;
    
    if(use_mp && mp_gate<eT>::eval(x.get_n_elem()))
      {
      arma_applier_3_mp(-=);
      }
    else
      {
      arma_applier_3(-=);
      }
    }
  }



template<typename eop_type>
template<typename T1>
inline
void
eop_core<eop_type>::apply_inplace_schur(Cube<typename T1::elem_type>& out, const eOpCube<T1, eop_type>& x)
  {
  arma_debug_sigprint();
  
  typedef typename T1::elem_type eT;
  
  const uword n_rows   = x.get_n_rows();
  const uword n_cols   = x.get_n_cols();
  const uword n_slices = x.get_n_slices();
  
  arma_conform_assert_same_size(out.n_rows, out.n_cols, out.n_slices, n_rows, n_cols, n_slices, "element-wise multiplication");
  
  const eT  k       = x.aux;
        eT* out_mem = out.memptr();
  
  const bool use_mp = (arma_config::openmp) && (eOpCube<T1, eop_type>::use_mp || (is_same_type<eop_type, eop_pow>::value && (is_cx<eT>::yes || x.aux != eT(2))));
  
  if(ProxyCube<T1>::use_at == false)
    {
    const uword n_elem = out.n_elem;
    
    if(use_mp && mp_gate<eT>::eval(n_elem))
      {
      typename ProxyCube<T1>::ea_type P = x.P.get_ea();
      
      arma_applier_1_mp(*=);
      }
    else
      {
      if(memory::is_aligned(out_mem))
        {
        memory::mark_as_aligned(out_mem);
        
        if(x.P.is_aligned())
          {
          typename ProxyCube<T1>::aligned_ea_type P = x.P.get_aligned_ea();
          
          arma_applier_1a(*=);
          }
        else
          {
          typename ProxyCube<T1>::ea_type P = x.P.get_ea();
          
          arma_applier_1u(*=);
          }
        }
      else
        {
        typename ProxyCube<T1>::ea_type P = x.P.get_ea();
        
        arma_applier_1u(*=);
        }
      }
    }
  else
    {
    const ProxyCube<T1>& P = x.P;
    
    if(use_mp && mp_gate<eT>::eval(x.get_n_elem()))
      {
      arma_applier_3_mp(*=);
      }
    else
      {
      arma_applier_3(*=);
      }
    }
  }



template<typename eop_type>
template<typename T1>
inline
void
eop_core<eop_type>::apply_inplace_div(Cube<typename T1::elem_type>& out, const eOpCube<T1, eop_type>& x)
  {
  arma_debug_sigprint();
  
  typedef typename T1::elem_type eT;
  
  const uword n_rows   = x.get_n_rows();
  const uword n_cols   = x.get_n_cols();
  const uword n_slices = x.get_n_slices();
  
  arma_conform_assert_same_size(out.n_rows, out.n_cols, out.n_slices, n_rows, n_cols, n_slices, "element-wise division");
  
  const eT  k       = x.aux;
        eT* out_mem = out.memptr();
  
  const bool use_mp = (arma_config::openmp) && (eOpCube<T1, eop_type>::use_mp || (is_same_type<eop_type, eop_pow>::value && (is_cx<eT>::yes || x.aux != eT(2))));
  
  if(ProxyCube<T1>::use_at == false)
    {
    const uword n_elem = out.n_elem;
    
    if(use_mp && mp_gate<eT>::eval(n_elem))
      {
      typename ProxyCube<T1>::ea_type P = x.P.get_ea();
      
      arma_applier_1_mp(/=);
      }
    else
      {
      if(memory::is_aligned(out_mem))
        {
        memory::mark_as_aligned(out_mem);
        
        if(x.P.is_aligned())
          {
          typename ProxyCube<T1>::aligned_ea_type P = x.P.get_aligned_ea();
          
          arma_applier_1a(/=);
          }
        else
          {
          typename ProxyCube<T1>::ea_type P = x.P.get_ea();
          
          arma_applier_1u(/=);
          }
        }
      else
        {
        typename ProxyCube<T1>::ea_type P = x.P.get_ea();
        
        arma_applier_1u(/=);
        }
      }
    }
  else
    {
    const ProxyCube<T1>& P = x.P;
    
    if(use_mp && mp_gate<eT>::eval(x.get_n_elem()))
      {
      arma_applier_3_mp(/=);
      }
    else
      {
      arma_applier_3(/=);
      }
    }
  }



//
// common



template<typename eop_type>
template<typename eT>
arma_inline
eT
eop_core<eop_type>::process(const eT, const eT)
  {
  arma_stop_logic_error("eop_core::process(): unhandled eop_type");
  return eT(0);
  }



template<> template<typename eT> arma_inline eT
eop_core<eop_scalar_plus      >::process(const eT val, const eT k) { return val + k;                  }

template<> template<typename eT> arma_inline eT
eop_core<eop_scalar_minus_pre >::process(const eT val, const eT k) { return k - val;                  }

template<> template<typename eT> arma_inline eT
eop_core<eop_scalar_minus_post>::process(const eT val, const eT k) { return val - k;                  }

template<> template<typename eT> arma_inline eT
eop_core<eop_scalar_times     >::process(const eT val, const eT k) { return val * k;                  }

template<> template<typename eT> arma_inline eT
eop_core<eop_scalar_div_pre   >::process(const eT val, const eT k) { return k / val;                  }

template<> template<typename eT> arma_inline eT
eop_core<eop_scalar_div_post  >::process(const eT val, const eT k) { return val / k;                  }

template<> template<typename eT> arma_inline eT
eop_core<eop_square           >::process(const eT val, const eT  ) { return val*val;                  }

template<> template<typename eT> arma_inline eT
eop_core<eop_neg              >::process(const eT val, const eT  ) { return eop_aux::neg(val);        }

template<> template<typename eT> arma_inline eT
eop_core<eop_sqrt             >::process(const eT val, const eT  ) { return eop_aux::sqrt(val);       }

template<> template<typename eT> arma_inline eT
eop_core<eop_log              >::process(const eT val, const eT  ) { return eop_aux::log(val);        }

template<> template<typename eT> arma_inline eT
eop_core<eop_log2             >::process(const eT val, const eT  ) { return eop_aux::log2(val);       }

template<> template<typename eT> arma_inline eT
eop_core<eop_log10            >::process(const eT val, const eT  ) { return eop_aux::log10(val);      }

template<> template<typename eT> arma_inline eT
eop_core<eop_trunc_log        >::process(const eT val, const eT  ) { return    arma::trunc_log(val);  }

template<> template<typename eT> arma_inline eT
eop_core<eop_log1p            >::process(const eT val, const eT  ) { return eop_aux::log1p(val);      }

template<> template<typename eT> arma_inline eT
eop_core<eop_exp              >::process(const eT val, const eT  ) { return eop_aux::exp(val);        }

template<> template<typename eT> arma_inline eT
eop_core<eop_exp2             >::process(const eT val, const eT  ) { return eop_aux::exp2(val);       }

template<> template<typename eT> arma_inline eT
eop_core<eop_exp10            >::process(const eT val, const eT  ) { return eop_aux::exp10(val);      }

template<> template<typename eT> arma_inline eT
eop_core<eop_trunc_exp        >::process(const eT val, const eT  ) { return    arma::trunc_exp(val);  }

template<> template<typename eT> arma_inline eT
eop_core<eop_expm1            >::process(const eT val, const eT  ) { return eop_aux::expm1(val);      }

template<> template<typename eT> arma_inline eT
eop_core<eop_cos              >::process(const eT val, const eT  ) { return eop_aux::cos(val);        }

template<> template<typename eT> arma_inline eT
eop_core<eop_sin              >::process(const eT val, const eT  ) { return eop_aux::sin(val);        }

template<> template<typename eT> arma_inline eT
eop_core<eop_tan              >::process(const eT val, const eT  ) { return eop_aux::tan(val);        }

template<> template<typename eT> arma_inline eT
eop_core<eop_acos             >::process(const eT val, const eT  ) { return eop_aux::acos(val);       }

template<> template<typename eT> arma_inline eT
eop_core<eop_asin             >::process(const eT val, const eT  ) { return eop_aux::asin(val);       }

template<> template<typename eT> arma_inline eT
eop_core<eop_atan             >::process(const eT val, const eT  ) { return eop_aux::atan(val);       }

template<> template<typename eT> arma_inline eT
eop_core<eop_cosh             >::process(const eT val, const eT  ) { return eop_aux::cosh(val);       }

template<> template<typename eT> arma_inline eT
eop_core<eop_sinh             >::process(const eT val, const eT  ) { return eop_aux::sinh(val);       }

template<> template<typename eT> arma_inline eT
eop_core<eop_tanh             >::process(const eT val, const eT  ) { return eop_aux::tanh(val);       }

template<> template<typename eT> arma_inline eT
eop_core<eop_acosh            >::process(const eT val, const eT  ) { return eop_aux::acosh(val);      }

template<> template<typename eT> arma_inline eT
eop_core<eop_asinh            >::process(const eT val, const eT  ) { return eop_aux::asinh(val);      }

template<> template<typename eT> arma_inline eT
eop_core<eop_atanh            >::process(const eT val, const eT  ) { return eop_aux::atanh(val);      }

template<> template<typename eT> arma_inline eT
eop_core<eop_sinc             >::process(const eT val, const eT  ) { return arma_sinc(val);           }

template<> template<typename eT> arma_inline eT
eop_core<eop_abs              >::process(const eT val, const eT  ) { return eop_aux::arma_abs(val);   }

template<> template<typename eT> arma_inline eT
eop_core<eop_arg              >::process(const eT val, const eT  ) { return arma_arg<eT>::eval(val);  }

template<> template<typename eT> arma_inline eT
eop_core<eop_conj             >::process(const eT val, const eT  ) { return eop_aux::conj(val);       }

template<> template<typename eT> arma_inline eT
eop_core<eop_pow              >::process(const eT val, const eT k) { return eop_aux::pow(val, k);     }

template<> template<typename eT> arma_inline eT
eop_core<eop_floor            >::process(const eT val, const eT  ) { return eop_aux::floor(val);      }

template<> template<typename eT> arma_inline eT
eop_core<eop_ceil             >::process(const eT val, const eT  ) { return eop_aux::ceil(val);       }

template<> template<typename eT> arma_inline eT
eop_core<eop_round            >::process(const eT val, const eT  ) { return eop_aux::round(val);      }

template<> template<typename eT> arma_inline eT
eop_core<eop_trunc            >::process(const eT val, const eT  ) { return eop_aux::trunc(val);      }

template<> template<typename eT> arma_inline eT
eop_core<eop_sign             >::process(const eT val, const eT  ) { return arma_sign(val);           }

template<> template<typename eT> arma_inline eT
eop_core<eop_cbrt             >::process(const eT val, const eT  ) { return eop_aux::cbrt(val);       }

template<> template<typename eT> arma_inline eT
eop_core<eop_erf              >::process(const eT val, const eT  ) { return eop_aux::erf(val);        }

template<> template<typename eT> arma_inline eT
eop_core<eop_erfc             >::process(const eT val, const eT  ) { return eop_aux::erfc(val);       }

template<> template<typename eT> arma_inline eT
eop_core<eop_lgamma           >::process(const eT val, const eT  ) { return eop_aux::lgamma(val);     }

template<> template<typename eT> arma_inline eT
eop_core<eop_tgamma           >::process(const eT val, const eT  ) { return eop_aux::tgamma(val);     }


#undef arma_applier_1u
#undef arma_applier_1a
#undef arma_applier_2
#undef arma_applier_3

#undef arma_applier_1_mp
#undef arma_applier_2_mp
#undef arma_applier_3_mp


//! @}
