/****************************************************************************
 *   This file is part of the aGrUM/pyAgrum library.                        *
 *                                                                          *
 *   Copyright (c) 2005-2026 by                                             *
 *       - Pierre-Henri WUILLEMIN(_at_LIP6)                                 *
 *       - Christophe GONZALES(_at_AMU)                                     *
 *                                                                          *
 *   The aGrUM/pyAgrum library is free software; you can redistribute it    *
 *   and/or modify it under the terms of either :                           *
 *                                                                          *
 *    - the GNU Lesser General Public License as published by               *
 *      the Free Software Foundation, either version 3 of the License,      *
 *      or (at your option) any later version,                              *
 *    - the MIT license (MIT),                                              *
 *    - or both in dual license, as here.                                   *
 *                                                                          *
 *   (see https://agrum.gitlab.io/articles/dual-licenses-lgplv3mit.html)    *
 *                                                                          *
 *   This aGrUM/pyAgrum library is distributed in the hope that it will be  *
 *   useful, but WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,          *
 *   INCLUDING BUT NOT LIMITED TO THE WARRANTIES MERCHANTABILITY or FITNESS *
 *   FOR A PARTICULAR PURPOSE  AND NONINFRINGEMENT. IN NO EVENT SHALL THE   *
 *   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER *
 *   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,        *
 *   ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR  *
 *   OTHER DEALINGS IN THE SOFTWARE.                                        *
 *                                                                          *
 *   See LICENCES for more details.                                         *
 *                                                                          *
 *   SPDX-FileCopyrightText: Copyright 2005-2026                            *
 *       - Pierre-Henri WUILLEMIN(_at_LIP6)                                 *
 *       - Christophe GONZALES(_at_AMU)                                     *
 *   SPDX-License-Identifier: LGPL-3.0-or-later OR MIT                      *
 *                                                                          *
 *   Contact  : info_at_agrum_dot_org                                       *
 *   homepage : http://agrum.gitlab.io                                      *
 *   gitlab   : https://gitlab.com/agrumery/agrum                           *
 *                                                                          *
 ****************************************************************************/


/**
 * @file
 * @brief Efficient functionals for combining multiDims
 *
 * @author Christophe GONZALES(_at_AMU) and Pierre-Henri WUILLEMIN(_at_LIP6)
 */

#ifndef GUM_OPERATORS_4_MULTI_DIM_H
#define GUM_OPERATORS_4_MULTI_DIM_H

#include <atomic>
#include <mutex>

#include <agrum/base/core/timer.h>
#include <agrum/base/multidim/implementations/multiDimArray.h>
#include <agrum/base/multidim/implementations/multiDimFunctionGraph.h>

namespace gum {

  // ==========================================================================
  /// @name Add MultiDims
  // ==========================================================================
  /// @{

  /**
   * @brief a specialized function for summing two multiDimArrays
   * @ingroup multidim_op_group
   *
   * The function produces a tensorial addition of t1 and t2, that is, if t1
   * and t2 are multiDimArrays over variables A,B,C,D and D,B,E respectively,
   * function add2MultiDimArrays will return a multiDimArray T defined as
   * T[A,B,C,D,E] = t1[A,B,C,D] + t2[D,B,E] for all A,B,C,D,E.
   *
   * @return function add2MultiDimArrays allocates on the heap the new T
   * multiDimArray and returns it
   *
   * @warning In practice, do not use this function but rather operator+ on
   * multiDimImplementations. This operator will take care to select this
   * function if it is appropriate for your addition.
   */
  template < typename T >
  MultiDimArray< T >* add2MultiDimArrays(const MultiDimArray< T >* t1,
                                         const MultiDimArray< T >* t2);

  /**
   * @brief a specialized function for summing two multiDimArrays
   * @ingroup multidim_op_group
   *
   * The function produces a tensorial addition of t1 and t2, that is, if t1
   * and t2 are multiDimArrays over variables A,B,C,D and D,B,E respectively,
   * function add2MultiDimArrays will return a multiDimArray T defined as
   * T[A,B,C,D,E] = t1[A,B,C,D] + t2[D,B,E] for all A,B,C,D,E.
   *
   * @return function add2MultiDimArrays allocates on the heap the new T
   * multiDimArray and returns it
   *
   * @warning In practice, do not use this function but rather operator+ on
   * multiDimImplementations. This operator will take care to select this
   * function if it is appropriate for your addition.
   */
  template < typename T >
  MultiDimImplementation< T >* add2MultiDimArrays(const MultiDimImplementation< T >* t1,
                                                  const MultiDimImplementation< T >* t2);

  /**
   * @brief a specialized function for summing two multiDimArrays
   * @ingroup multidim_op_group
   *
   * The function produces a tensorial addition of t1 and t2, that is, if t1
   * and t2 are multiDimArrays over variables A,B,C,D and D,B,E respectively,
   * function add2MultiDimArrays will return a multiDimArray T defined as
   * T[A,B,C,D,E] = t1[A,B,C,D] + t2[D,B,E] for all A,B,C,D,E.
   *
   * @return function add2MultiDimArrays allocates on the heap the new T
   * multiDimArray and returns it
   *
   * @warning In practice, do not use this function but rather operator+ on
   * multiDimImplementations. This operator will take care to select this
   * function if it is appropriate for your addition.
   */
  template < typename T >
  MultiDimArray< T* >* add2MultiDimArrays4Pointers(const MultiDimArray< T* >* t1,
                                                   const MultiDimArray< T* >* t2);

  /**
   * @brief a specialized function for summing two multiDimArrays
   * @ingroup multidim_op_group
   *
   * The function produces a tensorial addition of t1 and t2, that is, if t1
   * and t2 are multiDimArrays over variables A,B,C,D and D,B,E respectively,
   * function add2MultiDimArrays will return a multiDimArray T defined as
   * T[A,B,C,D,E] = t1[A,B,C,D] + t2[D,B,E] for all A,B,C,D,E.
   *
   * @return function add2MultiDimArrays allocates on the heap the new T
   * multiDimArray and returns it
   *
   * @warning In practice, do not use this function but rather operator+ on
   * multiDimImplementations. This operator will take care to select this
   * function if it is appropriate for your addition.
   */
  template < typename T >
  MultiDimImplementation< T* >* add2MultiDimArrays4Pointers(const MultiDimImplementation< T* >* t1,
                                                            const MultiDimImplementation< T* >* t2);

  /**
   * @brief a specialized function for summing two multiDimArrays
   * @ingroup multidim_op_group
   *
   * The function produces a tensorial addition of t1 and t2, that is, if t1
   * and t2 are multiDimArrays over variables A,B,C,D and D,B,E respectively,
   * function add2MultiDimArrays will return a multiDimArray T defined as
   * T[A,B,C,D,E] = t1[A,B,C,D] + t2[D,B,E] for all A,B,C,D,E.
   *
   * @return function add2MultiDimArrays allocates on the heap the new T
   * multiDimArray and returns it
   *
   * @warning In practice, do not use this function but rather operator+ on
   * multiDimImplementations. This operator will take care to select this
   * function if it is appropriate for your addition.
   */
  template < typename T >
  MultiDimFunctionGraph< T >* add2MultiDimFunctionGraphs(const MultiDimFunctionGraph< T >* t1,
                                                         const MultiDimFunctionGraph< T >* t2);

  /**
   * @brief a specialized function for summing two multiDimArrays
   * @ingroup multidim_op_group
   *
   * The function produces a tensorial addition of t1 and t2, that is, if t1
   * and t2 are multiDimArrays over variables A,B,C,D and D,B,E respectively,
   * function add2MultiDimArrays will return a multiDimArray T defined as
   * T[A,B,C,D,E] = t1[A,B,C,D] + t2[D,B,E] for all A,B,C,D,E.
   *
   * @return function add2MultiDimArrays allocates on the heap the new T
   * multiDimArray and returns it
   *
   * @warning In practice, do not use this function but rather operator+ on
   * multiDimImplementations. This operator will take care to select this
   * function if it is appropriate for your addition.
   */
  template < typename T >
  MultiDimImplementation< T >* add2MultiDimFunctionGraphs(const MultiDimImplementation< T >* t1,
                                                          const MultiDimImplementation< T >* t2);

  /// @}
  // ==========================================================================
  /// @name Substract MultiDims
  // ==========================================================================
  /// @{

  /**
   * @brief a specialized function for subtracting two multiDimArrays
   * @ingroup multidim_op_group
   *
   * The function produces a tensorial subtraction of t2 from t1, that is, if
   * t1 and t2 are multiDimArrays over variables A,B,C,D and D,B,E
   * respectively, function subtract2MultiDimArrays will return a multiDimArray
   * T defined as T[A,B,C,D,E] = t1[A,B,C,D] - t2[D,B,E] for all A,B,C,D,E.
   *
   * @return function subtract2MultiDimArrays allocates on the heap the new T
   * multiDimArray and returns it
   *
   * @warning In practice, do not use this function but rather operator- on
   * multiDimImplementations. This operator will take care to select this
   * function if it is appropriate for your subtraction.
   */
  template < typename T >
  MultiDimArray< T >* subtract2MultiDimArrays(const MultiDimArray< T >* t1,
                                              const MultiDimArray< T >* t2);

  /**
   * @brief a specialized function for subtracting two multiDimArrays
   * @ingroup multidim_op_group
   *
   * The function produces a tensorial subtraction of t2 from t1, that is, if
   * t1 and t2 are multiDimArrays over variables A,B,C,D and D,B,E
   * respectively, function subtract2MultiDimArrays will return a multiDimArray
   * T defined as T[A,B,C,D,E] = t1[A,B,C,D] - t2[D,B,E] for all A,B,C,D,E.
   *
   * @return function subtract2MultiDimArrays allocates on the heap the new T
   * multiDimArray and returns it
   *
   * @warning In practice, do not use this function but rather operator- on
   * multiDimImplementations. This operator will take care to select this
   * function if it is appropriate for your subtraction.
   */
  template < typename T >
  MultiDimImplementation< T >* subtract2MultiDimArrays(const MultiDimImplementation< T >* t1,
                                                       const MultiDimImplementation< T >* t2);

  /**
   * @brief a specialized function for subtracting two multiDimArrays
   * @ingroup multidim_op_group
   *
   * The function produces a tensorial subtraction of t2 from t1, that is, if
   * t1 and t2 are multiDimArrays over variables A,B,C,D and D,B,E
   * respectively, function subtract2MultiDimArrays will return a multiDimArray
   * T defined as T[A,B,C,D,E] = t1[A,B,C,D] - t2[D,B,E] for all A,B,C,D,E.
   *
   * @return function subtract2MultiDimArrays allocates on the heap the new T
   * multiDimArray and returns it
   *
   * @warning In practice, do not use this function but rather operator- on
   * multiDimImplementations. This operator will take care to select this
   * function if it is appropriate for your subtraction.
   */
  template < typename T >
  MultiDimArray< T* >* subtract2MultiDimArrays4Pointers(const MultiDimArray< T* >* t1,
                                                        const MultiDimArray< T* >* t2);

  /**
   * @brief a specialized function for subtracting two multiDimArrays
   * @ingroup multidim_op_group
   *
   * The function produces a tensorial subtraction of t2 from t1, that is, if
   * t1 and t2 are multiDimArrays over variables A,B,C,D and D,B,E
   * respectively, function subtract2MultiDimArrays will return a multiDimArray
   * T defined as T[A,B,C,D,E] = t1[A,B,C,D] - t2[D,B,E] for all A,B,C,D,E.
   *
   * @return function subtract2MultiDimArrays allocates on the heap the new T
   * multiDimArray and returns it
   *
   * @warning In practice, do not use this function but rather operator- on
   * multiDimImplementations. This operator will take care to select this
   * function if it is appropriate for your subtraction.
   */
  template < typename T >
  MultiDimImplementation< T* >*
      subtract2MultiDimArrays4Pointers(const MultiDimImplementation< T* >* t1,
                                       const MultiDimImplementation< T* >* t2);

  /**
   * @brief a specialized function for subtracting two multiDimArrays
   * @ingroup multidim_op_group
   *
   * The function produces a tensorial subtraction of t2 from t1, that is, if
   * t1 and t2 are multiDimArrays over variables A,B,C,D and D,B,E
   * respectively, function subtract2MultiDimArrays will return a multiDimArray
   * T defined as T[A,B,C,D,E] = t1[A,B,C,D] - t2[D,B,E] for all A,B,C,D,E.
   *
   * @return function subtract2MultiDimArrays allocates on the heap the new T
   * multiDimArray and returns it
   *
   * @warning In practice, do not use this function but rather operator- on
   * multiDimImplementations. This operator will take care to select this
   * function if it is appropriate for your subtraction.
   */
  template < typename T >
  MultiDimFunctionGraph< T >* subtract2MultiDimFunctionGraphs(const MultiDimFunctionGraph< T >* t1,
                                                              const MultiDimFunctionGraph< T >* t2);

  /**
   * @brief a specialized function for subtracting two multiDimArrays
   * @ingroup multidim_op_group
   *
   * The function produces a tensorial subtraction of t2 from t1, that is, if
   * t1 and t2 are multiDimArrays over variables A,B,C,D and D,B,E
   * respectively, function subtract2MultiDimArrays will return a multiDimArray
   * T defined as T[A,B,C,D,E] = t1[A,B,C,D] - t2[D,B,E] for all A,B,C,D,E.
   *
   * @return function subtract2MultiDimArrays allocates on the heap the new T
   * multiDimArray and returns it
   *
   * @warning In practice, do not use this function but rather operator- on
   * multiDimImplementations. This operator will take care to select this
   * function if it is appropriate for your subtraction.
   */
  template < typename T >
  MultiDimImplementation< T >*
      subtract2MultiDimFunctionGraphs(const MultiDimImplementation< T >* t1,
                                      const MultiDimImplementation< T >* t2);

  /// @}
  // ==========================================================================
  /// @name Multiply MultiDims
  // ==========================================================================
  /// @{

  /**
   * @brief a specialized function for multiplying two multiDimArrays
   * @ingroup multidim_op_group
   *
   * The function produces a tensorial product of t2 from t1, that is, if t1
   * and t2 are multiDimArrays over variables A,B,C,D and D,B,E respectively,
   * function multiply2MultiDimArrays will return a multiDimArray T defined as
   * T[A,B,C,D,E] = t1[A,B,C,D] * t2[D,B,E] for all A,B,C,D,E.
   *
   * @return function multiply2MultiDimArrays allocates on the heap the new T
   * multiDimArray and returns it
   *
   * @warning In practice, do not use this function but rather operator* on
   * multiDimImplementations. This operator will take care to select this
   * function if it is appropriate for your multiplication.
   */
  template < typename T >
  MultiDimArray< T >* multiply2MultiDimArrays(const MultiDimArray< T >* t1,
                                              const MultiDimArray< T >* t2);

  /**
   * @brief a specialized function for multiplying two multiDimArrays
   * @ingroup multidim_op_group
   *
   * The function produces a tensorial product of t2 from t1, that is, if t1
   * and t2 are multiDimArrays over variables A,B,C,D and D,B,E respectively,
   * function multiply2MultiDimArrays will return a multiDimArray T defined as
   * T[A,B,C,D,E] = t1[A,B,C,D] * t2[D,B,E] for all A,B,C,D,E.
   *
   * @return function multiply2MultiDimArrays allocates on the heap the new T
   * multiDimArray and returns it
   *
   * @warning In practice, do not use this function but rather operator* on
   * multiDimImplementations. This operator will take care to select this
   * function if it is appropriate for your multiplication.
   */
  template < typename T >
  MultiDimImplementation< T >* multiply2MultiDimArrays(const MultiDimImplementation< T >* t1,
                                                       const MultiDimImplementation< T >* t2);

  /**
   * @brief a specialized function for multiplying two multiDimArrays
   * @ingroup multidim_op_group
   *
   * The function produces a tensorial product of t2 from t1, that is, if t1
   * and t2 are multiDimArrays over variables A,B,C,D and D,B,E respectively,
   * function multiply2MultiDimArrays will return a multiDimArray T defined as
   * T[A,B,C,D,E] = t1[A,B,C,D] * t2[D,B,E] for all A,B,C,D,E.
   *
   * @return function multiply2MultiDimArrays allocates on the heap the new T
   * multiDimArray and returns it
   *
   * @warning In practice, do not use this function but rather operator* on
   * multiDimImplementations. This operator will take care to select this
   * function if it is appropriate for your multiplication.
   */
  template < typename T >
  MultiDimArray< T* >* multiply2MultiDimArrays4Pointers(const MultiDimArray< T* >* t1,
                                                        const MultiDimArray< T* >* t2);

  /**
   * @brief a specialized function for multiplying two multiDimArrays
   * @ingroup multidim_op_group
   *
   * The function produces a tensorial product of t2 from t1, that is, if t1
   * and t2 are multiDimArrays over variables A,B,C,D and D,B,E respectively,
   * function multiply2MultiDimArrays will return a multiDimArray T defined as
   * T[A,B,C,D,E] = t1[A,B,C,D] * t2[D,B,E] for all A,B,C,D,E.
   *
   * @return function multiply2MultiDimArrays allocates on the heap the new T
   * multiDimArray and returns it
   *
   * @warning In practice, do not use this function but rather operator* on
   * multiDimImplementations. This operator will take care to select this
   * function if it is appropriate for your multiplication.
   */
  template < typename T >
  MultiDimImplementation< T* >*
      multiply2MultiDimArrays4Pointers(const MultiDimImplementation< T* >* t1,
                                       const MultiDimImplementation< T* >* t2);

  /**
   * @brief a specialized function for multiplying two multiDimArrays
   * @ingroup multidim_op_group
   *
   * The function produces a tensorial product of t2 from t1, that is, if t1
   * and t2 are multiDimArrays over variables A,B,C,D and D,B,E respectively,
   * function multiply2MultiDimArrays will return a multiDimArray T defined as
   * T[A,B,C,D,E] = t1[A,B,C,D] * t2[D,B,E] for all A,B,C,D,E.
   *
   * @return function multiply2MultiDimArrays allocates on the heap the new T
   * multiDimArray and returns it
   *
   * @warning In practice, do not use this function but rather operator* on
   * multiDimImplementations. This operator will take care to select this
   * function if it is appropriate for your multiplication.
   */
  template < typename T >
  MultiDimFunctionGraph< T >* multiply2MultiDimFunctionGraphs(const MultiDimFunctionGraph< T >* t1,
                                                              const MultiDimFunctionGraph< T >* t2);

  /**
   * @brief a specialized function for multiplying two multiDimArrays
   * @ingroup multidim_op_group
   *
   * The function produces a tensorial product of t2 from t1, that is, if t1
   * and t2 are multiDimArrays over variables A,B,C,D and D,B,E respectively,
   * function multiply2MultiDimArrays will return a multiDimArray T defined as
   * T[A,B,C,D,E] = t1[A,B,C,D] * t2[D,B,E] for all A,B,C,D,E.
   *
   * @return function multiply2MultiDimArrays allocates on the heap the new T
   * multiDimArray and returns it
   *
   * @warning In practice, do not use this function but rather operator* on
   * multiDimImplementations. This operator will take care to select this
   * function if it is appropriate for your multiplication.
   */
  template < typename T >
  MultiDimImplementation< T >*
      multiply2MultiDimFunctionGraphs(const MultiDimImplementation< T >* t1,
                                      const MultiDimImplementation< T >* t2);

  /// @}
  // ==========================================================================
  /// @name Divide MultiDims
  // ==========================================================================
  /// @{

  /**
   * @brief a specialized function for dividing two multiDimArrays
   * @ingroup multidim_op_group
   *
   * The function produces a tensorial division of t1 by t2, that is, if t1 and
   * t2 are multiDimArrays over variables A,B,C,D and D,B,E respectively,
   * function divide2MultiDimArrays will return a multiDimArray T defined as
   * T[A,B,C,D,E] = t1[A,B,C,D] / t2[D,B,E] for all A,B,C,D,E.
   *
   * @return function divide2MultiDimArrays allocates on the heap the new T
   * multiDimArray and returns it
   *
   * @warning In practice, do not use this function but rather operator/ on
   * multiDimImplementations. This operator will take care to select this
   * function if it is appropriate for your division.
   */
  template < typename T >
  MultiDimArray< T >* divide2MultiDimArrays(const MultiDimArray< T >* t1,
                                            const MultiDimArray< T >* t2);

  /**
   * @brief a specialized function for dividing two multiDimArrays
   * @ingroup multidim_op_group
   *
   * The function produces a tensorial division of t1 by t2, that is, if t1 and
   * t2 are multiDimArrays over variables A,B,C,D and D,B,E respectively,
   * function divide2MultiDimArrays will return a multiDimArray T defined as
   * T[A,B,C,D,E] = t1[A,B,C,D] / t2[D,B,E] for all A,B,C,D,E.
   *
   * @return function divide2MultiDimArrays allocates on the heap the new T
   * multiDimArray and returns it
   *
   * @warning In practice, do not use this function but rather operator/ on
   * multiDimImplementations. This operator will take care to select this
   * function if it is appropriate for your division.
   */
  template < typename T >
  MultiDimImplementation< T >* divide2MultiDimArrays(const MultiDimImplementation< T >* t1,
                                                     const MultiDimImplementation< T >* t2);

  /**
   * @brief a specialized function for dividing two multiDimArrays
   * @ingroup multidim_op_group
   *
   * The function produces a tensorial division of t1 by t2, that is, if t1 and
   * t2 are multiDimArrays over variables A,B,C,D and D,B,E respectively,
   * function divide2MultiDimArrays will return a multiDimArray T defined as
   * T[A,B,C,D,E] = t1[A,B,C,D] / t2[D,B,E] for all A,B,C,D,E.
   *
   * @return function divide2MultiDimArrays allocates on the heap the new T
   * multiDimArray and returns it
   *
   * @warning In practice, do not use this function but rather operator/ on
   * multiDimImplementations. This operator will take care to select this
   * function if it is appropriate for your division.
   */
  template < typename T >
  MultiDimArray< T* >* divide2MultiDimArrays4Pointers(const MultiDimArray< T* >* t1,
                                                      const MultiDimArray< T* >* t2);

  /**
   * @brief a specialized function for dividing two multiDimArrays
   * @ingroup multidim_op_group
   *
   * The function produces a tensorial division of t1 by t2, that is, if t1 and
   * t2 are multiDimArrays over variables A,B,C,D and D,B,E respectively,
   * function divide2MultiDimArrays will return a multiDimArray T defined as
   * T[A,B,C,D,E] = t1[A,B,C,D] / t2[D,B,E] for all A,B,C,D,E.
   *
   * @return function divide2MultiDimArrays allocates on the heap the new T
   * multiDimArray and returns it
   *
   * @warning In practice, do not use this function but rather operator/ on
   * multiDimImplementations. This operator will take care to select this
   * function if it is appropriate for your division.
   */
  template < typename T >
  MultiDimImplementation< T* >*
      divide2MultiDimArrays4Pointers(const MultiDimImplementation< T* >* t1,
                                     const MultiDimImplementation< T* >* t2);

  /**
   * @brief a specialized function for dividing two multiDimArrays
   * @ingroup multidim_op_group
   *
   * The function produces a tensorial division of t1 by t2, that is, if t1 and
   * t2 are multiDimArrays over variables A,B,C,D and D,B,E respectively,
   * function divide2MultiDimArrays will return a multiDimArray T defined as
   * T[A,B,C,D,E] = t1[A,B,C,D] / t2[D,B,E] for all A,B,C,D,E.
   *
   * @return function divide2MultiDimArrays allocates on the heap the new T
   * multiDimArray and returns it
   *
   * @warning In practice, do not use this function but rather operator/ on
   * multiDimImplementations. This operator will take care to select this
   * function if it is appropriate for your division.
   */
  template < typename T >
  MultiDimFunctionGraph< T >* divide2MultiDimFunctionGraphs(const MultiDimFunctionGraph< T >* t1,
                                                            const MultiDimFunctionGraph< T >* t2);

  /**
   * @brief a specialized function for dividing two multiDimArrays
   * @ingroup multidim_op_group
   *
   * The function produces a tensorial division of t1 by t2, that is, if t1 and
   * t2 are multiDimArrays over variables A,B,C,D and D,B,E respectively,
   * function divide2MultiDimArrays will return a multiDimArray T defined as
   * T[A,B,C,D,E] = t1[A,B,C,D] / t2[D,B,E] for all A,B,C,D,E.
   *
   * @return function divide2MultiDimArrays allocates on the heap the new T
   * multiDimArray and returns it
   *
   * @warning In practice, do not use this function but rather operator/ on
   * multiDimImplementations. This operator will take care to select this
   * function if it is appropriate for your division.
   */
  template < typename T >
  MultiDimImplementation< T >* divide2MultiDimFunctionGraphs(const MultiDimImplementation< T >* t1,
                                                             const MultiDimImplementation< T >* t2);

  /// @}
  // ==========================================================================
  /// @name Maximise MultiDims
  // ==========================================================================
  /// @{

  /**
   * @brief a specialized function for compare value from two
   * multiDimFunctionGraphs and gets the maximal
   * @ingroup multidim_op_group
   *
   * The function produces a tensorial comparison between t1 and t2, that is,
   * if t1 and t2 are multiDimFunctionGraphs over variables A,B,C,D and D,B,E
   * respectively, function maximise2MultiDimFunctionGraphs will return a
   * multiDimFunctionGraph T defined as T[A,B,C,D,E] = max( t1[A,B,C,D],
   * t2[D,B,E] ) for all A,B,C,D,E.
   *
   * @return function maximise2MultiDimFunctionGraphs allocates on the heap the
   * new T multiDimFunctionGraph and returns it
   */
  template < typename T >
  MultiDimFunctionGraph< T >* maximise2MultiDimFunctionGraphs(const MultiDimFunctionGraph< T >* t1,
                                                              const MultiDimFunctionGraph< T >* t2);

  /**
   * @brief a specialized function for compare value from two
   * multiDimFunctionGraphs and gets the maximal
   * @ingroup multidim_op_group
   *
   * The function produces a tensorial comparison between t1 and t2, that is,
   * if t1 and t2 are multiDimFunctionGraphs over variables A,B,C,D and D,B,E
   * respectively, function maximise2MultiDimFunctionGraphs will return a
   * multiDimFunctionGraph T defined as T[A,B,C,D,E] = max( t1[A,B,C,D],
   * t2[D,B,E] ) for all A,B,C,D,E.
   *
   * @return function maximise2MultiDimFunctionGraphs allocates on the heap the
   * new T multiDimFunctionGraph and returns it
   */
  template < typename T >
  MultiDimImplementation< T >*
      maximise2MultiDimFunctionGraphs(const MultiDimImplementation< T >* t1,
                                      const MultiDimImplementation< T >* t2);

  /// @}
  // ==========================================================================
  /// @name Operators over MultiDim
  // ==========================================================================
  /// @{

  /// The function to be used to add two MultiDimImplementations
  /// @ingroup multidim_op_group
  template < typename T >
  MultiDimImplementation< T >* operator+(const MultiDimImplementation< T >&,
                                         const MultiDimImplementation< T >&);

  /// The function to be used to subtract two MultiDimImplementations
  /// @ingroup multidim_op_group
  template < typename T >
  MultiDimImplementation< T >* operator-(const MultiDimImplementation< T >&,
                                         const MultiDimImplementation< T >&);

  /// The function to be used to multiply two MultiDimImplementations
  /// @ingroup multidim_op_group
  template < typename T >
  MultiDimImplementation< T >* operator*(const MultiDimImplementation< T >&,
                                         const MultiDimImplementation< T >&);

  /// The function to be used to divide two MultiDimImplementations
  /// @ingroup multidim_op_group
  template < typename T >
  MultiDimImplementation< T >* operator/(const MultiDimImplementation< T >&,
                                         const MultiDimImplementation< T >&);

  /// @}

  // ==========================================================================
  // DO NOT FORGET TO REGISTER YOUR BINARY FUNCTIONS AT THE END OF FILE
  // operators4MultiDim_tpl.h
  // ==========================================================================

  /**
   * @brief the function used to register all the operators on
   * multidimImplementations over non-pointers types.
   * @ingroup multidim_op_group
   */
  template < typename GUM_ELEMENT >
  void operators4MultiDimInit();

  /**
   * @brief the function used to register all the operators on
   * multidimImplementations over pointers types.
   * @ingroup multidim_op_group
   */
  template < typename GUM_ELEMENT >
  void pointerOperators4MultiDimInit();

  /**
   * @brief a class used to register operators over non-pointers types
   * @ingroup multidim_op_group
   *
   * This class is of course completely redundant with function
   * operators4MultiDimInit. Its aim is to enable specialization of function
   * operators4MultiDimInit for pointer types: C++ allows partial
   * specialization of templated classes (e.g., create different
   * implementations for C<T> and C<T*>) but it does not allows partial
   * specialization for functions. Hence, by creating a class the primary
   * purpose of which is to run function operators4MultiDimInit, we allow this
   * partial specialization. This is most effective to produce different codes
   * for pointer types and non-pointer types.
   */
  template < typename GUM_ELEMENT >
  struct Operators4MultiDimInitialize {
    void init();
  };

  /**
   * @brief a class used to register operators over pointers types
   * @ingroup multidim_op_group
   *
   * This class is of course completely redundant with function
   * pointerOperators4MultiDimInit. Its aim is to enable different
   * implementations of the operators for multidims depending in whether these
   * multidim contain pointers or not. Actually, C++ allows partial
   * specialization of templated classes (e.g., create different
   * implementations for C<T> and C<T*>) but it does not allows partial
   * specialization for functions. Hence, by creating a class the primary
   * purpose of which is to run function operators4MultiDimInit or
   * pointerOperators4MultiDimInit, we allow this partial specialization to
   * obtain.
   */
  template < typename GUM_ELEMENT >
  struct Operators4MultiDimInitialize< GUM_ELEMENT* > {
    void init();
  };

  /// @}

} /* namespace gum */

// always include the templatized implementations
#include <agrum/base/multidim/utils/operators/operators4MultiDim_tpl.h>

#endif /* GUM_OPERATORS_4_MULTI_DIM_H */
