Got the least-squares approach to work for the acoustic wave equation that includes inlet and outlet radiation conditions. There is no need for special saddle-point preconditioners, since the least-squares functional is symmetric and positive-definite. The conjugate gradient solver with Jacobi preconditioning works reliably. Solved the horn problem below with 1.8 million DoFs. Used a mixed element where pressure is approximated using Lagrange functions and velocity is represented using Raviart-Thomas functions.
import meshio
msh = meshio.read("AcousticRadiation3.msh")
meshio.write("mesh.xdmf", meshio.Mesh(points=msh.points, cells={"tetra": msh.cells["tetra"]}))
from dolfin import *
import numpy as np
import matplotlib.pyplot as plt
import os, sys, traceback
k0 = 4.0
class Hard(SubDomain):
def inside(self, x, on_boundary):
return on_boundary
class InputBC(SubDomain):
def inside(self, x, on_boundary):
return on_boundary and near(x[2], -3.0, tol)
class OutputBC(SubDomain):
def inside(self, x, on_boundary):
rr = sqrt(x[0] * x[0] + x[1] * x[1] + x[2] * x[2])
rra = sqrt(x[0] * x[0] + x[1] * x[1])
return on_boundary and (near(rr, 2.0, 5.0e-2) and (x[2] >= 0.0))
mesh = Mesh()
with XDMFFile("mesh.xdmf") as infile:
infile.read(mesh)
mvc = MeshValueCollection("size_t", mesh, 3)
info(mesh)
#plot(mesh)
#plt.show()
# Mark boundaries
sub_domains = MeshFunction("size_t", mesh, mesh.topology().dim() - 1)
sub_domains.set_all(3)
hard = Hard()
hard.mark(sub_domains, 0)
in_port = InputBC()
in_port.mark(sub_domains, 1)
out_port = OutputBC()
out_port.mark(sub_domains, 2)
File("BoxSubDomains.pvd").write(sub_domains)
# Set up function spaces
# For low order problem
cell = tetrahedron
VelocityFn = FiniteElement('RT', cell, 2) # Velocity
PressureFn = FiniteElement('Lagrange', cell, 2) # Lagrange element for pressure
element = FunctionSpace(mesh, MixedElement([VelocityFn, VelocityFn, PressureFn, PressureFn]))
u_r, u_i, p_r, p_i = TrialFunctions(element)
v_r, v_i, q_r, q_i = TestFunctions(element)
#surface integral definitions from boundaries
ds = Measure('ds', domain = mesh, subdomain_data = sub_domains)
# with source and sink terms
u0 = Constant((0.0, 0.0, 0.0)) #Hard V definition
p_src = Constant((1.0))
#Boundary condition dictionary
boundary_conditions = {0: {'Hard' : u0},
1: {'InputBC': p_src},
2: {'OutputBC': u0}}
n = FacetNormal(mesh)
#Build Hard boundary conditions for real and imaginary parts
bcs = []
integral_source = []
integrals_load =[]
for i in boundary_conditions:
if 'Hard' in boundary_conditions[i]:
bb1 = (inner(dot(n, v_r), dot(n, u_r)) + inner(dot(n, v_i), dot(n, u_i))) * ds(i)
integrals_load.append(bb1)
# Build input BC source term and loading term
for i in boundary_conditions:
if 'InputBC' in boundary_conditions[i]:
r = boundary_conditions[i]['InputBC']
bb1 = inner(dot(n, v_i) - q_i, r) * ds(i)
integral_source.append(bb1)
bb1 = (inner(dot(n, v_r) - q_r, dot(n, u_r) - p_r) + inner(dot(n, v_i) - q_i, dot(n, u_i) - p_i)) * ds(i)
integrals_load.append(bb1)
for i in boundary_conditions:
if 'OutputBC' in boundary_conditions[i]:
r = boundary_conditions[i]['OutputBC']
bb2 = (inner(dot(n, v_r) - q_r, dot(n, u_r) - p_r) + inner(dot(n, v_i) - q_i, dot(n, u_i) - p_i)) * ds(i)
integrals_load.append(bb2)
a = (inner(div(v_r)+k0*q_i, div(u_r)+k0*p_i) + inner(div(v_i) - k0*q_r, div(u_i) - k0*p_r) + inner(grad(q_r) + k0*v_i, grad(p_r) + k0*u_i) + inner(grad(q_i) - k0*v_r, grad(p_i) - k0*u_r)) * dx + sum(integrals_load)
L = sum(integral_source)
u1 = Function(element)
#solve(a == L, u1, bcs, solver_parameters = {'linear_solver' : 'mumps'})
vdim = u1.vector().size()
print ('Solution vector size = ',vdim)
u1.vector()[:] = np.random.uniform(-1, 1, vdim)
A, b = assemble_system(a, L, bcs)
solver = PETScKrylovSolver("cg","jacobi")
solver.parameters['absolute_tolerance'] = 1e-7
solver.parameters['relative_tolerance'] = 1e-12
solver.parameters['maximum_iterations'] = 10000
solver.parameters['monitor_convergence'] = True
solver.parameters['nonzero_initial_guess'] = False
solver.parameters['report'] = True
solver.set_operators(A, A)
solver.solve(u1.vector(),b)
w_r, w_i, z_r, z_i = u1.split(True)
fp = File("Preal.pvd")
fp << z_r
fp = File("Pimag.pvd")
fp << z_i
fp = File("Ureal.pvd")
fp << z_r
fp = File("Uimag.pvd")
fp << w_i
ut = z_r.copy(deepcopy=True)
vt = w_r.copy(deepcopy=True)
fp = File('PressureWaveFile.pvd')
for i in range(50):
ut.vector().zero()
ut.vector().axpy(sin(pi * i / 25.0), z_i.vector())
ut.vector().axpy(cos(pi * i / 25.0), z_r.vector())
fp << (ut, i)
fp = File('VelocityWaveFile.pvd')
for i in range(50):
vt.vector().zero()
vt.vector().axpy(sin(pi * i / 25.0), w_i.vector())
vt.vector().axpy(cos(pi * i / 25.0), w_r.vector())
fp << (vt, i)
sys.exit(0)