Module traceon.excitation

The excitation module allows to specify the excitation (or element types) of the different physical groups (electrodes) created with the traceon.geometry module.

The possible excitations are as follows:

  • Fixed voltage (electrode connect to a power supply)
  • Voltage function (a generic Python function specifies the voltage as a function of position)
  • Dielectric, with arbitrary electric permittivity
  • Current coil, with fixed total amount of current (only in radial symmetry)
  • Magnetostatic scalar potential
  • Magnetizable material, with arbitrary magnetic permeability

Currently current excitations are not supported in 3D. But magnetostatic fields can still be computed using the magnetostatic scalar potential.

Once the excitation is specified, it can be passed to solve_direct() to compute the resulting field.

Classes

class Excitation (mesh, symmetry)
Expand source code
class Excitation:
    """ """
     
    def __init__(self, mesh, symmetry):
        self.mesh = mesh
        self.electrodes = mesh.get_electrodes()
        self.excitation_types = {}
        self.symmetry = symmetry
         
        if symmetry == Symmetry.RADIAL:
            assert self.mesh.points.shape[1] == 2 or np.all(self.mesh.points[:, 1] == 0.), \
                "When symmetry is RADIAL, the geometry should lie in the XZ plane"
    
    def __str__(self):
        return f'<Traceon Excitation,\n\t' \
            + '\n\t'.join([f'{n}={v} ({t})' for n, (t, v) in self.excitation_types.items()]) \
            + '>'
     
    def add_voltage(self, **kwargs):
        """
        Apply a fixed voltage to the geometries assigned the given name.
        
        Parameters
        ----------
        **kwargs : dict
            The keys of the dictionary are the geometry names, while the values are the voltages in units of Volt. For example,
            calling the function as `add_voltage(lens=50)` assigns a 50V value to the geometry elements part of the 'lens' physical group.
            Alternatively, the value can be a function, which takes x, y, z coordinates as argument and returns the voltage at that position.
            Note that in 2D symmetries (such as radial symmetry) the z value for this function will always be zero.
        
        """
        for name, voltage in kwargs.items():
            assert name in self.electrodes, f'Cannot add {name} to excitation, since it\'s not present in the mesh'
            if isinstance(voltage, int) or isinstance(voltage, float):
                self.excitation_types[name] = (ExcitationType.VOLTAGE_FIXED, voltage)
            elif callable(voltage):
                self.excitation_types[name] = (ExcitationType.VOLTAGE_FUN, voltage)
            else:
                raise NotImplementedError('Unrecognized voltage value')

    def add_current(self, **kwargs):
        """
        Apply a fixed total current to the geometries assigned the given name. Note that a coil is assumed,
        which implies that the current density is constant as a function of (r, z). In a solid piece of conducting material the current density would
        be higher at small r (as the 'loop' around the axis is shorter and therefore the resistance is lower).
        
        Parameters
        ----------
        **kwargs : dict
            The keys of the dictionary are the geometry names, while the values are the currents in units of Ampere. For example,
            calling the function as `add_current(coild=10)` assigns a 10A value to the geometry elements part of the 'coil' physical group.
        """

        assert self.symmetry == Symmetry.RADIAL, "Currently magnetostatics are only supported for radially symmetric meshes"
         
        for name, current in kwargs.items():
            assert name in self.mesh.physical_to_triangles.keys(), "Current can only be applied to a triangle electrode"
            self.excitation_types[name] = (ExcitationType.CURRENT, current)

    def has_current(self):
        """Check whether a current is applied in this excitation."""
        return any([t == ExcitationType.CURRENT for t, _ in self.excitation_types.values()])
    
    def is_electrostatic(self):
        """Check whether the excitation contains electrostatic fields."""
        return any([t in [ExcitationType.VOLTAGE_FIXED, ExcitationType.VOLTAGE_FUN] for t, _ in self.excitation_types.values()])
     
    def is_magnetostatic(self):
        """Check whether the excitation contains magnetostatic fields."""
        return any([t in [ExcitationType.MAGNETOSTATIC_POT, ExcitationType.CURRENT] for t, _ in self.excitation_types.values()])
     
    def add_magnetostatic_potential(self, **kwargs):
        """
        Apply a fixed magnetostatic potential to the geometries assigned the given name.
        
        Parameters
        ----------
        **kwargs : dict
            The keys of the dictionary are the geometry names, while the values are the voltages in units of Ampere. For example,
            calling the function as `add_magnetostatic_potential(lens=50)` assigns a 50A value to the geometry elements part of the 'lens' physical group.
        """
        for name, pot in kwargs.items():
            assert name in self.electrodes, f'Cannot add {name} to excitation, since it\'s not present in the mesh'
            self.excitation_types[name] = (ExcitationType.MAGNETOSTATIC_POT, pot)

    def add_magnetizable(self, **kwargs):
        """
        Assign a relative magnetic permeability to the geometries assigned the given name.
        
        Parameters
        ----------
        **kwargs : dict
            The keys of the dictionary are the geometry names, while the values are the relative dielectric constants. For example,
            calling the function as `add_dielectric(spacer=2)` assign the relative dielectric constant of 2 to the `spacer` physical group.
         
        """

        for name, permeability in kwargs.items():
            assert name in self.electrodes, f'Cannot add {name} to excitation, since it\'s not present in the mesh'
            self.excitation_types[name] = (ExcitationType.MAGNETIZABLE, permeability)
     
    def add_dielectric(self, **kwargs):
        """
        Assign a dielectric constant to the geometries assigned the given name.
        
        Parameters
        ----------
        **kwargs : dict
            The keys of the dictionary are the geometry names, while the values are the relative dielectric constants. For example,
            calling the function as `add_dielectric(spacer=2)` assign the relative dielectric constant of 2 to the `spacer` physical group.
         
        """
        for name, permittivity in kwargs.items():
            assert name in self.electrodes, f'Cannot add {name} to excitation, since it\'s not present in the mesh'
            self.excitation_types[name] = (ExcitationType.DIELECTRIC, permittivity)

    def add_electrostatic_boundary(self, *args, ensure_inward_normals=True):
        """
        Specify geometry elements as electrostatic boundary elements. At the boundary we require E·n = 0 at every point on the boundary. This
        is equivalent to stating that the directional derivative of the electrostatic potential through the boundary is zero. Placing boundaries between
        the spaces of electrodes usually helps convergence tremendously. Note that a boundary is equivalent to a dielectric with a dielectric
        constant of zero. This is how a boundary is actually implemented internally.
        
        Parameters
        ----------
        *args: list of str
            The geometry names that should be considered a boundary.
        """
        if ensure_inward_normals:
            for electrode in args:
                self.mesh.ensure_inward_normals(electrode)
        
        self.add_dielectric(**{a:0 for a in args})
    
    def add_magnetostatic_boundary(self, *args, ensure_inward_normals=True):
        """
        Specify geometry elements as magnetostatic boundary elements. At the boundary we require H·n = 0 at every point on the boundary. This
        is equivalent to stating that the directional derivative of the magnetostatic potential through the boundary is zero. Placing boundaries between
        the spaces of electrodes usually helps convergence tremendously. Note that a boundary is equivalent to a magnetic material with a magnetic 
        permeability of zero. This is how a boundary is actually implemented internally.
        
        Parameters
        ----------
        *args: list of str
            The geometry names that should be considered a boundary.
        """
        if ensure_inward_normals:
            for electrode in args:
                print('flipping normals', electrode)
                self.mesh.ensure_inward_normals(electrode)
        
        self.add_magnetizable(**{a:0 for a in args})
    
    def _split_for_superposition(self):
        
        # Names that have a fixed voltage excitation, not equal to 0.0
        types = self.excitation_types
        non_zero_fixed = [n for n, (t, v) in types.items() if t in [ExcitationType.VOLTAGE_FIXED,
                                                                    ExcitationType.CURRENT] and v != 0.0]
        
        excitations = []
         
        for name in non_zero_fixed:
             
            new_types_dict = {}
             
            for n, (t, v) in types.items():
                assert t != ExcitationType.VOLTAGE_FUN, "VOLTAGE_FUN excitation not supported for superposition."
                 
                if n == name:
                    new_types_dict[n] = (t, 1.0)
                elif t == ExcitationType.VOLTAGE_FIXED:
                    new_types_dict[n] = (t, 0.0)
                elif t == ExcitationType.CURRENT:
                    new_types_dict[n] = (t, 0.0)
                else:
                    new_types_dict[n] = (t, v)
            
            exc = Excitation(self.mesh, self.symmetry)
            exc.excitation_types = new_types_dict
            excitations.append(exc)

        assert len(non_zero_fixed) == len(excitations)
        return {n:e for (n,e) in zip(non_zero_fixed, excitations)}

    def _get_active_elements(self, type_):
        assert type_ in ['electrostatic', 'magnetostatic']
        
        if self.symmetry == Symmetry.RADIAL:
            elements = self.mesh.lines
            physicals = self.mesh.physical_to_lines
        else:
            elements = self.mesh.triangles
            physicals = self.mesh.physical_to_triangles

        def type_check(excitation_type):
            if type_ == 'electrostatic':
                return excitation_type.is_electrostatic()
            else:
                return excitation_type in [ExcitationType.MAGNETIZABLE, ExcitationType.MAGNETOSTATIC_POT]
        
        inactive = np.full(len(elements), True)
        for name, value in self.excitation_types.items():
            if type_check(value[0]):
                inactive[ physicals[name] ] = False
         
        map_index = np.arange(len(elements)) - np.cumsum(inactive)
        names = {n:map_index[i] for n, i in physicals.items() \
                    if n in self.excitation_types and type_check(self.excitation_types[n][0])}
         
        return self.mesh.points[ elements[~inactive] ], names
     
    def get_electrostatic_active_elements(self):
        """Get elements in the mesh that have an electrostatic excitation
        applied to them. 
         
        Returns
        --------
        A tuple of two elements: (points, names). points is a Numpy array of shape (N, 4, 3) in the case of 2D and (N, 3, 3) in the case of 3D. \
        This array contains the vertices of the line elements or the triangles. \
        Multiple points per line elements are used in the case of 2D since higher order BEM is employed, in which the true position on the line \
        element is given by a polynomial interpolation of the points. \
        names is a dictionary, the keys being the names of the physical groups mentioned by this excitation, \
        while the values are Numpy arrays of indices that can be used to index the points array.
        """
        return self._get_active_elements('electrostatic')
    
    def get_magnetostatic_active_elements(self):
        """Get elements in the mesh that have an magnetostatic excitation
        applied to them. This does not include current excitation, as these are not part of the matrix.
    
        Returns
        --------
        A tuple of two elements: (points, names). points is a Numpy array of shape (N, 4, 3) in the case of 2D and (N, 3, 3) in the case of 3D. \
        This array contains the vertices of the line elements or the triangles. \
        Multiple points per line elements are used in the case of 2D since higher order BEM is employed, in which the true position on the line \
        element is given by a polynomial interpolation of the points. \
        names is a dictionary, the keys being the names of the physical groups mentioned by this excitation, \
        while the values are Numpy arrays of indices that can be used to index the points array.
        """

        return self._get_active_elements('magnetostatic')

Methods

def add_current(self, **kwargs)

Apply a fixed total current to the geometries assigned the given name. Note that a coil is assumed, which implies that the current density is constant as a function of (r, z). In a solid piece of conducting material the current density would be higher at small r (as the 'loop' around the axis is shorter and therefore the resistance is lower).

Parameters

**kwargs : dict
The keys of the dictionary are the geometry names, while the values are the currents in units of Ampere. For example, calling the function as add_current(coild=10) assigns a 10A value to the geometry elements part of the 'coil' physical group.
def add_dielectric(self, **kwargs)

Assign a dielectric constant to the geometries assigned the given name.

Parameters

**kwargs : dict
The keys of the dictionary are the geometry names, while the values are the relative dielectric constants. For example, calling the function as add_dielectric(spacer=2) assign the relative dielectric constant of 2 to the spacer physical group.
def add_electrostatic_boundary(self, *args, ensure_inward_normals=True)

Specify geometry elements as electrostatic boundary elements. At the boundary we require E·n = 0 at every point on the boundary. This is equivalent to stating that the directional derivative of the electrostatic potential through the boundary is zero. Placing boundaries between the spaces of electrodes usually helps convergence tremendously. Note that a boundary is equivalent to a dielectric with a dielectric constant of zero. This is how a boundary is actually implemented internally.

Parameters

*args : list of str
The geometry names that should be considered a boundary.
def add_magnetizable(self, **kwargs)

Assign a relative magnetic permeability to the geometries assigned the given name.

Parameters

**kwargs : dict
The keys of the dictionary are the geometry names, while the values are the relative dielectric constants. For example, calling the function as add_dielectric(spacer=2) assign the relative dielectric constant of 2 to the spacer physical group.
def add_magnetostatic_boundary(self, *args, ensure_inward_normals=True)

Specify geometry elements as magnetostatic boundary elements. At the boundary we require H·n = 0 at every point on the boundary. This is equivalent to stating that the directional derivative of the magnetostatic potential through the boundary is zero. Placing boundaries between the spaces of electrodes usually helps convergence tremendously. Note that a boundary is equivalent to a magnetic material with a magnetic permeability of zero. This is how a boundary is actually implemented internally.

Parameters

*args : list of str
The geometry names that should be considered a boundary.
def add_magnetostatic_potential(self, **kwargs)

Apply a fixed magnetostatic potential to the geometries assigned the given name.

Parameters

**kwargs : dict
The keys of the dictionary are the geometry names, while the values are the voltages in units of Ampere. For example, calling the function as add_magnetostatic_potential(lens=50) assigns a 50A value to the geometry elements part of the 'lens' physical group.
def add_voltage(self, **kwargs)

Apply a fixed voltage to the geometries assigned the given name.

Parameters

**kwargs : dict
The keys of the dictionary are the geometry names, while the values are the voltages in units of Volt. For example, calling the function as add_voltage(lens=50) assigns a 50V value to the geometry elements part of the 'lens' physical group. Alternatively, the value can be a function, which takes x, y, z coordinates as argument and returns the voltage at that position. Note that in 2D symmetries (such as radial symmetry) the z value for this function will always be zero.
def get_electrostatic_active_elements(self)

Get elements in the mesh that have an electrostatic excitation applied to them.

Returns

A tuple of two elements: (points, names). points is a Numpy array of shape (N, 4, 3) in the case of 2D and (N, 3, 3) in the case of 3D. This array contains the vertices of the line elements or the triangles. Multiple points per line elements are used in the case of 2D since higher order BEM is employed, in which the true position on the line element is given by a polynomial interpolation of the points. names is a dictionary, the keys being the names of the physical groups mentioned by this excitation, while the values are Numpy arrays of indices that can be used to index the points array.

def get_magnetostatic_active_elements(self)

Get elements in the mesh that have an magnetostatic excitation applied to them. This does not include current excitation, as these are not part of the matrix.

Returns

A tuple of two elements: (points, names). points is a Numpy array of shape (N, 4, 3) in the case of 2D and (N, 3, 3) in the case of 3D. This array contains the vertices of the line elements or the triangles. Multiple points per line elements are used in the case of 2D since higher order BEM is employed, in which the true position on the line element is given by a polynomial interpolation of the points. names is a dictionary, the keys being the names of the physical groups mentioned by this excitation, while the values are Numpy arrays of indices that can be used to index the points array.

def has_current(self)

Check whether a current is applied in this excitation.

def is_electrostatic(self)

Check whether the excitation contains electrostatic fields.

def is_magnetostatic(self)

Check whether the excitation contains magnetostatic fields.

class ExcitationType (value, names=None, *, module=None, qualname=None, type=None, start=1)

Possible excitation that can be applied to elements of the geometry. See the methods of Excitation for documentation.

Expand source code
class ExcitationType(IntEnum):
    """Possible excitation that can be applied to elements of the geometry. See the methods of `Excitation` for documentation."""
    VOLTAGE_FIXED = 1
    VOLTAGE_FUN = 2
    DIELECTRIC = 3
     
    CURRENT = 4
    MAGNETOSTATIC_POT = 5
    MAGNETIZABLE = 6
     
    def is_electrostatic(self):
        return self in [ExcitationType.VOLTAGE_FIXED,
                        ExcitationType.VOLTAGE_FUN,
                        ExcitationType.DIELECTRIC]

    def is_magnetostatic(self):
        return self in [ExcitationType.MAGNETOSTATIC_POT,
                        ExcitationType.MAGNETIZABLE,
                        ExcitationType.CURRENT]
     
    def __str__(self):
        if self == ExcitationType.VOLTAGE_FIXED:
            return 'voltage fixed'
        elif self == ExcitationType.VOLTAGE_FUN:
            return 'voltage function'
        elif self == ExcitationType.DIELECTRIC:
            return 'dielectric'
        elif self == ExcitationType.CURRENT:
            return 'current'
        elif self == ExcitationType.MAGNETOSTATIC_POT:
            return 'magnetostatic potential'
        elif self == ExcitationType.MAGNETIZABLE:
            return 'magnetizable'
         
        raise RuntimeError('ExcitationType not understood in __str__ method')

Ancestors

  • enum.IntEnum
  • builtins.int
  • enum.Enum

Class variables

var CURRENT
var DIELECTRIC
var MAGNETIZABLE
var MAGNETOSTATIC_POT
var VOLTAGE_FIXED
var VOLTAGE_FUN

Methods

def is_electrostatic(self)
def is_magnetostatic(self)
class Symmetry (value, names=None, *, module=None, qualname=None, type=None, start=1)

Symmetry to be used for solver. Used when deciding which formulas to use in the Boundary Element Method. The currently supported symmetries are radial symmetry (also called cylindrical symmetry) and general 3D geometries.

Expand source code
class Symmetry(IntEnum):
    """Symmetry to be used for solver. Used when deciding which formulas to use in the Boundary Element Method. The currently
    supported symmetries are radial symmetry (also called cylindrical symmetry) and general 3D geometries.
    """
    RADIAL = 0
    THREE_D = 2
    
    def __str__(self):
        if self == Symmetry.RADIAL:
            return 'radial'
        elif self == Symmetry.THREE_D:
            return '3d' 
    
    def is_2d(self):
        return self == Symmetry.RADIAL
        
    def is_3d(self):
        return self == Symmetry.THREE_D

Ancestors

  • enum.IntEnum
  • builtins.int
  • enum.Enum

Class variables

var RADIAL
var THREE_D

Methods

def is_2d(self)
def is_3d(self)