gvec.util#

GVEC utility module

This module is part of the gvec python package, but also used directly in the tests.

class gvec.util.CaseInsensitiveDict(data=(), /, **kwargs)#

Bases: MutableMapping

A dictionary-like Mutable Mapping where string keys are case-insensitive.

Implements all methods and operations of MutableMapping as well as dict’s copy. Also provides lower_items and lower_keys.

Keys that are not strings will be stored as-is. The structure remembers the case of the last used key, and iter(instance), keys(), items(), iterkeys(), and iteritems() will contain case-sensitive keys. However, querying and contains testing is case insensitive.

Raises:

ValueError – If the constructor, .update, or equality comparison operations are given keys that have equal .lower() representations. This is to avoid ambiguity in lookups and ensure consistent behavior.

Examples

>>> cid = CaseInsensitiveDict()
>>> cid['param'] = 'value'
>>> cid['Param'] == 'value'
True
copy()#

Return a deep copy.

lower_items()#
lower_keys()#
serialize()#

Recursively serialize this object, converting Mappings to dicts and Iterables to lists.

update([E, ]**F) None.  Update D from mapping/iterable E and F.#

If E present and has a .keys() method, does: for k in E: D[k] = E[k] If E present and lacks .keys() method, does: for (k, v) in E: D[k] = v In either case, this is followed by: for k, v in F.items(): D[k] = v

gvec.util.adapt_parameter_file(source: str | Path, target: str | Path, **kwargs)#

Copy the source file to the target file and replace the parameters according to kwargs.

Parameters:
  • source (str or Path) – The path to the source parameter file.

  • target (str or Path) – The path to the target parameter file.

  • **kwargs – Keyword arguments representing the parameters to be replaced. if the value of the key is “!”, the line with the keyword is uncommented, if possible

Raises:

AssertionError – If the number of occurrences for any parameter is not exactly 1.

Notes

  • If no parameters are provided in kwargs, the function simply copies the source file to the target file.

  • The function replaces the parameters in the format key = value, where value is either a sequence of characters containing no whitespace or a single pair of parentheses with any content. The value from kwargs is inserted using the standard python string conversion. There may be a comment, starting with !, after the value.

  • If a parameter already exists in the source file, its value is replaced with the corresponding value from kwargs.

  • If a parameter does not exist in the source file, it is added to the target file.

  • If the value of the key starts with “!”, the line with the keyword is just uncommented. (i.e. "!key=2.5" -> "key=2.5") If no line with the keyword is found, the key is added with the value, excluding the leading "!" (i.e. value is "!0.5" -> "key=0.5" is added)

Example

adapt_parameter_file('/path/to/source.ini', '/path/to/target.ini', param1=1.2, param2="(1, 2, 3)")

gvec.util.axis_from_boundary(parameters: MutableMapping) MutableMapping#
gvec.util.boundary_generator(case: str, X1_00=1.0, a0=0.5, ellipticity=0.4, helix_r=0.5)#

Define parameters for some simple boundaries for testing.

Parameters:
  • case (str) –

    The name of the boundary:

    see boundary_generator_cases dictionary

  • X1_00 (float, optional) – =major radius if \(X^1=R\)

  • a0 (float, optional) – =minor radius scale

  • ellipticity (float, optional) – =ellipticity of the cross section

Return type:

parameter dictionary describing

gvec.util.boundary_generator_cases()#
gvec.util.bspl2gvec(name: Literal['iota', 'pres'], bspl: BSpline | None = None, knots: _Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | bool | int | float | complex | str | bytes | _NestedSequence[bool | int | float | complex | str | bytes] | None = None, coefs: _Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | bool | int | float | complex | str | bytes | _NestedSequence[bool | int | float | complex | str | bytes] | None = None, params: dict = {}) dict#

Translates a scipy B-spline object or B-spline coefficients and knots for either a iota or pressure profile into a dictionary entries that can be handed to adapt_parameter_file.

Parameters:
  • name (str) – profile identifyer, has to be either iota or pres.

  • bspl (scipy.interpolate.BSpline) – scipy BSpline object. If this is not provided knots and coefs are expected.

  • knots (ArrayLike) – Knots for the B-splines. Note that repeated edge knots according to the degree are expected.

  • coefs (ArrayLike) – Coefficients for the B-splines.

  • params (dict, optional) – Dictionary of gvec input parameters that will be adapted. Defaults to {}.

Raises:
  • ValueError – If name is neither iota nor pres.

  • TypeError – If neither bspl nor knots and coefs is provided.

Returns:

Dictionary of gvec input parameters

Return type:

dict

gvec.util.chdir(target: Path | str)#

Contextmanager to change the current working directory.

Using a context has the benefit of automatically changing back to the original directory when the context is exited, even if an exception is raised.

gvec.util.check_boundary_direction(parameters: Mapping) bool#

Determine whether the boundary is described by right-handed logical coordinates (θ,ζ).

GVEC requires a right-handed logical coordinate system (ρ,θ,ζ). The logical coordinate system of the poloidal plane, (ρ,θ) is also required to be right-handed, which requires the poloidal angle to increase in the counter-clockwise direction. As a consequence the toroidal angle has to increase in the clockwise direction when viewed from above. This is ensured in the definition of the h-maps.

Returns:

True if (ρ,θ) is right-handed / θ increases counter-clockwise, False otherwise.

Return type:

bool

gvec.util.compute_FD(f: ndarray, pos, coefs, axis=0)#

1D Finite difference of a function f on equispaced n-dimnesional grid, using FD coefficients coefficients coefs and relative integer positions to the central evaluation point pos, along one given axis.

Warning

  • if data is periodic, meaning that endpoints of periodic interval are excluded, result can be on all points.

  • If data is not periodic, the result at the boundaries is WRONG, for the points |min(pos)| on the left and max(pos) on the right along the given axis.

Parameters:
  • f (numpy.ndarray) – function values on equispaced n-dimnesional grid

  • pos (int) – relative integer positions to the central evaluation point, as 1d list or 1d array of integers

  • coefs (float) – FD coefficients for each position, as 1d list or 1d array, same size as pos!

  • axis (int, optional) – axis along which the FD is computed, default is 0

Returns:

df – Finite-Difference result, same shape as f (see warning above!)

Return type:

numpy.ndarray

Examples

  • examples for first derivative of f:
    • 1st order forward FD: pos=[1,0]; coefs=[-1,1]/(dx)

    • 2nd order central FD: pos=[-1,1]; coefs=[-1,1]/(2*dx)

    • 4th order central FD: pos=[-2,-1,1,2]; coefs=[1/12,-2/3,2/3,-1/12]/(dx)

    • 6th order central FD: pos=[-3,-2,-1,1,2,3]; coefs=[-1/60,3/20,-3/4,3/4,-3/20,1/60]/(dx)

    • 8th order central FD: pos=[-4,-3,-2,-1,1,2,3,4]; coefs=[1/280,-4/105,1/5,-4/5,4/5,-1/5,4/105,-1/280]/(dx)

  • examples for second derivatives of f:
    • 2nd order central FD: pos=[-1,0,1]; coefs=[1,-2,1]/(dx**2)

    • 4th order central FD: pos=[-2,-1,1,2]; coefs=[-1/12, 4/3,-5/2, 4/3,-1/12]/(dx**2)

    • 6th order central FD: pos=[-3,-2,-1,1,2,3]; coefs=[1/90,-3/20,3/2,-49/18,3/2,-3/20,1/90]/(dx**2)

    • 8th order central FD: pos=[-4,-3,-2,-1,1,2,3,4]; coefs=[-1/560,8/315,-1/5,8/5,-205/72,8/5,-1/5,8/315,-1/560]/(dx**2)

gvec.util.compute_boundary_perturbation(base_parameters: Mapping, perturbed_parameters: Mapping) tuple[CaseInsensitiveDict, CaseInsensitiveDict]#

Computes the difference between the perturbed and base boundary parameters as a perturbation.

gvec.util.effective_minor_radius(parameters: Mapping, resolution: tuple[int, int] = (1000, 100))#
gvec.util.ellipse_circumference_factor(epsilon: float) float#

Compute the circumference factor of an ellipse with elongation epsilon. This uses the approximation by Ramanujan, accurate up to h^5 (6.5% error at ε=10).

A = a b π = aeff^2 π ε = a / b >= 1 C = 2 π aeff Cf(ε) Cf ~ (1 + ε) / (2 √ε) [1 + 3 h / (10 + √(4 - 3 h))] h = (ε - 1)^2 / (ε + 1)^2

gvec.util.evaluate_axis(zeta: ndarray, parameters: Mapping) tuple[ndarray, ndarray]#

Evaluate the magnetic axis at the given zeta points.

Parameters:
  • zeta (1D np.ndarray) – The toroidal angles at which to evaluate the axis.

  • parameters (Mapping) – The parameters defining the axis.

Returns:

The (X^1, X^2) coordinates of the axis at the given zeta points.

Return type:

tuple[1D np.ndarray, 1D np.ndarray]

gvec.util.evaluate_boundary(theta: ndarray, zeta: ndarray, parameters: Mapping) tuple[ndarray, ndarray]#

Evaluate the boundary at the given (theta, zeta) points.

Parameters:
  • theta (1D np.ndarray) – The poloidal angles at which to evaluate the boundary.

  • zeta (1D np.ndarray) – The toroidal angles at which to evaluate the boundary.

  • parameters (Mapping) – The parameters defining the boundary.

Returns:

The (X^1, X^2) coordinates of the boundary at the given (theta, zeta) points.

Return type:

tuple[2D np.ndarray, 2D np.ndarray]

gvec.util.flatten_parameters(parameters: Mapping) CaseInsensitiveDict#

Flatten parameters from a hierarchical dictionary

gvec.util.flip_boundary_theta(parameters: MutableMapping) MutableMapping#

Flip the boundary parameters in the poloidal direction. θ → -θ.

gvec.util.flip_boundary_zeta(parameters: MutableMapping) MutableMapping#
gvec.util.flip_parameters_theta(parameters: MutableMapping) MutableMapping#
gvec.util.flip_parameters_zeta(parameters: MutableMapping) MutableMapping#
gvec.util.get_compile_options() str#
gvec.util.linking_number(curve_a: ndarray, curve_b: ndarray, tol=1e-15, endpoint=False)#

Compute the linking number of two non-intersecting curves \(C_a(\zeta_a),C_b(\zeta_b)\), solving the (non-singular) double integral over two curves

\[ \text{Lk} = \frac{1}{4\pi}\int_0^{2\pi} \int_0^{2\pi} \frac{ r_b - r_a}{\abs{r_b - r_a}^3} \cdot \left(\frac{\partial r_a}{\partial\zeta_a} \times \frac{\partial r_b}{\partial\zeta_b}\right) d{\zeta_a} d{\zeta_b} \]

Here, the double integral is approximated by representing the curves as polygons, and computing the linking number as the sum of solid angles between all linear segments of polygon curves. The solid angle of two linear segments can be computed exactly. See paper by [Klenin and Langowski](https://onlinelibrary.wiley.com/doi/10.1002/1097-0282(20001015)54:5%3C307::AID-BIP20%3E3.0.CO;2-Y) See GVEC documentation for more details.

Parameters:
  • curve_a (np.ndarray) – the x,y,z point positions of the first curve. First and last point must coincide, if endpoint=True. shape must be [npoints_a,3]

  • curve_b (np.ndarray) – the x,y,z point positions of the first curve. First and last point must coincide, if endpoint=True. shape must be [npoints_b,3]

  • tol (float) – tolerance to consider points coincident (default: 1e-15)

  • endpoint (bool) – True: the first and last point of each curve coincide, else the last point is connected to the first point to close the curve. Default is False.

Returns:

Lk – the linking number of the two curves

Return type:

float

gvec.util.logging_setup()#

Setup default logging configuration for GVEC.

gvec.util.parameters_from_vmec(nml: Mapping, name: str) CaseInsensitiveDict#
gvec.util.read_parameter_file_ini(path: str | Path) CaseInsensitiveDict#

Read the parameters from the specified parameter file in GVEC-ini format.

Parameters:

path (str | Path) – The path to the parameter file.

Returns:

A mapping (with case insensitive keys) containing the parameters from the parameter file.

Return type:

CaseInsensitiveDict

Example: >>> read_parameter_file_ini(‘/path/to/parameter.ini’) {‘param1’: 1.2, ‘param2’: (1, 2, 3), ‘param3’: {(-1, 0): 0.5, (0, 0): 1.0}}

gvec.util.read_parameters(path: Path | str, format: Literal['ini', 'yaml', 'toml'] | None = None) CaseInsensitiveDict#
gvec.util.shift_boundary_theta_pi(parameters: MutableMapping) MutableMapping#

Shift the theta origin of the boundary by pi.

cos(m(θ+π)-nζ) = (-1)^m cos(mθ-nζ) sin(m(θ+π)-nζ) = (-1)^m sin(mθ-nζ)

gvec.util.signed_cross_sectional_area(parameters: Mapping, zeta: float, resolution: int = 1000) float#
gvec.util.solid_angle_between_segments(r1: ndarray, r2: ndarray, r3: ndarray, r4: ndarray, tol=1e-15)#

Compute the solid angle between two line segments r1->r2 and r3->r4. See paper by [Klenin and Langowski](https://onlinelibrary.wiley.com/doi/10.1002/1097-0282(20001015)54:5%3C307::AID-BIP20%3E3.0.CO;2-Y) and direct summation from https://doi.org/10.1145/3450626.3459778 r1,r2,r3,r4 must have the same shape, with last dimension of size 3 (x,y,z) :param r1: last dimension is x,y,z position of the first point of the first segment :type r1: np.ndarray :param r2: last dimension is x,y,z position of the second point of the first segment :type r2: np.ndarray :param r3: last dimension is x,y,z position of the first point of the second segment :type r3: np.ndarray :param r4: last dimension is x,y,z position of the second point of the second segment :type r4: np.ndarray :param tol: tolerance to consider points coincident (default: 1e-15) :type tol: float

Returns:

solid angle between the two segments, without 1/(4pi) factor

Return type:

omega (float)

gvec.util.stack_parameters(parameters: Mapping) CaseInsensitiveDict#

Stack parameters into a hierarchical dictionary

gvec.util.stringify_mn_parameters(parameters: Mapping) CaseInsensitiveDict#

Serialize parameters into a string

gvec.util.unstringify_mn_parameters(parameters: Mapping) CaseInsensitiveDict#

Deserialize parameters from a string

gvec.util.version_info() str#
gvec.util.write_parameter_file_ini(parameters: Mapping, path: str | Path = 'parameter.ini', header: str = '')#

Write the parameters to the specified parameter file in GVEC-ini format.

Parameters:
  • parameters – A mapping containing the parameters to be written to the parameter file.

  • path – The path to the parameter file.

gvec.util.write_parameters(parameters: Mapping, path: Path | str = 'parameter.ini', format: Literal['ini', 'yaml', 'toml'] | None = None)#
gvec.util.writhe_from_polygon(curve: ndarray, endpoint=False)#

Compute the writhe of a closed curve \(C(\zeta)\), solving the (singular!) double integral over the curve

\[ \text{Wr} = \frac{1}{4\pi}\int_0^{2\pi} \int_0^{2\pi} \frac{ r(\zeta^\prime) - r(\zeta)}{\abs{r(\zeta^\prime) - r(\zeta)}^3} \cdot \left(\frac{\partial r}{\partial\zeta} \times \frac{\partial r}{\partial\zeta^\prime} \right) d{\zeta} d{\zeta^\prime} \]

Here, the double integral is approximated by representing the curve as a polygon, and computing the writhe as the sum of solid angles between all linear segments of polygon curve. The solid angle of two linear segments can be computed exactly. See paper by [Klenin and Langowski](https://onlinelibrary.wiley.com/doi/10.1002/1097-0282(20001015)54:5%3C307::AID-BIP20%3E3.0.CO;2-Y) See GVEC documentation for more details.

Parameters:
  • curve (np.ndarray) – the x,y,z point positions of the curve. First and last point must coincide, if endpoint=True. shape must be [npoints,3]

  • endpoint (bool) – True: the first and last point of the curve coincide, else the last point is connected to the first point to close the curve. Default is False

Returns:

Wr – the approximate writhe of the curve

Return type:

float

Warning

The algorithm converges very slowly with the number of line segments.