// -*- c++ -*- (enables emacs c++ mode)
//===========================================================================
//
// Copyright (C) 2000-2008 Yves Renard
//
// This file is a part of GETFEM++
//
// Getfem++  is  free software;  you  can  redistribute  it  and/or modify it
// under  the  terms  of the  GNU  Lesser General Public License as published
// by  the  Free Software Foundation;  either version 2.1 of the License,  or
// (at your option) any later version.
// This program  is  distributed  in  the  hope  that it will be useful,  but
// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
// or  FITNESS  FOR  A PARTICULAR PURPOSE.  See the GNU Lesser General Public
// License for more details.
// You  should  have received a copy of the GNU Lesser General Public License
// along  with  this program;  if not, write to the Free Software Foundation,
// Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301, USA.
//
// As a special exception, you  may use  this file  as it is a part of a free
// software  library  without  restriction.  Specifically,  if   other  files
// instantiate  templates  or  use macros or inline functions from this file,
// or  you compile this  file  and  link  it  with other files  to produce an
// executable, this file  does  not  by itself cause the resulting executable
// to be covered  by the GNU Lesser General Public License.  This   exception
// does not  however  invalidate  any  other  reasons why the executable file
// might be covered by the GNU Lesser General Public License.
//
//===========================================================================

/**@file getfem_integration.h
   @author  Yves Renard <Yves.Renard@insa-lyon.fr>
   @date December 17, 2000.
   @brief Integration methods (exact and approximated) on convexes

   @section im_list List of integration methods for getfem::int_method_descriptor

   - "IM_EXACT_SIMPLEX(n)"             : exact integration on simplexes.
   - "IM_PRODUCT(IM1, IM2)"            : product of two integration methods
   - "IM_EXACT_PARALLELEPIPED(n)"      : exact integration on parallelepipeds
   - "IM_EXACT_PRISM(n)"               : exact integration on prisms
   - "IM_GAUSS1D(K)"                   : Gauss method on the segment, 
                                         order K=1, 3, 5, 7, ..., 99.
   - "IM_GAUSSLOBATTO1D(K)"            : Gauss-Lobatto method on the segment, 
                                         order K=1, 3, 5, 7, ..., 99.
   - "IM_NC(N,K)"                      : Newton-Cotes approximative 
                                         integration on simplexes, order K
   - "IM_NC_PARALLELEPIPED(N,K)"       : product of Newton-Cotes integration 
                                         on parallelepipeds
   - "IM_NC_PRISM(N,K)"                : product of Newton-Cotes integration
                                         on prisms
   - "IM_GAUSS_PARALLELEPIPED(N,K)"    : product of Gauss1D integration
                                         on parallelepipeds (K=1,3,..99)
   - "IM_TRIANGLE(K)"                  : Gauss methods on triangles
                                         (K=1..10,13,17,19)
   - "IM_QUAD(K)"                      : Gauss methods on quadrilaterons
                                         (K=2, 3, 5, 7, 9, 17)
   - "IM_HEXAHEDRON(K)"                : Gauss methods on hexahedrons
                                         (K=5,9,11)
   - "IM_TETRAHEDRON(K)"               : Gauss methods on tetrahedrons
                                         (K=1, 2, 3, 5, 6, or 8)
   - "IM_SIMPLEX4D(3)"                 : Gauss method on a 4-dimensional
                                         simplex.
   - "IM_CUBE4D(K)"                    : Gauss method on a 4-dimensional cube
                                         (K=5,9).
   - "IM_STRUCTURED_COMPOSITE(IM1, K)" : Composite method on a grid with
                                         K divisions
   - "IM_HCT_COMPOSITE(IM1)"           : Composite integration suited to the
                                         HCT composite finite element.
   - "IM_QUAQC1_COMPOSITE(IM1)"        : Composite integration suited to the
                                         QUADC1 composite finite element.

   - "IM_QUASI_POLAR(IM1, IP1)"        : if IM1 is an integration method on a
               square, gives an integration method on a triangle which is
	       close to a polar integration with respect to vertex IP1.
	       if IM1 is an integration method on a tetrahedron, gives an
	       integration method on a tetrahedron which is close to a
	       cylindrical integration with respect to vertex IP1 (does not work very well).
	       if IM1 is an integration method on a prism. Gives an integration
               method on a tetrahedron which is close to a
	       cylindrical integration with respect to vertex IP1.
   - "IM_QUASI_POLAR(IM1, IP1, IP2)"   : IM1 should be an integration method
               on a prism. Gives an integration method on a tetrahedron which
	       is close to a cylindrical integration with respect to IP1-IP2
	       axis.
*/
#ifndef GETFEM_INTEGRATION_H__
#define GETFEM_INTEGRATION_H__

#include "getfem_config.h"
#include "bgeot_convex_ref.h"
#include "bgeot_geometric_trans.h"
#include "bgeot_node_tab.h"

namespace getfem
{
  /** Description of an exact integration of polynomials.
   *  This class is not to be manipulate by itself. Use ppoly_integration and
   *  the functions written to produce the basic descriptions.
   */
  class poly_integration {
    protected :

      bgeot::pconvex_structure cvs;

      mutable std::vector<long_scalar_type> int_monomials;
      mutable std::vector< std::vector<long_scalar_type> > int_face_monomials;

    public :

      /// Dimension of convex of reference.
      dim_type dim(void) const { return cvs->dim(); }
      /// {Structure of convex of reference.  
      bgeot::pconvex_structure structure(void) const { return cvs; }

      virtual long_scalar_type int_monomial(const bgeot::power_index &power)
	const = 0;
      virtual long_scalar_type int_monomial_on_face(const bgeot::power_index
					       &power, short_type f) const = 0;

      /// Evaluate the integral of the polynomial P on the reference element.
      long_scalar_type int_poly(const base_poly &P) const;
      /** Evaluate the integral of the polynomial P on the face f of the
       *    reference element.
       */
      long_scalar_type int_poly_on_face(const base_poly &P,
					short_type f) const;

      virtual ~poly_integration() {}

  };

  typedef const poly_integration *ppoly_integration;

   /** Description of an approximate integration of polynomials of
   *  several variables on a reference element.
   *  This class is not to be manipulate by itself. Use papprox\_integration
   *  and the functions written to produce the basic descriptions.
   */

  class integration_method;  
  typedef boost::intrusive_ptr<const integration_method> pintegration_method;


  class approx_integration {
  protected :
    
    typedef bgeot::node_tab PT_TAB;
    bgeot::pconvex_ref cvr;
    bgeot::pstored_point_tab pint_points;
    std::vector<scalar_type> int_coeffs;
    std::vector<size_type> repartition;
    
    // index 0 : points for volumic integration, index > 0 : points for faces
    std::vector<PT_TAB> pt_to_store; 
    bool valid;
    
  public :
    
    /// Dimension of reference convex.
    dim_type dim(void) const { return cvr->structure()->dim(); }
    size_type nb_points(void) const { return int_coeffs.size(); }
    /// Number of integration nodes on the reference element.
    size_type nb_points_on_convex(void) const { return repartition[0]; }
    /// Number of integration nodes on the face f of the reference element.
    size_type nb_points_on_face(short_type f) const
    { return repartition[f+1] - repartition[f]; }
    size_type ind_first_point_on_face(short_type f) const 
    { return repartition[f]; }
    /// Structure of the reference element.
    bgeot::pconvex_structure structure(void) const
    { return cvr->structure()->basic_structure(); }
    bgeot::pconvex_ref ref_convex(void) const { return cvr; }
    
    const std::vector<size_type> &repart(void) const { return repartition; }
    
    /// Gives an array of integration nodes.
    const bgeot::stored_point_tab  &
    integration_points(void) const
    { return *(pint_points); }
    /// Gives the integration node i on the reference element.
    const base_node &point(size_type i) const
    { return (*pint_points)[i]; }
    /// Gives the integration node i of the face f.
    const base_node &
    point_on_face(short_type f, size_type i) const 
    { return (*pint_points)[repartition[f] + i]; }
    /// Gives an array of the integration coefficients.
    const std::vector<scalar_type> &integration_coefficients(void) const
    { return int_coeffs; }
    /// Gives the integration coefficient corresponding to node i.
    scalar_type coeff(size_type i) const { return int_coeffs[i]; }
    /// Gives the integration coefficient corresponding to node i of face f.
    scalar_type coeff_on_face(short_type f, size_type i) const
    { return int_coeffs[repartition[f] + i]; }
    
    void add_point(const base_node &pt, scalar_type w,
		   short_type f=short_type(-1));
    void add_point_norepeat(const base_node &pt, scalar_type w,
			    short_type f=short_type(-1));
    void add_point_full_symmetric(base_node pt, scalar_type w);
    void add_method_on_face(pintegration_method ppi, short_type f);
    void valid_method(void);
    
    approx_integration(void) : valid(false) { }
    approx_integration(bgeot::pconvex_ref cr)
      : cvr(cr), repartition(cr->structure()->nb_faces()+1),
	pt_to_store(cr->structure()->nb_faces()+1), valid(false)
    { std::fill(repartition.begin(), repartition.end(), 0); } 
    
  };

  typedef const approx_integration *papprox_integration;

  /**
     the list of main integration method types 
  */
  typedef enum { IM_APPROX, IM_EXACT, IM_NONE } integration_method_type;

  /**
     this structure is not intended to be used directly. It is built via
     the int_method_descriptor() function
  */
  class integration_method : virtual public dal::static_stored_object {    
    union {
      ppoly_integration ppi; /* for exact integrations */
      papprox_integration pai; /* for approximate integrations (i.e. cubatures) */
    } method;
    integration_method_type im_type;

    void remove(void) {
      switch (type()) {
      case IM_EXACT: if (method.ppi) delete method.ppi; break;
      case IM_APPROX: if (method.pai) delete method.pai; break;
      case IM_NONE: break;
      }
    }

  public:
    integration_method_type type(void) const { return im_type; }
    papprox_integration approx_method(void) const { return method.pai; }
    ppoly_integration exact_method(void) const { return method.ppi; }

    void set_approx_method(papprox_integration paii)
    { remove(); method.pai = paii; im_type = IM_APPROX; }
    void set_exact_method(ppoly_integration ppii)
    { remove(); method.ppi = ppii; im_type = IM_EXACT; }

    const bgeot::stored_point_tab &integration_points(void) const { 
      if (type() == IM_EXACT) {
	size_type n = method.ppi->structure()->dim();
	std::vector<base_node> spt(1); spt[0] = base_node(n);
	return (*store_point_tab(spt));
      }
      else if (type() == IM_APPROX)
	return method.pai->integration_points();
      else GMM_ASSERT1(false, "IM_NONE has no points");
    }

    bgeot::pconvex_structure structure(void) const { 
      switch (type()) {
      case IM_EXACT: return method.ppi->structure();
      case IM_APPROX: return method.pai->structure();
      case IM_NONE: GMM_ASSERT1(false, "IM_NONE has no structure");
      default: GMM_ASSERT3(false, "");
      }
      return 0;
    }

    integration_method(ppoly_integration p)
    { method.ppi = p; im_type = IM_EXACT; }

    integration_method(papprox_integration p)
    { method.pai = p; im_type = IM_APPROX; }

    integration_method(void) { im_type = IM_NONE; method.pai = 0; }
    ~integration_method(void) { remove(); }
  };


  /** Get an integration method from its name .
      @see @ref im_list 
      @param name the integration method name, for example "IM_TRIANGLE(6)"
      @param throw_if_not_found choose if an exception must be thrown
      when the integration method does not exist (if no exception, a
      null pointer is returned).
  */
  pintegration_method int_method_descriptor(std::string name,
					    bool throw_if_not_found = true);

  /** Get the string name of an integration method .
   */
  std::string name_of_int_method(pintegration_method p);
  
  /**
     return an exact integration method for convex type handled by pgt.
     If pgt is not linear, classical_exact_im will fail.
  */
  pintegration_method classical_exact_im(bgeot::pgeometric_trans pgt);
  /**
     try to find an approximate integration method for the geometric
     transformation pgt which is able to integrate exactly polynomials
     of degree <= "degree". It may return a higher order integration
     method if no method match the exact degree.
  */
  pintegration_method classical_approx_im(bgeot::pgeometric_trans pgt, dim_type degree);

  /// return IM_EXACT_SIMPLEX(n)
  pintegration_method exact_simplex_im(size_type n);
  /// return IM_EXACT_PARALLELEPIPED(n)
  pintegration_method exact_parallelepiped_im(size_type n);
  /// return IM_EXACT_PRISM(n)
  pintegration_method exact_prism_im(size_type n);
  /// use classical_exact_im instead.
  pintegration_method exact_classical_im(bgeot::pgeometric_trans pgt) IS_DEPRECATED;
  /// return IM_NONE
  pintegration_method im_none(void);

  class mesh_precomposite;
  class mesh_im;
  papprox_integration composite_approx_int_method(const mesh_precomposite &mp, 
						  const mesh_im &mf,
						  bgeot::pconvex_ref cr);

  /* try to integrate all monomials up to order 'order' and return the 
     max. error */
  scalar_type test_integration_error(papprox_integration pim, dim_type order);

  papprox_integration get_approx_im_or_fail(pintegration_method pim);

}  /* end of namespace getfem.                                            */


#endif /* BGEOT_INTEGRATION_H__                                           */
