Hello,
I have problems in understanding how mpi works. In the code below, I am trying to calculate the error of FEA results with the exact solution. Also I want to plot the displacements of a certain node through time. But when I run in parallel I get different results:
import dolfinx as fe
from dolfinx.fem.petsc import NonlinearProblem, LinearProblem, assemble_vector, assemble_matrix, create_vector, apply_lifting, set_bc
from dolfinx.nls.petsc import NewtonSolver
import numpy as np
import os
from mpi4py import MPI
import ufl
from petsc4py import PETSc
import pyvista
import pandas as pd
import meshio
from scipy.spatial import cKDTree
from scipy.special import jv, yv, iv, kv
from itertools import product
import h5py
import matplotlib.pyplot as plt
def create_mesh(mesh, cell_type, prune_z=False):
cells = mesh.get_cells_type(cell_type)
cell_data = mesh.get_cell_data("gmsh:physical", cell_type)
points = mesh.points[:, :2] if prune_z else mesh.points
out_mesh = meshio.Mesh(points=points, cells={cell_type: cells}, cell_data={"name_to_read": [cell_data.astype(np.int32)]})
return out_mesh
def main(rho, E, nu, maximum_pressure, minimum_pressure, mesh_file, num_steps=100, element_degree=1):
maximum_pressure = fe.default_scalar_type(maximum_pressure)
minimum_pressure = fe.default_scalar_type(minimum_pressure)
rho = fe.default_scalar_type(rho)
E = fe.default_scalar_type(E)
nu = fe.default_scalar_type(nu)
proc = MPI.COMM_WORLD.rank
############################################Importing the mesh############################################
##########################################################################################################
#Wrinting the mesh as XDMF file to read from that later
if proc == 0:
# Read in mesh
msh = meshio.read(mesh_file)
# Create and save one file for the mesh, and one file for the facets
tetra_mesh = create_mesh(msh, "tetra", prune_z=False)
triangle_mesh = create_mesh(msh, "triangle", prune_z=True)
facets = create_mesh(msh, "triangle", prune_z=False)
meshio.write(os.path.join("output", "mt.xdmf"), triangle_mesh)
meshio.write(os.path.join("output", "mesh.xdmf"), tetra_mesh)
MPI.COMM_WORLD.barrier()
#Reading the xdmf files
with fe.io.XDMFFile(MPI.COMM_WORLD, os.path.join("output", "mesh.xdmf"), "r") as xdmf:
mesh = xdmf.read_mesh(name="Grid")
cell_markers = xdmf.read_meshtags(mesh, name="Grid")
gdim = mesh.topology.dim
mesh.topology.create_connectivity(gdim, gdim - 1)
with fe.io.XDMFFile(MPI.COMM_WORLD, os.path.join("output", "mt.xdmf"), "r") as xdmf:
facet_markers = xdmf.read_meshtags(mesh, name="Grid")
points = mesh.geometry.x
radius_to_check = 2
for i, p in enumerate(points):
if np.linalg.norm(p) == radius_to_check:
index_of_radius_to_check = i
break
######################################################################################################
#######################creating function space and boundary condition#################################
element = ufl.VectorElement("Lagrange", mesh.ufl_cell(), element_degree)
V = fe.fem.functionspace(mesh, element)
gamma_p = 0.01
t = np.linspace(0, 2*np.pi / gamma_p, num_steps)
dt = fe.fem.Constant(mesh, t[1] - t[0])
pressure = maximum_pressure / 2 * (1 - np.cos(gamma_p * t))
p = fe.fem.Constant(mesh, pressure[0])
######################################################################################################
######################################################################################################
######################################################################################################
#########################################Formulation##################################################
u = ufl.TrialFunction(V)
v = ufl.TestFunction(V)
old_u = fe.fem.Function(V)
old_velocity = fe.fem.Function(V)
old_acceleration = fe.fem.Function(V)
d = len(u)
I = ufl.variable(ufl.Identity(d)) # Identity tensor
F = ufl.variable(I + ufl.grad(u)) # Deformation gradient
C = ufl.variable(F.T*F) # Right Cauchy-Green tensor
J = ufl.variable(ufl.det(F))
metadata = {"quadrature_degree": 2}
ds = ufl.Measure('ds', domain=mesh, subdomain_data=facet_markers, metadata=metadata)
dx = ufl.Measure("dx", domain=mesh, metadata=metadata)
# Generalized-alpha method parameters
alpha_m = fe.fem.Constant(mesh, 0.01)
alpha_f = fe.fem.Constant(mesh, 0.01)
gamma = fe.fem.Constant(mesh, fe.default_scalar_type(0.5+alpha_f-alpha_m))
beta = fe.fem.Constant(mesh, fe.default_scalar_type((gamma+0.5)**2/4.))
# Update formula for acceleration
# a = 1/(2*beta)*((u - u0 - v0*dt)/(0.5*dt*dt) - (1-2*beta)*a0)
def update_a(u, u_old, v_old, a_old, ufl=True):
if ufl:
dt_ = dt
beta_ = beta
else:
dt_ = float(dt)
beta_ = float(beta)
return (u-u_old-dt_*v_old)/beta_/dt_**2 - (1-2*beta_)/2/beta_*a_old
# Update formula for velocity
# v = dt * ((1-gamma)*a0 + gamma*a) + v0
def update_v(a, u_old, v_old, a_old, ufl=True):
if ufl:
dt_ = dt
gamma_ = gamma
else:
dt_ = float(dt)
gamma_ = float(gamma)
return v_old + dt_*((1-gamma_)*a_old + gamma_*a)
def update_fields(u_new, u_old, v_old, a_old):
"""Update fields at the end of each time step."""
# Get vectors (references)
u_vec, u0_vec = u_new.x.array[:], u_old.x.array[:]
v0_vec, a0_vec = v_old.x.array[:], a_old.x.array[:]
# use update functions using vector arguments
a_vec = update_a(u_vec, u0_vec, v0_vec, a0_vec, ufl=False)
v_vec = update_v(a_vec, u0_vec, v0_vec, a0_vec, ufl=False)
# Update (u_old <- u)
v_old.x.array[:], a_old.x.array[:] = v_vec, a_vec
u_old.x.array[:] = u_new.x.array[:]
def avg(x_old, x_new, alpha):
return alpha*x_old + (1-alpha)*x_new
normal = -ufl.FacetNormal(mesh)
mu = fe.fem.Constant(mesh, E / (2 * (1 + nu)))
lmbda = fe.fem.Constant(mesh, E * nu / ((1 + nu) * (1 - 2 * nu)))
def epsilon(u):
return ufl.sym(ufl.grad(u))
def S(u):
return 2.0 * mu * epsilon(u) + lmbda * ufl.nabla_div(u) * I
acceleration = update_a(u, old_u, old_velocity, old_acceleration, ufl=False)
velocity = update_v(acceleration, old_u, old_velocity, old_acceleration, ufl=True)
formulation = rho * ufl.inner(avg(old_acceleration, acceleration, alpha_m), v) * dx \
+ ufl.inner(epsilon(v), S(avg(old_u, u, alpha_f))) * dx \
- ufl.dot(v, p * normal) * ds(112)
bilinear_form = fe.fem.form(ufl.lhs(formulation))
linear_form = fe.fem.form(ufl.rhs(formulation))
######################################################################################################
######################################################################################################
######################################################################################################
###############################################Solving################################################
A = assemble_matrix(bilinear_form, bcs=[])
A.assemble()
b = create_vector(linear_form)
solver = PETSc.KSP().create(mesh.comm)
solver.setInitialGuessNonzero(True)
solver.setOperators(A)
solver.getPC().setType(PETSc.PC.Type.SOR)
displacement = fe.fem.Function(V)
class exact_solution():
def __init__(self, rho, inner_radius, k, lmbda, miu, p_0, gamma, num_steps): # p = -p_0 * (1-cos(gammma*tau))
self.a = inner_radius
self.k = k # outer_radius = k * a
self.phi = -1/2
self.n = 3/2
self.C_11 = lmbda + 2 * miu #E * (1 - miu) / ((1 + miu) * (1 - 2*miu))
self.C_12 = lmbda #E * miu / ((1 + miu) * (1 - 2*miu))
self.m = self.C_12 / self.C_11
self.S_1 = self.n - 2 * self.m - self.phi
self.alpha = [1.01034, 3.33252, 9.48585, 6.37556, 15.74444, 12.61203, 28.29456, 18.87993, 25.1555, 22.01717, 31.43413, 34.57406, 37.71428, 40.8547, 43.99529, 47.13602, 50.27685, 62.84095, 53.41778, 59.69984, 56.55877, 65.98211, 78.54709, 69.12331, 109.96094, 72.26454, 75.4058, 84.82974, 87.97109, 81.68841, 144.51722, 97.39524, 94.25384, 91.11246, 141.37571, 100.53665, 103.67807, 106.8195, 113.10239, 116.24384, 128.80974, 119.38531]
self.alpha.sort()
self.p_0 = p_0
self.gamma = gamma
self.tau = 0 # 6 * 2 * np.pi
self.c = np.sqrt(self.C_11 / rho)
self.dtau = 2*np.pi / self.gamma /num_steps # dt /(2*np.pi) # * self.c
def return_tau(self):
return self.tau
def step_one_period(self):
self.tau += self.gamma * 2*np.pi # self.c
def step_in_time(self):
self.tau += self.dtau
# print(f"tau: {self.tau}")
def r(self, x): # distance to the center
return np.sqrt(np.power(x[0], 2) + np.power(x[1], 2) + np.power(x[2], 2))
def theta_cood(self, x):
return np.arccos(x[2]/self.r(x))
def phi_cood(self, x):
return np.sign(x[1]) * np.arccos(x[0]/np.sqrt(np.power(x[0], 2) + np.power(x[1], 2)))
def F(self, p, x):
k = self.k
n = self.n
S_1 = self.S_1
a = self.a
return (kv(n, p*k + 0j) * S_1 + p*k * kv(n-1, p*k + 0j)) * iv(n, p*self.r(x)/a + 0j)\
- (iv(n, p*k + 0j) * S_1 - p*k * iv(n-1, p*k + 0j)) * kv(n, p*self.r(x)/a + 0j)
def G(self, p):
k = self.k
n = self.n
S_1 = self.S_1
a = self.a
return (kv(n, p*k + 0j) * S_1 + p*k * kv(n-1, p*k + 0j)) * (iv(n, p + 0j) * S_1 - p * iv(n-1, p + 0j))\
- (kv(n, p + 0j) * S_1 + p * kv(n-1, p + 0j)) * (iv(n, p*k + 0j) * S_1 - p*k * iv(n-1, p*k + 0j))
def __call__(self, x, radial=False):
p_0 = self.p_0
a = self.a
phi = self.phi
C_11 = self.C_11
k = self.k
n = self.n
S_1 = self.S_1
gamma = self.gamma
alpha = self.alpha
first_term = -(p_0 * np.power(self.r(x)/a, phi) / C_11)\
* (-np.power(k, 2*n) * (-2*n + S_1) + S_1 * np.power(self.r(x)/a, 2*n))\
/ ((-1+np.power(k, 2*n)) * (-2*n + S_1) * S_1 * np.power(self.r(x)/a, n))
second_term = -(p_0 * np.power(self.r(x)/a, phi) / C_11)\
* self.F(np.array([1j * gamma]), x) * np.cos(gamma * self.tau)\
/ self.G(np.array([1j * gamma]))
series = 0
for s in range(len(alpha)):
first_parantheses = S_1 * jv(n, alpha[s] + 0j) - alpha[s] * jv(n-1, alpha[s] + 0j)
second_parantheses = np.power(S_1, 2) - 2 * S_1 * n + np.power(alpha[s], 2)
third_parantheses = np.power(S_1 * jv(n, alpha[s] * k + 0j) - alpha[s] * k * jv(n-1, alpha[s] * k + 0j), 2)
forth_parantheses = S_1 * jv(n, alpha[s] * k + 0j) - alpha[s] * k * jv(n-1, alpha[s] * k + 0j)
fifth_parantheses = np.power(S_1, 2) - 2 * S_1 * n + np.power(alpha[s] * k, 2)
sixth_parantheses = np.power(S_1 * jv(n, alpha[s] + 0j) - alpha[s] * jv(n-1, alpha[s] + 0j), 2)
series += 2 * self.F(np.array([1j * alpha[s]]), x)/(np.power(gamma, 2) - np.power(alpha[s], 2))\
* first_parantheses * forth_parantheses / (second_parantheses * third_parantheses - fifth_parantheses * sixth_parantheses)\
* np.cos(alpha[s] * self.tau)
third_term = (p_0 * np.power(self.r(x)/a, phi) * np.power(gamma, 2) / C_11) * series
u = np.zeros((1, x.shape[1]))
u = first_term + second_term + third_term
displacement = np.zeros(x.shape)
mag = np.linalg.norm(x, axis=0)
for i in range(mag.shape[0]):
displacement[:, i] = u[i].real * x[:, i] / mag[i]
if radial==True:
return u.real
return displacement #u.real # [0]
radial_disp = []
u_exact = exact_solution(1, 1, 2, lmbda.value, mu.value, 18/2, gamma_p, num_steps)
radial_disp = []
error = []
# Stepping through time and solving for displacements and strains
for i in range(int(num_steps)):
print(i)
if i < num_steps:
p.value = pressure[i]
# Update the right hand side reusing the initial vector
with b.localForm() as loc_b:
loc_b.set(0)
assemble_vector(b, linear_form)
# Apply Dirichlet boundary condition to the vector
apply_lifting(b, [bilinear_form], [[]])
b.ghostUpdate(addv=PETSc.InsertMode.ADD_VALUES, mode=PETSc.ScatterMode.REVERSE)
set_bc(b, [])
# Solve linear problem
solver.solve(b, displacement.vector)
displacement.x.scatter_forward()
radial_disp.append(np.linalg.norm(displacement.x.array.reshape((-1, 3))[index_of_radius_to_check, :]))
displacement_exact = fe.fem.Function(V)
displacement_exact.interpolate(u_exact)
u_exact.step_in_time()
error_L2 = MPI.COMM_WORLD.allreduce(
fe.fem.assemble.assemble_scalar(fe.fem.form(((displacement - displacement_exact))** 2 * ufl.dx))**0.5,
op=MPI.SUM)
if mesh.comm.rank == 0:
error.append(error_L2)
# Update old fields with new quantities
update_fields(displacement, old_u, old_velocity, old_acceleration)
if proc == 0:
plt.plot(t, [radial_disp[i] for i in range(int(num_steps))])
plt.show()
print(error)
if __name__ == "__main__":
mesh_file = "test.msh"
main(1.0, 400, 0.3, 18, 0, mesh_file)
Both the plotted radial displacement and the error with respect to the exact solution are different in parallel.
Thank you in advance.