/* Author: Abner Salgado, Texas A&M University 2009               */

/*    $Id: step-35.cc 28376 2013-02-13 15:19:38Z heister $       */
/*                                                                */
/*    Copyright (C) 2009-2012 by deal.II authors */
/*                                                                */
/*    This file is subject to QPL and may not be  distributed     */
/*    without copyright and license information. Please refer     */
/*    to the file deal.II/doc/license.html for the  text  and     */
/*    further information on this license.                        */

// @sect3{Include files}

// We start by including all the necessary deal.II header files and some C++
// related ones. Each one of them has been discussed in previous tutorial
// programs, so we will not get into details here.
#include <deal.II/base/parameter_handler.h>
#include <deal.II/base/point.h>
#include <deal.II/base/function.h>
#include <deal.II/base/quadrature_lib.h>
#include <deal.II/base/multithread_info.h>
#include <deal.II/base/thread_management.h>
#include <deal.II/base/work_stream.h>
#include <deal.II/base/parallel.h>
#include <deal.II/base/utilities.h>
#include <deal.II/base/conditional_ostream.h>

#include <deal.II/lac/vector.h>
#include <deal.II/lac/sparse_matrix.h>
#include <deal.II/lac/solver_cg.h>
#include <deal.II/lac/precondition.h>
#include <deal.II/lac/solver_gmres.h>
#include <deal.II/lac/sparse_ilu.h>
#include <deal.II/lac/sparse_direct.h>
#include <deal.II/lac/constraint_matrix.h>

#include <deal.II/grid/tria.h>
#include <deal.II/grid/grid_generator.h>
#include <deal.II/grid/grid_refinement.h>
#include <deal.II/grid/tria_accessor.h>
#include <deal.II/grid/tria_iterator.h>
#include <deal.II/grid/tria_boundary_lib.h>
#include <deal.II/grid/grid_in.h>

#include <deal.II/dofs/dof_handler.h>
#include <deal.II/dofs/dof_accessor.h>
#include <deal.II/dofs/dof_tools.h>
#include <deal.II/dofs/dof_renumbering.h>

#include <deal.II/fe/fe_q.h>
#include <deal.II/fe/fe_values.h>
#include <deal.II/fe/fe_tools.h>
#include <deal.II/fe/fe_system.h>

#include <deal.II/numerics/matrix_tools.h>
#include <deal.II/numerics/vector_tools.h>
#include <deal.II/numerics/data_out.h>

#include <fstream>
#include <cmath>
#include <iostream>

// Finally this is as in all previous programs:
namespace Step35
{
  using namespace dealii;

  // @sect3{Run time parameters}
  //
  // Since our method has several parameters that can be fine-tuned we put
  // them into an external file, so that they can be determined at run-time.
  //
  // This includes, in particular, the formulation of the equation for the
  // auxiliary variable $\phi$, for which we declare an enum.
  // Next, we declare a class that is going to read and store all the
  // parameters that our program needs to run.
  namespace RunTimeParameters
  {
    enum MethodFormulation
    {
      METHOD_STANDARD,
      METHOD_ROTATIONAL
    };

    class Data_Storage
    {
    public:
      Data_Storage();
      ~Data_Storage();
      void read_data (const char *filename);
      MethodFormulation form;
      double initial_time,
             final_time,
             Reynolds;
      double dt;
      unsigned int n_global_refines,
               pressure_degree;
      unsigned int vel_max_iterations,
               vel_Krylov_size,
               vel_off_diagonals,
               vel_update_prec;
      double vel_eps,
             vel_diag_strength;
      bool verbose;
      unsigned int output_interval;
    protected:
      ParameterHandler prm;
    };

    // In the constructor of this class we declare all the parameters. The
    // details of how this works have been discussed elsewhere, for example in
    // step-19 and step-29.
    Data_Storage::Data_Storage()
    {
      prm.declare_entry ("Method_Form", "rotational",
                         Patterns::Selection ("rotational|standard"),
                         " Used to select the type of method that we are going "
                         "to use. ");
      prm.enter_subsection ("Physical data");
      {
        prm.declare_entry ("initial_time", "0.",
                           Patterns::Double (0.),
                           " The initial time of the simulation. ");
        prm.declare_entry ("final_time", "1.",
                           Patterns::Double (0.),
                           " The final time of the simulation. ");
        prm.declare_entry ("Reynolds", "1.",
                           Patterns::Double (0.),
                           " The Reynolds number. ");
      }
      prm.leave_subsection();

      prm.enter_subsection ("Time step data");
      {
        prm.declare_entry ("dt", "5e-4",
                           Patterns::Double (0.),
                           " The time step size. ");
      }
      prm.leave_subsection();

      prm.enter_subsection ("Space discretization");
      {
        prm.declare_entry ("n_of_refines", "0",
                           Patterns::Integer (0, 15),
                           " The number of global refines we do on the mesh. ");
        prm.declare_entry ("pressure_fe_degree", "1",
                           Patterns::Integer (1, 5),
                           " The polynomial degree for the pressure space. ");
      }
      prm.leave_subsection();

      prm.enter_subsection ("Data solve velocity");
      {
        prm.declare_entry ("max_iterations", "1000",
                           Patterns::Integer (1, 1000),
                           " The maximal number of iterations GMRES must make. ");
        prm.declare_entry ("eps", "1e-12",
                           Patterns::Double (0.),
                           " The stopping criterion. ");
        prm.declare_entry ("Krylov_size", "30",
                           Patterns::Integer(1),
                           " The size of the Krylov subspace to be used. ");
        prm.declare_entry ("off_diagonals", "60",
                           Patterns::Integer(0),
                           " The number of off-diagonal elements ILU must "
                           "compute. ");
        prm.declare_entry ("diag_strength", "0.01",
                           Patterns::Double (0.),
                           " Diagonal strengthening coefficient. ");
        prm.declare_entry ("update_prec", "15",
                           Patterns::Integer(1),
                           " This number indicates how often we need to "
                           "update the preconditioner");
      }
      prm.leave_subsection();

      prm.declare_entry ("verbose", "true",
                         Patterns::Bool(),
                         " This indicates whether the output of the solution "
                         "process should be verbose. ");

      prm.declare_entry ("output_interval", "1",
                         Patterns::Integer(1),
                         " This indicates between how many time steps we print "
                         "the solution. ");
    }

    Data_Storage::~Data_Storage()
    {}

    void Data_Storage::read_data (const char *filename)
    {
      std::ifstream file (filename);
      AssertThrow (file, ExcFileNotOpen (filename));

      prm.read_input (file);

      if (prm.get ("Method_Form") == std::string ("rotational"))
        form = METHOD_ROTATIONAL;
      else
        form = METHOD_STANDARD;

      prm.enter_subsection ("Physical data");
      {
        initial_time = prm.get_double ("initial_time");
        final_time   = prm.get_double ("final_time");
        Reynolds     = prm.get_double ("Reynolds");
      }
      prm.leave_subsection();

      prm.enter_subsection ("Time step data");
      {
        dt = prm.get_double ("dt");
      }
      prm.leave_subsection();

      prm.enter_subsection ("Space discretization");
      {
        n_global_refines = prm.get_integer ("n_of_refines");
        pressure_degree     = prm.get_integer ("pressure_fe_degree");
      }
      prm.leave_subsection();

      prm.enter_subsection ("Data solve velocity");
      {
        vel_max_iterations = prm.get_integer ("max_iterations");
        vel_eps            = prm.get_double ("eps");
        vel_Krylov_size    = prm.get_integer ("Krylov_size");
        vel_off_diagonals  = prm.get_integer ("off_diagonals");
        vel_diag_strength  = prm.get_double ("diag_strength");
        vel_update_prec    = prm.get_integer ("update_prec");
      }
      prm.leave_subsection();

      verbose = prm.get_bool ("verbose");

      output_interval = prm.get_integer ("output_interval");
    }
  }

  // @sect3{Equation data}

  // In the next namespace, we declare the initial and boundary conditions:
  namespace EquationData
  {
    // As we have chosen a completely decoupled formulation, we will not take
    // advantage of deal.II's capabilities to handle vector valued
    // problems. We do, however, want to use an interface for the equation
    // data that is somehow dimension independent. To be able to do that, our
    // functions should be able to know on which spatial component we are
    // currently working, and we should be able to have a common interface to
    // do that. The following class is an attempt in that direction.
    template <int dim>
    class MultiComponentFunction: public Function<dim>
    {
    public:
      MultiComponentFunction (const double initial_time = 0.);
      void set_component (const unsigned int d);
    protected:
      unsigned int comp;
    };

    template <int dim>
    MultiComponentFunction<dim>::
    MultiComponentFunction (const double initial_time)
      :
      Function<dim> (1, initial_time), comp(0)
    {}

    template <int dim>
    void MultiComponentFunction<dim>::set_component(const unsigned int d)
    {
      Assert (d<dim, ExcIndexRange (d, 0, dim));
      comp = d;
    }

    // With this class defined, we declare classes that describe the boundary
    // conditions for velocity and pressure:
    template <int dim>
    class Velocity : public MultiComponentFunction<dim>
    {
    public:
      Velocity (const double initial_time = 0.0);

      virtual double value (const Point<dim> &p,
                            const unsigned int component = 0) const;

      virtual void value_list (const std::vector< Point<dim> > &points,
                               std::vector<double> &values,
                               const unsigned int component = 0) const;
    };

    template <int dim>
    Velocity<dim>::Velocity (const double initial_time)
      :
      MultiComponentFunction<dim> (initial_time)
    {}

    template <int dim>
    void Velocity<dim>::value_list (const std::vector<Point<dim> > &points,
                                    std::vector<double> &values,
                                    const unsigned int) const
    {
      const unsigned int n_points = points.size();
      Assert (values.size() == n_points,
              ExcDimensionMismatch (values.size(), n_points));
      for (unsigned int i=0; i<n_points; ++i)
        values[i] = Velocity<dim>::value (points[i]);
    }

    template <int dim>
    double Velocity<dim>::value (const Point<dim> &p,
                                 const unsigned int) const
    {
      if (this->comp == 0)
        {
          const double Um = 1.5;
          const double H  = 4.1;
          return 4.*Um*p(1)*(H - p(1))/(H*H);
        }
      else
        return 0.;
    }

    template <int dim>
    class Pressure: public Function<dim>
    {
    public:
      Pressure (const double initial_time = 0.0);

      virtual double value (const Point<dim> &p,
                            const unsigned int component = 0) const;

      virtual void value_list (const std::vector< Point<dim> > &points,
                               std::vector<double> &values,
                               const unsigned int component = 0) const;
    };

    template <int dim>
    Pressure<dim>::Pressure (const double initial_time)
      :
      Function<dim> (1, initial_time)
    {}

    template <int dim>
    double Pressure<dim>::value (const Point<dim> &p,
                                 const unsigned int) const
    {
      return 25.-p(0);
    }

    template <int dim>
    void Pressure<dim>::value_list (const std::vector<Point<dim> > &points,
                                    std::vector<double> &values,
                                    const unsigned int) const
    {
      const unsigned int n_points = points.size();
      Assert (values.size() == n_points, ExcDimensionMismatch (values.size(), n_points));
      for (unsigned int i=0; i<n_points; ++i)
        values[i] = Pressure<dim>::value (points[i]);
    }
  }

  // @sect3{The NavierStokesProjection class}

  // Now for the main class of the program. It implements the various versions
  // of the projection method for Navier-Stokes equations.  The names for all
  // the methods and member variables should be self-explanatory, taking into
  // account the implementation details given in the introduction.
  template <int dim>
  class NavierStokesProjection
  {
  public:
    NavierStokesProjection (const RunTimeParameters::Data_Storage &data);

    void run (const bool         verbose    = false,
              const unsigned int n_plots = 10);
  protected:
    RunTimeParameters::MethodFormulation type;

    const unsigned int deg;
    const double       dt;
    const double       t_0, T, Re;

    EquationData::Velocity<dim>       vel_exact;
    std::map<unsigned int, double>    boundary_values;
    std::vector<types::boundary_id> boundary_indicators;

    Triangulation<dim> triangulation;

    FE_Q<dim>          fe_velocity;
    FE_Q<dim>          fe_pressure;

    DoFHandler<dim>    dof_handler_velocity;
    DoFHandler<dim>    dof_handler_pressure;

    QGauss<dim>        quadrature_pressure;
    QGauss<dim>        quadrature_velocity;

    SparsityPattern    sparsity_pattern_velocity;
    SparsityPattern    sparsity_pattern_pressure;
    SparsityPattern    sparsity_pattern_pres_vel;

    SparseMatrix<double> vel_Laplace_plus_Mass;
    SparseMatrix<double> vel_it_matrix[dim];
    SparseMatrix<double> vel_Mass;
    SparseMatrix<double> vel_Laplace;
    SparseMatrix<double> vel_Advection;
    SparseMatrix<double> pres_Laplace;
    SparseMatrix<double> pres_Mass;
    SparseMatrix<double> pres_Diff[dim];
    SparseMatrix<double> pres_iterative;

    Vector<double> pres_n;
    Vector<double> pres_n_minus_1;
    Vector<double> phi_n;
    Vector<double> phi_n_minus_1;
    Vector<double> u_n[dim];
    Vector<double> u_n_minus_1[dim];
    Vector<double> u_star[dim];
    Vector<double> force[dim];
    Vector<double> v_tmp;
    Vector<double> pres_tmp;
    Vector<double> rot_u;

    SparseILU<double> prec_velocity[dim];
    SparseILU<double> prec_pres_Laplace;
    SparseDirectUMFPACK prec_mass;
    SparseDirectUMFPACK prec_vel_mass;

    DeclException2 (ExcInvalidTimeStep,
                    double, double,
                    << " The time step " << arg1 << " is out of range."
                    << std::endl
                    << " The permitted range is (0," << arg2 << "]");

    void create_triangulation_and_dofs (const unsigned int n_refines);

    void initialize();

    void interpolate_velocity ();

    void diffusion_step (const bool reinit_prec);

    void projection_step (const bool reinit_prec);

    void update_pressure (const bool reinit_prec);

  private:
    unsigned int vel_max_its;
    unsigned int vel_Krylov_size;
    unsigned int vel_off_diagonals;
    unsigned int vel_update_prec;
    double       vel_eps;
    double       vel_diag_strength;

    void initialize_velocity_matrices();

    void initialize_pressure_matrices();

    // The next few structures and functions are for doing various things in
    // parallel. They follow the scheme laid out in @ref threads, using the
    // WorkStream class. As explained there, this requires us to declare two
    // structures for each of the assemblers, a per-task data and a scratch
    // data structure. These are then handed over to functions that assemble
    // local contributions and that copy these local contributions to the
    // global objects.
    //
    // One of the things that are specific to this program is that we don't
    // just have a single DoFHandler object that represents both the
    // velocities and the pressure, but we use individual DoFHandler objects
    // for these two kinds of variables. We pay for this optimization when we
    // want to assemble terms that involve both variables, such as the
    // divergence of the velocity and the gradient of the pressure, times the
    // respective test functions. When doing so, we can't just anymore use a
    // single FEValues object, but rather we need two, and they need to be
    // initialized with cell iterators that point to the same cell in the
    // triangulation but different DoFHandlers.
    //
    // To do this in practice, we declare a "synchronous" iterator -- an
    // object that internally consists of several (in our case two) iterators,
    // and each time the synchronous iteration is moved up one step, each of
    // the iterators stored internally is moved up one step as well, thereby
    // always staying in sync. As it so happens, there is a deal.II class that
    // facilitates this sort of thing.
    typedef std_cxx1x::tuple< typename DoFHandler<dim>::active_cell_iterator,
            typename DoFHandler<dim>::active_cell_iterator
            > IteratorTuple;

    typedef SynchronousIterators<IteratorTuple> IteratorPair;

    void initialize_gradient_operator();

    struct InitGradPerTaskData
    {
      unsigned int              d;
      unsigned int              vel_dpc;
      unsigned int              pres_dpc;
      FullMatrix<double>        local_grad;
      std::vector<unsigned int> vel_local_dof_indices;
      std::vector<unsigned int> pres_local_dof_indices;

      InitGradPerTaskData (const unsigned int dd,
                           const unsigned int vdpc,
                           const unsigned int pdpc)
        :
        d(dd),
        vel_dpc (vdpc),
        pres_dpc (pdpc),
        local_grad (vdpc, pdpc),
        vel_local_dof_indices (vdpc),
        pres_local_dof_indices (pdpc)
      {}
    };

    struct InitGradScratchData
    {
      unsigned int  nqp;
      FEValues<dim> fe_val_vel;
      FEValues<dim> fe_val_pres;
      InitGradScratchData (const FE_Q<dim> &fe_v,
                           const FE_Q<dim> &fe_p,
                           const QGauss<dim> &quad,
                           const UpdateFlags flags_v,
                           const UpdateFlags flags_p)
        :
        nqp (quad.size()),
        fe_val_vel (fe_v, quad, flags_v),
        fe_val_pres (fe_p, quad, flags_p)
      {}
      InitGradScratchData (const InitGradScratchData &data)
        :
        nqp (data.nqp),
        fe_val_vel (data.fe_val_vel.get_fe(),
                    data.fe_val_vel.get_quadrature(),
                    data.fe_val_vel.get_update_flags()),
        fe_val_pres (data.fe_val_pres.get_fe(),
                     data.fe_val_pres.get_quadrature(),
                     data.fe_val_pres.get_update_flags())
      {}
    };

    void assemble_one_cell_of_gradient (const IteratorPair &SI,
                                        InitGradScratchData &scratch,
                                        InitGradPerTaskData &data);

    void copy_gradient_local_to_global (const InitGradPerTaskData &data);

    // The same general layout also applies to the following classes and
    // functions implementing the assembly of the advection term:
    void assemble_advection_term();

    struct AdvectionPerTaskData
    {
      FullMatrix<double>        local_advection;
      std::vector<unsigned int> local_dof_indices;
      AdvectionPerTaskData (const unsigned int dpc)
        :
        local_advection (dpc, dpc),
        local_dof_indices (dpc)
      {}
    };

    struct AdvectionScratchData
    {
      unsigned int                 nqp;
      unsigned int                 dpc;
      std::vector< Point<dim> >    u_star_local;
      std::vector< Tensor<1,dim> > grad_u_star;
      std::vector<double>          u_star_tmp;
      FEValues<dim>                fe_val;
      AdvectionScratchData (const FE_Q<dim> &fe,
                            const QGauss<dim> &quad,
                            const UpdateFlags flags)
        :
        nqp (quad.size()),
        dpc (fe.dofs_per_cell),
        u_star_local (nqp),
        grad_u_star (nqp),
        u_star_tmp (nqp),
        fe_val (fe, quad, flags)
      {}

      AdvectionScratchData (const AdvectionScratchData &data)
        :
        nqp (data.nqp),
        dpc (data.dpc),
        u_star_local (nqp),
        grad_u_star (nqp),
        u_star_tmp (nqp),
        fe_val (data.fe_val.get_fe(),
                data.fe_val.get_quadrature(),
                data.fe_val.get_update_flags())
      {}
    };

    void assemble_one_cell_of_advection (const typename DoFHandler<dim>::active_cell_iterator &cell,
                                         AdvectionScratchData &scratch,
                                         AdvectionPerTaskData &data);

    void copy_advection_local_to_global (const AdvectionPerTaskData &data);

    // The final few functions implement the diffusion solve as well as
    // postprocessing the output, including computing the curl of the
    // velocity:
    void diffusion_component_solve (const unsigned int d);

    void output_results (const unsigned int step);

    void assemble_vorticity (const bool reinit_prec);
  };

  // @sect4{ NavierStokesProjection::NavierStokesProjection }

  // In the constructor, we just read all the data from the
  // Data_Storage object that is passed as an argument, verify
  // that the data we read is reasonable and, finally, create the
  // triangulation and load the initial data.
  template <int dim>
  NavierStokesProjection<dim>::NavierStokesProjection(const RunTimeParameters::Data_Storage &data)
    :
    type (data.form),
    deg (data.pressure_degree),
    dt (data.dt),
    t_0 (data.initial_time),
    T (data.final_time),
    Re (data.Reynolds),
    vel_exact (data.initial_time),
    fe_velocity (deg+1),
    fe_pressure (deg),
    dof_handler_velocity (triangulation),
    dof_handler_pressure (triangulation),
    quadrature_pressure (deg+1),
    quadrature_velocity (deg+2),
    vel_max_its (data.vel_max_iterations),
    vel_Krylov_size (data.vel_Krylov_size),
    vel_off_diagonals (data.vel_off_diagonals),
    vel_update_prec (data.vel_update_prec),
    vel_eps (data.vel_eps),
    vel_diag_strength (data.vel_diag_strength)
  {
    if (deg < 1)
      std::cout << " WARNING: The chosen pair of finite element spaces is not stable."
                << std::endl
                << " The obtained results will be nonsense"
                << std::endl;

    AssertThrow (!  ( (dt <= 0.) || (dt > .5*T)), ExcInvalidTimeStep (dt, .5*T));

    create_triangulation_and_dofs (data.n_global_refines);
    initialize();
  }

  // @sect4{ NavierStokesProjection::create_triangulation_and_dofs }

  // The method that creates the triangulation and refines it the needed
  // number of times.  After creating the triangulation, it creates the mesh
  // dependent data, i.e. it distributes degrees of freedom and renumbers
  // them, and initializes the matrices and vectors that we will use.
  template <int dim>
  void
  NavierStokesProjection<dim>::
  create_triangulation_and_dofs (const unsigned int n_refines)
  {
    GridIn<dim> grid_in;
    grid_in.attach_triangulation (triangulation);

    {
      std::string filename = "nsbench2.inp";
      std::ifstream file (filename.c_str());
      Assert (file, ExcFileNotOpen (filename.c_str()));
      grid_in.read_ucd (file);
    }

    std::cout << "Number of refines = " << n_refines
              << std::endl;
    triangulation.refine_global (n_refines);
    std::cout << "Number of active cells: " << triangulation.n_active_cells()
              << std::endl;

    boundary_indicators = triangulation.get_boundary_indicators();

    dof_handler_velocity.distribute_dofs (fe_velocity);
    DoFRenumbering::boost::Cuthill_McKee (dof_handler_velocity);
    dof_handler_pressure.distribute_dofs (fe_pressure);
    DoFRenumbering::boost::Cuthill_McKee (dof_handler_pressure);

    initialize_velocity_matrices();
    initialize_pressure_matrices();
    initialize_gradient_operator();

    pres_n.reinit (dof_handler_pressure.n_dofs());
    pres_n_minus_1.reinit (dof_handler_pressure.n_dofs());
    phi_n.reinit (dof_handler_pressure.n_dofs());
    phi_n_minus_1.reinit (dof_handler_pressure.n_dofs());
    pres_tmp.reinit (dof_handler_pressure.n_dofs());
    for (unsigned int d=0; d<dim; ++d)
      {
        u_n[d].reinit (dof_handler_velocity.n_dofs());
        u_n_minus_1[d].reinit (dof_handler_velocity.n_dofs());
        u_star[d].reinit (dof_handler_velocity.n_dofs());
        force[d].reinit (dof_handler_velocity.n_dofs());
      }
    v_tmp.reinit (dof_handler_velocity.n_dofs());
    rot_u.reinit (dof_handler_velocity.n_dofs());

    std::cout << "dim (X_h) = " << (dof_handler_velocity.n_dofs()*dim)
              << std::endl
              << "dim (M_h) = " << dof_handler_pressure.n_dofs()
              << std::endl
              << "Re        = " << Re
              << std::endl
              << std::endl;
  }

  // @sect4{ NavierStokesProjection::initialize }

  // This method creates the constant matrices and loads the initial data
  template <int dim>
  void
  NavierStokesProjection<dim>::initialize()
  {
    vel_Laplace_plus_Mass = 0.;
    vel_Laplace_plus_Mass.add (1./Re, vel_Laplace);
    vel_Laplace_plus_Mass.add (1.5/dt, vel_Mass);

    EquationData::Pressure<dim> pres (t_0);
    VectorTools::interpolate (dof_handler_pressure, pres, pres_n_minus_1);
    pres.advance_time (dt);
    VectorTools::interpolate (dof_handler_pressure, pres, pres_n);
    phi_n = 0.;
    phi_n_minus_1 = 0.;
    for (unsigned int d=0; d<dim; ++d)
      {
        vel_exact.set_time (t_0);
        vel_exact.set_component(d);
        VectorTools::interpolate (dof_handler_velocity, ZeroFunction<dim>(), u_n_minus_1[d]);
        vel_exact.advance_time (dt);
        VectorTools::interpolate (dof_handler_velocity, ZeroFunction<dim>(), u_n[d]);
      }
  }

  // @sect4{ The NavierStokesProjection::initialize_*_matrices methods }

  // In this set of methods we initialize the sparsity patterns, the
  // constraints (if any) and assemble the matrices that do not depend on the
  // timestep dt. Note that for the Laplace and mass matrices, we
  // can use functions in the library that do this. Because the expensive
  // operations of this function -- creating the two matrices -- are entirely
  // independent, we could in principle mark them as tasks that can be worked
  // on in %parallel using the Threads::new_task functions. We won't do that
  // here since these functions internally already are parallelized, and in
  // particular because the current function is only called once per program
  // run and so does not incur a cost in each time step. The necessary
  // modifications would be quite straightforward, however.
  template <int dim>
  void
  NavierStokesProjection<dim>::initialize_velocity_matrices()
  {
    sparsity_pattern_velocity.reinit (dof_handler_velocity.n_dofs(),
                                      dof_handler_velocity.n_dofs(),
                                      dof_handler_velocity.max_couplings_between_dofs());
    DoFTools::make_sparsity_pattern (dof_handler_velocity,
                                     sparsity_pattern_velocity);
    sparsity_pattern_velocity.compress();

    vel_Laplace_plus_Mass.reinit (sparsity_pattern_velocity);
    for (unsigned int d=0; d<dim; ++d)
      vel_it_matrix[d].reinit (sparsity_pattern_velocity);
    vel_Mass.reinit (sparsity_pattern_velocity);
    vel_Laplace.reinit (sparsity_pattern_velocity);
    vel_Advection.reinit (sparsity_pattern_velocity);

    MatrixCreator::create_mass_matrix (dof_handler_velocity,
                                       quadrature_velocity,
                                       vel_Mass);
    MatrixCreator::create_laplace_matrix (dof_handler_velocity,
                                          quadrature_velocity,
                                          vel_Laplace);
  }

  // The initialization of the matrices that act on the pressure space is
  // similar to the ones that act on the velocity space.
  template <int dim>
  void
  NavierStokesProjection<dim>::initialize_pressure_matrices()
  {
    sparsity_pattern_pressure.reinit (dof_handler_pressure.n_dofs(), dof_handler_pressure.n_dofs(),
                                      dof_handler_pressure.max_couplings_between_dofs());
    DoFTools::make_sparsity_pattern (dof_handler_pressure, sparsity_pattern_pressure);

    sparsity_pattern_pressure.compress();

    pres_Laplace.reinit (sparsity_pattern_pressure);
    pres_iterative.reinit (sparsity_pattern_pressure);
    pres_Mass.reinit (sparsity_pattern_pressure);

    MatrixCreator::create_laplace_matrix (dof_handler_pressure,
                                          quadrature_pressure,
                                          pres_Laplace);
    MatrixCreator::create_mass_matrix (dof_handler_pressure,
                                       quadrature_pressure,
                                       pres_Mass);
  }

  // For the gradient operator, we start by initializing the sparsity pattern
  // and compressing it.  It is important to notice here that the gradient
  // operator acts from the pressure space into the velocity space, so we have
  // to deal with two different finite element spaces. To keep the loops
  // synchronized, we use the typedef's that we have defined
  // before, namely PairedIterators and
  // IteratorPair.
  template <int dim>
  void
  NavierStokesProjection<dim>::initialize_gradient_operator()
  {
    sparsity_pattern_pres_vel.reinit (dof_handler_velocity.n_dofs(),
                                      dof_handler_pressure.n_dofs(),
                                      dof_handler_velocity.max_couplings_between_dofs());
    DoFTools::make_sparsity_pattern (dof_handler_velocity,
                                     dof_handler_pressure,
                                     sparsity_pattern_pres_vel);
    sparsity_pattern_pres_vel.compress();

    InitGradPerTaskData per_task_data (0, fe_velocity.dofs_per_cell,
                                       fe_pressure.dofs_per_cell);
    InitGradScratchData scratch_data (fe_velocity,
                                      fe_pressure,
                                      quadrature_velocity,
                                      update_gradients | update_JxW_values,
                                      update_values);

    for (unsigned int d=0; d<dim; ++d)
      {
        pres_Diff[d].reinit (sparsity_pattern_pres_vel);
        per_task_data.d = d;
        WorkStream::run (IteratorPair (IteratorTuple (dof_handler_velocity.begin_active(),
                                                      dof_handler_pressure.begin_active()
                                                     )
                                      ),
                         IteratorPair (IteratorTuple (dof_handler_velocity.end(),
                                                      dof_handler_pressure.end()
                                                     )
                                      ),
                         *this,
                         &NavierStokesProjection<dim>::assemble_one_cell_of_gradient,
                         &NavierStokesProjection<dim>::copy_gradient_local_to_global,
                         scratch_data,
                         per_task_data
                        );
      }
  }

  template <int dim>
  void
  NavierStokesProjection<dim>::
  assemble_one_cell_of_gradient (const IteratorPair &SI,
                                 InitGradScratchData &scratch,
                                 InitGradPerTaskData &data)
  {
    scratch.fe_val_vel.reinit (std_cxx1x::get<0> (SI.iterators));
    scratch.fe_val_pres.reinit (std_cxx1x::get<1> (SI.iterators));

    std_cxx1x::get<0> (SI.iterators)->get_dof_indices (data.vel_local_dof_indices);
    std_cxx1x::get<1> (SI.iterators)->get_dof_indices (data.pres_local_dof_indices);

    data.local_grad = 0.;
    for (unsigned int q=0; q<scratch.nqp; ++q)
      {
        for (unsigned int i=0; i<data.vel_dpc; ++i)
          for (unsigned int j=0; j<data.pres_dpc; ++j)
            data.local_grad (i, j) += -scratch.fe_val_vel.JxW(q) *
                                      scratch.fe_val_vel.shape_grad (i, q)[data.d] *
                                      scratch.fe_val_pres.shape_value (j, q);
      }
  }

  template <int dim>
  void
  NavierStokesProjection<dim>::
  copy_gradient_local_to_global(const InitGradPerTaskData &data)
  {
    for (unsigned int i=0; i<data.vel_dpc; ++i)
      for (unsigned int j=0; j<data.pres_dpc; ++j)
        pres_Diff[data.d].add (data.vel_local_dof_indices[i], data.pres_local_dof_indices[j],
                               data.local_grad (i, j) );
  }

  // @sect4{ NavierStokesProjection::run }

  // This is the time marching function, which starting at t_0
  // advances in time using the projection method with time step
  // dt until T.
  //
  // Its second parameter, verbose indicates whether the function
  // should output information what it is doing at any given moment: for
  // example, it will say whether we are working on the diffusion, projection
  // substep; updating preconditioners etc. Rather than implementing this
  // output using code like
  // @code
  //   if (verbose) std::cout << "something";
  // @endcode
  // we use the ConditionalOStream class to do that for us. That
  // class takes an output stream and a condition that indicates whether the
  // things you pass to it should be passed through to the given output
  // stream, or should just be ignored. This way, above code simply becomes
  // @code
  //   verbose_cout << "something";
  // @endcode
  // and does the right thing in either case.
  template <int dim>
  void
  NavierStokesProjection<dim>::run (const bool verbose,
                                    const unsigned int output_interval)
  {
    ConditionalOStream verbose_cout (std::cout, verbose);

    const unsigned int n_steps =  static_cast<unsigned int>((T - t_0)/dt);
    vel_exact.set_time (2.*dt);
    output_results(1);
    for (unsigned int n = 2; n<=n_steps; ++n)
      {
        if (n % output_interval == 0)
          {
            verbose_cout << "Plotting Solution" << std::endl;
            output_results(n);
          }
        std::cout << "Step = " << n << " Time = " << (n*dt) << std::endl;
        verbose_cout << "  Interpolating the velocity " << std::endl;

        interpolate_velocity();
        verbose_cout << "  Diffusion Step" << std::endl;
        if (n % vel_update_prec == 0)
          verbose_cout << "    With reinitialization of the preconditioner"
                       << std::endl;
        diffusion_step ((n%vel_update_prec == 0) || (n == 2));
        verbose_cout << "  Projection Step" << std::endl;
        projection_step ( (n == 2));
        verbose_cout << "  Updating the Pressure" << std::endl;
        update_pressure ( (n == 2));
        vel_exact.advance_time(dt);
      }
    output_results (n_steps);
  }

  template <int dim>
  void
  NavierStokesProjection<dim>::interpolate_velocity()
  {
    for (unsigned int d=0; d<dim; ++d)
      u_star[d].equ (2., u_n[d], -1, u_n_minus_1[d]);
  }

  // @sect4{NavierStokesProjection::diffusion_step}

  // The implementation of a diffusion step. Note that the expensive operation
  // is the diffusion solve at the end of the function, which we have to do
  // once for each velocity component. To accellerate things a bit, we allow
  // to do this in %parallel, using the Threads::new_task function which makes
  // sure that the dim solves are all taken care of and are
  // scheduled to available processors: if your machine has more than one
  // processor core and no other parts of this program are using resources
  // currently, then the diffusion solves will run in %parallel. On the other
  // hand, if your system has only one processor core then running things in
  // %parallel would be inefficient (since it leads, for example, to cache
  // congestion) and things will be executed sequentially.
  template <int dim>
  void
  NavierStokesProjection<dim>::diffusion_step (const bool reinit_prec)
  {
    pres_tmp.equ (-1., pres_n, -4./3., phi_n, 1./3., phi_n_minus_1);

    assemble_advection_term();

    for (unsigned int d=0; d<dim; ++d)
      {
        force[d] = 0.;
        v_tmp.equ (2./dt,u_n[d],-.5/dt,u_n_minus_1[d]);
        vel_Mass.vmult_add (force[d], v_tmp);

        pres_Diff[d].vmult_add (force[d], pres_tmp);
        u_n_minus_1[d] = u_n[d];

        vel_it_matrix[d].copy_from (vel_Laplace_plus_Mass);
        vel_it_matrix[d].add (1., vel_Advection);

        vel_exact.set_component(d);
        boundary_values.clear();
        for (std::vector<types::boundary_id>::const_iterator
             boundaries = boundary_indicators.begin();
             boundaries != boundary_indicators.end();
             ++boundaries)
          {
            switch (*boundaries)
              {
              case 1:
                VectorTools::
                interpolate_boundary_values (dof_handler_velocity,
                                             *boundaries,
                                             ZeroFunction<dim>(),
                                             boundary_values);
                break;
              case 2:
                VectorTools::
                interpolate_boundary_values (dof_handler_velocity,
                                             *boundaries,
                                             vel_exact,
                                             boundary_values);
                break;
              case 3:
                if (d != 0)
                  VectorTools::
                  interpolate_boundary_values (dof_handler_velocity,
                                               *boundaries,
                                               ZeroFunction<dim>(),
                                               boundary_values);
                break;
              case 4:
                VectorTools::
                interpolate_boundary_values (dof_handler_velocity,
                                             *boundaries,
                                             ZeroFunction<dim>(),
                                             boundary_values);
                break;
              default:
                Assert (false, ExcNotImplemented());
              }
          }
        MatrixTools::apply_boundary_values (boundary_values,
                                            vel_it_matrix[d],
                                            u_n[d],
                                            force[d]);
      }

    Threads::TaskGroup<void> tasks;
    for (unsigned int d=0; d<dim; ++d)
      {
        if (reinit_prec)
          prec_velocity[d].initialize (vel_it_matrix[d],
                                       SparseILU<double>::
                                       AdditionalData (vel_diag_strength,
                                                       vel_off_diagonals));
        tasks += Threads::new_task (&NavierStokesProjection<dim>::
                                    diffusion_component_solve,
                                    *this, d);
      }
    tasks.join_all();
  }

  template <int dim>
  void
  NavierStokesProjection<dim>::diffusion_component_solve (const unsigned int d)
  {
    SolverControl solver_control (vel_max_its, vel_eps*force[d].l2_norm());
    SolverGMRES<> gmres (solver_control,
                         SolverGMRES<>::AdditionalData (vel_Krylov_size));
    gmres.solve (vel_it_matrix[d], u_n[d], force[d], prec_velocity[d]);
  }

  // @sect4{ The NavierStokesProjection::assemble_advection_term method and related}

  // The following few functions deal with assembling the advection terms,
  // which is the part of the system matrix for the diffusion step that
  // changes at every time step. As mentioned above, we will run the assembly
  // loop over all cells in %parallel, using the WorkStream class and other
  // facilities as described in the documentation module on @ref threads.
  template <int dim>
  void
  NavierStokesProjection<dim>::assemble_advection_term()
  {
    vel_Advection = 0.;
    AdvectionPerTaskData data (fe_velocity.dofs_per_cell);
    AdvectionScratchData scratch (fe_velocity, quadrature_velocity,
                                  update_values |
                                  update_JxW_values |
                                  update_gradients);
    WorkStream::run (dof_handler_velocity.begin_active(),
                     dof_handler_velocity.end(), *this,
                     &NavierStokesProjection<dim>::assemble_one_cell_of_advection,
                     &NavierStokesProjection<dim>::copy_advection_local_to_global,
                     scratch,
                     data);
  }

  template <int dim>
  void
  NavierStokesProjection<dim>::
  assemble_one_cell_of_advection(const typename DoFHandler<dim>::active_cell_iterator &cell,
                                 AdvectionScratchData &scratch,
                                 AdvectionPerTaskData &data)
  {
    scratch.fe_val.reinit(cell);
    cell->get_dof_indices (data.local_dof_indices);
    for (unsigned int d=0; d<dim; ++d)
      {
        scratch.fe_val.get_function_values (u_star[d], scratch.u_star_tmp);
        for (unsigned int q=0; q<scratch.nqp; ++q)
          scratch.u_star_local[q](d) = scratch.u_star_tmp[q];
      }

    for (unsigned int d=0; d<dim; ++d)
      {
        scratch.fe_val.get_function_gradients (u_star[d], scratch.grad_u_star);
        for (unsigned int q=0; q<scratch.nqp; ++q)
          {
            if (d==0)
              scratch.u_star_tmp[q] = 0.;
            scratch.u_star_tmp[q] += scratch.grad_u_star[q][d];
          }
      }

    data.local_advection = 0.;
    for (unsigned int q=0; q<scratch.nqp; ++q)
      for (unsigned int i=0; i<scratch.dpc; ++i)
        for (unsigned int j=0; j<scratch.dpc; ++j)
          data.local_advection(i,j) += (scratch.u_star_local[q] *
                                        scratch.fe_val.shape_grad (j, q) *
                                        scratch.fe_val.shape_value (i, q)
                                        +
                                        0.5 *
                                        scratch.u_star_tmp[q] *
                                        scratch.fe_val.shape_value (i, q) *
                                        scratch.fe_val.shape_value (j, q))
                                       *
                                       scratch.fe_val.JxW(q) ;
  }

  template <int dim>
  void
  NavierStokesProjection<dim>::
  copy_advection_local_to_global(const AdvectionPerTaskData &data)
  {
    for (unsigned int i=0; i<fe_velocity.dofs_per_cell; ++i)
      for (unsigned int j=0; j<fe_velocity.dofs_per_cell; ++j)
        vel_Advection.add (data.local_dof_indices[i],
                           data.local_dof_indices[j],
                           data.local_advection(i,j));
  }

  // @sect4{NavierStokesProjection::projection_step}

  // This implements the projection step:
  template <int dim>
  void
  NavierStokesProjection<dim>::projection_step (const bool reinit_prec)
  {
    pres_iterative.copy_from (pres_Laplace);

    pres_tmp = 0.;
    for (unsigned d=0; d<dim; ++d)
      pres_Diff[d].Tvmult_add (pres_tmp, u_n[d]);

    phi_n_minus_1 = phi_n;

    static std::map<unsigned int, double> bval;
    if (reinit_prec)
      VectorTools::interpolate_boundary_values (dof_handler_pressure, 3,
                                                ZeroFunction<dim>(), bval);

    MatrixTools::apply_boundary_values (bval, pres_iterative, phi_n, pres_tmp);

    if (reinit_prec)
      prec_pres_Laplace.initialize(pres_iterative,
                                   SparseILU<double>::AdditionalData (vel_diag_strength,
                                       vel_off_diagonals) );

    SolverControl solvercontrol (vel_max_its, vel_eps*pres_tmp.l2_norm());
    SolverCG<> cg (solvercontrol);
    cg.solve (pres_iterative, phi_n, pres_tmp, prec_pres_Laplace);

    phi_n *= 1.5/dt;
  }

  // @sect4{ NavierStokesProjection::update_pressure }

  // This is the pressure update step of the projection method. It implements
  // the standard formulation of the method, that is @f[ p^{n+1} = p^n +
  // \phi^{n+1}, @f] or the rotational form, which is @f[ p^{n+1} = p^n +
  // \phi^{n+1} - \frac{1}{Re} \nabla\cdot u^{n+1}.  @f]
  template <int dim>
  void
  NavierStokesProjection<dim>::update_pressure (const bool reinit_prec)
  {
    pres_n_minus_1 = pres_n;
    switch (type)
      {
      case RunTimeParameters::METHOD_STANDARD:
        pres_n += phi_n;
        break;
      case RunTimeParameters::METHOD_ROTATIONAL:
        if (reinit_prec)
          prec_mass.initialize (pres_Mass);
        pres_n = pres_tmp;
        prec_mass.solve (pres_n);
        pres_n.sadd(1./Re, 1., pres_n_minus_1, 1., phi_n);
        break;
      default:
        Assert (false, ExcNotImplemented());
      };
  }

  // @sect4{ NavierStokesProjection::output_results }

  // This method plots the current solution. The main difficulty is that we
  // want to create a single output file that contains the data for all
  // velocity components, the pressure, and also the vorticity of the flow. On
  // the other hand, velocities and the pressure live on separate DoFHandler
  // objects, and so can't be written to the same file using a single DataOut
  // object. As a consequence, we have to work a bit harder to get the various
  // pieces of data into a single DoFHandler object, and then use that to
  // drive graphical output.
  //
  // We will not elaborate on this process here, but rather refer to step-31
  // and step-32, where a similar procedure is used (and is documented) to
  // create a joint DoFHandler object for all variables.
  //
  // Let us also note that we here compute the vorticity as a scalar quantity
  // in a separate function, using the $L^2$ projection of the quantity
  // $\text{curl} u$ onto the finite element space used for the components of
  // the velocity. In principle, however, we could also have computed as a
  // pointwise quantity from the velocity, and do so through the
  // DataPostprocessor mechanism discussed in step-29 and step-33.
  template <int dim>
  void NavierStokesProjection<dim>::output_results (const unsigned int step)
  {
    assemble_vorticity ( (step == 1));
    const FESystem<dim> joint_fe (fe_velocity, dim,
                                  fe_pressure, 1,
                                  fe_velocity, 1);
    DoFHandler<dim> joint_dof_handler (triangulation);
    joint_dof_handler.distribute_dofs (joint_fe);
    Assert (joint_dof_handler.n_dofs() ==
            ((dim + 1)*dof_handler_velocity.n_dofs() +
             dof_handler_pressure.n_dofs()),
            ExcInternalError());
    static Vector<double> joint_solution (joint_dof_handler.n_dofs());
    std::vector<unsigned int> loc_joint_dof_indices (joint_fe.dofs_per_cell),
        loc_vel_dof_indices (fe_velocity.dofs_per_cell),
        loc_pres_dof_indices (fe_pressure.dofs_per_cell);
    typename DoFHandler<dim>::active_cell_iterator
    joint_cell = joint_dof_handler.begin_active(),
    joint_endc = joint_dof_handler.end(),
    vel_cell   = dof_handler_velocity.begin_active(),
    pres_cell  = dof_handler_pressure.begin_active();
    for (; joint_cell != joint_endc; ++joint_cell, ++vel_cell, ++pres_cell)
      {
        joint_cell->get_dof_indices (loc_joint_dof_indices);
        vel_cell->get_dof_indices (loc_vel_dof_indices),
                 pres_cell->get_dof_indices (loc_pres_dof_indices);
        for (unsigned int i=0; i<joint_fe.dofs_per_cell; ++i)
          switch (joint_fe.system_to_base_index(i).first.first)
            {
            case 0:
              Assert (joint_fe.system_to_base_index(i).first.second < dim,
                      ExcInternalError());
              joint_solution (loc_joint_dof_indices[i]) =
                u_n[ joint_fe.system_to_base_index(i).first.second ]
                (loc_vel_dof_indices[ joint_fe.system_to_base_index(i).second ]);
              break;
            case 1:
              Assert (joint_fe.system_to_base_index(i).first.second == 0,
                      ExcInternalError());
              joint_solution (loc_joint_dof_indices[i]) =
                pres_n (loc_pres_dof_indices[ joint_fe.system_to_base_index(i).second ]);
              break;
            case 2:
              Assert (joint_fe.system_to_base_index(i).first.second == 0,
                      ExcInternalError());
              joint_solution (loc_joint_dof_indices[i]) =
                rot_u (loc_vel_dof_indices[ joint_fe.system_to_base_index(i).second ]);
              break;
            default:
              Assert (false, ExcInternalError());
            }
      }
    std::vector<std::string> joint_solution_names (dim, "v");
    joint_solution_names.push_back ("p");
    joint_solution_names.push_back ("rot_u");
    DataOut<dim> data_out;
    data_out.attach_dof_handler (joint_dof_handler);
    std::vector< DataComponentInterpretation::DataComponentInterpretation >
    component_interpretation (dim+2,
                              DataComponentInterpretation::component_is_part_of_vector);
    component_interpretation[dim]
      = DataComponentInterpretation::component_is_scalar;
    component_interpretation[dim+1]
      = DataComponentInterpretation::component_is_scalar;
    data_out.add_data_vector (joint_solution,
                              joint_solution_names,
                              DataOut<dim>::type_dof_data,
                              component_interpretation);
    data_out.build_patches (deg + 1);
    std::ofstream output (("solution-" +
                           Utilities::int_to_string (step, 5) +
                           ".vtk").c_str());
    data_out.write_vtk (output);
  }

  // Following is the helper function that computes the vorticity by
  // projecting the term $\text{curl} u$ onto the finite element space used
  // for the components of the velocity. The function is only called whenever
  // we generate graphical output, so not very often, and as a consequence we
  // didn't bother parallelizing it using the WorkStream concept as we do for
  // the other assembly functions. That should not be overly complicated,
  // however, if needed. Moreover, the implementation that we have here only
  // works for 2d, so we bail if that is not the case.
  template <int dim>
  void NavierStokesProjection<dim>::assemble_vorticity (const bool reinit_prec)
  {
    Assert (dim == 2, ExcNotImplemented());
    if (reinit_prec)
      prec_vel_mass.initialize (vel_Mass);

    FEValues<dim> fe_val_vel (fe_velocity, quadrature_velocity,
                              update_gradients |
                              update_JxW_values |
                              update_values);
    const unsigned int dpc = fe_velocity.dofs_per_cell,
                       nqp = quadrature_velocity.size();
    std::vector<unsigned int> ldi (dpc);
    Vector<double> loc_rot (dpc);

    std::vector< Tensor<1,dim> > grad_u1 (nqp), grad_u2 (nqp);
    rot_u = 0.;

    typename DoFHandler<dim>::active_cell_iterator
    cell = dof_handler_velocity.begin_active(),
    end  = dof_handler_velocity.end();
    for (; cell != end; ++cell)
      {
        fe_val_vel.reinit (cell);
        cell->get_dof_indices (ldi);
        fe_val_vel.get_function_gradients (u_n[0], grad_u1);
        fe_val_vel.get_function_gradients (u_n[1], grad_u2);
        loc_rot = 0.;
        for (unsigned int q=0; q<nqp; ++q)
          for (unsigned int i=0; i<dpc; ++i)
            loc_rot(i) += (grad_u2[q][0] - grad_u1[q][1]) *
                          fe_val_vel.shape_value (i, q) *
                          fe_val_vel.JxW(q);

        for (unsigned int i=0; i<dpc; ++i)
          rot_u (ldi[i]) += loc_rot(i);
      }

    prec_vel_mass.solve (rot_u);
  }
}

// @sect3{ The main function }

// The main function looks very much like in all the other tutorial programs,
// so there is little to comment on here:
int main()
{
  try
    {
      using namespace dealii;
      using namespace Step35;

      RunTimeParameters::Data_Storage data;
      data.read_data ("parameter-file.prm");

      deallog.depth_console (data.verbose ? 2 : 0);

      NavierStokesProjection<2> test (data);
      test.run (data.verbose, data.output_interval);
    }
  catch (std::exception &exc)
    {
      std::cerr << std::endl << std::endl
                << "----------------------------------------------------"
                << std::endl;
      std::cerr << "Exception on processing: " << std::endl
                << exc.what() << std::endl
                << "Aborting!" << std::endl
                << "----------------------------------------------------"
                << std::endl;
      return 1;
    }
  catch (...)
    {
      std::cerr << std::endl << std::endl
                << "----------------------------------------------------"
                << std::endl;
      std::cerr << "Unknown exception!" << std::endl
                << "Aborting!" << std::endl
                << "----------------------------------------------------"
                << std::endl;
      return 1;
    }
  std::cout << "----------------------------------------------------"
            << std::endl
            << "Apparently everything went fine!"
            << std::endl
            << "Don't forget to brush your teeth :-)"
            << std::endl << std::endl;
  return 0;
}


/* CirKit: A circuit toolkit
 * Copyright (C) 2009-2015  University of Bremen
 * Copyright (C) 2015-2017  EPFL
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, 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.
 */

#include "spectral_canonization.hpp"

#include <iostream>
#include <numeric>

#include <boost/format.hpp>

#include <core/utils/range_utils.hpp>
#include <core/utils/timer.hpp>

namespace cirkit
{

/******************************************************************************
 * Types                                                                      *
 ******************************************************************************/

/******************************************************************************
 * Private functions                                                          *
 ******************************************************************************/

tt make_sum( const boost::dynamic_bitset<>& mask )
{
  boost::dynamic_bitset<> sum( std::max<unsigned>( 1 << mask.size(), 64u ) );
  foreach_bit( mask, [&sum]( unsigned pos ) {
      sum ^= tt_nth_var( pos );
    } );
  if ( mask.size() < 6u )
  {
    tt_shrink( sum, mask.size() );
  }

  return sum;
}

std::vector<int> rademacher_walsh_spectrum( const tt& func )
{
  const auto n = tt_num_vars( func );
  std::vector<int> spectrum;

  foreach_bitset( n, [n, &func, &spectrum]( const boost::dynamic_bitset<>& bs ) {
      const auto row = make_sum( bs );
      spectrum.push_back( ( 1 << n ) - 2 * ( row ^ func ).count() );
    } );

  return spectrum;
}

std::vector<int> autocorrelation_spectrum( const tt& func )
{
  const auto n = tt_num_vars( func );
  std::vector<int> spectrum;

  foreach_bitset( n, [n, &func, &spectrum]( const boost::dynamic_bitset<>& bs ) {
      if ( bs.none() )
      {
        spectrum.push_back( 1 << n );
      }
      else
      {
        boost::dynamic_bitset<> fs( func.size() );
        foreach_bitset( n, [&bs, &fs, &func]( const boost::dynamic_bitset<>& bs2 ) {
            fs.set( bs2.to_ulong(), func.test( ( bs2 ^ bs ).to_ulong() ) );
          } );
        spectrum.push_back( ( 1 << n ) - 2 * ( fs ^ func ).count() );
      }
    } );
  return spectrum;
}

void print_spectrum( const std::vector<int>& spectrum, unsigned nvars )
{
  for ( auto i = 0u; i < spectrum.size(); ++i )
  {
    std::cout << boost::dynamic_bitset<>( nvars, i ) << boost::format( " %5d" ) % spectrum[i] << std::endl;
  }
}

void print_spectrum_ordered( const std::vector<int>& spectrum, unsigned nvars )
{
  if ( nvars == 4u )
  {
    for ( auto i : {0u,16u,1u,2u,4u,8u,16u,3u,5u,9u,6u,10u,12u,16u,7u,11u,13u,14u,16u,15u} )
    {
      if ( i == 16u )
      {
        std::cout << " |";
      }
      else
      {
        std::cout << " " << spectrum[i];
      }
    }
    std::cout << std::endl;
  }
  else
  {
    std::cout << any_join( spectrum, " " ) << std::endl;
  }
}

int compare_abs( int a, int b )
{
  if ( abs( a ) < abs( b ) )
  {
    return -1;
  }
  else if ( abs( a ) > abs( b ) )
  {
    return 1;
  }
  else if ( a < b )
  {
    return -1;
  }
  else if ( a > b )
  {
    return 1;
  }
  else
  {
    return 0;
  }
}

/******************************************************************************
 * Spectral operations                                                        *
 ******************************************************************************/

void operation1( std::vector<int>& spectrum, tt& func, const std::vector<unsigned>& perm )
{
  std::vector<int> spectrum_p( spectrum.size() );
  tt func_p = func;

  for ( auto row = 0u; row < spectrum.size(); ++row )
  {
    boost::dynamic_bitset<> idx( perm.size(), row ), idx_p( perm.size() );
    for ( auto i = 0u; i < perm.size(); ++i )
    {
      idx_p[i] = idx[perm[i]];
    }

    spectrum_p[idx_p.to_ulong()] = spectrum[row];
    func_p.set( idx_p.to_ulong(), func.test( row ) );
  }

  spectrum = spectrum_p;
  func = func_p;
}

void operation2( std::vector<int>& spectrum, tt& func, unsigned var )
{
  func = tt_flip( func, var );

  for ( auto row = 1u; row < spectrum.size(); ++row )
  {
    if ( ( row >> var ) & 1 )
    {
      spectrum[row] = -spectrum[row];
    }
  }
}

void operation3( std::vector<int>& spectrum, tt& func )
{
  func.flip();

  std::transform( spectrum.begin(), spectrum.end(), spectrum.begin(), std::negate<int>() );
}

void operation4( std::vector<int>& spectrum, tt& func, unsigned var, unsigned diff )
{
  for ( auto row = 0u; row < spectrum.size(); ++row )
  {
    if ( ( row >> var ) & 1 )
    {
      auto row2 = row ^ diff;
      if ( row < row2 )
      {
        std::swap( spectrum[row], spectrum[row2] );
      }
    }
  }

  const auto nvars = tt_num_vars( func );
  boost::dynamic_bitset<> mask( nvars, diff );
  const auto varbs = onehot_bitset( nvars, var );
  const auto inc_mask = ~( varbs | mask );

  //std::cout << "var = " << var << ", mask = " << mask << ", diff = " << diff << std::endl;
  //std::cout << "func b " << func << " " << func[15] << " " << func[14] << std::endl;

  auto start = varbs;
  do {
    //std::cout << start << std::endl;

    auto pattern = start;
    do {
      //std::cout << " " << pattern;
      if ( ( pattern & mask ).count() % 2 )
      {
        const auto r1 = pattern.to_ulong();
        const auto r2 = ( pattern ^ varbs ).to_ulong();

        const auto tmp = func.test( r1 );
        func.set( r1, func.test( r2 ) );
        func.set( r2, tmp );
        //std::cout << " *, swap with " << ( pattern ^ varbs ) << " (" << r1 << " with " << r2 << ")";
      }
      //std::cout << std::endl;
      inc_pos( pattern, mask );
    } while ( ( pattern & mask ).any() );

    inc_pos( start, inc_mask );
  } while ( ( start & inc_mask ).any() );

  //std::cout << "func a " << func << std::endl;
}

void operation5( std::vector<int>& spectrum, tt& func, unsigned row )
{
  const auto nvars = tt_num_vars( func );
  boost::dynamic_bitset<> mask( nvars, row );

  func ^= make_sum( mask );

  for ( auto i = 0u; i < spectrum.size(); ++i )
  {
    const auto j = i ^ row;
    if ( i < j )
    {
      std::swap( spectrum[i], spectrum[j] );
    }
  }
}

/******************************************************************************
 * Canonization operations                                                    *
 ******************************************************************************/

void maximize_zero_coefficient( std::vector<int>& spectrum, tt& func )
{
  const auto max_coeff = std::max_element( spectrum.begin(), spectrum.end(), []( int a, int b ) { return compare_abs( a, b ) == -1; } );

  if ( *max_coeff < 0 )
  {
    operation3( spectrum, func );
  }

  const auto pos = std::distance( spectrum.begin(), max_coeff );

  if ( pos != 0 )
  {
    operation5( spectrum, func, pos );
  }
}

void minimize_order( std::vector<int>& spectrum, tt& func )
{
  const auto n = tt_num_vars( func );

  for ( auto var = 0u; var < n; ++var )
  {
    const auto row = 1u << var;
    /* sweep through the other ones */
    auto best_row = row;
    auto best_val = spectrum[row];
    for ( auto row2 = row + 1u; row2 < spectrum.size(); ++row2 )
    {
      if ( ( row & row2 ) != row ) continue;

      if ( compare_abs( spectrum[row2], best_val ) == 1 )
      {
        best_val = spectrum[row2];
        best_row = row2;
      }
    }

    /* update values */
    const auto diff = row ^ best_row;
    if ( diff )
    {
      operation4( spectrum, func, var, diff );
    }
  }
}

void sort_inputs( std::vector<int>& spectrum, tt& func )
{
  auto cmp = [&spectrum]( unsigned i1, unsigned i2 ) {
    switch ( compare_abs( spectrum[1 << i1], spectrum[1 << i2] ) )
    {
    case -1: return false;
    case 1: return true;
    default:
    return false;
    }
  };

  auto cmp4 = [&spectrum]( unsigned i1, unsigned i2 ) {
    switch ( compare_abs( spectrum[1 << i1], spectrum[1 << i2] ) )
    {
    case -1: return false;
    case 1: return true;
    default:
    {
      for ( auto row : {3, 5, 9, 6, 10, 12} )
      {
        if ( ( ( row >> i1 ) & 1 ) && !( ( row >> i2 ) & 1 ) )
        {
          auto row2 = ( row ^ ( 1 << i1 ) ) | ( 1 << i2 );
          switch ( compare_abs( spectrum[row], spectrum[row2] ) )
          {
          case -1: return true;
          case 1: return false;
          }
        }
        else if ( !( ( row >> i1 ) & 1 ) && ( ( row >> i2 ) & 1 ) )
        {
          auto row2 = ( row ^ ( 1 << i2 ) ) | ( 1 << i1 );
          switch ( compare_abs( spectrum[row], spectrum[row2] ) )
          {
          case -1: return true;
          case 1: return false;
          }
        }
      }
      return false;
    }
    }
  };

  const auto nvars = tt_num_vars( func );
  std::vector<unsigned> proj( nvars );
  std::iota( proj.begin(), proj.end(), 0u );

  if ( nvars <= 3 )
  {
    std::sort( proj.begin(), proj.end(), cmp );
  }
  else
  {
    std::sort( proj.begin(), proj.end(), cmp4 );
  }

  operation1( spectrum, func, proj );
}

void invert_inputs( std::vector<int>& spectrum, tt& func )
{
  const auto nvars = tt_num_vars( func );

  for ( auto i = 0u; i < nvars; ++i )
  {
    if ( spectrum[1 << i] < 0 )
    {
      operation2( spectrum, func, i );
    }
  }
}

/******************************************************************************
 * Public functions                                                           *
 ******************************************************************************/

tt spectral_canonization( const tt& func, const properties::ptr& settings, const properties::ptr& statistics )
{
  const auto verbose = get( settings, "verbose", false );
  const auto very_verbose = get( settings, "very_verbose", false );

  properties_timer t( statistics );

  const auto nvars = tt_num_vars( func );
  auto spectrum = rademacher_walsh_spectrum( func );
  set( statistics, "spectrum_init", spectrum );
  auto cfunc = func;

  if ( verbose )
  {
    std::cout << "AC " << any_join( autocorrelation_spectrum( func ), " " ) << std::endl;
    std::cout << "before" << std::endl;
    if ( very_verbose ) print_spectrum( spectrum, nvars );
    print_spectrum_ordered( spectrum, nvars );
  }

  maximize_zero_coefficient( spectrum, cfunc );

  if ( verbose )
  {
    std::cout << "after step 1" << std::endl;
    if ( very_verbose ) print_spectrum( spectrum, nvars );
    print_spectrum_ordered( spectrum, nvars );
    std::cout << cfunc << std::endl;
  }

  minimize_order( spectrum, cfunc );

  if ( verbose )
  {
    std::cout << "after step 2" << std::endl;
    if ( very_verbose ) print_spectrum( spectrum, nvars );
    print_spectrum_ordered( spectrum, nvars );
    std::cout << cfunc << std::endl;
  }

  invert_inputs( spectrum, cfunc );

  if ( verbose )
  {
    std::cout << "after step 3" << std::endl;
    if ( very_verbose ) print_spectrum( spectrum, nvars );
    print_spectrum_ordered( spectrum, nvars );
    std::cout << cfunc << std::endl;
  }

  sort_inputs( spectrum, cfunc );

  if ( verbose )
  {
    std::cout << "after step 4" << std::endl;
    if ( very_verbose ) print_spectrum( spectrum, nvars );
    print_spectrum_ordered( spectrum, nvars );
    std::cout << cfunc << std::endl;
  }

  set( statistics, "spectrum_final", spectrum );
  set( statistics, "class", get_spectral_class( func ) );

  // if ( !( ( spectrum == std::vector<int>( {16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} ) ) ||
  //         ( spectrum == std::vector<int>( {14, 2, 2, -2, 2, -2, -2, 2, 2, -2, -2, 2, -2, 2, 2, -2} ) ) ||
  //         ( spectrum == std::vector<int>( {12, 4, 4, -4, 4, -4, -4, 4, 0, 0, 0, 0, 0, 0, 0, 0} ) ) ||
  //         ( spectrum == std::vector<int>( {10, 6, 6, -6, 2, -2, -2, 2, 2, -2, -2, 2, 2, -2, -2, 2} ) ) ||
  //         ( spectrum == std::vector<int>( {8, 8, 8, -8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} ) ) ||
  //         ( spectrum == std::vector<int>( {8, 8, 4, -4, 4, -4, 0, 0, 4, -4, 0, 0, 0, 0, -4, 4} ) ) ||
  //         ( spectrum == std::vector<int>( {6, 6, 6, -2, 6, -2, -2, -2, 6, -2, -2, -2, -2, -2, -2, 6} ) ) ||
  //         ( spectrum == std::vector<int>( {4, 4, 4, 4, 4, 4, -4, -4, 4, -4, 4, -4, -4, 4, 4, -4} ) ) ) )
  // {
  //   std::cout << "[w] wrongly classified " << func << std::endl;
  //   print_spectrum_ordered( spectrum );
  //   print_spectrum( spectrum, nvars );
  // }

  return cfunc;
}

unsigned get_spectral_class( const tt& func )
{
  const auto nvars = tt_num_vars( func );

  assert( nvars >= 2u && nvars <= 5u );

  auto spectrum = rademacher_walsh_spectrum( func );
  std::transform( spectrum.begin(), spectrum.end(), spectrum.begin(), []( int i ) { return abs( i ); } );
  std::stable_sort( spectrum.begin(), spectrum.end(), std::not2( std::less<int>() ) );

  switch ( nvars )
  {
  case 2u:
    return spectrum.front() == 4 ? 0u : 1u;
  case 3u:
    switch ( spectrum.front() )
    {
    case 8: return 0u;
    case 6: return 1u;
    case 4: return 2u;
    } break;
  case 4u:
    switch ( spectrum.front() )
    {
    case 16: return 0u;
    case 14: return 1u;
    case 12: return 2u;
    case 10: return 3u;
    case 8: return spectrum[2u] == 8 ? 4u : 5u;
    case 6: return 6u;
    case 4: return 7u;
    } break;
  case 5u:
    switch ( spectrum.front() )
    {
    case 32: return 0u;
    case 30: return 1u;
    case 28: return 2u;
    case 26: return 3u;
    case 24: return spectrum[4u] == 8 ? 4u : 5u;
    case 22: return spectrum[2u] == 6 ? 6u : 7u;
    case 20:
      if ( spectrum[1u] == 12 && spectrum[2u] == 4 ) return 8u;
      else if ( spectrum[1u] == 8 ) return 9u;
      else if ( spectrum[1u] == 12 && spectrum[2u] == 8 ) return 10u;
      else if ( spectrum[1u] == 12 && spectrum[2u] == 12 ) return 11u;
      else assert( false ); break;
    case 18:
      if ( spectrum[1u] == 10 && spectrum[2u] == 6 ) return 12u;
      else if ( spectrum[1u] == 14 && spectrum[2u] == 6 ) return 13u;
      else if ( spectrum[1u] == 10 && spectrum[2u] == 10 ) return 14u;
      else if ( spectrum[1u] == 14 && spectrum[2u] == 10 ) return 15u;
      else if ( spectrum[1u] == 14 && spectrum[2u] == 14 ) return 16u;
      else assert( false ); break;
    case 16:
      if ( spectrum[1u] == 16 && spectrum[2u] == 16 ) return 17u;
      else if ( spectrum[1u] == 8 && spectrum[9u] == 8 ) return 18u;
      else if ( spectrum[1u] == 16 && spectrum[2u] == 8 && spectrum[6] == 8) return 19u;
      else if ( spectrum[1u] == 8 && spectrum[9u] == 4 ) return 20u;
      else if ( spectrum[1u] == 12 && spectrum[2u] == 8 ) return 21u;
      else if ( spectrum[1u] == 16 && spectrum[2u] == 8 && spectrum[6] == 4 ) return 22u;
      else if ( spectrum[1u] == 12 && spectrum[2u] == 12 ) return 23u;
      else if ( spectrum[1u] == 16 && spectrum[2u] == 12 ) return 24u;
      else assert( false ); break;
    case 14:
      if ( spectrum[1u] == 10 && spectrum[2u] == 10 && spectrum[5u] == 6 )
      {
        auto ac = autocorrelation_spectrum( func );
        std::transform( ac.begin(), ac.end(), ac.begin(), []( int i ) { return abs( i ); } );
        std::stable_sort( ac.begin(), ac.end(), std::not2( std::less<int>() ) );
        return ac[1u] == 12 ? 25u : 26u;
      }
      else if ( spectrum[1u] == 14 && spectrum[2u] == 10 && spectrum[4u] == 6 ) return 27u;
      else if ( spectrum[1u] == 10 && spectrum[2u] == 10 && spectrum[5u] == 10 ) return 28u;
      else if ( spectrum[1u] == 14 && spectrum[2u] == 14 ) return 29u;
      else if ( spectrum[1u] == 14 && spectrum[2u] == 10 && spectrum[4u] == 10 ) return 30;
      else assert( false ); break;
    case 12:
      if ( spectrum[1u] == 8 ) return 33u;
      else if ( spectrum[1u] == 12 && spectrum[2u] == 8 )
      {
        auto ac = autocorrelation_spectrum( func );
        std::transform( ac.begin(), ac.end(), ac.begin(), []( int i ) { return abs( i ); } );
        std::stable_sort( ac.begin(), ac.end(), std::not2( std::less<int>() ) );
        return ac[1u] == 16 ? 34u : 35u;
      }
      else if ( spectrum[1u] == 12 && spectrum[2u] == 12 && spectrum[3u] == 8 ) return 36;
      else if ( spectrum[4u] == 4 )
      {
        auto ac = autocorrelation_spectrum( func );
        std::transform( ac.begin(), ac.end(), ac.begin(), []( int i ) { return abs( i ); } );
        std::stable_sort( ac.begin(), ac.end(), std::not2( std::less<int>() ) );
        return ac[4u] == 8 ? 31u : 32u;
      }
      else if ( spectrum[4u] == 8 )
      {
        auto ac = autocorrelation_spectrum( func );
        std::transform( ac.begin(), ac.end(), ac.begin(), []( int i ) { return abs( i ); } );
        std::stable_sort( ac.begin(), ac.end(), std::not2( std::less<int>() ) );
        return ac[1u] == 16 ? 37u : 38u;
      }
      else if ( spectrum[4u] == 12 ) return 39u;
      else assert( false ); break;
    case 10:
      if ( spectrum[4u] == 6 ) return 40u;
      else if ( spectrum[4u] == 10 )
      {
        auto ac = autocorrelation_spectrum( func );
        std::transform( ac.begin(), ac.end(), ac.begin(), []( int i ) { return abs( i ); } );
        std::stable_sort( ac.begin(), ac.end(), std::not2( std::less<int>() ) );
        return ac[1u] == 12 ? 41u : ( ac[1u] == 20 ? 42u : 43u );
      }
      else assert( false ); break;
    case 8:
      if ( spectrum[12u] == 8 )
      {
        auto ac = autocorrelation_spectrum( func );
        std::transform( ac.begin(), ac.end(), ac.begin(), []( int i ) { return abs( i ); } );
        std::stable_sort( ac.begin(), ac.end(), std::not2( std::less<int>() ) );
        return ac[1u] == 32 ? 44u : ( ac[1u] == 8 ? 45u : 46u );
      }
      else if ( spectrum[12u] == 4 ) return 47u;
      else assert( false ); break;
    } break;
  }

  return 0u;
}

}

// Local Variables:
// c-basic-offset: 2
// eval: (c-set-offset 'substatement-open 0)
// eval: (c-set-offset 'innamespace 0)
// End:


// Distributed under the MIT License.
// See LICENSE.txt for details.

#pragma once

#include <boost/none.hpp>
#include <boost/optional.hpp>
#include <vector>

#include "DataStructures/DataVector.hpp"
#include "ErrorHandling/Error.hpp"
#include "Utilities/Gsl.hpp"

namespace RootFinder {
namespace bracketing_detail {
// Brackets a root, given a functor f(x) that returns a
// boost::optional<double> and given two arrays x and y (with y=f(x))
// containing points that have already been tried for bracketing.
//
// Returns a std::array<double,4> containing {{x1,x2,y1,y2}} where
// x1 and x2 bracket the root, and y1=f(x1) and y2=f(x2).
//
// Note that y might be undefined (i.e. an invalid boost::optional)
// for values of x at or near the endpoints of the interval.  We
// assume that if f(x1) and f(x2) are valid for some x1 and x2, then
// f(x) is valid for all x between x1 and x2.
//
// Assumes that there is a root between the first and last points of
// the array.
// We also assume that there is only one root.
//
// So this means we have only 2 possibilities for the validity of the
// points in the input x,y arrays:
// 1) All points are invalid, e.g. "X X X X X X X X".
//    (here X represents an invalid point)
// 2) All valid points are adjacent, with the same sign, e.g. "X X o o X X"
//    or "o o X X X" or "X X X o o".
//    (here o represents a valid point)
// Note that we assume that all valid points have the same sign; otherwise
// the caller would have known that the root was bracketed and the caller would
// not have called bracket_by_contracting.
//
// Also note that we exclude the case "o o o o o" (no roots), since the
// caller would have known that too.  If such a case is found, an error is
// thrown.  An error is also thrown if the size of the region where the
// sign changes is so small that the number of iterations is exceeded.
//
// For case 1) above, we bisect each pair of points, and call
// bracket_by_contracting recursively until we find a valid point.
// For case 2) above, it is sufficent to check for a bracket only
// between valid and invalid points.  That is, for "X X + + X X" we
// check only between points 1 and 2 and between points 3 and 4 (where
// points are numbered starting from zero).  For "+ + X X X" we check
// only between points 1 and 2.
template <typename Functor>
std::array<double, 4> bracket_by_contracting(
    const std::vector<double>& x, const std::vector<boost::optional<double>>& y,
    const Functor& f, const size_t level = 0) noexcept {
  constexpr size_t max_level = 6;
  if (level > max_level) {
    ERROR("Too many iterations in bracket_by_contracting. Either refine the "
          "initial range/guess or increase max_level.");
  }

  // First check if we have any valid points.
  size_t last_valid_index = y.size();
  for (size_t i = y.size(); i >= 1; --i) {
    if (y[i - 1]) {
      last_valid_index = i - 1;
      break;
    }
  }

  if (last_valid_index == y.size()) {
    // No valid points!

    // Create larger arrays with one point between each of the already
    // computed points.
    std::vector<double> bisected_x(x.size() * 2 - 1);
    std::vector<boost::optional<double>> bisected_y(y.size() * 2 - 1);

    // Copy all even-numbered points in the range.
    for (size_t i = 0; i < x.size(); ++i) {
      bisected_x[2 * i] = x[i];
      bisected_y[2 * i] = y[i];
    }

    // Fill midpoints and check for bracket on each one.
    for (size_t i = 0; i < x.size() - 1; ++i) {
      bisected_x[2 * i + 1] = x[i] + 0.5 * (x[i + 1] - x[i]);
      bisected_y[2 * i + 1] = f(bisected_x[2 * i + 1]);
      if (bisected_y[2 * i + 1]) {
        // Valid point! We know that all the other points are
        // invalid, so we need to check only 3 points in the next
        // iteration: the new valid point and its neighbors.
        return bracket_by_contracting({{x[i], bisected_x[2 * i + 1], x[i + 1]}},
                                      {{y[i], bisected_y[2 * i + 1], y[i + 1]}},
                                      f, level + 1);
      }
    }
    // We still have no valid points. So recurse, using all points.
    // The next iteration will bisect all the points.
    return bracket_by_contracting(bisected_x, bisected_y, f, level + 1);
  }

  // If we get here, we have found a valid point; in particular we have
  // found the last valid point in the array.

  // Find the first valid point in the array.
  size_t first_valid_index = 0;
  for (size_t i = 0; i < y.size(); ++i) {
    if (y[i]) {
      first_valid_index = i;
      break;
    }
  }

  // Make a new set of points that includes only the points that
  // neighbor the boundary between valid and invalid points.
  std::vector<double> x_near_valid_point;
  std::vector<boost::optional<double>> y_near_valid_point;

  if (first_valid_index == 0 and last_valid_index == y.size() - 1) {
    ERROR(
        "bracket_while_contracting: found a case where all points are valid,"
        "which should not happen under our assumptions.");
  }

  if (first_valid_index > 0) {
    // Check for a root between first_valid_index-1 and first_valid_index.
    const double x_test =
        x[first_valid_index - 1] +
        0.5 * (x[first_valid_index] - x[first_valid_index - 1]);
    const auto y_test = f(x_test);
    if (y_test and y[first_valid_index].get() * y_test.get() <= 0.0) {
      // Bracketed!
      return std::array<double, 4>{{x_test, x[first_valid_index], y_test.get(),
                                    y[first_valid_index].get()}};
    } else {
      x_near_valid_point.push_back(x[first_valid_index - 1]);
      y_near_valid_point.push_back(y[first_valid_index - 1]);
      x_near_valid_point.push_back(x_test);
      y_near_valid_point.push_back(y_test);
      x_near_valid_point.push_back(x[first_valid_index]);
      y_near_valid_point.push_back(y[first_valid_index]);
    }
  }
  if (last_valid_index < y.size() - 1) {
    // Check for a root between last_valid_index and last_valid_index+1.
    const double x_test = x[last_valid_index] +
                          0.5 * (x[last_valid_index + 1] - x[last_valid_index]);
    const auto y_test = f(x_test);
    if (y_test and y[last_valid_index].get() * y_test.get() <= 0.0) {
      // Bracketed!
      return std::array<double, 4>{{x[last_valid_index], x_test,
                                    y[last_valid_index].get(), y_test.get()}};
    } else {
      if (first_valid_index != last_valid_index or first_valid_index == 0) {
        x_near_valid_point.push_back(x[last_valid_index]);
        y_near_valid_point.push_back(y[last_valid_index]);
      }  // else we already pushed back last_valid_index (==first_valid_index).
      x_near_valid_point.push_back(x_test);
      y_near_valid_point.push_back(y_test);
      x_near_valid_point.push_back(x[last_valid_index + 1]);
      y_near_valid_point.push_back(y[last_valid_index + 1]);
    }
  }

  // We have one or more valid points but we didn't find a bracket.
  // That is, we have something like "X X o o X X" or "X X o o" or "o o X X".
  // So recurse, zooming in to the boundary (either one boundary or two
  // boundaries) between valid and invalid points.
  // Note that "o o o o" is prohibited by our assumptions, and checked for
  // above just in case it occurs by mistake.
  return bracket_by_contracting(x_near_valid_point, y_near_valid_point, f,
                                level + 1);
}
}  // namespace bracketing_detail

/*!
 * \ingroup NumericalAlgorithmsGroup
 * \brief Brackets the root of the function `f`, assuming a single
 * root in a given interval \f$f[x_\mathrm{lo},x_\mathrm{up}]\f$
 * and assuming that `f` is defined only in an unknown smaller
 * interval \f$f[x_a,x_b]\f$ where
 * \f$x_\mathrm{lo} \leq x_a \leq x_b \leq x_\mathrm{hi}\f$.
 *
 * `f` is a unary invokable that takes a `double` which is the current value at
 * which to evaluate `f`.  `f` returns a `boost::optional<double>` which
 * evaluates to false if the function is undefined at the supplied point.
 *
 * Assumes that there is only one root in the interval.
 *
 * Assumes that if \f$f(x_1)\f$ and \f$f(x_2)\f$ are both defined for
 * some \f$(x_1,x_2)\f$, then \f$f(x)\f$ is defined for all \f$x\f$
 * between \f$x_1\f$ and \f$x_2\f$.
 *
 * On input, assumes that the root lies in the interval
 * [`lower_bound`,`upper_bound`].  Optionally takes a `guess` for the
 * location of the root.  If `guess` is supplied, then evaluates the
 * function first at `guess` and `upper_bound` before trying
 * `lower_bound`: this means that it would be optimal if `guess`
 * underestimates the actual root and if `upper_bound` was less likely
 * to be undefined than `lower_bound`.
 *
 * On return, `lower_bound` and `upper_bound` are replaced with values that
 * bracket the root and for which the function is defined, and
 * `f_at_lower_bound` and `f_at_upper_bound` are replaced with
 * `f` evaluated at those bracketing points.
 *
 * `bracket_possibly_undefined_function_in_interval` throws an error if
 *  all points are valid but of the same sign (because that would indicate
 *  multiple roots but we assume only one root), if no root exists, or
 *  if the range of a sign change is sufficently small relative to the
 *  given interval that the number of iterations to find the root is exceeded.
 *
 */
template <typename Functor>
void bracket_possibly_undefined_function_in_interval(
    const gsl::not_null<double*> lower_bound,
    const gsl::not_null<double*> upper_bound,
    const gsl::not_null<double*> f_at_lower_bound,
    const gsl::not_null<double*> f_at_upper_bound, const Functor& f,
    const double guess) noexcept {
  // Initial values of x1,x2,y1,y2.  Use `guess` and `upper_bound`,
  // because in typical usage `guess` underestimates the actual
  // root, and `lower_bound` is more likely than `upper_bound` to be
  // invalid.
  double x1 = guess;
  double x2 = *upper_bound;
  auto y1 = f(x1);
  auto y2 = f(x2);
  const bool y1_defined = static_cast<bool>(y1);
  const bool y2_defined = static_cast<bool>(y2);
  if (not(y1_defined and y2_defined and y1.get() * y2.get() <= 0.0)) {
    // Root is not bracketed.
    // Before moving to the general algorithm, try the remaining
    // input point that was supplied.
    const double x3 = *lower_bound;
    const auto y3 = f(x3);
    const bool y3_defined = static_cast<bool>(y3);
    if (y1_defined and y3_defined and y1.get() * y3.get() <= 0.0) {
      // Bracketed! Throw out x2,y2.  Rename variables to keep x1 < x2.
      x2 = x1;
      y2 = y1;
      x1 = x3;
      y1 = y3;
    } else {
      // Our simple checks didn't work, so call the more general method.
      // There are 8 cases:
      //
      // y3 y1 y2
      // --------
      // X  X  X
      // o  X  X
      // X  o  X
      // o  o  X
      // X  o  o
      // o  o  o
      // X  X  o
      // o  X  o
      //
      // where X means an invalid point, o means a valid point.
      // All valid points have the same sign, or we would have found a
      // bracket already.
      //
      // Before calling the general case, error on "o o o" and "o X o".
      // Both of these are prohibited by our assumptions (we
      // assume the root is in the interval so no "o o o", and we
      // assume that all invalid points are at the end of interval, so no
      // "o X o").
      if (y2_defined and y3_defined) {
        ERROR(
            "bracket_possibly_undefined_function_in_interval: found "
            "case that should not happen under our assumptions.");
      }
      std::array<double, 4> tmp = bracketing_detail::bracket_by_contracting(
          {{x3, x1, x2}}, {{y3, y1, y2}}, f);
      x1 = tmp[0];
      x2 = tmp[1];
      y1 = tmp[2];
      y2 = tmp[3];
    }
  }
  *f_at_lower_bound = y1.get();
  *f_at_upper_bound = y2.get();
  *lower_bound = x1;
  *upper_bound = x2;
}

/*!
 * \ingroup NumericalAlgorithmsGroup
 * \brief Brackets the single root of the
 * function `f` for each element in a `DataVector`, assuming the root
 * lies in the given interval and that `f` may be undefined at some
 * points in the interval.
 *
 * `f` is a binary invokable that takes a `double` and a `size_t` as
 * arguments.  The `double` is the current value at which to evaluate
 * `f`, and the `size_t` is the index into the `DataVector`s.  `f`
 * returns a `boost::optional<double>` which evaluates to false if the
 * function is undefined at the supplied point.
 *
 * Assumes that there is only one root in the interval.
 *
 * Assumes that if \f$f(x_1)\f$ and \f$f(x_2)\f$ are both defined for
 * some \f$(x_1,x_2)\f$, then \f$f(x)\f$ is defined for all \f$x\f$
 * between \f$x_1\f$ and \f$x_2\f$.
 *
 * On input, assumes that the root lies in the interval
 * [`lower_bound`,`upper_bound`].  Optionally takes a `guess` for the
 * location of the root.
 *
 * On return, `lower_bound` and `upper_bound` are replaced with values that
 * bracket the root and for which the function is defined, and
 * `f_at_lower_bound` and `f_at_upper_bound` are replaced with
 * `f` evaluated at those bracketing points.
 *
 */
template <typename Functor>
void bracket_possibly_undefined_function_in_interval(
    const gsl::not_null<DataVector*> lower_bound,
    const gsl::not_null<DataVector*> upper_bound,
    const gsl::not_null<DataVector*> f_at_lower_bound,
    const gsl::not_null<DataVector*> f_at_upper_bound, const Functor& f,
    const DataVector& guess) noexcept {
  for (size_t s = 0; s < lower_bound->size(); ++s) {
    bracket_possibly_undefined_function_in_interval(
        &((*lower_bound)[s]), &((*upper_bound)[s]), &((*f_at_lower_bound)[s]),
        &((*f_at_upper_bound)[s]),
        [&f, &s ](const double x) noexcept { return f(x, s); }, guess[s]);
  }
}

/*
 * Version of `bracket_possibly_undefined_function_in_interval`
 * without a supplied initial guess; uses the mean of `lower_bound` and
 * `upper_bound` as the guess.
 */
template <typename Functor>
void bracket_possibly_undefined_function_in_interval(
    const gsl::not_null<double*> lower_bound,
    const gsl::not_null<double*> upper_bound,
    const gsl::not_null<double*> f_at_lower_bound,
    const gsl::not_null<double*> f_at_upper_bound, const Functor& f) noexcept {
  bracket_possibly_undefined_function_in_interval(
      lower_bound, upper_bound, f_at_lower_bound, f_at_upper_bound, f,
      *lower_bound + 0.5 * (*upper_bound - *lower_bound));
}

/*
 * Version of `bracket_possibly_undefined_function_in_interval`
 * without a supplied initial guess; uses the mean of `lower_bound` and
 * `upper_bound` as the guess.
 */
template <typename Functor>
void bracket_possibly_undefined_function_in_interval(
    const gsl::not_null<DataVector*> lower_bound,
    const gsl::not_null<DataVector*> upper_bound,
    const gsl::not_null<DataVector*> f_at_lower_bound,
    const gsl::not_null<DataVector*> f_at_upper_bound,
    const Functor& f) noexcept {
  bracket_possibly_undefined_function_in_interval(
      lower_bound, upper_bound, f_at_lower_bound, f_at_upper_bound, f,
      *lower_bound + 0.5 * (*upper_bound - *lower_bound));
}
}  // namespace RootFinder