
.. _demo_hemlholtz_2d:

Helmholtz equation
==================
Copyright (C) 2018 Samuel Groth

Helmholtz problem in both complex and real modes
In the complex mode, the exact solution is a plane wave propagating at an angle
theta to the positive x-axis. Chosen for comparison with results from Ihlenburg\'s
book \"Finite Element Analysis of Acoustic Scattering\" p138-139.
In real mode, the Method of Manufactured Solutions is used to produce the exact
solution and source term. ::

  import numpy as np
  from dolfinx import (Function, FunctionSpace, UnitSquareMesh, fem,
                       has_petsc_complex)
  from dolfinx.fem.assemble import assemble_scalar
  from dolfinx.io import XDMFFile
  from mpi4py import MPI
  from ufl import FacetNormal, TestFunction, TrialFunction, dx, grad, inner
  
  # wavenumber
  k0 = 4 * np.pi
  
  # approximation space polynomial degree
  deg = 1
  
  # number of elements in each direction of mesh
  n_elem = 128
  
  mesh = UnitSquareMesh(MPI.COMM_WORLD, n_elem, n_elem)
  n = FacetNormal(mesh)
  
  # Source amplitude
  if has_petsc_complex:
      A = 1 + 1j
  else:
      A = 1
  
  
  # Test and trial function space
  V = FunctionSpace(mesh, ("Lagrange", deg))
  
  # Define variational problem
  u = TrialFunction(V)
  v = TestFunction(V)
  f = Function(V)
  f.interpolate(lambda x: A * k0**2 * np.cos(k0 * x[0]) * np.cos(k0 * x[1]))
  a = inner(grad(u), grad(v)) * dx - k0**2 * inner(u, v) * dx
  L = inner(f, v) * dx
  
  # Compute solution
  uh = fem.Function(V)
  uh.name = "u"
  problem = fem.LinearProblem(a, L, u=uh, petsc_options={"ksp_type": "preonly", "pc_type": "lu"})
  problem.solve()
  
  # Save solution in XDMF format (to be viewed in Paraview, for example)
  with XDMFFile(MPI.COMM_WORLD, "plane_wave.xdmf", "w", encoding=XDMFFile.Encoding.HDF5) as file:
      file.write_mesh(mesh)
      file.write_function(uh)
  
Calculate L2 and H1 errors of FEM solution and best approximation.
This demonstrates the error bounds given in Ihlenburg. Pollution errors
are evident for high wavenumbers. ::


  # Function space for exact solution - need it to be higher than deg
  V_exact = FunctionSpace(mesh, ("Lagrange", deg + 3))
  u_exact = Function(V_exact)
  u_exact.interpolate(lambda x: A * np.cos(k0 * x[0]) * np.cos(k0 * x[1]))
  
  # H1 errors
  diff = uh - u_exact
  H1_diff = mesh.mpi_comm().allreduce(assemble_scalar(inner(grad(diff), grad(diff)) * dx), op=MPI.SUM)
  H1_exact = mesh.mpi_comm().allreduce(assemble_scalar(inner(grad(u_exact), grad(u_exact)) * dx), op=MPI.SUM)
  print("Relative H1 error of FEM solution:", abs(np.sqrt(H1_diff) / np.sqrt(H1_exact)))
  
  # L2 errors
  L2_diff = mesh.mpi_comm().allreduce(assemble_scalar(inner(diff, diff) * dx), op=MPI.SUM)
  L2_exact = mesh.mpi_comm().allreduce(assemble_scalar(inner(u_exact, u_exact) * dx), op=MPI.SUM)
  print("Relative L2 error of FEM solution:", abs(np.sqrt(L2_diff) / np.sqrt(L2_exact)))
