// Copyright (c) 2010-2023, Lawrence Livermore National Security, LLC. Produced
// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
// LICENSE and NOTICE for details. LLNL-CODE-806117.
//
// This file is part of the MFEM library. For more information and source code
// availability visit https://mfem.org.
//
// MFEM is free software; you can redistribute it and/or modify it under the
// terms of the BSD-3 license. We welcome feedback and contributions, see file
// CONTRIBUTING.md for details.
//
//      --------------------------------------------------------------
//      Field Diff Miniapp: Compare grid functions on different meshes
//      --------------------------------------------------------------
//
// This miniapp compares two different high-order grid functions, defined on two
// different high-order meshes, based on the GSLIB-FindPoints general off-grid
// interpolation utility. Using a set of points defined within the bounding box
// of the domain, FindPoints is used to interpolate the grid functions from the
// two different meshes and output the difference between the interpolated
// values. The miniapp also uses FindPoints to interpolate the solution from one
// mesh onto another, and visualize the difference using GLVis.
//
// Compile with: make field-diff
//
// Sample runs:
//    field-diff
//    field-diff -m1 triple-pt-1.mesh -s1 triple-pt-1.gf -m2 triple-pt-2.mesh -s2 triple-pt-2.gf -p 200

#include "mfem.hpp"
#include <fstream>

using namespace mfem;
using namespace std;

int main (int argc, char *argv[])
{
   // Set the method's default parameters.
   const char *mesh_file_1 = "triple-pt-1.mesh";
   const char *mesh_file_2 = "triple-pt-2.mesh";
   const char *sltn_file_1 = "triple-pt-1.gf";
   const char *sltn_file_2 = "triple-pt-2.gf";
   bool visualization    = true;
   int pts_cnt_1D = 100;

   // Parse command-line options.
   OptionsParser args(argc, argv);
   args.AddOption(&mesh_file_1, "-m1", "--mesh1",
                  "Mesh file for solution 1.");
   args.AddOption(&mesh_file_2, "-m2", "--mesh2",
                  "Mesh file for solution 2.");
   args.AddOption(&sltn_file_1, "-s1", "--solution1",
                  "Grid function for solution 1.");
   args.AddOption(&sltn_file_2, "-s2", "--solution2",
                  "Grid function for solution 2.");
   args.AddOption(&pts_cnt_1D, "-p", "--points1D",
                  "Number of comparison points in one direction");
   args.AddOption(&visualization, "-vis", "--visualization", "-no-vis",
                  "--no-visualization",
                  "Enable or disable GLVis visualization.");
   args.Parse();
   if (!args.Good())
   {
      args.PrintUsage(cout);
      return 1;
   }
   args.PrintOptions(cout);

   // Input meshes.
   Mesh mesh_1(mesh_file_1, 1, 1, false);
   Mesh mesh_2(mesh_file_2, 1, 1, false);
   const int dim = mesh_1.Dimension();
   MFEM_VERIFY(dim > 1, "GSLIB requires a 2D or a 3D mesh" );

   // The Nodes GridFunctions for each mesh are required.
   if (mesh_1.GetNodes() == NULL) { mesh_1.SetCurvature(1, false, dim, 0); }
   if (mesh_2.GetNodes() == NULL) { mesh_2.SetCurvature(1, false, dim, 0); }
   const int mesh_poly_deg = mesh_1.GetNodes()->FESpace()->GetElementOrder(0);
   cout << "Mesh curvature: "
        << mesh_1.GetNodes()->OwnFEC()->Name() << " " << mesh_poly_deg << endl;

   // The visualization of the difference assumes byNODES ordering.
   if (mesh_1.GetNodes()->FESpace()->GetOrdering() == Ordering::byVDIM)
   { mesh_1.SetCurvature(mesh_poly_deg, false, dim, Ordering::byNODES); }
   if (mesh_2.GetNodes()->FESpace()->GetOrdering() == Ordering::byVDIM)
   { mesh_2.SetCurvature(mesh_poly_deg, false, dim, Ordering::byNODES); }

   // Mesh bounding box.
   Vector pos_min, pos_max;
   MFEM_VERIFY(mesh_poly_deg > 0, "The order of the mesh must be positive.");
   mesh_1.GetBoundingBox(pos_min, pos_max, mesh_poly_deg);
   cout << "Generating equidistant points for:\n"
        << "  x in [" << pos_min(0) << ", " << pos_max(0) << "]\n"
        << "  y in [" << pos_min(1) << ", " << pos_max(1) << "]\n";
   if (dim == 3)
   {
      cout << "z in [" << pos_min(2) << ", " << pos_max(2) << "]\n";
   }

   ifstream mat_stream_1(sltn_file_1), mat_stream_2(sltn_file_2);
   GridFunction func_1(&mesh_1, mat_stream_1);
   GridFunction func_2(&mesh_2, mat_stream_2);

   // Display the meshes and the fields through glvis.
   if (visualization)
   {
      char vishost[] = "localhost";
      int  visport   = 19916;
      socketstream sout1, sout2;
      sout1.open(vishost, visport);
      sout2.open(vishost, visport);
      if (!sout1)
      {
         cout << "Unable to connect to GLVis server at "
              << vishost << ':' << visport << endl;
      }
      else
      {
         sout1.precision(8);
         sout1 << "solution\n" << mesh_1 << func_1
               << "window_title 'Solution 1'"
               << "window_geometry 0 0 600 600";
         if (dim == 2) { sout1 << "keys RmjAc"; }
         if (dim == 3) { sout1 << "keys mA\n"; }
         sout1 << flush;

         sout2.precision(8);
         sout2 << "solution\n" << mesh_2 << func_2
               << "window_title 'Solution 2'"
               << "window_geometry 600 0 600 600";
         if (dim == 2) { sout2 << "keys RmjAc"; }
         if (dim == 3) { sout2 << "keys mA\n"; }
         sout2 << flush;
      }
   }

   // Generate equidistant points in physical coordinates over the whole mesh.
   // Note that some points might be outside, if the mesh is not a box. Note
   // also that all tasks search the same points (not mandatory).
   const int pts_cnt = pow(pts_cnt_1D, dim);
   Vector vxyz(pts_cnt * dim);
   if (dim == 2)
   {
      L2_QuadrilateralElement el(pts_cnt_1D - 1, BasisType::ClosedUniform);
      const IntegrationRule &ir = el.GetNodes();
      for (int i = 0; i < ir.GetNPoints(); i++)
      {
         const IntegrationPoint &ip = ir.IntPoint(i);
         vxyz(i)           = pos_min(0) + ip.x * (pos_max(0)-pos_min(0));
         vxyz(pts_cnt + i) = pos_min(1) + ip.y * (pos_max(1)-pos_min(1));
      }
   }
   else
   {
      L2_HexahedronElement el(pts_cnt_1D - 1, BasisType::ClosedUniform);
      const IntegrationRule &ir = el.GetNodes();
      for (int i = 0; i < ir.GetNPoints(); i++)
      {
         const IntegrationPoint &ip = ir.IntPoint(i);
         vxyz(i)             = pos_min(0) + ip.x * (pos_max(0)-pos_min(0));
         vxyz(pts_cnt + i)   = pos_min(1) + ip.y * (pos_max(1)-pos_min(1));
         vxyz(2*pts_cnt + i) = pos_min(2) + ip.z * (pos_max(2)-pos_min(2));
      }
   }

   FindPointsGSLIB finder1, finder2;
   Vector interp_vals_1(pts_cnt), interp_vals_2(pts_cnt);

   // First solution.
   finder1.Interpolate(mesh_1, vxyz, func_1, interp_vals_1);

   // Second solution.
   finder2.Interpolate(mesh_2, vxyz, func_2, interp_vals_2);

   // Compute differences between the two sets of values.
   double avg_diff = 0.0, max_diff = 0.0, diff_p;
   for (int p = 0; p < pts_cnt; p++)
   {
      diff_p = fabs(interp_vals_1(p) - interp_vals_2(p));
      avg_diff += diff_p;
      if (diff_p > max_diff) { max_diff = diff_p; }
   }
   avg_diff /= pts_cnt;

   GridFunction *n1 = mesh_1.GetNodes(), *n2 = mesh_2.GetNodes();
   double *nd1 = n1->GetData(), *nd2 = n2->GetData();
   double avg_dist = 0.0;
   const int node_cnt = n1->Size() / dim;
   if (n1->Size() == n2->Size())
   {
      for (int i = 0; i < node_cnt; i++)
      {
         double diff_i = 0.0;
         for (int d = 0; d < dim; d++)
         {
            const int j = i + d * node_cnt;
            diff_i += (nd1[j] - nd2[j]) * (nd1[j] - nd2[j]);
         }
         avg_dist += sqrt(diff_i);
      }
      avg_dist /= node_cnt;
   }
   else { avg_dist = -1.0; }

   std::cout << "Avg position difference: " << avg_dist << std::endl
             << "Searched " << pts_cnt << " points.\n"
             << "Max diff: " << max_diff << std::endl
             << "Avg diff: " << avg_diff << std::endl;

   // Visualize the difference at the nodes of mesh 1.
   FiniteElementSpace m1_fes(&mesh_1, mesh_1.GetNodes()->FESpace()->FEColl());
   GridFunction diff(&m1_fes);
   GridFunctionCoefficient f1c(&func_1);
   diff.ProjectDiscCoefficient(f1c, GridFunction::ARITHMETIC);
   vxyz = *mesh_1.GetNodes();
   const int nodes_cnt = vxyz.Size() / dim;
   interp_vals_2.SetSize(nodes_cnt);
   finder2.Interpolate(vxyz, func_2, interp_vals_2);

   for (int n = 0; n < nodes_cnt; n++)
   {
      diff(n) = fabs(diff(n) - interp_vals_2(n));
   }

   if (visualization)
   {
      char vishost[] = "localhost";
      int  visport   = 19916;
      socketstream sout3;
      sout3.open(vishost, visport);
      sout3.precision(8);
      sout3 << "solution\n" << mesh_1 << diff
            << "window_title 'Difference'"
            << "window_geometry 1200 0 600 600";
      if (dim == 2) { sout3 << "keys RmjAcpppppppppppppppppppppp"; }
      if (dim == 3) { sout3 << "keys mA\n"; }
      sout3 << flush;
   }

   ConstantCoefficient coeff1(1.0);
   DomainLFIntegrator *lf_integ = new DomainLFIntegrator(coeff1);
   LinearForm lf(func_1.FESpace());
   lf.AddDomainIntegrator(lf_integ);
   lf.Assemble();
   const double vol_diff = diff * lf;
   std::cout << "Vol diff: " << vol_diff << std::endl;

   // Free the internal gslib data.
   finder1.FreeData();
   finder2.FreeData();

   return 0;
}
