text: //                                MFEM Example 10
  //                             SUNDIALS Modification
  //
  // Compile with: make ex10
  //
  // Sample runs:
  //    ex10 -m ../../data/beam-quad.mesh -r 2 -o 2 -s 12 -dt 0.15 -vs 10
  //    ex10 -m ../../data/beam-tri.mesh  -r 2 -o 2 -s 16 -dt 0.3  -vs 5
  //    ex10 -m ../../data/beam-hex.mesh  -r 1 -o 2 -s 12 -dt 0.2  -vs 5
  //    ex10 -m ../../data/beam-tri.mesh  -r 2 -o 2 -s  2 -dt 3 -nls kinsol
  //    ex10 -m ../../data/beam-quad.mesh -r 2 -o 2 -s  2 -dt 3 -nls kinsol
  //    ex10 -m ../../data/beam-hex.mesh  -r 1 -o 2 -s  2 -dt 3 -nls kinsol
  //    ex10 -m ../../data/beam-quad.mesh -r 2 -o 2 -s 14 -dt 0.15 -vs 10
  //    ex10 -m ../../data/beam-tri.mesh  -r 2 -o 2 -s 17 -dt 0.01 -vs 30
  //    ex10 -m ../../data/beam-hex.mesh  -r 1 -o 2 -s 14 -dt 0.15 -vs 10
  //    ex10 -m ../../data/beam-quad-amr.mesh -r 2 -o 2 -s 12 -dt 0.15 -vs 10
  //
  // Description:  This examples solves a time dependent nonlinear elasticity
  //               problem of the form dv/dt = H(x) + S v, dx/dt = v, where H is a
  //               hyperelastic model and S is a viscosity operator of Laplacian
  //               type. The geometry of the domain is assumed to be as follows:
  //
  //                                 +---------------------+
  //                    boundary --->|                     |
  //                    attribute 1  |                     |
  //                    (fixed)      +---------------------+
  //
  //               The example demonstrates the use of nonlinear operators (the
  //               class HyperelasticOperator defining H(x)), as well as their
  //               implicit time integration using a Newton method for solving an
  //               associated reduced backward-Euler type nonlinear equation
  //               (class ReducedSystemOperator). Each Newton step requires the
  //               inversion of a Jacobian matrix, which is done through a
  //               (preconditioned) inner solver. Note that implementing the
  //               method HyperelasticOperator::ImplicitSolve is the only
  //               requirement for high-order implicit (SDIRK) time integration.
  //
  //               We recommend viewing examples 2 and 9 before viewing this
  //               example.
  
  #include "mfem.hpp"
  #include <memory>
  #include <iostream>
  #include <fstream>
  #include <string>
  #include <map>
  
  #ifndef MFEM_USE_SUNDIALS
  #error This example requires that MFEM is built with MFEM_USE_SUNDIALS=YES
  #endif
  
  using namespace std;
  using namespace mfem;
  
  class ReducedSystemOperator;
  
  /** After spatial discretization, the hyperelastic model can be written as a
   *  system of ODEs:
   *     dv/dt = -M^{-1}*(H(x) + S*v)
   *     dx/dt = v,
   *  where x is the vector representing the deformation, v is the velocity field,
   *  M is the mass matrix, S is the viscosity matrix, and H(x) is the nonlinear
   *  hyperelastic operator.
   *
   *  Class HyperelasticOperator represents the right-hand side of the above
   *  system of ODEs. */
  class HyperelasticOperator : public TimeDependentOperator
  {
  protected:
     FiniteElementSpace &fespace;
  
     BilinearForm M, S;
     NonlinearForm H;
     double viscosity;
     HyperelasticModel *model;
  
     CGSolver M_solver; // Krylov solver for inverting the mass matrix M
     DSmoother M_prec;  // Preconditioner for the mass matrix M
  
     /** Nonlinear operator defining the reduced backward Euler equation for the
         velocity. Used in the implementation of method ImplicitSolve. */
     ReducedSystemOperator *reduced_oper;
  
     /// Newton solver for the reduced backward Euler equation
     NewtonSolver *newton_solver;
  
     /// Solver for the Jacobian solve in the Newton method
     Solver *J_solver;
     /// Preconditioner for the Jacobian solve in the Newton method
     Solver *J_prec;
  
     mutable Vector z; // auxiliary vector
  
     SparseMatrix *grad_H;
     SparseMatrix *Jacobian;
  
     double saved_gamma; // saved gamma value from implicit setup
  
  public:
     /// Solver type to use in the ImplicitSolve() method, used by SDIRK methods.
     enum NonlinearSolverType
     {
        NEWTON = 0, ///< Use MFEM's plain NewtonSolver
        KINSOL = 1  ///< Use SUNDIALS' KINSOL (through MFEM's class KINSolver)
     };
  
     HyperelasticOperator(FiniteElementSpace &f, Array<int> &ess_bdr,
                          double visc, double mu, double K,
                          NonlinearSolverType nls_type);
  
     /// Compute the right-hand side of the ODE system.
     virtual void Mult(const Vector &vx, Vector &dvx_dt) const;
  
     /** Solve the Backward-Euler equation: k = f(x + dt*k, t), for the unknown k.
         This is the only requirement for high-order SDIRK implicit integration.*/
     virtual void ImplicitSolve(const double dt, const Vector &x, Vector &k);
  
  
     /// Custom Jacobian system solver for the SUNDIALS time integrators.
     /** For the ODE system represented by HyperelasticOperator
  
            M dv/dt = -(H(x) + S*v)
            dx/dt = v,
  
         this class facilitates the solution of linear systems of the form
  
             (M + γS) yv + γJ yx = M bv,   J=(dH/dx)(x)
                  - γ yv +    yx =   bx
  
         for given bv, bx, x, and γ = GetTimeStep(). */
  
     /** Linear solve applicable to the SUNDIALS format.
         Solves (Mass - dt J) y = Mass b, where in our case:
         Mass = | M  0 |  J = | -S  -grad_H |  y = | v_hat |  b = | b_v |
                | 0  I |      |  I     0    |      | x_hat |      | b_x |
         The result replaces the rhs b.
         We substitute x_hat = b_x + dt v_hat and solve
         (M + dt S + dt^2 grad_H) v_hat = M b_v - dt grad_H b_x. */
  
     /** Setup the linear system. This method is used by the implicit
         SUNDIALS solvers. */
     virtual int SUNImplicitSetup(const Vector &y, const Vector &fy,
                                  int jok, int *jcur, double gamma);
  
     /** Solve the linear system. This method is used by the implicit
         SUNDIALS solvers. */
     virtual int SUNImplicitSolve(const Vector &b, Vector &x, double tol);
  
     double ElasticEnergy(const Vector &x) const;
     double KineticEnergy(const Vector &v) const;
     void GetElasticEnergyDensity(const GridFunction &x, GridFunction &w) const;
  
     virtual ~HyperelasticOperator();
  };
  
  /** Nonlinear operator of the form:
      k --> (M + dt*S)*k + H(x + dt*v + dt^2*k) + S*v,
      where M and S are given BilinearForms, H is a given NonlinearForm, v and x
      are given vectors, and dt is a scalar. */
  class ReducedSystemOperator : public Operator
  {
  private:
     BilinearForm *M, *S;
     NonlinearForm *H;
     mutable SparseMatrix *Jacobian;
     double dt;
     const Vector *v, *x;
     mutable Vector w, z;
  
  public:
     ReducedSystemOperator(BilinearForm *M_, BilinearForm *S_, NonlinearForm *H_);
  
     /// Set current dt, v, x values - needed to compute action and Jacobian.
     void SetParameters(double dt_, const Vector *v_, const Vector *x_);
  
     /// Compute y = H(x + dt (v + dt k)) + M k + S (v + dt k).
     virtual void Mult(const Vector &k, Vector &y) const;
  
     /// Compute J = M + dt S + dt^2 grad_H(x + dt (v + dt k)).
     virtual Operator &GetGradient(const Vector &k) const;
  
     virtual ~ReducedSystemOperator();
  };
  
  
  /** Function representing the elastic energy density for the given hyperelastic
      model+deformation. Used in HyperelasticOperator::GetElasticEnergyDensity. */
  class ElasticEnergyCoefficient : public Coefficient
  {
  private:
     HyperelasticModel  &model;
     const GridFunction &x;
     DenseMatrix         J;
  
  public:
     ElasticEnergyCoefficient(HyperelasticModel &m, const GridFunction &x_)
        : model(m), x(x_) { }
     virtual double Eval(ElementTransformation &T, const IntegrationPoint &ip);
     virtual ~ElasticEnergyCoefficient() { }
  };
  
  void InitialDeformation(const Vector &x, Vector &y);
  
  void InitialVelocity(const Vector &x, Vector &v);
  
  void visualize(ostream &out, Mesh *mesh, GridFunction *deformed_nodes,
                 GridFunction *field, const char *field_name = NULL,
                 bool init_vis = false);
  
  
  int main(int argc, char *argv[])
  {
     // 1. Parse command-line options.
     const char *mesh_file = "../../data/beam-quad.mesh";
     int ref_levels = 2;
     int order = 2;
     int ode_solver_type = 3;
     double t_final = 300.0;
     double dt = 3.0;
     double visc = 1e-2;
     double mu = 0.25;
     double K = 5.0;
     bool visualization = true;
     const char *nls = "newton";
     int vis_steps = 1;
  
     // Relative and absolute tolerances for CVODE and ARKODE.
     const double reltol = 1e-1, abstol = 1e-1;
     // Since this example uses the loose tolerances defined above, it is
     // necessary to lower the linear solver tolerance for CVODE which is relative
     // to the above tolerances.
     const double cvode_eps_lin = 1e-4;
     // Similarly, the nonlinear tolerance for ARKODE needs to be tightened.
     const double arkode_eps_nonlin = 1e-6;
  
     OptionsParser args(argc, argv);
     args.AddOption(&mesh_file, "-m", "--mesh",
                    "Mesh file to use.");
     args.AddOption(&ref_levels, "-r", "--refine",
                    "Number of times to refine the mesh uniformly.");
     args.AddOption(&order, "-o", "--order",
                    "Order (degree) of the finite elements.");
     args.AddOption(&ode_solver_type, "-s", "--ode-solver",
                    "ODE solver:\n\t"
                    "1  - Backward Euler,\n\t"
                    "2  - SDIRK2, L-stable\n\t"
                    "3  - SDIRK3, L-stable\n\t"
                    "4  - Implicit Midpoint,\n\t"
                    "5  - SDIRK2, A-stable,\n\t"
                    "6  - SDIRK3, A-stable,\n\t"
                    "7  - Forward Euler,\n\t"
                    "8  - RK2,\n\t"
                    "9  - RK3 SSP,\n\t"
                    "10 - RK4,\n\t"
                    "11 - CVODE implicit BDF, approximate Jacobian,\n\t"
                    "12 - CVODE implicit BDF, specified Jacobian,\n\t"
                    "13 - CVODE implicit ADAMS, approximate Jacobian,\n\t"
                    "14 - CVODE implicit ADAMS, specified Jacobian,\n\t"
                    "15 - ARKODE implicit, approximate Jacobian,\n\t"
                    "16 - ARKODE implicit, specified Jacobian,\n\t"
                    "17 - ARKODE explicit, 4th order.");
     args.AddOption(&nls, "-nls", "--nonlinear-solver",
                    "Nonlinear systems solver: "
                    "\"newton\" (plain Newton) or \"kinsol\" (KINSOL).");
     args.AddOption(&t_final, "-tf", "--t-final",
                    "Final time; start time is 0.");
     args.AddOption(&dt, "-dt", "--time-step",
                    "Time step.");
     args.AddOption(&visc, "-v", "--viscosity",
                    "Viscosity coefficient.");
     args.AddOption(&mu, "-mu", "--shear-modulus",
                    "Shear modulus in the Neo-Hookean hyperelastic model.");
     args.AddOption(&K, "-K", "--bulk-modulus",
                    "Bulk modulus in the Neo-Hookean hyperelastic model.");
     args.AddOption(&visualization, "-vis", "--visualization", "-no-vis",
                    "--no-visualization",
                    "Enable or disable GLVis visualization.");
     args.AddOption(&vis_steps, "-vs", "--visualization-steps",
                    "Visualize every n-th timestep.");
     args.Parse();
     if (!args.Good())
     {
        args.PrintUsage(cout);
        return 1;
     }
     args.PrintOptions(cout);
  
     // check for vaild ODE solver option
     if (ode_solver_type < 1 || ode_solver_type > 17)
     {
        cout << "Unknown ODE solver type: " << ode_solver_type << '\n';
        return 1;
     }
  
     // 2. Read the mesh from the given mesh file. We can handle triangular,
     //    quadrilateral, tetrahedral and hexahedral meshes with the same code.
     Mesh *mesh = new Mesh(mesh_file, 1, 1);
     int dim = mesh->Dimension();
  
     // 3. Setup the nonlinear solver
     map<string,HyperelasticOperator::NonlinearSolverType> nls_map;
     nls_map["newton"] = HyperelasticOperator::NEWTON;
     nls_map["kinsol"] = HyperelasticOperator::KINSOL;
     if (nls_map.find(nls) == nls_map.end())
     {
        cout << "Unknown type of nonlinear solver: " << nls << endl;
        return 4;
     }
  
     // 4. Refine the mesh to increase the resolution. In this example we do
     //    'ref_levels' of uniform refinement, where 'ref_levels' is a
     //    command-line parameter.
     for (int lev = 0; lev < ref_levels; lev++)
     {
        mesh->UniformRefinement();
     }
  
     // 5. Define the vector finite element spaces representing the mesh
     //    deformation x, the velocity v, and the initial configuration, x_ref.
     //    Define also the elastic energy density, w, which is in a discontinuous
     //    higher-order space. Since x and v are integrated in time as a system,
     //    we group them together in block vector vx, with offsets given by the
     //    fe_offset array.
     H1_FECollection fe_coll(order, dim);
     FiniteElementSpace fespace(mesh, &fe_coll, dim);
  
     int fe_size = fespace.GetTrueVSize();
     cout << "Number of velocity/deformation unknowns: " << fe_size << endl;
     Array<int> fe_offset(3);
     fe_offset[0] = 0;
     fe_offset[1] = fe_size;
     fe_offset[2] = 2*fe_size;
  
     BlockVector vx(fe_offset);
     GridFunction v, x;
     v.MakeTRef(&fespace, vx.GetBlock(0), 0);
     x.MakeTRef(&fespace, vx.GetBlock(1), 0);
  
     GridFunction x_ref(&fespace);
     mesh->GetNodes(x_ref);
  
     L2_FECollection w_fec(order + 1, dim);
     FiniteElementSpace w_fespace(mesh, &w_fec);
     GridFunction w(&w_fespace);
  
     // 6. Set the initial conditions for v and x, and the boundary conditions on
     //    a beam-like mesh (see description above).
     VectorFunctionCoefficient velo(dim, InitialVelocity);
     v.ProjectCoefficient(velo);
     v.SetTrueVector();
     VectorFunctionCoefficient deform(dim, InitialDeformation);
     x.ProjectCoefficient(deform);
     x.SetTrueVector();
  
     Array<int> ess_bdr(fespace.GetMesh()->bdr_attributes.Max());
     ess_bdr = 0;
     ess_bdr[0] = 1; // boundary attribute 1 (index 0) is fixed
  
     // 7. Initialize the hyperelastic operator, the GLVis visualization and print
     //    the initial energies.
     HyperelasticOperator oper(fespace, ess_bdr, visc, mu, K, nls_map[nls]);
  
     socketstream vis_v, vis_w;
     if (visualization)
     {
        char vishost[] = "localhost";
        int  visport   = 19916;
        vis_v.open(vishost, visport);
        vis_v.precision(8);
        v.SetFromTrueVector(); x.SetFromTrueVector();
        visualize(vis_v, mesh, &x, &v, "Velocity", true);
        vis_w.open(vishost, visport);
        if (vis_w)
        {
           oper.GetElasticEnergyDensity(x, w);
           vis_w.precision(8);
           visualize(vis_w, mesh, &x, &w, "Elastic energy density", true);
        }
     }
  
     double ee0 = oper.ElasticEnergy(x.GetTrueVector());
     double ke0 = oper.KineticEnergy(v.GetTrueVector());
     cout << "initial elastic energy (EE) = " << ee0 << endl;
     cout << "initial kinetic energy (KE) = " << ke0 << endl;
     cout << "initial   total energy (TE) = " << (ee0 + ke0) << endl;
  
     // 8. Define the ODE solver used for time integration. Several implicit
     //    singly diagonal implicit Runge-Kutta (SDIRK) methods, as well as
     //    explicit Runge-Kutta methods are available.
     double t = 0.0;
     oper.SetTime(t);
  
     ODESolver *ode_solver = NULL;
     CVODESolver *cvode = NULL;
     ARKStepSolver *arkode = NULL;
     switch (ode_solver_type)
     {
        // Implicit L-stable methods
        case 1:  ode_solver = new BackwardEulerSolver; break;
        case 2:  ode_solver = new SDIRK23Solver(2); break;
        case 3:  ode_solver = new SDIRK33Solver; break;
        // Implicit A-stable methods (not L-stable)
        case 4:  ode_solver = new ImplicitMidpointSolver; break;
        case 5:  ode_solver = new SDIRK23Solver; break;
        case 6:  ode_solver = new SDIRK34Solver; break;
        // Explicit methods
        case 7:  ode_solver = new ForwardEulerSolver; break;
        case 8:  ode_solver = new RK2Solver(0.5); break; // midpoint method
        case 9:  ode_solver = new RK3SSPSolver; break;
        case 10: ode_solver = new RK4Solver; break;
        // CVODE BDF
        case 11:
        case 12:
           cvode = new CVODESolver(CV_BDF);
           cvode->Init(oper);
           cvode->SetSStolerances(reltol, abstol);
           CVodeSetEpsLin(cvode->GetMem(), cvode_eps_lin);
           cvode->SetMaxStep(dt);
           if (ode_solver_type == 11)
           {
              cvode->UseSundialsLinearSolver();
           }
           ode_solver = cvode; break;
        // CVODE Adams
        case 13:
        case 14:
           cvode = new CVODESolver(CV_ADAMS);
           cvode->Init(oper);
           cvode->SetSStolerances(reltol, abstol);
           CVodeSetEpsLin(cvode->GetMem(), cvode_eps_lin);
           cvode->SetMaxStep(dt);
           if (ode_solver_type == 13)
           {
              cvode->UseSundialsLinearSolver();
           }
           ode_solver = cvode; break;
        // ARKStep Implicit methods
        case 15:
        case 16:
           arkode = new ARKStepSolver(ARKStepSolver::IMPLICIT);
           arkode->Init(oper);
           arkode->SetSStolerances(reltol, abstol);
           ARKStepSetNonlinConvCoef(arkode->GetMem(), arkode_eps_nonlin);
           arkode->SetMaxStep(dt);
           if (ode_solver_type == 15)
           {
              arkode->UseSundialsLinearSolver();
           }
           ode_solver = arkode; break;
        // ARKStep Explicit methods
        case 17:
           arkode = new ARKStepSolver(ARKStepSolver::EXPLICIT);
           arkode->Init(oper);
           arkode->SetSStolerances(reltol, abstol);
           arkode->SetMaxStep(dt);
           ode_solver = arkode; break;
     }
  
     // Initialize MFEM integrators, SUNDIALS integrators are initialized above
     if (ode_solver_type < 11) { ode_solver->Init(oper); }
  
     // 9. Perform time-integration (looping over the time iterations, ti, with a
     //    time-step dt).
     bool last_step = false;
     for (int ti = 1; !last_step; ti++)
     {
        double dt_real = min(dt, t_final - t);
  
        ode_solver->Step(vx, t, dt_real);
  
        last_step = (t >= t_final - 1e-8*dt);
  
        if (last_step || (ti % vis_steps) == 0)
        {
           double ee = oper.ElasticEnergy(x.GetTrueVector());
           double ke = oper.KineticEnergy(v.GetTrueVector());
  
           cout << "step " << ti << ", t = " << t << ", EE = " << ee << ", KE = "
                << ke << ", ΔTE = " << (ee+ke)-(ee0+ke0) << endl;
  
           if (cvode) { cvode->PrintInfo(); }
           else if (arkode) { arkode->PrintInfo(); }
  
           if (visualization)
           {
              v.SetFromTrueVector(); x.SetFromTrueVector();
              visualize(vis_v, mesh, &x, &v);
              if (vis_w)
              {
                 oper.GetElasticEnergyDensity(x, w);
                 visualize(vis_w, mesh, &x, &w);
              }
           }
        }
     }
  
     // 10. Save the displaced mesh, the velocity and elastic energy.
     {
        v.SetFromTrueVector(); x.SetFromTrueVector();
        GridFunction *nodes = &x;
        int owns_nodes = 0;
        mesh->SwapNodes(nodes, owns_nodes);
        ofstream mesh_ofs("deformed.mesh");
        mesh_ofs.precision(8);
        mesh->Print(mesh_ofs);
        mesh->SwapNodes(nodes, owns_nodes);
        ofstream velo_ofs("velocity.sol");
        velo_ofs.precision(8);
        v.Save(velo_ofs);
        ofstream ee_ofs("elastic_energy.sol");
        ee_ofs.precision(8);
        oper.GetElasticEnergyDensity(x, w);
        w.Save(ee_ofs);
     }
  
     // 11. Free the used memory.
     delete ode_solver;
     delete mesh;
  
     return 0;
  }
  
  
  void visualize(ostream &out, Mesh *mesh, GridFunction *deformed_nodes,
                 GridFunction *field, const char *field_name, bool init_vis)
  {
     if (!out)
     {
        return;
     }
  
     GridFunction *nodes = deformed_nodes;
     int owns_nodes = 0;
  
     mesh->SwapNodes(nodes, owns_nodes);
  
     out << "solution\n" << *mesh << *field;
  
     mesh->SwapNodes(nodes, owns_nodes);
  
     if (init_vis)
     {
        out << "window_size 800 800\n";
        out << "window_title '" << field_name << "'\n";
        if (mesh->SpaceDimension() == 2)
        {
           out << "view 0 0\n"; // view from top
           out << "keys jl\n";  // turn off perspective and light
        }
        out << "keys cm\n";         // show colorbar and mesh
        out << "autoscale value\n"; // update value-range; keep mesh-extents fixed
        out << "pause\n";
     }
     out << flush;
  }
  
  
  ReducedSystemOperator::ReducedSystemOperator(
     BilinearForm *M_, BilinearForm *S_, NonlinearForm *H_)
     : Operator(M_->Height()), M(M_), S(S_), H(H_), Jacobian(NULL),
       dt(0.0), v(NULL), x(NULL), w(height), z(height)
  { }
  
  void ReducedSystemOperator::SetParameters(double dt_, const Vector *v_,
                                            const Vector *x_)
  {
     dt = dt_;  v = v_;  x = x_;
  }
  
  void ReducedSystemOperator::Mult(const Vector &k, Vector &y) const
  {
     // compute: y = H(x + dt*(v + dt*k)) + M*k + S*(v + dt*k)
     add(*v, dt, k, w);
     add(*x, dt, w, z);
     H->Mult(z, y);
     M->AddMult(k, y);
     S->AddMult(w, y);
  }
  
  Operator &ReducedSystemOperator::GetGradient(const Vector &k) const
  {
     delete Jacobian;
     Jacobian = Add(1.0, M->SpMat(), dt, S->SpMat());
     add(*v, dt, k, w);
     add(*x, dt, w, z);
     SparseMatrix *grad_H = dynamic_cast<SparseMatrix *>(&H->GetGradient(z));
     Jacobian->Add(dt*dt, *grad_H);
     return *Jacobian;
  }
  
  ReducedSystemOperator::~ReducedSystemOperator()
  {
     delete Jacobian;
  }
  
  
  HyperelasticOperator::HyperelasticOperator(FiniteElementSpace &f,
                                             Array<int> &ess_bdr, double visc,
                                             double mu, double K,
                                             NonlinearSolverType nls_type)
     : TimeDependentOperator(2*f.GetTrueVSize(), 0.0), fespace(f),
       M(&fespace), S(&fespace), H(&fespace),
       viscosity(visc), z(height/2),
       grad_H(NULL), Jacobian(NULL)
  {
     const double rel_tol = 1e-8;
     const int skip_zero_entries = 0;
  
     const double ref_density = 1.0; // density in the reference configuration
     ConstantCoefficient rho0(ref_density);
     M.AddDomainIntegrator(new VectorMassIntegrator(rho0));
     M.Assemble(skip_zero_entries);
     Array<int> ess_tdof_list;
     fespace.GetEssentialTrueDofs(ess_bdr, ess_tdof_list);
     SparseMatrix tmp;
     M.FormSystemMatrix(ess_tdof_list, tmp);
  
     M_solver.iterative_mode = false;
     M_solver.SetRelTol(rel_tol);
     M_solver.SetAbsTol(0.0);
     M_solver.SetMaxIter(30);
     M_solver.SetPrintLevel(0);
     M_solver.SetPreconditioner(M_prec);
     M_solver.SetOperator(M.SpMat());
  
     model = new NeoHookeanModel(mu, K);
     H.AddDomainIntegrator(new HyperelasticNLFIntegrator(model));
     H.SetEssentialTrueDofs(ess_tdof_list);
  
     ConstantCoefficient visc_coeff(viscosity);
     S.AddDomainIntegrator(new VectorDiffusionIntegrator(visc_coeff));
     S.Assemble(skip_zero_entries);
     S.FormSystemMatrix(ess_tdof_list, tmp);
  
     reduced_oper = new ReducedSystemOperator(&M, &S, &H);
  
  #ifndef MFEM_USE_SUITESPARSE
     J_prec = new DSmoother(1);
     MINRESSolver *J_minres = new MINRESSolver;
     J_minres->SetRelTol(rel_tol);
     J_minres->SetAbsTol(0.0);
     J_minres->SetMaxIter(300);
     J_minres->SetPrintLevel(-1);
     J_minres->SetPreconditioner(*J_prec);
     J_solver = J_minres;
  #else
     J_solver = new UMFPackSolver;
     J_prec = NULL;
  #endif
  
     if (nls_type == KINSOL)
     {
        KINSolver *kinsolver = new KINSolver(KIN_NONE, true);
        newton_solver = kinsolver;
        newton_solver->SetOperator(*reduced_oper);
        newton_solver->SetMaxIter(200);
        newton_solver->SetRelTol(rel_tol);
        newton_solver->SetPrintLevel(0);
        kinsolver->SetMaxSetupCalls(4);
     }
     else
     {
        newton_solver = new NewtonSolver();
        newton_solver->SetOperator(*reduced_oper);
        newton_solver->SetMaxIter(10);
        newton_solver->SetRelTol(rel_tol);
        newton_solver->SetPrintLevel(-1);
     }
     newton_solver->SetSolver(*J_solver);
     newton_solver->iterative_mode = false;
  }
  
  void HyperelasticOperator::Mult(const Vector &vx, Vector &dvx_dt) const
  {
     // Create views to the sub-vectors v, x of vx, and dv_dt, dx_dt of dvx_dt
     int sc = height/2;
     Vector v(vx.GetData() +  0, sc);
     Vector x(vx.GetData() + sc, sc);
     Vector dv_dt(dvx_dt.GetData() +  0, sc);
     Vector dx_dt(dvx_dt.GetData() + sc, sc);
  
     H.Mult(x, z);
     if (viscosity != 0.0)
     {
        S.AddMult(v, z);
     }
     z.Neg(); // z = -z
     M_solver.Mult(z, dv_dt);
  
     dx_dt = v;
  }
  
  void HyperelasticOperator::ImplicitSolve(const double dt,
                                           const Vector &vx, Vector &dvx_dt)
  {
     int sc = height/2;
     Vector v(vx.GetData() +  0, sc);
     Vector x(vx.GetData() + sc, sc);
     Vector dv_dt(dvx_dt.GetData() +  0, sc);
     Vector dx_dt(dvx_dt.GetData() + sc, sc);
  
     // By eliminating kx from the coupled system:
     //    kv = -M^{-1}*[H(x + dt*kx) + S*(v + dt*kv)]
     //    kx = v + dt*kv
     // we reduce it to a nonlinear equation for kv, represented by the
     // reduced_oper. This equation is solved with the newton_solver
     // object (using J_solver and J_prec internally).
     reduced_oper->SetParameters(dt, &v, &x);
     Vector zero; // empty vector is interpreted as zero r.h.s. by NewtonSolver
     newton_solver->Mult(zero, dv_dt);
     MFEM_VERIFY(newton_solver->GetConverged(),
                 "Nonlinear solver did not converge.");
  #ifdef MFEM_DEBUG
     cout << "  num nonlin sol iters = " << newton_solver->GetNumIterations()
          << ", final norm = " << newton_solver->GetFinalNorm() << '\n';
  #endif
     add(v, dt, dv_dt, dx_dt);
  }
  
  int HyperelasticOperator::SUNImplicitSetup(const Vector &y,
                                             const Vector &fy, int jok, int *jcur,
                                             double gamma)
  {
     int sc = y.Size() / 2;
     const Vector x(y.GetData() + sc, sc);
  
     // J = M + dt*(S + dt*grad(H))
     if (Jacobian) { delete Jacobian; }
     Jacobian = Add(1.0, M.SpMat(), gamma, S.SpMat());
     grad_H = dynamic_cast<SparseMatrix *>(&H.GetGradient(x));
     Jacobian->Add(gamma * gamma, *grad_H);
  
     // Set Jacobian solve operator
     J_solver->SetOperator(*Jacobian);
  
     // Indicate that the Jacobian was updated
     *jcur = 1;
  
     // Save gamma for use in solve
     saved_gamma = gamma;
  
     // Return success
     return 0;
  }
  
  int HyperelasticOperator::SUNImplicitSolve(const Vector &b, Vector &x,
                                             double tol)
  {
     int sc = b.Size() / 2;
     Vector b_v(b.GetData() +  0, sc);
     Vector b_x(b.GetData() + sc, sc);
     Vector x_v(x.GetData() +  0, sc);
     Vector x_x(x.GetData() + sc, sc);
     Vector rhs(sc);
  
     // rhs = M b_v - dt*grad(H) b_x
     grad_H->Mult(b_x, rhs);
     rhs *= -saved_gamma;
     M.AddMult(b_v, rhs);
  
     J_solver->iterative_mode = false;
     J_solver->Mult(rhs, x_v);
  
     add(b_x, saved_gamma, x_v, x_x);
  
     return 0;
  }
  
  double HyperelasticOperator::ElasticEnergy(const Vector &x) const
  {
     return H.GetEnergy(x);
  }
  
  double HyperelasticOperator::KineticEnergy(const Vector &v) const
  {
     return 0.5*M.InnerProduct(v, v);
  }
  
  void HyperelasticOperator::GetElasticEnergyDensity(
     const GridFunction &x, GridFunction &w) const
  {
     ElasticEnergyCoefficient w_coeff(*model, x);
     w.ProjectCoefficient(w_coeff);
  }
  
  HyperelasticOperator::~HyperelasticOperator()
  {
     delete Jacobian;
     delete newton_solver;
     delete J_solver;
     delete J_prec;
     delete reduced_oper;
     delete model;
  }
  
  
  double ElasticEnergyCoefficient::Eval(ElementTransformation &T,
                                        const IntegrationPoint &ip)
  {
     model.SetTransformation(T);
     x.GetVectorGradient(T, J);
     // return model.EvalW(J);  // in reference configuration
     return model.EvalW(J)/J.Det(); // in deformed configuration
  }
  
  
  void InitialDeformation(const Vector &x, Vector &y)
  {
     // set the initial configuration to be the same as the reference, stress
     // free, configuration
     y = x;
  }
  
  void InitialVelocity(const Vector &x, Vector &v)
  {
     const int dim = x.Size();
     const double s = 0.1/64.;
  
     v = 0.0;
     v(dim-1) = s*x(0)*x(0)*(8.0-x(0));
     v(0) = -s*x(0)*x(0);
  }
  