I am trying to reproduce an FEM result in a paper. Due to possible copyright I cannot show the result directly but fortunately there is a free link
An Incomplete Gauge for 3D Nodal Finite Element Magnetostatics
The important Figs. are 1-3. Basically the problem is quite simple. An iron cube 4x4x4cm sitting in a vertical 1Tesla field. Due to symmetry only 1/8 must be simulated using FEM. The air boundary of the 1/8th model is set at 10x10x10cm. Boundary conditions on the magnetic vector potential are imposed on the boundary faces to ensure the symmetry and also a field of 1T in the z-direction.
The basic equation to be solved is curl(v*curl(A)) = J. In this problem J (current density) = 0. The resultant matrix to be solved after discretizing is often ill-conditioned, but can be improved by applying a gauge (typically Coulomb div(A)=0), but with loss of accuracy. Coulomb gauging results in a Poisson Equation: Div(Grad(A))=J, and when J=0 the Laplacian. Even with the ill-conditioning an ICCG solver can usually converge to a solution. Using the MVP for magnetostatics is not particularly computationally efficient and so reduced-total scalar solutions have been the preferred method for this type of problem for nearly 30 years. However that requires solving separate pde's in the different material regions and imposing interface constraints - but that is question for another time.
My code to solve the problem is shown, and uses hexahedron (brick) finite elements, as did the result in the paper.
Clear["Global`*"];
Needs["NDSolve`FEM`"];
\[Mu]o = 4.0*\[Pi]*10^-7;
\[Mu]r = 1000.0;(*iron relative permeability*)
a = 0.02; (*iron cube length(s)*)
ironEdgeBricks =
4; (*integer number of brick elements along iron edge*)
airRegionScale =
5; (*integer scaling factor of air region to iron region*)
fluxDensity = 1.0; (*applied flux density in z direction*)
n = ironEdgeBricks*airRegionScale + 1;
b = airRegionScale*a;
coordinates =
Flatten[Table[{x, y, z}, {x, 0, b, b/(n - 1)}, {y, 0, b,
b/(n - 1)}, {z, 0, b, b/(n - 1)}], 2]; incidents =
Flatten[Table[
Block[{p1 = (j - 1)*n + i, p2 = j*n + i, p3 = p2 + 1, p4 = p1 + 1,
p5, p6, p7, p8},
{p5, p6, p7, p8} = {p1, p2, p3, p4} + k*n*n;
{p1, p2, p3, p4} += (k - 1)*n*n;
{p1, p2, p3, p4, p5, p6, p7, p8}], {i, 1, n - 1}, {j, 1,
n - 1}, {k, 1, n - 1}], 2];
mesh = ToElementMesh["Coordinates" -> coordinates,
"MeshElements" -> {HexahedronElement[incidents]}, "MeshOrder" -> 1];
Show[mesh["Wireframe"], RegionPlot3D[Cuboid[{0, 0, 0}, {a, a, a}]],
Axes -> True, AxesLabel -> {x, y, z}]
Now to the solution
u = {ux[x, y, z], uy[x, y, z],
uz[x, y, z]}; (*vector potential components*)
\[Nu]1 =
If[x \[LessSlantEqual] a && y \[LessSlantEqual] a &&
z \[LessSlantEqual] a, 1/(\[Mu]r*\[Mu]o),
1/\[Mu]o];(*permeability depending on iron cube in mesh*)
\[CapitalGamma]d = {DirichletCondition[ux[x, y, z] == 0, y == 0],
DirichletCondition[ux[x, y, z] == -fluxDensity*b/2, y == b],
DirichletCondition[uy[x, y, z] == 0, x == 0],
DirichletCondition[uy[x, y, z] == fluxDensity*b/2, x == b],
DirichletCondition[uz[x, y, z] == 0,
y == b || y == 0 || x == 0 || x == b || z == 0 || z == b]};
\[CapitalGamma]n = {0, 0, 0};
op1 = Curl[\[Nu]1*Curl[u, {x, y, z}], {x, y, z}];(*Ungauged*)
op2 = {D[\[Nu]1*(D[uy[x, y, z], x] - D[ux[x, y, z], y]), y] -
D[\[Nu]1*(D[ux[x, y, z], z] - D[uz[x, y, z], x]), z],
D[\[Nu]1*(D[uz[x, y, z], y] - D[uy[x, y, z], z]), z] -
D[\[Nu]1*(D[uy[x, y, z], x] - D[ux[x, y, z], y]), x],
D[\[Nu]1*(D[ux[x, y, z], z] - D[uz[x, y, z], x]), x] -
D[\[Nu]1*(D[uz[x, y, z], y] - D[uy[x, y, z], z]),
y]};(*Ungauged*)
op3 = Div[\[Nu]1*Grad[u, {x, y, z}], {x, y, z}]; (*Coulomb gauged*)
op4 = {Inactive[Div][
Inactive[Dot][\[Nu]1*IdentityMatrix[3],
Inactive[Grad][ux[x, y, z], {x, y, z}]], {x, y, z}],
Inactive[Div][
Inactive[Dot][\[Nu]1*IdentityMatrix[3],
Inactive[Grad][uy[x, y, z], {x, y, z}]], {x, y, z}],
Inactive[Div][
Inactive[Dot][\[Nu]1*IdentityMatrix[3],
Inactive[Grad][uz[x, y, z], {x, y, z}]], {x, y,
z}]}; (*Coulomb gauged*)
op5 = {Inactive[Div][\[Nu]1*
Inactive[Grad][ux[x, y, z], {x, y, z}], {x, y, z}],
Inactive[Div][\[Nu]1*Inactive[Grad][uy[x, y, z], {x, y, z}], {x, y,
z}], Inactive[Div][\[Nu]1*
Inactive[Grad][uz[x, y, z], {x, y, z}], {x, y,
z}]}; (*Coulomb gauged*)
op6 = Curl[\[Nu]1*Curl[u, {x, y, z}], {x, y, z}] -
Grad[\[Nu]1*Div[u, {x, y, z}], {x, y, z}]; (*Coulomb gauged*)
{mvpAx, mvpAy, mvpAz} =
NDSolveValue[{op6 == \[CapitalGamma]n, \[CapitalGamma]d}, {ux, uy,
uz}, {x, y, z} \[Element]
mesh];(*solve for magnetic vector potential A*)
(*flux density is curl of MVP A*)
{B1x, B1y,
B1z} = {(D[mvpAz[x, y, z], y] -
D[mvpAy[x, y, z], z]), (D[mvpAx[x, y, z], z] -
D[mvpAz[x, y, z], x]),
D[mvpAy[x, y, z], x] - D[mvpAx[x, y, z], y]};
Plot[{mvpAx[xp, a/2, a/2], mvpAy[xp, a/2, a/2],
mvpAz[xp, a/2, a/2]}, {xp, 0, b}, PlotLegends -> "Expressions",
AxesLabel -> {"x distance (m)", "Potential (V.s/m)"},
PlotLabel -> "MVP along x directed line for y=z=a/2"]
Plot[Evaluate[{B1x, B1y, B1z} /. {x -> xp, y -> a/2, z -> a/2}], {xp,
0, b}, PlotLegends -> {"Bx", "By", "Bz"}, PlotRange -> Full,
AxesLabel -> {"x distance (m)", "Flux Density(T)"},
PlotLabel -> "Flux Density along x directed line for y=z=a/2"]
Here is an ungauged result:
There is an appreciable difference (factor 2) compared to the result in the paper for the flux density plotted along an x directed line midway in the iron cube. This problem has also been analyzed in a second paper but you need an IEEE Magnetics membership to access. Basically the results in the two papers are similar, so I am assuming the mistake is on my side, or MM implements the FEM solution somehow differently and it is not really applicable.
In the x direction Bx is continuous at the cube edge as the line is normal to the sudden discontinuity in the reluctance. Bz shows the required discontinuity jump, and Bz tends to 1T outside the iron cube as expected, but its amplitude at x=0 should be closer to 3T. By should show discontinuity also at the cube edge and its magnitude should be much higher.
My questions are:
Have I implemented the pde in MM correctly? I implemented various forms of the pde (op1 - op6 both gauged and ungauged) and all the gauged ones give the same result, and all the ungauged ones give the same result. I tried Inactive pde forms as well, but I think as "v1" is symmetric it does not do anything, it is just most MM examples show it being used.
The B=curl(A) result shows quite some discretization effects presumably due to the differentiation, yet the interpolated potential result looks quite smooth. How can that be improved without reducing mesh size?
Could it be that the way MM applies NDSolve to the FEM is not the best for this type of problem?
Any input most welcomed.
First Edit for some more insight:
A simpler variation that can be more easily verified is that of a solid permeable cylinder in a uniform field (Bz = 1T). An axisymmetric 3D (2D) simulation can be made. Here is some MM code for the axisymmetric Poisson equation:
Clear["Global`*"];
Needs["NDSolve`FEM`"];
\[Mu]o = 4.0*\[Pi]*10^-7;
\[Mu]r = 1000.0;(*permeability iron region*)
h = 0.02; (*half height \
and radius of permeable cylinder*)
hAir = 0.1; (*height/width/depth \
air region*)
fluxDens = 1.0; (*z directed B field*)
(*create Mesh*)
mesh = ToElementMesh[Rectangle[{0, -hAir}, {hAir, hAir}],
MaxCellMeasure -> 0.004^2, "MeshOrder" -> 2];
Show[mesh["Wireframe"], RegionPlot[Rectangle[{0, -h}, {h, h}]]]
(*Solve*)
\[Nu] =
If[x <= h && -h <= y <= h, 1/(\[Mu]o*\[Mu]r),
1/\[Mu]o]; (*isotropic reluctivity*)
\[CapitalGamma]d = \
{DirichletCondition[u[x, y] == 0, x == 0],
DirichletCondition[u[x, y] == -fluxDens*hAir^2/2, x == hAir]};
op = Div[\[Nu]/x*Grad[u[x, y], {x, y}], {x, y}];
mvpA = NDSolveValue[{op == 0, \[CapitalGamma]d},
u, {x, y} \[Element] mesh];
ContourPlot[mvpA[x, y], {x, y} \[Element] mesh,
ColorFunction -> "TemperatureMap", AspectRatio -> Automatic,
PlotLegends -> Automatic, Contours -> 20]
(*Flux Density*)
{B1x,
B1y} = {D[mvpA[x, y], y]/x, -D[mvpA[x, y], x]/x};
Plot[mvpA[xp, h/2], {xp, 0.0001, hAir}, PlotRange -> Full,
AxesLabel -> {"x distance (m)", "Magnetic Vector Potential (Wb/m)"},
PlotLabel ->
"Magnetic Vector Potential along x directed line for y=h/2"]
Plot[Evaluate[{B1x, B1y} /. {x -> xp, y -> h/2}], {xp, 0.0001, hAir},
PlotLegends -> {"Bx", "By"}, PlotRange -> Full,
AxesLabel -> {"x distance (m)", "Flux Density(T)"},
PlotLabel -> "Flux Density along x directed line for y=h/2"]
Here are the results 1) Azimuthal MVP 2) Flux Densities:
They compare favourably to that using the freely distributed FEMM software:
Now here is some 1/8 symmetry 3D code for the same problem but with the ungauged Curl-Curl equation (v12+ with OpenCascade needed):
Clear["Global`*"];
Needs["NDSolve`FEM`"];
Needs["OpenCascadeLink`"];
\[Mu]o = 4.0*\[Pi]*10^-7;
\[Mu]r = 1000.0;(*permeability iron region*)
h = 0.02; (*height and \
radius of permeable cylinder*)
hAir = 0.1; (*height/width/depth air \
region*)
fluxDens = 1.0; (*z directed B field*)
(*Create Air Region and Iron Cylinder*)
airShape =
OpenCascadeShape[Cuboid[{0, 0, 0}, {hAir, hAir, hAir}]];
ironShape =
OpenCascadeShapeIntersection[airShape,
OpenCascadeShape[Cylinder[{{0, 0, -1}, {0, 0, h}}, h]]];
regIron =
MeshRegion[
ToElementMesh[OpenCascadeShapeSurfaceMeshToBoundaryMesh[ironShape],
MaxCellMeasure -> Infinity]];
(*Create Problem Region*)
combined = OpenCascadeShapeUnion[{airShape, ironShape}];
problemShape = OpenCascadeShapeSewing[{combined, ironShape}];
bmesh = OpenCascadeShapeSurfaceMeshToBoundaryMesh[problemShape];
groups = bmesh["BoundaryElementMarkerUnion"]
temp = Most[Range[0, 1, 1/(Length[groups])]];
colors = {Opacity[0.75], ColorData["BrightBands"][#]} & /@ temp;
bmesh["Wireframe"["MeshElementStyle" -> FaceForm /@ colors,
"MeshElementMarkerStyle" -> White]]
(*Create Mesh*)
mrf = With[{rmf1 = RegionMember[regIron]},
Function[{vertices, volume},
Block[{x, y, z}, {x, y, z} = Mean[vertices];
If[rmf1[{x, y, z}], volume > 0.002^3,
volume > (2*(x^2 + y^2 + z^2 - h^2) + 0.002)^3]]]];
mesh = ToElementMesh[bmesh, MeshRefinementFunction -> mrf,
MaxCellMeasure -> 0.01^3, "MeshOrder" -> 2]
Show[mesh["Wireframe"], Axes -> True, AxesLabel -> {x, y, z}]
(*Solve*)
\[Nu] =
If[x^2 + y^2 \[LessSlantEqual] h^2 && z \[LessSlantEqual] h,
1/(\[Mu]r*\[Mu]o),
1/\[Mu]o]; (*isotropic reluctivity*)
\[CapitalGamma]d = \
{DirichletCondition[ux[x, y, z] == 0, y == 0],
DirichletCondition[ux[x, y, z] == -fluxDens*hAir/2, y == hAir],
DirichletCondition[uy[x, y, z] == 0, x == 0],
DirichletCondition[uy[x, y, z] == fluxDens*hAir/2, x == hAir],
DirichletCondition[uz[x, y, z] == 0, z == 0 || y == 0 || x == 0]};
\[CapitalGamma]n = {0, 0, 0};
u = {ux[x, y, z], uy[x, y, z], uz[x, y, z]};
op = Curl[\[Nu]*Curl[u, {x, y, z}], {x, y,
z}];(*Ungauged*)
mvpA = {mvpAx, mvpAy, mvpAz} =
NDSolveValue[{op == \[CapitalGamma]n, \[CapitalGamma]d}, {ux, uy,
uz}, {x, y, z} \[Element] mesh];
(*flux density = curl A*)
{Bx, By,
Bz} = {(D[mvpAz[x, y, z], y] -
D[mvpAy[x, y, z], z]), (D[mvpAx[x, y, z], z] -
D[mvpAz[x, y, z], x]),
D[mvpAy[x, y, z], x] - D[mvpAx[x, y, z], y]};
Plot[{mvpAx[xp, 0, h/2], mvpAy[xp, 0, h/2], mvpAz[xp, 0, h/2]}, {xp,
0, hAir}, PlotLegends -> "Expressions",
AxesLabel -> {"x distance (m)", "Potential (Wb/m)"},
PlotLabel -> "MVP along x directed line for y=0,z=h/2"]
Plot[Evaluate[{Bx, By, Bz} /. {x -> xp, y -> 0, z -> h/2}], {xp, 0,
hAir}, PlotLegends -> {"Bx", "By", "Bz"}, PlotRange -> Full,
AxesLabel -> {"x distance (m)", "Flux Density(T)"},
PlotLabel -> "Flux Density along x directed line for y=0,z=h/2"]
Plot[Evaluate[{Bx, By, Bz} /. {x -> 0, y -> yp, z -> h/2}], {yp, 0,
hAir}, PlotLegends -> {"Bx", "By", "Bz"}, PlotRange -> Full,
AxesLabel -> {"y distance (m)", "Flux Density(T)"},
PlotLabel -> "Flux Density along y directed line for x=0,z=h/2"]
Here is the mesh and result:
Again the 3D result gives a lower flux density in the cylinder than expected, even though Bz is 1T outside of the cylinder as required. In summary I still do not know why the result is in error. As User21 points out maybe it is the applied boundary conditions, but I have not found a condition that makes it right. While I had/have access to advanced 3D software like Opera and Maxwell, I like understanding the basics as well and Mathematica is great for that.
As reference the 3D code for the cylinder takes 23sec to run on an early 2011 MacBookPro with 4 cores and updated to 16Gig Ram + SSD.
appro
. $\endgroup$