// Copyright (c) 2010-2025, 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.


#ifndef MFEM_HYPERBOLIC
#define MFEM_HYPERBOLIC

#include "nonlinearform.hpp"

namespace mfem
{

// This file contains general hyperbolic conservation element/face form
// integrators. HyperbolicFormIntegrator and NumericalFlux are defined.
//
// HyperbolicFormIntegrator is a NonlinearFormIntegrator that implements
// element weak divergence and interface flux
//
//     ∫_K F(u):∇v,   -∫_f F̂(u)⋅n[v]
//
// Here, K is an element, f is a face, n normal and [⋅] is jump. This form
// integrator is coupled with NumericalFlux that implements the numerical flux
// F̂. For NumericalFlux, the Rusanov flux, also known as local Lax-Friedrichs
// flux, or component-wise upwinded flux are provided.
//
// To implement a specific hyperbolic conservation laws, users can create
// derived classes from FluxFunction with overloaded ComputeFlux. One can
// optionally overload ComputeFluxDotN to avoid creating dense matrix when
// computing normal flux. Several example equations are also defined including:
// advection, Burgers', shallow water, and Euler equations. Users can control
// the quadrature rule by either providing the integration rule, or integration
// order offset. Integration will use 2*p + IntOrderOffset order quadrature
// rule.
//
// At each call of HyperbolicFormIntegrator::AssembleElementVector
// HyperbolicFormIntegrator::AssembleFaceVector, the maximum characteristic
// speed will be updated. This will not be reinitialized automatically. To
// reinitialize, use HyperbolicFormIntegrator::ResetMaxCharSpeed. See, ex18.hpp.
//
// Note: To avoid communication overhead, we update the maximum characteristic
// speed within each MPI process only. Use the appropriate MPI routine to gather
// the information.

/**
 * @brief Abstract class for hyperbolic flux for a system of hyperbolic
 * conservation laws
 *
 */
class FluxFunction
{
public:
   const int num_equations;
   const int dim;

   FluxFunction(const int num_equations, const int dim)
      : num_equations(num_equations), dim(dim) { }

   virtual ~FluxFunction() {}

   /**
    * @brief Compute flux F(u, x). Must be implemented in a derived class.
    *
    * Used in HyperbolicFormIntegrator::AssembleElementVector() for evaluation
    * of (F(u), ∇v) and in the default implementation of ComputeFluxDotN()
    * for evaluation of F(u)⋅n.
    * @param[in] state state at the current integration point (num_equations)
    * @param[in] Tr element transformation
    * @param[out] flux flux from the given element at the current
    * integration point (num_equations, dim)
    * @return real_t maximum characteristic speed |dF(u,x)/du|
    *
    * @note One can put assertion in here to detect non-physical solution
    */
   virtual real_t ComputeFlux(const Vector &state, ElementTransformation &Tr,
                              DenseMatrix &flux) const = 0;
   /**
    * @brief Compute normal flux F(u, x)⋅n. Optionally overloaded in a derived
    * class to avoid creating a full dense matrix for flux.
    *
    * Used in NumericalFlux for evaluation of the normal flux on a face.
    * @param[in] state state at the current integration point (num_equations)
    * @param[in] normal normal vector, see mfem::CalcOrtho() (dim)
    * @param[in] Tr face transformation
    * @param[out] fluxDotN normal flux from the given element at the current
    * integration point (num_equations)
    * @return real_t maximum (normal) characteristic speed |dF(u,x)/du⋅n|
    */
   virtual real_t ComputeFluxDotN(const Vector &state, const Vector &normal,
                                  FaceElementTransformations &Tr,
                                  Vector &fluxDotN) const;

   /**
    * @brief Compute average flux over the given interval of states.
    * Optionally overloaded in a derived class.
    *
    * The average flux is defined as F̄(u1,u2) = ∫ F(u) du / (u2 - u1) for
    * u ∈ [u1,u2], where u1 is the first state (@a state1) and the u2 the
    * second state (@a state2), while F(u) is the flux as defined in
    * ComputeFlux().
    *
    * Used in the default implementation of ComputeAvgFluxDotN().
    * @param[in] state1 state of the beginning of the interval (num_equations)
    * @param[in] state2 state of the end of the interval (num_equations)
    * @param[in] Tr element transformation
    * @param[out] flux_ average flux from the given element at the current
    * integration point (num_equations, dim)
    * @return real_t maximum characteristic speed |dF(u,x)/du| over
    * the interval [u1,u2]
    */
   virtual real_t ComputeAvgFlux(const Vector &state1, const Vector &state2,
                                 ElementTransformation &Tr,
                                 DenseMatrix &flux_) const
   { MFEM_ABORT("Not Implemented."); }

   /**
    * @brief Compute average normal flux over the given interval of states.
    * Optionally overloaded in a derived class.
    *
    * The average normal flux is defined as F̄(u1,u2)n = ∫ F(u)n du / (u2 - u1)
    * for u ∈ [u1,u2], where u1 is the first state (@a state1) and the u2 the
    * second state (@a state2), while n is the normal and F(u) is the flux as
    * defined in ComputeFlux().
    *
    * Used in NumericalFlux::Average() and NumericalFlux::AverageGrad() for
    * evaluation of the average normal flux on a face.
    * @param[in] state1 state of the beginning of the interval (num_equations)
    * @param[in] state2 state of the end of the interval (num_equations)
    * @param[in] normal normal vector, see mfem::CalcOrtho() (dim)
    * @param[in] Tr face transformation
    * @param[out] fluxDotN average normal flux from the given element at the
    * current integration point (num_equations)
    * @return real_t maximum (normal) characteristic speed |dF(u,x)/du⋅n|
    * over the interval [u1,u2]
    */
   virtual real_t ComputeAvgFluxDotN(const Vector &state1, const Vector &state2,
                                     const Vector &normal,
                                     FaceElementTransformations &Tr,
                                     Vector &fluxDotN) const;

   /**
    * @brief Compute flux Jacobian J(u, x). Optionally overloaded in a derived
    * class when Jacobian is necessary (e.g. Newton iteration, flux limiter)
    *
    * Used in HyperbolicFormIntegrator::AssembleElementGrad() for evaluation of
    * Jacobian of the flux in an element and in the default implementation of
    * ComputeFluxJacobianDotN().
    * @param[in] state state at the current integration point (num_equations)
    * @param[in] Tr element transformation
    * @param[out] J_ flux Jacobian, $ J(i,j,d) = dF_{id} / du_j $
    */
   virtual void ComputeFluxJacobian(const Vector &state,
                                    ElementTransformation &Tr,
                                    DenseTensor &J_) const
   { MFEM_ABORT("Not Implemented."); }

   /**
    * @brief Compute normal flux Jacobian J(u, x)⋅n. Optionally overloaded in
    * a derived class to avoid creating a full dense tensor for Jacobian.
    *
    * Used in NumericalFlux for evaluation of Jacobian of the normal flux on
    * a face.
    * @param[in] state state at the current integration point (num_equations)
    * @param[in] normal normal vector, see mfem::CalcOrtho() (dim)
    * @param[in] Tr element transformation
    * @param[out] JDotN normal flux Jacobian, $ JDotN(i,j) = d(F_{id} n_d) / du_j $
    */
   virtual void ComputeFluxJacobianDotN(const Vector &state,
                                        const Vector &normal,
                                        ElementTransformation &Tr,
                                        DenseMatrix &JDotN) const;

private:
#ifndef MFEM_THREAD_SAFE
   mutable DenseMatrix flux;
   mutable DenseTensor J;
#endif
};


/**
 * @brief Abstract class for numerical flux for a system of hyperbolic
 * conservation laws on a face with states, fluxes and characteristic speed
 *
 */
class NumericalFlux
{
public:
   /**
    * @brief Constructor for a flux function
    * @param fluxFunction flux function F(u,x)
    */
   NumericalFlux(const FluxFunction &fluxFunction)
      : fluxFunction(fluxFunction) { }

   /**
    * @brief Evaluates normal numerical flux for the given states and normal.
    * Must be implemented in a derived class.
    *
    * Used in HyperbolicFormIntegrator::AssembleFaceVector() for evaluation of
    * <F̂(u⁻,u⁺,x) n, [v]> term at the face.
    * @param[in] state1 state value at a point from the first element
    * (num_equations)
    * @param[in] state2 state value at a point from the second element
    * (num_equations)
    * @param[in] nor scaled normal vector, see mfem::CalcOrtho() (dim)
    * @param[in] Tr face transformation
    * @param[out] flux numerical flux (num_equations)
    * @return real_t maximum characteristic speed |dF(u,x)/du⋅n|
    */
   virtual real_t Eval(const Vector &state1, const Vector &state2,
                       const Vector &nor, FaceElementTransformations &Tr,
                       Vector &flux) const = 0;

   /**
    * @brief Evaluates Jacobian of the normal numerical flux for the given
    * states and normal. Optionally overloaded in a derived class.
    *
    * Used in HyperbolicFormIntegrator::AssembleFaceGrad() for Jacobian
    * of the term <F̂(u⁻,u⁺,x) n, [v]> at the face.
    * @param[in] side indicates gradient w.r.t. the first (side = 1)
    * or second (side = 2) state
    * @param[in] state1 state value of the beginning of the interval
    * (num_equations)
    * @param[in] state2 state value of the end of the interval
    * (num_equations)
    * @param[in] nor scaled normal vector, see mfem::CalcOrtho() (dim)
    * @param[in] Tr face transformation
    * @param[out] grad Jacobian of normal numerical flux (num_equations, dim)
    */
   virtual void Grad(int side, const Vector &state1, const Vector &state2,
                     const Vector &nor, FaceElementTransformations &Tr,
                     DenseMatrix &grad) const
   { MFEM_ABORT("Not implemented."); }

   /**
    * @brief Evaluates average normal numerical flux over the interval between
    * the given end states in the second argument and for the given normal.
    * Optionally overloaded in a derived class.
    *
    * Presently, not used. Reserved for future use.
    * @param[in] state1 state value of the beginning of the interval
    * (num_equations)
    * @param[in] state2 state value of the end of the interval
    * (num_equations)
    * @param[in] nor scaled normal vector, see mfem::CalcOrtho() (dim)
    * @param[in] Tr face transformation
    * @param[out] flux numerical flux (num_equations)
    * @return real_t maximum characteristic speed |dF(u,x)/du⋅n|
    */
   virtual real_t Average(const Vector &state1, const Vector &state2,
                          const Vector &nor, FaceElementTransformations &Tr,
                          Vector &flux) const
   { MFEM_ABORT("Not implemented."); }

   /**
    * @brief Evaluates Jacobian of the average normal numerical flux over the
    * interval between the given end states in the second argument and for the
    * given normal. Optionally overloaded in a derived class.
    *
    * Presently, not used. Reserved for future use.
    * @param[in] side indicates gradient w.r.t. the first (side = 1)
    * or second (side = 2) state
    * @param[in] state1 state value of the beginning of the interval
    * (num_equations)
    * @param[in] state2 state value of the end of the interval
    * (num_equations)
    * @param[in] nor scaled normal vector, see mfem::CalcOrtho() (dim)
    * @param[in] Tr face transformation
    * @param[out] grad Jacobian of the average normal numerical flux
    * (num_equations, dim)
    */
   virtual void AverageGrad(int side, const Vector &state1, const Vector &state2,
                            const Vector &nor, FaceElementTransformations &Tr,
                            DenseMatrix &grad) const
   { MFEM_ABORT("Not implemented."); }

   virtual ~NumericalFlux() = default;

   /// @brief Get flux function F
   /// @return constant reference to the flux function.
   const FluxFunction &GetFluxFunction() const { return fluxFunction; }

protected:
   const FluxFunction &fluxFunction;
};

/// @deprecated Use NumericalFlux instead.
MFEM_DEPRECATED typedef NumericalFlux RiemannSolver;

/**
 * @brief Abstract hyperbolic form integrator, assembling (F(u, x), ∇v) and
 * <F̂(u⁻,u⁺,x) n, [v]> terms for scalar finite elements.
 *
 * This form integrator is coupled with a NumericalFlux that implements the
 * numerical flux F̂ at the faces. The flux F is obtained from the FluxFunction
 * assigned to the aforementioned NumericalFlux.
 */
class HyperbolicFormIntegrator : public NonlinearFormIntegrator
{
private:
   // The maximum characteristic speed, updated during element/face vector assembly
   real_t max_char_speed;
   const NumericalFlux &numFlux;   // Numerical flux that maps F(u±,x) to F̂
   const FluxFunction &fluxFunction;
   const int IntOrderOffset; // integration order offset, 2*p + IntOrderOffset.
   const real_t sign;
#ifndef MFEM_THREAD_SAFE
   // Local storage for element integration
   Vector shape;              // shape function value at an integration point
   Vector state;              // state value at an integration point
   DenseMatrix flux;          // flux value at an integration point
   DenseTensor J;             // Jacobian matrix at an integration point
   DenseMatrix dshape;  // derivative of shape function at an integration point

   Vector shape1;  // shape function value at an integration point - first elem
   Vector shape2;  // shape function value at an integration point - second elem
   Vector state1;  // state value at an integration point - first elem
   Vector state2;  // state value at an integration point - second elem
   Vector nor;     // normal vector, see mfem::CalcOrtho()
   Vector fluxN;   // F̂(u±,x) n
   DenseMatrix JDotN;   // Ĵ(u±,x) n
#endif

public:
   const int num_equations;  // the number of equations
   /**
    * @brief Construct a new Hyperbolic Form Integrator object
    *
    * @param[in] numFlux numerical flux
    * @param[in] IntOrderOffset integration order offset
    * @param[in] sign sign of the convection term
    */
   HyperbolicFormIntegrator(
      const NumericalFlux &numFlux,
      const int IntOrderOffset = 0,
      const real_t sign = 1.);

   /**
    * @brief Reset the Max Char Speed 0
    *
    */
   void ResetMaxCharSpeed()
   {
      max_char_speed = 0.0;
   }

   real_t GetMaxCharSpeed()
   {
      return max_char_speed;
   }

   const FluxFunction &GetFluxFunction() { return fluxFunction; }

   /**
    * @brief Implements (F(u), ∇v) with abstract F computed by
    * FluxFunction::ComputeFlux()
    *
    * @param[in] el local finite element
    * @param[in] Tr element transformation
    * @param[in] elfun local coefficient of basis
    * @param[out] elvect evaluated dual vector
    */
   void AssembleElementVector(const FiniteElement &el,
                              ElementTransformation &Tr,
                              const Vector &elfun, Vector &elvect) override;

   /**
    * @brief Implements (J(u), ∇v) with abstract J computed by
    * FluxFunction::ComputeFluxJacobian()
    *
    * @param[in] el local finite element
    * @param[in] Tr element transformation
    * @param[in] elfun local coefficient of basis
    * @param[out] grad evaluated Jacobian
    */
   void AssembleElementGrad(const FiniteElement &el,
                            ElementTransformation &Tr,
                            const Vector &elfun, DenseMatrix &grad) override;

   /**
    * @brief Implements <-F̂(u⁻,u⁺,x) n, [v]> with abstract F̂ computed by
    * NumericalFlux::Eval() of the numerical flux object
    *
    * @param[in] el1 finite element of the first element
    * @param[in] el2 finite element of the second element
    * @param[in] Tr face element transformations
    * @param[in] elfun local coefficient of basis from both elements
    * @param[out] elvect evaluated dual vector <-F̂(u⁻,u⁺,x) n, [v]>
    */
   void AssembleFaceVector(const FiniteElement &el1,
                           const FiniteElement &el2,
                           FaceElementTransformations &Tr,
                           const Vector &elfun, Vector &elvect) override;

   /**
    * @brief Implements <-Ĵ(u⁻,u⁺,x) n, [v]> with abstract Ĵ computed by
    * NumericalFlux::Grad() of the numerical flux object
    *
    * @param[in] el1 finite element of the first element
    * @param[in] el2 finite element of the second element
    * @param[in] Tr face element transformations
    * @param[in] elfun local coefficient of basis from both elements
    * @param[out] elmat evaluated Jacobian matrix <-Ĵ(u⁻,u⁺,x) n, [v]>
    */
   void AssembleFaceGrad(const FiniteElement &el1,
                         const FiniteElement &el2,
                         FaceElementTransformations &Tr,
                         const Vector &elfun, DenseMatrix &elmat) override;
};


/**
 * @brief Rusanov flux, also known as local Lax-Friedrichs,
 *    F̂ n = ½(F(u⁺,x)n + F(u⁻,x)n) - ½λ(u⁺ - u⁻)
 * where λ is the maximum characteristic speed.
 * @note The implementation assumes monotonous |dF(u,x)/du⋅n| in u, so the
 * maximum characteristic speed λ for any interval [u⁻, u⁺] is given by
 * max(|dF(u⁺,x)/du⁺⋅n|, |dF(u⁻,x)/du⁻⋅n|).
 */
class RusanovFlux : public NumericalFlux
{
public:
   /**
    * @brief Constructor for a flux function
    * @param fluxFunction flux function F(u,x)
    */
   RusanovFlux(const FluxFunction &fluxFunction);

   /**
    * @brief  Normal numerical flux F̂(u⁻,u⁺,x) n
    * @note Systems of equations are treated component-wise
    *
    * @param[in] state1 state value (u⁻) at a point from the first element
    * (num_equations)
    * @param[in] state2 state value (u⁺) at a point from the second element
    * (num_equations)
    * @param[in] nor normal vector (not a unit vector) (dim)
    * @param[in] Tr face element transformation
    * @param[out] flux F̂ n = ½(F(u⁺,x)n + F(u⁻,x)n) - ½λ(u⁺ - u⁻)
    * @return max(|dF(u⁺,x)/du⁺⋅n|, |dF(u⁻,x)/du⁻⋅n|)
    */
   real_t Eval(const Vector &state1, const Vector &state2,
               const Vector &nor, FaceElementTransformations &Tr,
               Vector &flux) const override;

   /**
    * @brief  Jacobian of normal numerical flux F̂(u⁻,u⁺,x) n
    * @note The Jacobian of flux J n is required to be implemented in
    * FluxFunction::ComputeFluxJacobianDotN()
    *
    * @param[in] side gradient w.r.t the first (u⁻) or second argument (u⁺)
    * @param[in] state1 state value (u⁻) of the beginning of the interval
    * (num_equations)
    * @param[in] state2 state value (u⁺) of the end of the interval
    * (num_equations)
    * @param[in] nor normal vector (not a unit vector) (dim)
    * @param[in] Tr face element transformation
    * @param[out] grad Jacobian of F(u⁻,u⁺,x) n
    * side = 1:
    *    ½J(u⁻,x)n + ½λ
    * side = 2:
    *    ½J(u⁺,x)n - ½λ
    */
   void Grad(int side, const Vector &state1, const Vector &state2,
             const Vector &nor, FaceElementTransformations &Tr,
             DenseMatrix &grad) const override;

   /**
    * @brief  Average normal numerical flux over the interval [u⁻, u⁺] in the
    * second argument of the flux F̂(u⁻,u,x) n
    * @note The average normal flux F̄ n is required to be implemented in
    * FluxFunction::ComputeAvgFluxDotN()
    * @note Systems of equations are treated component-wise
    *
    * @param[in] state1 state value (u⁻) of the beginning of the interval
    * (num_equations)
    * @param[in] state2 state value (u⁺) of the end of the interval
    * (num_equations)
    * @param[in] nor normal vector (not a unit vector) (dim)
    * @param[in] Tr face element transformation
    * @param[out] flux ½(F̄(u⁻,u⁺,x)n + F(u⁻,x)n) - ¼λ(u⁺ - u⁻)
    * @return max(|dF(u⁺,x)/du⁺⋅n|, |dF(u⁻,x)/du⁻⋅n|)
    */
   real_t Average(const Vector &state1, const Vector &state2,
                  const Vector &nor, FaceElementTransformations &Tr,
                  Vector &flux) const override;

   /**
    * @brief  Jacobian of average normal numerical flux over the interval
    * [u⁻, u⁺] in the second argument of the flux F̂(u⁻,u,x) n
    * @note The average normal flux F̄ n is required to be implemented in
    * FluxFunction::ComputeAvgFluxDotN() and the Jacobian of flux J n in
    * FluxFunction::ComputeFluxJacobianDotN()
    * @note Only the diagonal terms of the J n are considered, i.e., systems
    * are treated as a set of independent equations
    *
    * @param[in] side gradient w.r.t the first (u⁻) or second argument (u⁺)
    * @param[in] state1 state value (u⁻) of the beginning of the interval
    * (num_equations)
    * @param[in] state2 state value (u⁺) of the end of the interval
    * (num_equations)
    * @param[in] nor normal vector (not a unit vector) (dim)
    * @param[in] Tr face element transformation
    * @param[out] grad Jacobian of F̄(u⁻,u⁺,x) n
    * side = 1:
    *    ½(F̄(u⁻,u⁺,x)n - F(u⁻,x)n) / (u⁺ - u⁻) - ½J(u⁻,x)n + ¼λ
    * side = 2:
    *    ½(F(u⁺,x)n - F̄(u⁻,u⁺,x)n) / (u⁺ - u⁻) - ¼λ
    */
   void AverageGrad(int side, const Vector &state1, const Vector &state2,
                    const Vector &nor, FaceElementTransformations &Tr,
                    DenseMatrix &grad) const override;

protected:
#ifndef MFEM_THREAD_SAFE
   mutable Vector fluxN1, fluxN2;
   mutable DenseMatrix JDotN;
#endif
};

/**
 * @brief Component-wise upwinded flux
 *
 * Upwinded flux for scalar equations, a special case of Godunov or
 * Engquist-Osher flux, is defined as follows:
 *    F̂ n = F(u⁺)n    for dF(u)/du < 0 on [u⁻,u⁺]
 *    F̂ n = F(u⁻)n    for dF(u)/du > 0 on [u⁻,u⁺]
 * @note This construction assumes monotonous F(u,x) in u
 * @note Systems of equations are treated component-wise
 */
class ComponentwiseUpwindFlux : public NumericalFlux
{
public:
   /**
    * @brief Constructor for a flux function
    * @param fluxFunction flux function F(u,x)
    */
   ComponentwiseUpwindFlux(const FluxFunction &fluxFunction);

   /**
    * @brief  Normal numerical flux F̂(u⁻,u⁺,x) n
    *
    * @param[in] state1 state value (u⁻) at a point from the first element
    * (num_equations)
    * @param[in] state2 state value (u⁺) at a point from the second element
    * (num_equations)
    * @param[in] nor normal vector (not a unit vector) (dim)
    * @param[in] Tr face element transformation
    * @param[out] flux F̂ n = min(F(u⁻,x)n, F(u⁺,x)n)    for u⁻ ≤ u⁺
    *               or F̂ n = max(F(u⁻,x)n, F(u⁺,x)n)    for u⁻ > u⁺
    * @return max(|dF(u⁺,x)/du⁺⋅n|, |dF(u⁻,x)/du⁻⋅n|)
    */
   real_t Eval(const Vector &state1, const Vector &state2,
               const Vector &nor, FaceElementTransformations &Tr,
               Vector &flux) const override;

   /**
    * @brief  Jacobian of normal numerical flux F̂(u⁻,u⁺,x) n
    * @note The Jacobian of flux J n is required to be implemented in
    * FluxFunction::ComputeFluxJacobianDotN()
    *
    * @param[in] side gradient w.r.t the first (u⁻) or second argument (u⁺)
    * @param[in] state1 state value (u⁻) of the beginning of the interval
    * (num_equations)
    * @param[in] state2 state value (u⁺) of the end of the interval
    * (num_equations)
    * @param[in] nor normal vector (not a unit vector) (dim)
    * @param[in] Tr face element transformation
    * @param[out] grad Jacobian of F(u⁻,u⁺,x) n
    * side = 1:
    *    max(J(u⁻,x)n, 0)
    * side = 2:
    *    min(J(u⁺,x)n, 0)
    */
   void Grad(int side, const Vector &state1, const Vector &state2,
             const Vector &nor, FaceElementTransformations &Tr,
             DenseMatrix &grad) const override;

   /**
    * @brief  Average normal numerical flux over the interval [u⁻, u⁺] in the
    * second argument of the flux F̂(u⁻,u,x) n
    * @note The average normal flux F̄ n is required to be implemented in
    * FluxFunction::ComputeAvgFluxDotN()
    *
    * @param[in] state1 state value (u⁻) of the beginning of the interval
    * (num_equations)
    * @param[in] state2 state value (u⁺) of the end of the interval
    * (num_equations)
    * @param[in] nor normal vector (not a unit vector) (dim)
    * @param[in] Tr face element transformation
    * @param[out] flux F̂ n = min(F(u⁻)n, F̄(u⁺,x)n)    for u⁻ ≤ u⁺
    *               or F̂ n = max(F(u⁻)n, F̄(u⁺,x)n)    for u⁻ > u⁺
    * @return max(|dF(u⁺,x)/du⁺⋅n|, |dF(u⁻,x)/du⁻⋅n|)
    */
   real_t Average(const Vector &state1, const Vector &state2,
                  const Vector &nor, FaceElementTransformations &Tr,
                  Vector &flux) const override;

   /**
    * @brief  Jacobian of average normal numerical flux over the interval
    * [u⁻, u⁺] in the second argument of the flux F̂(u⁻,u,x) n
    * @note The average normal flux F̄ n is required to be implemented in
    * FluxFunction::ComputeAvgFluxDotN() and the Jacobian of flux J n in
    * FluxFunction::ComputeFluxJacobianDotN()
    *
    * @param[in] side gradient w.r.t the first (u⁻) or second argument (u⁺)
    * @param[in] state1 state value (u⁻) of the beginning of the interval
    * (num_equations)
    * @param[in] state2 state value (u⁺) of the end of the interval
    * (num_equations)
    * @param[in] nor normal vector (not a unit vector) (dim)
    * @param[in] Tr face element transformation
    * @param[out] grad Jacobian of F̄(u⁻,u⁺,x) n
    * side = 1:
    *    (F(u⁺) - F̄(u⁻,u⁺))n / (u⁺ - u⁻)      when negative
    *    J(u⁻,x) n                            otherwise
    * side = 2:
    *    min((F(u⁺) - F̄(u⁻,u⁺))n / (u⁺ - u⁻), 0)
    */
   void AverageGrad(int side, const Vector &state1, const Vector &state2,
                    const Vector &nor, FaceElementTransformations &Tr,
                    DenseMatrix &grad) const override;

protected:
#ifndef MFEM_THREAD_SAFE
   mutable Vector fluxN1, fluxN2;
   mutable DenseMatrix JDotN;
#endif
};

/// Advection flux
class AdvectionFlux : public FluxFunction
{
private:
   VectorCoefficient &b;  // velocity coefficient
#ifndef MFEM_THREAD_SAFE
   mutable Vector bval;           // velocity value storage
#endif

public:

   /**
    * @brief Construct AdvectionFlux FluxFunction with given velocity
    *
    * @param b velocity coefficient, possibly depends on space
    */
   AdvectionFlux(VectorCoefficient &b)
      : FluxFunction(1, b.GetVDim()), b(b)
   {
#ifndef MFEM_THREAD_SAFE
      bval.SetSize(b.GetVDim());
#endif
   }

   /**
    * @brief Compute F(u)
    *
    * @param state state (u) at current integration point
    * @param Tr current element transformation with the integration point
    * @param flux F(u) = ubᵀ
    * @return real_t maximum characteristic speed, |b|
    */
   real_t ComputeFlux(const Vector &state, ElementTransformation &Tr,
                      DenseMatrix &flux) const override;

   /**
    * @brief Compute F(u) n
    *
    * @param state state (u) at current integration point
    * @param normal normal vector, usually not a unit vector
    * @param Tr current element transformation with the integration point
    * @param fluxDotN F(u) n = u (bᵀn)
    * @return real_t maximum characteristic speed, |b|
    */
   real_t ComputeFluxDotN(const Vector &state,
                          const Vector &normal, FaceElementTransformations &Tr,
                          Vector &fluxDotN) const override;

   /**
    * @brief Compute average flux F̄(u)
    *
    * @param state1 state value (u⁻) of the beginning of the interval
    * @param state2 state value (u⁺) of the end of the interval
    * @param Tr current element transformation with the integration point
    * @param flux F̄(u) = (u⁻+u⁺)/2*bᵀ
    * @return real_t maximum characteristic speed, |b|
    */
   real_t ComputeAvgFlux(const Vector &state1, const Vector &state2,
                         ElementTransformation &Tr, DenseMatrix &flux) const override;

   /**
    * @brief Compute average flux F̄(u) n
    *
    * @param state1 state value (u⁻) of the beginning of the interval
    * @param state2 state value (u⁺) of the end of the interval
    * @param normal normal vector, usually not a unit vector
    * @param Tr current element transformation with the integration point
    * @param fluxDotN F̄(u) n = (u⁻+u⁺)/2*(bᵀn)
    * @return real_t maximum characteristic speed, |b|
    */
   real_t ComputeAvgFluxDotN(const Vector &state1, const Vector &state2,
                             const Vector &normal, FaceElementTransformations &Tr,
                             Vector &fluxDotN) const override;

   /**
    * @brief Compute J(u)
    *
    * @param state state (u) at current integration point
    * @param Tr current element transformation with the integration point
    * @param J J(u) = diag(b)
    */
   void ComputeFluxJacobian(const Vector &state,
                            ElementTransformation &Tr,
                            DenseTensor &J) const override;

   /**
    * @brief Compute J(u) n
    *
    * @param state state (u) at current integration point
    * @param normal normal vector, usually not a unit vector
    * @param Tr current element transformation with the integration point
    * @param JDotN J(u) n = bᵀn
    */
   void ComputeFluxJacobianDotN(const Vector &state,
                                const Vector &normal,
                                ElementTransformation &Tr,
                                DenseMatrix &JDotN) const override;
};

/// Burgers flux
class BurgersFlux : public FluxFunction
{
public:
   /**
    * @brief Construct BurgersFlux FluxFunction with given spatial dimension
    *
    * @param dim spatial dimension
    */
   BurgersFlux(const int dim)
      : FluxFunction(1, dim) {}

   /**
    * @brief Compute F(u)
    *
    * @param state state (u) at current integration point
    * @param Tr current element transformation with the integration point
    * @param flux F(u) = ½u²*1ᵀ where 1 is (dim) vector
    * @return real_t maximum characteristic speed, |u|
    */
   real_t ComputeFlux(const Vector &state, ElementTransformation &Tr,
                      DenseMatrix &flux) const override;

   /**
    * @brief Compute F(u) n
    *
    * @param state state (u) at current integration point
    * @param normal normal vector, usually not a unit vector
    * @param Tr current element transformation with the integration point
    * @param fluxDotN F(u) n = ½u²*(1ᵀn) where 1 is (dim) vector
    * @return real_t maximum characteristic speed, |u|
    */
   real_t ComputeFluxDotN(const Vector &state,
                          const Vector &normal,
                          FaceElementTransformations &Tr,
                          Vector &fluxDotN) const override;

   /**
    * @brief Compute average flux F̄(u)
    *
    * @param state1 state value (u⁻) of the beginning of the interval
    * @param state2 state value (u⁺) of the end of the interval
    * @param Tr current element transformation with the integration point
    * @param flux F̄(u) = (u⁻²+u⁻*u⁺+u⁺²)/6*1ᵀ where 1 is (dim) vector
    * @return real_t maximum characteristic speed, |u|
    */
   real_t ComputeAvgFlux(const Vector &state1,
                         const Vector &state2,
                         ElementTransformation &Tr,
                         DenseMatrix &flux) const override;

   /**
    * @brief Compute average flux F̄(u) n
    *
    * @param state1 state value (u⁻) of the beginning of the interval
    * @param state2 state value (u⁺) of the end of the interval
    * @param normal normal vector, usually not a unit vector
    * @param Tr current element transformation with the integration point
    * @param fluxDotN F̄(u) n = (u⁻²+u⁻*u⁺+u⁺²)/6*(1ᵀn) where 1 is (dim) vector
    * @return real_t maximum characteristic speed, |u|
    */
   real_t ComputeAvgFluxDotN(const Vector &state1,
                             const Vector &state2,
                             const Vector &normal,
                             FaceElementTransformations &Tr,
                             Vector &fluxDotN) const override;

   /**
    * @brief Compute J(u)
    *
    * @param state state (u) at current integration point
    * @param Tr current element transformation with the integration point
    * @param J J(u) = diag(u*1) where 1 is (dim) vector
    */
   void ComputeFluxJacobian(const Vector &state,
                            ElementTransformation &Tr,
                            DenseTensor &J) const override;

   /**
    * @brief Compute J(u) n
    *
    * @param state state (u) at current integration point
    * @param normal normal vector, usually not a unit vector
    * @param Tr current element transformation with the integration point
    * @param JDotN J(u) n = u*(1ᵀn) where 1 is (dim) vector
    */
   void ComputeFluxJacobianDotN(const Vector &state,
                                const Vector &normal,
                                ElementTransformation &Tr,
                                DenseMatrix &JDotN) const override;
};

/// Shallow water flux
class ShallowWaterFlux : public FluxFunction
{
private:
   const real_t g;  // gravity constant

public:
   /**
    * @brief Construct a new ShallowWaterFlux FluxFunction with given spatial
    * dimension and gravity constant
    *
    * @param dim spatial dimension
    * @param g gravity constant
    */
   ShallowWaterFlux(const int dim, const real_t g=9.8)
      : FluxFunction(dim + 1, dim), g(g) {}

   /**
    * @brief Compute F(h, hu)
    *
    * @param state state (h, hu) at current integration point
    * @param Tr current element transformation with the integration point
    * @param flux F(h, hu) = [huᵀ; huuᵀ + ½gh²I]
    * @return real_t maximum characteristic speed, |u| + √(gh)
    */
   real_t ComputeFlux(const Vector &state, ElementTransformation &Tr,
                      DenseMatrix &flux) const override;

   /**
    * @brief Compute normal flux, F(h, hu)
    *
    * @param state state (h, hu) at current integration point
    * @param normal normal vector, usually not a unit vector
    * @param Tr current element transformation with the integration point
    * @param fluxN F(ρ, ρu, E)n = [ρu⋅n; ρu(u⋅n) + pn; (u⋅n)(E + p)]
    * @return real_t maximum characteristic speed, |u| + √(γp/ρ)
    */
   real_t ComputeFluxDotN(const Vector &state, const Vector &normal,
                          FaceElementTransformations &Tr,
                          Vector &fluxN) const override;
};

/// Euler flux
class EulerFlux : public FluxFunction
{
private:
   const real_t specific_heat_ratio;  // specific heat ratio, γ
   // const real_t gas_constant;         // gas constant

public:
   /**
    * @brief Construct a new EulerFlux FluxFunction with given spatial
    * dimension and specific heat ratio
    *
    * @param dim spatial dimension
    * @param specific_heat_ratio specific heat ratio, γ
    */
   EulerFlux(const int dim, const real_t specific_heat_ratio)
      : FluxFunction(dim + 2, dim),
        specific_heat_ratio(specific_heat_ratio) {}

   /**
    * @brief Compute F(ρ, ρu, E)
    *
    * @param state state (ρ, ρu, E) at current integration point
    * @param Tr current element transformation with the integration point
    * @param flux F(ρ, ρu, E) = [ρuᵀ; ρuuᵀ + pI; uᵀ(E + p)]
    * @return real_t maximum characteristic speed, |u| + √(γp/ρ)
    */
   real_t ComputeFlux(const Vector &state, ElementTransformation &Tr,
                      DenseMatrix &flux) const override;

   /**
    * @brief Compute normal flux, F(ρ, ρu, E)n
    *
    * @param x x (ρ, ρu, E) at current integration point
    * @param normal normal vector, usually not a unit vector
    * @param Tr current element transformation with the integration point
    * @param fluxN F(ρ, ρu, E)n = [ρu⋅n; ρu(u⋅n) + pn; (u⋅n)(E + p)]
    * @return real_t maximum characteristic speed, |u| + √(γp/ρ)
    */
   real_t ComputeFluxDotN(const Vector &x, const Vector &normal,
                          FaceElementTransformations &Tr,
                          Vector &fluxN) const override;
};

} // namespace mfem

#endif // MFEM_HYPERBOLIC
