diff --git a/CHANGELOG.md b/CHANGELOG.md index 79501a0f8e..b1ddf6c441 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ New Features - Adds batching feature to singular integrals. - ``desc.objectives.CoilSetMinDistance`` and ``desc.objectives.PlasmaCoilSetMinDistance`` now include the option to use a softmin which can give smoother gradients. They also both now have a ``dist_chunk_size`` option to break up the distance calculation into smaller pieces to save memory - Adds a new function ``desc.coils.initialize_helical_coils`` for creating an initial guess for stage 2 helical coil optimization. +- Adds ``chunk_size`` option to compute magnetic field methods to increase performance. - Adds ``desc.vmec_utils.make_boozmn_output `` for writing boozmn.nc style output files for compatibility with other codes which expect such files from the Booz_Xform code. - Renames compute quantity ``sqrt(g)_B`` to ``sqrt(g)_Boozer_DESC`` to more accurately reflect what the quantiy is (the jacobian from (rho,theta_B,zeta_B) to (rho,theta,zeta)), and adds a new function to compute ``sqrt(g)_Boozer`` which is the jacobian from (rho,theta_B,zeta_B) to (R,phi,Z). diff --git a/README.rst b/README.rst index 24e56055ad..ab0dae8850 100644 --- a/README.rst +++ b/README.rst @@ -97,7 +97,7 @@ Contribute - `Contributing guidelines `_ - `Issue Tracker `_ - `Source Code `_ -- `Documentation `_ +- `Documentation `_ .. |License| image:: https://img.shields.io/github/license/PlasmaControl/desc?color=blue&logo=open-source-initiative&logoColor=white :target: https://github.com/PlasmaControl/DESC/blob/master/LICENSE diff --git a/desc/batching.py b/desc/batching.py index 071a62505e..4b9b4964a9 100644 --- a/desc/batching.py +++ b/desc/batching.py @@ -4,8 +4,7 @@ ---------- The ``_batch_and_remainder``, ``_evaluate_in_chunks``, ``jacrev_chunked``, and ``jacfwd_chunked`` functions are adapted from JAX. -https://github.com/jax-ml/jax/blob/main/jax/_src/lax/control_flow/loops.py#L2139. -https://github.com/jax-ml/jax/blob/main/jax/_src/lax/control_flow/loops.py#L2209. +https://github.com/jax-ml/jax/blob/main/jax/_src/lax/control_flow/loops.py. https://github.com/jax-ml/jax/blob/main/jax/_src/api.py. The original copyright notice is as follows @@ -42,7 +41,7 @@ tree_transpose, ) -from desc.backend import jax, jnp, scan +from desc.backend import jax, jnp, scan, vmap from desc.utils import errorif if jax.__version_info__ >= (0, 4, 16): @@ -191,7 +190,7 @@ def vmap_chunked( reduction=None, chunk_reduction=_identity, ): - """Behaves like jax.vmap but uses scan to chunk the computations in smaller chunks. + """Behaves like ``vmap`` but uses scan to chunk the computations in smaller chunks. Parameters ---------- @@ -217,10 +216,7 @@ def vmap_chunked( """ in_axes, argnums = _parse_in_axes(in_axes) - if isinstance(argnums, int): - argnums = (argnums,) - - f = jax.vmap(f, in_axes=in_axes) + f = vmap(f, in_axes=in_axes) if chunk_size is None: return lambda *args, **kwargs: chunk_reduction(f(*args, **kwargs)) return partial( @@ -244,12 +240,11 @@ def batch_map( ): """Compute ``chunk_reduction(fun(fun_input))`` in batches. - This utility is like ``desc.backend.imap(fun,fun_input,batch_size)`` except that - ``fun`` is assumed to be vectorized natively. No JAX vectorization such as ``vmap`` - is applied to the supplied function. This makes compilation faster and avoids the - weaknesses of applying JAX vectorization, such as executing all branches of code - conditioned on dynamic values. For example, this function would be useful for - GitHub issue #1303. + This utility is like ``vmap_chunked`` except that ``fun`` is assumed to be + vectorized natively. No JAX vectorization such as ``vmap`` is applied to the + supplied function. This makes compilation faster and avoids the weaknesses of + applying JAX vectorization, such as executing all branches of code conditioned on + dynamic values. For example, this function would be useful for GitHub issue #1303. Parameters ---------- @@ -265,7 +260,8 @@ def batch_map( Should take two arguments and return one output, e.g. ``jnp.add``. chunk_reduction : callable Chunk-wise reduction operation. - Should apply ``reduction`` along the mapped axis, e.g. ``jnp.add.reduce``. + Should typically apply ``reduction`` along the mapped axis, + e.g. ``jnp.add.reduce``. Returns ------- diff --git a/desc/coils.py b/desc/coils.py index 7f7943b5fd..4b650b6dfd 100644 --- a/desc/coils.py +++ b/desc/coils.py @@ -1,23 +1,14 @@ """Classes for magnetic field coils.""" -import functools import numbers from abc import ABC from collections.abc import MutableSequence +from functools import partial import numpy as np from scipy.constants import mu_0 -from desc.backend import ( - fori_loop, - jit, - jnp, - scan, - tree_leaves, - tree_stack, - tree_unstack, - vmap, -) +from desc.backend import jit, jnp, scan, tree_leaves, tree_stack, tree_unstack, vmap from desc.compute import get_params, rpz2xyz, rpz2xyz_vec, xyz2rpz, xyz2rpz_vec from desc.compute.geom_utils import reflection_matrix from desc.compute.utils import _compute as compute_fun @@ -28,19 +19,29 @@ SplineXYZCurve, ) from desc.grid import Grid, LinearGrid +from desc.integrals.quad_utils import nfp_loop from desc.magnetic_fields import _MagneticField +from desc.magnetic_fields._core import ( + biot_savart_general, + biot_savart_general_vector_potential, +) from desc.optimizable import Optimizable, OptimizableCollection, optimizable_parameter from desc.utils import cross, dot, equals, errorif, flatten_list, safenorm, warnif -@jit -def biot_savart_hh(eval_pts, coil_pts_start, coil_pts_end, current): +@partial(jit, static_argnames=["chunk_size"]) +def biot_savart_hh(eval_pts, coil_pts_start, coil_pts_end, current, *, chunk_size=None): """Biot-Savart law for filamentary coils following [1]. The coil is approximated by a series of straight line segments and an analytic expression is used to evaluate the field from each segment. + References + ---------- + [1] Hanson & Hirshman, "Compact expressions for the Biot-Savart + fields of a filamentary segment" (2002) + Parameters ---------- eval_pts : array-like shape(n,3) @@ -51,14 +52,16 @@ def biot_savart_hh(eval_pts, coil_pts_start, coil_pts_end, current): though this is not checked. current : float Current through the coil (in Amps). + chunk_size : int or None + Size to split computation into chunks of evaluation points. + If no chunking should be done or the chunk size is the full input + then supply ``None``. Default is ``None``. Returns ------- B : ndarray, shape(n,3) magnetic field in cartesian components at specified points - [1] Hanson & Hirshman, "Compact expressions for the Biot-Savart - fields of a filamentary segment" (2002) """ d_vec = coil_pts_end - coil_pts_start L = jnp.linalg.norm(d_vec, axis=-1) @@ -84,14 +87,21 @@ def biot_savart_hh(eval_pts, coil_pts_start, coil_pts_end, current): return B -@jit -def biot_savart_vector_potential_hh(eval_pts, coil_pts_start, coil_pts_end, current): +@partial(jit, static_argnames=["chunk_size"]) +def biot_savart_vector_potential_hh( + eval_pts, coil_pts_start, coil_pts_end, current, *, chunk_size=None +): """Biot-Savart law for vector potential for filamentary coils following [1]. The coil is approximated by a series of straight line segments and an analytic expression is used to evaluate the vector potential from each segment. This expression assumes the Coulomb gauge. + References + ---------- + [1] Hanson & Hirshman, "Compact expressions for the Biot-Savart + fields of a filamentary segment" (2002) + Parameters ---------- eval_pts : array-like shape(n,3) @@ -102,14 +112,16 @@ def biot_savart_vector_potential_hh(eval_pts, coil_pts_start, coil_pts_end, curr though this is not checked. current : float Current through the coil (in Amps). + chunk_size : int or None + Size to split computation into chunks of evaluation points. + If no chunking should be done or the chunk size is the full input + then supply ``None``. Default is ``None``. Returns ------- A : ndarray, shape(n,3) Magnetic vector potential in cartesian components at specified points - [1] Hanson & Hirshman, "Compact expressions for the Biot-Savart - fields of a filamentary segment" (2002) """ d_vec = coil_pts_end - coil_pts_start L = jnp.linalg.norm(d_vec, axis=-1) @@ -131,79 +143,87 @@ def biot_savart_vector_potential_hh(eval_pts, coil_pts_start, coil_pts_end, curr return A -@jit -def biot_savart_quad(eval_pts, coil_pts, tangents, current): +@partial(jit, static_argnames=["chunk_size"]) +def biot_savart_quad(eval_pts, coil_pts, tangents, current, *, chunk_size=None): """Biot-Savart law for filamentary coil using numerical quadrature. + Notes + ----- + This method does not give curl(B) == 0 exactly. The error in the curl + scales the same as the error in B itself, so will only be zero when fully + converged. However in practice, for smooth curves described by Fourier series, + this method converges exponentially in the number of coil points. + Parameters ---------- - eval_pts : array-like shape(n,3) - Evaluation points in cartesian coordinates - coil_pts : array-like shape(m,3) - Points in cartesian space defining coil - tangents : array-like, shape(m,3) + eval_pts : array-like + Shape (n, 3). + Evaluation points in cartesian coordinates. + coil_pts : array-like + Shape (m, 3). + Points in cartesian space defining coil. + tangents : array-like + Shape (m, 3). Tangent vectors to the coil at coil_pts. If the curve is given by x(s) with curve parameter s, coil_pts = x, tangents = dx/ds*ds where ds is the spacing between points. current : float Current through the coil (in Amps). + chunk_size : int or None + Size to split computation into chunks of evaluation points. + If no chunking should be done or the chunk size is the full input + then supply ``None``. Default is ``None``. Returns ------- - B : ndarray, shape(n,3) - magnetic field in cartesian components at specified points + B : ndarray + Shape(n, 3). + Magnetic field in cartesian components at specified points. - Notes - ----- - This method does not give curl(B) == 0 exactly. The error in the curl - scales the same as the error in B itself, so will only be zero when fully - converged. However in practice, for smooth curves described by Fourier series, - this method converges exponentially in the number of coil points. """ - dl = tangents - R_vec = eval_pts[jnp.newaxis, :] - coil_pts[:, jnp.newaxis, :] - R_mag = jnp.linalg.norm(R_vec, axis=-1) - - vec = jnp.cross(dl[:, jnp.newaxis, :], R_vec, axis=-1) - denom = R_mag**3 - - B = jnp.sum(mu_0 / (4 * jnp.pi) * current * vec / denom[:, :, None], axis=0) - return B + return biot_savart_general( + eval_pts, coil_pts, current * tangents, chunk_size=chunk_size + ) -@jit -def biot_savart_vector_potential_quad(eval_pts, coil_pts, tangents, current): +@partial(jit, static_argnames=["chunk_size"]) +def biot_savart_vector_potential_quad( + eval_pts, coil_pts, tangents, current, *, chunk_size=None +): """Biot-Savart law (for A) for filamentary coil using numerical quadrature. This expression assumes the Coulomb gauge. Parameters ---------- - eval_pts : array-like shape(n,3) - Evaluation points in cartesian coordinates - coil_pts : array-like shape(m,3) - Points in cartesian space defining coil - tangents : array-like, shape(m,3) + eval_pts : array-like + Shape (n, 3). + Evaluation points in cartesian coordinates. + coil_pts : array-like + Shape (m, 3). + Points in cartesian space defining coil. + tangents : array-like + Shape (m, 3). Tangent vectors to the coil at coil_pts. If the curve is given by x(s) with curve parameter s, coil_pts = x, tangents = dx/ds*ds where ds is the spacing between points. current : float Current through the coil (in Amps). + chunk_size : int or None + Size to split computation into chunks of evaluation points. + If no chunking should be done or the chunk size is the full input + then supply ``None``. Default is ``None``. Returns ------- - A : ndarray, shape(n,3) + A : ndarray + Shape (n, 3). Magnetic vector potential in cartesian components at specified points. - """ - dl = tangents - R_vec = eval_pts[jnp.newaxis, :] - coil_pts[:, jnp.newaxis, :] - R_mag = jnp.linalg.norm(R_vec, axis=-1) - - vec = dl[:, jnp.newaxis, :] - denom = R_mag - A = jnp.sum(mu_0 / (4 * jnp.pi) * current * vec / denom[:, :, None], axis=0) - return A + """ + return biot_savart_general_vector_potential( + eval_pts, coil_pts, current * tangents, chunk_size=chunk_size + ) class _Coil(_MagneticField, Optimizable, ABC): @@ -296,6 +316,7 @@ def _compute_A_or_B( source_grid=None, transforms=None, compute_A_or_B="B", + chunk_size=None, ): """Compute magnetic field or vector potential at a set of points. @@ -318,7 +339,10 @@ def _compute_A_or_B( compute_A_or_B: {"A", "B"}, optional whether to compute the magnetic vector potential "A" or the magnetic field "B". Defaults to "B" - + chunk_size : int or None + Size to split computation into chunks of evaluation points. + If no chunking should be done or the chunk size is the full input + then supply ``None``. Default is ``None``. Returns ------- @@ -374,14 +398,26 @@ def _compute_A_or_B( data["x_s"] = rpz2xyz_vec(data["x_s"], phi=data["x"][:, 1]) data["x"] = rpz2xyz(data["x"]) - AB = op(coords, data["x"], data["x_s"] * data["ds"][:, None], current) + AB = op( + coords, + data["x"], + data["x_s"] * data["ds"][:, None], + current, + chunk_size=chunk_size, + ) if basis.lower() == "rpz": AB = xyz2rpz_vec(AB, phi=phi) return AB def compute_magnetic_field( - self, coords, params=None, basis="rpz", source_grid=None, transforms=None + self, + coords, + params=None, + basis="rpz", + source_grid=None, + transforms=None, + chunk_size=None, ): """Compute magnetic field at a set of points. @@ -401,6 +437,10 @@ def compute_magnetic_field( points. Should NOT include endpoint at 2pi. transforms : dict of Transform or array-like Transforms for R, Z, lambda, etc. Default is to build from grid. + chunk_size : int or None + Size to split computation into chunks of evaluation points. + If no chunking should be done or the chunk size is the full input + then supply ``None``. Default is ``None``. Returns @@ -416,10 +456,18 @@ def compute_magnetic_field( may not be zero if not fully converged. """ - return self._compute_A_or_B(coords, params, basis, source_grid, transforms, "B") + return self._compute_A_or_B( + coords, params, basis, source_grid, transforms, "B", chunk_size=chunk_size + ) def compute_magnetic_vector_potential( - self, coords, params=None, basis="rpz", source_grid=None, transforms=None + self, + coords, + params=None, + basis="rpz", + source_grid=None, + transforms=None, + chunk_size=None, ): """Compute magnetic vector potential at a set of points. @@ -439,6 +487,10 @@ def compute_magnetic_vector_potential( points. Should NOT include endpoint at 2pi. transforms : dict of Transform or array-like Transforms for R, Z, lambda, etc. Default is to build from grid. + chunk_size : int or None + Size to split computation into chunks of evaluation points. + If no chunking should be done or the chunk size is the full input + then supply ``None``. Default is ``None``. Returns ------- @@ -454,7 +506,9 @@ def compute_magnetic_vector_potential( may not be zero if not fully converged. """ - return self._compute_A_or_B(coords, params, basis, source_grid, transforms, "A") + return self._compute_A_or_B( + coords, params, basis, source_grid, transforms, "A", chunk_size=chunk_size + ) def __repr__(self): """Get the string form of the object.""" @@ -974,6 +1028,7 @@ def _compute_A_or_B( source_grid=None, transforms=None, compute_A_or_B="B", + chunk_size=None, ): """Compute magnetic field or vector potential at a set of points. @@ -996,6 +1051,10 @@ def _compute_A_or_B( compute_A_or_B: {"A", "B"}, optional whether to compute the magnetic vector potential "A" or the magnetic field "B". Defaults to "B" + chunk_size : int or None + Size to split computation into chunks of evaluation points. + If no chunking should be done or the chunk size is the full input + then supply ``None``. Default is ``None``. Returns ------- @@ -1037,14 +1096,20 @@ def _compute_A_or_B( # coils curvature which is a 2nd derivative of the position, and doing that # with only possibly c1 cubic splines is inaccurate, so we don't do it # (for now, maybe in the future?) - AB = op(coords, coil_pts_start, coil_pts_end, current) + AB = op(coords, coil_pts_start, coil_pts_end, current, chunk_size=chunk_size) if basis == "rpz": AB = xyz2rpz_vec(AB, x=coords[:, 0], y=coords[:, 1]) return AB def compute_magnetic_field( - self, coords, params=None, basis="rpz", source_grid=None, transforms=None + self, + coords, + params=None, + basis="rpz", + source_grid=None, + transforms=None, + chunk_size=None, ): """Compute magnetic field at a set of points. @@ -1064,6 +1129,10 @@ def compute_magnetic_field( points. Should NOT include endpoint at 2pi. transforms : dict of Transform or array-like Transforms for R, Z, lambda, etc. Default is to build from grid. + chunk_size : int or None + Size to split computation into chunks of evaluation points. + If no chunking should be done or the chunk size is the full input + then supply ``None``. Default is ``None``. Returns ------- @@ -1077,10 +1146,18 @@ def compute_magnetic_field( is approximately quadratic in the number of coil points. """ - return self._compute_A_or_B(coords, params, basis, source_grid, transforms, "B") + return self._compute_A_or_B( + coords, params, basis, source_grid, transforms, "B", chunk_size=chunk_size + ) def compute_magnetic_vector_potential( - self, coords, params=None, basis="rpz", source_grid=None, transforms=None + self, + coords, + params=None, + basis="rpz", + source_grid=None, + transforms=None, + chunk_size=None, ): """Compute magnetic vector potential at a set of points. @@ -1101,6 +1178,10 @@ def compute_magnetic_vector_potential( points. Should NOT include endpoint at 2pi. transforms : dict of Transform or array-like Transforms for R, Z, lambda, etc. Default is to build from grid. + chunk_size : int or None + Size to split computation into chunks of evaluation points. + If no chunking should be done or the chunk size is the full input + then supply ``None``. Default is ``None``. Returns ------- @@ -1115,7 +1196,9 @@ def compute_magnetic_vector_potential( Convergence is approximately quadratic in the number of coil points. """ - return self._compute_A_or_B(coords, params, basis, source_grid, transforms, "A") + return self._compute_A_or_B( + coords, params, basis, source_grid, transforms, "A", chunk_size=chunk_size + ) @classmethod def from_values( @@ -1332,7 +1415,7 @@ def compute( Returns ------- - data : list of dict of ndarray + data : list[dict[str, jnp.ndarray]] Computed quantity and intermediate variables, for each coil in the set. List entries map to coils in coilset, each dict contains data for an individual coil. @@ -1479,6 +1562,7 @@ def _compute_A_or_B( source_grid=None, transforms=None, compute_A_or_B="B", + chunk_size=None, ): """Compute magnetic field at a set of points. @@ -1498,6 +1582,10 @@ def _compute_A_or_B( compute_A_or_B: {"A", "B"}, optional whether to compute the magnetic vector potential "A" or the magnetic field "B". Defaults to "B" + chunk_size : int or None + Size to split computation into chunks of evaluation points. + If no chunking should be done or the chunk size is the full input + then supply ``None``. Default is ``None``. Returns ------- @@ -1546,17 +1634,23 @@ def _compute_A_or_B( }[compute_A_or_B] # sum the magnetic fields from each field period - def nfp_loop(k, AB): - coords_nfp = coords_rpz + jnp.array([0, 2 * jnp.pi * k / self.NFP, 0]) + def func(zeta_j): + coords_nfp = jnp.column_stack([coords_rpz[:, 0], zeta_j, coords_rpz[:, 2]]) def body(AB, x): - AB += op(coords_nfp, params=x, basis="rpz", source_grid=source_grid) + AB += op( + coords_nfp, + params=x, + basis="rpz", + source_grid=source_grid, + chunk_size=chunk_size, + ) return AB, None - AB += scan(body, jnp.zeros(coords_nfp.shape), tree_stack(params))[0] - return AB + return scan(body, jnp.zeros(coords_nfp.shape), tree_stack(params))[0] - AB = fori_loop(0, self.NFP, nfp_loop, jnp.zeros_like(coords_rpz)) + assert self.NFP == source_grid.NFP + AB = nfp_loop(source_grid, func, jnp.zeros_like(coords_rpz)) # sum the magnetic field/potential from both halves of # the symmetric field period @@ -1570,7 +1664,13 @@ def body(AB, x): return AB def compute_magnetic_field( - self, coords, params=None, basis="rpz", source_grid=None, transforms=None + self, + coords, + params=None, + basis="rpz", + source_grid=None, + transforms=None, + chunk_size=None, ): """Compute magnetic field at a set of points. @@ -1587,6 +1687,10 @@ def compute_magnetic_field( points. Should NOT include endpoint at 2pi. transforms : dict of Transform or array-like Transforms for R, Z, lambda, etc. Default is to build from grid. + chunk_size : int or None + Size to split computation into chunks of evaluation points. + If no chunking should be done or the chunk size is the full input + then supply ``None``. Default is ``None``. Returns ------- @@ -1594,10 +1698,18 @@ def compute_magnetic_field( Magnetic field at specified nodes, in [R,phi,Z] or [X,Y,Z] coordinates. """ - return self._compute_A_or_B(coords, params, basis, source_grid, transforms, "B") + return self._compute_A_or_B( + coords, params, basis, source_grid, transforms, "B", chunk_size=chunk_size + ) def compute_magnetic_vector_potential( - self, coords, params=None, basis="rpz", source_grid=None, transforms=None + self, + coords, + params=None, + basis="rpz", + source_grid=None, + transforms=None, + chunk_size=None, ): """Compute magnetic vector potential at a set of points. @@ -1614,6 +1726,10 @@ def compute_magnetic_vector_potential( points. Should NOT include endpoint at 2pi. transforms : dict of Transform or array-like Transforms for R, Z, lambda, etc. Default is to build from grid. + chunk_size : int or None + Size to split computation into chunks of evaluation points. + If no chunking should be done or the chunk size is the full input + then supply ``None``. Default is ``None``. Returns ------- @@ -1622,7 +1738,9 @@ def compute_magnetic_vector_potential( or xyz coordinates """ - return self._compute_A_or_B(coords, params, basis, source_grid, transforms, "A") + return self._compute_A_or_B( + coords, params, basis, source_grid, transforms, "A", chunk_size=chunk_size + ) @classmethod def linspaced_angular( @@ -2394,7 +2512,7 @@ def compute( Returns ------- - data : list of dict of ndarray + data : list[dict[str, jnp.ndarray]] Computed quantity and intermediate variables, for each coil in the set. List entries map to coils in coilset, each dict contains data for an individual coil. @@ -2462,6 +2580,7 @@ def _compute_A_or_B( source_grid=None, transforms=None, compute_A_or_B="B", + chunk_size=None, ): """Compute magnetic field or vector potential at a set of points. @@ -2483,6 +2602,10 @@ def _compute_A_or_B( compute_A_or_B: {"A", "B"}, optional whether to compute the magnetic vector potential "A" or the magnetic field "B". Defaults to "B" + chunk_size : int or None + Size to split computation into chunks of evaluation points. + If no chunking should be done or the chunk size is the full input + then supply ``None``. Default is ``None``. Returns ------- @@ -2504,17 +2627,23 @@ def _compute_A_or_B( if compute_A_or_B == "B": for coil, par, grd, tr in zip(self.coils, params, source_grid, transforms): AB += coil.compute_magnetic_field( - coords, par, basis, grd, transforms=tr + coords, par, basis, grd, transforms=tr, chunk_size=chunk_size ) elif compute_A_or_B == "A": for coil, par, grd, tr in zip(self.coils, params, source_grid, transforms): AB += coil.compute_magnetic_vector_potential( - coords, par, basis, grd, transforms=tr + coords, par, basis, grd, transforms=tr, chunk_size=chunk_size ) return AB def compute_magnetic_field( - self, coords, params=None, basis="rpz", source_grid=None, transforms=None + self, + coords, + params=None, + basis="rpz", + source_grid=None, + transforms=None, + chunk_size=None, ): """Compute magnetic field at a set of points. @@ -2533,6 +2662,10 @@ def compute_magnetic_field( If array-like, should be 1 value per coil. transforms : dict of Transform or array-like Transforms for R, Z, lambda, etc. Default is to build from grid. + chunk_size : int or None + Size to split computation into chunks of evaluation points. + If no chunking should be done or the chunk size is the full input + then supply ``None``. Default is ``None``. Returns ------- @@ -2540,10 +2673,18 @@ def compute_magnetic_field( magnetic field at specified points, in either rpz or xyz coordinates """ - return self._compute_A_or_B(coords, params, basis, source_grid, transforms, "B") + return self._compute_A_or_B( + coords, params, basis, source_grid, transforms, "B", chunk_size=chunk_size + ) def compute_magnetic_vector_potential( - self, coords, params=None, basis="rpz", source_grid=None, transforms=None + self, + coords, + params=None, + basis="rpz", + source_grid=None, + transforms=None, + chunk_size=None, ): """Compute magnetic vector potential at a set of points. @@ -2562,6 +2703,10 @@ def compute_magnetic_vector_potential( If array-like, should be 1 value per coil. transforms : dict of Transform or array-like Transforms for R, Z, lambda, etc. Default is to build from grid. + chunk_size : int or None + Size to split computation into chunks of evaluation points. + If no chunking should be done or the chunk size is the full input + then supply ``None``. Default is ``None``. Returns ------- @@ -2570,7 +2715,9 @@ def compute_magnetic_vector_potential( or xyz coordinates """ - return self._compute_A_or_B(coords, params, basis, source_grid, transforms, "A") + return self._compute_A_or_B( + coords, params, basis, source_grid, transforms, "A", chunk_size=chunk_size + ) def to_FourierPlanar( self, N=10, grid=None, basis="xyz", name="", check_intersection=True @@ -2910,7 +3057,7 @@ def flatten_coils(coilset): return cset -@functools.partial(jnp.vectorize, signature="(m,3),(n,3),(m,3),(n,3),(m),(n)->()") +@partial(jnp.vectorize, signature="(m,3),(n,3),(m,3),(n,3),(m),(n)->()") def _linking_number(x1, x2, x1_s, x2_s, dx1, dx2): """Linking number between curves x1 and x2 with tangents x1_s, x2_s.""" x1_s *= dx1[:, None] diff --git a/desc/compute/_surface.py b/desc/compute/_surface.py index 7ed57bf848..9c7ea39c1a 100644 --- a/desc/compute/_surface.py +++ b/desc/compute/_surface.py @@ -21,14 +21,14 @@ }, profiles=[], coordinates="tz", - data=[], + data=["zeta", "omega"], parameterization="desc.geometry.surface.FourierRZToroidalSurface", ) def _x_FourierRZToroidalSurface(params, transforms, profiles, data, **kwargs): R = transforms["R"].transform(params["R_lmn"]) Z = transforms["Z"].transform(params["Z_lmn"]) # TODO(#568): change when zeta no longer equals phi - phi = transforms["grid"].nodes[:, 2] + phi = data["zeta"] + data["omega"] coords = jnp.stack([R, phi, Z], axis=1) # default basis for "x" is rpz, the conversion will be done # in the main _compute function @@ -462,7 +462,7 @@ def _Phi_z_CurrentPotentialField(params, transforms, profiles, data, **kwargs): ], ) def _K_sup_theta_CurrentPotentialField(params, transforms, profiles, data, **kwargs): - data["K^theta"] = -data["Phi_z"] * (1 / data["|e_theta x e_zeta|"]) + data["K^theta"] = -data["Phi_z"] / data["|e_theta x e_zeta|"] return data @@ -484,7 +484,7 @@ def _K_sup_theta_CurrentPotentialField(params, transforms, profiles, data, **kwa ], ) def _K_sup_zeta_CurrentPotentialField(params, transforms, profiles, data, **kwargs): - data["K^zeta"] = data["Phi_t"] * (1 / data["|e_theta x e_zeta|"]) + data["K^zeta"] = data["Phi_t"] / data["|e_theta x e_zeta|"] return data @@ -507,9 +507,10 @@ def _K_sup_zeta_CurrentPotentialField(params, transforms, profiles, data, **kwar ], ) def _K_CurrentPotentialField(params, transforms, profiles, data, **kwargs): - data["K"] = (data["K^zeta"] * data["e_zeta"].T).T + ( - data["K^theta"] * data["e_theta"].T - ).T + data["K"] = ( + data["K^zeta"][:, jnp.newaxis] * data["e_zeta"] + + data["K^theta"][:, jnp.newaxis] * data["e_theta"] + ) return data diff --git a/desc/compute/utils.py b/desc/compute/utils.py index d92b4bc38c..68b87f9c60 100644 --- a/desc/compute/utils.py +++ b/desc/compute/utils.py @@ -61,7 +61,7 @@ def compute( # noqa: C901 Returns ------- - data : dict of ndarray + data : dict[str, jnp.ndarray] Computed quantity and intermediate variables. """ @@ -177,6 +177,12 @@ def _compute( using recursion to compute dependencies. If you want to call this function, you cannot give the argument basis='xyz' since that will break the recursion. In that case, either call above function or manually convert the output to xyz basis. + + Returns + ------- + data : dict[str, jnp.ndarray] + Computed quantity and intermediate variables. + """ assert kwargs.get("basis", "rpz") == "rpz", "_compute only works in rpz coordinates" parameterization = _parse_parameterization(parameterization) diff --git a/desc/equilibrium/coords.py b/desc/equilibrium/coords.py index 7ed6158a00..d58a4f60c9 100644 --- a/desc/equilibrium/coords.py +++ b/desc/equilibrium/coords.py @@ -4,7 +4,8 @@ import numpy as np -from desc.backend import fori_loop, jit, jnp, put, root, root_scalar, vmap +from desc.backend import jit, jnp, root, root_scalar, vmap +from desc.batching import batch_map from desc.compute import compute as compute_fun from desc.compute import data_index, get_data_deps, get_profiles, get_transforms from desc.grid import ConcentricGrid, Grid, LinearGrid, QuadratureGrid @@ -278,17 +279,13 @@ def _initial_guess_nn_search(coords, inbasis, eq, period, compute): # nearest neighbor search on dense grid yg = ConcentricGrid(eq.L_grid, eq.M_grid, max(eq.N_grid, eq.M_grid)).nodes xg = compute(yg, inbasis) - idx = jnp.zeros(len(coords)).astype(int) coords = jnp.asarray(coords) - def _distance_body(i, idx): - d = _fixup_residual(coords[i] - xg, period) - distance = safenorm(d, axis=-1) - k = jnp.argmin(distance) - idx = put(idx, i, k) - return idx + def _distance_body(coords): + distance = safenorm(_fixup_residual(coords - xg, period), axis=-1) + return jnp.argmin(distance, axis=-1) - idx = fori_loop(0, len(coords), _distance_body, idx) + idx = batch_map(_distance_body, coords[..., jnp.newaxis, :], 20) return yg[idx] diff --git a/desc/equilibrium/equilibrium.py b/desc/equilibrium/equilibrium.py index 37c8cb520f..f838c0269c 100644 --- a/desc/equilibrium/equilibrium.py +++ b/desc/equilibrium/equilibrium.py @@ -109,7 +109,8 @@ class Equilibrium(IOAble, Optimizable): anisotropy : Profile or ndarray Anisotropic pressure profile or array of mode numbers and spectral coefficients. Default is a PowerSeriesProfile with zero anisotropic pressure. - surface: Surface or ndarray shape(k,5) (optional) + surface: Surface or ndarray + Shape(k,5) (optional). Fixed boundary surface shape, as a Surface object or array of spectral mode numbers and coefficients of the form [l, m, n, R, Z]. Default is a FourierRZToroidalSurface with major radius 10 and minor radius 1 @@ -843,7 +844,7 @@ def compute( # noqa: C901 Returns ------- - data : dict of ndarray + data : dict[str, ndarray] Computed quantity and intermediate variables. """ diff --git a/desc/geometry/core.py b/desc/geometry/core.py index a389eb4441..b9c9d062db 100644 --- a/desc/geometry/core.py +++ b/desc/geometry/core.py @@ -111,7 +111,7 @@ def compute( Returns ------- - data : dict of ndarray + data : dict[str, jnp.ndarray] Computed quantity and intermediate variables. """ @@ -394,7 +394,7 @@ def N(self): @property def sym(self): - """bool: Whether or not the surface is stellarator symmetric.""" + """bool: Whether the surface is stellarator symmetric.""" return self._sym def _compute_orientation(self): @@ -467,7 +467,7 @@ def compute( Returns ------- - data : dict of ndarray + data : dict[str, jnp.ndarray] Computed quantity and intermediate variables. """ diff --git a/desc/integrals/__init__.py b/desc/integrals/__init__.py index e3d59d02ef..0afddb6e1d 100644 --- a/desc/integrals/__init__.py +++ b/desc/integrals/__init__.py @@ -1,5 +1,6 @@ """Classes for function integration.""" +from ._laplace import VacuumSolver from .bounce_integral import Bounce1D, Bounce2D from .singularities import ( DFTInterpolator, diff --git a/desc/integrals/_laplace.py b/desc/integrals/_laplace.py new file mode 100644 index 0000000000..0b1341f316 --- /dev/null +++ b/desc/integrals/_laplace.py @@ -0,0 +1,401 @@ +"""High order accurate vacuum field solver.""" + +from functools import partial + +from matplotlib import pyplot as plt + +from desc.backend import jit, jnp +from desc.basis import DoubleFourierSeries +from desc.batching import vmap_chunked +from desc.grid import LinearGrid +from desc.integrals.quad_utils import eta_zero +from desc.integrals.singularities import ( + _kernel_biot_savart_coulomb, + _kernel_Bn_over_r, + _kernel_Phi_dGp_dn, + _nonsingular_part, + get_interpolator, + singular_integral, +) +from desc.io import IOAble +from desc.transform import Transform +from desc.utils import errorif, setdefault + + +@partial(vmap_chunked, in_axes=(None, 0, None), chunk_size=None) +def _green(self, Phi_mn, chunk_size): + """Compute green(Φₘₙ). + + green is linear map of Φₘₙ. + Finite-dimensional approximation gives green(Φₘₙ) = A @ Φₘₙ. + """ + src_data = self._data["src"].copy() + src_data["Phi"] = self._transform["src"].transform(Phi_mn) + Phi = ( + src_data["Phi"] + if self._same_grid_phi_src + else self._transform["Phi"].transform(Phi_mn) + ) + # TODO: Can generalize this to be one singular integral without interpolation. + return Phi + singular_integral( + self._data["Phi"], + src_data, + _kernel_Phi_dGp_dn, + self._interpolator["Phi"], + chunk_size, + ).squeeze() / (2 * jnp.pi) + + +@partial(jit, static_argnames=["chunk_size"]) +def _compute_Phi_mn(self, *, chunk_size=None): + if "Phi_mn" not in self._data["Phi"]: + num_modes = self._transform["Phi"].num_modes + num_nodes = self._transform["Phi"].grid.num_nodes + full_rank = num_modes == num_nodes + + b = -singular_integral( + self._data["Phi"], + self._data["src"], + _kernel_Bn_over_r, + self._interpolator["Phi"], + chunk_size, + ).squeeze() / (2 * jnp.pi) + # Least squares method can significantly reduce size of A while + # retaining FFT interpolation accuracy in the singular integrals. + # Green is expensive, so constructing Jacobian A then solving + # better than iterative methods like ``jax.scipy.sparse.linalg.cg``. + A = _green(self, jnp.eye(num_modes), chunk_size).T + self._data["Phi"]["Phi_mn"] = ( + jnp.linalg.solve(A, b) if full_rank else jnp.linalg.lstsq(A, b)[0] + ) + return self._data + + +class VacuumSolver(IOAble): + """Compute vacuum field that satisfies LCFS boundary condition in plasma interior. + + Let D, D^∁ denote the interior, exterior of a toroidal region with + boundary ∂D. Computes the magnetic field 𝐁 in units of Tesla such that + + - 𝐁 = 𝐁₀ + ∇Φ on D + - 𝐁 ⋅ 𝐧 = 0 on ∂D + - ∇ × 𝐁₀ = μ₀ 𝐉 on D ∪ D^∁ + - ∇ ⋅ 𝐁₀ = 0 on D ∪ D^∁ + - ∇²Φ = 0 on D + + That is, given a magnetic field 𝐁₀ due to volume current sources, + finds the unique vacuum field ∇Φ such that 𝐁 ⋅ 𝐧 = 0 without assuming + nested flux surfaces. + + Examples + -------- + In a vacuum, the magnetic field may be written 𝐁 = ∇𝛷. The solution to + ∇²𝛷 = 0, under a homogenous boundary condition 𝐁 ⋅ 𝐧 = 0, is 𝛷 = 0. To + obtain a non-trivial solution, the boundary condition may be modified. + Let 𝐁 = 𝐁₀ + ∇Φ. If 𝐁₀ ≠ 0 and satisfies ∇ × 𝐁₀ = 0, then ∇²Φ = 0 solved + under an inhomogeneous boundary condition yields a non-trivial solution. + If 𝐁₀ ≠ -∇Φ, then 𝐁 ≠ 0. + + Parameters + ---------- + surface : Surface + Geometry defining ∂D. + B0 : _MagneticField + Magnetic field such that ∇ × 𝐁₀ = μ₀ 𝐉 + where 𝐉 is the current in amperes everywhere. + evl_grid : Grid + Evaluation points on D for the magnetic field. + Phi_grid : Grid + Interpolation points on ∂D. + Resolution determines accuracy of Φ interpolation. + Default resolution is ``Phi_grid.M=surface.M*2`` and ``Phi_grid.N=surface.N*2``. + src_grid : Grid + Source points on ∂D for quadrature of kernels. + Default resolution is ``src_grid.M=surface.M*4`` and ``src_grid.N=surface.N*4``. + Phi_M : int + Poloidal Fourier resolution to interpolate Φ on ∂D. + Should be at most ``Phi_grid.M``. + Phi_N : int + Toroidal Fourier resolution to interpolate Φ on ∂D. + Should be at most ``Phi_grid.N``. + sym + Symmetry for interpolation basis. + interior : bool + If true, it is assumed the evaluation grid is subset of D. + If false, it is assumed the evaluation grid is subset of ∂D. + chunk_size : int or None + Size to split computation into chunks. + If no chunking should be done or the chunk size is the full input + then supply ``None``. Default is ``None``. + B0n : jnp.ndarray + Optional, 𝐁₀⋅𝐧 on ``src_grid``. + use_dft : bool + Whether to use matrix multiplication transform from spectral to physical domain + instead of inverse fast Fourier transform. This is useful when the real domain + grid is much sparser than the spectral domain grid because the MMT is exact + while the FFT will truncate higher frequencies. + + """ + + def __init__( + self, + surface, + B0, + evl_grid, + Phi_grid=None, + src_grid=None, + Phi_M=None, + Phi_N=None, + sym=None, + interior=True, + *, + chunk_size=None, + B0n=None, + use_dft=False, + **kwargs, + ): + # TODO (#1599). + errorif( + use_dft, + RuntimeError, + msg="JAX performs matrix multiplication incorrectly for large matrices. " + "Until this is fixed, the inversion to obtain Phi cannot be done " + "with an interpolator that uses matrix multiplication transforms.", + ) + # TODO (#1206) + if Phi_grid is None: + Phi_grid = LinearGrid( + M=surface.M * 2, + N=surface.N * 2, + NFP=surface.NFP if surface.N > 0 else 64, + ) + if src_grid is None: + src_grid = Phi_grid = LinearGrid( + M=surface.M * 4, + N=surface.N * 4, + NFP=surface.NFP if surface.N > 0 else 64, + ) + self._same_grid_phi_src = src_grid.equiv(Phi_grid) + + errorif( + Phi_M is not None and Phi_M > Phi_grid.M, + msg=f"Got Phi_M={Phi_M} > {Phi_grid.M}=Phi_grid.M.", + ) + errorif( + Phi_N is not None and Phi_N > Phi_grid.N, + msg=f"Got Phi_N={Phi_N} > {Phi_grid.N}=Phi_grid.N.", + ) + basis = DoubleFourierSeries( + M=setdefault(Phi_M, Phi_grid.M), + N=setdefault(Phi_N, Phi_grid.N), + NFP=surface.NFP, + # TODO: Reviewer should update this. + sym=setdefault(sym, False) and surface.sym, + ) + + # Compute data on source grid. + position = ["R", "phi", "Z"] + src_transform = Transform(src_grid, basis, derivs=1) + src_data = surface.compute( + position + ["n_rho", "|e_theta x e_zeta|", "e_theta", "e_zeta"], + grid=src_grid, + ) + # Compute data on Phi grid. + if self._same_grid_phi_src: + Phi_transform = src_transform + Phi_data = src_data + else: + Phi_transform = Transform(Phi_grid, basis) + Phi_data = surface.compute(position, grid=Phi_grid) + # Compute data on evaluation grid. + if evl_grid.equiv(Phi_grid): + evl_data = Phi_data + elif not self._same_grid_phi_src and evl_grid.equiv(src_grid): + evl_data = src_data + else: + evl_data = surface.compute(position, grid=evl_grid) + + errorif(B0 is None and B0n is None) + src_data["Bn"] = ( + B0n + if B0n is not None + else B0.compute_Bnormal( + surface, + eval_grid=src_grid, + source_grid=src_grid, + vc_source_grid=src_grid, + chunk_size=chunk_size, + )[0] + ) + + self._B0 = B0 + self._evl_grid = evl_grid + self._interior = interior + self._data = {"evl": evl_data, "Phi": Phi_data, "src": src_data} + self._transform = {"Phi": Phi_transform, "src": src_transform} + self._interpolator = { + "evl": get_interpolator(evl_grid, src_grid, src_data, use_dft, **kwargs), + "Phi": get_interpolator(Phi_grid, src_grid, src_data, use_dft, **kwargs), + } + + def compute_Phi_mn(self, chunk_size=None): + """Compute Fourier coefficients of vacuum potential Φ on ∂D. + + Parameters + ---------- + chunk_size : int or None + Size to split computation into chunks. + If no chunking should be done or the chunk size is the full input + then supply ``None``. Default is ``None``. + + Returns + ------- + data : dict + Fourier coefficients of Φ on ∂D stored in ``data["Phi"]["Phi_mn"]``. + + """ + return _compute_Phi_mn(self, chunk_size=chunk_size) + + def _compute_virtual_current(self): + """𝐊_vc = -𝐧 × ∇Φ. + + This is the vacuum portion of the virtual casing current. + """ + if "K_vc" in self._data["src"]: + return self._data + + Phi_mn = self._data["Phi"]["Phi_mn"] + data = self._data["src"] + data["Phi_t"] = self._transform["src"].transform(Phi_mn, dt=1) + data["Phi_z"] = self._transform["src"].transform(Phi_mn, dz=1) + data["K^theta"] = data["Phi_z"] / data["|e_theta x e_zeta|"] + data["K^zeta"] = -data["Phi_t"] / data["|e_theta x e_zeta|"] + data["K_vc"] = ( + data["K^theta"][:, jnp.newaxis] * data["e_theta"] + + data["K^zeta"][:, jnp.newaxis] * data["e_zeta"] + ) + return self._data + + def compute_vacuum_field(self, chunk_size=None): + """Compute magnetic field due to vacuum potential Φ. + + Parameters + ---------- + chunk_size : int or None + Size to split computation into chunks. + If no chunking should be done or the chunk size is the full input + then supply ``None``. Default is ``None``. + + Returns + ------- + data : dict + Vacuum field ∇Φ stored in ``data["evl"]["grad(Phi)"]``. + + """ + if "grad(phi)" in self._data["evl"]: + return self._data + + self._data = self.compute_Phi_mn(chunk_size) + self._data = self._compute_virtual_current() + self._data["evl"]["grad(Phi)"] = ( + _nonsingular_part( + self._data["evl"], + self._evl_grid, + self._data["src"], + self._transform["src"].grid, + st=jnp.nan, + sz=jnp.nan, + kernel=_kernel_biot_savart_coulomb, + chunk_size=chunk_size, + _eta=eta_zero, + ) + if self._interior + else 2 + * singular_integral( + self._data["evl"], + self._data["src"], + kernel=_kernel_biot_savart_coulomb, + interpolator=self._interpolator["evl"], + chunk_size=chunk_size, + ) + ) + return self._data + + def compute_current_field(self, chunk_size=None): + """Compute magnetic field 𝐁₀ due to volume current sources. + + Parameters + ---------- + chunk_size : int or None + Size to split computation into chunks. + If no chunking should be done or the chunk size is the full input + then supply ``None``. Default is ``None``. + + Returns + ------- + data : dict + 𝐁₀ stored in ``data["evl"]["B0"]``. + + """ + if "B0" in self._data["evl"]: + return self._data + + data = self._data["evl"] + data["B0"] = self._B0.compute_magnetic_field( + coords=jnp.column_stack([data["R"], data["phi"], data["Z"]]), + source_grid=self._transform["src"].grid, + chunk_size=chunk_size, + ) + return self._data + + def compute_magnetic_field(self, chunk_size=None): + """Compute magnetic field 𝐁 = 𝐁₀ + ∇Φ. + + Parameters + ---------- + chunk_size : int or None + Size to split computation into chunks. + If no chunking should be done or the chunk size is the full input + then supply ``None``. Default is ``None``. + + Returns + ------- + data : dict + 𝐁 stored in ``data["evl"]["B0+grad(Phi)"]``. + + """ + if "B0+grad(Phi)" in self._data["evl"]: + return self._data + + self._data = self.compute_current_field(chunk_size) + self._data = self.compute_vacuum_field(chunk_size) + self._data["evl"]["B0+grad(Phi)"] = ( + self._data["evl"]["B0"] + self._data["evl"]["grad(Phi)"] + ) + return self._data + + def plot_Bn_error(self, Bn): + """Plot 𝐁 ⋅ 𝐧 error on ∂D. + + Parameters + ---------- + Bn : jnp.ndarray + 𝐁 ⋅ 𝐧 on the evaluation grid. + + Returns + ------- + fig, ax + Matplotlib (fig, ax) tuple. + + """ + errorif(self._interior) + grid = self._evl_grid + theta = grid.meshgrid_reshape(grid.nodes[:, 1], "rtz")[0] + zeta = grid.meshgrid_reshape(grid.nodes[:, 2], "rtz")[0] + Bn = grid.meshgrid_reshape(Bn, "rtz")[0] + + fig, ax = plt.subplots() + contour = ax.contourf(theta, zeta, Bn) + fig.colorbar(contour, ax=ax) + ax.set_title(r"$(B_0 + \nabla \Phi) \cdot n$ on $\partial D$") + return fig, ax diff --git a/desc/integrals/quad_utils.py b/desc/integrals/quad_utils.py index 57cd99d1ab..0f1a436194 100644 --- a/desc/integrals/quad_utils.py +++ b/desc/integrals/quad_utils.py @@ -16,7 +16,7 @@ from orthax.chebyshev import chebgauss, chebweight from orthax.legendre import legder, legval -from desc.backend import eigh_tridiagonal, jnp, put +from desc.backend import eigh_tridiagonal, fori_loop, jnp, put from desc.utils import errorif @@ -330,3 +330,134 @@ def get_quadrature(quad, automorphism): # Recall bijection_from_disc(auto(x), ζ₁, ζ₂) = ζ. x = auto(x) return x, w + + +def nfp_loop(source_grid, func, init_val): + """Calculate effects from source points on a single field period. + + The integral is computed on the full domain because the kernels of interest + have toroidal variation and are not NFP periodic. To that end, the integral + is computed on every field period and summed. The ``source_grid`` is the + first field period because DESC truncates the computational domain to + ζ ∈ [0, 2π/grid.NFP) and changes variables to the spectrally condensed + ζ* = basis.NFP ζ. The domain is shifted to the next field period by + incrementing the toroidal coordinate of the grid by 2π/NFP. + + For an axisymmetric configuration, it is most efficient for ``source_grid`` to + be a single toroidal cross-section. To capture toroidal effects of the kernels + on those grids for axisymmetric configurations, we set a dummy value for NFP to + an integer larger than 1 so that the toroidal increment can move to a new spot. + + Parameters + ---------- + source_grid : _Grid + Grid with points ζ ∈ [0, 2π/grid.NFP). + func : callable + Should accept argument ``zeta_j`` denoting toroidal coordinates of + field period ``j``. + init_val : jnp.ndarray + Initial loop carry value. + + Returns + ------- + result : jnp.ndarray + Shape is ``init_val.shape``. + + """ + errorif( + source_grid.num_zeta == 1 and source_grid.NFP == 1, + msg="Source grid cannot compute toroidal effects.\n" + "Increase NFP of source grid to e.g. 64.", + ) + + def body(j, f): + zeta_j = source_grid.nodes[:, 2] + j * 2 * jnp.pi / source_grid.NFP + return f + func(zeta_j) + + return fori_loop(0, source_grid.NFP, body, init_val) + + +def chi(r): + """Partition of unity function in polar coordinates. Eq 39 in [2]. + + Parameters + ---------- + r : jnp.ndarray + Absolute value of radial coordinate in polar domain. + + """ + return jnp.exp(-36 * jnp.abs(r) ** 8) + + +def eta(theta, zeta, theta0, zeta0, ht, hz, st, sz): + """Partition of unity function in rectangular coordinates. + + Consider the mapping from + (θ,ζ) ∈ [-π, π) × [-π/NFP, π/NFP) to (ρ,ω) ∈ [−1, 1] × [0, 2π) + defined by + θ − θ₀ = h₁ s₁/2 ρ sin ω + ζ − ζ₀ = h₂ s₂/2 ρ cos ω + with Jacobian determinant norm h₁h₂ s₁s₂/4 |ρ|. + + In general in dimensions higher than one, the mapping that determines a + change of variable for integration must be bijective. This is satisfied + only if s₁ = 2π/h₁ and s₂ = (2π/NFP)/h₂. In the particular case the + integrand is nonzero in a subset of the domain, then the change of variable + need only be a bijective map where the function does not vanish, more + precisely, its set of compact support. + + The functions we integrate are proportional to η₀(θ,ζ) = χ₀(r) far from the + singularity at (θ₀,ζ₀). Therefore, the support matches χ₀(r)'s, assuming + this region is sufficiently large compared to the singular region. + Here χ₀(r) has support where the argument r lies in [0, 1]. The map r + defines a coordinate mapping between the toroidal domain and a polar domain + such that the integration region in the polar domain (ρ,ω) ∈ [−1, 1] × [0, 2π) + equals the compact support, and furthermore is a circular region around the + singular point in (θ,ζ) geometry when s₁ × s₂ denote the number of grid points + on a uniformly discretized toroidal domain (θ,ζ) ∈ [0, 2π)². + χ₀ : r ↦ exp(−36r⁸) + + r : ρ, ω ↦ |ρ| + + r : θ, ζ ↦ 2 [ (θ−θ₀)²/(h₁s₁)² + (ζ−ζ₀)²/(h₂s₂)² ]⁰ᐧ⁵ + + Hence, r ≥ 1 (r ≤ 1) outside (inside) the integration domain. + + The choice for the size of the support is determined by s₁ and s₂. + The optimal choice is dependent on the nature of the singularity e.g. if the + integrand decays quickly then the elliptical grid determined by s₁ and s₂ + can be made smaller and the integration will have higher resolution for the + same number of quadrature points. + + With the above definitions the support lies on an s₁ × s₂ subset + of a field period which has ``num_theta`` × ``num_zeta`` nodes total. + Since kernels are 2π periodic, the choice for s₂ should be multiplied by NFP. + Then the support lies on an s₁ × s₂ subset of the full domain. For large NFP + devices such as Heliotron or tokamaks it is typical that s₁ ≪ s₂. + + Parameters + ---------- + theta, zeta : jnp.ndarray + Coordinates of points to evaluate partition function η₀(θ,ζ). + theta0, zeta0 : jnp.ndarray + Origin (θ₀,ζ₀) where the partition η₀ is unity. + ht, hz : float + Grid step size in θ and ζ. + st, sz : int + Extent of support is an ``st`` × ``sz`` subset + of the full domain (θ,ζ) ∈ [0, 2π)² of ``source_grid``. + Subset of ``source_grid.num_theta`` × ``source_grid.num_zeta*source_grid.NFP``. + + """ + dt = jnp.abs(theta - theta0) + dz = jnp.abs(zeta - zeta0) + # The distance spans (dθ,dζ) ∈ [0, π]², independent of NFP. + dt = jnp.minimum(dt, 2 * jnp.pi - dt) + dz = jnp.minimum(dz, 2 * jnp.pi - dz) + r = 2 * jnp.hypot(dt / (ht * st), dz / (hz * sz)) + return chi(r) + + +def eta_zero(theta, zeta, theta0, zeta0, ht, hz, st, sz): + """Returns η = 0 to integrate smooth functions with ``_nonsingular_part``.""" + return 0 diff --git a/desc/integrals/singularities.py b/desc/integrals/singularities.py index b5d669078b..8a2f4da712 100644 --- a/desc/integrals/singularities.py +++ b/desc/integrals/singularities.py @@ -2,116 +2,38 @@ from abc import ABC, abstractmethod -import numpy as np import scipy from interpax import fft_interp2d from scipy.constants import mu_0 -from desc.backend import fori_loop, jnp, rfft2 +from desc.backend import jnp, rfft2 from desc.batching import batch_map, vmap_chunked from desc.compute.geom_utils import rpz2xyz, rpz2xyz_vec, xyz2rpz_vec from desc.grid import LinearGrid from desc.integrals._interp_utils import rfft2_modes, rfft2_vander +from desc.integrals.quad_utils import chi, eta, nfp_loop from desc.io import IOAble from desc.utils import ( check_posint, + dot, errorif, parse_argname_change, safediv, safenorm, + setdefault, warnif, ) -def _chi(r): - """Partition of unity function in polar coordinates. Eq 39 in [2]. - - Parameters - ---------- - r : jnp.ndarray - Absolute value of radial coordinate in polar domain. - - """ - return jnp.exp(-36 * jnp.abs(r) ** 8) - - -def _eta(theta, zeta, theta0, zeta0, ht, hz, st, sz): - """Partition of unity function in rectangular coordinates. - - Consider the mapping from - (θ,ζ) ∈ [-π, π) × [-π/NFP, π/NFP) to (ρ,ω) ∈ [−1, 1] × [0, 2π) - defined by - θ − θ₀ = h₁ s₁/2 ρ sin ω - ζ − ζ₀ = h₂ s₂/2 ρ cos ω - with Jacobian determinant norm h₁h₂ s₁s₂/4 |ρ|. - - In general in dimensions higher than one, the mapping that determines a - change of variable for integration must be bijective. This is satisfied - only if s₁ = 2π/h₁ and s₂ = (2π/NFP)/h₂. In the particular case the - integrand is nonzero in a subset of the domain, then the change of variable - need only be a bijective map where the function does not vanish, more - precisely, its set of compact support. - - The functions we integrate are proportional to η₀(θ,ζ) = χ₀(r) far from the - singularity at (θ₀,ζ₀). Therefore, the support matches χ₀(r)'s, assuming - this region is sufficiently large compared to the singular region. - Here χ₀(r) has support where the argument r lies in [0, 1]. The map r - defines a coordinate mapping between the toroidal domain and a polar domain - such that the integration region in the polar domain (ρ,ω) ∈ [−1, 1] × [0, 2π) - equals the compact support, and furthermore is a circular region around the - singular point in (θ,ζ) geometry when s₁ × s₂ denote the number of grid points - on a uniformly discretized toroidal domain (θ,ζ) ∈ [0, 2π)². - χ₀ : r ↦ exp(−36r⁸) - - r : ρ, ω ↦ |ρ| - - r : θ, ζ ↦ 2 [ (θ−θ₀)²/(h₁s₁)² + (ζ−ζ₀)²/(h₂s₂)² ]⁰ᐧ⁵ - - Hence, r ≥ 1 (r ≤ 1) outside (inside) the integration domain. - - The choice for the size of the support is determined by s₁ and s₂. - The optimal choice is dependent on the nature of the singularity e.g. if the - integrand decays quickly then the elliptical grid determined by s₁ and s₂ - can be made smaller and the integration will have higher resolution for the - same number of quadrature points. - - With the above definitions the support lies on an s₁ × s₂ subset - of a field period which has ``num_theta`` × ``num_zeta`` nodes total. - Since kernels are 2π periodic, the choice for s₂ should be multiplied by NFP. - Then the support lies on an s₁ × s₂ subset of the full domain. For large NFP - devices such as Heliotron or tokamaks it is typical that s₁ ≪ s₂. - - Parameters - ---------- - theta, zeta : jnp.ndarray - Coordinates of points to evaluate partition function η₀(θ,ζ). - theta0, zeta0 : jnp.ndarray - Origin (θ₀,ζ₀) where the partition η₀ is unity. - ht, hz : float - Grid step size in θ and ζ. - st, sz : int - Extent of support is an ``st`` × ``sz`` subset - of the full domain (θ,ζ) ∈ [0, 2π)² of ``source_grid``. - Subset of ``source_grid.num_theta`` × ``source_grid.num_zeta*source_grid.NFP``. - - """ - dt = jnp.abs(theta - theta0) - dz = jnp.abs(zeta - zeta0) - # The distance spans (dθ,dζ) ∈ [0, π]², independent of NFP. - dt = jnp.minimum(dt, 2 * jnp.pi - dt) - dz = jnp.minimum(dz, 2 * jnp.pi - dz) - r = 2 * jnp.hypot(dt / (ht * st), dz / (hz * sz)) - return _chi(r) - - def _get_default_params(grid): - k = max(min(grid.num_theta, grid.num_zeta * grid.NFP), 2) - s = k - 1 - q = k // 2 + int(jnp.sqrt(k)) + Nt = grid.num_theta + Nz = grid.num_zeta * grid.NFP + q = int(jnp.sqrt(grid.num_nodes) / 2) + s = min(q, Nt, Nz) return s, s, q -def _heuristic_support_params(grid): +def heuristic_support_params(grid, mean_ratio, local_ratio=None): """Parameters for heuristic support size and heuristic quadrature resolution. Return parameters following the asymptotic rule of thumb @@ -122,6 +44,10 @@ def _heuristic_support_params(grid): ---------- grid : LinearGrid Grid that can fft2. + mean_ratio : float + Mean ratio. + local_ratio : jnp.ndarray + Local ratio. Returns ------- @@ -134,21 +60,107 @@ def _heuristic_support_params(grid): """ assert grid.can_fft2 - # TODO: Account for real space grid anisotropy by choosing - # ratio = |e_zeta|/|e_theta| - # ratio_avg = surface_average(ratio, jacobian=|e_theta x e_zeta|) - # st/(sz*NFP) (at evaluation point i) = mean (ratio(i), ratio_avg). - # Then we have ~circle in real space around each singular point. - # The grid resolution can then still be chosen independently according - # to the frequency content of R, Z, λ to net the best function approximation. + # This is simple and should improve convergence, but do later. + errorif(local_ratio is not None, NotImplementedError) Nt = grid.num_theta Nz = grid.num_zeta * grid.NFP - st = int(min(1 + jnp.sqrt(Nt), Nt)) - sz = int(min(1 + jnp.sqrt(Nz), Nz)) - q = int(1 + (Nt * Nz) ** 0.25) + if grid.num_zeta > 1: # actually has toroidal resolution + q = int(jnp.sqrt(grid.num_nodes) / 2) + else: # axisymmetry + q = int(jnp.sqrt(Nt * Nz) / 2) + s = min(q, Nt, Nz) + local_ratio = setdefault(local_ratio, mean_ratio) + ratio = (mean_ratio + local_ratio) / 2 + # Size of singular region in real space = s * h * |e_.| + # For it to be a circle, choose radius ~ equal + # s_t * h_t * |e_t| = s_z * h_z * |e_z| + # s_z / s_t = h_t / h_z |e_t| / |e_z| = Nz*NFP/Nt |e_t| / |e_z| + # Denote ratio = < |e_z| / |e_t| > and + # s_ratio = s_z / s_t = Nz*NFP/Nt / ratio + # Also want sqrt(s_z*s_t) ~ s = q. + s_ratio = jnp.sqrt(Nz / Nt / ratio) + st = min(Nt, int(jnp.ceil(s / s_ratio))) + sz = min(Nz, int(jnp.ceil(s * s_ratio))) return st, sz, q +def best_ratio(data): + """Ratio to make singular integration partition ~circle in real space. + + Parameters + ---------- + data : dict[str, jnp.ndarray] + Dictionary of data evaluated on grid that ``can_fft2`` with keys + ``|e_theta x e_zeta|``, ``e_theta``, and ``e_zeta``. + + Returns + ------- + mean : float + Mean ratio. + local : jnp.ndarray + Local ratio. + + """ + local = jnp.linalg.norm(data["e_zeta"], axis=-1) / jnp.linalg.norm( + data["e_theta"], axis=-1 + ) + mean = jnp.mean(local * data["|e_theta x e_zeta|"]) / jnp.mean( + data["|e_theta x e_zeta|"] + ) + return mean, local + + +def get_interpolator( + eval_grid, source_grid, src_data, use_dft=False, warn_fft=True, **kwargs +): + """Get interpolator from Cartesian to polar domain. + + Parameters + ---------- + eval_grid, source_grid : Grid + Evaluation and source points for the integral transform. + src_data : dict[str, jnp.ndarray] + Dictionary of data evaluated on grid that ``can_fft2`` with keys + ``|e_theta x e_zeta|``, ``e_theta``, and ``e_zeta``. + use_dft : bool + Whether to use matrix multiplication transform from spectral to physical domain + instead of inverse fast Fourier transform. This is useful when the real domain + grid is much sparser than the spectral domain grid because the MMT is exact + while the FFT will truncate higher frequencies. + warn_fft : bool + Set to ``False`` to turn off warnings about FFT frequency truncation. + + Returns + ------- + f : _BIESTInterpolator + Interpolator that uses the specified method. + + """ + st, sz, q = heuristic_support_params(source_grid, best_ratio(src_data)[0]) + if use_dft: + f = DFTInterpolator(eval_grid, source_grid, st, sz, q) + else: + try: + f = FFTInterpolator(eval_grid, source_grid, st, sz, q, warn_fft=warn_fft) + except AssertionError as e: + warnif( + True, + msg="Could not build fft interpolator, switching to dft which is slow." + "\nReason: " + str(e), + ) + f = DFTInterpolator(eval_grid, source_grid, st, sz, q) + use_dft = True + # TODO (#1599). + warnif( + use_dft, + RuntimeWarning, + msg="JAX performs matrix multiplication incorrectly for large matrices. " + "Until this is fixed, it is recommended to choose a small chunk size " + "when using an interpolator that uses matrix multiplication transforms.", + ) + return f + + def _get_quadrature_nodes(q): """Polar nodes for quadrature around singular point. @@ -342,15 +354,16 @@ class FFTInterpolator(_BIESTInterpolator): def __init__(self, eval_grid, source_grid, st, sz, q, **kwargs): st = parse_argname_change(st, kwargs, "s", "st") assert eval_grid.can_fft2, "Got False for eval_grid.can_fft2." + warn = kwargs.get("warn_fft", True) warnif( - eval_grid.num_theta < source_grid.num_theta, + warn and eval_grid.num_theta < source_grid.num_theta, msg="Frequency spectrum of FFT interpolation will be truncated because " "the evaluation grid has less resolution than the source grid.\n" f"Got eval_grid.num_theta = {eval_grid.num_theta} < " f"{source_grid.num_theta} = source_grid.num_theta.", ) warnif( - eval_grid.num_zeta < source_grid.num_zeta, + warn and eval_grid.num_zeta < source_grid.num_zeta, msg="Frequency spectrum of FFT interpolation will be truncated because " "the evaluation grid has less resolution than the source grid.\n" f"Got eval_grid.num_zeta = {eval_grid.num_zeta} < " @@ -389,7 +402,8 @@ def __call__(self, f, i, *, is_fourier=False, vander=None): """ # Would need to add interpax code to DESC # https://github.com/f0uriest/interpax/issues/53 - # for is_fourier to do anything. + # for is_fourier to do anything. Should also use rfft2 and irfft2. + # TODO (#1206) shape = f.shape[1:] return fft_interp2d( self._source_grid.meshgrid_reshape(f, "rtz")[0], @@ -488,17 +502,20 @@ def _nonsingular_part( sz, kernel, chunk_size=None, + _eta=eta, ): """Integrate kernel over non-singular points. Generally follows sec 3.2.1 of [2]. """ - source_theta = source_grid.nodes[:, 1] + assert source_grid.can_fft2 + source_data.setdefault("theta", source_grid.nodes[:, 1]) # make sure source dict has zeta and phi to avoid # adding keys to dict during iteration source_zeta = source_data.setdefault("zeta", source_grid.nodes[:, 2]) source_phi = source_data["phi"] + # slim down to skip batching quantities that aren't used eval_data = {key: eval_data[key] for key in kernel.keys if key in eval_data} eval_data["theta"] = jnp.asarray(eval_grid.nodes[:, 1]) eval_data["zeta"] = jnp.asarray(eval_grid.nodes[:, 2]) @@ -507,37 +524,17 @@ def _nonsingular_part( hz = 2 * jnp.pi / source_grid.num_zeta / source_grid.NFP w = source_data["|e_theta x e_zeta|"][jnp.newaxis] * ht * hz - def nfp_loop(j, f_data): - """Calculate effects from source points on a single field period. - - The surface integral is computed on the full domain because the kernels of - interest have toroidal variation and are not NFP periodic. To that end, the - integral is computed on every field period and summed. The ``source_grid`` is - the first field period because DESC truncates the computational domain to - ζ ∈ [0, 2π/grid.NFP) and changes variables to the spectrally condensed - ζ* = basis.NFP ζ. Therefore, we shift the domain to the next field period by - incrementing the toroidal coordinate of the grid by 2π/NFP. For an axisymmetric - configuration, it is most efficient for ``source_grid`` to be a single toroidal - cross-section. To capture toroidal effects of the kernels on those grids for - axisymmetric configurations, we set a dummy value for NFP to an integer larger - than 1 so that the toroidal increment can move to a new spot. - """ - f, source_data = f_data - source_data["zeta"] = (source_zeta + j * 2 * jnp.pi / source_grid.NFP) % ( - 2 * jnp.pi - ) - source_data["phi"] = (source_phi + j * 2 * jnp.pi / source_grid.NFP) % ( - 2 * jnp.pi - ) + def func(zeta_j): + source_data["zeta"] = zeta_j + source_data["phi"] = zeta_j # TODO (#465) - # nest this def to avoid having to pass the modified source_data around the loop - # easier to just close over it and let JAX figure it out + # nest this def and let JAX figure it out def eval_pt(eval_data_i): k = kernel(eval_data_i, source_data).reshape( -1, source_grid.num_nodes, kernel.ndim ) - eta = _eta( - source_theta, + e = 1 - _eta( + source_data["theta"], source_data["zeta"], eval_data_i["theta"][:, jnp.newaxis], eval_data_i["zeta"][:, jnp.newaxis], @@ -546,24 +543,13 @@ def eval_pt(eval_data_i): st, sz, ) - return jnp.sum(k * (w * (1 - eta))[..., jnp.newaxis], axis=1) + return jnp.sum(k * (w * e)[..., jnp.newaxis], axis=1) - f += batch_map(eval_pt, eval_data, chunk_size).reshape( + return batch_map(eval_pt, eval_data, chunk_size).reshape( eval_grid.num_nodes, kernel.ndim ) - return f, source_data - - # This error should be raised earlier since this is not the only place - # we need the higher dummy NFP value, but the error message is more - # helpful with the nfp loop docstring. - errorif( - source_grid.num_zeta == 1 and source_grid.NFP == 1, - msg="Source grid cannot compute toroidal effects.\n" - "Increase NFP of source grid to e.g. 64.\n" - "This is required to " + nfp_loop.__doc__, - ) - f = jnp.zeros((eval_grid.num_nodes, kernel.ndim)) - f, _ = fori_loop(0, source_grid.NFP, nfp_loop, (f, source_data)) + + f = nfp_loop(source_grid, func, jnp.zeros((eval_grid.num_nodes, kernel.ndim))) # undo rotation of source_zeta source_data["zeta"] = source_zeta @@ -592,7 +578,7 @@ def _singular_part(eval_data, source_data, kernel, interpolator, chunk_size=None r = jnp.abs(r) # integrand of eq 38 in [2] except stuff that needs to be interpolated v = ( - _chi(r) + chi(r) * (interpolator.ht * interpolator.hz) * (interpolator.st * interpolator.sz / 4) * r @@ -604,11 +590,10 @@ def _singular_part(eval_data, source_data, kernel, interpolator, chunk_size=None if "phi" in keys: keys.remove("phi") # ϕ is not a periodic map of θ, ζ. keys.add("omega") - keys = list(keys) # Note that it is necessary to take the Fourier transforms of the # vector components of the orthonormal polar basis vectors R̂, ϕ̂, Ẑ. # Vector components of the Cartesian basis are not NFP periodic. - fsource = [interpolator.fourier(source_data[key]) for key in keys] + fsource = [(key, interpolator.fourier(source_data[key])) for key in keys] def polar_pt(i): """See sec 3.2.2 of [2]. @@ -618,9 +603,11 @@ def polar_pt(i): so only the diagonal term of the kernel is needed. """ vander = interpolator.vander_polar(i) + # TODO: Don't FFT and interpolate Phi; it's known analytically as just + # a single harmonic source_data_polar = { key: interpolator(val, i, is_fourier=True, vander=vander) - for key, val in zip(keys, fsource) + for key, val in fsource } # Coordinates of the polar nodes around the evaluation point. source_data_polar["theta"] = eval_theta + interpolator.shift_t[i] @@ -754,64 +741,48 @@ def singular_integral( return out1 + out2 -def _kernel_nr_over_r3(eval_data, source_data, diag=False): - # n * r / |r|^3 - source_x = jnp.atleast_2d( - rpz2xyz(jnp.array([source_data["R"], source_data["phi"], source_data["Z"]]).T) +def _dx(eval_data, source_data, diag=False): + """Returns dx = x−x'.""" + source_x = rpz2xyz( + jnp.column_stack([source_data["R"], source_data["phi"], source_data["Z"]]) ) - eval_x = jnp.atleast_2d( - rpz2xyz(jnp.array([eval_data["R"], eval_data["phi"], eval_data["Z"]]).T) + eval_x = rpz2xyz( + jnp.column_stack([eval_data["R"], eval_data["phi"], eval_data["Z"]]) ) - if diag: - dx = eval_x - source_x - else: - dx = eval_x[:, None] - source_x[None] - n = rpz2xyz_vec(source_data["e^rho"], phi=source_data["phi"]) - n = n / jnp.linalg.norm(n, axis=-1, keepdims=True) - r = safenorm(dx, axis=-1) - return safediv(jnp.sum(n * dx, axis=-1), r**3) - - -_kernel_nr_over_r3.ndim = 1 -_kernel_nr_over_r3.keys = ["R", "phi", "Z", "e^rho"] + if not diag: + eval_x = eval_x[:, jnp.newaxis] + return eval_x - source_x def _kernel_1_over_r(eval_data, source_data, diag=False): - # 1/|r| - source_x = jnp.atleast_2d( - rpz2xyz(jnp.array([source_data["R"], source_data["phi"], source_data["Z"]]).T) - ) - eval_x = jnp.atleast_2d( - rpz2xyz(jnp.array([eval_data["R"], eval_data["phi"], eval_data["Z"]]).T) - ) - if diag: - dx = eval_x - source_x - else: - dx = eval_x[:, None] - source_x[None] - r = safenorm(dx, axis=-1) - return safediv(1, r) + """Returns G(x,x') = |x−x'|⁻¹.""" + dx = _dx(eval_data, source_data, diag) + return safediv(1, safenorm(dx, axis=-1)) _kernel_1_over_r.ndim = 1 _kernel_1_over_r.keys = ["R", "phi", "Z"] +def _kernel_nr_over_r3(eval_data, source_data, diag=False): + """Returns n ⋅ −∇G(x,x') = n ⋅ (x−x')|x−x'|⁻³.""" + dx = _dx(eval_data, source_data, diag) + n = rpz2xyz_vec(source_data["n_rho"], phi=source_data["phi"]) + return safediv(dot(n, dx), safenorm(dx, axis=-1) ** 3) + + +_kernel_nr_over_r3.ndim = 1 +_kernel_nr_over_r3.keys = ["R", "phi", "Z", "n_rho"] + + def _kernel_biot_savart(eval_data, source_data, diag=False): - # K x r / |r|^3 - source_x = jnp.atleast_2d( - rpz2xyz(jnp.array([source_data["R"], source_data["phi"], source_data["Z"]]).T) - ) - eval_x = jnp.atleast_2d( - rpz2xyz(jnp.array([eval_data["R"], eval_data["phi"], eval_data["Z"]]).T) - ) - if diag: - dx = eval_x - source_x - else: - dx = eval_x[:, None] - source_x[None] + """Returns (μ₀/4π) K × −∇G(x,x') = (μ₀/4π) K × (x−x')|x−x'|⁻³.""" + dx = _dx(eval_data, source_data, diag) K = rpz2xyz_vec(source_data["K_vc"], phi=source_data["phi"]) - num = jnp.cross(K, dx, axis=-1) - r = safenorm(dx, axis=-1)[..., None] - return mu_0 / 4 / jnp.pi * safediv(num, r**3) + return safediv( + mu_0 / (4 * jnp.pi) * jnp.cross(K, dx, axis=-1), + safenorm(dx, axis=-1, keepdims=True) ** 3, + ) _kernel_biot_savart.ndim = 3 @@ -819,31 +790,64 @@ def _kernel_biot_savart(eval_data, source_data, diag=False): def _kernel_biot_savart_A(eval_data, source_data, diag=False): - # K / |r| - source_x = jnp.atleast_2d( - rpz2xyz(jnp.array([source_data["R"], source_data["phi"], source_data["Z"]]).T) - ) - eval_x = jnp.atleast_2d( - rpz2xyz(jnp.array([eval_data["R"], eval_data["phi"], eval_data["Z"]]).T) - ) - if diag: - dx = eval_x - source_x - else: - dx = eval_x[:, None] - source_x[None] - r = safenorm(dx, axis=-1)[..., None] + """Returns (μ₀/4π) K G(x,x') = (μ₀/4π) K |x−x'|⁻¹.""" + dx = _dx(eval_data, source_data, diag) K = rpz2xyz_vec(source_data["K_vc"], phi=source_data["phi"]) - return mu_0 / 4 / jnp.pi * safediv(K, r) + return safediv( + mu_0 / (4 * jnp.pi) * K, + safenorm(dx, axis=-1, keepdims=True), + ) _kernel_biot_savart_A.ndim = 3 _kernel_biot_savart_A.keys = ["R", "phi", "Z", "K_vc"] +def _kernel_Bn_over_r(eval_data, source_data, diag=False): + """Returns Bₙ G(x,x') = Bₙ |x−x'|⁻¹.""" + dx = _dx(eval_data, source_data, diag) + return safediv(source_data["Bn"], safenorm(dx, axis=-1)) + + +_kernel_Bn_over_r.ndim = 1 +_kernel_Bn_over_r.keys = ["R", "phi", "Z", "Bn"] + + +def _kernel_Phi_dGp_dn(eval_data, source_data, diag=False): + """Returns Φ n ⋅ −∇G(x,x') = Φ n ⋅ (x−x')|x−x'|⁻³.""" + dx = _dx(eval_data, source_data, diag) + n = rpz2xyz_vec(source_data["n_rho"], phi=source_data["phi"]) + # Phi has units Tesla-meters. + return safediv(source_data["Phi"] * dot(n, dx), safenorm(dx, axis=-1) ** 3) + + +_kernel_Phi_dGp_dn.ndim = 1 +_kernel_Phi_dGp_dn.keys = ["R", "phi", "Z", "n_rho", "Phi"] + + +def _kernel_biot_savart_coulomb(eval_data, source_data, diag=False): + """Returns [ K (Tesla) × −∇G(x,x') - Bₙ ∇G(x,x') ] / 4π.""" + dx = _dx(eval_data, source_data, diag) + K = rpz2xyz_vec(source_data["K_vc"], phi=source_data["phi"]) + numerator = jnp.cross(K, dx, axis=-1) + source_data["Bn"][:, jnp.newaxis] * dx + return safediv( + numerator / (4 * jnp.pi), + safenorm(dx, axis=-1, keepdims=True) ** 3, + ) + + +_kernel_biot_savart_coulomb.ndim = 3 +_kernel_biot_savart_coulomb.keys = ["R", "phi", "Z", "K_vc", "Bn"] + + kernels = { "1_over_r": _kernel_1_over_r, "nr_over_r3": _kernel_nr_over_r3, "biot_savart": _kernel_biot_savart, "biot_savart_A": _kernel_biot_savart_A, + "Bn_over_r": _kernel_Bn_over_r, + "Phi_dGp_dn": _kernel_Phi_dGp_dn, + "biot_savart_coulomb": _kernel_biot_savart_coulomb, } @@ -964,28 +968,19 @@ def compute_B_plasma(eq, eval_grid, source_grid=None, normal_only=False): """ if source_grid is None: source_grid = LinearGrid( - rho=np.array([1.0]), M=eq.M_grid, N=eq.N_grid, NFP=eq.NFP if eq.N > 0 else 64, sym=False, ) - data_keys = ["K_vc", "B", "R", "phi", "Z", "e^rho", "n_rho", "|e_theta x e_zeta|"] - eval_data = eq.compute(data_keys, grid=eval_grid) - source_data = eq.compute(data_keys, grid=source_grid) - st, sz, q = _get_default_params(source_grid) - try: - interpolator = FFTInterpolator(eval_grid, source_grid, st, sz, q) - except AssertionError as e: - warnif( - True, - msg="Could not build fft interpolator, switching to dft which is slow." - "\nReason: " + str(e), - ) - interpolator = DFTInterpolator(eval_grid, source_grid, st, sz, q) + names = ["K_vc", "B", "R", "phi", "Z", "e^rho", "n_rho", "|e_theta x e_zeta|"] + eval_data = eq.compute(names, grid=eval_grid) + source_data = eq.compute(names, grid=source_grid) if hasattr(eq.surface, "Phi_mn"): source_data["K_vc"] += eq.surface.compute("K", grid=source_grid)["K"] + + interpolator = get_interpolator(eval_grid, source_grid, source_data) Bplasma = virtual_casing_biot_savart(eval_data, source_data, interpolator) # need extra factor of B/2 bc we're evaluating on plasma surface Bplasma = Bplasma + eval_data["B"] / 2 diff --git a/desc/magnetic_fields/_core.py b/desc/magnetic_fields/_core.py index 422dbb27c5..e1c262b01c 100644 --- a/desc/magnetic_fields/_core.py +++ b/desc/magnetic_fields/_core.py @@ -5,7 +5,6 @@ from collections.abc import MutableSequence import numpy as np -import scipy from diffrax import ( DiscreteTerminatingEvent, ODETerm, @@ -16,13 +15,15 @@ ) from interpax import approx_df, interp1d, interp2d, interp3d from netCDF4 import Dataset, chartostring, stringtochar +from scipy.constants import mu_0 -from desc.backend import fori_loop, jit, jnp, sign +from desc.backend import jit, jnp, sign from desc.basis import ( ChebyshevDoubleFourierBasis, ChebyshevPolynomial, DoubleFourierSeries, ) +from desc.batching import batch_map from desc.compute import compute as compute_fun from desc.compute import rpz2xyz, rpz2xyz_vec, xyz2rpz, xyz2rpz_vec from desc.compute.utils import get_params, get_transforms @@ -33,76 +34,103 @@ from desc.io import IOAble from desc.optimizable import Optimizable, OptimizableCollection, optimizable_parameter from desc.transform import Transform -from desc.utils import copy_coeffs, errorif, flatten_list, setdefault, warnif +from desc.utils import ( + copy_coeffs, + dot, + errorif, + flatten_list, + safediv, + setdefault, + warnif, +) from desc.vmec_utils import ptolemy_identity_fwd, ptolemy_identity_rev -def biot_savart_general(re, rs, J, dV): +def biot_savart_general(re, rs, J, dV=jnp.array([1.0]), chunk_size=None): """Biot-Savart law for arbitrary sources. Parameters ---------- - re : ndarray, shape(n_eval_pts, 3) - evaluation points to evaluate B at, in cartesian. - rs : ndarray, shape(n_src_pts, 3) - source points for current density J, in cartesian. - J : ndarray, shape(n_src_pts, 3) - current density vector at source points, in cartesian. - dV : ndarray, shape(n_src_pts) - volume element at source points + re : ndarray + Shape (n_eval_pts, 3). + Evaluation points to evaluate B at, in cartesian. + rs : ndarray + Shape (n_src_pts, 3). + Source points for current density J, in cartesian. + J : ndarray + Shape (n_src_pts, 3). + Current density vector at source points, in cartesian. + dV : ndarray + Shape (n_src_pts, ). + Volume element at source points + chunk_size : int or None + Size to split computation into chunks of evaluation points. + If no chunking should be done or the chunk size is the full input + then supply ``None``. Default is ``None``. Returns ------- - B : ndarray, shape(n,3) - magnetic field in cartesian components at specified points + B : ndarray + Shape(n_eval_pts, 3). + Magnetic field in cartesian components at specified points. + """ re, rs, J, dV = map(jnp.asarray, (re, rs, J, dV)) - assert J.shape == rs.shape - JdV = J * dV[:, None] - B = jnp.zeros_like(re) - - def body(i, B): - r = re - rs[i, :] - num = jnp.cross(JdV[i, :], r, axis=-1) - den = jnp.linalg.norm(r, axis=-1) ** 3 - B = B + jnp.where(den[:, None] == 0, 0, num / den[:, None]) - return B + JdV = J * dV[:, jnp.newaxis] + assert JdV.shape == rs.shape - return scipy.constants.mu_0 / 4 / jnp.pi * fori_loop(0, J.shape[0], body, B) + def biot(re): + dr = rs - re + num = jnp.cross(dr, JdV, axis=-1) + den = jnp.linalg.norm(dr, axis=-1, keepdims=True) ** 3 + return safediv(num, den).sum(axis=-2) * mu_0 / (4 * jnp.pi) + # It is more efficient to sum over the sources in batches of evaluation points. + return batch_map(biot, re[..., jnp.newaxis, :], chunk_size) -def biot_savart_general_vector_potential(re, rs, J, dV): + +def biot_savart_general_vector_potential( + re, rs, J, dV=jnp.array([1.0]), chunk_size=None +): """Biot-Savart law for arbitrary sources for vector potential. Parameters ---------- - re : ndarray, shape(n_eval_pts, 3) - evaluation points to evaluate B at, in cartesian. - rs : ndarray, shape(n_src_pts, 3) - source points for current density J, in cartesian. - J : ndarray, shape(n_src_pts, 3) - current density vector at source points, in cartesian. - dV : ndarray, shape(n_src_pts) - volume element at source points + re : ndarray + Shape (n_eval_pts, 3). + Evaluation points to evaluate B at, in cartesian. + rs : ndarray + Shape (n_src_pts, 3). + Source points for current density J, in cartesian. + J : ndarray + Shape (n_src_pts, 3). + Current density vector at source points, in cartesian. + dV : ndarray + Shape (n_src_pts, ). + Volume element at source points + chunk_size : int or None + Size to split computation into chunks of evaluation points. + If no chunking should be done or the chunk size is the full input + then supply ``None``. Default is ``None``. Returns ------- - A : ndarray, shape(n,3) - magnetic vector potential in cartesian components at specified points + A : ndarray + Shape(n_eval_pts, 3). + Magnetic vector potential in cartesian components at specified points. + """ re, rs, J, dV = map(jnp.asarray, (re, rs, J, dV)) - assert J.shape == rs.shape - JdV = J * dV[:, None] - A = jnp.zeros_like(re) - - def body(i, A): - r = re - rs[i, :] - num = JdV[i, :] - den = jnp.linalg.norm(r, axis=-1) - A = A + jnp.where(den[:, None] == 0, 0, num / den[:, None]) - return A + JdV = J * dV[:, jnp.newaxis] + assert JdV.shape == rs.shape + + def biot(re): + dr = rs - re + den = jnp.linalg.norm(dr, axis=-1, keepdims=True) + return safediv(JdV, den).sum(axis=-2) * mu_0 / (4 * jnp.pi) - return scipy.constants.mu_0 / 4 / jnp.pi * fori_loop(0, J.shape[0], body, A) + # It is more efficient to sum over the sources in batches of evaluation points. + return batch_map(biot, re[..., jnp.newaxis, :], chunk_size) def read_BNORM_file(fname, surface, eval_grid=None, scale_by_curpol=True): @@ -221,7 +249,13 @@ def __sub__(self, x): @abstractmethod def compute_magnetic_field( - self, coords, params=None, basis="rpz", source_grid=None, transforms=None + self, + coords, + params=None, + basis="rpz", + source_grid=None, + transforms=None, + chunk_size=None, ): """Compute magnetic field at a set of points. @@ -238,6 +272,10 @@ def compute_magnetic_field( Biot-Savart. Should NOT include endpoint at 2pi. transforms : dict of Transform Transforms for R, Z, lambda, etc. Default is to build from source_grid + chunk_size : int or None + Size to split computation into chunks of evaluation points. + If no chunking should be done or the chunk size is the full input + then supply ``None``. Default is ``None``. Returns ------- @@ -246,13 +284,19 @@ def compute_magnetic_field( """ - def __call__(self, grid, params=None, basis="rpz"): + def __call__(self, grid, params=None, basis="rpz", chunk_size=None): """Compute magnetic field at a set of points.""" - return self.compute_magnetic_field(grid, params, basis) + return self.compute_magnetic_field(grid, params, basis, chunk_size=chunk_size) @abstractmethod def compute_magnetic_vector_potential( - self, coords, params=None, basis="rpz", source_grid=None, transforms=None + self, + coords, + params=None, + basis="rpz", + source_grid=None, + transforms=None, + chunk_size=None, ): """Compute magnetic vector potential at a set of points. @@ -269,6 +313,10 @@ def compute_magnetic_vector_potential( Biot-Savart. Should NOT include endpoint at 2pi. transforms : dict of Transform Transforms for R, Z, lambda, etc. Default is to build from source_grid + chunk_size : int or None + Size to split computation into chunks of evaluation points. + If no chunking should be done or the chunk size is the full input + then supply ``None``. Default is ``None``. Returns ------- @@ -285,6 +333,7 @@ def compute_Bnormal( vc_source_grid=None, params=None, basis="rpz", + chunk_size=None, ): """Compute Bnormal from self on the given surface. @@ -300,7 +349,7 @@ def compute_Bnormal( if None defaults to a LinearGrid with twice the surface poloidal and toroidal resolutions points are in surface angular coordinates i.e theta and zeta - source_grid : Grid, int or None + source_grid : Grid or int or None Grid used to discretize MagneticField object if calculating B from Biot-Savart. Should NOT include endpoint at 2pi. vc_source_grid : LinearGrid @@ -315,6 +364,10 @@ def compute_Bnormal( basis : {"rpz", "xyz"} basis for returned coordinates on the surface cylindrical "rpz" by default + chunk_size : int or None + Size to split computation into chunks of evaluation points. + If no chunking should be done or the chunk size is the full input + then supply ``None``. Default is ``None``. Returns ------- @@ -342,9 +395,13 @@ def compute_Bnormal( coords = data["x"] surf_normal = data["n_rho"] B = self.compute_magnetic_field( - coords, basis="rpz", source_grid=source_grid, params=params + coords, + basis="rpz", + source_grid=source_grid, + params=params, + chunk_size=chunk_size, ) - Bnorm = jnp.sum(B * surf_normal, axis=-1) + Bnorm = dot(B, surf_normal) if calc_Bplasma: Bplasma = compute_B_plasma(eq, eval_grid, vc_source_grid, normal_only=True) @@ -485,6 +542,7 @@ def save_mgrid( nZ=101, nphi=90, save_vector_potential=True, + chunk_size=None, ): """Save the magnetic field to an mgrid NetCDF file in "raw" format. @@ -507,8 +565,12 @@ def save_mgrid( nphi : int, optional Number of grid points in the toroidal angle (default = 90). save_vector_potential : bool, optional - Whether or not to save the magnetic vector potential to the mgrid + Whether to save the magnetic vector potential to the mgrid file, in addition to the magnetic field. Defaults to True. + chunk_size : int or None + Size to split computation into chunks of evaluation points. + If no chunking should be done or the chunk size is the full input + then supply ``None``. Default is ``None``. Returns ------- @@ -524,14 +586,16 @@ def save_mgrid( grid = np.array([RR.flatten(), PHI.flatten(), ZZ.flatten()]).T # evaluate magnetic field on grid - field = self.compute_magnetic_field(grid, basis="rpz") + field = self.compute_magnetic_field(grid, basis="rpz", chunk_size=chunk_size) B_R = field[:, 0].reshape(nphi, nZ, nR) B_phi = field[:, 1].reshape(nphi, nZ, nR) B_Z = field[:, 2].reshape(nphi, nZ, nR) # evaluate magnetic vector potential on grid if save_vector_potential: - field = self.compute_magnetic_vector_potential(grid, basis="rpz") + field = self.compute_magnetic_vector_potential( + grid, basis="rpz", chunk_size=chunk_size + ) A_R = field[:, 0].reshape(nphi, nZ, nR) A_phi = field[:, 1].reshape(nphi, nZ, nR) A_Z = field[:, 2].reshape(nphi, nZ, nR) @@ -695,7 +759,12 @@ def params(self, params): self._params = params def compute_magnetic_field( - self, coords, params=None, basis="rpz", source_grid=None + self, + coords, + params=None, + basis="rpz", + source_grid=None, + chunk_size=None, ): """Compute magnetic field at a set of points. @@ -708,7 +777,12 @@ def compute_magnetic_field( basis : {"rpz", "xyz"} Basis for input coordinates and returned magnetic field. source_grid : Grid, int or None or array-like, optional - Unused by this class, only kept for API compatibility + Unused by this class, only kept for API compatibility. + chunk_size : int or None + Unused by this class, only kept for API compatibility. + Size to split computation into chunks of evaluation points. + If no chunking should be done or the chunk size is the full input + then supply ``None``. Default is ``None``. Returns ------- @@ -728,7 +802,12 @@ def compute_magnetic_field( return B def compute_magnetic_vector_potential( - self, coords, params=None, basis="rpz", source_grid=None + self, + coords, + params=None, + basis="rpz", + source_grid=None, + chunk_size=None, ): """Compute magnetic vector potential at a set of points. @@ -741,7 +820,11 @@ def compute_magnetic_vector_potential( basis : {"rpz", "xyz"} Basis for input coordinates and returned magnetic vector potential. source_grid : Grid, int or None or array-like, optional - Unused by this MagneticField class. + Unused by this MagneticField class, only kept for API compatibility. + chunk_size : int or None + Size to split computation into chunks of evaluation points. + If no chunking should be done or the chunk size is the full input + then supply ``None``. Default is ``None``. Returns ------- @@ -810,7 +893,13 @@ def __hasattr__(self, attr): return hasattr(self, attr) or hasattr(self._field, attr) def compute_magnetic_field( - self, coords, params=None, basis="rpz", source_grid=None, transforms=None + self, + coords, + params=None, + basis="rpz", + source_grid=None, + transforms=None, + chunk_size=None, ): """Compute magnetic field at a set of points. @@ -827,7 +916,10 @@ def compute_magnetic_field( Biot-Savart. Should NOT include endpoint at 2pi. transforms : dict of Transform Transforms for R, Z, lambda, etc. Default is to build from source_grid - + chunk_size : int or None + Size to split computation into chunks of evaluation points. + If no chunking should be done or the chunk size is the full input + then supply ``None``. Default is ``None``. Returns ------- @@ -836,11 +928,17 @@ def compute_magnetic_field( """ return self._scale * self._field.compute_magnetic_field( - coords, params, basis, source_grid + coords, params, basis, source_grid, chunk_size=chunk_size ) def compute_magnetic_vector_potential( - self, coords, params=None, basis="rpz", source_grid=None, transforms=None + self, + coords, + params=None, + basis="rpz", + source_grid=None, + transforms=None, + chunk_size=None, ): """Compute magnetic vector potential at a set of points. @@ -857,6 +955,10 @@ def compute_magnetic_vector_potential( Biot-Savart. Should NOT include endpoint at 2pi. transforms : dict of Transform Transforms for R, Z, lambda, etc. Default is to build from source_grid + chunk_size : int or None + Size to split computation into chunks of evaluation points. + If no chunking should be done or the chunk size is the full input + then supply ``None``. Default is ``None``. Returns ------- @@ -865,7 +967,7 @@ def compute_magnetic_vector_potential( """ return self._scale * self._field.compute_magnetic_vector_potential( - coords, params, basis, source_grid + coords, params, basis, source_grid, chunk_size=chunk_size ) @@ -897,6 +999,7 @@ def _compute_A_or_B( source_grid=None, transforms=None, compute_A_or_B="B", + chunk_size=None, ): """Compute magnetic field or vector potential at a set of points. @@ -916,6 +1019,10 @@ def _compute_A_or_B( compute_A_or_B: {"A", "B"}, optional whether to compute the magnetic vector potential "A" or the magnetic field "B". Defaults to "B" + chunk_size : int or None + Size to split computation into chunks of evaluation points. + If no chunking should be done or the chunk size is the full input + then supply ``None``. Default is ``None``. Returns ------- @@ -956,12 +1063,23 @@ def _compute_A_or_B( AB = 0 for i, (field, g, tr) in enumerate(zip(self._fields, source_grid, transforms)): AB += getattr(field, op)( - coords, params[i % len(params)], basis, source_grid=g, transforms=tr + coords, + params[i % len(params)], + basis, + source_grid=g, + transforms=tr, + chunk_size=chunk_size, ) return AB def compute_magnetic_field( - self, coords, params=None, basis="rpz", source_grid=None, transforms=None + self, + coords, + params=None, + basis="rpz", + source_grid=None, + transforms=None, + chunk_size=None, ): """Compute magnetic field at a set of points. @@ -978,6 +1096,10 @@ def compute_magnetic_field( Biot-Savart. Should NOT include endpoint at 2pi. transforms : dict of Transform Transforms for R, Z, lambda, etc. Default is to build from source_grid + chunk_size : int or None + Size to split computation into chunks of evaluation points. + If no chunking should be done or the chunk size is the full input + then supply ``None``. Default is ``None``. Returns ------- @@ -986,11 +1108,23 @@ def compute_magnetic_field( """ return self._compute_A_or_B( - coords, params, basis, source_grid, transforms, compute_A_or_B="B" + coords, + params, + basis, + source_grid, + transforms, + compute_A_or_B="B", + chunk_size=chunk_size, ) def compute_magnetic_vector_potential( - self, coords, params=None, basis="rpz", source_grid=None, transforms=None + self, + coords, + params=None, + basis="rpz", + source_grid=None, + transforms=None, + chunk_size=None, ): """Compute magnetic vector potential at a set of points. @@ -1007,6 +1141,10 @@ def compute_magnetic_vector_potential( Biot-Savart. Should NOT include endpoint at 2pi. transforms : dict of Transform Transforms for R, Z, lambda, etc. Default is to build from source_grid + chunk_size : int or None + Size to split computation into chunks of evaluation points. + If no chunking should be done or the chunk size is the full input + then supply ``None``. Default is ``None``. Returns ------- @@ -1015,7 +1153,13 @@ def compute_magnetic_vector_potential( """ return self._compute_A_or_B( - coords, params, basis, source_grid, transforms, compute_A_or_B="A" + coords, + params, + basis, + source_grid, + transforms, + compute_A_or_B="A", + chunk_size=chunk_size, ) # dunder methods required by MutableSequence @@ -1086,7 +1230,13 @@ def B0(self, new): self._B0 = float(np.squeeze(new)) def compute_magnetic_field( - self, coords, params=None, basis="rpz", source_grid=None, transforms=None + self, + coords, + params=None, + basis="rpz", + source_grid=None, + transforms=None, + chunk_size=None, ): """Compute magnetic field at a set of points. @@ -1102,7 +1252,12 @@ def compute_magnetic_field( Unused by this MagneticField class. transforms : dict of Transform Transforms for R, Z, lambda, etc. Default is to build from source_grid - Unused by this MagneticField class. + Unused by this MagneticField class, only kept for API compatibility. + chunk_size : int or None + Unused by this class, only kept for API compatibility. + Size to split computation into chunks of evaluation points. + If no chunking should be done or the chunk size is the full input + then supply ``None``. Default is ``None``. Returns ------- @@ -1127,7 +1282,13 @@ def compute_magnetic_field( return B def compute_magnetic_vector_potential( - self, coords, params=None, basis="rpz", source_grid=None, transforms=None + self, + coords, + params=None, + basis="rpz", + source_grid=None, + transforms=None, + chunk_size=None, ): """Compute magnetic vector potential at a set of points. @@ -1145,7 +1306,12 @@ def compute_magnetic_vector_potential( Unused by this MagneticField class. transforms : dict of Transform Transforms for R, Z, lambda, etc. Default is to build from source_grid - Unused by this MagneticField class. + Unused by this MagneticField class, only kept for API compatibility. + chunk_size : int or None + Unused by this class, only kept for API compatibility. + Size to split computation into chunks of evaluation points. + If no chunking should be done or the chunk size is the full input + then supply ``None``. Default is ``None``. Returns ------- @@ -1197,7 +1363,13 @@ def B0(self, new): self._B0 = float(np.squeeze(new)) def compute_magnetic_field( - self, coords, params=None, basis="rpz", source_grid=None, transforms=None + self, + coords, + params=None, + basis="rpz", + source_grid=None, + transforms=None, + chunk_size=None, ): """Compute magnetic field at a set of points. @@ -1213,7 +1385,12 @@ def compute_magnetic_field( Unused by this MagneticField class. transforms : dict of Transform Transforms for R, Z, lambda, etc. Default is to build from source_grid - Unused by this MagneticField class. + Unused by this MagneticField class, only kept for API compatibility. + chunk_size : int or None + Unused by this class, only kept for API compatibility. + Size to split computation into chunks of evaluation points. + If no chunking should be done or the chunk size is the full input + then supply ``None``. Default is ``None``. Returns ------- @@ -1230,11 +1407,16 @@ def compute_magnetic_field( B = jnp.array([brp, brp, bz]).T # b/c it only has a nonzero z component, no need # to switch bases back if xyz is given - return B def compute_magnetic_vector_potential( - self, coords, params=None, basis="rpz", source_grid=None, transforms=None + self, + coords, + params=None, + basis="rpz", + source_grid=None, + transforms=None, + chunk_size=None, ): """Compute magnetic vector potential at a set of points. @@ -1252,7 +1434,12 @@ def compute_magnetic_vector_potential( Unused by this MagneticField class. transforms : dict of Transform Transforms for R, Z, lambda, etc. Default is to build from source_grid - Unused by this MagneticField class. + Unused by this MagneticField class, only kept for API compatibility. + chunk_size : int or None + Unused by this class, only kept for API compatibility. + Size to split computation into chunks of evaluation points. + If no chunking should be done or the chunk size is the full input + then supply ``None``. Default is ``None``. Returns ------- @@ -1344,7 +1531,13 @@ def iota(self, new): self._iota = float(np.squeeze(new)) def compute_magnetic_field( - self, coords, params=None, basis="rpz", source_grid=None, transforms=None + self, + coords, + params=None, + basis="rpz", + source_grid=None, + transforms=None, + chunk_size=None, ): """Compute magnetic field at a set of points. @@ -1361,6 +1554,11 @@ def compute_magnetic_field( transforms : dict of Transform Transforms for R, Z, lambda, etc. Default is to build from source_grid Unused by this MagneticField class. + chunk_size : int or None + Unused by this class, only kept for API compatibility. + Size to split computation into chunks of evaluation points. + If no chunking should be done or the chunk size is the full input + then supply ``None``. Default is ``None``. Returns ------- @@ -1392,7 +1590,13 @@ def compute_magnetic_field( return B def compute_magnetic_vector_potential( - self, coords, params=None, basis="rpz", source_grid=None, transforms=None + self, + coords, + params=None, + basis="rpz", + source_grid=None, + transforms=None, + chunk_size=None, ): """Compute magnetic vector potential at a set of points. @@ -1409,6 +1613,10 @@ def compute_magnetic_vector_potential( transforms : dict of Transform Transforms for R, Z, lambda, etc. Default is to build from source_grid Unused by this MagneticField class. + chunk_size : int or None + Size to split computation into chunks of evaluation points. + If no chunking should be done or the chunk size is the full input + then supply ``None``. Default is ``None``. Returns ------- @@ -1590,6 +1798,7 @@ def _compute_A_or_B( source_grid=None, transforms=None, compute_A_or_B="B", + chunk_size=None, ): """Compute magnetic field or magnetic vector potential at a set of points. @@ -1609,6 +1818,11 @@ def _compute_A_or_B( compute_A_or_B: {"A", "B"}, optional whether to compute the magnetic vector potential "A" or the magnetic field "B". Defaults to "B" + chunk_size : int or None + Unused by this class, only kept for API compatibility. + Size to split computation into chunks of evaluation points. + If no chunking should be done or the chunk size is the full input + then supply ``None``. Default is ``None``. Returns ------- @@ -1733,7 +1947,13 @@ def _compute_A_or_B( return AB def compute_magnetic_field( - self, coords, params=None, basis="rpz", source_grid=None, transforms=None + self, + coords, + params=None, + basis="rpz", + source_grid=None, + transforms=None, + chunk_size=None, ): """Compute magnetic field at a set of points. @@ -1750,6 +1970,10 @@ def compute_magnetic_field( transforms : dict of Transform Transforms for R, Z, lambda, etc. Default is to build from source_grid Unused by this MagneticField class. + chunk_size : int or None + Size to split computation into chunks of evaluation points. + If no chunking should be done or the chunk size is the full input + then supply ``None``. Default is ``None``. Returns ------- @@ -1757,10 +1981,18 @@ def compute_magnetic_field( magnetic field at specified points, in cylindrical form [BR, Bphi,BZ] """ - return self._compute_A_or_B(coords, params, basis, source_grid, transforms, "B") + return self._compute_A_or_B( + coords, params, basis, source_grid, transforms, "B", chunk_size=chunk_size + ) def compute_magnetic_vector_potential( - self, coords, params=None, basis="rpz", source_grid=None, transforms=None + self, + coords, + params=None, + basis="rpz", + source_grid=None, + transforms=None, + chunk_size=None, ): """Compute magnetic vector potential at a set of points. @@ -1777,6 +2009,10 @@ def compute_magnetic_vector_potential( transforms : dict of Transform Transforms for R, Z, lambda, etc. Default is to build from source_grid Unused by this MagneticField class. + chunk_size : int or None + Size to split computation into chunks of evaluation points. + If no chunking should be done or the chunk size is the full input + then supply ``None``. Default is ``None``. Returns ------- @@ -1784,7 +2020,9 @@ def compute_magnetic_vector_potential( magnetic vector potential at specified points """ - return self._compute_A_or_B(coords, params, basis, source_grid, transforms, "A") + return self._compute_A_or_B( + coords, params, basis, source_grid, transforms, "A", chunk_size=chunk_size + ) @classmethod def from_mgrid(cls, mgrid_file, extcur=None, method="cubic", extrap=False): @@ -1885,7 +2123,16 @@ def from_mgrid(cls, mgrid_file, extcur=None, method="cubic", extrap=False): @classmethod def from_field( - cls, field, R, phi, Z, params=None, method="cubic", extrap=False, NFP=None + cls, + field, + R, + phi, + Z, + params=None, + method="cubic", + extrap=False, + NFP=None, + chunk_size=None, ): """Create a splined magnetic field from another field for faster evaluation. @@ -1904,18 +2151,24 @@ def from_field( whether to extrapolate splines beyond specified R,phi,Z NFP : int, optional Number of toroidal field periods. If not provided, will default to 1 or - the provided field's NFP, if it has that attribute. + the provided field's NFP, if it has that attribute. + chunk_size : int or None + Size to split computation into chunks of evaluation points. + If no chunking should be done or the chunk size is the full input + then supply ``None``. Default is ``None``. """ R, phi, Z = map(np.asarray, (R, phi, Z)) rr, pp, zz = np.meshgrid(R, phi, Z, indexing="ij") shp = rr.shape coords = np.array([rr.flatten(), pp.flatten(), zz.flatten()]).T - BR, BP, BZ = field.compute_magnetic_field(coords, params, basis="rpz").T + BR, BP, BZ = field.compute_magnetic_field( + coords, params, basis="rpz", chunk_size=chunk_size + ).T NFP = getattr(field, "_NFP", 1) try: AR, AP, AZ = field.compute_magnetic_vector_potential( - coords, params, basis="rpz" + coords, params, basis="rpz", chunk_size=chunk_size ).T AR = AR.reshape(shp) AP = AP.reshape(shp) @@ -1968,7 +2221,13 @@ def NFP(self): return self._NFP def compute_magnetic_field( - self, coords, params=None, basis="rpz", source_grid=None, transforms=None + self, + coords, + params=None, + basis="rpz", + source_grid=None, + transforms=None, + chunk_size=None, ): """Compute magnetic field at a set of points. @@ -1985,6 +2244,11 @@ def compute_magnetic_field( transforms : dict of Transform Transforms for R, Z, lambda, etc. Default is to build from source_grid Unused by this MagneticField class. + chunk_size : int or None + Unused by this class, only kept for API compatibility. + Size to split computation into chunks of evaluation points. + If no chunking should be done or the chunk size is the full input + then supply ``None``. Default is ``None``. Returns ------- @@ -2013,7 +2277,13 @@ def compute_magnetic_field( return B def compute_magnetic_vector_potential( - self, coords, params=None, basis="rpz", source_grid=None, transforms=None + self, + coords, + params=None, + basis="rpz", + source_grid=None, + transforms=None, + chunk_size=None, ): """Compute magnetic vector potential at a set of points. @@ -2030,6 +2300,10 @@ def compute_magnetic_vector_potential( transforms : dict of Transform Transforms for R, Z, lambda, etc. Default is to build from source_grid Unused by this MagneticField class. + chunk_size : int or None + Size to split computation into chunks of evaluation points. + If no chunking should be done or the chunk size is the full input + then supply ``None``. Default is ``None``. Returns ------- @@ -2079,6 +2353,7 @@ def _compute_A_or_B( source_grid=None, transforms=None, compute_A_or_B="B", + chunk_size=None, ): """Compute magnetic field or vector potential at a set of points. @@ -2098,6 +2373,11 @@ def _compute_A_or_B( compute_A_or_B: {"A", "B"}, optional whether to compute the magnetic vector potential "A" or the magnetic field "B". Defaults to "B" + chunk_size : int or None + Unused by this class, only kept for API compatibility. + Size to split computation into chunks of evaluation points. + If no chunking should be done or the chunk size is the full input + then supply ``None``. Default is ``None``. Returns ------- @@ -2150,7 +2430,13 @@ def _compute_A_or_B( return A def compute_magnetic_field( - self, coords, params=None, basis="rpz", source_grid=None, transforms=None + self, + coords, + params=None, + basis="rpz", + source_grid=None, + transforms=None, + chunk_size=None, ): """Compute magnetic field at a set of points. @@ -2167,6 +2453,10 @@ def compute_magnetic_field( transforms : dict of Transform Transforms for R, Z, lambda, etc. Default is to build from source_grid Unused by this MagneticField class. + chunk_size : int or None + Size to split computation into chunks of evaluation points. + If no chunking should be done or the chunk size is the full input + then supply ``None``. Default is ``None``. Returns ------- @@ -2174,10 +2464,18 @@ def compute_magnetic_field( magnetic field at specified points """ - return self._compute_A_or_B(coords, params, basis, source_grid, transforms, "B") + return self._compute_A_or_B( + coords, params, basis, source_grid, transforms, "B", chunk_size=chunk_size + ) def compute_magnetic_vector_potential( - self, coords, params=None, basis="rpz", source_grid=None, transforms=None + self, + coords, + params=None, + basis="rpz", + source_grid=None, + transforms=None, + chunk_size=None, ): """Compute magnetic vector potential at a set of points. @@ -2194,6 +2492,10 @@ def compute_magnetic_vector_potential( transforms : dict of Transform Transforms for R, Z, lambda, etc. Default is to build from source_grid Unused by this MagneticField class. + chunk_size : int or None + Size to split computation into chunks of evaluation points. + If no chunking should be done or the chunk size is the full input + then supply ``None``. Default is ``None``. Returns ------- @@ -2201,7 +2503,9 @@ def compute_magnetic_vector_potential( magnetic vector potential at specified points """ - return self._compute_A_or_B(coords, params, basis, source_grid, transforms, "A") + return self._compute_A_or_B( + coords, params, basis, source_grid, transforms, "A", chunk_size=chunk_size + ) def field_line_integrate( @@ -2218,6 +2522,7 @@ def field_line_integrate( solver=Tsit5(), bounds_R=(0, np.inf), bounds_Z=(-np.inf, np.inf), + chunk_size=None, **kwargs, ): """Trace field lines by integration, using diffrax package. @@ -2253,6 +2558,10 @@ def field_line_integrate( Z bounds for field line integration bounding box. Trajectories that leave this box will be stopped, and NaN returned for points outside the box. Defaults to (-np.inf,np.inf) + chunk_size : int or None + Size to split computation into chunks of evaluation points. + If no chunking should be done or the chunk size is the full input + then supply ``None``. Default is ``None``. kwargs: dict keyword arguments to be passed into the ``diffrax.diffeqsolve`` @@ -2274,7 +2583,7 @@ def odefun(s, rpz, args): rpz = rpz.reshape((3, -1)).T r = rpz[:, 0] br, bp, bz = field.compute_magnetic_field( - rpz, params, basis="rpz", source_grid=source_grid + rpz, params, basis="rpz", source_grid=source_grid, chunk_size=chunk_size ).T return jnp.array( [r * br / bp * jnp.sign(bp), jnp.sign(bp), r * bz / bp * jnp.sign(bp)] @@ -2548,7 +2857,7 @@ def compute( Returns ------- - data : dict of ndarray + data : dict[str, jnp.ndarray] Computed quantity and intermediate variables. """ diff --git a/desc/magnetic_fields/_current_potential.py b/desc/magnetic_fields/_current_potential.py index 5ba1320138..ecb77b77ed 100644 --- a/desc/magnetic_fields/_current_potential.py +++ b/desc/magnetic_fields/_current_potential.py @@ -7,7 +7,7 @@ import skimage.measure from scipy.constants import mu_0 -from desc.backend import cho_factor, cho_solve, fori_loop, jnp +from desc.backend import cho_factor, cho_solve, jnp from desc.basis import DoubleFourierSeries from desc.compute import rpz2xyz, rpz2xyz_vec, xyz2rpz_vec from desc.compute.utils import _compute as compute_fun @@ -27,6 +27,7 @@ warnif, ) +from ..integrals.quad_utils import nfp_loop from ._core import ( _MagneticField, biot_savart_general, @@ -41,7 +42,8 @@ class CurrentPotentialField(_MagneticField, FourierRZToroidalSurface): where: - n is the winding surface unit normal. - - Phi is the current potential function, which is a function of theta and zeta. + - Φ is the current potential function, which is a function of theta and zeta. + - ∇Φ dot n is assumed to be zero. This function then uses biot-savart to find the B field from this current density K on the surface. @@ -207,6 +209,7 @@ def _compute_A_or_B( source_grid=None, transforms=None, compute_A_or_B="B", + chunk_size=None, ): """Compute magnetic field or vector potential at a set of points. @@ -225,6 +228,10 @@ def _compute_A_or_B( compute_A_or_B: {"A", "B"}, optional whether to compute the magnetic vector potential "A" or the magnetic field "B". Defaults to "B" + chunk_size : int or None + Size to split computation into chunks of evaluation points. + If no chunking should be done or the chunk size is the full input + then supply ``None``. Default is ``None``. Returns ------- @@ -245,10 +252,17 @@ def _compute_A_or_B( source_grid=source_grid, transforms=transforms, compute_A_or_B=compute_A_or_B, + chunk_size=chunk_size, ) def compute_magnetic_field( - self, coords, params=None, basis="rpz", source_grid=None, transforms=None + self, + coords, + params=None, + basis="rpz", + source_grid=None, + transforms=None, + chunk_size=None, ): """Compute magnetic field at a set of points. @@ -264,6 +278,10 @@ def compute_magnetic_field( Source grid upon which to evaluate the surface current density K. transforms : dict of Transform Transforms for R, Z, lambda, etc. Default is to build from source_grid + chunk_size : int or None + Size to split computation into chunks of evaluation points. + If no chunking should be done or the chunk size is the full input + then supply ``None``. Default is ``None``. Returns ------- @@ -271,10 +289,18 @@ def compute_magnetic_field( magnetic field at specified points """ - return self._compute_A_or_B(coords, params, basis, source_grid, transforms, "B") + return self._compute_A_or_B( + coords, params, basis, source_grid, transforms, "B", chunk_size=chunk_size + ) def compute_magnetic_vector_potential( - self, coords, params=None, basis="rpz", source_grid=None, transforms=None + self, + coords, + params=None, + basis="rpz", + source_grid=None, + transforms=None, + chunk_size=None, ): """Compute magnetic vector potential at a set of points. @@ -292,6 +318,10 @@ def compute_magnetic_vector_potential( Source grid upon which to evaluate the surface current density K. transforms : dict of Transform Transforms for R, Z, lambda, etc. Default is to build from source_grid + chunk_size : int or None + Size to split computation into chunks of evaluation points. + If no chunking should be done or the chunk size is the full input + then supply ``None``. Default is ``None``. Returns ------- @@ -299,7 +329,9 @@ def compute_magnetic_vector_potential( Magnetic vector potential at specified points. """ - return self._compute_A_or_B(coords, params, basis, source_grid, transforms, "A") + return self._compute_A_or_B( + coords, params, basis, source_grid, transforms, "A", chunk_size=chunk_size + ) @classmethod def from_surface( @@ -372,9 +404,10 @@ class FourierCurrentPotentialField(_MagneticField, FourierRZToroidalSurface): where: - n is the winding surface unit normal. - - Phi is the current potential function, which is a function of theta and zeta, + - Φ is the current potential function, which is a function of theta and zeta, and is given as a secular linear term in theta/zeta and a double Fourier series in theta/zeta. + - ∇Φ dot n is assumed to be zero. This class then uses biot-savart to find the B field from this current density K on the surface. @@ -589,6 +622,7 @@ def _compute_A_or_B( source_grid=None, transforms=None, compute_A_or_B="B", + chunk_size=None, ): """Compute magnetic field or vector potential at a set of points. @@ -607,6 +641,10 @@ def _compute_A_or_B( compute_A_or_B: {"A", "B"}, optional whether to compute the magnetic vector potential "A" or the magnetic field "B". Defaults to "B" + chunk_size : int or None + Size to split computation into chunks of evaluation points. + If no chunking should be done or the chunk size is the full input + then supply ``None``. Default is ``None``. Returns ------- @@ -627,10 +665,17 @@ def _compute_A_or_B( source_grid=source_grid, transforms=transforms, compute_A_or_B=compute_A_or_B, + chunk_size=chunk_size, ) def compute_magnetic_field( - self, coords, params=None, basis="rpz", source_grid=None, transforms=None + self, + coords, + params=None, + basis="rpz", + source_grid=None, + transforms=None, + chunk_size=None, ): """Compute magnetic field at a set of points. @@ -646,6 +691,10 @@ def compute_magnetic_field( Source grid upon which to evaluate the surface current density K. transforms : dict of Transform Transforms for R, Z, lambda, etc. Default is to build from source_grid + chunk_size : int or None + Size to split computation into chunks of evaluation points. + If no chunking should be done or the chunk size is the full input + then supply ``None``. Default is ``None``. Returns ------- @@ -653,10 +702,18 @@ def compute_magnetic_field( magnetic field at specified points """ - return self._compute_A_or_B(coords, params, basis, source_grid, transforms, "B") + return self._compute_A_or_B( + coords, params, basis, source_grid, transforms, "B", chunk_size=chunk_size + ) def compute_magnetic_vector_potential( - self, coords, params=None, basis="rpz", source_grid=None, transforms=None + self, + coords, + params=None, + basis="rpz", + source_grid=None, + transforms=None, + chunk_size=None, ): """Compute magnetic vector potential at a set of points. @@ -674,6 +731,10 @@ def compute_magnetic_vector_potential( Source grid upon which to evaluate the surface current density K. transforms : dict of Transform Transforms for R, Z, lambda, etc. Default is to build from source_grid + chunk_size : int or None + Size to split computation into chunks of evaluation points. + If no chunking should be done or the chunk size is the full input + then supply ``None``. Default is ``None``. Returns ------- @@ -681,7 +742,9 @@ def compute_magnetic_vector_potential( Magnetic vector potential at specified points. """ - return self._compute_A_or_B(coords, params, basis, source_grid, transforms, "A") + return self._compute_A_or_B( + coords, params, basis, source_grid, transforms, "A", chunk_size=chunk_size + ) @classmethod def from_surface( @@ -779,10 +842,13 @@ def to_CoilSet( # noqa: C901 - FIXME: simplify this Φ(θ,ζ) = Φₛᵥ(θ,ζ) + Gζ/2π + Iθ/2π - where n is the winding surface unit normal, Φ is the current potential - function, which is a function of theta and zeta, and is given as a - secular linear term in theta (I) and zeta (G) and a double Fourier - series in theta/zeta. + where: + + - n is the winding surface unit normal. + - Φ is the current potential function, which is a function of theta and zeta, + and is given as a secular linear term in theta (I) and zeta (G) and a double + Fourier series in theta/zeta. + - ∇Φ dot n is assumed to be zero. NOTE: The function is not jit/AD compatible @@ -948,6 +1014,7 @@ def _compute_A_or_B_from_CurrentPotentialField( basis="rpz", transforms=None, compute_A_or_B="B", + chunk_size=None, ): """Compute magnetic field or vector potential at a set of points. @@ -967,7 +1034,10 @@ def _compute_A_or_B_from_CurrentPotentialField( compute_A_or_B: {"A", "B"}, optional whether to compute the magnetic vector potential "A" or the magnetic field "B". Defaults to "B" - + chunk_size : int or None + Size to split computation into chunks of evaluation points. + If no chunking should be done or the chunk size is the full input + then supply ``None``. Default is ``None``. Returns ------- @@ -1007,35 +1077,23 @@ def _compute_A_or_B_from_CurrentPotentialField( profiles={}, ) - _rs = data["x"] - _K = data["K"] - + R = data["x"][:, 0] + Z = data["x"][:, 2] # surface element, must divide by NFP to remove the NFP multiple on the # surface grid weights, as we account for that when doing the for loop # over NFP - _dV = source_grid.weights * data["|e_theta x e_zeta|"] / source_grid.NFP + dV = source_grid.weights * data["|e_theta x e_zeta|"] / source_grid.NFP - def nfp_loop(j, f): - # calculate (by rotating) rs, rs_t, rz_t - phi = (source_grid.nodes[:, 2] + j * 2 * jnp.pi / source_grid.NFP) % ( - 2 * jnp.pi - ) - # new coords are just old R,Z at a new phi (bc of discrete NFP symmetry) - rs = jnp.vstack((_rs[:, 0], phi, _rs[:, 2])).T - rs = rpz2xyz(rs) - K = rpz2xyz_vec(_K, phi=phi) - fj = op( - coords, - rs, - K, - _dV, - ) - f += fj - return f + def func(zeta_j): + rs = rpz2xyz(jnp.column_stack([R, zeta_j, Z])) + K = rpz2xyz_vec(data["K"], phi=zeta_j) + return op(coords, rs, K, dV, chunk_size=chunk_size) - B = fori_loop(0, source_grid.NFP, nfp_loop, jnp.zeros_like(coords)) + B = nfp_loop(source_grid, func, jnp.zeros_like(coords)) if basis == "rpz": B = xyz2rpz_vec(B, x=coords[:, 0], y=coords[:, 1]) + else: + assert basis == "xyz" return B @@ -1052,6 +1110,7 @@ def solve_regularized_surface_current( # noqa: C901 fxn too complex external_field=None, external_field_grid=None, verbose=1, + chunk_size=None, ): """Runs REGCOIL-like algorithm to find the current potential for the surface. @@ -1159,6 +1218,10 @@ def solve_regularized_surface_current( # noqa: C901 fxn too complex 1 will display Bn max,min,average and chi^2 values for each lambda_regularization. 2 will display jacobian timing info + chunk_size : int or None + Size to split computation into chunks of evaluation points. + If no chunking should be done or the chunk size is the full input + then supply ``None``. Default is ``None``. Returns ------- @@ -1295,7 +1358,9 @@ def solve_regularized_surface_current( # noqa: C901 fxn too complex G_tot = -(eq.compute("G", grid=source_grid)["G"][0] / mu_0 * 2 * jnp.pi) if external_field: - G_ext = _G_from_external_field(external_field, eq, external_field_grid) + G_ext = _G_from_external_field( + external_field, eq, external_field_grid, chunk_size=chunk_size + ) else: G_ext = 0 @@ -1320,7 +1385,11 @@ def Bn_from_K(phi_mn, I, G): params["I"] = I params["G"] = G Bn, _ = current_potential_field.compute_Bnormal( - eq.surface, eval_grid=eval_grid, source_grid=source_grid, params=params + eq.surface, + eval_grid=eval_grid, + source_grid=source_grid, + params=params, + chunk_size=chunk_size, ) return Bn @@ -1411,7 +1480,10 @@ def surfint_Bnsqd(phi_mn, I, G): # find external field's Bnormal contribution if external_field: Bn_ext, _ = external_field.compute_Bnormal( - eq.surface, eval_grid=eval_grid, source_grid=external_field_grid + eq.surface, + eval_grid=eval_grid, + source_grid=external_field_grid, + chunk_size=chunk_size, ) Bn_ext = Bn_ext * ne_mag * eval_grid.weights @@ -1546,7 +1618,7 @@ def _find_current_potential_contours( net_poloidal_current=None, net_toroidal_current=None, helicity=None, - plot_kwargs={}, + plot_kwargs=None, ): """Find contours of constant current potential (i.e. coils). @@ -1562,6 +1634,7 @@ def _find_current_potential_contours( - Φ is the current potential function, which is a function of theta and zeta, and is given as a secular linear term in theta (I) and zeta (G) and a double Fourier series in theta/zeta. + - ∇Φ dot n is assumed to be zero. Parameters ---------- @@ -1605,6 +1678,8 @@ def _find_current_potential_contours( # we know that I = -(G - G_ext) / (helicity * NFP) # if net_toroidal_current is zero, then we have modular coils, # and just make helicity zero + if plot_kwargs is None: + plot_kwargs = {} net_poloidal_current = setdefault( net_poloidal_current, surface_current_field.G, net_poloidal_current ) @@ -1890,7 +1965,7 @@ def _find_XYZ_points( return contour_X, contour_Y, contour_Z -def _G_from_external_field(external_field, eq, external_field_grid): +def _G_from_external_field(external_field, eq, external_field_grid, chunk_size=None): # calculate the portion of G provided by external field # by integrating external toroidal field along a curve of constant theta try: @@ -1916,7 +1991,10 @@ def _G_from_external_field(external_field, eq, external_field_grid): (curve_data["R"], curve_data["phi"], curve_data["Z"]) ).T ext_field_along_curve = external_field.compute_magnetic_field( - curve_coords, basis="rpz", source_grid=external_field_grid + curve_coords, + basis="rpz", + source_grid=external_field_grid, + chunk_size=chunk_size, ) # calculate covariant B_zeta = B dot e_zeta from external field ext_field_B_zeta = dot(ext_field_along_curve, curve_data["e_zeta"], axis=-1) diff --git a/desc/magnetic_fields/_dommaschk.py b/desc/magnetic_fields/_dommaschk.py index d270debe1e..e4efeec7da 100644 --- a/desc/magnetic_fields/_dommaschk.py +++ b/desc/magnetic_fields/_dommaschk.py @@ -161,7 +161,15 @@ def _set_potentials(self): @classmethod def fit_magnetic_field( # noqa: C901 - cls, field, coords, max_m, max_l, sym=False, verbose=1, NFP=1 + cls, + field, + coords, + max_m, + max_l, + sym=False, + verbose=1, + NFP=1, + chunk_size=None, ): """Fit a vacuum magnetic field with a Dommaschk Potential field. @@ -185,6 +193,11 @@ def fit_magnetic_field( # noqa: C901 that are integer multiples of NFP. verbose (int): verbosity level of fitting routine, > 0 prints residuals, >1 prints timing info + chunk_size : int or None + Size to split computation into chunks of evaluation points. + If no chunking should be done or the chunk size is the full input + then supply ``None``. Default is ``None``. + """ # We seek c in Ac = b # A will be the BR, Bphi and BZ from each individual @@ -193,7 +206,7 @@ def fit_magnetic_field( # noqa: C901 # c will be [B0, a_00, a_10, a_01, a_11... etc] # b is the magnetic field at each node which we are fitting if isinstance(field, _MagneticField): - B = field.compute_magnetic_field(coords) + B = field.compute_magnetic_field(coords, chunk_size=chunk_size) elif callable(field): B = field(coords) else: # it must be the field evaluated at the passed-in coords @@ -312,6 +325,7 @@ def get_B_dom(coords, X): * abcd_zero_due_to_sym_inds[3], "B0": X[0], }, + chunk_size=chunk_size, ) X = [] diff --git a/desc/objectives/_coils.py b/desc/objectives/_coils.py index 1a8051a529..67aee34058 100644 --- a/desc/objectives/_coils.py +++ b/desc/objectives/_coils.py @@ -1259,6 +1259,10 @@ class QuadraticFlux(_Objective): vacuum : bool If true, B_plasma (the contribution to the normal field on the boundary from the plasma currents) is set to zero. + bs_chunk_size : int or None + Size to split Biot-Savart computation into chunks of evaluation points. + If no chunking should be done or the chunk size is the full input + then supply ``None``. """ @@ -1288,6 +1292,9 @@ def __init__( vacuum=False, name="Quadratic flux", jac_chunk_size=None, + *, + bs_chunk_size=10, + **kwargs, ): from desc.geometry import FourierRZToroidalSurface @@ -1299,6 +1306,7 @@ def __init__( self._field = field self._field_grid = field_grid self._vacuum = vacuum + self._bs_chunk_size = bs_chunk_size errorif( isinstance(eq, FourierRZToroidalSurface), TypeError, @@ -1425,6 +1433,7 @@ def compute(self, field_params, constants=None): source_grid=constants["field_grid"], basis="rpz", params=field_params, + chunk_size=self._bs_chunk_size, ) B_ext = jnp.sum(B_ext * eval_data["n_rho"], axis=-1) f = (B_ext + B_plasma) * jnp.sqrt(eval_data["|e_theta x e_zeta|"]) @@ -1463,6 +1472,10 @@ class SurfaceQuadraticFlux(_Objective): the docs of that object's ``compute_magnetic_field`` method for more detail. field_fixed : bool Whether or not to fix the magnetic field's DOFs during the optimization. + bs_chunk_size : int or None + Size to split Biot-Savart computation into chunks of evaluation points. + If no chunking should be done or the chunk size is the full input + then supply ``None``. """ @@ -1491,6 +1504,9 @@ def __init__( name="Surface Quadratic Flux", field_fixed=False, jac_chunk_size=None, + *, + bs_chunk_size=10, + **kwargs, ): if target is None and bounds is None: target = 0 @@ -1499,6 +1515,7 @@ def __init__( self._field = field self._field_grid = field_grid self._field_fixed = field_fixed + self._bs_chunk_size = bs_chunk_size things = [surface] if not field_fixed: @@ -1621,6 +1638,7 @@ def compute(self, params_1, params_2=None, constants=None): source_grid=constants["field_grid"], basis="rpz", params=field_params, + chunk_size=self._bs_chunk_size, ) B_ext = jnp.sum(B_ext * eval_data["n_rho"], axis=-1) f = B_ext * jnp.sqrt(eval_data["|e_theta x e_zeta|"]) @@ -1660,10 +1678,14 @@ class ToroidalFlux(_Objective): plasma geometry at. Defaults to a LinearGrid(L=eq.L_grid, M=eq.M_grid, zeta=jnp.array(0.0), NFP=eq.NFP). field_fixed : bool - Whether or not to fix the field's DOFs during the optimization. + Whether to fix the field's DOFs during the optimization. eq_fixed : bool - Whether or not to fix the equilibrium (or QFM surface) DOFs + Whether to fix the equilibrium (or QFM surface) DOFs during the optimization. + bs_chunk_size : int or None + Size to split Biot-Savart computation into chunks of evaluation points. + If no chunking should be done or the chunk size is the full input + then supply ``None``. """ @@ -1700,6 +1722,9 @@ def __init__( field_fixed=False, eq_fixed=False, jac_chunk_size=None, + *, + bs_chunk_size=10, + **kwargs, ): if target is None and bounds is None: target = 1.0 if not hasattr(eq, "Psi") else eq.Psi @@ -1709,6 +1734,7 @@ def __init__( self._eq = eq self._field_fixed = field_fixed self._eq_fixed = eq_fixed + self._bs_chunk_size = bs_chunk_size errorif( eq_fixed and field_fixed, ValueError, @@ -1748,7 +1774,9 @@ def build(self, use_jit=True, verbose=1): eq = self._eq self._use_vector_potential = True try: - self._field.compute_magnetic_vector_potential([0, 0, 0]) + self._field.compute_magnetic_vector_potential( + [0, 0, 0], chunk_size=self._bs_chunk_size + ) except (NotImplementedError, ValueError) as e: self._use_vector_potential = False errorif( @@ -1883,6 +1911,7 @@ def compute(self, params_1, params_2=None, constants=None): basis="rpz", source_grid=constants["field_grid"], params=field_params, + chunk_size=self._bs_chunk_size, ) A_dot_e_theta = jnp.sum(A * data["e_theta"], axis=1) @@ -1893,6 +1922,7 @@ def compute(self, params_1, params_2=None, constants=None): basis="rpz", source_grid=constants["field_grid"], params=field_params, + chunk_size=self._bs_chunk_size, ) B_dot_n_zeta = jnp.sum(B * data["n_zeta"], axis=1) @@ -2373,12 +2403,7 @@ def build(self, use_jit=True, verbose=1): ) else: # it does not have I,G bc is CurrentPotentialField Phi = surface_current_field.compute("Phi", grid=source_grid)["Phi"] - self._normalization = np.max( - [ - np.mean(np.abs(Phi)), - 1, - ] - ) + self._normalization = np.max([np.mean(np.abs(Phi)), 1]) self._constants = { "surface_transforms": surface_transforms, diff --git a/desc/objectives/_free_boundary.py b/desc/objectives/_free_boundary.py index d83895a092..b52ff2cd48 100644 --- a/desc/objectives/_free_boundary.py +++ b/desc/objectives/_free_boundary.py @@ -19,7 +19,7 @@ warnif, ) -from ..integrals.singularities import _get_default_params +from ..integrals.singularities import best_ratio, heuristic_support_params from .normalization import compute_scaling_factors @@ -354,10 +354,14 @@ class BoundaryError(_Objective): Equilibrium that will be optimized to satisfy the Objective. field : MagneticField External field produced by coils. - s : int + s : int or tuple[int] Hyperparameter for the singular integration scheme. ``s`` is roughly equal to the size of the local singular grid with - respect to the global grid. + respect to the global grid. More precisely the local singular grid + is an ``st`` × ``sz`` subset of the full domain (θ,ζ) ∈ [0, 2π)² of + ``source_grid``. That is a subset of + ``source_grid.num_theta`` × ``source_grid.num_zeta*source_grid.NFP``. + If given an integer then ``st=s``, ``sz=s``, otherwise ``st=s[0]``, ``sz=s[1]``. q : int Order of integration on the local singular grid. source_grid, eval_grid : Grid, optional @@ -431,9 +435,12 @@ def __init__( target = 0 self._source_grid = source_grid self._eval_grid = eval_grid - self._eval_grid_is_source_grid = source_grid == eval_grid - self._st = s - self._sz = kwargs.get("sz", s) + if isinstance(s, (tuple, list)): + self._st = s[0] + self._sz = s[1] + else: + self._st = s + self._sz = s self._q = q self._field = field self._field_grid = field_grid @@ -492,7 +499,7 @@ def build(self, use_jit=True, verbose=1): else: eval_grid = self._eval_grid - self._eval_grid_is_source_grid = eval_grid.equiv(source_grid) + self._use_same_grid = eval_grid.equiv(source_grid) errorif( not np.all(source_grid.nodes[:, 0] == 1.0), @@ -509,10 +516,15 @@ def build(self, use_jit=True, verbose=1): ValueError, "Source grids for singular integrals must be non-symmetric", ) - st, sz, q = _get_default_params(source_grid) - self._st = setdefault(self._st, st) - self._sz = setdefault(self._sz, sz) - self._q = setdefault(self._q, q) + + if self._st is None or self._sz is None or self._q is None: + ratio_data = eq.compute( + ["|e_theta x e_zeta|", "e_theta", "e_zeta"], grid=source_grid + ) + st, sz, q = heuristic_support_params(source_grid, best_ratio(ratio_data)[0]) + self._st = setdefault(self._st, st) + self._sz = setdefault(self._sz, sz) + self._q = setdefault(self._q, q) try: interpolator = FFTInterpolator( @@ -556,7 +568,7 @@ def build(self, use_jit=True, verbose=1): source_profiles = get_profiles(self._eq_data_keys, obj=eq, grid=source_grid) source_transforms = get_transforms(self._eq_data_keys, obj=eq, grid=source_grid) - if self._eval_grid_is_source_grid: + if self._use_same_grid: eval_profiles = source_profiles eval_transforms = source_transforms else: @@ -582,7 +594,7 @@ def build(self, use_jit=True, verbose=1): ) self._constants["sheet_eval_transforms"] = ( self._constants["sheet_source_transforms"] - if self._eval_grid_is_source_grid + if self._use_same_grid else get_transforms( self._sheet_data_keys, obj=eq.surface, grid=eval_grid ) @@ -643,7 +655,7 @@ def compute(self, eq_params, field_params=None, constants=None): ) eval_data = ( source_data - if self._eval_grid_is_source_grid + if self._use_same_grid else compute_fun( "desc.equilibrium.equilibrium.Equilibrium", self._eq_data_keys, @@ -670,7 +682,7 @@ def compute(self, eq_params, field_params=None, constants=None): ) sheet_eval_data = ( sheet_source_data - if self._eval_grid_is_source_grid + if self._use_same_grid else compute_fun( p, self._sheet_data_keys, diff --git a/desc/transform.py b/desc/transform.py index 1660fe5e6e..3d4822e138 100644 --- a/desc/transform.py +++ b/desc/transform.py @@ -410,6 +410,7 @@ def transform(self, c, dr=0, dt=0, dz=0): cc = A @ c_mtrx return (cc @ B.T).flatten(order="F") + # TODO: Use real fft (jnp.irfft and jnp.fft) elif self.method == "fft": A = self.matrices["fft"].get(dr, {}).get(dt, {}) if isinstance(A, dict): diff --git a/desc/utils.py b/desc/utils.py index f6a2c3f8c6..24fa44fa35 100644 --- a/desc/utils.py +++ b/desc/utils.py @@ -892,7 +892,7 @@ def cross(a, b, axis=-1): return jnp.cross(a, b, axis=axis) -def safenorm(x, ord=None, axis=None, fill=0, threshold=0): +def safenorm(x, ord=None, axis=None, fill=0, threshold=0, keepdims=False): """Like jnp.linalg.norm, but without nan gradient at x=0. Parameters @@ -911,12 +911,14 @@ def safenorm(x, ord=None, axis=None, fill=0, threshold=0): """ is_zero = (jnp.abs(x) <= threshold).all(axis=axis, keepdims=True) y = jnp.where(is_zero, jnp.ones_like(x), x) # replace x with ones if is_zero - n = jnp.linalg.norm(y, ord=ord, axis=axis) - n = jnp.where(is_zero.squeeze(), fill, n) # replace norm with zero if is_zero + if not keepdims: + is_zero = is_zero.squeeze(axis=axis) + n = jnp.linalg.norm(y, ord=ord, axis=axis, keepdims=keepdims) + n = jnp.where(is_zero, fill, n) # replace norm with zero if is_zero return n -def safenormalize(x, ord=None, axis=None, fill=0, threshold=0): +def safenormalize(x, ord=None, axis=None, fill=0, threshold=0, keepdims=False): """Normalize a vector to unit length, but without nan gradient at x=0. Parameters @@ -935,7 +937,7 @@ def safenormalize(x, ord=None, axis=None, fill=0, threshold=0): """ is_zero = (jnp.abs(x) <= threshold).all(axis=axis, keepdims=True) y = jnp.where(is_zero, jnp.ones_like(x), x) # replace x with ones if is_zero - n = safenorm(x, ord, axis, fill, threshold) * jnp.ones_like(x) + n = safenorm(x, ord, axis, fill, threshold, keepdims) * jnp.ones_like(x) # return unit vector with equal components if norm <= threshold return jnp.where(n <= threshold, jnp.ones_like(y) / jnp.sqrt(y.size), y / n) diff --git a/docs/notebooks/tutorials/coil_optimization_REGCOIL.ipynb b/docs/notebooks/tutorials/coil_optimization_REGCOIL.ipynb index dced55c381..5f017edc15 100644 --- a/docs/notebooks/tutorials/coil_optimization_REGCOIL.ipynb +++ b/docs/notebooks/tutorials/coil_optimization_REGCOIL.ipynb @@ -879,6 +879,7 @@ " # lambda_regularization can also be just a single number in which case no scan is performed\n", " vacuum=True, # this is a vacuum equilibrium, so no need to calculate the Bn contribution from the plasma currents\n", " regularization_type=\"regcoil\",\n", + " chunk_size=40,\n", ")\n", "surface_current_field = fields[\n", " 0\n", @@ -1249,6 +1250,7 @@ " # there are two different regularizations available, \"simple\" penalizes the Phi_mn current potential components only,\n", " # which is cheaper than the \"regcoil\" regularization, with the tradeoff that chi^2_K is not guaranteed to monotonically decrease\n", " regularization_type=\"simple\",\n", + " chunk_size=40,\n", ")\n", "surface_current_field_helical = fields_helical[0]\n", "plot_regcoil_outputs(surface_current_field_helical, data_helical, eq, vacuum=True);" @@ -11983,7 +11985,7 @@ 0.009337773911075792, -0.007412988041618385, 0.0042529007378760975, - -0.00008824485091241119, + -8.824485091241119e-05, -0.004621881585104598, 0.007585576634221491, -0.007745746453738683, @@ -12013,7 +12015,7 @@ 0.009337773911075958, -0.007412988041618551, 0.004252900737876236, - -0.00008824485091218914, + -8.824485091218914e-05, -0.004621881585104792, 0.007585576634221464, -0.007745746453738517, @@ -12299,7 +12301,7 @@ 0.0016318161009278853, -0.004419017158051264, 0.003325814291292639, - -0.00006556134584650986, + -6.556134584650986e-05, -0.002807551587817453, 0.004570379652521439, -0.0037866230894404995, @@ -12329,7 +12331,7 @@ 0.0016318161009279408, -0.004419017158051319, 0.003325814291292639, - -0.0000655613458464821, + -6.55613458464821e-05, -0.002807551587817564, 0.0045703796525213974, -0.0037866230894404995, @@ -12370,7 +12372,7 @@ 0.004824124235799282, -0.005660476628259692, 0.003926951152056379, - 0.00009564281431989619, + 9.564281431989619e-05, -0.00301793863086372, 0.005805625604342363, -0.007052851120649621, @@ -12400,7 +12402,7 @@ 0.00482412423579931, -0.005660476628259609, 0.003926951152056435, - 0.00009564281431989619, + 9.564281431989619e-05, -0.0030179386308640255, 0.005805625604342224, -0.007052851120649739, @@ -12539,7 +12541,7 @@ 0.0009903985996350673, 0.0035555786851441673, -0.0041549676085843035, - 0.000018304785378231436, + 1.8304785378231436e-05, 0.0030613289249409315, -0.0028605145389600795, -0.0001993734225027577, @@ -12569,7 +12571,7 @@ 0.0009903985996351783, 0.003555578685144112, -0.0041549676085845255, - 0.000018304785377787347, + 1.8304785377787347e-05, 0.0030613289249409315, -0.0028605145389600795, -0.00019937342250270218, @@ -12975,7 +12977,7 @@ -0.0021598972929629534, 0.0009697438078062337, 0.001311564543183097, - -0.000018713592086661013, + -1.8713592086661013e-05, 0.00035029590155888357, 0.0003850027945963841, -0.001142187628043495, @@ -13005,7 +13007,7 @@ -0.002159897292962787, 0.0009697438078065113, 0.0013115645431828749, - -0.000018713592086661013, + -1.8713592086661013e-05, 0.00035029590155902235, 0.00038500279459610653, -0.0011421876280433563, @@ -13022,7 +13024,7 @@ -0.0013933540462206517, 0.0013248718107975455, 0.0007274548014176707, - 0.000004770005622545259, + 4.770005622545259e-06, -0.0033838946018846794, 0.0034294420899701056, 0.00394108890217193, @@ -13052,7 +13054,7 @@ -0.0013933540462207072, 0.001324871810797601, 0.0007274548014183924, - 0.000004770005622989348, + 4.770005622989348e-06, -0.0033838946018842075, 0.003429442089970494, 0.003941088902172013, @@ -13156,7 +13158,7 @@ 0.0011409475925795998, 0.0019431514027244412, -0.0023374787840945155, - -0.00005961170825741213, + -5.961170825741213e-05, 0.001700785353970613, -0.0004949816847232835, 0.0006833721750099664, @@ -13186,7 +13188,7 @@ 0.0011409475925795443, 0.0019431514027244412, -0.00233747878409446, - -0.000059611708257523155, + -5.9611708257523155e-05, 0.001700785353970391, -0.0004949816847228949, 0.0006833721750100774, @@ -13334,7 +13336,7 @@ -0.00268599142388537, -0.0017684418044425082, 0.0029101486273307997, - 0.000043340046089801465, + 4.3340046089801465e-05, -0.0031978561149422213, 0.0007792824491131833, 0.0023266511661613754, @@ -13364,7 +13366,7 @@ -0.0026859914238848426, -0.0017684418044427337, 0.0029101486273307997, - 0.000043340046090356577, + 4.3340046090356577e-05, -0.003197856114942166, 0.0007792824491131278, 0.0023266511661612643, @@ -13516,7 +13518,7 @@ 0.0016950048717270239, 0.0003598666958673502, -0.0016589849460970085, - -0.000003584765406225432, + -3.584765406225432e-06, -0.0010250177347306405, -0.0022262654705416107, 0.0013462923600428556, @@ -13546,7 +13548,7 @@ 0.0016950048717269683, 0.0003598666958679053, -0.001658984946097286, - -0.0000035847654063225765, + -3.5847654063225765e-06, -0.0010250177347302242, -0.0022262654705416662, 0.0013462923600428556, @@ -13773,8 +13775,8 @@ 0.0014654219094153431, -0.0007365024320623026, 0.00021338306738871715, - -0.000042166869548387886, - 0.00008679140771192251, + -4.2166869548387886e-05, + 8.679140771192251e-05, -0.00013692642262458365, 0.0002470464623721552, -0.00034174301240517783, @@ -13803,8 +13805,8 @@ 0.001465421909415121, -0.0007365024320619695, 0.00021338306738871715, - -0.00004216686954855442, - 0.00008679140771167271, + -4.216686954855442e-05, + 8.679140771167271e-05, -0.00013692642262444488, 0.0002470464623722385, -0.00034174301240550396, @@ -13833,10 +13835,10 @@ -0.0020372981017019454, 0.001405975995470654, -0.0006551540409375778, - 0.00007242525476131023, - 0.0000964136179217312, - -0.00005466277516591456, - 0.0000015287338522851357, + 7.242525476131023e-05, + 9.64136179217312e-05, + -5.466277516591456e-05, + 1.5287338522851357e-06, 0.00010190549601594989, -0.00019240802895414272, 0.00030555010008247174, @@ -13863,10 +13865,10 @@ -0.0020372981017021674, 0.0014059759954700435, -0.0006551540409372447, - 0.00007242525476153228, - 0.00009641361792200875, - -0.0000546627751666362, - 0.0000015287338522296245, + 7.242525476153228e-05, + 9.641361792200875e-05, + -5.46627751666362e-05, + 1.5287338522296245e-06, 0.00010190549601626908, -0.0001924080289543023, 0.0003055501000826383, @@ -13894,12 +13896,12 @@ -0.0022662052051796167, 0.0013362339041765314, -0.0005692002145960551, - -0.0000743766193224138, + -7.43766193224138e-05, 0.00023608239626471406, -0.00019324807066661842, 0.00013769692952078638, - -0.0000481693576541431, - -0.00004988440220300011, + -4.81693576541431e-05, + -4.988440220300011e-05, 0.00015587469444694468, -0.0002294141870648747, 0.0003354894666457997, @@ -13924,15 +13926,15 @@ -0.0022662052051800052, 0.0013362339041762539, -0.000569200214595722, - -0.0000743766193224693, + -7.43766193224693e-05, 0.00023608239626454752, -0.00019324807066672944, 0.00013769692952103618, - -0.00004816935765375452, - -0.00004988440220334012, + -4.816935765375452e-05, + -4.988440220334012e-05, 0.00015587469444708, 1.9113718054930007e-16, - -0.00008910024518982151, + -8.910024518982151e-05, 0.0001961047316331177, -0.00027210634544130863, 0.00032509026429899945, @@ -13960,9 +13962,9 @@ -0.00032509026429844434, 0.0002721063454415029, -0.00019610473163278463, - 0.00008910024519083459, + 8.910024519083459e-05, 5.823903232455784e-17, - -0.00008910024519048765, + -8.910024519048765e-05, 0.000196104731633423, -0.00027210634544136414, 0.0003250902642988329, @@ -13990,15 +13992,15 @@ -0.0003250902642986664, 0.00027210634544155843, -0.00019610473163250708, - 0.00008910024519033499, + 8.910024519033499e-05, 1.9113718054930007e-16, -0.0001558746944467504, - 0.00004988440220376339, - 0.00004816935765436514, + 4.988440220376339e-05, + 4.816935765436514e-05, -0.00013769692952061985, 0.00019324807066734007, -0.000236082396264492, - 0.00007437661932252482, + 7.437661932252482e-05, 0.0005692002145958885, -0.001336233904176476, 0.002266205205179561, @@ -14023,12 +14025,12 @@ -0.00033548946664541113, 0.00022941418706526329, -0.0001558746944468406, - 0.00004988440220343726, - 0.00004816935765486474, + 4.988440220343726e-05, + 4.816935765486474e-05, -0.0001376969295208974, 0.0001932480706669515, -0.0002360823962639924, - 0.00007437661932269135, + 7.437661932269135e-05, 0.000569200214595944, -0.0013362339041761984, 0.002266205205179561, @@ -14056,10 +14058,10 @@ -0.00030555010008229827, 0.00019240802895487824, -0.00010190549601568621, - -0.00000152873385200758, - 0.000054662775166358646, - -0.0000964136179217312, - -0.00007242525476147676, + -1.52873385200758e-06, + 5.4662775166358646e-05, + -9.64136179217312e-05, + -7.242525476147676e-05, 0.0006551540409372447, -0.0014059759954703765, 0.002037298101702112, @@ -14086,10 +14088,10 @@ -0.0003055501000823746, 0.00019240802895454517, -0.00010190549601542254, - -0.000001528733852201869, - 0.00005466277516619211, - -0.00009641361792134262, - -0.00007242525476147676, + -1.528733852201869e-06, + 5.466277516619211e-05, + -9.641361792134262e-05, + -7.242525476147676e-05, 0.0006551540409374113, -0.0014059759954702655, 0.002037298101702223, @@ -14118,8 +14120,8 @@ 0.00034174301240617183, -0.00024704646237200256, 0.0001369264226246114, - -0.00008679140771125637, - 0.00004216686954860993, + -8.679140771125637e-05, + 4.216686954860993e-05, -0.00021338306738905022, 0.0007365024320619695, -0.001465421909415121, @@ -14148,8 +14150,8 @@ 0.0003417430124056219, -0.0002470464623715307, 0.00013692642262394528, - -0.00008679140771172822, - 0.00004216686954888749, + -8.679140771172822e-05, + 4.216686954888749e-05, -0.0002133830673889947, 0.000736502432061803, -0.001465421909415232, @@ -14376,7 +14378,7 @@ -0.0013462923600431886, 0.0022262654705408336, 0.0010250177347300854, - 0.0000035847654058784872, + 3.5847654058784872e-06, 0.00165898494609662, -0.00035986669586762776, -0.001695004871727246, @@ -14406,7 +14408,7 @@ -0.001346292360043022, 0.0022262654705412777, 0.0010250177347300021, - 0.000003584765405642565, + 3.584765405642565e-06, 0.0016589849460968975, -0.00035986669586768327, -0.0016950048717274124, @@ -14558,7 +14560,7 @@ -0.002326651166161209, -0.0007792824491132389, 0.0031978561149418328, - -0.0000433400460904676, + -4.33400460904676e-05, -0.0029101486273311605, 0.0017684418044417276, 0.002685991423885037, @@ -14588,7 +14590,7 @@ -0.0023266511661612643, -0.0007792824491131833, 0.0031978561149422213, - -0.00004334004609041209, + -4.334004609041209e-05, -0.0029101486273314103, 0.001768441804442373, 0.0026859914238847593, @@ -14736,7 +14738,7 @@ -0.0006833721750101884, 0.0004949816847227839, -0.0017007853539704465, - 0.000059611708257523155, + 5.9611708257523155e-05, 0.0023374787840946265, -0.0019431514027242747, -0.0011409475925797663, @@ -14766,7 +14768,7 @@ -0.0006833721750099664, 0.0004949816847227839, -0.001700785353970058, - 0.000059611708257467644, + 5.9611708257467644e-05, 0.002337478784094682, -0.0019431514027246077, -0.0011409475925795443, @@ -14870,7 +14872,7 @@ -0.00394108890217193, -0.0034294420899702444, 0.003383894601884513, - -0.000004770005622878326, + -4.770005622878326e-06, -0.0007274548014175597, -0.001324871810797379, 0.0013933540462211513, @@ -14900,7 +14902,7 @@ -0.003941088902172235, -0.0034294420899701333, 0.0033838946018843186, - -0.000004770005622822815, + -4.770005622822815e-06, -0.0007274548014179483, -0.0013248718107975455, 0.0013933540462206517, @@ -14917,7 +14919,7 @@ 0.001142187628043717, -0.00038500279459621756, -0.00035029590155882806, - 0.00001871359208654999, + 1.871359208654999e-05, -0.0013115645431829859, -0.0009697438078062337, 0.0021598972929627314, @@ -14947,7 +14949,7 @@ 0.0011421876280434673, -0.00038500279459660613, -0.0003502959015586615, - 0.0000187135920866055, + 1.87135920866055e-05, -0.0013115645431827638, -0.0009697438078060672, 0.002159897292962676, @@ -15353,7 +15355,7 @@ 0.0001993734225027577, 0.002860514538960135, -0.0030613289249409315, - -0.00001830478537834246, + -1.830478537834246e-05, 0.00415496760858447, -0.003555578685144223, -0.0009903985996354003, @@ -15383,7 +15385,7 @@ 0.00019937342250270218, 0.0028605145389601905, -0.0030613289249407094, - -0.000018304785378120414, + -1.8304785378120414e-05, 0.004154967608584359, -0.0035555786851441673, -0.0009903985996355114, @@ -15522,7 +15524,7 @@ 0.007052851120649975, -0.005805625604342418, 0.003017938630863498, - -0.00009564281431961863, + -9.564281431961863e-05, -0.003926951152056435, 0.005660476628259803, -0.004824124235799254, @@ -15552,7 +15554,7 @@ 0.007052851120649267, -0.005805625604342057, 0.0030179386308635536, - -0.00009564281431978516, + -9.564281431978516e-05, -0.00392695115205649, 0.005660476628259636, -0.0048241242357995595, @@ -15593,7 +15595,7 @@ 0.003786623089440777, -0.004570379652521148, 0.0028075515878178414, - 0.00006556134584659312, + 6.556134584659312e-05, -0.0033258142912923616, 0.004419017158051292, -0.001631816100927802, @@ -15623,7 +15625,7 @@ 0.0037866230894406452, -0.004570379652521411, 0.0028075515878173973, - 0.0000655613458467319, + 6.55613458467319e-05, -0.003325814291292306, 0.004419017158051347, -0.0016318161009277188, @@ -15909,7 +15911,7 @@ 0.007745746453738683, -0.007585576634221575, 0.004621881585104348, - 0.00008824485091210588, + 8.824485091210588e-05, -0.004252900737876236, 0.007412988041618301, -0.009337773911076166, @@ -15939,7 +15941,7 @@ 0.0077457464537386, -0.0075855766342216024, 0.0046218815851043205, - 0.00008824485091232792, + 8.824485091232792e-05, -0.004252900737876264, 0.007412988041618301, -0.009337773911076222, @@ -55224,7 +55226,7 @@ 0.005846612627715287, 0.0039169751287624855, 0.001986246526758322, - 0.000054981809316651924, + 5.4981809316651924e-05, -0.0018762435460255927, -0.0038068599351731595, -0.005736331497657328, @@ -77040,7 +77042,7 @@ -0.005846612627715287, -0.0039169751287624855, -0.001986246526758322, - -0.000054981809316651924, + -5.4981809316651924e-05, 0.0018762435460255927, 0.0038068599351731595, 0.005736331497657328, @@ -84312,7 +84314,7 @@ 0.005846612627715287, 0.0039169751287624855, 0.001986246526758322, - 0.000054981809316651924, + 5.4981809316651924e-05, -0.0018762435460255927, -0.0038068599351731595, -0.005736331497657328, @@ -106128,7 +106130,7 @@ -0.005846612627715287, -0.0039169751287624855, -0.001986246526758322, - -0.000054981809316651924, + -5.4981809316651924e-05, 0.0018762435460255927, 0.0038068599351731595, 0.005736331497657328, @@ -119863,7 +119865,7 @@ 4879 ], "intensity": [ - 0.00002388304808366239, + 2.388304808366239e-05, -0.0017761315205484828, 0.001702929559231063, -0.00307223482780385, @@ -119878,7 +119880,7 @@ -0.0014177750654665354, 0.0013110389262552884, -0.0008932715852702762, - -0.000014450715197664777, + -1.4450715197664777e-05, 0.0008640317279309425, -0.001339138761492504, 0.0013968270690436471, @@ -119886,21 +119888,21 @@ 0.0010327932617344947, -0.0013123809690656601, 0.00159446816645886, - -0.00005090502303914206, + -5.090502303914206e-05, 0.0006413483239790802, 0.0013617559707380226, -0.001074198091439508, 0.00303849843840996, -0.00174823463278538, 0.0018136898860299268, - 0.000011737583498852127, + 1.1737583498852127e-05, -0.0017836612516448705, 0.0017270726599753227, -0.003051101765181599, 0.0010693262352114926, -0.0013898888098120296, -0.0006423949765085202, - 0.00006959032301338765, + 6.959032301338765e-05, -0.0015927485565331648, 0.0012767186196644621, -0.0010648766293410079, @@ -119908,7 +119910,7 @@ -0.0014161052675511726, 0.001320780314203919, -0.0008806543426718917, - 0.000014151496991282207, + 1.4151496991282207e-05, 0.0008977214622479503, -0.001310006526306834, 0.0014295358089593915, @@ -119916,14 +119918,14 @@ 0.0010758799957768603, -0.0012736453178153263, 0.001596437801779805, - -0.00009010970756998199, + -9.010970756998199e-05, 0.0006384823137891815, 0.0014077962176553302, -0.0010851464304130096, 0.0030975010252521553, -0.0016913442293175285, 0.0018265665707021816, - 0.00002388304808366239, + 2.388304808366239e-05, -0.0001988471505034593, -0.0020730038677023834, 0.001487029766664073, @@ -119992,7 +119994,7 @@ 0.0011862156734126629, -0.0013435883243124451, 0.00021543530078761092, - 0.000047506161670717126, + 4.7506161670717126e-05, -0.00054752910201798, 0.0003826626120233228, -0.0005615530111643415, @@ -120022,7 +120024,7 @@ 0.001180412804216884, -0.001356817019207178, 0.000220159640299229, - 0.00003775579823169958, + 3.775579823169958e-05, -0.0005503655389559219, 0.0003989816329794505, -0.0005586729214163671, @@ -120054,10 +120056,10 @@ -0.00147806667008295, 0.0005150911386141299, -0.0002223502510044154, - -0.00007389373592453079, - 0.000055166945028584284, + -7.389373592453079e-05, + 5.5166945028584284e-05, -0.00011715331834233404, - 0.000017057498890136298, + 1.7057498890136298e-05, 0.00022865187561557906, -0.00041147875216094454, 0.0009661926797896529, @@ -120084,10 +120086,10 @@ -0.0014921435678796815, 0.0005235216946603528, -0.00022060435661549896, - -0.00006781360850224708, - 0.0000750478810822018, + -6.781360850224708e-05, + 7.50478810822018e-05, -0.0001150462802416886, - 0.00002271175062464891, + 2.271175062464891e-05, 0.0002363665547723604, -0.00040482777789838093, 0.000969467905575709, @@ -120197,7 +120199,7 @@ 0.00260718714580155, 0.004178063558643933, -0.0018368955299749704, - 0.000048791270688551503, + 4.8791270688551503e-05, -0.0009935361117351595, -0.0031003324948417907, 0.0009013838933836389, @@ -120227,7 +120229,7 @@ 0.0026092532447344104, 0.004177738160788209, -0.0018049256116561545, - 0.0000542864034662673, + 5.42864034662673e-05, -0.0009477630441044661, -0.001306868292316305, -0.0033749906466961307, @@ -120483,11 +120485,11 @@ -0.0008780507224644157, 0.0008083539862737857, -0.0001478238412778705, - -0.00008020931562591835, + -8.020931562591835e-05, -0.00014828434300073813, 0.00014005807511152218, -0.00012497620028246428, - 0.00007732041427853053, + 7.732041427853053e-05, -0.00020217506625069148, -0.00021695745721343805, -0.0005944049353047498, @@ -120513,11 +120515,11 @@ -0.0008418972546086949, 0.0008081393891694016, -0.0001610752934135621, - -0.00007645841145065909, + -7.645841145065909e-05, -0.0001458361964627474, 0.00014214502674113067, -0.0001318625875702465, - 0.00007996348881244337, + 7.996348881244337e-05, -0.00019862725004947862, -0.00022121894675439555, -0.0006254396384740647, @@ -120545,11 +120547,11 @@ 0.0005942273484409144, 0.00012810125944342354, -0.0002857868263274994, - -0.00000167992385703461, - 0.0000019785327415370645, - 0.000005286998764311089, - -0.00005329626816825892, - -0.00003142534230934442, + -1.67992385703461e-06, + 1.9785327415370645e-06, + 5.286998764311089e-06, + -5.329626816825892e-05, + -3.142534230934442e-05, -0.0003290605674024592, -0.00045536624508177737, -0.0016927831977879856, @@ -120575,11 +120577,11 @@ 0.0005818414811318104, 0.00011893561879010021, -0.0002803170503168725, - -2.8797560391548692e-8, - 0.0000028651527859420872, - -0.0000016390522084122573, - -0.00004756051262450731, - -0.000026540314662958477, + -2.8797560391548692e-08, + 2.8651527859420872e-06, + -1.6390522084122573e-06, + -4.756051262450731e-05, + -2.6540314662958477e-05, -0.00033683942192716917, -0.0004893471374227377, -0.0017059491624104835, @@ -120606,11 +120608,11 @@ 0.000387207881668037, 0.0003030642942798208, -0.00037920320640567635, - 0.00003593178458424274, - -0.000032090899182896624, - 0.000039150249514480735, - -0.00008907097989830888, - 0.000033902269069694925, + 3.593178458424274e-05, + -3.2090899182896624e-05, + 3.9150249514480735e-05, + -8.907097989830888e-05, + 3.3902269069694925e-05, -0.00029733304809087405, -0.0003039811778443012, -0.0017206746035307798, @@ -120636,11 +120638,11 @@ 0.0003652073644541041, 0.00029867697199260776, -0.0003732940452115404, - 0.000037149397678212795, - -0.00003251521593900862, - 0.00003336836520123001, - -0.00008057549069086319, - 0.00003819190765063473, + 3.7149397678212795e-05, + -3.251521593900862e-05, + 3.336836520123001e-05, + -8.057549069086319e-05, + 3.819190765063473e-05, -0.00030920687346899496, -0.00033789872125089256, -0.0017320709238722776, @@ -120663,15 +120665,15 @@ 0.008559128421540933, 0.003431456847441396, 0.0007166532915580546, - 0.000055533421243393, + 5.5533421243393e-05, 0.0002491922872828767, 0.00034886739414701016, -0.00037373441944213117, - -0.000011887269630508292, - -0.000004042858221395344, - 0.000018412646601810495, - -0.0000774356115870632, - 0.00003505918878508986, + -1.1887269630508292e-05, + -4.042858221395344e-06, + 1.8412646601810495e-05, + -7.74356115870632e-05, + 3.505918878508986e-05, -0.00021263632061283664, -0.00018235172307346176, -0.0013462688109900022, @@ -120693,15 +120695,15 @@ 0.00854455839569084, 0.003445434572457225, 0.0007327305871471577, - 0.00004939144053457445, + 4.939144053457445e-05, 0.0002242907154478411, 0.0003496900145903144, -0.0003686766334313914, - -0.000011056393071189906, - -0.0000056156641147980155, - 0.000014594240288728943, - -0.00006756483201543698, - 0.000036686065913211095, + -1.1056393071189906e-05, + -5.6156641147980155e-06, + 1.4594240288728943e-05, + -6.756483201543698e-05, + 3.6686065913211095e-05, -0.00022806949691977309, -0.00021383977244432905, -0.00135651154790418, @@ -120728,13 +120730,13 @@ 0.0002386841015565904, 0.00029887601019976026, -0.00031859775606919927, - -0.00009004765117544343, - 0.000024750443094627228, - -0.000004479672080636988, - -0.00006659529709778329, - 0.000019378055058744913, + -9.004765117544343e-05, + 2.4750443094627228e-05, + -4.479672080636988e-06, + -6.659529709778329e-05, + 1.9378055058744913e-05, -0.00014465096371461787, - -0.00009905587215601908, + -9.905587215601908e-05, -0.000901426802747273, -0.0014804773990022314, -0.004511347403761401, @@ -120758,11 +120760,11 @@ 0.00021807876008544412, 0.00030438649760877186, -0.00031569731600757057, - -0.00008992666683013018, - 0.00002234694183793845, - -0.000006056506381376312, - -0.00005722725090817015, - 0.00001700937752181407, + -8.992666683013018e-05, + 2.234694183793845e-05, + -6.056506381376312e-06, + -5.722725090817015e-05, + 1.700937752181407e-05, -0.00016202870540327332, -0.00012640017776777108, -0.0009115839416094326, @@ -120790,12 +120792,12 @@ 0.00021575591510348847, -0.0002507128995917352, -0.0001651156357357057, - 0.0000337606686126287, - -0.000023142348003262007, - -0.00005153916390847497, - -0.000012551891837363768, - -0.00009255584023224032, - -0.0000735785857150711, + 3.37606686126287e-05, + -2.3142348003262007e-05, + -5.153916390847497e-05, + -1.2551891837363768e-05, + -9.255584023224032e-05, + -7.35785857150711e-05, -0.0005295032861011714, -0.0008560940397736984, -0.0031052150293308304, @@ -120820,12 +120822,12 @@ 0.00022385004516138444, -0.0002512242057265442, -0.000166087008370277, - 0.00003088770056193938, - -0.000022832490846560938, - -0.00004439415296781806, - -0.000019012318173676127, + 3.088770056193938e-05, + -2.2832490846560938e-05, + -4.439415296781806e-05, + -1.9012318173676127e-05, -0.00010972262317989825, - -0.00009589478118921682, + -9.589478118921682e-05, -0.0005405182110513121, -0.0009105212582731944, -0.0031290505735658085, @@ -120851,12 +120853,12 @@ 0.00018816019787133215, -0.0002013134096300229, -0.00020430595089410472, - 0.00000326389029473947, - -0.000017362201153483204, - -0.000050168166479110755, - -0.000026128095440427647, - -0.00007983122380206611, - -0.00006302704323638044, + 3.26389029473947e-06, + -1.7362201153483204e-05, + -5.0168166479110755e-05, + -2.6128095440427647e-05, + -7.983122380206611e-05, + -6.302704323638044e-05, -0.00030038731033761346, -0.00036995685570036284, -0.0016681469860047376, @@ -120881,12 +120883,12 @@ 0.0001957681348952862, -0.0002059876579530412, -0.00020642264980982557, - 2.5212282324998736e-7, - -0.000016025842978864477, - -0.00004634767770280655, - -0.000035617129651619886, - -0.0000947905298149553, - -0.00008034744499802501, + 2.5212282324998736e-07, + -1.6025842978864477e-05, + -4.634767770280655e-05, + -3.5617129651619886e-05, + -9.47905298149553e-05, + -8.034744499802501e-05, -0.0003128408550718383, -0.0004194908889955458, -0.001683715855571484, @@ -120912,14 +120914,14 @@ 0.00028624281414652936, -0.000167044565937035, -0.00021154081259675645, - -0.00004295628244689542, - -0.000010256002902060765, - -0.00003661827316562442, - -0.00003541106904587133, - -0.0000738925053756354, - -0.00007104480872296482, + -4.295628244689542e-05, + -1.0256002902060765e-05, + -3.661827316562442e-05, + -3.541106904587133e-05, + -7.38925053756354e-05, + -7.104480872296482e-05, -0.00017293459283059632, - -0.00008216049171189601, + -8.216049171189601e-05, -0.0005881059562844658, -0.001583087983383733, -0.005835616570479339, @@ -120942,12 +120944,12 @@ 0.00029073990067144436, -0.00017557645895830245, -0.00021436257723119525, - -0.00004590934287526285, - -0.000008972190003669199, - -0.00003643405788792965, - -0.00004623414018456002, - -0.00008537024781310174, - -0.00008416438557813912, + -4.590934287526285e-05, + -8.972190003669199e-06, + -3.643405788792965e-05, + -4.623414018456002e-05, + -8.537024781310174e-05, + -8.416438557813912e-05, -0.00018697392602499762, -0.0001264207875136195, -0.0005974276827262083, @@ -120973,16 +120975,16 @@ 0.0004923850443454963, -0.00010783833024362264, -0.00020650496578904063, - -0.00008388047446609322, - -0.000009787875964639874, - -0.000013347649838084554, - -0.000030059135171122692, - -0.00007036764124712555, - -0.0000773733560619128, + -8.388047446609322e-05, + -9.787875964639874e-06, + -1.3347649838084554e-05, + -3.0059135171122692e-05, + -7.036764124712555e-05, + -7.73733560619128e-05, -0.00011506125078449074, - 0.00004795817889564227, - 0.00005411162197798363, - 0.00003362290065399829, + 4.795817889564227e-05, + 5.411162197798363e-05, + 3.362290065399829e-05, -0.0025334452283774416, -0.007612855339906634, -0.013803745148853528, @@ -121003,16 +121005,16 @@ 0.0004925873150459087, -0.00011879188169350161, -0.00020928274778743278, - -0.00008681668225219558, - -0.000009555913348766802, - -0.0000164222440430565, - -0.0000405430010221539, - -0.00007804853494319672, - -0.00008749274976973772, + -8.681668225219558e-05, + -9.555913348766802e-06, + -1.64222440430565e-05, + -4.05430010221539e-05, + -7.804853494319672e-05, + -8.749274976973772e-05, -0.00013040928856610453, - 0.000009194424020279331, - 0.00004923475236905144, - 0.000022279935309572245, + 9.194424020279331e-06, + 4.923475236905144e-05, + 2.2279935309572245e-05, -0.002547126118146682, -0.007647539867550419, -0.013760584201281349, @@ -121032,16 +121034,16 @@ 0.003093815439975334, 0.0020216962151911513, 0.0007434444470718355, - -0.000011507406712127854, + -1.1507406712127854e-05, -0.00018901493482214015, -0.00011592485956206833, - -0.000018201327098928365, - 0.000013652548859037122, - -0.000012165550409661157, - -0.00005865722441069625, - -0.00007893103089256176, - -0.00008935895275197114, - 0.00007487103372702449, + -1.8201327098928365e-05, + 1.3652548859037122e-05, + -1.2165550409661157e-05, + -5.865722441069625e-05, + -7.893103089256176e-05, + -8.935895275197114e-05, + 7.487103372702449e-05, 0.0003267298995867552, 0.0009823522260893214, 0.00018238829895064334, @@ -121062,16 +121064,16 @@ 0.003107710979983933, 0.0020370376584890842, 0.0007396654011916982, - -0.00002284307627854271, + -2.284307627854271e-05, -0.0001910774273500372, -0.00011913519042079363, - -0.00001971711178203977, - 0.000008117352108963951, - -0.000021143906093880533, - -0.00006306523862883596, - -0.0000872459579588601, + -1.971711178203977e-05, + 8.117352108963951e-06, + -2.1143906093880533e-05, + -6.306523862883596e-05, + -8.72459579588601e-05, -0.00010539752218769393, - 0.00004167462819669386, + 4.167462819669386e-05, 0.0003248474913056641, 0.0009752210006990401, 0.0001728443418142682, @@ -121096,13 +121098,13 @@ 0.00011707917176970037, -0.00015994082515264545, -0.000139951487061869, - -0.00003510939656032417, - 0.000031436283405681385, - 0.000015746790545230272, - -0.00003950252007367873, - -0.00007259782564413891, - -0.00007803401423814809, - 0.00005567593237360676, + -3.510939656032417e-05, + 3.1436283405681385e-05, + 1.5746790545230272e-05, + -3.950252007367873e-05, + -7.259782564413891e-05, + -7.803401423814809e-05, + 5.567593237360676e-05, 0.00034788405933511437, 0.001306329469516566, 0.0018773872736124076, @@ -121126,13 +121128,13 @@ 0.00010730969923825642, -0.0001609843913515452, -0.0001438402645156481, - -0.0000386434131230405, - 0.000024360644930137327, - 0.000008746183672665314, - -0.00004164991029587654, - -0.00007999082999968838, - -0.00009395905912148985, - 0.000027926731684321614, + -3.86434131230405e-05, + 2.4360644930137327e-05, + 8.746183672665314e-06, + -4.164991029587654e-05, + -7.999082999968838e-05, + -9.395905912148985e-05, + 2.7926731684321614e-05, 0.0003478931336045297, 0.0013018471734351795, 0.0018701417952735758, @@ -121157,13 +121159,13 @@ 0.00023514086973303651, -0.00011731565540328703, -0.00015906190003280122, - -0.00006198566133966684, - 0.000033297965642139224, - 0.00004189325140804752, - -0.000013651405727084859, - -0.00006299412174582185, - -0.0000722424402261841, - 0.000024623240771571244, + -6.198566133966684e-05, + 3.3297965642139224e-05, + 4.189325140804752e-05, + -1.3651405727084859e-05, + -6.299412174582185e-05, + -7.22424402261841e-05, + 2.4623240771571244e-05, 0.00024995453698330774, 0.0011752674372576366, 0.002510197930811892, @@ -121173,7 +121175,7 @@ -0.020271397577435443, -0.026694412934665707, -0.02008794445583365, - 0.00005159732512685991, + 5.159732512685991e-05, 0.027816730736540708, 0.040568247298403005, 0.020116026095999065, @@ -121187,13 +121189,13 @@ 0.00022837547517033077, -0.00011742045693558455, -0.00016393002089568022, - -0.00006737437961396588, - 0.000025496218014724676, - 0.00003673048345243903, - -0.000014644114919417614, - -0.00006991450092630187, - -0.00008725651734897902, - 0.000001993867535563426, + -6.737437961396588e-05, + 2.5496218014724676e-05, + 3.673048345243903e-05, + -1.4644114919417614e-05, + -6.991450092630187e-05, + -8.725651734897902e-05, + 1.993867535563426e-06, 0.000251066404146204, 0.0011723269132856181, 0.00250396796774406, @@ -121216,15 +121218,15 @@ 0.0007226438732261638, 0.0007649013225702963, 0.00029015621951788625, - -0.0000682371221387837, + -6.82371221387837e-05, -0.00017463828727539614, - -0.00009705216767723915, - 0.000014841777061468593, - 0.00005768927143987418, - 0.00001278095722131958, - -0.000053406475762559764, - -0.00007227526020588354, - -0.000002858151041174961, + -9.705216767723915e-05, + 1.4841777061468593e-05, + 5.768927143987418e-05, + 1.278095722131958e-05, + -5.3406475762559764e-05, + -7.227526020588354e-05, + -2.858151041174961e-06, 0.00013282444783703634, 0.0008260057858447193, 0.0023011283067571674, @@ -121246,15 +121248,15 @@ 0.0007375825485748372, 0.0007582744099144212, 0.0002872594787898775, - -0.00006768098921594183, + -6.768098921594183e-05, -0.0001805140058447167, -0.00010379857064412073, - 0.000006902647849162909, - 0.0000538449707579701, - 0.000012033950744245469, - -0.00005992967276434704, - -0.00008576008108219467, - -0.000020869892424988637, + 6.902647849162909e-06, + 5.38449707579701e-05, + 1.2033950744245469e-05, + -5.992967276434704e-05, + -8.576008108219467e-05, + -2.0869892424988637e-05, 0.0001345353196904752, 0.000823909153041592, 0.002295132345775215, @@ -121277,16 +121279,16 @@ -0.0005491825644042905, 0.0004172248352674596, 0.0002583509944687079, - -0.00003098513117213574, + -3.098513117213574e-05, -0.0001838279078128564, -0.0001380081664688115, - -0.000020022806935623822, - 0.0000567968320171132, - 0.000034575418606254, - -0.000045018355811489764, - -0.00007970596708378048, - -0.000025825006051087058, - 0.00004747541970671956, + -2.0022806935623822e-05, + 5.67968320171132e-05, + 3.4575418606254e-05, + -4.5018355811489764e-05, + -7.970596708378048e-05, + -2.5825006051087058e-05, + 4.747541970671956e-05, 0.00047016464556391127, 0.0016500669024988846, 0.0035168481570471265, @@ -121307,16 +121309,16 @@ -0.0005362501129366139, 0.0004131872961761691, 0.0002597277568877057, - -0.000030039696819271434, + -3.0039696819271434e-05, -0.00019043257772860365, -0.00014545177916674631, - -0.0000277564570358145, - 0.00005363126509478967, - 0.00003349239275898004, - -0.00005100948715272597, - -0.00009132609960348265, - -0.0000398215606226759, - 0.000049534959210054054, + -2.77564570358145e-05, + 5.363126509478967e-05, + 3.349239275898004e-05, + -5.100948715272597e-05, + -9.132609960348265e-05, + -3.98215606226759e-05, + 4.9534959210054054e-05, 0.0004685236543057042, 0.0016438892803936913, 0.0035025847340272283, @@ -121338,16 +121340,16 @@ -0.0023901607450418413, -0.00012986308760731672, 0.0001561682657298936, - -0.000018839012668251905, + -1.8839012668251905e-05, -0.00018489883590504567, -0.00017811504004652053, - -0.00006502231130846917, - 0.00004026019138689209, - 0.000048366793783671197, - -0.00003700732702237127, - -0.00009125027198184678, - -0.000048198906873960314, - 0.0000022040579125481307, + -6.502231130846917e-05, + 4.026019138689209e-05, + 4.8366793783671197e-05, + -3.700732702237127e-05, + -9.125027198184678e-05, + -4.8198906873960314e-05, + 2.2040579125481307e-06, 0.00022218355621103725, 0.0009697183152478795, 0.0024377561334622774, @@ -121368,16 +121370,16 @@ -0.0023793448211148487, -0.00012973744994908344, 0.00016186545184987395, - -0.000017662453239933047, + -1.7662453239933047e-05, -0.00019173672159225787, -0.00018560562410114862, - -0.00007240334293148962, - 0.000037217114573611476, - 0.00004668651054244922, - -0.000042298628307223693, + -7.240334293148962e-05, + 3.7217114573611476e-05, + 4.668651054244922e-05, + -4.2298628307223693e-05, -0.0001009697144545834, - -0.000058791702883156084, - 0.000004567915735193765, + -5.8791702883156084e-05, + 4.567915735193765e-06, 0.00022080529032473167, 0.0009631755550387503, 0.0024258681369468305, @@ -121398,18 +121400,18 @@ -0.005483289473708943, -0.004031802373854703, -0.0009259633801887384, - 0.000006629279791971269, - -0.000018808377554611205, + 6.629279791971269e-06, + -1.8808377554611205e-05, -0.00017833333530125882, -0.0002085480519530333, -0.00011196977745377222, - 0.000012697133443373915, - 0.00005385978557671537, - -0.000025915155407866783, + 1.2697133443373915e-05, + 5.385978557671537e-05, + -2.5915155407866783e-05, -0.00010056877657165642, - -0.0000698823013581632, - -0.00001617685519552486, - 0.00009643916886375347, + -6.98823013581632e-05, + -1.617685519552486e-05, + 9.643916886375347e-05, 0.0004984876672489791, 0.0012844425943795165, 0.0026917328047919922, @@ -121428,18 +121430,18 @@ -0.005425655211691991, -0.004022684974738526, -0.0009205600388545282, - 0.000016347587982819007, - -0.000017435726270461358, + 1.6347587982819007e-05, + -1.7435726270461358e-05, -0.0001848315217820977, -0.00021556660506961997, -0.00011896272324804728, - 0.000009420226393152537, - 0.00005155765752629369, - -0.000030433034892323895, + 9.420226393152537e-06, + 5.155765752629369e-05, + -3.0433034892323895e-05, -0.00010859434552744885, - -0.00007761316780863373, - -0.000013417374410773693, - 0.00009521983084117674, + -7.761316780863373e-05, + -1.3417374410773693e-05, + 9.521983084117674e-05, 0.0004915360876130638, 0.0012744702583109176, 0.0027251535275219204, @@ -121460,17 +121462,17 @@ -0.003840595228330399, -0.0018067847213534716, -0.0002002872255410446, - -0.0000018934410784170533, + -1.8934410784170533e-06, -0.00015482244698339744, -0.00022204109375853517, -0.00015388692701226377, - -0.000019699828559279162, - 0.000052573766457927683, - -0.000009590817854912447, - -0.00009877719389150014, - -0.00008617474030218722, - -0.000022758753822620412, - 0.000051651938222015145, + -1.9699828559279162e-05, + 5.2573766457927683e-05, + -9.590817854912447e-06, + -9.877719389150014e-05, + -8.617474030218722e-05, + -2.2758753822620412e-05, + 5.1651938222015145e-05, 0.0002661653207716389, 0.000507549023028173, 0.0009835410578125257, @@ -121490,17 +121492,17 @@ -0.0038323379939484243, -0.0017958738773296607, -0.00018725239933304087, - -2.755428964054229e-7, + -2.755428964054229e-07, -0.0001604542130617001, -0.00022824927436611913, -0.00016048536256393523, - -0.000023335266583912923, - 0.00004975856915882618, - -0.000013407927079523549, + -2.3335266583912923e-05, + 4.975856915882618e-05, + -1.3407927079523549e-05, -0.00010546124166105617, - -0.0000914718132963834, - -0.000019465775110025234, - 0.000050494454857566036, + -9.14718132963834e-05, + -1.9465775110025234e-05, + 5.0494454857566036e-05, 0.0002588599562109154, 0.0004991914721220406, 0.0010151417035220837, @@ -121521,17 +121523,17 @@ -0.0015696399640095701, -0.002178999492713166, -0.0004944388965572393, - 0.000047494375708458014, - -0.00009521716622638021, + 4.7494375708458014e-05, + -9.521716622638021e-05, -0.00021116623079365626, -0.00018440462906532172, - -0.000053680454448812, - 0.0000457840306581527, - 0.000012422041412899806, - -0.00008081848376275242, - -0.00008972275642226547, - -0.000025540985321665634, - 0.00004012410392464447, + -5.3680454448812e-05, + 4.57840306581527e-05, + 1.2422041412899806e-05, + -8.081848376275242e-05, + -8.972275642226547e-05, + -2.5540985321665634e-05, + 4.012410392464447e-05, 0.00018293877727418726, 0.00016958729761206343, -0.000168410644919347, @@ -121551,17 +121553,17 @@ -0.0015613180524287284, -0.0021635732695611917, -0.00047924398317176424, - 0.00004944118040431756, - -0.00009958454813467095, + 4.944118040431756e-05, + -9.958454813467095e-05, -0.00021640089086212436, -0.00019057527656837853, - -0.00005759948184437342, - 0.0000426119081160564, - 0.000009102493484818088, - -0.00008655546732726547, - -0.00009289973324289108, - -0.0000216245989475044, - 0.00003889141005802599, + -5.759948184437342e-05, + 4.26119081160564e-05, + 9.102493484818088e-06, + -8.655546732726547e-05, + -9.289973324289108e-05, + -2.16245989475044e-05, + 3.889141005802599e-05, 0.00017543527487094772, 0.00016271996940927537, -0.00013806602734618885, @@ -121582,19 +121584,19 @@ 0.0008691767845260765, -0.0015742481662812597, -0.0007936703627595259, - 0.00008509665057086248, - 0.00002020526442023068, + 8.509665057086248e-05, + 2.020526442023068e-05, -0.00016712141875358807, -0.000197877082060599, - -0.00008806029912644821, - 0.00003363209289536541, - 0.00003501887752061451, - -0.000045713443575461316, - -0.00007618115452291112, - -0.000025060284196351627, - 0.000029987176230715306, + -8.806029912644821e-05, + 3.363209289536541e-05, + 3.501887752061451e-05, + -4.5713443575461316e-05, + -7.618115452291112e-05, + -2.5060284196351627e-05, + 2.9987176230715306e-05, 0.00015333525684482023, - 0.00009823605936150908, + 9.823605936150908e-05, -0.0006156312434803305, -0.0004220517908313694, 0.0013208986863230592, @@ -121612,19 +121614,19 @@ 0.0008781885592270089, -0.001556351229358969, -0.0007778369108202943, - 0.00008744246963229604, - 0.000017321736606732267, + 8.744246963229604e-05, + 1.7321736606732267e-05, -0.00017136419748024745, -0.00020354040712172427, - -0.00009205651846629914, - 0.00003025292581095378, - 0.00003192304859352221, - -0.000050856124707538894, - -0.00007747567733170494, - -0.000020559624818420463, - 0.000028498601597473915, + -9.205651846629914e-05, + 3.025292581095378e-05, + 3.192304859352221e-05, + -5.0856124707538894e-05, + -7.747567733170494e-05, + -2.0559624818420463e-05, + 2.8498601597473915e-05, 0.00014591477939215723, - 0.0000928958523545291, + 9.28958523545291e-05, -0.000586164045547903, -0.00038375731522842987, 0.0013302910689563492, @@ -121643,17 +121645,17 @@ 0.0018865238901606836, -0.00036729975066832293, -0.0008822652583035346, - 0.00003990807553401548, + 3.990807553401548e-05, 0.0001707508879034823, - -0.00007587457775417106, + -7.587457775417106e-05, -0.00019092457384706164, -0.00011904353639469681, - 0.000011276633059130953, - 0.00005390273681611054, - -0.0000025944449796459743, - -0.00004421929774094929, - -0.00001932709266802782, - 0.000011117545670729422, + 1.1276633059130953e-05, + 5.390273681611054e-05, + -2.5944449796459743e-06, + -4.421929774094929e-05, + -1.932709266802782e-05, + 1.1117545670729422e-05, 0.00011712224771406304, 0.00011757380659385813, -0.0006267877564674492, @@ -121673,17 +121675,17 @@ 0.0018963837702878935, -0.0003493219153858503, -0.000867391168143855, - 0.00004266438774545293, + 4.266438774545293e-05, 0.00016936029889023807, - -0.00007921992205941951, + -7.921992205941951e-05, -0.00019596763088833806, -0.0001228577562612252, - 0.000007812772693020007, - 0.00005076316791881361, - -0.0000074034317110503075, - -0.00004385581701582841, - -0.00001445762124768707, - 0.000009188098386779295, + 7.812772693020007e-06, + 5.076316791881361e-05, + -7.4034317110503075e-06, + -4.385581701582841e-05, + -1.445762124768707e-05, + 9.188098386779295e-06, 0.00011021220041983991, 0.0001138922201721795, -0.0005981685918488147, @@ -121706,16 +121708,16 @@ -0.0006246306849999467, -0.00010650870417698734, 0.00029045749746480317, - 0.000060053871882517995, + 6.0053871882517995e-05, -0.00015546248765301946, -0.0001439035257673788, - -0.000021396971437568865, - 0.00005919149042856799, - 0.000039113954874666156, - -0.0000013819955729612351, - -0.000004346553216152713, - -0.000014340929168245615, - 0.000058731975507123204, + -2.1396971437568865e-05, + 5.919149042856799e-05, + 3.9113954874666156e-05, + -1.3819955729612351e-06, + -4.346553216152713e-06, + -1.4340929168245615e-05, + 5.8731975507123204e-05, 0.00012119027490186258, -0.0004817826561232421, -0.0010650159226232735, @@ -121736,16 +121738,16 @@ -0.000612010303959501, -0.00010342454961970198, 0.00029033980344040115, - 0.000057422018032300296, + 5.7422018032300296e-05, -0.0001597725815498377, -0.00014729393637622905, - -0.000024847824617583704, - 0.000055817177361724724, - 0.00003448443116927065, - 3.6229944831742096e-7, - 5.028782705450251e-7, - -0.00001683048107834265, - 0.00005289350600588542, + -2.4847824617583704e-05, + 5.5817177361724724e-05, + 3.448443116927065e-05, + 3.6229944831742096e-07, + 5.028782705450251e-07, + -1.683048107834265e-05, + 5.289350600588542e-05, 0.00011926723650884275, -0.00045444625345629155, -0.001035847297519565, @@ -121768,16 +121770,16 @@ -0.00025987945197389263, 0.000298340405989001, 0.00020695888990199203, - -0.00008178075919614979, + -8.178075919614979e-05, -0.00015915795000103085, - -0.000060776640119155126, - 0.000044145498988870926, - 0.00006825206251258145, - 0.00004114020188323508, - 0.000020833006775322933, - -0.00003602489734940066, - -0.000014995794467377778, - 0.00008233482964727176, + -6.0776640119155126e-05, + 4.4145498988870926e-05, + 6.825206251258145e-05, + 4.114020188323508e-05, + 2.0833006775322933e-05, + -3.602489734940066e-05, + -1.4995794467377778e-05, + 8.233482964727176e-05, -0.0003464911122642454, -0.0008838070274651128, -0.0011494319675477037, @@ -121798,16 +121800,16 @@ -0.0002566562836158696, 0.00029905095723779557, 0.0002047817250138845, - -0.00008528812641166322, + -8.528812641166322e-05, -0.00016195147402743002, - -0.00006412598160561714, - 0.00004046697200205415, - 0.00006373755416259152, - 0.000043889992587809945, - 0.000025159337836897476, - -0.00003905310070299821, - -0.00001913534870736283, - 0.00008207123218152239, + -6.412598160561714e-05, + 4.046697200205415e-05, + 6.373755416259152e-05, + 4.3889992587809945e-05, + 2.5159337836897476e-05, + -3.905310070299821e-05, + -1.913534870736283e-05, + 8.207123218152239e-05, -0.0003213436753732492, -0.0008601383357336277, -0.0011484441214582675, @@ -121829,21 +121831,21 @@ -0.0002959268106222152, 0.00016543881208387737, 0.0003119706925935666, - 0.0000213669056688455, + 2.13669056688455e-05, -0.00015222610281691586, -0.00010314384087366653, - 0.000009360214701633058, - 0.00007521991089536582, - 0.00007332309518551243, - 0.0000513855978769719, - -0.00004320165676869205, - -0.00008308704911367125, - 0.000008751031462678997, + 9.360214701633058e-06, + 7.521991089536582e-05, + 7.332309518551243e-05, + 5.13855978769719e-05, + -4.320165676869205e-05, + -8.308704911367125e-05, + 8.751031462678997e-06, -0.00026488327942375, -0.000701019968674932, -0.0010131331038748435, -0.0011063990333358461, - 0.00007976380978713644, + 7.976380978713644e-05, 0.0017086379090477046, 0.0032603011758133393, 0.003720686948451971, @@ -121859,21 +121861,21 @@ -0.0002928403112664724, 0.00016635408263494306, 0.0003099262065899295, - 0.00001864656483952297, + 1.864656483952297e-05, -0.00015434810930118112, -0.00010629590167750673, - 0.000005439408115151835, - 0.00007080987177571758, - 0.00007659992940748294, - 0.000054677086160211254, - -0.00004656161402049485, - -0.00008495910109386864, - 0.000009689588803379519, + 5.439408115151835e-06, + 7.080987177571758e-05, + 7.659992940748294e-05, + 5.4677086160211254e-05, + -4.656161402049485e-05, + -8.495910109386864e-05, + 9.689588803379519e-06, -0.0002431520551275927, -0.000683087621008177, -0.0010143398359222895, -0.0011190100630799036, - 0.00005904385026787973, + 5.904385026787973e-05, 0.0016468492147593545, 0.003151849273800281, 0.0021616092960868795, @@ -121888,18 +121890,18 @@ 0.00015743535828619134, 0.00034359206622059675, -0.0001917071633907033, - -0.000037040874067591544, + -3.7040874067591544e-05, 0.0003197586398952725, 0.00013135044012231177, -0.00011279860802263083, -0.00014195263753077151, - -0.0000367841091631052, - 0.00005502407154587685, - 0.0000886209117794863, - 0.00007866308065453515, - -0.00002980091941551923, + -3.67841091631052e-05, + 5.502407154587685e-05, + 8.86209117794863e-05, + 7.866308065453515e-05, + -2.980091941551923e-05, -0.000127697301504762, - -0.00006708251268769702, + -6.708251268769702e-05, -0.0002357149747016158, -0.0005660279296782981, -0.00088721102259118, @@ -121918,18 +121920,18 @@ 0.00016180290946936137, 0.00034695478034006303, -0.00018907869166904032, - -0.000036627980421117786, + -3.6627980421117786e-05, 0.00031747873135383453, 0.00012928113480268458, -0.00011428291240401684, -0.0001447959718446901, - -0.00004077980918854145, - 0.00005072200678182461, - 0.00009188759980186125, - 0.00008052423846333799, - -0.00003311647729175573, + -4.077980918854145e-05, + 5.072200678182461e-05, + 9.188759980186125e-05, + 8.052423846333799e-05, + -3.311647729175573e-05, -0.00012694784232739824, - -0.00006583563699963483, + -6.583563699963483e-05, -0.00021863410994135197, -0.0005534404738697835, -0.0008893539944091616, @@ -121941,24 +121943,24 @@ 0.002318045812303423, 0.002095241725124165, 0.0013992033913734667, - -0.00002309621683144929, + -2.309621683144929e-05, -0.0014774206384811583, -0.002468161919931392, -0.0024316742974055594, -0.0014559340092671798, -0.0002727220422007213, 0.0002585072367072626, - -0.00003747842474520255, + -3.747842474520255e-05, -0.000187754583024486, 0.00021332192212808205, 0.00021623259156319503, - -0.00004391571541056487, + -4.391571541056487e-05, -0.00016155937473625085, - -0.00008823139083281648, - 0.000015108176251386253, - 0.0000818965262375428, - 0.00009579831773307186, - -0.0000015515829452945608, + -8.823139083281648e-05, + 1.5108176251386253e-05, + 8.18965262375428e-05, + 9.579831773307186e-05, + -1.5515829452945608e-06, -0.0001346874818195803, -0.00012202077070319739, -0.0002220955417848674, @@ -121971,24 +121973,24 @@ 0.002382478926327074, 0.002162287656571453, 0.0014416719909805008, - 0.0000027074619773673984, + 2.7074619773673984e-06, -0.001459399630750965, -0.002468437092242821, -0.0024261702735508387, -0.001449147588096358, -0.00027252341850232263, 0.00025921775488571346, - -0.000035631883240502476, + -3.5631883240502476e-05, -0.00018850861348427086, 0.00021042337150616985, 0.00021453948459451783, - -0.00004489876096457742, + -4.489876096457742e-05, -0.00016397138107993793, - -0.00009208138907534371, - 0.000010903315674062432, - 0.00008464297890426167, - 0.00009605611480188316, - -0.00000436389528184856, + -9.208138907534371e-05, + 1.0903315674062432e-05, + 8.464297890426167e-05, + 9.605611480188316e-05, + -4.36389528184856e-06, -0.00013131130569718502, -0.00012172638663632678, -0.00021049603410942466, @@ -122008,18 +122010,18 @@ -0.0016318983261903982, -0.001323495224009446, -0.000515749974604478, - 0.00008661967884355528, - 0.00006862813175890405, + 8.661967884355528e-05, + 6.862813175890405e-05, -0.0002161677707478597, - 0.0000371715479220818, + 3.71715479220818e-05, 0.0002444943650775881, - 0.00004013589342628189, + 4.013589342628189e-05, -0.00014922782225563436, -0.0001354900891039177, - -0.000032207920559421414, - 0.00005362251268836801, - 0.00009891328876189411, - 0.00002843638389173578, + -3.2207920559421414e-05, + 5.362251268836801e-05, + 9.891328876189411e-05, + 2.843638389173578e-05, -0.00010649488655227932, -0.0001376039612206495, -0.0002035523902339257, @@ -122038,18 +122040,18 @@ -0.0016275222951133755, -0.0013190791423201296, -0.0005193033838842241, - 0.00008514341458578783, - 0.00006939718195324651, + 8.514341458578783e-05, + 6.939718195324651e-05, -0.00021859603804143227, - 0.0000333038364001476, + 3.33038364001476e-05, 0.00024277212157306817, - 0.00003943241535540589, + 3.943241535540589e-05, -0.0001510924836391709, -0.00013898193655167515, - -0.00003634687158606364, - 0.00005546671466222755, - 0.00009766749495736798, - 0.000026534684296563424, + -3.634687158606364e-05, + 5.546671466222755e-05, + 9.766749495736798e-05, + 2.6534684296563424e-05, -0.00010088897692623521, -0.00013964951648356917, -0.00019749588968972945, @@ -122057,7 +122059,7 @@ -0.0007362950229229545, -0.0010347739481111463, -0.0006839080111853315, - 0.000058713899374532075, + 5.8713899374532075e-05, 0.0009533904134162272, 0.0005446804515365885, 0.001251550135421596, @@ -122069,19 +122071,19 @@ -0.0008419362283880059, -0.000957208260375357, -0.0005524882456873343, - -0.00005977312669265929, - 0.00009835420436148573, + -5.977312669265929e-05, + 9.835420436148573e-05, -0.00014677911324993418, -0.00011949767880859473, 0.0001941828912422439, 0.00012148388139588564, -0.00010356485493734709, -0.0001653873955708266, - -0.00007691932614878463, - 0.000014016837157885053, - 0.00008546743965830343, - 0.000047367029146505734, - -0.000058790673984528653, + -7.691932614878463e-05, + 1.4016837157885053e-05, + 8.546743965830343e-05, + 4.7367029146505734e-05, + -5.8790673984528653e-05, -0.00011871275519150697, -0.00016293099121324962, -0.0003102859480245903, @@ -122099,19 +122101,19 @@ -0.000838938939038376, -0.0009553915578751384, -0.0005591410963137569, - -0.00006298037334759023, - 0.00009779556856391692, + -6.298037334759023e-05, + 9.779556856391692e-05, -0.00015116504425276463, -0.00012459588190513672, 0.00019194031605599706, 0.00012077798675075652, -0.00010480014541459012, -0.00016836820039968048, - -0.00008102739547706362, - 0.000014783407080778514, - 0.00008306391465101897, - 0.00004660089544962317, - -0.000051688074036448003, + -8.102739547706362e-05, + 1.4783407080778514e-05, + 8.306391465101897e-05, + 4.660089544962317e-05, + -5.1688074036448003e-05, -0.00012425002878623025, -0.00016154969425514584, -0.00030468872803065796, @@ -122131,19 +122133,19 @@ -0.0005464675991140388, -0.00043787635145346086, -0.00013168755285191156, - 0.00008350521977154205, - -0.00004751134570635032, + 8.350521977154205e-05, + -4.751134570635032e-05, -0.00019027092497923013, - 0.00007906913358576407, + 7.906913358576407e-05, 0.0001766785151393797, - -0.00003481973483018194, + -3.481973483018194e-05, -0.00016435768572341215, -0.00011358842012147904, - -0.000021935953317148732, - 0.00005836232067265966, - 0.000049894477095701176, - -0.000017598492561461775, - -0.0000784897150044569, + -2.1935953317148732e-05, + 5.836232067265966e-05, + 4.9894477095701176e-05, + -1.7598492561461775e-05, + -7.84897150044569e-05, -0.00011674396092917056, -0.00020522426163160277, -0.000538628255557407, @@ -122161,19 +122163,19 @@ -0.000547189466228196, -0.00044679742562798595, -0.00013623789445169667, - 0.00008142502684521164, - -0.000053902421973515334, + 8.142502684521164e-05, + -5.3902421973515334e-05, -0.00019672504808103416, - 0.00007580962456936036, + 7.580962456936036e-05, 0.00017565738201406273, - -0.00003540841795289503, + -3.540841795289503e-05, -0.00016676145381481412, -0.00011767418104024105, - -0.00002219285759345327, - 0.00005529564946433167, - 0.00005023781443220034, - -0.000009884235171193082, - -0.00008806372237197624, + -2.219285759345327e-05, + 5.529564946433167e-05, + 5.023781443220034e-05, + -9.884235171193082e-06, + -8.806372237197624e-05, -0.00011838683936950947, -0.0001982506231448995, -0.0005330406623823983, @@ -122181,311 +122183,311 @@ -0.0008629837659526884, -0.0004590566663429537, 0.00022310809279695672, - 0.0000372843866643119, + 3.72843866643119e-05, 0.0006378244492488989, 0.0007206682373483099, 0.0006657034368131343, 0.00031310369589082263, 0.00023771132190857224, 0.00020950564048904312, - -0.000007222389916050869, + -7.222389916050869e-06, -0.00023503559057124564, -0.00026499720559697943, -0.00013150530470496143, - 0.00006401538441368866, - 0.00003280987351803104, + 6.401538441368866e-05, + 3.280987351803104e-05, -0.00016598358714823398, - -0.00005300209045846695, + -5.300209045846695e-05, 0.00017882617244618403, - 0.00004630592169713618, + 4.630592169713618e-05, -0.00013167124897462128, -0.00013502510969217748, - -0.00004453983610853992, - 0.00003049240749922771, - 0.000038799586224222, - 0.0000019344724179717315, - -0.00004604724557660684, - -0.00007962064379323109, + -4.453983610853992e-05, + 3.049240749922771e-05, + 3.8799586224222e-05, + 1.9344724179717315e-06, + -4.604724557660684e-05, + -7.962064379323109e-05, -0.00011568133797136551, -0.000392532897076546, -0.0008556649540128607, -0.0007982446865088733, -0.0005659860662130978, - 0.00005148920822347123, + 5.148920822347123e-05, 0.0006597996217459756, 0.0007696193572998247, 0.0006979675471694112, 0.0003330649071531024, 0.000260148363203134, 0.00021316776641868156, - -0.0000081982033670695, + -8.1982033670695e-06, -0.00023795167425745534, -0.00027525218036125883, -0.00013711273543634583, - 0.000060291209331503895, - 0.00002457708717508477, + 6.0291209331503895e-05, + 2.457708717508477e-05, -0.0001737646026900062, - -0.00005768371872704681, + -5.768371872704681e-05, 0.0001771787294280823, - 0.00004629022824660327, + 4.629022824660327e-05, -0.00013351768236448947, -0.00013904308337249816, - -0.00004558105822188968, - 0.000027273260955673528, - 0.0000400077852066616, - 0.000009479716105502894, - -0.00005935171912027499, - -0.00008227421025043736, + -4.558105822188968e-05, + 2.7273260955673528e-05, + 4.00077852066616e-05, + 9.479716105502894e-06, + -5.935171912027499e-05, + -8.227421025043736e-05, -0.0001063596199374548, -0.00038659264762472165, -0.0008376200886557768, -0.0008016298754461377, -0.0005932610915519751, - 0.0000372843866643119, - -0.00002998014550048851, + 3.72843866643119e-05, + -2.998014550048851e-05, 0.0004925359079949836, 0.0006600530292013012, 0.0005628773228328035, 0.00021379973577531836, 0.00012110254683076832, 0.00017035919407171, - 0.00008199471964111891, - -0.0000654028772982687, + 8.199471964111891e-05, + -6.54028772982687e-05, -0.00012018426152859204, - -0.00008897150824665, - 0.000056299506037820667, - 0.00008673111351714875, - -0.00008737469614909386, + -8.897150824665e-05, + 5.6299506037820667e-05, + 8.673111351714875e-05, + -8.737469614909386e-05, -0.00014296120867306712, 0.00011463369291309065, 0.00012254420031400704, - -0.00007318096127772047, + -7.318096127772047e-05, -0.00013586344339039746, - -0.000053627314782611835, - 0.000021725496908051625, - 0.000028463534344132027, - 0.000002373849514932801, - -0.000038614448598194975, - -0.0000783932117146868, - -0.0000617826927592982, + -5.3627314782611835e-05, + 2.1725496908051625e-05, + 2.8463534344132027e-05, + 2.373849514932801e-06, + -3.8614448598194975e-05, + -7.83932117146868e-05, + -6.17826927592982e-05, -0.0002753239241058144, -0.0006994762154908084, -0.0007545556038136185, -0.0005391971746927932, - -0.00003132092119234394, + -3.132092119234394e-05, 0.0005119000429544653, 0.0007038893524076876, 0.0005885782894021144, 0.0002307348350494613, 0.0001451344614803467, 0.00017529082880169832, - 0.00007874527802966809, - -0.000069945111535082, + 7.874527802966809e-05, + -6.9945111535082e-05, -0.00013082600315658807, - -0.00009545685296953721, - 0.00005090404167657655, - 0.00007698766098341634, - -0.00009631591488291034, + -9.545685296953721e-05, + 5.090404167657655e-05, + 7.698766098341634e-05, + -9.631591488291034e-05, -0.00014930119139520346, 0.00011208615748909402, 0.00012292457816505222, - -0.0000745517273505154, + -7.45517273505154e-05, -0.0001397039016775929, - -0.000055123290869282376, - 0.000018763886847323832, - 0.000030201742968616213, - 0.00000932186003706292, - -0.000054477795537452646, - -0.00008021527280342773, - -0.000050134509016675237, + -5.5123290869282376e-05, + 1.8763886847323832e-05, + 3.0201742968616213e-05, + 9.32186003706292e-06, + -5.4477795537452646e-05, + -8.021527280342773e-05, + -5.0134509016675237e-05, -0.000271739997795159, -0.0006835051903391544, -0.0007599132292816037, -0.0005563340107642396, - -0.00002998014550048851, - -0.00001516444750369647, + -2.998014550048851e-05, + -1.516444750369647e-05, 0.0005205400860331298, 0.0006366863167361839, 0.0005999485251232124, 0.00018334830396443702, - 0.00004898387784423708, + 4.898387784423708e-05, 0.00010816599704881614, - 0.0000815149424595932, - -0.0000053051105848748526, - -0.00004388807242582949, - -0.00004115843400704664, - 0.000056630560217526345, + 8.15149424595932e-05, + -5.3051105848748526e-06, + -4.388807242582949e-05, + -4.115843400704664e-05, + 5.6630560217526345e-05, 0.00012229941157080047, - 0.000002654646215518317, + 2.654646215518317e-06, -0.00016128567811428456, - 0.000003039868176753382, + 3.039868176753382e-06, 0.00016734709941399804, - 0.0000036670191420431656, + 3.6670191420431656e-06, -0.00011609280255025565, - -0.00005477795467496378, - 0.000044551781285699995, - 0.00004608513760545168, - 0.000010923705851512456, - -0.000056366850938381616, + -5.477795467496378e-05, + 4.4551781285699995e-05, + 4.608513760545168e-05, + 1.0923705851512456e-05, + -5.6366850938381616e-05, -0.00011484774669515827, - -0.00007797892110278175, + -7.797892110278175e-05, -0.00020095463628973915, -0.0006165154078858095, -0.0006426523146836869, -0.0005482976944907222, - -0.000029110322205223925, + -2.9110322205223925e-05, 0.0005404459802984887, 0.0006748500488406952, 0.0006177613685336319, 0.0001966184185265227, - 0.00007375093467839645, + 7.375093467839645e-05, 0.00011374245021740253, - 0.00007624905276085983, - -0.00001077119056271911, - -0.000054043879121096826, - -0.0000484212382725272, - 0.00004965705650415548, + 7.624905276085983e-05, + -1.077119056271911e-05, + -5.4043879121096826e-05, + -4.84212382725272e-05, + 4.965705650415548e-05, 0.00011149027847352799, - -0.00000718757183804073, + -7.18757183804073e-06, -0.0001693232120363878, - -6.077388006552617e-7, + -6.077388006552617e-07, 0.00016785202659850055, - 0.0000026610207299966653, + 2.6610207299966653e-06, -0.00011959594077457907, - -0.00005641495760231352, - 0.00004209941000887011, - 0.00004809848068366655, - 0.000017345283898195375, - -0.00007299823711478834, + -5.641495760231352e-05, + 4.209941000887011e-05, + 4.809848068366655e-05, + 1.7345283898195375e-05, + -7.299823711478834e-05, -0.0001146075986269737, - -0.00006486386906985242, + -6.486386906985242e-05, -0.00020240109969307433, -0.000603676083632084, -0.0006468751661356428, -0.000553916275461903, - -0.00001516444750369647, - -8.524191147608255e-7, + -1.516444750369647e-05, + -8.524191147608255e-07, 0.000519147088189207, 0.00075318283142695, 0.0006787710163042747, 0.00025478080798352473, - 0.00003789936638226807, - 0.00007495894922861918, - 0.00006281932609147527, - 0.000001610843939174566, - -0.000027120222132910683, - -0.0000169551499156496, - 0.00005608661062216696, + 3.789936638226807e-05, + 7.495894922861918e-05, + 6.281932609147527e-05, + 1.610843939174566e-06, + -2.7120222132910683e-05, + -1.69551499156496e-05, + 5.608661062216696e-05, 0.00014228702149755252, - 0.00007976786679648456, + 7.976786679648456e-05, -0.00011582444584686104, -0.0001084968843185499, 0.00014849358859523882, - 0.00009335812185020063, - -0.00008114581495268262, - -0.00005533516661691419, - 0.00009116830853858659, + 9.335812185020063e-05, + -8.114581495268262e-05, + -5.533516661691419e-05, + 9.116830853858659e-05, 0.00012343961153618517, - 0.00007168740615034044, - -0.00005870771031640652, + 7.168740615034044e-05, + -5.870771031640652e-05, -0.00018079020337033355, -0.00015185202428046946, -0.00022509748835543742, -0.0005762407322686686, -0.0006739658363388984, -0.0005283841201209945, - -0.000023341018978541766, + -2.3341018978541766e-05, 0.0005413636212345335, 0.0007842784117502308, 0.000687483937849699, 0.000264101522085765, - 0.00006245923148850136, - 0.00008052255153901234, - 0.00005608873891434186, - -0.000004029862005443606, - -0.000036057754366639864, - -0.000024919506254557762, - 0.00004776145933299263, + 6.245923148850136e-05, + 8.052255153901234e-05, + 5.608873891434186e-05, + -4.029862005443606e-06, + -3.6057754366639864e-05, + -2.4919506254557762e-05, + 4.776145933299263e-05, 0.0001309093561347258, - 0.00006932001596303903, + 6.932001596303903e-05, -0.00012542435739854155, -0.00011334013578906921, 0.00014878950271123915, - 0.00009260336819108783, - -0.00008413664218914264, - -0.00005689316901974235, - 0.00008933421677189394, + 9.260336819108783e-05, + -8.413664218914264e-05, + -5.689316901974235e-05, + 8.933421677189394e-05, 0.00012568765011771978, - 0.00007812982192260419, - -0.00007413542504355948, + 7.812982192260419e-05, + -7.413542504355948e-05, -0.0001780691507474162, -0.00013854166310869317, -0.00023329335991246954, -0.0005662982777222447, -0.0006739826908536073, -0.0005231475849057066, - -8.524191147608255e-7, - -0.00006978027405569201, + -8.524191147608255e-07, + -6.978027405569201e-05, 0.0005517567985405709, 0.0007981564897215259, 0.0008317564510699365, 0.00037295704486825797, - 0.00009910011412418986, - 0.00007767038926733642, - 0.00006675964217658639, - 2.1215708994441965e-7, - -0.000037742147838124865, - -0.000024394472053107812, - 0.00004710618813481566, + 9.910011412418986e-05, + 7.767038926733642e-05, + 6.675964217658639e-05, + 2.1215708994441965e-07, + -3.7742147838124865e-05, + -2.4394472053107812e-05, + 4.710618813481566e-05, 0.0001412775606737479, 0.0001384883901039431, - -0.000038870918222205314, + -3.8870918222205314e-05, -0.00017254219807247257, - 0.000058186396846349464, + 5.8186396846349464e-05, 0.00017155671461080706, - -0.000028165220246596594, - -0.0000639786483563946, + -2.8165220246596594e-05, + -6.39786483563946e-05, 0.00013275149384527106, 0.0002690200421800521, 0.00024048537296487873, - 0.000026306416071157835, + 2.6306416071157835e-05, -0.00022287471936745576, -0.0002666004326391642, -0.00031633149165977284, -0.0006784250531785565, -0.0007449966012663358, -0.0006807408615023621, - -0.0000964715343794639, + -9.64715343794639e-05, 0.0005762037814574582, 0.0008202269618025656, 0.0008308573899916027, 0.000378807060899331, 0.0001226178289519641, - 0.00008262591370066559, - 0.00005921968462622422, - -0.000004868498318183946, - -0.00004490602024039303, - -0.0000329477319306689, - 0.00003777660997175343, + 8.262591370066559e-05, + 5.921968462622422e-05, + -4.868498318183946e-06, + -4.490602024039303e-05, + -3.29477319306689e-05, + 3.777660997175343e-05, 0.00012981710426458634, 0.00012771882809329348, - -0.000049771700597016804, + -4.9771700597016804e-05, -0.00017855809595494144, - 0.000057928128415810095, + 5.7928128415810095e-05, 0.00017094779614991646, - -0.00003049671017466151, - -0.0000653574621910237, + -3.049671017466151e-05, + -6.53574621910237e-05, 0.0001315712980430787, 0.00027171497390855937, 0.00024777906682731733, - 0.000013758729667476505, + 1.3758729667476505e-05, -0.00021799330504788025, -0.0002542902661265452, -0.0003314692464503654, -0.0006701166077795329, -0.0007388697331743472, -0.0006673489806423922, - -0.00006978027405569201, + -6.978027405569201e-05, -0.00025877892282857406, 0.0004127453762791547, 0.0008620739988321784, @@ -122493,19 +122495,19 @@ 0.000523238633697482, 0.00019645859879045385, 0.00011373204167507644, - 0.00009404924760630447, - 0.00001841525621621726, - -0.000048440414309659236, - -0.00005130099439247334, - 0.000024006367191159403, + 9.404924760630447e-05, + 1.841525621621726e-05, + -4.8440414309659236e-05, + -5.130099439247334e-05, + 2.4006367191159403e-05, 0.00011939449658072038, 0.0001714023736620866, - 0.000042923834431339165, + 4.2923834431339165e-05, -0.00017021320346956233, - -0.00007400508074709733, + -7.400508074709733e-05, 0.0001954205597027098, - 0.00005104686442455834, - -0.00008425296288826797, + 5.104686442455834e-05, + -8.425296288826797e-05, 0.00013213288514177148, 0.0004419265329120448, 0.0005494770419323818, @@ -122523,19 +122525,19 @@ 0.0005268870079551299, 0.00021814125962400598, 0.00011751520859980985, - 0.00008625656104074432, - 0.00001458737855329715, - -0.00005346382009814952, - -0.00006024852433542185, - 0.00001410200457443499, + 8.625656104074432e-05, + 1.458737855329715e-05, + -5.346382009814952e-05, + -6.024852433542185e-05, + 1.410200457443499e-05, 0.00010827202301906899, 0.00016055829711267316, - 0.00003106372960110293, + 3.106372960110293e-05, -0.00017727010064159688, - -0.00007512159386747043, + -7.512159386747043e-05, 0.00019485570092671602, - 0.000049457863134171366, - -0.00008544788536805361, + 4.9457863134171366e-05, + -8.544788536805361e-05, 0.0001316512514276704, 0.0004454621290965419, 0.0005584459674059615, @@ -122555,19 +122557,19 @@ 0.0003082098975227477, 0.00015627371659394045, 0.00012865013640005402, - 0.000059209386105376046, - -0.0000449723154752227, - -0.00007808294865409841, - -0.000012985182218308186, - 0.00008216714194241625, + 5.9209386105376046e-05, + -4.49723154752227e-05, + -7.808294865409841e-05, + -1.2985182218308186e-05, + 8.216714194241625e-05, 0.00017264280880432774, 0.00011216978465727693, -0.00011485472240348547, -0.00018896677865322788, 0.00012430692896895715, 0.0001492036438794285, - -0.00009965606152281334, - 0.00005938510069197257, + -9.965606152281334e-05, + 5.938510069197257e-05, 0.0005555746121881414, 0.0009565117154538398, 0.0008495782054362753, @@ -122585,19 +122587,19 @@ 0.0003270982608577435, 0.00015828580799504628, 0.00012096890429272402, - 0.00005728072525552544, - -0.00004767602841229877, - -0.00008713634116014628, - -0.000023007475717047055, - 0.0000717052560587098, + 5.728072525552544e-05, + -4.767602841229877e-05, + -8.713634116014628e-05, + -2.3007475717047055e-05, + 7.17052560587098e-05, 0.00016193268308620793, - 0.00009973766655210603, + 9.973766655210603e-05, -0.00012274462173817515, -0.00019116724747881273, 0.00012367945554058788, 0.00014835894798735666, -0.00010070475693279372, - 0.000059711780932828764, + 5.9711780932828764e-05, 0.0005603806072405933, 0.0009677018951960705, 0.0008452197397832691, @@ -122617,18 +122619,18 @@ 0.0001913669063552749, 0.00014252693082617978, 0.00010748488234991059, - -0.000024849812882851355, - -0.0000919761367543649, - -0.000053966170773167435, - 0.00003693591419211186, + -2.4849812882851355e-05, + -9.19761367543649e-05, + -5.3966170773167435e-05, + 3.693591419211186e-05, 0.00014286781530425583, 0.00015805638981995649, - -0.000033378470894182586, + -3.3378470894182586e-05, -0.00023882960178116984, - -0.00003253491228264882, + -3.253491228264882e-05, 0.00021762684916407646, - -0.00007023862312928916, - -0.00008804205334069778, + -7.023862312928916e-05, + -8.804205334069778e-05, 0.0005168845860244743, 0.0013184515746292407, 0.00163418585774444, @@ -122647,18 +122649,18 @@ 0.00019097986704364373, 0.0001351770598069785, 0.00010804378278707336, - -0.00002524062873267578, + -2.524062873267578e-05, -0.00010077950719947914, - -0.00006368105640231397, - 0.00002734886782571857, + -6.368105640231397e-05, + 2.734886782571857e-05, 0.00013247307177200346, 0.00014545903655229127, - -0.000041860042223108774, + -4.1860042223108774e-05, -0.00024224637787600094, - -0.00003333397055088273, + -3.333397055088273e-05, 0.00021744979258958043, - -0.00007116970898065673, - -0.00008672494062652555, + -7.116970898065673e-05, + -8.672494062652555e-05, 0.0005232639942733808, 0.0013319735829679098, 0.0016338867092025402, @@ -122678,24 +122680,24 @@ 0.00020367388215039903, 0.000123181963797434, 0.00013692935061482262, - 0.000006279022467592577, - -0.00009002056484827438, - -0.00008367143155114117, - -0.00001077280083755788, - 0.00009553902737061243, + 6.279022467592577e-06, + -9.002056484827438e-05, + -8.367143155114117e-05, + -1.077280083755788e-05, + 9.553902737061243e-05, 0.0001702745507578954, - 0.000050784505115871554, + 5.0784505115871554e-05, -0.00020984600375440762, -0.00020866428119212596, 0.00018851472675803722, - 0.00003580464820303458, + 3.580464820303458e-05, -0.00026124928367055077, 0.0002711214078970765, 0.0014465684735754125, 0.0024294473794100077, 0.002460563215261259, 0.0014697671489695956, - 0.00003560000853889767, + 3.560000853889767e-05, -0.001430488801103412, -0.0021488960893997367, -0.002372331800660001, @@ -122708,24 +122710,24 @@ 0.00020031716971748625, 0.00011635144855412526, 0.00014045369970310293, - 0.000008008344821485393, - -0.00009820491345702311, - -0.00009273616760897854, - -0.00001937103446071652, - 0.00008562772663037272, + 8.008344821485393e-06, + -9.820491345702311e-05, + -9.273616760897854e-05, + -1.937103446071652e-05, + 8.562772663037272e-05, 0.00015791380745769237, - 0.000041947660983605276, + 4.1947660983605276e-05, -0.0002145167480857163, -0.00020972494366493948, 0.00018886572307570402, - 0.000034995282911823145, + 3.4995282911823145e-05, -0.0002587287770652926, 0.0002791206851770362, 0.0014620884580034137, 0.0024327697018503283, 0.0024667947823871113, 0.0014756965099100077, - 0.000013147755586098166, + 1.3147755586098166e-05, -0.001416218295591636, -0.002124266963420265, -0.002354089641600364, @@ -122737,18 +122739,18 @@ 0.0008903029029778125, 0.0005693801655652946, 0.000211600055177863, - 0.00006583708673233914, + 6.583708673233914e-05, 0.0001314204244229759, - 0.00003542826760777649, - -0.00007454228528696004, - -0.00009161684682087269, - -0.000050939780513006875, - 0.00004373919681676309, + 3.542826760777649e-05, + -7.454228528696004e-05, + -9.161684682087269e-05, + -5.0939780513006875e-05, + 4.373919681676309e-05, 0.00015022668033500477, 0.00011979500045492306, -0.0001240575113173645, -0.00031493273148164124, - 0.00003747456744174515, + 3.747456744174515e-05, 0.0001902497672785249, -0.00034792385653750546, -0.0001622632348290392, @@ -122767,18 +122769,18 @@ 0.0008940895590553079, 0.0005720300433028173, 0.00020484857013834305, - 0.000059743597324774645, + 5.9743597324774645e-05, 0.00013818181995079712, - 0.00003890954309377468, - -0.00008178745606726737, - -0.00009980799825882731, - -0.000058516650799152246, - 0.00003447457482164247, + 3.890954309377468e-05, + -8.178745606726737e-05, + -9.980799825882731e-05, + -5.8516650799152246e-05, + 3.447457482164247e-05, 0.00013847551976139854, 0.00011081160970321976, -0.0001299249824807863, -0.00031629488514737115, - 0.00003816724660894598, + 3.816724660894598e-05, 0.00018958312050002755, -0.0003440305328890636, -0.00015292802511816323, @@ -122793,21 +122795,21 @@ -0.002228588217874948, -0.003229726729323365, -0.0017383656100474403, - -0.00008170744689483454, + -8.170744689483454e-05, 0.001113610956805644, 0.0010164989878007865, 0.0007017766611429533, 0.00023658440792294355, - -0.000011390152765744862, - 0.0000880912164901948, - 0.0000494611143576877, - -0.000049134459713890565, - -0.00007714575155751675, - -0.00007128334806940022, - -0.0000030730416026503095, + -1.1390152765744862e-05, + 8.80912164901948e-05, + 4.94611143576877e-05, + -4.9134459713890565e-05, + -7.714575155751675e-05, + -7.128334806940022e-05, + -3.0730416026503095e-06, 0.00011072221726569404, 0.0001593820152338718, - -0.000013130630500404346, + -1.3130630500404346e-05, -0.00030691794580595507, -0.0001648590411007407, 0.00029504142112029414, @@ -122828,16 +122830,16 @@ 0.0010177630166623949, 0.0006968636996430455, 0.00022616289346658362, - -0.000016542896390099932, - 0.00009805691295570889, - 0.00005418815013502113, - -0.00005521762995387037, - -0.00008437863186846907, - -0.00007786401041901085, - -0.000011530581213244667, - 0.00009990317733277128, + -1.6542896390099932e-05, + 9.805691295570889e-05, + 5.418815013502113e-05, + -5.521762995387037e-05, + -8.437863186846907e-05, + -7.786401041901085e-05, + -1.1530581213244667e-05, + 9.990317733277128e-05, 0.00015042728808456175, - -0.000020039961743503103, + -2.0039961743503103e-05, -0.00030854978176982203, -0.00016404967241190948, 0.0002945080012584911, @@ -122859,16 +122861,16 @@ 0.0011513852582944843, 0.0008808659278605796, 0.00031599747087964825, - -0.00008592183610651586, - 0.000020806295391905086, - 0.00004275829229992419, - -0.000020407629527800328, - -0.000045318851434867166, - -0.00006441101245094818, - -0.00003879687984740133, - 0.00006751554698344772, + -8.592183610651586e-05, + 2.0806295391905086e-05, + 4.275829229992419e-05, + -2.0407629527800328e-05, + -4.5318851434867166e-05, + -6.441101245094818e-05, + -3.879687984740133e-05, + 6.751554698344772e-05, 0.0001664803623192436, - 0.00009082143300060827, + 9.082143300060827e-05, -0.00020169957001642605, -0.00029705751017203194, 0.0002599889309722282, @@ -122889,16 +122891,16 @@ 0.0011476975955963568, 0.0008682165446175438, 0.0003016861805653511, - -0.00009005738320504924, - 0.00003359637934918666, - 0.000048154089616456264, - -0.00002522736180234543, - -0.000051649374691773695, - -0.0000700526362574379, - -0.000046292901320571384, - 0.00005788319927017137, + -9.005738320504924e-05, + 3.359637934918666e-05, + 4.8154089616456264e-05, + -2.522736180234543e-05, + -5.1649374691773695e-05, + -7.00526362574379e-05, + -4.6292901320571384e-05, + 5.788319927017137e-05, 0.000157701012737438, - 0.00008312235311820472, + 8.312235311820472e-05, -0.00020349982648783634, -0.0002963931208058796, 0.00025949863940204156, @@ -122921,16 +122923,16 @@ 0.0010579624799583178, 0.0004510582591389097, -0.00012584085829902333, - -0.00005265906151391382, - 0.00002156019163703983, - 0.0000031447281012116014, - -0.000002697557815469276, - -0.00003534384541107881, - -0.00005493834587218362, - 0.000027265631171094364, + -5.265906151391382e-05, + 2.156019163703983e-05, + 3.1447281012116014e-06, + -2.697557815469276e-06, + -3.534384541107881e-05, + -5.493834587218362e-05, + 2.7265631171094364e-05, 0.00015132992675645065, 0.0001650071759026566, - -0.00005463558232370078, + -5.463558232370078e-05, -0.0002878747342386302, 0.00010800544799333288, 0.0006148849934155531, @@ -122951,16 +122953,16 @@ 0.0010380228623782773, 0.00043255589951129417, -0.0001291138497615894, - -0.00003772998407040916, - 0.00002705514080185064, - -4.3400978461471756e-7, - -0.000008307641099368268, - -0.000040108486320587583, - -0.0000613311809971687, - 0.00001899301403329395, + -3.772998407040916e-05, + 2.705514080185064e-05, + -4.3400978461471756e-07, + -8.307641099368268e-06, + -4.0108486320587583e-05, + -6.13311809971687e-05, + 1.899301403329395e-05, 0.00014285234102663147, 0.0001568577425985426, - -0.00005645832429231251, + -5.645832429231251e-05, -0.0002876472421221158, 0.00010737419713535676, 0.0006222550131330273, @@ -122983,17 +122985,17 @@ 0.0005973828965511112, -0.0001237982893563814, -0.00011122000525610208, - -0.0000032532420552226987, - 0.000016721774746806156, - 0.0000406277235438754, - 0.000006333589431682779, - -0.00005075059108704296, - -0.0000062243877956889494, + -3.2532420552226987e-06, + 1.6721774746806156e-05, + 4.06277235438754e-05, + 6.333589431682779e-06, + -5.075059108704296e-05, + -6.2243877956889494e-06, 0.00012641333984187667, 0.00020056665314500003, - 0.00008143914656056191, + 8.143914656056191e-05, -0.00016631233745126715, - -0.00003682787615269559, + -3.682787615269559e-05, 0.0008708805500427404, 0.00034984376330141667, -0.001906541276093614, @@ -123012,18 +123014,18 @@ 0.0009974907415742783, 0.0005742053568281602, -0.00012662861917906676, - -0.00009503403455547767, - 0.000001838372288696119, - 0.00001424624379878922, - 0.000035459878908164225, - 0.000002407974556487931, - -0.00005592143633241869, - -0.00001305637732780794, + -9.503403455547767e-05, + 1.838372288696119e-06, + 1.424624379878922e-05, + 3.5459878908164225e-05, + 2.407974556487931e-06, + -5.592143633241869e-05, + -1.305637732780794e-05, 0.0001183500953849156, 0.00019237260410441973, - 0.00007974953508472509, + 7.974953508472509e-05, -0.00016682330259196193, - -0.00003783244295567347, + -3.783244295567347e-05, 0.0008783891628260895, 0.0003546811778124783, -0.0018930351831496985, @@ -123044,17 +123046,17 @@ 0.000588472221988784, -0.00010670443504103256, -0.00014781379449812994, - -0.000021251450751434052, - 0.00002118943036047608, - 0.00007339718423875686, - 0.000049518595734496336, - -0.000032825163986242024, - -0.000029303302282437742, - 0.00009511605857183482, + -2.1251450751434052e-05, + 2.118943036047608e-05, + 7.339718423875686e-05, + 4.9518595734496336e-05, + -3.2825163986242024e-05, + -2.9303302282437742e-05, + 9.511605857183482e-05, 0.0002071856961235168, 0.00017289579073256068, - -0.00001350153530756068, - -0.00008058040858947892, + -1.350153530756068e-05, + -8.058040858947892e-05, 0.0007809427314631788, 0.001555495162635164, -0.0008884860299118126, @@ -123074,17 +123076,17 @@ 0.0005599390854260122, -0.0001097306589914178, -0.00013132074811181315, - -0.00001697805918348545, - 0.000019575310148003195, - 0.00006834135090413794, - 0.00004644907200859941, - -0.00003669208035747234, - -0.000034714107403797715, - 0.00008757009413366015, + -1.697805918348545e-05, + 1.9575310148003195e-05, + 6.834135090413794e-05, + 4.644907200859941e-05, + -3.669208035747234e-05, + -3.4714107403797715e-05, + 8.757009413366015e-05, 0.00019939060300207506, 0.0001714721971240372, - -0.00001502749957963122, - -0.00008215588266764625, + -1.502749957963122e-05, + -8.215588266764625e-05, 0.0007877266596757179, 0.0015552284357795258, -0.0008780777495580455, @@ -123105,17 +123107,17 @@ 0.00014376597312599964, -0.0001809004906460076, -0.00017774771734679757, - -0.000030321000352905147, - 0.000020395311047957038, - 0.00008803077862024922, - 0.0000848658491755902, - -0.000010937243908391728, - -0.00004209225236214831, - 0.00006010366314758809, + -3.0321000352905147e-05, + 2.0395311047957038e-05, + 8.803077862024922e-05, + 8.48658491755902e-05, + -1.0937243908391728e-05, + -4.209225236214831e-05, + 6.010366314758809e-05, 0.00019301303417409808, 0.0002172896262071037, 0.00010434015752562879, - -0.000042069014104680795, + -4.2069014104680795e-05, 0.0004808665595845185, 0.00216106057658775, 0.001552534020105199, @@ -123135,17 +123137,17 @@ 0.00010908080096147125, -0.00018488233128493583, -0.00016186168197801615, - -0.00002721321876169871, - 0.00001931008575686296, - 0.00008276307386294178, - 0.00008275570358704334, - -0.000013474139110407536, - -0.00004620657569694677, - 0.00005317222102893866, + -2.721321876169871e-05, + 1.931008575686296e-05, + 8.276307386294178e-05, + 8.275570358704334e-05, + -1.3474139110407536e-05, + -4.620657569694677e-05, + 5.317222102893866e-05, 0.0001860671224343092, 0.00021621929290466715, 0.00010158693542714134, - -0.00004431204902445589, + -4.431204902445589e-05, 0.0004859964349120158, 0.002154019938190798, 0.0015588581936664704, @@ -123166,17 +123168,17 @@ -0.0010059602342009817, -0.0005220566625566923, -0.00026101391349563383, - -0.00004069656524434854, - 0.00001618006390827318, - 0.00008587532468015891, + -4.069656524434854e-05, + 1.618006390827318e-05, + 8.587532468015891e-05, 0.00010331376249001868, - 0.00001065666249606914, - -0.000049469240927358694, - 0.00002517673317394697, + 1.065666249606914e-05, + -4.9469240927358694e-05, + 2.517673317394697e-05, 0.00016156445769905438, 0.00022867941754156545, 0.0001661793937015322, - 0.000007430293321146101, + 7.430293321146101e-06, 0.0001866675749948413, 0.0017923889097294643, 0.003827317446159146, @@ -123196,17 +123198,17 @@ -0.0010475670901452612, -0.0005277718455372932, -0.0002465598598397578, - -0.00003907971338719106, - 0.00001522036217070957, - 0.0000801483572174688, + -3.907971338719106e-05, + 1.522036217070957e-05, + 8.01483572174688e-05, 0.00010237912591241945, - 0.000009395012369833265, - -0.00005251599831064238, - 0.00001895793653715204, + 9.395012369833265e-06, + -5.251599831064238e-05, + 1.895793653715204e-05, 0.0001558942578731162, 0.00022799154654474493, 0.00016208331373410276, - 0.000004533402918837487, + 4.533402918837487e-06, 0.00018923881269285154, 0.0017775580127024598, 0.0038289174861765906, @@ -123227,18 +123229,18 @@ -0.002712595851663308, -0.001302118761648602, -0.0004928971717987471, - -0.00008440057928217224, - 0.000007910167536295255, - 0.00007133929046053262, + -8.440057928217224e-05, + 7.910167536295255e-06, + 7.133929046053262e-05, 0.00010586962286379346, - 0.000026818914744396342, - -0.00005133204360401289, - -0.000008392166583649348, + 2.6818914744396342e-05, + -5.133204360401289e-05, + -8.392166583649348e-06, 0.00011865705140089023, 0.00021580524879678427, 0.0001913704000444878, - 0.000023610814277570582, - -0.000019110696172731684, + 2.3610814277570582e-05, + -1.9110696172731684e-05, 0.0009177294612654249, 0.004023227049705329, 0.005451896735177843, @@ -123257,18 +123259,18 @@ -0.002761709154211295, -0.0013102921718266907, -0.0004806078599023749, - -0.00008462685411762209, - 0.0000066333100235624275, - 0.00006505781061952432, + -8.462685411762209e-05, + 6.6333100235624275e-06, + 6.505781061952432e-05, 0.00010645277496719905, - 0.00002666592078427324, - -0.00005363261294837124, - -0.000013788457765165435, + 2.666592078427324e-05, + -5.363261294837124e-05, + -1.3788457765165435e-05, 0.00011463654978939191, 0.0002154671686229057, 0.00018593095288191167, - 0.00002013923987362917, - -0.00001994635925171227, + 2.013923987362917e-05, + -1.994635925171227e-05, 0.0008950912715618767, 0.004020522138676913, 0.0054878577247523316, @@ -123289,16 +123291,16 @@ -0.0024581281707198666, -0.0009630831366542706, -0.00020928204424186105, - -0.000012419170839712095, - 0.00005186351760521543, - 0.00009754633864401213, - 0.000037913975873618216, - -0.000046412517033066475, - -0.000037181662177477736, - 0.00007081747857383743, + -1.2419170839712095e-05, + 5.186351760521543e-05, + 9.754633864401213e-05, + 3.7913975873618216e-05, + -4.6412517033066475e-05, + -3.7181662177477736e-05, + 7.081747857383743e-05, 0.00018593081581133337, 0.00019874782398089514, - 0.00002228769033964516, + 2.228769033964516e-05, -0.00016593184154165051, 0.00012948665392314393, 0.0023854330648162086, @@ -123319,16 +123321,16 @@ -0.0024694126381732875, -0.0009536295223087275, -0.0002117632919126234, - -0.00001445009324800419, - 0.000045154736320492556, + -1.445009324800419e-05, + 4.5154736320492556e-05, 0.00010010425871044738, - 0.00003855817119100886, - -0.0000483510412423785, - -0.00004161963480641758, - 0.0000687384346163538, + 3.855817119100886e-05, + -4.83510412423785e-05, + -4.161963480641758e-05, + 6.87384346163538e-05, 0.00018585046292856315, 0.00019208347498316406, - 0.00001831831036673738, + 1.831831036673738e-05, -0.00017094294138512804, 0.00010003717824965503, 0.002380310939459003, @@ -123350,16 +123352,16 @@ -0.003538968975079464, -0.0016417064097742013, -0.0004567229691698327, - -0.00005979062407060809, - 0.00003222411041277198, - 0.00008709590003679889, - 0.0000459795338983734, - -0.00003313177274631246, - -0.00005476647203124283, - 0.000025119701480299244, + -5.979062407060809e-05, + 3.222411041277198e-05, + 8.709590003679889e-05, + 4.59795338983734e-05, + -3.313177274631246e-05, + -5.476647203124283e-05, + 2.5119701480299244e-05, 0.00014608185394616858, 0.00019745775996615235, - 0.00003293951763687537, + 3.293951763687537e-05, -0.0002635819527937099, -0.00040949051986999773, 0.000545628721619877, @@ -123380,16 +123382,16 @@ -0.0035539764696835645, -0.001635736659390217, -0.00046193392211336243, - -0.00006295086522389685, - 0.000025488323819887053, - 0.0000921531904789385, - 0.00004694007041126463, - -0.00003509776331044971, - -0.00005807062305992007, - 0.000025157500501480114, + -6.295086522389685e-05, + 2.5488323819887053e-05, + 9.21531904789385e-05, + 4.694007041126463e-05, + -3.509776331044971e-05, + -5.807062305992007e-05, + 2.5157500501480114e-05, 0.00014611621507156972, 0.0001897999850914219, - 0.00002849004731167058, + 2.849004731167058e-05, -0.0002733739041934037, -0.00044389032270234326, 0.0005409963429390057, @@ -123412,15 +123414,15 @@ -0.002290316473006141, -0.0008123559860954155, -0.00014716038454691148, - 0.00001254102893790495, - 0.00008064329848250237, - 0.00005439476409812897, - -0.000011636506167751893, - -0.00005627810771476377, - -0.00001024992230898274, + 1.254102893790495e-05, + 8.064329848250237e-05, + 5.439476409812897e-05, + -1.1636506167751893e-05, + -5.627810771476377e-05, + -1.024992230898274e-05, 0.0001048305538926932, 0.00018708091946759002, - 0.00006917155697217002, + 6.917155697217002e-05, -0.00028917333832518963, -0.0007504589742433654, -0.0007286604837629363, @@ -123442,15 +123444,15 @@ -0.0022884766804069727, -0.0008208020142217087, -0.0001517038289459559, - 0.000006467196216962577, - 0.00008870361261637533, - 0.00005502375689462837, - -0.000013930874356801998, - -0.00005822696296057812, - -0.000008076108022422945, + 6.467196216962577e-06, + 8.870361261637533e-05, + 5.502375689462837e-05, + -1.3930874356801998e-05, + -5.822696296057812e-05, + -8.076108022422945e-06, 0.00010480419439394863, 0.00017876728687644925, - 0.00006417630619349893, + 6.417630619349893e-05, -0.0003040376309656669, -0.000787234365127498, -0.0007298551932271014, @@ -123463,7 +123465,7 @@ -0.018661952488107012, 0.006686102209999031, -0.027715092084415378, - 0.00008994407945660601, + 8.994407945660601e-05, 0.0201762614582395, 0.02674053624447348, 0.02025725114272465, @@ -123473,13 +123475,13 @@ -0.002496162262110475, -0.0011616148483494248, -0.00026588331930189863, - -0.000011177663607001678, - 0.00008120955489688868, - 0.00006399862784176364, - 0.000014940046074589811, - -0.000040467552346377794, - -0.000029127779056091116, - 0.0000687271067590013, + -1.1177663607001678e-05, + 8.120955489688868e-05, + 6.399862784176364e-05, + 1.4940046074589811e-05, + -4.0467552346377794e-05, + -2.9127779056091116e-05, + 6.87271067590013e-05, 0.00016963582099413174, 0.0001182375075187771, -0.0002269428960652011, @@ -123493,7 +123495,7 @@ -0.01999749805003121, -0.040529891030096264, -0.0278437202367414, - -0.0000872223276453428, + -8.72223276453428e-05, 0.020043043981599167, 0.026575136619757474, 0.020183267424813855, @@ -123503,13 +123505,13 @@ -0.002499073491758563, -0.0011737664451246022, -0.00027188899548699484, - -0.000015643775122398096, - 0.0000926285134052127, - 0.00006353066985652917, - 0.000012233674893410583, - -0.000040807144133960095, - -0.000024996842747344456, - 0.0000684691384427065, + -1.5643775122398096e-05, + 9.26285134052127e-05, + 6.353066985652917e-05, + 1.2233674893410583e-05, + -4.0807144133960095e-05, + -2.4996842747344456e-05, + 6.84691384427065e-05, 0.00016111276470032505, 0.00011256424420434306, -0.0002466936254267836, @@ -123534,13 +123536,13 @@ -0.0018592732208767837, -0.0012925759184959462, -0.0003645275331134934, - -0.00003818117229896704, - 0.0000869678442666678, - 0.00007376492262353597, - 0.00004164595802755769, - -0.00001358785816689592, - -0.0000278102273575132, - 0.00004000264299884988, + -3.818117229896704e-05, + 8.69678442666678e-05, + 7.376492262353597e-05, + 4.164595802755769e-05, + -1.358785816689592e-05, + -2.78102273575132e-05, + 4.000264299884988e-05, 0.00014837762476249303, 0.00016204191527750784, -0.00010191851329189163, @@ -123564,13 +123566,13 @@ -0.0018674442566278054, -0.0013087825324673156, -0.0003718633977267486, - -0.00003992093120108438, + -3.992093120108438e-05, 0.000101800640020508, - 0.00007142379527878126, - 0.00003879875215505565, - -0.000012084694970149552, - -0.000022136902980005756, - 0.000039404011381726776, + 7.142379527878126e-05, + 3.879875215505565e-05, + -1.2084694970149552e-05, + -2.2136902980005756e-05, + 3.9404011381726776e-05, 0.0001402125621255135, 0.00015554186231305696, -0.00012579550745019113, @@ -123595,16 +123597,16 @@ -0.00015919261760155524, -0.0009678831172329137, -0.00034268224307677753, - -0.000053365584007573626, - 0.00009745860380104722, - 0.00008071079071110211, - 0.00006257888585218957, - 0.000015680703561285897, - -0.000010948675479736792, - 0.000020507126280250226, + -5.3365584007573626e-05, + 9.745860380104722e-05, + 8.071079071110211e-05, + 6.257888585218957e-05, + 1.5680703561285897e-05, + -1.0948675479736792e-05, + 2.0507126280250226e-05, 0.0001222806264134535, 0.00019315975733490202, - 0.00003181330463190011, + 3.181330463190011e-05, -0.0007306849042935898, -0.0020482839690022466, -0.003141428794788242, @@ -123625,16 +123627,16 @@ -0.00017285698664755162, -0.000988292998474554, -0.0003509951668500503, - -0.000051216784560097857, + -5.1216784560097857e-05, 0.00011530353361385657, - 0.00007585991779196303, - 0.0000603090237115178, - 0.000019129365026140797, - -0.000004409074967754963, - 0.000019577018638939343, + 7.585991779196303e-05, + 6.03090237115178e-05, + 1.9129365026140797e-05, + -4.409074967754963e-06, + 1.9577018638939343e-05, 0.0001151489533434576, 0.00018571157556774887, - 0.000005097438340089726, + 5.097438340089726e-06, -0.0007565650276533777, -0.0020344739429363516, -0.0031239368442688674, @@ -123654,16 +123656,16 @@ 0.013776863464074546, 0.00760894419125022, 0.002562956216200613, - -0.000017070261076416776, - -0.00006739259013684347, - -0.000022921185474949302, + -1.7070261076416776e-05, + -6.739259013684347e-05, + -2.2921185474949302e-05, 0.00012151908141561259, - 0.00008062207687587272, - 0.00007704800667118655, - 0.000035264357415879784, - 0.00001452954053759691, - 0.000008991734385600924, - 0.00008845921710635567, + 8.062207687587272e-05, + 7.704800667118655e-05, + 3.5264357415879784e-05, + 1.452954053759691e-05, + 8.991734385600924e-06, + 8.845921710635567e-05, 0.00021284035796759334, 0.00013019632728477937, -0.0004877386613334906, @@ -123684,16 +123686,16 @@ 0.01369359698958747, 0.00756506477283847, 0.0025440765860832047, - -0.00004160063966122691, - -0.00007612851091848234, - -0.000015834704784260247, + -4.160063966122691e-05, + -7.612851091848234e-05, + -1.5834704784260247e-05, 0.00014138171512069597, - 0.00007291767715147323, - 0.0000765023544289482, - 0.00004047522686397659, - 0.00002101061489587523, - 0.000007877101186577501, - 0.00008304278955368507, + 7.291767715147323e-05, + 7.65023544289482e-05, + 4.047522686397659e-05, + 2.101061489587523e-05, + 7.877101186577501e-06, + 8.304278955368507e-05, 0.00020432316483627645, 0.0001022150836537721, -0.0005059153306993385, @@ -123719,12 +123721,12 @@ 0.0005800570168561173, 0.00010974212710143191, 0.00017716211256493607, - 0.00007704578654318972, - 0.00008415756046012094, - 0.00004222894134176962, - 0.00003560165728259411, - 0.000006232226914834806, - 0.000046166657705473354, + 7.704578654318972e-05, + 8.415756046012094e-05, + 4.222894134176962e-05, + 3.560165728259411e-05, + 6.232226914834806e-06, + 4.6166657705473354e-05, 0.00021950752743375517, 0.00018807303906473384, -0.00029021439526258314, @@ -123749,12 +123751,12 @@ 0.0005716029620960249, 0.00012254158268161362, 0.0001973652779565905, - 0.00006653584608676394, - 0.00008673932706389363, - 0.00004859797129594033, - 0.000040959341934209874, - 0.000005195335416892347, - 0.00004294907190031701, + 6.653584608676394e-05, + 8.673932706389363e-05, + 4.859797129594033e-05, + 4.0959341934209874e-05, + 5.195335416892347e-06, + 4.294907190031701e-05, 0.00020969610118993663, 0.0001603064580836071, -0.00030115312505690604, @@ -123780,12 +123782,12 @@ 0.0016684049885441613, 0.0003985954964804345, 0.0003022723914094728, - 0.00007341076494577037, - 0.00009417793814952974, - 0.00003410984064352461, - 0.00004645225706459922, - 0.000010522359829788458, - -9.134822955181653e-7, + 7.341076494577037e-05, + 9.417793814952974e-05, + 3.410984064352461e-05, + 4.645225706459922e-05, + 1.0522359829788458e-05, + -9.134822955181653e-07, 0.00021300725596983154, 0.00021854822985756708, -0.00019788495281958284, @@ -123810,12 +123812,12 @@ 0.0016610032994808066, 0.00041746161003605525, 0.0003204413748571439, - 0.000060532294846619175, + 6.0532294846619175e-05, 0.00010122413981248164, - 0.00004059160574856166, - 0.000049730323736849855, - 0.000009913444307075569, - -0.0000018442988385940798, + 4.059160574856166e-05, + 4.9730323736849855e-05, + 9.913444307075569e-06, + -1.8442988385940798e-06, 0.0002015466527097892, 0.000192122274771169, -0.00020379342367902266, @@ -123841,12 +123843,12 @@ 0.003117149872219853, 0.000883889032207229, 0.0005296327553288311, - 0.00009011798678128531, + 9.011798678128531e-05, 0.0001110844052058475, - 0.00002110611240915456, - 0.0000450997234011812, - 0.000014498236648843699, - -0.00003169540948081876, + 2.110611240915456e-05, + 4.50997234011812e-05, + 1.4498236648843699e-05, + -3.169540948081876e-05, 0.00017362722515913681, 0.00026311003872474454, -0.00022612188988796765, @@ -123871,12 +123873,12 @@ 0.0031114864901201043, 0.000908614151005947, 0.000542802846173802, - 0.00007558358526403805, + 7.558358526403805e-05, 0.00012344565228456005, - 0.00002635726182334004, - 0.00004577167158327996, - 0.000014713449551974324, - -0.00003070052954597193, + 2.635726182334004e-05, + 4.577167158327996e-05, + 1.4713449551974324e-05, + -3.070052954597193e-05, 0.0001602962720336473, 0.00023898345777697308, -0.0002300493330235782, @@ -123904,11 +123906,11 @@ 0.0009011659861724148, 0.00012327774664153512, 0.00016706252520764449, - -0.0000106821961133708, - 0.000058101394643892453, - -0.0000044743699037912165, - -0.000022500345378319824, - 0.0000973453544445043, + -1.06821961133708e-05, + 5.8101394643892453e-05, + -4.4743699037912165e-06, + -2.2500345378319824e-05, + 9.73453544445043e-05, 0.0003258641581214152, -0.0003053352629853737, -0.0002213859203836424, @@ -123934,11 +123936,11 @@ 0.0009060940151148882, 0.00010790684843285192, 0.00018467450093994611, - -0.00000803711885665304, - 0.00005627134047620577, - -0.000003156400444188837, - -0.000020348755757134733, - 0.0000824881720440792, + -8.03711885665304e-06, + 5.627134047620577e-05, + -3.156400444188837e-06, + -2.0348755757134733e-05, + 8.24881720440792e-05, 0.00030536571396474663, -0.0003096734609832354, -0.00024242444692864823, @@ -123965,15 +123967,15 @@ 0.0013475634456577429, 0.00021490153537506728, 0.00023814776354049162, - -0.000026360096689297138, - 0.00006820315860875555, - -0.000026078877411315183, - 0.000006523716020091063, - 0.000016598547612472017, + -2.6360096689297138e-05, + 6.820315860875555e-05, + -2.6078877411315183e-05, + 6.523716020091063e-06, + 1.6598547612472017e-05, 0.00037529416890929115, -0.0003500319269982639, -0.0002234762343958937, - -0.00006902668472567452, + -6.902668472567452e-05, -0.0007465114393410358, -0.003465104369942021, -0.008536705313668576, @@ -123995,15 +123997,15 @@ 0.0013413504244373887, 0.00019952923203018087, 0.00025972854998793576, - -0.000027441293477184283, - 0.00006452634299086549, - -0.000023801824766672108, - 0.000008630681970516188, - 0.0000011751908080215349, + -2.7441293477184283e-05, + 6.452634299086549e-05, + -2.3801824766672108e-05, + 8.630681970516188e-06, + 1.1751908080215349e-06, 0.0003601863732316012, -0.00035559161360698493, -0.0002486507745581909, - -0.00008161302542843217, + -8.161302542843217e-05, -0.0007597341070825978, -0.003484716310276874, -0.008585655232437737, @@ -124026,11 +124028,11 @@ 0.001725467508621258, 0.00034378779205558184, 0.00032441137133348114, - -0.000025292096509495465, - 0.00008063608636768504, - -0.00004444678214019513, - 0.00003406024098613321, - -0.00003549328805802212, + -2.5292096509495465e-05, + 8.063608636768504e-05, + -4.444678214019513e-05, + 3.406024098613321e-05, + -3.549328805802212e-05, 0.00037422142344837667, -0.000300567861374545, -0.0003638239508654477, @@ -124056,11 +124058,11 @@ 0.0017065353275449093, 0.0003294202450151906, 0.000347498008163559, - -0.000030719891918134376, - 0.0000759978130601078, - -0.00004194741785249632, - 0.00003453835537968979, - -0.00005051394323696344, + -3.0719891918134376e-05, + 7.59978130601078e-05, + -4.194741785249632e-05, + 3.453835537968979e-05, + -5.051394323696344e-05, 0.0003655263812316745, -0.0003074882780962872, -0.0003868442085490473, @@ -124087,11 +124089,11 @@ 0.001702064869183499, 0.0004988827255428155, 0.0003551253879058926, - 0.00003935636882132775, - 0.0000467073688892825, - -0.000008289368260832075, - -0.0000022314605830573014, - -0.000003739969616115408, + 3.935636882132775e-05, + 4.67073688892825e-05, + -8.289368260832075e-06, + -2.2314605830573014e-06, + -3.739969616115408e-06, 0.00027445955266749153, -0.00012352923282738805, -0.0005804902083120448, @@ -124117,11 +124119,11 @@ 0.0016712672249758192, 0.0004871386511387651, 0.0003767100698717946, - 0.000029719633614321375, - 0.00004187642735568352, - -0.000006585064208394143, - -0.000004777657645871742, - -0.00001794168386607071, + 2.9719633614321375e-05, + 4.187642735568352e-05, + -6.585064208394143e-06, + -4.777657645871742e-06, + -1.794168386607071e-05, 0.00027077169389210675, -0.00013354248377997791, -0.0005983892548018132, @@ -124149,11 +124151,11 @@ 0.0006354396930716823, 0.000238434665688134, 0.00020792896840152704, - -0.00008211228481675678, + -8.211228481675678e-05, 0.00012274164255728583, -0.0001448834995006094, 0.00013604875154436097, - 0.00006473572565186749, + 6.473572565186749e-05, 0.0001556368197122284, -0.0008029441430536739, 0.0008493250402157956, @@ -124179,11 +124181,11 @@ 0.000628941627079766, 0.0002562559216138715, 0.00019537543250344044, - -0.00008661131624961913, + -8.661131624961913e-05, 0.00012306258859107454, -0.0001505646615812403, 0.0001230113903543484, - 0.00006217639273629182, + 6.217639273629182e-05, 0.00013829855478470727, -0.0008185067666565797, 0.0008456052957311767, @@ -124238,7 +124240,7 @@ -0.0026227936461778404, -0.0005022225108144793, 0.0007240283903104413, - -0.00008620079444276996, + -8.620079444276996e-05, 0.00048098280502073554, -0.00033220450298071183, 0.00035299002410386194, @@ -124439,7 +124441,7 @@ 0.0034108845133496205, 0.0013699412405189515, 0.000983218159372196, - -0.000014790873523479447, + -1.4790873523479447e-05, 0.001865865121455892, -0.004164473588889118, -0.002591589720686455, @@ -124469,7 +124471,7 @@ -0.0009145095558597478, 0.003074446147780475, 0.0009993074979132472, - -0.000029787997536025223, + -2.9787997536025223e-05, 0.0018880664260113925, -0.004139745872793554, -0.0025423735000640246, @@ -124579,10 +124581,10 @@ -0.0009820315120362974, 0.00039165334308406815, -0.00024964024558913727, - -0.00004191425922627831, - 0.00008899099936241231, + -4.191425922627831e-05, + 8.899099936241231e-05, -0.00010235065541692751, - 0.0000544378828683504, + 5.44378828683504e-05, 0.0002062012629459579, -0.000523012595114458, 0.0014638865062082751, @@ -124609,10 +124611,10 @@ -0.0009568502805579387, 0.00040589506931613706, -0.00023183316545064835, - -0.0000021345437964130274, + -2.1345437964130274e-06, 0.00011847041256181667, - -0.00007249969603818072, - 0.0000734428400139941, + -7.249969603818072e-05, + 7.34428400139941e-05, 0.00022991282517497027, -0.0005104169208934135, 0.0014792639314248686, @@ -124644,7 +124646,7 @@ 0.0005304229581553299, -0.00043390238441457543, 0.0005347592208118661, - -0.00004657239489539873, + -4.657239489539873e-05, -0.00021270590058702844, 0.0013190557527816044, -0.0011957125785418267, @@ -124674,7 +124676,7 @@ 0.0005651548692794127, -0.0003986559915996011, 0.0005491526261858314, - -0.00003950807573454177, + -3.950807573454177e-05, -0.00020970789865215869, 0.0013553456458148122, -0.0011776875857658626, @@ -124689,7 +124691,7 @@ 0.0009199694693133083, -0.0016586001307823256, -0.001271808214666114, - -0.00009787743374778867, + -9.787743374778867e-05, -0.001978337223706829, 0.0020495899510899226, -0.000964375288086091, @@ -124743,7 +124745,7 @@ -0.0014532290558713816, 0.0021104833644021116, 0.0002398613311028758, - 0.00002388304808366239, + 2.388304808366239e-05, -0.0017761315205484828, 0.001702929559231063, -0.00307223482780385, @@ -124758,7 +124760,7 @@ -0.0014177750654665354, 0.0013110389262552884, -0.0008932715852702762, - -0.000014450715197664777, + -1.4450715197664777e-05, 0.0008640317279309425, -0.001339138761492504, 0.0013968270690436471, @@ -124766,21 +124768,21 @@ 0.0010327932617344947, -0.0013123809690656601, 0.00159446816645886, - -0.00005090502303914206, + -5.090502303914206e-05, 0.0006413483239790802, 0.0013617559707380226, -0.001074198091439508, 0.00303849843840996, -0.00174823463278538, 0.0018136898860299268, - 0.000011737583498852127, + 1.1737583498852127e-05, -0.0017836612516448705, 0.0017270726599753227, -0.003051101765181599, 0.0010693262352114926, -0.0013898888098120296, -0.0006423949765085202, - 0.00006959032301338765, + 6.959032301338765e-05, -0.0015927485565331648, 0.0012767186196644621, -0.0010648766293410079, @@ -124788,7 +124790,7 @@ -0.0014161052675511726, 0.001320780314203919, -0.0008806543426718917, - 0.000014151496991282207, + 1.4151496991282207e-05, 0.0008977214622479503, -0.001310006526306834, 0.0014295358089593915, @@ -124796,14 +124798,14 @@ 0.0010758799957768603, -0.0012736453178153263, 0.001596437801779805, - -0.00009010970756998199, + -9.010970756998199e-05, 0.0006384823137891815, 0.0014077962176553302, -0.0010851464304130096, 0.0030975010252521553, -0.0016913442293175285, 0.0018265665707021816, - 0.00002388304808366239 + 2.388304808366239e-05 ], "j": [ 1, @@ -159981,7 +159983,7 @@ 1.5702661059856742 ], "y": [ - 0.00008514652798973567, + 8.514652798973567e-05, 0.0007014367562974889, 0.0013039674685636693, 0.0018795526391164065, @@ -160781,7 +160783,7 @@ -0.001708596121576049, -0.0011333571221742768, -0.0005310607514237456, - 0.00008514652798968813 + 8.514652798968813e-05 ], "z": [ -0.0030377018479799786, @@ -161857,7 +161859,7 @@ 0.01301885783169949, 0.008644866115003005, 0.004327287338398018, - 0.00006737996074820977, + 6.737996074820977e-05, -0.00413607530766903, -0.008286687858502178, -0.012390260707297334, @@ -164277,7 +164279,7 @@ 0.014091657862638038, 0.009405684707848836, 0.004739901996067019, - 0.0000880415353053788, + 8.80415353053788e-05, -0.004555636330969567, -0.009196089397968116, -0.013837285874374588, @@ -180775,7 +180777,7 @@ 0.02925060410808682, 0.019453153914263503, 0.009697964351379158, - -0.00002343472515756539, + -2.343472515756539e-05, -0.009720908327104693, -0.019405054472328976, -0.0290865228359971, @@ -195682,7 +195684,7 @@ 0.01294713245057154, 0.00865803871179721, 0.004330501388347376, - -0.00003323980320794893, + -3.323980320794893e-05, -0.004429396769942476, -0.00885280855763829, -0.013297179446929704, @@ -198879,7 +198881,7 @@ ], "metadata": { "kernelspec": { - "display_name": "desc-env", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, @@ -198893,7 +198895,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.8" + "version": "3.13.2" } }, "nbformat": 4, diff --git a/tests/conftest.py b/tests/conftest.py index c5c489e192..f7ae700ff4 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -367,6 +367,7 @@ def regcoil_helical_coils_scan(): current_helicity=(1 * eq.NFP, -1), vacuum=True, regularization_type="regcoil", + chunk_size=20, ) surface_current_field = fields[0] return (data, surface_current_field, eq) @@ -401,6 +402,7 @@ def regcoil_modular_coils(): lambda_regularization=lambda_regularization, regularization_type="regcoil", vacuum=True, + chunk_size=20, ) surface_current_field = surface_current_field[0] @@ -440,6 +442,7 @@ def regcoil_windowpane_coils(): vacuum=True, current_helicity=(0, 0), external_field=ToroidalMagneticField(B0=G, R0=1), + chunk_size=20, ) surface_current_field = surface_current_field[0] @@ -479,6 +482,7 @@ def regcoil_PF_coils(): vacuum=True, current_helicity=(0, 1), external_field=ToroidalMagneticField(B0=G, R0=1), + chunk_size=20, ) surface_current_field = surface_current_field[0] diff --git a/tests/inputs/master_compute_data_rpz.pkl b/tests/inputs/master_compute_data_rpz.pkl index e527d25b2e..0acc226633 100644 Binary files a/tests/inputs/master_compute_data_rpz.pkl and b/tests/inputs/master_compute_data_rpz.pkl differ diff --git a/tests/test_examples.py b/tests/test_examples.py index e7caa58ec4..f9cec52b2f 100644 --- a/tests/test_examples.py +++ b/tests/test_examples.py @@ -1238,6 +1238,7 @@ def test_regcoil_axisymmetric(): vacuum=True, verbose=2, regularization_type="regcoil", + chunk_size=20, ) # is a list of length one, index into it surface_current_field = surface_current_field[0] @@ -1250,7 +1251,9 @@ def test_regcoil_axisymmetric(): B = coords["B"] coords = np.vstack([coords["R"], coords["phi"], coords["Z"]]).T B_from_surf = surface_current_field.compute_magnetic_field( - coords, source_grid=LinearGrid(M=200, N=200, NFP=surf_winding.NFP) + coords, + source_grid=LinearGrid(M=200, N=200, NFP=surf_winding.NFP), + chunk_size=20, ) np.testing.assert_allclose(B, B_from_surf, rtol=1e-4, atol=1e-8) @@ -1269,6 +1272,7 @@ def test_regcoil_axisymmetric(): lambda_regularization=1e4, vacuum=True, regularization_type="simple", + chunk_size=20, ) surface_current_field = surface_current_field[0] phi_mn_opt = surface_current_field.Phi_mn @@ -1278,7 +1282,9 @@ def test_regcoil_axisymmetric(): surface_current_field.compute("Phi", grid=grid)["Phi"], correct_phi, atol=1e-16 ) B_from_surf = surface_current_field.compute_magnetic_field( - coords, source_grid=LinearGrid(M=200, N=200, NFP=surf_winding.NFP) + coords, + source_grid=LinearGrid(M=200, N=200, NFP=surf_winding.NFP), + chunk_size=20, ) np.testing.assert_allclose(B, B_from_surf, rtol=1e-4, atol=1e-8) @@ -1294,6 +1300,7 @@ def test_regcoil_axisymmetric(): # G is providing, in the same direction external_field=ToroidalMagneticField(B0=-mu_0 * (G / 2) / 2 / np.pi, R0=1), vacuum=True, + chunk_size=20, ) surface_current_field = surface_current_field[0] phi_mn_opt = surface_current_field.Phi_mn @@ -1306,7 +1313,9 @@ def test_regcoil_axisymmetric(): ) np.testing.assert_allclose(data["chi^2_B"][0], 0, atol=1e-11) B_from_surf = surface_current_field.compute_magnetic_field( - coords, source_grid=LinearGrid(M=200, N=200, NFP=surf_winding.NFP) + coords, + source_grid=LinearGrid(M=200, N=200, NFP=surf_winding.NFP), + chunk_size=20, ) np.testing.assert_allclose(B, B_from_surf * 2, rtol=1e-4, atol=1e-8) @@ -1332,6 +1341,7 @@ def test_regcoil_modular_check_B(regcoil_modular_coils): coords, source_grid=LinearGrid(M=60, N=60, NFP=surface_current_field.NFP), basis="rpz", + chunk_size=20, ) np.testing.assert_allclose(B, B_from_surf, rtol=5e-2, atol=5e-4) @@ -1359,6 +1369,7 @@ def test_regcoil_windowpane_check_B(regcoil_windowpane_coils): coords, source_grid=data["source_grid"], basis="rpz", + chunk_size=20, ) np.testing.assert_allclose(B, B_from_surf, rtol=1e-2, atol=5e-4) @@ -1450,11 +1461,13 @@ def test_regcoil_helical_coils_check_objective_method( coords, source_grid=sgrid, basis="rpz", + chunk_size=20, ) B_from_orig_surf = initial_surface_current_field.compute_magnetic_field( coords, source_grid=sgrid, basis="rpz", + chunk_size=20, ) np.testing.assert_allclose(B, B_from_surf, atol=6e-4, rtol=5e-2) np.testing.assert_allclose(B_from_orig_surf, B_from_surf, atol=1e-8, rtol=1e-8) diff --git a/tests/test_fast_ion.py b/tests/test_fast_ion.py index 34de79d6c7..81a6668d62 100644 --- a/tests/test_fast_ion.py +++ b/tests/test_fast_ion.py @@ -12,7 +12,6 @@ @pytest.mark.unit -@pytest.mark.slow @pytest.mark.mpl_image_compare(remove_text=True, tolerance=tol_1d) def test_Gamma_c_Nemov_2D(): """Test Γ_c Nemov with W7-X.""" @@ -36,7 +35,6 @@ def test_Gamma_c_Nemov_2D(): @pytest.mark.unit -@pytest.mark.slow @pytest.mark.mpl_image_compare(remove_text=True, tolerance=tol_1d) def test_Gamma_c_Velasco_2D(): """Test Γ_c Velasco with W7-X.""" diff --git a/tests/test_integrals.py b/tests/test_integrals.py index 4c4d405d59..a08f4aeb82 100644 --- a/tests/test_integrals.py +++ b/tests/test_integrals.py @@ -19,12 +19,14 @@ from desc.equilibrium import Equilibrium from desc.equilibrium.coords import get_rtz_grid from desc.examples import get +from desc.geometry import FourierRZToroidalSurface from desc.grid import ConcentricGrid, Grid, LinearGrid, QuadratureGrid from desc.integrals import ( Bounce1D, Bounce2D, DFTInterpolator, FFTInterpolator, + VacuumSolver, line_integrals, singular_integral, surface_averages, @@ -54,11 +56,13 @@ tanh_sinh, ) from desc.integrals.singularities import ( - _get_default_params, _get_quadrature_nodes, _kernel_nr_over_r3, + best_ratio, + heuristic_support_params, ) from desc.integrals.surface_integral import _get_grid_surface +from desc.magnetic_fields import ToroidalMagneticField from desc.transform import Transform from desc.utils import dot, errorif, safediv @@ -583,26 +587,30 @@ def test_surface_min_max(self): class TestSingularities: - """Tests for high order singular integration. - - Hyperparams from Dhairya for greens ID test: - - M q Nu Nv N=Nu*Nv error - 13 10 15 13 195 0.246547 - 13 10 30 13 390 0.0313301 - 13 12 45 13 585 0.0022925 - 13 12 60 13 780 0.00024359 - 13 12 75 13 975 1.97686e-05 - 19 16 90 19 1710 1.2541e-05 - 19 16 105 19 1995 2.91152e-06 - 19 18 120 19 2280 7.03463e-07 - 19 18 135 19 2565 1.60672e-07 - 25 20 150 25 3750 7.59613e-09 - 31 22 210 31 6510 1.04357e-09 - 37 24 240 37 8880 1.80728e-11 - 43 28 300 43 12900 2.14129e-12 - - """ + """Tests for high order singular integration.""" + + @pytest.mark.unit + @pytest.mark.parametrize("interpolator", [FFTInterpolator, DFTInterpolator]) + def test_biest_interpolators(self, interpolator): + """Test that FFT and DFT interpolation gives same result for standard grids.""" + grid = LinearGrid(0, *_f_2d_nyquist_freq()) + h_t = 2 * np.pi / grid.num_theta + h_z = 2 * np.pi / grid.num_zeta / grid.NFP + + st = 3 + sz = 5 + q = 4 + r, w, _, _ = _get_quadrature_nodes(q) + dt = st / 2 * h_t * r * np.sin(w) + dz = sz / 2 * h_z * r * np.cos(w) + + interp = interpolator(grid, grid, st, sz, q) + theta = grid.nodes[:, 1] + zeta = grid.nodes[:, 2] + f = _f_2d(theta, zeta) + for i in range(dt.size): + truth = _f_2d(theta + dt[i], zeta + dz[i]) + np.testing.assert_allclose(interp(f, i), truth) @pytest.mark.unit def test_singular_integral_greens_id(self): @@ -619,6 +627,22 @@ def test_singular_integral_greens_id(self): So we integrate the kernel n⋅(r-r')/|r-r'|³ and can benchmark the residual. + Hyperparams from Dhairya for greens ID test: + + M q Nu Nv N=Nu*Nv error + 13 10 15 13 195 0.246547 + 13 10 30 13 390 0.0313301 + 13 12 45 13 585 0.0022925 + 13 12 60 13 780 0.00024359 + 13 12 75 13 975 1.97686e-05 + 19 16 90 19 1710 1.2541e-05 + 19 16 105 19 1995 2.91152e-06 + 19 18 120 19 2280 7.03463e-07 + 19 18 135 19 2565 1.60672e-07 + 25 20 150 25 3750 7.59613e-09 + 31 22 210 31 6510 1.04357e-09 + 37 24 240 37 8880 1.80728e-11 + 43 28 300 43 12900 2.14129e-12 """ eq = Equilibrium() Nu = [13, 13, 13, 19, 19, 25, 37] @@ -643,7 +667,7 @@ def test_singular_integral_greens_id(self): Nv = 100 es = 6e-7 grid = LinearGrid(M=Nu // 2, N=Nv // 2, NFP=eq.NFP) - st, sz, q = _get_default_params(grid) + st, sz, q = heuristic_support_params(grid, best_ratio(data)[0]) interpolator = FFTInterpolator(grid, grid, st, sz, q) data = eq.compute(_kernel_nr_over_r3.keys + ["|e_theta x e_zeta|"], grid=grid) err = singular_integral(data, data, "nr_over_r3", interpolator, chunk_size=50) @@ -667,7 +691,7 @@ def test_singular_integral_vac_estell(self, interpolator): "|e_theta x e_zeta|", ] data = eq.compute(keys, grid=grid) - st, sz, q = _get_default_params(grid) + st, sz, q = heuristic_support_params(grid, best_ratio(data)[0]) interp = interpolator(grid, grid, st, sz, q) Bplasma = virtual_casing_biot_savart(data, data, interp, chunk_size=50) # need extra factor of B/2 bc we're evaluating on plasma surface @@ -675,30 +699,287 @@ def test_singular_integral_vac_estell(self, interpolator): Bplasma = np.linalg.norm(Bplasma, axis=-1) # scale by total field magnitude B = Bplasma / np.linalg.norm(data["B"], axis=-1).mean() - np.testing.assert_allclose(B, 0, atol=0.005) + np.testing.assert_allclose(B, 0, atol=0.0054) + + +class TestVacuumSolver: + """Test vacuum field solver.""" @pytest.mark.unit - @pytest.mark.parametrize("interpolator", [FFTInterpolator, DFTInterpolator]) - def test_biest_interpolators(self, interpolator): - """Test that FFT and DFT interpolation gives same result for standard grids.""" - grid = LinearGrid(0, *_f_2d_nyquist_freq()) - h_t = 2 * np.pi / grid.num_theta - h_z = 2 * np.pi / grid.num_zeta / grid.NFP + def test_harmonic_simple(self, chunk_size=1000, atol=1e-4): + """Test that Laplace solution recovers expected analytic result. + + Define boundary R_b(θ,ζ) = R₀ + a cos θ and Z_b(θ,ζ) = -a sin θ. + θ = 0 is outboard side and θ increases clockwise. + Define harmonic map Φ: ρ,θ,ζ ↦ Z(ρ,θ,ζ). + Choose b.c. B₀⋅n = -∇ϕ⋅n + = -[0, 0, 1]⋅[cos(θ)cos(ζ), cos(θ)sin(ζ), -sin(θ)] + = sin(θ) + and test that ‖ Φ − Z ‖_∞ → 0. + """ + a = 1 + surf = FourierRZToroidalSurface() # Choosing a = 1. + Phi_grid = LinearGrid(M=1, N=0, NFP=surf.NFP) + evl_grid = LinearGrid(M=5, N=5, NFP=surf.NFP) + src_grid = LinearGrid(M=50, N=50, NFP=surf.NFP) + + theta = src_grid.nodes[:, 1] + B0n = np.sin(theta) + vac = VacuumSolver( + surf, + None, + evl_grid, + Phi_grid, + src_grid, + interior=False, + chunk_size=chunk_size, + B0n=B0n, + warn_fft=False, + ) + np.testing.assert_allclose(vac._data["src"]["Z"], -a * np.sin(theta)) + np.testing.assert_allclose(vac._data["src"]["n_rho"][:, 2], -B0n, atol=1e-12) + + data = vac.compute_Phi_mn(chunk_size) + Z = data["evl"]["Z"] + Phi_mn = data["Phi"]["Phi_mn"] + Phi = Transform(evl_grid, vac._transform["Phi"].basis).transform(Phi_mn) + np.testing.assert_allclose(np.ptp(Z - Phi), 0, atol=atol) + + data = vac.compute_vacuum_field(chunk_size)["evl"].copy() + data = surf.compute(["n_rho", "theta"], grid=evl_grid, data=data) + dPhi_dn = dot(data["grad(Phi)"], data["n_rho"]) + B0n = np.sin(data["theta"]) + np.testing.assert_allclose(B0n + dPhi_dn, 0, atol=atol) - st = 3 - sz = 5 - q = 4 - r, w, _, _ = _get_quadrature_nodes(q) - dt = st / 2 * h_t * r * np.sin(w) - dz = sz / 2 * h_z * r * np.cos(w) + @pytest.mark.unit + def test_harmonic_general(self, chunk_size=1000, atol=1e-4): + """Test that Laplace solution recovers expected analytic result. - interp = interpolator(grid, grid, st, sz, q) - theta = grid.nodes[:, 1] - zeta = grid.nodes[:, 2] - f = _f_2d(theta, zeta) - for i in range(dt.size): - truth = _f_2d(theta + dt[i], zeta + dz[i]) - np.testing.assert_allclose(interp(f, i), truth) + Define boundary R_b(θ,ζ) and Z_b(θ,ζ). + Define harmonic map Φ: ρ,θ,ζ ↦ Z(ρ,θ,ζ). + Choose b.c. B₀⋅n = -∇ϕ⋅n and test that ‖ Φ − Z ‖_∞ → 0. + """ + # elliptic cross-section with torsion + surf = FourierRZToroidalSurface( + R_lmn=[10, 1, 0.2], + Z_lmn=[-2, -0.2], + modes_R=[[0, 0], [1, 0], [0, 1]], + modes_Z=[[-1, 0], [0, -1]], + ) + Phi_grid = LinearGrid(M=15, N=15, NFP=surf.NFP) + evl_grid = Phi_grid + src_grid = LinearGrid(M=50, N=50, NFP=surf.NFP) + + vac = VacuumSolver( + surf, + None, + evl_grid, + Phi_grid, + src_grid, + # Φ = Z, so this should be exact. + Phi_M=surf.M, + Phi_N=surf.N, + interior=False, + chunk_size=chunk_size, + B0n=-surf.compute("n_rho", grid=src_grid)["n_rho"][:, 2], + warn_fft=False, + ) + + data = vac.compute_Phi_mn(chunk_size) + Z = data["evl"]["Z"] + Phi_mn = data["Phi"]["Phi_mn"] + Phi = vac._transform["Phi"].transform(Phi_mn) + np.testing.assert_allclose(np.ptp(Z - Phi), 0, atol=atol) + + data = vac.compute_vacuum_field(chunk_size)["evl"].copy() + data = surf.compute("n_rho", grid=evl_grid, data=data) + B0n = -data["n_rho"][:, 2] + dPhi_dn = dot(data["grad(Phi)"], data["n_rho"]) + np.testing.assert_allclose(B0n + dPhi_dn, 0, atol=atol) + + @pytest.mark.unit + @pytest.mark.slow + def test_dommaschk_vacuum(self, chunk_size=50, atol=4e-4): + """Test computed vacuum field matches Dommaschk potential.""" + C_r = { + (0, -2): 0.000056, + (0, -1): -0.000921, + (0, 0): 0.997922, + (0, 1): -0.000921, + (0, 2): 0.000056, + (1, -2): -0.000067, + (1, -1): -0.034645, + (1, 0): 0.093260, + (1, 1): 0.000880, + (1, 2): 0.000178, + (2, -2): 0.000373, + (2, -1): 0.000575, + (2, 0): 0.002916, + (2, 1): -0.000231, + (2, 2): 0.000082, + (3, -2): 0.000462, + (3, -1): -0.001509, + (3, 0): 0.001748, + (3, 1): -0.000239, + (3, 2): 0.000052, + } + C_z = { + (0, -2): 0.000076, + (0, -1): 0.000923, + (0, 0): 0.000000, + (0, 1): -0.000923, + (0, 2): -0.000076, + (1, -2): 0.000069, + (1, -1): 0.035178, + (1, 0): 0.099830, + (1, 1): 0.000860, + (1, 2): -0.000179, + (2, -2): -0.000374, + (2, -1): 0.000257, + (2, 0): 0.003096, + (2, 1): 0.003021, + (2, 2): 0.000007, + (3, -2): -0.000518, + (3, -1): 0.002233, + (3, 0): 0.001828, + (3, 1): 0.000257, + (3, 2): 0.000035, + } + eq = Equilibrium(surface=self._merkel_surf(C_r, C_z)) + + Phi_grid = LinearGrid(M=20, N=20, NFP=eq.NFP if eq.N > 0 else 64) + evl_grid = Phi_grid + src_grid = LinearGrid(M=50, N=50, NFP=eq.NFP) + src_data = eq.compute(["G", "R0"], grid=src_grid) + B0 = ToroidalMagneticField( + B0=src_grid.compress(src_data["G"])[-1] / src_data["R0"], + R0=src_data["R0"], + ) + vac = VacuumSolver( + eq.surface, + B0, + evl_grid, + Phi_grid, + src_grid, + Phi_M=8, + Phi_N=8, + interior=False, + chunk_size=chunk_size, + warn_fft=False, + ) + data = vac.compute_magnetic_field(chunk_size)["evl"].copy() + data = eq.compute("n_rho", grid=evl_grid, data=data) + Bn = dot(data["B0+grad(Phi)"], data["n_rho"]) + np.testing.assert_allclose(Bn, 0, atol=atol) + + @staticmethod + def _merkel_surf(C_r, C_z): + """Convert merkel coefficients to DESC coefficients.""" + m_b = max( + max(abs(mn[0]) for mn in C_r.keys()), max(abs(mn[0]) for mn in C_z.keys()) + ) + n_b = max( + max(abs(mn[1]) for mn in C_r.keys()), max(abs(mn[1]) for mn in C_z.keys()) + ) + R_lmn = { + (m, n): 0.0 for m in range(-m_b, m_b + 1) for n in range(-n_b, n_b + 1) + } + Z_lmn = { + (m, n): 0.0 for m in range(-m_b, m_b + 1) for n in range(-n_b, n_b + 1) + } + for m in range(0, m_b + 1): + for n in range(-n_b, n_b + 1): + if (m, n) in C_r: + R_lmn[(m, abs(n))] += C_r[(m, n)] + if n < 0: + R_lmn[(-m, n)] += C_r[(m, n)] + elif n > 0: + R_lmn[(-m, -n)] -= C_r[(m, n)] + if (m, n) in C_z: + Z_lmn[(-m, abs(n))] += C_z[(m, n)] + if n < 0: + Z_lmn[(m, n)] -= C_z[(m, n)] + elif n > 0: + Z_lmn[(m, -n)] += C_z[(m, n)] + + grid = LinearGrid(rho=1, M=5, N=5) + R_bench = TestVacuumSolver._manual_transform( + np.array(list(R_lmn.values())), + np.array([mn[0] for mn in R_lmn.keys()]), + np.array([mn[1] for mn in R_lmn.keys()]), + -grid.nodes[:, 1], # theta is flipped + grid.nodes[:, 2], + ) + R_merk = TestVacuumSolver._merkel_transform( + np.array(list(C_r.values())), + np.array([mn[0] for mn in C_r.keys()]), + np.array([mn[1] for mn in C_r.keys()]), + -grid.nodes[:, 1], # theta is flipped + grid.nodes[:, 2], + ) + Z_bench = TestVacuumSolver._manual_transform( + np.array(list(Z_lmn.values())), + np.array([mn[0] for mn in Z_lmn.keys()]), + np.array([mn[1] for mn in Z_lmn.keys()]), + -grid.nodes[:, 1], # theta is flipped + grid.nodes[:, 2], + ) + Z_merk = TestVacuumSolver._merkel_transform( + np.array(list(C_z.values())), + np.array([mn[0] for mn in C_z.keys()]), + np.array([mn[1] for mn in C_z.keys()]), + -grid.nodes[:, 1], # theta is flipped + grid.nodes[:, 2], + fun=np.sin, + ) + np.testing.assert_allclose(R_bench, R_merk) + np.testing.assert_allclose(Z_bench, Z_merk) + with pytest.warns(UserWarning, match="Left handed"): + surf = FourierRZToroidalSurface( + R_lmn=list(R_lmn.values()), + Z_lmn=list(Z_lmn.values()), + modes_R=list(R_lmn.keys()), + modes_Z=list(Z_lmn.keys()), + ) + surf_data = surf.compute(["R", "Z"], grid=grid) + np.testing.assert_allclose(surf_data["R"], R_merk) + np.testing.assert_allclose(surf_data["Z"], Z_merk) + return surf + + @staticmethod + def _manual_transform(coef, m, n, theta, zeta): + """Evaluates Double Fourier Series of form G_n^m at theta and zeta pts.""" + op_four = np.where( + ((m < 0) & (n < 0))[:, np.newaxis], + np.sin(np.abs(m)[:, np.newaxis] * theta) + * np.sin(np.abs(n)[:, np.newaxis] * zeta), + n[:, np.newaxis] * zeta * np.nan, + ) + op_three = np.where( + ((m < 0) & (n >= 0))[:, np.newaxis], + np.sin(np.abs(m)[:, np.newaxis] * theta) * np.cos(n[:, np.newaxis] * zeta), + op_four, + ) + op_two = np.where( + ((m >= 0) & (n < 0))[:, np.newaxis], + np.cos(m[:, np.newaxis] * theta) * np.sin(np.abs(n)[:, np.newaxis] * zeta), + op_three, + ) + op_one = np.where( + ((m >= 0) & (n >= 0))[:, np.newaxis], + np.cos(m[:, np.newaxis] * theta) * np.cos(n[:, np.newaxis] * zeta), + op_two, + ) + return np.sum(coef[:, np.newaxis] * op_one, axis=0) + + @staticmethod + def _merkel_transform(coef, m, n, theta, zeta, fun=np.cos): + """Evaluates double Fourier series of form cos(m theta + n zeta).""" + return np.sum( + coef[:, np.newaxis] + * fun(m[:, np.newaxis] * theta + n[:, np.newaxis] * zeta), + axis=0, + ) class TestBouncePoints: diff --git a/tests/test_neoclassical.py b/tests/test_neoclassical.py index cc5b87190e..89046682f9 100644 --- a/tests/test_neoclassical.py +++ b/tests/test_neoclassical.py @@ -16,7 +16,6 @@ @pytest.mark.unit -@pytest.mark.slow @pytest.mark.mpl_image_compare(remove_text=True, tolerance=tol_1d) def test_effective_ripple_2D(): """Test effective ripple with W7-X against NEO."""