电磁有限元法 (FEniCS) 插值 - 泄漏效应

计算科学 有限元 pde 芬尼克斯 电磁学 视界
2021-12-01 13:01:52

至于事情的背景:

  • 我正在使用专用 FEM 求解器的 FEniCS
  • 我要解决的问题是控制 PDE 的静磁问题

    ×1μ×A=J
    由于域是二维的,它简化为泊松方程:
    1μ2Az=Jz
    . 该域包含不同磁概率的子域:

    1. 空气和铜线(带电流源Jz) - 相对渗透率为 1
    2. 铁磁芯 - 10e3 的相对磁导率

此外,还有边界条件:Az=0在封装一切的空气子域的外边缘。

计算成功,我得到了磁矢量势的分布Az. 接下来,我将磁通量分布评估为:

B=×A
.

现在,如果我们尝试将解决方案可视化为表面,这显然需要在单元格内进行插值,我们将获得以下结果:

结果插值

我在可视化数据时遇到了这个问题。我知道解决方案与节点相关联。在位于边缘的节点上存在一些非零解,边缘是低渗透率域和高渗透率域之间的边界。例如,下一个节点的值为 0(零),但这些值正在单元内插值。我们知道,磁通量不会以这种方式表现,并且这些域之间的过渡应该是陡峭的。

节点处的解决方案似乎是正确的,但是当您尝试插入数据时会出现问题:

  • 用于可视化目的
  • 在任何点读取数据并将解决方案投影到另一个网格时(模拟多物理场时)

如何解决这个问题?我是否需要在铁磁材料(或与此相关的任何材料)的边缘添加一些额外的边界条件?我不能假设渗透率总是那么高,有时它可能是μr=10.

编辑:这是 Python 代码

from dolfin import *
import matplotlib.pyplot as plt
import numpy as np
import mshr
import copy
from scipy import constants
#############################################################################
import time
#############################################################################
from ProcessSubDomains import *
#############################################################################
mesh = Mesh('Mesh.xml')
# ---------------------------------------------------------------------------
markSubdomains('subdomains.xml', subdom_filename) # custom function that marks each subdomain with a number
materials = MeshFunction('size_t', mesh, mesh.topology().dim())
File(subdom_filename+'.xml') >> materials

#############################################################################
# MATERIAL PROPERTIES
class MaterialProperty(UserExpression):
    def __init__(self, materials, property, material_subdomains, property_index, **kwargs):
        super(MaterialProperty, self).__init__(**kwargs)    
        self.materials = materials
        self.property = property
        self.material_subdomains = material_subdomains
        self.property_index = property_index

    def eval_cell(self, values, x, cell):
        label = self.materials[cell.index]
        for key in self.material_subdomains:
            if label in self.material_subdomains[key]:
                values[0] = self.property[ self.property_index[key] ]

print("Assigning material properties...")
material_subdomains = {'air': [0, 2], 'iron': [1], 'wire': [3, 4, 5, 6]} # numbers associeted with subdomains
property_index = {'air': 0, 'iron': 1, 'wire': 2} # property index in permeability array
permeability = constants.mu_0*np.array([1, 35e3, 0.9991])
mu = MaterialProperty(materials, permeability, material_subdomains, property_index, degree=0)

#############################################################################
# DEFINE FUNCTION SPACE
V = FunctionSpace(mesh, 'P', 1)

#############################################################################
# BOUNDRY CONDITIONS
tol = 1e-14
def boundry(x, on_boundry):
    return on_boundry and ( near(abs(x[0]), 0.1, tol) or near(abs(x[1]), 0.1, tol) ) # checking when on Dirichlet BC
u_D = Constant(0) # value on Dirichlet BC
bc = DirichletBC(V, u_D, boundry)

#############################################################################
# REDEFINE INTEGRATION MEASURES
dx = Measure('dx', domain = mesh, subdomain_data = materials)

#############################################################################
# SOURCES
I = 400000.0
J_A = Constant(I)
J_B = Constant(I)

#############################################################################
# TRIAL AND TEST FUNCTIONS
A_z = TrialFunction(V)
v = TestFunction(V)

#############################################################################
# SOLVE VARIATIONAL PROBLEM
print("Solving variational form...")
a = (1/mu)*inner( grad(A_z), grad(v) )*dx
L = J_A*v*dx(3) - J_A*v*dx(4) + J_B*v*dx(5) - J_B*v*dx(6)

A_z = Function(V)
solve( a == L, A_z, bc)

#############################################################################
# POSTPROCESSING
W = VectorFunctionSpace(mesh, 'P', 1)

Bx = A_z.dx(1)
By = -A_z.dx(0)
B = project( as_vector(( Bx, By )), W )

B_abs = np.power( Bx**2 + By**2, 0.5 )

plot(A_z)
plt.show()

plot(B, mode='glyphs')
plt.show()

plot(B_abs)
plt.show()


在找到标量泊松方程的解之后 - 值Az最好是创建相应的向量场A=[0,0,Az],但以下操作失败:

A_vec = project( as_vector([0, 0, A_z]), W )

然后执行:

B_vec = project(curl(A_vec), W)

但是由于几何的二维性质(可能),这种操作似乎是不可能的。

编辑(解决方案):

所以我找到了解决方案!在这里:FEniCS course 这需要再次求解 PDE(实际上它是特殊类型的映射)。假设您已经解决了方程并且有Az.

必须在不连续伽辽金函数空间中求解的新公式是:

Ωwhvdx=Ωfvdx

在哪里f是您要映射的先前解决方案,即f=|B|

# POSTPROCESSING
# calculate derivatives
Bx = A_z.dx(1)
By = -A_z.dx(0)

B_abs = np.power( Bx**2 + By**2, 0.5 ) # compute length of vector

# define new function space as Discontinuous Galerkin
abs_B = FunctionSpace(mesh, 'DG', 0)
f = B_abs # obtained solution is "source" for solving another PDE

# make new weak formulation
w_h = TrialFunction(abs_B)
v = TestFunction(abs_B)

a = w_h*v*dx
L = f*v*dx

w_h = Function(abs_B)
solve(a == L, w_h)

# plot the solution
plot(w_h)
plt.show()

DG B_abs


补充说明:

@AntonMenshov:插值是在 ParaView 中自行完成的,也可以通过读取脚本中任意点的值来进行插值。

是的,可视化在商业软件 (Ansys Maxwell/Electronics) 中。不幸的是,除了解决了哪个 PDE 之外,文档没有说明它是如何完成的。第二个问题是,基本上不可能将数据(与节点关联的网格和解决方案)导出到软件之外。

网格被保存为.msh文件(Ansys 扩展)和解决方案为二进制文件。在查看.msh文件时,似乎该程序使用的是二阶元素而不是一阶元素,这是某种线索,为什么它会以这种方式运行。也许可以通过读取每个节点的位置、重建网格并读取这些节点上的值来完成。但这需要一些额外的劳动力,很多。

软件的另一个提示是它在每个子域之间设置默认的 Neumann 边界条件(如果没有另外说明),因此B和正切分量H在边界处是连续的。


@Paul:是的,我在这里使用的是 Continuous Galerkin 元素(拉格朗日)。我考虑在靠近边界的地方添加额外的元素层,这些元素的值会很快达到较小的值,这将减少后期处理中的这种发光。我还没有尝试过使用不连续 Galerkin 元素,这可能是一种选择,但需要调整变分公式。


当尝试沿某条线插入值以解决问题时Azpython/FEniCS 没有问题。但是当我试图插入甚至是B这当然是作为导数计算的,即

B_x = A_z.dx(1)

当我试图绘制组件时,我得到:

Traceback (most recent call last):
...
some lines
...
/anaconda3/lib/python3.6/site-packages/dolfin/function/function.py", line 257, in ufl_evaluate
    assert derivatives == ()   # TODO: Handle derivatives

所以,对于 FEniCS 版本 2018.1.0 来说,这样的操作是不可能的。在 ParaView 或内置 FEniCSplot()功能中对其进行可视化时,这也会影响结果。

但是有一条出路,至少对于可视化部分。正确的解决方案是磁矢量势的分布A. 然后我们可以计算×A要得到B这要归功于 ParaView 中的计算器。

ParaView 中的导数

python 脚本中的插值仍然缺少此功能。也许可以通过添加自定义代码来执行类似于上面显示的操作。

2个回答

您的问题是project()在 FEniCS 中使用。这是FEniCS 文档,其中讨论了您可能想要使用project操作符的原因。请注意,在该示例中,确切的通量是连续的,而数值计算的通量是不连续的。不是你的情况。在您的情况下,您的B实际上是不连续的,但是当您将其投影到连续空间时,您会错误地使其连续。

您要做的是简单地避免使用该project功能。您的后处理步骤应简化为:

编辑: OP 说代码不起作用。也许出于某种原因,FEniCS 计算Bx并计算节点ByA_z的数量并对其进行平均,这会产生“发光”效果。我没有 FEniCS,所以我真的只是根据 OP 的讨论和代码猜测它是如何工作的。无论如何,这是第二次尝试,首先将解决方案A_z投影到“DG”空间,希望将解决方案与网格节点分离,这应该允许BxBy展示适当的不连续性。

#############################################################################
# POSTPROCESSING

# project A_z onto a linear DG space
W = FunctionSpace(mesh, 'DG', 1)
A_z2 = project(A_z,W)

# now Bx and By should be associated with triangles instead of nodes
Bx = A_z2.dx(1)
By = -A_z2.dx(0)

B = as_vector(( Bx, By )) 

B_abs = np.power( Bx**2 + By**2, 0.5 )

print("Plotting fields...")

File('results/A_z.xml') << A_z
File('results/A_z.pvd') << A_z
plot(A_z)
plt.show()

File('results/B_vec.pvd') << B
plot(B, mode='glyphs')
plt.show()

plot(B_abs)
plt.show()

plot(mesh)
plot(B_abs)
plt.show()

@LedHead:是的,我曾经project计算过B上面显示为节点处的解决方案。

from dolfin import *
import matplotlib.pyplot as plt
import numpy as np
import mshr
import copy
from scipy import constants
#############################################################################
import time
#############################################################################
from ProcessSubDomains import *
#############################################################################
mesh = Mesh('Mesh.xml')
# ---------------------------------------------------------------------------
markSubdomains('subdomains.xml', subdom_filename) # custom function that marks each subdomain with a number
materials = MeshFunction('size_t', mesh, mesh.topology().dim())
File(subdom_filename+'.xml') >> materials

#############################################################################
# MATERIAL PROPERTIES
class MaterialProperty(UserExpression):
    def __init__(self, materials, property, material_subdomains, property_index, **kwargs):
        super(MaterialProperty, self).__init__(**kwargs)    
        self.materials = materials
        self.property = property
        self.material_subdomains = material_subdomains
        self.property_index = property_index

    def eval_cell(self, values, x, cell):
        label = self.materials[cell.index]
        for key in self.material_subdomains:
            if label in self.material_subdomains[key]:
                values[0] = self.property[ self.property_index[key] ]

print("Assigning material properties...")
material_subdomains = {'air': [0, 2], 'iron': [1], 'wire': [3, 4, 5, 6]} # numbers associeted with subdomains
property_index = {'air': 0, 'iron': 1, 'wire': 2} # property index in permeability array
permeability = constants.mu_0*np.array([1, 35e3, 0.9991])
mu = MaterialProperty(materials, permeability, material_subdomains, property_index, degree=0)

#############################################################################
# DEFINE FUNCTION SPACE
V = FunctionSpace(mesh, 'P', 1)

#############################################################################
# BOUNDRY CONDITIONS
tol = 1e-14
def boundry(x, on_boundry):
    return on_boundry and ( near(abs(x[0]), 0.1, tol) or near(abs(x[1]), 0.1, tol) ) # checking when on Dirichlet BC
u_D = Constant(0) # value on Dirichlet BC
bc = DirichletBC(V, u_D, boundry)

#############################################################################
# REDEFINE INTEGRATION MEASURES
dx = Measure('dx', domain = mesh, subdomain_data = materials)

#############################################################################
# SOURCES
I = 400000.0
J_A = Constant(I)
J_B = Constant(I)

#############################################################################
# TRIAL AND TEST FUNCTIONS
A_z = TrialFunction(V)
v = TestFunction(V)

#############################################################################
# SOLVE VARIATIONAL PROBLEM
print("Solving variational form...")
a = (1/mu)*inner( grad(A_z), grad(v) )*dx
L = J_A*v*dx(3) - J_A*v*dx(4) + J_B*v*dx(5) - J_B*v*dx(6)

A_z = Function(V)
solve( a == L, A_z, bc)

#############################################################################
# POSTPROCESSING
W = VectorFunctionSpace(mesh, 'P', 1)

Bx = A_z.dx(1)
By = -A_z.dx(0)
B = project( as_vector(( Bx, By )), W )

B_abs = np.power( Bx**2 + By**2, 0.5 )

plot(A_z)
plt.show()

plot(B, mode='glyphs')
plt.show()

plot(B_abs)
plt.show()

在找到标量泊松方程的解之后 - 值Az最好是创建相应的向量场A=[0,0,Az],但以下操作失败:

A_vec = project( as_vector([0, 0, A_z]), W )

然后执行:

B_vec = project(curl(A_vec), W)

但是由于几何的二维性质(可能),这种操作似乎是不可能的。

在这一点上,在节点、单元中心(使用插值)、法线向量或其他任何地方提取解都不是问题——更多的是算法本身计算一个场的导数并确保它在整个单元中是恒定的,如导数所示在 ParaView 中获得。

我已经按照教程中的建议做了一些事情:

V = A_z.function_space()
mesh = V.mesh()
degree = V.ufl_element().degree()

W = VectorFunctionSpace(mesh, 'P', degree)
B_vec = project( as_vector([Bx, By]), W)

plot(B_vec, title='flux field')
plt.show()

正如预期的那样,向量与节点相关联。

但仍在密谋|B|是问题所在。:)

节点处的 B 个向量

编辑:

所以我找到了解决方案!在这里:FEniCS course 这需要再次求解 PDE(实际上它是特殊类型的映射)。假设您已经解决了方程并且有Az.

必须在不连续伽辽金函数空间中求解的新公式是:

Ωwhvdx=Ωfvdx

在哪里f是您要映射的先前解决方案,即f=|B|

# POSTPROCESSING
W = VectorFunctionSpace(mesh, 'P', 1) # new function space for mapping B as vector

# calculate derivatives
Bx = A_z.dx(1)
By = -A_z.dx(0)

B = project( as_vector(( Bx, By )), W ) # project B as vector to new function space
B_abs = np.power( Bx**2 + By**2, 0.5 ) # compute length of vector

# plot B vectors
plot(B)
plt.show()

# define new function space as Discontinuous Galerkin
abs_B = FunctionSpace(mesh, 'DG', 0)
f = B_abs # obtained solution is "source" for solving another PDE

# make new weak formulation
w_h = TrialFunction(abs_B)
v = TestFunction(abs_B)

a = w_h*v*dx
L = f*v*dx

w_h = Function(abs_B)
solve(a == L, w_h)

# plot the solution
plot(w_h)
plt.show()

DG B_abs