// 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. // // ---------------------------------------------------------------- // ParHeat Miniapp: Gradients of PDE constrained objective function // ---------------------------------------------------------------- // (Parallel Version) // // The following example computes the gradients of a specified objective // function with respect to parametric fields. The objective function is having // the following form f(u(\rho)) where u(\rho) is a solution of a specific state // problem (in the example that is the diffusion equation), and \rho is a // parametric field discretized by finite elements. The parametric field (also // called density in topology optimization) controls the coefficients of the // state equation. For the considered case, the density controls the diffusion // coefficient within the computational domain. // // For more information, the users are referred to: // // Hinze, M.; Pinnau, R.; Ulbrich, M. & Ulbrich, S. // Optimization with PDE Constraints // Springer Netherlands, 2009 // // Bendsøe, M. P. & Sigmund, O. // Topology Optimization - Theory, Methods and Applications // Springer Verlag, Berlin Heidelberg, 2003 // // Compile with: make parheat // // Sample runs: // // mpirun -np 4 parheat --visualization // mpirun -np 4 parheat --visualization -m ../../data/beam-quad.mesh #include "mfem.hpp" #include #include #include "pparamnonlinearform.hpp" #include "mtop_integrators.hpp" int main(int argc, char *argv[]) { // 1. Initialize MPI and HYPRE. mfem::Mpi::Init(argc, argv); int myrank = mfem::Mpi::WorldRank(); mfem::Hypre::Init(); // Parse command-line options. const char *mesh_file = "../../data/star.mesh"; int order = 1; bool static_cond = false; int ser_ref_levels = 1; int par_ref_levels = 1; double newton_rel_tol = 1e-7; double newton_abs_tol = 1e-12; int newton_iter = 10; int print_level = 1; bool visualization = false; mfem::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", "Finite element order (polynomial degree) or -1 for" " isoparametric space."); args.AddOption(&visualization, "-vis", "--visualization", "-no-vis", "--no-visualization", "Enable or disable GLVis visualization."); args.AddOption(&static_cond, "-sc", "--static-condensation", "-no-sc", "--no-static-condensation", "Enable static condensation."); args.AddOption(&newton_rel_tol, "-rel", "--relative-tolerance", "Relative tolerance for the Newton solve."); args.AddOption(&newton_abs_tol, "-abs", "--absolute-tolerance", "Absolute tolerance for the Newton solve."); args.AddOption(&newton_iter, "-it", "--newton-iterations", "Maximum iterations for the Newton solve."); args.Parse(); if (!args.Good()) { if (myrank == 0) { args.PrintUsage(std::cout); } return 1; } if (myrank == 0) { args.PrintOptions(std::cout); } // 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. mfem::Mesh mesh(mesh_file, 1, 1); int dim = mesh.Dimension(); // 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 10,000 elements. { int ref_levels = (int)floor(log(10000./mesh.GetNE())/log(2.)/dim); for (int l = 0; l < ref_levels; l++) { mesh.UniformRefinement(); } } // 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. mfem::ParMesh pmesh(MPI_COMM_WORLD, mesh); mesh.Clear(); { for (int l = 0; l < par_ref_levels; l++) { pmesh.UniformRefinement(); } } // Define the Diffusion coefficient. mfem::ConstantCoefficient* diffco=new mfem::ConstantCoefficient(1.0); // Define the Heat source. mfem::ConstantCoefficient* loadco=new mfem::ConstantCoefficient(1.0); // Define the q-function. mfem::QLinearDiffusion* qfun=new mfem::QLinearDiffusion(*diffco,*loadco,1.0, 1e-7,4.0,0.5); // Define FE collection and space for the state solution. mfem::H1_FECollection sfec(order, dim); mfem::ParFiniteElementSpace* sfes=new mfem::ParFiniteElementSpace(&pmesh,&sfec, 1); // Define FE collection and space for the density field. mfem::L2_FECollection pfec(order, dim); mfem::ParFiniteElementSpace* pfes=new mfem::ParFiniteElementSpace(&pmesh,&pfec, 1); // Define the arrays for the nonlinear form. mfem::Array asfes; mfem::Array apfes; asfes.Append(sfes); apfes.Append(pfes); // Define parametric block nonlinear form using single scalar H1 field // and L2 scalar density field. mfem::ParParametricBNLForm* nf=new mfem::ParParametricBNLForm(asfes,apfes); // Add a parametric integrator. nf->AddDomainIntegrator(new mfem::ParametricLinearDiffusion(*qfun)); // Define true block vectors for state, adjoint, resudual. mfem::BlockVector solbv; solbv.Update(nf->GetBlockTrueOffsets()); solbv=0.0; mfem::BlockVector adjbv; adjbv.Update(nf->GetBlockTrueOffsets()); adjbv=0.0; mfem::BlockVector resbv; resbv.Update(nf->GetBlockTrueOffsets()); resbv=0.0; // Define true block vectors for parametric field and gradients. mfem::BlockVector prmbv; prmbv.Update(nf->ParamGetBlockTrueOffsets()); prmbv=0.0; mfem::BlockVector grdbv; grdbv.Update(nf->ParamGetBlockTrueOffsets()); grdbv=0.0; // Set the BCs for the physics. mfem::Array *> ess_bdr; mfem::Array ess_rhs; ess_bdr.Append(new mfem::Array(pmesh.bdr_attributes.Max())); ess_rhs.Append(nullptr); (*ess_bdr[0]) = 1; nf->SetEssentialBC(ess_bdr,ess_rhs); delete ess_bdr[0]; // Set the density field to 0.5. prmbv=0.5; // Set the density as parametric field in the parametric BNLForm. nf->SetParamFields(prmbv); //set the density // Compute the stiffness/tangent matrix for density prmbv=0.5. mfem::BlockOperator *A = &nf->GetGradient(solbv); mfem::HypreBoomerAMG* prec=new mfem::HypreBoomerAMG(); prec->SetPrintLevel(print_level); // Use only block (0,0) as in this case we have a single field. prec->SetOperator(A->GetBlock(0,0)); // Construct block preconditioner for the BNLForm. mfem::BlockDiagonalPreconditioner *blpr = new mfem::BlockDiagonalPreconditioner( nf->GetBlockTrueOffsets()); blpr->SetDiagonalBlock(0,prec); // Define the solvers. mfem::GMRESSolver *gmres; gmres = new mfem::GMRESSolver(MPI_COMM_WORLD); gmres->SetAbsTol(newton_abs_tol/10); gmres->SetRelTol(newton_rel_tol/10); gmres->SetMaxIter(100); gmres->SetPrintLevel(print_level); gmres->SetPreconditioner(*blpr); gmres->SetOperator(*A); // Solve the problem. solbv=0.0; nf->Mult(solbv,resbv); resbv.Neg(); //compute RHS gmres->Mult(resbv, solbv); // Compute the energy of the state system. double energy = nf->GetEnergy(solbv); if (myrank==0) { std::cout << "energy =" << energy << std::endl; } // Define the block nonlinear form utilized for representing the objective - // use the state array from the BNLForm. mfem::ParBlockNonlinearForm* ob=new mfem::ParBlockNonlinearForm(asfes); // Add the integrator for the objective. ob->AddDomainIntegrator(new mfem::DiffusionObjIntegrator()); // Compute the objective. double obj=ob->GetEnergy(solbv); if (myrank==0) { std::cout << "Objective =" << obj << std::endl; } // Solve the adjoint. { mfem::BlockVector adjrhs; adjrhs.Update(nf->GetBlockTrueOffsets()); adjrhs=0.0; // Compute the RHS for the adjoint, i.e., the gradients with respect to // the parametric fields. ob->Mult(solbv, adjrhs); // Get the tangent matrix from the state problem. We do not need to // transpose the operator for diffusion. Compute the adjoint solution. gmres->Mult(adjrhs, adjbv); } // Compute gradients. // First set the adjoint field. nf->SetAdjointFields(adjbv); // Set the state field. nf->SetStateFields(solbv); // Call the parametric Mult. nf->ParamMult(prmbv, grdbv); // Dump out the data. if (visualization) { mfem::ParaViewDataCollection *dacol=new mfem::ParaViewDataCollection("ParHeat", &pmesh); mfem::ParGridFunction gfgrd(pfes); gfgrd.SetFromTrueDofs(grdbv.GetBlock(0)); mfem::ParGridFunction gfdns(pfes); gfdns.SetFromTrueDofs(prmbv.GetBlock(0)); // Define state grid function. mfem::ParGridFunction gfsol(sfes); gfsol.SetFromTrueDofs(solbv.GetBlock(0)); mfem::ParGridFunction gfadj(sfes); gfadj.SetFromTrueDofs(adjbv.GetBlock(0)); dacol->SetLevelsOfDetail(order); dacol->RegisterField("sol", &gfsol); dacol->RegisterField("adj", &gfadj); dacol->RegisterField("dns", &gfdns); dacol->RegisterField("grd", &gfgrd); dacol->SetTime(1.0); dacol->SetCycle(1); dacol->Save(); delete dacol; } // FD check { mfem::BlockVector prtbv; mfem::BlockVector tmpbv; prtbv.Update(nf->ParamGetBlockTrueOffsets()); tmpbv.Update(nf->ParamGetBlockTrueOffsets()); prtbv.GetBlock(0).Randomize(); prtbv*=1.0; double lsc=1.0; double gQoI=ob->GetEnergy(solbv); double lQoI; double nd=mfem::InnerProduct(MPI_COMM_WORLD,prtbv,prtbv); double td=mfem::InnerProduct(MPI_COMM_WORLD,prtbv,grdbv); td=td/nd; for (int l = 0; l < 10; l++) { lsc/=10.0; prtbv/=10.0; add(prmbv,prtbv,tmpbv); nf->SetParamFields(tmpbv); // Solve the physics. solbv=0.0; nf->Mult(solbv,resbv); resbv.Neg(); //compute RHS A = &nf->GetGradient(solbv); prec->SetPrintLevel(0); prec->SetOperator(A->GetBlock(0,0)); gmres->SetOperator(*A); gmres->SetPrintLevel(0); gmres->Mult(resbv,solbv); // Compute the objective. lQoI=ob->GetEnergy(solbv); double ld=(lQoI-gQoI)/lsc; if (myrank==0) { std::cout << "dx=" << lsc <<" FD approximation=" << ld/nd << " adjoint gradient=" << td << " err=" << std::fabs(ld/nd-td) << std::endl; } } } delete ob; delete gmres; delete blpr; delete prec; delete nf; delete pfes; delete sfes; delete qfun; delete loadco; delete diffco; return 0; }