// MFEM Example 36 // // Compile with: make ex36 // // Sample runs: ex36 -o 2 // ex36 -o 2 -r 4 // // Description: This example code demonstrates the use of MFEM to solve the // bound-constrained energy minimization problem // // minimize ||∇u||² subject to u ≥ ϕ in H¹₀. // // This is known as the obstacle problem, and it is a simple // mathematical model for contact mechanics. // // In this example, the obstacle ϕ is a half-sphere centered // at the origin of a circular domain Ω. After solving to a // specified tolerance, the numerical solution is compared to // a closed-form exact solution to assess accuracy. // // The problem is discretized and solved using the proximal // Galerkin finite element method, introduced by Keith and // Surowiec [1]. // // This example highlights the ability of MFEM to deliver high- // order solutions to variation inequality problems and // showcases how to set up and solve nonlinear mixed methods. // // [1] Keith, B. and Surowiec, T. (2023) Proximal Galerkin: A structure- // preserving finite element method for pointwise bound constraints. // arXiv:2307.12444 [math.NA] #include "mfem.hpp" #include #include using namespace std; using namespace mfem; double spherical_obstacle(const Vector &pt); double exact_solution_obstacle(const Vector &pt); void exact_solution_gradient_obstacle(const Vector &pt, Vector &grad); class LogarithmGridFunctionCoefficient : public Coefficient { protected: GridFunction *u; // grid function Coefficient *obstacle; double min_val; public: LogarithmGridFunctionCoefficient(GridFunction &u_, Coefficient &obst_, double min_val_=-36) : u(&u_), obstacle(&obst_), min_val(min_val_) { } virtual double Eval(ElementTransformation &T, const IntegrationPoint &ip); }; class ExponentialGridFunctionCoefficient : public Coefficient { protected: GridFunction *u; Coefficient *obstacle; double min_val; double max_val; public: ExponentialGridFunctionCoefficient(GridFunction &u_, Coefficient &obst_, double min_val_=0.0, double max_val_=1e6) : u(&u_), obstacle(&obst_), min_val(min_val_), max_val(max_val_) { } virtual double Eval(ElementTransformation &T, const IntegrationPoint &ip); }; int main(int argc, char *argv[]) { // 1. Parse command-line options. int order = 1; int max_it = 10; int ref_levels = 3; double alpha = 1.0; double tol = 1e-5; bool visualization = true; OptionsParser args(argc, argv); args.AddOption(&order, "-o", "--order", "Finite element order (polynomial degree)."); args.AddOption(&ref_levels, "-r", "--refs", "Number of h-refinements."); args.AddOption(&max_it, "-mi", "--max-it", "Maximum number of iterations"); args.AddOption(&tol, "-tol", "--tol", "Stopping criteria based on the difference between" "successive solution updates"); args.AddOption(&alpha, "-step", "--step", "Step size alpha"); 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); // 2. Read the mesh from the mesh file. const char *mesh_file = "../data/disc-nurbs.mesh"; Mesh mesh(mesh_file, 1, 1); int dim = mesh.Dimension(); // 3. Postprocess the mesh. // 3A. Refine the mesh to increase the resolution. for (int l = 0; l < ref_levels; l++) { mesh.UniformRefinement(); } // 3B. Interpolate the geometry after refinement to control geometry error. // NOTE: Minimum second-order interpolation is used to improve the accuracy. int curvature_order = max(order,2); mesh.SetCurvature(curvature_order); // 3C. Rescale the domain to a unit circle (radius = 1). GridFunction *nodes = mesh.GetNodes(); double scale = 2*sqrt(2); *nodes /= scale; // 4. Define the necessary finite element spaces on the mesh. H1_FECollection H1fec(order+1, dim); FiniteElementSpace H1fes(&mesh, &H1fec); L2_FECollection L2fec(order-1, dim); FiniteElementSpace L2fes(&mesh, &L2fec); cout << "Number of H1 finite element unknowns: " << H1fes.GetTrueVSize() << endl; cout << "Number of L2 finite element unknowns: " << L2fes.GetTrueVSize() << endl; Array offsets(3); offsets[0] = 0; offsets[1] = H1fes.GetVSize(); offsets[2] = L2fes.GetVSize(); offsets.PartialSum(); BlockVector x(offsets), rhs(offsets); x = 0.0; rhs = 0.0; // 5. Determine the list of true (i.e. conforming) essential boundary dofs. Array ess_bdr; if (mesh.bdr_attributes.Size()) { ess_bdr.SetSize(mesh.bdr_attributes.Max()); ess_bdr = 1; } // 6. Define an initial guess for the solution. auto IC_func = [](const Vector &x) { double r0 = 1.0; double rr = 0.0; for (int i=0; iGetValue(T, ip) - obstacle->Eval(T, ip); return max(min_val, log(val)); } double ExponentialGridFunctionCoefficient::Eval(ElementTransformation &T, const IntegrationPoint &ip) { MFEM_ASSERT(u != NULL, "grid function is not set"); double val = u->GetValue(T, ip); return min(max_val, max(min_val, exp(val) + obstacle->Eval(T, ip))); } double spherical_obstacle(const Vector &pt) { double x = pt(0), y = pt(1); double r = sqrt(x*x + y*y); double r0 = 0.5; double beta = 0.9; double b = r0*beta; double tmp = sqrt(r0*r0 - b*b); double B = tmp + b*b/tmp; double C = -b/tmp; if (r > b) { return B + r * C; } else { return sqrt(r0*r0 - r*r); } } double exact_solution_obstacle(const Vector &pt) { double x = pt(0), y = pt(1); double r = sqrt(x*x + y*y); double r0 = 0.5; double a = 0.348982574111686; double A = -0.340129705945858; if (r > a) { return A * log(r); } else { return sqrt(r0*r0-r*r); } } void exact_solution_gradient_obstacle(const Vector &pt, Vector &grad) { double x = pt(0), y = pt(1); double r = sqrt(x*x + y*y); double r0 = 0.5; double a = 0.348982574111686; double A = -0.340129705945858; if (r > a) { grad(0) = A * x / (r*r); grad(1) = A * y / (r*r); } else { grad(0) = - x / sqrt( r0*r0 - r*r ); grad(1) = - y / sqrt( r0*r0 - r*r ); } }