int main(int argc, char *argv[]) { // 1. Initialize MPI. int num_procs, myid; MPI_Init(&argc, &argv); MPI_Comm_size(MPI_COMM_WORLD, &num_procs); MPI_Comm_rank(MPI_COMM_WORLD, &myid); // 2. Parse command-line options. const char *mesh_file = "../data/beam-quad.mesh"; int ser_ref_levels = 2; int par_ref_levels = 0; int order = 2; int ode_solver_type = 3; double t_final = 300.0; double dt = 3; double visc = 1e-2; bool visualization = true; int vis_steps = 1; OptionsParser args(argc, argv); args.AddOption(&mesh_file, "-m", "--mesh", "Mesh file to use."); 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(&ode_solver_type, "-s", "--ode-solver", "ODE solver: 1 - Backward Euler, 2 - SDIRK2, 3 - SDIRK3,\n\t" "\t 11 - Forward Euler, 12 - RK2, 13 - RK3 SSP, 14 - RK4."); args.AddOption(&t_final, "-tf", "--t-final", "Final time; start time is 0."); args.AddOption(&dt, "-dt", "--time-step", "Time step."); args.AddOption(&visc, "-v", "--viscosity", "Viscosity coefficient."); args.AddOption(&visualization, "-vis", "--visualization", "-no-vis", "--no-visualization", "Enable or disable GLVis visualization."); args.AddOption(&vis_steps, "-vs", "--visualization-steps", "Visualize every n-th timestep."); args.Parse(); if (!args.Good()) { if (myid == 0) args.PrintUsage(cout); MPI_Finalize(); return 1; } if (myid == 0) args.PrintOptions(cout); // 3. Read the serial mesh from the given mesh file on all processors. We can // handle triangular, quadrilateral, tetrahedral and hexahedral meshes // with the same code. Mesh *mesh; ifstream imesh(mesh_file); if (!imesh) { if (myid == 0) cerr << "\nCan not open mesh file: " << mesh_file << '\n' << endl; MPI_Finalize(); return 2; } mesh = new Mesh(imesh, 1, 1); imesh.close(); int dim = mesh->Dimension(); // 4. Define the ODE solver used for time integration. Several implicit // singly diagonal implicit Runge-Kutta (SDIRK) methods, as well as // explicit Runge-Kutta methods are available. ODESolver *ode_solver; switch (ode_solver_type) { // Implicit L-stable methods case 1: ode_solver = new BackwardEulerSolver; break; case 2: ode_solver = new SDIRK23Solver(2); break; case 3: ode_solver = new SDIRK33Solver; break; // Explicit methods case 11: ode_solver = new ForwardEulerSolver; break; case 12: ode_solver = new RK2Solver(0.5); break; // midpoint method case 13: ode_solver = new RK3SSPSolver; break; case 14: ode_solver = new RK4Solver; 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 (myid == 0) cout << "Unknown ODE solver type: " << ode_solver_type << '\n'; MPI_Finalize(); 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. for (int lev = 0; lev < ser_ref_levels; lev++) mesh->UniformRefinement(); // 6. Define a 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(); // 7. Define the parallel vector finite element spaces representing the mesh // deformation x_gf, the velocity v_gf, and the initial configuration, // x_ref. Define also the elastic energy density, w_gf, which is in a // discontinuous higher-order space. Since x and v are integrated in time // as a system, we group them together in block vector vx, on the unique // parallel degrees of freedom, with offsets given by array true_offset. H1_FECollection fe_coll(order, dim); ParFiniteElementSpace fespace(pmesh, &fe_coll, dim); int glob_size = fespace.GlobalTrueVSize(); if (myid == 0) cout << "Number of velocity/deformation unknowns: " << glob_size << endl; int true_size = fespace.TrueVSize(); Array<int> true_offset(3); true_offset[0] = 0; true_offset[1] = true_size; true_offset[2] = 2*true_size; BlockVector vx(true_offset); ParGridFunction v_gf(&fespace), x_gf(&fespace); ParGridFunction x_ref(&fespace); pmesh->GetNodes(x_ref); L2_FECollection w_fec(order + 1, dim); ParFiniteElementSpace w_fespace(pmesh, &w_fec); ParGridFunction w_gf(&w_fespace); // 8. Set the initial conditions for v_gf, x_gf and vx, and define the // boundary conditions on a beam-like mesh (see description above). VectorFunctionCoefficient velo(dim, InitialVelocity); v_gf.ProjectCoefficient(velo); VectorFunctionCoefficient deform(dim, InitialDeformation); x_gf.ProjectCoefficient(deform); v_gf.GetTrueDofs(vx.GetBlock(0)); x_gf.GetTrueDofs(vx.GetBlock(1)); Array<int> ess_bdr(fespace.GetMesh()->bdr_attributes.Max()); ess_bdr = 0; ess_bdr[0] = 1; // boundary attribute 1 (index 0) is fixed // 9. Initialize the hyperelastic operator, the GLVis visualization and print // the initial energies. HyperelasticOperator oper(fespace, ess_bdr, visc); socketstream vis_v, vis_w; if (visualization) { char vishost[] = "localhost"; int visport = 19916; vis_v.open(vishost, visport); vis_v.precision(8); visualize(vis_v, pmesh, &x_gf, &v_gf, "Velocity", true); // Make sure all ranks have sent their 'v' solution before initiating // another set of GLVis connections (one from each rank): MPI_Barrier(pmesh->GetComm()); vis_w.open(vishost, visport); if (vis_w) { oper.GetElasticEnergyDensity(x_gf, w_gf); vis_w.precision(8); visualize(vis_w, pmesh, &x_gf, &w_gf, "Elastic energy density", true); } } double ee0 = oper.ElasticEnergy(x_gf); double ke0 = oper.KineticEnergy(v_gf); if (myid == 0) { cout << "initial elastic energy (EE) = " << ee0 << endl; cout << "initial kinetic energy (KE) = " << ke0 << endl; cout << "initial total energy (TE) = " << (ee0 + ke0) << endl; } // 10. Perform time-integration (looping over the time iterations, ti, with a // time-step dt). ode_solver->Init(oper); double t = 0.0; bool last_step = false; for (int ti = 1; !last_step; ti++) { if (t + dt >= t_final - dt/2) last_step = true; ode_solver->Step(vx, t, dt); if (last_step || (ti % vis_steps) == 0) { v_gf.Distribute(vx.GetBlock(0)); x_gf.Distribute(vx.GetBlock(1)); double ee = oper.ElasticEnergy(x_gf); double ke = oper.KineticEnergy(v_gf); if (myid == 0) cout << "step " << ti << ", t = " << t << ", EE = " << ee << ", KE = " << ke << ", ΔTE = " << (ee+ke)-(ee0+ke0) << endl; if (visualization) { visualize(vis_v, pmesh, &x_gf, &v_gf); if (vis_w) { oper.GetElasticEnergyDensity(x_gf, w_gf); visualize(vis_w, pmesh, &x_gf, &w_gf); } } } } // 11. Save the displaced mesh, the velocity and elastic energy. { GridFunction *nodes = &x_gf; int owns_nodes = 0; pmesh->SwapNodes(nodes, owns_nodes); ostringstream mesh_name, velo_name, ee_name; mesh_name << "deformed." << setfill('0') << setw(6) << myid; velo_name << "velocity." << setfill('0') << setw(6) << myid; ee_name << "elastic_energy." << setfill('0') << setw(6) << myid; ofstream mesh_ofs(mesh_name.str().c_str()); mesh_ofs.precision(8); pmesh->Print(mesh_ofs); pmesh->SwapNodes(nodes, owns_nodes); ofstream velo_ofs(velo_name.str().c_str()); velo_ofs.precision(8); v_gf.Save(velo_ofs); ofstream ee_ofs(ee_name.str().c_str()); ee_ofs.precision(8); oper.GetElasticEnergyDensity(x_gf, w_gf); w_gf.Save(ee_ofs); } // 10. Free the used memory. delete ode_solver; delete pmesh; MPI_Finalize(); return 0; }
int main(int argc, char *argv[]) { // 1. Initialize MPI. int num_procs, myid; MPI_Init(&argc, &argv); MPI_Comm_size(MPI_COMM_WORLD, &num_procs); MPI_Comm_rank(MPI_COMM_WORLD, &myid); // 2. Parse command-line options. const char *mesh_file = "../data/beam-tri.mesh"; int order = 1; int nev = 5; bool visualization = 1; bool amg_elast = 0; OptionsParser args(argc, argv); args.AddOption(&mesh_file, "-m", "--mesh", "Mesh file to use."); args.AddOption(&order, "-o", "--order", "Finite element order (polynomial degree)."); args.AddOption(&nev, "-n", "--num-eigs", "Number of desired eigenmodes."); args.AddOption(&amg_elast, "-elast", "--amg-for-elasticity", "-sys", "--amg-for-systems", "Use the special AMG elasticity solver (GM/LN approaches), " "or standard AMG for systems (unknown approach)."); args.AddOption(&visualization, "-vis", "--visualization", "-no-vis", "--no-visualization", "Enable or disable GLVis visualization."); args.Parse(); if (!args.Good()) { if (myid == 0) { args.PrintUsage(cout); } MPI_Finalize(); return 1; } if (myid == 0) { args.PrintOptions(cout); } // 3. Read the (serial) mesh from the given mesh file on all processors. We // can handle triangular, quadrilateral, tetrahedral, hexahedral, surface // and volume meshes with the same code. Mesh *mesh = new Mesh(mesh_file, 1, 1); int dim = mesh->Dimension(); if (mesh->attributes.Max() < 2) { if (myid == 0) cerr << "\nInput mesh should have at least two materials!" << " (See schematic in ex12p.cpp)\n" << endl; MPI_Finalize(); return 3; } // 4. Select the order of the finite element discretization space. For NURBS // meshes, we increase the order by degree elevation. if (mesh->NURBSext && order > mesh->NURBSext->GetOrder()) { mesh->DegreeElevate(order - mesh->NURBSext->GetOrder()); } // 5. Refine the serial mesh on all processors to increase the resolution. In // this example we do 'ref_levels' of uniform refinement. We choose // 'ref_levels' to be the largest number that gives a final mesh with no // more than 1,000 elements. { int ref_levels = (int)floor(log(1000./mesh->GetNE())/log(2.)/dim); for (int l = 0; l < ref_levels; l++) { mesh->UniformRefinement(); } } // 6. Define a 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; { int par_ref_levels = 1; for (int l = 0; l < par_ref_levels; l++) { pmesh->UniformRefinement(); } } // 7. Define a parallel finite element space on the parallel mesh. Here we // use vector finite elements, i.e. dim copies of a scalar finite element // space. We use the ordering by vector dimension (the last argument of // the FiniteElementSpace constructor) which is expected in the systems // version of BoomerAMG preconditioner. For NURBS meshes, we use the // (degree elevated) NURBS space associated with the mesh nodes. FiniteElementCollection *fec; ParFiniteElementSpace *fespace; const bool use_nodal_fespace = pmesh->NURBSext && !amg_elast; if (use_nodal_fespace) { fec = NULL; fespace = (ParFiniteElementSpace *)pmesh->GetNodes()->FESpace(); } else { fec = new H1_FECollection(order, dim); fespace = new ParFiniteElementSpace(pmesh, fec, dim, Ordering::byVDIM); } HYPRE_Int size = fespace->GlobalTrueVSize(); if (myid == 0) { cout << "Number of unknowns: " << size << endl << "Assembling: " << flush; } // 8. Set up the parallel bilinear forms a(.,.) and m(.,.) on the finite // element space corresponding to the linear elasticity integrator with // piece-wise constants coefficient lambda and mu, a simple mass matrix // needed on the right hand side of the generalized eigenvalue problem // below. The boundary conditions are implemented by marking only boundary // attribute 1 as essential. We use special values on the diagonal to // shift the Dirichlet eigenvalues out of the computational range. After // serial/parallel assembly we extract the corresponding parallel matrices // A and M. Vector lambda(pmesh->attributes.Max()); lambda = 1.0; lambda(0) = lambda(1)*50; PWConstCoefficient lambda_func(lambda); Vector mu(pmesh->attributes.Max()); mu = 1.0; mu(0) = mu(1)*50; PWConstCoefficient mu_func(mu); Array<int> ess_bdr(pmesh->bdr_attributes.Max()); ess_bdr = 0; ess_bdr[0] = 1; ParBilinearForm *a = new ParBilinearForm(fespace); a->AddDomainIntegrator(new ElasticityIntegrator(lambda_func, mu_func)); if (myid == 0) { cout << "matrix ... " << flush; } a->Assemble(); a->EliminateEssentialBCDiag(ess_bdr, 1.0); a->Finalize(); ParBilinearForm *m = new ParBilinearForm(fespace); m->AddDomainIntegrator(new VectorMassIntegrator()); m->Assemble(); // shift the eigenvalue corresponding to eliminated dofs to a large value m->EliminateEssentialBCDiag(ess_bdr, numeric_limits<double>::min()); m->Finalize(); if (myid == 0) { cout << "done." << endl; } HypreParMatrix *A = a->ParallelAssemble(); HypreParMatrix *M = m->ParallelAssemble(); delete a; delete m; // 9. Define and configure the LOBPCG eigensolver and the BoomerAMG // preconditioner for A to be used within the solver. Set the matrices // which define the generalized eigenproblem A x = lambda M x. HypreBoomerAMG * amg = new HypreBoomerAMG(*A); amg->SetPrintLevel(0); if (amg_elast) { amg->SetElasticityOptions(fespace); } else { amg->SetSystemsOptions(dim); } HypreLOBPCG * lobpcg = new HypreLOBPCG(MPI_COMM_WORLD); lobpcg->SetNumModes(nev); lobpcg->SetPreconditioner(*amg); lobpcg->SetMaxIter(100); lobpcg->SetTol(1e-8); lobpcg->SetPrecondUsageMode(1); lobpcg->SetPrintLevel(1); lobpcg->SetMassMatrix(*M); lobpcg->SetOperator(*A); // 10. Compute the eigenmodes and extract the array of eigenvalues. Define a // parallel grid function to represent each of the eigenmodes returned by // the solver. Array<double> eigenvalues; lobpcg->Solve(); lobpcg->GetEigenvalues(eigenvalues); ParGridFunction x(fespace); // 11. For non-NURBS meshes, make the mesh curved based on the finite element // space. This means that we define the mesh elements through a fespace // based transformation of the reference element. This allows us to save // the displaced mesh as a curved mesh when using high-order finite // element displacement field. We assume that the initial mesh (read from // the file) is not higher order curved mesh compared to the chosen FE // space. if (!use_nodal_fespace) { pmesh->SetNodalFESpace(fespace); } // 12. Save the refined mesh and the modes in parallel. This output can be // viewed later using GLVis: "glvis -np <np> -m mesh -g mode". { ostringstream mesh_name, mode_name; mesh_name << "mesh." << setfill('0') << setw(6) << myid; ofstream mesh_ofs(mesh_name.str().c_str()); mesh_ofs.precision(8); pmesh->Print(mesh_ofs); for (int i=0; i<nev; i++) { // convert eigenvector from HypreParVector to ParGridFunction x = lobpcg->GetEigenvector(i); mode_name << "mode_" << setfill('0') << setw(2) << i << "." << setfill('0') << setw(6) << myid; ofstream mode_ofs(mode_name.str().c_str()); mode_ofs.precision(8); x.Save(mode_ofs); mode_name.str(""); } } // 13. Send the above data by socket to a GLVis server. Use the "n" and "b" // keys in GLVis to visualize the displacements. if (visualization) { char vishost[] = "localhost"; int visport = 19916; socketstream mode_sock(vishost, visport); for (int i=0; i<nev; i++) { if ( myid == 0 ) { cout << "Eigenmode " << i+1 << '/' << nev << ", Lambda = " << eigenvalues[i] << endl; } // convert eigenvector from HypreParVector to ParGridFunction x = lobpcg->GetEigenvector(i); mode_sock << "parallel " << num_procs << " " << myid << "\n" << "solution\n" << *pmesh << x << flush << "window_title 'Eigenmode " << i+1 << '/' << nev << ", Lambda = " << eigenvalues[i] << "'" << endl; char c; if (myid == 0) { cout << "press (q)uit or (c)ontinue --> " << flush; cin >> c; } MPI_Bcast(&c, 1, MPI_CHAR, 0, MPI_COMM_WORLD); if (c != 'c') { break; } } mode_sock.close(); }