Hello Kostas,
Thank you again for the help!
I have been running some more tests, and I have found some very odd results.
First, using the md.add_multiplier("mult40", mf, "f", mim, 40) method, the solver appears to converge, but only for very simple test cases where the output forces are all zero. If I try to use it for a real case the matrix becomes singular with the error message
Level 1 Warning in ../../src/gmm/gmm_MUMPS_interface.h, line 150: Solve with MUMPS failed: matrix is singular
Second, using the md.add_multiplier("mult40", mf, "f", mim, 40) method appears to give similar multiplier sizes (around 200 to 700) as the md.add_Dirichlet_condition_with_multipliers(mim, 'f', mf, 46, data_for_velocity_boundaries), but they are not eactly the same. They are both very different, however, from the 3000-4000 multiplier sizes I got when just using md.add_filtered_fem_variable("mult1", mf, 40).
However, when I ran the
mfmult = md.mesh_fem_of_variable("mult" )
R=mfmult.reduction_matrix()
E=mfmult.extension_matrix()
code, the resulting reduction and extension matrices were both 0x0:
2025-05-17 09:32:29,024 [INFO] Reduction matrix: matrix(0, 0)
2025-05-17 09:32:29,025 [INFO] Extension matrix: matrix(0, 0)
(I printed them in my log file)
So, I am confused how this is happening, since if these are 0x0 it would imply that the U and V matrices are also 0, which (I think?) would imply that the entire linear problem being solved is a zero matrix. These boundary conditions should be being applied to 5D slices of 6D space, so I am wondering if I could be applying the bulk assembly term wrong. One thought I had is that I might need to only apply my main linear term on the bulk of the simulation and not the edges, but I am unsure how to do this in getfem.
If it helps, the part of my code calling getFEM is below.
# Define hyperrectangular mesh around the spacecraft at 0, 0 and the plasma momentum
m = gf.Mesh('regular simplices', grid_x, grid_x, grid_x, grid_p + v_x, grid_p + v_y, grid_p)
# Confirm that this function operates in 6 dimensions
dims = 6
# create a MeshFem of for a field of dimension 1 (i.e. a scalar field)
mf = gf.MeshFem(m, 1)
# assign the Q2 fem to all convexes of the MeshFem
mf.set_fem(gf.Fem('FEM_PK(' + str(dims) + ',1)'))
# view the expression of its basis functions on the reference convex
logging.debug('Basis functions per element: ' + str(gf.Fem('FEM_PK(' + str(dims) + ',1)').poly_str()))
# an exact integration will be used
mim = gf.MeshIm(m, gf.Integ('IM_NC(' + str(dims) + ',1)'))
# detect the border of the mesh
fb0 = m.outer_faces_with_direction([ 1., 0., 0., 0., 0., 0.], 0.01)
fb1 = m.outer_faces_with_direction([-1., 0., 0., 0., 0., 0.], 0.01)
fb2 = m.outer_faces_with_direction([0., 1., 0., 0., 0., 0.], 0.01)
fb3 = m.outer_faces_with_direction([0., -1., 0., 0., 0., 0.], 0.01)
fb4 = m.outer_faces_with_direction([0., 0., 1., 0., 0., 0.], 0.01)
fb5 = m.outer_faces_with_direction([0., 0., -1., 0., 0., 0.], 0.01)
fb6 = m.outer_faces_with_direction([0., 0., 0., 1., 0., 0.], 0.01)
fb7 = m.outer_faces_with_direction([0., 0., 0., -1., 0., 0.], 0.01)
fb8 = m.outer_faces_with_direction([0., 0., 0., 0., 1., 0.], 0.01)
fb9 = m.outer_faces_with_direction([0., 0., 0., 0., -1., 0.], 0.01)
fb10 = m.outer_faces_with_direction([0., 0., 0., 0., 0., 1.], 0.01)
fb11 = m.outer_faces_with_direction([0., 0., 0., 0., 0., -1.], 0.01)
# mark it as boundary #4X
m.set_region(40, fb0)# Face the plasma is entering on
m.set_region(41, fb1)# Face the plasma is exiting
m.set_region(42, fb2)# Side face
m.set_region(43, fb3)# Side face
m.set_region(44, fb4)# Side face
m.set_region(45, fb5)# Side face
m.set_region(46, fb6)# Velocity extreme
m.set_region(47, fb7)# Velocity extreme
m.set_region(48, fb8)# Velocity extreme
m.set_region(49, fb9)# Velocity extreme
m.set_region(50, fb10)# Velocity extreme
m.set_region(51, fb11)# Velocity extreme
# empty real model
md = gf.Model('real')
# declare that "f" is an unknown of the system, representing density in
# ions / (kg^3 * m^6 / s^3)
# on the finite element method `mf`
md.add_fem_variable('f', mf)
# Define electric and orthogonal magnetic field. E3, B1, B2 have no in-plane
# acceleration.
md.add_initialized_fem_data('E1', mf, mf.eval('0 - B_earth * v_y', {**locals(), **globals()}))
md.add_initialized_fem_data('E2', mf, mf.eval('0 + B_earth * v_x', {**locals(), **globals()}))
md.add_initialized_fem_data('E3', mf, mf.eval('0', {**locals(), **globals()}))# No E3 since Earth's magnetic field is in that direction
md.add_initialized_fem_data('B1', mf, mf.eval('0', {**locals(), **globals()}))#*
md.add_initialized_fem_data('B2', mf, mf.eval('0', {**locals(), **globals()}))#*
md.add_initialized_fem_data('B3', mf, mf.eval('B_earth', {**locals(), **globals()}))#*
# Define the 6D Vlasov equation
# 6 dimensions are: x1, x2, x3, p1, p2, p3, in order. x is position, and p is momentum.
vlasov = '([X(4); X(5); X(6); 0; 0; 0].Grad_f + ([0; 0; 0; E1; E2; E3] + [0; 0; 0; X(5) * B3 - X(6) * B2; X(6) * B1 - X(4) * B3; X(4) * B2 - X(5) * B1]).Grad_f) * Test_f'
# add generic assembly brick for the bulk of the simulation
md.add_nonlinear_term(mim, vlasov)
# add Dirichlet conditions for inlet and momentum-space edges
g = mf.eval('p_0 * np.exp(-0.5*((u - v_x)/v_therm)**2-0.5*((v - v_y)/v_therm)**2-0.5*((w)/v_therm)**2)', {**locals(), **globals()})
md.add_initialized_fem_data('DirichletData', mf, g)
g2 = mf.eval('0', globals())
md.add_initialized_fem_data('zeros', mf, g2)
# Test why filtered FEM variable has a null space
md.add_filtered_fem_variable("mult", mf, 40)
mfmult = md.mesh_fem_of_variable("mult" )
R=mfmult.reduction_matrix()
E=mfmult.extension_matrix()
logging.info('Reduction matrix: ' + str(R))
logging.info('Extension matrix: ' + str(E))
# Use linear terms with multiplier preconditioning
md.add_multiplier("mult40", mf, "f", mim, 40)
md.add_linear_term(mim, 'mult40 * (DirichletData - f)', 40)
md.add_multiplier("mult46", mf, "f", mim, 46)
md.add_linear_term(mim, '-mult46 * f', 46)
md.add_multiplier("mult47", mf, "f", mim, 47)
md.add_linear_term(mim, '-mult47 * f', 47)
md.add_multiplier("mult48", mf, "f", mim, 48)
md.add_linear_term(mim, '-mult48 * f', 48)
md.add_multiplier("mult49", mf, "f", mim, 49)
md.add_linear_term(mim, '-mult49 * f', 49)
md.add_multiplier("mult50", mf, "f", mim, 50)
md.add_linear_term(mim, '-mult50 * f', 50)
md.add_multiplier("mult51", mf, "f", mim, 51)
md.add_linear_term(mim, '-mult51 * f', 51)
# add Neumann condition
md.add_source_term_brick(mim, 'f', 'zeros', 41)
md.add_source_term_brick(mim, 'f', 'zeros', 42)
md.add_source_term_brick(mim, 'f', 'zeros', 43)
md.add_source_term_brick(mim, 'f', 'zeros', 44)
md.add_source_term_brick(mim, 'f', 'zeros', 45)
# List variables
md.variable_list()
# solve the linear system
md.solve("noisy", "lsolver", "mumps")
Sincerely,
Eric Comstock