// MFEM Example 9 Continuous - Parallel Version // // Author: Madison Sheridan // Created: 05/25/2022 // // Based on Example 9p // // Compile with: make ex9p-continuous // // Sample runs: // mpirun -np 4 ex9p-continuous -m ../data/periodic-segment.mesh -p 0 -dt 0.005 // mpirun -np 4 ex9p-continuous -m ../data/periodic-square.mesh -p 0 -dt 0.01 // mpirun -np 4 ex9p-continuous -m ../data/periodic-hexagon.mesh -p 0 -dt 0.01 // mpirun -np 4 ex9p-continuous -m ../data/periodic-square.mesh -p 1 -dt 0.005 -tf 9 // mpirun -np 4 ex9p-continuous -m ../data/periodic-hexagon.mesh -p 1 -dt 0.005 -tf 9 // mpirun -np 4 ex9p-continuous -m ../data/amr-quad.mesh -p 1 -rp 1 -dt 0.002 -tf 9 // mpirun -np 4 ex9p-continuous -m ../data/amr-quad.mesh -p 1 -rp 1 -dt 0.02 -s 13 -tf 9 // mpirun -np 4 ex9p-continuous -m ../data/star-q3.mesh -p 1 -rp 1 -dt 0.004 -tf 9 // mpirun -np 4 ex9p-continuous -m ../data/star-mixed.mesh -p 1 -rp 1 -dt 0.004 -tf 9 // mpirun -np 4 ex9p-continuous -m ../data/disc-nurbs.mesh -p 1 -rp 1 -dt 0.005 -tf 9 // mpirun -np 4 ex9p-continuous -m ../data/disc-nurbs.mesh -p 2 -rp 1 -dt 0.005 -tf 9 // mpirun -np 4 ex9p-continuous -m ../data/periodic-square.mesh -p 3 -rp 2 -dt 0.0025 -tf 9 -vs 20 // mpirun -np 4 ex9p-continuous -m ../data/periodic-cube.mesh -p 0 -o 2 -rp 1 -dt 0.01 -tf 8 // mpirun -np 4 ex9p-continuous -m ../data/periodic-square.msh -p 0 -rs 2 -dt 0.005 -tf 2 // mpirun -np 4 ex9p-continuous -m ../data/periodic-cube.msh -p 0 -rs 1 -o 2 -tf 2 // mpirun -np 3 ex9p-continuous -m ../data/amr-hex.mesh -p 1 -rs 1 -rp 0 -dt 0.005 -tf 0.5 // // Device sample runs: // mpirun -np 4 ex9p-continuous -pa // mpirun -np 4 ex9p-continuous -ea // mpirun -np 4 ex9p-continuous -fa // mpirun -np 4 ex9p-continuous -pa -m ../data/periodic-cube.mesh // mpirun -np 4 ex9p-continuous -pa -m ../data/periodic-cube.mesh -d cuda // mpirun -np 4 ex9p-continuous -ea -m ../data/periodic-cube.mesh -d cuda // mpirun -np 4 ex9p-continuous -fa -m ../data/periodic-cube.mesh -d cuda // mpirun -np 4 ex9p-continuous -pa -m ../data/amr-quad.mesh -p 1 -rp 1 -dt 0.002 -tf 9 -d cuda // // Description: This example code solves the time-dependent advection equation // du/dt + v.grad(u) = 0, where v is a given fluid velocity, and // u0(x)=u(0,x) is a given initial condition. // // The example demonstrates the use of Continuous Galerkin // bilinear forms in MFEM (face integrators), the use of explicit // ODE time integrators, the definition of periodic // boundary conditions through periodic meshes, as well as the use // of GLVis for persistent visualization of a time-evolving // solution. Saving of time-dependent data files for visualization // with VisIt (visit.llnl.gov) and ParaView (paraview.org), as // well as the optional saving with ADIOS2 (adios2.readthedocs.io) // are also illustrated. #include "mfem.hpp" #include #include #include // For timer using namespace std; using namespace mfem; // Choice for the problem setup. The fluid velocity, initial condition and // inflow boundary condition are chosen based on this parameter. int problem; // Velocity coefficient void velocity_function(const Vector &x, Vector &v); void test_sparse_matrices(const SparseMatrix &A, const SparseMatrix &AT); void create_global_expansion_matrix(ParFiniteElementSpace &pfes, SparseMatrix & spm); void test_hpm_pgf(HypreParVector U, HypreParMatrix k); SparseMatrix build_sparse_from_dense(DenseMatrix & dm); // Exact solution double exact_sol(const Vector &x, double t); double zero_sol(const Vector &x, double t) { return 0;} // Inflow boundary condition double inflow_function(const Vector &x); // Mesh bounding box Vector bb_min, bb_max; /** A time-dependent operator for the right-hand side of the ODE. The CG weak form of du/dt = -v.grad(u) is M du/dt = (D + K) u, where M and K are the mass and advection matrices, and D is the artificial viscosity matrix as described in Guermond/Popov 2016. This can be written as a general ODE, du/dt = M^{-1} (D + K) u, and this class is used to evaluate the right-hand side. */ class FE_Evolution : public TimeDependentOperator { private: ParFiniteElementSpace &pfes; ParMesh &pmesh; Array ess_tdof_list; HypreParMatrix M_hpm, *K_hpm, *KT_hpm; HypreParVector lumpedM; ParBilinearForm &K_pbf, &KT_pbf; SparseMatrix dij_sparse, K_spmat, KT_spmat; Vector dii; double timestep; public: FE_Evolution(ParBilinearForm &M_, ParBilinearForm &K_, ParBilinearForm &KT_); /** FE_Evolution::build_dij_matrix Builds dij_matrix used in the low order approximation, which is based on Guermond/Popov 2016. */ void build_dij_matrix(const Vector &U, const VectorFunctionCoefficient &velocity); void calculate_timestep(); double get_timestep(); virtual void Mult(const Vector &x, Vector &y) const; virtual void ImplicitSolve(const double dt, const Vector &x, Vector &k); virtual ~FE_Evolution(); }; int main(int argc, char *argv[]) { // 0. Start Timer clock_t t_start, t_end; t_start = clock(); // 1. Initialize MPI and HYPRE. Mpi::Init(); int num_procs = Mpi::WorldSize(); int myid = Mpi::WorldRank(); Hypre::Init(); // 2. Parse command-line options. problem = 4; const char *mesh_file = "../data/periodic-square.mesh"; int ser_ref_levels = 3; int par_ref_levels = 0; int order = 1; bool pa = false; bool ea = false; bool fa = false; const char *device_config = "cpu"; int ode_solver_type = 1; double t_final = 2.84; // Period of the exact solution for p.4 bool one_time_step = false; double dt = 0.01; bool match_dt_to_h = false; bool visualization = true; bool visit = false; bool paraview = false; bool adios2 = false; bool binary = false; int vis_steps = 2; int precision = 12; cout.precision(precision); OptionsParser args(argc, argv); args.AddOption(&mesh_file, "-m", "--mesh", "Mesh file to use."); args.AddOption(&problem, "-p", "--problem", "Problem setup to use. See options in velocity_function()."); args.AddOption(&ser_ref_levels, "-rs", "--refine-serial", "Number of times to refine the mesh uniformly in serial."); args.AddOption(&par_ref_levels, "-rp", "--refine-parallel", "Number of times to refine the mesh uniformly in parallel."); args.AddOption(&order, "-o", "--order", "Order (degree) of the finite elements."); args.AddOption(&pa, "-pa", "--partial-assembly", "-no-pa", "--no-partial-assembly", "Enable Partial Assembly."); args.AddOption(&ea, "-ea", "--element-assembly", "-no-ea", "--no-element-assembly", "Enable Element Assembly."); args.AddOption(&fa, "-fa", "--full-assembly", "-no-fa", "--no-full-assembly", "Enable Full Assembly."); args.AddOption(&device_config, "-d", "--device", "Device configuration string, see Device::Configure()."); args.AddOption(&ode_solver_type, "-s", "--ode-solver", "ODE solver: 1 - Forward Euler,\n\t" " 2 - RK2 SSP, 3 - RK3 SSP, 4 - RK4, 6 - RK6,\n\t" " 11 - Backward Euler,\n\t" " 12 - SDIRK23 (L-stable), 13 - SDIRK33,\n\t" " 22 - Implicit Midpoint Method,\n\t" " 23 - SDIRK23 (A-stable), 24 - SDIRK34"); args.AddOption(&t_final, "-tf", "--t-final", "Final time; start time is 0."); args.AddOption(&one_time_step, "-ots", "--one-time-step", "-no-ots", "--no-one-time-step", "Set end time to one time step for convergence testing."); args.AddOption(&dt, "-dt", "--time-step", "Time step."); args.AddOption(&match_dt_to_h, "-ct", "--conv-test", "-no-ct", "--no-conv-test", "Enable convergence testing by matching dt to h."); args.AddOption(&visualization, "-vis", "--visualization", "-no-vis", "--no-visualization", "Enable or disable GLVis visualization."); args.AddOption(&visit, "-visit", "--visit-datafiles", "-no-visit", "--no-visit-datafiles", "Save data files for VisIt (visit.llnl.gov) visualization."); args.AddOption(¶view, "-paraview", "--paraview-datafiles", "-no-paraview", "--no-paraview-datafiles", "Save data files for ParaView (paraview.org) visualization."); args.AddOption(&adios2, "-adios2", "--adios2-streams", "-no-adios2", "--no-adios2-streams", "Save data using adios2 streams."); args.AddOption(&binary, "-binary", "--binary-datafiles", "-ascii", "--ascii-datafiles", "Use binary (Sidre) or ascii format for VisIt data files."); args.AddOption(&vis_steps, "-vs", "--visualization-steps", "Visualize every n-th timestep."); args.Parse(); if (!args.Good()) { if (Mpi::Root()) { args.PrintUsage(cout); } return 1; } if (Mpi::Root()) { args.PrintOptions(cout); } Device device(device_config); if (Mpi::Root()) { device.Print(); } // 3. Read the serial mesh from the given mesh file on all processors. We can // handle geometrically periodic meshes in this code. Mesh *mesh = new Mesh(mesh_file, 1, 1); int dim = mesh->Dimension(); // 4. Define the ODE solver used for time integration. Several explicit // Runge-Kutta methods are available. ODESolver *ode_solver = NULL; switch (ode_solver_type) { // Explicit methods case 1: ode_solver = new ForwardEulerSolver; break; case 2: ode_solver = new RK2Solver(1.0); break; case 3: ode_solver = new RK3SSPSolver; break; case 4: ode_solver = new RK4Solver; break; case 6: ode_solver = new RK6Solver; break; // Implicit (L-stable) methods case 11: ode_solver = new BackwardEulerSolver; break; case 12: ode_solver = new SDIRK23Solver(2); break; case 13: ode_solver = new SDIRK33Solver; break; // Implicit A-stable methods (not L-stable) case 22: ode_solver = new ImplicitMidpointSolver; break; case 23: ode_solver = new SDIRK23Solver; break; case 24: ode_solver = new SDIRK34Solver; break; default: if (Mpi::Root()) { cout << "Unknown ODE solver type: " << ode_solver_type << '\n'; } delete mesh; return 3; } // 5. Refine the mesh in serial to increase the resolution. In this example // we do 'ser_ref_levels' of uniform refinement, where 'ser_ref_levels' is // a command-line parameter. If the mesh is of NURBS type, we convert it // to a (piecewise-polynomial) high-order mesh. for (int lev = 0; lev < ser_ref_levels; lev++) { mesh->UniformRefinement(); } if (mesh->NURBSext) { mesh->SetCurvature(max(order, 1)); } mesh->GetBoundingBox(bb_min, bb_max, max(order, 1)); // 6. Define the parallel mesh by a partitioning of the serial mesh. Refine // this mesh further in parallel to increase the resolution. Once the // parallel mesh is defined, the serial mesh can be deleted. ParMesh *pmesh = new ParMesh(MPI_COMM_WORLD, *mesh); delete mesh; for (int lev = 0; lev < par_ref_levels; lev++) { pmesh->UniformRefinement(); } double hmin, hmax, kmin, kmax; pmesh->GetCharacteristics(hmin, hmax, kmin, kmax); if (match_dt_to_h) { dt = hmin; } if (one_time_step) { t_final = dt; } // 7. Define the parallel H1 finite element space on the // parallel refined mesh of the given polynomial order. // H1_FECollection fec(order, dim, BasisType::Positive); H1_FECollection fec(order, dim); ParFiniteElementSpace *fes = new ParFiniteElementSpace(pmesh, &fec); HYPRE_BigInt global_vSize = fes->GlobalTrueVSize(); if (Mpi::Root()) { cout << "Number of unknowns: " << global_vSize << endl; cout << "Dim: " << dim << endl; } // 8. Set up and assemble the parallel bilinear and linear forms (and the // parallel hypre matrices) corresponding to the H1 discretization. VectorFunctionCoefficient velocity(dim, velocity_function); FunctionCoefficient inflow(inflow_function); FunctionCoefficient u0(exact_sol); ParBilinearForm *m = new ParBilinearForm(fes); ParBilinearForm *k = new ParBilinearForm(fes); ParBilinearForm *kT = new ParBilinearForm(fes); if (pa) { m->SetAssemblyLevel(AssemblyLevel::PARTIAL); k->SetAssemblyLevel(AssemblyLevel::PARTIAL); kT->SetAssemblyLevel(AssemblyLevel::PARTIAL); } else if (ea) { m->SetAssemblyLevel(AssemblyLevel::ELEMENT); k->SetAssemblyLevel(AssemblyLevel::ELEMENT); kT->SetAssemblyLevel(AssemblyLevel::ELEMENT); } else if (fa) { m->SetAssemblyLevel(AssemblyLevel::FULL); k->SetAssemblyLevel(AssemblyLevel::FULL); kT->SetAssemblyLevel(AssemblyLevel::FULL); } m->AddDomainIntegrator(new LumpedIntegrator(new MassIntegrator)); constexpr double alpha = -1.0; k->AddDomainIntegrator(new ConvectionIntegrator(velocity, alpha)); kT->AddDomainIntegrator(new ConservativeConvectionIntegrator(velocity, -alpha)); int skip_zeros = 0; m->Assemble(); k->Assemble(skip_zeros); kT->Assemble(skip_zeros); m->Finalize(); k->Finalize(skip_zeros); kT->Finalize(skip_zeros); // 9. Define the initial conditions, save the corresponding grid function to // a file and (optionally) save data in the VisIt format and initialize // GLVis visualization. ParGridFunction *u = new ParGridFunction(fes); u->ProjectCoefficient(u0); HypreParVector *U = u->GetTrueDofs(); ConstantCoefficient zero(0.0); { ostringstream mesh_name, sol_name; mesh_name << "ex9p-continuous-mesh." << setfill('0') << setw(6) << myid; sol_name << "ex9p-continuous-init." << setfill('0') << setw(6) << myid; ofstream omesh(mesh_name.str().c_str()); omesh.precision(precision); pmesh->Print(omesh); ofstream osol(sol_name.str().c_str()); osol.precision(precision); u->Save(osol); } // Create data collection for solution output: either VisItDataCollection for // ascii data files, or SidreDataCollection for binary data files. DataCollection *dc = NULL; if (visit) { if (binary) { #ifdef MFEM_USE_SIDRE dc = new SidreDataCollection("Example9-Parallel-Continuous", pmesh); #else MFEM_ABORT("Must build with MFEM_USE_SIDRE=YES for binary output."); #endif } else { dc = new VisItDataCollection("Example9-Parallel-Continuous", pmesh); dc->SetPrecision(precision); // To save the mesh using MFEM's parallel mesh format: // dc->SetFormat(DataCollection::PARALLEL_FORMAT); } dc->RegisterField("solution", u); dc->SetCycle(0); dc->SetTime(0.0); dc->Save(); } ParaViewDataCollection *pd = NULL; if (paraview) { pd = new ParaViewDataCollection("Example9P-Continuous", pmesh); pd->SetPrefixPath("ParaView"); pd->RegisterField("solution", u); pd->SetLevelsOfDetail(order); pd->SetDataFormat(VTKFormat::BINARY); pd->SetHighOrderOutput(true); pd->SetCycle(0); pd->SetTime(0.0); pd->Save(); } // Optionally output a BP (binary pack) file using ADIOS2. This can be // visualized with the ParaView VTX reader. #ifdef MFEM_USE_ADIOS2 ADIOS2DataCollection *adios2_dc = NULL; if (adios2) { std::string postfix(mesh_file); postfix.erase(0, std::string("../data/").size() ); postfix += "_o" + std::to_string(order); const std::string collection_name = "ex9p-continuous-" + postfix + ".bp"; adios2_dc = new ADIOS2DataCollection(MPI_COMM_WORLD, collection_name, pmesh); // output data substreams are half the number of mpi processes adios2_dc->SetParameter("SubStreams", std::to_string(num_procs/2) ); // adios2_dc->SetLevelsOfDetail(2); adios2_dc->RegisterField("solution", u); adios2_dc->SetCycle(0); adios2_dc->SetTime(0.0); adios2_dc->Save(); } #endif socketstream sout; if (visualization) { char vishost[] = "localhost"; int visport = 19916; sout.open(vishost, visport); } // 10. Define the time-dependent evolution operator describing the ODE // right-hand side, and perform time-integration (looping over the time // iterations, ti, with a time-step dt). FE_Evolution adv(*m, *k, *kT); double t = 0.0; adv.SetTime(t); ode_solver->Init(adv); // dij_matrix has no time dependence adv.build_dij_matrix(*u, velocity); // adv.calculate_timestep(); // Optimize time step, unless running convergence analysis // if (!match_dt_to_h) // { // dt = adv.get_timestep(); // According to CFL, take min across processors. // } cout << "dt: " << dt << endl; cout << "cfl: " << adv.get_timestep() << endl; // assert(dt <= adv.get_timestep()); // In either case, we must satisfy the CFL. if (dt > adv.get_timestep()) { cout << "CFL condition not satisfied.\n"; } bool done = false; for (int ti = 0; !done; ) { double dt_real = min(dt, t_final - t); ode_solver->Step(*U, t, dt_real); ti++; done = (t >= t_final - 1e-8*dt); if (done || ti % vis_steps == 0) { if (Mpi::Root()) { cout << "time step: " << ti << ", time: " << t << endl; } // 11. Extract the parallel grid function corresponding to the finite // element approximation U (the local solution on each processor). *u = *U; // Synchronizes MPI processes. if (visualization) { sout << "parallel " << num_procs << " " << myid << "\n"; sout << "solution\n" << *pmesh << *u << flush; } if (visit) { dc->SetCycle(ti); dc->SetTime(t); dc->Save(); } if (paraview) { pd->SetCycle(ti); pd->SetTime(t); pd->Save(); } #ifdef MFEM_USE_ADIOS2 // transient solutions can be visualized with ParaView if (adios2) { adios2_dc->SetCycle(ti); adios2_dc->SetTime(t); adios2_dc->Save(); } #endif } } // Output final computation time double computation_time; if (Mpi::Root()) { t_end = clock(); computation_time = ((float)(t_end - t_start))/CLOCKS_PER_SEC; cout << "Single processor final computation time: " << computation_time << " seconds.\n"; } // 12. Save the final solution in parallel. This output can be viewed later // using GLVis: "glvis -np -m ex9p-continuous-mesh -g ex9p-continuous-final". { ostringstream sol_name; sol_name << "ex9p-continuous-final." << setfill('0') << setw(6) << myid; ofstream osol(sol_name.str().c_str()); osol.precision(precision); u->Save(osol); } // 13. Save the exact solution and compute the error. This can be viewed // later using: "glvis -np -m ex9p-continuous-mesh -g ex9p-continuous-final-exact" { FunctionCoefficient u_exact(exact_sol); u_exact.SetTime(t); ParGridFunction *u_ex = new ParGridFunction(fes); u_ex->ProjectCoefficient(u_exact); ostringstream e_sol_name; e_sol_name << "ex9p-continuous-final-exact." << setfill('0') << setw(6) << myid; ofstream e_osol(e_sol_name.str().c_str()); e_osol.precision(precision); u_ex->Save(e_osol); double L1_error = u->ComputeL1Error(u_exact); double L2_error = u->ComputeL2Error(u_exact); double Linf_error = u->ComputeMaxError(u_exact); double zero_L2_error = u->ComputeL2Error(zero); if (myid == 0) { cout << "\n|| E_h - E ||_{L^1} = " << L1_error << '\n' << endl; cout << "\n|| E_h - E ||_{L^2} = " << L2_error << '\n' << endl; cout << "\n|| E_h - E ||_{L^inf} = " << Linf_error << '\n' << endl; cout << "\n|| E ||_{L^2} = " << zero_L2_error << '\n' << endl; } if (Mpi::Root()) { ostringstream convergence_filename; convergence_filename << "ex9p-analysis/ex9pc_np" << num_procs;// num_procs if (ser_ref_levels != 0) { convergence_filename << "_s" << setfill('0') << setw(2) << ser_ref_levels; } if (par_ref_levels != 0) { convergence_filename << "_p" << setfill('0') << setw(2) << par_ref_levels; } convergence_filename << "_refinement_" << setfill('0') << setw(2) << to_string(par_ref_levels + ser_ref_levels) << ".out"; ofstream convergence_file(convergence_filename.str().c_str()); convergence_file.precision(precision); convergence_file << "Processor_Runtime " << computation_time << "\n" << "n_processes " << num_procs << "\n" << "n_refinements " << to_string(par_ref_levels + ser_ref_levels) << "\n" << "n_Dofs " << global_vSize << "\n" << "h " << hmin << "\n" << "L1_Error " << L1_error << "\n" << "L2_Error " << L2_error << "\n" << "Linf_Error " << Linf_error << "\n" << "dt " << dt << "\n" << "Endtime " << t << "\n"; convergence_file.close(); } delete u_ex; } cout << "Freeing memory\n"; // 15. Free the used memory. delete u; delete k; delete m; delete fes; delete pmesh; delete ode_solver; delete pd; #ifdef MFEM_USE_ADIOS2 if (adios2) { delete adios2_dc; } #endif delete dc; cout << "Done Freeing memory!\n"; return 0; } /* Implementation of class FE_Evolution */ FE_Evolution::FE_Evolution(ParBilinearForm &M_, ParBilinearForm &K_, ParBilinearForm &KT_) : TimeDependentOperator(K_.Height()), pfes(*(K_.ParFESpace())), pmesh(*(pfes.GetParMesh())), lumpedM(&pfes), timestep(0.), K_pbf(K_), KT_pbf(KT_), dii(pfes.GetTrueVSize()) { if (pfes.GetParMesh()->bdr_attributes.Size()) { Array ess_bdr(pmesh.bdr_attributes.Max()); ess_bdr = 1; pfes.GetEssentialTrueDofs(ess_bdr, ess_tdof_list); } cout << "Initialize FE_Evolution class.\n"; M_.FormSystemMatrix(ess_tdof_list, M_hpm); M_hpm.GetDiag(lumpedM); K_hpm = K_pbf.ParallelAssemble(); K_hpm->MergeDiagAndOffd(K_spmat); dij_sparse = K_spmat; KT_hpm = KT_pbf.ParallelAssemble(); KT_hpm->MergeDiagAndOffd(KT_spmat); cout << "End FEEvolution constructor.\n"; } // TODO: Implement an ImplicitSolve function // Solve the equation: // u_t = M^{-1}(Ku), // by solving associated linear system // (M - dt*K) d = K*u void FE_Evolution::ImplicitSolve(const double dt, const Vector &x, Vector &k) { // K_hpm.Mult(x, z); } /****************************************************************************** * FE_Evolution::build_dij_matrix(const Vector &U, const VectorFunctionCoefficient &velocity) * Purpose: * ***************************************************************************/ void FE_Evolution::build_dij_matrix(const Vector &U, const VectorFunctionCoefficient &velocity) { cout << "Build dij\n"; const int m = dij_sparse.Height(); const int *I = K_spmat.HostReadI(), *J = K_spmat.HostReadJ(); const double *K_data = K_spmat.HostReadData(); const double *KT_data = KT_spmat.HostReadData(); for (int i = 0, k = 0; i < m; i++) { double rowsum = 0; for (int end = I[i+1]; k < end; k++) { int j = J[k]; // global index if (i != j) { double kij = K_spmat(i,j); double kji = KT_spmat(i,j); double dij = fmax(abs(kij), abs(kji)); dij_sparse(i,j) = dij; rowsum += dij; } else { dij_sparse(i, j) = 0.; // Need to clear entry before Mult() } } dii(i) = -1 * rowsum; // To be used in Mult() } cout << "Finished dij matrix.\n"; } /****************************************************************************** * FE_Evolution::calculate_timestep() * Purpose: * Compute maximum timestep according to global CFL condition outline in * Corollary 4.2 in Guermond 2016. * ***************************************************************************/ void FE_Evolution::calculate_timestep() { cout << "Calculating timestep.\n"; int n = lumpedM.Size(); double t_min = 0; double t_temp = 0; for (int i = 0; i < n; i++) { assert(lumpedM(i) > 0); // Assumption, equation (3.6) t_temp = lumpedM(i) / (2. * abs(dii[i])); if (i == 0) { t_min = t_temp; } // t_min must be initialized to something other than 0 else // all other indices { if (t_temp < t_min) { t_min = t_temp; } } } this->timestep = t_min; cout << "Finished calculating timestep.\n"; } /****************************************************************************** * FE_Evolution::get_timestep() * Purpose: * Retrieve timestep. * ***************************************************************************/ double FE_Evolution::get_timestep() { MPI_Allreduce(MPI_IN_PLACE, &this->timestep, 1, MPI_DOUBLE, MPI_MIN, pmesh.GetComm()); return timestep; } /****************************************************************************** * FE_Evolution::Mult() * Purpose: * ***************************************************************************/ void FE_Evolution::Mult(const Vector &x, Vector &y) const { const HypreParVector * U = dynamic_cast(&x); Vector * x_global = U->GlobalVector(); int n = x.Size(); Vector z(n), rhs(n); K_hpm->Mult(*U, z); dij_sparse.Mult(*x_global, rhs); z += rhs; assert(lumpedM.Size() == n); y.SetSize(n); // TODO: Somehow y is of size local at this point. Resizing is a bandaid. for (int i = 0; i < n; i++) { double diag_comp = dii(i) * x(i); // Leftover piece from rhs due to sparsity issue in dij. y[i] = ( z[i] + diag_comp ) / lumpedM(i); } assert (y.Size() == n); } /****************************************************************************** * FE_Evolution::~FE_Evolution() * Purpose: * ***************************************************************************/ FE_Evolution::~FE_Evolution() { } // Velocity coefficient void velocity_function(const Vector &x, Vector &v) { int dim = x.Size(); // map to the reference [-1,1] domain Vector X(dim); for (int i = 0; i < dim; i++) { double center = (bb_min[i] + bb_max[i]) * 0.5; X(i) = 2 * (x(i) - center) / (bb_max[i] - bb_min[i]); } // Reset the velocity vector v = 0.0; switch (problem) { case 0: { // Translations in 1D, 2D, and 3D switch (dim) { case 1: v(0) = 1.0; break; case 2: v(0) = sqrt(2./3.); v(1) = sqrt(1./3.); break; case 3: v(0) = sqrt(3./6.); v(1) = sqrt(2./6.); v(2) = sqrt(1./6.); break; } break; } case 1: case 2: { // Clockwise rotation in 2D around the origin const double w = M_PI/2; switch (dim) { case 1: v(0) = 1.0; break; case 2: v(0) = w*X(1); v(1) = -w*X(0); break; case 3: v(0) = w*X(1); v(1) = -w*X(0); v(2) = 0.0; break; } break; } case 3: { // Clockwise twisting rotation in 2D around the origin const double w = M_PI/2; double d = max((X(0)+1.)*(1.-X(0)),0.) * max((X(1)+1.)*(1.-X(1)),0.); d = d*d; switch (dim) { case 1: v(0) = 1.0; break; case 2: v(0) = d*w*X(1); v(1) = -d*w*X(0); break; case 3: v(0) = d*w*X(1); v(1) = -d*w*X(0); v(2) = 0.0; break; } break; } case 4: case 5: { switch (dim) { case 1: v(0) = 1; break; case 2: v(0) = sqrt(1./2.); v(1) = sqrt(1./2.); break; case 3: v(0) = sqrt(1./3.); v(1) = sqrt(1./3.); v(2) = sqrt(1./3.); break; } break; } } } // Exact Solution u(x,t) // Note that u(x,0) = u_0(x) double exact_sol(const Vector &x, const double t) { int dim = x.Size(); // map to the reference [-1,1] domain Vector X(dim); for (int i = 0; i < dim; i++) { double center = (bb_min[i] + bb_max[i]) * 0.5; X(i) = 2 * (x(i) - center) / (bb_max[i] - bb_min[i]); } // Get velocity Vector v = X; velocity_function(X, v); switch (problem) { case 0: case 1: { switch (dim) { case 1: { double val = exp(-40.*pow(X(0)-0.5-v[0]*t,2)); return val; } case 2: case 3: { double rx = 0.45, ry = 0.25, cx = 0., cy = -0.2, w = 10.; if (dim == 3) { const double s = (1. + 0.25*cos(2*M_PI*X(2))); rx *= s; ry *= s; } double val = ( erfc(w*(X(0)-v[0]*t - cx-rx))*erfc(-w*(X(0)-v[0]*t-cx+rx)) * erfc(w*(X(1)-v[1]*t - cy-ry))*erfc(-w*(X(1)-v[1]*t-cy+ry)) )/16; return val; } } } case 2: { double x_ = X(0), y_ = X(1), rho, phi; rho = hypot(x_, y_); phi = atan2(y_, x_); return pow(sin(M_PI*rho),2)*sin(3*phi); } case 3: { const double f = M_PI; return sin(f*(X(0) - v[0]*t))*sin(f*(X(1)-v[1]*t)); } case 4: { double coeff = M_PI; switch (dim) { case 1: { double val = sin(coeff*(X[0] - v[0]*t)); return val; } case 2: { double val = sin(coeff*(X[0]-v[0]*t))*sin(coeff*(X[1]-v[1]*t)); return val; } case 3: { double val = sin(coeff*(X[0]-v[0]*t))*sin(coeff*(X[1]-v[1]*t))*sin(coeff*(X[2]-v[2]*t)); return val; } } } case 5: // step function { switch (dim) { case 1: { if (pow(X[0] - v[0]*t, 2) < 0.25) { return 1.; } else { return 0.; } } case 2: { if (pow(X[0]-v[0]*t,2) + pow(X[1]-v[1]*t, 2) < 0.25) { return 1.; } else{ return 0.; } } } } } return 100.; } // Inflow boundary condition (zero for the problems considered in this example) double inflow_function(const Vector &x) { switch (problem) { case 0: case 1: case 2: case 3: case 4: case 5: return 0.0; } return 0.0; } // Function to test the matrix generated by the conservative convection integrator is in fact // the right matrix we need void test_sparse_matrices(const SparseMatrix &A, const SparseMatrix &AT) { const int *I = A.HostReadI(), *J = A.HostReadJ(); const int n = A.Size(); const double *K_data = A.HostReadData(); const int *IT = AT.HostReadI(), *JT = AT.HostReadJ(); const int nT = AT.Size(); const double *KT_data = AT.HostReadData(); if (!Mpi::Root()) { cout << "n: " << n << " nT: " << nT << endl; for (int i = 0, k = 0; i < n; i++) { // int end = I[i+1], endT = IT[i+1]; // cout << "row i, end: " << end << " endT: " << endT << endl; for (int end = I[i+1]; k < end; k++) { int j = J[k], jT = JT[k]; if (j != jT) { cout << "row i, j: " << j << " jT: " << jT << endl; } // if (i != j) // { // double kij = A[k]; // double kji = AT[k]; // } } } } } void create_global_expansion_matrix(ParFiniteElementSpace &pfes, SparseMatrix & spm) { const int *I = spm.HostReadI(), *J = spm.HostReadJ(); const int n = spm.Height(); const double *K_data = spm.HostReadData(); for (int i = 0, k = 0, i_new = 0; i < n; i++) { for (int end = I[i+1]; k < end; k++) { int j = J[k]; cout << "row i: " << i << " j: " << j << " local j: " << pfes.GetLocalTDofNumber(j) << endl; } } cout << "Num of face neighbord dofs: " << pfes.num_face_nbr_dofs << endl; } // void test_hpm_pgf(HypreParVector U, HypreParMatrix K_hpm) // { // if (!Mpi::Root()) // Just output results on one processor. // { // // U = 1; // tdof // cout << "Testing row sums\n"; // SparseMatrix K_spm, K_diag; // K_hpm.GetDiag(K_spm); // tdof x tdof // K_hpm.MergeDiagAndOffd(K_diag); // tdof x gdof // cout << "K_hpm size: (" << K_hpm.Height() << "," << K_hpm.Width() << ")" << endl; // cout << "K_spm size: (" << K_spm.Height() << "," << K_spm.Width() << ")" << endl; // cout << "K_diag size: (" << K_diag.Height() << "," << K_diag.Width() << ")" << endl; // cout << "U size: " << U.Size() << endl; // // Vector z(U.Size()); // // K_hpm.Mult(U, z); // // cout << "Resulting vector z values: \n"; // // z.Print(cout); // Vector y(U.Size()); // K_spm.Mult(U, y); // Test to see how HypreParVector handles multiplication with mismatched sizes. // cout << "Resulting vector y values: \n"; // y.Print(cout); // Vector DiagRowSums(K_diag.Height()), SpmRowSums(K_spm.Height()); // K_diag.GetRowSums(DiagRowSums); // K_spm.GetRowSums(SpmRowSums); // cout << "(tdof x gdof) Row Sums: \n"; // DiagRowSums.Print(cout); // cout << "K_diag spmat.Print()\n"; // K_diag.Print(cout); // cout << "(tdof x tdof) Row Sums: \n"; // SpmRowSums.Print(cout); // cout << "K_spm spmat.Print()\n"; // K_spm.Print(cout); // assert(false); // terminate the program early // } // } void test_hpm_pgf(HypreParVector U, HypreParMatrix K_hpm) { // *U = 1.; // Set U to ones for testing row sums and mat-vec multiplication Vector y(U.Size()); if (!Mpi::Root()) // Just output results on one processor. { // U = 1; // tdof cout << "Testing row sums\n"; SparseMatrix K_spm_diag, K_spm_merged; K_hpm.GetDiag(K_spm_diag); // tdof x tdof K_hpm.MergeDiagAndOffd(K_spm_merged); // tdof x gdof cout << "K_spm_diag size: (" << K_spm_diag.Height() << "," << K_spm_diag.Width() << ")\n"; cout << "K_spm_merged size: (" << K_spm_merged.Height() << "," << K_spm_merged.Width() << ")\n"; cout << "U size: " << U.Size() << endl; Vector U_full(K_spm_merged.Width()); U_full = 1.0; // See how HypreParVector handles multiplication with mismatched sizes. K_spm_merged.Mult(U_full, y); cout << "Resulting vector y values: \n"; y.Print(); cout << endl; Vector DiagRowSums(K_spm_diag.Height()), MergedRowSums(K_spm_merged.Height()); K_spm_merged.GetRowSums(MergedRowSums); K_spm_diag.GetRowSums(DiagRowSums); cout << "Diag (tdof x tdof) Row Sums: \n"; DiagRowSums.Print(); cout << "Merged (tdof x gdof) Row Sums: \n"; MergedRowSums.Print(); y -= MergedRowSums; cout << "Should be 0s: \n"; y.Print(); } // This will be the way we should implement the mulitplication. By using the hpm. y = 0.0; K_hpm.Mult(U, y); if (!Mpi::Root()) { cout << endl; y.Print(); } } SparseMatrix build_sparse_from_dense(DenseMatrix & dm) { double a; const int m = dm.Height(), n = dm.Width(); Array I, J; Array data; int counter = 0; for (int i = 0; i < m; i++) { I.Append(counter); for (int j = 0; j < n; j++) { a = dm(i,j); if (a != 0) { data.Append(a); J.Append(j); // Increment counter for I array counter++; } } } SparseMatrix spm(I, J, data, m, n); // spm->PrintInfo(cout); // assert(false); return spm; }