Magnetostatic Problem on a Topologically Closed Conductor

Summary

Solves for the magnetic field around a topologically closed toroidal conductor carrying a net current, using a magnetic vector potential discretized using conforming Nédélec elements, following the method described in P. Dular, International Compumag Society Newsletter, 7, no. 2 (2000): 4-7. for enforcing global constraints.

Description

This problem solves the magnetostatic problem with strong form:

with the constitutive relations

where is the magnetic flux density, is the magnetic field, is the current density, is the electric field, is the externally applied electric field, is the material permeability, is the material resistivity, and is the material conductivity. To enforce the divergence free condition on , we shall also introduce the magnetic vector potential defined such that .

In addition, we wish to impose a global voltage constraint on a toroidal conductor domain of the form

where is any path traversing around the conductor lying entirely within with winding number equal to one, and is the loop voltage imposed on the conductor.

In a simply connected domain, satisfying can be achieved by introducing a continuous single-valued scalar electric potential such that . However, in order for the electric field to be nonzero circulating the closed conductor, this is no longer sufficient, since . We shall therefore represent the electric field by

With this, we can arrive at the weak form for we intend to solve, of the form

where

Discretizing the External Electric Field

We must now find a suitable choice of basis for the external electric field, such that

for some unique set of degrees of freedom . We shall also wish to restrict the support of to the smallest number of elements possible in to minimise the number of degrees of freedom required to represent .

To do this, we first identify a 'cut' surface of the toroidal conductor that is required to make it simply connected. Next, we define a one element wide 'transition' region on one side of the cut surface, using an MFEMCutTransitionSubMesh object, comprised of all elements with at least one vertex lying on the cut surface that lie on one side of the cut.

Next, to enforce in this region, we define a scalar field variable defined on a (nodal) conforming finite element space on this submesh such that :

so .

Finally, to impose the global voltage constraint , we identify that

since outside . The loop voltage constraint is thus strongly imposed by Dirichlet conditions on , setting on and on . For lowest order conforming representations of , this fully constrains in the transition region.

This is shown explicitly in the example file:

# Solve for the electric field on a closed conductor subject to
# global loop voltage constraint.

initial_coil_domains = 'TorusCore TorusSheath'
coil_cut_surface = 'Cut'
coil_loop_voltage = -1.0
coil_conductivity = 1.0

[Problem<<<{"href": "../Problem/index.html"}>>>]
  type = MFEMProblem
[]

[Mesh<<<{"href": "../Mesh/index.html"}>>>]
    type = MFEMMesh
    file = ../mesh/embedded_concentric_torus.e
[]

[FunctorMaterials<<<{"href": "../FunctorMaterials/index.html"}>>>]
  [Conductor]
    type = MFEMGenericFunctorMaterial
    prop_names = conductivity
    prop_values = ${coil_conductivity}
  []
[]

[ICs<<<{"href": "../ICs/index.html"}>>>]
  [coil_external_potential_ic]
    type = MFEMScalarBoundaryIC
    variable = coil_external_potential
    boundary = ${coil_cut_surface}
    coefficient = ${coil_loop_voltage}
  []
[]

[SubMeshes]
  [cut]
    type = MFEMCutTransitionSubMesh
    cut_boundary = ${coil_cut_surface}
    block = ${initial_coil_domains}
    transition_subdomain = transition_dom
    transition_subdomain_boundary = transition_bdr
    closed_subdomain = coil_dom
  []
  [coil]
    type = MFEMDomainSubMesh
    block = coil_dom
    execution_order_group = 2
  []
[]

[FESpaces]
  [H1FESpace]
    type = MFEMScalarFESpace
    fec_type = H1
    fec_order = FIRST
  []
  [HCurlFESpace]
    type = MFEMVectorFESpace
    fec_type = ND
    fec_order = FIRST
  []
  [CoilH1FESpace]
    type = MFEMScalarFESpace
    fec_type = H1
    fec_order = FIRST
    submesh = coil
  []
  [CoilHCurlFESpace]
    type = MFEMVectorFESpace
    fec_type = ND
    fec_order = FIRST
    submesh = coil
  []
  [TransitionH1FESpace]
    type = MFEMScalarFESpace
    fec_type = H1
    fec_order = FIRST
    submesh = cut
  []
  [TransitionHCurlFESpace]
    type = MFEMVectorFESpace
    fec_type = ND
    fec_order = FIRST
    submesh = cut
  []
[]

[Variables<<<{"href": "../Variables/index.html"}>>>]
  [coil_induced_potential]
    type = MFEMVariable
    fespace = CoilH1FESpace
  []
[]

[AuxVariables<<<{"href": "../AuxVariables/index.html"}>>>]
  [coil_external_potential]
    type = MFEMVariable
    fespace = CoilH1FESpace
  []
  [transition_external_potential]
    type = MFEMVariable
    fespace = TransitionH1FESpace
  []
  [transition_external_e_field]
    type = MFEMVariable
    fespace = TransitionHCurlFESpace
  []
  [induced_potential]
    type = MFEMVariable
    fespace = H1FESpace
  []
  [induced_e_field]
    type = MFEMVariable
    fespace = HCurlFESpace
  []
  [external_e_field]
    type = MFEMVariable
    fespace = HCurlFESpace
  []
  [e_field]
    type = MFEMVariable
    fespace = HCurlFESpace
  []
[]

[AuxKernels<<<{"href": "../AuxKernels/index.html"}>>>]
  [update_induced_e_field]
    type = MFEMGradAux
    variable = induced_e_field
    source = induced_potential
    scale_factor = -1.0
    execute_on = TIMESTEP_END
  []
  [update_external_e_field]
    type = MFEMGradAux
    variable = transition_external_e_field
    source = transition_external_potential
    scale_factor = -1.0
    execute_on = TIMESTEP_END
  []
  [update_total_e_field]
    type = MFEMSumAux
    variable = e_field
    first_source_variable = induced_e_field
    second_source_variable = external_e_field
    execute_on = TIMESTEP_END
    execution_order_group = 3
  []
[]

[Kernels<<<{"href": "../Kernels/index.html"}>>>]
  [diff]
    type = MFEMDiffusionKernel
    variable = coil_induced_potential
    coefficient = conductivity
  []
  [source]
    type = MFEMMixedGradGradKernel
    trial_variable = coil_external_potential
    variable = coil_induced_potential
    coefficient = conductivity
    block = 'transition_dom'
  []
[]

[Solver]
  type = MFEMSuperLU
[]

[Executioner<<<{"href": "../Executioner/index.html"}>>>]
  type = MFEMSteady
[]

[Transfers<<<{"href": "../Transfers/index.html"}>>>]
  [submesh_transfer_from_coil]
    type = MFEMSubMeshTransfer
    from_variable = coil_induced_potential
    to_variable = induced_potential
    execute_on = TIMESTEP_END
  []
  [submesh_transfer_to_transition]
    type = MFEMSubMeshTransfer
    from_variable = coil_external_potential
    to_variable = transition_external_potential
    execute_on = TIMESTEP_END
  []
  [submesh_transfer_from_transition]
    type = MFEMSubMeshTransfer
    from_variable = transition_external_e_field
    to_variable = external_e_field
    execute_on = TIMESTEP_END
    execution_order_group = 2
  []
[]

[Outputs<<<{"href": "../Mastodon/Outputs/index.html"}>>>]
  [GlobalParaViewDataCollection]
    type = MFEMParaViewDataCollection
    file_base = OutputData/WholePotentialCoil
    vtk_format = ASCII
  []
  [TransitionParaViewDataCollection]
    type = MFEMParaViewDataCollection
    file_base = OutputData/CutPotentialCoil
    vtk_format = ASCII
    submesh = cut
  []
  [CoilParaViewDataCollection]
    type = MFEMParaViewDataCollection
    file_base = OutputData/Coil
    vtk_format = ASCII
    submesh = coil
  []
[]
(moose/test/tests/mfem/submeshes/cut_closed_coil.i)

Solving for the Total Electric Field

With now constrained, we now must solve for the remaining degrees of freedom in arising due to surface charges in the coil as required for continuity of current in the conductor (), by solving

for . We then can calculate the total electric field from the sum of its induced and external components.

Solving for the Magnetic Flux Density

Finally, with the electric field in the conductor, we can now find the magnetic flux density around the current density flowing in the coil in the magnetostatic limit.

This is typically solved using HypreAMS as a preconditioner. Note that the above magnetostatic problem is singular in non-conductive regions, and thus either singular = true should be passed to the preconditioner or a small mass term with should be added for stability.

Example File

The full closed coil magnetostatic example detailed above can be found below:

# Magnetostatic problem solved on a closed conductor subject to
# global loop voltage constraint.

[Mesh<<<{"href": "../Mesh/index.html"}>>>]
  type = MFEMMesh
  file = ../mesh/embedded_concentric_torus.e
[]

[Problem<<<{"href": "../Problem/index.html"}>>>]
  type = MFEMProblem
[]

[FESpaces]
  [HCurlFESpace]
    type = MFEMVectorFESpace
    fec_type = ND
    fec_order = FIRST
  []
  [HDivFESpace]
    type = MFEMVectorFESpace
    fec_type = RT
    fec_order = CONSTANT
  []
[]

[Variables<<<{"href": "../Variables/index.html"}>>>]
  [a_field]
    type = MFEMVariable
    fespace = HCurlFESpace
  []
[]

[AuxVariables<<<{"href": "../AuxVariables/index.html"}>>>]
  [b_field]
    type = MFEMVariable
    fespace = HDivFESpace
  []
  [e_field]
    type = MFEMVariable
    fespace = HCurlFESpace
  []
[]

[AuxKernels<<<{"href": "../AuxKernels/index.html"}>>>]
  [curl]
    type = MFEMCurlAux
    variable = b_field
    source = a_field
    scale_factor = 1.0
    execute_on = TIMESTEP_END
    execution_order_group = 3
  []
[]

[Functions<<<{"href": "../Functions/index.html"}>>>]
  [exact_a_field]
    type = ParsedVectorFunction<<<{"description": "Returns a vector function based on string descriptions for each component.", "href": "../../source/functions/MooseParsedVectorFunction.html"}>>>
    expression_x<<<{"description": "x-component of function."}>>> = '0'
    expression_y<<<{"description": "y-component of function."}>>> = '0'
    expression_z<<<{"description": "z-component of function."}>>> = '0'
  []
[]

[BCs<<<{"href": "../BCs/index.html"}>>>]
  [tangential_a_bdr]
    type = MFEMVectorTangentialDirichletBC
    variable = a_field
    vector_coefficient = exact_a_field
    boundary = 'Exterior'
  []
[]

[FunctorMaterials<<<{"href": "../FunctorMaterials/index.html"}>>>]
  [Vacuum]
    type = MFEMGenericFunctorMaterial
    prop_names = reluctivity
    prop_values = 1.0
  []
  [Conductor]
    type = MFEMGenericFunctorMaterial
    prop_names = conductivity
    prop_values = 1.0
    block = 'TorusCore TorusSheath'
  []
[]

[Kernels<<<{"href": "../Kernels/index.html"}>>>]
  [mass]
    type = MFEMVectorFEMassKernel
    variable = a_field
    coefficient = 1e-10
  []
  [curlcurl]
    type = MFEMCurlCurlKernel
    variable = a_field
    coefficient = reluctivity
  []
  [source]
    type = MFEMMixedVectorMassKernel
    variable = a_field
    trial_variable = e_field
    coefficient = conductivity
    block = 'TorusCore TorusSheath'
  []
[]

[Preconditioner]
  [ams]
    type = MFEMHypreAMS
    fespace = HCurlFESpace
  []
[]

[Solver]
  type = MFEMHyprePCG
  preconditioner = ams
  l_tol = 1e-14
  l_max_its = 1000
[]

[Executioner<<<{"href": "../Executioner/index.html"}>>>]
  type = MFEMSteady
  device = cpu
[]

[MultiApps<<<{"href": "../MultiApps/index.html"}>>>]
  [coil]
    type = FullSolveMultiApp<<<{"description": "Performs a complete simulation during each execution.", "href": "../../source/multiapps/FullSolveMultiApp.html"}>>>
    input_files<<<{"description": "The input file for each App.  If this parameter only contains one input file it will be used for all of the Apps.  When using 'positions_from_file' it is also admissable to provide one input_file per file."}>>> = cut_closed_coil.i
    execute_on<<<{"description": "The list of flag(s) indicating when this object should be executed. For a description of each flag, see https://mooseframework.inl.gov/source/interfaces/SetupInterface.html."}>>> = INITIAL
  []
[]

[Transfers<<<{"href": "../Transfers/index.html"}>>>]
  [from_coil]
    type = MultiAppMFEMCopyTransfer
    source_variable = e_field
    variable = e_field
    from_multi_app = coil
  []
[]

[Postprocessors<<<{"href": "../Postprocessors/index.html"}>>>]
  [CoilPower]
    type = MFEMVectorFEInnerProductIntegralPostprocessor
    coefficient = conductivity
    dual_variable = e_field
    primal_variable = e_field
    block = 'TorusCore TorusSheath'
  []
[]

[Outputs<<<{"href": "../Mastodon/Outputs/index.html"}>>>]
  [ParaViewDataCollection]
    type = MFEMParaViewDataCollection
    file_base = OutputData/MagnetostaticClosedCoil
    vtk_format = ASCII
  []
  [ReportedPostprocessors]
    type = CSV<<<{"description": "Output for postprocessors, vector postprocessors, and scalar variables using comma seperated values (CSV).", "href": "../../source/outputs/CSV.html"}>>>
    file_base<<<{"description": "The desired solution output name without an extension. If not provided, MOOSE sets it with Outputs/file_base when available. Otherwise, MOOSE uses input file name and this object name for a master input or uses master file_base, the subapp name and this object name for a subapp input to set it."}>>> = OutputData/AVMagnetostaticClosedCoilCSV
  []
[]
(moose/test/tests/mfem/submeshes/av_magnetostatic.i)

Global Current Constraints

Strongly constraining the total current through the conductor instead of the loop voltage is also possible, by defining a cut plane in the domain outside the conductor region using the H-phi formulation instead of the A-V formulation detailed above; the details are similar, using a magnetic scalar potential instead of an electric scalar potential. The constraint to be enforced is

where is any path encircling the conductor, and I is the total current passing through the path . Outside the conductor, the magnetic field is curl-free, and thus can be expressed by the gradient of a scalar magnetic potential (up to a sign).

An example file for this approach can be found below:

# Solve for the magnetic field around a closed conductor subject to
# global current constraint.

initial_vacuum_domains = 'Exterior'
vacuum_cut_surface = 'Cut'
conductor_current = 1.0
vacuum_permeability = 1.0

[Problem<<<{"href": "../Problem/index.html"}>>>]
  type = MFEMProblem
[]

[Mesh<<<{"href": "../Mesh/index.html"}>>>]
    type = MFEMMesh
    file = ../mesh/split_embedded_concentric_torus.e
[]

[FunctorMaterials<<<{"href": "../FunctorMaterials/index.html"}>>>]
  [Conductor]
    type = MFEMGenericFunctorMaterial
    prop_names = permeability
    prop_values = ${vacuum_permeability}
  []
[]

[ICs<<<{"href": "../ICs/index.html"}>>>]
  [vacuum_cut_potential_ic]
    type = MFEMScalarBoundaryIC
    variable = vacuum_cut_potential
    boundary = ${vacuum_cut_surface}
    coefficient = ${conductor_current}
  []
[]

[SubMeshes]
  [cut]
    type = MFEMCutTransitionSubMesh
    cut_boundary = ${vacuum_cut_surface}
    block = ${initial_vacuum_domains}
    transition_subdomain = transition_dom
    transition_subdomain_boundary = transition_bdr
    closed_subdomain = vacuum_dom
  []
  [vacuum]
    type = MFEMDomainSubMesh
    block = vacuum_dom
    execution_order_group = 2
  []
[]

[FESpaces]
  [VacuumH1FESpace]
    type = MFEMScalarFESpace
    fec_type = H1
    fec_order = FIRST
    submesh = vacuum
  []
  [VacuumHCurlFESpace]
    type = MFEMVectorFESpace
    fec_type = ND
    fec_order = FIRST
    submesh = vacuum
  []
  [TransitionH1FESpace]
    type = MFEMScalarFESpace
    fec_type = H1
    fec_order = FIRST
    submesh = cut
  []
  [TransitionHCurlFESpace]
    type = MFEMVectorFESpace
    fec_type = ND
    fec_order = FIRST
    submesh = cut
  []
  [HCurlFESpace]
    type = MFEMVectorFESpace
    fec_type = ND
    fec_order = FIRST
  []
[]

[Variables<<<{"href": "../Variables/index.html"}>>>]
  [vacuum_magnetic_potential]
    type = MFEMVariable
    fespace = VacuumH1FESpace
  []
[]

[AuxVariables<<<{"href": "../AuxVariables/index.html"}>>>]
  [vacuum_cut_potential]
    type = MFEMVariable
    fespace = VacuumH1FESpace
  []
  [transition_cut_potential]
    type = MFEMVariable
    fespace = TransitionH1FESpace
  []
  [transition_cut_function_field]
    type = MFEMVariable
    fespace = TransitionHCurlFESpace
  []
  [background_h_field]
    type = MFEMVariable
    fespace = VacuumHCurlFESpace
  []
  [cut_function_field]
    type = MFEMVariable
    fespace = VacuumHCurlFESpace
  []
  [vacuum_h_field]
    type = MFEMVariable
    fespace = VacuumHCurlFESpace
  []
  [h_field]
    type = MFEMVariable
    fespace = HCurlFESpace
  []
[]

[AuxKernels<<<{"href": "../AuxKernels/index.html"}>>>]
  [update_background_h_field]
    type = MFEMGradAux
    variable = background_h_field
    source = vacuum_magnetic_potential
    scale_factor = -1.0
    execute_on = TIMESTEP_END
  []
  [update_transition_cut_function_field]
    type = MFEMGradAux
    variable = transition_cut_function_field
    source = transition_cut_potential
    scale_factor = -1.0
    execute_on = TIMESTEP_END
  []
  [update_total_h_field]
    type = MFEMSumAux
    variable = vacuum_h_field
    first_source_variable = background_h_field
    second_source_variable = cut_function_field
    execute_on = TIMESTEP_END
    execution_order_group = 3
  []
[]

[BCs<<<{"href": "../BCs/index.html"}>>>]
  # Set zero of magnetic potential on symmetry plane
  [Exterior]
    type = MFEMScalarDirichletBC
    variable = vacuum_magnetic_potential
    boundary = 'Cut'
    coefficient = 0.0
  []
[]

[Kernels<<<{"href": "../Kernels/index.html"}>>>]
  [diff]
    type = MFEMDiffusionKernel
    variable = vacuum_magnetic_potential
    coefficient = permeability
  []
  [source]
    type = MFEMMixedGradGradKernel
    trial_variable = vacuum_cut_potential
    variable = vacuum_magnetic_potential
    coefficient = permeability
    block = 'transition_dom'
  []
[]

[Preconditioner]
  [boomeramg]
    type = MFEMHypreBoomerAMG
  []
[]

[Solver]
  type = MFEMHypreGMRES
  preconditioner = boomeramg
  l_tol = 1e-8
  l_max_its = 100
[]

[Executioner<<<{"href": "../Executioner/index.html"}>>>]
  type = MFEMSteady
[]

[Transfers<<<{"href": "../Transfers/index.html"}>>>]
  [submesh_transfer_to_transition]
    type = MFEMSubMeshTransfer
    from_variable = vacuum_cut_potential
    to_variable = transition_cut_potential
    execute_on = TIMESTEP_END
  []
  [submesh_transfer_from_transition]
    type = MFEMSubMeshTransfer
    from_variable = transition_cut_function_field
    to_variable = cut_function_field
    execute_on = TIMESTEP_END
    execution_order_group = 2
  []
  [submesh_transfer_from_vacuum]
    type = MFEMSubMeshTransfer
    from_variable = vacuum_h_field
    to_variable = h_field
    execute_on = TIMESTEP_END
    execution_order_group = 4
  []
[]

[Postprocessors<<<{"href": "../Postprocessors/index.html"}>>>]
  [MagneticEnergy]
    type = MFEMVectorFEInnerProductIntegralPostprocessor
    coefficient = ${fparse 0.5*vacuum_permeability}
    dual_variable = vacuum_h_field
    primal_variable = vacuum_h_field
    execution_order_group = 4
    block = 'Exterior'
  []
[]

[Outputs<<<{"href": "../Mastodon/Outputs/index.html"}>>>]
  [ReportedPostprocessors]
    type = CSV<<<{"description": "Output for postprocessors, vector postprocessors, and scalar variables using comma seperated values (CSV).", "href": "../../source/outputs/CSV.html"}>>>
    file_base<<<{"description": "The desired solution output name without an extension. If not provided, MOOSE sets it with Outputs/file_base when available. Otherwise, MOOSE uses input file name and this object name for a master input or uses master file_base, the subapp name and this object name for a subapp input to set it."}>>> = OutputData/HPhiMagnetostaticClosedCoilCSV
  []
  [VacuumParaViewDataCollection]
    type = MFEMParaViewDataCollection
    file_base = OutputData/HPhiMagnetostaticClosedCoil
    vtk_format = ASCII
    submesh = vacuum
  []
[]
(moose/test/tests/mfem/submeshes/hphi_magnetostatic.i)